From 5368449e3fd09312bebb0774702ffd87ca841827 Mon Sep 17 00:00:00 2001 From: Aaron Chong Date: Fri, 7 Jun 2024 17:25:14 +0800 Subject: [PATCH] Shows outdated tasks as interrupted Signed-off-by: Aaron Chong --- .../src/components/tasks/tasks-app.tsx | 4 +- .../src/managers/resource-manager.ts | 3 + .../lib/tasks/task-table-datagrid.tsx | 57 ++++++++++++++++++- 3 files changed, 60 insertions(+), 4 deletions(-) diff --git a/packages/dashboard/src/components/tasks/tasks-app.tsx b/packages/dashboard/src/components/tasks/tasks-app.tsx index 3980d3e37..6dcfe6ddc 100644 --- a/packages/dashboard/src/components/tasks/tasks-app.tsx +++ b/packages/dashboard/src/components/tasks/tasks-app.tsx @@ -23,7 +23,7 @@ import { Tasks, Window, } from 'react-components'; -import { AppControllerContext } from '../app-contexts'; +import { AppControllerContext, ResourcesContext } from '../app-contexts'; import { AppEvents } from '../app-events'; import { MicroAppProps } from '../micro-app'; import { RmfAppContext } from '../rmf-app'; @@ -85,6 +85,7 @@ export const TasksApp = React.memo( ) => { const rmf = React.useContext(RmfAppContext); const appController = React.useContext(AppControllerContext); + const resourcesContext = React.useContext(ResourcesContext); const [autoRefresh, setAutoRefresh] = React.useState(true); const [refreshTaskAppCount, setRefreshTaskAppCount] = React.useState(0); const [selectedPanelIndex, setSelectedPanelIndex] = React.useState(TaskTablePanel.QueueTable); @@ -424,6 +425,7 @@ export const TasksApp = React.memo( { setSelectedTask(task); if (task.assigned_to) { diff --git a/packages/dashboard/src/managers/resource-manager.ts b/packages/dashboard/src/managers/resource-manager.ts index 4227326a4..67cc08218 100644 --- a/packages/dashboard/src/managers/resource-manager.ts +++ b/packages/dashboard/src/managers/resource-manager.ts @@ -18,6 +18,7 @@ export interface ResourceConfigurationsType { attributionPrefix?: string; cartIds?: string[]; loggedInDisplayLevel?: string; + dismissStaleTasks?: boolean; } export default class ResourceManager { @@ -32,6 +33,7 @@ export default class ResourceManager { attributionPrefix?: string; cartIds?: string[]; loggedInDisplayLevel?: string; + dismissStaleTasks?: boolean; /** * Gets the default resource manager using the embedded resource file (aka "assets/resources/main.json"). @@ -71,6 +73,7 @@ export default class ResourceManager { this.attributionPrefix = resources.attributionPrefix || 'OSRC-SG'; this.cartIds = resources.cartIds || []; this.loggedInDisplayLevel = resources.loggedInDisplayLevel; + this.dismissStaleTasks = resources.dismissStaleTasks ?? false; } } diff --git a/packages/react-components/lib/tasks/task-table-datagrid.tsx b/packages/react-components/lib/tasks/task-table-datagrid.tsx index d63c3cc12..8de8f76b2 100644 --- a/packages/react-components/lib/tasks/task-table-datagrid.tsx +++ b/packages/react-components/lib/tasks/task-table-datagrid.tsx @@ -11,7 +11,16 @@ import { GridFilterModel, GridSortModel, } from '@mui/x-data-grid'; -import { styled, Stack, Typography, Tooltip, useMediaQuery, SxProps, Theme } from '@mui/material'; +import { + Box, + styled, + Stack, + Typography, + Tooltip, + useMediaQuery, + SxProps, + Theme, +} from '@mui/material'; import * as React from 'react'; import { TaskState, ApiServerModelsRmfApiTaskStateStatus as Status } from 'api-client'; import { InsertInvitation as ScheduleIcon, Person as UserIcon } from '@mui/icons-material/'; @@ -54,6 +63,20 @@ const StyledDataGrid = styled(DataGrid)(({ theme }) => ({ }, })); +function isTaskOutdated(taskState: TaskState): boolean { + if ( + !taskState.unix_millis_finish_time || + !taskState.status || + (taskState.status !== Status.Underway && taskState.status !== Status.Queued) + ) { + return false; + } + + const finishDateTime = new Date(taskState.unix_millis_finish_time); + const nowDateTime = new Date(); + return nowDateTime > finishDateTime; +} + export interface Tasks { isLoading: boolean; data: TaskState[]; @@ -74,6 +97,7 @@ export type MuiMouseEvent = MuiEvent>; export interface TableDataGridState { tasks: Tasks; + dismissStaleTasks: boolean; onTaskClick?(ev: MuiMouseEvent, task: TaskState): void; onPageChange: (newPage: number) => void; onPageSizeChange: (newPageSize: number) => void; @@ -122,6 +146,7 @@ const TaskRequester = (requester: string | null, sx: SxProps): JSX.Elemen export function TaskDataGridTable({ tasks, + dismissStaleTasks, onTaskClick, onPageChange, onPageSizeChange, @@ -251,8 +276,30 @@ export function TaskDataGridTable({ field: 'status', headerName: 'State', editable: false, - valueGetter: (params: GridValueGetterParams) => - params.row.status ? params.row.status : 'unknown', + renderCell: (cellValues) => { + const statusString = cellValues.row.status ? cellValues.row.status : 'unknown'; + if (dismissStaleTasks && isTaskOutdated(cellValues.row)) { + return ( + + + Finish time is in the past, but task is still queued or underway. + + + The task may have been interrupted or stalled during the execution. + + Last status update: {statusString} + + } + > + {'interrupted'} + + ); + } + + return {statusString}; + }, flex: 1, filterOperators: getMinimalStringFilterOperators, filterable: true, @@ -299,6 +346,10 @@ export function TaskDataGridTable({ onRowClick={handleEvent} getCellClassName={(params: GridCellParams) => { if (params.field === 'status') { + if (isTaskOutdated(params.row)) { + return classes.taskUnknownCell; + } + switch (params.value) { case Status.Underway: return classes.taskActiveCell;