Verified Commit 8ef279d7 authored by Miguel Ferreira's avatar Miguel Ferreira Committed by GitLab
Browse files

feat: Expose `re_requested` and `state` on merge request webhook reviewers

Changelog: Improvements
parent 9c3772eb
Loading
Loading
Loading
Loading
+43 −0
Original line number Diff line number Diff line
@@ -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)
}
```
+16 −3
Original line number Diff line number Diff line
@@ -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 {
@@ -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 {
@@ -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"`
+77 −1
Original line number Diff line number Diff line
@@ -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")