Skip to content

Commit

Permalink
Use graphQL to fetch check runs and check suites, fixes #1105, fixes #…
Browse files Browse the repository at this point in the history
  • Loading branch information
Rachel Macfarlane authored Aug 13, 2020
1 parent 40dd0d7 commit f821fc0
Show file tree
Hide file tree
Showing 10 changed files with 162 additions and 32 deletions.
5 changes: 2 additions & 3 deletions preview-src/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@
*--------------------------------------------------------------------------------------------*/

import { vscode } from './message';
import { GithubItemStateEnum, IAccount, ReviewState, ILabel, MergeMethod, MergeMethodsAvailability, PullRequestMergeability } from '../src/github/interface';
import { GithubItemStateEnum, IAccount, ReviewState, ILabel, MergeMethod, MergeMethodsAvailability, PullRequestMergeability, PullRequestChecks } from '../src/github/interface';
import { TimelineEvent } from '../src/common/timelineEvent';
import { ReposGetCombinedStatusForRefResponseData } from '@octokit/types';

export interface PullRequest {
number: number;
Expand Down Expand Up @@ -35,7 +34,7 @@ export interface PullRequest {
hasWritePermission: boolean;
pendingCommentText?: string;
pendingCommentDrafts?: { [key: string]: string; };
status: ReposGetCombinedStatusForRefResponseData;
status: PullRequestChecks;
mergeable: PullRequestMergeability;
defaultMergeMethod: MergeMethod;
mergeMethodsAvailability: MergeMethodsAvailability;
Expand Down
1 change: 1 addition & 0 deletions preview-src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ body .comment-container .review-comment-header {
.status-check {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 5px;
margin-left: 15px;
}
Expand Down
10 changes: 6 additions & 4 deletions preview-src/merge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -245,10 +245,12 @@ const StatusCheckDetails = ({ statuses }: Partial<PullRequest['status']>) =>
<div>{
statuses.map(s =>
<div key={s.id} className='status-check'>
<StateIcon state={s.state} />
<Avatar for={{ avatarUrl: s.avatar_url, url: s.url }} />
<span className='status-check-detail-text'>{s.context}{s.description}</span>
<a href={s.target_url}>Details</a>
<div>
<StateIcon state={s.state} />
<Avatar for={{ avatarUrl: s.avatar_url, url: s.url }} />
<span className='status-check-detail-text'>{s.context} {s.description ? `— ${s.description}` : ''}</span>
</div>
{ !!s.target_url ? <a href={s.target_url}>Details</a> : null }
</div>
)
}</div>;
Expand Down
2 changes: 1 addition & 1 deletion src/github/credentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ const link = (url: string, token: string) =>
headers: {
...headers,
authorization: token ? `Bearer ${token}` : '',
Accept: 'application/vnd.github.shadow-cat-preview+json'
Accept: 'application/vnd.github.shadow-cat-preview+json, application/vnd.github.antiope-preview+json'
}
}))).concat(createHttpLink({
uri: `${url}/graphql`,
Expand Down
59 changes: 46 additions & 13 deletions src/github/folderRepositoryManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { IComment } from '../common/comment';
import { Remote, parseRepositoryRemotes } from '../common/remote';
import { TimelineEvent, EventType, ReviewEvent as CommonReviewEvent, isReviewEvent } from '../common/timelineEvent';
import { GitHubRepository, PullRequestData, ItemsData, ViewerPermission } from './githubRepository';
import { IPullRequestsPagingOptions, PRType, ReviewEvent, IPullRequestEditData, PullRequest, IRawFileChange, IAccount, ILabel, RepoAccessAndMergeMethods, PullRequestMergeability, User } from './interface';
import { IPullRequestsPagingOptions, PRType, ReviewEvent, IPullRequestEditData, PullRequest, IRawFileChange, IAccount, ILabel, RepoAccessAndMergeMethods, PullRequestMergeability, User, PullRequestChecks } from './interface';
import { PullRequestGitHelper, PullRequestMetadata } from './pullRequestGitHelper';
import { PullRequestModel, IResolvedPullRequestModel } from './pullRequestModel';
import { GitHubManager } from '../authentication/githubServer';
Expand All @@ -21,7 +21,7 @@ import Logger from '../common/logger';
import { EXTENSION_ID } from '../constants';
import { fromPRUri } from '../common/uri';
import { convertRESTPullRequestToRawPullRequest, parseGraphQLTimelineEvents, getRelatedUsersFromTimelineEvents, parseGraphQLComment, getReactionGroup, convertRESTUserToAccount, convertRESTReviewEvent, parseGraphQLReviewEvent, loginComparator, parseGraphQlIssueComment, convertPullRequestsGetCommentsResponseItemToComment, convertRESTIssueToRawPullRequest, parseGraphQLUser } from './utils';
import { PendingReviewIdResponse, TimelineEventsResponse, PullRequestCommentsResponse, AddCommentResponse, SubmitReviewResponse, DeleteReviewResponse, EditCommentResponse, DeleteReactionResponse, AddReactionResponse, MarkPullRequestReadyForReviewResponse, PullRequestState, UpdatePullRequestResponse, EditIssueCommentResponse, AddIssueCommentResponse, UserResponse, StartReviewResponse } from './graphql';
import { PendingReviewIdResponse, TimelineEventsResponse, PullRequestCommentsResponse, AddCommentResponse, SubmitReviewResponse, DeleteReviewResponse, EditCommentResponse, DeleteReactionResponse, AddReactionResponse, MarkPullRequestReadyForReviewResponse, PullRequestState, UpdatePullRequestResponse, EditIssueCommentResponse, AddIssueCommentResponse, UserResponse, StartReviewResponse, GetChecksResponse, isCheckRun } from './graphql';
import { ITelemetry } from '../common/telemetry';
import { ApiImpl } from '../api/api1';
import { Protocol } from '../common/protocol';
Expand Down Expand Up @@ -830,20 +830,53 @@ export class FolderRepositoryManager implements vscode.Disposable {
return max;
}

async getStatusChecks(pullRequest: PullRequestModel): Promise<OctokitTypes.ReposGetCombinedStatusForRefResponseData | undefined> {
if (!pullRequest.isResolved()) {
return;
}
async getStatusChecks(pullRequest: PullRequestModel): Promise<PullRequestChecks> {
const { query, remote, schema } = await pullRequest.githubRepository.ensure();
const result = await query<GetChecksResponse>({
query: schema.GetChecks,
variables: {
owner: remote.owner,
name: remote.repositoryName,
number: pullRequest.number
}
});

const { remote, octokit } = await pullRequest.githubRepository.ensure();
// We always fetch the status checks for only the last commit, so there should only be one node present
const statusCheckRollup = result.data.repository.pullRequest.commits.nodes[0].commit.statusCheckRollup;

const result = await octokit.repos.getCombinedStatusForRef({
owner: remote.owner,
repo: remote.repositoryName,
ref: pullRequest.head.sha
});
if (!statusCheckRollup) {
return {
state: 'pending',
statuses: []
};
}

return result.data;
return {
state: statusCheckRollup.state.toLowerCase(),
statuses: statusCheckRollup.contexts.nodes.map(context => {
if (isCheckRun(context)) {
return {
id: context.id,
url: context.checkSuite.app?.url,
avatar_url: context.checkSuite.app?.logoUrl,
state: context.conclusion?.toLowerCase() || 'pending',
description: context.title,
context: context.name,
target_url: context.detailsUrl
};
} else {
return {
id: context.id,
url: context.targetUrl,
avatar_url: context.avatarUrl,
state: context.state.toLowerCase(),
description: context.description,
context: context.context,
target_url: context.targetUrl
};
}
})
};
}

async getReviewRequests(pullRequest: PullRequestModel): Promise<IAccount[]> {
Expand Down
46 changes: 46 additions & 0 deletions src/github/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -528,3 +528,49 @@ export interface StartReviewResponse {
};
};
}

export interface StatusContext {
id: string;
state: 'ERROR' | 'EXPECTED' | 'FAILURE' | 'PENDING' | 'SUCCESS';
description?: string;
context: string;
targetUrl?: string;
avatarUrl?: string;
}

export interface CheckRun {
id: string;
conclusion?: 'ACTION_REQUIRED' | 'CANCELLED' | 'FAILURE' | 'NEUTRAL' | 'SKIPPED' | 'STALE' | 'SUCCESS' | 'TIMED_OUT';
name: string;
title?: string;
detailsUrl?: string;
checkSuite: {
app?: {
logoUrl: string;
url: string;
};
};
}

export function isCheckRun(x: CheckRun | StatusContext): x is CheckRun {
return !!(x as CheckRun).conclusion;
}

export interface GetChecksResponse {
repository: {
pullRequest: {
commits: {
nodes: {
commit: {
statusCheckRollup?: {
state: string;
contexts: {
nodes: (StatusContext | CheckRun)[]
}
}
}
}[]
}
}
};
}
13 changes: 13 additions & 0 deletions src/github/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,16 @@ export interface User extends IAccount {
repoNameWithOwner: string;
}[];
}

export interface PullRequestChecks {
state: string;
statuses: {
id: string;
url?: string;
avatar_url?: string;
state: string;
description?: string;
target_url?: string;
context: string;
}[];
}
42 changes: 42 additions & 0 deletions src/github/queries.gql
Original file line number Diff line number Diff line change
Expand Up @@ -749,3 +749,45 @@ query GetRepositoryForkDetails($owner: String!, $name: String!) {
}
}
}

query GetChecks($owner: String!, $name: String!, $number: Int!) {
repository(owner: $owner, name: $name) {
pullRequest(number: $number) {
commits(last: 1) {
nodes {
commit {
statusCheckRollup {
state
contexts(first: 100) {
nodes {
...on StatusContext {
id
state
targetUrl
description
context
avatarUrl
}
...on CheckRun {
id
conclusion
title
detailsUrl
name
resourcePath
checkSuite {
app {
logoUrl
url
}
}
}
}
}
}
}
}
}
}
}
}
4 changes: 2 additions & 2 deletions src/test/builders/managedPullRequestBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {
TimelineEventsResponse as TimelineEventsGraphQL
} from '../../github/graphql';
import {
ReposGetCombinedStatusForRefResponseData as CombinedStatusREST,
PullsListRequestedReviewersResponseData as ReviewRequestsREST,
IssuesListEventsForTimelineResponseData as TimelineEventREST,
} from '@octokit/types';
Expand All @@ -15,14 +14,15 @@ import { RepoUnion as RepositoryREST, RepositoryBuilder as RepositoryRESTBuilder
import { CombinedStatusBuilder as CombinedStatusRESTBuilder } from './rest/combinedStatusBuilder';
import { ReviewRequestsBuilder as ReviewRequestsRESTBuilder } from './rest/reviewRequestsBuilder';
import { createBuilderClass } from './base';
import { PullRequestChecks } from '../../github/interface';

type ResponseFlavor<APIFlavor, GQL, RST> = APIFlavor extends 'graphql' ? GQL : RST;

export interface ManagedPullRequest<APIFlavor> {
pullRequest: ResponseFlavor<APIFlavor, PullRequestGraphQL, PullRequestREST>;
timelineEvents: ResponseFlavor<APIFlavor, TimelineEventsGraphQL, TimelineEventREST[]>;
repositoryREST: RepositoryREST;
combinedStatusREST: CombinedStatusREST;
combinedStatusREST: PullRequestChecks;
reviewRequestsREST: ReviewRequestsREST;
}

Expand Down
12 changes: 3 additions & 9 deletions src/test/builders/rest/combinedStatusBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import * as OctokitTypes from '@octokit/types';

import { createBuilderClass } from '../base';
import { RepositoryBuilder } from './repoBuilder';
import { OctokitCommon } from '../../../github/common';
import { PullRequestChecks } from '../../../github/interface';

export const StatusItemBuilder = createBuilderClass<OctokitCommon.ReposGetCombinedStatusForRefResponseStatusesItem>()({
url: { default: 'https://api.github.com/repos/octocat/Hello-World/statuses/0000000000000000000000000000000000000000' },
Expand All @@ -19,14 +18,9 @@ export const StatusItemBuilder = createBuilderClass<OctokitCommon.ReposGetCombin

export type StatusItemBuilder = InstanceType<typeof StatusItemBuilder>;

export const CombinedStatusBuilder = createBuilderClass<OctokitTypes.ReposGetCombinedStatusForRefResponseData>()({
export const CombinedStatusBuilder = createBuilderClass<PullRequestChecks>()({
state: { default: 'success' },
statuses: { default: [] },
sha: { default: '0000000000000000000000000000000000000000' },
commit_url: { default: 'https://api.github.com/repos/octocat/Hello-World/commits/0000000000000000000000000000000000000000' },
url: { default: 'https://api.github.com/repos/octocat/Hello-World/0000000000000000000000000000000000000000/status' },
total_count: { default: 1 },
repository: { linked: RepositoryBuilder },
statuses: { default: [] }
});

export type CombinedStatusBuilder = InstanceType<typeof CombinedStatusBuilder>;

0 comments on commit f821fc0

Please sign in to comment.