Repository: xanzy/go-gitlab
Pull request #528 (2018-12-14 06:31:58)

Add project level MergeRequest API

bilby91: Anything I can do to speed up the review of this one @svanharmelen :) ?

Looking forward to merge this MR in order to add support for this configurations on https://github.com/terraform-providers/terraform-provider-gitlab 
bilby91: @svanharmelen Made the requested changes! 

Ended up moving the calls to the `projects.go` file since it made sense. I kept some of the structures under `merge_request_approvals.go` since the other api was also reusing some of the structs. 

Let me know if it looks good now or you want any further change. 

Patch:
diff --git a/merge_request_approvals.go b/merge_request_approvals.go
index fdaf87c..85fd882 100644
--- a/merge_request_approvals.go
+++ b/merge_request_approvals.go
@@ -20,52 +20,55 @@ type MergeRequestApprovalsService struct {
 // GitLab API docs:
 // https://docs.gitlab.com/ee/api/merge_request_approvals.html#merge-request-level-mr-approvals
 type MergeRequestApprovals struct {
-	ID                   int        `json:"id"`
-	ProjectID            int        `json:"project_id"`
-	Title                string     `json:"title"`
-	Description          string     `json:"description"`
-	State                string     `json:"state"`
-	CreatedAt            *time.Time `json:"created_at"`
-	UpdatedAt            *time.Time `json:"updated_at"`
-	MergeStatus          string     `json:"merge_status"`
-	ApprovalsBeforeMerge int        `json:"approvals_before_merge"`
-	ApprovalsRequired    int        `json:"approvals_required"`
-	ApprovalsLeft        int        `json:"approvals_left"`
-	ApprovedBy           []struct {
-		User struct {
-			ID        int    `json:"id"`
-			Name      string `json:"name"`
-			Username  string `json:"username"`
-			State     string `json:"state"`
-			AvatarURL string `json:"avatar_url"`
-			WebURL    string `json:"web_url"`
-		} `json:"user"`
-	} `json:"approved_by"`
-	Approvers []struct {
-		User struct {
-			ID        int    `json:"id"`
-			Name      string `json:"name"`
-			Username  string `json:"username"`
-			State     string `json:"state"`
-			AvatarURL string `json:"avatar_url"`
-			WebURL    string `json:"web_url"`
-		} `json:"user"`
-	} `json:"approvers"`
-	ApproverGroups []struct {
-		Group struct {
-			ID                   int    `json:"id"`
-			Name                 string `json:"name"`
-			Path                 string `json:"path"`
-			Description          string `json:"description"`
-			Visibility           string `json:"visibility"`
-			AvatarURL            string `json:"avatar_url"`
-			WebURL               string `json:"web_url"`
-			FullName             string `json:"full_name"`
-			FullPath             string `json:"full_path"`
-			LFSEnabled           bool   `json:"lfs_enabled"`
-			RequestAccessEnabled bool   `json:"request_access_enabled"`
-		} `json:"group"`
-	} `json:"approver_group"`
+	ID                   int                          `json:"id"`
+	ProjectID            int                          `json:"project_id"`
+	Title                string                       `json:"title"`
+	Description          string                       `json:"description"`
+	State                string                       `json:"state"`
+	CreatedAt            *time.Time                   `json:"created_at"`
+	UpdatedAt            *time.Time                   `json:"updated_at"`
+	MergeStatus          string                       `json:"merge_status"`
+	ApprovalsBeforeMerge int                          `json:"approvals_before_merge"`
+	ApprovalsRequired    int                          `json:"approvals_required"`
+	ApprovalsLeft        int                          `json:"approvals_left"`
+	ApprovedBy           []*MergeRequestApproverUser  `json:"approved_by"`
+	Approvers            []*MergeRequestApproverUser  `json:"approvers"`
+	ApproverGroups       []*MergeRequestApproverGroup `json:"approver_groups"`
+}
+
+// MergeRequestApproverGroup  represents GitLab project level merge request approver group.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/merge_request_approvals.html#project-level-mr-approvals
+type MergeRequestApproverGroup struct {
+	Group struct {
+		ID                   int    `json:"id"`
+		Name                 string `json:"name"`
+		Path                 string `json:"path"`
+		Description          string `json:"description"`
+		Visibility           string `json:"visibility"`
+		AvatarURL            string `json:"avatar_url"`
+		WebURL               string `json:"web_url"`
+		FullName             string `json:"full_name"`
+		FullPath             string `json:"full_path"`
+		LFSEnabled           bool   `json:"lfs_enabled"`
+		RequestAccessEnabled bool   `json:"request_access_enabled"`
+	}
+}
+
+// MergeRequestApproverUser  represents GitLab project level merge request approver user.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/merge_request_approvals.html#project-level-mr-approvals
+type MergeRequestApproverUser struct {
+	User struct {
+		ID        int    `json:"id"`
+		Name      string `json:"name"`
+		Username  string `json:"username"`
+		State     string `json:"state"`
+		AvatarURL string `json:"avatar_url"`
+		WebURL    string `json:"web_url"`
+	}
 }
 
 func (m MergeRequestApprovals) String() string {
diff --git a/projects.go b/projects.go
index c30aa32..0b34ad7 100644
--- a/projects.go
+++ b/projects.go
@@ -1172,3 +1172,114 @@ func (s *ProjectsService) DeleteProjectPushRule(pid interface{}, options ...Opti
 
 	return s.client.Do(req, nil)
 }
+
+// ProjectApprovals  represents GitLab project level merge request approvals.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/merge_request_approvals.html#project-level-mr-approvals
+type ProjectApprovals struct {
+	ApprovalsBeforeMerge                      int                          `json:"approvals_before_merge"`
+	ResetApprovalsOnPush                      bool                         `json:"reset_approvals_on_push"`
+	DisableOverridingApproversPerMergeRequest bool                         `json:"disable_overriding_approvers_per_merge_request"`
+	Approvers                                 []*MergeRequestApproverUser  `json:"approvers"`
+	ApproverGroups                            []*MergeRequestApproverGroup `json:"approver_groups"`
+}
+
+// ApprovalsConfigurationOptions represents the available
+// ApprovalsConfigurationOptions() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/merge_request_approvals.html#change-configuration
+type ApprovalsConfigurationOptions struct {
+	ApprovalsBeforeMerge                      *int  `url:"approvals_before_merge,omitempty" json:"approvals_before_merge,omitempty"`
+	ResetApprovalsOnPush                      *bool `url:"reset_approvals_on_push,omitempty" json:"reset_approvals_on_push,omitempty"`
+	DisableOverridingApproversPerMergeRequest *bool `url:"disable_overriding_approvers_per_merge_request,omitempty" json:"disable_overriding_approvers_per_merge_request,omitempty"`
+}
+
+// ApproversConfigurationOptions represents the available
+// ApproversConfigurationOptions() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/merge_request_approvals.html#change-allowed-approvers
+type ApproversConfigurationOptions struct {
+	ApproverIDs      []*int `url:"approver_ids,omitempty" json:"approver_ids,omitempty"`
+	ApproverGroupIDs []*int `url:"approver_group_ids,omitempty" json:"approver_group_ids,omitempty"`
+}
+
+// GetApprovalsConfigurationOptions get the approval configurations options for a given project on GitLab.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/merge_request_approvals.html#get-configuration
+func (s *ProjectsService) GetApprovalsConfigurationOptions(pid interface{}, options ...OptionFunc) (*ProjectApprovals, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/approvals", url.QueryEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	mrp := new(ProjectApprovals)
+
+	resp, err := s.client.Do(req, mrp)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return mrp, resp, err
+}
+
+// ChangeApprovalsConfigurationOptions updates the approval configurations for a given project on GitLab.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/merge_request_approvals.html#change-configuration
+func (s *ProjectsService) ChangeApprovalsConfigurationOptions(pid interface{}, opt *ApprovalsConfigurationOptions, options ...OptionFunc) (*ProjectApprovals, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/approvals", url.QueryEscape(project))
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	mrp := new(ProjectApprovals)
+
+	resp, err := s.client.Do(req, mrp)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return mrp, resp, err
+}
+
+// ChangeApproversConfigurationOptions updates the list of approvers for a given project on GitLab.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/merge_request_approvals.html#change-allowed-approvers
+func (s *ProjectsService) ChangeApproversConfigurationOptions(pid interface{}, opt *ApproversConfigurationOptions, options ...OptionFunc) (*ProjectApprovals, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/approvers", url.QueryEscape(project))
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	mrp := new(ProjectApprovals)
+
+	resp, err := s.client.Do(req, mrp)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return mrp, resp, err
+}


Test patch:
diff --git a/gitlab_test.go b/gitlab_test.go
index 2427449..dde0905 100644
--- a/gitlab_test.go
+++ b/gitlab_test.go
@@ -1,6 +1,7 @@
 package gitlab
 
 import (
+	"bytes"
 	"context"
 	"encoding/json"
 	"io/ioutil"
@@ -44,6 +45,19 @@ func testMethod(t *testing.T, r *http.Request, want string) {
 	}
 }
 
+func testBody(t *testing.T, r *http.Request, want string) {
+	buffer := new(bytes.Buffer)
+	_, err := buffer.ReadFrom(r.Body)
+
+	if err != nil {
+		t.Fatalf("Failed to Read Body: %v", err)
+	}
+
+	if got := buffer.String(); got != want {
+		t.Errorf("Request body: %s, want %s", got, want)
+	}
+}
+
 func TestNewClient(t *testing.T) {
 	c := NewClient(nil, "")
 	expectedBaseURL := defaultBaseURL + apiVersionPath
diff --git a/projects_test.go b/projects_test.go
index 06ce1cd..fbc0e8c 100644
--- a/projects_test.go
+++ b/projects_test.go
@@ -351,3 +351,141 @@ func TestDeleteSharedProjectFromGroup(t *testing.T) {
 		t.Errorf("Projects.DeleteSharedProjectFromGroup returned error: %v", err)
 	}
 }
+
+func TestGetApprovalsConfigurationOptions(t *testing.T) {
+	mux, server, client := setup()
+	defer teardown(server)
+
+	mux.HandleFunc("/api/v4/projects/1/approvals", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		fmt.Fprint(w, `{
+			"approvals_before_merge": 3,
+			"reset_approvals_on_push": false,
+			"disable_overriding_approvers_per_merge_request": false,
+			"approvers": [],
+			"approver_groups": []
+		}`)
+	})
+
+	approvals, _, err := client.Projects.GetApprovalsConfigurationOptions(1)
+
+	if err != nil {
+		t.Errorf("Projects.GetApprovalsConfigurationOptions returned error: %v", err)
+	}
+
+	want := &ProjectApprovals{
+		ApprovalsBeforeMerge:                      3,
+		ResetApprovalsOnPush:                      false,
+		DisableOverridingApproversPerMergeRequest: false,
+		Approvers:      []*MergeRequestApproverUser{},
+		ApproverGroups: []*MergeRequestApproverGroup{},
+	}
+
+	if !reflect.DeepEqual(want, approvals) {
+		t.Errorf("Projects.GetApprovalsConfigurationOptions  returned %+v, want %+v", approvals, want)
+	}
+}
+
+func TestChangeApprovalsConfigurationOptions(t *testing.T) {
+	mux, server, client := setup()
+	defer teardown(server)
+
+	mux.HandleFunc("/api/v4/projects/1/approvals", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "POST")
+		testBody(t, r, `{"approvals_before_merge":3}`)
+		fmt.Fprint(w, `{
+			"approvals_before_merge": 3,
+			"reset_approvals_on_push": false,
+			"disable_overriding_approvers_per_merge_request": false,
+			"approvers": [],
+			"approver_groups": []
+		}`)
+	})
+
+	opt := &ApprovalsConfigurationOptions{
+		ApprovalsBeforeMerge: Int(3),
+	}
+
+	approvals, _, err := client.Projects.ChangeApprovalsConfigurationOptions(1, opt)
+
+	if err != nil {
+		t.Errorf("Projects.ChangeApprovalConfigurationOptions returned error: %v", err)
+	}
+
+	want := &ProjectApprovals{
+		ApprovalsBeforeMerge:                      3,
+		ResetApprovalsOnPush:                      false,
+		DisableOverridingApproversPerMergeRequest: false,
+		Approvers:      []*MergeRequestApproverUser{},
+		ApproverGroups: []*MergeRequestApproverGroup{},
+	}
+
+	if !reflect.DeepEqual(want, approvals) {
+		t.Errorf("Projects.ChangeApprovalConfigurationOptions  returned %+v, want %+v", approvals, want)
+	}
+}
+
+func TestChangeApproversConfigurationOptions(t *testing.T) {
+	mux, server, client := setup()
+	defer teardown(server)
+
+	mux.HandleFunc("/api/v4/projects/1/approvers", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "POST")
+		testBody(t, r, `{"approver_ids":[1],"approver_group_ids":[2]}`)
+		fmt.Fprint(w, `{
+			"approvers": [{"user":{"id":1}}],
+			"approver_groups": [{"group":{"id":2}}]
+		}`)
+	})
+
+	opt := &ApproversConfigurationOptions{
+		ApproverIDs:      []*int{Int(1)},
+		ApproverGroupIDs: []*int{Int(2)},
+	}
+
+	approvals, _, err := client.Projects.ChangeApproversConfigurationOptions(1, opt)
+
+	if err != nil {
+		t.Errorf("Projects.ChangeApproversConfigurationOptions returned error: %v", err)
+	}
+
+	want := &ProjectApprovals{
+		Approvers: []*MergeRequestApproverUser{
+			&MergeRequestApproverUser{
+				User: struct {
+					ID        int    `json:"id"`
+					Name      string `json:"name"`
+					Username  string `json:"username"`
+					State     string `json:"state"`
+					AvatarURL string `json:"avatar_url"`
+					WebURL    string `json:"web_url"`
+				}{
+					ID: 1,
+				},
+			},
+		},
+		ApproverGroups: []*MergeRequestApproverGroup{
+			&MergeRequestApproverGroup{
+				Group: struct {
+					ID                   int    `json:"id"`
+					Name                 string `json:"name"`
+					Path                 string `json:"path"`
+					Description          string `json:"description"`
+					Visibility           string `json:"visibility"`
+					AvatarURL            string `json:"avatar_url"`
+					WebURL               string `json:"web_url"`
+					FullName             string `json:"full_name"`
+					FullPath             string `json:"full_path"`
+					LFSEnabled           bool   `json:"lfs_enabled"`
+					RequestAccessEnabled bool   `json:"request_access_enabled"`
+				}{
+					ID: 2,
+				},
+			},
+		},
+	}
+
+	if !reflect.DeepEqual(want, approvals) {
+		t.Errorf("Projects.ChangeApproversConfigurationOptions  returned %+v, want %+v", approvals, want)
+	}
+}