@backstage/plugin-scaffolder 1.12.0 → 1.13.0-next.1

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.
Files changed (37) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/alpha/package.json +1 -1
  3. package/dist/alpha.d.ts +12 -59
  4. package/dist/alpha.esm.js +27 -33
  5. package/dist/alpha.esm.js.map +1 -1
  6. package/dist/esm/{alpha/ListTasksPage-2e8f4176.esm.js → ListTasksPage-ca799894.esm.js} +3 -2
  7. package/dist/esm/ListTasksPage-ca799894.esm.js.map +1 -0
  8. package/dist/esm/{index/Router-6fd61bff.esm.js → Router-1b71c4f0.esm.js} +39 -28
  9. package/dist/esm/Router-1b71c4f0.esm.js.map +1 -0
  10. package/dist/esm/TaskPage-589238dc.esm.js +1684 -0
  11. package/dist/esm/TaskPage-589238dc.esm.js.map +1 -0
  12. package/dist/esm/TemplateEditorIntro-644bad26.esm.js +1128 -0
  13. package/dist/esm/TemplateEditorIntro-644bad26.esm.js.map +1 -0
  14. package/dist/esm/TemplateFormPreviewer-32c3dcf3.esm.js +630 -0
  15. package/dist/esm/TemplateFormPreviewer-32c3dcf3.esm.js.map +1 -0
  16. package/dist/esm/TemplateTypePicker-4f07b216.esm.js +58 -0
  17. package/dist/esm/TemplateTypePicker-4f07b216.esm.js.map +1 -0
  18. package/dist/esm/{alpha/index-2131f4a0.esm.js → index-bce9c23d.esm.js} +95 -156
  19. package/dist/esm/index-bce9c23d.esm.js.map +1 -0
  20. package/dist/index.d.ts +28 -167
  21. package/dist/index.esm.js +27 -65
  22. package/dist/index.esm.js.map +1 -1
  23. package/dist/types/plugin.d-bc532f9e.d.ts +232 -0
  24. package/package.json +24 -22
  25. package/dist/esm/alpha/ListTasksPage-2e8f4176.esm.js.map +0 -1
  26. package/dist/esm/alpha/Router-2826a2b8.esm.js +0 -1566
  27. package/dist/esm/alpha/Router-2826a2b8.esm.js.map +0 -1
  28. package/dist/esm/alpha/alpha-714dad1b.esm.js +0 -3422
  29. package/dist/esm/alpha/alpha-714dad1b.esm.js.map +0 -1
  30. package/dist/esm/alpha/index-2131f4a0.esm.js.map +0 -1
  31. package/dist/esm/index/ListTasksPage-fa403ee3.esm.js +0 -192
  32. package/dist/esm/index/ListTasksPage-fa403ee3.esm.js.map +0 -1
  33. package/dist/esm/index/Router-6fd61bff.esm.js.map +0 -1
  34. package/dist/esm/index/index-0b6cdf44.esm.js +0 -3475
  35. package/dist/esm/index/index-0b6cdf44.esm.js.map +0 -1
  36. package/dist/esm/index/index-f404fb0b.esm.js +0 -449
  37. package/dist/esm/index/index-f404fb0b.esm.js.map +0 -1
@@ -1,1566 +0,0 @@
1
- import React, { useState, useCallback, useMemo, Component, useEffect } from 'react';
2
- import { useNavigate, Navigate, useOutlet, Routes, Route } from 'react-router-dom';
3
- import { ItemCardHeader, MarkdownContent, LinkButton, Link, ContentHeader, Progress, WarningPanel, Content, ItemCardGrid, Page, Header, CreateButton, SupportButton, StructuredMetadataTable, InfoCard } from '@backstage/core-components';
4
- import { useApp, useRouteRef, useApi, alertApiRef, useRouteRefParams, useAnalytics, errorApiRef, featureFlagsApiRef, useApiHolder, AnalyticsContext } from '@backstage/core-plugin-api';
5
- import { getEntityRelations, getEntitySourceLocation, FavoriteEntity, EntityRefLinks, useEntityList, useEntityTypeFilter, EntityListProvider, CatalogFilterLayout, EntitySearchBar, EntityKindPicker, UserListPicker, EntityTagPicker, catalogApiRef, humanizeEntityRef } from '@backstage/plugin-catalog-react';
6
- import { RELATION_OWNED_BY, parseEntityRef, stringifyEntityRef, DEFAULT_NAMESPACE } from '@backstage/catalog-model';
7
- import { makeStyles, useTheme, Card, CardMedia, CardContent, Box, Typography, Chip, CardActions, Tooltip, IconButton, FormControlLabel, Checkbox, TextField, Paper, Button, Stepper, Step, StepLabel, StepContent, LinearProgress, FormControl, InputLabel, Select, MenuItem as MenuItem$1, CardHeader } from '@material-ui/core';
8
- import { scmIntegrationsApiRef, ScmIntegrationIcon } from '@backstage/integration-react';
9
- import LanguageIcon from '@material-ui/icons/Language';
10
- import WarningIcon from '@material-ui/icons/Warning';
11
- import { s as selectedTemplateRouteRef, v as viewTechDocRouteRef, e as editRouteRef, a as actionsRouteRef, b as scaffolderListTaskRouteRef, r as registerComponentRouteRef, c as scaffolderTaskRouteRef, d as rootRouteRef, u as useDryRun, f as useDirectoryEditor, D as DirectoryEditorProvider, g as DryRunProvider, T as TemplateEditorBrowser, h as TemplateEditorTextArea, i as DryRunResults, j as TemplateEditorIntro, W as WebFileSystemAccess, l as legacySelectedTemplateRouteRef, A as ActionsPage, k as TaskPage } from './alpha-714dad1b.esm.js';
12
- import capitalize from 'lodash/capitalize';
13
- import CheckBoxIcon from '@material-ui/icons/CheckBox';
14
- import CheckBoxOutlineBlankIcon from '@material-ui/icons/CheckBoxOutlineBlank';
15
- import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
16
- import { Autocomplete } from '@material-ui/lab';
17
- import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha';
18
- import { usePermission } from '@backstage/plugin-permission-react';
19
- import IconButton$1 from '@material-ui/core/IconButton';
20
- import ListItemIcon from '@material-ui/core/ListItemIcon';
21
- import ListItemText from '@material-ui/core/ListItemText';
22
- import MenuItem from '@material-ui/core/MenuItem';
23
- import MenuList from '@material-ui/core/MenuList';
24
- import Popover from '@material-ui/core/Popover';
25
- import { makeStyles as makeStyles$1 } from '@material-ui/core/styles';
26
- import Description from '@material-ui/icons/Description';
27
- import Edit from '@material-ui/icons/Edit';
28
- import List from '@material-ui/icons/List';
29
- import MoreVert from '@material-ui/icons/MoreVert';
30
- import qs from 'qs';
31
- import useAsync from 'react-use/lib/useAsync';
32
- import { useTemplateSecrets, scaffolderApiRef, useCustomFieldExtensions, useCustomLayouts, SecretsContextProvider } from '@backstage/plugin-scaffolder-react';
33
- import { withTheme } from '@rjsf/core';
34
- import { Theme } from '@rjsf/material-ui';
35
- import cloneDeep from 'lodash/cloneDeep';
36
- import { extractSchemaFromStep } from '@backstage/plugin-scaffolder-react/alpha';
37
- import { StreamLanguage } from '@codemirror/language';
38
- import { yaml as yaml$1 } from '@codemirror/legacy-modes/mode/yaml';
39
- import CloseIcon from '@material-ui/icons/Close';
40
- import CodeMirror from '@uiw/react-codemirror';
41
- import yaml from 'yaml';
42
- import useDebounce from 'react-use/lib/useDebounce';
43
- import { D as DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS, L as ListTasksPage } from './ListTasksPage-2e8f4176.esm.js';
44
- import '@backstage/errors';
45
- import 'zen-observable';
46
- import '@material-ui/core/FormControl';
47
- import '@material-ui/lab/Autocomplete';
48
- import 'zod';
49
- import 'zod-to-json-schema';
50
- import '@material-ui/core/FormHelperText';
51
- import '@material-ui/core/Input';
52
- import '@material-ui/core/InputLabel';
53
- import 'react-use/lib/useEffectOnce';
54
- import '@material-ui/core/Button';
55
- import '@material-ui/core/useMediaQuery';
56
- import '@material-ui/icons/AddCircleOutline';
57
- import '@material-ui/icons/Repeat';
58
- import '@material-ui/icons/Toc';
59
- import 'classnames';
60
- import '@material-ui/icons/Settings';
61
- import '@material-ui/icons/FontDownload';
62
- import 'luxon';
63
- import 'humanize-duration';
64
- import '@material-ui/core/Typography';
65
- import '@react-hookz/web';
66
- import '@rjsf/validator-ajv8';
67
- import '@material-ui/core/Accordion';
68
- import '@material-ui/core/AccordionDetails';
69
- import '@material-ui/core/AccordionSummary';
70
- import '@material-ui/core/Divider';
71
- import '@material-ui/icons/ExpandLess';
72
- import '@material-ui/core/List';
73
- import '@material-ui/core/ListItem';
74
- import '@material-ui/core/ListItemSecondaryAction';
75
- import '@material-ui/icons/Cancel';
76
- import '@material-ui/icons/Check';
77
- import '@material-ui/icons/Delete';
78
- import '@material-ui/core/Box';
79
- import '@material-ui/core/Tab';
80
- import '@material-ui/core/Tabs';
81
- import '@material-ui/core/Grid';
82
- import '@material-ui/core/Step';
83
- import '@material-ui/core/StepLabel';
84
- import '@material-ui/core/Stepper';
85
- import '@material-ui/icons/FiberManualRecord';
86
- import 'react-use/lib/useInterval';
87
- import '@material-ui/lab/TreeView';
88
- import '@material-ui/icons/ChevronRight';
89
- import '@material-ui/lab/TreeItem';
90
- import '@material-ui/icons/Refresh';
91
- import '@material-ui/icons/Save';
92
- import '@codemirror/view';
93
- import '@material-ui/core/Card';
94
- import '@material-ui/core/CardActionArea';
95
- import '@material-ui/core/CardContent';
96
- import '@material-ui/core/Tooltip';
97
- import '@material-ui/icons/InfoOutlined';
98
-
99
- const useStyles$5 = makeStyles((theme) => ({
100
- cardHeader: {
101
- position: "relative"
102
- },
103
- title: {
104
- backgroundImage: ({ backgroundImage }) => backgroundImage,
105
- color: ({ fontColor }) => fontColor
106
- },
107
- box: {
108
- overflow: "hidden",
109
- textOverflow: "ellipsis",
110
- display: "-webkit-box",
111
- "-webkit-line-clamp": 10,
112
- "-webkit-box-orient": "vertical"
113
- },
114
- label: {
115
- color: theme.palette.text.secondary,
116
- textTransform: "uppercase",
117
- fontSize: "0.65rem",
118
- fontWeight: "bold",
119
- letterSpacing: 0.5,
120
- lineHeight: 1,
121
- paddingBottom: "0.2rem"
122
- },
123
- linksLabel: {
124
- padding: "0 16px"
125
- },
126
- description: {
127
- "& p": {
128
- margin: "0px"
129
- }
130
- },
131
- leftButton: {
132
- marginRight: "auto"
133
- },
134
- starButton: {
135
- position: "absolute",
136
- top: theme.spacing(0.5),
137
- right: theme.spacing(0.5),
138
- padding: "0.25rem",
139
- color: theme.palette.common.white
140
- }
141
- }));
142
- const MuiIcon = ({ icon: Icon }) => /* @__PURE__ */ React.createElement(Icon, null);
143
- const useDeprecationStyles = makeStyles((theme) => ({
144
- deprecationIcon: {
145
- position: "absolute",
146
- top: theme.spacing(0.5),
147
- right: theme.spacing(3.5),
148
- padding: "0.25rem"
149
- },
150
- link: {
151
- color: theme.palette.warning.light
152
- }
153
- }));
154
- const getTemplateCardProps = (template) => {
155
- var _a, _b, _c, _d, _e, _f;
156
- return {
157
- key: template.metadata.uid,
158
- name: template.metadata.name,
159
- title: `${(_a = template.metadata.title || template.metadata.name) != null ? _a : ""}`,
160
- type: (_b = template.spec.type) != null ? _b : "",
161
- description: (_c = template.metadata.description) != null ? _c : "-",
162
- tags: (_e = (_d = template.metadata) == null ? void 0 : _d.tags) != null ? _e : [],
163
- links: (_f = template.metadata.links) != null ? _f : []
164
- };
165
- };
166
- const DeprecationWarning = () => {
167
- const styles = useDeprecationStyles();
168
- const Title = /* @__PURE__ */ React.createElement(Typography, { style: { padding: 10, maxWidth: 300 } }, "This template uses a syntax that has been deprecated, and should be migrated to a newer syntax. Click for more info.");
169
- return /* @__PURE__ */ React.createElement("div", { className: styles.deprecationIcon }, /* @__PURE__ */ React.createElement(Tooltip, { title: Title }, /* @__PURE__ */ React.createElement(
170
- Link,
171
- {
172
- to: "https://backstage.io/docs/features/software-templates/migrating-from-v1beta2-to-v1beta3",
173
- className: styles.link
174
- },
175
- /* @__PURE__ */ React.createElement(WarningIcon, null)
176
- )));
177
- };
178
- const TemplateCard = ({ template, deprecated }) => {
179
- var _a, _b, _c;
180
- const app = useApp();
181
- const backstageTheme = useTheme();
182
- const templateRoute = useRouteRef(selectedTemplateRouteRef);
183
- const templateProps = getTemplateCardProps(template);
184
- const ownedByRelations = getEntityRelations(
185
- template,
186
- RELATION_OWNED_BY
187
- );
188
- const themeId = backstageTheme.getPageTheme({ themeId: templateProps.type }) ? templateProps.type : "other";
189
- const theme = backstageTheme.getPageTheme({ themeId });
190
- const classes = useStyles$5({
191
- fontColor: theme.fontColor,
192
- backgroundImage: theme.backgroundImage
193
- });
194
- const { name, namespace } = parseEntityRef(stringifyEntityRef(template));
195
- const href = templateRoute({ templateName: name, namespace });
196
- const viewTechDoc = useRouteRef(viewTechDocRouteRef);
197
- const viewTechDocsAnnotation = (_a = template.metadata.annotations) == null ? void 0 : _a["backstage.io/techdocs-ref"];
198
- const viewTechDocsLink = !!viewTechDocsAnnotation && !!viewTechDoc && viewTechDoc({
199
- namespace: template.metadata.namespace || DEFAULT_NAMESPACE,
200
- kind: template.kind,
201
- name: template.metadata.name
202
- });
203
- const iconResolver = (key) => {
204
- var _a2;
205
- return key ? (_a2 = app.getSystemIcon(key)) != null ? _a2 : LanguageIcon : LanguageIcon;
206
- };
207
- const scmIntegrationsApi = useApi(scmIntegrationsApiRef);
208
- const sourceLocation = getEntitySourceLocation(template, scmIntegrationsApi);
209
- return /* @__PURE__ */ React.createElement(Card, null, /* @__PURE__ */ React.createElement(CardMedia, { className: classes.cardHeader }, /* @__PURE__ */ React.createElement(FavoriteEntity, { className: classes.starButton, entity: template }), deprecated && /* @__PURE__ */ React.createElement(DeprecationWarning, null), /* @__PURE__ */ React.createElement(
210
- ItemCardHeader,
211
- {
212
- title: templateProps.title,
213
- subtitle: templateProps.type,
214
- classes: { root: classes.title }
215
- }
216
- )), /* @__PURE__ */ React.createElement(
217
- CardContent,
218
- {
219
- style: { display: "flex", flexDirection: "column", gap: "16px" }
220
- },
221
- /* @__PURE__ */ React.createElement(Box, { className: classes.box }, /* @__PURE__ */ React.createElement(Typography, { variant: "body2", className: classes.label }, "Description"), /* @__PURE__ */ React.createElement(
222
- MarkdownContent,
223
- {
224
- className: classes.description,
225
- content: templateProps.description
226
- }
227
- )),
228
- /* @__PURE__ */ React.createElement(Box, { className: classes.box }, /* @__PURE__ */ React.createElement(Typography, { variant: "body2", className: classes.label }, "Owner"), /* @__PURE__ */ React.createElement(EntityRefLinks, { entityRefs: ownedByRelations, defaultKind: "Group" })),
229
- /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(
230
- Typography,
231
- {
232
- style: { marginBottom: "4px" },
233
- variant: "body2",
234
- className: classes.label
235
- },
236
- "Tags"
237
- ), (_b = templateProps.tags) == null ? void 0 : _b.map((tag) => /* @__PURE__ */ React.createElement(Chip, { size: "small", label: tag, key: tag })))
238
- ), /* @__PURE__ */ React.createElement(
239
- Typography,
240
- {
241
- variant: "body2",
242
- className: [classes.label, classes.linksLabel].join(" ")
243
- },
244
- "Links"
245
- ), /* @__PURE__ */ React.createElement(CardActions, null, /* @__PURE__ */ React.createElement("div", { className: classes.leftButton }, sourceLocation && /* @__PURE__ */ React.createElement(
246
- Tooltip,
247
- {
248
- title: sourceLocation.integrationType || sourceLocation.locationTargetUrl
249
- },
250
- /* @__PURE__ */ React.createElement(
251
- IconButton,
252
- {
253
- className: classes.leftButton,
254
- href: sourceLocation.locationTargetUrl
255
- },
256
- /* @__PURE__ */ React.createElement(ScmIntegrationIcon, { type: sourceLocation.integrationType })
257
- )
258
- ), viewTechDocsLink && /* @__PURE__ */ React.createElement(Tooltip, { title: "View TechDocs" }, /* @__PURE__ */ React.createElement(
259
- IconButton,
260
- {
261
- className: classes.leftButton,
262
- href: viewTechDocsLink
263
- },
264
- /* @__PURE__ */ React.createElement(MuiIcon, { icon: iconResolver("docs") })
265
- )), (_c = templateProps.links) == null ? void 0 : _c.map((link, i) => /* @__PURE__ */ React.createElement(Tooltip, { key: `${link.url}_${i}`, title: link.title || link.url }, /* @__PURE__ */ React.createElement(IconButton, { size: "medium", href: link.url }, /* @__PURE__ */ React.createElement(MuiIcon, { icon: iconResolver(link.icon) }))))), /* @__PURE__ */ React.createElement(
266
- LinkButton,
267
- {
268
- color: "primary",
269
- to: href,
270
- "aria-label": `Choose ${templateProps.title}`
271
- },
272
- "Choose"
273
- )));
274
- };
275
-
276
- const TemplateList = ({
277
- TemplateCardComponent,
278
- group
279
- }) => {
280
- const { loading, error, entities } = useEntityList();
281
- const Card = TemplateCardComponent || TemplateCard;
282
- const maybeFilteredEntities = group ? entities.filter((e) => group.filter(e)) : entities;
283
- const titleComponent = (() => {
284
- if (group && group.title) {
285
- if (typeof group.title === "string") {
286
- return /* @__PURE__ */ React.createElement(ContentHeader, { title: group.title });
287
- }
288
- return group.title;
289
- }
290
- return /* @__PURE__ */ React.createElement(ContentHeader, { title: "Other Templates" });
291
- })();
292
- if (group && maybeFilteredEntities.length === 0) {
293
- return null;
294
- }
295
- return /* @__PURE__ */ React.createElement(React.Fragment, null, loading && /* @__PURE__ */ React.createElement(Progress, null), error && /* @__PURE__ */ React.createElement(WarningPanel, { title: "Oops! Something went wrong loading the templates" }, error.message), !error && !loading && !entities.length && /* @__PURE__ */ React.createElement(Typography, { variant: "body2" }, "No templates found that match your filter. Learn more about", " ", /* @__PURE__ */ React.createElement(Link, { to: "https://backstage.io/docs/features/software-templates/adding-templates" }, "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(
296
- Card,
297
- {
298
- key: stringifyEntityRef(template),
299
- template,
300
- deprecated: template.apiVersion === "backstage.io/v1beta2"
301
- }
302
- )))));
303
- };
304
-
305
- const icon = /* @__PURE__ */ React.createElement(CheckBoxOutlineBlankIcon, { fontSize: "small" });
306
- const checkedIcon = /* @__PURE__ */ React.createElement(CheckBoxIcon, { fontSize: "small" });
307
- const TemplateTypePicker = () => {
308
- const alertApi = useApi(alertApiRef);
309
- const { error, loading, availableTypes, selectedTypes, setSelectedTypes } = useEntityTypeFilter();
310
- if (loading)
311
- return /* @__PURE__ */ React.createElement(Progress, null);
312
- if (!availableTypes)
313
- return null;
314
- if (error) {
315
- alertApi.post({
316
- message: `Failed to load entity types`,
317
- severity: "error"
318
- });
319
- return null;
320
- }
321
- return /* @__PURE__ */ React.createElement(Box, { pb: 1, pt: 1 }, /* @__PURE__ */ React.createElement(Typography, { variant: "button" }, "Categories"), /* @__PURE__ */ React.createElement(
322
- Autocomplete,
323
- {
324
- multiple: true,
325
- "aria-label": "Categories",
326
- options: availableTypes,
327
- value: selectedTypes,
328
- onChange: (_, value) => setSelectedTypes(value),
329
- renderOption: (option, { selected }) => /* @__PURE__ */ React.createElement(
330
- FormControlLabel,
331
- {
332
- control: /* @__PURE__ */ React.createElement(
333
- Checkbox,
334
- {
335
- icon,
336
- checkedIcon,
337
- checked: selected
338
- }
339
- ),
340
- label: capitalize(option)
341
- }
342
- ),
343
- size: "small",
344
- popupIcon: /* @__PURE__ */ React.createElement(ExpandMoreIcon, { "data-testid": "categories-picker-expand" }),
345
- renderInput: (params) => /* @__PURE__ */ React.createElement(TextField, { ...params, variant: "outlined" })
346
- }
347
- ));
348
- };
349
-
350
- const useStyles$4 = makeStyles$1((theme) => ({
351
- button: {
352
- color: theme.page.fontColor
353
- }
354
- }));
355
- function ScaffolderPageContextMenu(props) {
356
- const classes = useStyles$4();
357
- const [anchorEl, setAnchorEl] = useState();
358
- const editLink = useRouteRef(editRouteRef);
359
- const actionsLink = useRouteRef(actionsRouteRef);
360
- const tasksLink = useRouteRef(scaffolderListTaskRouteRef);
361
- const navigate = useNavigate();
362
- const showEditor = props.editor !== false;
363
- const showActions = props.actions !== false;
364
- const showTasks = props.tasks !== false;
365
- if (!showEditor && !showActions) {
366
- return null;
367
- }
368
- const onOpen = (event) => {
369
- setAnchorEl(event.currentTarget);
370
- };
371
- const onClose = () => {
372
- setAnchorEl(void 0);
373
- };
374
- return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
375
- IconButton$1,
376
- {
377
- "aria-label": "more",
378
- "aria-controls": "long-menu",
379
- "aria-haspopup": "true",
380
- onClick: onOpen,
381
- "data-testid": "menu-button",
382
- color: "inherit",
383
- className: classes.button
384
- },
385
- /* @__PURE__ */ React.createElement(MoreVert, null)
386
- ), /* @__PURE__ */ React.createElement(
387
- Popover,
388
- {
389
- open: Boolean(anchorEl),
390
- onClose,
391
- anchorEl,
392
- anchorOrigin: { vertical: "bottom", horizontal: "right" },
393
- transformOrigin: { vertical: "top", horizontal: "right" }
394
- },
395
- /* @__PURE__ */ React.createElement(MenuList, null, showEditor && /* @__PURE__ */ React.createElement(MenuItem, { onClick: () => navigate(editLink()) }, /* @__PURE__ */ React.createElement(ListItemIcon, null, /* @__PURE__ */ React.createElement(Edit, { fontSize: "small" })), /* @__PURE__ */ React.createElement(ListItemText, { primary: "Template Editor" })), showActions && /* @__PURE__ */ React.createElement(MenuItem, { onClick: () => navigate(actionsLink()) }, /* @__PURE__ */ React.createElement(ListItemIcon, null, /* @__PURE__ */ React.createElement(Description, { fontSize: "small" })), /* @__PURE__ */ React.createElement(ListItemText, { primary: "Installed Actions" })), showTasks && /* @__PURE__ */ React.createElement(MenuItem, { onClick: () => navigate(tasksLink()) }, /* @__PURE__ */ React.createElement(ListItemIcon, null, /* @__PURE__ */ React.createElement(List, { fontSize: "small" })), /* @__PURE__ */ React.createElement(ListItemText, { primary: "Task List" })))
396
- ));
397
- }
398
-
399
- const ScaffolderPageContents = ({
400
- TemplateCardComponent,
401
- groups,
402
- contextMenu,
403
- headerOptions
404
- }) => {
405
- const registerComponentLink = useRouteRef(registerComponentRouteRef);
406
- const otherTemplatesGroup = {
407
- title: groups ? "Other Templates" : "Templates",
408
- filter: (entity) => {
409
- const filtered = (groups != null ? groups : []).map((group) => group.filter(entity));
410
- return !filtered.some((result) => result === true);
411
- }
412
- };
413
- const { allowed } = usePermission({
414
- permission: catalogEntityCreatePermission
415
- });
416
- return /* @__PURE__ */ React.createElement(Page, { themeId: "home" }, /* @__PURE__ */ React.createElement(
417
- Header,
418
- {
419
- pageTitleOverride: "Create a New Component",
420
- title: "Create a New Component",
421
- subtitle: "Create new software components using standard templates",
422
- ...headerOptions
423
- },
424
- /* @__PURE__ */ React.createElement(ScaffolderPageContextMenu, { ...contextMenu })
425
- ), /* @__PURE__ */ React.createElement(Content, null, /* @__PURE__ */ React.createElement(ContentHeader, { title: "Available Templates" }, allowed && /* @__PURE__ */ React.createElement(
426
- CreateButton,
427
- {
428
- title: "Register Existing Component",
429
- to: registerComponentLink && registerComponentLink()
430
- }
431
- ), /* @__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, { initialFilter: "template", hidden: true }), /* @__PURE__ */ React.createElement(
432
- UserListPicker,
433
- {
434
- initialFilter: "all",
435
- availableFilters: ["all", "starred"]
436
- }
437
- ), /* @__PURE__ */ React.createElement(TemplateTypePicker, null), /* @__PURE__ */ React.createElement(EntityTagPicker, null)), /* @__PURE__ */ React.createElement(CatalogFilterLayout.Content, null, groups && groups.map((group, index) => /* @__PURE__ */ React.createElement(
438
- TemplateList,
439
- {
440
- key: index,
441
- TemplateCardComponent,
442
- group
443
- }
444
- )), /* @__PURE__ */ React.createElement(
445
- TemplateList,
446
- {
447
- key: "other",
448
- TemplateCardComponent,
449
- group: otherTemplatesGroup
450
- }
451
- )))));
452
- };
453
- const ScaffolderPage = ({
454
- TemplateCardComponent,
455
- groups,
456
- contextMenu,
457
- headerOptions
458
- }) => /* @__PURE__ */ React.createElement(EntityListProvider, null, /* @__PURE__ */ React.createElement(
459
- ScaffolderPageContents,
460
- {
461
- TemplateCardComponent,
462
- groups,
463
- contextMenu,
464
- headerOptions
465
- }
466
- ));
467
-
468
- function isObject$1(value) {
469
- return typeof value === "object" && value !== null && !Array.isArray(value);
470
- }
471
- function extractUiSchema(schema, uiSchema) {
472
- if (!isObject$1(schema)) {
473
- return;
474
- }
475
- const { properties, items, anyOf, oneOf, allOf, dependencies } = schema;
476
- for (const propName in schema) {
477
- if (!schema.hasOwnProperty(propName)) {
478
- continue;
479
- }
480
- if (propName.startsWith("ui:")) {
481
- uiSchema[propName] = schema[propName];
482
- delete schema[propName];
483
- }
484
- }
485
- if (isObject$1(properties)) {
486
- for (const propName in properties) {
487
- if (!properties.hasOwnProperty(propName)) {
488
- continue;
489
- }
490
- const schemaNode = properties[propName];
491
- if (!isObject$1(schemaNode)) {
492
- continue;
493
- }
494
- const innerUiSchema = {};
495
- uiSchema[propName] = uiSchema[propName] || innerUiSchema;
496
- extractUiSchema(schemaNode, innerUiSchema);
497
- }
498
- }
499
- if (isObject$1(items)) {
500
- const innerUiSchema = {};
501
- uiSchema.items = innerUiSchema;
502
- extractUiSchema(items, innerUiSchema);
503
- }
504
- if (Array.isArray(anyOf)) {
505
- for (const schemaNode of anyOf) {
506
- if (!isObject$1(schemaNode)) {
507
- continue;
508
- }
509
- extractUiSchema(schemaNode, uiSchema);
510
- }
511
- }
512
- if (Array.isArray(oneOf)) {
513
- for (const schemaNode of oneOf) {
514
- if (!isObject$1(schemaNode)) {
515
- continue;
516
- }
517
- extractUiSchema(schemaNode, uiSchema);
518
- }
519
- }
520
- if (Array.isArray(allOf)) {
521
- for (const schemaNode of allOf) {
522
- if (!isObject$1(schemaNode)) {
523
- continue;
524
- }
525
- extractUiSchema(schemaNode, uiSchema);
526
- }
527
- }
528
- if (isObject$1(dependencies)) {
529
- for (const depName of Object.keys(dependencies)) {
530
- const schemaNode = dependencies[depName];
531
- if (!isObject$1(schemaNode)) {
532
- continue;
533
- }
534
- extractUiSchema(schemaNode, uiSchema);
535
- }
536
- }
537
- }
538
- function transformSchemaToProps(inputSchema, layouts = []) {
539
- var _a;
540
- const customLayoutName = inputSchema["ui:ObjectFieldTemplate"];
541
- inputSchema.type = inputSchema.type || "object";
542
- const schema = JSON.parse(JSON.stringify(inputSchema));
543
- delete schema.title;
544
- const uiSchema = {};
545
- extractUiSchema(schema, uiSchema);
546
- if (customLayoutName) {
547
- const Layout = (_a = layouts.find(
548
- (layout) => layout.name === customLayoutName
549
- )) == null ? void 0 : _a.component;
550
- if (Layout) {
551
- uiSchema["ui:ObjectFieldTemplate"] = Layout;
552
- }
553
- }
554
- return { schema, uiSchema };
555
- }
556
-
557
- const DescriptionField = ({ description }) => description && /* @__PURE__ */ React.createElement(MarkdownContent, { content: description, linkTarget: "_blank" });
558
-
559
- var fieldOverrides = /*#__PURE__*/Object.freeze({
560
- __proto__: null,
561
- DescriptionField: DescriptionField
562
- });
563
-
564
- function getReviewData(formData, uiSchemas) {
565
- const reviewData = {};
566
- const orderedReviewProperties = new Set(
567
- uiSchemas.map((us) => us.name).concat(Object.getOwnPropertyNames(formData))
568
- );
569
- for (const key of orderedReviewProperties) {
570
- const uiSchema = uiSchemas.find((us) => us.name === key);
571
- if (!uiSchema) {
572
- reviewData[key] = formData[key];
573
- continue;
574
- }
575
- if (uiSchema["ui:widget"] === "password") {
576
- reviewData[key] = "******";
577
- continue;
578
- }
579
- if (!uiSchema["ui:backstage"] || !uiSchema["ui:backstage"].review) {
580
- reviewData[key] = formData[key];
581
- continue;
582
- }
583
- const review = uiSchema["ui:backstage"].review;
584
- if (review.mask) {
585
- reviewData[key] = review.mask;
586
- continue;
587
- }
588
- if (!review.show) {
589
- continue;
590
- }
591
- reviewData[key] = formData[key];
592
- }
593
- return reviewData;
594
- }
595
- function getUiSchemasFromSteps(steps) {
596
- const uiSchemas = [];
597
- steps.forEach((step) => {
598
- const schemaProps = step.schema.properties;
599
- for (const key in schemaProps) {
600
- if (schemaProps.hasOwnProperty(key)) {
601
- const uiSchema = schemaProps[key];
602
- uiSchema.name = key;
603
- uiSchemas.push(uiSchema);
604
- }
605
- }
606
- });
607
- return uiSchemas;
608
- }
609
- const ReviewStep = (props) => {
610
- const {
611
- disableButtons,
612
- formData,
613
- handleBack,
614
- handleCreate,
615
- handleReset,
616
- steps
617
- } = props;
618
- return /* @__PURE__ */ React.createElement(Content, null, /* @__PURE__ */ React.createElement(Paper, { square: true, elevation: 0 }, /* @__PURE__ */ React.createElement(Typography, { variant: "h6" }, "Review and create"), /* @__PURE__ */ React.createElement(
619
- StructuredMetadataTable,
620
- {
621
- dense: true,
622
- metadata: getReviewData(
623
- formData,
624
- getUiSchemasFromSteps(
625
- steps.map(({ mergedSchema }) => ({ schema: mergedSchema }))
626
- )
627
- )
628
- }
629
- ), /* @__PURE__ */ React.createElement(Box, { mb: 4 }), /* @__PURE__ */ React.createElement(Button, { onClick: handleBack, disabled: disableButtons }, "Back"), /* @__PURE__ */ React.createElement(Button, { onClick: handleReset, disabled: disableButtons }, "Reset"), /* @__PURE__ */ React.createElement(
630
- Button,
631
- {
632
- variant: "contained",
633
- color: "primary",
634
- onClick: handleCreate,
635
- disabled: disableButtons
636
- },
637
- "Create"
638
- )));
639
- };
640
-
641
- const Form$1 = withTheme(Theme);
642
- function getSchemasFromSteps(steps) {
643
- return steps.map(({ schema }) => ({
644
- mergedSchema: schema,
645
- ...extractSchemaFromStep(schema)
646
- }));
647
- }
648
- const MultistepJsonForm = (props) => {
649
- const {
650
- formData,
651
- onChange,
652
- onReset,
653
- onFinish,
654
- fields,
655
- widgets,
656
- layouts,
657
- ReviewStepComponent
658
- } = props;
659
- const { templateName } = useRouteRefParams(selectedTemplateRouteRef);
660
- const analytics = useAnalytics();
661
- const [activeStep, setActiveStep] = useState(0);
662
- const [disableButtons, setDisableButtons] = useState(false);
663
- const errorApi = useApi(errorApiRef);
664
- const featureFlagApi = useApi(featureFlagsApiRef);
665
- const featureFlagKey = "backstage:featureFlag";
666
- const filterOutProperties = (step) => {
667
- var _a;
668
- const filteredStep = cloneDeep(step);
669
- const removedPropertyKeys = [];
670
- if (filteredStep.schema.properties) {
671
- filteredStep.schema.properties = Object.fromEntries(
672
- Object.entries(filteredStep.schema.properties).filter(
673
- ([key, value]) => {
674
- if (value[featureFlagKey]) {
675
- if (featureFlagApi.isActive(value[featureFlagKey])) {
676
- return true;
677
- }
678
- removedPropertyKeys.push(key);
679
- return false;
680
- }
681
- return true;
682
- }
683
- )
684
- );
685
- filteredStep.schema.required = Array.isArray(filteredStep.schema.required) ? (_a = filteredStep.schema.required) == null ? void 0 : _a.filter(
686
- (r) => !removedPropertyKeys.includes(r)
687
- ) : filteredStep.schema.required;
688
- }
689
- return filteredStep;
690
- };
691
- const steps = props.steps.filter((step) => {
692
- const featureFlag = step.schema[featureFlagKey];
693
- return typeof featureFlag !== "string" || featureFlagApi.isActive(featureFlag);
694
- }).map(filterOutProperties);
695
- const handleReset = () => {
696
- setActiveStep(0);
697
- onReset();
698
- };
699
- const handleNext = () => {
700
- const stepNum = Math.min(activeStep + 1, steps.length);
701
- setActiveStep(stepNum);
702
- analytics.captureEvent("click", `Next Step (${stepNum})`);
703
- };
704
- const handleBack = () => setActiveStep(Math.max(activeStep - 1, 0));
705
- const handleCreate = async () => {
706
- if (!onFinish) {
707
- return;
708
- }
709
- setDisableButtons(true);
710
- try {
711
- await onFinish();
712
- analytics.captureEvent("create", formData.name || `new ${templateName}`);
713
- } catch (err) {
714
- errorApi.post(err);
715
- } finally {
716
- setDisableButtons(false);
717
- }
718
- };
719
- const ReviewStepElement = ReviewStepComponent != null ? ReviewStepComponent : ReviewStep;
720
- return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Stepper, { activeStep, orientation: "vertical" }, steps.map(({ title, schema, ...formProps }, index) => {
721
- return /* @__PURE__ */ React.createElement(Step, { key: title }, /* @__PURE__ */ React.createElement(
722
- StepLabel,
723
- {
724
- "aria-label": `Step ${index + 1} ${title}`,
725
- "aria-disabled": "false",
726
- tabIndex: 0
727
- },
728
- /* @__PURE__ */ React.createElement(Typography, { variant: "h6", component: "h3" }, title)
729
- ), /* @__PURE__ */ React.createElement(StepContent, { key: title }, /* @__PURE__ */ React.createElement(
730
- Form$1,
731
- {
732
- showErrorList: false,
733
- fields: { ...fieldOverrides, ...fields },
734
- widgets,
735
- noHtml5Validate: true,
736
- formData,
737
- formContext: { formData },
738
- onChange,
739
- onSubmit: (e) => {
740
- if (e.errors.length === 0)
741
- handleNext();
742
- },
743
- ...formProps,
744
- ...transformSchemaToProps(schema, layouts)
745
- },
746
- /* @__PURE__ */ React.createElement(Button, { disabled: activeStep === 0, onClick: handleBack }, "Back"),
747
- /* @__PURE__ */ React.createElement(Button, { variant: "contained", color: "primary", type: "submit" }, "Next step")
748
- )));
749
- })), activeStep === steps.length && /* @__PURE__ */ React.createElement(
750
- ReviewStepElement,
751
- {
752
- disableButtons,
753
- handleBack,
754
- handleCreate,
755
- handleReset,
756
- formData,
757
- steps: getSchemasFromSteps(steps)
758
- }
759
- ));
760
- };
761
-
762
- function isObject(obj) {
763
- return typeof obj === "object" && obj !== null && !Array.isArray(obj);
764
- }
765
- function isArray(obj) {
766
- return typeof obj === "object" && obj !== null && Array.isArray(obj);
767
- }
768
- const createValidator = (rootSchema, validators, context) => {
769
- function validate(schema, formData, errors) {
770
- var _a;
771
- const schemaProps = schema.properties;
772
- const customObject = schema.type === "object" && schemaProps === void 0;
773
- if (!isObject(schemaProps) && !customObject) {
774
- return;
775
- }
776
- if (schemaProps) {
777
- for (const [key, propData] of Object.entries(formData)) {
778
- const propValidation = errors[key];
779
- const doValidate = (item) => {
780
- if (item && isObject(item)) {
781
- const fieldName = item["ui:field"];
782
- if (fieldName && typeof validators[fieldName] === "function") {
783
- validators[fieldName](
784
- propData,
785
- propValidation,
786
- context
787
- );
788
- }
789
- }
790
- };
791
- const propSchemaProps = schemaProps[key];
792
- if (isObject(propData) && isObject(propSchemaProps)) {
793
- validate(
794
- propSchemaProps,
795
- propData,
796
- propValidation
797
- );
798
- } else if (isArray(propData)) {
799
- if (isObject(propSchemaProps)) {
800
- const { items } = propSchemaProps;
801
- if (isObject(items)) {
802
- if (items.type === "object") {
803
- const properties = (_a = items == null ? void 0 : items.properties) != null ? _a : [];
804
- for (const [, value] of Object.entries(properties)) {
805
- doValidate(value);
806
- }
807
- } else {
808
- doValidate(items);
809
- }
810
- }
811
- }
812
- } else {
813
- doValidate(propSchemaProps);
814
- }
815
- }
816
- } else if (customObject) {
817
- const fieldName = schema["ui:field"];
818
- if (fieldName && typeof validators[fieldName] === "function") {
819
- validators[fieldName](formData, errors, context);
820
- }
821
- }
822
- }
823
- return (formData, errors) => {
824
- validate(rootSchema, formData, errors);
825
- return errors;
826
- };
827
- };
828
-
829
- const useTemplateParameterSchema = (templateRef) => {
830
- const scaffolderApi = useApi(scaffolderApiRef);
831
- const { value, loading, error } = useAsync(
832
- () => scaffolderApi.getTemplateParameterSchema(templateRef),
833
- [scaffolderApi, templateRef]
834
- );
835
- return { schema: value, loading, error };
836
- };
837
- const TemplatePage = ({
838
- ReviewStepComponent,
839
- customFieldExtensions = [],
840
- layouts = [],
841
- headerOptions
842
- }) => {
843
- const apiHolder = useApiHolder();
844
- const secretsContext = useTemplateSecrets();
845
- const errorApi = useApi(errorApiRef);
846
- const scaffolderApi = useApi(scaffolderApiRef);
847
- const { templateName, namespace } = useRouteRefParams(
848
- selectedTemplateRouteRef
849
- );
850
- const templateRef = stringifyEntityRef({
851
- name: templateName,
852
- kind: "template",
853
- namespace
854
- });
855
- const navigate = useNavigate();
856
- const scaffolderTaskRoute = useRouteRef(scaffolderTaskRouteRef);
857
- const rootRoute = useRouteRef(rootRouteRef);
858
- const { schema, loading, error } = useTemplateParameterSchema(templateRef);
859
- const [formState, setFormState] = useState(() => {
860
- var _a;
861
- const query = qs.parse(window.location.search, {
862
- ignoreQueryPrefix: true
863
- });
864
- try {
865
- return JSON.parse(query.formData);
866
- } catch (e) {
867
- return (_a = query.formData) != null ? _a : {};
868
- }
869
- });
870
- const handleFormReset = () => setFormState({});
871
- const handleChange = useCallback(
872
- (e) => setFormState(e.formData),
873
- [setFormState]
874
- );
875
- const handleCreate = async () => {
876
- var _a;
877
- const { taskId } = await scaffolderApi.scaffold({
878
- templateRef,
879
- values: formState,
880
- secrets: secretsContext == null ? void 0 : secretsContext.secrets
881
- });
882
- const formParams = qs.stringify(
883
- { formData: formState },
884
- { addQueryPrefix: true }
885
- );
886
- const newUrl = `${window.location.pathname}${formParams}`;
887
- (_a = window.history) == null ? void 0 : _a.replaceState(null, document.title, newUrl);
888
- navigate(scaffolderTaskRoute({ taskId }));
889
- };
890
- if (error) {
891
- errorApi.post(new Error(`Failed to load template, ${error}`));
892
- return /* @__PURE__ */ React.createElement(Navigate, { to: rootRoute() });
893
- }
894
- if (!loading && !schema) {
895
- errorApi.post(new Error("Template was not found."));
896
- return /* @__PURE__ */ React.createElement(Navigate, { to: rootRoute() });
897
- }
898
- const customFieldComponents = Object.fromEntries(
899
- customFieldExtensions.map(({ name, component }) => [name, component])
900
- );
901
- const customFieldValidators = Object.fromEntries(
902
- customFieldExtensions.map(({ name, validation }) => [name, validation])
903
- );
904
- return /* @__PURE__ */ React.createElement(AnalyticsContext, { attributes: { entityRef: templateRef } }, /* @__PURE__ */ React.createElement(Page, { themeId: "home" }, /* @__PURE__ */ React.createElement(
905
- Header,
906
- {
907
- pageTitleOverride: "Create a New Component",
908
- title: "Create a New Component",
909
- subtitle: "Create new software components using standard templates",
910
- ...headerOptions
911
- }
912
- ), /* @__PURE__ */ React.createElement(Content, null, loading && /* @__PURE__ */ React.createElement(LinearProgress, { "data-testid": "loading-progress" }), schema && /* @__PURE__ */ React.createElement(
913
- InfoCard,
914
- {
915
- title: schema.title,
916
- noPadding: true,
917
- titleTypographyProps: { component: "h2" }
918
- },
919
- /* @__PURE__ */ React.createElement(
920
- MultistepJsonForm,
921
- {
922
- ReviewStepComponent,
923
- formData: formState,
924
- fields: customFieldComponents,
925
- onChange: handleChange,
926
- onReset: handleFormReset,
927
- onFinish: handleCreate,
928
- layouts,
929
- steps: schema.steps.map((step) => {
930
- return {
931
- ...step,
932
- validate: createValidator(
933
- step.schema,
934
- customFieldValidators,
935
- { apiHolder }
936
- )
937
- };
938
- })
939
- }
940
- )
941
- ))));
942
- };
943
-
944
- const useStyles$3 = makeStyles$1({
945
- containerWrapper: {
946
- position: "relative",
947
- width: "100%",
948
- height: "100%"
949
- },
950
- container: {
951
- position: "absolute",
952
- top: 0,
953
- bottom: 0,
954
- left: 0,
955
- right: 0,
956
- overflow: "auto"
957
- }
958
- });
959
- class ErrorBoundary extends Component {
960
- constructor() {
961
- super(...arguments);
962
- this.state = {
963
- shouldRender: true
964
- };
965
- }
966
- componentDidUpdate(prevProps) {
967
- if (prevProps.invalidator !== this.props.invalidator) {
968
- this.setState({ shouldRender: true });
969
- }
970
- }
971
- componentDidCatch(error) {
972
- this.props.setErrorText(error.message);
973
- this.setState({ shouldRender: false });
974
- }
975
- render() {
976
- return this.state.shouldRender ? this.props.children : null;
977
- }
978
- }
979
- function isJsonObject(value) {
980
- return typeof value === "object" && value !== null && !Array.isArray(value);
981
- }
982
- function TemplateEditorForm(props) {
983
- const {
984
- content,
985
- contentIsSpec,
986
- data,
987
- onUpdate,
988
- onDryRun,
989
- setErrorText,
990
- fieldExtensions = [],
991
- layouts = []
992
- } = props;
993
- const classes = useStyles$3();
994
- const apiHolder = useApiHolder();
995
- const [steps, setSteps] = useState();
996
- const fields = useMemo(() => {
997
- return Object.fromEntries(
998
- fieldExtensions.map(({ name, component }) => [name, component])
999
- );
1000
- }, [fieldExtensions]);
1001
- useDebounce(
1002
- () => {
1003
- try {
1004
- if (!content) {
1005
- setSteps(void 0);
1006
- return;
1007
- }
1008
- const parsed = yaml.parse(content);
1009
- if (!isJsonObject(parsed)) {
1010
- setSteps(void 0);
1011
- return;
1012
- }
1013
- let rootObj = parsed;
1014
- if (!contentIsSpec) {
1015
- const isTemplate = String(parsed.kind).toLocaleLowerCase("en-US") === "template";
1016
- if (!isTemplate) {
1017
- setSteps(void 0);
1018
- return;
1019
- }
1020
- rootObj = isJsonObject(parsed.spec) ? parsed.spec : {};
1021
- }
1022
- const { parameters } = rootObj;
1023
- if (!Array.isArray(parameters)) {
1024
- setErrorText("Template parameters must be an array");
1025
- setSteps(void 0);
1026
- return;
1027
- }
1028
- const fieldValidators = Object.fromEntries(
1029
- fieldExtensions.map(({ name, validation }) => [name, validation])
1030
- );
1031
- setErrorText();
1032
- setSteps(
1033
- parameters.flatMap(
1034
- (param) => isJsonObject(param) ? [
1035
- {
1036
- title: String(param.title),
1037
- schema: param,
1038
- validate: createValidator(param, fieldValidators, {
1039
- apiHolder
1040
- })
1041
- }
1042
- ] : []
1043
- )
1044
- );
1045
- } catch (e) {
1046
- setErrorText(e.message);
1047
- }
1048
- },
1049
- 250,
1050
- [contentIsSpec, content, apiHolder]
1051
- );
1052
- if (!steps) {
1053
- return null;
1054
- }
1055
- return /* @__PURE__ */ React.createElement("div", { className: classes.containerWrapper }, /* @__PURE__ */ React.createElement("div", { className: classes.container }, /* @__PURE__ */ React.createElement(ErrorBoundary, { invalidator: steps, setErrorText }, /* @__PURE__ */ React.createElement(
1056
- MultistepJsonForm,
1057
- {
1058
- steps,
1059
- fields,
1060
- formData: data,
1061
- onChange: (e) => onUpdate(e.formData),
1062
- onReset: () => onUpdate({}),
1063
- finishButtonLabel: onDryRun && "Try It",
1064
- onFinish: onDryRun && (() => onDryRun(data)),
1065
- layouts
1066
- }
1067
- ))));
1068
- }
1069
- function TemplateEditorFormDirectoryEditorDryRun(props) {
1070
- const { setErrorText, fieldExtensions = [], layouts } = props;
1071
- const dryRun = useDryRun();
1072
- const directoryEditor = useDirectoryEditor();
1073
- const { selectedFile } = directoryEditor;
1074
- const [data, setData] = useState({});
1075
- const handleDryRun = async () => {
1076
- if (!selectedFile) {
1077
- return;
1078
- }
1079
- try {
1080
- await dryRun.execute({
1081
- templateContent: selectedFile.content,
1082
- values: data,
1083
- files: directoryEditor.files
1084
- });
1085
- setErrorText();
1086
- } catch (e) {
1087
- setErrorText(String(e.cause || e));
1088
- throw e;
1089
- }
1090
- };
1091
- const content = selectedFile && selectedFile.path.match(/\.ya?ml$/) ? selectedFile.content : void 0;
1092
- return /* @__PURE__ */ React.createElement(
1093
- TemplateEditorForm,
1094
- {
1095
- onDryRun: handleDryRun,
1096
- fieldExtensions,
1097
- setErrorText,
1098
- content,
1099
- data,
1100
- onUpdate: setData,
1101
- layouts
1102
- }
1103
- );
1104
- }
1105
- TemplateEditorForm.DirectoryEditorDryRun = TemplateEditorFormDirectoryEditorDryRun;
1106
-
1107
- const Form = withTheme(Theme);
1108
- const useStyles$2 = makeStyles((theme) => ({
1109
- root: {
1110
- gridArea: "pageContent",
1111
- display: "grid",
1112
- gridTemplateAreas: `
1113
- "controls controls"
1114
- "fieldForm preview"
1115
- `,
1116
- gridTemplateRows: "auto 1fr",
1117
- gridTemplateColumns: "1fr 1fr"
1118
- },
1119
- controls: {
1120
- gridArea: "controls",
1121
- display: "flex",
1122
- flexFlow: "row nowrap",
1123
- alignItems: "center",
1124
- margin: theme.spacing(1)
1125
- },
1126
- fieldForm: {
1127
- gridArea: "fieldForm"
1128
- },
1129
- preview: {
1130
- gridArea: "preview"
1131
- }
1132
- }));
1133
- const CustomFieldExplorer = ({
1134
- customFieldExtensions = [],
1135
- onClose
1136
- }) => {
1137
- var _a, _b;
1138
- const classes = useStyles$2();
1139
- const fieldOptions = customFieldExtensions.filter((field) => !!field.schema);
1140
- const [selectedField, setSelectedField] = useState(fieldOptions[0]);
1141
- const [fieldFormState, setFieldFormState] = useState({});
1142
- const [formState, setFormState] = useState({});
1143
- const [refreshKey, setRefreshKey] = useState(Date.now());
1144
- const sampleFieldTemplate = useMemo(
1145
- () => {
1146
- var _a2, _b2;
1147
- return yaml.stringify({
1148
- parameters: [
1149
- {
1150
- title: `${selectedField.name} Example`,
1151
- properties: {
1152
- [selectedField.name]: {
1153
- type: (_b2 = (_a2 = selectedField.schema) == null ? void 0 : _a2.returnValue) == null ? void 0 : _b2.type,
1154
- "ui:field": selectedField.name,
1155
- "ui:options": fieldFormState
1156
- }
1157
- }
1158
- }
1159
- ]
1160
- });
1161
- },
1162
- [fieldFormState, selectedField]
1163
- );
1164
- const fieldComponents = useMemo(() => {
1165
- return Object.fromEntries(
1166
- customFieldExtensions.map(({ name, component }) => [name, component])
1167
- );
1168
- }, [customFieldExtensions]);
1169
- const handleSelectionChange = useCallback(
1170
- (selection) => {
1171
- setSelectedField(selection);
1172
- setFieldFormState({});
1173
- setFormState({});
1174
- },
1175
- [setFieldFormState, setFormState, setSelectedField]
1176
- );
1177
- const handleFieldConfigChange = useCallback(
1178
- (state) => {
1179
- setFieldFormState(state);
1180
- setFormState({});
1181
- setRefreshKey(Date.now());
1182
- },
1183
- [setFieldFormState, setRefreshKey]
1184
- );
1185
- return /* @__PURE__ */ React.createElement("main", { className: classes.root }, /* @__PURE__ */ React.createElement("div", { className: classes.controls }, /* @__PURE__ */ React.createElement(FormControl, { variant: "outlined", size: "small", fullWidth: true }, /* @__PURE__ */ React.createElement(InputLabel, { id: "select-field-label" }, "Choose Custom Field Extension"), /* @__PURE__ */ React.createElement(
1186
- Select,
1187
- {
1188
- value: selectedField,
1189
- label: "Choose Custom Field Extension",
1190
- labelId: "select-field-label",
1191
- onChange: (e) => handleSelectionChange(e.target.value)
1192
- },
1193
- fieldOptions.map((option, idx) => /* @__PURE__ */ React.createElement(MenuItem$1, { key: idx, value: option }, option.name))
1194
- )), /* @__PURE__ */ React.createElement(IconButton, { size: "medium", onClick: onClose }, /* @__PURE__ */ React.createElement(CloseIcon, null))), /* @__PURE__ */ React.createElement("div", { className: classes.fieldForm }, /* @__PURE__ */ React.createElement(Card, null, /* @__PURE__ */ React.createElement(CardHeader, { title: "Field Options" }), /* @__PURE__ */ React.createElement(CardContent, null, /* @__PURE__ */ React.createElement(
1195
- Form,
1196
- {
1197
- showErrorList: false,
1198
- fields: { ...fieldOverrides, ...fieldComponents },
1199
- noHtml5Validate: true,
1200
- formData: fieldFormState,
1201
- formContext: { fieldFormState },
1202
- onSubmit: (e) => handleFieldConfigChange(e.formData),
1203
- schema: ((_a = selectedField.schema) == null ? void 0 : _a.uiOptions) || {}
1204
- },
1205
- /* @__PURE__ */ React.createElement(
1206
- Button,
1207
- {
1208
- variant: "contained",
1209
- color: "primary",
1210
- type: "submit",
1211
- disabled: !((_b = selectedField.schema) == null ? void 0 : _b.uiOptions)
1212
- },
1213
- "Apply"
1214
- )
1215
- )))), /* @__PURE__ */ React.createElement("div", { className: classes.preview }, /* @__PURE__ */ React.createElement(Card, null, /* @__PURE__ */ React.createElement(CardHeader, { title: "Example Template Spec" }), /* @__PURE__ */ React.createElement(CardContent, null, /* @__PURE__ */ React.createElement(
1216
- CodeMirror,
1217
- {
1218
- readOnly: true,
1219
- theme: "dark",
1220
- height: "100%",
1221
- extensions: [StreamLanguage.define(yaml$1)],
1222
- value: sampleFieldTemplate
1223
- }
1224
- ))), /* @__PURE__ */ React.createElement(
1225
- TemplateEditorForm,
1226
- {
1227
- key: refreshKey,
1228
- content: sampleFieldTemplate,
1229
- contentIsSpec: true,
1230
- fieldExtensions: customFieldExtensions,
1231
- data: formState,
1232
- onUpdate: setFormState,
1233
- setErrorText: () => null
1234
- }
1235
- )));
1236
- };
1237
-
1238
- const useStyles$1 = makeStyles({
1239
- // Reset and fix sizing to make sure scrolling behaves correctly
1240
- root: {
1241
- gridArea: "pageContent",
1242
- display: "grid",
1243
- gridTemplateAreas: `
1244
- "browser editor preview"
1245
- "results results results"
1246
- `,
1247
- gridTemplateColumns: "1fr 3fr 2fr",
1248
- gridTemplateRows: "1fr auto"
1249
- },
1250
- browser: {
1251
- gridArea: "browser",
1252
- overflow: "auto"
1253
- },
1254
- editor: {
1255
- gridArea: "editor",
1256
- overflow: "auto"
1257
- },
1258
- preview: {
1259
- gridArea: "preview",
1260
- overflow: "auto"
1261
- },
1262
- results: {
1263
- gridArea: "results"
1264
- }
1265
- });
1266
- const TemplateEditor = (props) => {
1267
- const classes = useStyles$1();
1268
- const [errorText, setErrorText] = useState();
1269
- return /* @__PURE__ */ React.createElement(DirectoryEditorProvider, { directory: props.directory }, /* @__PURE__ */ React.createElement(DryRunProvider, null, /* @__PURE__ */ React.createElement("main", { className: classes.root }, /* @__PURE__ */ React.createElement("section", { className: classes.browser }, /* @__PURE__ */ React.createElement(TemplateEditorBrowser, { onClose: props.onClose })), /* @__PURE__ */ React.createElement("section", { className: classes.editor }, /* @__PURE__ */ React.createElement(TemplateEditorTextArea.DirectoryEditor, { errorText })), /* @__PURE__ */ React.createElement("section", { className: classes.preview }, /* @__PURE__ */ React.createElement(
1270
- TemplateEditorForm.DirectoryEditorDryRun,
1271
- {
1272
- setErrorText,
1273
- fieldExtensions: props.fieldExtensions,
1274
- layouts: props.layouts
1275
- }
1276
- )), /* @__PURE__ */ React.createElement("section", { className: classes.results }, /* @__PURE__ */ React.createElement(DryRunResults, null)))));
1277
- };
1278
-
1279
- const EXAMPLE_TEMPLATE_PARAMS_YAML = `# Edit the template parameters below to see how they will render in the scaffolder form UI
1280
- parameters:
1281
- - title: Fill in some steps
1282
- required:
1283
- - name
1284
- properties:
1285
- name:
1286
- title: Name
1287
- type: string
1288
- description: Unique name of the component
1289
- owner:
1290
- title: Owner
1291
- type: string
1292
- description: Owner of the component
1293
- ui:field: OwnerPicker
1294
- ui:options:
1295
- catalogFilter:
1296
- kind: Group
1297
- - title: Choose a location
1298
- required:
1299
- - repoUrl
1300
- properties:
1301
- repoUrl:
1302
- title: Repository Location
1303
- type: string
1304
- ui:field: RepoUrlPicker
1305
- ui:options:
1306
- allowedHosts:
1307
- - github.com
1308
- steps:
1309
- - id: fetch-base
1310
- name: Fetch Base
1311
- action: fetch:template
1312
- input:
1313
- url: ./template
1314
- values:
1315
- name: \${{parameters.name}}
1316
- `;
1317
- const useStyles = makeStyles((theme) => ({
1318
- root: {
1319
- gridArea: "pageContent",
1320
- display: "grid",
1321
- gridTemplateAreas: `
1322
- "controls controls"
1323
- "textArea preview"
1324
- `,
1325
- gridTemplateRows: "auto 1fr",
1326
- gridTemplateColumns: "1fr 1fr"
1327
- },
1328
- controls: {
1329
- gridArea: "controls",
1330
- display: "flex",
1331
- flexFlow: "row nowrap",
1332
- alignItems: "center",
1333
- margin: theme.spacing(1)
1334
- },
1335
- textArea: {
1336
- gridArea: "textArea"
1337
- },
1338
- preview: {
1339
- gridArea: "preview"
1340
- }
1341
- }));
1342
- const TemplateFormPreviewer = ({
1343
- defaultPreviewTemplate = EXAMPLE_TEMPLATE_PARAMS_YAML,
1344
- customFieldExtensions = [],
1345
- onClose,
1346
- layouts = []
1347
- }) => {
1348
- const classes = useStyles();
1349
- const alertApi = useApi(alertApiRef);
1350
- const catalogApi = useApi(catalogApiRef);
1351
- const [selectedTemplate, setSelectedTemplate] = useState("");
1352
- const [errorText, setErrorText] = useState();
1353
- const [templateOptions, setTemplateOptions] = useState([]);
1354
- const [templateYaml, setTemplateYaml] = useState(defaultPreviewTemplate);
1355
- const [formState, setFormState] = useState({});
1356
- const { loading } = useAsync(
1357
- () => catalogApi.getEntities({
1358
- filter: { kind: "template" },
1359
- fields: [
1360
- "kind",
1361
- "metadata.namespace",
1362
- "metadata.name",
1363
- "metadata.title",
1364
- "spec.parameters",
1365
- "spec.steps",
1366
- "spec.output"
1367
- ]
1368
- }).then(
1369
- ({ items }) => setTemplateOptions(
1370
- items.map((template) => {
1371
- var _a;
1372
- return {
1373
- label: (_a = template.metadata.title) != null ? _a : humanizeEntityRef(template, { defaultKind: "template" }),
1374
- value: template
1375
- };
1376
- })
1377
- )
1378
- ).catch(
1379
- (e) => alertApi.post({
1380
- message: `Error loading exisiting templates: ${e.message}`,
1381
- severity: "error"
1382
- })
1383
- ),
1384
- [catalogApi]
1385
- );
1386
- const handleSelectChange = useCallback(
1387
- (selected) => {
1388
- setSelectedTemplate(selected);
1389
- setTemplateYaml(yaml.stringify(selected.spec));
1390
- },
1391
- [setTemplateYaml]
1392
- );
1393
- return /* @__PURE__ */ React.createElement(React.Fragment, null, loading && /* @__PURE__ */ React.createElement(LinearProgress, null), /* @__PURE__ */ React.createElement("main", { className: classes.root }, /* @__PURE__ */ React.createElement("div", { className: classes.controls }, /* @__PURE__ */ React.createElement(FormControl, { variant: "outlined", size: "small", fullWidth: true }, /* @__PURE__ */ React.createElement(InputLabel, { id: "select-template-label" }, "Load Existing Template"), /* @__PURE__ */ React.createElement(
1394
- Select,
1395
- {
1396
- value: selectedTemplate,
1397
- label: "Load Existing Template",
1398
- labelId: "select-template-label",
1399
- onChange: (e) => handleSelectChange(e.target.value)
1400
- },
1401
- templateOptions.map((option, idx) => /* @__PURE__ */ React.createElement(MenuItem$1, { key: idx, value: option.value }, option.label))
1402
- )), /* @__PURE__ */ React.createElement(IconButton, { size: "medium", onClick: onClose }, /* @__PURE__ */ React.createElement(CloseIcon, null))), /* @__PURE__ */ React.createElement("div", { className: classes.textArea }, /* @__PURE__ */ React.createElement(
1403
- TemplateEditorTextArea,
1404
- {
1405
- content: templateYaml,
1406
- onUpdate: setTemplateYaml,
1407
- errorText
1408
- }
1409
- )), /* @__PURE__ */ React.createElement("div", { className: classes.preview }, /* @__PURE__ */ React.createElement(
1410
- TemplateEditorForm,
1411
- {
1412
- content: templateYaml,
1413
- contentIsSpec: true,
1414
- fieldExtensions: customFieldExtensions,
1415
- data: formState,
1416
- onUpdate: setFormState,
1417
- setErrorText,
1418
- layouts
1419
- }
1420
- ))));
1421
- };
1422
-
1423
- function TemplateEditorPage(props) {
1424
- const [selection, setSelection] = useState();
1425
- let content = null;
1426
- if ((selection == null ? void 0 : selection.type) === "local") {
1427
- content = /* @__PURE__ */ React.createElement(
1428
- TemplateEditor,
1429
- {
1430
- directory: selection.directory,
1431
- fieldExtensions: props.customFieldExtensions,
1432
- onClose: () => setSelection(void 0),
1433
- layouts: props.layouts
1434
- }
1435
- );
1436
- } else if ((selection == null ? void 0 : selection.type) === "form") {
1437
- content = /* @__PURE__ */ React.createElement(
1438
- TemplateFormPreviewer,
1439
- {
1440
- defaultPreviewTemplate: props.defaultPreviewTemplate,
1441
- customFieldExtensions: props.customFieldExtensions,
1442
- onClose: () => setSelection(void 0),
1443
- layouts: props.layouts
1444
- }
1445
- );
1446
- } else if ((selection == null ? void 0 : selection.type) === "field-explorer") {
1447
- content = /* @__PURE__ */ React.createElement(
1448
- CustomFieldExplorer,
1449
- {
1450
- customFieldExtensions: props.customFieldExtensions,
1451
- onClose: () => setSelection(void 0)
1452
- }
1453
- );
1454
- } else {
1455
- content = /* @__PURE__ */ React.createElement(Content, null, /* @__PURE__ */ React.createElement(
1456
- TemplateEditorIntro,
1457
- {
1458
- onSelect: (option) => {
1459
- if (option === "local") {
1460
- WebFileSystemAccess.requestDirectoryAccess().then((directory) => setSelection({ type: "local", directory })).catch(() => {
1461
- });
1462
- } else if (option === "form") {
1463
- setSelection({ type: "form" });
1464
- } else if (option === "field-explorer") {
1465
- setSelection({ type: "field-explorer" });
1466
- }
1467
- }
1468
- }
1469
- ));
1470
- }
1471
- return /* @__PURE__ */ React.createElement(Page, { themeId: "home" }, /* @__PURE__ */ React.createElement(
1472
- Header,
1473
- {
1474
- title: "Template Editor",
1475
- subtitle: "Edit, preview, and try out templates and template forms"
1476
- }
1477
- ), content);
1478
- }
1479
-
1480
- const Router = (props) => {
1481
- const { groups, components = {}, defaultPreviewTemplate } = props;
1482
- const { ReviewStepComponent, TemplateCardComponent, TaskPageComponent } = components;
1483
- const outlet = useOutlet();
1484
- const TaskPageElement = TaskPageComponent != null ? TaskPageComponent : TaskPage;
1485
- const customFieldExtensions = useCustomFieldExtensions(outlet);
1486
- const fieldExtensions = [
1487
- ...customFieldExtensions,
1488
- ...DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS.filter(
1489
- ({ name }) => !customFieldExtensions.some(
1490
- (customFieldExtension) => customFieldExtension.name === name
1491
- )
1492
- )
1493
- ];
1494
- const customLayouts = useCustomLayouts(outlet);
1495
- const RedirectingComponent = () => {
1496
- const { templateName } = useRouteRefParams(legacySelectedTemplateRouteRef);
1497
- const newLink = useRouteRef(selectedTemplateRouteRef);
1498
- useEffect(
1499
- () => (
1500
- // eslint-disable-next-line no-console
1501
- console.warn(
1502
- "The route /template/:templateName is deprecated, please use the new /template/:namespace/:templateName route instead"
1503
- )
1504
- ),
1505
- []
1506
- );
1507
- return /* @__PURE__ */ React.createElement(Navigate, { to: newLink({ namespace: "default", templateName }) });
1508
- };
1509
- return /* @__PURE__ */ React.createElement(Routes, null, /* @__PURE__ */ React.createElement(
1510
- Route,
1511
- {
1512
- path: "/",
1513
- element: /* @__PURE__ */ React.createElement(
1514
- ScaffolderPage,
1515
- {
1516
- groups,
1517
- TemplateCardComponent,
1518
- contextMenu: props.contextMenu,
1519
- headerOptions: props.headerOptions
1520
- }
1521
- )
1522
- }
1523
- ), /* @__PURE__ */ React.createElement(
1524
- Route,
1525
- {
1526
- path: legacySelectedTemplateRouteRef.path,
1527
- element: /* @__PURE__ */ React.createElement(RedirectingComponent, null)
1528
- }
1529
- ), /* @__PURE__ */ React.createElement(
1530
- Route,
1531
- {
1532
- path: selectedTemplateRouteRef.path,
1533
- element: /* @__PURE__ */ React.createElement(SecretsContextProvider, null, /* @__PURE__ */ React.createElement(
1534
- TemplatePage,
1535
- {
1536
- ReviewStepComponent,
1537
- customFieldExtensions: fieldExtensions,
1538
- layouts: customLayouts,
1539
- headerOptions: props.headerOptions
1540
- }
1541
- ))
1542
- }
1543
- ), /* @__PURE__ */ React.createElement(
1544
- Route,
1545
- {
1546
- path: scaffolderListTaskRouteRef.path,
1547
- element: /* @__PURE__ */ React.createElement(ListTasksPage, null)
1548
- }
1549
- ), /* @__PURE__ */ React.createElement(Route, { path: scaffolderTaskRouteRef.path, element: /* @__PURE__ */ React.createElement(TaskPageElement, null) }), /* @__PURE__ */ React.createElement(Route, { path: actionsRouteRef.path, element: /* @__PURE__ */ React.createElement(ActionsPage, null) }), /* @__PURE__ */ React.createElement(
1550
- Route,
1551
- {
1552
- path: editRouteRef.path,
1553
- element: /* @__PURE__ */ React.createElement(SecretsContextProvider, null, /* @__PURE__ */ React.createElement(
1554
- TemplateEditorPage,
1555
- {
1556
- defaultPreviewTemplate,
1557
- customFieldExtensions: fieldExtensions,
1558
- layouts: customLayouts
1559
- }
1560
- ))
1561
- }
1562
- ), /* @__PURE__ */ React.createElement(Route, { path: "preview", element: /* @__PURE__ */ React.createElement(Navigate, { to: "../edit" }) }));
1563
- };
1564
-
1565
- export { Router };
1566
- //# sourceMappingURL=Router-2826a2b8.esm.js.map