Skip to content

Commit

Permalink
Approve via pull request review comments
Browse files Browse the repository at this point in the history
Signed-off-by: Kyle Harding <[email protected]>
  • Loading branch information
klutchell committed Nov 25, 2024
1 parent cf13a27 commit 91707f2
Show file tree
Hide file tree
Showing 4 changed files with 204 additions and 161 deletions.
50 changes: 25 additions & 25 deletions app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,30 +23,30 @@ default_events:
- deployment_protection_rule
# - fork
# - gollum
- issue_comment
# - issue_comment
# - issues
# - label
# - milestone
# - member
# - membership
# - org_block
# - organization
# - page_build
# - project
# - project_card
# - project_column
# - public
# - pull_request
# - pull_request_review
# - pull_request_review_comment
# - push
# - release
# - repository
# - repository_import
# - status
# - team
# - team_add
# - watch
# - label
# - milestone
# - member
# - membership
# - org_block
# - organization
# - page_build
# - project
# - project_card
# - project_column
# - public
# - pull_request
# - pull_request_review
- pull_request_review_comment
# - push
# - release
# - repository
# - repository_import
# - status
# - team
# - team_add
# - watch

# The set of permissions needed by the GitHub App. The format of the object uses
# the permission name for the key (for example, issues) and the access type for
Expand Down Expand Up @@ -74,7 +74,7 @@ default_permissions:

# Issues and related comments, assignees, labels, and milestones.
# https://developer.github.com/v3/apps/permissions/#permission-on-issues
issues: write
# issues: write

# Search repositories, list collaborators, and access repository metadata.
# https://developer.github.com/v3/apps/permissions/#metadata-permissions
Expand Down Expand Up @@ -106,7 +106,7 @@ default_permissions:

# Organization members and teams.
# https://developer.github.com/v3/apps/permissions/#permission-on-members
members: read
# members: read

# View and manage users blocked by the organization.
# https://developer.github.com/v3/apps/permissions/#permission-on-organization-user-blocking
Expand Down
76 changes: 44 additions & 32 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,43 @@ export async function whoAmI(context: any): Promise<any> {
return { login: viewer.login, id: viewer.databaseId };
}

// https://octokit.github.io/rest.js/v21/#repos-get-collaborator-permission-level
// https://docs.github.com/en/rest/collaborators/collaborators#list-repository-collaborators
export async function hasRepoWriteAccess(
context: any,
username: string,
): Promise<boolean> {
const request = context.repo({
username,
});
// // https://octokit.github.io/rest.js/v21/#repos-get-collaborator-permission-level
// // https://docs.github.com/en/rest/collaborators/collaborators#list-repository-collaborators
// export async function hasRepoWriteAccess(
// context: any,
// username: string,
// ): Promise<boolean> {
// const request = context.repo({
// username,
// });

const {
data: { permission },
} = await context.octokit.rest.repos.getCollaboratorPermissionLevel(request);
// const {
// data: { permission },
// } = await context.octokit.rest.repos.getCollaboratorPermissionLevel(request);

context.log.info(
`Permission level for ${username}: ${JSON.stringify(permission, null, 2)}`,
);
// context.log.info(
// `Permission level for ${username}: ${JSON.stringify(permission, null, 2)}`,
// );

return ['admin', 'write'].includes(permission);
}
// return ['admin', 'write'].includes(permission);
// }

export async function addCommentReaction(
// export async function addCommentReaction(
// context: any,
// commentId: number,
// content: string,
// ): Promise<void> {
// const request = context.repo({
// comment_id: commentId,
// content,
// });

// await context.octokit.reactions.createForIssueComment(request);
// }

// https://octokit.github.io/rest.js/v21/#reactions-create-for-pull-request-review-comment
// https://docs.github.com/en/rest/reactions/reactions#create-reaction-for-a-pull-request-review-comment
export async function addPullRequestReviewCommentReaction(
context: any,
commentId: number,
content: string,
Expand All @@ -36,7 +51,7 @@ export async function addCommentReaction(
content,
});

await context.octokit.reactions.createForIssueComment(request);
await context.octokit.reactions.createForPullRequestReviewComment(request);
}

// https://docs.github.com/en/rest/deployments/deployments#list-deployments
Expand Down Expand Up @@ -72,29 +87,26 @@ export async function listPullRequestCommits(
return commits;
}

export async function getPullRequest(
context: any,
prNumber: number,
): Promise<any> {
const request = context.repo({
pull_number: prNumber,
});
const { data: pullRequest } = await context.octokit.rest.pulls.get(request);
return pullRequest;
}
// export async function getPullRequest(
// context: any,
// prNumber: number,
// ): Promise<any> {
// const request = context.repo({
// pull_number: prNumber,
// });
// const { data: pullRequest } = await context.octokit.rest.pulls.get(request);
// return pullRequest;
// }

// https://octokit.github.io/rest.js/v21/#actions-list-workflow-runs-for-repo
// https://docs.github.com/en/rest/actions/workflow-runs#list-workflow-runs-for-a-repository
// https://docs.github.com/en/search-github/getting-started-with-searching-on-github/understanding-the-search-syntax#query-for-dates
export async function listWorkflowRuns(
context: any,
headSha: string,
created: string,
): Promise<any> {
// what is the status "requested" used for?
const request = context.repo({
status: 'waiting',
created,
head_sha: headSha,
});

Expand Down
69 changes: 29 additions & 40 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Context, Probot } from 'probot';
import type {
IssueCommentCreatedEvent,
PullRequestReviewCommentCreatedEvent,
DeploymentProtectionRuleRequestedEvent,
} from '@octokit/webhooks-types';
import * as GitHubClient from './client.js';
Expand Down Expand Up @@ -71,16 +71,16 @@ export default (app: Probot) => {
});
});

app.on('issue_comment.created', async (context: Context) => {
const { issue, comment } = context.payload as IssueCommentCreatedEvent;

console.log('Received event: issue_comment.created');
// console.log(JSON.stringify(context.payload, null, 2));
app.on('pull_request_review_comment.created', async (context: Context) => {
const {
comment,
pull_request: {
head: { sha },
},
} = context.payload as PullRequestReviewCommentCreatedEvent;

if (issue.pull_request == null) {
context.log.info('Ignoring non-pull request comment');
return;
}
console.log('Received event: pull_request_review_comment.created');
console.log(JSON.stringify(context.payload, null, 2));

if (comment.user.type === 'Bot') {
context.log.info('Ignoring bot comment');
Expand All @@ -97,9 +97,6 @@ export default (app: Probot) => {
return;
}

// post a reaction to the comment with :eyes:
await GitHubClient.addCommentReaction(context, comment.id, 'eyes');

let appUser;
try {
appUser = await GitHubClient.whoAmI(context);
Expand All @@ -112,36 +109,11 @@ export default (app: Probot) => {
return;
}

const hasRepoWriteAccess = await GitHubClient.hasRepoWriteAccess(
context,
comment.user.login,
);

if (!hasRepoWriteAccess) {
context.log.info('User does not have write access');
return;
}

const {
head: { sha },
} = await GitHubClient.getPullRequest(context, issue.number);

// Get the ISO date 2-min before the comment.created_at
const created = new Date(comment.created_at);
created.setMinutes(created.getMinutes() - 2);
let approved = false;

// filter workflow runs with the given hash and a creation date 2 minutes or more before the comment created date
// https://docs.github.com/en/search-github/getting-started-with-searching-on-github/understanding-the-search-syntax#query-for-dates
const runs = await GitHubClient.listWorkflowRuns(
context,
sha,
`<${created.toISOString()}`,
);

if (runs.length === 0) {
context.log.info('No workflow runs found for sha %s', sha);
return;
}
const runs = await GitHubClient.listWorkflowRuns(context, sha);

for (const run of runs) {
const deployments = await GitHubClient.listPendingDeployments(
Expand Down Expand Up @@ -175,8 +147,25 @@ export default (app: Probot) => {
'approved',
`Approved by ${comment.user.login} via ${appUser.login}`,
);
approved = true;
}
}

if (approved) {
// post a reaction to the comment with :rocket:
await GitHubClient.addPullRequestReviewCommentReaction(
context,
comment.id,
'rocket',
);
} else {
// post a reaction to the comment with :confused:
await GitHubClient.addPullRequestReviewCommentReaction(
context,
comment.id,
'confused',
);
}
});
// For more information on building apps:
// https://probot.github.io/docs/
Expand Down
Loading

0 comments on commit 91707f2

Please sign in to comment.