Merge pull request #1425 from spotify/rugvip/graphiql

graphiql: add GitHub endpoint and add to App
This commit is contained in:
Patrik Oldsberg
2020-06-24 10:25:10 +02:00
committed by GitHub
8 changed files with 180 additions and 64 deletions
+1
View File
@@ -9,6 +9,7 @@
"@backstage/plugin-circleci": "^0.1.1-alpha.12",
"@backstage/plugin-explore": "^0.1.1-alpha.12",
"@backstage/plugin-gitops-profiles": "^0.1.1-alpha.12",
"@backstage/plugin-graphiql": "^0.1.1-alpha.12",
"@backstage/plugin-lighthouse": "^0.1.1-alpha.12",
"@backstage/plugin-register-component": "^0.1.1-alpha.12",
"@backstage/plugin-scaffolder": "^0.1.1-alpha.12",
+22 -1
View File
@@ -45,6 +45,10 @@ import { CircleCIApi, circleCIApiRef } from '@backstage/plugin-circleci';
import { catalogApiRef, CatalogClient } from '@backstage/plugin-catalog';
import { gitOpsApiRef, GitOpsRestApi } from '@backstage/plugin-gitops-profiles';
import {
graphQlBrowseApiRef,
GraphQLEndpoints,
} from '@backstage/plugin-graphiql';
export const apis = (config: ConfigApi) => {
// eslint-disable-next-line no-console
@@ -78,7 +82,7 @@ export const apis = (config: ConfigApi) => {
}),
);
builder.add(
const githubAuthApi = builder.add(
githubAuthApiRef,
GithubAuth.create({
apiOrigin: 'http://localhost:7000',
@@ -105,5 +109,22 @@ export const apis = (config: ConfigApi) => {
builder.add(gitOpsApiRef, new GitOpsRestApi('http://localhost:3008'));
builder.add(
graphQlBrowseApiRef,
GraphQLEndpoints.from([
GraphQLEndpoints.create({
id: 'gitlab',
title: 'GitLab',
url: 'https://gitlab.com/api/graphql',
}),
GraphQLEndpoints.github({
id: 'github',
title: 'GitHub',
errorApi,
githubAuthApi,
}),
]),
);
return builder.build();
};
@@ -39,6 +39,7 @@ import {
SidebarPinButton,
} from '@backstage/core';
import { NavLink } from 'react-router-dom';
import { graphiQLRouteRef } from '@backstage/plugin-graphiql';
const useSidebarLogoStyles = makeStyles({
root: {
@@ -94,6 +95,11 @@ const Root: FC<{}> = ({ children }) => (
<SidebarItem icon={MapIcon} to="tech-radar" text="Tech Radar" />
<SidebarItem icon={RuleIcon} to="lighthouse" text="Lighthouse" />
<SidebarItem icon={BuildIcon} to="circleci" text="CircleCI" />
<SidebarItem
icon={graphiQLRouteRef.icon!}
to={graphiQLRouteRef.path}
text={graphiQLRouteRef.title}
/>
<SidebarSpace />
<SidebarDivider />
<SidebarThemeToggle />
+1
View File
@@ -24,3 +24,4 @@ export { plugin as RegisterComponent } from '@backstage/plugin-register-componen
export { plugin as Sentry } from '@backstage/plugin-sentry';
export { plugin as GitopsProfiles } from '@backstage/plugin-gitops-profiles';
export { plugin as TechDocs } from '@backstage/plugin-techdocs';
export { plugin as GraphiQL } from '@backstage/plugin-graphiql';
@@ -24,6 +24,10 @@ import {
AlertApiForwarder,
oauthRequestApiRef,
OAuthRequestManager,
GoogleAuth,
googleAuthApiRef,
GithubAuth,
githubAuthApiRef,
} from '@backstage/core';
// TODO(rugvip): We should likely figure out how to reuse all of these between apps
@@ -49,3 +53,25 @@ export const oauthRequestApiFactory = createApiFactory({
deps: {},
factory: () => new OAuthRequestManager(),
});
export const googleAuthApiFactory = createApiFactory({
implements: googleAuthApiRef,
deps: { oauthRequestApi: oauthRequestApiRef },
factory: ({ oauthRequestApi }) =>
GoogleAuth.create({
apiOrigin: 'http://localhost:7000',
basePath: '/auth/',
oauthRequestApi,
}),
});
export const githubAuthApiFactory = createApiFactory({
implements: githubAuthApiRef,
deps: { oauthRequestApi: oauthRequestApiRef },
factory: ({ oauthRequestApi }) =>
GithubAuth.create({
apiOrigin: 'http://localhost:7000',
basePath: '/auth/',
oauthRequestApi,
}),
});
+12 -2
View File
@@ -15,15 +15,25 @@
*/
import { createDevApp } from '@backstage/dev-utils';
import { githubAuthApiRef, errorApiRef } from '@backstage/core';
import { plugin, GraphQLEndpoints, graphQlBrowseApiRef } from '../src';
createDevApp()
.registerPlugin(plugin)
.registerApiFactory({
implements: graphQlBrowseApiRef,
deps: {},
factory() {
deps: {
errorApi: errorApiRef,
githubAuthApi: githubAuthApiRef,
},
factory({ errorApi, githubAuthApi }) {
return GraphQLEndpoints.from([
GraphQLEndpoints.github({
id: 'github',
title: 'GitHub',
errorApi,
githubAuthApi,
}),
GraphQLEndpoints.create({
id: 'gitlab',
title: 'GitLab',
+16 -61
View File
@@ -1,64 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<g id="surface1">
<path
fill="#E535AB"
d="M 3.449219 18.160156 L 2.585938 17.660156 L 12.195312 1.019531 L 13.058594 1.515625 Z M 3.449219 18.160156 "
/>
<path
fill="#E535AB"
d="M 2.386719 16.332031 L 21.605469 16.332031 L 21.605469 17.328125 L 2.386719 17.328125 Z M 2.386719 16.332031 "
/>
<path
fill="#E535AB"
d="M 12.382812 22.441406 L 2.769531 16.890625 L 3.265625 16.027344 L 12.878906 21.578125 Z M 12.382812 22.441406 "
/>
<path
fill="#E535AB"
d="M 20.730469 7.976562 L 11.117188 2.425781 L 11.617188 1.5625 L 21.230469 7.113281 Z M 20.730469 7.976562 "
/>
<path
fill="#E535AB"
d="M 3.269531 7.972656 L 2.769531 7.109375 L 12.382812 1.558594 L 12.882812 2.421875 Z M 3.269531 7.972656 "
/>
<path
fill="#E535AB"
d="M 20.554688 18.160156 L 10.945312 1.515625 L 11.808594 1.019531 L 21.417969 17.660156 Z M 20.554688 18.160156 "
/>
<path
fill="#E535AB"
d="M 3.148438 6.449219 L 4.144531 6.449219 L 4.144531 17.550781 L 3.148438 17.550781 Z M 3.148438 6.449219 "
/>
<path
fill="#E535AB"
d="M 19.855469 6.449219 L 20.851562 6.449219 L 20.851562 17.550781 L 19.855469 17.550781 Z M 19.855469 6.449219 "
/>
<path
fill="#E535AB"
d="M 12.210938 22.019531 L 11.777344 21.265625 L 20.136719 16.441406 L 20.570312 17.191406 Z M 12.210938 22.019531 "
/>
<path
fill="#E535AB"
d="M 22.171875 17.875 C 21.59375 18.875 20.308594 19.21875 19.308594 18.640625 C 18.304688 18.066406 17.964844 16.78125 18.539062 15.78125 C 19.117188 14.777344 20.398438 14.4375 21.402344 15.011719 C 22.410156 15.59375 22.753906 16.871094 22.171875 17.875 "
/>
<path
fill="#E535AB"
d="M 5.453125 8.21875 C 4.878906 9.222656 3.59375 9.5625 2.59375 8.988281 C 1.589844 8.410156 1.246094 7.128906 1.824219 6.125 C 2.398438 5.125 3.683594 4.78125 4.6875 5.359375 C 5.6875 5.941406 6.03125 7.21875 5.453125 8.21875 "
/>
<path
fill="#E535AB"
d="M 1.828125 17.875 C 1.253906 16.871094 1.597656 15.59375 2.597656 15.011719 C 3.601562 14.4375 4.878906 14.777344 5.460938 15.78125 C 6.035156 16.78125 5.695312 18.058594 4.691406 18.640625 C 3.683594 19.21875 2.40625 18.875 1.828125 17.875 "
/>
<path
fill="#E535AB"
d="M 18.546875 8.21875 C 17.96875 7.21875 18.3125 5.941406 19.3125 5.359375 C 20.316406 4.78125 21.59375 5.125 22.175781 6.125 C 22.753906 7.128906 22.410156 8.40625 21.40625 8.988281 C 20.40625 9.5625 19.121094 9.222656 18.546875 8.21875 "
/>
<path
fill="#E535AB"
d="M 12 23.746094 C 10.84375 23.746094 9.90625 22.8125 9.90625 21.652344 C 9.90625 20.496094 10.84375 19.558594 12 19.558594 C 13.15625 19.558594 14.09375 20.496094 14.09375 21.652344 C 14.09375 22.804688 13.15625 23.746094 12 23.746094 "
/>
<path
fill="#E535AB"
d="M 12 4.441406 C 10.84375 4.441406 9.90625 3.503906 9.90625 2.347656 C 9.90625 1.1875 10.84375 0.253906 12 0.253906 C 13.15625 0.253906 14.09375 1.1875 14.09375 2.347656 C 14.09375 3.503906 13.15625 4.441406 12 4.441406 "
/>
<g>
<path d="M 3.449219 18.160156 L 2.585938 17.660156 L 12.195312 1.019531 L 13.058594 1.515625 Z M 3.449219 18.160156" />
<path d="M 2.386719 16.332031 L 21.605469 16.332031 L 21.605469 17.328125 L 2.386719 17.328125 Z M 2.386719 16.332031" />
<path d="M 12.382812 22.441406 L 2.769531 16.890625 L 3.265625 16.027344 L 12.878906 21.578125 Z M 12.382812 22.441406" />
<path d="M 20.730469 7.976562 L 11.117188 2.425781 L 11.617188 1.5625 L 21.230469 7.113281 Z M 20.730469 7.976562" />
<path d="M 3.269531 7.972656 L 2.769531 7.109375 L 12.382812 1.558594 L 12.882812 2.421875 Z M 3.269531 7.972656" />
<path d="M 20.554688 18.160156 L 10.945312 1.515625 L 11.808594 1.019531 L 21.417969 17.660156 Z M 20.554688 18.160156" />
<path d="M 3.148438 6.449219 L 4.144531 6.449219 L 4.144531 17.550781 L 3.148438 17.550781 Z M 3.148438 6.449219" />
<path d="M 19.855469 6.449219 L 20.851562 6.449219 L 20.851562 17.550781 L 19.855469 17.550781 Z M 19.855469 6.449219" />
<path d="M 12.210938 22.019531 L 11.777344 21.265625 L 20.136719 16.441406 L 20.570312 17.191406 Z M 12.210938 22.019531" />
<path d="M 22.171875 17.875 C 21.59375 18.875 20.308594 19.21875 19.308594 18.640625 C 18.304688 18.066406 17.964844 16.78125 18.539062 15.78125 C 19.117188 14.777344 20.398438 14.4375 21.402344 15.011719 C 22.410156 15.59375 22.753906 16.871094 22.171875 17.875" />
<path d="M 5.453125 8.21875 C 4.878906 9.222656 3.59375 9.5625 2.59375 8.988281 C 1.589844 8.410156 1.246094 7.128906 1.824219 6.125 C 2.398438 5.125 3.683594 4.78125 4.6875 5.359375 C 5.6875 5.941406 6.03125 7.21875 5.453125 8.21875" />
<path d="M 1.828125 17.875 C 1.253906 16.871094 1.597656 15.59375 2.597656 15.011719 C 3.601562 14.4375 4.878906 14.777344 5.460938 15.78125 C 6.035156 16.78125 5.695312 18.058594 4.691406 18.640625 C 3.683594 19.21875 2.40625 18.875 1.828125 17.875" />
<path d="M 18.546875 8.21875 C 17.96875 7.21875 18.3125 5.941406 19.3125 5.359375 C 20.316406 4.78125 21.59375 5.125 22.175781 6.125 C 22.753906 7.128906 22.410156 8.40625 21.40625 8.988281 C 20.40625 9.5625 19.121094 9.222656 18.546875 8.21875" />
<path d="M 12 23.746094 C 10.84375 23.746094 9.90625 22.8125 9.90625 21.652344 C 9.90625 20.496094 10.84375 19.558594 12 19.558594 C 13.15625 19.558594 14.09375 20.496094 14.09375 21.652344 C 14.09375 22.804688 13.15625 23.746094 12 23.746094" />
<path d="M 12 4.441406 C 10.84375 4.441406 9.90625 3.503906 9.90625 2.347656 C 9.90625 1.1875 10.84375 0.253906 12 0.253906 C 13.15625 0.253906 14.09375 1.1875 14.09375 2.347656 C 14.09375 3.503906 13.15625 4.441406 12 4.441406" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

@@ -15,6 +15,7 @@
*/
import { GraphQLBrowseApi, GraphQLEndpoint } from './types';
import { ErrorApi, OAuthApi } from '@backstage/core';
// Helper for generic http endpoints
export type EndpointConfig = {
@@ -28,6 +29,23 @@ export type EndpointConfig = {
headers?: { [name in string]: string };
};
export type GithubEndpointConfig = {
id: string;
title: string;
/**
* Github GraphQL API url, defaults to https://api.github.com/graphql
*/
url?: string;
/**
* Errors will be posted to the ErrorApi if it is provided.
*/
errorApi?: ErrorApi;
/**
* GitHub Auth API used to authenticate requests.
*/
githubAuthApi: OAuthApi;
};
export class GraphQLEndpoints implements GraphQLBrowseApi {
// Create a support
static create(config: EndpointConfig): GraphQLEndpoint {
@@ -51,6 +69,84 @@ export class GraphQLEndpoints implements GraphQLBrowseApi {
};
}
/**
* Creates a GitHub GraphQL endpoint that uses the GithubAuth API to authenticate requests.
*
* If a request requires more permissions than is granted by the existing session,
* the fetcher will automatically ask for the additional scopes that are required.
*/
static github(config: GithubEndpointConfig): GraphQLEndpoint {
const {
id,
title,
url = 'https://api.github.com/graphql',
errorApi,
githubAuthApi,
} = config;
type ResponseBody = {
errors?: Array<{ type: string; message: string }>;
};
return {
id,
title,
fetcher: async (params: any) => {
let retried = false;
const doRequest = async (): Promise<any> => {
const res = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${await githubAuthApi.getAccessToken()}`,
},
body: JSON.stringify(params),
});
if (!res.ok) {
throw new Error(
`Request failed with status ${res.status} ${res.statusText}`,
);
}
const data = (await res.json()) as ResponseBody;
if (!data.errors || retried) {
return data;
}
retried = true;
const missingScopes = data.errors
.filter(({ type }) => type === 'INSUFFICIENT_SCOPES')
.flatMap(({ message }) => {
const scopesMatch = message.match(
/one of the following scopes: (\[.*?\])/,
);
if (!scopesMatch) {
return [];
}
try {
const scopes = JSON.parse(scopesMatch[1].replace(/'/g, '"'));
return scopes;
} catch (error) {
if (errorApi) {
errorApi.post(
new Error(
`Failed to parse scope string "${scopesMatch[1]}", ${error}`,
),
);
}
return [];
}
});
await githubAuthApi.getAccessToken(missingScopes);
return doRequest();
};
return await doRequest();
},
};
}
static from(endpoints: GraphQLEndpoint[]) {
return new GraphQLEndpoints(endpoints);
}