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