diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 8f90cd5c205a2..0fd3d1165ed8e 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -2545,6 +2545,7 @@ const docTemplate = `{ ], "summary": "List organization members", "operationId": "list-organization-members", + "deprecated": true, "parameters": [ { "type": "string", @@ -2971,6 +2972,55 @@ const docTemplate = `{ } } }, + "/organizations/{organization}/paginated-members": { + "get": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "Members" + ], + "summary": "Paginated organization members", + "operationId": "paginated-organization-members", + "parameters": [ + { + "type": "string", + "description": "Organization ID", + "name": "organization", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "Page limit, if 0 returns all members", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "description": "Page offset", + "name": "offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/codersdk.PaginatedMembersResponse" + } + } + } + } + } + }, "/organizations/{organization}/provisionerdaemons": { "get": { "security": [ @@ -12902,6 +12952,20 @@ const docTemplate = `{ } } }, + "codersdk.PaginatedMembersResponse": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "members": { + "type": "array", + "items": { + "$ref": "#/definitions/codersdk.OrganizationMemberWithUserData" + } + } + } + }, "codersdk.PatchGroupIDPSyncConfigRequest": { "type": "object", "properties": { diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index fcfe56d3fc4aa..21546acb32ab3 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -2223,6 +2223,7 @@ "tags": ["Members"], "summary": "List organization members", "operationId": "list-organization-members", + "deprecated": true, "parameters": [ { "type": "string", @@ -2607,6 +2608,51 @@ } } }, + "/organizations/{organization}/paginated-members": { + "get": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "produces": ["application/json"], + "tags": ["Members"], + "summary": "Paginated organization members", + "operationId": "paginated-organization-members", + "parameters": [ + { + "type": "string", + "description": "Organization ID", + "name": "organization", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "Page limit, if 0 returns all members", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "description": "Page offset", + "name": "offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/codersdk.PaginatedMembersResponse" + } + } + } + } + } + }, "/organizations/{organization}/provisionerdaemons": { "get": { "security": [ @@ -11629,6 +11675,20 @@ } } }, + "codersdk.PaginatedMembersResponse": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "members": { + "type": "array", + "items": { + "$ref": "#/definitions/codersdk.OrganizationMemberWithUserData" + } + } + } + }, "codersdk.PatchGroupIDPSyncConfigRequest": { "type": "object", "properties": { diff --git a/coderd/coderd.go b/coderd/coderd.go index ab8e99d29dea8..da4e281dbe506 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -1002,6 +1002,7 @@ func New(options *Options) *API { }) }) }) + r.Get("/paginated-members", api.paginatedMembers) r.Route("/members", func(r chi.Router) { r.Get("/", api.listMembers) r.Route("/roles", func(r chi.Router) { diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index a4d76fa0198ed..9c88e986cbffc 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -3581,6 +3581,14 @@ func (q *querier) OrganizationMembers(ctx context.Context, arg database.Organiza return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.OrganizationMembers)(ctx, arg) } +func (q *querier) PaginatedOrganizationMembers(ctx context.Context, arg database.PaginatedOrganizationMembersParams) ([]database.PaginatedOrganizationMembersRow, error) { + // Required to have permission to read all members in the organization + if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceOrganizationMember.InOrg(arg.OrganizationID)); err != nil { + return nil, err + } + return q.db.PaginatedOrganizationMembers(ctx, arg) +} + func (q *querier) ReduceWorkspaceAgentShareLevelToAuthenticatedByTemplate(ctx context.Context, templateID uuid.UUID) error { template, err := q.db.GetTemplateByID(ctx, templateID) if err != nil { diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index 614a357efcbc5..ec8ced783fa0a 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -985,6 +985,32 @@ func (s *MethodTestSuite) TestOrganization() { mem, policy.ActionRead, ) })) + s.Run("PaginatedOrganizationMembers", s.Subtest(func(db database.Store, check *expects) { + o := dbgen.Organization(s.T(), db, database.Organization{}) + u := dbgen.User(s.T(), db, database.User{}) + mem := dbgen.OrganizationMember(s.T(), db, database.OrganizationMember{ + OrganizationID: o.ID, + UserID: u.ID, + Roles: []string{rbac.RoleOrgAdmin()}, + }) + + check.Args(database.PaginatedOrganizationMembersParams{ + OrganizationID: o.ID, + LimitOpt: 0, + }).Asserts( + rbac.ResourceOrganizationMember.InOrg(o.ID), policy.ActionRead, + ).Returns([]database.PaginatedOrganizationMembersRow{ + { + OrganizationMember: mem, + Username: u.Username, + AvatarURL: u.AvatarURL, + Name: u.Name, + Email: u.Email, + GlobalRoles: u.RBACRoles, + Count: 1, + }, + }) + })) s.Run("UpdateMemberRoles", s.Subtest(func(db database.Store, check *expects) { o := dbgen.Organization(s.T(), db, database.Organization{}) u := dbgen.User(s.T(), db, database.User{}) diff --git a/coderd/database/dbauthz/setup_test.go b/coderd/database/dbauthz/setup_test.go index 4faac05b4746e..1a822254a9e7a 100644 --- a/coderd/database/dbauthz/setup_test.go +++ b/coderd/database/dbauthz/setup_test.go @@ -503,7 +503,7 @@ func asserts(inputs ...any) []AssertRBAC { // Could be the string type. actionAsString, ok := inputs[i+1].(string) if !ok { - panic(fmt.Sprintf("action '%q' not a supported action", actionAsString)) + panic(fmt.Sprintf("action '%T' not a supported action", inputs[i+1])) } action = policy.Action(actionAsString) } diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index 7f7ff987ff544..63ee1d0bd95e7 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -9584,6 +9584,53 @@ func (q *FakeQuerier) OrganizationMembers(_ context.Context, arg database.Organi return tmp, nil } +func (q *FakeQuerier) PaginatedOrganizationMembers(_ context.Context, arg database.PaginatedOrganizationMembersParams) ([]database.PaginatedOrganizationMembersRow, error) { + err := validateDatabaseType(arg) + if err != nil { + return nil, err + } + + q.mutex.RLock() + defer q.mutex.RUnlock() + + // All of the members in the organization + orgMembers := make([]database.OrganizationMember, 0) + for _, mem := range q.organizationMembers { + if arg.OrganizationID != uuid.Nil && mem.OrganizationID != arg.OrganizationID { + continue + } + + orgMembers = append(orgMembers, mem) + } + + selectedMembers := make([]database.PaginatedOrganizationMembersRow, 0) + + skippedMembers := 0 + for _, organizationMember := range q.organizationMembers { + if skippedMembers < int(arg.OffsetOpt) { + skippedMembers++ + continue + } + + // if the limit is set to 0 we treat that as returning all of the org members + if int(arg.LimitOpt) != 0 && len(selectedMembers) >= int(arg.LimitOpt) { + break + } + + user, _ := q.getUserByIDNoLock(organizationMember.UserID) + selectedMembers = append(selectedMembers, database.PaginatedOrganizationMembersRow{ + OrganizationMember: organizationMember, + Username: user.Username, + AvatarURL: user.AvatarURL, + Name: user.Name, + Email: user.Email, + GlobalRoles: user.RBACRoles, + Count: int64(len(orgMembers)), + }) + } + return selectedMembers, nil +} + func (q *FakeQuerier) ReduceWorkspaceAgentShareLevelToAuthenticatedByTemplate(_ context.Context, templateID uuid.UUID) error { err := validateDatabaseType(templateID) if err != nil { diff --git a/coderd/database/dbmetrics/querymetrics.go b/coderd/database/dbmetrics/querymetrics.go index 0d021f978151b..407d9e48bfcf8 100644 --- a/coderd/database/dbmetrics/querymetrics.go +++ b/coderd/database/dbmetrics/querymetrics.go @@ -2278,6 +2278,13 @@ func (m queryMetricsStore) OrganizationMembers(ctx context.Context, arg database return r0, r1 } +func (m queryMetricsStore) PaginatedOrganizationMembers(ctx context.Context, arg database.PaginatedOrganizationMembersParams) ([]database.PaginatedOrganizationMembersRow, error) { + start := time.Now() + r0, r1 := m.s.PaginatedOrganizationMembers(ctx, arg) + m.queryLatencies.WithLabelValues("PaginatedOrganizationMembers").Observe(time.Since(start).Seconds()) + return r0, r1 +} + func (m queryMetricsStore) ReduceWorkspaceAgentShareLevelToAuthenticatedByTemplate(ctx context.Context, templateID uuid.UUID) error { start := time.Now() r0 := m.s.ReduceWorkspaceAgentShareLevelToAuthenticatedByTemplate(ctx, templateID) diff --git a/coderd/database/dbmock/dbmock.go b/coderd/database/dbmock/dbmock.go index 6e07614f4cb3f..fbe4d0745fbb0 100644 --- a/coderd/database/dbmock/dbmock.go +++ b/coderd/database/dbmock/dbmock.go @@ -4823,6 +4823,21 @@ func (mr *MockStoreMockRecorder) PGLocks(ctx any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PGLocks", reflect.TypeOf((*MockStore)(nil).PGLocks), ctx) } +// PaginatedOrganizationMembers mocks base method. +func (m *MockStore) PaginatedOrganizationMembers(ctx context.Context, arg database.PaginatedOrganizationMembersParams) ([]database.PaginatedOrganizationMembersRow, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PaginatedOrganizationMembers", ctx, arg) + ret0, _ := ret[0].([]database.PaginatedOrganizationMembersRow) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PaginatedOrganizationMembers indicates an expected call of PaginatedOrganizationMembers. +func (mr *MockStoreMockRecorder) PaginatedOrganizationMembers(ctx, arg any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PaginatedOrganizationMembers", reflect.TypeOf((*MockStore)(nil).PaginatedOrganizationMembers), ctx, arg) +} + // Ping mocks base method. func (m *MockStore) Ping(ctx context.Context) (time.Duration, error) { m.ctrl.T.Helper() diff --git a/coderd/database/modelmethods.go b/coderd/database/modelmethods.go index fe782bdd14170..a9dbc3e530994 100644 --- a/coderd/database/modelmethods.go +++ b/coderd/database/modelmethods.go @@ -256,6 +256,10 @@ func (m OrganizationMembersRow) RBACObject() rbac.Object { return m.OrganizationMember.RBACObject() } +func (m PaginatedOrganizationMembersRow) RBACObject() rbac.Object { + return m.OrganizationMember.RBACObject() +} + func (m GetOrganizationIDsByMemberIDsRow) RBACObject() rbac.Object { // TODO: This feels incorrect as we are really returning a list of orgmembers. // This return type should be refactored to return a list of orgmembers, not this diff --git a/coderd/database/querier.go b/coderd/database/querier.go index 28227797c7e3f..d72469650f0ea 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -478,6 +478,7 @@ type sqlcQuerier interface { // - Use just 'user_id' to get all orgs a user is a member of // - Use both to get a specific org member row OrganizationMembers(ctx context.Context, arg OrganizationMembersParams) ([]OrganizationMembersRow, error) + PaginatedOrganizationMembers(ctx context.Context, arg PaginatedOrganizationMembersParams) ([]PaginatedOrganizationMembersRow, error) ReduceWorkspaceAgentShareLevelToAuthenticatedByTemplate(ctx context.Context, templateID uuid.UUID) error RegisterWorkspaceProxy(ctx context.Context, arg RegisterWorkspaceProxyParams) (WorkspaceProxy, error) RemoveUserFromAllGroups(ctx context.Context, userID uuid.UUID) error diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 593fd065089b4..b394a0b0121ec 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -5270,6 +5270,81 @@ func (q *sqlQuerier) OrganizationMembers(ctx context.Context, arg OrganizationMe return items, nil } +const paginatedOrganizationMembers = `-- name: PaginatedOrganizationMembers :many +SELECT + organization_members.user_id, organization_members.organization_id, organization_members.created_at, organization_members.updated_at, organization_members.roles, + users.username, users.avatar_url, users.name, users.email, users.rbac_roles as "global_roles", + COUNT(*) OVER() AS count +FROM + organization_members + INNER JOIN + users ON organization_members.user_id = users.id AND users.deleted = false +WHERE + -- Filter by organization id + CASE + WHEN $1 :: uuid != '00000000-0000-0000-0000-000000000000'::uuid THEN + organization_id = $1 + ELSE true + END +ORDER BY + -- Deterministic and consistent ordering of all users. This is to ensure consistent pagination. + LOWER(username) ASC OFFSET $2 +LIMIT + -- A null limit means "no limit", so 0 means return all + NULLIF($3 :: int, 0) +` + +type PaginatedOrganizationMembersParams struct { + OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"` + OffsetOpt int32 `db:"offset_opt" json:"offset_opt"` + LimitOpt int32 `db:"limit_opt" json:"limit_opt"` +} + +type PaginatedOrganizationMembersRow struct { + OrganizationMember OrganizationMember `db:"organization_member" json:"organization_member"` + Username string `db:"username" json:"username"` + AvatarURL string `db:"avatar_url" json:"avatar_url"` + Name string `db:"name" json:"name"` + Email string `db:"email" json:"email"` + GlobalRoles pq.StringArray `db:"global_roles" json:"global_roles"` + Count int64 `db:"count" json:"count"` +} + +func (q *sqlQuerier) PaginatedOrganizationMembers(ctx context.Context, arg PaginatedOrganizationMembersParams) ([]PaginatedOrganizationMembersRow, error) { + rows, err := q.db.QueryContext(ctx, paginatedOrganizationMembers, arg.OrganizationID, arg.OffsetOpt, arg.LimitOpt) + if err != nil { + return nil, err + } + defer rows.Close() + var items []PaginatedOrganizationMembersRow + for rows.Next() { + var i PaginatedOrganizationMembersRow + if err := rows.Scan( + &i.OrganizationMember.UserID, + &i.OrganizationMember.OrganizationID, + &i.OrganizationMember.CreatedAt, + &i.OrganizationMember.UpdatedAt, + pq.Array(&i.OrganizationMember.Roles), + &i.Username, + &i.AvatarURL, + &i.Name, + &i.Email, + &i.GlobalRoles, + &i.Count, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const updateMemberRoles = `-- name: UpdateMemberRoles :one UPDATE organization_members diff --git a/coderd/database/queries/organizationmembers.sql b/coderd/database/queries/organizationmembers.sql index 8685e71129ac9..a92cd681eabf6 100644 --- a/coderd/database/queries/organizationmembers.sql +++ b/coderd/database/queries/organizationmembers.sql @@ -66,3 +66,26 @@ WHERE user_id = @user_id AND organization_id = @org_id RETURNING *; + +-- name: PaginatedOrganizationMembers :many +SELECT + sqlc.embed(organization_members), + users.username, users.avatar_url, users.name, users.email, users.rbac_roles as "global_roles", + COUNT(*) OVER() AS count +FROM + organization_members + INNER JOIN + users ON organization_members.user_id = users.id AND users.deleted = false +WHERE + -- Filter by organization id + CASE + WHEN @organization_id :: uuid != '00000000-0000-0000-0000-000000000000'::uuid THEN + organization_id = @organization_id + ELSE true + END +ORDER BY + -- Deterministic and consistent ordering of all users. This is to ensure consistent pagination. + LOWER(username) ASC OFFSET @offset_opt +LIMIT + -- A null limit means "no limit", so 0 means return all + NULLIF(@limit_opt :: int, 0); diff --git a/coderd/members.go b/coderd/members.go index c89b4c9c09c1a..1852e6448408f 100644 --- a/coderd/members.go +++ b/coderd/members.go @@ -142,6 +142,7 @@ func (api *API) deleteOrganizationMember(rw http.ResponseWriter, r *http.Request rw.WriteHeader(http.StatusNoContent) } +// @Deprecated use /organizations/{organization}/paginated-members [get] // @Summary List organization members // @ID list-organization-members // @Security CoderSessionToken @@ -178,6 +179,66 @@ func (api *API) listMembers(rw http.ResponseWriter, r *http.Request) { httpapi.Write(ctx, rw, http.StatusOK, resp) } +// @Summary Paginated organization members +// @ID paginated-organization-members +// @Security CoderSessionToken +// @Produce json +// @Tags Members +// @Param organization path string true "Organization ID" +// @Param limit query int false "Page limit, if 0 returns all members" +// @Param offset query int false "Page offset" +// @Success 200 {object} []codersdk.PaginatedMembersResponse +// @Router /organizations/{organization}/paginated-members [get] +func (api *API) paginatedMembers(rw http.ResponseWriter, r *http.Request) { + var ( + ctx = r.Context() + organization = httpmw.OrganizationParam(r) + paginationParams, ok = parsePagination(rw, r) + ) + if !ok { + return + } + + paginatedMemberRows, err := api.Database.PaginatedOrganizationMembers(ctx, database.PaginatedOrganizationMembersParams{ + OrganizationID: organization.ID, + LimitOpt: int32(paginationParams.Limit), + OffsetOpt: int32(paginationParams.Offset), + }) + if httpapi.Is404Error(err) { + httpapi.ResourceNotFound(rw) + return + } + if err != nil { + httpapi.InternalServerError(rw, err) + return + } + + memberRows := make([]database.OrganizationMembersRow, 0) + for _, pRow := range paginatedMemberRows { + row := database.OrganizationMembersRow{ + OrganizationMember: pRow.OrganizationMember, + Username: pRow.Username, + AvatarURL: pRow.AvatarURL, + Name: pRow.Name, + Email: pRow.Email, + GlobalRoles: pRow.GlobalRoles, + } + + memberRows = append(memberRows, row) + } + + members, err := convertOrganizationMembersWithUserData(ctx, api.Database, memberRows) + if err != nil { + httpapi.InternalServerError(rw, err) + } + + resp := codersdk.PaginatedMembersResponse{ + Members: members, + Count: int(paginatedMemberRows[0].Count), + } + httpapi.Write(ctx, rw, http.StatusOK, resp) +} + // @Summary Assign role to organization member // @ID assign-role-to-organization-member // @Security CoderSessionToken diff --git a/codersdk/organizations.go b/codersdk/organizations.go index 781baaaa5d5d6..e093f6f85594a 100644 --- a/codersdk/organizations.go +++ b/codersdk/organizations.go @@ -81,6 +81,17 @@ type OrganizationMemberWithUserData struct { OrganizationMember `table:"m,recursive_inline"` } +type PaginatedMembersRequest struct { + OrganizationID uuid.UUID `table:"organization id" json:"organization_id" format:"uuid"` + Limit int `json:"limit,omitempty"` + Offset int `json:"offset,omitempty"` +} + +type PaginatedMembersResponse struct { + Members []OrganizationMemberWithUserData + Count int `json:"count"` +} + type CreateOrganizationRequest struct { Name string `json:"name" validate:"required,organization_name"` // DisplayName will default to the same value as `Name` if not provided. diff --git a/docs/reference/api/members.md b/docs/reference/api/members.md index 5dc39cee2d088..fd075f9f0d550 100644 --- a/docs/reference/api/members.md +++ b/docs/reference/api/members.md @@ -813,6 +813,96 @@ curl -X PUT http://coder-server:8080/api/v2/organizations/{organization}/members To perform this operation, you must be authenticated. [Learn more](authentication.md). +## Paginated organization members + +### Code samples + +```shell +# Example request using curl +curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/paginated-members \ + -H 'Accept: application/json' \ + -H 'Coder-Session-Token: API_KEY' +``` + +`GET /organizations/{organization}/paginated-members` + +### Parameters + +| Name | In | Type | Required | Description | +|----------------|-------|---------|----------|--------------------------------------| +| `organization` | path | string | true | Organization ID | +| `limit` | query | integer | false | Page limit, if 0 returns all members | +| `offset` | query | integer | false | Page offset | + +### Example responses + +> 200 Response + +```json +[ + { + "count": 0, + "members": [ + { + "avatar_url": "string", + "created_at": "2019-08-24T14:15:22Z", + "email": "string", + "global_roles": [ + { + "display_name": "string", + "name": "string", + "organization_id": "string" + } + ], + "name": "string", + "organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6", + "roles": [ + { + "display_name": "string", + "name": "string", + "organization_id": "string" + } + ], + "updated_at": "2019-08-24T14:15:22Z", + "user_id": "a169451c-8525-4352-b8ca-070dd449a1a5", + "username": "string" + } + ] + } +] +``` + +### Responses + +| Status | Meaning | Description | Schema | +|--------|---------------------------------------------------------|-------------|-------------------------------------------------------------------------------------------| +| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | array of [codersdk.PaginatedMembersResponse](schemas.md#codersdkpaginatedmembersresponse) | + +

Response Schema

+ +Status Code **200** + +| Name | Type | Required | Restrictions | Description | +|-----------------------|-------------------|----------|--------------|-------------| +| `[array item]` | array | false | | | +| `» count` | integer | false | | | +| `» members` | array | false | | | +| `»» avatar_url` | string | false | | | +| `»» created_at` | string(date-time) | false | | | +| `»» email` | string | false | | | +| `»» global_roles` | array | false | | | +| `»»» display_name` | string | false | | | +| `»»» name` | string | false | | | +| `»»» organization_id` | string | false | | | +| `»» name` | string | false | | | +| `»» organization_id` | string(uuid) | false | | | +| `»» roles` | array | false | | | +| `»» updated_at` | string(date-time) | false | | | +| `»» user_id` | string(uuid) | false | | | +| `»» username` | string | false | | | + +To perform this operation, you must be authenticated. [Learn more](authentication.md). + ## Get site member roles ### Code samples diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md index 9fa22af7356ae..42ef8a7ade184 100644 --- a/docs/reference/api/schemas.md +++ b/docs/reference/api/schemas.md @@ -4189,6 +4189,47 @@ Git clone makes use of this by parsing the URL from: 'Username for "https://gith | » `[any property]` | array of string | false | | | | `organization_assign_default` | boolean | false | | Organization assign default will ensure the default org is always included for every user, regardless of their claims. This preserves legacy behavior. | +## codersdk.PaginatedMembersResponse + +```json +{ + "count": 0, + "members": [ + { + "avatar_url": "string", + "created_at": "2019-08-24T14:15:22Z", + "email": "string", + "global_roles": [ + { + "display_name": "string", + "name": "string", + "organization_id": "string" + } + ], + "name": "string", + "organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6", + "roles": [ + { + "display_name": "string", + "name": "string", + "organization_id": "string" + } + ], + "updated_at": "2019-08-24T14:15:22Z", + "user_id": "a169451c-8525-4352-b8ca-070dd449a1a5", + "username": "string" + } + ] +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +|-----------|---------------------------------------------------------------------------------------------|----------|--------------|-------------| +| `count` | integer | false | | | +| `members` | array of [codersdk.OrganizationMemberWithUserData](#codersdkorganizationmemberwithuserdata) | false | | | + ## codersdk.PatchGroupIDPSyncConfigRequest ```json diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 222c07575b969..6fdfb5ea9d9a1 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -1484,6 +1484,19 @@ export interface OrganizationSyncSettings { readonly organization_assign_default: boolean; } +// From codersdk/organizations.go +export interface PaginatedMembersRequest { + readonly organization_id: string; + readonly limit?: number; + readonly offset?: number; +} + +// From codersdk/organizations.go +export interface PaginatedMembersResponse { + readonly Members: readonly OrganizationMemberWithUserData[]; + readonly count: number; +} + // From codersdk/pagination.go export interface Pagination { readonly after_id?: string; pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy