diff --git a/.changeset/chilled-dolphins-tap.md b/.changeset/chilled-dolphins-tap.md new file mode 100644 index 0000000000..c4f4c98aec --- /dev/null +++ b/.changeset/chilled-dolphins-tap.md @@ -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 diff --git a/packages/app/package.json b/packages/app/package.json index a0c48e66fe..7936db0090 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -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:^", diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx index 4d5f65cbbe..b0b227be92 100644 --- a/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/app/src/components/catalog/EntityPage.tsx @@ -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 = ( + + + ); diff --git a/plugins/github-pull-requests-board/src/api/useGetPullRequestDetails.ts b/plugins/github-pull-requests-board/src/api/useGetPullRequestDetails.ts index 75f4d37cf4..8b919ce3d6 100644 --- a/plugins/github-pull-requests-board/src/api/useGetPullRequestDetails.ts +++ b/plugins/github-pull-requests-board/src/api/useGetPullRequestDetails.ts @@ -57,6 +57,15 @@ export const useGetPullRequestDetails = () => { state } } + commits(last: 1) { + nodes { + commit { + statusCheckRollup { + state + } + } + } + } mergeable state reviewDecision diff --git a/plugins/github-pull-requests-board/src/components/Card/Card.tsx b/plugins/github-pull-requests-board/src/components/Card/Card.tsx index 4fe833fa67..6642cdeb06 100644 --- a/plugins/github-pull-requests-board/src/components/Card/Card.tsx +++ b/plugins/github-pull-requests-board/src/components/Card/Card.tsx @@ -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> = ( @@ -45,6 +46,7 @@ const Card: FunctionComponent> = ( isDraft, repositoryIsArchived, labels, + status, children, } = props; @@ -63,6 +65,7 @@ const Card: FunctionComponent> = ( isDraft={isDraft} repositoryIsArchived={repositoryIsArchived} labels={labels} + status={status} /> {children} diff --git a/plugins/github-pull-requests-board/src/components/Card/CardHeader.test.tsx b/plugins/github-pull-requests-board/src/components/Card/CardHeader.test.tsx index 3171bff10f..02f8b8c837 100644 --- a/plugins/github-pull-requests-board/src/components/Card/CardHeader.test.tsx +++ b/plugins/github-pull-requests-board/src/components/Card/CardHeader.test.tsx @@ -37,6 +37,13 @@ const props = { name: 'documentation', }, ], + status: { + commit: { + statusCheckRollup: { + state: 'SUCCESS', + }, + }, + }, }; describe('', () => { @@ -54,4 +61,20 @@ describe('', () => { await renderInTestApp(); expect(screen.queryByRole('listitem')).not.toBeInTheDocument(); }); + + it('finds commit status in PR Card Header', async () => { + await renderInTestApp(); + 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(); + expect(CardHeader.defaultProps?.status).toBeUndefined(); + expect(screen.queryByText('Commit Status:')).not.toBeInTheDocument(); + }); }); diff --git a/plugins/github-pull-requests-board/src/components/Card/CardHeader.tsx b/plugins/github-pull-requests-board/src/components/Card/CardHeader.tsx index c59349b95f..15919d4297 100644 --- a/plugins/github-pull-requests-board/src/components/Card/CardHeader.tsx +++ b/plugins/github-pull-requests-board/src/components/Card/CardHeader.tsx @@ -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) => { @@ -47,6 +48,7 @@ const CardHeader: FunctionComponent = (props: Props) => { isDraft, repositoryIsArchived, labels, + status, } = props; return ( @@ -86,6 +88,14 @@ const CardHeader: FunctionComponent = (props: Props) => { )} + {status && ( + + + Commit Status:{' '} + {status.commit.statusCheckRollup.state} + + + )} {labels && ( {labels.map(data => { diff --git a/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsCard/EntityTeamPullRequestsCard.test.tsx b/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsCard/EntityTeamPullRequestsCard.test.tsx index 219e587971..a4e0240cbd 100644 --- a/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsCard/EntityTeamPullRequestsCard.test.tsx +++ b/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsCard/EntityTeamPullRequestsCard.test.tsx @@ -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', + }, + }, + }, + }), ], }, ]; diff --git a/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsCard/EntityTeamPullRequestsCard.tsx b/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsCard/EntityTeamPullRequestsCard.tsx index 25ef32362b..9285d6b032 100644 --- a/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsCard/EntityTeamPullRequestsCard.tsx +++ b/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsCard/EntityTeamPullRequestsCard.tsx @@ -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} /> ), )} diff --git a/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsContent/EntityTeamPullRequestsContent.test.tsx b/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsContent/EntityTeamPullRequestsContent.test.tsx index 35c323a4f1..dc02030f47 100644 --- a/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsContent/EntityTeamPullRequestsContent.test.tsx +++ b/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsContent/EntityTeamPullRequestsContent.test.tsx @@ -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( , ); 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(); + const { getByText, getAllByText, getByTitle, queryAllByTitle } = render( + , + ); 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(); + const { getByText, getAllByText, getByTitle, queryAllByTitle } = render( + , + ); 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(); + const { getByText, getAllByText, getByTitle, queryAllByTitle } = render( + , + ); 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(); + const { getByText, getAllByText, getByTitle, queryAllByTitle } = render( + , + ); 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(); + const { getByText, getAllByText, getByTitle, queryAllByTitle } = render( + , + ); 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(); + const { getByText, getAllByText, getByTitle, queryAllByTitle } = render( + , + ); 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(); + const { getByText, getAllByText, getByTitle, queryAllByTitle } = render( + , + ); const teamToggle = getByTitle('Show PRs from your team'); fireEvent.click(teamToggle); const draftToggle = getByTitle('Show draft PRs'); diff --git a/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsContent/EntityTeamPullRequestsContent.tsx b/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsContent/EntityTeamPullRequestsContent.tsx index f901acb119..668995a306 100644 --- a/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsContent/EntityTeamPullRequestsContent.tsx +++ b/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsContent/EntityTeamPullRequestsContent.tsx @@ -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} diff --git a/plugins/github-pull-requests-board/src/components/PullRequestCard/PullRequestCard.tsx b/plugins/github-pull-requests-board/src/components/PullRequestCard/PullRequestCard.tsx index e9ad2aeab6..59f7892d0e 100644 --- a/plugins/github-pull-requests-board/src/components/PullRequestCard/PullRequestCard.tsx +++ b/plugins/github-pull-requests-board/src/components/PullRequestCard/PullRequestCard.tsx @@ -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) => { author, url, reviews, + status, repositoryName, repositoryIsArchived, isDraft, @@ -66,6 +68,7 @@ const PullRequestCard: FunctionComponent = (props: Props) => { isDraft={isDraft} repositoryIsArchived={repositoryIsArchived} labels={labels} + status={status} > {!!approvedReviews.length && (