Merge remote-tracking branch 'upstream/master' into gh-3811-repo-url-file-registration

This commit is contained in:
Jussi Hallila
2021-01-18 19:30:22 +01:00
36 changed files with 425 additions and 197 deletions
+1 -1
View File
@@ -51,7 +51,7 @@ export interface Config {
cert?: string;
privateKey?: string;
decryptionPvk?: string;
signatureAlgorithm?: 'sha1' | 'sha256' | 'sha512';
signatureAlgorithm?: 'sha256' | 'sha512';
digestAlgorithm?: string;
};
okta?: {
+3 -1
View File
@@ -58,7 +58,7 @@
"passport-oauth2": "^1.5.0",
"passport-okta-oauth": "^0.0.1",
"passport-onelogin-oauth": "^0.0.1",
"passport-saml": "^1.3.5, <1.4.0",
"passport-saml": "^2.0.0",
"uuid": "^8.0.0",
"winston": "^3.2.1",
"yn": "^4.0.0"
@@ -74,6 +74,8 @@
"@types/passport-google-oauth20": "^2.0.3",
"@types/passport-microsoft": "^0.0.0",
"@types/passport-saml": "^1.1.2",
"@types/passport-strategy": "^0.2.35",
"@types/xml2js": "^0.4.7",
"msw": "^0.21.2",
"nock": "^13.0.5"
},
@@ -15,8 +15,8 @@
*/
import express from 'express';
import { SamlConfig } from 'passport-saml/lib/passport-saml/types';
import {
SamlConfig,
Strategy as SamlStrategy,
Profile as SamlProfile,
VerifyWithoutRequest,
+1 -1
View File
@@ -34,7 +34,7 @@
"@backstage/backend-common": "^0.4.2",
"@backstage/catalog-model": "^0.6.0",
"@backstage/config": "^0.1.2",
"@octokit/graphql": "^4.5.6",
"@octokit/graphql": "^4.5.8",
"@types/express": "^4.17.6",
"@types/ldapjs": "^1.0.9",
"codeowners-utils": "^1.0.2",
+1 -1
View File
@@ -38,7 +38,7 @@
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"@octokit/rest": "^18.0.6",
"@octokit/rest": "^18.0.12",
"git-url-parse": "^11.4.3",
"react": "^16.13.1",
"react-dom": "^16.13.1",
-2
View File
@@ -37,8 +37,6 @@
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"@octokit/rest": "^18.0.0",
"@octokit/types": "^5.4.1",
"moment": "^2.27.0",
"qs": "^6.9.4",
"react": "^16.13.1",
+1 -2
View File
@@ -40,8 +40,7 @@
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"@octokit/rest": "^18.0.0",
"@octokit/types": "^5.4.1",
"@octokit/rest": "^18.0.12",
"moment": "^2.27.0",
"react": "^16.13.1",
"react-dom": "^16.13.1",
@@ -15,12 +15,7 @@
*/
import { createApiRef } from '@backstage/core';
import {
ActionsListWorkflowRunsForRepoResponseData,
ActionsGetWorkflowResponseData,
ActionsGetWorkflowRunResponseData,
EndpointInterface,
} from '@octokit/types';
import { RestEndpointMethodTypes } from '@octokit/rest';
export const githubActionsApiRef = createApiRef<GithubActionsApi>({
id: 'plugin.githubactions.service',
@@ -42,7 +37,9 @@ export type GithubActionsApi = {
pageSize?: number;
page?: number;
branch?: string;
}) => Promise<ActionsListWorkflowRunsForRepoResponseData>;
}) => Promise<
RestEndpointMethodTypes['actions']['listWorkflowRuns']['response']['data']
>;
getWorkflow: ({
token,
owner,
@@ -53,7 +50,9 @@ export type GithubActionsApi = {
owner: string;
repo: string;
id: number;
}) => Promise<ActionsGetWorkflowResponseData>;
}) => Promise<
RestEndpointMethodTypes['actions']['getWorkflow']['response']['data']
>;
getWorkflowRun: ({
token,
owner,
@@ -64,7 +63,9 @@ export type GithubActionsApi = {
owner: string;
repo: string;
id: number;
}) => Promise<ActionsGetWorkflowRunResponseData>;
}) => Promise<
RestEndpointMethodTypes['actions']['getWorkflowRun']['response']['data']
>;
reRunWorkflow: ({
token,
owner,
@@ -86,5 +87,7 @@ export type GithubActionsApi = {
owner: string;
repo: string;
runId: number;
}) => Promise<EndpointInterface>;
}) => Promise<
RestEndpointMethodTypes['actions']['downloadJobLogsForWorkflowRun']['response']['data']
>;
};
@@ -15,13 +15,7 @@
*/
import { GithubActionsApi } from './GithubActionsApi';
import { Octokit } from '@octokit/rest';
import {
ActionsListWorkflowRunsForRepoResponseData,
ActionsGetWorkflowResponseData,
ActionsGetWorkflowRunResponseData,
EndpointInterface,
} from '@octokit/types';
import { Octokit, RestEndpointMethodTypes } from '@octokit/rest';
export class GithubActionsClient implements GithubActionsApi {
async reRunWorkflow({
@@ -55,7 +49,9 @@ export class GithubActionsClient implements GithubActionsApi {
pageSize?: number;
page?: number;
branch?: string;
}): Promise<ActionsListWorkflowRunsForRepoResponseData> {
}): Promise<
RestEndpointMethodTypes['actions']['listWorkflowRuns']['response']['data']
> {
const workflowRuns = await new Octokit({
auth: token,
}).actions.listWorkflowRunsForRepo({
@@ -77,7 +73,9 @@ export class GithubActionsClient implements GithubActionsApi {
owner: string;
repo: string;
id: number;
}): Promise<ActionsGetWorkflowResponseData> {
}): Promise<
RestEndpointMethodTypes['actions']['getWorkflow']['response']['data']
> {
const workflow = await new Octokit({ auth: token }).actions.getWorkflow({
owner,
repo,
@@ -95,7 +93,9 @@ export class GithubActionsClient implements GithubActionsApi {
owner: string;
repo: string;
id: number;
}): Promise<ActionsGetWorkflowRunResponseData> {
}): Promise<
RestEndpointMethodTypes['actions']['getWorkflowRun']['response']['data']
> {
const run = await new Octokit({ auth: token }).actions.getWorkflowRun({
owner,
repo,
@@ -113,7 +113,9 @@ export class GithubActionsClient implements GithubActionsApi {
owner: string;
repo: string;
runId: number;
}): Promise<EndpointInterface> {
}): Promise<
RestEndpointMethodTypes['actions']['downloadJobLogsForWorkflowRun']['response']['data']
> {
const workflow = await new Octokit({
auth: token,
}).actions.downloadJobLogsForWorkflowRun({
@@ -209,8 +209,8 @@ export const WorkflowRunDetails = ({ entity }: { entity: Entity }) => {
</TableCell>
<TableCell>
<WorkflowRunStatus
status={details.value?.status}
conclusion={details.value?.conclusion}
status={details.value?.status || undefined}
conclusion={details.value?.conclusion || undefined}
/>
</TableCell>
</TableRow>
@@ -218,7 +218,7 @@ export const WorkflowRunDetails = ({ entity }: { entity: Entity }) => {
<TableCell>
<Typography noWrap>Author</Typography>
</TableCell>
<TableCell>{`${details.value?.head_commit.author.name} (${details.value?.head_commit.author.email})`}</TableCell>
<TableCell>{`${details.value?.head_commit.author?.name} (${details.value?.head_commit.author?.email})`}</TableCell>
</TableRow>
<TableRow>
<TableCell>
@@ -18,7 +18,6 @@ import { useAsyncRetry } from 'react-use';
import { WorkflowRun } from './WorkflowRunsTable/WorkflowRunsTable';
import { githubActionsApiRef } from '../api/GithubActionsApi';
import { useApi, githubAuthApiRef, errorApiRef } from '@backstage/core';
import { ActionsListWorkflowRunsForRepoResponseData } from '@octokit/types';
export function useWorkflowRuns({
owner,
@@ -55,44 +54,40 @@ export function useWorkflowRuns({
page: page + 1,
branch,
})
.then(
(
workflowRunsData: ActionsListWorkflowRunsForRepoResponseData,
): WorkflowRun[] => {
setTotal(workflowRunsData.total_count);
// Transformation here
return workflowRunsData.workflow_runs.map(run => ({
message: run.head_commit.message,
id: `${run.id}`,
onReRunClick: async () => {
try {
await api.reRunWorkflow({
token,
owner,
repo,
runId: run.id,
});
} catch (e) {
errorApi.post(e);
}
.then((workflowRunsData): WorkflowRun[] => {
setTotal(workflowRunsData.total_count);
// Transformation here
return workflowRunsData.workflow_runs.map(run => ({
message: run.head_commit.message,
id: `${run.id}`,
onReRunClick: async () => {
try {
await api.reRunWorkflow({
token,
owner,
repo,
runId: run.id,
});
} catch (e) {
errorApi.post(e);
}
},
source: {
branchName: run.head_branch,
commit: {
hash: run.head_commit.id,
url: run.head_repository.branches_url.replace(
'{/branch}',
run.head_branch,
),
},
source: {
branchName: run.head_branch,
commit: {
hash: run.head_commit.id,
url: run.head_repository.branches_url.replace(
'{/branch}',
run.head_branch,
),
},
},
status: run.status,
conclusion: run.conclusion,
url: run.url,
githubUrl: run.html_url,
}));
},
)
},
status: run.status,
conclusion: run.conclusion,
url: run.url,
githubUrl: run.html_url,
}));
})
);
}, [page, pageSize, repo, owner]);
+50 -39
View File
@@ -12,53 +12,64 @@ By exposing GraphiQL as a plugin instead of a standalone app, it's possible to p
Start out by installing the plugin in your Backstage app:
```bash
cd packages/app
yarn add @backstage/plugin-graphiql
```
Then add an entry to your App's `plugins.ts` to import the plugin.
```diff
# in packages/app/src/plugins.ts
+export { plugin as GraphiQL } from '@backstage/plugin-graphiql';
```
The plugin registers a `/graphiql` route, which you can link to from the Sidebar if desired.
```diff
# in packages/app/src/App.tsx
+import { Router as GraphiQLRouter } from '@backstage/plugin-graphiql';
const AppRoutes = () => (
<Routes>
+ <Route path="/graphiql" element={<GraphiQLRouter />} />
```
### Adding GraphQL endpoints
For the plugin to function, you need to supply GraphQL endpoints through the GraphQLBrowse API, which is done by implementing the `GraphQLBrowseApi` exported by this plugin.
For the plugin to function, you need to supply GraphQL endpoints through the GraphQLBrowse API. This is done by implementing the `graphQlBrowseApiRef` exported by this plugin.
If all you need is a static list of endpoints, the plugin exports a `GraphQLEndpoints` class that implements the `GraphQLBrowseApi` for you. Here's and example of how you could expose two GraphQL endpoints in your App:
If all you need is a static list of endpoints, the plugin exports a `GraphQLEndpoints` class that implements the `GraphQLBrowseApi` for you. Here's an example of how you could expose two GraphQL endpoints in your App:
```tsx
import {
graphQlBrowseApiRef,
GraphQLEndpoints,
} from '@backstage/plugin-graphiql';
```diff
# in packages/app/src/apis.ts
+import {
+ graphQlBrowseApiRef,
+ GraphQLEndpoints,
+} from '@backstage/plugin-graphiql';
// Implement the Graph QL browse API using a static list of endpoints
const graphQlBrowseApi = GraphQLEndpoints.from([
// Use the .create function if all you need is a static URL and headers.
GraphQLEndpoints.create({
id: 'gitlab',
title: 'GitLab',
url: 'https://gitlab.com/api/graphql',
// Optional extra headers
headers: { Extra: 'Header' },
}),
{
id: 'hooli-search',
title: 'Hooli Search',
// Custom fetch function, this one is equivalent to using GraphQLEndpoints.create()
// with url set to https://internal.hooli.com/search
fetcher: async (params: any) => {
return fetch('https://internal.hooli.com/search', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(params),
}).then(res => res.json());
},
},
]);
// ApiRegistry builder created somewhere in your App
const builder = ApiRegistry.builder();
// Add the instance to the API registry
builder.add(graphQlBrowseApiRef, graphQlBrowseApi);
export const apis = [
+ createApiFactory({
+ api: graphQlBrowseApiRef,
+ deps: { errorApi: errorApiRef, githubAuthApi: githubAuthApiRef },
+ factory: ({ errorApi, githubAuthApi }) =>
+ GraphQLEndpoints.from([
+ // Use the .create function if all you need is a static URL and headers.
+ GraphQLEndpoints.create({
+ id: 'gitlab',
+ title: 'GitLab',
+ url: 'https://gitlab.com/api/graphql',
+ // Optional extra headers
+ headers: { Extra: 'Header' },
+ }),
+ {
+ id: 'hooli-search',
+ title: 'Hooli Search',
+ // Custom fetch function, this one is equivalent to using GraphQLEndpoints.create()
+ // with url set to https://internal.hooli.com/search
+ fetcher: async (params: any) => {
+ return fetch('https://internal.hooli.com/search', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(params),
+ }).then(res => res.json());
+ },
+ }
+ ]),
+ }),
```
+2 -1
View File
@@ -25,7 +25,8 @@ import { graphiQLRouteRef } from './route-refs';
export const plugin = createPlugin({
id: 'graphiql',
apis: [
// GitLab is used as an example endpoint, but most plug
// GitLab is used as an example endpoint, but most will want to plug in
// their own instead.
createApiFactory(
graphQlBrowseApiRef,
GraphQLEndpoints.from([
@@ -125,7 +125,9 @@ export const MembersListCard = ({
UserEntity
>).filter(member =>
member?.relations?.some(
r => r.type === RELATION_MEMBER_OF && r.target.name === groupName,
r =>
r.type === RELATION_MEMBER_OF &&
r.target.name.toLowerCase() === groupName.toLowerCase(),
),
);
return groupMembersList;
+3 -4
View File
@@ -33,9 +33,9 @@
"@backstage/catalog-model": "^0.6.0",
"@backstage/config": "^0.1.2",
"@backstage/integration": "^0.1.5",
"@gitbeaker/core": "^25.2.0",
"@gitbeaker/node": "^25.2.0",
"@octokit/rest": "^18.0.0",
"@gitbeaker/core": "^28.0.2",
"@gitbeaker/node": "^28.0.2",
"@octokit/rest": "^18.0.12",
"@types/dockerode": "^3.2.1",
"@types/express": "^4.17.6",
"azure-devops-node-api": "^10.1.1",
@@ -60,7 +60,6 @@
"devDependencies": {
"@backstage/cli": "^0.4.5",
"@backstage/test-utils": "^0.1.5",
"@octokit/types": "^5.4.1",
"@types/fs-extra": "^9.0.1",
"@types/git-url-parse": "^9.0.0",
"@types/supertest": "^2.0.8",
@@ -17,15 +17,10 @@
jest.mock('@octokit/rest');
jest.mock('./helpers');
import { Octokit } from '@octokit/rest';
import {
OctokitResponse,
ReposCreateInOrgResponseData,
UsersGetByUsernameResponseData,
} from '@octokit/types';
import { getVoidLogger } from '@backstage/backend-common';
import { Octokit, RestEndpointMethodTypes } from '@octokit/rest';
import { GithubPublisher } from './github';
import { initRepoAndPush } from './helpers';
import { getVoidLogger } from '@backstage/backend-common';
const { mockGithubClient } = require('@octokit/rest') as {
mockGithubClient: {
@@ -54,12 +49,12 @@ describe('GitHub Publisher', () => {
data: {
clone_url: 'https://github.com/backstage/backstage.git',
},
} as OctokitResponse<ReposCreateInOrgResponseData>);
} as RestEndpointMethodTypes['repos']['createInOrg']['response']);
mockGithubClient.users.getByUsername.mockResolvedValue({
data: {
type: 'Organization',
},
} as OctokitResponse<UsersGetByUsernameResponseData>);
} as RestEndpointMethodTypes['users']['getByUsername']['response']);
const result = await publisher.publish({
values: {
@@ -104,12 +99,12 @@ describe('GitHub Publisher', () => {
data: {
clone_url: 'https://github.com/backstage/backstage.git',
},
} as OctokitResponse<ReposCreateInOrgResponseData>);
} as RestEndpointMethodTypes['repos']['createForAuthenticatedUser']['response']);
mockGithubClient.users.getByUsername.mockResolvedValue({
data: {
type: 'User',
},
} as OctokitResponse<UsersGetByUsernameResponseData>);
} as RestEndpointMethodTypes['users']['getByUsername']['response']);
const result = await publisher.publish({
values: {
@@ -148,12 +143,12 @@ describe('GitHub Publisher', () => {
data: {
clone_url: 'https://github.com/backstage/backstage.git',
},
} as OctokitResponse<ReposCreateInOrgResponseData>);
} as RestEndpointMethodTypes['repos']['createForAuthenticatedUser']['response']);
mockGithubClient.users.getByUsername.mockResolvedValue({
data: {
type: 'User',
},
} as OctokitResponse<UsersGetByUsernameResponseData>);
} as RestEndpointMethodTypes['users']['getByUsername']['response']);
const result = await publisher.publish({
values: {
@@ -205,12 +200,12 @@ describe('GitHub Publisher', () => {
data: {
clone_url: 'https://github.com/backstage/backstage.git',
},
} as OctokitResponse<ReposCreateInOrgResponseData>);
} as RestEndpointMethodTypes['repos']['createInOrg']['response']);
mockGithubClient.users.getByUsername.mockResolvedValue({
data: {
type: 'Organization',
},
} as OctokitResponse<UsersGetByUsernameResponseData>);
} as RestEndpointMethodTypes['users']['getByUsername']['response']);
const result = await publisher.publish({
values: {
@@ -254,12 +249,12 @@ describe('GitHub Publisher', () => {
data: {
clone_url: 'https://github.com/backstage/backstage.git',
},
} as OctokitResponse<ReposCreateInOrgResponseData>);
} as RestEndpointMethodTypes['repos']['createForAuthenticatedUser']['response']);
mockGithubClient.users.getByUsername.mockResolvedValue({
data: {
type: 'User',
},
} as OctokitResponse<UsersGetByUsernameResponseData>);
} as RestEndpointMethodTypes['users']['getByUsername']['response']);
const result = await publisher.publish({
values: {
@@ -115,7 +115,15 @@ export const Reader = ({ entityId, onReady }: Props) => {
baseUrl: window.location.origin,
onClick: (_: MouseEvent, url: string) => {
const parsedUrl = new URL(url);
navigate(`${parsedUrl.pathname}${parsedUrl.hash}`);
if (parsedUrl.hash) {
history.pushState(
null,
'',
`${parsedUrl.pathname}${parsedUrl.hash}`,
);
} else {
navigate(parsedUrl.pathname);
}
shadowRoot?.querySelector(parsedUrl.hash)?.scrollIntoView();
},