Repository: xanzy/go-gitlab
Pull request #2021 (2024-10-01 15:26:40)

Support API endpoint: List memberships for a billable member of a group
Implemented support for the following API endpoint:
https://docs.gitlab.com/ee/api/members.html#list-memberships-for-a-billable-member-of-a-group



Patch:
diff --git a/group_members.go b/group_members.go
index cdf225c..c18b180 100644
--- a/group_members.go
+++ b/group_members.go
@@ -30,17 +30,6 @@ type GroupMembersService struct {
 	client *Client
 }
 
-// GroupMemberSAMLIdentity represents the SAML Identity link for the group member.
-//
-// GitLab API docs: https://docs.gitlab.com/ee/api/members.html#list-all-members-of-a-group-or-project
-// Gitlab MR for API change: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/20357
-// Gitlab MR for API Doc change: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/25652
-type GroupMemberSAMLIdentity struct {
-	ExternUID      string `json:"extern_uid"`
-	Provider       string `json:"provider"`
-	SAMLProviderID int    `json:"saml_provider_id"`
-}
-
 // GroupMember represents a GitLab group member.
 //
 // GitLab API docs: https://docs.gitlab.com/ee/api/members.html
@@ -59,6 +48,50 @@ type GroupMember struct {
 	MemberRole        *MemberRole              `json:"member_role"`
 }
 
+// GroupMemberSAMLIdentity represents the SAML Identity link for the group member.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/members.html#list-all-members-of-a-group-or-project
+type GroupMemberSAMLIdentity struct {
+	ExternUID      string `json:"extern_uid"`
+	Provider       string `json:"provider"`
+	SAMLProviderID int    `json:"saml_provider_id"`
+}
+
+// BillableGroupMember represents a GitLab billable group member.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/members.html#list-all-billable-members-of-a-group
+type BillableGroupMember struct {
+	ID             int        `json:"id"`
+	Username       string     `json:"username"`
+	Name           string     `json:"name"`
+	State          string     `json:"state"`
+	AvatarURL      string     `json:"avatar_url"`
+	WebURL         string     `json:"web_url"`
+	Email          string     `json:"email"`
+	LastActivityOn *ISOTime   `json:"last_activity_on"`
+	MembershipType string     `json:"membership_type"`
+	Removable      bool       `json:"removable"`
+	CreatedAt      *time.Time `json:"created_at"`
+	IsLastOwner    bool       `json:"is_last_owner"`
+	LastLoginAt    *time.Time `json:"last_login_at"`
+}
+
+// BillableUserMembership represents a Membership of a billable user of a group
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/members.html#list-memberships-for-a-billable-member-of-a-group
+type BillableUserMembership struct {
+	ID               int                 `json:"id"`
+	SourceID         int                 `json:"source_id"`
+	SourceFullName   string              `json:"source_full_name"`
+	SourceMembersURL string              `json:"source_members_url"`
+	CreatedAt        *time.Time          `json:"created_at"`
+	ExpiresAt        *time.Time          `json:"expires_at"`
+	AccessLevel      *AccessLevelDetails `json:"access_level"`
+}
+
 // ListGroupMembersOptions represents the available ListGroupMembers() and
 // ListAllGroupMembers() options.
 //
@@ -184,25 +217,6 @@ func (s *GroupMembersService) GetInheritedGroupMember(gid interface{}, user int,
 	return gm, resp, err
 }
 
-// BillableGroupMember represents a GitLab billable group member.
-//
-// GitLab API docs: https://docs.gitlab.com/ee/api/members.html#list-all-billable-members-of-a-group
-type BillableGroupMember struct {
-	ID             int        `json:"id"`
-	Username       string     `json:"username"`
-	Name           string     `json:"name"`
-	State          string     `json:"state"`
-	AvatarURL      string     `json:"avatar_url"`
-	WebURL         string     `json:"web_url"`
-	Email          string     `json:"email"`
-	LastActivityOn *ISOTime   `json:"last_activity_on"`
-	MembershipType string     `json:"membership_type"`
-	Removable      bool       `json:"removable"`
-	CreatedAt      *time.Time `json:"created_at"`
-	IsLastOwner    bool       `json:"is_last_owner"`
-	LastLoginAt    *time.Time `json:"last_login_at"`
-}
-
 // ListBillableGroupMembersOptions represents the available ListBillableGroupMembers() options.
 //
 // GitLab API docs:
@@ -239,6 +253,32 @@ func (s *GroupsService) ListBillableGroupMembers(gid interface{}, opt *ListBilla
 	return bgm, resp, nil
 }
 
+// ListMembershipsForBillableGroupMember Gets a list of memberships for a billable member of a group.
+// Lists all projects and groups a user is a member of. Only projects and groups within the group hierarchy are included.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/members.html#list-memberships-for-a-billable-member-of-a-group
+func (s *GroupsService) ListMembershipsForBillableGroupMember(gid interface{}, user int, options ...RequestOptionFunc) ([]*BillableUserMembership, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/billable_members/%d/memberships", PathEscape(group), user)
+
+	req, err := s.client.NewRequest(http.MethodGet, u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var bum []*BillableUserMembership
+	resp, err := s.client.Do(req, &bum)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return bum, resp, nil
+}
+
 // RemoveBillableGroupMember removes a given group members that count as billable.
 //
 // GitLab API docs:
diff --git a/types.go b/types.go
index 9ce13d7..5b7f74e 100644
--- a/types.go
+++ b/types.go
@@ -89,6 +89,11 @@ func AccessLevel(v AccessLevelValue) *AccessLevelValue {
 	return Ptr(v)
 }
 
+type AccessLevelDetails struct {
+	IntegerValue AccessLevelValue `json:"integer_value"`
+	StringValue  string           `json:"string_value"`
+}
+
 // UserIDValue represents a user ID value within GitLab.
 type UserIDValue string
 


Test patch:
diff --git a/group_members_test.go b/group_members_test.go
index 652d634..98fc0d2 100644
--- a/group_members_test.go
+++ b/group_members_test.go
@@ -81,6 +81,50 @@ func TestListBillableGroupMembers(t *testing.T) {
 	assert.Equal(t, want, billableMembers, "Expected returned Groups.ListBillableGroupMembers to equal")
 }
 
+func TestListMembershipsForBillableGroupMember(t *testing.T) {
+	mux, client := setup(t)
+	mux.HandleFunc("/api/v4/groups/1/billable_members/42/memberships",
+		func(w http.ResponseWriter, r *http.Request) {
+			testMethod(t, r, http.MethodGet)
+			fmt.Fprint(w,
+				`[
+					{
+						"id":21,
+						"source_id":36,
+						"source_full_name":"Root Group / Test Group",
+						"source_members_url":"https://gitlab.example.com/groups/root-group/test-group/-/group_members",
+						"created_at":"2021-03-31T17:28:44.812Z",
+						"access_level": {
+							"string_value": "Developer",
+							"integer_value": 30
+						}
+					}
+				]`)
+		})
+
+	memberships, _, err := client.Groups.ListMembershipsForBillableGroupMember(1, 42)
+	if err != nil {
+		t.Errorf("Groups.ListMembershipsForBillableGroupMember returned error: %v", err)
+	}
+
+	createdAt, _ := time.Parse(time.RFC3339, "2021-03-31T17:28:44.812Z")
+
+	want := []*BillableUserMembership{
+		{
+			ID:               21,
+			SourceID:         36,
+			SourceFullName:   "Root Group / Test Group",
+			SourceMembersURL: "https://gitlab.example.com/groups/root-group/test-group/-/group_members",
+			CreatedAt:        &createdAt,
+			AccessLevel: &AccessLevelDetails{
+				IntegerValue: 30,
+				StringValue:  "Developer",
+			},
+		},
+	}
+	assert.Equal(t, want, memberships, "Expected returned Groups.ListMembershipsForBillableGroupMember to equal")
+}
+
 func TestListGroupMembersWithoutEmail(t *testing.T) {
 	mux, client := setup(t)