Skip to content

Commit 964234d

Browse files
TheauWaldbr
authored andcommitted
feat: avoid re fetching the jobs when switching from an app to another
1 parent 43207fd commit 964234d

File tree

6 files changed

+107
-68
lines changed

6 files changed

+107
-68
lines changed

packages/diracx-web-components/src/components/JobMonitor/JobDataTable.tsx

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ interface JobDataTableProps {
6767
setColumnPinning: React.Dispatch<React.SetStateAction<ColumnPinningState>>;
6868
/** Status Colors */
6969
statusColors: Record<string, string>;
70+
/** Mutate Jobs */
71+
mutateJobs: () => void;
7072
}
7173

7274
/**
@@ -85,6 +87,7 @@ export function JobDataTable({
8587
columnPinning,
8688
setColumnPinning,
8789
statusColors,
90+
mutateJobs,
8891
}: JobDataTableProps) {
8992
// Authentication
9093
const { configuration } = useOIDCContext();
@@ -151,10 +154,8 @@ export function JobDataTable({
151154
const selectedIds = Object.keys(rowSelection).map(Number);
152155
await deleteJobs(diracxUrl, selectedIds, accessToken, accessTokenPayload);
153156
setBackdropOpen(false);
154-
// Refresh the data manually
155-
setSearchBody((prev) => {
156-
return { ...prev };
157-
});
157+
// Refresh the data
158+
mutateJobs();
158159
clearSelected();
159160
setSnackbarInfo({
160161
open: true,
@@ -182,6 +183,7 @@ export function JobDataTable({
182183
rowSelection,
183184
clearSelected,
184185
setSearchBody,
186+
mutateJobs,
185187
]);
186188

187189
/**
@@ -205,10 +207,8 @@ export function JobDataTable({
205207

206208
setBackdropOpen(false);
207209

208-
// Refresh the data manually
209-
setSearchBody((prev) => {
210-
return { ...prev };
211-
});
210+
// Refresh the data
211+
mutateJobs();
212212

213213
clearSelected();
214214
// Handle Snackbar Messaging
@@ -252,6 +252,7 @@ export function JobDataTable({
252252
rowSelection,
253253
clearSelected,
254254
setSearchBody,
255+
mutateJobs,
255256
]);
256257

257258
/**
@@ -273,10 +274,8 @@ export function JobDataTable({
273274
const areSucceedJobs = Object.keys(data.success).length > 0;
274275

275276
setBackdropOpen(false);
276-
// Refresh the data manually
277-
setSearchBody((prev) => {
278-
return { ...prev };
279-
});
277+
// Refresh the data
278+
mutateJobs();
280279
clearSelected();
281280
// Handle Snackbar Messaging
282281
if (areSucceedJobs && failedJobs.length > 0) {
@@ -312,7 +311,14 @@ export function JobDataTable({
312311
} finally {
313312
setBackdropOpen(false);
314313
}
315-
}, [accessToken, diracxUrl, rowSelection, clearSelected, setSearchBody]);
314+
}, [
315+
accessToken,
316+
diracxUrl,
317+
rowSelection,
318+
clearSelected,
319+
setSearchBody,
320+
mutateJobs,
321+
]);
316322

317323
/**
318324
* Handle the history of the selected job

packages/diracx-web-components/src/components/JobMonitor/JobMonitor.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
ColumnDef,
2929
} from "@tanstack/react-table";
3030

31+
import { mutate } from "swr";
3132
import { useApplicationId } from "../../hooks/application";
3233
import { Filter } from "../../types/Filter";
3334
import {
@@ -36,9 +37,11 @@ import {
3637
CategoryType,
3738
JobMonitorChartType,
3839
} from "../../types";
40+
import { useDiracxUrl } from "../../hooks";
3941
import { JobDataTable } from "./JobDataTable";
4042
import { JobSearchBar } from "./JobSearchBar";
4143
import { JobSunburst } from "./JobSunburst";
44+
import { getSearchJobUrl } from "./jobDataService";
4245

4346
/**
4447
* Build the Job Monitor application
@@ -48,6 +51,7 @@ import { JobSunburst } from "./JobSunburst";
4851
export default function JobMonitor() {
4952
const appId = useApplicationId();
5053
const theme = useTheme();
54+
const diracxUrl = useDiracxUrl();
5155

5256
// Load the initial state from local storage
5357
const initialState = sessionStorage.getItem(`${appId}_State`);
@@ -298,6 +302,15 @@ export default function JobMonitor() {
298302
}));
299303
}, [filters, columns, setSearchBody, setPagination]);
300304

305+
const mutateJobs = useMemo(() => {
306+
return () => {
307+
mutate([
308+
getSearchJobUrl(diracxUrl, pagination.pageIndex, pagination.pageSize),
309+
searchBody,
310+
]);
311+
};
312+
}, [diracxUrl, pagination.pageIndex, pagination.pageSize, searchBody]);
313+
301314
return (
302315
<Box
303316
sx={{
@@ -333,6 +346,7 @@ export default function JobMonitor() {
333346
},
334347
],
335348
}}
349+
mutateJobs={mutateJobs}
336350
/>
337351
</Box>
338352

@@ -350,6 +364,7 @@ export default function JobMonitor() {
350364
rowSelection={rowSelection}
351365
setRowSelection={setRowSelection}
352366
statusColors={statusColors}
367+
mutateJobs={mutateJobs}
353368
/>
354369
)}
355370
{chartType === JobMonitorChartType.SUNBURST && (

packages/diracx-web-components/src/components/JobMonitor/JobSearchBar.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ interface JobSearchBarProps {
3434
/** The columns to display in the job monitor */
3535
// eslint-disable-next-line @typescript-eslint/no-explicit-any
3636
columns: ColumnDef<Job, any>[];
37+
/** Function to mutate the job data */
38+
mutateJobs: () => void;
3739
/** Props for the plot type selector */
3840
plotTypeSelectorProps?: {
3941
/** The type of the plot */
@@ -51,8 +53,10 @@ export function JobSearchBar({
5153
setFilters,
5254
handleApplyFilters,
5355
columns,
56+
mutateJobs,
5457
plotTypeSelectorProps,
5558
}: JobSearchBarProps) {
59+
// Authentication
5660
const { configuration } = useOIDCContext();
5761
const { accessToken } = useOidcAccessToken(configuration?.scope);
5862

@@ -66,6 +70,7 @@ export function JobSearchBar({
6670
<SearchBar
6771
filters={filters}
6872
setFilters={setFilters}
73+
refreshFunction={mutateJobs}
6974
createSuggestions={({ previousToken, previousEquation, equationIndex }) =>
7075
createSuggestions({
7176
diracxUrl,

packages/diracx-web-components/src/components/JobMonitor/jobDataService.ts

Lines changed: 56 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
"use client";
2+
import useSWR from "swr";
23

34
import dayjs from "dayjs";
45
import utc from "dayjs/plugin/utc";
56

6-
import { useEffect, useState } from "react";
7-
8-
dayjs.extend(utc);
97
import { fetcher } from "../../hooks/utils";
108
import { Filter, SearchBody, Job, JobHistory } from "../../types";
119
import type { JobSummary } from "../../types";
1210

1311
type TimeUnit = "minute" | "hour" | "day" | "month" | "year";
1412

13+
dayjs.extend(utc);
1514
/**
1615
* Convert the 'last' operator in the search body to a date filter.
1716
* @param searchBody The search body to be processed
@@ -249,56 +248,39 @@ export function useJobs(
249248
page: number,
250249
rowsPerPage: number,
251250
) {
252-
const [data, setData] = useState<Job[] | null>(null);
253-
const [headers, setHeaders] = useState<Headers>(new Headers());
254-
const [isLoading, setIsLoading] = useState<boolean>(true);
255-
const [error, setError] = useState<Error | null>(null);
256-
257-
useEffect(() => {
258-
if (!diracxUrl) {
259-
return;
260-
}
261-
262-
let cancelled = false;
251+
/** The url to fetch jobs */
252+
const urlGetJobs = getSearchJobUrl(diracxUrl, page, rowsPerPage);
263253

264-
async function fetchJobs() {
265-
setIsLoading(true);
254+
/** The key used to revalidate the SWR cache */
255+
const swrKey: [string, SearchBody] | null = urlGetJobs
256+
? [urlGetJobs, searchBody]
257+
: null;
266258

267-
const urlGetJobs = `${diracxUrl}/api/jobs/search?page=${page + 1}&per_page=${rowsPerPage}`;
268-
try {
269-
processSearchBody(searchBody);
270-
271-
const body = {
272-
search: searchBody?.search || [],
273-
sort: searchBody?.sort || [],
274-
};
275-
276-
// Expect the response to be an array of objects with all the grouping fields
277-
const res = await fetcher<Job[]>([
278-
urlGetJobs,
279-
accessToken,
280-
"POST",
281-
body,
282-
]);
283-
284-
if (!cancelled) {
285-
setData(res.data);
286-
setIsLoading(false);
287-
setHeaders(res.headers);
288-
setError(null);
289-
}
290-
} catch {
291-
setData(null);
292-
setIsLoading(false);
293-
setError(new Error("Failed to fetch jobs"));
294-
}
295-
}
296-
fetchJobs();
297-
298-
return () => {
299-
cancelled = true;
300-
};
301-
}, [diracxUrl, accessToken, searchBody, page, rowsPerPage]);
259+
const {
260+
data: swrData,
261+
error: swrError,
262+
isLoading,
263+
} = useSWR(
264+
swrKey,
265+
async ([url, _searchBody]) => {
266+
processSearchBody(_searchBody);
267+
268+
const body = {
269+
search: _searchBody.search || [],
270+
sort: _searchBody.sort || [],
271+
};
272+
273+
return await fetcher<Job[]>([url, accessToken, "POST", body]);
274+
},
275+
{
276+
revalidateOnMount: true,
277+
revalidateOnFocus: false,
278+
revalidateOnReconnect: false,
279+
revalidateIfStale: false,
280+
dedupingInterval: 60000,
281+
shouldRetryOnError: false,
282+
},
283+
);
302284

303285
if (!diracxUrl) {
304286
return {
@@ -310,9 +292,29 @@ export function useJobs(
310292
}
311293

312294
return {
313-
headers,
314-
data,
295+
headers: swrData?.headers ?? new Headers(),
296+
data: (swrData?.data as Job[] | undefined) ?? null,
315297
isLoading,
316-
error,
298+
error: swrError,
317299
};
318300
}
301+
302+
/**
303+
* Generates the URL for searching jobs.
304+
*
305+
* @param diracxUrl - The base URL of the DiracX API.
306+
* @param page - The page number for pagination.
307+
* @param rowsPerPage - The number of rows per page.
308+
* @returns The URL for the job search API endpoint.
309+
*/
310+
export function getSearchJobUrl(
311+
diracxUrl: string | null,
312+
page: number,
313+
rowsPerPage: number,
314+
) {
315+
if (!diracxUrl) {
316+
return null;
317+
}
318+
319+
return `${diracxUrl}/api/jobs/search?page=${page + 1}&per_page=${rowsPerPage}`;
320+
}

packages/diracx-web-components/src/components/shared/SearchBar/SearchBar.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ export interface SearchBarProps<T extends string> {
5656
equations: SearchBarTokenEquation[],
5757
setFilters: React.Dispatch<React.SetStateAction<Filter[]>>,
5858
) => void;
59+
/** The function to call when the search is refreshed (optional) */
60+
refreshFunction?: (
61+
equations: SearchBarTokenEquation[],
62+
setFilters: React.Dispatch<React.SetStateAction<Filter[]>>,
63+
) => void;
5964
/** The function to call when the search is cleared (optional) */
6065
clearFunction?: (
6166
setFilters: React.Dispatch<React.SetStateAction<Filter[]>>,
@@ -85,6 +90,7 @@ export function SearchBar<T extends string>({
8590
createSuggestions,
8691
searchFunction = convertAndApplyFilters,
8792
clearFunction = defaultClearFunction,
93+
refreshFunction = convertAndApplyFilters,
8894
allowKeyWordSearch = true,
8995
plotTypeSelectorProps,
9096
}: SearchBarProps<T>) {
@@ -446,7 +452,7 @@ export function SearchBar<T extends string>({
446452
</Box>
447453
<Box sx={{ marginLeft: "auto", display: "flex", alignItems: "center" }}>
448454
<IconButton
449-
onClick={() => searchFunction(tokenEquations, setFilters)}
455+
onClick={() => refreshFunction(tokenEquations, setFilters)}
450456
disabled={
451457
!tokenEquations.every((eq) => eq.status === EquationStatus.VALID)
452458
}

packages/diracx-web/test/e2e/jobMonitor.cy.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,15 @@ describe("Job Monitor", () => {
8181
) {
8282
cy.log("No data available, adding jobs");
8383
addJobs(55);
84+
// Wait for the jobs to be created
85+
cy.wait(2000);
8486
} else {
8587
cy.log("Data available, checking if enough jobs are present");
8688
checkAndAddJobs(55);
8789
}
90+
91+
// refresh the jobs
92+
cy.get('[data-testid="RefreshIcon"]').click();
8893
});
8994
});
9095

0 commit comments

Comments
 (0)