diff --git a/src/services/initialization.service.spec.ts b/src/services/initialization.service.spec.ts index 073bc74f048fe627bbe3227fe7a8476773c842fe..19669c17d3c20ec8062a6339cd114fddf7adf0bc 100644 --- a/src/services/initialization.service.spec.ts +++ b/src/services/initialization.service.spec.ts @@ -5,13 +5,12 @@ import explorationEntityData from 'db/explorationEntity.json' import quizEntityData from 'db/quizEntity.json' import { FluidType } from 'enums' import { DateTime } from 'luxon' -import { Profile, UserChallenge } from 'models' +import { Profile, Trigger, UserChallenge } from 'models' import { allChallengeEntityData } from 'tests/__mocks__/challengeEntity.mock' import { graphData } from 'tests/__mocks__/chartData.mock' import mockClient from 'tests/__mocks__/client.mock' import { allDuelEntity } from 'tests/__mocks__/duelData.mock' import { allExplorationEntities } from 'tests/__mocks__/explorationData.mock' -import { fluidPrices } from 'tests/__mocks__/fluidPrice.mock' import { fluidStatusData } from 'tests/__mocks__/fluidStatusData.mock' import { allQuizEntities } from 'tests/__mocks__/quizData.mock' import { mockProfileState } from 'tests/__mocks__/store' @@ -184,15 +183,35 @@ describe('Initialization service', () => { }) describe('initFluidPrices method', () => { - it('should do nothing because prices are already created', async () => { - mockGetAllPrices.mockResolvedValueOnce(fluidPrices) - const isDone = await initializationService.initFluidPrices() - expect(isDone).toBeTruthy() - }) - it('should try to fetch prices from remote doctype', async () => { - mockGetAllPrices.mockResolvedValueOnce('') - const isDone = await initializationService.initFluidPrices() - expect(isDone).toBeFalsy() + it('should launch fluidsPrices service', async () => { + const mockQueryResult: QueryResult<Trigger[]> = { + data: [ + { + _id: 'triggerId', + type: '', + arguments: '', + message: { account: '', konnector: '' }, + worker: '', + }, + ], + bookmark: '', + next: false, + skip: 0, + } + mockClient.query.mockResolvedValueOnce(mockQueryResult) + await initializationService.initFluidPrices() + expect(mockClient.getStackClient).toBeCalledTimes(1) + }) + it('should not launch fluidsPrices service because trigger is not found', async () => { + const mockQueryResult: QueryResult<Trigger[]> = { + data: [], + bookmark: '', + next: false, + skip: 0, + } + mockClient.query.mockResolvedValueOnce(mockQueryResult) + await initializationService.initFluidPrices() + expect(mockClient.getStackClient).toBeCalledTimes(0) }) }) describe('initChallengeEntity method', () => { diff --git a/src/services/initialization.service.ts b/src/services/initialization.service.ts index a209b48de64b24fab7a231abadda791ca381c3c4..8dd6d371c0140e66bc432eb5bfdfaa12b2a73722 100644 --- a/src/services/initialization.service.ts +++ b/src/services/initialization.service.ts @@ -1,5 +1,5 @@ import * as Sentry from '@sentry/react' -import { Client } from 'cozy-client' +import { Client, Q, QueryDefinition, QueryResult } from 'cozy-client' import logger from 'cozy-logger' import challengeEntityData from 'db/challengeEntity.json' import duelEntityData from 'db/duelEntity.json' @@ -12,8 +12,7 @@ import { EXPLORATION_DOCTYPE, PROFILE_DOCTYPE, QUIZ_DOCTYPE, - REMOTE_ORG_ECOLYO_AGENT_PRICES, - REMOTE_ORG_ECOLYO_AGENT_PRICES_REC, + TRIGGERS_DOCTYPE, } from 'doctypes' import { FluidType } from 'enums' import { DateTime } from 'luxon' @@ -21,7 +20,6 @@ import { initSchemaDoctype } from 'migrations/migration' import { migrations } from 'migrations/migration.data' import { Dataload, - FluidPrice, FluidStatus, InitSteps, InitStepsErrors, @@ -29,6 +27,7 @@ import { ProfileEcogesture, ProfileType, TermsStatus, + Trigger, UserChallenge, } from 'models' import React from 'react' @@ -43,8 +42,6 @@ import { getActualAnalysisDate } from 'utils/date' import { logDuration } from 'utils/duration' import { hashFile } from 'utils/hash' import logApp from 'utils/logger' -import EnvironmentService from './environment.service' -import FluidPricesService from './fluidsPrices.service' import ProfileEcogestureService from './profileEcogesture.service' import ProfileTypeEntityService from './profileTypeEntity.service' import TermsService from './terms.service' @@ -161,47 +158,28 @@ export default class InitializationService { } } - public async initFluidPrices(): Promise<boolean> { + public async initFluidPrices(): Promise<void> { const startTime = performance.now() - const fpService = new FluidPricesService(this._client) - // Populate data if none ecogesture exists - const loadedPrices = await fpService.getAllPrices() - if (loadedPrices?.length) { - logDuration('[Initialization] FluidPrices db already created', startTime) - return true + logDuration('[Initialization] Launching fluidPrices service', startTime) + const triggerQuery: QueryDefinition = Q(TRIGGERS_DOCTYPE).where({ + 'message.name': 'fluidsPrices', + }) + const { + data: [trigger], + }: QueryResult<Array<Trigger>> = await this._client.query(triggerQuery) + if (trigger?._id) { + this._client + .getStackClient() + .fetchJSON('POST', `/jobs/triggers/${trigger._id}/launch`) + logDuration( + '[Initialization] fluidPrices service launched successfully', + startTime + ) } else { - try { - const fluidTypes: FluidType[] = [ - FluidType.ELECTRICITY, - FluidType.WATER, - FluidType.GAS, - ] - const allPrices: FluidPrice[] = [] - const env = new EnvironmentService() - const remoteUrl = env.isProduction() - ? REMOTE_ORG_ECOLYO_AGENT_PRICES - : REMOTE_ORG_ECOLYO_AGENT_PRICES_REC - - for (const fluid of fluidTypes) { - const prices = await this._client - .getStackClient() - .fetchJSON('GET', `${remoteUrl}?fluidtype=${fluid}`) - allPrices.push(...prices) - } - for (const price of allPrices) { - await fpService.createPrice(price) - } - logDuration('[Initialization] FluidPrices db created', startTime) - return true - } catch (error) { - const errorMessage = `Initialization error - initFluidPrices: ${JSON.stringify( - error - )}` - logStack('error', errorMessage) - logApp.error(errorMessage) - Sentry.captureException(errorMessage) - return false - } + logDuration( + '[Initialization] FluidPrices service trigger not found', + startTime + ) } } diff --git a/src/targets/services/fluidsPrices.ts b/src/targets/services/fluidsPrices.ts index e625a385086937796fbe16b27d78b2047df9e896..8bdfe52b45192366a8e2ce30593efcf693e1ae46 100644 --- a/src/targets/services/fluidsPrices.ts +++ b/src/targets/services/fluidsPrices.ts @@ -51,65 +51,56 @@ const synchroPricesToUpdate = async ( const remotePrices = await getRemotePricesByFluid(client, fluidType) let firstEditedPrice: string | null = null await Promise.all( - remotePrices.map(remotePrice => { - // eslint-disable-next-line no-async-promise-executor - return new Promise<void>(async resolve => { - try { - // Check if price exist in database - const existingPrice = await fps.checkIfPriceExists(remotePrice) - if (existingPrice) { - // Check if the remote price is more recent - if ( - existingPrice.UpdatedAt && - remotePrice.UpdatedAt && - existingPrice.UpdatedAt < remotePrice.UpdatedAt - ) { - logStack( - 'debug', - `Price exist in db but not up to date, updating it` - ) - // If a price has been updated, set the oldest startDate of the edited price so we can redo aggregation - if (firstEditedPrice === null) { - firstEditedPrice = remotePrice.startDate - } - if (firstEditedPrice >= remotePrice.startDate) { - firstEditedPrice = remotePrice.startDate - } - - // update this price in db - await fps.updatePrice(existingPrice, { - price: remotePrice.price, - UpdatedAt: remotePrice.UpdatedAt, - startDate: remotePrice.startDate, - endDate: remotePrice.endDate, - }) - } else if (!existingPrice.UpdatedAt && remotePrice.UpdatedAt) { - // updatedAt key doesn't exist in db - await fps.updatePrice(existingPrice, { - UpdatedAt: remotePrice.UpdatedAt, - }) - } else { - logStack('debug', `Price up to date`) - } - } else { - logStack('debug', `Price doesn't exist in db, creating new price`) - // If a price has been updated, set the oldest startDate of the edited price so we can redo aggregation - if (firstEditedPrice === null) { - firstEditedPrice = remotePrice.startDate - } - if (firstEditedPrice >= remotePrice.startDate) { - firstEditedPrice = remotePrice.startDate - } - // create price in db - await fps.createPrice(remotePrice) + remotePrices.map(async remotePrice => { + try { + const existingPrice = await fps.checkIfPriceExists(remotePrice) + if (!existingPrice) { + logStack('debug', `Price doesn't exist in db, creating a new price`) + // If a price has been updated, set the oldest startDate of the edited price so we can redo aggregation + if ( + firstEditedPrice === null || + firstEditedPrice >= remotePrice.startDate + ) { + firstEditedPrice = remotePrice.startDate } - } catch (error) { - logStack('error', `Error: ${error}`) - Sentry.captureException(JSON.stringify({ error })) - } finally { - resolve() + await fps.createPrice(remotePrice) + return } - }) + + if (!existingPrice.UpdatedAt && remotePrice.UpdatedAt) { + // updatedAt key doesn't exist in db + await fps.updatePrice(existingPrice, { + UpdatedAt: remotePrice.UpdatedAt, + }) + return + } else if ( + !existingPrice.UpdatedAt || + !remotePrice.UpdatedAt || + existingPrice.UpdatedAt >= remotePrice.UpdatedAt + ) { + logStack('debug', `Price up to date`) + return + } + logStack('debug', `Price exists in db but not up to date, updating it`) + // If a price has been updated, set the oldest startDate of the edited price so we can redo aggregation + if ( + firstEditedPrice === null || + firstEditedPrice >= remotePrice.startDate + ) { + firstEditedPrice = remotePrice.startDate + } + + // update this price in db + await fps.updatePrice(existingPrice, { + price: remotePrice.price, + UpdatedAt: remotePrice.UpdatedAt, + startDate: remotePrice.startDate, + endDate: remotePrice.endDate, + }) + } catch (error) { + logStack('error', `Error: ${error}`) + Sentry.captureException(JSON.stringify({ error })) + } }) ) return firstEditedPrice @@ -167,41 +158,36 @@ const aggregatePrices = async ( ) await Promise.all( tsa.map(async ts => { - // eslint-disable-next-line no-async-promise-executor - return new Promise<void>(async resolve => { - let date: DateTime = DateTime.local() - Object.assign(date, firstDate) - try { - do { - const tp = await getTimePeriod(ts, date) - // Get doc for aggregation - const data = await qr.fetchFluidRawDoctype( - tp, - TimeStep.DAY, - fluidType - ) - // Get doc to update - const docToUpdate = await qr.fetchFluidRawDoctype(tp, ts, fluidType) + let date: DateTime = DateTime.local() + Object.assign(date, firstDate) + try { + do { + const tp = await getTimePeriod(ts, date) + // Get doc for aggregation + const data = await qr.fetchFluidRawDoctype( + tp, + TimeStep.DAY, + fluidType + ) + // Get doc to update + const docToUpdate = await qr.fetchFluidRawDoctype(tp, ts, fluidType) - if (docToUpdate?.data && data?.data) { - docToUpdate.data[0].price = data.data.map(price).reduce(sum) - } - // Save updated docs - await cdm.saveDocs(docToUpdate.data) - // Update date according to timestep - if (ts === TimeStep.YEAR) { - date = date.plus({ year: 1 }).startOf('month') - } else { - date = date.plus({ month: 1 }).startOf('month') - } - } while (date < today) - } catch (error) { - logStack('info', `Error : ${error}`) - Sentry.captureException(JSON.stringify({ error })) - } finally { - resolve() - } - }) + if (docToUpdate?.data && data?.data) { + docToUpdate.data[0].price = data.data.map(price).reduce(sum) + } + // Save updated docs + await cdm.saveDocs(docToUpdate?.data) + // Update date according to timestep + if (ts === TimeStep.YEAR) { + date = date.plus({ year: 1 }).startOf('month') + } else { + date = date.plus({ month: 1 }).startOf('month') + } + } while (date < today) + } catch (error) { + logStack('info', `Error : ${error}`) + Sentry.captureException(JSON.stringify({ error })) + } }) ) logStack('debug', `Aggregation done`) @@ -302,51 +288,46 @@ const applyPrices = async (client: Client, fluidType: FluidType) => { // Hourly and daily prices await Promise.all( tsa.map(async timeStep => { - // eslint-disable-next-line no-async-promise-executor - return new Promise<void>(async resolve => { - let date: DateTime = DateTime.local().setZone('utc', { - keepLocalTime: true, - }) - Object.assign(date, firstDate) - try { - do { - // Get price - const priceData = await fluidsPricesService.getPrices( - fluidType, - date - ) - const tp = await getTimePeriod(timeStep, date) - // Get doc to update - const data = await qr.fetchFluidRawDoctype( - tp, - timeStep, - fluidType - ) - - // If lastItem has a price, skip this day (in order to save perf) - const lastItem = data?.data && data.data[data.data.length - 1] - if (lastItem && priceData) { - // if a price has been updated in backoffice re-calculates all price from the firstEditedPriceDate - data?.data.forEach((element: DataloadEntity) => { - element.price = element.load * priceData.price - }) - // Save updated docs - await cdm.saveDocs(data.data) - } - // Update date - if (timeStep === TimeStep.HALF_AN_HOUR) { - date = date.plus({ days: 1 }) - } else { - date = date.plus({ month: 1 }).startOf('month') - } - } while (date < today) - } catch (error) { - logStack('error', `ERROR : ${error} `) - Sentry.captureException(JSON.stringify({ error })) - } finally { - resolve() - } + let date: DateTime = DateTime.local().setZone('utc', { + keepLocalTime: true, }) + Object.assign(date, firstDate) + try { + do { + // Get price + const priceData = await fluidsPricesService.getPrices( + fluidType, + date + ) + const tp = await getTimePeriod(timeStep, date) + // Get doc to update + const data = await qr.fetchFluidRawDoctype( + tp, + timeStep, + fluidType + ) + + // If lastItem has a price, skip this day (in order to save perf) + const lastItem = data?.data && data.data[data.data.length - 1] + if (lastItem && priceData) { + // if a price has been updated in backoffice re-calculates all price from the firstEditedPriceDate + data?.data.forEach((element: DataloadEntity) => { + element.price = element.load * priceData.price + }) + // Save updated docs + await cdm.saveDocs(data.data) + } + // Update date + if (timeStep === TimeStep.HALF_AN_HOUR) { + date = date.plus({ days: 1 }) + } else { + date = date.plus({ month: 1 }).startOf('month') + } + } while (date < today) + } catch (error) { + logStack('error', `ERROR : ${error} `) + Sentry.captureException(JSON.stringify({ error })) + } }) )