diff --git a/draft_notes.go b/draft_notes.go new file mode 100644 index 000000000..376e4d0c8 --- /dev/null +++ b/draft_notes.go @@ -0,0 +1,233 @@ +// +// Copyright 2021, Sander van Harmelen +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package gitlab + +import ( + "fmt" + "net/http" +) + +type DraftNote struct { + ID int `json:"id"` + AuthorID int `json:"author_id"` + MergeRequestID int `json:"merge_request_id"` + ResolveDiscussion bool `json:"resolve_discussion"` + DiscussionID string `json:"discussion_id"` + Note string `json:"note"` + CommitID string `json:"commit_id"` + LineCode string `json:"line_code"` + Position *NotePosition `json:"position"` +} + +// DraftNotesService handles communication with the draft notes related methods +// of the GitLab API. +// +// GitLab API docs: +// https://docs.gitlab.com/ee/api/draft_notes.html#list-all-merge-request-draft-notes +type DraftNotesService struct { + client *Client +} + +// ListDraftNotesOptions represents the available ListDraftNotes() +// options. +// +// GitLab API docs: +// https://docs.gitlab.com/ee/api/draft_notes.html#list-all-merge-request-draft-notes +type ListDraftNotesOptions struct { + ListOptions + OrderBy *string `url:"order_by,omitempty" json:"order_by,omitempty"` + Sort *string `url:"sort,omitempty" json:"sort,omitempty"` +} + +// ListDraftNotes gets a list of all draft notes for a merge request. +// +// Gitlab API docs: +// https://docs.gitlab.com/ee/api/draft_notes.html#list-all-merge-request-draft-notes +func (s *DraftNotesService) ListDraftNotes(pid interface{}, mergeRequest int, opt *ListDraftNotesOptions, options ...RequestOptionFunc) ([]*DraftNote, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/merge_requests/%d/draft_notes", PathEscape(project), mergeRequest) + + req, err := s.client.NewRequest(http.MethodGet, u, opt, options) + if err != nil { + return nil, nil, err + } + + var n []*DraftNote + resp, err := s.client.Do(req, &n) + if err != nil { + return nil, resp, err + } + + return n, resp, nil +} + +// GetDraftNote gets a single draft note for a merge request. +// +// Gitlab API docs: +// https://docs.gitlab.com/ee/api/draft_notes.html#get-a-single-draft-note +func (s *DraftNotesService) GetDraftNote(pid interface{}, mergeRequest int, note int, options ...RequestOptionFunc) (*DraftNote, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/merge_requests/%d/draft_notes/%d", PathEscape(project), mergeRequest, note) + + req, err := s.client.NewRequest(http.MethodGet, u, nil, options) + if err != nil { + return nil, nil, err + } + + n := new(DraftNote) + resp, err := s.client.Do(req, &n) + if err != nil { + return nil, resp, err + } + + return n, resp, nil +} + +// CreateDraftNoteOptions represents the available CreateDraftNote() +// options. +// +// Gitlab API docs: +// https://docs.gitlab.com/ee/api/draft_notes.html#create-a-draft-note +type CreateDraftNoteOptions struct { + Note *string `url:"note" json:"note"` + CommitID *string `url:"commit_id,omitempty" json:"commit_id,omitempty"` + InReplyToDiscussionID *string `url:"in_reply_to_discussion_id,omitempty" json:"in_reply_to_discussion_id,omitempty"` + ResolveDiscussion *bool `url:"resolve_discussion,omitempty" json:"resolve_discussion,omitempty"` + Position *PositionOptions `url:"position,omitempty" json:"position,omitempty"` +} + +// CreateDraftNote creates a draft note for a merge request. +// +// Gitlab API docs: +// https://docs.gitlab.com/ee/api/draft_notes.html#create-a-draft-note +func (s *DraftNotesService) CreateDraftNote(pid interface{}, mergeRequest int, opt *CreateDraftNoteOptions, options ...RequestOptionFunc) (*DraftNote, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/merge_requests/%d/draft_notes", PathEscape(project), mergeRequest) + + req, err := s.client.NewRequest(http.MethodPost, u, opt, options) + if err != nil { + return nil, nil, err + } + + n := new(DraftNote) + resp, err := s.client.Do(req, &n) + if err != nil { + return nil, resp, err + } + + return n, resp, nil +} + +// UpdateDraftNoteOptions represents the available UpdateDraftNote() +// options. +// +// Gitlab API docs: +// https://docs.gitlab.com/ee/api/draft_notes.html#create-a-draft-note +type UpdateDraftNoteOptions struct { + Note *string `url:"note,omitempty" json:"note,omitempty"` + Position *PositionOptions `url:"position,omitempty" json:"position,omitempty"` +} + +// UpdateDraftNote updates a draft note for a merge request. +// +// Gitlab API docs: https://docs.gitlab.com/ee/api/draft_notes.html#create-a-draft-note +func (s *DraftNotesService) UpdateDraftNote(pid interface{}, mergeRequest int, note int, opt *UpdateDraftNoteOptions, options ...RequestOptionFunc) (*DraftNote, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/merge_requests/%d/draft_notes/%d", PathEscape(project), mergeRequest, note) + + req, err := s.client.NewRequest(http.MethodPut, u, opt, options) + if err != nil { + return nil, nil, err + } + + n := new(DraftNote) + resp, err := s.client.Do(req, &n) + if err != nil { + return nil, resp, err + } + + return n, resp, nil +} + +// DeleteDraftNote deletes a single draft note for a merge request. +// +// Gitlab API docs: +// https://docs.gitlab.com/ee/api/draft_notes.html#delete-a-draft-note +func (s *DraftNotesService) DeleteDraftNote(pid interface{}, mergeRequest int, note int, options ...RequestOptionFunc) (*Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, err + } + u := fmt.Sprintf("projects/%s/merge_requests/%d/draft_notes/%d", PathEscape(project), mergeRequest, note) + + req, err := s.client.NewRequest(http.MethodDelete, u, nil, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// PublishDraftNote publishes a single draft note for a merge request. +// +// Gitlab API docs: +// https://docs.gitlab.com/ee/api/draft_notes.html#publish-a-draft-note +func (s *DraftNotesService) PublishDraftNote(pid interface{}, mergeRequest int, note int, options ...RequestOptionFunc) (*Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, err + } + u := fmt.Sprintf("projects/%s/merge_requests/%d/draft_notes/%d/publish", PathEscape(project), mergeRequest, note) + + req, err := s.client.NewRequest(http.MethodPut, u, nil, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// PublishAllDraftNotes publishes all draft notes for a merge request that belong to the user. +// +// Gitlab API docs: +// https://docs.gitlab.com/ee/api/draft_notes.html#publish-a-draft-note +func (s *DraftNotesService) PublishAllDraftNotes(pid interface{}, mergeRequest int, options ...RequestOptionFunc) (*Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, err + } + u := fmt.Sprintf("projects/%s/merge_requests/%d/draft_notes/bulk_publish", PathEscape(project), mergeRequest) + + req, err := s.client.NewRequest(http.MethodPost, u, nil, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} diff --git a/draft_notes_test.go b/draft_notes_test.go new file mode 100644 index 000000000..266efaac6 --- /dev/null +++ b/draft_notes_test.go @@ -0,0 +1,212 @@ +// +// Copyright 2021, Sander van Harmelen +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package gitlab + +import ( + "net/http" + "reflect" + "testing" +) + +func TestGetDraftNote(t *testing.T) { + mux, client := setup(t) + mux.HandleFunc("/api/v4/projects/1/merge_requests/4329/draft_notes/3", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodGet) + mustWriteHTTPResponse(t, w, "testdata/get_draft_note.json") + }) + + note, _, err := client.DraftNotes.GetDraftNote("1", 4329, 3) + if err != nil { + t.Fatal(err) + } + + want := &DraftNote{ + ID: 37349978, + AuthorID: 10271899, + MergeRequestID: 291473309, + ResolveDiscussion: false, + DiscussionID: "", + Note: "Some draft note", + CommitID: "", + LineCode: "", + Position: nil, + } + + if !reflect.DeepEqual(note, want) { + t.Errorf("DraftNotes.GetDraftNote want %#v, got %#v", note, want) + } +} + +func TestListDraftNotes(t *testing.T) { + mux, client := setup(t) + mux.HandleFunc("/api/v4/projects/1/merge_requests/4329/draft_notes", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodGet) + mustWriteHTTPResponse(t, w, "testdata/list_draft_notes.json") + }) + + notes, _, err := client.DraftNotes.ListDraftNotes("1", 4329, nil) + if err != nil { + t.Fatal(err) + } + + want := []*DraftNote{ + { + ID: 37349978, + AuthorID: 10271899, + MergeRequestID: 291473309, + ResolveDiscussion: false, + DiscussionID: "", + Note: "Some draft note", + CommitID: "", + LineCode: "", + Position: nil, + }, + { + ID: 37349979, + AuthorID: 10271899, + MergeRequestID: 291473309, + ResolveDiscussion: false, + DiscussionID: "", + Note: "Some draft note 2", + CommitID: "", + LineCode: "3dacf79e0d779e2baa1c700cf56510e42f55cf85_10_9", + Position: &NotePosition{ + BaseSHA: "64581c4ee41beb44d943d7801f82d9038e25e453", + StartSHA: "87bffbff93bf334889780f54ae1922355661f867", + HeadSHA: "2c972dbf9094c380f5f00dcd8112d2c69b24c859", + OldPath: "src/some-dir/some-file.js", + NewPath: "src/some-dir/some-file.js", + PositionType: "text", + NewLine: 9, + LineRange: &LineRange{ + StartRange: &LinePosition{ + LineCode: "3dacf79e0d779e2baa1c700cf56510e42f55cf85_10_9", + Type: "new", + NewLine: 9, + }, + EndRange: &LinePosition{ + LineCode: "3dacf79e0d779e2baa1c700cf56510e42f55cf85_10_9", + Type: "new", + NewLine: 9, + }, + }, + }, + }, + } + + if !reflect.DeepEqual(notes, want) { + t.Errorf("DraftNotes.GetDraftNote want %#v, got %#v", notes, want) + } +} + +func TestCreateDraftNote(t *testing.T) { + mux, client := setup(t) + mux.HandleFunc("/api/v4/projects/1/merge_requests/4329/draft_notes", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodPost) + mustWriteHTTPResponse(t, w, "testdata/create_draft_note.json") + }) + + note, _, err := client.DraftNotes.CreateDraftNote("1", 4329, &CreateDraftNoteOptions{ + Note: Ptr("Some new draft note"), + }) + if err != nil { + t.Fatal(err) + } + + want := &DraftNote{ + ID: 37349980, + AuthorID: 10271899, + MergeRequestID: 291473309, + ResolveDiscussion: false, + DiscussionID: "", + Note: "Some new draft note", + CommitID: "", + LineCode: "", + Position: nil, + } + + if !reflect.DeepEqual(note, want) { + t.Errorf("DraftNotes.GetDraftNote want %#v, got %#v", note, want) + } +} + +func TestUpdateDraftNote(t *testing.T) { + mux, client := setup(t) + mux.HandleFunc("/api/v4/projects/1/merge_requests/4329/draft_notes/3", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodPut) + mustWriteHTTPResponse(t, w, "testdata/update_draft_note.json") + }) + + note, _, err := client.DraftNotes.UpdateDraftNote("1", 4329, 3, &UpdateDraftNoteOptions{ + Note: Ptr("Some changed draft note"), + }) + if err != nil { + t.Fatal(err) + } + + want := &DraftNote{ + ID: 37349980, + AuthorID: 10271899, + MergeRequestID: 291473309, + ResolveDiscussion: false, + DiscussionID: "", + Note: "Some changed draft note", + CommitID: "", + LineCode: "", + Position: nil, + } + + if !reflect.DeepEqual(note, want) { + t.Errorf("DraftNotes.UpdateDraftNote want %#v, got %#v", note, want) + } +} + +func TestDeleteDraftNote(t *testing.T) { + mux, client := setup(t) + mux.HandleFunc("/api/v4/projects/1/merge_requests/4329/draft_notes/3", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodDelete) + }) + + _, err := client.DraftNotes.DeleteDraftNote("1", 4329, 3) + if err != nil { + t.Errorf("DraftNotes.DeleteDraftNote returned error: %v", err) + } +} + +func TestPublishDraftNote(t *testing.T) { + mux, client := setup(t) + mux.HandleFunc("/api/v4/projects/1/merge_requests/4329/draft_notes/3/publish", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodPut) + }) + + _, err := client.DraftNotes.PublishDraftNote("1", 4329, 3) + if err != nil { + t.Errorf("DraftNotes.PublishDraftNote returned error: %v", err) + } +} + +func TestPublishAllDraftNotes(t *testing.T) { + mux, client := setup(t) + mux.HandleFunc("/api/v4/projects/1/merge_requests/4329/draft_notes/bulk_publish", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodPost) + }) + + _, err := client.DraftNotes.PublishAllDraftNotes("1", 4329) + if err != nil { + t.Errorf("DraftNotes.PublishAllDraftNotes returned error: %v", err) + } +} diff --git a/gitlab.go b/gitlab.go index c773ef5cb..e49205019 100644 --- a/gitlab.go +++ b/gitlab.go @@ -124,6 +124,7 @@ type Client struct { Deployments *DeploymentsService Discussions *DiscussionsService DockerfileTemplate *DockerfileTemplatesService + DraftNotes *DraftNotesService Environments *EnvironmentsService EpicIssues *EpicIssuesService Epics *EpicsService @@ -357,6 +358,7 @@ func newClient(options ...ClientOptionFunc) (*Client, error) { c.Deployments = &DeploymentsService{client: c} c.Discussions = &DiscussionsService{client: c} c.DockerfileTemplate = &DockerfileTemplatesService{client: c} + c.DraftNotes = &DraftNotesService{client: c} c.Environments = &EnvironmentsService{client: c} c.EpicIssues = &EpicIssuesService{client: c} c.Epics = &EpicsService{client: c} diff --git a/groups_test.go b/groups_test.go index d848e7424..7fc6c3fc6 100644 --- a/groups_test.go +++ b/groups_test.go @@ -179,7 +179,6 @@ func TestDeleteGroup_WithPermanentDelete(t *testing.T) { PermanentlyRemove: Ptr(true), FullPath: Ptr("testPath"), }) - if err != nil { t.Errorf("Groups.DeleteGroup returned error: %v", err) } diff --git a/testdata/create_draft_note.json b/testdata/create_draft_note.json new file mode 100644 index 000000000..bc0dc685d --- /dev/null +++ b/testdata/create_draft_note.json @@ -0,0 +1,11 @@ +{ + "id": 37349980, + "author_id": 10271899, + "merge_request_id": 291473309, + "resolve_discussion": false, + "discussion_id": null, + "note": "Some new draft note", + "commit_id": null, + "position": null, + "line_code": null +} diff --git a/testdata/get_draft_note.json b/testdata/get_draft_note.json new file mode 100644 index 000000000..a1e9cd467 --- /dev/null +++ b/testdata/get_draft_note.json @@ -0,0 +1,11 @@ +{ + "id": 37349978, + "author_id": 10271899, + "merge_request_id": 291473309, + "resolve_discussion": false, + "discussion_id": null, + "note": "Some draft note", + "commit_id": null, + "position": null, + "line_code": null +} diff --git a/testdata/list_draft_notes.json b/testdata/list_draft_notes.json new file mode 100644 index 000000000..15e235bd3 --- /dev/null +++ b/testdata/list_draft_notes.json @@ -0,0 +1,47 @@ +[ + { + "id": 37349978, + "author_id": 10271899, + "merge_request_id": 291473309, + "resolve_discussion": false, + "discussion_id": null, + "note": "Some draft note", + "commit_id": null, + "position": null, + "line_code": null + }, + { + "id": 37349979, + "author_id": 10271899, + "merge_request_id": 291473309, + "resolve_discussion": false, + "discussion_id": null, + "note": "Some draft note 2", + "commit_id": null, + "line_code": "3dacf79e0d779e2baa1c700cf56510e42f55cf85_10_9", + "position": { + "base_sha": "64581c4ee41beb44d943d7801f82d9038e25e453", + "start_sha": "87bffbff93bf334889780f54ae1922355661f867", + "head_sha": "2c972dbf9094c380f5f00dcd8112d2c69b24c859", + "old_path": "src/some-dir/some-file.js", + "new_path": "src/some-dir/some-file.js", + "position_type": "text", + "old_line": null, + "new_line": 9, + "line_range": { + "start": { + "line_code": "3dacf79e0d779e2baa1c700cf56510e42f55cf85_10_9", + "type": "new", + "old_line": null, + "new_line": 9 + }, + "end": { + "line_code": "3dacf79e0d779e2baa1c700cf56510e42f55cf85_10_9", + "type": "new", + "old_line": null, + "new_line": 9 + } + } + } + } +] diff --git a/testdata/update_draft_note.json b/testdata/update_draft_note.json new file mode 100644 index 000000000..98cea1aab --- /dev/null +++ b/testdata/update_draft_note.json @@ -0,0 +1,11 @@ +{ + "id": 37349980, + "author_id": 10271899, + "merge_request_id": 291473309, + "resolve_discussion": false, + "discussion_id": null, + "note": "Some changed draft note", + "commit_id": null, + "position": null, + "line_code": null +}