Skip to content

Commit

Permalink
api-server: Support task labels in main (#950)
Browse files Browse the repository at this point in the history
Signed-off-by: Teo Koon Peng <[email protected]>
  • Loading branch information
koonpeng authored Jun 6, 2024
1 parent e3d2b1c commit 49a866a
Show file tree
Hide file tree
Showing 17 changed files with 12,155 additions and 9,939 deletions.
27 changes: 21 additions & 6 deletions packages/api-client/lib/openapi/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ export const ApiServerModelsRmfApiRobotStateStatus = {
} as const;

export type ApiServerModelsRmfApiRobotStateStatus =
typeof ApiServerModelsRmfApiRobotStateStatus[keyof typeof ApiServerModelsRmfApiRobotStateStatus];
(typeof ApiServerModelsRmfApiRobotStateStatus)[keyof typeof ApiServerModelsRmfApiRobotStateStatus];

/**
*
Expand All @@ -174,7 +174,7 @@ export const ApiServerModelsRmfApiTaskStateStatus = {
} as const;

export type ApiServerModelsRmfApiTaskStateStatus =
typeof ApiServerModelsRmfApiTaskStateStatus[keyof typeof ApiServerModelsRmfApiTaskStateStatus];
(typeof ApiServerModelsRmfApiTaskStateStatus)[keyof typeof ApiServerModelsRmfApiTaskStateStatus];

/**
*
Expand Down Expand Up @@ -829,7 +829,7 @@ export const HealthStatus = {
Dead: 'Dead',
} as const;

export type HealthStatus = typeof HealthStatus[keyof typeof HealthStatus];
export type HealthStatus = (typeof HealthStatus)[keyof typeof HealthStatus];

/**
*
Expand Down Expand Up @@ -1354,7 +1354,7 @@ export const Period = {
Minute: 'minute',
} as const;

export type Period = typeof Period[keyof typeof Period];
export type Period = (typeof Period)[keyof typeof Period];

/**
*
Expand Down Expand Up @@ -1895,7 +1895,7 @@ export const Status2 = {
CanceledInFlight: 'canceled_in_flight',
} as const;

export type Status2 = typeof Status2[keyof typeof Status2];
export type Status2 = (typeof Status2)[keyof typeof Status2];

/**
*
Expand Down Expand Up @@ -2507,7 +2507,7 @@ export const Tier = {
Error: 'error',
} as const;

export type Tier = typeof Tier[keyof typeof Tier];
export type Tier = (typeof Tier)[keyof typeof Tier];

/**
*
Expand Down Expand Up @@ -9372,6 +9372,7 @@ export const TasksApiAxiosParamCreator = function (configuration?: Configuration
* @param {string | null} [category] comma separated list of task categories
* @param {string | null} [assignedTo] comma separated list of assigned robot names
* @param {string | null} [status] comma separated list of statuses
* @param {string | null} [label] comma separated list of labels, each item must be in the form &lt;key&gt;&#x3D;&lt;value&gt;, multiple items will filter tasks with all the labels
* @param {string} [startTimeBetween] The period of starting time to fetch, in unix millis. This must be a comma separated string, \&#39;X,Y\&#39; to fetch between X millis and Y millis inclusive. Example: \&quot;1000,2000\&quot; - Fetches logs between unix millis 1000 and 2000.
* @param {string} [finishTimeBetween] The period of finishing time to fetch, in unix millis. This must be a comma separated string, \&#39;X,Y\&#39; to fetch between X millis and Y millis inclusive. Example: \&quot;1000,2000\&quot; - Fetches logs between unix millis 1000 and 2000. \&quot;-60000\&quot; - Fetches logs in the last minute.
* @param {number | null} [limit] defaults to 100
Expand All @@ -9386,6 +9387,7 @@ export const TasksApiAxiosParamCreator = function (configuration?: Configuration
category?: string | null,
assignedTo?: string | null,
status?: string | null,
label?: string | null,
startTimeBetween?: string,
finishTimeBetween?: string,
limit?: number | null,
Expand Down Expand Up @@ -9422,6 +9424,10 @@ export const TasksApiAxiosParamCreator = function (configuration?: Configuration
localVarQueryParameter['status'] = status;
}

if (label !== undefined) {
localVarQueryParameter['label'] = label;
}

if (startTimeBetween !== undefined) {
localVarQueryParameter['start_time_between'] = startTimeBetween;
}
Expand Down Expand Up @@ -10253,6 +10259,7 @@ export const TasksApiFp = function (configuration?: Configuration) {
* @param {string | null} [category] comma separated list of task categories
* @param {string | null} [assignedTo] comma separated list of assigned robot names
* @param {string | null} [status] comma separated list of statuses
* @param {string | null} [label] comma separated list of labels, each item must be in the form &lt;key&gt;&#x3D;&lt;value&gt;, multiple items will filter tasks with all the labels
* @param {string} [startTimeBetween] The period of starting time to fetch, in unix millis. This must be a comma separated string, \&#39;X,Y\&#39; to fetch between X millis and Y millis inclusive. Example: \&quot;1000,2000\&quot; - Fetches logs between unix millis 1000 and 2000.
* @param {string} [finishTimeBetween] The period of finishing time to fetch, in unix millis. This must be a comma separated string, \&#39;X,Y\&#39; to fetch between X millis and Y millis inclusive. Example: \&quot;1000,2000\&quot; - Fetches logs between unix millis 1000 and 2000. \&quot;-60000\&quot; - Fetches logs in the last minute.
* @param {number | null} [limit] defaults to 100
Expand All @@ -10267,6 +10274,7 @@ export const TasksApiFp = function (configuration?: Configuration) {
category?: string | null,
assignedTo?: string | null,
status?: string | null,
label?: string | null,
startTimeBetween?: string,
finishTimeBetween?: string,
limit?: number | null,
Expand All @@ -10280,6 +10288,7 @@ export const TasksApiFp = function (configuration?: Configuration) {
category,
assignedTo,
status,
label,
startTimeBetween,
finishTimeBetween,
limit,
Expand Down Expand Up @@ -10759,6 +10768,7 @@ export const TasksApiFactory = function (
* @param {string | null} [category] comma separated list of task categories
* @param {string | null} [assignedTo] comma separated list of assigned robot names
* @param {string | null} [status] comma separated list of statuses
* @param {string | null} [label] comma separated list of labels, each item must be in the form &lt;key&gt;&#x3D;&lt;value&gt;, multiple items will filter tasks with all the labels
* @param {string} [startTimeBetween] The period of starting time to fetch, in unix millis. This must be a comma separated string, \&#39;X,Y\&#39; to fetch between X millis and Y millis inclusive. Example: \&quot;1000,2000\&quot; - Fetches logs between unix millis 1000 and 2000.
* @param {string} [finishTimeBetween] The period of finishing time to fetch, in unix millis. This must be a comma separated string, \&#39;X,Y\&#39; to fetch between X millis and Y millis inclusive. Example: \&quot;1000,2000\&quot; - Fetches logs between unix millis 1000 and 2000. \&quot;-60000\&quot; - Fetches logs in the last minute.
* @param {number | null} [limit] defaults to 100
Expand All @@ -10773,6 +10783,7 @@ export const TasksApiFactory = function (
category?: string | null,
assignedTo?: string | null,
status?: string | null,
label?: string | null,
startTimeBetween?: string,
finishTimeBetween?: string,
limit?: number | null,
Expand All @@ -10787,6 +10798,7 @@ export const TasksApiFactory = function (
category,
assignedTo,
status,
label,
startTimeBetween,
finishTimeBetween,
limit,
Expand Down Expand Up @@ -11281,6 +11293,7 @@ export class TasksApi extends BaseAPI {
* @param {string | null} [category] comma separated list of task categories
* @param {string | null} [assignedTo] comma separated list of assigned robot names
* @param {string | null} [status] comma separated list of statuses
* @param {string | null} [label] comma separated list of labels, each item must be in the form &lt;key&gt;&#x3D;&lt;value&gt;, multiple items will filter tasks with all the labels
* @param {string} [startTimeBetween] The period of starting time to fetch, in unix millis. This must be a comma separated string, \&#39;X,Y\&#39; to fetch between X millis and Y millis inclusive. Example: \&quot;1000,2000\&quot; - Fetches logs between unix millis 1000 and 2000.
* @param {string} [finishTimeBetween] The period of finishing time to fetch, in unix millis. This must be a comma separated string, \&#39;X,Y\&#39; to fetch between X millis and Y millis inclusive. Example: \&quot;1000,2000\&quot; - Fetches logs between unix millis 1000 and 2000. \&quot;-60000\&quot; - Fetches logs in the last minute.
* @param {number | null} [limit] defaults to 100
Expand All @@ -11296,6 +11309,7 @@ export class TasksApi extends BaseAPI {
category?: string | null,
assignedTo?: string | null,
status?: string | null,
label?: string | null,
startTimeBetween?: string,
finishTimeBetween?: string,
limit?: number | null,
Expand All @@ -11310,6 +11324,7 @@ export class TasksApi extends BaseAPI {
category,
assignedTo,
status,
label,
startTimeBetween,
finishTimeBetween,
limit,
Expand Down
5 changes: 4 additions & 1 deletion packages/api-client/lib/openapi/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,10 @@ export class BaseAPI {
* @extends {Error}
*/
export class RequiredError extends Error {
constructor(public field: string, msg?: string) {
constructor(
public field: string,
msg?: string,
) {
super(msg);
this.name = 'RequiredError';
}
Expand Down
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: '0c773dc3f4b1a5df8bccb1b9da9d66fc3b075b28',
rmfServer: 'bd127fc270aa49f6f767f5d9de47ea11d46f9171',
openapiGenerator: '',
};
13 changes: 13 additions & 0 deletions packages/api-client/schema/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -788,6 +788,19 @@ export default {
},
description: 'comma separated list of statuses',
},
{
name: 'label',
in: 'query',
required: false,
schema: {
anyOf: [{ type: 'string' }, { type: 'null' }],
description:
'comma separated list of labels, each item must be in the form <key>=<value>, multiple items will filter tasks with all the labels',
title: 'Label',
},
description:
'comma separated list of labels, each item must be in the form <key>=<value>, multiple items will filter tasks with all the labels',
},
{
name: 'start_time_between',
in: 'query',
Expand Down
1 change: 1 addition & 0 deletions packages/api-server/api_server/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from .fleets import *
from .health import *
from .ingestors import *
from .labels import *
from .lifts import *
from .pagination import *
from .rmf_api.activity_discovery_request import ActivityDiscoveryRequest
Expand Down
25 changes: 25 additions & 0 deletions packages/api-server/api_server/models/labels.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from typing import Sequence

from pydantic import RootModel


class Labels(RootModel):
"""
Labels for a resource.
"""

root: dict[str, str]

@staticmethod
def _parse_label(s: str) -> tuple[str, str]:
sep = s.find("=")
if sep == -1:
return s, ""
return s[:sep], s[sep + 1 :]

@staticmethod
def from_strings(labels: Sequence[str]) -> "Labels":
return Labels(dict(Labels._parse_label(s) for s in labels))

def to_strings(self) -> list[str]:
return [f"{k}={v}" for k, v in self.root.items()]
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
TaskEventLogPhasesLog,
TaskFavorite,
TaskFavoritePydantic,
TaskLabel,
TaskRequest,
TaskState,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ class TaskState(Model):
status = CharField(255, null=True, index=True)
unix_millis_request_time = DatetimeField(null=True, index=True)
requester = CharField(255, null=True, index=True)
labels: ReverseRelation["TaskLabel"]


class TaskLabel(Model):
state = ForeignKeyField("models.TaskState", null=True, related_name="labels")
label_name = CharField(255, null=False, index=True)
label_value = CharField(255, null=True, index=True)


class TaskEventLog(Model):
Expand Down
38 changes: 30 additions & 8 deletions packages/api-server/api_server/query.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from typing import Dict, Optional

import tortoise.functions as tfuncs
from tortoise.expressions import Q
from tortoise.queryset import MODEL, QuerySet

from api_server.models.pagination import Pagination
Expand All @@ -8,25 +8,47 @@
def add_pagination(
query: QuerySet[MODEL],
pagination: Pagination,
field_mappings: Optional[Dict[str, str]] = None,
field_mappings: dict[str, str] | None = None,
group_by: str | None = None,
) -> QuerySet[MODEL]:
"""
Adds pagination and ordering to a query.
Adds pagination and ordering to a query. If the order field starts with `label=`, it is
assumed to be a label and label sorting will used. In this case, the model must have
a reverse relation named "labels" and the `group_by` param is required.
:param field_mapping: A dict mapping the order fields to the fields used to build the
query. e.g. a url of `?order_by=order_field` and a field mapping of `{"order_field": "db_field"}`
will order the query result according to `db_field`.
:param group_by: Required when sorting by labels, must be the foreign key column of the label table.
"""
field_mappings = field_mappings or {}
annotations = {}
query = query.limit(pagination.limit).offset(pagination.offset)
if pagination.order_by is not None:
order_fields = []
order_values = pagination.order_by.split(",")
for v in order_values:
# perform the mapping after stripping the order prefix
order_prefix = ""
order_field = v
if v[0] in ["-", "+"]:
stripped = v[1:]
order_fields.append(v[0] + field_mappings.get(stripped, stripped))
else:
order_fields.append(field_mappings.get(v, v))
order_prefix = v[0]
order_field = v[1:]
order_field = field_mappings.get(order_field, order_field)

# add annotations required for sorting by labels
if order_field.startswith("label="):
f = order_field[6:]
annotations[f"label_sort_{f}"] = tfuncs.Max(
"labels__label_value",
_filter=Q(labels__label_name=f),
)
order_field = f"label_sort_{f}"

order_fields.append(order_prefix + order_field)

query = query.annotate(**annotations)
if group_by is not None:
query = query.group_by(group_by)
query = query.order_by(*order_fields)
return query
Loading

0 comments on commit 49a866a

Please sign in to comment.