Repository: xanzy/go-gitlab
Pull request #1750 (2023-07-05 11:27:08)

Add the field GroupID to the Milestone struct
See issue #1749 

I'm going to test my project with my branch to see if this correction works as expected.


Patch:
diff --git a/milestones.go b/milestones.go
index 095c913..17c97e0 100644
--- a/milestones.go
+++ b/milestones.go
@@ -36,6 +36,7 @@ type MilestonesService struct {
 type Milestone struct {
 	ID          int        `json:"id"`
 	IID         int        `json:"iid"`
+	GroupID     int        `json:"group_id"`
 	ProjectID   int        `json:"project_id"`
 	Title       string     `json:"title"`
 	Description string     `json:"description"`
diff --git a/services.go b/services.go
index 69842ae..e7acb9e 100644
--- a/services.go
+++ b/services.go
@@ -44,7 +44,6 @@ type Service struct {
 	Active                   bool       `json:"active"`
 	PushEvents               bool       `json:"push_events"`
 	IssuesEvents             bool       `json:"issues_events"`
-	AlertEvents              bool       `json:"alert_events"`
 	ConfidentialIssuesEvents bool       `json:"confidential_issues_events"`
 	CommitEvents             bool       `json:"commit_events"`
 	MergeRequestsEvents      bool       `json:"merge_requests_events"`
@@ -55,7 +54,6 @@ type Service struct {
 	PipelineEvents           bool       `json:"pipeline_events"`
 	JobEvents                bool       `json:"job_events"`
 	WikiPageEvents           bool       `json:"wiki_page_events"`
-	VulnerabilityEvents      bool       `json:"vulnerability_events"`
 	DeploymentEvents         bool       `json:"deployment_events"`
 }
 
@@ -193,8 +191,8 @@ type DiscordService struct {
 // GitLab API docs:
 // https://docs.gitlab.com/ee/api/services.html#discord
 type DiscordServiceProperties struct {
-	BranchesToBeNotified      string `url:"branches_to_be_notified,omitempty" json:"branches_to_be_notified,omitempty"`
 	NotifyOnlyBrokenPipelines bool   `url:"notify_only_broken_pipelines,omitempty" json:"notify_only_broken_pipelines,omitempty"`
+	BranchesToBeNotified      string `url:"branches_to_be_notified,omitempty" json:"branches_to_be_notified,omitempty"`
 }
 
 // GetDiscordService gets Discord service settings for a project.
@@ -228,18 +226,7 @@ func (s *ServicesService) GetDiscordService(pid interface{}, options ...RequestO
 // GitLab API docs:
 // https://docs.gitlab.com/ee/api/services.html#createedit-discord-service
 type SetDiscordServiceOptions struct {
-	WebHook                   *string `url:"webhook,omitempty" json:"webhook,omitempty"`
-	BranchesToBeNotified      *string `url:"branches_to_be_notified,omitempty" json:"branches_to_be_notified,omitempty"`
-	ConfidentialIssuesEvents  *bool   `url:"confidential_issues_events,omitempty" json:"confidential_issues_events,omitempty"`
-	ConfidentialNoteEvents    *bool   `url:"confidential_note_events,omitempty" json:"confidential_note_events,omitempty"`
-	IssuesEvents              *bool   `url:"issues_events,omitempty" json:"issues_events,omitempty"`
-	MergeRequestsEvents       *bool   `url:"merge_requests_events,omitempty" json:"merge_requests_events,omitempty"`
-	NoteEvents                *bool   `url:"note_events,omitempty" json:"note_events,omitempty"`
-	NotifyOnlyBrokenPipelines *bool   `url:"notify_only_broken_pipelines,omitempty" json:"notify_only_broken_pipelines,omitempty"`
-	PipelineEvents            *bool   `url:"pipeline_events,omitempty" json:"pipeline_events,omitempty"`
-	PushEvents                *bool   `url:"push_events,omitempty" json:"push_events,omitempty"`
-	TagPushEvents             *bool   `url:"tag_push_events,omitempty" json:"tag_push_events,omitempty"`
-	WikiPageEvents            *bool   `url:"wiki_page_events,omitempty" json:"wiki_page_events,omitempty"`
+	WebHook *string `json:"webhook,omitempty"`
 }
 
 // SetDiscordService sets Discord service for a project.
@@ -1006,7 +993,6 @@ type MattermostServiceProperties struct {
 	TagPushChannel            string    `json:"tag_push_channel"`
 	PipelineChannel           string    `json:"pipeline_channel"`
 	PushChannel               string    `json:"push_channel"`
-	VulnerabilityChannel      string    `json:"vulnerability_channel"`
 	WikiPageChannel           string    `json:"wiki_page_channel"`
 }
 
@@ -1046,24 +1032,24 @@ type SetMattermostServiceOptions struct {
 	Channel                   *string `url:"channel,omitempty" json:"channel,omitempty"`
 	NotifyOnlyBrokenPipelines *bool   `url:"notify_only_broken_pipelines,omitempty" json:"notify_only_broken_pipelines,omitempty"`
 	BranchesToBeNotified      *string `url:"branches_to_be_notified,omitempty" json:"branches_to_be_notified,omitempty"`
-	PushEvents                *bool   `url:"push_events,omitempty" json:"push_events,omitempty"`
-	IssuesEvents              *bool   `url:"issues_events,omitempty" json:"issues_events,omitempty"`
+	ConfidentialIssueChannel  *string `url:"confidential_issue_channel,omitempty" json:"confidential_issue_channel,omitempty"`
 	ConfidentialIssuesEvents  *bool   `url:"confidential_issues_events,omitempty" json:"confidential_issues_events,omitempty"`
+	ConfidentialNoteChannel   *string `json:"confidential_note_channel,omitempty"`
+	ConfidentialNoteEvents    *bool   `url:"confidential_note_events,omitempty" json:"confidential_note_events,omitempty"`
+	IssueChannel              *string `url:"issue_channel,omitempty" json:"issue_channel,omitempty"`
+	IssuesEvents              *bool   `url:"issues_events,omitempty" json:"issues_events,omitempty"`
+	MergeRequestChannel       *string `url:"merge_request_channel,omitempty" json:"merge_request_channel,omitempty"`
 	MergeRequestsEvents       *bool   `url:"merge_requests_events,omitempty" json:"merge_requests_events,omitempty"`
+	TagPushChannel            *string `url:"tag_push_channel,omitempty" json:"tag_push_channel,omitempty"`
 	TagPushEvents             *bool   `url:"tag_push_events,omitempty" json:"tag_push_events,omitempty"`
+	NoteChannel               *string `url:"note_channel,omitempty" json:"note_channel,omitempty"`
 	NoteEvents                *bool   `url:"note_events,omitempty" json:"note_events,omitempty"`
-	ConfidentialNoteChannel   *string `url:"confidential_note_channel,omitempty" json:"confidential_note_channel,omitempty"`
+	PipelineChannel           *string `url:"pipeline_channel,omitempty" json:"pipeline_channel,omitempty"`
 	PipelineEvents            *bool   `url:"pipeline_events,omitempty" json:"pipeline_events,omitempty"`
-	WikiPageEvents            *bool   `url:"wiki_page_events,omitempty" json:"wiki_page_events,omitempty"`
 	PushChannel               *string `url:"push_channel,omitempty" json:"push_channel,omitempty"`
-	IssueChannel              *string `url:"issue_channel,omitempty" json:"issue_channel,omitempty"`
-	ConfidentialIssueChannel  *string `url:"confidential_issue_channel,omitempty" json:"confidential_issue_channel,omitempty"`
-	MergeRequestChannel       *string `url:"merge_request_channel,omitempty" json:"merge_request_channel,omitempty"`
-	NoteChannel               *string `url:"note_channel,omitempty" json:"note_channel,omitempty"`
-	ConfidentialNoteEvents    *bool   `url:"confidential_note_events,omitempty" json:"confidential_note_events,omitempty"`
-	TagPushChannel            *string `url:"tag_push_channel,omitempty" json:"tag_push_channel,omitempty"`
-	PipelineChannel           *string `url:"pipeline_channel,omitempty" json:"pipeline_channel,omitempty"`
+	PushEvents                *bool   `url:"push_events,omitempty" json:"push_events,omitempty"`
 	WikiPageChannel           *string `url:"wiki_page_channel,omitempty" json:"wiki_page_channel,omitempty"`
+	WikiPageEvents            *bool   `url:"wiki_page_events,omitempty" json:"wiki_page_events,omitempty"`
 }
 
 // SetMattermostService sets Mattermost service for a project.
@@ -1423,7 +1409,6 @@ type SlackServiceProperties struct {
 	NotifyOnlyBrokenPipelines BoolValue `json:"notify_only_broken_pipelines"`
 	NotifyOnlyDefaultBranch   BoolValue `json:"notify_only_default_branch"`
 	BranchesToBeNotified      string    `json:"branches_to_be_notified"`
-	AlertChannel              string    `json:"alert_channel"`
 	ConfidentialIssueChannel  string    `json:"confidential_issue_channel"`
 	ConfidentialNoteChannel   string    `json:"confidential_note_channel"`
 	DeploymentChannel         string    `json:"deployment_channel"`
@@ -1433,7 +1418,6 @@ type SlackServiceProperties struct {
 	TagPushChannel            string    `json:"tag_push_channel"`
 	PipelineChannel           string    `json:"pipeline_channel"`
 	PushChannel               string    `json:"push_channel"`
-	VulnerabilityChannel      string    `json:"vulnerability_channel"`
 	WikiPageChannel           string    `json:"wiki_page_channel"`
 }
 
@@ -1474,28 +1458,29 @@ type SetSlackServiceOptions struct {
 	NotifyOnlyBrokenPipelines *bool   `url:"notify_only_broken_pipelines,omitempty" json:"notify_only_broken_pipelines,omitempty"`
 	NotifyOnlyDefaultBranch   *bool   `url:"notify_only_default_branch,omitempty" json:"notify_only_default_branch,omitempty"`
 	BranchesToBeNotified      *string `url:"branches_to_be_notified,omitempty" json:"branches_to_be_notified,omitempty"`
-	AlertChannel              *string `url:"alert_channel,omitempty" json:"alert_channel,omitempty"`
-	AlertEvents               *bool   `url:"alert_events,omitempty" json:"alert_events,omitempty"`
 	ConfidentialIssueChannel  *string `url:"confidential_issue_channel,omitempty" json:"confidential_issue_channel,omitempty"`
 	ConfidentialIssuesEvents  *bool   `url:"confidential_issues_events,omitempty" json:"confidential_issues_events,omitempty"`
-	ConfidentialNoteChannel   *string `url:"confidential_note_channel,omitempty" json:"confidential_note_channel,omitempty"`
-	ConfidentialNoteEvents    *bool   `url:"confidential_note_events,omitempty" json:"confidential_note_events,omitempty"`
-	DeploymentChannel         *string `url:"deployment_channel,omitempty" json:"deployment_channel,omitempty"`
-	DeploymentEvents          *bool   `url:"deployment_events,omitempty" json:"deployment_events,omitempty"`
-	IssueChannel              *string `url:"issue_channel,omitempty" json:"issue_channel,omitempty"`
-	IssuesEvents              *bool   `url:"issues_events,omitempty" json:"issues_events,omitempty"`
-	MergeRequestChannel       *string `url:"merge_request_channel,omitempty" json:"merge_request_channel,omitempty"`
-	MergeRequestsEvents       *bool   `url:"merge_requests_events,omitempty" json:"merge_requests_events,omitempty"`
-	NoteChannel               *string `url:"note_channel,omitempty" json:"note_channel,omitempty"`
-	NoteEvents                *bool   `url:"note_events,omitempty" json:"note_events,omitempty"`
-	PipelineChannel           *string `url:"pipeline_channel,omitempty" json:"pipeline_channel,omitempty"`
-	PipelineEvents            *bool   `url:"pipeline_events,omitempty" json:"pipeline_events,omitempty"`
-	PushChannel               *string `url:"push_channel,omitempty" json:"push_channel,omitempty"`
-	PushEvents                *bool   `url:"push_events,omitempty" json:"push_events,omitempty"`
-	TagPushChannel            *string `url:"tag_push_channel,omitempty" json:"tag_push_channel,omitempty"`
-	TagPushEvents             *bool   `url:"tag_push_events,omitempty" json:"tag_push_events,omitempty"`
-	WikiPageChannel           *string `url:"wiki_page_channel,omitempty" json:"wiki_page_channel,omitempty"`
-	WikiPageEvents            *bool   `url:"wiki_page_events,omitempty" json:"wiki_page_events,omitempty"`
+	// TODO: Currently, GitLab ignores this option (not implemented yet?), so
+	// there is no way to set it. Uncomment when this is fixed.
+	// See: https://gitlab.com/gitlab-org/gitlab-ce/issues/49730
+	// ConfidentialNoteChannel   *string `json:"confidential_note_channel,omitempty"`
+	ConfidentialNoteEvents *bool   `url:"confidential_note_events,omitempty" json:"confidential_note_events,omitempty"`
+	DeploymentChannel      *string `url:"deployment_channel,omitempty" json:"deployment_channel,omitempty"`
+	DeploymentEvents       *bool   `url:"deployment_events,omitempty" json:"deployment_events,omitempty"`
+	IssueChannel           *string `url:"issue_channel,omitempty" json:"issue_channel,omitempty"`
+	IssuesEvents           *bool   `url:"issues_events,omitempty" json:"issues_events,omitempty"`
+	MergeRequestChannel    *string `url:"merge_request_channel,omitempty" json:"merge_request_channel,omitempty"`
+	MergeRequestsEvents    *bool   `url:"merge_requests_events,omitempty" json:"merge_requests_events,omitempty"`
+	TagPushChannel         *string `url:"tag_push_channel,omitempty" json:"tag_push_channel,omitempty"`
+	TagPushEvents          *bool   `url:"tag_push_events,omitempty" json:"tag_push_events,omitempty"`
+	NoteChannel            *string `url:"note_channel,omitempty" json:"note_channel,omitempty"`
+	NoteEvents             *bool   `url:"note_events,omitempty" json:"note_events,omitempty"`
+	PipelineChannel        *string `url:"pipeline_channel,omitempty" json:"pipeline_channel,omitempty"`
+	PipelineEvents         *bool   `url:"pipeline_events,omitempty" json:"pipeline_events,omitempty"`
+	PushChannel            *string `url:"push_channel,omitempty" json:"push_channel,omitempty"`
+	PushEvents             *bool   `url:"push_events,omitempty" json:"push_events,omitempty"`
+	WikiPageChannel        *string `url:"wiki_page_channel,omitempty" json:"wiki_page_channel,omitempty"`
+	WikiPageEvents         *bool   `url:"wiki_page_events,omitempty" json:"wiki_page_events,omitempty"`
 }
 
 // SetSlackService sets Slack service for a project


Test patch:
diff --git a/group_boards_test.go b/group_boards_test.go
index 1a3aeb2..27b8a45 100644
--- a/group_boards_test.go
+++ b/group_boards_test.go
@@ -358,7 +358,7 @@ func TestGroupIssueBoardsService_UpdateIssueBoard(t *testing.T) {
 		Milestone: &Milestone{
 			ID:          44,
 			IID:         1,
-			ProjectID:   0,
+			GroupID:     5,
 			Title:       "Group Milestone",
 			Description: "Group Milestone Desc",
 			State:       "active",
@@ -580,6 +580,7 @@ func TestGroupIssueBoardsService_CreateGroupIssueBoardList(t *testing.T) {
 		Milestone: &Milestone{
 			ID:          7,
 			IID:         3,
+			GroupID:     12,
 			Title:       "Milestone with due date",
 			Description: "",
 			State:       "active",
diff --git a/issues_test.go b/issues_test.go
index c7d4cbc..1908638 100644
--- a/issues_test.go
+++ b/issues_test.go
@@ -857,3 +857,76 @@ func TestGetIssueParticipants(t *testing.T) {
 		t.Errorf("Issues.GetIssueParticipants returned %+v, want %+v", issueParticipants, want)
 	}
 }
+
+func TestGetIssueMilestone(t *testing.T) {
+	mux, client := setup(t)
+
+	mux.HandleFunc("/api/v4/projects/1/issues/5", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, http.MethodGet)
+		fmt.Fprint(w, `{"id":1, "description": "This is test project", "author" : {"id" : 1, "name": "snehal"}, "assignees":[{"id":1}],"merge_requests_count": 1,
+			"milestone": {"due_date": null, "project_id": 1, "state": "closed", "description": "test", "iid": 3, "id": 11, "title": "v3.0"}}`)
+	})
+
+	issue, _, err := client.Issues.GetIssue("1", 5)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	want := &Issue{
+		ID:                1,
+		Description:       "This is test project",
+		Author:            &IssueAuthor{ID: 1, Name: "snehal"},
+		Assignees:         []*IssueAssignee{{ID: 1}},
+		MergeRequestCount: 1,
+		Milestone: &Milestone{
+			DueDate:     nil,
+			ProjectID:   1,
+			State:       "closed",
+			Description: "test",
+			IID:         3,
+			ID:          11,
+			Title:       "v3.0",
+		},
+	}
+
+	if !reflect.DeepEqual(want, issue) {
+		t.Errorf("Issues.GetIssue returned %+v, want %+v", issue, want)
+	}
+}
+
+// See issue #1749 - Issue with a group milestone
+func TestGetIssueGroupMilestone(t *testing.T) {
+	mux, client := setup(t)
+
+	mux.HandleFunc("/api/v4/projects/1/issues/5", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, http.MethodGet)
+		fmt.Fprint(w, `{"id":1, "description": "This is test project", "author" : {"id" : 1, "name": "snehal"}, "assignees":[{"id":1}],"merge_requests_count": 1,
+			"milestone": {"due_date": null, "group_id": 13, "state": "closed", "description": "test", "iid": 3, "id": 11, "title": "v3.0"}}`)
+	})
+
+	issue, _, err := client.Issues.GetIssue("1", 5)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	want := &Issue{
+		ID:                1,
+		Description:       "This is test project",
+		Author:            &IssueAuthor{ID: 1, Name: "snehal"},
+		Assignees:         []*IssueAssignee{{ID: 1}},
+		MergeRequestCount: 1,
+		Milestone: &Milestone{
+			DueDate:     nil,
+			GroupID:     13,
+			State:       "closed",
+			Description: "test",
+			IID:         3,
+			ID:          11,
+			Title:       "v3.0",
+		},
+	}
+
+	if !reflect.DeepEqual(want, issue) {
+		t.Errorf("Issues.GetIssue returned %+v, want %+v", issue, want)
+	}
+}