@backstage/plugin-scaffolder 0.11.13 → 0.11.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,63 @@
1
1
  # @backstage/plugin-scaffolder
2
2
 
3
+ ## 0.11.17
4
+
5
+ ### Patch Changes
6
+
7
+ - 4ce51ab0f1: Internal refactor of the `react-use` imports to use `react-use/lib/*` instead.
8
+ - Updated dependencies
9
+ - @backstage/core-plugin-api@0.4.1
10
+ - @backstage/plugin-catalog-react@0.6.10
11
+ - @backstage/core-components@0.8.3
12
+
13
+ ## 0.11.16
14
+
15
+ ### Patch Changes
16
+
17
+ - 9c25894892: Implement a `EntityTagsPicker` field extension
18
+ - 7d4b4e937c: Uptake changes to the GitHub Credentials Provider interface.
19
+ - d078377f67: Support navigating back to pre-filled templates to update inputs of scaffolder tasks for resubmission
20
+ - Updated dependencies
21
+ - @backstage/plugin-catalog-react@0.6.9
22
+ - @backstage/plugin-scaffolder-common@0.1.2
23
+ - @backstage/integration@0.7.0
24
+ - @backstage/integration-react@0.1.17
25
+
26
+ ## 0.11.15
27
+
28
+ ### Patch Changes
29
+
30
+ - c5eb756760: Fix a small browser console warning
31
+ - ff5ff57883: EntityPicker can require an existing entity be selected by disallowing arbitrary values
32
+ - 0f645a7947: Added OwnedEntityPicker field which displays Owned Entities in options
33
+ - b646a73fe0: In @backstage/plugin-scaffolder - When user will have one option available in hostUrl or owner - autoselect and select component should be readonly.
34
+
35
+ in @backstage/core-components - Select component has extended API with few more props: native : boolean, disabled: boolean. native - if set to true - Select component will use native browser select picker (not rendered by Material UI lib ).
36
+ disabled - if set to true - action on component will not be possible.
37
+
38
+ - 7a4bd2ceac: Prefer using `Link` from `@backstage/core-components` rather than material-UI.
39
+ - 4c269c7c23: Add DescriptionField override to support Markdown
40
+ - Updated dependencies
41
+ - @backstage/core-plugin-api@0.4.0
42
+ - @backstage/plugin-catalog-react@0.6.8
43
+ - @backstage/core-components@0.8.2
44
+ - @backstage/catalog-client@0.5.3
45
+ - @backstage/integration-react@0.1.16
46
+
47
+ ## 0.11.14
48
+
49
+ ### Patch Changes
50
+
51
+ - 6845cce533: Can specify allowedOwners to the RepoUrlPicker picker in a template definition
52
+ - cd450844f6: Moved React dependencies to `peerDependencies` and allow both React v16 and v17 to be used.
53
+ - 2edcf7738f: Fix bug with setting owner in RepoUrlPicker causing validation failure
54
+ - b291c3176e: Switch to using `LogViewer` component from `@backstage/core-components` to display scaffolder logs.
55
+ - Updated dependencies
56
+ - @backstage/core-components@0.8.0
57
+ - @backstage/core-plugin-api@0.3.0
58
+ - @backstage/integration-react@0.1.15
59
+ - @backstage/plugin-catalog-react@0.6.5
60
+
3
61
  ## 0.11.13
4
62
 
5
63
  ### Patch Changes
@@ -1,12 +1,13 @@
1
- import React, { useState, useCallback, useEffect, memo, Suspense, useMemo } from 'react';
1
+ import React, { useState, useCallback, useEffect, memo, useMemo } from 'react';
2
2
  import { useNavigate, Navigate, generatePath, useParams as useParams$1, useOutlet, Routes, Route } from 'react-router';
3
- import { Page, Header, Lifecycle, Content, ContentHeader, CreateButton, SupportButton, StructuredMetadataTable, InfoCard, Link, Progress, ErrorPage } from '@backstage/core-components';
3
+ import { Page, Header, Lifecycle, Content, ContentHeader, CreateButton, SupportButton, MarkdownContent, StructuredMetadataTable, InfoCard, Link, ErrorPage, LogViewer, Progress } from '@backstage/core-components';
4
4
  import { useRouteRef, useApi, errorApiRef, useApiHolder, useApp, useElementFilter } from '@backstage/core-plugin-api';
5
5
  import { EntityListProvider, EntitySearchBar, EntityKindPicker, UserListPicker, EntityTagPicker, entityRouteRef } from '@backstage/plugin-catalog-react';
6
6
  import { makeStyles, Stepper, Step, StepLabel, Typography, StepContent, Button, Paper, Box, LinearProgress, Grid, StepButton, CircularProgress, TableContainer, Table, TableHead, TableRow, TableCell, TableBody } from '@material-ui/core';
7
- import { E as EntityPicker, a as EntityNamePicker, e as entityNamePickerValidation, R as RepoUrlPicker, r as repoPickerValidation, O as OwnerPicker, b as registerComponentRouteRef, T as TemplateTypePicker, c as TemplateList, s as scaffolderApiRef, d as rootRouteRef, F as FIELD_EXTENSION_WRAPPER_KEY, f as FIELD_EXTENSION_KEY } from './index-385b3fc1.esm.js';
7
+ import { E as EntityPicker, a as EntityNamePicker, e as entityNamePickerValidation, b as EntityTagsPicker, R as RepoUrlPicker, r as repoPickerValidation, O as OwnerPicker, c as OwnedEntityPicker, d as registerComponentRouteRef, T as TemplateTypePicker, f as TemplateList, s as scaffolderApiRef, g as rootRouteRef, F as FIELD_EXTENSION_WRAPPER_KEY, h as FIELD_EXTENSION_KEY } from './index-768702c3.esm.js';
8
+ import qs from 'qs';
8
9
  import { useParams } from 'react-router-dom';
9
- import { useAsync, useInterval } from 'react-use';
10
+ import useAsync from 'react-use/lib/useAsync';
10
11
  import { withTheme } from '@rjsf/core';
11
12
  import { Theme } from '@rjsf/material-ui';
12
13
  import Grid$1 from '@material-ui/core/Grid';
@@ -20,19 +21,20 @@ import Check from '@material-ui/icons/Check';
20
21
  import FiberManualRecordIcon from '@material-ui/icons/FiberManualRecord';
21
22
  import classNames from 'classnames';
22
23
  import { DateTime, Interval } from 'luxon';
24
+ import useInterval from 'react-use/lib/useInterval';
23
25
  import { useImmerReducer } from 'use-immer';
24
26
  import { parseEntityName } from '@backstage/catalog-model';
25
27
  import LanguageIcon from '@material-ui/icons/Language';
26
28
  import '@backstage/errors';
27
- import 'qs';
28
29
  import 'zen-observable';
29
30
  import '@material-ui/core/FormControl';
30
31
  import '@material-ui/lab/Autocomplete';
32
+ import 'react-use';
33
+ import '@material-ui/lab';
31
34
  import '@backstage/integration-react';
32
- import '@material-ui/core/Select';
33
- import '@material-ui/core/InputLabel';
34
- import '@material-ui/core/Input';
35
35
  import '@material-ui/core/FormHelperText';
36
+ import '@material-ui/core/Input';
37
+ import '@material-ui/core/InputLabel';
36
38
  import '@material-ui/icons/Star';
37
39
  import '@material-ui/icons/StarBorder';
38
40
  import '@material-ui/icons/Warning';
@@ -40,7 +42,6 @@ import 'lodash/capitalize';
40
42
  import '@material-ui/icons/CheckBox';
41
43
  import '@material-ui/icons/CheckBoxOutlineBlank';
42
44
  import '@material-ui/icons/ExpandMore';
43
- import '@material-ui/lab';
44
45
 
45
46
  const DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS = [
46
47
  {
@@ -52,6 +53,10 @@ const DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS = [
52
53
  name: "EntityNamePicker",
53
54
  validation: entityNamePickerValidation
54
55
  },
56
+ {
57
+ component: EntityTagsPicker,
58
+ name: "EntityTagsPicker"
59
+ },
55
60
  {
56
61
  component: RepoUrlPicker,
57
62
  name: "RepoUrlPicker",
@@ -60,6 +65,10 @@ const DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS = [
60
65
  {
61
66
  component: OwnerPicker,
62
67
  name: "OwnerPicker"
68
+ },
69
+ {
70
+ component: OwnedEntityPicker,
71
+ name: "OwnedEntityPicker"
63
72
  }
64
73
  ];
65
74
 
@@ -105,10 +114,12 @@ const ScaffolderPageContents = ({
105
114
  }), /* @__PURE__ */ React.createElement(UserListPicker, {
106
115
  initialFilter: "all",
107
116
  availableFilters: ["all", "starred"]
108
- }), /* @__PURE__ */ React.createElement(TemplateTypePicker, null), /* @__PURE__ */ React.createElement(EntityTagPicker, null)), /* @__PURE__ */ React.createElement("div", null, groups && groups.map((group) => /* @__PURE__ */ React.createElement(TemplateList, {
117
+ }), /* @__PURE__ */ React.createElement(TemplateTypePicker, null), /* @__PURE__ */ React.createElement(EntityTagPicker, null)), /* @__PURE__ */ React.createElement("div", null, groups && groups.map((group, index) => /* @__PURE__ */ React.createElement(TemplateList, {
118
+ key: index,
109
119
  TemplateCardComponent,
110
120
  group
111
121
  })), /* @__PURE__ */ React.createElement(TemplateList, {
122
+ key: "other",
112
123
  TemplateCardComponent,
113
124
  group: otherTemplatesGroup
114
125
  })))));
@@ -128,7 +139,7 @@ function extractUiSchema(schema, uiSchema) {
128
139
  if (!isObject$1(schema)) {
129
140
  return;
130
141
  }
131
- const {properties, items, anyOf, oneOf, allOf, dependencies} = schema;
142
+ const { properties, items, anyOf, oneOf, allOf, dependencies } = schema;
132
143
  for (const propName in schema) {
133
144
  if (!schema.hasOwnProperty(propName)) {
134
145
  continue;
@@ -197,9 +208,18 @@ function transformSchemaToProps(inputSchema) {
197
208
  delete schema.title;
198
209
  const uiSchema = {};
199
210
  extractUiSchema(schema, uiSchema);
200
- return {schema, uiSchema};
211
+ return { schema, uiSchema };
201
212
  }
202
213
 
214
+ const DescriptionField = ({ description }) => description && /* @__PURE__ */ React.createElement(MarkdownContent, {
215
+ content: description
216
+ });
217
+
218
+ var fieldOverrides = /*#__PURE__*/Object.freeze({
219
+ __proto__: null,
220
+ DescriptionField: DescriptionField
221
+ });
222
+
203
223
  const Form = withTheme(Theme);
204
224
  function getUiSchemasFromSteps(steps) {
205
225
  const uiSchemas = [];
@@ -278,7 +298,7 @@ const MultistepJsonForm = ({
278
298
  return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Stepper, {
279
299
  activeStep,
280
300
  orientation: "vertical"
281
- }, steps.map(({title, schema, ...formProps}, index) => {
301
+ }, steps.map(({ title, schema, ...formProps }, index) => {
282
302
  return /* @__PURE__ */ React.createElement(Step, {
283
303
  key: title
284
304
  }, /* @__PURE__ */ React.createElement(StepLabel, {
@@ -292,7 +312,7 @@ const MultistepJsonForm = ({
292
312
  key: title
293
313
  }, /* @__PURE__ */ React.createElement(Form, {
294
314
  showErrorList: false,
295
- fields,
315
+ fields: { ...fieldOverrides, ...fields },
296
316
  widgets,
297
317
  noHtml5Validate: true,
298
318
  formData,
@@ -337,12 +357,12 @@ const MultistepJsonForm = ({
337
357
 
338
358
  const useTemplateParameterSchema = (templateName) => {
339
359
  const scaffolderApi = useApi(scaffolderApiRef);
340
- const {value, loading, error} = useAsync(() => scaffolderApi.getTemplateParameterSchema({
360
+ const { value, loading, error } = useAsync(() => scaffolderApi.getTemplateParameterSchema({
341
361
  name: templateName,
342
362
  kind: "template",
343
363
  namespace: "default"
344
364
  }), [scaffolderApi, templateName]);
345
- return {schema: value, loading, error};
365
+ return { schema: value, loading, error };
346
366
  };
347
367
  function isObject(obj) {
348
368
  return typeof obj === "object" && obj !== null && !Array.isArray(obj);
@@ -380,16 +400,26 @@ const TemplatePage = ({
380
400
  const apiHolder = useApiHolder();
381
401
  const errorApi = useApi(errorApiRef);
382
402
  const scaffolderApi = useApi(scaffolderApiRef);
383
- const {templateName} = useParams();
403
+ const { templateName } = useParams();
384
404
  const navigate = useNavigate();
385
405
  const rootLink = useRouteRef(rootRouteRef);
386
- const {schema, loading, error} = useTemplateParameterSchema(templateName);
387
- const [formState, setFormState] = useState({});
406
+ const { schema, loading, error } = useTemplateParameterSchema(templateName);
407
+ const [formState, setFormState] = useState(() => {
408
+ var _a;
409
+ const query = qs.parse(window.location.search, {
410
+ ignoreQueryPrefix: true
411
+ });
412
+ return (_a = query.formData) != null ? _a : {};
413
+ });
388
414
  const handleFormReset = () => setFormState({});
389
415
  const handleChange = useCallback((e) => setFormState(e.formData), [setFormState]);
390
416
  const handleCreate = async () => {
417
+ var _a;
391
418
  const id = await scaffolderApi.scaffold(templateName, formState);
392
- navigate(generatePath(`${rootLink()}/tasks/:taskId`, {taskId: id}));
419
+ const formParams = qs.stringify({ formData: formState }, { addQueryPrefix: true });
420
+ const newUrl = `${window.location.pathname}${formParams}`;
421
+ (_a = window.history) == null ? void 0 : _a.replaceState(null, document.title, newUrl);
422
+ navigate(generatePath(`${rootLink()}/tasks/:taskId`, { taskId: id }));
393
423
  };
394
424
  if (error) {
395
425
  errorApi.post(new Error(`Failed to load template, ${error}`));
@@ -403,8 +433,8 @@ const TemplatePage = ({
403
433
  to: rootLink()
404
434
  });
405
435
  }
406
- const customFieldComponents = Object.fromEntries(customFieldExtensions.map(({name, component}) => [name, component]));
407
- const customFieldValidators = Object.fromEntries(customFieldExtensions.map(({name, validation}) => [name, validation]));
436
+ const customFieldComponents = Object.fromEntries(customFieldExtensions.map(({ name, component }) => [name, component]));
437
+ const customFieldValidators = Object.fromEntries(customFieldExtensions.map(({ name, validation }) => [name, validation]));
408
438
  return /* @__PURE__ */ React.createElement(Page, {
409
439
  themeId: "home"
410
440
  }, /* @__PURE__ */ React.createElement(Header, {
@@ -418,7 +448,7 @@ const TemplatePage = ({
418
448
  }), schema && /* @__PURE__ */ React.createElement(InfoCard, {
419
449
  title: schema.title,
420
450
  noPadding: true,
421
- titleTypographyProps: {component: "h2"}
451
+ titleTypographyProps: { component: "h2" }
422
452
  }, /* @__PURE__ */ React.createElement(MultistepJsonForm, {
423
453
  formData: formState,
424
454
  fields: customFieldComponents,
@@ -428,7 +458,7 @@ const TemplatePage = ({
428
458
  steps: schema.steps.map((step) => {
429
459
  return {
430
460
  ...step,
431
- validate: createValidator(step.schema, customFieldValidators, {apiHolder})
461
+ validate: createValidator(step.schema, customFieldValidators, { apiHolder })
432
462
  };
433
463
  })
434
464
  }))));
@@ -439,7 +469,7 @@ function reducer(draft, action) {
439
469
  switch (action.type) {
440
470
  case "INIT": {
441
471
  draft.steps = action.data.spec.steps.reduce((current, next) => {
442
- current[next.id] = {status: "open", id: next.id};
472
+ current[next.id] = { status: "open", id: next.id };
443
473
  return current;
444
474
  }, {});
445
475
  draft.stepLogs = action.data.spec.steps.reduce((current, next) => {
@@ -505,13 +535,13 @@ const useTaskEventStream = (taskId) => {
505
535
  if (didCancel) {
506
536
  return;
507
537
  }
508
- dispatch({type: "INIT", data: task});
509
- const observable = scaffolderApi.streamLogs({taskId});
538
+ dispatch({ type: "INIT", data: task });
539
+ const observable = scaffolderApi.streamLogs({ taskId });
510
540
  const collectedLogEvents = new Array();
511
541
  function emitLogs() {
512
542
  if (collectedLogEvents.length) {
513
543
  const logs = collectedLogEvents.splice(0, collectedLogEvents.length);
514
- dispatch({type: "LOGS", data: logs});
544
+ dispatch({ type: "LOGS", data: logs });
515
545
  }
516
546
  }
517
547
  logPusher = setInterval(emitLogs, 500);
@@ -522,7 +552,7 @@ const useTaskEventStream = (taskId) => {
522
552
  return collectedLogEvents.push(event);
523
553
  case "completion":
524
554
  emitLogs();
525
- dispatch({type: "COMPLETED", data: event});
555
+ dispatch({ type: "COMPLETED", data: event });
526
556
  return void 0;
527
557
  default:
528
558
  throw new Error(`Unhandled event type ${event.type} in observer`);
@@ -530,12 +560,12 @@ const useTaskEventStream = (taskId) => {
530
560
  },
531
561
  error: (error) => {
532
562
  emitLogs();
533
- dispatch({type: "ERROR", data: error});
563
+ dispatch({ type: "ERROR", data: error });
534
564
  }
535
565
  });
536
566
  }, (error) => {
537
567
  if (!didCancel) {
538
- dispatch({type: "ERROR", data: error});
568
+ dispatch({ type: "ERROR", data: error });
539
569
  }
540
570
  });
541
571
  return () => {
@@ -562,7 +592,7 @@ const useStyles$2 = makeStyles({
562
592
  }
563
593
  });
564
594
  const IconLink = (props) => {
565
- const {href, text, Icon, ...linkProps} = props;
595
+ const { href, text, Icon, ...linkProps } = props;
566
596
  const classes = useStyles$2();
567
597
  return /* @__PURE__ */ React.createElement(Grid, {
568
598
  container: true,
@@ -581,9 +611,9 @@ const IconLink = (props) => {
581
611
  }, text || href)));
582
612
  };
583
613
 
584
- const TaskPageLinks = ({output}) => {
585
- const {entityRef: entityRefOutput, remoteUrl} = output;
586
- let {links = []} = output;
614
+ const TaskPageLinks = ({ output }) => {
615
+ const { entityRef: entityRefOutput, remoteUrl } = output;
616
+ let { links = [] } = output;
587
617
  const app = useApp();
588
618
  const entityRoute = useRouteRef(entityRouteRef);
589
619
  const iconResolver = (key) => {
@@ -591,7 +621,7 @@ const TaskPageLinks = ({output}) => {
591
621
  return key ? (_a = app.getSystemIcon(key)) != null ? _a : LanguageIcon : LanguageIcon;
592
622
  };
593
623
  if (remoteUrl) {
594
- links = [{url: remoteUrl, title: "Repo"}, ...links];
624
+ links = [{ url: remoteUrl, title: "Repo" }, ...links];
595
625
  }
596
626
  if (entityRefOutput) {
597
627
  links = [
@@ -606,14 +636,14 @@ const TaskPageLinks = ({output}) => {
606
636
  return /* @__PURE__ */ React.createElement(Box, {
607
637
  px: 3,
608
638
  pb: 3
609
- }, links.filter(({url, entityRef}) => url || entityRef).map(({url, entityRef, title, icon}) => {
639
+ }, links.filter(({ url, entityRef }) => url || entityRef).map(({ url, entityRef, title, icon }) => {
610
640
  if (entityRef) {
611
641
  const entityName = parseEntityName(entityRef);
612
642
  const target = entityRoute(entityName);
613
- return {title, icon, url: target};
643
+ return { title, icon, url: target };
614
644
  }
615
- return {title, icon, url};
616
- }).map(({url, title, icon}, i) => /* @__PURE__ */ React.createElement(IconLink, {
645
+ return { title, icon, url };
646
+ }).map(({ url, title, icon }, i) => /* @__PURE__ */ React.createElement(IconLink, {
617
647
  key: `output-link-${i}`,
618
648
  href: url,
619
649
  text: title != null ? title : url,
@@ -622,15 +652,14 @@ const TaskPageLinks = ({output}) => {
622
652
  })));
623
653
  };
624
654
 
625
- const LazyLog = React.lazy(() => import('react-lazylog/build/LazyLog'));
626
655
  const humanizeDuration = require("humanize-duration");
627
656
  const useStyles$1 = makeStyles$1((theme) => createStyles({
628
657
  root: {
629
658
  width: "100%"
630
659
  },
631
660
  button: {
632
- marginTop: theme.spacing(1),
633
- marginRight: theme.spacing(1)
661
+ marginBottom: theme.spacing(2),
662
+ marginLeft: theme.spacing(2)
634
663
  },
635
664
  actionsContainer: {
636
665
  marginBottom: theme.spacing(2)
@@ -648,7 +677,7 @@ const useStyles$1 = makeStyles$1((theme) => createStyles({
648
677
  width: "100%"
649
678
  }
650
679
  }));
651
- const StepTimeTicker = ({step}) => {
680
+ const StepTimeTicker = ({ step }) => {
652
681
  const [time, setTime] = useState("");
653
682
  useInterval(() => {
654
683
  if (!step.startedAt) {
@@ -658,7 +687,7 @@ const StepTimeTicker = ({step}) => {
658
687
  const end = step.endedAt ? DateTime.fromISO(step.endedAt) : DateTime.local();
659
688
  const startedAt = DateTime.fromISO(step.startedAt);
660
689
  const formatted = Interval.fromDateTimes(startedAt, end).toDuration().valueOf();
661
- setTime(humanizeDuration(formatted, {round: true}));
690
+ setTime(humanizeDuration(formatted, { round: true }));
662
691
  }, 1e3);
663
692
  return /* @__PURE__ */ React.createElement(Typography$1, {
664
693
  variant: "caption"
@@ -680,7 +709,7 @@ const useStepIconStyles = makeStyles$1((theme) => createStyles({
680
709
  }));
681
710
  function TaskStepIconComponent(props) {
682
711
  const classes = useStepIconStyles();
683
- const {active, completed, error} = props;
712
+ const { active, completed, error } = props;
684
713
  const getMiddle = () => {
685
714
  if (active) {
686
715
  return /* @__PURE__ */ React.createElement(CircularProgress, {
@@ -743,24 +772,14 @@ const TaskStatusStepper = memo(({
743
772
  })))));
744
773
  })));
745
774
  });
746
- const TaskLogger = memo(({log}) => {
747
- return /* @__PURE__ */ React.createElement(Suspense, {
748
- fallback: /* @__PURE__ */ React.createElement(Progress, null)
749
- }, /* @__PURE__ */ React.createElement("div", {
750
- style: {height: "80vh"}
751
- }, /* @__PURE__ */ React.createElement(LazyLog, {
752
- text: log,
753
- extraLines: 1,
754
- follow: true,
755
- selectableLines: true,
756
- enableSearch: true
757
- })));
758
- });
759
- const hasLinks = ({entityRef, remoteUrl, links = []}) => !!(entityRef || remoteUrl || links.length > 0);
775
+ const hasLinks = ({ entityRef, remoteUrl, links = [] }) => !!(entityRef || remoteUrl || links.length > 0);
760
776
  const TaskPage = () => {
777
+ const classes = useStyles$1();
778
+ const navigate = useNavigate();
779
+ const rootLink = useRouteRef(rootRouteRef);
761
780
  const [userSelectedStepId, setUserSelectedStepId] = useState(void 0);
762
781
  const [lastActiveStepId, setLastActiveStepId] = useState(void 0);
763
- const {taskId} = useParams$1();
782
+ const { taskId } = useParams$1();
764
783
  const taskStream = useTaskEventStream(taskId);
765
784
  const completed = taskStream.completed;
766
785
  const steps = useMemo(() => {
@@ -794,7 +813,17 @@ const TaskPage = () => {
794
813
  return log.join("\n");
795
814
  }, [taskStream.stepLogs, currentStepId]);
796
815
  const taskNotFound = taskStream.completed === true && taskStream.loading === false && !taskStream.task;
797
- const {output} = taskStream;
816
+ const { output } = taskStream;
817
+ const handleStartOver = () => {
818
+ var _a, _b;
819
+ if (!taskStream.task || !((_b = (_a = taskStream.task) == null ? void 0 : _a.spec.metadata) == null ? void 0 : _b.name)) {
820
+ navigate(generatePath(rootLink()));
821
+ }
822
+ const formData = taskStream.task.spec.apiVersion === "backstage.io/v1beta2" ? taskStream.task.spec.values : taskStream.task.spec.parameters;
823
+ navigate(generatePath(`${rootLink()}/templates/:templateName?${qs.stringify({ formData })}`, {
824
+ templateName: taskStream.task.spec.metadata.name
825
+ }));
826
+ };
798
827
  return /* @__PURE__ */ React.createElement(Page, {
799
828
  themeId: "home"
800
829
  }, /* @__PURE__ */ React.createElement(Header, {
@@ -819,12 +848,20 @@ const TaskPage = () => {
819
848
  onUserStepChange: setUserSelectedStepId
820
849
  }), output && hasLinks(output) && /* @__PURE__ */ React.createElement(TaskPageLinks, {
821
850
  output
822
- }))), /* @__PURE__ */ React.createElement(Grid$1, {
851
+ }), /* @__PURE__ */ React.createElement(Button, {
852
+ className: classes.button,
853
+ onClick: handleStartOver,
854
+ disabled: !completed,
855
+ variant: "contained",
856
+ color: "primary"
857
+ }, "Start Over"))), /* @__PURE__ */ React.createElement(Grid$1, {
823
858
  item: true,
824
859
  xs: 9
825
- }, /* @__PURE__ */ React.createElement(TaskLogger, {
826
- log: logAsString
827
- }))))));
860
+ }, /* @__PURE__ */ React.createElement("div", {
861
+ style: { height: "80vh" }
862
+ }, /* @__PURE__ */ React.createElement(LogViewer, {
863
+ text: logAsString
864
+ })))))));
828
865
  };
829
866
 
830
867
  const useStyles = makeStyles((theme) => ({
@@ -851,7 +888,7 @@ const useStyles = makeStyles((theme) => ({
851
888
  const ActionsPage = () => {
852
889
  const api = useApi(scaffolderApiRef);
853
890
  const classes = useStyles();
854
- const {loading, value, error} = useAsync(async () => {
891
+ const { loading, value, error } = useAsync(async () => {
855
892
  return api.listActions();
856
893
  });
857
894
  if (loading) {
@@ -935,7 +972,7 @@ const ActionsPage = () => {
935
972
  }), /* @__PURE__ */ React.createElement(Content, null, items));
936
973
  };
937
974
 
938
- const Router = ({TemplateCardComponent, groups}) => {
975
+ const Router = ({ TemplateCardComponent, groups }) => {
939
976
  const outlet = useOutlet();
940
977
  const customFieldExtensions = useElementFilter(outlet, (elements) => elements.selectByComponentData({
941
978
  key: FIELD_EXTENSION_WRAPPER_KEY
@@ -944,7 +981,7 @@ const Router = ({TemplateCardComponent, groups}) => {
944
981
  }));
945
982
  const fieldExtensions = [
946
983
  ...customFieldExtensions,
947
- ...DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS.filter(({name}) => !customFieldExtensions.some((customFieldExtension) => customFieldExtension.name === name))
984
+ ...DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS.filter(({ name }) => !customFieldExtensions.some((customFieldExtension) => customFieldExtension.name === name))
948
985
  ];
949
986
  return /* @__PURE__ */ React.createElement(Routes, null, /* @__PURE__ */ React.createElement(Route, {
950
987
  path: "/",
@@ -967,4 +1004,4 @@ const Router = ({TemplateCardComponent, groups}) => {
967
1004
  };
968
1005
 
969
1006
  export { Router };
970
- //# sourceMappingURL=Router-8b2aa17e.esm.js.map
1007
+ //# sourceMappingURL=Router-552fb2ce.esm.js.map