diff --git a/packages/api-client/lib/openapi/api.ts b/packages/api-client/lib/openapi/api.ts index 173fe08ed..20bb5a3c4 100644 --- a/packages/api-client/lib/openapi/api.ts +++ b/packages/api-client/lib/openapi/api.ts @@ -9131,8 +9131,12 @@ export const TasksApiAxiosParamCreator = function (configuration?: Configuration * @summary Query Task States * @param {string} [taskId] comma separated list of task ids * @param {string} [category] comma separated list of task categories + * @param {string} [requester] comma separated list of requester names + * @param {string} [pickup] comma separated list of pickup names + * @param {string} [destination] comma separated list of destination names * @param {string} [assignedTo] comma separated list of assigned robot names * @param {string} [status] comma separated list of statuses + * @param {string} [requestTimeBetween] The period of request time to fetch, in unix millis. This must be a comma separated string, \'X,Y\' to fetch between X millis and Y millis inclusive. Example: \"1000,2000\" - Fetches logs between unix millis 1000 and 2000. * @param {string} [startTimeBetween] The period of starting time to fetch, in unix millis. This must be a comma separated string, \'X,Y\' to fetch between X millis and Y millis inclusive. Example: \"1000,2000\" - Fetches logs between unix millis 1000 and 2000. * @param {string} [finishTimeBetween] The period of finishing time to fetch, in unix millis. This must be a comma separated string, \'X,Y\' to fetch between X millis and Y millis inclusive. Example: \"1000,2000\" - Fetches logs between unix millis 1000 and 2000. \"-60000\" - Fetches logs in the last minute. * @param {number} [limit] defaults to 100 @@ -9144,8 +9148,12 @@ export const TasksApiAxiosParamCreator = function (configuration?: Configuration queryTaskStatesTasksGet: async ( taskId?: string, category?: string, + requester?: string, + pickup?: string, + destination?: string, assignedTo?: string, status?: string, + requestTimeBetween?: string, startTimeBetween?: string, finishTimeBetween?: string, limit?: number, @@ -9173,6 +9181,18 @@ export const TasksApiAxiosParamCreator = function (configuration?: Configuration localVarQueryParameter['category'] = category; } + if (requester !== undefined) { + localVarQueryParameter['requester'] = requester; + } + + if (pickup !== undefined) { + localVarQueryParameter['pickup'] = pickup; + } + + if (destination !== undefined) { + localVarQueryParameter['destination'] = destination; + } + if (assignedTo !== undefined) { localVarQueryParameter['assigned_to'] = assignedTo; } @@ -9181,6 +9201,10 @@ export const TasksApiAxiosParamCreator = function (configuration?: Configuration localVarQueryParameter['status'] = status; } + if (requestTimeBetween !== undefined) { + localVarQueryParameter['request_time_between'] = requestTimeBetween; + } + if (startTimeBetween !== undefined) { localVarQueryParameter['start_time_between'] = startTimeBetween; } @@ -9730,8 +9754,12 @@ export const TasksApiFp = function (configuration?: Configuration) { * @summary Query Task States * @param {string} [taskId] comma separated list of task ids * @param {string} [category] comma separated list of task categories + * @param {string} [requester] comma separated list of requester names + * @param {string} [pickup] comma separated list of pickup names + * @param {string} [destination] comma separated list of destination names * @param {string} [assignedTo] comma separated list of assigned robot names * @param {string} [status] comma separated list of statuses + * @param {string} [requestTimeBetween] The period of request time to fetch, in unix millis. This must be a comma separated string, \'X,Y\' to fetch between X millis and Y millis inclusive. Example: \"1000,2000\" - Fetches logs between unix millis 1000 and 2000. * @param {string} [startTimeBetween] The period of starting time to fetch, in unix millis. This must be a comma separated string, \'X,Y\' to fetch between X millis and Y millis inclusive. Example: \"1000,2000\" - Fetches logs between unix millis 1000 and 2000. * @param {string} [finishTimeBetween] The period of finishing time to fetch, in unix millis. This must be a comma separated string, \'X,Y\' to fetch between X millis and Y millis inclusive. Example: \"1000,2000\" - Fetches logs between unix millis 1000 and 2000. \"-60000\" - Fetches logs in the last minute. * @param {number} [limit] defaults to 100 @@ -9743,8 +9771,12 @@ export const TasksApiFp = function (configuration?: Configuration) { async queryTaskStatesTasksGet( taskId?: string, category?: string, + requester?: string, + pickup?: string, + destination?: string, assignedTo?: string, status?: string, + requestTimeBetween?: string, startTimeBetween?: string, finishTimeBetween?: string, limit?: number, @@ -9755,8 +9787,12 @@ export const TasksApiFp = function (configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.queryTaskStatesTasksGet( taskId, category, + requester, + pickup, + destination, assignedTo, status, + requestTimeBetween, startTimeBetween, finishTimeBetween, limit, @@ -10165,8 +10201,12 @@ export const TasksApiFactory = function ( * @summary Query Task States * @param {string} [taskId] comma separated list of task ids * @param {string} [category] comma separated list of task categories + * @param {string} [requester] comma separated list of requester names + * @param {string} [pickup] comma separated list of pickup names + * @param {string} [destination] comma separated list of destination names * @param {string} [assignedTo] comma separated list of assigned robot names * @param {string} [status] comma separated list of statuses + * @param {string} [requestTimeBetween] The period of request time to fetch, in unix millis. This must be a comma separated string, \'X,Y\' to fetch between X millis and Y millis inclusive. Example: \"1000,2000\" - Fetches logs between unix millis 1000 and 2000. * @param {string} [startTimeBetween] The period of starting time to fetch, in unix millis. This must be a comma separated string, \'X,Y\' to fetch between X millis and Y millis inclusive. Example: \"1000,2000\" - Fetches logs between unix millis 1000 and 2000. * @param {string} [finishTimeBetween] The period of finishing time to fetch, in unix millis. This must be a comma separated string, \'X,Y\' to fetch between X millis and Y millis inclusive. Example: \"1000,2000\" - Fetches logs between unix millis 1000 and 2000. \"-60000\" - Fetches logs in the last minute. * @param {number} [limit] defaults to 100 @@ -10178,8 +10218,12 @@ export const TasksApiFactory = function ( queryTaskStatesTasksGet( taskId?: string, category?: string, + requester?: string, + pickup?: string, + destination?: string, assignedTo?: string, status?: string, + requestTimeBetween?: string, startTimeBetween?: string, finishTimeBetween?: string, limit?: number, @@ -10191,8 +10235,12 @@ export const TasksApiFactory = function ( .queryTaskStatesTasksGet( taskId, category, + requester, + pickup, + destination, assignedTo, status, + requestTimeBetween, startTimeBetween, finishTimeBetween, limit, @@ -10625,8 +10673,12 @@ export class TasksApi extends BaseAPI { * @summary Query Task States * @param {string} [taskId] comma separated list of task ids * @param {string} [category] comma separated list of task categories + * @param {string} [requester] comma separated list of requester names + * @param {string} [pickup] comma separated list of pickup names + * @param {string} [destination] comma separated list of destination names * @param {string} [assignedTo] comma separated list of assigned robot names * @param {string} [status] comma separated list of statuses + * @param {string} [requestTimeBetween] The period of request time to fetch, in unix millis. This must be a comma separated string, \'X,Y\' to fetch between X millis and Y millis inclusive. Example: \"1000,2000\" - Fetches logs between unix millis 1000 and 2000. * @param {string} [startTimeBetween] The period of starting time to fetch, in unix millis. This must be a comma separated string, \'X,Y\' to fetch between X millis and Y millis inclusive. Example: \"1000,2000\" - Fetches logs between unix millis 1000 and 2000. * @param {string} [finishTimeBetween] The period of finishing time to fetch, in unix millis. This must be a comma separated string, \'X,Y\' to fetch between X millis and Y millis inclusive. Example: \"1000,2000\" - Fetches logs between unix millis 1000 and 2000. \"-60000\" - Fetches logs in the last minute. * @param {number} [limit] defaults to 100 @@ -10639,8 +10691,12 @@ export class TasksApi extends BaseAPI { public queryTaskStatesTasksGet( taskId?: string, category?: string, + requester?: string, + pickup?: string, + destination?: string, assignedTo?: string, status?: string, + requestTimeBetween?: string, startTimeBetween?: string, finishTimeBetween?: string, limit?: number, @@ -10652,8 +10708,12 @@ export class TasksApi extends BaseAPI { .queryTaskStatesTasksGet( taskId, category, + requester, + pickup, + destination, assignedTo, status, + requestTimeBetween, startTimeBetween, finishTimeBetween, limit, diff --git a/packages/api-client/lib/version.ts b/packages/api-client/lib/version.ts index f1990d980..fd7b7b378 100644 --- a/packages/api-client/lib/version.ts +++ b/packages/api-client/lib/version.ts @@ -3,6 +3,6 @@ import { version as rmfModelVer } from 'rmf-models'; export const version = { rmfModels: rmfModelVer, - rmfServer: '1970313b2544aa6fb1ee65c9356f2eface9b174b', + rmfServer: '4f6e8567a0a3a44896f60898e84130c2be8a94c7', openapiGenerator: '6.2.1', }; diff --git a/packages/api-client/schema/index.ts b/packages/api-client/schema/index.ts index fddc56842..6c36fad06 100644 --- a/packages/api-client/schema/index.ts +++ b/packages/api-client/schema/index.ts @@ -853,6 +853,39 @@ export default { name: 'category', in: 'query', }, + { + description: 'comma separated list of requester names', + required: false, + schema: { + title: 'Requester', + type: 'string', + description: 'comma separated list of requester names', + }, + name: 'requester', + in: 'query', + }, + { + description: 'comma separated list of pickup names', + required: false, + schema: { + title: 'Pickup', + type: 'string', + description: 'comma separated list of pickup names', + }, + name: 'pickup', + in: 'query', + }, + { + description: 'comma separated list of destination names', + required: false, + schema: { + title: 'Destination', + type: 'string', + description: 'comma separated list of destination names', + }, + name: 'destination', + in: 'query', + }, { description: 'comma separated list of assigned robot names', required: false, @@ -875,6 +908,19 @@ export default { name: 'status', in: 'query', }, + { + description: + '\n The period of request time to fetch, in unix millis.\n\n This must be a comma separated string, \'X,Y\' to fetch between X millis and Y millis inclusive.\n\n Example:\n "1000,2000" - Fetches logs between unix millis 1000 and 2000.\n ', + required: false, + schema: { + title: 'Request Time Between', + type: 'string', + description: + '\n The period of request time to fetch, in unix millis.\n\n This must be a comma separated string, \'X,Y\' to fetch between X millis and Y millis inclusive.\n\n Example:\n "1000,2000" - Fetches logs between unix millis 1000 and 2000.\n ', + }, + name: 'request_time_between', + in: 'query', + }, { description: '\n The period of starting time to fetch, in unix millis.\n\n This must be a comma separated string, \'X,Y\' to fetch between X millis and Y millis inclusive.\n\n Example:\n "1000,2000" - Fetches logs between unix millis 1000 and 2000.\n ', diff --git a/packages/api-server/api_server/dependencies.py b/packages/api-server/api_server/dependencies.py index e21cae0bc..199c86061 100644 --- a/packages/api-server/api_server/dependencies.py +++ b/packages/api-server/api_server/dependencies.py @@ -51,6 +51,37 @@ def between_query( return period +def request_time_between_query( + request_time_between: str = Query( + None, + description=""" + The period of request time to fetch, in unix millis. + + This must be a comma separated string, 'X,Y' to fetch between X millis and Y millis inclusive. + + Example: + "1000,2000" - Fetches logs between unix millis 1000 and 2000. + """, + ), + now: int = Depends(clock.now), +) -> Tuple[datetime, datetime] | None: + if request_time_between is None: + return None + if request_time_between.startswith("-"): + # Cap at 0 millis + period = ( + datetime.fromtimestamp(0), + datetime.fromtimestamp(now / 1000), + ) + else: + parts = request_time_between.split(",") + period = ( + datetime.fromtimestamp(int(parts[0]) / 1000), + datetime.fromtimestamp(int(parts[1]) / 1000), + ) + return period + + def start_time_between_query( start_time_between: str = Query( None, 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 1fdd39950..f36ac3fbc 100644 --- a/packages/api-server/api_server/models/tortoise_models/tasks.py +++ b/packages/api-server/api_server/models/tortoise_models/tasks.py @@ -28,6 +28,8 @@ class TaskState(Model): 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) class TaskEventLog(Model): diff --git a/packages/api-server/api_server/repositories/tasks.py b/packages/api-server/api_server/repositories/tasks.py index 2c55f69fc..717b16ab9 100644 --- a/packages/api-server/api_server/repositories/tasks.py +++ b/packages/api-server/api_server/repositories/tasks.py @@ -28,15 +28,126 @@ from api_server.rmf_io import task_events +def parse_pickup(task_request: TaskRequest) -> Optional[str]: + # patrol + if task_request.category.lower() == "patrol": + return None + + # custom deliveries + supportedDeliveries = [ + "delivery_pickup", + "delivery_sequential_lot_pickup", + "delivery_area_pickup", + ] + if ( + "category" not in task_request.description + or task_request.description["category"] not in supportedDeliveries + ): + return None + + category = task_request.description["category"] + try: + perform_action_description = task_request.description["phases"][0]["activity"][ + "description" + ]["activities"][1]["description"]["description"] + if category == "delivery_pickup": + return perform_action_description["pickup_lot"] + return perform_action_description["pickup_zone"] + except Exception as e: # pylint: disable=W0703 + logger.error(format_exception(e)) + logger.error(f"Failed to parse pickup for task of category {category}") + return None + + +def parse_destination( + task_state: TaskState, task_request: TaskRequest +) -> Optional[str]: + # patrol + try: + if ( + task_request.category.lower() == "patrol" + and task_request.description["places"] is not None + and len(task_request.description["places"]) > 0 + ): + return task_request.description["places"][-1] + except Exception as e: # pylint: disable=W0703 + logger.error(format_exception(e)) + logger.error("Failed to parse destination for patrol") + return None + + # custom deliveries + supportedDeliveries = [ + "delivery_pickup", + "delivery_sequential_lot_pickup", + "delivery_area_pickup", + ] + if ( + "category" not in task_request.description + or task_request.description["category"] not in supportedDeliveries + ): + return None + + category = task_request.description["category"] + try: + destination = task_request.description["phases"][0]["activity"]["description"][ + "activities" + ][2]["description"] + return destination + except Exception as e: # pylint: disable=W0703 + logger.error(format_exception(e)) + logger.error( + f"Failed to parse destination from task request of category {category}" + ) + + # automated tasks that can only be parsed with state + if task_state.category is not None and task_state.category == "Charge Battery": + try: + if ( + task_state.phases is None + or "1" not in task_state.phases + or task_state.phases["1"].events is None + or "1" not in task_state.phases["1"].events + or task_state.phases["1"].events["1"].name is None + ): + raise ValueError + + charge_event_name = task_state.phases["1"].events["1"].name + charge_place_split = charge_event_name.split("[place:")[1] + charge_place = charge_place_split.split("]")[0] + return charge_place + except Exception as e: # pylint: disable=W0703 + logger.error(format_exception(e)) + logger.error( + f"Failed to parse charging point from task state of id {task_state.booking.id}" + ) + return None + return None + + class TaskRepository: def __init__(self, user: User): self.user = user - async def save_task_request(self, task_id: str, task_request: TaskRequest) -> None: + async def save_task_request( + self, task_state: TaskState, task_request: TaskRequest + ) -> None: await DbTaskRequest.update_or_create( - {"request": task_request.json()}, id_=task_id + {"request": task_request.json()}, id_=task_state.booking.id ) + # Add pickup and destination to task state model for filter and sort + pickup = parse_pickup(task_request) + destination = parse_destination(task_state, task_request) + db_task_state = await DbTaskState.get_or_none(id_=task_state.booking.id) + if db_task_state is not None: + db_task_state.update_from_dict( + { + "pickup": pickup, + "destination": destination, + } + ) + await db_task_state.save() + async def get_task_request(self, task_id: str) -> Optional[TaskRequest]: result = await DbTaskRequest.get_or_none(id_=task_id) if result is None: diff --git a/packages/api-server/api_server/routes/tasks/tasks.py b/packages/api-server/api_server/routes/tasks/tasks.py index 7e107b3f7..9c6e3d22b 100644 --- a/packages/api-server/api_server/routes/tasks/tasks.py +++ b/packages/api-server/api_server/routes/tasks/tasks.py @@ -9,6 +9,7 @@ between_query, finish_time_between_query, pagination_query, + request_time_between_query, sio_user, start_time_between_query, ) @@ -66,6 +67,18 @@ async def query_task_states( category: Optional[str] = Query( None, description="comma separated list of task categories" ), + request_time_between: Optional[Tuple[datetime, datetime]] = Depends( + request_time_between_query + ), + requester: Optional[str] = Query( + None, description="comma separated list of requester names" + ), + pickup: Optional[str] = Query( + None, description="comma separated list of pickup names" + ), + destination: Optional[str] = Query( + None, description="comma separated list of destination names" + ), assigned_to: Optional[str] = Query( None, description="comma separated list of assigned robot names" ), @@ -83,6 +96,15 @@ async def query_task_states( filters["id___in"] = task_id.split(",") if category is not None: filters["category__in"] = category.split(",") + if request_time_between is not None: + filters["unix_millis_request_time__gte"] = request_time_between[0] + filters["unix_millis_request_time__lte"] = request_time_between[1] + if requester is not None: + filters["requester__in"] = requester.split(",") + if pickup is not None: + filters["pickup__in"] = pickup.split(",") + if destination is not None: + filters["destination__in"] = destination.split(",") if assigned_to is not None: filters["assigned_to__in"] = assigned_to.split(",") if start_time_between is not None: @@ -185,7 +207,7 @@ async def post_dispatch_task( 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.booking.id, request.request) + await task_repo.save_task_request(new_state, request.request) return resp.__root__ diff --git a/packages/dashboard/src/components/tasks/tasks-app.tsx b/packages/dashboard/src/components/tasks/tasks-app.tsx index a676033e8..11fdea12e 100644 --- a/packages/dashboard/src/components/tasks/tasks-app.tsx +++ b/packages/dashboard/src/components/tasks/tasks-app.tsx @@ -171,8 +171,12 @@ export const TasksApp = React.memo( const resp = await rmf.tasksApi.queryTaskStatesTasksGet( filterColumn && filterColumn === 'id_' ? filterValue : undefined, filterColumn && filterColumn === 'category' ? filterValue : undefined, + filterColumn && filterColumn === 'requester' ? filterValue : undefined, + filterColumn && filterColumn === 'pickup' ? filterValue : undefined, + filterColumn && filterColumn === 'destination' ? filterValue : undefined, filterColumn && filterColumn === 'assigned_to' ? filterValue : undefined, filterColumn && filterColumn === 'status' ? filterValue : undefined, + filterColumn && filterColumn === 'unix_millis_request_time' ? filterValue : undefined, filterColumn && filterColumn === 'unix_millis_start_time' ? filterValue : undefined, filterColumn && filterColumn === 'unix_millis_finish_time' ? filterValue : undefined, GET_LIMIT, @@ -229,6 +233,9 @@ export const TasksApp = React.memo( } const resp = await rmf.tasksApi.queryTaskStatesTasksGet( + undefined, + undefined, + undefined, undefined, undefined, undefined, @@ -237,6 +244,7 @@ export const TasksApp = React.memo( undefined, undefined, undefined, + undefined, '-unix_millis_start_time', undefined, ); diff --git a/packages/react-components/lib/tasks/task-table-datagrid.tsx b/packages/react-components/lib/tasks/task-table-datagrid.tsx index f8c1ba243..855774f1c 100644 --- a/packages/react-components/lib/tasks/task-table-datagrid.tsx +++ b/packages/react-components/lib/tasks/task-table-datagrid.tsx @@ -163,8 +163,7 @@ export function TaskDataGridTable({ }, flex: 1, filterOperators: getMinimalDateOperators, - sortable: false, - filterable: false, + filterable: true, }, { field: 'requester', @@ -174,8 +173,7 @@ export function TaskDataGridTable({ renderCell: (cellValues) => TaskRequester(cellValues.row.booking.requester), flex: 1, filterOperators: getMinimalStringFilterOperators, - sortable: false, - filterable: false, + filterable: true, }, { field: 'pickup', @@ -188,8 +186,7 @@ export function TaskDataGridTable({ }, flex: 1, filterOperators: getMinimalStringFilterOperators, - sortable: false, - filterable: false, + filterable: true, }, { field: 'destination', @@ -202,8 +199,7 @@ export function TaskDataGridTable({ }, flex: 1, filterOperators: getMinimalStringFilterOperators, - sortable: false, - filterable: false, + filterable: true, }, { field: 'assigned_to',