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

Skip to content

Commit d9668f7

Browse files
authored
add debounced search on type to the search bar (#2703)
* debounced search on type * loading workspaces on page entry * fixing e2e test * removing boilerplate
1 parent 6a55889 commit d9668f7

File tree

7 files changed

+78
-11
lines changed

7 files changed

+78
-11
lines changed

site/e2e/pom/WorkspacesPage.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Page } from "@playwright/test"
22
import { BasePom } from "./BasePom"
33

44
export class WorkspacesPage extends BasePom {
5-
constructor(baseURL: string | undefined, page: Page) {
6-
super(baseURL, "/workspaces", page)
5+
constructor(baseURL: string | undefined, page: Page, params?: string) {
6+
super(baseURL, `/workspaces${params && params}`, page)
77
}
88
}

site/e2e/tests/login.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ test("Login takes user to /workspaces", async ({ baseURL, page }) => {
1010
const signInPage = new SignInPage(baseURL, page)
1111
await signInPage.submitBuiltInAuthentication(email, password)
1212

13-
const workspacesPage = new WorkspacesPage(baseURL, page)
13+
const workspacesPage = new WorkspacesPage(baseURL, page, "?filter=owner%3Ame")
1414
await waitForClientSideNavigation(page, { to: workspacesPage.url })
1515

1616
await page.waitForSelector("text=Workspaces")

site/package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,10 @@
4242
"formik": "2.2.9",
4343
"front-matter": "4.0.2",
4444
"history": "5.3.0",
45+
"just-debounce-it": "3.0.1",
4546
"react": "17.0.2",
4647
"react-dom": "17.0.2",
47-
"react-helmet": "^6.1.0",
48+
"react-helmet": "6.1.0",
4849
"react-markdown": "8.0.3",
4950
"react-router-dom": "6.3.0",
5051
"sourcemapped-stacktrace": "1.1.11",
@@ -74,7 +75,7 @@
7475
"@types/node": "14.18.16",
7576
"@types/react": "17.0.44",
7677
"@types/react-dom": "17.0.16",
77-
"@types/react-helmet": "^6.1.5",
78+
"@types/react-helmet": "6.1.5",
7879
"@types/superagent": "4.1.15",
7980
"@types/uuid": "8.3.4",
8081
"@typescript-eslint/eslint-plugin": "5.27.0",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { fireEvent, screen } from "@testing-library/react"
2+
import userEvent from "@testing-library/user-event"
3+
import { render } from "../../testHelpers/renderHelpers"
4+
import { SearchBarWithFilter } from "./SearchBarWithFilter"
5+
6+
// mock the debounce utility
7+
jest.mock("just-debounce-it", () =>
8+
jest.fn((fn) => {
9+
fn.cancel = jest.fn()
10+
return fn
11+
}),
12+
)
13+
14+
describe("SearchBarWithFilter", () => {
15+
it("calls the onFilter handler on keystroke", async () => {
16+
// When
17+
const onFilter = jest.fn()
18+
render(<SearchBarWithFilter onFilter={onFilter} />)
19+
20+
const searchInput = screen.getByRole("textbox")
21+
await userEvent.type(searchInput, "workspace") // 9 characters
22+
23+
// Then
24+
expect(onFilter).toBeCalledTimes(10) // 9 characters + 1 on component mount
25+
})
26+
27+
it("calls the onFilter handler on submit", async () => {
28+
// When
29+
const onFilter = jest.fn()
30+
render(<SearchBarWithFilter onFilter={onFilter} />)
31+
32+
const searchInput = screen.getByRole("textbox")
33+
await fireEvent.keyDown(searchInput, { key: "Enter", code: "Enter", charCode: 13 })
34+
35+
// Then
36+
expect(onFilter).toBeCalledTimes(1)
37+
})
38+
})

site/src/components/SearchBarWithFilter/SearchBarWithFilter.tsx

+19-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import { makeStyles } from "@material-ui/core/styles"
77
import TextField from "@material-ui/core/TextField"
88
import SearchIcon from "@material-ui/icons/Search"
99
import { FormikErrors, useFormik } from "formik"
10-
import { useState } from "react"
10+
import debounce from "just-debounce-it"
11+
import { useCallback, useEffect, useState } from "react"
1112
import { getValidationErrorMessage } from "../../api/errors"
1213
import { getFormHelpers } from "../../util/formUtils"
1314
import { CloseDropdown, OpenDropdown } from "../DropdownArrows/DropdownArrows"
@@ -53,6 +54,23 @@ export const SearchBarWithFilter: React.FC<SearchBarWithFilterProps> = ({
5354
},
5455
})
5556

57+
// debounce query string entry by user
58+
// we want the dependency array empty here
59+
// as we don't need to redefine the function
60+
// eslint-disable-next-line react-hooks/exhaustive-deps
61+
const debouncedOnFilter = useCallback(
62+
debounce((debouncedQueryString: string) => {
63+
onFilter(debouncedQueryString)
64+
}, 300),
65+
[],
66+
)
67+
68+
// update the query params while typing
69+
useEffect(() => {
70+
debouncedOnFilter(form.values.query)
71+
return () => debouncedOnFilter.cancel()
72+
}, [debouncedOnFilter, form.values.query])
73+
5674
const getFieldHelpers = getFormHelpers<FilterFormValues>(form)
5775

5876
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null)

site/src/pages/WorkspacesPage/WorkspacesPage.tsx

+8-3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const WorkspacesPage: FC = () => {
1212
const [searchParams, setSearchParams] = useSearchParams()
1313
const { workspaceRefs } = workspacesState.context
1414

15+
// On page load, populate the table with workspaces
1516
useEffect(() => {
1617
const filter = searchParams.get("filter")
1718
const query = filter ?? workspaceFilterQuery.me
@@ -20,7 +21,8 @@ const WorkspacesPage: FC = () => {
2021
type: "GET_WORKSPACES",
2122
query,
2223
})
23-
}, [searchParams, send])
24+
// eslint-disable-next-line react-hooks/exhaustive-deps
25+
}, [])
2426

2527
return (
2628
<>
@@ -33,8 +35,11 @@ const WorkspacesPage: FC = () => {
3335
loading={workspacesState.hasTag("loading")}
3436
workspaceRefs={workspaceRefs}
3537
onFilter={(query) => {
36-
searchParams.set("filter", query)
37-
setSearchParams(searchParams)
38+
setSearchParams({ filter: query })
39+
send({
40+
type: "GET_WORKSPACES",
41+
query,
42+
})
3843
}}
3944
/>
4045
</>

site/yarn.lock

+7-2
Original file line numberDiff line numberDiff line change
@@ -3041,7 +3041,7 @@
30413041
dependencies:
30423042
"@types/react" "^17"
30433043

3044-
"@types/react-helmet@^6.1.5":
3044+
30453045
version "6.1.5"
30463046
resolved "https://registry.yarnpkg.com/@types/react-helmet/-/react-helmet-6.1.5.tgz#35f89a6b1646ee2bc342a33a9a6c8777933f9083"
30473047
integrity sha512-/ICuy7OHZxR0YCAZLNg9r7I9aijWUWvxaPR6uTuyxe8tAj5RL4Sw1+R6NhXUtOsarkGYPmaHdBDvuXh2DIN/uA==
@@ -9122,6 +9122,11 @@ junk@^3.1.0:
91229122
resolved "https://registry.yarnpkg.com/junk/-/junk-3.1.0.tgz#31499098d902b7e98c5d9b9c80f43457a88abfa1"
91239123
integrity sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ==
91249124

9125+
9126+
version "3.0.1"
9127+
resolved "https://registry.yarnpkg.com/just-debounce-it/-/just-debounce-it-3.0.1.tgz#8c8a4c9327c9523366ec79ac9a959a938153bd2f"
9128+
integrity sha512-6EQWOpRV8fm/ame6XvGBSxvsjoMbqj7JS9TV/4Q9aOXt9DQw22GBfTGP6gTAqcBNN/PbzlwtwH7jtM0k9oe9pg==
9129+
91259130
kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0:
91269131
version "3.2.2"
91279132
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
@@ -11487,7 +11492,7 @@ react-helmet-async@^1.0.7:
1148711492
react-fast-compare "^3.2.0"
1148811493
shallowequal "^1.1.0"
1148911494

11490-
react-helmet@^6.1.0:
11495+
1149111496
version "6.1.0"
1149211497
resolved "https://registry.yarnpkg.com/react-helmet/-/react-helmet-6.1.0.tgz#a750d5165cb13cf213e44747502652e794468726"
1149311498
integrity sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw==

0 commit comments

Comments
 (0)