Skip to content

Commit

Permalink
Update '@roadiehq/backstage-plugin-github-pull-requests' and '@roadie…
Browse files Browse the repository at this point in the history
…hq/backstage-plugin-github-insights' to use SCM auth instead of GitHub auth to allow multiple integrations to work at the same time.
  • Loading branch information
Xantier committed Jan 9, 2025
1 parent 5e0fdbc commit 6e4add9
Show file tree
Hide file tree
Showing 44 changed files with 819 additions and 437 deletions.
8 changes: 8 additions & 0 deletions .changeset/breezy-days-refuse.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@roadiehq/backstage-plugin-github-pull-requests': major
'@roadiehq/backstage-plugin-github-insights': major
---

BREAKING: Needs SCM auth API to be configured in the application.

Migrate to use SCM auth instead of direct GitHub to allow possibility to work with multiple GitHub integrations at once.
47 changes: 46 additions & 1 deletion packages/app/src/apis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,67 @@ import {
ScmIntegrationsApi,
scmIntegrationsApiRef,
ScmAuth,
scmAuthApiRef,
} from '@backstage/integration-react';
import {
AnyApiFactory,
ApiRef,
configApiRef,
createApiFactory,
createApiRef,
discoveryApiRef,
fetchApiRef,
githubAuthApiRef,
OAuthApi,
oauthRequestApiRef,
ProfileInfoApi,
SessionApi,
} from '@backstage/core-plugin-api';
import fetch from 'cross-fetch';
import { GithubAuth } from '@backstage/core-app-api';

const ghesAuthApiRef: ApiRef<OAuthApi & ProfileInfoApi & SessionApi> =
createApiRef({
id: 'internal.auth.ghe',
});

export const apis: AnyApiFactory[] = [
createApiFactory({
api: scmIntegrationsApiRef,
deps: { configApi: configApiRef },
factory: ({ configApi }) => ScmIntegrationsApi.fromConfig(configApi),
}),
ScmAuth.createDefaultApiFactory(),
createApiFactory({
api: ghesAuthApiRef,
deps: {
discoveryApi: discoveryApiRef,
oauthRequestApi: oauthRequestApiRef,
configApi: configApiRef,
},
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
GithubAuth.create({
configApi,
discoveryApi,
oauthRequestApi,
provider: { id: 'ghes', title: 'GitHub Enterprise', icon: () => null },
defaultScopes: ['read:user'],
environment: configApi.getOptionalString('auth.environment'),
}),
}),
createApiFactory({
api: scmAuthApiRef,
deps: {
gheAuthApi: ghesAuthApiRef,
githubAuthApi: githubAuthApiRef,
},
factory: ({ githubAuthApi, gheAuthApi }) =>
ScmAuth.merge(
ScmAuth.forGithub(githubAuthApi),
ScmAuth.forGithub(gheAuthApi, {
host: 'ghes.enginehouse.io',
}),
),
}),
createApiFactory({
api: fetchApiRef,
deps: {},
Expand Down
8 changes: 4 additions & 4 deletions packages/app/src/components/catalog/EntityPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import { EntityTechdocsContent } from '@backstage/plugin-techdocs';
import {
EntityGithubPullRequestsContent,
EntityGithubPullRequestsOverviewCard,
EntityGithubPullRequestsTable,
} from '@roadiehq/backstage-plugin-github-pull-requests';
import { EntityBitbucketPullRequestsContent } from '@roadiehq/backstage-plugin-bitbucket-pullrequest';
import {
Expand Down Expand Up @@ -168,31 +169,27 @@ const overviewContent = (
<Grid item md={6}>
<EntityAboutCard variant="gridItem" />
</Grid>

<EntitySwitch>
<EntitySwitch.Case if={isWizAvailable}>
<Grid item md={6}>
<EntityIssuesWidget />
</Grid>
</EntitySwitch.Case>
</EntitySwitch>

<EntitySwitch>
<EntitySwitch.Case if={isWizAvailable}>
<Grid item md={6}>
<EntityIssuesChart />
</Grid>
</EntitySwitch.Case>
</EntitySwitch>

<EntitySwitch>
<EntitySwitch.Case if={isWizAvailable}>
<Grid item md={6}>
<EntitySeverityChart />
</Grid>
</EntitySwitch.Case>
</EntitySwitch>

<EntitySwitch>
<EntitySwitch.Case if={isLambdaFunctionAvailable}>
<Grid item md={6}>
Expand Down Expand Up @@ -305,6 +302,9 @@ const overviewContent = (
<Grid item md={6}>
<EntityPrometheusGraphCard />
</Grid>
<Grid item md={6}>
<EntityGithubPullRequestsTable />
</Grid>
</Grid>
);

Expand Down
4 changes: 4 additions & 0 deletions packages/backend/src/plugins/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import { createRouter } from '@backstage/plugin-auth-backend';
import { Router } from 'express';
import { PluginEnvironment } from '../types';
import { providers } from '@backstage/plugin-auth-backend';

export default async function createPlugin({
logger,
Expand All @@ -30,5 +31,8 @@ export default async function createPlugin({
database,
discovery,
tokenManager,
providerFactories: {
ghes: providers.github.create(),
},
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"@backstage/catalog-model": "^1.7.1",
"@backstage/core-components": "^0.16.1",
"@backstage/core-plugin-api": "^1.10.1",
"@backstage/integration": "^1.16.0",
"@backstage/integration-react": "^1.2.1",
"@backstage/plugin-catalog-react": "^1.14.2",
"@backstage/theme": "^0.6.2",
Expand All @@ -60,8 +61,8 @@
"git-url-parse": "^14.0.0",
"history": "^5.0.0",
"immer": "9.0.7",
"zustand": "3.6.9",
"react-use": "^17.2.4"
"react-use": "^17.2.4",
"zustand": "3.6.9"
},
"peerDependencies": {
"react": "^16.13.1 || ^17.0.0 || ^18.0.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@
*/

import { GithubApi } from './GithubApi';
import { OAuthApi } from '@backstage/core-plugin-api';
import { ConfigApi } from '@backstage/core-plugin-api';
import { Octokit } from '@octokit/rest';
import parseGitUrl from 'git-url-parse';
import { MarkdownContentProps } from '../components/Widgets/MarkdownContent/types';
import { readGithubIntegrationConfigs } from '@backstage/integration';
import { ScmAuthApi } from '@backstage/integration-react';

const mimeTypeMap: Record<string, string> = {
svg: 'image/svg+xml',
Expand Down Expand Up @@ -79,10 +81,29 @@ const combinePaths = (readmePath: string, relativePath: string): string => {
};

export class GithubClient implements GithubApi {
private githubAuthApi: OAuthApi;
private readonly configApi: ConfigApi;
private readonly scmAuthApi: ScmAuthApi;

constructor(deps: { githubAuthApi: OAuthApi }) {
this.githubAuthApi = deps.githubAuthApi;
constructor(options: { configApi: ConfigApi; scmAuthApi: ScmAuthApi }) {
this.configApi = options.configApi;
this.scmAuthApi = options.scmAuthApi;
}

private async getOctokit(hostname: string = 'github.com'): Promise<Octokit> {
const { token } = await this.scmAuthApi.getCredentials({
url: `https://${hostname}/`,
additionalScope: {
customScopes: {
github: ['repo'],
},
},
});
const configs = readGithubIntegrationConfigs(
this.configApi.getOptionalConfigArray('integrations.github') ?? [],
);
const githubIntegrationConfig = configs.find(v => v.host === hostname);
const baseUrl = githubIntegrationConfig?.apiBaseUrl;
return new Octokit({ auth: token, baseUrl });
}

async getContent(props: MarkdownContentProps): Promise<{
Expand All @@ -97,8 +118,16 @@ export class GithubClient implements GithubApi {
branch,
baseUrl = defaultBaseUrl,
} = props;
const token = await this.githubAuthApi.getAccessToken();
const octokit = new Octokit({ auth: token, baseUrl });

let hostname = baseUrl ?? 'github.com';
try {
const u = new URL(hostname);
hostname = `${u.protocol}//${u.host}`;
} catch (e) {
// ignored
}

const octokit = await this.getOctokit(hostname);

let query = 'readme';
if (customReadmePath) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021 Larder Software Limited
* Copyright 2025 Larder Software Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -19,34 +19,24 @@ import { render } from '@testing-library/react';
import { InsightsPage } from './InsightsPage';
import { ThemeProvider } from '@material-ui/core';
import { lightTheme } from '@backstage/theme';
import { wrapInTestApp, TestApiProvider } from '@backstage/test-utils';
import { TestApiProvider, wrapInTestApp } from '@backstage/test-utils';
import { EntityProvider } from '@backstage/plugin-catalog-react';

import { AnyApiRef, githubAuthApiRef } from '@backstage/core-plugin-api';
import { AnyApiRef } from '@backstage/core-plugin-api';

import { ConfigReader } from '@backstage/core-app-api';
import {
GithubAuth,
OAuthRequestManager,
UrlPatternDiscovery,
ConfigReader,
} from '@backstage/core-app-api';
import {
scmIntegrationsApiRef,
scmAuthApiRef,
ScmIntegrationsApi,
scmIntegrationsApiRef,
} from '@backstage/integration-react';
import { defaultIntegrationsConfig } from '../../mocks/scmIntegrationsApiMock';

const oauthRequestApi = new OAuthRequestManager();
const mockScmAuth = {
getCredentials: async () => ({ token: 'test-token' }),
};
const apis: [AnyApiRef, Partial<unknown>][] = [
[
githubAuthApiRef,
GithubAuth.create({
discoveryApi: UrlPatternDiscovery.compile(
'http://example.com/{{pluginId}}',
),
oauthRequestApi,
}),
],
[scmAuthApiRef, mockScmAuth],
[
scmIntegrationsApiRef,
ScmIntegrationsApi.fromConfig(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021 Larder Software Limited
* Copyright 2025 Larder Software Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -16,13 +16,13 @@

import React from 'react';
import { render, screen } from '@testing-library/react';
import { AnyApiRef, githubAuthApiRef } from '@backstage/core-plugin-api';
import { AnyApiRef } from '@backstage/core-plugin-api';
import { ConfigReader } from '@backstage/core-app-api';
import { rest } from 'msw';
import {
setupRequestMockHandlers,
wrapInTestApp,
TestApiProvider,
wrapInTestApp,
} from '@backstage/test-utils';
import { setupServer } from 'msw/node';
import {
Expand All @@ -35,17 +35,18 @@ import { lightTheme } from '@backstage/theme';
import ComplianceCard from '.';
import { EntityProvider } from '@backstage/plugin-catalog-react';
import {
scmIntegrationsApiRef,
scmAuthApiRef,
ScmIntegrationsApi,
scmIntegrationsApiRef,
} from '@backstage/integration-react';
import { defaultIntegrationsConfig } from '../../../mocks/scmIntegrationsApiMock';

const mockGithubAuth = {
getAccessToken: async (_: string[]) => 'test-token',
const mockScmAuth = {
getCredentials: async () => ({ token: 'test-token' }),
};

const apis: [AnyApiRef, Partial<unknown>][] = [
[githubAuthApiRef, mockGithubAuth],
[scmAuthApiRef, mockScmAuth],
[
scmIntegrationsApiRef,
ScmIntegrationsApi.fromConfig(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021 Larder Software Limited
* Copyright 2025 Larder Software Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -16,13 +16,13 @@

import React from 'react';
import { render, screen } from '@testing-library/react';
import { AnyApiRef, githubAuthApiRef } from '@backstage/core-plugin-api';
import { AnyApiRef } from '@backstage/core-plugin-api';
import { ConfigReader } from '@backstage/core-app-api';
import { rest } from 'msw';
import {
setupRequestMockHandlers,
wrapInTestApp,
TestApiProvider,
wrapInTestApp,
} from '@backstage/test-utils';
import { setupServer } from 'msw/node';
import { contributorsResponseMock, entityMock } from '../../../mocks/mocks';
Expand All @@ -31,23 +31,18 @@ import { lightTheme } from '@backstage/theme';
import { ContributorsCard } from '..';
import { EntityProvider } from '@backstage/plugin-catalog-react';
import {
scmIntegrationsApiRef,
scmAuthApiRef,
ScmIntegrationsApi,
scmIntegrationsApiRef,
} from '@backstage/integration-react';
import { defaultIntegrationsConfig } from '../../../mocks/scmIntegrationsApiMock';

const mockGithubAuth = {
getAccessToken: async (_: string[]) => 'test-token',
sessionState$: jest.fn(() => ({
subscribe: (fn: (a: string) => void) => {
fn('SignedIn');
return { unsubscribe: jest.fn() };
},
})),
const mockScmAuth = {
getCredentials: async () => ({ token: 'test-token' }),
};

const apis: [AnyApiRef, Partial<unknown>][] = [
[githubAuthApiRef, mockGithubAuth],
[scmAuthApiRef, mockScmAuth],
[
scmIntegrationsApiRef,
ScmIntegrationsApi.fromConfig(
Expand Down
Loading

0 comments on commit 6e4add9

Please sign in to comment.