Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hammer/task queue table #844

Closed
wants to merge 17 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
180 changes: 175 additions & 5 deletions packages/api-client/lib/openapi/api.ts

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/api-client/lib/version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ import { version as rmfModelVer } from 'rmf-models';

export const version = {
rmfModels: rmfModelVer,
rmfServer: '9c6122b74319a51ceff1ca27a493e1cbaf35aa42',
rmfServer: '1970313b2544aa6fb1ee65c9356f2eface9b174b',
openapiGenerator: '6.2.1',
};
77 changes: 74 additions & 3 deletions packages/api-client/schema/index.ts

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
TaskEventLogPhasesLog,
TaskFavorite,
TaskFavoritePydantic,
TaskRequest,
TaskState,
)
from .user import *
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@
from .log import LogMixin


class TaskRequest(Model):
id_ = CharField(255, pk=True, source_field="id")
request = JSONField()


class TaskState(Model):
id_ = CharField(255, pk=True, source_field="id")
data = JSONField()
Expand Down
20 changes: 20 additions & 0 deletions packages/api-server/api_server/repositories/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@
Pagination,
Phases,
TaskEventLog,
TaskRequest,
TaskState,
User,
)
from api_server.models import tortoise_models as ttm
from api_server.models.rmf_api.log_entry import Tier
from api_server.models.rmf_api.task_state import Category, Id, Phase
from api_server.models.tortoise_models import TaskRequest as DbTaskRequest
from api_server.models.tortoise_models import TaskState as DbTaskState
from api_server.query import add_pagination
from api_server.rmf_io import task_events
Expand All @@ -30,6 +32,24 @@ class TaskRepository:
def __init__(self, user: User):
self.user = user

async def save_task_request(self, task_id: str, task_request: TaskRequest) -> None:
await DbTaskRequest.update_or_create(
{"request": task_request.json()}, id_=task_id
)

async def get_task_request(self, task_id: str) -> Optional[TaskRequest]:
result = await DbTaskRequest.get_or_none(id_=task_id)
if result is None:
return None
return TaskRequest(**result.request)

async def query_task_requests(self, task_ids: List[str]) -> List[DbTaskRequest]:
filters = {"id___in": task_ids}
try:
return await DbTaskRequest.filter(**filters)
except FieldError as e:
raise HTTPException(422, str(e)) from e

async def save_task_state(self, task_state: TaskState) -> None:
db_task_state = await DbTaskState.get_or_none(id_=task_state.booking.id)
if db_task_state is not None:
Expand Down
37 changes: 37 additions & 0 deletions packages/api-server/api_server/routes/tasks/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,42 @@
router = FastIORouter(tags=["Tasks"])


@router.get("/{task_id}/request", response_model=mdl.TaskRequest)
async def get_task_request(
task_repo: TaskRepository = Depends(task_repo_dep),
task_id: str = Path(..., description="task_id"),
):
result = await task_repo.get_task_request(task_id)
if result is None:
raise HTTPException(status_code=404)
return result


@router.get("/requests", response_model=List[Optional[mdl.TaskRequest]])
async def query_task_requests(
task_repo: TaskRepository = Depends(task_repo_dep),
task_ids: Optional[str] = Query(
None, description="comma separated list of task ids"
),
):
task_id_splits = []
if task_ids is not None:
task_id_splits = task_ids.split(",")
valid_task_requests = await task_repo.query_task_requests(task_id_splits)

valid_task_request_map = {}
for valid_req in valid_task_requests:
valid_task_request_map[valid_req.id_] = mdl.TaskRequest(**valid_req.request)

return_requests = []
for id_query in task_id_splits:
if id_query in valid_task_request_map:
return_requests.append(valid_task_request_map[id_query])
else:
return_requests.append(None)
return return_requests


@router.get("", response_model=List[mdl.TaskState])
async def query_task_states(
task_repo: TaskRepository = Depends(task_repo_dep),
Expand Down Expand Up @@ -149,6 +185,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)
return resp.__root__


Expand Down
13 changes: 13 additions & 0 deletions packages/api-server/api_server/routes/tasks/test_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,19 @@ def test_success(self):
self.assertEqual(200, resp.status_code, resp.content)
self.assertEqual(task_id, resp.json()["booking"]["id"])

def test_task_request_exist(self):
task_id = str(uuid4())
with patch.object(tasks_service(), "call") as mock:
mock.return_value = f'{{ "success": true, "state": {{ "booking": {{ "id": "{task_id}" }} }} }}'
resp = self.post_task_request()
self.assertEqual(200, resp.status_code, resp.content)

# check that the task request is in the database
resp = self.client.get(f"/tasks/{task_id}/request")
self.assertEqual(200, resp.status_code, resp.content)
self.assertEqual("test", resp.json()["category"])
self.assertEqual("description", resp.json()["description"])

def test_fail_with_multiple_errors(self):
# fails with multiple errors
with patch.object(tasks_service(), "call") as mock:
Expand Down
42 changes: 40 additions & 2 deletions packages/dashboard/src/components/tasks/tasks-app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
Tooltip,
useMediaQuery,
} from '@mui/material';
import { TaskState } from 'api-client';
import { TaskRequest, TaskState } from 'api-client';
import React from 'react';
import {
FilterFields,
Expand Down Expand Up @@ -91,6 +91,7 @@ export const TasksApp = React.memo(
const [tasksState, setTasksState] = React.useState<Tasks>({
isLoading: true,
data: [],
requests: {},
total: 0,
page: 1,
pageSize: 10,
Expand Down Expand Up @@ -182,10 +183,25 @@ export const TasksApp = React.memo(
const results = resp.data as TaskState[];
const newTasks = results.slice(0, GET_LIMIT);

const taskIds: string[] = newTasks.map((task) => task.booking.id);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can add a comment here explaining why we duplicated code and did not reuse the getAllTaskRequest function

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;
}

setTasksState((old) => ({
...old,
isLoading: false,
data: newTasks,
requests: taskRequestMap,
total:
results.length === GET_LIMIT
? tasksState.page * GET_LIMIT + 1
Expand Down Expand Up @@ -226,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
39 changes: 26 additions & 13 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,53 @@ 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',
// 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';
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ describe('Tasks table', () => {
const tasks: Tasks = {
isLoading: false,
data: [],
requests: {},
total: 0,
page: 1,
pageSize: 10,
Expand Down Expand Up @@ -40,9 +41,11 @@ describe('Tasks table', () => {
});

it('shows titles correctly', () => {
expect(screen.queryByText('ID')).toBeTruthy();
expect(screen.queryByText('Category')).toBeTruthy();
expect(screen.queryByText('Assignee')).toBeTruthy();
expect(screen.queryByText('Date')).toBeTruthy();
expect(screen.queryByText('Requester')).toBeTruthy();
expect(screen.queryByText('Pickup')).toBeTruthy();
expect(screen.queryByText('Destination')).toBeTruthy();
expect(screen.queryByText('Robot')).toBeTruthy();
expect(screen.queryByText('Start Time')).toBeTruthy();
expect(screen.queryByText('End Time')).toBeTruthy();
expect(screen.queryByText('State')).toBeTruthy();
Expand Down
Loading
Loading