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...
Couleur | Code hexadecimal |
---|---|
Bleu Klein | #003CDC |
Bleu profond | #000F41 |
Jaune | #FFC800 |
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.
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.
Couleur | Code 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.
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.
- JavaScript
- TypeScript
import React from "react";
//...
export const UserValues = ({ name, age }) => {
const getYearOfBirth = () => {
const thisYear = new Date();
return thisYear.getYear() + 1900 - age;
};
return <div>...</div>;
};
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>;
};
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.
À 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 :
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.
- Admin Router
- Player Router
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]
/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
/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
/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
/admin/gameday/infrastructures
Affiche les infrastructures de chaque compte du gameday, visualisé graphiquement grâce au CloudMapper.
Chaos : /admin/gameday/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.
Staffing : /player/staffing
Permet au de suivre d'effectuer la session de recrutement.
Gameday : /player/gameday
Pour les informations d'un gameday.
Permet d'abord de créer un gameday, puis , d'utiliser le mode chaos, d'attribuer des pénalités...
Règles du jeu : /player/gameday/rules
Permet au joueur de connaître les règles et le déroulement d'un gameday.
Étapes à suivre : /player/gameday/scenario
Pour avoir les étapes du scénario à suivre.
Tableau des scores : /player/gameday/scores
Permet le suivi des scores des différentes équipes : où elles en sont en terme de profit, qui est le leader, quelles infrastructures rapportent le plus...
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 :
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>;
//...
};
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 Hook | Description |
---|---|
useForm | Permet 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. |
useDocumentTitle | Permet d'aisément modifier le titre du document HTML de manière dynamique. |
useGameConfig | Trè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...) |
useStaffingValues | Logique 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
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'arborescenceApp > 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 :
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).
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.
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'arborescenceApp > 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 :
import React from "react";
import { useScenarioDataContext } from "../../context/ScenarioDataContext";
export const StaffSetup: React.FC = () => {
// ...
const { scenarioData, isScenarioDataLoading } = useScenarioDataContext();
// ...
};
Mais... et Redux alors ?
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.