@backstage/plugin-scaffolder 1.13.0-next.0 → 1.13.0-next.2

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