diff --git a/apps/data-management/src/components/Browse/BrowseFilterTool.vue b/apps/data-management/src/components/Browse/BrowseFilterTool.vue index 8534363f..6082eb00 100644 --- a/apps/data-management/src/components/Browse/BrowseFilterTool.vue +++ b/apps/data-management/src/components/Browse/BrowseFilterTool.vue @@ -732,7 +732,7 @@ const onClearFilters = () => { onMounted(async () => { workspaces.value = await hs.workspaces.listAllItems({ - order_by: ['name'], + sortby: ['name'], }) workspacesLoaded.value = true void applyRouteState() diff --git a/apps/data-management/src/components/Datastream/DatastreamSelectorCard.vue b/apps/data-management/src/components/Datastream/DatastreamSelectorCard.vue index f4c1b289..5f4ed043 100644 --- a/apps/data-management/src/components/Datastream/DatastreamSelectorCard.vue +++ b/apps/data-management/src/components/Datastream/DatastreamSelectorCard.vue @@ -804,7 +804,7 @@ async function loadFallbackData() { ? Promise.resolve([]) : hs.monitoringSites.listAllItems({ workspace_id: [workspaceId], - order_by: ['name'], + sortby: ['name'], } as any), ]) fetchedDatastreams.value = datastreams as Datastream[] diff --git a/apps/data-management/src/components/Datastream/DatastreamTable.vue b/apps/data-management/src/components/Datastream/DatastreamTable.vue index dd4b4808..235617a9 100644 --- a/apps/data-management/src/components/Datastream/DatastreamTable.vue +++ b/apps/data-management/src/components/Datastream/DatastreamTable.vue @@ -1629,15 +1629,15 @@ const loadLinkedTasks = async () => { const [etlTasks, dataProductTasks, monitoringTasks] = await Promise.all([ hs.tasks.listAllItems({ monitoring_site_id: [site.id], - order_by: ['name'], + sortby: ['name'], }), hs.dataProductTasks.listAllItems({ monitoring_site_id: [site.id], - order_by: ['name'], + sortby: ['name'], }), hs.monitoringTasks.listAllItems({ monitoring_site_id: [site.id], - order_by: ['name'], + sortby: ['name'], }), ]) if (requestId !== linkedTasksRequestId) return diff --git a/apps/data-management/src/components/Orchestration/data-products/AggregationForm.vue b/apps/data-management/src/components/Orchestration/data-products/AggregationForm.vue index 7091a9f3..49c66ad8 100644 --- a/apps/data-management/src/components/Orchestration/data-products/AggregationForm.vue +++ b/apps/data-management/src/components/Orchestration/data-products/AggregationForm.vue @@ -367,7 +367,7 @@ async function loadDatastreams() { try { const items = await hs.datastreams.listAllItems({ workspace_id: [workspaceId], - order_by: ['name'], + sortby: ['name'], expand_related: true, } as any) datastreams.value = items as Datastream[] diff --git a/apps/data-management/src/components/Orchestration/data-products/DerivationForm.vue b/apps/data-management/src/components/Orchestration/data-products/DerivationForm.vue index 98d750f7..84ce8f4f 100644 --- a/apps/data-management/src/components/Orchestration/data-products/DerivationForm.vue +++ b/apps/data-management/src/components/Orchestration/data-products/DerivationForm.vue @@ -499,7 +499,7 @@ async function loadDatastreams() { try { const items = await hs.datastreams.listAllItems({ workspace_id: [workspaceId], - order_by: ['name'], + sortby: ['name'], expand_related: true, } as any) datastreams.value = items as Datastream[] diff --git a/apps/data-management/src/components/Orchestration/data-products/RatingCurveForm.vue b/apps/data-management/src/components/Orchestration/data-products/RatingCurveForm.vue index c8e9dcc5..8c865f41 100644 --- a/apps/data-management/src/components/Orchestration/data-products/RatingCurveForm.vue +++ b/apps/data-management/src/components/Orchestration/data-products/RatingCurveForm.vue @@ -333,7 +333,7 @@ async function loadOptions() { try { const datastreamItems = await hs.datastreams.listAllItems({ workspace_id: [workspaceId], - order_by: ['name'], + sortby: ['name'], expand_related: true, } as any) datastreams.value = datastreamItems as Datastream[] @@ -391,7 +391,7 @@ async function loadRatingCurves(monitoringSiteId: string) { const items = await hs.ratingCurves.listItemsForMonitoringSite( monitoringSiteId, { - order_by: ['name'], + sortby: ['name'], } ) ratingCurves.value = [...items].sort((a, b) => a.name.localeCompare(b.name)) diff --git a/apps/data-management/src/components/Orchestration/data-products/RatingCurveTable.vue b/apps/data-management/src/components/Orchestration/data-products/RatingCurveTable.vue index 836ccef8..8890d744 100644 --- a/apps/data-management/src/components/Orchestration/data-products/RatingCurveTable.vue +++ b/apps/data-management/src/components/Orchestration/data-products/RatingCurveTable.vue @@ -893,7 +893,7 @@ async function refreshRatingCurves() { backendLoading.value = true try { const items = await hs.ratingCurves.listItemsForMonitoringSite(props.monitoringSiteId, { - order_by: ['name'], + sortby: ['name'], }) backendRatingCurves.value = items.sort((a, b) => a.name.localeCompare(b.name) diff --git a/apps/data-management/src/components/Orchestration/monitoring/QualityManagementForm.vue b/apps/data-management/src/components/Orchestration/monitoring/QualityManagementForm.vue index f6ccced1..0f06f8aa 100644 --- a/apps/data-management/src/components/Orchestration/monitoring/QualityManagementForm.vue +++ b/apps/data-management/src/components/Orchestration/monitoring/QualityManagementForm.vue @@ -465,7 +465,7 @@ async function loadDatastreams() { try { const items = await hs.datastreams.listAllItems({ workspace_id: [selectedWorkspace.value.id], - order_by: ['name'], + sortby: ['name'], expand_related: true, } as any) datastreams.value = items as Datastream[] @@ -497,7 +497,7 @@ async function loadExistingTask() { hs.monitoringTasks.get(props.editTaskId), hs.monitoringRules.list({ task_id: props.editTaskId, - order_by: ['datastreamId', 'ruleType'], + sortby: ['datastreamId', 'ruleType'], } as any), ]) diff --git a/apps/data-management/src/components/Workspace/AccessControl/ManageCollaborators.vue b/apps/data-management/src/components/Workspace/AccessControl/ManageCollaborators.vue index 5150e94c..9f262924 100644 --- a/apps/data-management/src/components/Workspace/AccessControl/ManageCollaborators.vue +++ b/apps/data-management/src/components/Workspace/AccessControl/ManageCollaborators.vue @@ -1007,7 +1007,7 @@ async function loadCollaboratorData() { const [cRes, rolesResponse] = await Promise.all([ hs.workspaces.getCollaborators(props.workspace.id), hs.workspaces.getRoles({ - order_by: ['name'], + sortby: ['name'], }), ]) diff --git a/apps/data-management/src/components/Workspace/AccessControl/ManageServiceAccounts.vue b/apps/data-management/src/components/Workspace/AccessControl/ManageServiceAccounts.vue index d21460e6..78d392eb 100644 --- a/apps/data-management/src/components/Workspace/AccessControl/ManageServiceAccounts.vue +++ b/apps/data-management/src/components/Workspace/AccessControl/ManageServiceAccounts.vue @@ -899,7 +899,7 @@ async function loadRoles() { try { const res = await hs.workspaces.getRoles({ workspace_id: [workspaceId.value, 'null'], - order_by: ['name'], + sortby: ['name'], }) if (!res.ok) { rolesLoadError.value = 'Unable to load service account roles.' diff --git a/apps/data-management/src/composables/__tests__/useOrchestrationData.spec.ts b/apps/data-management/src/composables/__tests__/useOrchestrationData.spec.ts index 3782f15e..5be21263 100644 --- a/apps/data-management/src/composables/__tests__/useOrchestrationData.spec.ts +++ b/apps/data-management/src/composables/__tests__/useOrchestrationData.spec.ts @@ -56,7 +56,7 @@ describe('useOrchestrationData', () => { expect(data.monitoringSites.value.map((item) => item.id)).toEqual(['monitoringSite-1']) expect(dataConnectionsListMock).toHaveBeenCalledWith({ workspace_id: 'workspace-1', - order_by: 'name', + sortby: 'name', }) expect(monitoringSitesTaskSummariesMock).toHaveBeenCalledWith({ workspace_id: ['workspace-1'], @@ -99,7 +99,7 @@ describe('useOrchestrationData', () => { expect(tasksListMock).toHaveBeenCalledWith({ workspace_id: ['workspace-1'], data_connection_id: ['dc-1'], - order_by: ['name'], + sortby: ['name'], expand_related: true, }) @@ -108,7 +108,7 @@ describe('useOrchestrationData', () => { expect(dataProductTasksListMock).toHaveBeenCalledWith({ workspace_id: ['workspace-1'], monitoring_site_id: ['monitoringSite-1'], - order_by: ['name'], + sortby: ['name'], }) await data.fetchTasksForGroup('quality', 'monitoringSite-1', 'workspace-1') @@ -116,7 +116,7 @@ describe('useOrchestrationData', () => { expect(monitoringTasksListMock).toHaveBeenCalledWith({ workspace_id: ['workspace-1'], monitoring_site_id: ['monitoringSite-1'], - order_by: ['name'], + sortby: ['name'], }) }) diff --git a/apps/data-management/src/composables/orchestration/useOrchestrationData.ts b/apps/data-management/src/composables/orchestration/useOrchestrationData.ts index 5c7fd436..d8c5252b 100644 --- a/apps/data-management/src/composables/orchestration/useOrchestrationData.ts +++ b/apps/data-management/src/composables/orchestration/useOrchestrationData.ts @@ -68,7 +68,7 @@ export function useOrchestrationData() { const [dcItems, taskSummaryResponse] = await Promise.all([ hs.dataConnections.listAllItems({ workspace_id: requestedWorkspaceId, - order_by: 'name', + sortby: 'name', } as any), hs.monitoringSites.listTaskSummaries({ workspace_id: [requestedWorkspaceId], @@ -99,7 +99,7 @@ export function useOrchestrationData() { } dataConnections.value = await hs.dataConnections.listAllItems({ workspace_id: requestedWorkspaceId, - order_by: 'name', + sortby: 'name', } as any) } @@ -135,7 +135,7 @@ export function useOrchestrationData() { const items = await hs.tasks.listAllItems({ workspace_id: [requestedWorkspaceId], data_connection_id: [groupId], - order_by: ['name'], + sortby: ['name'], expand_related: true, } as any) if (requestId !== taskRequestId) return @@ -144,7 +144,7 @@ export function useOrchestrationData() { const items = await hs.dataProductTasks.listAllItems({ workspace_id: [requestedWorkspaceId], monitoring_site_id: [groupId], - order_by: ['name'], + sortby: ['name'], } as any) if (requestId !== taskRequestId) return dataProductTasks.value = items as any @@ -152,7 +152,7 @@ export function useOrchestrationData() { const items = await hs.monitoringTasks.listAllItems({ workspace_id: [requestedWorkspaceId], monitoring_site_id: [groupId], - order_by: ['name'], + sortby: ['name'], } as any) if (requestId !== taskRequestId) return monitoringTasks.value = items as any diff --git a/apps/data-management/src/composables/orchestration/useSimpleTaskDetails.ts b/apps/data-management/src/composables/orchestration/useSimpleTaskDetails.ts index bf46f389..c7e9550d 100644 --- a/apps/data-management/src/composables/orchestration/useSimpleTaskDetails.ts +++ b/apps/data-management/src/composables/orchestration/useSimpleTaskDetails.ts @@ -254,7 +254,7 @@ export function useSimpleTaskDetails( loadingRuns.value = true try { const response = await service.getTaskRuns(task.value.id, { - order_by: ['-startedAt'], + sortby: ['-startedAt'], offset: 0, limit: 50, }) diff --git a/apps/data-management/src/composables/useMetadata.ts b/apps/data-management/src/composables/useMetadata.ts index 4fe790d5..438db01e 100644 --- a/apps/data-management/src/composables/useMetadata.ts +++ b/apps/data-management/src/composables/useMetadata.ts @@ -54,18 +54,18 @@ export function useMetadata(localWorkspace?: Ref) { methodsResponse, resultQualifiersResponse, ] = await Promise.all([ - hs.units.listAllItems({ order_by: ['name'], ...workspaceFilter }), + hs.units.listAllItems({ sortby: ['name'], ...workspaceFilter }), hs.observedProperties.listAllItems({ - order_by: ['name'], + sortby: ['name'], ...workspaceFilter, }), hs.processingLevels.listAllItems({ - order_by: ['code'], + sortby: ['code'], ...workspaceFilter, }), - hs.methods.listAllItems({ order_by: ['name'], ...workspaceFilter }), + hs.methods.listAllItems({ sortby: ['name'], ...workspaceFilter }), hs.resultQualifiers.listAllItems({ - order_by: ['code'], + sortby: ['code'], ...workspaceFilter, }), ]) diff --git a/apps/data-management/src/store/orchestration.ts b/apps/data-management/src/store/orchestration.ts index 66b73f30..a7192853 100644 --- a/apps/data-management/src/store/orchestration.ts +++ b/apps/data-management/src/store/orchestration.ts @@ -156,7 +156,7 @@ export const useOrchestrationStore = defineStore('orchestration', () => { const requestId = ++workspaceMonitoringSitesRequestId const list = await hs.monitoringSites.listAllItems({ workspace_id: [requestedWorkspaceId], - order_by: ['name'], + sortby: ['name'], } as any) if (requestId !== workspaceMonitoringSitesRequestId) { return workspaceMonitoringSites.value diff --git a/apps/data-management/src/store/ratingCurves.ts b/apps/data-management/src/store/ratingCurves.ts index abaac54d..eda55bf5 100644 --- a/apps/data-management/src/store/ratingCurves.ts +++ b/apps/data-management/src/store/ratingCurves.ts @@ -65,7 +65,7 @@ export const useRatingCurveStore = defineStore('ratingCurves', () => { loading.value = true try { const items = await hs.ratingCurves.listItemsForMonitoringSite(monitoringSiteId, { - order_by: ['name'], + sortby: ['name'], }) existingRatingCurves.value = [...items].sort((a, b) => a.name.localeCompare(b.name) diff --git a/apps/data-management/src/utils/__tests__/observationsUtils.spec.ts b/apps/data-management/src/utils/__tests__/observationsUtils.spec.ts index 31c4cbe9..839bfa81 100644 --- a/apps/data-management/src/utils/__tests__/observationsUtils.spec.ts +++ b/apps/data-management/src/utils/__tests__/observationsUtils.spec.ts @@ -181,7 +181,7 @@ describe('fetchRecentObservationsPage', () => { ]) expect(getObservationsMock).toHaveBeenCalledWith('123', { offset: 0, - order_by: ['-phenomenonTime'], + sortby: ['-phenomenonTime'], limit: 200, format: 'column', }) diff --git a/apps/data-management/src/utils/observationsUtils.ts b/apps/data-management/src/utils/observationsUtils.ts index 46642faa..557d2971 100644 --- a/apps/data-management/src/utils/observationsUtils.ts +++ b/apps/data-management/src/utils/observationsUtils.ts @@ -37,7 +37,7 @@ export const fetchObservations = async ( if (!phenomenonBeginTime || !phenomenonEndTime) return [] const options: any = { - order_by: ['phenomenonTime'], + sortby: ['phenomenonTime'], limit: 50_000, format: 'column', phenomenon_time_min: startTime ?? phenomenonBeginTime, @@ -57,7 +57,7 @@ export const fetchRecentObservationsPage = async ( ) => { const res = await hs.datastreams.getObservations(datastream.id, { offset: 0, - order_by: ['-phenomenonTime'], + sortby: ['-phenomenonTime'], limit: pageSize, format: 'column', }) diff --git a/apps/docs/docs/developing-and-contributing/how-to/using-the-typescript-client.md b/apps/docs/docs/developing-and-contributing/how-to/using-the-typescript-client.md index 3f7ceeb4..f7398e87 100644 --- a/apps/docs/docs/developing-and-contributing/how-to/using-the-typescript-client.md +++ b/apps/docs/docs/developing-and-contributing/how-to/using-the-typescript-client.md @@ -64,7 +64,7 @@ Most endpoints support query params such as: - `page` - `page_size` -- `order_by` +- `sortby` - `expand_related` - Resource-specific filters @@ -96,11 +96,11 @@ const allWorkspaces = await hs.workspaces.listAllItems({ ```ts const ordered = await hs.workspaces.list({ - order_by: ["name"], + sortby: ["name"], }); const multiOrdered = await hs.workspaces.list({ - order_by: ["-name", "isPrivate"], + sortby: ["-name", "isPrivate"], }); ``` @@ -567,7 +567,7 @@ const observations = await hs.datastreams.getObservations( "00000000-0000-0000-0000-000000000000", { format: "row", - order_by: ["phenomenonTime"], + sortby: ["phenomenonTime"], phenomenon_time_min: "2025-01-01T00:00:00Z", phenomenon_time_max: "2025-12-31T23:59:59Z", page_size: 1000, @@ -758,7 +758,7 @@ const taskId = "00000000-0000-0000-0000-000000000000"; const runs = await hs.tasks.getTaskRuns(taskId, { page_size: 50, - order_by: ["-startedAt"], + sortby: ["-startedAt"], }); const run = await hs.tasks.getTaskRun( @@ -906,7 +906,7 @@ const sourceRes = await hs.datastreams.getObservations( history.sourceDatastream.id, { format: "record", - order_by: ["phenomenonTime"], + sortby: ["phenomenonTime"], phenomenon_time_min: rangeStart, phenomenon_time_max: rangeEnd, } diff --git a/apps/docs/docs/developing-and-contributing/tutorials/building-your-first-app.md b/apps/docs/docs/developing-and-contributing/tutorials/building-your-first-app.md index 8a8177eb..5e1e32bb 100644 --- a/apps/docs/docs/developing-and-contributing/tutorials/building-your-first-app.md +++ b/apps/docs/docs/developing-and-contributing/tutorials/building-your-first-app.md @@ -87,7 +87,7 @@ const row = ({ name, latitude, longitude }: MonitoringSite) => { const res = await hs.monitoringSites.list({ page: 1, page_size: 50, - order_by: ["name"], + sortby: ["name"], is_private: false, }); diff --git a/apps/docs/docs/user-guides/how-to/using-the-python-client.md b/apps/docs/docs/user-guides/how-to/using-the-python-client.md index 51e6b154..5f578a63 100644 --- a/apps/docs/docs/user-guides/how-to/using-the-python-client.md +++ b/apps/docs/docs/user-guides/how-to/using-the-python-client.md @@ -60,7 +60,7 @@ All `list()` methods accept these parameters in addition to any entity-specific |---|---|---|---| | `page` | `int` | `1` | Page number to fetch | | `page_size` | `int` | `100` | Results per page | -| `order_by` | `List[str]` | `[]` | Fields to sort by; prefix with `-` for descending | +| `sortby` | `List[str]` | `[]` | Fields to sort by; prefix with `-` for descending | | `fetch_all` | `bool` | `False` | If `True`, fetches and merges all pages automatically | ### Model methods @@ -423,7 +423,7 @@ datastream.get_observations( result_qualifier_code=None, page=1, page_size=100000, - order_by=None, + sortby=None, fetch_all=False, ) -> ObservationCollection ``` @@ -562,7 +562,7 @@ Mappings are a list of dicts with `source_identifier` and `target_datastream_id` | Method | Returns | Description | |---|---|---| | `trigger()` | `TaskRun` | Dispatch an immediate run | -| `list_runs(status=None, started_at_min=None, started_at_max=None, finished_at_min=None, finished_at_max=None, page=1, page_size=100, order_by=None)` | `List[TaskRun]` | Fetch run history | +| `list_runs(status=None, started_at_min=None, started_at_max=None, finished_at_min=None, finished_at_max=None, page=1, page_size=100, sortby=None)` | `List[TaskRun]` | Fetch run history | | `get_run(run_id)` | `TaskRun` | Fetch a single run by ID | --- diff --git a/apps/qc-app/docs/API_REFERENCE.md b/apps/qc-app/docs/API_REFERENCE.md index 4dbd1150..7dc2c8cc 100644 --- a/apps/qc-app/docs/API_REFERENCE.md +++ b/apps/qc-app/docs/API_REFERENCE.md @@ -56,7 +56,7 @@ qc-utils service layer lives under `packages/qc-utils/src/services/`. ### Observation read Paged GET against `/data/datastreams/{id}/observations` with -`format=column`, `order_by=phenomenonTime`, `page_size=50000`, and +`format=column`, `sortby=phenomenonTime`, `page_size=50000`, and `phenomenon_time_min` / `phenomenon_time_max` ISO-8601 bounds. The app paginates client-side and caches the merged window in `useObservationStore`. diff --git a/apps/qc-app/src/utils/observations.ts b/apps/qc-app/src/utils/observations.ts index 3f734e96..ffab7965 100644 --- a/apps/qc-app/src/utils/observations.ts +++ b/apps/qc-app/src/utils/observations.ts @@ -18,7 +18,7 @@ export const fetchObservationsSync = async ( limit: 50_000, phenomenon_time_min: startTime?.toISOString() ?? phenomenonBeginTime, phenomenon_time_max: endTime?.toISOString() ?? phenomenonEndTime, - order_by: ['phenomenonTime'], + sortby: ['phenomenonTime'], format: 'column', }) diff --git a/django/contracts/openapi/data.openapi.json b/django/contracts/openapi/data.openapi.json index f8f6f2c6..38c76331 100644 --- a/django/contracts/openapi/data.openapi.json +++ b/django/contracts/openapi/data.openapi.json @@ -260,6 +260,28 @@ }, "title": "Role Id", "type": "array" + }, + "sortby": { + "anyOf": [ + { + "items": { + "enum": [ + "id", + "roleId", + "-id", + "-roleId" + ], + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": [], + "description": "Select one or more fields to sort the response by.", + "title": "Sortby" } }, "title": "CollaboratorQueryParameters", @@ -621,40 +643,6 @@ "description": "Number of items to skip.", "title": "Offset" }, - "order_by": { - "anyOf": [ - { - "items": { - "enum": [ - "id", - "name", - "timestampKey", - "timestampFormat", - "timezoneType", - "timezone", - "workspaceId", - "workspaceName", - "-id", - "-name", - "-timestampKey", - "-timestampFormat", - "-timezoneType", - "-timezone", - "-workspaceId", - "-workspaceName" - ], - "type": "string" - }, - "type": "array" - }, - { - "type": "null" - } - ], - "default": [], - "description": "Select one or more fields to order the response by.", - "title": "Order By" - }, "payload_type": { "default": [], "description": "Filter data connections by payload type.", @@ -690,6 +678,52 @@ "title": "Properties", "type": "array" }, + "q": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND.", + "title": "Q" + }, + "sortby": { + "anyOf": [ + { + "items": { + "enum": [ + "id", + "name", + "timestampKey", + "timestampFormat", + "timezoneType", + "timezone", + "workspaceId", + "workspaceName", + "-id", + "-name", + "-timestampKey", + "-timestampFormat", + "-timezoneType", + "-timezone", + "-workspaceId", + "-workspaceName" + ], + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": [], + "description": "Select one or more fields to sort the response by.", + "title": "Sortby" + }, "workspace_id": { "default": [], "description": "Filter data connections by workspace ID.", @@ -1204,42 +1238,6 @@ "description": "Number of items to skip.", "title": "Offset" }, - "order_by": { - "anyOf": [ - { - "items": { - "enum": [ - "id", - "name", - "monitoringSiteId", - "monitoringSiteName", - "workspaceId", - "workspaceName", - "latestRunStatus", - "latestRunStartedAt", - "latestRunFinishedAt", - "-id", - "-name", - "-monitoringSiteId", - "-monitoringSiteName", - "-workspaceId", - "-workspaceName", - "-latestRunStatus", - "-latestRunStartedAt", - "-latestRunFinishedAt" - ], - "type": "string" - }, - "type": "array" - }, - { - "type": "null" - } - ], - "default": [], - "description": "Select one or more fields to order the response by.", - "title": "Order By" - }, "output_datastream_id": { "default": [], "description": "Filter data product tasks by output datastream ID.", @@ -1270,6 +1268,18 @@ "title": "Properties", "type": "array" }, + "q": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND.", + "title": "Q" + }, "rating_curve_id": { "default": [], "description": "Filter data product tasks by rating curve ID.", @@ -1280,6 +1290,42 @@ "title": "Rating Curve Id", "type": "array" }, + "sortby": { + "anyOf": [ + { + "items": { + "enum": [ + "id", + "name", + "monitoringSiteId", + "monitoringSiteName", + "workspaceId", + "workspaceName", + "latestRunStatus", + "latestRunStartedAt", + "latestRunFinishedAt", + "-id", + "-name", + "-monitoringSiteId", + "-monitoringSiteName", + "-workspaceId", + "-workspaceName", + "-latestRunStatus", + "-latestRunStartedAt", + "-latestRunFinishedAt" + ], + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": [], + "description": "Select one or more fields to sort the response by.", + "title": "Sortby" + }, "transformation_type": { "default": [], "description": "Filter data product tasks by transformation type.", @@ -1778,28 +1824,6 @@ "description": "Number of items to skip.", "title": "Offset" }, - "order_by": { - "anyOf": [ - { - "items": { - "enum": [ - "id", - "outputDatastreamId", - "-id", - "-outputDatastreamId" - ], - "type": "string" - }, - "type": "array" - }, - { - "type": "null" - } - ], - "default": [], - "description": "Select one or more fields to order the response by.", - "title": "Order By" - }, "output_datastream_id": { "default": [], "description": "Filter transformations by output datastream ID.", @@ -1836,6 +1860,30 @@ "title": "Properties", "type": "array" }, + "sortby": { + "anyOf": [ + { + "items": { + "enum": [ + "id", + "outputDatastreamId", + "transformationType", + "-id", + "-outputDatastreamId", + "-transformationType" + ], + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": [], + "description": "Select one or more fields to sort the response by.", + "title": "Sortby" + }, "task_id": { "default": [], "description": "Filter transformations by data product task ID.", @@ -2644,46 +2692,6 @@ "description": "Number of items to skip.", "title": "Offset" }, - "order_by": { - "anyOf": [ - { - "items": { - "enum": [ - "name", - "observationType", - "sampledMedium", - "status", - "resultType", - "isPrivate", - "valueCount", - "phenomenonBeginTime", - "phenomenonEndTime", - "resultBeginTime", - "resultEndTime", - "-name", - "-observationType", - "-sampledMedium", - "-status", - "-resultType", - "-isPrivate", - "-valueCount", - "-phenomenonBeginTime", - "-phenomenonEndTime", - "-resultBeginTime", - "-resultEndTime" - ], - "type": "string" - }, - "type": "array" - }, - { - "type": "null" - } - ], - "default": [], - "description": "Select one or more fields to order the response by.", - "title": "Order By" - }, "phenomenon_begin_time_max": { "anyOf": [ { @@ -2798,6 +2806,18 @@ "title": "Properties", "type": "array" }, + "q": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND.", + "title": "Q" + }, "result_begin_time_max": { "anyOf": [ { @@ -2890,17 +2910,61 @@ "title": "Sampled Medium", "type": "array" }, - "status": { - "default": [], - "description": "Filter monitoring_sites by status.", - "items": { - "type": "string" - }, - "title": "Status", - "type": "array" - }, - "tag": { - "default": [], + "sortby": { + "anyOf": [ + { + "items": { + "enum": [ + "name", + "observationType", + "sampledMedium", + "status", + "resultType", + "isPrivate", + "isVisible", + "aggregationStatistic", + "valueCount", + "phenomenonBeginTime", + "phenomenonEndTime", + "resultBeginTime", + "resultEndTime", + "-name", + "-observationType", + "-sampledMedium", + "-status", + "-resultType", + "-isPrivate", + "-isVisible", + "-aggregationStatistic", + "-valueCount", + "-phenomenonBeginTime", + "-phenomenonEndTime", + "-resultBeginTime", + "-resultEndTime" + ], + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": [], + "description": "Select one or more fields to sort the response by.", + "title": "Sortby" + }, + "status": { + "default": [], + "description": "Filter monitoring_sites by status.", + "items": { + "type": "string" + }, + "title": "Status", + "type": "array" + }, + "tag": { + "default": [], "description": "Filter datastreams by tag. Format tag filters as {key}:{value}", "items": { "type": "string" @@ -3384,7 +3448,22 @@ "description": "Number of items to skip.", "title": "Offset" }, - "order_by": { + "properties": { + "description": "Comma-separated list of properties to include in the response. All properties are returned if omitted.", + "explode": false, + "items": { + "enum": [ + "id", + "sourceIdentifier", + "targetDatastreamId" + ], + "type": "string" + }, + "style": "form", + "title": "Properties", + "type": "array" + }, + "sortby": { "anyOf": [ { "items": { @@ -3405,23 +3484,8 @@ } ], "default": [], - "description": "Select one or more fields to order the response by.", - "title": "Order By" - }, - "properties": { - "description": "Comma-separated list of properties to include in the response. All properties are returned if omitted.", - "explode": false, - "items": { - "enum": [ - "id", - "sourceIdentifier", - "targetDatastreamId" - ], - "type": "string" - }, - "style": "form", - "title": "Properties", - "type": "array" + "description": "Select one or more fields to sort the response by.", + "title": "Sortby" }, "source_identifier": { "default": [], @@ -3760,7 +3824,40 @@ "description": "Number of items to skip.", "title": "Offset" }, - "order_by": { + "properties": { + "description": "Comma-separated list of properties to include in the response. All properties are returned if omitted.", + "explode": false, + "items": { + "enum": [ + "id", + "workspaceId", + "dataConnectionId", + "schedule", + "latestRun", + "mappingCount", + "name", + "description", + "taskVariables" + ], + "type": "string" + }, + "style": "form", + "title": "Properties", + "type": "array" + }, + "q": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND.", + "title": "Q" + }, + "sortby": { "anyOf": [ { "items": { @@ -3793,29 +3890,8 @@ } ], "default": [], - "description": "Select one or more fields to order the response by.", - "title": "Order By" - }, - "properties": { - "description": "Comma-separated list of properties to include in the response. All properties are returned if omitted.", - "explode": false, - "items": { - "enum": [ - "id", - "workspaceId", - "dataConnectionId", - "schedule", - "latestRun", - "mappingCount", - "name", - "description", - "taskVariables" - ], - "type": "string" - }, - "style": "form", - "title": "Properties", - "type": "array" + "description": "Select one or more fields to sort the response by.", + "title": "Sortby" }, "workspace_id": { "default": [], @@ -5012,34 +5088,6 @@ "description": "Number of items to skip.", "title": "Offset" }, - "order_by": { - "anyOf": [ - { - "items": { - "enum": [ - "name", - "code", - "type", - "sensorModel", - "sensorModelManufacturer", - "-name", - "-code", - "-type", - "-sensorModel", - "-sensorModelManufacturer" - ], - "type": "string" - }, - "type": "array" - }, - { - "type": "null" - } - ], - "default": [], - "description": "Select one or more fields to order the response by.", - "title": "Order By" - }, "properties": { "description": "Comma-separated list of properties to include in the response. All properties are returned if omitted.", "explode": false, @@ -5062,6 +5110,18 @@ "title": "Properties", "type": "array" }, + "q": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND.", + "title": "Q" + }, "sensor_model": { "default": [], "description": "Filter methods by sensor model", @@ -5080,6 +5140,38 @@ "title": "Sensor Model Manufacturer", "type": "array" }, + "sortby": { + "anyOf": [ + { + "items": { + "enum": [ + "name", + "code", + "type", + "sensorModel", + "sensorModelManufacturer", + "definition", + "sensorModelDefinition", + "-name", + "-code", + "-type", + "-sensorModel", + "-sensorModelManufacturer", + "-definition", + "-sensorModelDefinition" + ], + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": [], + "description": "Select one or more fields to sort the response by.", + "title": "Sortby" + }, "type": { "default": [], "description": "Filter methods by type", @@ -5455,30 +5547,6 @@ "description": "Number of items to skip.", "title": "Offset" }, - "order_by": { - "anyOf": [ - { - "items": { - "enum": [ - "id", - "ruleType", - "datastreamId", - "-id", - "-ruleType", - "-datastreamId" - ], - "type": "string" - }, - "type": "array" - }, - { - "type": "null" - } - ], - "default": [], - "description": "Select one or more fields to order the response by.", - "title": "Order By" - }, "properties": { "description": "Comma-separated list of properties to include in the response. All properties are returned if omitted.", "explode": false, @@ -5508,6 +5576,32 @@ "title": "Rule Type", "type": "array" }, + "sortby": { + "anyOf": [ + { + "items": { + "enum": [ + "id", + "ruleType", + "datastreamId", + "lastCheckedAt", + "-id", + "-ruleType", + "-datastreamId", + "-lastCheckedAt" + ], + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": [], + "description": "Select one or more fields to sort the response by.", + "title": "Sortby" + }, "task_id": { "default": [], "description": "Filter rules by monitoring task ID.", @@ -6252,7 +6346,48 @@ "description": "Number of items to skip.", "title": "Offset" }, - "order_by": { + "properties": { + "description": "Comma-separated list of properties to include in the response. All properties are returned if omitted.", + "explode": false, + "items": { + "enum": [ + "id", + "workspaceId", + "name", + "description", + "code", + "type", + "latitude", + "longitude", + "elevation_m", + "elevationDatum", + "adminArea1", + "adminArea2", + "country", + "dataDisclaimer", + "isPrivate", + "tags", + "linkedResources" + ], + "type": "string" + }, + "style": "form", + "title": "Properties", + "type": "array" + }, + "q": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND.", + "title": "Q" + }, + "sortby": { "anyOf": [ { "items": { @@ -6289,37 +6424,8 @@ } ], "default": [], - "description": "Select one or more fields to order the response by.", - "title": "Order By" - }, - "properties": { - "description": "Comma-separated list of properties to include in the response. All properties are returned if omitted.", - "explode": false, - "items": { - "enum": [ - "id", - "workspaceId", - "name", - "description", - "code", - "type", - "latitude", - "longitude", - "elevation_m", - "elevationDatum", - "adminArea1", - "adminArea2", - "country", - "dataDisclaimer", - "isPrivate", - "tags", - "linkedResources" - ], - "type": "string" - }, - "style": "form", - "title": "Properties", - "type": "array" + "description": "Select one or more fields to sort the response by.", + "title": "Sortby" }, "tag": { "default": [], @@ -6785,7 +6891,49 @@ "description": "Number of items to skip.", "title": "Offset" }, - "order_by": { + "properties": { + "description": "Comma-separated list of properties to include in the response. All properties are returned if omitted.", + "explode": false, + "items": { + "enum": [ + "id", + "workspaceId", + "monitoringSiteId", + "schedule", + "latestRun", + "ruleTypeCounts", + "recipients", + "name", + "description" + ], + "type": "string" + }, + "style": "form", + "title": "Properties", + "type": "array" + }, + "q": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND.", + "title": "Q" + }, + "rule_type": { + "default": [], + "description": "Filter monitoring tasks by rule type.", + "items": { + "type": "string" + }, + "title": "Rule Type", + "type": "array" + }, + "sortby": { "anyOf": [ { "items": { @@ -6818,38 +6966,8 @@ } ], "default": [], - "description": "Select one or more fields to order the response by.", - "title": "Order By" - }, - "properties": { - "description": "Comma-separated list of properties to include in the response. All properties are returned if omitted.", - "explode": false, - "items": { - "enum": [ - "id", - "workspaceId", - "monitoringSiteId", - "schedule", - "latestRun", - "ruleTypeCounts", - "recipients", - "name", - "description" - ], - "type": "string" - }, - "style": "form", - "title": "Properties", - "type": "array" - }, - "rule_type": { - "default": [], - "description": "Filter monitoring tasks by rule type.", - "items": { - "type": "string" - }, - "title": "Rule Type", - "type": "array" + "description": "Select one or more fields to sort the response by.", + "title": "Sortby" }, "workspace_id": { "default": [], @@ -7392,28 +7510,6 @@ "description": "Number of items to skip.", "title": "Offset" }, - "order_by": { - "anyOf": [ - { - "items": { - "enum": [ - "phenomenonTime", - "datastreamId", - "-phenomenonTime", - "-datastreamId" - ], - "type": "string" - }, - "type": "array" - }, - { - "type": "null" - } - ], - "default": [], - "description": "Select one or more fields to order the response by.", - "title": "Order By" - }, "phenomenon_time_max": { "anyOf": [ { @@ -7472,6 +7568,28 @@ }, "title": "Result Qualifier Code", "type": "array" + }, + "sortby": { + "anyOf": [ + { + "items": { + "enum": [ + "phenomenonTime", + "datastreamId", + "-phenomenonTime", + "-datastreamId" + ], + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": [], + "description": "Select one or more fields to sort the response by.", + "title": "Sortby" } }, "title": "ObservationQueryParameters", @@ -7804,7 +7922,38 @@ "description": "Number of items to skip.", "title": "Offset" }, - "order_by": { + "properties": { + "description": "Comma-separated list of properties to include in the response. All properties are returned if omitted.", + "explode": false, + "items": { + "enum": [ + "id", + "workspaceId", + "name", + "definition", + "description", + "type", + "code" + ], + "type": "string" + }, + "style": "form", + "title": "Properties", + "type": "array" + }, + "q": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND.", + "title": "Q" + }, + "sortby": { "anyOf": [ { "items": { @@ -7825,27 +7974,8 @@ } ], "default": [], - "description": "Select one or more fields to order the response by.", - "title": "Order By" - }, - "properties": { - "description": "Comma-separated list of properties to include in the response. All properties are returned if omitted.", - "explode": false, - "items": { - "enum": [ - "id", - "workspaceId", - "name", - "definition", - "description", - "type", - "code" - ], - "type": "string" - }, - "style": "form", - "title": "Properties", - "type": "array" + "description": "Select one or more fields to sort the response by.", + "title": "Sortby" }, "type": { "default": [], @@ -9301,7 +9431,37 @@ "description": "Number of items to skip.", "title": "Offset" }, - "order_by": { + "properties": { + "description": "Comma-separated list of properties to include in the response. All properties are returned if omitted.", + "explode": false, + "items": { + "enum": [ + "id", + "workspaceId", + "code", + "name", + "description", + "definition" + ], + "type": "string" + }, + "style": "form", + "title": "Properties", + "type": "array" + }, + "q": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND.", + "title": "Q" + }, + "sortby": { "anyOf": [ { "items": { @@ -9320,26 +9480,8 @@ } ], "default": [], - "description": "Select one or more fields to order the response by.", - "title": "Order By" - }, - "properties": { - "description": "Comma-separated list of properties to include in the response. All properties are returned if omitted.", - "explode": false, - "items": { - "enum": [ - "id", - "workspaceId", - "code", - "name", - "description", - "definition" - ], - "type": "string" - }, - "style": "form", - "title": "Properties", - "type": "array" + "description": "Select one or more fields to sort the response by.", + "title": "Sortby" }, "workspace_id": { "default": [], @@ -9533,32 +9675,6 @@ "description": "Number of items to skip.", "title": "Offset" }, - "order_by": { - "anyOf": [ - { - "items": { - "enum": [ - "id", - "createdAt", - "phenomenonTimeStart", - "phenomenonTimeEnd", - "-id", - "-createdAt", - "-phenomenonTimeStart", - "-phenomenonTimeEnd" - ], - "type": "string" - }, - "type": "array" - }, - { - "type": "null" - } - ], - "default": [], - "description": "Select one or more fields to order the response by.", - "title": "Order By" - }, "properties": { "description": "Comma-separated list of properties to include in the response. All properties are returned if omitted.", "explode": false, @@ -9579,6 +9695,32 @@ "title": "Properties", "type": "array" }, + "sortby": { + "anyOf": [ + { + "items": { + "enum": [ + "id", + "createdAt", + "phenomenonTimeStart", + "phenomenonTimeEnd", + "-id", + "-createdAt", + "-phenomenonTimeStart", + "-phenomenonTimeEnd" + ], + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": [], + "description": "Select one or more fields to sort the response by.", + "title": "Sortby" + }, "source_datastream_id": { "default": [], "description": "Filter histories by source datastream ID.", @@ -9839,7 +9981,26 @@ "description": "Number of items to skip.", "title": "Offset" }, - "order_by": { + "properties": { + "description": "Comma-separated list of properties to include in the response. All properties are returned if omitted.", + "explode": false, + "items": { + "enum": [ + "id", + "createdBy", + "order", + "operationType", + "createdAt", + "comment", + "arguments" + ], + "type": "string" + }, + "style": "form", + "title": "Properties", + "type": "array" + }, + "sortby": { "anyOf": [ { "items": { @@ -9862,27 +10023,8 @@ } ], "default": [], - "description": "Select one or more fields to order the response by.", - "title": "Order By" - }, - "properties": { - "description": "Comma-separated list of properties to include in the response. All properties are returned if omitted.", - "explode": false, - "items": { - "enum": [ - "id", - "createdBy", - "order", - "operationType", - "createdAt", - "comment", - "arguments" - ], - "type": "string" - }, - "style": "form", - "title": "Properties", - "type": "array" + "description": "Select one or more fields to sort the response by.", + "title": "Sortby" } }, "title": "QualityControlOperationQueryParameters", @@ -10121,36 +10263,6 @@ "description": "Number of items to skip.", "title": "Offset" }, - "order_by": { - "anyOf": [ - { - "items": { - "enum": [ - "id", - "createdAt", - "phenomenonTimeStart", - "phenomenonTimeEnd", - "status", - "committedAt", - "-id", - "-createdAt", - "-phenomenonTimeStart", - "-phenomenonTimeEnd", - "-status", - "-committedAt" - ], - "type": "string" - }, - "type": "array" - }, - { - "type": "null" - } - ], - "default": [], - "description": "Select one or more fields to order the response by.", - "title": "Order By" - }, "properties": { "description": "Comma-separated list of properties to include in the response. All properties are returned if omitted.", "explode": false, @@ -10207,6 +10319,36 @@ "description": "Return sessions overlapping with this range start.", "title": "Range Start" }, + "sortby": { + "anyOf": [ + { + "items": { + "enum": [ + "id", + "createdAt", + "phenomenonTimeStart", + "phenomenonTimeEnd", + "status", + "committedAt", + "-id", + "-createdAt", + "-phenomenonTimeStart", + "-phenomenonTimeEnd", + "-status", + "-committedAt" + ], + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": [], + "description": "Select one or more fields to sort the response by.", + "title": "Sortby" + }, "status": { "anyOf": [ { @@ -10533,7 +10675,37 @@ "description": "Number of items to skip.", "title": "Offset" }, - "order_by": { + "properties": { + "description": "Comma-separated list of properties to include in the response. All properties are returned if omitted.", + "explode": false, + "items": { + "enum": [ + "id", + "monitoringSiteId", + "name", + "description", + "fittingMethod", + "points" + ], + "type": "string" + }, + "style": "form", + "title": "Properties", + "type": "array" + }, + "q": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND.", + "title": "Q" + }, + "sortby": { "anyOf": [ { "items": { @@ -10544,12 +10716,14 @@ "monitoringSiteName", "workspaceId", "workspaceName", + "fittingMethod", "-id", "-name", "-monitoringSiteId", "-monitoringSiteName", "-workspaceId", - "-workspaceName" + "-workspaceName", + "-fittingMethod" ], "type": "string" }, @@ -10560,26 +10734,8 @@ } ], "default": [], - "description": "Select one or more fields to order the response by.", - "title": "Order By" - }, - "properties": { - "description": "Comma-separated list of properties to include in the response. All properties are returned if omitted.", - "explode": false, - "items": { - "enum": [ - "id", - "monitoringSiteId", - "name", - "description", - "fittingMethod", - "points" - ], - "type": "string" - }, - "style": "form", - "title": "Properties", - "type": "array" + "description": "Select one or more fields to sort the response by.", + "title": "Sortby" }, "workspace_id": { "default": [], @@ -10832,7 +10988,35 @@ "description": "Number of items to skip.", "title": "Offset" }, - "order_by": { + "properties": { + "description": "Comma-separated list of properties to include in the response. All properties are returned if omitted.", + "explode": false, + "items": { + "enum": [ + "id", + "workspaceId", + "code", + "description" + ], + "type": "string" + }, + "style": "form", + "title": "Properties", + "type": "array" + }, + "q": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND.", + "title": "Q" + }, + "sortby": { "anyOf": [ { "items": { @@ -10849,24 +11033,8 @@ } ], "default": [], - "description": "Select one or more fields to order the response by.", - "title": "Order By" - }, - "properties": { - "description": "Comma-separated list of properties to include in the response. All properties are returned if omitted.", - "explode": false, - "items": { - "enum": [ - "id", - "workspaceId", - "code", - "description" - ], - "type": "string" - }, - "style": "form", - "title": "Properties", - "type": "array" + "description": "Select one or more fields to sort the response by.", + "title": "Sortby" }, "workspace_id": { "default": [], @@ -10994,26 +11162,6 @@ "description": "Number of items to skip.", "title": "Offset" }, - "order_by": { - "anyOf": [ - { - "items": { - "enum": [ - "name", - "-name" - ], - "type": "string" - }, - "type": "array" - }, - { - "type": "null" - } - ], - "default": [], - "description": "Select one or more fields to order the response by.", - "title": "Order By" - }, "properties": { "description": "Comma-separated list of properties to include in the response. All properties are returned if omitted.", "explode": false, @@ -11031,6 +11179,26 @@ "title": "Properties", "type": "array" }, + "sortby": { + "anyOf": [ + { + "items": { + "enum": [ + "name", + "-name" + ], + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": [], + "description": "Select one or more fields to sort the response by.", + "title": "Sortby" + }, "workspace_id": { "default": [], "description": "Filter roles by workspace ID.", @@ -11531,30 +11699,6 @@ "description": "Number of items to skip.", "title": "Offset" }, - "order_by": { - "anyOf": [ - { - "items": { - "enum": [ - "name", - "isActive", - "keyExpiresAt", - "-name", - "-isActive", - "-keyExpiresAt" - ], - "type": "string" - }, - "type": "array" - }, - { - "type": "null" - } - ], - "default": [], - "description": "Select one or more fields to order the response by.", - "title": "Order By" - }, "properties": { "description": "Comma-separated list of properties to include in the response. All properties are returned if omitted.", "explode": false, @@ -11575,6 +11719,34 @@ "style": "form", "title": "Properties", "type": "array" + }, + "sortby": { + "anyOf": [ + { + "items": { + "enum": [ + "name", + "isActive", + "keyExpiresAt", + "createdAt", + "lastUsedAt", + "-name", + "-isActive", + "-keyExpiresAt", + "-createdAt", + "-lastUsedAt" + ], + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": [], + "description": "Select one or more fields to sort the response by.", + "title": "Sortby" } }, "title": "ServiceAccountQueryParameters", @@ -11754,41 +11926,41 @@ "description": "Number of items to skip.", "title": "Offset" }, - "order_by": { - "default": [], - "description": "Select one or more fields to order the response by.", + "properties": { + "description": "Comma-separated list of properties to include in the response. All properties are returned if omitted.", + "explode": false, "items": { "enum": [ "id", "status", + "message", + "result", "startedAt", - "finishedAt", - "-id", - "-status", - "-startedAt", - "-finishedAt" + "finishedAt" ], "type": "string" }, - "title": "Order By", + "style": "form", + "title": "Properties", "type": "array" }, - "properties": { - "description": "Comma-separated list of properties to include in the response. All properties are returned if omitted.", - "explode": false, + "sortby": { + "default": [], + "description": "Select one or more fields to sort the response by.", "items": { "enum": [ "id", "status", - "message", - "result", "startedAt", - "finishedAt" + "finishedAt", + "-id", + "-status", + "-startedAt", + "-finishedAt" ], "type": "string" }, - "style": "form", - "title": "Properties", + "title": "Sortby", "type": "array" }, "started_at_max": { @@ -12168,7 +12340,37 @@ "description": "Number of items to skip.", "title": "Offset" }, - "order_by": { + "properties": { + "description": "Comma-separated list of properties to include in the response. All properties are returned if omitted.", + "explode": false, + "items": { + "enum": [ + "id", + "workspaceId", + "name", + "symbol", + "definition", + "type" + ], + "type": "string" + }, + "style": "form", + "title": "Properties", + "type": "array" + }, + "q": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND.", + "title": "Q" + }, + "sortby": { "anyOf": [ { "items": { @@ -12189,26 +12391,8 @@ } ], "default": [], - "description": "Select one or more fields to order the response by.", - "title": "Order By" - }, - "properties": { - "description": "Comma-separated list of properties to include in the response. All properties are returned if omitted.", - "explode": false, - "items": { - "enum": [ - "id", - "workspaceId", - "name", - "symbol", - "definition", - "type" - ], - "type": "string" - }, - "style": "form", - "title": "Properties", - "type": "array" + "description": "Select one or more fields to sort the response by.", + "title": "Sortby" }, "type": { "default": [], @@ -12650,30 +12834,30 @@ "description": "Number of items to skip.", "title": "Offset" }, - "order_desc": { + "properties": { "anyOf": [ { - "type": "boolean" + "type": "string" }, { "type": "null" } ], - "default": false, - "description": "Sort terms by descending.", - "title": "Order Desc" + "description": "Comma-separated list of properties to include in the response. All properties are returned if omitted.", + "title": "Properties" }, - "properties": { + "sort_desc": { "anyOf": [ { - "type": "string" + "type": "boolean" }, { "type": "null" } ], - "description": "Comma-separated list of properties to include in the response. All properties are returned if omitted.", - "title": "Properties" + "default": false, + "description": "Sort terms by descending.", + "title": "Sort Desc" } }, "title": "VocabularyQueryParameters", @@ -12834,7 +13018,37 @@ "description": "Number of items to skip.", "title": "Offset" }, - "order_by": { + "properties": { + "description": "Comma-separated list of properties to include in the response. All properties are returned if omitted.", + "explode": false, + "items": { + "enum": [ + "id", + "ownerEmail", + "pendingTransferToEmail", + "collaboratorRoleId", + "name", + "isPrivate" + ], + "type": "string" + }, + "style": "form", + "title": "Properties", + "type": "array" + }, + "q": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND.", + "title": "Q" + }, + "sortby": { "anyOf": [ { "items": { @@ -12853,26 +13067,8 @@ } ], "default": [], - "description": "Select one or more fields to order the response by.", - "title": "Order By" - }, - "properties": { - "description": "Comma-separated list of properties to include in the response. All properties are returned if omitted.", - "explode": false, - "items": { - "enum": [ - "id", - "ownerEmail", - "pendingTransferToEmail", - "collaboratorRoleId", - "name", - "isPrivate" - ], - "type": "string" - }, - "style": "form", - "title": "Properties", - "type": "array" + "description": "Select one or more fields to sort the response by.", + "title": "Sortby" } }, "title": "WorkspaceQueryParameters", @@ -13063,9 +13259,9 @@ } }, { - "description": "Select one or more fields to order the response by.", + "description": "Select one or more fields to sort the response by.", "in": "query", - "name": "order_by", + "name": "sortby", "required": false, "schema": { "anyOf": [ @@ -13078,12 +13274,14 @@ "monitoringSiteName", "workspaceId", "workspaceName", + "fittingMethod", "-id", "-name", "-monitoringSiteId", "-monitoringSiteName", "-workspaceId", - "-workspaceName" + "-workspaceName", + "-fittingMethod" ], "type": "string" }, @@ -13094,8 +13292,26 @@ } ], "default": [], - "description": "Select one or more fields to order the response by.", - "title": "Order By" + "description": "Select one or more fields to sort the response by.", + "title": "Sortby" + } + }, + { + "description": "Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND.", + "in": "query", + "name": "q", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND.", + "title": "Q" } }, { @@ -13644,9 +13860,9 @@ } }, { - "description": "Select one or more fields to order the response by.", + "description": "Select one or more fields to sort the response by.", "in": "query", - "name": "order_by", + "name": "sortby", "required": false, "schema": { "anyOf": [ @@ -13681,8 +13897,26 @@ } ], "default": [], - "description": "Select one or more fields to order the response by.", - "title": "Order By" + "description": "Select one or more fields to sort the response by.", + "title": "Sortby" + } + }, + { + "description": "Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND.", + "in": "query", + "name": "q", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND.", + "title": "Q" } }, { @@ -14326,13 +14560,13 @@ } }, { - "description": "Select one or more fields to order the response by.", + "description": "Select one or more fields to sort the response by.", "in": "query", - "name": "order_by", + "name": "sortby", "required": false, "schema": { "default": [], - "description": "Select one or more fields to order the response by.", + "description": "Select one or more fields to sort the response by.", "items": { "enum": [ "id", @@ -14346,7 +14580,7 @@ ], "type": "string" }, - "title": "Order By", + "title": "Sortby", "type": "array" } }, @@ -14796,9 +15030,9 @@ } }, { - "description": "Select one or more fields to order the response by.", + "description": "Select one or more fields to sort the response by.", "in": "query", - "name": "order_by", + "name": "sortby", "required": false, "schema": { "anyOf": [ @@ -14807,8 +15041,10 @@ "enum": [ "id", "outputDatastreamId", + "transformationType", "-id", - "-outputDatastreamId" + "-outputDatastreamId", + "-transformationType" ], "type": "string" }, @@ -14819,8 +15055,8 @@ } ], "default": [], - "description": "Select one or more fields to order the response by.", - "title": "Order By" + "description": "Select one or more fields to sort the response by.", + "title": "Sortby" } }, { @@ -15472,9 +15708,9 @@ } }, { - "description": "Select one or more fields to order the response by.", + "description": "Select one or more fields to sort the response by.", "in": "query", - "name": "order_by", + "name": "sortby", "required": false, "schema": { "anyOf": [ @@ -15487,6 +15723,8 @@ "status", "resultType", "isPrivate", + "isVisible", + "aggregationStatistic", "valueCount", "phenomenonBeginTime", "phenomenonEndTime", @@ -15498,6 +15736,8 @@ "-status", "-resultType", "-isPrivate", + "-isVisible", + "-aggregationStatistic", "-valueCount", "-phenomenonBeginTime", "-phenomenonEndTime", @@ -15513,8 +15753,26 @@ } ], "default": [], - "description": "Select one or more fields to order the response by.", - "title": "Order By" + "description": "Select one or more fields to sort the response by.", + "title": "Sortby" + } + }, + { + "description": "Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND.", + "in": "query", + "name": "q", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND.", + "title": "Q" } }, { @@ -16141,7 +16399,7 @@ { "description": "Sort terms by descending.", "in": "query", - "name": "order_desc", + "name": "sort_desc", "required": false, "schema": { "anyOf": [ @@ -16154,7 +16412,7 @@ ], "default": false, "description": "Sort terms by descending.", - "title": "Order Desc" + "title": "Sort Desc" } } ], @@ -16261,7 +16519,7 @@ { "description": "Sort terms by descending.", "in": "query", - "name": "order_desc", + "name": "sort_desc", "required": false, "schema": { "anyOf": [ @@ -16274,7 +16532,7 @@ ], "default": false, "description": "Sort terms by descending.", - "title": "Order Desc" + "title": "Sort Desc" } } ], @@ -16381,7 +16639,7 @@ { "description": "Sort terms by descending.", "in": "query", - "name": "order_desc", + "name": "sort_desc", "required": false, "schema": { "anyOf": [ @@ -16394,7 +16652,7 @@ ], "default": false, "description": "Sort terms by descending.", - "title": "Order Desc" + "title": "Sort Desc" } } ], @@ -16501,7 +16759,7 @@ { "description": "Sort terms by descending.", "in": "query", - "name": "order_desc", + "name": "sort_desc", "required": false, "schema": { "anyOf": [ @@ -16514,7 +16772,7 @@ ], "default": false, "description": "Sort terms by descending.", - "title": "Order Desc" + "title": "Sort Desc" } } ], @@ -17709,9 +17967,9 @@ } }, { - "description": "Select one or more fields to order the response by.", + "description": "Select one or more fields to sort the response by.", "in": "query", - "name": "order_by", + "name": "sortby", "required": false, "schema": { "anyOf": [ @@ -17744,8 +18002,26 @@ } ], "default": [], - "description": "Select one or more fields to order the response by.", - "title": "Order By" + "description": "Select one or more fields to sort the response by.", + "title": "Sortby" + } + }, + { + "description": "Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND.", + "in": "query", + "name": "q", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND.", + "title": "Q" } }, { @@ -18285,9 +18561,9 @@ } }, { - "description": "Select one or more fields to order the response by.", + "description": "Select one or more fields to sort the response by.", "in": "query", - "name": "order_by", + "name": "sortby", "required": false, "schema": { "anyOf": [ @@ -18310,8 +18586,8 @@ } ], "default": [], - "description": "Select one or more fields to order the response by.", - "title": "Order By" + "description": "Select one or more fields to sort the response by.", + "title": "Sortby" } }, { @@ -18911,9 +19187,9 @@ } }, { - "description": "Select one or more fields to order the response by.", + "description": "Select one or more fields to sort the response by.", "in": "query", - "name": "order_by", + "name": "sortby", "required": false, "schema": { "anyOf": [ @@ -18948,8 +19224,26 @@ } ], "default": [], - "description": "Select one or more fields to order the response by.", - "title": "Order By" + "description": "Select one or more fields to sort the response by.", + "title": "Sortby" + } + }, + { + "description": "Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND.", + "in": "query", + "name": "q", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND.", + "title": "Q" } }, { @@ -19635,13 +19929,13 @@ } }, { - "description": "Select one or more fields to order the response by.", + "description": "Select one or more fields to sort the response by.", "in": "query", - "name": "order_by", + "name": "sortby", "required": false, "schema": { "default": [], - "description": "Select one or more fields to order the response by.", + "description": "Select one or more fields to sort the response by.", "items": { "enum": [ "id", @@ -19655,7 +19949,7 @@ ], "type": "string" }, - "title": "Order By", + "title": "Sortby", "type": "array" } }, @@ -20100,9 +20394,9 @@ } }, { - "description": "Select one or more fields to order the response by.", + "description": "Select one or more fields to sort the response by.", "in": "query", - "name": "order_by", + "name": "sortby", "required": false, "schema": { "anyOf": [ @@ -20114,11 +20408,15 @@ "type", "sensorModel", "sensorModelManufacturer", + "definition", + "sensorModelDefinition", "-name", "-code", "-type", "-sensorModel", - "-sensorModelManufacturer" + "-sensorModelManufacturer", + "-definition", + "-sensorModelDefinition" ], "type": "string" }, @@ -20129,8 +20427,26 @@ } ], "default": [], - "description": "Select one or more fields to order the response by.", - "title": "Order By" + "description": "Select one or more fields to sort the response by.", + "title": "Sortby" + } + }, + { + "description": "Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND.", + "in": "query", + "name": "q", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND.", + "title": "Q" } }, { @@ -20468,7 +20784,7 @@ { "description": "Sort terms by descending.", "in": "query", - "name": "order_desc", + "name": "sort_desc", "required": false, "schema": { "anyOf": [ @@ -20481,7 +20797,7 @@ ], "default": false, "description": "Sort terms by descending.", - "title": "Order Desc" + "title": "Sort Desc" } } ], @@ -20877,9 +21193,9 @@ } }, { - "description": "Select one or more fields to order the response by.", + "description": "Select one or more fields to sort the response by.", "in": "query", - "name": "order_by", + "name": "sortby", "required": false, "schema": { "anyOf": [ @@ -20889,9 +21205,11 @@ "id", "ruleType", "datastreamId", + "lastCheckedAt", "-id", "-ruleType", - "-datastreamId" + "-datastreamId", + "-lastCheckedAt" ], "type": "string" }, @@ -20902,8 +21220,8 @@ } ], "default": [], - "description": "Select one or more fields to order the response by.", - "title": "Order By" + "description": "Select one or more fields to sort the response by.", + "title": "Sortby" } }, { @@ -21516,9 +21834,9 @@ } }, { - "description": "Select one or more fields to order the response by.", + "description": "Select one or more fields to sort the response by.", "in": "query", - "name": "order_by", + "name": "sortby", "required": false, "schema": { "anyOf": [ @@ -21557,8 +21875,26 @@ } ], "default": [], - "description": "Select one or more fields to order the response by.", - "title": "Order By" + "description": "Select one or more fields to sort the response by.", + "title": "Sortby" + } + }, + { + "description": "Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND.", + "in": "query", + "name": "q", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND.", + "title": "Q" } }, { @@ -21881,7 +22217,7 @@ { "description": "Sort terms by descending.", "in": "query", - "name": "order_desc", + "name": "sort_desc", "required": false, "schema": { "anyOf": [ @@ -21894,7 +22230,7 @@ ], "default": false, "description": "Sort terms by descending.", - "title": "Order Desc" + "title": "Sort Desc" } } ], @@ -22211,7 +22547,7 @@ { "description": "Sort terms by descending.", "in": "query", - "name": "order_desc", + "name": "sort_desc", "required": false, "schema": { "anyOf": [ @@ -22224,7 +22560,7 @@ ], "default": false, "description": "Sort terms by descending.", - "title": "Order Desc" + "title": "Sort Desc" } } ], @@ -23354,9 +23690,9 @@ } }, { - "description": "Select one or more fields to order the response by.", + "description": "Select one or more fields to sort the response by.", "in": "query", - "name": "order_by", + "name": "sortby", "required": false, "schema": { "anyOf": [ @@ -23391,8 +23727,26 @@ } ], "default": [], - "description": "Select one or more fields to order the response by.", - "title": "Order By" + "description": "Select one or more fields to sort the response by.", + "title": "Sortby" + } + }, + { + "description": "Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND.", + "in": "query", + "name": "q", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND.", + "title": "Q" } }, { @@ -24005,13 +24359,13 @@ } }, { - "description": "Select one or more fields to order the response by.", + "description": "Select one or more fields to sort the response by.", "in": "query", - "name": "order_by", + "name": "sortby", "required": false, "schema": { "default": [], - "description": "Select one or more fields to order the response by.", + "description": "Select one or more fields to sort the response by.", "items": { "enum": [ "id", @@ -24025,7 +24379,7 @@ ], "type": "string" }, - "title": "Order By", + "title": "Sortby", "type": "array" } }, @@ -24483,9 +24837,9 @@ } }, { - "description": "Select one or more fields to order the response by.", + "description": "Select one or more fields to sort the response by.", "in": "query", - "name": "order_by", + "name": "sortby", "required": false, "schema": { "anyOf": [ @@ -24506,8 +24860,8 @@ } ], "default": [], - "description": "Select one or more fields to order the response by.", - "title": "Order By" + "description": "Select one or more fields to sort the response by.", + "title": "Sortby" } }, { @@ -25192,9 +25546,9 @@ } }, { - "description": "Select one or more fields to order the response by.", + "description": "Select one or more fields to sort the response by.", "in": "query", - "name": "order_by", + "name": "sortby", "required": false, "schema": { "anyOf": [ @@ -25217,8 +25571,26 @@ } ], "default": [], - "description": "Select one or more fields to order the response by.", - "title": "Order By" + "description": "Select one or more fields to sort the response by.", + "title": "Sortby" + } + }, + { + "description": "Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND.", + "in": "query", + "name": "q", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND.", + "title": "Q" } }, { @@ -25526,7 +25898,7 @@ { "description": "Sort terms by descending.", "in": "query", - "name": "order_desc", + "name": "sort_desc", "required": false, "schema": { "anyOf": [ @@ -25539,7 +25911,7 @@ ], "default": false, "description": "Sort terms by descending.", - "title": "Order Desc" + "title": "Sort Desc" } } ], @@ -25930,9 +26302,9 @@ } }, { - "description": "Select one or more fields to order the response by.", + "description": "Select one or more fields to sort the response by.", "in": "query", - "name": "order_by", + "name": "sortby", "required": false, "schema": { "anyOf": [ @@ -25953,8 +26325,26 @@ } ], "default": [], - "description": "Select one or more fields to order the response by.", - "title": "Order By" + "description": "Select one or more fields to sort the response by.", + "title": "Sortby" + } + }, + { + "description": "Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND.", + "in": "query", + "name": "q", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND.", + "title": "Q" } }, { @@ -26533,9 +26923,9 @@ } }, { - "description": "Select one or more fields to order the response by.", + "description": "Select one or more fields to sort the response by.", "in": "query", - "name": "order_by", + "name": "sortby", "required": false, "schema": { "anyOf": [ @@ -26560,8 +26950,8 @@ } ], "default": [], - "description": "Select one or more fields to order the response by.", - "title": "Order By" + "description": "Select one or more fields to sort the response by.", + "title": "Sortby" } }, { @@ -27044,9 +27434,9 @@ } }, { - "description": "Select one or more fields to order the response by.", + "description": "Select one or more fields to sort the response by.", "in": "query", - "name": "order_by", + "name": "sortby", "required": false, "schema": { "anyOf": [ @@ -27075,8 +27465,8 @@ } ], "default": [], - "description": "Select one or more fields to order the response by.", - "title": "Order By" + "description": "Select one or more fields to sort the response by.", + "title": "Sortby" } }, { @@ -27851,9 +28241,9 @@ } }, { - "description": "Select one or more fields to order the response by.", + "description": "Select one or more fields to sort the response by.", "in": "query", - "name": "order_by", + "name": "sortby", "required": false, "schema": { "anyOf": [ @@ -27878,8 +28268,8 @@ } ], "default": [], - "description": "Select one or more fields to order the response by.", - "title": "Order By" + "description": "Select one or more fields to sort the response by.", + "title": "Sortby" } } ], @@ -28485,9 +28875,9 @@ } }, { - "description": "Select one or more fields to order the response by.", + "description": "Select one or more fields to sort the response by.", "in": "query", - "name": "order_by", + "name": "sortby", "required": false, "schema": { "anyOf": [ @@ -28506,8 +28896,26 @@ } ], "default": [], - "description": "Select one or more fields to order the response by.", - "title": "Order By" + "description": "Select one or more fields to sort the response by.", + "title": "Sortby" + } + }, + { + "description": "Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND.", + "in": "query", + "name": "q", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND.", + "title": "Q" } }, { @@ -29046,9 +29454,9 @@ } }, { - "description": "Select one or more fields to order the response by.", + "description": "Select one or more fields to sort the response by.", "in": "query", - "name": "order_by", + "name": "sortby", "required": false, "schema": { "anyOf": [ @@ -29067,8 +29475,8 @@ } ], "default": [], - "description": "Select one or more fields to order the response by.", - "title": "Order By" + "description": "Select one or more fields to sort the response by.", + "title": "Sortby" } }, { @@ -29322,9 +29730,9 @@ } }, { - "description": "Select one or more fields to order the response by.", + "description": "Select one or more fields to sort the response by.", "in": "query", - "name": "order_by", + "name": "sortby", "required": false, "schema": { "anyOf": [ @@ -29347,8 +29755,26 @@ } ], "default": [], - "description": "Select one or more fields to order the response by.", - "title": "Order By" + "description": "Select one or more fields to sort the response by.", + "title": "Sortby" + } + }, + { + "description": "Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND.", + "in": "query", + "name": "q", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND.", + "title": "Q" } }, { @@ -29645,7 +30071,7 @@ { "description": "Sort terms by descending.", "in": "query", - "name": "order_desc", + "name": "sort_desc", "required": false, "schema": { "anyOf": [ @@ -29658,7 +30084,7 @@ ], "default": false, "description": "Sort terms by descending.", - "title": "Order Desc" + "title": "Sort Desc" } } ], @@ -30050,9 +30476,9 @@ } }, { - "description": "Select one or more fields to order the response by.", + "description": "Select one or more fields to sort the response by.", "in": "query", - "name": "order_by", + "name": "sortby", "required": false, "schema": { "anyOf": [ @@ -30073,8 +30499,26 @@ } ], "default": [], - "description": "Select one or more fields to order the response by.", - "title": "Order By" + "description": "Select one or more fields to sort the response by.", + "title": "Sortby" + } + }, + { + "description": "Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND.", + "in": "query", + "name": "q", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND.", + "title": "Q" } }, { @@ -30690,6 +31134,34 @@ "title": "Role Id", "type": "array" } + }, + { + "description": "Select one or more fields to sort the response by.", + "in": "query", + "name": "sortby", + "required": false, + "schema": { + "anyOf": [ + { + "items": { + "enum": [ + "id", + "roleId", + "-id", + "-roleId" + ], + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": [], + "description": "Select one or more fields to sort the response by.", + "title": "Sortby" + } } ], "responses": { @@ -31014,9 +31486,9 @@ } }, { - "description": "Select one or more fields to order the response by.", + "description": "Select one or more fields to sort the response by.", "in": "query", - "name": "order_by", + "name": "sortby", "required": false, "schema": { "anyOf": [ @@ -31026,9 +31498,13 @@ "name", "isActive", "keyExpiresAt", + "createdAt", + "lastUsedAt", "-name", "-isActive", - "-keyExpiresAt" + "-keyExpiresAt", + "-createdAt", + "-lastUsedAt" ], "type": "string" }, @@ -31039,8 +31515,8 @@ } ], "default": [], - "description": "Select one or more fields to order the response by.", - "title": "Order By" + "description": "Select one or more fields to sort the response by.", + "title": "Sortby" } } ], diff --git a/django/core/iam/migrations/0012_workspace_search_vector_and_more.py b/django/core/iam/migrations/0012_workspace_search_vector_and_more.py new file mode 100644 index 00000000..d01a9a19 --- /dev/null +++ b/django/core/iam/migrations/0012_workspace_search_vector_and_more.py @@ -0,0 +1,89 @@ +# Generated by Django 6.0.7 on 2026-09-17 20:54 + +import django.contrib.postgres.indexes +import django.contrib.postgres.search +from django.db import migrations + + +def _search_vector_trigger_sql( + table: str, + field_weights: dict[str, str], + vector_column: str = "search_vector", +) -> list[migrations.RunSQL]: + """ + Builds the migration operations needed to keep a `SearchVectorField` current via a + Postgres trigger, plus a one-time backfill for existing rows. + + INSERT and UPDATE are handled by separate triggers rather than one combined + `BEFORE INSERT OR UPDATE` trigger: the UPDATE trigger has a `WHEN` guard comparing + OLD/NEW on only the source columns, so it skips recomputing the vector when a save + touches unrelated columns (e.g. observation-statistics updates). INSERT has no OLD + row to compare against, so it always runs unconditionally. + """ + + function_name = f"{table}_{vector_column}_trigger" + insert_trigger_name = f"{table}_{vector_column}_ins_trg" + update_trigger_name = f"{table}_{vector_column}_upd_trg" + + new_expr = " || ".join( + f"setweight(to_tsvector('english', coalesce(NEW.{field}, '')), '{weight}')" + for field, weight in field_weights.items() + ) + backfill_expr = " || ".join( + f"setweight(to_tsvector('english', coalesce({field}, '')), '{weight}')" + for field, weight in field_weights.items() + ) + base_fields = dict.fromkeys(field.split("::", 1)[0] for field in field_weights) + when_condition = " OR ".join( + f"OLD.{field} IS DISTINCT FROM NEW.{field}" for field in base_fields + ) + + forward_sql = f""" + CREATE OR REPLACE FUNCTION {function_name}() RETURNS trigger AS $$ + BEGIN + NEW.{vector_column} := {new_expr}; + RETURN NEW; + END + $$ LANGUAGE plpgsql; + + DROP TRIGGER IF EXISTS {insert_trigger_name} ON {table}; + CREATE TRIGGER {insert_trigger_name} + BEFORE INSERT ON {table} + FOR EACH ROW EXECUTE FUNCTION {function_name}(); + + DROP TRIGGER IF EXISTS {update_trigger_name} ON {table}; + CREATE TRIGGER {update_trigger_name} + BEFORE UPDATE ON {table} + FOR EACH ROW WHEN ({when_condition}) + EXECUTE FUNCTION {function_name}(); + + UPDATE {table} SET {vector_column} = {backfill_expr}; + """ + + reverse_sql = f""" + DROP TRIGGER IF EXISTS {insert_trigger_name} ON {table}; + DROP TRIGGER IF EXISTS {update_trigger_name} ON {table}; + DROP FUNCTION IF EXISTS {function_name}(); + """ + + return [migrations.RunSQL(sql=forward_sql, reverse_sql=reverse_sql)] + + +class Migration(migrations.Migration): + + dependencies = [ + ('iam', '0011_alter_collaborator_unique_workspace_user_collaborator_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='workspace', + name='search_vector', + field=django.contrib.postgres.search.SearchVectorField(editable=False, null=True), + ), + *_search_vector_trigger_sql("iam_workspace", {"name": "A"}), + migrations.AddIndex( + model_name='workspace', + index=django.contrib.postgres.indexes.GinIndex(fields=['search_vector'], name='iam_workspace_search_gin'), + ), + ] diff --git a/django/core/iam/models/workspace.py b/django/core/iam/models/workspace.py index c4e65518..76fd4f61 100644 --- a/django/core/iam/models/workspace.py +++ b/django/core/iam/models/workspace.py @@ -2,6 +2,8 @@ import typing from django.db import models +from django.contrib.postgres.indexes import GinIndex +from django.contrib.postgres.search import SearchVectorField from django.core.exceptions import ValidationError from django.conf import settings @@ -36,6 +38,7 @@ class Workspace(models.Model): related_name="owned_workspaces", ) is_private = models.BooleanField(default=False) + search_vector = SearchVectorField(null=True, editable=False) objects = WorkspaceQuerySet.as_manager() @@ -51,6 +54,9 @@ class Meta: fields=["name", "owner"], name="unique_workspace_name_per_owner" ) ] + indexes = [ + GinIndex(fields=["search_vector"], name="iam_workspace_search_gin"), + ] @property def transfer(self): diff --git a/django/core/sta/migrations/0017_datastream_search_vector_method_search_vector_and_more.py b/django/core/sta/migrations/0017_datastream_search_vector_method_search_vector_and_more.py new file mode 100644 index 00000000..43487b87 --- /dev/null +++ b/django/core/sta/migrations/0017_datastream_search_vector_method_search_vector_and_more.py @@ -0,0 +1,185 @@ +# Generated by Django 6.0.7 on 2026-09-17 20:54 + +import django.contrib.postgres.indexes +import django.contrib.postgres.search +from django.db import migrations + + +def _search_vector_trigger_sql( + table: str, + field_weights: dict[str, str], + vector_column: str = "search_vector", +) -> list[migrations.RunSQL]: + """ + Builds the migration operations needed to keep a `SearchVectorField` current via a + Postgres trigger, plus a one-time backfill for existing rows. + + INSERT and UPDATE are handled by separate triggers rather than one combined + `BEFORE INSERT OR UPDATE` trigger: the UPDATE trigger has a `WHEN` guard comparing + OLD/NEW on only the source columns, so it skips recomputing the vector when a save + touches unrelated columns (e.g. observation-statistics updates). INSERT has no OLD + row to compare against, so it always runs unconditionally. + """ + + function_name = f"{table}_{vector_column}_trigger" + insert_trigger_name = f"{table}_{vector_column}_ins_trg" + update_trigger_name = f"{table}_{vector_column}_upd_trg" + + new_expr = " || ".join( + f"setweight(to_tsvector('english', coalesce(NEW.{field}, '')), '{weight}')" + for field, weight in field_weights.items() + ) + backfill_expr = " || ".join( + f"setweight(to_tsvector('english', coalesce({field}, '')), '{weight}')" + for field, weight in field_weights.items() + ) + base_fields = dict.fromkeys(field.split("::", 1)[0] for field in field_weights) + when_condition = " OR ".join( + f"OLD.{field} IS DISTINCT FROM NEW.{field}" for field in base_fields + ) + + forward_sql = f""" + CREATE OR REPLACE FUNCTION {function_name}() RETURNS trigger AS $$ + BEGIN + NEW.{vector_column} := {new_expr}; + RETURN NEW; + END + $$ LANGUAGE plpgsql; + + DROP TRIGGER IF EXISTS {insert_trigger_name} ON {table}; + CREATE TRIGGER {insert_trigger_name} + BEFORE INSERT ON {table} + FOR EACH ROW EXECUTE FUNCTION {function_name}(); + + DROP TRIGGER IF EXISTS {update_trigger_name} ON {table}; + CREATE TRIGGER {update_trigger_name} + BEFORE UPDATE ON {table} + FOR EACH ROW WHEN ({when_condition}) + EXECUTE FUNCTION {function_name}(); + + UPDATE {table} SET {vector_column} = {backfill_expr}; + """ + + reverse_sql = f""" + DROP TRIGGER IF EXISTS {insert_trigger_name} ON {table}; + DROP TRIGGER IF EXISTS {update_trigger_name} ON {table}; + DROP FUNCTION IF EXISTS {function_name}(); + """ + + return [migrations.RunSQL(sql=forward_sql, reverse_sql=reverse_sql)] + + +class Migration(migrations.Migration): + + dependencies = [ + ('iam', '0012_workspace_search_vector_and_more'), + ('sta', '0016_rename_metadata_table_fields'), + ] + + operations = [ + migrations.AddField( + model_name='datastream', + name='search_vector', + field=django.contrib.postgres.search.SearchVectorField(editable=False, null=True), + ), + migrations.AddField( + model_name='method', + name='search_vector', + field=django.contrib.postgres.search.SearchVectorField(editable=False, null=True), + ), + migrations.AddField( + model_name='monitoringsite', + name='search_vector', + field=django.contrib.postgres.search.SearchVectorField(editable=False, null=True), + ), + migrations.AddField( + model_name='observedproperty', + name='search_vector', + field=django.contrib.postgres.search.SearchVectorField(editable=False, null=True), + ), + migrations.AddField( + model_name='processinglevel', + name='search_vector', + field=django.contrib.postgres.search.SearchVectorField(editable=False, null=True), + ), + migrations.AddField( + model_name='resultqualifier', + name='search_vector', + field=django.contrib.postgres.search.SearchVectorField(editable=False, null=True), + ), + migrations.AddField( + model_name='unit', + name='search_vector', + field=django.contrib.postgres.search.SearchVectorField(editable=False, null=True), + ), + *_search_vector_trigger_sql( + "sta_monitoringsite", + {"name": "A", "description": "B", "code": "C", "type": "C", "tags::text": "D"}, + ), + *_search_vector_trigger_sql( + "sta_datastream", + { + "name": "A", + "description": "B", + "observation_type": "C", + "result_type": "C", + "sampled_medium": "C", + "status": "C", + "tags::text": "D", + }, + ), + *_search_vector_trigger_sql( + "sta_observedproperty", + {"name": "A", "definition": "B", "description": "B", "type": "C", "code": "C"}, + ), + *_search_vector_trigger_sql( + "sta_method", + { + "name": "A", + "description": "B", + "definition": "B", + "type": "C", + "sensor_model": "C", + "sensor_model_manufacturer": "C", + "sensor_model_definition": "C", + }, + ), + *_search_vector_trigger_sql( + "sta_unit", {"name": "A", "definition": "B", "symbol": "C", "type": "C"} + ), + *_search_vector_trigger_sql( + "sta_processinglevel", + {"name": "A", "description": "B", "definition": "B", "code": "C"}, + ), + *_search_vector_trigger_sql( + "sta_resultqualifier", {"description": "A", "code": "C"} + ), + migrations.AddIndex( + model_name='datastream', + index=django.contrib.postgres.indexes.GinIndex(fields=['search_vector'], name='sta_datastream_search_gin'), + ), + migrations.AddIndex( + model_name='method', + index=django.contrib.postgres.indexes.GinIndex(fields=['search_vector'], name='sta_method_search_gin'), + ), + migrations.AddIndex( + model_name='monitoringsite', + index=django.contrib.postgres.indexes.GinIndex(fields=['search_vector'], name='sta_monitoringsite_search_gin'), + ), + migrations.AddIndex( + model_name='observedproperty', + index=django.contrib.postgres.indexes.GinIndex(fields=['search_vector'], name='sta_obsproperty_search_gin'), + ), + migrations.AddIndex( + model_name='processinglevel', + index=django.contrib.postgres.indexes.GinIndex(fields=['search_vector'], name='sta_proclevel_search_gin'), + ), + migrations.AddIndex( + model_name='resultqualifier', + index=django.contrib.postgres.indexes.GinIndex(fields=['search_vector'], name='sta_resultqual_search_gin'), + ), + migrations.AddIndex( + model_name='unit', + index=django.contrib.postgres.indexes.GinIndex(fields=['search_vector'], name='sta_unit_search_gin'), + ), + ] diff --git a/django/core/sta/models/datastream.py b/django/core/sta/models/datastream.py index 84dd1fe5..d9f6b257 100644 --- a/django/core/sta/models/datastream.py +++ b/django/core/sta/models/datastream.py @@ -2,6 +2,7 @@ from django.db import models from django.contrib.postgres.indexes import GinIndex +from django.contrib.postgres.search import SearchVectorField from django.conf import settings from django.core.exceptions import ObjectDoesNotExist, ValidationError @@ -67,6 +68,7 @@ class Datastream(models.Model): is_private = models.BooleanField(default=True) is_visible = models.BooleanField(default=True) tags = models.JSONField(default=dict, blank=True, validators=[validate_tags]) + search_vector = SearchVectorField(null=True, editable=False) objects = DatastreamQuerySet.as_manager() @@ -77,6 +79,7 @@ class Meta: name="sta_datastream_tags_gin", opclasses=["jsonb_path_ops"], ), + GinIndex(fields=["search_vector"], name="sta_datastream_search_gin"), ] def __str__(self): diff --git a/django/core/sta/models/method.py b/django/core/sta/models/method.py index 1c5eb99e..99dd5b51 100644 --- a/django/core/sta/models/method.py +++ b/django/core/sta/models/method.py @@ -1,6 +1,8 @@ import uuid from django.db import models +from django.contrib.postgres.indexes import GinIndex +from django.contrib.postgres.search import SearchVectorField from core.iam.models import Workspace from core.iam.permissions.registry import register_resource_type @@ -28,10 +30,16 @@ class Method(models.Model): sensor_model_definition = models.CharField( max_length=500, null=True, blank=True ) + search_vector = SearchVectorField(null=True, editable=False) def __str__(self): return f"{self.name} — {self.id}" + class Meta: + indexes = [ + GinIndex(fields=["search_vector"], name="sta_method_search_gin"), + ] + class MethodType(models.Model): name = models.CharField(max_length=255, unique=True) diff --git a/django/core/sta/models/monitoring_site.py b/django/core/sta/models/monitoring_site.py index 846a45ac..60304bbe 100644 --- a/django/core/sta/models/monitoring_site.py +++ b/django/core/sta/models/monitoring_site.py @@ -2,6 +2,7 @@ from django.db import models from django.contrib.postgres.indexes import GinIndex +from django.contrib.postgres.search import SearchVectorField from django.conf import settings from core.iam.models import Workspace @@ -42,6 +43,7 @@ class MonitoringSite(models.Model): is_private = models.BooleanField(default=False) data_disclaimer = models.TextField(null=True, blank=True) tags = models.JSONField(default=dict, blank=True, validators=[validate_tags]) + search_vector = SearchVectorField(null=True, editable=False) objects = MonitoringSiteQuerySet.as_manager() @@ -52,6 +54,7 @@ class Meta: name="sta_monitoringsite_tags_gin", opclasses=["jsonb_path_ops"], ), + GinIndex(fields=["search_vector"], name="sta_monitoringsite_search_gin"), ] def __str__(self): diff --git a/django/core/sta/models/observed_property.py b/django/core/sta/models/observed_property.py index 768c1664..93a25971 100644 --- a/django/core/sta/models/observed_property.py +++ b/django/core/sta/models/observed_property.py @@ -1,6 +1,8 @@ import uuid from django.db import models +from django.contrib.postgres.indexes import GinIndex +from django.contrib.postgres.search import SearchVectorField from core.iam.models import Workspace from core.iam.permissions.registry import register_resource_type @@ -21,12 +23,16 @@ class ObservedProperty(models.Model): description = models.TextField() type = models.CharField(max_length=500) code = models.CharField(max_length=500) + search_vector = SearchVectorField(null=True, editable=False) def __str__(self): return f"{self.name} — {self.id}" class Meta: verbose_name_plural = "Observed properties" + indexes = [ + GinIndex(fields=["search_vector"], name="sta_obsproperty_search_gin"), + ] class VariableType(models.Model): diff --git a/django/core/sta/models/processing_level.py b/django/core/sta/models/processing_level.py index 7fce5260..6a3f5b23 100644 --- a/django/core/sta/models/processing_level.py +++ b/django/core/sta/models/processing_level.py @@ -1,6 +1,8 @@ import uuid from django.db import models +from django.contrib.postgres.indexes import GinIndex +from django.contrib.postgres.search import SearchVectorField from core.iam.models import Workspace from core.iam.permissions.registry import register_resource_type @@ -20,6 +22,12 @@ class ProcessingLevel(models.Model): name = models.CharField(max_length=255) description = models.TextField() definition = models.TextField(null=True, blank=True) + search_vector = SearchVectorField(null=True, editable=False) def __str__(self): return f"{self.name} — {self.id}" + + class Meta: + indexes = [ + GinIndex(fields=["search_vector"], name="sta_proclevel_search_gin"), + ] diff --git a/django/core/sta/models/result_qualifier.py b/django/core/sta/models/result_qualifier.py index 71f3e19b..9fd8abdf 100644 --- a/django/core/sta/models/result_qualifier.py +++ b/django/core/sta/models/result_qualifier.py @@ -1,6 +1,8 @@ import uuid from django.db import models +from django.contrib.postgres.indexes import GinIndex +from django.contrib.postgres.search import SearchVectorField from core.iam.models import Workspace from core.iam.permissions.registry import register_resource_type @@ -18,6 +20,7 @@ class ResultQualifier(models.Model): ) code = models.CharField(max_length=255) description = models.TextField() + search_vector = SearchVectorField(null=True, editable=False) def __str__(self): return f"{self.code} — {self.id}" @@ -30,3 +33,6 @@ class Meta: nulls_distinct=False, ), ] + indexes = [ + GinIndex(fields=["search_vector"], name="sta_resultqual_search_gin"), + ] diff --git a/django/core/sta/models/unit.py b/django/core/sta/models/unit.py index 5c4e4c2b..27f44b18 100644 --- a/django/core/sta/models/unit.py +++ b/django/core/sta/models/unit.py @@ -1,6 +1,8 @@ import uuid from django.db import models +from django.contrib.postgres.indexes import GinIndex +from django.contrib.postgres.search import SearchVectorField from core.iam.models import Workspace from core.iam.permissions.registry import register_resource_type @@ -20,10 +22,16 @@ class Unit(models.Model): symbol = models.CharField(max_length=255) definition = models.TextField(null=True, blank=True) type = models.CharField(max_length=255) + search_vector = SearchVectorField(null=True, editable=False) def __str__(self): return f"{self.name} — {self.id}" + class Meta: + indexes = [ + GinIndex(fields=["search_vector"], name="sta_unit_search_gin"), + ] + class UnitType(models.Model): name = models.CharField(max_length=255, unique=True) diff --git a/django/interfaces/api/schemas/__init__.py b/django/interfaces/api/schemas/__init__.py index 039cf43f..d552c26a 100644 --- a/django/interfaces/api/schemas/__init__.py +++ b/django/interfaces/api/schemas/__init__.py @@ -3,7 +3,7 @@ CreatedResponse, ItemResponse, split_comma_separated, comma_array_schema) from interfaces.api.schemas.iam.user import UserContactResponse from interfaces.api.schemas.iam.role import (RoleResponse, RoleQueryParameters, - RoleItemQueryParameters, RoleOrderByFields) + RoleItemQueryParameters, RoleSortByFields) from interfaces.api.schemas.iam.workspace import ( WorkspaceResponse, WorkspaceQueryParameters, diff --git a/django/interfaces/api/schemas/base.py b/django/interfaces/api/schemas/base.py index ad37d12e..c07e8a1e 100644 --- a/django/interfaces/api/schemas/base.py +++ b/django/interfaces/api/schemas/base.py @@ -125,7 +125,7 @@ class CollectionQueryParameters(BaseQueryParameters): class VocabularyQueryParameters(CollectionQueryParameters): - order_desc: Optional[bool] = Query( + sort_desc: Optional[bool] = Query( False, description="Sort terms by descending.", ) diff --git a/django/interfaces/api/schemas/etl/data_connection.py b/django/interfaces/api/schemas/etl/data_connection.py index ae193cb5..1c4711c2 100644 --- a/django/interfaces/api/schemas/etl/data_connection.py +++ b/django/interfaces/api/schemas/etl/data_connection.py @@ -35,7 +35,7 @@ } DataConnectionIncludeRelation = Literal[*DATA_CONNECTION_INCLUDE_RELATIONS.keys()] -_order_by_fields = ( +_sortby_fields = ( "id", "name", "timestampKey", @@ -46,8 +46,8 @@ "workspaceName", ) -DataConnectionOrderByFields = Literal[ - *_order_by_fields, *[f"-{f}" for f in _order_by_fields] +DataConnectionSortByFields = Literal[ + *_sortby_fields, *[f"-{f}" for f in _sortby_fields] ] _property_fields = ( @@ -94,8 +94,13 @@ class DataConnectionItemQueryParameters(DataConnectionFilterFields, BaseQueryPar class DataConnectionQueryParameters(DataConnectionFilterFields, CollectionQueryParameters): - order_by: Optional[list[DataConnectionOrderByFields]] = Query( - [], description="Select one or more fields to order the response by." + sortby: Optional[list[DataConnectionSortByFields]] = Query( + [], description="Select one or more fields to sort the response by." + ) + q: Optional[str] = Query( + None, + description="Full-text search query. Comma-separated terms are combined with OR; " + "whitespace-separated words within a term are combined with AND.", ) workspace: list[uuid.UUID] = Query( [], description="Filter data connections by workspace ID.", alias="workspace_id" diff --git a/django/interfaces/api/schemas/etl/mapping.py b/django/interfaces/api/schemas/etl/mapping.py index 0b9b89c1..0de2ef2b 100644 --- a/django/interfaces/api/schemas/etl/mapping.py +++ b/django/interfaces/api/schemas/etl/mapping.py @@ -22,9 +22,9 @@ class EtlMappingFields(Schema): target_datastream_id: uuid.UUID -_order_by_fields = ("id", "sourceIdentifier", "targetDatastreamId") +_sortby_fields = ("id", "sourceIdentifier", "targetDatastreamId") -EtlMappingOrderByFields = Literal[*_order_by_fields, *[f"-{f}" for f in _order_by_fields]] +EtlMappingSortByFields = Literal[*_sortby_fields, *[f"-{f}" for f in _sortby_fields]] _property_fields = ( "id", @@ -72,8 +72,8 @@ class EtlMappingItemQueryParameters(EtlMappingFilterFields, BaseQueryParameters) class EtlMappingQueryParameters(EtlMappingFilterFields, CollectionQueryParameters): - order_by: Optional[list[EtlMappingOrderByFields]] = Query( - [], description="Select one or more fields to order the response by." + sortby: Optional[list[EtlMappingSortByFields]] = Query( + [], description="Select one or more fields to sort the response by." ) source_identifier: list[str] = Query( [], description="Filter mappings by source identifier." diff --git a/django/interfaces/api/schemas/etl/task.py b/django/interfaces/api/schemas/etl/task.py index fd6f8b12..6586729b 100644 --- a/django/interfaces/api/schemas/etl/task.py +++ b/django/interfaces/api/schemas/etl/task.py @@ -40,7 +40,7 @@ class EtlTaskFields(Schema): } EtlTaskIncludeRelation = Literal[*ETL_TASK_INCLUDE_RELATIONS.keys()] -_order_by_fields = ( +_sortby_fields = ( "id", "name", "dataConnectionId", @@ -51,7 +51,7 @@ class EtlTaskFields(Schema): "latestRunStartedAt", "latestRunFinishedAt", ) -EtlTaskOrderByFields = Literal[*_order_by_fields, *[f"-{f}" for f in _order_by_fields]] +EtlTaskSortByFields = Literal[*_sortby_fields, *[f"-{f}" for f in _sortby_fields]] _property_fields = ( "id", @@ -90,8 +90,13 @@ class EtlTaskItemQueryParameters(EtlTaskFilterFields, BaseQueryParameters): class EtlTaskQueryParameters(EtlTaskFilterFields, CollectionQueryParameters): - order_by: Optional[list[EtlTaskOrderByFields]] = Query( - [], description="Select one or more fields to order the response by." + sortby: Optional[list[EtlTaskSortByFields]] = Query( + [], description="Select one or more fields to sort the response by." + ) + q: Optional[str] = Query( + None, + description="Full-text search query. Comma-separated terms are combined with OR; " + "whitespace-separated words within a term are combined with AND.", ) monitoring_site_id: list[uuid.UUID] = Query( [], description="Filter ETL tasks by monitoring_site ID." diff --git a/django/interfaces/api/schemas/iam/collaborator.py b/django/interfaces/api/schemas/iam/collaborator.py index ec5e192a..ff679cdc 100644 --- a/django/interfaces/api/schemas/iam/collaborator.py +++ b/django/interfaces/api/schemas/iam/collaborator.py @@ -48,6 +48,9 @@ _property_fields = ("roleId", "userEmail", "serviceAccountEmail") CollaboratorPropertyName = Literal[*_property_fields] +_sortby_fields = ("id", "roleId") +CollaboratorSortByFields = Literal[*_sortby_fields, *[f"-{f}" for f in _sortby_fields]] + class CollaboratorFilterFields(Schema): properties: Annotated[ @@ -71,6 +74,9 @@ class CollaboratorFilterFields(Schema): class CollaboratorQueryParameters(CollaboratorFilterFields, CollectionQueryParameters): role_id: list[uuid.UUID] = Query([], description="Filter collaborators by role ID.") + sortby: Optional[list[CollaboratorSortByFields]] = Query( + [], description="Select one or more fields to sort the response by." + ) class CollaboratorResponse(BaseGetResponse): diff --git a/django/interfaces/api/schemas/iam/role.py b/django/interfaces/api/schemas/iam/role.py index 3417a7d6..482a9a72 100644 --- a/django/interfaces/api/schemas/iam/role.py +++ b/django/interfaces/api/schemas/iam/role.py @@ -29,8 +29,8 @@ class PermissionResponse(BaseGetResponse): permission_type: PERMISSIONS = Field(..., alias="action") -_order_by_fields = ("name",) -RoleOrderByFields = Literal[*_order_by_fields, *[f"-{f}" for f in _order_by_fields]] +_sortby_fields = ("name",) +RoleSortByFields = Literal[*_sortby_fields, *[f"-{f}" for f in _sortby_fields]] _property_fields = ( "id", @@ -58,8 +58,8 @@ class RoleItemQueryParameters(RoleFilterFields, BaseQueryParameters): class RoleQueryParameters(RoleFilterFields, CollectionQueryParameters): - order_by: Optional[list[RoleOrderByFields]] = Query( - [], description="Select one or more fields to order the response by." + sortby: Optional[list[RoleSortByFields]] = Query( + [], description="Select one or more fields to sort the response by." ) workspace_id: list[uuid.UUID | Literal["null"]] = Query( [], description="Filter roles by workspace ID." diff --git a/django/interfaces/api/schemas/iam/service_account.py b/django/interfaces/api/schemas/iam/service_account.py index bc6e4903..9f51cb4e 100644 --- a/django/interfaces/api/schemas/iam/service_account.py +++ b/django/interfaces/api/schemas/iam/service_account.py @@ -42,13 +42,15 @@ class ServiceAccountFields(ServiceAccountInputFields): ServiceAccountIncludeRelation = Literal[*SERVICE_ACCOUNT_INCLUDE_RELATIONS.keys()] -_order_by_fields = ( +_sortby_fields = ( "name", "isActive", "keyExpiresAt", + "createdAt", + "lastUsedAt", ) -ServiceAccountOrderByFields = Literal[ - *_order_by_fields, *[f"-{f}" for f in _order_by_fields] +ServiceAccountSortByFields = Literal[ + *_sortby_fields, *[f"-{f}" for f in _sortby_fields] ] @@ -85,8 +87,8 @@ class ServiceAccountItemQueryParameters(ServiceAccountFilterFields, BaseQueryPar class ServiceAccountQueryParameters(ServiceAccountFilterFields, CollectionQueryParameters): - order_by: Optional[list[ServiceAccountOrderByFields]] = Query( - [], description="Select one or more fields to order the response by." + sortby: Optional[list[ServiceAccountSortByFields]] = Query( + [], description="Select one or more fields to sort the response by." ) diff --git a/django/interfaces/api/schemas/iam/workspace.py b/django/interfaces/api/schemas/iam/workspace.py index ffffeaba..5709811b 100644 --- a/django/interfaces/api/schemas/iam/workspace.py +++ b/django/interfaces/api/schemas/iam/workspace.py @@ -42,12 +42,12 @@ class WorkspaceFields(Schema): } WorkspaceIncludeRelation = Literal[*WORKSPACE_INCLUDE_RELATIONS.keys()] -_order_by_fields = ( +_sortby_fields = ( "name", "isPrivate", ) -WorkspaceOrderByFields = Literal[ - *_order_by_fields, *[f"-{f}" for f in _order_by_fields] +WorkspaceSortByFields = Literal[ + *_sortby_fields, *[f"-{f}" for f in _sortby_fields] ] _property_fields = ( @@ -85,8 +85,13 @@ class WorkspaceItemQueryParameters(WorkspaceFilterFields, BaseQueryParameters): class WorkspaceQueryParameters(WorkspaceFilterFields, CollectionQueryParameters): - order_by: Optional[list[WorkspaceOrderByFields]] = Query( - [], description="Select one or more fields to order the response by." + sortby: Optional[list[WorkspaceSortByFields]] = Query( + [], description="Select one or more fields to sort the response by." + ) + q: Optional[str] = Query( + None, + description="Full-text search query. Comma-separated terms are combined with OR; " + "whitespace-separated words within a term are combined with AND.", ) is_associated: Optional[bool] = Query( None, diff --git a/django/interfaces/api/schemas/monitoring/rule.py b/django/interfaces/api/schemas/monitoring/rule.py index fda2d93e..c0c2b235 100644 --- a/django/interfaces/api/schemas/monitoring/rule.py +++ b/django/interfaces/api/schemas/monitoring/rule.py @@ -37,9 +37,9 @@ class MonitoringRuleFields(Schema): } MonitoringRuleIncludeRelation = Literal[*MONITORING_RULE_INCLUDE_RELATIONS.keys()] -_order_by_fields = ("id", "ruleType", "datastreamId") -MonitoringRuleOrderByFields = Literal[ - *_order_by_fields, *[f"-{f}" for f in _order_by_fields] +_sortby_fields = ("id", "ruleType", "datastreamId", "lastCheckedAt") +MonitoringRuleSortByFields = Literal[ + *_sortby_fields, *[f"-{f}" for f in _sortby_fields] ] _property_fields = ( @@ -77,8 +77,8 @@ class MonitoringRuleItemQueryParameters(MonitoringRuleFilterFields, BaseQueryPar class MonitoringRuleQueryParameters(MonitoringRuleFilterFields, CollectionQueryParameters): - order_by: Optional[list[MonitoringRuleOrderByFields]] = Query( - [], description="Select one or more fields to order the response by." + sortby: Optional[list[MonitoringRuleSortByFields]] = Query( + [], description="Select one or more fields to sort the response by." ) datastream_id: list[uuid.UUID] = Query( [], description="Filter rules by datastream ID." diff --git a/django/interfaces/api/schemas/monitoring/task.py b/django/interfaces/api/schemas/monitoring/task.py index 32b126a6..99954848 100644 --- a/django/interfaces/api/schemas/monitoring/task.py +++ b/django/interfaces/api/schemas/monitoring/task.py @@ -39,7 +39,7 @@ class MonitoringTaskFields(Schema): } MonitoringTaskIncludeRelation = Literal[*MONITORING_TASK_INCLUDE_RELATIONS.keys()] -_order_by_fields = ( +_sortby_fields = ( "id", "name", "monitoringSiteId", @@ -50,8 +50,8 @@ class MonitoringTaskFields(Schema): "latestRunStartedAt", "latestRunFinishedAt", ) -MonitoringTaskOrderByFields = Literal[ - *_order_by_fields, *[f"-{f}" for f in _order_by_fields] +MonitoringTaskSortByFields = Literal[ + *_sortby_fields, *[f"-{f}" for f in _sortby_fields] ] _property_fields = ( @@ -92,8 +92,13 @@ class MonitoringTaskItemQueryParameters(MonitoringTaskFilterFields, BaseQueryPar class MonitoringTaskQueryParameters(MonitoringTaskFilterFields, CollectionQueryParameters): - order_by: Optional[list[MonitoringTaskOrderByFields]] = Query( - [], description="Select one or more fields to order the response by." + sortby: Optional[list[MonitoringTaskSortByFields]] = Query( + [], description="Select one or more fields to sort the response by." + ) + q: Optional[str] = Query( + None, + description="Full-text search query. Comma-separated terms are combined with OR; " + "whitespace-separated words within a term are combined with AND.", ) monitoring_site: list[uuid.UUID] = Query( [], description="Filter monitoring tasks by monitoring_site ID.", alias="monitoring_site_id" diff --git a/django/interfaces/api/schemas/orchestration/run.py b/django/interfaces/api/schemas/orchestration/run.py index f6cc36c1..807c3e9d 100644 --- a/django/interfaces/api/schemas/orchestration/run.py +++ b/django/interfaces/api/schemas/orchestration/run.py @@ -14,9 +14,9 @@ ) -_order_by_fields = ("id", "status", "startedAt", "finishedAt") -TaskRunOrderByFields = Literal[ - *_order_by_fields, *[f"-{f}" for f in _order_by_fields] +_sortby_fields = ("id", "status", "startedAt", "finishedAt") +TaskRunSortByFields = Literal[ + *_sortby_fields, *[f"-{f}" for f in _sortby_fields] ] TaskRunPropertyName = Literal[ @@ -34,8 +34,8 @@ class TaskRunQueryParameters(CollectionQueryParameters): description="Comma-separated list of properties to include in the response. " "All properties are returned if omitted.", ) - order_by: list[TaskRunOrderByFields] = Query( - [], description="Select one or more fields to order the response by." + sortby: list[TaskRunSortByFields] = Query( + [], description="Select one or more fields to sort the response by." ) status: list[Literal["PENDING", "STARTED", "SUCCESS", "FAILURE"]] = Query( [], description="Filters task runs by their status." diff --git a/django/interfaces/api/schemas/products/rating_curve.py b/django/interfaces/api/schemas/products/rating_curve.py index 4e0c183a..ee71173c 100644 --- a/django/interfaces/api/schemas/products/rating_curve.py +++ b/django/interfaces/api/schemas/products/rating_curve.py @@ -36,16 +36,17 @@ class RatingCurveFields(Schema): } RatingCurveIncludeRelation = Literal[*RATING_CURVE_INCLUDE_RELATIONS.keys()] -_order_by_fields = ( +_sortby_fields = ( "id", "name", "monitoringSiteId", "monitoringSiteName", "workspaceId", "workspaceName", + "fittingMethod", ) -RatingCurveOrderByFields = Literal[ - *_order_by_fields, *[f"-{f}" for f in _order_by_fields] +RatingCurveSortByFields = Literal[ + *_sortby_fields, *[f"-{f}" for f in _sortby_fields] ] _property_fields = ( @@ -81,8 +82,13 @@ class RatingCurveItemQueryParameters(RatingCurveFilterFields, BaseQueryParameter class RatingCurveQueryParameters(RatingCurveFilterFields, CollectionQueryParameters): - order_by: Optional[list[RatingCurveOrderByFields]] = Query( - [], description="Select one or more fields to order the response by." + sortby: Optional[list[RatingCurveSortByFields]] = Query( + [], description="Select one or more fields to sort the response by." + ) + q: Optional[str] = Query( + None, + description="Full-text search query. Comma-separated terms are combined with OR; " + "whitespace-separated words within a term are combined with AND.", ) monitoring_site_id: list[uuid.UUID] = Query( [], description="Filter rating curves by monitoring site ID." diff --git a/django/interfaces/api/schemas/products/task.py b/django/interfaces/api/schemas/products/task.py index 2bdce8b4..6d5139df 100644 --- a/django/interfaces/api/schemas/products/task.py +++ b/django/interfaces/api/schemas/products/task.py @@ -39,7 +39,7 @@ class DataProductTaskFields(Schema): } DataProductTaskIncludeRelation = Literal[*DATA_PRODUCT_TASK_INCLUDE_RELATIONS.keys()] -_order_by_fields = ( +_sortby_fields = ( "id", "name", "monitoringSiteId", @@ -50,8 +50,8 @@ class DataProductTaskFields(Schema): "latestRunStartedAt", "latestRunFinishedAt", ) -DataProductTaskOrderByFields = Literal[ - *_order_by_fields, *[f"-{f}" for f in _order_by_fields] +DataProductTaskSortByFields = Literal[ + *_sortby_fields, *[f"-{f}" for f in _sortby_fields] ] _property_fields = ( @@ -91,8 +91,13 @@ class DataProductTaskItemQueryParameters(DataProductTaskFilterFields, BaseQueryP class DataProductTaskQueryParameters(DataProductTaskFilterFields, CollectionQueryParameters): - order_by: Optional[list[DataProductTaskOrderByFields]] = Query( - [], description="Select one or more fields to order the response by." + sortby: Optional[list[DataProductTaskSortByFields]] = Query( + [], description="Select one or more fields to sort the response by." + ) + q: Optional[str] = Query( + None, + description="Full-text search query. Comma-separated terms are combined with OR; " + "whitespace-separated words within a term are combined with AND.", ) monitoring_site: list[uuid.UUID] = Query( [], description="Filter data product tasks by monitoring_site ID.", alias="monitoring_site_id" diff --git a/django/interfaces/api/schemas/products/transformation.py b/django/interfaces/api/schemas/products/transformation.py index 3f06fe84..a13eec50 100644 --- a/django/interfaces/api/schemas/products/transformation.py +++ b/django/interfaces/api/schemas/products/transformation.py @@ -65,9 +65,9 @@ class DataProductTransformationFields(Schema): *DATA_PRODUCT_TRANSFORMATION_INCLUDE_RELATIONS.keys() ] -_order_by_fields = ("id", "outputDatastreamId") -DataProductTransformationOrderByFields = Literal[ - *_order_by_fields, *[f"-{f}" for f in _order_by_fields] +_sortby_fields = ("id", "outputDatastreamId", "transformationType") +DataProductTransformationSortByFields = Literal[ + *_sortby_fields, *[f"-{f}" for f in _sortby_fields] ] _property_fields = ( @@ -107,8 +107,8 @@ class DataProductTransformationItemQueryParameters( class DataProductTransformationQueryParameters( DataProductTransformationFilterFields, CollectionQueryParameters ): - order_by: Optional[list[DataProductTransformationOrderByFields]] = Query( - [], description="Select one or more fields to order the response by." + sortby: Optional[list[DataProductTransformationSortByFields]] = Query( + [], description="Select one or more fields to sort the response by." ) transformation_type: list[str] = Query( [], description="Filter transformations by type." diff --git a/django/interfaces/api/schemas/quality/history.py b/django/interfaces/api/schemas/quality/history.py index c050e0d4..dad50d16 100644 --- a/django/interfaces/api/schemas/quality/history.py +++ b/django/interfaces/api/schemas/quality/history.py @@ -32,9 +32,9 @@ *QUALITY_CONTROL_HISTORY_INCLUDE_RELATIONS.keys() ] -_order_by_fields = ("id", "createdAt", "phenomenonTimeStart", "phenomenonTimeEnd") -QualityControlHistoryOrderByFields = Literal[ - *_order_by_fields, *[f"-{f}" for f in _order_by_fields] +_sortby_fields = ("id", "createdAt", "phenomenonTimeStart", "phenomenonTimeEnd") +QualityControlHistorySortByFields = Literal[ + *_sortby_fields, *[f"-{f}" for f in _sortby_fields] ] _property_fields = ( @@ -79,8 +79,8 @@ class QualityControlHistoryItemQueryParameters( class QualityControlHistoryQueryParameters( QualityControlHistoryFilterFields, CollectionQueryParameters ): - order_by: Optional[list[QualityControlHistoryOrderByFields]] = Query( - [], description="Select one or more fields to order the response by." + sortby: Optional[list[QualityControlHistorySortByFields]] = Query( + [], description="Select one or more fields to sort the response by." ) managed_datastream_id: list[uuid.UUID] = Query( [], description="Filter histories by managed datastream ID." diff --git a/django/interfaces/api/schemas/quality/operation.py b/django/interfaces/api/schemas/quality/operation.py index 8a20d6a7..74ee4d81 100644 --- a/django/interfaces/api/schemas/quality/operation.py +++ b/django/interfaces/api/schemas/quality/operation.py @@ -36,9 +36,9 @@ "ASSIGN_DATETIMES_BULK", ] -_order_by_fields = ("id", "order", "operationType", "createdAt") -QualityControlOperationOrderByFields = Literal[ - *_order_by_fields, *[f"-{f}" for f in _order_by_fields] +_sortby_fields = ("id", "order", "operationType", "createdAt") +QualityControlOperationSortByFields = Literal[ + *_sortby_fields, *[f"-{f}" for f in _sortby_fields] ] _property_fields = ( @@ -70,8 +70,8 @@ class QualityControlOperationItemQueryParameters(QualityControlOperationFilterFi class QualityControlOperationQueryParameters(QualityControlOperationFilterFields, CollectionQueryParameters): - order_by: Optional[list[QualityControlOperationOrderByFields]] = Query( - [], description="Select one or more fields to order the response by." + sortby: Optional[list[QualityControlOperationSortByFields]] = Query( + [], description="Select one or more fields to sort the response by." ) diff --git a/django/interfaces/api/schemas/quality/session.py b/django/interfaces/api/schemas/quality/session.py index be45c687..5dfcfeba 100644 --- a/django/interfaces/api/schemas/quality/session.py +++ b/django/interfaces/api/schemas/quality/session.py @@ -19,7 +19,7 @@ SessionStatus = Literal["in_progress", "committed"] -_order_by_fields = ( +_sortby_fields = ( "id", "createdAt", "phenomenonTimeStart", @@ -27,8 +27,8 @@ "status", "committedAt", ) -QualityControlSessionOrderByFields = Literal[ - *_order_by_fields, *[f"-{f}" for f in _order_by_fields] +QualityControlSessionSortByFields = Literal[ + *_sortby_fields, *[f"-{f}" for f in _sortby_fields] ] _property_fields = ( @@ -65,8 +65,8 @@ class QualityControlSessionItemQueryParameters(QualityControlSessionFilterFields class QualityControlSessionQueryParameters(QualityControlSessionFilterFields, CollectionQueryParameters): - order_by: Optional[list[QualityControlSessionOrderByFields]] = Query( - [], description="Select one or more fields to order the response by." + sortby: Optional[list[QualityControlSessionSortByFields]] = Query( + [], description="Select one or more fields to sort the response by." ) status: Optional[SessionStatus] = None range_start: Optional[ISODatetime] = Query(None, description="Return sessions overlapping with this range start.") diff --git a/django/interfaces/api/schemas/sta/datastream.py b/django/interfaces/api/schemas/sta/datastream.py index c733be6f..095cc46e 100644 --- a/django/interfaces/api/schemas/sta/datastream.py +++ b/django/interfaces/api/schemas/sta/datastream.py @@ -91,21 +91,23 @@ class DatastreamRelatedFields(Schema): } DatastreamIncludeRelation = Literal[*DATASTREAM_INCLUDE_RELATIONS.keys()] -_order_by_fields = ( +_sortby_fields = ( "name", "observationType", "sampledMedium", "status", "resultType", "isPrivate", + "isVisible", + "aggregationStatistic", "valueCount", "phenomenonBeginTime", "phenomenonEndTime", "resultBeginTime", "resultEndTime", ) -DatastreamOrderByFields = Literal[ - *_order_by_fields, *[f"-{f}" for f in _order_by_fields] +DatastreamSortByFields = Literal[ + *_sortby_fields, *[f"-{f}" for f in _sortby_fields] ] _property_fields = ( @@ -144,8 +146,13 @@ class DatastreamItemQueryParameters(DatastreamFilterFields, BaseQueryParameters) class DatastreamQueryParameters(DatastreamFilterFields, CollectionQueryParameters): - order_by: Optional[list[DatastreamOrderByFields]] = Query( - [], description="Select one or more fields to order the response by." + sortby: Optional[list[DatastreamSortByFields]] = Query( + [], description="Select one or more fields to sort the response by." + ) + q: Optional[str] = Query( + None, + description="Full-text search query. Comma-separated terms are combined with OR; " + "whitespace-separated words within a term are combined with AND.", ) monitoring_site__workspace_id: list[uuid.UUID] = Query( [], description="Filter datastreams by workspace ID.", alias="workspace_id" diff --git a/django/interfaces/api/schemas/sta/method.py b/django/interfaces/api/schemas/sta/method.py index 915c9256..c067f0f9 100644 --- a/django/interfaces/api/schemas/sta/method.py +++ b/django/interfaces/api/schemas/sta/method.py @@ -37,14 +37,16 @@ class MethodFields(Schema): } MethodIncludeRelation = Literal[*METHOD_INCLUDE_RELATIONS.keys()] -_order_by_fields = ( +_sortby_fields = ( "name", "code", "type", "sensorModel", "sensorModelManufacturer", + "definition", + "sensorModelDefinition", ) -MethodOrderByFields = Literal[*_order_by_fields, *[f"-{f}" for f in _order_by_fields]] +MethodSortByFields = Literal[*_sortby_fields, *[f"-{f}" for f in _sortby_fields]] _property_fields = ("id", "workspaceId", *(to_camel(name) for name in MethodFields.model_fields)) MethodPropertyName = Literal[*_property_fields] @@ -75,8 +77,13 @@ class MethodItemQueryParameters(MethodFilterFields, BaseQueryParameters): class MethodQueryParameters(MethodFilterFields, CollectionQueryParameters): - order_by: Optional[list[MethodOrderByFields]] = Query( - [], description="Select one or more fields to order the response by." + sortby: Optional[list[MethodSortByFields]] = Query( + [], description="Select one or more fields to sort the response by." + ) + q: Optional[str] = Query( + None, + description="Full-text search query. Comma-separated terms are combined with OR; " + "whitespace-separated words within a term are combined with AND.", ) workspace_id: list[uuid.UUID | Literal["null"]] = Query( [], description="Filter methods by workspace ID." diff --git a/django/interfaces/api/schemas/sta/monitoring_site.py b/django/interfaces/api/schemas/sta/monitoring_site.py index b0aa8a52..84740b51 100644 --- a/django/interfaces/api/schemas/sta/monitoring_site.py +++ b/django/interfaces/api/schemas/sta/monitoring_site.py @@ -60,7 +60,7 @@ def check_country_code(cls, value): } MonitoringSiteIncludeRelation = Literal[*MONITORING_SITE_INCLUDE_RELATIONS.keys()] -_order_by_fields = ( +_sortby_fields = ( "name", "code", "type", @@ -73,8 +73,8 @@ def check_country_code(cls, value): "adminArea2", "country", ) -MonitoringSiteOrderByFields = Literal[ - *_order_by_fields, *[f"-{field}" for field in _order_by_fields] +MonitoringSiteSortByFields = Literal[ + *_sortby_fields, *[f"-{field}" for field in _sortby_fields] ] _property_fields = ( @@ -115,8 +115,13 @@ class MonitoringSiteItemQueryParameters(MonitoringSiteFilterFields, BaseQueryPar class MonitoringSiteQueryParameters(MonitoringSiteFilterFields, CollectionQueryParameters): - order_by: Optional[list[MonitoringSiteOrderByFields]] = Query( - [], description="Select one or more fields to order the response by." + sortby: Optional[list[MonitoringSiteSortByFields]] = Query( + [], description="Select one or more fields to sort the response by." + ) + q: Optional[str] = Query( + None, + description="Full-text search query. Comma-separated terms are combined with OR; " + "whitespace-separated words within a term are combined with AND.", ) workspace_id: list[uuid.UUID] = Query( [], description="Filter monitoring sites by workspace ID." diff --git a/django/interfaces/api/schemas/sta/observation.py b/django/interfaces/api/schemas/sta/observation.py index da896719..99962b65 100644 --- a/django/interfaces/api/schemas/sta/observation.py +++ b/django/interfaces/api/schemas/sta/observation.py @@ -40,9 +40,9 @@ class ObservationFields(Schema): } ObservationIncludeRelation = Literal[*OBSERVATION_INCLUDE_RELATIONS.keys()] -_order_by_fields = ("phenomenonTime", "datastreamId") -ObservationOrderByFields = Literal[ - *_order_by_fields, *[f"-{f}" for f in _order_by_fields] +_sortby_fields = ("phenomenonTime", "datastreamId") +ObservationSortByFields = Literal[ + *_sortby_fields, *[f"-{f}" for f in _sortby_fields] ] _property_fields = ( @@ -83,8 +83,8 @@ class ObservationQueryParameters(ObservationFilterFields, CollectionQueryParamet datastream_id: list[uuid.UUID] = Query( [], description="Filter observations by datastream ID." ) - order_by: Optional[list[ObservationOrderByFields]] = Query( - [], description="Select one or more fields to order the response by." + sortby: Optional[list[ObservationSortByFields]] = Query( + [], description="Select one or more fields to sort the response by." ) response_format: Optional[Literal["record", "row", "column"]] = Query( None, diff --git a/django/interfaces/api/schemas/sta/observed_property.py b/django/interfaces/api/schemas/sta/observed_property.py index 513bf16d..7f357ef4 100644 --- a/django/interfaces/api/schemas/sta/observed_property.py +++ b/django/interfaces/api/schemas/sta/observed_property.py @@ -34,13 +34,13 @@ class ObservedPropertyFields(Schema): } ObservedPropertyIncludeRelation = Literal[*OBSERVED_PROPERTY_INCLUDE_RELATIONS.keys()] -_order_by_fields = ( +_sortby_fields = ( "name", "type", "code", ) -ObservedPropertyOrderByFields = Literal[ - *_order_by_fields, *[f"-{f}" for f in _order_by_fields] +ObservedPropertySortByFields = Literal[ + *_sortby_fields, *[f"-{f}" for f in _sortby_fields] ] _property_fields = ( @@ -74,8 +74,13 @@ class ObservedPropertyItemQueryParameters(ObservedPropertyFilterFields, BaseQuer class ObservedPropertyQueryParameters(ObservedPropertyFilterFields, CollectionQueryParameters): - order_by: Optional[list[ObservedPropertyOrderByFields]] = Query( - [], description="Select one or more fields to order the response by." + sortby: Optional[list[ObservedPropertySortByFields]] = Query( + [], description="Select one or more fields to sort the response by." + ) + q: Optional[str] = Query( + None, + description="Full-text search query. Comma-separated terms are combined with OR; " + "whitespace-separated words within a term are combined with AND.", ) workspace_id: list[uuid.UUID | Literal["null"]] = Query( [], description="Filter observed properties by workspace ID." diff --git a/django/interfaces/api/schemas/sta/processing_level.py b/django/interfaces/api/schemas/sta/processing_level.py index 6a6c17d6..5d5cbac7 100644 --- a/django/interfaces/api/schemas/sta/processing_level.py +++ b/django/interfaces/api/schemas/sta/processing_level.py @@ -33,9 +33,9 @@ class ProcessingLevelFields(Schema): } ProcessingLevelIncludeRelation = Literal[*PROCESSING_LEVEL_INCLUDE_RELATIONS.keys()] -_order_by_fields = ("code", "name") -ProcessingLevelOrderByFields = Literal[ - *_order_by_fields, *[f"-{f}" for f in _order_by_fields] +_sortby_fields = ("code", "name") +ProcessingLevelSortByFields = Literal[ + *_sortby_fields, *[f"-{f}" for f in _sortby_fields] ] _property_fields = ( @@ -69,8 +69,13 @@ class ProcessingLevelItemQueryParameters(ProcessingLevelFilterFields, BaseQueryP class ProcessingLevelQueryParameters(ProcessingLevelFilterFields, CollectionQueryParameters): - order_by: Optional[list[ProcessingLevelOrderByFields]] = Query( - [], description="Select one or more fields to order the response by." + sortby: Optional[list[ProcessingLevelSortByFields]] = Query( + [], description="Select one or more fields to sort the response by." + ) + q: Optional[str] = Query( + None, + description="Full-text search query. Comma-separated terms are combined with OR; " + "whitespace-separated words within a term are combined with AND.", ) workspace_id: list[uuid.UUID | Literal["null"]] = Query( [], description="Filter processing levels by workspace ID." diff --git a/django/interfaces/api/schemas/sta/result_qualifier.py b/django/interfaces/api/schemas/sta/result_qualifier.py index 9624d896..d941cb85 100644 --- a/django/interfaces/api/schemas/sta/result_qualifier.py +++ b/django/interfaces/api/schemas/sta/result_qualifier.py @@ -31,9 +31,9 @@ class ResultQualifierFields(Schema): } ResultQualifierIncludeRelation = Literal[*RESULT_QUALIFIER_INCLUDE_RELATIONS.keys()] -_order_by_fields = ("code",) -ResultQualifierOrderByFields = Literal[ - *_order_by_fields, *[f"-{f}" for f in _order_by_fields] +_sortby_fields = ("code",) +ResultQualifierSortByFields = Literal[ + *_sortby_fields, *[f"-{f}" for f in _sortby_fields] ] _property_fields = ( @@ -67,8 +67,13 @@ class ResultQualifierItemQueryParameters(ResultQualifierFilterFields, BaseQueryP class ResultQualifierQueryParameters(ResultQualifierFilterFields, CollectionQueryParameters): - order_by: Optional[list[ResultQualifierOrderByFields]] = Query( - [], description="Select one or more fields to order the response by." + sortby: Optional[list[ResultQualifierSortByFields]] = Query( + [], description="Select one or more fields to sort the response by." + ) + q: Optional[str] = Query( + None, + description="Full-text search query. Comma-separated terms are combined with OR; " + "whitespace-separated words within a term are combined with AND.", ) workspace_id: list[uuid.UUID | Literal["null"]] = Query( [], description="Filter result qualifiers by workspace ID." diff --git a/django/interfaces/api/schemas/sta/unit.py b/django/interfaces/api/schemas/sta/unit.py index b65160d6..7135132d 100644 --- a/django/interfaces/api/schemas/sta/unit.py +++ b/django/interfaces/api/schemas/sta/unit.py @@ -33,12 +33,12 @@ class UnitFields(Schema): } UnitIncludeRelation = Literal[*UNIT_INCLUDE_RELATIONS.keys()] -_order_by_fields = ( +_sortby_fields = ( "name", "symbol", "type", ) -UnitOrderByFields = Literal[*_order_by_fields, *[f"-{f}" for f in _order_by_fields]] +UnitSortByFields = Literal[*_sortby_fields, *[f"-{f}" for f in _sortby_fields]] _property_fields = ("id", "workspaceId", *(to_camel(name) for name in UnitFields.model_fields)) UnitPropertyName = Literal[*_property_fields] @@ -69,8 +69,13 @@ class UnitItemQueryParameters(UnitFilterFields, BaseQueryParameters): class UnitQueryParameters(UnitFilterFields, CollectionQueryParameters): - order_by: Optional[list[UnitOrderByFields]] = Query( - [], description="Select one or more fields to order the response by." + sortby: Optional[list[UnitSortByFields]] = Query( + [], description="Select one or more fields to sort the response by." + ) + q: Optional[str] = Query( + None, + description="Full-text search query. Comma-separated terms are combined with OR; " + "whitespace-separated words within a term are combined with AND.", ) workspace_id: list[uuid.UUID | Literal["null"]] = Query( [], description="Filter units by workspace ID." diff --git a/django/interfaces/api/service.py b/django/interfaces/api/service.py index 37881299..9c275f27 100644 --- a/django/interfaces/api/service.py +++ b/django/interfaces/api/service.py @@ -1,12 +1,15 @@ import json import uuid + from typing import Union, Any, Optional, Type from pydantic.alias_generators import to_snake from django.conf import settings from django.contrib.auth import get_user_model +from django.contrib.postgres.search import SearchQuery, SearchRank from django.core.exceptions import EmptyResultSet from django.db import connection -from django.db.models import QuerySet, Model, Q +from django.db.models import QuerySet, Model, Q, F + from core.iam.models import Workspace, ServiceAccount from core.iam.permissions.anonymous import AnonymousPrincipal from interfaces.api.http.errors import BadRequestError, NotFoundError @@ -16,6 +19,8 @@ class APIService: + SEARCH_CONFIG = "english" + @staticmethod def get_workspace( principal: Union[User, ServiceAccount, AnonymousPrincipal], @@ -77,38 +82,73 @@ def apply_filters(queryset: QuerySet, field_name: str, values: Optional[Any]): return queryset.filter(**{field_name: values}) @staticmethod - def apply_ordering( + def apply_sorting( queryset: QuerySet, - order_by: list[str], + sortby: list[str], allowed_fields: list[str], field_aliases: Optional[dict[str, str]] = None, + default_sortby: tuple[str, ...] = ("id",), + rank: bool = False, ): - order_by_fields = [] field_aliases = field_aliases or {} - stripped_fields = [field.lstrip("-") for field in order_by] - if len(stripped_fields) != len(set(stripped_fields)): - raise BadRequestError("Fields cannot be repeated in order_by arguments") - - for field in order_by: - if field not in allowed_fields: - raise BadRequestError(f"Response cannot be ordered by field '{field}'") - descending = field.startswith("-") - stripped_field = field.lstrip("-") - resolved_field = field_aliases.get(stripped_field, to_snake(stripped_field)) - order_by_fields.append(f"-{resolved_field}" if descending else resolved_field) - - # Requested fields (e.g. "name") are rarely unique, so rows that tie on - # them have no guaranteed relative order. Since results are fetched a - # page at a time via separate queries (see paginatedFetch on the - # client), an unstable tie order lets rows shift between pages and - # silently drop out of every page. Appending the primary key as a - # final tiebreaker makes the ordering - and therefore pagination - - # deterministic. - if "id" not in stripped_fields: - order_by_fields.append("id") - - return queryset.order_by(*order_by_fields) + if not sortby: + sortby_fields = [] if rank else list(default_sortby) + else: + stripped_fields = [field.lstrip("-") for field in sortby] + if len(stripped_fields) != len(set(stripped_fields)): + raise BadRequestError("Fields cannot be repeated in sortby arguments") + + sortby_fields = [] + for field in sortby: + if field not in allowed_fields: + raise BadRequestError(f"Response cannot be sorted by field '{field}'") + descending = field.startswith("-") + stripped_field = field.lstrip("-") + resolved_field = field_aliases.get(stripped_field, to_snake(stripped_field)) + sortby_fields.append(f"-{resolved_field}" if descending else resolved_field) + + if rank: + sortby_fields.append("-rank") + + stripped_sortby_fields = [field.lstrip("-") for field in sortby_fields] + if "id" not in stripped_sortby_fields: + sortby_fields.append("id") + + return queryset.order_by(*sortby_fields) + + @classmethod + def apply_search( + cls, queryset: QuerySet, q: Optional[str], field: str = "search_vector" + ) -> tuple[QuerySet, bool]: + """ + Filters a queryset by the `q` full-text search parameter and annotates a `rank` + field for relevance ordering. + + Comma-separated terms are combined with OR; the whitespace-separated words within + each term are combined with AND. Matching is case-insensitive. + + Returns the (possibly unchanged) queryset and whether search was applied, so + callers can pass that through to `apply_sorting`'s `rank` argument. + """ + + if not q or not q.strip(): + return queryset, False + + groups = [group.strip() for group in q.split(",") if group.strip()] + if not groups: + return queryset, False + + search_query = None + for group in groups: + group_query = SearchQuery(group, config=cls.SEARCH_CONFIG, search_type="plain") + search_query = group_query if search_query is None else search_query | group_query + + queryset = queryset.annotate(rank=SearchRank(F(field), search_query)).filter( + **{field: search_query} + ) + + return queryset, True @staticmethod def resolve_include_set(include: Optional[list[str]]) -> set[str]: @@ -306,13 +346,13 @@ def list( vocabulary_model: Type[Model], offset: Optional[int] = None, limit: Optional[int] = None, - order_desc: bool = False, + sort_desc: bool = False, ): queryset = vocabulary_model.objects - queryset = self.apply_ordering( + queryset = self.apply_sorting( queryset, - ["-name"] if order_desc else ["name"], + ["-name"] if sort_desc else ["name"], [ "name", ], diff --git a/django/interfaces/api/services/etl/data_connection.py b/django/interfaces/api/services/etl/data_connection.py index 88b63718..7d0e74a6 100644 --- a/django/interfaces/api/services/etl/data_connection.py +++ b/django/interfaces/api/services/etl/data_connection.py @@ -9,7 +9,6 @@ from django.db.models.functions import Coalesce from django.db.models.query import QuerySet from django.contrib.auth import get_user_model -from django.contrib.postgres.search import SearchVector, SearchQuery from django.utils import timezone as django_tz from core.types import Unset @@ -25,7 +24,7 @@ ) from interfaces.api.schemas.etl.data_connection import ( DATA_CONNECTION_INCLUDE_RELATIONS, - DataConnectionOrderByFields, + DataConnectionSortByFields, DataConnectionResponse, ) @@ -79,7 +78,7 @@ def annotate_task_counts(queryset: QuerySet) -> QuerySet: task_attention_count=attention_count_subquery, ) - order_by_aliases = { + sortby_aliases = { "timestampKey": "payload__timestamp_key", "timestampFormat": "payload__timestamp_format", "workspaceName": "workspace__name", @@ -138,7 +137,7 @@ def list( principal: User | ServiceAccount | AnonymousPrincipal, offset: Optional[int] = None, limit: Optional[int] = None, - order_by: Optional[list[str]] = None, + sortby: Optional[list[str]] = None, filtering: Optional[dict] = None, include: Optional[list[str]] = None, ): @@ -147,25 +146,20 @@ def list( queryset = DataConnection.objects - if "search_term" in filtering: - search_vector = SearchVector( - "name", "description", "workspace__name", "source_url", - "timezone_type", "timezone" - ) - queryset = queryset.annotate(search=search_vector).filter(search=SearchQuery(filtering["search_term"])) - if "workspace" in filtering: queryset = self.apply_filters(queryset, "workspace_id", filtering["workspace"]) if "payload_type" in filtering: queryset = self.apply_filters(queryset, "payload__payload_type", filtering["payload_type"]) - if order_by: - queryset = self.apply_ordering( - queryset, order_by, list(get_args(DataConnectionOrderByFields)), self.order_by_aliases - ) - else: - queryset = queryset.order_by("-id") + queryset, has_search = self.apply_search(queryset, filtering.get("q")) + queryset = self.apply_sorting( + queryset, + sortby, + list(get_args(DataConnectionSortByFields)), + self.sortby_aliases, + rank=has_search, + ) queryset = queryset.prefetch_related("placeholder_variables", "payload") if requested_includes: diff --git a/django/interfaces/api/services/etl/mapping.py b/django/interfaces/api/services/etl/mapping.py index f944ac15..a7ad9e8e 100644 --- a/django/interfaces/api/services/etl/mapping.py +++ b/django/interfaces/api/services/etl/mapping.py @@ -11,7 +11,7 @@ from interfaces.api.service import APIService from interfaces.api.schemas.etl.mapping import ( EtlMappingFields, - EtlMappingOrderByFields, + EtlMappingSortByFields, EtlMappingPatchBody, EtlMappingPostBody, EtlMappingResponse, @@ -72,7 +72,7 @@ def list( principal: User | ServiceAccount | AnonymousPrincipal, offset: Optional[int] = None, limit: Optional[int] = None, - order_by: Optional[list[str]] = None, + sortby: Optional[list[str]] = None, filtering: Optional[dict] = None, include: Optional[list[str]] = None, ): @@ -90,9 +90,9 @@ def list( queryset, "etl_task__data_connection__workspace_id", filtering["workspace_id"] ) - if order_by: - queryset = self.apply_ordering( - queryset, order_by, list(get_args(EtlMappingOrderByFields)) + if sortby: + queryset = self.apply_sorting( + queryset, sortby, list(get_args(EtlMappingSortByFields)) ) else: queryset = queryset.order_by("id") diff --git a/django/interfaces/api/services/etl/task.py b/django/interfaces/api/services/etl/task.py index 1743683c..55826273 100644 --- a/django/interfaces/api/services/etl/task.py +++ b/django/interfaces/api/services/etl/task.py @@ -4,7 +4,6 @@ from django.db import transaction from django.db.models import Count from django.contrib.auth import get_user_model -from django.contrib.postgres.search import SearchVector, SearchQuery from core.types import Unset from core.iam.models import ServiceAccount @@ -14,7 +13,7 @@ from processing.orchestration.services import TaskService from processing.etl.models import EtlTask, DataConnection from interfaces.api.schemas.etl.task import ( - EtlTaskOrderByFields, + EtlTaskSortByFields, EtlTaskResponse, EtlTaskPostBody, EtlTaskPatchBody, @@ -30,10 +29,10 @@ class EtlTaskAPIService(TaskService[EtlTask], APIService): task_model = EtlTask INCLUDE_RELATIONS = ETL_TASK_INCLUDE_RELATIONS - order_by_fields = { - "id", "name", "data_connection_id", "data_connection__name", "data_connection__workspace_id", - "data_connection__workspace__name", "latest_run_status", "latest_run_started_at", - "latest_run_finished_at", + sortby_aliases = { + "dataConnectionName": "data_connection__name", + "workspaceId": "data_connection__workspace_id", + "workspaceName": "data_connection__workspace__name", } def get_task_for_action( @@ -87,7 +86,7 @@ def list( principal: User | ServiceAccount | AnonymousPrincipal, offset: Optional[int] = None, limit: Optional[int] = None, - order_by: Optional[list[str]] = None, + sortby: Optional[list[str]] = None, filtering: Optional[dict] = None, include: Optional[list[str]] = None, properties: Optional[list[str]] = None, @@ -97,23 +96,17 @@ def list( queryset = self.task_model.objects - order_by = order_by or [] + sortby = sortby or [] latest_run_fields = [ "latest_run_status", "latest_run_started_at_min", "latest_run_started_at_max", "latest_run_finished_at_min", "latest_run_finished_at_max", ] if any(field in filtering for field in latest_run_fields) or any( - term.lstrip("-") in self.latest_run_filter_fields for term in order_by + term.lstrip("-") in self.latest_run_filter_fields for term in sortby ): queryset = self.annotate_latest_run(queryset, fields=self.latest_run_filter_fields) - if "search_term" in filtering: - search_vector = SearchVector("name", "description", "data_connection__name") - queryset = queryset.annotate(search=search_vector).filter( - search=SearchQuery(filtering["search_term"]) - ) - if "monitoring_site_id" in filtering: queryset = self.apply_filters( queryset, "etl_mappings__target_datastream__monitoring_site_id", filtering["monitoring_site_id"] @@ -142,12 +135,14 @@ def list( if "latest_run_finished_at_max" in filtering: queryset = queryset.filter(latest_run_finished_at__lte=filtering["latest_run_finished_at_max"]) - if order_by: - queryset = self.apply_ordering( - queryset, order_by, list(get_args(EtlTaskOrderByFields)) - ) - else: - queryset = queryset.order_by("-id") + queryset, has_search = self.apply_search(queryset, filtering.get("q")) + queryset = self.apply_sorting( + queryset, + sortby, + list(get_args(EtlTaskSortByFields)), + self.sortby_aliases, + rank=has_search, + ) queryset = queryset.select_related("periodic_task__crontab", "periodic_task__interval") diff --git a/django/interfaces/api/services/iam/collaborator.py b/django/interfaces/api/services/iam/collaborator.py index 4a171b1c..457f4c55 100644 --- a/django/interfaces/api/services/iam/collaborator.py +++ b/django/interfaces/api/services/iam/collaborator.py @@ -1,6 +1,6 @@ import uuid -from typing import Optional +from typing import Optional, get_args from django.db.models import Q from django.contrib.auth import get_user_model @@ -8,7 +8,10 @@ from core.iam.permissions.anonymous import AnonymousPrincipal from interfaces.api.http.errors import BadRequestError, PermissionDeniedError from interfaces.api.schemas import CollaboratorPostBody, CollaboratorDeleteBody, CollaboratorResponse -from interfaces.api.schemas.iam.collaborator import COLLABORATOR_INCLUDE_RELATIONS +from interfaces.api.schemas.iam.collaborator import ( + COLLABORATOR_INCLUDE_RELATIONS, + CollaboratorSortByFields, +) from interfaces.api.service import APIService from .role import RoleAPIService @@ -52,6 +55,7 @@ def list( workspace_id: uuid.UUID, offset: Optional[int] = None, limit: Optional[int] = None, + sortby: Optional[list[str]] = None, filtering: Optional[dict] = None, include: Optional[list[str]] = None, ): @@ -74,11 +78,9 @@ def list( queryset = principal.filter_by_permission(queryset, "can_view").distinct() - # The client fetches collaborators page by page. Without an explicit - # ordering, PostgreSQL is free to return tied rows in a different - # order for each request, which can make a collaborator move between - # pages or disappear from the merged result. Keep pagination stable. - queryset = queryset.order_by("id") + queryset = self.apply_sorting( + queryset, sortby, list(get_args(CollaboratorSortByFields)), {"roleId": "role_id"} + ) queryset, meta = self.apply_pagination(queryset, offset, limit) diff --git a/django/interfaces/api/services/iam/role.py b/django/interfaces/api/services/iam/role.py index ca811a97..d88aaaf0 100644 --- a/django/interfaces/api/services/iam/role.py +++ b/django/interfaces/api/services/iam/role.py @@ -6,7 +6,7 @@ from core.iam.models import ServiceAccount, Role from core.iam.permissions.anonymous import AnonymousPrincipal from interfaces.api.http.errors import NotFoundError, PermissionDeniedError -from interfaces.api.schemas import RoleOrderByFields, RoleResponse +from interfaces.api.schemas import RoleSortByFields, RoleResponse from interfaces.api.service import APIService User = get_user_model() @@ -42,7 +42,7 @@ def list( principal: User | ServiceAccount | AnonymousPrincipal, offset: Optional[int] = None, limit: Optional[int] = None, - order_by: Optional[list[str]] = None, + sortby: Optional[list[str]] = None, filtering: Optional[dict] = None, ): queryset = Role.objects @@ -53,14 +53,11 @@ def list( if field in filtering: queryset = self.apply_filters(queryset, field, filtering[field]) - if order_by: - queryset = self.apply_ordering( - queryset, - order_by, - list(get_args(RoleOrderByFields)), - ) - else: - queryset = queryset.order_by("id") + queryset = self.apply_sorting( + queryset, + sortby, + list(get_args(RoleSortByFields)), + ) queryset = ( principal.filter_by_permission(queryset, "can_view") diff --git a/django/interfaces/api/services/iam/service_account.py b/django/interfaces/api/services/iam/service_account.py index aaa79a2d..6ca11a18 100644 --- a/django/interfaces/api/services/iam/service_account.py +++ b/django/interfaces/api/services/iam/service_account.py @@ -15,7 +15,7 @@ ) from interfaces.api.schemas.iam.service_account import ( ServiceAccountInputFields, - ServiceAccountOrderByFields, + ServiceAccountSortByFields, SERVICE_ACCOUNT_INCLUDE_RELATIONS, ) @@ -69,7 +69,7 @@ def list( workspace_id: uuid.UUID, offset: Optional[int] = None, limit: Optional[int] = None, - order_by: Optional[list[str]] = None, + sortby: Optional[list[str]] = None, filtering: Optional[dict] = None, include: Optional[list[str]] = None, ): @@ -79,12 +79,9 @@ def list( ) queryset = ServiceAccount.objects.filter(workspace=workspace) - if order_by: - queryset = self.apply_ordering( - queryset, order_by, list(get_args(ServiceAccountOrderByFields)) - ) - else: - queryset = queryset.order_by("id") + queryset = self.apply_sorting( + queryset, sortby, list(get_args(ServiceAccountSortByFields)) + ) if requested_includes: select_paths = [ diff --git a/django/interfaces/api/services/iam/workspace.py b/django/interfaces/api/services/iam/workspace.py index b5f3e18b..89e1741e 100644 --- a/django/interfaces/api/services/iam/workspace.py +++ b/django/interfaces/api/services/iam/workspace.py @@ -16,7 +16,7 @@ ) from interfaces.api.schemas.iam.workspace import ( WORKSPACE_INCLUDE_RELATIONS, - WorkspaceOrderByFields, + WorkspaceSortByFields, ) User = get_user_model() @@ -54,7 +54,7 @@ def list( principal: User | ServiceAccount | AnonymousPrincipal, offset: Optional[int] = None, limit: Optional[int] = None, - order_by: Optional[list[str]] = None, + sortby: Optional[list[str]] = None, filtering: Optional[dict] = None, include: Optional[list[str]] = None, ): @@ -90,14 +90,13 @@ def list( else: queryset = self.apply_filters(queryset, field, filtering[field]) - if order_by: - queryset = self.apply_ordering( - queryset, - order_by, - list(get_args(WorkspaceOrderByFields)), - ) - else: - queryset = queryset.order_by("id") + queryset, has_search = self.apply_search(queryset, filtering.get("q")) + queryset = self.apply_sorting( + queryset, + sortby, + list(get_args(WorkspaceSortByFields)), + rank=has_search, + ) queryset = queryset.select_related( "owner", "transfer_confirmation", "transfer_confirmation__new_owner" diff --git a/django/interfaces/api/services/monitoring/rule.py b/django/interfaces/api/services/monitoring/rule.py index 7db8ff5f..eb81dbf9 100644 --- a/django/interfaces/api/services/monitoring/rule.py +++ b/django/interfaces/api/services/monitoring/rule.py @@ -12,7 +12,7 @@ from interfaces.api.services.monitoring.task import MonitoringTaskAPIService from interfaces.api.schemas.monitoring.rule import ( MonitoringRuleFields, - MonitoringRuleOrderByFields, + MonitoringRuleSortByFields, MonitoringRulePatchBody, MonitoringRulePostBody, MonitoringRuleResponse, @@ -59,7 +59,7 @@ def list( principal: User | ServiceAccount | AnonymousPrincipal, offset: Optional[int] = None, limit: Optional[int] = None, - order_by: Optional[list[str]] = None, + sortby: Optional[list[str]] = None, filtering: Optional[dict] = None, include: Optional[list[str]] = None, ): @@ -76,9 +76,9 @@ def list( queryset, "task__monitoring_site__workspace_id", filtering["workspace_id"] ) - if order_by: - queryset = self.apply_ordering( - queryset, order_by, list(get_args(MonitoringRuleOrderByFields)) + if sortby: + queryset = self.apply_sorting( + queryset, sortby, list(get_args(MonitoringRuleSortByFields)) ) else: queryset = queryset.order_by("datastream_id", "rule_type") diff --git a/django/interfaces/api/services/monitoring/task.py b/django/interfaces/api/services/monitoring/task.py index e667c343..5eb569c8 100644 --- a/django/interfaces/api/services/monitoring/task.py +++ b/django/interfaces/api/services/monitoring/task.py @@ -4,7 +4,6 @@ from django.db import transaction from django.db.models import Count from django.contrib.auth import get_user_model -from django.contrib.postgres.search import SearchVector, SearchQuery from core.types import Unset from core.iam.models import ServiceAccount @@ -15,7 +14,7 @@ from interfaces.api.http.errors import PermissionDeniedError, NotFoundError from interfaces.api.service import APIService from interfaces.api.schemas.monitoring.task import ( - MonitoringTaskOrderByFields, + MonitoringTaskSortByFields, MonitoringTaskResponse, MonitoringTaskPostBody, MonitoringTaskPatchBody, @@ -33,10 +32,10 @@ class MonitoringTaskAPIService(TaskService[MonitoringTask], APIService): task_model = MonitoringTask INCLUDE_RELATIONS = MONITORING_TASK_INCLUDE_RELATIONS - order_by_fields = { - "id", "name", "monitoring_site_id", "monitoring_site__name", - "monitoring_site__workspace_id", "monitoring_site__workspace__name", - "latest_run_status", "latest_run_started_at", "latest_run_finished_at", + sortby_aliases = { + "monitoringSiteName": "monitoring_site__name", + "workspaceId": "monitoring_site__workspace_id", + "workspaceName": "monitoring_site__workspace__name", } @classmethod @@ -124,7 +123,7 @@ def list( principal: User | ServiceAccount | AnonymousPrincipal, offset: Optional[int] = None, limit: Optional[int] = None, - order_by: Optional[list[str]] = None, + sortby: Optional[list[str]] = None, filtering: Optional[dict] = None, include: Optional[list[str]] = None, properties: Optional[list[str]] = None, @@ -134,19 +133,13 @@ def list( queryset = self.task_model.objects - order_by = order_by or [] + sortby = sortby or [] if "latest_run_status" in filtering or any( - term.lstrip("-") in self.latest_run_filter_fields for term in order_by + term.lstrip("-") in self.latest_run_filter_fields for term in sortby ): queryset = self.annotate_latest_run(queryset, fields=self.latest_run_filter_fields) - if "search_term" in filtering: - search_vector = SearchVector("name", "description", "monitoring_site__name") - queryset = queryset.annotate(search=search_vector).filter( - search=SearchQuery(filtering["search_term"]) - ) - if "monitoring_site" in filtering: queryset = self.apply_filters(queryset, "monitoring_site_id", filtering["monitoring_site"]) @@ -164,12 +157,14 @@ def list( if "rule_type" in filtering: queryset = self.apply_filters(queryset, "rules__rule_type", filtering["rule_type"]) - if order_by: - queryset = self.apply_ordering( - queryset, order_by, list(get_args(MonitoringTaskOrderByFields)) - ) - else: - queryset = queryset.order_by("-id") + queryset, has_search = self.apply_search(queryset, filtering.get("q")) + queryset = self.apply_sorting( + queryset, + sortby, + list(get_args(MonitoringTaskSortByFields)), + self.sortby_aliases, + rank=has_search, + ) queryset = queryset.select_related( "periodic_task__crontab", "periodic_task__interval" diff --git a/django/interfaces/api/services/products/rating_curve.py b/django/interfaces/api/services/products/rating_curve.py index 8148f047..6c75fd01 100644 --- a/django/interfaces/api/services/products/rating_curve.py +++ b/django/interfaces/api/services/products/rating_curve.py @@ -13,7 +13,7 @@ from interfaces.api.service import APIService from interfaces.api.schemas.products.rating_curve import ( RatingCurveFields, - RatingCurveOrderByFields, + RatingCurveSortByFields, RatingCurvePatchBody, RatingCurvePostBody, RatingCurveResponse, @@ -22,7 +22,7 @@ User = get_user_model() -RATING_CURVE_ORDER_BY_ALIASES = { +RATING_CURVE_SORTBY_ALIASES = { "monitoringSiteName": "monitoring_site__name", "workspaceId": "monitoring_site__workspace_id", "workspaceName": "monitoring_site__workspace__name", @@ -68,7 +68,7 @@ def list( principal: User | ServiceAccount | AnonymousPrincipal, offset: Optional[int] = None, limit: Optional[int] = None, - order_by: Optional[list[str]] = None, + sortby: Optional[list[str]] = None, filtering: Optional[dict] = None, include: Optional[list[str]] = None, ): @@ -79,15 +79,14 @@ def list( if field in filtering: queryset = self.apply_filters(queryset, field, filtering[field]) - if order_by: - queryset = self.apply_ordering( - queryset, - order_by, - list(get_args(RatingCurveOrderByFields)), - field_aliases=RATING_CURVE_ORDER_BY_ALIASES, - ) - else: - queryset = queryset.order_by("-id") + queryset, has_search = self.apply_search(queryset, filtering.get("q")) + queryset = self.apply_sorting( + queryset, + sortby, + list(get_args(RatingCurveSortByFields)), + field_aliases=RATING_CURVE_SORTBY_ALIASES, + rank=has_search, + ) queryset = queryset.prefetch_related("points") diff --git a/django/interfaces/api/services/products/task.py b/django/interfaces/api/services/products/task.py index 89ddec7a..d4e65fe4 100644 --- a/django/interfaces/api/services/products/task.py +++ b/django/interfaces/api/services/products/task.py @@ -4,7 +4,6 @@ from typing import Optional, Literal, get_args from django.db import transaction from django.contrib.auth import get_user_model -from django.contrib.postgres.search import SearchVector, SearchQuery from core.types import Unset from core.iam.models import ServiceAccount @@ -15,7 +14,7 @@ from interfaces.api.http.errors import PermissionDeniedError, NotFoundError from interfaces.api.service import APIService from interfaces.api.schemas.products.task import ( - DataProductTaskOrderByFields, + DataProductTaskSortByFields, DataProductTaskResponse, DataProductTaskPostBody, DataProductTaskPatchBody, @@ -35,10 +34,10 @@ class DataProductTaskAPIService(TaskService[DataProductTask], APIService): task_model = DataProductTask INCLUDE_RELATIONS = DATA_PRODUCT_TASK_INCLUDE_RELATIONS - order_by_fields = { - "id", "name", "monitoring_site_id", "monitoring_site__name", - "monitoring_site__workspace_id", "monitoring_site__workspace__name", - "latest_run_status", "latest_run_started_at", "latest_run_finished_at", + sortby_aliases = { + "monitoringSiteName": "monitoring_site__name", + "workspaceId": "monitoring_site__workspace_id", + "workspaceName": "monitoring_site__workspace__name", } @classmethod @@ -121,7 +120,7 @@ def list( principal: User | ServiceAccount | AnonymousPrincipal, offset: Optional[int] = None, limit: Optional[int] = None, - order_by: Optional[list[str]] = None, + sortby: Optional[list[str]] = None, filtering: Optional[dict] = None, include: Optional[list[str]] = None, properties: Optional[list[str]] = None, @@ -131,19 +130,13 @@ def list( queryset = self.task_model.objects - order_by = order_by or [] + sortby = sortby or [] if "latest_run_status" in filtering or any( - term.lstrip("-") in self.latest_run_filter_fields for term in order_by + term.lstrip("-") in self.latest_run_filter_fields for term in sortby ): queryset = self.annotate_latest_run(queryset, fields=self.latest_run_filter_fields) - if "search_term" in filtering: - search_vector = SearchVector("name", "description", "monitoring_site__name") - queryset = queryset.annotate(search=search_vector).filter( - search=SearchQuery(filtering["search_term"]) - ) - if "monitoring_site" in filtering: queryset = self.apply_filters(queryset, "monitoring_site_id", filtering["monitoring_site"]) @@ -177,12 +170,14 @@ def list( queryset, "transformations__rating_curve", filtering["rating_curve"] ) - if order_by: - queryset = self.apply_ordering( - queryset, order_by, list(get_args(DataProductTaskOrderByFields)) - ) - else: - queryset = queryset.order_by("-id") + queryset, has_search = self.apply_search(queryset, filtering.get("q")) + queryset = self.apply_sorting( + queryset, + sortby, + list(get_args(DataProductTaskSortByFields)), + self.sortby_aliases, + rank=has_search, + ) queryset = queryset.select_related( "periodic_task__crontab", "periodic_task__interval" diff --git a/django/interfaces/api/services/products/transformation.py b/django/interfaces/api/services/products/transformation.py index b1aeffae..a9a0ca94 100644 --- a/django/interfaces/api/services/products/transformation.py +++ b/django/interfaces/api/services/products/transformation.py @@ -13,7 +13,7 @@ from interfaces.api.services.products.task import DataProductTaskAPIService from interfaces.api.schemas.products.transformation import ( DataProductTransformationFields, - DataProductTransformationOrderByFields, + DataProductTransformationSortByFields, DataProductTransformationPatchBody, DataProductTransformationPostBody, DataProductTransformationResponse, @@ -77,7 +77,7 @@ def list( principal: User | ServiceAccount | AnonymousPrincipal, offset: Optional[int] = None, limit: Optional[int] = None, - order_by: Optional[list[str]] = None, + sortby: Optional[list[str]] = None, filtering: Optional[dict] = None, include: Optional[list[str]] = None, ): @@ -99,9 +99,9 @@ def list( queryset, "task__monitoring_site__workspace_id", filtering["workspace_id"] ) - if order_by: - queryset = self.apply_ordering( - queryset, order_by, list(get_args(DataProductTransformationOrderByFields)) + if sortby: + queryset = self.apply_sorting( + queryset, sortby, list(get_args(DataProductTransformationSortByFields)) ) else: queryset = queryset.order_by("id") diff --git a/django/interfaces/api/services/quality/history.py b/django/interfaces/api/services/quality/history.py index a1e813b2..bbe87526 100644 --- a/django/interfaces/api/services/quality/history.py +++ b/django/interfaces/api/services/quality/history.py @@ -11,7 +11,7 @@ from interfaces.api.http.errors import ConflictError, PermissionDeniedError, NotFoundError from interfaces.api.service import APIService from interfaces.api.schemas.quality.history import ( - QualityControlHistoryOrderByFields, + QualityControlHistorySortByFields, QualityControlHistoryResponse, QualityControlHistoryPostBody, QUALITY_CONTROL_HISTORY_INCLUDE_RELATIONS, @@ -82,7 +82,7 @@ def list( principal: User | ServiceAccount | AnonymousPrincipal, offset: Optional[int] = None, limit: Optional[int] = None, - order_by: Optional[list[str]] = None, + sortby: Optional[list[str]] = None, filtering: Optional[dict] = None, include: Optional[list[str]] = None, ): @@ -93,14 +93,11 @@ def list( if field in filtering: queryset = self.apply_filters(queryset, field, filtering[field]) - if order_by: - queryset = self.apply_ordering( - queryset, - order_by, - list(get_args(QualityControlHistoryOrderByFields)), - ) - else: - queryset = queryset.order_by("-id") + queryset = self.apply_sorting( + queryset, + sortby, + list(get_args(QualityControlHistorySortByFields)), + ) queryset = queryset.select_related( "managed_datastream__monitoring_site__workspace", diff --git a/django/interfaces/api/services/quality/operation.py b/django/interfaces/api/services/quality/operation.py index a2cdf651..bbfda82a 100644 --- a/django/interfaces/api/services/quality/operation.py +++ b/django/interfaces/api/services/quality/operation.py @@ -13,7 +13,7 @@ from interfaces.api.service import APIService from interfaces.api.services.quality.session import QCSessionAPIService from interfaces.api.schemas.quality.operation import ( - QualityControlOperationOrderByFields, + QualityControlOperationSortByFields, QualityControlOperationResponse, ) @@ -33,8 +33,6 @@ class OperationInput(BaseModel): class QCOperationAPIService(APIService): - order_by_fields = {"id", "order", "operation_type", "created_at"} - def get( self, principal: User | ServiceAccount | AnonymousPrincipal, @@ -76,17 +74,17 @@ def list( session: uuid.UUID | QCSession, offset: Optional[int] = None, limit: Optional[int] = None, - order_by: Optional[list[str]] = None, + sortby: Optional[list[str]] = None, ): session = qc_session_service.get(principal=principal, history=history, session=session, action="view") queryset = QCOperation.objects.filter(session=session) - if order_by: - queryset = self.apply_ordering( - queryset, order_by, list(get_args(QualityControlOperationOrderByFields)) - ) - else: - queryset = queryset.order_by("order") + queryset = self.apply_sorting( + queryset, + sortby, + list(get_args(QualityControlOperationSortByFields)), + default_sortby=("order",), + ) queryset, meta = self.apply_pagination(queryset, offset, limit) operations = list(queryset.all()) diff --git a/django/interfaces/api/services/quality/session.py b/django/interfaces/api/services/quality/session.py index 135cb34d..f2191c88 100644 --- a/django/interfaces/api/services/quality/session.py +++ b/django/interfaces/api/services/quality/session.py @@ -16,7 +16,7 @@ from interfaces.api.services.sta import ObservationAPIService from interfaces.api.services.quality.history import QCHistoryAPIService from interfaces.api.schemas.quality.session import ( - QualityControlSessionOrderByFields, + QualityControlSessionSortByFields, QualityControlSessionResponse, ) @@ -29,15 +29,6 @@ class QCSessionAPIService(APIService): - order_by_fields = { - "id", - "created_at", - "phenomenon_time_start", - "phenomenon_time_end", - "status", - "committed_at", - } - @staticmethod def select_related_fields(queryset: QuerySet) -> QuerySet: return queryset.select_related("created_by").prefetch_related("dependencies") @@ -104,7 +95,7 @@ def list( history: uuid.UUID | QCHistory, offset: Optional[int] = None, limit: Optional[int] = None, - order_by: Optional[list[str]] = None, + sortby: Optional[list[str]] = None, filtering: Optional[dict] = None, ): filtering = filtering or {} @@ -136,13 +127,9 @@ def list( session_ids |= self._get_ancestor_ids(session_ids) queryset = QCSession.objects.filter(history=history, pk__in=session_ids) - order_by = order_by or [] - if order_by: - queryset = self.apply_ordering( - queryset, order_by, list(get_args(QualityControlSessionOrderByFields)) - ) - else: - queryset = queryset.order_by("phenomenon_time_start") + queryset = self.apply_sorting( + queryset, sortby, list(get_args(QualityControlSessionSortByFields)) + ) queryset = self.select_related_fields(queryset) queryset, meta = self.apply_pagination(queryset, offset, limit) diff --git a/django/interfaces/api/services/sta/datastream.py b/django/interfaces/api/services/sta/datastream.py index def9f397..36e253b4 100644 --- a/django/interfaces/api/services/sta/datastream.py +++ b/django/interfaces/api/services/sta/datastream.py @@ -27,7 +27,7 @@ LinkedResourcePostBody, ) from interfaces.api.schemas.sta.datastream import ( - DatastreamOrderByFields, + DatastreamSortByFields, DatastreamResponse, DATASTREAM_INCLUDE_RELATIONS, ) @@ -110,7 +110,7 @@ def list( principal: User | ServiceAccount | AnonymousPrincipal, offset: Optional[int] = None, limit: Optional[int] = None, - order_by: Optional[list[str]] = None, + sortby: Optional[list[str]] = None, filtering: Optional[dict] = None, include: Optional[list[str]] = None, ): @@ -158,15 +158,13 @@ def list( queryset = self.apply_filters(queryset, field, filtering[field]) queryset = self.apply_tag_filter(queryset, filtering.get("tag")) - - if order_by: - queryset = self.apply_ordering( - queryset, - order_by, - list(get_args(DatastreamOrderByFields)), - ) - else: - queryset = queryset.order_by("id") + queryset, has_search = self.apply_search(queryset, filtering.get("q")) + queryset = self.apply_sorting( + queryset, + sortby, + list(get_args(DatastreamSortByFields)), + rank=has_search, + ) queryset = queryset.select_related("monitoring_site").prefetch_related( "datastream_linked_resources" @@ -509,10 +507,10 @@ def list_aggregation_statistics( self, offset: Optional[int] = None, limit: Optional[int] = None, - order_desc: bool = False, + sort_desc: bool = False, ): queryset = DatastreamAggregation.objects.order_by( - f"{'-' if order_desc else ''}name" + f"{'-' if sort_desc else ''}name" ) queryset, meta = self.apply_pagination(queryset, offset, limit) @@ -522,9 +520,9 @@ def list_statuses( self, offset: Optional[int] = None, limit: Optional[int] = None, - order_desc: bool = False, + sort_desc: bool = False, ): - queryset = DatastreamStatus.objects.order_by(f"{'-' if order_desc else ''}name") + queryset = DatastreamStatus.objects.order_by(f"{'-' if sort_desc else ''}name") queryset, meta = self.apply_pagination(queryset, offset, limit) return {"data": list(queryset.values_list("name", flat=True)), "meta": meta} @@ -533,9 +531,9 @@ def list_sampled_mediums( self, offset: Optional[int] = None, limit: Optional[int] = None, - order_desc: bool = False, + sort_desc: bool = False, ): - queryset = SampledMedium.objects.order_by(f"{'-' if order_desc else ''}name") + queryset = SampledMedium.objects.order_by(f"{'-' if sort_desc else ''}name") queryset, meta = self.apply_pagination(queryset, offset, limit) return {"data": list(queryset.values_list("name", flat=True)), "meta": meta} @@ -544,10 +542,10 @@ def list_linked_resource_types( self, offset: Optional[int] = None, limit: Optional[int] = None, - order_desc: bool = False, + sort_desc: bool = False, ): queryset = LinkedResourceType.objects.order_by( - f"{'-' if order_desc else ''}name" + f"{'-' if sort_desc else ''}name" ) queryset, meta = self.apply_pagination(queryset, offset, limit) diff --git a/django/interfaces/api/services/sta/method.py b/django/interfaces/api/services/sta/method.py index f7fc361d..2695367e 100644 --- a/django/interfaces/api/services/sta/method.py +++ b/django/interfaces/api/services/sta/method.py @@ -17,7 +17,7 @@ ) from interfaces.api.schemas.sta.method import ( MethodFields, - MethodOrderByFields, + MethodSortByFields, METHOD_INCLUDE_RELATIONS, ) @@ -59,7 +59,7 @@ def list( principal: User | ServiceAccount | AnonymousPrincipal, offset: Optional[int] = None, limit: Optional[int] = None, - order_by: Optional[list[str]] = None, + sortby: Optional[list[str]] = None, filtering: Optional[dict] = None, include: Optional[list[str]] = None, ): @@ -77,14 +77,13 @@ def list( if field in filtering: queryset = self.apply_filters(queryset, field, filtering[field]) - if order_by: - queryset = self.apply_ordering( - queryset, - order_by, - list(get_args(MethodOrderByFields)), - ) - else: - queryset = queryset.order_by("id") + queryset, has_search = self.apply_search(queryset, filtering.get("q")) + queryset = self.apply_sorting( + queryset, + sortby, + list(get_args(MethodSortByFields)), + rank=has_search, + ) if requested_includes: select_paths = [ @@ -195,9 +194,9 @@ def list_types( self, offset: Optional[int] = None, limit: Optional[int] = None, - order_desc: bool = False, + sort_desc: bool = False, ): - queryset = MethodType.objects.order_by(f"{'-' if order_desc else ''}name") + queryset = MethodType.objects.order_by(f"{'-' if sort_desc else ''}name") queryset, meta = self.apply_pagination(queryset, offset, limit) return {"data": list(queryset.values_list("name", flat=True)), "meta": meta} diff --git a/django/interfaces/api/services/sta/monitoring_site.py b/django/interfaces/api/services/sta/monitoring_site.py index 95a39a16..5cc4f53d 100644 --- a/django/interfaces/api/services/sta/monitoring_site.py +++ b/django/interfaces/api/services/sta/monitoring_site.py @@ -30,7 +30,7 @@ ) from interfaces.api.schemas.sta.monitoring_site import ( MonitoringSiteFields, - MonitoringSiteOrderByFields, + MonitoringSiteSortByFields, MONITORING_SITE_INCLUDE_RELATIONS, ) from processing.orchestration.attention import attention_filter, latest_run_status_subquery @@ -398,7 +398,7 @@ def list( principal: User | ServiceAccount | AnonymousPrincipal, offset: Optional[int] = None, limit: Optional[int] = None, - order_by: Optional[list[str]] = None, + sortby: Optional[list[str]] = None, filtering: Optional[dict] = None, include: Optional[list[str]] = None, ): @@ -426,20 +426,18 @@ def list( queryset = self.apply_bbox_filter(queryset, filtering.get("bbox")) queryset = self.apply_tag_filter(queryset, filtering.get("tag")) - - if order_by: - queryset = self.apply_ordering( - queryset, - order_by, - list(get_args(MonitoringSiteOrderByFields)), - { - "elevationDatum": "elevation_datum", - "adminArea1": "admin_area_1", - "adminArea2": "admin_area_2", - }, - ) - else: - queryset = queryset.order_by("id") + queryset, has_search = self.apply_search(queryset, filtering.get("q")) + queryset = self.apply_sorting( + queryset, + sortby, + list(get_args(MonitoringSiteSortByFields)), + { + "elevationDatum": "elevation_datum", + "adminArea1": "admin_area_1", + "adminArea2": "admin_area_2", + }, + rank=has_search, + ) queryset = queryset.prefetch_related("monitoring_site_linked_resources") @@ -647,9 +645,9 @@ def list_site_types( self, offset: Optional[int] = None, limit: Optional[int] = None, - order_desc: bool = False, + sort_desc: bool = False, ): - queryset = SiteType.objects.order_by(f"{'-' if order_desc else ''}name") + queryset = SiteType.objects.order_by(f"{'-' if sort_desc else ''}name") queryset, meta = self.apply_pagination(queryset, offset, limit) return {"data": list(queryset.values_list("name", flat=True)), "meta": meta} @@ -658,10 +656,10 @@ def list_linked_resource_types( self, offset: Optional[int] = None, limit: Optional[int] = None, - order_desc: bool = False, + sort_desc: bool = False, ): queryset = LinkedResourceType.objects.order_by( - f"{'-' if order_desc else ''}name" + f"{'-' if sort_desc else ''}name" ) queryset, meta = self.apply_pagination(queryset, offset, limit) diff --git a/django/interfaces/api/services/sta/observation.py b/django/interfaces/api/services/sta/observation.py index 271ce813..242098d0 100644 --- a/django/interfaces/api/services/sta/observation.py +++ b/django/interfaces/api/services/sta/observation.py @@ -19,7 +19,7 @@ from interfaces.api.http.errors import BadRequestError, ConflictError, PermissionDeniedError, NotFoundError from interfaces.api.schemas.sta.observation import ( ObservationFields, - ObservationOrderByFields, + ObservationSortByFields, ObservationResponse, ObservationPostBody, ObservationBulkPostBody, @@ -142,7 +142,7 @@ def list( response: HttpResponse, offset: Optional[int] = None, limit: Optional[int] = None, - order_by: Optional[list[str]] = None, + sortby: Optional[list[str]] = None, filtering: Optional[dict] = None, response_format: Optional[str] = None, include: Optional[list[str]] = None, @@ -197,13 +197,11 @@ def list( result_qualifier_codes=F("result_qualifiers") ) - if not order_by: - order_by = ["datastreamId", "phenomenonTime"] - - queryset = self.apply_ordering( + queryset = self.apply_sorting( queryset, - order_by, - list(get_args(ObservationOrderByFields)), + sortby, + list(get_args(ObservationSortByFields)), + default_sortby=("datastream_id", "phenomenon_time"), ) select_paths = ["datastream__monitoring_site"] diff --git a/django/interfaces/api/services/sta/observed_property.py b/django/interfaces/api/services/sta/observed_property.py index 7824c72d..92164be3 100644 --- a/django/interfaces/api/services/sta/observed_property.py +++ b/django/interfaces/api/services/sta/observed_property.py @@ -17,7 +17,7 @@ ) from interfaces.api.schemas.sta.observed_property import ( ObservedPropertyFields, - ObservedPropertyOrderByFields, + ObservedPropertySortByFields, OBSERVED_PROPERTY_INCLUDE_RELATIONS, ) @@ -59,7 +59,7 @@ def list( principal: User | ServiceAccount | AnonymousPrincipal, offset: Optional[int] = None, limit: Optional[int] = None, - order_by: Optional[list[str]] = None, + sortby: Optional[list[str]] = None, filtering: Optional[dict] = None, include: Optional[list[str]] = None, ): @@ -75,14 +75,13 @@ def list( if field in filtering: queryset = self.apply_filters(queryset, field, filtering[field]) - if order_by: - queryset = self.apply_ordering( - queryset, - order_by, - list(get_args(ObservedPropertyOrderByFields)), - ) - else: - queryset = queryset.order_by("id") + queryset, has_search = self.apply_search(queryset, filtering.get("q")) + queryset = self.apply_sorting( + queryset, + sortby, + list(get_args(ObservedPropertySortByFields)), + rank=has_search, + ) if requested_includes: select_paths = [ @@ -194,9 +193,9 @@ def list_variable_types( self, offset: Optional[int] = None, limit: Optional[int] = None, - order_desc: bool = False, + sort_desc: bool = False, ): - queryset = VariableType.objects.order_by(f"{'-' if order_desc else ''}name") + queryset = VariableType.objects.order_by(f"{'-' if sort_desc else ''}name") queryset, meta = self.apply_pagination(queryset, offset, limit) return {"data": list(queryset.values_list("name", flat=True)), "meta": meta} diff --git a/django/interfaces/api/services/sta/processing_level.py b/django/interfaces/api/services/sta/processing_level.py index d938b19d..5ce91651 100644 --- a/django/interfaces/api/services/sta/processing_level.py +++ b/django/interfaces/api/services/sta/processing_level.py @@ -17,7 +17,7 @@ ) from interfaces.api.schemas.sta.processing_level import ( ProcessingLevelFields, - ProcessingLevelOrderByFields, + ProcessingLevelSortByFields, PROCESSING_LEVEL_INCLUDE_RELATIONS, ) @@ -63,7 +63,7 @@ def list( principal: User | ServiceAccount | AnonymousPrincipal, offset: Optional[int] = None, limit: Optional[int] = None, - order_by: Optional[list[str]] = None, + sortby: Optional[list[str]] = None, filtering: Optional[dict] = None, include: Optional[list[str]] = None, ): @@ -78,14 +78,13 @@ def list( if field in filtering: queryset = self.apply_filters(queryset, field, filtering[field]) - if order_by: - queryset = self.apply_ordering( - queryset, - order_by, - list(get_args(ProcessingLevelOrderByFields)), - ) - else: - queryset = queryset.order_by("id") + queryset, has_search = self.apply_search(queryset, filtering.get("q")) + queryset = self.apply_sorting( + queryset, + sortby, + list(get_args(ProcessingLevelSortByFields)), + rank=has_search, + ) if requested_includes: select_paths = [ diff --git a/django/interfaces/api/services/sta/result_qualifier.py b/django/interfaces/api/services/sta/result_qualifier.py index ce45cb34..d386483c 100644 --- a/django/interfaces/api/services/sta/result_qualifier.py +++ b/django/interfaces/api/services/sta/result_qualifier.py @@ -17,7 +17,7 @@ ) from interfaces.api.schemas.sta.result_qualifier import ( ResultQualifierFields, - ResultQualifierOrderByFields, + ResultQualifierSortByFields, RESULT_QUALIFIER_INCLUDE_RELATIONS, ) @@ -61,7 +61,7 @@ def list( principal: User | ServiceAccount | AnonymousPrincipal, offset: Optional[int] = None, limit: Optional[int] = None, - order_by: Optional[list[str]] = None, + sortby: Optional[list[str]] = None, filtering: Optional[dict] = None, include: Optional[list[str]] = None, ): @@ -80,14 +80,13 @@ def list( ): queryset = ResultQualifier.objects.none() - if order_by: - queryset = self.apply_ordering( - queryset, - order_by, - list(get_args(ResultQualifierOrderByFields)), - ) - else: - queryset = queryset.order_by("id") + queryset, has_search = self.apply_search(queryset, filtering.get("q")) + queryset = self.apply_sorting( + queryset, + sortby, + list(get_args(ResultQualifierSortByFields)), + rank=has_search, + ) if requested_includes: select_paths = [ diff --git a/django/interfaces/api/services/sta/unit.py b/django/interfaces/api/services/sta/unit.py index 2ad40be7..4d545f98 100644 --- a/django/interfaces/api/services/sta/unit.py +++ b/django/interfaces/api/services/sta/unit.py @@ -17,7 +17,7 @@ ) from interfaces.api.schemas.sta.unit import ( UnitFields, - UnitOrderByFields, + UnitSortByFields, UNIT_INCLUDE_RELATIONS, ) @@ -58,7 +58,7 @@ def list( principal: User | ServiceAccount | AnonymousPrincipal, offset: int | None = None, limit: int | None = None, - order_by: list[str] | None = None, + sortby: list[str] | None = None, filtering: dict | None = None, include: Optional[list[str]] = None, ): @@ -74,14 +74,13 @@ def list( if field in filtering: queryset = self.apply_filters(queryset, field, filtering[field]) - if order_by: - queryset = self.apply_ordering( - queryset, - order_by, - list(get_args(UnitOrderByFields)), - ) - else: - queryset = queryset.order_by("id") + queryset, has_search = self.apply_search(queryset, filtering.get("q")) + queryset = self.apply_sorting( + queryset, + sortby, + list(get_args(UnitSortByFields)), + rank=has_search, + ) if requested_includes: select_paths = [ @@ -184,9 +183,9 @@ def list_unit_types( self, offset: int | None = None, limit: int | None = None, - order_desc: bool = False, + sort_desc: bool = False, ): - queryset = UnitType.objects.order_by(f"{'-' if order_desc else ''}name") + queryset = UnitType.objects.order_by(f"{'-' if sort_desc else ''}name") queryset, meta = self.apply_pagination(queryset, offset, limit) return {"data": list(queryset.values_list("name", flat=True)), "meta": meta} diff --git a/django/interfaces/api/views/etl/data_connection.py b/django/interfaces/api/views/etl/data_connection.py index f2fc91d6..cc66f48f 100644 --- a/django/interfaces/api/views/etl/data_connection.py +++ b/django/interfaces/api/views/etl/data_connection.py @@ -42,7 +42,7 @@ def get_data_connections( principal=request.principal, offset=query.offset, limit=query.limit, - order_by=query.order_by, + sortby=query.sortby, filtering=query.dict(exclude_unset=True), include=query.include, ) diff --git a/django/interfaces/api/views/etl/mapping.py b/django/interfaces/api/views/etl/mapping.py index 7bf44f83..db4c981e 100644 --- a/django/interfaces/api/views/etl/mapping.py +++ b/django/interfaces/api/views/etl/mapping.py @@ -39,7 +39,7 @@ def get_etl_mappings( principal=request.principal, offset=query.offset, limit=query.limit, - order_by=query.order_by, + sortby=query.sortby, filtering=query.dict(exclude_unset=True), include=query.include, ) diff --git a/django/interfaces/api/views/etl/task.py b/django/interfaces/api/views/etl/task.py index 441f1e25..88faa972 100644 --- a/django/interfaces/api/views/etl/task.py +++ b/django/interfaces/api/views/etl/task.py @@ -42,7 +42,7 @@ def get_etl_tasks( principal=request.principal, offset=query.offset, limit=query.limit, - order_by=query.order_by, + sortby=query.sortby, filtering=query.dict(exclude_unset=True), include=query.include, properties=query.properties, @@ -202,7 +202,7 @@ def get_etl_task_runs( run_kwargs = query.model_dump( exclude_unset=True, include={ - "offset", "limit", "order_by", "status", + "offset", "limit", "sortby", "status", "started_at__gte", "started_at__lte", "finished_at__gte", "finished_at__lte", }, ) diff --git a/django/interfaces/api/views/iam/collaborator.py b/django/interfaces/api/views/iam/collaborator.py index 7e200e64..671d672b 100644 --- a/django/interfaces/api/views/iam/collaborator.py +++ b/django/interfaces/api/views/iam/collaborator.py @@ -43,6 +43,7 @@ def get_collaborators( workspace_id=workspace_id, offset=query.offset, limit=query.limit, + sortby=query.sortby, filtering=query.dict(exclude_unset=True), include=query.include, ) diff --git a/django/interfaces/api/views/iam/role.py b/django/interfaces/api/views/iam/role.py index 35b2be7a..62af2be4 100644 --- a/django/interfaces/api/views/iam/role.py +++ b/django/interfaces/api/views/iam/role.py @@ -38,7 +38,7 @@ def get_roles( principal=request.principal, offset=query.offset, limit=query.limit, - order_by=query.order_by, + sortby=query.sortby, filtering=query.dict(exclude_unset=True), ) diff --git a/django/interfaces/api/views/iam/service_account.py b/django/interfaces/api/views/iam/service_account.py index da9ceabb..ecf92ed3 100644 --- a/django/interfaces/api/views/iam/service_account.py +++ b/django/interfaces/api/views/iam/service_account.py @@ -45,7 +45,7 @@ def get_service_accounts( workspace_id=workspace_id, offset=query.offset, limit=query.limit, - order_by=query.order_by, + sortby=query.sortby, filtering=query.dict(exclude_unset=True), include=query.include, ) diff --git a/django/interfaces/api/views/iam/workspace.py b/django/interfaces/api/views/iam/workspace.py index bc778848..2a2133fe 100644 --- a/django/interfaces/api/views/iam/workspace.py +++ b/django/interfaces/api/views/iam/workspace.py @@ -46,7 +46,7 @@ def get_workspaces( principal=request.principal, offset=query.offset, limit=query.limit, - order_by=query.order_by, + sortby=query.sortby, filtering=query.dict(exclude_unset=True), include=query.include, ) diff --git a/django/interfaces/api/views/monitoring/rule.py b/django/interfaces/api/views/monitoring/rule.py index b18b806e..2a4f3971 100644 --- a/django/interfaces/api/views/monitoring/rule.py +++ b/django/interfaces/api/views/monitoring/rule.py @@ -41,7 +41,7 @@ def get_monitoring_rules( principal=request.principal, offset=query.offset, limit=query.limit, - order_by=query.order_by, + sortby=query.sortby, filtering=query.dict(exclude_unset=True), include=query.include, ) diff --git a/django/interfaces/api/views/monitoring/task.py b/django/interfaces/api/views/monitoring/task.py index d8631db6..2d5f50f2 100644 --- a/django/interfaces/api/views/monitoring/task.py +++ b/django/interfaces/api/views/monitoring/task.py @@ -42,7 +42,7 @@ def get_monitoring_tasks( principal=request.principal, offset=query.offset, limit=query.limit, - order_by=query.order_by, + sortby=query.sortby, filtering=query.dict(exclude_unset=True), include=query.include, properties=query.properties, @@ -202,7 +202,7 @@ def get_monitoring_task_runs( run_kwargs = query.model_dump( exclude_unset=True, include={ - "offset", "limit", "order_by", "status", + "offset", "limit", "sortby", "status", "started_at__gte", "started_at__lte", "finished_at__gte", "finished_at__lte", }, ) diff --git a/django/interfaces/api/views/products/rating_curve.py b/django/interfaces/api/views/products/rating_curve.py index 63b233ac..8fd15dc4 100644 --- a/django/interfaces/api/views/products/rating_curve.py +++ b/django/interfaces/api/views/products/rating_curve.py @@ -39,7 +39,7 @@ def get_rating_curves( principal=request.principal, offset=query.offset, limit=query.limit, - order_by=query.order_by, + sortby=query.sortby, filtering=query.dict(exclude_unset=True), include=query.include, ) diff --git a/django/interfaces/api/views/products/task.py b/django/interfaces/api/views/products/task.py index 27d880fa..4e99a0a1 100644 --- a/django/interfaces/api/views/products/task.py +++ b/django/interfaces/api/views/products/task.py @@ -42,7 +42,7 @@ def get_data_product_tasks( principal=request.principal, offset=query.offset, limit=query.limit, - order_by=query.order_by, + sortby=query.sortby, filtering=query.dict(exclude_unset=True), include=query.include, properties=query.properties, @@ -204,7 +204,7 @@ def get_data_product_task_runs( run_kwargs = query.model_dump( exclude_unset=True, include={ - "offset", "limit", "order_by", "status", + "offset", "limit", "sortby", "status", "started_at__gte", "started_at__lte", "finished_at__gte", "finished_at__lte", }, ) diff --git a/django/interfaces/api/views/products/transformation.py b/django/interfaces/api/views/products/transformation.py index ea43bb22..5477599f 100644 --- a/django/interfaces/api/views/products/transformation.py +++ b/django/interfaces/api/views/products/transformation.py @@ -39,7 +39,7 @@ def get_data_product_transformations( principal=request.principal, offset=query.offset, limit=query.limit, - order_by=query.order_by, + sortby=query.sortby, filtering=query.dict(exclude_unset=True), include=query.include, ) diff --git a/django/interfaces/api/views/quality/history.py b/django/interfaces/api/views/quality/history.py index 543eb99d..138e9cc1 100644 --- a/django/interfaces/api/views/quality/history.py +++ b/django/interfaces/api/views/quality/history.py @@ -37,7 +37,7 @@ def get_qc_histories( principal=request.principal, offset=query.offset, limit=query.limit, - order_by=query.order_by, + sortby=query.sortby, filtering=query.dict(exclude_unset=True), include=query.include, ) diff --git a/django/interfaces/api/views/quality/operation.py b/django/interfaces/api/views/quality/operation.py index 951bb3b9..03c79491 100644 --- a/django/interfaces/api/views/quality/operation.py +++ b/django/interfaces/api/views/quality/operation.py @@ -38,7 +38,7 @@ def get_qc_operations( session=session_id, offset=query.offset, limit=query.limit, - order_by=query.order_by, + sortby=query.sortby, ) diff --git a/django/interfaces/api/views/quality/session.py b/django/interfaces/api/views/quality/session.py index 67a77d3d..87b28d46 100644 --- a/django/interfaces/api/views/quality/session.py +++ b/django/interfaces/api/views/quality/session.py @@ -41,7 +41,7 @@ def get_qc_sessions( history=history_id, offset=query.offset, limit=query.limit, - order_by=query.order_by, + sortby=query.sortby, filtering=query.dict(exclude_unset=True), ) diff --git a/django/interfaces/api/views/sta/datastream.py b/django/interfaces/api/views/sta/datastream.py index 6a8e164d..5bcaafd4 100644 --- a/django/interfaces/api/views/sta/datastream.py +++ b/django/interfaces/api/views/sta/datastream.py @@ -50,7 +50,7 @@ def get_datastreams( principal=request.principal, offset=query.offset, limit=query.limit, - order_by=query.order_by, + sortby=query.sortby, filtering=query.dict(exclude_unset=True), include=query.include, ) @@ -140,7 +140,7 @@ def get_datastream_aggregation_statistics( return 200, datastream_service.list_aggregation_statistics( offset=query.offset, limit=query.limit, - order_desc=query.order_desc, + sort_desc=query.sort_desc, ) @@ -156,7 +156,7 @@ def get_datastream_statuses( return 200, datastream_service.list_statuses( offset=query.offset, limit=query.limit, - order_desc=query.order_desc, + sort_desc=query.sort_desc, ) @@ -172,7 +172,7 @@ def get_datastream_sampled_mediums( return 200, datastream_service.list_sampled_mediums( offset=query.offset, limit=query.limit, - order_desc=query.order_desc, + sort_desc=query.sort_desc, ) @@ -190,7 +190,7 @@ def get_datastream_linked_resource_types( return 200, datastream_service.list_linked_resource_types( offset=query.offset, limit=query.limit, - order_desc=query.order_desc, + sort_desc=query.sort_desc, ) diff --git a/django/interfaces/api/views/sta/method.py b/django/interfaces/api/views/sta/method.py index 4b473dd8..bd670daa 100644 --- a/django/interfaces/api/views/sta/method.py +++ b/django/interfaces/api/views/sta/method.py @@ -44,7 +44,7 @@ def get_methods( principal=request.principal, offset=query.offset, limit=query.limit, - order_by=query.order_by, + sortby=query.sortby, filtering=query.dict(exclude_unset=True), include=query.include, ) @@ -88,7 +88,7 @@ def get_types( return 200, method_service.list_types( offset=query.offset, limit=query.limit, - order_desc=query.order_desc, + sort_desc=query.sort_desc, ) diff --git a/django/interfaces/api/views/sta/monitoring_site.py b/django/interfaces/api/views/sta/monitoring_site.py index 5bf48f29..ade31097 100644 --- a/django/interfaces/api/views/sta/monitoring_site.py +++ b/django/interfaces/api/views/sta/monitoring_site.py @@ -56,7 +56,7 @@ def get_monitoring_sites( principal=request.principal, offset=query.offset, limit=query.limit, - order_by=query.order_by, + sortby=query.sortby, filtering=query.dict(exclude_unset=True), include=query.include, ) @@ -190,7 +190,7 @@ def get_site_types( return 200, monitoring_site_service.list_site_types( offset=query.offset, limit=query.limit, - order_desc=query.order_desc, + sort_desc=query.sort_desc, ) @@ -219,7 +219,7 @@ def get_monitoring_site_linked_resource_types( return 200, monitoring_site_service.list_linked_resource_types( offset=query.offset, limit=query.limit, - order_desc=query.order_desc, + sort_desc=query.sort_desc, ) diff --git a/django/interfaces/api/views/sta/observation.py b/django/interfaces/api/views/sta/observation.py index 6f29e82d..d96d11f2 100644 --- a/django/interfaces/api/views/sta/observation.py +++ b/django/interfaces/api/views/sta/observation.py @@ -54,7 +54,7 @@ def get_observations( response=response, offset=query.offset, limit=query.limit, - order_by=query.order_by, + sortby=query.sortby, filtering=query.dict(exclude_unset=True), response_format=query.response_format, include=query.include, diff --git a/django/interfaces/api/views/sta/observed_property.py b/django/interfaces/api/views/sta/observed_property.py index b56d3711..e25946da 100644 --- a/django/interfaces/api/views/sta/observed_property.py +++ b/django/interfaces/api/views/sta/observed_property.py @@ -44,7 +44,7 @@ def get_observed_properties( principal=request.principal, offset=query.offset, limit=query.limit, - order_by=query.order_by, + sortby=query.sortby, filtering=query.dict(exclude_unset=True), include=query.include, ) @@ -90,7 +90,7 @@ def get_datastream_aggregation_statistics( return 200, observed_property_service.list_variable_types( offset=query.offset, limit=query.limit, - order_desc=query.order_desc, + sort_desc=query.sort_desc, ) diff --git a/django/interfaces/api/views/sta/processing_level.py b/django/interfaces/api/views/sta/processing_level.py index bb3aff35..388e3f34 100644 --- a/django/interfaces/api/views/sta/processing_level.py +++ b/django/interfaces/api/views/sta/processing_level.py @@ -43,7 +43,7 @@ def get_processing_levels( principal=request.principal, offset=query.offset, limit=query.limit, - order_by=query.order_by, + sortby=query.sortby, filtering=query.dict(exclude_unset=True), include=query.include, ) diff --git a/django/interfaces/api/views/sta/result_qualifier.py b/django/interfaces/api/views/sta/result_qualifier.py index 2cdfb089..5995ac9b 100644 --- a/django/interfaces/api/views/sta/result_qualifier.py +++ b/django/interfaces/api/views/sta/result_qualifier.py @@ -43,7 +43,7 @@ def get_result_qualifiers( principal=request.principal, offset=query.offset, limit=query.limit, - order_by=query.order_by, + sortby=query.sortby, filtering=query.dict(exclude_unset=True), include=query.include, ) diff --git a/django/interfaces/api/views/sta/unit.py b/django/interfaces/api/views/sta/unit.py index 0710a80c..b47964d8 100644 --- a/django/interfaces/api/views/sta/unit.py +++ b/django/interfaces/api/views/sta/unit.py @@ -44,7 +44,7 @@ def get_units( principal=request.principal, offset=query.offset, limit=query.limit, - order_by=query.order_by, + sortby=query.sortby, filtering=query.dict(exclude_unset=True), include=query.include, ) @@ -87,7 +87,7 @@ def get_unit_types( return 200, unit_service.list_unit_types( offset=query.offset, limit=query.limit, - order_desc=query.order_desc, + sort_desc=query.sort_desc, ) diff --git a/django/processing/etl/migrations/0011_dataconnection_search_vector_and_more.py b/django/processing/etl/migrations/0011_dataconnection_search_vector_and_more.py new file mode 100644 index 00000000..41879dfe --- /dev/null +++ b/django/processing/etl/migrations/0011_dataconnection_search_vector_and_more.py @@ -0,0 +1,92 @@ +# Generated by Django 6.0.7 on 2026-09-17 20:54 + +import django.contrib.postgres.indexes +import django.contrib.postgres.search +from django.db import migrations + + +def _search_vector_trigger_sql( + table: str, + field_weights: dict[str, str], + vector_column: str = "search_vector", +) -> list[migrations.RunSQL]: + """ + Builds the migration operations needed to keep a `SearchVectorField` current via a + Postgres trigger, plus a one-time backfill for existing rows. + + INSERT and UPDATE are handled by separate triggers rather than one combined + `BEFORE INSERT OR UPDATE` trigger: the UPDATE trigger has a `WHEN` guard comparing + OLD/NEW on only the source columns, so it skips recomputing the vector when a save + touches unrelated columns (e.g. observation-statistics updates). INSERT has no OLD + row to compare against, so it always runs unconditionally. + """ + + function_name = f"{table}_{vector_column}_trigger" + insert_trigger_name = f"{table}_{vector_column}_ins_trg" + update_trigger_name = f"{table}_{vector_column}_upd_trg" + + new_expr = " || ".join( + f"setweight(to_tsvector('english', coalesce(NEW.{field}, '')), '{weight}')" + for field, weight in field_weights.items() + ) + backfill_expr = " || ".join( + f"setweight(to_tsvector('english', coalesce({field}, '')), '{weight}')" + for field, weight in field_weights.items() + ) + base_fields = dict.fromkeys(field.split("::", 1)[0] for field in field_weights) + when_condition = " OR ".join( + f"OLD.{field} IS DISTINCT FROM NEW.{field}" for field in base_fields + ) + + forward_sql = f""" + CREATE OR REPLACE FUNCTION {function_name}() RETURNS trigger AS $$ + BEGIN + NEW.{vector_column} := {new_expr}; + RETURN NEW; + END + $$ LANGUAGE plpgsql; + + DROP TRIGGER IF EXISTS {insert_trigger_name} ON {table}; + CREATE TRIGGER {insert_trigger_name} + BEFORE INSERT ON {table} + FOR EACH ROW EXECUTE FUNCTION {function_name}(); + + DROP TRIGGER IF EXISTS {update_trigger_name} ON {table}; + CREATE TRIGGER {update_trigger_name} + BEFORE UPDATE ON {table} + FOR EACH ROW WHEN ({when_condition}) + EXECUTE FUNCTION {function_name}(); + + UPDATE {table} SET {vector_column} = {backfill_expr}; + """ + + reverse_sql = f""" + DROP TRIGGER IF EXISTS {insert_trigger_name} ON {table}; + DROP TRIGGER IF EXISTS {update_trigger_name} ON {table}; + DROP FUNCTION IF EXISTS {function_name}(); + """ + + return [migrations.RunSQL(sql=forward_sql, reverse_sql=reverse_sql)] + + +class Migration(migrations.Migration): + + dependencies = [ + ('etl', '0010_etlmapping_unique_etl_mapping_target_datastream_and_more'), + ('iam', '0012_workspace_search_vector_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='dataconnection', + name='search_vector', + field=django.contrib.postgres.search.SearchVectorField(editable=False, null=True), + ), + *_search_vector_trigger_sql( + "etl_dataconnection", {"name": "A", "description": "B", "timezone_type": "C"} + ), + migrations.AddIndex( + model_name='dataconnection', + index=django.contrib.postgres.indexes.GinIndex(fields=['search_vector'], name='etl_dataconn_search_gin'), + ), + ] diff --git a/django/processing/etl/models/data_connection.py b/django/processing/etl/models/data_connection.py index 453bba03..27691caf 100644 --- a/django/processing/etl/models/data_connection.py +++ b/django/processing/etl/models/data_connection.py @@ -8,6 +8,8 @@ from django.core.exceptions import ValidationError from django.db import models +from django.contrib.postgres.indexes import GinIndex +from django.contrib.postgres.search import SearchVectorField from django.db.models.signals import pre_delete from django.dispatch import receiver from django_celery_beat.models import PeriodicTask @@ -79,9 +81,13 @@ class DataConnection(models.Model, ResourcePermissionMixin): auth_header_value = models.TextField(null=True, blank=True) timezone_type = models.CharField(max_length=255, choices=TimezoneType, null=True, blank=True) timezone = models.CharField(max_length=255, blank=True, null=True) + search_vector = SearchVectorField(null=True, editable=False) class Meta: app_label = "etl" + indexes = [ + GinIndex(fields=["search_vector"], name="etl_dataconn_search_gin"), + ] def __str__(self): return f"{self.name} - {self.id}" diff --git a/django/processing/orchestration/migrations/0002_task_search_vector_task_orch_task_search_gin.py b/django/processing/orchestration/migrations/0002_task_search_vector_task_orch_task_search_gin.py new file mode 100644 index 00000000..b4f69069 --- /dev/null +++ b/django/processing/orchestration/migrations/0002_task_search_vector_task_orch_task_search_gin.py @@ -0,0 +1,90 @@ +# Generated by Django 6.0.7 on 2026-09-17 20:54 + +import django.contrib.postgres.indexes +import django.contrib.postgres.search +from django.db import migrations + + +def _search_vector_trigger_sql( + table: str, + field_weights: dict[str, str], + vector_column: str = "search_vector", +) -> list[migrations.RunSQL]: + """ + Builds the migration operations needed to keep a `SearchVectorField` current via a + Postgres trigger, plus a one-time backfill for existing rows. + + INSERT and UPDATE are handled by separate triggers rather than one combined + `BEFORE INSERT OR UPDATE` trigger: the UPDATE trigger has a `WHEN` guard comparing + OLD/NEW on only the source columns, so it skips recomputing the vector when a save + touches unrelated columns (e.g. observation-statistics updates). INSERT has no OLD + row to compare against, so it always runs unconditionally. + """ + + function_name = f"{table}_{vector_column}_trigger" + insert_trigger_name = f"{table}_{vector_column}_ins_trg" + update_trigger_name = f"{table}_{vector_column}_upd_trg" + + new_expr = " || ".join( + f"setweight(to_tsvector('english', coalesce(NEW.{field}, '')), '{weight}')" + for field, weight in field_weights.items() + ) + backfill_expr = " || ".join( + f"setweight(to_tsvector('english', coalesce({field}, '')), '{weight}')" + for field, weight in field_weights.items() + ) + base_fields = dict.fromkeys(field.split("::", 1)[0] for field in field_weights) + when_condition = " OR ".join( + f"OLD.{field} IS DISTINCT FROM NEW.{field}" for field in base_fields + ) + + forward_sql = f""" + CREATE OR REPLACE FUNCTION {function_name}() RETURNS trigger AS $$ + BEGIN + NEW.{vector_column} := {new_expr}; + RETURN NEW; + END + $$ LANGUAGE plpgsql; + + DROP TRIGGER IF EXISTS {insert_trigger_name} ON {table}; + CREATE TRIGGER {insert_trigger_name} + BEFORE INSERT ON {table} + FOR EACH ROW EXECUTE FUNCTION {function_name}(); + + DROP TRIGGER IF EXISTS {update_trigger_name} ON {table}; + CREATE TRIGGER {update_trigger_name} + BEFORE UPDATE ON {table} + FOR EACH ROW WHEN ({when_condition}) + EXECUTE FUNCTION {function_name}(); + + UPDATE {table} SET {vector_column} = {backfill_expr}; + """ + + reverse_sql = f""" + DROP TRIGGER IF EXISTS {insert_trigger_name} ON {table}; + DROP TRIGGER IF EXISTS {update_trigger_name} ON {table}; + DROP FUNCTION IF EXISTS {function_name}(); + """ + + return [migrations.RunSQL(sql=forward_sql, reverse_sql=reverse_sql)] + + +class Migration(migrations.Migration): + + dependencies = [ + ('django_celery_beat', '0019_alter_periodictasks_options'), + ('orchestration', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='task', + name='search_vector', + field=django.contrib.postgres.search.SearchVectorField(editable=False, null=True), + ), + *_search_vector_trigger_sql("orchestration_task", {"name": "A", "description": "B"}), + migrations.AddIndex( + model_name='task', + index=django.contrib.postgres.indexes.GinIndex(fields=['search_vector'], name='orch_task_search_gin'), + ), + ] diff --git a/django/processing/orchestration/models/task.py b/django/processing/orchestration/models/task.py index 0e3803f4..4d8cdba5 100644 --- a/django/processing/orchestration/models/task.py +++ b/django/processing/orchestration/models/task.py @@ -1,6 +1,8 @@ import uuid from django.db import models +from django.contrib.postgres.indexes import GinIndex +from django.contrib.postgres.search import SearchVectorField from django.db.models.signals import pre_delete from django.dispatch import receiver from django_celery_beat.models import PeriodicTask @@ -16,9 +18,13 @@ class Task(models.Model): PeriodicTask, null=True, blank=True, on_delete=models.SET_NULL, related_name="orchestration_task" ) next_run_at = models.DateTimeField(null=True, blank=True) + search_vector = SearchVectorField(null=True, editable=False) class Meta: app_label = "orchestration" + indexes = [ + GinIndex(fields=["search_vector"], name="orch_task_search_gin"), + ] def __str__(self): return self.name diff --git a/django/processing/orchestration/services/task.py b/django/processing/orchestration/services/task.py index e52505f7..1c671e98 100644 --- a/django/processing/orchestration/services/task.py +++ b/django/processing/orchestration/services/task.py @@ -6,7 +6,6 @@ from django.db import transaction from django.db.models import QuerySet, Subquery, OuterRef from django.contrib.auth import get_user_model -from django.contrib.postgres.search import SearchVector, SearchQuery from core.types import Unset from core.iam.models import ServiceAccount @@ -25,8 +24,8 @@ class TaskService(SchedulingService, Generic[T]): task_model: type[T] - task_run_order_by_fields = ("id", "status", "startedAt", "finishedAt") - task_run_order_by_aliases = {"startedAt": "started_at", "finishedAt": "finished_at"} + task_run_sortby_fields = ("id", "status", "startedAt", "finishedAt") + task_run_sortby_aliases = {"startedAt": "started_at", "finishedAt": "finished_at"} @validate_call(config=ConfigDict(arbitrary_types_allowed=True)) def get( @@ -127,8 +126,7 @@ def get_run_collection( principal: User | ServiceAccount | AnonymousPrincipal | Unset = Unset, offset: int = Field(ge=0, default=0), limit: int = Field(ge=0, le=100000, default=100), - order_by: list[str] = Field(default_factory=list), - search_term: str | Unset = Unset, + sortby: list[str] = Field(default_factory=list), status: list[str] | Unset = Unset, started_at__gte: datetime | Unset = Unset, started_at__lte: datetime | Unset = Unset, @@ -143,10 +141,6 @@ def get_run_collection( queryset = TaskRun.objects.filter(task=task) - if search_term is not Unset: - search_vector = SearchVector("status", "message") - queryset = queryset.annotate(search=search_vector).filter(search=SearchQuery(search_term)) - if status is not Unset: queryset = queryset.filter(status__in=status) @@ -162,16 +156,17 @@ def get_run_collection( if finished_at__lte is not Unset: queryset = queryset.filter(finished_at__lte=finished_at__lte) - if order_by: - allowed_fields = [ - *self.task_run_order_by_fields, - *[f"-{f}" for f in self.task_run_order_by_fields], - ] - queryset = APIService.apply_ordering( - queryset, order_by, allowed_fields, field_aliases=self.task_run_order_by_aliases - ) - else: - queryset = queryset.order_by("-started_at", "-id") + allowed_fields = [ + *self.task_run_sortby_fields, + *[f"-{f}" for f in self.task_run_sortby_fields], + ] + queryset = APIService.apply_sorting( + queryset, + sortby, + allowed_fields, + field_aliases=self.task_run_sortby_aliases, + default_sortby=("-started_at", "-id"), + ) count = queryset.count() @@ -199,7 +194,7 @@ def annotate_latest_run(cls, queryset: QuerySet, fields: set[str] | None = None) Annotate a task queryset with fields from the latest run. Each annotation is a correlated subquery evaluated per row, so callers that only need a - subset (e.g., for filtering or ordering) should pass ``fields`` to avoid computing the + subset (e.g., for filtering or sorting) should pass ``fields`` to avoid computing the rest. See ``attach_latest_runs`` for resolving full latest-run data on a page of results without the per-row subqueries. """ @@ -222,7 +217,7 @@ def attach_latest_runs(cls, tasks: list[T]) -> list[T]: Resolves the most recent run per task with one ``DISTINCT ON`` query instead of the six correlated subqueries ``annotate_latest_run`` would add to the (un-paginated) collection query. This keeps the collection scan free of latest-run subqueries unless they are needed - for filtering or ordering. + for filtering or sorting. """ task_ids = [task.pk for task in tasks] diff --git a/django/processing/products/migrations/0006_ratingcurve_search_vector_and_more.py b/django/processing/products/migrations/0006_ratingcurve_search_vector_and_more.py new file mode 100644 index 00000000..a540a7a9 --- /dev/null +++ b/django/processing/products/migrations/0006_ratingcurve_search_vector_and_more.py @@ -0,0 +1,92 @@ +# Generated by Django 6.0.7 on 2026-09-17 20:54 + +import django.contrib.postgres.indexes +import django.contrib.postgres.search +from django.db import migrations + + +def _search_vector_trigger_sql( + table: str, + field_weights: dict[str, str], + vector_column: str = "search_vector", +) -> list[migrations.RunSQL]: + """ + Builds the migration operations needed to keep a `SearchVectorField` current via a + Postgres trigger, plus a one-time backfill for existing rows. + + INSERT and UPDATE are handled by separate triggers rather than one combined + `BEFORE INSERT OR UPDATE` trigger: the UPDATE trigger has a `WHEN` guard comparing + OLD/NEW on only the source columns, so it skips recomputing the vector when a save + touches unrelated columns (e.g. observation-statistics updates). INSERT has no OLD + row to compare against, so it always runs unconditionally. + """ + + function_name = f"{table}_{vector_column}_trigger" + insert_trigger_name = f"{table}_{vector_column}_ins_trg" + update_trigger_name = f"{table}_{vector_column}_upd_trg" + + new_expr = " || ".join( + f"setweight(to_tsvector('english', coalesce(NEW.{field}, '')), '{weight}')" + for field, weight in field_weights.items() + ) + backfill_expr = " || ".join( + f"setweight(to_tsvector('english', coalesce({field}, '')), '{weight}')" + for field, weight in field_weights.items() + ) + base_fields = dict.fromkeys(field.split("::", 1)[0] for field in field_weights) + when_condition = " OR ".join( + f"OLD.{field} IS DISTINCT FROM NEW.{field}" for field in base_fields + ) + + forward_sql = f""" + CREATE OR REPLACE FUNCTION {function_name}() RETURNS trigger AS $$ + BEGIN + NEW.{vector_column} := {new_expr}; + RETURN NEW; + END + $$ LANGUAGE plpgsql; + + DROP TRIGGER IF EXISTS {insert_trigger_name} ON {table}; + CREATE TRIGGER {insert_trigger_name} + BEFORE INSERT ON {table} + FOR EACH ROW EXECUTE FUNCTION {function_name}(); + + DROP TRIGGER IF EXISTS {update_trigger_name} ON {table}; + CREATE TRIGGER {update_trigger_name} + BEFORE UPDATE ON {table} + FOR EACH ROW WHEN ({when_condition}) + EXECUTE FUNCTION {function_name}(); + + UPDATE {table} SET {vector_column} = {backfill_expr}; + """ + + reverse_sql = f""" + DROP TRIGGER IF EXISTS {insert_trigger_name} ON {table}; + DROP TRIGGER IF EXISTS {update_trigger_name} ON {table}; + DROP FUNCTION IF EXISTS {function_name}(); + """ + + return [migrations.RunSQL(sql=forward_sql, reverse_sql=reverse_sql)] + + +class Migration(migrations.Migration): + + dependencies = [ + ('products', '0005_ratingcurvepoint_unique_rating_curve_point_input_value'), + ('sta', '0017_datastream_search_vector_method_search_vector_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='ratingcurve', + name='search_vector', + field=django.contrib.postgres.search.SearchVectorField(editable=False, null=True), + ), + *_search_vector_trigger_sql( + "products_ratingcurve", {"name": "A", "description": "B", "fitting_method": "C"} + ), + migrations.AddIndex( + model_name='ratingcurve', + index=django.contrib.postgres.indexes.GinIndex(fields=['search_vector'], name='products_ratingcurve_search'), + ), + ] diff --git a/django/processing/products/models/rating_curve.py b/django/processing/products/models/rating_curve.py index d4370ac2..0a95d422 100644 --- a/django/processing/products/models/rating_curve.py +++ b/django/processing/products/models/rating_curve.py @@ -1,6 +1,8 @@ import uuid from django.db import models +from django.contrib.postgres.indexes import GinIndex +from django.contrib.postgres.search import SearchVectorField from core.iam.permissions.mixins import ResourcePermissionMixin from core.iam.permissions.registry import register_resource_type @@ -23,9 +25,13 @@ class RatingCurve(models.Model, ResourcePermissionMixin): name = models.CharField(max_length=255) description = models.TextField(null=True, blank=True) fitting_method = models.CharField(max_length=20, choices=FittingMethod) + search_vector = SearchVectorField(null=True, editable=False) class Meta: app_label = "products" + indexes = [ + GinIndex(fields=["search_vector"], name="products_ratingcurve_search"), + ] def __str__(self): return f"{self.name} - {self.id}" diff --git a/django/tests/core/test_service.py b/django/tests/core/test_service.py index 5989f3f2..1bdd206a 100644 --- a/django/tests/core/test_service.py +++ b/django/tests/core/test_service.py @@ -2,22 +2,22 @@ from core.sta.models import Datastream -def test_apply_ordering_appends_id_tiebreaker_for_deterministic_pagination(): +def test_apply_sorting_appends_id_tiebreaker_for_deterministic_pagination(): """Regression test for #484 ("Not all datastreams show up in aggregation/expression form"). - Rows that tie on the requested ordering field (e.g. many datastreams + Rows that tie on the requested sorting field (e.g., many datastreams named "Mean daily discharge (gpm) - provisional" across different sites) have no guaranteed relative order. Pages are fetched with independent queries (see the client's `paginatedFetch`), so without a unique tiebreaker a tied row can land on neither page and silently disappear from every page of results. Appending the primary key as a - final sort key makes ordering - and therefore pagination - deterministic. + final sort key makes sorting - and therefore pagination - deterministic. """ - queryset = APIService.apply_ordering(Datastream.objects.all(), ["name"], ["name"]) + queryset = APIService.apply_sorting(Datastream.objects.all(), ["name"], ["name"]) assert queryset.query.order_by == ("name", "id") -def test_apply_ordering_does_not_duplicate_an_already_requested_id(): - queryset = APIService.apply_ordering(Datastream.objects.all(), ["id"], ["id"]) +def test_apply_sorting_does_not_duplicate_an_already_requested_id(): + queryset = APIService.apply_sorting(Datastream.objects.all(), ["id"], ["id"]) assert queryset.query.order_by == ("id",) diff --git a/django/tests/interfaces/api/test_data_connection_views.py b/django/tests/interfaces/api/test_data_connection_views.py index 760b1302..d6f67754 100644 --- a/django/tests/interfaces/api/test_data_connection_views.py +++ b/django/tests/interfaces/api/test_data_connection_views.py @@ -98,6 +98,51 @@ def test_get_data_connections_returns_401_when_unauthenticated(client): assert response.status_code == 401 +# --- full-text search (q) ------------------------------------------------------------ + + +def test_get_data_connections_q_matches_name(client): + owner = UserFactory() + workspace = WorkspaceFactory(owner=owner) + match = _make_data_connection(workspace, name="Zephyr Gauge Feed") + _make_data_connection(workspace, name="Unrelated Feed") + client.force_login(owner) + + response = client.get(DATA_CONNECTIONS_URL, {"q": "Zephyr"}) + + assert response.status_code == 200 + assert {d["id"] for d in response.json()["data"]} == {str(match.id)} + + +def test_get_data_connections_q_matches_timezone_type_field(client): + owner = UserFactory() + workspace = WorkspaceFactory(owner=owner) + match = _make_data_connection( + workspace, name="Site A", timezone_type="iana", timezone="America/Denver" + ) + _make_data_connection(workspace, name="Site B", timezone_type=None, timezone=None) + client.force_login(owner) + + response = client.get(DATA_CONNECTIONS_URL, {"q": "iana"}) + + assert {d["id"] for d in response.json()["data"]} == {str(match.id)} + + +def test_get_data_connections_q_orders_by_relevance_by_default(client): + owner = UserFactory() + workspace = WorkspaceFactory(owner=owner) + name_match = _make_data_connection(workspace, name="Willowbrook Feed") + description_match = _make_data_connection( + workspace, name="Other Feed", description="Willowbrook mentioned here" + ) + client.force_login(owner) + + response = client.get(DATA_CONNECTIONS_URL, {"q": "Willowbrook"}) + + ids = [d["id"] for d in response.json()["data"]] + assert ids == [str(name_match.id), str(description_match.id)] + + def test_get_data_connections_include_workspace_sideloads_it(client): owner = UserFactory() workspace = WorkspaceFactory(owner=owner) diff --git a/django/tests/interfaces/api/test_data_product_transformation_views.py b/django/tests/interfaces/api/test_data_product_transformation_views.py index 08b48983..c7c865a4 100644 --- a/django/tests/interfaces/api/test_data_product_transformation_views.py +++ b/django/tests/interfaces/api/test_data_product_transformation_views.py @@ -333,7 +333,7 @@ def test_get_transformations_include_does_not_scale_queries_with_transformation_ assert len(large.captured_queries) == len(small.captured_queries) -def test_get_transformations_orders_by_output_datastream_id(client): +def test_get_transformations_sorts_by_output_datastream_id(client): owner = UserFactory() workspace = WorkspaceFactory(owner=owner) task, monitoring_site = _make_task_with_monitoring_site(workspace) @@ -342,7 +342,7 @@ def test_get_transformations_orders_by_output_datastream_id(client): client.force_login(owner) response = client.get( - TRANSFORMATIONS_URL, {"task_id": str(task.id), "order_by": "outputDatastreamId"} + TRANSFORMATIONS_URL, {"task_id": str(task.id), "sortby": "outputDatastreamId"} ) expected = sorted([str(t1.output_datastream_id), str(t2.output_datastream_id)]) @@ -350,13 +350,13 @@ def test_get_transformations_orders_by_output_datastream_id(client): assert [t["outputDatastreamId"] for t in response.json()["data"]] == expected -def test_get_transformations_order_by_rejects_unknown_field(client): +def test_get_transformations_sortby_rejects_unknown_field(client): owner = UserFactory() workspace = WorkspaceFactory(owner=owner) task, _ = _make_task_with_monitoring_site(workspace) client.force_login(owner) - response = client.get(TRANSFORMATIONS_URL, {"task_id": str(task.id), "order_by": "bogus"}) + response = client.get(TRANSFORMATIONS_URL, {"task_id": str(task.id), "sortby": "bogus"}) assert response.status_code == 400 diff --git a/django/tests/interfaces/api/test_datastream_views.py b/django/tests/interfaces/api/test_datastream_views.py index 6c4cfeea..c77c9853 100644 --- a/django/tests/interfaces/api/test_datastream_views.py +++ b/django/tests/interfaces/api/test_datastream_views.py @@ -115,6 +115,20 @@ def test_get_datastreams_returns_400_for_malformed_tag(client): assert response.status_code == 400 +# --- full-text search (q) ------------------------------------------------------------ + + +def test_get_datastreams_q_matches_tag_value(client): + workspace = WorkspaceFactory() + match = _make_datastream(workspace, name="Datastream A", tags={"season": "summer"}) + _make_datastream(workspace, name="Datastream B", tags={}) + + response = client.get(DATASTREAMS_URL, {"q": "summer"}) + + assert response.status_code == 200 + assert {d["id"] for d in response.json()["data"]} == {str(match.id)} + + # --- create_datastream --------------------------------------------------------------- diff --git a/django/tests/interfaces/api/test_etl_task_views.py b/django/tests/interfaces/api/test_etl_task_views.py index e285f40f..d5a0ef23 100644 --- a/django/tests/interfaces/api/test_etl_task_views.py +++ b/django/tests/interfaces/api/test_etl_task_views.py @@ -136,6 +136,22 @@ def test_get_etl_tasks_returns_401_when_unauthenticated(client): assert response.status_code == 401 +# --- full-text search (q) ------------------------------------------------------------ + + +def test_get_etl_tasks_q_matches_name(client): + owner = UserFactory() + workspace = WorkspaceFactory(owner=owner) + match = _make_etl_task(workspace, name="Zephyr Ingest Task") + _make_etl_task(workspace, name="Unrelated Task") + client.force_login(owner) + + response = client.get(ETL_TASKS_URL, {"q": "Zephyr"}) + + assert response.status_code == 200 + assert {t["id"] for t in response.json()["data"]} == {str(match.id)} + + # --- create_etl_task ----------------------------------------------------------------- @@ -383,7 +399,7 @@ def test_get_etl_task_run_returns_200_for_workspace_owner(client): assert response.json()["id"] == str(run.id) -def test_get_etl_task_runs_order_by_started_at_ascending(client): +def test_get_etl_task_runs_sortby_started_at_ascending(client): owner = UserFactory() workspace = WorkspaceFactory(owner=owner) task = _make_etl_task(workspace) @@ -392,20 +408,20 @@ def test_get_etl_task_runs_order_by_started_at_ascending(client): newer = TaskRun.objects.create(task=task, status="SUCCESS", started_at=now) client.force_login(owner) - response = client.get(f"{_detail_url(task.id)}/runs", {"order_by": "startedAt"}) + response = client.get(f"{_detail_url(task.id)}/runs", {"sortby": "startedAt"}) assert response.status_code == 200 ids = [r["id"] for r in response.json()["data"]] assert ids == [str(older.id), str(newer.id)] -def test_get_etl_task_runs_order_by_rejects_unknown_field(client): +def test_get_etl_task_runs_sortby_rejects_unknown_field(client): owner = UserFactory() workspace = WorkspaceFactory(owner=owner) task = _make_etl_task(workspace) client.force_login(owner) - response = client.get(f"{_detail_url(task.id)}/runs", {"order_by": "bogus"}) + response = client.get(f"{_detail_url(task.id)}/runs", {"sortby": "bogus"}) assert response.status_code == 400 diff --git a/django/tests/interfaces/api/test_monitoring_rule_views.py b/django/tests/interfaces/api/test_monitoring_rule_views.py index 5785bebe..6a144f91 100644 --- a/django/tests/interfaces/api/test_monitoring_rule_views.py +++ b/django/tests/interfaces/api/test_monitoring_rule_views.py @@ -320,7 +320,7 @@ def test_get_monitoring_rules_filters_by_rule_type(client): assert [r["id"] for r in response.json()["data"]] == [str(rule_a.id)] -def test_get_monitoring_rules_orders_by_rule_type(client): +def test_get_monitoring_rules_sorts_by_rule_type(client): owner = UserFactory() workspace = WorkspaceFactory(owner=owner) task, datastream = _make_task_with_datastream(workspace) @@ -328,7 +328,7 @@ def test_get_monitoring_rules_orders_by_rule_type(client): MonitoringRuleFactory(task=task, datastream=datastream, rule_type="missing_data") client.force_login(owner) - response = client.get(RULES_URL, {"task_id": str(task.id), "order_by": "ruleType"}) + response = client.get(RULES_URL, {"task_id": str(task.id), "sortby": "ruleType"}) assert response.status_code == 200 assert [r["ruleType"] for r in response.json()["data"]] == [ @@ -337,7 +337,7 @@ def test_get_monitoring_rules_orders_by_rule_type(client): ] -def test_get_monitoring_rules_orders_by_datastream_id(client): +def test_get_monitoring_rules_sorts_by_datastream_id(client): owner = UserFactory() workspace = WorkspaceFactory(owner=owner) task, datastream_a = _make_task_with_datastream(workspace) @@ -346,7 +346,7 @@ def test_get_monitoring_rules_orders_by_datastream_id(client): MonitoringRuleFactory(task=task, datastream=datastream_b) client.force_login(owner) - response = client.get(RULES_URL, {"task_id": str(task.id), "order_by": "-datastreamId"}) + response = client.get(RULES_URL, {"task_id": str(task.id), "sortby": "-datastreamId"}) datastream_ids_desc = sorted( [str(datastream_a.id), str(datastream_b.id)], reverse=True @@ -356,13 +356,13 @@ def test_get_monitoring_rules_orders_by_datastream_id(client): assert [r["datastreamId"] for r in response.json()["data"]] == datastream_ids_desc -def test_get_monitoring_rules_order_by_rejects_unknown_field(client): +def test_get_monitoring_rules_sortby_rejects_unknown_field(client): owner = UserFactory() workspace = WorkspaceFactory(owner=owner) task, _ = _make_task_with_datastream(workspace) client.force_login(owner) - response = client.get(RULES_URL, {"task_id": str(task.id), "order_by": "bogus"}) + response = client.get(RULES_URL, {"task_id": str(task.id), "sortby": "bogus"}) assert response.status_code == 400 diff --git a/django/tests/interfaces/api/test_monitoring_site_views.py b/django/tests/interfaces/api/test_monitoring_site_views.py index b7e3516c..6d0fdc41 100644 --- a/django/tests/interfaces/api/test_monitoring_site_views.py +++ b/django/tests/interfaces/api/test_monitoring_site_views.py @@ -99,6 +99,121 @@ def test_get_monitoring_sites_returns_400_for_malformed_tag(client): assert response.status_code == 400 +# --- full-text search (q) ------------------------------------------------------------ + + +def test_get_monitoring_sites_q_matches_name(client): + workspace = WorkspaceFactory() + match = MonitoringSiteFactory( + workspace=workspace, name="Zephyrsite Creek", description="A creek site.", type="Stream" + ) + MonitoringSiteFactory( + workspace=workspace, name="Unrelated Site", description="Nothing to do with it.", type="Stream" + ) + + response = client.get(MONITORING_SITES_URL, {"q": "Zephyrsite"}) + + assert response.status_code == 200 + assert {s["id"] for s in response.json()["data"]} == {str(match.id)} + + +def test_get_monitoring_sites_q_matches_type_field(client): + workspace = WorkspaceFactory() + match = MonitoringSiteFactory( + workspace=workspace, name="Site A", description="desc a", type="Groundwater Well" + ) + MonitoringSiteFactory(workspace=workspace, name="Site B", description="desc b", type="Stream") + + response = client.get(MONITORING_SITES_URL, {"q": "Groundwater"}) + + assert {s["id"] for s in response.json()["data"]} == {str(match.id)} + + +def test_get_monitoring_sites_q_matches_tag_value(client): + workspace = WorkspaceFactory() + match = MonitoringSiteFactory( + workspace=workspace, name="Site A", type="Stream", tags={"season": "summer"} + ) + MonitoringSiteFactory(workspace=workspace, name="Site B", type="Stream", tags={}) + + response = client.get(MONITORING_SITES_URL, {"q": "summer"}) + + assert {s["id"] for s in response.json()["data"]} == {str(match.id)} + + +def test_get_monitoring_sites_q_comma_separated_terms_are_or(client): + workspace = WorkspaceFactory() + site_a = MonitoringSiteFactory(workspace=workspace, name="Alphasite", type="Stream") + site_b = MonitoringSiteFactory(workspace=workspace, name="Betasite", type="Stream") + MonitoringSiteFactory(workspace=workspace, name="Gammasite", type="Stream") + + response = client.get(MONITORING_SITES_URL, {"q": "Alphasite, Betasite"}) + + assert {s["id"] for s in response.json()["data"]} == {str(site_a.id), str(site_b.id)} + + +def test_get_monitoring_sites_q_whitespace_separated_terms_are_and(client): + workspace = WorkspaceFactory() + match = MonitoringSiteFactory(workspace=workspace, name="Redfish Creek", type="Stream") + MonitoringSiteFactory(workspace=workspace, name="Redfish Lake", type="Stream") + + response = client.get(MONITORING_SITES_URL, {"q": "Redfish Creek"}) + + assert {s["id"] for s in response.json()["data"]} == {str(match.id)} + + +def test_get_monitoring_sites_q_is_case_insensitive(client): + workspace = WorkspaceFactory() + match = MonitoringSiteFactory(workspace=workspace, name="Zephyrsite Creek", type="Stream") + + response = client.get(MONITORING_SITES_URL, {"q": "ZEPHYRSITE"}) + + assert {s["id"] for s in response.json()["data"]} == {str(match.id)} + + +def test_get_monitoring_sites_q_orders_by_relevance_by_default(client): + workspace = WorkspaceFactory() + name_match = MonitoringSiteFactory(workspace=workspace, name="Willowbrook", type="Stream") + description_match = MonitoringSiteFactory( + workspace=workspace, + name="Other Site", + description="Willowbrook mentioned here", + type="Stream", + ) + + response = client.get(MONITORING_SITES_URL, {"q": "Willowbrook"}) + + ids = [s["id"] for s in response.json()["data"]] + assert ids == [str(name_match.id), str(description_match.id)] + + +def test_get_monitoring_sites_user_sortby_takes_precedence_over_relevance(client): + workspace = WorkspaceFactory() + name_match = MonitoringSiteFactory(workspace=workspace, name="Bravo Fernwood", type="Stream") + description_match = MonitoringSiteFactory( + workspace=workspace, + name="Alpha Site", + description="Fernwood mentioned", + type="Stream", + ) + + response = client.get(MONITORING_SITES_URL, {"q": "Fernwood", "sortby": "name"}) + + names = [s["name"] for s in response.json()["data"]] + assert names == [description_match.name, name_match.name] + + +def test_get_monitoring_sites_empty_q_does_not_filter(client): + workspace = WorkspaceFactory() + MonitoringSiteFactory(workspace=workspace, name="Site A", type="Stream") + MonitoringSiteFactory(workspace=workspace, name="Site B", type="Stream") + + without_q = client.get(MONITORING_SITES_URL) + with_empty_q = client.get(MONITORING_SITES_URL, {"q": ""}) + + assert without_q.json()["meta"]["totalCount"] == with_empty_q.json()["meta"]["totalCount"] == 2 + + # --- create_monitoring_site ------------------------------------------------------------------ diff --git a/django/tests/interfaces/api/test_rating_curve_views.py b/django/tests/interfaces/api/test_rating_curve_views.py index d905941e..f575b5e1 100644 --- a/django/tests/interfaces/api/test_rating_curve_views.py +++ b/django/tests/interfaces/api/test_rating_curve_views.py @@ -291,7 +291,7 @@ def test_get_rating_curves_filters_by_workspace_id(client): @pytest.mark.parametrize( - "order_by_value,expected_order", + "sortby_value,expected_order", [ ("name", ["A Curve", "B Curve"]), ("-name", ["B Curve", "A Curve"]), @@ -299,7 +299,7 @@ def test_get_rating_curves_filters_by_workspace_id(client): ("workspaceName", ["A Curve", "B Curve"]), ], ) -def test_get_rating_curves_orders_by_requested_field(client, order_by_value, expected_order): +def test_get_rating_curves_sorts_by_requested_field(client, sortby_value, expected_order): owner = UserFactory() workspace_a = WorkspaceFactory(owner=owner, name="Workspace A") workspace_b = WorkspaceFactory(owner=owner, name="Workspace B") @@ -309,13 +309,13 @@ def test_get_rating_curves_orders_by_requested_field(client, order_by_value, exp RatingCurveFactory(monitoring_site=site_b, name="B Curve") client.force_login(owner) - response = client.get(RATING_CURVES_URL, {"order_by": order_by_value}) + response = client.get(RATING_CURVES_URL, {"sortby": sortby_value}) assert response.status_code == 200 assert [r["name"] for r in response.json()["data"]] == expected_order -def test_get_rating_curves_orders_by_workspace_id(client): +def test_get_rating_curves_sorts_by_workspace_id(client): owner = UserFactory() workspace_a = WorkspaceFactory(owner=owner) workspace_b = WorkspaceFactory(owner=owner) @@ -325,7 +325,7 @@ def test_get_rating_curves_orders_by_workspace_id(client): RatingCurveFactory(monitoring_site=site_b, name="Second") client.force_login(owner) - response = client.get(RATING_CURVES_URL, {"order_by": "workspaceId"}) + response = client.get(RATING_CURVES_URL, {"sortby": "workspaceId"}) workspace_ids_sorted = sorted([str(workspace_a.id), str(workspace_b.id)]) expected_first_name = "First" if workspace_ids_sorted[0] == str(workspace_a.id) else "Second" @@ -334,10 +334,10 @@ def test_get_rating_curves_orders_by_workspace_id(client): assert response.json()["data"][0]["name"] == expected_first_name -def test_get_rating_curves_order_by_rejects_unknown_field(client): +def test_get_rating_curves_sortby_rejects_unknown_field(client): client.force_login(UserFactory()) - response = client.get(RATING_CURVES_URL, {"order_by": "bogus"}) + response = client.get(RATING_CURVES_URL, {"sortby": "bogus"}) assert response.status_code == 400 diff --git a/packages/hydroserver-ts/src/api/__tests__/rating-curve.service.spec.ts b/packages/hydroserver-ts/src/api/__tests__/rating-curve.service.spec.ts index 67283560..a87cb62b 100644 --- a/packages/hydroserver-ts/src/api/__tests__/rating-curve.service.spec.ts +++ b/packages/hydroserver-ts/src/api/__tests__/rating-curve.service.spec.ts @@ -37,13 +37,13 @@ describe('RatingCurveService', () => { const client = new HydroServer({ host: 'https://hydro.example.com' }) const response = await client.ratingCurves.listItemsForMonitoringSite('monitoringSite-1', { - order_by: ['name'], + sortby: ['name'], }) expect(response).toHaveLength(1) const url = new URL(fetchMock.mock.calls[0][0]) expect(url.href).toBe( - 'https://hydro.example.com/api/data/data-product-rating-curves?order_by=name&monitoring_site_id=monitoringSite-1&offset=0&limit=200' + 'https://hydro.example.com/api/data/data-product-rating-curves?sortby=name&monitoring_site_id=monitoringSite-1&offset=0&limit=200' ) }) diff --git a/packages/hydroserver-ts/src/api/__tests__/task.service.spec.ts b/packages/hydroserver-ts/src/api/__tests__/task.service.spec.ts index 933315f1..5c8f19a3 100644 --- a/packages/hydroserver-ts/src/api/__tests__/task.service.spec.ts +++ b/packages/hydroserver-ts/src/api/__tests__/task.service.spec.ts @@ -52,7 +52,7 @@ describe('TaskService', () => { const client = new HydroServer({ host: 'https://hydro.example.com' }) const response = await client.tasks.getTaskRuns('task-1', { - order_by: ['-startedAt'], + sortby: ['-startedAt'], }) expect(response.ok).toBe(true) diff --git a/packages/hydroserver-ts/src/generated/data.types.d.ts b/packages/hydroserver-ts/src/generated/data.types.d.ts index 0e15f95e..c3df75d2 100644 --- a/packages/hydroserver-ts/src/generated/data.types.d.ts +++ b/packages/hydroserver-ts/src/generated/data.types.d.ts @@ -1981,6 +1981,12 @@ export interface components { * @default [] */ role_id: string[]; + /** + * Sortby + * @description Select one or more fields to sort the response by. + * @default [] + */ + sortby: ("id" | "roleId" | "-id" | "-roleId")[] | null; }; /** CollaboratorResponse */ CollaboratorResponse: { @@ -2089,12 +2095,6 @@ export interface components { * @default 0 */ offset: number | null; - /** - * Order By - * @description Select one or more fields to order the response by. - * @default [] - */ - order_by: ("id" | "name" | "timestampKey" | "timestampFormat" | "timezoneType" | "timezone" | "workspaceId" | "workspaceName" | "-id" | "-name" | "-timestampKey" | "-timestampFormat" | "-timezoneType" | "-timezone" | "-workspaceId" | "-workspaceName")[] | null; /** * Payload Type * @description Filter data connections by payload type. @@ -2106,6 +2106,17 @@ export interface components { * @description Comma-separated list of properties to include in the response. All properties are returned if omitted. */ properties?: ("id" | "name" | "description" | "sourceUrl" | "authHeaderName" | "authHeaderValue" | "timezoneType" | "timezone" | "workspaceId" | "payload" | "placeholderVariables" | "notification" | "taskCount" | "taskAttentionCount")[]; + /** + * Q + * @description Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND. + */ + q?: string | null; + /** + * Sortby + * @description Select one or more fields to sort the response by. + * @default [] + */ + sortby: ("id" | "name" | "timestampKey" | "timestampFormat" | "timezoneType" | "timezone" | "workspaceId" | "workspaceName" | "-id" | "-name" | "-timestampKey" | "-timestampFormat" | "-timezoneType" | "-timezone" | "-workspaceId" | "-workspaceName")[] | null; /** * Workspace Id * @description Filter data connections by workspace ID. @@ -2264,12 +2275,6 @@ export interface components { * @default 0 */ offset: number | null; - /** - * Order By - * @description Select one or more fields to order the response by. - * @default [] - */ - order_by: ("id" | "name" | "monitoringSiteId" | "monitoringSiteName" | "workspaceId" | "workspaceName" | "latestRunStatus" | "latestRunStartedAt" | "latestRunFinishedAt" | "-id" | "-name" | "-monitoringSiteId" | "-monitoringSiteName" | "-workspaceId" | "-workspaceName" | "-latestRunStatus" | "-latestRunStartedAt" | "-latestRunFinishedAt")[] | null; /** * Output Datastream Id * @description Filter data product tasks by output datastream ID. @@ -2281,12 +2286,23 @@ export interface components { * @description Comma-separated list of properties to include in the response. All properties are returned if omitted. */ properties?: ("id" | "workspaceId" | "monitoringSiteId" | "schedule" | "latestRun" | "transformationTypes" | "name" | "description")[]; + /** + * Q + * @description Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND. + */ + q?: string | null; /** * Rating Curve Id * @description Filter data product tasks by rating curve ID. * @default [] */ rating_curve_id: string[]; + /** + * Sortby + * @description Select one or more fields to sort the response by. + * @default [] + */ + sortby: ("id" | "name" | "monitoringSiteId" | "monitoringSiteName" | "workspaceId" | "workspaceName" | "latestRunStatus" | "latestRunStartedAt" | "latestRunFinishedAt" | "-id" | "-name" | "-monitoringSiteId" | "-monitoringSiteName" | "-workspaceId" | "-workspaceName" | "-latestRunStatus" | "-latestRunStartedAt" | "-latestRunFinishedAt")[] | null; /** * Transformation Type * @description Filter data product tasks by transformation type. @@ -2448,12 +2464,6 @@ export interface components { * @default 0 */ offset: number | null; - /** - * Order By - * @description Select one or more fields to order the response by. - * @default [] - */ - order_by: ("id" | "outputDatastreamId" | "-id" | "-outputDatastreamId")[] | null; /** * Output Datastream Id * @description Filter transformations by output datastream ID. @@ -2465,6 +2475,12 @@ export interface components { * @description Comma-separated list of properties to include in the response. All properties are returned if omitted. */ properties?: ("id" | "transformationType" | "outputDatastreamId" | "inputDatastreams" | "ratingCurveId" | "formula" | "aggregationMethod" | "outputIntervalUnits" | "outputInterval" | "timezoneType" | "timezone" | "minValues" | "stopOnNoData" | "stopOnError")[]; + /** + * Sortby + * @description Select one or more fields to sort the response by. + * @default [] + */ + sortby: ("id" | "outputDatastreamId" | "transformationType" | "-id" | "-outputDatastreamId" | "-transformationType")[] | null; /** * Task Id * @description Filter transformations by data product task ID. @@ -2756,12 +2772,6 @@ export interface components { * @default 0 */ offset: number | null; - /** - * Order By - * @description Select one or more fields to order the response by. - * @default [] - */ - order_by: ("name" | "observationType" | "sampledMedium" | "status" | "resultType" | "isPrivate" | "valueCount" | "phenomenonBeginTime" | "phenomenonEndTime" | "resultBeginTime" | "resultEndTime" | "-name" | "-observationType" | "-sampledMedium" | "-status" | "-resultType" | "-isPrivate" | "-valueCount" | "-phenomenonBeginTime" | "-phenomenonEndTime" | "-resultBeginTime" | "-resultEndTime")[] | null; /** * Phenomenon Begin Time Max * @description Sets the maximum phenomenon begin time of filtered datastreams. @@ -2793,6 +2803,11 @@ export interface components { * @description Comma-separated list of properties to include in the response. All properties are returned if omitted. */ properties?: ("id" | "workspaceId" | "name" | "description" | "observationType" | "sampledMedium" | "noDataValue" | "aggregationStatistic" | "timeAggregationInterval" | "status" | "resultType" | "valueCount" | "phenomenonBeginTime" | "phenomenonEndTime" | "resultBeginTime" | "resultEndTime" | "isPrivate" | "isVisible" | "timeAggregationIntervalUnit" | "intendedTimeSpacing" | "intendedTimeSpacingUnit" | "monitoringSiteId" | "methodId" | "observedPropertyId" | "processingLevelId" | "unitId" | "tags" | "linkedResources")[]; + /** + * Q + * @description Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND. + */ + q?: string | null; /** * Result Begin Time Max * @description Sets the maximum result begin time of filtered datastreams. @@ -2831,6 +2846,12 @@ export interface components { * @default [] */ sampled_medium: string[]; + /** + * Sortby + * @description Select one or more fields to sort the response by. + * @default [] + */ + sortby: ("name" | "observationType" | "sampledMedium" | "status" | "resultType" | "isPrivate" | "isVisible" | "aggregationStatistic" | "valueCount" | "phenomenonBeginTime" | "phenomenonEndTime" | "resultBeginTime" | "resultEndTime" | "-name" | "-observationType" | "-sampledMedium" | "-status" | "-resultType" | "-isPrivate" | "-isVisible" | "-aggregationStatistic" | "-valueCount" | "-phenomenonBeginTime" | "-phenomenonEndTime" | "-resultBeginTime" | "-resultEndTime")[] | null; /** * Status * @description Filter monitoring_sites by status. @@ -3045,17 +3066,17 @@ export interface components { * @default 0 */ offset: number | null; - /** - * Order By - * @description Select one or more fields to order the response by. - * @default [] - */ - order_by: ("id" | "sourceIdentifier" | "targetDatastreamId" | "-id" | "-sourceIdentifier" | "-targetDatastreamId")[] | null; /** * Properties * @description Comma-separated list of properties to include in the response. All properties are returned if omitted. */ properties?: ("id" | "sourceIdentifier" | "targetDatastreamId")[]; + /** + * Sortby + * @description Select one or more fields to sort the response by. + * @default [] + */ + sortby: ("id" | "sourceIdentifier" | "targetDatastreamId" | "-id" | "-sourceIdentifier" | "-targetDatastreamId")[] | null; /** * Source Identifier * @description Filter mappings by source identifier. @@ -3203,17 +3224,22 @@ export interface components { * @default 0 */ offset: number | null; - /** - * Order By - * @description Select one or more fields to order the response by. - * @default [] - */ - order_by: ("id" | "name" | "dataConnectionId" | "dataConnectionName" | "workspaceId" | "workspaceName" | "latestRunStatus" | "latestRunStartedAt" | "latestRunFinishedAt" | "-id" | "-name" | "-dataConnectionId" | "-dataConnectionName" | "-workspaceId" | "-workspaceName" | "-latestRunStatus" | "-latestRunStartedAt" | "-latestRunFinishedAt")[] | null; /** * Properties * @description Comma-separated list of properties to include in the response. All properties are returned if omitted. */ properties?: ("id" | "workspaceId" | "dataConnectionId" | "schedule" | "latestRun" | "mappingCount" | "name" | "description" | "taskVariables")[]; + /** + * Q + * @description Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND. + */ + q?: string | null; + /** + * Sortby + * @description Select one or more fields to sort the response by. + * @default [] + */ + sortby: ("id" | "name" | "dataConnectionId" | "dataConnectionName" | "workspaceId" | "workspaceName" | "latestRunStatus" | "latestRunStartedAt" | "latestRunFinishedAt" | "-id" | "-name" | "-dataConnectionId" | "-dataConnectionName" | "-workspaceId" | "-workspaceName" | "-latestRunStatus" | "-latestRunStartedAt" | "-latestRunFinishedAt")[] | null; /** * Workspace Id * @description Filter ETL tasks by workspace ID. @@ -3596,17 +3622,16 @@ export interface components { * @default 0 */ offset: number | null; - /** - * Order By - * @description Select one or more fields to order the response by. - * @default [] - */ - order_by: ("name" | "code" | "type" | "sensorModel" | "sensorModelManufacturer" | "-name" | "-code" | "-type" | "-sensorModel" | "-sensorModelManufacturer")[] | null; /** * Properties * @description Comma-separated list of properties to include in the response. All properties are returned if omitted. */ properties?: ("id" | "workspaceId" | "name" | "code" | "type" | "description" | "definition" | "sensorModel" | "sensorModelManufacturer" | "sensorModelDefinition")[]; + /** + * Q + * @description Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND. + */ + q?: string | null; /** * Sensor Model * @description Filter methods by sensor model @@ -3619,6 +3644,12 @@ export interface components { * @default [] */ sensor_model_manufacturer: string[]; + /** + * Sortby + * @description Select one or more fields to sort the response by. + * @default [] + */ + sortby: ("name" | "code" | "type" | "sensorModel" | "sensorModelManufacturer" | "definition" | "sensorModelDefinition" | "-name" | "-code" | "-type" | "-sensorModel" | "-sensorModelManufacturer" | "-definition" | "-sensorModelDefinition")[] | null; /** * Type * @description Filter methods by type @@ -3735,12 +3766,6 @@ export interface components { * @default 0 */ offset: number | null; - /** - * Order By - * @description Select one or more fields to order the response by. - * @default [] - */ - order_by: ("id" | "ruleType" | "datastreamId" | "-id" | "-ruleType" | "-datastreamId")[] | null; /** * Properties * @description Comma-separated list of properties to include in the response. All properties are returned if omitted. @@ -3752,6 +3777,12 @@ export interface components { * @default [] */ rule_type: string[]; + /** + * Sortby + * @description Select one or more fields to sort the response by. + * @default [] + */ + sortby: ("id" | "ruleType" | "datastreamId" | "lastCheckedAt" | "-id" | "-ruleType" | "-datastreamId" | "-lastCheckedAt")[] | null; /** * Task Id * @description Filter rules by monitoring task ID. @@ -4023,17 +4054,22 @@ export interface components { * @default 0 */ offset: number | null; - /** - * Order By - * @description Select one or more fields to order the response by. - * @default [] - */ - order_by: ("name" | "code" | "type" | "isPrivate" | "latitude" | "longitude" | "elevation_m" | "elevationDatum" | "adminArea1" | "adminArea2" | "country" | "-name" | "-code" | "-type" | "-isPrivate" | "-latitude" | "-longitude" | "-elevation_m" | "-elevationDatum" | "-adminArea1" | "-adminArea2" | "-country")[] | null; /** * Properties * @description Comma-separated list of properties to include in the response. All properties are returned if omitted. */ properties?: ("id" | "workspaceId" | "name" | "description" | "code" | "type" | "latitude" | "longitude" | "elevation_m" | "elevationDatum" | "adminArea1" | "adminArea2" | "country" | "dataDisclaimer" | "isPrivate" | "tags" | "linkedResources")[]; + /** + * Q + * @description Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND. + */ + q?: string | null; + /** + * Sortby + * @description Select one or more fields to sort the response by. + * @default [] + */ + sortby: ("name" | "code" | "type" | "isPrivate" | "latitude" | "longitude" | "elevation_m" | "elevationDatum" | "adminArea1" | "adminArea2" | "country" | "-name" | "-code" | "-type" | "-isPrivate" | "-latitude" | "-longitude" | "-elevation_m" | "-elevationDatum" | "-adminArea1" | "-adminArea2" | "-country")[] | null; /** * Tag * @description Filter monitoring sites by tag. Format tag filters as {key}:{value} @@ -4232,23 +4268,28 @@ export interface components { * @default 0 */ offset: number | null; - /** - * Order By - * @description Select one or more fields to order the response by. - * @default [] - */ - order_by: ("id" | "name" | "monitoringSiteId" | "monitoringSiteName" | "workspaceId" | "workspaceName" | "latestRunStatus" | "latestRunStartedAt" | "latestRunFinishedAt" | "-id" | "-name" | "-monitoringSiteId" | "-monitoringSiteName" | "-workspaceId" | "-workspaceName" | "-latestRunStatus" | "-latestRunStartedAt" | "-latestRunFinishedAt")[] | null; /** * Properties * @description Comma-separated list of properties to include in the response. All properties are returned if omitted. */ properties?: ("id" | "workspaceId" | "monitoringSiteId" | "schedule" | "latestRun" | "ruleTypeCounts" | "recipients" | "name" | "description")[]; + /** + * Q + * @description Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND. + */ + q?: string | null; /** * Rule Type * @description Filter monitoring tasks by rule type. * @default [] */ rule_type: string[]; + /** + * Sortby + * @description Select one or more fields to sort the response by. + * @default [] + */ + sortby: ("id" | "name" | "monitoringSiteId" | "monitoringSiteName" | "workspaceId" | "workspaceName" | "latestRunStatus" | "latestRunStartedAt" | "latestRunFinishedAt" | "-id" | "-name" | "-monitoringSiteId" | "-monitoringSiteName" | "-workspaceId" | "-workspaceName" | "-latestRunStatus" | "-latestRunStartedAt" | "-latestRunFinishedAt")[] | null; /** * Workspace Id * @description Filter monitoring tasks by workspace ID. @@ -4440,12 +4481,6 @@ export interface components { * @default 0 */ offset: number | null; - /** - * Order By - * @description Select one or more fields to order the response by. - * @default [] - */ - order_by: ("phenomenonTime" | "datastreamId" | "-phenomenonTime" | "-datastreamId")[] | null; /** * Phenomenon Time Max * @description Sets the maximum phenomenon time of filtered observations. @@ -4467,6 +4502,12 @@ export interface components { * @default [] */ result_qualifier_code: string[]; + /** + * Sortby + * @description Select one or more fields to sort the response by. + * @default [] + */ + sortby: ("phenomenonTime" | "datastreamId" | "-phenomenonTime" | "-datastreamId")[] | null; }; /** ObservationResponse */ ObservationResponse: { @@ -4588,17 +4629,22 @@ export interface components { * @default 0 */ offset: number | null; - /** - * Order By - * @description Select one or more fields to order the response by. - * @default [] - */ - order_by: ("name" | "type" | "code" | "-name" | "-type" | "-code")[] | null; /** * Properties * @description Comma-separated list of properties to include in the response. All properties are returned if omitted. */ properties?: ("id" | "workspaceId" | "name" | "definition" | "description" | "type" | "code")[]; + /** + * Q + * @description Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND. + */ + q?: string | null; + /** + * Sortby + * @description Select one or more fields to sort the response by. + * @default [] + */ + sortby: ("name" | "type" | "code" | "-name" | "-type" | "-code")[] | null; /** * Type * @description Filter observed properties by type @@ -5029,17 +5075,22 @@ export interface components { * @default 0 */ offset: number | null; - /** - * Order By - * @description Select one or more fields to order the response by. - * @default [] - */ - order_by: ("code" | "name" | "-code" | "-name")[] | null; /** * Properties * @description Comma-separated list of properties to include in the response. All properties are returned if omitted. */ properties?: ("id" | "workspaceId" | "code" | "name" | "description" | "definition")[]; + /** + * Q + * @description Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND. + */ + q?: string | null; + /** + * Sortby + * @description Select one or more fields to sort the response by. + * @default [] + */ + sortby: ("code" | "name" | "-code" | "-name")[] | null; /** * Workspace Id * @description Filter processing levels by workspace ID. @@ -5116,17 +5167,17 @@ export interface components { * @default 0 */ offset: number | null; - /** - * Order By - * @description Select one or more fields to order the response by. - * @default [] - */ - order_by: ("id" | "createdAt" | "phenomenonTimeStart" | "phenomenonTimeEnd" | "-id" | "-createdAt" | "-phenomenonTimeStart" | "-phenomenonTimeEnd")[] | null; /** * Properties * @description Comma-separated list of properties to include in the response. All properties are returned if omitted. */ properties?: ("id" | "managedDatastreamId" | "sourceDatastreamId" | "createdAt" | "phenomenonTimeStart" | "phenomenonTimeEnd" | "sourceChecksum" | "managedChecksum")[]; + /** + * Sortby + * @description Select one or more fields to sort the response by. + * @default [] + */ + sortby: ("id" | "createdAt" | "phenomenonTimeStart" | "phenomenonTimeEnd" | "-id" | "-createdAt" | "-phenomenonTimeStart" | "-phenomenonTimeEnd")[] | null; /** * Source Datastream Id * @description Filter histories by source datastream ID. @@ -5219,17 +5270,17 @@ export interface components { * @default 0 */ offset: number | null; - /** - * Order By - * @description Select one or more fields to order the response by. - * @default [] - */ - order_by: ("id" | "order" | "operationType" | "createdAt" | "-id" | "-order" | "-operationType" | "-createdAt")[] | null; /** * Properties * @description Comma-separated list of properties to include in the response. All properties are returned if omitted. */ properties?: ("id" | "createdBy" | "order" | "operationType" | "createdAt" | "comment" | "arguments")[]; + /** + * Sortby + * @description Select one or more fields to sort the response by. + * @default [] + */ + sortby: ("id" | "order" | "operationType" | "createdAt" | "-id" | "-order" | "-operationType" | "-createdAt")[] | null; }; /** QualityControlOperationResponse */ QualityControlOperationResponse: { @@ -5310,12 +5361,6 @@ export interface components { * @default 0 */ offset: number | null; - /** - * Order By - * @description Select one or more fields to order the response by. - * @default [] - */ - order_by: ("id" | "createdAt" | "phenomenonTimeStart" | "phenomenonTimeEnd" | "status" | "committedAt" | "-id" | "-createdAt" | "-phenomenonTimeStart" | "-phenomenonTimeEnd" | "-status" | "-committedAt")[] | null; /** * Properties * @description Comma-separated list of properties to include in the response. All properties are returned if omitted. @@ -5331,6 +5376,12 @@ export interface components { * @description Return sessions overlapping with this range start. */ range_start?: string | null; + /** + * Sortby + * @description Select one or more fields to sort the response by. + * @default [] + */ + sortby: ("id" | "createdAt" | "phenomenonTimeStart" | "phenomenonTimeEnd" | "status" | "committedAt" | "-id" | "-createdAt" | "-phenomenonTimeStart" | "-phenomenonTimeEnd" | "-status" | "-committedAt")[] | null; /** Status */ status?: ("in_progress" | "committed") | null; }; @@ -5460,17 +5511,22 @@ export interface components { * @default 0 */ offset: number | null; - /** - * Order By - * @description Select one or more fields to order the response by. - * @default [] - */ - order_by: ("id" | "name" | "monitoringSiteId" | "monitoringSiteName" | "workspaceId" | "workspaceName" | "-id" | "-name" | "-monitoringSiteId" | "-monitoringSiteName" | "-workspaceId" | "-workspaceName")[] | null; /** * Properties * @description Comma-separated list of properties to include in the response. All properties are returned if omitted. */ properties?: ("id" | "monitoringSiteId" | "name" | "description" | "fittingMethod" | "points")[]; + /** + * Q + * @description Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND. + */ + q?: string | null; + /** + * Sortby + * @description Select one or more fields to sort the response by. + * @default [] + */ + sortby: ("id" | "name" | "monitoringSiteId" | "monitoringSiteName" | "workspaceId" | "workspaceName" | "fittingMethod" | "-id" | "-name" | "-monitoringSiteId" | "-monitoringSiteName" | "-workspaceId" | "-workspaceName" | "-fittingMethod")[] | null; /** * Workspace Id * @description Filter rating curves by workspace ID. @@ -5570,17 +5626,22 @@ export interface components { * @default 0 */ offset: number | null; - /** - * Order By - * @description Select one or more fields to order the response by. - * @default [] - */ - order_by: ("code" | "-code")[] | null; /** * Properties * @description Comma-separated list of properties to include in the response. All properties are returned if omitted. */ properties?: ("id" | "workspaceId" | "code" | "description")[]; + /** + * Q + * @description Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND. + */ + q?: string | null; + /** + * Sortby + * @description Select one or more fields to sort the response by. + * @default [] + */ + sortby: ("code" | "-code")[] | null; /** * Workspace Id * @description Filter result qualifiers by workspace ID. @@ -5629,17 +5690,17 @@ export interface components { * @default 0 */ offset: number | null; - /** - * Order By - * @description Select one or more fields to order the response by. - * @default [] - */ - order_by: ("name" | "-name")[] | null; /** * Properties * @description Comma-separated list of properties to include in the response. All properties are returned if omitted. */ properties?: ("id" | "workspaceId" | "name" | "description" | "permissions")[]; + /** + * Sortby + * @description Select one or more fields to sort the response by. + * @default [] + */ + sortby: ("name" | "-name")[] | null; /** * Workspace Id * @description Filter roles by workspace ID. @@ -5780,17 +5841,17 @@ export interface components { * @default 0 */ offset: number | null; - /** - * Order By - * @description Select one or more fields to order the response by. - * @default [] - */ - order_by: ("name" | "isActive" | "keyExpiresAt" | "-name" | "-isActive" | "-keyExpiresAt")[] | null; /** * Properties * @description Comma-separated list of properties to include in the response. All properties are returned if omitted. */ properties?: ("id" | "workspaceId" | "name" | "description" | "isActive" | "keyExpiresAt" | "email" | "createdAt" | "lastUsedAt")[]; + /** + * Sortby + * @description Select one or more fields to sort the response by. + * @default [] + */ + sortby: ("name" | "isActive" | "keyExpiresAt" | "createdAt" | "lastUsedAt" | "-name" | "-isActive" | "-keyExpiresAt" | "-createdAt" | "-lastUsedAt")[] | null; }; /** ServiceAccountResponse */ ServiceAccountResponse: { @@ -5858,17 +5919,17 @@ export interface components { * @default 0 */ offset: number | null; - /** - * Order By - * @description Select one or more fields to order the response by. - * @default [] - */ - order_by: ("id" | "status" | "startedAt" | "finishedAt" | "-id" | "-status" | "-startedAt" | "-finishedAt")[]; /** * Properties * @description Comma-separated list of properties to include in the response. All properties are returned if omitted. */ properties?: ("id" | "status" | "message" | "result" | "startedAt" | "finishedAt")[]; + /** + * Sortby + * @description Select one or more fields to sort the response by. + * @default [] + */ + sortby: ("id" | "status" | "startedAt" | "finishedAt" | "-id" | "-status" | "-startedAt" | "-finishedAt")[]; /** * Started At Max * @description Filters for task runs started on or before this date and time. @@ -6002,17 +6063,22 @@ export interface components { * @default 0 */ offset: number | null; - /** - * Order By - * @description Select one or more fields to order the response by. - * @default [] - */ - order_by: ("name" | "symbol" | "type" | "-name" | "-symbol" | "-type")[] | null; /** * Properties * @description Comma-separated list of properties to include in the response. All properties are returned if omitted. */ properties?: ("id" | "workspaceId" | "name" | "symbol" | "definition" | "type")[]; + /** + * Q + * @description Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND. + */ + q?: string | null; + /** + * Sortby + * @description Select one or more fields to sort the response by. + * @default [] + */ + sortby: ("name" | "symbol" | "type" | "-name" | "-symbol" | "-type")[] | null; /** * Type * @description Filter units by type @@ -6184,17 +6250,17 @@ export interface components { * @default 0 */ offset: number | null; - /** - * Order Desc - * @description Sort terms by descending. - * @default false - */ - order_desc: boolean | null; /** * Properties * @description Comma-separated list of properties to include in the response. All properties are returned if omitted. */ properties?: string | null; + /** + * Sort Desc + * @description Sort terms by descending. + * @default false + */ + sort_desc: boolean | null; }; /** WorkspaceItemQueryParameters */ WorkspaceItemQueryParameters: { @@ -6254,17 +6320,22 @@ export interface components { * @default 0 */ offset: number | null; - /** - * Order By - * @description Select one or more fields to order the response by. - * @default [] - */ - order_by: ("name" | "isPrivate" | "-name" | "-isPrivate")[] | null; /** * Properties * @description Comma-separated list of properties to include in the response. All properties are returned if omitted. */ properties?: ("id" | "ownerEmail" | "pendingTransferToEmail" | "collaboratorRoleId" | "name" | "isPrivate")[]; + /** + * Q + * @description Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND. + */ + q?: string | null; + /** + * Sortby + * @description Select one or more fields to sort the response by. + * @default [] + */ + sortby: ("name" | "isPrivate" | "-name" | "-isPrivate")[] | null; }; /** WorkspaceResponse */ WorkspaceResponse: { @@ -6312,8 +6383,10 @@ export interface operations { offset?: number | null; /** @description The maximum number of items to return. */ limit?: number | null; - /** @description Select one or more fields to order the response by. */ - order_by?: ("id" | "name" | "monitoringSiteId" | "monitoringSiteName" | "workspaceId" | "workspaceName" | "-id" | "-name" | "-monitoringSiteId" | "-monitoringSiteName" | "-workspaceId" | "-workspaceName")[] | null; + /** @description Select one or more fields to sort the response by. */ + sortby?: ("id" | "name" | "monitoringSiteId" | "monitoringSiteName" | "workspaceId" | "workspaceName" | "fittingMethod" | "-id" | "-name" | "-monitoringSiteId" | "-monitoringSiteName" | "-workspaceId" | "-workspaceName" | "-fittingMethod")[] | null; + /** @description Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND. */ + q?: string | null; /** @description Filter rating curves by monitoring site ID. */ monitoring_site_id?: string[]; /** @description Filter rating curves by workspace ID. */ @@ -6577,8 +6650,10 @@ export interface operations { offset?: number | null; /** @description The maximum number of items to return. */ limit?: number | null; - /** @description Select one or more fields to order the response by. */ - order_by?: ("id" | "name" | "monitoringSiteId" | "monitoringSiteName" | "workspaceId" | "workspaceName" | "latestRunStatus" | "latestRunStartedAt" | "latestRunFinishedAt" | "-id" | "-name" | "-monitoringSiteId" | "-monitoringSiteName" | "-workspaceId" | "-workspaceName" | "-latestRunStatus" | "-latestRunStartedAt" | "-latestRunFinishedAt")[] | null; + /** @description Select one or more fields to sort the response by. */ + sortby?: ("id" | "name" | "monitoringSiteId" | "monitoringSiteName" | "workspaceId" | "workspaceName" | "latestRunStatus" | "latestRunStartedAt" | "latestRunFinishedAt" | "-id" | "-name" | "-monitoringSiteId" | "-monitoringSiteName" | "-workspaceId" | "-workspaceName" | "-latestRunStatus" | "-latestRunStartedAt" | "-latestRunFinishedAt")[] | null; + /** @description Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND. */ + q?: string | null; /** @description Filter data product tasks by monitoring_site ID. */ monitoring_site_id?: string[]; /** @description Filter data product tasks by workspace ID. */ @@ -6852,8 +6927,8 @@ export interface operations { offset?: number | null; /** @description The maximum number of items to return. */ limit?: number | null; - /** @description Select one or more fields to order the response by. */ - order_by?: ("id" | "status" | "startedAt" | "finishedAt" | "-id" | "-status" | "-startedAt" | "-finishedAt")[]; + /** @description Select one or more fields to sort the response by. */ + sortby?: ("id" | "status" | "startedAt" | "finishedAt" | "-id" | "-status" | "-startedAt" | "-finishedAt")[]; /** @description Filters task runs by their status. */ status?: ("PENDING" | "STARTED" | "SUCCESS" | "FAILURE")[]; /** @description Filters for task runs started on or before this date and time. */ @@ -7021,8 +7096,8 @@ export interface operations { offset?: number | null; /** @description The maximum number of items to return. */ limit?: number | null; - /** @description Select one or more fields to order the response by. */ - order_by?: ("id" | "outputDatastreamId" | "-id" | "-outputDatastreamId")[] | null; + /** @description Select one or more fields to sort the response by. */ + sortby?: ("id" | "outputDatastreamId" | "transformationType" | "-id" | "-outputDatastreamId" | "-transformationType")[] | null; /** @description Filter transformations by type. */ transformation_type?: string[]; /** @description Filter transformations by output datastream ID. */ @@ -7310,8 +7385,10 @@ export interface operations { offset?: number | null; /** @description The maximum number of items to return. */ limit?: number | null; - /** @description Select one or more fields to order the response by. */ - order_by?: ("name" | "observationType" | "sampledMedium" | "status" | "resultType" | "isPrivate" | "valueCount" | "phenomenonBeginTime" | "phenomenonEndTime" | "resultBeginTime" | "resultEndTime" | "-name" | "-observationType" | "-sampledMedium" | "-status" | "-resultType" | "-isPrivate" | "-valueCount" | "-phenomenonBeginTime" | "-phenomenonEndTime" | "-resultBeginTime" | "-resultEndTime")[] | null; + /** @description Select one or more fields to sort the response by. */ + sortby?: ("name" | "observationType" | "sampledMedium" | "status" | "resultType" | "isPrivate" | "isVisible" | "aggregationStatistic" | "valueCount" | "phenomenonBeginTime" | "phenomenonEndTime" | "resultBeginTime" | "resultEndTime" | "-name" | "-observationType" | "-sampledMedium" | "-status" | "-resultType" | "-isPrivate" | "-isVisible" | "-aggregationStatistic" | "-valueCount" | "-phenomenonBeginTime" | "-phenomenonEndTime" | "-resultBeginTime" | "-resultEndTime")[] | null; + /** @description Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND. */ + q?: string | null; /** @description Filter datastreams by workspace ID. */ workspace_id?: string[]; /** @description Filter datastreams by monitoring_site ID. */ @@ -7448,7 +7525,7 @@ export interface operations { /** @description The maximum number of items to return. */ limit?: number | null; /** @description Sort terms by descending. */ - order_desc?: boolean | null; + sort_desc?: boolean | null; }; header?: never; path?: never; @@ -7479,7 +7556,7 @@ export interface operations { /** @description The maximum number of items to return. */ limit?: number | null; /** @description Sort terms by descending. */ - order_desc?: boolean | null; + sort_desc?: boolean | null; }; header?: never; path?: never; @@ -7510,7 +7587,7 @@ export interface operations { /** @description The maximum number of items to return. */ limit?: number | null; /** @description Sort terms by descending. */ - order_desc?: boolean | null; + sort_desc?: boolean | null; }; header?: never; path?: never; @@ -7541,7 +7618,7 @@ export interface operations { /** @description The maximum number of items to return. */ limit?: number | null; /** @description Sort terms by descending. */ - order_desc?: boolean | null; + sort_desc?: boolean | null; }; header?: never; path?: never; @@ -8062,8 +8139,10 @@ export interface operations { offset?: number | null; /** @description The maximum number of items to return. */ limit?: number | null; - /** @description Select one or more fields to order the response by. */ - order_by?: ("id" | "name" | "timestampKey" | "timestampFormat" | "timezoneType" | "timezone" | "workspaceId" | "workspaceName" | "-id" | "-name" | "-timestampKey" | "-timestampFormat" | "-timezoneType" | "-timezone" | "-workspaceId" | "-workspaceName")[] | null; + /** @description Select one or more fields to sort the response by. */ + sortby?: ("id" | "name" | "timestampKey" | "timestampFormat" | "timezoneType" | "timezone" | "workspaceId" | "workspaceName" | "-id" | "-name" | "-timestampKey" | "-timestampFormat" | "-timezoneType" | "-timezone" | "-workspaceId" | "-workspaceName")[] | null; + /** @description Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND. */ + q?: string | null; /** @description Filter data connections by workspace ID. */ workspace_id?: string[]; /** @description Filter data connections by payload type. */ @@ -8318,8 +8397,8 @@ export interface operations { offset?: number | null; /** @description The maximum number of items to return. */ limit?: number | null; - /** @description Select one or more fields to order the response by. */ - order_by?: ("id" | "sourceIdentifier" | "targetDatastreamId" | "-id" | "-sourceIdentifier" | "-targetDatastreamId")[] | null; + /** @description Select one or more fields to sort the response by. */ + sortby?: ("id" | "sourceIdentifier" | "targetDatastreamId" | "-id" | "-sourceIdentifier" | "-targetDatastreamId")[] | null; /** @description Filter mappings by source identifier. */ source_identifier?: string[]; /** @description Filter mappings by target datastream ID. */ @@ -8605,8 +8684,10 @@ export interface operations { offset?: number | null; /** @description The maximum number of items to return. */ limit?: number | null; - /** @description Select one or more fields to order the response by. */ - order_by?: ("id" | "name" | "dataConnectionId" | "dataConnectionName" | "workspaceId" | "workspaceName" | "latestRunStatus" | "latestRunStartedAt" | "latestRunFinishedAt" | "-id" | "-name" | "-dataConnectionId" | "-dataConnectionName" | "-workspaceId" | "-workspaceName" | "-latestRunStatus" | "-latestRunStartedAt" | "-latestRunFinishedAt")[] | null; + /** @description Select one or more fields to sort the response by. */ + sortby?: ("id" | "name" | "dataConnectionId" | "dataConnectionName" | "workspaceId" | "workspaceName" | "latestRunStatus" | "latestRunStartedAt" | "latestRunFinishedAt" | "-id" | "-name" | "-dataConnectionId" | "-dataConnectionName" | "-workspaceId" | "-workspaceName" | "-latestRunStatus" | "-latestRunStartedAt" | "-latestRunFinishedAt")[] | null; + /** @description Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND. */ + q?: string | null; /** @description Filter ETL tasks by monitoring_site ID. */ monitoring_site_id?: string[]; /** @description Filter ETL tasks by workspace ID. */ @@ -8882,8 +8963,8 @@ export interface operations { offset?: number | null; /** @description The maximum number of items to return. */ limit?: number | null; - /** @description Select one or more fields to order the response by. */ - order_by?: ("id" | "status" | "startedAt" | "finishedAt" | "-id" | "-status" | "-startedAt" | "-finishedAt")[]; + /** @description Select one or more fields to sort the response by. */ + sortby?: ("id" | "status" | "startedAt" | "finishedAt" | "-id" | "-status" | "-startedAt" | "-finishedAt")[]; /** @description Filters task runs by their status. */ status?: ("PENDING" | "STARTED" | "SUCCESS" | "FAILURE")[]; /** @description Filters for task runs started on or before this date and time. */ @@ -9051,8 +9132,10 @@ export interface operations { offset?: number | null; /** @description The maximum number of items to return. */ limit?: number | null; - /** @description Select one or more fields to order the response by. */ - order_by?: ("name" | "code" | "type" | "sensorModel" | "sensorModelManufacturer" | "-name" | "-code" | "-type" | "-sensorModel" | "-sensorModelManufacturer")[] | null; + /** @description Select one or more fields to sort the response by. */ + sortby?: ("name" | "code" | "type" | "sensorModel" | "sensorModelManufacturer" | "definition" | "sensorModelDefinition" | "-name" | "-code" | "-type" | "-sensorModel" | "-sensorModelManufacturer" | "-definition" | "-sensorModelDefinition")[] | null; + /** @description Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND. */ + q?: string | null; /** @description Filter methods by workspace ID. */ workspace_id?: (string | "null")[]; /** @description Filter methods by monitoring_site ID. */ @@ -9164,7 +9247,7 @@ export interface operations { /** @description The maximum number of items to return. */ limit?: number | null; /** @description Sort terms by descending. */ - order_desc?: boolean | null; + sort_desc?: boolean | null; }; header?: never; path?: never; @@ -9346,8 +9429,8 @@ export interface operations { offset?: number | null; /** @description The maximum number of items to return. */ limit?: number | null; - /** @description Select one or more fields to order the response by. */ - order_by?: ("id" | "ruleType" | "datastreamId" | "-id" | "-ruleType" | "-datastreamId")[] | null; + /** @description Select one or more fields to sort the response by. */ + sortby?: ("id" | "ruleType" | "datastreamId" | "lastCheckedAt" | "-id" | "-ruleType" | "-datastreamId" | "-lastCheckedAt")[] | null; /** @description Filter rules by datastream ID. */ datastream_id?: string[]; /** @description Filter rules by rule type. */ @@ -9633,8 +9716,10 @@ export interface operations { offset?: number | null; /** @description The maximum number of items to return. */ limit?: number | null; - /** @description Select one or more fields to order the response by. */ - order_by?: ("name" | "code" | "type" | "isPrivate" | "latitude" | "longitude" | "elevation_m" | "elevationDatum" | "adminArea1" | "adminArea2" | "country" | "-name" | "-code" | "-type" | "-isPrivate" | "-latitude" | "-longitude" | "-elevation_m" | "-elevationDatum" | "-adminArea1" | "-adminArea2" | "-country")[] | null; + /** @description Select one or more fields to sort the response by. */ + sortby?: ("name" | "code" | "type" | "isPrivate" | "latitude" | "longitude" | "elevation_m" | "elevationDatum" | "adminArea1" | "adminArea2" | "country" | "-name" | "-code" | "-type" | "-isPrivate" | "-latitude" | "-longitude" | "-elevation_m" | "-elevationDatum" | "-adminArea1" | "-adminArea2" | "-country")[] | null; + /** @description Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND. */ + q?: string | null; /** @description Filter monitoring sites by workspace ID. */ workspace_id?: string[]; /** @description Filter monitoring sites by bounding box. Format bounding box as {min_lon},{min_lat},{max_lon},{max_lat} */ @@ -9732,7 +9817,7 @@ export interface operations { /** @description The maximum number of items to return. */ limit?: number | null; /** @description Sort terms by descending. */ - order_desc?: boolean | null; + sort_desc?: boolean | null; }; header?: never; path?: never; @@ -9853,7 +9938,7 @@ export interface operations { /** @description The maximum number of items to return. */ limit?: number | null; /** @description Sort terms by descending. */ - order_desc?: boolean | null; + sort_desc?: boolean | null; }; header?: never; path?: never; @@ -10338,8 +10423,10 @@ export interface operations { offset?: number | null; /** @description The maximum number of items to return. */ limit?: number | null; - /** @description Select one or more fields to order the response by. */ - order_by?: ("id" | "name" | "monitoringSiteId" | "monitoringSiteName" | "workspaceId" | "workspaceName" | "latestRunStatus" | "latestRunStartedAt" | "latestRunFinishedAt" | "-id" | "-name" | "-monitoringSiteId" | "-monitoringSiteName" | "-workspaceId" | "-workspaceName" | "-latestRunStatus" | "-latestRunStartedAt" | "-latestRunFinishedAt")[] | null; + /** @description Select one or more fields to sort the response by. */ + sortby?: ("id" | "name" | "monitoringSiteId" | "monitoringSiteName" | "workspaceId" | "workspaceName" | "latestRunStatus" | "latestRunStartedAt" | "latestRunFinishedAt" | "-id" | "-name" | "-monitoringSiteId" | "-monitoringSiteName" | "-workspaceId" | "-workspaceName" | "-latestRunStatus" | "-latestRunStartedAt" | "-latestRunFinishedAt")[] | null; + /** @description Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND. */ + q?: string | null; /** @description Filter monitoring tasks by monitoring_site ID. */ monitoring_site_id?: string[]; /** @description Filter monitoring tasks by workspace ID. */ @@ -10609,8 +10696,8 @@ export interface operations { offset?: number | null; /** @description The maximum number of items to return. */ limit?: number | null; - /** @description Select one or more fields to order the response by. */ - order_by?: ("id" | "status" | "startedAt" | "finishedAt" | "-id" | "-status" | "-startedAt" | "-finishedAt")[]; + /** @description Select one or more fields to sort the response by. */ + sortby?: ("id" | "status" | "startedAt" | "finishedAt" | "-id" | "-status" | "-startedAt" | "-finishedAt")[]; /** @description Filters task runs by their status. */ status?: ("PENDING" | "STARTED" | "SUCCESS" | "FAILURE")[]; /** @description Filters for task runs started on or before this date and time. */ @@ -10780,8 +10867,8 @@ export interface operations { limit?: number | null; /** @description Filter observations by datastream ID. */ datastream_id?: string[]; - /** @description Select one or more fields to order the response by. */ - order_by?: ("phenomenonTime" | "datastreamId" | "-phenomenonTime" | "-datastreamId")[] | null; + /** @description Select one or more fields to sort the response by. */ + sortby?: ("phenomenonTime" | "datastreamId" | "-phenomenonTime" | "-datastreamId")[] | null; /** @description Controls the format of the observations response. */ format?: ("record" | "row" | "column") | null; /** @description Sets the maximum phenomenon time of filtered observations. */ @@ -11081,8 +11168,10 @@ export interface operations { offset?: number | null; /** @description The maximum number of items to return. */ limit?: number | null; - /** @description Select one or more fields to order the response by. */ - order_by?: ("name" | "type" | "code" | "-name" | "-type" | "-code")[] | null; + /** @description Select one or more fields to sort the response by. */ + sortby?: ("name" | "type" | "code" | "-name" | "-type" | "-code")[] | null; + /** @description Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND. */ + q?: string | null; /** @description Filter observed properties by workspace ID. */ workspace_id?: (string | "null")[]; /** @description Filter observed properties by monitoring_site ID. */ @@ -11190,7 +11279,7 @@ export interface operations { /** @description The maximum number of items to return. */ limit?: number | null; /** @description Sort terms by descending. */ - order_desc?: boolean | null; + sort_desc?: boolean | null; }; header?: never; path?: never; @@ -11372,8 +11461,10 @@ export interface operations { offset?: number | null; /** @description The maximum number of items to return. */ limit?: number | null; - /** @description Select one or more fields to order the response by. */ - order_by?: ("code" | "name" | "-code" | "-name")[] | null; + /** @description Select one or more fields to sort the response by. */ + sortby?: ("code" | "name" | "-code" | "-name")[] | null; + /** @description Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND. */ + q?: string | null; /** @description Filter processing levels by workspace ID. */ workspace_id?: (string | "null")[]; /** @description Filter processing levels by monitoring_site ID. */ @@ -11630,8 +11721,8 @@ export interface operations { offset?: number | null; /** @description The maximum number of items to return. */ limit?: number | null; - /** @description Select one or more fields to order the response by. */ - order_by?: ("id" | "createdAt" | "phenomenonTimeStart" | "phenomenonTimeEnd" | "-id" | "-createdAt" | "-phenomenonTimeStart" | "-phenomenonTimeEnd")[] | null; + /** @description Select one or more fields to sort the response by. */ + sortby?: ("id" | "createdAt" | "phenomenonTimeStart" | "phenomenonTimeEnd" | "-id" | "-createdAt" | "-phenomenonTimeStart" | "-phenomenonTimeEnd")[] | null; /** @description Filter histories by managed datastream ID. */ managed_datastream_id?: string[]; /** @description Filter histories by source datastream ID. */ @@ -11844,8 +11935,8 @@ export interface operations { offset?: number | null; /** @description The maximum number of items to return. */ limit?: number | null; - /** @description Select one or more fields to order the response by. */ - order_by?: ("id" | "createdAt" | "phenomenonTimeStart" | "phenomenonTimeEnd" | "status" | "committedAt" | "-id" | "-createdAt" | "-phenomenonTimeStart" | "-phenomenonTimeEnd" | "-status" | "-committedAt")[] | null; + /** @description Select one or more fields to sort the response by. */ + sortby?: ("id" | "createdAt" | "phenomenonTimeStart" | "phenomenonTimeEnd" | "status" | "committedAt" | "-id" | "-createdAt" | "-phenomenonTimeStart" | "-phenomenonTimeEnd" | "-status" | "-committedAt")[] | null; status?: ("in_progress" | "committed") | null; /** @description Return sessions overlapping with this range start. */ range_start?: string | null; @@ -12194,8 +12285,8 @@ export interface operations { offset?: number | null; /** @description The maximum number of items to return. */ limit?: number | null; - /** @description Select one or more fields to order the response by. */ - order_by?: ("id" | "order" | "operationType" | "createdAt" | "-id" | "-order" | "-operationType" | "-createdAt")[] | null; + /** @description Select one or more fields to sort the response by. */ + sortby?: ("id" | "order" | "operationType" | "createdAt" | "-id" | "-order" | "-operationType" | "-createdAt")[] | null; }; header?: never; path: { @@ -12483,8 +12574,10 @@ export interface operations { offset?: number | null; /** @description The maximum number of items to return. */ limit?: number | null; - /** @description Select one or more fields to order the response by. */ - order_by?: ("code" | "-code")[] | null; + /** @description Select one or more fields to sort the response by. */ + sortby?: ("code" | "-code")[] | null; + /** @description Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND. */ + q?: string | null; /** @description Filter result qualifiers by workspace ID. */ workspace_id?: (string | "null")[]; /** @description Filter result qualifiers by monitoring_site ID. */ @@ -12714,8 +12807,8 @@ export interface operations { offset?: number | null; /** @description The maximum number of items to return. */ limit?: number | null; - /** @description Select one or more fields to order the response by. */ - order_by?: ("name" | "-name")[] | null; + /** @description Select one or more fields to sort the response by. */ + sortby?: ("name" | "-name")[] | null; /** @description Filter roles by workspace ID. */ workspace_id?: (string | "null")[]; }; @@ -12799,8 +12892,10 @@ export interface operations { offset?: number | null; /** @description The maximum number of items to return. */ limit?: number | null; - /** @description Select one or more fields to order the response by. */ - order_by?: ("name" | "symbol" | "type" | "-name" | "-symbol" | "-type")[] | null; + /** @description Select one or more fields to sort the response by. */ + sortby?: ("name" | "symbol" | "type" | "-name" | "-symbol" | "-type")[] | null; + /** @description Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND. */ + q?: string | null; /** @description Filter units by workspace ID. */ workspace_id?: (string | "null")[]; /** @description Filter units by monitoring_site ID. */ @@ -12899,7 +12994,7 @@ export interface operations { /** @description The maximum number of items to return. */ limit?: number | null; /** @description Sort terms by descending. */ - order_desc?: boolean | null; + sort_desc?: boolean | null; }; header?: never; path?: never; @@ -13081,8 +13176,10 @@ export interface operations { offset?: number | null; /** @description The maximum number of items to return. */ limit?: number | null; - /** @description Select one or more fields to order the response by. */ - order_by?: ("name" | "isPrivate" | "-name" | "-isPrivate")[] | null; + /** @description Select one or more fields to sort the response by. */ + sortby?: ("name" | "isPrivate" | "-name" | "-isPrivate")[] | null; + /** @description Full-text search query. Comma-separated terms are combined with OR; whitespace-separated words within a term are combined with AND. */ + q?: string | null; /** @description Whether the workspace is associated with the authenticated user */ is_associated?: boolean | null; /** @description Whether the returned workspaces should be private or public. */ @@ -13321,6 +13418,8 @@ export interface operations { limit?: number | null; /** @description Filter collaborators by role ID. */ role_id?: string[]; + /** @description Select one or more fields to sort the response by. */ + sortby?: ("id" | "roleId" | "-id" | "-roleId")[] | null; }; header?: never; path: { @@ -13525,8 +13624,8 @@ export interface operations { offset?: number | null; /** @description The maximum number of items to return. */ limit?: number | null; - /** @description Select one or more fields to order the response by. */ - order_by?: ("name" | "isActive" | "keyExpiresAt" | "-name" | "-isActive" | "-keyExpiresAt")[] | null; + /** @description Select one or more fields to sort the response by. */ + sortby?: ("name" | "isActive" | "keyExpiresAt" | "createdAt" | "lastUsedAt" | "-name" | "-isActive" | "-keyExpiresAt" | "-createdAt" | "-lastUsedAt")[] | null; }; header?: never; path: { diff --git a/packages/hydroserverpy/src/hydroserverpy/api/README.md b/packages/hydroserverpy/src/hydroserverpy/api/README.md index 87457720..3cf94655 100644 --- a/packages/hydroserverpy/src/hydroserverpy/api/README.md +++ b/packages/hydroserverpy/src/hydroserverpy/api/README.md @@ -857,7 +857,7 @@ from datetime import datetime, timezone task = hs_api.tasks.get(uid='00000000-0000-0000-0000-000000000000') # List recent runs -runs = task.list_runs(order_by=['-started_at'], limit=10) +runs = task.list_runs(sortby=['-started_at'], limit=10) # Filter runs by status failed_runs = task.list_runs(status='FAILURE') @@ -1012,7 +1012,7 @@ print(task_run.status) # 'PENDING' task = hs_api.data_product_tasks.get(uid='00000000-0000-0000-0000-000000000000') # List recent runs -runs = task.list_runs(order_by=['-started_at'], limit=10) +runs = task.list_runs(sortby=['-started_at'], limit=10) # Filter runs by status failed_runs = task.list_runs(status='FAILURE') @@ -1213,7 +1213,7 @@ print(task_run.status) # 'PENDING' task = hs_api.monitoring_tasks.get(uid='00000000-0000-0000-0000-000000000000') # List recent runs -runs = task.list_runs(order_by=['-started_at'], limit=10) +runs = task.list_runs(sortby=['-started_at'], limit=10) # Filter runs by status failed_runs = task.list_runs(status='FAILURE') diff --git a/packages/hydroserverpy/src/hydroserverpy/api/models/base.py b/packages/hydroserverpy/src/hydroserverpy/api/models/base.py index 82d8fce2..0f2c0bdb 100644 --- a/packages/hydroserverpy/src/hydroserverpy/api/models/base.py +++ b/packages/hydroserverpy/src/hydroserverpy/api/models/base.py @@ -97,7 +97,7 @@ def delete(self): class HydroServerCollection: items: List["HydroServerBaseModel"] filters: Optional[dict[str, Any]] = None - order_by: Optional[List[str]] = None + sortby: Optional[List[str]] = None offset: Optional[int] = None limit: Optional[int] = None total_count: Optional[int] = None @@ -115,7 +115,7 @@ def __init__( self._service = service self.filters = data.get("filters") - self.order_by = data.get("order_by") + self.sortby = data.get("sortby") payload = response.json() if response is not None else {} meta = payload.get("meta", {}) if isinstance(payload, dict) else {} @@ -164,7 +164,7 @@ def next_page(self): **(self.filters or {}), offset=(self.offset or 0) + limit, limit=limit, - order_by=self.order_by or ... + sortby=self.sortby or ... ) def previous_page(self): @@ -182,7 +182,7 @@ def previous_page(self): **(self.filters or {}), offset=max(0, self.offset - limit), limit=limit, - order_by=self.order_by or ... + sortby=self.sortby or ... ) def fetch_all(self) -> "HydroServerCollection": @@ -201,7 +201,7 @@ def fetch_all(self) -> "HydroServerCollection": **(self.filters or {}), offset=next_offset, limit=limit, - order_by=self.order_by or ... + sortby=self.sortby or ... ) if not page.items: break @@ -215,7 +215,7 @@ def fetch_all(self) -> "HydroServerCollection": service=self._service, items=all_items, filters=self.filters, - order_by=self.order_by, + sortby=self.sortby, offset=0, limit=len(all_items), total_count=len(all_items), diff --git a/packages/hydroserverpy/src/hydroserverpy/api/models/etl/task.py b/packages/hydroserverpy/src/hydroserverpy/api/models/etl/task.py index 8c8cb08f..cc233c12 100644 --- a/packages/hydroserverpy/src/hydroserverpy/api/models/etl/task.py +++ b/packages/hydroserverpy/src/hydroserverpy/api/models/etl/task.py @@ -85,7 +85,7 @@ def list_runs( self, offset: int = ..., limit: int = ..., - order_by: List[str] = ..., + sortby: List[str] = ..., status: str = ..., started_at_min: datetime = ..., started_at_max: datetime = ..., @@ -98,7 +98,7 @@ def list_runs( uid=self.uid, offset=offset, limit=limit, - order_by=order_by, + sortby=sortby, status=status, started_at_min=started_at_min, started_at_max=started_at_max, diff --git a/packages/hydroserverpy/src/hydroserverpy/api/models/monitoring/task.py b/packages/hydroserverpy/src/hydroserverpy/api/models/monitoring/task.py index c83acf67..8b206204 100644 --- a/packages/hydroserverpy/src/hydroserverpy/api/models/monitoring/task.py +++ b/packages/hydroserverpy/src/hydroserverpy/api/models/monitoring/task.py @@ -80,7 +80,7 @@ def list_runs( self, offset: int = ..., limit: int = ..., - order_by: List[str] = ..., + sortby: List[str] = ..., status: str = ..., started_at_min: datetime = ..., started_at_max: datetime = ..., @@ -93,7 +93,7 @@ def list_runs( uid=self.uid, offset=offset, limit=limit, - order_by=order_by, + sortby=sortby, status=status, started_at_min=started_at_min, started_at_max=started_at_max, diff --git a/packages/hydroserverpy/src/hydroserverpy/api/models/products/task.py b/packages/hydroserverpy/src/hydroserverpy/api/models/products/task.py index 3468945b..0be63ffc 100644 --- a/packages/hydroserverpy/src/hydroserverpy/api/models/products/task.py +++ b/packages/hydroserverpy/src/hydroserverpy/api/models/products/task.py @@ -67,7 +67,7 @@ def list_runs( self, offset: int = ..., limit: int = ..., - order_by: List[str] = ..., + sortby: List[str] = ..., status: str = ..., started_at_min: datetime = ..., started_at_max: datetime = ..., @@ -80,7 +80,7 @@ def list_runs( uid=self.uid, offset=offset, limit=limit, - order_by=order_by, + sortby=sortby, status=status, started_at_min=started_at_min, started_at_max=started_at_max, diff --git a/packages/hydroserverpy/src/hydroserverpy/api/models/sta/datastream.py b/packages/hydroserverpy/src/hydroserverpy/api/models/sta/datastream.py index 08a8e03d..5f512a62 100644 --- a/packages/hydroserverpy/src/hydroserverpy/api/models/sta/datastream.py +++ b/packages/hydroserverpy/src/hydroserverpy/api/models/sta/datastream.py @@ -170,7 +170,7 @@ def get_observations( self, offset: int = ..., limit: int = 100000, - order_by: List[str] = ..., + sortby: List[str] = ..., phenomenon_time_max: datetime = ..., phenomenon_time_min: datetime = ..., result_qualifier_code: str = ..., @@ -182,7 +182,7 @@ def get_observations( uid=self.uid, offset=offset, limit=limit, - order_by=order_by, + sortby=sortby, phenomenon_time_max=phenomenon_time_max, phenomenon_time_min=phenomenon_time_min, result_qualifier_code=result_qualifier_code, @@ -226,7 +226,7 @@ def sync_phenomenon_end_time(self): params={ "datastream_id": str(self.uid), "limit": 1, - "order_by": "-phenomenonTime", + "sortby": "-phenomenonTime", }, ).json() diff --git a/packages/hydroserverpy/src/hydroserverpy/api/models/sta/observation.py b/packages/hydroserverpy/src/hydroserverpy/api/models/sta/observation.py index 219c824f..6b7ec365 100644 --- a/packages/hydroserverpy/src/hydroserverpy/api/models/sta/observation.py +++ b/packages/hydroserverpy/src/hydroserverpy/api/models/sta/observation.py @@ -12,7 +12,7 @@ class ObservationCollection: dataframe: pd.DataFrame filters: Optional[dict[str, Any]] = None - order_by: Optional[List[str]] = None + sortby: Optional[List[str]] = None offset: Optional[int] = None limit: Optional[int] = None total_count: Optional[int] = None @@ -24,11 +24,11 @@ def __init__( **data ): self.filters = data.get("filters") - raw_order_by = data.get("order_by") - if isinstance(raw_order_by, str): - self.order_by = [item for item in raw_order_by.split(",") if item] + raw_sortby = data.get("sortby") + if isinstance(raw_sortby, str): + self.sortby = [item for item in raw_sortby.split(",") if item] else: - self.order_by = raw_order_by + self.sortby = raw_sortby if "dataframe" in data: payload_meta = {} @@ -80,7 +80,7 @@ def next_page(self): **(self.filters or {}), offset=(self.offset or 0) + limit, limit=limit, - order_by=self.order_by or ..., + sortby=self.sortby or ..., ) def previous_page(self): @@ -95,7 +95,7 @@ def previous_page(self): **(self.filters or {}), offset=max(0, self.offset - limit), limit=limit, - order_by=self.order_by or ..., + sortby=self.sortby or ..., ) def fetch_all(self) -> "ObservationCollection": @@ -111,7 +111,7 @@ def fetch_all(self) -> "ObservationCollection": **(self.filters or {}), offset=next_offset, limit=limit, - order_by=self.order_by or ..., + sortby=self.sortby or ..., ) if observations.dataframe.empty: break @@ -128,7 +128,7 @@ def fetch_all(self) -> "ObservationCollection": dataframe=merged_dataframe, datastream=self.datastream, filters=self.filters, - order_by=self.order_by or ..., + sortby=self.sortby or ..., offset=0, limit=len(merged_dataframe), total_count=len(merged_dataframe), diff --git a/packages/hydroserverpy/src/hydroserverpy/api/services/base.py b/packages/hydroserverpy/src/hydroserverpy/api/services/base.py index 8813a17a..be768595 100644 --- a/packages/hydroserverpy/src/hydroserverpy/api/services/base.py +++ b/packages/hydroserverpy/src/hydroserverpy/api/services/base.py @@ -3,7 +3,7 @@ from typing import TYPE_CHECKING, Type, List, Union from datetime import datetime from hydroserverpy.api.models.base import HydroServerBaseModel, HydroServerCollection -from hydroserverpy.api.utils import order_by_to_camel +from hydroserverpy.api.utils import sortby_to_camel if TYPE_CHECKING: from hydroserverpy import HydroServer @@ -19,7 +19,7 @@ def list( self, offset: int = ..., limit: int = ..., - order_by: List[str] = ..., + sortby: List[str] = ..., fetch_all: bool = False, **kwargs ): @@ -30,7 +30,7 @@ def list( params.update({ "offset": offset, "limit": limit, - "order_by": [order_by_to_camel(order) for order in order_by] if order_by is not ... else order_by + "sortby": [sortby_to_camel(order) for order in sortby] if sortby is not ... else sortby }) params = { k: ("null" if v is None else v) @@ -45,7 +45,7 @@ def list( client=self.client, service=self, response=response, - order_by=params.get("order_by"), + sortby=params.get("sortby"), filters={ (k[:-3] if k.endswith("_id") else k): v for k, v in kwargs.items() diff --git a/packages/hydroserverpy/src/hydroserverpy/api/services/etl/data_connection.py b/packages/hydroserverpy/src/hydroserverpy/api/services/etl/data_connection.py index 5d8b0f85..85c076a1 100644 --- a/packages/hydroserverpy/src/hydroserverpy/api/services/etl/data_connection.py +++ b/packages/hydroserverpy/src/hydroserverpy/api/services/etl/data_connection.py @@ -17,7 +17,7 @@ def list( self, offset: int = ..., limit: int = ..., - order_by: List[str] = ..., + sortby: List[str] = ..., workspace: Optional[Union[UUID, str]] = ..., payload_type: str = ..., fetch_all: bool = False, @@ -27,7 +27,7 @@ def list( return super().list( offset=offset, limit=limit, - order_by=order_by, + sortby=sortby, fetch_all=fetch_all, workspace_id=normalize_uuid(workspace), payload_type=payload_type, diff --git a/packages/hydroserverpy/src/hydroserverpy/api/services/etl/mapping.py b/packages/hydroserverpy/src/hydroserverpy/api/services/etl/mapping.py index 91fdcf88..e52f667c 100644 --- a/packages/hydroserverpy/src/hydroserverpy/api/services/etl/mapping.py +++ b/packages/hydroserverpy/src/hydroserverpy/api/services/etl/mapping.py @@ -19,7 +19,7 @@ def list( workspace: Optional[Union[UUID, str]] = ..., offset: int = ..., limit: int = ..., - order_by: List[str] = ..., + sortby: List[str] = ..., source_identifier: str = ..., fetch_all: bool = False, ): @@ -28,7 +28,7 @@ def list( return super().list( offset=offset, limit=limit, - order_by=order_by, + sortby=sortby, fetch_all=fetch_all, etl_task_id=normalize_uuid(etl_task), workspace_id=normalize_uuid(workspace), diff --git a/packages/hydroserverpy/src/hydroserverpy/api/services/etl/task.py b/packages/hydroserverpy/src/hydroserverpy/api/services/etl/task.py index 3512f176..5d9c7697 100644 --- a/packages/hydroserverpy/src/hydroserverpy/api/services/etl/task.py +++ b/packages/hydroserverpy/src/hydroserverpy/api/services/etl/task.py @@ -3,7 +3,7 @@ from uuid import UUID from hydroserverpy.api.models.etl.task import EtlTask from hydroserverpy.api.models.orchestration.run import TaskRun -from hydroserverpy.api.utils import normalize_uuid, order_by_to_camel +from hydroserverpy.api.utils import normalize_uuid, sortby_to_camel from ..base import HydroServerBaseService if TYPE_CHECKING: @@ -19,7 +19,7 @@ def list( self, offset: int = ..., limit: int = ..., - order_by: List[str] = ..., + sortby: List[str] = ..., workspace: Optional[Union[UUID, str]] = ..., data_connection: Optional[Union[UUID, str]] = ..., latest_run_status: str = ..., @@ -34,7 +34,7 @@ def list( return super().list( offset=offset, limit=limit, - order_by=order_by, + sortby=sortby, fetch_all=fetch_all, workspace_id=normalize_uuid(workspace), data_connection_id=normalize_uuid(data_connection), @@ -127,7 +127,7 @@ def list_runs( uid: Union[UUID, str], offset: int = ..., limit: int = ..., - order_by: List[str] = ..., + sortby: List[str] = ..., status: str = ..., started_at_min: datetime = ..., started_at_max: datetime = ..., @@ -139,7 +139,7 @@ def list_runs( params = { "offset": offset, "limit": limit, - "order_by": [order_by_to_camel(o) for o in order_by] if order_by is not ... else order_by, + "sortby": [sortby_to_camel(o) for o in sortby] if sortby is not ... else sortby, "status": status, "started_at_min": started_at_min, "started_at_max": started_at_max, diff --git a/packages/hydroserverpy/src/hydroserverpy/api/services/iam/role.py b/packages/hydroserverpy/src/hydroserverpy/api/services/iam/role.py index bed9ff47..023f22e4 100644 --- a/packages/hydroserverpy/src/hydroserverpy/api/services/iam/role.py +++ b/packages/hydroserverpy/src/hydroserverpy/api/services/iam/role.py @@ -19,7 +19,7 @@ def list( self, offset: int = ..., limit: int = ..., - order_by: List[str] = ..., + sortby: List[str] = ..., workspace: Optional[Union["Workspace", UUID, str]] = ..., fetch_all: bool = False, ) -> List["Role"]: @@ -28,7 +28,7 @@ def list( return super().list( offset=offset, limit=limit, - order_by=order_by, + sortby=sortby, fetch_all=fetch_all, workspace_id=normalize_uuid(workspace), ) diff --git a/packages/hydroserverpy/src/hydroserverpy/api/services/iam/workspace.py b/packages/hydroserverpy/src/hydroserverpy/api/services/iam/workspace.py index d1314687..cccfcc1a 100644 --- a/packages/hydroserverpy/src/hydroserverpy/api/services/iam/workspace.py +++ b/packages/hydroserverpy/src/hydroserverpy/api/services/iam/workspace.py @@ -25,7 +25,7 @@ def list( self, offset: int = ..., limit: int = ..., - order_by: List[str] = ..., + sortby: List[str] = ..., is_private: bool = ..., is_associated: bool = ..., fetch_all: bool = False, @@ -35,7 +35,7 @@ def list( return super().list( offset=offset, limit=limit, - order_by=order_by, + sortby=sortby, fetch_all=fetch_all, is_private=is_private, is_associated=is_associated, diff --git a/packages/hydroserverpy/src/hydroserverpy/api/services/monitoring/rule.py b/packages/hydroserverpy/src/hydroserverpy/api/services/monitoring/rule.py index 3f4aa27c..fd63c736 100644 --- a/packages/hydroserverpy/src/hydroserverpy/api/services/monitoring/rule.py +++ b/packages/hydroserverpy/src/hydroserverpy/api/services/monitoring/rule.py @@ -19,7 +19,7 @@ def list( workspace: Optional[Union[UUID, str]] = ..., offset: int = ..., limit: int = ..., - order_by: List[str] = ..., + sortby: List[str] = ..., datastream: Optional[Union[UUID, str]] = ..., rule_type: str = ..., fetch_all: bool = False, @@ -29,7 +29,7 @@ def list( return super().list( offset=offset, limit=limit, - order_by=order_by, + sortby=sortby, fetch_all=fetch_all, task_id=normalize_uuid(task), workspace_id=normalize_uuid(workspace), diff --git a/packages/hydroserverpy/src/hydroserverpy/api/services/monitoring/task.py b/packages/hydroserverpy/src/hydroserverpy/api/services/monitoring/task.py index 9beac6a3..67b872e2 100644 --- a/packages/hydroserverpy/src/hydroserverpy/api/services/monitoring/task.py +++ b/packages/hydroserverpy/src/hydroserverpy/api/services/monitoring/task.py @@ -3,7 +3,7 @@ from uuid import UUID from hydroserverpy.api.models.monitoring.task import MonitoringTask from hydroserverpy.api.models.orchestration.run import TaskRun -from hydroserverpy.api.utils import normalize_uuid, order_by_to_camel +from hydroserverpy.api.utils import normalize_uuid, sortby_to_camel from ..base import HydroServerBaseService if TYPE_CHECKING: @@ -19,7 +19,7 @@ def list( self, offset: int = ..., limit: int = ..., - order_by: List[str] = ..., + sortby: List[str] = ..., monitoring_site: Optional[Union[UUID, str]] = ..., workspace: Optional[Union[UUID, str]] = ..., latest_run_status: str = ..., @@ -32,7 +32,7 @@ def list( return super().list( offset=offset, limit=limit, - order_by=order_by, + sortby=sortby, fetch_all=fetch_all, monitoring_site_id=normalize_uuid(monitoring_site), workspace_id=normalize_uuid(workspace), @@ -123,7 +123,7 @@ def list_runs( uid: Union[UUID, str], offset: int = ..., limit: int = ..., - order_by: List[str] = ..., + sortby: List[str] = ..., status: str = ..., started_at_min: datetime = ..., started_at_max: datetime = ..., @@ -135,7 +135,7 @@ def list_runs( params = { "offset": offset, "limit": limit, - "order_by": [order_by_to_camel(o) for o in order_by] if order_by is not ... else order_by, + "sortby": [sortby_to_camel(o) for o in sortby] if sortby is not ... else sortby, "status": status, "started_at_min": started_at_min, "started_at_max": started_at_max, diff --git a/packages/hydroserverpy/src/hydroserverpy/api/services/products/rating_curve.py b/packages/hydroserverpy/src/hydroserverpy/api/services/products/rating_curve.py index 15bcb0e0..c83e3188 100644 --- a/packages/hydroserverpy/src/hydroserverpy/api/services/products/rating_curve.py +++ b/packages/hydroserverpy/src/hydroserverpy/api/services/products/rating_curve.py @@ -17,7 +17,7 @@ def list( self, offset: int = ..., limit: int = ..., - order_by: List[str] = ..., + sortby: List[str] = ..., monitoring_site: Optional[Union[UUID, str]] = ..., workspace: Optional[Union[UUID, str]] = ..., fetch_all: bool = False, @@ -27,7 +27,7 @@ def list( return super().list( offset=offset, limit=limit, - order_by=order_by, + sortby=sortby, fetch_all=fetch_all, monitoring_site_id=normalize_uuid(monitoring_site), workspace_id=normalize_uuid(workspace), diff --git a/packages/hydroserverpy/src/hydroserverpy/api/services/products/task.py b/packages/hydroserverpy/src/hydroserverpy/api/services/products/task.py index 7b50d020..65096f5a 100644 --- a/packages/hydroserverpy/src/hydroserverpy/api/services/products/task.py +++ b/packages/hydroserverpy/src/hydroserverpy/api/services/products/task.py @@ -3,7 +3,7 @@ from uuid import UUID from hydroserverpy.api.models.products.task import DataProductTask from hydroserverpy.api.models.orchestration.run import TaskRun -from hydroserverpy.api.utils import normalize_uuid, order_by_to_camel +from hydroserverpy.api.utils import normalize_uuid, sortby_to_camel from ..base import HydroServerBaseService if TYPE_CHECKING: @@ -19,7 +19,7 @@ def list( self, offset: int = ..., limit: int = ..., - order_by: List[str] = ..., + sortby: List[str] = ..., monitoring_site: Optional[Union[UUID, str]] = ..., workspace: Optional[Union[UUID, str]] = ..., latest_run_status: str = ..., @@ -34,7 +34,7 @@ def list( return super().list( offset=offset, limit=limit, - order_by=order_by, + sortby=sortby, fetch_all=fetch_all, monitoring_site_id=normalize_uuid(monitoring_site), workspace_id=normalize_uuid(workspace), @@ -123,7 +123,7 @@ def list_runs( uid: Union[UUID, str], offset: int = ..., limit: int = ..., - order_by: List[str] = ..., + sortby: List[str] = ..., status: str = ..., started_at_min: datetime = ..., started_at_max: datetime = ..., @@ -135,7 +135,7 @@ def list_runs( params = { "offset": offset, "limit": limit, - "order_by": [order_by_to_camel(o) for o in order_by] if order_by is not ... else order_by, + "sortby": [sortby_to_camel(o) for o in sortby] if sortby is not ... else sortby, "status": status, "started_at_min": started_at_min, "started_at_max": started_at_max, diff --git a/packages/hydroserverpy/src/hydroserverpy/api/services/sta/datastream.py b/packages/hydroserverpy/src/hydroserverpy/api/services/sta/datastream.py index 6863e45a..f1934645 100644 --- a/packages/hydroserverpy/src/hydroserverpy/api/services/sta/datastream.py +++ b/packages/hydroserverpy/src/hydroserverpy/api/services/sta/datastream.py @@ -29,7 +29,7 @@ def list( self, offset: int = ..., limit: int = ..., - order_by: List[str] = ..., + sortby: List[str] = ..., workspace: Union["Workspace", UUID, str] = ..., monitoring_site: Union["MonitoringSite", UUID, str] = ..., method: Union["Method", UUID, str] = ..., @@ -59,7 +59,7 @@ def list( return super().list( offset=offset, limit=limit, - order_by=order_by, + sortby=sortby, workspace_id=normalize_uuid(workspace), monitoring_site_id=normalize_uuid(monitoring_site), method_id=normalize_uuid(method), @@ -238,7 +238,7 @@ def get_observations( uid: Union[UUID, str], offset: int = ..., limit: int = 100000, - order_by: List[str] = ..., + sortby: List[str] = ..., phenomenon_time_max: datetime = ..., phenomenon_time_min: datetime = ..., result_qualifier_code: str = ..., @@ -250,7 +250,7 @@ def get_observations( "datastream_id": str(uid), "offset": offset, "limit": limit, - "order_by": ",".join(order_by) if order_by is not ... else order_by, + "sortby": ",".join(sortby) if sortby is not ... else sortby, "phenomenon_time_max": phenomenon_time_max, "phenomenon_time_min": phenomenon_time_min, "result_qualifier_code": result_qualifier_code, @@ -268,11 +268,11 @@ def get_observations( collection = ObservationCollection( datastream=datastream, response=response, - order_by=order_by if order_by is not ... else None, + sortby=sortby if sortby is not ... else None, filters={ k: v for k, v in params.items() - if k not in ["datastream_id", "offset", "limit", "order_by", "format"] + if k not in ["datastream_id", "offset", "limit", "sortby", "format"] }, ) if fetch_all is True: diff --git a/packages/hydroserverpy/src/hydroserverpy/api/services/sta/method.py b/packages/hydroserverpy/src/hydroserverpy/api/services/sta/method.py index ecd62b5f..b3389d84 100644 --- a/packages/hydroserverpy/src/hydroserverpy/api/services/sta/method.py +++ b/packages/hydroserverpy/src/hydroserverpy/api/services/sta/method.py @@ -18,7 +18,7 @@ def list( self, offset: int = ..., limit: int = ..., - order_by: List[str] = ..., + sortby: List[str] = ..., workspace: Optional[Union["Workspace", UUID, str]] = ..., monitoring_site: Optional[Union["MonitoringSite", UUID, str]] = ..., datastream: Optional[Union["Datastream", UUID, str]] = ..., @@ -32,7 +32,7 @@ def list( return super().list( offset=offset, limit=limit, - order_by=order_by, + sortby=sortby, workspace_id=normalize_uuid(workspace), monitoring_site_id=normalize_uuid(monitoring_site), datastream_id=normalize_uuid(datastream), diff --git a/packages/hydroserverpy/src/hydroserverpy/api/services/sta/monitoring_site.py b/packages/hydroserverpy/src/hydroserverpy/api/services/sta/monitoring_site.py index fbe9aa5a..c084ea68 100644 --- a/packages/hydroserverpy/src/hydroserverpy/api/services/sta/monitoring_site.py +++ b/packages/hydroserverpy/src/hydroserverpy/api/services/sta/monitoring_site.py @@ -19,7 +19,7 @@ def list( self, offset: int = ..., limit: int = ..., - order_by: List[str] = ..., + sortby: List[str] = ..., workspace: Union["Workspace", UUID, str] = ..., bbox: Tuple[float, float, float, float] = ..., admin_area_1: str = ..., @@ -35,7 +35,7 @@ def list( return super().list( offset=offset, limit=limit, - order_by=order_by, + sortby=sortby, workspace_id=normalize_uuid(workspace), bbox=",".join([str(i) for i in bbox]) if bbox is not ... else bbox, admin_area_1=admin_area_1, diff --git a/packages/hydroserverpy/src/hydroserverpy/api/services/sta/observed_property.py b/packages/hydroserverpy/src/hydroserverpy/api/services/sta/observed_property.py index 8ea9d927..0097d8f5 100644 --- a/packages/hydroserverpy/src/hydroserverpy/api/services/sta/observed_property.py +++ b/packages/hydroserverpy/src/hydroserverpy/api/services/sta/observed_property.py @@ -18,7 +18,7 @@ def list( self, offset: int = ..., limit: int = ..., - order_by: List[str] = ..., + sortby: List[str] = ..., workspace: Optional[Union["Workspace", UUID, str]] = ..., monitoring_site: Optional[Union["MonitoringSite", UUID, str]] = ..., datastream: Optional[Union["Datastream", UUID, str]] = ..., @@ -30,7 +30,7 @@ def list( return super().list( offset=offset, limit=limit, - order_by=order_by, + sortby=sortby, workspace_id=normalize_uuid(workspace), monitoring_site_id=normalize_uuid(monitoring_site), datastream_id=normalize_uuid(datastream), diff --git a/packages/hydroserverpy/src/hydroserverpy/api/services/sta/processing_level.py b/packages/hydroserverpy/src/hydroserverpy/api/services/sta/processing_level.py index a6f1018a..6167b756 100644 --- a/packages/hydroserverpy/src/hydroserverpy/api/services/sta/processing_level.py +++ b/packages/hydroserverpy/src/hydroserverpy/api/services/sta/processing_level.py @@ -18,7 +18,7 @@ def list( self, offset: int = ..., limit: int = ..., - order_by: List[str] = ..., + sortby: List[str] = ..., workspace: Optional[Union["Workspace", UUID, str]] = ..., monitoring_site: Optional[Union["MonitoringSite", UUID, str]] = ..., datastream: Optional[Union["Datastream", UUID, str]] = ..., @@ -29,7 +29,7 @@ def list( return super().list( offset=offset, limit=limit, - order_by=order_by, + sortby=sortby, workspace_id=normalize_uuid(workspace), monitoring_site_id=normalize_uuid(monitoring_site), datastream_id=normalize_uuid(datastream), diff --git a/packages/hydroserverpy/src/hydroserverpy/api/services/sta/result_qualifier.py b/packages/hydroserverpy/src/hydroserverpy/api/services/sta/result_qualifier.py index 0b69e943..2b475db9 100644 --- a/packages/hydroserverpy/src/hydroserverpy/api/services/sta/result_qualifier.py +++ b/packages/hydroserverpy/src/hydroserverpy/api/services/sta/result_qualifier.py @@ -18,7 +18,7 @@ def list( self, offset: int = ..., limit: int = ..., - order_by: List[str] = ..., + sortby: List[str] = ..., workspace: Optional[Union["Workspace", UUID, str]] = ..., fetch_all: bool = False, ) -> List["ResultQualifier"]: @@ -27,7 +27,7 @@ def list( return super().list( offset=offset, limit=limit, - order_by=order_by, + sortby=sortby, workspace_id=normalize_uuid(workspace), fetch_all=fetch_all, ) diff --git a/packages/hydroserverpy/src/hydroserverpy/api/services/sta/unit.py b/packages/hydroserverpy/src/hydroserverpy/api/services/sta/unit.py index e65c613c..60183fb3 100644 --- a/packages/hydroserverpy/src/hydroserverpy/api/services/sta/unit.py +++ b/packages/hydroserverpy/src/hydroserverpy/api/services/sta/unit.py @@ -18,7 +18,7 @@ def list( self, offset: int = ..., limit: int = ..., - order_by: List[str] = ..., + sortby: List[str] = ..., workspace: Union["Workspace", UUID, str] = ..., monitoring_site: Optional[Union["MonitoringSite", UUID, str]] = ..., datastream: Optional[Union["Datastream", UUID, str]] = ..., @@ -30,7 +30,7 @@ def list( return super().list( offset=offset, limit=limit, - order_by=order_by, + sortby=sortby, workspace_id=normalize_uuid(workspace), monitoring_site_id=normalize_uuid(monitoring_site), datastream_id=normalize_uuid(datastream), diff --git a/packages/hydroserverpy/src/hydroserverpy/api/utils.py b/packages/hydroserverpy/src/hydroserverpy/api/utils.py index d698c0f8..1abc7b7c 100644 --- a/packages/hydroserverpy/src/hydroserverpy/api/utils.py +++ b/packages/hydroserverpy/src/hydroserverpy/api/utils.py @@ -16,7 +16,7 @@ def normalize_uuid( return str(obj) -def order_by_to_camel(s: str) -> str: +def sortby_to_camel(s: str) -> str: if s.startswith('-'): return '-' + to_camel(s[1:]) return to_camel(s)