@backstage/plugin-scaffolder 0.11.13 → 0.11.17

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.
@@ -2,19 +2,20 @@ import { createApiRef, useApi, attachComponentData, createExternalRouteRef, crea
2
2
  import { ResponseError } from '@backstage/errors';
3
3
  import qs from 'qs';
4
4
  import ObservableImpl from 'zen-observable';
5
- import { catalogApiRef, formatEntityRefTitle, useStarredEntity, getEntityRelations, getEntitySourceLocation, EntityRefLinks, useEntityListProvider, useEntityTypeFilter } from '@backstage/plugin-catalog-react';
6
- import { TextField, withStyles, makeStyles, IconButton, Tooltip, useTheme, Card, CardMedia, CardContent, Box, Typography, Chip, CardActions, Link, FormControlLabel, Checkbox } from '@material-ui/core';
5
+ import { catalogApiRef, formatEntityRefTitle, useOwnedEntities, useStarredEntity, getEntityRelations, getEntitySourceLocation, EntityRefLinks, useEntityListProvider, useEntityTypeFilter } from '@backstage/plugin-catalog-react';
6
+ import { TextField, FormControl as FormControl$1, withStyles, makeStyles, IconButton, Tooltip, useTheme, Card, CardMedia, CardContent, Box, Typography, Chip, CardActions, Link, FormControlLabel, Checkbox } from '@material-ui/core';
7
7
  import FormControl from '@material-ui/core/FormControl';
8
8
  import Autocomplete from '@material-ui/lab/Autocomplete';
9
- import React, { useCallback, useEffect } from 'react';
10
- import { useAsync } from 'react-use';
11
- import { KubernetesValidatorFunctions, RELATION_OWNED_BY, stringifyEntityRef } from '@backstage/catalog-model';
9
+ import React, { useCallback, useEffect, useState } from 'react';
10
+ import useAsync from 'react-use/lib/useAsync';
11
+ import { KubernetesValidatorFunctions, makeValidator, RELATION_OWNED_BY, stringifyEntityRef } from '@backstage/catalog-model';
12
+ import { useAsync as useAsync$1, useEffectOnce } from 'react-use';
13
+ import { Autocomplete as Autocomplete$1 } from '@material-ui/lab';
14
+ import { Progress, Select, ItemCardHeader, Button, ContentHeader, WarningPanel, Link as Link$1, Content, ItemCardGrid } from '@backstage/core-components';
12
15
  import { scmIntegrationsApiRef, ScmIntegrationIcon } from '@backstage/integration-react';
13
- import Select from '@material-ui/core/Select';
14
- import InputLabel from '@material-ui/core/InputLabel';
15
- import Input from '@material-ui/core/Input';
16
16
  import FormHelperText from '@material-ui/core/FormHelperText';
17
- import { Progress, ItemCardHeader, Button, ContentHeader, WarningPanel, Content, ItemCardGrid } from '@backstage/core-components';
17
+ import Input from '@material-ui/core/Input';
18
+ import InputLabel from '@material-ui/core/InputLabel';
18
19
  import Star from '@material-ui/icons/Star';
19
20
  import StarBorder from '@material-ui/icons/StarBorder';
20
21
  import WarningIcon from '@material-ui/icons/Warning';
@@ -23,11 +24,9 @@ import capitalize from 'lodash/capitalize';
23
24
  import CheckBoxIcon from '@material-ui/icons/CheckBox';
24
25
  import CheckBoxOutlineBlankIcon from '@material-ui/icons/CheckBoxOutlineBlank';
25
26
  import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
26
- import { Autocomplete as Autocomplete$1 } from '@material-ui/lab';
27
27
 
28
28
  const scaffolderApiRef = createApiRef({
29
- id: "plugin.scaffolder.service",
30
- description: "Used to make requests towards the scaffolder backend"
29
+ id: "plugin.scaffolder.service"
31
30
  });
32
31
  class ScaffolderClient {
33
32
  constructor(options) {
@@ -43,17 +42,17 @@ class ScaffolderClient {
43
42
  ...this.scmIntegrationsApi.bitbucket.list(),
44
43
  ...this.scmIntegrationsApi.github.list(),
45
44
  ...this.scmIntegrationsApi.gitlab.list()
46
- ].map((c) => ({type: c.type, title: c.title, host: c.config.host})).filter((c) => options.allowedHosts.includes(c.host));
45
+ ].map((c) => ({ type: c.type, title: c.title, host: c.config.host })).filter((c) => options.allowedHosts.includes(c.host));
47
46
  }
48
47
  async getTemplateParameterSchema(templateName) {
49
- const {namespace, kind, name} = templateName;
48
+ const { namespace, kind, name } = templateName;
50
49
  const token = await this.identityApi.getIdToken();
51
50
  const baseUrl = await this.discoveryApi.getBaseUrl("scaffolder");
52
51
  const templatePath = [namespace, kind, name].map((s) => encodeURIComponent(s)).join("/");
53
52
  const url = `${baseUrl}/v2/templates/${templatePath}/parameter-schema`;
54
53
  const response = await fetch(url, {
55
54
  headers: {
56
- ...token && {Authorization: `Bearer ${token}`}
55
+ ...token && { Authorization: `Bearer ${token}` }
57
56
  }
58
57
  });
59
58
  if (!response.ok) {
@@ -69,16 +68,16 @@ class ScaffolderClient {
69
68
  method: "POST",
70
69
  headers: {
71
70
  "Content-Type": "application/json",
72
- ...token && {Authorization: `Bearer ${token}`}
71
+ ...token && { Authorization: `Bearer ${token}` }
73
72
  },
74
- body: JSON.stringify({templateName, values: {...values}})
73
+ body: JSON.stringify({ templateName, values: { ...values } })
75
74
  });
76
75
  if (response.status !== 201) {
77
76
  const status = `${response.status} ${response.statusText}`;
78
77
  const body = await response.text();
79
78
  throw new Error(`Backend request failed, ${status} ${body.trim()}`);
80
79
  }
81
- const {id} = await response.json();
80
+ const { id } = await response.json();
82
81
  return id;
83
82
  }
84
83
  async getTask(taskId) {
@@ -86,7 +85,7 @@ class ScaffolderClient {
86
85
  const baseUrl = await this.discoveryApi.getBaseUrl("scaffolder");
87
86
  const url = `${baseUrl}/v2/tasks/${encodeURIComponent(taskId)}`;
88
87
  const response = await fetch(url, {
89
- headers: token ? {Authorization: `Bearer ${token}`} : {}
88
+ headers: token ? { Authorization: `Bearer ${token}` } : {}
90
89
  });
91
90
  if (!response.ok) {
92
91
  throw await ResponseError.fromResponse(response);
@@ -110,7 +109,7 @@ class ScaffolderClient {
110
109
  }
111
110
  this.discoveryApi.getBaseUrl("scaffolder").then((baseUrl) => {
112
111
  const url = `${baseUrl}/v2/tasks/${encodeURIComponent(taskId)}/eventstream`;
113
- const eventSource = new EventSource(url, {withCredentials: true});
112
+ const eventSource = new EventSource(url, { withCredentials: true });
114
113
  eventSource.addEventListener("log", (event) => {
115
114
  if (event.data) {
116
115
  try {
@@ -147,7 +146,7 @@ class ScaffolderClient {
147
146
  return new ObservableImpl((subscriber) => {
148
147
  this.discoveryApi.getBaseUrl("scaffolder").then(async (baseUrl) => {
149
148
  while (!subscriber.closed) {
150
- const url = `${baseUrl}/v2/tasks/${encodeURIComponent(taskId)}/events?${qs.stringify({after})}`;
149
+ const url = `${baseUrl}/v2/tasks/${encodeURIComponent(taskId)}/events?${qs.stringify({ after })}`;
151
150
  const response = await fetch(url);
152
151
  if (!response.ok) {
153
152
  await new Promise((resolve) => setTimeout(resolve, 1e3));
@@ -170,7 +169,7 @@ class ScaffolderClient {
170
169
  const baseUrl = await this.discoveryApi.getBaseUrl("scaffolder");
171
170
  const token = await this.identityApi.getIdToken();
172
171
  const response = await fetch(`${baseUrl}/v2/actions`, {
173
- headers: token ? {Authorization: `Bearer ${token}`} : {}
172
+ headers: token ? { Authorization: `Bearer ${token}` } : {}
174
173
  });
175
174
  if (!response.ok) {
176
175
  throw await ResponseError.fromResponse(response);
@@ -179,9 +178,13 @@ class ScaffolderClient {
179
178
  }
180
179
  }
181
180
 
181
+ const allowArbitraryValues = (uiSchema) => {
182
+ var _a, _b;
183
+ return (_b = (_a = uiSchema["ui:options"]) == null ? void 0 : _a.allowArbitraryValues) != null ? _b : true;
184
+ };
182
185
  const EntityPicker = ({
183
186
  onChange,
184
- schema: {title = "Entity", description = "An entity from the catalog"},
187
+ schema: { title = "Entity", description = "An entity from the catalog" },
185
188
  required,
186
189
  uiSchema,
187
190
  rawErrors,
@@ -192,23 +195,29 @@ const EntityPicker = ({
192
195
  const allowedKinds = (_a = uiSchema["ui:options"]) == null ? void 0 : _a.allowedKinds;
193
196
  const defaultKind = (_b = uiSchema["ui:options"]) == null ? void 0 : _b.defaultKind;
194
197
  const catalogApi = useApi(catalogApiRef);
195
- const {value: entities, loading} = useAsync(() => catalogApi.getEntities(allowedKinds ? {filter: {kind: allowedKinds}} : void 0));
196
- const entityRefs = entities == null ? void 0 : entities.items.map((e) => formatEntityRefTitle(e, {defaultKind}));
197
- const onSelect = (_, value) => {
198
+ const { value: entities, loading } = useAsync(() => catalogApi.getEntities(allowedKinds ? { filter: { kind: allowedKinds } } : void 0));
199
+ const entityRefs = entities == null ? void 0 : entities.items.map((e) => formatEntityRefTitle(e, { defaultKind }));
200
+ const onSelect = useCallback((_, value) => {
198
201
  onChange(value || "");
199
- };
202
+ }, [onChange]);
203
+ useEffect(() => {
204
+ if ((entityRefs == null ? void 0 : entityRefs.length) === 1) {
205
+ onChange(entityRefs[0]);
206
+ }
207
+ }, [entityRefs, onChange]);
200
208
  return /* @__PURE__ */ React.createElement(FormControl, {
201
209
  margin: "normal",
202
210
  required,
203
211
  error: (rawErrors == null ? void 0 : rawErrors.length) > 0 && !formData
204
212
  }, /* @__PURE__ */ React.createElement(Autocomplete, {
213
+ disabled: (entityRefs == null ? void 0 : entityRefs.length) === 1,
205
214
  id: idSchema == null ? void 0 : idSchema.$id,
206
215
  value: formData || "",
207
216
  loading,
208
217
  onChange: onSelect,
209
218
  options: entityRefs || [],
210
219
  autoSelect: true,
211
- freeSolo: true,
220
+ freeSolo: allowArbitraryValues(uiSchema),
212
221
  renderInput: (params) => /* @__PURE__ */ React.createElement(TextField, {
213
222
  ...params,
214
223
  label: title,
@@ -224,10 +233,10 @@ const EntityPicker = ({
224
233
  const TextValuePicker = ({
225
234
  onChange,
226
235
  required,
227
- schema: {title, description},
236
+ schema: { title, description },
228
237
  rawErrors,
229
238
  formData,
230
- uiSchema: {"ui:autofocus": autoFocus},
239
+ uiSchema: { "ui:autofocus": autoFocus },
231
240
  idSchema,
232
241
  placeholder
233
242
  }) => /* @__PURE__ */ React.createElement(TextField, {
@@ -237,17 +246,17 @@ const TextValuePicker = ({
237
246
  helperText: description,
238
247
  required,
239
248
  value: formData != null ? formData : "",
240
- onChange: ({target: {value}}) => onChange(value),
249
+ onChange: ({ target: { value } }) => onChange(value),
241
250
  margin: "normal",
242
251
  error: (rawErrors == null ? void 0 : rawErrors.length) > 0 && !formData,
243
- inputProps: {autoFocus}
252
+ inputProps: { autoFocus }
244
253
  });
245
254
 
246
255
  const EntityNamePicker = ({
247
- schema: {title = "Name", description = "Unique name of the component"},
256
+ schema: { title = "Name", description = "Unique name of the component" },
248
257
  ...props
249
258
  }) => /* @__PURE__ */ React.createElement(TextValuePicker, {
250
- schema: {title, description},
259
+ schema: { title, description },
251
260
  ...props
252
261
  });
253
262
 
@@ -257,8 +266,70 @@ const entityNamePickerValidation = (value, validation) => {
257
266
  }
258
267
  };
259
268
 
269
+ const EntityTagsPicker = ({
270
+ formData,
271
+ onChange,
272
+ uiSchema
273
+ }) => {
274
+ var _a;
275
+ const catalogApi = useApi(catalogApiRef);
276
+ const [inputValue, setInputValue] = useState("");
277
+ const [inputError, setInputError] = useState(false);
278
+ const tagValidator = makeValidator().isValidTag;
279
+ const kinds = (_a = uiSchema["ui:options"]) == null ? void 0 : _a.kinds;
280
+ const { loading, value: existingTags } = useAsync$1(async () => {
281
+ const tagsRequest = { fields: ["metadata.tags"] };
282
+ if (kinds) {
283
+ tagsRequest.filter = { kind: kinds };
284
+ }
285
+ const entities = await catalogApi.getEntities(tagsRequest);
286
+ return [
287
+ ...new Set(entities.items.flatMap((e) => {
288
+ var _a2;
289
+ return (_a2 = e.metadata) == null ? void 0 : _a2.tags;
290
+ }).filter(Boolean))
291
+ ].sort();
292
+ });
293
+ const setTags = (_, values) => {
294
+ let hasError = false;
295
+ let addDuplicate = false;
296
+ const currentTags = formData || [];
297
+ if ((values == null ? void 0 : values.length) && currentTags.length < values.length) {
298
+ const newTag = values[values.length - 1] = values[values.length - 1].toLocaleLowerCase("en-US").trim();
299
+ hasError = !tagValidator(newTag);
300
+ addDuplicate = currentTags.indexOf(newTag) !== -1;
301
+ }
302
+ setInputError(hasError);
303
+ setInputValue(!hasError ? "" : inputValue);
304
+ if (!hasError && !addDuplicate) {
305
+ onChange(values || []);
306
+ }
307
+ };
308
+ useEffectOnce(() => onChange(formData || []));
309
+ return /* @__PURE__ */ React.createElement(FormControl$1, {
310
+ margin: "normal"
311
+ }, /* @__PURE__ */ React.createElement(Autocomplete$1, {
312
+ multiple: true,
313
+ freeSolo: true,
314
+ filterSelectedOptions: true,
315
+ onChange: setTags,
316
+ value: formData || [],
317
+ inputValue,
318
+ loading,
319
+ options: existingTags || [],
320
+ ChipProps: { size: "small" },
321
+ renderInput: (params) => /* @__PURE__ */ React.createElement(TextField, {
322
+ ...params,
323
+ label: "Tags",
324
+ onChange: (e) => setInputValue(e.target.value),
325
+ error: inputError,
326
+ helperText: "Add any relevant tags, hit 'Enter' to add new tags. Valid format: [a-z0-9+#] separated by [-], at most 63 characters"
327
+ })
328
+ }));
329
+ };
330
+
260
331
  const OwnerPicker = ({
261
- schema: {title = "Owner", description = "The owner of the component"},
332
+ schema: { title = "Owner", description = "The owner of the component" },
262
333
  uiSchema,
263
334
  ...props
264
335
  }) => {
@@ -275,12 +346,12 @@ const OwnerPicker = ({
275
346
  };
276
347
  return /* @__PURE__ */ React.createElement(EntityPicker, {
277
348
  ...props,
278
- schema: {title, description},
349
+ schema: { title, description },
279
350
  uiSchema: ownerUiSchema
280
351
  });
281
352
  };
282
353
 
283
- function splitFormData(url) {
354
+ function splitFormData(url, allowedOwners) {
284
355
  let host = void 0;
285
356
  let owner = void 0;
286
357
  let repo = void 0;
@@ -291,7 +362,7 @@ function splitFormData(url) {
291
362
  if (url) {
292
363
  const parsed = new URL(`https://${url}`);
293
364
  host = parsed.host;
294
- owner = parsed.searchParams.get("owner") || void 0;
365
+ owner = parsed.searchParams.get("owner") || (allowedOwners == null ? void 0 : allowedOwners[0]);
295
366
  repo = parsed.searchParams.get("repo") || void 0;
296
367
  organization = parsed.searchParams.get("organization") || void 0;
297
368
  workspace = parsed.searchParams.get("workspace") || void 0;
@@ -299,7 +370,7 @@ function splitFormData(url) {
299
370
  }
300
371
  } catch {
301
372
  }
302
- return {host, owner, repo, organization, workspace, project};
373
+ return { host, owner, repo, organization, workspace, project };
303
374
  }
304
375
  function serializeFormData(data) {
305
376
  if (!data.host) {
@@ -329,17 +400,18 @@ const RepoUrlPicker = ({
329
400
  rawErrors,
330
401
  formData
331
402
  }) => {
332
- var _a, _b, _c;
403
+ var _a, _b, _c, _d, _e;
333
404
  const scaffolderApi = useApi(scaffolderApiRef);
334
405
  const integrationApi = useApi(scmIntegrationsApiRef);
335
406
  const allowedHosts = (_a = uiSchema["ui:options"]) == null ? void 0 : _a.allowedHosts;
336
- const {value: integrations, loading} = useAsync(async () => {
337
- return await scaffolderApi.getIntegrationsList({allowedHosts});
407
+ const allowedOwners = (_b = uiSchema["ui:options"]) == null ? void 0 : _b.allowedOwners;
408
+ const { value: integrations, loading } = useAsync(async () => {
409
+ return await scaffolderApi.getIntegrationsList({ allowedHosts });
338
410
  });
339
- const {host, owner, repo, organization, workspace, project} = splitFormData(formData);
340
- const updateHost = useCallback((evt) => {
411
+ const { host, owner, repo, organization, workspace, project } = splitFormData(formData, allowedOwners);
412
+ const updateHost = useCallback((value) => {
341
413
  onChange(serializeFormData({
342
- host: evt.target.value,
414
+ host: value,
343
415
  owner,
344
416
  repo,
345
417
  organization,
@@ -347,6 +419,14 @@ const RepoUrlPicker = ({
347
419
  project
348
420
  }));
349
421
  }, [onChange, owner, repo, organization, workspace, project]);
422
+ const updateOwnerSelect = useCallback((value) => onChange(serializeFormData({
423
+ host,
424
+ owner: value,
425
+ repo,
426
+ organization,
427
+ workspace,
428
+ project
429
+ })), [onChange, host, repo, organization, workspace, project]);
350
430
  const updateOwner = useCallback((evt) => onChange(serializeFormData({
351
431
  host,
352
432
  owner: evt.target.value,
@@ -411,21 +491,20 @@ const RepoUrlPicker = ({
411
491
  if (loading) {
412
492
  return /* @__PURE__ */ React.createElement(Progress, null);
413
493
  }
494
+ const hostsOptions = integrations ? integrations.filter((i) => allowedHosts == null ? void 0 : allowedHosts.includes(i.host)).map((i) => ({ label: i.title, value: i.host })) : [{ label: "Loading...", value: "loading" }];
495
+ const ownersOptions = allowedOwners ? allowedOwners.map((i) => ({ label: i, value: i })) : [{ label: "Loading...", value: "loading" }];
414
496
  return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(FormControl, {
415
497
  margin: "normal",
416
498
  required: true,
417
499
  error: (rawErrors == null ? void 0 : rawErrors.length) > 0 && !host
418
- }, /* @__PURE__ */ React.createElement(InputLabel, {
419
- htmlFor: "hostInput"
420
- }, "Host"), /* @__PURE__ */ React.createElement(Select, {
500
+ }, /* @__PURE__ */ React.createElement(Select, {
421
501
  native: true,
422
- id: "hostInput",
502
+ disabled: hostsOptions.length === 1,
503
+ label: "Host",
423
504
  onChange: updateHost,
424
- value: host
425
- }, integrations ? integrations.filter((i) => allowedHosts == null ? void 0 : allowedHosts.includes(i.host)).map((i) => /* @__PURE__ */ React.createElement("option", {
426
- key: i.host,
427
- value: i.host
428
- }, i.title)) : /* @__PURE__ */ React.createElement("p", null, "loading")), /* @__PURE__ */ React.createElement(FormHelperText, null, "The host where the repository will be created")), host === "dev.azure.com" && /* @__PURE__ */ React.createElement(FormControl, {
505
+ selected: host,
506
+ items: hostsOptions
507
+ }), /* @__PURE__ */ React.createElement(FormHelperText, null, "The host where the repository will be created")), host === "dev.azure.com" && /* @__PURE__ */ React.createElement(FormControl, {
429
508
  margin: "normal",
430
509
  required: true,
431
510
  error: (rawErrors == null ? void 0 : rawErrors.length) > 0 && !organization
@@ -435,7 +514,7 @@ const RepoUrlPicker = ({
435
514
  id: "repoInput",
436
515
  onChange: updateOrganization,
437
516
  value: organization
438
- }), /* @__PURE__ */ React.createElement(FormHelperText, null, "The name of the organization")), host && ((_b = integrationApi.byHost(host)) == null ? void 0 : _b.type) === "bitbucket" && /* @__PURE__ */ React.createElement(React.Fragment, null, host === "bitbucket.org" && /* @__PURE__ */ React.createElement(FormControl, {
517
+ }), /* @__PURE__ */ React.createElement(FormHelperText, null, "The name of the organization")), host && ((_c = integrationApi.byHost(host)) == null ? void 0 : _c.type) === "bitbucket" && /* @__PURE__ */ React.createElement(React.Fragment, null, host === "bitbucket.org" && /* @__PURE__ */ React.createElement(FormControl, {
439
518
  margin: "normal",
440
519
  required: true,
441
520
  error: (rawErrors == null ? void 0 : rawErrors.length) > 0 && !workspace
@@ -455,7 +534,7 @@ const RepoUrlPicker = ({
455
534
  id: "wokrspaceInput",
456
535
  onChange: updateProject,
457
536
  value: project
458
- }), /* @__PURE__ */ React.createElement(FormHelperText, null, "The project where the repository will be created"))), host && ((_c = integrationApi.byHost(host)) == null ? void 0 : _c.type) !== "bitbucket" && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(FormControl, {
537
+ }), /* @__PURE__ */ React.createElement(FormHelperText, null, "The project where the repository will be created"))), host && ((_d = integrationApi.byHost(host)) == null ? void 0 : _d.type) !== "bitbucket" && !allowedOwners && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(FormControl, {
459
538
  margin: "normal",
460
539
  required: true,
461
540
  error: (rawErrors == null ? void 0 : rawErrors.length) > 0 && !owner
@@ -465,6 +544,17 @@ const RepoUrlPicker = ({
465
544
  id: "ownerInput",
466
545
  onChange: updateOwner,
467
546
  value: owner
547
+ }), /* @__PURE__ */ React.createElement(FormHelperText, null, "The organization, user or project that this repo will belong to"))), host && ((_e = integrationApi.byHost(host)) == null ? void 0 : _e.type) !== "bitbucket" && allowedOwners && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(FormControl, {
548
+ margin: "normal",
549
+ required: true,
550
+ error: (rawErrors == null ? void 0 : rawErrors.length) > 0 && !owner
551
+ }, /* @__PURE__ */ React.createElement(Select, {
552
+ native: true,
553
+ label: "Owner Available",
554
+ onChange: updateOwnerSelect,
555
+ disabled: ownersOptions.length === 1,
556
+ selected: owner,
557
+ items: ownersOptions
468
558
  }), /* @__PURE__ */ React.createElement(FormHelperText, null, "The organization, user or project that this repo will belong to"))), /* @__PURE__ */ React.createElement(FormControl, {
469
559
  margin: "normal",
470
560
  required: true,
@@ -481,7 +571,7 @@ const RepoUrlPicker = ({
481
571
  const repoPickerValidation = (value, validation, context) => {
482
572
  var _a;
483
573
  try {
484
- const {host, searchParams} = new URL(`https://${value}`);
574
+ const { host, searchParams } = new URL(`https://${value}`);
485
575
  const integrationApi = context.apiHolder.get(scmIntegrationsApiRef);
486
576
  if (!host) {
487
577
  validation.addError("Incomplete repository location provided, host not provided");
@@ -507,6 +597,47 @@ const repoPickerValidation = (value, validation, context) => {
507
597
  }
508
598
  };
509
599
 
600
+ const OwnedEntityPicker = ({
601
+ onChange,
602
+ schema: { title = "Entity", description = "An entity from the catalog" },
603
+ required,
604
+ uiSchema,
605
+ rawErrors,
606
+ formData,
607
+ idSchema
608
+ }) => {
609
+ var _a, _b;
610
+ const allowedKinds = (_a = uiSchema["ui:options"]) == null ? void 0 : _a.allowedKinds;
611
+ const defaultKind = (_b = uiSchema["ui:options"]) == null ? void 0 : _b.defaultKind;
612
+ const { ownedEntities, loading } = useOwnedEntities(allowedKinds);
613
+ const entityRefs = ownedEntities == null ? void 0 : ownedEntities.items.map((e) => formatEntityRefTitle(e, { defaultKind })).filter((n) => n);
614
+ const onSelect = (_, value) => {
615
+ onChange(value || "");
616
+ };
617
+ return /* @__PURE__ */ React.createElement(FormControl, {
618
+ margin: "normal",
619
+ required,
620
+ error: (rawErrors == null ? void 0 : rawErrors.length) > 0 && !formData
621
+ }, /* @__PURE__ */ React.createElement(Autocomplete, {
622
+ id: idSchema == null ? void 0 : idSchema.$id,
623
+ value: formData || "",
624
+ loading,
625
+ onChange: onSelect,
626
+ options: entityRefs || [],
627
+ autoSelect: true,
628
+ freeSolo: true,
629
+ renderInput: (params) => /* @__PURE__ */ React.createElement(TextField, {
630
+ ...params,
631
+ label: title,
632
+ margin: "normal",
633
+ helperText: description,
634
+ variant: "outlined",
635
+ required,
636
+ InputProps: params.InputProps
637
+ })
638
+ }));
639
+ };
640
+
510
641
  const FIELD_EXTENSION_WRAPPER_KEY = "scaffolder.extensions.wrapper.v1";
511
642
  const FIELD_EXTENSION_KEY = "scaffolder.extensions.field.v1";
512
643
  function createScaffolderFieldExtension(options) {
@@ -539,7 +670,7 @@ const scaffolderPlugin = createPlugin({
539
670
  identityApi: identityApiRef,
540
671
  scmIntegrationsApi: scmIntegrationsApiRef
541
672
  },
542
- factory: ({discoveryApi, identityApi, scmIntegrationsApi}) => new ScaffolderClient({discoveryApi, identityApi, scmIntegrationsApi})
673
+ factory: ({ discoveryApi, identityApi, scmIntegrationsApi }) => new ScaffolderClient({ discoveryApi, identityApi, scmIntegrationsApi })
543
674
  })
544
675
  ],
545
676
  routes: {
@@ -569,9 +700,17 @@ const OwnerPickerFieldExtension = scaffolderPlugin.provide(createScaffolderField
569
700
  }));
570
701
  const ScaffolderPage = scaffolderPlugin.provide(createRoutableExtension({
571
702
  name: "ScaffolderPage",
572
- component: () => import('./Router-8b2aa17e.esm.js').then((m) => m.Router),
703
+ component: () => import('./Router-552fb2ce.esm.js').then((m) => m.Router),
573
704
  mountPoint: rootRouteRef
574
705
  }));
706
+ const OwnedEntityPickerFieldExtension = scaffolderPlugin.provide(createScaffolderFieldExtension({
707
+ component: OwnedEntityPicker,
708
+ name: "OwnedEntityPicker"
709
+ }));
710
+ const EntityTagsPickerFieldExtension = scaffolderPlugin.provide(createScaffolderFieldExtension({
711
+ component: EntityTagsPicker,
712
+ name: "EntityTagsPicker"
713
+ }));
575
714
 
576
715
  const YellowStar = withStyles({
577
716
  root: {
@@ -595,7 +734,7 @@ const favouriteTemplateTooltip = (isStarred) => isStarred ? "Remove from favorit
595
734
  const favouriteTemplateIcon = (isStarred) => isStarred ? /* @__PURE__ */ React.createElement(YellowStar, null) : /* @__PURE__ */ React.createElement(WhiteBorderStar, null);
596
735
  const FavouriteTemplate = (props) => {
597
736
  const classes = useStyles$1();
598
- const {toggleStarredEntity, isStarredEntity} = useStarredEntity(props.entity);
737
+ const { toggleStarredEntity, isStarredEntity } = useStarredEntity(props.entity);
599
738
  return /* @__PURE__ */ React.createElement(IconButton, {
600
739
  color: "inherit",
601
740
  className: classes.starButton,
@@ -611,7 +750,7 @@ const useStyles = makeStyles((theme) => ({
611
750
  position: "relative"
612
751
  },
613
752
  title: {
614
- backgroundImage: ({backgroundImage}) => backgroundImage
753
+ backgroundImage: ({ backgroundImage }) => backgroundImage
615
754
  },
616
755
  box: {
617
756
  overflow: "hidden",
@@ -659,7 +798,7 @@ const getTemplateCardProps = (template) => {
659
798
  const DeprecationWarning = () => {
660
799
  const styles = useDeprecationStyles();
661
800
  const Title = /* @__PURE__ */ React.createElement(Typography, {
662
- style: {padding: 10, maxWidth: 300}
801
+ style: { padding: 10, maxWidth: 300 }
663
802
  }, "This template syntax is deprecated. Click for more info.");
664
803
  return /* @__PURE__ */ React.createElement("div", {
665
804
  className: styles.deprecationIcon
@@ -670,15 +809,15 @@ const DeprecationWarning = () => {
670
809
  className: styles.link
671
810
  }, /* @__PURE__ */ React.createElement(WarningIcon, null))));
672
811
  };
673
- const TemplateCard = ({template, deprecated}) => {
812
+ const TemplateCard = ({ template, deprecated }) => {
674
813
  var _a;
675
814
  const backstageTheme = useTheme();
676
815
  const rootLink = useRouteRef(rootRouteRef);
677
816
  const templateProps = getTemplateCardProps(template);
678
817
  const ownedByRelations = getEntityRelations(template, RELATION_OWNED_BY);
679
- const themeId = backstageTheme.getPageTheme({themeId: templateProps.type}) ? templateProps.type : "other";
680
- const theme = backstageTheme.getPageTheme({themeId});
681
- const classes = useStyles({backgroundImage: theme.backgroundImage});
818
+ const themeId = backstageTheme.getPageTheme({ themeId: templateProps.type }) ? templateProps.type : "other";
819
+ const theme = backstageTheme.getPageTheme({ themeId });
820
+ const classes = useStyles({ backgroundImage: theme.backgroundImage });
682
821
  const href = generatePath(`${rootLink()}/templates/:templateName`, {
683
822
  templateName: templateProps.name
684
823
  });
@@ -691,9 +830,9 @@ const TemplateCard = ({template, deprecated}) => {
691
830
  }), deprecated && /* @__PURE__ */ React.createElement(DeprecationWarning, null), /* @__PURE__ */ React.createElement(ItemCardHeader, {
692
831
  title: templateProps.title,
693
832
  subtitle: templateProps.type,
694
- classes: {root: classes.title}
833
+ classes: { root: classes.title }
695
834
  })), /* @__PURE__ */ React.createElement(CardContent, {
696
- style: {display: "grid"}
835
+ style: { display: "grid" }
697
836
  }, /* @__PURE__ */ React.createElement(Box, {
698
837
  className: classes.box
699
838
  }, /* @__PURE__ */ React.createElement(Typography, {
@@ -730,7 +869,7 @@ const TemplateList = ({
730
869
  TemplateCardComponent,
731
870
  group
732
871
  }) => {
733
- const {loading, error, entities} = useEntityListProvider();
872
+ const { loading, error, entities } = useEntityListProvider();
734
873
  const Card = TemplateCardComponent || TemplateCard;
735
874
  const maybeFilteredEntities = group ? entities.filter((e) => group.filter(e)) : entities;
736
875
  const title = group ? group.titleComponent || /* @__PURE__ */ React.createElement(ContentHeader, {
@@ -745,8 +884,8 @@ const TemplateList = ({
745
884
  title: "Oops! Something went wrong loading the templates"
746
885
  }, error.message), !error && !loading && !entities.length && /* @__PURE__ */ React.createElement(Typography, {
747
886
  variant: "body2"
748
- }, "No templates found that match your filter. Learn more about", " ", /* @__PURE__ */ React.createElement(Link, {
749
- href: "https://backstage.io/docs/features/software-templates/adding-templates"
887
+ }, "No templates found that match your filter. Learn more about", " ", /* @__PURE__ */ React.createElement(Link$1, {
888
+ to: "https://backstage.io/docs/features/software-templates/adding-templates"
750
889
  }, "adding templates"), "."), /* @__PURE__ */ React.createElement(Content, null, title, /* @__PURE__ */ React.createElement(ItemCardGrid, null, maybeFilteredEntities && (maybeFilteredEntities == null ? void 0 : maybeFilteredEntities.length) > 0 && maybeFilteredEntities.map((template) => /* @__PURE__ */ React.createElement(Card, {
751
890
  key: stringifyEntityRef(template),
752
891
  template
@@ -761,7 +900,7 @@ const checkedIcon = /* @__PURE__ */ React.createElement(CheckBoxIcon, {
761
900
  });
762
901
  const TemplateTypePicker = () => {
763
902
  const alertApi = useApi(alertApiRef);
764
- const {error, loading, availableTypes, selectedTypes, setSelectedTypes} = useEntityTypeFilter();
903
+ const { error, loading, availableTypes, selectedTypes, setSelectedTypes } = useEntityTypeFilter();
765
904
  if (loading)
766
905
  return /* @__PURE__ */ React.createElement(Progress, null);
767
906
  if (!availableTypes)
@@ -784,7 +923,7 @@ const TemplateTypePicker = () => {
784
923
  options: availableTypes,
785
924
  value: selectedTypes,
786
925
  onChange: (_, value) => setSelectedTypes(value),
787
- renderOption: (option, {selected}) => /* @__PURE__ */ React.createElement(FormControlLabel, {
926
+ renderOption: (option, { selected }) => /* @__PURE__ */ React.createElement(FormControlLabel, {
788
927
  control: /* @__PURE__ */ React.createElement(Checkbox, {
789
928
  icon,
790
929
  checkedIcon,
@@ -803,5 +942,5 @@ const TemplateTypePicker = () => {
803
942
  }));
804
943
  };
805
944
 
806
- export { EntityPicker as E, FIELD_EXTENSION_WRAPPER_KEY as F, OwnerPicker as O, RepoUrlPicker as R, ScaffolderClient as S, TemplateTypePicker as T, EntityNamePicker as a, registerComponentRouteRef as b, TemplateList as c, rootRouteRef as d, entityNamePickerValidation as e, FIELD_EXTENSION_KEY as f, createScaffolderFieldExtension as g, ScaffolderFieldExtensions as h, EntityPickerFieldExtension as i, EntityNamePickerFieldExtension as j, OwnerPickerFieldExtension as k, RepoUrlPickerFieldExtension as l, ScaffolderPage as m, scaffolderPlugin as n, TextValuePicker as o, FavouriteTemplate as p, repoPickerValidation as r, scaffolderApiRef as s };
807
- //# sourceMappingURL=index-385b3fc1.esm.js.map
945
+ export { EntityPicker as E, FIELD_EXTENSION_WRAPPER_KEY as F, OwnerPicker as O, RepoUrlPicker as R, ScaffolderClient as S, TemplateTypePicker as T, EntityNamePicker as a, EntityTagsPicker as b, OwnedEntityPicker as c, registerComponentRouteRef as d, entityNamePickerValidation as e, TemplateList as f, rootRouteRef as g, FIELD_EXTENSION_KEY as h, createScaffolderFieldExtension as i, ScaffolderFieldExtensions as j, EntityPickerFieldExtension as k, EntityNamePickerFieldExtension as l, EntityTagsPickerFieldExtension as m, OwnerPickerFieldExtension as n, OwnedEntityPickerFieldExtension as o, RepoUrlPickerFieldExtension as p, ScaffolderPage as q, repoPickerValidation as r, scaffolderApiRef as s, scaffolderPlugin as t, TextValuePicker as u, FavouriteTemplate as v };
946
+ //# sourceMappingURL=index-768702c3.esm.js.map