Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@
setColumnPinning: React.Dispatch<React.SetStateAction<ColumnPinningState>>;
/** Status Colors */
statusColors: Record<string, string>;
/** Mutate Jobs */
mutateJobs: () => void;
}

/**
Expand All @@ -85,6 +87,7 @@
columnPinning,
setColumnPinning,
statusColors,
mutateJobs,
}: JobDataTableProps) {
// Authentication
const { configuration } = useOIDCContext();
Expand Down Expand Up @@ -151,10 +154,8 @@
const selectedIds = Object.keys(rowSelection).map(Number);
await deleteJobs(diracxUrl, selectedIds, accessToken, accessTokenPayload);
setBackdropOpen(false);
// Refresh the data manually
setSearchBody((prev) => {
return { ...prev };
});
// Refresh the data
mutateJobs();
clearSelected();
setSnackbarInfo({
open: true,
Expand All @@ -175,13 +176,14 @@
} finally {
setBackdropOpen(false);
}
}, [

Check warning on line 179 in packages/diracx-web-components/src/components/JobMonitor/JobDataTable.tsx

View workflow job for this annotation

GitHub Actions / basic-checks

React Hook useCallback has an unnecessary dependency: 'setSearchBody'. Either exclude it or remove the dependency array
accessToken,
accessTokenPayload,
diracxUrl,
rowSelection,
clearSelected,
setSearchBody,
mutateJobs,
]);

/**
Expand All @@ -205,10 +207,8 @@

setBackdropOpen(false);

// Refresh the data manually
setSearchBody((prev) => {
return { ...prev };
});
// Refresh the data
mutateJobs();

clearSelected();
// Handle Snackbar Messaging
Expand Down Expand Up @@ -245,13 +245,14 @@
} finally {
setBackdropOpen(false);
}
}, [

Check warning on line 248 in packages/diracx-web-components/src/components/JobMonitor/JobDataTable.tsx

View workflow job for this annotation

GitHub Actions / basic-checks

React Hook useCallback has an unnecessary dependency: 'setSearchBody'. Either exclude it or remove the dependency array
accessToken,
accessTokenPayload,
diracxUrl,
rowSelection,
clearSelected,
setSearchBody,
mutateJobs,
]);

/**
Expand All @@ -273,10 +274,8 @@
const areSucceedJobs = Object.keys(data.success).length > 0;

setBackdropOpen(false);
// Refresh the data manually
setSearchBody((prev) => {
return { ...prev };
});
// Refresh the data
mutateJobs();
clearSelected();
// Handle Snackbar Messaging
if (areSucceedJobs && failedJobs.length > 0) {
Expand Down Expand Up @@ -312,7 +311,14 @@
} finally {
setBackdropOpen(false);
}
}, [accessToken, diracxUrl, rowSelection, clearSelected, setSearchBody]);
}, [

Check warning on line 314 in packages/diracx-web-components/src/components/JobMonitor/JobDataTable.tsx

View workflow job for this annotation

GitHub Actions / basic-checks

React Hook useCallback has an unnecessary dependency: 'setSearchBody'. Either exclude it or remove the dependency array
accessToken,
diracxUrl,
rowSelection,
clearSelected,
setSearchBody,
mutateJobs,
]);

/**
* Handle the history of the selected job
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
ColumnDef,
} from "@tanstack/react-table";

import { mutate } from "swr";
import { useApplicationId } from "../../hooks/application";
import { Filter } from "../../types/Filter";
import {
Expand All @@ -36,9 +37,11 @@ import {
CategoryType,
JobMonitorChartType,
} from "../../types";
import { useDiracxUrl } from "../../hooks";
import { JobDataTable } from "./JobDataTable";
import { JobSearchBar } from "./JobSearchBar";
import { JobSunburst } from "./JobSunburst";
import { getSearchJobUrl } from "./jobDataService";

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

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

const mutateJobs = useMemo(() => {
return () => {
mutate([
getSearchJobUrl(diracxUrl, pagination.pageIndex, pagination.pageSize),
searchBody,
]);
};
}, [diracxUrl, pagination.pageIndex, pagination.pageSize, searchBody]);

return (
<Box
sx={{
Expand Down Expand Up @@ -333,6 +346,7 @@ export default function JobMonitor() {
},
],
}}
mutateJobs={mutateJobs}
/>
</Box>

Expand All @@ -350,6 +364,7 @@ export default function JobMonitor() {
rowSelection={rowSelection}
setRowSelection={setRowSelection}
statusColors={statusColors}
mutateJobs={mutateJobs}
/>
)}
{chartType === JobMonitorChartType.SUNBURST && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ interface JobSearchBarProps {
/** The columns to display in the job monitor */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
columns: ColumnDef<Job, any>[];
/** Function to mutate the job data */
mutateJobs: () => void;
/** Props for the plot type selector */
plotTypeSelectorProps?: {
/** The type of the plot */
Expand All @@ -51,8 +53,10 @@ export function JobSearchBar({
setFilters,
handleApplyFilters,
columns,
mutateJobs,
plotTypeSelectorProps,
}: JobSearchBarProps) {
// Authentication
const { configuration } = useOIDCContext();
const { accessToken } = useOidcAccessToken(configuration?.scope);

Expand All @@ -66,6 +70,7 @@ export function JobSearchBar({
<SearchBar
filters={filters}
setFilters={setFilters}
refreshFunction={mutateJobs}
createSuggestions={({ previousToken, previousEquation, equationIndex }) =>
createSuggestions({
diracxUrl,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
"use client";
import useSWR from "swr";

import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";

import { useEffect, useState } from "react";

dayjs.extend(utc);
import { fetcher } from "../../hooks/utils";
import { Filter, SearchBody, Job, JobHistory } from "../../types";
import type { JobSummary } from "../../types";

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

dayjs.extend(utc);
/**
* Convert the 'last' operator in the search body to a date filter.
* @param searchBody The search body to be processed
Expand Down Expand Up @@ -249,64 +248,73 @@ export function useJobs(
page: number,
rowsPerPage: number,
) {
const [data, setData] = useState<Job[] | null>(null);
const [headers, setHeaders] = useState<Headers>(new Headers());
const [isLoading, setIsLoading] = useState<boolean>(true);
const [error, setError] = useState<Error | null>(null);

useEffect(() => {
if (!diracxUrl) {
setData(null);
setIsLoading(false);
setError(new Error("Invalid URL generated for fetching jobs."));
return;
}

let cancelled = false;

async function fetchJobs() {
setIsLoading(true);
/** The url to fetch jobs */
const urlGetJobs = getSearchJobUrl(diracxUrl, page, rowsPerPage);

const urlGetJobs = `${diracxUrl}/api/jobs/search?page=${page + 1}&per_page=${rowsPerPage}`;
try {
processSearchBody(searchBody);
/** The key used to revalidate the SWR cache */
const swrKey: [string, SearchBody] | null = urlGetJobs
? [urlGetJobs, searchBody]
: null;

const body = {
search: searchBody?.search || [],
sort: searchBody?.sort || [],
};

// Expect the response to be an array of objects with all the grouping fields
const res = await fetcher<Job[]>([
urlGetJobs,
accessToken,
"POST",
body,
]);

if (!cancelled) {
setData(res.data);
setIsLoading(false);
setHeaders(res.headers);
setError(null);
}
} catch {
setData(null);
setIsLoading(false);
setError(new Error("Failed to fetch jobs"));
const {
data: swrData,
error: swrError,
isLoading,
} = useSWR(
swrKey,
async ([url, _searchBody]) => {
processSearchBody(_searchBody);

const body = {
search: _searchBody.search || [],
sort: _searchBody.sort || [],
};

if (diracxUrl) {
return await fetcher<Job[]>([url, accessToken, "POST", body]);
}
}
fetchJobs();

return () => {
cancelled = true;
};
}, [diracxUrl, accessToken, searchBody, page, rowsPerPage]);
return {
headers: new Headers(),
data: null as Job[] | null,
isLoading: false,
error: new Error("Invalid URL generated for fetching jobs."),
};
},
{
revalidateOnMount: false,
revalidateOnFocus: false,
revalidateOnReconnect: false,
revalidateIfStale: false,
refreshInterval: 0,
shouldRetryOnError: false,
},
);

return {
headers,
data,
headers: swrData?.headers ?? new Headers(),
data: (swrData?.data as Job[] | undefined) ?? null,
isLoading,
error,
error: swrError,
};
}

/**
* Generates the URL for searching jobs.
*
* @param diracxUrl - The base URL of the DiracX API.
* @param page - The page number for pagination.
* @param rowsPerPage - The number of rows per page.
* @returns The URL for the job search API endpoint.
*/
export function getSearchJobUrl(
diracxUrl: string | null,
page: number,
rowsPerPage: number,
) {
if (!diracxUrl) {
return null;
}

return `${diracxUrl}/api/jobs/search?page=${page + 1}&per_page=${rowsPerPage}`;
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ export interface SearchBarProps<T extends string> {
equations: SearchBarTokenEquation[],
setFilters: React.Dispatch<React.SetStateAction<Filter[]>>,
) => void;
/** The function to call when the search is refreshed (optional) */
refreshFunction?: (
equations: SearchBarTokenEquation[],
setFilters: React.Dispatch<React.SetStateAction<Filter[]>>,
) => void;
/** The function to call when the search is cleared (optional) */
clearFunction?: (
setFilters: React.Dispatch<React.SetStateAction<Filter[]>>,
Expand Down Expand Up @@ -85,6 +90,7 @@ export function SearchBar<T extends string>({
createSuggestions,
searchFunction = convertAndApplyFilters,
clearFunction = defaultClearFunction,
refreshFunction = convertAndApplyFilters,
allowKeyWordSearch = true,
plotTypeSelectorProps,
}: SearchBarProps<T>) {
Expand Down Expand Up @@ -446,7 +452,7 @@ export function SearchBar<T extends string>({
</Box>
<Box sx={{ marginLeft: "auto", display: "flex", alignItems: "center" }}>
<IconButton
onClick={() => searchFunction(tokenEquations, setFilters)}
onClick={() => refreshFunction(tokenEquations, setFilters)}
disabled={
!tokenEquations.every((eq) => eq.status === EquationStatus.VALID)
}
Expand Down
5 changes: 5 additions & 0 deletions packages/diracx-web/test/e2e/jobMonitor.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,15 @@ describe("Job Monitor", () => {
) {
cy.log("No data available, adding jobs");
addJobs(55);
// Wait for the jobs to be created
cy.wait(2000);
} else {
cy.log("Data available, checking if enough jobs are present");
checkAndAddJobs(55);
}

// refresh the jobs
cy.get('[data-testid="RefreshIcon"]').click();
});
});

Expand Down