@backstage/plugin-scaffolder 1.2.0 → 1.3.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.
@@ -1,1013 +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-6a9fc0c0.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-67a72a06.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
- onChange,
537
- onSubmit: (e) => {
538
- if (e.errors.length === 0)
539
- handleNext();
540
- },
541
- ...formProps,
542
- ...transformSchemaToProps(schema)
543
- }, /* @__PURE__ */ React.createElement(Button$1, {
544
- disabled: activeStep === 0,
545
- onClick: handleBack
546
- }, "Back"), /* @__PURE__ */ React.createElement(Button$1, {
547
- variant: "contained",
548
- color: "primary",
549
- type: "submit"
550
- }, "Next step"))));
551
- })), activeStep === steps.length && /* @__PURE__ */ React.createElement(Content, null, /* @__PURE__ */ React.createElement(Paper, {
552
- square: true,
553
- elevation: 0
554
- }, /* @__PURE__ */ React.createElement(Typography, {
555
- variant: "h6"
556
- }, "Review and create"), /* @__PURE__ */ React.createElement(StructuredMetadataTable, {
557
- dense: true,
558
- metadata: getReviewData(formData, steps)
559
- }), /* @__PURE__ */ React.createElement(Box, {
560
- mb: 4
561
- }), /* @__PURE__ */ React.createElement(Button$1, {
562
- onClick: handleBack,
563
- disabled: disableButtons
564
- }, "Back"), /* @__PURE__ */ React.createElement(Button$1, {
565
- onClick: handleReset,
566
- disabled: disableButtons
567
- }, "Reset"), /* @__PURE__ */ React.createElement(Button$1, {
568
- variant: "contained",
569
- color: "primary",
570
- onClick: handleCreate,
571
- disabled: !onFinish || disableButtons
572
- }, "Create"))));
573
- };
574
-
575
- function isObject(obj) {
576
- return typeof obj === "object" && obj !== null && !Array.isArray(obj);
577
- }
578
- const createValidator = (rootSchema, validators, context) => {
579
- function validate(schema, formData, errors) {
580
- const schemaProps = schema.properties;
581
- const customObject = schema.type === "object" && schemaProps === void 0;
582
- if (!isObject(schemaProps) && !customObject) {
583
- return;
584
- }
585
- if (schemaProps) {
586
- for (const [key, propData] of Object.entries(formData)) {
587
- const propValidation = errors[key];
588
- if (isObject(propData)) {
589
- const propSchemaProps = schemaProps[key];
590
- if (isObject(propSchemaProps)) {
591
- validate(propSchemaProps, propData, propValidation);
592
- }
593
- } else {
594
- const propSchema = schemaProps[key];
595
- const fieldName = isObject(propSchema) && propSchema["ui:field"];
596
- if (fieldName && typeof validators[fieldName] === "function") {
597
- validators[fieldName](propData, propValidation, context);
598
- }
599
- }
600
- }
601
- } else if (customObject) {
602
- const fieldName = schema["ui:field"];
603
- if (fieldName && typeof validators[fieldName] === "function") {
604
- validators[fieldName](formData, errors, context);
605
- }
606
- }
607
- }
608
- return (formData, errors) => {
609
- validate(rootSchema, formData, errors);
610
- return errors;
611
- };
612
- };
613
-
614
- const useTemplateParameterSchema = (templateRef) => {
615
- const scaffolderApi = useApi(scaffolderApiRef);
616
- const { value, loading, error } = useAsync(() => scaffolderApi.getTemplateParameterSchema(templateRef), [scaffolderApi, templateRef]);
617
- return { schema: value, loading, error };
618
- };
619
- const TemplatePage = ({
620
- customFieldExtensions = []
621
- }) => {
622
- const apiHolder = useApiHolder();
623
- const secretsContext = useContext(SecretsContext);
624
- const errorApi = useApi(errorApiRef);
625
- const scaffolderApi = useApi(scaffolderApiRef);
626
- const { templateName } = useParams();
627
- const navigate = useNavigate();
628
- const scaffolderTaskRoute = useRouteRef(scaffolderTaskRouteRef);
629
- const rootRoute = useRouteRef(rootRouteRef);
630
- const { schema, loading, error } = useTemplateParameterSchema(templateName);
631
- const [formState, setFormState] = useState(() => {
632
- var _a;
633
- const query = qs.parse(window.location.search, {
634
- ignoreQueryPrefix: true
635
- });
636
- try {
637
- return JSON.parse(query.formData);
638
- } catch (e) {
639
- return (_a = query.formData) != null ? _a : {};
640
- }
641
- });
642
- const handleFormReset = () => setFormState({});
643
- const handleChange = useCallback((e) => setFormState(e.formData), [setFormState]);
644
- const handleCreate = async () => {
645
- var _a;
646
- const { taskId } = await scaffolderApi.scaffold({
647
- templateRef: stringifyEntityRef({
648
- name: templateName,
649
- kind: "template",
650
- namespace: "default"
651
- }),
652
- values: formState,
653
- secrets: secretsContext == null ? void 0 : secretsContext.secrets
654
- });
655
- const formParams = qs.stringify({ formData: formState }, { addQueryPrefix: true });
656
- const newUrl = `${window.location.pathname}${formParams}`;
657
- (_a = window.history) == null ? void 0 : _a.replaceState(null, document.title, newUrl);
658
- navigate(scaffolderTaskRoute({ taskId }));
659
- };
660
- if (error) {
661
- errorApi.post(new Error(`Failed to load template, ${error}`));
662
- return /* @__PURE__ */ React.createElement(Navigate, {
663
- to: rootRoute()
664
- });
665
- }
666
- if (!loading && !schema) {
667
- errorApi.post(new Error("Template was not found."));
668
- return /* @__PURE__ */ React.createElement(Navigate, {
669
- to: rootRoute()
670
- });
671
- }
672
- const customFieldComponents = Object.fromEntries(customFieldExtensions.map(({ name, component }) => [name, component]));
673
- const customFieldValidators = Object.fromEntries(customFieldExtensions.map(({ name, validation }) => [name, validation]));
674
- return /* @__PURE__ */ React.createElement(Page, {
675
- themeId: "home"
676
- }, /* @__PURE__ */ React.createElement(Header, {
677
- pageTitleOverride: "Create a New Component",
678
- title: "Create a New Component",
679
- subtitle: "Create new software components using standard templates"
680
- }), /* @__PURE__ */ React.createElement(Content, null, loading && /* @__PURE__ */ React.createElement(LinearProgress, {
681
- "data-testid": "loading-progress"
682
- }), schema && /* @__PURE__ */ React.createElement(InfoCard, {
683
- title: schema.title,
684
- noPadding: true,
685
- titleTypographyProps: { component: "h2" }
686
- }, /* @__PURE__ */ React.createElement(MultistepJsonForm, {
687
- formData: formState,
688
- fields: customFieldComponents,
689
- onChange: handleChange,
690
- onReset: handleFormReset,
691
- onFinish: handleCreate,
692
- steps: schema.steps.map((step) => {
693
- return {
694
- ...step,
695
- validate: createValidator(step.schema, customFieldValidators, { apiHolder })
696
- };
697
- })
698
- }))));
699
- };
700
-
701
- const useStyles$1 = makeStyles((theme) => ({
702
- code: {
703
- fontFamily: "Menlo, monospace",
704
- padding: theme.spacing(1),
705
- backgroundColor: theme.palette.type === "dark" ? theme.palette.grey[700] : theme.palette.grey[300],
706
- display: "inline-block",
707
- borderRadius: 5,
708
- border: `1px solid ${theme.palette.grey[500]}`,
709
- position: "relative"
710
- },
711
- codeRequired: {
712
- "&::after": {
713
- position: "absolute",
714
- content: '"*"',
715
- top: 0,
716
- right: theme.spacing(0.5),
717
- fontWeight: "bolder",
718
- color: theme.palette.error.light
719
- }
720
- }
721
- }));
722
- const ActionsPage = () => {
723
- const api = useApi(scaffolderApiRef);
724
- const classes = useStyles$1();
725
- const { loading, value, error } = useAsync(async () => {
726
- return api.listActions();
727
- });
728
- if (loading) {
729
- return /* @__PURE__ */ React.createElement(Progress, null);
730
- }
731
- if (error) {
732
- return /* @__PURE__ */ React.createElement(ErrorPage, {
733
- statusMessage: "Failed to load installed actions",
734
- status: "500"
735
- });
736
- }
737
- const formatRows = (input) => {
738
- const properties = input.properties;
739
- if (!properties) {
740
- return void 0;
741
- }
742
- return Object.entries(properties).map((entry) => {
743
- var _a;
744
- const [key] = entry;
745
- const props = entry[1];
746
- const codeClassname = classNames(classes.code, {
747
- [classes.codeRequired]: (_a = input.required) == null ? void 0 : _a.includes(key)
748
- });
749
- return /* @__PURE__ */ React.createElement(TableRow, {
750
- key
751
- }, /* @__PURE__ */ React.createElement(TableCell, null, /* @__PURE__ */ React.createElement("div", {
752
- className: codeClassname
753
- }, key)), /* @__PURE__ */ React.createElement(TableCell, null, props.title), /* @__PURE__ */ React.createElement(TableCell, null, props.description), /* @__PURE__ */ React.createElement(TableCell, null, /* @__PURE__ */ React.createElement("span", {
754
- className: classes.code
755
- }, props.type)));
756
- });
757
- };
758
- const renderTable = (input) => {
759
- if (!input.properties) {
760
- return void 0;
761
- }
762
- return /* @__PURE__ */ React.createElement(TableContainer, {
763
- component: Paper
764
- }, /* @__PURE__ */ React.createElement(Table, {
765
- size: "small"
766
- }, /* @__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))));
767
- };
768
- const renderTables = (name, input) => {
769
- if (!input) {
770
- return void 0;
771
- }
772
- return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Typography, {
773
- variant: "h6"
774
- }, name), input.map((i, index) => /* @__PURE__ */ React.createElement("div", {
775
- key: index
776
- }, renderTable(i))));
777
- };
778
- const items = value == null ? void 0 : value.map((action) => {
779
- var _a, _b, _c, _d;
780
- if (action.id.startsWith("legacy:")) {
781
- return void 0;
782
- }
783
- const oneOf = renderTables("oneOf", (_b = (_a = action.schema) == null ? void 0 : _a.input) == null ? void 0 : _b.oneOf);
784
- return /* @__PURE__ */ React.createElement(Box, {
785
- pb: 4,
786
- key: action.id
787
- }, /* @__PURE__ */ React.createElement(Typography, {
788
- variant: "h4",
789
- className: classes.code
790
- }, action.id), /* @__PURE__ */ React.createElement(Typography, null, action.description), ((_c = action.schema) == null ? void 0 : _c.input) && /* @__PURE__ */ React.createElement(Box, {
791
- pb: 2
792
- }, /* @__PURE__ */ React.createElement(Typography, {
793
- variant: "h5"
794
- }, "Input"), renderTable(action.schema.input), oneOf), ((_d = action.schema) == null ? void 0 : _d.output) && /* @__PURE__ */ React.createElement(Box, {
795
- pb: 2
796
- }, /* @__PURE__ */ React.createElement(Typography, {
797
- variant: "h5"
798
- }, "Output"), renderTable(action.schema.output)));
799
- });
800
- return /* @__PURE__ */ React.createElement(Page, {
801
- themeId: "home"
802
- }, /* @__PURE__ */ React.createElement(Header, {
803
- pageTitleOverride: "Create a New Component",
804
- title: "Installed actions",
805
- subtitle: "This is the collection of all installed actions"
806
- }), /* @__PURE__ */ React.createElement(Content, null, items));
807
- };
808
-
809
- const EXAMPLE_TEMPLATE_PARAMS_YAML = `# Edit the template parameters below to see how they will render in the scaffolder form UI
810
- parameters:
811
- - title: Fill in some steps
812
- required:
813
- - name
814
- properties:
815
- name:
816
- title: Name
817
- type: string
818
- description: Unique name of the component
819
- owner:
820
- title: Owner
821
- type: string
822
- description: Owner of the component
823
- ui:field: OwnerPicker
824
- ui:options:
825
- allowedKinds:
826
- - Group
827
- - title: Choose a location
828
- required:
829
- - repoUrl
830
- properties:
831
- repoUrl:
832
- title: Repository Location
833
- type: string
834
- ui:field: RepoUrlPicker
835
- ui:options:
836
- allowedHosts:
837
- - github.com
838
- `;
839
- const useStyles = makeStyles({
840
- templateSelect: {
841
- marginBottom: "10px"
842
- },
843
- grid: {
844
- height: "100%"
845
- },
846
- codeMirror: {
847
- height: "95%"
848
- }
849
- });
850
- const TemplateEditorPage = ({
851
- defaultPreviewTemplate = EXAMPLE_TEMPLATE_PARAMS_YAML,
852
- customFieldExtensions = []
853
- }) => {
854
- const classes = useStyles();
855
- const alertApi = useApi(alertApiRef);
856
- const catalogApi = useApi(catalogApiRef);
857
- const apiHolder = useApiHolder();
858
- const [selectedTemplate, setSelectedTemplate] = useState("");
859
- const [schema, setSchema] = useState({
860
- title: "",
861
- steps: []
862
- });
863
- const [templateOptions, setTemplateOptions] = useState([]);
864
- const [templateYaml, setTemplateYaml] = useState(defaultPreviewTemplate);
865
- const [formState, setFormState] = useState({});
866
- const { loading } = useAsync(() => catalogApi.getEntities({
867
- filter: { kind: "template" },
868
- fields: [
869
- "kind",
870
- "metadata.namespace",
871
- "metadata.name",
872
- "metadata.title",
873
- "spec.parameters"
874
- ]
875
- }).then(({ items }) => setTemplateOptions(items.map((template) => {
876
- var _a;
877
- return {
878
- label: (_a = template.metadata.title) != null ? _a : humanizeEntityRef(template, { defaultKind: "template" }),
879
- value: template
880
- };
881
- }))).catch((e) => alertApi.post({
882
- message: `Error loading exisiting templates: ${e.message}`,
883
- severity: "error"
884
- })), [catalogApi]);
885
- const errorPanel = document.createElement("div");
886
- errorPanel.style.color = "red";
887
- useDebounce(() => {
888
- try {
889
- const parsedTemplate = yaml.parse(templateYaml);
890
- setSchema({
891
- title: "Preview",
892
- steps: parsedTemplate.parameters.map((param) => ({
893
- title: param.title,
894
- schema: param
895
- }))
896
- });
897
- setFormState({});
898
- } catch (e) {
899
- errorPanel.textContent = e.message;
900
- }
901
- }, 250, [setFormState, setSchema, templateYaml]);
902
- const handleSelectChange = useCallback((selected) => {
903
- setSelectedTemplate(selected);
904
- setTemplateYaml(yaml.stringify(selected.spec));
905
- }, [setTemplateYaml]);
906
- const handleFormReset = () => setFormState({});
907
- const handleFormChange = useCallback((e) => setFormState(e.formData), [setFormState]);
908
- const handleCodeChange = useCallback((code) => {
909
- setTemplateYaml(code);
910
- }, [setTemplateYaml]);
911
- const customFieldComponents = Object.fromEntries(customFieldExtensions.map(({ name, component }) => [name, component]));
912
- const customFieldValidators = Object.fromEntries(customFieldExtensions.map(({ name, validation }) => [name, validation]));
913
- return /* @__PURE__ */ React.createElement(Page, {
914
- themeId: "home"
915
- }, /* @__PURE__ */ React.createElement(Header, {
916
- title: "Template Editor",
917
- subtitle: "Preview your template parameter UI"
918
- }), /* @__PURE__ */ React.createElement(Content, null, loading && /* @__PURE__ */ React.createElement(LinearProgress, null), /* @__PURE__ */ React.createElement(Grid, {
919
- container: true,
920
- className: classes.grid
921
- }, /* @__PURE__ */ React.createElement(Grid, {
922
- item: true,
923
- xs: 6
924
- }, /* @__PURE__ */ React.createElement(FormControl, {
925
- className: classes.templateSelect,
926
- variant: "outlined",
927
- fullWidth: true
928
- }, /* @__PURE__ */ React.createElement(InputLabel, {
929
- id: "select-template-label"
930
- }, "Load Existing Template"), /* @__PURE__ */ React.createElement(Select, {
931
- value: selectedTemplate,
932
- label: "Load Existing Template",
933
- labelId: "select-template-label",
934
- onChange: (e) => handleSelectChange(e.target.value)
935
- }, templateOptions.map((option, idx) => /* @__PURE__ */ React.createElement(MenuItem$1, {
936
- key: idx,
937
- value: option.value
938
- }, option.label)))), /* @__PURE__ */ React.createElement(CodeMirror, {
939
- className: classes.codeMirror,
940
- value: templateYaml,
941
- theme: "dark",
942
- height: "100%",
943
- extensions: [
944
- StreamLanguage.define(yaml$1),
945
- showPanel.of(() => ({ dom: errorPanel, top: true }))
946
- ],
947
- onChange: handleCodeChange
948
- })), /* @__PURE__ */ React.createElement(Grid, {
949
- item: true,
950
- xs: 6
951
- }, schema && /* @__PURE__ */ React.createElement(InfoCard, {
952
- key: JSON.stringify(schema)
953
- }, /* @__PURE__ */ React.createElement(MultistepJsonForm, {
954
- formData: formState,
955
- fields: customFieldComponents,
956
- onChange: handleFormChange,
957
- onReset: handleFormReset,
958
- steps: schema.steps.map((step) => {
959
- return {
960
- ...step,
961
- validate: createValidator(step.schema, customFieldValidators, { apiHolder })
962
- };
963
- })
964
- }))))));
965
- };
966
-
967
- const Router = (props) => {
968
- const { groups, components = {}, defaultPreviewTemplate } = props;
969
- const { TemplateCardComponent, TaskPageComponent } = components;
970
- const outlet = useOutlet();
971
- const TaskPageElement = TaskPageComponent != null ? TaskPageComponent : TaskPage;
972
- const customFieldExtensions = useElementFilter(outlet, (elements) => elements.selectByComponentData({
973
- key: FIELD_EXTENSION_WRAPPER_KEY
974
- }).findComponentData({
975
- key: FIELD_EXTENSION_KEY
976
- }));
977
- const fieldExtensions = [
978
- ...customFieldExtensions,
979
- ...DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS.filter(({ name }) => !customFieldExtensions.some((customFieldExtension) => customFieldExtension.name === name))
980
- ];
981
- return /* @__PURE__ */ React.createElement(Routes, null, /* @__PURE__ */ React.createElement(Route, {
982
- element: /* @__PURE__ */ React.createElement(ScaffolderPage, {
983
- groups,
984
- TemplateCardComponent,
985
- contextMenu: props.contextMenu
986
- })
987
- }), /* @__PURE__ */ React.createElement(Route, {
988
- path: selectedTemplateRouteRef.path,
989
- element: /* @__PURE__ */ React.createElement(SecretsContextProvider, null, /* @__PURE__ */ React.createElement(TemplatePage, {
990
- customFieldExtensions: fieldExtensions
991
- }))
992
- }), /* @__PURE__ */ React.createElement(Route, {
993
- path: scaffolderTaskRouteRef.path,
994
- element: /* @__PURE__ */ React.createElement(TaskPageElement, null)
995
- }), /* @__PURE__ */ React.createElement(Route, {
996
- path: actionsRouteRef.path,
997
- element: /* @__PURE__ */ React.createElement(ActionsPage, null)
998
- }), /* @__PURE__ */ React.createElement(Route, {
999
- path: editRouteRef.path,
1000
- element: /* @__PURE__ */ React.createElement(SecretsContextProvider, null, /* @__PURE__ */ React.createElement(TemplateEditorPage, {
1001
- defaultPreviewTemplate,
1002
- customFieldExtensions: fieldExtensions
1003
- }))
1004
- }), /* @__PURE__ */ React.createElement(Route, {
1005
- path: "preview",
1006
- element: /* @__PURE__ */ React.createElement(Navigate, {
1007
- to: "../edit"
1008
- })
1009
- }));
1010
- };
1011
-
1012
- export { Router };
1013
- //# sourceMappingURL=Router-b7921312.esm.js.map