Skip to content

Commit

Permalink
Direct dispatch to fleet or robot (#1004)
Browse files Browse the repository at this point in the history
* Basic implementation of priority based on legacy implementation

Signed-off-by: Aaron Chong <aaronchongth@gmail.com>

* Use icons for favorite

Signed-off-by: Aaron Chong <aaronchongth@gmail.com>

* Fix warning time, use switch and made buttons nicer

Signed-off-by: Aaron Chong <aaronchongth@gmail.com>

* To display priority

Signed-off-by: Aaron Chong <aaronchongth@gmail.com>

* Lint

Signed-off-by: Aaron Chong <aaronchongth@gmail.com>

* Helper function to handle null and undefined priority, clean up creation

Signed-off-by: Aaron Chong <aaronchongth@gmail.com>

* Use switch for priority, flip low priority icon

Signed-off-by: Aaron Chong <aaronchongth@gmail.com>

* Remove getDefaultTaskPriorty

Signed-off-by: Aaron Chong <aaronchongth@gmail.com>

* Basic implementation working, still with frontend errors

Signed-off-by: Aaron Chong <aaronchongth@gmail.com>

* Fix component render sequence issues

Signed-off-by: Aaron Chong <aaronchongth@gmail.com>

* Lint

Signed-off-by: Aaron Chong <aaronchongth@gmail.com>

* Fix capitalization, disable scheuling for robot dispatch, allow edit scheduled fleet task

Signed-off-by: Aaron Chong <aaronchongth@gmail.com>

* Comments and consolidate logs

Signed-off-by: Aaron Chong <aaronchongth@gmail.com>

* Slight tree view for robots, change dropdown size, use const enum

Signed-off-by: Aaron Chong <aaronchongth@gmail.com>

* Refactor dispatch and schedule callbacks, fix edit schedule event, add comments

Signed-off-by: Aaron Chong <aaronchongth@gmail.com>

* Remove stale print

Signed-off-by: Aaron Chong <aaronchongth@gmail.com>

* lint

Signed-off-by: Aaron Chong <aaronchongth@gmail.com>

* Make task form more generically named, new callback for schedule editing

Signed-off-by: Aaron Chong <aaronchongth@gmail.com>

* Document props

Signed-off-by: Aaron Chong <aaronchongth@gmail.com>

---------

Signed-off-by: Aaron Chong <aaronchongth@gmail.com>
  • Loading branch information
aaronchongth authored Sep 18, 2024
1 parent 89c9d72 commit 5d670b5
Show file tree
Hide file tree
Showing 14 changed files with 514 additions and 204 deletions.
64 changes: 28 additions & 36 deletions packages/dashboard/src/components/appbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,19 +43,19 @@ import {
import { AlertRequest, FireAlarmTriggerState, TaskFavorite } from 'api-client';
import { formatDistance } from 'date-fns';
import React from 'react';
import { ConfirmationDialog, CreateTaskForm, CreateTaskFormProps } from 'react-components';
import { ConfirmationDialog, TaskForm, TaskFormProps } from 'react-components';
import { Subscription } from 'rxjs';

import { useAppController } from '../hooks/use-app-controller';
import { useAuthenticator } from '../hooks/use-authenticator';
import { useCreateTaskFormData } from '../hooks/use-create-task-form';
import { useTaskFormData } from '../hooks/use-create-task-form';
import { useResources } from '../hooks/use-resources';
import { useRmfApi } from '../hooks/use-rmf-api';
import { useSettings } from '../hooks/use-settings';
import { useTaskRegistry } from '../hooks/use-task-registry';
import { useUserProfile } from '../hooks/use-user-profile';
import { AppEvents } from './app-events';
import { toApiSchedule } from './tasks/utils';
import { dispatchTask, scheduleTask } from './tasks/utils';
import { DashboardThemes } from './theme';

export const APP_BAR_HEIGHT = '3.5rem';
Expand Down Expand Up @@ -116,8 +116,8 @@ export const AppBar = React.memo(
FireAlarmTriggerState | undefined
>(undefined);

const { waypointNames, pickupPoints, dropoffPoints, cleaningZoneNames } =
useCreateTaskFormData(rmfApi);
const { waypointNames, pickupPoints, dropoffPoints, cleaningZoneNames, fleets } =
useTaskFormData(rmfApi);
const username = profile.user.username;

async function handleLogout(): Promise<void> {
Expand Down Expand Up @@ -147,30 +147,23 @@ export const AppBar = React.memo(
return () => subs.forEach((s) => s.unsubscribe());
}, [rmfApi]);

const submitTasks = React.useCallback<Required<CreateTaskFormProps>['submitTasks']>(
async (taskRequests, schedule) => {
if (!schedule) {
await Promise.all(
taskRequests.map((request) => {
console.debug('submitTask:');
console.debug(request);
return rmfApi.tasksApi.postDispatchTaskTasksDispatchTaskPost({
type: 'dispatch_task_request',
request,
});
}),
);
} else {
const scheduleRequests = taskRequests.map((req) => {
console.debug('schedule task:');
console.debug(req);
console.debug(schedule);
return toApiSchedule(req, schedule);
});
await Promise.all(
scheduleRequests.map((req) => rmfApi.tasksApi.postScheduledTaskScheduledTasksPost(req)),
);
const dispatchTaskCallback = React.useCallback<Required<TaskFormProps>['onDispatchTask']>(
async (taskRequest, robotDispatchTarget) => {
if (!rmfApi) {
throw new Error('tasks api not available');
}
await dispatchTask(rmfApi, taskRequest, robotDispatchTarget);
AppEvents.refreshTaskApp.next();
},
[rmfApi],
);

const scheduleTaskCallback = React.useCallback<Required<TaskFormProps>['onScheduleTask']>(
async (taskRequest, schedule) => {
if (!rmfApi) {
throw new Error('tasks api not available');
}
await scheduleTask(rmfApi, taskRequest, schedule);
AppEvents.refreshTaskApp.next();
},
[rmfApi],
Expand All @@ -189,19 +182,15 @@ export const AppBar = React.memo(
return () => sub.unsubscribe();
}, [rmfApi]);

const submitFavoriteTask = React.useCallback<
Required<CreateTaskFormProps>['submitFavoriteTask']
>(
const submitFavoriteTask = React.useCallback<Required<TaskFormProps>['submitFavoriteTask']>(
async (taskFavoriteRequest) => {
await rmfApi.tasksApi.postFavoriteTaskFavoriteTasksPost(taskFavoriteRequest);
AppEvents.refreshFavoriteTasks.next();
},
[rmfApi],
);

const deleteFavoriteTask = React.useCallback<
Required<CreateTaskFormProps>['deleteFavoriteTask']
>(
const deleteFavoriteTask = React.useCallback<Required<TaskFormProps>['deleteFavoriteTask']>(
async (favoriteTask) => {
if (!favoriteTask.id) {
throw new Error('Id is needed');
Expand Down Expand Up @@ -464,8 +453,9 @@ export const AppBar = React.memo(
</Menu>

{openCreateTaskForm && (
<CreateTaskForm
<TaskForm
user={username ? username : 'unknown user'}
fleets={fleets}
tasksToDisplay={taskRegistry.taskDefinitions}
patrolWaypoints={waypointNames}
cleaningZones={cleaningZoneNames}
Expand All @@ -476,7 +466,9 @@ export const AppBar = React.memo(
favoritesTasks={favoritesTasks}
open={openCreateTaskForm}
onClose={() => setOpenCreateTaskForm(false)}
submitTasks={submitTasks}
onDispatchTask={dispatchTaskCallback}
onScheduleTask={scheduleTaskCallback}
onEditScheduleTask={undefined}
submitFavoriteTask={submitFavoriteTask}
deleteFavoriteTask={deleteFavoriteTask}
onSuccess={() => {
Expand Down
82 changes: 53 additions & 29 deletions packages/dashboard/src/components/tasks/task-schedule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ import { ScheduledTask, ScheduledTaskScheduleOutput as ApiSchedule } from 'api-c
import React from 'react';
import {
ConfirmationDialog,
CreateTaskForm,
CreateTaskFormProps,
EventEditDeletePopup,
Schedule,
TaskForm,
TaskFormProps,
} from 'react-components';

import { useAppController } from '../../hooks/use-app-controller';
import { useCreateTaskFormData } from '../../hooks/use-create-task-form';
import { useTaskFormData } from '../../hooks/use-create-task-form';
import { useRmfApi } from '../../hooks/use-rmf-api';
import { useTaskRegistry } from '../../hooks/use-task-registry';
import { useUserProfile } from '../../hooks/use-user-profile';
Expand All @@ -33,7 +33,7 @@ import {
scheduleWithSelectedDay,
toISOStringWithTimezone,
} from './task-schedule-utils';
import { toApiSchedule } from './utils';
import { dispatchTask, editScheduledTaskEvent, editScheduledTaskSchedule } from './utils';

enum EventScopes {
ALL = 'all',
Expand Down Expand Up @@ -71,8 +71,8 @@ export const TaskSchedule = () => {
const rmfApi = useRmfApi();
const { showAlert } = useAppController();

const { waypointNames, pickupPoints, dropoffPoints, cleaningZoneNames } =
useCreateTaskFormData(rmfApi);
const { waypointNames, pickupPoints, dropoffPoints, cleaningZoneNames, fleets } =
useTaskFormData(rmfApi);
const username = useUserProfile().user.username;
const taskRegistry = useTaskRegistry();
const [eventScope, setEventScope] = React.useState<string>(EventScopes.CURRENT);
Expand Down Expand Up @@ -191,30 +191,51 @@ export const TaskSchedule = () => {
);
};

const submitTasks = React.useCallback<Required<CreateTaskFormProps>['submitTasks']>(
async (taskRequests, schedule) => {
if (!schedule || !currentScheduleTask) {
throw new Error('No schedule or task selected for submission.');
const dispatchTaskCallback = React.useCallback<Required<TaskFormProps>['onDispatchTask']>(
async (taskRequest, robotDispatchTarget) => {
if (!rmfApi) {
throw new Error('tasks api not available');
}
await dispatchTask(rmfApi, taskRequest, robotDispatchTarget);
AppEvents.refreshTaskApp.next();
},
[rmfApi],
);

const scheduleRequests = taskRequests.map((req) => toApiSchedule(req, schedule));
const editScheduledTaskCallback = React.useCallback<
Required<TaskFormProps>['onEditScheduleTask']
>(
async (taskRequest, schedule) => {
if (!rmfApi) {
throw new Error('tasks api not available');
}

let exceptDate: string | undefined = undefined;
if (eventScope === EventScopes.CURRENT) {
exceptDate = toISOStringWithTimezone(exceptDateRef.current);
console.debug(`Editing schedule id ${currentScheduleTask.id}, event date ${exceptDate}`);
} else {
console.debug(`Editing schedule id ${currentScheduleTask.id}`);
if (!currentScheduleTask) {
throw new Error('No schedule task selected for submission.');
}

await Promise.all(
scheduleRequests.map((req) =>
rmfApi.tasksApi.updateScheduleTaskScheduledTasksTaskIdUpdatePost(
currentScheduleTask.id,
req,
exceptDate,
),
),
// Edit entire schedule
if (eventScope !== EventScopes.CURRENT) {
console.debug(
`Editing schedule id [${currentScheduleTask.id}] with new schedule: ${schedule}`,
);
await editScheduledTaskSchedule(rmfApi, taskRequest, schedule, currentScheduleTask.id);

setEventScope(EventScopes.CURRENT);
AppEvents.refreshTaskSchedule.next();
return;
}

// Edit a single event
console.debug(
`Editing schedule id [${currentScheduleTask.id}] event [${exceptDateRef.current}] with new schedule: ${schedule}`,
);
await editScheduledTaskEvent(
rmfApi,
taskRequest,
schedule,
exceptDateRef.current,
currentScheduleTask.id,
);

setEventScope(EventScopes.CURRENT);
Expand Down Expand Up @@ -316,22 +337,25 @@ export const TaskSchedule = () => {
onSelectedDateChange={setSelectedDate}
/>
{openCreateTaskForm && (
<CreateTaskForm
<TaskForm
user={username ? username : 'unknown user'}
fleets={fleets}
tasksToDisplay={taskRegistry.taskDefinitions}
patrolWaypoints={waypointNames}
cleaningZones={cleaningZoneNames}
pickupPoints={pickupPoints}
dropoffPoints={dropoffPoints}
open={openCreateTaskForm}
scheduleToEdit={scheduleToEdit}
requestTask={currentScheduleTask?.task_request}
schedule={scheduleToEdit}
taskRequest={currentScheduleTask?.task_request}
onClose={() => {
setOpenCreateTaskForm(false);
setEventScope(EventScopes.CURRENT);
AppEvents.refreshTaskSchedule.next();
}}
submitTasks={submitTasks}
onDispatchTask={dispatchTaskCallback}
onScheduleTask={undefined}
onEditScheduleTask={editScheduledTaskCallback}
onSuccess={() => {
setOpenCreateTaskForm(false);
showAlert('success', 'Successfully created task');
Expand Down
82 changes: 80 additions & 2 deletions packages/dashboard/src/components/tasks/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import { PostScheduledTaskRequest, TaskRequest, TaskStateOutput as TaskState } from 'api-client';
import { getTaskBookingLabelFromTaskState, Schedule } from 'react-components';
import {
AddExceptDateRequest,
PostScheduledTaskRequest,
RobotTaskRequest,
TaskRequest,
TaskStateOutput as TaskState,
} from 'api-client';
import { getTaskBookingLabelFromTaskState, RobotDispatchTarget, Schedule } from 'react-components';

import { RmfApi } from '../../services/rmf-api';
import { toISOStringWithTimezone } from './task-schedule-utils';

export function exportCsvFull(timestamp: Date, allTasks: TaskState[]) {
const columnSeparator = ';';
Expand Down Expand Up @@ -115,3 +124,72 @@ export const toApiSchedule = (
schedules: apiSchedules,
};
};

export async function dispatchTask(
rmf: RmfApi,
taskRequest: TaskRequest,
robotDispatchTarget: RobotDispatchTarget | null,
) {
if (robotDispatchTarget) {
const robotTask: RobotTaskRequest = {
type: 'robot_task_request',
robot: robotDispatchTarget.robot,
fleet: robotDispatchTarget.fleet,
request: taskRequest,
};
console.debug(`dispatch robot task: ${robotTask}`);
await rmf.tasksApi.postRobotTaskTasksRobotTaskPost(robotTask);
return;
}

console.debug(`dispatch task: ${taskRequest}`);
await rmf.tasksApi.postDispatchTaskTasksDispatchTaskPost({
type: 'dispatch_task_request',
request: taskRequest,
});
}

export async function scheduleTask(rmf: RmfApi, taskRequest: TaskRequest, schedule: Schedule) {
console.debug(`schedule task: ${taskRequest}\nschedule: ${schedule}`);
const scheduleRequest = toApiSchedule(taskRequest, schedule);
await rmf.tasksApi.postScheduledTaskScheduledTasksPost(scheduleRequest);
}

export async function editScheduledTaskEvent(
rmf: RmfApi,
taskRequest: TaskRequest,
newEventSchedule: Schedule,
newEventDate: Date,
originalScheduleTaskId: number,
) {
const addExceptDateRequest: AddExceptDateRequest = {
except_date: toISOStringWithTimezone(newEventDate),
};
console.debug(
`Adding [${addExceptDateRequest.except_date}] to except date of scheduled task [${originalScheduleTaskId}]`,
);
await rmf.tasksApi.addExceptDateScheduledTasksTaskIdExceptDatePost(
originalScheduleTaskId,
addExceptDateRequest,
);

console.debug(
`creating new schedule for edited event: ${taskRequest}\nschedule: ${newEventSchedule}`,
);
const newScheduleRequest = toApiSchedule(taskRequest, newEventSchedule);
await rmf.tasksApi.postScheduledTaskScheduledTasksPost(newScheduleRequest);
}

export async function editScheduledTaskSchedule(
rmf: RmfApi,
taskRequest: TaskRequest,
newSchedule: Schedule,
scheduleTaskId: number,
) {
const scheduleRequest = toApiSchedule(taskRequest, newSchedule);

await rmf.tasksApi.updateScheduleTaskScheduledTasksTaskIdUpdatePost(
scheduleTaskId,
scheduleRequest,
);
}
17 changes: 15 additions & 2 deletions packages/dashboard/src/hooks/use-create-task-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import { Subscription } from 'rxjs';

import { RmfApi } from '../services/rmf-api';

export const useCreateTaskFormData = (rmfApi: RmfApi | undefined) => {
export const useTaskFormData = (rmfApi: RmfApi | undefined) => {
const [waypointNames, setWaypointNames] = React.useState<string[]>([]);
const [cleaningZoneNames, setCleaningZoneNames] = React.useState<string[]>([]);
const [pickupPoints, setPickupPoints] = React.useState<Record<string, string>>({});
const [dropoffPoints, setDropoffPoints] = React.useState<Record<string, string>>({});
const [fleets, setFleets] = React.useState<Record<string, string[]>>({});

React.useEffect(() => {
if (!rmfApi) {
Expand Down Expand Up @@ -42,9 +43,21 @@ export const useCreateTaskFormData = (rmfApi: RmfApi | undefined) => {
setWaypointNames(waypointNames);
}),
);
subs.push(
rmfApi.fleetsObs.subscribe((fleetStates) => {
const result: Record<string, string[]> = {};
for (const fleet of fleetStates) {
if (!fleet.name || !fleet.robots) {
continue;
}
result[fleet.name] = Object.keys(fleet.robots);
}
setFleets(result);
}),
);

return () => subs.forEach((s) => s.unsubscribe());
}, [rmfApi]);

return { waypointNames, pickupPoints, dropoffPoints, cleaningZoneNames };
return { waypointNames, pickupPoints, dropoffPoints, cleaningZoneNames, fleets };
};
1 change: 1 addition & 0 deletions packages/dashboard/src/services/rmf-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ export class DefaultRmfApi implements RmfApi {
return this._ingestorStateObsStore[guid];
}

// NOTE: This only emits once and doesn't update when the fleet changes.
fleetsObs: Observable<FleetState[]>;
private _fleetStateObsStore: Record<string, Observable<FleetState>> = {};
getFleetStateObs(name: string): Observable<FleetState> {
Expand Down
Loading

0 comments on commit 5d670b5

Please sign in to comment.