This repository was archived by the owner on Feb 14, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
refactor!: simplification of the migrateSituation code #43
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,113 +1,161 @@ | ||
import { getValueWithoutQuotes } from './migrateSituation/getValueWithoutQuotes' | ||
import { handleSituationKeysMigration } from './migrateSituation/handleSituationKeysMigration' | ||
import { handleSituationValuesMigration } from './migrateSituation/handleSituationValuesMigration' | ||
import { handleSpecialCases } from './migrateSituation/handleSpecialCases' | ||
import { Evaluation } from 'publicodes' | ||
import { Evaluation, Situation } from 'publicodes' | ||
import { getValueWithoutQuotes, RuleName } from '../commons' | ||
|
||
export type NodeValue = Evaluation | ||
|
||
export type Situation = { | ||
[key: string]: NodeValue | ||
} | ||
|
||
export type DottedName = string | ||
/** | ||
* Associate a old value to a new value. | ||
*/ | ||
export type ValueMigration = Record<string, string> | ||
|
||
export type MigrationType = { | ||
keysToMigrate: Record<DottedName, DottedName> | ||
valuesToMigrate: Record<DottedName, Record<string, NodeValue>> | ||
/** | ||
* Migration instructions. It contains the rules and values to migrate. | ||
*/ | ||
export type Migration = { | ||
keysToMigrate: Record<RuleName, RuleName> | ||
valuesToMigrate: Record<RuleName, ValueMigration> | ||
} | ||
|
||
/** | ||
* Migrate rules and answers from a situation which used to work with an old version of a model to a new version according to the migration instructions. | ||
* Migrate a situation from an old version of a model to a new version | ||
* according to the provided migration instructions. | ||
* | ||
* @param situation - The situation object containing all answers for a given simulation. | ||
* @param instructions - The migration instructions object. | ||
* | ||
* @param {Object} options - The options object. | ||
* @param {Situation} options.situation - The `situation` as Publicodes object containing all answers for a given simulation. | ||
* @param {DottedName[]} [options.foldedSteps=[]] - In case of form app, an array containing answered questions. | ||
* @param {MigrationType} options.migrationInstructions - An object containing keys and values to migrate formatted as follows: | ||
* @returns The migrated situation (and foldedSteps if specified). | ||
* | ||
* @example | ||
* ``` | ||
* { | ||
* keysToMigrate: { | ||
* oldKey: newKey | ||
* } | ||
* valuesToMigrate: { | ||
* key: { | ||
* oldValue: newValue | ||
* ```typescript | ||
* import { migrateSituation } from '@publicodes/tools/migration' | ||
* | ||
* const situation = { | ||
* "age": 25 | ||
* "job": "developer", | ||
* "city": "Paris" | ||
* } | ||
* | ||
* const instructions = { | ||
* keysToMigrate: { | ||
* // The rule `age` will be renamed to `âge`. | ||
* age: 'âge', | ||
* // The rule `city` will be removed. | ||
* city: '' | ||
* }, | ||
* valuesToMigrate: { | ||
* job: { | ||
* // The value `developer` will be translated to `développeur`. | ||
* developer: 'développeur' | ||
* } | ||
* } | ||
* } | ||
* | ||
* migrateSituation(situation, instructions) // { "âge": 25, "job": "'développeur'" } | ||
* ``` | ||
* An example can be found in {@link https://github.com/incubateur-ademe/nosgestesclimat/blob/preprod/migration/migration.yaml | nosgestesclimat repository}. | ||
* @returns {Object} The migrated situation (and foldedSteps if specified). | ||
* | ||
* @note An example of instructions can be found {@link https://github.com/incubateur-ademe/nosgestesclimat/blob/preprod/migration/migration.yaml | here}. | ||
*/ | ||
export function migrateSituation({ | ||
situation, | ||
foldedSteps = [], | ||
migrationInstructions, | ||
}: { | ||
situation: Situation | ||
foldedSteps?: DottedName[] | ||
migrationInstructions: MigrationType | ||
}) { | ||
let situationMigrated = { ...situation } | ||
let foldedStepsMigrated = [...foldedSteps] | ||
export function migrateSituation( | ||
situation: Situation<RuleName>, | ||
instructions: Migration, | ||
): Situation<RuleName> { | ||
let newSituation = { ...situation } | ||
const currentRules = Object.keys(situation) | ||
const valueKeysToMigrate = Object.keys(instructions.valuesToMigrate) | ||
|
||
Object.entries(situationMigrated).map(([ruleName, nodeValue]) => { | ||
situationMigrated = handleSpecialCases({ | ||
ruleName, | ||
nodeValue, | ||
situation: situationMigrated, | ||
}) | ||
Object.entries(situation).map(([rule, value]) => { | ||
handleSpecialCases(rule, value, newSituation) | ||
|
||
// We check if the non supported ruleName is a key to migrate. | ||
// Ex: "logement . chauffage . bois . type . bûche . consommation": "xxx" which is now ""logement . chauffage . bois . type . bûches . consommation": "xxx" | ||
if (Object.keys(migrationInstructions.keysToMigrate).includes(ruleName)) { | ||
const result = handleSituationKeysMigration({ | ||
ruleName, | ||
nodeValue, | ||
situation: situationMigrated, | ||
foldedSteps: foldedStepsMigrated, | ||
migrationInstructions, | ||
}) | ||
if (currentRules.includes(rule)) { | ||
updateKey(rule, value, newSituation, instructions.keysToMigrate[rule]) | ||
} | ||
|
||
situationMigrated = result.situationMigrated | ||
foldedStepsMigrated = result.foldedStepsMigrated | ||
const formattedValue = getValueWithoutQuotes(value) ?? (value as string) | ||
const valuesMigration = | ||
instructions.valuesToMigrate[ | ||
valueKeysToMigrate.find((key) => rule.includes(key)) | ||
] ?? {} | ||
const oldValuesName = Object.keys(valuesMigration) | ||
|
||
if (oldValuesName.includes(formattedValue)) { | ||
updateValue(rule, valuesMigration[formattedValue], newSituation) | ||
} | ||
}) | ||
|
||
return newSituation | ||
} | ||
|
||
/** | ||
* Handle migration of old value format : an object { valeur: number, unité: string }. | ||
* | ||
* @example | ||
* ```json | ||
* { valeur: number, unité: string } | ||
* ``` | ||
*/ | ||
function handleSpecialCases( | ||
rule: RuleName, | ||
oldValue: Evaluation, | ||
situation: Situation<RuleName>, | ||
): void { | ||
// Special case, number store as a string, we have to convert it to a number | ||
if ( | ||
oldValue && | ||
typeof oldValue === 'string' && | ||
!isNaN(parseFloat(oldValue)) | ||
) { | ||
situation[rule] = parseFloat(oldValue) | ||
} | ||
|
||
const matchingValueToMigrateObject = | ||
migrationInstructions.valuesToMigrate[ | ||
Object.keys(migrationInstructions.valuesToMigrate).find((key) => | ||
ruleName.includes(key), | ||
) as any | ||
] | ||
// Special case : wrong value format, legacy from previous publicodes version | ||
// handle the case where valeur is a string "2.33" | ||
if (oldValue && oldValue['valeur'] !== undefined) { | ||
situation[rule] = | ||
typeof oldValue['valeur'] === 'string' && | ||
!isNaN(parseFloat(oldValue['valeur'])) | ||
? parseFloat(oldValue['valeur']) | ||
: (oldValue['valeur'] as number) | ||
} | ||
// Special case : other wrong value format, legacy from previous publicodes version | ||
// handle the case where nodeValue is a string "2.33" | ||
if (oldValue && oldValue['nodeValue'] !== undefined) { | ||
situation[rule] = | ||
typeof oldValue['nodeValue'] === 'string' && | ||
!isNaN(parseFloat(oldValue['nodeValue'])) | ||
? parseFloat(oldValue['nodeValue']) | ||
: (oldValue['nodeValue'] as number) | ||
} | ||
} | ||
|
||
const formattedNodeValue = | ||
getValueWithoutQuotes(nodeValue) || (nodeValue as string) | ||
function updateKey( | ||
rule: RuleName, | ||
oldValue: Evaluation, | ||
situation: Situation<RuleName>, | ||
ruleToMigrate: RuleName | undefined, | ||
): void { | ||
if (ruleToMigrate === undefined) { | ||
return | ||
} | ||
|
||
if ( | ||
// We check if the value of the non supported ruleName value is a value to migrate. | ||
// Ex: answer "logement . chauffage . bois . type": "bûche" changed to "bûches" | ||
// If a value is specified but empty, we consider it to be deleted (we need to ask the question again) | ||
// Ex: answer "transport . boulot . commun . type": "vélo" | ||
matchingValueToMigrateObject && | ||
Object.keys(matchingValueToMigrateObject).includes( | ||
// If the string start with a ', we remove it along with the last character | ||
// Ex: "'bûche'" => "bûche" | ||
formattedNodeValue, | ||
) | ||
) { | ||
const result = handleSituationValuesMigration({ | ||
ruleName, | ||
nodeValue: formattedNodeValue, | ||
situation: situationMigrated, | ||
foldedSteps: foldedStepsMigrated, | ||
migrationInstructions, | ||
}) | ||
delete situation[rule] | ||
|
||
situationMigrated = result.situationMigrated | ||
foldedStepsMigrated = result.foldedStepsMigrated | ||
} | ||
}) | ||
if (ruleToMigrate !== '') { | ||
situation[ruleToMigrate] = | ||
typeof oldValue === 'object' ? (oldValue as any)?.valeur : oldValue | ||
} | ||
} | ||
|
||
return { situationMigrated, foldedStepsMigrated } | ||
function updateValue( | ||
rule: RuleName, | ||
value: string, | ||
situation: Situation<RuleName>, | ||
): void { | ||
// The value is not a value to migrate and the key has to be deleted | ||
if (value === '') { | ||
delete situation[rule] | ||
} else { | ||
// The value is renamed and needs to be migrated | ||
situation[rule] = | ||
typeof value === 'string' && value !== 'oui' && value !== 'non' | ||
? `'${value}'` | ||
: value | ||
} | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.