Loading docs/release-3.0-migration.md +43 −0 Original line number Diff line number Diff line Loading @@ -86,3 +86,46 @@ _, _, err := client.ProjectFeatureFlags.UpdateProjectFeatureFlag(1, "awesome_fea }, }) ``` ## Merge request webhook reviewers now use `EventReviewer` Merge request webhook events model reviewers with a dedicated `EventReviewer` type instead of `EventUser`. `EventReviewer` includes all the standard user fields plus the reviewer-specific `State` and `ReRequested` fields that GitLab emits, which were previously discarded on unmarshal. The affected fields are `MergeEvent.Reviewers` and `MergeEventChangesReviewers.Previous` / `MergeEventChangesReviewers.Current`, which change from `[]*EventUser` to `[]*EventReviewer`. **New type:** ```go type EventReviewer struct { ID int64 `json:"id"` Name string `json:"name"` Username string `json:"username"` AvatarURL string `json:"avatar_url"` Email string `json:"email"` State string `json:"state"` ReRequested bool `json:"re_requested"` } ``` `State` is one of `unreviewed`, `review_started`, `reviewed`, `requested_changes`, `approved`, or `unapproved`. `ReRequested` is `true` when a reviewer has been re-requested to review. **Changes:** ```go // Before (v2.x): reviewers were []*EventUser for _, reviewer := range event.Reviewers { fmt.Println(reviewer.Username) } // After (v3.0): reviewers are []*EventReviewer, exposing State and ReRequested for _, reviewer := range event.Reviewers { fmt.Println(reviewer.Username, reviewer.State, reviewer.ReRequested) } ``` event_webhook_types.go +16 −3 Original line number Diff line number Diff line Loading @@ -715,7 +715,7 @@ type MergeEvent struct { Labels []*EventLabel `json:"labels"` Changes MergeEventChanges `json:"changes"` Assignees []*EventUser `json:"assignees"` Reviewers []*EventUser `json:"reviewers"` Reviewers []*EventReviewer `json:"reviewers"` } type MergeEventProject struct { Loading Loading @@ -817,8 +817,8 @@ type MergeEventChanges struct { } type MergeEventChangesReviewers struct { Previous []*EventUser `json:"previous"` Current []*EventUser `json:"current"` Previous []*EventReviewer `json:"previous"` Current []*EventReviewer `json:"current"` } type MergeEventChangesDraft struct { Loading Loading @@ -876,6 +876,19 @@ type EventUser struct { Email string `json:"email"` } // EventReviewer represents a merge request reviewer in a webhook event. // GitLab API docs: // https://docs.gitlab.com/user/project/integrations/webhook_events/#reviewer-state-tracking type EventReviewer struct { ID int64 `json:"id"` Name string `json:"name"` Username string `json:"username"` AvatarURL string `json:"avatar_url"` Email string `json:"email"` State string `json:"state"` ReRequested bool `json:"re_requested"` } // MergeParams represents the merge params. type MergeParams struct { ForceRemoveSourceBranch bool `json:"force_remove_source_branch"` Loading event_webhook_types_test.go +77 −1 Original line number Diff line number Diff line Loading @@ -919,11 +919,87 @@ func TestMergeEventUnmarshal(t *testing.T) { }, }, Assignees: []*EventUser{user1}, Reviewers: []*EventUser{user1}, Reviewers: []*EventReviewer{ { ID: 1, Name: "User1", Username: "user1", AvatarURL: "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon", }, }, } assert.Equal(t, expectedEvent, event) } func TestMergeEventUnmarshal_ReRequestReview(t *testing.T) { t.Parallel() // GIVEN a merge request webhook payload for a re-request review action, // where changes.reviewers holds the previous and current reviewer states. jsonObject := []byte(`{ "object_kind": "merge_request", "event_type": "merge_request", "object_attributes": { "action": "update" }, "changes": { "reviewers": { "previous": [ { "id": 6, "name": "User1", "username": "user1", "state": "approved", "re_requested": false, "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", "email": "user1@example.com" } ], "current": [ { "id": 6, "name": "User1", "username": "user1", "state": "unreviewed", "re_requested": true, "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", "email": "user1@example.com" } ] } } }`) // WHEN the payload is unmarshalled into a MergeEvent. var event *MergeEvent err := json.Unmarshal(jsonObject, &event) require.NoError(t, err) // THEN the reviewer's state transition and re_requested flag are captured. require.Len(t, event.Changes.Reviewers.Previous, 1) require.Len(t, event.Changes.Reviewers.Current, 1) assert.Equal(t, &EventReviewer{ ID: 6, Name: "User1", Username: "user1", AvatarURL: "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", Email: "user1@example.com", State: "approved", ReRequested: false, }, event.Changes.Reviewers.Previous[0]) assert.Equal(t, &EventReviewer{ ID: 6, Name: "User1", Username: "user1", AvatarURL: "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", Email: "user1@example.com", State: "unreviewed", ReRequested: true, }, event.Changes.Reviewers.Current[0]) } func TestMemberEventUnmarshal(t *testing.T) { t.Parallel() jsonObject := loadFixture(t, "testdata/webhooks/member.json") Loading Loading
docs/release-3.0-migration.md +43 −0 Original line number Diff line number Diff line Loading @@ -86,3 +86,46 @@ _, _, err := client.ProjectFeatureFlags.UpdateProjectFeatureFlag(1, "awesome_fea }, }) ``` ## Merge request webhook reviewers now use `EventReviewer` Merge request webhook events model reviewers with a dedicated `EventReviewer` type instead of `EventUser`. `EventReviewer` includes all the standard user fields plus the reviewer-specific `State` and `ReRequested` fields that GitLab emits, which were previously discarded on unmarshal. The affected fields are `MergeEvent.Reviewers` and `MergeEventChangesReviewers.Previous` / `MergeEventChangesReviewers.Current`, which change from `[]*EventUser` to `[]*EventReviewer`. **New type:** ```go type EventReviewer struct { ID int64 `json:"id"` Name string `json:"name"` Username string `json:"username"` AvatarURL string `json:"avatar_url"` Email string `json:"email"` State string `json:"state"` ReRequested bool `json:"re_requested"` } ``` `State` is one of `unreviewed`, `review_started`, `reviewed`, `requested_changes`, `approved`, or `unapproved`. `ReRequested` is `true` when a reviewer has been re-requested to review. **Changes:** ```go // Before (v2.x): reviewers were []*EventUser for _, reviewer := range event.Reviewers { fmt.Println(reviewer.Username) } // After (v3.0): reviewers are []*EventReviewer, exposing State and ReRequested for _, reviewer := range event.Reviewers { fmt.Println(reviewer.Username, reviewer.State, reviewer.ReRequested) } ```
event_webhook_types.go +16 −3 Original line number Diff line number Diff line Loading @@ -715,7 +715,7 @@ type MergeEvent struct { Labels []*EventLabel `json:"labels"` Changes MergeEventChanges `json:"changes"` Assignees []*EventUser `json:"assignees"` Reviewers []*EventUser `json:"reviewers"` Reviewers []*EventReviewer `json:"reviewers"` } type MergeEventProject struct { Loading Loading @@ -817,8 +817,8 @@ type MergeEventChanges struct { } type MergeEventChangesReviewers struct { Previous []*EventUser `json:"previous"` Current []*EventUser `json:"current"` Previous []*EventReviewer `json:"previous"` Current []*EventReviewer `json:"current"` } type MergeEventChangesDraft struct { Loading Loading @@ -876,6 +876,19 @@ type EventUser struct { Email string `json:"email"` } // EventReviewer represents a merge request reviewer in a webhook event. // GitLab API docs: // https://docs.gitlab.com/user/project/integrations/webhook_events/#reviewer-state-tracking type EventReviewer struct { ID int64 `json:"id"` Name string `json:"name"` Username string `json:"username"` AvatarURL string `json:"avatar_url"` Email string `json:"email"` State string `json:"state"` ReRequested bool `json:"re_requested"` } // MergeParams represents the merge params. type MergeParams struct { ForceRemoveSourceBranch bool `json:"force_remove_source_branch"` Loading
event_webhook_types_test.go +77 −1 Original line number Diff line number Diff line Loading @@ -919,11 +919,87 @@ func TestMergeEventUnmarshal(t *testing.T) { }, }, Assignees: []*EventUser{user1}, Reviewers: []*EventUser{user1}, Reviewers: []*EventReviewer{ { ID: 1, Name: "User1", Username: "user1", AvatarURL: "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon", }, }, } assert.Equal(t, expectedEvent, event) } func TestMergeEventUnmarshal_ReRequestReview(t *testing.T) { t.Parallel() // GIVEN a merge request webhook payload for a re-request review action, // where changes.reviewers holds the previous and current reviewer states. jsonObject := []byte(`{ "object_kind": "merge_request", "event_type": "merge_request", "object_attributes": { "action": "update" }, "changes": { "reviewers": { "previous": [ { "id": 6, "name": "User1", "username": "user1", "state": "approved", "re_requested": false, "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", "email": "user1@example.com" } ], "current": [ { "id": 6, "name": "User1", "username": "user1", "state": "unreviewed", "re_requested": true, "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", "email": "user1@example.com" } ] } } }`) // WHEN the payload is unmarshalled into a MergeEvent. var event *MergeEvent err := json.Unmarshal(jsonObject, &event) require.NoError(t, err) // THEN the reviewer's state transition and re_requested flag are captured. require.Len(t, event.Changes.Reviewers.Previous, 1) require.Len(t, event.Changes.Reviewers.Current, 1) assert.Equal(t, &EventReviewer{ ID: 6, Name: "User1", Username: "user1", AvatarURL: "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", Email: "user1@example.com", State: "approved", ReRequested: false, }, event.Changes.Reviewers.Previous[0]) assert.Equal(t, &EventReviewer{ ID: 6, Name: "User1", Username: "user1", AvatarURL: "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", Email: "user1@example.com", State: "unreviewed", ReRequested: true, }, event.Changes.Reviewers.Current[0]) } func TestMemberEventUnmarshal(t *testing.T) { t.Parallel() jsonObject := loadFixture(t, "testdata/webhooks/member.json") Loading