From 124bfdd1ccbb2e5695fe309855a105bff55542a4 Mon Sep 17 00:00:00 2001 From: Mael Thomas Date: Tue, 14 Mar 2017 11:42:44 +0100 Subject: [PATCH] =?UTF-8?q?D=C3=A9placement=20de=20la=20g=C3=A9n=C3=A9rati?= =?UTF-8?q?on=20du=20JSX=20dans=20traverse.js?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- drawing.svg | 293 +++++++++++++++++++++++ package.json | 2 +- règles/rémunération-travail/cdd/CIF.yaml | 11 - source/.eslintrc | 4 + source/components/Rule.css | 2 +- source/components/Rule.js | 85 +++---- source/engine/mockSituation.yaml | 10 + source/engine/traverse-common-jsx.js | 15 ++ source/engine/traverse.js | 171 +++++++++++-- 9 files changed, 500 insertions(+), 93 deletions(-) create mode 100644 drawing.svg create mode 100644 source/engine/mockSituation.yaml create mode 100644 source/engine/traverse-common-jsx.js diff --git a/drawing.svg b/drawing.svg new file mode 100644 index 000000000..8cd940fe8 --- /dev/null +++ b/drawing.svg @@ -0,0 +1,293 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/package.json b/package.json index 82349508c..fac755fb7 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,6 @@ "ramda": "^0.23.0", "react": "^15.0.1", "react-dom": "^15.0.1", - "react-hot-loader": "3.0.0-beta.2", "react-json-tree": "^0.10.0", "react-redux": "^5.0.2", "react-router": "^3.0.2", @@ -55,6 +54,7 @@ "json-loader": "^0.5.4", "nearley-loader": "0.0.2", "postcss-loader": "^1.2.2", + "react-hot-loader": "^3.0.0-beta.6", "redux-devtools": "^3.2.0", "redux-devtools-dock-monitor": "^1.1.1", "redux-devtools-log-monitor": "^1.0.9", diff --git a/règles/rémunération-travail/cdd/CIF.yaml b/règles/rémunération-travail/cdd/CIF.yaml index 01721bde5..61e46080c 100644 --- a/règles/rémunération-travail/cdd/CIF.yaml +++ b/règles/rémunération-travail/cdd/CIF.yaml @@ -13,17 +13,6 @@ - motif . jeune vacances - contrat aidé - # Données de test - # non applicable si: - # l'une de ces conditions: - # - A - # - toutes ces conditions: - # - B - # - C - # - l'une de ces conditions: - # - X - # - Z - formule: multiplication: assiette: salaire de base diff --git a/source/.eslintrc b/source/.eslintrc index 098dedeef..d69429c45 100644 --- a/source/.eslintrc +++ b/source/.eslintrc @@ -13,9 +13,13 @@ rules: no-global-assign: 0 no-unsafe-negation: 0 no-undef: 1 + react/jsx-uses-vars: 2 + react/jsx-uses-react: 2 parser: babel-eslint +plugins: + - react env: browser: true commonjs: true diff --git a/source/components/Rule.css b/source/components/Rule.css index 2bdd3b1a6..3847fca1d 100644 --- a/source/components/Rule.css +++ b/source/components/Rule.css @@ -121,7 +121,7 @@ background: #407ee7; } -.multiplicationSign { +.operator { margin: .1em .6em; font-size: 150%; vertical-align: sub; diff --git a/source/components/Rule.js b/source/components/Rule.js index f20a18fb6..a8749a30e 100644 --- a/source/components/Rule.js +++ b/source/components/Rule.js @@ -7,22 +7,11 @@ import R from 'ramda' import PageTypeIcon from './PageTypeIcon' import {connect} from 'react-redux' import {formValueSelector} from 'redux-form' +import mockSituation from '../engine/mockSituation.yaml' // situationGate function useful for testing : -let testingSituationGate = v => - R.path(v.split('.'))({ - 'Salariat ': { - ' CDD ': { - ' événements': '_', - ' motif': 'saisonnier', - ' engagement employeur complément formation': 'non', - ' durée contrat': '2', - }, - ' contrat aidé': 'non', - ' salaire de base': '1481', - ' congés non pris': '3', - }, -}) +let testingSituationGate = v => // eslint-disable-line no-unused-vars + R.path(v.split('.'))(mockSituation) @connect(state => ({ situationGate: name => formValueSelector('conversation')(state, name) @@ -68,16 +57,17 @@ export default class Rule extends Component {
{ do { - let cond = - R.toPairs(rule).find(([,v]) => v.rulePropType == 'cond') - cond != null &&
-

Conditions de déclenchement

- -
+ let [,cond] = + R.toPairs(rule).find(([,v]) => v.rulePropType == 'cond') || [] + cond != null && +
+

Conditions de déclenchement

+ {cond.jsx} +
}}

Calcul

- + {rule['formule'].jsx}
@@ -159,9 +149,9 @@ let Multiplication = ({base, rate}) => -let Variable = (yo) => do {let {nodeValue, variableName} = yo; -console.log('yo', yo); - +let Variable = (yo) => do { + let {nodeValue, variableName} = yo +; {variableName} @@ -186,38 +176,25 @@ let Percentage = ({percentage}) => -let NodeValue = ({data}) => do { - let valeur = data == null ? - '?' - : ( R.is(Number)(data) ? - Math.round(data) - : ( data ? 'oui' : 'non') - ); - - ←  - {valeur} - -} - -let Formula = ({explanation, nodeValue}) => do { -
-
- {expression} - -
-
-} +// let Formula = ({explanation, nodeValue}) => do { +//
+//
+// {expression} +// +//
+//
+// } let JSONView = ({o, rootKey}) => ( -
- ''} - theme={theme} - hideRoot={true} - shouldExpandNode={() => true} - data={rootKey ? {[rootKey]: o} : o} - /> -
+
+ ''} + theme={theme} + hideRoot={true} + shouldExpandNode={() => true} + data={rootKey ? {[rootKey]: o} : o} + /> +
) diff --git a/source/engine/mockSituation.yaml b/source/engine/mockSituation.yaml new file mode 100644 index 000000000..a96788678 --- /dev/null +++ b/source/engine/mockSituation.yaml @@ -0,0 +1,10 @@ +"Salariat ": + " CDD ": + " événements": "_" + " motif": "saisonnier" + " engagement employeur complément formation": "non" + " durée contrat": "2" + + " contrat aidé": "non" + " salaire de base": "1481" + " congés non pris": "3" diff --git a/source/engine/traverse-common-jsx.js b/source/engine/traverse-common-jsx.js new file mode 100644 index 000000000..77e9c0b82 --- /dev/null +++ b/source/engine/traverse-common-jsx.js @@ -0,0 +1,15 @@ +import React from 'react' +import R from 'ramda' + +export let NodeValue = ({data}) => do { + let valeur = data == null ? + '?' + : ( R.is(Number)(data) ? + Math.round(data) + : ( data ? 'oui' : 'non') + ) + + ;←  + {valeur} + +} diff --git a/source/engine/traverse.js b/source/engine/traverse.js index 4e2bc6a61..55f2678e2 100644 --- a/source/engine/traverse.js +++ b/source/engine/traverse.js @@ -1,10 +1,14 @@ -import {rules, findRuleByName, parentName} from './rules' +import React from 'react' +import {rules, findRuleByName} from './rules' import {completeVariableName, evaluateVariable, knownVariable} from './expressions' import R from 'ramda' import knownMecanisms from './known-mecanisms.yaml' import { Parser } from 'nearley' import Grammar from './grammar.ne' import variablesInDevelopment from './variablesInDevelopment.yaml' +import {NodeValue} from './traverse-common-jsx' + + let nearley = () => new Parser(Grammar.ParserRules, Grammar.ParserStart) @@ -71,7 +75,12 @@ let fillVariableNode = (rule, situationGate) => (parseResult) => { variableName, type: 'boolean | numeric', explanation: null, - missingVariables: known ? [] : [variableName] + missingVariables: known ? [] : [variableName], + jsx: + + {variableName} + + } } @@ -99,7 +108,14 @@ let treat = (situationGate, rule) => rawNode => { if (parseResult.category == 'calcExpression') { let filledExplanation = parseResult.explanation.map( - R.when(R.propEq('category', 'variable'), fillVariableNode(rule, situationGate)) + R.cond([ + [R.propEq('category', 'variable'), fillVariableNode(rule, situationGate)], + [R.propEq('category', 'value'), node => + R.assoc('jsx', + {node.nodeValue} + )(node) + ] + ]) ), [{nodeValue: value1}, {nodeValue: value2}] = filledExplanation, operatorFunctionName = { @@ -115,17 +131,34 @@ let treat = (situationGate, rule) => rawNode => { return { text: rawNode, - nodeValue: nodeValue, + nodeValue, category: 'calcExpression', type: 'numeric', - explanation: filledExplanation + explanation: filledExplanation, + jsx: +
+
+ Éxpression de calcul + +
+ {filledExplanation[0].jsx} + {parseResult.operator} + {filledExplanation[1].jsx} +
} } if (parseResult.category == 'comparison') { //TODO mutualise code for 'comparison' & 'calclExpression'. Harmonise their names let filledExplanation = parseResult.explanation.map( - R.when(R.propEq('category', 'variable'), fillVariableNode(rule, situationGate)) + R.cond([ + [R.propEq('category', 'variable'), fillVariableNode(rule, situationGate)], + [R.propEq('category', 'value'), node => + R.assoc('jsx', + {node.nodeValue} + )(node) + ] + ]) ), [{nodeValue: value1}, {nodeValue: value2}] = filledExplanation, comparatorFunctionName = { @@ -140,13 +173,18 @@ let treat = (situationGate, rule) => rawNode => { null : comparatorFunction(value1, value2) - return { text: rawNode, nodeValue: nodeValue, category: 'comparison', type: 'boolean', - explanation: filledExplanation + explanation: filledExplanation, + jsx: +
+ {filledExplanation[0].jsx} + {parseResult.operator} + {filledExplanation[1].jsx} +
} } } @@ -156,13 +194,17 @@ let treat = (situationGate, rule) => rawNode => { return { category: 'number', nodeValue: rawNode, - type: 'numeric' + type: 'numeric', + jsx: + + {rawNode} + } } if (!R.is(Object)(rawNode)) { - console.log('Cette donnée : ', rawNode) + console.log('Cette donnée : ', rawNode) // eslint-disable-line no-console throw ' doit être un Number, String ou Object' } @@ -172,7 +214,7 @@ let treat = (situationGate, rule) => rawNode => { v = rawNode[k] if (k === "l'une de ces conditions") { - return R.pipe( + let result = R.pipe( R.unless(R.is(Array), () => {throw 'should be array'}), R.reduce( (memo, next) => { let {nodeValue, explanation} = memo, @@ -193,6 +235,18 @@ let treat = (situationGate, rule) => rawNode => { explanation: [] }) // Reduce but don't use R.reduced to set the nodeValue : we need to treat all the nodes )(v) + return {...result, + jsx: +
+
+ {result.name} + +
+
    + {result.explanation.map(item =>
  • {item.jsx}
  • )} +
+
+ } } if (k === 'toutes ces conditions') { return R.pipe( @@ -220,12 +274,16 @@ let treat = (situationGate, rule) => rawNode => { let treatNumericalLogicRec = R.ifElse( R.is(String), - rate => ({ + rate => ({ //TODO unifier ce code nodeValue: transformPercentage(rate), type: 'numeric', category: 'percentage', percentage: rate, - explanation: null + explanation: null, + jsx: + + {rate} + }), R.pipe( R.unless( @@ -261,7 +319,13 @@ let treat = (situationGate, rule) => rawNode => { condition: conditionNode, conditionValue: conditionNode.nodeValue, type: 'boolean', - explanation: childNumericalLogic + explanation: childNumericalLogic, + jsx: + {conditionNode.jsx} + + ---> {childNumericalLogic.jsx} + + }], } }, { @@ -270,6 +334,18 @@ let treat = (situationGate, rule) => rawNode => { name: "logique numérique", type: 'boolean || numeric', // lol ! explanation: [] + }), + node => ({...node, + jsx: +
+
+ logique numérique + +
+
    + {node.explanation.map(item =>
  • {item.jsx}
  • )} +
+
}) )) @@ -281,11 +357,15 @@ let treat = (situationGate, rule) => rawNode => { //TODO gérer les taux historisés if (R.is(String)(v)) return { - type: 'numeric', category: 'percentage', + type: 'numeric', percentage: v, nodeValue: transformPercentage(v), - explanation: null + explanation: null, + jsx: + + {v} + } else { let node = reTreat(v) @@ -294,7 +374,8 @@ let treat = (situationGate, rule) => rawNode => { category: 'percentage', percentage: node.nodeValue, nodeValue: node.nodeValue, - explanation: node + explanation: node, + jsx: node.jsx } } } @@ -322,7 +403,14 @@ let treat = (situationGate, rule) => rawNode => { facteur //TODO limit: 'plafond' //TODO introduire 'prorata' ou 'multiplicateur', pour sémantiser les opérandes ? - } + }, + jsx: +
+ {base.jsx} + × + {rate && rate.jsx} + {facteur && facteur.jsx} +
} } @@ -330,14 +418,25 @@ let treat = (situationGate, rule) => rawNode => { let contenders = v.map(treat(situationGate, rule)), contenderValues = R.pluck('nodeValue')(contenders), stopEverything = R.contains(null, contenderValues), - maxValue = R.max(...contenderValues) + maxValue = R.max(...contenderValues), + nodeValue = stopEverything ? null : maxValue return { type: 'numeric', category: 'mecanism', name: 'le maximum de', - nodeValue: stopEverything ? null : maxValue, - explanation: contenders + nodeValue, + explanation: contenders, + jsx: +
+
+ le maximum de + +
+
    + {contenders.map(item =>
  • {item.jsx}
  • )} +
+
} } @@ -351,14 +450,24 @@ let treatRuleRoot = (situationGate, rule) => R.evolve({ // -> Voilà les attribu // 'cond' : Conditions d'applicabilité de la règle 'non applicable si': value => { - let child = treat(situationGate, rule)(value) + let + child = treat(situationGate, rule)(value), + nodeValue = child.nodeValue return { category: 'ruleProp', rulePropType: 'cond', name: 'non applicable si', type: 'boolean', nodeValue: child.nodeValue, - explanation: child + explanation: child, + jsx: +
+
+ non applicable si + +
+ { child.jsx } +
} } , @@ -368,15 +477,25 @@ let treatRuleRoot = (situationGate, rule) => R.evolve({ // -> Voilà les attribu // note: pour certaines variables booléennes, ex. appartenance à régime Alsace-Moselle, la formule et le non applicable si se rejoignent // [n'importe quel mécanisme numérique] : multiplication || barème en taux marginaux || le maximum de || le minimum de || ... 'formule': value => { - let child = treat(situationGate, rule)(value) + let + child = treat(situationGate, rule)(value), + nodeValue = child.nodeValue return { category: 'ruleProp', rulePropType: 'formula', name: 'formule', type: 'numeric', - nodeValue: child.nodeValue, + nodeValue: nodeValue, explanation: child, - shortCircuit: R.pathEq(['non applicable si', 'nodeValue'], true) + shortCircuit: R.pathEq(['non applicable si', 'nodeValue'], true), + jsx: +
+
+ formula + +
+ { child.jsx } +
} } ,