introduce @backstage/integration-react and its scmIntegrationsApiRef
Signed-off-by: Fredrik Adelöw <freben@gmail.com>
This commit is contained in:
@@ -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),
|
||||
+ }),
|
||||
```
|
||||
@@ -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`.
|
||||
@@ -23,7 +23,7 @@ describe('Catalog', () => {
|
||||
|
||||
cy.visit('/catalog');
|
||||
|
||||
cy.contains('Owned (7)').should('be.visible');
|
||||
cy.contains('Owned (8)').should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -15,22 +15,32 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
AnyApiFactory,
|
||||
configApiRef,
|
||||
createApiFactory,
|
||||
errorApiRef,
|
||||
githubAuthApiRef,
|
||||
createApiFactory,
|
||||
} from '@backstage/core';
|
||||
|
||||
import {
|
||||
ScmIntegrationsApi,
|
||||
scmIntegrationsApiRef,
|
||||
} from '@backstage/integration-react';
|
||||
import {
|
||||
costInsightsApiRef,
|
||||
ExampleCostInsightsClient,
|
||||
} from '@backstage/plugin-cost-insights';
|
||||
import {
|
||||
graphQlBrowseApiRef,
|
||||
GraphQLEndpoints,
|
||||
} from '@backstage/plugin-graphiql';
|
||||
|
||||
import {
|
||||
costInsightsApiRef,
|
||||
ExampleCostInsightsClient,
|
||||
} from '@backstage/plugin-cost-insights';
|
||||
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 },
|
||||
|
||||
@@ -69,6 +69,7 @@
|
||||
"@backstage/plugin-techdocs": "*",
|
||||
"@backstage/plugin-techdocs-backend": "*",
|
||||
"@backstage/plugin-user-settings": "*",
|
||||
"@backstage/integration-react": "*",
|
||||
"@backstage/test-utils": "*",
|
||||
"@backstage/theme": "*"
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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),
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 = () => <Box p={3}>Page belonging to another plugin.</Box>;
|
||||
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,
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
extends: [require.resolve('@backstage/cli/config/eslint')],
|
||||
};
|
||||
@@ -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.
|
||||
@@ -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<ScmIntegration>;
|
||||
}) => {
|
||||
const integrations = props.group.list();
|
||||
if (!integrations) {
|
||||
return (
|
||||
<Typography color="textSecondary">
|
||||
No integrations of this type
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Typography variant="caption">
|
||||
<pre>{JSON.stringify(integrations, undefined, 2)}</pre>
|
||||
</Typography>
|
||||
);
|
||||
};
|
||||
|
||||
export const DevPage = () => {
|
||||
const integrations = useApi(scmIntegrationsApiRef);
|
||||
return (
|
||||
<Content>
|
||||
<Typography paragraph variant="h2">
|
||||
Azure
|
||||
</Typography>
|
||||
<Integrations group={integrations.azure} />
|
||||
<Typography paragraph variant="h2">
|
||||
Bitbucket
|
||||
</Typography>
|
||||
<Integrations group={integrations.bitbucket} />
|
||||
<Typography paragraph variant="h2">
|
||||
GitHub
|
||||
</Typography>
|
||||
<Integrations group={integrations.github} />
|
||||
<Typography paragraph variant="h2">
|
||||
GitLab
|
||||
</Typography>
|
||||
<Integrations group={integrations.gitlab} />
|
||||
</Content>
|
||||
);
|
||||
};
|
||||
@@ -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: <DevPage />,
|
||||
title: 'Root Page',
|
||||
})
|
||||
.render();
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
});
|
||||
});
|
||||
@@ -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<ScmIntegrationsApi> = createApiRef({
|
||||
id: 'integration.scmintegrations',
|
||||
description: 'All of the registered SCM integrations of your config',
|
||||
});
|
||||
@@ -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';
|
||||
@@ -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';
|
||||
@@ -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';
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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<typeof catalogApiRef.T> = {
|
||||
getEntities: jest.fn(),
|
||||
@@ -105,7 +108,7 @@ describe('CatalogImportClient', () => {
|
||||
catalogImportClient = new CatalogImportClient({
|
||||
discoveryApi,
|
||||
githubAuthApi,
|
||||
configApi,
|
||||
scmIntegrationsApi,
|
||||
identityApi,
|
||||
catalogApi,
|
||||
});
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -60,7 +60,7 @@ describe('<ImportComponentPage />', () => {
|
||||
getAccessToken: async () => 'token',
|
||||
},
|
||||
identityApi,
|
||||
configApi: {} as any,
|
||||
scmIntegrationsApi: {} as any,
|
||||
catalogApi: {} as any,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
}),
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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('<AboutCard />', () => {
|
||||
kind: 'Component',
|
||||
metadata: {
|
||||
name: 'software',
|
||||
description: 'This is the decription',
|
||||
description: 'This is the description',
|
||||
},
|
||||
spec: {
|
||||
owner: 'guest',
|
||||
@@ -53,10 +52,12 @@ describe('<AboutCard />', () => {
|
||||
],
|
||||
};
|
||||
const apis = ApiRegistry.with(
|
||||
configApiRef,
|
||||
new ConfigReader({
|
||||
integrations: {},
|
||||
}),
|
||||
scmIntegrationsApiRef,
|
||||
ScmIntegrationsApi.fromConfig(
|
||||
new ConfigReader({
|
||||
integrations: {},
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
const { getByText } = await renderInTestApp(
|
||||
@@ -70,7 +71,7 @@ describe('<AboutCard />', () => {
|
||||
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('<AboutCard />', () => {
|
||||
},
|
||||
};
|
||||
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('<AboutCard />', () => {
|
||||
},
|
||||
};
|
||||
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('<AboutCard />', () => {
|
||||
lifecycle: 'production',
|
||||
},
|
||||
};
|
||||
const apis = ApiRegistry.with(configApiRef, new ConfigReader({}));
|
||||
const apis = ApiRegistry.with(
|
||||
scmIntegrationsApiRef,
|
||||
ScmIntegrationsApi.fromConfig(new ConfigReader({})),
|
||||
);
|
||||
|
||||
const { getByText } = await renderInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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<LogEvent>;
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
@@ -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: {
|
||||
|
||||
Reference in New Issue
Block a user