@backstage/plugin-catalog-import 0.7.10 → 0.8.0-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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,47 @@
1
1
  # @backstage/plugin-catalog-import
2
2
 
3
+ ## 0.8.0-next.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 2e8764b95f: Make filename, branch name and examples URLs used in catalog import customizable.
8
+
9
+ Catalog backend ingestion loop can be already configured to fetch targets with custom catalog filename (other than `catalog-info.yaml`). It's now possible to customize said filename and branch name used in pull requests created by catalog import flow too. This allows organizations to further customize Backstage experience and to better reflect their branding.
10
+
11
+ Filename (default: `catalog-info.yaml`) and branch name (default: `backstage-integration`) used in pull requests can be configured in `app-config.yaml` as follows:
12
+
13
+ ```yaml
14
+ // app-config.yaml
15
+
16
+ catalog:
17
+ import:
18
+ entityFilename: anvil.yaml
19
+ pullRequestBranchName: anvil-integration
20
+ ```
21
+
22
+ Following React components have also been updated to accept optional props for providing example entity and repository paths:
23
+
24
+ ```tsx
25
+ <StepInitAnalyzeUrl
26
+ ...
27
+ exampleLocationUrl="https://github.com/acme-corp/our-awesome-api/blob/main/anvil.yaml"
28
+ />
29
+ ```
30
+
31
+ ```tsx
32
+ <ImportInfoCard
33
+ exampleLocationUrl="https://github.com/acme-corp/our-awesome-api/blob/main/anvil.yaml"
34
+ exampleRepositoryUrl="https://github.com/acme-corp/our-awesome-api"
35
+ />
36
+ ```
37
+
38
+ ### Patch Changes
39
+
40
+ - Updated dependencies
41
+ - @backstage/core-components@0.8.7-next.0
42
+ - @backstage/integration-react@0.1.20-next.0
43
+ - @backstage/plugin-catalog-react@0.6.13-next.0
44
+
3
45
  ## 0.7.10
4
46
 
5
47
  ### Patch Changes
package/README.md CHANGED
@@ -35,39 +35,73 @@ import { CatalogImportPage } from '@backstage/plugin-catalog-import';
35
35
 
36
36
  ## Customizations
37
37
 
38
- ### Disable the creation of Pull Requests
38
+ ### Custom layout
39
+
40
+ A custom layout can be passed to the import page, as it's already
41
+ supported by the search page. If no custom layout is passed, the default layout
42
+ is used.
43
+
44
+ ```typescript
45
+ <Route path="/catalog-import" element={<CatalogImportPage />}>
46
+ <Page themeId="home">
47
+ <Header title="Register an existing component" />
48
+ <Content>
49
+ <ContentHeader title="Start tracking your components">
50
+ <SupportButton>
51
+ Start tracking your component in Backstage by adding it to the
52
+ software catalog.
53
+ </SupportButton>
54
+ </ContentHeader>
55
+
56
+ <Grid container spacing={2} direction="row-reverse">
57
+ <Grid item xs={12} md={4} lg={6} xl={8}>
58
+ Hello World
59
+ </Grid>
60
+
61
+ <Grid item xs={12} md={8} lg={6} xl={4}>
62
+ <ImportStepper />
63
+ </Grid>
64
+ </Grid>
65
+ </Content>
66
+ </Page>
67
+ </Route>
68
+ ```
39
69
 
40
- The pull request feature can be disabled by options that are passed to the `CatalogImportPage`:
70
+ Previously it was possible to disable and customize the automatic pull request
71
+ feature by passing options to `<CatalogImportPage>` (`pullRequest.disable` and
72
+ `pullRequest.preparePullRequest`). This functionality is moved to the
73
+ `CatalogImportApi` which now provides an optional `preparePullRequest()`
74
+ function. The function can either be overridden to generate a different content
75
+ for the pull request, or removed to disable this feature.
41
76
 
42
- ```tsx
43
- // packages/app/src/App.tsx
77
+ ### Entity filename and branch name
44
78
 
45
- <Route
46
- path="/catalog-import"
47
- element={<CatalogImportPage pullRequest={{ disable: true }} />}
48
- />
79
+ Entity filename (default: `catalog-info.yaml`) and branch name (default: `backstage-integration`) used in pull requests can be configured in `app-config.yaml` as follows:
80
+
81
+ ```yaml
82
+ // app-config.yaml
83
+
84
+ catalog:
85
+ import:
86
+ entityFilename: anvil.yaml
87
+ pullRequestBranchName: anvil-integration
49
88
  ```
50
89
 
51
- ### Customize the title and body of the Pull Request
90
+ ### Entity examples
52
91
 
53
- The pull request form is filled with a default title and body.
54
- This can be configured by options that are passed to the `CatalogImportPage`:
92
+ Following React components accept optional props for providing custom example entity and repository paths:
55
93
 
56
94
  ```tsx
57
- // packages/app/src/App.tsx
95
+ <StepInitAnalyzeUrl
96
+ ...
97
+ exampleLocationUrl="https://github.com/acme-corp/our-awesome-api/blob/main/anvil.yaml"
98
+ />
99
+ ```
58
100
 
59
- <Route
60
- path="/catalog-import"
61
- element={
62
- <CatalogImportPage
63
- pullRequest={{
64
- preparePullRequest: () => ({
65
- title: 'My title',
66
- body: 'My **markdown** body',
67
- }),
68
- }}
69
- />
70
- }
101
+ ```tsx
102
+ <ImportInfoCard
103
+ exampleLocationUrl="https://github.com/acme-corp/our-awesome-api/blob/main/anvil.yaml"
104
+ exampleRepositoryUrl="https://github.com/acme-corp/our-awesome-api"
71
105
  />
72
106
  ```
73
107
 
package/config.d.ts ADDED
@@ -0,0 +1,40 @@
1
+ /*
2
+ * Copyright 2022 The Backstage Authors
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ export interface Config {
18
+ /**
19
+ * Configuration options for the catalog plugin.
20
+ */
21
+ catalog?: {
22
+ /**
23
+ * List of import flow specific options and attributes
24
+ */
25
+ import?: {
26
+ /**
27
+ * Catalog entity descriptor filename, defaults to "catalog-info.yaml"
28
+ * @visibility frontend
29
+ */
30
+ entityFilename?: string;
31
+ /**
32
+ * A branch name used in pull request when registering existing component via UI
33
+ * Valid git refname required, see: https://git-scm.com/docs/git-check-ref-format
34
+ * Defaults to "backstage-integration"
35
+ * @visibility frontend
36
+ */
37
+ pullRequestBranchName?: string;
38
+ };
39
+ };
40
+ }
@@ -29,4 +29,4 @@ const ImportPage = () => {
29
29
  };
30
30
 
31
31
  export { ImportPage };
32
- //# sourceMappingURL=index-1d37898f.esm.js.map
32
+ //# sourceMappingURL=index-ebc7277b.esm.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index-1d37898f.esm.js","sources":["../../src/components/ImportPage/ImportPage.tsx"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport React from 'react';\nimport { useOutlet } from 'react-router';\nimport { DefaultImportPage } from '../DefaultImportPage';\n\nexport const ImportPage = () => {\n const outlet = useOutlet();\n\n return outlet || <DefaultImportPage />;\n};\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;MAoBa,aAAa,MAAM;AAC9B,QAAM,SAAS;AAEf,SAAO,8CAAW,mBAAD;AAAA;;;;"}
1
+ {"version":3,"file":"index-ebc7277b.esm.js","sources":["../../src/components/ImportPage/ImportPage.tsx"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport React from 'react';\nimport { useOutlet } from 'react-router';\nimport { DefaultImportPage } from '../DefaultImportPage';\n\nexport const ImportPage = () => {\n const outlet = useOutlet();\n\n return outlet || <DefaultImportPage />;\n};\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;MAoBa,aAAa,MAAM;AAC9B,QAAM,SAAS;AAEf,SAAO,8CAAW,mBAAD;AAAA;;;;"}
package/dist/index.d.ts CHANGED
@@ -17,7 +17,7 @@ declare const CatalogImportPage: () => JSX.Element;
17
17
 
18
18
  declare const DefaultImportPage: () => JSX.Element;
19
19
 
20
- declare type Props$7 = {
20
+ declare type Props$8 = {
21
21
  locations: Array<{
22
22
  target: string;
23
23
  entities: (Entity | EntityName)[];
@@ -28,9 +28,13 @@ declare type Props$7 = {
28
28
  onItemClick?: (target: string) => void;
29
29
  withLinks?: boolean;
30
30
  };
31
- declare const EntityListComponent: ({ locations, collapsed, locationListItemIcon, onItemClick, firstListItem, withLinks, }: Props$7) => JSX.Element;
31
+ declare const EntityListComponent: ({ locations, collapsed, locationListItemIcon, onItemClick, firstListItem, withLinks, }: Props$8) => JSX.Element;
32
32
 
33
- declare const ImportInfoCard: () => JSX.Element;
33
+ declare type Props$7 = {
34
+ exampleLocationUrl?: string;
35
+ exampleRepositoryUrl?: string;
36
+ };
37
+ declare const ImportInfoCard: ({ exampleLocationUrl, exampleRepositoryUrl, }: Props$7) => JSX.Element;
34
38
 
35
39
  declare type RecursivePartial<T> = {
36
40
  [P in keyof T]?: T[P] extends (infer U)[] ? RecursivePartial<U>[] : T[P] extends object ? RecursivePartial<T[P]> : T[P];
@@ -231,6 +235,7 @@ declare type Props$5 = {
231
235
  }) => void;
232
236
  disablePullRequest?: boolean;
233
237
  analysisUrl?: string;
238
+ exampleLocationUrl?: string;
234
239
  };
235
240
  /**
236
241
  * A form that lets the user input a url and analyze it for existing locations or potential entities.
@@ -239,7 +244,7 @@ declare type Props$5 = {
239
244
  * @param analysisUrl - a url that can be used as a default value
240
245
  * @param disablePullRequest - if true, repositories without entities will abort the wizard
241
246
  */
242
- declare const StepInitAnalyzeUrl: ({ onAnalysis, analysisUrl, disablePullRequest, }: Props$5) => JSX.Element;
247
+ declare const StepInitAnalyzeUrl: ({ onAnalysis, analysisUrl, disablePullRequest, exampleLocationUrl, }: Props$5) => JSX.Element;
243
248
 
244
249
  declare type Props$4<TFieldValue extends string> = {
245
250
  name: TFieldValue;
package/dist/index.esm.js CHANGED
@@ -38,6 +38,22 @@ const getGithubIntegrationConfig = (scmIntegrationsApi, location) => {
38
38
  };
39
39
  };
40
40
 
41
+ function asInputRef(renderResult) {
42
+ const { ref, ...rest } = renderResult;
43
+ return {
44
+ inputRef: ref,
45
+ ...rest
46
+ };
47
+ }
48
+ function getCatalogFilename(config) {
49
+ var _a;
50
+ return (_a = config.getOptionalString("catalog.import.entityFilename")) != null ? _a : "catalog-info.yaml";
51
+ }
52
+ function getBranchName(config) {
53
+ var _a;
54
+ return (_a = config.getOptionalString("catalog.import.pullRequestBranchName")) != null ? _a : "backstage-integration";
55
+ }
56
+
41
57
  class CatalogImportClient {
42
58
  constructor(options) {
43
59
  this.discoveryApi = options.discoveryApi;
@@ -76,10 +92,11 @@ class CatalogImportClient {
76
92
  const ghConfig = getGithubIntegrationConfig(this.scmIntegrationsApi, url);
77
93
  if (!ghConfig) {
78
94
  const other = this.scmIntegrationsApi.byUrl(url);
95
+ const catalogFilename = getCatalogFilename(this.configApi);
79
96
  if (other) {
80
- throw new Error(`The ${other.title} integration only supports full URLs to catalog-info.yaml files. Did you try to pass in the URL of a directory instead?`);
97
+ throw new Error(`The ${other.title} integration only supports full URLs to ${catalogFilename} files. Did you try to pass in the URL of a directory instead?`);
81
98
  }
82
- throw new Error("This URL was not recognized as a valid GitHub URL because there was no configured integration that matched the given host name. You could try to paste the full URL to a catalog-info.yaml file instead.");
99
+ throw new Error(`This URL was not recognized as a valid GitHub URL because there was no configured integration that matched the given host name. You could try to paste the full URL to a ${catalogFilename} file instead.`);
83
100
  }
84
101
  const locations = await this.checkGitHubForExistingCatalogInfo({
85
102
  ...ghConfig,
@@ -104,8 +121,9 @@ class CatalogImportClient {
104
121
  var _a;
105
122
  const appTitle = (_a = this.configApi.getOptionalString("app.title")) != null ? _a : "Backstage";
106
123
  const appBaseUrl = this.configApi.getString("app.baseUrl");
124
+ const catalogFilename = getCatalogFilename(this.configApi);
107
125
  return {
108
- title: "Add catalog-info.yaml config file",
126
+ title: `Add ${catalogFilename} config file`,
109
127
  body: `This pull request adds a **Backstage entity metadata file** to this repository so that the component can be added to the [${appTitle} software catalog](${appBaseUrl}).
110
128
 
111
129
  After this pull request is merged, the component will become available.
@@ -164,8 +182,8 @@ For more information, read an [overview of the Backstage software catalog](https
164
182
  auth: token,
165
183
  baseUrl: githubIntegrationConfig.apiBaseUrl
166
184
  });
167
- const catalogFileName = "catalog-info.yaml";
168
- const query = `repo:${owner}/${repo}+filename:${catalogFileName}`;
185
+ const catalogFilename = getCatalogFilename(this.configApi);
186
+ const query = `repo:${owner}/${repo}+filename:${catalogFilename}`;
169
187
  const searchResult = await octo.search.code({ q: query }).catch((e) => {
170
188
  throw new Error(formatHttpErrorMessage("Couldn't search repository for metadata file.", e));
171
189
  });
@@ -216,8 +234,8 @@ For more information, read an [overview of the Backstage software catalog](https
216
234
  auth: token,
217
235
  baseUrl: githubIntegrationConfig.apiBaseUrl
218
236
  });
219
- const branchName = "backstage-integration";
220
- const fileName = "catalog-info.yaml";
237
+ const branchName = getBranchName(this.configApi);
238
+ const fileName = getCatalogFilename(this.configApi);
221
239
  const repoData = await octo.repos.get({
222
240
  owner,
223
241
  repo
@@ -308,16 +326,25 @@ const catalogImportPlugin = createPlugin({
308
326
  });
309
327
  const CatalogImportPage = catalogImportPlugin.provide(createRoutableExtension({
310
328
  name: "CatalogImportPage",
311
- component: () => import('./esm/index-1d37898f.esm.js').then((m) => m.ImportPage),
329
+ component: () => import('./esm/index-ebc7277b.esm.js').then((m) => m.ImportPage),
312
330
  mountPoint: rootRouteRef
313
331
  }));
314
332
 
315
- const ImportInfoCard = () => {
333
+ function useCatalogFilename() {
334
+ const config = useApi(configApiRef);
335
+ return getCatalogFilename(config);
336
+ }
337
+
338
+ const ImportInfoCard = ({
339
+ exampleLocationUrl = "https://github.com/backstage/backstage/blob/master/catalog-info.yaml",
340
+ exampleRepositoryUrl = "https://github.com/backstage/backstage"
341
+ }) => {
316
342
  const configApi = useApi(configApiRef);
317
343
  const appTitle = configApi.getOptional("app.title") || "Backstage";
318
344
  const catalogImportApi = useApi(catalogImportApiRef);
319
345
  const integrations = configApi.getConfig("integrations");
320
346
  const hasGithubIntegration = integrations.has("github");
347
+ const catalogFilename = useCatalogFilename();
321
348
  return /* @__PURE__ */ React.createElement(InfoCard, {
322
349
  title: "Register an existing component",
323
350
  deepLink: {
@@ -333,7 +360,7 @@ const ImportInfoCard = () => {
333
360
  variant: "subtitle2",
334
361
  color: "textSecondary",
335
362
  paragraph: true
336
- }, "Example:", " ", /* @__PURE__ */ React.createElement("code", null, "https://github.com/backstage/backstage/blob/master/catalog-info.yaml")), /* @__PURE__ */ React.createElement(Typography, {
363
+ }, "Example: ", /* @__PURE__ */ React.createElement("code", null, exampleLocationUrl)), /* @__PURE__ */ React.createElement(Typography, {
337
364
  variant: "body2",
338
365
  paragraph: true
339
366
  }, "The wizard analyzes the file, previews the entities, and adds them to the ", appTitle, " catalog."), hasGithubIntegration && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Typography, {
@@ -346,13 +373,13 @@ const ImportInfoCard = () => {
346
373
  variant: "subtitle2",
347
374
  color: "textSecondary",
348
375
  paragraph: true
349
- }, "Example: ", /* @__PURE__ */ React.createElement("code", null, "https://github.com/backstage/backstage")), /* @__PURE__ */ React.createElement(Typography, {
376
+ }, "Example: ", /* @__PURE__ */ React.createElement("code", null, exampleRepositoryUrl)), /* @__PURE__ */ React.createElement(Typography, {
350
377
  variant: "body2",
351
378
  paragraph: true
352
- }, "The wizard discovers all ", /* @__PURE__ */ React.createElement("code", null, "catalog-info.yaml"), " files in the repository, previews the entities, and adds them to the ", appTitle, " ", "catalog."), catalogImportApi.preparePullRequest && /* @__PURE__ */ React.createElement(Typography, {
379
+ }, "The wizard discovers all ", /* @__PURE__ */ React.createElement("code", null, catalogFilename), " files in the repository, previews the entities, and adds them to the ", appTitle, " ", "catalog."), catalogImportApi.preparePullRequest && /* @__PURE__ */ React.createElement(Typography, {
353
380
  variant: "body2",
354
381
  paragraph: true
355
- }, "If no entities are found, the wizard will prepare a Pull Request that adds an example ", /* @__PURE__ */ React.createElement("code", null, "catalog-info.yaml"), " and prepares the ", appTitle, " catalog to load all entities as soon as the Pull Request is merged.")));
382
+ }, "If no entities are found, the wizard will prepare a Pull Request that adds an example ", /* @__PURE__ */ React.createElement("code", null, catalogFilename), " and prepares the ", appTitle, " catalog to load all entities as soon as the Pull Request is merged.")));
356
383
  };
357
384
 
358
385
  function init(initialUrl) {
@@ -491,14 +518,6 @@ const BackButton = (props) => {
491
518
  }, props.children || "Back");
492
519
  };
493
520
 
494
- function asInputRef(renderResult) {
495
- const { ref, ...rest } = renderResult;
496
- return {
497
- inputRef: ref,
498
- ...rest
499
- };
500
- }
501
-
502
521
  const useStyles$2 = makeStyles((theme) => ({
503
522
  nested: {
504
523
  paddingLeft: theme.spacing(4)
@@ -591,7 +610,8 @@ const StepFinishImportLocation = ({ prepareResult, onReset }) => {
591
610
  const StepInitAnalyzeUrl = ({
592
611
  onAnalysis,
593
612
  analysisUrl = "",
594
- disablePullRequest = false
613
+ disablePullRequest = false,
614
+ exampleLocationUrl = "https://github.com/backstage/backstage/blob/master/catalog-info.yaml"
595
615
  }) => {
596
616
  const errorApi = useApi(errorApiRef);
597
617
  const catalogImportApi = useApi(catalogImportApiRef);
@@ -660,7 +680,7 @@ const StepInitAnalyzeUrl = ({
660
680
  fullWidth: true,
661
681
  id: "url",
662
682
  label: "Repository URL",
663
- placeholder: "https://github.com/backstage/backstage/blob/master/catalog-info.yaml",
683
+ placeholder: exampleLocationUrl,
664
684
  helperText: "Enter the full path to your entity file to start tracking your component",
665
685
  margin: "normal",
666
686
  variant: "outlined",
@@ -741,11 +761,12 @@ const PreviewCatalogInfoComponent = ({
741
761
  entities,
742
762
  classes
743
763
  }) => {
764
+ const catalogFilename = useCatalogFilename();
744
765
  return /* @__PURE__ */ React.createElement(Card, {
745
766
  variant: "outlined",
746
767
  className: classes == null ? void 0 : classes.card
747
768
  }, /* @__PURE__ */ React.createElement(CardHeader, {
748
- title: /* @__PURE__ */ React.createElement("code", null, `${trimEnd(repositoryUrl, "/")}/catalog-info.yaml`)
769
+ title: /* @__PURE__ */ React.createElement("code", null, `${trimEnd(repositoryUrl, "/")}/${catalogFilename}`)
749
770
  }), /* @__PURE__ */ React.createElement(CardContent, {
750
771
  className: classes == null ? void 0 : classes.cardContent
751
772
  }, /* @__PURE__ */ React.createElement(CodeSnippet, {
@@ -808,6 +829,7 @@ const StepPrepareCreatePullRequest = ({
808
829
  const errorApi = useApi(errorApiRef);
809
830
  const [submitted, setSubmitted] = useState(false);
810
831
  const [error, setError] = useState();
832
+ const catalogFilename = useCatalogFilename();
811
833
  const {
812
834
  loading: prDefaultsLoading,
813
835
  value: prDefaults,
@@ -863,7 +885,7 @@ const StepPrepareCreatePullRequest = ({
863
885
  catalogImportApi,
864
886
  onPrepare
865
887
  ]);
866
- return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Typography, null, "You entered a link to a ", analyzeResult.integrationType, " repository but a", " ", /* @__PURE__ */ React.createElement("code", null, "catalog-info.yaml"), " could not be found. Use this form to open a Pull Request that creates one."), !prDefaultsLoading && /* @__PURE__ */ React.createElement(PreparePullRequestForm, {
888
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Typography, null, "You entered a link to a ", analyzeResult.integrationType, " repository but a", " ", /* @__PURE__ */ React.createElement("code", null, catalogFilename), " could not be found. Use this form to open a Pull Request that creates one."), !prDefaultsLoading && /* @__PURE__ */ React.createElement(PreparePullRequestForm, {
867
889
  onSubmit: handleResult,
868
890
  defaultValues: {
869
891
  title: (_a = prDefaults == null ? void 0 : prDefaults.title) != null ? _a : "",
@@ -1 +1 @@
1
- {"version":3,"file":"index.esm.js","sources":["../src/api/CatalogImportApi.ts","../src/api/GitHub.ts","../src/api/CatalogImportClient.ts","../src/plugin.ts","../src/components/ImportInfoCard/ImportInfoCard.tsx","../src/components/useImportState.ts","../src/components/Buttons/index.tsx","../src/components/helpers.ts","../src/components/EntityListComponent/EntityListComponent.tsx","../src/components/StepFinishImportLocation/StepFinishImportLocation.tsx","../src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.tsx","../src/components/StepPrepareCreatePullRequest/AutocompleteTextField.tsx","../src/components/StepPrepareCreatePullRequest/PreparePullRequestForm.tsx","../src/components/StepPrepareCreatePullRequest/PreviewCatalogInfoComponent.tsx","../src/components/StepPrepareCreatePullRequest/PreviewPullRequestComponent.tsx","../src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx","../src/components/StepPrepareSelectLocations/StepPrepareSelectLocations.tsx","../src/components/StepReviewLocation/StepReviewLocation.tsx","../src/components/ImportStepper/defaults.tsx","../src/components/ImportStepper/ImportStepper.tsx","../src/components/DefaultImportPage/DefaultImportPage.tsx"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { EntityName } from '@backstage/catalog-model';\nimport { createApiRef } from '@backstage/core-plugin-api';\nimport { PartialEntity } from '../types';\n\nexport const catalogImportApiRef = createApiRef<CatalogImportApi>({\n id: 'plugin.catalog-import.service',\n});\n\n// result of the analyze state\nexport type AnalyzeResult =\n | {\n type: 'locations';\n locations: Array<{\n target: string;\n exists?: boolean;\n entities: EntityName[];\n }>;\n }\n | {\n type: 'repository';\n url: string;\n integrationType: string;\n generatedEntities: PartialEntity[];\n };\n\nexport interface CatalogImportApi {\n analyzeUrl(url: string): Promise<AnalyzeResult>;\n\n preparePullRequest?(): Promise<{\n title: string;\n body: string;\n }>;\n submitPullRequest(options: {\n repositoryUrl: string;\n fileContent: string;\n title: string;\n body: string;\n }): Promise<{ link: string; location: string }>;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ScmIntegrationRegistry } from '@backstage/integration';\nimport parseGitUrl from 'git-url-parse';\n\nexport const getGithubIntegrationConfig = (\n scmIntegrationsApi: ScmIntegrationRegistry,\n location: string,\n) => {\n const integration = scmIntegrationsApi.github.byUrl(location);\n if (!integration) {\n return undefined;\n }\n\n const { name: repo, owner } = parseGitUrl(location);\n return {\n repo,\n owner,\n githubIntegrationConfig: integration.config,\n };\n};\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { CatalogApi } from '@backstage/catalog-client';\nimport { EntityName } from '@backstage/catalog-model';\nimport {\n ConfigApi,\n DiscoveryApi,\n IdentityApi,\n} from '@backstage/core-plugin-api';\nimport {\n GitHubIntegrationConfig,\n ScmIntegrationRegistry,\n} from '@backstage/integration';\nimport { ScmAuthApi } from '@backstage/integration-react';\nimport { Octokit } from '@octokit/rest';\nimport { Base64 } from 'js-base64';\nimport { PartialEntity } from '../types';\nimport { AnalyzeResult, CatalogImportApi } from './CatalogImportApi';\nimport { getGithubIntegrationConfig } from './GitHub';\nimport { trimEnd } from 'lodash';\n\nexport class CatalogImportClient implements CatalogImportApi {\n private readonly discoveryApi: DiscoveryApi;\n private readonly identityApi: IdentityApi;\n private readonly scmAuthApi: ScmAuthApi;\n private readonly scmIntegrationsApi: ScmIntegrationRegistry;\n private readonly catalogApi: CatalogApi;\n private readonly configApi: ConfigApi;\n\n constructor(options: {\n discoveryApi: DiscoveryApi;\n scmAuthApi: ScmAuthApi;\n identityApi: IdentityApi;\n scmIntegrationsApi: ScmIntegrationRegistry;\n catalogApi: CatalogApi;\n configApi: ConfigApi;\n }) {\n this.discoveryApi = options.discoveryApi;\n this.scmAuthApi = options.scmAuthApi;\n this.identityApi = options.identityApi;\n this.scmIntegrationsApi = options.scmIntegrationsApi;\n this.catalogApi = options.catalogApi;\n this.configApi = options.configApi;\n }\n\n async analyzeUrl(url: string): Promise<AnalyzeResult> {\n if (\n new URL(url).pathname.match(/\\.ya?ml$/) ||\n new URL(url).searchParams.get('path')?.match(/.ya?ml$/)\n ) {\n const location = await this.catalogApi.addLocation({\n type: 'url',\n target: url,\n dryRun: true,\n });\n\n return {\n type: 'locations',\n locations: [\n {\n exists: location.exists,\n target: location.location.target,\n entities: location.entities.map(e => ({\n kind: e.kind,\n namespace: e.metadata.namespace ?? 'default',\n name: e.metadata.name,\n })),\n },\n ],\n };\n }\n\n const ghConfig = getGithubIntegrationConfig(this.scmIntegrationsApi, url);\n if (!ghConfig) {\n const other = this.scmIntegrationsApi.byUrl(url);\n if (other) {\n throw new Error(\n `The ${other.title} integration only supports full URLs to catalog-info.yaml files. Did you try to pass in the URL of a directory instead?`,\n );\n }\n throw new Error(\n 'This URL was not recognized as a valid GitHub URL because there was no configured integration that matched the given host name. You could try to paste the full URL to a catalog-info.yaml file instead.',\n );\n }\n\n // TODO: this could be part of the analyze-location endpoint\n const locations = await this.checkGitHubForExistingCatalogInfo({\n ...ghConfig,\n url,\n });\n\n if (locations.length > 0) {\n return {\n type: 'locations',\n locations,\n };\n }\n\n return {\n type: 'repository',\n integrationType: 'github',\n url: url,\n generatedEntities: await this.generateEntityDefinitions({\n repo: url,\n }),\n };\n }\n\n async preparePullRequest(): Promise<{\n title: string;\n body: string;\n }> {\n const appTitle =\n this.configApi.getOptionalString('app.title') ?? 'Backstage';\n const appBaseUrl = this.configApi.getString('app.baseUrl');\n\n return {\n title: 'Add catalog-info.yaml config file',\n body: `This pull request adds a **Backstage entity metadata file** \\\nto this repository so that the component can be added to the \\\n[${appTitle} software catalog](${appBaseUrl}).\\n\\nAfter this pull request is merged, \\\nthe component will become available.\\n\\nFor more information, read an \\\n[overview of the Backstage software catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview).`,\n };\n }\n\n async submitPullRequest({\n repositoryUrl,\n fileContent,\n title,\n body,\n }: {\n repositoryUrl: string;\n fileContent: string;\n title: string;\n body: string;\n }): Promise<{ link: string; location: string }> {\n const ghConfig = getGithubIntegrationConfig(\n this.scmIntegrationsApi,\n repositoryUrl,\n );\n\n if (ghConfig) {\n return await this.submitGitHubPrToRepo({\n ...ghConfig,\n repositoryUrl,\n fileContent,\n title,\n body,\n });\n }\n\n throw new Error('unimplemented!');\n }\n\n // TODO: this could be part of the catalog api\n private async generateEntityDefinitions({\n repo,\n }: {\n repo: string;\n }): Promise<PartialEntity[]> {\n const { token } = await this.identityApi.getCredentials();\n const response = await fetch(\n `${await this.discoveryApi.getBaseUrl('catalog')}/analyze-location`,\n {\n headers: {\n 'Content-Type': 'application/json',\n ...(token && { Authorization: `Bearer ${token}` }),\n },\n method: 'POST',\n body: JSON.stringify({\n location: { type: 'url', target: repo },\n }),\n },\n ).catch(e => {\n throw new Error(`Failed to generate entity definitions, ${e.message}`);\n });\n if (!response.ok) {\n throw new Error(\n `Failed to generate entity definitions. Received http response ${response.status}: ${response.statusText}`,\n );\n }\n\n const payload = await response.json();\n return payload.generateEntities.map((x: any) => x.entity);\n }\n\n // TODO: this response should better be part of the analyze-locations response and scm-independent / implemented per scm\n private async checkGitHubForExistingCatalogInfo({\n url,\n owner,\n repo,\n githubIntegrationConfig,\n }: {\n url: string;\n owner: string;\n repo: string;\n githubIntegrationConfig: GitHubIntegrationConfig;\n }): Promise<\n Array<{\n target: string;\n entities: EntityName[];\n }>\n > {\n const { token } = await this.scmAuthApi.getCredentials({ url });\n const octo = new Octokit({\n auth: token,\n baseUrl: githubIntegrationConfig.apiBaseUrl,\n });\n const catalogFileName = 'catalog-info.yaml';\n const query = `repo:${owner}/${repo}+filename:${catalogFileName}`;\n\n const searchResult = await octo.search.code({ q: query }).catch(e => {\n throw new Error(\n formatHttpErrorMessage(\n \"Couldn't search repository for metadata file.\",\n e,\n ),\n );\n });\n const exists = searchResult.data.total_count > 0;\n if (exists) {\n const repoInformation = await octo.repos.get({ owner, repo }).catch(e => {\n throw new Error(formatHttpErrorMessage(\"Couldn't fetch repo data\", e));\n });\n const defaultBranch = repoInformation.data.default_branch;\n\n return await Promise.all(\n searchResult.data.items\n .map(i => `${trimEnd(url, '/')}/blob/${defaultBranch}/${i.path}`)\n .map(async target => {\n const result = await this.catalogApi.addLocation({\n type: 'url',\n target,\n dryRun: true,\n });\n return {\n target,\n exists: result.exists,\n entities: result.entities.map(e => ({\n kind: e.kind,\n namespace: e.metadata.namespace ?? 'default',\n name: e.metadata.name,\n })),\n };\n }),\n );\n }\n\n return [];\n }\n\n // TODO: extract this function and implement for non-github\n private async submitGitHubPrToRepo({\n owner,\n repo,\n title,\n body,\n fileContent,\n repositoryUrl,\n githubIntegrationConfig,\n }: {\n owner: string;\n repo: string;\n title: string;\n body: string;\n fileContent: string;\n repositoryUrl: string;\n githubIntegrationConfig: GitHubIntegrationConfig;\n }): Promise<{ link: string; location: string }> {\n const { token } = await this.scmAuthApi.getCredentials({\n url: repositoryUrl,\n additionalScope: {\n repoWrite: true,\n },\n });\n\n const octo = new Octokit({\n auth: token,\n baseUrl: githubIntegrationConfig.apiBaseUrl,\n });\n\n const branchName = 'backstage-integration';\n const fileName = 'catalog-info.yaml';\n\n const repoData = await octo.repos\n .get({\n owner,\n repo,\n })\n .catch(e => {\n throw new Error(formatHttpErrorMessage(\"Couldn't fetch repo data\", e));\n });\n\n const parentRef = await octo.git\n .getRef({\n owner,\n repo,\n ref: `heads/${repoData.data.default_branch}`,\n })\n .catch(e => {\n throw new Error(\n formatHttpErrorMessage(\"Couldn't fetch default branch data\", e),\n );\n });\n\n await octo.git\n .createRef({\n owner,\n repo,\n ref: `refs/heads/${branchName}`,\n sha: parentRef.data.object.sha,\n })\n .catch(e => {\n throw new Error(\n formatHttpErrorMessage(\n `Couldn't create a new branch with name '${branchName}'`,\n e,\n ),\n );\n });\n\n await octo.repos\n .createOrUpdateFileContents({\n owner,\n repo,\n path: fileName,\n message: title,\n content: Base64.encode(fileContent),\n branch: branchName,\n })\n .catch(e => {\n throw new Error(\n formatHttpErrorMessage(\n `Couldn't create a commit with ${fileName} file added`,\n e,\n ),\n );\n });\n\n const pullRequestResponse = await octo.pulls\n .create({\n owner,\n repo,\n title,\n head: branchName,\n body,\n base: repoData.data.default_branch,\n })\n .catch(e => {\n throw new Error(\n formatHttpErrorMessage(\n `Couldn't create a pull request for ${branchName} branch`,\n e,\n ),\n );\n });\n\n return {\n link: pullRequestResponse.data.html_url,\n location: `https://${githubIntegrationConfig.host}/${owner}/${repo}/blob/${repoData.data.default_branch}/${fileName}`,\n };\n }\n}\n\nfunction formatHttpErrorMessage(\n message: string,\n error: { status: number; message: string },\n) {\n return `${message}, received http response status code ${error.status}: ${error.message}`;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n configApiRef,\n createApiFactory,\n createPlugin,\n createRoutableExtension,\n createRouteRef,\n discoveryApiRef,\n identityApiRef,\n} from '@backstage/core-plugin-api';\nimport {\n scmAuthApiRef,\n scmIntegrationsApiRef,\n} from '@backstage/integration-react';\nimport { catalogApiRef } from '@backstage/plugin-catalog-react';\nimport { catalogImportApiRef, CatalogImportClient } from './api';\n\nexport const rootRouteRef = createRouteRef({\n id: 'catalog-import',\n});\n\nexport const catalogImportPlugin = createPlugin({\n id: 'catalog-import',\n apis: [\n createApiFactory({\n api: catalogImportApiRef,\n deps: {\n discoveryApi: discoveryApiRef,\n scmAuthApi: scmAuthApiRef,\n identityApi: identityApiRef,\n scmIntegrationsApi: scmIntegrationsApiRef,\n catalogApi: catalogApiRef,\n configApi: configApiRef,\n },\n factory: ({\n discoveryApi,\n scmAuthApi,\n identityApi,\n scmIntegrationsApi,\n catalogApi,\n configApi,\n }) =>\n new CatalogImportClient({\n discoveryApi,\n scmAuthApi,\n scmIntegrationsApi,\n identityApi,\n catalogApi,\n configApi,\n }),\n }),\n ],\n routes: {\n importPage: rootRouteRef,\n },\n});\n\nexport const CatalogImportPage = catalogImportPlugin.provide(\n createRoutableExtension({\n name: 'CatalogImportPage',\n component: () => import('./components/ImportPage').then(m => m.ImportPage),\n mountPoint: rootRouteRef,\n }),\n);\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { InfoCard } from '@backstage/core-components';\nimport { configApiRef, useApi } from '@backstage/core-plugin-api';\nimport { Chip, Typography } from '@material-ui/core';\nimport React from 'react';\nimport { catalogImportApiRef } from '../../api';\n\nexport const ImportInfoCard = () => {\n const configApi = useApi(configApiRef);\n const appTitle = configApi.getOptional('app.title') || 'Backstage';\n const catalogImportApi = useApi(catalogImportApiRef);\n\n const integrations = configApi.getConfig('integrations');\n const hasGithubIntegration = integrations.has('github');\n\n return (\n <InfoCard\n title=\"Register an existing component\"\n deepLink={{\n title: 'Learn more about the Software Catalog',\n link: 'https://backstage.io/docs/features/software-catalog/software-catalog-overview',\n }}\n >\n <Typography variant=\"body2\" paragraph>\n Enter the URL to your source code repository to add it to {appTitle}.\n </Typography>\n <Typography variant=\"h6\">Link to an existing entity file</Typography>\n <Typography variant=\"subtitle2\" color=\"textSecondary\" paragraph>\n Example:{' '}\n <code>\n https://github.com/backstage/backstage/blob/master/catalog-info.yaml\n </code>\n </Typography>\n <Typography variant=\"body2\" paragraph>\n The wizard analyzes the file, previews the entities, and adds them to\n the {appTitle} catalog.\n </Typography>\n {hasGithubIntegration && (\n <>\n <Typography variant=\"h6\">\n Link to a repository{' '}\n <Chip label=\"GitHub only\" variant=\"outlined\" size=\"small\" />\n </Typography>\n <Typography variant=\"subtitle2\" color=\"textSecondary\" paragraph>\n Example: <code>https://github.com/backstage/backstage</code>\n </Typography>\n <Typography variant=\"body2\" paragraph>\n The wizard discovers all <code>catalog-info.yaml</code> files in the\n repository, previews the entities, and adds them to the {appTitle}{' '}\n catalog.\n </Typography>\n {catalogImportApi.preparePullRequest && (\n <Typography variant=\"body2\" paragraph>\n If no entities are found, the wizard will prepare a Pull Request\n that adds an example <code>catalog-info.yaml</code> and prepares\n the {appTitle} catalog to load all entities as soon as the Pull\n Request is merged.\n </Typography>\n )}\n </>\n )}\n </InfoCard>\n );\n};\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Entity, EntityName } from '@backstage/catalog-model';\nimport { useReducer } from 'react';\nimport { AnalyzeResult } from '../api';\n\n// the configuration of the stepper\nexport type ImportFlows =\n | 'unknown'\n | 'single-location'\n | 'multiple-locations'\n | 'no-location';\n\n// the available states of the stepper\ntype ImportStateTypes = 'analyze' | 'prepare' | 'review' | 'finish';\n\n// result of the prepare state\nexport type PrepareResult =\n | {\n type: 'locations';\n locations: Array<{\n exists?: boolean;\n target: string;\n entities: EntityName[];\n }>;\n }\n | {\n type: 'repository';\n url: string;\n integrationType: string;\n pullRequest: {\n url: string;\n };\n locations: Array<{\n target: string;\n entities: EntityName[];\n }>;\n };\n\n// result of the review result\nexport type ReviewResult =\n | {\n type: 'locations';\n locations: Array<{\n target: string;\n entities: Entity[];\n }>;\n refreshed: Array<{ target: string }>;\n }\n | {\n type: 'repository';\n url: string;\n integrationType: string;\n pullRequest: {\n url: string;\n };\n locations: Array<{\n target: string;\n entities: Entity[];\n }>;\n };\n\n// function type for the 'analysis' -> 'prepare'/'review' transition\ntype onAnalysisFn = (\n flow: ImportFlows,\n url: string,\n result: AnalyzeResult,\n opts?: { prepareResult?: PrepareResult },\n) => void;\n\n// function type for the 'prepare' -> 'review' transition\ntype onPrepareFn = (\n result: PrepareResult,\n opts?: { notRepeatable?: boolean },\n) => void;\n\n// function type for the 'review' -> 'finish' transition\ntype onReviewFn = (result: ReviewResult) => void;\n\n// the type interfaces that are available in each state. every state provides\n// already known information and means to go to the next, or the previous step.\ntype State =\n | {\n activeState: 'analyze';\n onAnalysis: onAnalysisFn;\n }\n | {\n activeState: 'prepare';\n analyzeResult: AnalyzeResult;\n prepareResult?: PrepareResult;\n onPrepare: onPrepareFn;\n }\n | {\n activeState: 'review';\n analyzeResult: AnalyzeResult;\n prepareResult: PrepareResult;\n onReview: onReviewFn;\n }\n | {\n activeState: 'finish';\n analyzeResult: AnalyzeResult;\n prepareResult: PrepareResult;\n reviewResult: ReviewResult;\n };\n\nexport type ImportState = State & {\n activeFlow: ImportFlows;\n activeStepNumber: number;\n analysisUrl?: string;\n\n onGoBack?: () => void;\n onReset: () => void;\n};\n\ntype ReducerActions =\n | { type: 'onAnalysis'; args: Parameters<onAnalysisFn> }\n | { type: 'onPrepare'; args: Parameters<onPrepareFn> }\n | { type: 'onReview'; args: Parameters<onReviewFn> }\n | { type: 'onGoBack' }\n | { type: 'onReset'; initialUrl?: string };\n\ntype ReducerState = {\n activeFlow: ImportFlows;\n activeState: ImportStateTypes;\n analysisUrl?: string;\n analyzeResult?: AnalyzeResult;\n prepareResult?: PrepareResult;\n reviewResult?: ReviewResult;\n\n previousStates: ImportStateTypes[];\n};\n\nfunction init(initialUrl?: string): ReducerState {\n return {\n activeFlow: 'unknown',\n activeState: 'analyze',\n analysisUrl: initialUrl,\n previousStates: [],\n };\n}\n\nfunction reducer(state: ReducerState, action: ReducerActions): ReducerState {\n switch (action.type) {\n case 'onAnalysis': {\n if (state.activeState !== 'analyze') {\n return state;\n }\n\n const { activeState, previousStates } = state;\n const [activeFlow, analysisUrl, analyzeResult, opts] = action.args;\n\n return {\n ...state,\n analysisUrl,\n activeFlow,\n analyzeResult,\n prepareResult: opts?.prepareResult,\n\n activeState: opts?.prepareResult === undefined ? 'prepare' : 'review',\n previousStates: previousStates.concat(activeState),\n };\n }\n\n case 'onPrepare': {\n if (state.activeState !== 'prepare') {\n return state;\n }\n\n const { activeState, previousStates } = state;\n const [prepareResult, opts] = action.args;\n\n return {\n ...state,\n prepareResult,\n\n activeState: 'review',\n previousStates: opts?.notRepeatable\n ? []\n : previousStates.concat(activeState),\n };\n }\n\n case 'onReview': {\n if (state.activeState !== 'review') {\n return state;\n }\n\n const { activeState, previousStates } = state;\n const [reviewResult] = action.args;\n\n return {\n ...state,\n reviewResult,\n\n activeState: 'finish',\n previousStates: previousStates.concat(activeState),\n };\n }\n\n case 'onGoBack': {\n const { activeState, previousStates } = state;\n\n return {\n ...state,\n\n activeState:\n previousStates.length > 0\n ? previousStates[previousStates.length - 1]\n : activeState,\n previousStates: previousStates.slice(0, previousStates.length - 1),\n };\n }\n\n case 'onReset':\n return {\n ...init(action.initialUrl),\n\n // we keep the old prepareResult since the form is animated and an\n // undefined value might crash the last step.\n prepareResult: state.prepareResult,\n };\n\n default:\n throw new Error();\n }\n}\n\n/**\n * A hook that manages the state machine of the form. It handles different flows\n * which each can implement up to four states:\n * 1. analyze\n * 2. preview (skippable from analyze->review)\n * 3. review\n * 4. finish\n *\n * @param options - options\n */\nexport const useImportState = (options?: {\n initialUrl?: string;\n}): ImportState => {\n const [state, dispatch] = useReducer(reducer, options?.initialUrl, init);\n\n const { activeFlow, activeState, analysisUrl, previousStates } = state;\n\n return {\n activeFlow,\n activeState,\n activeStepNumber: ['analyze', 'prepare', 'review', 'finish'].indexOf(\n activeState,\n ),\n analysisUrl: analysisUrl,\n\n analyzeResult: state.analyzeResult!,\n prepareResult: state.prepareResult!,\n reviewResult: state.reviewResult!,\n\n onAnalysis: (flow, url, result, opts) =>\n dispatch({\n type: 'onAnalysis',\n args: [flow, url, result, opts],\n }),\n\n onPrepare: (result, opts) =>\n dispatch({\n type: 'onPrepare',\n args: [result, opts],\n }),\n\n onReview: result => dispatch({ type: 'onReview', args: [result] }),\n\n onGoBack:\n previousStates.length > 0\n ? () => dispatch({ type: 'onGoBack' })\n : undefined,\n\n onReset: () =>\n dispatch({ type: 'onReset', initialUrl: options?.initialUrl }),\n };\n};\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Button, CircularProgress } from '@material-ui/core';\nimport { makeStyles } from '@material-ui/core/styles';\nimport React, { ComponentProps } from 'react';\n\nconst useStyles = makeStyles(theme => ({\n wrapper: {\n marginTop: theme.spacing(1),\n marginRight: theme.spacing(1),\n position: 'relative',\n },\n buttonProgress: {\n position: 'absolute',\n top: '50%',\n left: '50%',\n marginTop: -12,\n marginLeft: -12,\n },\n button: {\n marginTop: theme.spacing(1),\n marginRight: theme.spacing(1),\n },\n}));\n\nexport const NextButton = (\n props: ComponentProps<typeof Button> & { loading?: boolean },\n) => {\n const { loading, ...buttonProps } = props;\n const classes = useStyles();\n\n return (\n <div className={classes.wrapper}>\n <Button\n color=\"primary\"\n variant=\"contained\"\n {...buttonProps}\n disabled={props.disabled || props.loading}\n />\n {props.loading && (\n <CircularProgress size=\"1.5rem\" className={classes.buttonProgress} />\n )}\n {props.loading}\n </div>\n );\n};\n\nexport const BackButton = (props: ComponentProps<typeof Button>) => {\n const classes = useStyles();\n\n return (\n <Button variant=\"outlined\" className={classes.button} {...props}>\n {props.children || 'Back'}\n </Button>\n );\n};\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { UseFormRegisterReturn } from 'react-hook-form';\n\n/**\n * A helper that converts the result of a render('name', opts) to make it compatible with material-ui.\n *\n * See also https://github.com/react-hook-form/react-hook-form/issues/4629#issuecomment-815840872\n * TODO: remove when updating to material-ui v5 (https://github.com/mui-org/material-ui/pull/23174)\n *\n * @param renderResult - the result of a render('name', opts)\n */\nexport function asInputRef(renderResult: UseFormRegisterReturn) {\n const { ref, ...rest } = renderResult;\n return {\n inputRef: ref,\n ...rest,\n };\n}\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Entity, EntityName } from '@backstage/catalog-model';\nimport { useApp } from '@backstage/core-plugin-api';\nimport {\n EntityRefLink,\n formatEntityRefTitle,\n} from '@backstage/plugin-catalog-react';\nimport {\n Collapse,\n IconButton,\n List,\n ListItem,\n ListItemIcon,\n ListItemSecondaryAction,\n ListItemText,\n} from '@material-ui/core';\nimport { makeStyles } from '@material-ui/core/styles';\nimport ExpandLessIcon from '@material-ui/icons/ExpandLess';\nimport ExpandMoreIcon from '@material-ui/icons/ExpandMore';\nimport WorkIcon from '@material-ui/icons/Work';\nimport React, { useState } from 'react';\n\nconst useStyles = makeStyles(theme => ({\n nested: {\n paddingLeft: theme.spacing(4),\n },\n}));\n\nfunction sortEntities(entities: Array<EntityName | Entity>) {\n return entities.sort((a, b) =>\n formatEntityRefTitle(a).localeCompare(formatEntityRefTitle(b)),\n );\n}\n\ntype Props = {\n locations: Array<{ target: string; entities: (Entity | EntityName)[] }>;\n locationListItemIcon: (target: string) => React.ReactElement;\n collapsed?: boolean;\n firstListItem?: React.ReactElement;\n onItemClick?: (target: string) => void;\n withLinks?: boolean;\n};\n\nexport const EntityListComponent = ({\n locations,\n collapsed = false,\n locationListItemIcon,\n onItemClick,\n firstListItem,\n withLinks = false,\n}: Props) => {\n const app = useApp();\n const classes = useStyles();\n\n const [expandedUrls, setExpandedUrls] = useState<string[]>([]);\n\n const handleClick = (url: string) => {\n setExpandedUrls(urls =>\n urls.includes(url) ? urls.filter(u => u !== url) : urls.concat(url),\n );\n };\n\n return (\n <List>\n {firstListItem}\n {locations.map(r => (\n <React.Fragment key={r.target}>\n <ListItem\n dense\n button={Boolean(onItemClick) as any}\n onClick={() => onItemClick?.call(this, r.target)}\n >\n <ListItemIcon>{locationListItemIcon(r.target)}</ListItemIcon>\n\n <ListItemText\n primary={r.target}\n secondary={`Entities: ${r.entities.length}`}\n />\n\n {collapsed && (\n <ListItemSecondaryAction>\n <IconButton edge=\"end\" onClick={() => handleClick(r.target)}>\n {expandedUrls.includes(r.target) ? (\n <ExpandLessIcon />\n ) : (\n <ExpandMoreIcon />\n )}\n </IconButton>\n </ListItemSecondaryAction>\n )}\n </ListItem>\n\n <Collapse\n in={!collapsed || expandedUrls.includes(r.target)}\n timeout=\"auto\"\n unmountOnExit\n >\n <List component=\"div\" disablePadding dense>\n {sortEntities(r.entities).map(entity => {\n const Icon =\n app.getSystemIcon(\n `kind:${entity.kind.toLocaleLowerCase('en-US')}`,\n ) ?? WorkIcon;\n return (\n <ListItem\n key={formatEntityRefTitle(entity)}\n className={classes.nested}\n {...(withLinks\n ? {\n component: EntityRefLink,\n entityRef: entity,\n button: withLinks as any,\n }\n : {})}\n >\n <ListItemIcon>\n <Icon />\n </ListItemIcon>\n <ListItemText primary={formatEntityRefTitle(entity)} />\n </ListItem>\n );\n })}\n </List>\n </Collapse>\n </React.Fragment>\n ))}\n </List>\n );\n};\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Grid, Typography } from '@material-ui/core';\nimport LocationOnIcon from '@material-ui/icons/LocationOn';\nimport React from 'react';\nimport { BackButton } from '../Buttons';\nimport { EntityListComponent } from '../EntityListComponent';\nimport { PrepareResult } from '../useImportState';\nimport { Link } from '@backstage/core-components';\nimport partition from 'lodash/partition';\n\ntype Props = {\n prepareResult: PrepareResult;\n onReset: () => void;\n};\n\nexport const StepFinishImportLocation = ({ prepareResult, onReset }: Props) => {\n const continueButton = (\n <Grid container spacing={0}>\n <BackButton onClick={onReset}>Register another</BackButton>\n </Grid>\n );\n\n if (prepareResult.type === 'repository') {\n return (\n <>\n <Typography paragraph>\n The following Pull Request has been opened:{' '}\n <Link\n to={prepareResult.pullRequest.url}\n target=\"_blank\"\n rel=\"noreferrer\"\n >\n {prepareResult.pullRequest.url}\n </Link>\n </Typography>\n\n <Typography paragraph>\n Your entities will be imported as soon as the Pull Request is merged.\n </Typography>\n\n {continueButton}\n </>\n );\n }\n\n const [existingLocations, newLocations] = partition(\n prepareResult.locations,\n l => l.exists,\n );\n\n return (\n <>\n {newLocations.length > 0 && (\n <>\n <Typography>\n The following entities have been added to the catalog:\n </Typography>\n\n <EntityListComponent\n locations={newLocations}\n locationListItemIcon={() => <LocationOnIcon />}\n withLinks\n />\n </>\n )}\n {existingLocations.length > 0 && (\n <>\n <Typography>\n A refresh was triggered for the following locations:\n </Typography>\n\n <EntityListComponent\n locations={existingLocations}\n locationListItemIcon={() => <LocationOnIcon />}\n withLinks\n />\n </>\n )}\n {continueButton}\n </>\n );\n};\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { errorApiRef, useApi } from '@backstage/core-plugin-api';\nimport { FormHelperText, Grid, TextField } from '@material-ui/core';\nimport React, { useCallback, useState } from 'react';\nimport { useForm } from 'react-hook-form';\nimport { AnalyzeResult, catalogImportApiRef } from '../../api';\nimport { NextButton } from '../Buttons';\nimport { asInputRef } from '../helpers';\nimport { ImportFlows, PrepareResult } from '../useImportState';\n\ntype FormData = {\n url: string;\n};\n\ntype Props = {\n onAnalysis: (\n flow: ImportFlows,\n url: string,\n result: AnalyzeResult,\n opts?: { prepareResult?: PrepareResult },\n ) => void;\n disablePullRequest?: boolean;\n analysisUrl?: string;\n};\n\n/**\n * A form that lets the user input a url and analyze it for existing locations or potential entities.\n *\n * @param onAnalysis - is called when the analysis was successful\n * @param analysisUrl - a url that can be used as a default value\n * @param disablePullRequest - if true, repositories without entities will abort the wizard\n */\nexport const StepInitAnalyzeUrl = ({\n onAnalysis,\n analysisUrl = '',\n disablePullRequest = false,\n}: Props) => {\n const errorApi = useApi(errorApiRef);\n const catalogImportApi = useApi(catalogImportApiRef);\n\n const {\n register,\n handleSubmit,\n formState: { errors },\n watch,\n } = useForm<FormData>({\n mode: 'onTouched',\n defaultValues: {\n url: analysisUrl,\n },\n });\n\n const [submitted, setSubmitted] = useState(false);\n const [error, setError] = useState<string | undefined>(undefined);\n\n const handleResult = useCallback(\n async ({ url }: FormData) => {\n setSubmitted(true);\n\n try {\n const analysisResult = await catalogImportApi.analyzeUrl(url);\n\n switch (analysisResult.type) {\n case 'repository':\n if (\n !disablePullRequest &&\n analysisResult.generatedEntities.length > 0\n ) {\n onAnalysis('no-location', url, analysisResult);\n } else {\n setError(\"Couldn't generate entities for your repository\");\n setSubmitted(false);\n }\n break;\n\n case 'locations': {\n if (analysisResult.locations.length === 1) {\n onAnalysis('single-location', url, analysisResult, {\n prepareResult: analysisResult,\n });\n } else if (analysisResult.locations.length > 1) {\n onAnalysis('multiple-locations', url, analysisResult);\n } else {\n setError('There are no entities at this location');\n setSubmitted(false);\n }\n break;\n }\n\n default: {\n const err = `Received unknown analysis result of type ${\n (analysisResult as any).type\n }. Please contact the support team.`;\n setError(err);\n setSubmitted(false);\n\n errorApi.post(new Error(err));\n break;\n }\n }\n } catch (e: any) {\n setError(e?.data?.error?.message ?? e.message);\n setSubmitted(false);\n }\n },\n [catalogImportApi, disablePullRequest, errorApi, onAnalysis],\n );\n\n return (\n <form onSubmit={handleSubmit(handleResult)}>\n <TextField\n {...asInputRef(\n register('url', {\n required: true,\n validate: {\n httpsValidator: (value: any) =>\n (typeof value === 'string' &&\n value.match(/^http[s]?:\\/\\//) !== null) ||\n 'Must start with http:// or https://.',\n },\n }),\n )}\n fullWidth\n id=\"url\"\n label=\"Repository URL\"\n placeholder=\"https://github.com/backstage/backstage/blob/master/catalog-info.yaml\"\n helperText=\"Enter the full path to your entity file to start tracking your component\"\n margin=\"normal\"\n variant=\"outlined\"\n error={Boolean(errors.url)}\n required\n />\n\n {errors.url && (\n <FormHelperText error>{errors.url.message}</FormHelperText>\n )}\n\n {error && <FormHelperText error>{error}</FormHelperText>}\n\n <Grid container spacing={0}>\n <NextButton\n disabled={Boolean(errors.url) || !watch('url')}\n loading={submitted}\n type=\"submit\"\n >\n Analyze\n </NextButton>\n </Grid>\n </form>\n );\n};\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { CircularProgress, TextField } from '@material-ui/core';\nimport { TextFieldProps } from '@material-ui/core/TextField/TextField';\nimport { Autocomplete } from '@material-ui/lab';\nimport React from 'react';\nimport { Controller, FieldErrors } from 'react-hook-form';\n\ntype Props<TFieldValue extends string> = {\n name: TFieldValue;\n options: string[];\n required?: boolean;\n\n errors?: FieldErrors;\n rules?: React.ComponentProps<typeof Controller>['rules'];\n\n loading?: boolean;\n loadingText?: string;\n\n helperText?: React.ReactNode;\n errorHelperText?: string;\n\n textFieldProps?: Omit<TextFieldProps, 'required' | 'fullWidth'>;\n};\n\nexport const AutocompleteTextField = <TFieldValue extends string>({\n name,\n options,\n required,\n errors,\n rules,\n loading = false,\n loadingText,\n helperText,\n errorHelperText,\n textFieldProps = {},\n}: Props<TFieldValue>) => {\n return (\n <Controller\n name={name}\n rules={rules}\n render={({ field: { onChange } }) => (\n <Autocomplete\n loading={loading}\n loadingText={loadingText}\n options={options || []}\n autoSelect\n freeSolo\n onChange={(_event: React.ChangeEvent<{}>, value: string | null) =>\n onChange(value)\n }\n renderInput={params => (\n <TextField\n {...params}\n helperText={(errors?.[name] && errorHelperText) || helperText}\n error={Boolean(errors?.[name])}\n margin=\"normal\"\n variant=\"outlined\"\n required={required}\n InputProps={{\n ...params.InputProps,\n endAdornment: (\n <React.Fragment>\n {loading ? (\n <CircularProgress color=\"inherit\" size=\"1em\" />\n ) : null}\n {params.InputProps.endAdornment}\n </React.Fragment>\n ),\n }}\n {...textFieldProps}\n />\n )}\n />\n )}\n />\n );\n};\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport React from 'react';\nimport {\n FormProvider,\n SubmitHandler,\n UnpackNestedValue,\n useForm,\n UseFormProps,\n UseFormReturn,\n} from 'react-hook-form';\n\ntype Props<TFieldValues extends Record<string, any>> = Pick<\n UseFormProps<TFieldValues>,\n 'defaultValues'\n> & {\n onSubmit: SubmitHandler<TFieldValues>;\n\n render: (\n props: Pick<\n UseFormReturn<TFieldValues>,\n 'formState' | 'register' | 'control' | 'setValue'\n > & {\n values: UnpackNestedValue<TFieldValues>;\n },\n ) => React.ReactNode;\n};\n\n/**\n * A form wrapper that creates a form that is used to prepare a pull request. It\n * hosts the form logic.\n *\n * @param defaultValues - the default values of the form\n * @param onSubmit - a callback that is executed when the form is submitted\n * (initiated by a button of type=\"submit\")\n * @param render - render the form elements\n */\nexport const PreparePullRequestForm = <\n TFieldValues extends Record<string, any>,\n>({\n defaultValues,\n onSubmit,\n render,\n}: Props<TFieldValues>) => {\n const methods = useForm<TFieldValues>({ mode: 'onTouched', defaultValues });\n const { handleSubmit, watch, control, register, formState, setValue } =\n methods;\n\n return (\n <FormProvider {...methods}>\n <form onSubmit={handleSubmit(onSubmit)}>\n {render({ values: watch(), formState, register, control, setValue })}\n </form>\n </FormProvider>\n );\n};\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Entity } from '@backstage/catalog-model';\nimport { Card, CardContent, CardHeader } from '@material-ui/core';\nimport React from 'react';\nimport YAML from 'yaml';\nimport { CodeSnippet } from '@backstage/core-components';\nimport { trimEnd } from 'lodash';\n\ntype Props = {\n repositoryUrl: string;\n entities: Entity[];\n classes?: { card?: string; cardContent?: string };\n};\n\nexport const PreviewCatalogInfoComponent = ({\n repositoryUrl,\n entities,\n classes,\n}: Props) => {\n return (\n <Card variant=\"outlined\" className={classes?.card}>\n <CardHeader\n title={\n <code>{`${trimEnd(repositoryUrl, '/')}/catalog-info.yaml`}</code>\n }\n />\n\n <CardContent className={classes?.cardContent}>\n <CodeSnippet\n text={entities\n .map(e => YAML.stringify(e))\n .join('---\\n')\n .trim()}\n language=\"yaml\"\n />\n </CardContent>\n </Card>\n );\n};\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Card, CardContent, CardHeader } from '@material-ui/core';\nimport React from 'react';\nimport { MarkdownContent } from '@backstage/core-components';\n\ntype Props = {\n title: string;\n description: string;\n classes?: { card?: string; cardContent?: string };\n};\n\nexport const PreviewPullRequestComponent = ({\n title,\n description,\n classes,\n}: Props) => {\n return (\n <Card variant=\"outlined\" className={classes?.card}>\n <CardHeader title={title} subheader=\"Create a new Pull Request\" />\n <CardContent className={classes?.cardContent}>\n <MarkdownContent content={description} />\n </CardContent>\n </Card>\n );\n};\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Entity } from '@backstage/catalog-model';\nimport { errorApiRef, useApi } from '@backstage/core-plugin-api';\nimport { assertError } from '@backstage/errors';\nimport {\n catalogApiRef,\n formatEntityRefTitle,\n} from '@backstage/plugin-catalog-react';\nimport { Box, FormHelperText, Grid, Typography } from '@material-ui/core';\nimport { makeStyles } from '@material-ui/core/styles';\nimport React, { useCallback, useEffect, useState } from 'react';\nimport { UnpackNestedValue, UseFormReturn } from 'react-hook-form';\nimport useAsync from 'react-use/lib/useAsync';\nimport YAML from 'yaml';\nimport { AnalyzeResult, catalogImportApiRef } from '../../api';\nimport { PartialEntity } from '../../types';\nimport { BackButton, NextButton } from '../Buttons';\nimport { PrepareResult } from '../useImportState';\nimport { PreparePullRequestForm } from './PreparePullRequestForm';\nimport { PreviewCatalogInfoComponent } from './PreviewCatalogInfoComponent';\nimport { PreviewPullRequestComponent } from './PreviewPullRequestComponent';\n\nconst useStyles = makeStyles(theme => ({\n previewCard: {\n marginTop: theme.spacing(1),\n },\n previewCardContent: {\n paddingTop: 0,\n },\n}));\n\ntype FormData = {\n title: string;\n body: string;\n componentName: string;\n owner: string;\n useCodeowners: boolean;\n};\n\ntype Props = {\n analyzeResult: Extract<AnalyzeResult, { type: 'repository' }>;\n onPrepare: (\n result: PrepareResult,\n opts?: { notRepeatable?: boolean },\n ) => void;\n onGoBack?: () => void;\n\n renderFormFields: (\n props: Pick<\n UseFormReturn<FormData>,\n 'register' | 'setValue' | 'formState'\n > & {\n values: UnpackNestedValue<FormData>;\n groups: string[];\n groupsLoading: boolean;\n },\n ) => React.ReactNode;\n};\n\nexport function generateEntities(\n entities: PartialEntity[],\n componentName: string,\n owner?: string,\n): Entity[] {\n return entities.map(e => ({\n ...e,\n apiVersion: e.apiVersion!,\n kind: e.kind!,\n metadata: {\n ...e.metadata,\n name: componentName,\n },\n spec: {\n ...e.spec,\n ...(owner ? { owner } : {}),\n },\n }));\n}\n\nexport const StepPrepareCreatePullRequest = ({\n analyzeResult,\n onPrepare,\n onGoBack,\n renderFormFields,\n}: Props) => {\n const classes = useStyles();\n const catalogApi = useApi(catalogApiRef);\n const catalogImportApi = useApi(catalogImportApiRef);\n const errorApi = useApi(errorApiRef);\n\n const [submitted, setSubmitted] = useState(false);\n const [error, setError] = useState<string>();\n\n const {\n loading: prDefaultsLoading,\n value: prDefaults,\n error: prDefaultsError,\n } = useAsync(\n () => catalogImportApi.preparePullRequest!(),\n [catalogImportApi.preparePullRequest],\n );\n\n useEffect(() => {\n if (prDefaultsError) {\n errorApi.post(prDefaultsError);\n }\n }, [prDefaultsError, errorApi]);\n\n const { loading: groupsLoading, value: groups } = useAsync(async () => {\n const groupEntities = await catalogApi.getEntities({\n filter: { kind: 'group' },\n });\n\n return groupEntities.items\n .map(e => formatEntityRefTitle(e, { defaultKind: 'group' }))\n .sort();\n });\n\n const handleResult = useCallback(\n async (data: FormData) => {\n setSubmitted(true);\n\n try {\n const pr = await catalogImportApi.submitPullRequest({\n repositoryUrl: analyzeResult.url,\n title: data.title,\n body: data.body,\n fileContent: generateEntities(\n analyzeResult.generatedEntities,\n data.componentName,\n data.owner,\n )\n .map(e => YAML.stringify(e))\n .join('---\\n'),\n });\n\n onPrepare(\n {\n type: 'repository',\n url: analyzeResult.url,\n integrationType: analyzeResult.integrationType,\n pullRequest: {\n url: pr.link,\n },\n locations: [\n {\n target: pr.location,\n entities: generateEntities(\n analyzeResult.generatedEntities,\n data.componentName,\n data.owner,\n ).map(e => ({\n kind: e.kind,\n namespace: e.metadata.namespace!,\n name: e.metadata.name,\n })),\n },\n ],\n },\n { notRepeatable: true },\n );\n } catch (e) {\n assertError(e);\n setError(e.message);\n setSubmitted(false);\n }\n },\n [\n analyzeResult.generatedEntities,\n analyzeResult.integrationType,\n analyzeResult.url,\n catalogImportApi,\n onPrepare,\n ],\n );\n\n return (\n <>\n <Typography>\n You entered a link to a {analyzeResult.integrationType} repository but a{' '}\n <code>catalog-info.yaml</code> could not be found. Use this form to open\n a Pull Request that creates one.\n </Typography>\n\n {!prDefaultsLoading && (\n <PreparePullRequestForm<FormData>\n onSubmit={handleResult}\n defaultValues={{\n title: prDefaults?.title ?? '',\n body: prDefaults?.body ?? '',\n owner:\n (analyzeResult.generatedEntities[0]?.spec?.owner as string) || '',\n componentName:\n analyzeResult.generatedEntities[0]?.metadata?.name || '',\n useCodeowners: false,\n }}\n render={({ values, formState, register, setValue }) => (\n <>\n {renderFormFields({\n values,\n formState,\n register,\n setValue,\n groups: groups ?? [],\n groupsLoading,\n })}\n\n <Box marginTop={2}>\n <Typography variant=\"h6\">Preview Pull Request</Typography>\n </Box>\n\n <PreviewPullRequestComponent\n title={values.title}\n description={values.body}\n classes={{\n card: classes.previewCard,\n cardContent: classes.previewCardContent,\n }}\n />\n\n <Box marginTop={2} marginBottom={1}>\n <Typography variant=\"h6\">Preview Entities</Typography>\n </Box>\n\n <PreviewCatalogInfoComponent\n entities={generateEntities(\n analyzeResult.generatedEntities,\n values.componentName,\n values.owner,\n )}\n repositoryUrl={analyzeResult.url}\n classes={{\n card: classes.previewCard,\n cardContent: classes.previewCardContent,\n }}\n />\n\n {error && <FormHelperText error>{error}</FormHelperText>}\n\n <Grid container spacing={0}>\n {onGoBack && (\n <BackButton onClick={onGoBack} disabled={submitted} />\n )}\n <NextButton\n type=\"submit\"\n disabled={Boolean(\n formState.errors.title ||\n formState.errors.body ||\n formState.errors.owner,\n )}\n loading={submitted}\n >\n Create PR\n </NextButton>\n </Grid>\n </>\n )}\n />\n )}\n </>\n );\n};\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n Checkbox,\n Grid,\n ListItem,\n ListItemIcon,\n ListItemText,\n Typography,\n} from '@material-ui/core';\nimport LocationOnIcon from '@material-ui/icons/LocationOn';\nimport React, { useCallback, useState } from 'react';\nimport { AnalyzeResult } from '../../api';\nimport { BackButton, NextButton } from '../Buttons';\nimport { EntityListComponent } from '../EntityListComponent';\nimport { PrepareResult } from '../useImportState';\nimport partition from 'lodash/partition';\n\ntype Props = {\n analyzeResult: Extract<AnalyzeResult, { type: 'locations' }>;\n prepareResult?: PrepareResult;\n onPrepare: (result: PrepareResult) => void;\n onGoBack?: () => void;\n};\n\n/**\n * A form that lets a user select one of a list of locations to import\n *\n * @param analyzeResult - the result of the analysis\n * @param prepareResult - the selectected locations from a previous step\n * @param onPrepare - called after the selection\n * @param onGoBack - called to go back to the previous step\n */\nexport const StepPrepareSelectLocations = ({\n analyzeResult,\n prepareResult,\n onPrepare,\n onGoBack,\n}: Props) => {\n const [selectedUrls, setSelectedUrls] = useState<string[]>(\n prepareResult?.locations.map(l => l.target) || [],\n );\n\n const [existingLocations, locations] = partition(\n analyzeResult?.locations,\n l => l.exists,\n );\n\n const handleResult = useCallback(async () => {\n onPrepare({\n type: 'locations',\n locations: locations.filter((l: any) => selectedUrls.includes(l.target)),\n });\n }, [locations, onPrepare, selectedUrls]);\n\n const onItemClick = (url: string) => {\n setSelectedUrls(urls =>\n urls.includes(url) ? urls.filter(u => u !== url) : urls.concat(url),\n );\n };\n\n const onSelectAll = () => {\n setSelectedUrls(urls =>\n urls.length < locations.length ? locations.map(l => l.target) : [],\n );\n };\n\n return (\n <>\n {locations.length > 0 && (\n <>\n <Typography>\n Select one or more locations that are present in your git\n repository:\n </Typography>\n <EntityListComponent\n firstListItem={\n <ListItem dense button onClick={onSelectAll}>\n <ListItemIcon>\n <Checkbox\n edge=\"start\"\n checked={selectedUrls.length === locations.length}\n indeterminate={\n selectedUrls.length > 0 &&\n selectedUrls.length < locations.length\n }\n tabIndex={-1}\n disableRipple\n />\n </ListItemIcon>\n <ListItemText primary=\"Select All\" />\n </ListItem>\n }\n onItemClick={onItemClick}\n locations={locations}\n locationListItemIcon={target => (\n <Checkbox\n edge=\"start\"\n checked={selectedUrls.includes(target)}\n tabIndex={-1}\n disableRipple\n />\n )}\n collapsed\n />\n </>\n )}\n\n {existingLocations.length > 0 && (\n <>\n <Typography>These locations already exist in the catalog:</Typography>\n <EntityListComponent\n locations={existingLocations}\n locationListItemIcon={() => <LocationOnIcon />}\n withLinks\n collapsed\n />\n </>\n )}\n\n <Grid container spacing={0}>\n {onGoBack && <BackButton onClick={onGoBack} />}\n <NextButton disabled={selectedUrls.length === 0} onClick={handleResult}>\n Review\n </NextButton>\n </Grid>\n </>\n );\n};\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { catalogApiRef } from '@backstage/plugin-catalog-react';\nimport { FormHelperText, Grid, Typography } from '@material-ui/core';\nimport LocationOnIcon from '@material-ui/icons/LocationOn';\nimport React, { useCallback, useState } from 'react';\nimport { BackButton, NextButton } from '../Buttons';\nimport { EntityListComponent } from '../EntityListComponent';\nimport { PrepareResult, ReviewResult } from '../useImportState';\n\nimport { configApiRef, useApi } from '@backstage/core-plugin-api';\nimport { Link } from '@backstage/core-components';\nimport { stringifyEntityRef } from '@backstage/catalog-model';\nimport { assertError } from '@backstage/errors';\n\ntype Props = {\n prepareResult: PrepareResult;\n onReview: (result: ReviewResult) => void;\n onGoBack?: () => void;\n};\n\nexport const StepReviewLocation = ({\n prepareResult,\n onReview,\n onGoBack,\n}: Props) => {\n const catalogApi = useApi(catalogApiRef);\n const configApi = useApi(configApiRef);\n\n const appTitle = configApi.getOptional('app.title') || 'Backstage';\n\n const [submitted, setSubmitted] = useState(false);\n const [error, setError] = useState<string>();\n const exists =\n prepareResult.type === 'locations' &&\n prepareResult.locations.some(l => l.exists)\n ? true\n : false;\n const handleClick = useCallback(async () => {\n setSubmitted(true);\n try {\n let refreshed = new Array<{ target: string }>();\n if (prepareResult.type === 'locations') {\n refreshed = await Promise.all(\n prepareResult.locations\n .filter(l => l.exists)\n .map(async l => {\n const ref = stringifyEntityRef(l.entities[0] ?? l);\n await catalogApi.refreshEntity(ref);\n return { target: l.target };\n }),\n );\n }\n\n const locations = await Promise.all(\n prepareResult.locations\n .filter((l: unknown) => !(l as { exists?: boolean }).exists)\n .map(async l => {\n const result = await catalogApi.addLocation({\n type: 'url',\n target: l.target,\n presence:\n prepareResult.type === 'repository' ? 'optional' : 'required',\n });\n return {\n target: result.location.target,\n entities: result.entities,\n };\n }),\n );\n\n onReview({\n ...prepareResult,\n ...{ refreshed },\n locations,\n });\n } catch (e) {\n assertError(e);\n // TODO: this error should be handled differently. We add it as 'optional' and\n // it is not uncommon that a PR has not been merged yet.\n if (\n prepareResult.type === 'repository' &&\n e.message.startsWith(\n 'Location was added but has no entities specified yet',\n )\n ) {\n onReview({\n ...prepareResult,\n locations: prepareResult.locations.map(l => ({\n target: l.target,\n entities: [],\n })),\n });\n } else {\n setError(e.message);\n setSubmitted(false);\n }\n }\n }, [prepareResult, onReview, catalogApi]);\n\n return (\n <>\n {prepareResult.type === 'repository' && (\n <>\n <Typography paragraph>\n The following Pull Request has been opened:{' '}\n <Link\n to={prepareResult.pullRequest.url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n {prepareResult.pullRequest.url}\n </Link>\n </Typography>\n\n <Typography paragraph>\n You can already import the location and {appTitle} will fetch the\n entities as soon as the Pull Request is merged.\n </Typography>\n </>\n )}\n\n <Typography>\n {exists\n ? 'The following locations already exist in the catalog:'\n : 'The following entities will be added to the catalog:'}\n </Typography>\n\n <EntityListComponent\n locations={prepareResult.locations}\n locationListItemIcon={() => <LocationOnIcon />}\n />\n\n {error && <FormHelperText error>{error}</FormHelperText>}\n\n <Grid container spacing={0}>\n {onGoBack && <BackButton onClick={onGoBack} disabled={submitted} />}\n <NextButton\n disabled={submitted}\n loading={submitted}\n onClick={() => handleClick()}\n >\n {exists ? 'Refresh' : 'Import'}\n </NextButton>\n </Grid>\n </>\n );\n};\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n Box,\n Checkbox,\n FormControlLabel,\n FormHelperText,\n StepLabel,\n TextField,\n Typography,\n} from '@material-ui/core';\nimport React from 'react';\nimport { BackButton } from '../Buttons';\nimport { asInputRef } from '../helpers';\nimport { StepFinishImportLocation } from '../StepFinishImportLocation';\nimport { StepInitAnalyzeUrl } from '../StepInitAnalyzeUrl';\nimport {\n AutocompleteTextField,\n StepPrepareCreatePullRequest,\n} from '../StepPrepareCreatePullRequest';\nimport { StepPrepareSelectLocations } from '../StepPrepareSelectLocations';\nimport { StepReviewLocation } from '../StepReviewLocation';\nimport { StepperApis } from '../types';\nimport { ImportFlows, ImportState } from '../useImportState';\n\nexport type StepConfiguration = {\n stepLabel: React.ReactElement;\n content: React.ReactElement;\n};\n\nexport type StepperProvider = {\n analyze: (\n s: Extract<ImportState, { activeState: 'analyze' }>,\n opts: { apis: StepperApis },\n ) => StepConfiguration;\n prepare: (\n s: Extract<ImportState, { activeState: 'prepare' }>,\n opts: { apis: StepperApis },\n ) => StepConfiguration;\n review: (\n s: Extract<ImportState, { activeState: 'review' }>,\n opts: { apis: StepperApis },\n ) => StepConfiguration;\n finish: (\n s: Extract<ImportState, { activeState: 'finish' }>,\n opts: { apis: StepperApis },\n ) => StepConfiguration;\n};\n\n/**\n * The default stepper generation function.\n *\n * Override this function to customize the import flow. Each flow should at\n * least override the prepare operation.\n *\n * @param flow - the name of the active flow\n * @param defaults - the default steps\n */\nexport function defaultGenerateStepper(\n flow: ImportFlows,\n defaults: StepperProvider,\n): StepperProvider {\n switch (flow) {\n // the prepare step is skipped but the label of the step is updated\n case 'single-location':\n return {\n ...defaults,\n prepare: () => ({\n stepLabel: (\n <StepLabel\n optional={\n <Typography variant=\"caption\">\n Discovered Locations: 1\n </Typography>\n }\n >\n Select Locations\n </StepLabel>\n ),\n content: <></>,\n }),\n };\n\n // let the user select one or more of the discovered locations in the prepare step\n case 'multiple-locations':\n return {\n ...defaults,\n prepare: (state, opts) => {\n if (state.analyzeResult.type !== 'locations') {\n return defaults.prepare(state, opts);\n }\n\n return {\n stepLabel: (\n <StepLabel\n optional={\n <Typography variant=\"caption\">\n Discovered Locations: {state.analyzeResult.locations.length}\n </Typography>\n }\n >\n Select Locations\n </StepLabel>\n ),\n content: (\n <StepPrepareSelectLocations\n analyzeResult={state.analyzeResult}\n prepareResult={state.prepareResult}\n onPrepare={state.onPrepare}\n onGoBack={state.onGoBack}\n />\n ),\n };\n },\n };\n\n case 'no-location':\n return {\n ...defaults,\n prepare: (state, opts) => {\n if (state.analyzeResult.type !== 'repository') {\n return defaults.prepare(state, opts);\n }\n\n return {\n stepLabel: <StepLabel>Create Pull Request</StepLabel>,\n content: (\n <StepPrepareCreatePullRequest\n analyzeResult={state.analyzeResult}\n onPrepare={state.onPrepare}\n onGoBack={state.onGoBack}\n renderFormFields={({\n values,\n setValue,\n formState,\n groupsLoading,\n groups,\n register,\n }) => (\n <>\n <Box marginTop={2}>\n <Typography variant=\"h6\">Pull Request Details</Typography>\n </Box>\n\n <TextField\n {...asInputRef(\n register('title', {\n required: true,\n }),\n )}\n label=\"Pull Request Title\"\n placeholder=\"Add Backstage catalog entity descriptor files\"\n margin=\"normal\"\n variant=\"outlined\"\n fullWidth\n error={Boolean(formState.errors.title)}\n required\n />\n\n <TextField\n {...asInputRef(\n register('body', {\n required: true,\n }),\n )}\n label=\"Pull Request Body\"\n placeholder=\"A describing text with Markdown support\"\n margin=\"normal\"\n variant=\"outlined\"\n fullWidth\n error={Boolean(formState.errors.body)}\n multiline\n required\n />\n\n <Box marginTop={2}>\n <Typography variant=\"h6\">Entity Configuration</Typography>\n </Box>\n\n <TextField\n {...asInputRef(\n register('componentName', { required: true }),\n )}\n label=\"Name of the created component\"\n placeholder=\"my-component\"\n margin=\"normal\"\n variant=\"outlined\"\n fullWidth\n error={Boolean(formState.errors.componentName)}\n required\n />\n\n {!values.useCodeowners && (\n <AutocompleteTextField\n name=\"owner\"\n errors={formState.errors}\n options={groups || []}\n loading={groupsLoading}\n loadingText=\"Loading groups…\"\n helperText=\"Select an owner from the list or enter a reference to a Group or a User\"\n errorHelperText=\"required value\"\n textFieldProps={{\n label: 'Entity Owner',\n placeholder: 'my-group',\n }}\n rules={{ required: true }}\n required\n />\n )}\n\n <FormControlLabel\n control={\n <Checkbox\n {...asInputRef(register('useCodeowners'))}\n onChange={(_, value) => {\n if (value) {\n setValue('owner', '');\n }\n }}\n />\n }\n label={\n <>\n Use <em>CODEOWNERS</em> file as Entity Owner\n </>\n }\n />\n <FormHelperText>\n WARNING: This may fail if no CODEOWNERS file is found at\n the target location.\n </FormHelperText>\n </>\n )}\n />\n ),\n };\n },\n };\n\n default:\n return defaults;\n }\n}\n\nexport const defaultStepper: StepperProvider = {\n analyze: (state, { apis }) => ({\n stepLabel: <StepLabel>Select URL</StepLabel>,\n content: (\n <StepInitAnalyzeUrl\n key=\"analyze\"\n analysisUrl={state.analysisUrl}\n onAnalysis={state.onAnalysis}\n disablePullRequest={!apis.catalogImportApi.preparePullRequest}\n />\n ),\n }),\n\n prepare: state => ({\n stepLabel: (\n <StepLabel optional={<Typography variant=\"caption\">Optional</Typography>}>\n Import Actions\n </StepLabel>\n ),\n content: <BackButton onClick={state.onGoBack} />,\n }),\n\n review: state => ({\n stepLabel: <StepLabel>Review</StepLabel>,\n content: (\n <StepReviewLocation\n prepareResult={state.prepareResult}\n onReview={state.onReview}\n onGoBack={state.onGoBack}\n />\n ),\n }),\n\n finish: state => ({\n stepLabel: <StepLabel>Finish</StepLabel>,\n content: (\n <StepFinishImportLocation\n prepareResult={state.prepareResult}\n onReset={state.onReset}\n />\n ),\n }),\n};\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { InfoCard, InfoCardVariants } from '@backstage/core-components';\nimport { useApi } from '@backstage/core-plugin-api';\nimport { Step, StepContent, Stepper } from '@material-ui/core';\nimport { makeStyles } from '@material-ui/core/styles';\nimport React, { useMemo } from 'react';\nimport { catalogImportApiRef } from '../../api';\nimport { ImportFlows, ImportState, useImportState } from '../useImportState';\nimport {\n defaultGenerateStepper,\n defaultStepper,\n StepConfiguration,\n StepperProvider,\n} from './defaults';\n\nconst useStyles = makeStyles(() => ({\n stepperRoot: {\n padding: 0,\n },\n}));\n\ntype Props = {\n initialUrl?: string;\n generateStepper?: (\n flow: ImportFlows,\n defaults: StepperProvider,\n ) => StepperProvider;\n variant?: InfoCardVariants;\n};\n\nexport const ImportStepper = ({\n initialUrl,\n generateStepper = defaultGenerateStepper,\n variant,\n}: Props) => {\n const catalogImportApi = useApi(catalogImportApiRef);\n const classes = useStyles();\n const state = useImportState({ initialUrl });\n\n const states = useMemo<StepperProvider>(\n () => generateStepper(state.activeFlow, defaultStepper),\n [generateStepper, state.activeFlow],\n );\n\n const render = (step: StepConfiguration) => {\n return (\n <Step>\n {step.stepLabel}\n <StepContent>{step.content}</StepContent>\n </Step>\n );\n };\n\n return (\n <InfoCard variant={variant}>\n <Stepper\n classes={{ root: classes.stepperRoot }}\n activeStep={state.activeStepNumber}\n orientation=\"vertical\"\n >\n {render(\n states.analyze(\n state as Extract<ImportState, { activeState: 'analyze' }>,\n { apis: { catalogImportApi } },\n ),\n )}\n {render(\n states.prepare(\n state as Extract<ImportState, { activeState: 'prepare' }>,\n { apis: { catalogImportApi } },\n ),\n )}\n {render(\n states.review(\n state as Extract<ImportState, { activeState: 'review' }>,\n { apis: { catalogImportApi } },\n ),\n )}\n {render(\n states.finish(\n state as Extract<ImportState, { activeState: 'finish' }>,\n { apis: { catalogImportApi } },\n ),\n )}\n </Stepper>\n </InfoCard>\n );\n};\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n Content,\n ContentHeader,\n Header,\n Page,\n SupportButton,\n} from '@backstage/core-components';\nimport { configApiRef, useApi } from '@backstage/core-plugin-api';\nimport { Grid } from '@material-ui/core';\nimport React from 'react';\nimport { ImportInfoCard } from '../ImportInfoCard';\nimport { ImportStepper } from '../ImportStepper';\n\nexport const DefaultImportPage = () => {\n const configApi = useApi(configApiRef);\n const appTitle = configApi.getOptional('app.title') || 'Backstage';\n\n return (\n <Page themeId=\"home\">\n <Header title=\"Register an existing component\" />\n <Content>\n <ContentHeader title={`Start tracking your component in ${appTitle}`}>\n <SupportButton>\n Start tracking your component in {appTitle} by adding it to the\n software catalog.\n </SupportButton>\n </ContentHeader>\n\n <Grid container spacing={2} direction=\"row-reverse\">\n <Grid item xs={12} md={4} lg={6} xl={8}>\n <ImportInfoCard />\n </Grid>\n\n <Grid item xs={12} md={8} lg={6} xl={4}>\n <ImportStepper />\n </Grid>\n </Grid>\n </Content>\n </Page>\n );\n};\n"],"names":["useStyles","this"],"mappings":"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"}
1
+ {"version":3,"file":"index.esm.js","sources":["../src/api/CatalogImportApi.ts","../src/api/GitHub.ts","../src/components/helpers.ts","../src/api/CatalogImportClient.ts","../src/plugin.ts","../src/hooks/useCatalogFilename.ts","../src/components/ImportInfoCard/ImportInfoCard.tsx","../src/components/useImportState.ts","../src/components/Buttons/index.tsx","../src/components/EntityListComponent/EntityListComponent.tsx","../src/components/StepFinishImportLocation/StepFinishImportLocation.tsx","../src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.tsx","../src/components/StepPrepareCreatePullRequest/AutocompleteTextField.tsx","../src/components/StepPrepareCreatePullRequest/PreparePullRequestForm.tsx","../src/components/StepPrepareCreatePullRequest/PreviewCatalogInfoComponent.tsx","../src/components/StepPrepareCreatePullRequest/PreviewPullRequestComponent.tsx","../src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx","../src/components/StepPrepareSelectLocations/StepPrepareSelectLocations.tsx","../src/components/StepReviewLocation/StepReviewLocation.tsx","../src/components/ImportStepper/defaults.tsx","../src/components/ImportStepper/ImportStepper.tsx","../src/components/DefaultImportPage/DefaultImportPage.tsx"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { EntityName } from '@backstage/catalog-model';\nimport { createApiRef } from '@backstage/core-plugin-api';\nimport { PartialEntity } from '../types';\n\nexport const catalogImportApiRef = createApiRef<CatalogImportApi>({\n id: 'plugin.catalog-import.service',\n});\n\n// result of the analyze state\nexport type AnalyzeResult =\n | {\n type: 'locations';\n locations: Array<{\n target: string;\n exists?: boolean;\n entities: EntityName[];\n }>;\n }\n | {\n type: 'repository';\n url: string;\n integrationType: string;\n generatedEntities: PartialEntity[];\n };\n\nexport interface CatalogImportApi {\n analyzeUrl(url: string): Promise<AnalyzeResult>;\n\n preparePullRequest?(): Promise<{\n title: string;\n body: string;\n }>;\n submitPullRequest(options: {\n repositoryUrl: string;\n fileContent: string;\n title: string;\n body: string;\n }): Promise<{ link: string; location: string }>;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ScmIntegrationRegistry } from '@backstage/integration';\nimport parseGitUrl from 'git-url-parse';\n\nexport const getGithubIntegrationConfig = (\n scmIntegrationsApi: ScmIntegrationRegistry,\n location: string,\n) => {\n const integration = scmIntegrationsApi.github.byUrl(location);\n if (!integration) {\n return undefined;\n }\n\n const { name: repo, owner } = parseGitUrl(location);\n return {\n repo,\n owner,\n githubIntegrationConfig: integration.config,\n };\n};\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { Config } from '@backstage/config';\nimport { UseFormRegisterReturn } from 'react-hook-form';\n\n/**\n * A helper that converts the result of a render('name', opts) to make it compatible with material-ui.\n *\n * See also https://github.com/react-hook-form/react-hook-form/issues/4629#issuecomment-815840872\n * TODO: remove when updating to material-ui v5 (https://github.com/mui-org/material-ui/pull/23174)\n *\n * @param renderResult - the result of a render('name', opts)\n */\nexport function asInputRef(renderResult: UseFormRegisterReturn) {\n const { ref, ...rest } = renderResult;\n return {\n inputRef: ref,\n ...rest,\n };\n}\n\nexport function getCatalogFilename(config: Config): string {\n return (\n config.getOptionalString('catalog.import.entityFilename') ??\n 'catalog-info.yaml'\n );\n}\n\nexport function getBranchName(config: Config): string {\n return (\n config.getOptionalString('catalog.import.pullRequestBranchName') ??\n 'backstage-integration'\n );\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { CatalogApi } from '@backstage/catalog-client';\nimport { EntityName } from '@backstage/catalog-model';\nimport {\n ConfigApi,\n DiscoveryApi,\n IdentityApi,\n} from '@backstage/core-plugin-api';\nimport {\n GitHubIntegrationConfig,\n ScmIntegrationRegistry,\n} from '@backstage/integration';\nimport { ScmAuthApi } from '@backstage/integration-react';\nimport { Octokit } from '@octokit/rest';\nimport { Base64 } from 'js-base64';\nimport { PartialEntity } from '../types';\nimport { AnalyzeResult, CatalogImportApi } from './CatalogImportApi';\nimport { getGithubIntegrationConfig } from './GitHub';\nimport { trimEnd } from 'lodash';\nimport { getBranchName, getCatalogFilename } from '../components/helpers';\n\nexport class CatalogImportClient implements CatalogImportApi {\n private readonly discoveryApi: DiscoveryApi;\n private readonly identityApi: IdentityApi;\n private readonly scmAuthApi: ScmAuthApi;\n private readonly scmIntegrationsApi: ScmIntegrationRegistry;\n private readonly catalogApi: CatalogApi;\n private readonly configApi: ConfigApi;\n\n constructor(options: {\n discoveryApi: DiscoveryApi;\n scmAuthApi: ScmAuthApi;\n identityApi: IdentityApi;\n scmIntegrationsApi: ScmIntegrationRegistry;\n catalogApi: CatalogApi;\n configApi: ConfigApi;\n }) {\n this.discoveryApi = options.discoveryApi;\n this.scmAuthApi = options.scmAuthApi;\n this.identityApi = options.identityApi;\n this.scmIntegrationsApi = options.scmIntegrationsApi;\n this.catalogApi = options.catalogApi;\n this.configApi = options.configApi;\n }\n\n async analyzeUrl(url: string): Promise<AnalyzeResult> {\n if (\n new URL(url).pathname.match(/\\.ya?ml$/) ||\n new URL(url).searchParams.get('path')?.match(/.ya?ml$/)\n ) {\n const location = await this.catalogApi.addLocation({\n type: 'url',\n target: url,\n dryRun: true,\n });\n\n return {\n type: 'locations',\n locations: [\n {\n exists: location.exists,\n target: location.location.target,\n entities: location.entities.map(e => ({\n kind: e.kind,\n namespace: e.metadata.namespace ?? 'default',\n name: e.metadata.name,\n })),\n },\n ],\n };\n }\n\n const ghConfig = getGithubIntegrationConfig(this.scmIntegrationsApi, url);\n if (!ghConfig) {\n const other = this.scmIntegrationsApi.byUrl(url);\n const catalogFilename = getCatalogFilename(this.configApi);\n\n if (other) {\n throw new Error(\n `The ${other.title} integration only supports full URLs to ${catalogFilename} files. Did you try to pass in the URL of a directory instead?`,\n );\n }\n throw new Error(\n `This URL was not recognized as a valid GitHub URL because there was no configured integration that matched the given host name. You could try to paste the full URL to a ${catalogFilename} file instead.`,\n );\n }\n\n // TODO: this could be part of the analyze-location endpoint\n const locations = await this.checkGitHubForExistingCatalogInfo({\n ...ghConfig,\n url,\n });\n\n if (locations.length > 0) {\n return {\n type: 'locations',\n locations,\n };\n }\n\n return {\n type: 'repository',\n integrationType: 'github',\n url: url,\n generatedEntities: await this.generateEntityDefinitions({\n repo: url,\n }),\n };\n }\n\n async preparePullRequest(): Promise<{\n title: string;\n body: string;\n }> {\n const appTitle =\n this.configApi.getOptionalString('app.title') ?? 'Backstage';\n const appBaseUrl = this.configApi.getString('app.baseUrl');\n const catalogFilename = getCatalogFilename(this.configApi);\n\n return {\n title: `Add ${catalogFilename} config file`,\n body: `This pull request adds a **Backstage entity metadata file** \\\nto this repository so that the component can be added to the \\\n[${appTitle} software catalog](${appBaseUrl}).\\n\\nAfter this pull request is merged, \\\nthe component will become available.\\n\\nFor more information, read an \\\n[overview of the Backstage software catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview).`,\n };\n }\n\n async submitPullRequest({\n repositoryUrl,\n fileContent,\n title,\n body,\n }: {\n repositoryUrl: string;\n fileContent: string;\n title: string;\n body: string;\n }): Promise<{ link: string; location: string }> {\n const ghConfig = getGithubIntegrationConfig(\n this.scmIntegrationsApi,\n repositoryUrl,\n );\n\n if (ghConfig) {\n return await this.submitGitHubPrToRepo({\n ...ghConfig,\n repositoryUrl,\n fileContent,\n title,\n body,\n });\n }\n\n throw new Error('unimplemented!');\n }\n\n // TODO: this could be part of the catalog api\n private async generateEntityDefinitions({\n repo,\n }: {\n repo: string;\n }): Promise<PartialEntity[]> {\n const { token } = await this.identityApi.getCredentials();\n const response = await fetch(\n `${await this.discoveryApi.getBaseUrl('catalog')}/analyze-location`,\n {\n headers: {\n 'Content-Type': 'application/json',\n ...(token && { Authorization: `Bearer ${token}` }),\n },\n method: 'POST',\n body: JSON.stringify({\n location: { type: 'url', target: repo },\n }),\n },\n ).catch(e => {\n throw new Error(`Failed to generate entity definitions, ${e.message}`);\n });\n if (!response.ok) {\n throw new Error(\n `Failed to generate entity definitions. Received http response ${response.status}: ${response.statusText}`,\n );\n }\n\n const payload = await response.json();\n return payload.generateEntities.map((x: any) => x.entity);\n }\n\n // TODO: this response should better be part of the analyze-locations response and scm-independent / implemented per scm\n private async checkGitHubForExistingCatalogInfo({\n url,\n owner,\n repo,\n githubIntegrationConfig,\n }: {\n url: string;\n owner: string;\n repo: string;\n githubIntegrationConfig: GitHubIntegrationConfig;\n }): Promise<\n Array<{\n target: string;\n entities: EntityName[];\n }>\n > {\n const { token } = await this.scmAuthApi.getCredentials({ url });\n const octo = new Octokit({\n auth: token,\n baseUrl: githubIntegrationConfig.apiBaseUrl,\n });\n const catalogFilename = getCatalogFilename(this.configApi);\n const query = `repo:${owner}/${repo}+filename:${catalogFilename}`;\n\n const searchResult = await octo.search.code({ q: query }).catch(e => {\n throw new Error(\n formatHttpErrorMessage(\n \"Couldn't search repository for metadata file.\",\n e,\n ),\n );\n });\n const exists = searchResult.data.total_count > 0;\n if (exists) {\n const repoInformation = await octo.repos.get({ owner, repo }).catch(e => {\n throw new Error(formatHttpErrorMessage(\"Couldn't fetch repo data\", e));\n });\n const defaultBranch = repoInformation.data.default_branch;\n\n return await Promise.all(\n searchResult.data.items\n .map(i => `${trimEnd(url, '/')}/blob/${defaultBranch}/${i.path}`)\n .map(async target => {\n const result = await this.catalogApi.addLocation({\n type: 'url',\n target,\n dryRun: true,\n });\n return {\n target,\n exists: result.exists,\n entities: result.entities.map(e => ({\n kind: e.kind,\n namespace: e.metadata.namespace ?? 'default',\n name: e.metadata.name,\n })),\n };\n }),\n );\n }\n\n return [];\n }\n\n // TODO: extract this function and implement for non-github\n private async submitGitHubPrToRepo({\n owner,\n repo,\n title,\n body,\n fileContent,\n repositoryUrl,\n githubIntegrationConfig,\n }: {\n owner: string;\n repo: string;\n title: string;\n body: string;\n fileContent: string;\n repositoryUrl: string;\n githubIntegrationConfig: GitHubIntegrationConfig;\n }): Promise<{ link: string; location: string }> {\n const { token } = await this.scmAuthApi.getCredentials({\n url: repositoryUrl,\n additionalScope: {\n repoWrite: true,\n },\n });\n\n const octo = new Octokit({\n auth: token,\n baseUrl: githubIntegrationConfig.apiBaseUrl,\n });\n\n const branchName = getBranchName(this.configApi);\n const fileName = getCatalogFilename(this.configApi);\n\n const repoData = await octo.repos\n .get({\n owner,\n repo,\n })\n .catch(e => {\n throw new Error(formatHttpErrorMessage(\"Couldn't fetch repo data\", e));\n });\n\n const parentRef = await octo.git\n .getRef({\n owner,\n repo,\n ref: `heads/${repoData.data.default_branch}`,\n })\n .catch(e => {\n throw new Error(\n formatHttpErrorMessage(\"Couldn't fetch default branch data\", e),\n );\n });\n\n await octo.git\n .createRef({\n owner,\n repo,\n ref: `refs/heads/${branchName}`,\n sha: parentRef.data.object.sha,\n })\n .catch(e => {\n throw new Error(\n formatHttpErrorMessage(\n `Couldn't create a new branch with name '${branchName}'`,\n e,\n ),\n );\n });\n\n await octo.repos\n .createOrUpdateFileContents({\n owner,\n repo,\n path: fileName,\n message: title,\n content: Base64.encode(fileContent),\n branch: branchName,\n })\n .catch(e => {\n throw new Error(\n formatHttpErrorMessage(\n `Couldn't create a commit with ${fileName} file added`,\n e,\n ),\n );\n });\n\n const pullRequestResponse = await octo.pulls\n .create({\n owner,\n repo,\n title,\n head: branchName,\n body,\n base: repoData.data.default_branch,\n })\n .catch(e => {\n throw new Error(\n formatHttpErrorMessage(\n `Couldn't create a pull request for ${branchName} branch`,\n e,\n ),\n );\n });\n\n return {\n link: pullRequestResponse.data.html_url,\n location: `https://${githubIntegrationConfig.host}/${owner}/${repo}/blob/${repoData.data.default_branch}/${fileName}`,\n };\n }\n}\n\nfunction formatHttpErrorMessage(\n message: string,\n error: { status: number; message: string },\n) {\n return `${message}, received http response status code ${error.status}: ${error.message}`;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n configApiRef,\n createApiFactory,\n createPlugin,\n createRoutableExtension,\n createRouteRef,\n discoveryApiRef,\n identityApiRef,\n} from '@backstage/core-plugin-api';\nimport {\n scmAuthApiRef,\n scmIntegrationsApiRef,\n} from '@backstage/integration-react';\nimport { catalogApiRef } from '@backstage/plugin-catalog-react';\nimport { catalogImportApiRef, CatalogImportClient } from './api';\n\nexport const rootRouteRef = createRouteRef({\n id: 'catalog-import',\n});\n\nexport const catalogImportPlugin = createPlugin({\n id: 'catalog-import',\n apis: [\n createApiFactory({\n api: catalogImportApiRef,\n deps: {\n discoveryApi: discoveryApiRef,\n scmAuthApi: scmAuthApiRef,\n identityApi: identityApiRef,\n scmIntegrationsApi: scmIntegrationsApiRef,\n catalogApi: catalogApiRef,\n configApi: configApiRef,\n },\n factory: ({\n discoveryApi,\n scmAuthApi,\n identityApi,\n scmIntegrationsApi,\n catalogApi,\n configApi,\n }) =>\n new CatalogImportClient({\n discoveryApi,\n scmAuthApi,\n scmIntegrationsApi,\n identityApi,\n catalogApi,\n configApi,\n }),\n }),\n ],\n routes: {\n importPage: rootRouteRef,\n },\n});\n\nexport const CatalogImportPage = catalogImportPlugin.provide(\n createRoutableExtension({\n name: 'CatalogImportPage',\n component: () => import('./components/ImportPage').then(m => m.ImportPage),\n mountPoint: rootRouteRef,\n }),\n);\n","/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { useApi, configApiRef } from '@backstage/core-plugin-api';\nimport { getCatalogFilename } from '../components/helpers';\n\nexport function useCatalogFilename(): string {\n const config = useApi(configApiRef);\n\n return getCatalogFilename(config);\n}\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { InfoCard } from '@backstage/core-components';\nimport { configApiRef, useApi } from '@backstage/core-plugin-api';\nimport { Chip, Typography } from '@material-ui/core';\nimport React from 'react';\nimport { catalogImportApiRef } from '../../api';\nimport { useCatalogFilename } from '../../hooks';\n\ntype Props = {\n exampleLocationUrl?: string;\n exampleRepositoryUrl?: string;\n};\n\nexport const ImportInfoCard = ({\n exampleLocationUrl = 'https://github.com/backstage/backstage/blob/master/catalog-info.yaml',\n exampleRepositoryUrl = 'https://github.com/backstage/backstage',\n}: Props) => {\n const configApi = useApi(configApiRef);\n const appTitle = configApi.getOptional('app.title') || 'Backstage';\n const catalogImportApi = useApi(catalogImportApiRef);\n\n const integrations = configApi.getConfig('integrations');\n const hasGithubIntegration = integrations.has('github');\n\n const catalogFilename = useCatalogFilename();\n\n return (\n <InfoCard\n title=\"Register an existing component\"\n deepLink={{\n title: 'Learn more about the Software Catalog',\n link: 'https://backstage.io/docs/features/software-catalog/software-catalog-overview',\n }}\n >\n <Typography variant=\"body2\" paragraph>\n Enter the URL to your source code repository to add it to {appTitle}.\n </Typography>\n <Typography variant=\"h6\">Link to an existing entity file</Typography>\n <Typography variant=\"subtitle2\" color=\"textSecondary\" paragraph>\n Example: <code>{exampleLocationUrl}</code>\n </Typography>\n <Typography variant=\"body2\" paragraph>\n The wizard analyzes the file, previews the entities, and adds them to\n the {appTitle} catalog.\n </Typography>\n {hasGithubIntegration && (\n <>\n <Typography variant=\"h6\">\n Link to a repository{' '}\n <Chip label=\"GitHub only\" variant=\"outlined\" size=\"small\" />\n </Typography>\n <Typography variant=\"subtitle2\" color=\"textSecondary\" paragraph>\n Example: <code>{exampleRepositoryUrl}</code>\n </Typography>\n <Typography variant=\"body2\" paragraph>\n The wizard discovers all <code>{catalogFilename}</code> files in the\n repository, previews the entities, and adds them to the {appTitle}{' '}\n catalog.\n </Typography>\n {catalogImportApi.preparePullRequest && (\n <Typography variant=\"body2\" paragraph>\n If no entities are found, the wizard will prepare a Pull Request\n that adds an example <code>{catalogFilename}</code> and prepares\n the {appTitle} catalog to load all entities as soon as the Pull\n Request is merged.\n </Typography>\n )}\n </>\n )}\n </InfoCard>\n );\n};\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Entity, EntityName } from '@backstage/catalog-model';\nimport { useReducer } from 'react';\nimport { AnalyzeResult } from '../api';\n\n// the configuration of the stepper\nexport type ImportFlows =\n | 'unknown'\n | 'single-location'\n | 'multiple-locations'\n | 'no-location';\n\n// the available states of the stepper\ntype ImportStateTypes = 'analyze' | 'prepare' | 'review' | 'finish';\n\n// result of the prepare state\nexport type PrepareResult =\n | {\n type: 'locations';\n locations: Array<{\n exists?: boolean;\n target: string;\n entities: EntityName[];\n }>;\n }\n | {\n type: 'repository';\n url: string;\n integrationType: string;\n pullRequest: {\n url: string;\n };\n locations: Array<{\n target: string;\n entities: EntityName[];\n }>;\n };\n\n// result of the review result\nexport type ReviewResult =\n | {\n type: 'locations';\n locations: Array<{\n target: string;\n entities: Entity[];\n }>;\n refreshed: Array<{ target: string }>;\n }\n | {\n type: 'repository';\n url: string;\n integrationType: string;\n pullRequest: {\n url: string;\n };\n locations: Array<{\n target: string;\n entities: Entity[];\n }>;\n };\n\n// function type for the 'analysis' -> 'prepare'/'review' transition\ntype onAnalysisFn = (\n flow: ImportFlows,\n url: string,\n result: AnalyzeResult,\n opts?: { prepareResult?: PrepareResult },\n) => void;\n\n// function type for the 'prepare' -> 'review' transition\ntype onPrepareFn = (\n result: PrepareResult,\n opts?: { notRepeatable?: boolean },\n) => void;\n\n// function type for the 'review' -> 'finish' transition\ntype onReviewFn = (result: ReviewResult) => void;\n\n// the type interfaces that are available in each state. every state provides\n// already known information and means to go to the next, or the previous step.\ntype State =\n | {\n activeState: 'analyze';\n onAnalysis: onAnalysisFn;\n }\n | {\n activeState: 'prepare';\n analyzeResult: AnalyzeResult;\n prepareResult?: PrepareResult;\n onPrepare: onPrepareFn;\n }\n | {\n activeState: 'review';\n analyzeResult: AnalyzeResult;\n prepareResult: PrepareResult;\n onReview: onReviewFn;\n }\n | {\n activeState: 'finish';\n analyzeResult: AnalyzeResult;\n prepareResult: PrepareResult;\n reviewResult: ReviewResult;\n };\n\nexport type ImportState = State & {\n activeFlow: ImportFlows;\n activeStepNumber: number;\n analysisUrl?: string;\n\n onGoBack?: () => void;\n onReset: () => void;\n};\n\ntype ReducerActions =\n | { type: 'onAnalysis'; args: Parameters<onAnalysisFn> }\n | { type: 'onPrepare'; args: Parameters<onPrepareFn> }\n | { type: 'onReview'; args: Parameters<onReviewFn> }\n | { type: 'onGoBack' }\n | { type: 'onReset'; initialUrl?: string };\n\ntype ReducerState = {\n activeFlow: ImportFlows;\n activeState: ImportStateTypes;\n analysisUrl?: string;\n analyzeResult?: AnalyzeResult;\n prepareResult?: PrepareResult;\n reviewResult?: ReviewResult;\n\n previousStates: ImportStateTypes[];\n};\n\nfunction init(initialUrl?: string): ReducerState {\n return {\n activeFlow: 'unknown',\n activeState: 'analyze',\n analysisUrl: initialUrl,\n previousStates: [],\n };\n}\n\nfunction reducer(state: ReducerState, action: ReducerActions): ReducerState {\n switch (action.type) {\n case 'onAnalysis': {\n if (state.activeState !== 'analyze') {\n return state;\n }\n\n const { activeState, previousStates } = state;\n const [activeFlow, analysisUrl, analyzeResult, opts] = action.args;\n\n return {\n ...state,\n analysisUrl,\n activeFlow,\n analyzeResult,\n prepareResult: opts?.prepareResult,\n\n activeState: opts?.prepareResult === undefined ? 'prepare' : 'review',\n previousStates: previousStates.concat(activeState),\n };\n }\n\n case 'onPrepare': {\n if (state.activeState !== 'prepare') {\n return state;\n }\n\n const { activeState, previousStates } = state;\n const [prepareResult, opts] = action.args;\n\n return {\n ...state,\n prepareResult,\n\n activeState: 'review',\n previousStates: opts?.notRepeatable\n ? []\n : previousStates.concat(activeState),\n };\n }\n\n case 'onReview': {\n if (state.activeState !== 'review') {\n return state;\n }\n\n const { activeState, previousStates } = state;\n const [reviewResult] = action.args;\n\n return {\n ...state,\n reviewResult,\n\n activeState: 'finish',\n previousStates: previousStates.concat(activeState),\n };\n }\n\n case 'onGoBack': {\n const { activeState, previousStates } = state;\n\n return {\n ...state,\n\n activeState:\n previousStates.length > 0\n ? previousStates[previousStates.length - 1]\n : activeState,\n previousStates: previousStates.slice(0, previousStates.length - 1),\n };\n }\n\n case 'onReset':\n return {\n ...init(action.initialUrl),\n\n // we keep the old prepareResult since the form is animated and an\n // undefined value might crash the last step.\n prepareResult: state.prepareResult,\n };\n\n default:\n throw new Error();\n }\n}\n\n/**\n * A hook that manages the state machine of the form. It handles different flows\n * which each can implement up to four states:\n * 1. analyze\n * 2. preview (skippable from analyze->review)\n * 3. review\n * 4. finish\n *\n * @param options - options\n */\nexport const useImportState = (options?: {\n initialUrl?: string;\n}): ImportState => {\n const [state, dispatch] = useReducer(reducer, options?.initialUrl, init);\n\n const { activeFlow, activeState, analysisUrl, previousStates } = state;\n\n return {\n activeFlow,\n activeState,\n activeStepNumber: ['analyze', 'prepare', 'review', 'finish'].indexOf(\n activeState,\n ),\n analysisUrl: analysisUrl,\n\n analyzeResult: state.analyzeResult!,\n prepareResult: state.prepareResult!,\n reviewResult: state.reviewResult!,\n\n onAnalysis: (flow, url, result, opts) =>\n dispatch({\n type: 'onAnalysis',\n args: [flow, url, result, opts],\n }),\n\n onPrepare: (result, opts) =>\n dispatch({\n type: 'onPrepare',\n args: [result, opts],\n }),\n\n onReview: result => dispatch({ type: 'onReview', args: [result] }),\n\n onGoBack:\n previousStates.length > 0\n ? () => dispatch({ type: 'onGoBack' })\n : undefined,\n\n onReset: () =>\n dispatch({ type: 'onReset', initialUrl: options?.initialUrl }),\n };\n};\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Button, CircularProgress } from '@material-ui/core';\nimport { makeStyles } from '@material-ui/core/styles';\nimport React, { ComponentProps } from 'react';\n\nconst useStyles = makeStyles(theme => ({\n wrapper: {\n marginTop: theme.spacing(1),\n marginRight: theme.spacing(1),\n position: 'relative',\n },\n buttonProgress: {\n position: 'absolute',\n top: '50%',\n left: '50%',\n marginTop: -12,\n marginLeft: -12,\n },\n button: {\n marginTop: theme.spacing(1),\n marginRight: theme.spacing(1),\n },\n}));\n\nexport const NextButton = (\n props: ComponentProps<typeof Button> & { loading?: boolean },\n) => {\n const { loading, ...buttonProps } = props;\n const classes = useStyles();\n\n return (\n <div className={classes.wrapper}>\n <Button\n color=\"primary\"\n variant=\"contained\"\n {...buttonProps}\n disabled={props.disabled || props.loading}\n />\n {props.loading && (\n <CircularProgress size=\"1.5rem\" className={classes.buttonProgress} />\n )}\n {props.loading}\n </div>\n );\n};\n\nexport const BackButton = (props: ComponentProps<typeof Button>) => {\n const classes = useStyles();\n\n return (\n <Button variant=\"outlined\" className={classes.button} {...props}>\n {props.children || 'Back'}\n </Button>\n );\n};\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Entity, EntityName } from '@backstage/catalog-model';\nimport { useApp } from '@backstage/core-plugin-api';\nimport {\n EntityRefLink,\n formatEntityRefTitle,\n} from '@backstage/plugin-catalog-react';\nimport {\n Collapse,\n IconButton,\n List,\n ListItem,\n ListItemIcon,\n ListItemSecondaryAction,\n ListItemText,\n} from '@material-ui/core';\nimport { makeStyles } from '@material-ui/core/styles';\nimport ExpandLessIcon from '@material-ui/icons/ExpandLess';\nimport ExpandMoreIcon from '@material-ui/icons/ExpandMore';\nimport WorkIcon from '@material-ui/icons/Work';\nimport React, { useState } from 'react';\n\nconst useStyles = makeStyles(theme => ({\n nested: {\n paddingLeft: theme.spacing(4),\n },\n}));\n\nfunction sortEntities(entities: Array<EntityName | Entity>) {\n return entities.sort((a, b) =>\n formatEntityRefTitle(a).localeCompare(formatEntityRefTitle(b)),\n );\n}\n\ntype Props = {\n locations: Array<{ target: string; entities: (Entity | EntityName)[] }>;\n locationListItemIcon: (target: string) => React.ReactElement;\n collapsed?: boolean;\n firstListItem?: React.ReactElement;\n onItemClick?: (target: string) => void;\n withLinks?: boolean;\n};\n\nexport const EntityListComponent = ({\n locations,\n collapsed = false,\n locationListItemIcon,\n onItemClick,\n firstListItem,\n withLinks = false,\n}: Props) => {\n const app = useApp();\n const classes = useStyles();\n\n const [expandedUrls, setExpandedUrls] = useState<string[]>([]);\n\n const handleClick = (url: string) => {\n setExpandedUrls(urls =>\n urls.includes(url) ? urls.filter(u => u !== url) : urls.concat(url),\n );\n };\n\n return (\n <List>\n {firstListItem}\n {locations.map(r => (\n <React.Fragment key={r.target}>\n <ListItem\n dense\n button={Boolean(onItemClick) as any}\n onClick={() => onItemClick?.call(this, r.target)}\n >\n <ListItemIcon>{locationListItemIcon(r.target)}</ListItemIcon>\n\n <ListItemText\n primary={r.target}\n secondary={`Entities: ${r.entities.length}`}\n />\n\n {collapsed && (\n <ListItemSecondaryAction>\n <IconButton edge=\"end\" onClick={() => handleClick(r.target)}>\n {expandedUrls.includes(r.target) ? (\n <ExpandLessIcon />\n ) : (\n <ExpandMoreIcon />\n )}\n </IconButton>\n </ListItemSecondaryAction>\n )}\n </ListItem>\n\n <Collapse\n in={!collapsed || expandedUrls.includes(r.target)}\n timeout=\"auto\"\n unmountOnExit\n >\n <List component=\"div\" disablePadding dense>\n {sortEntities(r.entities).map(entity => {\n const Icon =\n app.getSystemIcon(\n `kind:${entity.kind.toLocaleLowerCase('en-US')}`,\n ) ?? WorkIcon;\n return (\n <ListItem\n key={formatEntityRefTitle(entity)}\n className={classes.nested}\n {...(withLinks\n ? {\n component: EntityRefLink,\n entityRef: entity,\n button: withLinks as any,\n }\n : {})}\n >\n <ListItemIcon>\n <Icon />\n </ListItemIcon>\n <ListItemText primary={formatEntityRefTitle(entity)} />\n </ListItem>\n );\n })}\n </List>\n </Collapse>\n </React.Fragment>\n ))}\n </List>\n );\n};\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Grid, Typography } from '@material-ui/core';\nimport LocationOnIcon from '@material-ui/icons/LocationOn';\nimport React from 'react';\nimport { BackButton } from '../Buttons';\nimport { EntityListComponent } from '../EntityListComponent';\nimport { PrepareResult } from '../useImportState';\nimport { Link } from '@backstage/core-components';\nimport partition from 'lodash/partition';\n\ntype Props = {\n prepareResult: PrepareResult;\n onReset: () => void;\n};\n\nexport const StepFinishImportLocation = ({ prepareResult, onReset }: Props) => {\n const continueButton = (\n <Grid container spacing={0}>\n <BackButton onClick={onReset}>Register another</BackButton>\n </Grid>\n );\n\n if (prepareResult.type === 'repository') {\n return (\n <>\n <Typography paragraph>\n The following Pull Request has been opened:{' '}\n <Link\n to={prepareResult.pullRequest.url}\n target=\"_blank\"\n rel=\"noreferrer\"\n >\n {prepareResult.pullRequest.url}\n </Link>\n </Typography>\n\n <Typography paragraph>\n Your entities will be imported as soon as the Pull Request is merged.\n </Typography>\n\n {continueButton}\n </>\n );\n }\n\n const [existingLocations, newLocations] = partition(\n prepareResult.locations,\n l => l.exists,\n );\n\n return (\n <>\n {newLocations.length > 0 && (\n <>\n <Typography>\n The following entities have been added to the catalog:\n </Typography>\n\n <EntityListComponent\n locations={newLocations}\n locationListItemIcon={() => <LocationOnIcon />}\n withLinks\n />\n </>\n )}\n {existingLocations.length > 0 && (\n <>\n <Typography>\n A refresh was triggered for the following locations:\n </Typography>\n\n <EntityListComponent\n locations={existingLocations}\n locationListItemIcon={() => <LocationOnIcon />}\n withLinks\n />\n </>\n )}\n {continueButton}\n </>\n );\n};\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { errorApiRef, useApi } from '@backstage/core-plugin-api';\nimport { FormHelperText, Grid, TextField } from '@material-ui/core';\nimport React, { useCallback, useState } from 'react';\nimport { useForm } from 'react-hook-form';\nimport { AnalyzeResult, catalogImportApiRef } from '../../api';\nimport { NextButton } from '../Buttons';\nimport { asInputRef } from '../helpers';\nimport { ImportFlows, PrepareResult } from '../useImportState';\n\ntype FormData = {\n url: string;\n};\n\ntype Props = {\n onAnalysis: (\n flow: ImportFlows,\n url: string,\n result: AnalyzeResult,\n opts?: { prepareResult?: PrepareResult },\n ) => void;\n disablePullRequest?: boolean;\n analysisUrl?: string;\n exampleLocationUrl?: string;\n};\n\n/**\n * A form that lets the user input a url and analyze it for existing locations or potential entities.\n *\n * @param onAnalysis - is called when the analysis was successful\n * @param analysisUrl - a url that can be used as a default value\n * @param disablePullRequest - if true, repositories without entities will abort the wizard\n */\nexport const StepInitAnalyzeUrl = ({\n onAnalysis,\n analysisUrl = '',\n disablePullRequest = false,\n exampleLocationUrl = 'https://github.com/backstage/backstage/blob/master/catalog-info.yaml',\n}: Props) => {\n const errorApi = useApi(errorApiRef);\n const catalogImportApi = useApi(catalogImportApiRef);\n\n const {\n register,\n handleSubmit,\n formState: { errors },\n watch,\n } = useForm<FormData>({\n mode: 'onTouched',\n defaultValues: {\n url: analysisUrl,\n },\n });\n\n const [submitted, setSubmitted] = useState(false);\n const [error, setError] = useState<string | undefined>(undefined);\n\n const handleResult = useCallback(\n async ({ url }: FormData) => {\n setSubmitted(true);\n\n try {\n const analysisResult = await catalogImportApi.analyzeUrl(url);\n\n switch (analysisResult.type) {\n case 'repository':\n if (\n !disablePullRequest &&\n analysisResult.generatedEntities.length > 0\n ) {\n onAnalysis('no-location', url, analysisResult);\n } else {\n setError(\"Couldn't generate entities for your repository\");\n setSubmitted(false);\n }\n break;\n\n case 'locations': {\n if (analysisResult.locations.length === 1) {\n onAnalysis('single-location', url, analysisResult, {\n prepareResult: analysisResult,\n });\n } else if (analysisResult.locations.length > 1) {\n onAnalysis('multiple-locations', url, analysisResult);\n } else {\n setError('There are no entities at this location');\n setSubmitted(false);\n }\n break;\n }\n\n default: {\n const err = `Received unknown analysis result of type ${\n (analysisResult as any).type\n }. Please contact the support team.`;\n setError(err);\n setSubmitted(false);\n\n errorApi.post(new Error(err));\n break;\n }\n }\n } catch (e: any) {\n setError(e?.data?.error?.message ?? e.message);\n setSubmitted(false);\n }\n },\n [catalogImportApi, disablePullRequest, errorApi, onAnalysis],\n );\n\n return (\n <form onSubmit={handleSubmit(handleResult)}>\n <TextField\n {...asInputRef(\n register('url', {\n required: true,\n validate: {\n httpsValidator: (value: any) =>\n (typeof value === 'string' &&\n value.match(/^http[s]?:\\/\\//) !== null) ||\n 'Must start with http:// or https://.',\n },\n }),\n )}\n fullWidth\n id=\"url\"\n label=\"Repository URL\"\n placeholder={exampleLocationUrl}\n helperText=\"Enter the full path to your entity file to start tracking your component\"\n margin=\"normal\"\n variant=\"outlined\"\n error={Boolean(errors.url)}\n required\n />\n\n {errors.url && (\n <FormHelperText error>{errors.url.message}</FormHelperText>\n )}\n\n {error && <FormHelperText error>{error}</FormHelperText>}\n\n <Grid container spacing={0}>\n <NextButton\n disabled={Boolean(errors.url) || !watch('url')}\n loading={submitted}\n type=\"submit\"\n >\n Analyze\n </NextButton>\n </Grid>\n </form>\n );\n};\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { CircularProgress, TextField } from '@material-ui/core';\nimport { TextFieldProps } from '@material-ui/core/TextField/TextField';\nimport { Autocomplete } from '@material-ui/lab';\nimport React from 'react';\nimport { Controller, FieldErrors } from 'react-hook-form';\n\ntype Props<TFieldValue extends string> = {\n name: TFieldValue;\n options: string[];\n required?: boolean;\n\n errors?: FieldErrors;\n rules?: React.ComponentProps<typeof Controller>['rules'];\n\n loading?: boolean;\n loadingText?: string;\n\n helperText?: React.ReactNode;\n errorHelperText?: string;\n\n textFieldProps?: Omit<TextFieldProps, 'required' | 'fullWidth'>;\n};\n\nexport const AutocompleteTextField = <TFieldValue extends string>({\n name,\n options,\n required,\n errors,\n rules,\n loading = false,\n loadingText,\n helperText,\n errorHelperText,\n textFieldProps = {},\n}: Props<TFieldValue>) => {\n return (\n <Controller\n name={name}\n rules={rules}\n render={({ field: { onChange } }) => (\n <Autocomplete\n loading={loading}\n loadingText={loadingText}\n options={options || []}\n autoSelect\n freeSolo\n onChange={(_event: React.ChangeEvent<{}>, value: string | null) =>\n onChange(value)\n }\n renderInput={params => (\n <TextField\n {...params}\n helperText={(errors?.[name] && errorHelperText) || helperText}\n error={Boolean(errors?.[name])}\n margin=\"normal\"\n variant=\"outlined\"\n required={required}\n InputProps={{\n ...params.InputProps,\n endAdornment: (\n <React.Fragment>\n {loading ? (\n <CircularProgress color=\"inherit\" size=\"1em\" />\n ) : null}\n {params.InputProps.endAdornment}\n </React.Fragment>\n ),\n }}\n {...textFieldProps}\n />\n )}\n />\n )}\n />\n );\n};\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport React from 'react';\nimport {\n FormProvider,\n SubmitHandler,\n UnpackNestedValue,\n useForm,\n UseFormProps,\n UseFormReturn,\n} from 'react-hook-form';\n\ntype Props<TFieldValues extends Record<string, any>> = Pick<\n UseFormProps<TFieldValues>,\n 'defaultValues'\n> & {\n onSubmit: SubmitHandler<TFieldValues>;\n\n render: (\n props: Pick<\n UseFormReturn<TFieldValues>,\n 'formState' | 'register' | 'control' | 'setValue'\n > & {\n values: UnpackNestedValue<TFieldValues>;\n },\n ) => React.ReactNode;\n};\n\n/**\n * A form wrapper that creates a form that is used to prepare a pull request. It\n * hosts the form logic.\n *\n * @param defaultValues - the default values of the form\n * @param onSubmit - a callback that is executed when the form is submitted\n * (initiated by a button of type=\"submit\")\n * @param render - render the form elements\n */\nexport const PreparePullRequestForm = <\n TFieldValues extends Record<string, any>,\n>({\n defaultValues,\n onSubmit,\n render,\n}: Props<TFieldValues>) => {\n const methods = useForm<TFieldValues>({ mode: 'onTouched', defaultValues });\n const { handleSubmit, watch, control, register, formState, setValue } =\n methods;\n\n return (\n <FormProvider {...methods}>\n <form onSubmit={handleSubmit(onSubmit)}>\n {render({ values: watch(), formState, register, control, setValue })}\n </form>\n </FormProvider>\n );\n};\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Entity } from '@backstage/catalog-model';\nimport { Card, CardContent, CardHeader } from '@material-ui/core';\nimport React from 'react';\nimport YAML from 'yaml';\nimport { CodeSnippet } from '@backstage/core-components';\nimport { trimEnd } from 'lodash';\nimport { useCatalogFilename } from '../../hooks';\n\ntype Props = {\n repositoryUrl: string;\n entities: Entity[];\n classes?: { card?: string; cardContent?: string };\n};\n\nexport const PreviewCatalogInfoComponent = ({\n repositoryUrl,\n entities,\n classes,\n}: Props) => {\n const catalogFilename = useCatalogFilename();\n\n return (\n <Card variant=\"outlined\" className={classes?.card}>\n <CardHeader\n title={\n <code>{`${trimEnd(repositoryUrl, '/')}/${catalogFilename}`}</code>\n }\n />\n\n <CardContent className={classes?.cardContent}>\n <CodeSnippet\n text={entities\n .map(e => YAML.stringify(e))\n .join('---\\n')\n .trim()}\n language=\"yaml\"\n />\n </CardContent>\n </Card>\n );\n};\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Card, CardContent, CardHeader } from '@material-ui/core';\nimport React from 'react';\nimport { MarkdownContent } from '@backstage/core-components';\n\ntype Props = {\n title: string;\n description: string;\n classes?: { card?: string; cardContent?: string };\n};\n\nexport const PreviewPullRequestComponent = ({\n title,\n description,\n classes,\n}: Props) => {\n return (\n <Card variant=\"outlined\" className={classes?.card}>\n <CardHeader title={title} subheader=\"Create a new Pull Request\" />\n <CardContent className={classes?.cardContent}>\n <MarkdownContent content={description} />\n </CardContent>\n </Card>\n );\n};\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Entity } from '@backstage/catalog-model';\nimport { errorApiRef, useApi } from '@backstage/core-plugin-api';\nimport { assertError } from '@backstage/errors';\nimport {\n catalogApiRef,\n formatEntityRefTitle,\n} from '@backstage/plugin-catalog-react';\nimport { Box, FormHelperText, Grid, Typography } from '@material-ui/core';\nimport { makeStyles } from '@material-ui/core/styles';\nimport React, { useCallback, useEffect, useState } from 'react';\nimport { UnpackNestedValue, UseFormReturn } from 'react-hook-form';\nimport useAsync from 'react-use/lib/useAsync';\nimport YAML from 'yaml';\nimport { AnalyzeResult, catalogImportApiRef } from '../../api';\nimport { useCatalogFilename } from '../../hooks';\nimport { PartialEntity } from '../../types';\nimport { BackButton, NextButton } from '../Buttons';\nimport { PrepareResult } from '../useImportState';\nimport { PreparePullRequestForm } from './PreparePullRequestForm';\nimport { PreviewCatalogInfoComponent } from './PreviewCatalogInfoComponent';\nimport { PreviewPullRequestComponent } from './PreviewPullRequestComponent';\n\nconst useStyles = makeStyles(theme => ({\n previewCard: {\n marginTop: theme.spacing(1),\n },\n previewCardContent: {\n paddingTop: 0,\n },\n}));\n\ntype FormData = {\n title: string;\n body: string;\n componentName: string;\n owner: string;\n useCodeowners: boolean;\n};\n\ntype Props = {\n analyzeResult: Extract<AnalyzeResult, { type: 'repository' }>;\n onPrepare: (\n result: PrepareResult,\n opts?: { notRepeatable?: boolean },\n ) => void;\n onGoBack?: () => void;\n\n renderFormFields: (\n props: Pick<\n UseFormReturn<FormData>,\n 'register' | 'setValue' | 'formState'\n > & {\n values: UnpackNestedValue<FormData>;\n groups: string[];\n groupsLoading: boolean;\n },\n ) => React.ReactNode;\n};\n\nexport function generateEntities(\n entities: PartialEntity[],\n componentName: string,\n owner?: string,\n): Entity[] {\n return entities.map(e => ({\n ...e,\n apiVersion: e.apiVersion!,\n kind: e.kind!,\n metadata: {\n ...e.metadata,\n name: componentName,\n },\n spec: {\n ...e.spec,\n ...(owner ? { owner } : {}),\n },\n }));\n}\n\nexport const StepPrepareCreatePullRequest = ({\n analyzeResult,\n onPrepare,\n onGoBack,\n renderFormFields,\n}: Props) => {\n const classes = useStyles();\n const catalogApi = useApi(catalogApiRef);\n const catalogImportApi = useApi(catalogImportApiRef);\n const errorApi = useApi(errorApiRef);\n\n const [submitted, setSubmitted] = useState(false);\n const [error, setError] = useState<string>();\n\n const catalogFilename = useCatalogFilename();\n\n const {\n loading: prDefaultsLoading,\n value: prDefaults,\n error: prDefaultsError,\n } = useAsync(\n () => catalogImportApi.preparePullRequest!(),\n [catalogImportApi.preparePullRequest],\n );\n\n useEffect(() => {\n if (prDefaultsError) {\n errorApi.post(prDefaultsError);\n }\n }, [prDefaultsError, errorApi]);\n\n const { loading: groupsLoading, value: groups } = useAsync(async () => {\n const groupEntities = await catalogApi.getEntities({\n filter: { kind: 'group' },\n });\n\n return groupEntities.items\n .map(e => formatEntityRefTitle(e, { defaultKind: 'group' }))\n .sort();\n });\n\n const handleResult = useCallback(\n async (data: FormData) => {\n setSubmitted(true);\n\n try {\n const pr = await catalogImportApi.submitPullRequest({\n repositoryUrl: analyzeResult.url,\n title: data.title,\n body: data.body,\n fileContent: generateEntities(\n analyzeResult.generatedEntities,\n data.componentName,\n data.owner,\n )\n .map(e => YAML.stringify(e))\n .join('---\\n'),\n });\n\n onPrepare(\n {\n type: 'repository',\n url: analyzeResult.url,\n integrationType: analyzeResult.integrationType,\n pullRequest: {\n url: pr.link,\n },\n locations: [\n {\n target: pr.location,\n entities: generateEntities(\n analyzeResult.generatedEntities,\n data.componentName,\n data.owner,\n ).map(e => ({\n kind: e.kind,\n namespace: e.metadata.namespace!,\n name: e.metadata.name,\n })),\n },\n ],\n },\n { notRepeatable: true },\n );\n } catch (e) {\n assertError(e);\n setError(e.message);\n setSubmitted(false);\n }\n },\n [\n analyzeResult.generatedEntities,\n analyzeResult.integrationType,\n analyzeResult.url,\n catalogImportApi,\n onPrepare,\n ],\n );\n\n return (\n <>\n <Typography>\n You entered a link to a {analyzeResult.integrationType} repository but a{' '}\n <code>{catalogFilename}</code> could not be found. Use this form to open\n a Pull Request that creates one.\n </Typography>\n\n {!prDefaultsLoading && (\n <PreparePullRequestForm<FormData>\n onSubmit={handleResult}\n defaultValues={{\n title: prDefaults?.title ?? '',\n body: prDefaults?.body ?? '',\n owner:\n (analyzeResult.generatedEntities[0]?.spec?.owner as string) || '',\n componentName:\n analyzeResult.generatedEntities[0]?.metadata?.name || '',\n useCodeowners: false,\n }}\n render={({ values, formState, register, setValue }) => (\n <>\n {renderFormFields({\n values,\n formState,\n register,\n setValue,\n groups: groups ?? [],\n groupsLoading,\n })}\n\n <Box marginTop={2}>\n <Typography variant=\"h6\">Preview Pull Request</Typography>\n </Box>\n\n <PreviewPullRequestComponent\n title={values.title}\n description={values.body}\n classes={{\n card: classes.previewCard,\n cardContent: classes.previewCardContent,\n }}\n />\n\n <Box marginTop={2} marginBottom={1}>\n <Typography variant=\"h6\">Preview Entities</Typography>\n </Box>\n\n <PreviewCatalogInfoComponent\n entities={generateEntities(\n analyzeResult.generatedEntities,\n values.componentName,\n values.owner,\n )}\n repositoryUrl={analyzeResult.url}\n classes={{\n card: classes.previewCard,\n cardContent: classes.previewCardContent,\n }}\n />\n\n {error && <FormHelperText error>{error}</FormHelperText>}\n\n <Grid container spacing={0}>\n {onGoBack && (\n <BackButton onClick={onGoBack} disabled={submitted} />\n )}\n <NextButton\n type=\"submit\"\n disabled={Boolean(\n formState.errors.title ||\n formState.errors.body ||\n formState.errors.owner,\n )}\n loading={submitted}\n >\n Create PR\n </NextButton>\n </Grid>\n </>\n )}\n />\n )}\n </>\n );\n};\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n Checkbox,\n Grid,\n ListItem,\n ListItemIcon,\n ListItemText,\n Typography,\n} from '@material-ui/core';\nimport LocationOnIcon from '@material-ui/icons/LocationOn';\nimport React, { useCallback, useState } from 'react';\nimport { AnalyzeResult } from '../../api';\nimport { BackButton, NextButton } from '../Buttons';\nimport { EntityListComponent } from '../EntityListComponent';\nimport { PrepareResult } from '../useImportState';\nimport partition from 'lodash/partition';\n\ntype Props = {\n analyzeResult: Extract<AnalyzeResult, { type: 'locations' }>;\n prepareResult?: PrepareResult;\n onPrepare: (result: PrepareResult) => void;\n onGoBack?: () => void;\n};\n\n/**\n * A form that lets a user select one of a list of locations to import\n *\n * @param analyzeResult - the result of the analysis\n * @param prepareResult - the selectected locations from a previous step\n * @param onPrepare - called after the selection\n * @param onGoBack - called to go back to the previous step\n */\nexport const StepPrepareSelectLocations = ({\n analyzeResult,\n prepareResult,\n onPrepare,\n onGoBack,\n}: Props) => {\n const [selectedUrls, setSelectedUrls] = useState<string[]>(\n prepareResult?.locations.map(l => l.target) || [],\n );\n\n const [existingLocations, locations] = partition(\n analyzeResult?.locations,\n l => l.exists,\n );\n\n const handleResult = useCallback(async () => {\n onPrepare({\n type: 'locations',\n locations: locations.filter((l: any) => selectedUrls.includes(l.target)),\n });\n }, [locations, onPrepare, selectedUrls]);\n\n const onItemClick = (url: string) => {\n setSelectedUrls(urls =>\n urls.includes(url) ? urls.filter(u => u !== url) : urls.concat(url),\n );\n };\n\n const onSelectAll = () => {\n setSelectedUrls(urls =>\n urls.length < locations.length ? locations.map(l => l.target) : [],\n );\n };\n\n return (\n <>\n {locations.length > 0 && (\n <>\n <Typography>\n Select one or more locations that are present in your git\n repository:\n </Typography>\n <EntityListComponent\n firstListItem={\n <ListItem dense button onClick={onSelectAll}>\n <ListItemIcon>\n <Checkbox\n edge=\"start\"\n checked={selectedUrls.length === locations.length}\n indeterminate={\n selectedUrls.length > 0 &&\n selectedUrls.length < locations.length\n }\n tabIndex={-1}\n disableRipple\n />\n </ListItemIcon>\n <ListItemText primary=\"Select All\" />\n </ListItem>\n }\n onItemClick={onItemClick}\n locations={locations}\n locationListItemIcon={target => (\n <Checkbox\n edge=\"start\"\n checked={selectedUrls.includes(target)}\n tabIndex={-1}\n disableRipple\n />\n )}\n collapsed\n />\n </>\n )}\n\n {existingLocations.length > 0 && (\n <>\n <Typography>These locations already exist in the catalog:</Typography>\n <EntityListComponent\n locations={existingLocations}\n locationListItemIcon={() => <LocationOnIcon />}\n withLinks\n collapsed\n />\n </>\n )}\n\n <Grid container spacing={0}>\n {onGoBack && <BackButton onClick={onGoBack} />}\n <NextButton disabled={selectedUrls.length === 0} onClick={handleResult}>\n Review\n </NextButton>\n </Grid>\n </>\n );\n};\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { catalogApiRef } from '@backstage/plugin-catalog-react';\nimport { FormHelperText, Grid, Typography } from '@material-ui/core';\nimport LocationOnIcon from '@material-ui/icons/LocationOn';\nimport React, { useCallback, useState } from 'react';\nimport { BackButton, NextButton } from '../Buttons';\nimport { EntityListComponent } from '../EntityListComponent';\nimport { PrepareResult, ReviewResult } from '../useImportState';\n\nimport { configApiRef, useApi } from '@backstage/core-plugin-api';\nimport { Link } from '@backstage/core-components';\nimport { stringifyEntityRef } from '@backstage/catalog-model';\nimport { assertError } from '@backstage/errors';\n\ntype Props = {\n prepareResult: PrepareResult;\n onReview: (result: ReviewResult) => void;\n onGoBack?: () => void;\n};\n\nexport const StepReviewLocation = ({\n prepareResult,\n onReview,\n onGoBack,\n}: Props) => {\n const catalogApi = useApi(catalogApiRef);\n const configApi = useApi(configApiRef);\n\n const appTitle = configApi.getOptional('app.title') || 'Backstage';\n\n const [submitted, setSubmitted] = useState(false);\n const [error, setError] = useState<string>();\n const exists =\n prepareResult.type === 'locations' &&\n prepareResult.locations.some(l => l.exists)\n ? true\n : false;\n const handleClick = useCallback(async () => {\n setSubmitted(true);\n try {\n let refreshed = new Array<{ target: string }>();\n if (prepareResult.type === 'locations') {\n refreshed = await Promise.all(\n prepareResult.locations\n .filter(l => l.exists)\n .map(async l => {\n const ref = stringifyEntityRef(l.entities[0] ?? l);\n await catalogApi.refreshEntity(ref);\n return { target: l.target };\n }),\n );\n }\n\n const locations = await Promise.all(\n prepareResult.locations\n .filter((l: unknown) => !(l as { exists?: boolean }).exists)\n .map(async l => {\n const result = await catalogApi.addLocation({\n type: 'url',\n target: l.target,\n presence:\n prepareResult.type === 'repository' ? 'optional' : 'required',\n });\n return {\n target: result.location.target,\n entities: result.entities,\n };\n }),\n );\n\n onReview({\n ...prepareResult,\n ...{ refreshed },\n locations,\n });\n } catch (e) {\n assertError(e);\n // TODO: this error should be handled differently. We add it as 'optional' and\n // it is not uncommon that a PR has not been merged yet.\n if (\n prepareResult.type === 'repository' &&\n e.message.startsWith(\n 'Location was added but has no entities specified yet',\n )\n ) {\n onReview({\n ...prepareResult,\n locations: prepareResult.locations.map(l => ({\n target: l.target,\n entities: [],\n })),\n });\n } else {\n setError(e.message);\n setSubmitted(false);\n }\n }\n }, [prepareResult, onReview, catalogApi]);\n\n return (\n <>\n {prepareResult.type === 'repository' && (\n <>\n <Typography paragraph>\n The following Pull Request has been opened:{' '}\n <Link\n to={prepareResult.pullRequest.url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n {prepareResult.pullRequest.url}\n </Link>\n </Typography>\n\n <Typography paragraph>\n You can already import the location and {appTitle} will fetch the\n entities as soon as the Pull Request is merged.\n </Typography>\n </>\n )}\n\n <Typography>\n {exists\n ? 'The following locations already exist in the catalog:'\n : 'The following entities will be added to the catalog:'}\n </Typography>\n\n <EntityListComponent\n locations={prepareResult.locations}\n locationListItemIcon={() => <LocationOnIcon />}\n />\n\n {error && <FormHelperText error>{error}</FormHelperText>}\n\n <Grid container spacing={0}>\n {onGoBack && <BackButton onClick={onGoBack} disabled={submitted} />}\n <NextButton\n disabled={submitted}\n loading={submitted}\n onClick={() => handleClick()}\n >\n {exists ? 'Refresh' : 'Import'}\n </NextButton>\n </Grid>\n </>\n );\n};\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n Box,\n Checkbox,\n FormControlLabel,\n FormHelperText,\n StepLabel,\n TextField,\n Typography,\n} from '@material-ui/core';\nimport React from 'react';\nimport { BackButton } from '../Buttons';\nimport { asInputRef } from '../helpers';\nimport { StepFinishImportLocation } from '../StepFinishImportLocation';\nimport { StepInitAnalyzeUrl } from '../StepInitAnalyzeUrl';\nimport {\n AutocompleteTextField,\n StepPrepareCreatePullRequest,\n} from '../StepPrepareCreatePullRequest';\nimport { StepPrepareSelectLocations } from '../StepPrepareSelectLocations';\nimport { StepReviewLocation } from '../StepReviewLocation';\nimport { StepperApis } from '../types';\nimport { ImportFlows, ImportState } from '../useImportState';\n\nexport type StepConfiguration = {\n stepLabel: React.ReactElement;\n content: React.ReactElement;\n};\n\nexport type StepperProvider = {\n analyze: (\n s: Extract<ImportState, { activeState: 'analyze' }>,\n opts: { apis: StepperApis },\n ) => StepConfiguration;\n prepare: (\n s: Extract<ImportState, { activeState: 'prepare' }>,\n opts: { apis: StepperApis },\n ) => StepConfiguration;\n review: (\n s: Extract<ImportState, { activeState: 'review' }>,\n opts: { apis: StepperApis },\n ) => StepConfiguration;\n finish: (\n s: Extract<ImportState, { activeState: 'finish' }>,\n opts: { apis: StepperApis },\n ) => StepConfiguration;\n};\n\n/**\n * The default stepper generation function.\n *\n * Override this function to customize the import flow. Each flow should at\n * least override the prepare operation.\n *\n * @param flow - the name of the active flow\n * @param defaults - the default steps\n */\nexport function defaultGenerateStepper(\n flow: ImportFlows,\n defaults: StepperProvider,\n): StepperProvider {\n switch (flow) {\n // the prepare step is skipped but the label of the step is updated\n case 'single-location':\n return {\n ...defaults,\n prepare: () => ({\n stepLabel: (\n <StepLabel\n optional={\n <Typography variant=\"caption\">\n Discovered Locations: 1\n </Typography>\n }\n >\n Select Locations\n </StepLabel>\n ),\n content: <></>,\n }),\n };\n\n // let the user select one or more of the discovered locations in the prepare step\n case 'multiple-locations':\n return {\n ...defaults,\n prepare: (state, opts) => {\n if (state.analyzeResult.type !== 'locations') {\n return defaults.prepare(state, opts);\n }\n\n return {\n stepLabel: (\n <StepLabel\n optional={\n <Typography variant=\"caption\">\n Discovered Locations: {state.analyzeResult.locations.length}\n </Typography>\n }\n >\n Select Locations\n </StepLabel>\n ),\n content: (\n <StepPrepareSelectLocations\n analyzeResult={state.analyzeResult}\n prepareResult={state.prepareResult}\n onPrepare={state.onPrepare}\n onGoBack={state.onGoBack}\n />\n ),\n };\n },\n };\n\n case 'no-location':\n return {\n ...defaults,\n prepare: (state, opts) => {\n if (state.analyzeResult.type !== 'repository') {\n return defaults.prepare(state, opts);\n }\n\n return {\n stepLabel: <StepLabel>Create Pull Request</StepLabel>,\n content: (\n <StepPrepareCreatePullRequest\n analyzeResult={state.analyzeResult}\n onPrepare={state.onPrepare}\n onGoBack={state.onGoBack}\n renderFormFields={({\n values,\n setValue,\n formState,\n groupsLoading,\n groups,\n register,\n }) => (\n <>\n <Box marginTop={2}>\n <Typography variant=\"h6\">Pull Request Details</Typography>\n </Box>\n\n <TextField\n {...asInputRef(\n register('title', {\n required: true,\n }),\n )}\n label=\"Pull Request Title\"\n placeholder=\"Add Backstage catalog entity descriptor files\"\n margin=\"normal\"\n variant=\"outlined\"\n fullWidth\n error={Boolean(formState.errors.title)}\n required\n />\n\n <TextField\n {...asInputRef(\n register('body', {\n required: true,\n }),\n )}\n label=\"Pull Request Body\"\n placeholder=\"A describing text with Markdown support\"\n margin=\"normal\"\n variant=\"outlined\"\n fullWidth\n error={Boolean(formState.errors.body)}\n multiline\n required\n />\n\n <Box marginTop={2}>\n <Typography variant=\"h6\">Entity Configuration</Typography>\n </Box>\n\n <TextField\n {...asInputRef(\n register('componentName', { required: true }),\n )}\n label=\"Name of the created component\"\n placeholder=\"my-component\"\n margin=\"normal\"\n variant=\"outlined\"\n fullWidth\n error={Boolean(formState.errors.componentName)}\n required\n />\n\n {!values.useCodeowners && (\n <AutocompleteTextField\n name=\"owner\"\n errors={formState.errors}\n options={groups || []}\n loading={groupsLoading}\n loadingText=\"Loading groups…\"\n helperText=\"Select an owner from the list or enter a reference to a Group or a User\"\n errorHelperText=\"required value\"\n textFieldProps={{\n label: 'Entity Owner',\n placeholder: 'my-group',\n }}\n rules={{ required: true }}\n required\n />\n )}\n\n <FormControlLabel\n control={\n <Checkbox\n {...asInputRef(register('useCodeowners'))}\n onChange={(_, value) => {\n if (value) {\n setValue('owner', '');\n }\n }}\n />\n }\n label={\n <>\n Use <em>CODEOWNERS</em> file as Entity Owner\n </>\n }\n />\n <FormHelperText>\n WARNING: This may fail if no CODEOWNERS file is found at\n the target location.\n </FormHelperText>\n </>\n )}\n />\n ),\n };\n },\n };\n\n default:\n return defaults;\n }\n}\n\nexport const defaultStepper: StepperProvider = {\n analyze: (state, { apis }) => ({\n stepLabel: <StepLabel>Select URL</StepLabel>,\n content: (\n <StepInitAnalyzeUrl\n key=\"analyze\"\n analysisUrl={state.analysisUrl}\n onAnalysis={state.onAnalysis}\n disablePullRequest={!apis.catalogImportApi.preparePullRequest}\n />\n ),\n }),\n\n prepare: state => ({\n stepLabel: (\n <StepLabel optional={<Typography variant=\"caption\">Optional</Typography>}>\n Import Actions\n </StepLabel>\n ),\n content: <BackButton onClick={state.onGoBack} />,\n }),\n\n review: state => ({\n stepLabel: <StepLabel>Review</StepLabel>,\n content: (\n <StepReviewLocation\n prepareResult={state.prepareResult}\n onReview={state.onReview}\n onGoBack={state.onGoBack}\n />\n ),\n }),\n\n finish: state => ({\n stepLabel: <StepLabel>Finish</StepLabel>,\n content: (\n <StepFinishImportLocation\n prepareResult={state.prepareResult}\n onReset={state.onReset}\n />\n ),\n }),\n};\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { InfoCard, InfoCardVariants } from '@backstage/core-components';\nimport { useApi } from '@backstage/core-plugin-api';\nimport { Step, StepContent, Stepper } from '@material-ui/core';\nimport { makeStyles } from '@material-ui/core/styles';\nimport React, { useMemo } from 'react';\nimport { catalogImportApiRef } from '../../api';\nimport { ImportFlows, ImportState, useImportState } from '../useImportState';\nimport {\n defaultGenerateStepper,\n defaultStepper,\n StepConfiguration,\n StepperProvider,\n} from './defaults';\n\nconst useStyles = makeStyles(() => ({\n stepperRoot: {\n padding: 0,\n },\n}));\n\ntype Props = {\n initialUrl?: string;\n generateStepper?: (\n flow: ImportFlows,\n defaults: StepperProvider,\n ) => StepperProvider;\n variant?: InfoCardVariants;\n};\n\nexport const ImportStepper = ({\n initialUrl,\n generateStepper = defaultGenerateStepper,\n variant,\n}: Props) => {\n const catalogImportApi = useApi(catalogImportApiRef);\n const classes = useStyles();\n const state = useImportState({ initialUrl });\n\n const states = useMemo<StepperProvider>(\n () => generateStepper(state.activeFlow, defaultStepper),\n [generateStepper, state.activeFlow],\n );\n\n const render = (step: StepConfiguration) => {\n return (\n <Step>\n {step.stepLabel}\n <StepContent>{step.content}</StepContent>\n </Step>\n );\n };\n\n return (\n <InfoCard variant={variant}>\n <Stepper\n classes={{ root: classes.stepperRoot }}\n activeStep={state.activeStepNumber}\n orientation=\"vertical\"\n >\n {render(\n states.analyze(\n state as Extract<ImportState, { activeState: 'analyze' }>,\n { apis: { catalogImportApi } },\n ),\n )}\n {render(\n states.prepare(\n state as Extract<ImportState, { activeState: 'prepare' }>,\n { apis: { catalogImportApi } },\n ),\n )}\n {render(\n states.review(\n state as Extract<ImportState, { activeState: 'review' }>,\n { apis: { catalogImportApi } },\n ),\n )}\n {render(\n states.finish(\n state as Extract<ImportState, { activeState: 'finish' }>,\n { apis: { catalogImportApi } },\n ),\n )}\n </Stepper>\n </InfoCard>\n );\n};\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n Content,\n ContentHeader,\n Header,\n Page,\n SupportButton,\n} from '@backstage/core-components';\nimport { configApiRef, useApi } from '@backstage/core-plugin-api';\nimport { Grid } from '@material-ui/core';\nimport React from 'react';\nimport { ImportInfoCard } from '../ImportInfoCard';\nimport { ImportStepper } from '../ImportStepper';\n\nexport const DefaultImportPage = () => {\n const configApi = useApi(configApiRef);\n const appTitle = configApi.getOptional('app.title') || 'Backstage';\n\n return (\n <Page themeId=\"home\">\n <Header title=\"Register an existing component\" />\n <Content>\n <ContentHeader title={`Start tracking your component in ${appTitle}`}>\n <SupportButton>\n Start tracking your component in {appTitle} by adding it to the\n software catalog.\n </SupportButton>\n </ContentHeader>\n\n <Grid container spacing={2} direction=\"row-reverse\">\n <Grid item xs={12} md={4} lg={6} xl={8}>\n <ImportInfoCard />\n </Grid>\n\n <Grid item xs={12} md={8} lg={6} xl={4}>\n <ImportStepper />\n </Grid>\n </Grid>\n </Content>\n </Page>\n );\n};\n"],"names":["useStyles","this"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@backstage/plugin-catalog-import",
3
3
  "description": "A Backstage plugin the helps you import entities into your catalog",
4
- "version": "0.7.10",
4
+ "version": "0.8.0-next.0",
5
5
  "main": "dist/index.esm.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "license": "Apache-2.0",
@@ -33,12 +33,13 @@
33
33
  "dependencies": {
34
34
  "@backstage/catalog-client": "^0.5.5",
35
35
  "@backstage/catalog-model": "^0.9.10",
36
- "@backstage/core-components": "^0.8.5",
36
+ "@backstage/config": "^0.1.13",
37
+ "@backstage/core-components": "^0.8.7-next.0",
37
38
  "@backstage/core-plugin-api": "^0.6.0",
38
39
  "@backstage/errors": "^0.2.0",
39
40
  "@backstage/integration": "^0.7.2",
40
- "@backstage/integration-react": "^0.1.19",
41
- "@backstage/plugin-catalog-react": "^0.6.12",
41
+ "@backstage/integration-react": "^0.1.20-next.0",
42
+ "@backstage/plugin-catalog-react": "^0.6.13-next.0",
42
43
  "@material-ui/core": "^4.12.2",
43
44
  "@material-ui/icons": "^4.9.1",
44
45
  "@material-ui/lab": "4.0.0-alpha.57",
@@ -56,9 +57,9 @@
56
57
  "react": "^16.13.1 || ^17.0.0"
57
58
  },
58
59
  "devDependencies": {
59
- "@backstage/cli": "^0.12.0",
60
- "@backstage/core-app-api": "^0.5.0",
61
- "@backstage/dev-utils": "^0.2.18",
60
+ "@backstage/cli": "^0.13.1-next.0",
61
+ "@backstage/core-app-api": "^0.5.1",
62
+ "@backstage/dev-utils": "^0.2.20-next.0",
62
63
  "@backstage/test-utils": "^0.2.3",
63
64
  "@testing-library/jest-dom": "^5.10.1",
64
65
  "@testing-library/react": "^11.2.5",
@@ -69,7 +70,9 @@
69
70
  "msw": "^0.35.0"
70
71
  },
71
72
  "files": [
72
- "dist"
73
+ "dist",
74
+ "config.d.ts"
73
75
  ],
74
- "gitHead": "600d6e3c854bbfb12a0078ca6f726d1c0d1fea0b"
76
+ "configSchema": "config.d.ts",
77
+ "gitHead": "a28838ac5c80c7332caa6ca0569d2ec85151784f"
75
78
  }