@backstage/plugin-scaffolder 0.11.16 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { createApiRef, useApi, attachComponentData, createExternalRouteRef, createRouteRef, createPlugin, createApiFactory, discoveryApiRef, identityApiRef, createRoutableExtension, useRouteRef, alertApiRef } from '@backstage/core-plugin-api';
1
+ import { createApiRef, useApi, attachComponentData, createExternalRouteRef, createRouteRef, createPlugin, createApiFactory, discoveryApiRef, fetchApiRef, createRoutableExtension, useRouteRef, alertApiRef } from '@backstage/core-plugin-api';
2
2
  import { ResponseError } from '@backstage/errors';
3
3
  import qs from 'qs';
4
4
  import ObservableImpl from 'zen-observable';
@@ -6,15 +6,16 @@ import { catalogApiRef, formatEntityRefTitle, useOwnedEntities, useStarredEntity
6
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, useState } from 'react';
10
- import { useAsync, useEffectOnce } from 'react-use';
9
+ import React, { useCallback, useEffect, useState, useMemo } from 'react';
10
+ import useAsync from 'react-use/lib/useAsync';
11
11
  import { KubernetesValidatorFunctions, makeValidator, RELATION_OWNED_BY, stringifyEntityRef } from '@backstage/catalog-model';
12
+ import useEffectOnce from 'react-use/lib/useEffectOnce';
12
13
  import { Autocomplete as Autocomplete$1 } from '@material-ui/lab';
13
- import { Progress, Select, ItemCardHeader, Button, ContentHeader, WarningPanel, Link as Link$1, Content, ItemCardGrid } from '@backstage/core-components';
14
14
  import { scmIntegrationsApiRef, ScmIntegrationIcon } from '@backstage/integration-react';
15
15
  import FormHelperText from '@material-ui/core/FormHelperText';
16
16
  import Input from '@material-ui/core/Input';
17
17
  import InputLabel from '@material-ui/core/InputLabel';
18
+ import { Select, Progress, ItemCardHeader, Button, ContentHeader, WarningPanel, Link as Link$1, Content, ItemCardGrid } from '@backstage/core-components';
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';
@@ -29,11 +30,11 @@ const scaffolderApiRef = createApiRef({
29
30
  });
30
31
  class ScaffolderClient {
31
32
  constructor(options) {
32
- var _a;
33
+ var _a, _b;
33
34
  this.discoveryApi = options.discoveryApi;
34
- this.identityApi = options.identityApi;
35
+ this.fetchApi = (_a = options.fetchApi) != null ? _a : { fetch };
35
36
  this.scmIntegrationsApi = options.scmIntegrationsApi;
36
- this.useLongPollingLogs = (_a = options.useLongPollingLogs) != null ? _a : false;
37
+ this.useLongPollingLogs = (_b = options.useLongPollingLogs) != null ? _b : false;
37
38
  }
38
39
  async getIntegrationsList(options) {
39
40
  return [
@@ -45,31 +46,28 @@ class ScaffolderClient {
45
46
  }
46
47
  async getTemplateParameterSchema(templateName) {
47
48
  const { namespace, kind, name } = templateName;
48
- const token = await this.identityApi.getIdToken();
49
49
  const baseUrl = await this.discoveryApi.getBaseUrl("scaffolder");
50
50
  const templatePath = [namespace, kind, name].map((s) => encodeURIComponent(s)).join("/");
51
51
  const url = `${baseUrl}/v2/templates/${templatePath}/parameter-schema`;
52
- const response = await fetch(url, {
53
- headers: {
54
- ...token && { Authorization: `Bearer ${token}` }
55
- }
56
- });
52
+ const response = await this.fetchApi.fetch(url);
57
53
  if (!response.ok) {
58
54
  throw await ResponseError.fromResponse(response);
59
55
  }
60
56
  const schema = await response.json();
61
57
  return schema;
62
58
  }
63
- async scaffold(templateName, values) {
64
- const token = await this.identityApi.getIdToken();
59
+ async scaffold(templateName, values, secrets = {}) {
65
60
  const url = `${await this.discoveryApi.getBaseUrl("scaffolder")}/v2/tasks`;
66
- const response = await fetch(url, {
61
+ const response = await this.fetchApi.fetch(url, {
67
62
  method: "POST",
68
63
  headers: {
69
- "Content-Type": "application/json",
70
- ...token && { Authorization: `Bearer ${token}` }
64
+ "Content-Type": "application/json"
71
65
  },
72
- body: JSON.stringify({ templateName, values: { ...values } })
66
+ body: JSON.stringify({
67
+ templateName,
68
+ values: { ...values },
69
+ secrets
70
+ })
73
71
  });
74
72
  if (response.status !== 201) {
75
73
  const status = `${response.status} ${response.statusText}`;
@@ -80,22 +78,19 @@ class ScaffolderClient {
80
78
  return id;
81
79
  }
82
80
  async getTask(taskId) {
83
- const token = await this.identityApi.getIdToken();
84
81
  const baseUrl = await this.discoveryApi.getBaseUrl("scaffolder");
85
82
  const url = `${baseUrl}/v2/tasks/${encodeURIComponent(taskId)}`;
86
- const response = await fetch(url, {
87
- headers: token ? { Authorization: `Bearer ${token}` } : {}
88
- });
83
+ const response = await this.fetchApi.fetch(url);
89
84
  if (!response.ok) {
90
85
  throw await ResponseError.fromResponse(response);
91
86
  }
92
87
  return await response.json();
93
88
  }
94
- streamLogs(opts) {
89
+ streamLogs(options) {
95
90
  if (this.useLongPollingLogs) {
96
- return this.streamLogsPolling(opts);
91
+ return this.streamLogsPolling(options);
97
92
  }
98
- return this.streamLogsEventStream(opts);
93
+ return this.streamLogsEventStream(options);
99
94
  }
100
95
  streamLogsEventStream({
101
96
  taskId,
@@ -146,7 +141,7 @@ class ScaffolderClient {
146
141
  this.discoveryApi.getBaseUrl("scaffolder").then(async (baseUrl) => {
147
142
  while (!subscriber.closed) {
148
143
  const url = `${baseUrl}/v2/tasks/${encodeURIComponent(taskId)}/events?${qs.stringify({ after })}`;
149
- const response = await fetch(url);
144
+ const response = await this.fetchApi.fetch(url);
150
145
  if (!response.ok) {
151
146
  await new Promise((resolve) => setTimeout(resolve, 1e3));
152
147
  continue;
@@ -166,10 +161,7 @@ class ScaffolderClient {
166
161
  }
167
162
  async listActions() {
168
163
  const baseUrl = await this.discoveryApi.getBaseUrl("scaffolder");
169
- const token = await this.identityApi.getIdToken();
170
- const response = await fetch(`${baseUrl}/v2/actions`, {
171
- headers: token ? { Authorization: `Bearer ${token}` } : {}
172
- });
164
+ const response = await this.fetchApi.fetch(`${baseUrl}/v2/actions`);
173
165
  if (!response.ok) {
174
166
  throw await ResponseError.fromResponse(response);
175
167
  }
@@ -350,221 +342,273 @@ const OwnerPicker = ({
350
342
  });
351
343
  };
352
344
 
353
- function splitFormData(url, allowedOwners) {
354
- let host = void 0;
355
- let owner = void 0;
356
- let repo = void 0;
357
- let organization = void 0;
358
- let workspace = void 0;
359
- let project = void 0;
360
- try {
361
- if (url) {
362
- const parsed = new URL(`https://${url}`);
363
- host = parsed.host;
364
- owner = parsed.searchParams.get("owner") || (allowedOwners == null ? void 0 : allowedOwners[0]);
365
- repo = parsed.searchParams.get("repo") || void 0;
366
- organization = parsed.searchParams.get("organization") || void 0;
367
- workspace = parsed.searchParams.get("workspace") || void 0;
368
- project = parsed.searchParams.get("project") || void 0;
369
- }
370
- } catch {
371
- }
372
- return { host, owner, repo, organization, workspace, project };
373
- }
374
- function serializeFormData(data) {
375
- if (!data.host) {
376
- return void 0;
377
- }
378
- const params = new URLSearchParams();
379
- if (data.owner) {
380
- params.set("owner", data.owner);
381
- }
382
- if (data.repo) {
383
- params.set("repo", data.repo);
384
- }
385
- if (data.organization) {
386
- params.set("organization", data.organization);
387
- }
388
- if (data.workspace) {
389
- params.set("workspace", data.workspace);
390
- }
391
- if (data.project) {
392
- params.set("project", data.project);
393
- }
394
- return `${data.host}?${params.toString()}`;
395
- }
396
- const RepoUrlPicker = ({
397
- onChange,
398
- uiSchema,
399
- rawErrors,
400
- formData
401
- }) => {
402
- var _a, _b, _c, _d, _e;
403
- const scaffolderApi = useApi(scaffolderApiRef);
404
- const integrationApi = useApi(scmIntegrationsApiRef);
405
- const allowedHosts = (_a = uiSchema["ui:options"]) == null ? void 0 : _a.allowedHosts;
406
- const allowedOwners = (_b = uiSchema["ui:options"]) == null ? void 0 : _b.allowedOwners;
407
- const { value: integrations, loading } = useAsync(async () => {
408
- return await scaffolderApi.getIntegrationsList({ allowedHosts });
409
- });
410
- const { host, owner, repo, organization, workspace, project } = splitFormData(formData, allowedOwners);
411
- const updateHost = useCallback((value) => {
412
- onChange(serializeFormData({
413
- host: value,
414
- owner,
415
- repo,
416
- organization,
417
- workspace,
418
- project
419
- }));
420
- }, [onChange, owner, repo, organization, workspace, project]);
421
- const updateOwnerSelect = useCallback((value) => onChange(serializeFormData({
422
- host,
423
- owner: value,
424
- repo,
425
- organization,
426
- workspace,
427
- project
428
- })), [onChange, host, repo, organization, workspace, project]);
429
- const updateOwner = useCallback((evt) => onChange(serializeFormData({
430
- host,
431
- owner: evt.target.value,
432
- repo,
433
- organization,
434
- workspace,
435
- project
436
- })), [onChange, host, repo, organization, workspace, project]);
437
- const updateRepo = useCallback((evt) => onChange(serializeFormData({
438
- host,
439
- owner,
440
- repo: evt.target.value,
441
- organization,
442
- workspace,
443
- project
444
- })), [onChange, host, owner, organization, workspace, project]);
445
- const updateOrganization = useCallback((evt) => onChange(serializeFormData({
446
- host,
447
- owner,
448
- repo,
449
- organization: evt.target.value,
450
- workspace,
451
- project
452
- })), [onChange, host, owner, repo, workspace, project]);
453
- const updateWorkspace = useCallback((evt) => onChange(serializeFormData({
454
- host,
455
- owner,
456
- repo,
457
- organization,
458
- workspace: evt.target.value,
459
- project
460
- })), [onChange, host, owner, repo, organization, project]);
461
- const updateProject = useCallback((evt) => onChange(serializeFormData({
462
- host,
463
- owner,
464
- repo,
465
- organization,
466
- workspace,
467
- project: evt.target.value
468
- })), [onChange, host, owner, repo, organization, workspace]);
469
- useEffect(() => {
470
- if (host === void 0 && (integrations == null ? void 0 : integrations.length)) {
471
- onChange(serializeFormData({
472
- host: integrations[0].host,
473
- owner,
474
- repo,
475
- organization,
476
- workspace,
477
- project
478
- }));
479
- }
480
- }, [
481
- onChange,
482
- integrations,
483
- host,
484
- owner,
485
- repo,
486
- organization,
487
- workspace,
488
- project
489
- ]);
490
- if (loading) {
491
- return /* @__PURE__ */ React.createElement(Progress, null);
492
- }
493
- 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" }];
494
- const ownersOptions = allowedOwners ? allowedOwners.map((i) => ({ label: i, value: i })) : [{ label: "Loading...", value: "loading" }];
345
+ const GithubRepoPicker = (props) => {
346
+ const { allowedOwners = [], rawErrors, state, onChange } = props;
347
+ const ownerItems = allowedOwners ? allowedOwners.map((i) => ({ label: i, value: i })) : [{ label: "Loading...", value: "loading" }];
348
+ const { owner, repoName } = state;
495
349
  return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(FormControl, {
496
350
  margin: "normal",
497
351
  required: true,
498
- error: (rawErrors == null ? void 0 : rawErrors.length) > 0 && !host
499
- }, /* @__PURE__ */ React.createElement(Select, {
352
+ error: (rawErrors == null ? void 0 : rawErrors.length) > 0 && !owner
353
+ }, (allowedOwners == null ? void 0 : allowedOwners.length) ? /* @__PURE__ */ React.createElement(Select, {
500
354
  native: true,
501
- disabled: hostsOptions.length === 1,
502
- label: "Host",
503
- onChange: updateHost,
504
- selected: host,
505
- items: hostsOptions
506
- }), /* @__PURE__ */ React.createElement(FormHelperText, null, "The host where the repository will be created")), host === "dev.azure.com" && /* @__PURE__ */ React.createElement(FormControl, {
355
+ label: "Owner Available",
356
+ onChange: (s) => onChange({ owner: String(Array.isArray(s) ? s[0] : s) }),
357
+ disabled: allowedOwners.length === 1,
358
+ selected: owner,
359
+ items: ownerItems
360
+ }) : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(InputLabel, {
361
+ htmlFor: "ownerInput"
362
+ }, "Owner"), /* @__PURE__ */ React.createElement(Input, {
363
+ id: "ownerInput",
364
+ onChange: (e) => onChange({ owner: e.target.value }),
365
+ value: owner
366
+ })), /* @__PURE__ */ React.createElement(FormHelperText, null, "The organization, user or project that this repo will belong to")), /* @__PURE__ */ React.createElement(FormControl, {
367
+ margin: "normal",
368
+ required: true,
369
+ error: (rawErrors == null ? void 0 : rawErrors.length) > 0 && !repoName
370
+ }, /* @__PURE__ */ React.createElement(InputLabel, {
371
+ htmlFor: "repoNameInput"
372
+ }, "Repository"), /* @__PURE__ */ React.createElement(Input, {
373
+ id: "repoNameInput",
374
+ onChange: (e) => onChange({ repoName: e.target.value }),
375
+ value: repoName
376
+ }), /* @__PURE__ */ React.createElement(FormHelperText, null, "The name of the repository")));
377
+ };
378
+
379
+ const GitlabRepoPicker = (props) => {
380
+ const { allowedOwners = [], rawErrors, state, onChange } = props;
381
+ const ownerItems = allowedOwners ? allowedOwners.map((i) => ({ label: i, value: i })) : [{ label: "Loading...", value: "loading" }];
382
+ const { owner, repoName } = state;
383
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(FormControl, {
384
+ margin: "normal",
385
+ required: true,
386
+ error: (rawErrors == null ? void 0 : rawErrors.length) > 0 && !owner
387
+ }, (allowedOwners == null ? void 0 : allowedOwners.length) ? /* @__PURE__ */ React.createElement(Select, {
388
+ native: true,
389
+ label: "Owner Available",
390
+ onChange: (selected) => onChange({
391
+ owner: String(Array.isArray(selected) ? selected[0] : selected)
392
+ }),
393
+ disabled: allowedOwners.length === 1,
394
+ selected: owner,
395
+ items: ownerItems
396
+ }) : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(InputLabel, {
397
+ htmlFor: "ownerInput"
398
+ }, "Owner"), /* @__PURE__ */ React.createElement(Input, {
399
+ id: "ownerInput",
400
+ onChange: (e) => onChange({ owner: e.target.value }),
401
+ value: owner
402
+ })), /* @__PURE__ */ React.createElement(FormHelperText, null, "The organization, user or project that this repo will belong to")), /* @__PURE__ */ React.createElement(FormControl, {
403
+ margin: "normal",
404
+ required: true,
405
+ error: (rawErrors == null ? void 0 : rawErrors.length) > 0 && !repoName
406
+ }, /* @__PURE__ */ React.createElement(InputLabel, {
407
+ htmlFor: "repoNameInput"
408
+ }, "Repository"), /* @__PURE__ */ React.createElement(Input, {
409
+ id: "repoNameInput",
410
+ onChange: (e) => onChange({ repoName: e.target.value }),
411
+ value: repoName
412
+ }), /* @__PURE__ */ React.createElement(FormHelperText, null, "The name of the repository")));
413
+ };
414
+
415
+ const AzureRepoPicker = (props) => {
416
+ const { rawErrors, state, onChange } = props;
417
+ const { organization, repoName, owner } = state;
418
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(FormControl, {
507
419
  margin: "normal",
508
420
  required: true,
509
421
  error: (rawErrors == null ? void 0 : rawErrors.length) > 0 && !organization
510
422
  }, /* @__PURE__ */ React.createElement(InputLabel, {
511
- htmlFor: "repoInput"
423
+ htmlFor: "orgInput"
512
424
  }, "Organization"), /* @__PURE__ */ React.createElement(Input, {
513
- id: "repoInput",
514
- onChange: updateOrganization,
425
+ id: "orgInput",
426
+ onChange: (e) => onChange({ organization: e.target.value }),
515
427
  value: organization
516
- }), /* @__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, {
428
+ }), /* @__PURE__ */ React.createElement(FormHelperText, null, "The organization that this repo will belong to")), /* @__PURE__ */ React.createElement(FormControl, {
429
+ margin: "normal",
430
+ required: true,
431
+ error: (rawErrors == null ? void 0 : rawErrors.length) > 0 && !owner
432
+ }, /* @__PURE__ */ React.createElement(InputLabel, {
433
+ htmlFor: "ownerInput"
434
+ }, "Owner"), /* @__PURE__ */ React.createElement(Input, {
435
+ id: "ownerInput",
436
+ onChange: (e) => onChange({ owner: e.target.value }),
437
+ value: owner
438
+ }), /* @__PURE__ */ React.createElement(FormHelperText, null, "The Owner that this repo will belong to")), /* @__PURE__ */ React.createElement(FormControl, {
439
+ margin: "normal",
440
+ required: true,
441
+ error: (rawErrors == null ? void 0 : rawErrors.length) > 0 && !repoName
442
+ }, /* @__PURE__ */ React.createElement(InputLabel, {
443
+ htmlFor: "repoNameInput"
444
+ }, "Repository"), /* @__PURE__ */ React.createElement(Input, {
445
+ id: "repoNameInput",
446
+ onChange: (e) => onChange({ repoName: e.target.value }),
447
+ value: repoName
448
+ }), /* @__PURE__ */ React.createElement(FormHelperText, null, "The name of the repository")));
449
+ };
450
+
451
+ const BitbucketRepoPicker = (props) => {
452
+ const { onChange, rawErrors, state } = props;
453
+ const { host, workspace, project, repoName } = state;
454
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, host === "bitbucket.org" && /* @__PURE__ */ React.createElement(FormControl, {
517
455
  margin: "normal",
518
456
  required: true,
519
457
  error: (rawErrors == null ? void 0 : rawErrors.length) > 0 && !workspace
520
458
  }, /* @__PURE__ */ React.createElement(InputLabel, {
521
- htmlFor: "wokrspaceInput"
459
+ htmlFor: "workspaceInput"
522
460
  }, "Workspace"), /* @__PURE__ */ React.createElement(Input, {
523
- id: "wokrspaceInput",
524
- onChange: updateWorkspace,
461
+ id: "workspaceInput",
462
+ onChange: (e) => onChange({ workspace: e.target.value }),
525
463
  value: workspace
526
- }), /* @__PURE__ */ React.createElement(FormHelperText, null, "The workspace where the repository will be created")), /* @__PURE__ */ React.createElement(FormControl, {
464
+ }), /* @__PURE__ */ React.createElement(FormHelperText, null, "The Organization that this repo will belong to")), /* @__PURE__ */ React.createElement(FormControl, {
527
465
  margin: "normal",
528
466
  required: true,
529
467
  error: (rawErrors == null ? void 0 : rawErrors.length) > 0 && !project
530
468
  }, /* @__PURE__ */ React.createElement(InputLabel, {
531
- htmlFor: "wokrspaceInput"
469
+ htmlFor: "projectInput"
532
470
  }, "Project"), /* @__PURE__ */ React.createElement(Input, {
533
- id: "wokrspaceInput",
534
- onChange: updateProject,
471
+ id: "projectInput",
472
+ onChange: (e) => onChange({ project: e.target.value }),
535
473
  value: project
536
- }), /* @__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, {
474
+ }), /* @__PURE__ */ React.createElement(FormHelperText, null, "The Project that this repo will belong to")), /* @__PURE__ */ React.createElement(FormControl, {
537
475
  margin: "normal",
538
476
  required: true,
539
- error: (rawErrors == null ? void 0 : rawErrors.length) > 0 && !owner
477
+ error: (rawErrors == null ? void 0 : rawErrors.length) > 0 && !repoName
540
478
  }, /* @__PURE__ */ React.createElement(InputLabel, {
541
- htmlFor: "ownerInput"
542
- }, "Owner"), /* @__PURE__ */ React.createElement(Input, {
543
- id: "ownerInput",
544
- onChange: updateOwner,
545
- value: owner
546
- }), /* @__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, {
479
+ htmlFor: "repoNameInput"
480
+ }, "Repository"), /* @__PURE__ */ React.createElement(Input, {
481
+ id: "repoNameInput",
482
+ onChange: (e) => onChange({ repoName: e.target.value }),
483
+ value: repoName
484
+ }), /* @__PURE__ */ React.createElement(FormHelperText, null, "The name of the repository")));
485
+ };
486
+
487
+ const RepoUrlPickerHost = (props) => {
488
+ const { host, hosts, onChange, rawErrors } = props;
489
+ const scaffolderApi = useApi(scaffolderApiRef);
490
+ const { value: integrations, loading } = useAsync(async () => {
491
+ return await scaffolderApi.getIntegrationsList({
492
+ allowedHosts: hosts != null ? hosts : []
493
+ });
494
+ });
495
+ useEffect(() => {
496
+ if (hosts && !host) {
497
+ onChange(hosts[0]);
498
+ }
499
+ }, [hosts, host, onChange]);
500
+ const hostsOptions = integrations ? integrations.filter((i) => hosts == null ? void 0 : hosts.includes(i.host)).map((i) => ({ label: i.title, value: i.host })) : [{ label: "Loading...", value: "loading" }];
501
+ if (loading) {
502
+ return /* @__PURE__ */ React.createElement(Progress, null);
503
+ }
504
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(FormControl, {
547
505
  margin: "normal",
548
506
  required: true,
549
- error: (rawErrors == null ? void 0 : rawErrors.length) > 0 && !owner
507
+ error: (rawErrors == null ? void 0 : rawErrors.length) > 0 && !host
550
508
  }, /* @__PURE__ */ React.createElement(Select, {
551
509
  native: true,
552
- label: "Owner Available",
553
- onChange: updateOwnerSelect,
554
- disabled: ownersOptions.length === 1,
555
- selected: owner,
556
- items: ownersOptions
557
- }), /* @__PURE__ */ React.createElement(FormHelperText, null, "The organization, user or project that this repo will belong to"))), /* @__PURE__ */ React.createElement(FormControl, {
558
- margin: "normal",
559
- required: true,
560
- error: (rawErrors == null ? void 0 : rawErrors.length) > 0 && !repo
561
- }, /* @__PURE__ */ React.createElement(InputLabel, {
562
- htmlFor: "repoInput"
563
- }, "Repository"), /* @__PURE__ */ React.createElement(Input, {
564
- id: "repoInput",
565
- onChange: updateRepo,
566
- value: repo
567
- }), /* @__PURE__ */ React.createElement(FormHelperText, null, "The name of the repository")));
510
+ disabled: (hosts == null ? void 0 : hosts.length) === 1,
511
+ label: "Host",
512
+ onChange: (s) => onChange(String(Array.isArray(s) ? s[0] : s)),
513
+ selected: host,
514
+ items: hostsOptions,
515
+ "data-testid": "host-select"
516
+ }), /* @__PURE__ */ React.createElement(FormHelperText, null, "The host where the repository will be created")));
517
+ };
518
+
519
+ function serializeRepoPickerUrl(data) {
520
+ if (!data.host) {
521
+ return void 0;
522
+ }
523
+ const params = new URLSearchParams();
524
+ if (data.owner) {
525
+ params.set("owner", data.owner);
526
+ }
527
+ if (data.repoName) {
528
+ params.set("repo", data.repoName);
529
+ }
530
+ if (data.organization) {
531
+ params.set("organization", data.organization);
532
+ }
533
+ if (data.workspace) {
534
+ params.set("workspace", data.workspace);
535
+ }
536
+ if (data.project) {
537
+ params.set("project", data.project);
538
+ }
539
+ return `${data.host}?${params.toString()}`;
540
+ }
541
+ function parseRepoPickerUrl(url) {
542
+ let host = void 0;
543
+ let owner = void 0;
544
+ let repoName = void 0;
545
+ let organization = void 0;
546
+ let workspace = void 0;
547
+ let project = void 0;
548
+ try {
549
+ if (url) {
550
+ const parsed = new URL(`https://${url}`);
551
+ host = parsed.host;
552
+ owner = parsed.searchParams.get("owner") || void 0;
553
+ repoName = parsed.searchParams.get("repo") || void 0;
554
+ organization = parsed.searchParams.get("organization") || void 0;
555
+ workspace = parsed.searchParams.get("workspace") || void 0;
556
+ project = parsed.searchParams.get("project") || void 0;
557
+ }
558
+ } catch {
559
+ }
560
+ return { host, owner, repoName, organization, workspace, project };
561
+ }
562
+
563
+ const RepoUrlPicker = (props) => {
564
+ var _a, _b;
565
+ const { uiSchema, onChange, rawErrors, formData } = props;
566
+ const [state, setState] = useState(parseRepoPickerUrl(formData));
567
+ const integrationApi = useApi(scmIntegrationsApiRef);
568
+ const allowedHosts = useMemo(() => {
569
+ var _a2, _b2;
570
+ return (_b2 = (_a2 = uiSchema == null ? void 0 : uiSchema["ui:options"]) == null ? void 0 : _a2.allowedHosts) != null ? _b2 : [];
571
+ }, [uiSchema]);
572
+ const allowedOwners = useMemo(() => {
573
+ var _a2, _b2;
574
+ return (_b2 = (_a2 = uiSchema == null ? void 0 : uiSchema["ui:options"]) == null ? void 0 : _a2.allowedOwners) != null ? _b2 : [];
575
+ }, [uiSchema]);
576
+ useEffect(() => {
577
+ onChange(serializeRepoPickerUrl(state));
578
+ }, [state, onChange]);
579
+ useEffect(() => {
580
+ if (allowedOwners.length === 1) {
581
+ setState((prevState) => ({ ...prevState, owner: allowedOwners[0] }));
582
+ }
583
+ }, [setState, allowedOwners]);
584
+ const updateLocalState = useCallback((newState) => {
585
+ setState((prevState) => ({ ...prevState, ...newState }));
586
+ }, [setState]);
587
+ const hostType = (_b = state.host && ((_a = integrationApi.byHost(state.host)) == null ? void 0 : _a.type)) != null ? _b : null;
588
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(RepoUrlPickerHost, {
589
+ host: state.host,
590
+ hosts: allowedHosts,
591
+ onChange: (host) => setState((prevState) => ({ ...prevState, host })),
592
+ rawErrors
593
+ }), hostType === "github" && /* @__PURE__ */ React.createElement(GithubRepoPicker, {
594
+ allowedOwners,
595
+ rawErrors,
596
+ state,
597
+ onChange: updateLocalState
598
+ }), hostType === "gitlab" && /* @__PURE__ */ React.createElement(GitlabRepoPicker, {
599
+ allowedOwners,
600
+ rawErrors,
601
+ state,
602
+ onChange: updateLocalState
603
+ }), hostType === "bitbucket" && /* @__PURE__ */ React.createElement(BitbucketRepoPicker, {
604
+ rawErrors,
605
+ state,
606
+ onChange: updateLocalState
607
+ }), hostType === "azure" && /* @__PURE__ */ React.createElement(AzureRepoPicker, {
608
+ rawErrors,
609
+ state,
610
+ onChange: updateLocalState
611
+ }));
568
612
  };
569
613
 
570
614
  const repoPickerValidation = (value, validation, context) => {
@@ -666,10 +710,14 @@ const scaffolderPlugin = createPlugin({
666
710
  api: scaffolderApiRef,
667
711
  deps: {
668
712
  discoveryApi: discoveryApiRef,
669
- identityApi: identityApiRef,
670
- scmIntegrationsApi: scmIntegrationsApiRef
713
+ scmIntegrationsApi: scmIntegrationsApiRef,
714
+ fetchApi: fetchApiRef
671
715
  },
672
- factory: ({ discoveryApi, identityApi, scmIntegrationsApi }) => new ScaffolderClient({ discoveryApi, identityApi, scmIntegrationsApi })
716
+ factory: ({ discoveryApi, scmIntegrationsApi, fetchApi }) => new ScaffolderClient({
717
+ discoveryApi,
718
+ scmIntegrationsApi,
719
+ fetchApi
720
+ })
673
721
  })
674
722
  ],
675
723
  routes: {
@@ -699,7 +747,7 @@ const OwnerPickerFieldExtension = scaffolderPlugin.provide(createScaffolderField
699
747
  }));
700
748
  const ScaffolderPage = scaffolderPlugin.provide(createRoutableExtension({
701
749
  name: "ScaffolderPage",
702
- component: () => import('./Router-a8e778fd.esm.js').then((m) => m.Router),
750
+ component: () => import('./Router-5ca674eb.esm.js').then((m) => m.Router),
703
751
  mountPoint: rootRouteRef
704
752
  }));
705
753
  const OwnedEntityPickerFieldExtension = scaffolderPlugin.provide(createScaffolderFieldExtension({
@@ -942,4 +990,4 @@ const TemplateTypePicker = () => {
942
990
  };
943
991
 
944
992
  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 };
945
- //# sourceMappingURL=index-f378943f.esm.js.map
993
+ //# sourceMappingURL=index-8b5d21c1.esm.js.map