diff --git a/packages/dashboard-e2e/README.md b/packages/dashboard-e2e/README.md index ae9ed894a..598362965 100644 --- a/packages/dashboard-e2e/README.md +++ b/packages/dashboard-e2e/README.md @@ -52,13 +52,13 @@ browser.overwriteCommand( ``` ```ts -// this will fail depending on the context on which `getLoopOption` is called. -const getLoopOption = async () => { - return $$('[role=option]').find(async (elem) => await elem.getText() === 'Loop'); +// this will fail depending on the context on which `getPatrolOption` is called. +const getPatrolOption = async () => { + return $$('[role=option]').find(async (elem) => await elem.getText() === 'Patrol'); }; -await browser.waitUntil(async () => !!(await getLoopOption)); // ok -await console.log(await getLoopOption()); // error +await browser.waitUntil(async () => !!(await getPatrolOption)); // ok +await console.log(await getPatrolOption()); // error ``` -Possible reason is because async selectors chaining relies on a "finalizer" or some kind of context to resolve the promises. As a result, when running `getLoopOption` without a wdio await, the chaining does not work. But this is all speculation, the inner workings of async selector chainings are very complex. +Possible reason is because async selectors chaining relies on a "finalizer" or some kind of context to resolve the promises. As a result, when running `getPatrolOption` without a wdio await, the chaining does not work. But this is all speculation, the inner workings of async selector chainings are very complex. diff --git a/packages/dashboard-e2e/tests/ui-interactions/submit-task.test.ts b/packages/dashboard-e2e/tests/ui-interactions/submit-task.test.ts index b9448604e..7c7531244 100644 --- a/packages/dashboard-e2e/tests/ui-interactions/submit-task.test.ts +++ b/packages/dashboard-e2e/tests/ui-interactions/submit-task.test.ts @@ -1,24 +1,24 @@ import { getAppBar } from '../utils'; describe('submit task', () => { - it('can submit loop task', async () => { + it('can submit patrol task', async () => { const appBar = await getAppBar(); await (await appBar.$('button[aria-label="Tasks"]')).click(); await (await appBar.$('button[aria-label="new task"]')).click(); await (await $('#task-type')).click(); - const getLoopOption = async () => { + const getPatrolOption = async () => { const options = await $$('[role=option]'); for (const opt of options) { const text = await opt.getText(); - if (text === 'Loop') { + if (text === 'Patrol') { return opt; } } return null; }; - await browser.waitUntil(async () => !!(await getLoopOption())); - const loopOption = (await getLoopOption())!; - await loopOption.click(); + await browser.waitUntil(async () => !!(await getPatrolOption())); + const patrolOption = (await getPatrolOption())!; + await patrolOption.click(); await (await $('#place-input')).setValue('coe'); diff --git a/packages/dashboard/src/components/appbar.tsx b/packages/dashboard/src/components/appbar.tsx index ac9000603..ba95a5856 100644 --- a/packages/dashboard/src/components/appbar.tsx +++ b/packages/dashboard/src/components/appbar.tsx @@ -35,7 +35,7 @@ import { AppBarTab, CreateTaskForm, CreateTaskFormProps, - getPlaces, + getNamedPlaces, HeaderBar, LogoButton, NavigationBar, @@ -167,8 +167,10 @@ export const AppBar = React.memo(({ extraToolbarItems }: AppBarProps): React.Rea const [brandingIconPath, setBrandingIconPath] = React.useState(''); const [settingsAnchor, setSettingsAnchor] = React.useState(null); const [openCreateTaskForm, setOpenCreateTaskForm] = React.useState(false); - const [placeNames, setPlaceNames] = React.useState([]); - const [workcells, setWorkcells] = React.useState(); + const [waypointNames, setWaypointNames] = React.useState([]); + const [cleaningZoneNames, setCleaningZoneNames] = React.useState([]); + const [pickupPointNames, setPickupPointNames] = React.useState([]); + const [dropoffPointNames, setDropoffPointNames] = React.useState([]); const [favoritesTasks, setFavoritesTasks] = React.useState([]); const [refreshTaskAppCount, setRefreshTaskAppCount] = React.useState(0); const [username, setUsername] = React.useState(null); @@ -214,13 +216,6 @@ export const AppBar = React.memo(({ extraToolbarItems }: AppBarProps): React.Rea })(); }, [logoResourcesContext, safeAsync, curTheme]); - React.useEffect(() => { - if (!resourceManager?.dispensers) { - return; - } - setWorkcells(Object.keys(resourceManager.dispensers.dispensers)); - }, [resourceManager]); - React.useEffect(() => { if (!rmf) { return; @@ -228,9 +223,13 @@ export const AppBar = React.memo(({ extraToolbarItems }: AppBarProps): React.Rea const subs: Subscription[] = []; subs.push( - rmf.buildingMapObs.subscribe((map) => - setPlaceNames(getPlaces(map).map((p) => p.vertex.name)), - ), + rmf.buildingMapObs.subscribe((map) => { + const namedPlaces = getNamedPlaces(map); + setPickupPointNames(namedPlaces.pickupPoints.map((w) => w.vertex.name)); + setDropoffPointNames(namedPlaces.dropoffPoints.map((w) => w.vertex.name)); + setCleaningZoneNames(namedPlaces.cleaningZones.map((w) => w.vertex.name)); + setWaypointNames(namedPlaces.places.map((w) => w.vertex.name)); + }), ); subs.push( AppEvents.refreshAlertCount.subscribe((_) => { @@ -578,11 +577,10 @@ export const AppBar = React.memo(({ extraToolbarItems }: AppBarProps): React.Rea {openCreateTaskForm && ( setOpenCreateTaskForm(false)} diff --git a/packages/react-components/lib/place.ts b/packages/react-components/lib/place.ts index 55acfdad6..6afaba637 100644 --- a/packages/react-components/lib/place.ts +++ b/packages/react-components/lib/place.ts @@ -1,10 +1,21 @@ import type { BuildingMap, GraphNode } from 'api-client'; +const DEFAULT_PICKUP_POINT_PARAM_NAME = 'pickup_dispenser'; +const DEFAULT_DROPOFF_POINT_PARAM_NAME = 'dropoff_ingestor'; +const DEFAULT_CLEANING_ZONE_PARAM_NAME = 'is_cleaning_zone'; + export interface Place { level: string; vertex: GraphNode; } +export interface NamedPlaces { + places: Place[]; + pickupPoints: Place[]; + dropoffPoints: Place[]; + cleaningZones: Place[]; +} + export function getPlaces(buildingMap: BuildingMap): Place[] { const places: Place[] = []; for (const level of buildingMap.levels) { @@ -18,3 +29,39 @@ export function getPlaces(buildingMap: BuildingMap): Place[] { } return places; } + +export function getNamedPlaces(buildingMap: BuildingMap): NamedPlaces { + const places = new Map(); + const pickupPoints = new Map(); + const dropoffPoints = new Map(); + const cleaningZones = new Map(); + + for (const level of buildingMap.levels) { + for (const graphs of level.nav_graphs) { + for (const vertex of graphs.vertices) { + if (!vertex.name) { + continue; + } + const place: Place = { level: level.name, vertex }; + for (const p of vertex.params) { + if (p.name === DEFAULT_PICKUP_POINT_PARAM_NAME) { + pickupPoints.set(vertex.name, place); + } + if (p.name === DEFAULT_DROPOFF_POINT_PARAM_NAME) { + dropoffPoints.set(vertex.name, place); + } + if (p.name === DEFAULT_CLEANING_ZONE_PARAM_NAME) { + cleaningZones.set(vertex.name, place); + } + } + places.set(vertex.name, place); + } + } + } + return { + places: Array.from(places.values()), + pickupPoints: Array.from(pickupPoints.values()), + dropoffPoints: Array.from(dropoffPoints.values()), + cleaningZones: Array.from(cleaningZones.values()), + }; +} diff --git a/packages/react-components/lib/tasks/create-task.stories.tsx b/packages/react-components/lib/tasks/create-task.stories.tsx index 08e98daa0..7b39ce6de 100644 --- a/packages/react-components/lib/tasks/create-task.stories.tsx +++ b/packages/react-components/lib/tasks/create-task.stories.tsx @@ -24,8 +24,7 @@ export const CreateTask: Story = (args) => { CreateTask.args = { submitTasks: async () => new Promise((res) => setTimeout(res, 500)), cleaningZones: ['test_zone_0', 'test_zone_1'], - loopWaypoints: ['test_waypoint_0', 'test_waypoint_1'], - deliveryWaypoints: ['test_waypoint_0', 'test_waypoint_1'], - dispensers: ['test_dispenser_0', 'test_dispenser_1'], - ingestors: ['test_ingestor_0', 'test_ingestor_1'], + patrolWaypoints: ['test_waypoint_0', 'test_waypoint_1'], + pickupPoints: ['test_waypoint_0'], + dropoffPoints: ['test_waypoint_1'], }; diff --git a/packages/react-components/lib/tasks/create-task.tsx b/packages/react-components/lib/tasks/create-task.tsx index 38382c1e0..271f8059a 100644 --- a/packages/react-components/lib/tasks/create-task.tsx +++ b/packages/react-components/lib/tasks/create-task.tsx @@ -45,7 +45,6 @@ import { PositiveIntField } from '../form-inputs'; interface DeliveryTaskDescription { pickup: { place: string; - handler: string; payload: { sku: string; quantity: number; @@ -53,7 +52,6 @@ interface DeliveryTaskDescription { }; dropoff: { place: string; - handler: string; payload: { sku: string; quantity: number; @@ -61,7 +59,7 @@ interface DeliveryTaskDescription { }; } -interface LoopTaskDescription { +interface PatrolTaskDescription { places: string[]; rounds: number; } @@ -70,7 +68,7 @@ interface CleanTaskDescription { zone: string; } -type TaskDescription = DeliveryTaskDescription | LoopTaskDescription | CleanTaskDescription; +type TaskDescription = DeliveryTaskDescription | PatrolTaskDescription | CleanTaskDescription; const classes = { title: 'dialogue-info-value', @@ -136,29 +134,27 @@ function FormToolbar({ onSelectFileClick }: FormToolbarProps) { interface DeliveryTaskFormProps { taskDesc: DeliveryTaskDescription; - deliveryWaypoints: string[]; - dispensers: string[]; - ingestors: string[]; + pickupPoints: string[]; + dropoffPoints: string[]; onChange(taskDesc: TaskDescription): void; } function DeliveryTaskForm({ taskDesc, - deliveryWaypoints, - dispensers, - ingestors, + pickupPoints, + dropoffPoints, onChange, }: DeliveryTaskFormProps) { const theme = useTheme(); return ( - + newValue !== null && @@ -184,18 +180,21 @@ function DeliveryTaskForm({ newValue !== null && onChange({ ...taskDesc, pickup: { ...taskDesc.pickup, - handler: newValue, + payload: { + ...taskDesc.pickup.payload, + sku: newValue, + }, }, }) } @@ -203,78 +202,23 @@ function DeliveryTaskForm({ onChange({ ...taskDesc, pickup: { - ...taskDesc.pickup, - handler: (ev.target as HTMLInputElement).value, - }, - }) - } - renderInput={(params) => } - /> - - - - newValue !== null && - onChange({ - ...taskDesc, - dropoff: { - ...taskDesc.dropoff, - place: newValue, - }, - }) - } - onBlur={(ev) => - onChange({ - ...taskDesc, - dropoff: { - ...taskDesc.dropoff, - place: (ev.target as HTMLInputElement).value, - }, - }) - } - renderInput={(params) => } - /> - - - - newValue !== null && - onChange({ - ...taskDesc, - dropoff: { ...taskDesc.dropoff, - handler: newValue, - }, - }) - } - onBlur={(ev) => - onChange({ - ...taskDesc, - dropoff: { - ...taskDesc.dropoff, - handler: (ev.target as HTMLInputElement).value, + payload: { + ...taskDesc.pickup.payload, + sku: (ev.target as HTMLInputElement).value, + }, }, }) } - renderInput={(params) => } + renderInput={(params) => } /> - + newValue !== null && @@ -284,7 +228,7 @@ function DeliveryTaskForm({ ...taskDesc.pickup, payload: { ...taskDesc.pickup.payload, - sku: newValue, + quantity: typeof newValue == 'string' ? parseInt(newValue) : newValue, }, }, }) @@ -293,50 +237,44 @@ function DeliveryTaskForm({ onChange({ ...taskDesc, pickup: { - ...taskDesc.dropoff, + ...taskDesc.pickup, payload: { ...taskDesc.pickup.payload, - sku: (ev.target as HTMLInputElement).value, + quantity: parseInt((ev.target as HTMLInputElement).value), }, }, }) } - renderInput={(params) => } + renderInput={(params) => } /> - + newValue !== null && onChange({ ...taskDesc, - pickup: { - ...taskDesc.pickup, - payload: { - ...taskDesc.pickup.payload, - quantity: typeof newValue == 'string' ? parseInt(newValue) : newValue, - }, + dropoff: { + ...taskDesc.dropoff, + place: newValue, }, }) } onBlur={(ev) => onChange({ ...taskDesc, - pickup: { - ...taskDesc.pickup, - payload: { - ...taskDesc.pickup.payload, - quantity: parseInt((ev.target as HTMLInputElement).value), - }, + dropoff: { + ...taskDesc.dropoff, + place: (ev.target as HTMLInputElement).value, }, }) } - renderInput={(params) => } + renderInput={(params) => } /> @@ -379,7 +317,7 @@ function DeliveryTaskForm({ id="dropoff_quantity" freeSolo fullWidth - value={taskDesc.dropoff.payload.quantity} + value={`${taskDesc.dropoff.payload.quantity}`} options={[]} onChange={(_ev, newValue) => newValue !== null && @@ -406,7 +344,7 @@ function DeliveryTaskForm({ }, }) } - renderInput={(params) => } + renderInput={(params) => } /> @@ -448,15 +386,14 @@ function PlaceList({ places, onClick }: PlaceListProps) { ); } -interface LoopTaskFormProps { - taskDesc: LoopTaskDescription; - loopWaypoints: string[]; - onChange(loopTaskDescription: LoopTaskDescription): void; +interface PatrolTaskFormProps { + taskDesc: PatrolTaskDescription; + patrolWaypoints: string[]; + onChange(patrolTaskDescription: PatrolTaskDescription): void; } -function LoopTaskForm({ taskDesc, loopWaypoints, onChange }: LoopTaskFormProps) { +function PatrolTaskForm({ taskDesc, patrolWaypoints, onChange }: PatrolTaskFormProps) { const theme = useTheme(); - return ( @@ -464,7 +401,7 @@ function LoopTaskForm({ taskDesc, loopWaypoints, onChange }: LoopTaskFormProps) id="place-input" freeSolo fullWidth - options={loopWaypoints} + options={patrolWaypoints} onChange={(_ev, newValue) => newValue !== null && onChange({ @@ -600,7 +537,7 @@ function defaultCleanTask(): CleanTaskDescription { }; } -function defaultLoopTask(): LoopTaskDescription { +function defaultPatrolTask(): PatrolTaskDescription { return { places: [], rounds: 1, @@ -611,7 +548,6 @@ function defaultDeliveryTask(): DeliveryTaskDescription { return { pickup: { place: '', - handler: '', payload: { sku: '', quantity: 1, @@ -619,7 +555,6 @@ function defaultDeliveryTask(): DeliveryTaskDescription { }, dropoff: { place: '', - handler: '', payload: { sku: '', quantity: 1, @@ -633,7 +568,7 @@ function defaultTaskDescription(taskCategory: string): TaskDescription | undefin case 'clean': return defaultCleanTask(); case 'patrol': - return defaultLoopTask(); + return defaultPatrolTask(); case 'delivery': return defaultDeliveryTask(); default: @@ -644,7 +579,7 @@ function defaultTaskDescription(taskCategory: string): TaskDescription | undefin function defaultTask(): TaskRequest { return { category: 'patrol', - description: defaultLoopTask(), + description: defaultPatrolTask(), unix_millis_earliest_start_time: 0, unix_millis_request_time: Date.now(), priority: { type: 'binary', value: 0 }, @@ -714,7 +649,7 @@ const defaultFavoriteTask = (): TaskFavorite => { id: '', name: '', category: 'patrol', - description: defaultLoopTask(), + description: defaultPatrolTask(), unix_millis_earliest_start_time: 0, priority: { type: 'binary', value: 0 }, user: '', @@ -729,10 +664,9 @@ export interface CreateTaskFormProps user: string; allowBatch?: boolean; cleaningZones?: string[]; - loopWaypoints?: string[]; - deliveryWaypoints?: string[]; - dispensers?: string[]; - ingestors?: string[]; + patrolWaypoints?: string[]; + pickupPoints?: string[]; + dropoffPoints?: string[]; favoritesTasks: TaskFavorite[]; submitTasks?(tasks: TaskRequest[], schedule: Schedule | null): Promise; tasksFromFile?(): Promise | TaskRequest[]; @@ -749,10 +683,9 @@ export interface CreateTaskFormProps export function CreateTaskForm({ user, cleaningZones = [], - loopWaypoints = [], - deliveryWaypoints = [], - dispensers = [], - ingestors = [], + patrolWaypoints = [], + pickupPoints = [], + dropoffPoints = [], favoritesTasks = [], submitTasks, tasksFromFile, @@ -843,9 +776,9 @@ export function CreateTaskForm({ ); case 'patrol': return ( - handleTaskDescriptionChange('patrol', desc)} /> ); @@ -853,9 +786,8 @@ export function CreateTaskForm({ return ( handleTaskDescriptionChange('delivery', desc)} /> ); @@ -1065,9 +997,29 @@ export function CreateTaskForm({ value={taskRequest.category} onChange={handleTaskTypeChange} > - Clean - Loop - Delivery + + Clean + + + Patrol + + + Delivery +