diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c6feeeb2eaa9ddae5ea272ceba544ee26f5fd793..f5dfb2a8b8d9b60e65e24db659cab25700af728b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -11,6 +11,7 @@ stages: - quality - test - build + - push-build - deploy - publish @@ -42,7 +43,7 @@ build_stack: - master - dev when: manual - + sonarqube-mr: stage: quality only: @@ -111,7 +112,7 @@ test: - master - merge_requests -build-mr: +build: stage: build before_script: - apk add git @@ -120,19 +121,21 @@ build-mr: - yarn - yarn build only: + - dev + - master - merge_requests artifacts: paths: - - build + - build/ -build-for-testing: - stage: build +br_build_test: + stage: push-build before_script: - apk add git - apk add bash script: - yarn - - yarn build-dev + - ls build - git config --global user.name build-pipeline - git config --global user.email "$GIT_USER" - git config --global user.password "$GIT_PWD" @@ -142,17 +145,17 @@ build-for-testing: - yarn deploy-test only: - merge_requests + needs: + - build when: manual - -build-dev-branch: - stage: build +br_build_dev: + stage: push-build before_script: - apk add git - apk add bash script: - yarn - - yarn build-dev - git config --global user.name build-pipeline - git config --global user.email "$GIT_USER" - git config --global user.password "$GIT_PWD" @@ -162,15 +165,16 @@ build-dev-branch: - yarn deploy-dev only: - dev + needs: + - build -build: - stage: build +br_build: + stage: push-build before_script: - apk add git - apk add bash script: - yarn - - yarn build - git config --global user.name build-pipeline - git config --global user.email "$GIT_USER" - git config --global user.password "$GIT_PWD" @@ -180,24 +184,26 @@ build: - yarn deploy only: - master + needs: + - build -deploy-to-dev: +deploy_test: stage: deploy tags: - deploy-alpha script: - cd /root/ecolyo-infra-scripts/cicid_scripts - './update_ecolyo_dev.sh' - dependencies: - - build-for-testing only: - merge_requests environment: name: dev url: https://ecolyo.dev.cozy.self-data.alpha.grandlyon.com/ when: manual + needs: + - br_build_test -deploy-to-ecolyodemo: +deploy_demo: stage: deploy tags: - deploy-alpha @@ -209,8 +215,10 @@ deploy-to-ecolyodemo: environment: name: ecolyodemo url: https://ecolyo.ecolyodemo.cozy.self-data.alpha.grandlyon.com/ + needs: + - br_build_dev -deploy-to-all-instances: +deploy_all: stage: deploy tags: - deploy-alpha @@ -218,7 +226,9 @@ deploy-to-all-instances: - cd /root/ecolyo-infra-scripts/cicid_scripts - './update_all_ecolyo_dev.sh' only: - - tags + - master + needs: + - br_build publish: stage: publish @@ -228,4 +238,4 @@ publish: - yarn cozyPublish only: - tags - when: manual \ No newline at end of file + when: manual diff --git a/.vscode/settings.json b/.vscode/settings.json index 060adef4027ac3903b1d57c1580258f67b87706b..76eee681f17ebde069be77176d18df9bf3727003 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -83,6 +83,7 @@ "Matomo", "MEGAUNIT", "monthlyanalysis", + "multifluid", "MULTIFLUID", "mutlifluid", "Picto", diff --git a/src/components/Connection/ConnectionResult.tsx b/src/components/Connection/ConnectionResult.tsx index 755e9bf6fadcfb431bbb21466ab5dd139ff1e37e..9b84097baf41dae29cf645dd15d3da9f65cec260 100644 --- a/src/components/Connection/ConnectionResult.tsx +++ b/src/components/Connection/ConnectionResult.tsx @@ -4,7 +4,7 @@ import StyledIcon from 'components/CommonKit/Icon/StyledIcon' import Loader from 'components/Loader/Loader' import { useClient } from 'cozy-client' import { useI18n } from 'cozy-ui/transpiled/react/I18n' -import { FluidState, FluidType } from 'enum/fluid.enum' +import { FluidType } from 'enum/fluid.enum' import { DateTime } from 'luxon' import { Account, @@ -170,14 +170,12 @@ const ConnectionResult: React.FC<ConnectionResultProps> = ({ <div className="connection-update-result"> <div className={ - status === 'errored' && - !hasUpdatedToday() && - fluidStatus.status !== FluidState.PARTNER_ISSUE + status === 'errored' && !hasUpdatedToday() && !fluidStatus.maintenance ? 'connection-update-errored' : '' } > - {fluidStatus.status === FluidState.PARTNER_ISSUE ? ( + {fluidStatus.maintenance ? ( // First check if there is partner error from backoffice <div className="connection-caption text-16-normal"> <div className="text-16-normal"> diff --git a/src/components/CustomPopup/CustomPopupModal.tsx b/src/components/CustomPopup/CustomPopupModal.tsx index 8e1d402d506314ea7fb28d9dae128e4fd7431e12..030ed9371a446b6451fc4c2726a6321e73b0db37 100644 --- a/src/components/CustomPopup/CustomPopupModal.tsx +++ b/src/components/CustomPopup/CustomPopupModal.tsx @@ -64,7 +64,7 @@ const CustomPopupModal: React.FC<CustomPopupModalProps> = ({ label: 'text-16-bold', }} > - {t('consumption.partners_issue_modal.ok')} + {t('consumption.partner_issue_modal.ok')} </Button> </div> </Dialog> diff --git a/src/components/CustomPopup/__snapshots__/CustomPopupModal.spec.tsx.snap b/src/components/CustomPopup/__snapshots__/CustomPopupModal.spec.tsx.snap index bb492567614e5d308210feb46a05d3790acd9f93..763abb2f2d90f65a61f92aa5db07d8850cae1a1f 100644 --- a/src/components/CustomPopup/__snapshots__/CustomPopupModal.spec.tsx.snap +++ b/src/components/CustomPopup/__snapshots__/CustomPopupModal.spec.tsx.snap @@ -476,7 +476,7 @@ exports[`CustomPopupModal component should render correctly 1`] = ` <span class="MuiButton-label text-16-bold" > - consumption.partners_issue_modal.ok + consumption.partner_issue_modal.ok </span> <span class="MuiTouchRipple-root" @@ -962,7 +962,7 @@ exports[`CustomPopupModal component should render correctly 1`] = ` <span className="MuiButton-label text-16-bold" > - consumption.partners_issue_modal.ok + consumption.partner_issue_modal.ok </span> <WithStyles(memo) center={false} diff --git a/src/components/Home/ConsumptionView.spec.tsx b/src/components/Home/ConsumptionView.spec.tsx index 56a2cfbd578552c56d99802dca7eac5c14b8b0c7..ad546465a3ec149c2e0bce52ae1376807c510366 100644 --- a/src/components/Home/ConsumptionView.spec.tsx +++ b/src/components/Home/ConsumptionView.spec.tsx @@ -48,8 +48,8 @@ jest.mock( () => 'mock-konnectorviewercard' ) jest.mock( - 'components/PartnersIssue/PartnersIssueModal', - () => 'mock-partnersissuemodal' + 'components/PartnerIssue/PartnerIssueModal', + () => 'mock-partnerissuemodal' ) jest.mock('components/CustomPopup/CustomPopupModal', () => 'mock-custompopup') jest.mock('components/Home/ReleaseNotesModal', () => 'mock-releasenotes') @@ -62,6 +62,12 @@ const useSelectorSpy = jest.spyOn(reactRedux, 'useSelector') const useDispatchSpy = jest.spyOn(reactRedux, 'useDispatch') const setCurrentTimeStepSpy = jest.spyOn(chartActions, 'setCurrentTimeStep') +const mockedPartnersIssueModal = { + enedis: false, + egl: false, + grdf: false, +} + describe('ConsumptionView component', () => { let store: MockStoreEnhanced< { @@ -215,6 +221,8 @@ describe('ConsumptionView component', () => { expect(wrapper.find('.consumptionview-content').exists()).toBeTruthy() expect(wrapper.find('mock-consumptiondetails').exists()).toBeTruthy() }) + + // todo describe and add multiple fluids ? it('should render partner issue Modal', async () => { const updatedStatus: FluidStatus[] = mockInitialEcolyoState.global.fluidStatus @@ -227,6 +235,7 @@ describe('ConsumptionView component', () => { global: { fluidStatus: updatedStatus, releaseNotes: mockInitialEcolyoState.global.releaseNotes, + openPartnersIssueModal: { ...mockedPartnersIssueModal, egl: true }, }, }) useDispatchSpy.mockReturnValue(jest.fn()) @@ -236,7 +245,7 @@ describe('ConsumptionView component', () => { <ConsumptionView fluidType={FluidType.ELECTRICITY} /> </Provider> ) - expect(wrapper.find('mock-partnersissuemodal').exists()).toBeTruthy() + expect(wrapper.find('mock-partnerissuemodal').exists()).toBeTruthy() }) it('should show expired modal when a GRDF consent is expired', () => { const updatedStatus: FluidStatus[] = @@ -273,6 +282,7 @@ describe('ConsumptionView component', () => { global: { fluidStatus: updatedStatus, releaseNotes: mockInitialEcolyoState.global.releaseNotes, + openPartnersIssueModal: mockedPartnersIssueModal, }, }) useDispatchSpy.mockReturnValue(jest.fn()) @@ -296,6 +306,7 @@ describe('ConsumptionView component', () => { global: { fluidStatus: updatedStatus, releaseNotes: mockInitialEcolyoState.global.releaseNotes, + openPartnersIssueModal: mockedPartnersIssueModal, }, }) useDispatchSpy.mockReturnValue(jest.fn()) @@ -318,11 +329,13 @@ describe('ConsumptionView component', () => { }, global: { fluidStatus: updatedStatus, + openPartnersIssueModal: mockedPartnersIssueModal, releaseNotes: { show: true, notes: [{ description: 'description', title: 'title' }], }, }, + openPartnersIssueModal: mockedPartnersIssueModal, }) useDispatchSpy.mockReturnValue(jest.fn()) mockUpdateProfile.mockResolvedValue(mockTestProfile1) @@ -346,6 +359,7 @@ describe('ConsumptionView component', () => { sgeConnect: { openSGEForm: true, }, + openPartnersIssueModal: mockedPartnersIssueModal, }, }) useDispatchSpy.mockReturnValue(jest.fn()) diff --git a/src/components/Home/ConsumptionView.tsx b/src/components/Home/ConsumptionView.tsx index 08b5ec0dbf8b32f8c785fdffef497c5282f88f28..a511cd05eb9250cacde7c58d5c1bd9ce427e418b 100644 --- a/src/components/Home/ConsumptionView.tsx +++ b/src/components/Home/ConsumptionView.tsx @@ -12,7 +12,7 @@ import FluidButtons from 'components/Home/FluidButtons' import KonnectorViewerCard from 'components/Konnector/KonnectorViewerCard' import KonnectorViewerList from 'components/Konnector/KonnectorViewerList' import Loader from 'components/Loader/Loader' -import PartnersIssueModal from 'components/PartnersIssue/PartnersIssueModal' +import PartnerIssueModal from 'components/PartnerIssue/PartnerIssueModal' import { useClient } from 'cozy-client' import { FluidType } from 'enum/fluid.enum' import { TimeStep } from 'enum/timeStep.enum' @@ -34,6 +34,7 @@ import { } from 'utils/utils' import './consumptionView.scss' import ReleaseNotesModal from './ReleaseNotesModal' + interface ConsumptionViewProps { fluidType: FluidType } @@ -62,13 +63,13 @@ const ConsumptionView: React.FC<ConsumptionViewProps> = ({ const [headerHeight, setHeaderHeight] = useState<number>(0) const [active, setActive] = useState<boolean>(false) - const [openExpiredConsentModal, setopenExpiredConsentModal] = + const [openExpiredConsentModal, setOpenExpiredConsentModal] = useState<boolean>(true) - const [consentExpiredFluids, setconsentExpiredFluids] = useState<FluidType[]>( + const [consentExpiredFluids, setConsentExpiredFluids] = useState<FluidType[]>( [] ) - const updatekey = + const updateKey = fluidType !== FluidType.MULTIFLUID && fluidStatus[fluidType].lastDataDate ? `${fluidStatus[fluidType].lastDataDate!.toLocaleString()} + ${ fluidStatus[fluidType].status + fluidType @@ -93,15 +94,42 @@ const ConsumptionView: React.FC<ConsumptionViewProps> = ({ } }, [dispatch, history, releaseNotes.notes, releaseNotes.redirectLink]) - const handleCloseModal = useCallback(async () => { - const profileService = new ProfileService(client) - const updatedProfile = await profileService.updateProfile({ - partnersIssueDate: getTodayDate(), - }) - if (updatedProfile) { - dispatch(setPartnersIssue(false)) + const getPartnerKey = (fluidType: FluidType): 'enedis' | 'egl' | 'grdf' => { + switch (fluidType) { + case FluidType.ELECTRICITY: + return 'enedis' + case FluidType.WATER: + return 'egl' + case FluidType.GAS: + return 'grdf' + default: + throw new Error('unknown fluidtype') } - }, [client, dispatch]) + } + + const handleClosePartnerIssueModal = useCallback( + async (fluidType: FluidType) => { + const profileService = new ProfileService(client) + const profileValues = await profileService.getProfile() + if (profileValues) { + const updatedProfile = await profileService.updateProfile({ + partnersIssueSeenDate: { + ...profileValues.partnersIssueSeenDate, + [getPartnerKey(fluidType)]: getTodayDate(), + }, + }) + if (updatedProfile) { + dispatch( + setPartnersIssue({ + ...openPartnersIssueModal, + [getPartnerKey(fluidType)]: false, + }) + ) + } + } + }, + [client, dispatch, openPartnersIssueModal] + ) const handleCloseCustomPopupModal = async () => { const profileService = new ProfileService(client) @@ -139,7 +167,7 @@ const ConsumptionView: React.FC<ConsumptionViewProps> = ({ } } - if (subscribed) setconsentExpiredFluids(expiredConsents) + if (subscribed) setConsentExpiredFluids(expiredConsents) return () => { subscribed = false } @@ -152,7 +180,7 @@ const ConsumptionView: React.FC<ConsumptionViewProps> = ({ <DateNavigator /> </Header> <Content height={headerHeight}> - <FluidButtons activeFluid={fluidType} key={updatekey} /> + <FluidButtons activeFluid={fluidType} key={updateKey} /> {openReleaseNoteModal && ( <ReleaseNotesModal @@ -211,12 +239,18 @@ const ConsumptionView: React.FC<ConsumptionViewProps> = ({ )} </Content> {sgeConnect?.openSGEForm && <SgeConnectView />} - - <PartnersIssueModal - open={openPartnersIssueModal} - fluidStatus={fluidStatus} - handleCloseClick={handleCloseModal} - /> + {/* Partner issue modals for individual fluids */} + {fluidStatus + .filter(fluid => fluid.maintenance) + .filter(fluid => fluid.fluidType === fluidType) + .map(issuedFluid => ( + <PartnerIssueModal + key={issuedFluid.fluidType} + issuedFluid={issuedFluid} + open={openPartnersIssueModal[getPartnerKey(issuedFluid.fluidType)]} + handleCloseClick={handleClosePartnerIssueModal} + /> + ))} <CustomPopupModal customPopup={customPopupModal} handleCloseClick={handleCloseCustomPopupModal} @@ -227,9 +261,9 @@ const ConsumptionView: React.FC<ConsumptionViewProps> = ({ <ExpiredConsentModal key={fluid} open={openExpiredConsentModal} - handleCloseClick={() => setopenExpiredConsentModal(false)} + handleCloseClick={() => setOpenExpiredConsentModal(false)} fluidType={fluid} - toggleModal={() => setopenExpiredConsentModal(prev => !prev)} + toggleModal={() => setOpenExpiredConsentModal(prev => !prev)} /> ) })} diff --git a/src/components/Home/FluidButton.tsx b/src/components/Home/FluidButton.tsx index bd1cc74ad471cf7713147a85c80d764316cc3744..73aee6c00d8ee731f892d15d43770d7a24991ed8 100644 --- a/src/components/Home/FluidButton.tsx +++ b/src/components/Home/FluidButton.tsx @@ -1,19 +1,18 @@ -import React, { useCallback, useEffect, useState } from 'react' +import ErrorNotif from 'assets/icons/ico/notif_error.svg' +import PartnerIssueNotif from 'assets/icons/ico/notif_maintenance.svg' +import StyledIcon from 'components/CommonKit/Icon/StyledIcon' +import { useClient } from 'cozy-client' import { useI18n } from 'cozy-ui/transpiled/react/I18n' - import { FluidState, FluidType } from 'enum/fluid.enum' -import { getNavPicto } from 'utils/picto' -import { useHistory } from 'react-router' -import StyledIcon from 'components/CommonKit/Icon/StyledIcon' import { UsageEventType } from 'enum/usageEvent.enum' -import UsageEventService from 'services/usageEvent.service' -import { useClient } from 'cozy-client' +import React, { useCallback, useEffect, useState } from 'react' import { useSelector } from 'react-redux' +import { useHistory } from 'react-router' +import DateChartService from 'services/dateChart.service' +import UsageEventService from 'services/usageEvent.service' import { AppStore } from 'store' +import { getNavPicto } from 'utils/picto' import { isKonnectorActive } from 'utils/utils' -import ErrorNotif from 'assets/icons/ico/notif_error.svg' -import PartnerIssueNotif from 'assets/icons/ico/notif_maintenance.svg' -import DateChartService from 'services/dateChart.service' interface FluidButtonProps { fluidType: FluidType @@ -71,12 +70,7 @@ const FluidButton: React.FC<FluidButtonProps> = ({ ) }, [history, fluidType, client]) - const serviceIsDown = () => { - return ( - fluidType !== FluidType.MULTIFLUID && - fluidStatus[fluidType]?.status === FluidState.PARTNER_ISSUE - ) - } + const isFluidMaintenance = () => fluidStatus[fluidType]?.maintenance useEffect(() => { //Show errors only on konnected konnectors that are in error, outdated, with no data (specific case), and not in multifluid @@ -102,7 +96,7 @@ const FluidButton: React.FC<FluidButtonProps> = ({ icon={iconType} size={fluidType === FluidType.MULTIFLUID ? 36 : 32} /> - {serviceIsDown() ? ( + {isFluidMaintenance() ? ( <StyledIcon icon={PartnerIssueNotif} size={22} diff --git a/src/components/Konnector/KonnectorViewerCard.tsx b/src/components/Konnector/KonnectorViewerCard.tsx index 91b264255c929628af449483c95d56afad76f9d9..0569d12be1539d66922f22a7ef69f0d007c27f78 100644 --- a/src/components/Konnector/KonnectorViewerCard.tsx +++ b/src/components/Konnector/KonnectorViewerCard.tsx @@ -1,34 +1,9 @@ -import { - Accordion, - AccordionDetails, - AccordionSummary, -} from '@material-ui/core' -import chevronDown from 'assets/icons/ico/chevron-down.svg' -import ErrorNotif from 'assets/icons/ico/notif_error.svg' -import PartnersIssueNotif from 'assets/icons/ico/notif_maintenance.svg' import classNames from 'classnames' -import StyledIcon from 'components/CommonKit/Icon/StyledIcon' -import Connection from 'components/Connection/Connection' -import ConnectionNotFound from 'components/Connection/ConnectionNotFound' -import ConnectionResult from 'components/Connection/ConnectionResult' -import KonnectorModal from 'components/Konnector/KonnectorModal' import { useClient } from 'cozy-client' -import { isKonnectorRunning } from 'cozy-harvest-lib/dist/helpers/triggers' -import ConnectionFlow from 'cozy-harvest-lib/dist/models/ConnectionFlow' -import { - ERROR_EVENT, - LOGIN_SUCCESS_EVENT, - SUCCESS_EVENT, -} from 'cozy-harvest-lib/dist/models/flowEvents' import { useI18n } from 'cozy-ui/transpiled/react/I18n' -import Icon from 'cozy-ui/transpiled/react/Icon' import { FluidState, FluidType } from 'enum/fluid.enum' -import { FluidSlugType } from 'enum/fluidSlug.enum' -import { KonnectorError } from 'enum/konnectorError.enum' -import { UsageEventType } from 'enum/usageEvent.enum' import { UserChallengeState } from 'enum/userChallenge.enum' import { UserDuelState } from 'enum/userDuel.enum' -import { DateTime } from 'luxon' import { Account, AccountAuthData, @@ -38,18 +13,13 @@ import { Trigger, UsageEvent, } from 'models' -import { PartnersInfo } from 'models/partnersInfo.model' import React, { useCallback, useEffect, useMemo, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' import AccountService from 'services/account.service' import ChallengeService from 'services/challenge.service' -import DateChartService from 'services/dateChart.service' import FluidService from 'services/fluid.service' -import PartnersInfoService from 'services/partnersInfo.service' -import UsageEventService from 'services/usageEvent.service' import { AppStore } from 'store' import { setChallengeConsumption } from 'store/challenge/challenge.actions' -import { setSelectedDate } from 'store/chart/chart.actions' import { setFluidStatus, toggleChallengeDuelNotification, @@ -59,6 +29,38 @@ import { import { getAddPicto, getParamPicto } from 'utils/picto' import './konnectorViewerCard.scss' +import { + Accordion, + AccordionDetails, + AccordionSummary, +} from '@material-ui/core' +import chevronDown from 'assets/icons/ico/chevron-down.svg' +import ErrorNotif from 'assets/icons/ico/notif_error.svg' +import PartnersIssueNotif from 'assets/icons/ico/notif_maintenance.svg' +import Connection from 'components/Connection/Connection' +import ConnectionNotFound from 'components/Connection/ConnectionNotFound' +import ConnectionResult from 'components/Connection/ConnectionResult' +import KonnectorModal from 'components/Konnector/KonnectorModal' +import Icon from 'cozy-ui/transpiled/react/Icon' + +import StyledIcon from 'components/CommonKit/Icon/StyledIcon' +import { isKonnectorRunning } from 'cozy-harvest-lib/dist/helpers/triggers' +import ConnectionFlow from 'cozy-harvest-lib/dist/models/ConnectionFlow' +import { + ERROR_EVENT, + LOGIN_SUCCESS_EVENT, + SUCCESS_EVENT, +} from 'cozy-harvest-lib/dist/models/flowEvents' +import { FluidSlugType } from 'enum/fluidSlug.enum' +import { KonnectorError } from 'enum/konnectorError.enum' +import { UsageEventType } from 'enum/usageEvent.enum' +import { DateTime } from 'luxon' +import { PartnersInfo } from 'models/partnersInfo.model' +import DateChartService from 'services/dateChart.service' +import PartnersInfoService from 'services/partnersInfo.service' +import UsageEventService from 'services/usageEvent.service' +import { setSelectedDate } from 'store/chart/chart.actions' + interface KonnectorViewerCardProps { fluidStatus: FluidStatus isParam: boolean @@ -351,7 +353,7 @@ const KonnectorViewerCard: React.FC<KonnectorViewerCardProps> = ({ icon={fluidStatus.connection.account ? iconType : iconAddType} size={49} /> - {fluidStatus.status === FluidState.PARTNER_ISSUE ? ( + {fluidStatus.maintenance ? ( <StyledIcon icon={PartnersIssueNotif} size={24} @@ -372,6 +374,7 @@ const KonnectorViewerCard: React.FC<KonnectorViewerCardProps> = ({ ) }, [ fluidStatus.connection.account, + fluidStatus.maintenance, fluidStatus.status, iconAddType, iconType, @@ -379,7 +382,7 @@ const KonnectorViewerCard: React.FC<KonnectorViewerCardProps> = ({ ]) const displayKonnectorHeader = useCallback(() => { - if (fluidStatus.status === FluidState.PARTNER_ISSUE) { + if (fluidStatus.maintenance) { return ( <span className="text-16-bold"> {t(`konnector_options.partner_issue`)} @@ -405,7 +408,7 @@ const KonnectorViewerCard: React.FC<KonnectorViewerCardProps> = ({ }, [ fluidStatus.connection.account, fluidStatus.fluidType, - fluidStatus.status, + fluidStatus.maintenance, isOutdatedData, t, ]) @@ -423,7 +426,7 @@ const KonnectorViewerCard: React.FC<KonnectorViewerCardProps> = ({ }, [account, client, handleAccountDeletion, shouldRefreshConsent]) /** Closes SGE form if opened */ - const closeSGEForm = () => { + const closeSGEForm = useCallback(() => { if (sgeConnect.openSGEForm) { dispatch( updateSgeStore({ @@ -435,7 +438,7 @@ const KonnectorViewerCard: React.FC<KonnectorViewerCardProps> = ({ }) ) } - } + }, [dispatch, sgeConnect]) useEffect(() => { let subscribed = true @@ -492,6 +495,7 @@ const KonnectorViewerCard: React.FC<KonnectorViewerCardProps> = ({ fluidSlug, sendUsageEventSuccess, shouldRefreshConsent, + closeSGEForm, ]) return ( @@ -502,7 +506,7 @@ const KonnectorViewerCard: React.FC<KonnectorViewerCardProps> = ({ onChange={toggleAccordion} classes={{ root: `expansion-panel-root ${ - fluidStatus.status !== FluidState.PARTNER_ISSUE && + !fluidStatus.maintenance && (fluidStatus.status === FluidState.ERROR || fluidStatus.status === FluidState.ERROR_LOGIN_FAILED || isOutdatedData) @@ -532,7 +536,7 @@ const KonnectorViewerCard: React.FC<KonnectorViewerCardProps> = ({ fluidStatus.fluidType ].toLowerCase()}-connected text-16-bold`]: fluidStatus.status !== FluidState.NOT_CONNECTED && - fluidStatus.status !== FluidState.PARTNER_ISSUE, + !fluidStatus.maintenance, })} > {displayKonnectorHeader()} diff --git a/src/components/PartnersIssue/PartnersIssueModal.spec.tsx b/src/components/PartnerIssue/PartnerIssueModal.spec.tsx similarity index 52% rename from src/components/PartnersIssue/PartnersIssueModal.spec.tsx rename to src/components/PartnerIssue/PartnerIssueModal.spec.tsx index 8d04d89bd6f5524332a6623d6d7c1f0ae6e9e02a..de6d946e2b07f301babef14a9df3194189e3ff11 100644 --- a/src/components/PartnersIssue/PartnersIssueModal.spec.tsx +++ b/src/components/PartnerIssue/PartnerIssueModal.spec.tsx @@ -1,11 +1,11 @@ -import React from 'react' +import { Button } from '@material-ui/core' import { mount } from 'enzyme' -import { Provider } from 'react-redux' import toJson from 'enzyme-to-json' +import React from 'react' +import { Provider } from 'react-redux' import configureStore from 'redux-mock-store' import { mockInitialEcolyoState } from '../../../tests/__mocks__/store' -import PartnersIssueModal from './PartnersIssueModal' -import { Button } from '@material-ui/core' +import PartnerIssueModal from './PartnerIssueModal' jest.mock('cozy-ui/transpiled/react/I18n', () => { return { @@ -19,29 +19,53 @@ jest.mock('cozy-ui/transpiled/react/I18n', () => { const mockStore = configureStore([]) const mockHandleClose = jest.fn() -describe('PartnersIssueModal component', () => { +describe('PartnerIssueModal component', () => { const store = mockStore({ ecolyo: mockInitialEcolyoState, }) it('should render correctly', () => { const wrapper = mount( <Provider store={store}> - <PartnersIssueModal + <PartnerIssueModal open={true} handleCloseClick={mockHandleClose} - fluidStatus={mockInitialEcolyoState.global.fluidStatus} + issuedFluid={mockInitialEcolyoState.global.fluidStatus[0]} /> </Provider> ) expect(toJson(wrapper)).toMatchSnapshot() }) + it('should render elec modal', () => { + const wrapper = mount( + <Provider store={store}> + <PartnerIssueModal + open={true} + handleCloseClick={mockHandleClose} + issuedFluid={mockInitialEcolyoState.global.fluidStatus[0]} + /> + </Provider> + ) + expect(wrapper.text().includes('error_connect_elec')).toBeTruthy() + }) + it('should render water modal', () => { + const wrapper = mount( + <Provider store={store}> + <PartnerIssueModal + open={true} + handleCloseClick={mockHandleClose} + issuedFluid={mockInitialEcolyoState.global.fluidStatus[1]} + /> + </Provider> + ) + expect(wrapper.text().includes('error_connect_water')).toBeTruthy() + }) it('should close modal', () => { const wrapper = mount( <Provider store={store}> - <PartnersIssueModal + <PartnerIssueModal open={true} handleCloseClick={mockHandleClose} - fluidStatus={mockInitialEcolyoState.global.fluidStatus} + issuedFluid={mockInitialEcolyoState.global.fluidStatus[0]} /> </Provider> ) @@ -52,13 +76,13 @@ describe('PartnersIssueModal component', () => { it('should not be rendered', () => { const wrapper = mount( <Provider store={store}> - <PartnersIssueModal + <PartnerIssueModal open={false} handleCloseClick={mockHandleClose} - fluidStatus={mockInitialEcolyoState.global.fluidStatus} + issuedFluid={mockInitialEcolyoState.global.fluidStatus[0]} /> </Provider> ) - expect(wrapper.find('div.partnersIssueModal').exists()).toBeFalsy() + expect(wrapper.find('div.partnerIssueModal').exists()).toBeFalsy() }) }) diff --git a/src/components/PartnerIssue/PartnerIssueModal.tsx b/src/components/PartnerIssue/PartnerIssueModal.tsx new file mode 100644 index 0000000000000000000000000000000000000000..3e449f713934ae6f63d3ad17c0a21f0f7a7abd60 --- /dev/null +++ b/src/components/PartnerIssue/PartnerIssueModal.tsx @@ -0,0 +1,98 @@ +import Button from '@material-ui/core/Button' +import Dialog from '@material-ui/core/Dialog' +import IconButton from '@material-ui/core/IconButton' +import CloseIcon from 'assets/icons/ico/close.svg' +import OrangeWarn from 'assets/icons/ico/warn-orange.svg' +import StyledIcon from 'components/CommonKit/Icon/StyledIcon' +import { useI18n } from 'cozy-ui/transpiled/react/I18n' +import Icon from 'cozy-ui/transpiled/react/Icon' +import { FluidType } from 'enum/fluid.enum' +import { FluidStatus } from 'models' +import React from 'react' +import './partnerIssueModal.scss' + +interface PartnerIssueModalProps { + open: boolean + issuedFluid: FluidStatus + handleCloseClick: (fluidType: FluidType) => void +} + +const PartnerIssueModal: React.FC<PartnerIssueModalProps> = ({ + open, + issuedFluid, + handleCloseClick, +}: PartnerIssueModalProps) => { + const { t } = useI18n() + + const getFluidTypeLabel = () => { + switch (issuedFluid.fluidType) { + case FluidType.ELECTRICITY: + return 'elec' + case FluidType.WATER: + return 'water' + case FluidType.GAS: + return 'gaz' + } + } + + return ( + <Dialog + open={open} + disableEscapeKeyDown + onClose={(event, reason): void => { + event && + reason !== 'backdropClick' && + handleCloseClick(issuedFluid.fluidType) + }} + aria-labelledby={'accessibility-title'} + classes={{ + root: 'modal-root', + paper: 'modal-paper', + }} + style={{ zIndex: 1500 }} + > + <div id={'accessibility-title'}> + {t('feedback.accessibility.window_title')} + </div> + <IconButton + aria-label={t('feedback.accessibility.button_close')} + className="modal-paper-close-button" + onClick={() => handleCloseClick(issuedFluid.fluidType)} + > + <Icon icon={CloseIcon} size={16} /> + </IconButton> + + <div className="partnerIssueModal"> + <StyledIcon icon={OrangeWarn} size={40} className={'warn-icon'} /> + <div className="partner-issue-title text-20-bold"> + {t('consumption.partner_issue_modal.title')} + </div> + <div + className="partner-issue-content text-16-normal" + dangerouslySetInnerHTML={{ + __html: t( + `consumption.partner_issue_modal.error_connect_${getFluidTypeLabel()}` + ), + }} + /> + <br /> + <div + dangerouslySetInnerHTML={{ + __html: t(`consumption.partner_issue_modal.additional_text`), + }} + /> + <Button + onClick={() => handleCloseClick(issuedFluid.fluidType)} + classes={{ + root: 'btn-highlight', + label: 'text-16-bold', + }} + > + {t('consumption.partner_issue_modal.ok')} + </Button> + </div> + </Dialog> + ) +} + +export default PartnerIssueModal diff --git a/src/components/PartnersIssue/__snapshots__/PartnersIssueModal.spec.tsx.snap b/src/components/PartnerIssue/__snapshots__/PartnerIssueModal.spec.tsx.snap similarity index 91% rename from src/components/PartnersIssue/__snapshots__/PartnersIssueModal.spec.tsx.snap rename to src/components/PartnerIssue/__snapshots__/PartnerIssueModal.spec.tsx.snap index 4da8c4e6889a12c8b55fffeb912ed50bac6fec2f..1b915b824f4f32b3047300b8500f8607388f185a 100644 --- a/src/components/PartnersIssue/__snapshots__/PartnersIssueModal.spec.tsx.snap +++ b/src/components/PartnerIssue/__snapshots__/PartnerIssueModal.spec.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`PartnersIssueModal component should render correctly 1`] = ` +exports[`PartnerIssueModal component should render correctly 1`] = ` <Provider store={ Object { @@ -13,75 +13,32 @@ exports[`PartnersIssueModal component should render correctly 1`] = ` } } > - <PartnersIssueModal - fluidStatus={ - Array [ - Object { - "connection": Object { - "account": null, - "isUpdating": false, - "konnector": null, - "konnectorConfig": Object { - "activation": "", - "name": "", - "oauth": false, - "siteLink": "", - "slug": "enedissgegrandlyon", - }, - "shouldLaunchKonnector": false, - "trigger": null, - "triggerState": null, - }, - "firstDataDate": null, - "fluidType": 0, - "lastDataDate": null, - "status": 0, - }, - Object { - "connection": Object { - "account": null, - "isUpdating": false, - "konnector": null, - "konnectorConfig": Object { - "activation": "", - "name": "", - "oauth": false, - "siteLink": "", - "slug": "eglgrandlyon", - }, - "shouldLaunchKonnector": false, - "trigger": null, - "triggerState": null, - }, - "firstDataDate": null, - "fluidType": 1, - "lastDataDate": null, - "status": 0, - }, - Object { - "connection": Object { - "account": null, - "isUpdating": false, - "konnector": null, - "konnectorConfig": Object { - "activation": "", - "name": "", - "oauth": false, - "siteLink": "", - "slug": "grdfgrandlyon", - }, - "shouldLaunchKonnector": false, - "trigger": null, - "triggerState": null, + <PartnerIssueModal + handleCloseClick={[MockFunction]} + issuedFluid={ + Object { + "connection": Object { + "account": null, + "isUpdating": false, + "konnector": null, + "konnectorConfig": Object { + "activation": "", + "name": "", + "oauth": false, + "siteLink": "", + "slug": "enedissgegrandlyon", }, - "firstDataDate": null, - "fluidType": 2, - "lastDataDate": null, - "status": 0, + "shouldLaunchKonnector": false, + "trigger": null, + "triggerState": null, }, - ] + "firstDataDate": null, + "fluidType": 0, + "lastDataDate": null, + "maintenance": false, + "status": 0, + } } - handleCloseClick={[MockFunction]} open={true} > <WithStyles(ForwardRef(Dialog)) @@ -528,7 +485,7 @@ exports[`PartnersIssueModal component should render correctly 1`] = ` /> </button> <div - class="partnersIssueModal" + class="partnerIssueModal" > <svg aria-hidden="true" @@ -541,30 +498,28 @@ exports[`PartnersIssueModal component should render correctly 1`] = ` /> </svg> <div - class="partners-issue-title text-20-bold" + class="partner-issue-title text-20-bold" > - consumption.partners_issue_modal.title + consumption.partner_issue_modal.title </div> <div - class="partners-issue-content text-16-normal" + class="partner-issue-content text-16-normal" > - consumption.partners_issue_modal.text_1 + consumption.partner_issue_modal.error_connect_elec </div> - <ul /> - <div - class="partners-issue-content text-16-normal" - > - consumption.partners_issue_modal.text_2 + <br /> + <div> + consumption.partner_issue_modal.additional_text </div> <button - class="MuiButtonBase-root MuiButton-root btn-highlight partnermodalclose MuiButton-text" + class="MuiButtonBase-root MuiButton-root btn-highlight MuiButton-text" tabindex="0" type="button" > <span class="MuiButton-label text-16-bold" > - consumption.partners_issue_modal.button_validate + consumption.partner_issue_modal.ok </span> <span class="MuiTouchRipple-root" @@ -786,7 +741,7 @@ exports[`PartnersIssueModal component should render correctly 1`] = ` <WithStyles(ForwardRef(IconButton)) aria-label="feedback.accessibility.button_close" className="modal-paper-close-button" - onClick={[MockFunction]} + onClick={[Function]} > <ForwardRef(IconButton) aria-label="feedback.accessibility.button_close" @@ -804,7 +759,7 @@ exports[`PartnersIssueModal component should render correctly 1`] = ` "sizeSmall": "MuiIconButton-sizeSmall", } } - onClick={[MockFunction]} + onClick={[Function]} > <WithStyles(ForwardRef(ButtonBase)) aria-label="feedback.accessibility.button_close" @@ -812,7 +767,7 @@ exports[`PartnersIssueModal component should render correctly 1`] = ` className="MuiIconButton-root modal-paper-close-button" disabled={false} focusRipple={true} - onClick={[MockFunction]} + onClick={[Function]} > <ForwardRef(ButtonBase) aria-label="feedback.accessibility.button_close" @@ -827,14 +782,14 @@ exports[`PartnersIssueModal component should render correctly 1`] = ` } disabled={false} focusRipple={true} - onClick={[MockFunction]} + onClick={[Function]} > <button aria-label="feedback.accessibility.button_close" className="MuiButtonBase-root MuiIconButton-root modal-paper-close-button" disabled={false} onBlur={[Function]} - onClick={[MockFunction]} + onClick={[Function]} onDragLeave={[Function]} onFocus={[Function]} onKeyDown={[Function]} @@ -909,7 +864,7 @@ exports[`PartnersIssueModal component should render correctly 1`] = ` </ForwardRef(IconButton)> </WithStyles(ForwardRef(IconButton))> <div - className="partnersIssueModal" + className="partnerIssueModal" > <StyledIcon className="warn-icon" @@ -945,29 +900,34 @@ exports[`PartnersIssueModal component should render correctly 1`] = ` </Icon> </StyledIcon> <div - className="partners-issue-title text-20-bold" + className="partner-issue-title text-20-bold" > - consumption.partners_issue_modal.title + consumption.partner_issue_modal.title </div> <div - className="partners-issue-content text-16-normal" - > - consumption.partners_issue_modal.text_1 - </div> - <ul /> + className="partner-issue-content text-16-normal" + dangerouslySetInnerHTML={ + Object { + "__html": "consumption.partner_issue_modal.error_connect_elec", + } + } + /> + <br /> <div - className="partners-issue-content text-16-normal" - > - consumption.partners_issue_modal.text_2 - </div> + dangerouslySetInnerHTML={ + Object { + "__html": "consumption.partner_issue_modal.additional_text", + } + } + /> <WithStyles(ForwardRef(Button)) classes={ Object { "label": "text-16-bold", - "root": "btn-highlight partnermodalclose", + "root": "btn-highlight", } } - onClick={[MockFunction]} + onClick={[Function]} > <ForwardRef(Button) classes={ @@ -992,7 +952,7 @@ exports[`PartnersIssueModal component should render correctly 1`] = ` "outlinedSecondary": "MuiButton-outlinedSecondary", "outlinedSizeLarge": "MuiButton-outlinedSizeLarge", "outlinedSizeSmall": "MuiButton-outlinedSizeSmall", - "root": "MuiButton-root btn-highlight partnermodalclose", + "root": "MuiButton-root btn-highlight", "sizeLarge": "MuiButton-sizeLarge", "sizeSmall": "MuiButton-sizeSmall", "startIcon": "MuiButton-startIcon", @@ -1003,19 +963,19 @@ exports[`PartnersIssueModal component should render correctly 1`] = ` "textSizeSmall": "MuiButton-textSizeSmall", } } - onClick={[MockFunction]} + onClick={[Function]} > <WithStyles(ForwardRef(ButtonBase)) - className="MuiButton-root btn-highlight partnermodalclose MuiButton-text" + className="MuiButton-root btn-highlight MuiButton-text" component="button" disabled={false} focusRipple={true} focusVisibleClassName="Mui-focusVisible" - onClick={[MockFunction]} + onClick={[Function]} type="button" > <ForwardRef(ButtonBase) - className="MuiButton-root btn-highlight partnermodalclose MuiButton-text" + className="MuiButton-root btn-highlight MuiButton-text" classes={ Object { "disabled": "Mui-disabled", @@ -1027,14 +987,14 @@ exports[`PartnersIssueModal component should render correctly 1`] = ` disabled={false} focusRipple={true} focusVisibleClassName="Mui-focusVisible" - onClick={[MockFunction]} + onClick={[Function]} type="button" > <button - className="MuiButtonBase-root MuiButton-root btn-highlight partnermodalclose MuiButton-text" + className="MuiButtonBase-root MuiButton-root btn-highlight MuiButton-text" disabled={false} onBlur={[Function]} - onClick={[MockFunction]} + onClick={[Function]} onDragLeave={[Function]} onFocus={[Function]} onKeyDown={[Function]} @@ -1051,7 +1011,7 @@ exports[`PartnersIssueModal component should render correctly 1`] = ` <span className="MuiButton-label text-16-bold" > - consumption.partners_issue_modal.button_validate + consumption.partner_issue_modal.ok </span> <WithStyles(memo) center={false} @@ -1104,6 +1064,6 @@ exports[`PartnersIssueModal component should render correctly 1`] = ` </ForwardRef(Modal)> </ForwardRef(Dialog)> </WithStyles(ForwardRef(Dialog))> - </PartnersIssueModal> + </PartnerIssueModal> </Provider> `; diff --git a/src/components/PartnersIssue/partnersIssueModal.scss b/src/components/PartnerIssue/partnerIssueModal.scss similarity index 58% rename from src/components/PartnersIssue/partnersIssueModal.scss rename to src/components/PartnerIssue/partnerIssueModal.scss index ffb8ff0beb1ad2a96b8681467440c33b3646224b..5ffa04a87f5eaae4394bdc6494905d9474423426 100644 --- a/src/components/PartnersIssue/partnersIssueModal.scss +++ b/src/components/PartnerIssue/partnerIssueModal.scss @@ -1,27 +1,41 @@ @import '../../styles/base/typo-variables'; @import '../../styles/base/color'; -.partnersIssueModal { +.partnerIssueModal { padding: 1rem; max-width: 20rem; + text-align: center; .warn-icon { margin: 1rem auto; display: block; } - .partners-issue-title { + .partner-issue-title { color: #ec9d41; margin: 1rem auto; text-align: center; } - .partners-issue-content { + .partner-issue-content { color: $grey-bright; + font-weight: bold; + + span { + &.gaz { + color: $gas-color; + } + &.elec { + color: $elec-color; + } + &.water { + color: $water-color; + } + } } button.btn-highlight { padding: 0.65rem; } } -.partners-issue-portal { +.partner-issue-portal { .modal-overlay { .modal-box { max-width: 21rem; diff --git a/src/components/PartnersIssue/PartnersIssueModal.tsx b/src/components/PartnersIssue/PartnersIssueModal.tsx deleted file mode 100644 index d79afb180370335afd8d90884454f3377203edd9..0000000000000000000000000000000000000000 --- a/src/components/PartnersIssue/PartnersIssueModal.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import Button from '@material-ui/core/Button' -import Dialog from '@material-ui/core/Dialog' -import IconButton from '@material-ui/core/IconButton' -import CloseIcon from 'assets/icons/ico/close.svg' -import OrangeWarn from 'assets/icons/ico/warn-orange.svg' -import StyledIcon from 'components/CommonKit/Icon/StyledIcon' -import { useI18n } from 'cozy-ui/transpiled/react/I18n' -import Icon from 'cozy-ui/transpiled/react/Icon' -import { FluidState } from 'enum/fluid.enum' -import { FluidStatus } from 'models' -import React, { useEffect, useState } from 'react' -import './partnersIssueModal.scss' - -interface PartnersIssueModalProps { - open: boolean - fluidStatus: FluidStatus[] - handleCloseClick: () => void -} - -const PartnersIssueModal: React.FC<PartnersIssueModalProps> = ({ - open, - fluidStatus, - handleCloseClick, -}: PartnersIssueModalProps) => { - const { t } = useI18n() - const [fluids, setFluids] = useState<FluidStatus[]>([]) - - useEffect(() => { - const issuedFluids = fluidStatus.filter(fluid => { - return fluid.status === FluidState.PARTNER_ISSUE - }) - setFluids(issuedFluids) - }, [fluidStatus]) - return ( - <Dialog - open={open} - disableEscapeKeyDown - onClose={(event, reason): void => { - event && reason !== 'backdropClick' && handleCloseClick() - }} - aria-labelledby={'accessibility-title'} - classes={{ - root: 'modal-root', - paper: 'modal-paper', - }} - style={{ zIndex: 1500 }} - > - <div id={'accessibility-title'}> - {t('feedback.accessibility.window_title')} - </div> - <IconButton - aria-label={t('feedback.accessibility.button_close')} - className="modal-paper-close-button" - onClick={handleCloseClick} - > - <Icon icon={CloseIcon} size={16} /> - </IconButton> - <div className="partnersIssueModal"> - <StyledIcon icon={OrangeWarn} size={40} className={'warn-icon'} /> - - <div className="partners-issue-title text-20-bold"> - {t('consumption.partners_issue_modal.title')} - </div> - <div className="partners-issue-content text-16-normal"> - {t('consumption.partners_issue_modal.text_1')} - </div> - <ul> - {fluids.map((fluid: FluidStatus, key) => { - return ( - <li key={key} className="text-16-bold"> - {fluid.connection.konnectorConfig.name} - </li> - ) - })} - </ul> - <div className="partners-issue-content text-16-normal"> - {t('consumption.partners_issue_modal.text_2')} - </div> - - <Button - onClick={handleCloseClick} - classes={{ - root: 'btn-highlight partnermodalclose', - label: 'text-16-bold', - }} - > - {t('consumption.partners_issue_modal.button_validate')} - </Button> - </div> - </Dialog> - ) -} - -export default PartnersIssueModal diff --git a/src/components/Splash/SplashRoot.tsx b/src/components/Splash/SplashRoot.tsx index 46fbf339072415e059c44261e56031b6fc776cc8..ea6cffa0c8b08d8a6ae93b8c42c74bca27d66bf9 100644 --- a/src/components/Splash/SplashRoot.tsx +++ b/src/components/Splash/SplashRoot.tsx @@ -2,7 +2,6 @@ import * as Sentry from '@sentry/react' import classNames from 'classnames' import useExploration from 'components/Hooks/useExploration' import { useClient } from 'cozy-client' -import { FluidState } from 'enum/fluid.enum' import { UsageEventType } from 'enum/usageEvent.enum' import { UserActionState } from 'enum/userAction.enum' import { UserChallengeState } from 'enum/userChallenge.enum' @@ -14,11 +13,18 @@ import { import { DateTime } from 'luxon' import { migrations } from 'migrations/migration.data' import { MigrationService } from 'migrations/migration.service' -import { FluidStatus, TermsStatus, UserChallenge } from 'models' +import { Profile, TermsStatus, UserChallenge } from 'models' +import { CustomPopup } from 'models/customPopup.model' import { InitSteps, InitStepsErrors } from 'models/initialisationSteps.model' import { PartnersInfo } from 'models/partnersInfo.model' import { ReleaseNotes } from 'models/releaseNotes.model' -import React, { Dispatch, ReactNode, useEffect, useState } from 'react' +import React, { + Dispatch, + ReactNode, + useCallback, + useEffect, + useState, +} from 'react' import { useDispatch } from 'react-redux' import ActionService from 'services/action.service' import ChallengeService from 'services/challenge.service' @@ -75,6 +81,7 @@ interface SplashRootProps { */ const SplashRoot = ({ fadeTimer = 1000, children }: SplashRootProps) => { const client = useClient() + const today = getTodayDate().toISO() const [{ splashEnd, splashStart }, setState] = useState({ splashEnd: false, splashStart: false, @@ -93,6 +100,84 @@ const SplashRoot = ({ fadeTimer = 1000, children }: SplashRootProps) => { | ProfileTypeActionTypes > = useDispatch() + /** Return current status of partner if modal has not been seen today */ + const getPartnerStatus = useCallback( + (currentStatus: boolean, lastSeenDate: DateTime) => { + if (today !== lastSeenDate.toISO()) { + return currentStatus + } + return false + }, + [today] + ) + + /** For each fluid, if notification is activated, set FluidStatus.maintenance to true */ + const processFluidsStatus = useCallback( + async (profile: Profile, partnersInfo: PartnersInfo) => { + if (partnersInfo.notification_activated && !profile?.isFirstConnection) { + const fluidService = new FluidService(client) + const _updatedFluidStatus = await fluidService.getFluidStatus( + partnersInfo + ) + dispatch(setFluidStatus(_updatedFluidStatus)) + } + }, + [client, dispatch] + ) + + /** Process customPopup and enable it if activated */ + const processCustomPopup = useCallback( + async (profile: Profile, customPopup: CustomPopup) => { + try { + if ( + today !== profile?.customPopupDate.toISO() && + !profile?.isFirstConnection + ) { + dispatch(setCustomPopup(customPopup)) + } + } catch (error) { + console.error('Error while checking customPopup informations') + } + }, + [dispatch, today] + ) + + /** + * For each fluid, set partnersIssue to true if notification is activated and seenDate < today + */ + const processPartnersStatus = useCallback( + async (profile: Profile, partnersInfo: PartnersInfo) => { + try { + if ( + partnersInfo.notification_activated && + !profile?.isFirstConnection + ) { + const partnersIssue = { + enedis: getPartnerStatus( + partnersInfo.enedis_failure, + profile.partnersIssueSeenDate.enedis + ), + egl: getPartnerStatus( + partnersInfo.egl_failure, + profile.partnersIssueSeenDate.egl + ), + grdf: getPartnerStatus( + partnersInfo.grdf_failure, + profile.partnersIssueSeenDate.grdf + ), + } + + if (Object.values(partnersIssue).some(issue => issue)) { + dispatch(setPartnersIssue(partnersIssue)) + } + } + } catch (error) { + console.error('Error while fetching partners informations') + } + }, + [dispatch, getPartnerStatus] + ) + useEffect(() => { let timeoutSplash: NodeJS.Timeout if (splashStart) { @@ -113,7 +198,6 @@ const SplashRoot = ({ fadeTimer = 1000, children }: SplashRootProps) => { setInitStep, setInitStepErrors ) - const partnersInfoService = new PartnersInfoService(client) const customPopupService = new CustomPupopService(client) const ms = new MigrationService(client, setInitStepErrors) try { @@ -263,55 +347,16 @@ const SplashRoot = ({ fadeTimer = 1000, children }: SplashRootProps) => { }) } - const today = getTodayDate().toISO() - - try { - // Check customPopup from backoffice - const customModalInfo = await customPopupService.getCustomPopup() - if ( - customModalInfo && - today !== profile?.customPopupDate.toISO() && - !profile?.isFirstConnection - ) { - dispatch(setCustomPopup(customModalInfo)) - } - } catch (error) { - console.error('Error while checking customPopup informations') + const customPopup = await customPopupService.getCustomPopup() + if (profile && customPopup) { + await processCustomPopup(profile, customPopup) } - try { - // Check partnersInfo from backoffice - const partnersInfo: PartnersInfo | undefined = - await partnersInfoService.getPartnersInfo() - - // If notification is activated and konnector is connected, set FluidStatus to PARTNER_ISSUE - if ( - partnersInfo?.notification_activated && - !profile?.isFirstConnection - ) { - const fluidService = new FluidService(client) - const _updatedFluidStatus: FluidStatus[] = - await fluidService.getFluidStatus(partnersInfo) - let isConcernedByPartnerIssue = false - for (const fluid of _updatedFluidStatus) { - if (fluid.status === FluidState.PARTNER_ISSUE) - isConcernedByPartnerIssue = true - } - dispatch(setFluidStatus(_updatedFluidStatus)) - /* - * If the partnersIssueModal has not been seen today - * and the user is concerned by any one of the partners' issue - * enable the modal - */ - if ( - today !== profile?.partnersIssueDate.toISO() && - isConcernedByPartnerIssue - ) { - dispatch(setPartnersIssue(true)) - } - } - } catch (error) { - console.error('Error while fetching partners informations') + const partnersInfoService = new PartnersInfoService(client) + const partnersInfo = await partnersInfoService.getPartnersInfo() + if (profile && partnersInfo) { + await processFluidsStatus(profile, partnersInfo) + await processPartnersStatus(profile, partnersInfo) } if (subscribed) { @@ -333,7 +378,15 @@ const SplashRoot = ({ fadeTimer = 1000, children }: SplashRootProps) => { return () => { subscribed = false } - }, [client, dispatch, initStepErrors, setValidExploration]) + }, [ + client, + dispatch, + initStepErrors, + processCustomPopup, + processFluidsStatus, + processPartnersStatus, + setValidExploration, + ]) return ( <> diff --git a/src/doctypes/remote/org.ecolyo.agent.partners.info.ts b/src/doctypes/remote/org.ecolyo.agent.partners.info.ts index b3b3be25a55d4bbfacd278ac0a44be25f561a13b..56ae4a166e793f18b2b93bfc78ac7a65e4a9f008 100644 --- a/src/doctypes/remote/org.ecolyo.agent.partners.info.ts +++ b/src/doctypes/remote/org.ecolyo.agent.partners.info.ts @@ -1,4 +1,4 @@ -export const REMOTE_ORG_ECOLYO_AGENT_PARTERS_INFO = +export const REMOTE_ORG_ECOLYO_AGENT_PARTNERS_INFO = '/remote/org.ecolyo.agent.partners.info' -export const REMOTE_ORG_ECOLYO_AGENT_PARTERS_INFO_REC = +export const REMOTE_ORG_ECOLYO_AGENT_PARTNERS_INFO_REC = '/remote/org.ecolyo.agent.partners.info.rec' diff --git a/src/enum/fluid.enum.ts b/src/enum/fluid.enum.ts index cd67a004d54e7b3c9b1e7a3216b917731ecb3edc..e62cd529ed496bf0b6c157c4d59c82b5ac04f3d4 100644 --- a/src/enum/fluid.enum.ts +++ b/src/enum/fluid.enum.ts @@ -11,5 +11,4 @@ export enum FluidState { DONE = 200, ERROR = 300, ERROR_LOGIN_FAILED = 301, - PARTNER_ISSUE = 500, } diff --git a/src/locales/fr.json b/src/locales/fr.json index 3a948a8c7fce48566e302c7326076ea47e69946e..4711915b7383cf8a32a242f5b4c20734f6981a6d 100644 --- a/src/locales/fr.json +++ b/src/locales/fr.json @@ -281,11 +281,12 @@ "button_next_value": "Sélectionner la valeur suivante", "checkbox_compare": "Afficher ou cacher la comparaison" }, - "partners_issue_modal": { - "title": "Un problème dans la récupération de vos données", - "text_1": "Ces partenaires nous indiquent qu’ils subissent en ce moment un soucis technique\u00a0:", - "text_2": "La visualisation de vos consommations peut s’en trouver affectée.", - "button_validate": "J'ai compris", + "partner_issue_modal": { + "title": "Attention\u00a0!", + "error_connect_gaz": "La connexion à vos données de <span class='gaz'>gaz</span> est actuellement dysfonctionnelle (Maintenance chez notre partenaire <span class='gaz'>GRDF</span> ou dans notre service)", + "error_connect_elec": "La connexion à vos données d'<span class='elec'>électricité</span> est actuellement dysfonctionnelle (Maintenance chez notre partenaire <span class='elec'>Enedis</span> ou dans notre service)", + "error_connect_water": "La connexion à vos données d'<span class='water'>eau</span> est actuellement dysfonctionnelle (Maintenance chez notre partenaire <span class='water'>Eau Publique du Grand Lyon</span> ou dans notre service)", + "additional_text": "La visualisation et/ou la connexion à vos données de consommation peut s'en trouver affectée.<br /><br /><i>Merci pour votre patience en attendant un retour à la normale :)</i>", "ok": "Ok" } }, diff --git a/src/migrations/migration.data.ts b/src/migrations/migration.data.ts index 62fa67e93d9445a8cf05988f2079321975815433..06a3a1b5b2bf46746da60bd6f64199b75904b90c 100644 --- a/src/migrations/migration.data.ts +++ b/src/migrations/migration.data.ts @@ -294,7 +294,7 @@ export const migrations: Migration[] = [ releaseNotes: null, docTypes: PROFILE_DOCTYPE, run: async (_client: Client, docs: any[]): Promise<Profile[]> => { - return docs.map((doc: Profile) => { + return docs.map(doc => { doc.partnersIssueDate = DateTime.local() .minus({ day: 1 }) .startOf('day') @@ -547,4 +547,27 @@ export const migrations: Migration[] = [ run: async (): Promise<any> => {}, isEmpty: true, }, + { + baseSchemaVersion: 21, + targetSchemaVersion: 22, + appVersion: '2.0.3', + description: + 'Profil now contains partnersIssueSeenDates in order to handle each partners issue date. Also removes previous partnersIssueDate', + releaseNotes: null, + docTypes: PROFILE_DOCTYPE, + run: async (_client: Client, docs: any[]): Promise<Profile[]> => { + return docs.map(doc => { + doc.partnersIssueSeenDate = { + enedis: DateTime.local().minus({ day: 1 }).startOf('day'), + egl: DateTime.local().minus({ day: 1 }).startOf('day'), + grdf: DateTime.local().minus({ day: 1 }).startOf('day'), + } + if (doc.partnersIssueDate) { + delete doc.partnersIssueDate + } + // TODO remove this ? + return doc + }) + }, + }, ] diff --git a/src/models/fluid.model.ts b/src/models/fluid.model.ts index 812d6e59e61e2852b3f74c5d564fc32bcbbe87e5..2994de62dc4b3ada6fcc0f347872a937964bba18 100644 --- a/src/models/fluid.model.ts +++ b/src/models/fluid.model.ts @@ -1,6 +1,6 @@ -import { DateTime } from 'luxon' import { FluidState, FluidType } from 'enum/fluid.enum' -import { Konnector, Trigger, Account, KonnectorConfig } from 'models' +import { DateTime } from 'luxon' +import { Account, Konnector, KonnectorConfig, Trigger } from 'models' import { TriggerState } from './trigger.model' export interface FluidConnection { @@ -15,6 +15,7 @@ export interface FluidConnection { export interface FluidStatus { fluidType: FluidType status: FluidState + maintenance: boolean firstDataDate: DateTime | null lastDataDate: DateTime | null connection: FluidConnection diff --git a/src/models/global.model.ts b/src/models/global.model.ts index 4fef5e1e9e6743aa2650c297b0da25ce1d6e47ca..0af069136897307a2aa5830d33d6334e1d2b95d1 100644 --- a/src/models/global.model.ts +++ b/src/models/global.model.ts @@ -17,7 +17,11 @@ export interface GlobalState { fluidStatus: FluidStatus[] fluidTypes: FluidType[] customPopupModal: CustomPopup - openPartnersIssueModal: boolean + openPartnersIssueModal: { + enedis: boolean + egl: boolean + grdf: boolean + } shouldRefreshConsent: boolean sgeConnect: SgeStore } diff --git a/src/models/profile.model.ts b/src/models/profile.model.ts index 8478e1b66d91beaa0375949e09b4deb3e277445a..8950b1d2292130b18ae1a21ed4d42b31524a0c50 100644 --- a/src/models/profile.model.ts +++ b/src/models/profile.model.ts @@ -22,7 +22,11 @@ export interface ProfileEntity { isProfileEcogestureCompleted: boolean onboarding: Onboarding mailToken: string - partnersIssueDate: string + partnersIssueSeenDate: { + enedis: string + egl: string + grdf: string + } haveSeenEcogestureModal: boolean activateHalfHourDate: string customPopupDate: string @@ -35,13 +39,17 @@ export interface Profile ProfileEntity, | 'lastConnectionDate' | 'monthlyAnalysisDate' - | 'partnersIssueDate' + | 'partnersIssueSeenDate' | 'activateHalfHourDate' | 'customPopupDate' > { lastConnectionDate: DateTime monthlyAnalysisDate: DateTime - partnersIssueDate: DateTime + partnersIssueSeenDate: { + enedis: DateTime + egl: DateTime + grdf: DateTime + } customPopupDate: DateTime activateHalfHourDate: DateTime } diff --git a/src/services/fluid.service.spec.ts b/src/services/fluid.service.spec.ts index 3386a9e185d6e3a7a0cfcccb9d015aa4e4f676bd..532d1add481a171c569d173e3400544188d321ff 100644 --- a/src/services/fluid.service.spec.ts +++ b/src/services/fluid.service.spec.ts @@ -1,16 +1,13 @@ -import FluidService from './fluid.service' -import mockClient from '../../tests/__mocks__/client' +/* eslint-disable camelcase */ +import { FluidState, FluidType } from 'enum/fluid.enum' +import { DateTime } from 'luxon' +import { FluidStatus } from 'models' import { accountsData } from '../../tests/__mocks__/accountsData.mock' +import mockClient from '../../tests/__mocks__/client' import { konnectorsData } from '../../tests/__mocks__/konnectorsData.mock' import { triggersData } from '../../tests/__mocks__/triggersData.mock' import { triggerStateData } from '../../tests/__mocks__/triggerStateData.mock' -import { DateTime } from 'luxon' -import { FluidState, FluidType } from 'enum/fluid.enum' -import { FluidStatus } from 'models' -import { - mockPartnersInfoActivated, - mockPartnersInfoDisabled, -} from '../../tests/__mocks__/fluidStatusData.mock' +import FluidService from './fluid.service' const mockGetAccountByType = jest.fn() jest.mock('./account.service', () => { @@ -64,7 +61,7 @@ const mockDataDates: (DateTime | null)[] = [ }), ] -describe('FLuid service', () => { +describe('Fluid service', () => { const fluidService = new FluidService(mockClient) beforeEach(() => { @@ -82,6 +79,7 @@ describe('FLuid service', () => { { fluidType: FluidType.ELECTRICITY, status: FluidState.ERROR, + maintenance: false, firstDataDate: mockDataDates[FluidType.ELECTRICITY], lastDataDate: mockDataDates[FluidType.ELECTRICITY], connection: { @@ -103,6 +101,7 @@ describe('FLuid service', () => { { fluidType: FluidType.WATER, status: FluidState.ERROR, + maintenance: false, firstDataDate: mockDataDates[FluidType.WATER], lastDataDate: mockDataDates[FluidType.WATER], connection: { @@ -125,6 +124,7 @@ describe('FLuid service', () => { { fluidType: FluidType.GAS, status: FluidState.ERROR, + maintenance: false, firstDataDate: mockDataDates[FluidType.GAS], lastDataDate: mockDataDates[FluidType.GAS], connection: { @@ -168,6 +168,7 @@ describe('FLuid service', () => { { fluidType: FluidType.ELECTRICITY, status: FluidState.NOT_CONNECTED, + maintenance: false, firstDataDate: mockDataDates[FluidType.ELECTRICITY], lastDataDate: mockDataDates[FluidType.ELECTRICITY], connection: { @@ -189,6 +190,7 @@ describe('FLuid service', () => { { fluidType: FluidType.WATER, status: FluidState.NOT_CONNECTED, + maintenance: false, firstDataDate: mockDataDates[FluidType.WATER], lastDataDate: mockDataDates[FluidType.WATER], connection: { @@ -211,6 +213,7 @@ describe('FLuid service', () => { { fluidType: FluidType.GAS, status: FluidState.NOT_CONNECTED, + maintenance: false, firstDataDate: mockDataDates[FluidType.GAS], lastDataDate: mockDataDates[FluidType.GAS], connection: { @@ -254,6 +257,7 @@ describe('FLuid service', () => { { fluidType: FluidType.ELECTRICITY, status: FluidState.KONNECTOR_NOT_FOUND, + maintenance: false, firstDataDate: mockDataDates[FluidType.ELECTRICITY], lastDataDate: mockDataDates[FluidType.ELECTRICITY], connection: { @@ -275,6 +279,7 @@ describe('FLuid service', () => { { fluidType: FluidType.WATER, status: FluidState.KONNECTOR_NOT_FOUND, + maintenance: false, firstDataDate: mockDataDates[FluidType.WATER], lastDataDate: mockDataDates[FluidType.WATER], connection: { @@ -297,6 +302,7 @@ describe('FLuid service', () => { { fluidType: FluidType.GAS, status: FluidState.KONNECTOR_NOT_FOUND, + maintenance: false, firstDataDate: mockDataDates[FluidType.GAS], lastDataDate: mockDataDates[FluidType.GAS], connection: { @@ -340,6 +346,7 @@ describe('FLuid service', () => { { fluidType: FluidType.ELECTRICITY, status: FluidState.NOT_CONNECTED, + maintenance: false, firstDataDate: mockDataDates[FluidType.ELECTRICITY], lastDataDate: mockDataDates[FluidType.ELECTRICITY], connection: { @@ -361,6 +368,7 @@ describe('FLuid service', () => { { fluidType: FluidType.WATER, status: FluidState.NOT_CONNECTED, + maintenance: false, firstDataDate: mockDataDates[FluidType.WATER], lastDataDate: mockDataDates[FluidType.WATER], connection: { @@ -383,6 +391,7 @@ describe('FLuid service', () => { { fluidType: FluidType.GAS, status: FluidState.NOT_CONNECTED, + maintenance: false, firstDataDate: mockDataDates[FluidType.GAS], lastDataDate: mockDataDates[FluidType.GAS], connection: { @@ -427,6 +436,7 @@ describe('FLuid service', () => { { fluidType: FluidType.ELECTRICITY, status: FluidState.ERROR, + maintenance: false, firstDataDate: _mockDataDates[FluidType.ELECTRICITY], lastDataDate: _mockDataDates[FluidType.ELECTRICITY], connection: { @@ -448,6 +458,7 @@ describe('FLuid service', () => { { fluidType: FluidType.WATER, status: FluidState.NOT_CONNECTED, + maintenance: false, firstDataDate: null, lastDataDate: null, connection: { @@ -470,6 +481,7 @@ describe('FLuid service', () => { { fluidType: FluidType.GAS, status: FluidState.NOT_CONNECTED, + maintenance: false, firstDataDate: null, lastDataDate: null, connection: { @@ -510,6 +522,20 @@ describe('FLuid service', () => { const result: FluidStatus[] = await fluidService.getFluidStatus() expect(result).toEqual(mockResult) }) + + it('should return fluids in maintenance', async () => { + mockFetchAllFirstDateData.mockResolvedValue(mockDataDates) + mockFetchAllLastDateData.mockResolvedValue(mockDataDates) + const fluidStatus = await fluidService.getFluidStatus({ + egl_failure: true, + enedis_failure: true, + grdf_failure: true, + notification_activated: true, + }) + expect(fluidStatus[0].maintenance).toBeTruthy() + expect(fluidStatus[1].maintenance).toBeTruthy() + expect(fluidStatus[2].maintenance).toBeTruthy() + }) }) describe('getOldFluidData method', () => { @@ -653,165 +679,5 @@ describe('FLuid service', () => { const result: FluidType[] = await FluidService.getOldFluidData([]) expect(result).toEqual([]) }) - it('should return the fluids concerned by partnerIssue', async () => { - const mockFluidStatus: FluidStatus[] = [ - { - fluidType: FluidType.ELECTRICITY, - status: FluidState.PARTNER_ISSUE, - firstDataDate: DateTime.fromISO('2019-01-01').setZone('utc', { - keepLocalTime: true, - }), - lastDataDate: DateTime.fromISO('2020-01-01').setZone('utc', { - keepLocalTime: true, - }), - connection: { - konnector: konnectorsData[0], - account: accountsData[0], - trigger: triggersData[0], - triggerState: triggerStateData, - shouldLaunchKonnector: false, - isUpdating: false, - konnectorConfig: { - name: 'Enedis', - oauth: false, - slug: 'enedissgegrandlyon', - siteLink: 'https://mon-compte-client.enedis.fr/', - activation: 'https://mon-compte-particulier.enedis.fr/donnees/', - }, - }, - }, - { - fluidType: FluidType.WATER, - status: FluidState.ERROR_LOGIN_FAILED, - firstDataDate: DateTime.fromISO('2019-01-01').setZone('utc', { - keepLocalTime: true, - }), - lastDataDate: DateTime.fromISO('2020-01-01').setZone('utc', { - keepLocalTime: true, - }), - connection: { - konnector: konnectorsData[0], - account: accountsData[0], - trigger: triggersData[0], - triggerState: triggerStateData, - shouldLaunchKonnector: false, - isUpdating: false, - konnectorConfig: { - name: 'Eau du Grand Lyon', - oauth: true, - slug: 'eglgrandlyon', - siteLink: '', - activation: '', - }, - }, - }, - ] - const result: FluidType[] = - await fluidService.getFluidsConcernedByPartnerIssue(mockFluidStatus) - expect(result).toEqual([FluidType.ELECTRICITY]) - }) - it('should return the fluids concerned by partnerIssue', async () => { - const mockFluidStatus: FluidStatus[] = [ - { - fluidType: FluidType.ELECTRICITY, - status: FluidState.PARTNER_ISSUE, - firstDataDate: DateTime.fromISO('2019-01-01').setZone('utc', { - keepLocalTime: true, - }), - lastDataDate: DateTime.fromISO('2020-01-01').setZone('utc', { - keepLocalTime: true, - }), - connection: { - konnector: konnectorsData[0], - account: accountsData[0], - trigger: triggersData[0], - triggerState: triggerStateData, - shouldLaunchKonnector: false, - isUpdating: false, - konnectorConfig: { - name: 'Enedis', - oauth: false, - slug: 'enedissgegrandlyon', - siteLink: 'https://mon-compte-client.enedis.fr/', - activation: 'https://mon-compte-particulier.enedis.fr/donnees/', - }, - }, - }, - { - fluidType: FluidType.WATER, - status: FluidState.ERROR_LOGIN_FAILED, - firstDataDate: DateTime.fromISO('2019-01-01').setZone('utc', { - keepLocalTime: true, - }), - lastDataDate: DateTime.fromISO('2020-01-01').setZone('utc', { - keepLocalTime: true, - }), - connection: { - konnector: konnectorsData[0], - account: accountsData[0], - trigger: triggersData[0], - triggerState: triggerStateData, - shouldLaunchKonnector: false, - isUpdating: false, - konnectorConfig: { - name: 'Eau du Grand Lyon', - oauth: true, - slug: 'eglgrandlyon', - siteLink: '', - activation: '', - }, - }, - }, - { - fluidType: FluidType.GAS, - status: FluidState.ERROR_LOGIN_FAILED, - firstDataDate: DateTime.fromISO('2019-01-01').setZone('utc', { - keepLocalTime: true, - }), - lastDataDate: DateTime.fromISO('2020-01-01').setZone('utc', { - keepLocalTime: true, - }), - connection: { - konnector: konnectorsData[0], - account: accountsData[0], - trigger: triggersData[0], - triggerState: triggerStateData, - shouldLaunchKonnector: false, - isUpdating: false, - konnectorConfig: { - name: 'GRDF', - oauth: true, - slug: 'grdfgrandlyon', - siteLink: '', - activation: '', - }, - }, - }, - ] - const result: boolean = fluidService.isFluidInPartnerIssue( - FluidType.ELECTRICITY, - mockFluidStatus[FluidType.ELECTRICITY].status, - mockPartnersInfoActivated - ) - expect(result).toEqual(true) - const result2: boolean = fluidService.isFluidInPartnerIssue( - FluidType.WATER, - FluidState.PARTNER_ISSUE, - mockPartnersInfoActivated - ) - expect(result2).toEqual(true) - const result3: boolean = fluidService.isFluidInPartnerIssue( - FluidType.GAS, - FluidState.PARTNER_ISSUE, - mockPartnersInfoActivated - ) - expect(result3).toEqual(true) - const result4: boolean = fluidService.isFluidInPartnerIssue( - FluidType.GAS, - FluidState.PARTNER_ISSUE, - mockPartnersInfoDisabled - ) - expect(result4).toEqual(false) - }) }) }) diff --git a/src/services/fluid.service.ts b/src/services/fluid.service.ts index 36d865381cf38a3d9897295b67b38217c476c85a..8376c496b1a48efa960b4f62241560c9c3b0a320 100644 --- a/src/services/fluid.service.ts +++ b/src/services/fluid.service.ts @@ -1,14 +1,13 @@ -import { FluidState, FluidType } from 'enum/fluid.enum' import { Client } from 'cozy-client' - +import { FluidState, FluidType } from 'enum/fluid.enum' +import { DateTime } from 'luxon' import { Account, FluidStatus, Konnector, Trigger, TriggerState } from 'models' +import { PartnersInfo } from 'models/partnersInfo.model' +import AccountService from 'services/account.service' +import ConsumptionService from 'services/consumption.service' import ConfigService from 'services/fluidConfig.service' import KonnectorService from 'services/konnector.service' -import ConsumptionService from 'services/consumption.service' -import AccountService from 'services/account.service' import TriggerService from 'services/triggers.service' -import { DateTime } from 'luxon' -import { PartnersInfo } from 'models/partnersInfo.model' export default class FluidService { private _client: Client @@ -21,68 +20,34 @@ export default class FluidService { konnector: Konnector | null, state: TriggerState | null ): FluidState => { - if (konnector) { - if (state) { - switch (state.status) { - case 'done': - return FluidState.DONE - case 'errored': - if (state?.last_error === 'LOGIN_FAILED') - return FluidState.ERROR_LOGIN_FAILED - else return FluidState.ERROR - default: - return FluidState.NOT_CONNECTED - } - } else { - return FluidState.NOT_CONNECTED - } + if (!konnector) return FluidState.KONNECTOR_NOT_FOUND + if (!state) { + return FluidState.NOT_CONNECTED } else { - return FluidState.KONNECTOR_NOT_FOUND + switch (state.status) { + case 'done': + return FluidState.DONE + case 'errored': + if (state?.last_error === 'LOGIN_FAILED') + return FluidState.ERROR_LOGIN_FAILED + else return FluidState.ERROR + default: + return FluidState.NOT_CONNECTED + } } } - public isFluidInPartnerIssue = ( + public parseFluidMaintenance( fluidType: FluidType, - fluidState: FluidState, - partnersInfo: PartnersInfo - ) => { - if ( - fluidType === FluidType.GAS && - partnersInfo.grdf_failure && - fluidState !== FluidState.NOT_CONNECTED && - fluidState !== FluidState.KONNECTOR_NOT_FOUND - ) { - return true - } - if ( - fluidType === FluidType.ELECTRICITY && - partnersInfo.enedis_failure && - fluidState !== FluidState.NOT_CONNECTED && - fluidState !== FluidState.KONNECTOR_NOT_FOUND - ) { - return true - } - if ( - fluidType === FluidType.WATER && - partnersInfo.egl_failure && - fluidState !== FluidState.NOT_CONNECTED && - fluidState !== FluidState.KONNECTOR_NOT_FOUND - ) { - return true - } - return false - } - - public getFluidsConcernedByPartnerIssue = async ( - statusArray: FluidStatus[] - ): Promise<FluidType[]> => { - const concernedFluids: FluidType[] = [] - statusArray.forEach(fluid => { - if (fluid.status === FluidState.PARTNER_ISSUE) { - concernedFluids.push(fluid.fluidType) - } - }) - return concernedFluids + partnersInfo?: PartnersInfo + ) { + return Boolean( + partnersInfo?.notification_activated && + ((fluidType === FluidType.GAS && partnersInfo.grdf_failure) || + (fluidType === FluidType.ELECTRICITY && + partnersInfo.enedis_failure) || + (fluidType === FluidType.WATER && partnersInfo.egl_failure)) + ) } public getFluidStatus = async ( @@ -150,15 +115,11 @@ export default class FluidService { const result: FluidStatus[] = [ { fluidType: FluidType.ELECTRICITY, - status: - partnersInfo?.notification_activated && - this.isFluidInPartnerIssue( - FluidType.ELECTRICITY, - this.parseFluidStatus(elecKonnector, elecStatus), - partnersInfo - ) - ? FluidState.PARTNER_ISSUE - : this.parseFluidStatus(elecKonnector, elecStatus), + status: this.parseFluidStatus(elecKonnector, elecStatus), + maintenance: this.parseFluidMaintenance( + FluidType.ELECTRICITY, + partnersInfo + ), firstDataDate: firstDataDates[FluidType.ELECTRICITY], lastDataDate: lastDataDates[FluidType.ELECTRICITY], connection: { @@ -173,15 +134,8 @@ export default class FluidService { }, { fluidType: FluidType.WATER, - status: - partnersInfo?.notification_activated && - this.isFluidInPartnerIssue( - FluidType.WATER, - this.parseFluidStatus(waterKonnector, waterStatus), - partnersInfo - ) - ? FluidState.PARTNER_ISSUE - : this.parseFluidStatus(waterKonnector, waterStatus), + status: this.parseFluidStatus(waterKonnector, waterStatus), + maintenance: this.parseFluidMaintenance(FluidType.WATER, partnersInfo), firstDataDate: firstDataDates[FluidType.WATER], lastDataDate: lastDataDates[FluidType.WATER], connection: { @@ -196,15 +150,8 @@ export default class FluidService { }, { fluidType: FluidType.GAS, - status: - partnersInfo?.notification_activated && - this.isFluidInPartnerIssue( - FluidType.GAS, - this.parseFluidStatus(gasKonnector, gasStatus), - partnersInfo - ) - ? FluidState.PARTNER_ISSUE - : this.parseFluidStatus(gasKonnector, gasStatus), + status: this.parseFluidStatus(gasKonnector, gasStatus), + maintenance: this.parseFluidMaintenance(FluidType.GAS, partnersInfo), firstDataDate: firstDataDates[FluidType.GAS], lastDataDate: lastDataDates[FluidType.GAS], connection: { diff --git a/src/services/partnersInfo.service.ts b/src/services/partnersInfo.service.ts index 867f42f41710c64ccd0cca0d2f536a790da0dd9d..26d814d8f82a0ecab9df128624bb846c6a5e5b3d 100644 --- a/src/services/partnersInfo.service.ts +++ b/src/services/partnersInfo.service.ts @@ -2,8 +2,8 @@ import * as Sentry from '@sentry/react' import { Client } from 'cozy-client' import logger from 'cozy-logger' import { - REMOTE_ORG_ECOLYO_AGENT_PARTERS_INFO, - REMOTE_ORG_ECOLYO_AGENT_PARTERS_INFO_REC, + REMOTE_ORG_ECOLYO_AGENT_PARTNERS_INFO, + REMOTE_ORG_ECOLYO_AGENT_PARTNERS_INFO_REC, } from 'doctypes/remote/org.ecolyo.agent.partners.info' import { PartnersInfo } from 'models/partnersInfo.model' import logApp from 'utils/logger' @@ -25,8 +25,8 @@ export default class PartnersInfoService { public async getPartnersInfo(): Promise<PartnersInfo | undefined> { const env = new EnvironmentService() const remoteUrl = env.isProduction() - ? REMOTE_ORG_ECOLYO_AGENT_PARTERS_INFO - : REMOTE_ORG_ECOLYO_AGENT_PARTERS_INFO_REC + ? REMOTE_ORG_ECOLYO_AGENT_PARTNERS_INFO + : REMOTE_ORG_ECOLYO_AGENT_PARTNERS_INFO_REC try { logApp.info('[Initialization] Getting PartnersInfo') const result = await this._client diff --git a/src/services/profile.service.spec.ts b/src/services/profile.service.spec.ts index e5b97cc26c71c7ea3c098b85ec0ab83e8d596838..8b63298aac546600d365c10b3d0a165c11cdda65 100644 --- a/src/services/profile.service.spec.ts +++ b/src/services/profile.service.spec.ts @@ -1,9 +1,9 @@ -import ProfileService from './profile.service' import { QueryResult } from 'cozy-client' import { DateTime } from 'luxon' import { Profile } from 'models' -import { profileData } from '../../tests/__mocks__/profile.mock' import mockClient from '../../tests/__mocks__/client' +import { profileData } from '../../tests/__mocks__/profile.mock' +import ProfileService from './profile.service' describe('UserProfile service', () => { const profileService = new ProfileService(mockClient) @@ -62,7 +62,6 @@ describe('UserProfile service', () => { const userProfile = { ...profileData, monthlyAnalysisDate: '2020-11-03T00:00:00.000Z', - partnersIssueDate: '2020-11-03T00:00:00.000Z', customPopupDate: '2020-11-03T00:00:00.000Z', } // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -90,9 +89,6 @@ describe('UserProfile service', () => { monthlyAnalysisDate: DateTime.fromISO('2020-11-03T00:00:00.000Z', { zone: 'utc', }), - partnersIssueDate: DateTime.fromISO('2020-11-03T00:00:00.000Z', { - zone: 'utc', - }), customPopupDate: DateTime.fromISO('2020-11-03T00:00:00.000Z', { zone: 'utc', }), diff --git a/src/services/profile.service.ts b/src/services/profile.service.ts index 5bd07d1d7969ed05694177b5fe06fa3da8778366..06f7d644e8277baba9382c98aeb1508902187241 100644 --- a/src/services/profile.service.ts +++ b/src/services/profile.service.ts @@ -1,7 +1,7 @@ import { Client, Q, QueryDefinition, QueryResult } from 'cozy-client' -import { Profile, ProfileEntity } from 'models' import { PROFILE_DOCTYPE } from 'doctypes' import { DateTime } from 'luxon' +import { Profile, ProfileEntity } from 'models' export default class ProfileService { private readonly _client: Client @@ -29,7 +29,11 @@ export default class ProfileService { ? this.getDate(profileEntity.monthlyAnalysisDate) : profileEntity.monthlyAnalysisDate, lastConnectionDate: this.getDate(profileEntity.lastConnectionDate), - partnersIssueDate: this.getDate(profileEntity.partnersIssueDate), + partnersIssueSeenDate: { + enedis: this.getDate(profileEntity.partnersIssueSeenDate.enedis), + egl: this.getDate(profileEntity.partnersIssueSeenDate.egl), + grdf: this.getDate(profileEntity.partnersIssueSeenDate.grdf), + }, activateHalfHourDate: this.getDate(profileEntity.activateHalfHourDate), customPopupDate: this.getDate(profileEntity.customPopupDate), } diff --git a/src/store/global/global.actions.ts b/src/store/global/global.actions.ts index 6ff372a55663811313847ddbe8a954c5c0e4fec2..c8e6204f68d7a420afeddcd6a6fe31401163ad00 100644 --- a/src/store/global/global.actions.ts +++ b/src/store/global/global.actions.ts @@ -68,7 +68,11 @@ interface ShowReleaseNotes { interface SetPartnersIssue { type: typeof SET_PARTNERS_ISSUE - payload?: boolean + payload?: { + enedis: boolean + egl: boolean + grdf: boolean + } } interface SetCustomPopup { @@ -177,9 +181,11 @@ export function updateTermValidation( } } -export function setPartnersIssue( - openPartnersIssueModal: boolean -): GlobalActionTypes { +export function setPartnersIssue(openPartnersIssueModal: { + enedis: boolean + egl: boolean + grdf: boolean +}): GlobalActionTypes { return { type: SET_PARTNERS_ISSUE, payload: openPartnersIssueModal, diff --git a/src/store/global/global.reducer.spec.ts b/src/store/global/global.reducer.spec.ts index 2ae4d0412ee7e8a07232de091fd43b9304868c08..f8203cab13b141e4eaecef84d102f4c342ea6875 100644 --- a/src/store/global/global.reducer.spec.ts +++ b/src/store/global/global.reducer.spec.ts @@ -132,6 +132,7 @@ describe('global reducer', () => { { fluidType: FluidType.ELECTRICITY, status: FluidState.ERROR, + maintenance: false, firstDataDate: mockDataDates[FluidType.ELECTRICITY], lastDataDate: mockDataDates[FluidType.ELECTRICITY], connection: { @@ -153,6 +154,7 @@ describe('global reducer', () => { { fluidType: FluidType.WATER, status: FluidState.ERROR, + maintenance: false, firstDataDate: mockDataDates[FluidType.WATER], lastDataDate: mockDataDates[FluidType.WATER], connection: { @@ -175,6 +177,7 @@ describe('global reducer', () => { { fluidType: FluidType.GAS, status: FluidState.KONNECTOR_NOT_FOUND, + maintenance: false, firstDataDate: mockDataDates[FluidType.GAS], lastDataDate: mockDataDates[FluidType.GAS], connection: { diff --git a/src/store/global/global.reducer.ts b/src/store/global/global.reducer.ts index 9a1e0abd4f9a655b6ad10201660e6203f8321d83..3c85abf91abe9478d68242e15d0d0a0a8e723244 100644 --- a/src/store/global/global.reducer.ts +++ b/src/store/global/global.reducer.ts @@ -43,6 +43,7 @@ const initialState: GlobalState = { { fluidType: FluidType.ELECTRICITY, status: FluidState.KONNECTOR_NOT_FOUND, + maintenance: false, firstDataDate: null, lastDataDate: null, connection: { @@ -64,6 +65,7 @@ const initialState: GlobalState = { { fluidType: FluidType.WATER, status: FluidState.KONNECTOR_NOT_FOUND, + maintenance: false, firstDataDate: null, lastDataDate: null, connection: { @@ -85,6 +87,7 @@ const initialState: GlobalState = { { fluidType: FluidType.GAS, status: FluidState.KONNECTOR_NOT_FOUND, + maintenance: false, firstDataDate: null, lastDataDate: null, connection: { @@ -111,7 +114,11 @@ const initialState: GlobalState = { description: '', endDate: '', }, - openPartnersIssueModal: false, + openPartnersIssueModal: { + enedis: false, + egl: false, + grdf: false, + }, shouldRefreshConsent: false, sgeConnect: { currentStep: 0, diff --git a/src/store/profile/profile.reducer.ts b/src/store/profile/profile.reducer.ts index d0c26e31f1d335be1e9326fb50fefe6c16593a2b..5c3d9433d75c9b740904fe7f2b972d421b497386 100644 --- a/src/store/profile/profile.reducer.ts +++ b/src/store/profile/profile.reducer.ts @@ -1,10 +1,10 @@ +import { DateTime } from 'luxon' +import { Profile } from 'models' import { Reducer } from 'redux' import { - UPDATE_PROFILE, ProfileActionTypes, + UPDATE_PROFILE, } from 'store/profile/profile.actions' -import { Profile } from 'models' -import { DateTime } from 'luxon' const initialState: Profile = { id: '', @@ -14,7 +14,11 @@ const initialState: Profile = { quizHash: '', explorationHash: '', isFirstConnection: false, - partnersIssueDate: DateTime.fromISO('0000-01-01T00:00:00.000Z'), + partnersIssueSeenDate: { + enedis: DateTime.fromISO('0000-01-01T00:00:00.000Z'), + egl: DateTime.fromISO('0000-01-01T00:00:00.000Z'), + grdf: DateTime.fromISO('0000-01-01T00:00:00.000Z'), + }, lastConnectionDate: DateTime.fromISO('0000-01-01T00:00:00.000Z'), customPopupDate: DateTime.fromISO('0000-01-01T00:00:00.000Z'), haveSeenLastAnalysis: true, diff --git a/tests/__mocks__/globalStateData.mock.ts b/tests/__mocks__/globalStateData.mock.ts index e2719ba60d8667a480ca1928c0c42066db653bc6..44350809d4a0d10537ffbc30412287c74c2a2270 100644 --- a/tests/__mocks__/globalStateData.mock.ts +++ b/tests/__mocks__/globalStateData.mock.ts @@ -17,11 +17,16 @@ export const globalStateData: GlobalState = { accepted: false, versionType: 'major', }, - openPartnersIssueModal: false, + openPartnersIssueModal: { + enedis: false, + egl: false, + grdf: false, + }, fluidStatus: [ { fluidType: FluidType.ELECTRICITY, status: FluidState.KONNECTOR_NOT_FOUND, + maintenance: false, firstDataDate: null, lastDataDate: null, connection: { @@ -43,6 +48,7 @@ export const globalStateData: GlobalState = { { fluidType: FluidType.WATER, status: FluidState.KONNECTOR_NOT_FOUND, + maintenance: false, firstDataDate: null, lastDataDate: null, connection: { @@ -64,6 +70,7 @@ export const globalStateData: GlobalState = { { fluidType: FluidType.GAS, status: FluidState.KONNECTOR_NOT_FOUND, + maintenance: false, firstDataDate: null, lastDataDate: null, connection: { diff --git a/tests/__mocks__/profile.mock.ts b/tests/__mocks__/profile.mock.ts index 27df89d8f7ed281b3d3ec8b029e5626f4e41af79..5aa144b8624de319827fed50f712142574a2423c 100644 --- a/tests/__mocks__/profile.mock.ts +++ b/tests/__mocks__/profile.mock.ts @@ -13,9 +13,6 @@ export const profileData: Profile = { isFirstConnection: true, sendConsumptionAlert: false, waterDailyConsumptionLimit: 0, - partnersIssueDate: DateTime.fromISO('2020-11-03T00:00:00.000Z', { - zone: 'utc', - }), mailToken: '', lastConnectionDate: DateTime.fromISO('2020-11-03T00:00:00.000Z', { zone: 'utc', @@ -37,4 +34,15 @@ export const profileData: Profile = { customPopupDate: DateTime.fromISO('2020-11-03T00:00:00.000Z', { zone: 'utc', }), + partnersIssueSeenDate: { + enedis: DateTime.fromISO('2020-11-03T00:00:00.000Z', { + zone: 'utc', + }), + egl: DateTime.fromISO('2020-11-03T00:00:00.000Z', { + zone: 'utc', + }), + grdf: DateTime.fromISO('2020-11-03T00:00:00.000Z', { + zone: 'utc', + }), + }, } diff --git a/tests/__mocks__/store.ts b/tests/__mocks__/store.ts index bf2cf076c7f5472a94546bf3d2ad88658e969afd..1ea3561a1304fcfb2ffe808370a0204707b4d4e2 100644 --- a/tests/__mocks__/store.ts +++ b/tests/__mocks__/store.ts @@ -45,7 +45,11 @@ export const mockInitialGlobalState: GlobalState = { description: '', endDate: '', }, - openPartnersIssueModal: false, + openPartnersIssueModal: { + enedis: false, + egl: false, + grdf: false, + }, releaseNotes: { show: false, notes: [{ description: '', title: '' }], @@ -54,6 +58,7 @@ export const mockInitialGlobalState: GlobalState = { { fluidType: FluidType.ELECTRICITY, status: FluidState.KONNECTOR_NOT_FOUND, + maintenance: false, firstDataDate: null, lastDataDate: null, connection: { @@ -75,6 +80,7 @@ export const mockInitialGlobalState: GlobalState = { { fluidType: FluidType.WATER, status: FluidState.KONNECTOR_NOT_FOUND, + maintenance: false, firstDataDate: null, lastDataDate: null, connection: { @@ -96,6 +102,7 @@ export const mockInitialGlobalState: GlobalState = { { fluidType: FluidType.GAS, status: FluidState.KONNECTOR_NOT_FOUND, + maintenance: false, firstDataDate: null, lastDataDate: null, connection: { @@ -134,6 +141,7 @@ export const mockInitialGlobalState: GlobalState = { export const mockExpiredElec: FluidStatus = { fluidType: FluidType.ELECTRICITY, status: FluidState.KONNECTOR_NOT_FOUND, + maintenance: true, firstDataDate: null, lastDataDate: null, connection: { @@ -167,9 +175,11 @@ export const mockExpiredElec: FluidStatus = { }, }, } + export const mockExpiredGas: FluidStatus = { fluidType: FluidType.GAS, status: FluidState.ERROR_LOGIN_FAILED, + maintenance: false, firstDataDate: null, lastDataDate: null, connection: { @@ -196,6 +206,10 @@ export const mockExpiredGas: FluidStatus = { }, }, } +export const mockMaintenanceGas: FluidStatus = { + ...mockExpiredGas, + maintenance: true, +} export const mockInitialProfileState: Profile = { id: '', @@ -205,7 +219,11 @@ export const mockInitialProfileState: Profile = { quizHash: '', explorationHash: '', isFirstConnection: false, - partnersIssueDate: DateTime.fromISO('0000-01-01T00:00:00.000Z'), + partnersIssueSeenDate: { + enedis: DateTime.fromISO('0000-01-01T00:00:00.000Z'), + egl: DateTime.fromISO('0000-01-01T00:00:00.000Z'), + grdf: DateTime.fromISO('0000-01-01T00:00:00.000Z'), + }, lastConnectionDate: DateTime.fromISO('0000-01-01T00:00:00.000Z'), haveSeenLastAnalysis: true, sendConsumptionAlert: false,