@backstage/plugin-scaffolder 0.14.0 → 1.0.0

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,124 @@
1
1
  # @backstage/plugin-scaffolder
2
2
 
3
+ ## 1.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - b58c70c223: This package has been promoted to v1.0! To understand how this change affects the package, please check out our [versioning policy](https://backstage.io/docs/overview/versioning-policy).
8
+
9
+ ### Minor Changes
10
+
11
+ - 9a408928a1: **BREAKING**: Removed the unused `titleComponent` property of `groups` passed to the `ScaffolderPage`. The property was already ignored, but existing usage should migrated to use the `title` property instead, which now accepts any `ReactNode`.
12
+
13
+ ### Patch Changes
14
+
15
+ - 9b7e361783: Remove beta labels
16
+ - a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3
17
+ - 20a262c214: The `ScaffolderPage` now uses the `CatalogFilterLayout`, which means the filters are put in a drawer on smaller screens.
18
+ - f24ef7864e: Minor typo fixes
19
+ - d8716924d6: Implement a template preview page (`/create/preview`) to test creating form UIs
20
+ - Updated dependencies
21
+ - @backstage/core-components@0.9.2
22
+ - @backstage/core-plugin-api@1.0.0
23
+ - @backstage/integration-react@1.0.0
24
+ - @backstage/plugin-catalog-react@1.0.0
25
+ - @backstage/plugin-permission-react@0.3.4
26
+ - @backstage/catalog-model@1.0.0
27
+ - @backstage/plugin-scaffolder-common@1.0.0
28
+ - @backstage/integration@1.0.0
29
+ - @backstage/catalog-client@1.0.0
30
+ - @backstage/config@1.0.0
31
+ - @backstage/errors@1.0.0
32
+ - @backstage/types@1.0.0
33
+ - @backstage/plugin-catalog-common@1.0.0
34
+
35
+ ## 0.15.0
36
+
37
+ ### Minor Changes
38
+
39
+ - 310e905998: The following deprecations are now breaking and have been removed:
40
+
41
+ - **BREAKING**: Support for `backstage.io/v1beta2` Software Templates has been removed. Please migrate your legacy templates to the new `scaffolder.backstage.io/v1beta3` `apiVersion` by following the [migration guide](https://backstage.io/docs/features/software-templates/migrating-from-v1beta2-to-v1beta3)
42
+
43
+ - **BREAKING**: Removed the deprecated `TemplateMetadata`. Please use `TemplateInfo` instead.
44
+
45
+ - **BREAKING**: Removed the deprecated `context.baseUrl`. It's now available on `context.templateInfo.baseUrl`.
46
+
47
+ - **BREAKING**: Removed the deprecated `DispatchResult`, use `TaskBrokerDispatchResult` instead.
48
+
49
+ - **BREAKING**: Removed the deprecated `runCommand`, use `executeShellCommond` instead.
50
+
51
+ - **BREAKING**: Removed the deprecated `Status` in favour of `TaskStatus` instead.
52
+
53
+ - **BREAKING**: Removed the deprecated `TaskState` in favour of `CurrentClaimedTask` instead.
54
+
55
+ - 1360f7d73a: **BREAKING**: Removed `ScaffolderTaskOutput.entityRef` and `ScaffolderTaskOutput.remoteUrl`, which both have been deprecated for over a year. Please use the `links` output instead.
56
+ - e63e5a9452: Removed the following previously deprecated exports:
57
+
58
+ - **BREAKING**: Removed the deprecated `TemplateList` component and the `TemplateListProps` type. Please use the `TemplateCard` to create your own list component instead to render these lists.
59
+
60
+ - **BREAKING**: Removed the deprecated `setSecret` method, please use `setSecrets` instead.
61
+
62
+ - **BREAKING**: Removed the deprecated `TemplateCardComponent` and `TaskPageComponent` props from the `ScaffolderPage` component. These are now provided using the `components` prop with the shape `{{ TemplateCardComponent: () => JSX.Element, TaskPageComponent: () => JSX.Element }}`
63
+
64
+ - **BREAKING**: Removed `JobStatus` as this type was actually a legacy type used in `v1alpha` templates and the workflow engine and should no longer be used or depended on.
65
+
66
+ ### Patch Changes
67
+
68
+ - d741c97b98: Render markdown for description in software templates
69
+ - 33e58456b5: Fixing the border color for the `FavoriteEntity` star button on the `TemplateCard`
70
+ - Updated dependencies
71
+ - @backstage/plugin-catalog-react@0.9.0
72
+ - @backstage/core-components@0.9.1
73
+ - @backstage/plugin-scaffolder-common@0.3.0
74
+ - @backstage/catalog-model@0.13.0
75
+ - @backstage/plugin-catalog-common@0.2.2
76
+ - @backstage/catalog-client@0.9.0
77
+ - @backstage/integration-react@0.1.25
78
+
79
+ ## 0.15.0-next.0
80
+
81
+ ### Minor Changes
82
+
83
+ - 310e905998: The following deprecations are now breaking and have been removed:
84
+
85
+ - **BREAKING**: Support for `backstage.io/v1beta2` Software Templates has been removed. Please migrate your legacy templates to the new `scaffolder.backstage.io/v1beta3` `apiVersion` by following the [migration guide](https://backstage.io/docs/features/software-templates/migrating-from-v1beta2-to-v1beta3)
86
+
87
+ - **BREAKING**: Removed the deprecated `TemplateMetadata`. Please use `TemplateInfo` instead.
88
+
89
+ - **BREAKING**: Removed the deprecated `context.baseUrl`. It's now available on `context.templateInfo.baseUrl`.
90
+
91
+ - **BREAKING**: Removed the deprecated `DispatchResult`, use `TaskBrokerDispatchResult` instead.
92
+
93
+ - **BREAKING**: Removed the deprecated `runCommand`, use `executeShellCommond` instead.
94
+
95
+ - **BREAKING**: Removed the deprecated `Status` in favour of `TaskStatus` instead.
96
+
97
+ - **BREAKING**: Removed the deprecated `TaskState` in favour of `CurrentClaimedTask` instead.
98
+
99
+ - 1360f7d73a: **BREAKING**: Removed `ScaffolderTaskOutput.entityRef` and `ScaffolderTaskOutput.remoteUrl`, which both have been deprecated for over a year. Please use the `links` output instead.
100
+ - e63e5a9452: Removed the following previously deprecated exports:
101
+
102
+ - **BREAKING**: Removed the deprecated `TemplateList` component and the `TemplateListProps` type. Please use the `TemplateCard` to create your own list component instead to render these lists.
103
+
104
+ - **BREAKING**: Removed the deprecated `setSecret` method, please use `setSecrets` instead.
105
+
106
+ - **BREAKING**: Removed the deprecated `TemplateCardComponent` and `TaskPageComponent` props from the `ScaffolderPage` component. These are now provided using the `components` prop with the shape `{{ TemplateCardComponent: () => JSX.Element, TaskPageComponent: () => JSX.Element }}`
107
+
108
+ - **BREAKING**: Removed `JobStatus` as this type was actually a legacy type used in `v1alpha` templates and the workflow engine and should no longer be used or depended on.
109
+
110
+ ### Patch Changes
111
+
112
+ - d741c97b98: Render markdown for description in software templates
113
+ - Updated dependencies
114
+ - @backstage/plugin-catalog-react@0.9.0-next.0
115
+ - @backstage/core-components@0.9.1-next.0
116
+ - @backstage/plugin-scaffolder-common@0.3.0-next.0
117
+ - @backstage/catalog-model@0.13.0-next.0
118
+ - @backstage/plugin-catalog-common@0.2.2-next.0
119
+ - @backstage/catalog-client@0.9.0-next.0
120
+ - @backstage/integration-react@0.1.25-next.0
121
+
3
122
  ## 0.14.0
4
123
 
5
124
  ### Minor Changes
@@ -1,10 +1,13 @@
1
1
  import React, { useState, useContext, useCallback } from 'react';
2
2
  import { useNavigate, Navigate, useOutlet, Routes, Route } from 'react-router';
3
- import { Page, Header, Lifecycle, Content, ContentHeader, CreateButton, SupportButton, MarkdownContent, StructuredMetadataTable, InfoCard, Progress, ErrorPage } from '@backstage/core-components';
4
- import { useRouteRef, useApi, errorApiRef, featureFlagsApiRef, useApiHolder, useElementFilter } from '@backstage/core-plugin-api';
5
- import { EntityListProvider, EntitySearchBar, EntityKindPicker, UserListPicker, EntityTagPicker } from '@backstage/plugin-catalog-react';
6
- import { makeStyles, Stepper, Step, StepLabel, Typography, StepContent, Button, Paper, Box, LinearProgress, TableContainer, Table, TableHead, TableRow, TableCell, TableBody } from '@material-ui/core';
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 SecretsContext, s as scaffolderApiRef, g as scaffolderTaskRouteRef, h as rootRouteRef, F as FIELD_EXTENSION_WRAPPER_KEY, i as FIELD_EXTENSION_KEY, j as SecretsContextProvider, k as TaskPage } from './index-da137e20.esm.js';
3
+ import { ItemCardHeader, MarkdownContent, Button, ContentHeader, Progress, WarningPanel, Link as Link$1, Content, ItemCardGrid, Page, Header, CreateButton, SupportButton, StructuredMetadataTable, InfoCard, ErrorPage } from '@backstage/core-components';
4
+ import { useRouteRef, useApi, errorApiRef, featureFlagsApiRef, useApiHolder, alertApiRef, useElementFilter } from '@backstage/core-plugin-api';
5
+ import { getEntityRelations, getEntitySourceLocation, FavoriteEntity, EntityRefLinks, useEntityList, EntityListProvider, CatalogFilterLayout, EntitySearchBar, EntityKindPicker, UserListPicker, EntityTagPicker, catalogApiRef, humanizeEntityRef } from '@backstage/plugin-catalog-react';
6
+ 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, s as selectedTemplateRouteRef, d as registerComponentRouteRef, T as TemplateTypePicker, S as SecretsContext, f as scaffolderApiRef, g as scaffolderTaskRouteRef, h as rootRouteRef, F as FIELD_EXTENSION_WRAPPER_KEY, i as FIELD_EXTENSION_KEY, j as SecretsContextProvider, k as TaskPage } from './index-25fdb62e.esm.js';
7
+ import { RELATION_OWNED_BY, stringifyEntityRef } from '@backstage/catalog-model';
8
+ import { makeStyles, useTheme, Card, CardMedia, CardContent, Box, Typography, Chip, CardActions, IconButton, Tooltip, Link, Stepper, Step, StepLabel, StepContent, Button as Button$1, Paper, LinearProgress, TableContainer, Table, TableHead, TableRow, TableCell, TableBody, Grid, FormControl, InputLabel, Select, MenuItem } from '@material-ui/core';
9
+ import { scmIntegrationsApiRef, ScmIntegrationIcon } from '@backstage/integration-react';
10
+ import WarningIcon from '@material-ui/icons/Warning';
8
11
  import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common';
9
12
  import { usePermission } from '@backstage/plugin-permission-react';
10
13
  import qs from 'qs';
@@ -13,20 +16,22 @@ import useAsync from 'react-use/lib/useAsync';
13
16
  import { withTheme } from '@rjsf/core';
14
17
  import { Theme } from '@rjsf/material-ui';
15
18
  import cloneDeep from 'lodash/cloneDeep';
16
- import { stringifyEntityRef } from '@backstage/catalog-model';
17
19
  import classNames from 'classnames';
20
+ import useDebounce from 'react-use/lib/useDebounce';
21
+ import { yaml as yaml$1 } from '@codemirror/legacy-modes/mode/yaml';
22
+ import { showPanel } from '@codemirror/panel';
23
+ import { StreamLanguage } from '@codemirror/stream-parser';
24
+ import CodeMirror from '@uiw/react-codemirror';
25
+ import yaml from 'yaml';
18
26
  import '@backstage/errors';
19
27
  import 'zen-observable';
20
28
  import '@material-ui/core/FormControl';
21
29
  import '@material-ui/lab/Autocomplete';
22
30
  import 'react-use/lib/useEffectOnce';
23
31
  import '@material-ui/lab';
24
- import '@backstage/integration-react';
25
32
  import '@material-ui/core/FormHelperText';
26
33
  import '@material-ui/core/Input';
27
34
  import '@material-ui/core/InputLabel';
28
- import 'react-use/lib/useDebounce';
29
- import '@material-ui/icons/Warning';
30
35
  import 'lodash/capitalize';
31
36
  import '@material-ui/icons/CheckBox';
32
37
  import '@material-ui/icons/CheckBoxOutlineBlank';
@@ -74,19 +79,174 @@ const DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS = [
74
79
  }
75
80
  ];
76
81
 
77
- const useStyles$1 = makeStyles((theme) => ({
78
- contentWrapper: {
79
- display: "grid",
80
- gridTemplateAreas: "'filters' 'grid'",
81
- gridTemplateColumns: "250px 1fr",
82
- gridColumnGap: theme.spacing(2)
82
+ const useStyles$2 = makeStyles((theme) => ({
83
+ cardHeader: {
84
+ position: "relative"
85
+ },
86
+ title: {
87
+ backgroundImage: ({ backgroundImage }) => backgroundImage
88
+ },
89
+ box: {
90
+ overflow: "hidden",
91
+ textOverflow: "ellipsis",
92
+ display: "-webkit-box",
93
+ "-webkit-line-clamp": 10,
94
+ "-webkit-box-orient": "vertical",
95
+ paddingBottom: "0.8em"
96
+ },
97
+ label: {
98
+ color: theme.palette.text.secondary,
99
+ textTransform: "uppercase",
100
+ fontSize: "0.65rem",
101
+ fontWeight: "bold",
102
+ letterSpacing: 0.5,
103
+ lineHeight: 1,
104
+ paddingBottom: "0.2rem"
105
+ },
106
+ leftButton: {
107
+ marginRight: "auto"
108
+ },
109
+ starButton: {
110
+ position: "absolute",
111
+ top: theme.spacing(0.5),
112
+ right: theme.spacing(0.5),
113
+ padding: "0.25rem",
114
+ color: "#fff"
115
+ }
116
+ }));
117
+ const useDeprecationStyles = makeStyles((theme) => ({
118
+ deprecationIcon: {
119
+ position: "absolute",
120
+ top: theme.spacing(0.5),
121
+ right: theme.spacing(3.5),
122
+ padding: "0.25rem"
123
+ },
124
+ link: {
125
+ color: theme.palette.warning.light
83
126
  }
84
127
  }));
128
+ const getTemplateCardProps = (template) => {
129
+ var _a, _b, _c, _d, _e;
130
+ return {
131
+ key: template.metadata.uid,
132
+ name: template.metadata.name,
133
+ title: `${(_a = template.metadata.title || template.metadata.name) != null ? _a : ""}`,
134
+ type: (_b = template.spec.type) != null ? _b : "",
135
+ description: (_c = template.metadata.description) != null ? _c : "-",
136
+ tags: (_e = (_d = template.metadata) == null ? void 0 : _d.tags) != null ? _e : []
137
+ };
138
+ };
139
+ const DeprecationWarning = () => {
140
+ const styles = useDeprecationStyles();
141
+ const Title = /* @__PURE__ */ React.createElement(Typography, {
142
+ style: { padding: 10, maxWidth: 300 }
143
+ }, "This template uses a syntax that has been deprecated, and should be migrated to a newer syntax. Click for more info.");
144
+ return /* @__PURE__ */ React.createElement("div", {
145
+ className: styles.deprecationIcon
146
+ }, /* @__PURE__ */ React.createElement(Tooltip, {
147
+ title: Title
148
+ }, /* @__PURE__ */ React.createElement(Link, {
149
+ href: "https://backstage.io/docs/features/software-templates/migrating-from-v1beta2-to-v1beta3",
150
+ className: styles.link
151
+ }, /* @__PURE__ */ React.createElement(WarningIcon, null))));
152
+ };
153
+ const TemplateCard = ({ template, deprecated }) => {
154
+ var _a;
155
+ const backstageTheme = useTheme();
156
+ const templateRoute = useRouteRef(selectedTemplateRouteRef);
157
+ const templateProps = getTemplateCardProps(template);
158
+ const ownedByRelations = getEntityRelations(template, RELATION_OWNED_BY);
159
+ const themeId = backstageTheme.getPageTheme({ themeId: templateProps.type }) ? templateProps.type : "other";
160
+ const theme = backstageTheme.getPageTheme({ themeId });
161
+ const classes = useStyles$2({ backgroundImage: theme.backgroundImage });
162
+ const href = templateRoute({ templateName: templateProps.name });
163
+ const scmIntegrationsApi = useApi(scmIntegrationsApiRef);
164
+ const sourceLocation = getEntitySourceLocation(template, scmIntegrationsApi);
165
+ return /* @__PURE__ */ React.createElement(Card, null, /* @__PURE__ */ React.createElement(CardMedia, {
166
+ className: classes.cardHeader
167
+ }, /* @__PURE__ */ React.createElement(FavoriteEntity, {
168
+ className: classes.starButton,
169
+ entity: template
170
+ }), deprecated && /* @__PURE__ */ React.createElement(DeprecationWarning, null), /* @__PURE__ */ React.createElement(ItemCardHeader, {
171
+ title: templateProps.title,
172
+ subtitle: templateProps.type,
173
+ classes: { root: classes.title }
174
+ })), /* @__PURE__ */ React.createElement(CardContent, {
175
+ style: { display: "grid" }
176
+ }, /* @__PURE__ */ React.createElement(Box, {
177
+ className: classes.box
178
+ }, /* @__PURE__ */ React.createElement(Typography, {
179
+ variant: "body2",
180
+ className: classes.label
181
+ }, "Description"), /* @__PURE__ */ React.createElement(MarkdownContent, {
182
+ content: templateProps.description
183
+ })), /* @__PURE__ */ React.createElement(Box, {
184
+ className: classes.box
185
+ }, /* @__PURE__ */ React.createElement(Typography, {
186
+ variant: "body2",
187
+ className: classes.label
188
+ }, "Owner"), /* @__PURE__ */ React.createElement(EntityRefLinks, {
189
+ entityRefs: ownedByRelations,
190
+ defaultKind: "Group"
191
+ })), /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Typography, {
192
+ variant: "body2",
193
+ className: classes.label
194
+ }, "Tags"), (_a = templateProps.tags) == null ? void 0 : _a.map((tag) => /* @__PURE__ */ React.createElement(Chip, {
195
+ size: "small",
196
+ label: tag,
197
+ key: tag
198
+ })))), /* @__PURE__ */ React.createElement(CardActions, null, sourceLocation && /* @__PURE__ */ React.createElement(IconButton, {
199
+ className: classes.leftButton,
200
+ href: sourceLocation.locationTargetUrl
201
+ }, /* @__PURE__ */ React.createElement(ScmIntegrationIcon, {
202
+ type: sourceLocation.integrationType
203
+ })), /* @__PURE__ */ React.createElement(Button, {
204
+ color: "primary",
205
+ to: href,
206
+ "aria-label": `Choose ${templateProps.title}`
207
+ }, "Choose")));
208
+ };
209
+
210
+ const TemplateList = ({
211
+ TemplateCardComponent,
212
+ group
213
+ }) => {
214
+ const { loading, error, entities } = useEntityList();
215
+ const Card = TemplateCardComponent || TemplateCard;
216
+ const maybeFilteredEntities = group ? entities.filter((e) => group.filter(e)) : entities;
217
+ const titleComponent = (() => {
218
+ if (group && group.title) {
219
+ if (typeof group.title === "string") {
220
+ return /* @__PURE__ */ React.createElement(ContentHeader, {
221
+ title: group.title
222
+ });
223
+ }
224
+ return group.title;
225
+ }
226
+ return /* @__PURE__ */ React.createElement(ContentHeader, {
227
+ title: "Other Templates"
228
+ });
229
+ })();
230
+ if (group && maybeFilteredEntities.length === 0) {
231
+ return null;
232
+ }
233
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, loading && /* @__PURE__ */ React.createElement(Progress, null), error && /* @__PURE__ */ React.createElement(WarningPanel, {
234
+ title: "Oops! Something went wrong loading the templates"
235
+ }, error.message), !error && !loading && !entities.length && /* @__PURE__ */ React.createElement(Typography, {
236
+ variant: "body2"
237
+ }, "No templates found that match your filter. Learn more about", " ", /* @__PURE__ */ React.createElement(Link$1, {
238
+ to: "https://backstage.io/docs/features/software-templates/adding-templates"
239
+ }, "adding templates"), "."), /* @__PURE__ */ React.createElement(Content, null, titleComponent, /* @__PURE__ */ React.createElement(ItemCardGrid, null, maybeFilteredEntities && (maybeFilteredEntities == null ? void 0 : maybeFilteredEntities.length) > 0 && maybeFilteredEntities.map((template) => /* @__PURE__ */ React.createElement(Card, {
240
+ key: stringifyEntityRef(template),
241
+ template,
242
+ deprecated: template.apiVersion === "backstage.io/v1beta2"
243
+ })))));
244
+ };
245
+
85
246
  const ScaffolderPageContents = ({
86
247
  TemplateCardComponent,
87
248
  groups
88
249
  }) => {
89
- const styles = useStyles$1();
90
250
  const registerComponentLink = useRouteRef(registerComponentRouteRef);
91
251
  const otherTemplatesGroup = {
92
252
  title: groups ? "Other Templates" : "Templates",
@@ -100,24 +260,20 @@ const ScaffolderPageContents = ({
100
260
  themeId: "home"
101
261
  }, /* @__PURE__ */ React.createElement(Header, {
102
262
  pageTitleOverride: "Create a New Component",
103
- title: /* @__PURE__ */ React.createElement(React.Fragment, null, "Create a New Component ", /* @__PURE__ */ React.createElement(Lifecycle, {
104
- shorthand: true
105
- })),
263
+ title: "Create a New Component",
106
264
  subtitle: "Create new software components using standard templates"
107
265
  }), /* @__PURE__ */ React.createElement(Content, null, /* @__PURE__ */ React.createElement(ContentHeader, {
108
266
  title: "Available Templates"
109
267
  }, allowed && /* @__PURE__ */ React.createElement(CreateButton, {
110
268
  title: "Register Existing Component",
111
269
  to: registerComponentLink && registerComponentLink()
112
- }), /* @__PURE__ */ React.createElement(SupportButton, null, "Create new software components using standard templates. Different templates create different kinds of components (services, websites, documentation, ...).")), /* @__PURE__ */ React.createElement("div", {
113
- className: styles.contentWrapper
114
- }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(EntitySearchBar, null), /* @__PURE__ */ React.createElement(EntityKindPicker, {
270
+ }), /* @__PURE__ */ React.createElement(SupportButton, null, "Create new software components using standard templates. Different templates create different kinds of components (services, websites, documentation, ...).")), /* @__PURE__ */ React.createElement(CatalogFilterLayout, null, /* @__PURE__ */ React.createElement(CatalogFilterLayout.Filters, null, /* @__PURE__ */ React.createElement(EntitySearchBar, null), /* @__PURE__ */ React.createElement(EntityKindPicker, {
115
271
  initialFilter: "template",
116
272
  hidden: true
117
273
  }), /* @__PURE__ */ React.createElement(UserListPicker, {
118
274
  initialFilter: "all",
119
275
  availableFilters: ["all", "starred"]
120
- }), /* @__PURE__ */ React.createElement(TemplateTypePicker, null), /* @__PURE__ */ React.createElement(EntityTagPicker, null)), /* @__PURE__ */ React.createElement("div", null, groups && groups.map((group, index) => /* @__PURE__ */ React.createElement(TemplateList, {
276
+ }), /* @__PURE__ */ React.createElement(TemplateTypePicker, null), /* @__PURE__ */ React.createElement(EntityTagPicker, null)), /* @__PURE__ */ React.createElement(CatalogFilterLayout.Content, null, groups && groups.map((group, index) => /* @__PURE__ */ React.createElement(TemplateList, {
121
277
  key: index,
122
278
  TemplateCardComponent,
123
279
  group
@@ -309,6 +465,9 @@ const MultistepJsonForm = (props) => {
309
465
  };
310
466
  const handleBack = () => setActiveStep(Math.max(activeStep - 1, 0));
311
467
  const handleCreate = async () => {
468
+ if (!onFinish) {
469
+ return;
470
+ }
312
471
  setDisableButtons(true);
313
472
  try {
314
473
  await onFinish();
@@ -345,10 +504,10 @@ const MultistepJsonForm = (props) => {
345
504
  },
346
505
  ...formProps,
347
506
  ...transformSchemaToProps(schema)
348
- }, /* @__PURE__ */ React.createElement(Button, {
507
+ }, /* @__PURE__ */ React.createElement(Button$1, {
349
508
  disabled: activeStep === 0,
350
509
  onClick: handleBack
351
- }, "Back"), /* @__PURE__ */ React.createElement(Button, {
510
+ }, "Back"), /* @__PURE__ */ React.createElement(Button$1, {
352
511
  variant: "contained",
353
512
  color: "primary",
354
513
  type: "submit"
@@ -363,17 +522,17 @@ const MultistepJsonForm = (props) => {
363
522
  metadata: getReviewData(formData, steps)
364
523
  }), /* @__PURE__ */ React.createElement(Box, {
365
524
  mb: 4
366
- }), /* @__PURE__ */ React.createElement(Button, {
525
+ }), /* @__PURE__ */ React.createElement(Button$1, {
367
526
  onClick: handleBack,
368
527
  disabled: disableButtons
369
- }, "Back"), /* @__PURE__ */ React.createElement(Button, {
528
+ }, "Back"), /* @__PURE__ */ React.createElement(Button$1, {
370
529
  onClick: handleReset,
371
530
  disabled: disableButtons
372
- }, "Reset"), /* @__PURE__ */ React.createElement(Button, {
531
+ }, "Reset"), /* @__PURE__ */ React.createElement(Button$1, {
373
532
  variant: "contained",
374
533
  color: "primary",
375
534
  onClick: handleCreate,
376
- disabled: disableButtons
535
+ disabled: !onFinish || disableButtons
377
536
  }, "Create"))));
378
537
  };
379
538
 
@@ -471,9 +630,7 @@ const TemplatePage = ({
471
630
  themeId: "home"
472
631
  }, /* @__PURE__ */ React.createElement(Header, {
473
632
  pageTitleOverride: "Create a New Component",
474
- title: /* @__PURE__ */ React.createElement(React.Fragment, null, "Create a New Component ", /* @__PURE__ */ React.createElement(Lifecycle, {
475
- shorthand: true
476
- })),
633
+ title: "Create a New Component",
477
634
  subtitle: "Create new software components using standard templates"
478
635
  }), /* @__PURE__ */ React.createElement(Content, null, loading && /* @__PURE__ */ React.createElement(LinearProgress, {
479
636
  "data-testid": "loading-progress"
@@ -496,7 +653,7 @@ const TemplatePage = ({
496
653
  }))));
497
654
  };
498
655
 
499
- const useStyles = makeStyles((theme) => ({
656
+ const useStyles$1 = makeStyles((theme) => ({
500
657
  code: {
501
658
  fontFamily: "Menlo, monospace",
502
659
  padding: theme.spacing(1),
@@ -519,7 +676,7 @@ const useStyles = makeStyles((theme) => ({
519
676
  }));
520
677
  const ActionsPage = () => {
521
678
  const api = useApi(scaffolderApiRef);
522
- const classes = useStyles();
679
+ const classes = useStyles$1();
523
680
  const { loading, value, error } = useAsync(async () => {
524
681
  return api.listActions();
525
682
  });
@@ -604,20 +761,169 @@ const ActionsPage = () => {
604
761
  }), /* @__PURE__ */ React.createElement(Content, null, items));
605
762
  };
606
763
 
607
- const Router = (props) => {
608
- var _a;
609
- const {
610
- TemplateCardComponent: legacyTemplateCardComponent,
611
- TaskPageComponent: legacyTaskPageComponent,
612
- groups,
613
- components = {}
614
- } = props;
615
- if (legacyTemplateCardComponent || legacyTaskPageComponent) {
616
- console.warn("DEPRECATION: 'TemplateCardComponent' and 'TaskPageComponent' are deprecated when calling the 'ScaffolderPage'. Use 'components' prop to pass these component overrides instead.");
764
+ const EXAMPLE_TEMPLATE_PARAMS_YAML = `# Edit the template parameters below to see how they will render in the scaffolder form UI
765
+ parameters:
766
+ - title: Fill in some steps
767
+ required:
768
+ - name
769
+ properties:
770
+ name:
771
+ title: Name
772
+ type: string
773
+ description: Unique name of the component
774
+ owner:
775
+ title: Owner
776
+ type: string
777
+ description: Owner of the component
778
+ ui:field: OwnerPicker
779
+ ui:options:
780
+ allowedKinds:
781
+ - Group
782
+ - title: Choose a location
783
+ required:
784
+ - repoUrl
785
+ properties:
786
+ repoUrl:
787
+ title: Repository Location
788
+ type: string
789
+ ui:field: RepoUrlPicker
790
+ ui:options:
791
+ allowedHosts:
792
+ - github.com
793
+ `;
794
+ const useStyles = makeStyles({
795
+ templateSelect: {
796
+ marginBottom: "10px"
797
+ },
798
+ grid: {
799
+ height: "100%"
800
+ },
801
+ codeMirror: {
802
+ height: "95%"
617
803
  }
804
+ });
805
+ const TemplatePreviewPage = ({
806
+ defaultPreviewTemplate = EXAMPLE_TEMPLATE_PARAMS_YAML,
807
+ customFieldExtensions = []
808
+ }) => {
809
+ const classes = useStyles();
810
+ const alertApi = useApi(alertApiRef);
811
+ const catalogApi = useApi(catalogApiRef);
812
+ const apiHolder = useApiHolder();
813
+ const [selectedTemplate, setSelectedTemplate] = useState("");
814
+ const [schema, setSchema] = useState({
815
+ title: "",
816
+ steps: []
817
+ });
818
+ const [templateOptions, setTemplateOptions] = useState([]);
819
+ const [templateYaml, setTemplateYaml] = useState(defaultPreviewTemplate);
820
+ const [formState, setFormState] = useState({});
821
+ const { loading } = useAsync(() => catalogApi.getEntities({
822
+ filter: { kind: "template" },
823
+ fields: [
824
+ "kind",
825
+ "metadata.namespace",
826
+ "metadata.name",
827
+ "metadata.title",
828
+ "spec.parameters"
829
+ ]
830
+ }).then(({ items }) => setTemplateOptions(items.map((template) => {
831
+ var _a;
832
+ return {
833
+ label: (_a = template.metadata.title) != null ? _a : humanizeEntityRef(template, { defaultKind: "template" }),
834
+ value: template
835
+ };
836
+ }))).catch((e) => alertApi.post({
837
+ message: `Error loading exisiting templates: ${e.message}`,
838
+ severity: "error"
839
+ })), [catalogApi]);
840
+ const errorPanel = document.createElement("div");
841
+ errorPanel.style.color = "red";
842
+ useDebounce(() => {
843
+ try {
844
+ const parsedTemplate = yaml.parse(templateYaml);
845
+ setSchema({
846
+ title: "Preview",
847
+ steps: parsedTemplate.parameters.map((param) => ({
848
+ title: param.title,
849
+ schema: param
850
+ }))
851
+ });
852
+ setFormState({});
853
+ } catch (e) {
854
+ errorPanel.textContent = e.message;
855
+ }
856
+ }, 250, [setFormState, setSchema, templateYaml]);
857
+ const handleSelectChange = useCallback((selected) => {
858
+ setSelectedTemplate(selected);
859
+ setTemplateYaml(yaml.stringify(selected.spec));
860
+ }, [setTemplateYaml]);
861
+ const handleFormReset = () => setFormState({});
862
+ const handleFormChange = useCallback((e) => setFormState(e.formData), [setFormState]);
863
+ const handleCodeChange = useCallback((code) => {
864
+ setTemplateYaml(code);
865
+ }, [setTemplateYaml]);
866
+ const customFieldComponents = Object.fromEntries(customFieldExtensions.map(({ name, component }) => [name, component]));
867
+ const customFieldValidators = Object.fromEntries(customFieldExtensions.map(({ name, validation }) => [name, validation]));
868
+ return /* @__PURE__ */ React.createElement(Page, {
869
+ themeId: "home"
870
+ }, /* @__PURE__ */ React.createElement(Header, {
871
+ title: "Template Preview",
872
+ subtitle: "Preview your template parameter UI"
873
+ }), /* @__PURE__ */ React.createElement(Content, null, loading && /* @__PURE__ */ React.createElement(LinearProgress, null), /* @__PURE__ */ React.createElement(Grid, {
874
+ container: true,
875
+ className: classes.grid
876
+ }, /* @__PURE__ */ React.createElement(Grid, {
877
+ item: true,
878
+ xs: 6
879
+ }, /* @__PURE__ */ React.createElement(FormControl, {
880
+ className: classes.templateSelect,
881
+ variant: "outlined",
882
+ fullWidth: true
883
+ }, /* @__PURE__ */ React.createElement(InputLabel, {
884
+ id: "select-template-label"
885
+ }, "Load Existing Template"), /* @__PURE__ */ React.createElement(Select, {
886
+ value: selectedTemplate,
887
+ label: "Load Existing Template",
888
+ labelId: "select-template-label",
889
+ onChange: (e) => handleSelectChange(e.target.value)
890
+ }, templateOptions.map((option, idx) => /* @__PURE__ */ React.createElement(MenuItem, {
891
+ key: idx,
892
+ value: option.value
893
+ }, option.label)))), /* @__PURE__ */ React.createElement(CodeMirror, {
894
+ className: classes.codeMirror,
895
+ value: templateYaml,
896
+ theme: "dark",
897
+ height: "100%",
898
+ extensions: [
899
+ StreamLanguage.define(yaml$1),
900
+ showPanel.of(() => ({ dom: errorPanel, top: true }))
901
+ ],
902
+ onChange: handleCodeChange
903
+ })), /* @__PURE__ */ React.createElement(Grid, {
904
+ item: true,
905
+ xs: 6
906
+ }, schema && /* @__PURE__ */ React.createElement(InfoCard, {
907
+ key: JSON.stringify(schema)
908
+ }, /* @__PURE__ */ React.createElement(MultistepJsonForm, {
909
+ formData: formState,
910
+ fields: customFieldComponents,
911
+ onChange: handleFormChange,
912
+ onReset: handleFormReset,
913
+ steps: schema.steps.map((step) => {
914
+ return {
915
+ ...step,
916
+ validate: createValidator(step.schema, customFieldValidators, { apiHolder })
917
+ };
918
+ })
919
+ }))))));
920
+ };
921
+
922
+ const Router = (props) => {
923
+ const { groups, components = {}, defaultPreviewTemplate } = props;
618
924
  const { TemplateCardComponent, TaskPageComponent } = components;
619
925
  const outlet = useOutlet();
620
- const TaskPageElement = (_a = TaskPageComponent != null ? TaskPageComponent : legacyTaskPageComponent) != null ? _a : TaskPage;
926
+ const TaskPageElement = TaskPageComponent != null ? TaskPageComponent : TaskPage;
621
927
  const customFieldExtensions = useElementFilter(outlet, (elements) => elements.selectByComponentData({
622
928
  key: FIELD_EXTENSION_WRAPPER_KEY
623
929
  }).findComponentData({
@@ -631,7 +937,7 @@ const Router = (props) => {
631
937
  path: "/",
632
938
  element: /* @__PURE__ */ React.createElement(ScaffolderPage, {
633
939
  groups,
634
- TemplateCardComponent: TemplateCardComponent != null ? TemplateCardComponent : legacyTemplateCardComponent
940
+ TemplateCardComponent
635
941
  })
636
942
  }), /* @__PURE__ */ React.createElement(Route, {
637
943
  path: "/templates/:templateName",
@@ -644,8 +950,14 @@ const Router = (props) => {
644
950
  }), /* @__PURE__ */ React.createElement(Route, {
645
951
  path: "/actions",
646
952
  element: /* @__PURE__ */ React.createElement(ActionsPage, null)
953
+ }), /* @__PURE__ */ React.createElement(Route, {
954
+ path: "/preview",
955
+ element: /* @__PURE__ */ React.createElement(SecretsContextProvider, null, /* @__PURE__ */ React.createElement(TemplatePreviewPage, {
956
+ defaultPreviewTemplate,
957
+ customFieldExtensions: fieldExtensions
958
+ }))
647
959
  }));
648
960
  };
649
961
 
650
962
  export { Router };
651
- //# sourceMappingURL=Router-ba25f61c.esm.js.map
963
+ //# sourceMappingURL=Router-773d053b.esm.js.map