support GitHub enterprise and hosted GitHub on GithubDeployments plugin

Signed-off-by: Andrew Johnson <ajohnson@gocardless.com>
This commit is contained in:
Andrew Johnson
2021-04-06 13:53:17 +01:00
parent 78c6e0d90f
commit 8e554c649a
5 changed files with 108 additions and 7 deletions
+3
View File
@@ -22,6 +22,9 @@
"dependencies": {
"@backstage/catalog-model": "^0.7.4",
"@backstage/core": "^0.7.3",
"@backstage/errors": "^0.1.1",
"@backstage/integration": "^0.5.1",
"@backstage/integration-react": "^0.1.1",
"@backstage/plugin-catalog-react": "^0.1.3",
"@backstage/theme": "^0.2.5",
"@material-ui/core": "^4.11.0",
@@ -14,8 +14,33 @@
* limitations under the License.
*/
import { createApiRef, OAuthApi } from '@backstage/core';
import { InputError } from '@backstage/errors';
import { ScmIntegrationRegistry } from '@backstage/integration';
import { graphql } from '@octokit/graphql';
const getBaseUrl = (
scmIntegrationsApi: ScmIntegrationRegistry,
host?: string,
): string => {
if (!host) {
return 'https://api.github.com';
}
const integrationConfig = scmIntegrationsApi.github.byHost(host);
if (!integrationConfig) {
throw new InputError(
`No matching GitHub integration configuration for host ${host}, please check your integrations config`,
);
}
if (!integrationConfig.config.apiBaseUrl) {
throw new InputError(`No apiBaseUrl available for host ${host}`);
}
return integrationConfig.config.apiBaseUrl;
};
export type GithubDeployment = {
environment: string;
state: string;
@@ -28,6 +53,7 @@ export type GithubDeployment = {
export interface GithubDeploymentsApi {
listDeployments(options: {
host?: string;
owner: string;
repo: string;
last: number;
@@ -41,6 +67,7 @@ export const githubDeploymentsApiRef = createApiRef<GithubDeploymentsApi>({
export type Options = {
githubAuthApi: OAuthApi;
scmIntegrationsApi: ScmIntegrationRegistry;
};
const deploymentsQuery = `
@@ -71,19 +98,24 @@ export type QueryResponse = {
export class GithubDeploymentsApiClient implements GithubDeploymentsApi {
private readonly githubAuthApi: OAuthApi;
private readonly scmIntegrationsApi: ScmIntegrationRegistry;
constructor(options: Options) {
this.githubAuthApi = options.githubAuthApi;
this.scmIntegrationsApi = options.scmIntegrationsApi;
}
async listDeployments(options: {
owner: string;
repo: string;
last: number;
host?: string;
}): Promise<GithubDeployment[]> {
const baseUrl = getBaseUrl(this.scmIntegrationsApi, options.host);
const token = await this.githubAuthApi.getAccessToken(['repo']);
const graphQLWithAuth = graphql.defaults({
baseUrl,
headers: {
authorization: `token ${token}`,
},
@@ -39,6 +39,7 @@ import {
import { setupServer } from 'msw/node';
import { graphql } from 'msw';
import { ScmIntegrations } from '@backstage/integration';
jest.mock('@backstage/plugin-catalog-react', () => ({
useEntity: () => {
@@ -48,7 +49,20 @@ jest.mock('@backstage/plugin-catalog-react', () => ({
const errorApiMock = { post: jest.fn(), error$: jest.fn() };
const configApi: ConfigApi = new ConfigReader({});
const configApi: ConfigApi = new ConfigReader({
integrations: {
github: [
{
host: 'missing-api-base-url.com',
},
{
host: 'my-github.com',
apiBaseUrl: 'https://api.my-github.com',
},
],
},
});
const scmIntegrationsApi = ScmIntegrations.fromConfig(configApi);
const githubAuthApi: OAuthApi = {
getAccessToken: async _ => 'access_token',
};
@@ -56,7 +70,10 @@ const githubAuthApi: OAuthApi = {
const apis = ApiRegistry.from([
[configApiRef, configApi],
[errorApiRef, errorApiMock],
[githubDeploymentsApiRef, new GithubDeploymentsApiClient({ githubAuthApi })],
[
githubDeploymentsApiRef,
new GithubDeploymentsApiClient({ scmIntegrationsApi, githubAuthApi }),
],
]);
describe('github-deployments', () => {
@@ -159,5 +176,41 @@ describe('github-deployments', () => {
).toBeInTheDocument();
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)),
),
);
const rendered = await renderInTestApp(
<ApiProvider apis={apis}>
<GithubDeploymentsCard host="unknown-host.com" />
</ApiProvider>,
);
expect(await rendered.findByText(
'Warning: No matching GitHub integration configuration for host unknown-host.com, please check your integrations config',
)).toBeInTheDocument();
});
it('shows error when baseApiURL does not exist forr host', async () => {
worker.use(
graphql.query('deployments', (_, res, ctx) =>
res(ctx.data(responseStub)),
),
);
const rendered = await renderInTestApp(
<ApiProvider apis={apis}>
<GithubDeploymentsCard host="missing-api-base-url.com" />
</ApiProvider>,
);
expect(await rendered.findByText(
'Warning: No apiBaseUrl available for host missing-api-base-url.com',
)).toBeInTheDocument();
});
});
});
@@ -29,9 +29,11 @@ import {
import GithubDeploymentsTable from './GithubDeploymentsTable/GithubDeploymentsTable';
const GithubDeploymentsComponent = ({
host,
projectSlug,
last,
}: {
host?: string;
projectSlug: string;
last: number;
}) => {
@@ -39,7 +41,7 @@ const GithubDeploymentsComponent = ({
const [owner, repo] = projectSlug.split('/');
const { loading, value, error, retry: reload } = useAsyncRetry(
async () => await api.listDeployments({ owner, repo, last }),
async () => await api.listDeployments({ host, owner, repo, last }),
);
if (error) {
@@ -55,7 +57,13 @@ const GithubDeploymentsComponent = ({
);
};
export const GithubDeploymentsCard = ({ last }: { last?: number }) => {
export const GithubDeploymentsCard = ({
last,
host,
}: {
last?: number;
host?: string;
}) => {
const { entity } = useEntity();
return !isGithubDeploymentsAvailable(entity) ? (
@@ -66,6 +74,7 @@ export const GithubDeploymentsCard = ({ last }: { last?: number }) => {
entity?.metadata.annotations?.[GITHUB_PROJECT_SLUG_ANNOTATION] || ''
}
last={last || 10}
host={host}
/>
);
};
+7 -3
View File
@@ -19,6 +19,7 @@ import {
createPlugin,
githubAuthApiRef,
} from '@backstage/core';
import { scmIntegrationsApiRef } from '@backstage/integration-react';
import { githubDeploymentsApiRef, GithubDeploymentsApiClient } from './api';
export const githubDeploymentsPlugin = createPlugin({
@@ -26,9 +27,12 @@ export const githubDeploymentsPlugin = createPlugin({
apis: [
createApiFactory({
api: githubDeploymentsApiRef,
deps: { githubAuthApi: githubAuthApiRef },
factory: ({ githubAuthApi }) =>
new GithubDeploymentsApiClient({ githubAuthApi }),
deps: {
scmIntegrationsApi: scmIntegrationsApiRef,
githubAuthApi: githubAuthApiRef,
},
factory: ({ scmIntegrationsApi, githubAuthApi }) =>
new GithubDeploymentsApiClient({ scmIntegrationsApi, githubAuthApi }),
}),
],
});