@backstage/plugin-scaffolder 0.11.12 → 0.11.16

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,77 @@
1
1
  # @backstage/plugin-scaffolder
2
2
 
3
+ ## 0.11.16
4
+
5
+ ### Patch Changes
6
+
7
+ - 9c25894892: Implement a `EntityTagsPicker` field extension
8
+ - 7d4b4e937c: Uptake changes to the GitHub Credentials Provider interface.
9
+ - d078377f67: Support navigating back to pre-filled templates to update inputs of scaffolder tasks for resubmission
10
+ - Updated dependencies
11
+ - @backstage/plugin-catalog-react@0.6.9
12
+ - @backstage/plugin-scaffolder-common@0.1.2
13
+ - @backstage/integration@0.7.0
14
+ - @backstage/integration-react@0.1.17
15
+
16
+ ## 0.11.15
17
+
18
+ ### Patch Changes
19
+
20
+ - c5eb756760: Fix a small browser console warning
21
+ - ff5ff57883: EntityPicker can require an existing entity be selected by disallowing arbitrary values
22
+ - 0f645a7947: Added OwnedEntityPicker field which displays Owned Entities in options
23
+ - b646a73fe0: In @backstage/plugin-scaffolder - When user will have one option available in hostUrl or owner - autoselect and select component should be readonly.
24
+
25
+ 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 ).
26
+ disabled - if set to true - action on component will not be possible.
27
+
28
+ - 7a4bd2ceac: Prefer using `Link` from `@backstage/core-components` rather than material-UI.
29
+ - 4c269c7c23: Add DescriptionField override to support Markdown
30
+ - Updated dependencies
31
+ - @backstage/core-plugin-api@0.4.0
32
+ - @backstage/plugin-catalog-react@0.6.8
33
+ - @backstage/core-components@0.8.2
34
+ - @backstage/catalog-client@0.5.3
35
+ - @backstage/integration-react@0.1.16
36
+
37
+ ## 0.11.14
38
+
39
+ ### Patch Changes
40
+
41
+ - 6845cce533: Can specify allowedOwners to the RepoUrlPicker picker in a template definition
42
+ - cd450844f6: Moved React dependencies to `peerDependencies` and allow both React v16 and v17 to be used.
43
+ - 2edcf7738f: Fix bug with setting owner in RepoUrlPicker causing validation failure
44
+ - b291c3176e: Switch to using `LogViewer` component from `@backstage/core-components` to display scaffolder logs.
45
+ - Updated dependencies
46
+ - @backstage/core-components@0.8.0
47
+ - @backstage/core-plugin-api@0.3.0
48
+ - @backstage/integration-react@0.1.15
49
+ - @backstage/plugin-catalog-react@0.6.5
50
+
51
+ ## 0.11.13
52
+
53
+ ### Patch Changes
54
+
55
+ - ed5bef529e: Add group filtering to the scaffolder page so that individuals can surface specific templates to end users ahead of others, or group templates together. This can be accomplished by passing in a `groups` prop to the `ScaffolderPage`
56
+
57
+ ```
58
+ <ScaffolderPage
59
+ groups={[
60
+ {
61
+ title: "Recommended",
62
+ filter: entity =>
63
+ entity?.metadata?.tags?.includes('recommended') ?? false,
64
+ },
65
+ ]}
66
+ />
67
+ ```
68
+
69
+ - Updated dependencies
70
+ - @backstage/integration@0.6.10
71
+ - @backstage/core-components@0.7.6
72
+ - @backstage/theme@0.2.14
73
+ - @backstage/core-plugin-api@0.2.2
74
+
3
75
  ## 0.11.12
4
76
 
5
77
  ### Patch Changes
@@ -1,10 +1,11 @@
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-9d7df4e7.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-f378943f.esm.js';
8
+ import qs from 'qs';
8
9
  import { useParams } from 'react-router-dom';
9
10
  import { useAsync, useInterval } from 'react-use';
10
11
  import { withTheme } from '@rjsf/core';
@@ -24,15 +25,14 @@ import { useImmerReducer } from 'use-immer';
24
25
  import { parseEntityName } from '@backstage/catalog-model';
25
26
  import LanguageIcon from '@material-ui/icons/Language';
26
27
  import '@backstage/errors';
27
- import 'qs';
28
28
  import 'zen-observable';
29
29
  import '@material-ui/core/FormControl';
30
30
  import '@material-ui/lab/Autocomplete';
31
+ import '@material-ui/lab';
31
32
  import '@backstage/integration-react';
32
- import '@material-ui/core/Select';
33
- import '@material-ui/core/InputLabel';
34
- import '@material-ui/core/Input';
35
33
  import '@material-ui/core/FormHelperText';
34
+ import '@material-ui/core/Input';
35
+ import '@material-ui/core/InputLabel';
36
36
  import '@material-ui/icons/Star';
37
37
  import '@material-ui/icons/StarBorder';
38
38
  import '@material-ui/icons/Warning';
@@ -40,7 +40,6 @@ import 'lodash/capitalize';
40
40
  import '@material-ui/icons/CheckBox';
41
41
  import '@material-ui/icons/CheckBoxOutlineBlank';
42
42
  import '@material-ui/icons/ExpandMore';
43
- import '@material-ui/lab';
44
43
 
45
44
  const DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS = [
46
45
  {
@@ -52,6 +51,10 @@ const DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS = [
52
51
  name: "EntityNamePicker",
53
52
  validation: entityNamePickerValidation
54
53
  },
54
+ {
55
+ component: EntityTagsPicker,
56
+ name: "EntityTagsPicker"
57
+ },
55
58
  {
56
59
  component: RepoUrlPicker,
57
60
  name: "RepoUrlPicker",
@@ -60,6 +63,10 @@ const DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS = [
60
63
  {
61
64
  component: OwnerPicker,
62
65
  name: "OwnerPicker"
66
+ },
67
+ {
68
+ component: OwnedEntityPicker,
69
+ name: "OwnedEntityPicker"
63
70
  }
64
71
  ];
65
72
 
@@ -72,10 +79,18 @@ const useStyles$3 = makeStyles((theme) => ({
72
79
  }
73
80
  }));
74
81
  const ScaffolderPageContents = ({
75
- TemplateCardComponent
82
+ TemplateCardComponent,
83
+ groups
76
84
  }) => {
77
85
  const styles = useStyles$3();
78
86
  const registerComponentLink = useRouteRef(registerComponentRouteRef);
87
+ const otherTemplatesGroup = {
88
+ title: groups ? "Other Templates" : "Templates",
89
+ filter: (entity) => {
90
+ const filtered = (groups != null ? groups : []).map((group) => group.filter(entity));
91
+ return !filtered.some((result) => result === true);
92
+ }
93
+ };
79
94
  return /* @__PURE__ */ React.createElement(Page, {
80
95
  themeId: "home"
81
96
  }, /* @__PURE__ */ React.createElement(Header, {
@@ -97,14 +112,22 @@ const ScaffolderPageContents = ({
97
112
  }), /* @__PURE__ */ React.createElement(UserListPicker, {
98
113
  initialFilter: "all",
99
114
  availableFilters: ["all", "starred"]
100
- }), /* @__PURE__ */ React.createElement(TemplateTypePicker, null), /* @__PURE__ */ React.createElement(EntityTagPicker, null)), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(TemplateList, {
101
- TemplateCardComponent
115
+ }), /* @__PURE__ */ React.createElement(TemplateTypePicker, null), /* @__PURE__ */ React.createElement(EntityTagPicker, null)), /* @__PURE__ */ React.createElement("div", null, groups && groups.map((group, index) => /* @__PURE__ */ React.createElement(TemplateList, {
116
+ key: index,
117
+ TemplateCardComponent,
118
+ group
119
+ })), /* @__PURE__ */ React.createElement(TemplateList, {
120
+ key: "other",
121
+ TemplateCardComponent,
122
+ group: otherTemplatesGroup
102
123
  })))));
103
124
  };
104
125
  const ScaffolderPage = ({
105
- TemplateCardComponent
126
+ TemplateCardComponent,
127
+ groups
106
128
  }) => /* @__PURE__ */ React.createElement(EntityListProvider, null, /* @__PURE__ */ React.createElement(ScaffolderPageContents, {
107
- TemplateCardComponent
129
+ TemplateCardComponent,
130
+ groups
108
131
  }));
109
132
 
110
133
  function isObject$1(value) {
@@ -114,7 +137,7 @@ function extractUiSchema(schema, uiSchema) {
114
137
  if (!isObject$1(schema)) {
115
138
  return;
116
139
  }
117
- const {properties, items, anyOf, oneOf, allOf, dependencies} = schema;
140
+ const { properties, items, anyOf, oneOf, allOf, dependencies } = schema;
118
141
  for (const propName in schema) {
119
142
  if (!schema.hasOwnProperty(propName)) {
120
143
  continue;
@@ -183,9 +206,18 @@ function transformSchemaToProps(inputSchema) {
183
206
  delete schema.title;
184
207
  const uiSchema = {};
185
208
  extractUiSchema(schema, uiSchema);
186
- return {schema, uiSchema};
209
+ return { schema, uiSchema };
187
210
  }
188
211
 
212
+ const DescriptionField = ({ description }) => description && /* @__PURE__ */ React.createElement(MarkdownContent, {
213
+ content: description
214
+ });
215
+
216
+ var fieldOverrides = /*#__PURE__*/Object.freeze({
217
+ __proto__: null,
218
+ DescriptionField: DescriptionField
219
+ });
220
+
189
221
  const Form = withTheme(Theme);
190
222
  function getUiSchemasFromSteps(steps) {
191
223
  const uiSchemas = [];
@@ -264,7 +296,7 @@ const MultistepJsonForm = ({
264
296
  return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Stepper, {
265
297
  activeStep,
266
298
  orientation: "vertical"
267
- }, steps.map(({title, schema, ...formProps}, index) => {
299
+ }, steps.map(({ title, schema, ...formProps }, index) => {
268
300
  return /* @__PURE__ */ React.createElement(Step, {
269
301
  key: title
270
302
  }, /* @__PURE__ */ React.createElement(StepLabel, {
@@ -278,7 +310,7 @@ const MultistepJsonForm = ({
278
310
  key: title
279
311
  }, /* @__PURE__ */ React.createElement(Form, {
280
312
  showErrorList: false,
281
- fields,
313
+ fields: { ...fieldOverrides, ...fields },
282
314
  widgets,
283
315
  noHtml5Validate: true,
284
316
  formData,
@@ -323,12 +355,12 @@ const MultistepJsonForm = ({
323
355
 
324
356
  const useTemplateParameterSchema = (templateName) => {
325
357
  const scaffolderApi = useApi(scaffolderApiRef);
326
- const {value, loading, error} = useAsync(() => scaffolderApi.getTemplateParameterSchema({
358
+ const { value, loading, error } = useAsync(() => scaffolderApi.getTemplateParameterSchema({
327
359
  name: templateName,
328
360
  kind: "template",
329
361
  namespace: "default"
330
362
  }), [scaffolderApi, templateName]);
331
- return {schema: value, loading, error};
363
+ return { schema: value, loading, error };
332
364
  };
333
365
  function isObject(obj) {
334
366
  return typeof obj === "object" && obj !== null && !Array.isArray(obj);
@@ -366,16 +398,26 @@ const TemplatePage = ({
366
398
  const apiHolder = useApiHolder();
367
399
  const errorApi = useApi(errorApiRef);
368
400
  const scaffolderApi = useApi(scaffolderApiRef);
369
- const {templateName} = useParams();
401
+ const { templateName } = useParams();
370
402
  const navigate = useNavigate();
371
403
  const rootLink = useRouteRef(rootRouteRef);
372
- const {schema, loading, error} = useTemplateParameterSchema(templateName);
373
- const [formState, setFormState] = useState({});
404
+ const { schema, loading, error } = useTemplateParameterSchema(templateName);
405
+ const [formState, setFormState] = useState(() => {
406
+ var _a;
407
+ const query = qs.parse(window.location.search, {
408
+ ignoreQueryPrefix: true
409
+ });
410
+ return (_a = query.formData) != null ? _a : {};
411
+ });
374
412
  const handleFormReset = () => setFormState({});
375
413
  const handleChange = useCallback((e) => setFormState(e.formData), [setFormState]);
376
414
  const handleCreate = async () => {
415
+ var _a;
377
416
  const id = await scaffolderApi.scaffold(templateName, formState);
378
- navigate(generatePath(`${rootLink()}/tasks/:taskId`, {taskId: id}));
417
+ const formParams = qs.stringify({ formData: formState }, { addQueryPrefix: true });
418
+ const newUrl = `${window.location.pathname}${formParams}`;
419
+ (_a = window.history) == null ? void 0 : _a.replaceState(null, document.title, newUrl);
420
+ navigate(generatePath(`${rootLink()}/tasks/:taskId`, { taskId: id }));
379
421
  };
380
422
  if (error) {
381
423
  errorApi.post(new Error(`Failed to load template, ${error}`));
@@ -389,8 +431,8 @@ const TemplatePage = ({
389
431
  to: rootLink()
390
432
  });
391
433
  }
392
- const customFieldComponents = Object.fromEntries(customFieldExtensions.map(({name, component}) => [name, component]));
393
- const customFieldValidators = Object.fromEntries(customFieldExtensions.map(({name, validation}) => [name, validation]));
434
+ const customFieldComponents = Object.fromEntries(customFieldExtensions.map(({ name, component }) => [name, component]));
435
+ const customFieldValidators = Object.fromEntries(customFieldExtensions.map(({ name, validation }) => [name, validation]));
394
436
  return /* @__PURE__ */ React.createElement(Page, {
395
437
  themeId: "home"
396
438
  }, /* @__PURE__ */ React.createElement(Header, {
@@ -404,7 +446,7 @@ const TemplatePage = ({
404
446
  }), schema && /* @__PURE__ */ React.createElement(InfoCard, {
405
447
  title: schema.title,
406
448
  noPadding: true,
407
- titleTypographyProps: {component: "h2"}
449
+ titleTypographyProps: { component: "h2" }
408
450
  }, /* @__PURE__ */ React.createElement(MultistepJsonForm, {
409
451
  formData: formState,
410
452
  fields: customFieldComponents,
@@ -414,7 +456,7 @@ const TemplatePage = ({
414
456
  steps: schema.steps.map((step) => {
415
457
  return {
416
458
  ...step,
417
- validate: createValidator(step.schema, customFieldValidators, {apiHolder})
459
+ validate: createValidator(step.schema, customFieldValidators, { apiHolder })
418
460
  };
419
461
  })
420
462
  }))));
@@ -425,7 +467,7 @@ function reducer(draft, action) {
425
467
  switch (action.type) {
426
468
  case "INIT": {
427
469
  draft.steps = action.data.spec.steps.reduce((current, next) => {
428
- current[next.id] = {status: "open", id: next.id};
470
+ current[next.id] = { status: "open", id: next.id };
429
471
  return current;
430
472
  }, {});
431
473
  draft.stepLogs = action.data.spec.steps.reduce((current, next) => {
@@ -491,13 +533,13 @@ const useTaskEventStream = (taskId) => {
491
533
  if (didCancel) {
492
534
  return;
493
535
  }
494
- dispatch({type: "INIT", data: task});
495
- const observable = scaffolderApi.streamLogs({taskId});
536
+ dispatch({ type: "INIT", data: task });
537
+ const observable = scaffolderApi.streamLogs({ taskId });
496
538
  const collectedLogEvents = new Array();
497
539
  function emitLogs() {
498
540
  if (collectedLogEvents.length) {
499
541
  const logs = collectedLogEvents.splice(0, collectedLogEvents.length);
500
- dispatch({type: "LOGS", data: logs});
542
+ dispatch({ type: "LOGS", data: logs });
501
543
  }
502
544
  }
503
545
  logPusher = setInterval(emitLogs, 500);
@@ -508,7 +550,7 @@ const useTaskEventStream = (taskId) => {
508
550
  return collectedLogEvents.push(event);
509
551
  case "completion":
510
552
  emitLogs();
511
- dispatch({type: "COMPLETED", data: event});
553
+ dispatch({ type: "COMPLETED", data: event });
512
554
  return void 0;
513
555
  default:
514
556
  throw new Error(`Unhandled event type ${event.type} in observer`);
@@ -516,12 +558,12 @@ const useTaskEventStream = (taskId) => {
516
558
  },
517
559
  error: (error) => {
518
560
  emitLogs();
519
- dispatch({type: "ERROR", data: error});
561
+ dispatch({ type: "ERROR", data: error });
520
562
  }
521
563
  });
522
564
  }, (error) => {
523
565
  if (!didCancel) {
524
- dispatch({type: "ERROR", data: error});
566
+ dispatch({ type: "ERROR", data: error });
525
567
  }
526
568
  });
527
569
  return () => {
@@ -548,7 +590,7 @@ const useStyles$2 = makeStyles({
548
590
  }
549
591
  });
550
592
  const IconLink = (props) => {
551
- const {href, text, Icon, ...linkProps} = props;
593
+ const { href, text, Icon, ...linkProps } = props;
552
594
  const classes = useStyles$2();
553
595
  return /* @__PURE__ */ React.createElement(Grid, {
554
596
  container: true,
@@ -567,9 +609,9 @@ const IconLink = (props) => {
567
609
  }, text || href)));
568
610
  };
569
611
 
570
- const TaskPageLinks = ({output}) => {
571
- const {entityRef: entityRefOutput, remoteUrl} = output;
572
- let {links = []} = output;
612
+ const TaskPageLinks = ({ output }) => {
613
+ const { entityRef: entityRefOutput, remoteUrl } = output;
614
+ let { links = [] } = output;
573
615
  const app = useApp();
574
616
  const entityRoute = useRouteRef(entityRouteRef);
575
617
  const iconResolver = (key) => {
@@ -577,7 +619,7 @@ const TaskPageLinks = ({output}) => {
577
619
  return key ? (_a = app.getSystemIcon(key)) != null ? _a : LanguageIcon : LanguageIcon;
578
620
  };
579
621
  if (remoteUrl) {
580
- links = [{url: remoteUrl, title: "Repo"}, ...links];
622
+ links = [{ url: remoteUrl, title: "Repo" }, ...links];
581
623
  }
582
624
  if (entityRefOutput) {
583
625
  links = [
@@ -592,14 +634,14 @@ const TaskPageLinks = ({output}) => {
592
634
  return /* @__PURE__ */ React.createElement(Box, {
593
635
  px: 3,
594
636
  pb: 3
595
- }, links.filter(({url, entityRef}) => url || entityRef).map(({url, entityRef, title, icon}) => {
637
+ }, links.filter(({ url, entityRef }) => url || entityRef).map(({ url, entityRef, title, icon }) => {
596
638
  if (entityRef) {
597
639
  const entityName = parseEntityName(entityRef);
598
640
  const target = entityRoute(entityName);
599
- return {title, icon, url: target};
641
+ return { title, icon, url: target };
600
642
  }
601
- return {title, icon, url};
602
- }).map(({url, title, icon}, i) => /* @__PURE__ */ React.createElement(IconLink, {
643
+ return { title, icon, url };
644
+ }).map(({ url, title, icon }, i) => /* @__PURE__ */ React.createElement(IconLink, {
603
645
  key: `output-link-${i}`,
604
646
  href: url,
605
647
  text: title != null ? title : url,
@@ -608,15 +650,14 @@ const TaskPageLinks = ({output}) => {
608
650
  })));
609
651
  };
610
652
 
611
- const LazyLog = React.lazy(() => import('react-lazylog/build/LazyLog'));
612
653
  const humanizeDuration = require("humanize-duration");
613
654
  const useStyles$1 = makeStyles$1((theme) => createStyles({
614
655
  root: {
615
656
  width: "100%"
616
657
  },
617
658
  button: {
618
- marginTop: theme.spacing(1),
619
- marginRight: theme.spacing(1)
659
+ marginBottom: theme.spacing(2),
660
+ marginLeft: theme.spacing(2)
620
661
  },
621
662
  actionsContainer: {
622
663
  marginBottom: theme.spacing(2)
@@ -634,7 +675,7 @@ const useStyles$1 = makeStyles$1((theme) => createStyles({
634
675
  width: "100%"
635
676
  }
636
677
  }));
637
- const StepTimeTicker = ({step}) => {
678
+ const StepTimeTicker = ({ step }) => {
638
679
  const [time, setTime] = useState("");
639
680
  useInterval(() => {
640
681
  if (!step.startedAt) {
@@ -644,7 +685,7 @@ const StepTimeTicker = ({step}) => {
644
685
  const end = step.endedAt ? DateTime.fromISO(step.endedAt) : DateTime.local();
645
686
  const startedAt = DateTime.fromISO(step.startedAt);
646
687
  const formatted = Interval.fromDateTimes(startedAt, end).toDuration().valueOf();
647
- setTime(humanizeDuration(formatted, {round: true}));
688
+ setTime(humanizeDuration(formatted, { round: true }));
648
689
  }, 1e3);
649
690
  return /* @__PURE__ */ React.createElement(Typography$1, {
650
691
  variant: "caption"
@@ -666,7 +707,7 @@ const useStepIconStyles = makeStyles$1((theme) => createStyles({
666
707
  }));
667
708
  function TaskStepIconComponent(props) {
668
709
  const classes = useStepIconStyles();
669
- const {active, completed, error} = props;
710
+ const { active, completed, error } = props;
670
711
  const getMiddle = () => {
671
712
  if (active) {
672
713
  return /* @__PURE__ */ React.createElement(CircularProgress, {
@@ -729,24 +770,14 @@ const TaskStatusStepper = memo(({
729
770
  })))));
730
771
  })));
731
772
  });
732
- const TaskLogger = memo(({log}) => {
733
- return /* @__PURE__ */ React.createElement(Suspense, {
734
- fallback: /* @__PURE__ */ React.createElement(Progress, null)
735
- }, /* @__PURE__ */ React.createElement("div", {
736
- style: {height: "80vh"}
737
- }, /* @__PURE__ */ React.createElement(LazyLog, {
738
- text: log,
739
- extraLines: 1,
740
- follow: true,
741
- selectableLines: true,
742
- enableSearch: true
743
- })));
744
- });
745
- const hasLinks = ({entityRef, remoteUrl, links = []}) => !!(entityRef || remoteUrl || links.length > 0);
773
+ const hasLinks = ({ entityRef, remoteUrl, links = [] }) => !!(entityRef || remoteUrl || links.length > 0);
746
774
  const TaskPage = () => {
775
+ const classes = useStyles$1();
776
+ const navigate = useNavigate();
777
+ const rootLink = useRouteRef(rootRouteRef);
747
778
  const [userSelectedStepId, setUserSelectedStepId] = useState(void 0);
748
779
  const [lastActiveStepId, setLastActiveStepId] = useState(void 0);
749
- const {taskId} = useParams$1();
780
+ const { taskId } = useParams$1();
750
781
  const taskStream = useTaskEventStream(taskId);
751
782
  const completed = taskStream.completed;
752
783
  const steps = useMemo(() => {
@@ -780,7 +811,17 @@ const TaskPage = () => {
780
811
  return log.join("\n");
781
812
  }, [taskStream.stepLogs, currentStepId]);
782
813
  const taskNotFound = taskStream.completed === true && taskStream.loading === false && !taskStream.task;
783
- const {output} = taskStream;
814
+ const { output } = taskStream;
815
+ const handleStartOver = () => {
816
+ var _a, _b;
817
+ if (!taskStream.task || !((_b = (_a = taskStream.task) == null ? void 0 : _a.spec.metadata) == null ? void 0 : _b.name)) {
818
+ navigate(generatePath(rootLink()));
819
+ }
820
+ const formData = taskStream.task.spec.apiVersion === "backstage.io/v1beta2" ? taskStream.task.spec.values : taskStream.task.spec.parameters;
821
+ navigate(generatePath(`${rootLink()}/templates/:templateName?${qs.stringify({ formData })}`, {
822
+ templateName: taskStream.task.spec.metadata.name
823
+ }));
824
+ };
784
825
  return /* @__PURE__ */ React.createElement(Page, {
785
826
  themeId: "home"
786
827
  }, /* @__PURE__ */ React.createElement(Header, {
@@ -805,12 +846,20 @@ const TaskPage = () => {
805
846
  onUserStepChange: setUserSelectedStepId
806
847
  }), output && hasLinks(output) && /* @__PURE__ */ React.createElement(TaskPageLinks, {
807
848
  output
808
- }))), /* @__PURE__ */ React.createElement(Grid$1, {
849
+ }), /* @__PURE__ */ React.createElement(Button, {
850
+ className: classes.button,
851
+ onClick: handleStartOver,
852
+ disabled: !completed,
853
+ variant: "contained",
854
+ color: "primary"
855
+ }, "Start Over"))), /* @__PURE__ */ React.createElement(Grid$1, {
809
856
  item: true,
810
857
  xs: 9
811
- }, /* @__PURE__ */ React.createElement(TaskLogger, {
812
- log: logAsString
813
- }))))));
858
+ }, /* @__PURE__ */ React.createElement("div", {
859
+ style: { height: "80vh" }
860
+ }, /* @__PURE__ */ React.createElement(LogViewer, {
861
+ text: logAsString
862
+ })))))));
814
863
  };
815
864
 
816
865
  const useStyles = makeStyles((theme) => ({
@@ -837,7 +886,7 @@ const useStyles = makeStyles((theme) => ({
837
886
  const ActionsPage = () => {
838
887
  const api = useApi(scaffolderApiRef);
839
888
  const classes = useStyles();
840
- const {loading, value, error} = useAsync(async () => {
889
+ const { loading, value, error } = useAsync(async () => {
841
890
  return api.listActions();
842
891
  });
843
892
  if (loading) {
@@ -921,7 +970,7 @@ const ActionsPage = () => {
921
970
  }), /* @__PURE__ */ React.createElement(Content, null, items));
922
971
  };
923
972
 
924
- const Router = ({TemplateCardComponent}) => {
973
+ const Router = ({ TemplateCardComponent, groups }) => {
925
974
  const outlet = useOutlet();
926
975
  const customFieldExtensions = useElementFilter(outlet, (elements) => elements.selectByComponentData({
927
976
  key: FIELD_EXTENSION_WRAPPER_KEY
@@ -930,12 +979,13 @@ const Router = ({TemplateCardComponent}) => {
930
979
  }));
931
980
  const fieldExtensions = [
932
981
  ...customFieldExtensions,
933
- ...DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS.filter(({name}) => !customFieldExtensions.some((customFieldExtension) => customFieldExtension.name === name))
982
+ ...DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS.filter(({ name }) => !customFieldExtensions.some((customFieldExtension) => customFieldExtension.name === name))
934
983
  ];
935
984
  return /* @__PURE__ */ React.createElement(Routes, null, /* @__PURE__ */ React.createElement(Route, {
936
985
  path: "/",
937
986
  element: /* @__PURE__ */ React.createElement(ScaffolderPage, {
938
- TemplateCardComponent
987
+ TemplateCardComponent,
988
+ groups
939
989
  })
940
990
  }), /* @__PURE__ */ React.createElement(Route, {
941
991
  path: "/templates/:templateName",
@@ -952,4 +1002,4 @@ const Router = ({TemplateCardComponent}) => {
952
1002
  };
953
1003
 
954
1004
  export { Router };
955
- //# sourceMappingURL=Router-5919d585.esm.js.map
1005
+ //# sourceMappingURL=Router-a8e778fd.esm.js.map