Add TaskStep component

This commit is contained in:
Johan Haals
2021-02-10 14:00:51 +01:00
parent 3796a666e0
commit efbd9c9a99
2 changed files with 101 additions and 54 deletions
@@ -15,7 +15,7 @@
*/
import { Page, Header, Lifecycle, Content } from '@backstage/core';
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, memo } from 'react';
import {
makeStyles,
Theme,
@@ -34,7 +34,11 @@ import Check from '@material-ui/icons/Check';
import Cancel from '@material-ui/icons/Cancel';
import Typography from '@material-ui/core/Typography';
import { useParams } from 'react-router';
import { useTaskEventStream, TaskStream } from '../hooks/useEventStream';
import {
useTaskEventStream,
TaskStream,
Status,
} from '../hooks/useEventStream';
import LazyLog from 'react-lazylog/build/LazyLog';
import { StepButton, StepIconProps } from '@material-ui/core';
@@ -130,36 +134,29 @@ const useStyles = makeStyles((theme: Theme) =>
}),
);
export const TaskStepper = ({ taskStream }: { taskStream: TaskStream }) => {
type Steps = {
log: string[];
id: string;
name: string;
status: Status;
};
export const TaskStepper = ({ steps }: { steps: Steps[] }) => {
const classes = useStyles();
const [activeStep, setActiveStep] = useState(0);
const [expandAll, setExpandAll] = useState(false);
const steps = taskStream?.task?.spec.steps ?? [];
const handleNext = () => {
setActiveStep(prevActiveStep => prevActiveStep + 1);
};
const handleBack = () => {
setActiveStep(prevActiveStep => prevActiveStep - 1);
};
const handleStep = (step: number) => {
setExpandAll(false);
setActiveStep(step);
};
const handleReset = () => {
setActiveStep(0);
};
useEffect(() => {
const activeIndex = Object.values(taskStream?.steps ?? {}).findIndex(step =>
const activeIndex = steps.findIndex(step =>
['failed', 'processing'].includes(step.status),
);
setActiveStep(activeIndex);
}, [taskStream]);
setActiveStep(2);
}, [steps]);
return (
<div className={classes.root}>
@@ -170,12 +167,24 @@ export const TaskStepper = ({ taskStream }: { taskStream: TaskStream }) => {
<Button variant="outlined">Raw Log</Button>
<Stepper activeStep={activeStep} orientation="vertical" nonLinear>
{steps.map((step, index) => {
const isCompleted =
taskStream.steps?.[step.id].status === 'completed';
const isFailed = taskStream.steps?.[step.id].status === 'failed';
const isCompleted = step.status === 'completed';
const isFailed = step.status === 'failed';
// return (
// <TaskStep
// key={String(index)}
// log={step.log.length ? step.log.join('\n') : 'please wait'}
// expanded={expandAll}
// name={step.name}
// isFailed={isFailed}
// isCompleted={isCompleted}
// handleStep={handleStep}
// index={index}
// />
// );
return (
<Step key={String(index)} expanded={expandAll}>
<StepButton onClick={() => handleStep(index)}>
<Step key={String(index)} expanded>
<StepButton onClick={() => {}}>
<StepLabel
StepIconProps={{ completed: isCompleted, error: isFailed }}
StepIconComponent={QontoStepIcon}
@@ -188,11 +197,7 @@ export const TaskStepper = ({ taskStream }: { taskStream: TaskStream }) => {
<div style={{ height: '50vh' }}>
<LazyLog
extraLines={1}
text={
taskStream.steps?.[step.id]?.log.length
? taskStream.steps?.[step.id]?.log.join('\n')
: 'fetching...'
}
text={step.log.length ? step.log.join('\n') : 'please wait'}
/>
</div>
</StepContent>
@@ -200,21 +205,62 @@ export const TaskStepper = ({ taskStream }: { taskStream: TaskStream }) => {
);
})}
</Stepper>
{activeStep === steps.length && (
<Paper square elevation={0} className={classes.resetContainer}>
<Typography>All steps completed - you&apos;re finished</Typography>
<Button onClick={handleReset} className={classes.button}>
Reset
</Button>
</Paper>
)}
</div>
);
};
type TaskStepOptions = {
name: string;
isCompleted: boolean;
isFailed: boolean;
expanded: boolean;
handleStep: () => void;
index: number;
log: string;
};
export const TaskStep = memo(
({
log,
name,
isCompleted,
isFailed,
handleStep,
index,
expanded,
}: TaskStepOptions) => {
const onClick = React.useCallback(() => handleStep(index), [
handleStep,
index,
]);
return (
<Step key={String(index)} expanded={expanded}>
<StepButton onClick={onClick}>
<StepLabel
StepIconProps={{ completed: isCompleted, error: isFailed }}
StepIconComponent={QontoStepIcon}
>
{name}
</StepLabel>
</StepButton>
<StepContent>
<div style={{ height: '50vh' }}>
<LazyLog extraLines={1} text={log} />
</div>
</StepContent>
</Step>
);
},
);
export const TaskPage = () => {
const { taskId } = useParams();
const taskStream = useTaskEventStream(taskId);
const steps =
taskStream.task?.spec.steps.map(step => ({
...step,
...taskStream?.steps?.[step.id],
})) ?? [];
return (
<Page themeId="home">
@@ -228,7 +274,7 @@ export const TaskPage = () => {
subtitle={`Activity for task: ${taskId}`}
/>
<Content>
<TaskStepper taskStream={taskStream} />
<TaskStepper steps={steps} />
</Content>
</Page>
);
@@ -14,16 +14,16 @@
* limitations under the License.
*/
import { useImmerReducer } from 'use-immer';
import { useEffect } from 'react';
import { useEffect, useState } from 'react';
import { scaffolderApiRef } from '../../api';
import { ScaffolderTask } from '../../types';
import { useDebounce } from 'react-use';
import { Subscription, useApi } from '@backstage/core';
type Status = 'open' | 'processing' | 'failed' | 'completed';
export type Status = 'open' | 'processing' | 'failed' | 'completed';
type Step = {
id: string;
status: Status;
log: string[];
endedAt?: string;
startedAt?: string;
};
@@ -34,7 +34,7 @@ export type TaskStream = {
log: string[];
completed: boolean;
task?: ScaffolderTask;
steps?: { [stepId in string]: Step };
steps: { [stepId in string]: Step };
};
type ReducerAction =
@@ -56,7 +56,7 @@ function reducer(draft: TaskStream, action: ReducerAction) {
switch (action.type) {
case 'INIT': {
draft.steps = action.data.spec.steps.reduce((current, next) => {
current[next.id] = { log: [], status: 'open', id: next.id };
current[next.id] = { status: 'open', id: next.id };
return current;
}, {} as { [stepId in string]: Step });
draft.loading = false;
@@ -72,12 +72,7 @@ function reducer(draft: TaskStream, action: ReducerAction) {
const currentStep = draft.steps?.[stepId];
const logLine = `${action.data.createdAt} ${action.data.body.message}`;
if (!currentStep) {
draft.log.push(logLine);
return;
}
currentStep.log.push(logLine);
draft.log.push(logLine);
if (
action.data.body.status &&
@@ -89,9 +84,7 @@ function reducer(draft: TaskStream, action: ReducerAction) {
currentStep.startedAt = action.data.createdAt;
}
if (
['zcancelled', 'failed', 'completed'].includes(currentStep.status)
) {
if (['cancelled', 'failed', 'completed'].includes(currentStep.status)) {
currentStep.endedAt = action.data.createdAt;
}
}
@@ -124,7 +117,14 @@ export const useTaskEventStream = (taskId: string): TaskStream => {
log: [],
steps: {} as { [stepId in string]: Step },
});
// const [debouncedState, setDebouncedState] = useState(state);
// useDebounce(
// () => {
// setDebouncedState(state);
// },
// 1000,
// [state],
// );
useEffect(() => {
let didCancel = false;
let subscription: Subscription | undefined;
@@ -167,4 +167,5 @@ export const useTaskEventStream = (taskId: string): TaskStream => {
}, [scaffolderApi, dispatch, taskId]);
return state;
// return debouncedState;
};