Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Derniers correctifs avant mise en prod : retours, cache, régressions #1270

Merged
merged 21 commits into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .github/workflows/run_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@ jobs:
- 6543:5432
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version-file: ".tool-versions"
Expand Down Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,4 @@ airflow-pgdata/
media

_build
.obsidian
6 changes: 3 additions & 3 deletions .secrets.baseline
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@
"filename": "core/settings.py",
"hashed_secret": "1ee34e26aeaf89c64ecc2c85efe6a961b75a50e9",
"is_verified": false,
"line_number": 219
"line_number": 220
}
],
"docker-compose.yml": [
Expand All @@ -160,7 +160,7 @@
"filename": "docker-compose.yml",
"hashed_secret": "3cf2012487b086bba2adb3386d69c2ab67a268b6",
"is_verified": false,
"line_number": 41
"line_number": 49
}
],
"iframe_without_js.html": [
Expand Down Expand Up @@ -207,5 +207,5 @@
}
]
},
"generated_at": "2025-01-22T18:36:09Z"
"generated_at": "2025-01-28T14:22:35Z"
}
3 changes: 2 additions & 1 deletion core/context_processors.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
},
Expand Down
1 change: 1 addition & 0 deletions core/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
"corsheaders.middleware.CorsMiddleware",
"django.middleware.security.SecurityMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware",
"qfdmd.middleware.AssistantMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
fabienheureux marked this conversation as resolved.
Show resolved Hide resolved
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
Expand Down
10 changes: 9 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
24 changes: 24 additions & 0 deletions docs/explications/201-frontend.md
Original file line number Diff line number Diff line change
@@ -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**
53 changes: 53 additions & 0 deletions docs/explications/301-routing-nginx-cache.md
Original file line number Diff line number Diff line change
@@ -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
8 changes: 8 additions & 0 deletions docs/explications/README.md
Original file line number Diff line number Diff line change
@@ -1 +1,9 @@
# ❓ EXPLICATIONS

```{toctree}
:hidden:

101-ingestion-de-source.md
201-frontend.md
301-routing-nginx-cache.md
```
34 changes: 7 additions & 27 deletions docs/reference/201-frontend.md
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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).

Expand Down
14 changes: 14 additions & 0 deletions docs/tutoriels/101-push-to-prod.md
Original file line number Diff line number Diff line change
@@ -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)
6 changes: 6 additions & 0 deletions docs/tutoriels/README.md
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
# 🙌 TUTORIELS

```{toctree}
:hidden:

101-push-to-prod
```
27 changes: 24 additions & 3 deletions e2e_tests/iframe.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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('');
Expand All @@ -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()
// });
8 changes: 6 additions & 2 deletions jinja2/tests/iframe.html
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,14 @@ <h3>LE FORMULAIRE</h3>
<hr>

<h2>ASSISTANT</h2>

{% if "noreferrer" in request.GET %}
<h3>Avec referrerpolicy="noreferrer"</h3>
<script src="http://localhost:8000/iframe.js" data-debug-referrer></script>
{% else %}
<h3>Par défaut</h3>
<script src="http://localhost:8000/iframe.js" data-testid='assistant'></script>
<h3>Avec referrerpolicy="noreferrer"</h3>
<script src="http://localhost:8000/iframe.js" data-testid='assistant' data-debug-referrer></script>
{% endif %}

<div>
Proin ut a fames amet mi a feugiat mus quam risus iaculis egestas dignissim maecenas scelerisque tristique a leo a diam a cursus consectetur sem ac litora aliquam a. Felis vestibulum vestibulum vitae a vestibulum aliquam id egestas a proin posuere aliquam bibendum facilisis adipiscing tempor aptent nibh feugiat felis rutrum integer condimentum tincidunt. Ipsum purus nulla habitant condimentum tempor vestibulum elementum scelerisque dui at condimentum gravida a scelerisque scelerisque a adipiscing parturient ornare enim sodales vestibulum. Nec egestas est habitant tempus tristique parturient conubia consectetur parturient vestibulum quam nam platea senectus suspendisse a varius accumsan auctor vehicula velit vel. Ullamcorper a et nulla et inceptos himenaeos in hac consectetur facilisis scelerisque condimentum pharetra facilisi porttitor ut feugiat suspendisse lectus quisque fermentum nibh platea.
Expand Down
5 changes: 5 additions & 0 deletions nginx/nginx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
events {}

http {
include /etc/nginx/servers.conf;
}
27 changes: 27 additions & 0 deletions nginx/servers.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
proxy_cache_path /tmp/nginx_assistant_cache levels=1:2 keys_zone=assistant:10m max_size=1g inactive=60m use_temp_path=off;
# Define a variable to disable cache if the logged_in cookie is set
map $http_cookie $no_cache {
default 0;
"~*logged_in=.*" 1;
}

server {
listen 80;
server_name _;

location / {
proxy_pass http://host.docker.internal:8000;

proxy_cache_bypass $no_cache;
proxy_no_cache $no_cache;

proxy_cache assistant;
proxy_set_header Host $http_host;
proxy_cache_key $request_method$request_uri$is_args$args;
proxy_cache_lock on;
proxy_cache_lock_timeout 10s;
proxy_cache_use_stale error timeout updating;
proxy_cache_background_update on;
add_header X-Cache-Status $upstream_cache_status;
}
}
Loading
Loading