feat: GH Workflow Runs as Card View (#20939)

* feat: GH Workflow Runs as Card View

Signed-off-by: blbose <lillian.bose@philips.com>

* feat: GH Workflow Runs as Card View

Signed-off-by: blbose <lillian.bose@philips.com>

* feat: GH Workflow Runs as Card View

Signed-off-by: blbose <lillian.bose@philips.com>

* fix dagda1 github link in OWNERS.md

Signed-off-by: Paul Cowan <paul.cowan@cutting.scot>
Signed-off-by: blbose <lillian.bose@philips.com>

* Added description for publish:gerrit scaffolder actions

Signed-off-by: Andy Muldoon <andy.muldoon@ericsson.com>
Signed-off-by: blbose <lillian.bose@philips.com>

* Add existing OpenCost plugin to the Backstage Plugin Directory

Signed-off-by: Matt Ray <github@mattray.dev>
Signed-off-by: blbose <lillian.bose@philips.com>

* Squared Penny the Pig logo

Signed-off-by: Matt Ray <github@mattray.dev>
Signed-off-by: blbose <lillian.bose@philips.com>

* chore(deps): update dependency @types/supertest to v2.0.15

Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Signed-off-by: blbose <lillian.bose@philips.com>

* chore(deps): update dependency @types/stoppable to v1.1.2

Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Signed-off-by: blbose <lillian.bose@philips.com>

* chore(deps): update dependency @types/rollup-plugin-peer-deps-external to v2.2.3

Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Signed-off-by: blbose <lillian.bose@philips.com>

* chore(deps): update dependency @types/regression to v2.0.4

Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Signed-off-by: blbose <lillian.bose@philips.com>

* chore(deps): update dependency @types/recursive-readdir to v2.2.3

Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Signed-off-by: blbose <lillian.bose@philips.com>

* chore(deps): update dependency @types/recharts to v1.8.26

Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Signed-off-by: blbose <lillian.bose@philips.com>

* chore(deps): update dependency @types/luxon to v3.3.3

Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Signed-off-by: blbose <lillian.bose@philips.com>

* chore(deps): update dependency @types/swagger-ui-react to v4.18.2

Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Signed-off-by: blbose <lillian.bose@philips.com>

* chore(deps): update dependency @types/tar to v6.1.7

Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Signed-off-by: blbose <lillian.bose@philips.com>

* chore(deps): update dependency @types/xml2js to v0.4.13

Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Signed-off-by: blbose <lillian.bose@philips.com>

* feat: GH Workflow Runs as Card View

Signed-off-by: blbose <lillian.bose@philips.com>

* feat: GH Workflow Runs as Card View

Signed-off-by: blbose <lillian.bose@philips.com>

* feat: GH Workflow Runs as Card View

Signed-off-by: blbose <lillian.bose@philips.com>

* feat: GH Workflow Runs as Card View

Signed-off-by: blbose <lillian.bose@philips.com>

* feat: GH Workflow Runs as Card View

Signed-off-by: blbose <lillian.bose@philips.com>

* feat: GH Workflow Runs as Card View

Signed-off-by: blbose <lillian.bose@philips.com>

* file mode changes revert

Signed-off-by: blbose <lillian.bose@philips.com>

* api-report warning fix

Signed-off-by: blbose <lillian.bose@philips.com>

* fix: UX perspective changes

Signed-off-by: blbose <lillian.bose@philips.com>

* fix:eslint warning removal

Signed-off-by: blbose <lillian.bose@philips.com>

* fix:avoiding of id based on acierto review

Signed-off-by: blbose <lillian.bose@philips.com>

* fix: latest review comments

Signed-off-by: blbose <lillian.bose@philips.com>

* fix: review comment changes from Freben

Signed-off-by: blbose <lillian.bose@philips.com>

* fix: review comment changes from Freben

Signed-off-by: blbose <lillian.bose@philips.com>

* fix: rebase conflict

Signed-off-by: blbose <lillian.bose@philips.com>

* fix: rebase conflicts

Signed-off-by: blbose <lillian.bose@philips.com>

* fix: rebase conflict

Signed-off-by: blbose <lillian.bose@philips.com>

* fix: rebase conflicts

Signed-off-by: blbose <lillian.bose@philips.com>

* fix: rebase conflict

Signed-off-by: blbose <lillian.bose@philips.com>

* fix: rebase conflicts

Signed-off-by: blbose <lillian.bose@philips.com>

* fix: rebase conflict

Signed-off-by: blbose <lillian.bose@philips.com>

* fix: rebase conflicts

Signed-off-by: blbose <lillian.bose@philips.com>

* fix: rebase conflict

Signed-off-by: blbose <lillian.bose@philips.com>

* fix: rebase conflicts

Signed-off-by: blbose <lillian.bose@philips.com>

* fix: rebase conflict

Signed-off-by: blbose <lillian.bose@philips.com>

* fix: rebase conflicts

Signed-off-by: blbose <lillian.bose@philips.com>

* fix: rebase conflict

Signed-off-by: blbose <lillian.bose@philips.com>

* fix: rebase conflicts

Signed-off-by: blbose <lillian.bose@philips.com>

---------

Signed-off-by: blbose <lillian.bose@philips.com>
Signed-off-by: Paul Cowan <paul.cowan@cutting.scot>
Signed-off-by: Andy Muldoon <andy.muldoon@ericsson.com>
Signed-off-by: Matt Ray <github@mattray.dev>
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Paul Cowan <paul.cowan@cutting.scot>
Co-authored-by: Andy Muldoon <andy.muldoon@ericsson.com>
Co-authored-by: Matt Ray <github@mattray.dev>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
This commit is contained in:
Lillian Bose
2023-11-28 00:25:41 +05:30
committed by GitHub
parent 90ffed5382
commit 08d7e4676a
12 changed files with 635 additions and 9 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-github-actions': patch
---
Github Workflow Runs UI is modified to show in optional Card view instead of table, with branch selection option
+16
View File
@@ -92,3 +92,19 @@ integrations:
- There is a limit of 100 apps for one OAuth client/token pair
- The OAuth application must be at the GitHub organization level in order to display the workflows. If you do
not see any workflows, confirm the OAuth application was created in the organization and not a specific user account.
## Optional Workflow Runs Card View
Github Workflow Runs optional UI to show in Card view instead of table, with branch selection option
```tsx
// You can add the tab to any number of pages, the service page is shown as an
// example given here
const serviceEntityPage = (
<EntityLayout>
{/* other tabs... */}
<EntityLayout.Route path="/github-actions" title="GitHub Actions">
<EntityGithubActionsContent view='cards' />
</EntityLayout.Route>
```
+51 -2
View File
@@ -16,6 +16,17 @@ import { RestEndpointMethodTypes } from '@octokit/rest';
import { RouteRef } from '@backstage/core-plugin-api';
import { ScmAuthApi } from '@backstage/integration-react';
// @public (undocumented)
export type Branch = {
name: string;
};
// @public (undocumented)
export type Branches = {
default_branch: string;
branches: Branch[];
};
// @public (undocumented)
export enum BuildStatus {
// (undocumented)
@@ -29,7 +40,7 @@ export enum BuildStatus {
}
// @public (undocumented)
export const EntityGithubActionsContent: () => JSX_2.Element;
export const EntityGithubActionsContent: (props: RouterProps) => JSX_2.Element;
// @public (undocumented)
export const EntityLatestGithubActionRunCard: (props: {
@@ -106,6 +117,21 @@ export type GithubActionsApi = {
}) => Promise<
RestEndpointMethodTypes['actions']['downloadJobLogsForWorkflowRun']['response']['data']
>;
listBranches: (options: {
hostname?: string;
owner: string;
repo: string;
page: number;
}) => Promise<
RestEndpointMethodTypes['repos']['listBranches']['response']['data']
>;
getDefaultBranch: (options: {
hostname?: string;
owner: string;
repo: string;
}) => Promise<
RestEndpointMethodTypes['repos']['get']['response']['data']['default_branch']
>;
};
// @public (undocumented)
@@ -124,6 +150,14 @@ export class GithubActionsClient implements GithubActionsApi {
RestEndpointMethodTypes['actions']['downloadJobLogsForWorkflowRun']['response']['data']
>;
// (undocumented)
getDefaultBranch(options: {
hostname?: string;
owner: string;
repo: string;
}): Promise<
RestEndpointMethodTypes['repos']['get']['response']['data']['default_branch']
>;
// (undocumented)
getWorkflow(options: {
hostname?: string;
owner: string;
@@ -142,6 +176,15 @@ export class GithubActionsClient implements GithubActionsApi {
RestEndpointMethodTypes['actions']['getWorkflowRun']['response']['data']
>;
// (undocumented)
listBranches(options: {
hostname?: string;
owner: string;
repo: string;
page?: number;
}): Promise<
RestEndpointMethodTypes['repos']['listBranches']['response']['data']
>;
// (undocumented)
listJobsForWorkflowRun(options: {
hostname?: string;
owner: string;
@@ -226,7 +269,13 @@ export const RecentWorkflowRunsCard: (props: {
}) => React_2.JSX.Element;
// @public (undocumented)
export const Router: () => React_2.JSX.Element;
export const Router: (props: RouterProps) => React_2.JSX.Element;
// @public (undocumented)
export interface RouterProps {
// (undocumented)
view?: 'cards' | 'table';
}
// @public (undocumented)
export type Step = {
+6
View File
@@ -63,6 +63,12 @@ const mockGithubActionsApi: GithubActionsApi = {
async reRunWorkflow() {
return {} as any;
},
async listBranches() {
return {} as any;
},
async getDefaultBranch() {
return {} as any;
},
};
createDevApp()
Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

@@ -83,4 +83,21 @@ export type GithubActionsApi = {
}) => Promise<
RestEndpointMethodTypes['actions']['downloadJobLogsForWorkflowRun']['response']['data']
>;
listBranches: (options: {
hostname?: string;
owner: string;
repo: string;
page: number;
}) => Promise<
RestEndpointMethodTypes['repos']['listBranches']['response']['data']
>;
getDefaultBranch: (options: {
hostname?: string;
owner: string;
repo: string;
}) => Promise<
RestEndpointMethodTypes['repos']['get']['response']['data']['default_branch']
>;
};
@@ -174,4 +174,41 @@ export class GithubActionsClient implements GithubActionsApi {
return workflow.data;
}
async listBranches(options: {
hostname?: string;
owner: string;
repo: string;
page?: number;
}): Promise<
RestEndpointMethodTypes['repos']['listBranches']['response']['data']
> {
const { hostname, owner, repo, page = 0 } = options;
const octokit = await this.getOctokit(hostname);
const response = await octokit.rest.repos.listBranches({
owner,
repo,
per_page: 100,
page,
});
return response.data;
}
async getDefaultBranch(options: {
hostname?: string;
owner: string;
repo: string;
}): Promise<
RestEndpointMethodTypes['repos']['get']['response']['data']['default_branch']
> {
const { hostname, owner, repo } = options;
const octokit = await this.getOctokit(hostname);
const response = await octokit.rest.repos.get({
owner,
repo,
});
return response.data.default_branch;
}
}
+16
View File
@@ -49,3 +49,19 @@ export enum BuildStatus {
'pending',
'running',
}
/** @public */
export type Branch = {
name: string;
};
/** @public */
export type Branches = {
default_branch: string;
branches: Branch[];
};
/** @public */
export interface RouterProps {
view?: 'cards' | 'table';
}
@@ -23,15 +23,18 @@ import {
import { Routes, Route } from 'react-router-dom';
import { buildRouteRef } from '../routes';
import { WorkflowRunDetails } from './WorkflowRunDetails';
import { WorkflowRunsCard } from './WorkflowRunsCard';
import { WorkflowRunsTable } from './WorkflowRunsTable';
import { GITHUB_ACTIONS_ANNOTATION } from './getProjectNameFromEntity';
import { RouterProps } from '../api/types';
/** @public */
export const isGithubActionsAvailable = (entity: Entity) =>
Boolean(entity.metadata.annotations?.[GITHUB_ACTIONS_ANNOTATION]);
/** @public */
export const Router = () => {
export const Router = (props: RouterProps) => {
const { view = 'table' } = props;
const { entity } = useEntity();
if (!isGithubActionsAvailable(entity)) {
@@ -39,14 +42,21 @@ export const Router = () => {
<MissingAnnotationEmptyState annotation={GITHUB_ACTIONS_ANNOTATION} />
);
}
const workflowRunsComponent =
view === 'cards' ? (
<WorkflowRunsCard entity={entity} />
) : (
<WorkflowRunsTable entity={entity} />
);
return (
<Routes>
<Route path="/" element={<WorkflowRunsTable entity={entity} />} />
<Route path="/" element={workflowRunsComponent} />
<Route
path={`${buildRouteRef.path}`}
element={<WorkflowRunDetails entity={entity} />}
/>
)
</Routes>
);
};
@@ -0,0 +1,412 @@
/*
* Copyright 2020 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { ChangeEvent, useEffect, useState } from 'react';
import {
Typography,
Box,
IconButton,
Tooltip,
Button,
Chip,
ButtonGroup,
Grid,
makeStyles,
createStyles,
Theme,
TablePagination,
Select,
MenuItem,
TextField,
CircularProgress,
} from '@material-ui/core';
import {
EmptyState,
Link,
MarkdownContent,
InfoCard,
} from '@backstage/core-components';
import GitHubIcon from '@material-ui/icons/GitHub';
import RetryIcon from '@material-ui/icons/Replay';
import SyncIcon from '@material-ui/icons/Sync';
import ExternalLinkIcon from '@material-ui/icons/Launch';
import { useRouteRef } from '@backstage/core-plugin-api';
import { useWorkflowRuns, WorkflowRun } from '../useWorkflowRuns';
import { WorkflowRunStatus } from '../WorkflowRunStatus';
import { buildRouteRef } from '../../routes';
import { getProjectNameFromEntity } from '../getProjectNameFromEntity';
import { getHostnameFromEntity } from '../getHostnameFromEntity';
import { Alert, Color } from '@material-ui/lab';
import { Entity } from '@backstage/catalog-model';
const useStyles = makeStyles((theme: Theme) =>
createStyles({
card: {
border: `1px solid ${theme.palette.divider}`,
boxShadow: theme.shadows[2],
borderRadius: '4px',
overflow: 'visible',
position: 'relative',
margin: theme.spacing(4, 1, 1),
flex: '1',
minWidth: '0px',
},
externalLinkIcon: {
fontSize: 'inherit',
verticalAlign: 'middle',
},
bottomline: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginTop: '-5px',
},
pagination: {
width: '100%',
},
}),
);
type WorkflowRunsCardViewProps = {
runs?: WorkflowRun[];
searchTerm: string;
loading: boolean;
onChangePageSize: (pageSize: number) => void;
onChangePage: (page: number) => void;
page: number;
total: number;
pageSize: number;
projectName: string;
};
const statusColors: Record<string, string> = {
skipped: 'warning',
canceled: 'info',
timed_out: 'error',
failure: 'error',
success: 'success',
};
const matchesSearchTerm = (run: WorkflowRun, searchTerm: string) => {
const lowerCaseSearchTerm = searchTerm.toLocaleLowerCase();
return (
run.workflowName?.toLocaleLowerCase().includes(lowerCaseSearchTerm) ||
run.source.branchName?.toLocaleLowerCase().includes(lowerCaseSearchTerm) ||
run.status?.toLocaleLowerCase().includes(lowerCaseSearchTerm) ||
run.id?.toLocaleLowerCase().includes(lowerCaseSearchTerm)
);
};
export const WorkflowRunsCardView = ({
runs,
searchTerm,
loading,
onChangePageSize,
onChangePage,
page,
total,
pageSize,
}: WorkflowRunsCardViewProps) => {
const classes = useStyles();
const routeLink = useRouteRef(buildRouteRef);
const filteredRuns = runs?.filter(run => matchesSearchTerm(run, searchTerm));
return (
<Grid container spacing={3}>
{filteredRuns && runs?.length !== 0 ? (
filteredRuns.map(run => (
<Grid key={run.id} item container xs={12} sm={4} md={4} xl={4}>
<Box className={classes.card}>
<Box
display="flex"
flexDirection="column"
m={3}
alignItems="center"
justifyContent="center"
>
<Box pt={2} sx={{ width: '100%' }} textAlign="center">
<Tooltip
title={run.status ?? 'No Status'}
placement="top-start"
>
<Alert
variant="outlined"
severity={
statusColors[
run.conclusion as keyof typeof statusColors
] as Color
}
style={{ alignItems: 'center' }}
>
<Typography variant="h6">
<Link to={routeLink({ id: run.id })}>
<Typography color="primary" variant="h6">
{run.workflowName}
</Typography>
</Link>
</Typography>
</Alert>
</Tooltip>
<Tooltip title={run.message ?? 'No run message'}>
<Typography
variant="body2"
component="span"
style={{ fontSize: 'smaller' }}
>
<MarkdownContent
content={`Commit ID : ${run.source.commit.hash!}`}
/>
</Typography>
</Tooltip>
{run.source.branchName && (
<MarkdownContent
content={`Branch : ${run.source.branchName}`}
/>
)}
<Chip
key={run.id}
size="small"
label={`Workflow ID : ${run.id}`}
/>
<Chip
size="small"
label={
<Box display="flex" alignItems="center">
<WorkflowRunStatus
status={run.status}
conclusion={run.conclusion}
/>
</Box>
}
/>
<div className={classes.bottomline}>
{run.githubUrl && (
<Link to={run.githubUrl}>
Workflow runs on GitHub{' '}
<ExternalLinkIcon
className={classes.externalLinkIcon}
/>
</Link>
)}
<ButtonGroup>
<Tooltip title="Rerun workflow">
<IconButton
onClick={run.onReRunClick}
style={{ fontSize: '12px' }}
>
<RetryIcon />
</IconButton>
</Tooltip>
</ButtonGroup>
</div>
</Box>
</Box>
</Box>
</Grid>
))
) : (
<Box p={2}>
{loading ? <CircularProgress /> : 'No matching runs found.'}
</Box>
)}
<div className={classes.pagination}>
<TablePagination
component="div"
count={total}
page={page}
rowsPerPage={pageSize}
onPageChange={(_, newPage) => onChangePage(newPage)}
onRowsPerPageChange={event =>
onChangePageSize(parseInt(event.target.value, 6))
}
labelRowsPerPage="Workflows per page"
rowsPerPageOptions={[6, 12, 18, { label: 'All', value: -1 }]}
/>
</div>
</Grid>
);
};
type WorkflowRunsCardProps = {
entity: Entity;
};
const WorkflowRunsCardSearch = ({
searchTerm,
handleSearch,
retry,
}: {
searchTerm: string;
handleSearch: (event: ChangeEvent<HTMLInputElement>) => void;
retry: () => void;
}) => {
return (
<>
<Box flexGrow={1} />
<TextField
type="search"
label="Search"
value={searchTerm}
onChange={handleSearch}
data-testid="search-control"
style={{ marginRight: '20px' }}
/>
<ButtonGroup>
<Tooltip title="Reload workflow runs">
<IconButton onClick={retry}>
<SyncIcon />
</IconButton>
</Tooltip>
</ButtonGroup>
</>
);
};
export const WorkflowRunsCard = ({ entity }: WorkflowRunsCardProps) => {
const projectName = getProjectNameFromEntity(entity);
const hostname = getHostnameFromEntity(entity);
const [owner, repo] = (projectName ?? '/').split('/');
const [branch, setBranch] = useState<string | undefined>('default');
const [runs, setRuns] = useState<WorkflowRun[] | undefined>([]);
const [searchTerm, setSearchTerm] = useState('');
const handleSearch = (event: ChangeEvent<HTMLInputElement>) => {
setSearchTerm(event.target.value);
};
const [
{ runs: runsData, branches, defaultBranch, ...cardProps },
{ retry, setPage, setPageSize },
] = useWorkflowRuns({
hostname,
owner,
repo,
branch: branch === 'all' ? undefined : branch,
});
const githubHost = hostname || 'github.com';
const hasNoRuns = !cardProps.loading && !runsData;
const handleMenuChange = (
event: ChangeEvent<{ name?: string; value: unknown }>,
) => {
const selectedValue = event.target.value as string;
setBranch(selectedValue);
setPage(0);
retry();
};
useEffect(() => {
setRuns(runsData);
}, [runsData, branch]);
useEffect(() => {
setBranch(defaultBranch);
}, [defaultBranch]);
return (
<Grid item>
{hasNoRuns ? (
<EmptyState
missing="data"
title="No Workflow Data"
description="This component has GitHub Actions enabled, but no data was found. Have you created any Workflows? Click the button below to create a new Workflow."
action={
<Button
variant="contained"
color="primary"
href={`https://${githubHost}/${projectName}/actions/new`}
>
Create new Workflow
</Button>
}
/>
) : (
<InfoCard
title={
<Box display="flex" alignItems="center">
<GitHubIcon />
<Box mr={1} />
<Typography variant="h6">{projectName}</Typography>
<Select
value={branch}
key={branch}
label="Branch"
onChange={handleMenuChange}
data-testid="menu-control"
style={{
marginLeft: '30px',
marginRight: '20px',
width: '230px',
}}
>
{branches.map(branchItem => (
<MenuItem key={branchItem.name} value={branchItem.name}>
{branchItem.name === defaultBranch ? (
<Typography variant="body2" component="span">
{branchItem.name}{' '}
<Typography
variant="body2"
component="span"
style={{ color: 'lightgray', fontSize: 'x-small' }}
>
(default)
</Typography>
</Typography>
) : (
branchItem.name
)}
</MenuItem>
))}
<MenuItem
value="all"
key="all"
style={{ color: 'lightGrey', fontSize: 'small' }}
>
select all branches
</MenuItem>
</Select>
<WorkflowRunsCardSearch
searchTerm={searchTerm}
handleSearch={handleSearch}
retry={retry}
/>
</Box>
}
>
<WorkflowRunsCardView
runs={runs}
loading={cardProps.loading}
onChangePageSize={setPageSize}
onChangePage={setPage}
page={cardProps.page}
total={cardProps.total}
pageSize={cardProps.pageSize}
searchTerm={searchTerm}
projectName={projectName}
/>
</InfoCard>
)}
</Grid>
);
};
export default WorkflowRunsCard;
@@ -0,0 +1,16 @@
/*
* Copyright 2020 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { WorkflowRunsCard, WorkflowRunsCardView } from './WorkflowRunsCard';
@@ -17,6 +17,7 @@ import { useState } from 'react';
import useAsyncRetry from 'react-use/lib/useAsyncRetry';
import { githubActionsApiRef } from '../api/GithubActionsApi';
import { useApi, errorApiRef } from '@backstage/core-plugin-api';
import { Branch } from '../api';
export type WorkflowRun = {
workflowName?: string;
@@ -41,12 +42,12 @@ export function useWorkflowRuns({
owner,
repo,
branch,
initialPageSize = 5,
initialPageSize = 6,
}: {
hostname?: string;
owner: string;
repo: string;
branch?: string;
branch?: string | undefined;
initialPageSize?: number;
}) {
const api = useApi(githubActionsApiRef);
@@ -56,6 +57,8 @@ export function useWorkflowRuns({
const [total, setTotal] = useState(0);
const [page, setPage] = useState(0);
const [pageSize, setPageSize] = useState(initialPageSize);
const [branches, setBranches] = useState<Branch[]>([]);
const [defaultBranch, setDefaultBranch] = useState<string>('');
const {
loading,
@@ -63,6 +66,43 @@ export function useWorkflowRuns({
retry,
error,
} = useAsyncRetry<WorkflowRun[]>(async () => {
const fetchedDefaultBranch = await api.getDefaultBranch({
hostname,
owner,
repo,
});
setDefaultBranch(fetchedDefaultBranch);
let selectedBranch = branch;
if (branch === 'default') {
selectedBranch = fetchedDefaultBranch;
}
const fetchBranches = async () => {
let next = true;
let iteratePage = 0;
const branchSet: Branch[] = [];
while (next) {
const branchesData = await api.listBranches({
hostname,
owner,
repo,
page: iteratePage,
});
if (branchesData.length === 0) {
next = false;
}
iteratePage++;
branchSet.push(...branchesData);
}
return branchSet;
};
const branchSet = await fetchBranches();
setBranches(branchSet);
// GitHub API pagination count starts from 1
const workflowRunsData = await api.listWorkflowRuns({
hostname,
@@ -70,7 +110,7 @@ export function useWorkflowRuns({
repo,
pageSize,
page: page + 1,
branch,
branch: selectedBranch,
});
setTotal(workflowRunsData.total_count);
// Transformation here
@@ -88,7 +128,7 @@ export function useWorkflowRuns({
});
} catch (e) {
errorApi.post(
new Error(`Failed to rerun the workflow: ${e.message}`),
new Error(`Failed to rerun the workflow: ${(e as Error).message}`),
);
}
},
@@ -115,6 +155,8 @@ export function useWorkflowRuns({
pageSize,
loading,
runs,
branches,
defaultBranch,
projectName: `${owner}/${repo}`,
total,
error,