diff --git a/src/components/ContentComponents/Konnector/KonnectorNotFound.tsx b/src/components/ContentComponents/Konnector/KonnectorNotFound.tsx new file mode 100644 index 0000000000000000000000000000000000000000..a3a27c229e3fb4d3a6fa77c6b11b63aac43a5af7 --- /dev/null +++ b/src/components/ContentComponents/Konnector/KonnectorNotFound.tsx @@ -0,0 +1,38 @@ +import React from 'react' +import { translate } from 'cozy-ui/react/I18n' + +import StyledButton from 'components/CommonKit/Button/StyledButton' + +interface KonnectorNotFoundProps { + konnectorSlug: string + t: Function +} + +const KonnectorNotFound: React.FC<KonnectorNotFoundProps> = ({ + t, +}: KonnectorNotFoundProps) => { + return ( + <div className="knotfound"> + <div className="knotfound-text"> + {' '} + {t('KONNECTORCONFIG.NOT_INSTALLED')} + </div> + <div className="knotfound-button"> + <StyledButton + type="button" + color="primary" + onClick={() => + window.open( + 'https://store.yoan.cozy.wf.alpha.grandlyon.com/#/discover/boulanger', + '_blank' + ) + } + > + {t('KONNECTORCONFIG.BTN_INSTALL')} + </StyledButton> + </div> + </div> + ) +} + +export default translate()(KonnectorNotFound) diff --git a/src/components/ContentComponents/KonnectorViewer/KonnectorViewer.tsx b/src/components/ContentComponents/KonnectorViewer/KonnectorViewer.tsx index 25c75055bcbecfff530f6a0b8af9e192599e245b..0b12f3c51b281976d309821a5c89c4bd57fdee55 100644 --- a/src/components/ContentComponents/KonnectorViewer/KonnectorViewer.tsx +++ b/src/components/ContentComponents/KonnectorViewer/KonnectorViewer.tsx @@ -1,7 +1,7 @@ import React, { useState, useEffect } from 'react' import { withClient, Client } from 'cozy-client' -import { Konnector } from 'doctypes' +import { Konnector, Trigger } from 'doctypes' import KonnectorService from 'services/konnectorService' import KonnectorStatusService from 'services/konnectorStatusService' @@ -20,7 +20,8 @@ const KonnectorViewer: React.FC<KonnectorViewerProps> = ({ isParam = false, }: KonnectorViewerProps) => { const [konnector, setKonnector] = useState<Konnector | null>(null) - const [lastTrigger, setLastTrigger] = useState<any>(null) + const [lastTrigger, setLastTrigger] = useState<Trigger | null>(null) + const [loaded, setLoaded] = useState<boolean>(false) useEffect(() => { let subscribed = true @@ -32,31 +33,29 @@ const KonnectorViewer: React.FC<KonnectorViewerProps> = ({ async function getData() { const _konnector: Konnector = await konnectorService.fetchKonnector() - if (!_konnector || !_konnector.slug) { - throw new Error( - `Could not find konnector for ${fluidConfig.konnectorConfig.slug}` - ) - } if (subscribed && _konnector) { setKonnector(_konnector) - } - - const allTriggers = await kss.getAllTriggers() - if (allTriggers) { - const lastTrigger = allTriggers - .filter( - trigger => - trigger.worker === 'konnector' && - trigger.message.konnector === fluidConfig.konnectorConfig.slug - ) - .sort((a, b) => (new Date(a) > new Date(b) ? 1 : -1)) - .shift() - if (subscribed && lastTrigger) { - setLastTrigger(lastTrigger) + const allTriggers = await kss.getAllTriggers() + if (allTriggers) { + const lastTrigger = allTriggers + .filter( + (trigger: Trigger) => + trigger.worker === 'konnector' && + trigger.message.konnector === fluidConfig.konnectorConfig.slug + ) + .sort((a: string | number | Date, b: string | number | Date) => + new Date(a) > new Date(b) ? 1 : -1 + ) + .shift() + if (subscribed && lastTrigger) { + setLastTrigger(lastTrigger) + } } } + if (subscribed) { + setLoaded(true) + } } - getData() return () => { subscribed = false @@ -65,7 +64,7 @@ const KonnectorViewer: React.FC<KonnectorViewerProps> = ({ return ( <> - {!konnector ? null : ( + {!loaded ? null : ( <KonnectorViewerCard fluidConfig={fluidConfig} konnector={konnector} diff --git a/src/components/ContentComponents/KonnectorViewer/KonnectorViewerCard copy.tsx b/src/components/ContentComponents/KonnectorViewer/KonnectorViewerCard copy.tsx new file mode 100644 index 0000000000000000000000000000000000000000..5a196ab0d35e78ab2f0507750c083b9d339e5dbf --- /dev/null +++ b/src/components/ContentComponents/KonnectorViewer/KonnectorViewerCard copy.tsx @@ -0,0 +1,342 @@ +import React, { useState, useEffect, useRef, useContext } from 'react' +import { ConnectionService } from 'services/connectionService' +import { TriggerService } from 'services/triggersService' +import { AccountService } from 'services/accountService' + +import { AppContext } from 'components/Contexts/AppContextProvider' + +import { JobService, JobState } from 'services/jobsService' +import { FluidType } from 'enum/fluid.enum' + +import { withClient, Client } from 'cozy-client' +import { translate } from 'cozy-ui/react/I18n' + +import { getPicto, getAddPicto, getFuildType, getParamPicto } from 'utils/utils' +import chevronDown from 'assets/icons/ico/chevron-down.svg' +import chevronUp from 'assets/icons/ico/chevron-up.svg' + +import StyledIcon from 'components/CommonKit/Icon/StyledIcon' +import StyledIconButton from 'components/CommonKit/IconButton/StyledIconButton' + +import failurePicto from 'components/ContentComponents/KonnectorViewer/picto-failure.png' +import KonnectorStatusService from 'services/konnectorStatusService' +import IFluidConfig from 'services/IFluidConfig' + +import KonnectorForm from 'components/ContentComponents/Konnector/KonnectorForm' +import KonnectorOAuthForm from 'components/ContentComponents/Konnector/KonnectorOAuthForm' +import KonnectorLoading from 'components/ContentComponents/Konnector/KonnectorLoading' +import KonnectorResult from 'components/ContentComponents/Konnector/KonnectorResult' + +import { Konnector, Trigger } from 'doctypes' + +interface KonnectorViewerCardProps { + fluidConfig: IFluidConfig + konnector: Konnector + trigger: any | null + client: Client + isParam: boolean + t: Function +} + +const KonnectorViewerCard: React.FC<KonnectorViewerCardProps> = ({ + fluidConfig, + konnector, + trigger, + client, + isParam, + t, +}: KonnectorViewerCardProps) => { + const [login, setLogin] = useState<string>('') + const [password, setPassword] = useState<string>('') + const [loading, setLoading] = useState<boolean>(false) + const [updating, setUpdating] = useState<boolean>(false) + const [konnectorAccountId, setAccountId] = useState<string>('') + const [isKonnectorAcc, setAccount] = useState<boolean>(false) + const [setActive, setActiveState] = useState('') + const [setHeight, setHeightState] = useState('0px') + const [updateTrigger, setUpdateTrigger] = useState<any>(null) + const [updateDate, setUpdateDate] = useState<string>( + new Date('0001-01-01T00:00:00Z').toDateString() + ) + // const [frequency, setFrequency] = useState<string>('daily') + const [error, setError] = useState<string>('') + const [jobState, setJobState] = useState<string>('') + const [launchedJob, setlaunchedJob] = useState<any>(null) + const content: React.MutableRefObject<null> = useRef<null>(null) + const type: string = fluidConfig.konnectorConfig.type + const fluid: FluidType = getFuildType(type) + const iconType = getPicto(fluid) + const context = useContext(AppContext) + + const iconAddType = isParam ? getParamPicto(fluid) : getAddPicto(fluid) + + const toggleAccordion = () => { + setActiveState(setActive === '' ? 'active' : '') + setHeightState( + setActive === 'active' && + content && + content.current && + content.current.scrollHeight + ? '0px' + : `${content.current.scrollHeight}px` + ) + } + + const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => { + try { + e.preventDefault() + setError('') + setLoading(true) + if (!login || !password) { + setError('Please enter a login and password') + setLoading(false) + return null + } + setJobState(JobState.Running) + const connectionService = new ConnectionService( + client, + fluidConfig.konnectorConfig.slug, + login, + password + ) + const data = await connectionService.connectNewUser() + setLoading(false) + if (!data) { + setError('Error during the user connection') + setLoading(false) + setJobState(JobState.Errored) + return null + } + trigger = await KonnectorStatusService.getSingleTrigger( + client, + data.trigger_id + ) + setUpdateTrigger(trigger) + setlaunchedJob(data) + setUpdateDate(new Date(data.queued_at).toDateString()) + setAccountId(data.message.account) + setError('Connected') + setAccount(true) + return data + } catch (error) { + setLoading(false) + setError(error.message) + } + } + + const deleteAccount = async () => { + try { + const account = await AccountService.getAccount( + client, + konnectorAccountId + ) + if (!account) { + setError('Error fetching account') + setLoading(false) + } + const delAccount = await AccountService.deleteAccount(client, account) + if (!delAccount) { + setError('Error during deletion account') + setLoading(false) + } + setError('') + setJobState('') + toggleAccordion() + trigger = null + setAccount(false) + await context.refreshFluidTypes() + } catch (error) { + setLoading(false) + setError(error.message) + } + } + + const updateKonnector = async () => { + setError('') + setJobState('') + setUpdating(true) + try { + const account = await AccountService.getAccount( + client, + konnectorAccountId + ) + const triggersServices = new TriggerService(client, account, konnector) + if (updateTrigger !== null) { + triggersServices.setTrigger(updateTrigger) + } else { + triggersServices.setTrigger(trigger) + } + const job = await triggersServices.launchTrigger() + if (!job) { + throw new Error(`Error during trigger launching`) + } + setlaunchedJob(job) + setUpdateDate(new Date(job.queued_at).toDateString()) + } catch (error) { + setUpdating(false) + setError(error.message) + } + } + + const initOauthAccount = async (oauthAccountId: string) => { + setError('') + setJobState('') + setLoading(true) + try { + const account = await AccountService.getAccount(client, oauthAccountId) + const triggersServices = new TriggerService(client, account, konnector) + const trigger: Trigger = await triggersServices.createTrigger() + if (!trigger) { + throw new Error(`Error during trigger creation`) + } + //Launch the creation trigger + const job = await triggersServices.launchTrigger() + if (!job) { + throw new Error(`Error during trigger launching`) + } + setUpdateTrigger(trigger) + setlaunchedJob(job) + setUpdateDate(new Date(job.queued_at).toDateString()) + setAccountId(job.message.account) + setError('Connected') + setAccount(true) + } catch (error) { + setLoading(false) + setError(error.message) + } + } + + const getIcon = (jobState: string) => { + switch (jobState) { + case JobState.Errored: + return failurePicto + // case JobState.Running: + // return pendingPicto + // case JobState.Done: + // return successPicto + default: + return '' + } + } + + const getKonnectorStateMarkup = (jobState: string) => { + const iconSrc = getIcon(jobState) + if (iconSrc === '') return '' + return <img className="state-icon" src={iconSrc}></img> + } + + const jobStateCallBack = async (state: string) => { + setJobState(state) + if (state !== JobState.Running) { + setLoading(false) + setUpdating(false) + setJobState(state) + context.refreshFluidTypes() + } + } + + useEffect(() => { + let subscribed = true + async function getTriggerData() { + if (trigger || launchedJob) { + const jobService = new JobService(client) + let runningJob = null + + if (trigger) { + let triggerState = null + triggerState = TriggerService.fetchStateFromTrigger(trigger) + const konnectorAcc = TriggerService.fetchKonnectorAccountFromTrigger( + trigger + ) + if (subscribed && konnectorAcc.konnector === konnector.slug) { + setAccountId(konnectorAcc.account) + setAccount(true) + } + if (subscribed && !launchedJob) setJobState(triggerState.status) + if (triggerState.status === JobState.Running) + runningJob = jobService.fetchJob(triggerState.last_executed_job_id) + } + if (launchedJob) runningJob = launchedJob + if (runningJob) jobService.watch(runningJob, jobStateCallBack) + } + } + getTriggerData() + return () => { + subscribed = false + } + }, [trigger, launchedJob]) + + return ( + <> + <div className={`accordion ${setActive}`}> + <div> + <div + className={`accordion-header ${setActive}`} + onClick={toggleAccordion} + > + <div className="accordion-icon"> + {!isKonnectorAcc ? ( + <StyledIcon className="icon" icon={iconAddType} size={49} /> + ) : ( + <StyledIcon className="icon" icon={iconType} size={49} /> + )} + </div> + <div className="state-picto"> + {getKonnectorStateMarkup(jobState)} + </div> + <div className="accordion-info"> + <div className="accordion-title text-16-normal"> + {!isKonnectorAcc + ? t('KONNECTORCONFIG.LABEL_CONNECTTO_' + FluidType[fluid]) + : t('FLUID.' + FluidType[fluid] + '.LABEL')} + </div> + </div> + <StyledIconButton icon={setActive ? chevronUp : chevronDown} /> + </div> + <div + ref={content} + style={{ maxHeight: `${setHeight}` }} + className={`accordion-content ${setActive}`} + > + {!isKonnectorAcc ? ( + !fluidConfig.konnectorConfig.oauth ? ( + <KonnectorForm + fluidConfig={fluidConfig} + login={login} + setLogin={setLogin} + password={password} + setPassword={setPassword} + loading={loading} + error={error} + handleSubmit={handleSubmit} + /> + ) : ( + <KonnectorOAuthForm + konnector={konnector} + siteLink={fluidConfig.siteLink} + onSuccess={initOauthAccount} + loading={loading} + /> + ) + ) : loading ? ( + <KonnectorLoading /> + ) : ( + <KonnectorResult + date={ + trigger + ? TriggerService.fetchDateFromTrigger(trigger) + : updateDate + } + updating={updating} + errored={jobState === JobState.Errored} + updateKonnector={updateKonnector} + deleteAccount={deleteAccount} + /> + )} + </div> + </div> + </div> + </> + ) +} +export default translate()(withClient(KonnectorViewerCard)) diff --git a/src/components/ContentComponents/KonnectorViewer/KonnectorViewerCard.tsx b/src/components/ContentComponents/KonnectorViewer/KonnectorViewerCard.tsx index 5a196ab0d35e78ab2f0507750c083b9d339e5dbf..37e3b72c28a5ffa4ae850920c8a4fbec7f82e807 100644 --- a/src/components/ContentComponents/KonnectorViewer/KonnectorViewerCard.tsx +++ b/src/components/ContentComponents/KonnectorViewer/KonnectorViewerCard.tsx @@ -22,6 +22,7 @@ import failurePicto from 'components/ContentComponents/KonnectorViewer/picto-fai import KonnectorStatusService from 'services/konnectorStatusService' import IFluidConfig from 'services/IFluidConfig' +import KonnectorNotFound from 'components/ContentComponents/Konnector/KonnectorNotFound' import KonnectorForm from 'components/ContentComponents/Konnector/KonnectorForm' import KonnectorOAuthForm from 'components/ContentComponents/Konnector/KonnectorOAuthForm' import KonnectorLoading from 'components/ContentComponents/Konnector/KonnectorLoading' @@ -32,7 +33,7 @@ import { Konnector, Trigger } from 'doctypes' interface KonnectorViewerCardProps { fluidConfig: IFluidConfig konnector: Konnector - trigger: any | null + trigger: Trigger | null client: Client isParam: boolean t: Function @@ -54,7 +55,7 @@ const KonnectorViewerCard: React.FC<KonnectorViewerCardProps> = ({ const [isKonnectorAcc, setAccount] = useState<boolean>(false) const [setActive, setActiveState] = useState('') const [setHeight, setHeightState] = useState('0px') - const [updateTrigger, setUpdateTrigger] = useState<any>(null) + const [updateTrigger, setUpdateTrigger] = useState<Trigger | null>(null) const [updateDate, setUpdateDate] = useState<string>( new Date('0001-01-01T00:00:00Z').toDateString() ) @@ -210,10 +211,6 @@ const KonnectorViewerCard: React.FC<KonnectorViewerCardProps> = ({ switch (jobState) { case JobState.Errored: return failurePicto - // case JobState.Running: - // return pendingPicto - // case JobState.Done: - // return successPicto default: return '' } @@ -238,7 +235,7 @@ const KonnectorViewerCard: React.FC<KonnectorViewerCardProps> = ({ useEffect(() => { let subscribed = true async function getTriggerData() { - if (trigger || launchedJob) { + if (konnector && (trigger || launchedJob)) { const jobService = new JobService(client) let runningJob = null @@ -293,12 +290,15 @@ const KonnectorViewerCard: React.FC<KonnectorViewerCardProps> = ({ </div> <StyledIconButton icon={setActive ? chevronUp : chevronDown} /> </div> + <div ref={content} style={{ maxHeight: `${setHeight}` }} className={`accordion-content ${setActive}`} > - {!isKonnectorAcc ? ( + {!konnector ? ( + <KonnectorNotFound /> + ) : !isKonnectorAcc ? ( !fluidConfig.konnectorConfig.oauth ? ( <KonnectorForm fluidConfig={fluidConfig} diff --git a/src/doctypes/io-cozy-triggers.ts b/src/doctypes/io-cozy-triggers.ts index 1366e7b53412a70a493b260d4ad9ad5fb5b1a5c6..9eb41891215ed36b3da3be7a7621cf074c66aa9b 100644 --- a/src/doctypes/io-cozy-triggers.ts +++ b/src/doctypes/io-cozy-triggers.ts @@ -3,7 +3,7 @@ export const TRIGGERS_DOCTYPE = 'io.cozy.triggers' export type Trigger = { _id: string type: string - workker: string + worker: string arguments: string message: { account: string @@ -16,7 +16,7 @@ export function isTrigger(trigger: any): trigger is Trigger { trigger && '_id' in trigger && 'type' in trigger && - 'workker' in trigger && + 'worker' in trigger && 'arguments' in trigger && 'message' in trigger ) diff --git a/src/locales/en.json b/src/locales/en.json index a727191ac90413326db0ff9b0ced3287f3224c57..27b218e1528aad2f9cb9879a2225a9d06420cf3e 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -121,7 +121,8 @@ "BTN_UPDATE": "Mettre à jour", "BTN_DELETE": "Supprimer", "LOADING_DATA": "Vos premières données seront disponibles dans quelques minutes et les prochaines données seront chargées automatiquement.", - "PLZ_WAIT": "Veuillez patienter..." + "PLZ_WAIT": "Veuillez patienter...", + "NOT_INSTALLED": "Le connecteur n'est pas installer. Veuillez l'installer en cliquant sur le bouton ci-dessous." }, "INDICATOR": { "DISPLAY_OTHER_FLUID": "Voir", diff --git a/src/styles/components/_konnector.scss b/src/styles/components/_konnector.scss index f12600f8c40f89692e245945242f78f4f827865b..001e97d751ea562371697a0b4a342fb7c5af4b45 100644 --- a/src/styles/components/_konnector.scss +++ b/src/styles/components/_konnector.scss @@ -111,6 +111,21 @@ } } +// KonnectorNotFound +.knotfound{ + margin: 0 1.5rem; + @media #{$large-phone} { + margin: 0; + } + .knotfound-text{ + color: $text-bright; + padding-top: 1rem; + } + .knotfound-button{ + margin-bottom: 1rem; + } +} + // KonnectorOAuthForm .koauthform{ margin: 0 1.5rem;