From fc4f5cd9a85317e397c0102f17df4032b7e884be Mon Sep 17 00:00:00 2001 From: Daniel Steinke Date: Fri, 28 Jan 2022 19:40:53 +0100 Subject: [PATCH 1/4] Added support for iterations and group iterations --- group_iterations.go | 92 +++++++++++++++++++++++++++++++++++++++++++++ iterations.go | 86 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 178 insertions(+) create mode 100644 group_iterations.go create mode 100644 iterations.go diff --git a/group_iterations.go b/group_iterations.go new file mode 100644 index 000000000..967d2987e --- /dev/null +++ b/group_iterations.go @@ -0,0 +1,92 @@ +// +// Copyright 2022, stonebyte +// +// 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" + "time" +) + +// IterationsAPI handles communication with the iterations related methods +// of the GitLab API +// +// GitLab API docs: https://docs.gitlab.com/ee/api/group_iterations.html + +type GroupIterationsService struct { + client *Client +} + +// GroupInteration represents a GitLab iteration. +// +// GitLab API docs: https://docs.gitlab.com/ee/api/group_iterations.html + +type GroupIteration struct { + ID int `json:"id"` + IID int `json:"iid"` + GroupID int `json:"group_id"` + Title string `json:"title"` + Description string `json:"description"` + State int `json:"state"` + CreatedAt *time.Time `json:"created_at"` + UpdatedAt *time.Time `json:"updated_at"` + DueDate *ISOTime `json:"due_date"` + StartDate *ISOTime `json:"start_date"` + WebURL string `json:"web_url"` +} + +func (i GroupIteration) String() string { + return Stringify(i) +} + +// ListGroupIterationsOptions contains the available +// ListGroupIterations() options +// +// GitLab API docs: https://docs.gitlab.com/ee/api/group_iterations.html#list-group-iterations + +type ListGroupIterationsOptions struct { + ListOptions + State *string `url:"state,omitempty" json:"state,omitempty"` + Search *string `url:"search,omitempty" json:"search,omitempty"` + IncludeAncestors *bool `url:"include_ancestors,omitempty" json:"include_ancestors,omitempty"` +} + +// ListGroupIterations returns alist of group iterations. +// +// GitLab API docs: https://docs.gitlab.com/ee/api/group_iterations.html#list-group-iterations + +func (s *GroupIterationsService) ListGroupIterations(gid interface{}, opt *ListGroupIterationsOptions, options ...RequestOptionFunc) ([]*GroupIteration, *Response, error) { + group, err := parseID(gid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("groups/%s/iterations", PathEscape(group)) + + req, err := s.client.NewRequest(http.MethodGet, u, opt, options) + if err != nil { + return nil, nil, err + } + + var i []*GroupIteration + resp, err := s.client.Do(req, &i) + if err != nil { + return nil, nil, err + } + + return i, resp, err + +} diff --git a/iterations.go b/iterations.go new file mode 100644 index 000000000..f2d053ac2 --- /dev/null +++ b/iterations.go @@ -0,0 +1,86 @@ +// +// Copyright 2022, stonebyte +// +// 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" + "time" +) + +// IterationsAPI handles communication with the iterations related methods +// of the GitLab API +// +// GitLab API docs: https://docs.gitlab.com/ee/api/iterations.html + +type IterationsService struct { + client *Client +} + +type Iteration struct { + ID int `json:"id"` + IID int `json:"iid"` + GroupID int `json:"group_id"` + Title string `json:"title"` + Description string `json:"description"` + State int `json:"state"` + CreatedAt *time.Time `json:"created_at"` + UpdatedAt *time.Time `json:"updated_at"` + DueDate *ISOTime `json:"due_date"` + StartDate *ISOTime `json:"start_date"` + WebURL string `json:"web_url"` +} + +func (i Iteration) String() string { + return Stringify(i) +} + +// ListGroupIterationsOptions contains the available +// ListGroupIterations() options +// +// GitLab API docs: https://docs.gitlab.com/ee/api/group_iterations.html#list-project-iterations +type ListProjectIterationsOptions struct { + ListOptions + State *string `url:"state,omitempty" json:"state,omitempty"` + Search *string `url:"search,omitempty" json:"search,omitempty"` + IncludeAncestors *bool `url:"include_ancestors,omitempty" json:"include_ancestors,omitempty"` +} + +//ListProjectIterations lists all iterations of the projects ancestor groups. +//As of GitLab 13.5, there are not direct project-level iterations. + +// GitLab API docs: https://docs.gitlab.com/ee/api/iterations.html +func (i *IterationsService) ListProjectIterations(pid interface{}, opt *ListGroupProjectsOptions, options ...RequestOptionFunc) ([]*Iteration, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/iterations", PathEscape(project)) + + req, err := i.client.NewRequest(http.MethodGet, u, opt, options) + if err != nil { + return nil, nil, err + } + + var it []*Iteration + resp, err := i.client.Do(req, &it) + if err != nil { + return nil, resp, err + } + + return it, resp, err +} From 2a8e7c02e2148e92210a1a140dc6322f7694815a Mon Sep 17 00:00:00 2001 From: Daniel Steinke Date: Sat, 29 Jan 2022 17:19:47 +0100 Subject: [PATCH 2/4] Add testcases for group and project iterations Added missing registration of services. --- gitlab.go | 4 +++ group_iterations.go | 1 + group_iterations_test.go | 48 ++++++++++++++++++++++++++ iterations.go => project_iterations.go | 11 ++++-- project_iterations_test.go | 48 ++++++++++++++++++++++++++ testdata/list_group_iterations.json | 16 +++++++++ 6 files changed, 125 insertions(+), 3 deletions(-) create mode 100644 group_iterations_test.go rename iterations.go => project_iterations.go (82%) create mode 100644 project_iterations_test.go create mode 100644 testdata/list_group_iterations.json diff --git a/gitlab.go b/gitlab.go index 7c6277712..ba7f86aff 100644 --- a/gitlab.go +++ b/gitlab.go @@ -131,6 +131,7 @@ type Client struct { GroupCluster *GroupClustersService GroupImportExport *GroupImportExportService GroupIssueBoards *GroupIssueBoardsService + GroupIterations *GroupIterationsService GroupLabels *GroupLabelsService GroupMembers *GroupMembersService GroupMilestones *GroupMilestonesService @@ -167,6 +168,7 @@ type Client struct { ProjectAccessTokens *ProjectAccessTokensService ProjectCluster *ProjectClustersService ProjectImportExport *ProjectImportExportService + ProjectIterations *ProjectIterationsService ProjectMembers *ProjectMembersService ProjectMirrors *ProjectMirrorService ProjectSnippets *ProjectSnippetsService @@ -325,6 +327,7 @@ func newClient(options ...ClientOptionFunc) (*Client, error) { c.GroupCluster = &GroupClustersService{client: c} c.GroupImportExport = &GroupImportExportService{client: c} c.GroupIssueBoards = &GroupIssueBoardsService{client: c} + c.GroupIterations = &GroupIterationsService{client: c} c.GroupLabels = &GroupLabelsService{client: c} c.GroupMembers = &GroupMembersService{client: c} c.GroupMilestones = &GroupMilestonesService{client: c} @@ -361,6 +364,7 @@ func newClient(options ...ClientOptionFunc) (*Client, error) { c.ProjectAccessTokens = &ProjectAccessTokensService{client: c} c.ProjectCluster = &ProjectClustersService{client: c} c.ProjectImportExport = &ProjectImportExportService{client: c} + c.ProjectIterations = &ProjectIterationsService{client: c} c.ProjectMembers = &ProjectMembersService{client: c} c.ProjectMirrors = &ProjectMirrorService{client: c} c.ProjectSnippets = &ProjectSnippetsService{client: c} diff --git a/group_iterations.go b/group_iterations.go index 967d2987e..1f9c78b75 100644 --- a/group_iterations.go +++ b/group_iterations.go @@ -38,6 +38,7 @@ type GroupIterationsService struct { type GroupIteration struct { ID int `json:"id"` IID int `json:"iid"` + Sequence int `json:"sequence"` GroupID int `json:"group_id"` Title string `json:"title"` Description string `json:"description"` diff --git a/group_iterations_test.go b/group_iterations_test.go new file mode 100644 index 000000000..860238d66 --- /dev/null +++ b/group_iterations_test.go @@ -0,0 +1,48 @@ +package gitlab + +import ( + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestListGroupIterations(t *testing.T) { + mux, server, client := setup(t) + defer teardown(server) + + mux.HandleFunc("/api/v4/groups/5/iterations", + func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodGet) + fmt.Fprintf(w, `[{ + "id": 53, + "iid": 13, + "sequence": 1, + "group_id": 5, + "title": "Iteration II", + "description": "Ipsum Lorem ipsum", + "state": 2, + "web_url": "http://gitlab.example.com/groups/my-group/-/iterations/13" + } + ]`) + }) + + iterations, _, err := client.GroupIterations.ListGroupIterations(5, &ListGroupIterationsOptions{}) + if err != nil { + t.Errorf("GroupIterations.ListGroupIterations returned error: %v", err) + } + + want := []*GroupIteration{{ + ID: 53, + IID: 13, + Sequence: 1, + GroupID: 5, + Title: "Iteration II", + Description: "Ipsum Lorem ipsum", + State: 2, + WebURL: "http://gitlab.example.com/groups/my-group/-/iterations/13", + }} + if !reflect.DeepEqual(want, iterations) { + t.Errorf("GroupIterations.ListGroupIterations returned %+v, want %+v", iterations, want) + } +} diff --git a/iterations.go b/project_iterations.go similarity index 82% rename from iterations.go rename to project_iterations.go index f2d053ac2..8cd9cae94 100644 --- a/iterations.go +++ b/project_iterations.go @@ -27,13 +27,18 @@ import ( // // GitLab API docs: https://docs.gitlab.com/ee/api/iterations.html -type IterationsService struct { +type ProjectIterationsService struct { client *Client } +// Iteration represents a GitLab iteration +// +// GitLab API docs: https://docs.gitlab.com/ee/api/iterations.html +// CAVEAT: GitLab docu currently misses `sequence` key. type Iteration struct { ID int `json:"id"` IID int `json:"iid"` + Sequence int `json:"sequence"` GroupID int `json:"group_id"` Title string `json:"title"` Description string `json:"description"` @@ -61,10 +66,10 @@ type ListProjectIterationsOptions struct { } //ListProjectIterations lists all iterations of the projects ancestor groups. -//As of GitLab 13.5, there are not direct project-level iterations. +//As of GitLab 13.5, there are no direct project-level iterations. // GitLab API docs: https://docs.gitlab.com/ee/api/iterations.html -func (i *IterationsService) ListProjectIterations(pid interface{}, opt *ListGroupProjectsOptions, options ...RequestOptionFunc) ([]*Iteration, *Response, error) { +func (i *ProjectIterationsService) ListProjectIterations(pid interface{}, opt *ListProjectIterationsOptions, options ...RequestOptionFunc) ([]*Iteration, *Response, error) { project, err := parseID(pid) if err != nil { return nil, nil, err diff --git a/project_iterations_test.go b/project_iterations_test.go new file mode 100644 index 000000000..59bb2d94d --- /dev/null +++ b/project_iterations_test.go @@ -0,0 +1,48 @@ +package gitlab + +import ( + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestListProjectIterations(t *testing.T) { + mux, server, client := setup(t) + defer teardown(server) + + mux.HandleFunc("/api/v4/projects/42/iterations", + func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodGet) + fmt.Fprintf(w, `[{ + "id": 53, + "iid": 13, + "sequence": 1, + "group_id": 5, + "title": "Iteration II", + "description": "Ipsum Lorem ipsum", + "state": 2, + "web_url": "http://gitlab.example.com/groups/my-group/-/iterations/13" + } + ]`) + }) + + iterations, _, err := client.ProjectIterations.ListProjectIterations(42, &ListProjectIterationsOptions{}) + if err != nil { + t.Errorf("GroupIterations.ListGroupIterations returned error: %v", err) + } + + want := []*Iteration{{ + ID: 53, + IID: 13, + Sequence: 1, + GroupID: 5, + Title: "Iteration II", + Description: "Ipsum Lorem ipsum", + State: 2, + WebURL: "http://gitlab.example.com/groups/my-group/-/iterations/13", + }} + if !reflect.DeepEqual(want, iterations) { + t.Errorf("ProjectIterations.ListProjectIterations returned %+v, want %+v", iterations, want) + } +} diff --git a/testdata/list_group_iterations.json b/testdata/list_group_iterations.json new file mode 100644 index 000000000..7fd39f682 --- /dev/null +++ b/testdata/list_group_iterations.json @@ -0,0 +1,16 @@ +[ + { + "id": 53, + "iid": 13, + "sequence": 1, + "group_id": 5, + "title": "Iteration II", + "description": "Ipsum Lorem ipsum", + "state": 2, + "created_at": "2020-01-27T05:07:12.573Z", + "updated_at": "2020-01-27T05:07:12.573Z", + "due_date": "2020-02-01", + "start_date": "2020-02-14", + "web_url": "http://gitlab.example.com/groups/my-group/-/iterations/13" + } +] \ No newline at end of file From 0f81468b0fd2e087ff1ad7f258e4c9ab19c21f50 Mon Sep 17 00:00:00 2001 From: Daniel Steinke Date: Sat, 29 Jan 2022 17:22:19 +0100 Subject: [PATCH 3/4] Fixed copyright note --- group_iterations.go | 2 +- project_iterations.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/group_iterations.go b/group_iterations.go index 1f9c78b75..bbc203f10 100644 --- a/group_iterations.go +++ b/group_iterations.go @@ -1,5 +1,5 @@ // -// Copyright 2022, stonebyte +// Copyright 2022, Daniel Steinke // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/project_iterations.go b/project_iterations.go index 8cd9cae94..f8e6bab0e 100644 --- a/project_iterations.go +++ b/project_iterations.go @@ -1,5 +1,5 @@ // -// Copyright 2022, stonebyte +// Copyright 2022, Daniel Steinke // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. From b65c13cb7eb246bc2ef6998736d8fd0320628b22 Mon Sep 17 00:00:00 2001 From: Sander van Harmelen Date: Thu, 10 Feb 2022 12:42:11 +0100 Subject: [PATCH 4/4] Cleanup the PR --- group_iterations.go | 23 +++++++++---------- group_iterations_test.go | 23 ++++++++++--------- project_iterations.go | 35 ++++++++++++++--------------- project_iterations_test.go | 25 +++++++++++---------- testdata/list_group_iterations.json | 2 +- 5 files changed, 53 insertions(+), 55 deletions(-) diff --git a/group_iterations.go b/group_iterations.go index bbc203f10..a642091c2 100644 --- a/group_iterations.go +++ b/group_iterations.go @@ -26,7 +26,6 @@ import ( // of the GitLab API // // GitLab API docs: https://docs.gitlab.com/ee/api/group_iterations.html - type GroupIterationsService struct { client *Client } @@ -34,7 +33,6 @@ type GroupIterationsService struct { // GroupInteration represents a GitLab iteration. // // GitLab API docs: https://docs.gitlab.com/ee/api/group_iterations.html - type GroupIteration struct { ID int `json:"id"` IID int `json:"iid"` @@ -54,11 +52,11 @@ func (i GroupIteration) String() string { return Stringify(i) } -// ListGroupIterationsOptions contains the available -// ListGroupIterations() options +// ListGroupIterationsOptions contains the available ListGroupIterations() +// options // -// GitLab API docs: https://docs.gitlab.com/ee/api/group_iterations.html#list-group-iterations - +// GitLab API docs: +// https://docs.gitlab.com/ee/api/group_iterations.html#list-group-iterations type ListGroupIterationsOptions struct { ListOptions State *string `url:"state,omitempty" json:"state,omitempty"` @@ -66,10 +64,10 @@ type ListGroupIterationsOptions struct { IncludeAncestors *bool `url:"include_ancestors,omitempty" json:"include_ancestors,omitempty"` } -// ListGroupIterations returns alist of group iterations. +// ListGroupIterations returns a list of group iterations. // -// GitLab API docs: https://docs.gitlab.com/ee/api/group_iterations.html#list-group-iterations - +// GitLab API docs: +// https://docs.gitlab.com/ee/api/group_iterations.html#list-group-iterations func (s *GroupIterationsService) ListGroupIterations(gid interface{}, opt *ListGroupIterationsOptions, options ...RequestOptionFunc) ([]*GroupIteration, *Response, error) { group, err := parseID(gid) if err != nil { @@ -82,12 +80,11 @@ func (s *GroupIterationsService) ListGroupIterations(gid interface{}, opt *ListG return nil, nil, err } - var i []*GroupIteration - resp, err := s.client.Do(req, &i) + var gis []*GroupIteration + resp, err := s.client.Do(req, &gis) if err != nil { return nil, nil, err } - return i, resp, err - + return gis, resp, err } diff --git a/group_iterations_test.go b/group_iterations_test.go index 860238d66..4d2030faf 100644 --- a/group_iterations_test.go +++ b/group_iterations_test.go @@ -14,17 +14,18 @@ func TestListGroupIterations(t *testing.T) { mux.HandleFunc("/api/v4/groups/5/iterations", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodGet) - fmt.Fprintf(w, `[{ - "id": 53, - "iid": 13, - "sequence": 1, - "group_id": 5, - "title": "Iteration II", - "description": "Ipsum Lorem ipsum", - "state": 2, - "web_url": "http://gitlab.example.com/groups/my-group/-/iterations/13" - } - ]`) + fmt.Fprintf(w, `[ + { + "id": 53, + "iid": 13, + "sequence": 1, + "group_id": 5, + "title": "Iteration II", + "description": "Ipsum Lorem ipsum", + "state": 2, + "web_url": "http://gitlab.example.com/groups/my-group/-/iterations/13" + } + ]`) }) iterations, _, err := client.GroupIterations.ListGroupIterations(5, &ListGroupIterationsOptions{}) diff --git a/project_iterations.go b/project_iterations.go index f8e6bab0e..78583efab 100644 --- a/project_iterations.go +++ b/project_iterations.go @@ -22,20 +22,18 @@ import ( "time" ) -// IterationsAPI handles communication with the iterations related methods -// of the GitLab API +// IterationsAPI handles communication with the project iterations related +// methods of the GitLab API // // GitLab API docs: https://docs.gitlab.com/ee/api/iterations.html - type ProjectIterationsService struct { client *Client } -// Iteration represents a GitLab iteration +// ProjectIteration represents a GitLab project iteration. // // GitLab API docs: https://docs.gitlab.com/ee/api/iterations.html -// CAVEAT: GitLab docu currently misses `sequence` key. -type Iteration struct { +type ProjectIteration struct { ID int `json:"id"` IID int `json:"iid"` Sequence int `json:"sequence"` @@ -50,14 +48,15 @@ type Iteration struct { WebURL string `json:"web_url"` } -func (i Iteration) String() string { +func (i ProjectIteration) String() string { return Stringify(i) } -// ListGroupIterationsOptions contains the available -// ListGroupIterations() options +// ListProjectIterationsOptions contains the available ListProjectIterations() +// options // -// GitLab API docs: https://docs.gitlab.com/ee/api/group_iterations.html#list-project-iterations +// GitLab API docs: +// https://docs.gitlab.com/ee/api/group_iterations.html#list-project-iterations type ListProjectIterationsOptions struct { ListOptions State *string `url:"state,omitempty" json:"state,omitempty"` @@ -65,11 +64,11 @@ type ListProjectIterationsOptions struct { IncludeAncestors *bool `url:"include_ancestors,omitempty" json:"include_ancestors,omitempty"` } -//ListProjectIterations lists all iterations of the projects ancestor groups. -//As of GitLab 13.5, there are no direct project-level iterations. - -// GitLab API docs: https://docs.gitlab.com/ee/api/iterations.html -func (i *ProjectIterationsService) ListProjectIterations(pid interface{}, opt *ListProjectIterationsOptions, options ...RequestOptionFunc) ([]*Iteration, *Response, error) { +// ListProjectIterations returns a list of projects iterations. +// +// GitLab API docs: +// https://docs.gitlab.com/ee/api/group_iterations.html#list-project-iterations +func (i *ProjectIterationsService) ListProjectIterations(pid interface{}, opt *ListProjectIterationsOptions, options ...RequestOptionFunc) ([]*ProjectIteration, *Response, error) { project, err := parseID(pid) if err != nil { return nil, nil, err @@ -81,11 +80,11 @@ func (i *ProjectIterationsService) ListProjectIterations(pid interface{}, opt *L return nil, nil, err } - var it []*Iteration - resp, err := i.client.Do(req, &it) + var pis []*ProjectIteration + resp, err := i.client.Do(req, &pis) if err != nil { return nil, resp, err } - return it, resp, err + return pis, resp, err } diff --git a/project_iterations_test.go b/project_iterations_test.go index 59bb2d94d..ca304c66c 100644 --- a/project_iterations_test.go +++ b/project_iterations_test.go @@ -14,17 +14,18 @@ func TestListProjectIterations(t *testing.T) { mux.HandleFunc("/api/v4/projects/42/iterations", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodGet) - fmt.Fprintf(w, `[{ - "id": 53, - "iid": 13, - "sequence": 1, - "group_id": 5, - "title": "Iteration II", - "description": "Ipsum Lorem ipsum", - "state": 2, - "web_url": "http://gitlab.example.com/groups/my-group/-/iterations/13" - } - ]`) + fmt.Fprintf(w, `[ + { + "id": 53, + "iid": 13, + "sequence": 1, + "group_id": 5, + "title": "Iteration II", + "description": "Ipsum Lorem ipsum", + "state": 2, + "web_url": "http://gitlab.example.com/groups/my-group/-/iterations/13" + } + ]`) }) iterations, _, err := client.ProjectIterations.ListProjectIterations(42, &ListProjectIterationsOptions{}) @@ -32,7 +33,7 @@ func TestListProjectIterations(t *testing.T) { t.Errorf("GroupIterations.ListGroupIterations returned error: %v", err) } - want := []*Iteration{{ + want := []*ProjectIteration{{ ID: 53, IID: 13, Sequence: 1, diff --git a/testdata/list_group_iterations.json b/testdata/list_group_iterations.json index 7fd39f682..9d032c083 100644 --- a/testdata/list_group_iterations.json +++ b/testdata/list_group_iterations.json @@ -13,4 +13,4 @@ "start_date": "2020-02-14", "web_url": "http://gitlab.example.com/groups/my-group/-/iterations/13" } -] \ No newline at end of file +]