diff --git a/internal/lookout/ui/src/App.tsx b/internal/lookout/ui/src/App.tsx index 453f2336614..66903d5d162 100644 --- a/internal/lookout/ui/src/App.tsx +++ b/internal/lookout/ui/src/App.tsx @@ -134,6 +134,7 @@ export function App(props: AppProps): JSX.Element { logService={props.v2LogService} cordonService={props.v2CordonService} debug={props.debugEnabled} + autoRefreshMs={props.jobsAutoRefreshMs} /> } /> diff --git a/internal/lookout/ui/src/components/AutoRefreshToggle.tsx b/internal/lookout/ui/src/components/AutoRefreshToggle.tsx index 6eca4efa440..eaa2c8f1406 100644 --- a/internal/lookout/ui/src/components/AutoRefreshToggle.tsx +++ b/internal/lookout/ui/src/components/AutoRefreshToggle.tsx @@ -20,7 +20,7 @@ export default function AutoRefreshToggle(props: AutoRefreshToggle) { color="primary" /> } - label="Auto-refresh" + label="Auto refresh" labelPlacement="start" /> diff --git a/internal/lookout/ui/src/components/lookoutV2/JobsTableActionBar.tsx b/internal/lookout/ui/src/components/lookoutV2/JobsTableActionBar.tsx index cf2a906ab14..2da3e7dc22d 100644 --- a/internal/lookout/ui/src/components/lookoutV2/JobsTableActionBar.tsx +++ b/internal/lookout/ui/src/components/lookoutV2/JobsTableActionBar.tsx @@ -1,6 +1,7 @@ import React, { memo, useCallback, useMemo, useState } from "react" import { Divider, Button, Checkbox, FormControlLabel, FormGroup, Tooltip } from "@mui/material" +import AutoRefreshToggle from "components/AutoRefreshToggle" import RefreshButton from "components/RefreshButton" import ColumnSelect from "components/lookoutV2/ColumnSelect" import GroupBySelect from "components/lookoutV2/GroupBySelect" @@ -25,6 +26,8 @@ export interface JobsTableActionBarProps { activeJobSets: boolean onActiveJobSetsChanged: (newVal: boolean) => void onRefresh: () => void + autoRefresh: boolean + onAutoRefreshChange: (autoRefresh: boolean) => void onAddAnnotationColumn: (annotationKey: string) => void onRemoveAnnotationColumn: (colId: ColumnId) => void onEditAnnotationColumn: (colId: ColumnId, annotationKey: string) => void @@ -49,6 +52,8 @@ export const JobsTableActionBar = memo( activeJobSets, onActiveJobSetsChanged, onRefresh, + autoRefresh, + onAutoRefreshChange, onAddAnnotationColumn, onRemoveAnnotationColumn, onEditAnnotationColumn, @@ -110,10 +115,12 @@ export const JobsTableActionBar = memo( + + + - { logService={logService} cordonService={new FakeCordonService()} debug={false} + autoRefreshMs={30000} /> ) diff --git a/internal/lookout/ui/src/containers/lookoutV2/JobsTableContainer.tsx b/internal/lookout/ui/src/containers/lookoutV2/JobsTableContainer.tsx index dd4fe03eb8e..2b61f3b3448 100644 --- a/internal/lookout/ui/src/containers/lookoutV2/JobsTableContainer.tsx +++ b/internal/lookout/ui/src/containers/lookoutV2/JobsTableContainer.tsx @@ -41,6 +41,7 @@ import _ from "lodash" import { isJobGroupRow, JobRow, JobTableRow } from "models/jobsTableModels" import { Job, JobFilter, JobId, Match, SortDirection } from "models/lookoutV2Models" import { useLocation, useNavigate, useParams } from "react-router-dom" +import IntervalService from "services/IntervalService" import { IGetJobsService } from "services/lookoutV2/GetJobsService" import { IGetRunErrorService } from "services/lookoutV2/GetRunErrorService" import { IGroupJobsService } from "services/lookoutV2/GroupJobsService" @@ -87,6 +88,7 @@ interface JobsTableContainerProps { logService: ILogService cordonService: ICordonService debug: boolean + autoRefreshMs: number } export type LookoutColumnFilter = { @@ -130,6 +132,7 @@ export const JobsTableContainer = ({ logService, cordonService, debug, + autoRefreshMs, }: JobsTableContainerProps) => { const openSnackbar = useCustomSnackbar() @@ -176,6 +179,21 @@ export const JobsTableContainer = ({ }) const { pageIndex, pageSize } = useMemo(() => pagination, [pagination]) + const [autoRefresh, setAutoRefresh] = useState( + initialPrefs.autoRefresh === undefined ? true : initialPrefs.autoRefresh, + ) + + const autoRefreshService = useMemo(() => new IntervalService(autoRefreshMs), [autoRefreshMs]) + + const onAutoRefreshChange = (autoRefresh: boolean) => { + setAutoRefresh(autoRefresh) + if (autoRefresh) { + autoRefreshService.start() + } else { + autoRefreshService.stop() + } + } + // Filtering const [columnFilterState, setColumnFilterState] = useState(initialPrefs.filters) const [lookoutFilters, setLookoutFilters] = useState([]) // Parsed later @@ -251,6 +269,7 @@ export const JobsTableContainer = ({ sidebarJobId: sidebarJobId, sidebarWidth: sidebarWidth, activeJobSets: activeJobSets, + autoRefresh: autoRefresh, } } @@ -290,6 +309,9 @@ export const JobsTableContainer = ({ if (prefs.activeJobSets !== undefined) { setActiveJobSets(prefs.activeJobSets) } + if (prefs.autoRefresh !== undefined) { + onAutoRefreshChange(prefs.autoRefresh) + } // Have to manually set text fields to the filter values since they are uncontrolled setTextFields(prefs.filters) @@ -318,6 +340,7 @@ export const JobsTableContainer = ({ sidebarJobId, sidebarWidth, activeJobSets, + autoRefresh, ]) const addCustomView = (name: string) => { @@ -346,6 +369,14 @@ export const JobsTableContainer = ({ setRowsToFetch(pendingDataForAllVisibleData(expanded, data, pageSize, pageIndex * pageSize)) } + useEffect(() => { + autoRefreshService.registerCallback(onRefresh) + if (autoRefresh) { + autoRefreshService.start() + } + return () => autoRefreshService.stop() + }, []) + const onColumnVisibilityChange = (colIdToToggle: ColumnId) => { // Refresh if we make a new aggregate column visible let shouldRefresh = false @@ -710,6 +741,8 @@ export const JobsTableContainer = ({ onRefresh() }} onRefresh={onRefresh} + autoRefresh={autoRefresh} + onAutoRefreshChange={onAutoRefreshChange} onAddAnnotationColumn={addAnnotationCol} onRemoveAnnotationColumn={removeAnnotationCol} onEditAnnotationColumn={editAnnotationCol} diff --git a/internal/lookout/ui/src/services/lookoutV2/JobsTablePreferencesService.ts b/internal/lookout/ui/src/services/lookoutV2/JobsTablePreferencesService.ts index 2651cf902a0..75270fb16f9 100644 --- a/internal/lookout/ui/src/services/lookoutV2/JobsTablePreferencesService.ts +++ b/internal/lookout/ui/src/services/lookoutV2/JobsTablePreferencesService.ts @@ -30,6 +30,7 @@ export interface JobsTablePreferences { sidebarJobId: JobId | undefined sidebarWidth?: number activeJobSets?: boolean + autoRefresh?: boolean } // Need two 'defaults' @@ -81,6 +82,8 @@ export interface QueryStringPrefs { sb: string | undefined // This is a boolean field, but the qs library turns it into a string. active: string | undefined + // This is a boolean field, but the qs library turns it into a string. + refresh: string | undefined } const toQueryStringSafe = (prefs: JobsTablePreferences): QueryStringPrefs => { @@ -105,6 +108,7 @@ const toQueryStringSafe = (prefs: JobsTablePreferences): QueryStringPrefs => { ps: prefs.pageSize.toString(), sb: prefs.sidebarJobId, active: prefs.activeJobSets === undefined ? undefined : `${prefs.activeJobSets}`, + refresh: prefs.autoRefresh === undefined ? undefined : `${prefs.autoRefresh}`, } } @@ -124,7 +128,7 @@ const columnMatchesFromQueryStringFilters = (f: QueryStringJobFilter[]): Record< } const fromQueryStringSafe = (serializedPrefs: Partial): Partial => { - const { g, e, page, ps, sort, f, sb, active } = serializedPrefs + const { g, e, page, ps, sort, f, sb, active, refresh } = serializedPrefs return { ...(g && Array.isArray(g) && g.every((a) => typeof a === "string") && { groupedColumns: g as ColumnId[] }), ...(e && { expandedState: Object.fromEntries(e.map((rowId) => [rowId, true])) }), @@ -137,6 +141,7 @@ const fromQueryStringSafe = (serializedPrefs: Partial): Partia ...(f && { columnMatches: columnMatchesFromQueryStringFilters(f) }), ...(sb && { sidebarJobId: sb }), ...(active && { activeJobSets: active.toLowerCase() === "true" }), + ...(refresh && { autoRefresh: refresh.toLowerCase() === "true" }), } } @@ -182,6 +187,7 @@ const mergeQueryParamsAndLocalStorage = ( } mergeColumnMatches(mergedPrefs.columnMatches, queryParamPrefs.columnMatches) mergedPrefs.activeJobSets = queryParamPrefs.activeJobSets + mergedPrefs.autoRefresh = queryParamPrefs.autoRefresh } return mergedPrefs }