Merge pull request #4886 from backstage/freben/integrations-api-ref

introduce the scmIntegrationsApiRef
This commit is contained in:
Fredrik Adelöw
2021-03-16 22:37:43 +01:00
committed by GitHub
36 changed files with 481 additions and 119 deletions
+1
View File
@@ -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({
+6 -10
View File
@@ -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,
}),
);
+5 -5
View File
@@ -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,
}),
+1
View File
@@ -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,
+8 -6
View File
@@ -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',
+1
View File
@@ -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",
+14 -10
View File
@@ -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,
});
+10 -12
View File
@@ -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));
+7 -7
View File
@@ -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: {