From ae2bc97932337cfdb54154cd2d77e944b5cb7d45 Mon Sep 17 00:00:00 2001 From: Neil Kalman Date: Mon, 26 Apr 2021 11:23:59 +0000 Subject: [PATCH] feat(engine): handle assignee and reviewer events --- .vscode/settings.json | 4 +- .../api/pull-request/pull-request.service.ts | 21 +- ...pull-request-review-request-added.event.ts | 4 +- ...ll-request-review-request-removed.event.ts | 4 +- server/src/engines/github.engine.ts | 77 ++++++- .../src/interfaces/github-pr-payload.model.ts | 3 + server/src/models/pull-request.model.ts | 5 + .../github-events.e2e-spec.ts.snap | 205 ++++++++++++++++++ server/test/github-events.e2e-spec.ts | 99 ++++++++- 9 files changed, 407 insertions(+), 15 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 1ca753f4..254bbae3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -55,5 +55,7 @@ "github-actions.workflows.pinned.workflows": [ ".github/workflows/server-unit-tests.yml", ".github/workflows/lint-server.yml" - ] + ], + "github-actions.workflows.pinned.refresh.enabled": true, + "github-actions.workflows.pinned.refresh.interval": 30 } \ No newline at end of file diff --git a/server/src/api/pull-request/pull-request.service.ts b/server/src/api/pull-request/pull-request.service.ts index 8dd72dd8..534541ef 100644 --- a/server/src/api/pull-request/pull-request.service.ts +++ b/server/src/api/pull-request/pull-request.service.ts @@ -4,7 +4,7 @@ import { ReturnModelType } from '@typegoose/typegoose'; import { BaseService } from '@kb-abstracts'; import { IGithubChanges } from '@kb-interfaces'; -import { PullRequest } from '@kb-models'; +import { PullRequest, User } from '@kb-models'; export interface INewData { title?: string; @@ -57,4 +57,23 @@ export class PullRequestService extends BaseService { ...historyUpdate }); } + + async updateAssignees(prid: string, assignees: User[]) { + await this.prModel.findOneAndUpdate({ prid }, { + assignees: assignees.map((user) => user.username) + }); + } + + async updateReviewers(prid: string, reviewer: User, forRemoval?: boolean) { + const changeQuery = { + $pull: {} as any, + $addToSet: {} as any + }; + const addDelAttr = forRemoval ? '$pull' : '$addToSet'; + changeQuery[addDelAttr].reviewers = reviewer.username; + if (forRemoval) { + changeQuery['$addToSet']['history.deletedReviewers'] = reviewer.username; + } + await this.prModel.findOneAndUpdate({ prid }, changeQuery); + } } diff --git a/server/src/dev-tools/captured-events/pull-request-review-request-added.event.ts b/server/src/dev-tools/captured-events/pull-request-review-request-added.event.ts index 6ba1fcd0..98481dc4 100644 --- a/server/src/dev-tools/captured-events/pull-request-review-request-added.event.ts +++ b/server/src/dev-tools/captured-events/pull-request-review-request-added.event.ts @@ -3,7 +3,7 @@ export const pullReuqestReviewRequestAddedEvent = { event: 'pull_request', payload: { action: 'review_requested', - number: 2, + number: 1, pull_request: { url: 'https://api.github.com/repos/Thatkookooguy/test-new-achievibit-events/pulls/2', id: 353189935, @@ -12,7 +12,7 @@ export const pullReuqestReviewRequestAddedEvent = { diff_url: 'https://github.com/Thatkookooguy/test-new-achievibit-events/pull/2.diff', patch_url: 'https://github.com/Thatkookooguy/test-new-achievibit-events/pull/2.patch', issue_url: 'https://api.github.com/repos/Thatkookooguy/test-new-achievibit-events/issues/2', - number: 2, + number: 1, state: 'open', locked: false, title: 'Update README.md', diff --git a/server/src/dev-tools/captured-events/pull-request-review-request-removed.event.ts b/server/src/dev-tools/captured-events/pull-request-review-request-removed.event.ts index ce030d6f..bd6a30d1 100644 --- a/server/src/dev-tools/captured-events/pull-request-review-request-removed.event.ts +++ b/server/src/dev-tools/captured-events/pull-request-review-request-removed.event.ts @@ -3,7 +3,7 @@ export const pullRequestReviewRequestRemovedEvent = { event: 'pull_request', payload: { action: 'review_request_removed', - number: 2, + number: 1, pull_request: { url: 'https://api.github.com/repos/Thatkookooguy/test-new-achievibit-events/pulls/2', id: 353189935, @@ -12,7 +12,7 @@ export const pullRequestReviewRequestRemovedEvent = { diff_url: 'https://github.com/Thatkookooguy/test-new-achievibit-events/pull/2.diff', patch_url: 'https://github.com/Thatkookooguy/test-new-achievibit-events/pull/2.patch', issue_url: 'https://api.github.com/repos/Thatkookooguy/test-new-achievibit-events/issues/2', - number: 2, + number: 1, state: 'open', locked: false, title: 'Update README.md', diff --git a/server/src/engines/github.engine.ts b/server/src/engines/github.engine.ts index ac0f8ac2..ea4f5b7d 100644 --- a/server/src/engines/github.engine.ts +++ b/server/src/engines/github.engine.ts @@ -135,25 +135,86 @@ export class GithubEngine extends Engine { eventData.changes ); } - handlePullRequestAssigneeAdded( + async handlePullRequestAssigneeAdded( eventData: IGithubPullRequestEvent ): Promise { - throw new Error('Method not implemented.'); + const { + githubCreator, + githubOwner, + githubPR + } = this.extractGithubEntities(eventData); + const pr = this.extractPullRequest( + githubPR, + this.extractUser(githubCreator), + this.extractRepo(eventData.repository), + this.extractUser(githubOwner) + ); + + const githubAssignees = eventData.pull_request.assignees; + const assignees = githubAssignees + .map((assignee) => this.extractUser(assignee)); + + // TODO: need to save these users!!! + + await this.pullRequestsService.updateAssignees(pr.prid, assignees); } - handlePullRequestAssigneeRemoved( + async handlePullRequestAssigneeRemoved( eventData: IGithubPullRequestEvent ): Promise { - throw new Error('Method not implemented.'); + const { + githubCreator, + githubOwner, + githubPR + } = this.extractGithubEntities(eventData); + const pr = this.extractPullRequest( + githubPR, + this.extractUser(githubCreator), + this.extractRepo(eventData.repository), + this.extractUser(githubOwner) + ); + + const githubAssignees = eventData.pull_request.assignees; + const assignees = githubAssignees + .map((assignee) => this.extractUser(assignee)); + + await this.pullRequestsService.updateAssignees(pr.prid, assignees); } - handlePullRequestReviewRequestAdded( + async handlePullRequestReviewRequestAdded( eventData: IGithubPullRequestEvent ): Promise { - throw new Error('Method not implemented.'); + const { + githubCreator, + githubOwner, + githubPR + } = this.extractGithubEntities(eventData); + const pr = this.extractPullRequest( + githubPR, + this.extractUser(githubCreator), + this.extractRepo(eventData.repository), + this.extractUser(githubOwner) + ); + const reviewer = this.extractUser(eventData.requested_reviewer); + + // TODO: need to save these users!!! + + await this.pullRequestsService.updateReviewers(pr.prid, reviewer); } - handlePullRequestReviewRequestRemoved( + async handlePullRequestReviewRequestRemoved( eventData: IGithubPullRequestEvent ): Promise { - throw new Error('Method not implemented.'); + const { + githubCreator, + githubOwner, + githubPR + } = this.extractGithubEntities(eventData); + const pr = this.extractPullRequest( + githubPR, + this.extractUser(githubCreator), + this.extractRepo(eventData.repository), + this.extractUser(githubOwner) + ); + const reviewer = this.extractUser(eventData.requested_reviewer); + await this.pullRequestsService.updateReviewers(pr.prid, reviewer, true); } handlePullRequestReviewCommentAdded( eventData: IGithubPullRequestEvent diff --git a/server/src/interfaces/github-pr-payload.model.ts b/server/src/interfaces/github-pr-payload.model.ts index 1c3f8d02..acaadd08 100644 --- a/server/src/interfaces/github-pr-payload.model.ts +++ b/server/src/interfaces/github-pr-payload.model.ts @@ -111,6 +111,7 @@ export interface IGithubPullRequest { merged_at: string; merge_commit_sha: string; assignee: string; + assignees: IGithubUser[]; milestone: string; commits_url: string; review_comments_url: string; @@ -189,4 +190,6 @@ export interface IGithubPullRequestEvent { sender: IGithubUser; label?: { name: string }; changes?: IGithubChanges; + requested_reviewer?: IGithubUser; + // assignees?: IGithubUser[]; } diff --git a/server/src/models/pull-request.model.ts b/server/src/models/pull-request.model.ts index 02c9bf3d..4fff3a2c 100644 --- a/server/src/models/pull-request.model.ts +++ b/server/src/models/pull-request.model.ts @@ -73,6 +73,11 @@ export class PullRequest extends BaseModel { @PersistInDb() assignees?: string[]; + @Expose() + @IsOptional() + @PersistInDb() + reviewers?: string[]; + constructor(partial: Partial = {}) { super(); Object.assign(this, partial); diff --git a/server/test/__snapshots__/github-events.e2e-spec.ts.snap b/server/test/__snapshots__/github-events.e2e-spec.ts.snap index 7bca0823..8fe3b7a7 100644 --- a/server/test/__snapshots__/github-events.e2e-spec.ts.snap +++ b/server/test/__snapshots__/github-events.e2e-spec.ts.snap @@ -12,6 +12,104 @@ Array [ ] `; +exports[`AppController (e2e) pr events / (POST) from github pull request assignee added 1`] = `"PullRequestAssigneeAdded"`; + +exports[`AppController (e2e) pr events / (POST) from github pull request assignee added 2`] = ` +Array [ + Object { + "achievements": Array [], + "avatar": "https://avatars3.githubusercontent.com/u/10427304?v=4", + "organization": false, + "organizations": Array [], + "repos": Array [], + "url": "https://github.com/Thatkookooguy", + "username": "Thatkookooguy", + "users": Array [], + }, +] +`; + +exports[`AppController (e2e) pr events / (POST) from github pull request assignee added 3`] = ` +Array [ + Object { + "fullname": "Thatkookooguy/test-new-achievibit-events", + "name": "test-new-achievibit-events", + "url": "https://github.com/Thatkookooguy/test-new-achievibit-events", + }, +] +`; + +exports[`AppController (e2e) pr events / (POST) from github pull request assignee added 4`] = ` +Array [ + Object { + "assignees": Array [ + "Thatkookooguy", + ], + "createdOn": 2019-12-12T13:48:34.000Z, + "creator": "Thatkookooguy", + "description": "", + "history": undefined, + "labels": Array [], + "number": 1, + "organization": undefined, + "prid": "Thatkookooguy/test-new-achievibit-events/pull/1", + "repository": "Thatkookooguy/test-new-achievibit-events", + "reviewers": Array [], + "title": "Create test-event.ts", + "updatedDate": undefined, + "url": "https://github.com/Thatkookooguy/test-new-achievibit-events/pull/1", + }, +] +`; + +exports[`AppController (e2e) pr events / (POST) from github pull request assignee removed 1`] = `"PullRequestAssigneeRemoved"`; + +exports[`AppController (e2e) pr events / (POST) from github pull request assignee removed 2`] = ` +Array [ + Object { + "achievements": Array [], + "avatar": "https://avatars3.githubusercontent.com/u/10427304?v=4", + "organization": false, + "organizations": Array [], + "repos": Array [], + "url": "https://github.com/Thatkookooguy", + "username": "Thatkookooguy", + "users": Array [], + }, +] +`; + +exports[`AppController (e2e) pr events / (POST) from github pull request assignee removed 3`] = ` +Array [ + Object { + "fullname": "Thatkookooguy/test-new-achievibit-events", + "name": "test-new-achievibit-events", + "url": "https://github.com/Thatkookooguy/test-new-achievibit-events", + }, +] +`; + +exports[`AppController (e2e) pr events / (POST) from github pull request assignee removed 4`] = ` +Array [ + Object { + "assignees": Array [], + "createdOn": 2019-12-12T13:48:34.000Z, + "creator": "Thatkookooguy", + "description": "", + "history": undefined, + "labels": Array [], + "number": 1, + "organization": undefined, + "prid": "Thatkookooguy/test-new-achievibit-events/pull/1", + "repository": "Thatkookooguy/test-new-achievibit-events", + "reviewers": Array [], + "title": "Create test-event.ts", + "updatedDate": undefined, + "url": "https://github.com/Thatkookooguy/test-new-achievibit-events/pull/1", + }, +] +`; + exports[`AppController (e2e) pr events / (POST) from github pull request created event should create user 1`] = `"PullRequestOpened"`; exports[`AppController (e2e) pr events / (POST) from github pull request created event should create user 2`] = ` @@ -52,6 +150,7 @@ Array [ "organization": undefined, "prid": "Thatkookooguy/test-new-achievibit-events/pull/1", "repository": "Thatkookooguy/test-new-achievibit-events", + "reviewers": Array [], "title": "Create test-event.ts", "updatedDate": undefined, "url": "https://github.com/Thatkookooguy/test-new-achievibit-events/pull/1", @@ -106,6 +205,7 @@ Array [ "organization": undefined, "prid": "Thatkookooguy/test-new-achievibit-events/pull/1", "repository": "Thatkookooguy/test-new-achievibit-events", + "reviewers": Array [], "title": "Create test-event.ts - title changed!", "updatedDate": undefined, "url": "https://github.com/Thatkookooguy/test-new-achievibit-events/pull/1", @@ -158,6 +258,7 @@ Array [ "organization": undefined, "prid": "Thatkookooguy/test-new-achievibit-events/pull/1", "repository": "Thatkookooguy/test-new-achievibit-events", + "reviewers": Array [], "title": "Create test-event.ts", "updatedDate": undefined, "url": "https://github.com/Thatkookooguy/test-new-achievibit-events/pull/1", @@ -211,6 +312,7 @@ Array [ "organization": undefined, "prid": "Thatkookooguy/test-new-achievibit-events/pull/1", "repository": "Thatkookooguy/test-new-achievibit-events", + "reviewers": Array [], "title": "Create test-event.ts", "updatedDate": undefined, "url": "https://github.com/Thatkookooguy/test-new-achievibit-events/pull/1", @@ -264,6 +366,109 @@ Array [ "organization": undefined, "prid": "Thatkookooguy/test-new-achievibit-events/pull/1", "repository": "Thatkookooguy/test-new-achievibit-events", + "reviewers": Array [], + "title": "Create test-event.ts", + "updatedDate": undefined, + "url": "https://github.com/Thatkookooguy/test-new-achievibit-events/pull/1", + }, +] +`; + +exports[`AppController (e2e) pr events / (POST) from github pull request review requested 1`] = `"PullRequestReviewRequestAdded"`; + +exports[`AppController (e2e) pr events / (POST) from github pull request review requested 2`] = ` +Array [ + Object { + "achievements": Array [], + "avatar": "https://avatars3.githubusercontent.com/u/10427304?v=4", + "organization": false, + "organizations": Array [], + "repos": Array [], + "url": "https://github.com/Thatkookooguy", + "username": "Thatkookooguy", + "users": Array [], + }, +] +`; + +exports[`AppController (e2e) pr events / (POST) from github pull request review requested 3`] = ` +Array [ + Object { + "fullname": "Thatkookooguy/test-new-achievibit-events", + "name": "test-new-achievibit-events", + "url": "https://github.com/Thatkookooguy/test-new-achievibit-events", + }, +] +`; + +exports[`AppController (e2e) pr events / (POST) from github pull request review requested 4`] = ` +Array [ + Object { + "assignees": Array [], + "createdOn": 2019-12-12T13:48:34.000Z, + "creator": "Thatkookooguy", + "description": "", + "history": undefined, + "labels": Array [], + "number": 1, + "organization": undefined, + "prid": "Thatkookooguy/test-new-achievibit-events/pull/1", + "repository": "Thatkookooguy/test-new-achievibit-events", + "reviewers": Array [ + "norq87", + ], + "title": "Create test-event.ts", + "updatedDate": undefined, + "url": "https://github.com/Thatkookooguy/test-new-achievibit-events/pull/1", + }, +] +`; + +exports[`AppController (e2e) pr events / (POST) from github pull request reviewer removed 1`] = `"PullRequestReviewRequestRemoved"`; + +exports[`AppController (e2e) pr events / (POST) from github pull request reviewer removed 2`] = ` +Array [ + Object { + "achievements": Array [], + "avatar": "https://avatars3.githubusercontent.com/u/10427304?v=4", + "organization": false, + "organizations": Array [], + "repos": Array [], + "url": "https://github.com/Thatkookooguy", + "username": "Thatkookooguy", + "users": Array [], + }, +] +`; + +exports[`AppController (e2e) pr events / (POST) from github pull request reviewer removed 3`] = ` +Array [ + Object { + "fullname": "Thatkookooguy/test-new-achievibit-events", + "name": "test-new-achievibit-events", + "url": "https://github.com/Thatkookooguy/test-new-achievibit-events", + }, +] +`; + +exports[`AppController (e2e) pr events / (POST) from github pull request reviewer removed 4`] = ` +Array [ + Object { + "assignees": Array [], + "createdOn": 2019-12-12T13:48:34.000Z, + "creator": "Thatkookooguy", + "description": "", + "history": Object { + "deletedReviewers": Array [ + "norq87", + ], + }, + "labels": Array [], + "number": 1, + "organization": undefined, + "prid": "Thatkookooguy/test-new-achievibit-events/pull/1", + "repository": "Thatkookooguy/test-new-achievibit-events", + "reviewers": Array [], "title": "Create test-event.ts", "updatedDate": undefined, "url": "https://github.com/Thatkookooguy/test-new-achievibit-events/pull/1", diff --git a/server/test/github-events.e2e-spec.ts b/server/test/github-events.e2e-spec.ts index f77a6ab9..f891d827 100644 --- a/server/test/github-events.e2e-spec.ts +++ b/server/test/github-events.e2e-spec.ts @@ -7,11 +7,15 @@ import { PullRequestService } from '@kb-api'; import { AppModule } from '@kb-app'; import { ConfigService } from '@kb-config'; import { + pullRequestAssigneeAddedEvent, + pullRequestAssigneeRemovedEvent, pullRequestCreatedEvent, pullRequestEditedEvent, pullRequestLabelAddedEvent, pullRequestLabelRemovedEvent, pullRequestLabelsInitializedEvent, + pullRequestReviewRequestRemovedEvent, + pullReuqestReviewRequestAddedEvent, webhookPingEvent } from '@kb-dev-tools'; import { PullRequest } from '@kb-models'; @@ -171,7 +175,100 @@ describe('AppController (e2e)', () => { expect(sendWebhookResponse.text).toMatchSnapshot(); await confirmPrDataCreated(); - }); + }); + + test('/ (POST) from github pull request assignee added', async () => { + const server = app.getHttpServer(); + await request(server) + .post('/api/webhook-event-manager') + .set('Accept', 'application/json') + .set('x-github-event', pullRequestCreatedEvent.event) + .send(pullRequestCreatedEvent.payload) + .expect(201); + const sendWebhookResponse = await request(server) + .post('/api/webhook-event-manager') + .set('Accept', 'application/json') + .set('x-github-event', pullRequestAssigneeAddedEvent.event) + .send(pullRequestAssigneeAddedEvent.payload) + .expect(201); + + expect(sendWebhookResponse.text).toMatchSnapshot(); + + await confirmPrDataCreated(); + }); + + test('/ (POST) from github pull request assignee removed', async () => { + const server = app.getHttpServer(); + await request(server) + .post('/api/webhook-event-manager') + .set('Accept', 'application/json') + .set('x-github-event', pullRequestCreatedEvent.event) + .send(pullRequestCreatedEvent.payload) + .expect(201); + await request(server) + .post('/api/webhook-event-manager') + .set('Accept', 'application/json') + .set('x-github-event', pullRequestAssigneeAddedEvent.event) + .send(pullRequestAssigneeAddedEvent.payload) + .expect(201); + + const sendWebhookResponse = await request(server) + .post('/api/webhook-event-manager') + .set('Accept', 'application/json') + .set('x-github-event', pullRequestAssigneeRemovedEvent.event) + .send(pullRequestAssigneeRemovedEvent.payload) + .expect(201); + + expect(sendWebhookResponse.text).toMatchSnapshot(); + + await confirmPrDataCreated(); + }); + + test('/ (POST) from github pull request review requested', async () => { + const server = app.getHttpServer(); + await request(server) + .post('/api/webhook-event-manager') + .set('Accept', 'application/json') + .set('x-github-event', pullRequestCreatedEvent.event) + .send(pullRequestCreatedEvent.payload) + .expect(201); + const sendWebhookResponse = await request(server) + .post('/api/webhook-event-manager') + .set('Accept', 'application/json') + .set('x-github-event', pullReuqestReviewRequestAddedEvent.event) + .send(pullReuqestReviewRequestAddedEvent.payload) + .expect(201); + + expect(sendWebhookResponse.text).toMatchSnapshot(); + + await confirmPrDataCreated(); + }); + + test('/ (POST) from github pull request reviewer removed', async () => { + const server = app.getHttpServer(); + await request(server) + .post('/api/webhook-event-manager') + .set('Accept', 'application/json') + .set('x-github-event', pullRequestCreatedEvent.event) + .send(pullRequestCreatedEvent.payload) + .expect(201); + await request(server) + .post('/api/webhook-event-manager') + .set('Accept', 'application/json') + .set('x-github-event', pullReuqestReviewRequestAddedEvent.event) + .send(pullReuqestReviewRequestAddedEvent.payload) + .expect(201); + const sendWebhookResponse = await request(server) + .post('/api/webhook-event-manager') + .set('Accept', 'application/json') + .set('x-github-event', pullRequestReviewRequestRemovedEvent.event) + .send(pullRequestReviewRequestRemovedEvent.payload) + .expect(201); + + expect(sendWebhookResponse.text).toMatchSnapshot(); + + await confirmPrDataCreated(); + }); async function confirmPrDataCreated() { const server = app.getHttpServer();