From 97bececf3d363fa99caa87d340818bd5df6696e7 Mon Sep 17 00:00:00 2001 From: Hugo NOUTS <hnouts@grandlyon.com> Date: Tue, 26 Oct 2021 08:11:58 +0000 Subject: [PATCH] fix(migrations): profileType migrations --- .../Analysis/AnalysisConsumption.tsx | 2 +- .../ProfileType/ProfileTypeView.tsx | 2 +- src/db/profileTypeData.json | 2 +- src/migrations/migration.data.ts | 77 +++++++++++++++++-- src/migrations/migration.spec.ts | 6 +- src/migrations/migration.ts | 20 +++-- src/migrations/migration.type.ts | 4 +- src/models/profileType.model.ts | 2 +- src/services/initialization.service.ts | 22 +----- src/services/profileType.service.ts | 10 ++- src/services/profileTypeEntity.service.ts | 16 ++-- src/store/profileType/profileType.reducer.ts | 2 +- tests/__mocks__/profileType.mock.ts | 14 ++-- tests/__mocks__/store.ts | 2 +- 14 files changed, 124 insertions(+), 57 deletions(-) diff --git a/src/components/Analysis/AnalysisConsumption.tsx b/src/components/Analysis/AnalysisConsumption.tsx index a42831c9f..7285f7c55 100644 --- a/src/components/Analysis/AnalysisConsumption.tsx +++ b/src/components/Analysis/AnalysisConsumption.tsx @@ -73,7 +73,7 @@ const AnalysisConsumption: React.FC<AnalysisConsumptionProps> = ({ async function loadAverageComsumption() { const profileTypeEntityService = new ProfileTypeEntityService(client) const profileType: ProfileType | null = await profileTypeEntityService.getProfileType( - analysisDate + analysisDate.plus({ month: -1 }) ) if (profileType !== null) { const profileTypeService: ProfileTypeService = new ProfileTypeService( diff --git a/src/components/ProfileType/ProfileTypeView.tsx b/src/components/ProfileType/ProfileTypeView.tsx index 6dedebeb3..bbb61f1f0 100644 --- a/src/components/ProfileType/ProfileTypeView.tsx +++ b/src/components/ProfileType/ProfileTypeView.tsx @@ -41,7 +41,7 @@ const ProfileTypeView = () => { }), housingType: HousingType.INDIVIDUAL_HOUSE, constructionYear: ConstructionYear.BETWEEN_1975_AND_1989, - area: 0, + area: '0', occupantsNumber: 1, outsideFacingWalls: OutsideFacingWalls.ONE, floor: Floor.NOT_APPLICABLE, diff --git a/src/db/profileTypeData.json b/src/db/profileTypeData.json index c98ed8a35..beda4cf31 100644 --- a/src/db/profileTypeData.json +++ b/src/db/profileTypeData.json @@ -4,7 +4,7 @@ "updateDate": "0000-01-01T00:00:00.000Z", "housingType": "individual_house", "constructionYear": "between_1975_and_1989", - "area": 100, + "area": "100", "occupantsNumber": 4, "outsideFacingWalls": "2", "floor": "not_applicable", diff --git a/src/migrations/migration.data.ts b/src/migrations/migration.data.ts index 05ff0a99c..5170b93c3 100644 --- a/src/migrations/migration.data.ts +++ b/src/migrations/migration.data.ts @@ -1,6 +1,13 @@ import { Migration } from './migration.type' -import { PROFILE_DOCTYPE } from 'doctypes' -import { Profile } from 'models' +import { + PROFILE_DOCTYPE, + PROFILETYPE_DOCTYPE, + USERCHALLENGE_DOCTYPE, +} from 'doctypes' +import { Profile, ProfileType, UserChallenge } from 'models' +import { Client, Q, QueryDefinition, QueryResult } from 'cozy-client' +import { DateTime } from 'luxon' +import { UserQuizState } from 'enum/userQuiz.enum' export const SCHEMA_INITIAL_VERSION = 0 @@ -12,14 +19,74 @@ export const migrations: Migration[] = [ { baseSchemaVersion: SCHEMA_INITIAL_VERSION, targetSchemaVersion: 1, - appVersion: '1.2.4', - description: 'Removing GCUApprovalDate from profil', + appVersion: '1.3.0', + description: + 'Removes old profileType artifacts from users database : \n - Oldest profileType gets deleted \n - Removes insulation work form field prone to errors \n - Changes area & outsideFacingWalls form field to strings \n - Changes updateDate values of all existing profileType to match "created_at" entry (former updateDate values got corrupted and held no meaning).', + docTypes: PROFILETYPE_DOCTYPE, + run: async (_client: Client, docs: any[]): Promise<ProfileType[]> => { + docs.sort(function(a, b) { + const c = DateTime.fromISO(a.cozyMetadata.createdAt, { + zone: 'utc', + }) + const d = DateTime.fromISO(b.cozyMetadata.createdAt, { + zone: 'utc', + }) + return d.millisecond - c.millisecond + }) + if (docs[0].area === 100) { + docs[0].deleteAction = true + } + return docs.map(doc => { + if ( + doc.individualInsulationWork.includes( + 'window_replacement_and_wall_insulation' + ) + ) { + doc.individualInsulationWork = 'window_replacement' + } + doc.outsideFacingWalls = doc.outsideFacingWalls.toString() + doc.area = doc.area.toString() + doc.updateDate = doc.cozyMetadata.createdAt + return doc + }) + }, + }, + { + baseSchemaVersion: 1, + targetSchemaVersion: 2, + appVersion: '1.3.0', + description: 'Removes old profileType and GCUApprovalDate from profile.', docTypes: PROFILE_DOCTYPE, - run: (docs: any[]): Profile[] => { + run: async (_client: Client, docs: any[]): Promise<Profile[]> => { return docs.map(doc => { if (doc.GCUApprovalDate) { delete doc.GCUApprovalDate } + if (doc.profileType) { + delete doc.profileType + } + return doc + }) + }, + }, + { + baseSchemaVersion: 2, + targetSchemaVersion: 3, + appVersion: '1.3.0', + description: + 'Updates userChallenges to make sure no quiz results are overflowing.', + docTypes: USERCHALLENGE_DOCTYPE, + run: async (_client: Client, docs: any[]): Promise<UserChallenge[]> => { + return docs.map(doc => { + if (doc.quiz.result > 5) { + doc.quiz.result = 5 + doc.progress = { + actionProgress: 5, + explorationProgress: 5, + quizProgress: 5, + } + doc.quiz.state = UserQuizState.DONE + } return doc }) }, diff --git a/src/migrations/migration.spec.ts b/src/migrations/migration.spec.ts index 7237d8f94..8c44c2cb2 100644 --- a/src/migrations/migration.spec.ts +++ b/src/migrations/migration.spec.ts @@ -19,7 +19,7 @@ describe('migration logger', () => { appVersion: '1.2.4', description: 'Removing mailToken from profil', docTypes: PROFILE_DOCTYPE, - run: (docs: any[]): Profile[] => { + run: async (mockClient, docs: any[]): Promise<Profile[]> => { return docs.map(doc => { if (doc.mailToken) { delete doc.mailToken @@ -61,7 +61,7 @@ describe('migration', () => { appVersion: '1.2.4', description: 'Removing mailToken from profil', docTypes: PROFILE_DOCTYPE, - run: (docs: any[]): Profile[] => { + run: async (mockClient, docs: any[]): Promise<Profile[]> => { return docs.map(doc => { if (doc.GCUApprovalDate) { delete doc.GCUApprovalDate @@ -155,7 +155,7 @@ describe('migration', () => { appVersion: '1.2.4', description: 'Removing mailToken from profil', docTypes: PROFILE_DOCTYPE, - run: (docs: any[]): Profile[] => { + run: async (mockClient, docs: any[]): Promise<Profile[]> => { return [] }, } diff --git a/src/migrations/migration.ts b/src/migrations/migration.ts index 44692d985..fb9e19614 100644 --- a/src/migrations/migration.ts +++ b/src/migrations/migration.ts @@ -46,7 +46,7 @@ async function updateSchemaVersion( _client: Client, targetSchemaVersion: number ): Promise<void> { - log.info('[Migartion] Update schema version') + log.info('[Migration] Update schema version') const query: QueryDefinition = Q(SCHEMAS_DOCTYPE) const data: QueryResult<Schema[]> = await _client.query(query.limitBy(1)) const doc = data.data[0] @@ -67,7 +67,11 @@ async function save(_client: Client, docs: any[]): Promise<MigrationResult> { if (docs.length) { log.info('[Migration] Saving docs...') docs.forEach(async doc => { - await _client.save(doc) + if (doc.deleteAction) { + await _client.destroy(doc) + } else { + await _client.save(doc) + } }) log.info('[Migration] Docs saved') migrationResult.type = migrationResult.errors.length @@ -115,11 +119,13 @@ export async function migrate( let result: MigrationResult try { const docToUpdate: any[] = await getDocs(_client, migration.docTypes) - - const migratedDocs = migration.run(docToUpdate) - - if (migratedDocs.length) { - result = await save(_client, migratedDocs) + if (docToUpdate.length) { + const migratedDocs = await migration.run(_client, docToUpdate) + if (migratedDocs.length) { + result = await save(_client, migratedDocs) + } else { + result = migrationNoop() + } } else { result = migrationNoop() } diff --git a/src/migrations/migration.type.ts b/src/migrations/migration.type.ts index 5f20e8765..480539c36 100644 --- a/src/migrations/migration.type.ts +++ b/src/migrations/migration.type.ts @@ -1,3 +1,5 @@ +import { Client } from 'cozy-client' + type SchemaVersion = number export type MigrationData = { @@ -24,5 +26,5 @@ export type Migration = { description: string docTypes: string appVersion: string - run: (docs: any[]) => any[] + run: (_client: Client, docs: any[]) => Promise<any[]> } diff --git a/src/models/profileType.model.ts b/src/models/profileType.model.ts index 1110f01ef..af01150b1 100644 --- a/src/models/profileType.model.ts +++ b/src/models/profileType.model.ts @@ -33,7 +33,7 @@ export interface ProfileType extends ProfileTypeIndexableTypes { updateDate: DateTime | null housingType: HousingType constructionYear: ConstructionYear - area: number + area: string occupantsNumber: number outsideFacingWalls: OutsideFacingWalls floor: Floor diff --git a/src/services/initialization.service.ts b/src/services/initialization.service.ts index ac54169e8..dcee9ea98 100644 --- a/src/services/initialization.service.ts +++ b/src/services/initialization.service.ts @@ -38,7 +38,6 @@ import explorationEntityData from 'db/explorationEntity.json' import ProfileService from 'services/profile.service' import profileData from 'db/profileData.json' -import profileTypeData from 'db/profileTypeData.json' import KonnectorStatusService from 'services/konnectorStatus.service' import KonnectorService from 'services/konnector.service' import AccountService from 'services/account.service' @@ -188,25 +187,8 @@ export default class InitializationService { public async initProfileType(): Promise<ProfileType | null> { const profileTypeEntityService = new ProfileTypeEntityService(this._client) try { - let loadedProfileType = await profileTypeEntityService.getProfileType() - if (!loadedProfileType) { - const { data: newProfileType } = await this._client.create( - PROFILETYPE_DOCTYPE, - profileTypeData[0].profileType - ) - if (newProfileType) { - log.info('[Initialization] ProfileType created') - loadedProfileType = await profileTypeEntityService.updateProfileType({ - updateDate: DateTime.local().setZone('utc', { - keepLocalTime: true, - }), - }) - } else { - throw new Error('initProfileType: ProfileType not created') - } - } else { - log.info('[Initialization] ProfileType loaded') - } + const loadedProfileType = await profileTypeEntityService.getProfileType() + log.info('[Initialization] ProfileType loaded') return loadedProfileType } catch (error) { log.error('Initialization error - initProfileType: ', error) diff --git a/src/services/profileType.service.ts b/src/services/profileType.service.ts index 375220292..f07b7857f 100644 --- a/src/services/profileType.service.ts +++ b/src/services/profileType.service.ts @@ -25,7 +25,8 @@ import { } from 'enum/profileType.enum' import { FluidType } from 'enum/fluid.enum' import ConverterService from './converter.service' -import { Client } from 'cozy-client' +import { Client, Q, QueryDefinition, QueryResult } from 'cozy-client' +import log from 'utils/logger' export default class ProfileTypeService { private readonly profileType: ProfileType @@ -33,6 +34,10 @@ export default class ProfileTypeService { private readonly year: number constructor(profileType: ProfileType, _client: Client, year: number) { + log.info( + '[ProfileType] Analysis loaded profileType relative to : ', + profileType.updateDate.toString() + ) this.profileType = profileType this._client = _client this.year = year @@ -49,7 +54,8 @@ export default class ProfileTypeService { const ratiosHeatingByHousingType = ratiosHeating[housingType] const currentRatio: number = ratiosHeatingByHousingType[constructionYear] - const estimatedConsumption: number = this.profileType.area * currentRatio + const estimatedConsumption: number = + parseInt(this.profileType.area) * currentRatio return estimatedConsumption } diff --git a/src/services/profileTypeEntity.service.ts b/src/services/profileTypeEntity.service.ts index e0c195369..de3237a1b 100644 --- a/src/services/profileTypeEntity.service.ts +++ b/src/services/profileTypeEntity.service.ts @@ -2,6 +2,7 @@ import { Client, Q, QueryDefinition, QueryResult } from 'cozy-client' import { ProfileType } from 'models' import { PROFILETYPE_DOCTYPE } from 'doctypes' import { DateTime } from 'luxon' +import profileTypeData from 'db/profileTypeData.json' export default class ProfileTypeEntityService { private readonly _client: Client @@ -12,14 +13,13 @@ export default class ProfileTypeEntityService { /** * Retrieve ProfileType from the PROFILETYPE_DOCTYPE - * When called with date parameter, fetch closes profileType to the date + * When called with date parameter, fetch closest profileType to the date * When called without parameters, fetch last profileType in doctype * @param {DateTime} date * @returns {ProfileType} */ public async getProfileType(date?: DateTime): Promise<ProfileType | null> { const query: QueryDefinition = Q(PROFILETYPE_DOCTYPE) - if (date) { const { data: [profileType], @@ -33,10 +33,14 @@ export default class ProfileTypeEntityService { if (result) { return this.parseProfileTypeEntityToProfileType(profileType) } else { - // If no entry is return for a given date - // it means asked date is more recent than our last profile - // fetch last profile - return this.getProfileType() + // If no entry is returned for a given date + // it means asked date is older than our last profile + // return default profiletype + const loadedProfileType: any = { + ...profileTypeData[0].profileType, + updateDate: date, + } + return loadedProfileType } } else { const { diff --git a/src/store/profileType/profileType.reducer.ts b/src/store/profileType/profileType.reducer.ts index 82e676205..66884deae 100644 --- a/src/store/profileType/profileType.reducer.ts +++ b/src/store/profileType/profileType.reducer.ts @@ -21,7 +21,7 @@ import { ProfileTypeActionTypes } from './profileType.actions' const initialState: ProfileType = { housingType: HousingType.INDIVIDUAL_HOUSE, constructionYear: ConstructionYear.BETWEEN_1975_AND_1989, - area: 100, + area: '100', occupantsNumber: 4, outsideFacingWalls: OutsideFacingWalls.TWO, floor: Floor.NOT_APPLICABLE, diff --git a/tests/__mocks__/profileType.mock.ts b/tests/__mocks__/profileType.mock.ts index c2b79f9c7..d487d4524 100644 --- a/tests/__mocks__/profileType.mock.ts +++ b/tests/__mocks__/profileType.mock.ts @@ -22,7 +22,7 @@ export const profileTypeData: ProfileType = { _id: 'ed8a160e06431be15c8fdbb428000f6a', _rev: '16-f829d012bf290a3f9257be592d8c65d8', id: 'ed8a160e06431be15c8fdbb428000f6a', - area: 64, + area: '64', coldWater: IndividualOrCollective.INDIVIDUAL, constructionYear: ConstructionYear.AFTER_1998, cookingFluid: 0, @@ -47,7 +47,7 @@ export const profileTypeData: ProfileType = { export const mockProfileType: ProfileType = { housingType: HousingType.APPARTMENT, constructionYear: ConstructionYear.AFTER_1998, - area: 43, + area: '43', occupantsNumber: 1, outsideFacingWalls: OutsideFacingWalls.TWO, floor: Floor.GROUND_FLOOR, @@ -83,7 +83,7 @@ export const mockMonthEcsConsumptionThermo = 110 export const mockProfileType1: ProfileType = { housingType: HousingType.APPARTMENT, constructionYear: ConstructionYear.BETWEEN_1948_AND_1974, - area: 43, + area: '43', occupantsNumber: 2, outsideFacingWalls: OutsideFacingWalls.TWO, floor: Floor.INTERMEDIATE_FLOOR, @@ -111,7 +111,7 @@ export const mockMonthEcsConsumption1Solar = 134 export const mockProfileType2: ProfileType = { housingType: HousingType.INDIVIDUAL_HOUSE, constructionYear: ConstructionYear.BETWEEN_1948_AND_1974, - area: 90, + area: '90', occupantsNumber: 4, outsideFacingWalls: OutsideFacingWalls.FOUR, floor: Floor.GROUND_FLOOR, @@ -137,7 +137,7 @@ export const mockMonthConsumption2 = 3000 export const mockTestProfile1: ProfileType = { housingType: HousingType.INDIVIDUAL_HOUSE, constructionYear: ConstructionYear.BETWEEN_1948_AND_1974, - area: 110, + area: '110', occupantsNumber: 5, outsideFacingWalls: OutsideFacingWalls.FOUR, floor: Floor.NOT_APPLICABLE, @@ -201,7 +201,7 @@ export const mockMonthlyForecastJanuaryTestProfile1: MonthlyForecast = { export const mockTestProfile2: ProfileType = { housingType: HousingType.APPARTMENT, constructionYear: ConstructionYear.BETWEEN_1975_AND_1989, - area: 50, + area: '50', occupantsNumber: 2, outsideFacingWalls: OutsideFacingWalls.TWO, floor: Floor.GROUND_FLOOR, @@ -266,7 +266,7 @@ export const mockMonthlyForecastJanuaryTestProfile2: MonthlyForecast = { export const mockTestProfile3: ProfileType = { housingType: HousingType.APPARTMENT, constructionYear: ConstructionYear.BETWEEN_1948_AND_1974, - area: 50, + area: '50', occupantsNumber: 2, outsideFacingWalls: OutsideFacingWalls.ONE, floor: Floor.GROUND_FLOOR, diff --git a/tests/__mocks__/store.ts b/tests/__mocks__/store.ts index 1fe5285dc..4613dba2b 100644 --- a/tests/__mocks__/store.ts +++ b/tests/__mocks__/store.ts @@ -122,7 +122,7 @@ export const mockInitialProfileState: Profile = { export const mockInitialProfileTypeState: ProfileType = { housingType: HousingType.INDIVIDUAL_HOUSE, constructionYear: ConstructionYear.BETWEEN_1975_AND_1989, - area: 100, + area: '100', occupantsNumber: 4, outsideFacingWalls: OutsideFacingWalls.TWO, floor: Floor.NOT_APPLICABLE, -- GitLab