@backstage/plugin-scaffolder 1.11.0 → 1.11.1-next.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.
Files changed (31) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/alpha/package.json +4 -3
  3. package/dist/alpha.d.ts +73 -0
  4. package/dist/alpha.esm.js +98 -0
  5. package/dist/alpha.esm.js.map +1 -0
  6. package/dist/esm/alpha/ListTasksPage-5aa6305b.esm.js +192 -0
  7. package/dist/esm/alpha/ListTasksPage-5aa6305b.esm.js.map +1 -0
  8. package/dist/esm/alpha/Router-0b12a83d.esm.js +1563 -0
  9. package/dist/esm/alpha/Router-0b12a83d.esm.js.map +1 -0
  10. package/dist/esm/alpha/alpha-d9ed6c0a.esm.js +3664 -0
  11. package/dist/esm/alpha/alpha-d9ed6c0a.esm.js.map +1 -0
  12. package/dist/esm/alpha/index-9804fdae.esm.js +446 -0
  13. package/dist/esm/alpha/index-9804fdae.esm.js.map +1 -0
  14. package/dist/esm/{ListTasksPage-a0fe74a9.esm.js → index/ListTasksPage-e88f6608.esm.js} +2 -2
  15. package/dist/esm/index/ListTasksPage-e88f6608.esm.js.map +1 -0
  16. package/dist/esm/{Router-fae7a62f.esm.js → index/Router-dc8c17c7.esm.js} +6 -5
  17. package/dist/esm/index/Router-dc8c17c7.esm.js.map +1 -0
  18. package/dist/esm/{index-83c9fe8a.esm.js → index/index-509695ba.esm.js} +6 -5
  19. package/dist/esm/index/index-509695ba.esm.js.map +1 -0
  20. package/dist/esm/{index-5b3a75fa.esm.js → index/index-d9b6a6fb.esm.js} +8 -7
  21. package/dist/esm/index/index-d9b6a6fb.esm.js.map +1 -0
  22. package/dist/index.d.ts +292 -392
  23. package/dist/index.esm.js +27 -26
  24. package/dist/index.esm.js.map +1 -1
  25. package/package.json +35 -24
  26. package/dist/esm/ListTasksPage-a0fe74a9.esm.js.map +0 -1
  27. package/dist/esm/Router-fae7a62f.esm.js.map +0 -1
  28. package/dist/esm/index-5b3a75fa.esm.js.map +0 -1
  29. package/dist/esm/index-83c9fe8a.esm.js.map +0 -1
  30. package/dist/index.alpha.d.ts +0 -642
  31. package/dist/index.beta.d.ts +0 -598
@@ -0,0 +1,3664 @@
1
+ import { scmIntegrationsApiRef, scmAuthApiRef } from '@backstage/integration-react';
2
+ import { scaffolderApiRef, useTemplateSecrets, createScaffolderFieldExtension } from '@backstage/plugin-scaffolder-react';
3
+ import { parseEntityRef, KubernetesValidatorFunctions, RELATION_OWNED_BY, makeValidator } from '@backstage/catalog-model';
4
+ import { ResponseError } from '@backstage/errors';
5
+ import qs from 'qs';
6
+ import ObservableImpl from 'zen-observable';
7
+ import { useApi, identityApiRef, createExternalRouteRef, createRouteRef, createSubRouteRef, useRouteRef, useApiHolder, useApp, useRouteRefParams, alertApiRef, createPlugin, createApiFactory, discoveryApiRef, fetchApiRef, createRoutableExtension } from '@backstage/core-plugin-api';
8
+ import { catalogApiRef, humanizeEntityRef, entityRouteRef } from '@backstage/plugin-catalog-react';
9
+ import { TextField, FormControl as FormControl$1, makeStyles as makeStyles$1, CircularProgress, Typography, Stepper, Step, StepButton, StepLabel, Box, LinearProgress, IconButton as IconButton$1, Popover as Popover$1, MenuList as MenuList$1, MenuItem as MenuItem$1, ListItemIcon as ListItemIcon$1, ListItemText as ListItemText$1, Paper, Accordion, AccordionSummary, AccordionDetails, Grid, TableContainer, Table, TableHead, TableRow, TableCell, TableBody, Chip, Card, List as List$1, InputLabel as InputLabel$1, Select as Select$1, CardHeader, CardContent, Button, Tooltip, Divider as Divider$1 } from '@material-ui/core';
10
+ import FormControl from '@material-ui/core/FormControl';
11
+ import Autocomplete from '@material-ui/lab/Autocomplete';
12
+ import React, { useCallback, useEffect, useState, useMemo, Fragment, createContext, useRef, useContext, Component, memo, Children } from 'react';
13
+ import useAsync from 'react-use/lib/useAsync';
14
+ import { z } from 'zod';
15
+ import zodToJsonSchema from 'zod-to-json-schema';
16
+ import FormHelperText from '@material-ui/core/FormHelperText';
17
+ import Input from '@material-ui/core/Input';
18
+ import InputLabel from '@material-ui/core/InputLabel';
19
+ import { Select, Progress, LogViewer, Page, Header, Content, ErrorPanel, ErrorPage, MarkdownContent, CodeSnippet, DismissableBanner, Link } from '@backstage/core-components';
20
+ import useDebounce from 'react-use/lib/useDebounce';
21
+ import useEffectOnce from 'react-use/lib/useEffectOnce';
22
+ import { Autocomplete as Autocomplete$1 } from '@material-ui/lab';
23
+ import { useNavigate, useParams } from 'react-router-dom';
24
+ import 'lodash/capitalize';
25
+ import '@material-ui/icons/CheckBox';
26
+ import '@material-ui/icons/CheckBoxOutlineBlank';
27
+ import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
28
+ import '@material-ui/core/Button';
29
+ import IconButton from '@material-ui/core/IconButton';
30
+ import '@material-ui/core/useMediaQuery';
31
+ import '@material-ui/icons/AddCircleOutline';
32
+ import '@backstage/plugin-catalog-common/alpha';
33
+ import '@backstage/plugin-permission-react';
34
+ import { DefaultTemplateOutputs, Stepper as Stepper$1, Form } from '@backstage/plugin-scaffolder-react/alpha';
35
+ import ListItemIcon from '@material-ui/core/ListItemIcon';
36
+ import ListItemText from '@material-ui/core/ListItemText';
37
+ import MenuItem from '@material-ui/core/MenuItem';
38
+ import MenuList from '@material-ui/core/MenuList';
39
+ import Popover from '@material-ui/core/Popover';
40
+ import { makeStyles, createStyles } from '@material-ui/core/styles';
41
+ import Description from '@material-ui/icons/Description';
42
+ import Edit from '@material-ui/icons/Edit';
43
+ import List from '@material-ui/icons/List';
44
+ import MoreVert from '@material-ui/icons/MoreVert';
45
+ import { useImmerReducer } from 'use-immer';
46
+ import RemoveCircleOutline from '@material-ui/icons/RemoveCircleOutline';
47
+ import PanoramaFishEyeIcon from '@material-ui/icons/PanoramaFishEye';
48
+ import classNames from 'classnames';
49
+ import CheckCircleOutline from '@material-ui/icons/CheckCircleOutline';
50
+ import ErrorOutline from '@material-ui/icons/ErrorOutline';
51
+ import useInterval from 'react-use/lib/useInterval';
52
+ import { DateTime, Interval } from 'luxon';
53
+ import humanizeDuration$1 from 'humanize-duration';
54
+ import { useMountEffect, useAsync as useAsync$1, useRerender, usePrevious, useKeyboardEvent } from '@react-hookz/web';
55
+ import Retry from '@material-ui/icons/Repeat';
56
+ import Toc from '@material-ui/icons/Toc';
57
+ import SettingsIcon from '@material-ui/icons/Settings';
58
+ import AllIcon from '@material-ui/icons/FontDownload';
59
+ import Typography$1 from '@material-ui/core/Typography';
60
+ import { StreamLanguage } from '@codemirror/language';
61
+ import { yaml as yaml$1 } from '@codemirror/legacy-modes/mode/yaml';
62
+ import CloseIcon from '@material-ui/icons/Close';
63
+ import CodeMirror from '@uiw/react-codemirror';
64
+ import yaml from 'yaml';
65
+ import validator from '@rjsf/validator-ajv8';
66
+ import Accordion$1 from '@material-ui/core/Accordion';
67
+ import AccordionDetails$1 from '@material-ui/core/AccordionDetails';
68
+ import AccordionSummary$1 from '@material-ui/core/AccordionSummary';
69
+ import Divider from '@material-ui/core/Divider';
70
+ import ExpandMoreIcon$1 from '@material-ui/icons/ExpandLess';
71
+ import List$2 from '@material-ui/core/List';
72
+ import ListItem from '@material-ui/core/ListItem';
73
+ import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction';
74
+ import CancelIcon from '@material-ui/icons/Cancel';
75
+ import CheckIcon from '@material-ui/icons/Check';
76
+ import DeleteIcon from '@material-ui/icons/Delete';
77
+ import Box$1 from '@material-ui/core/Box';
78
+ import Tab from '@material-ui/core/Tab';
79
+ import Tabs from '@material-ui/core/Tabs';
80
+ import Grid$1 from '@material-ui/core/Grid';
81
+ import Step$1 from '@material-ui/core/Step';
82
+ import StepLabel$1 from '@material-ui/core/StepLabel';
83
+ import Stepper$2 from '@material-ui/core/Stepper';
84
+ import FiberManualRecordIcon from '@material-ui/icons/FiberManualRecord';
85
+ import LanguageIcon from '@material-ui/icons/Language';
86
+ import TreeView from '@material-ui/lab/TreeView';
87
+ import ChevronRightIcon from '@material-ui/icons/ChevronRight';
88
+ import TreeItem from '@material-ui/lab/TreeItem';
89
+ import RefreshIcon from '@material-ui/icons/Refresh';
90
+ import SaveIcon from '@material-ui/icons/Save';
91
+ import { showPanel } from '@codemirror/view';
92
+ import Card$1 from '@material-ui/core/Card';
93
+ import CardActionArea from '@material-ui/core/CardActionArea';
94
+ import CardContent$1 from '@material-ui/core/CardContent';
95
+ import Tooltip$1 from '@material-ui/core/Tooltip';
96
+ import InfoOutlinedIcon from '@material-ui/icons/InfoOutlined';
97
+
98
+ class ScaffolderClient {
99
+ constructor(options) {
100
+ var _a, _b;
101
+ this.discoveryApi = options.discoveryApi;
102
+ this.fetchApi = (_a = options.fetchApi) != null ? _a : { fetch };
103
+ this.scmIntegrationsApi = options.scmIntegrationsApi;
104
+ this.useLongPollingLogs = (_b = options.useLongPollingLogs) != null ? _b : false;
105
+ this.identityApi = options.identityApi;
106
+ }
107
+ async listTasks(options) {
108
+ if (!this.identityApi) {
109
+ throw new Error(
110
+ "IdentityApi is not available in the ScaffolderClient, please pass through the IdentityApi to the ScaffolderClient constructor in order to use the listTasks method"
111
+ );
112
+ }
113
+ const baseUrl = await this.discoveryApi.getBaseUrl("scaffolder");
114
+ const { userEntityRef } = await this.identityApi.getBackstageIdentity();
115
+ const query = qs.stringify(
116
+ options.filterByOwnership === "owned" ? { createdBy: userEntityRef } : {}
117
+ );
118
+ const response = await this.fetchApi.fetch(`${baseUrl}/v2/tasks?${query}`);
119
+ if (!response.ok) {
120
+ throw await ResponseError.fromResponse(response);
121
+ }
122
+ return await response.json();
123
+ }
124
+ async getIntegrationsList(options) {
125
+ const integrations = [
126
+ ...this.scmIntegrationsApi.azure.list(),
127
+ ...this.scmIntegrationsApi.bitbucket.list().filter(
128
+ (item) => !this.scmIntegrationsApi.bitbucketCloud.byHost(item.config.host) && !this.scmIntegrationsApi.bitbucketServer.byHost(item.config.host)
129
+ ),
130
+ ...this.scmIntegrationsApi.bitbucketCloud.list(),
131
+ ...this.scmIntegrationsApi.bitbucketServer.list(),
132
+ ...this.scmIntegrationsApi.gerrit.list(),
133
+ ...this.scmIntegrationsApi.github.list(),
134
+ ...this.scmIntegrationsApi.gitlab.list()
135
+ ].map((c) => ({ type: c.type, title: c.title, host: c.config.host })).filter((c) => options.allowedHosts.includes(c.host));
136
+ return {
137
+ integrations
138
+ };
139
+ }
140
+ async getTemplateParameterSchema(templateRef) {
141
+ const { namespace, kind, name } = parseEntityRef(templateRef, {
142
+ defaultKind: "template"
143
+ });
144
+ const baseUrl = await this.discoveryApi.getBaseUrl("scaffolder");
145
+ const templatePath = [namespace, kind, name].map((s) => encodeURIComponent(s)).join("/");
146
+ const url = `${baseUrl}/v2/templates/${templatePath}/parameter-schema`;
147
+ const response = await this.fetchApi.fetch(url);
148
+ if (!response.ok) {
149
+ throw await ResponseError.fromResponse(response);
150
+ }
151
+ const schema = await response.json();
152
+ return schema;
153
+ }
154
+ async scaffold(options) {
155
+ const { templateRef, values, secrets = {} } = options;
156
+ const url = `${await this.discoveryApi.getBaseUrl("scaffolder")}/v2/tasks`;
157
+ const response = await this.fetchApi.fetch(url, {
158
+ method: "POST",
159
+ headers: {
160
+ "Content-Type": "application/json"
161
+ },
162
+ body: JSON.stringify({
163
+ templateRef,
164
+ values: { ...values },
165
+ secrets
166
+ })
167
+ });
168
+ if (response.status !== 201) {
169
+ const status = `${response.status} ${response.statusText}`;
170
+ const body = await response.text();
171
+ throw new Error(`Backend request failed, ${status} ${body.trim()}`);
172
+ }
173
+ const { id } = await response.json();
174
+ return { taskId: id };
175
+ }
176
+ async getTask(taskId) {
177
+ const baseUrl = await this.discoveryApi.getBaseUrl("scaffolder");
178
+ const url = `${baseUrl}/v2/tasks/${encodeURIComponent(taskId)}`;
179
+ const response = await this.fetchApi.fetch(url);
180
+ if (!response.ok) {
181
+ throw await ResponseError.fromResponse(response);
182
+ }
183
+ return await response.json();
184
+ }
185
+ streamLogs(options) {
186
+ if (this.useLongPollingLogs) {
187
+ return this.streamLogsPolling(options);
188
+ }
189
+ return this.streamLogsEventStream(options);
190
+ }
191
+ async dryRun(options) {
192
+ const baseUrl = await this.discoveryApi.getBaseUrl("scaffolder");
193
+ const response = await this.fetchApi.fetch(`${baseUrl}/v2/dry-run`, {
194
+ method: "POST",
195
+ headers: {
196
+ "Content-Type": "application/json"
197
+ },
198
+ body: JSON.stringify({
199
+ template: options.template,
200
+ values: options.values,
201
+ secrets: options.secrets,
202
+ directoryContents: options.directoryContents
203
+ })
204
+ });
205
+ if (!response.ok) {
206
+ throw await ResponseError.fromResponse(response);
207
+ }
208
+ return response.json();
209
+ }
210
+ streamLogsEventStream({
211
+ taskId,
212
+ after
213
+ }) {
214
+ return new ObservableImpl((subscriber) => {
215
+ const params = new URLSearchParams();
216
+ if (after !== void 0) {
217
+ params.set("after", String(Number(after)));
218
+ }
219
+ this.discoveryApi.getBaseUrl("scaffolder").then(
220
+ (baseUrl) => {
221
+ const url = `${baseUrl}/v2/tasks/${encodeURIComponent(
222
+ taskId
223
+ )}/eventstream`;
224
+ const eventSource = new EventSource(url, { withCredentials: true });
225
+ eventSource.addEventListener("log", (event) => {
226
+ if (event.data) {
227
+ try {
228
+ subscriber.next(JSON.parse(event.data));
229
+ } catch (ex) {
230
+ subscriber.error(ex);
231
+ }
232
+ }
233
+ });
234
+ eventSource.addEventListener("completion", (event) => {
235
+ if (event.data) {
236
+ try {
237
+ subscriber.next(JSON.parse(event.data));
238
+ } catch (ex) {
239
+ subscriber.error(ex);
240
+ }
241
+ }
242
+ eventSource.close();
243
+ subscriber.complete();
244
+ });
245
+ eventSource.addEventListener("error", (event) => {
246
+ subscriber.error(event);
247
+ });
248
+ },
249
+ (error) => {
250
+ subscriber.error(error);
251
+ }
252
+ );
253
+ });
254
+ }
255
+ streamLogsPolling({
256
+ taskId,
257
+ after: inputAfter
258
+ }) {
259
+ let after = inputAfter;
260
+ return new ObservableImpl((subscriber) => {
261
+ this.discoveryApi.getBaseUrl("scaffolder").then(async (baseUrl) => {
262
+ while (!subscriber.closed) {
263
+ const url = `${baseUrl}/v2/tasks/${encodeURIComponent(
264
+ taskId
265
+ )}/events?${qs.stringify({ after })}`;
266
+ const response = await this.fetchApi.fetch(url);
267
+ if (!response.ok) {
268
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
269
+ continue;
270
+ }
271
+ const logs = await response.json();
272
+ for (const event of logs) {
273
+ after = Number(event.id);
274
+ subscriber.next(event);
275
+ if (event.type === "completion") {
276
+ subscriber.complete();
277
+ return;
278
+ }
279
+ }
280
+ }
281
+ });
282
+ });
283
+ }
284
+ async listActions() {
285
+ const baseUrl = await this.discoveryApi.getBaseUrl("scaffolder");
286
+ const response = await this.fetchApi.fetch(`${baseUrl}/v2/actions`);
287
+ if (!response.ok) {
288
+ throw await ResponseError.fromResponse(response);
289
+ }
290
+ return await response.json();
291
+ }
292
+ }
293
+
294
+ function makeFieldSchemaFromZod(returnSchema, uiOptionsSchema) {
295
+ return {
296
+ schema: {
297
+ returnValue: zodToJsonSchema(returnSchema),
298
+ uiOptions: uiOptionsSchema ? zodToJsonSchema(uiOptionsSchema) : void 0
299
+ },
300
+ type: null,
301
+ uiOptionsType: null
302
+ };
303
+ }
304
+
305
+ const entityQueryFilterExpressionSchema = z.record(
306
+ z.string().or(z.array(z.string()))
307
+ );
308
+ const EntityPickerFieldSchema = makeFieldSchemaFromZod(
309
+ z.string(),
310
+ z.object({
311
+ /**
312
+ * @deprecated Use `catalogFilter` instead.
313
+ */
314
+ allowedKinds: z.array(z.string()).optional().describe(
315
+ "DEPRECATED: Use `catalogFilter` instead. List of kinds of entities to derive options from"
316
+ ),
317
+ defaultKind: z.string().optional().describe(
318
+ "The default entity kind. Options of this kind will not be prefixed."
319
+ ),
320
+ allowArbitraryValues: z.boolean().optional().describe("Whether to allow arbitrary user input. Defaults to true"),
321
+ defaultNamespace: z.union([z.string(), z.literal(false)]).optional().describe(
322
+ "The default namespace. Options with this namespace will not be prefixed."
323
+ ),
324
+ catalogFilter: z.array(entityQueryFilterExpressionSchema).or(entityQueryFilterExpressionSchema).optional().describe("List of key-value filter expression for entities")
325
+ })
326
+ );
327
+ const EntityPickerSchema = EntityPickerFieldSchema.schema;
328
+
329
+ const EntityPicker = (props) => {
330
+ var _a, _b, _c, _d, _e, _f;
331
+ const {
332
+ onChange,
333
+ schema: { title = "Entity", description = "An entity from the catalog" },
334
+ required,
335
+ uiSchema,
336
+ rawErrors,
337
+ formData,
338
+ idSchema
339
+ } = props;
340
+ const allowedKinds = (_a = uiSchema["ui:options"]) == null ? void 0 : _a.allowedKinds;
341
+ const catalogFilter = ((_b = uiSchema["ui:options"]) == null ? void 0 : _b.catalogFilter) || allowedKinds && { kind: allowedKinds };
342
+ const defaultKind = (_c = uiSchema["ui:options"]) == null ? void 0 : _c.defaultKind;
343
+ const defaultNamespace = (_d = uiSchema["ui:options"]) == null ? void 0 : _d.defaultNamespace;
344
+ const catalogApi = useApi(catalogApiRef);
345
+ const { value: entities, loading } = useAsync(
346
+ () => catalogApi.getEntities(
347
+ catalogFilter ? { filter: catalogFilter } : void 0
348
+ )
349
+ );
350
+ const entityRefs = entities == null ? void 0 : entities.items.map(
351
+ (e) => humanizeEntityRef(e, { defaultKind, defaultNamespace })
352
+ );
353
+ const onSelect = useCallback(
354
+ (_, value) => {
355
+ onChange(value != null ? value : void 0);
356
+ },
357
+ [onChange]
358
+ );
359
+ useEffect(() => {
360
+ if ((entityRefs == null ? void 0 : entityRefs.length) === 1) {
361
+ onChange(entityRefs[0]);
362
+ }
363
+ }, [entityRefs, onChange]);
364
+ return /* @__PURE__ */ React.createElement(
365
+ FormControl,
366
+ {
367
+ margin: "normal",
368
+ required,
369
+ error: (rawErrors == null ? void 0 : rawErrors.length) > 0 && !formData
370
+ },
371
+ /* @__PURE__ */ React.createElement(
372
+ Autocomplete,
373
+ {
374
+ disabled: (entityRefs == null ? void 0 : entityRefs.length) === 1,
375
+ id: idSchema == null ? void 0 : idSchema.$id,
376
+ value: formData || "",
377
+ loading,
378
+ onChange: onSelect,
379
+ options: entityRefs || [],
380
+ autoSelect: true,
381
+ freeSolo: (_f = (_e = uiSchema["ui:options"]) == null ? void 0 : _e.allowArbitraryValues) != null ? _f : true,
382
+ renderInput: (params) => /* @__PURE__ */ React.createElement(
383
+ TextField,
384
+ {
385
+ ...params,
386
+ label: title,
387
+ margin: "dense",
388
+ helperText: description,
389
+ FormHelperTextProps: { margin: "dense", style: { marginLeft: 0 } },
390
+ variant: "outlined",
391
+ required,
392
+ InputProps: params.InputProps
393
+ }
394
+ )
395
+ }
396
+ )
397
+ );
398
+ };
399
+
400
+ const entityNamePickerValidation = (value, validation) => {
401
+ if (!KubernetesValidatorFunctions.isValidObjectName(value)) {
402
+ validation.addError(
403
+ "Must start and end with an alphanumeric character, and contain only alphanumeric characters, hyphens, underscores, and periods. Maximum length is 63 characters."
404
+ );
405
+ }
406
+ };
407
+
408
+ const EntityNamePickerFieldSchema = makeFieldSchemaFromZod(z.string());
409
+ const EntityNamePickerSchema = EntityNamePickerFieldSchema.schema;
410
+
411
+ const EntityNamePicker = (props) => {
412
+ const {
413
+ onChange,
414
+ required,
415
+ schema: { title = "Name", description = "Unique name of the component" },
416
+ rawErrors,
417
+ formData,
418
+ uiSchema: { "ui:autofocus": autoFocus },
419
+ idSchema,
420
+ placeholder
421
+ } = props;
422
+ return /* @__PURE__ */ React.createElement(
423
+ TextField,
424
+ {
425
+ id: idSchema == null ? void 0 : idSchema.$id,
426
+ label: title,
427
+ placeholder,
428
+ helperText: description,
429
+ required,
430
+ value: formData != null ? formData : "",
431
+ onChange: ({ target: { value } }) => onChange(value),
432
+ margin: "normal",
433
+ error: (rawErrors == null ? void 0 : rawErrors.length) > 0 && !formData,
434
+ inputProps: { autoFocus }
435
+ }
436
+ );
437
+ };
438
+
439
+ const OwnerPickerFieldSchema = makeFieldSchemaFromZod(
440
+ z.string(),
441
+ z.object({
442
+ /**
443
+ * @deprecated Use `catalogFilter` instead.
444
+ */
445
+ allowedKinds: z.array(z.string()).default(["Group", "User"]).optional().describe(
446
+ "DEPRECATED: Use `catalogFilter` instead. List of kinds of entities to derive options from. Defaults to Group and User"
447
+ ),
448
+ allowArbitraryValues: z.boolean().optional().describe("Whether to allow arbitrary user input. Defaults to true"),
449
+ defaultNamespace: z.union([z.string(), z.literal(false)]).optional().describe(
450
+ "The default namespace. Options with this namespace will not be prefixed."
451
+ ),
452
+ catalogFilter: z.array(entityQueryFilterExpressionSchema).or(entityQueryFilterExpressionSchema).optional().describe("List of key-value filter expression for entities")
453
+ })
454
+ );
455
+ const OwnerPickerSchema = OwnerPickerFieldSchema.schema;
456
+
457
+ const OwnerPicker = (props) => {
458
+ var _a, _b, _c, _d, _e;
459
+ const {
460
+ schema: { title = "Owner", description = "The owner of the component" },
461
+ uiSchema,
462
+ ...restProps
463
+ } = props;
464
+ const defaultNamespace = (_a = uiSchema["ui:options"]) == null ? void 0 : _a.defaultNamespace;
465
+ const allowedKinds = (_b = uiSchema["ui:options"]) == null ? void 0 : _b.allowedKinds;
466
+ const catalogFilter = ((_c = uiSchema["ui:options"]) == null ? void 0 : _c.catalogFilter) || {
467
+ kind: allowedKinds || ["Group", "User"]
468
+ };
469
+ const ownerUiSchema = {
470
+ ...uiSchema,
471
+ "ui:options": {
472
+ catalogFilter,
473
+ defaultKind: "Group",
474
+ allowArbitraryValues: (_e = (_d = uiSchema["ui:options"]) == null ? void 0 : _d.allowArbitraryValues) != null ? _e : true,
475
+ ...defaultNamespace !== void 0 ? { defaultNamespace } : {}
476
+ }
477
+ };
478
+ return /* @__PURE__ */ React.createElement(
479
+ EntityPicker,
480
+ {
481
+ ...restProps,
482
+ schema: { title, description },
483
+ uiSchema: ownerUiSchema
484
+ }
485
+ );
486
+ };
487
+
488
+ const RepoUrlPickerFieldSchema = makeFieldSchemaFromZod(
489
+ z.string(),
490
+ z.object({
491
+ allowedHosts: z.array(z.string()).optional().describe("List of allowed SCM platform hosts"),
492
+ allowedOrganizations: z.array(z.string()).optional().describe("List of allowed organizations in the given SCM platform"),
493
+ allowedOwners: z.array(z.string()).optional().describe("List of allowed owners in the given SCM platform"),
494
+ allowedProjects: z.array(z.string()).optional().describe("List of allowed projects in the given SCM platform"),
495
+ allowedRepos: z.array(z.string()).optional().describe("List of allowed repos in the given SCM platform"),
496
+ requestUserCredentials: z.object({
497
+ secretsKey: z.string().describe(
498
+ "Key used within the template secrets context to store the credential"
499
+ ),
500
+ additionalScopes: z.object({
501
+ gerrit: z.array(z.string()).optional().describe("Additional Gerrit scopes to request"),
502
+ github: z.array(z.string()).optional().describe("Additional GitHub scopes to request"),
503
+ gitlab: z.array(z.string()).optional().describe("Additional GitLab scopes to request"),
504
+ bitbucket: z.array(z.string()).optional().describe("Additional BitBucket scopes to request"),
505
+ azure: z.array(z.string()).optional().describe("Additional Azure scopes to request")
506
+ }).optional().describe("Additional permission scopes to request")
507
+ }).optional().describe(
508
+ "If defined will request user credentials to auth against the given SCM platform"
509
+ )
510
+ })
511
+ );
512
+ const RepoUrlPickerSchema = RepoUrlPickerFieldSchema.schema;
513
+
514
+ const repoPickerValidation = (value, validation, context) => {
515
+ var _a, _b;
516
+ try {
517
+ const { host, searchParams } = new URL(`https://${value}`);
518
+ const integrationApi = context.apiHolder.get(scmIntegrationsApiRef);
519
+ if (!host) {
520
+ validation.addError(
521
+ "Incomplete repository location provided, host not provided"
522
+ );
523
+ } else {
524
+ if (((_a = integrationApi == null ? void 0 : integrationApi.byHost(host)) == null ? void 0 : _a.type) === "bitbucket") {
525
+ if (host === "bitbucket.org" && !searchParams.get("workspace")) {
526
+ validation.addError(
527
+ "Incomplete repository location provided, workspace not provided"
528
+ );
529
+ }
530
+ if (!searchParams.get("project")) {
531
+ validation.addError(
532
+ "Incomplete repository location provided, project not provided"
533
+ );
534
+ }
535
+ } else if (((_b = integrationApi == null ? void 0 : integrationApi.byHost(host)) == null ? void 0 : _b.type) !== "gerrit") {
536
+ if (!searchParams.get("owner")) {
537
+ validation.addError(
538
+ "Incomplete repository location provided, owner not provided"
539
+ );
540
+ }
541
+ }
542
+ if (!searchParams.get("repo")) {
543
+ validation.addError(
544
+ "Incomplete repository location provided, repo not provided"
545
+ );
546
+ }
547
+ }
548
+ } catch {
549
+ validation.addError("Unable to parse the Repository URL");
550
+ }
551
+ };
552
+
553
+ const GithubRepoPicker = (props) => {
554
+ const { allowedOwners = [], rawErrors, state, onChange } = props;
555
+ const ownerItems = allowedOwners ? allowedOwners.map((i) => ({ label: i, value: i })) : [{ label: "Loading...", value: "loading" }];
556
+ const { owner } = state;
557
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
558
+ FormControl,
559
+ {
560
+ margin: "normal",
561
+ required: true,
562
+ error: (rawErrors == null ? void 0 : rawErrors.length) > 0 && !owner
563
+ },
564
+ (allowedOwners == null ? void 0 : allowedOwners.length) ? /* @__PURE__ */ React.createElement(
565
+ Select,
566
+ {
567
+ native: true,
568
+ label: "Owner Available",
569
+ onChange: (s) => onChange({ owner: String(Array.isArray(s) ? s[0] : s) }),
570
+ disabled: allowedOwners.length === 1,
571
+ selected: owner,
572
+ items: ownerItems
573
+ }
574
+ ) : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(InputLabel, { htmlFor: "ownerInput" }, "Owner"), /* @__PURE__ */ React.createElement(
575
+ Input,
576
+ {
577
+ id: "ownerInput",
578
+ onChange: (e) => onChange({ owner: e.target.value }),
579
+ value: owner
580
+ }
581
+ )),
582
+ /* @__PURE__ */ React.createElement(FormHelperText, null, "The organization, user or project that this repo will belong to")
583
+ ));
584
+ };
585
+
586
+ const GitlabRepoPicker = (props) => {
587
+ const { allowedOwners = [], state, onChange, rawErrors } = props;
588
+ const ownerItems = allowedOwners ? allowedOwners.map((i) => ({ label: i, value: i })) : [{ label: "Loading...", value: "loading" }];
589
+ const { owner } = state;
590
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
591
+ FormControl,
592
+ {
593
+ margin: "normal",
594
+ required: true,
595
+ error: (rawErrors == null ? void 0 : rawErrors.length) > 0 && !owner
596
+ },
597
+ (allowedOwners == null ? void 0 : allowedOwners.length) ? /* @__PURE__ */ React.createElement(
598
+ Select,
599
+ {
600
+ native: true,
601
+ label: "Owner Available",
602
+ onChange: (selected) => onChange({
603
+ owner: String(Array.isArray(selected) ? selected[0] : selected)
604
+ }),
605
+ disabled: allowedOwners.length === 1,
606
+ selected: owner,
607
+ items: ownerItems
608
+ }
609
+ ) : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(InputLabel, { htmlFor: "ownerInput" }, "Owner"), /* @__PURE__ */ React.createElement(
610
+ Input,
611
+ {
612
+ id: "ownerInput",
613
+ onChange: (e) => onChange({ owner: e.target.value }),
614
+ value: owner
615
+ }
616
+ )),
617
+ /* @__PURE__ */ React.createElement(FormHelperText, null, "GitLab namespace where this repository will belong to. It can be the name of organization, group, subgroup, user, or the project.")
618
+ ));
619
+ };
620
+
621
+ const AzureRepoPicker = (props) => {
622
+ const {
623
+ allowedOrganizations = [],
624
+ allowedOwners = [],
625
+ rawErrors,
626
+ state,
627
+ onChange
628
+ } = props;
629
+ const organizationItems = allowedOrganizations ? allowedOrganizations.map((i) => ({ label: i, value: i })) : [{ label: "Loading...", value: "loading" }];
630
+ const ownerItems = allowedOwners ? allowedOwners.map((i) => ({ label: i, value: i })) : [{ label: "Loading...", value: "loading" }];
631
+ const { organization, owner } = state;
632
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
633
+ FormControl,
634
+ {
635
+ margin: "normal",
636
+ required: true,
637
+ error: (rawErrors == null ? void 0 : rawErrors.length) > 0 && !organization
638
+ },
639
+ (allowedOrganizations == null ? void 0 : allowedOrganizations.length) ? /* @__PURE__ */ React.createElement(
640
+ Select,
641
+ {
642
+ native: true,
643
+ label: "Organization",
644
+ onChange: (s) => onChange({ organization: String(Array.isArray(s) ? s[0] : s) }),
645
+ disabled: allowedOrganizations.length === 1,
646
+ selected: organization,
647
+ items: organizationItems
648
+ }
649
+ ) : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(InputLabel, { htmlFor: "orgInput" }, "Organization"), /* @__PURE__ */ React.createElement(
650
+ Input,
651
+ {
652
+ id: "orgInput",
653
+ onChange: (e) => onChange({ organization: e.target.value }),
654
+ value: organization
655
+ }
656
+ )),
657
+ /* @__PURE__ */ React.createElement(FormHelperText, null, "The Organization that this repo will belong to")
658
+ ), /* @__PURE__ */ React.createElement(
659
+ FormControl,
660
+ {
661
+ margin: "normal",
662
+ required: true,
663
+ error: (rawErrors == null ? void 0 : rawErrors.length) > 0 && !owner
664
+ },
665
+ (allowedOwners == null ? void 0 : allowedOwners.length) ? /* @__PURE__ */ React.createElement(
666
+ Select,
667
+ {
668
+ native: true,
669
+ label: "Owner",
670
+ onChange: (s) => onChange({ owner: String(Array.isArray(s) ? s[0] : s) }),
671
+ disabled: allowedOwners.length === 1,
672
+ selected: owner,
673
+ items: ownerItems
674
+ }
675
+ ) : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(InputLabel, { htmlFor: "ownerInput" }, "Project"), /* @__PURE__ */ React.createElement(
676
+ Input,
677
+ {
678
+ id: "ownerInput",
679
+ onChange: (e) => onChange({ owner: e.target.value }),
680
+ value: owner
681
+ }
682
+ )),
683
+ /* @__PURE__ */ React.createElement(FormHelperText, null, "The Project that this repo will belong to")
684
+ ));
685
+ };
686
+
687
+ const BitbucketRepoPicker = (props) => {
688
+ const {
689
+ allowedOwners = [],
690
+ allowedProjects = [],
691
+ onChange,
692
+ rawErrors,
693
+ state
694
+ } = props;
695
+ const { host, workspace, project } = state;
696
+ const ownerItems = allowedOwners ? allowedOwners == null ? void 0 : allowedOwners.map((i) => ({ label: i, value: i })) : [];
697
+ const projectItems = allowedProjects ? allowedProjects == null ? void 0 : allowedProjects.map((i) => ({ label: i, value: i })) : [];
698
+ useEffect(() => {
699
+ if (host === "bitbucket.org" && allowedOwners.length) {
700
+ onChange({ workspace: allowedOwners[0] });
701
+ }
702
+ }, [allowedOwners, host, onChange]);
703
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, host === "bitbucket.org" && /* @__PURE__ */ React.createElement(
704
+ FormControl,
705
+ {
706
+ margin: "normal",
707
+ required: true,
708
+ error: (rawErrors == null ? void 0 : rawErrors.length) > 0 && !workspace
709
+ },
710
+ (allowedOwners == null ? void 0 : allowedOwners.length) ? /* @__PURE__ */ React.createElement(
711
+ Select,
712
+ {
713
+ native: true,
714
+ label: "Allowed Workspaces",
715
+ onChange: (s) => onChange({ workspace: String(Array.isArray(s) ? s[0] : s) }),
716
+ disabled: allowedOwners.length === 1,
717
+ selected: workspace,
718
+ items: ownerItems
719
+ }
720
+ ) : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(InputLabel, { htmlFor: "workspaceInput" }, "Workspace"), /* @__PURE__ */ React.createElement(
721
+ Input,
722
+ {
723
+ id: "workspaceInput",
724
+ onChange: (e) => onChange({ workspace: e.target.value }),
725
+ value: workspace
726
+ }
727
+ )),
728
+ /* @__PURE__ */ React.createElement(FormHelperText, null, "The Workspace that this repo will belong to")
729
+ ), /* @__PURE__ */ React.createElement(
730
+ FormControl,
731
+ {
732
+ margin: "normal",
733
+ required: true,
734
+ error: (rawErrors == null ? void 0 : rawErrors.length) > 0 && !project
735
+ },
736
+ (allowedProjects == null ? void 0 : allowedProjects.length) ? /* @__PURE__ */ React.createElement(
737
+ Select,
738
+ {
739
+ native: true,
740
+ label: "Allowed Projects",
741
+ onChange: (s) => onChange({ project: String(Array.isArray(s) ? s[0] : s) }),
742
+ disabled: allowedProjects.length === 1,
743
+ selected: project,
744
+ items: projectItems
745
+ }
746
+ ) : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(InputLabel, { htmlFor: "projectInput" }, "Project"), /* @__PURE__ */ React.createElement(
747
+ Input,
748
+ {
749
+ id: "projectInput",
750
+ onChange: (e) => onChange({ project: e.target.value }),
751
+ value: project
752
+ }
753
+ )),
754
+ /* @__PURE__ */ React.createElement(FormHelperText, null, "The Project that this repo will belong to")
755
+ ));
756
+ };
757
+
758
+ const GerritRepoPicker = (props) => {
759
+ const { onChange, rawErrors, state } = props;
760
+ const { workspace, owner } = state;
761
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(FormControl, { margin: "normal", error: (rawErrors == null ? void 0 : rawErrors.length) > 0 && !workspace }, /* @__PURE__ */ React.createElement(InputLabel, { htmlFor: "ownerInput" }, "Owner"), /* @__PURE__ */ React.createElement(
762
+ Input,
763
+ {
764
+ id: "ownerInput",
765
+ onChange: (e) => onChange({ owner: e.target.value }),
766
+ value: owner
767
+ }
768
+ ), /* @__PURE__ */ React.createElement(FormHelperText, null, "The owner of the project (optional)")), /* @__PURE__ */ React.createElement(
769
+ FormControl,
770
+ {
771
+ margin: "normal",
772
+ required: true,
773
+ error: (rawErrors == null ? void 0 : rawErrors.length) > 0 && !workspace
774
+ },
775
+ /* @__PURE__ */ React.createElement(InputLabel, { htmlFor: "parentInput" }, "Parent"),
776
+ /* @__PURE__ */ React.createElement(
777
+ Input,
778
+ {
779
+ id: "parentInput",
780
+ onChange: (e) => onChange({ workspace: e.target.value }),
781
+ value: workspace
782
+ }
783
+ ),
784
+ /* @__PURE__ */ React.createElement(FormHelperText, null, "The project parent that the repo will belong to")
785
+ ));
786
+ };
787
+
788
+ const RepoUrlPickerHost = (props) => {
789
+ const { host, hosts, onChange, rawErrors } = props;
790
+ const scaffolderApi = useApi(scaffolderApiRef);
791
+ const { value: { integrations } = { integrations: [] }, loading } = useAsync(
792
+ async () => {
793
+ return await scaffolderApi.getIntegrationsList({
794
+ allowedHosts: hosts != null ? hosts : []
795
+ });
796
+ }
797
+ );
798
+ useEffect(() => {
799
+ if (!host) {
800
+ if (hosts == null ? void 0 : hosts.length) {
801
+ onChange(hosts[0]);
802
+ } else if (integrations == null ? void 0 : integrations.length) {
803
+ onChange(integrations[0].host);
804
+ }
805
+ }
806
+ }, [hosts, host, onChange, integrations]);
807
+ const hostsOptions = integrations ? integrations.filter((i) => (hosts == null ? void 0 : hosts.length) ? hosts == null ? void 0 : hosts.includes(i.host) : true).map((i) => ({ label: i.title, value: i.host })) : [{ label: "Loading...", value: "loading" }];
808
+ if (loading) {
809
+ return /* @__PURE__ */ React.createElement(Progress, null);
810
+ }
811
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
812
+ FormControl,
813
+ {
814
+ margin: "normal",
815
+ required: true,
816
+ error: (rawErrors == null ? void 0 : rawErrors.length) > 0 && !host
817
+ },
818
+ /* @__PURE__ */ React.createElement(
819
+ Select,
820
+ {
821
+ native: true,
822
+ disabled: (hosts == null ? void 0 : hosts.length) === 1,
823
+ label: "Host",
824
+ onChange: (s) => onChange(String(Array.isArray(s) ? s[0] : s)),
825
+ selected: host,
826
+ items: hostsOptions,
827
+ "data-testid": "host-select"
828
+ }
829
+ ),
830
+ /* @__PURE__ */ React.createElement(FormHelperText, null, "The host where the repository will be created")
831
+ ));
832
+ };
833
+
834
+ const RepoUrlPickerRepoName = (props) => {
835
+ const { repoName, allowedRepos, onChange, rawErrors } = props;
836
+ useEffect(() => {
837
+ if (!repoName) {
838
+ if (allowedRepos == null ? void 0 : allowedRepos.length) {
839
+ onChange(allowedRepos[0]);
840
+ }
841
+ }
842
+ }, [allowedRepos, repoName, onChange]);
843
+ const repoItems = allowedRepos ? allowedRepos.map((i) => ({ label: i, value: i })) : [{ label: "Loading...", value: "loading" }];
844
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
845
+ FormControl,
846
+ {
847
+ margin: "normal",
848
+ required: true,
849
+ error: (rawErrors == null ? void 0 : rawErrors.length) > 0 && !repoName
850
+ },
851
+ (allowedRepos == null ? void 0 : allowedRepos.length) ? /* @__PURE__ */ React.createElement(
852
+ Select,
853
+ {
854
+ native: true,
855
+ label: "Repositories Available",
856
+ onChange: (selected) => String(Array.isArray(selected) ? selected[0] : selected),
857
+ disabled: allowedRepos.length === 1,
858
+ selected: repoName,
859
+ items: repoItems
860
+ }
861
+ ) : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(InputLabel, { htmlFor: "repoNameInput" }, "Repository"), /* @__PURE__ */ React.createElement(
862
+ Input,
863
+ {
864
+ id: "repoNameInput",
865
+ onChange: (e) => onChange(String(e.target.value)),
866
+ value: repoName
867
+ }
868
+ )),
869
+ /* @__PURE__ */ React.createElement(FormHelperText, null, "The name of the repository")
870
+ ));
871
+ };
872
+
873
+ function serializeRepoPickerUrl(data) {
874
+ if (!data.host) {
875
+ return void 0;
876
+ }
877
+ const params = new URLSearchParams();
878
+ if (data.owner) {
879
+ params.set("owner", data.owner);
880
+ }
881
+ if (data.repoName) {
882
+ params.set("repo", data.repoName);
883
+ }
884
+ if (data.organization) {
885
+ params.set("organization", data.organization);
886
+ }
887
+ if (data.workspace) {
888
+ params.set("workspace", data.workspace);
889
+ }
890
+ if (data.project) {
891
+ params.set("project", data.project);
892
+ }
893
+ return `${data.host}?${params.toString()}`;
894
+ }
895
+ function parseRepoPickerUrl(url) {
896
+ let host = "";
897
+ let owner = "";
898
+ let repoName = "";
899
+ let organization = "";
900
+ let workspace = "";
901
+ let project = "";
902
+ try {
903
+ if (url) {
904
+ const parsed = new URL(`https://${url}`);
905
+ host = parsed.host;
906
+ owner = parsed.searchParams.get("owner") || "";
907
+ repoName = parsed.searchParams.get("repo") || "";
908
+ organization = parsed.searchParams.get("organization") || "";
909
+ workspace = parsed.searchParams.get("workspace") || "";
910
+ project = parsed.searchParams.get("project") || "";
911
+ }
912
+ } catch {
913
+ }
914
+ return { host, owner, repoName, organization, workspace, project };
915
+ }
916
+
917
+ const RepoUrlPicker = (props) => {
918
+ var _a, _b;
919
+ const { uiSchema, onChange, rawErrors, formData } = props;
920
+ const [state, setState] = useState(
921
+ parseRepoPickerUrl(formData)
922
+ );
923
+ const integrationApi = useApi(scmIntegrationsApiRef);
924
+ const scmAuthApi = useApi(scmAuthApiRef);
925
+ const { setSecrets } = useTemplateSecrets();
926
+ const allowedHosts = useMemo(
927
+ () => {
928
+ var _a2, _b2;
929
+ return (_b2 = (_a2 = uiSchema == null ? void 0 : uiSchema["ui:options"]) == null ? void 0 : _a2.allowedHosts) != null ? _b2 : [];
930
+ },
931
+ [uiSchema]
932
+ );
933
+ const allowedOrganizations = useMemo(
934
+ () => {
935
+ var _a2, _b2;
936
+ return (_b2 = (_a2 = uiSchema == null ? void 0 : uiSchema["ui:options"]) == null ? void 0 : _a2.allowedOrganizations) != null ? _b2 : [];
937
+ },
938
+ [uiSchema]
939
+ );
940
+ const allowedOwners = useMemo(
941
+ () => {
942
+ var _a2, _b2;
943
+ return (_b2 = (_a2 = uiSchema == null ? void 0 : uiSchema["ui:options"]) == null ? void 0 : _a2.allowedOwners) != null ? _b2 : [];
944
+ },
945
+ [uiSchema]
946
+ );
947
+ const allowedProjects = useMemo(
948
+ () => {
949
+ var _a2, _b2;
950
+ return (_b2 = (_a2 = uiSchema == null ? void 0 : uiSchema["ui:options"]) == null ? void 0 : _a2.allowedProjects) != null ? _b2 : [];
951
+ },
952
+ [uiSchema]
953
+ );
954
+ const allowedRepos = useMemo(
955
+ () => {
956
+ var _a2, _b2;
957
+ return (_b2 = (_a2 = uiSchema == null ? void 0 : uiSchema["ui:options"]) == null ? void 0 : _a2.allowedRepos) != null ? _b2 : [];
958
+ },
959
+ [uiSchema]
960
+ );
961
+ const { owner, organization, project, repoName } = state;
962
+ useEffect(() => {
963
+ onChange(serializeRepoPickerUrl(state));
964
+ }, [state, onChange]);
965
+ useEffect(() => {
966
+ if (allowedOrganizations.length > 0 && !organization) {
967
+ setState((prevState) => ({
968
+ ...prevState,
969
+ organization: allowedOrganizations[0]
970
+ }));
971
+ }
972
+ }, [setState, allowedOrganizations, organization]);
973
+ useEffect(() => {
974
+ if (allowedOwners.length > 0 && !owner) {
975
+ setState((prevState) => ({
976
+ ...prevState,
977
+ owner: allowedOwners[0]
978
+ }));
979
+ }
980
+ }, [setState, allowedOwners, owner]);
981
+ useEffect(() => {
982
+ if (allowedProjects.length > 0 && !project) {
983
+ setState((prevState) => ({
984
+ ...prevState,
985
+ project: allowedProjects[0]
986
+ }));
987
+ }
988
+ }, [setState, allowedProjects, project]);
989
+ useEffect(() => {
990
+ if (allowedRepos.length > 0 && !repoName) {
991
+ setState((prevState) => ({ ...prevState, repoName: allowedRepos[0] }));
992
+ }
993
+ }, [setState, allowedRepos, repoName]);
994
+ const updateLocalState = useCallback(
995
+ (newState) => {
996
+ setState((prevState) => ({ ...prevState, ...newState }));
997
+ },
998
+ [setState]
999
+ );
1000
+ useDebounce(
1001
+ async () => {
1002
+ var _a2;
1003
+ const { requestUserCredentials } = (_a2 = uiSchema == null ? void 0 : uiSchema["ui:options"]) != null ? _a2 : {};
1004
+ if (!requestUserCredentials || !(state.host && state.owner && state.repoName)) {
1005
+ return;
1006
+ }
1007
+ const [encodedHost, encodedOwner, encodedRepoName] = [
1008
+ state.host,
1009
+ state.owner,
1010
+ state.repoName
1011
+ ].map(encodeURIComponent);
1012
+ const { token } = await scmAuthApi.getCredentials({
1013
+ url: `https://${encodedHost}/${encodedOwner}/${encodedRepoName}`,
1014
+ additionalScope: {
1015
+ repoWrite: true,
1016
+ customScopes: requestUserCredentials.additionalScopes
1017
+ }
1018
+ });
1019
+ setSecrets({ [requestUserCredentials.secretsKey]: token });
1020
+ },
1021
+ 500,
1022
+ [state, uiSchema]
1023
+ );
1024
+ const hostType = (_b = state.host && ((_a = integrationApi.byHost(state.host)) == null ? void 0 : _a.type)) != null ? _b : null;
1025
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
1026
+ RepoUrlPickerHost,
1027
+ {
1028
+ host: state.host,
1029
+ hosts: allowedHosts,
1030
+ onChange: (host) => setState((prevState) => ({ ...prevState, host })),
1031
+ rawErrors
1032
+ }
1033
+ ), hostType === "github" && /* @__PURE__ */ React.createElement(
1034
+ GithubRepoPicker,
1035
+ {
1036
+ allowedOwners,
1037
+ onChange: updateLocalState,
1038
+ rawErrors,
1039
+ state
1040
+ }
1041
+ ), hostType === "gitlab" && /* @__PURE__ */ React.createElement(
1042
+ GitlabRepoPicker,
1043
+ {
1044
+ allowedOwners,
1045
+ rawErrors,
1046
+ state,
1047
+ onChange: updateLocalState
1048
+ }
1049
+ ), hostType === "bitbucket" && /* @__PURE__ */ React.createElement(
1050
+ BitbucketRepoPicker,
1051
+ {
1052
+ allowedOwners,
1053
+ allowedProjects,
1054
+ rawErrors,
1055
+ state,
1056
+ onChange: updateLocalState
1057
+ }
1058
+ ), hostType === "azure" && /* @__PURE__ */ React.createElement(
1059
+ AzureRepoPicker,
1060
+ {
1061
+ allowedOrganizations,
1062
+ allowedOwners,
1063
+ rawErrors,
1064
+ state,
1065
+ onChange: updateLocalState
1066
+ }
1067
+ ), hostType === "gerrit" && /* @__PURE__ */ React.createElement(
1068
+ GerritRepoPicker,
1069
+ {
1070
+ rawErrors,
1071
+ state,
1072
+ onChange: updateLocalState
1073
+ }
1074
+ ), /* @__PURE__ */ React.createElement(
1075
+ RepoUrlPickerRepoName,
1076
+ {
1077
+ repoName: state.repoName,
1078
+ allowedRepos,
1079
+ onChange: (repo) => setState((prevState) => ({ ...prevState, repoName: repo })),
1080
+ rawErrors
1081
+ }
1082
+ ));
1083
+ };
1084
+
1085
+ const OwnedEntityPickerFieldSchema = makeFieldSchemaFromZod(
1086
+ z.string(),
1087
+ z.object({
1088
+ allowedKinds: z.array(z.string()).optional().describe("List of kinds of entities to derive options from"),
1089
+ defaultKind: z.string().optional().describe(
1090
+ "The default entity kind. Options of this kind will not be prefixed."
1091
+ ),
1092
+ allowArbitraryValues: z.boolean().optional().describe("Whether to allow arbitrary user input. Defaults to true"),
1093
+ defaultNamespace: z.union([z.string(), z.literal(false)]).optional().describe(
1094
+ "The default namespace. Options with this namespace will not be prefixed."
1095
+ )
1096
+ })
1097
+ );
1098
+ const OwnedEntityPickerSchema = OwnedEntityPickerFieldSchema.schema;
1099
+
1100
+ const OwnedEntityPicker = (props) => {
1101
+ var _a, _b, _c, _d, _e;
1102
+ const {
1103
+ onChange,
1104
+ schema: { title = "Entity", description = "An entity from the catalog" },
1105
+ required,
1106
+ uiSchema,
1107
+ rawErrors,
1108
+ formData,
1109
+ idSchema
1110
+ } = props;
1111
+ const allowedKinds = (_a = uiSchema["ui:options"]) == null ? void 0 : _a.allowedKinds;
1112
+ const defaultKind = (_b = uiSchema["ui:options"]) == null ? void 0 : _b.defaultKind;
1113
+ const defaultNamespace = (_c = uiSchema["ui:options"]) == null ? void 0 : _c.defaultNamespace;
1114
+ const allowArbitraryValues = (_e = (_d = uiSchema["ui:options"]) == null ? void 0 : _d.allowArbitraryValues) != null ? _e : true;
1115
+ const { ownedEntities, loading } = useOwnedEntities(allowedKinds);
1116
+ const entityRefs = ownedEntities == null ? void 0 : ownedEntities.items.map((e) => humanizeEntityRef(e, { defaultKind, defaultNamespace })).filter((n) => n);
1117
+ const onSelect = (_, value) => {
1118
+ onChange(value || "");
1119
+ };
1120
+ return /* @__PURE__ */ React.createElement(
1121
+ FormControl,
1122
+ {
1123
+ margin: "normal",
1124
+ required,
1125
+ error: (rawErrors == null ? void 0 : rawErrors.length) > 0 && !formData
1126
+ },
1127
+ /* @__PURE__ */ React.createElement(
1128
+ Autocomplete,
1129
+ {
1130
+ id: idSchema == null ? void 0 : idSchema.$id,
1131
+ value: formData || "",
1132
+ loading,
1133
+ onChange: onSelect,
1134
+ options: entityRefs || [],
1135
+ autoSelect: true,
1136
+ freeSolo: allowArbitraryValues,
1137
+ renderInput: (params) => /* @__PURE__ */ React.createElement(
1138
+ TextField,
1139
+ {
1140
+ ...params,
1141
+ label: title,
1142
+ margin: "normal",
1143
+ helperText: description,
1144
+ variant: "outlined",
1145
+ required,
1146
+ InputProps: params.InputProps
1147
+ }
1148
+ )
1149
+ }
1150
+ )
1151
+ );
1152
+ };
1153
+ function useOwnedEntities(allowedKinds) {
1154
+ const identityApi = useApi(identityApiRef);
1155
+ const catalogApi = useApi(catalogApiRef);
1156
+ const { loading, value: refs } = useAsync(async () => {
1157
+ const identity = await identityApi.getBackstageIdentity();
1158
+ const identityRefs = identity.ownershipEntityRefs;
1159
+ const catalogs = await catalogApi.getEntities(
1160
+ allowedKinds ? {
1161
+ filter: {
1162
+ kind: allowedKinds,
1163
+ [`relations.${RELATION_OWNED_BY}`]: identityRefs || []
1164
+ }
1165
+ } : {
1166
+ filter: {
1167
+ [`relations.${RELATION_OWNED_BY}`]: identityRefs || []
1168
+ }
1169
+ }
1170
+ );
1171
+ return catalogs;
1172
+ }, []);
1173
+ const ownedEntities = useMemo(() => {
1174
+ return refs;
1175
+ }, [refs]);
1176
+ return useMemo(() => ({ loading, ownedEntities }), [loading, ownedEntities]);
1177
+ }
1178
+
1179
+ const EntityTagsPickerFieldSchema = makeFieldSchemaFromZod(
1180
+ z.array(z.string()),
1181
+ z.object({
1182
+ kinds: z.array(z.string()).optional().describe("List of kinds of entities to derive tags from"),
1183
+ showCounts: z.boolean().optional().describe("Whether to show usage counts per tag"),
1184
+ helperText: z.string().optional().describe("Helper text to display")
1185
+ })
1186
+ );
1187
+ const EntityTagsPickerSchema = EntityTagsPickerFieldSchema.schema;
1188
+
1189
+ const EntityTagsPicker = (props) => {
1190
+ var _a, _b, _c;
1191
+ const { formData, onChange, uiSchema } = props;
1192
+ const catalogApi = useApi(catalogApiRef);
1193
+ const [tagOptions, setTagOptions] = useState([]);
1194
+ const [inputValue, setInputValue] = useState("");
1195
+ const [inputError, setInputError] = useState(false);
1196
+ const tagValidator = makeValidator().isValidTag;
1197
+ const kinds = (_a = uiSchema["ui:options"]) == null ? void 0 : _a.kinds;
1198
+ const showCounts = (_b = uiSchema["ui:options"]) == null ? void 0 : _b.showCounts;
1199
+ const helperText = (_c = uiSchema["ui:options"]) == null ? void 0 : _c.helperText;
1200
+ const { loading, value: existingTags } = useAsync(async () => {
1201
+ const facet = "metadata.tags";
1202
+ const tagsRequest = { facets: [facet] };
1203
+ if (kinds) {
1204
+ tagsRequest.filter = { kind: kinds };
1205
+ }
1206
+ const { facets } = await catalogApi.getEntityFacets(tagsRequest);
1207
+ const tagFacets = Object.fromEntries(
1208
+ facets[facet].map(({ value, count }) => [value, count])
1209
+ );
1210
+ setTagOptions(
1211
+ Object.keys(tagFacets).sort(
1212
+ (a, b) => showCounts ? tagFacets[b] - tagFacets[a] : a.localeCompare(b)
1213
+ )
1214
+ );
1215
+ return tagFacets;
1216
+ });
1217
+ const setTags = (_, values) => {
1218
+ let hasError = false;
1219
+ let addDuplicate = false;
1220
+ const currentTags = formData || [];
1221
+ if ((values == null ? void 0 : values.length) && currentTags.length < values.length) {
1222
+ const newTag = values[values.length - 1] = values[values.length - 1].toLocaleLowerCase("en-US").trim();
1223
+ hasError = !tagValidator(newTag);
1224
+ addDuplicate = currentTags.indexOf(newTag) !== -1;
1225
+ }
1226
+ setInputError(hasError);
1227
+ setInputValue(!hasError ? "" : inputValue);
1228
+ if (!hasError && !addDuplicate) {
1229
+ onChange(values || []);
1230
+ }
1231
+ };
1232
+ useEffectOnce(() => onChange(formData || []));
1233
+ return /* @__PURE__ */ React.createElement(FormControl$1, { margin: "normal" }, /* @__PURE__ */ React.createElement(
1234
+ Autocomplete$1,
1235
+ {
1236
+ multiple: true,
1237
+ freeSolo: true,
1238
+ filterSelectedOptions: true,
1239
+ onChange: setTags,
1240
+ value: formData || [],
1241
+ inputValue,
1242
+ loading,
1243
+ options: tagOptions,
1244
+ ChipProps: { size: "small" },
1245
+ renderOption: (option) => showCounts ? `${option} (${existingTags == null ? void 0 : existingTags[option]})` : option,
1246
+ renderInput: (params) => /* @__PURE__ */ React.createElement(
1247
+ TextField,
1248
+ {
1249
+ ...params,
1250
+ label: "Tags",
1251
+ onChange: (e) => setInputValue(e.target.value),
1252
+ error: inputError,
1253
+ helperText: helperText != null ? helperText : "Add any relevant tags, hit 'Enter' to add new tags. Valid format: [a-z0-9+#] separated by [-], at most 63 characters"
1254
+ }
1255
+ )
1256
+ }
1257
+ ));
1258
+ };
1259
+
1260
+ const registerComponentRouteRef = createExternalRouteRef({
1261
+ id: "register-component",
1262
+ optional: true
1263
+ });
1264
+ const viewTechDocRouteRef = createExternalRouteRef({
1265
+ id: "view-techdoc",
1266
+ optional: true,
1267
+ params: ["namespace", "kind", "name"]
1268
+ });
1269
+ const rootRouteRef = createRouteRef({
1270
+ id: "scaffolder"
1271
+ });
1272
+ const legacySelectedTemplateRouteRef = createSubRouteRef({
1273
+ id: "scaffolder/legacy/selected-template",
1274
+ parent: rootRouteRef,
1275
+ path: "/templates/:templateName"
1276
+ });
1277
+ const selectedTemplateRouteRef = createSubRouteRef({
1278
+ id: "scaffolder/selected-template",
1279
+ parent: rootRouteRef,
1280
+ path: "/templates/:namespace/:templateName"
1281
+ });
1282
+ const scaffolderTaskRouteRef = createSubRouteRef({
1283
+ id: "scaffolder/task",
1284
+ parent: rootRouteRef,
1285
+ path: "/tasks/:taskId"
1286
+ });
1287
+ const scaffolderListTaskRouteRef = createSubRouteRef({
1288
+ id: "scaffolder/list-tasks",
1289
+ parent: rootRouteRef,
1290
+ path: "/tasks"
1291
+ });
1292
+ const actionsRouteRef = createSubRouteRef({
1293
+ id: "scaffolder/actions",
1294
+ parent: rootRouteRef,
1295
+ path: "/actions"
1296
+ });
1297
+ const editRouteRef = createSubRouteRef({
1298
+ id: "scaffolder/edit",
1299
+ parent: rootRouteRef,
1300
+ path: "/edit"
1301
+ });
1302
+
1303
+ const nextRouteRef = createRouteRef({
1304
+ id: "scaffolder/next"
1305
+ });
1306
+ const nextSelectedTemplateRouteRef = createSubRouteRef({
1307
+ id: "scaffolder/next/selected-template",
1308
+ parent: nextRouteRef,
1309
+ path: "/templates/:namespace/:templateName"
1310
+ });
1311
+ const nextScaffolderTaskRouteRef = createSubRouteRef({
1312
+ id: "scaffolder/next/task",
1313
+ parent: nextRouteRef,
1314
+ path: "/tasks/:taskId"
1315
+ });
1316
+ const nextScaffolderListTaskRouteRef = createSubRouteRef({
1317
+ id: "scaffolder/next/list-tasks",
1318
+ parent: nextRouteRef,
1319
+ path: "/tasks"
1320
+ });
1321
+ const nextActionsRouteRef = createSubRouteRef({
1322
+ id: "scaffolder/next/actions",
1323
+ parent: nextRouteRef,
1324
+ path: "/actions"
1325
+ });
1326
+ const nextEditRouteRef = createSubRouteRef({
1327
+ id: "scaffolder/next/edit",
1328
+ parent: nextRouteRef,
1329
+ path: "/edit"
1330
+ });
1331
+
1332
+ const useStyles$k = makeStyles({
1333
+ button: {
1334
+ color: "white"
1335
+ }
1336
+ });
1337
+ function ContextMenu$1(props) {
1338
+ const classes = useStyles$k();
1339
+ const [anchorEl, setAnchorEl] = useState();
1340
+ const editLink = useRouteRef(nextEditRouteRef);
1341
+ const actionsLink = useRouteRef(nextActionsRouteRef);
1342
+ const tasksLink = useRouteRef(nextScaffolderListTaskRouteRef);
1343
+ const navigate = useNavigate();
1344
+ const showEditor = props.editor !== false;
1345
+ const showActions = props.actions !== false;
1346
+ const showTasks = props.tasks !== false;
1347
+ if (!showEditor && !showActions) {
1348
+ return null;
1349
+ }
1350
+ const onOpen = (event) => {
1351
+ setAnchorEl(event.currentTarget);
1352
+ };
1353
+ const onClose = () => {
1354
+ setAnchorEl(void 0);
1355
+ };
1356
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
1357
+ IconButton,
1358
+ {
1359
+ "aria-label": "more",
1360
+ "aria-controls": "long-menu",
1361
+ "aria-haspopup": "true",
1362
+ onClick: onOpen,
1363
+ "data-testid": "menu-button",
1364
+ color: "inherit",
1365
+ className: classes.button
1366
+ },
1367
+ /* @__PURE__ */ React.createElement(MoreVert, null)
1368
+ ), /* @__PURE__ */ React.createElement(
1369
+ Popover,
1370
+ {
1371
+ open: Boolean(anchorEl),
1372
+ onClose,
1373
+ anchorEl,
1374
+ anchorOrigin: { vertical: "bottom", horizontal: "right" },
1375
+ transformOrigin: { vertical: "top", horizontal: "right" }
1376
+ },
1377
+ /* @__PURE__ */ React.createElement(MenuList, null, showEditor && /* @__PURE__ */ React.createElement(MenuItem, { onClick: () => navigate(editLink()) }, /* @__PURE__ */ React.createElement(ListItemIcon, null, /* @__PURE__ */ React.createElement(Edit, { fontSize: "small" })), /* @__PURE__ */ React.createElement(ListItemText, { primary: "Template Editor" })), showActions && /* @__PURE__ */ React.createElement(MenuItem, { onClick: () => navigate(actionsLink()) }, /* @__PURE__ */ React.createElement(ListItemIcon, null, /* @__PURE__ */ React.createElement(Description, { fontSize: "small" })), /* @__PURE__ */ React.createElement(ListItemText, { primary: "Installed Actions" })), showTasks && /* @__PURE__ */ React.createElement(MenuItem, { onClick: () => navigate(tasksLink()) }, /* @__PURE__ */ React.createElement(ListItemIcon, null, /* @__PURE__ */ React.createElement(List, { fontSize: "small" })), /* @__PURE__ */ React.createElement(ListItemText, { primary: "Task List" })))
1378
+ ));
1379
+ }
1380
+
1381
+ function reducer(draft, action) {
1382
+ var _a, _b, _c;
1383
+ switch (action.type) {
1384
+ case "INIT": {
1385
+ draft.steps = action.data.spec.steps.reduce((current, next) => {
1386
+ current[next.id] = { status: "open", id: next.id };
1387
+ return current;
1388
+ }, {});
1389
+ draft.stepLogs = action.data.spec.steps.reduce((current, next) => {
1390
+ current[next.id] = [];
1391
+ return current;
1392
+ }, {});
1393
+ draft.loading = false;
1394
+ draft.error = void 0;
1395
+ draft.completed = false;
1396
+ draft.task = action.data;
1397
+ return;
1398
+ }
1399
+ case "LOGS": {
1400
+ const entries = action.data;
1401
+ for (const entry of entries) {
1402
+ const logLine = `${entry.createdAt} ${entry.body.message}`;
1403
+ if (!entry.body.stepId || !((_a = draft.steps) == null ? void 0 : _a[entry.body.stepId])) {
1404
+ continue;
1405
+ }
1406
+ const currentStepLog = (_b = draft.stepLogs) == null ? void 0 : _b[entry.body.stepId];
1407
+ const currentStep = (_c = draft.steps) == null ? void 0 : _c[entry.body.stepId];
1408
+ if (entry.body.status && entry.body.status !== currentStep.status) {
1409
+ currentStep.status = entry.body.status;
1410
+ if (currentStep.status === "processing") {
1411
+ currentStep.startedAt = entry.createdAt;
1412
+ }
1413
+ if (["cancelled", "failed", "completed"].includes(currentStep.status)) {
1414
+ currentStep.endedAt = entry.createdAt;
1415
+ }
1416
+ }
1417
+ currentStepLog == null ? void 0 : currentStepLog.push(logLine);
1418
+ }
1419
+ return;
1420
+ }
1421
+ case "COMPLETED": {
1422
+ draft.completed = true;
1423
+ draft.output = action.data.body.output;
1424
+ draft.error = action.data.body.error;
1425
+ return;
1426
+ }
1427
+ case "ERROR": {
1428
+ draft.error = action.data;
1429
+ draft.loading = false;
1430
+ draft.completed = true;
1431
+ return;
1432
+ }
1433
+ default:
1434
+ return;
1435
+ }
1436
+ }
1437
+ const useTaskEventStream = (taskId) => {
1438
+ const scaffolderApi = useApi(scaffolderApiRef);
1439
+ const [state, dispatch] = useImmerReducer(reducer, {
1440
+ loading: true,
1441
+ completed: false,
1442
+ stepLogs: {},
1443
+ steps: {}
1444
+ });
1445
+ useEffect(() => {
1446
+ let didCancel = false;
1447
+ let subscription;
1448
+ let logPusher;
1449
+ scaffolderApi.getTask(taskId).then(
1450
+ (task) => {
1451
+ if (didCancel) {
1452
+ return;
1453
+ }
1454
+ dispatch({ type: "INIT", data: task });
1455
+ const observable = scaffolderApi.streamLogs({ taskId });
1456
+ const collectedLogEvents = new Array();
1457
+ function emitLogs() {
1458
+ if (collectedLogEvents.length) {
1459
+ const logs = collectedLogEvents.splice(
1460
+ 0,
1461
+ collectedLogEvents.length
1462
+ );
1463
+ dispatch({ type: "LOGS", data: logs });
1464
+ }
1465
+ }
1466
+ logPusher = setInterval(emitLogs, 500);
1467
+ subscription = observable.subscribe({
1468
+ next: (event) => {
1469
+ switch (event.type) {
1470
+ case "log":
1471
+ return collectedLogEvents.push(event);
1472
+ case "completion":
1473
+ emitLogs();
1474
+ dispatch({ type: "COMPLETED", data: event });
1475
+ return void 0;
1476
+ default:
1477
+ throw new Error(
1478
+ `Unhandled event type ${event.type} in observer`
1479
+ );
1480
+ }
1481
+ },
1482
+ error: (error) => {
1483
+ emitLogs();
1484
+ dispatch({ type: "ERROR", data: error });
1485
+ }
1486
+ });
1487
+ },
1488
+ (error) => {
1489
+ if (!didCancel) {
1490
+ dispatch({ type: "ERROR", data: error });
1491
+ }
1492
+ }
1493
+ );
1494
+ return () => {
1495
+ didCancel = true;
1496
+ if (subscription) {
1497
+ subscription.unsubscribe();
1498
+ }
1499
+ if (logPusher) {
1500
+ clearInterval(logPusher);
1501
+ }
1502
+ };
1503
+ }, [scaffolderApi, dispatch, taskId]);
1504
+ return state;
1505
+ };
1506
+
1507
+ const useStepIconStyles$1 = makeStyles$1((theme) => ({
1508
+ root: {
1509
+ color: theme.palette.text.disabled
1510
+ },
1511
+ completed: {
1512
+ color: theme.palette.status.ok
1513
+ },
1514
+ error: {
1515
+ color: theme.palette.status.error
1516
+ }
1517
+ }));
1518
+ const StepIcon = (props) => {
1519
+ const classes = useStepIconStyles$1();
1520
+ const { active, completed, error, skipped } = props;
1521
+ const getMiddle = () => {
1522
+ if (active) {
1523
+ return /* @__PURE__ */ React.createElement(CircularProgress, { size: "20px" });
1524
+ }
1525
+ if (completed) {
1526
+ return /* @__PURE__ */ React.createElement(CheckCircleOutline, null);
1527
+ }
1528
+ if (error) {
1529
+ return /* @__PURE__ */ React.createElement(ErrorOutline, null);
1530
+ }
1531
+ if (skipped) {
1532
+ return /* @__PURE__ */ React.createElement(RemoveCircleOutline, null);
1533
+ }
1534
+ return /* @__PURE__ */ React.createElement(PanoramaFishEyeIcon, null);
1535
+ };
1536
+ return /* @__PURE__ */ React.createElement(
1537
+ "div",
1538
+ {
1539
+ className: classNames(classes.root, {
1540
+ [classes.completed]: completed,
1541
+ [classes.error]: error
1542
+ })
1543
+ },
1544
+ getMiddle()
1545
+ );
1546
+ };
1547
+
1548
+ const StepTime = (props) => {
1549
+ const [time, setTime] = useState("");
1550
+ const { step } = props;
1551
+ const calculate = useCallback(() => {
1552
+ if (!step.startedAt) {
1553
+ setTime("");
1554
+ return;
1555
+ }
1556
+ const end = step.endedAt ? DateTime.fromISO(step.endedAt) : DateTime.local();
1557
+ const startedAt = DateTime.fromISO(step.startedAt);
1558
+ const formatted = Interval.fromDateTimes(startedAt, end).toDuration().valueOf();
1559
+ setTime(humanizeDuration$1(formatted, { round: true }));
1560
+ }, [step.endedAt, step.startedAt]);
1561
+ useMountEffect(() => calculate());
1562
+ useInterval(() => !step.endedAt && calculate(), 1e3);
1563
+ return /* @__PURE__ */ React.createElement(Typography, { variant: "caption" }, time);
1564
+ };
1565
+
1566
+ const TaskSteps = (props) => {
1567
+ return /* @__PURE__ */ React.createElement(
1568
+ Stepper,
1569
+ {
1570
+ activeStep: props.activeStep,
1571
+ alternativeLabel: true,
1572
+ variant: "elevation"
1573
+ },
1574
+ props.steps.map((step, index) => {
1575
+ const isCompleted = step.status === "completed";
1576
+ const isFailed = step.status === "failed";
1577
+ const isActive = step.status === "processing";
1578
+ const isSkipped = step.status === "skipped";
1579
+ const stepIconProps = {
1580
+ completed: isCompleted,
1581
+ error: isFailed,
1582
+ active: isActive,
1583
+ skipped: isSkipped
1584
+ };
1585
+ return /* @__PURE__ */ React.createElement(Step, { key: index }, /* @__PURE__ */ React.createElement(StepButton, null, /* @__PURE__ */ React.createElement(
1586
+ StepLabel,
1587
+ {
1588
+ StepIconProps: stepIconProps,
1589
+ StepIconComponent: StepIcon
1590
+ },
1591
+ /* @__PURE__ */ React.createElement(Box, null, step.name),
1592
+ /* @__PURE__ */ React.createElement(StepTime, { step })
1593
+ )));
1594
+ })
1595
+ );
1596
+ };
1597
+
1598
+ const useStyles$j = makeStyles$1((theme) => ({
1599
+ failed: {
1600
+ backgroundColor: theme.palette.error.main
1601
+ },
1602
+ success: {
1603
+ backgroundColor: theme.palette.success.main
1604
+ }
1605
+ }));
1606
+ const TaskBorder = (props) => {
1607
+ const styles = useStyles$j();
1608
+ if (!props.isComplete) {
1609
+ return /* @__PURE__ */ React.createElement(LinearProgress, { variant: "indeterminate" });
1610
+ }
1611
+ return /* @__PURE__ */ React.createElement(
1612
+ LinearProgress,
1613
+ {
1614
+ variant: "determinate",
1615
+ classes: { bar: props.isError ? styles.failed : styles.success },
1616
+ value: 100
1617
+ }
1618
+ );
1619
+ };
1620
+
1621
+ const useStyles$i = makeStyles({
1622
+ root: {
1623
+ width: "100%",
1624
+ height: "100%",
1625
+ position: "relative"
1626
+ }
1627
+ });
1628
+ const TaskLogStream = (props) => {
1629
+ const styles = useStyles$i();
1630
+ return /* @__PURE__ */ React.createElement("div", { className: styles.root }, /* @__PURE__ */ React.createElement(
1631
+ LogViewer,
1632
+ {
1633
+ text: Object.values(props.logs).map((l) => l.join("\n")).filter(Boolean).join("\n")
1634
+ }
1635
+ ));
1636
+ };
1637
+
1638
+ const useStyles$h = makeStyles$1((theme) => ({
1639
+ button: {
1640
+ color: theme.palette.common.white
1641
+ }
1642
+ }));
1643
+ const ContextMenu = (props) => {
1644
+ const { logsVisible, onToggleLogs, onStartOver } = props;
1645
+ const classes = useStyles$h();
1646
+ const [anchorEl, setAnchorEl] = useState();
1647
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
1648
+ IconButton$1,
1649
+ {
1650
+ "aria-label": "more",
1651
+ "aria-controls": "long-menu",
1652
+ "aria-haspopup": "true",
1653
+ onClick: (event) => {
1654
+ setAnchorEl(event.currentTarget);
1655
+ },
1656
+ "data-testid": "menu-button",
1657
+ color: "inherit",
1658
+ className: classes.button
1659
+ },
1660
+ /* @__PURE__ */ React.createElement(MoreVert, null)
1661
+ ), /* @__PURE__ */ React.createElement(
1662
+ Popover$1,
1663
+ {
1664
+ open: Boolean(anchorEl),
1665
+ onClose: () => setAnchorEl(void 0),
1666
+ anchorEl,
1667
+ anchorOrigin: { vertical: "bottom", horizontal: "right" },
1668
+ transformOrigin: { vertical: "top", horizontal: "right" }
1669
+ },
1670
+ /* @__PURE__ */ React.createElement(MenuList$1, null, /* @__PURE__ */ React.createElement(MenuItem$1, { onClick: () => onToggleLogs == null ? void 0 : onToggleLogs(!logsVisible) }, /* @__PURE__ */ React.createElement(ListItemIcon$1, null, /* @__PURE__ */ React.createElement(Toc, { fontSize: "small" })), /* @__PURE__ */ React.createElement(ListItemText$1, { primary: logsVisible ? "Hide Logs" : "Show Logs" })), /* @__PURE__ */ React.createElement(MenuItem$1, { onClick: onStartOver }, /* @__PURE__ */ React.createElement(ListItemIcon$1, null, /* @__PURE__ */ React.createElement(Retry, { fontSize: "small" })), /* @__PURE__ */ React.createElement(ListItemText$1, { primary: "Start Over" })))
1671
+ ));
1672
+ };
1673
+
1674
+ const useStyles$g = makeStyles$1({
1675
+ contentWrapper: {
1676
+ display: "flex",
1677
+ flexDirection: "column"
1678
+ }
1679
+ });
1680
+ const OngoingTask = (props) => {
1681
+ var _a, _b, _c, _d, _e, _f, _g, _h;
1682
+ const { taskId } = useParams();
1683
+ const templateRouteRef = useRouteRef(nextSelectedTemplateRouteRef);
1684
+ const navigate = useNavigate();
1685
+ const taskStream = useTaskEventStream(taskId);
1686
+ const classes = useStyles$g();
1687
+ const steps = useMemo(
1688
+ () => {
1689
+ var _a2, _b2;
1690
+ return (_b2 = (_a2 = taskStream.task) == null ? void 0 : _a2.spec.steps.map((step) => {
1691
+ var _a3;
1692
+ return {
1693
+ ...step,
1694
+ ...(_a3 = taskStream == null ? void 0 : taskStream.steps) == null ? void 0 : _a3[step.id]
1695
+ };
1696
+ })) != null ? _b2 : [];
1697
+ },
1698
+ [taskStream]
1699
+ );
1700
+ const [logsVisible, setLogVisibleState] = useState(false);
1701
+ useEffect(() => {
1702
+ if (taskStream.error) {
1703
+ setLogVisibleState(true);
1704
+ }
1705
+ }, [taskStream.error]);
1706
+ const activeStep = useMemo(() => {
1707
+ for (let i = steps.length - 1; i >= 0; i--) {
1708
+ if (steps[i].status !== "open") {
1709
+ return i;
1710
+ }
1711
+ }
1712
+ return 0;
1713
+ }, [steps]);
1714
+ const startOver = useCallback(() => {
1715
+ var _a2, _b2, _c2, _d2, _e2, _f2;
1716
+ const { namespace, name } = (_d2 = (_c2 = (_b2 = (_a2 = taskStream.task) == null ? void 0 : _a2.spec.templateInfo) == null ? void 0 : _b2.entity) == null ? void 0 : _c2.metadata) != null ? _d2 : {};
1717
+ const formData = (_f2 = (_e2 = taskStream.task) == null ? void 0 : _e2.spec.parameters) != null ? _f2 : {};
1718
+ if (!namespace || !name) {
1719
+ return;
1720
+ }
1721
+ navigate({
1722
+ pathname: templateRouteRef({
1723
+ namespace,
1724
+ templateName: name
1725
+ }),
1726
+ search: `?${qs.stringify({ formData: JSON.stringify(formData) })}`
1727
+ });
1728
+ }, [
1729
+ navigate,
1730
+ (_a = taskStream.task) == null ? void 0 : _a.spec.parameters,
1731
+ (_d = (_c = (_b = taskStream.task) == null ? void 0 : _b.spec.templateInfo) == null ? void 0 : _c.entity) == null ? void 0 : _d.metadata,
1732
+ templateRouteRef
1733
+ ]);
1734
+ const Outputs = (_e = props.TemplateOutputsComponent) != null ? _e : DefaultTemplateOutputs;
1735
+ const templateName = (_h = (_g = (_f = taskStream.task) == null ? void 0 : _f.spec.templateInfo) == null ? void 0 : _g.entity) == null ? void 0 : _h.metadata.name;
1736
+ return /* @__PURE__ */ React.createElement(Page, { themeId: "website" }, /* @__PURE__ */ React.createElement(
1737
+ Header,
1738
+ {
1739
+ pageTitleOverride: `Run of ${templateName}`,
1740
+ title: /* @__PURE__ */ React.createElement("div", null, "Run of ", /* @__PURE__ */ React.createElement("code", null, templateName)),
1741
+ subtitle: `Task ${taskId}`
1742
+ },
1743
+ /* @__PURE__ */ React.createElement(
1744
+ ContextMenu,
1745
+ {
1746
+ onToggleLogs: setLogVisibleState,
1747
+ onStartOver: startOver,
1748
+ logsVisible
1749
+ }
1750
+ )
1751
+ ), /* @__PURE__ */ React.createElement(Content, { className: classes.contentWrapper }, taskStream.error ? /* @__PURE__ */ React.createElement(Box, { paddingBottom: 2 }, /* @__PURE__ */ React.createElement(
1752
+ ErrorPanel,
1753
+ {
1754
+ error: taskStream.error,
1755
+ title: taskStream.error.message
1756
+ }
1757
+ )) : null, /* @__PURE__ */ React.createElement(Box, { paddingBottom: 2 }, /* @__PURE__ */ React.createElement(Paper, { style: { position: "relative", overflow: "hidden" } }, /* @__PURE__ */ React.createElement(
1758
+ TaskBorder,
1759
+ {
1760
+ isComplete: taskStream.completed,
1761
+ isError: Boolean(taskStream.error)
1762
+ }
1763
+ ), /* @__PURE__ */ React.createElement(Box, { padding: 2 }, /* @__PURE__ */ React.createElement(TaskSteps, { steps, activeStep })))), /* @__PURE__ */ React.createElement(Outputs, { output: taskStream.output }), logsVisible ? /* @__PURE__ */ React.createElement(Box, { paddingBottom: 2, height: "100%" }, /* @__PURE__ */ React.createElement(Paper, { style: { height: "100%" } }, /* @__PURE__ */ React.createElement(Box, { padding: 2, height: "100%" }, /* @__PURE__ */ React.createElement(TaskLogStream, { logs: taskStream.stepLogs })))) : null));
1764
+ };
1765
+
1766
+ const useStyles$f = makeStyles$1((theme) => ({
1767
+ code: {
1768
+ fontFamily: "Menlo, monospace",
1769
+ padding: theme.spacing(1),
1770
+ backgroundColor: theme.palette.type === "dark" ? theme.palette.grey[700] : theme.palette.grey[300],
1771
+ display: "inline-block",
1772
+ borderRadius: 5,
1773
+ border: `1px solid ${theme.palette.grey[500]}`,
1774
+ position: "relative"
1775
+ },
1776
+ codeRequired: {
1777
+ "&::after": {
1778
+ position: "absolute",
1779
+ content: '"*"',
1780
+ top: 0,
1781
+ right: theme.spacing(0.5),
1782
+ fontWeight: "bolder",
1783
+ color: theme.palette.error.light
1784
+ }
1785
+ }
1786
+ }));
1787
+ const ExamplesTable = (props) => {
1788
+ return /* @__PURE__ */ React.createElement(Grid, { container: true }, props.examples.map((example, index) => {
1789
+ return /* @__PURE__ */ React.createElement(Fragment, { key: `example-${index}` }, /* @__PURE__ */ React.createElement(Grid, { item: true, lg: 3 }, /* @__PURE__ */ React.createElement(Box, { padding: 4 }, /* @__PURE__ */ React.createElement(Typography, null, example.description))), /* @__PURE__ */ React.createElement(Grid, { item: true, lg: 9 }, /* @__PURE__ */ React.createElement(Box, { padding: 1 }, /* @__PURE__ */ React.createElement(
1790
+ CodeSnippet,
1791
+ {
1792
+ text: example.example,
1793
+ showLineNumbers: true,
1794
+ showCopyCodeButton: true,
1795
+ language: "yaml"
1796
+ }
1797
+ ))));
1798
+ }));
1799
+ };
1800
+ const ActionsPage = () => {
1801
+ const api = useApi(scaffolderApiRef);
1802
+ const classes = useStyles$f();
1803
+ const { loading, value, error } = useAsync(async () => {
1804
+ return api.listActions();
1805
+ });
1806
+ if (loading) {
1807
+ return /* @__PURE__ */ React.createElement(Progress, null);
1808
+ }
1809
+ if (error) {
1810
+ return /* @__PURE__ */ React.createElement(
1811
+ ErrorPage,
1812
+ {
1813
+ statusMessage: "Failed to load installed actions",
1814
+ status: "500"
1815
+ }
1816
+ );
1817
+ }
1818
+ const formatRows = (input) => {
1819
+ const properties = input.properties;
1820
+ if (!properties) {
1821
+ return void 0;
1822
+ }
1823
+ return Object.entries(properties).map((entry) => {
1824
+ var _a;
1825
+ const [key] = entry;
1826
+ const props = entry[1];
1827
+ const codeClassname = classNames(classes.code, {
1828
+ [classes.codeRequired]: (_a = input.required) == null ? void 0 : _a.includes(key)
1829
+ });
1830
+ return /* @__PURE__ */ React.createElement(TableRow, { key }, /* @__PURE__ */ React.createElement(TableCell, null, /* @__PURE__ */ React.createElement("div", { className: codeClassname }, key)), /* @__PURE__ */ React.createElement(TableCell, null, props.title), /* @__PURE__ */ React.createElement(TableCell, null, props.description), /* @__PURE__ */ React.createElement(TableCell, null, /* @__PURE__ */ React.createElement(React.Fragment, null, [props.type].flat().map((type) => /* @__PURE__ */ React.createElement(Chip, { label: type, key: type })))));
1831
+ });
1832
+ };
1833
+ const renderTable = (input) => {
1834
+ if (!input.properties) {
1835
+ return void 0;
1836
+ }
1837
+ return /* @__PURE__ */ React.createElement(TableContainer, { component: Paper }, /* @__PURE__ */ React.createElement(Table, { size: "small" }, /* @__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))));
1838
+ };
1839
+ const renderTables = (name, input) => {
1840
+ if (!input) {
1841
+ return void 0;
1842
+ }
1843
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Typography, { variant: "h6" }, name), input.map((i, index) => /* @__PURE__ */ React.createElement("div", { key: index }, renderTable(i))));
1844
+ };
1845
+ const items = value == null ? void 0 : value.map((action) => {
1846
+ var _a, _b, _c, _d;
1847
+ if (action.id.startsWith("legacy:")) {
1848
+ return void 0;
1849
+ }
1850
+ const oneOf = renderTables("oneOf", (_b = (_a = action.schema) == null ? void 0 : _a.input) == null ? void 0 : _b.oneOf);
1851
+ return /* @__PURE__ */ React.createElement(Box, { pb: 4, key: action.id }, /* @__PURE__ */ React.createElement(Typography, { variant: "h4", className: classes.code }, action.id), action.description && /* @__PURE__ */ React.createElement(MarkdownContent, { content: action.description }), ((_c = action.schema) == null ? void 0 : _c.input) && /* @__PURE__ */ React.createElement(Box, { pb: 2 }, /* @__PURE__ */ React.createElement(Typography, { variant: "h5" }, "Input"), renderTable(action.schema.input), oneOf), ((_d = action.schema) == null ? void 0 : _d.output) && /* @__PURE__ */ React.createElement(Box, { pb: 2 }, /* @__PURE__ */ React.createElement(Typography, { variant: "h5" }, "Output"), renderTable(action.schema.output)), action.examples && /* @__PURE__ */ React.createElement(Accordion, null, /* @__PURE__ */ React.createElement(AccordionSummary, { expandIcon: /* @__PURE__ */ React.createElement(ExpandMoreIcon, null) }, /* @__PURE__ */ React.createElement(Typography, { variant: "h5" }, "Examples")), /* @__PURE__ */ React.createElement(AccordionDetails, null, /* @__PURE__ */ React.createElement(Box, { pb: 2 }, /* @__PURE__ */ React.createElement(ExamplesTable, { examples: action.examples })))));
1852
+ });
1853
+ return /* @__PURE__ */ React.createElement(Page, { themeId: "home" }, /* @__PURE__ */ React.createElement(
1854
+ Header,
1855
+ {
1856
+ pageTitleOverride: "Create a New Component",
1857
+ title: "Installed actions",
1858
+ subtitle: "This is the collection of all installed actions"
1859
+ }
1860
+ ), /* @__PURE__ */ React.createElement(Content, null, items));
1861
+ };
1862
+
1863
+ const useStyles$e = makeStyles$1(
1864
+ (theme) => ({
1865
+ root: {
1866
+ backgroundColor: "rgba(0, 0, 0, .11)",
1867
+ boxShadow: "none",
1868
+ margin: theme.spacing(1, 0, 1, 0)
1869
+ },
1870
+ title: {
1871
+ margin: theme.spacing(1, 0, 0, 1),
1872
+ textTransform: "uppercase",
1873
+ fontSize: 12,
1874
+ fontWeight: "bold"
1875
+ },
1876
+ listIcon: {
1877
+ minWidth: 30,
1878
+ color: theme.palette.text.primary
1879
+ },
1880
+ menuItem: {
1881
+ minHeight: theme.spacing(6)
1882
+ },
1883
+ groupWrapper: {
1884
+ margin: theme.spacing(1, 1, 2, 1)
1885
+ }
1886
+ }),
1887
+ {
1888
+ name: "ScaffolderReactOwnerListPicker"
1889
+ }
1890
+ );
1891
+ function getFilterGroups() {
1892
+ return [
1893
+ {
1894
+ name: "Task Owner",
1895
+ items: [
1896
+ {
1897
+ id: "owned",
1898
+ label: "Owned",
1899
+ icon: SettingsIcon
1900
+ },
1901
+ {
1902
+ id: "all",
1903
+ label: "All",
1904
+ icon: AllIcon
1905
+ }
1906
+ ]
1907
+ }
1908
+ ];
1909
+ }
1910
+ const OwnerListPicker = (props) => {
1911
+ const { filter, onSelectOwner } = props;
1912
+ const classes = useStyles$e();
1913
+ const filterGroups = getFilterGroups();
1914
+ return /* @__PURE__ */ React.createElement(Card, { className: classes.root }, filterGroups.map((group) => /* @__PURE__ */ React.createElement(Fragment, { key: group.name }, /* @__PURE__ */ React.createElement(Typography, { variant: "subtitle2", className: classes.title }, group.name), /* @__PURE__ */ React.createElement(Card, { className: classes.groupWrapper }, /* @__PURE__ */ React.createElement(List$1, { disablePadding: true, dense: true }, group.items.map((item) => /* @__PURE__ */ React.createElement(
1915
+ MenuItem$1,
1916
+ {
1917
+ key: item.id,
1918
+ button: true,
1919
+ divider: true,
1920
+ onClick: () => onSelectOwner(item.id),
1921
+ selected: item.id === filter,
1922
+ className: classes.menuItem,
1923
+ "data-testid": `owner-picker-${item.id}`
1924
+ },
1925
+ item.icon && /* @__PURE__ */ React.createElement(ListItemIcon$1, { className: classes.listIcon }, /* @__PURE__ */ React.createElement(item.icon, { fontSize: "small" })),
1926
+ /* @__PURE__ */ React.createElement(ListItemText$1, null, /* @__PURE__ */ React.createElement(Typography, { variant: "body1" }, item.label))
1927
+ )))))));
1928
+ };
1929
+
1930
+ const showDirectoryPicker = window.showDirectoryPicker;
1931
+ class WebFileAccess {
1932
+ constructor(path, handle) {
1933
+ this.path = path;
1934
+ this.handle = handle;
1935
+ }
1936
+ file() {
1937
+ return this.handle.getFile();
1938
+ }
1939
+ async save(data) {
1940
+ const writable = await this.handle.createWritable();
1941
+ await writable.write(data);
1942
+ await writable.close();
1943
+ }
1944
+ }
1945
+ class WebDirectoryAccess {
1946
+ constructor(handle) {
1947
+ this.handle = handle;
1948
+ }
1949
+ async listFiles() {
1950
+ const content = [];
1951
+ for await (const entry of this.listDirectoryContents(this.handle)) {
1952
+ content.push(entry);
1953
+ }
1954
+ return content;
1955
+ }
1956
+ async *listDirectoryContents(dirHandle, basePath = []) {
1957
+ for await (const handle of dirHandle.values()) {
1958
+ if (handle.kind === "file") {
1959
+ yield new WebFileAccess([...basePath, handle.name].join("/"), handle);
1960
+ } else if (handle.kind === "directory") {
1961
+ if (handle.name === ".git") {
1962
+ continue;
1963
+ }
1964
+ yield* this.listDirectoryContents(handle, [...basePath, handle.name]);
1965
+ }
1966
+ }
1967
+ }
1968
+ }
1969
+ class WebFileSystemAccess {
1970
+ static isSupported() {
1971
+ return Boolean(showDirectoryPicker);
1972
+ }
1973
+ static async requestDirectoryAccess() {
1974
+ if (!showDirectoryPicker) {
1975
+ throw new Error("File system access is not supported");
1976
+ }
1977
+ const handle = await showDirectoryPicker();
1978
+ return new WebDirectoryAccess(handle);
1979
+ }
1980
+ constructor() {
1981
+ }
1982
+ }
1983
+
1984
+ const MAX_CONTENT_SIZE = 64 * 1024;
1985
+ const CHUNK_SIZE = 32 * 1024;
1986
+ const DryRunContext = createContext(void 0);
1987
+ function base64EncodeContent(content) {
1988
+ if (content.length > MAX_CONTENT_SIZE) {
1989
+ return window.btoa("<file too large>");
1990
+ }
1991
+ try {
1992
+ return window.btoa(content);
1993
+ } catch {
1994
+ const decoder = new TextEncoder();
1995
+ const buffer = decoder.encode(content);
1996
+ const chunks = new Array();
1997
+ for (let offset = 0; offset < buffer.length; offset += CHUNK_SIZE) {
1998
+ chunks.push(
1999
+ String.fromCharCode(...buffer.slice(offset, offset + CHUNK_SIZE))
2000
+ );
2001
+ }
2002
+ return window.btoa(chunks.join(""));
2003
+ }
2004
+ }
2005
+ function DryRunProvider(props) {
2006
+ const scaffolderApi = useApi(scaffolderApiRef);
2007
+ const [state, setState] = useState({
2008
+ results: [],
2009
+ selectedResult: void 0
2010
+ });
2011
+ const idRef = useRef(1);
2012
+ const selectResult = useCallback((id) => {
2013
+ setState((prevState) => {
2014
+ const result = prevState.results.find((r) => r.id === id);
2015
+ if (result === prevState.selectedResult) {
2016
+ return prevState;
2017
+ }
2018
+ return {
2019
+ results: prevState.results,
2020
+ selectedResult: result
2021
+ };
2022
+ });
2023
+ }, []);
2024
+ const deleteResult = useCallback((id) => {
2025
+ setState((prevState) => {
2026
+ var _a;
2027
+ const index = prevState.results.findIndex((r) => r.id === id);
2028
+ if (index === -1) {
2029
+ return prevState;
2030
+ }
2031
+ const newResults = prevState.results.slice();
2032
+ const [deleted] = newResults.splice(index, 1);
2033
+ return {
2034
+ results: newResults,
2035
+ selectedResult: ((_a = prevState.selectedResult) == null ? void 0 : _a.id) === deleted.id ? newResults[0] : prevState.selectedResult
2036
+ };
2037
+ });
2038
+ }, []);
2039
+ const execute = useCallback(
2040
+ async (options) => {
2041
+ if (!scaffolderApi.dryRun) {
2042
+ throw new Error("Scaffolder API does not support dry-run");
2043
+ }
2044
+ const parsed = yaml.parse(options.templateContent);
2045
+ const response = await scaffolderApi.dryRun({
2046
+ template: parsed,
2047
+ values: options.values,
2048
+ secrets: {},
2049
+ directoryContents: options.files.map((file) => ({
2050
+ path: file.path,
2051
+ base64Content: base64EncodeContent(file.content)
2052
+ }))
2053
+ });
2054
+ const result = {
2055
+ ...response,
2056
+ id: idRef.current++
2057
+ };
2058
+ setState((prevState) => {
2059
+ var _a;
2060
+ return {
2061
+ results: [...prevState.results, result],
2062
+ selectedResult: (_a = prevState.selectedResult) != null ? _a : result
2063
+ };
2064
+ });
2065
+ },
2066
+ [scaffolderApi]
2067
+ );
2068
+ const dryRun = useMemo(
2069
+ () => ({
2070
+ ...state,
2071
+ selectResult,
2072
+ deleteResult,
2073
+ execute
2074
+ }),
2075
+ [state, selectResult, deleteResult, execute]
2076
+ );
2077
+ return /* @__PURE__ */ React.createElement(DryRunContext.Provider, { value: dryRun }, props.children);
2078
+ }
2079
+ function useDryRun() {
2080
+ const value = useContext(DryRunContext);
2081
+ if (!value) {
2082
+ throw new Error("must be used within a DryRunProvider");
2083
+ }
2084
+ return value;
2085
+ }
2086
+
2087
+ var __accessCheck = (obj, member, msg) => {
2088
+ if (!member.has(obj))
2089
+ throw TypeError("Cannot " + msg);
2090
+ };
2091
+ var __privateGet = (obj, member, getter) => {
2092
+ __accessCheck(obj, member, "read from private field");
2093
+ return getter ? getter.call(obj) : member.get(obj);
2094
+ };
2095
+ var __privateAdd = (obj, member, value) => {
2096
+ if (member.has(obj))
2097
+ throw TypeError("Cannot add the same private member more than once");
2098
+ member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
2099
+ };
2100
+ var __privateSet = (obj, member, value, setter) => {
2101
+ __accessCheck(obj, member, "write to private field");
2102
+ setter ? setter.call(obj, value) : member.set(obj, value);
2103
+ return value;
2104
+ };
2105
+ var _access, _signalUpdate, _content, _savedContent, _access2, _listeners, _files, _selectedFile, _signalUpdate2;
2106
+ const MAX_SIZE = 1024 * 1024;
2107
+ const MAX_SIZE_MESSAGE = "This file is too large to be displayed";
2108
+ class DirectoryEditorFileManager {
2109
+ constructor(access, signalUpdate) {
2110
+ __privateAdd(this, _access, void 0);
2111
+ __privateAdd(this, _signalUpdate, void 0);
2112
+ __privateAdd(this, _content, void 0);
2113
+ __privateAdd(this, _savedContent, void 0);
2114
+ __privateSet(this, _access, access);
2115
+ __privateSet(this, _signalUpdate, signalUpdate);
2116
+ }
2117
+ get path() {
2118
+ return __privateGet(this, _access).path;
2119
+ }
2120
+ get content() {
2121
+ var _a;
2122
+ return (_a = __privateGet(this, _content)) != null ? _a : MAX_SIZE_MESSAGE;
2123
+ }
2124
+ updateContent(content) {
2125
+ if (__privateGet(this, _content) === void 0) {
2126
+ return;
2127
+ }
2128
+ __privateSet(this, _content, content);
2129
+ __privateGet(this, _signalUpdate).call(this);
2130
+ }
2131
+ get dirty() {
2132
+ return __privateGet(this, _content) !== __privateGet(this, _savedContent);
2133
+ }
2134
+ async save() {
2135
+ if (__privateGet(this, _content) !== void 0) {
2136
+ await __privateGet(this, _access).save(__privateGet(this, _content));
2137
+ __privateSet(this, _savedContent, __privateGet(this, _content));
2138
+ __privateGet(this, _signalUpdate).call(this);
2139
+ }
2140
+ }
2141
+ async reload() {
2142
+ const file = await __privateGet(this, _access).file();
2143
+ if (file.size > MAX_SIZE) {
2144
+ if (__privateGet(this, _content) !== void 0) {
2145
+ __privateSet(this, _content, void 0);
2146
+ __privateSet(this, _savedContent, void 0);
2147
+ __privateGet(this, _signalUpdate).call(this);
2148
+ }
2149
+ return;
2150
+ }
2151
+ const content = await file.text();
2152
+ if (__privateGet(this, _content) !== content) {
2153
+ __privateSet(this, _content, content);
2154
+ __privateSet(this, _savedContent, content);
2155
+ __privateGet(this, _signalUpdate).call(this);
2156
+ }
2157
+ }
2158
+ }
2159
+ _access = new WeakMap();
2160
+ _signalUpdate = new WeakMap();
2161
+ _content = new WeakMap();
2162
+ _savedContent = new WeakMap();
2163
+ class DirectoryEditorManager {
2164
+ constructor(access) {
2165
+ __privateAdd(this, _access2, void 0);
2166
+ __privateAdd(this, _listeners, /* @__PURE__ */ new Set());
2167
+ __privateAdd(this, _files, []);
2168
+ __privateAdd(this, _selectedFile, void 0);
2169
+ this.setSelectedFile = (path) => {
2170
+ const prev = __privateGet(this, _selectedFile);
2171
+ const next = __privateGet(this, _files).find((file) => file.path === path);
2172
+ if (prev !== next) {
2173
+ __privateSet(this, _selectedFile, next);
2174
+ __privateGet(this, _signalUpdate2).call(this);
2175
+ }
2176
+ };
2177
+ __privateAdd(this, _signalUpdate2, () => {
2178
+ __privateGet(this, _listeners).forEach((listener) => listener());
2179
+ });
2180
+ __privateSet(this, _access2, access);
2181
+ }
2182
+ get files() {
2183
+ return __privateGet(this, _files);
2184
+ }
2185
+ get selectedFile() {
2186
+ return __privateGet(this, _selectedFile);
2187
+ }
2188
+ get dirty() {
2189
+ return __privateGet(this, _files).some((file) => file.dirty);
2190
+ }
2191
+ async save() {
2192
+ await Promise.all(__privateGet(this, _files).map((file) => file.save()));
2193
+ }
2194
+ async reload() {
2195
+ var _a;
2196
+ const selectedPath = (_a = __privateGet(this, _selectedFile)) == null ? void 0 : _a.path;
2197
+ const files = await __privateGet(this, _access2).listFiles();
2198
+ const fileManagers = await Promise.all(
2199
+ files.map(async (file) => {
2200
+ const manager = new DirectoryEditorFileManager(
2201
+ file,
2202
+ __privateGet(this, _signalUpdate2)
2203
+ );
2204
+ await manager.reload();
2205
+ return manager;
2206
+ })
2207
+ );
2208
+ __privateGet(this, _files).length = 0;
2209
+ __privateGet(this, _files).push(...fileManagers);
2210
+ this.setSelectedFile(selectedPath);
2211
+ __privateGet(this, _signalUpdate2).call(this);
2212
+ }
2213
+ subscribe(listener) {
2214
+ __privateGet(this, _listeners).add(listener);
2215
+ return () => {
2216
+ __privateGet(this, _listeners).delete(listener);
2217
+ };
2218
+ }
2219
+ }
2220
+ _access2 = new WeakMap();
2221
+ _listeners = new WeakMap();
2222
+ _files = new WeakMap();
2223
+ _selectedFile = new WeakMap();
2224
+ _signalUpdate2 = new WeakMap();
2225
+ const DirectoryEditorContext = createContext(
2226
+ void 0
2227
+ );
2228
+ function useDirectoryEditor() {
2229
+ const value = useContext(DirectoryEditorContext);
2230
+ const rerender = useRerender();
2231
+ useEffect(() => value == null ? void 0 : value.subscribe(rerender), [value, rerender]);
2232
+ if (!value) {
2233
+ throw new Error("must be used within a DirectoryEditorProvider");
2234
+ }
2235
+ return value;
2236
+ }
2237
+ function DirectoryEditorProvider(props) {
2238
+ const { directory } = props;
2239
+ const [{ result, error }, { execute }] = useAsync$1(
2240
+ async (dir) => {
2241
+ const manager = new DirectoryEditorManager(dir);
2242
+ await manager.reload();
2243
+ const firstYaml = manager.files.find((file) => file.path.match(/\.ya?ml$/));
2244
+ if (firstYaml) {
2245
+ manager.setSelectedFile(firstYaml.path);
2246
+ }
2247
+ return manager;
2248
+ }
2249
+ );
2250
+ useEffect(() => {
2251
+ execute(directory);
2252
+ }, [execute, directory]);
2253
+ if (error) {
2254
+ return /* @__PURE__ */ React.createElement(ErrorPanel, { error });
2255
+ } else if (!result) {
2256
+ return /* @__PURE__ */ React.createElement(Progress, null);
2257
+ }
2258
+ return /* @__PURE__ */ React.createElement(DirectoryEditorContext.Provider, { value: result }, props.children);
2259
+ }
2260
+
2261
+ const useStyles$d = makeStyles({
2262
+ containerWrapper: {
2263
+ position: "relative",
2264
+ width: "100%",
2265
+ height: "100%"
2266
+ },
2267
+ container: {
2268
+ position: "absolute",
2269
+ top: 0,
2270
+ bottom: 0,
2271
+ left: 0,
2272
+ right: 0,
2273
+ overflow: "auto"
2274
+ }
2275
+ });
2276
+ class ErrorBoundary extends Component {
2277
+ constructor() {
2278
+ super(...arguments);
2279
+ this.state = {
2280
+ shouldRender: true
2281
+ };
2282
+ }
2283
+ componentDidUpdate(prevProps) {
2284
+ if (prevProps.invalidator !== this.props.invalidator) {
2285
+ this.setState({ shouldRender: true });
2286
+ }
2287
+ }
2288
+ componentDidCatch(error) {
2289
+ this.props.setErrorText(error.message);
2290
+ this.setState({ shouldRender: false });
2291
+ }
2292
+ render() {
2293
+ return this.state.shouldRender ? this.props.children : null;
2294
+ }
2295
+ }
2296
+ function isJsonObject(value) {
2297
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2298
+ }
2299
+ function TemplateEditorForm(props) {
2300
+ const {
2301
+ content,
2302
+ contentIsSpec,
2303
+ onDryRun,
2304
+ setErrorText,
2305
+ fieldExtensions = [],
2306
+ layouts = []
2307
+ } = props;
2308
+ const classes = useStyles$d();
2309
+ const apiHolder = useApiHolder();
2310
+ const [steps, setSteps] = useState();
2311
+ useDebounce(
2312
+ () => {
2313
+ try {
2314
+ if (!content) {
2315
+ setSteps(void 0);
2316
+ return;
2317
+ }
2318
+ const parsed = yaml.parse(content);
2319
+ if (!isJsonObject(parsed)) {
2320
+ setSteps(void 0);
2321
+ return;
2322
+ }
2323
+ let rootObj = parsed;
2324
+ if (!contentIsSpec) {
2325
+ const isTemplate = String(parsed.kind).toLocaleLowerCase("en-US") === "template";
2326
+ if (!isTemplate) {
2327
+ setSteps(void 0);
2328
+ return;
2329
+ }
2330
+ rootObj = isJsonObject(parsed.spec) ? parsed.spec : {};
2331
+ }
2332
+ const { parameters } = rootObj;
2333
+ if (!Array.isArray(parameters)) {
2334
+ setErrorText("Template parameters must be an array");
2335
+ setSteps(void 0);
2336
+ return;
2337
+ }
2338
+ setErrorText();
2339
+ setSteps(
2340
+ parameters.flatMap(
2341
+ (param) => isJsonObject(param) ? [
2342
+ {
2343
+ title: String(param.title),
2344
+ schema: param
2345
+ }
2346
+ ] : []
2347
+ )
2348
+ );
2349
+ } catch (e) {
2350
+ setErrorText(e.message);
2351
+ }
2352
+ },
2353
+ 250,
2354
+ [contentIsSpec, content, apiHolder]
2355
+ );
2356
+ if (!steps) {
2357
+ return null;
2358
+ }
2359
+ return /* @__PURE__ */ React.createElement("div", { className: classes.containerWrapper }, /* @__PURE__ */ React.createElement("div", { className: classes.container }, /* @__PURE__ */ React.createElement(ErrorBoundary, { invalidator: steps, setErrorText }, /* @__PURE__ */ React.createElement(
2360
+ Stepper$1,
2361
+ {
2362
+ manifest: { steps, title: "Template Editor" },
2363
+ extensions: fieldExtensions,
2364
+ onCreate: async (data) => {
2365
+ await (onDryRun == null ? void 0 : onDryRun(data));
2366
+ },
2367
+ layouts,
2368
+ components: { createButtonText: onDryRun && "Try It" }
2369
+ }
2370
+ ))));
2371
+ }
2372
+ function TemplateEditorFormDirectoryEditorDryRun(props) {
2373
+ const { setErrorText, fieldExtensions = [], layouts } = props;
2374
+ const dryRun = useDryRun();
2375
+ const directoryEditor = useDirectoryEditor();
2376
+ const { selectedFile } = directoryEditor;
2377
+ const handleDryRun = async (values) => {
2378
+ if (!selectedFile) {
2379
+ return;
2380
+ }
2381
+ try {
2382
+ await dryRun.execute({
2383
+ templateContent: selectedFile.content,
2384
+ values,
2385
+ files: directoryEditor.files
2386
+ });
2387
+ setErrorText();
2388
+ } catch (e) {
2389
+ setErrorText(String(e.cause || e));
2390
+ throw e;
2391
+ }
2392
+ };
2393
+ const content = selectedFile && selectedFile.path.match(/\.ya?ml$/) ? selectedFile.content : void 0;
2394
+ return /* @__PURE__ */ React.createElement(
2395
+ TemplateEditorForm,
2396
+ {
2397
+ onDryRun: handleDryRun,
2398
+ fieldExtensions,
2399
+ setErrorText,
2400
+ content,
2401
+ layouts
2402
+ }
2403
+ );
2404
+ }
2405
+ TemplateEditorForm.DirectoryEditorDryRun = TemplateEditorFormDirectoryEditorDryRun;
2406
+
2407
+ const useStyles$c = makeStyles$1((theme) => ({
2408
+ root: {
2409
+ gridArea: "pageContent",
2410
+ display: "grid",
2411
+ gridTemplateAreas: `
2412
+ "controls controls"
2413
+ "fieldForm preview"
2414
+ `,
2415
+ gridTemplateRows: "auto 1fr",
2416
+ gridTemplateColumns: "1fr 1fr"
2417
+ },
2418
+ controls: {
2419
+ gridArea: "controls",
2420
+ display: "flex",
2421
+ flexFlow: "row nowrap",
2422
+ alignItems: "center",
2423
+ margin: theme.spacing(1)
2424
+ },
2425
+ fieldForm: {
2426
+ gridArea: "fieldForm"
2427
+ },
2428
+ preview: {
2429
+ gridArea: "preview"
2430
+ }
2431
+ }));
2432
+ const CustomFieldExplorer = ({
2433
+ customFieldExtensions = [],
2434
+ onClose
2435
+ }) => {
2436
+ var _a, _b;
2437
+ const classes = useStyles$c();
2438
+ const fieldOptions = customFieldExtensions.filter((field) => !!field.schema);
2439
+ const [selectedField, setSelectedField] = useState(fieldOptions[0]);
2440
+ const [fieldFormState, setFieldFormState] = useState({});
2441
+ const [refreshKey, setRefreshKey] = useState(Date.now());
2442
+ const sampleFieldTemplate = useMemo(
2443
+ () => {
2444
+ var _a2, _b2;
2445
+ return yaml.stringify({
2446
+ parameters: [
2447
+ {
2448
+ title: `${selectedField.name} Example`,
2449
+ properties: {
2450
+ [selectedField.name]: {
2451
+ type: (_b2 = (_a2 = selectedField.schema) == null ? void 0 : _a2.returnValue) == null ? void 0 : _b2.type,
2452
+ "ui:field": selectedField.name,
2453
+ "ui:options": fieldFormState
2454
+ }
2455
+ }
2456
+ }
2457
+ ]
2458
+ });
2459
+ },
2460
+ [fieldFormState, selectedField]
2461
+ );
2462
+ const fieldComponents = useMemo(() => {
2463
+ return Object.fromEntries(
2464
+ customFieldExtensions.map(({ name, component }) => [name, component])
2465
+ );
2466
+ }, [customFieldExtensions]);
2467
+ const handleSelectionChange = useCallback(
2468
+ (selection) => {
2469
+ setSelectedField(selection);
2470
+ setFieldFormState({});
2471
+ },
2472
+ [setFieldFormState, setSelectedField]
2473
+ );
2474
+ const handleFieldConfigChange = useCallback(
2475
+ (state) => {
2476
+ setFieldFormState(state);
2477
+ setRefreshKey(Date.now());
2478
+ },
2479
+ [setFieldFormState, setRefreshKey]
2480
+ );
2481
+ return /* @__PURE__ */ React.createElement("main", { className: classes.root }, /* @__PURE__ */ React.createElement("div", { className: classes.controls }, /* @__PURE__ */ React.createElement(FormControl$1, { variant: "outlined", size: "small", fullWidth: true }, /* @__PURE__ */ React.createElement(InputLabel$1, { id: "select-field-label" }, "Choose Custom Field Extension"), /* @__PURE__ */ React.createElement(
2482
+ Select$1,
2483
+ {
2484
+ value: selectedField,
2485
+ label: "Choose Custom Field Extension",
2486
+ labelId: "select-field-label",
2487
+ onChange: (e) => handleSelectionChange(e.target.value)
2488
+ },
2489
+ fieldOptions.map((option, idx) => /* @__PURE__ */ React.createElement(MenuItem$1, { key: idx, value: option }, option.name))
2490
+ )), /* @__PURE__ */ React.createElement(IconButton$1, { size: "medium", onClick: onClose }, /* @__PURE__ */ React.createElement(CloseIcon, null))), /* @__PURE__ */ React.createElement("div", { className: classes.fieldForm }, /* @__PURE__ */ React.createElement(Card, null, /* @__PURE__ */ React.createElement(CardHeader, { title: "Field Options" }), /* @__PURE__ */ React.createElement(CardContent, null, /* @__PURE__ */ React.createElement(
2491
+ Form,
2492
+ {
2493
+ showErrorList: false,
2494
+ fields: { ...fieldComponents },
2495
+ noHtml5Validate: true,
2496
+ formData: fieldFormState,
2497
+ formContext: { fieldFormState },
2498
+ onSubmit: (e) => handleFieldConfigChange(e.formData),
2499
+ validator,
2500
+ schema: ((_a = selectedField.schema) == null ? void 0 : _a.uiOptions) || {}
2501
+ },
2502
+ /* @__PURE__ */ React.createElement(
2503
+ Button,
2504
+ {
2505
+ variant: "contained",
2506
+ color: "primary",
2507
+ type: "submit",
2508
+ disabled: !((_b = selectedField.schema) == null ? void 0 : _b.uiOptions)
2509
+ },
2510
+ "Apply"
2511
+ )
2512
+ )))), /* @__PURE__ */ React.createElement("div", { className: classes.preview }, /* @__PURE__ */ React.createElement(Card, null, /* @__PURE__ */ React.createElement(CardHeader, { title: "Example Template Spec" }), /* @__PURE__ */ React.createElement(CardContent, null, /* @__PURE__ */ React.createElement(
2513
+ CodeMirror,
2514
+ {
2515
+ readOnly: true,
2516
+ theme: "dark",
2517
+ height: "100%",
2518
+ extensions: [StreamLanguage.define(yaml$1)],
2519
+ value: sampleFieldTemplate
2520
+ }
2521
+ ))), /* @__PURE__ */ React.createElement(
2522
+ TemplateEditorForm,
2523
+ {
2524
+ key: refreshKey,
2525
+ content: sampleFieldTemplate,
2526
+ contentIsSpec: true,
2527
+ fieldExtensions: customFieldExtensions,
2528
+ setErrorText: () => null
2529
+ }
2530
+ )));
2531
+ };
2532
+
2533
+ const useStyles$b = makeStyles((theme) => ({
2534
+ root: {
2535
+ overflowY: "auto",
2536
+ background: theme.palette.background.default
2537
+ },
2538
+ iconSuccess: {
2539
+ minWidth: 0,
2540
+ marginRight: theme.spacing(1),
2541
+ color: theme.palette.status.ok
2542
+ },
2543
+ iconFailure: {
2544
+ minWidth: 0,
2545
+ marginRight: theme.spacing(1),
2546
+ color: theme.palette.status.error
2547
+ }
2548
+ }));
2549
+ function DryRunResultsList() {
2550
+ const classes = useStyles$b();
2551
+ const dryRun = useDryRun();
2552
+ return /* @__PURE__ */ React.createElement(List$2, { className: classes.root, dense: true }, dryRun.results.map((result) => {
2553
+ var _a;
2554
+ const failed = result.log.some((l) => l.body.status === "failed");
2555
+ return /* @__PURE__ */ React.createElement(
2556
+ ListItem,
2557
+ {
2558
+ button: true,
2559
+ key: result.id,
2560
+ selected: ((_a = dryRun.selectedResult) == null ? void 0 : _a.id) === result.id,
2561
+ onClick: () => dryRun.selectResult(result.id)
2562
+ },
2563
+ /* @__PURE__ */ React.createElement(
2564
+ ListItemIcon,
2565
+ {
2566
+ className: failed ? classes.iconFailure : classes.iconSuccess
2567
+ },
2568
+ failed ? /* @__PURE__ */ React.createElement(CancelIcon, null) : /* @__PURE__ */ React.createElement(CheckIcon, null)
2569
+ ),
2570
+ /* @__PURE__ */ React.createElement(ListItemText, { primary: `Result ${result.id}` }),
2571
+ /* @__PURE__ */ React.createElement(ListItemSecondaryAction, null, /* @__PURE__ */ React.createElement(
2572
+ IconButton,
2573
+ {
2574
+ edge: "end",
2575
+ "aria-label": "delete",
2576
+ onClick: () => dryRun.deleteResult(result.id)
2577
+ },
2578
+ /* @__PURE__ */ React.createElement(DeleteIcon, null)
2579
+ ))
2580
+ );
2581
+ }));
2582
+ }
2583
+
2584
+ const TaskErrors = ({ error }) => {
2585
+ const id = useRef("");
2586
+ useEffect(() => {
2587
+ id.current = String(Math.random());
2588
+ }, [error]);
2589
+ return error ? /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(
2590
+ DismissableBanner,
2591
+ {
2592
+ id: id.current,
2593
+ variant: "warning",
2594
+ message: error.message
2595
+ }
2596
+ )) : null;
2597
+ };
2598
+
2599
+ const useStyles$a = makeStyles$1({
2600
+ svgIcon: {
2601
+ display: "inline-block",
2602
+ "& svg": {
2603
+ display: "inline-block",
2604
+ fontSize: "inherit",
2605
+ verticalAlign: "baseline"
2606
+ }
2607
+ }
2608
+ });
2609
+ const IconLink = (props) => {
2610
+ const { href, text, Icon, ...linkProps } = props;
2611
+ const classes = useStyles$a();
2612
+ return /* @__PURE__ */ React.createElement(Grid, { container: true, direction: "row", spacing: 1 }, /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(Typography, { component: "div", className: classes.svgIcon }, Icon ? /* @__PURE__ */ React.createElement(Icon, null) : /* @__PURE__ */ React.createElement(LanguageIcon, null))), /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(Link, { to: href, ...linkProps }, text || href)));
2613
+ };
2614
+
2615
+ const TaskPageLinks = ({ output }) => {
2616
+ const { links = [] } = output;
2617
+ const app = useApp();
2618
+ const entityRoute = useRouteRef(entityRouteRef);
2619
+ const iconResolver = (key) => {
2620
+ var _a;
2621
+ return key ? (_a = app.getSystemIcon(key)) != null ? _a : LanguageIcon : LanguageIcon;
2622
+ };
2623
+ return /* @__PURE__ */ React.createElement(Box, { px: 3, pb: 3 }, links.filter(({ url, entityRef }) => url || entityRef).map(({ url, entityRef, title, icon }) => {
2624
+ if (entityRef) {
2625
+ const entityName = parseEntityRef(entityRef, {
2626
+ defaultKind: "<unknown>",
2627
+ defaultNamespace: "<unknown>"
2628
+ });
2629
+ const target = entityRoute(entityName);
2630
+ return { title, icon, url: target };
2631
+ }
2632
+ return { title, icon, url };
2633
+ }).map(({ url, title, icon }, i) => /* @__PURE__ */ React.createElement(
2634
+ IconLink,
2635
+ {
2636
+ key: `output-link-${i}`,
2637
+ href: url,
2638
+ text: title != null ? title : url,
2639
+ Icon: iconResolver(icon),
2640
+ target: "_blank"
2641
+ }
2642
+ )));
2643
+ };
2644
+
2645
+ const humanizeDuration = require("humanize-duration");
2646
+ const useStyles$9 = makeStyles(
2647
+ (theme) => createStyles({
2648
+ root: {
2649
+ width: "100%"
2650
+ },
2651
+ button: {
2652
+ marginBottom: theme.spacing(2),
2653
+ marginLeft: theme.spacing(2)
2654
+ },
2655
+ actionsContainer: {
2656
+ marginBottom: theme.spacing(2)
2657
+ },
2658
+ resetContainer: {
2659
+ padding: theme.spacing(3)
2660
+ },
2661
+ labelWrapper: {
2662
+ display: "flex",
2663
+ flex: 1,
2664
+ flexDirection: "row",
2665
+ justifyContent: "space-between"
2666
+ },
2667
+ stepWrapper: {
2668
+ width: "100%"
2669
+ }
2670
+ })
2671
+ );
2672
+ const StepTimeTicker = ({ step }) => {
2673
+ const [time, setTime] = useState("");
2674
+ useInterval(() => {
2675
+ if (!step.startedAt) {
2676
+ setTime("");
2677
+ return;
2678
+ }
2679
+ const end = step.endedAt ? DateTime.fromISO(step.endedAt) : DateTime.local();
2680
+ const startedAt = DateTime.fromISO(step.startedAt);
2681
+ const formatted = Interval.fromDateTimes(startedAt, end).toDuration().valueOf();
2682
+ setTime(humanizeDuration(formatted, { round: true }));
2683
+ }, 1e3);
2684
+ return /* @__PURE__ */ React.createElement(Typography$1, { variant: "caption" }, time);
2685
+ };
2686
+ const useStepIconStyles = makeStyles(
2687
+ (theme) => createStyles({
2688
+ root: {
2689
+ color: theme.palette.text.disabled,
2690
+ display: "flex",
2691
+ height: 22,
2692
+ alignItems: "center"
2693
+ },
2694
+ completed: {
2695
+ color: theme.palette.status.ok
2696
+ },
2697
+ error: {
2698
+ color: theme.palette.status.error
2699
+ }
2700
+ })
2701
+ );
2702
+ function TaskStepIconComponent(props) {
2703
+ const classes = useStepIconStyles();
2704
+ const { active, completed, error } = props;
2705
+ const getMiddle = () => {
2706
+ if (active) {
2707
+ return /* @__PURE__ */ React.createElement(CircularProgress, { size: "24px" });
2708
+ }
2709
+ if (completed) {
2710
+ return /* @__PURE__ */ React.createElement(CheckIcon, null);
2711
+ }
2712
+ if (error) {
2713
+ return /* @__PURE__ */ React.createElement(CancelIcon, null);
2714
+ }
2715
+ return /* @__PURE__ */ React.createElement(FiberManualRecordIcon, null);
2716
+ };
2717
+ return /* @__PURE__ */ React.createElement(
2718
+ "div",
2719
+ {
2720
+ className: classNames(classes.root, {
2721
+ [classes.completed]: completed,
2722
+ [classes.error]: error
2723
+ })
2724
+ },
2725
+ getMiddle()
2726
+ );
2727
+ }
2728
+ const TaskStatusStepper = memo(
2729
+ (props) => {
2730
+ const { steps, currentStepId, onUserStepChange } = props;
2731
+ const classes = useStyles$9(props);
2732
+ return /* @__PURE__ */ React.createElement("div", { className: classes.root }, /* @__PURE__ */ React.createElement(
2733
+ Stepper$2,
2734
+ {
2735
+ activeStep: steps.findIndex((s) => s.id === currentStepId),
2736
+ orientation: "vertical",
2737
+ nonLinear: true
2738
+ },
2739
+ steps.map((step, index) => {
2740
+ const isCompleted = step.status === "completed";
2741
+ const isFailed = step.status === "failed";
2742
+ const isActive = step.status === "processing";
2743
+ const isSkipped = step.status === "skipped";
2744
+ return /* @__PURE__ */ React.createElement(Step$1, { key: String(index), expanded: true }, /* @__PURE__ */ React.createElement(StepButton, { onClick: () => onUserStepChange(step.id) }, /* @__PURE__ */ React.createElement(
2745
+ StepLabel$1,
2746
+ {
2747
+ StepIconProps: {
2748
+ completed: isCompleted,
2749
+ error: isFailed,
2750
+ active: isActive
2751
+ },
2752
+ StepIconComponent: TaskStepIconComponent,
2753
+ className: classes.stepWrapper
2754
+ },
2755
+ /* @__PURE__ */ React.createElement("div", { className: classes.labelWrapper }, /* @__PURE__ */ React.createElement(Typography$1, { variant: "subtitle2" }, step.name), isSkipped ? /* @__PURE__ */ React.createElement(Typography$1, { variant: "caption" }, "Skipped") : /* @__PURE__ */ React.createElement(StepTimeTicker, { step }))
2756
+ )));
2757
+ })
2758
+ ));
2759
+ }
2760
+ );
2761
+ const hasLinks = ({ links = [] }) => links.length > 0;
2762
+ const TaskPage = ({ loadingText }) => {
2763
+ const classes = useStyles$9();
2764
+ const navigate = useNavigate();
2765
+ const rootPath = useRouteRef(rootRouteRef);
2766
+ const templateRoute = useRouteRef(selectedTemplateRouteRef);
2767
+ const [userSelectedStepId, setUserSelectedStepId] = useState(void 0);
2768
+ const [lastActiveStepId, setLastActiveStepId] = useState(
2769
+ void 0
2770
+ );
2771
+ const { taskId } = useRouteRefParams(scaffolderTaskRouteRef);
2772
+ const taskStream = useTaskEventStream(taskId);
2773
+ const completed = taskStream.completed;
2774
+ const steps = useMemo(
2775
+ () => {
2776
+ var _a, _b;
2777
+ return (_b = (_a = taskStream.task) == null ? void 0 : _a.spec.steps.map((step) => {
2778
+ var _a2;
2779
+ return {
2780
+ ...step,
2781
+ ...(_a2 = taskStream == null ? void 0 : taskStream.steps) == null ? void 0 : _a2[step.id]
2782
+ };
2783
+ })) != null ? _b : [];
2784
+ },
2785
+ [taskStream]
2786
+ );
2787
+ useEffect(() => {
2788
+ var _a;
2789
+ const mostRecentFailedOrActiveStep = steps.find(
2790
+ (step) => ["failed", "processing"].includes(step.status)
2791
+ );
2792
+ if (completed && !mostRecentFailedOrActiveStep) {
2793
+ setLastActiveStepId((_a = steps[steps.length - 1]) == null ? void 0 : _a.id);
2794
+ return;
2795
+ }
2796
+ setLastActiveStepId(mostRecentFailedOrActiveStep == null ? void 0 : mostRecentFailedOrActiveStep.id);
2797
+ }, [steps, completed]);
2798
+ const currentStepId = userSelectedStepId != null ? userSelectedStepId : lastActiveStepId;
2799
+ const logAsString = useMemo(() => {
2800
+ if (!currentStepId) {
2801
+ return loadingText ? loadingText : "Loading...";
2802
+ }
2803
+ const log = taskStream.stepLogs[currentStepId];
2804
+ if (!(log == null ? void 0 : log.length)) {
2805
+ return "Waiting for logs...";
2806
+ }
2807
+ return log.join("\n");
2808
+ }, [taskStream.stepLogs, currentStepId, loadingText]);
2809
+ const taskNotFound = taskStream.completed === true && taskStream.loading === false && !taskStream.task;
2810
+ const { output } = taskStream;
2811
+ const handleStartOver = () => {
2812
+ var _a, _b, _c;
2813
+ if (!taskStream.task || !((_b = (_a = taskStream.task) == null ? void 0 : _a.spec.templateInfo) == null ? void 0 : _b.entityRef)) {
2814
+ navigate(rootPath());
2815
+ return;
2816
+ }
2817
+ const formData = taskStream.task.spec.parameters;
2818
+ const { name, namespace } = parseEntityRef(
2819
+ (_c = taskStream.task.spec.templateInfo) == null ? void 0 : _c.entityRef
2820
+ );
2821
+ navigate(
2822
+ `${templateRoute({ templateName: name, namespace })}?${qs.stringify({
2823
+ formData: JSON.stringify(formData)
2824
+ })}`
2825
+ );
2826
+ };
2827
+ return /* @__PURE__ */ React.createElement(Page, { themeId: "home" }, /* @__PURE__ */ React.createElement(
2828
+ Header,
2829
+ {
2830
+ pageTitleOverride: `Task ${taskId}`,
2831
+ title: "Task Activity",
2832
+ subtitle: `Activity for task: ${taskId}`
2833
+ }
2834
+ ), /* @__PURE__ */ React.createElement(Content, null, taskNotFound ? /* @__PURE__ */ React.createElement(
2835
+ ErrorPage,
2836
+ {
2837
+ status: "404",
2838
+ statusMessage: "Task not found",
2839
+ additionalInfo: "No task found with this ID"
2840
+ }
2841
+ ) : /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(Grid$1, { container: true }, /* @__PURE__ */ React.createElement(Grid$1, { item: true, xs: 3 }, /* @__PURE__ */ React.createElement(Paper, null, /* @__PURE__ */ React.createElement(
2842
+ TaskStatusStepper,
2843
+ {
2844
+ steps,
2845
+ currentStepId,
2846
+ onUserStepChange: setUserSelectedStepId
2847
+ }
2848
+ ), output && hasLinks(output) && /* @__PURE__ */ React.createElement(TaskPageLinks, { output }), /* @__PURE__ */ React.createElement(
2849
+ Button,
2850
+ {
2851
+ className: classes.button,
2852
+ onClick: handleStartOver,
2853
+ disabled: !completed,
2854
+ variant: "contained",
2855
+ color: "primary"
2856
+ },
2857
+ "Start Over"
2858
+ ))), /* @__PURE__ */ React.createElement(Grid$1, { item: true, xs: 9 }, !currentStepId && /* @__PURE__ */ React.createElement(Progress, null), /* @__PURE__ */ React.createElement("div", { style: { height: "80vh" } }, /* @__PURE__ */ React.createElement(TaskErrors, { error: taskStream.error }), /* @__PURE__ */ React.createElement(LogViewer, { text: logAsString })))))));
2859
+ };
2860
+
2861
+ const useStyles$8 = makeStyles({
2862
+ root: {
2863
+ whiteSpace: "nowrap",
2864
+ overflowY: "auto"
2865
+ }
2866
+ });
2867
+ function parseFileEntires(paths) {
2868
+ const root = {
2869
+ type: "directory",
2870
+ name: "",
2871
+ path: "",
2872
+ children: []
2873
+ };
2874
+ for (const path of paths.slice().sort()) {
2875
+ const parts = path.split("/");
2876
+ let current = root;
2877
+ for (let i = 0; i < parts.length; i++) {
2878
+ const part = parts[i];
2879
+ if (part === "") {
2880
+ throw new Error(`Invalid path part: ''`);
2881
+ }
2882
+ const entryPath = parts.slice(0, i + 1).join("/");
2883
+ const existing = current.children.find((child) => child.name === part);
2884
+ if ((existing == null ? void 0 : existing.type) === "file") {
2885
+ throw new Error(`Duplicate filename at '${entryPath}'`);
2886
+ } else if (existing) {
2887
+ current = existing;
2888
+ } else {
2889
+ if (i < parts.length - 1) {
2890
+ const newEntry = {
2891
+ type: "directory",
2892
+ name: part,
2893
+ path: entryPath,
2894
+ children: []
2895
+ };
2896
+ const firstFileIndex = current.children.findIndex(
2897
+ (child) => child.type === "file"
2898
+ );
2899
+ current.children.splice(firstFileIndex, 0, newEntry);
2900
+ current = newEntry;
2901
+ } else {
2902
+ current.children.push({
2903
+ type: "file",
2904
+ name: part,
2905
+ path: entryPath
2906
+ });
2907
+ }
2908
+ }
2909
+ }
2910
+ }
2911
+ return root.children;
2912
+ }
2913
+ function FileTreeItem({ entry }) {
2914
+ if (entry.type === "file") {
2915
+ return /* @__PURE__ */ React.createElement(TreeItem, { nodeId: entry.path, label: entry.name });
2916
+ }
2917
+ return /* @__PURE__ */ React.createElement(TreeItem, { nodeId: entry.path, label: entry.name }, entry.children.map((child) => /* @__PURE__ */ React.createElement(FileTreeItem, { key: child.path, entry: child })));
2918
+ }
2919
+ function FileBrowser(props) {
2920
+ const classes = useStyles$8();
2921
+ const fileTree = useMemo(
2922
+ () => parseFileEntires(props.filePaths),
2923
+ [props.filePaths]
2924
+ );
2925
+ return /* @__PURE__ */ React.createElement(
2926
+ TreeView,
2927
+ {
2928
+ selected: props.selected,
2929
+ className: classes.root,
2930
+ defaultCollapseIcon: /* @__PURE__ */ React.createElement(ExpandMoreIcon, null),
2931
+ defaultExpandIcon: /* @__PURE__ */ React.createElement(ChevronRightIcon, null),
2932
+ onNodeSelect: (_e, nodeId) => {
2933
+ if (props.onSelect && props.filePaths.includes(nodeId)) {
2934
+ props.onSelect(nodeId);
2935
+ }
2936
+ }
2937
+ },
2938
+ fileTree.map((entry) => /* @__PURE__ */ React.createElement(FileTreeItem, { key: entry.path, entry }))
2939
+ );
2940
+ }
2941
+
2942
+ const useStyles$7 = makeStyles((theme) => ({
2943
+ root: {
2944
+ display: "grid",
2945
+ gridTemplateColumns: "280px auto 3fr",
2946
+ gridTemplateRows: "1fr"
2947
+ },
2948
+ child: {
2949
+ overflowY: "auto",
2950
+ height: "100%",
2951
+ minHeight: 0
2952
+ },
2953
+ firstChild: {
2954
+ background: theme.palette.background.paper
2955
+ }
2956
+ }));
2957
+ function DryRunResultsSplitView(props) {
2958
+ const classes = useStyles$7();
2959
+ const childArray = Children.toArray(props.children);
2960
+ if (childArray.length !== 2) {
2961
+ throw new Error("must have exactly 2 children");
2962
+ }
2963
+ return /* @__PURE__ */ React.createElement("div", { className: classes.root }, /* @__PURE__ */ React.createElement("div", { className: classNames(classes.child, classes.firstChild) }, childArray[0]), /* @__PURE__ */ React.createElement(Divider, { orientation: "horizontal" }), /* @__PURE__ */ React.createElement("div", { className: classes.child }, childArray[1]));
2964
+ }
2965
+
2966
+ const useStyles$6 = makeStyles({
2967
+ root: {
2968
+ display: "flex",
2969
+ flexFlow: "column nowrap"
2970
+ },
2971
+ contentWrapper: {
2972
+ flex: 1,
2973
+ position: "relative"
2974
+ },
2975
+ content: {
2976
+ position: "absolute",
2977
+ top: 0,
2978
+ left: 0,
2979
+ right: 0,
2980
+ bottom: 0,
2981
+ display: "flex",
2982
+ "& > *": {
2983
+ flex: 1
2984
+ }
2985
+ },
2986
+ codeMirror: {
2987
+ height: "100%",
2988
+ overflowY: "auto"
2989
+ }
2990
+ });
2991
+ function FilesContent() {
2992
+ const classes = useStyles$6();
2993
+ const { selectedResult } = useDryRun();
2994
+ const [selectedPath, setSelectedPath] = useState("");
2995
+ const selectedFile = selectedResult == null ? void 0 : selectedResult.directoryContents.find(
2996
+ (f) => f.path === selectedPath
2997
+ );
2998
+ useEffect(() => {
2999
+ if (selectedResult) {
3000
+ const [firstFile] = selectedResult.directoryContents;
3001
+ if (firstFile) {
3002
+ setSelectedPath(firstFile.path);
3003
+ } else {
3004
+ setSelectedPath("");
3005
+ }
3006
+ }
3007
+ return void 0;
3008
+ }, [selectedResult]);
3009
+ if (!selectedResult) {
3010
+ return null;
3011
+ }
3012
+ return /* @__PURE__ */ React.createElement(DryRunResultsSplitView, null, /* @__PURE__ */ React.createElement(
3013
+ FileBrowser,
3014
+ {
3015
+ selected: selectedPath,
3016
+ onSelect: setSelectedPath,
3017
+ filePaths: selectedResult.directoryContents.map((file) => file.path)
3018
+ }
3019
+ ), /* @__PURE__ */ React.createElement(
3020
+ CodeMirror,
3021
+ {
3022
+ className: classes.codeMirror,
3023
+ theme: "dark",
3024
+ height: "100%",
3025
+ extensions: [StreamLanguage.define(yaml$1)],
3026
+ readOnly: true,
3027
+ value: (selectedFile == null ? void 0 : selectedFile.base64Content) ? atob(selectedFile.base64Content) : ""
3028
+ }
3029
+ ));
3030
+ }
3031
+ function LogContent() {
3032
+ var _a, _b;
3033
+ const { selectedResult } = useDryRun();
3034
+ const [currentStepId, setUserSelectedStepId] = useState();
3035
+ const steps = useMemo(() => {
3036
+ var _a2;
3037
+ if (!selectedResult) {
3038
+ return [];
3039
+ }
3040
+ return (_a2 = selectedResult.steps.map((step) => {
3041
+ var _a3, _b2;
3042
+ const stepLog = selectedResult.log.filter(
3043
+ (l) => l.body.stepId === step.id
3044
+ );
3045
+ return {
3046
+ id: step.id,
3047
+ name: step.name,
3048
+ logString: stepLog.map((l) => l.body.message).join("\n"),
3049
+ status: (_b2 = (_a3 = stepLog[stepLog.length - 1]) == null ? void 0 : _a3.body.status) != null ? _b2 : "completed"
3050
+ };
3051
+ })) != null ? _a2 : [];
3052
+ }, [selectedResult]);
3053
+ if (!selectedResult) {
3054
+ return null;
3055
+ }
3056
+ const selectedStep = (_a = steps.find((s) => s.id === currentStepId)) != null ? _a : steps[0];
3057
+ return /* @__PURE__ */ React.createElement(DryRunResultsSplitView, null, /* @__PURE__ */ React.createElement(
3058
+ TaskStatusStepper,
3059
+ {
3060
+ steps,
3061
+ currentStepId: selectedStep.id,
3062
+ onUserStepChange: setUserSelectedStepId
3063
+ }
3064
+ ), /* @__PURE__ */ React.createElement(LogViewer, { text: (_b = selectedStep == null ? void 0 : selectedStep.logString) != null ? _b : "" }));
3065
+ }
3066
+ function OutputContent() {
3067
+ var _a, _b;
3068
+ const classes = useStyles$6();
3069
+ const { selectedResult } = useDryRun();
3070
+ if (!selectedResult) {
3071
+ return null;
3072
+ }
3073
+ return /* @__PURE__ */ React.createElement(DryRunResultsSplitView, null, /* @__PURE__ */ React.createElement(Box$1, { pt: 2 }, ((_b = (_a = selectedResult.output) == null ? void 0 : _a.links) == null ? void 0 : _b.length) && /* @__PURE__ */ React.createElement(TaskPageLinks, { output: selectedResult.output })), /* @__PURE__ */ React.createElement(
3074
+ CodeMirror,
3075
+ {
3076
+ className: classes.codeMirror,
3077
+ theme: "dark",
3078
+ height: "100%",
3079
+ extensions: [StreamLanguage.define(yaml$1)],
3080
+ readOnly: true,
3081
+ value: JSON.stringify(selectedResult.output, null, 2)
3082
+ }
3083
+ ));
3084
+ }
3085
+ function DryRunResultsView() {
3086
+ const classes = useStyles$6();
3087
+ const [selectedTab, setSelectedTab] = useState(
3088
+ "files"
3089
+ );
3090
+ return /* @__PURE__ */ React.createElement("div", { className: classes.root }, /* @__PURE__ */ React.createElement(Tabs, { value: selectedTab, onChange: (_, v) => setSelectedTab(v) }, /* @__PURE__ */ React.createElement(Tab, { value: "files", label: "Files" }), /* @__PURE__ */ React.createElement(Tab, { value: "log", label: "Log" }), /* @__PURE__ */ React.createElement(Tab, { value: "output", label: "Output" })), /* @__PURE__ */ React.createElement(Divider, null), /* @__PURE__ */ React.createElement("div", { className: classes.contentWrapper }, /* @__PURE__ */ React.createElement("div", { className: classes.content }, selectedTab === "files" && /* @__PURE__ */ React.createElement(FilesContent, null), selectedTab === "log" && /* @__PURE__ */ React.createElement(LogContent, null), selectedTab === "output" && /* @__PURE__ */ React.createElement(OutputContent, null))));
3091
+ }
3092
+
3093
+ const useStyles$5 = makeStyles((theme) => ({
3094
+ header: {
3095
+ height: 48,
3096
+ minHeight: 0,
3097
+ "&.Mui-expanded": {
3098
+ height: 48,
3099
+ minHeight: 0
3100
+ }
3101
+ },
3102
+ content: {
3103
+ display: "grid",
3104
+ background: theme.palette.background.default,
3105
+ gridTemplateColumns: "180px auto 1fr",
3106
+ gridTemplateRows: "1fr",
3107
+ padding: 0,
3108
+ height: 400
3109
+ }
3110
+ }));
3111
+ function DryRunResults() {
3112
+ const classes = useStyles$5();
3113
+ const dryRun = useDryRun();
3114
+ const [expanded, setExpanded] = useState(false);
3115
+ const [hidden, setHidden] = useState(true);
3116
+ const resultsLength = dryRun.results.length;
3117
+ const prevResultsLength = usePrevious(resultsLength);
3118
+ useEffect(() => {
3119
+ if (prevResultsLength === 0 && resultsLength === 1) {
3120
+ setHidden(false);
3121
+ setExpanded(true);
3122
+ } else if (prevResultsLength === 1 && resultsLength === 0) {
3123
+ setExpanded(false);
3124
+ }
3125
+ }, [prevResultsLength, resultsLength]);
3126
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
3127
+ Accordion$1,
3128
+ {
3129
+ variant: "outlined",
3130
+ expanded,
3131
+ hidden: resultsLength === 0 && hidden,
3132
+ onChange: (_, exp) => setExpanded(exp),
3133
+ onTransitionEnd: () => resultsLength === 0 && setHidden(true)
3134
+ },
3135
+ /* @__PURE__ */ React.createElement(
3136
+ AccordionSummary$1,
3137
+ {
3138
+ className: classes.header,
3139
+ expandIcon: /* @__PURE__ */ React.createElement(ExpandMoreIcon$1, null)
3140
+ },
3141
+ /* @__PURE__ */ React.createElement(Typography$1, null, "Dry-run results")
3142
+ ),
3143
+ /* @__PURE__ */ React.createElement(Divider, { orientation: "horizontal" }),
3144
+ /* @__PURE__ */ React.createElement(AccordionDetails$1, { className: classes.content }, /* @__PURE__ */ React.createElement(DryRunResultsList, null), /* @__PURE__ */ React.createElement(Divider, { orientation: "horizontal" }), /* @__PURE__ */ React.createElement(DryRunResultsView, null))
3145
+ ));
3146
+ }
3147
+
3148
+ const useStyles$4 = makeStyles$1((theme) => ({
3149
+ button: {
3150
+ padding: theme.spacing(1)
3151
+ },
3152
+ buttons: {
3153
+ display: "flex",
3154
+ flexFlow: "row nowrap",
3155
+ alignItems: "center",
3156
+ justifyContent: "flex-start"
3157
+ },
3158
+ buttonsGap: {
3159
+ flex: "1 1 auto"
3160
+ },
3161
+ buttonsDivider: {
3162
+ marginBottom: theme.spacing(1)
3163
+ }
3164
+ }));
3165
+ function TemplateEditorBrowser(props) {
3166
+ var _a, _b;
3167
+ const classes = useStyles$4();
3168
+ const directoryEditor = useDirectoryEditor();
3169
+ const changedFiles = directoryEditor.files.filter((file) => file.dirty);
3170
+ const handleClose = () => {
3171
+ if (!props.onClose) {
3172
+ return;
3173
+ }
3174
+ if (changedFiles.length > 0) {
3175
+ const accepted = window.confirm(
3176
+ "Are you sure? Unsaved changes will be lost"
3177
+ );
3178
+ if (!accepted) {
3179
+ return;
3180
+ }
3181
+ }
3182
+ props.onClose();
3183
+ };
3184
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { className: classes.buttons }, /* @__PURE__ */ React.createElement(Tooltip, { title: "Save all files" }, /* @__PURE__ */ React.createElement(
3185
+ IconButton$1,
3186
+ {
3187
+ className: classes.button,
3188
+ disabled: directoryEditor.files.every((file) => !file.dirty),
3189
+ onClick: () => directoryEditor.save()
3190
+ },
3191
+ /* @__PURE__ */ React.createElement(SaveIcon, null)
3192
+ )), /* @__PURE__ */ React.createElement(Tooltip, { title: "Reload directory" }, /* @__PURE__ */ React.createElement(
3193
+ IconButton$1,
3194
+ {
3195
+ className: classes.button,
3196
+ onClick: () => directoryEditor.reload()
3197
+ },
3198
+ /* @__PURE__ */ React.createElement(RefreshIcon, null)
3199
+ )), /* @__PURE__ */ React.createElement("div", { className: classes.buttonsGap }), /* @__PURE__ */ React.createElement(Tooltip, { title: "Close directory" }, /* @__PURE__ */ React.createElement(IconButton$1, { className: classes.button, onClick: handleClose }, /* @__PURE__ */ React.createElement(CloseIcon, null)))), /* @__PURE__ */ React.createElement(Divider$1, { className: classes.buttonsDivider }), /* @__PURE__ */ React.createElement(
3200
+ FileBrowser,
3201
+ {
3202
+ selected: (_b = (_a = directoryEditor.selectedFile) == null ? void 0 : _a.path) != null ? _b : "",
3203
+ onSelect: directoryEditor.setSelectedFile,
3204
+ filePaths: directoryEditor.files.map((file) => file.path)
3205
+ }
3206
+ ));
3207
+ }
3208
+
3209
+ const useStyles$3 = makeStyles$1((theme) => ({
3210
+ container: {
3211
+ position: "relative",
3212
+ width: "100%",
3213
+ height: "100%"
3214
+ },
3215
+ codeMirror: {
3216
+ position: "absolute",
3217
+ top: 0,
3218
+ bottom: 0,
3219
+ left: 0,
3220
+ right: 0
3221
+ },
3222
+ errorPanel: {
3223
+ color: theme.palette.error.main,
3224
+ lineHeight: 2,
3225
+ margin: theme.spacing(0, 1)
3226
+ },
3227
+ floatingButtons: {
3228
+ position: "absolute",
3229
+ top: theme.spacing(1),
3230
+ right: theme.spacing(3)
3231
+ },
3232
+ floatingButton: {
3233
+ padding: theme.spacing(1)
3234
+ }
3235
+ }));
3236
+ function TemplateEditorTextArea(props) {
3237
+ const { errorText } = props;
3238
+ const classes = useStyles$3();
3239
+ const panelExtension = useMemo(() => {
3240
+ if (!errorText) {
3241
+ return showPanel.of(null);
3242
+ }
3243
+ const dom = document.createElement("div");
3244
+ dom.classList.add(classes.errorPanel);
3245
+ dom.textContent = errorText;
3246
+ return showPanel.of(() => ({ dom, bottom: true }));
3247
+ }, [classes, errorText]);
3248
+ useKeyboardEvent(
3249
+ (e) => e.key === "s" && (e.ctrlKey || e.metaKey),
3250
+ (e) => {
3251
+ e.preventDefault();
3252
+ if (props.onSave) {
3253
+ props.onSave();
3254
+ }
3255
+ }
3256
+ );
3257
+ return /* @__PURE__ */ React.createElement("div", { className: classes.container }, /* @__PURE__ */ React.createElement(
3258
+ CodeMirror,
3259
+ {
3260
+ className: classes.codeMirror,
3261
+ theme: "dark",
3262
+ height: "100%",
3263
+ extensions: [StreamLanguage.define(yaml$1), panelExtension],
3264
+ value: props.content,
3265
+ onChange: props.onUpdate
3266
+ }
3267
+ ), (props.onSave || props.onReload) && /* @__PURE__ */ React.createElement("div", { className: classes.floatingButtons }, /* @__PURE__ */ React.createElement(Paper, null, props.onSave && /* @__PURE__ */ React.createElement(Tooltip, { title: "Save file" }, /* @__PURE__ */ React.createElement(
3268
+ IconButton$1,
3269
+ {
3270
+ className: classes.floatingButton,
3271
+ onClick: () => {
3272
+ var _a;
3273
+ return (_a = props.onSave) == null ? void 0 : _a.call(props);
3274
+ }
3275
+ },
3276
+ /* @__PURE__ */ React.createElement(SaveIcon, null)
3277
+ )), props.onReload && /* @__PURE__ */ React.createElement(Tooltip, { title: "Reload file" }, /* @__PURE__ */ React.createElement(
3278
+ IconButton$1,
3279
+ {
3280
+ className: classes.floatingButton,
3281
+ onClick: () => {
3282
+ var _a;
3283
+ return (_a = props.onReload) == null ? void 0 : _a.call(props);
3284
+ }
3285
+ },
3286
+ /* @__PURE__ */ React.createElement(RefreshIcon, null)
3287
+ )))));
3288
+ }
3289
+ function TemplateEditorDirectoryEditorTextArea(props) {
3290
+ var _a, _b;
3291
+ const directoryEditor = useDirectoryEditor();
3292
+ const actions = ((_a = directoryEditor.selectedFile) == null ? void 0 : _a.dirty) ? {
3293
+ onSave: () => directoryEditor.save(),
3294
+ onReload: () => directoryEditor.reload()
3295
+ } : {
3296
+ onReload: () => directoryEditor.reload()
3297
+ };
3298
+ return /* @__PURE__ */ React.createElement(
3299
+ TemplateEditorTextArea,
3300
+ {
3301
+ errorText: props.errorText,
3302
+ content: (_b = directoryEditor.selectedFile) == null ? void 0 : _b.content,
3303
+ onUpdate: (content) => {
3304
+ var _a2;
3305
+ return (_a2 = directoryEditor.selectedFile) == null ? void 0 : _a2.updateContent(content);
3306
+ },
3307
+ ...actions
3308
+ }
3309
+ );
3310
+ }
3311
+ TemplateEditorTextArea.DirectoryEditor = TemplateEditorDirectoryEditorTextArea;
3312
+
3313
+ const useStyles$2 = makeStyles$1({
3314
+ // Reset and fix sizing to make sure scrolling behaves correctly
3315
+ root: {
3316
+ gridArea: "pageContent",
3317
+ display: "grid",
3318
+ gridTemplateAreas: `
3319
+ "browser editor preview"
3320
+ "results results results"
3321
+ `,
3322
+ gridTemplateColumns: "1fr 3fr 2fr",
3323
+ gridTemplateRows: "1fr auto"
3324
+ },
3325
+ browser: {
3326
+ gridArea: "browser",
3327
+ overflow: "auto"
3328
+ },
3329
+ editor: {
3330
+ gridArea: "editor",
3331
+ overflow: "auto"
3332
+ },
3333
+ preview: {
3334
+ gridArea: "preview",
3335
+ overflow: "auto"
3336
+ },
3337
+ results: {
3338
+ gridArea: "results"
3339
+ }
3340
+ });
3341
+ const TemplateEditor = (props) => {
3342
+ const classes = useStyles$2();
3343
+ const [errorText, setErrorText] = useState();
3344
+ return /* @__PURE__ */ React.createElement(DirectoryEditorProvider, { directory: props.directory }, /* @__PURE__ */ React.createElement(DryRunProvider, null, /* @__PURE__ */ React.createElement("main", { className: classes.root }, /* @__PURE__ */ React.createElement("section", { className: classes.browser }, /* @__PURE__ */ React.createElement(TemplateEditorBrowser, { onClose: props.onClose })), /* @__PURE__ */ React.createElement("section", { className: classes.editor }, /* @__PURE__ */ React.createElement(TemplateEditorTextArea.DirectoryEditor, { errorText })), /* @__PURE__ */ React.createElement("section", { className: classes.preview }, /* @__PURE__ */ React.createElement(
3345
+ TemplateEditorForm.DirectoryEditorDryRun,
3346
+ {
3347
+ setErrorText,
3348
+ fieldExtensions: props.fieldExtensions,
3349
+ layouts: props.layouts
3350
+ }
3351
+ )), /* @__PURE__ */ React.createElement("section", { className: classes.results }, /* @__PURE__ */ React.createElement(DryRunResults, null)))));
3352
+ };
3353
+
3354
+ const EXAMPLE_TEMPLATE_PARAMS_YAML = `# Edit the template parameters below to see how they will render in the scaffolder form UI
3355
+ parameters:
3356
+ - title: Fill in some steps
3357
+ required:
3358
+ - name
3359
+ properties:
3360
+ name:
3361
+ title: Name
3362
+ type: string
3363
+ description: Unique name of the component
3364
+ owner:
3365
+ title: Owner
3366
+ type: string
3367
+ description: Owner of the component
3368
+ ui:field: OwnerPicker
3369
+ ui:options:
3370
+ catalogFilter:
3371
+ kind: Group
3372
+ - title: Choose a location
3373
+ required:
3374
+ - repoUrl
3375
+ properties:
3376
+ repoUrl:
3377
+ title: Repository Location
3378
+ type: string
3379
+ ui:field: RepoUrlPicker
3380
+ ui:options:
3381
+ allowedHosts:
3382
+ - github.com
3383
+ steps:
3384
+ - id: fetch-base
3385
+ name: Fetch Base
3386
+ action: fetch:template
3387
+ input:
3388
+ url: ./template
3389
+ values:
3390
+ name: \${{parameters.name}}
3391
+ `;
3392
+ const useStyles$1 = makeStyles$1((theme) => ({
3393
+ root: {
3394
+ gridArea: "pageContent",
3395
+ display: "grid",
3396
+ gridTemplateAreas: `
3397
+ "controls controls"
3398
+ "textArea preview"
3399
+ `,
3400
+ gridTemplateRows: "auto 1fr",
3401
+ gridTemplateColumns: "1fr 1fr"
3402
+ },
3403
+ controls: {
3404
+ gridArea: "controls",
3405
+ display: "flex",
3406
+ flexFlow: "row nowrap",
3407
+ alignItems: "center",
3408
+ margin: theme.spacing(1)
3409
+ },
3410
+ textArea: {
3411
+ gridArea: "textArea"
3412
+ },
3413
+ preview: {
3414
+ gridArea: "preview"
3415
+ }
3416
+ }));
3417
+ const TemplateFormPreviewer = ({
3418
+ defaultPreviewTemplate = EXAMPLE_TEMPLATE_PARAMS_YAML,
3419
+ customFieldExtensions = [],
3420
+ onClose,
3421
+ layouts = []
3422
+ }) => {
3423
+ const classes = useStyles$1();
3424
+ const alertApi = useApi(alertApiRef);
3425
+ const catalogApi = useApi(catalogApiRef);
3426
+ const [selectedTemplate, setSelectedTemplate] = useState("");
3427
+ const [errorText, setErrorText] = useState();
3428
+ const [templateOptions, setTemplateOptions] = useState([]);
3429
+ const [templateYaml, setTemplateYaml] = useState(defaultPreviewTemplate);
3430
+ const { loading } = useAsync(
3431
+ () => catalogApi.getEntities({
3432
+ filter: { kind: "template" },
3433
+ fields: [
3434
+ "kind",
3435
+ "metadata.namespace",
3436
+ "metadata.name",
3437
+ "metadata.title",
3438
+ "spec.parameters",
3439
+ "spec.steps",
3440
+ "spec.output"
3441
+ ]
3442
+ }).then(
3443
+ ({ items }) => setTemplateOptions(
3444
+ items.map((template) => {
3445
+ var _a;
3446
+ return {
3447
+ label: (_a = template.metadata.title) != null ? _a : humanizeEntityRef(template, { defaultKind: "template" }),
3448
+ value: template
3449
+ };
3450
+ })
3451
+ )
3452
+ ).catch(
3453
+ (e) => alertApi.post({
3454
+ message: `Error loading exisiting templates: ${e.message}`,
3455
+ severity: "error"
3456
+ })
3457
+ ),
3458
+ [catalogApi]
3459
+ );
3460
+ const handleSelectChange = useCallback(
3461
+ (selected) => {
3462
+ setSelectedTemplate(selected);
3463
+ setTemplateYaml(yaml.stringify(selected.spec));
3464
+ },
3465
+ [setTemplateYaml]
3466
+ );
3467
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, loading && /* @__PURE__ */ React.createElement(LinearProgress, null), /* @__PURE__ */ React.createElement("main", { className: classes.root }, /* @__PURE__ */ React.createElement("div", { className: classes.controls }, /* @__PURE__ */ React.createElement(FormControl$1, { variant: "outlined", size: "small", fullWidth: true }, /* @__PURE__ */ React.createElement(InputLabel$1, { id: "select-template-label" }, "Load Existing Template"), /* @__PURE__ */ React.createElement(
3468
+ Select$1,
3469
+ {
3470
+ value: selectedTemplate,
3471
+ label: "Load Existing Template",
3472
+ labelId: "select-template-label",
3473
+ onChange: (e) => handleSelectChange(e.target.value)
3474
+ },
3475
+ templateOptions.map((option, idx) => /* @__PURE__ */ React.createElement(MenuItem$1, { key: idx, value: option.value }, option.label))
3476
+ )), /* @__PURE__ */ React.createElement(IconButton$1, { size: "medium", onClick: onClose }, /* @__PURE__ */ React.createElement(CloseIcon, null))), /* @__PURE__ */ React.createElement("div", { className: classes.textArea }, /* @__PURE__ */ React.createElement(
3477
+ TemplateEditorTextArea,
3478
+ {
3479
+ content: templateYaml,
3480
+ onUpdate: setTemplateYaml,
3481
+ errorText
3482
+ }
3483
+ )), /* @__PURE__ */ React.createElement("div", { className: classes.preview }, /* @__PURE__ */ React.createElement(
3484
+ TemplateEditorForm,
3485
+ {
3486
+ content: templateYaml,
3487
+ contentIsSpec: true,
3488
+ fieldExtensions: customFieldExtensions,
3489
+ setErrorText,
3490
+ layouts
3491
+ }
3492
+ ))));
3493
+ };
3494
+
3495
+ const useStyles = makeStyles((theme) => ({
3496
+ introText: {
3497
+ textAlign: "center",
3498
+ marginTop: theme.spacing(2)
3499
+ },
3500
+ card: {
3501
+ position: "relative",
3502
+ maxWidth: 340,
3503
+ marginTop: theme.spacing(4),
3504
+ margin: theme.spacing(0, 2)
3505
+ },
3506
+ infoIcon: {
3507
+ position: "absolute",
3508
+ top: theme.spacing(1),
3509
+ right: theme.spacing(1)
3510
+ }
3511
+ }));
3512
+ function TemplateEditorIntro(props) {
3513
+ const classes = useStyles();
3514
+ const supportsLoad = WebFileSystemAccess.isSupported();
3515
+ const cardLoadLocal = /* @__PURE__ */ React.createElement(Card$1, { className: classes.card, elevation: 4 }, /* @__PURE__ */ React.createElement(
3516
+ CardActionArea,
3517
+ {
3518
+ disabled: !supportsLoad,
3519
+ onClick: () => {
3520
+ var _a;
3521
+ return (_a = props.onSelect) == null ? void 0 : _a.call(props, "local");
3522
+ }
3523
+ },
3524
+ /* @__PURE__ */ React.createElement(CardContent$1, null, /* @__PURE__ */ React.createElement(
3525
+ Typography$1,
3526
+ {
3527
+ variant: "h5",
3528
+ gutterBottom: true,
3529
+ color: supportsLoad ? void 0 : "textSecondary",
3530
+ style: { display: "flex", flexFlow: "row nowrap" }
3531
+ },
3532
+ "Load Template Directory"
3533
+ ), /* @__PURE__ */ React.createElement(
3534
+ Typography$1,
3535
+ {
3536
+ variant: "body1",
3537
+ color: supportsLoad ? void 0 : "textSecondary"
3538
+ },
3539
+ "Load a local template directory, allowing you to both edit and try executing your own template."
3540
+ ))
3541
+ ), !supportsLoad && /* @__PURE__ */ React.createElement("div", { className: classes.infoIcon }, /* @__PURE__ */ React.createElement(
3542
+ Tooltip$1,
3543
+ {
3544
+ placement: "top",
3545
+ title: "Only supported in some Chromium-based browsers"
3546
+ },
3547
+ /* @__PURE__ */ React.createElement(InfoOutlinedIcon, null)
3548
+ )));
3549
+ const cardFormEditor = /* @__PURE__ */ React.createElement(Card$1, { className: classes.card, elevation: 4 }, /* @__PURE__ */ React.createElement(CardActionArea, { onClick: () => {
3550
+ var _a;
3551
+ return (_a = props.onSelect) == null ? void 0 : _a.call(props, "form");
3552
+ } }, /* @__PURE__ */ React.createElement(CardContent$1, null, /* @__PURE__ */ React.createElement(Typography$1, { variant: "h5", gutterBottom: true }, "Edit Template Form"), /* @__PURE__ */ React.createElement(Typography$1, { variant: "body1" }, "Preview and edit a template form, either using a sample template or by loading a template from the catalog."))));
3553
+ const cardFieldExplorer = /* @__PURE__ */ React.createElement(Card$1, { className: classes.card, elevation: 4 }, /* @__PURE__ */ React.createElement(CardActionArea, { onClick: () => {
3554
+ var _a;
3555
+ return (_a = props.onSelect) == null ? void 0 : _a.call(props, "field-explorer");
3556
+ } }, /* @__PURE__ */ React.createElement(CardContent$1, null, /* @__PURE__ */ React.createElement(Typography$1, { variant: "h5", gutterBottom: true }, "Custom Field Explorer"), /* @__PURE__ */ React.createElement(Typography$1, { variant: "body1" }, "View and play around with available installed custom field extensions."))));
3557
+ return /* @__PURE__ */ React.createElement("div", { style: props.style }, /* @__PURE__ */ React.createElement(Typography$1, { variant: "h6", className: classes.introText }, "Get started by choosing one of the options below"), /* @__PURE__ */ React.createElement(
3558
+ "div",
3559
+ {
3560
+ style: {
3561
+ display: "flex",
3562
+ flexFlow: "row wrap",
3563
+ alignItems: "flex-start",
3564
+ justifyContent: "center",
3565
+ alignContent: "flex-start"
3566
+ }
3567
+ },
3568
+ supportsLoad && cardLoadLocal,
3569
+ cardFormEditor,
3570
+ !supportsLoad && cardLoadLocal,
3571
+ cardFieldExplorer
3572
+ ));
3573
+ }
3574
+
3575
+ const scaffolderPlugin = createPlugin({
3576
+ id: "scaffolder",
3577
+ apis: [
3578
+ createApiFactory({
3579
+ api: scaffolderApiRef,
3580
+ deps: {
3581
+ discoveryApi: discoveryApiRef,
3582
+ scmIntegrationsApi: scmIntegrationsApiRef,
3583
+ fetchApi: fetchApiRef,
3584
+ identityApi: identityApiRef
3585
+ },
3586
+ factory: ({ discoveryApi, scmIntegrationsApi, fetchApi, identityApi }) => new ScaffolderClient({
3587
+ discoveryApi,
3588
+ scmIntegrationsApi,
3589
+ fetchApi,
3590
+ identityApi
3591
+ })
3592
+ })
3593
+ ],
3594
+ routes: {
3595
+ root: rootRouteRef,
3596
+ selectedTemplate: selectedTemplateRouteRef,
3597
+ ongoingTask: scaffolderTaskRouteRef
3598
+ },
3599
+ externalRoutes: {
3600
+ registerComponent: registerComponentRouteRef,
3601
+ viewTechDoc: viewTechDocRouteRef
3602
+ }
3603
+ });
3604
+ scaffolderPlugin.provide(
3605
+ createScaffolderFieldExtension({
3606
+ component: EntityPicker,
3607
+ name: "EntityPicker",
3608
+ schema: EntityPickerSchema
3609
+ })
3610
+ );
3611
+ scaffolderPlugin.provide(
3612
+ createScaffolderFieldExtension({
3613
+ component: EntityNamePicker,
3614
+ name: "EntityNamePicker",
3615
+ validation: entityNamePickerValidation,
3616
+ schema: EntityNamePickerSchema
3617
+ })
3618
+ );
3619
+ scaffolderPlugin.provide(
3620
+ createScaffolderFieldExtension({
3621
+ component: RepoUrlPicker,
3622
+ name: "RepoUrlPicker",
3623
+ validation: repoPickerValidation,
3624
+ schema: RepoUrlPickerSchema
3625
+ })
3626
+ );
3627
+ scaffolderPlugin.provide(
3628
+ createScaffolderFieldExtension({
3629
+ component: OwnerPicker,
3630
+ name: "OwnerPicker",
3631
+ schema: OwnerPickerSchema
3632
+ })
3633
+ );
3634
+ scaffolderPlugin.provide(
3635
+ createRoutableExtension({
3636
+ name: "ScaffolderPage",
3637
+ component: () => import('./Router-0b12a83d.esm.js').then((m) => m.Router),
3638
+ mountPoint: rootRouteRef
3639
+ })
3640
+ );
3641
+ scaffolderPlugin.provide(
3642
+ createScaffolderFieldExtension({
3643
+ component: OwnedEntityPicker,
3644
+ name: "OwnedEntityPicker",
3645
+ schema: OwnedEntityPickerSchema
3646
+ })
3647
+ );
3648
+ scaffolderPlugin.provide(
3649
+ createScaffolderFieldExtension({
3650
+ component: EntityTagsPicker,
3651
+ name: "EntityTagsPicker",
3652
+ schema: EntityTagsPickerSchema
3653
+ })
3654
+ );
3655
+ const NextScaffolderPage = scaffolderPlugin.provide(
3656
+ createRoutableExtension({
3657
+ name: "NextScaffolderPage",
3658
+ component: () => import('./index-9804fdae.esm.js').then((m) => m.Router),
3659
+ mountPoint: nextRouteRef
3660
+ })
3661
+ );
3662
+
3663
+ export { ActionsPage as A, OwnedEntityPickerSchema as B, OwnerListPicker as C, DirectoryEditorProvider as D, EntityPicker as E, nextSelectedTemplateRouteRef as F, ContextMenu$1 as G, nextRouteRef as H, nextScaffolderTaskRouteRef as I, TemplateEditor as J, TemplateFormPreviewer as K, CustomFieldExplorer as L, nextEditRouteRef as M, nextActionsRouteRef as N, OwnerPicker as O, nextScaffolderListTaskRouteRef as P, OngoingTask as Q, RepoUrlPicker as R, NextScaffolderPage as S, TemplateEditorBrowser as T, WebFileSystemAccess as W, actionsRouteRef as a, scaffolderListTaskRouteRef as b, scaffolderTaskRouteRef as c, rootRouteRef as d, editRouteRef as e, useDirectoryEditor as f, DryRunProvider as g, TemplateEditorTextArea as h, DryRunResults as i, TemplateEditorIntro as j, TaskPage as k, legacySelectedTemplateRouteRef as l, EntityPickerSchema as m, EntityNamePicker as n, entityNamePickerValidation as o, EntityNamePickerSchema as p, EntityTagsPicker as q, registerComponentRouteRef as r, selectedTemplateRouteRef as s, EntityTagsPickerSchema as t, useDryRun as u, viewTechDocRouteRef as v, repoPickerValidation as w, RepoUrlPickerSchema as x, OwnerPickerSchema as y, OwnedEntityPicker as z };
3664
+ //# sourceMappingURL=alpha-d9ed6c0a.esm.js.map