Contribuer au code
Apporter des modifications au code d’OSRD
Ce chapitre décrit le processus aboutissant à l’intégration de code au sein du projet. Si vous avez besoin d’aide, ouvrez une issue ou envoyez un message instantané.
L’application OSRD est divisée en plusieurs services écrits dans plusieurs langues. Nous essayons de suivre les bonnes pratiques générales en matière de code et de respecter les spécificités de chaque langage lorsque cela est nécessaire.
1 - Principes généraux
À lire en premier !
- Expliquez ce que vous faites et pourquoi.
- Documentez le nouveau code.
- Ajoutez des tests clairs et simples.
- Décomposez le travail en morceaux intelligibles.
- Prenez le temps de choisir de bons noms.
- Évitez les abréviations peu connues.
- Contrôle et cohérence de la réutilisation du code de tiers : une dépendance est ajoutée seulement si elle est absolument nécessaire.
- Chaque dépendance ajoutée diminue notre autonomie et notre cohérence.
- Nous essayons de limiter à un petit nombre les PRs de mise à jour des dépendances chaque semaine
dans chaque composant, donc regrouper les montées de version dans une même PR est une bonne option
(reportez-vous au
README.md
de chaque composant). - Ne pas réinventer la roue : en opposition au point précédent, ne réinventez pas tout à tout prix.
- S’il existe une dépendance dans l’écosystème qui est le standard « de facto », nous devrions fortement envisager de l’utiliser.
- Plus de code et de recommandations générales dans le dépôt principal CONTRIBUTING.md.
- Demandez toute l’aide dont vous avez besoin !
Consulter les conventions pour le back-end ‣
Consulter les conventions pour le front-end ‣
Continuer vers l’écriture de code ‣
Continuer vers l’écriture de tests ‣
2 - Conventions back-end
Conventions de codes et bonnes pratiques pour le back-end
Python
Le code Python est utilisé pour certains paquets et pour les tests d’intégration.
- Suivez le Zen of Python.
- Les projets sont organisés avec uv
- Le code est linté avec ruff.
- Le code est formaté avec ruff.
- Les tests sont écrits avec pytest.
- Les types sont vérifiés avec pyright.
Rust
- Comme référence pour le développement de notre API, nous utilisons les Rust API guidelines.
D’une manière générale, il convient de les respecter.
- Préférer les importations granulaires aux importations globales comme
diesel::*
. - Les tests sont écrits avec le framework de base.
- Utilisez l’exemple de documentation pour savoir comment formuler et formater votre documentation.
- Utilisez un style de commentaire cohérent :
///
les commentaires de la documentation sont au-dessus des invocations #[derive(Trait)]
.- Les commentaires
//
doivent généralement être placés au-dessus de la ligne en question, plutôt qu’en ligne. - Les commentaires commencent par des lettres majuscules.
Terminez-les par un point s’ils ressemblent à une phrase.
- Utilisez les commentaires pour organiser des portions de code longues et complexes qui ne peuvent être raisonnablement remaniées en fonctions distinctes.
- Le code est linté avec clippy.
- Le code est formaté avec fmt.
Java
3 - Conventions front-end
Conventions de codes et bonnes pratiques pour le front-end
Nous utilisons ReactJS et tous les fichiers doivent être écrits en Typescript.
Le code est linté avec eslint, et formaté avec prettier.
Nomenclature

Les applications (osrd eex, osrd stdcm, éditeur infra, éditeur matériel) proposent des vues (gestion des projets, gestions des études, etc.) liées à des modules (projet, étude, etc.) qui contiennent les composants.
Ces vues sont constituées de composants et sous-composants tous issus des modules.
En plus de contenir les fichiers de vues des applications, elles peuvent contenir un répertoire scripts qui propose des scripts liés à ces vues. Les vues déterminent la logique et l’accès au store.
Les modules sont des collections de composants rattachés à un objet (un scénario, un matériel roulant, un TrainSchedule). Ils contiennent :
- un répertoire components qui héberge tous les composants
- un répertoire styles optionnel par module pour le style des composants en scss
- un répertoire assets optionnel par module (qui contient les assets, de jeux de données par défaut par ex, spécifiques au module)
- un fichier reducers optionnel par module
- un fichier types optionnel par module
- un fichier consts optionnel par module
Un répertoire assets (qui contient les images et autre fichiers).
Enfin, un répertoire common qui propose :
- un répertoire utils pour les fonctions utilitaires communes à l’ensemble du projet
- un fichier types pour les types communs à l’ensemble du projet
- un fichier consts pour les constantes communes à l’ensemble du projet
Principes d’implémentation
Naming
On utilise en général les conventions suivantes dans le code:
- les composants et les types sont en PascalCase
- les variables sont en camelCase
- les clés de traduction sont en camelCase également
- les constantes sont en SCREAMING_SNAKE_CASE (sauf cas particulier)
- les classes css sont en kebab-case
Styles & SCSS
ATTENTION : en CSS/React, le scope d’une classe ne dépend pas de l’endroit où le fichier est importé mais est valide pour toute l’application. Si vous importez un fichier scss
au fin fond d’un composant (ce que nous déconseillons fortement par ailleurs), ses classes seront disponibles pour toute l’application et peuvent donc provoquer des effets de bord.
Il est donc très recommandé de pouvoir facilement suivre l’arborescence des applications, vues, modules et composants également au sein du code SCSS, et notamment imbriquer les noms de classes pour éviter les effets de bord.
Quelques conventions supplémentaires:
- toutes les tailles sont exprimées en px, sauf pour les polices que l’on exprime en rem.
- nous utilisons la bibliothèque classnames pour appliquer conditionnellement des classes : les classes sont séparées chacune dans un
string
et les opérations booléennes ou autres sont réalisées dans un objet qui retournera — ou pas — le nom de propriété comme nom de classe à utiliser dans le CSS.
Store/Redux
Le store permet de stocker des données qui seront accessibles à n’importe quel endroit de l’application. Il est découpé en slices, qui correspondent à nos applications.
Attention cependant à ce que notre store ne devienne pas un fourre-tout.
L’ajout de nouvelles propriétés dans le store doit être justifié par le fait que la donnée en question est “haut-niveau” et devra être accessible depuis des endroits éloignés de la code base, et qu’une simple variable de state ou de contexte ne soit pas pertinent pour stocker cette information.
Pour plus de détails, nous vous invitons à consulter la documentation officielle.
Nous utilisons Redux ToolKit pour réaliser nos appels au backend.
RTK permet notamment de faire des appels à l’api et de mettre en cache les réponses. Pour plus de détails, nous vous invitons à consulter la documentation officielle.
Les endpoints et les types du backend sont générés directement à partir de l’open-api d’editoast et stockés dans generatedOsrdApi
(avec la commande npm run generate-types
). Ce fichier généré est ensuite enrichi par d’autres endpoints ou types dans osrdEditoastApi
, qui peuvent être utilisés dans l’application.
A noter qu’il est possible de transformer des mutations en queries lors de la génération de generatedOsrdApi en modifiant le fichier openapi-editoast-config
.
Lorsqu’un appel d’endpoint doit être skip parce qu’une variable n’est pas définie, on utilise skipToken de RTK pour éviter de devoir mettre des casts.
Traduction de l’application
La traduction de l’application est effectuée sur Weblate.
Imports
Il est recommandé d’utiliser le chemin complet pour chaque import, sauf si votre fichier à importer est dans le même répertoire.
ESLint est configuré pour trier automatiquement les imports en 4 groupes, séparés par une ligne vide et chacun trié alphabétiquement :
- React
- Librairies externes
- Fichiers internes en chemin absolu
- Fichiers internes en chemin relatif
Concernant l’import/export de types, ESLint et Typescript sont configurés de manière à ajouter automatiquement type
devant un import de types, ce qui permet de:
- Améliorer les performances et le travail d’analyse du compilateur et du linter.
- Rendre ces déclarations plus lisibles, on voit clairement ce qu’on est en train d’importer.
- Alléger le bundle final (tous les types disparaissent à la compilation).
- Éviter des cycles de dépendances (ex: l’erreur disparaît avec le mot clé
type
)

4 - Écrire du code
Apporter des modifications au code d’OSRD
Si vous n’être pas un habitué de Git, suivez ce tutoriel
Créez une branche
Si vous comptez contribuer régulièrement, vous pouvez demander accès au dépôt principal. Sinon, créez un fork.
Ajoutez des changements sur votre branche
Essayez de découper votre travail en étapes macroscopiques, et sauvegardez vos changements dans un commit à la fin de chaque étape. Essayez de suivre les conventions du projet.
Conservez votre branche à jour
git switch <your_branch>
git fetch
git rebase origin/dev
Continuer vers le style des commits ‣
5 - Conventions de commits
Quelques bonnes pratiques et règles pour les messages de commits
Style de commits
Le format général des commits est le suivant :
composant1, composant2: description du changement à l'impératif
Description détaillée ou technique du contenu et de la motivation du
changement, si le titre n'est pas complètement évident.
- le message comme le code doit être en anglais (seulement des caractères ASCII pour le titre)
- il peut y avoir plusieurs composants, séparés par des
:
quand il y a une relation hiérarchique, ou des ,
sinon - les composants sont en minuscule, avec éventuellement
-
, _
ou .
- la description du changement à l’impératif commence par un verbe en minuscule
- le titre ne doit pas comporter de lien (
#
est interdit)
Idéalement :
- le titre doit être autonome : pas besoin de lire autre chose pour le comprendre
- le titre du commit est entièrement en minuscule
- le titre est clair pour une personne étrangère au code
- le corps du commit contient une description du changement
Une vérification automatique est effectuée pour vérifier autant que possible ce formatage.
Contre-exemples de titres de commit
A éviter absolument:
component: update ./some/file.ext
: préciser la mise à jour en question plutôt que le fichier,
les fichiers sont des éléments techniques bienvenus dans le corps du commitcomponent: fix #42
: préciser le problème corrigé, les liens (vers l’issue, etc.) sont
encouragés dans le corps du commitwip
: décrire le travail (et le finir)
Bienvenus pour faciliter la relecture, mais ne pas merger:
fixup! previous commit
: un autosquash devra être lancé avant le mergeRevert "previous commit of the same PR"
: les deux commits devront être retirés avant merge
Le Developer Certificate of Origin (DCO)
Tous les projets d’OSRD utilisent le DCO (certificat du développeur d’origine)
pour des raisons légales. Le DCO permet de confirmer que vous avez les droits
sur le code que vous contribuez. Pour en savoir plus sur l’histoire et
l’objectif du DCO, vous pouvez lire The Developer Certificate of Origin
de Roscoe A. Bartlett.
Pour se conformer au DCO, tous les commits doivent inclure une ligne
Signed-off-by.
Pour signer un commit, il suffit d’ajouter l’option -s
à votre commande git commit
, comme ceci :
git commit -s -m "Your commit message"
Cela s’applique également lors de l’utilisation de la commande git revert
.
Allez dans Fichiers
-> Préférences
-> Paramètres
, puis recherchez et
activez le paramètre Always Sign Off.
Ensuite, lorsque vous ferez un commit via l’interface de VS Code, ils seront
automatiquement signés.
Continuer vers le partage des changements ‣
6 - Partagez vos changements
Comment soumettre votre code pour qu’il soit vérifié ?
L’auteur d’une pull request (PR) est responsable de son « cycle de vie ». Il se charge de contacter les différents acteurs, de suivre la revue de code, répondre aux commentaires et corriger le code suite à la revue de code (vous pouvez également consulter la page dédiée à la revue de code).
Ouvrez une pull request
Une fois que vos changements sont prêts, il est temps de proposer de les intégrer à la branche dev
.
Cela se fait dans l’interface web de Github.
Si possible :
- Faites des PRs d’unités logiques et atomiques également (évitez de mélanger le refactoring, les nouvelles fonctionnalités et la correction de bugs en même temps).
- Ajoutez une description aux PR pour expliquer ce qu’elles font et pourquoi.
- Aidez le relecteur en suivant les conseils donnés dans l’article de mtlynch.
- Ajoutez les balises
area:<affected_area>
pour montrer quelle partie de l’application a été impactée. Cela peut être fait via l’interface web.
Prenez en compte les retours
Une fois que votre PR est ouverte, d’autres contributeurs doivent donner leur avis sur votre proposition :
- N’importe qui peut donner son avis.
- Il est nécessaire d’obtenir l’approbation d’un contributeur familier avec le code.
- Il est d’usage de prendre en compte tous les commentaires critiques.
- Les commentaires sont souvent écrits dans un style plutôt direct, dans le souci de collaborer efficacement vers une solution acceptable par tous.
- Une fois que tous les commentaires ont été pris en compte, un mainteneur intègre le changement.
Dans l’idéal, évitez les PR conséquentes et découpez le travail en plusieurs PR :
- facilite la relecture et peut aussi l’accélérer (plus facile de trouver 1 heure pour faire une relecture qu’une demi-journée),
- permet une approche plus agile avec des retours sur les premières itérations avant d’enchaîner sur la suite,
- garde un historique git plus simple à explorer (notamment lors d’un
git bisect
à la recherche d’une regression par exemple).
En cas d’impossibilité à éviter une PR conséquente, ne pas hésiter à solliciter plusieurs relecteurs qui pourront s’organiser, voire de faire la relecture ensemble, relecteurs et auteur.
Sur les PR conséquentes et vouées à évoluer dans le temps, conserver les corrections suite à la
relecture dans des commits séparés facilite le travail de relecture. En cas de rebase
et de
relectures multiples par la même personne ils sont le moyen d’économiser une nouvelle relecture
complète (demandez de l’aide au besoin) :
- N’hésitez pas à relancer vos interlocuteurs, plusieurs fois si besoin est : vous êtes responsable de la vie de votre pull request.
Cycle de review
Une revue de code est un processus itératif.
Pour la fluidité d’une review, il est impératif de configurer correctement ses notifications github.
Il est conseillé de configurer les dépôts OSRD en “Participating and @mentions”. Cela permet d’être notifié d’activités uniquement sur les issues et PR auxquelles vous participez.
sequenceDiagram
actor A as Auteur PR
actor R as Reviewer/mainteneur
A->>R: Demande une review en notifiant spécifiquement quelques personnes
R->>A: Répond à la demande par oui ou non
loop Boucle entre auteur et reviewer
R-->>A: Commente, demande des changements
A-->>R: Répond à chaque commentaire/demande de changement
A-->>R: Corrige le code si nécessaire dans des « fixups » dédiés
R-->>A: Vérifie, teste, et commente à nouveau le code
R-->>A: Résout les conversations/demandes de changement le cas échéant
end
A->>R: Rebase si nécessaire
R->>A: Vérifie l'historique des commits
R->>A: Approuve la PR
Note right of A: Ajoute à la merge queue
Les mainteneurs sont automatiquement notifiés par le système de CODEOWNERS
. L’auteur d’une PR est responsable de faire avancer sa PR dans le processus de review (quitte à notifier manuellement un mainteneur).
Continuer enfin vers les tests ‣
7 - Tests
Recommandations pour les tests
Back-end
- Les tests d’intégration sont écrits avec pytest dans le dossier
/tests
. - Chaque route décrite dans les fichiers
openapi.yaml
doit avoir un test d’intégration. - Le test doit vérifier à la fois le format et le contenu des réponses valides et invalides.
Front-end
L’écriture fonctionnelle des tests est réalisée avec les Product Owners, et les développeurs choisissent une implémentation technique qui répond précisément aux besoins exprimés et qui s’intègre dans les recommandations présentes ici.
Nous utilisons Playwright pour écrire les tests bout en bout, et vitest pour écrire les tests unitaires.
Les navigateurs testés sont pour l’instant Firefox et Chromium.
Principes de base
- Les tests doivent être courts (1min max) et aller droit au but.
- Les timeout arbitraires sont proscrits, un test doit systématiquement attendre un évènement spécifique. Il est possible d’utiliser le polling (retenter une action — un clic par exemple — au bout d’un certain temps) proposé dans l’API de Playwright.
- Les tests doivent tous pouvoir être parallélisables.
- Les tests ne doivent pas pointer/attendre des éléments de texte issus de la traduction, préférer l’arborescence du DOM ou encore placer des
id
spécifiques. - On ne teste pas les données mais l’application et ses fonctionnalités. Des tests spécifiques aux données sont à élaborer par ailleurs.
Données
Les données testées doivent impérativement être des données publiques.
Les données nécessaires (infrastructure et matériel) aux tests sont proposées dans des fichiers json
de l’application, injectées au début de chaque test et effacées à la fin peu importe son résultat ou la manière d’être stoppé, y compris par CTRL+C
.
Cela se fait par des appels API en typescript avant de lancer le test à proprement parler.
Les données testées sont les mêmes en local ou via l’intégration continue.
Processus de développement des tests e2e
Les tests e2e sont implémentés de manière itérative, et livrés en même temps que les développements. À noter que:
- les tests e2e ne doivent être développés que sur les parcours utilisateurs critiques de l’application.
- ce workflow permet d’éviter les régressions immédiates après la sortie d’une fonctionnalité, de faire monter toute l’équipe en compétences sur les tests e2e et d’éviter des PRs très longues qui ajouteraient des tests e2e entiers
- on accepte que les tests e2e soient partiels le temps des développements et que le développement des tests alourdisse la taille des tickets et le temps de développement.
- certaines parties du test devront être mockées le temps que la fonctionnalité soit entièrement développée mais à la fin des développements, le test e2e devra être complet et les données mockées devront avoir disparu. Les modifications finales à faire dans le test pour retirer le mocking seront normalement minimes (uniquement changer les expected values).
- dans le cas de l’ajout d’une nouvelle fonctionnalité, il est préférable de séparer en des commits individuels l’implémentation de la nouvelle fonctionnalité et les tests afin de faciliter la review.
- les cas de test et les parcours utilisateur devront être définis en amont, au moment de la conception du ticket (refinement), avant le PIP. Ils pourront être proposés par un QA ou un.e PO, et devront être validés par un QA, le.la PO compétent.e et des devs front.
- si un test e2e touche à la configuration des tests e2e, à l’architecture du projet (typiquement le snapshotting) ou présente un risque de ralentir la CI, un atelier de conception (refinement) devra être organisé pour consulter les personnes en charge de l’architecture du projet et de la CI, notamment la team devops.
Atomicité d’un test
Chaque test doit être atomique : il se suffit à lui-même et ne peut pas être divisé.
Un test va cibler une fonctionnalité ou un composant, si ce dernier n’est pas trop gros. Un test ne testera pas tout un module ou tout une application, ce sera forcément un ensemble de tests afin de préserver l’atomicité des tests.
Si un test a besoin que des éléments soient créés ou ajoutés, ces opérations doivent être opérées par des appels API en typescript en amont du test, à l’instar de ce qui est fait pour l’ajout de données. Ces éléments doivent être supprimés à l’issue du test, peu importe son résultat ou la manière d’être stoppé, y compris par CTRL+C
.
Cela permettra notamment la parallélisation des tests.
Un test peut cependant, dans certains cas de figure où cela est pertinent, contenir plusieurs sous-divisions de test, clairement explicitées, et justifiées (plusieurs test()
dans un seul describe()
).
Exemple de test
Le besoin : « nous voulons tester l’ajout d’un train dans une grille horaire ».
- ajouter l’infrastructure et le matériel roulant de test dans la base de données par appels API
- créer projet, étude et scénario avec choix de l’infra de test par appels API
- début du test qui clique sur « ajouter un ou plusieurs trains » jusqu’à la vérification de la présence des trains dans la grille horaire
- le test a réussi, a échoué, ou est stoppé, le projet, l’étude et le scénario sont effacés, ainsi que le matériel roulant et et l’infrastructure de test par appels API
NB : le test ne va pas tester toutes les possibilités offertes par l’ajout de trains, cela relève d’un test spécifique qui testerait la réponse de l’interface pour tous les cas de figure sans ajouter de trains.
Continuer vers l’écriture de code ‣