From 3e50d8aed20202c15f1dff3b3574c946fa0cdd37 Mon Sep 17 00:00:00 2001 From: Benjamin Arias Date: Tue, 14 May 2024 15:22:51 +0200 Subject: [PATCH] feat: Add a situation (and foldedSteps) migration function --- source/index.ts | 1 + source/migrations/index.ts | 1 + source/migrations/migrateSituation.ts | 85 +++++++++++++++++ .../deleteKeyFromSituationAndFoldedSteps.ts | 18 ++++ .../handleSituationKeysMigration.ts | 76 +++++++++++++++ .../handleSituationValuesMigration.ts | 92 +++++++++++++++++++ .../migrateSituation/handleSpecialCases.ts | 40 ++++++++ types/types.d.ts | 14 +++ yarn.lock | 31 ++++++- 9 files changed, 355 insertions(+), 3 deletions(-) create mode 100644 source/migrations/index.ts create mode 100644 source/migrations/migrateSituation.ts create mode 100644 source/migrations/migrateSituation/deleteKeyFromSituationAndFoldedSteps.ts create mode 100644 source/migrations/migrateSituation/handleSituationKeysMigration.ts create mode 100644 source/migrations/migrateSituation/handleSituationValuesMigration.ts create mode 100644 source/migrations/migrateSituation/handleSpecialCases.ts create mode 100644 types/types.d.ts diff --git a/source/index.ts b/source/index.ts index 8d1a41d..f21cc99 100644 --- a/source/index.ts +++ b/source/index.ts @@ -1,2 +1,3 @@ export * from './commons' export * from './serializeParsedRules' +export * from './migrations' diff --git a/source/migrations/index.ts b/source/migrations/index.ts new file mode 100644 index 0000000..e4eb151 --- /dev/null +++ b/source/migrations/index.ts @@ -0,0 +1 @@ +export * from './migrateSituation' diff --git a/source/migrations/migrateSituation.ts b/source/migrations/migrateSituation.ts new file mode 100644 index 0000000..e794913 --- /dev/null +++ b/source/migrations/migrateSituation.ts @@ -0,0 +1,85 @@ +import { DottedName, MigrationType, Situation } from '../../types/types' +import { handleSituationKeysMigration } from './migrateSituation/handleSituationKeysMigration' +import { handleSituationValuesMigration } from './migrateSituation/handleSituationValuesMigration' +import { handleSpecialCases } from './migrateSituation/handleSpecialCases' + +type Props = { + situation: Situation + foldedSteps?: DottedName[] + migrationInstructions: MigrationType +} + +export function migrateSituation({ + situation, + foldedSteps = [], + migrationInstructions, +}: Props) { + let situationMigrated = { ...situation } + let foldedStepsMigrated = [...foldedSteps] + + Object.entries(situationMigrated).map(([ruleName, nodeValue]) => { + const situationUpdated = handleSpecialCases({ + ruleName, + nodeValue, + situation: situationMigrated, + }) + + situationMigrated = situationUpdated + + // 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 = + typeof nodeValue === 'string' && + nodeValue.startsWith("'") && + nodeValue !== 'oui' && + nodeValue !== 'non' + ? nodeValue.slice(1, -1) + : (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 } +} diff --git a/source/migrations/migrateSituation/deleteKeyFromSituationAndFoldedSteps.ts b/source/migrations/migrateSituation/deleteKeyFromSituationAndFoldedSteps.ts new file mode 100644 index 0000000..3693ba5 --- /dev/null +++ b/source/migrations/migrateSituation/deleteKeyFromSituationAndFoldedSteps.ts @@ -0,0 +1,18 @@ +import { DottedName, Situation } from '../../../types/types' + +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) + } +} diff --git a/source/migrations/migrateSituation/handleSituationKeysMigration.ts b/source/migrations/migrateSituation/handleSituationKeysMigration.ts new file mode 100644 index 0000000..32c9e7c --- /dev/null +++ b/source/migrations/migrateSituation/handleSituationKeysMigration.ts @@ -0,0 +1,76 @@ +import { + DottedName, + MigrationType, + NodeValue, + Situation, +} from '../../../types/types' +import { deleteKeyFromSituationAndFoldedSteps } from './deleteKeyFromSituationAndFoldedSteps' + +type Props = { + ruleName: string + nodeValue: NodeValue + situation: Situation + foldedSteps: DottedName[] + migrationInstructions: MigrationType +} + +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] + } +} + +export function handleSituationKeysMigration({ + ruleName, + nodeValue, + situation, + foldedSteps, + migrationInstructions, +}: Props) { + 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 } +} diff --git a/source/migrations/migrateSituation/handleSituationValuesMigration.ts b/source/migrations/migrateSituation/handleSituationValuesMigration.ts new file mode 100644 index 0000000..8f89539 --- /dev/null +++ b/source/migrations/migrateSituation/handleSituationValuesMigration.ts @@ -0,0 +1,92 @@ +import { + DottedName, + MigrationType, + NodeValue, + Situation, +} from '../../../types/types' +import { deleteKeyFromSituationAndFoldedSteps } from './deleteKeyFromSituationAndFoldedSteps' + +type Props = { + ruleName: DottedName + nodeValue: NodeValue + situation: Situation + foldedSteps: DottedName[] + migrationInstructions: MigrationType +} + +function getMigratedValue({ + ruleName, + nodeValue, + migrationInstructions, +}: { + ruleName: DottedName + nodeValue: NodeValue + migrationInstructions: MigrationType +}): string | number { + if ( + typeof migrationInstructions.valuesToMigrate[ruleName][ + nodeValue as string + ] === 'string' && + migrationInstructions.valuesToMigrate[ruleName][nodeValue as string] !== + 'oui' && + migrationInstructions.valuesToMigrate[ruleName][nodeValue as string] !== + 'non' + ) { + return `'${migrationInstructions.valuesToMigrate[ruleName][nodeValue as string]}'` + } + + if ( + ( + migrationInstructions.valuesToMigrate[ruleName][nodeValue as string] as { + valeur: number + } + )?.valeur !== undefined + ) { + return ( + migrationInstructions.valuesToMigrate[ruleName][nodeValue as string] as { + valeur: number + } + ).valeur + } + + return migrationInstructions.valuesToMigrate[ruleName][ + nodeValue as string + ] as string | number +} + +export function handleSituationValuesMigration({ + ruleName, + nodeValue, + situation, + foldedSteps, + migrationInstructions, +}: Props) { + if (!migrationInstructions.valuesToMigrate[ruleName]) { + return + } + + const situationMigrated = { ...situation } + const foldedStepsMigrated = [...foldedSteps] + + // The value is not a value to migrate and the key has to be deleted + if ( + migrationInstructions.valuesToMigrate[ruleName][nodeValue as string] === '' + ) { + deleteKeyFromSituationAndFoldedSteps({ + ruleName, + situation: situationMigrated, + foldedSteps: foldedStepsMigrated, + }) + + return { situationMigrated, foldedStepsMigrated } + } + + // The value is renamed and needs to be migrated + situationMigrated[ruleName] = getMigratedValue({ + ruleName, + nodeValue, + migrationInstructions, + }) + + return { situationMigrated, foldedStepsMigrated } +} diff --git a/source/migrations/migrateSituation/handleSpecialCases.ts b/source/migrations/migrateSituation/handleSpecialCases.ts new file mode 100644 index 0000000..e4e8032 --- /dev/null +++ b/source/migrations/migrateSituation/handleSpecialCases.ts @@ -0,0 +1,40 @@ +type Props = { + ruleName: string + nodeValue: any + situation: any +} + +// Handle migration of old value format : an object { valeur: number, unité: string } +export function handleSpecialCases({ ruleName, nodeValue, situation }: Props) { + const situationUpdated = { ...situation } + + // Special case, number store as a string, we have to convert it to a number + if ( + nodeValue && + typeof nodeValue === 'string' && + !isNaN(parseFloat(nodeValue)) + ) { + situationUpdated[ruleName] = parseFloat(nodeValue) + } + + // Special case : wrong value format, legacy from previous publicodes version + // handle the case where valeur is a string "2.33" + if (nodeValue && nodeValue.valeur !== undefined) { + situationUpdated[ruleName] = + typeof nodeValue.valeur === 'string' && + !isNaN(parseFloat(nodeValue.valeur)) + ? parseFloat(nodeValue.valeur) + : (nodeValue.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 (nodeValue && nodeValue.nodeValue !== undefined) { + situationUpdated[ruleName] = + typeof nodeValue.nodeValue === 'string' && + !isNaN(parseFloat(nodeValue.nodeValue)) + ? parseFloat(nodeValue.nodeValue) + : (nodeValue.nodeValue as number) + } + + return situationUpdated +} diff --git a/types/types.d.ts b/types/types.d.ts new file mode 100644 index 0000000..c6979a5 --- /dev/null +++ b/types/types.d.ts @@ -0,0 +1,14 @@ +import { Evaluation } from 'publicodes' + +export type Situation = { + [key: string]: string | number +} + +export type DottedName = string + +export type MigrationType = { + keysToMigrate: Record + valuesToMigrate: Record> +} + +export type NodeValue = Evaluation diff --git a/yarn.lock b/yarn.lock index 784d6a0..f6c68d4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2578,7 +2578,16 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -2596,7 +2605,14 @@ string-width@^5.0.1, string-width@^5.1.2: emoji-regex "^9.2.2" strip-ansi "^7.0.1" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -2869,7 +2885,16 @@ which@^2.0.1: dependencies: isexe "^2.0.0" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==