Skip to content

Commit

Permalink
Export minimal with new columns
Browse files Browse the repository at this point in the history
Signed-off-by: Aaron Chong <aaronchongth@gmail.com>
  • Loading branch information
aaronchongth committed Nov 28, 2023
1 parent e2f021c commit f31dce3
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 121 deletions.
24 changes: 23 additions & 1 deletion packages/dashboard/src/components/tasks/tasks-app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -242,14 +242,36 @@ export const TasksApp = React.memo(
return allTasks;
};

const getAllTaskRequests = async (tasks: TaskState[]) => {
if (!rmf) {
return {};
}

const taskIds: string[] = tasks.map((task) => task.booking.id);
const taskIdsQuery = taskIds.join(',');
const taskRequests = (await rmf.tasksApi.queryTaskRequestsTasksRequestsGet(taskIdsQuery))
.data;

const taskRequestMap: Record<string, TaskRequest> = {};
let requestIndex = 0;
for (const id of taskIds) {
if (requestIndex < taskRequests.length && taskRequests[requestIndex]) {
taskRequestMap[id] = taskRequests[requestIndex];
}
++requestIndex;
}
return taskRequestMap;
};

const exportTasksToCsv = async (minimal: boolean) => {
const now = new Date();
const allTasks = await getAllTasks(now);
const allTaskRequests = await getAllTaskRequests(allTasks);
if (!allTasks || !allTasks.length) {
return;
}
if (minimal) {
downloadCsvMinimal(now, allTasks);
downloadCsvMinimal(now, allTasks, allTaskRequests);
} else {
downloadCsvFull(now, allTasks);
}
Expand Down
40 changes: 28 additions & 12 deletions packages/dashboard/src/components/tasks/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { PostScheduledTaskRequest, TaskRequest, TaskState } from 'api-client';
import { Schedule } from 'react-components';
import { Schedule, parsePickup, parseDestination } from 'react-components';
import schema from 'api-client/dist/schema';
import { ajv } from '../utils';

Expand Down Expand Up @@ -46,40 +46,56 @@ export function downloadCsvFull(timestamp: Date, allTasks: TaskState[]) {
});
}

export function downloadCsvMinimal(timestamp: Date, allTasks: TaskState[]) {
export function downloadCsvMinimal(
timestamp: Date,
allTasks: TaskState[],
taskRequestMap: Record<string, TaskRequest>,
) {
const columnSeparator = ';';
const rowSeparator = '\n';
const keys = [
'Date',
'Requester',
'ID',
'Category',
'Assignee',
'Pickup',
'Destination',
'Robot',
'Start Time',
'End Time',
'State',
];
let csvContent = keys.join(columnSeparator) + rowSeparator;
allTasks.forEach((task) => {
const request: TaskRequest | undefined = taskRequestMap[task.booking.id];
const values = [
// Date
task.booking.unix_millis_request_time
? `${new Date(task.booking.unix_millis_request_time).toLocaleDateString()}`
: 'unknown',
task.booking.requester ? task.booking.requester : 'unknown',
task.booking.id,
task.category ? task.category : 'unknown',
task.assigned_to ? task.assigned_to.name : 'unknown',
: 'n/a',
// Requester
task.booking.requester ? task.booking.requester : 'n/a',
// Category
task.category ? task.category : 'n/a',
// Pickup
parsePickup(task, request),
// Destination
parseDestination(task, request),
// Robot
task.assigned_to ? task.assigned_to.name : 'n/a',
// Start Time
task.unix_millis_start_time
? `${new Date(task.unix_millis_start_time).toLocaleDateString()} ${new Date(
task.unix_millis_start_time,
).toLocaleTimeString()}`
: 'unknown',
: 'n/a',
// End Time
task.unix_millis_finish_time
? `${new Date(task.unix_millis_finish_time).toLocaleDateString()} ${new Date(
task.unix_millis_finish_time,
).toLocaleTimeString()}`
: 'unknown',
task.status ? task.status : 'unknown',
: 'n/a',
// State
task.status ? task.status : 'n/a',
];
csvContent += values.join(columnSeparator) + rowSeparator;
});
Expand Down
1 change: 1 addition & 0 deletions packages/react-components/lib/tasks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export * from './task-table';
export * from './task-timeline';
export * from './task-table-datagrid';
export * from './task-schedule-event-edit-delete-popup';
export * from './utils';
116 changes: 9 additions & 107 deletions packages/react-components/lib/tasks/task-table-datagrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { styled, TextField, Stack, Typography, Tooltip, useMediaQuery } from '@m
import * as React from 'react';
import { TaskState, TaskRequest, Status } from 'api-client';
import { InsertInvitation as ScheduleIcon, Person as UserIcon } from '@mui/icons-material/';
import { parsePickup, parseDestination } from './utils';

const classes = {
taskActiveCell: 'MuiDataGrid-cell-active-cell',
Expand Down Expand Up @@ -134,111 +135,6 @@ export function TaskDataGridTable({
(operator) => operator.value === 'equals',
);

const getPickup = (state: TaskState): string => {
const request: TaskRequest | undefined = tasks.requests[state.booking.id];
if (request === undefined || request.category.toLowerCase() === 'patrol') {
return 'n/a';
}

// custom deliveries
const supportedDeliveries = [
'delivery_pickup',
'delivery_sequential_lot_pickup',
'delivery_area_pickup',
];
if (
!request.description['category'] ||
!supportedDeliveries.includes(request.description['category'])
) {
return 'n/a';
}

// TODO(ac): use schemas
try {
const deliveryType: string = request.description['category'];
const perform_action_description =
request.description['phases'][0]['activity']['description']['activities'][1]['description'][
'description'
];

switch (deliveryType) {
case 'delivery_pickup': {
const pickup_lot: string = perform_action_description['pickup_lot'];
return pickup_lot;
}
case 'delivery_sequential_lot_pickup':
case 'delivery_area_pickup': {
const pickup_zone: string = perform_action_description['pickup_zone'];
return pickup_zone;
}
default:
return 'n/a';
}
} catch (e) {
console.error(`Failed to parse pickup lot/zone from task request: ${(e as Error).message}`);
}

return 'n/a';
};

const getDestination = (state: TaskState): string => {
const request: TaskRequest | undefined = tasks.requests[state.booking.id];
if (request === undefined) {
return 'n/a';
}

// patrol
if (
request.category.toLowerCase() === 'patrol' &&
request.description['places'] !== undefined &&
request.description['places'].length > 0
) {
return request.description['places'].at(-1);
}

// custom deliveries
const supportedDeliveries = [
'delivery_pickup',
'delivery_sequential_lot_pickup',
'delivery_area_pickup',
];
if (
!request.description['category'] ||
!supportedDeliveries.includes((request.description['category'] as string).toLowerCase())
) {
return 'n/a';
}

// TODO(ac): use schemas
try {
const destination =
request.description['phases'][0]['activity']['description']['activities'][2]['description'];
return destination;
} catch (e) {
console.error(`Failed to parse destination from task request: ${(e as Error).message}`);
}

// automated tasks that can only be parsed with state
if (state.category && state.category === 'Charge Battery') {
try {
const charge_phase = state['phases'] ? state['phases']['1'] : undefined;
const charge_events = charge_phase ? charge_phase.events : undefined;
const charge_event_name = charge_events ? charge_events['1'].name : undefined;
if (charge_event_name === undefined) {
console.error('Unable to parse charging event name.');
return 'n/a';
}

const charging_place = charge_event_name?.split('[place:')[1].split(']')[0];
return charging_place;
} catch (e) {
console.error(`Failed to parse charging point from task state: ${(e as Error).message}`);
}
}

return 'n/a';
};

const getMinimalDateOperators = getGridDateOperators(true).filter(
(operator) => operator.value === 'onOrAfter' || operator.value === 'onOrBefore',
);
Expand Down Expand Up @@ -295,7 +191,10 @@ export function TaskDataGridTable({
headerName: 'Pickup',
width: 150,
editable: false,
valueGetter: (params: GridValueGetterParams) => getPickup(params.row),
valueGetter: (params: GridValueGetterParams) => {
const request: TaskRequest | undefined = tasks.requests[params.row.booking.id];
return parsePickup(params.row, request);
},
flex: 1,
filterOperators: getMinimalStringFilterOperators,
filterable: true,
Expand All @@ -305,7 +204,10 @@ export function TaskDataGridTable({
headerName: 'Destination',
width: 150,
editable: false,
valueGetter: (params: GridValueGetterParams) => getDestination(params.row),
valueGetter: (params: GridValueGetterParams) => {
const request: TaskRequest | undefined = tasks.requests[params.row.booking.id];
return parseDestination(params.row, request);
},
flex: 1,
filterOperators: getMinimalStringFilterOperators,
filterable: true,
Expand Down
105 changes: 104 additions & 1 deletion packages/react-components/lib/tasks/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { TaskType as RmfTaskType } from 'rmf-models';
import type { TaskState } from 'api-client';
import type { TaskState, TaskRequest } from 'api-client';

export function taskTypeToStr(taskType: number): string {
switch (taskType) {
Expand Down Expand Up @@ -46,3 +46,106 @@ export function parseTaskDetail(
return {};
}
}

export function parsePickup(state: TaskState, request?: TaskRequest): string {
if (request === undefined || request.category.toLowerCase() === 'patrol') {
return 'n/a';
}

// custom deliveries
const supportedDeliveries = [
'delivery_pickup',
'delivery_sequential_lot_pickup',
'delivery_area_pickup',
];
if (
!request.description['category'] ||
!supportedDeliveries.includes(request.description['category'])
) {
return 'n/a';
}

// TODO(ac): use schemas
try {
const deliveryType: string = request.description['category'];
const perform_action_description =
request.description['phases'][0]['activity']['description']['activities'][1]['description'][
'description'
];

switch (deliveryType) {
case 'delivery_pickup': {
const pickup_lot: string = perform_action_description['pickup_lot'];
return pickup_lot;
}
case 'delivery_sequential_lot_pickup':
case 'delivery_area_pickup': {
const pickup_zone: string = perform_action_description['pickup_zone'];
return pickup_zone;
}
default:
return 'n/a';
}
} catch (e) {
console.error(`Failed to parse pickup lot/zone from task request: ${(e as Error).message}`);
}

return 'n/a';
}

export function parseDestination(state: TaskState, request: TaskRequest): string {
if (request === undefined) {
return 'n/a';
}

// patrol
if (
request.category.toLowerCase() === 'patrol' &&
request.description['places'] !== undefined &&
request.description['places'].length > 0
) {
return request.description['places'].at(-1);
}

// custom deliveries
const supportedDeliveries = [
'delivery_pickup',
'delivery_sequential_lot_pickup',
'delivery_area_pickup',
];
if (
!request.description['category'] ||
!supportedDeliveries.includes((request.description['category'] as string).toLowerCase())
) {
return 'n/a';
}

// TODO(ac): use schemas
try {
const destination =
request.description['phases'][0]['activity']['description']['activities'][2]['description'];
return destination;
} catch (e) {
console.error(`Failed to parse destination from task request: ${(e as Error).message}`);
}

// automated tasks that can only be parsed with state
if (state.category && state.category === 'Charge Battery') {
try {
const charge_phase = state['phases'] ? state['phases']['1'] : undefined;
const charge_events = charge_phase ? charge_phase.events : undefined;
const charge_event_name = charge_events ? charge_events['1'].name : undefined;
if (charge_event_name === undefined) {
console.error('Unable to parse charging event name.');
return 'n/a';
}

const charging_place = charge_event_name?.split('[place:')[1].split(']')[0];
return charging_place;
} catch (e) {
console.error(`Failed to parse charging point from task state: ${(e as Error).message}`);
}
}

return 'n/a';
}

0 comments on commit f31dce3

Please sign in to comment.