@backstage/plugin-scaffolder 1.3.0-next.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,1014 +0,0 @@
1
- import React, { useState, useContext, useCallback } from 'react';
2
- import { useNavigate, Navigate, useOutlet, Routes, Route } from 'react-router';
3
- import { ItemCardHeader, MarkdownContent, Button, ContentHeader, Progress, WarningPanel, Link as Link$1, Content, ItemCardGrid, Page, Header, CreateButton, SupportButton, StructuredMetadataTable, InfoCard, ErrorPage } from '@backstage/core-components';
4
- import { useRouteRef, useApi, errorApiRef, featureFlagsApiRef, useApiHolder, alertApiRef, useElementFilter } from '@backstage/core-plugin-api';
5
- import { getEntityRelations, getEntitySourceLocation, FavoriteEntity, EntityRefLinks, useEntityList, EntityListProvider, CatalogFilterLayout, EntitySearchBar, EntityKindPicker, UserListPicker, EntityTagPicker, catalogApiRef, humanizeEntityRef } from '@backstage/plugin-catalog-react';
6
- import { s as selectedTemplateRouteRef, r as rootRouteRef, a as registerComponentRouteRef, T as TemplateTypePicker, S as SecretsContext, b as scaffolderApiRef, c as scaffolderTaskRouteRef, F as FIELD_EXTENSION_WRAPPER_KEY, d as FIELD_EXTENSION_KEY, e as SecretsContextProvider, f as actionsRouteRef, g as editRouteRef, h as TaskPage } from './index-9dc0f892.esm.js';
7
- import { RELATION_OWNED_BY, stringifyEntityRef } from '@backstage/catalog-model';
8
- import { makeStyles, useTheme, Card, CardMedia, CardContent, Box, Typography, Chip, CardActions, IconButton, Tooltip, Link, Stepper, Step, StepLabel, StepContent, Button as Button$1, Paper, LinearProgress, TableContainer, Table, TableHead, TableRow, TableCell, TableBody, Grid, FormControl, InputLabel, Select, MenuItem as MenuItem$1 } from '@material-ui/core';
9
- import { scmIntegrationsApiRef, ScmIntegrationIcon } from '@backstage/integration-react';
10
- import WarningIcon from '@material-ui/icons/Warning';
11
- import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common';
12
- import { usePermission } from '@backstage/plugin-permission-react';
13
- import IconButton$1 from '@material-ui/core/IconButton';
14
- import ListItemIcon from '@material-ui/core/ListItemIcon';
15
- import ListItemText from '@material-ui/core/ListItemText';
16
- import MenuItem from '@material-ui/core/MenuItem';
17
- import MenuList from '@material-ui/core/MenuList';
18
- import Popover from '@material-ui/core/Popover';
19
- import { makeStyles as makeStyles$1 } from '@material-ui/core/styles';
20
- import Description from '@material-ui/icons/Description';
21
- import Edit from '@material-ui/icons/Edit';
22
- import MoreVert from '@material-ui/icons/MoreVert';
23
- import qs from 'qs';
24
- import { useParams } from 'react-router-dom';
25
- import useAsync from 'react-use/lib/useAsync';
26
- import { withTheme } from '@rjsf/core';
27
- import { Theme } from '@rjsf/material-ui';
28
- import cloneDeep from 'lodash/cloneDeep';
29
- import classNames from 'classnames';
30
- import useDebounce from 'react-use/lib/useDebounce';
31
- import { yaml as yaml$1 } from '@codemirror/legacy-modes/mode/yaml';
32
- import { showPanel } from '@codemirror/view';
33
- import { StreamLanguage } from '@codemirror/language';
34
- import CodeMirror from '@uiw/react-codemirror';
35
- import yaml from 'yaml';
36
- import { D as DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS } from './default-fb4a2845.esm.js';
37
- import '@backstage/errors';
38
- import 'zen-observable';
39
- import '@material-ui/core/FormControl';
40
- import '@material-ui/lab/Autocomplete';
41
- import 'react-use/lib/useEffectOnce';
42
- import '@material-ui/lab';
43
- import '@material-ui/core/FormHelperText';
44
- import '@material-ui/core/Input';
45
- import '@material-ui/core/InputLabel';
46
- import 'lodash/capitalize';
47
- import '@material-ui/icons/CheckBox';
48
- import '@material-ui/icons/CheckBoxOutlineBlank';
49
- import '@material-ui/icons/ExpandMore';
50
- import '@material-ui/core/Grid';
51
- import '@material-ui/core/Step';
52
- import '@material-ui/core/StepLabel';
53
- import '@material-ui/core/Stepper';
54
- import '@material-ui/core/Typography';
55
- import '@material-ui/icons/Cancel';
56
- import '@material-ui/icons/Check';
57
- import '@material-ui/icons/FiberManualRecord';
58
- import 'luxon';
59
- import 'react-use/lib/useInterval';
60
- import 'use-immer';
61
- import '@material-ui/icons/Language';
62
-
63
- const useStyles$3 = makeStyles((theme) => ({
64
- cardHeader: {
65
- position: "relative"
66
- },
67
- title: {
68
- backgroundImage: ({ backgroundImage }) => backgroundImage
69
- },
70
- box: {
71
- overflow: "hidden",
72
- textOverflow: "ellipsis",
73
- display: "-webkit-box",
74
- "-webkit-line-clamp": 10,
75
- "-webkit-box-orient": "vertical",
76
- paddingBottom: "0.8em"
77
- },
78
- label: {
79
- color: theme.palette.text.secondary,
80
- textTransform: "uppercase",
81
- fontSize: "0.65rem",
82
- fontWeight: "bold",
83
- letterSpacing: 0.5,
84
- lineHeight: 1,
85
- paddingBottom: "0.2rem"
86
- },
87
- leftButton: {
88
- marginRight: "auto"
89
- },
90
- starButton: {
91
- position: "absolute",
92
- top: theme.spacing(0.5),
93
- right: theme.spacing(0.5),
94
- padding: "0.25rem",
95
- color: "#fff"
96
- }
97
- }));
98
- const useDeprecationStyles = makeStyles((theme) => ({
99
- deprecationIcon: {
100
- position: "absolute",
101
- top: theme.spacing(0.5),
102
- right: theme.spacing(3.5),
103
- padding: "0.25rem"
104
- },
105
- link: {
106
- color: theme.palette.warning.light
107
- }
108
- }));
109
- const getTemplateCardProps = (template) => {
110
- var _a, _b, _c, _d, _e;
111
- return {
112
- key: template.metadata.uid,
113
- name: template.metadata.name,
114
- title: `${(_a = template.metadata.title || template.metadata.name) != null ? _a : ""}`,
115
- type: (_b = template.spec.type) != null ? _b : "",
116
- description: (_c = template.metadata.description) != null ? _c : "-",
117
- tags: (_e = (_d = template.metadata) == null ? void 0 : _d.tags) != null ? _e : []
118
- };
119
- };
120
- const DeprecationWarning = () => {
121
- const styles = useDeprecationStyles();
122
- const Title = /* @__PURE__ */ React.createElement(Typography, {
123
- style: { padding: 10, maxWidth: 300 }
124
- }, "This template uses a syntax that has been deprecated, and should be migrated to a newer syntax. Click for more info.");
125
- return /* @__PURE__ */ React.createElement("div", {
126
- className: styles.deprecationIcon
127
- }, /* @__PURE__ */ React.createElement(Tooltip, {
128
- title: Title
129
- }, /* @__PURE__ */ React.createElement(Link, {
130
- href: "https://backstage.io/docs/features/software-templates/migrating-from-v1beta2-to-v1beta3",
131
- className: styles.link
132
- }, /* @__PURE__ */ React.createElement(WarningIcon, null))));
133
- };
134
- const TemplateCard = ({ template, deprecated }) => {
135
- var _a;
136
- const backstageTheme = useTheme();
137
- const templateRoute = useRouteRef(selectedTemplateRouteRef);
138
- const templateProps = getTemplateCardProps(template);
139
- const ownedByRelations = getEntityRelations(template, RELATION_OWNED_BY);
140
- const themeId = backstageTheme.getPageTheme({ themeId: templateProps.type }) ? templateProps.type : "other";
141
- const theme = backstageTheme.getPageTheme({ themeId });
142
- const classes = useStyles$3({ backgroundImage: theme.backgroundImage });
143
- const href = templateRoute({ templateName: templateProps.name });
144
- const scmIntegrationsApi = useApi(scmIntegrationsApiRef);
145
- const sourceLocation = getEntitySourceLocation(template, scmIntegrationsApi);
146
- return /* @__PURE__ */ React.createElement(Card, null, /* @__PURE__ */ React.createElement(CardMedia, {
147
- className: classes.cardHeader
148
- }, /* @__PURE__ */ React.createElement(FavoriteEntity, {
149
- className: classes.starButton,
150
- entity: template
151
- }), deprecated && /* @__PURE__ */ React.createElement(DeprecationWarning, null), /* @__PURE__ */ React.createElement(ItemCardHeader, {
152
- title: templateProps.title,
153
- subtitle: templateProps.type,
154
- classes: { root: classes.title }
155
- })), /* @__PURE__ */ React.createElement(CardContent, {
156
- style: { display: "grid" }
157
- }, /* @__PURE__ */ React.createElement(Box, {
158
- className: classes.box
159
- }, /* @__PURE__ */ React.createElement(Typography, {
160
- variant: "body2",
161
- className: classes.label
162
- }, "Description"), /* @__PURE__ */ React.createElement(MarkdownContent, {
163
- content: templateProps.description
164
- })), /* @__PURE__ */ React.createElement(Box, {
165
- className: classes.box
166
- }, /* @__PURE__ */ React.createElement(Typography, {
167
- variant: "body2",
168
- className: classes.label
169
- }, "Owner"), /* @__PURE__ */ React.createElement(EntityRefLinks, {
170
- entityRefs: ownedByRelations,
171
- defaultKind: "Group"
172
- })), /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Typography, {
173
- variant: "body2",
174
- className: classes.label
175
- }, "Tags"), (_a = templateProps.tags) == null ? void 0 : _a.map((tag) => /* @__PURE__ */ React.createElement(Chip, {
176
- size: "small",
177
- label: tag,
178
- key: tag
179
- })))), /* @__PURE__ */ React.createElement(CardActions, null, sourceLocation && /* @__PURE__ */ React.createElement(IconButton, {
180
- className: classes.leftButton,
181
- href: sourceLocation.locationTargetUrl
182
- }, /* @__PURE__ */ React.createElement(ScmIntegrationIcon, {
183
- type: sourceLocation.integrationType
184
- })), /* @__PURE__ */ React.createElement(Button, {
185
- color: "primary",
186
- to: href,
187
- "aria-label": `Choose ${templateProps.title}`
188
- }, "Choose")));
189
- };
190
-
191
- const TemplateList = ({
192
- TemplateCardComponent,
193
- group
194
- }) => {
195
- const { loading, error, entities } = useEntityList();
196
- const Card = TemplateCardComponent || TemplateCard;
197
- const maybeFilteredEntities = group ? entities.filter((e) => group.filter(e)) : entities;
198
- const titleComponent = (() => {
199
- if (group && group.title) {
200
- if (typeof group.title === "string") {
201
- return /* @__PURE__ */ React.createElement(ContentHeader, {
202
- title: group.title
203
- });
204
- }
205
- return group.title;
206
- }
207
- return /* @__PURE__ */ React.createElement(ContentHeader, {
208
- title: "Other Templates"
209
- });
210
- })();
211
- if (group && maybeFilteredEntities.length === 0) {
212
- return null;
213
- }
214
- return /* @__PURE__ */ React.createElement(React.Fragment, null, loading && /* @__PURE__ */ React.createElement(Progress, null), error && /* @__PURE__ */ React.createElement(WarningPanel, {
215
- title: "Oops! Something went wrong loading the templates"
216
- }, error.message), !error && !loading && !entities.length && /* @__PURE__ */ React.createElement(Typography, {
217
- variant: "body2"
218
- }, "No templates found that match your filter. Learn more about", " ", /* @__PURE__ */ React.createElement(Link$1, {
219
- to: "https://backstage.io/docs/features/software-templates/adding-templates"
220
- }, "adding templates"), "."), /* @__PURE__ */ React.createElement(Content, null, titleComponent, /* @__PURE__ */ React.createElement(ItemCardGrid, null, maybeFilteredEntities && (maybeFilteredEntities == null ? void 0 : maybeFilteredEntities.length) > 0 && maybeFilteredEntities.map((template) => /* @__PURE__ */ React.createElement(Card, {
221
- key: stringifyEntityRef(template),
222
- template,
223
- deprecated: template.apiVersion === "backstage.io/v1beta2"
224
- })))));
225
- };
226
-
227
- const useStyles$2 = makeStyles$1({
228
- button: {
229
- color: "white"
230
- }
231
- });
232
- function ScaffolderPageContextMenu(props) {
233
- const classes = useStyles$2();
234
- const [anchorEl, setAnchorEl] = useState();
235
- const pageLink = useRouteRef(rootRouteRef);
236
- const navigate = useNavigate();
237
- const showEditor = props.editor !== false;
238
- const showActions = props.actions !== false;
239
- if (!showEditor && !showActions) {
240
- return null;
241
- }
242
- const onOpen = (event) => {
243
- setAnchorEl(event.currentTarget);
244
- };
245
- const onClose = () => {
246
- setAnchorEl(void 0);
247
- };
248
- return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(IconButton$1, {
249
- "aria-label": "more",
250
- "aria-controls": "long-menu",
251
- "aria-haspopup": "true",
252
- onClick: onOpen,
253
- "data-testid": "menu-button",
254
- color: "inherit",
255
- className: classes.button
256
- }, /* @__PURE__ */ React.createElement(MoreVert, null)), /* @__PURE__ */ React.createElement(Popover, {
257
- open: Boolean(anchorEl),
258
- onClose,
259
- anchorEl,
260
- anchorOrigin: { vertical: "bottom", horizontal: "right" },
261
- transformOrigin: { vertical: "top", horizontal: "right" }
262
- }, /* @__PURE__ */ React.createElement(MenuList, null, showEditor && /* @__PURE__ */ React.createElement(MenuItem, {
263
- onClick: () => navigate(`${pageLink()}/edit`)
264
- }, /* @__PURE__ */ React.createElement(ListItemIcon, null, /* @__PURE__ */ React.createElement(Edit, {
265
- fontSize: "small"
266
- })), /* @__PURE__ */ React.createElement(ListItemText, {
267
- primary: "Template Editor"
268
- })), showActions && /* @__PURE__ */ React.createElement(MenuItem, {
269
- onClick: () => navigate(`${pageLink()}/actions`)
270
- }, /* @__PURE__ */ React.createElement(ListItemIcon, null, /* @__PURE__ */ React.createElement(Description, {
271
- fontSize: "small"
272
- })), /* @__PURE__ */ React.createElement(ListItemText, {
273
- primary: "Installed Actions"
274
- })))));
275
- }
276
-
277
- const ScaffolderPageContents = ({
278
- TemplateCardComponent,
279
- groups,
280
- contextMenu
281
- }) => {
282
- const registerComponentLink = useRouteRef(registerComponentRouteRef);
283
- const otherTemplatesGroup = {
284
- title: groups ? "Other Templates" : "Templates",
285
- filter: (entity) => {
286
- const filtered = (groups != null ? groups : []).map((group) => group.filter(entity));
287
- return !filtered.some((result) => result === true);
288
- }
289
- };
290
- const { allowed } = usePermission({
291
- permission: catalogEntityCreatePermission
292
- });
293
- return /* @__PURE__ */ React.createElement(Page, {
294
- themeId: "home"
295
- }, /* @__PURE__ */ React.createElement(Header, {
296
- pageTitleOverride: "Create a New Component",
297
- title: "Create a New Component",
298
- subtitle: "Create new software components using standard templates"
299
- }, /* @__PURE__ */ React.createElement(ScaffolderPageContextMenu, {
300
- ...contextMenu
301
- })), /* @__PURE__ */ React.createElement(Content, null, /* @__PURE__ */ React.createElement(ContentHeader, {
302
- title: "Available Templates"
303
- }, allowed && /* @__PURE__ */ React.createElement(CreateButton, {
304
- title: "Register Existing Component",
305
- to: registerComponentLink && registerComponentLink()
306
- }), /* @__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, {
307
- initialFilter: "template",
308
- hidden: true
309
- }), /* @__PURE__ */ React.createElement(UserListPicker, {
310
- initialFilter: "all",
311
- availableFilters: ["all", "starred"]
312
- }), /* @__PURE__ */ React.createElement(TemplateTypePicker, null), /* @__PURE__ */ React.createElement(EntityTagPicker, null)), /* @__PURE__ */ React.createElement(CatalogFilterLayout.Content, null, groups && groups.map((group, index) => /* @__PURE__ */ React.createElement(TemplateList, {
313
- key: index,
314
- TemplateCardComponent,
315
- group
316
- })), /* @__PURE__ */ React.createElement(TemplateList, {
317
- key: "other",
318
- TemplateCardComponent,
319
- group: otherTemplatesGroup
320
- })))));
321
- };
322
- const ScaffolderPage = ({
323
- TemplateCardComponent,
324
- groups
325
- }) => /* @__PURE__ */ React.createElement(EntityListProvider, null, /* @__PURE__ */ React.createElement(ScaffolderPageContents, {
326
- TemplateCardComponent,
327
- groups
328
- }));
329
-
330
- function isObject$1(value) {
331
- return typeof value === "object" && value !== null && !Array.isArray(value);
332
- }
333
- function extractUiSchema(schema, uiSchema) {
334
- if (!isObject$1(schema)) {
335
- return;
336
- }
337
- const { properties, items, anyOf, oneOf, allOf, dependencies } = schema;
338
- for (const propName in schema) {
339
- if (!schema.hasOwnProperty(propName)) {
340
- continue;
341
- }
342
- if (propName.startsWith("ui:")) {
343
- uiSchema[propName] = schema[propName];
344
- delete schema[propName];
345
- }
346
- }
347
- if (isObject$1(properties)) {
348
- for (const propName in properties) {
349
- if (!properties.hasOwnProperty(propName)) {
350
- continue;
351
- }
352
- const schemaNode = properties[propName];
353
- if (!isObject$1(schemaNode)) {
354
- continue;
355
- }
356
- const innerUiSchema = {};
357
- uiSchema[propName] = innerUiSchema;
358
- extractUiSchema(schemaNode, innerUiSchema);
359
- }
360
- }
361
- if (isObject$1(items)) {
362
- const innerUiSchema = {};
363
- uiSchema.items = innerUiSchema;
364
- extractUiSchema(items, innerUiSchema);
365
- }
366
- if (Array.isArray(anyOf)) {
367
- for (const schemaNode of anyOf) {
368
- if (!isObject$1(schemaNode)) {
369
- continue;
370
- }
371
- extractUiSchema(schemaNode, uiSchema);
372
- }
373
- }
374
- if (Array.isArray(oneOf)) {
375
- for (const schemaNode of oneOf) {
376
- if (!isObject$1(schemaNode)) {
377
- continue;
378
- }
379
- extractUiSchema(schemaNode, uiSchema);
380
- }
381
- }
382
- if (Array.isArray(allOf)) {
383
- for (const schemaNode of allOf) {
384
- if (!isObject$1(schemaNode)) {
385
- continue;
386
- }
387
- extractUiSchema(schemaNode, uiSchema);
388
- }
389
- }
390
- if (isObject$1(dependencies)) {
391
- for (const depName of Object.keys(dependencies)) {
392
- const schemaNode = dependencies[depName];
393
- if (!isObject$1(schemaNode)) {
394
- continue;
395
- }
396
- extractUiSchema(schemaNode, uiSchema);
397
- }
398
- }
399
- }
400
- function transformSchemaToProps(inputSchema) {
401
- inputSchema.type = inputSchema.type || "object";
402
- const schema = JSON.parse(JSON.stringify(inputSchema));
403
- delete schema.title;
404
- const uiSchema = {};
405
- extractUiSchema(schema, uiSchema);
406
- return { schema, uiSchema };
407
- }
408
-
409
- const DescriptionField = ({ description }) => description && /* @__PURE__ */ React.createElement(MarkdownContent, {
410
- content: description,
411
- linkTarget: "_blank"
412
- });
413
-
414
- var fieldOverrides = /*#__PURE__*/Object.freeze({
415
- __proto__: null,
416
- DescriptionField: DescriptionField
417
- });
418
-
419
- const Form = withTheme(Theme);
420
- function getUiSchemasFromSteps(steps) {
421
- const uiSchemas = [];
422
- steps.forEach((step) => {
423
- const schemaProps = step.schema.properties;
424
- for (const key in schemaProps) {
425
- if (schemaProps.hasOwnProperty(key)) {
426
- const uiSchema = schemaProps[key];
427
- uiSchema.name = key;
428
- uiSchemas.push(uiSchema);
429
- }
430
- }
431
- });
432
- return uiSchemas;
433
- }
434
- function getReviewData(formData, steps) {
435
- const uiSchemas = getUiSchemasFromSteps(steps);
436
- const reviewData = {};
437
- for (const key in formData) {
438
- if (formData.hasOwnProperty(key)) {
439
- const uiSchema = uiSchemas.find((us) => us.name === key);
440
- if (!uiSchema) {
441
- reviewData[key] = formData[key];
442
- continue;
443
- }
444
- if (uiSchema["ui:widget"] === "password") {
445
- reviewData[key] = "******";
446
- continue;
447
- }
448
- if (!uiSchema["ui:backstage"] || !uiSchema["ui:backstage"].review) {
449
- reviewData[key] = formData[key];
450
- continue;
451
- }
452
- const review = uiSchema["ui:backstage"].review;
453
- if (!review.show) {
454
- continue;
455
- }
456
- if (review.mask) {
457
- reviewData[key] = review.mask;
458
- continue;
459
- }
460
- reviewData[key] = formData[key];
461
- }
462
- }
463
- return reviewData;
464
- }
465
- const MultistepJsonForm = (props) => {
466
- const { formData, onChange, onReset, onFinish, fields, widgets } = props;
467
- const [activeStep, setActiveStep] = useState(0);
468
- const [disableButtons, setDisableButtons] = useState(false);
469
- const errorApi = useApi(errorApiRef);
470
- const featureFlagApi = useApi(featureFlagsApiRef);
471
- const featureFlagKey = "backstage:featureFlag";
472
- const filterOutProperties = (step) => {
473
- var _a;
474
- const filteredStep = cloneDeep(step);
475
- const removedPropertyKeys = [];
476
- if (filteredStep.schema.properties) {
477
- filteredStep.schema.properties = Object.fromEntries(Object.entries(filteredStep.schema.properties).filter(([key, value]) => {
478
- if (value[featureFlagKey]) {
479
- if (featureFlagApi.isActive(value[featureFlagKey])) {
480
- return true;
481
- }
482
- removedPropertyKeys.push(key);
483
- return false;
484
- }
485
- return true;
486
- }));
487
- filteredStep.schema.required = Array.isArray(filteredStep.schema.required) ? (_a = filteredStep.schema.required) == null ? void 0 : _a.filter((r) => !removedPropertyKeys.includes(r)) : filteredStep.schema.required;
488
- }
489
- return filteredStep;
490
- };
491
- const steps = props.steps.filter((step) => {
492
- const featureFlag = step.schema[featureFlagKey];
493
- return typeof featureFlag !== "string" || featureFlagApi.isActive(featureFlag);
494
- }).map(filterOutProperties);
495
- const handleReset = () => {
496
- setActiveStep(0);
497
- onReset();
498
- };
499
- const handleNext = () => {
500
- setActiveStep(Math.min(activeStep + 1, steps.length));
501
- };
502
- const handleBack = () => setActiveStep(Math.max(activeStep - 1, 0));
503
- const handleCreate = async () => {
504
- if (!onFinish) {
505
- return;
506
- }
507
- setDisableButtons(true);
508
- try {
509
- await onFinish();
510
- } catch (err) {
511
- setDisableButtons(false);
512
- errorApi.post(err);
513
- }
514
- };
515
- return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Stepper, {
516
- activeStep,
517
- orientation: "vertical"
518
- }, steps.map(({ title, schema, ...formProps }, index) => {
519
- return /* @__PURE__ */ React.createElement(Step, {
520
- key: title
521
- }, /* @__PURE__ */ React.createElement(StepLabel, {
522
- "aria-label": `Step ${index + 1} ${title}`,
523
- "aria-disabled": "false",
524
- tabIndex: 0
525
- }, /* @__PURE__ */ React.createElement(Typography, {
526
- variant: "h6",
527
- component: "h3"
528
- }, title)), /* @__PURE__ */ React.createElement(StepContent, {
529
- key: title
530
- }, /* @__PURE__ */ React.createElement(Form, {
531
- showErrorList: false,
532
- fields: { ...fieldOverrides, ...fields },
533
- widgets,
534
- noHtml5Validate: true,
535
- formData,
536
- formContext: { formData },
537
- onChange,
538
- onSubmit: (e) => {
539
- if (e.errors.length === 0)
540
- handleNext();
541
- },
542
- ...formProps,
543
- ...transformSchemaToProps(schema)
544
- }, /* @__PURE__ */ React.createElement(Button$1, {
545
- disabled: activeStep === 0,
546
- onClick: handleBack
547
- }, "Back"), /* @__PURE__ */ React.createElement(Button$1, {
548
- variant: "contained",
549
- color: "primary",
550
- type: "submit"
551
- }, "Next step"))));
552
- })), activeStep === steps.length && /* @__PURE__ */ React.createElement(Content, null, /* @__PURE__ */ React.createElement(Paper, {
553
- square: true,
554
- elevation: 0
555
- }, /* @__PURE__ */ React.createElement(Typography, {
556
- variant: "h6"
557
- }, "Review and create"), /* @__PURE__ */ React.createElement(StructuredMetadataTable, {
558
- dense: true,
559
- metadata: getReviewData(formData, steps)
560
- }), /* @__PURE__ */ React.createElement(Box, {
561
- mb: 4
562
- }), /* @__PURE__ */ React.createElement(Button$1, {
563
- onClick: handleBack,
564
- disabled: disableButtons
565
- }, "Back"), /* @__PURE__ */ React.createElement(Button$1, {
566
- onClick: handleReset,
567
- disabled: disableButtons
568
- }, "Reset"), /* @__PURE__ */ React.createElement(Button$1, {
569
- variant: "contained",
570
- color: "primary",
571
- onClick: handleCreate,
572
- disabled: !onFinish || disableButtons
573
- }, "Create"))));
574
- };
575
-
576
- function isObject(obj) {
577
- return typeof obj === "object" && obj !== null && !Array.isArray(obj);
578
- }
579
- const createValidator = (rootSchema, validators, context) => {
580
- function validate(schema, formData, errors) {
581
- const schemaProps = schema.properties;
582
- const customObject = schema.type === "object" && schemaProps === void 0;
583
- if (!isObject(schemaProps) && !customObject) {
584
- return;
585
- }
586
- if (schemaProps) {
587
- for (const [key, propData] of Object.entries(formData)) {
588
- const propValidation = errors[key];
589
- if (isObject(propData)) {
590
- const propSchemaProps = schemaProps[key];
591
- if (isObject(propSchemaProps)) {
592
- validate(propSchemaProps, propData, propValidation);
593
- }
594
- } else {
595
- const propSchema = schemaProps[key];
596
- const fieldName = isObject(propSchema) && propSchema["ui:field"];
597
- if (fieldName && typeof validators[fieldName] === "function") {
598
- validators[fieldName](propData, propValidation, context);
599
- }
600
- }
601
- }
602
- } else if (customObject) {
603
- const fieldName = schema["ui:field"];
604
- if (fieldName && typeof validators[fieldName] === "function") {
605
- validators[fieldName](formData, errors, context);
606
- }
607
- }
608
- }
609
- return (formData, errors) => {
610
- validate(rootSchema, formData, errors);
611
- return errors;
612
- };
613
- };
614
-
615
- const useTemplateParameterSchema = (templateRef) => {
616
- const scaffolderApi = useApi(scaffolderApiRef);
617
- const { value, loading, error } = useAsync(() => scaffolderApi.getTemplateParameterSchema(templateRef), [scaffolderApi, templateRef]);
618
- return { schema: value, loading, error };
619
- };
620
- const TemplatePage = ({
621
- customFieldExtensions = []
622
- }) => {
623
- const apiHolder = useApiHolder();
624
- const secretsContext = useContext(SecretsContext);
625
- const errorApi = useApi(errorApiRef);
626
- const scaffolderApi = useApi(scaffolderApiRef);
627
- const { templateName } = useParams();
628
- const navigate = useNavigate();
629
- const scaffolderTaskRoute = useRouteRef(scaffolderTaskRouteRef);
630
- const rootRoute = useRouteRef(rootRouteRef);
631
- const { schema, loading, error } = useTemplateParameterSchema(templateName);
632
- const [formState, setFormState] = useState(() => {
633
- var _a;
634
- const query = qs.parse(window.location.search, {
635
- ignoreQueryPrefix: true
636
- });
637
- try {
638
- return JSON.parse(query.formData);
639
- } catch (e) {
640
- return (_a = query.formData) != null ? _a : {};
641
- }
642
- });
643
- const handleFormReset = () => setFormState({});
644
- const handleChange = useCallback((e) => setFormState(e.formData), [setFormState]);
645
- const handleCreate = async () => {
646
- var _a;
647
- const { taskId } = await scaffolderApi.scaffold({
648
- templateRef: stringifyEntityRef({
649
- name: templateName,
650
- kind: "template",
651
- namespace: "default"
652
- }),
653
- values: formState,
654
- secrets: secretsContext == null ? void 0 : secretsContext.secrets
655
- });
656
- const formParams = qs.stringify({ formData: formState }, { addQueryPrefix: true });
657
- const newUrl = `${window.location.pathname}${formParams}`;
658
- (_a = window.history) == null ? void 0 : _a.replaceState(null, document.title, newUrl);
659
- navigate(scaffolderTaskRoute({ taskId }));
660
- };
661
- if (error) {
662
- errorApi.post(new Error(`Failed to load template, ${error}`));
663
- return /* @__PURE__ */ React.createElement(Navigate, {
664
- to: rootRoute()
665
- });
666
- }
667
- if (!loading && !schema) {
668
- errorApi.post(new Error("Template was not found."));
669
- return /* @__PURE__ */ React.createElement(Navigate, {
670
- to: rootRoute()
671
- });
672
- }
673
- const customFieldComponents = Object.fromEntries(customFieldExtensions.map(({ name, component }) => [name, component]));
674
- const customFieldValidators = Object.fromEntries(customFieldExtensions.map(({ name, validation }) => [name, validation]));
675
- return /* @__PURE__ */ React.createElement(Page, {
676
- themeId: "home"
677
- }, /* @__PURE__ */ React.createElement(Header, {
678
- pageTitleOverride: "Create a New Component",
679
- title: "Create a New Component",
680
- subtitle: "Create new software components using standard templates"
681
- }), /* @__PURE__ */ React.createElement(Content, null, loading && /* @__PURE__ */ React.createElement(LinearProgress, {
682
- "data-testid": "loading-progress"
683
- }), schema && /* @__PURE__ */ React.createElement(InfoCard, {
684
- title: schema.title,
685
- noPadding: true,
686
- titleTypographyProps: { component: "h2" }
687
- }, /* @__PURE__ */ React.createElement(MultistepJsonForm, {
688
- formData: formState,
689
- fields: customFieldComponents,
690
- onChange: handleChange,
691
- onReset: handleFormReset,
692
- onFinish: handleCreate,
693
- steps: schema.steps.map((step) => {
694
- return {
695
- ...step,
696
- validate: createValidator(step.schema, customFieldValidators, { apiHolder })
697
- };
698
- })
699
- }))));
700
- };
701
-
702
- const useStyles$1 = makeStyles((theme) => ({
703
- code: {
704
- fontFamily: "Menlo, monospace",
705
- padding: theme.spacing(1),
706
- backgroundColor: theme.palette.type === "dark" ? theme.palette.grey[700] : theme.palette.grey[300],
707
- display: "inline-block",
708
- borderRadius: 5,
709
- border: `1px solid ${theme.palette.grey[500]}`,
710
- position: "relative"
711
- },
712
- codeRequired: {
713
- "&::after": {
714
- position: "absolute",
715
- content: '"*"',
716
- top: 0,
717
- right: theme.spacing(0.5),
718
- fontWeight: "bolder",
719
- color: theme.palette.error.light
720
- }
721
- }
722
- }));
723
- const ActionsPage = () => {
724
- const api = useApi(scaffolderApiRef);
725
- const classes = useStyles$1();
726
- const { loading, value, error } = useAsync(async () => {
727
- return api.listActions();
728
- });
729
- if (loading) {
730
- return /* @__PURE__ */ React.createElement(Progress, null);
731
- }
732
- if (error) {
733
- return /* @__PURE__ */ React.createElement(ErrorPage, {
734
- statusMessage: "Failed to load installed actions",
735
- status: "500"
736
- });
737
- }
738
- const formatRows = (input) => {
739
- const properties = input.properties;
740
- if (!properties) {
741
- return void 0;
742
- }
743
- return Object.entries(properties).map((entry) => {
744
- var _a;
745
- const [key] = entry;
746
- const props = entry[1];
747
- const codeClassname = classNames(classes.code, {
748
- [classes.codeRequired]: (_a = input.required) == null ? void 0 : _a.includes(key)
749
- });
750
- return /* @__PURE__ */ React.createElement(TableRow, {
751
- key
752
- }, /* @__PURE__ */ React.createElement(TableCell, null, /* @__PURE__ */ React.createElement("div", {
753
- className: codeClassname
754
- }, key)), /* @__PURE__ */ React.createElement(TableCell, null, props.title), /* @__PURE__ */ React.createElement(TableCell, null, props.description), /* @__PURE__ */ React.createElement(TableCell, null, /* @__PURE__ */ React.createElement("span", {
755
- className: classes.code
756
- }, props.type)));
757
- });
758
- };
759
- const renderTable = (input) => {
760
- if (!input.properties) {
761
- return void 0;
762
- }
763
- return /* @__PURE__ */ React.createElement(TableContainer, {
764
- component: Paper
765
- }, /* @__PURE__ */ React.createElement(Table, {
766
- size: "small"
767
- }, /* @__PURE__ */ React.createElement(TableHead, null, /* @__PURE__ */ React.createElement(TableRow, null, /* @__PURE__ */ React.createElement(TableCell, null, "Name"), /* @__PURE__ */ React.createElement(TableCell, null, "Title"), /* @__PURE__ */ React.createElement(TableCell, null, "Description"), /* @__PURE__ */ React.createElement(TableCell, null, "Type"))), /* @__PURE__ */ React.createElement(TableBody, null, formatRows(input))));
768
- };
769
- const renderTables = (name, input) => {
770
- if (!input) {
771
- return void 0;
772
- }
773
- return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Typography, {
774
- variant: "h6"
775
- }, name), input.map((i, index) => /* @__PURE__ */ React.createElement("div", {
776
- key: index
777
- }, renderTable(i))));
778
- };
779
- const items = value == null ? void 0 : value.map((action) => {
780
- var _a, _b, _c, _d;
781
- if (action.id.startsWith("legacy:")) {
782
- return void 0;
783
- }
784
- const oneOf = renderTables("oneOf", (_b = (_a = action.schema) == null ? void 0 : _a.input) == null ? void 0 : _b.oneOf);
785
- return /* @__PURE__ */ React.createElement(Box, {
786
- pb: 4,
787
- key: action.id
788
- }, /* @__PURE__ */ React.createElement(Typography, {
789
- variant: "h4",
790
- className: classes.code
791
- }, action.id), /* @__PURE__ */ React.createElement(Typography, null, action.description), ((_c = action.schema) == null ? void 0 : _c.input) && /* @__PURE__ */ React.createElement(Box, {
792
- pb: 2
793
- }, /* @__PURE__ */ React.createElement(Typography, {
794
- variant: "h5"
795
- }, "Input"), renderTable(action.schema.input), oneOf), ((_d = action.schema) == null ? void 0 : _d.output) && /* @__PURE__ */ React.createElement(Box, {
796
- pb: 2
797
- }, /* @__PURE__ */ React.createElement(Typography, {
798
- variant: "h5"
799
- }, "Output"), renderTable(action.schema.output)));
800
- });
801
- return /* @__PURE__ */ React.createElement(Page, {
802
- themeId: "home"
803
- }, /* @__PURE__ */ React.createElement(Header, {
804
- pageTitleOverride: "Create a New Component",
805
- title: "Installed actions",
806
- subtitle: "This is the collection of all installed actions"
807
- }), /* @__PURE__ */ React.createElement(Content, null, items));
808
- };
809
-
810
- const EXAMPLE_TEMPLATE_PARAMS_YAML = `# Edit the template parameters below to see how they will render in the scaffolder form UI
811
- parameters:
812
- - title: Fill in some steps
813
- required:
814
- - name
815
- properties:
816
- name:
817
- title: Name
818
- type: string
819
- description: Unique name of the component
820
- owner:
821
- title: Owner
822
- type: string
823
- description: Owner of the component
824
- ui:field: OwnerPicker
825
- ui:options:
826
- allowedKinds:
827
- - Group
828
- - title: Choose a location
829
- required:
830
- - repoUrl
831
- properties:
832
- repoUrl:
833
- title: Repository Location
834
- type: string
835
- ui:field: RepoUrlPicker
836
- ui:options:
837
- allowedHosts:
838
- - github.com
839
- `;
840
- const useStyles = makeStyles({
841
- templateSelect: {
842
- marginBottom: "10px"
843
- },
844
- grid: {
845
- height: "100%"
846
- },
847
- codeMirror: {
848
- height: "95%"
849
- }
850
- });
851
- const TemplateEditorPage = ({
852
- defaultPreviewTemplate = EXAMPLE_TEMPLATE_PARAMS_YAML,
853
- customFieldExtensions = []
854
- }) => {
855
- const classes = useStyles();
856
- const alertApi = useApi(alertApiRef);
857
- const catalogApi = useApi(catalogApiRef);
858
- const apiHolder = useApiHolder();
859
- const [selectedTemplate, setSelectedTemplate] = useState("");
860
- const [schema, setSchema] = useState({
861
- title: "",
862
- steps: []
863
- });
864
- const [templateOptions, setTemplateOptions] = useState([]);
865
- const [templateYaml, setTemplateYaml] = useState(defaultPreviewTemplate);
866
- const [formState, setFormState] = useState({});
867
- const { loading } = useAsync(() => catalogApi.getEntities({
868
- filter: { kind: "template" },
869
- fields: [
870
- "kind",
871
- "metadata.namespace",
872
- "metadata.name",
873
- "metadata.title",
874
- "spec.parameters"
875
- ]
876
- }).then(({ items }) => setTemplateOptions(items.map((template) => {
877
- var _a;
878
- return {
879
- label: (_a = template.metadata.title) != null ? _a : humanizeEntityRef(template, { defaultKind: "template" }),
880
- value: template
881
- };
882
- }))).catch((e) => alertApi.post({
883
- message: `Error loading exisiting templates: ${e.message}`,
884
- severity: "error"
885
- })), [catalogApi]);
886
- const errorPanel = document.createElement("div");
887
- errorPanel.style.color = "red";
888
- useDebounce(() => {
889
- try {
890
- const parsedTemplate = yaml.parse(templateYaml);
891
- setSchema({
892
- title: "Preview",
893
- steps: parsedTemplate.parameters.map((param) => ({
894
- title: param.title,
895
- schema: param
896
- }))
897
- });
898
- setFormState({});
899
- } catch (e) {
900
- errorPanel.textContent = e.message;
901
- }
902
- }, 250, [setFormState, setSchema, templateYaml]);
903
- const handleSelectChange = useCallback((selected) => {
904
- setSelectedTemplate(selected);
905
- setTemplateYaml(yaml.stringify(selected.spec));
906
- }, [setTemplateYaml]);
907
- const handleFormReset = () => setFormState({});
908
- const handleFormChange = useCallback((e) => setFormState(e.formData), [setFormState]);
909
- const handleCodeChange = useCallback((code) => {
910
- setTemplateYaml(code);
911
- }, [setTemplateYaml]);
912
- const customFieldComponents = Object.fromEntries(customFieldExtensions.map(({ name, component }) => [name, component]));
913
- const customFieldValidators = Object.fromEntries(customFieldExtensions.map(({ name, validation }) => [name, validation]));
914
- return /* @__PURE__ */ React.createElement(Page, {
915
- themeId: "home"
916
- }, /* @__PURE__ */ React.createElement(Header, {
917
- title: "Template Editor",
918
- subtitle: "Preview your template parameter UI"
919
- }), /* @__PURE__ */ React.createElement(Content, null, loading && /* @__PURE__ */ React.createElement(LinearProgress, null), /* @__PURE__ */ React.createElement(Grid, {
920
- container: true,
921
- className: classes.grid
922
- }, /* @__PURE__ */ React.createElement(Grid, {
923
- item: true,
924
- xs: 6
925
- }, /* @__PURE__ */ React.createElement(FormControl, {
926
- className: classes.templateSelect,
927
- variant: "outlined",
928
- fullWidth: true
929
- }, /* @__PURE__ */ React.createElement(InputLabel, {
930
- id: "select-template-label"
931
- }, "Load Existing Template"), /* @__PURE__ */ React.createElement(Select, {
932
- value: selectedTemplate,
933
- label: "Load Existing Template",
934
- labelId: "select-template-label",
935
- onChange: (e) => handleSelectChange(e.target.value)
936
- }, templateOptions.map((option, idx) => /* @__PURE__ */ React.createElement(MenuItem$1, {
937
- key: idx,
938
- value: option.value
939
- }, option.label)))), /* @__PURE__ */ React.createElement(CodeMirror, {
940
- className: classes.codeMirror,
941
- value: templateYaml,
942
- theme: "dark",
943
- height: "100%",
944
- extensions: [
945
- StreamLanguage.define(yaml$1),
946
- showPanel.of(() => ({ dom: errorPanel, top: true }))
947
- ],
948
- onChange: handleCodeChange
949
- })), /* @__PURE__ */ React.createElement(Grid, {
950
- item: true,
951
- xs: 6
952
- }, schema && /* @__PURE__ */ React.createElement(InfoCard, {
953
- key: JSON.stringify(schema)
954
- }, /* @__PURE__ */ React.createElement(MultistepJsonForm, {
955
- formData: formState,
956
- fields: customFieldComponents,
957
- onChange: handleFormChange,
958
- onReset: handleFormReset,
959
- steps: schema.steps.map((step) => {
960
- return {
961
- ...step,
962
- validate: createValidator(step.schema, customFieldValidators, { apiHolder })
963
- };
964
- })
965
- }))))));
966
- };
967
-
968
- const Router = (props) => {
969
- const { groups, components = {}, defaultPreviewTemplate } = props;
970
- const { TemplateCardComponent, TaskPageComponent } = components;
971
- const outlet = useOutlet();
972
- const TaskPageElement = TaskPageComponent != null ? TaskPageComponent : TaskPage;
973
- const customFieldExtensions = useElementFilter(outlet, (elements) => elements.selectByComponentData({
974
- key: FIELD_EXTENSION_WRAPPER_KEY
975
- }).findComponentData({
976
- key: FIELD_EXTENSION_KEY
977
- }));
978
- const fieldExtensions = [
979
- ...customFieldExtensions,
980
- ...DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS.filter(({ name }) => !customFieldExtensions.some((customFieldExtension) => customFieldExtension.name === name))
981
- ];
982
- return /* @__PURE__ */ React.createElement(Routes, null, /* @__PURE__ */ React.createElement(Route, {
983
- element: /* @__PURE__ */ React.createElement(ScaffolderPage, {
984
- groups,
985
- TemplateCardComponent,
986
- contextMenu: props.contextMenu
987
- })
988
- }), /* @__PURE__ */ React.createElement(Route, {
989
- path: selectedTemplateRouteRef.path,
990
- element: /* @__PURE__ */ React.createElement(SecretsContextProvider, null, /* @__PURE__ */ React.createElement(TemplatePage, {
991
- customFieldExtensions: fieldExtensions
992
- }))
993
- }), /* @__PURE__ */ React.createElement(Route, {
994
- path: scaffolderTaskRouteRef.path,
995
- element: /* @__PURE__ */ React.createElement(TaskPageElement, null)
996
- }), /* @__PURE__ */ React.createElement(Route, {
997
- path: actionsRouteRef.path,
998
- element: /* @__PURE__ */ React.createElement(ActionsPage, null)
999
- }), /* @__PURE__ */ React.createElement(Route, {
1000
- path: editRouteRef.path,
1001
- element: /* @__PURE__ */ React.createElement(SecretsContextProvider, null, /* @__PURE__ */ React.createElement(TemplateEditorPage, {
1002
- defaultPreviewTemplate,
1003
- customFieldExtensions: fieldExtensions
1004
- }))
1005
- }), /* @__PURE__ */ React.createElement(Route, {
1006
- path: "preview",
1007
- element: /* @__PURE__ */ React.createElement(Navigate, {
1008
- to: "../edit"
1009
- })
1010
- }));
1011
- };
1012
-
1013
- export { Router };
1014
- //# sourceMappingURL=Router-980d33c1.esm.js.map