Loading src/components/jobs/JobList.tsx +1 −5 Original line number Diff line number Diff line Loading @@ -37,14 +37,12 @@ export function JobList({ totalJobs, fetchNextPage, hasNextPage, isFetchingNextPage, onSort, }: { jobs: Job[]; totalJobs: number; fetchNextPage: (options?: FetchNextPageOptions | undefined) => void; hasNextPage: boolean; isFetchingNextPage: boolean; onSort: (header: string, sorted: boolean, direction: "asc" | "desc") => void; }) { const navigate = useNavigate({ from: JobsRoute.fullPath }); Loading Loading @@ -73,8 +71,7 @@ export function JobList({ if ( lastItem.index >= rows.length - 1 && hasNextPage && !isFetchingNextPage hasNextPage ) { fetchNextPage(); } Loading @@ -82,7 +79,6 @@ export function JobList({ hasNextPage, fetchNextPage, rows.length, isFetchingNextPage, rowVirtualizer.getVirtualItems(), ]); Loading src/routes/simulations.$simulationId.jobs.tsx +19 −35 Original line number Diff line number Diff line import { createFileRoute } from "@tanstack/react-router"; import { ListResponse } from "../util/queryOptions"; import { useInfiniteQuery } from "@tanstack/react-query"; import { simulationConfigurationQueryOptions } from "../util/queryOptions"; import { useQuery } from "@tanstack/react-query"; import { LoadingSpinner } from "../components/shared/loadingSpinner"; import { JobList } from "../components/jobs/JobList"; import { headers as JobColumns } from "../components/jobs/list/JobListColumns"; import { ArrowDownTrayIcon } from "@heroicons/react/24/outline"; import { operatorCombinator, sortCombinator } from "../util/filterCombinator"; import { Job } from "../models/Job.model"; import axios from "axios"; import { toDate } from "date-fns"; import { useState } from "react"; import { Tooltip } from "react-tooltip"; import { JobListFilterModal } from "../components/jobs/list/JobListFilterModal"; import { useJobReplay } from "../util/hooks/useReplay"; export const Route = createFileRoute("/simulations/$simulationId/jobs")({ component: SimulationJobs, }); const jobLimit = 15; function SimulationJobs() { const { simulationId } = Route.useParams(); const search = Route.useSearch(); const currentTimestamp = search.currentTimestamp ? toDate(search.currentTimestamp) : undefined; const [columns, setColumns] = useState(structuredClone(JobColumns)); const { data, isLoading, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteQuery({ queryKey: ["simulation", "jobs", simulationId, columns], queryFn: async ({ pageParam }) => { const sortParams = sortCombinator(columns); const filterParams = operatorCombinator(columns); const fields = `&fields=job_id,name,node_count,state_current,time_limit,time_start,time_end,time_submission`; const params = fields + (sortParams ? "&" : "") + sortParams + (filterParams ? "&" : "") + filterParams; const res = await axios.get<ListResponse<Job>>( `/frontier/simulation/${simulationId}/scheduler/jobs?limit=${jobLimit}&offset=${pageParam * jobLimit}${params}`, ); return res.data; }, initialPageParam: 0, getNextPageParam: (lastPage, allPages) => allPages.length <= lastPage.total_results / jobLimit ? allPages.length : undefined, refetchOnWindowFocus: false, }); const { data: sim } = useQuery(simulationConfigurationQueryOptions(simulationId)); const { data: jobs, totalResults, fetchNextPage, hasNextPage } = useJobReplay({ sim: sim, timestamp: currentTimestamp, stepInterval: search.playbackInterval, summarize: !currentTimestamp, sort: sortCombinator(columns), filters: operatorCombinator(columns), }) const onSort = ( header: string, Loading @@ -66,11 +52,10 @@ function SimulationJobs() { setColumns(updatedColumns); }; if (isLoading) { if (!jobs) { return <LoadingSpinner />; } const jobs = data?.pages.map((page) => page.results).flat() ?? []; return ( <div className="flex flex-1 flex-col overflow-hidden"> <div className="flex items-center justify-end gap-4 px-4 py-4"> Loading @@ -87,10 +72,9 @@ function SimulationJobs() { </div> <JobList jobs={jobs} totalJobs={data?.pages[0].total_results ?? 0} totalJobs={totalResults} fetchNextPage={fetchNextPage} hasNextPage={hasNextPage} isFetchingNextPage={isFetchingNextPage} onSort={onSort} /> </div> Loading src/util/filterCombinator.ts +3 −19 Original line number Diff line number Diff line import { ColumnHeader } from "../models/dataGrid/columnHeader.model"; export function sortCombinator(columns: ColumnHeader[]) { return columns.reduce((prev, curr) => { if (curr.sort.sorted) { return ( prev + `${prev ? "&" : ""}sort=${curr.sort.direction}:${curr.propertyName}` ); } return prev + ""; }, ""); return columns .flatMap(col => col.sort.sorted ? [`${col.sort.direction}:${col.propertyName}`] : []) } export function operatorCombinator(columns: ColumnHeader[]) { return columns .filter((column) => column.activeFilters.length > 0) .reduce((prev, curr) => { const columnFilters = curr.activeFilters.reduce( (previousFilters, currentFilter) => previousFilters + `${previousFilters ? "&" : ""}${curr.propertyName}=${currentFilter.operator}:${currentFilter.value}`, "" ); return prev + `${prev ? "&" : ""}${columnFilters}`; }, ""); .flatMap(col => col.activeFilters.map(f => `${col.propertyName}=${f.operator}:${f.value}`)) } Loading
src/components/jobs/JobList.tsx +1 −5 Original line number Diff line number Diff line Loading @@ -37,14 +37,12 @@ export function JobList({ totalJobs, fetchNextPage, hasNextPage, isFetchingNextPage, onSort, }: { jobs: Job[]; totalJobs: number; fetchNextPage: (options?: FetchNextPageOptions | undefined) => void; hasNextPage: boolean; isFetchingNextPage: boolean; onSort: (header: string, sorted: boolean, direction: "asc" | "desc") => void; }) { const navigate = useNavigate({ from: JobsRoute.fullPath }); Loading Loading @@ -73,8 +71,7 @@ export function JobList({ if ( lastItem.index >= rows.length - 1 && hasNextPage && !isFetchingNextPage hasNextPage ) { fetchNextPage(); } Loading @@ -82,7 +79,6 @@ export function JobList({ hasNextPage, fetchNextPage, rows.length, isFetchingNextPage, rowVirtualizer.getVirtualItems(), ]); Loading
src/routes/simulations.$simulationId.jobs.tsx +19 −35 Original line number Diff line number Diff line import { createFileRoute } from "@tanstack/react-router"; import { ListResponse } from "../util/queryOptions"; import { useInfiniteQuery } from "@tanstack/react-query"; import { simulationConfigurationQueryOptions } from "../util/queryOptions"; import { useQuery } from "@tanstack/react-query"; import { LoadingSpinner } from "../components/shared/loadingSpinner"; import { JobList } from "../components/jobs/JobList"; import { headers as JobColumns } from "../components/jobs/list/JobListColumns"; import { ArrowDownTrayIcon } from "@heroicons/react/24/outline"; import { operatorCombinator, sortCombinator } from "../util/filterCombinator"; import { Job } from "../models/Job.model"; import axios from "axios"; import { toDate } from "date-fns"; import { useState } from "react"; import { Tooltip } from "react-tooltip"; import { JobListFilterModal } from "../components/jobs/list/JobListFilterModal"; import { useJobReplay } from "../util/hooks/useReplay"; export const Route = createFileRoute("/simulations/$simulationId/jobs")({ component: SimulationJobs, }); const jobLimit = 15; function SimulationJobs() { const { simulationId } = Route.useParams(); const search = Route.useSearch(); const currentTimestamp = search.currentTimestamp ? toDate(search.currentTimestamp) : undefined; const [columns, setColumns] = useState(structuredClone(JobColumns)); const { data, isLoading, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteQuery({ queryKey: ["simulation", "jobs", simulationId, columns], queryFn: async ({ pageParam }) => { const sortParams = sortCombinator(columns); const filterParams = operatorCombinator(columns); const fields = `&fields=job_id,name,node_count,state_current,time_limit,time_start,time_end,time_submission`; const params = fields + (sortParams ? "&" : "") + sortParams + (filterParams ? "&" : "") + filterParams; const res = await axios.get<ListResponse<Job>>( `/frontier/simulation/${simulationId}/scheduler/jobs?limit=${jobLimit}&offset=${pageParam * jobLimit}${params}`, ); return res.data; }, initialPageParam: 0, getNextPageParam: (lastPage, allPages) => allPages.length <= lastPage.total_results / jobLimit ? allPages.length : undefined, refetchOnWindowFocus: false, }); const { data: sim } = useQuery(simulationConfigurationQueryOptions(simulationId)); const { data: jobs, totalResults, fetchNextPage, hasNextPage } = useJobReplay({ sim: sim, timestamp: currentTimestamp, stepInterval: search.playbackInterval, summarize: !currentTimestamp, sort: sortCombinator(columns), filters: operatorCombinator(columns), }) const onSort = ( header: string, Loading @@ -66,11 +52,10 @@ function SimulationJobs() { setColumns(updatedColumns); }; if (isLoading) { if (!jobs) { return <LoadingSpinner />; } const jobs = data?.pages.map((page) => page.results).flat() ?? []; return ( <div className="flex flex-1 flex-col overflow-hidden"> <div className="flex items-center justify-end gap-4 px-4 py-4"> Loading @@ -87,10 +72,9 @@ function SimulationJobs() { </div> <JobList jobs={jobs} totalJobs={data?.pages[0].total_results ?? 0} totalJobs={totalResults} fetchNextPage={fetchNextPage} hasNextPage={hasNextPage} isFetchingNextPage={isFetchingNextPage} onSort={onSort} /> </div> Loading
src/util/filterCombinator.ts +3 −19 Original line number Diff line number Diff line import { ColumnHeader } from "../models/dataGrid/columnHeader.model"; export function sortCombinator(columns: ColumnHeader[]) { return columns.reduce((prev, curr) => { if (curr.sort.sorted) { return ( prev + `${prev ? "&" : ""}sort=${curr.sort.direction}:${curr.propertyName}` ); } return prev + ""; }, ""); return columns .flatMap(col => col.sort.sorted ? [`${col.sort.direction}:${col.propertyName}`] : []) } export function operatorCombinator(columns: ColumnHeader[]) { return columns .filter((column) => column.activeFilters.length > 0) .reduce((prev, curr) => { const columnFilters = curr.activeFilters.reduce( (previousFilters, currentFilter) => previousFilters + `${previousFilters ? "&" : ""}${curr.propertyName}=${currentFilter.operator}:${currentFilter.value}`, "" ); return prev + `${prev ? "&" : ""}${columnFilters}`; }, ""); .flatMap(col => col.activeFilters.map(f => `${col.propertyName}=${f.operator}:${f.value}`)) }