🔥 Remplace UNSAFE_evaluateNode par de meilleurs abstractions

pull/1312/head
Johan Girod 2020-12-16 18:45:45 +01:00
parent 8c0b925956
commit adcbd330bd
6 changed files with 80 additions and 90 deletions

View File

@ -1,11 +1,7 @@
import { hideNotification } from 'Actions/actions'
import animate from 'Components/ui/animate'
import {
useInversionFail,
EngineContext,
useEngine,
} from 'Components/utils/EngineContext'
import { useContext } from 'react'
import { useEngine, useInversionFail } from 'Components/utils/EngineContext'
import Engine, { EvaluatedRule } from 'publicodes'
import emoji from 'react-easy-emoji'
import { useTranslation } from 'react-i18next'
import { useDispatch, useSelector } from 'react-redux'
@ -13,7 +9,6 @@ import { RootState } from 'Reducers/rootReducer'
import './Notifications.css'
import { Markdown } from './utils/markdown'
import { ScrollToElement } from './utils/Scroll'
import Engine, { EvaluatedRule, ASTNode, UNSAFE_evaluateRule } from 'publicodes'
// To add a new notification to a simulator, you should create a publicode rule
// with the "type: notification" attribute. The display can be customized with
@ -27,11 +22,11 @@ type Notification = Pick<EvaluatedRule, 'dottedName' | 'description'> & {
export function getNotifications(engine: Engine) {
return Object.values(engine.getRules())
.filter(
(rule: ASTNode & { nodeKind: 'rule' }) =>
rule.rawNode['type'] === 'notification'
(rule) =>
rule.rawNode['type'] === 'notification' &&
!!engine.evaluate(rule.dottedName).nodeValue
)
.map((node) => UNSAFE_evaluateRule(engine, node.dottedName))
.filter((node) => !!node.nodeValue)
.map((node) => node.dottedName)
}
export default function Notifications() {
const { t } = useTranslation()

View File

@ -1,5 +1,6 @@
import { setActiveTarget, updateSituation } from 'Actions/actions'
import InputSuggestions from 'Components/conversation/InputSuggestions'
import Value, { Condition } from 'Components/EngineValue'
import PeriodSwitch from 'Components/PeriodSwitch'
import RuleLink from 'Components/RuleLink'
import Animate from 'Components/ui/animate'
@ -11,14 +12,9 @@ import {
useInversionFail,
} from 'Components/utils/EngineContext'
import { SitePathsContext } from 'Components/utils/SitePathsContext'
import {
ASTNode,
EvaluatedNode,
EvaluatedRule,
UNSAFE_evaluateRule,
formatValue,
reduceAST,
} from 'publicodes'
import { DottedName } from 'modele-social'
import { Names } from 'modele-social/dist/names'
import { ASTNode, EvaluatedRule, formatValue, reduceAST } from 'publicodes'
import { isNil } from 'ramda'
import { Fragment, useCallback, useContext } from 'react'
import emoji from 'react-easy-emoji'
@ -26,11 +22,9 @@ import { Trans, useTranslation } from 'react-i18next'
import { useDispatch, useSelector } from 'react-redux'
import { useLocation } from 'react-router-dom'
import { RootState } from 'Reducers/rootReducer'
import { DottedName } from 'modele-social'
import { targetUnitSelector } from 'Selectors/simulationSelectors'
import CurrencyInput from './CurrencyInput/CurrencyInput'
import './TargetSelection.css'
import { Names } from 'modele-social/dist/names'
export default function TargetSelection({ showPeriodSwitch = true }) {
const objectifs = useSelector(
@ -89,10 +83,13 @@ type TargetProps = {
const Target = ({ dottedName }: TargetProps) => {
const activeInput = useSelector((state: RootState) => state.activeTargetInput)
const engine = useEngine()
const target = UNSAFE_evaluateRule(engine, dottedName, {
const rule = engine.getRule(dottedName)
const evaluation = engine.evaluate({
valeur: dottedName,
unité: useSelector(targetUnitSelector),
arrondi: 'oui',
})
const target = { ...evaluation, ...rule.rawNode, ...rule }
const dispatch = useDispatch()
const onSuggestionClick = useCallback(
(value) => {
@ -269,30 +266,26 @@ function TargetInputOrValue({
}
function TitreRestaurant() {
const targetUnit = useSelector(targetUnitSelector)
const { language } = useTranslation().i18n
const titresRestaurant = UNSAFE_evaluateRule(
useEngine(),
'contrat salarié . frais professionnels . titres-restaurant . montant',
{
unité: targetUnit,
arrondi: 'oui',
}
)
if (!titresRestaurant?.nodeValue) return null
const dottedName =
'contrat salarié . frais professionnels . titres-restaurant . montant'
return (
<Animate.fromTop>
<div className="aidesGlimpse">
<RuleLink dottedName={titresRestaurant.dottedName}>
+{' '}
<strong>
{formatValue(titresRestaurant, { displayedUnit: '€', language })}
</strong>{' '}
<Trans>en titres-restaurant</Trans> {emoji(' 🍽')}
</RuleLink>
</div>
</Animate.fromTop>
<Condition expression={dottedName}>
<Animate.fromTop>
<div className="aidesGlimpse">
<RuleLink dottedName={dottedName}>
+{' '}
<strong>
<Value
expression={dottedName}
displayedUnit="€"
unit={targetUnit}
/>
</strong>{' '}
<Trans>en titres-restaurant</Trans> {emoji(' 🍽')}
</RuleLink>
</div>
</Animate.fromTop>
</Condition>
)
}
function AidesGlimpse() {
@ -300,14 +293,7 @@ function AidesGlimpse() {
const { language } = useTranslation().i18n
const dottedName = 'contrat salarié . aides employeur' as Names
const engine = useEngine()
const evaluation = engine.evaluate({
valeur: dottedName,
unité: targetUnit,
arrondi: 'oui',
})
const aides = engine.getRule(dottedName)
if (!evaluation?.nodeValue) return null
// Dans le cas où il n'y a qu'une seule aide à l'embauche qui s'applique, nous
// faisons un lien direct vers cette aide, plutôt qu'un lien vers la liste qui
// est une somme des aides qui sont toutes nulle sauf l'aide active.
@ -329,18 +315,22 @@ function AidesGlimpse() {
aides
)
return (
<Animate.fromTop>
<div className="aidesGlimpse">
<RuleLink dottedName={aideLink}>
<Trans>en incluant</Trans>{' '}
<strong>
<span>
{formatValue(evaluation, { displayedUnit: '€', language })}
</span>
</strong>{' '}
<Trans>d'aides</Trans> {emoji(aides.rawNode.icônes ?? '')}
</RuleLink>
</div>
</Animate.fromTop>
<Condition expression={dottedName}>
<Animate.fromTop>
<div className="aidesGlimpse">
<RuleLink dottedName={aideLink}>
<Trans>en incluant</Trans>{' '}
<strong>
<Value
expression={dottedName}
displayedUnit="€"
unit={targetUnit}
/>
</strong>{' '}
<Trans>d'aides</Trans> {emoji(aides.rawNode.icônes ?? '')}
</RuleLink>
</div>
</Animate.fromTop>
</Condition>
)
}

View File

@ -2,7 +2,7 @@ import { setSimulationConfig, updateSituation } from 'Actions/actions'
import Aide from 'Components/conversation/Aide'
import { Explicable, ExplicableRule } from 'Components/conversation/Explicable'
import RuleInput from 'Components/conversation/RuleInput'
import { Condition } from 'Components/EngineValue'
import Value, { Condition } from 'Components/EngineValue'
import RuleLink from 'Components/RuleLink'
import 'Components/TargetSelection.css'
import Animate from 'Components/ui/animate'
@ -344,7 +344,8 @@ type SimpleFieldProps = {
function SimpleField({ dottedName, question, summary }: SimpleFieldProps) {
const dispatch = useDispatch()
const engine = useContext(EngineContext)
const evaluatedRule = UNSAFE_evaluateRule(engine, dottedName)
const evaluation = engine.evaluate(dottedName)
const rule = engine.getRule(dottedName)
const situation = useSelector(situationSelector)
const dispatchValue = useCallback(
@ -361,8 +362,8 @@ function SimpleField({ dottedName, question, summary }: SimpleFieldProps) {
if (
!(dottedName in situation) &&
evaluatedRule.nodeValue === false &&
!(dottedName in evaluatedRule.missingVariables)
evaluation.nodeValue === false &&
!(dottedName in evaluation.missingVariables)
) {
return null
}
@ -381,10 +382,10 @@ function SimpleField({ dottedName, question, summary }: SimpleFieldProps) {
`}
>
<p>
{question ?? evaluatedRule.question}
{question ?? rule.rawNode.question}
<ExplicableRule dottedName={dottedName} />
</p>
<p className="ui__ notice">{summary ?? evaluatedRule.résumé}</p>
<p className="ui__ notice">{summary ?? rule.rawNode.résumé}</p>
</div>
<RuleInput dottedName={dottedName} onChange={dispatchValue} />
</Question>
@ -395,8 +396,8 @@ function SimpleField({ dottedName, question, summary }: SimpleFieldProps) {
function Results() {
const engine = useEngine()
const results = (simulationConfig.objectifs as DottedName[]).map((objectif) =>
UNSAFE_evaluateRule(engine, objectif, { unité: '€/an' })
const rules = (simulationConfig.objectifs as DottedName[]).map((objectif) =>
engine.getRule(objectif)
)
return (
<div
@ -411,18 +412,22 @@ function Results() {
</h1>
<>
<Animate.fromTop>
{results.map((r) => (
{rules.map((r) => (
<Fragment key={r.dottedName}>
<h4>
{r.title} <small>{r.résumé}</small>
{r.title} <small>{r.rawNode.résumé}</small>
</h4>
{r.description && <p className="ui__ notice">{r.description}</p>}
{r.rawNode.description && (
<p className="ui__ notice">{r.rawNode.description}</p>
)}
<p className="ui__ lead" css="margin-bottom: 1rem;">
<RuleLink dottedName={r.dottedName}>
{formatValue(r, {
displayedUnit: '€',
precision: 0,
})}
<Value
expression={r.dottedName}
displayedUnit="€"
unit="€/an"
precision={0}
/>
</RuleLink>
</p>
</Fragment>

View File

@ -3,11 +3,11 @@ import Simulation from 'Components/Simulation'
import Animate from 'Components/ui/animate'
import Warning from 'Components/ui/WarningBlock'
import { IsEmbeddedContext } from 'Components/utils/embeddedContext'
import { EngineContext, useEngine } from 'Components/utils/EngineContext'
import { EvaluatedRule, UNSAFE_evaluateRule, formatValue } from 'publicodes'
import { useEngine } from 'Components/utils/EngineContext'
import { DottedName } from 'modele-social'
import { formatValue } from 'publicodes'
import React, { useContext, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { DottedName } from 'modele-social'
import styled from 'styled-components'
declare global {

View File

@ -6,7 +6,6 @@
// renamed the test configuration may be adapted but the persisted snapshot will remain unchanged).
/* eslint-disable no-undef */
import Engine, { UNSAFE_evaluateRule } from 'publicodes'
import rules from 'modele-social'
import artisteAuteurConfig from '../../source/site/pages/Simulateurs/configs/artiste-auteur.yaml'
import autoentrepreneurConfig from '../../source/site/pages/Simulateurs/configs/auto-entrepreneur.yaml'
@ -17,11 +16,10 @@ import professionLibéraleConfig from '../../source/site/pages/Simulateurs/confi
import aideDéclarationConfig from '../../source/site/pages/Gérer/AideDéclarationIndépendant/config.yaml'
import artisteAuteurSituations from './simulations-artiste-auteur.yaml'
import autoEntrepreneurSituations from './simulations-auto-entrepreneur.yaml'
import professionsLibéralesSituations from './simulations-professions-libérales.yaml'
import independentSituations from './simulations-indépendant.yaml'
import professionsLibéralesSituations from './simulations-professions-libérales.yaml'
import remunerationDirigeantSituations from './simulations-rémunération-dirigeant.yaml'
import employeeSituations from './simulations-salarié.yaml'
import aideDéclarationIndépendantsSituations from './aide-déclaration-indépendants.yaml'
const roundResult = (arr) => arr.map((x) => Math.round(x))
const engine = new Engine(rules)
@ -40,9 +38,11 @@ const runSimulations = (situations, targets, baseSituation = {}) =>
const res = targets.map((target) => engine.evaluate(target).nodeValue)
const evaluatedNotifications = Object.values(engine.getRules())
.filter((rule) => rule.rawNode['type'] === 'notification')
.map((node) => UNSAFE_evaluateRule(engine, node.dottedName))
.filter((node) => !!node.nodeValue)
.filter(
(rule) =>
rule.rawNode['type'] === 'notification' &&
!!engine.evaluate(rule.dottedName).nodeValue
)
.map((node) => node.dottedName)
const snapshotedDisplayedNotifications = evaluatedNotifications.length

View File

@ -9,4 +9,4 @@ export const transformAST = publicodes.transformAST
export const formatValue = publicodes.formatValue
export const utils = publicodes.utils
export const translateRules = publicodes.translateRules
export const UNSAFE_evaluateRule = publicodes.UNSAFE_evaluateRule
export const UNSAFE_isNotApplicable = publicodes.UNSAFE_isNotApplicable