Separate logs

This commit is contained in:
Johan Haals
2021-02-10 16:16:56 +01:00
parent 2876700adb
commit 2b15455d2b
2 changed files with 96 additions and 54 deletions
@@ -15,7 +15,7 @@
*/
import { Page, Header, Lifecycle, Content } from '@backstage/core';
import React, { useState, useEffect, memo } from 'react';
import React, { useState, useEffect, memo, useMemo } from 'react';
import {
makeStyles,
Theme,
@@ -62,52 +62,52 @@ const useStyles = makeStyles((theme: Theme) =>
);
type Steps = {
log: string[];
id: string;
name: string;
status: Status;
};
export const TaskStatusStepper = memo(({ steps }: { steps: Steps[] }) => {
const classes = useStyles();
const [activeStep, setActiveStep] = useState(0);
export const TaskStatusStepper = memo(
({
steps,
currentStepId,
onUserStepChange,
}: {
steps: Steps[];
currentStepId: string | undefined;
onUserStepChange: (id: string) => void;
}) => {
const classes = useStyles();
const handleStep = (step: number) => {
setActiveStep(step);
};
useEffect(() => {
const activeIndex = steps.findIndex(step =>
['failed', 'processing'].includes(step.status),
return (
<div className={classes.root}>
<Stepper
activeStep={steps.findIndex(s => s.id === currentStepId)}
orientation="vertical"
nonLinear
>
{steps.map((step, index) => {
const isCompleted = step.status === 'completed';
const isFailed = step.status === 'failed';
return (
<Step key={String(index)} expanded>
<StepButton onClick={() => onUserStepChange(step.id)}>
<StepLabel
StepIconProps={{ completed: isCompleted, error: isFailed }}
>
{step.name}
</StepLabel>
</StepButton>
</Step>
);
})}
</Stepper>
</div>
);
setActiveStep(activeIndex);
}, [steps]);
return (
<div className={classes.root}>
<Stepper activeStep={activeStep} orientation="vertical" nonLinear>
{steps.map((step, index) => {
const isCompleted = step.status === 'completed';
const isFailed = step.status === 'failed';
return (
<Step key={String(index)} expanded>
<StepButton onClick={() => {}}>
<StepLabel
StepIconProps={{ completed: isCompleted, error: isFailed }}
>
{step.name}
</StepLabel>
</StepButton>
</Step>
);
})}
</Stepper>
</div>
);
});
},
);
const TaskLogger = memo(({ log }: { log: string }) => {
console.log('rendering my log');
return (
<div style={{ height: '80vh' }}>
<LazyLog text={log} extraLines={1} follow />
@@ -125,13 +125,43 @@ const TaskActionsBar = () => {
};
export const TaskPage = () => {
const [userSelectedStepId, setUserSelectedStepId] = useState<
string | undefined
>(undefined);
const [lastActiveStepId, setLastActiveStepId] = useState<string | undefined>(
undefined,
);
const { taskId } = useParams();
const taskStream = useTaskEventStream(taskId);
const steps =
taskStream.task?.spec.steps.map(step => ({
...step,
...taskStream?.steps?.[step.id],
})) ?? [];
const steps = useMemo(
() =>
taskStream.task?.spec.steps.map(step => ({
...step,
...taskStream?.steps?.[step.id],
})) ?? [],
[taskStream],
);
useEffect(() => {
const activeStep = steps.find(step =>
['failed', 'processing'].includes(step.status),
);
setLastActiveStepId(activeStep?.id);
}, [steps]);
const currentStepId = userSelectedStepId ?? lastActiveStepId;
const logAsString = useMemo(() => {
if (!currentStepId) {
return 'Loading...';
}
const log = taskStream.stepLogs[currentStepId];
if (!log?.length) {
return 'Waiting for logs...';
}
return log.join('\n');
}, [taskStream.stepLogs, currentStepId]);
return (
<Page themeId="home">
@@ -145,17 +175,16 @@ export const TaskPage = () => {
subtitle={`Activity for task: ${taskId}`}
/>
<Content>
<TaskActionsBar />
<Grid container>
<Grid item xs={2}>
<TaskStatusStepper steps={steps} />
<TaskStatusStepper
steps={steps}
currentStepId={currentStepId}
onUserStepChange={setUserSelectedStepId}
/>
</Grid>
<Grid item xs={10}>
<TaskLogger
log={
taskStream.log.length ? taskStream.log.join('\n') : 'loading...'
}
/>
<TaskLogger log={logAsString} />
</Grid>
</Grid>
</Content>
@@ -29,7 +29,7 @@ type Step = {
export type TaskStream = {
loading: boolean;
error?: Error;
log: string[];
stepLogs: { [stepId in string]: string[] };
completed: boolean;
task?: ScaffolderTask;
steps: { [stepId in string]: Step };
@@ -53,17 +53,21 @@ function reducer(draft: TaskStream, action: ReducerAction) {
current[next.id] = { status: 'open', id: next.id };
return current;
}, {} as { [stepId in string]: Step });
draft.stepLogs = action.data.spec.steps.reduce((current, next) => {
current[next.id] = [];
return current;
}, {} as { [stepId in string]: string[] });
draft.loading = false;
draft.error = undefined;
draft.completed = false;
draft.task = action.data;
draft.log = [];
return;
}
case 'LOGS': {
const entries = action.data;
const logLines = [];
for (const entry of entries) {
const logLine = `${entry.createdAt} ${entry.body.message}`;
logLines.push(logLine);
@@ -72,6 +76,7 @@ function reducer(draft: TaskStream, action: ReducerAction) {
continue;
}
const currentStepLog = draft.stepLogs?.[entry.body.stepId];
const currentStep = draft.steps?.[entry.body.stepId];
if (entry.body.status && entry.body.status !== currentStep.status) {
@@ -87,9 +92,10 @@ function reducer(draft: TaskStream, action: ReducerAction) {
currentStep.endedAt = entry.createdAt;
}
}
currentStepLog?.push(logLine);
}
draft.log.push(...logLines);
return;
}
@@ -115,7 +121,7 @@ export const useTaskEventStream = (taskId: string): TaskStream => {
const [state, dispatch] = useImmerReducer(reducer, {
loading: true,
completed: false,
log: [],
stepLogs: {} as { [stepId in string]: string[] },
steps: {} as { [stepId in string]: Step },
});
@@ -130,6 +136,13 @@ export const useTaskEventStream = (taskId: string): TaskStream => {
return;
}
dispatch({ type: 'INIT', data: task });
// TODO(blam): Use a normal fetch to fetch the current log for the event stream
// and use that for an INIT_EVENTs dispatch event, and then
// use the last event ID to subscribe using after option to
// stream logs. Without this, if you have a lot of logs, it can look like the
// task is being rebuilt on load as it progresses through the steps at a slower
// rate whilst it builds the status from the event logs
const observable = scaffolderApi.streamLogs({ taskId });
const collectedLogEvents = new Array<LogEvent>();