diff --git a/.changeset/dull-jobs-lick.md b/.changeset/dull-jobs-lick.md new file mode 100644 index 0000000000000..2174ff06f5d16 --- /dev/null +++ b/.changeset/dull-jobs-lick.md @@ -0,0 +1,30 @@ +--- +'@backstage/create-app': patch +--- + +Supply a `scmIntegrationsApiRef` from the new `@backstage/integration-react`. + +This is a new facility that plugins will start to use. You will have to add it to your local `packages/app` as described below. If this is not done, runtime errors will be seen in the frontend, on the form `No API factory available for dependency apiRef{integration.scmintegrations}`. + +In `packages/app/package.json`: + +```diff + "dependencies": { ++ "@backstage/integration-react": "^0.1.1", +``` + +In `packages/app/src/apis.ts`: + +```diff ++import { ++ scmIntegrationsApiRef, ++ ScmIntegrationsApi, ++} from '@backstage/integration-react'; + + export const apis: AnyApiFactory[] = [ ++ createApiFactory({ ++ api: scmIntegrationsApiRef, ++ deps: { configApi: configApiRef }, ++ factory: ({ configApi }) => ScmIntegrationsApi.fromConfig(configApi), ++ }), +``` diff --git a/.changeset/late-apples-doubt.md b/.changeset/late-apples-doubt.md new file mode 100644 index 0000000000000..f00ee76aadd1c --- /dev/null +++ b/.changeset/late-apples-doubt.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-catalog': patch +'@backstage/plugin-catalog-import': patch +'@backstage/plugin-scaffolder': patch +--- + +Use `scmIntegrationsApiRef` from the new `@backstage/integration-react`. diff --git a/cypress/src/integration/catalog.ts b/cypress/src/integration/catalog.ts index 31d39e7efdf5b..4016c154703b0 100644 --- a/cypress/src/integration/catalog.ts +++ b/cypress/src/integration/catalog.ts @@ -23,7 +23,7 @@ describe('Catalog', () => { cy.visit('/catalog'); - cy.contains('Owned (7)').should('be.visible'); + cy.contains('Owned (8)').should('be.visible'); }); }); }); diff --git a/packages/app/package.json b/packages/app/package.json index 21f0d0c3d06a3..e05c0a0b5da71 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -7,6 +7,7 @@ "@backstage/catalog-model": "^0.7.4", "@backstage/cli": "^0.6.4", "@backstage/core": "^0.7.1", + "@backstage/integration-react": "^0.1.1", "@backstage/plugin-api-docs": "^0.4.8", "@backstage/plugin-catalog": "^0.4.2", "@backstage/plugin-catalog-import": "^0.4.3", @@ -29,8 +30,8 @@ "@backstage/plugin-register-component": "^0.2.12", "@backstage/plugin-rollbar": "^0.3.3", "@backstage/plugin-scaffolder": "^0.7.1", - "@backstage/plugin-sentry": "^0.3.8", "@backstage/plugin-search": "^0.3.3", + "@backstage/plugin-sentry": "^0.3.8", "@backstage/plugin-tech-radar": "^0.3.7", "@backstage/plugin-techdocs": "^0.6.1", "@backstage/plugin-user-settings": "^0.2.7", diff --git a/packages/app/src/apis.ts b/packages/app/src/apis.ts index bd16cd74eff87..ccc576e727bbd 100644 --- a/packages/app/src/apis.ts +++ b/packages/app/src/apis.ts @@ -15,22 +15,32 @@ */ import { + AnyApiFactory, + configApiRef, + createApiFactory, errorApiRef, githubAuthApiRef, - createApiFactory, } from '@backstage/core'; - import { - graphQlBrowseApiRef, - GraphQLEndpoints, -} from '@backstage/plugin-graphiql'; - + ScmIntegrationsApi, + scmIntegrationsApiRef, +} from '@backstage/integration-react'; import { costInsightsApiRef, ExampleCostInsightsClient, } from '@backstage/plugin-cost-insights'; +import { + graphQlBrowseApiRef, + GraphQLEndpoints, +} from '@backstage/plugin-graphiql'; + +export const apis: AnyApiFactory[] = [ + createApiFactory({ + api: scmIntegrationsApiRef, + deps: { configApi: configApiRef }, + factory: ({ configApi }) => ScmIntegrationsApi.fromConfig(configApi), + }), -export const apis = [ createApiFactory({ api: graphQlBrowseApiRef, deps: { errorApi: errorApiRef, githubAuthApi: githubAuthApiRef }, diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 4a78ce87153bb..9a19d782d2ec0 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -69,6 +69,7 @@ "@backstage/plugin-techdocs": "*", "@backstage/plugin-techdocs-backend": "*", "@backstage/plugin-user-settings": "*", + "@backstage/integration-react": "*", "@backstage/test-utils": "*", "@backstage/theme": "*" }, diff --git a/packages/create-app/src/lib/versions.ts b/packages/create-app/src/lib/versions.ts index 32a87bb323a65..10f07bef02acd 100644 --- a/packages/create-app/src/lib/versions.ts +++ b/packages/create-app/src/lib/versions.ts @@ -37,6 +37,7 @@ import { version as cli } from '../../../cli/package.json'; import { version as config } from '../../../config/package.json'; import { version as core } from '../../../core/package.json'; import { version as errors } from '../../../errors/package.json'; +import { version as integrationReact } from '../../../integration-react/package.json'; import { version as testUtils } from '../../../test-utils/package.json'; import { version as theme } from '../../../theme/package.json'; @@ -69,6 +70,7 @@ export const packageVersions = { '@backstage/config': config, '@backstage/core': core, '@backstage/errors': errors, + '@backstage/integration-react': integrationReact, '@backstage/plugin-api-docs': pluginApiDocs, '@backstage/plugin-app-backend': pluginAppBackend, '@backstage/plugin-auth-backend': pluginAuthBackend, diff --git a/packages/create-app/templates/default-app/packages/app/package.json.hbs b/packages/create-app/templates/default-app/packages/app/package.json.hbs index 10701d65eba93..62690114bd8b3 100644 --- a/packages/create-app/templates/default-app/packages/app/package.json.hbs +++ b/packages/create-app/templates/default-app/packages/app/package.json.hbs @@ -19,6 +19,7 @@ "@backstage/plugin-github-actions": "^{{version '@backstage/plugin-github-actions'}}", "@backstage/plugin-user-settings": "^{{version '@backstage/plugin-user-settings'}}", "@backstage/plugin-search": "^{{version '@backstage/plugin-search'}}", + "@backstage/integration-react": "^{{version '@backstage/integration-react'}}", "@backstage/test-utils": "^{{version '@backstage/test-utils'}}", "@backstage/theme": "^{{version '@backstage/theme'}}", "history": "^5.0.0", diff --git a/packages/create-app/templates/default-app/packages/app/src/apis.ts b/packages/create-app/templates/default-app/packages/app/src/apis.ts index 24b22a83bccc9..d803416c3f770 100644 --- a/packages/create-app/templates/default-app/packages/app/src/apis.ts +++ b/packages/create-app/templates/default-app/packages/app/src/apis.ts @@ -1 +1,14 @@ -export const apis = []; +import { + AnyApiFactory, configApiRef, createApiFactory +} from '@backstage/core'; +import { + ScmIntegrationsApi, scmIntegrationsApiRef +} from '@backstage/integration-react'; + +export const apis: AnyApiFactory[] = [ + createApiFactory({ + api: scmIntegrationsApiRef, + deps: { configApi: configApiRef }, + factory: ({ configApi }) => ScmIntegrationsApi.fromConfig(configApi), + }), +]; diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index d511d015eac64..3845da7e9b22d 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -31,6 +31,7 @@ "dependencies": { "@backstage/core": "^0.7.0", "@backstage/catalog-model": "^0.7.3", + "@backstage/integration-react": "^0.1.1", "@backstage/plugin-catalog-react": "^0.1.1", "@backstage/test-utils": "^0.1.8", "@backstage/theme": "^0.2.3", diff --git a/packages/dev-utils/src/devApp/render.tsx b/packages/dev-utils/src/devApp/render.tsx index 0c3a1395a6eb6..1785fcbe386bf 100644 --- a/packages/dev-utils/src/devApp/render.tsx +++ b/packages/dev-utils/src/devApp/render.tsx @@ -19,6 +19,8 @@ import { AnyApiFactory, ApiFactory, attachComponentData, + configApiRef, + createApiFactory, createApp, createPlugin, createRouteRef, @@ -31,6 +33,10 @@ import { SidebarPage, SidebarSpacer, } from '@backstage/core'; +import { + ScmIntegrationsApi, + scmIntegrationsApiRef, +} from '@backstage/integration-react'; import { Box } from '@material-ui/core'; import BookmarkIcon from '@material-ui/icons/Bookmark'; import SentimentDissatisfiedIcon from '@material-ui/icons/SentimentDissatisfied'; @@ -136,6 +142,17 @@ class DevAppBuilder { const DummyPage = () => Page belonging to another plugin.; attachComponentData(DummyPage, 'core.mountPoint', dummyRouteRef); + const apis = [...this.apis]; + if (!apis.some(api => api.api.id === scmIntegrationsApiRef.id)) { + apis.push( + createApiFactory({ + api: scmIntegrationsApiRef, + deps: { configApi: configApiRef }, + factory: ({ configApi }) => ScmIntegrationsApi.fromConfig(configApi), + }), + ); + } + const app = createApp({ apis: this.apis, plugins: this.plugins, diff --git a/packages/integration-react/.eslintrc.js b/packages/integration-react/.eslintrc.js new file mode 100644 index 0000000000000..13573efa9c466 --- /dev/null +++ b/packages/integration-react/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: [require.resolve('@backstage/cli/config/eslint')], +}; diff --git a/packages/integration-react/README.md b/packages/integration-react/README.md new file mode 100644 index 0000000000000..99ecf30b749e7 --- /dev/null +++ b/packages/integration-react/README.md @@ -0,0 +1,5 @@ +# Integrations React-specific functionality + +Exposes a frontend API for interacting with configured integrations (as added +under the `integrations` root config key). Most of the actual code is in the +`@backstage/integration` package. diff --git a/packages/integration-react/dev/DevPage.tsx b/packages/integration-react/dev/DevPage.tsx new file mode 100644 index 0000000000000..f86cc35fc52b6 --- /dev/null +++ b/packages/integration-react/dev/DevPage.tsx @@ -0,0 +1,64 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { Content, useApi } from '@backstage/core'; +import { ScmIntegration, ScmIntegrationsGroup } from '@backstage/integration'; +import { Typography } from '@material-ui/core'; +import React from 'react'; +import { scmIntegrationsApiRef } from '../src/ScmIntegrationsApi'; + +const Integrations = (props: { + group: ScmIntegrationsGroup; +}) => { + const integrations = props.group.list(); + if (!integrations) { + return ( + + No integrations of this type + + ); + } + + return ( + + {JSON.stringify(integrations, undefined, 2)} + + ); +}; + +export const DevPage = () => { + const integrations = useApi(scmIntegrationsApiRef); + return ( + + + Azure + + + + Bitbucket + + + + GitHub + + + + GitLab + + + + ); +}; diff --git a/packages/integration-react/dev/index.tsx b/packages/integration-react/dev/index.tsx new file mode 100644 index 0000000000000..34cfee3fd810d --- /dev/null +++ b/packages/integration-react/dev/index.tsx @@ -0,0 +1,35 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { configApiRef, createApiFactory } from '@backstage/core'; +import { createDevApp } from '@backstage/dev-utils'; +import { ScmIntegrations } from '@backstage/integration'; +import React from 'react'; +import { scmIntegrationsApiRef } from '../src/ScmIntegrationsApi'; +import { DevPage } from './DevPage'; + +createDevApp() + .registerApi( + createApiFactory({ + api: scmIntegrationsApiRef, + deps: { configApi: configApiRef }, + factory: ({ configApi }) => ScmIntegrations.fromConfig(configApi), + }), + ) + .addPage({ + element: , + title: 'Root Page', + }) + .render(); diff --git a/packages/integration-react/package.json b/packages/integration-react/package.json new file mode 100644 index 0000000000000..d675be833ad5b --- /dev/null +++ b/packages/integration-react/package.json @@ -0,0 +1,49 @@ +{ + "name": "@backstage/integration-react", + "version": "0.1.1", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "private": true, + "publishConfig": { + "access": "public", + "main": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, + "scripts": { + "build": "backstage-cli plugin:build", + "start": "backstage-cli plugin:serve", + "lint": "backstage-cli lint", + "test": "backstage-cli test", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", + "clean": "backstage-cli clean" + }, + "dependencies": { + "@backstage/config": "^0.1.2", + "@backstage/core": "^0.7.0", + "@backstage/integration": "^0.5.0", + "@backstage/theme": "^0.2.3", + "@material-ui/core": "^4.11.0", + "@material-ui/icons": "^4.9.1", + "@material-ui/lab": "4.0.0-alpha.45", + "react": "^16.13.1", + "react-dom": "^16.13.1", + "react-use": "^15.3.3" + }, + "devDependencies": { + "@backstage/cli": "^0.6.3", + "@backstage/dev-utils": "^0.1.13", + "@backstage/test-utils": "^0.1.8", + "@testing-library/jest-dom": "^5.10.1", + "@testing-library/react": "^11.2.5", + "@testing-library/user-event": "^12.0.7", + "@types/jest": "^26.0.7", + "@types/node": "^14.14.32", + "msw": "^0.21.2", + "cross-fetch": "^3.0.6" + }, + "files": [ + "dist" + ] +} diff --git a/packages/integration-react/src/ScmIntegrationsApi.test.ts b/packages/integration-react/src/ScmIntegrationsApi.test.ts new file mode 100644 index 0000000000000..3788574950d80 --- /dev/null +++ b/packages/integration-react/src/ScmIntegrationsApi.test.ts @@ -0,0 +1,31 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { ConfigReader } from '@backstage/config'; +import { + ScmIntegrationsApi, + scmIntegrationsApiRef, +} from './ScmIntegrationsApi'; + +describe('scmIntegrationsApiRef', () => { + it('should export api', () => { + expect(scmIntegrationsApiRef).toBeDefined(); + }); + + it('should be instantiated', () => { + const i = ScmIntegrationsApi.fromConfig(new ConfigReader({})); + expect(i.list().length).toBe(4); // The default ones + }); +}); diff --git a/packages/integration-react/src/ScmIntegrationsApi.ts b/packages/integration-react/src/ScmIntegrationsApi.ts new file mode 100644 index 0000000000000..0494cd7aa82f5 --- /dev/null +++ b/packages/integration-react/src/ScmIntegrationsApi.ts @@ -0,0 +1,30 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { Config } from '@backstage/config'; +import { ApiRef, createApiRef } from '@backstage/core'; +import { ScmIntegrations } from '@backstage/integration'; + +export class ScmIntegrationsApi extends ScmIntegrations { + static fromConfig(config: Config): ScmIntegrationsApi { + return ScmIntegrations.fromConfig(config); + } +} + +export const scmIntegrationsApiRef: ApiRef = createApiRef({ + id: 'integration.scmintegrations', + description: 'All of the registered SCM integrations of your config', +}); diff --git a/packages/integration-react/src/index.ts b/packages/integration-react/src/index.ts new file mode 100644 index 0000000000000..5483765b2e7ed --- /dev/null +++ b/packages/integration-react/src/index.ts @@ -0,0 +1,20 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { + ScmIntegrationsApi, + scmIntegrationsApiRef, +} from './ScmIntegrationsApi'; diff --git a/packages/integration-react/src/setupTests.ts b/packages/integration-react/src/setupTests.ts new file mode 100644 index 0000000000000..3ffe1424cc7ef --- /dev/null +++ b/packages/integration-react/src/setupTests.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 '@testing-library/jest-dom'; +import 'cross-fetch/polyfill'; diff --git a/packages/integration/src/index.ts b/packages/integration/src/index.ts index c39608fe8ef89..e89cf6ef924d6 100644 --- a/packages/integration/src/index.ts +++ b/packages/integration/src/index.ts @@ -20,4 +20,8 @@ export * from './github'; export * from './gitlab'; export { defaultScmResolveUrl } from './helpers'; export { ScmIntegrations } from './ScmIntegrations'; -export type { ScmIntegration, ScmIntegrationRegistry } from './types'; +export type { + ScmIntegration, + ScmIntegrationRegistry, + ScmIntegrationsGroup, +} from './types'; diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index 935d65891e148..62f6e3bd60cff 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -34,6 +34,7 @@ "@backstage/catalog-client": "^0.3.6", "@backstage/core": "^0.7.1", "@backstage/integration": "^0.5.0", + "@backstage/integration-react": "^0.1.1", "@backstage/plugin-catalog-react": "^0.1.1", "@backstage/theme": "^0.2.4", "@material-ui/core": "^4.11.0", diff --git a/plugins/catalog-import/src/api/CatalogImportClient.test.ts b/plugins/catalog-import/src/api/CatalogImportClient.test.ts index 5187ffab52870..9d3ca8d22abc9 100644 --- a/plugins/catalog-import/src/api/CatalogImportClient.test.ts +++ b/plugins/catalog-import/src/api/CatalogImportClient.test.ts @@ -52,7 +52,10 @@ jest.mock('./GitHub', () => ({ })); import { ConfigReader, OAuthApi, UrlPatternDiscovery } from '@backstage/core'; -import { GitHubIntegrationConfig } from '@backstage/integration'; +import { + GitHubIntegrationConfig, + ScmIntegrations, +} from '@backstage/integration'; import { catalogApiRef } from '@backstage/plugin-catalog-react'; import { msw } from '@backstage/test-utils'; import { Octokit } from '@octokit/rest'; @@ -87,7 +90,7 @@ describe('CatalogImportClient', () => { }, }; - const configApi = new ConfigReader({}); + const scmIntegrationsApi = ScmIntegrations.fromConfig(new ConfigReader({})); const catalogApi: jest.Mocked = { getEntities: jest.fn(), @@ -105,7 +108,7 @@ describe('CatalogImportClient', () => { catalogImportClient = new CatalogImportClient({ discoveryApi, githubAuthApi, - configApi, + scmIntegrationsApi, identityApi, catalogApi, }); diff --git a/plugins/catalog-import/src/api/CatalogImportClient.ts b/plugins/catalog-import/src/api/CatalogImportClient.ts index ccf16e00f15c7..f6e8cb69d4677 100644 --- a/plugins/catalog-import/src/api/CatalogImportClient.ts +++ b/plugins/catalog-import/src/api/CatalogImportClient.ts @@ -16,13 +16,11 @@ import { CatalogApi } from '@backstage/catalog-client'; import { EntityName } from '@backstage/catalog-model'; +import { DiscoveryApi, IdentityApi, OAuthApi } from '@backstage/core'; import { - ConfigApi, - DiscoveryApi, - IdentityApi, - OAuthApi, -} from '@backstage/core'; -import { GitHubIntegrationConfig } from '@backstage/integration'; + GitHubIntegrationConfig, + ScmIntegrationRegistry, +} from '@backstage/integration'; import { Octokit } from '@octokit/rest'; import { PartialEntity } from '../types'; import { AnalyzeResult, CatalogImportApi } from './CatalogImportApi'; @@ -32,20 +30,20 @@ export class CatalogImportClient implements CatalogImportApi { private readonly discoveryApi: DiscoveryApi; private readonly identityApi: IdentityApi; private readonly githubAuthApi: OAuthApi; - private readonly configApi: ConfigApi; + private readonly scmIntegrationsApi: ScmIntegrationRegistry; private readonly catalogApi: CatalogApi; constructor(options: { discoveryApi: DiscoveryApi; githubAuthApi: OAuthApi; identityApi: IdentityApi; - configApi: ConfigApi; + scmIntegrationsApi: ScmIntegrationRegistry; catalogApi: CatalogApi; }) { this.discoveryApi = options.discoveryApi; this.githubAuthApi = options.githubAuthApi; this.identityApi = options.identityApi; - this.configApi = options.configApi; + this.scmIntegrationsApi = options.scmIntegrationsApi; this.catalogApi = options.catalogApi; } @@ -72,7 +70,7 @@ export class CatalogImportClient implements CatalogImportApi { }; } - const ghConfig = getGithubIntegrationConfig(this.configApi, url); + const ghConfig = getGithubIntegrationConfig(this.scmIntegrationsApi, url); if (!ghConfig) { 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.', @@ -113,7 +111,10 @@ export class CatalogImportClient implements CatalogImportApi { title: string; body: string; }): Promise<{ link: string; location: string }> { - const ghConfig = getGithubIntegrationConfig(this.configApi, repositoryUrl); + const ghConfig = getGithubIntegrationConfig( + this.scmIntegrationsApi, + repositoryUrl, + ); if (ghConfig) { return await this.submitGitHubPrToRepo({ diff --git a/plugins/catalog-import/src/api/GitHub.ts b/plugins/catalog-import/src/api/GitHub.ts index 6a798ec20107d..1e807159822cb 100644 --- a/plugins/catalog-import/src/api/GitHub.ts +++ b/plugins/catalog-import/src/api/GitHub.ts @@ -14,26 +14,22 @@ * limitations under the License. */ -import { ConfigApi } from '@backstage/core'; -import { ScmIntegrations } from '@backstage/integration'; +import { ScmIntegrationRegistry } from '@backstage/integration'; import parseGitUrl from 'git-url-parse'; export const getGithubIntegrationConfig = ( - config: ConfigApi, + scmIntegrationsApi: ScmIntegrationRegistry, location: string, ) => { - const { name: repo, owner } = parseGitUrl(location); - - const scmIntegrations = ScmIntegrations.fromConfig(config); - const githubIntegrationConfig = scmIntegrations.github.byUrl(location); - - if (!githubIntegrationConfig) { + const integration = scmIntegrationsApi.github.byUrl(location); + if (!integration) { return undefined; } + const { name: repo, owner } = parseGitUrl(location); return { repo, owner, - githubIntegrationConfig: githubIntegrationConfig.config, + githubIntegrationConfig: integration.config, }; }; diff --git a/plugins/catalog-import/src/components/ImportComponentPage.test.tsx b/plugins/catalog-import/src/components/ImportComponentPage.test.tsx index 4cadea85c09dc..08903e9933cf7 100644 --- a/plugins/catalog-import/src/components/ImportComponentPage.test.tsx +++ b/plugins/catalog-import/src/components/ImportComponentPage.test.tsx @@ -60,7 +60,7 @@ describe('', () => { getAccessToken: async () => 'token', }, identityApi, - configApi: {} as any, + scmIntegrationsApi: {} as any, catalogApi: {} as any, }), ); diff --git a/plugins/catalog-import/src/plugin.ts b/plugins/catalog-import/src/plugin.ts index 18b3124b3be38..b0beaf5a50101 100644 --- a/plugins/catalog-import/src/plugin.ts +++ b/plugins/catalog-import/src/plugin.ts @@ -15,15 +15,15 @@ */ import { - identityApiRef, - configApiRef, createApiFactory, createPlugin, createRoutableExtension, createRouteRef, discoveryApiRef, githubAuthApiRef, + identityApiRef, } from '@backstage/core'; +import { scmIntegrationsApiRef } from '@backstage/integration-react'; import { catalogApiRef } from '@backstage/plugin-catalog-react'; import { catalogImportApiRef, CatalogImportClient } from './api'; @@ -41,20 +41,20 @@ export const catalogImportPlugin = createPlugin({ discoveryApi: discoveryApiRef, githubAuthApi: githubAuthApiRef, identityApi: identityApiRef, - configApi: configApiRef, + scmIntegrationsApi: scmIntegrationsApiRef, catalogApi: catalogApiRef, }, factory: ({ discoveryApi, githubAuthApi, identityApi, - configApi, + scmIntegrationsApi, catalogApi, }) => new CatalogImportClient({ discoveryApi, githubAuthApi, - configApi, + scmIntegrationsApi, identityApi, catalogApi, }), diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index e5b76d50dc37d..73b00a1af210b 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -34,6 +34,7 @@ "@backstage/catalog-model": "^0.7.4", "@backstage/core": "^0.7.1", "@backstage/integration": "^0.5.1", + "@backstage/integration-react": "^0.1.1", "@backstage/plugin-catalog-react": "^0.1.1", "@backstage/theme": "^0.2.4", "@material-ui/core": "^4.11.0", diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx index 926deec32502b..435a8d14d573a 100644 --- a/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx @@ -15,12 +15,11 @@ */ import { RELATION_OWNED_BY } from '@backstage/catalog-model'; +import { ApiProvider, ApiRegistry, ConfigReader } from '@backstage/core'; import { - ApiProvider, - ApiRegistry, - configApiRef, - ConfigReader, -} from '@backstage/core'; + ScmIntegrationsApi, + scmIntegrationsApiRef, +} from '@backstage/integration-react'; import { EntityProvider } from '@backstage/plugin-catalog-react'; import { renderInTestApp } from '@backstage/test-utils'; import { act, fireEvent } from '@testing-library/react'; @@ -34,7 +33,7 @@ describe('', () => { kind: 'Component', metadata: { name: 'software', - description: 'This is the decription', + description: 'This is the description', }, spec: { owner: 'guest', @@ -53,10 +52,12 @@ describe('', () => { ], }; const apis = ApiRegistry.with( - configApiRef, - new ConfigReader({ - integrations: {}, - }), + scmIntegrationsApiRef, + ScmIntegrationsApi.fromConfig( + new ConfigReader({ + integrations: {}, + }), + ), ); const { getByText } = await renderInTestApp( @@ -70,7 +71,7 @@ describe('', () => { expect(getByText('service')).toBeInTheDocument(); expect(getByText('user:guest')).toBeInTheDocument(); expect(getByText('production')).toBeInTheDocument(); - expect(getByText('This is the decription')).toBeInTheDocument(); + expect(getByText('This is the description')).toBeInTheDocument(); }); it('renders "view source" link', async () => { @@ -91,17 +92,19 @@ describe('', () => { }, }; const apis = ApiRegistry.with( - configApiRef, - new ConfigReader({ - integrations: { - github: [ - { - host: 'github.com', - token: '...', - }, - ], - }, - }), + scmIntegrationsApiRef, + ScmIntegrationsApi.fromConfig( + new ConfigReader({ + integrations: { + github: [ + { + host: 'github.com', + token: '...', + }, + ], + }, + }), + ), ); const { getByText } = await renderInTestApp( @@ -135,17 +138,19 @@ describe('', () => { }, }; const apis = ApiRegistry.with( - configApiRef, - new ConfigReader({ - integrations: { - github: [ - { - host: 'github.com', - token: '...', - }, - ], - }, - }), + scmIntegrationsApiRef, + ScmIntegrationsApi.fromConfig( + new ConfigReader({ + integrations: { + github: [ + { + host: 'github.com', + token: '...', + }, + ], + }, + }), + ), ); const { getByTitle } = await renderInTestApp( @@ -180,7 +185,10 @@ describe('', () => { lifecycle: 'production', }, }; - const apis = ApiRegistry.with(configApiRef, new ConfigReader({})); + const apis = ApiRegistry.with( + scmIntegrationsApiRef, + ScmIntegrationsApi.fromConfig(new ConfigReader({})), + ); const { getByText } = await renderInTestApp( diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.tsx index c8bac651ce332..e0cd0c2160c1c 100644 --- a/plugins/catalog/src/components/AboutCard/AboutCard.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutCard.tsx @@ -21,11 +21,11 @@ import { RELATION_PROVIDES_API, } from '@backstage/catalog-model'; import { - configApiRef, HeaderIconLinkRow, IconLinkVerticalProps, useApi, } from '@backstage/core'; +import { scmIntegrationsApiRef } from '@backstage/integration-react'; import { getEntityRelations, useEntity } from '@backstage/plugin-catalog-react'; import { Card, @@ -64,8 +64,11 @@ type AboutCardProps = { export function AboutCard({ variant }: AboutCardProps) { const classes = useStyles(); const { entity } = useEntity(); - const configApi = useApi(configApiRef); - const entitySourceLocation = getEntitySourceLocation(entity, configApi); + const scmIntegrationsApi = useApi(scmIntegrationsApiRef); + const entitySourceLocation = getEntitySourceLocation( + entity, + scmIntegrationsApi, + ); const entityMetadataEditUrl = getEntityMetadataEditUrl(entity); const providesApiRelations = getEntityRelations( entity, diff --git a/plugins/catalog/src/utils/getEntitySourceLocation.ts b/plugins/catalog/src/utils/getEntitySourceLocation.ts index 5b957a850a370..36e9d5d1872fc 100644 --- a/plugins/catalog/src/utils/getEntitySourceLocation.ts +++ b/plugins/catalog/src/utils/getEntitySourceLocation.ts @@ -19,8 +19,7 @@ import { parseLocationReference, SOURCE_LOCATION_ANNOTATION, } from '@backstage/catalog-model'; -import { ConfigApi } from '@backstage/core'; -import { ScmIntegrations } from '@backstage/integration'; +import { ScmIntegrationRegistry } from '@backstage/integration'; export type EntitySourceLocation = { locationTargetUrl: string; @@ -29,7 +28,7 @@ export type EntitySourceLocation = { export function getEntitySourceLocation( entity: Entity, - config: ConfigApi, + scmIntegrationsApi: ScmIntegrationRegistry, ): EntitySourceLocation | undefined { const sourceLocation = entity.metadata.annotations?.[SOURCE_LOCATION_ANNOTATION]; @@ -40,9 +39,7 @@ export function getEntitySourceLocation( try { const sourceLocationRef = parseLocationReference(sourceLocation); - const scmIntegrations = ScmIntegrations.fromConfig(config); - const integration = scmIntegrations.byUrl(sourceLocationRef.target); - + const integration = scmIntegrationsApi.byUrl(sourceLocationRef.target); return { locationTargetUrl: sourceLocationRef.target, integrationType: integration?.type, diff --git a/plugins/scaffolder/dev/index.tsx b/plugins/scaffolder/dev/index.tsx index a41d088329c53..d2b003e0ca607 100644 --- a/plugins/scaffolder/dev/index.tsx +++ b/plugins/scaffolder/dev/index.tsx @@ -14,13 +14,14 @@ * limitations under the License. */ -import React from 'react'; -import { createDevApp } from '@backstage/dev-utils'; -import { configApiRef, discoveryApiRef, identityApiRef } from '@backstage/core'; import { CatalogClient } from '@backstage/catalog-client'; +import { configApiRef, discoveryApiRef, identityApiRef } from '@backstage/core'; +import { createDevApp } from '@backstage/dev-utils'; +import { scmIntegrationsApiRef } from '@backstage/integration-react'; import { catalogApiRef } from '@backstage/plugin-catalog-react'; +import React from 'react'; +import { scaffolderApiRef, ScaffolderClient } from '../src'; import { ScaffolderPage } from '../src/plugin'; -import { ScaffolderClient, scaffolderApiRef } from '../src'; createDevApp() .registerApi({ @@ -34,9 +35,10 @@ createDevApp() discoveryApi: discoveryApiRef, identityApi: identityApiRef, configApi: configApiRef, + scmIntegrationsApi: scmIntegrationsApiRef, }, - factory: ({ discoveryApi, identityApi, configApi }) => - new ScaffolderClient({ discoveryApi, identityApi, configApi }), + factory: ({ discoveryApi, identityApi, scmIntegrationsApi }) => + new ScaffolderClient({ discoveryApi, identityApi, scmIntegrationsApi }), }) .addPage({ path: '/create', diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index a1fd3919c697d..08cdc89c12a37 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -35,6 +35,7 @@ "@backstage/config": "^0.1.3", "@backstage/core": "^0.7.1", "@backstage/integration": "^0.5.1", + "@backstage/integration-react": "^0.1.1", "@backstage/plugin-catalog-react": "^0.1.1", "@backstage/theme": "^0.2.4", "@material-ui/core": "^4.11.0", diff --git a/plugins/scaffolder/src/api.test.ts b/plugins/scaffolder/src/api.test.ts index 3e5e81bb2a0b2..7739371348387 100644 --- a/plugins/scaffolder/src/api.test.ts +++ b/plugins/scaffolder/src/api.test.ts @@ -13,23 +13,27 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { ConfigReader } from '@backstage/core'; +import { ScmIntegrations } from '@backstage/integration'; import { ScaffolderClient } from './api'; describe('api', () => { const discoveryApi = {} as any; const identityApi = {} as any; - const configApi = new ConfigReader({ - integrations: { - github: [ - { - host: 'hello.com', - }, - ], - }, - }); + const scmIntegrationsApi = ScmIntegrations.fromConfig( + new ConfigReader({ + integrations: { + github: [ + { + host: 'hello.com', + }, + ], + }, + }), + ); const apiClient = new ScaffolderClient({ - configApi, + scmIntegrationsApi, discoveryApi, identityApi, }); diff --git a/plugins/scaffolder/src/api.ts b/plugins/scaffolder/src/api.ts index 6c94169b3df56..e266d06a91c2d 100644 --- a/plugins/scaffolder/src/api.ts +++ b/plugins/scaffolder/src/api.ts @@ -19,11 +19,10 @@ import { JsonObject } from '@backstage/config'; import { createApiRef, DiscoveryApi, - Observable, - ConfigApi, IdentityApi, + Observable, } from '@backstage/core'; -import { ScmIntegrations } from '@backstage/integration'; +import { ScmIntegrationRegistry } from '@backstage/integration'; import ObservableImpl from 'zen-observable'; import { ListActionsResponse, ScaffolderTask, Status } from './types'; @@ -83,29 +82,28 @@ export interface ScaffolderApi { after?: number; }): Observable; } + export class ScaffolderClient implements ScaffolderApi { private readonly discoveryApi: DiscoveryApi; private readonly identityApi: IdentityApi; - private readonly configApi: ConfigApi; + private readonly scmIntegrationsApi: ScmIntegrationRegistry; constructor(options: { discoveryApi: DiscoveryApi; identityApi: IdentityApi; - configApi: ConfigApi; + scmIntegrationsApi: ScmIntegrationRegistry; }) { this.discoveryApi = options.discoveryApi; this.identityApi = options.identityApi; - this.configApi = options.configApi; + this.scmIntegrationsApi = options.scmIntegrationsApi; } async getIntegrationsList(options: { allowedHosts: string[] }) { - const integrations = ScmIntegrations.fromConfig(this.configApi); - return [ - ...integrations.azure.list(), - ...integrations.bitbucket.list(), - ...integrations.github.list(), - ...integrations.gitlab.list(), + ...this.scmIntegrationsApi.azure.list(), + ...this.scmIntegrationsApi.bitbucket.list(), + ...this.scmIntegrationsApi.github.list(), + ...this.scmIntegrationsApi.gitlab.list(), ] .map(c => ({ type: c.type, title: c.title, host: c.config.host })) .filter(c => options.allowedHosts.includes(c.host)); diff --git a/plugins/scaffolder/src/plugin.ts b/plugins/scaffolder/src/plugin.ts index 8ba03f4682ce6..1aa25d8e18f7f 100644 --- a/plugins/scaffolder/src/plugin.ts +++ b/plugins/scaffolder/src/plugin.ts @@ -15,15 +15,15 @@ */ import { - createPlugin, createApiFactory, + createPlugin, + createRoutableExtension, discoveryApiRef, identityApiRef, - configApiRef, - createRoutableExtension, } from '@backstage/core'; -import { rootRouteRef } from './routes'; +import { scmIntegrationsApiRef } from '@backstage/integration-react'; import { scaffolderApiRef, ScaffolderClient } from './api'; +import { rootRouteRef } from './routes'; export const scaffolderPlugin = createPlugin({ id: 'scaffolder', @@ -33,10 +33,10 @@ export const scaffolderPlugin = createPlugin({ deps: { discoveryApi: discoveryApiRef, identityApi: identityApiRef, - configApi: configApiRef, + scmIntegrationsApi: scmIntegrationsApiRef, }, - factory: ({ discoveryApi, identityApi, configApi }) => - new ScaffolderClient({ discoveryApi, identityApi, configApi }), + factory: ({ discoveryApi, identityApi, scmIntegrationsApi }) => + new ScaffolderClient({ discoveryApi, identityApi, scmIntegrationsApi }), }), ], routes: {
{JSON.stringify(integrations, undefined, 2)}