From b6034a1814b35fbcfd1fba4d4bbce45deff44777 Mon Sep 17 00:00:00 2001 From: Aaron Chong Date: Mon, 11 Dec 2023 00:41:31 +0800 Subject: [PATCH 1/2] If the obstruction is related to a latching issue, allow resume (#858) * If the obstruction is related to a latching issue, allow resume Signed-off-by: Aaron Chong * Revert to original navigation, will update in next version bump Signed-off-by: Aaron Chong * Sort create task fields, except card IDs Signed-off-by: Aaron Chong * Remove stale console.log Signed-off-by: Aaron Chong --------- Signed-off-by: Aaron Chong --- .../src/components/delivery-alert-store.tsx | 9 +- .../src/components/tasks/task-schedule.tsx | 99 +------------------ .../lib/tasks/create-task.tsx | 10 +- 3 files changed, 17 insertions(+), 101 deletions(-) diff --git a/packages/dashboard/src/components/delivery-alert-store.tsx b/packages/dashboard/src/components/delivery-alert-store.tsx index 3f3f4f229..b267856da 100644 --- a/packages/dashboard/src/components/delivery-alert-store.tsx +++ b/packages/dashboard/src/components/delivery-alert-store.tsx @@ -693,12 +693,19 @@ export const DeliveryAlertStore = React.memo(() => { ); } + // Allow resume if the obstruction is related to a latching problem. return ( setAlerts((prev) => Object.fromEntries( diff --git a/packages/dashboard/src/components/tasks/task-schedule.tsx b/packages/dashboard/src/components/tasks/task-schedule.tsx index 800745221..dcfa425cc 100644 --- a/packages/dashboard/src/components/tasks/task-schedule.tsx +++ b/packages/dashboard/src/components/tasks/task-schedule.tsx @@ -8,7 +8,7 @@ import { SchedulerHelpers, SchedulerProps, } from '@aldabil/react-scheduler/types'; -import { Button, Grid } from '@mui/material'; +import { Button } from '@mui/material'; import { ApiServerModelsTortoiseModelsScheduledTaskScheduledTask as ScheduledTask, ApiServerModelsTortoiseModelsScheduledTaskScheduledTaskScheduleLeaf as ApiSchedule, @@ -261,95 +261,15 @@ export const TaskSchedule = () => { disablingCellsWithoutEvents(calendarEvents, { start, ...props }), }; - interface ViewSettings { - daySettings: DayProps | null; - weekSettings: WeekProps | null; - monthSettings: MonthProps | null; - } - - const [viewSettings, setViewSettings] = React.useState({ - daySettings: null, - weekSettings: defaultWeekSettings, - monthSettings: null, - }); - - const translations = { - navigation: { - month: 'Month', - week: 'Week', - day: 'Day', - today: 'Go to today', - }, - form: { - addTitle: 'Add Event', - editTitle: 'Edit Event', - confirm: 'Confirm', - delete: 'Delete', - cancel: 'Cancel', - }, - event: { - title: 'Title', - start: 'Start', - end: 'End', - allDay: 'All Day', - }, - moreEvents: 'More...', - loading: 'Loading...', - }; - return (
- - - - - { }} /> )} - translations={{ - ...translations, - navigation: { - ...translations.navigation, - today: viewSettings.daySettings - ? 'Today' - : viewSettings.weekSettings - ? 'This week' - : 'This month', - }, - }} /> {openCreateTaskForm && ( { const pickupLot = pickupPoints[newValue] ?? ''; @@ -358,7 +358,7 @@ function DeliveryTaskForm({ id="dropoff-location" freeSolo fullWidth - options={Object.keys(dropoffPoints)} + options={Object.keys(dropoffPoints).sort()} value={taskDesc.phases[0].activity.description.activities[2].description} onInputChange={(_ev, newValue) => { const newTaskDesc = { ...taskDesc }; @@ -428,7 +428,7 @@ function DeliveryCustomTaskForm({ id="pickup-zone" freeSolo fullWidth - options={pickupZones} + options={pickupZones.sort()} value={taskDesc.phases[0].activity.description.activities[0].description} onInputChange={(_ev, newValue) => { const newTaskDesc = { ...taskDesc }; @@ -513,7 +513,7 @@ function DeliveryCustomTaskForm({ id="dropoff-location" freeSolo fullWidth - options={dropoffPoints} + options={dropoffPoints.sort()} value={taskDesc.phases[0].activity.description.activities[2].description} onInputChange={(_ev, newValue) => { const newTaskDesc = { ...taskDesc }; @@ -609,7 +609,7 @@ function PatrolTaskForm({ taskDesc, patrolWaypoints, onChange, allowSubmit }: Pa id="place-input" freeSolo fullWidth - options={patrolWaypoints} + options={patrolWaypoints.sort()} onChange={(_ev, newValue) => newValue !== null && onInputChange({ From 286b741725524a40face06627253493d46b9f300 Mon Sep 17 00:00:00 2001 From: Aaron Chong Date: Tue, 12 Dec 2023 01:07:30 +0800 Subject: [PATCH 2/2] Catch failure to load old schedules in database (#859) * Catch failure to load old schedules in database Signed-off-by: Aaron Chong * Revert naming of database name Signed-off-by: Aaron Chong * Allow stub to be persistent if we choose to re-use databases Signed-off-by: Aaron Chong * Better logs when schedules are ignored or loaded when read from the database, this happens when the server restarts Signed-off-by: Aaron Chong * Log jobs left in schedule Signed-off-by: Aaron Chong * New schedule app refreshing event, since an ongoing task is force refresh the schedule as well Signed-off-by: Aaron Chong * Fix id access for scheduled task Signed-off-by: Aaron Chong * Lint Signed-off-by: Aaron Chong --------- Signed-off-by: Aaron Chong --- packages/api-server/api_server/app.py | 16 ++++++++++----- .../api-server/api_server/authenticator.py | 15 +++++++++++--- .../routes/tasks/scheduled_tasks.py | 6 +++++- packages/api-server/psql_local_config.py | 4 +++- .../dashboard/src/components/app-events.ts | 1 + .../src/components/tasks/task-schedule.tsx | 20 +++++++++---------- 6 files changed, 42 insertions(+), 20 deletions(-) diff --git a/packages/api-server/api_server/app.py b/packages/api-server/api_server/app.py index 6fc16a05c..1a4e8a4e4 100644 --- a/packages/api-server/api_server/app.py +++ b/packages/api-server/api_server/app.py @@ -196,13 +196,19 @@ def on_signal(sig, frame): for t in scheduled_tasks: user = await User.load_from_db(t.created_by) if user is None: - logger.warning(f"user [{t.created_by}] does not exist") + logger.warning(f"User who scheduled task, [{t.created_by}] does not exist") + logger.warning(f"Skipping request: [{t.task_request}]") continue task_repo = TaskRepository(user) - await routes.scheduled_tasks.schedule_task(t, task_repo) - scheduled += 1 - logger.info(f"loaded {scheduled} tasks") - logger.info("successfully started scheduler") + try: + await routes.scheduled_tasks.schedule_task(t, task_repo) + scheduled += 1 + except Exception as e: # pylint: disable=broad-except + logger.warning(f"Unable to schedule task requested by {t.created_by}: {e}") + logger.warning(f"Skipping request: [{t.task_request}]") + logger.info( + f"Retrieved {len(scheduled_tasks)} scheduled tasks, scheduled {scheduled} tasks" + ) ros.spin_background() logger.info("started app") diff --git a/packages/api-server/api_server/authenticator.py b/packages/api-server/api_server/authenticator.py index 4196fc48e..f680a9eb4 100644 --- a/packages/api-server/api_server/authenticator.py +++ b/packages/api-server/api_server/authenticator.py @@ -79,13 +79,22 @@ async def dep( class StubAuthenticator(JwtAuthenticator): def __init__(self): # pylint: disable=super-init-not-called - self._user = User(username="stub", is_admin=True) + self._user = None - async def verify_token(self, token: Optional[str]) -> User: + async def _get_user(self, claims: dict) -> User: + if self._user is None: + self._user = await User.load_or_create_from_db("stub") + await self._user.update_admin(True) return self._user + async def verify_token(self, token: Optional[str]) -> User: + return await self._get_user({}) + def fastapi_dep(self) -> Callable[..., Union[Coroutine[Any, Any, User], User]]: - return lambda: self._user + async def dep(): + return await self._get_user({}) + + return dep if app_config.jwt_public_key: diff --git a/packages/api-server/api_server/routes/tasks/scheduled_tasks.py b/packages/api-server/api_server/routes/tasks/scheduled_tasks.py index 992e8f9da..60f4dddd2 100644 --- a/packages/api-server/api_server/routes/tasks/scheduled_tasks.py +++ b/packages/api-server/api_server/routes/tasks/scheduled_tasks.py @@ -54,6 +54,7 @@ def do(): if datetime_to_iso[:10] in task.except_dates: return asyncio.get_event_loop().create_task(run()) + logger.warning(f"schedule has {len(schedule.get_jobs())} jobs left") for _, j in jobs: j.do(do) @@ -150,7 +151,10 @@ async def del_scheduled_tasks_event( for sche in task.schedules: schedule.clear(sche.get_id()) - await schedule_task(task, task_repo) + try: + await schedule_task(task, task_repo) + except schedule.ScheduleError as e: + raise HTTPException(422, str(e)) from e @router.post( diff --git a/packages/api-server/psql_local_config.py b/packages/api-server/psql_local_config.py index 5bb0c8fad..a61c39660 100644 --- a/packages/api-server/psql_local_config.py +++ b/packages/api-server/psql_local_config.py @@ -1,6 +1,8 @@ +import os + from sqlite_local_config import config -here = dirname(__file__) +here = os.path.dirname(__file__) run_dir = f"{here}/run" config.update( diff --git a/packages/dashboard/src/components/app-events.ts b/packages/dashboard/src/components/app-events.ts index 23230dc8a..c93bc1189 100644 --- a/packages/dashboard/src/components/app-events.ts +++ b/packages/dashboard/src/components/app-events.ts @@ -18,6 +18,7 @@ export const AppEvents = { robotSelect: new Subject<[fleetName: string, robotName: string] | null>(), taskSelect: new Subject(), refreshTaskApp: new Subject(), + refreshTaskSchedule: new Subject(), refreshAlert: new Subject(), alertListOpenedAlert: new Subject(), disabledLayers: new ReplaySubject>(), diff --git a/packages/dashboard/src/components/tasks/task-schedule.tsx b/packages/dashboard/src/components/tasks/task-schedule.tsx index dcfa425cc..ec1f73571 100644 --- a/packages/dashboard/src/components/tasks/task-schedule.tsx +++ b/packages/dashboard/src/components/tasks/task-schedule.tsx @@ -75,7 +75,7 @@ export const TaskSchedule = () => { useCreateTaskFormData(rmf); const username = useGetUsername(rmf); const [eventScope, setEventScope] = React.useState(EventScopes.CURRENT); - const [refreshTaskAppCount, setRefreshTaskAppCount] = React.useState(0); + const [refreshTaskScheduleCount, setRefreshTaskScheduleCount] = React.useState(0); const exceptDateRef = React.useRef(new Date()); const currentEventIdRef = React.useRef(-1); const [currentScheduleTask, setCurrentScheduledTask] = React.useState( @@ -92,9 +92,9 @@ export const TaskSchedule = () => { }); React.useEffect(() => { - const sub = AppEvents.refreshTaskApp.subscribe({ + const sub = AppEvents.refreshTaskSchedule.subscribe({ next: () => { - setRefreshTaskAppCount((oldValue) => ++oldValue); + setRefreshTaskScheduleCount((oldValue) => ++oldValue); }, }); return () => sub.unsubscribe(); @@ -144,7 +144,7 @@ export const TaskSchedule = () => { onClose={() => { scheduler.close(); setEventScope(EventScopes.CURRENT); - AppEvents.refreshTaskApp.next(); + AppEvents.refreshTaskSchedule.next(); }} onSubmit={() => { setOpenCreateTaskForm(true); @@ -157,7 +157,7 @@ export const TaskSchedule = () => { if (eventScope === EventScopes.CURRENT) { setScheduleToEdit(scheduleWithSelectedDay(task.schedules, exceptDateRef.current)); } - AppEvents.refreshTaskApp.next(); + AppEvents.refreshTaskSchedule.next(); scheduler.close(); }} > @@ -200,7 +200,7 @@ export const TaskSchedule = () => { ); setEventScope(EventScopes.CURRENT); - AppEvents.refreshTaskApp.next(); + AppEvents.refreshTaskSchedule.next(); }, [rmf, currentScheduleTask, eventScope], ); @@ -225,7 +225,7 @@ export const TaskSchedule = () => { } else { await rmf.tasksApi.delScheduledTasksScheduledTasksTaskIdDelete(task.id); } - AppEvents.refreshTaskApp.next(); + AppEvents.refreshTaskSchedule.next(); // Set the default values setOpenDeleteScheduleDialog(false); @@ -265,7 +265,7 @@ export const TaskSchedule = () => {
{ value={eventScope} onChange={(event: React.ChangeEvent) => { setEventScope(event.target.value); - AppEvents.refreshTaskApp.next(); + AppEvents.refreshTaskSchedule.next(); }} /> )} @@ -305,7 +305,7 @@ export const TaskSchedule = () => { onClose={() => { setOpenCreateTaskForm(false); setEventScope(EventScopes.CURRENT); - AppEvents.refreshTaskApp.next(); + AppEvents.refreshTaskSchedule.next(); }} submitTasks={submitTasks} onSuccess={() => {