diff --git a/plugins/github-deployments/package.json b/plugins/github-deployments/package.json index b8761c1385..528d660ea8 100644 --- a/plugins/github-deployments/package.json +++ b/plugins/github-deployments/package.json @@ -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", diff --git a/plugins/github-deployments/src/api/index.ts b/plugins/github-deployments/src/api/index.ts index ca69ac3853..f239af9ef8 100644 --- a/plugins/github-deployments/src/api/index.ts +++ b/plugins/github-deployments/src/api/index.ts @@ -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({ 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 { + const baseUrl = getBaseUrl(this.scmIntegrationsApi, options.host); const token = await this.githubAuthApi.getAccessToken(['repo']); const graphQLWithAuth = graphql.defaults({ + baseUrl, headers: { authorization: `token ${token}`, }, diff --git a/plugins/github-deployments/src/components/GithubDeploymentsCard.test.tsx b/plugins/github-deployments/src/components/GithubDeploymentsCard.test.tsx index e20183f1d6..db0092caa8 100644 --- a/plugins/github-deployments/src/components/GithubDeploymentsCard.test.tsx +++ b/plugins/github-deployments/src/components/GithubDeploymentsCard.test.tsx @@ -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( + + + , + ); + + 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( + + + , + ); + + expect(await rendered.findByText( + 'Warning: No apiBaseUrl available for host missing-api-base-url.com', + )).toBeInTheDocument(); + }); }); }); diff --git a/plugins/github-deployments/src/components/GithubDeploymentsCard.tsx b/plugins/github-deployments/src/components/GithubDeploymentsCard.tsx index 99dfba560a..691c63fcc3 100644 --- a/plugins/github-deployments/src/components/GithubDeploymentsCard.tsx +++ b/plugins/github-deployments/src/components/GithubDeploymentsCard.tsx @@ -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} /> ); }; diff --git a/plugins/github-deployments/src/plugin.ts b/plugins/github-deployments/src/plugin.ts index 3707b1cf68..943d5e0bc6 100644 --- a/plugins/github-deployments/src/plugin.ts +++ b/plugins/github-deployments/src/plugin.ts @@ -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 }), }), ], });