Flaskista Flask+Reactiin

  • Keskustelun aloittaja Keskustelun aloittaja Makis
  • Aloitettu Aloitettu
Nyt en kyllä ymmärrä, että miksei kirjoittamani testi toimi.

Komponentti ottaa vastaan parametrin short, sisältö on (kovasti pätkittynä) tällainen:
Koodi:
{
        "orig_title": "Robot Dreams",
        "genres": []
}
Nyt sitten testi kaatuu, koska genres on undefined (ja ei, ei auta vaikka lisään tuohon dataa). Siis short.genres.length tuottaa "Cannot read property 'length' of undefined".

Melkoisen työlästä saada testejä toimimaan. Koitin myös routea testata, mutta pitkän ihmettelyn jälkeen selvisi että CORS-ongelmasta kyse. En oikein tiedä, miten tuon voisi kiertää.

E. Vielä kummempaa: muut samanlaiset arrayt ei aiheuta ongelmia. Printtasin jopa tuon shortin sisällön:
Koodi:
      [
        {
          orig_title: 'Robot Dreams',
          authors: [ [Object] ],
          translators: [],
          tags: [],
          issues: [],
          type: { name: 'Novelli', id: 1 },
          title: 'Robottiunta',
          works: [ [Object] ],
          contributors: [ [Object] ],
          editions: [ [Object] ],
          genres: [ [Object] ],
          id: 1,
          pubyear: 1986
        }
      ]
Jos poistan genre-kohdan koodista, niin testi menee läpi. Ja siinä siis luetaan lengthiä muistakin arraystä.

E2. Ei kun eipä muuten toimikaan. Tuossa käytetään authorseja ja editionseja - jos on annettu tietty parametri. Jos annan tuollaisen parametrin niin kilahtaa sitten siihen kohtaan. Eli jotain häikkää noiden käsittelyssä nyt on ylipäätään.

E3. Ei kun mitä hittoa? Kaikki nuo kentät on komponentin mielestä ilmeisesti undefined kun screen.debug() tuottaa HTML:ää jossa ei ole yhtään noista arvoista. Mikään waitFor ei näytä auttavan.
 
Viimeksi muokattu:
Tuon routen sain toimimaan helpperifunktiolla:
Koodi:
export const renderWithRouter = (ui, { route = "/" } = {}) => {
  return {
    ...render(ui, { wrapper: BrowserRouter }),
  };
};
Mutta käsittääkseni tuo vaatii sen, että routella on parametri. Eli tuo ei vastaa sitä, että kutsutaan routea vaikkapa /publisher/1 -tyyliin, vaan pitää olla propsit myös. Mikä taas vaatii tietysti pientä puuhastelua komponentissa (tsekataan palauttaako useParams() jotain ja jos ei, niin sitten käytetään propsin arvoa). Vähän hölmöä lisätä testejä varten koodia tuotantosoftaan, mutta en keksi muutakaan konstia.
 
Pitänee raportoida tyhmätkin virheet. Toisessa casessa siis odotettiin sisään objectia. Olin määritellyt testiaineistoon arvon [{...}] ... huhheijaa. Ei sitten sattunut ennen tätä silmään. Tuon kun korjasi niin alkoi toimimaan.
 
On testien kirjoittaminen kyllä niin perseestä kuin voi olla. Vaikka luulet että olet jo jonkun tyyppisen testin saanut kehitettyä, niin ehei. Heti seuraavassa komponentissa, joka vaikuttaa olevan samantyyppinen, tulee joku kryptinen virhe. Kuten "arvon pitäisi olla html-elementti, mutta onkin merkkijono". Dafuq? Jos noin on, niin miten sovellus oikein toimii ylipäätään?
 
Aika jännää käytöstä kun käyttää useQueryä. Se muistaa ilmeisesti edellisen saman queryn sisällön vaikka sivulta käy pois, joten kun sille palaa, vaikka tarkoitus on renderöidä uusi sisältö, niin vanha näytetään kunnes uusi saadaan ladattua. En oikein keksi miten tuon saisi muutettua, koska onhan tuo vähän hölmöä. Luulin ensin että se johtui useMemo:sta jota käytin tuossa (ajatuksena siis se, että ei tarvitse lähettää joka välissä serverille kyselyä), mutta samaa tapahtuu ilmankin. Koodi on siis tällainen:
Koodi:
const fetchPubseries = async (id: string): Promise<Pubseries> => {
    const url = baseURL + id;
    const response = await getApiContent(url, null);
    return response.data;
}

export const PubseriesPage = ({ id }: PubseriesPageProps) => {
    const params = useParams();
    try {
        thisId = selectId(params, id);
    } catch (e) {
        console.log(`${e} bookseries`);
    }

    const data = useQuery("pubseries", () => fetchPubseries(thisId)).data;
    
    ...
    return (
                {data === undefined ? (
                <div className="progressbar">
                    <ProgressSpinner />
                </div>

            ) : (
            ...
    )
datalla on siis arvo aina sen jälkeen kun on kerran käynyt sivulla. Yritin myös määritellä tuota thisId:tä sille useMemo():lle depsinä, mutta se ei tuntunut vaikuttavan.
 
Aika jännä, vaikka muutin fetchin tällaiseksi:
Koodi:
    const fetchPubseries = async (id: string, user: User | null): Promise<Pubseries> => {
        const url = baseURL + id;
        const response = await getApiContent(url, user).then(response => {
            setLoading(false);
            return response.data;
        })
            .catch((error) => console.log(error));
        return response;
    }
ja tietysti vaihdoin jsx:stä data -> loading, niin edelleen tekee samalla tavalla. Vaikka sitä jotenkin kuvittelisi, että tuo loading asetetaan aina trueksi kun komponentti avataan ja asetetaan falseksi vasta kun data uusi saadaan haettua.
 
Tuo Reactin Discord-kanava muuten on alkanut tuntua joltain sisäpiirin rinkirunkulta. Olen muutaman kysymyksen esittänyt sinne ja niihin ei ole reagoitu _mitenkään_. Ihan kuin olisin shadowbannattu. Onkohan olemassa jotain nyyppäystävällistä paikkaa, jossa voi kysyä tyhmiä?
 
Selvisihän se lopulta itselläkin. Nyt pitäisi vissiin koittaa tajuta useMutation.
 
Aika jännää käytöstä kun käyttää useQueryä. Se muistaa ilmeisesti edellisen saman queryn sisällön vaikka sivulta käy pois, joten kun sille palaa, vaikka tarkoitus on renderöidä uusi sisältö, niin vanha näytetään kunnes uusi saadaan ladattua.
Ei tuossa ole mitään jännää, se on osa react-queryn core-toiminnallisuutta. Ongelma on että et avainna queryäsi oikein, jos haun tulos riippuu parametreista (id, filteri yms) niin ne pitää luetella queryn avaimessa:
Koodi:
const pubseries = useQuery(["pubseries", thisId], () => fetchPubseries(thisId));

Nyt esim id:t 1 ja 2 cachetetaan erikseen ja jos pyydetään id:ta 2 niin query ei palauta id:llä 1 cachetettu dataa. Cachetuksen voi toki myös estää kokonaan antamalla querylle optioissa { cacheTime: 0 }.

Erillinen loading-state on myös turha react-queryn kanssa, paikallisen loading-indikaattorin voit näyttää:
Koodi:
{pubseries.isLoading && <LoadingIndicator />}
{pubseries.isSuccess && <PubseriesComponent data={pubseries.data} />}

Globaalin loading-indikaattorin voit toteuttaa useIsFetching() ja useIsMutating() -hookkien kanssa, ne palauttavat keskeneräisten operaatioiden lukumäärän.

Oletuksena react-query hakee taustalla kaikki kyselyt uusiksi kun fokus palaa ikkunaan/tabiin, tuon käytännön voi estää globaalisti QueryClientProviderin asetuksissa.
 
Viimeksi muokattu:
Jaahas. Sain queryt toimimaan niin testit hajosivat. Ja noiden korjaaminen on ei-triviaali homma. Varmaan puolentusinaa artikkelia + tanstackin omat sivut + tanstackin koodit katsonut ja ei lähde. Reactifluxilla ei taaskaan näytä kukaan osaavan auttaa, joten liityin Tanstackin Discordiin. Ja jumalauta, pitäisi puhelimella vahvistaa identiteetti. Siis pitäisi lähettää puhelinnumero johonkin epämääräiseen paikkaan että pääsen esittämään kysymyksiä tuonne? Tänä aikana kun identiteettivarkaudet on yleisiä ja puhelinnumero on helppo väärentää?
 
Tuo ei käsittääkseni ole ongelma, sivua kyllä kutsutaan, mutta vaikka laitan kuinka pitkän odotuksen (tai waitFor:n) niin sivu ei päivity latausruudusta.
 
Sitä tässä kyllä ihmettelen, että millä näitä yksikkötestejä pitäisi tehdä, jos tarkoitus on testata muutakin kuin mitä käyttäjälle näkyy? Näyttää olevan periaate että näillä työkaluilla pitäisi aina tsekata käyttäjälle näkyvät jutut, eli esimerkiksi classin perusteella ei taida edes pystyä hakemaan elementtejä.
 
Sitä tässä kyllä ihmettelen, että millä näitä yksikkötestejä pitäisi tehdä, jos tarkoitus on testata muutakin kuin mitä käyttäjälle näkyy? Näyttää olevan periaate että näillä työkaluilla pitäisi aina tsekata käyttäjälle näkyvät jutut, eli esimerkiksi classin perusteella ei taida edes pystyä hakemaan elementtejä.
Yksikkötestit lienee parempi kirjoittaa vaan (pure) funktioille, etenkin jos löytyy utility-funkkareita. Eli pseudo-tyyliin:
JavaScript:
util.ts:
export const isOdd = (input: number): boolean => !!(input % 2)

component.tsx
import { isOdd } from 'utils';

const MyComponent = () => {
    const [ myNumber, setMyNumber ] = useState(0);
    return (
      <div>{ isOdd(myNumber) }</div>
    );
}
.

__tests__/utils.ts
import { isOdd } from 'utils';

describe('Is odd or even?', () => {
  it('should return true', () => {
      isOdd(1);
  });
});

Oletko tutustunut React Testing Library | Testing Library , jota hyödyntää komponenttitestaukseen?
 
Viimeksi muokattu:
Olipa konstikasta saada testit toimimaan. Käytännössä ensin piti tällaiset utility-funktiot luoda:
Koodi:
const createTestQueryClient = () => new QueryClient({
  defaultOptions: {
    queries: {
      retry: false,
    },
  },
  logger: {
    log: console.log,
    warn: console.warn,
    error: () => { },
  }
})

export function renderWithClient(ui: React.ReactElement) {
  const testQueryClient = createTestQueryClient()
  const { rerender, ...result } = render(
    <QueryClientProvider client={testQueryClient}>{ui}</QueryClientProvider>
  )
  return {
    ...result,
    rerender: (rerenderUi: React.ReactElement) => {
      rerender(
        <QueryClientProvider client={testQueryClient}>{rerenderUi}</QueryClientProvider>
      )
    }
  }
}
Ja sitten itse testit näyttää tällaisilta:
Koodi:
describe("PubseriesListPage", () => {
    it("renders PubserisListPage", async () => {
        const history = createMemoryHistory();
        const result = renderWithClient(
            <Router navigator={history} location="/">
                <PubseriesListPage />
            </Router>
        )
        await waitFor(() => expect(result.getByText(/Kustantajien sarjat/)).toBeDefined())
    })
})
Tuossa oli siis kaksi olennaista, ensinnäkin createMemoryHistory(). Jostain syystä tuon renderWithClientin (tai useQueryn?) kanssa pitikin alkaa käyttää Routea, aiemmin ei tarvinnut. Sillä taas navigator on pakollinen attribuutti.

Toiseksi esimerkeissä tuo expect oli tehty muotoon expect(await ...) ja se ei myöskään toiminut, vaan pitää käyttää waitFor():ia.
 
Jaahas, niin jumissa tuon useFormArrayn kanssa, että saa nähdä miten tästä pääsee eteenpäin. Tuossa on siis append ja remove-funktiot, mutta kun ajan nuo, niin array ei muutu mihinkään. Discord-kanavalla jengi ei joko osaa tai halua vastata. Toisella käyttäjällä on samantapainen ongelma eikä hänkään tunnu saavan apua. Olen debugannut tuota sinne komponenttiin ja sisäisesti näyttää siltä, että rivi lisätään/poistetaan, mutta sitten kuitenkin array on ennallaan kun suoritus palaa minun komponenttiin. Epäilen itse, että ehkä tuossa tapahtuu jotain redrawin kanssa, mutta vaatisi jo melko syvällistä Reactin ja React Hook Formin ymmärtämistä että homma selviäisi.
 
Mitenhän ketussa Typescriptin tyypeissä vältetään circular referencet?

Minulla on siis tyyppejä tyyliin Work ja Person. Workissa on esimerkiksi authors, joka on Person-tyyppiä. Vastaavasti Personilla on works. -> circular reference.

Tämä ei ole normaalisti ongelma, mutta React Hook Formsin FieldArrayn kanssa tämä on ongelma. Ei kelpaa, vaan tuo on purettava. No, koitin käyttää Omitia ja Pickiä, mutta sitten koko softa hajosi kun minulla on siellä koodia tyyliin

works = person.works

Hups kun personissa on määritelty works: Omit<Work, "authors"> ja worksissa taas on authors-kenttä.

Pitääkö minun tehdä n kappaletta erilaisia tyyppimäärittelyjä ja käytännössä muokata joka ikistä tiedostoa, mitä softasta löytyy, koska yksi v*n komponentti kehitti tästä ongelman? Normaalistihan tuo ei ole ongelma, koska ei bäkkäristä oikeasti tule dataa, jossa olisi circular reference.
 
Hngh. Melkein pari kuukautta meni tuon 11.1 mainitsemani ongelman ratkomiseen. Se ratkesi päivittämällä React 18:aan. Tosin jännästi sekä react että react-dom pitää päivittää @rc:hen, muuten ei tapahdu mitään.

Nyt vaan dependencyt taitaa olla päin v*ua, muiden pakettien päivittäminen ei sitten onnistukaan. PrimeReactiin kosahtaa, vaikka yrittää jotain toista pakettia päivittää. Ja tuota ei tunnu saavan päivitettyä myöskään uudempaan.

On tämä saatana yksi savotta.
 
Ongelma selvisi, kun joku tiesi täsmälleen oikean pakettiversion PR:lle. Se ei siis ollut uusin.
 
Edelleen tuosta isommasta ongelmasta. Softa alkoi taas kaatuilla kun päivitin React Hook Formsin 7.32.2 -> 7.43.1. Nyt vähän vaikuttaa että saattaa olla tuon komponentin tai Reactin bugi, ainakin virheilmoituksessakin sanotaan

"Uncaught Error Error: Should have a queue. This is likely a bug in React. Please file an issue."

Onneksi palauttamalla RHF:n versio takaisin vika hävisi.
 
Äh, miksei heti osu oikeaan paikkaan kun hakee jotain? Loppujen lopuksi erilaisten ympäristömuuttujien käyttö ympäristön mukaan on triviaalia: Adding Custom Environment Variables | Create React App.

Eli tein kaksi tiedostoa, .env.development ja .env.production. Näihin ympäristöstä riippuvat muuttujat ja - get this - mitään muuta ei tarvitse tehdä! Ei mitään koko softan rikkovia asennuksia (dotenv) tai mitään. Muuttujat on suoraan käytettävissä koodissa. npm run build käyttää production-tiedostoa, npm start developmentia.
 
Vittu. Taidan kohta lyödä hanskat tiskiin. Tuo saatanan react-hook-form on niin paska komponentti että huumori loppuu ja tuki on luokkaa "annan vinkin josta ei ole hyötyä ja sitten sanon, että ei olisi kannattanut käyttää näin paljon aikaa tähän". No öh, Hermanni. Saatat olla oikeassa, olisi pitänyt koko RHM nakata vittuun jo kuukausi sitten. Tuommoisesta RHM-komponentista kun ei ilmeisesti saa tehtyä genericiä, tai ainakaan kukaan ei osaa, eli jos haluan käyttää formissa osakomponenttia, jota voi käyttää useamassa erilaisessa formissa, niin copypasta on paras ratkaisu. Tuo kun vaatii sen formin parametrinä ja formia ei selvästikään ole tarkoitus pystyä generalisoimaan.

Yritin juuri sellaista vaihtoehtoa, että yritän nostaa osan tuosta komponentista omaan osaansa, mutta ei tuokaan taida vittu toimia. Nyt sitten systeemi keksi, että vaikka minulla on viisi propsia, niin komponentti ei ota kuin yhden. Jos huumorin vuoksi koitan antaa vain tuon yhden parametrin, niin virhe on tietysti se, että puuttuu neljä kenttää...

Pohjimmiltaan siis ongelma on siinä, että FieldArrayn fieldit on tyyppiä FieldArrayWithId<T, "kenttä", "id"> jossa tuon T:n luulisi olevan generalisoitavissa, mutta ehei. Toimii vain tuossa on just se formin tyyppi, johon tämä komponentti tulee. En tajua, miten sekään toimii, mutta toimii kuitenkin.

Määrittely on näinkin hilpeä:

type FieldArrayWithId<TFieldValues extends FieldValues = FieldValues, TFieldArrayName extends FieldArrayPath<TFieldValues> = FieldArrayPath<TFieldValues>, TKeyName extends string = "id"> = FieldArray<TFieldValues, TFieldArrayName> & Record<TKeyName, string>

Ei ole Reactin minun hommaa. Jos tässä ei olisi toinen kaveri noita tietoja syöttämässä, niin lopettaisin tämän homman tähän jo nyt. Vapaa-ajalla koodailun kun olisi kai tarkoitus olla kivaa ja sitä se on useimmissa ympäristöissä. Tässä joutuu tappelemaan koko ajan ihan idioottimaisten ongelmien, jatkuvasti muuttuvien rajapintojen, puutteellisten tai vanhentuineiden dokumentaatioiden jne kanssa. Koodaamisen sijaan aika menee tuollaisten asioiden selvittämiseen.
 
Apinan raivolla sain ehkä tuon toimimaan. Hankala varmistaa, kun WSL alkoi keljuilla. Samat kyselyt, jotka serverillä tapahtuvat sekunnissa, kestävät nyt kehitysympäristössä (WSL) jopa minuutteja. Ne kyselyt ei siis välttämättä edes mene perille, vaan Chromen debuggerin mukaan pendaavat jotain.

Kai se on uskottava, että Windows on täysin kelvoton tekele muuhun kuin pelaamiseen. Hetken jo tuon WSL:n ansiosta kuvittelin, että sillä tekisi jotain muutakin, mutta ei sitten. Aiemminhan jo ollut sitä ongelmaa, että WSL:n pääprosessi alkaa vetää 100% CPU-ajasta aivan yht'äkkiä vaikka konetta ei edes käyttäisi (eilen teki sen sillä välin kun käytin koiraa ulkona). Tuon saa ainakin näennäisesti korjattua kun tappaa sen prosessin. Pitää vaan terminaalit avata uudestaan, Code osaa reconnectata automaattisesti. Ärsyttävää, oli vielä ärsyttävämpää ennen kuin hoksasin tuon prosessijutun. Sitä ennen piti rebootata koko kone että toipui.

Kai se pitää alkaa katsella Linux-distroja sillä silmällä ja ihmetellä että mihin levyn kulmalle sen laittaisi. Hm. Olikohan minulla koneessa kaksi m.2-paikkaa. Windows on yhdellä, mutta on vain gigainen levy ja aika täynnä.
 
Jaahas, kai se on pakko lähteä kaupoille. Tuossa tuotantoversiossa on nyt joku CORS-ongelma (mistähän hitosta tuokin tuohon pölähti kun on aiemmin toiminut?) + jotain muuta, mistä ei tunnu tulevan minkäänlaista virhettä, ei vaan toimi. Tässä WSL:ssä on niin paljon omia ongelmia, että mahdoton sanoa mikä johtuu minun koodista ja mikä jostain muusta.
 
Wintoosa Prohonhan saa asennettua lisäosista Hyper-V:n ja siihen sitten pika-asennuksella ubuntu tahi distro makunsa mukaan. Mulla oli vielä vähän aikaa sitten raudalle asennettu Proxmox jossa wintoosa kiltisti ja sitten mitä distroi/ kontteja haluaakaan (paitsi että en tarvinnut muita distroja ja 5700TX ei suostunut virtualosoitavaks niin vetäsin wintoosan raudalle suoraan kun ei ollut varaa ostaa uutta korttia mun harvoja pelikertoja varten). Itse mielelläni dockeroin kaiken mahdollisen niin on vähän irti käyttämäni OS:n ominaisuuksista. Jotenkin näistä helpoin ratkaisu ollut.
 
Kävin hakemassa toisen m.2-aseman ja nakkasin siihen Fedoran. Ei olekaan ollut RPM-pohjaista systeemiä sitten 90-luvun kun Red Hatia kokeilin.
 
On tämäkin työmaa. Luulin siis jo korjanneeni tuon softan, mutta nyt huomasin, että react-form-hooksin dirtyfields on aina tyhjä. Toisin sanoen se on sitä mieltä, että mitään kenttiä ei ole muokatta vaikka olisi. Siinäpä taas selvittelet että mikä vittu tällä kertaa tuota ahdistaa. Lisäksi yht'äkkiä välillä sivun käsittely kestää esim. minuutin. Bäkkärissä siitä menee sekunti, loput aikaa React vatuloi jotain.
 
Onpas tämä TypeScript jännä kieli. Nämä koodit on erit:
Koodi:
const methods = useForm<ShortForm>({ defaultValues: formData });
Koodi:
    const { register, control, handleSubmit,
        formState: { isDirty, dirtyFields } } =
        useForm<ShortForm>({ defaultValues: formData });
Erillä tarkoitan siis sitä, että esimerkiksi methods.formState.dirtyFields on eri kuin jälkimmäisestä destrukturoinnista tuleva dirtyFields. Jos siis teen edellisen, niin tuo kenttä on tyhjä, jälkimmäisessä siinä on arvoja. VS Code näyttää nuo jopa eri tyyppisenä.
Tyhmä minä kun kuvittelin, että nuo olsiivat teknisesti sama juttu.
 
Alkaa kyllä tuntua, että nakkaan kohta react-hook-formsin helvettiin. Ei ole muuta kuin ongelmia tuon kanssa. Nyt sitten alkoi kaatuilla tietyssä tilanteessa, enkä tajua millään, mikä on erona versioon, joka toimi. Kirjaimellisesti kaatuminen tapahtuu bundle.js:n rivillä miljoona ja jotain (en liioittele). Siinäpä debuggaat kun ainoa virhe on "elm.focus is not a function" ja kaatuminen tapahtuu "Object.focus"-funktiossa. Ilmeisesti siis jotain ongelmaa fokuksen kanssa, mutta siinäpä keksit että mitä.
 
Ajattelin sitten kokeilla sitä kontitusta kun töissä tuli muutama päivä taukoa mutta huhhuijaa että tuotakin saa säätää. Dockerissakin näyttää tärkeintä olevan, että jos tulee ongelma, niin mitään vihjeitä ongelmasta ei tahdo saada.

Frontti vielä ilmeisesti lähti suht näppärästi liikkeelle ja tietokanta tuntuisi toimivan (saan mysql-cli:llä yhteyden tietokantaan), mutta flask-appi ei vaan reagoi mihinkään. Ajan tuota interaktiivisesti, niin ilmoittaa kyllä että applari lähti käyntiin, mutta jos vaikka koitan jotain kysellä apista, niin curl sanoo vain "Recv failure: connection reset by peer". Flask-kontissa ei näy mitään.

Tietokannan kanssakin sai kyllä vääntää, mariadb kun ei tykkää jos sitä yrittää ajaa roottina. Enkä vielä keksinyt, että miten Dockerfileessä saa luotua tietokannan ja populoitua sen. Käsin kontin shellistä onnistui.

Ajatellin staging-version kontittaa, varsinainen tuotanto saa olla toistaiseksi niin kuin on.
 
Frontti vielä ilmeisesti lähti suht näppärästi liikkeelle ja tietokanta tuntuisi toimivan (saan mysql-cli:llä yhteyden tietokantaan), mutta flask-appi ei vaan reagoi mihinkään. Ajan tuota interaktiivisesti, niin ilmoittaa kyllä että applari lähti käyntiin, mutta jos vaikka koitan jotain kysellä apista, niin curl sanoo vain "Recv failure: connection reset by peer". Flask-kontissa ei näy mitään.

Julkaisitko portin kontin käynnistyksessä -p vivulla? Ajatko curlia kontin sisällä vai hostikoneella?


Tietokannan kanssakin sai kyllä vääntää, mariadb kun ei tykkää jos sitä yrittää ajaa roottina. Enkä vielä keksinyt, että miten Dockerfileessä saa luotua tietokannan ja populoitua sen. Käsin kontin shellistä onnistui.

Kannattaa käyttää valmista MariaDB konttia, niin suurin osa ongelmista on ratkottu valmiiksi. Kannan alustamiseen löytyy erilaisia ratkaisuja tarpeesta riippuen, esim. bäkkärin käynnistyksen yhteydessä. Itse olen tykännyt käyttää työkaluja kuten SQLAlchemy, mutta voi tuon ratkoa muutenkin. Itselleni on ollut oleellista että kantaan tulevat muutokset hoituu mahdollisimman automaattisesti kun bäkkärikontin päivittää.
 
Juu, -p 5000:5000. Käynnistin myös interaktiivisessa moodissa ja siellä Flaskin käsin. Curlia siis tietysti kontin ulkopuolelta, yritän kutsua API:a.

Ainakin nuo MariaDB:n viralliset kontit on siis sellaisia, että niissä ei voi ajaa roottina. Kannan populointi siis onnistuu ihan sql-dumpista (koska tämä on staging, niin kannan voi luoda aina uusiksi), mutta se pitää vissiin käsin kopioida kontin hakemistoon, koska ei minun kannata koko bäkkäriä tuon tiedoston takia ladata sinne. Voisin kyllä tässä tapauksessa pyörittää samassa kontissa bäkkäriä ja tietokantaakin, mutta en saanut multi-stage-himmeliä oikein toimimaan noiden kahden kanssa.
 
Sitä muuten olen ihmetellyt, että eikö tuo docker compose huomioi Dockerfileisiin tulleita muutoksia, ellei aja --no-cachella? Tuntuu että sama mitä tuonne ilman tuota vipua tekee, niin kontti ei muutu miksikään.

Ja jostain syystä docker compose down ei tapa noita kontteja, vaan pitää käydä kill -9:llä tappamassa (pelkkä killikään ei riitä).
 
Näyttäisi että tällä Dockerfileellä tuo Flaski lähti ns. jekkasemaan:
Koodi:
FROM --platform=$BUILDPLATFORM python:3.8.0 AS builder
WORKDIR /app
COPY ./requirements.txt requirements.txt
RUN pip3 install -r requirements.txt
COPY . .
ENV FLASK_RUN_PORT 5000
ENV FLASK_RUN_HOST 0.0.0.0
EXPOSE 5000
CMD ["flask", "run"]
 
Koko orkesterin saaminen soittamaan yhteen vaati sitten vielä muutaman kikan. Tein oman verkon ja määritin backendille ja tietokannalle staattiset IP:t. Lisäksi piti konffata healthcheck, jotta bäkkäri odottaa että tietokanta on ylhäällä. Eli näin:
Koodi:
services:
  frontend:
    build:
      context: ./suomisf-ui
      dockerfile: Dockerfile
    ports:
      - "3001:80"
    depends_on:
      - backend
    networks:
      customnetwork:
        ipv4_address: 172.20.0.8

  backend:
    build:
      context: ./suomisf
      dockerfile: Dockerfile
    ports:
      - "5000:5000"
    depends_on:
      database:
        condition: service_healthy
    networks:
      customnetwork:
        ipv4_address: 172.20.0.9

  database:
    build:
      context: ./suomisf-db
      dockerfile: Dockerfile
    ports:
      - "3307:3306"
    networks:
      customnetwork:
        ipv4_address: 172.20.0.10
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-pxxxxxx"]
      timeout: 5s
      retries: 20

networks:
  customnetwork:
    ipam:
      config:
        - subnet: 172.20.0.0/16

Frontin koodaaminen kontissa ei kyllä tunnu yhtään yhtä kätevältä kuin ilman kun kontti ilmeisesti pitää joka välissä buildata? Docker Explorer-lisäsi VS Codeen kyllä sinällän näyttäisi toimivan nätisti.

Mutta nyt näyttäisi lokaalisti lähtevän systeemi käyntiin. Startatessa populoidaan aina tietokanta tässä versiossa, uskoisin että testiserveriksi tuo on parempi kuin säilöä kantaa. Nyt tarvitsee vain kopioida sql-tiedosto tietokannan juureen niin tietokanta päivittyy. Tehtyjä muutoksia ei kuitenkaan tarvitse säilöä.

Kunnon CI/CD -putki vaatisi tietysti seuraavaksi GitHubin actioneihin perehtymistä, mutta taidan vain kirjoittaa skriptin joka fetchaa uusimmat muutokset, rebuildaa ja nostaa systeemin ylös.
 
Meinaa kyllä vituttaa se, miten vaikea on pakottaa docker compose tekemään kontit uusiksi. Ei sitten saatanallakaan tee uusiksi vaikka muokkaan Dockerfilejä. Yritä siinä sitten kokeilla miten saa ehdollisia käännöksiä aikaan kun ei hyvällä eikä pahalla aja uudestaan, vaikka kuinka poistan imaget ja sörkin ties mitä parametrejä komentoihin (--pull --no-cache ei tee vissiin yhtään mitään).
 
Ei helvetti tässä composessa ole mitään järkeä. Välillä tuota ei saa kääntämään paketteja uusiksi millään ja sitten se yht'äkkiä päättää kääntää vaikka ei tarvitse. Tein staging-version buildilla (--env-file) ja sitten ajattelin käynnistää kontit docker compose up:lla. Niin eiköhän tuo kääntänyt kontit uusiksi productioniksi ja käynnisti ne, vaikka stagingit oli tarjolla. Mutta sitten korjasin bugin yhdessä konteista ja yritin uudestaan docker compose up, niin arvatkaapa käännettiinkö kontteja uusiksi? No ei tietenkään.

Yritän kovasti tässä keksiä edes jotain logiikka, että tämä homma selkeytyisi, mutta ei onnistu. Paketteja käännellään ja ollaan kääntämättä täysin satunnaisen oloisesti.
 
Ei vittu, minulla menee hermot ja pää leviää nyt. Tätä sontaläjäähän ei tunnu saavan konffattua mitenkään järkevästi niin, että olisi parametrisoitavissa asioita. Siis vaikka että ajetaan eri portissa kontteja jos on staging tai testing. Googlailun perusteella hirveää kludgea vaan vaihtoehtona. No, en saa sitten millään tuota toimimaan, joten päätin että ei helvetti, olkoon. Palataan siihen tilanteeseen, jossa olin. Paitsi että nyt en saa sitten millään enää frontia, joka meni melkein heittämällä alun perin, toimimaan. Jostain syystä, riippumatta siitä mitä docker-composeen laitan, tuo mappaa itsensä porttiin 80. Tuolla saa auki etusivun, mutta mikään muu sivu ei toimi. Tuo ei myöskään jostain syystä enää osaa ottaa yhteyttä tietokantapalvelimeen, vaikka yhteys sinne kyllä toimii.

Tällainen on frontin määrittely composessa:
Koodi:
services:
  frontend:
    build:
      context: ./suomisf-ui
      dockerfile: Dockerfile
    ports:
      - "3003:80"
    depends_on:
      - backend
    networks:
      customnetwork:
        ipv4_address: 172.20.0.8
Porttimäärittelyn vaihdolla ei ole mitään vaikutusta. Tuohon saa laittaa ihan mitä vain ja aina on sama tulos. 172.20.0.8 toimii, 172.20.0.8:3003 ei. Eikä localhost portilla tai ilman. Myös osoita 172.20.0.1 toimii. Ihan vitun sama mitä tuohon ports-kohtaan tänään laitan, niin tekee saman. 3003:3000, 3003:3003, 3000:3000, 3000:80, 3003:80 jne. Ja mikään muu kuin etusivu ei toimi millään kombolla. Ja sama mitä Reactin .env-tiedostossa on kanssa. Restarttasin koko dockerin ym ym ja aina toimii miten sattuu.

Jaahas. Poistin koko projektin ja kloonasin uudestaan GitHubista ja nyt tuo ei enää edes käänny koska se ei löydä jotain kirjastoa, joka on ollut käytössä aina.

Ja tuo _toimi_ jo. Nyt vaan ei sitten millään. Taidan polkea koko paskan suohon ja unohtaa. Ostan vaikka mieluummin toisen serverin kuin enää tämän kanssa tuhlaan aikaani.
 
Jatkuvan buildaamisen sijaan voit mountata koodin volumena sisään, mutta sitten tarvitset oman docker-composen/Dockerfilen deviin ja toisen prodiin.
Dockerfilestä COPY . . pois ja loppuun CMD ["flask", "run", "--debug"] niin Flask lataa itsensä uudestaan kun koodi muuttuu levyllä.
docker-compose.yml:iin lisää volumes
YAML:
services:
  backend:
    build:
      context: ./suomisf
      dockerfile: Dockerfile
    ports:
      - "5000:5000"
    depends_on:
      database:
        condition: service_healthy
    networks:
      customnetwork:
        ipv4_address: 172.20.0.9
    volumes:
      - ./suomisf:/app
 
Joo, en saatana saa tuota networkingiä toimimaan vaikka tekee mitä ja selaa miljoonat ohjeet. Se on ihan sama mitä docker-composeen, Reactin konffeihin tai komentoriville tungen, parhaimmillaan saan etusivun auki, mutta mikään muu ei toimi frontissa. Eihän tässä ole niin mitään järkeä:

Koodi:
CONTAINER ID   IMAGE            COMMAND                  CREATED         STATUS                   PORTS                                                 NAMES
19a8b8f439cd   sfbib-frontend   "/docker-entrypoint.…"   9 minutes ago   Up 8 minutes             3000/tcp, 0.0.0.0:3000->80/tcp, :::3000->80/tcp       sfbib-frontend-1
07928e87d477   sfbib-backend    "flask run"              9 minutes ago   Up 8 minutes             5005/tcp, 0.0.0.0:5005->5000/tcp, :::5005->5000/tcp   sfbib-backend-1
8bd01f59c77c   sfbib-database   "docker-entrypoint.s…"   9 minutes ago   Up 9 minutes (healthy)   0.0.0.0:3307->3306/tcp, :::3307->3306/tcp             sfbib-database-1

$ docker container port 19a8b8f439cd
80/tcp -> 0.0.0.0:3000
80/tcp -> [::]:3000

$ docker container port 07928e87d477
5000/tcp -> 0.0.0.0:5005
5000/tcp -> [::]:5005
curl 0.0.0.0:3000 toimii.
curl 0.0.0.0:5005 ei toimi.
Mutta
Koodi:
$ docker exec -it 07928e87d477 sh
# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
638: eth0@if639: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:ac:16:00:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.22.0.3/16 brd 172.22.255.255 scope global eth0
       valid_lft forever preferred_lft forever

curl 172.22.0.3:5005 toimii.

Kun taas
Koodi:
$ docker exec -it 19a8b8f439cd sh
/var/www/html # ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
640: eth0@if641: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP 
    link/ether 02:42:ac:16:00:04 brd ff:ff:ff:ff:ff:ff
    inet 172.22.0.4/16 brd 172.22.255.255 scope global eth0
       valid_lft forever preferred_lft forever
$ curl 172.22.0.4:3000
curl: (7) Failed to connect to 172.22.0.4 port 3000: Connection refused
Kun taas tuolla ip:llä toimii ilman porttinumeroa. Etusivu siis, mitään alasivuja ei saa auki millään osoitteella.
 
Viitsikö joku vääntää rautalangasta, miten tämän saisi toimimaan? Itse pudotan hanskat tiskiin, kohta kolme päivää olen tätä säätänyt mutta aina kun jotain muuttaa, niin joku saattaa alkaa toimimaan ja toinen hajoaa. Nyt viimeksi hajosi etäyhteys tietokantaan. Ei tarvinnut aiemmin tehdä mitään, ja yhteys onnistui. Nyt sitten access deniedia pukkaa samoilla tunnuksilla. Tässä tämän hetkinen docker-compose.yml:
Koodi:
services:
  frontend:
    image: 'sfbib-fe'
    build:
      context: ./suomisf-ui
      dockerfile: Dockerfile
    ports:
      - "3000:80"
    depends_on: 
      - backend
    networks: 
      suomisf-network:
          ipv4_address: 172.20.0.8

  backend:
    image: 'sfbib-be'
    build:
      context: ./suomisf
      dockerfile: Dockerfile
    ports:
      - "5005:5000"
    depends_on:
      database:
        condition: service_healthy
    networks: 
      suomisf-network:
          ipv4_address: 172.20.0.9

  database:
    image: 'sfbib-db'
    build:
      context: ./suomisf-db
      dockerfile: Dockerfile
    ports:
      - "3307:3306"
    networks: 
      suomisf-network:
          ipv4_address: 172.20.0.10
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-ppassword"]
      timeout: 3s
      retries: 20

networks:
  suomisf-network:
    ipam:
      driver: default
      config:
        - subnet: 172.20.0.0/16



Tässä Reactin Dockerfile:
Koodi:
# Use the official Node.js base image
FROM node:latest as build

# Set the working directory in the container
WORKDIR /build

# Copy package.json and package-lock.json to the working directory
COPY package.json package.json
COPY yarn.lock yarn.lock

# Install the dependencies
RUN yarn install --production=false

# Copy the entire project directory to the working directory
COPY . .

# Build the React app
RUN npm run build

# Use a lightweight web server to serve the static files
#FROM nginx:latest
FROM nginx:alpine
WORKDIR /var/www/html

# Copy the static files generated by the React build
COPY --from=build build/build /usr/share/nginx/html

EXPOSE 3000
# Start Nginx
CMD ["nginx", "-g", "daemon off;"]
(Jatkuu seuraavassa viestissä)
 
Tässä .env Reactille:
Koodi:
REACT_APP_SITE_URL=http://frontend/
REACT_APP_API_URL=http://backend:5005/api/
REACT_APP_IMAGE_URL=https://www.sf-bibliografia.fi/

Tässä bäkkärin Dockerfile:
Koodi:
FROM --platform=$BUILDPLATFORM python:3.8.0 AS builder

WORKDIR /app
COPY ./requirements.txt requirements.txt

RUN pip3 install -r requirements.txt

COPY . .

ENV FLASK_RUN_PORT 5005

EXPOSE 5005

CMD ["flask", "run"]

Tässä .env bäkkärille:
Koodi:
USER='root' 
PASSWORD='password' 
DATABASE_URL='mysql+pymysql://test:password@localhost:3307/suomisf' 
JWT_SECRET_TOKEN="xxxxxxx"

Tässä tietokanta:
Koodi:
FROM mariadb:latest

COPY backup.sql /docker-entrypoint-initdb.d/create.sql

ENV MYSQL_ROOT_PASSWORD password
ENV MYSQL_DATABASE suomisf
ENV MYSQL_USER: test
ENV MYSQL_PASSWORD password

EXPOSE 3306
Mikä noissa on väärin / mitä puuttuu?
 
Viimeksi muokattu:
Pari nopeaa huomiota

$ curl 172.22.0.4:3000
curl: (7) Failed to connect to 172.22.0.4 port 3000: Connection refused
Kun taas tuolla ip:llä toimii ilman porttinumeroa. Etusivu siis, mitään alasivuja ei saa auki millään osoitteella

Tämä johtuu siitä että nginx kuuntelee porttia 80, tällöin myöskään dockerfilen expose 3000 ei tee mitään.


Jos kantaan yhdistetään vain backendistä, voi ports määrityksen jättää pois databasesta. Tällöin bäkkärin connection stringissä korvataan localhost:3307 -> database:3306, jolloin hyödynnetään jaettua verkkonamespacea. Kannasta ei myöskään kannata tehdä omaa dockerfileä, vaan tarjota alustustiedosto ajonaikaisesti volumena.
 
@localhost:3307/suomisf'
EXPOSE 3306

En tiedä onko tossa mitään, mutta eri portit myslille?
 
Tämä johtuu siitä että nginx kuuntelee porttia 80, tällöin myöskään dockerfilen expose 3000 ei tee mitään.
Hmm, eikö sillä sitten ollut vaikutusta, kun yritin package.json:sta vaihtaa Reactin portin 3000:een jossain vaiheessa? Tosin eikö tuon sitten pitäisi toimia porttimäärittelyllä 3000:80, niin eikö sen pitäisi toimia? Tuotakin kokeilin, mutta ei muuttanut tilannetta.

Jos kantaan yhdistetään vain backendistä, voi ports määrityksen jättää pois databasesta. Tällöin bäkkärin connection stringissä korvataan localhost:3307 -> database:3306, jolloin hyödynnetään jaettua verkkonamespacea.
Koitin noita namespaceja, mutta joku noissakin tökki. Mutta pitääpä vielä kokeilla tuota ehdotusta.

Kannasta ei myöskään kannata tehdä omaa dockerfileä, vaan tarjota alustustiedosto ajonaikaisesti volumena.
Tätä en hiffaa, mistä se mariadb sitten tulee? Sinällään siis itse kanta voi hävitä joka kerta kun kontti sammutetaan, koska kyse on testikannasta. Ja itse asiassa olisi helpompikin, niin päivitykset tapahtuu pelkästään kanta restarttaamalla (ok, luontitiedosto pitää kopioida paikalleen).
 
Hmm, eikö sillä sitten ollut vaikutusta, kun yritin package.json:sta vaihtaa Reactin portin 3000:een jossain vaiheessa?
Pasteamassasi docker-fileessa teet React-sovellukselle prod-buildin (npm run build) ja servaat ne staattiset assetit nginx:llä. Reactin portilla on merkitystä vain kun ajat sitä dev-moodissa (npm start) ja nyt siellä kontissa on vain nginx kuuntelemassa portissa 80(?).

Tuolla tavalla voisit viedä UI:n tuotantoon, mutta devatessa halunnet kuitenkin hyödyntää hot-reloadia joten en ihan heti näe mikä hyöty tuosta React-appsin dockeroinnista olisi. Tässä on toisenlainen lähestymistapa, eli oman imagen luomisen sijaan käynnistät virallisen node-imagen, mounttaat työhakemiston sen sisälle ja käynnistät react dev-serverin siellä: React hot reload doesn't work in docker container
Bash:
docker run -u=1000:1000 -v $(pwd):/app -w=/app -d -p 3000:3000 --rm --name=nodedev node bash -c "npm install && npm run dev"
 
Tuolla tavalla voisit viedä UI:n tuotantoon, mutta devatessa halunnet kuitenkin hyödyntää hot-reloadia joten en ihan heti näe mikä hyöty tuosta React-appsin dockeroinnista olisi.
Tarkoitus ei ole siis devata tässä, vaan käyttää testaamiseen. Minulla on serveri, jossa on jo dockeroimaton tuotantoversio, haluaisin samalle serverille testiversion, tässä lie kontitus paras ratkaisu. Eli frontti, joka löytyy oman porttinsa takaa + oma bäkkäri ja tietokanta. Viime mainittu voisi toki olla olemassaolevassa kannassakin, mutta sitten pitäisi parametrisoida tietokannan nimi.
 
Tietämättä yhtään, mistä noi serverit on tilattu ja mitä ne maksaa, mutta yks vaihtoehto on myös käyttää erillistä testipannua testaamiseen. Julkaisukonffit vois pitää identtisesti ja olla vaan avaamatta yhteyksiä ulkomaailmasta testipannulle pois lukien oma IP tai miten luvitus onkaan helpoin järjestää, että pääsee itse käsiksi. Resursseissa voi ja kannattaakin säästää, eli testille joku pieni kautta halpa kone, joka jaksaa pyöritellä sen verran kuormaa kuin testaamisessa tarvittee.

Muutenkin minusta parempaa käytäntöä, kun tuotantoserverillä ei testailla kautta häärätä mitään ylimääräisiä.
 

Statistiikka

Viestiketjuista
261 793
Viestejä
4 547 336
Jäsenet
74 849
Uusin jäsen
ookooo

Hinta.fi

Back
Ylös Bottom