Skip to content

Commit 9966edc

Browse files
committed
ui: refactor schedules page to use useSWR hook
- Refactors the schedules page to use `useSwrWithClusterId` instead of redux sagas to fetch data. - Removed show and status props from being stored in redux and browser session. - Deleted schedules page story book Epic: None Release note: None
1 parent 3e293ec commit 9966edc

File tree

7 files changed

+82
-202
lines changed

7 files changed

+82
-202
lines changed

pkg/ui/workspaces/cluster-ui/src/loading/loading.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ export const Loading = (props: React.PropsWithChildren<LoadingProps>) => {
141141
}
142142
if (props.loading) {
143143
return (
144-
<div>
144+
<div data-testid={"loading-spinner"}>
145145
<Spinner className={cx("loading-indicator", props.loadingClassName)} />
146146
</div>
147147
);

pkg/ui/workspaces/cluster-ui/src/schedules/schedulesPage/schedulesPage.fixture.tsx

Lines changed: 0 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,9 @@
22
//
33
// Use of this software is governed by the CockroachDB Software License
44
// included in the /LICENSE file.
5-
import { createMemoryHistory } from "history";
65
import Long from "long";
76
import moment from "moment-timezone";
87

9-
import { Schedule } from "src/api/schedulesApi";
10-
11-
import { SchedulesPageProps } from "./schedulesPage";
12-
13-
const schedulesTimeoutErrorMessage =
14-
"Unable to retrieve the Schedules table. To reduce the amount of data, try filtering the table.";
15-
168
const defaultScheduleProperties = {
179
owner: "root",
1810
created: moment("15 Aug 2022"),
@@ -44,59 +36,3 @@ export const allSchedulesFixture = [
4436
activeScheduleFixture,
4537
pausedScheduleFixture,
4638
];
47-
48-
const history = createMemoryHistory({ initialEntries: ["/statements"] });
49-
50-
const staticScheduleProps: Omit<
51-
SchedulesPageProps,
52-
"schedules" | "schedulesError" | "schedulesLoading" | "onFilterChange"
53-
> = {
54-
history,
55-
location: {
56-
pathname: "/schedules",
57-
search: "",
58-
hash: "",
59-
state: null,
60-
},
61-
match: {
62-
path: "/schedules",
63-
url: "/schedules",
64-
isExact: true,
65-
params: "{}",
66-
},
67-
sort: {
68-
columnTitle: "creationTime",
69-
ascending: false,
70-
},
71-
status: "",
72-
show: "50",
73-
setSort: () => {},
74-
setStatus: () => {},
75-
setShow: () => {},
76-
refreshSchedules: () => null,
77-
};
78-
79-
const getSchedulesPageProps = (
80-
schedules: Array<Schedule>,
81-
error: Error | null = null,
82-
loading = false,
83-
): SchedulesPageProps => ({
84-
...staticScheduleProps,
85-
schedules,
86-
schedulesError: error,
87-
schedulesLoading: loading,
88-
});
89-
90-
export const withData: SchedulesPageProps =
91-
getSchedulesPageProps(allSchedulesFixture);
92-
export const empty: SchedulesPageProps = getSchedulesPageProps([]);
93-
export const loading: SchedulesPageProps = getSchedulesPageProps(
94-
allSchedulesFixture,
95-
null,
96-
true,
97-
);
98-
export const error: SchedulesPageProps = getSchedulesPageProps(
99-
allSchedulesFixture,
100-
new Error(schedulesTimeoutErrorMessage),
101-
false,
102-
);

pkg/ui/workspaces/cluster-ui/src/schedules/schedulesPage/schedulesPage.spec.tsx

Lines changed: 59 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,16 @@ import * as H from "history";
88
import React from "react";
99
import { MemoryRouter } from "react-router-dom";
1010

11-
import { Schedule } from "src/api/schedulesApi";
11+
import * as utils from "src/util/hooks";
1212

1313
import { SchedulesPage, SchedulesPageProps } from "./schedulesPage";
1414
import { allSchedulesFixture } from "./schedulesPage.fixture";
1515

16-
const getMockSchedulesPageProps = (
17-
schedules: Array<Schedule>,
18-
): SchedulesPageProps => {
16+
const getMockSchedulesPageProps = (): SchedulesPageProps => {
1917
const history = H.createHashHistory();
2018
return {
2119
sort: { columnTitle: null, ascending: true },
22-
status: "",
23-
show: "50",
2420
setSort: () => {},
25-
setStatus: () => {},
26-
setShow: () => {},
27-
schedules: schedules,
28-
schedulesLoading: false,
29-
schedulesError: null,
30-
refreshSchedules: () => {},
3121
location: history.location,
3222
history,
3323
match: {
@@ -40,10 +30,22 @@ const getMockSchedulesPageProps = (
4030
};
4131

4232
describe("Schedules", () => {
33+
let spy: jest.MockInstance<any, any>;
34+
afterEach(() => {
35+
spy.mockClear();
36+
});
37+
4338
it("renders expected schedules table columns", () => {
39+
spy = jest.spyOn(utils, "useSwrWithClusterId").mockReturnValue({
40+
data: allSchedulesFixture,
41+
isLoading: false,
42+
error: null,
43+
mutate: null,
44+
isValidating: false,
45+
});
4446
const { getByText } = render(
4547
<MemoryRouter>
46-
<SchedulesPage {...getMockSchedulesPageProps(allSchedulesFixture)} />
48+
<SchedulesPage {...getMockSchedulesPageProps()} />
4749
</MemoryRouter>,
4850
);
4951
const expectedColumnTitles = [
@@ -60,14 +62,57 @@ describe("Schedules", () => {
6062
for (const columnTitle of expectedColumnTitles) {
6163
getByText(columnTitle);
6264
}
65+
spy.mockClear();
6366
});
6467

6568
it("renders a message when the table is empty", () => {
69+
spy = jest.spyOn(utils, "useSwrWithClusterId").mockReturnValue({
70+
data: [],
71+
isLoading: false,
72+
error: null,
73+
mutate: null,
74+
isValidating: false,
75+
});
76+
6677
const { getByText } = render(
6778
<MemoryRouter>
68-
<SchedulesPage {...getMockSchedulesPageProps([])} />
79+
<SchedulesPage {...getMockSchedulesPageProps()} />
6980
</MemoryRouter>,
7081
);
7182
getByText("No schedules to show");
7283
});
84+
85+
it("renders an error message when there is an error retrieving data", () => {
86+
spy = jest.spyOn(utils, "useSwrWithClusterId").mockReturnValue({
87+
data: [],
88+
isLoading: false,
89+
error: new Error("Error retrieving data"),
90+
mutate: null,
91+
isValidating: false,
92+
});
93+
94+
const { getByText } = render(
95+
<MemoryRouter>
96+
<SchedulesPage {...getMockSchedulesPageProps()} />
97+
</MemoryRouter>,
98+
);
99+
getByText("Error retrieving data");
100+
});
101+
102+
it("renders a loading spinner when data is still loading", () => {
103+
spy = jest.spyOn(utils, "useSwrWithClusterId").mockReturnValue({
104+
data: [],
105+
isLoading: true,
106+
error: null,
107+
mutate: null,
108+
isValidating: false,
109+
});
110+
111+
const { getByTestId } = render(
112+
<MemoryRouter>
113+
<SchedulesPage {...getMockSchedulesPageProps()} />
114+
</MemoryRouter>,
115+
);
116+
getByTestId("loading-spinner");
117+
});
73118
});

pkg/ui/workspaces/cluster-ui/src/schedules/schedulesPage/schedulesPage.stories.tsx

Lines changed: 0 additions & 18 deletions
This file was deleted.

pkg/ui/workspaces/cluster-ui/src/schedules/schedulesPage/schedulesPage.tsx

Lines changed: 22 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,18 @@
55
import { InlineAlert } from "@cockroachlabs/ui-components";
66
import classNames from "classnames/bind";
77
import moment from "moment-timezone";
8-
import React, { useEffect } from "react";
8+
import React, { useEffect, useState } from "react";
99
import { Helmet } from "react-helmet";
1010
import { RouteComponentProps } from "react-router-dom";
1111

12-
import { Schedules } from "src/api/schedulesApi";
12+
import { getSchedules } from "src/api/schedulesApi";
1313
import { commonStyles } from "src/common";
1414
import { Delayed } from "src/delayed";
1515
import { Dropdown } from "src/dropdown";
1616
import { Loading } from "src/loading";
1717
import { PageConfig, PageConfigItem } from "src/pageConfig";
1818
import { SortSetting } from "src/sortedtable";
19-
import { syncHistory } from "src/util";
19+
import { syncHistory, useSwrWithClusterId } from "src/util";
2020

2121
import styles from "../schedules.module.scss";
2222

@@ -27,37 +27,18 @@ const cx = classNames.bind(styles);
2727

2828
export interface SchedulesPageStateProps {
2929
sort: SortSetting;
30-
status: string;
31-
show: string;
32-
schedules: Schedules;
33-
schedulesError: Error | null;
34-
schedulesLoading: boolean;
3530
}
3631

3732
export interface SchedulesPageDispatchProps {
3833
setSort: (value: SortSetting) => void;
39-
setStatus: (value: string) => void;
40-
setShow: (value: string) => void;
41-
refreshSchedules: (req: { status: string; limit: number }) => void;
42-
onFilterChange?: (req: { status: string; limit: number }) => void;
4334
}
4435

4536
export type SchedulesPageProps = SchedulesPageStateProps &
4637
SchedulesPageDispatchProps &
4738
RouteComponentProps;
4839

4940
export const SchedulesPage: React.FC<SchedulesPageProps> = props => {
50-
const {
51-
history,
52-
onFilterChange,
53-
refreshSchedules,
54-
status,
55-
setStatus,
56-
show,
57-
setShow,
58-
sort,
59-
setSort,
60-
} = props;
41+
const { history, sort, setSort } = props;
6142
const searchParams = new URLSearchParams(history.location.search);
6243

6344
// Sort Settings.
@@ -71,11 +52,14 @@ export const SchedulesPage: React.FC<SchedulesPageProps> = props => {
7152
setSort({ columnTitle, ascending });
7253
}, [setSort, columnTitle, ascending]);
7354

55+
const [status, setStatus] = useState(statusOptions[0].value);
56+
const [show, setShow] = useState(showOptions[0].value);
57+
7458
// Filter Status.
75-
const paramStatus = searchParams.get("status") || undefined;
59+
const paramStatus = searchParams.get("status");
7660
useEffect(() => {
7761
if (
78-
paramStatus === undefined ||
62+
!paramStatus ||
7963
statusOptions.find(option => option["value"] === paramStatus) ===
8064
undefined
8165
) {
@@ -85,26 +69,26 @@ export const SchedulesPage: React.FC<SchedulesPageProps> = props => {
8569
}, [paramStatus, setStatus]);
8670

8771
// Filter Show.
88-
const paramShow = searchParams.get("show") || undefined;
72+
const paramShow = searchParams.get("show");
8973
useEffect(() => {
9074
if (
91-
paramShow === undefined ||
75+
!paramShow ||
9276
showOptions.find(option => option["value"] === paramShow) === undefined
9377
) {
9478
return;
9579
}
9680
setShow(paramShow);
9781
}, [paramShow, setShow]);
9882

99-
useEffect(() => {
100-
const req = {
101-
status: status,
102-
limit: parseInt(show, 10),
103-
};
104-
105-
onFilterChange ? onFilterChange(req) : refreshSchedules(req);
106-
}, [status, show, refreshSchedules, onFilterChange]);
107-
83+
const limit = parseInt(show) ?? 0;
84+
const { data, error, isLoading } = useSwrWithClusterId(
85+
{
86+
name: "schedules",
87+
status,
88+
limit,
89+
},
90+
() => getSchedules({ status, limit }),
91+
);
10892
const onStatusSelected = (item: string) => {
10993
setStatus(item);
11094
syncHistory(
@@ -136,8 +120,6 @@ export const SchedulesPage: React.FC<SchedulesPageProps> = props => {
136120
);
137121
};
138122

139-
const isLoading = !props.schedules || props.schedulesLoading;
140-
const error = props.schedulesError;
141123
return (
142124
<div className={cx("schedules-page")}>
143125
<Helmet title="Schedules" />
@@ -175,8 +157,8 @@ export const SchedulesPage: React.FC<SchedulesPageProps> = props => {
175157
error={error}
176158
render={() => (
177159
<ScheduleTable
178-
isUsedFilter={status.length > 0}
179-
schedules={props.schedules}
160+
isUsedFilter={!!status}
161+
schedules={data}
180162
setSort={changeSortSetting}
181163
sort={sort}
182164
/>

pkg/ui/workspaces/db-console/src/redux/apiReducers.ts

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -466,20 +466,6 @@ const schemaInsightsReducerObj = new CachedDataReducer(
466466
);
467467
export const refreshSchemaInsights = schemaInsightsReducerObj.refresh;
468468

469-
export const schedulesKey = (req: { status: string; limit: number }) =>
470-
`${encodeURIComponent(req.status)}/${encodeURIComponent(
471-
req.limit?.toString(),
472-
)}`;
473-
474-
const schedulesReducerObj = new KeyedCachedDataReducer(
475-
clusterUiApi.getSchedules,
476-
"schedules",
477-
schedulesKey,
478-
moment.duration(10, "s"),
479-
moment.duration(1, "minute"),
480-
);
481-
export const refreshSchedules = schedulesReducerObj.refresh;
482-
483469
export const scheduleKey = (scheduleID: Long): string => scheduleID.toString();
484470

485471
const scheduleReducerObj = new KeyedCachedDataReducer(
@@ -592,7 +578,6 @@ export interface APIReducersState {
592578
statementFingerprintInsights: KeyedCachedDataReducerState<
593579
clusterUiApi.SqlApiResponse<StmtInsightEvent[]>
594580
>;
595-
schedules: KeyedCachedDataReducerState<clusterUiApi.Schedules>;
596581
schedule: KeyedCachedDataReducerState<clusterUiApi.Schedule>;
597582
snapshots: KeyedCachedDataReducerState<clusterUiApi.ListTracingSnapshotsResponse>;
598583
snapshot: KeyedCachedDataReducerState<clusterUiApi.GetTracingSnapshotResponse>;
@@ -644,7 +629,6 @@ export const apiReducersReducer = combineReducers<APIReducersState>({
644629
txnInsightDetailsReducerObj.reducer,
645630
[stmtInsightsReducerObj.actionNamespace]: stmtInsightsReducerObj.reducer,
646631
[schemaInsightsReducerObj.actionNamespace]: schemaInsightsReducerObj.reducer,
647-
[schedulesReducerObj.actionNamespace]: schedulesReducerObj.reducer,
648632
[scheduleReducerObj.actionNamespace]: scheduleReducerObj.reducer,
649633
[snapshotsReducerObj.actionNamespace]: snapshotsReducerObj.reducer,
650634
[snapshotReducerObj.actionNamespace]: snapshotReducerObj.reducer,

0 commit comments

Comments
 (0)