Support GHE
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-github-actions': minor
|
||||
---
|
||||
|
||||
Support GHE
|
||||
@@ -34,6 +34,7 @@
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.6.1",
|
||||
"@backstage/core": "^0.4.4",
|
||||
"@backstage/integration": "^0.2.0",
|
||||
"@backstage/plugin-catalog": "^0.2.11",
|
||||
"@backstage/theme": "^0.2.2",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
|
||||
@@ -24,14 +24,14 @@ export const githubActionsApiRef = createApiRef<GithubActionsApi>({
|
||||
|
||||
export type GithubActionsApi = {
|
||||
listWorkflowRuns: ({
|
||||
token,
|
||||
hostname,
|
||||
owner,
|
||||
repo,
|
||||
pageSize,
|
||||
page,
|
||||
branch,
|
||||
}: {
|
||||
token: string;
|
||||
hostname?: string;
|
||||
owner: string;
|
||||
repo: string;
|
||||
pageSize?: number;
|
||||
@@ -41,12 +41,12 @@ export type GithubActionsApi = {
|
||||
RestEndpointMethodTypes['actions']['listWorkflowRuns']['response']['data']
|
||||
>;
|
||||
getWorkflow: ({
|
||||
token,
|
||||
hostname,
|
||||
owner,
|
||||
repo,
|
||||
id,
|
||||
}: {
|
||||
token: string;
|
||||
hostname?: string;
|
||||
owner: string;
|
||||
repo: string;
|
||||
id: number;
|
||||
@@ -54,12 +54,12 @@ export type GithubActionsApi = {
|
||||
RestEndpointMethodTypes['actions']['getWorkflow']['response']['data']
|
||||
>;
|
||||
getWorkflowRun: ({
|
||||
token,
|
||||
hostname,
|
||||
owner,
|
||||
repo,
|
||||
id,
|
||||
}: {
|
||||
token: string;
|
||||
hostname?: string;
|
||||
owner: string;
|
||||
repo: string;
|
||||
id: number;
|
||||
@@ -67,23 +67,23 @@ export type GithubActionsApi = {
|
||||
RestEndpointMethodTypes['actions']['getWorkflowRun']['response']['data']
|
||||
>;
|
||||
reRunWorkflow: ({
|
||||
token,
|
||||
hostname,
|
||||
owner,
|
||||
repo,
|
||||
runId,
|
||||
}: {
|
||||
token: string;
|
||||
hostname?: string;
|
||||
owner: string;
|
||||
repo: string;
|
||||
runId: number;
|
||||
}) => Promise<any>;
|
||||
downloadJobLogsForWorkflowRun: ({
|
||||
token,
|
||||
hostname,
|
||||
owner,
|
||||
repo,
|
||||
runId,
|
||||
}: {
|
||||
token: string;
|
||||
hostname?: string;
|
||||
owner: string;
|
||||
repo: string;
|
||||
runId: number;
|
||||
|
||||
@@ -14,36 +14,60 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ConfigApi, OAuthApi } from '@backstage/core';
|
||||
import { readGitHubIntegrationConfigs } from '@backstage/integration';
|
||||
import { GithubActionsApi } from './GithubActionsApi';
|
||||
import { Octokit, RestEndpointMethodTypes } from '@octokit/rest';
|
||||
|
||||
export class GithubActionsClient implements GithubActionsApi {
|
||||
private readonly configApi: ConfigApi;
|
||||
private readonly githubAuthApi: OAuthApi;
|
||||
|
||||
constructor(options: { configApi: ConfigApi; githubAuthApi: OAuthApi }) {
|
||||
this.configApi = options.configApi;
|
||||
this.githubAuthApi = options.githubAuthApi;
|
||||
}
|
||||
|
||||
private async getOctokit(hostname?: string): Promise<Octokit> {
|
||||
// TODO: Get access token for the specified hostname
|
||||
const token = await this.githubAuthApi.getAccessToken(['repo']);
|
||||
const configs = readGitHubIntegrationConfigs(
|
||||
this.configApi.getOptionalConfigArray('integrations.github') ?? [],
|
||||
);
|
||||
const githubIntegrationConfig = configs.find(
|
||||
v => v.host === hostname ?? 'github.com',
|
||||
);
|
||||
const baseUrl = githubIntegrationConfig?.apiBaseUrl;
|
||||
return new Octokit({ auth: token, baseUrl });
|
||||
}
|
||||
|
||||
async reRunWorkflow({
|
||||
token,
|
||||
hostname,
|
||||
owner,
|
||||
repo,
|
||||
runId,
|
||||
}: {
|
||||
token: string;
|
||||
hostname?: string;
|
||||
owner: string;
|
||||
repo: string;
|
||||
runId: number;
|
||||
}): Promise<any> {
|
||||
return new Octokit({ auth: token }).actions.reRunWorkflow({
|
||||
const octokit = await this.getOctokit(hostname);
|
||||
return octokit.actions.reRunWorkflow({
|
||||
owner,
|
||||
repo,
|
||||
run_id: runId,
|
||||
});
|
||||
}
|
||||
async listWorkflowRuns({
|
||||
token,
|
||||
hostname,
|
||||
owner,
|
||||
repo,
|
||||
pageSize = 100,
|
||||
page = 0,
|
||||
branch,
|
||||
}: {
|
||||
token: string;
|
||||
hostname?: string;
|
||||
owner: string;
|
||||
repo: string;
|
||||
pageSize?: number;
|
||||
@@ -52,9 +76,8 @@ export class GithubActionsClient implements GithubActionsApi {
|
||||
}): Promise<
|
||||
RestEndpointMethodTypes['actions']['listWorkflowRuns']['response']['data']
|
||||
> {
|
||||
const workflowRuns = await new Octokit({
|
||||
auth: token,
|
||||
}).actions.listWorkflowRunsForRepo({
|
||||
const octokit = await this.getOctokit(hostname);
|
||||
const workflowRuns = await octokit.actions.listWorkflowRunsForRepo({
|
||||
owner,
|
||||
repo,
|
||||
per_page: pageSize,
|
||||
@@ -64,19 +87,20 @@ export class GithubActionsClient implements GithubActionsApi {
|
||||
return workflowRuns.data;
|
||||
}
|
||||
async getWorkflow({
|
||||
token,
|
||||
hostname,
|
||||
owner,
|
||||
repo,
|
||||
id,
|
||||
}: {
|
||||
token: string;
|
||||
hostname?: string;
|
||||
owner: string;
|
||||
repo: string;
|
||||
id: number;
|
||||
}): Promise<
|
||||
RestEndpointMethodTypes['actions']['getWorkflow']['response']['data']
|
||||
> {
|
||||
const workflow = await new Octokit({ auth: token }).actions.getWorkflow({
|
||||
const octokit = await this.getOctokit(hostname);
|
||||
const workflow = await octokit.actions.getWorkflow({
|
||||
owner,
|
||||
repo,
|
||||
workflow_id: id,
|
||||
@@ -84,19 +108,20 @@ export class GithubActionsClient implements GithubActionsApi {
|
||||
return workflow.data;
|
||||
}
|
||||
async getWorkflowRun({
|
||||
token,
|
||||
hostname,
|
||||
owner,
|
||||
repo,
|
||||
id,
|
||||
}: {
|
||||
token: string;
|
||||
hostname?: string;
|
||||
owner: string;
|
||||
repo: string;
|
||||
id: number;
|
||||
}): Promise<
|
||||
RestEndpointMethodTypes['actions']['getWorkflowRun']['response']['data']
|
||||
> {
|
||||
const run = await new Octokit({ auth: token }).actions.getWorkflowRun({
|
||||
const octokit = await this.getOctokit(hostname);
|
||||
const run = await octokit.actions.getWorkflowRun({
|
||||
owner,
|
||||
repo,
|
||||
run_id: id,
|
||||
@@ -104,21 +129,20 @@ export class GithubActionsClient implements GithubActionsApi {
|
||||
return run.data;
|
||||
}
|
||||
async downloadJobLogsForWorkflowRun({
|
||||
token,
|
||||
hostname,
|
||||
owner,
|
||||
repo,
|
||||
runId,
|
||||
}: {
|
||||
token: string;
|
||||
hostname?: string;
|
||||
owner: string;
|
||||
repo: string;
|
||||
runId: number;
|
||||
}): Promise<
|
||||
RestEndpointMethodTypes['actions']['downloadJobLogsForWorkflowRun']['response']['data']
|
||||
> {
|
||||
const workflow = await new Octokit({
|
||||
auth: token,
|
||||
}).actions.downloadJobLogsForWorkflowRun({
|
||||
const octokit = await this.getOctokit(hostname);
|
||||
const workflow = await octokit.actions.downloadJobLogsForWorkflowRun({
|
||||
owner,
|
||||
repo,
|
||||
job_id: runId,
|
||||
|
||||
@@ -17,6 +17,7 @@ import React, { useEffect } from 'react';
|
||||
import { useWorkflowRuns } from '../useWorkflowRuns';
|
||||
import { WorkflowRun, WorkflowRunsTable } from '../WorkflowRunsTable';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { readGitHubIntegrationConfigs } from '@backstage/integration';
|
||||
import { WorkflowRunStatus } from '../WorkflowRunStatus';
|
||||
import {
|
||||
Link,
|
||||
@@ -28,6 +29,7 @@ import {
|
||||
import {
|
||||
InfoCard,
|
||||
StructuredMetadataTable,
|
||||
configApiRef,
|
||||
errorApiRef,
|
||||
useApi,
|
||||
} from '@backstage/core';
|
||||
@@ -84,11 +86,17 @@ export const LatestWorkflowRunCard = ({
|
||||
// Display the card full height suitable for
|
||||
variant,
|
||||
}: Props) => {
|
||||
const config = useApi(configApiRef);
|
||||
const errorApi = useApi(errorApiRef);
|
||||
// TODO: Get github hostname from metadata annotation
|
||||
const hostname = readGitHubIntegrationConfigs(
|
||||
config.getOptionalConfigArray('integrations.github') ?? [],
|
||||
)[0].host;
|
||||
const [owner, repo] = (
|
||||
entity?.metadata.annotations?.[GITHUB_ACTIONS_ANNOTATION] ?? '/'
|
||||
).split('/');
|
||||
const [{ runs, loading, error }] = useWorkflowRuns({
|
||||
hostname,
|
||||
owner,
|
||||
repo,
|
||||
branch,
|
||||
|
||||
@@ -14,7 +14,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ApiProvider, ApiRegistry, errorApiRef } from '@backstage/core';
|
||||
import {
|
||||
ApiProvider,
|
||||
ApiRegistry,
|
||||
errorApiRef,
|
||||
configApiRef,
|
||||
ConfigApi,
|
||||
ConfigReader,
|
||||
} from '@backstage/core';
|
||||
import { lightTheme } from '@backstage/theme';
|
||||
import { ThemeProvider } from '@material-ui/core';
|
||||
import { render } from '@testing-library/react';
|
||||
@@ -33,6 +40,8 @@ const mockErrorApi: jest.Mocked<typeof errorApiRef.T> = {
|
||||
error$: jest.fn(),
|
||||
};
|
||||
|
||||
const configApi: ConfigApi = new ConfigReader({});
|
||||
|
||||
describe('<RecentWorkflowRunsCard />', () => {
|
||||
const entity = {
|
||||
apiVersion: 'v1',
|
||||
@@ -69,7 +78,12 @@ describe('<RecentWorkflowRunsCard />', () => {
|
||||
render(
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<MemoryRouter>
|
||||
<ApiProvider apis={ApiRegistry.with(errorApiRef, mockErrorApi)}>
|
||||
<ApiProvider
|
||||
apis={ApiRegistry.with(errorApiRef, mockErrorApi).with(
|
||||
configApiRef,
|
||||
configApi,
|
||||
)}
|
||||
>
|
||||
<RecentWorkflowRunsCard {...props} />
|
||||
</ApiProvider>
|
||||
</MemoryRouter>
|
||||
|
||||
@@ -15,12 +15,14 @@
|
||||
*/
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import {
|
||||
configApiRef,
|
||||
EmptyState,
|
||||
errorApiRef,
|
||||
InfoCard,
|
||||
Table,
|
||||
useApi,
|
||||
} from '@backstage/core';
|
||||
import { readGitHubIntegrationConfigs } from '@backstage/integration';
|
||||
import { Button, Link } from '@material-ui/core';
|
||||
import React, { useEffect } from 'react';
|
||||
import { generatePath, Link as RouterLink } from 'react-router-dom';
|
||||
@@ -45,11 +47,17 @@ export const RecentWorkflowRunsCard = ({
|
||||
limit = 5,
|
||||
variant,
|
||||
}: Props) => {
|
||||
const config = useApi(configApiRef);
|
||||
const errorApi = useApi(errorApiRef);
|
||||
// TODO: Get github hostname from metadata annotation
|
||||
const hostname = readGitHubIntegrationConfigs(
|
||||
config.getOptionalConfigArray('integrations.github') ?? [],
|
||||
)[0].host;
|
||||
const [owner, repo] = (
|
||||
entity?.metadata.annotations?.[GITHUB_ACTIONS_ANNOTATION] ?? '/'
|
||||
).split('/');
|
||||
const [{ runs = [], loading, error }] = useWorkflowRuns({
|
||||
hostname,
|
||||
owner,
|
||||
repo,
|
||||
branch,
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { Link } from '@backstage/core';
|
||||
import { configApiRef, Link, useApi } from '@backstage/core';
|
||||
import { readGitHubIntegrationConfigs } from '@backstage/integration';
|
||||
import {
|
||||
Accordion,
|
||||
AccordionDetails,
|
||||
@@ -159,10 +160,15 @@ const JobListItem = ({
|
||||
};
|
||||
|
||||
export const WorkflowRunDetails = ({ entity }: { entity: Entity }) => {
|
||||
const config = useApi(configApiRef);
|
||||
const projectName = useProjectName(entity);
|
||||
|
||||
// TODO: Get github hostname from metadata annotation
|
||||
const hostname = readGitHubIntegrationConfigs(
|
||||
config.getOptionalConfigArray('integrations.github') ?? [],
|
||||
)[0].host;
|
||||
const [owner, repo] = projectName.value ? projectName.value.split('/') : [];
|
||||
const details = useWorkflowRunsDetails(repo, owner);
|
||||
const details = useWorkflowRunsDetails({ hostname, owner, repo });
|
||||
const jobs = useWorkflowRunJobs(details.value?.jobs_url);
|
||||
|
||||
const error = projectName.error || (projectName.value && details.error);
|
||||
|
||||
@@ -13,20 +13,26 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { useApi, githubAuthApiRef } from '@backstage/core';
|
||||
import { useApi } from '@backstage/core';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useAsync } from 'react-use';
|
||||
import { githubActionsApiRef } from '../../api';
|
||||
|
||||
export const useWorkflowRunsDetails = (repo: string, owner: string) => {
|
||||
export const useWorkflowRunsDetails = ({
|
||||
hostname,
|
||||
owner,
|
||||
repo,
|
||||
}: {
|
||||
hostname?: string;
|
||||
owner: string;
|
||||
repo: string;
|
||||
}) => {
|
||||
const api = useApi(githubActionsApiRef);
|
||||
const auth = useApi(githubAuthApiRef);
|
||||
const { id } = useParams();
|
||||
const details = useAsync(async () => {
|
||||
const token = await auth.getAccessToken(['repo']);
|
||||
return repo && owner
|
||||
? api.getWorkflowRun({
|
||||
token,
|
||||
hostname,
|
||||
owner,
|
||||
repo,
|
||||
id: parseInt(id, 10),
|
||||
|
||||
@@ -34,6 +34,8 @@ import { useProjectName } from '../useProjectName';
|
||||
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
|
||||
import DescriptionIcon from '@material-ui/icons/Description';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { configApiRef, useApi } from '@backstage/core';
|
||||
import { readGitHubIntegrationConfigs } from '@backstage/integration';
|
||||
|
||||
const LazyLog = React.lazy(() => import('react-lazylog/build/LazyLog'));
|
||||
const LinePart = React.lazy(() => import('react-lazylog/build/LinePart'));
|
||||
@@ -107,11 +109,21 @@ export const WorkflowRunLogs = ({
|
||||
runId: string;
|
||||
inProgress: boolean;
|
||||
}) => {
|
||||
const config = useApi(configApiRef);
|
||||
const classes = useStyles();
|
||||
const projectName = useProjectName(entity);
|
||||
|
||||
// TODO: Get github hostname from metadata annotation
|
||||
const hostname = readGitHubIntegrationConfigs(
|
||||
config.getOptionalConfigArray('integrations.github') ?? [],
|
||||
)[0].host;
|
||||
const [owner, repo] = projectName.value ? projectName.value.split('/') : [];
|
||||
const jobLogs = useDownloadWorkflowRunLogs(repo, owner, runId);
|
||||
const jobLogs = useDownloadWorkflowRunLogs({
|
||||
hostname,
|
||||
owner,
|
||||
repo,
|
||||
id: runId,
|
||||
});
|
||||
const [open, setOpen] = React.useState(false);
|
||||
|
||||
const handleOpen = () => {
|
||||
|
||||
+13
-9
@@ -14,22 +14,26 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useApi, githubAuthApiRef } from '@backstage/core';
|
||||
import { useApi } from '@backstage/core';
|
||||
import { useAsync } from 'react-use';
|
||||
import { githubActionsApiRef } from '../../api';
|
||||
|
||||
export const useDownloadWorkflowRunLogs = (
|
||||
repo: string,
|
||||
owner: string,
|
||||
id: string,
|
||||
) => {
|
||||
export const useDownloadWorkflowRunLogs = ({
|
||||
hostname,
|
||||
owner,
|
||||
repo,
|
||||
id,
|
||||
}: {
|
||||
hostname?: string;
|
||||
owner: string;
|
||||
repo: string;
|
||||
id: string;
|
||||
}) => {
|
||||
const api = useApi(githubActionsApiRef);
|
||||
const auth = useApi(githubAuthApiRef);
|
||||
const details = useAsync(async () => {
|
||||
const token = await auth.getAccessToken(['repo']);
|
||||
return repo && owner
|
||||
? api.downloadJobLogsForWorkflowRun({
|
||||
token,
|
||||
hostname,
|
||||
owner,
|
||||
repo,
|
||||
runId: parseInt(id, 10),
|
||||
|
||||
@@ -25,13 +25,20 @@ import {
|
||||
import RetryIcon from '@material-ui/icons/Replay';
|
||||
import GitHubIcon from '@material-ui/icons/GitHub';
|
||||
import { Link as RouterLink, generatePath } from 'react-router-dom';
|
||||
import { EmptyState, Table, TableColumn } from '@backstage/core';
|
||||
import {
|
||||
EmptyState,
|
||||
Table,
|
||||
TableColumn,
|
||||
configApiRef,
|
||||
useApi,
|
||||
} from '@backstage/core';
|
||||
import { useWorkflowRuns } from '../useWorkflowRuns';
|
||||
import { WorkflowRunStatus } from '../WorkflowRunStatus';
|
||||
import SyncIcon from '@material-ui/icons/Sync';
|
||||
import { buildRouteRef } from '../../plugin';
|
||||
import { useProjectName } from '../useProjectName';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { readGitHubIntegrationConfigs } from '@backstage/integration';
|
||||
|
||||
export type WorkflowRun = {
|
||||
id: string;
|
||||
@@ -162,12 +169,18 @@ export const WorkflowRunsTable = ({
|
||||
entity: Entity;
|
||||
branch?: string;
|
||||
}) => {
|
||||
const config = useApi(configApiRef);
|
||||
const { value: projectName, loading } = useProjectName(entity);
|
||||
// TODO: Get github hostname from metadata annotation
|
||||
const hostname = readGitHubIntegrationConfigs(
|
||||
config.getOptionalConfigArray('integrations.github') ?? [],
|
||||
)[0].host;
|
||||
const [owner, repo] = (projectName ?? '/').split('/');
|
||||
const [
|
||||
{ runs, ...tableProps },
|
||||
{ retry, setPage, setPageSize },
|
||||
] = useWorkflowRuns({
|
||||
hostname,
|
||||
owner,
|
||||
repo,
|
||||
branch,
|
||||
|
||||
@@ -17,21 +17,22 @@ import { useState } from 'react';
|
||||
import { useAsyncRetry } from 'react-use';
|
||||
import { WorkflowRun } from './WorkflowRunsTable/WorkflowRunsTable';
|
||||
import { githubActionsApiRef } from '../api/GithubActionsApi';
|
||||
import { useApi, githubAuthApiRef, errorApiRef } from '@backstage/core';
|
||||
import { useApi, errorApiRef } from '@backstage/core';
|
||||
|
||||
export function useWorkflowRuns({
|
||||
hostname,
|
||||
owner,
|
||||
repo,
|
||||
branch,
|
||||
initialPageSize = 5,
|
||||
}: {
|
||||
hostname?: string;
|
||||
owner: string;
|
||||
repo: string;
|
||||
branch?: string;
|
||||
initialPageSize?: number;
|
||||
}) {
|
||||
const api = useApi(githubActionsApiRef);
|
||||
const auth = useApi(githubAuthApiRef);
|
||||
|
||||
const errorApi = useApi(errorApiRef);
|
||||
|
||||
@@ -42,12 +43,11 @@ export function useWorkflowRuns({
|
||||
const { loading, value: runs, retry, error } = useAsyncRetry<
|
||||
WorkflowRun[]
|
||||
>(async () => {
|
||||
const token = await auth.getAccessToken(['repo']);
|
||||
return (
|
||||
api
|
||||
// GitHub API pagination count starts from 1
|
||||
.listWorkflowRuns({
|
||||
token,
|
||||
hostname,
|
||||
owner,
|
||||
repo,
|
||||
pageSize,
|
||||
@@ -63,7 +63,7 @@ export function useWorkflowRuns({
|
||||
onReRunClick: async () => {
|
||||
try {
|
||||
await api.reRunWorkflow({
|
||||
token,
|
||||
hostname,
|
||||
owner,
|
||||
repo,
|
||||
runId: run.id,
|
||||
|
||||
@@ -15,9 +15,11 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
configApiRef,
|
||||
createPlugin,
|
||||
createRouteRef,
|
||||
createApiFactory,
|
||||
githubAuthApiRef,
|
||||
} from '@backstage/core';
|
||||
import { githubActionsApiRef, GithubActionsClient } from './api';
|
||||
|
||||
@@ -34,5 +36,12 @@ export const buildRouteRef = createRouteRef({
|
||||
|
||||
export const plugin = createPlugin({
|
||||
id: 'github-actions',
|
||||
apis: [createApiFactory(githubActionsApiRef, new GithubActionsClient())],
|
||||
apis: [
|
||||
createApiFactory({
|
||||
api: githubActionsApiRef,
|
||||
deps: { configApi: configApiRef, githubAuthApi: githubAuthApiRef },
|
||||
factory: ({ configApi, githubAuthApi }) =>
|
||||
new GithubActionsClient({ configApi, githubAuthApi }),
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user