@backstage/plugin-scaffolder-react 1.1.0 → 1.2.0-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.esm.js CHANGED
@@ -1,17 +1,7 @@
1
- import { attachComponentData, createApiRef, useElementFilter, useApi, featureFlagsApiRef, useAnalytics, useApiHolder, useApp, errorApiRef, useRouteRef } from '@backstage/core-plugin-api';
1
+ import { attachComponentData, createApiRef, useElementFilter, useApi } from '@backstage/core-plugin-api';
2
2
  import { createVersionedContext, createVersionedValueMap, getOrCreateGlobalSingleton } from '@backstage/version-bridge';
3
- import React, { useState, useContext, useCallback, useMemo, useEffect } from 'react';
4
- import { makeStyles, Stepper as Stepper$1, Step, StepLabel, Button, useTheme, Card, CardContent, Grid, Box, Divider, Chip, CardActions, Typography, Paper } from '@material-ui/core';
5
- import { Draft07 } from 'json-schema-library';
6
- import { StructuredMetadataTable, MarkdownContent, ItemCardHeader, Link, UserIcon, Content, ItemCardGrid, ContentHeader, Progress, InfoCard } from '@backstage/core-components';
7
- import validator from '@rjsf/validator-ajv8';
8
- import qs from 'qs';
9
- import useAsync from 'react-use/lib/useAsync';
10
- import { withTheme } from '@rjsf/core-v5';
11
- import { RELATION_OWNED_BY, stringifyEntityRef, parseEntityRef } from '@backstage/catalog-model';
12
- import { FavoriteEntity, getEntityRelations, EntityRefLinks, entityRouteRef } from '@backstage/plugin-catalog-react';
13
- import LanguageIcon from '@material-ui/icons/Language';
14
- import WebIcon from '@material-ui/icons/Web';
3
+ import React, { useState, useContext, useCallback, useEffect } from 'react';
4
+ import { useImmerReducer } from 'use-immer';
15
5
 
16
6
  const FIELD_EXTENSION_WRAPPER_KEY = "scaffolder.extensions.wrapper.v1";
17
7
  const FIELD_EXTENSION_KEY = "scaffolder.extensions.field.v1";
@@ -97,661 +87,143 @@ const useCustomLayouts = (outlet) => {
97
87
  );
98
88
  };
99
89
 
100
- function createScaffolderLayout(options) {
101
- return {
102
- expose() {
103
- const LayoutDataHolder = () => null;
104
- attachComponentData(LayoutDataHolder, LAYOUTS_KEY, options);
105
- return LayoutDataHolder;
106
- }
107
- };
108
- }
109
- const ScaffolderLayouts = () => null;
110
- attachComponentData(ScaffolderLayouts, LAYOUTS_WRAPPER_KEY, true);
111
-
112
- function isObject$1(value) {
113
- return typeof value === "object" && value !== null && !Array.isArray(value);
114
- }
115
- function extractUiSchema(schema, uiSchema) {
116
- if (!isObject$1(schema)) {
117
- return;
118
- }
119
- const { properties, items, anyOf, oneOf, allOf, dependencies } = schema;
120
- for (const propName in schema) {
121
- if (!schema.hasOwnProperty(propName)) {
122
- continue;
123
- }
124
- if (propName.startsWith("ui:")) {
125
- uiSchema[propName] = schema[propName];
126
- delete schema[propName];
127
- }
128
- }
129
- if (isObject$1(properties)) {
130
- for (const propName in properties) {
131
- if (!properties.hasOwnProperty(propName)) {
132
- continue;
133
- }
134
- const schemaNode = properties[propName];
135
- if (!isObject$1(schemaNode)) {
136
- continue;
137
- }
138
- const innerUiSchema = {};
139
- uiSchema[propName] = innerUiSchema;
140
- extractUiSchema(schemaNode, innerUiSchema);
141
- }
142
- }
143
- if (isObject$1(items)) {
144
- const innerUiSchema = {};
145
- uiSchema.items = innerUiSchema;
146
- extractUiSchema(items, innerUiSchema);
147
- }
148
- if (Array.isArray(anyOf)) {
149
- for (const schemaNode of anyOf) {
150
- if (!isObject$1(schemaNode)) {
151
- continue;
152
- }
153
- extractUiSchema(schemaNode, uiSchema);
154
- }
155
- }
156
- if (Array.isArray(oneOf)) {
157
- for (const schemaNode of oneOf) {
158
- if (!isObject$1(schemaNode)) {
159
- continue;
160
- }
161
- extractUiSchema(schemaNode, uiSchema);
162
- }
163
- }
164
- if (Array.isArray(allOf)) {
165
- for (const schemaNode of allOf) {
166
- if (!isObject$1(schemaNode)) {
167
- continue;
168
- }
169
- extractUiSchema(schemaNode, uiSchema);
170
- }
171
- }
172
- if (isObject$1(dependencies)) {
173
- for (const depName of Object.keys(dependencies)) {
174
- const schemaNode = dependencies[depName];
175
- if (!isObject$1(schemaNode)) {
176
- continue;
90
+ function reducer(draft, action) {
91
+ var _a, _b, _c;
92
+ switch (action.type) {
93
+ case "INIT": {
94
+ draft.steps = action.data.spec.steps.reduce((current, next) => {
95
+ current[next.id] = { status: "open", id: next.id };
96
+ return current;
97
+ }, {});
98
+ draft.stepLogs = action.data.spec.steps.reduce((current, next) => {
99
+ current[next.id] = [];
100
+ return current;
101
+ }, {});
102
+ draft.loading = false;
103
+ draft.error = void 0;
104
+ draft.completed = false;
105
+ draft.task = action.data;
106
+ return;
107
+ }
108
+ case "LOGS": {
109
+ const entries = action.data;
110
+ for (const entry of entries) {
111
+ const logLine = `${entry.createdAt} ${entry.body.message}`;
112
+ if (!entry.body.stepId || !((_a = draft.steps) == null ? void 0 : _a[entry.body.stepId])) {
113
+ continue;
114
+ }
115
+ const currentStepLog = (_b = draft.stepLogs) == null ? void 0 : _b[entry.body.stepId];
116
+ const currentStep = (_c = draft.steps) == null ? void 0 : _c[entry.body.stepId];
117
+ if (entry.body.status && entry.body.status !== currentStep.status) {
118
+ currentStep.status = entry.body.status;
119
+ if (currentStep.status === "processing") {
120
+ currentStep.startedAt = entry.createdAt;
121
+ }
122
+ if (["cancelled", "failed", "completed"].includes(currentStep.status)) {
123
+ currentStep.endedAt = entry.createdAt;
124
+ }
125
+ }
126
+ currentStepLog == null ? void 0 : currentStepLog.push(logLine);
177
127
  }
178
- extractUiSchema(schemaNode, uiSchema);
128
+ return;
179
129
  }
180
- }
181
- }
182
- const extractSchemaFromStep = (inputStep) => {
183
- const uiSchema = {};
184
- const returnSchema = JSON.parse(JSON.stringify(inputStep));
185
- extractUiSchema(returnSchema, uiSchema);
186
- return { uiSchema, schema: returnSchema };
187
- };
188
- const createFieldValidation = () => {
189
- const fieldValidation = {
190
- __errors: [],
191
- addError: (message) => {
192
- var _a;
193
- (_a = fieldValidation.__errors) == null ? void 0 : _a.push(message);
130
+ case "COMPLETED": {
131
+ draft.completed = true;
132
+ draft.output = action.data.body.output;
133
+ draft.error = action.data.body.error;
134
+ return;
194
135
  }
195
- };
196
- return fieldValidation;
197
- };
198
-
199
- function isFieldValidation(error) {
200
- return !!error && "__errors" in error;
201
- }
202
- function hasErrors(errors) {
203
- var _a;
204
- if (!errors) {
205
- return false;
206
- }
207
- for (const error of Object.values(errors)) {
208
- if (isFieldValidation(error)) {
209
- if (((_a = error.__errors) != null ? _a : []).length > 0) {
210
- return true;
211
- }
212
- continue;
136
+ case "ERROR": {
137
+ draft.error = action.data;
138
+ draft.loading = false;
139
+ draft.completed = true;
140
+ return;
213
141
  }
214
- return hasErrors(error);
142
+ default:
143
+ return;
215
144
  }
216
- return false;
217
145
  }
218
- function isObject(value) {
219
- return typeof value === "object" && value !== null && !Array.isArray(value);
220
- }
221
-
222
- const createAsyncValidators = (rootSchema, validators, context) => {
223
- async function validate(formData, pathPrefix = "#", current = formData) {
224
- const parsedSchema = new Draft07(rootSchema);
225
- const formValidation = {};
226
- const validateForm = async (validatorName, key, value) => {
227
- const validator = validators[validatorName];
228
- if (validator) {
229
- const fieldValidation = createFieldValidation();
230
- try {
231
- await validator(value, fieldValidation, { ...context, formData });
232
- } catch (ex) {
233
- fieldValidation.addError(ex.message);
234
- }
235
- formValidation[key] = fieldValidation;
236
- }
237
- };
238
- for (const [key, value] of Object.entries(current)) {
239
- const path = `${pathPrefix}/${key}`;
240
- const definitionInSchema = parsedSchema.getSchema(path, formData);
241
- if (definitionInSchema && "ui:field" in definitionInSchema) {
242
- if ("ui:field" in definitionInSchema) {
243
- await validateForm(definitionInSchema["ui:field"], key, value);
146
+ const useTaskEventStream = (taskId) => {
147
+ const scaffolderApi = useApi(scaffolderApiRef);
148
+ const [state, dispatch] = useImmerReducer(reducer, {
149
+ loading: true,
150
+ completed: false,
151
+ stepLogs: {},
152
+ steps: {}
153
+ });
154
+ useEffect(() => {
155
+ let didCancel = false;
156
+ let subscription;
157
+ let logPusher;
158
+ scaffolderApi.getTask(taskId).then(
159
+ (task) => {
160
+ if (didCancel) {
161
+ return;
244
162
  }
245
- } else if (definitionInSchema && definitionInSchema.items && "ui:field" in definitionInSchema.items) {
246
- if ("ui:field" in definitionInSchema.items) {
247
- await validateForm(definitionInSchema.items["ui:field"], key, value);
163
+ dispatch({ type: "INIT", data: task });
164
+ const observable = scaffolderApi.streamLogs({ taskId });
165
+ const collectedLogEvents = new Array();
166
+ function emitLogs() {
167
+ if (collectedLogEvents.length) {
168
+ const logs = collectedLogEvents.splice(
169
+ 0,
170
+ collectedLogEvents.length
171
+ );
172
+ dispatch({ type: "LOGS", data: logs });
173
+ }
248
174
  }
249
- } else if (isObject(value)) {
250
- formValidation[key] = await validate(formData, path, value);
251
- }
252
- }
253
- return formValidation;
254
- }
255
- return async (formData) => {
256
- return await validate(formData);
257
- };
258
- };
259
-
260
- const ReviewState = (props) => {
261
- const reviewData = Object.fromEntries(
262
- Object.entries(props.formState).map(([key, value]) => {
263
- var _a;
264
- for (const step of props.schemas) {
265
- const parsedSchema = new Draft07(step.mergedSchema);
266
- const definitionInSchema = parsedSchema.getSchema(
267
- `#/${key}`,
268
- props.formState
269
- );
270
- if (definitionInSchema) {
271
- const backstageReviewOptions = (_a = definitionInSchema["ui:backstage"]) == null ? void 0 : _a.review;
272
- if (backstageReviewOptions) {
273
- if (backstageReviewOptions.mask) {
274
- return [key, backstageReviewOptions.mask];
175
+ logPusher = setInterval(emitLogs, 500);
176
+ subscription = observable.subscribe({
177
+ next: (event) => {
178
+ switch (event.type) {
179
+ case "log":
180
+ return collectedLogEvents.push(event);
181
+ case "completion":
182
+ emitLogs();
183
+ dispatch({ type: "COMPLETED", data: event });
184
+ return void 0;
185
+ default:
186
+ throw new Error(
187
+ `Unhandled event type ${event.type} in observer`
188
+ );
275
189
  }
276
- if (backstageReviewOptions.show === false) {
277
- return [];
278
- }
279
- }
280
- if (definitionInSchema["ui:widget"] === "password") {
281
- return [key, "******"];
190
+ },
191
+ error: (error) => {
192
+ emitLogs();
193
+ dispatch({ type: "ERROR", data: error });
282
194
  }
195
+ });
196
+ },
197
+ (error) => {
198
+ if (!didCancel) {
199
+ dispatch({ type: "ERROR", data: error });
283
200
  }
284
201
  }
285
- return [key, value];
286
- })
287
- );
288
- return /* @__PURE__ */ React.createElement(StructuredMetadataTable, { metadata: reviewData });
289
- };
290
-
291
- const useTemplateSchema = (manifest) => {
292
- const featureFlags = useApi(featureFlagsApiRef);
293
- const steps = manifest.steps.map(({ title, description, schema }) => ({
294
- title,
295
- description,
296
- mergedSchema: schema,
297
- ...extractSchemaFromStep(schema)
298
- }));
299
- const returningSteps = steps.filter((step) => {
300
- var _a;
301
- const stepFeatureFlag = (_a = step.uiSchema["ui:backstage"]) == null ? void 0 : _a.featureFlag;
302
- return stepFeatureFlag ? featureFlags.isActive(stepFeatureFlag) : true;
303
- }).map((step) => ({
304
- ...step,
305
- schema: {
306
- ...step.schema,
307
- // Title is rendered at the top of the page, so let's ignore this from jsonschemaform
308
- title: void 0,
309
- properties: Object.fromEntries(
310
- Object.entries(step.schema.properties).filter(
311
- ([key]) => {
312
- var _a, _b;
313
- const stepFeatureFlag = (_b = (_a = step.uiSchema[key]) == null ? void 0 : _a["ui:backstage"]) == null ? void 0 : _b.featureFlag;
314
- return stepFeatureFlag ? featureFlags.isActive(stepFeatureFlag) : true;
315
- }
316
- )
317
- )
318
- }
319
- }));
320
- return {
321
- steps: returningSteps
322
- };
323
- };
324
-
325
- const useFormDataFromQuery = (initialState) => {
326
- return useState(() => {
327
- if (initialState) {
328
- return initialState;
329
- }
330
- const query = qs.parse(window.location.search, {
331
- ignoreQueryPrefix: true
332
- });
333
- try {
334
- return JSON.parse(query.formData);
335
- } catch (e) {
336
- return {};
337
- }
338
- });
339
- };
340
-
341
- const useTemplateParameterSchema = (templateRef) => {
342
- const scaffolderApi = useApi(scaffolderApiRef);
343
- const { value, loading, error } = useAsync(
344
- () => scaffolderApi.getTemplateParameterSchema(templateRef),
345
- [scaffolderApi, templateRef]
346
- );
347
- return { manifest: value, loading, error };
348
- };
349
-
350
- const useTransformSchemaToProps = (step, options = {}) => {
351
- var _a;
352
- const { layouts = [] } = options;
353
- const objectFieldTemplate = step == null ? void 0 : step.uiSchema["ui:ObjectFieldTemplate"];
354
- if (typeof objectFieldTemplate !== "string") {
355
- return step;
356
- }
357
- const Layout = (_a = layouts.find(
358
- (layout) => layout.name === objectFieldTemplate
359
- )) == null ? void 0 : _a.component;
360
- if (!Layout) {
361
- return step;
362
- }
363
- return {
364
- ...step,
365
- uiSchema: {
366
- ...step.uiSchema,
367
- ["ui:ObjectFieldTemplate"]: Layout
368
- }
369
- };
370
- };
371
-
372
- const DescriptionField = ({ description }) => description && /* @__PURE__ */ React.createElement(MarkdownContent, { content: description, linkTarget: "_blank" });
373
-
374
- var FieldOverrides = /*#__PURE__*/Object.freeze({
375
- __proto__: null,
376
- DescriptionField: DescriptionField
377
- });
378
-
379
- const Form = withTheme(require("@rjsf/material-ui-v5").Theme);
380
-
381
- const useStyles$5 = makeStyles((theme) => ({
382
- backButton: {
383
- marginRight: theme.spacing(1)
384
- },
385
- footer: {
386
- display: "flex",
387
- flexDirection: "row",
388
- justifyContent: "right"
389
- },
390
- formWrapper: {
391
- padding: theme.spacing(2)
392
- }
393
- }));
394
- const Stepper = (stepperProps) => {
395
- var _a;
396
- const { layouts = [], components = {}, ...props } = stepperProps;
397
- const {
398
- ReviewStateComponent = ReviewState,
399
- createButtonText = "Create",
400
- reviewButtonText = "Review"
401
- } = components;
402
- const analytics = useAnalytics();
403
- const { steps } = useTemplateSchema(props.manifest);
404
- const apiHolder = useApiHolder();
405
- const [activeStep, setActiveStep] = useState(0);
406
- const [formState, setFormState] = useFormDataFromQuery(props.initialState);
407
- const [errors, setErrors] = useState();
408
- const styles = useStyles$5();
409
- const extensions = useMemo(() => {
410
- return Object.fromEntries(
411
- props.extensions.map(({ name, component }) => [name, component])
412
- );
413
- }, [props.extensions]);
414
- const validators = useMemo(() => {
415
- return Object.fromEntries(
416
- props.extensions.map(({ name, validation: validation2 }) => [name, validation2])
417
202
  );
418
- }, [props.extensions]);
419
- const validation = useMemo(() => {
420
- var _a2;
421
- return createAsyncValidators((_a2 = steps[activeStep]) == null ? void 0 : _a2.mergedSchema, validators, {
422
- apiHolder
423
- });
424
- }, [steps, activeStep, validators, apiHolder]);
425
- const handleBack = () => {
426
- setActiveStep((prevActiveStep) => prevActiveStep - 1);
427
- };
428
- const handleChange = useCallback(
429
- (e) => setFormState((current) => ({ ...current, ...e.formData })),
430
- [setFormState]
431
- );
432
- const currentStep = useTransformSchemaToProps(steps[activeStep], { layouts });
433
- const handleNext = async ({
434
- formData = {}
435
- }) => {
436
- setErrors(void 0);
437
- const returnedValidation = await validation(formData);
438
- if (hasErrors(returnedValidation)) {
439
- setErrors(returnedValidation);
440
- } else {
441
- setErrors(void 0);
442
- setActiveStep((prevActiveStep) => {
443
- const stepNum = prevActiveStep + 1;
444
- analytics.captureEvent("click", `Next Step (${stepNum})`);
445
- return stepNum;
446
- });
447
- }
448
- setFormState((current) => ({ ...current, ...formData }));
449
- };
450
- return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Stepper$1, { activeStep, alternativeLabel: true, variant: "elevation" }, steps.map((step, index) => /* @__PURE__ */ React.createElement(Step, { key: index }, /* @__PURE__ */ React.createElement(StepLabel, null, step.title))), /* @__PURE__ */ React.createElement(Step, null, /* @__PURE__ */ React.createElement(StepLabel, null, "Review"))), /* @__PURE__ */ React.createElement("div", { className: styles.formWrapper }, activeStep < steps.length ? /* @__PURE__ */ React.createElement(
451
- Form,
452
- {
453
- validator,
454
- extraErrors: errors,
455
- formData: formState,
456
- formContext: { formData: formState },
457
- schema: currentStep.schema,
458
- uiSchema: currentStep.uiSchema,
459
- onSubmit: handleNext,
460
- fields: { ...FieldOverrides, ...extensions },
461
- showErrorList: false,
462
- onChange: handleChange,
463
- ...(_a = props.FormProps) != null ? _a : {}
464
- },
465
- /* @__PURE__ */ React.createElement("div", { className: styles.footer }, /* @__PURE__ */ React.createElement(
466
- Button,
467
- {
468
- onClick: handleBack,
469
- className: styles.backButton,
470
- disabled: activeStep < 1
471
- },
472
- "Back"
473
- ), /* @__PURE__ */ React.createElement(Button, { variant: "contained", color: "primary", type: "submit" }, activeStep === steps.length - 1 ? reviewButtonText : "Next"))
474
- ) : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(ReviewStateComponent, { formState, schemas: steps }), /* @__PURE__ */ React.createElement("div", { className: styles.footer }, /* @__PURE__ */ React.createElement(
475
- Button,
476
- {
477
- onClick: handleBack,
478
- className: styles.backButton,
479
- disabled: activeStep < 1
480
- },
481
- "Back"
482
- ), /* @__PURE__ */ React.createElement(
483
- Button,
484
- {
485
- variant: "contained",
486
- onClick: () => {
487
- var _a2;
488
- props.onCreate(formState);
489
- const name = typeof formState.name === "string" ? formState.name : void 0;
490
- analytics.captureEvent(
491
- "create",
492
- (_a2 = name != null ? name : props.templateName) != null ? _a2 : "unknown"
493
- );
203
+ return () => {
204
+ didCancel = true;
205
+ if (subscription) {
206
+ subscription.unsubscribe();
494
207
  }
495
- },
496
- createButtonText
497
- )))));
498
- };
499
-
500
- const useStyles$4 = makeStyles(
501
- () => ({
502
- header: {
503
- backgroundImage: ({ cardBackgroundImage }) => cardBackgroundImage
504
- },
505
- subtitleWrapper: {
506
- display: "flex",
507
- justifyContent: "space-between"
508
- }
509
- })
510
- );
511
- const CardHeader = (props) => {
512
- const {
513
- template: {
514
- metadata: { title, name },
515
- spec: { type }
516
- }
517
- } = props;
518
- const { getPageTheme } = useTheme();
519
- const themeForType = getPageTheme({ themeId: type });
520
- const styles = useStyles$4({
521
- cardBackgroundImage: themeForType.backgroundImage
522
- });
523
- const SubtitleComponent = /* @__PURE__ */ React.createElement("div", { className: styles.subtitleWrapper }, /* @__PURE__ */ React.createElement("div", null, type), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(FavoriteEntity, { entity: props.template, style: { padding: 0 } })));
524
- return /* @__PURE__ */ React.createElement(
525
- ItemCardHeader,
526
- {
527
- title: title != null ? title : name,
528
- subtitle: SubtitleComponent,
529
- classes: { root: styles.header }
530
- }
531
- );
532
- };
533
-
534
- const useStyles$3 = makeStyles(() => ({
535
- linkText: {
536
- display: "inline-flex",
537
- alignItems: "center"
538
- }
539
- }));
540
- const CardLink = ({ icon: Icon, text, url }) => {
541
- const styles = useStyles$3();
542
- return /* @__PURE__ */ React.createElement("div", { className: styles.linkText }, /* @__PURE__ */ React.createElement(Icon, { fontSize: "small" }), /* @__PURE__ */ React.createElement(Link, { style: { marginLeft: "8px" }, to: url }, text || url));
543
- };
544
-
545
- const useStyles$2 = makeStyles((theme) => ({
546
- box: {
547
- overflow: "hidden",
548
- textOverflow: "ellipsis",
549
- display: "-webkit-box",
550
- "-webkit-line-clamp": 10,
551
- "-webkit-box-orient": "vertical"
552
- },
553
- markdown: {
554
- /** to make the styles for React Markdown not leak into the description */
555
- "& :first-child": {
556
- margin: 0
557
- }
558
- },
559
- label: {
560
- color: theme.palette.text.secondary,
561
- textTransform: "uppercase",
562
- fontWeight: "bold",
563
- letterSpacing: 0.5,
564
- lineHeight: 1,
565
- fontSize: "0.75rem"
566
- },
567
- footer: {
568
- display: "flex",
569
- justifyContent: "space-between",
570
- flex: 1,
571
- alignItems: "center"
572
- },
573
- ownedBy: {
574
- display: "flex",
575
- alignItems: "center",
576
- flex: 1,
577
- color: theme.palette.link
578
- }
579
- }));
580
- const TemplateCard = (props) => {
581
- var _a, _b, _c, _d, _e, _f, _g;
582
- const { template } = props;
583
- const styles = useStyles$2();
584
- const ownedByRelations = getEntityRelations(template, RELATION_OWNED_BY);
585
- const app = useApp();
586
- const iconResolver = (key) => {
587
- var _a2;
588
- return key ? (_a2 = app.getSystemIcon(key)) != null ? _a2 : LanguageIcon : LanguageIcon;
589
- };
590
- return /* @__PURE__ */ React.createElement(Card, null, /* @__PURE__ */ React.createElement(CardHeader, { template }), /* @__PURE__ */ React.createElement(CardContent, null, /* @__PURE__ */ React.createElement(Grid, { container: true, spacing: 2 }, /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React.createElement(Box, { className: styles.box }, /* @__PURE__ */ React.createElement(
591
- MarkdownContent,
592
- {
593
- className: styles.markdown,
594
- content: (_a = template.metadata.description) != null ? _a : "No description"
595
- }
596
- ))), ((_c = (_b = template.metadata.tags) == null ? void 0 : _b.length) != null ? _c : 0) > 0 && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React.createElement(Divider, null)), /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React.createElement(Grid, { container: true, spacing: 2 }, (_d = template.metadata.tags) == null ? void 0 : _d.map((tag) => /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(
597
- Chip,
598
- {
599
- style: { margin: 0 },
600
- size: "small",
601
- label: tag,
602
- key: tag
603
- }
604
- )))))), (props.additionalLinks || ((_e = template.metadata.links) == null ? void 0 : _e.length)) && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React.createElement(Divider, null)), /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React.createElement(Grid, { container: true, spacing: 2 }, (_f = props.additionalLinks) == null ? void 0 : _f.map(({ icon, text, url }) => /* @__PURE__ */ React.createElement(Grid, { className: styles.linkText, item: true, xs: 6 }, /* @__PURE__ */ React.createElement(CardLink, { icon, text, url }))), (_g = template.metadata.links) == null ? void 0 : _g.map(({ url, icon, title }) => /* @__PURE__ */ React.createElement(Grid, { className: styles.linkText, item: true, xs: 6 }, /* @__PURE__ */ React.createElement(
605
- CardLink,
606
- {
607
- icon: iconResolver(icon),
608
- text: title || url,
609
- url
610
- }
611
- )))))))), /* @__PURE__ */ React.createElement(CardActions, { style: { padding: "16px" } }, /* @__PURE__ */ React.createElement("div", { className: styles.footer }, /* @__PURE__ */ React.createElement("div", { className: styles.ownedBy }, ownedByRelations.length > 0 && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(UserIcon, { fontSize: "small" }), /* @__PURE__ */ React.createElement(
612
- EntityRefLinks,
613
- {
614
- style: { marginLeft: "8px" },
615
- entityRefs: ownedByRelations,
616
- defaultKind: "Group"
617
- }
618
- ))), /* @__PURE__ */ React.createElement(
619
- Button,
620
- {
621
- size: "small",
622
- variant: "outlined",
623
- color: "primary",
624
- onClick: () => {
625
- var _a2;
626
- return (_a2 = props.onSelected) == null ? void 0 : _a2.call(props, template);
208
+ if (logPusher) {
209
+ clearInterval(logPusher);
627
210
  }
628
- },
629
- "Choose"
630
- ))));
631
- };
632
-
633
- const TemplateGroup = (props) => {
634
- const {
635
- templates,
636
- title,
637
- components: { CardComponent } = {},
638
- onSelected
639
- } = props;
640
- const titleComponent = typeof title === "string" ? /* @__PURE__ */ React.createElement(ContentHeader, { title }) : title;
641
- if (templates.length === 0) {
642
- return /* @__PURE__ */ React.createElement(Content, null, titleComponent, /* @__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"), "."));
643
- }
644
- const Card = CardComponent || TemplateCard;
645
- return /* @__PURE__ */ React.createElement(Content, null, titleComponent, /* @__PURE__ */ React.createElement(ItemCardGrid, null, templates.map(({ template, additionalLinks }) => /* @__PURE__ */ React.createElement(
646
- Card,
647
- {
648
- key: stringifyEntityRef(template),
649
- additionalLinks,
650
- template,
651
- onSelected
652
- }
653
- ))));
654
- };
655
-
656
- const useStyles$1 = makeStyles(() => ({
657
- markdown: {
658
- /** to make the styles for React Markdown not leak into the description */
659
- "& :first-child": {
660
- marginTop: 0
661
- },
662
- "& :last-child": {
663
- marginBottom: 0
664
- }
665
- }
666
- }));
667
- const Workflow = (workflowProps) => {
668
- var _a;
669
- const { title, description, namespace, templateName, ...props } = workflowProps;
670
- const styles = useStyles$1();
671
- const templateRef = stringifyEntityRef({
672
- kind: "Template",
673
- namespace,
674
- name: templateName
675
- });
676
- const errorApi = useApi(errorApiRef);
677
- const { loading, manifest, error } = useTemplateParameterSchema(templateRef);
678
- useEffect(() => {
679
- if (error) {
680
- errorApi.post(new Error(`Failed to load template, ${error}`));
681
- }
682
- }, [error, errorApi]);
683
- if (error) {
684
- return props.onError(error);
685
- }
686
- return /* @__PURE__ */ React.createElement(Content, null, loading && /* @__PURE__ */ React.createElement(Progress, null), manifest && /* @__PURE__ */ React.createElement(
687
- InfoCard,
688
- {
689
- title: title != null ? title : manifest.title,
690
- subheader: /* @__PURE__ */ React.createElement(
691
- MarkdownContent,
692
- {
693
- className: styles.markdown,
694
- content: (_a = description != null ? description : manifest.description) != null ? _a : "No description"
695
- }
696
- ),
697
- noPadding: true,
698
- titleTypographyProps: { component: "h2" }
699
- },
700
- /* @__PURE__ */ React.createElement(Stepper, { manifest, ...props })
701
- ));
702
- };
703
- const EmbeddableWorkflow = (props) => /* @__PURE__ */ React.createElement(SecretsContextProvider, null, /* @__PURE__ */ React.createElement(Workflow, { ...props }));
704
-
705
- const useStyles = makeStyles({
706
- root: {
707
- "&:hover": {
708
- textDecoration: "none"
709
- }
710
- }
711
- });
712
- const LinkOutputs = (props) => {
713
- const { links = [] } = props.output;
714
- const classes = useStyles();
715
- const app = useApp();
716
- const entityRoute = useRouteRef(entityRouteRef);
717
- const iconResolver = (key) => {
718
- var _a;
719
- return (_a = app.getSystemIcon(key)) != null ? _a : WebIcon;
720
- };
721
- return /* @__PURE__ */ React.createElement(React.Fragment, null, links.filter(({ url, entityRef }) => url || entityRef).map(({ url, entityRef, title, icon }) => {
722
- if (entityRef) {
723
- const entityName = parseEntityRef(entityRef);
724
- const target = entityRoute(entityName);
725
- return { title, icon, url: target };
726
- }
727
- return { title, icon, url };
728
- }).map(({ url, title, icon }, i) => {
729
- const Icon = iconResolver(icon);
730
- return /* @__PURE__ */ React.createElement(Link, { to: url, key: i, classes: { root: classes.root } }, /* @__PURE__ */ React.createElement(Button, { startIcon: /* @__PURE__ */ React.createElement(Icon, null), component: "div", color: "primary" }, title));
731
- }));
732
- };
733
-
734
- const DefaultTemplateOutputs = (props) => {
735
- var _a;
736
- if (!((_a = props.output) == null ? void 0 : _a.links)) {
737
- return null;
738
- }
739
- return /* @__PURE__ */ React.createElement(Box, { paddingBottom: 2 }, /* @__PURE__ */ React.createElement(Paper, null, /* @__PURE__ */ React.createElement(Box, { padding: 2, justifyContent: "center", display: "flex", gridGap: 16 }, /* @__PURE__ */ React.createElement(LinkOutputs, { output: props.output }))));
211
+ };
212
+ }, [scaffolderApi, dispatch, taskId]);
213
+ return state;
740
214
  };
741
215
 
742
- function createNextScaffolderFieldExtension(options) {
216
+ function createScaffolderLayout(options) {
743
217
  return {
744
218
  expose() {
745
- const FieldExtensionDataHolder = () => null;
746
- attachComponentData(
747
- FieldExtensionDataHolder,
748
- FIELD_EXTENSION_KEY,
749
- options
750
- );
751
- return FieldExtensionDataHolder;
219
+ const LayoutDataHolder = () => null;
220
+ attachComponentData(LayoutDataHolder, LAYOUTS_KEY, options);
221
+ return LayoutDataHolder;
752
222
  }
753
223
  };
754
224
  }
225
+ const ScaffolderLayouts = () => null;
226
+ attachComponentData(ScaffolderLayouts, LAYOUTS_WRAPPER_KEY, true);
755
227
 
756
- export { DefaultTemplateOutputs, EmbeddableWorkflow, Form, ReviewState, ScaffolderFieldExtensions, ScaffolderLayouts, SecretsContextProvider, Stepper, TemplateCard, TemplateGroup, Workflow, createFieldValidation, createNextScaffolderFieldExtension, createScaffolderFieldExtension, createScaffolderLayout, extractSchemaFromStep, scaffolderApiRef, useCustomFieldExtensions, useCustomLayouts, useFormDataFromQuery, useTemplateParameterSchema, useTemplateSchema, useTemplateSecrets };
228
+ export { ScaffolderFieldExtensions, ScaffolderLayouts, SecretsContextProvider, createScaffolderFieldExtension, createScaffolderLayout, scaffolderApiRef, useCustomFieldExtensions, useCustomLayouts, useTaskEventStream, useTemplateSecrets };
757
229
  //# sourceMappingURL=index.esm.js.map