Skip to content

chore(site): remove users and pagination services #9932

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Oct 2, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Move users loading to react-query
  • Loading branch information
BrunoQuaresma committed Sep 28, 2023
commit cda4b4f96149d769d49276ec96c9e8a368047c3c
9 changes: 8 additions & 1 deletion site/src/api/queries/users.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import * as API from "api/api";
import { UpdateUserPasswordRequest } from "api/typesGenerated";
import { UpdateUserPasswordRequest, UsersRequest } from "api/typesGenerated";

export const users = (req: UsersRequest) => {
return {
queryKey: ["users", req],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we really be using req as a key here? not req.id or something?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Req is basically the page, offset and filter params so in this case I think makes sense to use them together. Reference: https://tanstack.com/query/v4/docs/react/guides/query-keys#query-keys-are-hashed-deterministically

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's not always needed, but I've seen the pattern where the payload for the query is treated as the last argument of the query key. It also makes it possible for the query function to access it via the queryKey property on its options argument, even if you extract the query function outside the hook

queryFn: () => API.getUsers(req),
};
};

export const updatePassword = () => {
return {
Expand Down
2 changes: 2 additions & 0 deletions site/src/hooks/usePagination.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export const usePagination = ({
const [searchParams, setSearchParams] = searchParamsResult;
const page = searchParams.get("page") ? Number(searchParams.get("page")) : 1;
const limit = DEFAULT_RECORDS_PER_PAGE;
const offset = page <= 0 ? 0 : (page - 1) * limit;

const goToPage = (page: number) => {
searchParams.set("page", page.toString());
Expand All @@ -19,5 +20,6 @@ export const usePagination = ({
page,
limit,
goToPage,
offset,
};
};
57 changes: 25 additions & 32 deletions site/src/pages/UsersPage/UsersPage.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import { useMachine } from "@xstate/react";
import { User } from "api/typesGenerated";
import { DeleteDialog } from "components/Dialogs/DeleteDialog/DeleteDialog";
import {
getPaginationContext,
nonInitialPage,
} from "components/PaginationWidget/utils";
import { nonInitialPage } from "components/PaginationWidget/utils";
import { useMe } from "hooks/useMe";
import { usePermissions } from "hooks/usePermissions";
import { FC, ReactNode, useEffect } from "react";
import { FC, ReactNode } from "react";
import { Helmet } from "react-helmet-async";
import { useSearchParams, useNavigate } from "react-router-dom";
import { usersMachine } from "xServices/users/usersXService";
Expand All @@ -22,6 +19,9 @@ import { useQuery } from "@tanstack/react-query";
import { getAuthMethods } from "api/api";
import { roles } from "api/queries/roles";
import { deploymentConfig } from "api/queries/deployment";
import { prepareQuery } from "utils/filters";
import { usePagination } from "hooks";
import * as UsersQuery from "api/queries/users";

export const Language = {
suspendDialogTitle: "Suspend user",
Expand All @@ -39,28 +39,27 @@ export const UsersPage: FC<{ children?: ReactNode }> = () => {
const navigate = useNavigate();
const searchParamsResult = useSearchParams();
const { entitlements } = useDashboard();
const [searchParams, setSearchParams] = searchParamsResult;
const [searchParams] = searchParamsResult;
const filter = searchParams.get("filter") ?? "";
const [usersState, usersSend] = useMachine(usersMachine, {
context: {
filter,
paginationContext: getPaginationContext(searchParams),
},
actions: {
updateURL: (context, event) =>
setSearchParams({ page: event.page, filter: context.filter }),
},
const [usersState, usersSend] = useMachine(usersMachine);
const pagination = usePagination({
searchParamsResult,
});
const usersQuery = useQuery(
UsersQuery.users({
q: prepareQuery(filter),
limit: pagination.limit,
offset: pagination.offset,
}),
);
const users = usersQuery.data?.users;
const count = usersQuery.data?.count;
const {
users,
getUsersError,
usernameToDelete,
usernameToSuspend,
usernameToActivate,
userIdToResetPassword,
newUserPassword,
paginationRef,
count,
} = usersState.context;
const { updateUsers: canEditUsers, viewDeploymentValues } = usePermissions();
const rolesQuery = useQuery({ ...roles(), enabled: canEditUsers });
Expand All @@ -77,12 +76,9 @@ export const UsersPage: FC<{ children?: ReactNode }> = () => {
const useFilterResult = useFilter({
searchParamsResult,
onUpdate: () => {
usersSend({ type: "UPDATE_PAGE", page: "1" });
pagination.goToPage(1);
},
});
useEffect(() => {
usersSend({ type: "UPDATE_FILTER", query: useFilterResult.query });
}, [useFilterResult.query, usersSend]);
const statusMenu = useStatusFilterMenu({
value: useFilterResult.values.status,
onChange: (option) =>
Expand All @@ -97,13 +93,8 @@ export const UsersPage: FC<{ children?: ReactNode }> = () => {
return getAuthMethods();
},
});
// Is loading if
// - users are loading or
// - the user can edit the users but the roles are loading
const isLoading =
usersState.matches("gettingUsers") ||
rolesQuery.isLoading ||
authMethods.isLoading;
usersQuery.isLoading || rolesQuery.isLoading || authMethods.isLoading;

return (
<>
Expand All @@ -115,7 +106,6 @@ export const UsersPage: FC<{ children?: ReactNode }> = () => {
roles={rolesQuery.data}
users={users}
authMethods={authMethods.data}
count={count}
onListWorkspaces={(user) => {
navigate(
"/workspaces?filter=" +
Expand Down Expand Up @@ -162,16 +152,19 @@ export const UsersPage: FC<{ children?: ReactNode }> = () => {
isLoading={isLoading}
canEditUsers={canEditUsers}
canViewActivity={entitlements.features.audit_log.enabled}
paginationRef={paginationRef}
isNonInitialPage={nonInitialPage(searchParams)}
actorID={me.id}
filterProps={{
filter: useFilterResult,
error: getUsersError,
error: usersQuery.error,
menus: {
status: statusMenu,
},
}}
count={count}
page={pagination.page}
limit={pagination.limit}
onPageChange={pagination.goToPage}
/>

<DeleteDialog
Expand Down
4 changes: 2 additions & 2 deletions site/src/pages/UsersPage/UsersPageView.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Meta, StoryObj } from "@storybook/react";
import { createPaginationRef } from "components/PaginationWidget/utils";
import {
MockUser,
MockUser2,
Expand Down Expand Up @@ -30,7 +29,8 @@ const meta: Meta<typeof UsersPageView> = {
title: "pages/UsersPageView",
component: UsersPageView,
args: {
paginationRef: createPaginationRef({ page: 1, limit: 25 }),
page: 1,
limit: 25,
isNonInitialPage: false,
users: [MockUser, MockUser2],
roles: MockAssignableSiteRoles,
Expand Down
25 changes: 18 additions & 7 deletions site/src/pages/UsersPage/UsersPageView.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import { PaginationWidget } from "components/PaginationWidget/PaginationWidget";
import { ComponentProps, FC } from "react";
import { PaginationMachineRef } from "xServices/pagination/paginationXService";
import * as TypesGen from "api/typesGenerated";
import { UsersTable } from "./UsersTable/UsersTable";
import { UsersFilter } from "./UsersFilter";
import {
PaginationStatus,
TableToolbar,
} from "components/TableToolbar/TableToolbar";
import { PaginationWidgetBase } from "components/PaginationWidget/PaginationWidgetBase";

export interface UsersPageViewProps {
users?: TypesGen.User[];
count?: number;
roles?: TypesGen.AssignableRoles[];
isUpdatingUserRoles?: boolean;
canEditUsers?: boolean;
Expand All @@ -30,14 +28,17 @@ export interface UsersPageViewProps {
roles: TypesGen.Role["name"][],
) => void;
filterProps: ComponentProps<typeof UsersFilter>;
paginationRef: PaginationMachineRef;
isNonInitialPage: boolean;
actorID: string;
// Pagination
count?: number;
page: number;
limit: number;
onPageChange: (page: number) => void;
}

export const UsersPageView: FC<React.PropsWithChildren<UsersPageViewProps>> = ({
users,
count,
roles,
onSuspendUser,
onDeleteUser,
Expand All @@ -52,10 +53,13 @@ export const UsersPageView: FC<React.PropsWithChildren<UsersPageViewProps>> = ({
canViewActivity,
isLoading,
filterProps,
paginationRef,
isNonInitialPage,
actorID,
authMethods,
count,
limit,
onPageChange,
page,
}) => {
return (
<>
Expand Down Expand Up @@ -90,7 +94,14 @@ export const UsersPageView: FC<React.PropsWithChildren<UsersPageViewProps>> = ({
authMethods={authMethods}
/>

<PaginationWidget numRecords={count} paginationRef={paginationRef} />
{count !== undefined && (
<PaginationWidgetBase
count={count}
limit={limit}
onChange={onPageChange}
page={page}
/>
)}
</>
);
};
7 changes: 5 additions & 2 deletions site/src/utils/filters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ import * as TypesGen from "api/typesGenerated";
export const queryToFilter = (
query?: string,
): TypesGen.WorkspaceFilter | TypesGen.UsersRequest => {
const preparedQuery = query?.trim().replace(/ +/g, " ");
return {
q: preparedQuery,
q: prepareQuery(query),
};
};

export const prepareQuery = (query?: string) => {
return query?.trim().replace(/ +/g, " ");
};

export const workspaceFilterQuery = {
me: "owner:me",
all: "",
Expand Down
Loading