From 98b48d60a4338175282b17403b995284472ddc2c Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Mon, 10 Mar 2025 20:30:33 +0000 Subject: [PATCH 01/10] feat: implement pagination for org members table --- site/src/api/api.ts | 17 ++ site/src/api/queries/organizations.ts | 22 +++ .../OrganizationMembersPage.tsx | 20 ++- .../OrganizationMembersPageView.tsx | 164 ++++++++++-------- 4 files changed, 146 insertions(+), 77 deletions(-) diff --git a/site/src/api/api.ts b/site/src/api/api.ts index 627ede80976c6..d9142dfa3612f 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -583,6 +583,23 @@ class ApiMethods { return response.data; }; + /** + * @param organization Can be the organization's ID or name + * @param options Pagination options + */ + getOrganizationPaginatedMembers = async ( + organization: string, + options?: TypesGen.Pagination + ) => { + const url = getURLWithSearchParams( + `/api/v2/organizations/${organization}/paginated-members`, + options + ); + const response = await this.axios.get(url); + + return response.data; + }; + /** * @param organization Can be the organization's ID or name */ diff --git a/site/src/api/queries/organizations.ts b/site/src/api/queries/organizations.ts index bca0bc6a72fff..23dac6ae66d78 100644 --- a/site/src/api/queries/organizations.ts +++ b/site/src/api/queries/organizations.ts @@ -2,9 +2,11 @@ import { API } from "api/api"; import type { CreateOrganizationRequest, GroupSyncSettings, + PaginatedMembersResponse, RoleSyncSettings, UpdateOrganizationRequest, } from "api/typesGenerated"; +import type { UsePaginatedQueryOptions } from "hooks/usePaginatedQuery"; import { type OrganizationPermissionName, type OrganizationPermissions, @@ -59,6 +61,26 @@ export const organizationMembersKey = (id: string) => [ "members", ]; +export function paginatedOrganizationMembers( + organizationName: string, + searchParams: URLSearchParams +): UsePaginatedQueryOptions { + return { + searchParams, + queryPayload: (params) => { + return { + limit: params.limit, + offset: params.offset, + }; + }, + queryKey: ({ payload }) => [ + ...organizationMembersKey(organizationName), + payload, + ], + queryFn: ({ payload }) => API.getOrganizationPaginatedMembers(organizationName, payload), + }; +} + export const organizationMembers = (id: string) => { return { queryFn: () => API.getOrganizationMembers(id), diff --git a/site/src/pages/OrganizationSettingsPage/OrganizationMembersPage.tsx b/site/src/pages/OrganizationSettingsPage/OrganizationMembersPage.tsx index ffa7b08b83742..69b83d32f25cf 100644 --- a/site/src/pages/OrganizationSettingsPage/OrganizationMembersPage.tsx +++ b/site/src/pages/OrganizationSettingsPage/OrganizationMembersPage.tsx @@ -3,7 +3,7 @@ import { getErrorMessage } from "api/errors"; import { groupsByUserIdInOrganization } from "api/queries/groups"; import { addOrganizationMember, - organizationMembers, + paginatedOrganizationMembers, removeOrganizationMember, updateOrganizationMemberRoles, } from "api/queries/organizations"; @@ -14,12 +14,13 @@ import { EmptyState } from "components/EmptyState/EmptyState"; import { displayError, displaySuccess } from "components/GlobalSnackbar/utils"; import { Stack } from "components/Stack/Stack"; import { useAuthenticated } from "contexts/auth/RequireAuth"; +import { usePaginatedQuery } from "hooks/usePaginatedQuery"; import { useOrganizationSettings } from "modules/management/OrganizationSettingsLayout"; import { RequirePermission } from "modules/permissions/RequirePermission"; import { type FC, useState } from "react"; import { Helmet } from "react-helmet-async"; import { useMutation, useQuery, useQueryClient } from "react-query"; -import { useParams } from "react-router-dom"; +import { useParams, useSearchParams } from "react-router-dom"; import { pageTitle } from "utils/page"; import { OrganizationMembersPageView } from "./OrganizationMembersPageView"; @@ -30,14 +31,18 @@ const OrganizationMembersPage: FC = () => { organization: string; }; const { organization, organizationPermissions } = useOrganizationSettings(); + const searchParamsResult = useSearchParams(); - const membersQuery = useQuery(organizationMembers(organizationName)); const organizationRolesQuery = useQuery(organizationRoles(organizationName)); const groupsByUserIdQuery = useQuery( groupsByUserIdInOrganization(organizationName), ); - const members = membersQuery.data?.map((member) => { + const membersQuery = usePaginatedQuery( + paginatedOrganizationMembers(organizationName, searchParamsResult[0]) + ); + + const members = membersQuery.data?.Members.map((member: OrganizationMemberWithUserData) => { const groups = groupsByUserIdQuery.data?.get(member.user_id) ?? []; return { ...member, groups }; }); @@ -76,6 +81,11 @@ const OrganizationMembersPage: FC = () => { ); } + const isLoading = + membersQuery.isLoading || + organizationRolesQuery.isLoading || + groupsByUserIdQuery.isLoading; + return ( <> {helmet} @@ -95,6 +105,8 @@ const OrganizationMembersPage: FC = () => { isUpdatingMemberRoles={updateMemberRolesMutation.isLoading} me={me} members={members} + isLoading={isLoading} + membersQuery={membersQuery} addMember={async (user: User) => { await addMemberMutation.mutateAsync(user.id); void membersQuery.refetch(); diff --git a/site/src/pages/OrganizationSettingsPage/OrganizationMembersPageView.tsx b/site/src/pages/OrganizationSettingsPage/OrganizationMembersPageView.tsx index 743e8a9381e15..95415f975efb7 100644 --- a/site/src/pages/OrganizationSettingsPage/OrganizationMembersPageView.tsx +++ b/site/src/pages/OrganizationSettingsPage/OrganizationMembersPageView.tsx @@ -18,7 +18,9 @@ import { MoreMenuTrigger, ThreeDotsButton, } from "components/MoreMenu/MoreMenu"; +import { PaginationContainer } from "components/PaginationWidget/PaginationContainer"; import { SettingsHeader } from "components/SettingsHeader/SettingsHeader"; +import { Loader } from "components/Loader/Loader"; import { Stack } from "components/Stack/Stack"; import { Table, @@ -28,6 +30,7 @@ import { TableRow, } from "components/Table/Table"; import { UserAutocomplete } from "components/UserAutocomplete/UserAutocomplete"; +import type { PaginationResultInfo } from "hooks/usePaginatedQuery"; import { TriangleAlert } from "lucide-react"; import { UserGroupsCell } from "pages/UsersPage/UsersTable/UserGroupsCell"; import { type FC, useState } from "react"; @@ -41,8 +44,12 @@ interface OrganizationMembersPageViewProps { error: unknown; isAddingMember: boolean; isUpdatingMemberRoles: boolean; + isLoading: boolean; me: User; members: Array | undefined; + membersQuery: PaginationResultInfo & { + isPreviousData: boolean; + }; addMember: (user: User) => Promise; removeMember: (member: OrganizationMemberWithUserData) => void; updateMemberRoles: ( @@ -64,8 +71,10 @@ export const OrganizationMembersPageView: FC< error, isAddingMember, isUpdatingMemberRoles, + isLoading, me, members, + membersQuery, addMember, removeMember, updateMemberRoles, @@ -92,80 +101,89 @@ export const OrganizationMembersPageView: FC< )} - - - - User - - - Roles - - - - - - Groups - - - - - - - - {members?.map((member) => ( - - - + ) : ( + +
+ + + User + + + Roles + + + + + + Groups + + + + + + + + {members?.map((member) => ( + + + + } + title={member.name || member.username} + subtitle={member.email} /> - } - title={member.name || member.username} - subtitle={member.email} - /> - - { - try { - await updateMemberRoles(member, roles); - displaySuccess("Roles updated successfully."); - } catch (error) { - displayError( - getErrorMessage(error, "Failed to update roles."), - ); - } - }} - /> - - - {member.user_id !== me.id && canEditMembers && ( - - - - - - removeMember(member)} - > - Remove - - - - )} - - - ))} - -
+ + { + try { + await updateMemberRoles(member, roles); + displaySuccess("Roles updated successfully."); + } catch (error) { + displayError( + getErrorMessage(error, "Failed to update roles."), + ); + } + }} + /> + + + {member.user_id !== me.id && canEditMembers && ( + + + + + + removeMember(member)} + > + Remove + + + + )} + + + ))} + + + + )} ); From 54a90679e4696a682210dbc33bb5a126e0de1aa0 Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Tue, 11 Mar 2025 15:18:41 +0000 Subject: [PATCH 02/10] fix: use new endpoint in Autocomplete component --- codersdk/organizations.go | 9 ++++----- site/src/api/api.ts | 7 ++++--- site/src/api/queries/organizations.ts | 14 ++++++++------ site/src/api/typesGenerated.ts | 3 +-- .../UserAutocomplete/UserAutocomplete.tsx | 13 ++++++++----- .../OrganizationMembersPage.tsx | 16 +++++++++------- .../OrganizationMembersPageView.tsx | 2 +- 7 files changed, 35 insertions(+), 29 deletions(-) diff --git a/codersdk/organizations.go b/codersdk/organizations.go index e093f6f85594a..8a028d46e098c 100644 --- a/codersdk/organizations.go +++ b/codersdk/organizations.go @@ -82,14 +82,13 @@ type OrganizationMemberWithUserData struct { } type PaginatedMembersRequest struct { - OrganizationID uuid.UUID `table:"organization id" json:"organization_id" format:"uuid"` - Limit int `json:"limit,omitempty"` - Offset int `json:"offset,omitempty"` + Limit int `json:"limit,omitempty"` + Offset int `json:"offset,omitempty"` } type PaginatedMembersResponse struct { - Members []OrganizationMemberWithUserData - Count int `json:"count"` + Members []OrganizationMemberWithUserData `json:"members"` + Count int `json:"count"` } type CreateOrganizationRequest struct { diff --git a/site/src/api/api.ts b/site/src/api/api.ts index d9142dfa3612f..b6012335f93d8 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -589,13 +589,14 @@ class ApiMethods { */ getOrganizationPaginatedMembers = async ( organization: string, - options?: TypesGen.Pagination + options?: TypesGen.Pagination, ) => { const url = getURLWithSearchParams( `/api/v2/organizations/${organization}/paginated-members`, - options + options, ); - const response = await this.axios.get(url); + const response = + await this.axios.get(url); return response.data; }; diff --git a/site/src/api/queries/organizations.ts b/site/src/api/queries/organizations.ts index 23dac6ae66d78..798430c173912 100644 --- a/site/src/api/queries/organizations.ts +++ b/site/src/api/queries/organizations.ts @@ -2,6 +2,7 @@ import { API } from "api/api"; import type { CreateOrganizationRequest, GroupSyncSettings, + PaginatedMembersRequest, PaginatedMembersResponse, RoleSyncSettings, UpdateOrganizationRequest, @@ -63,21 +64,22 @@ export const organizationMembersKey = (id: string) => [ export function paginatedOrganizationMembers( organizationName: string, - searchParams: URLSearchParams -): UsePaginatedQueryOptions { + searchParams: URLSearchParams, +): UsePaginatedQueryOptions { return { searchParams, - queryPayload: (params) => { + queryPayload: ({ limit, offset }) => { return { - limit: params.limit, - offset: params.offset, + limit: limit, + offset: offset, }; }, queryKey: ({ payload }) => [ ...organizationMembersKey(organizationName), payload, ], - queryFn: ({ payload }) => API.getOrganizationPaginatedMembers(organizationName, payload), + queryFn: ({ payload }) => + API.getOrganizationPaginatedMembers(organizationName, payload), }; } diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 6fdfb5ea9d9a1..cd993e61db94a 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -1486,14 +1486,13 @@ export interface OrganizationSyncSettings { // 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 members: readonly OrganizationMemberWithUserData[]; readonly count: number; } diff --git a/site/src/components/UserAutocomplete/UserAutocomplete.tsx b/site/src/components/UserAutocomplete/UserAutocomplete.tsx index f5bfd109c4a5c..88ce8a6ff9795 100644 --- a/site/src/components/UserAutocomplete/UserAutocomplete.tsx +++ b/site/src/components/UserAutocomplete/UserAutocomplete.tsx @@ -3,12 +3,13 @@ import Autocomplete from "@mui/material/Autocomplete"; import CircularProgress from "@mui/material/CircularProgress"; import TextField from "@mui/material/TextField"; import { getErrorMessage } from "api/errors"; -import { organizationMembers } from "api/queries/organizations"; +import { paginatedOrganizationMembers } from "api/queries/organizations"; import { users } from "api/queries/users"; import type { OrganizationMemberWithUserData, User } from "api/typesGenerated"; import { Avatar } from "components/Avatar/Avatar"; import { AvatarData } from "components/Avatar/AvatarData"; import { useDebouncedFunction } from "hooks/debounce"; +import { usePaginatedQuery } from "hooks/usePaginatedQuery"; import { type ChangeEvent, type ComponentProps, @@ -69,18 +70,20 @@ export const MemberAutocomplete: FC = ({ }) => { const [filter, setFilter] = useState(); + const searchParams = new URLSearchParams(); + searchParams.append("limit", "0"); + // Currently this queries all members, as there is no pagination. - const membersQuery = useQuery({ - ...organizationMembers(organizationId), + const membersQuery = usePaginatedQuery({ + ...paginatedOrganizationMembers(organizationId, searchParams), enabled: filter !== undefined, - keepPreviousData: true, }); return ( error={membersQuery.error} isFetching={membersQuery.isFetching} setFilter={setFilter} - users={membersQuery.data} + users={membersQuery.data?.members} {...props} /> ); diff --git a/site/src/pages/OrganizationSettingsPage/OrganizationMembersPage.tsx b/site/src/pages/OrganizationSettingsPage/OrganizationMembersPage.tsx index 69b83d32f25cf..40fe42e5d98c8 100644 --- a/site/src/pages/OrganizationSettingsPage/OrganizationMembersPage.tsx +++ b/site/src/pages/OrganizationSettingsPage/OrganizationMembersPage.tsx @@ -39,13 +39,15 @@ const OrganizationMembersPage: FC = () => { ); const membersQuery = usePaginatedQuery( - paginatedOrganizationMembers(organizationName, searchParamsResult[0]) + paginatedOrganizationMembers(organizationName, searchParamsResult[0]), ); - const members = membersQuery.data?.Members.map((member: OrganizationMemberWithUserData) => { - const groups = groupsByUserIdQuery.data?.get(member.user_id) ?? []; - return { ...member, groups }; - }); + const members = membersQuery.data?.members.map( + (member: OrganizationMemberWithUserData) => { + const groups = groupsByUserIdQuery.data?.get(member.user_id) ?? []; + return { ...member, groups }; + }, + ); const addMemberMutation = useMutation( addOrganizationMember(queryClient, organizationName), @@ -81,8 +83,8 @@ const OrganizationMembersPage: FC = () => { ); } - const isLoading = - membersQuery.isLoading || + const isLoading = + membersQuery.isLoading || organizationRolesQuery.isLoading || groupsByUserIdQuery.isLoading; diff --git a/site/src/pages/OrganizationSettingsPage/OrganizationMembersPageView.tsx b/site/src/pages/OrganizationSettingsPage/OrganizationMembersPageView.tsx index 95415f975efb7..1b85583e8c374 100644 --- a/site/src/pages/OrganizationSettingsPage/OrganizationMembersPageView.tsx +++ b/site/src/pages/OrganizationSettingsPage/OrganizationMembersPageView.tsx @@ -11,6 +11,7 @@ import { ErrorAlert } from "components/Alert/ErrorAlert"; import { Avatar } from "components/Avatar/Avatar"; import { AvatarData } from "components/Avatar/AvatarData"; import { displayError, displaySuccess } from "components/GlobalSnackbar/utils"; +import { Loader } from "components/Loader/Loader"; import { MoreMenu, MoreMenuContent, @@ -20,7 +21,6 @@ import { } from "components/MoreMenu/MoreMenu"; import { PaginationContainer } from "components/PaginationWidget/PaginationContainer"; import { SettingsHeader } from "components/SettingsHeader/SettingsHeader"; -import { Loader } from "components/Loader/Loader"; import { Stack } from "components/Stack/Stack"; import { Table, From 1f6b13c7d9e3ccc29ce6721a87d1b88cf2ad06b9 Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Tue, 11 Mar 2025 15:21:38 +0000 Subject: [PATCH 03/10] fix: remove unused code --- site/src/api/queries/organizations.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/site/src/api/queries/organizations.ts b/site/src/api/queries/organizations.ts index 798430c173912..0c988a7932abc 100644 --- a/site/src/api/queries/organizations.ts +++ b/site/src/api/queries/organizations.ts @@ -83,13 +83,6 @@ export function paginatedOrganizationMembers( }; } -export const organizationMembers = (id: string) => { - return { - queryFn: () => API.getOrganizationMembers(id), - queryKey: organizationMembersKey(id), - }; -}; - export const addOrganizationMember = (queryClient: QueryClient, id: string) => { return { mutationFn: (userId: string) => { From cfd11f6f945c9bdba8d8ac0e3a14d726538f6faf Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Wed, 12 Mar 2025 14:07:49 +0000 Subject: [PATCH 04/10] feat: create un-paginated version of query --- site/src/api/queries/organizations.ts | 38 ++++++++++++++----- .../UserAutocomplete/UserAutocomplete.tsx | 14 +++---- 2 files changed, 35 insertions(+), 17 deletions(-) diff --git a/site/src/api/queries/organizations.ts b/site/src/api/queries/organizations.ts index 0c988a7932abc..2dc0402d75484 100644 --- a/site/src/api/queries/organizations.ts +++ b/site/src/api/queries/organizations.ts @@ -62,10 +62,32 @@ export const organizationMembersKey = (id: string) => [ "members", ]; -export function paginatedOrganizationMembers( - organizationName: string, +/** + * Creates a query configuration to fetch all members of an organization. + * + * Unlike the paginated version, this function sets the `limit` parameter to 0, + * which instructs the API to return all organization members in a single request + * without pagination. + * + * @param id - The unique identifier of the organization + * @returns A query configuration object for use with React Query + * + * @see paginatedOrganizationMembers - For fetching members with pagination support + */ +export const organizationMembers = (id: string) => { + return { + queryFn: () => API.getOrganizationPaginatedMembers(id, { limit: 0 }), + queryKey: organizationMembersKey(id), + }; +}; + +export const paginatedOrganizationMembers = ( + id: string, searchParams: URLSearchParams, -): UsePaginatedQueryOptions { +): UsePaginatedQueryOptions< + PaginatedMembersResponse, + PaginatedMembersRequest +> => { return { searchParams, queryPayload: ({ limit, offset }) => { @@ -74,14 +96,10 @@ export function paginatedOrganizationMembers( offset: offset, }; }, - queryKey: ({ payload }) => [ - ...organizationMembersKey(organizationName), - payload, - ], - queryFn: ({ payload }) => - API.getOrganizationPaginatedMembers(organizationName, payload), + queryKey: ({ payload }) => [...organizationMembersKey(id), payload], + queryFn: ({ payload }) => API.getOrganizationPaginatedMembers(id, payload), }; -} +}; export const addOrganizationMember = (queryClient: QueryClient, id: string) => { return { diff --git a/site/src/components/UserAutocomplete/UserAutocomplete.tsx b/site/src/components/UserAutocomplete/UserAutocomplete.tsx index 88ce8a6ff9795..1fe997bdc5bbf 100644 --- a/site/src/components/UserAutocomplete/UserAutocomplete.tsx +++ b/site/src/components/UserAutocomplete/UserAutocomplete.tsx @@ -3,7 +3,10 @@ import Autocomplete from "@mui/material/Autocomplete"; import CircularProgress from "@mui/material/CircularProgress"; import TextField from "@mui/material/TextField"; import { getErrorMessage } from "api/errors"; -import { paginatedOrganizationMembers } from "api/queries/organizations"; +import { + organizationMembers, + paginatedOrganizationMembers, +} from "api/queries/organizations"; import { users } from "api/queries/users"; import type { OrganizationMemberWithUserData, User } from "api/typesGenerated"; import { Avatar } from "components/Avatar/Avatar"; @@ -70,13 +73,10 @@ export const MemberAutocomplete: FC = ({ }) => { const [filter, setFilter] = useState(); - const searchParams = new URLSearchParams(); - searchParams.append("limit", "0"); - - // Currently this queries all members, as there is no pagination. - const membersQuery = usePaginatedQuery({ - ...paginatedOrganizationMembers(organizationId, searchParams), + const membersQuery = useQuery({ + ...organizationMembers(organizationId), enabled: filter !== undefined, + keepPreviousData: true, }); return ( From fb15b46de776a6cce134a71469016407d22ba648 Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Wed, 12 Mar 2025 14:40:24 +0000 Subject: [PATCH 05/10] wip --- site/src/components/UserAutocomplete/UserAutocomplete.tsx | 6 +----- .../OrganizationMembersPage.test.tsx | 4 ++-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/site/src/components/UserAutocomplete/UserAutocomplete.tsx b/site/src/components/UserAutocomplete/UserAutocomplete.tsx index 1fe997bdc5bbf..e375116cd2d22 100644 --- a/site/src/components/UserAutocomplete/UserAutocomplete.tsx +++ b/site/src/components/UserAutocomplete/UserAutocomplete.tsx @@ -3,16 +3,12 @@ import Autocomplete from "@mui/material/Autocomplete"; import CircularProgress from "@mui/material/CircularProgress"; import TextField from "@mui/material/TextField"; import { getErrorMessage } from "api/errors"; -import { - organizationMembers, - paginatedOrganizationMembers, -} from "api/queries/organizations"; +import { organizationMembers } from "api/queries/organizations"; import { users } from "api/queries/users"; import type { OrganizationMemberWithUserData, User } from "api/typesGenerated"; import { Avatar } from "components/Avatar/Avatar"; import { AvatarData } from "components/Avatar/AvatarData"; import { useDebouncedFunction } from "hooks/debounce"; -import { usePaginatedQuery } from "hooks/usePaginatedQuery"; import { type ChangeEvent, type ComponentProps, diff --git a/site/src/pages/OrganizationSettingsPage/OrganizationMembersPage.test.tsx b/site/src/pages/OrganizationSettingsPage/OrganizationMembersPage.test.tsx index 1270f78484dc7..f828969238cec 100644 --- a/site/src/pages/OrganizationSettingsPage/OrganizationMembersPage.test.tsx +++ b/site/src/pages/OrganizationSettingsPage/OrganizationMembersPage.test.tsx @@ -38,8 +38,8 @@ beforeEach(() => { const renderPage = async () => { renderWithOrganizationSettingsLayout(, { - route: `/organizations/${MockOrganization.name}/members`, - path: "/organizations/:organization/members", + route: `/organizations/${MockOrganization.name}/paginated-members`, + path: "/organizations/:organization/paginated-members", }); await waitForLoaderToBeRemoved(); }; From 26ae355890c1872cdd0efbc54d044212688f3595 Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Wed, 12 Mar 2025 15:24:29 +0000 Subject: [PATCH 06/10] fix: mock paginated response --- site/src/testHelpers/handlers.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/site/src/testHelpers/handlers.ts b/site/src/testHelpers/handlers.ts index 7fbd14147af83..cde141ac90e37 100644 --- a/site/src/testHelpers/handlers.ts +++ b/site/src/testHelpers/handlers.ts @@ -70,6 +70,12 @@ export const handlers = [ M.MockOrganizationMember2, ]); }), + http.get("/api/v2/organizations/:organizationId/paginated-members", () => { + return HttpResponse.json({ + members: [M.MockOrganizationMember, M.MockOrganizationMember2], + count: 2, + }); + }), http.delete( "/api/v2/organizations/:organizationId/members/:userId", async () => { From 4ed07c4651d377b3bd672d6d4c5f7a6124372dfc Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Wed, 12 Mar 2025 15:25:57 +0000 Subject: [PATCH 07/10] fix: remove unused mock --- site/src/testHelpers/handlers.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/site/src/testHelpers/handlers.ts b/site/src/testHelpers/handlers.ts index cde141ac90e37..79bc116891bf9 100644 --- a/site/src/testHelpers/handlers.ts +++ b/site/src/testHelpers/handlers.ts @@ -64,12 +64,6 @@ export const handlers = [ M.MockOrganizationAuditorRole, ]); }), - http.get("/api/v2/organizations/:organizationId/members", () => { - return HttpResponse.json([ - M.MockOrganizationMember, - M.MockOrganizationMember2, - ]); - }), http.get("/api/v2/organizations/:organizationId/paginated-members", () => { return HttpResponse.json({ members: [M.MockOrganizationMember, M.MockOrganizationMember2], From f07f92ec722834fc8d29becd808f4eea8f5ecd26 Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Thu, 13 Mar 2025 16:39:16 +0000 Subject: [PATCH 08/10] fix: return the correct users in dbmem query --- coderd/database/dbmem/dbmem.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index 63ee1d0bd95e7..1ece2571f4960 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -9596,7 +9596,7 @@ func (q *FakeQuerier) PaginatedOrganizationMembers(_ context.Context, arg databa // 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 { + if mem.OrganizationID != arg.OrganizationID { continue } @@ -9606,7 +9606,7 @@ func (q *FakeQuerier) PaginatedOrganizationMembers(_ context.Context, arg databa selectedMembers := make([]database.PaginatedOrganizationMembersRow, 0) skippedMembers := 0 - for _, organizationMember := range q.organizationMembers { + for _, organizationMember := range orgMembers { if skippedMembers < int(arg.OffsetOpt) { skippedMembers++ continue From 5a58c1e87a7bbe61b9236a140b8f1176281a8f9a Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Thu, 13 Mar 2025 18:13:55 +0000 Subject: [PATCH 09/10] fix: update stories for new pagination related args --- .../OrganizationMembersPageView.stories.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/site/src/pages/OrganizationSettingsPage/OrganizationMembersPageView.stories.tsx b/site/src/pages/OrganizationSettingsPage/OrganizationMembersPageView.stories.tsx index f3427bd58775d..6e096cfbb8fec 100644 --- a/site/src/pages/OrganizationSettingsPage/OrganizationMembersPageView.stories.tsx +++ b/site/src/pages/OrganizationSettingsPage/OrganizationMembersPageView.stories.tsx @@ -5,6 +5,8 @@ import { MockUser, } from "testHelpers/entities"; import { OrganizationMembersPageView } from "./OrganizationMembersPageView"; +import { UsePaginatedQueryResult } from "hooks/usePaginatedQuery"; +import { mockSuccessResult } from "components/PaginationWidget/PaginationContainer.mocks"; const meta: Meta = { title: "pages/OrganizationMembersPageView", @@ -14,11 +16,16 @@ const meta: Meta = { error: undefined, isAddingMember: false, isUpdatingMemberRoles: false, + canViewMembers: true, me: MockUser, members: [ { ...MockOrganizationMember, groups: [] }, { ...MockOrganizationMember2, groups: [] }, ], + membersQuery: { + ...mockSuccessResult, + totalRecords: 2, + } as UsePaginatedQueryResult, addMember: () => Promise.resolve(), removeMember: () => Promise.resolve(), updateMemberRoles: () => Promise.resolve(), From d0a15e56a8dce4f29a4ea2fba4e0abd95b1712bd Mon Sep 17 00:00:00 2001 From: Brett Kolodny Date: Thu, 13 Mar 2025 18:18:44 +0000 Subject: [PATCH 10/10] fix: fmt --- .../OrganizationMembersPageView.stories.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/site/src/pages/OrganizationSettingsPage/OrganizationMembersPageView.stories.tsx b/site/src/pages/OrganizationSettingsPage/OrganizationMembersPageView.stories.tsx index 6e096cfbb8fec..1c2f2c6e804a3 100644 --- a/site/src/pages/OrganizationSettingsPage/OrganizationMembersPageView.stories.tsx +++ b/site/src/pages/OrganizationSettingsPage/OrganizationMembersPageView.stories.tsx @@ -1,12 +1,12 @@ import type { Meta, StoryObj } from "@storybook/react"; +import { mockSuccessResult } from "components/PaginationWidget/PaginationContainer.mocks"; +import type { UsePaginatedQueryResult } from "hooks/usePaginatedQuery"; import { MockOrganizationMember, MockOrganizationMember2, MockUser, } from "testHelpers/entities"; import { OrganizationMembersPageView } from "./OrganizationMembersPageView"; -import { UsePaginatedQueryResult } from "hooks/usePaginatedQuery"; -import { mockSuccessResult } from "components/PaginationWidget/PaginationContainer.mocks"; const meta: Meta = { title: "pages/OrganizationMembersPageView",