Guides pratiques

Marches à suivre pour des tâches classiques

Les guides pratiques sont des marches à suivre. Vous êtes guidés pas-à-pas dans la résolution de problèmes, ou dans des scénarios classiques. Les connaissances requises sont plus élevées que pour les tutoriels, et le fonctionnement d’OSRD doit être compris.

1 - Contribuer à OSRD

Comment apporter sa pierre à l’édifice

1.1 - Avant toutes choses

Quelques premières informations importantes.

Déjà, merci de prendre le temps de contribuer !

Si les sections suivantes forment un guide du contributeur, ce n’est pas pour autant un ensemble de règles strictes. En fait, si vous avez déjà contribué à des gros projets open-source, la suite ne sera pas surprenante. Dans le cas contraire, elle vous sera probablement très utile !

Communiquer

Vous pouvez gagner du temps en discutant de votre projet de contribution avec les autres contributeurs :

Se renseigner

Comme dans tout projet, chaque changement repose sur ce qui a été fait par le passé. Avant d’apporter un changement, renseignez-vous sur l’existant :

  • Vous pouvez lire la documentation technique
  • Il est préférable de lire le code source de l’application en rapport avec votre projet
  • Vous pouvez contacter les derniers développeurs à avoir travaillé sur les zones du code en rapport avec votre projet

Continuer vers la mise en place ‣

1.2 - Licence et mise en place

Comment mettre en place l’environnement de développement ? Qu’implique notre licence ?

La licence des contributions de code

Tout le code du dépot OSRD est mis à disposition sous la licence LGPLv3. En contribuant du code, vous acceptez la redistribution de votre contribution sous cette license.

La licence LGPL interdit de modifier OSRD sans publier le code source de l’application modifiée : profitez du travail des autres, et laissez les autres profiter de votre travail !

Cette contrainte n’est pas contagieuse à travers les API : Il est possible d’utiliser OSRD comme bibliothèque, framework ou serveur pour s’interfacer avec des composants propriétaires. N’hésitez pas à proposer des changements pour répondre à vos besoins.

Mise en place

Obtenir le code source

  • Installer git1
  • Ouvrir un terminal2 dans le dossier qui contiendra le code source d’OSRD
  • git clone git@github.com:osrd-project/osrd

Lancer l’application

Docker est un outil qui réduit considérablement la préparation nécessaire pour travailler sur OSRD:

  • télécharger le dernier build de développement : docker compose pull
  • démarrer OSRD : docker compose up
  • compiler et démarrer OSRD: docker compose up --build
  • review une PR avec les images compilées par la CI: TAG=pr-XXXXX docker compose up --no-build --pull always

Pour commencer :

Continuer vers la contribution au code ‣


  1. Sous Linux, utilisez le gestionnaire de packet (comme apt↩︎

  2. Sous Windows, ouvrez Git Bash ↩︎

1.3 - 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.3.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 ‣

1.3.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.

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

1.3.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

Diagramme de l’Infrastructure

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

Routage & SLUG

Rédaction en cours

projects/{nom du projet}/studies/{nom de l'étude}/scenarios/{nom du scenario}

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. Vous pouvez utiliser les CSS modules pour éviter les conflits.

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, le compilateur se chargera de fabriquer la hiérarchie nécessaire.

Si par exemple nous avons un composant rollingStockSelector qui propose une liste de matériel rollingStockList représentés par des cartes rollingStockCard contenant une image représentant le matériel roulant rollingStockImg nous devrions avoir la structure SCSS suivante :

.rollinStockSelector {
  .rollingStockList {
    .rollingStockCard {
      .rollingStockImg {
        width: 5rem;
        height: auto;
      }
    }
  }
}

Ainsi, on a la garantie que l’image contenue dans la carte de matériel roulant héritera bien des bonnes propriétés css .rollinStockSelector.rollingStockList.rollingStockCard.rollingStockImg.

CSS Modules

Les CSS modules permettent de scoper les styles CSS à un composant spécifique, évitant ainsi les conflits de noms de classe globaux.

Vite prend en charge nativement les CSS modules. Assurez-vous que votre fichier CSS a l’extension .module.css. Par exemple, styles.module.css.

Utilisation des CSS modules dans les composants
  1. Créez un fichier SCSS avec l’extension .module.scss :
/* MyComponent.module.scss */
.container {
  background-color: white;
}

.title {
  font-size: 24px;
  color: #333;
}
  1. Utilisez les classes dans votre composant React :

Vite transforme les classes en objets qui contiennent les classes hashées (exemple _container_h3d8bg) et les utilise au moment de la génération du bundle, rendant ainsi les classes uniques.

import React from "react";
import styles from "./MyComponent.module.scss";

export function MyComponent() {
  return (
    <div className={styles.container}>
      <h1 className={styles["title"]}>Mon Titre</h1>
    </div>
  );
}

Pour plus d’information, vous pouvez regarder la documentation de vite.js

Noms de classes, utilisation de cx()

Les classes sont ajoutées les unes à la suite des autres, normalement, dans la propriété className="".

Cependant, quand cela est nécessaire — tests pour l’utilisation d’une classe, concaténation, etc. — nous utilisons la bibliothèque classnames qui préconise l’usage suivant :

<div className="rollingStockSelector">
  <div className="rollingStockList">
    <div className="rollingStockCard w-100 my-2">
      <img
        className={cx("rollingStockImg", "m-2", "p-1", "bg-white", {
          valid: isValid(),
          selected: rollingStockID === selectedRollingStockID,
        })}
      />
    </div>
  </div>
</div>

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

Tout ce qui est selector est géré par la vue et passé en props aux composants et sous-composants.

Par conséquent les appels au store en lecture et en écriture doivent être passés un niveau de la vue, en irrigant par des props et states les composants proposées par la vue.

RTK

Utiliser les endpoints générés à partir des fichiers openapi.yaml pour consommer le backend.

Fonctionnement du cache dans RTK Query

Lorsque de la donnée est récupérée depuis le back, RTK va mettre cette donnée en cache dans le store. Si le même endpoint est appelé avec les même paramètres, RTK va réutiliser la donnée dans le cache plutôt que de rappeler le back.

Dans le store, vous verrez cette clé editoastApi qui contient la donnée en cache de tous les endpoints editoast :

store Redux

Ici par exemple l’endpoint getProjects a été appelé.

RTK stocke le nom de l’endpoint, ainsi que les paramètres d’appel, pour former une clé unique nomDuEndpoint({ paramètres }). (ici getProjects({"ordering":"LastModifiedDesc","pageSize":1000})).

{
  'getProjectsByProjectIdStudiesAndStudyId({"projectId":13,"studyId":16})': {
    status :"fulfilled",
    etc
  },
  'getProjectsByProjectIdStudiesAndStudyId({"projectId":13,"studyId":14})': {
    
  }
}

Dans ce deuxième exemple, le même endpoint a été appelé avec le même paramètre projectId, mais un paramètre studyId différent.

Sérialisation des clés dans le cache

Les string utilisées comme clé dans le cache sont à peu de choses près l’objet paramètre passé à la moulinette JSON.stringify que transforme un object JS en string (donc sérialisé).

Normalement La sérialisation ne conserve pas l’ordre des clés des objets. Par exemple, JSON.stringify ne produira pas la même string avec ces deux objets: { a: 1, b: 2 } et { b: 2, a: 1 }.

RTK va optimiser la mise en cache en faisant en sorte que le résultat d’un appel avec {"projectId":13,"studyId":16} ou {"studyId":16, "projectId":13} soient stockées dans la même clé dans le cache.

Pour voir le fonctionnement en détail, voici le code de cette fonction de sérialisation :

Fonction de sérialisation RTK
const defaultSerializeQueryArgs: SerializeQueryArgs<any> = ({
    endpointName,
    queryArgs,
  }) => {
    let serialized = ''

    const cached = cache?.get(queryArgs)

    if (typeof cached === 'string') {
      serialized = cached
    } else {
      const stringified = JSON.stringify(queryArgs, (key, value) =>
        isPlainObject(value)
          ? Object.keys(value)
              .sort() // les clés sont remises dans l’ordre ici
              .reduce<any>((acc, key) => {
                acc[key] = (value as any)[key]
                return acc
              }, {})
          : value
      )
      if (isPlainObject(queryArgs)) {
        cache?.set(queryArgs, stringified)
      }
      serialized = stringified
    }
    // Sort the object keys before stringifying, to prevent useQuery({ a: 1, b: 2 }) having a different cache key than useQuery({ b: 2, a: 1 })
    return `${endpointName}(${serialized})`
  }
Souscriptions à la donnée

Dans la terminologie de RTK query, Lorsqu’un composant react appelle un endpoint défini dans RTK Query, il souscrit à la donnée.

RTK compte le nombre de référence à la même paire (endpoint,{paramètres}). Lorsque deux composants souscrivent à la même donnée. Ils partagent la même clé dans le cache.

import { osrdEditoastApi } from "./api.ts";

function Component1() {
  // component subscribes to the data
  const { data } = osrdEditoastApi.useGetXQuery(1);

  return <div>...</div>;
}

function Component2() {
  // component subscribes to the data
  const { data } = osrdEditoastApi.useGetXQuery(2);

  return <div>...</div>;
}

function Component3() {
  // component subscribes to the data
  const { data } = osrdEditoastApi.useGetXQuery(3);

  return <div>...</div>;
}

function Component4() {
  // component subscribes to the *same* data as ComponentThree,
  // as it has the same query parameters
  const { data } = osrdEditoastApi.useGetXQuery(3);

  return <div>...</div>;
}

Ici Component3 et Component4 ne vont générer qu’un seul appel vers le back. Ils souscrivent à la même donnée (même endpoint et même paramètre 3). Ils vont partager la même clé dans le cache.

Au total ici il y aura trois appels vers le back, avec les paramètres 1, 2, 3.

Tant qu’il existe au moins un composant react monté, qui appelle le hook osrdEditoastApi.endpoints.getProjectsByProjectId.useQuery par exemple, la donnée sera conservée dans le cache.

Dès que le dernier composant est démonté, la donnée est supprimée du cache au bout de 60 secondes (valeur par défaut).

Traduction de l’application

La traduction de l’application est effectuée sur Transifex. La langue par défaut est le français. A noter que si l’on doit corriger une traduction, il est recommandé de passer par Transifex. En revanche, si on ajoute une nouvelle clé de traduction, celle-ci peut être ajoutée dans le code directement, dans toutes les langues disponibles.

Lois et éléments importants

Aucun composant ne doit détenir la responsabilité de mise à jour de la donnée qu’il utilise

Seules les vues contiennent les sélecteurs du store, donnés ensuite en props aux composants du module lié à la vue.

Le SCSS n’est pas scopé

Un fichier .scss enfoui dans l’arborescence ne vous garantit pas que les classes contenues soient seulement accessibles à cet endroit, y compris par import react (formellement interdit au passage : vous devez utiliser l’import SCSS), toutes les classes déclarées sont accessibles partout.

Préférez un choix judicieux de nom de classe racine pour un module donné et utilisez l’arborescence possible dans le fichier SCSS.

Les imports doivent être triés d’une certaine manière

ESLint est configuré pour trier automatiquement les imports en 4 groupes, chacun trié alphabétiquement :

  • React
  • Librairies externes
  • Fichiers internes en chemin absolu
  • Fichiers internes en chemin relatif

Chacun de ces groupes est séparé par une ligne vide.

ESLint déclenchera un avertissement si ces recommandations ne sont pas respectées.

Les liens des imports doivent être absolus

Vous devez utiliser le chemin complet pour tous vos imports.

Le chemin peut être relatif seulement si le fichier à importer est dans le même répertoire.

TypeScript

import & export

ESLint et Typescript sont configurés pour imposer l’import type pour un import de type.

Ceci permet de :

  • Automatiquement ajouter type devant l’import quand on ajoute un type avec l’autocomplétion dans un fichier.
  • Afficher 2 erreurs de chacun de ces packages demandant d’ajouter type devant l’import si vous ne l’avez pas fait.

Lorsque qu’un import ou un export ne comporte que des types, il faut l’indiquer par le mot clé type.

export type { Direction, DirectionalTrackRange as TrackRange };
import type { typedEntries, ValueOf } from "utils/types";

Quand un import ne contient pas que des types, il sera agencé de cette manière, par ordre alphabétique.

import {
  osrdEditoastApi,
  type ScenarioCreateForm,
} from "common/api/osrdEditoastApi";

Cette pratique 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.
  • Éviter des cycles de dépendances :

dependency cycle

L’erreur disparaît avec le mot clé type

dependency cycle

  • Alléger le bundle final (tous les types disparaissent à la compilation).

1.3.4 - Écrire du code

Apporter des modifications au code d’OSRD
  1. Si vous n’être pas un habitué de Git, suivez ce tutoriel

  2. Créez une branche
    Si vous comptez contribuer régulièrement, vous pouvez demander accès au dépot principal. Sinon, créez un fork.

  3. 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.

  4. Conservez votre branche à jour

    git switch <your_branch>
    git fetch
    git rebase origin/dev
    

Continuer vers le style des commits ‣

1.3.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

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 commit
  • component: fix #42 : préciser le problème corrigé, les liens (vers l’issue, etc.) sont encouragés dans le corps du commit
  • wip : 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 merge
  • Revert "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’orgine) 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.

Comment signer un commit avec Git dans un terminal ?

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.

Comment signer un commit avec Git dans Visual Studio Code (VS Code) ?

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 ‣

1.3.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).

En cas de PR conséquente, ne pas hésiter à solliciter plusieurs reviewers qui pourront s’organiser, voire de faire la review ensemble, reviewers et auteur.

  1. 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.
  2. 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.

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) :

  1. 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éciquement 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 ou ferme la PR
  Note left of R: Et fusionne si mainteneur

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 ‣

1.3.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.

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 ».

  1. ajouter l’infrastructure et le matériel roulant de test dans la base de données par appels API
  2. créer projet, étude et scénario avec choix de l’infra de test par appels API
  3. 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
  4. 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 ‣

1.4 - Revue de code

Comment faire des retours constructifs

Le reviewer/mainteneur s’engage à effectuer la revue de code rapidement, c’est aussi à lui qu’appartient de fermer les « request changes », de bien vérifier l’historique des commits, et de fusionner la « pull request » s’il en a les droits.

Nous vous soumettons quelques conseils et recommandations qui nous semblent pertinentes pour une revue de code humaine, pertinente et enrichissante pour tous ses contributeurs :

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éciquement 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 ou ferme la PR
  Note left of R: Et fusionne si mainteneur

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).

La pyramide de la revue de code

Script pour le test d’une PR

Lors de la revue d’une PR, il est utile de tester les changements en démarrant une instance de l’application OSRD basée sur la branche de la PR.

Un script est disponible pour lancer automatiquement une instance séparée et dédiée de l’application en utilisant le numéro de la PR. Le script utilise les images Docker déjà construites par la CI et lance l’application de manière isolée. Cela permet d’exécuter les deux instances en parallèle, sans conflits (idéal pour comparer les modifications, par exemple).

De plus, vous pouvez fournir une sauvegarde de la base de données, que le script chargera directement dans l’application.

L’application sera lancée sur le port 4001. Vous pouvez y accéder en suivant : http://localhost:4001/

Commandes Disponibles :

  • ./scripts/pr-tests-compose.sh 8914 up : Télécharge les images CI générées pour la PR #8914 et lance l’application.
  • ./scripts/pr-tests-compose.sh 8914 up-and-load-backup ./path_to_backup : Télécharge les images pour la PR #8914, restaure les données à partir de la sauvegarde spécifiée, et démarre l’application.
  • ./scripts/pr-tests-compose.sh down : Arrête l’instance de test de l’application pour la PR #8914.
  • ./scripts/pr-tests-compose.sh down-and-clean : Arrête l’instance de test et nettoie l’ensemble des volumes docker de l’instance (base de donnée PG, cache Redis, RabbitMQ) pour éviter tout effet de bord.

Accès aux Services :

À l’exception du serveur frontend, tous les services sont disponibles sur localhost, avec un léger ajustement de port (pour éviter les conflits avec l’environnement de développement) : pour la liste des ports, reportez-vous au fichier docker compose dédié.

1.5 - Signaler des problèmes

Comment signaler un bug ou suggérer une amélioration

N’hésitez pas à signaler tout ce qui vous semble important !

Notre outil de suivi des bugs est github. Il est nécessaire de s’inscrire pour signaler un bug.

Suivez ce lien et choisissez le modèle qui correspond à votre cas de figure.

Bugs

  • Le bug doit avoir une description correcte et le modèle du bug doit être rempli avec soin.
  • Le bug doit être étiqueté avec (pour les membres de l’équipe) :
    • kind:bug
    • une ou plusieurs area:<affected_area> si possible (si la zone affectée n’est pas connue, laissez-la vide et elle sera ajoutée plus tard par un autre membre de l’équipe).
    • un severity:<bug_severity> si possible (si la sévérité n’est pas connue, laissez-la vide et elle sera ajoutée plus tard par un autre membre de l’équipe).
      • severity:minor : L’utilisateur peut encore utiliser la fonctionnalité.
      • severity:major : L’utilisateur ne peut parfois pas utiliser la fonctionnalité.
      • severity:critical : L’utilisateur ne peut pas utiliser la fonctionnalité.
  • Les membres de l’équipe OSRD peuvent modifier les étiquettes des problèmes (gravité, domaine, type, …). Vous pouvez laisser un commentaire pour expliquer les changements.
  • Si vous travaillez sur un bug ou planifiez de travailler sur un bug, assignez vous au bug.
  • Les PRs qui résolvent des bugs doivent ajouter des tests de régression pour s’assurer que le bug ne reviendra pas dans le futur.

1.6 - Installer docker

Peu importe votre système d’exploitation, docker requiert linux pour fonctionner. Lorsqu’utilisé sous un autre système d’exploitation, docker a besoin de machines virtuelles linux pour build et exécuter des images.

Il y a deux types d’installation docker :

  • docker engine est l’application en ligne de commande
  • docker desktop est une application graphique, qui gère aussi la virtualisation

Voici nos suggestions :

Docker sous WSL

Cette option d’installation est très utile, car elle permet de disposer d’une installation tout à fait normale de docker engine Linux à l’intérieur de WSL, qui reste accessible depuis Windows.

MacOS colima

Cette procédure permet d’installer docker sans passer par docker desktop. Elle utilise colima comme solution de virtualisation.

  1. Installez homebrew
  2. brew install docker docker-compose colima
  3. Installez le plugin compose : mkdir -p ~/.docker/cli-plugins && ln -sfn $(brew --prefix)/opt/docker-compose/bin/docker-compose ~/.docker/cli-plugins/docker-compose
  4. Configurez colima :
  • pour des macbooks apple silicon (M1/M2) : colima start --cpu 2 --memory 6 --arch aarch64 --vm-type=vz --vz-rosetta --mount-type=virtiofs
  • pour de petites infrastructures: colima start --cpu 2 --memory 4
  • pour de grosses infrastructures: colima start --cpu 2 --memory 6
  1. brew services start colima pour lancer automatiquement colima au démarrage
  2. Quittez votre terminal, ouvrez-en un nouveau
  3. Vous pouvez maintenant utiliser docker CLI

1.7 -

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éciquement 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 ou ferme la PR
  Note left of R: Et fusionne si mainteneur

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).

2 - Déployer OSRD

Apprenez à déployer OSRD dans différents environnements

Tout d’abord, nous recommandons de se familiariser sur l’architecture des conteneurs d’OSRD.

Nous allons couvrir comment déployer OSRD dans les configurations suivantes :

Il est également possible de déployer manuellement chaque service d’OSRD sur un système, mais nous ne couvrirons pas ce sujet dans ce guide.

2.1 - Docker Compose

Utiliser docker compose pour un déploiement sur un seul nœud

Le projet OSRD inclut un fichier docker-compose.yml conçu pour faciliter le déploiement d’un environnement OSRD pleinement fonctionnel. Exclusivement destiné à des fins de développement, cette configuration Docker Compose pourrait être adaptée pour des déploiements rapides sur un seul nœud.

Prérequis

Avant de procéder au déploiement, assurez-vous que vous avez installé :

  • Docker
  • Docker Compose

Vue d’ensemble de la configuration

Le fichier docker-compose.yml définit les services suivants :

  1. PostgreSQL : Une base de données PostgreSQL avec l’extension PostGIS.
  2. Redis : Un serveur Redis pour le cache.
  3. Core : Le service central OSRD.
  4. Front : Le service front-end pour OSRD.
  5. Editoast : Un service OSRD responsable de diverses fonctions d’édition.
  6. Gateway : Sert de passerelle pour les services OSRD.
  7. Wait-Healthy : Un service utilitaire pour s’assurer que tous les services sont sains avant de procéder.

Chaque service est configuré avec des contrôles de santé, des montages de volumes et les variables d’environnement nécessaires.

Étapes du déploiement

  1. Cloner le dépôt : Tout d’abord, clonez le dépôt OSRD sur votre machine locale.
  2. Configuration : La configuration par défaut nécessite le renseignement d’une variable d’environnement pour le service Editoast: ROOT_URL. Il faut lui donner la valeur de l’URL qui pointe vers le service Editoast par la gateway. Par exemple “http://your-domain.com/api". Vous pouvez également ajuster d’autres variables d’environnement si nécessaire.
  3. Construire et exécuter : Naviguez vers le répertoire contenant docker-compose.yml et exécutez :
docker-compose up --build

Cette commande construit les images et démarre les services définis dans le fichier Docker Compose.

Accès aux services

Bien que tous les services HTTP soient utilisés via la passerelle (http://localhost:4000), vous pouvez accéder directement à chaque service en utilisant leurs ports exposés :

  • PostgreSQL : Accessible sur localhost:5432.
  • Redis : Accessible sur localhost:6379.
  • Service Core : Accessible sur localhost:8080.
  • Front-End : Accessible sur localhost:3000.
  • Editoast : Accessible sur localhost:8090.

Notes et considérations

  • Cette configuration est conçue pour le développement et les déploiements rapides. Pour les environnements de production, des considérations supplémentaires en matière de sécurité, de scalabilité et de fiabilité doivent être abordées.
  • Assurez-vous que le POSTGRES_PASSWORD et d’autres identifiants sensibles sont gérés en toute sécurité, en particulier dans les déploiements de production.

2.2 - Kubernetes avec Helm

Utilisation de Helm pour les déploiements Kubernetes

La Helm Chart du projet OSRD fournit une solution pour déployer les services OSRD dans un environnement Kubernetes de manière standardisée. Ce document décrit les options de configuration disponibles dans le Helm Chart.

Prérequis

Avant de procéder au déploiement, assurez-vous que vous avez installé :

  • Un cluster Kubernetes opérationnel
  • Une base de données PostgreSQL avec PostGIS
  • Un serveur Redis (utilisé pour le cache)

Le serveur de tuiles

Le serveur de tuiles est le composant responsable de la génération des tuiles cartographiques vectorielles. Il est recommandé de le séparer du Editoast standard lors de l’exécution d’une configuration de production, car Editoast ne peut pas être mis à l’échelle horizontalement (il est stateful).

Vous pouvez visualiser le déploiement recommandé ici :

flowchart TD
    gw["gateway"]
    front["fichier statiques front-end"]
    gw -- fichier local --> front
    
    navigateur --> gw
    gw -- HTTP --> editoast
    gw -- HTTP --> tileserver-1
    gw -- HTTP --> tileserver-2
    gw -- HTTP --> tileserver-n...
    editoast -- HTTP --> core

Le Helm Chart utilise leHorizontalPodAutoscaler de Kubernetes pour lancer autant de serveurs de tuiles que nécessaire en fonction de la charge de travail.

Configuration de la Helm Chart (values)

Le Helm Chart est configurable à travers les valeurs suivantes :

Service Core

  • core : Configuration pour le service central OSRD.
    • internalUrl : URL interne pour la communication entre services.
    • image : Image Docker à utiliser.
    • pullPolicy : Politique de récupération de l’image.
    • replicaCount : Nombre de réplicas.
    • service : Type de service et configuration des ports.
    • resources, env, annotations, labels, nodeSelector, tolerations, affinity : Diverses options de déploiement Kubernetes.

Service Editoast

  • editoast : Configuration pour le service Editoast.
    • Comprend des options similaires à core pour le déploiement Kubernetes.
    • init : Configuration d’initialisation.

Serveur de tuiles

  • tileServer : Service Editoast spécialisé qui sert uniquement des tuiles cartographiques vectorielles.
    • enabled : Définir sur true pour activer la fonctionnalité de serveur de tuiles.
    • image : Image Docker à utiliser (généralement la même que Editoast).
    • replicaCount : Nombre de réplicas, permettant la mise à l’échelle horizontale.
    • hpa : Configuration de l’Horizontal Pod Autoscaler.
    • Autres options standard de déploiement Kubernetes.

Gateway

  • gateway : Configuration pour le gateway OSRD.
    • Comprend des options de service, d’ingress et d’autres options de déploiement Kubernetes.
    • config : Configurations spécifiques pour l’authentification et les proxys de confiance.

Déploiement

Le chart est disponible dans le dépôt OCI ghcr. Vous pouvez trouver 2 versions de la chart :

Pour déployer les services OSRD en utilisant Helm :

  1. Configurer les valeurs : Ajustez les valeurs selon vos besoins de déploiement.

  2. Installer le Chart : Utilisez la commande helm install pour installer la chart dans votre cluster Kubernetes.

    helm install osrd oci://ghcr.io/osrd-project/charts/osrd -f values.yml
    

3 - Le design d'OSRD

Les couleurs, la police, les usages…

Tout est présenté dans un site dédié https://design.osrd.fr

Un « design system » est en cours d’élaboration.

4 - Le logo

Le logo d’OSRD, ses variantes, et son usage

Vous pouvez télécharger chaque logo indépendamment en cliquant directement dessus, ou tous les logos compressés dans un fichier zip.

Il est conseillé de bien choisir le logo à utiliser en fonction du fond sur lequel vous voulez l’afficher.

La modification, ajout ou suppression de l’ombrage autre que tel que présenté dans les logos ne sont pas autorisés (cela est valable plus globalement dans tout le design, le choix de mettre des ombres portées fait partie des réflexions de design, ce n’est pas un élément variable).

Officiel

Officiel pour les fonds sombres

Blanc

Noir

Favicons, logos seuls

🚫 Ce qu’on ne doit pas faire

Trop petit (< 16px de hauteur)

Disproportion

Changer la couleur du texte ou l’ombre portée

Changer le sens

Déformation

✅ Ce qu’on peut faire

Modification de la couleur interne pour un évènement

Utilisation seule du logo (sans le texte)

Les couleurs

Ces couleurs sont celles du logo, elles ne sont pas à confondre avec celles du design global de l’interface d’OSRD.

#786ABF #C7B2DE