10000 Add filter search on Users page · coder/coder@acbd54a · GitHub
[go: up one dir, main page]

Skip to content

Commit acbd54a

Browse files
committed
Add filter search on Users page
1 parent 37f9dff commit acbd54a

File tree

14 files changed

+108
-58
lines changed

14 files changed

+108
-58
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
10000
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,9 @@ export const getApiKey = async (): Promise<TypesGen.GenerateAPIKeyResponse> => {
7272
return response.data
7373
}
7474

75-
export const getUsers = async (): Promise<TypesGen.User[]> => {
76-
const response = await axios.get<TypesGen.User[]>("/api/v2/users?q=status:active,suspended")
75+
export const getUsers = async (filter?: TypesGen.UsersRequest): Promise<TypesGen.User[]> => {
76+
const url = getURLWithSearchParams("/api/v2/users", filter)
77+
const response = await axios.get<TypesGen.User[]>(url)
7778
return response.data
7879
}
7980

@@ -144,8 +145,10 @@ export const getWorkspace = async (
144145
return response.data
145146
}
146147

147-
export const getWorkspacesURL = (filter?: TypesGen.WorkspaceFilter): string => {
148-
const basePath = "/api/v2/workspaces"
148+
export const getURLWithSearchParams = (
149+
basePath: string,
150+
filter?: TypesGen.WorkspaceFilter | TypesGen.UsersRequest,
151+
): string => {
149152
const searchParams = new URLSearchParams()
150153

151154
if (filter?.q && filter.q !== "") {
@@ -160,7 +163,7 @@ export const getWorkspacesURL = (filter?: TypesGen.WorkspaceFilter): string => {
160163
export const getWorkspaces = async (
161164
filter?: TypesGen.WorkspaceFilter,
162165
): Promise<TypesGen.Workspace[]> => {
163-
const url = getWorkspacesURL(filter)
166+
const url = getURLWithSearchParams("/api/v2/workspaces", filter)
164167
const response = await axios.get<TypesGen.Workspace[]>(url)
165168
return response.data
166169
}

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"
@@ -31,6 +33,7 @@ export const UsersPage: React.FC = () => {
3133
newUserPassword,
3234
} = usersState.context
3335
const navigate = useNavigate()
36+
const [searchParams, setSearchParams] = useSearchParams()
3437
const userToBeSuspended = users?.find((u) => u.id === userIdToSuspend)
3538
const userToBeActivated = users?.find((u) => u.id === userIdToActivate)
3639
const userToResetPassword = users?.find((u) => u.id === userIdToResetPassword)
@@ -46,8 +49,13 @@ export const UsersPage: React.FC = () => {
4649

4750
// Fetch users on component mount
4851
useEffect(() => {
49-
usersSend("GET_USERS")
50-
}, [usersSend])
52+
const filter = searchParams.get("filter")
53+
const query = filter !== null ? filter : userFilterQuery.active
54+
usersSend({
55+
type: "GET_USERS",
56+
query,
57+
})
58+
}, [searchParams, usersSend])
5159

5260
// Fetch roles on component mount
5361
useEffect(() => {
@@ -91,6 +99,11 @@ export const UsersPage: React.FC = () => {
9199
isLoading={isLoading}
92100
canEditUsers={canEditUsers}
93101
canCreateUser={canCreateUser}
102+
filter={usersState.context.filter}
103+
onFilter={(query) => {
104+
searchParams.set("filter", query)
105+
setSearchParams(searchParams)
106+
}}
94107
/>
95108

96109
<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 {
77
workspaceItemMachine,
88
WorkspaceItemMachineRef,

site/src/pages/WorkspacesPage/WorkspacesPageView.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ import {
3535
HelpTooltipText,
3636
HelpTooltipTitle,
3737
} from "../../components/Tooltips/HelpTooltip/HelpTooltip"
38-
import { getDisplayStatus, workspaceFilterQuery } from "../../util/workspace"
38+
import { workspaceFilterQuery } from "../../util/filters"
39+
import { getDisplayStatus } from "../../util/workspace"
3940
import { WorkspaceItemMachineRef } from "../../xServices/workspaces/workspacesXService"
4041

4142
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)
0