Skip to content

Commit

Permalink
feat: Add a situation (and foldedSteps) migration function
Browse files Browse the repository at this point in the history
  • Loading branch information
bjlaa committed May 14, 2024
1 parent ee4e38a commit 3e50d8a
Show file tree
Hide file tree
Showing 9 changed files with 355 additions and 3 deletions.
1 change: 1 addition & 0 deletions source/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './commons'
export * from './serializeParsedRules'
export * from './migrations'
1 change: 1 addition & 0 deletions source/migrations/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './migrateSituation'
85 changes: 85 additions & 0 deletions source/migrations/migrateSituation.ts
Original file line number Diff line number Diff line change
@@ -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 }
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
76 changes: 76 additions & 0 deletions source/migrations/migrateSituation/handleSituationKeysMigration.ts
Original file line number Diff line number Diff line change
@@ -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 }
}
Original file line number Diff line number Diff line change
@@ -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 }
}
40 changes: 40 additions & 0 deletions source/migrations/migrateSituation/handleSpecialCases.ts
Original file line number Diff line number Diff line change
@@ -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
}
14 changes: 14 additions & 0 deletions types/types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Evaluation } from 'publicodes'

export type Situation = {
[key: string]: string | number
}

export type DottedName = string

export type MigrationType = {
keysToMigrate: Record<DottedName, DottedName>
valuesToMigrate: Record<DottedName, Record<string, NodeValue>>
}

export type NodeValue = Evaluation
31 changes: 28 additions & 3 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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==
Expand All @@ -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==
Expand Down Expand Up @@ -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==
Expand Down

0 comments on commit 3e50d8a

Please sign in to comment.