Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 456e04f

Browse files
committed
Add filter search on Users page
1 parent 8293894 commit 456e04f

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
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+
}

site/src/util/workspace.test.ts

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
11
import dayjs from "dayjs"
22
import * as TypesGen from "../api/typesGenerated"
33
import * as Mocks from "../testHelpers/entities"
4-
import {
5-
defaultWorkspaceExtension,
6-
isWorkspaceDeleted,
7-
isWorkspaceOn,
8-
workspaceQueryToFilter,
9-
} from "./workspace"
4+
import { defaultWorkspaceExtension, isWorkspaceDeleted, isWorkspaceOn } from "./workspace"
105

116
describe("util > workspace", () => {
127
describe("isWorkspaceOn", () => {
@@ -106,18 +101,4 @@ describe("util > workspace", () => {
106101
expect(defaultWorkspaceExtension(dayjs(startTime))).toEqual(request)
107102
})
108103
})
109-
describe("workspaceQueryToFilter", () => {
110-
it.each<[string | undefined, TypesGen.WorkspaceFilter]>([
111-
[undefined, {}],
112-
["", { q: "" }],
113-
["asdkfvjn", { q: "asdkfvjn" }],
114-
["owner:me", { q: "owner:me" }],
115-
["owner:me owner:me2", { q: "owner:me owner:me2" }],
116-
["me/dev", { q: "me/dev" }],
117-
["me/", { q: "me/" }],
118-
[" key:val owner:me ", { q: "key:val owner:me" }],
119-
])(`query=%p, filter=%p`, (query, filter) => {
120-
expect(workspaceQueryToFilter(query)).toEqual(filter)
121-
})
122-
})
123104
})

site/src/util/workspace.ts

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -296,15 +296,3 @@ export const defaultWorkspaceExtension = (
296296
deadline: fourHoursFromNow.format(),
297297
}
298298
}
299-
300-
export const workspaceQueryToFilter = (query?: string): TypesGen.WorkspaceFilter => {
301-
const preparedQuery = query?.trim().replace(/ +/g, " ")
302-
return {
303-
q: preparedQuery,
304-
}
305-
}
306-
307-
export const workspaceFilterQuery = {
308-
me: "owner:me",
309-
all: "",
310-
}

site/src/xServices/users/usersXService.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
} from "../../api/errors"
1111
import * as TypesGen from "../../api/typesGenerated"
1212
import { displayError, displaySuccess } from "../../components/GlobalSnackbar/utils"
13+
import { queryToFilter } from "../../util/filters"
1314
import { generateRandomString } from "../../util/random"
1415

1516
export const Language = {
@@ -28,6 +29,7 @@ export const Language = {
2829
export interface UsersContext {
2930
// Get users
3031
users?: TypesGen.User[]
32+
filter?: string
3133
getUsersError?: Error | unknown
3234
createUserErrorMessage?: string
3335
createUserFormErrors?: FieldErrors
@@ -47,7 +49,7 @@ export interface UsersContext {
4749
}
4850

4951
export type UsersEvent =
50-
| { type: "GET_USERS" }
52+
| { type: "GET_USERS"; query: string }
5153
| { type: "CREATE"; user: TypesGen.CreateUserRequest }
5254
| { type: "CANCEL_CREATE_USER" }
5355
// Suspend events
@@ -97,7 +99,10 @@ export const usersMachine = createMachine(
9799
states: {
98100
idle: {
99101
on: {
100-
GET_USERS: "gettingUsers",
102+
GET_USERS: {
103+
actions: "assignFilter",
104+
target: "gettingUsers",
105+
},
101106
CREATE: "creatingUser",
102107
CANCEL_CREATE_USER: { actions: ["clearCreateUserError"] },
103108
SUSPEND_USER: {
@@ -242,7 +247,10 @@ export const usersMachine = createMachine(
242247
},
243248
error: {
244249
on: {
245-
GET_USERS: "gettingUsers",
250+
GET_USERS: {
251+
actions: "assignFilter",
252+
target: "gettingUsers",
253+
},
246254
},
247255
},
248256
},
@@ -252,7 +260,7 @@ export const usersMachine = createMachine(
252260
// Passing API.getUsers directly does not invoke the function properly
253261
// when it is mocked. This happen in the UsersPage tests inside of the
254262
// "shows a success message and refresh the page" test case.
255-
getUsers: () => API.getUsers(),
263+
getUsers: (context) => API.getUsers(queryToFilter(context.filter)),
256264
createUser: (_, event) => API.createUser(event.user),
257265
suspendUser: (context) => {
258266
if (!context.userIdToSuspend) {
@@ -297,6 +305,9 @@ export const usersMachine = createMachine(
297305
assignUsers: assign({
298306
users: (_, event) => event.data,
299307
}),
308+
assignFilter: assign({
309+
filter: (_, event) => event.query,
310+
}),
300311
assignGetUsersError: assign({
301312
getUsersError: (_, event) => event.data,
302313
}),

site/src/xServices/workspaces/workspacesXService.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as API from "../../api/api"
33
import { getErrorMessage } from "../../api/errors"
44
import * as TypesGen from "../../api/typesGenerated"
55
import { displayError, displayMsg, displaySuccess } from "../../components/GlobalSnackbar/utils"
6-
import { workspaceQueryToFilter } from "../../util/workspace"
6+
import { queryToFilter } from "../../util/filters"
77

88
/**
99
* Workspace item machine
@@ -318,7 +318,7 @@ export const workspacesMachine = createMachine(
318318
}),
319319
},
320320
services: {
321-
getWorkspaces: (context) => API.getWorkspaces(workspaceQueryToFilter(context.filter)),
321+
getWorkspaces: (context) => API.getWorkspaces(queryToFilter(context.filter)),
322322
},
323323
},
324324
)

0 commit comments

Comments
 (0)