From 0ed0b5ac59596443fec0afc8e26929036ff5308c Mon Sep 17 00:00:00 2001 From: Guilhem CARRON <gcarron@grandlyon.com> Date: Wed, 2 Feb 2022 16:39:49 +0000 Subject: [PATCH] chore(release): 1.7.3 --- CHANGELOG.md | 15 +++ manifest.webapp | 10 +- package.json | 3 +- .../Konnector/KonnectorViewerCard.tsx | 12 +- src/components/Splash/SplashRoot.tsx | 3 + src/db/fluidPrices.json | 6 + src/db/profileData.json | 2 + src/migrations/migration.data.ts | 26 +--- src/models/profile.model.ts | 1 + src/services/account.service.ts | 7 +- src/services/fluidsPrices.service.ts | 8 +- src/services/initialization.service.spec.ts | 113 ++++++++++++++++++ src/services/initialization.service.ts | 68 ++++++++++- tests/__mocks__/profile.mock.ts | 1 + yarn.lock | 5 + 15 files changed, 236 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 580584bc9..7770d02e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,21 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +### [1.7.3](https://forge.grandlyon.com/web-et-numerique/llle_project/ecolyo/compare/v1.7.2...v1.7.3) (2022-02-02) + + +### Features + +* **fluidPrice:** Add new elec fluid price (1 feb 2022) ([0a82a61](https://forge.grandlyon.com/web-et-numerique/llle_project/ecolyo/commit/0a82a61d2a9793b019dc123ad393d5b03e60725d)) +* **fluidPrices:** Replaced migration system with hash system ([669ecaa](https://forge.grandlyon.com/web-et-numerique/llle_project/ecolyo/commit/669ecaa8767ed1ba3ac1a5d9877400665eca247b)) + + +### Bug Fixes + +* **manifest:** Change text for backoffice remote doctype description ([21f6c2d](https://forge.grandlyon.com/web-et-numerique/llle_project/ecolyo/commit/21f6c2d8655c63677cb3d37085a76aaad3babf02)) +* **modal:** Sort prices by fluid so it displays in the right order ([dc1db27](https://forge.grandlyon.com/web-et-numerique/llle_project/ecolyo/commit/dc1db276c209b188b25b0c0ff85542a1047d8ef3)) +* **connection:** Fix account duplication in connection flow ([917218f4](https://forge.grandlyon.com/web-et-numerique/llle_project/ecolyo/commit/917218f48f1fb8b1362279208751d75160e84e62)) + ### [1.7.2](https://forge.grandlyon.com/web-et-numerique/llle_project/ecolyo/compare/v1.7.1...v1.7.2) (2022-01-28) diff --git a/manifest.webapp b/manifest.webapp index 856c53207..320346246 100644 --- a/manifest.webapp +++ b/manifest.webapp @@ -3,7 +3,7 @@ "slug": "ecolyo", "icon": "icon.svg", "categories": ["energy"], - "version": "1.7.2", + "version": "1.7.3", "licence": "AGPL-3.0", "editor": "Métropole de Lyon", "default_locale": "fr", @@ -55,13 +55,13 @@ "description": "Requis pour envoyer des statistiques d'utilisation anonymisées." }, "backoffice-rec": { - "description": "Requis pour la récupération et l’envoi de statistiques d’utilisation anonymisées." + "description": "Requis pour la récupération des données de la newsletter dans un environment de test." }, "backoffice": { - "description": "Requis pour la récupération et l’envoi de statistiques d’utilisation anonymisées." + "description": "Requis pour la récupération des données de la newsletter." }, "backoffice-partners-info-rec": { - "description": "Requis pour la récupération du status des services partenaires" + "description": "Requis pour la récupération du status des services partenaires dans un environment de test." }, "backoffice-partners-info": { "description": "Requis pour la récupération du status des services partenaires" @@ -121,7 +121,7 @@ "description": "Required for retrieving newsletter informations from backoffice prod." }, "backoffice-partners-info-rec": { - "description": "Required for getting the status of partners' services" + "description": "Required for getting the status of partners' services in test environment." }, "backoffice-partners-info": { "description": "Required for getting the status of partners' services" diff --git a/package.json b/package.json index fbca002b1..6e0fbfc6c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ecolyo", - "version": "1.7.2", + "version": "1.7.3", "scripts": { "tx": "tx pull --all || true", "lint": "yarn lint:js && yarn lint:styles", @@ -101,6 +101,7 @@ "@cozy/minilog": "^1.0.0", "@material-ui/core": "~4.8.3", "@material-ui/styles": "^4.11.3", + "@simbathesailor/use-what-changed": "^2.0.0", "axios": "^0.19.0", "cozy-bar": "7.14.0", "cozy-client": "24.9.2", diff --git a/src/components/Konnector/KonnectorViewerCard.tsx b/src/components/Konnector/KonnectorViewerCard.tsx index f837bd05c..5b95d8fe6 100644 --- a/src/components/Konnector/KonnectorViewerCard.tsx +++ b/src/components/Konnector/KonnectorViewerCard.tsx @@ -311,6 +311,12 @@ const KonnectorViewerCard: React.FC<KonnectorViewerCardProps> = ({ fluidStatus.connection.shouldLaunchKonnector && !isKonnectorRunning(trigger) ) { + if (subscribed) { + if (fluidStatus.connection.isUpdating) setIsUpdating(true) + setOpenModal(true) + fluidStatus.connection.shouldLaunchKonnector = false + } + const connectionFlow = new ConnectionFlow(client, trigger, konnector) await connectionFlow.launch() connectionFlow.jobWatcher.on(ERROR_EVENT, () => { @@ -320,7 +326,6 @@ const KonnectorViewerCard: React.FC<KonnectorViewerCardProps> = ({ setKonnectorErrorDescription(connectionFlow.jobWatcher.on()._error) callbackResponse(ERROR_EVENT) }) - // When LOGIN SUCESS EVENT is triggered, the status retrieve from the trigger is still running connectionFlow.jobWatcher.on(LOGIN_SUCCESS_EVENT, () => { if (subscribed) { sendUsageEventSuccess(fluidSlug, fluidStatus.lastDataDate === null) @@ -333,11 +338,6 @@ const KonnectorViewerCard: React.FC<KonnectorViewerCardProps> = ({ } callbackResponse(SUCCESS_EVENT) }) - - if (subscribed) { - if (fluidStatus.connection.isUpdating) setIsUpdating(true) - setOpenModal(true) - } } } getData() diff --git a/src/components/Splash/SplashRoot.tsx b/src/components/Splash/SplashRoot.tsx index c1305efcb..12719870f 100644 --- a/src/components/Splash/SplashRoot.tsx +++ b/src/components/Splash/SplashRoot.tsx @@ -135,6 +135,7 @@ const SplashRoot = ({ setValidExploration(UserExplorationID.EXPLORATION007) const [ ecogestureHash, + fluidPricesHash, duelHash, quizHash, challengeHash, @@ -142,6 +143,7 @@ const SplashRoot = ({ analysisResult, ] = await Promise.all([ initializationService.initEcogesture(profile.ecogestureHash), + initializationService.initFluidPrices(profile.fluidPricesHash), initializationService.initDuelEntity(profile.duelHash), initializationService.initQuizEntity(profile.quizHash), initializationService.initExplorationEntity(profile.challengeHash), @@ -149,6 +151,7 @@ const SplashRoot = ({ initializationService.initAnalysis(profile), ]) profile.ecogestureHash = ecogestureHash + profile.fluidPricesHash = fluidPricesHash profile.duelHash = duelHash profile.quizHash = quizHash profile.challengeHash = challengeHash diff --git a/src/db/fluidPrices.json b/src/db/fluidPrices.json index 218978887..42e6b7167 100644 --- a/src/db/fluidPrices.json +++ b/src/db/fluidPrices.json @@ -81,6 +81,12 @@ "fluidType": 0, "price": 0.1558, "startDate": "2021-08-01T00:00:00.000Z", + "endDate": "2022-01-31T23:59:59.000Z" + }, + { + "fluidType": 0, + "price": 0.174, + "startDate": "2022-02-01T00:00:00.000Z", "endDate": null }, { diff --git a/src/db/profileData.json b/src/db/profileData.json index bbd3db0ef..27e8408d2 100644 --- a/src/db/profileData.json +++ b/src/db/profileData.json @@ -5,6 +5,7 @@ "mailToken": "", "duelHash": "", "quizHash": "", + "fluidPricesHash": "", "isFirstConnection": true, "lastConnectionDate": "0000-01-01T00:00:00.000Z", "haveSeenLastAnalysis": true, @@ -13,6 +14,7 @@ "sendConsumptionAlert": false, "waterDailyConsumptionLimit": 0, "isProfileTypeCompleted": false, + "isProfileEcogestureCompleted": false, "onboarding": { "isWelcomeSeen": false }, diff --git a/src/migrations/migration.data.ts b/src/migrations/migration.data.ts index 014a1a104..a99bd4e99 100644 --- a/src/migrations/migration.data.ts +++ b/src/migrations/migration.data.ts @@ -284,20 +284,12 @@ export const migrations: Migration[] = [ baseSchemaVersion: 8, targetSchemaVersion: 9, appVersion: '1.6.0', - description: 'Init new doctype fluidPrices', + description: 'Init new doctype fluidPrices --deprecated--', releaseNotes: null, docTypes: FLUIDPRICES_DOCTYPE, isCreate: true, run: async (_client: Client, docs: any[]): Promise<any> => { - if (docs.length > 0) { - console.log('collection already exists, delete all') - docs.forEach(async doc => { - await _client.destroy(doc) - }) - } - for (const fluidPrice of fluidsPricesData) { - await _client.create(FLUIDPRICES_DOCTYPE, fluidPrice) - } + return null }, }, { @@ -502,22 +494,12 @@ export const migrations: Migration[] = [ baseSchemaVersion: 17, targetSchemaVersion: 18, appVersion: '1.7.0', - description: 'Init new fluidPrices for water', + description: 'Init new fluidPrices for water -- deprecated --', releaseNotes: null, docTypes: FLUIDPRICES_DOCTYPE, run: async (_client: Client, docs: any[]): Promise<any> => { - const waterPricesData: any[] = fluidsPricesData.filter(fluidPriceData => { - return fluidPriceData.fluidType === FluidType.WATER - }) - const createWaterPricesData = waterPricesData.map( - (waterPriceData: any) => { - waterPriceData.createAction = true - waterPriceData.doctype = FLUIDPRICES_DOCTYPE - return waterPriceData - } - ) - return createWaterPricesData + return null }, }, { diff --git a/src/models/profile.model.ts b/src/models/profile.model.ts index 5f1741939..3073cdd0e 100644 --- a/src/models/profile.model.ts +++ b/src/models/profile.model.ts @@ -11,6 +11,7 @@ export interface ProfileEntity { duelHash: string quizHash: string explorationHash: string + fluidPricesHash: string isFirstConnection: boolean lastConnectionDate: string haveSeenLastAnalysis: boolean diff --git a/src/services/account.service.ts b/src/services/account.service.ts index c462d91d0..d75083462 100644 --- a/src/services/account.service.ts +++ b/src/services/account.service.ts @@ -31,12 +31,7 @@ export default class AccountService { konnector, accountAuthData ) - const account = await createAccount( - this._client, - konnector, - accountAttributes - ) - return account + return createAccount(this._client, konnector, accountAttributes) } public async getAccount(id: string): Promise<Account> { diff --git a/src/services/fluidsPrices.service.ts b/src/services/fluidsPrices.service.ts index 2304a2f51..bea791b40 100644 --- a/src/services/fluidsPrices.service.ts +++ b/src/services/fluidsPrices.service.ts @@ -17,7 +17,8 @@ export default class FluidPricesService { * @returns {FluidPrice[]} */ public async getAllPrices(): Promise<FluidPrice[]> { - const query: QueryDefinition = Q(FLUIDPRICES_DOCTYPE) + const query: QueryDefinition = Q(FLUIDPRICES_DOCTYPE).limitBy(900) + //TODO : handle case of 1000+ entries in doctype const { data: fluidsPrices, }: QueryResult<FluidPrice[]> = await this._client.query(query) @@ -52,6 +53,7 @@ export default class FluidPricesService { public async getAllLastPrices(): Promise<FluidPrice[]> { const query: QueryDefinition = Q(FLUIDPRICES_DOCTYPE) .where({ endDate: { $eq: null } }) + .sortBy([{ fluidType: 'asc' }]) .limitBy(3) const { @@ -94,8 +96,8 @@ export default class FluidPricesService { public async deleteAllFluidsPrices(): Promise<boolean> { const fluidsPrices = await this.getAllPrices() try { - for (let index = 0; index < fluidsPrices.length; index++) { - await this._client.destroy(fluidsPrices[index]) + for (const price of fluidsPrices) { + await this._client.destroy(price) } return true } catch (error) { diff --git a/src/services/initialization.service.spec.ts b/src/services/initialization.service.spec.ts index a79575e9f..ea766400e 100644 --- a/src/services/initialization.service.spec.ts +++ b/src/services/initialization.service.spec.ts @@ -9,6 +9,7 @@ import challengeEntityData from 'db/challengeEntity.json' import duelEntityData from 'db/duelEntity.json' import quizEntityData from 'db/quizEntity.json' import explorationEntityData from 'db/explorationEntity.json' +import fluidPrices from 'db/fluidPrices.json' import { hashFile } from 'utils/hash' import { getActualAnalysisDate } from 'utils/date' @@ -22,6 +23,7 @@ import { allChallengeEntityData } from '../../tests/__mocks__/challengeEntity.mo import { allDuelEntity } from '../../tests/__mocks__/duelData.mock' import { allQuizEntities } from '../../tests/__mocks__/quizData.mock' import { allExplorationEntities } from '../../tests/__mocks__/explorationData.mock' +import { fluidPrices as allFluidPrices } from '../../tests/__mocks__/fluidPrice.mock' import { mockOutdatedTerm, mockUpToDateTerm, @@ -84,6 +86,17 @@ jest.mock('./challenge.service', () => { }) }) +const mockGetAllPrices = jest.fn() +const mockDeleteAllFluidsPrices = jest.fn() +jest.mock('./fluidsPrices.service', () => { + return jest.fn(() => { + return { + getAllPrices: mockGetAllPrices, + deleteAllFluidsPrices: mockDeleteAllFluidsPrices, + } + }) +}) + const mockGetAllDuelEntities = jest.fn() const mockDeleteAllDuelEntities = jest.fn() jest.mock('./duel.service', () => { @@ -340,6 +353,106 @@ describe('Initialization service', () => { }) }) + describe('initFluidPrices method', () => { + beforeEach(() => { + mockGetAllPrices.mockClear() + mockDeleteAllFluidsPrices.mockClear() + }) + it('should return hash when fluidPrices hash is already up to date', async () => { + mockGetAllPrices.mockResolvedValueOnce(fluidPrices) + const hash = hashFile(fluidPrices) + await expect( + initializationService.initFluidPrices(hash) + ).resolves.toEqual(hash) + }) + it('should return hash when fluidPrices are created', async () => { + mockGetAllPrices + .mockResolvedValueOnce(null) + .mockResolvedValueOnce(fluidPrices) + const mockQueryResult: QueryResult<boolean> = { + data: true, + bookmark: '', + next: false, + skip: 0, + } + mockClient.create.mockResolvedValue(mockQueryResult) + const hash = hashFile(fluidPrices) + await expect( + initializationService.initFluidPrices(hash) + ).resolves.toEqual(hash) + }) + it('should throw an error when fluidPrices entities should be created and created fluidPrices entities number does not match', async () => { + mockGetAllPrices + .mockResolvedValueOnce(null) + .mockResolvedValueOnce(allFluidPrices) + const mockQueryResult: QueryResult<boolean> = { + data: true, + bookmark: '', + next: false, + skip: 0, + } + mockClient.create.mockResolvedValue(mockQueryResult) + await expect( + initializationService.initFluidPrices(hashFile(fluidPrices)) + ).rejects.toThrow( + new Error( + 'initFluidPrices: Created fluidPrices type entities does not match' + ) + ) + }) + it('should throw an error when fluidPrices should be created and creation failed', async () => { + mockGetAllPrices.mockResolvedValueOnce(null) + mockClient.create.mockRejectedValue(new Error()) + await expect( + initializationService.initChallengeEntity(hashFile(fluidPrices)) + ).rejects.toThrow(new Error()) + }) + it('should return hash when fluidPrices are updated', async () => { + mockGetAllPrices + .mockResolvedValueOnce(fluidPrices) + .mockResolvedValueOnce(fluidPrices) + mockDeleteAllFluidsPrices.mockResolvedValue(true) + const mockQueryResult: QueryResult<boolean> = { + data: true, + bookmark: '', + next: false, + skip: 0, + } + mockClient.create.mockResolvedValue(mockQueryResult) + await expect(initializationService.initFluidPrices('')).resolves.toEqual( + hashFile(fluidPrices) + ) + }) + it('should throw an error when fluidPrices should be updated and fluidPrices entities number does not match', async () => { + mockGetAllPrices + .mockResolvedValueOnce('') + .mockResolvedValueOnce(allFluidPrices) + mockDeleteAllFluidsPrices.mockResolvedValue(true) + const mockQueryResult: QueryResult<boolean> = { + data: true, + bookmark: '', + next: false, + skip: 0, + } + mockClient.create.mockResolvedValue(mockQueryResult) + await expect(initializationService.initFluidPrices('')).rejects.toThrow( + new Error( + 'initFluidPrices: Created fluidPrices type entities does not match' + ) + ) + }) + it('should throw an error whenfluidPrices should be updated and fluidPrices entities creation failed', async () => { + mockGetAllPrices + .mockResolvedValueOnce(fluidPrices) + .mockResolvedValueOnce(fluidPrices) + mockDeleteAllFluidsPrices.mockResolvedValue(true) + mockClient.create.mockRejectedValueOnce(new Error()) + expect(initializationService.initFluidPrices('')).rejects.toThrow( + new Error() + ) + }) + }) + describe('initChallengeEntity method', () => { beforeEach(() => { mockGetAllChallengeEntities.mockClear() diff --git a/src/services/initialization.service.ts b/src/services/initialization.service.ts index 923c9836d..bb1704d2d 100644 --- a/src/services/initialization.service.ts +++ b/src/services/initialization.service.ts @@ -16,6 +16,7 @@ import { DUEL_DOCTYPE, QUIZ_DOCTYPE, EXPLORATION_DOCTYPE, + FLUIDPRICES_DOCTYPE, } from 'doctypes' import { FluidType } from 'enum/fluid.enum' @@ -35,6 +36,7 @@ import challengeEntityData from 'db/challengeEntity.json' import duelEntityData from 'db/duelEntity.json' import quizEntityData from 'db/quizEntity.json' import explorationEntityData from 'db/explorationEntity.json' +import fluidPrices from 'db/fluidPrices.json' import ProfileService from 'services/profile.service' import profileData from 'db/profileData.json' @@ -58,6 +60,7 @@ import TermsService from './terms.service' import log from 'utils/logger' import { ProfileEcogesture } from 'models/profileEcogesture.model' import ProfileEcogestureService from './profileEcogesture.service' +import FluidPricesService from './fluidsPrices.service' export default class InitializationService { private readonly _client: Client @@ -287,6 +290,69 @@ export default class InitializationService { } } + public async initFluidPrices(hash: string): Promise<string> { + const hashFluidPricesType = hashFile(fluidPrices) + const fpService = new FluidPricesService(this._client) + // Populate data if none ecogesture exists + const loadedPrices = await fpService.getAllPrices() + if (!loadedPrices || (loadedPrices && loadedPrices.length === 0)) { + // Populate the doctype with data + + try { + for (const price of fluidPrices) { + await this._client.create(FLUIDPRICES_DOCTYPE, price) + } + // Check of created document based on count + const checkCount = await fpService.getAllPrices() + if ( + !checkCount || + (checkCount && checkCount.length !== fluidPrices.length) + ) { + throw new Error( + 'initFluidPrices: Created fluidPrices type entities does not match' + ) + } + log.info('[Initialization] FluidPrices list created') + return hashFluidPricesType + } catch (error) { + log.error('Initialization error - initFluidPrices: ', error) + throw error + } + } + // Update if the hash is not the same as the one from profile + if (hash !== hashFluidPricesType) { + // Update the doctype + try { + // Deletion of all documents + await fpService.deleteAllFluidsPrices() + // Population with the data + for (const price of fluidPrices) { + await this._client.create(FLUIDPRICES_DOCTYPE, price) + } + // Check of created document based on count + const checkCount = await fpService.getAllPrices() + + if ( + !checkCount || + (checkCount && checkCount.length !== fluidPrices.length) + ) { + throw new Error( + 'initFluidPrices: Created fluidPrices type entities does not match' + ) + } + log.info('[Initialization] FluidPrices updated') + return hashFluidPricesType + } catch (error) { + log.error('Initialization error - initFluidPrices: ', error) + throw error + } + } else { + // Doctype already up to date + log.info('[Initialization] FluidPrices already up-to-date') + return hashFluidPricesType + } + } + public async initChallengeEntity(hash: string): Promise<string> { const challengeHash = hashFile(challengeEntityData) const challengeService = new ChallengeService(this._client) @@ -407,7 +473,7 @@ export default class InitializationService { } } else { // Doctype already up to date - log.info('[Initialization] Challenge Entity loaded') + log.info('[Initialization] Duel Entity loaded') return hashDuelEntity } } diff --git a/tests/__mocks__/profile.mock.ts b/tests/__mocks__/profile.mock.ts index aaee9099c..57ea3c609 100644 --- a/tests/__mocks__/profile.mock.ts +++ b/tests/__mocks__/profile.mock.ts @@ -6,6 +6,7 @@ export const profileData: Profile = { _rev: '16-57473da4fc26315247c217083175dfa0', id: '4d9403218ef13e65b2e3a8ad1700bc41', ecogestureHash: '9798a0aaccb47cff906fc4931a2eff5f9371dd8b', + fluidPricesHash: '9798a0aaccb47cff906fc4931a2eff5f9371dd8a', challengeHash: '1136feb6185c7643e071d14180c0e95782aa4ba3', duelHash: '1136feb6185c7643e071d14180c0e95782aa4ba3', quizHash: '1136feb6185c7643e071d14180c0e95782aa4ba3', diff --git a/yarn.lock b/yarn.lock index ba2048428..d7e96dd9d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1490,6 +1490,11 @@ "@sentry/types" "6.15.0" tslib "^1.9.3" +"@simbathesailor/use-what-changed@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@simbathesailor/use-what-changed/-/use-what-changed-2.0.0.tgz#7f82d78f92c8588b5fadd702065dde93bd781403" + integrity sha512-ulBNrPSvfho9UN6zS2fii3AsdEcp2fMaKeqUZZeCNPaZbB6aXyTUhpEN9atjMAbu/eyK3AY8L4SYJUG62Ekocw== + "@sindresorhus/fnv1a@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@sindresorhus/fnv1a/-/fnv1a-1.2.0.tgz#d554da64c406f3b62ad06dfce9efd537a4a55de4" -- GitLab