Scaffolder: display entity button

Co-authored-by: Fredrik Adelöw <freben@gmail.com>
Co-authored-by: blam<ben@blam.sh>
This commit is contained in:
Johan Haals
2021-02-12 15:13:08 +01:00
parent 9fe687aa37
commit 73760c688f
5 changed files with 62 additions and 34 deletions
@@ -45,11 +45,11 @@ export function registerLegacyActions(
registry.register({
id: 'legacy:prepare',
async handler(ctx) {
ctx.logger.info('Preparing the skeleton');
const { protocol, url } = ctx.parameters;
const preparer =
protocol === 'file' ? new FilePreparer() : preparers.get(url as string);
ctx.logger.info('Prepare the skeleton');
await preparer.prepare({
url: url as string,
logger: ctx.logger,
@@ -61,11 +61,8 @@ export function registerLegacyActions(
registry.register({
id: 'legacy:template',
async handler(ctx) {
const { logger } = ctx;
ctx.logger.info('Running the templater');
const templater = templaters.get(ctx.parameters.templater as string);
logger.info('Run the templater');
await templater.run({
workspacePath: ctx.workspacePath,
dockerClient,
@@ -120,15 +117,14 @@ export function registerLegacyActions(
registry.register({
id: 'catalog:register',
async handler(ctx) {
const { logger } = ctx;
const { catalogInfoUrl } = ctx.parameters; // TODO update schema
const { catalogInfoUrl } = ctx.parameters;
ctx.logger.info(`Registering ${catalogInfoUrl} in the catalog`);
logger.info(`Registering ${catalogInfoUrl} in the catalog`);
const result = await catalogClient.addLocation({
type: 'url',
target: catalogInfoUrl as string,
});
if (result.entities.length === 1) {
if (result.entities.length >= 1) {
const { kind, name, namespace } = getEntityName(result.entities[0]);
ctx.output('entityRef', `${kind}:${namespace}/${name}`);
}
@@ -45,7 +45,7 @@ export class TaskWorker {
async runOneTask(task: Task) {
try {
const { actionRegistry, logger } = this.options;
const { actionRegistry } = this.options;
const workspacePath = path.join(
this.options.workingDirectory,
@@ -90,6 +90,7 @@ export function templateEntityToSpec(
output: {
remoteUrl: '{{ steps.publish.output.remoteUrl }}',
catalogInfoUrl: '{{ steps.publish.output.catalogInfoUrl }}',
entityRef: '{{ steps.register.output.entityRef }}',
},
};
}
@@ -25,7 +25,14 @@ import Typography from '@material-ui/core/Typography';
import { useParams } from 'react-router';
import { useTaskEventStream } from '../hooks/useEventStream';
import LazyLog from 'react-lazylog/build/LazyLog';
import { CircularProgress, StepButton, StepIconProps } from '@material-ui/core';
import {
Box,
Button,
CircularProgress,
Paper,
StepButton,
StepIconProps,
} from '@material-ui/core';
import { Status } from '../../types';
import { DateTime, Interval } from 'luxon';
import { useInterval } from 'react-use';
@@ -33,6 +40,8 @@ import clsx from 'clsx';
import Check from '@material-ui/icons/Check';
import Cancel from '@material-ui/icons/Cancel';
import FiberManualRecordIcon from '@material-ui/icons/FiberManualRecord';
import { EntityRefLink } from '@backstage/plugin-catalog-react';
import { parseEntityName } from '@backstage/catalog-model';
// typings are wrong for this library, so fallback to not parsing types.
const humanizeDuration = require('humanize-duration');
@@ -197,7 +206,7 @@ export const TaskStatusStepper = memo(
const TaskLogger = memo(({ log }: { log: string }) => {
return (
<div style={{ height: '80vh' }}>
<LazyLog text={log} extraLines={1} follow />
<LazyLog text={log} extraLines={1} follow selectableLines enableSearch />
</div>
);
});
@@ -212,7 +221,6 @@ export const TaskPage = () => {
const { taskId } = useParams();
const taskStream = useTaskEventStream(taskId);
const completed = taskStream.completed;
const steps = useMemo(
() =>
taskStream.task?.spec.steps.map(step => ({
@@ -221,17 +229,17 @@ export const TaskPage = () => {
})) ?? [],
[taskStream],
);
useEffect(() => {
const activeStep = steps.find(step =>
const mostRecentFailedOrActiveStep = steps.find(step =>
['failed', 'processing'].includes(step.status),
);
if (completed) {
if (completed && !mostRecentFailedOrActiveStep) {
setLastActiveStepId(steps[steps.length - 1]?.id);
return;
}
setLastActiveStepId(activeStep?.id);
setLastActiveStepId(mostRecentFailedOrActiveStep?.id);
}, [steps, completed]);
const currentStepId = userSelectedStepId ?? lastActiveStepId;
@@ -253,6 +261,7 @@ export const TaskPage = () => {
taskStream.loading === false &&
!taskStream.task;
const entityRef = taskStream.output?.entityRef;
return (
<Page themeId="home">
<Header
@@ -268,18 +277,31 @@ export const TaskPage = () => {
{taskNotFound ? (
<div>Task not found</div>
) : (
<Grid container>
<Grid item xs={3}>
<TaskStatusStepper
steps={steps}
currentStepId={currentStepId}
onUserStepChange={setUserSelectedStepId}
/>
<div>
<Grid container>
<Grid item xs={3}>
<Paper>
<TaskStatusStepper
steps={steps}
currentStepId={currentStepId}
onUserStepChange={setUserSelectedStepId}
/>
{entityRef && (
<Box px={3} pb={3}>
<Button variant="outlined">
<EntityRefLink entityRef={parseEntityName(entityRef)}>
Open in catalog
</EntityRefLink>
</Button>
</Box>
)}
</Paper>
</Grid>
<Grid item xs={9}>
<TaskLogger log={logAsString} />
</Grid>
</Grid>
<Grid item xs={9}>
<TaskLogger log={logAsString} />
</Grid>
</Grid>
</div>
)}
</Content>
</Page>
@@ -26,6 +26,8 @@ type Step = {
startedAt?: string;
};
type TaskOutput = { entityRef?: string } & { [key in string]: string };
export type TaskStream = {
loading: boolean;
error?: Error;
@@ -33,17 +35,23 @@ export type TaskStream = {
completed: boolean;
task?: ScaffolderTask;
steps: { [stepId in string]: Step };
output?: TaskOutput;
};
type ReducerLogEntry = {
createdAt: string;
body: { stepId?: string; status?: Status; message: string };
body: {
stepId?: string;
status?: Status;
message: string;
output?: TaskOutput;
};
};
type ReducerAction =
| { type: 'INIT'; data: ScaffolderTask }
| { type: 'LOGS'; data: ReducerLogEntry[] }
| { type: 'COMPLETED' }
| { type: 'COMPLETED'; data: ReducerLogEntry }
| { type: 'ERROR'; data: Error };
function reducer(draft: TaskStream, action: ReducerAction) {
@@ -101,6 +109,7 @@ function reducer(draft: TaskStream, action: ReducerAction) {
case 'COMPLETED': {
draft.completed = true;
draft.output = action.data.body.output;
return;
}
@@ -164,6 +173,10 @@ export const useTaskEventStream = (taskId: string): TaskStream => {
switch (event.type) {
case 'log':
return collectedLogEvents.push(event);
case 'completion':
emitLogs();
dispatch({ type: 'COMPLETED', data: event });
return undefined;
default:
throw new Error(
`Unhandled event type ${event.type} in observer`,
@@ -174,10 +187,6 @@ export const useTaskEventStream = (taskId: string): TaskStream => {
emitLogs();
dispatch({ type: 'ERROR', data: error });
},
complete: () => {
emitLogs();
dispatch({ type: 'COMPLETED' });
},
});
},
error => {