Skip to main content

Front-end (dashboard)

1️⃣ Présentation

L'application web telle qu'elle est aujourd'hui contient 2 applications, à l'origine séparée en 2 dépôts très similaires : celle pour les joueurs, et celle des administrateurs.

Pour les joueurs comme pour les administrateurs, le dashboard permet de gérer les 3 modes de jeux Gameday, Staffing et CloudGame.

Objectifs principaux

Adminstrateurs

L'objectif principal du dashboard administrateur est de créer et manager les sessions de jeu.

Lors d'un Gameday, il permet d'abord de créer le jeu en sélectionnant les comptes, le scénario et ses étapes, et d'attribuer une équipe à chaque compte. Ensuite, il est également possible via l'application d'administrer le jeu, de surveiller les infrastrucures des équipes via le CloudMapper, et d'attribuer des pénalités via le mode Chaos (que celles-ci soient programmées dans le déroulement du Gameday afin de rééquilibrer les forces, ou spontanées pour éviter des triches lors du jeu).

Joueurs

L'utilité principale pour les joueurs est de lire le scénario à réaliser.

En outre, dans le cadre d'un staffing ils peuvent visualiser leur infrastructure en temps réel via l'onglet CloudMapper, et pour un gameday ils peuvent suivre l'évolution des scores selon le temps, l'état des différentes teams, la répartition des coûts par structure...

2️⃣ Charte graphique

Charte actuelle

La charte actuellement présente sur l'application correspond à l'identité graphique d'Ippon. On y retrouver les mêmes polices d'écritures, couleurs...

CouleurCode hexadecimal
Bleu Klein#003CDC
Bleu profond#000F41
Jaune#FFC800

alt text

Pour ce qui est de la typographie, celle-ci réfère également à celle d'Ippon, à savoir Saira ExtraCondensed pour les titres, et Open Sans pour le texte.

alt text

Nouvelle identité

Suite à l'annonce du Gameday interne, de nouveaux éléments graphiques sont parvenus pour le Cloud Challenge, amenés par Alice DEROYE (aderoye@ippon.fr).

Parmi ceux-là, on retrouve un nouveau logo, de nouvelles couleurs et une nouvelle philosophie dans le design avec des formes plus douces, plus arrondies.

alt text

CouleurCode hexadecimal
Bleu Klein#003CDC
Bleu marine vif#0777d9
Turquoise#56dbc6

Retrouvez ci-dessous la charte graphique mise en mouvement avec un extrait de la vidéo de présentation du Cloud Challenge interne, également créé par Alice DEROYE.

alt text

La plupart des assets sont partagés dans le Google Drive suivant : https://drive.google.com/drive/folders/1DXdujkACzNxD-TSU9L_zqAD5oa5bbmpw?usp=sharing.

Ne pas hésiter à demander les accès ou à contacter tmorassin@ippon.fr.

3️⃣ Maintenabilité

TypeScript

Afin de faciliter la maintenabilité du code, et partiellement sa documentation, l'entièreté du front-end a été migrée du JavaScript vers le TypeScript.

Voici ci-dessous un exemple de la migration en TypeSprit d'un composant React.

/src/player/gameday/UserValues.tsx
import React from "react";
//...

interface UserValuesProps {
name: string;
age: number;
}

export const UserValues: React.FC<UserValuesProps> = ({ name, age }) => {
const getYearOfBirth: number = () => {
const thisYear: Date = new Date();

return thisYear.getYear() + 1900 - age;
};

return <div>...</div>;
};
intérêt

Le TypeScript permet de typer fortement les constantes et fonction, afin d'améliorer et de sécuriser la production de code JavaScript, notamment en évitant au build certaines erreurs de typage pouvant survenir à l'utilisation.

Tests unitaires

Certains composants du front sont maintenant couverts par des tests unitaires afin de s'assurer de la non régression des composants.

La librairie utilisée est Testing Library. Celle-ci permet principalement de tester non pas les states des composants React eux-mêmes, mais plutôt l'état du DOM généré en fonction. Il est donc possible de s'assurer de l'affichage de certains éléments, de vérifier que la navigation se fait correctement, dmais également de simuler des actions utilisateur : click, hover...

En ce jour, près de 32% du code est couvert par les tests, avec un total de 68 tests.

4️⃣ Architecture

Arborescence des fichiers

Le principal de l'application se trouve dans le sous-dossier /dashboard/src. Les fichiers sont principalement divisés entre les fichiers TSX communs, ceux spécialement conçus pour l'interface administrateur, et ceux de l'interface des joueurs.

├── src
│ ├── __test__utils # Contenant des fichiers utilitaires pour les tests unitaires
│ ├── admin # Dossier spécialisé pour les composants admin
│ │ ├── gameday # Mode de jeu Gameday
│ │ └── staffing # Session de Staffing
│ ├── assets # Les images en format (.png, .svg et .json)
│ ├── common # Pour les composants communs, partagés entre admin et players
│ ├── context # Contenant les déclarations de l'API Context
│ ├── home # Pour la page d'accueil de l'application
│ ├── hooks # Contenant les custom hooks de React
│ ├── interfaces # Types et interfaces des objects de l'application
│ └── player # Dossier spécialisé pour les composants player
│ │ ├── event # CloudGame (mode salon)
│ │ ├── gameday # Mode de jeu Gameday
│ │ └── staffing # Session de Staffing

Les composants communs

/common

Dossier contenant 2 types de fichiers :

  • des helpers, contenant des fonctions génériques, réutilisables partout (possiblement dans d'autres projets),
  • des composants spécifiques au Cloud Challenge, mais partagés entre les pages admin et players.
/hooks et /context

Le dossier /hooks contient tous les custom hooks, partagés au sein de multiple composants.

Le dossier /context lui contient les context et exporte leur provider, avec toute la logique allant avec.

Pour des explications plus précises avec des exemples, se réferer à la partie 5️⃣ Spécifications techniques.

/home

Dossier contenant les deux fichiers spécifique à la page d'accueil, à savoir HomePage.tsx et le fichier HomePage.test.tsx qui l'accompagne.

Les dossiers spécifiques aux rôles

/admin

Ce dossier contient tous les fichiers relatifs aux pages et composants spécialisés pour les administrateurs. En son sein, on retrouve des sous-dossiers correspondants aux différentes pages : /gameday, /staffing mais également le fichier DashboardDanger.tsx.

/player

Exactement le même fonctionnement que pour le dossier /admin : contient tous les fichiers relatifs aux pages et composants spécialisés pour les joueurs. En son sein, on retrouve des sous-dossiers correspondants aux différentes pages : /event, /gameday et /staffing, mais également un dossier /helpers pouvant être refactorisé.

Les fichiers divers

/assets

Ce dossier contient l'ensemble des images utilisées dans l'application. Le format .svg est privilégié afin pour des raisons de qualité d'image et de performance.

Lottiefiles

À noter que les fichiers au format .json sont des fichiers conçus pour la librairie graphique Lottie permettant des animations faciles à utiliser et au poids plume.

/interfaces

Ce dossier contient les interfaces permettant le typage des éléments Typescript. Par exemple :

/src/interfaces/Account.ts
import { Level } from "./Types";

export interface Account {
accountId: string;
name: string;
masterPassword: string;
level: Level;
color: string;
}

Le routing (navigation)

La première page, rendue à la racine de l'application est la page src/home/HomePage.tsx, où les utilisateurs peuvent choisir leur rôle, entre celui d'administrateur et celui de joueur.

Staffing : /admin/staffing

Pour gérer les sessions de recrutement en OneToOne.

Permet soit de créer une nouvelle session, soit de naviger parmis les sessions existantes.

Session spécifique au candidat : /admin/staffing/[name]

Permet de superviser la session en suivant en temps réel l'avancement du candidat, ainsi que de visualiser son infrastructure.

Gameday : /admin/gameday

Pour gérer les gameday.

Permet d'abord de créer un gameday, puis , d'utiliser le mode chaos, d'attribuer des pénalités...

Route de base : /admin/gameday

Permet d'abord de créer un gameday en choissant la difficulté, les comptes et les étapes du scénario, puis d'afficher toutes les informations relative au gameday une fois celui-ci créé.

Comptes : /admin/gameday/accounts

Liste tous les comptes utilisés pour le gameday. Permet d'abord d'attribuer un compte à chaque équipe, puis une fois attribué d'accéder à la console AWS du coup, et permet étalement de nettoyer ce compte.

Infrascrutures : /admin/gameday/infrastructures

Affiche les infrastructures de chaque compte du gameday, visualisé graphiquement grâce au CloudMapper.

Chaos : /admin/gameday/chaos

Pour utiliser le mode chaos : terminer une instance EC2 pour toutes les équipes, terminer toutes les instances sur une ou plusieurs Région, ou encore attribuer des pénalités de points aux équipes essayant de tricher.

DangerZone : /admin/dangerzone

Pour gérer les comptes AWS.

Permet d'enregistrer une équipe si un gameday est lancé avec ce compte, et de nettoyer ce compte ou de réinitialiser tous les comptes.

5️⃣ Spécifications techniques

Custom hooks

Lorsque la logique d'un composant survient plusieurs fois dans l'application, et la logique seule, une bonne pratique consiste à l'exporter pour éviter la duplication de code.

Pour faire ceci, on crée une fonction ressemblant fortement à un composant, en ayant pour convention de nommage de commencer par use[...], comme useFetch() par exemple.

Avec un exemple, c'est mieux

Ainsi, le code de notre custom hook "useFetch" ressemblerait à ceci :

/src/hooks/useFetch.ts
import { useEffect, useState } from "react";
import axios from "axios";

export const useFetch = (fetchUrl: string) => {
// On gère la logique pouvant impacter l'interface utilisateur :
// les données, le chargement et la gestion des erreurs
const [data, setData] = useState<any>({});
const [isLoading, setIsLoading] = useState<boolean>(true);
const [hasError, setHasError] = useState<boolean>(false);

// On fetch les données
useEffect(() => {
setIsLoading(true);
axios
.get(fetchUrl)
.then((response) => {
setData(response.data);
setIsLoading(false);
})
.catch((error) => {
setHasError(true);
console.error(error);
setIsLoading(false);
});
}, []);

// On renvoie toute la logique
return { data, isLoading, hasError };
};

Ainsi, dans tous les autres composants nécessitant également de récupérer les données depuis une API, on pourra importer et utiliser cette logique :

import React from "react";
import { data, isLoading, hasError } from "../hooks/useFetch.tsx";
//...

export const UserDataComponent: React.FC = () => {
const fetchUrl = "some url endpoint";
const { data, isLoading, hasError } = useFetch(fetchUrl);

if (isLoading) {
return <Loader />;
}

if (hasError) {
return <ErrorComponent />;
}

return <p>...</p>;
//...
};
useHooks

D'excellents exemples de custom hooks sont illustrés dans cette librairie : https://usehooks-ts.com/.

Les custom hooks du Cloud Challenge

Au sein de l'application, quatre hooks sont utilisés :

Le HookDescription
useFormPermet de gérer les formulaires de l'application : toutes les valeurs et leur logique de changement, à partir d'un template qu'on lui donne en entrée.
useDocumentTitlePermet d'aisément modifier le titre du document HTML de manière dynamique.
useGameConfigTrès fortement similaire au hook useFetch de l'exemple précédent, celui-ci permet de gérer les données gameConfig (contenant les comptes, le niveau de jeu, les étapes du scénario...)
useStaffingValuesLogique partagée entre la page de Staffing de l'administrateur et de celle du joueur, ce hook permet de récupérer les données de l'objet "équipe" correspondant au candidat, et de renvoyer uniquement les étapes qui ont été sélectionnées pour cette session.

API Context : résolution des problèmes de props drilling

le props drilling ?

En React, le props drilling consiste à "percer" l'arbre des composants de notre application en faisant passer des valeurs des composants parents vers les composants enfants, par le biais des props.

Avec un exemple, c'est toujours mieux :

const App: React.FC = () => {
const userData = {...};
return (
<Router userData={userData} />
);
};

// ... which renders ...
<PageLayout userData={props.userData} />

// ... which renders ...
<ProfilePageContent userData={props.userData} />

// ... which renders ...
<UserInformations userData={props.userData} />

// ... which renders ...
const UserCard = (props) => {
return <p>{props.userData.name}</p>;
}

Le props drilling au sein du Cloud Challenge

En cas pratique sur l'application, auparavant, les données nécessaires étaient fetch à la racine de l'application, comme les scénarios par exemple. En faisant ainsi, si un composant nécessite des données, celles-ci devaient passer à travers l'entièreté de l'application.

StaffSetup nécessite les scénarios, donc la prop scénario devait passer à travers l'arborescence App > AppRouter > PlayerRoutes > DashboardStaffing > TabSession > StaffSetup.

La solution : l'API Context

L'API Context de React permet de faire passer les données à travers de l'arborescence, en la rendant accessible directement depuis n'importe quel niveau. On comprend ainsi que cette API est particulièrement utile et puissante pour résoudre les problèmes de props drilling.

Comment mettre en place un contexte

Reprenons notre exemple des scénarios. Nous allons maintenant d'abord créer notre contexte :

/src/context/ScenarioDataContext.tsx
interface ScenarioDataContextProps {
scenarioData: ScenarioData;
isScenarioDataLoading: boolean;
scenarioDataHasError: boolean;
}

const ScenarioDataContext = createContext<ScenarioDataContextProps>({
scenarioData: {},
isScenarioDataLoading: false,
scenarioDataHasError: false,
});

// On vient créé une sorte de hook, permettant aux enfants d'accéder aux valeurs du contexte
export const useScenarioDataContext = () => useContext(ScenarioDataContext);

Notre contexte est maintenant créé. Il faut maintenant créer le Provider, qui est un composant qui va englober notre application et permettre à tous ses éléments enfants de consumer le contexte (et donc d'accéder aux valeurs partagées).

/src/context/ScenarioDataContext.tsx
export const ScenarioDataProvider: React.FC = ({ children }) => {
const [scenarioData, setScenarioData] = useState<ScenarioData>({});
const [isScenarioDataLoading, setIsScenarioDataLoading] = useState<boolean>(true);
const [scenarioDataHasError, setScenarioDataHasError] = useState<boolean>(false);

useEffect(() => {
// On vient récupérer les données des scénario, et gérer la logique (data, loading, error)
fetchData();
}, []);

// On renvoie le Provider lié au contexte précédemment créé, en lui donnant nos valeurs
return (
<ScenarioDataContext.Provider value={{ scenarioData, isScenarioDataLoading, scenarioDataHasError }}>
{children}
</ScenarioDataContext.Provider>
);
};

Dernière étape, et tout sera enfin prêt : il faut englober l'application avec le Provider de notre ScenarioDataContext. Seulement, il faut choisir avec précaution à quel moment utiliser le provider. Dans notre cas particulier, on ne veut aller récupérer les données qu'une fois que l'administrateur est connecté, et pas avant. Ainsi, on va l'utiliser dans le composant AdminRoutes, qui parvient après l'authentification Keycloak.

/src/AdminRoutes.tsx
const AdminRoutes: React.FC = () => {
return (
<ScenarioDataProvider>
<Routes>
<Route index element={<WelcomePage role={"admin"} />} />
// ...
</Routes>
</ScenarioDataProvider>
);
};

Comment utiliser les données du contexte

Pour rappel, auparavant :

StaffSetup nécessite les scénarios, donc la prop scénario devait passer à travers l'arborescence App > AppRouter > PlayerRoutes > DashboardStaffing > TabSession > StaffSetup.

Maintenant, cette propriété scénario n'a plus besoin d'exister, et donc de venir perforer l'arborescence. C'est le composant StaffSetup qui va venir consumer les valeurs du contexte, grâce au hook créé précédemment :

src/player/staffing/StaffSetup
import React from "react";
import { useScenarioDataContext } from "../../context/ScenarioDataContext";

export const StaffSetup: React.FC = () => {
// ...
const { scenarioData, isScenarioDataLoading } = useScenarioDataContext();
// ...
};

Mais... et Redux alors ?

Redux

Redux (https://redux.js.org/) est une libraire de state management développé pour faciliter la gestion des états sur des applications React. Elle est très connue et largement utilisée en raison de ses performances, et particulièrement utile sur de grandes applications avec notamment de très nombreux états et changements d'états.

A priori, Redux aurait pu être un bon choix pour éviter le props drilling, et gérer globalement les états. Seulement, le dashboard du Cloud Challenge ne constitue pas une grosse application, et n'a pas de très nombreux changements d'états passant au travers de l'application.

En réalité, seulement 3 états principaux traversent l'application : scenario, gameConfig, et state (scores). Et ces 3 objets-là se voient être invariables : scenario et gameConfig sont simplement fetch au début du parcours utilisateur, et state lui est refraîchi toutes les 10 secondes dans le cadre d'un gameday. Ainsi, il n'y a pas de mise à jour de l'état provenant de l'application.

Les besoins de l'application consistent finalement en la lecture seule de données. Pour répondre à ce besoin, l'API Context se voit être tout à fait adaptée. Et au contraire, une solution comme Redux serait non seulement "overkill" car utilisée à faible capacité, mais engendrerait également des dépendances externes alors inutiles.