From 01b3e44429d9aab6125b2b93eaafa4340e2cfd88 Mon Sep 17 00:00:00 2001 From: Ivan Shmidt Date: Mon, 29 Jun 2020 14:02:51 +0200 Subject: [PATCH] feat(scaffolder): do real fetch --- plugins/scaffolder/src/api.ts | 9 ++- .../components/JobStatusModal/JobStage.tsx | 27 ++++--- .../JobStatusModal/JobStatusModal.tsx | 7 +- .../src/components/JobStatusModal/jobMocks.ts | 78 ------------------- .../JobStatusModal/useJobPolling.ts | 41 ++++++++++ 5 files changed, 66 insertions(+), 96 deletions(-) delete mode 100644 plugins/scaffolder/src/components/JobStatusModal/jobMocks.ts create mode 100644 plugins/scaffolder/src/components/JobStatusModal/useJobPolling.ts diff --git a/plugins/scaffolder/src/api.ts b/plugins/scaffolder/src/api.ts index f87285a80c..04588d7177 100644 --- a/plugins/scaffolder/src/api.ts +++ b/plugins/scaffolder/src/api.ts @@ -42,11 +42,18 @@ export class ScaffolderApi { values: Record, ) { const url = `${this.apiOrigin}${this.basePath}/jobs`; - const jobId = await fetch(url, { + const { id: jobId } = await fetch(url, { method: 'POST', body: JSON.stringify({ template, values }), }).then(x => x.json()); return jobId; } + + async getJob(jobId: string) { + const url = `${this.apiOrigin}${this.basePath}/job/${encodeURIComponent( + jobId, + )}`; + return fetch(url).then(x => x.json()); + } } diff --git a/plugins/scaffolder/src/components/JobStatusModal/JobStage.tsx b/plugins/scaffolder/src/components/JobStatusModal/JobStage.tsx index f1dec09f88..ef799d13b4 100644 --- a/plugins/scaffolder/src/components/JobStatusModal/JobStage.tsx +++ b/plugins/scaffolder/src/components/JobStatusModal/JobStage.tsx @@ -87,16 +87,10 @@ type Props = { className?: string; log: string[]; startedAt: string; - finishedAt?: string; + endedAt?: string; status?: Job['status']; }; -export const JobStage = ({ - finishedAt, - startedAt, - name, - log, - status, -}: Props) => { +export const JobStage = ({ endedAt, startedAt, name, log, status }: Props) => { const classes = useStyles(); const [expanded, setExpanded] = useState(false); @@ -104,18 +98,23 @@ export const JobStage = ({ if (status === 'FAILED') setExpanded(true); }, [status === 'FAILED', setExpanded]); - const timeElapsed = moment - .duration(moment(finishedAt ?? moment()).diff(moment(startedAt))) - .humanize(); + const timeElapsed = + status !== 'PENDING' + ? moment + .duration(moment(endedAt ?? moment()).diff(moment(startedAt))) + .humanize() + : null; return ( - {name} ({timeElapsed}) + {name} {timeElapsed && `(${timeElapsed})`} diff --git a/plugins/scaffolder/src/components/JobStatusModal/JobStatusModal.tsx b/plugins/scaffolder/src/components/JobStatusModal/JobStatusModal.tsx index f946df1bf3..c589e01127 100644 --- a/plugins/scaffolder/src/components/JobStatusModal/JobStatusModal.tsx +++ b/plugins/scaffolder/src/components/JobStatusModal/JobStatusModal.tsx @@ -6,7 +6,7 @@ import { DialogContent, } from '@material-ui/core'; import { JobStage } from './JobStage'; -import { useJob } from './jobMocks'; +import { useJobPolling } from './useJobPolling'; type Props = { onClose: () => void; @@ -14,7 +14,8 @@ type Props = { }; export const JobStatusModal = ({ onClose, jobId }: Props) => { - const job = useJob(jobId); + console.log({ jobId }); + const job = useJobPolling(jobId); return ( @@ -30,7 +31,7 @@ export const JobStatusModal = ({ onClose, jobId }: Props) => { name={step.name} key={step.name} startedAt={step.startedAt} - finishedAt={step.finishedAt} + endedAt={step.endedAt} status={step.status} /> )) diff --git a/plugins/scaffolder/src/components/JobStatusModal/jobMocks.ts b/plugins/scaffolder/src/components/JobStatusModal/jobMocks.ts deleted file mode 100644 index 08a3d3b720..0000000000 --- a/plugins/scaffolder/src/components/JobStatusModal/jobMocks.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { useMemo, useState, useEffect } from 'react'; -import { Job } from './types'; - -function* emulatePoll() { - const now = () => new Date().toString(); - const job: Job = { - id: '132536-42362-4253532', - metadata: { entity: {}, values: {} }, - status: 'STARTED', - stages: [ - { - name: 'created', - startedAt: now(), - log: [ - 'Job id #rw-tstywe-tdsy was successfully created and placed in the queue', - ], - status: 'STARTED', - }, - ], - }; - let newTime = now(); - job.stages[0].finishedAt = newTime; - job.stages.push({ - startedAt: newTime, - name: 'preparing', - log: ['preparing blahblah', 'some other stuuff'], - status: 'COMPLETE', - }); - yield job; - - newTime = now(); - job.stages[1].finishedAt = newTime; - job.stages.push({ - startedAt: newTime, - name: 'templating', - log: ['templating blahblah', 'some other stuuff'], - status: 'COMPLETE', - }); - yield job; - - newTime = now(); - job.stages[2].finishedAt = newTime; - job.stages.push({ - startedAt: newTime, - name: 'pushing', - log: ['pushing blahblah', 'some other stuuff'], - status: 'STARTED', - }); - yield job; - yield job; - job.stages[3].status = 'FAILED'; - job.stages[3].log.push('ERROR OCCURED'); - - while (true) yield job; -} - -export const useJob = (jobId: string | null) => { - const apiMock = useMemo(() => emulatePoll(), [jobId]); - const [job, setJob] = useState(undefined); - useEffect(() => { - if (!jobId) return; - const nextJobState = apiMock.next().value as Job; - setJob({ ...nextJobState }); - const intervalId = setInterval(() => { - const nextJobState = apiMock.next().value as Job; - - if (nextJobState?.status === 'FAILED') { - clearInterval(intervalId); - } - - setJob({ ...nextJobState }); - }, 3000); - return () => { - clearInterval(intervalId); - }; - }, [jobId, setJob]); - return job; -}; diff --git a/plugins/scaffolder/src/components/JobStatusModal/useJobPolling.ts b/plugins/scaffolder/src/components/JobStatusModal/useJobPolling.ts new file mode 100644 index 0000000000..89b7228a9c --- /dev/null +++ b/plugins/scaffolder/src/components/JobStatusModal/useJobPolling.ts @@ -0,0 +1,41 @@ +import { useState, useEffect } from 'react'; +import { Job } from './types'; +import { useApi } from '@backstage/core'; +import { scaffolderApiRef } from '../../api'; + +const poll = (thunk: () => Promise, ms: number) => { + let shouldStop = false; + (async () => { + while (!shouldStop) { + await thunk(); + await new Promise(res => setTimeout(res, ms)); + } + })(); + + return () => { + shouldStop = true; + }; +}; + +export const useJobPolling = (jobId: string | null) => { + const scaffolderApi = useApi(scaffolderApiRef); + + const [job, setJob] = useState(undefined); + useEffect(() => { + if (!jobId) return; + const stopPolling = poll(async () => { + const nextJobState = await scaffolderApi.getJob(jobId); + if ( + nextJobState.status === 'FAILED' || + nextJobState.status === 'COMPLETED' + ) { + stopPolling(); + } + setJob(nextJobState); + }, 500); + return () => { + stopPolling(); + }; + }, [jobId, setJob]); + return job; +};