feat(scaffolder): do real fetch

This commit is contained in:
Ivan Shmidt
2020-06-29 14:02:51 +02:00
parent 3f3409ac4c
commit f937602641
5 changed files with 66 additions and 96 deletions
+8 -1
View File
@@ -42,11 +42,18 @@ export class ScaffolderApi {
values: Record<string, any>,
) {
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());
}
}
@@ -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 (
<ExpansionPanel
TransitionProps={{ unmountOnExit: true }}
className={
status === 'COMPLETE'
? classes.success
status === 'STARTED'
? classes.running
: status === 'FAILED'
? classes.failed
: status === 'COMPLETED'
? classes.success
: classes.neutral
}
expanded={expanded}
@@ -130,7 +129,7 @@ export const JobStage = ({
}}
>
<Typography variant="button">
{name} ({timeElapsed})
{name} {timeElapsed && `(${timeElapsed})`}
</Typography>
</ExpansionPanelSummary>
<ExpansionPanelDetails className={classes.expansionPanelDetails}>
@@ -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 (
<Dialog open onClose={onClose} fullWidth>
<DialogTitle id="responsive-dialog-title">
@@ -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}
/>
))
@@ -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<Job | void>(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;
};
@@ -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<void>, 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<Job | void>(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;
};