diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index ecf569a1d..342c5f530 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -90,7 +90,6 @@ jobs: - 6543:5432 steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 with: node-version-file: ".tool-versions" @@ -136,8 +135,6 @@ jobs: - name: Run Playwright tests run: npx playwright test --reporter=list --update-snapshots --shard=${{ matrix.shard }} - - - uses: actions/upload-artifact@v4 if: always() with: diff --git a/.gitignore b/.gitignore index 147882b1d..79bcd2068 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,4 @@ airflow-pgdata/ media _build +.obsidian diff --git a/.secrets.baseline b/.secrets.baseline index c43b96127..bafc8019b 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -151,7 +151,7 @@ "filename": "core/settings.py", "hashed_secret": "1ee34e26aeaf89c64ecc2c85efe6a961b75a50e9", "is_verified": false, - "line_number": 219 + "line_number": 220 } ], "docker-compose.yml": [ @@ -160,7 +160,7 @@ "filename": "docker-compose.yml", "hashed_secret": "3cf2012487b086bba2adb3386d69c2ab67a268b6", "is_verified": false, - "line_number": 41 + "line_number": 49 } ], "iframe_without_js.html": [ @@ -207,5 +207,5 @@ } ] }, - "generated_at": "2025-01-22T18:36:09Z" + "generated_at": "2025-01-28T14:22:35Z" } diff --git a/core/context_processors.py b/core/context_processors.py index ae3ec21cb..433ff7aea 100644 --- a/core/context_processors.py +++ b/core/context_processors.py @@ -23,7 +23,8 @@ def assistant(request) -> dict: return { "assistant": { "is_home": request.path == reverse("qfdmd:home"), - "is_iframe": request.session.get("iframe"), + "is_iframe": request.COOKIES.get("iframe") == "1" + or "iframe" in request.GET, "POSTHOG_KEY": settings.ASSISTANT["POSTHOG_KEY"], "MATOMO_ID": settings.ASSISTANT["MATOMO_ID"], }, diff --git a/core/settings.py b/core/settings.py index f818a73cc..3005e8011 100644 --- a/core/settings.py +++ b/core/settings.py @@ -76,6 +76,7 @@ "corsheaders.middleware.CorsMiddleware", "django.middleware.security.SecurityMiddleware", "whitenoise.middleware.WhiteNoiseMiddleware", + "qfdmd.middleware.AssistantMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", diff --git a/docker-compose.yml b/docker-compose.yml index 3e3c7684d..6eae08b36 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -34,6 +34,14 @@ services: ports: - 6543:5432 profiles: [lvao, airflow] + lvao-proxy: + image: nginx:latest + volumes: + - ./nginx/servers.conf:/etc/nginx/servers.conf + - ./nginx/nginx.conf:/etc/nginx/nginx.conf + profiles: [proxy] + ports: + - "8001:80" airflow-db: image: postgres:15 environment: @@ -63,7 +71,7 @@ services: context: . dockerfile: airflow-webserver.Dockerfile ports: - - "8080:8080" + - "8001:8080" healthcheck: test: ["CMD", "curl", "--fail", "http://localhost:8080/health"] interval: 30s diff --git a/docs/README.md b/docs/README.md index 2429be034..e8dfcbab9 100644 --- a/docs/README.md +++ b/docs/README.md @@ -56,12 +56,13 @@ Décrit les apprentissages pas à pas de l'utilisation des outils, par exemple - [🧐 REFERENCE](./reference/README.md) - [Règles de codage](./reference/101-coding-guidelines.md) - - [Frontend et templating](./reference/201-frontend.md) + - [Frontend](./reference/201-frontend.md) - [Règle de codage de la base de données](./reference/301-db-guidelines.md) - [Architecture des fichiers de code de la partie data](./reference/302-organisations-des-fichiers-data.md) - [Guide de la docmentation technique](./reference/901-documentation-technique.md) - [❓ EXPLICATIONS](./explications/README.md) + - [Frontend](./explications/201-frontend.md) - [🤔 COMMENT FAIRE ?](./comment-faire/README.md) - [Commandes Django utiles](./comment-faire/101-commandes-django.md) diff --git a/docs/explications/201-frontend.md b/docs/explications/201-frontend.md new file mode 100644 index 000000000..6c987088d --- /dev/null +++ b/docs/explications/201-frontend.md @@ -0,0 +1,24 @@ +# 🎨 Frontend + +Le projet dispose de deux frontend principaux +- **Longue Vie Aux Objets** : https://lvao.ademe.fr +- L'**assistant** : https://quefairedemesobjets.ademe.fr + +Ils ont été développés en silo, et ne partagent à date qu'un minimum de code. +L'objectif à terme est d'en mutualiser une majeure partie au travers de refactorisations. + +Longue vie aux objet est quant à lui découpé en deux briques fonctionnelles : +- Le **formulaire**, aussi appelé *version épargnons* +- La **carte**, intégrée au sein de l'assistant et par de nombreuses collectivités sous forme d'iframe +Les différences sont [détaillées ici](https://www.notion.so/accelerateur-transition-ecologique-ademe/Sp-cifications-de-la-carte-170dcd6cdaee4a62b9f70c2040b363e2?pvs=4) + +Comprendre l'**historique** du projet permet de comprendre comment s'imbriquent ces différentes applications +- **2023** : Longue vie aux objets : développée initialement, dans sa version *carte* +- **2024** : *Formulaire* : développée dans le cadre d'une campagne de communication [Épargnons nos ressources](https://epargnonsnosressources.gouv.fr) +- **2025** : *Assistant* : développé en remplacement d'un outil précédemment développé sous Gatsby + +**Les technologies employées dans l'assistant sont à prendre comme référence pour l'intégralité des développements futurs sur le frontend**, à savoir : +- [**templating Django**](https://docs.djangoproject.com/en/5.1/topics/templates/) + Jinja est encore utilisé dans la carte mais est progressivement déprévié +- **Parcel** pour la compilation des fichiers JS/CSS +- **Tailwind** diff --git a/docs/explications/301-routing-nginx-cache.md b/docs/explications/301-routing-nginx-cache.md new file mode 100644 index 000000000..a880b6d61 --- /dev/null +++ b/docs/explications/301-routing-nginx-cache.md @@ -0,0 +1,53 @@ +# 🚏 Routing, nginx et cache +Le projet est actuellement déployé sur **Scalingo**, Scalingo qui impose une limite de 50 requêtes/seconde sur un worker. +Nous avons décidé d'ajouter **Nginx** en janvier 2025 afin d'agir comme serveur de cache et anticiper sur l'atteinte de cette limite. +# nginx +Dans l'hypothèse d'un pic de charge, +- Le **cache** de certaines **vues Django** (déchet / produit, page d'accueil de l'assistant...) +- Le **cache** des **fichiers statiques** (CSS, JS...) + +Des images valant mille mots, ci-dessous un schéma résumant le parcours d'une requête lorsqu'elle atteint `lvao.ademe.fr` ou `quefairedemesdechets.ademe.fr` + + +```mermaid +sequenceDiagram + +participant Client +participant Nginx +participant Django + + + +Client->>Nginx: Request lvao.ademe.fr + +alt Cache Hit + +Nginx-->>Client: Return cached response + +else Cache Miss + +Nginx->>Django: Forward request to Django + +Django-->>Nginx: Generate response + +alt Authenticated User + +Django->>Nginx: Add 'logged_in' cookie + +end + +alt iframe query param present + +Django->>Nginx: Add iframe cookie + +end + +Nginx-->>Client: Return Django response + +end +``` + +Les cookies définis expirent à la fin de la session, cela veut dire qu'ils seront re-générés si l'utilisateur ferme son navigateur. + +# whitenoise +# middleware \ No newline at end of file diff --git a/docs/explications/README.md b/docs/explications/README.md index cf1a19263..8cb97eccb 100644 --- a/docs/explications/README.md +++ b/docs/explications/README.md @@ -1 +1,9 @@ # ❓ EXPLICATIONS + +```{toctree} +:hidden: + +101-ingestion-de-source.md +201-frontend.md +301-routing-nginx-cache.md +``` diff --git a/docs/reference/201-frontend.md b/docs/reference/201-frontend.md index 298261d07..f1f9ef060 100644 --- a/docs/reference/201-frontend.md +++ b/docs/reference/201-frontend.md @@ -1,42 +1,23 @@ -# Frontend et templating - -Le projet utilise le templating Django pour développer le frontend. - -- Jinja vs templating Django -- Parcel -- Tailwind -- Organisation et découpage des templates -- Utilisation des formulaires -- Django DSFR - -## Jinja et templating Django - -Le projet utilise Jinja et le templating Django. -:warning: **Ces deux approches cohabitent mais il est envisagé d'abandonner Jinja à terme.** -Quand bien même les templates continuent d'être placé dans le dossier `jinja2` afin de garantir une rétrocompatibilité, tous les futurs développement doivent s'efforcer de s'affranchir de Jinja. - +# Frontend ## Parcel - -[Parcel](https://parceljs.org) est utilisé pour compiler les fichiers statiques : CSS, JS notamment. +[Parcel](https://parceljs.org) est utilisé pour compiler les fichiers statiques : **CSS, JS** notamment. Les sources sont configurées dans le fichier package.json` ### Transformers - Le projet utilise des _transformers_, ceux-ci sont principalement utilisés pour assainir le DSFR aujourd'hui. +⚠️ Ils sont pour le moment désactivés, ils le seront dans un futur proche (écrit le 28/1/2025) ### PostCSS - Parcel embarque PostCSS, celui-ci est étendu dans le projet pour supporter Tailwind et le [_CSS nesting_](https://www.w3.org/TR/css-nesting-1/) - ## Organisation et découpage des templates On considère que le découpage de templates peut avoir lieu dans plusieurs situations : -- Rendre un élément de design réutilisable (composant du DSFR, composant custom) -- Rendre un template moins lourd et le découper en petites unités fonctionnelles +- Rendre un **élément de design réutilisable** (composant du DSFR, composant custom) +- Rendre un **template moins lourd** et le découper en petites unités fonctionnelles -Un template de composant étant considéré comme réutilisable, il sera nommé `nom_du_composant.html` -Un fragment de template étant considéré local au template qu'il compose, il sera nommé `_nom_du_fragment.html` et placé dans un dossier portant le même nom que le template où il est utilisé. +Un **template de composant** étant considéré comme réutilisable, il sera nommé `nom_du_composant.html` +Un **fragment de template** étant considéré local au template qu'il compose, il sera nommé `_nom_du_fragment.html` et placé dans un dossier portant le même nom que le template où il est utilisé. L'organisation précise des templates est laissée au jugement du développement, mais l'idée est de faire remonter à proprement parler au niveau le plus haut possible les fichiers de templates. Par exemple, si on travaille sur le template de la fiche détaillée d'un acteur, qui utilise un composant tag, on peut retrouver l'arborescence suivante @@ -62,7 +43,6 @@ Avec par exemple Note : cette convention a été adoptée en cours de projet et il est possible qu'une partie des templates legacy ne la respecte pas encore. ## Tailwind - Le projet utilise Tailwind. Les classes Tailwind sont préfixées de `qfdmo` afin de les *namespacer* par rapport aux autres dépendances utilisées (DSFR notamment). diff --git a/docs/tutoriels/101-push-to-prod.md b/docs/tutoriels/101-push-to-prod.md new file mode 100644 index 000000000..1244ff003 --- /dev/null +++ b/docs/tutoriels/101-push-to-prod.md @@ -0,0 +1,14 @@ +# 🚀 Mise en production + +1. Créer un tag depuis l'interface GitHub ou la ligne de commande +``` +git checkout main +git reset --hard origin/main +git tag v1.x.y +git push --tags +``` +On en profite au passage pour s'assurer que notre branche main est bien iso avec celle du dépôt distant. + +2. Cela va déclencher une action GitHub, qui va automatiquement créer la release +3. Une fois la release créée, celle-ci peut être modifiée pour adapter son contenu à la teneur des changements déployés +4. L'équipe doit être prévenue sur le [canal équipe](https://mattermost.incubateur.net/betagouv/channels/longuevieauxobjets-equipe) \ No newline at end of file diff --git a/docs/tutoriels/README.md b/docs/tutoriels/README.md index 32e408953..1977e3443 100644 --- a/docs/tutoriels/README.md +++ b/docs/tutoriels/README.md @@ -1 +1,7 @@ # 🙌 TUTORIELS + +```{toctree} +:hidden: + +101-push-to-prod +``` diff --git a/e2e_tests/iframe.spec.ts b/e2e_tests/iframe.spec.ts index e2c133d42..7ac0d6601 100644 --- a/e2e_tests/iframe.spec.ts +++ b/e2e_tests/iframe.spec.ts @@ -65,15 +65,15 @@ test("Desktop | iframe with 0px parent height displays correctly", async ({ page }); test("Desktop | iframe cannot read the referrer when referrerPolicy is set to no-referrer", async ({ page }) => { - await page.goto("http://localhost:8000/test_iframe?carte=1", { waitUntil: "networkidle" }); + await page.goto("http://localhost:8000/test_iframe?carte=1&noreferrer", { waitUntil: "networkidle" }); // Get the content frame of the iframe const iframeElement = await page.$("iframe[referrerpolicy='no-referrer']"); const iframe = await iframeElement?.contentFrame(); - expect(iframe).not.toBeNull(); + expect(iframe).toBeTruthy(); // Evaluate the referrer inside the iframe - const referrer = await iframe.evaluate(() => document.referrer); + const referrer = await iframe?.evaluate(() => document.referrer); // Assert that the referrer is set and not undefined expect(referrer).toBe(''); @@ -93,3 +93,24 @@ test("iframe can read the referrer when referrerPolicy is not set", async ({ pag // Assert that the referrer is set and not undefined expect(referrer).toBe('http://localhost:8000/test_iframe?carte=1'); }); + +// Need to be run locally with nginx running +// test("Desktop | iframe mode is kept during navigation", async ({ browser, page }) => { +// await page.goto("http://localhost:8001/dechet/chaussures?iframe", { waitUntil: "networkidle" }); +// page.getByTestId("header-logo-link").click() +// await expect(page).toHaveURL("http://localhost:8001/dechet/") +// expect(await page.$("body > footer")).toBeFalsy() +// await page.close() + +// const newPage = await browser.newPage() +// await newPage.goto("http://localhost:8001/dechet/chaussures", { waitUntil: "networkidle" }); +// expect(browser.contexts) +// expect(await newPage.$("body > footer")).toBeTruthy() +// await newPage.close() + +// const yetAnotherPage = await browser.newPage() +// await yetAnotherPage.goto("http://localhost:8001/dechet/chaussures?iframe", { waitUntil: "networkidle" }); +// await yetAnotherPage.goto("http://localhost:8001/dechet/", { waitUntil: "networkidle" }); +// await yetAnotherPage.goto("http://localhost:8001/dechet/chaussures", { waitUntil: "networkidle" }); +// expect(await yetAnotherPage.$("body > footer")).not.toBeTruthy() +// }); diff --git a/jinja2/tests/iframe.html b/jinja2/tests/iframe.html index e168b4ba7..6701afe5e 100644 --- a/jinja2/tests/iframe.html +++ b/jinja2/tests/iframe.html @@ -47,10 +47,14 @@