feat(scaffolder): support updating template inputs of tasks
Signed-off-by: Phil Kuang <pkuang@factset.com>
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder': patch
|
||||
'@backstage/plugin-scaffolder-backend': patch
|
||||
'@backstage/plugin-scaffolder-common': patch
|
||||
---
|
||||
|
||||
Support navigating back to pre-filled templates to update inputs of scaffolder tasks for resubmission
|
||||
@@ -25,7 +25,11 @@ import { Schema } from 'jsonschema';
|
||||
import { ScmIntegrationRegistry } from '@backstage/integration';
|
||||
import { ScmIntegrations } from '@backstage/integration';
|
||||
import { SpawnOptionsWithoutStdio } from 'child_process';
|
||||
import { TaskSpec } from '@backstage/plugin-scaffolder-common';
|
||||
import { TaskSpecV1beta2 } from '@backstage/plugin-scaffolder-common';
|
||||
import { TaskSpecV1beta3 } from '@backstage/plugin-scaffolder-common';
|
||||
import { TemplateEntityV1beta2 } from '@backstage/catalog-model';
|
||||
import { TemplateMetadata } from '@backstage/plugin-scaffolder-common';
|
||||
import { UrlReader } from '@backstage/backend-common';
|
||||
import { Writable } from 'stream';
|
||||
|
||||
@@ -432,52 +436,11 @@ export type TaskSecrets = {
|
||||
token: string | undefined;
|
||||
};
|
||||
|
||||
// @public
|
||||
export type TaskSpec = TaskSpecV1beta2 | TaskSpecV1beta3;
|
||||
export { TaskSpec };
|
||||
|
||||
// @public
|
||||
export interface TaskSpecV1beta2 {
|
||||
// (undocumented)
|
||||
apiVersion: 'backstage.io/v1beta2';
|
||||
// (undocumented)
|
||||
baseUrl?: string;
|
||||
// (undocumented)
|
||||
metadata?: TemplateMetadata;
|
||||
// (undocumented)
|
||||
output: {
|
||||
[name: string]: string;
|
||||
};
|
||||
// (undocumented)
|
||||
steps: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
action: string;
|
||||
input?: JsonObject;
|
||||
if?: string | boolean;
|
||||
}>;
|
||||
// (undocumented)
|
||||
values: JsonObject;
|
||||
}
|
||||
export { TaskSpecV1beta2 };
|
||||
|
||||
// @public
|
||||
export interface TaskSpecV1beta3 {
|
||||
// (undocumented)
|
||||
apiVersion: 'scaffolder.backstage.io/v1beta3';
|
||||
// (undocumented)
|
||||
baseUrl?: string;
|
||||
// (undocumented)
|
||||
metadata?: TemplateMetadata;
|
||||
// (undocumented)
|
||||
output: {
|
||||
[name: string]: JsonValue;
|
||||
};
|
||||
// (undocumented)
|
||||
parameters: JsonObject;
|
||||
// Warning: (ae-forgotten-export) The symbol "TaskStep" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// (undocumented)
|
||||
steps: TaskStep[];
|
||||
}
|
||||
export { TaskSpecV1beta3 };
|
||||
|
||||
// @public
|
||||
export interface TaskState {
|
||||
@@ -573,8 +536,5 @@ export class TemplateActionRegistry {
|
||||
): void;
|
||||
}
|
||||
|
||||
// @public
|
||||
export type TemplateMetadata = {
|
||||
name: string;
|
||||
};
|
||||
export { TemplateMetadata };
|
||||
```
|
||||
|
||||
@@ -15,6 +15,21 @@
|
||||
*/
|
||||
|
||||
import { JsonValue, JsonObject } from '@backstage/types';
|
||||
import {
|
||||
TaskSpec,
|
||||
TaskStep,
|
||||
TemplateMetadata,
|
||||
TaskSpecV1beta2,
|
||||
TaskSpecV1beta3,
|
||||
} from '@backstage/plugin-scaffolder-common';
|
||||
|
||||
export type {
|
||||
TaskSpec,
|
||||
TaskStep,
|
||||
TemplateMetadata,
|
||||
TaskSpecV1beta2,
|
||||
TaskSpecV1beta3,
|
||||
};
|
||||
|
||||
/**
|
||||
* Status
|
||||
@@ -69,64 +84,6 @@ export type SerializedTaskEvent = {
|
||||
createdAt: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* TemplateMetadata
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type TemplateMetadata = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* TaskSpecV1beta2
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface TaskSpecV1beta2 {
|
||||
apiVersion: 'backstage.io/v1beta2';
|
||||
baseUrl?: string;
|
||||
values: JsonObject;
|
||||
steps: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
action: string;
|
||||
input?: JsonObject;
|
||||
if?: string | boolean;
|
||||
}>;
|
||||
output: { [name: string]: string };
|
||||
metadata?: TemplateMetadata;
|
||||
}
|
||||
|
||||
export interface TaskStep {
|
||||
id: string;
|
||||
name: string;
|
||||
action: string;
|
||||
input?: JsonObject;
|
||||
if?: string | boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* TaskSpecV1beta3
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface TaskSpecV1beta3 {
|
||||
apiVersion: 'scaffolder.backstage.io/v1beta3';
|
||||
baseUrl?: string;
|
||||
parameters: JsonObject;
|
||||
steps: TaskStep[];
|
||||
output: { [name: string]: JsonValue };
|
||||
metadata?: TemplateMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* TaskSpec
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type TaskSpec = TaskSpecV1beta2 | TaskSpecV1beta3;
|
||||
|
||||
/**
|
||||
* TaskSecrets
|
||||
*
|
||||
|
||||
@@ -6,6 +6,60 @@
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import { JSONSchema } from '@backstage/catalog-model';
|
||||
import { JsonValue } from '@backstage/types';
|
||||
|
||||
// @public
|
||||
export type TaskSpec = TaskSpecV1beta2 | TaskSpecV1beta3;
|
||||
|
||||
// @public
|
||||
export interface TaskSpecV1beta2 {
|
||||
// (undocumented)
|
||||
apiVersion: 'backstage.io/v1beta2';
|
||||
// (undocumented)
|
||||
baseUrl?: string;
|
||||
// (undocumented)
|
||||
metadata?: TemplateMetadata;
|
||||
// (undocumented)
|
||||
output: {
|
||||
[name: string]: string;
|
||||
};
|
||||
// (undocumented)
|
||||
steps: TaskStep[];
|
||||
// (undocumented)
|
||||
values: JsonObject;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface TaskSpecV1beta3 {
|
||||
// (undocumented)
|
||||
apiVersion: 'scaffolder.backstage.io/v1beta3';
|
||||
// (undocumented)
|
||||
baseUrl?: string;
|
||||
// (undocumented)
|
||||
metadata?: TemplateMetadata;
|
||||
// (undocumented)
|
||||
output: {
|
||||
[name: string]: JsonValue;
|
||||
};
|
||||
// (undocumented)
|
||||
parameters: JsonObject;
|
||||
// (undocumented)
|
||||
steps: TaskStep[];
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface TaskStep {
|
||||
// (undocumented)
|
||||
action: string;
|
||||
// (undocumented)
|
||||
id: string;
|
||||
// (undocumented)
|
||||
if?: string | boolean;
|
||||
// (undocumented)
|
||||
input?: JsonObject;
|
||||
// (undocumented)
|
||||
name: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface TemplateEntityV1beta3 extends Entity {
|
||||
@@ -33,4 +87,9 @@ export interface TemplateEntityV1beta3 extends Entity {
|
||||
|
||||
// @public (undocumented)
|
||||
export const templateEntityV1beta3Schema: JSONSchema;
|
||||
|
||||
// @public
|
||||
export type TemplateMetadata = {
|
||||
name: string;
|
||||
};
|
||||
```
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { JsonValue, JsonObject } from '@backstage/types';
|
||||
|
||||
/**
|
||||
* TemplateMetadata
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type TemplateMetadata = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* TaskStep
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface TaskStep {
|
||||
id: string;
|
||||
name: string;
|
||||
action: string;
|
||||
input?: JsonObject;
|
||||
if?: string | boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* TaskSpecV1beta2
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface TaskSpecV1beta2 {
|
||||
apiVersion: 'backstage.io/v1beta2';
|
||||
baseUrl?: string;
|
||||
values: JsonObject;
|
||||
steps: TaskStep[];
|
||||
output: { [name: string]: string };
|
||||
metadata?: TemplateMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* TaskSpecV1beta3
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface TaskSpecV1beta3 {
|
||||
apiVersion: 'scaffolder.backstage.io/v1beta3';
|
||||
baseUrl?: string;
|
||||
parameters: JsonObject;
|
||||
steps: TaskStep[];
|
||||
output: { [name: string]: JsonValue };
|
||||
metadata?: TemplateMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* TaskSpec
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type TaskSpec = TaskSpecV1beta2 | TaskSpecV1beta3;
|
||||
@@ -30,3 +30,5 @@ export const templateEntityV1beta3Schema: JSONSchema = v1beta3Schema as Omit<
|
||||
JSONSchema,
|
||||
'examples'
|
||||
>;
|
||||
|
||||
export * from './TaskSpec';
|
||||
|
||||
@@ -21,12 +21,12 @@ import { IconButton } from '@material-ui/core';
|
||||
import { IdentityApi } from '@backstage/core-plugin-api';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import { JSONSchema } from '@backstage/catalog-model';
|
||||
import { JsonValue } from '@backstage/types';
|
||||
import { Observable } from '@backstage/types';
|
||||
import { default as React_2 } from 'react';
|
||||
import { ReactNode } from 'react';
|
||||
import { RouteRef } from '@backstage/core-plugin-api';
|
||||
import { ScmIntegrationRegistry } from '@backstage/integration';
|
||||
import { TaskSpec } from '@backstage/plugin-scaffolder-common';
|
||||
import { TemplateEntityV1beta2 } from '@backstage/catalog-model';
|
||||
|
||||
// Warning: (ae-missing-release-tag) "createScaffolderFieldExtension" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
"@backstage/integration": "^0.6.10",
|
||||
"@backstage/integration-react": "^0.1.16",
|
||||
"@backstage/plugin-catalog-react": "^0.6.8",
|
||||
"@backstage/plugin-scaffolder-common": "^0.1.1",
|
||||
"@backstage/theme": "^0.2.14",
|
||||
"@backstage/types": "^0.1.1",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
|
||||
@@ -22,8 +22,10 @@ import {
|
||||
Page,
|
||||
LogViewer,
|
||||
} from '@backstage/core-components';
|
||||
import { useRouteRef } from '@backstage/core-plugin-api';
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
import {
|
||||
Button,
|
||||
CircularProgress,
|
||||
Paper,
|
||||
StepButton,
|
||||
@@ -40,9 +42,11 @@ import Check from '@material-ui/icons/Check';
|
||||
import FiberManualRecordIcon from '@material-ui/icons/FiberManualRecord';
|
||||
import classNames from 'classnames';
|
||||
import { DateTime, Interval } from 'luxon';
|
||||
import qs from 'qs';
|
||||
import React, { memo, useEffect, useMemo, useState } from 'react';
|
||||
import { useParams } from 'react-router';
|
||||
import { generatePath, useNavigate, useParams } from 'react-router';
|
||||
import { useInterval } from 'react-use';
|
||||
import { rootRouteRef } from '../../routes';
|
||||
import { Status, TaskOutput } from '../../types';
|
||||
import { useTaskEventStream } from '../hooks/useEventStream';
|
||||
import { TaskPageLinks } from './TaskPageLinks';
|
||||
@@ -56,8 +60,8 @@ const useStyles = makeStyles((theme: Theme) =>
|
||||
width: '100%',
|
||||
},
|
||||
button: {
|
||||
marginTop: theme.spacing(1),
|
||||
marginRight: theme.spacing(1),
|
||||
marginBottom: theme.spacing(2),
|
||||
marginLeft: theme.spacing(2),
|
||||
},
|
||||
actionsContainer: {
|
||||
marginBottom: theme.spacing(2),
|
||||
@@ -215,6 +219,9 @@ const hasLinks = ({ entityRef, remoteUrl, links = [] }: TaskOutput): boolean =>
|
||||
!!(entityRef || remoteUrl || links.length > 0);
|
||||
|
||||
export const TaskPage = () => {
|
||||
const classes = useStyles();
|
||||
const navigate = useNavigate();
|
||||
const rootLink = useRouteRef(rootRouteRef);
|
||||
const [userSelectedStepId, setUserSelectedStepId] = useState<
|
||||
string | undefined
|
||||
>(undefined);
|
||||
@@ -266,6 +273,26 @@ export const TaskPage = () => {
|
||||
|
||||
const { output } = taskStream;
|
||||
|
||||
const handleStartOver = () => {
|
||||
if (!taskStream.task || !taskStream.task?.spec.metadata?.name) {
|
||||
navigate(generatePath(rootLink()));
|
||||
}
|
||||
|
||||
const formData =
|
||||
taskStream.task!.spec.apiVersion === 'backstage.io/v1beta2'
|
||||
? taskStream.task!.spec.values
|
||||
: taskStream.task!.spec.parameters;
|
||||
|
||||
navigate(
|
||||
generatePath(
|
||||
`${rootLink()}/templates/:templateName?${qs.stringify({ formData })}`,
|
||||
{
|
||||
templateName: taskStream.task!.spec.metadata!.name,
|
||||
},
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Page themeId="home">
|
||||
<Header
|
||||
@@ -297,6 +324,15 @@ export const TaskPage = () => {
|
||||
{output && hasLinks(output) && (
|
||||
<TaskPageLinks output={output} />
|
||||
)}
|
||||
<Button
|
||||
className={classes.button}
|
||||
onClick={handleStartOver}
|
||||
disabled={!completed}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
>
|
||||
Start Over
|
||||
</Button>
|
||||
</Paper>
|
||||
</Grid>
|
||||
<Grid item xs={9}>
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
import { JsonObject, JsonValue } from '@backstage/types';
|
||||
import { LinearProgress } from '@material-ui/core';
|
||||
import { FormValidation, IChangeEvent } from '@rjsf/core';
|
||||
import qs from 'qs';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { generatePath, Navigate, useNavigate } from 'react-router';
|
||||
import { useParams } from 'react-router-dom';
|
||||
@@ -120,7 +121,12 @@ export const TemplatePage = ({
|
||||
const navigate = useNavigate();
|
||||
const rootLink = useRouteRef(rootRouteRef);
|
||||
const { schema, loading, error } = useTemplateParameterSchema(templateName);
|
||||
const [formState, setFormState] = useState({});
|
||||
const query = qs.parse(window.location.search, {
|
||||
ignoreQueryPrefix: true,
|
||||
});
|
||||
const [formState, setFormState] = useState(
|
||||
(query.formData ?? {}) as Record<string, any>,
|
||||
);
|
||||
const handleFormReset = () => setFormState({});
|
||||
const handleChange = useCallback(
|
||||
(e: IChangeEvent) => setFormState(e.formData),
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { JSONSchema } from '@backstage/catalog-model';
|
||||
import { JsonValue } from '@backstage/types';
|
||||
import { TaskSpec } from '@backstage/plugin-scaffolder-common';
|
||||
|
||||
export type Status = 'open' | 'processing' | 'failed' | 'completed' | 'skipped';
|
||||
export type JobStatus = 'PENDING' | 'STARTED' | 'COMPLETED' | 'FAILED';
|
||||
@@ -39,18 +39,9 @@ export type Stage = {
|
||||
endedAt?: string;
|
||||
};
|
||||
|
||||
export type ScaffolderStep = {
|
||||
id: string;
|
||||
name: string;
|
||||
action: string;
|
||||
parameters?: { [name: string]: JsonValue };
|
||||
};
|
||||
|
||||
export type ScaffolderTask = {
|
||||
id: string;
|
||||
spec: {
|
||||
steps: ScaffolderStep[];
|
||||
};
|
||||
spec: TaskSpec;
|
||||
status: 'failed' | 'completed' | 'processing' | 'open' | 'cancelled';
|
||||
lastHeartbeatAt: string;
|
||||
createdAt: string;
|
||||
|
||||
Reference in New Issue
Block a user