Repository: xanzy/go-gitlab
Pull request #612 (2019-04-20 19:06:44)

Add EpicsService
Epics were introduced in GitLab Ultimate 10.2

GitLab docs: https://docs.gitlab.com/ee/api/epics.html


Patch:
diff --git a/epics.go b/epics.go
new file mode 100644
index 0000000..c72538c
--- /dev/null
+++ b/epics.go
@@ -0,0 +1,212 @@
+package gitlab
+
+import (
+	"fmt"
+	"net/url"
+	"time"
+)
+
+// EpicsService handles communication with the epic related methods
+// of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/epics.html
+type EpicsService struct {
+	client *Client
+}
+
+// EpicAuthor represents a author of the epic.
+type EpicAuthor struct {
+	ID        int    `json:"id"`
+	State     string `json:"state"`
+	WebURL    string `json:"web_url"`
+	Name      string `json:"name"`
+	AvatarURL string `json:"avatar_url"`
+	Username  string `json:"username"`
+}
+
+// Epic represents a GitLab epic.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/epics.html
+type Epic struct {
+	ID                      int         `json:"id"`
+	IID                     int         `json:"iid"`
+	GroupID                 int         `json:"group_id"`
+	Author                  *EpicAuthor `json:"author"`
+	Description             string      `json:"description"`
+	State                   string      `json:"state"`
+	Upvotes                 int         `json:"upvotes"`
+	Downvotes               int         `json:"downvotes"`
+	Labels                  []string    `json:"labels"`
+	Title                   string      `json:"title"`
+	UpdatedAt               *time.Time  `json:"updated_at"`
+	CreatedAt               *time.Time  `json:"created_at"`
+	UserNotesCount          int         `json:"user_notes_count"`
+	StartDate               *ISOTime    `json:"start_date"`
+	StartDateIsFixed        bool        `json:"start_date_is_fixed"`
+	StartDateFixed          *ISOTime    `json:"start_date_fixed"`
+	StartDateFromMilestones *ISOTime    `json:"start_date_from_milestones"`
+	DueDate                 *ISOTime    `json:"due_date"`
+	DueDateIsFixed          bool        `json:"due_date_is_fixed"`
+	DueDateFixed            *ISOTime    `json:"due_date_fixed"`
+	DueDateFromMilestones   *ISOTime    `json:"due_date_from_milestones"`
+}
+
+func (e Epic) String() string {
+	return Stringify(e)
+}
+
+// ListGroupEpicsOptions represents the available ListGroupEpics() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/epics.html#list-epics-for-a-group
+type ListGroupEpicsOptions struct {
+	ListOptions
+	State    *string `url:"state,omitempty" json:"state,omitempty"`
+	Labels   Labels  `url:"labels,comma,omitempty" json:"labels,omitempty"`
+	AuthorID *int    `url:"author_id,omitempty" json:"author_id,omitempty"`
+	OrderBy  *string `url:"order_by,omitempty" json:"order_by,omitempty"`
+	Sort     *string `url:"sort,omitempty" json:"sort,omitempty"`
+	Search   *string `url:"search,omitempty" json:"search,omitempty"`
+}
+
+// ListGroupEpics gets a list of group epics. This function accepts
+// pagination parameters page and per_page to return the list of group epics.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/epics.html#list-epics-for-a-group
+func (s *EpicsService) ListGroupEpics(gid interface{}, opt *ListGroupEpicsOptions, options ...OptionFunc) ([]*Epic, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/epics", url.QueryEscape(group))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var i []*Epic
+	resp, err := s.client.Do(req, &i)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return i, resp, err
+}
+
+// GetEpic gets a single group epic.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/epics.html#single-epic
+func (s *EpicsService) GetEpic(gid interface{}, epic int, options ...OptionFunc) (*Epic, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/epics/%d", url.QueryEscape(group), epic)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	i := new(Epic)
+	resp, err := s.client.Do(req, i)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return i, resp, err
+}
+
+// CreateEpicOptions represents the available CreateEpic() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/epics.html#new-epic
+type CreateEpicOptions struct {
+	Title            *string  `url:"title,omitempty" json:"title,omitempty"`
+	Description      *string  `url:"description,omitempty" json:"description,omitempty"`
+	Labels           Labels   `url:"labels,comma,omitempty" json:"labels,omitempty"`
+	StartDateIsFixed bool     `json:"start_date_is_fixed"`
+	StartDateFixed   *ISOTime `json:"start_date_fixed"`
+	DueDateIsFixed   bool     `json:"due_date_is_fixed"`
+	DueDateFixed     *ISOTime `json:"due_date_fixed"`
+}
+
+// CreateEpic creates a new group epic.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/epics.html#new-epic
+func (s *EpicsService) CreateEpic(gid interface{}, opt *CreateEpicOptions, options ...OptionFunc) (*Epic, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/epics", url.QueryEscape(group))
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	i := new(Epic)
+	resp, err := s.client.Do(req, i)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return i, resp, err
+}
+
+// UpdateEpicOptions represents the available UpdateEpic() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/epics.html#update-epic
+type UpdateEpicOptions struct {
+	Title            *string  `url:"title,omitempty" json:"title,omitempty"`
+	Description      *string  `url:"description,omitempty" json:"description,omitempty"`
+	Labels           Labels   `url:"labels,comma,omitempty" json:"labels,omitempty"`
+	StartDateIsFixed bool     `json:"start_date_is_fixed"`
+	StartDateFixed   *ISOTime `json:"start_date_fixed"`
+	DueDateIsFixed   bool     `json:"due_date_is_fixed"`
+	DueDateFixed     *ISOTime `json:"due_date_fixed"`
+	StateEvent       *string  `url:"state_event,omitempty" json:"state_event,omitempty"`
+}
+
+// UpdateEpic updates an existing group epic. This function is also used
+// to mark an epic as closed.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/epics.html#update-epic
+func (s *EpicsService) UpdateEpic(gid interface{}, epic int, opt *UpdateEpicOptions, options ...OptionFunc) (*Epic, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/epics/%d", url.QueryEscape(group), epic)
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	i := new(Epic)
+	resp, err := s.client.Do(req, i)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return i, resp, err
+}
+
+// DeleteEpic deletes a single group epic.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/epics.html#delete-epic
+func (s *EpicsService) DeleteEpic(gid interface{}, epic int, options ...OptionFunc) (*Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("groups/%s/epics/%d", url.QueryEscape(group), epic)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
diff --git a/gitlab.go b/gitlab.go
index ec9cdcf..13953d8 100644
--- a/gitlab.go
+++ b/gitlab.go
@@ -300,6 +300,7 @@ type Client struct {
 	Deployments           *DeploymentsService
 	Discussions           *DiscussionsService
 	Environments          *EnvironmentsService
+	Epics                 *EpicsService
 	Events                *EventsService
 	Features              *FeaturesService
 	GitIgnoreTemplates    *GitIgnoreTemplatesService
@@ -447,6 +448,7 @@ func newClient(httpClient *http.Client) *Client {
 	c.Deployments = &DeploymentsService{client: c}
 	c.Discussions = &DiscussionsService{client: c}
 	c.Environments = &EnvironmentsService{client: c}
+	c.Epics = &EpicsService{client: c}
 	c.Events = &EventsService{client: c}
 	c.Features = &FeaturesService{client: c}
 	c.GitIgnoreTemplates = &GitIgnoreTemplatesService{client: c}


Test patch:
diff --git a/epics_test.go b/epics_test.go
new file mode 100644
index 0000000..8df8cbc
--- /dev/null
+++ b/epics_test.go
@@ -0,0 +1,146 @@
+package gitlab
+
+import (
+	"fmt"
+	"log"
+	"net/http"
+	"reflect"
+	"testing"
+)
+
+func TestGetEpic(t *testing.T) {
+	mux, server, client := setup()
+	defer teardown(server)
+
+	mux.HandleFunc("/api/v4/groups/7/epics/8", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		fmt.Fprint(w, `{"id":8, "title": "Incredible idea", "description": "This is a test epic", "author" : {"id" : 26, "name": "jramsay"}}`)
+	})
+
+	epic, _, err := client.Epics.GetEpic("7", 8)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	want := &Epic{
+		ID:          8,
+		Title:       "Incredible idea",
+		Description: "This is a test epic",
+		Author:      &EpicAuthor{ID: 26, Name: "jramsay"},
+	}
+
+	if !reflect.DeepEqual(want, epic) {
+		t.Errorf("Epics.GetEpic returned %+v, want %+v", epic, want)
+	}
+}
+
+func TestDeleteEpic(t *testing.T) {
+	mux, server, client := setup()
+	defer teardown(server)
+
+	mux.HandleFunc("/api/v4/groups/7/epics/8", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "DELETE")
+	})
+
+	_, err := client.Epics.DeleteEpic("7", 8)
+	if err != nil {
+		log.Fatal(err)
+	}
+}
+
+func TestListGroupEpics(t *testing.T) {
+	mux, server, client := setup()
+	defer teardown(server)
+
+	mux.HandleFunc("/api/v4/groups/7/epics", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		testURL(t, r, "/api/v4/groups/7/epics?author_id=26&state=opened")
+		fmt.Fprint(w, `[{"id":8, "title": "Incredible idea", "description": "This is a test epic", "author" : {"id" : 26, "name": "jramsay"}}]`)
+	})
+
+	listGroupEpics := &ListGroupEpicsOptions{
+		AuthorID: Int(26),
+		State:    String("opened"),
+	}
+
+	epics, _, err := client.Epics.ListGroupEpics("7", listGroupEpics)
+
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	want := []*Epic{{
+		ID:          8,
+		Title:       "Incredible idea",
+		Description: "This is a test epic",
+		Author:      &EpicAuthor{ID: 26, Name: "jramsay"},
+	}}
+
+	if !reflect.DeepEqual(want, epics) {
+		t.Errorf("Epics.ListGroupEpics returned %+v, want %+v", epics, want)
+	}
+}
+
+func TestCreateEpic(t *testing.T) {
+	mux, server, client := setup()
+	defer teardown(server)
+
+	mux.HandleFunc("/api/v4/groups/7/epics", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "POST")
+		fmt.Fprint(w, `{"id":8, "title": "Incredible idea", "description": "This is a test epic", "author" : {"id" : 26, "name": "jramsay"}}`)
+	})
+
+	createEpicOptions := &CreateEpicOptions{
+		Title:       String("Incredible idea"),
+		Description: String("This is a test epic"),
+	}
+
+	epic, _, err := client.Epics.CreateEpic("7", createEpicOptions)
+
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	want := &Epic{
+		ID:          8,
+		Title:       "Incredible idea",
+		Description: "This is a test epic",
+		Author:      &EpicAuthor{ID: 26, Name: "jramsay"},
+	}
+
+	if !reflect.DeepEqual(want, epic) {
+		t.Errorf("Epics.CreateEpic returned %+v, want %+v", epic, want)
+	}
+}
+
+func TestUpdateEpic(t *testing.T) {
+	mux, server, client := setup()
+	defer teardown(server)
+
+	mux.HandleFunc("/api/v4/groups/7/epics/8", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "PUT")
+		fmt.Fprint(w, `{"id":8, "title": "Incredible idea", "description": "This is a test epic", "author" : {"id" : 26, "name": "jramsay"}}`)
+	})
+
+	updateEpicOptions := &UpdateEpicOptions{
+		Title:       String("Incredible idea"),
+		Description: String("This is a test epic"),
+	}
+
+	epic, _, err := client.Epics.UpdateEpic("7", 8, updateEpicOptions)
+
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	want := &Epic{
+		ID:          8,
+		Title:       "Incredible idea",
+		Description: "This is a test epic",
+		Author:      &EpicAuthor{ID: 26, Name: "jramsay"},
+	}
+
+	if !reflect.DeepEqual(want, epic) {
+		t.Errorf("Epics.UpdateEpic returned %+v, want %+v", epic, want)
+	}
+}