Loading gitlab_test/groups_integration_test.go +114 −0 Original line number Diff line number Diff line Loading @@ -583,6 +583,120 @@ func Test_GroupsListSAMLUsers_Integration(t *testing.T) { assert.NotNil(t, users) } // Test_GroupsListGroups_CustomAttributesFiltering_Integration verifies that // ListGroupsOptions.CustomAttributes is filtered server-side, and that // multiple keys are combined with AND. Filtering by custom attributes requires // an administrator token. func Test_GroupsListGroups_CustomAttributesFiltering_Integration(t *testing.T) { // GIVEN a GitLab client and two test groups, only one of which carries the // attributes under test. The location value is unique per run so groups // left behind by an earlier run can't match the filter. client := SetupIntegrationClient(t) matching := CreateTestGroup(t, client) other := CreateTestGroup(t, client) location := fmt.Sprintf("antarctica-%d", time.Now().UnixNano()) SetTestGroupCustomAttribute(t, client, matching.ID, "location", location) SetTestGroupCustomAttribute(t, client, matching.ID, "role", "developer") SetTestGroupCustomAttribute(t, client, other.ID, "location", "arctic") // WHEN listing groups filtered by both custom attributes groups, _, err := client.Groups.ListGroups(&gitlab.ListGroupsOptions{ WithCustomAttributes: gitlab.Ptr(true), CustomAttributes: gitlab.CustomAttributesFilter{ "location": location, "role": "developer", }, }, gitlab.WithContext(t.Context())) require.NoError(t, err, "Failed to list groups") // THEN only the group carrying both attributes is returned, with its // custom attributes included in the response require.Len(t, groups, 1) assert.Equal(t, matching.ID, groups[0].ID) assert.ElementsMatch(t, []*gitlab.CustomAttribute{ {Key: "location", Value: location}, {Key: "role", Value: "developer"}, }, groups[0].CustomAttributes) // AND WHEN one of the two attribute values doesn't match groups, _, err = client.Groups.ListGroups(&gitlab.ListGroupsOptions{ CustomAttributes: gitlab.CustomAttributesFilter{ "location": location, "role": "maintainer", }, }, gitlab.WithContext(t.Context())) require.NoError(t, err, "Failed to list groups") // THEN no groups are returned, i.e. the keys are combined with AND assert.Empty(t, groups) } // Test_GroupsListSubGroups_CustomAttributesFiltering_Integration verifies the // filter also applies to the subgroups endpoint, which uses the // ListSubGroupsOptions alias of ListGroupsOptions. func Test_GroupsListSubGroups_CustomAttributesFiltering_Integration(t *testing.T) { // GIVEN a GitLab client and a parent group with two subgroups, only one of // which carries the attribute under test client := SetupIntegrationClient(t) parent := CreateTestGroup(t, client) matching := CreateTestSubGroup(t, client, parent.ID) other := CreateTestSubGroup(t, client, parent.ID) location := fmt.Sprintf("antarctica-%d", time.Now().UnixNano()) SetTestGroupCustomAttribute(t, client, matching.ID, "location", location) SetTestGroupCustomAttribute(t, client, other.ID, "location", "arctic") // WHEN listing the subgroups filtered by the custom attribute groups, _, err := client.Groups.ListSubGroups(parent.ID, &gitlab.ListSubGroupsOptions{ WithCustomAttributes: gitlab.Ptr(true), CustomAttributes: gitlab.CustomAttributesFilter{"location": location}, }, gitlab.WithContext(t.Context())) require.NoError(t, err, "Failed to list subgroups") // THEN only the matching subgroup is returned require.Len(t, groups, 1) assert.Equal(t, matching.ID, groups[0].ID) assert.Equal(t, []*gitlab.CustomAttribute{ {Key: "location", Value: location}, }, groups[0].CustomAttributes) } // Test_GroupsListDescendantGroups_CustomAttributesFiltering_Integration // verifies the filter also applies to the descendant groups endpoint, which // uses the ListDescendantGroupsOptions alias of ListGroupsOptions. The // attribute is set on a group nested two levels deep, which the subgroups // endpoint wouldn't return. func Test_GroupsListDescendantGroups_CustomAttributesFiltering_Integration(t *testing.T) { // GIVEN a GitLab client and a group hierarchy where only the deepest group // carries the attribute under test client := SetupIntegrationClient(t) parent := CreateTestGroup(t, client) child := CreateTestSubGroup(t, client, parent.ID) grandchild := CreateTestSubGroup(t, client, child.ID) location := fmt.Sprintf("antarctica-%d", time.Now().UnixNano()) SetTestGroupCustomAttribute(t, client, grandchild.ID, "location", location) SetTestGroupCustomAttribute(t, client, child.ID, "location", "arctic") // WHEN listing the descendant groups filtered by the custom attribute groups, _, err := client.Groups.ListDescendantGroups(parent.ID, &gitlab.ListDescendantGroupsOptions{ WithCustomAttributes: gitlab.Ptr(true), CustomAttributes: gitlab.CustomAttributesFilter{"location": location}, }, gitlab.WithContext(t.Context())) require.NoError(t, err, "Failed to list descendant groups") // THEN only the matching descendant group is returned require.Len(t, groups, 1) assert.Equal(t, grandchild.ID, groups[0].ID) assert.Equal(t, []*gitlab.CustomAttribute{ {Key: "location", Value: location}, }, groups[0].CustomAttributes) } func Test_GroupsSyncGroupWithLDAP_Integration(t *testing.T) { // GIVEN a GitLab client and a test group client := SetupIntegrationClient(t) Loading gitlab_test/utils_test.go +32 −0 Original line number Diff line number Diff line Loading @@ -241,6 +241,38 @@ func CreateTestGroupWithOptions(t *testing.T, client *gitlab.Client, opts *gitla return group } // CreateTestSubGroup creates a test subgroup of the given parent group with a // random name and path. The subgroup is automatically cleaned up when the test // finishes. func CreateTestSubGroup(t *testing.T, client *gitlab.Client, parentID int64) *gitlab.Group { t.Helper() suffix := time.Now().UnixNano() return CreateTestGroupWithOptions(t, client, &gitlab.CreateGroupOptions{ Name: gitlab.Ptr(fmt.Sprintf("testsubgroup%d", suffix)), Path: gitlab.Ptr(fmt.Sprintf("testsubgroup%d", suffix)), Visibility: gitlab.Ptr(gitlab.PublicVisibility), ParentID: gitlab.Ptr(parentID), }) } // SetTestGroupCustomAttribute sets a custom attribute on the given group. // Setting custom attributes requires an administrator token. The attribute is // automatically cleaned up when the test finishes. func SetTestGroupCustomAttribute(t *testing.T, client *gitlab.Client, gid int64, key, value string) { t.Helper() _, _, err := client.CustomAttribute.SetCustomGroupAttribute(gid, gitlab.CustomAttribute{ Key: key, Value: value, }, gitlab.WithContext(t.Context())) require.NoError(t, err, "Failed to set custom attribute on test group") t.Cleanup(func() { _, _ = client.CustomAttribute.DeleteCustomGroupAttribute(gid, key, gitlab.WithContext(context.Background())) }) } // CreateTestEpic creates a test epic with a random title in the specified // group. The epic is automatically cleaned up when the test finishes. func CreateTestEpic(t *testing.T, client *gitlab.Client, gid any) (*gitlab.Epic, error) { Loading groups.go +6 −0 Original line number Diff line number Diff line Loading @@ -457,6 +457,12 @@ type ListGroupsOptions struct { MarkedForDeletionOn *ISOTime `url:"marked_for_deletion_on,omitempty" json:"marked_for_deletion_on,omitempty"` Active *bool `url:"active,omitempty" json:"active,omitempty"` Archived *bool `url:"archived,omitempty" json:"archived,omitempty"` // CustomAttributes filters groups by custom attributes, producing query // parameters of the form custom_attributes[key]=value. This is distinct // from WithCustomAttributes, which only controls whether custom attributes // are included in the response. CustomAttributes CustomAttributesFilter `url:"custom_attributes,omitempty" json:"custom_attributes,omitempty"` } func (s *GroupsService) ListGroups(opt *ListGroupsOptions, options ...RequestOptionFunc) ([]*Group, *Response, error) { Loading groups_test.go +120 −0 Original line number Diff line number Diff line Loading @@ -61,6 +61,126 @@ func TestListGroups_Filtering(t *testing.T) { assert.Equal(t, want, groups) } func TestListGroups_CustomAttributesFiltering(t *testing.T) { t.Parallel() mux, client := setup(t) mux.HandleFunc("/api/v4/groups", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodGet) testParam(t, r, "custom_attributes[location]", "Antarctica") testParam(t, r, "custom_attributes[role]", "Developer") testParam(t, r, "with_custom_attributes", "true") fmt.Fprint(w, `[ { "id": 1, "custom_attributes": [ { "key": "location", "value": "Antarctica" }, { "key": "role", "value": "Developer" } ] } ]`) }) withCustomAttributes := true opt := &ListGroupsOptions{ WithCustomAttributes: &withCustomAttributes, CustomAttributes: CustomAttributesFilter{ "location": "Antarctica", "role": "Developer", }, } groups, _, err := client.Groups.ListGroups(opt) require.NoError(t, err) want := []*Group{{ ID: 1, CustomAttributes: []*CustomAttribute{ {Key: "location", Value: "Antarctica"}, {Key: "role", Value: "Developer"}, }, }} assert.Equal(t, want, groups) } // TestListGroups_CustomAttributesFiltering_Empty ensures that a nil or empty // CustomAttributesFilter emits no custom_attributes query parameters (the // omitempty tag is honored before EncodeValues is dispatched). func TestListGroups_CustomAttributesFiltering_Empty(t *testing.T) { t.Parallel() mux, client := setup(t) mux.HandleFunc("/api/v4/groups", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodGet) for key := range r.URL.Query() { assert.NotContains(t, key, "custom_attributes", "expected no custom_attributes params, got %q", key) } fmt.Fprint(w, `[{"id": 1}]`) }) for name, opt := range map[string]*ListGroupsOptions{ "nil filter": {CustomAttributes: nil}, "empty filter": {CustomAttributes: CustomAttributesFilter{}}, } { t.Run(name, func(t *testing.T) { t.Parallel() groups, _, err := client.Groups.ListGroups(opt) require.NoError(t, err) assert.Equal(t, []*Group{{ID: 1}}, groups) }) } } // TestListSubGroups_CustomAttributesFiltering and its descendant-groups // counterpart confirm the filter also applies to the ListSubGroupsOptions and // ListDescendantGroupsOptions aliases of ListGroupsOptions. func TestListSubGroups_CustomAttributesFiltering(t *testing.T) { t.Parallel() mux, client := setup(t) mux.HandleFunc("/api/v4/groups/1/subgroups", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodGet) testParam(t, r, "custom_attributes[location]", "Antarctica") fmt.Fprint(w, `[{"id": 2}]`) }) opt := &ListSubGroupsOptions{ CustomAttributes: CustomAttributesFilter{"location": "Antarctica"}, } groups, _, err := client.Groups.ListSubGroups(1, opt) require.NoError(t, err) assert.Equal(t, []*Group{{ID: 2}}, groups) } func TestListDescendantGroups_CustomAttributesFiltering(t *testing.T) { t.Parallel() mux, client := setup(t) mux.HandleFunc("/api/v4/groups/1/descendant_groups", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodGet) testParam(t, r, "custom_attributes[location]", "Antarctica") fmt.Fprint(w, `[{"id": 2}]`) }) opt := &ListDescendantGroupsOptions{ CustomAttributes: CustomAttributesFilter{"location": "Antarctica"}, } groups, _, err := client.Groups.ListDescendantGroups(1, opt) require.NoError(t, err) assert.Equal(t, []*Group{{ID: 2}}, groups) } func TestListGroups_Visibility(t *testing.T) { t.Parallel() mux, client := setup(t) Loading types.go +15 −0 Original line number Diff line number Diff line Loading @@ -595,6 +595,21 @@ func (l *LabelOptions) EncodeValues(key string, v *url.Values) error { return nil } // CustomAttributesFilter is a set of custom attribute key/value pairs used to // filter list results. It encodes to query parameters of the form // custom_attributes[key]=value, as expected by the GitLab API. // // GitLab API docs: https://docs.gitlab.com/api/custom_attributes/ type CustomAttributesFilter map[string]string // EncodeValues implements the query.Encoder interface. func (f CustomAttributesFilter) EncodeValues(key string, v *url.Values) error { for k, val := range f { v.Set(key+"["+k+"]", val) } return nil } // LinkTypeValue represents a release link type. type LinkTypeValue string Loading Loading
gitlab_test/groups_integration_test.go +114 −0 Original line number Diff line number Diff line Loading @@ -583,6 +583,120 @@ func Test_GroupsListSAMLUsers_Integration(t *testing.T) { assert.NotNil(t, users) } // Test_GroupsListGroups_CustomAttributesFiltering_Integration verifies that // ListGroupsOptions.CustomAttributes is filtered server-side, and that // multiple keys are combined with AND. Filtering by custom attributes requires // an administrator token. func Test_GroupsListGroups_CustomAttributesFiltering_Integration(t *testing.T) { // GIVEN a GitLab client and two test groups, only one of which carries the // attributes under test. The location value is unique per run so groups // left behind by an earlier run can't match the filter. client := SetupIntegrationClient(t) matching := CreateTestGroup(t, client) other := CreateTestGroup(t, client) location := fmt.Sprintf("antarctica-%d", time.Now().UnixNano()) SetTestGroupCustomAttribute(t, client, matching.ID, "location", location) SetTestGroupCustomAttribute(t, client, matching.ID, "role", "developer") SetTestGroupCustomAttribute(t, client, other.ID, "location", "arctic") // WHEN listing groups filtered by both custom attributes groups, _, err := client.Groups.ListGroups(&gitlab.ListGroupsOptions{ WithCustomAttributes: gitlab.Ptr(true), CustomAttributes: gitlab.CustomAttributesFilter{ "location": location, "role": "developer", }, }, gitlab.WithContext(t.Context())) require.NoError(t, err, "Failed to list groups") // THEN only the group carrying both attributes is returned, with its // custom attributes included in the response require.Len(t, groups, 1) assert.Equal(t, matching.ID, groups[0].ID) assert.ElementsMatch(t, []*gitlab.CustomAttribute{ {Key: "location", Value: location}, {Key: "role", Value: "developer"}, }, groups[0].CustomAttributes) // AND WHEN one of the two attribute values doesn't match groups, _, err = client.Groups.ListGroups(&gitlab.ListGroupsOptions{ CustomAttributes: gitlab.CustomAttributesFilter{ "location": location, "role": "maintainer", }, }, gitlab.WithContext(t.Context())) require.NoError(t, err, "Failed to list groups") // THEN no groups are returned, i.e. the keys are combined with AND assert.Empty(t, groups) } // Test_GroupsListSubGroups_CustomAttributesFiltering_Integration verifies the // filter also applies to the subgroups endpoint, which uses the // ListSubGroupsOptions alias of ListGroupsOptions. func Test_GroupsListSubGroups_CustomAttributesFiltering_Integration(t *testing.T) { // GIVEN a GitLab client and a parent group with two subgroups, only one of // which carries the attribute under test client := SetupIntegrationClient(t) parent := CreateTestGroup(t, client) matching := CreateTestSubGroup(t, client, parent.ID) other := CreateTestSubGroup(t, client, parent.ID) location := fmt.Sprintf("antarctica-%d", time.Now().UnixNano()) SetTestGroupCustomAttribute(t, client, matching.ID, "location", location) SetTestGroupCustomAttribute(t, client, other.ID, "location", "arctic") // WHEN listing the subgroups filtered by the custom attribute groups, _, err := client.Groups.ListSubGroups(parent.ID, &gitlab.ListSubGroupsOptions{ WithCustomAttributes: gitlab.Ptr(true), CustomAttributes: gitlab.CustomAttributesFilter{"location": location}, }, gitlab.WithContext(t.Context())) require.NoError(t, err, "Failed to list subgroups") // THEN only the matching subgroup is returned require.Len(t, groups, 1) assert.Equal(t, matching.ID, groups[0].ID) assert.Equal(t, []*gitlab.CustomAttribute{ {Key: "location", Value: location}, }, groups[0].CustomAttributes) } // Test_GroupsListDescendantGroups_CustomAttributesFiltering_Integration // verifies the filter also applies to the descendant groups endpoint, which // uses the ListDescendantGroupsOptions alias of ListGroupsOptions. The // attribute is set on a group nested two levels deep, which the subgroups // endpoint wouldn't return. func Test_GroupsListDescendantGroups_CustomAttributesFiltering_Integration(t *testing.T) { // GIVEN a GitLab client and a group hierarchy where only the deepest group // carries the attribute under test client := SetupIntegrationClient(t) parent := CreateTestGroup(t, client) child := CreateTestSubGroup(t, client, parent.ID) grandchild := CreateTestSubGroup(t, client, child.ID) location := fmt.Sprintf("antarctica-%d", time.Now().UnixNano()) SetTestGroupCustomAttribute(t, client, grandchild.ID, "location", location) SetTestGroupCustomAttribute(t, client, child.ID, "location", "arctic") // WHEN listing the descendant groups filtered by the custom attribute groups, _, err := client.Groups.ListDescendantGroups(parent.ID, &gitlab.ListDescendantGroupsOptions{ WithCustomAttributes: gitlab.Ptr(true), CustomAttributes: gitlab.CustomAttributesFilter{"location": location}, }, gitlab.WithContext(t.Context())) require.NoError(t, err, "Failed to list descendant groups") // THEN only the matching descendant group is returned require.Len(t, groups, 1) assert.Equal(t, grandchild.ID, groups[0].ID) assert.Equal(t, []*gitlab.CustomAttribute{ {Key: "location", Value: location}, }, groups[0].CustomAttributes) } func Test_GroupsSyncGroupWithLDAP_Integration(t *testing.T) { // GIVEN a GitLab client and a test group client := SetupIntegrationClient(t) Loading
gitlab_test/utils_test.go +32 −0 Original line number Diff line number Diff line Loading @@ -241,6 +241,38 @@ func CreateTestGroupWithOptions(t *testing.T, client *gitlab.Client, opts *gitla return group } // CreateTestSubGroup creates a test subgroup of the given parent group with a // random name and path. The subgroup is automatically cleaned up when the test // finishes. func CreateTestSubGroup(t *testing.T, client *gitlab.Client, parentID int64) *gitlab.Group { t.Helper() suffix := time.Now().UnixNano() return CreateTestGroupWithOptions(t, client, &gitlab.CreateGroupOptions{ Name: gitlab.Ptr(fmt.Sprintf("testsubgroup%d", suffix)), Path: gitlab.Ptr(fmt.Sprintf("testsubgroup%d", suffix)), Visibility: gitlab.Ptr(gitlab.PublicVisibility), ParentID: gitlab.Ptr(parentID), }) } // SetTestGroupCustomAttribute sets a custom attribute on the given group. // Setting custom attributes requires an administrator token. The attribute is // automatically cleaned up when the test finishes. func SetTestGroupCustomAttribute(t *testing.T, client *gitlab.Client, gid int64, key, value string) { t.Helper() _, _, err := client.CustomAttribute.SetCustomGroupAttribute(gid, gitlab.CustomAttribute{ Key: key, Value: value, }, gitlab.WithContext(t.Context())) require.NoError(t, err, "Failed to set custom attribute on test group") t.Cleanup(func() { _, _ = client.CustomAttribute.DeleteCustomGroupAttribute(gid, key, gitlab.WithContext(context.Background())) }) } // CreateTestEpic creates a test epic with a random title in the specified // group. The epic is automatically cleaned up when the test finishes. func CreateTestEpic(t *testing.T, client *gitlab.Client, gid any) (*gitlab.Epic, error) { Loading
groups.go +6 −0 Original line number Diff line number Diff line Loading @@ -457,6 +457,12 @@ type ListGroupsOptions struct { MarkedForDeletionOn *ISOTime `url:"marked_for_deletion_on,omitempty" json:"marked_for_deletion_on,omitempty"` Active *bool `url:"active,omitempty" json:"active,omitempty"` Archived *bool `url:"archived,omitempty" json:"archived,omitempty"` // CustomAttributes filters groups by custom attributes, producing query // parameters of the form custom_attributes[key]=value. This is distinct // from WithCustomAttributes, which only controls whether custom attributes // are included in the response. CustomAttributes CustomAttributesFilter `url:"custom_attributes,omitempty" json:"custom_attributes,omitempty"` } func (s *GroupsService) ListGroups(opt *ListGroupsOptions, options ...RequestOptionFunc) ([]*Group, *Response, error) { Loading
groups_test.go +120 −0 Original line number Diff line number Diff line Loading @@ -61,6 +61,126 @@ func TestListGroups_Filtering(t *testing.T) { assert.Equal(t, want, groups) } func TestListGroups_CustomAttributesFiltering(t *testing.T) { t.Parallel() mux, client := setup(t) mux.HandleFunc("/api/v4/groups", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodGet) testParam(t, r, "custom_attributes[location]", "Antarctica") testParam(t, r, "custom_attributes[role]", "Developer") testParam(t, r, "with_custom_attributes", "true") fmt.Fprint(w, `[ { "id": 1, "custom_attributes": [ { "key": "location", "value": "Antarctica" }, { "key": "role", "value": "Developer" } ] } ]`) }) withCustomAttributes := true opt := &ListGroupsOptions{ WithCustomAttributes: &withCustomAttributes, CustomAttributes: CustomAttributesFilter{ "location": "Antarctica", "role": "Developer", }, } groups, _, err := client.Groups.ListGroups(opt) require.NoError(t, err) want := []*Group{{ ID: 1, CustomAttributes: []*CustomAttribute{ {Key: "location", Value: "Antarctica"}, {Key: "role", Value: "Developer"}, }, }} assert.Equal(t, want, groups) } // TestListGroups_CustomAttributesFiltering_Empty ensures that a nil or empty // CustomAttributesFilter emits no custom_attributes query parameters (the // omitempty tag is honored before EncodeValues is dispatched). func TestListGroups_CustomAttributesFiltering_Empty(t *testing.T) { t.Parallel() mux, client := setup(t) mux.HandleFunc("/api/v4/groups", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodGet) for key := range r.URL.Query() { assert.NotContains(t, key, "custom_attributes", "expected no custom_attributes params, got %q", key) } fmt.Fprint(w, `[{"id": 1}]`) }) for name, opt := range map[string]*ListGroupsOptions{ "nil filter": {CustomAttributes: nil}, "empty filter": {CustomAttributes: CustomAttributesFilter{}}, } { t.Run(name, func(t *testing.T) { t.Parallel() groups, _, err := client.Groups.ListGroups(opt) require.NoError(t, err) assert.Equal(t, []*Group{{ID: 1}}, groups) }) } } // TestListSubGroups_CustomAttributesFiltering and its descendant-groups // counterpart confirm the filter also applies to the ListSubGroupsOptions and // ListDescendantGroupsOptions aliases of ListGroupsOptions. func TestListSubGroups_CustomAttributesFiltering(t *testing.T) { t.Parallel() mux, client := setup(t) mux.HandleFunc("/api/v4/groups/1/subgroups", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodGet) testParam(t, r, "custom_attributes[location]", "Antarctica") fmt.Fprint(w, `[{"id": 2}]`) }) opt := &ListSubGroupsOptions{ CustomAttributes: CustomAttributesFilter{"location": "Antarctica"}, } groups, _, err := client.Groups.ListSubGroups(1, opt) require.NoError(t, err) assert.Equal(t, []*Group{{ID: 2}}, groups) } func TestListDescendantGroups_CustomAttributesFiltering(t *testing.T) { t.Parallel() mux, client := setup(t) mux.HandleFunc("/api/v4/groups/1/descendant_groups", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodGet) testParam(t, r, "custom_attributes[location]", "Antarctica") fmt.Fprint(w, `[{"id": 2}]`) }) opt := &ListDescendantGroupsOptions{ CustomAttributes: CustomAttributesFilter{"location": "Antarctica"}, } groups, _, err := client.Groups.ListDescendantGroups(1, opt) require.NoError(t, err) assert.Equal(t, []*Group{{ID: 2}}, groups) } func TestListGroups_Visibility(t *testing.T) { t.Parallel() mux, client := setup(t) Loading
types.go +15 −0 Original line number Diff line number Diff line Loading @@ -595,6 +595,21 @@ func (l *LabelOptions) EncodeValues(key string, v *url.Values) error { return nil } // CustomAttributesFilter is a set of custom attribute key/value pairs used to // filter list results. It encodes to query parameters of the form // custom_attributes[key]=value, as expected by the GitLab API. // // GitLab API docs: https://docs.gitlab.com/api/custom_attributes/ type CustomAttributesFilter map[string]string // EncodeValues implements the query.Encoder interface. func (f CustomAttributesFilter) EncodeValues(key string, v *url.Values) error { for k, val := range f { v.Set(key+"["+k+"]", val) } return nil } // LinkTypeValue represents a release link type. type LinkTypeValue string Loading