mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
chore: add group_ids filter to /groups endpoint (#14688)
Allow filtering groups by IDs.
This commit is contained in:
Generated
+7
@@ -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": {
|
||||
|
||||
Generated
+7
@@ -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": {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()),
|
||||
|
||||
Generated
+6
-5
@@ -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
|
||||
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
Generated
+1
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user