From 4326ef03e58d3c2160885d7ab30975a550e9e2ba Mon Sep 17 00:00:00 2001 From: Ken Lippold Date: Tue, 15 Sep 2026 13:48:06 -0700 Subject: [PATCH 1/2] Rename API query param order_by to sortby --- .../components/Browse/BrowseFilterTool.vue | 2 +- .../components/Datastream/DatastreamTable.vue | 6 +- .../data-products/AggregationForm.vue | 2 +- .../data-products/DerivationForm.vue | 2 +- .../data-products/RatingCurveForm.vue | 4 +- .../data-products/RatingCurveTable.vue | 2 +- .../monitoring/QualityManagementForm.vue | 4 +- .../AccessControl/ManageCollaborators.vue | 2 +- .../AccessControl/ManageServiceAccounts.vue | 2 +- .../__tests__/useOrchestrationData.spec.ts | 8 +- .../orchestration/useOrchestrationData.ts | 10 +- .../orchestration/useSimpleTaskDetails.ts | 2 +- .../src/composables/useMetadata.ts | 10 +- .../src/store/orchestration.ts | 2 +- .../data-management/src/store/ratingCurves.ts | 2 +- .../utils/__tests__/observationsUtils.spec.ts | 2 +- .../src/utils/observationsUtils.ts | 4 +- .../how-to/using-the-typescript-client.md | 12 +- .../tutorials/building-your-first-app.md | 2 +- .../how-to/using-the-python-client.md | 6 +- apps/qc-app/docs/API_REFERENCE.md | 2 +- apps/qc-app/src/utils/observations.ts | 2 +- django/contracts/openapi/data.openapi.json | 1536 +++++++++-------- django/interfaces/api/schemas/__init__.py | 2 +- django/interfaces/api/schemas/base.py | 2 +- .../api/schemas/etl/data_connection.py | 10 +- django/interfaces/api/schemas/etl/mapping.py | 8 +- django/interfaces/api/schemas/etl/task.py | 8 +- .../api/schemas/iam/collaborator.py | 6 + django/interfaces/api/schemas/iam/role.py | 8 +- .../api/schemas/iam/service_account.py | 12 +- .../interfaces/api/schemas/iam/workspace.py | 10 +- .../interfaces/api/schemas/monitoring/rule.py | 10 +- .../interfaces/api/schemas/monitoring/task.py | 10 +- .../api/schemas/orchestration/run.py | 10 +- .../api/schemas/products/rating_curve.py | 11 +- .../interfaces/api/schemas/products/task.py | 10 +- .../api/schemas/products/transformation.py | 10 +- .../interfaces/api/schemas/quality/history.py | 10 +- .../api/schemas/quality/operation.py | 10 +- .../interfaces/api/schemas/quality/session.py | 10 +- .../interfaces/api/schemas/sta/datastream.py | 12 +- django/interfaces/api/schemas/sta/method.py | 10 +- .../api/schemas/sta/monitoring_site.py | 10 +- .../interfaces/api/schemas/sta/observation.py | 10 +- .../api/schemas/sta/observed_property.py | 10 +- .../api/schemas/sta/processing_level.py | 10 +- .../api/schemas/sta/result_qualifier.py | 10 +- django/interfaces/api/schemas/sta/unit.py | 8 +- django/interfaces/api/service.py | 57 +- .../api/services/etl/data_connection.py | 15 +- django/interfaces/api/services/etl/mapping.py | 13 +- django/interfaces/api/services/etl/task.py | 25 +- .../api/services/iam/collaborator.py | 16 +- django/interfaces/api/services/iam/role.py | 17 +- .../api/services/iam/service_account.py | 13 +- .../interfaces/api/services/iam/workspace.py | 17 +- .../api/services/monitoring/rule.py | 13 +- .../api/services/monitoring/task.py | 25 +- .../api/services/products/rating_curve.py | 21 +- .../interfaces/api/services/products/task.py | 25 +- .../api/services/products/transformation.py | 13 +- .../api/services/quality/history.py | 17 +- .../api/services/quality/operation.py | 18 +- .../api/services/quality/session.py | 23 +- .../interfaces/api/services/sta/datastream.py | 33 +- django/interfaces/api/services/sta/method.py | 21 +- .../api/services/sta/monitoring_site.py | 35 +- .../api/services/sta/observation.py | 14 +- .../api/services/sta/observed_property.py | 21 +- .../api/services/sta/processing_level.py | 17 +- .../api/services/sta/result_qualifier.py | 17 +- django/interfaces/api/services/sta/unit.py | 21 +- .../api/views/etl/data_connection.py | 2 +- django/interfaces/api/views/etl/mapping.py | 2 +- django/interfaces/api/views/etl/task.py | 6 +- .../interfaces/api/views/iam/collaborator.py | 1 + django/interfaces/api/views/iam/role.py | 2 +- .../api/views/iam/service_account.py | 2 +- django/interfaces/api/views/iam/workspace.py | 2 +- .../interfaces/api/views/monitoring/rule.py | 2 +- .../interfaces/api/views/monitoring/task.py | 6 +- .../api/views/products/rating_curve.py | 2 +- django/interfaces/api/views/products/task.py | 6 +- .../api/views/products/transformation.py | 2 +- .../interfaces/api/views/quality/history.py | 2 +- .../interfaces/api/views/quality/operation.py | 2 +- .../interfaces/api/views/quality/session.py | 2 +- django/interfaces/api/views/sta/datastream.py | 10 +- django/interfaces/api/views/sta/method.py | 4 +- .../api/views/sta/monitoring_site.py | 6 +- .../interfaces/api/views/sta/observation.py | 2 +- .../api/views/sta/observed_property.py | 4 +- .../api/views/sta/processing_level.py | 2 +- .../api/views/sta/result_qualifier.py | 2 +- django/interfaces/api/views/sta/unit.py | 4 +- .../processing/orchestration/services/task.py | 31 +- django/tests/core/test_service.py | 12 +- .../test_data_product_transformation_views.py | 8 +- .../interfaces/api/test_etl_task_views.py | 8 +- .../api/test_monitoring_rule_views.py | 12 +- .../interfaces/api/test_rating_curve_views.py | 14 +- .../__tests__/rating-curve.service.spec.ts | 4 +- .../src/api/__tests__/task.service.spec.ts | 2 +- .../src/generated/data.types.d.ts | 414 ++--- .../src/hydroserverpy/api/README.md | 6 +- .../src/hydroserverpy/api/models/base.py | 12 +- .../src/hydroserverpy/api/models/etl/task.py | 4 +- .../api/models/monitoring/task.py | 4 +- .../hydroserverpy/api/models/products/task.py | 4 +- .../api/models/sta/datastream.py | 6 +- .../api/models/sta/observation.py | 18 +- .../src/hydroserverpy/api/services/base.py | 8 +- .../api/services/etl/data_connection.py | 4 +- .../hydroserverpy/api/services/etl/mapping.py | 8 +- .../hydroserverpy/api/services/etl/task.py | 10 +- .../hydroserverpy/api/services/iam/role.py | 4 +- .../api/services/iam/workspace.py | 4 +- .../api/services/monitoring/rule.py | 8 +- .../api/services/monitoring/task.py | 10 +- .../api/services/products/rating_curve.py | 4 +- .../api/services/products/task.py | 10 +- .../api/services/sta/datastream.py | 12 +- .../hydroserverpy/api/services/sta/method.py | 4 +- .../api/services/sta/monitoring_site.py | 4 +- .../api/services/sta/observed_property.py | 4 +- .../api/services/sta/processing_level.py | 4 +- .../api/services/sta/result_qualifier.py | 4 +- .../hydroserverpy/api/services/sta/unit.py | 4 +- .../src/hydroserverpy/api/utils.py | 2 +- 130 files changed, 1574 insertions(+), 1538 deletions(-) diff --git a/apps/data-management/src/components/Browse/BrowseFilterTool.vue b/apps/data-management/src/components/Browse/BrowseFilterTool.vue index 03162579..9b41aa01 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/DatastreamTable.vue b/apps/data-management/src/components/Datastream/DatastreamTable.vue index 74e364f8..97e7949f 100644 --- a/apps/data-management/src/components/Datastream/DatastreamTable.vue +++ b/apps/data-management/src/components/Datastream/DatastreamTable.vue @@ -1724,15 +1724,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 a6fbc3b4..47c9bf7f 100644 --- a/apps/data-management/src/components/Orchestration/data-products/AggregationForm.vue +++ b/apps/data-management/src/components/Orchestration/data-products/AggregationForm.vue @@ -364,7 +364,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 81610de6..7b3293ef 100644 --- a/apps/data-management/src/components/Orchestration/data-products/DerivationForm.vue +++ b/apps/data-management/src/components/Orchestration/data-products/DerivationForm.vue @@ -510,7 +510,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 bfc38940..6b594e09 100644 --- a/apps/data-management/src/components/Orchestration/data-products/RatingCurveForm.vue +++ b/apps/data-management/src/components/Orchestration/data-products/RatingCurveForm.vue @@ -315,7 +315,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[] @@ -369,7 +369,7 @@ async function loadRatingCurves(monitoringSiteId: string) { ratingCurvesLoading.value = true try { const items = await hs.ratingCurves.listItemsForMonitoringSite(monitoringSiteId, { - order_by: ['name'], + sortby: ['name'], }) ratingCurves.value = [...items].sort((a, b) => a.name.localeCompare(b.name)) } catch (error: any) { 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 2142dad4..5d129344 100644 --- a/apps/data-management/src/components/Orchestration/monitoring/QualityManagementForm.vue +++ b/apps/data-management/src/components/Orchestration/monitoring/QualityManagementForm.vue @@ -464,7 +464,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[] @@ -495,7 +495,7 @@ async function loadExistingTask() { const [taskRes, rulesRes] = await Promise.all([ hs.monitoringTasks.get(props.editTaskId), hs.monitoringTasks.listRules(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 6ff6c098..a470f98b 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'], }) await data.fetchTasksForGroup('aggregation', 'monitoringSite-1', 'workspace-1') @@ -107,7 +107,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') @@ -115,7 +115,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 d4ed4f79..f4a94996 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'], } as any) if (requestId !== taskRequestId) return workspaceTasks.value = items as any @@ -143,7 +143,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 @@ -151,7 +151,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 a3ae245d..3934336f 100644 --- a/apps/data-management/src/store/orchestration.ts +++ b/apps/data-management/src/store/orchestration.ts @@ -100,7 +100,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 f3aa814e..af21a044 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( @@ -904,7 +904,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 5dbbc97e..cfcc6701 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,40 @@ "title": "Properties", "type": "array" }, + "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 +1226,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.", @@ -1280,6 +1266,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.", @@ -1772,28 +1794,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.", @@ -1830,6 +1830,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" + }, "transformation_type": { "default": [], "description": "Filter transformations by type.", @@ -2612,46 +2636,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": [ { @@ -2858,6 +2842,50 @@ "title": "Sampled Medium", "type": "array" }, + "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.", @@ -3336,7 +3364,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": { @@ -3357,23 +3400,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": [], @@ -3696,7 +3724,28 @@ "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" + }, + "sortby": { "anyOf": [ { "items": { @@ -3723,35 +3772,14 @@ "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, - "items": { - "enum": [ - "id", - "workspaceId", - "dataConnectionId", - "schedule", - "latestRun", - "mappingCount", - "name", - "description", - "taskVariables" - ], - "type": "string" - }, - "style": "form", - "title": "Properties", - "type": "array" + }, + { + "type": "null" + } + ], + "default": [], + "description": "Select one or more fields to sort the response by.", + "title": "Sortby" }, "workspace_id": { "default": [], @@ -4948,34 +4976,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, @@ -5016,6 +5016,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", @@ -5385,30 +5417,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, @@ -5437,6 +5445,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" } }, "title": "MonitoringRuleQueryParameters", @@ -6156,7 +6190,36 @@ "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" + }, + "sortby": { "anyOf": [ { "items": { @@ -6193,37 +6256,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": [], @@ -6689,7 +6723,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", + "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" + }, + "sortby": { "anyOf": [ { "items": { @@ -6722,38 +6786,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": [], @@ -7296,28 +7330,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": [ { @@ -7370,12 +7382,34 @@ }, "result_qualifier_code": { "default": [], - "description": "Filter observations by result qualifier code.", - "items": { - "type": "string" - }, - "title": "Result Qualifier Code", - "type": "array" + "description": "Filter observations by result qualifier code.", + "items": { + "type": "string" + }, + "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", @@ -7708,7 +7742,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", + "workspaceId", + "name", + "definition", + "description", + "type", + "code" + ], + "type": "string" + }, + "style": "form", + "title": "Properties", + "type": "array" + }, + "sortby": { "anyOf": [ { "items": { @@ -7729,27 +7782,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": [], @@ -9202,7 +9236,25 @@ "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" + }, + "sortby": { "anyOf": [ { "items": { @@ -9221,26 +9273,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": [], @@ -9434,7 +9468,27 @@ "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", + "managedDatastreamId", + "sourceDatastreamId", + "createdAt", + "phenomenonTimeStart", + "phenomenonTimeEnd", + "sourceChecksum", + "managedChecksum" + ], + "type": "string" + }, + "style": "form", + "title": "Properties", + "type": "array" + }, + "sortby": { "anyOf": [ { "items": { @@ -9457,28 +9511,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", - "managedDatastreamId", - "sourceDatastreamId", - "createdAt", - "phenomenonTimeStart", - "phenomenonTimeEnd", - "sourceChecksum", - "managedChecksum" - ], - "type": "string" - }, - "style": "form", - "title": "Properties", - "type": "array" + "description": "Select one or more fields to sort the response by.", + "title": "Sortby" }, "source_datastream_id": { "default": [], @@ -9740,7 +9774,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": { @@ -9763,27 +9816,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", @@ -10022,36 +10056,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, @@ -10108,6 +10112,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": [ { @@ -10434,36 +10468,6 @@ "description": "Number of items to skip.", "title": "Offset" }, - "order_by": { - "anyOf": [ - { - "items": { - "enum": [ - "id", - "name", - "monitoringSiteId", - "monitoringSiteName", - "workspaceId", - "workspaceName", - "-id", - "-name", - "-monitoringSiteId", - "-monitoringSiteName", - "-workspaceId", - "-workspaceName" - ], - "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, @@ -10482,6 +10486,38 @@ "title": "Properties", "type": "array" }, + "sortby": { + "anyOf": [ + { + "items": { + "enum": [ + "id", + "name", + "monitoringSiteId", + "monitoringSiteName", + "workspaceId", + "workspaceName", + "fittingMethod", + "-id", + "-name", + "-monitoringSiteId", + "-monitoringSiteName", + "-workspaceId", + "-workspaceName", + "-fittingMethod" + ], + "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 rating curves by workspace ID.", @@ -10733,7 +10769,23 @@ "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" + }, + "sortby": { "anyOf": [ { "items": { @@ -10750,24 +10802,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": [], @@ -10895,26 +10931,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, @@ -10932,6 +10948,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.", @@ -11432,30 +11468,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, @@ -11476,6 +11488,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", @@ -11655,9 +11695,21 @@ "description": "Number of items to skip.", "title": "Offset" }, - "order_by": { + "properties": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Comma-separated list of properties to include in the response. All properties are returned if omitted.", + "title": "Properties" + }, + "sortby": { "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", @@ -11671,21 +11723,9 @@ ], "type": "string" }, - "title": "Order By", + "title": "Sortby", "type": "array" }, - "properties": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Comma-separated list of properties to include in the response. All properties are returned if omitted.", - "title": "Properties" - }, "started_at_max": { "anyOf": [ { @@ -12063,7 +12103,25 @@ "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" + }, + "sortby": { "anyOf": [ { "items": { @@ -12084,26 +12142,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": [], @@ -12527,30 +12567,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", @@ -12711,7 +12751,25 @@ "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" + }, + "sortby": { "anyOf": [ { "items": { @@ -12730,26 +12788,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", @@ -12967,9 +13007,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": [ @@ -12982,6 +13022,8 @@ "status", "resultType", "isPrivate", + "isVisible", + "aggregationStatistic", "valueCount", "phenomenonBeginTime", "phenomenonEndTime", @@ -12993,6 +13035,8 @@ "-status", "-resultType", "-isPrivate", + "-isVisible", + "-aggregationStatistic", "-valueCount", "-phenomenonBeginTime", "-phenomenonEndTime", @@ -13008,8 +13052,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" } }, { @@ -13636,7 +13680,7 @@ { "description": "Sort terms by descending.", "in": "query", - "name": "order_desc", + "name": "sort_desc", "required": false, "schema": { "anyOf": [ @@ -13649,7 +13693,7 @@ ], "default": false, "description": "Sort terms by descending.", - "title": "Order Desc" + "title": "Sort Desc" } } ], @@ -13756,7 +13800,7 @@ { "description": "Sort terms by descending.", "in": "query", - "name": "order_desc", + "name": "sort_desc", "required": false, "schema": { "anyOf": [ @@ -13769,7 +13813,7 @@ ], "default": false, "description": "Sort terms by descending.", - "title": "Order Desc" + "title": "Sort Desc" } } ], @@ -13876,7 +13920,7 @@ { "description": "Sort terms by descending.", "in": "query", - "name": "order_desc", + "name": "sort_desc", "required": false, "schema": { "anyOf": [ @@ -13889,7 +13933,7 @@ ], "default": false, "description": "Sort terms by descending.", - "title": "Order Desc" + "title": "Sort Desc" } } ], @@ -13996,7 +14040,7 @@ { "description": "Sort terms by descending.", "in": "query", - "name": "order_desc", + "name": "sort_desc", "required": false, "schema": { "anyOf": [ @@ -14009,7 +14053,7 @@ ], "default": false, "description": "Sort terms by descending.", - "title": "Order Desc" + "title": "Sort Desc" } } ], @@ -15204,9 +15248,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": [ @@ -15239,8 +15283,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" } }, { @@ -15786,9 +15830,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": [ @@ -15823,8 +15867,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" } }, { @@ -16508,9 +16552,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": [ @@ -16533,8 +16577,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" } }, { @@ -17143,13 +17187,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", @@ -17163,7 +17207,7 @@ ], "type": "string" }, - "title": "Order By", + "title": "Sortby", "type": "array" } }, @@ -17608,9 +17652,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": [ @@ -17622,11 +17666,15 @@ "type", "sensorModel", "sensorModelManufacturer", + "definition", + "sensorModelDefinition", "-name", "-code", "-type", "-sensorModel", - "-sensorModelManufacturer" + "-sensorModelManufacturer", + "-definition", + "-sensorModelDefinition" ], "type": "string" }, @@ -17637,8 +17685,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" } }, { @@ -17976,7 +18024,7 @@ { "description": "Sort terms by descending.", "in": "query", - "name": "order_desc", + "name": "sort_desc", "required": false, "schema": { "anyOf": [ @@ -17989,7 +18037,7 @@ ], "default": false, "description": "Sort terms by descending.", - "title": "Order Desc" + "title": "Sort Desc" } } ], @@ -18394,9 +18442,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": [ @@ -18435,8 +18483,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" } }, { @@ -18759,7 +18807,7 @@ { "description": "Sort terms by descending.", "in": "query", - "name": "order_desc", + "name": "sort_desc", "required": false, "schema": { "anyOf": [ @@ -18772,7 +18820,7 @@ ], "default": false, "description": "Sort terms by descending.", - "title": "Order Desc" + "title": "Sort Desc" } } ], @@ -19089,7 +19137,7 @@ { "description": "Sort terms by descending.", "in": "query", - "name": "order_desc", + "name": "sort_desc", "required": false, "schema": { "anyOf": [ @@ -19102,7 +19150,7 @@ ], "default": false, "description": "Sort terms by descending.", - "title": "Order Desc" + "title": "Sort Desc" } } ], @@ -20232,9 +20280,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": [ @@ -20269,8 +20317,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" } }, { @@ -20886,9 +20934,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": [ @@ -20898,9 +20946,11 @@ "id", "ruleType", "datastreamId", + "lastCheckedAt", "-id", "-ruleType", - "-datastreamId" + "-datastreamId", + "-lastCheckedAt" ], "type": "string" }, @@ -20911,8 +20961,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" } }, { @@ -21526,13 +21576,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", @@ -21546,7 +21596,7 @@ ], "type": "string" }, - "title": "Order By", + "title": "Sortby", "type": "array" } }, @@ -22004,9 +22054,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": [ @@ -22027,8 +22077,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" } }, { @@ -22713,9 +22763,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": [ @@ -22738,8 +22788,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" } }, { @@ -23047,7 +23097,7 @@ { "description": "Sort terms by descending.", "in": "query", - "name": "order_desc", + "name": "sort_desc", "required": false, "schema": { "anyOf": [ @@ -23060,7 +23110,7 @@ ], "default": false, "description": "Sort terms by descending.", - "title": "Order Desc" + "title": "Sort Desc" } } ], @@ -23451,9 +23501,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": [ @@ -23474,8 +23524,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" } }, { @@ -24051,9 +24101,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": [ @@ -24066,12 +24116,14 @@ "monitoringSiteName", "workspaceId", "workspaceName", + "fittingMethod", "-id", "-name", "-monitoringSiteId", "-monitoringSiteName", "-workspaceId", - "-workspaceName" + "-workspaceName", + "-fittingMethod" ], "type": "string" }, @@ -24082,8 +24134,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" } }, { @@ -24632,9 +24684,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": [ @@ -24669,8 +24721,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" } }, { @@ -25308,13 +25360,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", @@ -25328,7 +25380,7 @@ ], "type": "string" }, - "title": "Order By", + "title": "Sortby", "type": "array" } }, @@ -25707,9 +25759,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": [ @@ -25718,8 +25770,10 @@ "enum": [ "id", "outputDatastreamId", + "transformationType", "-id", - "-outputDatastreamId" + "-outputDatastreamId", + "-transformationType" ], "type": "string" }, @@ -25730,8 +25784,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" } }, { @@ -26449,9 +26503,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": [ @@ -26476,8 +26530,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" } }, { @@ -26960,9 +27014,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": [ @@ -26991,8 +27045,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" } }, { @@ -27767,9 +27821,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": [ @@ -27794,8 +27848,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" } } ], @@ -28401,9 +28455,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": [ @@ -28422,8 +28476,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" } }, { @@ -28962,9 +29016,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": [ @@ -28983,8 +29037,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" } }, { @@ -29238,9 +29292,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": [ @@ -29263,8 +29317,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" } }, { @@ -29561,7 +29615,7 @@ { "description": "Sort terms by descending.", "in": "query", - "name": "order_desc", + "name": "sort_desc", "required": false, "schema": { "anyOf": [ @@ -29574,7 +29628,7 @@ ], "default": false, "description": "Sort terms by descending.", - "title": "Order Desc" + "title": "Sort Desc" } } ], @@ -29966,9 +30020,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": [ @@ -29989,8 +30043,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" } }, { @@ -30606,6 +30660,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": { @@ -30930,9 +31012,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": [ @@ -30942,9 +31024,13 @@ "name", "isActive", "keyExpiresAt", + "createdAt", + "lastUsedAt", "-name", "-isActive", - "-keyExpiresAt" + "-keyExpiresAt", + "-createdAt", + "-lastUsedAt" ], "type": "string" }, @@ -30955,8 +31041,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/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..46c86151 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,8 @@ 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." ) 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 f22a9ac7..e47bb904 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..6fe4daba 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,8 @@ 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." ) 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..288d517a 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,8 @@ 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." ) 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 ef9d5aea..ce49dc60 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..1bdeda44 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,8 @@ 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." ) 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 4b1fe388..0cdcac5f 100644 --- a/django/interfaces/api/schemas/orchestration/run.py +++ b/django/interfaces/api/schemas/orchestration/run.py @@ -8,15 +8,15 @@ from interfaces.api.schemas import BaseGetResponse, CollectionQueryParameters -_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] ] class TaskRunQueryParameters(CollectionQueryParameters): - 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..1305ede5 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,8 @@ 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." ) 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..cb014a2e 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,8 @@ 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." ) 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 79814bb0..22f58051 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 1ef19da3..09cf29db 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,8 @@ 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." ) 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..aa72e14e 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,8 @@ 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." ) 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..5276c842 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,8 @@ 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." ) 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..d6fa93dd 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,8 @@ 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." ) 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..08a09def 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,8 @@ 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." ) 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..16afc779 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,8 @@ 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." ) 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..6bd7e2f7 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,8 @@ 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." ) 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..e740cfe6 100644 --- a/django/interfaces/api/service.py +++ b/django/interfaces/api/service.py @@ -77,38 +77,37 @@ 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",), ): - 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 = 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) + + # Add ID as a sortby tiebreaker if not already present. + 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) @staticmethod def resolve_include_set(include: Optional[list[str]]) -> set[str]: @@ -306,13 +305,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..7d80e3af 100644 --- a/django/interfaces/api/services/etl/data_connection.py +++ b/django/interfaces/api/services/etl/data_connection.py @@ -25,7 +25,7 @@ ) from interfaces.api.schemas.etl.data_connection import ( DATA_CONNECTION_INCLUDE_RELATIONS, - DataConnectionOrderByFields, + DataConnectionSortByFields, DataConnectionResponse, ) @@ -79,7 +79,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 +138,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, ): @@ -160,12 +160,9 @@ def list( 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 = self.apply_sorting( + queryset, sortby, list(get_args(DataConnectionSortByFields)), self.sortby_aliases + ) 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 35ef396f..2e4c7928 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, @@ -93,7 +93,7 @@ def list( task_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, ): @@ -107,12 +107,9 @@ 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(EtlMappingOrderByFields)) - ) - else: - queryset = queryset.order_by("id") + queryset = self.apply_sorting( + queryset, sortby, list(get_args(EtlMappingSortByFields)) + ) if requested_includes: select_paths, prefetch_paths = self._include_query_hints(requested_includes) diff --git a/django/interfaces/api/services/etl/task.py b/django/interfaces/api/services/etl/task.py index 4314d999..64c6c012 100644 --- a/django/interfaces/api/services/etl/task.py +++ b/django/interfaces/api/services/etl/task.py @@ -14,7 +14,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 +30,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 +87,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, ): @@ -96,14 +96,14 @@ 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) @@ -141,12 +141,9 @@ 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 = self.apply_sorting( + queryset, sortby, list(get_args(EtlTaskSortByFields)), self.sortby_aliases + ) 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..459d54a9 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,11 @@ 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 = self.apply_sorting( + queryset, + sortby, + list(get_args(WorkspaceSortByFields)), + ) 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 a8d3522e..73bcd314 100644 --- a/django/interfaces/api/services/monitoring/rule.py +++ b/django/interfaces/api/services/monitoring/rule.py @@ -11,7 +11,7 @@ from interfaces.api.service import APIService from interfaces.api.schemas.monitoring.rule import ( MonitoringRuleFields, - MonitoringRuleOrderByFields, + MonitoringRuleSortByFields, MonitoringRulePatchBody, MonitoringRulePostBody, MonitoringRuleResponse, @@ -75,7 +75,7 @@ def list( task_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, ): @@ -87,12 +87,9 @@ 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(MonitoringRuleOrderByFields)) - ) - else: - queryset = queryset.order_by("datastream_id", "rule_type") + queryset = self.apply_sorting( + queryset, sortby, list(get_args(MonitoringRuleSortByFields)) + ) if requested_includes: select_paths = [ diff --git a/django/interfaces/api/services/monitoring/task.py b/django/interfaces/api/services/monitoring/task.py index 40ae6bbf..40aed5da 100644 --- a/django/interfaces/api/services/monitoring/task.py +++ b/django/interfaces/api/services/monitoring/task.py @@ -15,7 +15,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 +33,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 +124,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, ): @@ -133,10 +133,10 @@ 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) @@ -163,12 +163,9 @@ 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 = self.apply_sorting( + queryset, sortby, list(get_args(MonitoringTaskSortByFields)), self.sortby_aliases + ) 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..7f8ddb0d 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,12 @@ 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 = self.apply_sorting( + queryset, + sortby, + list(get_args(RatingCurveSortByFields)), + field_aliases=RATING_CURVE_SORTBY_ALIASES, + ) queryset = queryset.prefetch_related("points") diff --git a/django/interfaces/api/services/products/task.py b/django/interfaces/api/services/products/task.py index 9986bc10..705cd8c3 100644 --- a/django/interfaces/api/services/products/task.py +++ b/django/interfaces/api/services/products/task.py @@ -15,7 +15,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 +35,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 +121,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, ): @@ -130,10 +130,10 @@ 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) @@ -176,12 +176,9 @@ 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 = self.apply_sorting( + queryset, sortby, list(get_args(DataProductTaskSortByFields)), self.sortby_aliases + ) 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 6d1ad5cf..c105ad1a 100644 --- a/django/interfaces/api/services/products/transformation.py +++ b/django/interfaces/api/services/products/transformation.py @@ -12,7 +12,7 @@ from interfaces.api.service import APIService from interfaces.api.schemas.products.transformation import ( DataProductTransformationFields, - DataProductTransformationOrderByFields, + DataProductTransformationSortByFields, DataProductTransformationPatchBody, DataProductTransformationPostBody, DataProductTransformationResponse, @@ -91,7 +91,7 @@ def list( task_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, ): @@ -107,12 +107,9 @@ 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(DataProductTransformationOrderByFields)) - ) - else: - queryset = queryset.order_by("id") + queryset = self.apply_sorting( + queryset, sortby, list(get_args(DataProductTransformationSortByFields)) + ) queryset = queryset.prefetch_related("input_datastreams") 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 7b908371..46aef504 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, ): @@ -159,14 +159,11 @@ def list( 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 = self.apply_sorting( + queryset, + sortby, + list(get_args(DatastreamSortByFields)), + ) queryset = queryset.select_related("monitoring_site").prefetch_related( "datastream_linked_resources" @@ -499,10 +496,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) @@ -512,9 +509,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} @@ -523,9 +520,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} @@ -534,10 +531,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..527e4e9c 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,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(MethodOrderByFields)), - ) - else: - queryset = queryset.order_by("id") + queryset = self.apply_sorting( + queryset, + sortby, + list(get_args(MethodSortByFields)), + ) if requested_includes: select_paths = [ @@ -195,9 +192,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..7ba62b90 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, ): @@ -427,19 +427,16 @@ 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 = self.apply_sorting( + queryset, + sortby, + list(get_args(MonitoringSiteSortByFields)), + { + "elevationDatum": "elevation_datum", + "adminArea1": "admin_area_1", + "adminArea2": "admin_area_2", + }, + ) queryset = queryset.prefetch_related("monitoring_site_linked_resources") @@ -647,9 +644,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 +655,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..0da15830 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,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(ObservedPropertyOrderByFields)), - ) - else: - queryset = queryset.order_by("id") + queryset = self.apply_sorting( + queryset, + sortby, + list(get_args(ObservedPropertySortByFields)), + ) if requested_includes: select_paths = [ @@ -194,9 +191,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..3da04283 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,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(ProcessingLevelOrderByFields)), - ) - else: - queryset = queryset.order_by("id") + queryset = self.apply_sorting( + queryset, + sortby, + list(get_args(ProcessingLevelSortByFields)), + ) 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..3587e4d9 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,11 @@ 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 = self.apply_sorting( + queryset, + sortby, + list(get_args(ResultQualifierSortByFields)), + ) 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..575d7e55 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,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(UnitOrderByFields)), - ) - else: - queryset = queryset.order_by("id") + queryset = self.apply_sorting( + queryset, + sortby, + list(get_args(UnitSortByFields)), + ) if requested_includes: select_paths = [ @@ -184,9 +181,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 553628d3..796060f0 100644 --- a/django/interfaces/api/views/etl/mapping.py +++ b/django/interfaces/api/views/etl/mapping.py @@ -41,7 +41,7 @@ def get_etl_mappings( task_id=task_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/etl/task.py b/django/interfaces/api/views/etl/task.py index 0d9175ab..c5480207 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, ) @@ -201,8 +201,8 @@ def get_etl_task_runs( count, runs = etl_task_service.get_run_collection( task=task_id, principal=request.principal, - order_by=query.order_by, - **query.model_dump(exclude_unset=True, exclude={"order_by"}), + sortby=query.sortby, + **query.model_dump(exclude_unset=True, exclude={"sortby"}), ) meta = etl_task_service.build_pagination_meta( 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 74272cbf..21b2527d 100644 --- a/django/interfaces/api/views/monitoring/rule.py +++ b/django/interfaces/api/views/monitoring/rule.py @@ -43,7 +43,7 @@ def get_monitoring_rules( task_id=task_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/monitoring/task.py b/django/interfaces/api/views/monitoring/task.py index fa1c145b..a9d5a01f 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, ) @@ -201,8 +201,8 @@ def get_monitoring_task_runs( count, runs = monitoring_task_service.get_run_collection( task=task_id, principal=request.principal, - order_by=query.order_by, - **query.model_dump(exclude_unset=True, exclude={"order_by"}), + sortby=query.sortby, + **query.model_dump(exclude_unset=True, exclude={"sortby"}), ) meta = monitoring_task_service.build_pagination_meta( diff --git a/django/interfaces/api/views/products/rating_curve.py b/django/interfaces/api/views/products/rating_curve.py index a495cdd0..319b4471 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 fc7a2691..b69da0ab 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, ) @@ -203,8 +203,8 @@ def get_data_product_task_runs( count, runs = data_product_task_service.get_run_collection( task=task_id, principal=request.principal, - order_by=query.order_by, - **query.model_dump(exclude_unset=True, exclude={"order_by"}), + sortby=query.sortby, + **query.model_dump(exclude_unset=True, exclude={"sortby"}), ) meta = data_product_task_service.build_pagination_meta( diff --git a/django/interfaces/api/views/products/transformation.py b/django/interfaces/api/views/products/transformation.py index 0fe94e9b..2c9ff712 100644 --- a/django/interfaces/api/views/products/transformation.py +++ b/django/interfaces/api/views/products/transformation.py @@ -41,7 +41,7 @@ def get_data_product_transformations( task_id=task_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/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/orchestration/services/task.py b/django/processing/orchestration/services/task.py index 0af91592..158a96d1 100644 --- a/django/processing/orchestration/services/task.py +++ b/django/processing/orchestration/services/task.py @@ -25,8 +25,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,7 +127,7 @@ def get_run_collection( principal: User | ServiceAccount | AnonymousPrincipal | Unset = Unset, offset: int = Field(ge=0, default=0), limit: int = Field(gt=0, default=100), - order_by: list[str] = Field(default_factory=list), + sortby: list[str] = Field(default_factory=list), search_term: str | Unset = Unset, status: list[str] | Unset = Unset, started_at__gte: datetime | Unset = Unset, @@ -162,16 +162,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 +200,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 +223,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/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_product_transformation_views.py b/django/tests/interfaces/api/test_data_product_transformation_views.py index 88335aab..dc55b8d5 100644 --- a/django/tests/interfaces/api/test_data_product_transformation_views.py +++ b/django/tests/interfaces/api/test_data_product_transformation_views.py @@ -295,7 +295,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) @@ -303,20 +303,20 @@ def test_get_transformations_orders_by_output_datastream_id(client): t2 = _make_rating_curve_transformation(task, monitoring_site) client.force_login(owner) - response = client.get(_transformations_url(task.id), {"order_by": "outputDatastreamId"}) + response = client.get(_transformations_url(task.id), {"sortby": "outputDatastreamId"}) expected = sorted([str(t1.output_datastream_id), str(t2.output_datastream_id)]) assert response.status_code == 200 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), {"order_by": "bogus"}) + response = client.get(_transformations_url(task.id), {"sortby": "bogus"}) assert response.status_code == 400 diff --git a/django/tests/interfaces/api/test_etl_task_views.py b/django/tests/interfaces/api/test_etl_task_views.py index 99c6ec23..e10dea0c 100644 --- a/django/tests/interfaces/api/test_etl_task_views.py +++ b/django/tests/interfaces/api/test_etl_task_views.py @@ -361,7 +361,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) @@ -370,19 +370,19 @@ 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 f39bfd09..eac5fb4b 100644 --- a/django/tests/interfaces/api/test_monitoring_rule_views.py +++ b/django/tests/interfaces/api/test_monitoring_rule_views.py @@ -296,7 +296,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) @@ -304,7 +304,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), {"order_by": "ruleType"}) + response = client.get(_rules_url(task.id), {"sortby": "ruleType"}) assert response.status_code == 200 assert [r["ruleType"] for r in response.json()["data"]] == [ @@ -313,7 +313,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) @@ -322,7 +322,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), {"order_by": "-datastreamId"}) + response = client.get(_rules_url(task.id), {"sortby": "-datastreamId"}) datastream_ids_desc = sorted( [str(datastream_a.id), str(datastream_b.id)], reverse=True @@ -332,13 +332,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), {"order_by": "bogus"}) + response = client.get(_rules_url(task.id), {"sortby": "bogus"}) assert response.status_code == 400 diff --git a/django/tests/interfaces/api/test_rating_curve_views.py b/django/tests/interfaces/api/test_rating_curve_views.py index a40b4d50..774444ab 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 d10efc11..d5732db8 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/products/rating-curves?order_by=name&monitoring_site_id=monitoringSite-1&offset=0&limit=200' + 'https://hydro.example.com/api/data/products/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 77680fb1..ba83366b 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 6299ffd7..c4b0b101 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,12 @@ 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")[]; + /** + * 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 +2270,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. @@ -2287,6 +2287,12 @@ export interface components { * @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. @@ -2443,12 +2449,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. @@ -2460,6 +2460,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; /** * Transformation Type * @description Filter transformations by type. @@ -2734,12 +2740,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. @@ -2809,6 +2809,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. @@ -3012,17 +3018,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. @@ -3159,17 +3165,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" | "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")[]; + /** + * 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. @@ -3552,12 +3558,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" | "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. @@ -3575,6 +3575,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 @@ -3686,12 +3692,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. @@ -3703,6 +3703,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; }; /** MonitoringRuleResponse */ MonitoringRuleResponse: { @@ -3957,17 +3963,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" | "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")[]; + /** + * 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} @@ -4166,12 +4172,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; /** * Properties * @description Comma-separated list of properties to include in the response. All properties are returned if omitted. @@ -4183,6 +4183,12 @@ export interface components { * @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. @@ -4374,12 +4380,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. @@ -4401,6 +4401,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: { @@ -4522,17 +4528,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" | "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")[]; + /** + * 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 @@ -4963,17 +4969,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: ("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")[]; + /** + * 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. @@ -5050,17 +5056,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. @@ -5153,17 +5159,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: { @@ -5244,12 +5250,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. @@ -5265,6 +5265,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; }; @@ -5394,17 +5400,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" | "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")[]; + /** + * 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. @@ -5504,17 +5510,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: ("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")[]; + /** + * 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. @@ -5563,17 +5569,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. @@ -5714,17 +5720,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: { @@ -5792,17 +5798,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?: string | null; + /** + * 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. @@ -5936,17 +5942,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" | "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")[]; + /** + * 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 @@ -6109,17 +6115,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: { @@ -6179,17 +6185,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" | "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")[]; + /** + * Sortby + * @description Select one or more fields to sort the response by. + * @default [] + */ + sortby: ("name" | "isPrivate" | "-name" | "-isPrivate")[] | null; }; /** WorkspaceResponse */ WorkspaceResponse: { @@ -6237,8 +6243,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" | "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 Filter datastreams by workspace ID. */ workspace_id?: string[]; /** @description Filter datastreams by monitoring_site ID. */ @@ -6375,7 +6381,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; @@ -6406,7 +6412,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; @@ -6437,7 +6443,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; @@ -6468,7 +6474,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; @@ -6989,8 +6995,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" | "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 Filter data connections by workspace ID. */ workspace_id?: string[]; /** @description Filter data connections by payload type. */ @@ -7245,8 +7251,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" | "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 Filter ETL tasks by monitoring_site ID. */ monitoring_site_id?: string[]; /** @description Filter ETL tasks by workspace ID. */ @@ -7522,8 +7528,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. */ @@ -7812,8 +7818,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. */ @@ -7981,8 +7987,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" | "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 Filter methods by workspace ID. */ workspace_id?: (string | "null")[]; /** @description Filter methods by monitoring_site ID. */ @@ -8094,7 +8100,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; @@ -8276,8 +8282,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" | "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 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} */ @@ -8375,7 +8381,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; @@ -8496,7 +8502,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; @@ -8981,8 +8987,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" | "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 Filter monitoring tasks by monitoring_site ID. */ monitoring_site_id?: string[]; /** @description Filter monitoring tasks by workspace ID. */ @@ -9252,8 +9258,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. */ @@ -9542,8 +9548,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. */ @@ -9713,8 +9719,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. */ @@ -10014,8 +10020,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" | "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 Filter observed properties by workspace ID. */ workspace_id?: (string | "null")[]; /** @description Filter observed properties by monitoring_site ID. */ @@ -10123,7 +10129,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; @@ -10305,8 +10311,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?: ("code" | "name" | "-code" | "-name")[] | null; + /** @description Select one or more fields to sort the response by. */ + sortby?: ("code" | "name" | "-code" | "-name")[] | null; /** @description Filter processing levels by workspace ID. */ workspace_id?: (string | "null")[]; /** @description Filter processing levels by monitoring_site ID. */ @@ -10563,8 +10569,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" | "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 Filter rating curves by monitoring site ID. */ monitoring_site_id?: string[]; /** @description Filter rating curves by workspace ID. */ @@ -10828,8 +10834,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" | "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 Filter data product tasks by monitoring_site ID. */ monitoring_site_id?: string[]; /** @description Filter data product tasks by workspace ID. */ @@ -11103,8 +11109,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. */ @@ -11223,8 +11229,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. */ @@ -11564,8 +11570,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. */ @@ -11778,8 +11784,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; @@ -12128,8 +12134,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: { @@ -12417,8 +12423,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?: ("code" | "-code")[] | null; + /** @description Select one or more fields to sort the response by. */ + sortby?: ("code" | "-code")[] | null; /** @description Filter result qualifiers by workspace ID. */ workspace_id?: (string | "null")[]; /** @description Filter result qualifiers by monitoring_site ID. */ @@ -12648,8 +12654,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")[]; }; @@ -12733,8 +12739,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" | "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 Filter units by workspace ID. */ workspace_id?: (string | "null")[]; /** @description Filter units by monitoring_site ID. */ @@ -12833,7 +12839,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; @@ -13015,8 +13021,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" | "isPrivate" | "-name" | "-isPrivate")[] | null; + /** @description Select one or more fields to sort the response by. */ + sortby?: ("name" | "isPrivate" | "-name" | "-isPrivate")[] | 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. */ @@ -13255,6 +13261,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: { @@ -13459,8 +13467,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 232bf617..a604ca81 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 @@ -218,7 +218,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) if total_count is None else total_count, diff --git a/packages/hydroserverpy/src/hydroserverpy/api/models/etl/task.py b/packages/hydroserverpy/src/hydroserverpy/api/models/etl/task.py index 76356a75..1c51b8a1 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 2e1fe7de..cd6fdbd2 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 708fc43e..8ecdc5c7 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 0165c1d6..59d8ed84 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 @@ -131,7 +131,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) if total_count is None else total_count, 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 dbfeb51f..6095eb6e 100644 --- a/packages/hydroserverpy/src/hydroserverpy/api/services/etl/mapping.py +++ b/packages/hydroserverpy/src/hydroserverpy/api/services/etl/mapping.py @@ -3,7 +3,7 @@ from uuid import UUID from hydroserverpy.api.models.etl.mapping import EtlMapping from hydroserverpy.api.models.base import HydroServerCollection -from hydroserverpy.api.utils import normalize_uuid, order_by_to_camel +from hydroserverpy.api.utils import normalize_uuid, sortby_to_camel if TYPE_CHECKING: from hydroserverpy import HydroServer @@ -22,7 +22,7 @@ def list( task_id: Union[UUID, str], offset: int = ..., limit: int = ..., - order_by: List[str] = ..., + sortby: List[str] = ..., source_identifier: str = ..., fetch_all: bool = False, ) -> HydroServerCollection: @@ -31,7 +31,7 @@ def list( 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, "source_identifier": source_identifier, } params = {k: ("null" if v is None else v) for k, v in params.items() if v is not ...} @@ -48,7 +48,7 @@ def list( client=self.client, service=self, response=response, - order_by=params.get("order_by"), + sortby=params.get("sortby"), filters={"task_id": task_id}, items=items, ) 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 38afa164..926d52b9 100644 --- a/packages/hydroserverpy/src/hydroserverpy/api/services/monitoring/rule.py +++ b/packages/hydroserverpy/src/hydroserverpy/api/services/monitoring/rule.py @@ -4,7 +4,7 @@ from uuid import UUID from hydroserverpy.api.models.monitoring.rule import MonitoringRule from hydroserverpy.api.models.base import HydroServerCollection -from hydroserverpy.api.utils import normalize_uuid, order_by_to_camel +from hydroserverpy.api.utils import normalize_uuid, sortby_to_camel if TYPE_CHECKING: from hydroserverpy import HydroServer @@ -29,7 +29,7 @@ def list( task_id: 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, @@ -39,7 +39,7 @@ def list( 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, "datastream_id": normalize_uuid(datastream), "rule_type": rule_type, } @@ -57,7 +57,7 @@ def list( client=self.client, service=self, response=response, - order_by=params.get("order_by"), + sortby=params.get("sortby"), filters={"task_id": task_id}, items=items, ) 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) From 0d2219243830b57cb2d5383dc017d1c6e79eb3e1 Mon Sep 17 00:00:00 2001 From: Ken Lippold Date: Fri, 18 Sep 2026 09:32:39 -0700 Subject: [PATCH 2/2] Added search query parameter to API --- django/contracts/openapi/data.openapi.json | 390 ++++++++++++++++++ .../0012_workspace_search_vector_and_more.py | 89 ++++ django/core/iam/models/workspace.py | 6 + ...ch_vector_method_search_vector_and_more.py | 185 +++++++++ django/core/sta/models/datastream.py | 3 + django/core/sta/models/method.py | 8 + django/core/sta/models/monitoring_site.py | 3 + django/core/sta/models/observed_property.py | 6 + django/core/sta/models/processing_level.py | 8 + django/core/sta/models/result_qualifier.py | 6 + django/core/sta/models/unit.py | 8 + .../api/schemas/etl/data_connection.py | 5 + django/interfaces/api/schemas/etl/task.py | 5 + .../interfaces/api/schemas/iam/workspace.py | 5 + .../interfaces/api/schemas/monitoring/task.py | 5 + .../api/schemas/products/rating_curve.py | 5 + .../interfaces/api/schemas/products/task.py | 5 + .../interfaces/api/schemas/sta/datastream.py | 5 + django/interfaces/api/schemas/sta/method.py | 5 + .../api/schemas/sta/monitoring_site.py | 5 + .../api/schemas/sta/observed_property.py | 5 + .../api/schemas/sta/processing_level.py | 5 + .../api/schemas/sta/result_qualifier.py | 5 + django/interfaces/api/schemas/sta/unit.py | 5 + django/interfaces/api/service.py | 47 ++- .../api/services/etl/data_connection.py | 15 +- django/interfaces/api/services/etl/task.py | 14 +- .../interfaces/api/services/iam/workspace.py | 2 + .../api/services/monitoring/task.py | 14 +- .../api/services/products/rating_curve.py | 2 + .../interfaces/api/services/products/task.py | 14 +- .../interfaces/api/services/sta/datastream.py | 3 +- django/interfaces/api/services/sta/method.py | 2 + .../api/services/sta/monitoring_site.py | 3 +- .../api/services/sta/observed_property.py | 2 + .../api/services/sta/processing_level.py | 2 + .../api/services/sta/result_qualifier.py | 2 + django/interfaces/api/services/sta/unit.py | 2 + ...1_dataconnection_search_vector_and_more.py | 92 +++++ .../processing/etl/models/data_connection.py | 6 + ...search_vector_task_orch_task_search_gin.py | 90 ++++ .../processing/orchestration/models/task.py | 6 + .../processing/orchestration/services/task.py | 6 - ...0006_ratingcurve_search_vector_and_more.py | 92 +++++ .../products/models/rating_curve.py | 6 + .../api/test_data_connection_views.py | 45 ++ .../interfaces/api/test_datastream_views.py | 14 + .../interfaces/api/test_etl_task_views.py | 16 + .../api/test_monitoring_site_views.py | 115 ++++++ .../src/generated/data.types.d.ts | 91 ++++ 50 files changed, 1436 insertions(+), 44 deletions(-) create mode 100644 django/core/iam/migrations/0012_workspace_search_vector_and_more.py create mode 100644 django/core/sta/migrations/0017_datastream_search_vector_method_search_vector_and_more.py create mode 100644 django/processing/etl/migrations/0011_dataconnection_search_vector_and_more.py create mode 100644 django/processing/orchestration/migrations/0002_task_search_vector_task_orch_task_search_gin.py create mode 100644 django/processing/products/migrations/0006_ratingcurve_search_vector_and_more.py diff --git a/django/contracts/openapi/data.openapi.json b/django/contracts/openapi/data.openapi.json index 25aa8cf3..38c76331 100644 --- a/django/contracts/openapi/data.openapi.json +++ b/django/contracts/openapi/data.openapi.json @@ -678,6 +678,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" + }, "sortby": { "anyOf": [ { @@ -1256,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.", @@ -2782,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": [ { @@ -3809,6 +3845,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" + }, "sortby": { "anyOf": [ { @@ -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", @@ -6315,6 +6375,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" + }, "sortby": { "anyOf": [ { @@ -6840,6 +6912,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" + }, "rule_type": { "default": [], "description": "Filter monitoring tasks by rule type.", @@ -7857,6 +7941,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" + }, "sortby": { "anyOf": [ { @@ -9353,6 +9449,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" + }, "sortby": { "anyOf": [ { @@ -10585,6 +10693,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" + }, "sortby": { "anyOf": [ { @@ -10884,6 +11004,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" + }, "sortby": { "anyOf": [ { @@ -12226,6 +12358,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" + }, "sortby": { "anyOf": [ { @@ -12892,6 +13036,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" + }, "sortby": { "anyOf": [ { @@ -13140,6 +13296,24 @@ "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" + } + }, { "description": "Filter rating curves by monitoring site ID.", "in": "query", @@ -13727,6 +13901,24 @@ "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" + } + }, { "description": "Filter data product tasks by monitoring_site ID.", "in": "query", @@ -15565,6 +15757,24 @@ "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" + } + }, { "description": "Filter datastreams by workspace ID.", "in": "query", @@ -17796,6 +18006,24 @@ "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" + } + }, { "description": "Filter data connections by workspace ID.", "in": "query", @@ -19000,6 +19228,24 @@ "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" + } + }, { "description": "Filter ETL tasks by monitoring_site ID.", "in": "query", @@ -20185,6 +20431,24 @@ "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" + } + }, { "description": "Filter methods by workspace ID.", "in": "query", @@ -21615,6 +21879,24 @@ "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" + } + }, { "description": "Filter monitoring sites by workspace ID.", "in": "query", @@ -23449,6 +23731,24 @@ "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" + } + }, { "description": "Filter monitoring tasks by monitoring_site ID.", "in": "query", @@ -25275,6 +25575,24 @@ "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" + } + }, { "description": "Filter observed properties by workspace ID.", "in": "query", @@ -26011,6 +26329,24 @@ "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" + } + }, { "description": "Filter processing levels by workspace ID.", "in": "query", @@ -28564,6 +28900,24 @@ "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" + } + }, { "description": "Filter result qualifiers by workspace ID.", "in": "query", @@ -29405,6 +29759,24 @@ "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" + } + }, { "description": "Filter units by workspace ID.", "in": "query", @@ -30131,6 +30503,24 @@ "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" + } + }, { "description": "Whether the workspace is associated with the authenticated user", "in": "query", 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/etl/data_connection.py b/django/interfaces/api/schemas/etl/data_connection.py index 46c86151..1c4711c2 100644 --- a/django/interfaces/api/schemas/etl/data_connection.py +++ b/django/interfaces/api/schemas/etl/data_connection.py @@ -97,6 +97,11 @@ class DataConnectionQueryParameters(DataConnectionFilterFields, CollectionQueryP 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/task.py b/django/interfaces/api/schemas/etl/task.py index 6fe4daba..6586729b 100644 --- a/django/interfaces/api/schemas/etl/task.py +++ b/django/interfaces/api/schemas/etl/task.py @@ -93,6 +93,11 @@ class EtlTaskQueryParameters(EtlTaskFilterFields, CollectionQueryParameters): 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/workspace.py b/django/interfaces/api/schemas/iam/workspace.py index 288d517a..5709811b 100644 --- a/django/interfaces/api/schemas/iam/workspace.py +++ b/django/interfaces/api/schemas/iam/workspace.py @@ -88,6 +88,11 @@ class WorkspaceQueryParameters(WorkspaceFilterFields, CollectionQueryParameters) 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, description="Whether the workspace is associated with the authenticated user", diff --git a/django/interfaces/api/schemas/monitoring/task.py b/django/interfaces/api/schemas/monitoring/task.py index 1bdeda44..99954848 100644 --- a/django/interfaces/api/schemas/monitoring/task.py +++ b/django/interfaces/api/schemas/monitoring/task.py @@ -95,6 +95,11 @@ class MonitoringTaskQueryParameters(MonitoringTaskFilterFields, CollectionQueryP 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/products/rating_curve.py b/django/interfaces/api/schemas/products/rating_curve.py index 1305ede5..ee71173c 100644 --- a/django/interfaces/api/schemas/products/rating_curve.py +++ b/django/interfaces/api/schemas/products/rating_curve.py @@ -85,6 +85,11 @@ class RatingCurveQueryParameters(RatingCurveFilterFields, CollectionQueryParamet 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 cb014a2e..6d5139df 100644 --- a/django/interfaces/api/schemas/products/task.py +++ b/django/interfaces/api/schemas/products/task.py @@ -94,6 +94,11 @@ class DataProductTaskQueryParameters(DataProductTaskFilterFields, CollectionQuer 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/sta/datastream.py b/django/interfaces/api/schemas/sta/datastream.py index 588e891b..095cc46e 100644 --- a/django/interfaces/api/schemas/sta/datastream.py +++ b/django/interfaces/api/schemas/sta/datastream.py @@ -149,6 +149,11 @@ class DatastreamQueryParameters(DatastreamFilterFields, CollectionQueryParameter 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 aa72e14e..c067f0f9 100644 --- a/django/interfaces/api/schemas/sta/method.py +++ b/django/interfaces/api/schemas/sta/method.py @@ -80,6 +80,11 @@ class MethodQueryParameters(MethodFilterFields, CollectionQueryParameters): 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 5276c842..84740b51 100644 --- a/django/interfaces/api/schemas/sta/monitoring_site.py +++ b/django/interfaces/api/schemas/sta/monitoring_site.py @@ -118,6 +118,11 @@ class MonitoringSiteQueryParameters(MonitoringSiteFilterFields, CollectionQueryP 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/observed_property.py b/django/interfaces/api/schemas/sta/observed_property.py index d6fa93dd..7f357ef4 100644 --- a/django/interfaces/api/schemas/sta/observed_property.py +++ b/django/interfaces/api/schemas/sta/observed_property.py @@ -77,6 +77,11 @@ class ObservedPropertyQueryParameters(ObservedPropertyFilterFields, CollectionQu 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 08a09def..5d5cbac7 100644 --- a/django/interfaces/api/schemas/sta/processing_level.py +++ b/django/interfaces/api/schemas/sta/processing_level.py @@ -72,6 +72,11 @@ class ProcessingLevelQueryParameters(ProcessingLevelFilterFields, CollectionQuer 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 16afc779..d941cb85 100644 --- a/django/interfaces/api/schemas/sta/result_qualifier.py +++ b/django/interfaces/api/schemas/sta/result_qualifier.py @@ -70,6 +70,11 @@ class ResultQualifierQueryParameters(ResultQualifierFilterFields, CollectionQuer 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 6bd7e2f7..7135132d 100644 --- a/django/interfaces/api/schemas/sta/unit.py +++ b/django/interfaces/api/schemas/sta/unit.py @@ -72,6 +72,11 @@ class UnitQueryParameters(UnitFilterFields, CollectionQueryParameters): 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 e740cfe6..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], @@ -83,11 +88,12 @@ def apply_sorting( allowed_fields: list[str], field_aliases: Optional[dict[str, str]] = None, default_sortby: tuple[str, ...] = ("id",), + rank: bool = False, ): field_aliases = field_aliases or {} if not sortby: - sortby_fields = list(default_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)): @@ -102,13 +108,48 @@ def apply_sorting( resolved_field = field_aliases.get(stripped_field, to_snake(stripped_field)) sortby_fields.append(f"-{resolved_field}" if descending else resolved_field) - # Add ID as a sortby tiebreaker if not already present. + 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]: """Resolve a list of include items into a set of unique items.""" diff --git a/django/interfaces/api/services/etl/data_connection.py b/django/interfaces/api/services/etl/data_connection.py index 7d80e3af..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 @@ -147,21 +146,19 @@ 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"]) + queryset, has_search = self.apply_search(queryset, filtering.get("q")) queryset = self.apply_sorting( - queryset, sortby, list(get_args(DataConnectionSortByFields)), self.sortby_aliases + queryset, + sortby, + list(get_args(DataConnectionSortByFields)), + self.sortby_aliases, + rank=has_search, ) queryset = queryset.prefetch_related("placeholder_variables", "payload") diff --git a/django/interfaces/api/services/etl/task.py b/django/interfaces/api/services/etl/task.py index c294d5dc..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 @@ -108,12 +107,6 @@ def list( ): 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,8 +135,13 @@ def list( if "latest_run_finished_at_max" in filtering: queryset = queryset.filter(latest_run_finished_at__lte=filtering["latest_run_finished_at_max"]) + queryset, has_search = self.apply_search(queryset, filtering.get("q")) queryset = self.apply_sorting( - queryset, sortby, list(get_args(EtlTaskSortByFields)), self.sortby_aliases + 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/workspace.py b/django/interfaces/api/services/iam/workspace.py index 459d54a9..89e1741e 100644 --- a/django/interfaces/api/services/iam/workspace.py +++ b/django/interfaces/api/services/iam/workspace.py @@ -90,10 +90,12 @@ def list( else: queryset = self.apply_filters(queryset, field, filtering[field]) + 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( diff --git a/django/interfaces/api/services/monitoring/task.py b/django/interfaces/api/services/monitoring/task.py index 7d94ce63..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 @@ -141,12 +140,6 @@ def list( ): 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,8 +157,13 @@ def list( if "rule_type" in filtering: queryset = self.apply_filters(queryset, "rules__rule_type", filtering["rule_type"]) + queryset, has_search = self.apply_search(queryset, filtering.get("q")) queryset = self.apply_sorting( - queryset, sortby, list(get_args(MonitoringTaskSortByFields)), self.sortby_aliases + queryset, + sortby, + list(get_args(MonitoringTaskSortByFields)), + self.sortby_aliases, + rank=has_search, ) queryset = queryset.select_related( diff --git a/django/interfaces/api/services/products/rating_curve.py b/django/interfaces/api/services/products/rating_curve.py index 7f8ddb0d..6c75fd01 100644 --- a/django/interfaces/api/services/products/rating_curve.py +++ b/django/interfaces/api/services/products/rating_curve.py @@ -79,11 +79,13 @@ def list( if field in filtering: queryset = self.apply_filters(queryset, field, filtering[field]) + 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 9e3c8eef..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 @@ -138,12 +137,6 @@ def list( ): 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,8 +170,13 @@ def list( queryset, "transformations__rating_curve", filtering["rating_curve"] ) + queryset, has_search = self.apply_search(queryset, filtering.get("q")) queryset = self.apply_sorting( - queryset, sortby, list(get_args(DataProductTaskSortByFields)), self.sortby_aliases + queryset, + sortby, + list(get_args(DataProductTaskSortByFields)), + self.sortby_aliases, + rank=has_search, ) queryset = queryset.select_related( diff --git a/django/interfaces/api/services/sta/datastream.py b/django/interfaces/api/services/sta/datastream.py index 044832cd..36e253b4 100644 --- a/django/interfaces/api/services/sta/datastream.py +++ b/django/interfaces/api/services/sta/datastream.py @@ -158,11 +158,12 @@ def list( queryset = self.apply_filters(queryset, field, filtering[field]) queryset = self.apply_tag_filter(queryset, filtering.get("tag")) - + 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( diff --git a/django/interfaces/api/services/sta/method.py b/django/interfaces/api/services/sta/method.py index 527e4e9c..2695367e 100644 --- a/django/interfaces/api/services/sta/method.py +++ b/django/interfaces/api/services/sta/method.py @@ -77,10 +77,12 @@ def list( if field in filtering: queryset = self.apply_filters(queryset, field, filtering[field]) + 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: diff --git a/django/interfaces/api/services/sta/monitoring_site.py b/django/interfaces/api/services/sta/monitoring_site.py index 7ba62b90..5cc4f53d 100644 --- a/django/interfaces/api/services/sta/monitoring_site.py +++ b/django/interfaces/api/services/sta/monitoring_site.py @@ -426,7 +426,7 @@ def list( queryset = self.apply_bbox_filter(queryset, filtering.get("bbox")) queryset = self.apply_tag_filter(queryset, filtering.get("tag")) - + queryset, has_search = self.apply_search(queryset, filtering.get("q")) queryset = self.apply_sorting( queryset, sortby, @@ -436,6 +436,7 @@ def list( "adminArea1": "admin_area_1", "adminArea2": "admin_area_2", }, + rank=has_search, ) queryset = queryset.prefetch_related("monitoring_site_linked_resources") diff --git a/django/interfaces/api/services/sta/observed_property.py b/django/interfaces/api/services/sta/observed_property.py index 0da15830..92164be3 100644 --- a/django/interfaces/api/services/sta/observed_property.py +++ b/django/interfaces/api/services/sta/observed_property.py @@ -75,10 +75,12 @@ def list( if field in filtering: queryset = self.apply_filters(queryset, field, filtering[field]) + 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: diff --git a/django/interfaces/api/services/sta/processing_level.py b/django/interfaces/api/services/sta/processing_level.py index 3da04283..5ce91651 100644 --- a/django/interfaces/api/services/sta/processing_level.py +++ b/django/interfaces/api/services/sta/processing_level.py @@ -78,10 +78,12 @@ def list( if field in filtering: queryset = self.apply_filters(queryset, field, filtering[field]) + 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: diff --git a/django/interfaces/api/services/sta/result_qualifier.py b/django/interfaces/api/services/sta/result_qualifier.py index 3587e4d9..d386483c 100644 --- a/django/interfaces/api/services/sta/result_qualifier.py +++ b/django/interfaces/api/services/sta/result_qualifier.py @@ -80,10 +80,12 @@ def list( ): queryset = ResultQualifier.objects.none() + 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: diff --git a/django/interfaces/api/services/sta/unit.py b/django/interfaces/api/services/sta/unit.py index 575d7e55..4d545f98 100644 --- a/django/interfaces/api/services/sta/unit.py +++ b/django/interfaces/api/services/sta/unit.py @@ -74,10 +74,12 @@ def list( if field in filtering: queryset = self.apply_filters(queryset, field, filtering[field]) + 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: 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 02344614..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 @@ -128,7 +127,6 @@ def get_run_collection( offset: int = Field(ge=0, default=0), limit: int = Field(ge=0, le=100000, default=100), sortby: list[str] = Field(default_factory=list), - search_term: str | Unset = Unset, 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) 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/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_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 7b471f8c..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 ----------------------------------------------------------------- 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/packages/hydroserver-ts/src/generated/data.types.d.ts b/packages/hydroserver-ts/src/generated/data.types.d.ts index a4532b6b..c3df75d2 100644 --- a/packages/hydroserver-ts/src/generated/data.types.d.ts +++ b/packages/hydroserver-ts/src/generated/data.types.d.ts @@ -2106,6 +2106,11 @@ 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. @@ -2281,6 +2286,11 @@ 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. @@ -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. @@ -3214,6 +3229,11 @@ export interface components { * @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. @@ -3607,6 +3627,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" | "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 @@ -4034,6 +4059,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" | "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. @@ -4243,6 +4273,11 @@ 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" | "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. @@ -4599,6 +4634,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" | "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. @@ -5040,6 +5080,11 @@ export interface components { * @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. @@ -5471,6 +5516,11 @@ export interface components { * @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. @@ -5581,6 +5631,11 @@ export interface components { * @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. @@ -6013,6 +6068,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" | "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. @@ -6265,6 +6325,11 @@ export interface components { * @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. @@ -6320,6 +6385,8 @@ export interface operations { limit?: number | 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. */ @@ -6585,6 +6652,8 @@ export interface operations { limit?: number | 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. */ @@ -7318,6 +7387,8 @@ export interface operations { limit?: number | 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. */ @@ -8070,6 +8141,8 @@ export interface operations { limit?: number | 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. */ @@ -8613,6 +8686,8 @@ export interface operations { limit?: number | 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. */ @@ -9059,6 +9134,8 @@ export interface operations { limit?: number | 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. */ @@ -9641,6 +9718,8 @@ export interface operations { limit?: number | 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} */ @@ -10346,6 +10425,8 @@ export interface operations { limit?: number | 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. */ @@ -11089,6 +11170,8 @@ export interface operations { limit?: number | 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. */ @@ -11380,6 +11463,8 @@ export interface operations { limit?: number | 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. */ @@ -12491,6 +12576,8 @@ export interface operations { limit?: number | 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. */ @@ -12807,6 +12894,8 @@ export interface operations { limit?: number | 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. */ @@ -13089,6 +13178,8 @@ export interface operations { limit?: number | 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. */