Skip to content

Commit

Permalink
Add PR Check status badges (#848)
Browse files Browse the repository at this point in the history
  • Loading branch information
teejae authored Jul 26, 2021
1 parent 627508d commit 1347e4c
Show file tree
Hide file tree
Showing 8 changed files with 192 additions and 29 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
"@types/lodash": "^4.14.171",
"assert-never": "^1.2.1",
"bootstrap": "^4",
"graphql": "^15.5.1",
"graphql-request": "^3.4.0",
"lodash": "^4.17.21",
"mobx": "^6.3.2",
"mobx-react-lite": "^3.2.0",
Expand Down
43 changes: 41 additions & 2 deletions src/components/PullRequestStatus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
OutgoingState,
PullRequestState,
} from "../filtering/status";
import { CheckStatus } from "../github-api/api";

const StateBox = styled.div`
padding: 0 8px;
Expand Down Expand Up @@ -53,6 +54,22 @@ const APPROVED_BY_EVERONE = (
</SpacedBadge>
);

const CHECK_STATUS_PASSED = (
<SpacedBadge pill variant="success" key="check-status-passed">
Checks Pass
</SpacedBadge>
);
const CHECK_STATUS_FAILED = (
<SpacedBadge pill variant="danger" key="check-status-passed">
Checks Fail
</SpacedBadge>
);
const CHECK_STATUS_PENDING = (
<SpacedBadge pill variant="warning" key="check-status-passed">
Checks Pending
</SpacedBadge>
);

const CHANGES_REQUESTED = (
<SpacedBadge pill variant="warning" key="changes-requested">
Changes requested
Expand Down Expand Up @@ -99,11 +116,30 @@ function getBadges(state: PullRequestState): JSX.Element[] {
return badges;
}

function getCheckStatusBadge(checkStatus?: CheckStatus): JSX.Element[] {
switch (checkStatus) {
case "PENDING":
return [CHECK_STATUS_PENDING];
case "SUCCESS":
return [CHECK_STATUS_PASSED];
case "FAILURE":
return [CHECK_STATUS_FAILED];
case "ERROR":
case "EXPECTED":
default:
return [];
}
}

function getIncomingStateBadges(state: IncomingState): JSX.Element[] {
const badges: JSX.Element[] = [];
badges.push(...getCheckStatusBadge(state.checkStatus));

if (state.newReviewRequested) {
return [UNREVIEWED];
badges.push(UNREVIEWED);
return badges;
}
const badges: JSX.Element[] = [];

if (state.authorResponded) {
badges.push(AUTHOR_REPLIED);
}
Expand All @@ -115,6 +151,8 @@ function getIncomingStateBadges(state: IncomingState): JSX.Element[] {

function getOutgoingStateBadges(state: OutgoingState): JSX.Element[] {
const badges: JSX.Element[] = [];
badges.push(...getCheckStatusBadge(state.checkStatus));

if (state.mergeable) {
badges.push(MERGEABLE);
}
Expand All @@ -128,5 +166,6 @@ function getOutgoingStateBadges(state: OutgoingState): JSX.Element[] {
badges.push(NO_REVIEWER_ASSIGNED);
}
}

return badges;
}
13 changes: 13 additions & 0 deletions src/filtering/status.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { CheckStatus } from "../github-api/api";
import { PullRequest, ReviewState } from "../storage/loaded-state";
import { userPreviouslyReviewed } from "./reviewed";
import {
Expand Down Expand Up @@ -46,6 +47,7 @@ function incomingPullRequestState(
newCommit: hasReviewed && hasNewCommit,
directlyAdded: (pr.requestedReviewers || []).includes(currentUserLogin),
teams: pr.requestedTeams || [],
checkStatus: pr.checkStatus,
};
}

Expand Down Expand Up @@ -110,6 +112,7 @@ function outgoingPullRequestState(
changesRequested: states.has("CHANGES_REQUESTED"),
mergeable: pr.mergeable === true,
approvedByEveryone: states.has("APPROVED") && states.size === 1,
checkStatus: pr.checkStatus,
};
}

Expand Down Expand Up @@ -154,6 +157,11 @@ export interface IncomingState {
* List of team names requested.
*/
teams: string[];

/**
* Current check status of tests.
*/
checkStatus?: CheckStatus;
}

/**
Expand Down Expand Up @@ -200,6 +208,11 @@ export interface OutgoingState {
* True if the PR was approved by all reviewers.
*/
approvedByEveryone: boolean;

/**
* Current check status of tests.
*/
checkStatus?: CheckStatus;
}

export function isReviewRequired(
Expand Down
24 changes: 24 additions & 0 deletions src/github-api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,30 @@ export interface GitHubApi {
): Promise<
GetResponseDataTypeFromEndpointMethod<Octokit["pulls"]["listCommits"]>
>;

/**
* Returns the current status fields for a pull request.
*/
loadPullRequestStatus(pr: PullRequestReference): Promise<PullRequestStatus>;
}

// Ref: https://docs.github.com/en/graphql/reference/enums#pullrequestreviewdecision
export type ReviewDecision =
| "APPROVED"
| "CHANGES_REQUESTED"
| "REVIEW_REQUIRED";

// Ref: https://docs.github.com/en/graphql/reference/enums#statusstate
export type CheckStatus =
| "ERROR"
| "EXPECTED"
| "FAILURE"
| "PENDING"
| "SUCCESS";

export interface PullRequestStatus {
reviewDecision: ReviewDecision;
checkStatus?: CheckStatus;
}

export interface RepoReference {
Expand Down
40 changes: 40 additions & 0 deletions src/github-api/implementation.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { throttling } from "@octokit/plugin-throttling";
import { Octokit } from "@octokit/rest";
import { GitHubApi } from "./api";
import { GraphQLClient, gql } from "graphql-request";

const ThrottledOctokit = Octokit.plugin(throttling as any);
const graphQLEndpoint = "https://api.github.com/graphql";

interface ThrottlingOptions {
method: string;
Expand Down Expand Up @@ -42,6 +44,13 @@ export function buildGitHubApi(token: string): GitHubApi {
},
},
});

const graphQLClient = new GraphQLClient(graphQLEndpoint, {
headers: {
Authorization: `Bearer ${token}`,
},
});

return {
async loadAuthenticatedUser() {
const response = await octokit.users.getAuthenticated({});
Expand Down Expand Up @@ -89,5 +98,36 @@ export function buildGitHubApi(token: string): GitHubApi {
})
);
},
loadPullRequestStatus(pr) {
const query = gql`
query {
repository(owner: "${pr.repo.owner}", name: "${pr.repo.name}") {
pullRequest(number: ${pr.number}) {
reviewDecision
commits(last: 1) {
nodes {
commit {
statusCheckRollup {
state
}
}
}
}
}
}
}
`;

return graphQLClient.request(query).then((response) => {
const result = response.repository.pullRequest;
const reviewDecision = result.reviewDecision;
const checkStatus =
result.commits.nodes?.[0]?.commit.statusCheckRollup?.state;
return {
reviewDecision,
checkStatus,
};
});
},
};
}
62 changes: 37 additions & 25 deletions src/loading/internal/pull-requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { RestEndpointMethodTypes } from "@octokit/rest";
import {
GitHubApi,
PullRequestReference,
PullRequestStatus,
RepoReference,
} from "../../github-api/api";
import { nonEmptyItems } from "../../helpers";
Expand Down Expand Up @@ -56,36 +57,44 @@ async function updateCommentsAndReviews(
repo,
number: rawPullRequest.number,
};
const [freshPullRequestDetails, freshReviews, freshComments, freshCommits] =
await Promise.all([
githubApi.loadPullRequestDetails(pr),
githubApi.loadReviews(pr).then((reviews) =>
reviews.map((review) => ({
authorLogin: review.user ? review.user.login : "",
state: review.state as ReviewState,
submittedAt: review.submitted_at,
}))
),
githubApi.loadComments(pr).then((comments) =>
comments.map((comment) => ({
authorLogin: comment.user ? comment.user.login : "",
createdAt: comment.created_at,
}))
),
githubApi.loadCommits(pr).then((commits) =>
commits.map((commit) => ({
authorLogin: commit.author ? commit.author.login : "",
createdAt: commit.commit.author?.date,
}))
),
]);
const [
freshPullRequestDetails,
freshReviews,
freshComments,
freshCommits,
pullRequestStatus,
] = await Promise.all([
githubApi.loadPullRequestDetails(pr),
githubApi.loadReviews(pr).then((reviews) =>
reviews.map((review) => ({
authorLogin: review.user ? review.user.login : "",
state: review.state as ReviewState,
submittedAt: review.submitted_at,
}))
),
githubApi.loadComments(pr).then((comments) =>
comments.map((comment) => ({
authorLogin: comment.user ? comment.user.login : "",
createdAt: comment.created_at,
}))
),
githubApi.loadCommits(pr).then((commits) =>
commits.map((commit) => ({
authorLogin: commit.author ? commit.author.login : "",
createdAt: commit.commit.author?.date,
}))
),
githubApi.loadPullRequestStatus(pr),
]);

return pullRequestFromResponse(
rawPullRequest,
freshPullRequestDetails,
freshReviews,
freshComments,
freshCommits,
isReviewRequested
isReviewRequested,
pullRequestStatus
);
}

Expand All @@ -95,7 +104,8 @@ function pullRequestFromResponse(
reviews: Review[],
comments: Comment[],
commits: Commit[],
reviewRequested: boolean
reviewRequested: boolean,
status: PullRequestStatus
): PullRequest {
const repo = extractRepo(response);
return {
Expand Down Expand Up @@ -127,6 +137,8 @@ function pullRequestFromResponse(
reviews,
comments,
commits,
reviewDecision: status.reviewDecision,
checkStatus: status.checkStatus,
};
}

Expand Down
9 changes: 8 additions & 1 deletion src/storage/loaded-state.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { PullRequestReference } from "../github-api/api";
import {
CheckStatus,
PullRequestReference,
ReviewDecision,
} from "../github-api/api";

export interface LoadedState {
/**
Expand Down Expand Up @@ -76,6 +80,9 @@ export interface PullRequest {
reviews: Review[];
comments: Comment[];
commits?: Commit[];

reviewDecision: ReviewDecision;
checkStatus?: CheckStatus;
}

export interface Comment {
Expand Down
28 changes: 27 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2793,6 +2793,13 @@ cosmiconfig@^6.0.0:
path-type "^4.0.0"
yaml "^1.7.2"

cross-fetch@^3.0.6:
version "3.1.4"
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.4.tgz#9723f3a3a247bf8b89039f3a380a9244e8fa2f39"
integrity sha512-1eAtFWdIubi6T4XPy6ei9iUFoKpUkIF971QLN8lIvvvwueI65+Nw5haMNKUwfJxabqlIIDODJKGrQ66gxC0PbQ==
dependencies:
node-fetch "2.6.1"

cross-spawn@^6.0.0:
version "6.0.5"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
Expand Down Expand Up @@ -3593,6 +3600,11 @@ extglob@^2.0.4:
snapdragon "^0.8.1"
to-regex "^3.0.1"

extract-files@^9.0.0:
version "9.0.0"
resolved "https://registry.yarnpkg.com/extract-files/-/extract-files-9.0.0.tgz#8a7744f2437f81f5ed3250ed9f1550de902fe54a"
integrity sha512-CvdFfHkC95B4bBBk36hcEmvdR2awOdhhVUYH6S/zrVj3477zven/fJMYg7121h4T1xHZC+tetUpubpAhxwI7hQ==

fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
Expand Down Expand Up @@ -3918,6 +3930,20 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.2.4:
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee"
integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==

graphql-request@^3.4.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/graphql-request/-/graphql-request-3.4.0.tgz#3a400cd5511eb3c064b1873afb059196bbea9c2b"
integrity sha512-acrTzidSlwAj8wBNO7Q/UQHS8T+z5qRGquCQRv9J1InwR01BBWV9ObnoE+JS5nCCEj8wSGS0yrDXVDoRiKZuOg==
dependencies:
cross-fetch "^3.0.6"
extract-files "^9.0.0"
form-data "^3.0.0"

graphql@^15.5.1:
version "15.5.1"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.5.1.tgz#f2f84415d8985e7b84731e7f3536f8bb9d383aad"
integrity sha512-FeTRX67T3LoE3LWAxxOlW2K3Bz+rMYAC18rRguK4wgXaTZMiJwSUwDmPFo3UadAKbzirKIg5Qy+sNJXbpPRnQw==

gzip-size@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-6.0.0.tgz#065367fd50c239c0671cbcbad5be3e2eeb10e462"
Expand Down Expand Up @@ -5510,7 +5536,7 @@ no-case@^3.0.4:
lower-case "^2.0.2"
tslib "^2.0.3"

node-fetch@^2.6.1:
node-fetch@2.6.1, node-fetch@^2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
Expand Down

0 comments on commit 1347e4c

Please sign in to comment.