diff --git a/CHANGELOG.md b/CHANGELOG.md index 580584bc976d11c8c6569e5770e8704db3406e95..7770d02e479197ae8c8ed4d0e62ccc15d7fe1e7b 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 856c53207f364e8bdde3055da5dbf9401dd625e6..320346246b9a3f745bf3c72ea1dd3079c14cd6e4 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 fbca002b19720557f92ceda9b532b0ca95531df4..6e0fbfc6cb71b7b16f9a00182047d236a419c998 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 f837bd05c5bcf0022dedbf3ababc13ac3781f238..5b95d8fe68ca1e6ffbc791ef46fda20c1e7ba2e4 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 c1305efcb2f20b33b525a0a8bed4dc0d5bcf6024..12719870f0514736462054edce1161d25f721eaf 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 2189788870321c50eed2d8e3fed56937f8a1b7e2..42e6b71677aa8a30bed782030b93ea8086041060 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 bbd3db0efaa3bf6dc0e26b4e0a8dbd227e1fa0e5..27e8408d285fc5f4fab68729f99cdcdd9fab3fe1 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 014a1a104c81a6f43bf29d9679aed3f96ea62e6e..a99bd4e99eb31bb9d9ebb7b60e9c745cc1038df1 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 5f17419398b7ae090182b69fb60ada24eab9052a..3073cdd0e0d4b82db6bb977153fb7567e70ba1e6 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 c462d91d07b77aa9ccd262757d82ed10017068ca..d750834626606b228a9dbd6dda292226c56c488b 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 2304a2f51321eec5319cf2303014ec02f0eaa422..bea791b40e0adec15fe2da943f3df4cc9c6c79ce 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 a79575e9f92b65cae32a8b509c4f6588fbed99cd..ea766400e86cbc57ca569cb0db43f60fd1852d8d 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 923c9836d95a51d89a0bd91de10f9785cc8724f5..bb1704d2df8cc46327ccc571ab6e75679d718530 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 aaee9099c64830813737693e0113150e2419adf6..57ea3c6099ffa237f363c284d2109dd395f8024d 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 ba204842817ece0837a4c7b43ef8e7478ae44d9f..d7e96dd9d28c4eacce472c5f5dba25f4bed9bae1 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"