feat(scaffolder): do real fetch
This commit is contained in:
@@ -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;
|
||||
};
|
||||
Reference in New Issue
Block a user