-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' of https://github.com/publicodes/publicodes-tools
- Loading branch information
Showing
12 changed files
with
572 additions
and
5 deletions.
There are no files selected for viewing
This file contains 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 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 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 |
---|---|---|
@@ -0,0 +1,46 @@ | ||
/** @packageDocumentation | ||
## Migrate a situation | ||
{@link migrateSituation | `migrateSituation`} allows to migrate situation and foldedSteps based on migration instructions. It's useful in forms when a model is updated and we want old answers to be kept and taken into account in the new model. | ||
### Usage | ||
For instance, we have a simple set of rules: | ||
```yaml | ||
age: | ||
question: "Quel est votre âge ?" | ||
```` | ||
and the following situation: | ||
```json | ||
{ | ||
age: 25 | ||
} | ||
``` | ||
If I change my model because I want to fix the accent to: | ||
```yaml | ||
âge: | ||
question: "Quel est votre âge ?" | ||
``` | ||
I don't want to lose the previous answer, so I can use `migrateSituation` with the following migration instructions: | ||
```yaml | ||
keysToMigrate: | ||
age: âge | ||
``` | ||
Then, calling `migrateSituation` with the situation and the migration instructions will return: | ||
```json | ||
{ | ||
âge: 25 | ||
} | ||
``` | ||
*/ | ||
|
||
export * from './migrateSituation' |
This file contains 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 |
---|---|---|
@@ -0,0 +1,113 @@ | ||
import { getValueWithoutQuotes } from './migrateSituation/getValueWithoutQuotes' | ||
import { handleSituationKeysMigration } from './migrateSituation/handleSituationKeysMigration' | ||
import { handleSituationValuesMigration } from './migrateSituation/handleSituationValuesMigration' | ||
import { handleSpecialCases } from './migrateSituation/handleSpecialCases' | ||
import { Evaluation } from 'publicodes' | ||
|
||
export type NodeValue = Evaluation | ||
|
||
export type Situation = { | ||
[key: string]: NodeValue | ||
} | ||
|
||
export type DottedName = string | ||
|
||
export type MigrationType = { | ||
keysToMigrate: Record<DottedName, DottedName> | ||
valuesToMigrate: Record<DottedName, Record<string, NodeValue>> | ||
} | ||
|
||
/** | ||
* 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. | ||
* | ||
* @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: | ||
* | ||
* @example | ||
* ``` | ||
* { | ||
* keysToMigrate: { | ||
* oldKey: newKey | ||
* } | ||
* valuesToMigrate: { | ||
* key: { | ||
* oldValue: newValue | ||
* } | ||
* } | ||
* ``` | ||
* 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). | ||
*/ | ||
export function migrateSituation({ | ||
situation, | ||
foldedSteps = [], | ||
migrationInstructions, | ||
}: { | ||
situation: Situation | ||
foldedSteps?: DottedName[] | ||
migrationInstructions: MigrationType | ||
}) { | ||
let situationMigrated = { ...situation } | ||
let foldedStepsMigrated = [...foldedSteps] | ||
|
||
Object.entries(situationMigrated).map(([ruleName, nodeValue]) => { | ||
situationMigrated = handleSpecialCases({ | ||
ruleName, | ||
nodeValue, | ||
situation: situationMigrated, | ||
}) | ||
|
||
// 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, | ||
}) | ||
|
||
situationMigrated = result.situationMigrated | ||
foldedStepsMigrated = result.foldedStepsMigrated | ||
} | ||
|
||
const matchingValueToMigrateObject = | ||
migrationInstructions.valuesToMigrate[ | ||
Object.keys(migrationInstructions.valuesToMigrate).find((key) => | ||
ruleName.includes(key), | ||
) as any | ||
] | ||
|
||
const formattedNodeValue = | ||
getValueWithoutQuotes(nodeValue) || (nodeValue as string) | ||
|
||
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, | ||
}) | ||
|
||
situationMigrated = result.situationMigrated | ||
foldedStepsMigrated = result.foldedStepsMigrated | ||
} | ||
}) | ||
|
||
return { situationMigrated, foldedStepsMigrated } | ||
} |
24 changes: 24 additions & 0 deletions
24
source/migration/migrateSituation/deleteKeyFromSituationAndFoldedSteps.ts
This file contains 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 |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { DottedName, Situation } from '../migrateSituation' | ||
|
||
/** | ||
* Delete a key from the situation and from the foldedSteps if it exists. | ||
* @param ruleName - The rulename to delete. | ||
* @param situation - The situation object. | ||
* @param foldedSteps - The foldedSteps array. | ||
*/ | ||
export function deleteKeyFromSituationAndFoldedSteps({ | ||
ruleName, | ||
situation, | ||
foldedSteps, | ||
}: { | ||
ruleName: string | ||
situation: Situation | ||
foldedSteps: DottedName[] | ||
}) { | ||
delete situation[ruleName] | ||
const index = foldedSteps?.indexOf(ruleName) | ||
|
||
if (index > -1) { | ||
foldedSteps.splice(index, 1) | ||
} | ||
} |
19 changes: 19 additions & 0 deletions
19
source/migration/migrateSituation/getValueWithoutQuotes.ts
This file contains 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 |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { NodeValue } from '../migrateSituation' | ||
|
||
/** | ||
* Returns the value without quotes if it is a string. | ||
* @param value - The value to parse. | ||
* | ||
* @returns The value without quotes if it is a string, null otherwise. | ||
*/ | ||
export function getValueWithoutQuotes(value: NodeValue) { | ||
if ( | ||
typeof value !== 'string' || | ||
!value.startsWith("'") || | ||
value === 'oui' || | ||
value === 'non' | ||
) { | ||
return null | ||
} | ||
return value.slice(1, -1) | ||
} |
97 changes: 97 additions & 0 deletions
97
source/migration/migrateSituation/handleSituationKeysMigration.ts
This file contains 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 |
---|---|---|
@@ -0,0 +1,97 @@ | ||
import { | ||
DottedName, | ||
MigrationType, | ||
NodeValue, | ||
Situation, | ||
} from '../migrateSituation' | ||
import { deleteKeyFromSituationAndFoldedSteps } from './deleteKeyFromSituationAndFoldedSteps' | ||
|
||
type Props = { | ||
ruleName: string | ||
nodeValue: NodeValue | ||
situation: Situation | ||
foldedSteps: DottedName[] | ||
migrationInstructions: MigrationType | ||
} | ||
|
||
/** | ||
* Updates a key in the situation object and foldedSteps. | ||
* @param ruleName - The name of the rule to update. | ||
* @param nodeValue - The new value for the rule. | ||
* @param situation - The situation object. | ||
* @param foldedSteps - The array of foldedSteps. | ||
* @param migrationInstructions - The migration instructions. | ||
*/ | ||
function updateKeyInSituationAndFoldedSteps({ | ||
ruleName, | ||
nodeValue, | ||
situation, | ||
foldedSteps, | ||
migrationInstructions, | ||
}: { | ||
ruleName: string | ||
nodeValue: NodeValue | ||
situation: Situation | ||
foldedSteps: DottedName[] | ||
migrationInstructions: MigrationType | ||
}) { | ||
situation[migrationInstructions.keysToMigrate[ruleName]] = | ||
(nodeValue as any)?.valeur ?? nodeValue | ||
|
||
delete situation[ruleName] | ||
|
||
const index = foldedSteps?.indexOf(ruleName) | ||
|
||
if (index > -1) { | ||
foldedSteps[index] = migrationInstructions.keysToMigrate[ruleName] | ||
} | ||
} | ||
|
||
/** | ||
* Updates a key in the situation and foldedSteps based on migration instructions. | ||
* If the key is not a key to migrate but a key to delete, it will be removed from the situation and foldedSteps. | ||
* If the key is renamed and needs to be migrated, it will be updated in the situation and foldedSteps. | ||
* | ||
* @param ruleName - The name of the rule/key to update. | ||
* @param nodeValue - The new value for the rule/key. | ||
* @param situation - The current situation object. | ||
* @param foldedSteps - The current foldedSteps array. | ||
* @param migrationInstructions - The migration instructions object. | ||
* | ||
* @returns An object containing the migrated situation and foldedSteps. | ||
*/ | ||
export function handleSituationKeysMigration({ | ||
ruleName, | ||
nodeValue, | ||
situation, | ||
foldedSteps, | ||
migrationInstructions, | ||
}: Props): { situationMigrated: Situation; foldedStepsMigrated: DottedName[] } { | ||
const situationMigrated = { ...situation } | ||
const foldedStepsMigrated = [...foldedSteps] | ||
|
||
// The key is not a key to migrate but a key to delete | ||
if (migrationInstructions.keysToMigrate[ruleName] === '') { | ||
deleteKeyFromSituationAndFoldedSteps({ | ||
ruleName, | ||
situation: situationMigrated, | ||
foldedSteps: foldedStepsMigrated, | ||
}) | ||
return { situationMigrated, foldedStepsMigrated } | ||
} | ||
|
||
if (!migrationInstructions.keysToMigrate[ruleName]) { | ||
return | ||
} | ||
|
||
// The key is renamed and needs to be migrated | ||
updateKeyInSituationAndFoldedSteps({ | ||
ruleName, | ||
nodeValue, | ||
situation: situationMigrated, | ||
foldedSteps: foldedStepsMigrated, | ||
migrationInstructions, | ||
}) | ||
|
||
return { situationMigrated, foldedStepsMigrated } | ||
} |
Oops, something went wrong.