From cbe9c556b358fcf1a7fa84a615f84888f9a94f11 Mon Sep 17 00:00:00 2001 From: Aaron Chong Date: Fri, 7 Jun 2024 16:16:03 +0800 Subject: [PATCH] Warning time to only display color and use labels Signed-off-by: Aaron Chong --- .../models/tortoise_models/tasks.py | 1 - .../api-server/api_server/routes/internal.py | 11 --- .../api_server/routes/tasks/tasks.py | 4 - .../src/components/tasks/task-alert.tsx | 29 ------ .../lib/tasks/create-task.tsx | 48 ++++++---- .../lib/tasks/task-booking-label-utils.tsx | 22 ++++- .../lib/tasks/task-table-datagrid.tsx | 95 +++++++++++++++++-- 7 files changed, 135 insertions(+), 75 deletions(-) diff --git a/packages/api-server/api_server/models/tortoise_models/tasks.py b/packages/api-server/api_server/models/tortoise_models/tasks.py index 6e3f3e665..23f0b4a62 100644 --- a/packages/api-server/api_server/models/tortoise_models/tasks.py +++ b/packages/api-server/api_server/models/tortoise_models/tasks.py @@ -28,7 +28,6 @@ class TaskState(Model): status = CharField(255, null=True, index=True) unix_millis_request_time = DatetimeField(null=True, index=True) requester = CharField(255, null=True, index=True) - unix_millis_warn_time = DatetimeField(null=True, index=True) pickup = CharField(255, null=True, index=True) destination = CharField(255, null=True, index=True) labels = ReverseRelation["TaskLabel"] diff --git a/packages/api-server/api_server/routes/internal.py b/packages/api-server/api_server/routes/internal.py index 0e6b94226..cca37fbc7 100644 --- a/packages/api-server/api_server/routes/internal.py +++ b/packages/api-server/api_server/routes/internal.py @@ -104,17 +104,6 @@ async def process_msg( alert = await alert_repo.create_alert(task_state.booking.id, "task") if alert is not None: alert_events.alerts.on_next(alert) - elif ( - task_state.unix_millis_finish_time - and task_state.unix_millis_warn_time - and task_state.unix_millis_finish_time > task_state.unix_millis_warn_time - ): - # TODO(AC): Perhaps set a late alert as its own category - late_alert_id = f"{task_state.booking.id}__late" - if not await alert_repo.alert_original_id_exists(late_alert_id): - alert = await alert_repo.create_alert(late_alert_id, "task") - if alert is not None: - alert_events.alerts.on_next(alert) elif payload_type == "task_log_update": task_log = mdl.TaskEventLog(**msg["data"]) diff --git a/packages/api-server/api_server/routes/tasks/tasks.py b/packages/api-server/api_server/routes/tasks/tasks.py index 11a43326f..b5b7671dd 100644 --- a/packages/api-server/api_server/routes/tasks/tasks.py +++ b/packages/api-server/api_server/routes/tasks/tasks.py @@ -295,8 +295,6 @@ async def post_dispatch_task( task_repo: TaskRepository = Depends(TaskRepository), logger: LoggerAdapter = Depends(get_logger), ): - task_warn_time = request.request.unix_millis_warn_time - # FIXME: In order to accommodate changing cancellation lots over time, and # avoiding updating all the saved scheduled tasks in the database, we only # insert cancellation lots as part of the cancellation behavior before @@ -344,8 +342,6 @@ async def post_dispatch_task( if not resp.__root__.success: return RawJSONResponse(resp.json(), 400) new_state = cast(mdl.TaskDispatchResponseItem, resp.__root__).state - if task_warn_time is not None: - new_state.unix_millis_warn_time = task_warn_time await task_repo.save_task_state(new_state) await task_repo.save_task_request(new_state, request.request) return resp.__root__ diff --git a/packages/dashboard/src/components/tasks/task-alert.tsx b/packages/dashboard/src/components/tasks/task-alert.tsx index 84af097ff..3319abf0b 100644 --- a/packages/dashboard/src/components/tasks/task-alert.tsx +++ b/packages/dashboard/src/components/tasks/task-alert.tsx @@ -62,13 +62,6 @@ export function TaskAlertDialog({ alert, removeAlert }: TaskAlertDialogProps): J if (errorLogEntries.length !== 0) { return 'Task error'; } - if ( - state.unix_millis_finish_time && - state.unix_millis_warn_time && - state.unix_millis_finish_time > state.unix_millis_warn_time - ) { - return 'Task warning'; - } return 'Task alert'; }; @@ -145,20 +138,6 @@ export function TaskAlertDialog({ alert, removeAlert }: TaskAlertDialogProps): J value: `${completionTimeString}Task completed!`, }, ]; - } else if ( - state.unix_millis_finish_time && - state.unix_millis_warn_time && - state.unix_millis_finish_time > state.unix_millis_warn_time - ) { - const completionTimeString = `${new Date(state.unix_millis_finish_time).toLocaleString()}`; - const warningTimeString = `${new Date(state.unix_millis_warn_time).toLocaleString()}`; - content = [ - ...content, - { - title: 'Late', - value: `Task is estimated to complete at ${completionTimeString}, later than the expected ${warningTimeString}.`, - }, - ]; } return content; @@ -183,14 +162,6 @@ export function TaskAlertDialog({ alert, removeAlert }: TaskAlertDialogProps): J return base.palette.error.dark; } - if ( - state.unix_millis_finish_time && - state.unix_millis_warn_time && - state.unix_millis_finish_time > state.unix_millis_warn_time - ) { - return base.palette.warning.dark; - } - return base.palette.background.default; }; diff --git a/packages/react-components/lib/tasks/create-task.tsx b/packages/react-components/lib/tasks/create-task.tsx index fc62722c0..4635b2e76 100644 --- a/packages/react-components/lib/tasks/create-task.tsx +++ b/packages/react-components/lib/tasks/create-task.tsx @@ -43,7 +43,10 @@ import React from 'react'; import { Loading } from '..'; import { ConfirmationDialog, ConfirmationDialogProps } from '../confirmation-dialog'; import { PositiveIntField } from '../form-inputs'; -import { serializeTaskBookingLabel } from './task-booking-label-utils'; +import { + getTaskBookingLabelFromTaskRequest, + serializeTaskBookingLabel, +} from './task-booking-label-utils'; interface TaskDefinition { task_definition_id: string; @@ -1403,9 +1406,23 @@ export function CreateTaskForm({ setScheduleUntilValue(event.target.value); }; - const [warnTimeChecked, setWarnTimeChecked] = React.useState(false); + const existingBookingLabel = requestTask + ? getTaskBookingLabelFromTaskRequest(requestTask) + : undefined; + let existingWarnTime: Date | null = null; + if (existingBookingLabel && existingBookingLabel.description.unix_millis_warn_time) { + const warnTimeInt = parseInt(existingBookingLabel.description.unix_millis_warn_time as string); + if (!Number.isNaN(warnTimeInt)) { + existingWarnTime = new Date(warnTimeInt); + } + } + const [warnTime, setWarnTime] = React.useState(existingWarnTime); const handleWarnTimeCheckboxChange = (event: React.ChangeEvent) => { - setWarnTimeChecked(event.target.checked); + if (event.target.checked) { + setWarnTime(new Date()); + } else { + setWarnTime(null); + } }; const handleTaskDescriptionChange = (newCategory: string, newDesc: TaskDescription) => { @@ -1570,6 +1587,10 @@ export function CreateTaskForm({ return; } + if (warnTime !== null) { + requestBookingLabel.description.unix_millis_warn_time = `${warnTime.valueOf()}`; + } + const labelString = serializeTaskBookingLabel(requestBookingLabel); if (labelString) { request.labels = [labelString]; @@ -1779,7 +1800,7 @@ export function CreateTaskForm({ { - if (!date || !warnTimeChecked) { - return; - } - taskRequest.unix_millis_warn_time = date.valueOf(); - setTaskRequest((prev) => { - return { - ...prev, - unix_millis_warn_time: date.valueOf(), - }; - }); + setWarnTime(date); }} label="Warn Time" renderInput={(props) => ( diff --git a/packages/react-components/lib/tasks/task-booking-label-utils.tsx b/packages/react-components/lib/tasks/task-booking-label-utils.tsx index bfd4b0150..1f9bc0d94 100644 --- a/packages/react-components/lib/tasks/task-booking-label-utils.tsx +++ b/packages/react-components/lib/tasks/task-booking-label-utils.tsx @@ -1,6 +1,6 @@ import { ajv } from '../utils/schema-utils'; import schema from 'api-client/dist/schema'; -import type { TaskBookingLabel, TaskState } from 'api-client'; +import type { TaskBookingLabel, TaskRequest, TaskState } from 'api-client'; const validateTaskBookingLabel = ajv.compile(schema.components.schemas.TaskBookingLabel); @@ -44,3 +44,23 @@ export function getTaskBookingLabelFromTaskState(taskState: TaskState): TaskBook } return requestLabel; } + +export function getTaskBookingLabelFromTaskRequest( + taskRequest: TaskRequest, +): TaskBookingLabel | null { + let requestLabel: TaskBookingLabel | null = null; + if (taskRequest.labels) { + for (const label of taskRequest.labels) { + try { + const parsedLabel = getTaskBookingLabelFromJsonString(label); + if (parsedLabel) { + requestLabel = parsedLabel; + break; + } + } catch (e) { + continue; + } + } + } + return requestLabel; +} diff --git a/packages/react-components/lib/tasks/task-table-datagrid.tsx b/packages/react-components/lib/tasks/task-table-datagrid.tsx index d63c3cc12..ba248960e 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/'; @@ -25,6 +34,7 @@ const classes = { taskPendingCell: 'MuiDataGrid-cell-pending-cell', taskQueuedCell: 'MuiDataGrid-cell-queued-cell', taskUnknownCell: 'MuiDataGrid-cell-unknown-cell', + taskLateCell: 'MuiDataGrid-cell-late-cell', }; const StyledDataGrid = styled(DataGrid)(({ theme }) => ({ @@ -52,6 +62,10 @@ const StyledDataGrid = styled(DataGrid)(({ theme }) => ({ backgroundColor: theme.palette.warning.main, color: theme.palette.getContrastText(theme.palette.warning.main), }, + [`& .${classes.taskLateCell}`]: { + backgroundColor: theme.palette.warning.main, + color: theme.palette.getContrastText(theme.palette.warning.main), + }, })); export interface Tasks { @@ -226,10 +240,25 @@ export function TaskDataGridTable({ headerName: 'Start Time', width: 150, editable: false, - renderCell: (cellValues) => - cellValues.row.unix_millis_start_time - ? `${new Date(cellValues.row.unix_millis_start_time).toLocaleTimeString()}` - : 'n/a', + renderCell: (cellValues) => { + const startDateTime = cellValues.row.unix_millis_start_time + ? new Date(cellValues.row.unix_millis_start_time) + : undefined; + const startTimeString = startDateTime ? `${startDateTime.toLocaleTimeString()}` : 'n/a'; + return ( + + + Start time: {startDateTime ? startDateTime.toLocaleString() : 'n/a'} + + + } + > + {startTimeString} + + ); + }, flex: 1, filterOperators: getMinimalDateOperators, filterable: true, @@ -239,10 +268,37 @@ export function TaskDataGridTable({ headerName: 'End Time', width: 150, editable: false, - renderCell: (cellValues) => - cellValues.row.unix_millis_finish_time - ? `${new Date(cellValues.row.unix_millis_finish_time).toLocaleTimeString()}` - : 'n/a', + renderCell: (cellValues) => { + const bookingLabel = getTaskBookingLabelFromTaskState(cellValues.row); + let warnDateTime: Date | undefined = undefined; + if (bookingLabel?.description.unix_millis_warn_time) { + const warnMillisNum = parseInt(bookingLabel.description.unix_millis_warn_time as string); + if (!Number.isNaN(warnMillisNum)) { + warnDateTime = new Date(warnMillisNum); + } + } + + const finishDateTime = cellValues.row.unix_millis_finish_time + ? new Date(cellValues.row.unix_millis_finish_time) + : undefined; + const finishTimeString = finishDateTime ? `${finishDateTime.toLocaleTimeString()}` : 'n/a'; + return ( + + + Warning time: {warnDateTime ? warnDateTime.toLocaleString() : 'n/a'} + + + Finish time: {finishDateTime ? finishDateTime.toLocaleString() : 'n/a'} + + + } + > + {finishTimeString} + + ); + }, flex: 1, filterOperators: getMinimalDateOperators, filterable: true, @@ -313,6 +369,27 @@ export function TaskDataGridTable({ default: return classes.taskUnknownCell; } + } else if (params.field === 'unix_millis_finish_time') { + if (!params.value) { + return classes.taskUnknownCell; + } + + const bookingLabel = getTaskBookingLabelFromTaskState(params.row); + let warnDateTime: Date | undefined = undefined; + if (bookingLabel?.description.unix_millis_warn_time) { + const warnMillisNum = parseInt( + bookingLabel.description.unix_millis_warn_time as string, + ); + if (!Number.isNaN(warnMillisNum)) { + warnDateTime = new Date(warnMillisNum); + } + } + + const finishDateTime = params.value ? new Date(params.value) : undefined; + + if (warnDateTime && finishDateTime && finishDateTime > warnDateTime) { + return classes.taskLateCell; + } } return ''; }}