This is an automated email from the ASF dual-hosted git repository.
henry3260 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new 5227a911412 [AIP-94] Add filters and pagination to airflowctl jobs
list (#68616)
5227a911412 is described below
commit 5227a911412d925e6d892be68a5f6f0a4cd33e0a
Author: Henry Chen <[email protected]>
AuthorDate: Tue Jul 21 01:31:54 2026 +0800
[AIP-94] Add filters and pagination to airflowctl jobs list (#68616)
* Support legacy list-jobs filters in airflowctl jobs list
* Always default jobs list ordering to most recent start date
Previously the default order_by switched between the API default and
-start_date depending on whether --limit was given, making the output
ordering hard to predict. The legacy airflow dags list-jobs command
always ordered by start_date descending, so apply that default
unconditionally.
Co-Authored-By: Claude Fable 5 <[email protected]>
---------
Co-authored-by: Claude Fable 5 <[email protected]>
---
.../core_api/openapi/v2-rest-api-generated.yaml | 8 +++
.../api_fastapi/core_api/routes/public/job.py | 4 ++
.../src/airflow/ui/openapi-gen/queries/common.ts | 5 +-
.../ui/openapi-gen/queries/ensureQueryData.ts | 6 +-
.../src/airflow/ui/openapi-gen/queries/prefetch.ts | 6 +-
.../src/airflow/ui/openapi-gen/queries/queries.ts | 6 +-
.../src/airflow/ui/openapi-gen/queries/suspense.ts | 6 +-
.../ui/openapi-gen/requests/services.gen.ts | 2 +
.../airflow/ui/openapi-gen/requests/types.gen.ts | 1 +
.../api_fastapi/core_api/routes/public/test_job.py | 17 ++++++
airflow-ctl/src/airflowctl/api/operations.py | 20 ++++++-
.../tests/airflow_ctl/api/test_operations.py | 68 +++++++++++++++++++---
12 files changed, 131 insertions(+), 18 deletions(-)
diff --git
a/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml
b/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml
index d1a6ee83fd0..1b32b764372 100644
---
a/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml
+++
b/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml
@@ -5404,6 +5404,14 @@ paths:
- type: string
- type: 'null'
title: Job State
+ - name: dag_id
+ in: query
+ required: false
+ schema:
+ anyOf:
+ - type: string
+ - type: 'null'
+ title: Dag Id
- name: job_type
in: query
required: false
diff --git a/airflow-core/src/airflow/api_fastapi/core_api/routes/public/job.py
b/airflow-core/src/airflow/api_fastapi/core_api/routes/public/job.py
index b56c78f2de6..3e7e16bb69a 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/routes/public/job.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/routes/public/job.py
@@ -86,6 +86,9 @@ def get_jobs(
state: Annotated[
FilterParam[str | None], Depends(filter_param_factory(Job.state, str |
None, filter_name="job_state"))
],
+ dag_id: Annotated[
+ FilterParam[str | None], Depends(filter_param_factory(Job.dag_id, str
| None, filter_name="dag_id"))
+ ],
job_type: Annotated[
FilterParam[str | None],
Depends(filter_param_factory(Job.job_type, str | None,
filter_name="job_type")),
@@ -109,6 +112,7 @@ def get_jobs(
start_date_range,
end_date_range,
state,
+ dag_id,
job_type,
hostname,
executor_class,
diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
b/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
index fa4853f6050..286a724232e 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
@@ -703,7 +703,8 @@ export const UseImportErrorServiceGetImportErrorsKeyFn = ({
bundleName, filename
export type JobServiceGetJobsDefaultResponse = Awaited<ReturnType<typeof
JobService.getJobs>>;
export type JobServiceGetJobsQueryResult<TData =
JobServiceGetJobsDefaultResponse, TError = unknown> = UseQueryResult<TData,
TError>;
export const useJobServiceGetJobsKey = "JobServiceGetJobs";
-export const UseJobServiceGetJobsKeyFn = ({ endDateGt, endDateGte, endDateLt,
endDateLte, executorClass, hostname, isAlive, jobState, jobType, limit, offset,
orderBy, startDateGt, startDateGte, startDateLt, startDateLte }: {
+export const UseJobServiceGetJobsKeyFn = ({ dagId, endDateGt, endDateGte,
endDateLt, endDateLte, executorClass, hostname, isAlive, jobState, jobType,
limit, offset, orderBy, startDateGt, startDateGte, startDateLt, startDateLte }:
{
+ dagId?: string;
endDateGt?: string;
endDateGte?: string;
endDateLt?: string;
@@ -720,7 +721,7 @@ export const UseJobServiceGetJobsKeyFn = ({ endDateGt,
endDateGte, endDateLt, en
startDateGte?: string;
startDateLt?: string;
startDateLte?: string;
-} = {}, queryKey?: Array<unknown>) => [useJobServiceGetJobsKey, ...(queryKey
?? [{ endDateGt, endDateGte, endDateLt, endDateLte, executorClass, hostname,
isAlive, jobState, jobType, limit, offset, orderBy, startDateGt, startDateGte,
startDateLt, startDateLte }])];
+} = {}, queryKey?: Array<unknown>) => [useJobServiceGetJobsKey, ...(queryKey
?? [{ dagId, endDateGt, endDateGte, endDateLt, endDateLte, executorClass,
hostname, isAlive, jobState, jobType, limit, offset, orderBy, startDateGt,
startDateGte, startDateLt, startDateLte }])];
export type PluginServiceGetPluginsDefaultResponse = Awaited<ReturnType<typeof
PluginService.getPlugins>>;
export type PluginServiceGetPluginsQueryResult<TData =
PluginServiceGetPluginsDefaultResponse, TError = unknown> =
UseQueryResult<TData, TError>;
export const usePluginServiceGetPluginsKey = "PluginServiceGetPlugins";
diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
b/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
index f83894f1bef..5984e9b22f6 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
@@ -1441,13 +1441,15 @@ export const
ensureUseImportErrorServiceGetImportErrorsData = (queryClient: Quer
* @param data.offset
* @param data.orderBy Attributes to order by, multi criteria sort is
supported. Prefix with `-` for descending order. Supported attributes: `id,
dag_id, state, job_type, start_date, end_date, latest_heartbeat,
executor_class, hostname, unixname`
* @param data.jobState
+* @param data.dagId
* @param data.jobType
* @param data.hostname
* @param data.executorClass
* @returns JobCollectionResponse Successful Response
* @throws ApiError
*/
-export const ensureUseJobServiceGetJobsData = (queryClient: QueryClient, {
endDateGt, endDateGte, endDateLt, endDateLte, executorClass, hostname, isAlive,
jobState, jobType, limit, offset, orderBy, startDateGt, startDateGte,
startDateLt, startDateLte }: {
+export const ensureUseJobServiceGetJobsData = (queryClient: QueryClient, {
dagId, endDateGt, endDateGte, endDateLt, endDateLte, executorClass, hostname,
isAlive, jobState, jobType, limit, offset, orderBy, startDateGt, startDateGte,
startDateLt, startDateLte }: {
+ dagId?: string;
endDateGt?: string;
endDateGte?: string;
endDateLt?: string;
@@ -1464,7 +1466,7 @@ export const ensureUseJobServiceGetJobsData =
(queryClient: QueryClient, { endDa
startDateGte?: string;
startDateLt?: string;
startDateLte?: string;
-} = {}) => queryClient.ensureQueryData({ queryKey:
Common.UseJobServiceGetJobsKeyFn({ endDateGt, endDateGte, endDateLt,
endDateLte, executorClass, hostname, isAlive, jobState, jobType, limit, offset,
orderBy, startDateGt, startDateGte, startDateLt, startDateLte }), queryFn: ()
=> JobService.getJobs({ endDateGt, endDateGte, endDateLt, endDateLte,
executorClass, hostname, isAlive, jobState, jobType, limit, offset, orderBy,
startDateGt, startDateGte, startDateLt, startDateLte }) });
+} = {}) => queryClient.ensureQueryData({ queryKey:
Common.UseJobServiceGetJobsKeyFn({ dagId, endDateGt, endDateGte, endDateLt,
endDateLte, executorClass, hostname, isAlive, jobState, jobType, limit, offset,
orderBy, startDateGt, startDateGte, startDateLt, startDateLte }), queryFn: ()
=> JobService.getJobs({ dagId, endDateGt, endDateGte, endDateLt, endDateLte,
executorClass, hostname, isAlive, jobState, jobType, limit, offset, orderBy,
startDateGt, startDateGte, startDateLt, startDateLte }) });
/**
* Get Plugins
* @param data The data for the request.
diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
b/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
index 1b4b2d46aac..cca9470844f 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
@@ -1441,13 +1441,15 @@ export const
prefetchUseImportErrorServiceGetImportErrors = (queryClient: QueryC
* @param data.offset
* @param data.orderBy Attributes to order by, multi criteria sort is
supported. Prefix with `-` for descending order. Supported attributes: `id,
dag_id, state, job_type, start_date, end_date, latest_heartbeat,
executor_class, hostname, unixname`
* @param data.jobState
+* @param data.dagId
* @param data.jobType
* @param data.hostname
* @param data.executorClass
* @returns JobCollectionResponse Successful Response
* @throws ApiError
*/
-export const prefetchUseJobServiceGetJobs = (queryClient: QueryClient, {
endDateGt, endDateGte, endDateLt, endDateLte, executorClass, hostname, isAlive,
jobState, jobType, limit, offset, orderBy, startDateGt, startDateGte,
startDateLt, startDateLte }: {
+export const prefetchUseJobServiceGetJobs = (queryClient: QueryClient, {
dagId, endDateGt, endDateGte, endDateLt, endDateLte, executorClass, hostname,
isAlive, jobState, jobType, limit, offset, orderBy, startDateGt, startDateGte,
startDateLt, startDateLte }: {
+ dagId?: string;
endDateGt?: string;
endDateGte?: string;
endDateLt?: string;
@@ -1464,7 +1466,7 @@ export const prefetchUseJobServiceGetJobs = (queryClient:
QueryClient, { endDate
startDateGte?: string;
startDateLt?: string;
startDateLte?: string;
-} = {}) => queryClient.prefetchQuery({ queryKey:
Common.UseJobServiceGetJobsKeyFn({ endDateGt, endDateGte, endDateLt,
endDateLte, executorClass, hostname, isAlive, jobState, jobType, limit, offset,
orderBy, startDateGt, startDateGte, startDateLt, startDateLte }), queryFn: ()
=> JobService.getJobs({ endDateGt, endDateGte, endDateLt, endDateLte,
executorClass, hostname, isAlive, jobState, jobType, limit, offset, orderBy,
startDateGt, startDateGte, startDateLt, startDateLte }) });
+} = {}) => queryClient.prefetchQuery({ queryKey:
Common.UseJobServiceGetJobsKeyFn({ dagId, endDateGt, endDateGte, endDateLt,
endDateLte, executorClass, hostname, isAlive, jobState, jobType, limit, offset,
orderBy, startDateGt, startDateGte, startDateLt, startDateLte }), queryFn: ()
=> JobService.getJobs({ dagId, endDateGt, endDateGte, endDateLt, endDateLte,
executorClass, hostname, isAlive, jobState, jobType, limit, offset, orderBy,
startDateGt, startDateGte, startDateLt, startDateLte }) });
/**
* Get Plugins
* @param data The data for the request.
diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts
b/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts
index 3d334e51a96..cc0d92736ca 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts
@@ -1441,13 +1441,15 @@ export const useImportErrorServiceGetImportErrors =
<TData = Common.ImportErrorS
* @param data.offset
* @param data.orderBy Attributes to order by, multi criteria sort is
supported. Prefix with `-` for descending order. Supported attributes: `id,
dag_id, state, job_type, start_date, end_date, latest_heartbeat,
executor_class, hostname, unixname`
* @param data.jobState
+* @param data.dagId
* @param data.jobType
* @param data.hostname
* @param data.executorClass
* @returns JobCollectionResponse Successful Response
* @throws ApiError
*/
-export const useJobServiceGetJobs = <TData =
Common.JobServiceGetJobsDefaultResponse, TError = unknown, TQueryKey extends
Array<unknown> = unknown[]>({ endDateGt, endDateGte, endDateLt, endDateLte,
executorClass, hostname, isAlive, jobState, jobType, limit, offset, orderBy,
startDateGt, startDateGte, startDateLt, startDateLte }: {
+export const useJobServiceGetJobs = <TData =
Common.JobServiceGetJobsDefaultResponse, TError = unknown, TQueryKey extends
Array<unknown> = unknown[]>({ dagId, endDateGt, endDateGte, endDateLt,
endDateLte, executorClass, hostname, isAlive, jobState, jobType, limit, offset,
orderBy, startDateGt, startDateGte, startDateLt, startDateLte }: {
+ dagId?: string;
endDateGt?: string;
endDateGte?: string;
endDateLt?: string;
@@ -1464,7 +1466,7 @@ export const useJobServiceGetJobs = <TData =
Common.JobServiceGetJobsDefaultResp
startDateGte?: string;
startDateLt?: string;
startDateLte?: string;
-} = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>,
"queryKey" | "queryFn">) => useQuery<TData, TError>({ queryKey:
Common.UseJobServiceGetJobsKeyFn({ endDateGt, endDateGte, endDateLt,
endDateLte, executorClass, hostname, isAlive, jobState, jobType, limit, offset,
orderBy, startDateGt, startDateGte, startDateLt, startDateLte }, queryKey),
queryFn: () => JobService.getJobs({ endDateGt, endDateGte, endDateLt,
endDateLte, executorClass, hostname, isAlive, jobState, [...]
+} = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>,
"queryKey" | "queryFn">) => useQuery<TData, TError>({ queryKey:
Common.UseJobServiceGetJobsKeyFn({ dagId, endDateGt, endDateGte, endDateLt,
endDateLte, executorClass, hostname, isAlive, jobState, jobType, limit, offset,
orderBy, startDateGt, startDateGte, startDateLt, startDateLte }, queryKey),
queryFn: () => JobService.getJobs({ dagId, endDateGt, endDateGte, endDateLt,
endDateLte, executorClass, hostname, isAli [...]
/**
* Get Plugins
* @param data The data for the request.
diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
b/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
index a83ac5cb0ae..62139d375db 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
@@ -1441,13 +1441,15 @@ export const
useImportErrorServiceGetImportErrorsSuspense = <TData = Common.Impo
* @param data.offset
* @param data.orderBy Attributes to order by, multi criteria sort is
supported. Prefix with `-` for descending order. Supported attributes: `id,
dag_id, state, job_type, start_date, end_date, latest_heartbeat,
executor_class, hostname, unixname`
* @param data.jobState
+* @param data.dagId
* @param data.jobType
* @param data.hostname
* @param data.executorClass
* @returns JobCollectionResponse Successful Response
* @throws ApiError
*/
-export const useJobServiceGetJobsSuspense = <TData =
Common.JobServiceGetJobsDefaultResponse, TError = unknown, TQueryKey extends
Array<unknown> = unknown[]>({ endDateGt, endDateGte, endDateLt, endDateLte,
executorClass, hostname, isAlive, jobState, jobType, limit, offset, orderBy,
startDateGt, startDateGte, startDateLt, startDateLte }: {
+export const useJobServiceGetJobsSuspense = <TData =
Common.JobServiceGetJobsDefaultResponse, TError = unknown, TQueryKey extends
Array<unknown> = unknown[]>({ dagId, endDateGt, endDateGte, endDateLt,
endDateLte, executorClass, hostname, isAlive, jobState, jobType, limit, offset,
orderBy, startDateGt, startDateGte, startDateLt, startDateLte }: {
+ dagId?: string;
endDateGt?: string;
endDateGte?: string;
endDateLt?: string;
@@ -1464,7 +1466,7 @@ export const useJobServiceGetJobsSuspense = <TData =
Common.JobServiceGetJobsDef
startDateGte?: string;
startDateLt?: string;
startDateLte?: string;
-} = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>,
"queryKey" | "queryFn">) => useSuspenseQuery<TData, TError>({ queryKey:
Common.UseJobServiceGetJobsKeyFn({ endDateGt, endDateGte, endDateLt,
endDateLte, executorClass, hostname, isAlive, jobState, jobType, limit, offset,
orderBy, startDateGt, startDateGte, startDateLt, startDateLte }, queryKey),
queryFn: () => JobService.getJobs({ endDateGt, endDateGte, endDateLt,
endDateLte, executorClass, hostname, isAlive, jo [...]
+} = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>,
"queryKey" | "queryFn">) => useSuspenseQuery<TData, TError>({ queryKey:
Common.UseJobServiceGetJobsKeyFn({ dagId, endDateGt, endDateGte, endDateLt,
endDateLte, executorClass, hostname, isAlive, jobState, jobType, limit, offset,
orderBy, startDateGt, startDateGte, startDateLt, startDateLte }, queryKey),
queryFn: () => JobService.getJobs({ dagId, endDateGt, endDateGte, endDateLt,
endDateLte, executorClass, hostnam [...]
/**
* Get Plugins
* @param data The data for the request.
diff --git a/airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts
b/airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts
index 0d225056ac2..ca8d6339321 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts
@@ -3430,6 +3430,7 @@ export class JobService {
* @param data.offset
* @param data.orderBy Attributes to order by, multi criteria sort is
supported. Prefix with `-` for descending order. Supported attributes: `id,
dag_id, state, job_type, start_date, end_date, latest_heartbeat,
executor_class, hostname, unixname`
* @param data.jobState
+ * @param data.dagId
* @param data.jobType
* @param data.hostname
* @param data.executorClass
@@ -3454,6 +3455,7 @@ export class JobService {
offset: data.offset,
order_by: data.orderBy,
job_state: data.jobState,
+ dag_id: data.dagId,
job_type: data.jobType,
hostname: data.hostname,
executor_class: data.executorClass
diff --git a/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts
b/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts
index 3bbc72ebc95..235c75ec085 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts
@@ -4191,6 +4191,7 @@ export type GetImportErrorsData = {
export type GetImportErrorsResponse = ImportErrorCollectionResponse;
export type GetJobsData = {
+ dagId?: string | null;
endDateGt?: string | null;
endDateGte?: string | null;
endDateLt?: string | null;
diff --git
a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_job.py
b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_job.py
index a350585dc3a..8e9538d3f63 100644
--- a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_job.py
+++ b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_job.py
@@ -170,6 +170,23 @@ class TestGetJobs(TestJobEndpoint):
}
assert resp_job == expected_job
+ def test_get_jobs_filters_by_dag_id(self, test_client, session: Session):
+ clear_db_jobs()
+ session.add_all(
+ [
+ Job(dag_id="target_dag", state=JobState.RUNNING,
job_type="SchedulerJob"),
+ Job(dag_id="other_dag", state=JobState.SUCCESS,
job_type="SchedulerJob"),
+ ]
+ )
+ session.commit()
+
+ response = test_client.get("/jobs", params={"dag_id": "target_dag"})
+
+ assert response.status_code == 200
+ response_json = response.json()
+ assert response_json["total_entries"] == 1
+ assert response_json["jobs"][0]["dag_id"] == "target_dag"
+
def test_should_raises_401_unauthenticated(self,
unauthenticated_test_client):
response = unauthenticated_test_client.get("/jobs")
assert response.status_code == 401
diff --git a/airflow-ctl/src/airflowctl/api/operations.py
b/airflow-ctl/src/airflowctl/api/operations.py
index f58ad78adeb..c682608e85f 100644
--- a/airflow-ctl/src/airflowctl/api/operations.py
+++ b/airflow-ctl/src/airflowctl/api/operations.py
@@ -685,9 +685,27 @@ class JobsOperations(BaseOperations):
job_type: str | None = None,
hostname: str | None = None,
is_alive: bool | None = None,
+ dag_id: str | None = None,
+ state: str | None = None,
+ limit: int | None = None,
+ offset: int | None = None,
+ order_by: str | None = None,
) -> JobCollectionResponse | ServerResponseError:
"""List all jobs."""
- params = _build_query_params(job_type=job_type or None,
hostname=hostname or None, is_alive=is_alive)
+ params = _build_query_params(
+ job_type=job_type or None,
+ hostname=hostname or None,
+ is_alive=is_alive,
+ dag_id=dag_id or None,
+ job_state=state or None,
+ order_by=order_by or "-start_date",
+ limit=limit,
+ offset=offset,
+ )
+
+ if limit is not None or offset is not None:
+ self.response = self.client.get("jobs", params=params)
+ return
JobCollectionResponse.model_validate_json(self.response.content)
return super().execute_list(path="jobs",
data_model=JobCollectionResponse, params=params)
diff --git a/airflow-ctl/tests/airflow_ctl/api/test_operations.py
b/airflow-ctl/tests/airflow_ctl/api/test_operations.py
index dc5cdd396db..384e6ecae3d 100644
--- a/airflow-ctl/tests/airflow_ctl/api/test_operations.py
+++ b/airflow-ctl/tests/airflow_ctl/api/test_operations.py
@@ -1359,6 +1359,9 @@ class TestJobsOperations:
assert params["job_type"] == "job_type"
assert params["hostname"] == "hostname"
assert params["is_alive"] == "true"
+ assert params["dag_id"] == "dag_id"
+ assert params["job_state"] == "success"
+ assert params["order_by"] == "-start_date"
assert params["limit"] == "50"
return httpx.Response(200,
json=json.loads(self.job_collection_response.model_dump_json()))
@@ -1367,33 +1370,84 @@ class TestJobsOperations:
job_type="job_type",
hostname="hostname",
is_alive=True,
+ dag_id="dag_id",
+ state="success",
+ order_by="-start_date",
)
assert response == self.job_collection_response
+ def test_list_with_limit_returns_single_page(self):
+ requests = []
+
+ def handle_request(request: httpx.Request) -> httpx.Response:
+ requests.append(request)
+ assert request.url.path == "/api/v2/jobs"
+ params = dict(request.url.params)
+ assert params["limit"] == "1"
+ assert params["order_by"] == "-start_date"
+ return httpx.Response(
+ 200,
+ json={
+
**json.loads(self.job_collection_response.model_dump_json()),
+ "total_entries": 2,
+ },
+ )
+
+ client = make_api_client(transport=httpx.MockTransport(handle_request))
+ response = client.jobs.list(limit=1)
+
+ assert response.jobs == self.job_collection_response.jobs
+ assert response.total_entries == 2
+ assert len(requests) == 1
+
+ def test_list_with_limit_preserves_explicit_order_by(self):
+ def handle_request(request: httpx.Request) -> httpx.Response:
+ assert request.url.path == "/api/v2/jobs"
+ params = dict(request.url.params)
+ assert params["limit"] == "1"
+ assert params["order_by"] == "id"
+ return httpx.Response(200,
json=json.loads(self.job_collection_response.model_dump_json()))
+
+ client = make_api_client(transport=httpx.MockTransport(handle_request))
+ response = client.jobs.list(limit=1, order_by="id")
+
+ assert response == self.job_collection_response
+
@pytest.mark.parametrize(
- ("job_type", "hostname", "is_alive", "expected_subset"),
+ ("job_type", "hostname", "is_alive", "dag_id", "state",
"expected_subset"),
[
- (None, None, None, {}),
- ("scheduler", None, None, {"job_type": "scheduler"}),
- (None, "host-a", None, {"hostname": "host-a"}),
- (None, None, False, {"is_alive": "false"}),
+ (None, None, None, None, None, {}),
+ ("scheduler", None, None, None, None, {"job_type": "scheduler"}),
+ (None, "host-a", None, None, None, {"hostname": "host-a"}),
+ (None, None, False, None, None, {"is_alive": "false"}),
+ (None, None, None, "dag_a", None, {"dag_id": "dag_a"}),
+ (None, None, None, None, "running", {"job_state": "running"}),
],
)
- def test_list_omits_empty_filters(self, job_type, hostname, is_alive,
expected_subset):
+ def test_list_omits_empty_filters(self, job_type, hostname, is_alive,
dag_id, state, expected_subset):
def handle_request(request: httpx.Request) -> httpx.Response:
assert request.url.path == "/api/v2/jobs"
params = dict(request.url.params)
assert params["limit"] == "50"
+ assert params["order_by"] == "-start_date"
for key, value in expected_subset.items():
assert params[key] == value
assert ("job_type" in params) is ("job_type" in expected_subset)
assert ("hostname" in params) is ("hostname" in expected_subset)
assert ("is_alive" in params) is ("is_alive" in expected_subset)
+ assert ("dag_id" in params) is ("dag_id" in expected_subset)
+ assert ("job_state" in params) is ("job_state" in expected_subset)
return httpx.Response(200,
json=json.loads(self.job_collection_response.model_dump_json()))
client = make_api_client(transport=httpx.MockTransport(handle_request))
- response = client.jobs.list(job_type=job_type, hostname=hostname,
is_alive=is_alive)
+ response = client.jobs.list(
+ job_type=job_type,
+ hostname=hostname,
+ is_alive=is_alive,
+ dag_id=dag_id,
+ state=state,
+ )
assert response == self.job_collection_response