Merge pull request #22792 from Josh-Uvi/github-pull-requests-board

feat: adds status to the github-pull-requests-board
This commit is contained in:
Ben Lambert
2024-02-21 13:03:16 +01:00
committed by GitHub
14 changed files with 362 additions and 147 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-github-pull-requests-board': patch
---
The `CardHeader` component in the `github-pull-requests-board` plugin will show the status for the PR
+1
View File
@@ -45,6 +45,7 @@
"@backstage/plugin-gcalendar": "workspace:^",
"@backstage/plugin-gcp-projects": "workspace:^",
"@backstage/plugin-github-actions": "workspace:^",
"@backstage/plugin-github-pull-requests-board": "workspace:^",
"@backstage/plugin-gocd": "workspace:^",
"@backstage/plugin-graphiql": "workspace:^",
"@backstage/plugin-home": "workspace:^",
@@ -185,6 +185,7 @@ import {
isLinguistAvailable,
EntityLinguistCard,
} from '@backstage/plugin-linguist';
import { EntityTeamPullRequestsContent } from '@backstage/plugin-github-pull-requests-board';
const customEntityFilterKind = ['Component', 'API', 'System'];
@@ -809,6 +810,9 @@ const groupPage = (
</Grid>
</Grid>
</EntityLayout.Route>
<EntityLayout.Route path="/pull-requests" title="Pull Requests">
<EntityTeamPullRequestsContent />
</EntityLayout.Route>
</EntityLayoutWrapper>
);
@@ -57,6 +57,15 @@ export const useGetPullRequestDetails = () => {
state
}
}
commits(last: 1) {
nodes {
commit {
statusCheckRollup {
state
}
}
}
}
mergeable
state
reviewDecision
@@ -16,7 +16,7 @@
import React, { PropsWithChildren, FunctionComponent } from 'react';
import { Box, Paper, CardActionArea } from '@material-ui/core';
import CardHeader from './CardHeader';
import { Label } from '../../utils/types';
import { Label, Status } from '../../utils/types';
type Props = {
title: string;
@@ -29,6 +29,7 @@ type Props = {
isDraft: boolean;
repositoryIsArchived: boolean;
labels?: Label[];
status?: Status;
};
const Card: FunctionComponent<PropsWithChildren<Props>> = (
@@ -45,6 +46,7 @@ const Card: FunctionComponent<PropsWithChildren<Props>> = (
isDraft,
repositoryIsArchived,
labels,
status,
children,
} = props;
@@ -63,6 +65,7 @@ const Card: FunctionComponent<PropsWithChildren<Props>> = (
isDraft={isDraft}
repositoryIsArchived={repositoryIsArchived}
labels={labels}
status={status}
/>
{children}
</Box>
@@ -37,6 +37,13 @@ const props = {
name: 'documentation',
},
],
status: {
commit: {
statusCheckRollup: {
state: 'SUCCESS',
},
},
},
};
describe('<CardHeader/>', () => {
@@ -54,4 +61,20 @@ describe('<CardHeader/>', () => {
await renderInTestApp(<CardHeader {...propsWithNoLabels} />);
expect(screen.queryByRole('listitem')).not.toBeInTheDocument();
});
it('finds commit status in PR Card Header', async () => {
await renderInTestApp(<CardHeader {...props} />);
expect(screen.getByText('Commit Status:')).toBeInTheDocument();
expect(props.status?.commit.statusCheckRollup.state).toBeTruthy();
});
it('does not find commit status in PR Card Header when PR does not include status', async () => {
const propsWithNoStatus = {
...props,
status: undefined,
};
await renderInTestApp(<CardHeader {...propsWithNoStatus} />);
expect(CardHeader.defaultProps?.status).toBeUndefined();
expect(screen.queryByText('Commit Status:')).not.toBeInTheDocument();
});
});
@@ -19,7 +19,7 @@ import { getElapsedTime } from '../../utils/functions';
import { UserHeader } from '../UserHeader';
import { DraftPrIcon } from '../icons/DraftPr';
import UnarchiveIcon from '@material-ui/icons/Unarchive';
import { Label } from '../../utils/types';
import { Label, Status } from '../../utils/types';
import { useFormClasses } from './styles';
type Props = {
@@ -32,6 +32,7 @@ type Props = {
isDraft: boolean;
repositoryIsArchived: boolean;
labels?: Label[];
status?: Status;
};
const CardHeader: FunctionComponent<Props> = (props: Props) => {
@@ -47,6 +48,7 @@ const CardHeader: FunctionComponent<Props> = (props: Props) => {
isDraft,
repositoryIsArchived,
labels,
status,
} = props;
return (
@@ -86,6 +88,14 @@ const CardHeader: FunctionComponent<Props> = (props: Props) => {
</Typography>
)}
</Box>
{status && (
<Box display="flex" alignItems="center" flexWrap="wrap" paddingTop={1}>
<Typography variant="body2" component="p">
Commit Status:{' '}
<strong>{status.commit.statusCheckRollup.state}</strong>
</Typography>
</Box>
)}
{labels && (
<Box display="flex" alignItems="center" flexWrap="wrap" paddingTop={1}>
{labels.map(data => {
@@ -15,7 +15,7 @@
*/
import React from 'react';
import { EntityTeamPullRequestsCard } from '../EntityTeamPullRequestsCard';
import { PullRequestsColumn } from '../../utils/types';
import { PullRequestsColumn, Status } from '../../utils/types';
import { render } from '@testing-library/react';
import { fireEvent } from '@testing-library/react';
@@ -33,13 +33,21 @@ jest.mock('../../hooks/useUserRepositoriesAndTeam', () => {
});
jest.mock('../../hooks/usePullRequestsByTeam', () => {
const buildPullRequest = (
prTitle: string,
authorLogin: string,
repoName: string,
isDraft: boolean,
isArchived: boolean,
) => {
const buildPullRequest = ({
prTitle,
authorLogin,
repoName,
isDraft,
isArchived,
status,
}: {
prTitle: string;
authorLogin: string;
repoName: string;
isDraft: boolean;
isArchived: boolean;
status: Status;
}) => {
return {
id: 'id',
title: prTitle,
@@ -62,6 +70,9 @@ jest.mock('../../hooks/usePullRequestsByTeam', () => {
labels: {
nodes: [],
},
commits: {
nodes: status,
},
isDraft: isDraft,
author: {
login: authorLogin,
@@ -77,62 +88,118 @@ jest.mock('../../hooks/usePullRequestsByTeam', () => {
{
title: 'column',
content: [
buildPullRequest(
'non-team-non-draft-non-archive',
'non-team-member',
'team-repo',
false,
false,
),
buildPullRequest(
'non-team-non-draft-is-archive',
'non-team-member',
'team-repo',
false,
true,
),
buildPullRequest(
'non-team-is-draft-non-archive',
'non-team-member',
'team-repo',
true,
false,
),
buildPullRequest(
'non-team-is-draft-is-archive',
'non-team-member',
'team-repo',
true,
true,
),
buildPullRequest(
'is-team-non-draft-non-archive',
'team-member',
'non-team-repo',
false,
false,
),
buildPullRequest(
'is-team-non-draft-is-archive',
'team-member',
'non-team-repo',
false,
true,
),
buildPullRequest(
'is-team-is-draft-non-archive',
'team-member',
'non-team-repo',
true,
false,
),
buildPullRequest(
'is-team-is-draft-is-archive',
'team-member',
'non-team-repo',
true,
true,
),
buildPullRequest({
prTitle: 'non-team-non-draft-non-archive',
authorLogin: 'non-team-member',
repoName: 'team-repo',
isDraft: false,
isArchived: false,
status: {
commit: {
statusCheckRollup: {
state: 'FAILURE',
},
},
},
}),
buildPullRequest({
prTitle: 'non-team-non-draft-is-archive',
authorLogin: 'non-team-member',
repoName: 'team-repo',
isDraft: false,
isArchived: true,
status: {
commit: {
statusCheckRollup: {
state: 'FAILURE',
},
},
},
}),
buildPullRequest({
prTitle: 'non-team-is-draft-non-archive',
authorLogin: 'non-team-member',
repoName: 'team-repo',
isDraft: true,
isArchived: false,
status: {
commit: {
statusCheckRollup: {
state: 'FAILURE',
},
},
},
}),
buildPullRequest({
prTitle: 'non-team-is-draft-is-archive',
authorLogin: 'non-team-member',
repoName: 'team-repo',
isDraft: true,
isArchived: true,
status: {
commit: {
statusCheckRollup: {
state: 'SUCCESS',
},
},
},
}),
buildPullRequest({
prTitle: 'is-team-non-draft-non-archive',
authorLogin: 'team-member',
repoName: 'non-team-repo',
isDraft: false,
isArchived: false,
status: {
commit: {
statusCheckRollup: {
state: 'FAILURE',
},
},
},
}),
buildPullRequest({
prTitle: 'is-team-non-draft-is-archive',
authorLogin: 'team-member',
repoName: 'non-team-repo',
isDraft: false,
isArchived: true,
status: {
commit: {
statusCheckRollup: {
state: 'FAILURE',
},
},
},
}),
buildPullRequest({
prTitle: 'is-team-is-draft-non-archive',
authorLogin: 'team-member',
repoName: 'non-team-repo',
isDraft: true,
isArchived: false,
status: {
commit: {
statusCheckRollup: {
state: 'FAILURE',
},
},
},
}),
buildPullRequest({
prTitle: 'is-team-is-draft-is-archive',
authorLogin: 'team-member',
repoName: 'non-team-repo',
isDraft: true,
isArchived: true,
status: {
commit: {
statusCheckRollup: {
state: 'SUCCESS',
},
},
},
}),
],
},
];
@@ -114,6 +114,7 @@ const EntityTeamPullRequestsCard = (props: EntityTeamPullRequestsCardProps) => {
repository,
isDraft,
labels,
commits,
},
index,
) =>
@@ -137,6 +138,7 @@ const EntityTeamPullRequestsCard = (props: EntityTeamPullRequestsCardProps) => {
repositoryIsArchived={repository.isArchived}
isDraft={isDraft}
labels={labels.nodes}
status={commits.nodes}
/>
),
)}
@@ -15,7 +15,7 @@
*/
import React from 'react';
import { EntityTeamPullRequestsContent } from '../EntityTeamPullRequestsContent';
import { PullRequestsColumn } from '../../utils/types';
import { PullRequestsColumn, Status } from '../../utils/types';
import { render } from '@testing-library/react';
import { fireEvent } from '@testing-library/react';
@@ -33,13 +33,21 @@ jest.mock('../../hooks/useUserRepositoriesAndTeam', () => {
});
jest.mock('../../hooks/usePullRequestsByTeam', () => {
const buildPullRequest = (
prTitle: string,
authorLogin: string,
repoName: string,
isDraft: boolean,
isArchived: boolean,
) => {
const buildPullRequest = ({
prTitle,
authorLogin,
repoName,
isDraft,
isArchived,
status,
}: {
prTitle: string;
authorLogin: string;
repoName: string;
isDraft: boolean;
isArchived: boolean;
status: Status;
}) => {
return {
id: 'id',
title: prTitle,
@@ -62,6 +70,9 @@ jest.mock('../../hooks/usePullRequestsByTeam', () => {
labels: {
nodes: [],
},
commits: {
nodes: status,
},
isDraft: isDraft,
author: {
login: authorLogin,
@@ -77,62 +88,118 @@ jest.mock('../../hooks/usePullRequestsByTeam', () => {
{
title: 'column',
content: [
buildPullRequest(
'non-team-non-draft-non-archive',
'non-team-member',
'team-repo',
false,
false,
),
buildPullRequest(
'non-team-non-draft-is-archive',
'non-team-member',
'team-repo',
false,
true,
),
buildPullRequest(
'non-team-is-draft-non-archive',
'non-team-member',
'team-repo',
true,
false,
),
buildPullRequest(
'non-team-is-draft-is-archive',
'non-team-member',
'team-repo',
true,
true,
),
buildPullRequest(
'is-team-non-draft-non-archive',
'team-member',
'non-team-repo',
false,
false,
),
buildPullRequest(
'is-team-non-draft-is-archive',
'team-member',
'non-team-repo',
false,
true,
),
buildPullRequest(
'is-team-is-draft-non-archive',
'team-member',
'non-team-repo',
true,
false,
),
buildPullRequest(
'is-team-is-draft-is-archive',
'team-member',
'non-team-repo',
true,
true,
),
buildPullRequest({
prTitle: 'non-team-non-draft-non-archive',
authorLogin: 'non-team-member',
repoName: 'team-repo',
isDraft: false,
isArchived: false,
status: {
commit: {
statusCheckRollup: {
state: 'FAILURE',
},
},
},
}),
buildPullRequest({
prTitle: 'non-team-non-draft-is-archive',
authorLogin: 'non-team-member',
repoName: 'team-repo',
isDraft: false,
isArchived: true,
status: {
commit: {
statusCheckRollup: {
state: 'FAILURE',
},
},
},
}),
buildPullRequest({
prTitle: 'non-team-is-draft-non-archive',
authorLogin: 'non-team-member',
repoName: 'team-repo',
isDraft: true,
isArchived: false,
status: {
commit: {
statusCheckRollup: {
state: 'FAILURE',
},
},
},
}),
buildPullRequest({
prTitle: 'non-team-is-draft-is-archive',
authorLogin: 'non-team-member',
repoName: 'team-repo',
isDraft: true,
isArchived: true,
status: {
commit: {
statusCheckRollup: {
state: 'SUCCESS',
},
},
},
}),
buildPullRequest({
prTitle: 'is-team-non-draft-non-archive',
authorLogin: 'team-member',
repoName: 'non-team-repo',
isDraft: false,
isArchived: false,
status: {
commit: {
statusCheckRollup: {
state: 'FAILURE',
},
},
},
}),
buildPullRequest({
prTitle: 'is-team-non-draft-is-archive',
authorLogin: 'team-member',
repoName: 'non-team-repo',
isDraft: false,
isArchived: true,
status: {
commit: {
statusCheckRollup: {
state: 'FAILURE',
},
},
},
}),
buildPullRequest({
prTitle: 'is-team-is-draft-non-archive',
authorLogin: 'team-member',
repoName: 'non-team-repo',
isDraft: true,
isArchived: false,
status: {
commit: {
statusCheckRollup: {
state: 'FAILURE',
},
},
},
}),
buildPullRequest({
prTitle: 'is-team-is-draft-is-archive',
authorLogin: 'team-member',
repoName: 'non-team-repo',
isDraft: true,
isArchived: true,
status: {
commit: {
statusCheckRollup: {
state: 'SUCCESS',
},
},
},
}),
],
},
];
@@ -152,7 +219,7 @@ describe('EntityTeamPullRequestsContent', () => {
describe('non-team PRs', () => {
describe('non-draft PRs', () => {
it('should show non-team PRs for un-archived repos when archived option is not checked', async () => {
const { getByText, getAllByText, queryAllByTitle } = await render(
const { getByText, getAllByText, queryAllByTitle } = render(
<EntityTeamPullRequestsContent />,
);
expect(getByText('non-team-non-draft-non-archive')).toBeInTheDocument();
@@ -162,8 +229,9 @@ describe('EntityTeamPullRequestsContent', () => {
});
it('should show non-team PRs for archived repos when archived option is checked', async () => {
const { getByText, getAllByText, getByTitle, queryAllByTitle } =
await render(<EntityTeamPullRequestsContent />);
const { getByText, getAllByText, getByTitle, queryAllByTitle } = render(
<EntityTeamPullRequestsContent />,
);
const archiveToggle = getByTitle('Show archived repos');
fireEvent.click(archiveToggle);
expect(getByText('non-team-non-draft-is-archive')).toBeInTheDocument();
@@ -175,8 +243,9 @@ describe('EntityTeamPullRequestsContent', () => {
describe('draft PRs', () => {
it('should show draft non-team PRs for un-archived repos when archived option is not checked', async () => {
const { getByText, getAllByText, getByTitle, queryAllByTitle } =
await render(<EntityTeamPullRequestsContent />);
const { getByText, getAllByText, getByTitle, queryAllByTitle } = render(
<EntityTeamPullRequestsContent />,
);
const draftToggle = getByTitle('Show draft PRs');
fireEvent.click(draftToggle);
expect(getByText('non-team-is-draft-non-archive')).toBeInTheDocument();
@@ -186,8 +255,9 @@ describe('EntityTeamPullRequestsContent', () => {
});
it('should show draft non-team PRs for archived repos when archived option is checked', async () => {
const { getByText, getAllByText, getByTitle, queryAllByTitle } =
await render(<EntityTeamPullRequestsContent />);
const { getByText, getAllByText, getByTitle, queryAllByTitle } = render(
<EntityTeamPullRequestsContent />,
);
const draftToggle = getByTitle('Show draft PRs');
fireEvent.click(draftToggle);
const archiveToggle = getByTitle('Show archived repos');
@@ -203,8 +273,9 @@ describe('EntityTeamPullRequestsContent', () => {
describe('team PRs', () => {
describe('non-draft PRs', () => {
it('should show team PRs for un-archived repos when archived option is not checked', async () => {
const { getByText, getAllByText, getByTitle, queryAllByTitle } =
await render(<EntityTeamPullRequestsContent />);
const { getByText, getAllByText, getByTitle, queryAllByTitle } = render(
<EntityTeamPullRequestsContent />,
);
const teamToggle = getByTitle('Show PRs from your team');
fireEvent.click(teamToggle);
expect(getByText('is-team-non-draft-non-archive')).toBeInTheDocument();
@@ -214,8 +285,9 @@ describe('EntityTeamPullRequestsContent', () => {
});
it('should show team PRs for archived repos when archived option is checked', async () => {
const { getByText, getAllByText, getByTitle, queryAllByTitle } =
await render(<EntityTeamPullRequestsContent />);
const { getByText, getAllByText, getByTitle, queryAllByTitle } = render(
<EntityTeamPullRequestsContent />,
);
const teamToggle = getByTitle('Show PRs from your team');
fireEvent.click(teamToggle);
const archiveToggle = getByTitle('Show archived repos');
@@ -229,8 +301,9 @@ describe('EntityTeamPullRequestsContent', () => {
describe('draft PRs', () => {
it('should show draft team PRs for un-archived repos when archived option is not checked', async () => {
const { getByText, getAllByText, getByTitle, queryAllByTitle } =
await render(<EntityTeamPullRequestsContent />);
const { getByText, getAllByText, getByTitle, queryAllByTitle } = render(
<EntityTeamPullRequestsContent />,
);
const teamToggle = getByTitle('Show PRs from your team');
fireEvent.click(teamToggle);
const draftToggle = getByTitle('Show draft PRs');
@@ -242,8 +315,9 @@ describe('EntityTeamPullRequestsContent', () => {
});
it('should show draft team PRs for archived repos when archived option is checked', async () => {
const { getByText, getAllByText, getByTitle, queryAllByTitle } =
await render(<EntityTeamPullRequestsContent />);
const { getByText, getAllByText, getByTitle, queryAllByTitle } = render(
<EntityTeamPullRequestsContent />,
);
const teamToggle = getByTitle('Show PRs from your team');
fireEvent.click(teamToggle);
const draftToggle = getByTitle('Show draft PRs');
@@ -103,6 +103,7 @@ const EntityTeamPullRequestsContent = (
author,
url,
latestReviews,
commits,
repository,
isDraft,
labels,
@@ -125,6 +126,7 @@ const EntityTeamPullRequestsContent = (
author={author}
url={url}
reviews={latestReviews.nodes}
status={commits.nodes}
repositoryName={repository.name}
repositoryIsArchived={repository.isArchived}
isDraft={isDraft}
@@ -19,7 +19,7 @@ import {
getChangeRequests,
getCommentedReviews,
} from '../../utils/functions';
import { Reviews, Author, Label } from '../../utils/types';
import { Reviews, Author, Label, Status } from '../../utils/types';
import { Card } from '../Card';
import { UserHeaderList } from '../UserHeaderList';
@@ -30,6 +30,7 @@ type Props = {
author: Author;
url: string;
reviews: Reviews;
status?: Status;
repositoryName: string;
repositoryIsArchived: boolean;
isDraft: boolean;
@@ -44,6 +45,7 @@ const PullRequestCard: FunctionComponent<Props> = (props: Props) => {
author,
url,
reviews,
status,
repositoryName,
repositoryIsArchived,
isDraft,
@@ -66,6 +68,7 @@ const PullRequestCard: FunctionComponent<Props> = (props: Props) => {
isDraft={isDraft}
repositoryIsArchived={repositoryIsArchived}
labels={labels}
status={status}
>
{!!approvedReviews.length && (
<UserHeaderList
@@ -81,6 +81,14 @@ export type Label = {
name: string;
};
export type Status = {
commit: {
statusCheckRollup: {
state: string;
};
};
};
export type PullRequest = {
id: string;
repository: Repository;
@@ -90,6 +98,9 @@ export type PullRequest = {
latestReviews: {
nodes: Reviews;
};
commits: {
nodes: Status;
};
mergeable: boolean;
state: string;
reviewDecision: ReviewDecision | null;
+2 -1
View File
@@ -6820,7 +6820,7 @@ __metadata:
languageName: unknown
linkType: soft
"@backstage/plugin-github-pull-requests-board@workspace:plugins/github-pull-requests-board":
"@backstage/plugin-github-pull-requests-board@workspace:^, @backstage/plugin-github-pull-requests-board@workspace:plugins/github-pull-requests-board":
version: 0.0.0-use.local
resolution: "@backstage/plugin-github-pull-requests-board@workspace:plugins/github-pull-requests-board"
dependencies:
@@ -27268,6 +27268,7 @@ __metadata:
"@backstage/plugin-gcalendar": "workspace:^"
"@backstage/plugin-gcp-projects": "workspace:^"
"@backstage/plugin-github-actions": "workspace:^"
"@backstage/plugin-github-pull-requests-board": "workspace:^"
"@backstage/plugin-gocd": "workspace:^"
"@backstage/plugin-graphiql": "workspace:^"
"@backstage/plugin-home": "workspace:^"