diff --git a/src/components/Connection/ConnectionOAuth.tsx b/src/components/Connection/ConnectionOAuth.tsx index bd4d0acab90dbab4d7ab949b67ad091ddacbc8ff..28ae7b3557d8654bfc0f7c5a9512c9e36b9bde76 100644 --- a/src/components/Connection/ConnectionOAuth.tsx +++ b/src/components/Connection/ConnectionOAuth.tsx @@ -67,11 +67,6 @@ const ConnectionOAuth: React.FC<ConnectionOAuthProps> = ({ account: account, trigger: trigger, } - await UsageEventService.addEvent(client, { - type: UsageEventType.KONNECTOR_CONNECT_EVENT, - target: konnectorSlug, - result: 'success', - }) dispatch( updatedFluidConnection(fluidStatus.fluidType, updatedConnection) ) diff --git a/src/components/Connection/ConnectionResult.tsx b/src/components/Connection/ConnectionResult.tsx index 22ea201a00fc680d581756ec30566b39457ec8dc..dfccf4c755655b7892a759ba0f9998b365a1d78d 100644 --- a/src/components/Connection/ConnectionResult.tsx +++ b/src/components/Connection/ConnectionResult.tsx @@ -1,255 +1,243 @@ -import React, { useState, useEffect, useCallback } from 'react' -import { useI18n } from 'cozy-ui/transpiled/react/I18n' -import { useClient } from 'cozy-client' -import { useDispatch } from 'react-redux' -import { updatedFluidConnection } from 'store/global/global.actions' - -import { Account, FluidConnection, FluidStatus } from 'models' -import AccountService from 'services/account.service' -import { getKonnectorUpdateError } from 'utils/utils' - -import StyledIcon from 'components/CommonKit/Icon/StyledIcon' -import warningWhite from 'assets/icons/ico/warning-white.svg' -import Button from '@material-ui/core/Button' -import StyledBlackSpinner from 'components/CommonKit/Spinner/StyledBlackSpinner' - -import './connectionResult.scss' -import { UsageEventType } from 'enum/usageEvent.enum' -import UsageEventService from 'services/usageEvent.service' -import DateChartService from 'services/dateChart.service' -import { FluidState, FluidType } from 'enum/fluid.enum' -import { DateTime } from 'luxon' - -interface ConnectionResultProps { - fluidStatus: FluidStatus - handleAccountDeletion: Function - fluidType: FluidType -} - -const ConnectionResult: React.FC<ConnectionResultProps> = ({ - fluidStatus, - handleAccountDeletion, - fluidType, -}: ConnectionResultProps) => { - const { t } = useI18n() - const client = useClient() - const dispatch = useDispatch() - const account: Account | null = fluidStatus.connection.account - - const [updating, setUpdating] = useState<boolean>(false) - const [lastExecutionDate, setLastExecutionDate] = useState<string | DateTime>( - '-' - ) - const [konnectorError, setKonnectorError] = useState<string>('') - const [status, setStatus] = useState<string>('') - const [outDatedDataDays, setOutDatedDataDays] = useState<number | null>(null) - - const updateKonnector = async () => { - setUpdating(true) - setStatus('') - setLastExecutionDate('-') - setKonnectorError('') - const updatedConnection: FluidConnection = { - ...fluidStatus.connection, - shouldLaunchKonnector: true, - isUpdating: true, - } - await UsageEventService.addEvent(client, { - type: UsageEventType.KONNECTOR_REFRESH_EVENT, - target: updatedConnection.konnector - ? updatedConnection.konnector.slug - : '', - result: - updatedConnection.triggerState && - updatedConnection.triggerState.status === 'done' - ? 'success' - : 'error', - }) - dispatch(updatedFluidConnection(fluidStatus.fluidType, updatedConnection)) - setUpdating(false) - } - - const deleteAccount = async () => { - setUpdating(true) - try { - if (account) { - const accountService = new AccountService(client) - await accountService.deleteAccount(account) - await handleAccountDeletion() - } - } catch (error) { - setUpdating(false) - } - } - const isOutdated = useCallback(() => { - const dateChartService = new DateChartService() - - return dateChartService.isDataOutdated(fluidStatus.lastDataDate, fluidType) - }, [fluidStatus, fluidType]) - - const hasUpdatedToday = useCallback(() => { - const todayDate = DateTime.local() - .setZone('utc', { - keepLocalTime: true, - }) - .minus({ days: 1 }) - .toLocaleString() - if (lastExecutionDate === '-') { - return false - } else if (lastExecutionDate > todayDate) { - return false - } else { - return true - } - }, [lastExecutionDate]) - - useEffect(() => { - if ( - fluidStatus.connection.triggerState && - fluidStatus.connection.triggerState.last_success - ) { - const result = DateTime.fromISO( - fluidStatus.connection.triggerState.last_success - ) - setLastExecutionDate(result) - } else { - setLastExecutionDate('-') - } - if ( - fluidStatus.connection.triggerState && - fluidStatus.connection.triggerState.status === 'errored' && - fluidStatus.connection.triggerState.last_error - ) { - setStatus('errored') - setKonnectorError( - getKonnectorUpdateError(fluidStatus.connection.triggerState.last_error) - ) - } - - if (isOutdated()) { - setOutDatedDataDays(isOutdated()) - } - }, [fluidStatus.connection.triggerState, isOutdated]) - - return ( - <div className="connection-update-result"> - <div - className={ - status === 'errored' && - !hasUpdatedToday() && - fluidStatus.status !== FluidState.PARTNER_ISSUE - ? 'connection-update-errored' - : '' - } - > - {outDatedDataDays || fluidStatus.status === FluidState.PARTNER_ISSUE ? ( - <div className="connection-caption text-16-normal"> - <div className="text-16-normal"> - {fluidStatus.status === FluidState.PARTNER_ISSUE ? ( - <div className="connection-caption"> - {t('konnector_form.wait_end_issue')} - </div> - ) : ( - <> - {hasUpdatedToday() === true ? ( - <> - <div className="connection-caption"> - {t('konnector_form.label_updated_at')} - </div> - <div className="text-16-bold"> - {lastExecutionDate.toLocaleString()} - </div> - <div> - {fluidStatus && - fluidStatus.connection && - fluidStatus.connection.konnector && - t('konnector_form.issue') + - ' ' + - fluidStatus.connection.konnector.name + - '.'} - </div> - </> - ) : ( - <div className="connection-caption-errored connection-update-errored warning-white text-16-normal"> - <StyledIcon - icon={warningWhite} - size={36} - className="warning-icon" - role="img" - title="Attention" - ariaHidden={false} - /> - <div className="text-16-normal"> - {t('konnector_form.resolve')} - </div> - </div> - )} - </> - )} - </div> - </div> - ) : status === 'errored' ? ( - <div className="connection-caption-errored warning-white text-16-normal"> - <StyledIcon - icon={warningWhite} - size={36} - className="warning-icon" - role="img" - title="Attention" - ariaHidden={false} - /> - - <div className="text-16-normal"> - {t(`konnector_form.${konnectorError}`)} - <div className="connection-caption"> - {t('konnector_form.label_updated_at')} - </div> - <div className="text-16-bold"> - {lastExecutionDate.toLocaleString()} - </div> - </div> - </div> - ) : ( - <div> - <div className="connection-caption text-16-normal"> - {t('konnector_form.label_updated_at')} - </div> - <div className="text-16-bold"> - {lastExecutionDate.toLocaleString()} - </div> - </div> - )} - </div> - <div className="inline-buttons"> - <Button - aria-label={t('konnector_form.accessibility.button_delete')} - onClick={deleteAccount} - disabled={updating} - classes={{ - root: 'btn-secondary-positive', - label: 'text-16-normal', - }} - > - {t('konnector_form.button_delete')} - </Button> - <Button - aria-label={t('konnector_form.accessibility.button_update')} - onClick={updateKonnector} - disabled={updating} - classes={{ - root: 'btn-highlight', - label: 'text-16-bold', - }} - > - {updating ? ( - <StyledBlackSpinner size="2em" /> - ) : ( - <div>{t('konnector_form.button_update')}</div> - )} - </Button> - </div> - </div> - ) -} - -export default ConnectionResult +import React, { useState, useEffect, useCallback } from 'react' +import { useI18n } from 'cozy-ui/transpiled/react/I18n' +import { useClient } from 'cozy-client' +import { useDispatch } from 'react-redux' +import { updatedFluidConnection } from 'store/global/global.actions' + +import { Account, FluidConnection, FluidStatus } from 'models' +import AccountService from 'services/account.service' +import { getKonnectorUpdateError } from 'utils/utils' + +import StyledIcon from 'components/CommonKit/Icon/StyledIcon' +import warningWhite from 'assets/icons/ico/warning-white.svg' +import Button from '@material-ui/core/Button' +import StyledBlackSpinner from 'components/CommonKit/Spinner/StyledBlackSpinner' + +import './connectionResult.scss' +import UsageEventService from 'services/usageEvent.service' +import DateChartService from 'services/dateChart.service' +import { FluidState, FluidType } from 'enum/fluid.enum' +import { DateTime } from 'luxon' + +interface ConnectionResultProps { + fluidStatus: FluidStatus + handleAccountDeletion: Function + fluidType: FluidType +} + +const ConnectionResult: React.FC<ConnectionResultProps> = ({ + fluidStatus, + handleAccountDeletion, + fluidType, +}: ConnectionResultProps) => { + const { t } = useI18n() + const client = useClient() + const dispatch = useDispatch() + const account: Account | null = fluidStatus.connection.account + + const [updating, setUpdating] = useState<boolean>(false) + const [lastExecutionDate, setLastExecutionDate] = useState<string | DateTime>( + '-' + ) + const [konnectorError, setKonnectorError] = useState<string>('') + const [status, setStatus] = useState<string>('') + const [outDatedDataDays, setOutDatedDataDays] = useState<number | null>(null) + + const updateKonnector = async () => { + setUpdating(true) + setStatus('') + setLastExecutionDate('-') + setKonnectorError('') + const updatedConnection: FluidConnection = { + ...fluidStatus.connection, + shouldLaunchKonnector: true, + isUpdating: true, + } + dispatch(updatedFluidConnection(fluidStatus.fluidType, updatedConnection)) + setUpdating(false) + } + + const deleteAccount = async () => { + setUpdating(true) + try { + if (account) { + const accountService = new AccountService(client) + await accountService.deleteAccount(account) + await handleAccountDeletion() + } + } catch (error) { + setUpdating(false) + } + } + const isOutdated = useCallback(() => { + const dateChartService = new DateChartService() + + return dateChartService.isDataOutdated(fluidStatus.lastDataDate, fluidType) + }, [fluidStatus, fluidType]) + + const hasUpdatedToday = useCallback(() => { + const todayDate = DateTime.local() + .setZone('utc', { + keepLocalTime: true, + }) + .minus({ days: 1 }) + .toLocaleString() + if (lastExecutionDate === '-') { + return false + } else if (lastExecutionDate > todayDate) { + return false + } else { + return true + } + }, [lastExecutionDate]) + + useEffect(() => { + if ( + fluidStatus.connection.triggerState && + fluidStatus.connection.triggerState.last_success + ) { + const result = DateTime.fromISO( + fluidStatus.connection.triggerState.last_success + ) + setLastExecutionDate(result) + } else { + setLastExecutionDate('-') + } + if ( + fluidStatus.connection.triggerState && + fluidStatus.connection.triggerState.status === 'errored' && + fluidStatus.connection.triggerState.last_error + ) { + setStatus('errored') + setKonnectorError( + getKonnectorUpdateError(fluidStatus.connection.triggerState.last_error) + ) + } + + if (isOutdated()) { + setOutDatedDataDays(isOutdated()) + } + }, [fluidStatus.connection.triggerState, isOutdated]) + + return ( + <div className="connection-update-result"> + <div + className={ + status === 'errored' && + !hasUpdatedToday() && + fluidStatus.status !== FluidState.PARTNER_ISSUE + ? 'connection-update-errored' + : '' + } + > + {outDatedDataDays || fluidStatus.status === FluidState.PARTNER_ISSUE ? ( + <div className="connection-caption text-16-normal"> + <div className="text-16-normal"> + {fluidStatus.status === FluidState.PARTNER_ISSUE ? ( + <div className="connection-caption"> + {t('konnector_form.wait_end_issue')} + </div> + ) : ( + <> + {hasUpdatedToday() === true ? ( + <> + <div className="connection-caption"> + {t('konnector_form.label_updated_at')} + </div> + <div className="text-16-bold"> + {lastExecutionDate.toLocaleString()} + </div> + <div> + {fluidStatus && + fluidStatus.connection && + fluidStatus.connection.konnector && + t('konnector_form.issue') + + ' ' + + fluidStatus.connection.konnector.name + + '.'} + </div> + </> + ) : ( + <div className="connection-caption-errored connection-update-errored warning-white text-16-normal"> + <StyledIcon + icon={warningWhite} + size={36} + className="warning-icon" + role="img" + title="Attention" + ariaHidden={false} + /> + <div className="text-16-normal"> + {t('konnector_form.resolve')} + </div> + </div> + )} + </> + )} + </div> + </div> + ) : status === 'errored' ? ( + <div className="connection-caption-errored warning-white text-16-normal"> + <StyledIcon + icon={warningWhite} + size={36} + className="warning-icon" + role="img" + title="Attention" + ariaHidden={false} + /> + + <div className="text-16-normal"> + {t(`konnector_form.${konnectorError}`)} + <div className="connection-caption"> + {t('konnector_form.label_updated_at')} + </div> + <div className="text-16-bold"> + {lastExecutionDate.toLocaleString()} + </div> + </div> + </div> + ) : ( + <div> + <div className="connection-caption text-16-normal"> + {t('konnector_form.label_updated_at')} + </div> + <div className="text-16-bold"> + {lastExecutionDate.toLocaleString()} + </div> + </div> + )} + </div> + <div className="inline-buttons"> + <Button + aria-label={t('konnector_form.accessibility.button_delete')} + onClick={deleteAccount} + disabled={updating} + classes={{ + root: 'btn-secondary-positive', + label: 'text-16-normal', + }} + > + {t('konnector_form.button_delete')} + </Button> + <Button + aria-label={t('konnector_form.accessibility.button_update')} + onClick={updateKonnector} + disabled={updating} + classes={{ + root: 'btn-highlight', + label: 'text-16-bold', + }} + > + {updating ? ( + <StyledBlackSpinner size="2em" /> + ) : ( + <div>{t('konnector_form.button_update')}</div> + )} + </Button> + </div> + </div> + ) +} + +export default ConnectionResult diff --git a/src/components/Connection/FormLogin.tsx b/src/components/Connection/FormLogin.tsx index 272a33aef70e8c9f792a7ec45572310b4fca8dd2..e64ee7b7d831cbfbe1456bb39a979afd208997d1 100644 --- a/src/components/Connection/FormLogin.tsx +++ b/src/components/Connection/FormLogin.tsx @@ -1,20 +1,22 @@ import React, { useState, useEffect } from 'react' import { useI18n } from 'cozy-ui/transpiled/react/I18n' +import { useClient } from 'cozy-client' +import { useDispatch } from 'react-redux' + +import { Account, FluidStatus, FluidConnection, UsageEvent } from 'models' +import AccountService from 'services/account.service' +import ConnectionService from 'services/connection.service' +import UsageEventService from 'services/usageEvent.service' + import './formLogin.scss' -import { Account, FluidConnection, FluidStatus } from 'models' import StyledIconButton from 'components/CommonKit/IconButton/StyledIconButton' import Button from '@material-ui/core/Button' import TrailingIcon from 'assets/icons/ico/trailing-icon.svg' import StyledIcon from 'components/CommonKit/Icon/StyledIcon' import { FluidType } from 'enum/fluid.enum' import { getPartnerPicto } from 'utils/picto' -import ConnectionService from 'services/connection.service' -import { useClient } from 'cozy-client' -import { useDispatch } from 'react-redux' -import UsageEventService from 'services/usageEvent.service' import { UsageEventType } from 'enum/usageEvent.enum' import { updatedFluidConnection } from 'store/global/global.actions' -import AccountService from 'services/account.service' interface FormLoginProps { fluidStatus: FluidStatus @@ -67,6 +69,16 @@ const FormLogin: React.FC<FormLoginProps> = ({ } } + const sendUsageEventError = async ( + konnectorSlug: string + ): Promise<UsageEvent> => { + return UsageEventService.addEvent(client, { + type: UsageEventType.KONNECTOR_CONNECT_EVENT, + target: konnectorSlug, + result: 'error', + }) + } + const connect = async () => { const connectionService = new ConnectionService(client) try { @@ -76,6 +88,7 @@ const FormLogin: React.FC<FormLoginProps> = ({ } = await connectionService.connectNewUser(konnectorSlug, login, password) if (!_trigger) { setError(t('konnector_form.error_account_creation')) + sendUsageEventError(konnectorSlug) return null } const updatedConnection: FluidConnection = { @@ -84,20 +97,12 @@ const FormLogin: React.FC<FormLoginProps> = ({ trigger: _trigger, } setLoading(false) - await UsageEventService.addEvent(client, { - type: UsageEventType.KONNECTOR_CONNECT_EVENT, - target: konnectorSlug, - result: 'success', - }) + // await sendUsageEventSuccess(konnectorSlug) dispatch(updatedFluidConnection(fluidStatus.fluidType, updatedConnection)) handleSuccess() } catch (err) { setLoading(false) - UsageEventService.addEvent(client, { - type: UsageEventType.KONNECTOR_CONNECT_EVENT, - target: konnectorSlug, - result: 'error', - }) + sendUsageEventError(konnectorSlug) console.log(err) } } diff --git a/src/components/Konnector/KonnectorViewerCard.tsx b/src/components/Konnector/KonnectorViewerCard.tsx index a06e52bebf9d4c207a9d7afb456d3152581a76e6..0aaa25927531e8977a95c34af849879ecad376d9 100644 --- a/src/components/Konnector/KonnectorViewerCard.tsx +++ b/src/components/Konnector/KonnectorViewerCard.tsx @@ -15,6 +15,7 @@ import { Trigger, FluidStatus, FluidConnection, + UsageEvent, } from 'models' import { getAddPicto, getParamPicto } from 'utils/picto' import { setChallengeConsumption } from 'store/challenge/challenge.actions' @@ -51,6 +52,8 @@ import { DateTime } from 'luxon' import { setSelectedDate } from 'store/chart/chart.actions' import DateChartService from 'services/dateChart.service' import StyledIcon from 'components/CommonKit/Icon/StyledIcon' +import UsageEventService from 'services/usageEvent.service' +import { UsageEventType } from 'enum/usageEvent.enum' import PartnersInfoService from 'services/partnersInfo.service' import { PartnersInfo } from 'models/partnersInfo.model' @@ -219,6 +222,32 @@ const KonnectorViewerCard: React.FC<KonnectorViewerCardProps> = ({ updatedFluidStatus.length, ]) + const sendUsageEventSuccess = async ( + konnectorSlug: string, + isConnexion: boolean + ): Promise<UsageEvent> => { + return UsageEventService.addEvent(client, { + type: isConnexion + ? UsageEventType.KONNECTOR_CONNECT_EVENT + : UsageEventType.KONNECTOR_REFRESH_EVENT, + target: konnectorSlug, + result: 'success', + }) + } + + const sendUsageEventError = async ( + konnectorSlug: string, + isConnexion: boolean + ): Promise<UsageEvent> => { + return UsageEventService.addEvent(client, { + type: isConnexion + ? UsageEventType.KONNECTOR_CONNECT_EVENT + : UsageEventType.KONNECTOR_REFRESH_EVENT, + target: konnectorSlug, + result: 'error', + }) + } + const getConnectionCard = useCallback(() => { if (fluidState === FluidState.KONNECTOR_NOT_FOUND && !isUpdating) { return <ConnectionNotFound konnectorSlug={fluidSlug} /> @@ -279,14 +308,23 @@ const KonnectorViewerCard: React.FC<KonnectorViewerCardProps> = ({ const connectionFlow = new ConnectionFlow(client, trigger, konnector) await connectionFlow.launch() connectionFlow.jobWatcher.on(ERROR_EVENT, () => { + if (subscribed) { + sendUsageEventError(fluidSlug, fluidStatus.lastDataDate === null) + } 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) + } callbackResponse(LOGIN_SUCCESS_EVENT) }) connectionFlow.jobWatcher.on(SUCCESS_EVENT, () => { + if (subscribed) { + sendUsageEventSuccess(fluidSlug, fluidStatus.lastDataDate === null) + } callbackResponse(SUCCESS_EVENT) }) diff --git a/src/enum/dacc.enum.ts b/src/enum/dacc.enum.ts index ed189b78cedb7066e9efd405f1f70a87cd61d414..219da93b8a757b095ff6d7443705951cda4a6a6b 100644 --- a/src/enum/dacc.enum.ts +++ b/src/enum/dacc.enum.ts @@ -11,4 +11,7 @@ export enum DaccEvent { QUIZ_STARS = 'quiz-stars', SUMMARY_SUBSCRIPTION_MONTHLY = 'summary-subscription-monthly', FLUID_DATA_GRANULARITY = 'fluid-data-granularity-monthly', + PARTNER_SUCESS_MONTHLY = 'konnector-attempts-before-success', + CONNECTION_COUNT_MONTHLY = 'connection-count-monthly', + PROFILE_COUNT_MONTHLY = 'profile-count', } diff --git a/src/enum/fluidSlug.enum.ts b/src/enum/fluidSlug.enum.ts new file mode 100644 index 0000000000000000000000000000000000000000..33db5dcd220a3fba1853921169d00c72e481b614 --- /dev/null +++ b/src/enum/fluidSlug.enum.ts @@ -0,0 +1,5 @@ +export enum FluidSlugType { + ELECTRICITY = 'enedisgrandlyon', + WATER = 'eglgrandlyon', + GAS = 'grdfgrandlyon', +} diff --git a/src/services/usageEvent.service.ts b/src/services/usageEvent.service.ts index e08967ec0e98cc3e8c214d097c255f7502ea7596..0d5cb79a98e47517b94b902790c3d4c5dccce278 100644 --- a/src/services/usageEvent.service.ts +++ b/src/services/usageEvent.service.ts @@ -91,11 +91,12 @@ export default class UsageEventService { */ static async getEvents( client: Client, - filterParams: MongoSelector + filterParams: MongoSelector, + desc = false ): Promise<UsageEvent[]> { const query: QueryDefinition = Q(USAGEEVENT_DOCTYPE) .where(filterParams) - .sortBy([{ eventDate: 'asc' }]) + .sortBy([{ eventDate: desc ? 'desc' : 'asc' }]) const { data: usageEventEntities, }: QueryResult<UsageEventEntity[]> = await client.query(query) diff --git a/src/targets/services/aggregatorUsageEvents.ts b/src/targets/services/aggregatorUsageEvents.ts index 2406bc765963bf7a233039850185ace4e4e6e256..3760f051fcc1004e0744d83ca377f2f9796856ad 100644 --- a/src/targets/services/aggregatorUsageEvents.ts +++ b/src/targets/services/aggregatorUsageEvents.ts @@ -20,6 +20,7 @@ import { UserChallengeState } from 'enum/userChallenge.enum' import ProfileTypeEntityService from 'services/profileTypeEntity.service' import TermsService from 'services/terms.service' import { WarmingType } from 'enum/profileType.enum' +import { FluidSlugType } from 'enum/fluidSlug.enum' const log = logger.namespace('aggregatorUsageEvents') @@ -581,6 +582,74 @@ const calculateConsumptionVariation = async (client: Client) => { } } +const sendConnectionCount = async (client: Client) => { + log('info', `sendConnectionCount`) + // Get daily connexion + const events: UsageEvent[] = await UsageEventService.getEvents(client, { + type: UsageEventType.CONNECTION_EVENT, + eventDate: { + $lt: DateTime.local() + .setZone('utc', { + keepLocalTime: true, + }) + .endOf('month') + .minus({ month: 1 }) + .toString(), + $gt: DateTime.local() + .setZone('utc', { + keepLocalTime: true, + }) + .startOf('month') + .minus({ month: 1 }) + .toString(), + }, + }) + + // If there is at least one connection, filter each unique connection in order to send it + if (events.length > 0) { + const uniqueDates = events + .map(s => s.eventDate.day) + .filter((s, i, a) => a.indexOf(s) == i) + + const connectionMonthly: Indicator = { + createdBy: 'ecolyo', + measureName: DaccEvent.CONNECTION_COUNT_MONTHLY, + startDate: DateTime.local() + .setZone('utc', { + keepLocalTime: true, + }) + .startOf('day') + .toISODate(), + value: uniqueDates.length, + } + await sendIndicator(connectionMonthly, client) + } +} + +const sendProfileCount = async (client: Client) => { + log('info', `sendProfileCount`) + // Get profile setEvents + const events: UsageEvent[] = await UsageEventService.getEvents(client, { + type: UsageEventType.PROFILE_SET_EVENT, + }) + + // If there is at least one connection, filter each unique connection in order to send it + if (events.length > 0) { + const profileSet: Indicator = { + createdBy: 'ecolyo', + measureName: DaccEvent.PROFILE_COUNT_MONTHLY, + startDate: DateTime.local() + .setZone('utc', { + keepLocalTime: true, + }) + .startOf('day') + .toISODate(), + value: events.length, + } + await sendIndicator(profileSet, client) + } +} + const sendEmailSubscription = async (client: Client) => { log('info', `sendEmailSubscription`) const profile = await new ProfileService(client).getProfile() @@ -635,6 +704,107 @@ const sendHalfHourConsumption = async (client: Client) => { await sendIndicator(halfHourConsumption, client) } +/** + * Send indicator to dacc, each month send if user has succed to configure a connector with the number of try. + * @param client CozyClient + */ +const sendKonnectorEvents = async (client: Client) => { + log('info', `sendKonnectorEvents`) + const slugs = Object.values(FluidSlugType) + const today = DateTime.local().setZone('utc', { + keepLocalTime: true, + }) + slugs.forEach(async slug => { + let successEvents: UsageEvent[] = await UsageEventService.getEvents( + client, + { + type: UsageEventType.KONNECTOR_CONNECT_EVENT, + target: slug, + result: 'success', + eventDate: { + $lte: today + .endOf('month') + .minus({ month: 1 }) + .toString(), + $gte: today + .startOf('month') + .minus({ month: 1 }) + .toString(), + }, + }, + true + ) + + // If there is no success in month, send nothing + if (successEvents && successEvents.length > 0) { + // Get all success events + successEvents = await UsageEventService.getEvents( + client, + { + type: UsageEventType.KONNECTOR_CONNECT_EVENT, + target: slug, + result: 'success', + }, + true + ) + // Remove success from other month, they should have been already proceced + // successEvents.length = successEventsOfCurrentMonth + + for (let index = 0; index < successEvents.length; index++) { + const successEvent = successEvents[index] + let query = null + + // If there is a previous value take it as reference for the query + // Else get all previous because it's the first one + if (index + 1 < successEvents.length) { + query = { + type: UsageEventType.KONNECTOR_CONNECT_EVENT, + target: slug, + result: 'error', + eventDate: { + $lte: successEvent.eventDate, + $gte: successEvents[index + 1].eventDate, + }, + } + } else { + query = { + type: UsageEventType.KONNECTOR_CONNECT_EVENT, + target: slug, + result: 'error', + eventDate: { + $lte: successEvent.eventDate, + }, + } + } + + const allConnectionEvents: UsageEvent[] = await UsageEventService.getEvents( + client, + query, + true + ) + + const konnectorSuccess: Indicator = { + createdBy: 'ecolyo', + measureName: DaccEvent.PARTNER_SUCESS_MONTHLY, + // eslint-disable-next-line @typescript-eslint/camelcase + group1: { fluid_type: slug }, + startDate: DateTime.local() + .setZone('utc', { + keepLocalTime: true, + }) + .startOf('day') + .toISODate(), + value: allConnectionEvents.length + 1, //+1 in order to count the success + } + // Send indicator if it's in current month + if (successEvent.eventDate.month === today.minus({ month: 1 }).month) { + await sendIndicator(konnectorSuccess, client) + } + } + } + }) +} + const aggregateEvents = async ( events: UsageEvent[], eventType: UsageEventType, @@ -1001,6 +1171,9 @@ const AggregatorUsageEvents = async ({ calculateConsumptionVariation(client) sendEmailSubscription(client) sendHalfHourConsumption(client) + sendKonnectorEvents(client) + sendConnectionCount(client) + sendProfileCount(client) } const uniqueReadUsageEvents: UsageEvent[] = uniq(readUsageEvents) log(