Skip to content

Commit e3e82d7

Browse files
committed
Add filter search on Users page
1 parent 3312c81 commit e3e82d7

File tree

14 files changed

+108
-53
lines changed

14 files changed

+108
-53
lines changed

site/src/api/api.test.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import axios from "axios"
2-
import { getApiKey, getWorkspacesURL, login, logout } from "./api"
2+
import { getApiKey, getURLWithSearchParams, login, logout } from "./api"
33
import * as TypesGen from "./typesGenerated"
44

55
describe("api.ts", () => {
@@ -114,16 +114,19 @@ describe("api.ts", () => {
114114
})
115115
})
116116

117-
describe("getWorkspacesURL", () => {
118-
it.each<[TypesGen.WorkspaceFilter | undefined, string]>([
119-
[undefined, "/api/v2/workspaces"],
117+
describe("getURLWithSearchParams", () => {
118+
it.each<[string, TypesGen.WorkspaceFilter | TypesGen.UsersRequest | undefined, string]>([
119+
["/api/v2/workspaces", undefined, "/api/v2/workspaces"],
120120

121-
[{ q: "" }, "/api/v2/workspaces"],
122-
[{ q: "owner:1" }, "/api/v2/workspaces?q=owner%3A1"],
121+
["/api/v2/workspaces", { q: "" }, "/api/v2/workspaces"],
122+
["/api/v2/workspaces", { q: "owner:1" }, "/api/v2/workspaces?q=owner%3A1"],
123123

124-
[{ q: "owner:me" }, "/api/v2/workspaces?q=owner%3Ame"],
125-
])(`getWorkspacesURL(%p) returns %p`, (filter, expected) => {
126-
expect(getWorkspacesURL(filter)).toBe(expected)
124+
["/api/v2/workspaces", { q: "owner:me" }, "/api/v2/workspaces?q=owner%3Ame"],
125+
126+
["/api/v2/users", { q: "status:active" }, "/api/v2/users?q=status%3Aactive"],
127+
["/api/v2/users", { q: "" }, "/api/v2/users"],
128+
])(`getURLWithSearchParams(%p) returns %p`, (basePath, filter, expected) => {
129+
expect(getURLWithSearchParams(basePath, filter)).toBe(expected)
127130
})
128131
})
129132
})

site/src/api/api.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,9 @@ export const getApiKey = async (): Promise<TypesGen.GenerateAPIKeyResponse> => {
6262
return response.data
6363
}
6464

65-
export const getUsers = async (): Promise<TypesGen.User[]> => {
66-
const response = await axios.get<TypesGen.User[]>("/api/v2/users?q=status:active,suspended")
65+
export const getUsers = async (filter?: TypesGen.UsersRequest): Promise<TypesGen.User[]> => {
66+
const url = getURLWithSearchParams("/api/v2/users", filter)
67+
const response = await axios.get<TypesGen.User[]>(url)
6768
return response.data
6869
}
6970

@@ -115,8 +116,10 @@ export const getWorkspace = async (
115116
return response.data
116117
}
117118

118-
export const getWorkspacesURL = (filter?: TypesGen.WorkspaceFilter): string => {
119-
const basePath = "/api/v2/workspaces"
119+
export const getURLWithSearchParams = (
120+
basePath: string,
121+
filter?: TypesGen.WorkspaceFilter | TypesGen.UsersRequest,
122+
): string => {
120123
const searchParams = new URLSearchParams()
121124

122125
if (filter?.q && filter.q !== "") {
@@ -129,7 +132,7 @@ export const getWorkspacesURL = (filter?: TypesGen.WorkspaceFilter): string => {
129132
}
130133

131134
export const getWorkspaces = async (filter?: TypesGen.WorkspaceFilter): Promise<TypesGen.Workspace[]> => {
132-
const url = getWorkspacesURL(filter)
135+
const url = getURLWithSearchParams("/api/v2/workspaces", filter)
133136
const response = await axios.get<TypesGen.Workspace[]>(url)
134137
return response.data
135138
}

site/src/components/SearchBarWithFilter/SearchBarWithFilter.stories.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ComponentMeta, Story } from "@storybook/react"
2-
import { workspaceFilterQuery } from "../../util/workspace"
2+
import { workspaceFilterQuery } from "../../util/filters"
33
import { SearchBarWithFilter, SearchBarWithFilterProps } from "./SearchBarWithFilter"
44

55
export default {

site/src/pages/UsersPage/UsersPage.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ import { useActor, useSelector } from "@xstate/react"
22
import React, { useContext, useEffect } from "react"
33
import { Helmet } from "react-helmet"
44
import { useNavigate } from "react-router"
5+
import { useSearchParams } from "react-router-dom"
56
import { ConfirmDialog } from "../../components/ConfirmDialog/ConfirmDialog"
67
import { ResetPasswordDialog } from "../../components/ResetPasswordDialog/ResetPasswordDialog"
8+
import { userFilterQuery } from "../../util/filters"
79
import { pageTitle } from "../../util/page"
810
import { selectPermissions } from "../../xServices/auth/authSelectors"
911
import { XServiceContext } from "../../xServices/StateContext"
@@ -25,6 +27,7 @@ export const UsersPage: React.FC = () => {
2527
const { users, getUsersError, userIdToSuspend, userIdToActivate, userIdToResetPassword, newUserPassword } =
2628
usersState.context
2729
const navigate = useNavigate()
30+
const [searchParams, setSearchParams] = useSearchParams()
2831
const userToBeSuspended = users?.find((u) => u.id === userIdToSuspend)
2932
const userToBeActivated = users?.find((u) => u.id === userIdToActivate)
3033
const userToResetPassword = users?.find((u) => u.id === userIdToResetPassword)
@@ -40,8 +43,13 @@ export const UsersPage: React.FC = () => {
4043

4144
// Fetch users on component mount
4245
useEffect(() => {
43-
usersSend("GET_USERS")
44-
}, [usersSend])
46+
const filter = searchParams.get("filter")
47+
const query = filter !== null ? filter : userFilterQuery.active
48+
usersSend({
49+
type: "GET_USERS",
50+
query,
51+
})
52+
}, [searchParams, usersSend])
4553

4654
// Fetch roles on component mount
4755
useEffect(() => {
@@ -85,6 +93,11 @@ export const UsersPage: React.FC = () => {
8593
isLoading={isLoading}
8694
canEditUsers={canEditUsers}
8795
canCreateUser={canCreateUser}
96+
filter={usersState.context.filter}
97+
onFilter={(query) => {
98+
searchParams.set("filter", query)
99+
setSearchParams(searchParams)
100+
}}
88101
/>
89102

90103
<ConfirmDialog

site/src/pages/UsersPage/UsersPageView.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,21 @@ import * as TypesGen from "../../api/typesGenerated"
55
import { ErrorSummary } from "../../components/ErrorSummary/ErrorSummary"
66
import { Margins } from "../../components/Margins/Margins"
77
import { PageHeader, PageHeaderTitle } from "../../components/PageHeader/PageHeader"
8+
import { SearchBarWithFilter } from "../../components/SearchBarWithFilter/SearchBarWithFilter"
89
import { UsersTable } from "../../components/UsersTable/UsersTable"
10+
import { userFilterQuery } from "../../util/filters"
911

1012
export const Language = {
1113
pageTitle: "Users",
1214
createButton: "New user",
15+
activeUsersFilterName: "Active users",
16+
allUsersFilterName: "All users",
1317
}
1418

1519
export interface UsersPageViewProps {
1620
users?: TypesGen.User[]
1721
roles?: TypesGen.Role[]
22+
filter?: string
1823
error?: unknown
1924
isUpdatingUserRoles?: boolean
2025
canEditUsers?: boolean
@@ -25,6 +30,7 @@ export interface UsersPageViewProps {
2530
onActivateUser: (user: TypesGen.User) => void
2631
onResetUserPassword: (user: TypesGen.User) => void
2732
onUpdateUserRoles: (user: TypesGen.User, roles: TypesGen.Role["name"][]) => void
33+
onFilter: (query: string) => void
2834
}
2935

3036
export const UsersPageView: FC<UsersPageViewProps> = ({
@@ -40,7 +46,14 @@ export const UsersPageView: FC<UsersPageViewProps> = ({
4046
canEditUsers,
4147
canCreateUser,
4248
isLoading,
49+
filter,
50+
onFilter,
4351
}) => {
52+
const presetFilters = [
53+
{ query: userFilterQuery.active, name: Language.activeUsersFilterName },
54+
{ query: userFilterQuery.all, name: Language.allUsersFilterName },
55+
]
56+
4457
return (
4558
<Margins>
4659
<PageHeader
@@ -55,6 +68,8 @@ export const UsersPageView: FC<UsersPageViewProps> = ({
5568
<PageHeaderTitle>Users</PageHeaderTitle>
5669
</PageHeader>
5770

71+
<SearchBarWithFilter filter={filter} onFilter={onFilter} presetFilters={presetFilters} />
72+
5873
{error ? (
5974
<ErrorSummary error={error} />
6075
) : (

site/src/pages/WorkspacesPage/WorkspacesPage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { useMachine } from "@xstate/react"
22
import { FC, useEffect } from "react"
33
import { Helmet } from "react-helmet"
44
import { useSearchParams } from "react-router-dom"
5+
import { workspaceFilterQuery } from "../../util/filters"
56
import { pageTitle } from "../../util/page"
6-
import { workspaceFilterQuery } from "../../util/workspace"
77
import { workspacesMachine } from "../../xServices/workspaces/workspacesXService"
88
import { WorkspacesPageView } from "./WorkspacesPageView"
99

site/src/pages/WorkspacesPage/WorkspacesPageView.stories.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { ComponentMeta, Story } from "@storybook/react"
22
import { spawn } from "xstate"
33
import { ProvisionerJobStatus, WorkspaceTransition } from "../../api/typesGenerated"
44
import { MockWorkspace } from "../../testHelpers/entities"
5-
import { workspaceFilterQuery } from "../../util/workspace"
5+
import { workspaceFilterQuery } from "../../util/filters"
66
import { workspaceItemMachine, WorkspaceItemMachineRef } from "../../xServices/workspaces/workspacesXService"
77
import { WorkspacesPageView, WorkspacesPageViewProps } from "./WorkspacesPageView"
88

site/src/pages/WorkspacesPage/WorkspacesPageView.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ import {
3131
HelpTooltipText,
3232
HelpTooltipTitle,
3333
} from "../../components/Tooltips/HelpTooltip/HelpTooltip"
34-
import { getDisplayStatus, workspaceFilterQuery } from "../../util/workspace"
34+
import { workspaceFilterQuery } from "../../util/filters"
35+
import { getDisplayStatus } from "../../util/workspace"
3536
import { WorkspaceItemMachineRef } from "../../xServices/workspaces/workspacesXService"
3637

3738
dayjs.extend(relativeTime)

site/src/util/filters.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import * as TypesGen from "../api/typesGenerated"
2+
import { queryToFilter } from "./filters"
3+
4+
describe("queryToFilter", () => {
5+
it.each<[string | undefined, TypesGen.WorkspaceFilter | TypesGen.UsersRequest]>([
6+
[undefined, {}],
7+
["", { q: "" }],
8+
["asdkfvjn", { q: "asdkfvjn" }],
9+
["owner:me", { q: "owner:me" }],
10+
["owner:me owner:me2", { q: "owner:me owner:me2" }],
11+
["me/dev", { q: "me/dev" }],
12+
["me/", { q: "me/" }],
13+
[" key:val owner:me ", { q: "key:val owner:me" }],
14+
])(`query=%p, filter=%p`, (query, filter) => {
15+
expect(queryToFilter(query)).toEqual(filter)
16+
})
17+
})

site/src/util/filters.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import * as TypesGen from "../api/typesGenerated"
2+
3+
export const queryToFilter = (query?: string): TypesGen.WorkspaceFilter | TypesGen.UsersRequest => {
4+
const preparedQuery = query?.trim().replace(/ +/g, " ")
5+
return {
6+
q: preparedQuery,
7+
}
8+
}
9+
10+
export const workspaceFilterQuery = {
11+
me: "owner:me",
12+
all: "",
13+
}
14+
15+
export const userFilterQuery = {
16+
active: "status:active",
17+
all: "",
18+
}

0 commit comments

Comments
 (0)
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