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

Skip to content

Commit 841a3b7

Browse files
committed
fix again
1 parent 2b3e9d0 commit 841a3b7

File tree

14 files changed

+495
-185
lines changed

14 files changed

+495
-185
lines changed

site/src/api/api.ts

+28-3
Original file line numberDiff line numberDiff line change
@@ -280,10 +280,35 @@ export const getURLWithSearchParams = (
280280
}
281281

282282
export const getWorkspaces = async (
283-
filter?: TypesGen.WorkspaceFilter,
283+
options: TypesGen.WorkspacesRequest,
284284
): Promise<TypesGen.Workspace[]> => {
285-
const url = getURLWithSearchParams("/api/v2/workspaces", filter)
286-
const response = await axios.get<TypesGen.Workspace[]>(url)
285+
const searchParams = new URLSearchParams()
286+
if (options.limit) {
287+
searchParams.set("limit", options.limit.toString())
288+
}
289+
if (options.offset) {
290+
searchParams.set("offset", options.offset.toString())
291+
}
292+
if (options.q) {
293+
searchParams.set("q", options.q)
294+
}
295+
296+
const response = await axios.get<TypesGen.Workspace[]>(
297+
`/api/v2/workspaces?${searchParams.toString()}`,
298+
)
299+
return response.data
300+
}
301+
302+
export const getWorkspacesCount = async (
303+
options: TypesGen.WorkspaceCountRequest,
304+
): Promise<TypesGen.WorkspaceCountResponse> => {
305+
const searchParams = new URLSearchParams()
306+
if (options.q) {
307+
searchParams.set("q", options.q)
308+
}
309+
const response = await axios.get(
310+
`/api/v2/workspaces/count?${searchParams.toString()}`,
311+
)
287312
return response.data
288313
}
289314

site/src/api/typesGenerated.ts

+15
Original file line numberDiff line numberDiff line change
@@ -845,6 +845,16 @@ export interface WorkspaceBuildsRequest extends Pagination {
845845
readonly Since: string
846846
}
847847

848+
// From codersdk/workspaces.go
849+
export interface WorkspaceCountRequest {
850+
readonly q?: string
851+
}
852+
853+
// From codersdk/workspaces.go
854+
export interface WorkspaceCountResponse {
855+
readonly count: number
856+
}
857+
848858
// From codersdk/workspaces.go
849859
export interface WorkspaceFilter {
850860
readonly q?: string
@@ -882,6 +892,11 @@ export interface WorkspaceResourceMetadata {
882892
readonly sensitive: boolean
883893
}
884894

895+
// From codersdk/workspaces.go
896+
export interface WorkspacesRequest extends Pagination {
897+
readonly q?: string
898+
}
899+
885900
// From codersdk/apikey.go
886901
export type APIKeyScope = "all" | "application_connect"
887902

site/src/components/AlertBanner/AlertBanner.stories.tsx

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { Story } from "@storybook/react"
2-
import { AlertBanner, AlertBannerProps } from "./AlertBanner"
2+
import { AlertBanner } from "./AlertBanner"
33
import Button from "@material-ui/core/Button"
44
import { makeMockApiError } from "testHelpers/entities"
5+
import { AlertBannerProps } from "./alertTypes"
56

67
export default {
78
title: "components/AlertBanner",
@@ -99,3 +100,9 @@ ErrorWithActionRetryAndDismiss.args = {
99100
dismissible: true,
100101
severity: "error",
101102
}
103+
104+
export const ErrorAsWarning = Template.bind({})
105+
ErrorAsWarning.args = {
106+
error: mockError,
107+
severity: "warning",
108+
}

site/src/components/AlertBanner/AlertBanner.tsx

+6-6
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ import { severityConstants } from "./severityConstants"
1111
import { AlertBannerCtas } from "./AlertBannerCtas"
1212

1313
/**
14-
* severity: the level of alert severity (see ./severityTypes.ts)
15-
* text: default text to be displayed to the user; useful for warnings or as a fallback error message
16-
* error: should be passed in if the severity is 'Error'; warnings can use 'text' instead
17-
* actions: an array of CTAs passed in by the consumer
18-
* dismissible: determines whether or not the banner should have a `Dismiss` CTA
19-
* retry: a handler to retry the action that spawned the error
14+
* @param severity: the level of alert severity (see ./severityTypes.ts)
15+
* @param text: default text to be displayed to the user; useful for warnings or as a fallback error message
16+
* @param error: should be passed in if the severity is 'Error'; warnings can use 'text' instead
17+
* @param actions: an array of CTAs passed in by the consumer
18+
* @param dismissible: determines whether or not the banner should have a `Dismiss` CTA
19+
* @param retry: a handler to retry the action that spawned the error
2020
*/
2121
export const AlertBanner: FC<AlertBannerProps> = ({
2222
severity,

site/src/components/PaginationWidget/PaginationWidget.tsx

+72-28
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import Button from "@material-ui/core/Button"
2-
import { makeStyles } from "@material-ui/core/styles"
2+
import { makeStyles, useTheme } from "@material-ui/core/styles"
3+
import useMediaQuery from "@material-ui/core/useMediaQuery"
34
import KeyboardArrowLeft from "@material-ui/icons/KeyboardArrowLeft"
45
import KeyboardArrowRight from "@material-ui/icons/KeyboardArrowRight"
6+
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne"
7+
import { Maybe } from "components/Conditionals/Maybe"
58
import { CSSProperties } from "react"
69

710
export type PaginationWidgetProps = {
@@ -24,7 +27,7 @@ export type PaginationWidgetProps = {
2427
const range = (start: number, stop: number, step = 1) =>
2528
Array.from({ length: (stop - start) / step + 1 }, (_, i) => start + i * step)
2629

27-
const DEFAULT_RECORDS_PER_PAGE = 25
30+
export const DEFAULT_RECORDS_PER_PAGE = 25
2831
// Number of pages to the left or right of the current page selection.
2932
const PAGE_NEIGHBORS = 1
3033
// Number of pages displayed for cases where there are multiple ellipsis showing. This can be
@@ -74,6 +77,38 @@ export const buildPagedList = (
7477
return range(1, numPages)
7578
}
7679

80+
interface PageButtonProps {
81+
activePage: number
82+
page: number
83+
numPages: number
84+
onPageClick?: (page: number) => void
85+
}
86+
87+
const PageButton = ({
88+
activePage,
89+
page,
90+
numPages,
91+
onPageClick,
92+
}: PageButtonProps): JSX.Element => {
93+
const styles = useStyles()
94+
return (
95+
<Button
96+
className={
97+
activePage === page
98+
? `${styles.pageButton} ${styles.activePageButton}`
99+
: styles.pageButton
100+
}
101+
aria-label={`${page === activePage ? "Current Page" : ""} ${
102+
page === numPages ? "Last Page" : ""
103+
} Page${page}`}
104+
name="Page button"
105+
onClick={() => onPageClick && onPageClick(page)}
106+
>
107+
<div>{page}</div>
108+
</Button>
109+
)
110+
}
111+
77112
export const PaginationWidget = ({
78113
prevLabel,
79114
nextLabel,
@@ -88,11 +123,12 @@ export const PaginationWidget = ({
88123
const numPages = numRecords ? Math.ceil(numRecords / numRecordsPerPage) : 0
89124
const firstPageActive = activePage === 1 && numPages !== 0
90125
const lastPageActive = activePage === numPages && numPages !== 0
91-
126+
const theme = useTheme()
127+
const isMobile = useMediaQuery(theme.breakpoints.down("sm"))
92128
const styles = useStyles()
93129

94130
// No need to display any pagination if we know the number of pages is 1
95-
if (numPages === 1) {
131+
if (numPages === 1 || numRecords === 0) {
96132
return null
97133
}
98134

@@ -107,30 +143,38 @@ export const PaginationWidget = ({
107143
<KeyboardArrowLeft />
108144
<div>{prevLabel}</div>
109145
</Button>
110-
{numPages > 0 &&
111-
buildPagedList(numPages, activePage).map((page) =>
112-
typeof page !== "number" ? (
113-
<Button className={styles.pageButton} key={`Page${page}`} disabled>
114-
<div>...</div>
115-
</Button>
116-
) : (
117-
<Button
118-
className={
119-
activePage === page
120-
? `${styles.pageButton} ${styles.activePageButton}`
121-
: styles.pageButton
122-
}
123-
aria-label={`${page === activePage ? "Current Page" : ""} ${
124-
page === numPages ? "Last Page" : ""
125-
} Page${page}`}
126-
name="Page button"
127-
key={`Page${page}`}
128-
onClick={() => onPageClick && onPageClick(page)}
129-
>
130-
<div>{page}</div>
131-
</Button>
132-
),
133-
)}
146+
<Maybe condition={numPages > 0}>
147+
<ChooseOne>
148+
<Cond condition={isMobile}>
149+
<PageButton
150+
activePage={activePage}
151+
page={activePage}
152+
numPages={numPages}
153+
/>
154+
</Cond>
155+
<Cond>
156+
{buildPagedList(numPages, activePage).map((page) =>
157+
typeof page !== "number" ? (
158+
<Button
159+
className={styles.pageButton}
160+
key={`Page${page}`}
161+
disabled
162+
>
163+
<div>...</div>
164+
</Button>
165+
) : (
166+
<PageButton
167+
key={`Page${page}`}
168+
activePage={activePage}
169+
page={page}
170+
numPages={numPages}
171+
onPageClick={onPageClick}
172+
/>
173+
),
174+
)}
175+
</Cond>
176+
</ChooseOne>
177+
</Maybe>
134178
<Button
135179
aria-label="Next page"
136180
disabled={lastPageActive}

site/src/pages/WorkspacePage/WorkspacePage.test.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ describe("WorkspacePage", () => {
220220

221221
await waitFor(() =>
222222
expect(api.startWorkspace).toBeCalledWith(
223-
"test-workspace",
223+
"test-outdated-workspace",
224224
"test-template-version",
225225
),
226226
)

site/src/pages/WorkspacesPage/WorkspacesPage.test.tsx

+17-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { screen } from "@testing-library/react"
1+
import { screen, waitFor } from "@testing-library/react"
22
import { rest } from "msw"
33
import * as CreateDayString from "util/createDayString"
44
import { Language as WorkspacesTableBodyLanguage } from "../../components/WorkspacesTable/WorkspacesTableBody"
@@ -34,9 +34,24 @@ describe("WorkspacesPage", () => {
3434

3535
it("renders a filled workspaces page", async () => {
3636
// When
37-
render(<WorkspacesPage />)
37+
const { container } = render(<WorkspacesPage />)
3838

3939
// Then
40+
const nextPage = await screen.findByRole("button", { name: "Next page" })
41+
expect(nextPage).toBeEnabled()
42+
await waitFor(
43+
async () => {
44+
const prevPage = await screen.findByRole("button", {
45+
name: "Previous page",
46+
})
47+
expect(prevPage).toBeDisabled()
48+
const pageButtons = await container.querySelectorAll(
49+
`button[name="Page button"]`,
50+
)
51+
expect(pageButtons.length).toBe(2)
52+
},
53+
{ timeout: 2000 },
54+
)
4055
await screen.findByText(MockWorkspace.name)
4156
})
4257
})

site/src/pages/WorkspacesPage/WorkspacesPage.tsx

+38-3
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,43 @@
11
import { useMachine } from "@xstate/react"
2+
import { DEFAULT_RECORDS_PER_PAGE } from "components/PaginationWidget/PaginationWidget"
23
import { FC } from "react"
34
import { Helmet } from "react-helmet-async"
4-
import { useSearchParams } from "react-router-dom"
5+
import { useNavigate, useSearchParams } from "react-router-dom"
56
import { workspaceFilterQuery } from "util/filters"
67
import { pageTitle } from "util/page"
78
import { workspacesMachine } from "xServices/workspaces/workspacesXService"
89
import { WorkspacesPageView } from "./WorkspacesPageView"
910

1011
const WorkspacesPage: FC = () => {
12+
const navigate = useNavigate()
1113
const [searchParams, setSearchParams] = useSearchParams()
1214
const filter = searchParams.get("filter") ?? workspaceFilterQuery.me
15+
const currentPage = searchParams.get("page")
16+
? Number(searchParams.get("page"))
17+
: 1
1318
const [workspacesState, send] = useMachine(workspacesMachine, {
1419
context: {
20+
page: currentPage,
21+
limit: DEFAULT_RECORDS_PER_PAGE,
1522
filter,
1623
},
24+
actions: {
25+
onPageChange: ({ page }) => {
26+
navigate({
27+
search: `?page=${page}`,
28+
})
29+
},
30+
},
1731
})
1832

19-
const { workspaceRefs } = workspacesState.context
33+
const {
34+
workspaceRefs,
35+
count,
36+
page,
37+
limit,
38+
getWorkspacesError,
39+
getCountError,
40+
} = workspacesState.context
2041

2142
return (
2243
<>
@@ -28,10 +49,24 @@ const WorkspacesPage: FC = () => {
2849
filter={workspacesState.context.filter}
2950
isLoading={!workspaceRefs}
3051
workspaceRefs={workspaceRefs}
52+
count={count}
53+
getWorkspacesError={getWorkspacesError}
54+
getCountError={getCountError}
55+
page={page}
56+
limit={limit}
57+
onNext={() => {
58+
send("NEXT")
59+
}}
60+
onPrevious={() => {
61+
send("PREVIOUS")
62+
}}
63+
onGoToPage={(page) => {
64+
send("GO_TO_PAGE", { page })
65+
}}
3166
onFilter={(query) => {
3267
setSearchParams({ filter: query })
3368
send({
34-
type: "GET_WORKSPACES",
69+
type: "UPDATE_FILTER",
3570
query,
3671
})
3772
}}

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

+3
Original file line numberDiff line numberDiff line change
@@ -107,16 +107,19 @@ AllStates.args = {
107107
...Object.values(workspaces),
108108
...Object.values(additionalWorkspaces),
109109
],
110+
count: 14,
110111
}
111112

112113
export const OwnerHasNoWorkspaces = Template.bind({})
113114
OwnerHasNoWorkspaces.args = {
114115
workspaceRefs: [],
115116
filter: workspaceFilterQuery.me,
117+
count: 0,
116118
}
117119

118120
export const NoResults = Template.bind({})
119121
NoResults.args = {
120122
workspaceRefs: [],
121123
filter: "searchtearmwithnoresults",
124+
count: 0,
122125
}

0 commit comments

Comments
 (0)