From 1df4afdba0bc327459f7f22822793f000ffc2606 Mon Sep 17 00:00:00 2001 From: "Tais P. Hansen" Date: Sun, 3 Apr 2022 18:45:11 +0200 Subject: [PATCH] Add support for the pull request review thread event (#2326) Fixes #2324 --- github/event.go | 2 + github/event_types.go | 18 ++ github/event_types_test.go | 249 ++++++++++++++++++++++++++ github/github-accessors.go | 64 +++++++ github/github-accessors_test.go | 65 +++++++ github/github-stringify_test.go | 11 ++ github/messages.go | 1 + github/messages_test.go | 4 + github/pulls_threads.go | 17 ++ github/pulls_threads_test.go | 191 ++++++++++++++++++++ github/repos_hooks_deliveries_test.go | 1 + 11 files changed, 623 insertions(+) create mode 100644 github/pulls_threads.go create mode 100644 github/pulls_threads_test.go diff --git a/github/event.go b/github/event.go index 9a39e2e95f..5c10365970 100644 --- a/github/event.go +++ b/github/event.go @@ -102,6 +102,8 @@ func (e *Event) ParsePayload() (payload interface{}, err error) { payload = &PullRequestReviewEvent{} case "PullRequestReviewCommentEvent": payload = &PullRequestReviewCommentEvent{} + case "PullRequestReviewThreadEvent": + payload = &PullRequestReviewThreadEvent{} case "PullRequestTargetEvent": payload = &PullRequestTargetEvent{} case "PushEvent": diff --git a/github/event_types.go b/github/event_types.go index 71baac886f..7cfba9abbd 100644 --- a/github/event_types.go +++ b/github/event_types.go @@ -830,6 +830,24 @@ type PullRequestReviewCommentEvent struct { Installation *Installation `json:"installation,omitempty"` } +// PullRequestReviewThreadEvent is triggered when a comment made as part of a +// review of a pull request is marked resolved or unresolved. +// The Webhook event name is "pull_request_review_thread". +// +// GitHub API docs: https://docs.github.com/en/developers/webhooks-and-events/webhook-events-and-payloads#pull_request_review_thread +type PullRequestReviewThreadEvent struct { + // Action is the action that was performed on the comment. + // Possible values are: "resolved", "unresolved". + Action *string `json:"action,omitempty"` + Thread *PullRequestThread `json:"thread,omitempty"` + PullRequest *PullRequest `json:"pull_request,omitempty"` + + // The following fields are only populated by Webhook events. + Repo *Repository `json:"repository,omitempty"` + Sender *User `json:"sender,omitempty"` + Installation *Installation `json:"installation,omitempty"` +} + // PullRequestTargetEvent is triggered when a pull request is assigned, unassigned, labeled, // unlabeled, opened, edited, closed, reopened, synchronize, ready_for_review, // locked, unlocked, a pull request review is requested, or a review request is removed. diff --git a/github/event_types_test.go b/github/event_types_test.go index 5f9dd2583d..5d82e3f4da 100644 --- a/github/event_types_test.go +++ b/github/event_types_test.go @@ -13061,6 +13061,255 @@ func TestPullRequestReviewCommentEvent_Marshal(t *testing.T) { testJSONMarshal(t, u, want) } +func TestPullRequestReviewThreadEvent_Marshal(t *testing.T) { + testJSONMarshal(t, &PullRequestReviewThreadEvent{}, "{}") + + u := &PullRequestReviewThreadEvent{ + Action: String("a"), + PullRequest: &PullRequest{ID: Int64(1)}, + Thread: &PullRequestThread{ + Comments: []*PullRequestComment{{ID: Int64(1)}, {ID: Int64(2)}}, + }, + Repo: &Repository{ + ID: Int64(1), + URL: String("s"), + Name: String("n"), + }, + Sender: &User{ + Login: String("l"), + ID: Int64(1), + NodeID: String("n"), + URL: String("u"), + ReposURL: String("r"), + EventsURL: String("e"), + AvatarURL: String("a"), + }, + Installation: &Installation{ + ID: Int64(1), + NodeID: String("nid"), + AppID: Int64(1), + AppSlug: String("as"), + TargetID: Int64(1), + Account: &User{ + Login: String("l"), + ID: Int64(1), + URL: String("u"), + AvatarURL: String("a"), + GravatarID: String("g"), + Name: String("n"), + Company: String("c"), + Blog: String("b"), + Location: String("l"), + Email: String("e"), + Hireable: Bool(true), + Bio: String("b"), + TwitterUsername: String("t"), + PublicRepos: Int(1), + Followers: Int(1), + Following: Int(1), + CreatedAt: &Timestamp{referenceTime}, + SuspendedAt: &Timestamp{referenceTime}, + }, + AccessTokensURL: String("atu"), + RepositoriesURL: String("ru"), + HTMLURL: String("hu"), + TargetType: String("tt"), + SingleFileName: String("sfn"), + RepositorySelection: String("rs"), + Events: []string{"e"}, + SingleFilePaths: []string{"s"}, + Permissions: &InstallationPermissions{ + Actions: String("a"), + Administration: String("ad"), + Checks: String("c"), + Contents: String("co"), + ContentReferences: String("cr"), + Deployments: String("d"), + Environments: String("e"), + Issues: String("i"), + Metadata: String("md"), + Members: String("m"), + OrganizationAdministration: String("oa"), + OrganizationHooks: String("oh"), + OrganizationPlan: String("op"), + OrganizationPreReceiveHooks: String("opr"), + OrganizationProjects: String("op"), + OrganizationSecrets: String("os"), + OrganizationSelfHostedRunners: String("osh"), + OrganizationUserBlocking: String("oub"), + Packages: String("pkg"), + Pages: String("pg"), + PullRequests: String("pr"), + RepositoryHooks: String("rh"), + RepositoryProjects: String("rp"), + RepositoryPreReceiveHooks: String("rprh"), + Secrets: String("s"), + SecretScanningAlerts: String("ssa"), + SecurityEvents: String("se"), + SingleFile: String("sf"), + Statuses: String("s"), + TeamDiscussions: String("td"), + VulnerabilityAlerts: String("va"), + Workflows: String("w"), + }, + CreatedAt: &Timestamp{referenceTime}, + UpdatedAt: &Timestamp{referenceTime}, + HasMultipleSingleFiles: Bool(false), + SuspendedBy: &User{ + Login: String("l"), + ID: Int64(1), + URL: String("u"), + AvatarURL: String("a"), + GravatarID: String("g"), + Name: String("n"), + Company: String("c"), + Blog: String("b"), + Location: String("l"), + Email: String("e"), + Hireable: Bool(true), + Bio: String("b"), + TwitterUsername: String("t"), + PublicRepos: Int(1), + Followers: Int(1), + Following: Int(1), + CreatedAt: &Timestamp{referenceTime}, + SuspendedAt: &Timestamp{referenceTime}, + }, + SuspendedAt: &Timestamp{referenceTime}, + }, + } + + want := `{ + "action": "a", + "pull_request": { + "id": 1 + }, + "thread": { + "comments": [ + { + "id": 1 + }, + { + "id": 2 + } + ] + }, + "repository": { + "id": 1, + "name": "n", + "url": "s" + }, + "sender": { + "login": "l", + "id": 1, + "node_id": "n", + "avatar_url": "a", + "url": "u", + "events_url": "e", + "repos_url": "r" + }, + "installation": { + "id": 1, + "node_id": "nid", + "app_id": 1, + "app_slug": "as", + "target_id": 1, + "account": { + "login": "l", + "id": 1, + "avatar_url": "a", + "gravatar_id": "g", + "name": "n", + "company": "c", + "blog": "b", + "location": "l", + "email": "e", + "hireable": true, + "bio": "b", + "twitter_username": "t", + "public_repos": 1, + "followers": 1, + "following": 1, + "created_at": ` + referenceTimeStr + `, + "suspended_at": ` + referenceTimeStr + `, + "url": "u" + }, + "access_tokens_url": "atu", + "repositories_url": "ru", + "html_url": "hu", + "target_type": "tt", + "single_file_name": "sfn", + "repository_selection": "rs", + "events": [ + "e" + ], + "single_file_paths": [ + "s" + ], + "permissions": { + "actions": "a", + "administration": "ad", + "checks": "c", + "contents": "co", + "content_references": "cr", + "deployments": "d", + "environments": "e", + "issues": "i", + "metadata": "md", + "members": "m", + "organization_administration": "oa", + "organization_hooks": "oh", + "organization_plan": "op", + "organization_pre_receive_hooks": "opr", + "organization_projects": "op", + "organization_secrets": "os", + "organization_self_hosted_runners": "osh", + "organization_user_blocking": "oub", + "packages": "pkg", + "pages": "pg", + "pull_requests": "pr", + "repository_hooks": "rh", + "repository_projects": "rp", + "repository_pre_receive_hooks": "rprh", + "secrets": "s", + "secret_scanning_alerts": "ssa", + "security_events": "se", + "single_file": "sf", + "statuses": "s", + "team_discussions": "td", + "vulnerability_alerts": "va", + "workflows": "w" + }, + "created_at": ` + referenceTimeStr + `, + "updated_at": ` + referenceTimeStr + `, + "has_multiple_single_files": false, + "suspended_by": { + "login": "l", + "id": 1, + "avatar_url": "a", + "gravatar_id": "g", + "name": "n", + "company": "c", + "blog": "b", + "location": "l", + "email": "e", + "hireable": true, + "bio": "b", + "twitter_username": "t", + "public_repos": 1, + "followers": 1, + "following": 1, + "created_at": ` + referenceTimeStr + `, + "suspended_at": ` + referenceTimeStr + `, + "url": "u" + }, + "suspended_at": ` + referenceTimeStr + ` + } + }` + + testJSONMarshal(t, u, want) +} + func TestPullRequestTargetEvent_Marshal(t *testing.T) { testJSONMarshal(t, &PullRequestTargetEvent{}, "{}") diff --git a/github/github-accessors.go b/github/github-accessors.go index 67bd7b92ee..724e67f0d3 100644 --- a/github/github-accessors.go +++ b/github/github-accessors.go @@ -12822,6 +12822,54 @@ func (p *PullRequestReviewsEnforcementUpdate) GetRequireCodeOwnerReviews() bool return *p.RequireCodeOwnerReviews } +// GetAction returns the Action field if it's non-nil, zero value otherwise. +func (p *PullRequestReviewThreadEvent) GetAction() string { + if p == nil || p.Action == nil { + return "" + } + return *p.Action +} + +// GetInstallation returns the Installation field. +func (p *PullRequestReviewThreadEvent) GetInstallation() *Installation { + if p == nil { + return nil + } + return p.Installation +} + +// GetPullRequest returns the PullRequest field. +func (p *PullRequestReviewThreadEvent) GetPullRequest() *PullRequest { + if p == nil { + return nil + } + return p.PullRequest +} + +// GetRepo returns the Repo field. +func (p *PullRequestReviewThreadEvent) GetRepo() *Repository { + if p == nil { + return nil + } + return p.Repo +} + +// GetSender returns the Sender field. +func (p *PullRequestReviewThreadEvent) GetSender() *User { + if p == nil { + return nil + } + return p.Sender +} + +// GetThread returns the Thread field. +func (p *PullRequestReviewThreadEvent) GetThread() *PullRequestThread { + if p == nil { + return nil + } + return p.Thread +} + // GetAction returns the Action field if it's non-nil, zero value otherwise. func (p *PullRequestTargetEvent) GetAction() string { if p == nil || p.Action == nil { @@ -12934,6 +12982,22 @@ func (p *PullRequestTargetEvent) GetSender() *User { return p.Sender } +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (p *PullRequestThread) GetID() int64 { + if p == nil || p.ID == nil { + return 0 + } + return *p.ID +} + +// GetNodeID returns the NodeID field if it's non-nil, zero value otherwise. +func (p *PullRequestThread) GetNodeID() string { + if p == nil || p.NodeID == nil { + return "" + } + return *p.NodeID +} + // GetMergablePulls returns the MergablePulls field if it's non-nil, zero value otherwise. func (p *PullStats) GetMergablePulls() int { if p == nil || p.MergablePulls == nil { diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go index 54b2410807..5ac8d7b9a6 100644 --- a/github/github-accessors_test.go +++ b/github/github-accessors_test.go @@ -14930,6 +14930,51 @@ func TestPullRequestReviewsEnforcementUpdate_GetRequireCodeOwnerReviews(tt *test p.GetRequireCodeOwnerReviews() } +func TestPullRequestReviewThreadEvent_GetAction(tt *testing.T) { + var zeroValue string + p := &PullRequestReviewThreadEvent{Action: &zeroValue} + p.GetAction() + p = &PullRequestReviewThreadEvent{} + p.GetAction() + p = nil + p.GetAction() +} + +func TestPullRequestReviewThreadEvent_GetInstallation(tt *testing.T) { + p := &PullRequestReviewThreadEvent{} + p.GetInstallation() + p = nil + p.GetInstallation() +} + +func TestPullRequestReviewThreadEvent_GetPullRequest(tt *testing.T) { + p := &PullRequestReviewThreadEvent{} + p.GetPullRequest() + p = nil + p.GetPullRequest() +} + +func TestPullRequestReviewThreadEvent_GetRepo(tt *testing.T) { + p := &PullRequestReviewThreadEvent{} + p.GetRepo() + p = nil + p.GetRepo() +} + +func TestPullRequestReviewThreadEvent_GetSender(tt *testing.T) { + p := &PullRequestReviewThreadEvent{} + p.GetSender() + p = nil + p.GetSender() +} + +func TestPullRequestReviewThreadEvent_GetThread(tt *testing.T) { + p := &PullRequestReviewThreadEvent{} + p.GetThread() + p = nil + p.GetThread() +} + func TestPullRequestTargetEvent_GetAction(tt *testing.T) { var zeroValue string p := &PullRequestTargetEvent{Action: &zeroValue} @@ -15040,6 +15085,26 @@ func TestPullRequestTargetEvent_GetSender(tt *testing.T) { p.GetSender() } +func TestPullRequestThread_GetID(tt *testing.T) { + var zeroValue int64 + p := &PullRequestThread{ID: &zeroValue} + p.GetID() + p = &PullRequestThread{} + p.GetID() + p = nil + p.GetID() +} + +func TestPullRequestThread_GetNodeID(tt *testing.T) { + var zeroValue string + p := &PullRequestThread{NodeID: &zeroValue} + p.GetNodeID() + p = &PullRequestThread{} + p.GetNodeID() + p = nil + p.GetNodeID() +} + func TestPullStats_GetMergablePulls(tt *testing.T) { var zeroValue int p := &PullStats{MergablePulls: &zeroValue} diff --git a/github/github-stringify_test.go b/github/github-stringify_test.go index 9e04a01b93..f3934ec0df 100644 --- a/github/github-stringify_test.go +++ b/github/github-stringify_test.go @@ -1322,6 +1322,17 @@ func TestPullRequestReviewRequest_String(t *testing.T) { } } +func TestPullRequestThread_String(t *testing.T) { + v := PullRequestThread{ + ID: Int64(0), + NodeID: String(""), + } + want := `github.PullRequestThread{ID:0, NodeID:""}` + if got := v.String(); got != want { + t.Errorf("PullRequestThread.String = %v, want %v", got, want) + } +} + func TestPullStats_String(t *testing.T) { v := PullStats{ TotalPulls: Int(0), diff --git a/github/messages.go b/github/messages.go index c1e8161b67..324d3b80cc 100644 --- a/github/messages.go +++ b/github/messages.go @@ -81,6 +81,7 @@ var ( "pull_request": "PullRequestEvent", "pull_request_review": "PullRequestReviewEvent", "pull_request_review_comment": "PullRequestReviewCommentEvent", + "pull_request_review_thread": "PullRequestReviewThreadEvent", "pull_request_target": "PullRequestTargetEvent", "push": "PushEvent", "repository": "RepositoryEvent", diff --git a/github/messages_test.go b/github/messages_test.go index a0cf83f2e7..1d9b7f63a8 100644 --- a/github/messages_test.go +++ b/github/messages_test.go @@ -405,6 +405,10 @@ func TestParseWebHook(t *testing.T) { payload: &PullRequestReviewCommentEvent{}, messageType: "pull_request_review_comment", }, + { + payload: &PullRequestReviewThreadEvent{}, + messageType: "pull_request_review_thread", + }, { payload: &PullRequestTargetEvent{}, messageType: "pull_request_target", diff --git a/github/pulls_threads.go b/github/pulls_threads.go new file mode 100644 index 0000000000..23e924d88f --- /dev/null +++ b/github/pulls_threads.go @@ -0,0 +1,17 @@ +// Copyright 2022 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +// PullRequestThread represents a thread of comments on a pull request. +type PullRequestThread struct { + ID *int64 `json:"id,omitempty"` + NodeID *string `json:"node_id,omitempty"` + Comments []*PullRequestComment `json:"comments,omitempty"` +} + +func (p PullRequestThread) String() string { + return Stringify(p) +} diff --git a/github/pulls_threads_test.go b/github/pulls_threads_test.go new file mode 100644 index 0000000000..6df2840403 --- /dev/null +++ b/github/pulls_threads_test.go @@ -0,0 +1,191 @@ +// Copyright 2022 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "testing" + "time" +) + +func TestPullRequestThread_Marshal(t *testing.T) { + testJSONMarshal(t, &PullRequestThread{}, "{}") + + createdAt := time.Date(2002, time.February, 10, 15, 30, 0, 0, time.UTC) + updatedAt := time.Date(2002, time.February, 10, 15, 30, 0, 0, time.UTC) + reactions := &Reactions{ + TotalCount: Int(1), + PlusOne: Int(1), + MinusOne: Int(0), + Laugh: Int(0), + Confused: Int(0), + Heart: Int(0), + Hooray: Int(0), + Rocket: Int(0), + Eyes: Int(0), + URL: String("u"), + } + user := &User{ + Login: String("ll"), + ID: Int64(123), + AvatarURL: String("a"), + GravatarID: String("g"), + Name: String("n"), + Company: String("c"), + Blog: String("b"), + Location: String("l"), + Email: String("e"), + Hireable: Bool(true), + PublicRepos: Int(1), + Followers: Int(1), + Following: Int(1), + CreatedAt: &Timestamp{referenceTime}, + URL: String("u"), + } + comment := &PullRequestComment{ + ID: Int64(10), + InReplyTo: Int64(8), + Body: String("Test comment"), + Path: String("file1.txt"), + DiffHunk: String("@@ -16,33 +16,40 @@ fmt.Println()"), + PullRequestReviewID: Int64(42), + Position: Int(1), + OriginalPosition: Int(4), + StartLine: Int(2), + Line: Int(3), + OriginalLine: Int(2), + OriginalStartLine: Int(2), + Side: String("RIGHT"), + StartSide: String("LEFT"), + CommitID: String("ab"), + OriginalCommitID: String("9c"), + User: user, + Reactions: reactions, + CreatedAt: &createdAt, + UpdatedAt: &updatedAt, + URL: String("pullrequestcommentUrl"), + HTMLURL: String("pullrequestcommentHTMLUrl"), + PullRequestURL: String("pullrequestcommentPullRequestURL"), + } + + u := &PullRequestThread{ + ID: Int64(1), + NodeID: String("nid"), + Comments: []*PullRequestComment{comment, comment}, + } + + want := `{ + "id": 1, + "node_id": "nid", + "comments": [ + { + "id": 10, + "in_reply_to_id": 8, + "body": "Test comment", + "path": "file1.txt", + "diff_hunk": "@@ -16,33 +16,40 @@ fmt.Println()", + "pull_request_review_id": 42, + "position": 1, + "original_position": 4, + "start_line": 2, + "line": 3, + "original_line": 2, + "original_start_line": 2, + "side": "RIGHT", + "start_side": "LEFT", + "commit_id": "ab", + "original_commit_id": "9c", + "user": { + "login": "ll", + "id": 123, + "avatar_url": "a", + "gravatar_id": "g", + "name": "n", + "company": "c", + "blog": "b", + "location": "l", + "email": "e", + "hireable": true, + "public_repos": 1, + "followers": 1, + "following": 1, + "created_at": ` + referenceTimeStr + `, + "url": "u" + }, + "reactions": { + "total_count": 1, + "+1": 1, + "-1": 0, + "laugh": 0, + "confused": 0, + "heart": 0, + "hooray": 0, + "rocket": 0, + "eyes": 0, + "url": "u" + }, + "created_at": "2002-02-10T15:30:00Z", + "updated_at": "2002-02-10T15:30:00Z", + "url": "pullrequestcommentUrl", + "html_url": "pullrequestcommentHTMLUrl", + "pull_request_url": "pullrequestcommentPullRequestURL" + }, + { + "id": 10, + "in_reply_to_id": 8, + "body": "Test comment", + "path": "file1.txt", + "diff_hunk": "@@ -16,33 +16,40 @@ fmt.Println()", + "pull_request_review_id": 42, + "position": 1, + "original_position": 4, + "start_line": 2, + "line": 3, + "original_line": 2, + "original_start_line": 2, + "side": "RIGHT", + "start_side": "LEFT", + "commit_id": "ab", + "original_commit_id": "9c", + "user": { + "login": "ll", + "id": 123, + "avatar_url": "a", + "gravatar_id": "g", + "name": "n", + "company": "c", + "blog": "b", + "location": "l", + "email": "e", + "hireable": true, + "public_repos": 1, + "followers": 1, + "following": 1, + "created_at": ` + referenceTimeStr + `, + "url": "u" + }, + "reactions": { + "total_count": 1, + "+1": 1, + "-1": 0, + "laugh": 0, + "confused": 0, + "heart": 0, + "hooray": 0, + "rocket": 0, + "eyes": 0, + "url": "u" + }, + "created_at": "2002-02-10T15:30:00Z", + "updated_at": "2002-02-10T15:30:00Z", + "url": "pullrequestcommentUrl", + "html_url": "pullrequestcommentHTMLUrl", + "pull_request_url": "pullrequestcommentPullRequestURL" + } + ] + }` + + testJSONMarshal(t, u, want) +} diff --git a/github/repos_hooks_deliveries_test.go b/github/repos_hooks_deliveries_test.go index 1a27685961..eea17386a0 100644 --- a/github/repos_hooks_deliveries_test.go +++ b/github/repos_hooks_deliveries_test.go @@ -178,6 +178,7 @@ var hookDeliveryPayloadTypeToStruct = map[string]interface{}{ "pull_request": &PullRequestEvent{}, "pull_request_review": &PullRequestReviewEvent{}, "pull_request_review_comment": &PullRequestReviewCommentEvent{}, + "pull_request_review_thread": &PullRequestReviewThreadEvent{}, "pull_request_target": &PullRequestTargetEvent{}, "push": &PushEvent{}, "release": &ReleaseEvent{},