introduce @backstage/integration-react and its scmIntegrationsApiRef
Signed-off-by: Fredrik Adelöw <freben@gmail.com>
This commit is contained in:
@@ -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