chore: add group_ids filter to /groups endpoint (#14688)

Allow filtering groups by IDs.
This commit is contained in:
Steven Masley
2024-09-16 13:01:46 -05:00
committed by GitHub
parent 5ed065d88d
commit c330af0e4d
10 changed files with 99 additions and 9 deletions
+7
View File
@@ -1062,6 +1062,13 @@ const docTemplate = `{
"name": "has_member",
"in": "query",
"required": true
},
{
"type": "string",
"description": "Comma separated list of group IDs",
"name": "group_ids",
"in": "query",
"required": true
}
],
"responses": {
+7
View File
@@ -916,6 +916,13 @@
"name": "has_member",
"in": "query",
"required": true
},
{
"type": "string",
"description": "Comma separated list of group IDs",
"name": "group_ids",
"in": "query",
"required": true
}
],
"responses": {
+6
View File
@@ -2714,6 +2714,12 @@ func (q *FakeQuerier) GetGroups(_ context.Context, arg database.GetGroupsParams)
orgDetailsCache := make(map[uuid.UUID]struct{ name, displayName string })
filtered := make([]database.GetGroupsRow, 0)
for _, group := range q.groups {
if len(arg.GroupIds) > 0 {
if !slices.Contains(arg.GroupIds, group.ID) {
continue
}
}
if arg.OrganizationID != uuid.Nil && group.OrganizationID != arg.OrganizationID {
continue
}
+14 -4
View File
@@ -1683,12 +1683,17 @@ WHERE
groups.name = ANY($3)
ELSE true
END
AND CASE WHEN array_length($4 :: uuid[], 1) > 0 THEN
groups.id = ANY($4)
ELSE true
END
`
type GetGroupsParams struct {
OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"`
HasMemberID uuid.UUID `db:"has_member_id" json:"has_member_id"`
GroupNames []string `db:"group_names" json:"group_names"`
OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"`
HasMemberID uuid.UUID `db:"has_member_id" json:"has_member_id"`
GroupNames []string `db:"group_names" json:"group_names"`
GroupIds []uuid.UUID `db:"group_ids" json:"group_ids"`
}
type GetGroupsRow struct {
@@ -1698,7 +1703,12 @@ type GetGroupsRow struct {
}
func (q *sqlQuerier) GetGroups(ctx context.Context, arg GetGroupsParams) ([]GetGroupsRow, error) {
rows, err := q.db.QueryContext(ctx, getGroups, arg.OrganizationID, arg.HasMemberID, pq.Array(arg.GroupNames))
rows, err := q.db.QueryContext(ctx, getGroups,
arg.OrganizationID,
arg.HasMemberID,
pq.Array(arg.GroupNames),
pq.Array(arg.GroupIds),
)
if err != nil {
return nil, err
}
+4
View File
@@ -56,6 +56,10 @@ WHERE
groups.name = ANY(@group_names)
ELSE true
END
AND CASE WHEN array_length(@group_ids :: uuid[], 1) > 0 THEN
groups.id = ANY(@group_ids)
ELSE true
END
;
-- name: InsertGroup :one
+11
View File
@@ -6,6 +6,7 @@ import (
"fmt"
"net/http"
"net/url"
"strings"
"github.com/google/uuid"
"golang.org/x/xerrors"
@@ -74,6 +75,9 @@ type GroupArguments struct {
Organization string
// HasMember can be a user uuid or username
HasMember string
// GroupIDs is a list of group UUIDs to filter by.
// If not set, all groups will be returned.
GroupIDs []uuid.UUID
}
func (c *Client) Groups(ctx context.Context, args GroupArguments) ([]Group, error) {
@@ -84,6 +88,13 @@ func (c *Client) Groups(ctx context.Context, args GroupArguments) ([]Group, erro
if args.HasMember != "" {
qp.Set("has_member", args.HasMember)
}
if len(args.GroupIDs) > 0 {
idStrs := make([]string, 0, len(args.GroupIDs))
for _, id := range args.GroupIDs {
idStrs = append(idStrs, id.String())
}
qp.Set("group_ids", strings.Join(idStrs, ","))
}
res, err := c.Request(ctx, http.MethodGet,
fmt.Sprintf("/api/v2/groups?%s", qp.Encode()),
+6 -5
View File
@@ -179,7 +179,7 @@ To perform this operation, you must be authenticated. [Learn more](authenticatio
```shell
# Example request using curl
curl -X GET http://coder-server:8080/api/v2/groups?organization=string&has_member=string \
curl -X GET http://coder-server:8080/api/v2/groups?organization=string&has_member=string&group_ids=string \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
@@ -188,10 +188,11 @@ curl -X GET http://coder-server:8080/api/v2/groups?organization=string&has_membe
### Parameters
| Name | In | Type | Required | Description |
| -------------- | ----- | ------ | -------- | ----------------------- |
| `organization` | query | string | true | Organization ID or name |
| `has_member` | query | string | true | User ID or name |
| Name | In | Type | Required | Description |
| -------------- | ----- | ------ | -------- | --------------------------------- |
| `organization` | query | string | true | Organization ID or name |
| `has_member` | query | string | true | User ID or name |
| `group_ids` | query | string | true | Comma separated list of group IDs |
### Example responses
+4
View File
@@ -430,6 +430,7 @@ func (api *API) groupsByOrganization(rw http.ResponseWriter, r *http.Request) {
// @Tags Enterprise
// @Param organization query string true "Organization ID or name"
// @Param has_member query string true "User ID or name"
// @Param group_ids query string true "Comma separated list of group IDs"
// @Success 200 {array} codersdk.Group
// @Router /groups [get]
func (api *API) groups(rw http.ResponseWriter, r *http.Request) {
@@ -457,6 +458,9 @@ func (api *API) groups(rw http.ResponseWriter, r *http.Request) {
}
return user.ID, nil
})
filter.GroupIds = parser.UUIDs(r.URL.Query(), []uuid.UUID{}, "group_ids")
parser.ErrorExcessParams(r.URL.Query())
if len(parser.Errors) > 0 {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
+39
View File
@@ -779,6 +779,45 @@ func TestGroup(t *testing.T) {
require.Contains(t, group.Members, user2.ReducedUser)
})
t.Run("ByIDs", func(t *testing.T) {
t.Parallel()
client, user := coderdenttest.New(t, &coderdenttest.Options{LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
codersdk.FeatureTemplateRBAC: 1,
},
}})
userAdminClient, _ := coderdtest.CreateAnotherUser(t, client, user.OrganizationID, rbac.RoleUserAdmin())
ctx := testutil.Context(t, testutil.WaitLong)
groupA, err := userAdminClient.CreateGroup(ctx, user.OrganizationID, codersdk.CreateGroupRequest{
Name: "group-a",
})
require.NoError(t, err)
groupB, err := userAdminClient.CreateGroup(ctx, user.OrganizationID, codersdk.CreateGroupRequest{
Name: "group-b",
})
require.NoError(t, err)
// group-c should be omitted from the filter
_, err = userAdminClient.CreateGroup(ctx, user.OrganizationID, codersdk.CreateGroupRequest{
Name: "group-c",
})
require.NoError(t, err)
found, err := userAdminClient.Groups(ctx, codersdk.GroupArguments{
GroupIDs: []uuid.UUID{groupA.ID, groupB.ID},
})
require.NoError(t, err)
foundIDs := db2sdk.List(found, func(g codersdk.Group) uuid.UUID {
return g.ID
})
require.ElementsMatch(t, []uuid.UUID{groupA.ID, groupB.ID}, foundIDs)
})
t.Run("everyoneGroupReturnsEmpty", func(t *testing.T) {
t.Parallel()
+1
View File
@@ -631,6 +631,7 @@ export interface Group {
export interface GroupArguments {
readonly Organization: string;
readonly HasMember: string;
readonly GroupIDs: Readonly<Array<string>>;
}
// From codersdk/idpsync.go