diff --git a/.changeset/tame-ads-exercise.md b/.changeset/tame-ads-exercise.md new file mode 100644 index 0000000000..a2e11393a6 --- /dev/null +++ b/.changeset/tame-ads-exercise.md @@ -0,0 +1,34 @@ +--- +'@backstage/plugin-catalog-import': minor +--- + +Make filename, branch name and examples URLs used in catalog import customizable. + +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. + +Filename (default: `catalog-info.yaml`) and branch name (default: `backstage-integration`) used in pull requests can be configured in `app-config.yaml` as follows: + +```yaml +// app-config.yaml + +catalog: + import: + entityFilename: anvil.yaml + pullRequestBranchName: anvil-integration +``` + +Following React components have also been updated to accept optional props for providing example entity and repository paths: + +```tsx + +``` + +```tsx + +``` diff --git a/app-config.yaml b/app-config.yaml index b43a229ae9..b84b5de2f2 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -194,6 +194,9 @@ integrations: secretAccessKey: ${AWS_SECRET_ACCESS_KEY} catalog: + import: + entityFilename: catalog-info.yaml + pullRequestBranchName: backstage-integration rules: - allow: - Component diff --git a/packages/create-app/templates/default-app/app-config.yaml.hbs b/packages/create-app/templates/default-app/app-config.yaml.hbs index c804b4b561..46e2ecbb05 100644 --- a/packages/create-app/templates/default-app/app-config.yaml.hbs +++ b/packages/create-app/templates/default-app/app-config.yaml.hbs @@ -80,6 +80,9 @@ scaffolder: # see https://backstage.io/docs/features/software-templates/configuration for software template options catalog: + import: + entityFilename: catalog-info.yaml + pullRequestBranchName: backstage-integration rules: - allow: [Component, System, API, Group, User, Resource, Location] locations: diff --git a/plugins/catalog-import/README.md b/plugins/catalog-import/README.md index bad48a8486..86f3aecba9 100644 --- a/plugins/catalog-import/README.md +++ b/plugins/catalog-import/README.md @@ -35,39 +35,73 @@ import { CatalogImportPage } from '@backstage/plugin-catalog-import'; ## Customizations -### Disable the creation of Pull Requests +### Custom layout -The pull request feature can be disabled by options that are passed to the `CatalogImportPage`: +A custom layout can be passed to the import page, as it's already +supported by the search page. If no custom layout is passed, the default layout +is used. + +```typescript +}> + +
+ + + + Start tracking your component in Backstage by adding it to the + software catalog. + + + + + + Hello World + + + + + + + + + +``` + +Previously it was possible to disable and customize the automatic pull request +feature by passing options to `` (`pullRequest.disable` and +`pullRequest.preparePullRequest`). This functionality is moved to the +`CatalogImportApi` which now provides an optional `preparePullRequest()` +function. The function can either be overridden to generate a different content +for the pull request, or removed to disable this feature. + +### Entity filename and branch name + +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: + +```yaml +// app-config.yaml + +catalog: + import: + entityFilename: anvil.yaml + pullRequestBranchName: anvil-integration +``` + +### Entity examples + +Following React components accept optional props for providing custom example entity and repository paths: ```tsx -// packages/app/src/App.tsx - -} + ``` -### Customize the title and body of the Pull Request - -The pull request form is filled with a default title and body. -This can be configured by options that are passed to the `CatalogImportPage`: - ```tsx -// packages/app/src/App.tsx - - ({ - title: 'My title', - body: 'My **markdown** body', - }), - }} - /> - } + ``` diff --git a/plugins/catalog-import/api-report.md b/plugins/catalog-import/api-report.md index a1ad76f323..a85777c563 100644 --- a/plugins/catalog-import/api-report.md +++ b/plugins/catalog-import/api-report.md @@ -60,7 +60,7 @@ export const AutocompleteTextField: ({ helperText, errorHelperText, textFieldProps, -}: Props_4) => JSX.Element; +}: Props_5) => JSX.Element; // Warning: (ae-missing-release-tag) "CatalogImportApi" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -171,10 +171,14 @@ export const EntityListComponent: ({ withLinks, }: Props) => JSX.Element; +// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "ImportInfoCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const ImportInfoCard: () => JSX.Element; +export const ImportInfoCard: ({ + exampleLocationUrl, + exampleRepositoryUrl, +}: Props_2) => JSX.Element; // Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "ImportStepper" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -184,7 +188,7 @@ export const ImportStepper: ({ initialUrl, generateStepper, variant, -}: Props_2) => JSX.Element; +}: Props_3) => JSX.Element; // Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "PreparePullRequestForm" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -196,7 +200,7 @@ export const PreparePullRequestForm: < defaultValues, onSubmit, render, -}: Props_5) => JSX.Element; +}: Props_6) => JSX.Element; // Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "PreviewCatalogInfoComponent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -206,7 +210,7 @@ export const PreviewCatalogInfoComponent: ({ repositoryUrl, entities, classes, -}: Props_6) => JSX.Element; +}: Props_7) => JSX.Element; // Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "PreviewPullRequestComponent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -216,7 +220,7 @@ export const PreviewPullRequestComponent: ({ title, description, classes, -}: Props_7) => JSX.Element; +}: Props_8) => JSX.Element; // Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "StepInitAnalyzeUrl" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -226,7 +230,8 @@ export const StepInitAnalyzeUrl: ({ onAnalysis, analysisUrl, disablePullRequest, -}: Props_3) => JSX.Element; + exampleLocationUrl, +}: Props_4) => JSX.Element; // Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "StepPrepareCreatePullRequest" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -237,7 +242,7 @@ export const StepPrepareCreatePullRequest: ({ onPrepare, onGoBack, renderFormFields, -}: Props_8) => JSX.Element; +}: Props_9) => JSX.Element; // Warnings were encountered during analysis: // diff --git a/plugins/catalog-import/config.d.ts b/plugins/catalog-import/config.d.ts new file mode 100644 index 0000000000..0cc49e418b --- /dev/null +++ b/plugins/catalog-import/config.d.ts @@ -0,0 +1,40 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface Config { + /** + * Configuration options for the catalog plugin. + */ + catalog?: { + /** + * List of import flow specific options and attributes + */ + import?: { + /** + * Catalog entity descriptor filename, defaults to "catalog-info.yaml" + * @visibility frontend + */ + entityFilename?: string; + /** + * A branch name used in pull request when registering existing component via UI + * Valid git refname required, see: https://git-scm.com/docs/git-check-ref-format + * Defaults to "backstage-integration" + * @visibility frontend + */ + pullRequestBranchName?: string; + }; + }; +} diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index ff9fcb24c2..2508543541 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -34,6 +34,7 @@ "@backstage/catalog-client": "^0.5.5", "@backstage/catalog-model": "^0.9.10", "@backstage/core-components": "^0.8.6", + "@backstage/config": "^0.1.13", "@backstage/core-plugin-api": "^0.6.0", "@backstage/errors": "^0.2.0", "@backstage/integration": "^0.7.2", @@ -69,6 +70,8 @@ "msw": "^0.35.0" }, "files": [ - "dist" - ] + "dist", + "config.d.ts" + ], + "configSchema": "config.d.ts" } diff --git a/plugins/catalog-import/src/api/CatalogImportClient.test.ts b/plugins/catalog-import/src/api/CatalogImportClient.test.ts index 202a7aba85..fa63825af1 100644 --- a/plugins/catalog-import/src/api/CatalogImportClient.test.ts +++ b/plugins/catalog-import/src/api/CatalogImportClient.test.ts @@ -396,6 +396,84 @@ describe('CatalogImportClient', () => { ], }); }); + + it('should find location with custom catalog filename', async () => { + const repositoryUrl = 'https://github.com/acme-corp/our-awesome-api'; + const entityFilename = 'anvil.yaml'; + + catalogImportClient = new CatalogImportClient({ + discoveryApi, + scmAuthApi, + scmIntegrationsApi, + identityApi, + catalogApi, + configApi: new ConfigReader({ + catalog: { + import: { + entityFilename, + }, + }, + }), + }); + + (new Octokit().search.code as any as jest.Mock).mockImplementationOnce( + async params => ({ + data: { + total_count: 1, + items: [{ path: params.q.split('+filename:').slice(-1)[0] }], + }, + }), + ); + + catalogApi.addLocation.mockImplementation(async ({ type, target }) => ({ + location: { + id: 'id-0', + type: type ?? 'url', + target, + }, + entities: [ + { + apiVersion: '1', + kind: 'Location', + metadata: { + name: 'my-entity', + namespace: 'my-namespace', + }, + }, + { + apiVersion: '1', + kind: 'Component', + metadata: { + name: 'my-entity', + namespace: 'my-namespace', + }, + }, + ], + })); + + await expect( + catalogImportClient.analyzeUrl(repositoryUrl), + ).resolves.toEqual({ + locations: [ + { + entities: [ + { + kind: 'Location', + namespace: 'my-namespace', + name: 'my-entity', + }, + { + kind: 'Component', + namespace: 'my-namespace', + name: 'my-entity', + }, + ], + target: `${repositoryUrl}/blob/main/${entityFilename}`, + }, + ], + type: 'locations', + }); + }); }); describe('submitPullRequest', () => { @@ -443,6 +521,67 @@ describe('CatalogImportClient', () => { base: 'main', }); }); + + it('should create GitHub pull request with custom filename and branch name', async () => { + const entityFilename = 'anvil.yaml'; + const pullRequestBranchName = 'anvil-integration'; + + catalogImportClient = new CatalogImportClient({ + discoveryApi, + scmAuthApi, + scmIntegrationsApi, + identityApi, + catalogApi, + configApi: new ConfigReader({ + catalog: { + import: { + entityFilename, + pullRequestBranchName, + }, + }, + }), + }); + + await expect( + catalogImportClient.submitPullRequest({ + repositoryUrl: 'https://github.com/acme-corp/our-awesome-api', + fileContent: '', + title: `Add ${entityFilename} config file`, + body: `Add ${entityFilename} config file`, + }), + ).resolves.toEqual( + expect.objectContaining({ + link: 'http://pull/request/0', + location: `https://github.com/acme-corp/our-awesome-api/blob/main/${entityFilename}`, + }), + ); + + expect( + (new Octokit().git.createRef as any as jest.Mock).mock.calls[0][0], + ).toEqual( + expect.objectContaining({ + ref: `refs/heads/${pullRequestBranchName}`, + }), + ); + + expect( + (new Octokit().repos.createOrUpdateFileContents as any as jest.Mock) + .mock.calls[0][0], + ).toEqual( + expect.objectContaining({ + path: entityFilename, + branch: pullRequestBranchName, + }), + ); + + expect( + (new Octokit().pulls.create as any as jest.Mock).mock.calls[0][0], + ).toEqual( + expect.objectContaining({ + head: pullRequestBranchName, + }), + ); + }); }); describe('preparePullRequest', () => { @@ -452,5 +591,34 @@ describe('CatalogImportClient', () => { body: expect.any(String), }); }); + + test('should prepare pull request details with custom filename', async () => { + const entityFilename = 'anvil.yaml'; + const pullRequestBranchName = 'anvil-integration'; + + catalogImportClient = new CatalogImportClient({ + discoveryApi, + scmAuthApi, + scmIntegrationsApi, + identityApi, + catalogApi, + configApi: new ConfigReader({ + catalog: { + import: { + entityFilename, + pullRequestBranchName, + }, + }, + app: { + baseUrl: 'https://demo.backstage.io/', + }, + }), + }); + + await expect(catalogImportClient.preparePullRequest()).resolves.toEqual({ + title: `Add ${entityFilename} config file`, + body: expect.any(String), + }); + }); }); }); diff --git a/plugins/catalog-import/src/api/CatalogImportClient.ts b/plugins/catalog-import/src/api/CatalogImportClient.ts index bc59657cad..60745923b4 100644 --- a/plugins/catalog-import/src/api/CatalogImportClient.ts +++ b/plugins/catalog-import/src/api/CatalogImportClient.ts @@ -32,6 +32,7 @@ import { PartialEntity } from '../types'; import { AnalyzeResult, CatalogImportApi } from './CatalogImportApi'; import { getGithubIntegrationConfig } from './GitHub'; import { trimEnd } from 'lodash'; +import { getBranchName, getCatalogFilename } from '../components/helpers'; export class CatalogImportClient implements CatalogImportApi { private readonly discoveryApi: DiscoveryApi; @@ -87,13 +88,15 @@ export class CatalogImportClient implements CatalogImportApi { const ghConfig = getGithubIntegrationConfig(this.scmIntegrationsApi, url); if (!ghConfig) { const other = this.scmIntegrationsApi.byUrl(url); + const catalogFilename = getCatalogFilename(this.configApi); + if (other) { 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?`, + `The ${other.title} integration only supports full URLs to ${catalogFilename} files. Did you try to pass in the URL of a directory instead?`, ); } 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.', + `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.`, ); } @@ -127,9 +130,10 @@ export class CatalogImportClient implements CatalogImportApi { const appTitle = this.configApi.getOptionalString('app.title') ?? 'Backstage'; const appBaseUrl = this.configApi.getString('app.baseUrl'); + const catalogFilename = getCatalogFilename(this.configApi); return { - title: 'Add catalog-info.yaml config file', + title: `Add ${catalogFilename} config file`, 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}).\n\nAfter this pull request is merged, \ @@ -221,8 +225,8 @@ the component will become available.\n\nFor more information, read an \ auth: token, baseUrl: githubIntegrationConfig.apiBaseUrl, }); - const catalogFileName = 'catalog-info.yaml'; - const query = `repo:${owner}/${repo}+filename:${catalogFileName}`; + const catalogFilename = getCatalogFilename(this.configApi); + const query = `repo:${owner}/${repo}+filename:${catalogFilename}`; const searchResult = await octo.search.code({ q: query }).catch(e => { throw new Error( @@ -294,8 +298,8 @@ the component will become available.\n\nFor more information, read an \ baseUrl: githubIntegrationConfig.apiBaseUrl, }); - const branchName = 'backstage-integration'; - const fileName = 'catalog-info.yaml'; + const branchName = getBranchName(this.configApi); + const fileName = getCatalogFilename(this.configApi); const repoData = await octo.repos .get({ diff --git a/plugins/catalog-import/src/components/ImportInfoCard/ImportInfoCard.tsx b/plugins/catalog-import/src/components/ImportInfoCard/ImportInfoCard.tsx index 2fab223f20..3e66e137eb 100644 --- a/plugins/catalog-import/src/components/ImportInfoCard/ImportInfoCard.tsx +++ b/plugins/catalog-import/src/components/ImportInfoCard/ImportInfoCard.tsx @@ -19,8 +19,17 @@ import { configApiRef, useApi } from '@backstage/core-plugin-api'; import { Chip, Typography } from '@material-ui/core'; import React from 'react'; import { catalogImportApiRef } from '../../api'; +import { useCatalogFilename } from '../../hooks'; -export const ImportInfoCard = () => { +type Props = { + exampleLocationUrl?: string; + exampleRepositoryUrl?: string; +}; + +export const ImportInfoCard = ({ + exampleLocationUrl = 'https://github.com/backstage/backstage/blob/master/catalog-info.yaml', + exampleRepositoryUrl = 'https://github.com/backstage/backstage', +}: Props) => { const configApi = useApi(configApiRef); const appTitle = configApi.getOptional('app.title') || 'Backstage'; const catalogImportApi = useApi(catalogImportApiRef); @@ -28,6 +37,8 @@ export const ImportInfoCard = () => { const integrations = configApi.getConfig('integrations'); const hasGithubIntegration = integrations.has('github'); + const catalogFilename = useCatalogFilename(); + return ( { Link to an existing entity file - Example:{' '} - - https://github.com/backstage/backstage/blob/master/catalog-info.yaml - + Example: {exampleLocationUrl} The wizard analyzes the file, previews the entities, and adds them to @@ -57,17 +65,17 @@ export const ImportInfoCard = () => { - Example: https://github.com/backstage/backstage + Example: {exampleRepositoryUrl} - The wizard discovers all catalog-info.yaml files in the + The wizard discovers all {catalogFilename} files in the repository, previews the entities, and adds them to the {appTitle}{' '} catalog. {catalogImportApi.preparePullRequest && ( If no entities are found, the wizard will prepare a Pull Request - that adds an example catalog-info.yaml and prepares + that adds an example {catalogFilename} and prepares the {appTitle} catalog to load all entities as soon as the Pull Request is merged. diff --git a/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.tsx b/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.tsx index 63f3c10fe3..83377d6b2f 100644 --- a/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.tsx +++ b/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.tsx @@ -36,6 +36,7 @@ type Props = { ) => void; disablePullRequest?: boolean; analysisUrl?: string; + exampleLocationUrl?: string; }; /** @@ -49,6 +50,7 @@ export const StepInitAnalyzeUrl = ({ onAnalysis, analysisUrl = '', disablePullRequest = false, + exampleLocationUrl = 'https://github.com/backstage/backstage/blob/master/catalog-info.yaml', }: Props) => { const errorApi = useApi(errorApiRef); const catalogImportApi = useApi(catalogImportApiRef); @@ -138,7 +140,7 @@ export const StepInitAnalyzeUrl = ({ fullWidth id="url" label="Repository URL" - placeholder="https://github.com/backstage/backstage/blob/master/catalog-info.yaml" + placeholder={exampleLocationUrl} helperText="Enter the full path to your entity file to start tracking your component" margin="normal" variant="outlined" diff --git a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/PreviewCatalogInfoComponent.test.tsx b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/PreviewCatalogInfoComponent.test.tsx index fe5e76c968..0a6960cdc7 100644 --- a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/PreviewCatalogInfoComponent.test.tsx +++ b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/PreviewCatalogInfoComponent.test.tsx @@ -15,6 +15,8 @@ */ import { Entity } from '@backstage/catalog-model'; +import { configApiRef } from '@backstage/core-plugin-api'; +import { MockConfigApi, TestApiProvider } from '@backstage/test-utils'; import { makeStyles } from '@material-ui/core'; import { render, screen } from '@testing-library/react'; import { renderHook } from '@testing-library/react-hooks'; @@ -44,13 +46,18 @@ const entities: Entity[] = [ }, ]; +const mockConfigApi = new MockConfigApi({}); +const apis = [[configApiRef, mockConfigApi]] as const; + describe('', () => { it('renders without exploding', () => { render( - , + + + , ); const repositoryUrl = screen.getByText( @@ -67,11 +74,13 @@ describe('', () => { const { result } = renderHook(() => useStyles()); render( - , + + + , ); const repositoryUrl = screen.getByText( @@ -88,11 +97,13 @@ describe('', () => { const { result } = renderHook(() => useStyles()); render( - , + + + , ); const repositoryUrl = screen.getByText( @@ -104,4 +115,34 @@ describe('', () => { expect(kindText).toBeInTheDocument(); expect(kindText).not.toBeVisible(); }); + + it('renders with custom catalog filename', () => { + render( + + + , + ); + + const repositoryUrl = screen.getByText( + 'http://acme-corp/awesome-api/anvil.yaml', + ); + expect(repositoryUrl).toBeInTheDocument(); + expect(repositoryUrl).toBeVisible(); + }); }); diff --git a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/PreviewCatalogInfoComponent.tsx b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/PreviewCatalogInfoComponent.tsx index d0f29c00e2..0f1dc33d2e 100644 --- a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/PreviewCatalogInfoComponent.tsx +++ b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/PreviewCatalogInfoComponent.tsx @@ -20,6 +20,7 @@ import React from 'react'; import YAML from 'yaml'; import { CodeSnippet } from '@backstage/core-components'; import { trimEnd } from 'lodash'; +import { useCatalogFilename } from '../../hooks'; type Props = { repositoryUrl: string; @@ -32,11 +33,13 @@ export const PreviewCatalogInfoComponent = ({ entities, classes, }: Props) => { + const catalogFilename = useCatalogFilename(); + return ( {`${trimEnd(repositoryUrl, '/')}/catalog-info.yaml`} + {`${trimEnd(repositoryUrl, '/')}/${catalogFilename}`} } /> diff --git a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.test.tsx b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.test.tsx index 3b6f353155..8d26657bb3 100644 --- a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.test.tsx +++ b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.test.tsx @@ -14,9 +14,9 @@ * limitations under the License. */ -import { errorApiRef } from '@backstage/core-plugin-api'; +import { configApiRef, errorApiRef } from '@backstage/core-plugin-api'; import { catalogApiRef } from '@backstage/plugin-catalog-react'; -import { TestApiProvider } from '@backstage/test-utils'; +import { TestApiProvider, MockConfigApi } from '@backstage/test-utils'; import { TextField } from '@material-ui/core'; import { act, render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; @@ -53,12 +53,15 @@ describe('', () => { post: jest.fn(), }; + const configApi = new MockConfigApi({}); + const Wrapper = ({ children }: { children?: React.ReactNode }) => ( {children} diff --git a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx index 077d99c4ba..a79caaa0b9 100644 --- a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx +++ b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx @@ -28,6 +28,7 @@ import { UnpackNestedValue, UseFormReturn } from 'react-hook-form'; import useAsync from 'react-use/lib/useAsync'; import YAML from 'yaml'; import { AnalyzeResult, catalogImportApiRef } from '../../api'; +import { useCatalogFilename } from '../../hooks'; import { PartialEntity } from '../../types'; import { BackButton, NextButton } from '../Buttons'; import { PrepareResult } from '../useImportState'; @@ -106,6 +107,8 @@ export const StepPrepareCreatePullRequest = ({ const [submitted, setSubmitted] = useState(false); const [error, setError] = useState(); + const catalogFilename = useCatalogFilename(); + const { loading: prDefaultsLoading, value: prDefaults, @@ -193,7 +196,7 @@ export const StepPrepareCreatePullRequest = ({ <> You entered a link to a {analyzeResult.integrationType} repository but a{' '} - catalog-info.yaml could not be found. Use this form to open + {catalogFilename} could not be found. Use this form to open a Pull Request that creates one. diff --git a/plugins/catalog-import/src/components/helpers.ts b/plugins/catalog-import/src/components/helpers.ts index 4240186459..b134a8726a 100644 --- a/plugins/catalog-import/src/components/helpers.ts +++ b/plugins/catalog-import/src/components/helpers.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import type { Config } from '@backstage/config'; import { UseFormRegisterReturn } from 'react-hook-form'; /** @@ -31,3 +32,17 @@ export function asInputRef(renderResult: UseFormRegisterReturn) { ...rest, }; } + +export function getCatalogFilename(config: Config): string { + return ( + config.getOptionalString('catalog.import.entityFilename') ?? + 'catalog-info.yaml' + ); +} + +export function getBranchName(config: Config): string { + return ( + config.getOptionalString('catalog.import.pullRequestBranchName') ?? + 'backstage-integration' + ); +} diff --git a/plugins/catalog-import/src/hooks/index.ts b/plugins/catalog-import/src/hooks/index.ts new file mode 100644 index 0000000000..afdfb0cbe2 --- /dev/null +++ b/plugins/catalog-import/src/hooks/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { useCatalogFilename } from './useCatalogFilename'; diff --git a/plugins/catalog-import/src/hooks/useCatalogFilename.ts b/plugins/catalog-import/src/hooks/useCatalogFilename.ts new file mode 100644 index 0000000000..0e2d770d3e --- /dev/null +++ b/plugins/catalog-import/src/hooks/useCatalogFilename.ts @@ -0,0 +1,24 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useApi, configApiRef } from '@backstage/core-plugin-api'; +import { getCatalogFilename } from '../components/helpers'; + +export function useCatalogFilename(): string { + const config = useApi(configApiRef); + + return getCatalogFilename(config); +}