Skip to content
This repository has been archived by the owner on Feb 14, 2025. It is now read-only.

Commit

Permalink
refactor(migration): cleaning
Browse files Browse the repository at this point in the history
  • Loading branch information
EmileRolley committed Jun 3, 2024
1 parent 6624433 commit 6c73142
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 110 deletions.
31 changes: 16 additions & 15 deletions src/migration/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,35 +12,36 @@ This is where the sitation migration comes in.
### Usage
{@link migrateSituation | `migrateSituation`} allows to migrate a siuation from
{@link migrateSituation | `migrateSituation`} allows to migrate a situation from
an old version of a model to a new version according to the provided _migration
instructions_.
```typescript
import { migrateSituation } from '@publicodes/tools/migration'
const oldSituation = {
"age": 25
const situation = {
"age": 25,
"job": "developer",
"city": "Paris"
}
// In the new model version, the rule `age` has been renamed to `âge` and the
// value `developer` has been translated to `développeur`.
const migrationInstructions = {
keysToMigrate: { age: 'âge' }
const instructions = {
keysToMigrate: {
// The rule `age` has been renamed to `âge`.
age: 'âge',
// The rule `city` has been removed.
city: ''
},
valuesToMigrate: {
job: { developer: 'développeur' }
job: {
// The value `developer` has been translated to `développeur`.
developer: 'développeur'
}
}
}
console.log(migrateSituation(oldSituation, migrationInstructions))
// Output:
// {
// "âge": 25,
// "job": "développeur"
// }
migrateSituation(situation, instructions) // { "âge": 25, "job": "'développeur'" }
```
*/

Expand Down
92 changes: 40 additions & 52 deletions src/migration/migrateSituation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export type ValueMigration = Record<string, string>
* Migration instructions. It contains the rules and values to migrate.
*/
export type Migration = {
rulesToMigrate: Record<RuleName, RuleName>
keysToMigrate: Record<RuleName, RuleName>
valuesToMigrate: Record<RuleName, ValueMigration>
}

Expand All @@ -28,9 +28,35 @@ export type Migration = {
*
* @returns The migrated situation (and foldedSteps if specified).
*
* TODO: exemple of instructions (empty string for deletion, new key name for renaming, new value for updating)
* @example
* ```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 of instructions can be found {@link https://github.com/incubateur-ademe/nosgestesclimat/blob/preprod/migration/migration.yaml | here}.
* @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: Situation,
Expand All @@ -44,7 +70,7 @@ export function migrateSituation(
handleSpecialCases(rule, value, newSituation)

if (currentRules.includes(rule)) {
updateKey(rule, value, newSituation, instructions.rulesToMigrate[rule])
updateKey(rule, value, newSituation, instructions.keysToMigrate[rule])
}

const formattedValue = getValueWithoutQuotes(value) ?? (value as string)
Expand All @@ -54,33 +80,21 @@ export function migrateSituation(
] ?? {}
const oldValuesName = Object.keys(valuesMigration)

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"
oldValuesName.includes(formattedValue)
) {
if (oldValuesName.includes(formattedValue)) {
updateValue(rule, valuesMigration[formattedValue], newSituation)
}
})

return newSituation
}

// Handle migration of old value format : an object { valeur: number, unité: string }
/**
* Handles special cases during the migration of old value formats.
* Handle migration of old value format : an object { valeur: number, unité: string }.
*
* @example
* ````
{ valeur: number, unité: string }
```
*
* @param rule - The name of the rule.
* @param oldValue - The node value.
* @param situation - The situation object.
* @returns - The updated situation object.
* ```json
* { valeur: number, unité: string }
* ```
*/
function handleSpecialCases(
rule: RuleName,
Expand Down Expand Up @@ -116,8 +130,6 @@ function handleSpecialCases(
}
}

/**
*/
function updateKey(
rule: RuleName,
oldValue: Evaluation,
Expand All @@ -136,9 +148,7 @@ function updateKey(
}
}

/**
*/
export function updateValue(
function updateValue(
rule: RuleName,
value: string,
situation: Situation,
Expand All @@ -148,31 +158,9 @@ export function updateValue(
delete situation[rule]
} else {
// The value is renamed and needs to be migrated
situation[rule] = getMigratedValue(value)
}
}

function getMigratedValue(value: string): string {
if (typeof value === 'string' && value !== 'oui' && value !== 'non') {
return `'${value}'`
situation[rule] =
typeof value === 'string' && value !== 'oui' && value !== 'non'
? `'${value}'`
: value
}

// FIXME: I'm not sure if it's necessary to check if the value is a number,
// as valuesToMigrate is a ValueMigration object (Record<string, string>).
// Is it possible to have objects in valuesToMigrate?
// if (
// (
// value as unknown as {
// valeur: number
// }
// )?.valeur !== undefined
// ) {
// return (
// value as unknown as {
// valeur: number
// }
// ).valeur as unknown as string
// }

return value
}
142 changes: 99 additions & 43 deletions test/migration/migrateSituation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
} from '../../src/migration/migrateSituation'

const instructions: Migration = {
rulesToMigrate: { age: 'âge', 'année de naissance': '' },
keysToMigrate: { age: 'âge', 'année de naissance': '' },
valuesToMigrate: { prénom: { jean: 'Jean avec un J', michel: '' } },
}

Expand All @@ -15,47 +15,103 @@ const migrateSituationWithInstructions = (situation: Situation) =>
describe('migrateSituation', () => {
it('should migrate key', () => {
expect(migrateSituationWithInstructions({ age: 27 })).toEqual({ âge: 27 })
}),
it('should migrate value', () => {
expect(migrateSituationWithInstructions({ prénom: 'jean' })).toEqual({
prénom: "'Jean avec un J'",
})
}),
it('should delete key', () => {
expect(
migrateSituationWithInstructions({ 'année de naissance': 1997 }),
).toEqual({})
}),
it('should delete value', () => {
expect(
migrateSituationWithInstructions({
prénom: 'michel',
}),
).toEqual({})
}),
it('should support old situations (1)', () => {
expect(
migrateSituationWithInstructions({
âge: { valeur: 27, unité: 'an' },
}),
).toEqual({
âge: 27,
})
}),
it('should support old situations (2)', () => {
expect(
migrateSituationWithInstructions({
âge: {
type: 'number',
fullPrecision: true,
isNullable: false,
nodeValue: 27,
nodeKind: 'constant',
rawNode: 27,
},
}),
).toEqual({
âge: 27,
})
})

it('should migrate value', () => {
expect(migrateSituationWithInstructions({ prénom: 'jean' })).toEqual({
prénom: "'Jean avec un J'",
})
})

it('should delete key', () => {
expect(
migrateSituationWithInstructions({ 'année de naissance': 1997 }),
).toEqual({})
})

it('should delete value', () => {
expect(
migrateSituationWithInstructions({
prénom: 'michel',
}),
).toEqual({})
})

it('should support old situations (1)', () => {
expect(
migrateSituationWithInstructions({
âge: { valeur: 27, unité: 'an' },
}),
).toEqual({
âge: 27,
})
})

it('should support old situations (2)', () => {
expect(
migrateSituationWithInstructions({
âge: {
type: 'number',
fullPrecision: true,
isNullable: false,
nodeValue: 27,
nodeKind: 'constant',
rawNode: 27,
},
}),
).toEqual({
âge: 27,
})
})

it('should migrate the API example', () => {
const oldSituation = {
age: 25,
job: 'developer',
city: 'Paris',
}

const instructions = {
keysToMigrate: {
age: 'âge',
city: '',
},
valuesToMigrate: {
job: {
developer: 'développeur',
},
},
}
expect(migrateSituation(oldSituation, instructions)).toEqual({
âge: 25,
job: "'développeur'",
})
})

it('should not modify the original situation', () => {
const situation = {
job: 'developer',
âge: {
type: 'number',
fullPrecision: true,
isNullable: false,
nodeValue: 27,
nodeKind: 'constant',
rawNode: 27,
},
}

migrateSituation(situation, instructions)
expect(situation).toEqual({
âge: {
type: 'number',
fullPrecision: true,
isNullable: false,
nodeValue: 27,
nodeKind: 'constant',
rawNode: 27,
},
job: 'developer',
})
})
})

0 comments on commit 6c73142

Please sign in to comment.