use location from entity
Signed-off-by: Andrew Johnson <ajohnson@gocardless.com>
This commit is contained in:
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { parseLocationReference } from '@backstage/catalog-model';
|
||||
import { createApiRef, OAuthApi } from '@backstage/core';
|
||||
import { InputError } from '@backstage/errors';
|
||||
import { ScmIntegrationRegistry } from '@backstage/integration';
|
||||
@@ -20,25 +21,29 @@ import { graphql } from '@octokit/graphql';
|
||||
|
||||
const getBaseUrl = (
|
||||
scmIntegrationsApi: ScmIntegrationRegistry,
|
||||
host?: string,
|
||||
location?: string,
|
||||
): string => {
|
||||
if (!host) {
|
||||
if (!location) {
|
||||
return 'https://api.github.com';
|
||||
}
|
||||
|
||||
const integrationConfig = scmIntegrationsApi.github.byHost(host);
|
||||
const config = scmIntegrationsApi.github.byUrl(
|
||||
parseLocationReference(location).target,
|
||||
);
|
||||
|
||||
if (!integrationConfig) {
|
||||
if (!config) {
|
||||
throw new InputError(
|
||||
`No matching GitHub integration configuration for host ${host}, please check your integrations config`,
|
||||
`No matching GitHub integration configuration for location ${location}, please check your integrations config`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!integrationConfig.config.apiBaseUrl) {
|
||||
throw new InputError(`No apiBaseUrl available for host ${host}`);
|
||||
if (!config.config.apiBaseUrl) {
|
||||
throw new InputError(
|
||||
`No apiBaseUrl available for location ${location}, please check your integrations config`,
|
||||
);
|
||||
}
|
||||
|
||||
return integrationConfig.config.apiBaseUrl;
|
||||
return config?.config.apiBaseUrl;
|
||||
};
|
||||
|
||||
export type GithubDeployment = {
|
||||
@@ -51,13 +56,17 @@ export type GithubDeployment = {
|
||||
};
|
||||
};
|
||||
|
||||
type QueryOptions = {
|
||||
owner: string;
|
||||
repo: string;
|
||||
last: number;
|
||||
};
|
||||
|
||||
export interface GithubDeploymentsApi {
|
||||
listDeployments(options: {
|
||||
host?: string;
|
||||
owner: string;
|
||||
repo: string;
|
||||
last: number;
|
||||
}): Promise<GithubDeployment[]>;
|
||||
listDeployments(
|
||||
options: QueryOptions,
|
||||
location?: string,
|
||||
): Promise<GithubDeployment[]>;
|
||||
}
|
||||
|
||||
export const githubDeploymentsApiRef = createApiRef<GithubDeploymentsApi>({
|
||||
@@ -105,13 +114,11 @@ export class GithubDeploymentsApiClient implements GithubDeploymentsApi {
|
||||
this.scmIntegrationsApi = options.scmIntegrationsApi;
|
||||
}
|
||||
|
||||
async listDeployments(options: {
|
||||
owner: string;
|
||||
repo: string;
|
||||
last: number;
|
||||
host?: string;
|
||||
}): Promise<GithubDeployment[]> {
|
||||
const baseUrl = getBaseUrl(this.scmIntegrationsApi, options.host);
|
||||
async listDeployments(
|
||||
options: QueryOptions,
|
||||
location?: string,
|
||||
): Promise<GithubDeployment[]> {
|
||||
const baseUrl = getBaseUrl(this.scmIntegrationsApi, location);
|
||||
const token = await this.githubAuthApi.getAccessToken(['repo']);
|
||||
|
||||
const graphQLWithAuth = graphql.defaults({
|
||||
|
||||
@@ -40,10 +40,13 @@ import {
|
||||
import { setupServer } from 'msw/node';
|
||||
import { graphql } from 'msw';
|
||||
import { ScmIntegrations } from '@backstage/integration';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
|
||||
let entity: { entity: Entity };
|
||||
|
||||
jest.mock('@backstage/plugin-catalog-react', () => ({
|
||||
useEntity: () => {
|
||||
return entityStub;
|
||||
return entity;
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -53,15 +56,24 @@ const configApi: ConfigApi = new ConfigReader({
|
||||
integrations: {
|
||||
github: [
|
||||
{
|
||||
host: 'missing-api-base-url.com',
|
||||
host: 'my-github-1.com',
|
||||
apiBaseUrl: 'https://api.my-github-1.com',
|
||||
},
|
||||
{
|
||||
host: 'my-github.com',
|
||||
apiBaseUrl: 'https://api.my-github.com',
|
||||
host: 'my-github-2.com',
|
||||
apiBaseUrl: 'https://api.my-github-2.com',
|
||||
},
|
||||
{
|
||||
host: 'my-github-3.com',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const GRAPHQL_GITHUB_API = graphql.link('https://api.github.com/graphql');
|
||||
const GRAPHQL_CUSTOM_API_1 = graphql.link('https://api.github-1.com/graphql');
|
||||
const GRAPHQL_CUSTOM_API_2 = graphql.link('https://api.github-2.com/graphql');
|
||||
|
||||
const scmIntegrationsApi = ScmIntegrations.fromConfig(configApi);
|
||||
const githubAuthApi: OAuthApi = {
|
||||
getAccessToken: async _ => 'access_token',
|
||||
@@ -76,11 +88,36 @@ const apis = ApiRegistry.from([
|
||||
],
|
||||
]);
|
||||
|
||||
const assertFetchedData = async () => {
|
||||
const rendered = await renderInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<GithubDeploymentsCard />
|
||||
</ApiProvider>,
|
||||
);
|
||||
|
||||
expect(await rendered.findByText('GitHub Deployments')).toBeInTheDocument();
|
||||
|
||||
expect(await rendered.findByText('active')).toBeInTheDocument();
|
||||
expect(await rendered.findByText('prd')).toBeInTheDocument();
|
||||
expect(await rendered.findByText('12345')).toHaveAttribute(
|
||||
'href',
|
||||
'https://exampleapi.com/123456789',
|
||||
);
|
||||
|
||||
expect(await rendered.findByText('pending')).toBeInTheDocument();
|
||||
expect(await rendered.findByText('lab')).toBeInTheDocument();
|
||||
expect(await rendered.findByText('54321')).toHaveAttribute(
|
||||
'href',
|
||||
'https://exampleapi.com/543212345',
|
||||
);
|
||||
};
|
||||
|
||||
describe('github-deployments', () => {
|
||||
const worker = setupServer();
|
||||
msw.setupDefaultHandlers(worker);
|
||||
|
||||
beforeEach(() => {
|
||||
worker.resetHandlers();
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
@@ -91,40 +128,27 @@ describe('github-deployments', () => {
|
||||
});
|
||||
|
||||
describe('GithubDeploymentsCard', () => {
|
||||
it('should display fetched data', async () => {
|
||||
beforeEach(() => {
|
||||
entity = entityStub;
|
||||
entity.entity.metadata.annotations = {
|
||||
'github.com/project-slug': 'org/repo',
|
||||
};
|
||||
});
|
||||
|
||||
it('displays fetched data using default github api', async () => {
|
||||
worker.use(
|
||||
graphql.query('deployments', (_, res, ctx) =>
|
||||
GRAPHQL_GITHUB_API.query('deployments', (_, res, ctx) =>
|
||||
res(ctx.data(responseStub)),
|
||||
),
|
||||
);
|
||||
|
||||
const rendered = await renderInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<GithubDeploymentsCard />
|
||||
</ApiProvider>,
|
||||
);
|
||||
|
||||
expect(
|
||||
await rendered.findByText('GitHub Deployments'),
|
||||
).toBeInTheDocument();
|
||||
expect(await rendered.findByText('active')).toBeInTheDocument();
|
||||
expect(await rendered.findByText('prd')).toBeInTheDocument();
|
||||
expect(await rendered.findByText('12345')).toHaveAttribute(
|
||||
'href',
|
||||
'https://exampleapi.com/123456789',
|
||||
);
|
||||
|
||||
expect(await rendered.findByText('pending')).toBeInTheDocument();
|
||||
expect(await rendered.findByText('lab')).toBeInTheDocument();
|
||||
expect(await rendered.findByText('54321')).toHaveAttribute(
|
||||
'href',
|
||||
'https://exampleapi.com/543212345',
|
||||
);
|
||||
await assertFetchedData();
|
||||
expect.assertions(7);
|
||||
});
|
||||
|
||||
it('should display empty state when no data', async () => {
|
||||
worker.use(
|
||||
graphql.query('deployments', (_, res, ctx) =>
|
||||
GRAPHQL_GITHUB_API.query('deployments', (_, res, ctx) =>
|
||||
res(ctx.data(noDataResponseStub)),
|
||||
),
|
||||
);
|
||||
@@ -145,7 +169,7 @@ describe('github-deployments', () => {
|
||||
|
||||
it('should shows new data on reload', async () => {
|
||||
worker.use(
|
||||
graphql.query('deployments', (_, res, ctx) =>
|
||||
GRAPHQL_GITHUB_API.query('deployments', (_, res, ctx) =>
|
||||
res(ctx.data(responseStub)),
|
||||
),
|
||||
);
|
||||
@@ -163,7 +187,7 @@ describe('github-deployments', () => {
|
||||
|
||||
worker.resetHandlers();
|
||||
worker.use(
|
||||
graphql.query('deployments', (_, res, ctx) =>
|
||||
GRAPHQL_GITHUB_API.query('deployments', (_, res, ctx) =>
|
||||
res(ctx.data(refreshedResponseStub)),
|
||||
),
|
||||
);
|
||||
@@ -177,44 +201,102 @@ describe('github-deployments', () => {
|
||||
expect(await rendered.findByText('failure')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows error when host does not exist', async () => {
|
||||
worker.use(
|
||||
graphql.query('deployments', (_, res, ctx) =>
|
||||
res(ctx.data(responseStub)),
|
||||
),
|
||||
);
|
||||
describe('entity with source location', () => {
|
||||
beforeEach(() => {
|
||||
entity = entityStub;
|
||||
entity.entity.metadata.annotations = {
|
||||
'github.com/project-slug': 'org/repo',
|
||||
'backstage.io/source-location':
|
||||
'url:https://my-github-1.com/org/repo',
|
||||
};
|
||||
});
|
||||
|
||||
const rendered = await renderInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<GithubDeploymentsCard host="unknown-host.com" />
|
||||
</ApiProvider>,
|
||||
);
|
||||
it('should fetch data using custom api', async () => {
|
||||
const customAPI = graphql.link('https://api.my-github-1.com/graphql');
|
||||
|
||||
expect(
|
||||
await rendered.findByText(
|
||||
'Warning: No matching GitHub integration configuration for host unknown-host.com, please check your integrations config',
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
worker.use(
|
||||
customAPI.query('deployments', (_, res, ctx) =>
|
||||
res(ctx.data(responseStub)),
|
||||
),
|
||||
);
|
||||
|
||||
await assertFetchedData();
|
||||
expect.assertions(7);
|
||||
});
|
||||
});
|
||||
|
||||
it('shows error when baseApiURL does not exist for host', async () => {
|
||||
worker.use(
|
||||
graphql.query('deployments', (_, res, ctx) =>
|
||||
res(ctx.data(responseStub)),
|
||||
),
|
||||
);
|
||||
describe('entity with managed by location', () => {
|
||||
beforeEach(() => {
|
||||
entity = entityStub;
|
||||
entity.entity.metadata.annotations = {
|
||||
'github.com/project-slug': 'org/repo',
|
||||
'backstage.io/managed-by-location':
|
||||
'url:https://my-github-2.com/org/repo',
|
||||
};
|
||||
});
|
||||
|
||||
const rendered = await renderInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<GithubDeploymentsCard host="missing-api-base-url.com" />
|
||||
</ApiProvider>,
|
||||
);
|
||||
it('should fetch data using custom api', async () => {
|
||||
const customAPI = graphql.link('https://api.my-github-2.com/graphql');
|
||||
|
||||
expect(
|
||||
await rendered.findByText(
|
||||
'Warning: No apiBaseUrl available for host missing-api-base-url.com',
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
worker.use(
|
||||
customAPI.query('deployments', (_, res, ctx) =>
|
||||
res(ctx.data(responseStub)),
|
||||
),
|
||||
);
|
||||
|
||||
await assertFetchedData();
|
||||
expect.assertions(7);
|
||||
});
|
||||
});
|
||||
|
||||
describe('entity with location without baseApiURL', () => {
|
||||
beforeEach(() => {
|
||||
entity = entityStub;
|
||||
entity.entity.metadata.annotations = {
|
||||
'github.com/project-slug': 'org/repo',
|
||||
'backstage.io/managed-by-location':
|
||||
'url:https://my-github-3.com/org/repo',
|
||||
};
|
||||
});
|
||||
|
||||
it('shows error when no matching github host', async () => {
|
||||
const rendered = await renderInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<GithubDeploymentsCard />
|
||||
</ApiProvider>,
|
||||
);
|
||||
|
||||
expect(
|
||||
await rendered.findByText(
|
||||
'Warning: No apiBaseUrl available for location url:https://my-github-3.com/org/repo, please check your integrations config',
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('entity with location without matching host', () => {
|
||||
beforeEach(() => {
|
||||
entity = entityStub;
|
||||
entity.entity.metadata.annotations = {
|
||||
'github.com/project-slug': 'org/repo',
|
||||
'backstage.io/managed-by-location':
|
||||
'url:https://my-github-unknown.com/org/repo',
|
||||
};
|
||||
});
|
||||
|
||||
it('shows error when no matching github host', async () => {
|
||||
const rendered = await renderInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<GithubDeploymentsCard />
|
||||
</ApiProvider>,
|
||||
);
|
||||
|
||||
expect(
|
||||
await rendered.findByText(
|
||||
'Warning: No matching GitHub integration configuration for location url:https://my-github-unknown.com/org/repo, please check your integrations config',
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -27,21 +27,25 @@ import {
|
||||
isGithubDeploymentsAvailable,
|
||||
} from '../Router';
|
||||
import GithubDeploymentsTable from './GithubDeploymentsTable/GithubDeploymentsTable';
|
||||
import {
|
||||
LOCATION_ANNOTATION,
|
||||
SOURCE_LOCATION_ANNOTATION,
|
||||
} from '@backstage/catalog-model';
|
||||
|
||||
const GithubDeploymentsComponent = ({
|
||||
host,
|
||||
projectSlug,
|
||||
last,
|
||||
location,
|
||||
}: {
|
||||
host?: string;
|
||||
projectSlug: string;
|
||||
last: number;
|
||||
location?: string;
|
||||
}) => {
|
||||
const api = useApi(githubDeploymentsApiRef);
|
||||
const [owner, repo] = projectSlug.split('/');
|
||||
|
||||
const { loading, value, error, retry: reload } = useAsyncRetry(
|
||||
async () => await api.listDeployments({ host, owner, repo, last }),
|
||||
async () => await api.listDeployments({ owner, repo, last }, location),
|
||||
);
|
||||
|
||||
if (error) {
|
||||
@@ -57,14 +61,11 @@ const GithubDeploymentsComponent = ({
|
||||
);
|
||||
};
|
||||
|
||||
export const GithubDeploymentsCard = ({
|
||||
last,
|
||||
host,
|
||||
}: {
|
||||
last?: number;
|
||||
host?: string;
|
||||
}) => {
|
||||
export const GithubDeploymentsCard = ({ last }: { last?: number }) => {
|
||||
const { entity } = useEntity();
|
||||
const location =
|
||||
entity?.metadata.annotations?.[SOURCE_LOCATION_ANNOTATION] ||
|
||||
entity?.metadata.annotations?.[LOCATION_ANNOTATION];
|
||||
|
||||
return !isGithubDeploymentsAvailable(entity) ? (
|
||||
<MissingAnnotationEmptyState annotation={GITHUB_PROJECT_SLUG_ANNOTATION} />
|
||||
@@ -74,7 +75,7 @@ export const GithubDeploymentsCard = ({
|
||||
entity?.metadata.annotations?.[GITHUB_PROJECT_SLUG_ANNOTATION] || ''
|
||||
}
|
||||
last={last || 10}
|
||||
host={host}
|
||||
location={location}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -13,15 +13,14 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { QueryResponse } from '../api';
|
||||
|
||||
export const entityStub = {
|
||||
export const entityStub: { entity: Entity } = {
|
||||
entity: {
|
||||
metadata: {
|
||||
namespace: 'default',
|
||||
annotations: {
|
||||
'github.com/project-slug': 'org/repo',
|
||||
},
|
||||
annotations: {},
|
||||
name: 'sample-service',
|
||||
description: 'Sample service',
|
||||
uid: 'g0h33dd9-56h7-835b-b63v-7x5da3j64851',
|
||||
|
||||
Reference in New Issue
Block a user