From 268289f1f70317c44680aa129a83f1635ee025e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matt=C3=A9o=20Delabre?= Date: Wed, 4 Dec 2019 18:54:44 -0500 Subject: [PATCH] =?UTF-8?q?app:=20Nettoyage=20du=20mod=C3=A8le=20de=20donn?= =?UTF-8?q?=C3=A9es?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/.eslintrc.json | 14 ++++- app/src/components/App.js | 2 +- app/src/components/DiseaseGraph.js | 21 ++++++- app/src/components/Graph.js | 10 ++++ app/src/components/TermInput.js | 8 +++ app/src/data/fuzzy.js | 5 ++ app/src/data/{mock.js => mock/data.js} | 60 +++++++++++--------- app/src/data/{fetch.js => mock/index.js} | 22 +++++--- app/src/data/model.js | 72 ++++++++++++++++++++++++ app/src/util.js | 2 +- package.json | 1 + 11 files changed, 175 insertions(+), 42 deletions(-) rename app/src/data/{mock.js => mock/data.js} (81%) rename app/src/data/{fetch.js => mock/index.js} (88%) create mode 100644 app/src/data/model.js diff --git a/app/.eslintrc.json b/app/.eslintrc.json index 7a6e58e..f547758 100644 --- a/app/.eslintrc.json +++ b/app/.eslintrc.json @@ -21,6 +21,11 @@ "plugins": [ "react" ], + "settings": { + "react": { + "version": "detect" + } + }, "rules": { "indent": [ "error", @@ -37,6 +42,13 @@ "semi": [ "error", "always" + ], + "no-irregular-whitespace": [ + "error", + { + "skipStrings": true, + "skipTemplates": true + } ] } -} \ No newline at end of file +} diff --git a/app/src/components/App.js b/app/src/components/App.js index db06a15..626f990 100644 --- a/app/src/components/App.js +++ b/app/src/components/App.js @@ -5,7 +5,7 @@ import {useAsync} from '../util.js'; import { diseasesBySymptoms, exploreSymptoms -} from '../data/fetch.js'; +} from '../data/mock'; const App = () => { diff --git a/app/src/components/DiseaseGraph.js b/app/src/components/DiseaseGraph.js index 250f0bd..2b4f538 100644 --- a/app/src/components/DiseaseGraph.js +++ b/app/src/components/DiseaseGraph.js @@ -1,9 +1,18 @@ import React from 'react'; +import PropTypes from 'prop-types'; import Graph from './Graph.js'; -import {types} from '../data/mock.js'; import {useAsync} from '../util.js'; -import {symptomsSubgraph} from '../data/fetch.js'; +import {Term, termTypes} from '../data/model.js'; +import {symptomsSubgraph} from '../data/mock'; +/** + * Graphe de maladies et symptômes. + * + * @prop terms Ensemble de symptômes recherchés par l’utilisateur. + * @prop setTerms Fonction de rappel pour ajouter de nouveaux termes de + * recherche. + * @prop results Maladies correspondant à la recherche de l’utilisateur. + */ const DiseaseGraph = ({terms, setTerms, results}) => { const {nodes, edges} = useAsync({ @@ -19,7 +28,7 @@ const DiseaseGraph = ({terms, setTerms, results}) => const render = id => { const isTerm = terms.some(({id: termId}) => termId === id); - const isDisease = nodes[id].types.includes(types.disease); + const isDisease = nodes[id].types.includes(termTypes.disease); return ( ); }; +DiseaseGraph.propTypes = { + terms: PropTypes.arrayOf(Term).isRequired, + setTerms: PropTypes.func.isRequired, + results: PropTypes.arrayOf(Term).isRequired, +}; + export default DiseaseGraph; diff --git a/app/src/components/Graph.js b/app/src/components/Graph.js index 8179a03..6976d0c 100644 --- a/app/src/components/Graph.js +++ b/app/src/components/Graph.js @@ -1,6 +1,8 @@ import React, {useState, useRef, useEffect} from 'react'; +import PropTypes from 'prop-types'; import {TransitionGroup, CSSTransition} from 'react-transition-group'; import Springy from 'springy'; +import {Relation} from '../data/model.js'; /** * Échappe une valeur utilisée dans un sélecteur d’attributs CSS. @@ -343,4 +345,12 @@ const Graph = ({ ); }; +Graph.propTypes = { + nodes: PropTypes.arrayOf(PropTypes.any).isRequired, + edges: PropTypes.arrayOf(Relation).isRequired, + emptyMessage: PropTypes.string.isRequired, + render: PropTypes.func.isRequired, + onNodeClick: PropTypes.func.isRequired, +}; + export default Graph; diff --git a/app/src/components/TermInput.js b/app/src/components/TermInput.js index a181f76..0eca385 100644 --- a/app/src/components/TermInput.js +++ b/app/src/components/TermInput.js @@ -1,4 +1,6 @@ import React, {useState, useRef} from 'react'; +import PropTypes from 'prop-types'; +import {Term} from '../data/model.js'; import * as fuzzy from '../data/fuzzy.js'; import * as util from '../util.js'; @@ -196,4 +198,10 @@ const TermInput = ({terms, availableTerms, setTerms}) => ); }; +TermInput.propTypes = { + terms: PropTypes.arrayOf(Term).isRequired, + availableTerms: PropTypes.arrayOf(Term).isRequired, + setTerms: PropTypes.func.isRequired +}; + export default TermInput; diff --git a/app/src/data/fuzzy.js b/app/src/data/fuzzy.js index ec05542..9315b1c 100644 --- a/app/src/data/fuzzy.js +++ b/app/src/data/fuzzy.js @@ -1,3 +1,8 @@ +/** + * @module + * Fonctions permettant la recherche approximative de termes. + */ + import * as diacritics from 'diacritics'; /** diff --git a/app/src/data/mock.js b/app/src/data/mock/data.js similarity index 81% rename from app/src/data/mock.js rename to app/src/data/mock/data.js index 195629c..b331257 100644 --- a/app/src/data/mock.js +++ b/app/src/data/mock/data.js @@ -3,14 +3,7 @@ * Jeu de données d’exemple. */ -/** - * Types de termes de la base. - */ -export const types = { - disease: 'Maladie', - symptom: 'Symptôme', - sign: 'Signe', -}; +import {termTypes} from '../model.js'; /** * Liste des termes de la base de données, contenant des maladies, des signes @@ -21,7 +14,7 @@ export const terms = { id: 'Q2840', name: 'Grippe', alias: [], - types: [types.disease], + types: [termTypes.disease], weight: 0.000035, }, Q154882: { @@ -30,14 +23,14 @@ export const terms = { alias: [ 'Maladie des légionnaires', ], - types: [types.disease], + types: [termTypes.disease], weight: 0.000015, }, Q155098: { id: 'Q155098', name: 'Leptospirose', alias: [], - types: [types.disease], + types: [termTypes.disease], weight: 0.00001, }, Q326663: { @@ -53,14 +46,14 @@ export const terms = { 'Maladie de Schneider', 'Maladie de Kumlinge', ], - types: [types.disease], + types: [termTypes.disease], weight: 0.000001, }, Q133780: { id: 'Q133780', name: 'Peste', alias: [], - types: [types.disease], + types: [termTypes.disease], weight: 0.000032, }, Q38933: { @@ -69,7 +62,8 @@ export const terms = { alias: [ 'Pyrexie', ], - types: [types.symptom], + types: [termTypes.symptom], + weight: 0, }, Q474959: { id: 'Q474959', @@ -77,7 +71,8 @@ export const terms = { alias: [ 'Douleur musculaire', ], - types: [types.symptom], + types: [termTypes.symptom], + weight: 0, }, Q86: { id: 'Q86', @@ -85,7 +80,8 @@ export const terms = { alias: [ 'Mal de tête', ], - types: [types.sign], + types: [termTypes.sign], + weight: 0, }, Q1115038: { id: 'Q1115038', @@ -94,13 +90,15 @@ export const terms = { 'Nez qui coule', 'Écoulement nasal', ], - types: [types.symptom], + types: [termTypes.symptom], + weight: 0, }, Q9690: { id: 'Q9690', name: 'Fatigue', alias: [], - types: [types.symptom], + types: [termTypes.symptom], + weight: 0, }, Q127076: { id: 'Q127076', @@ -110,7 +108,8 @@ export const terms = { 'Vomissage', 'Vomir', ], - types: [types.symptom, types.sign], + types: [termTypes.symptom, termTypes.sign], + weight: 0, }, Q178061: { id: 'Q178061', @@ -118,7 +117,7 @@ export const terms = { alias: [ 'Insuffisance circulatoire aiguë', ], - types: [types.disease], + types: [termTypes.disease], weight: 0.000038, }, Q35805: { @@ -128,7 +127,8 @@ export const terms = { 'Tousse', 'Toussote', ], - types: [types.symptom, types.sign], + types: [termTypes.symptom, termTypes.sign], + weight: 0, }, Q647099: { id: 'Q647099', @@ -136,13 +136,15 @@ export const terms = { alias: [ 'Expectoration sanglante', ], - types: [types.symptom], + types: [termTypes.symptom], + weight: 0, }, Q653197: { id: 'Q653197', name: 'Rash', alias: [], - types: [types.symptom, types.sign], + types: [termTypes.symptom, termTypes.sign], + weight: 0, }, Q160796: { id: 'Q160796', @@ -150,28 +152,29 @@ export const terms = { alias: [ 'Confusion mentale', ], - types: [types.disease], + types: [termTypes.disease], weight: 0.000004, }, Q186235: { id: 'Q186235', name: 'Myocardite', alias: [], - types: [types.disease], + types: [termTypes.disease], weight: 0.0000075, }, Q476921: { id: 'Q476921', name: 'Insuffisance rénale', alias: [], - types: [types.disease], + types: [termTypes.disease], weight: 0.0000046, }, Q281289: { id: 'Q281289', name: 'Photophobie', alias: [], - types: [types.sign], + types: [termTypes.sign], + weight: 0, }, Q159557: { id: 'Q159557', @@ -181,7 +184,8 @@ export const terms = { 'Coma végétatif', 'Perte de connaissance', ], - types: [types.sign], + types: [termTypes.sign], + weight: 0, }, }; diff --git a/app/src/data/fetch.js b/app/src/data/mock/index.js similarity index 88% rename from app/src/data/fetch.js rename to app/src/data/mock/index.js index 7af1913..dd21fc6 100644 --- a/app/src/data/fetch.js +++ b/app/src/data/mock/index.js @@ -1,4 +1,10 @@ -import * as mock from './mock.js'; +/** + * @module + * Fonctions de requêtage des données d’exemple. + */ + +import {termTypes} from '../model.js'; +import * as data from './data.js'; /** * Recherche l’ensemble des maladies liées par une relation « a pour symptôme » @@ -14,7 +20,7 @@ export const diseasesBySymptoms = async query => if (!query.length) { // Si aucun terme dans la requête, tout correspond - allMatches = Object.values(mock.terms).map(({id}) => id); + allMatches = Object.values(data.terms).map(({id}) => id); } else { @@ -32,7 +38,7 @@ export const diseasesBySymptoms = async query => while (stack.length) { const current = stack.pop(); - const neighbors = mock.symptomOf.filter( + const neighbors = data.symptomOf.filter( ([from]) => from === current ).map( ([, to]) => to @@ -65,9 +71,9 @@ export const diseasesBySymptoms = async query => // On ne garde que les maladies return allMatches.map( - id => mock.terms[id] + id => data.terms[id] ).filter( - term => term.types.includes(mock.types.disease) + term => term.types.includes(termTypes.disease) ); }; @@ -91,7 +97,7 @@ export const exploreSymptoms = async terms => while (stack.length) { const current = stack.pop(); - const neighbors = mock.hasSymptom.filter( + const neighbors = data.hasSymptom.filter( ([from]) => from === current ).map( ([, to]) => to @@ -107,7 +113,7 @@ export const exploreSymptoms = async terms => } } - return selected.map(id => mock.terms[id]); + return selected.map(id => data.terms[id]); }; /** @@ -131,7 +137,7 @@ export const symptomsSubgraph = async terms => } // Sélection des arêtes liant les nœuds sélectionnés - for (let [from, to] of mock.hasSymptom) + for (let [from, to] of data.hasSymptom) { if ( termsIds.includes(from) diff --git a/app/src/data/model.js b/app/src/data/model.js new file mode 100644 index 0000000..61d886f --- /dev/null +++ b/app/src/data/model.js @@ -0,0 +1,72 @@ +/** + * @module + * Définit le modèle de données utilisé par l’application. + */ + +import PropTypes from 'prop-types'; + +/** + * Types de termes existants. + */ +export const termTypes = { + disease: 'Maladie', + symptom: 'Symptôme', + sign: 'Signe', +}; + +/** + * Type de terme. + */ +export const TermType = PropTypes.oneOf(Object.values(termTypes)); + +/** + * Terme. + * + * Peut être une maladie, un symptôme ou un signe. + */ +export const Term = PropTypes.exact({ + id: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + alias: PropTypes.arrayOf(PropTypes.string).isRequired, + types: PropTypes.arrayOf(TermType).isRequired, + weight: PropTypes.number.isRequired, +}); + +/** + * Relation entre deux termes. + */ +export const Relation = (props, propName, componentName) => +{ + if (!(propName in props)) + { + return new Error( + `Missing ${propName} in props supplied to ${componentName}.` + ); + } + + const value = props[propName]; + + if (!Array.isArray(value)) + { + return new Error( + `Relation ${propName} must be an array.` + ); + } + + if (value.length !== 2) + { + return new Error( + `Relation ${propName} must contain exactly two entries.` + ); + } + + if ( + typeof value[0] !== 'string' + || typeof value[1] !== 'string' + ) + { + return new Error( + `Entries of relation ${propName} must be string IDs.` + ); + } +}; diff --git a/app/src/util.js b/app/src/util.js index d5afd19..9c831ad 100644 --- a/app/src/util.js +++ b/app/src/util.js @@ -28,7 +28,7 @@ export const useAsync = (initial, func, ...args) => /** * Crée un état composé d’une liste et d’un élément ayant le focus dans cette * liste. À la modification de la liste ou de l’indice de l’élément ayant le - * focus, la contrainte suivante est imposée : + * focus, la contrainte suivante est imposée : * * — si la liste n’est pas vide, focus ∈ [0, taille de la liste]. * — sinon, focus = 0. diff --git a/package.json b/package.json index e419816..efc13e3 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "diacritics": "^1.3.0", "eslint": "^6.7.2", "eslint-plugin-react": "^7.17.0", + "prop-types": "^15.7.2", "react": "^16.12.0", "react-dom": "^16.12.0", "react-transition-group": "^4.3.0",