This is an automated email from the ASF dual-hosted git repository.
kaxilnaik 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 63c12efbbf3 Add Jobs page to the Airflow UI (#61512)
63c12efbbf3 is described below
commit 63c12efbbf396b869169c23d5b297aa17c32c393
Author: Kaxil Naik <[email protected]>
AuthorDate: Fri Feb 6 16:24:53 2026 +0000
Add Jobs page to the Airflow UI (#61512)
Add a new Jobs table under Browse menu with sorting, pagination,
and filters for state, type, hostname, executor class, start date
range, and end date range. Fix the backend API to not hardcode a
running-state filter so all job states are queryable. Render job
states with StateBadge for visual consistency with other pages.
- Simplify redundant ternary in filterConfigs (both branches identical)
- Return undefined instead of "" for null state in cell renderer
- Add comment explaining JobState-to-TaskInstanceState cast
---
.../src/airflow/api_fastapi/common/types.py | 1 +
.../api_fastapi/core_api/openapi/_private_ui.yaml | 1 +
.../api_fastapi/core_api/routes/public/job.py | 9 +-
.../airflow/ui/openapi-gen/requests/schemas.gen.ts | 2 +-
.../airflow/ui/openapi-gen/requests/types.gen.ts | 2 +-
.../airflow/ui/public/i18n/locales/en/admin.json | 17 +++
.../airflow/ui/public/i18n/locales/en/common.json | 1 +
.../src/airflow/ui/src/constants/filterConfigs.tsx | 53 ++++++-
.../src/airflow/ui/src/constants/searchParams.ts | 10 ++
.../src/airflow/ui/src/constants/stateOptions.ts | 19 +++
.../airflow/ui/src/layouts/Nav/BrowseButton.tsx | 5 +
airflow-core/src/airflow/ui/src/pages/Jobs.tsx | 155 +++++++++++++++++++++
airflow-core/src/airflow/ui/src/router.tsx | 5 +
.../src/airflow/ui/src/utils/useFiltersHandler.ts | 6 +
.../api_fastapi/core_api/routes/public/test_job.py | 18 +--
15 files changed, 286 insertions(+), 18 deletions(-)
diff --git a/airflow-core/src/airflow/api_fastapi/common/types.py
b/airflow-core/src/airflow/api_fastapi/common/types.py
index c19dd632343..f31809f4a4f 100644
--- a/airflow-core/src/airflow/api_fastapi/common/types.py
+++ b/airflow-core/src/airflow/api_fastapi/common/types.py
@@ -99,6 +99,7 @@ class MenuItem(Enum):
CONNECTIONS = "Connections"
DAGS = "Dags"
DOCS = "Docs"
+ JOBS = "Jobs"
PLUGINS = "Plugins"
POOLS = "Pools"
PROVIDERS = "Providers"
diff --git
a/airflow-core/src/airflow/api_fastapi/core_api/openapi/_private_ui.yaml
b/airflow-core/src/airflow/api_fastapi/core_api/openapi/_private_ui.yaml
index 524ef91f2a1..53f7b5c2c81 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/openapi/_private_ui.yaml
+++ b/airflow-core/src/airflow/api_fastapi/core_api/openapi/_private_ui.yaml
@@ -2381,6 +2381,7 @@ components:
- Connections
- Dags
- Docs
+ - Jobs
- Plugins
- Pools
- Providers
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 fa6c3b0a71e..b56c78f2de6 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
@@ -41,7 +41,7 @@ from airflow.api_fastapi.core_api.datamodels.job import (
)
from airflow.api_fastapi.core_api.openapi.exceptions import
create_openapi_http_exception_doc
from airflow.api_fastapi.core_api.security import AccessView,
requires_access_view
-from airflow.jobs.job import Job, JobState
+from airflow.jobs.job import Job
job_router = AirflowRouter(tags=["Job"], prefix="/jobs")
@@ -101,12 +101,7 @@ def get_jobs(
is_alive: bool | None = None,
) -> JobCollectionResponse:
"""Get all jobs."""
- base_select = (
- select(Job)
- .where(Job.state == JobState.RUNNING)
- .order_by(Job.latest_heartbeat.desc())
- .options(joinedload(Job.dag_model))
- )
+ base_select =
select(Job).order_by(Job.latest_heartbeat.desc()).options(joinedload(Job.dag_model))
jobs_select, total_entries = paginated_select(
statement=base_select,
diff --git a/airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts
b/airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts
index c92419c5893..0bc652b64e6 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts
@@ -8142,7 +8142,7 @@ export const $LightGridTaskInstanceSummary = {
export const $MenuItem = {
type: 'string',
- enum: ['Required Actions', 'Assets', 'Audit Log', 'Config', 'Connections',
'Dags', 'Docs', 'Plugins', 'Pools', 'Providers', 'Variables', 'XComs'],
+ enum: ['Required Actions', 'Assets', 'Audit Log', 'Config', 'Connections',
'Dags', 'Docs', 'Jobs', 'Plugins', 'Pools', 'Providers', 'Variables', 'XComs'],
title: 'MenuItem',
description: 'Define all menu items defined in the menu.'
} as const;
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 6f5856a62e9..1383566b21a 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
@@ -2012,7 +2012,7 @@ export type LightGridTaskInstanceSummary = {
/**
* Define all menu items defined in the menu.
*/
-export type MenuItem = 'Required Actions' | 'Assets' | 'Audit Log' | 'Config'
| 'Connections' | 'Dags' | 'Docs' | 'Plugins' | 'Pools' | 'Providers' |
'Variables' | 'XComs';
+export type MenuItem = 'Required Actions' | 'Assets' | 'Audit Log' | 'Config'
| 'Connections' | 'Dags' | 'Docs' | 'Jobs' | 'Plugins' | 'Pools' | 'Providers'
| 'Variables' | 'XComs';
/**
* Menu Item Collection serializer for responses.
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/en/admin.json
b/airflow-core/src/airflow/ui/public/i18n/locales/en/admin.json
index e421b761cc0..1f0136b906d 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/en/admin.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/en/admin.json
@@ -81,6 +81,23 @@
"formActions": {
"save": "Save"
},
+ "jobs": {
+ "columns": {
+ "executorClass": "Executor Class",
+ "hostname": "Hostname",
+ "id": "ID",
+ "jobType": "Job Type",
+ "latestHeartbeat": "Latest Heartbeat",
+ "unixname": "Unix Name"
+ },
+ "filters": {
+ "allStates": "All States",
+ "allTypes": "All Types",
+ "dagProcessorJob": "DagProcessorJob",
+ "schedulerJob": "SchedulerJob",
+ "triggererJob": "TriggererJob"
+ }
+ },
"plugins": {
"columns": {
"source": "Source"
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/en/common.json
b/airflow-core/src/airflow/ui/public/i18n/locales/en/common.json
index 7821e6babf7..3a1b62ba7c8 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/en/common.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/en/common.json
@@ -22,6 +22,7 @@
"backfill_other": "Backfills",
"browse": {
"auditLog": "Audit Log",
+ "jobs": "Jobs",
"requiredActions": "Required Actions",
"xcoms": "XComs"
},
diff --git a/airflow-core/src/airflow/ui/src/constants/filterConfigs.tsx
b/airflow-core/src/airflow/ui/src/constants/filterConfigs.tsx
index a2308e2869f..ad8f1619d65 100644
--- a/airflow-core/src/airflow/ui/src/constants/filterConfigs.tsx
+++ b/airflow-core/src/airflow/ui/src/constants/filterConfigs.tsx
@@ -32,6 +32,7 @@ import {
MdPlayArrow,
MdCheckCircle,
MdBuild,
+ MdComputer,
} from "react-icons/md";
import { PiQueue } from "react-icons/pi";
@@ -41,7 +42,13 @@ import { TaskIcon } from "src/assets/TaskIcon";
import type { FilterConfig } from "src/components/FilterBar";
import { RunTypeIcon } from "src/components/RunTypeIcon";
import { StateBadge } from "src/components/StateBadge";
-import { dagRunStateOptions, dagRunTypeOptions, taskInstanceStateOptions }
from "src/constants/stateOptions";
+import {
+ dagRunStateOptions,
+ dagRunTypeOptions,
+ jobStateOptions,
+ jobTypeOptions,
+ taskInstanceStateOptions,
+} from "src/constants/stateOptions";
import { SearchParamsKeys } from "./searchParams";
@@ -120,6 +127,13 @@ export const useFilterConfigs = () => {
min: 0,
type: FilterTypes.NUMBER,
},
+ [SearchParamsKeys.END_DATE_RANGE]: {
+ endKey: SearchParamsKeys.END_DATE_LTE,
+ icon: <MdDateRange />,
+ label: translate("common:endDate"),
+ startKey: SearchParamsKeys.END_DATE_GTE,
+ type: FilterTypes.DATERANGE,
+ },
[SearchParamsKeys.EVENT_DATE_RANGE]: {
endKey: SearchParamsKeys.BEFORE,
icon: <MdDateRange />,
@@ -131,6 +145,36 @@ export const useFilterConfigs = () => {
label: translate("browse:auditLog.filters.eventType"),
type: FilterTypes.TEXT,
},
+ [SearchParamsKeys.EXECUTOR_CLASS]: {
+ hotkeyDisabled: true,
+ icon: <MdBuild />,
+ label: translate("admin:jobs.columns.executorClass"),
+ type: FilterTypes.TEXT,
+ },
+ [SearchParamsKeys.HOSTNAME]: {
+ hotkeyDisabled: true,
+ icon: <MdComputer />,
+ label: translate("admin:jobs.columns.hostname"),
+ type: FilterTypes.TEXT,
+ },
+ [SearchParamsKeys.JOB_STATE]: {
+ icon: <MdCheckCircle />,
+ label: translate("common:state"),
+ options: jobStateOptions.items.map((option) => ({
+ label: translate(option.label),
+ value: option.value === "all" ? "" : option.value,
+ })),
+ type: FilterTypes.SELECT,
+ },
+ [SearchParamsKeys.JOB_TYPE]: {
+ icon: <MdBuild />,
+ label: translate("admin:jobs.columns.jobType"),
+ options: jobTypeOptions.items.map((option) => ({
+ label: translate(option.label),
+ value: option.value === "all" ? "" : option.value,
+ })),
+ type: FilterTypes.SELECT,
+ },
[SearchParamsKeys.KEY_PATTERN]: {
icon: <MdSearch />,
label: translate("admin:columns.key"),
@@ -231,6 +275,13 @@ export const useFilterConfigs = () => {
})),
type: FilterTypes.SELECT,
},
+ [SearchParamsKeys.START_DATE_RANGE]: {
+ endKey: SearchParamsKeys.START_DATE_LTE,
+ icon: <MdDateRange />,
+ label: translate("common:startDate"),
+ startKey: SearchParamsKeys.START_DATE_GTE,
+ type: FilterTypes.DATERANGE,
+ },
[SearchParamsKeys.STATE]: {
icon: <MdCheckCircle />,
label: translate("common:state"),
diff --git a/airflow-core/src/airflow/ui/src/constants/searchParams.ts
b/airflow-core/src/airflow/ui/src/constants/searchParams.ts
index 91e2a0be00f..4791dd3f903 100644
--- a/airflow-core/src/airflow/ui/src/constants/searchParams.ts
+++ b/airflow-core/src/airflow/ui/src/constants/searchParams.ts
@@ -33,11 +33,18 @@ export enum SearchParamsKeys {
DURATION_GTE = "duration_gte",
DURATION_LTE = "duration_lte",
END_DATE = "end_date",
+ END_DATE_GTE = "end_date_gte",
+ END_DATE_LTE = "end_date_lte",
+ END_DATE_RANGE = "end_date_range",
EVENT_DATE_RANGE = "event_date_range",
EVENT_TYPE = "event_type",
EXCLUDED_EVENTS = "excluded_events",
+ EXECUTOR_CLASS = "executor_class",
FAVORITE = "favorite",
+ HOSTNAME = "hostname",
INCLUDED_EVENTS = "included_events",
+ JOB_STATE = "job_state",
+ JOB_TYPE = "job_type",
KEY_PATTERN = "key_pattern",
LAST_DAG_RUN_STATE = "last_dag_run_state",
LIMIT = "limit",
@@ -69,6 +76,9 @@ export enum SearchParamsKeys {
SORT = "sort",
SOURCE = "log_source",
START_DATE = "start_date",
+ START_DATE_GTE = "start_date_gte",
+ START_DATE_LTE = "start_date_lte",
+ START_DATE_RANGE = "start_date_range",
STATE = "state",
SUBJECT_SEARCH = "subject_search",
TAGS = "tags",
diff --git a/airflow-core/src/airflow/ui/src/constants/stateOptions.ts
b/airflow-core/src/airflow/ui/src/constants/stateOptions.ts
index eccb2be11be..b9df60a5208 100644
--- a/airflow-core/src/airflow/ui/src/constants/stateOptions.ts
+++ b/airflow-core/src/airflow/ui/src/constants/stateOptions.ts
@@ -52,6 +52,25 @@ export const dagRunStateOptions = createListCollection({
],
});
+export const jobStateOptions = createListCollection({
+ items: [
+ { label: "admin:jobs.filters.allStates", value: "all" },
+ { label: "common:states.running", value: "running" },
+ { label: "common:states.success", value: "success" },
+ { label: "common:states.restarting", value: "restarting" },
+ { label: "common:states.failed", value: "failed" },
+ ],
+});
+
+export const jobTypeOptions = createListCollection({
+ items: [
+ { label: "admin:jobs.filters.allTypes", value: "all" },
+ { label: "admin:jobs.filters.schedulerJob", value: "SchedulerJob" },
+ { label: "admin:jobs.filters.triggererJob", value: "TriggererJob" },
+ { label: "admin:jobs.filters.dagProcessorJob", value: "DagProcessorJob" },
+ ],
+});
+
export const dagRunTypeOptions = createListCollection({
items: [
{ label: "dags:filters.allRunTypes", value: "all" },
diff --git a/airflow-core/src/airflow/ui/src/layouts/Nav/BrowseButton.tsx
b/airflow-core/src/airflow/ui/src/layouts/Nav/BrowseButton.tsx
index 986fa973bbc..a9de356ff9d 100644
--- a/airflow-core/src/airflow/ui/src/layouts/Nav/BrowseButton.tsx
+++ b/airflow-core/src/airflow/ui/src/layouts/Nav/BrowseButton.tsx
@@ -33,6 +33,11 @@ const links = [
key: "auditLog",
title: "Audit Log",
},
+ {
+ href: "/jobs",
+ key: "jobs",
+ title: "Jobs",
+ },
{
href: "/xcoms",
key: "xcoms",
diff --git a/airflow-core/src/airflow/ui/src/pages/Jobs.tsx
b/airflow-core/src/airflow/ui/src/pages/Jobs.tsx
new file mode 100644
index 00000000000..f1cddffedb5
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/pages/Jobs.tsx
@@ -0,0 +1,155 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { Box, Heading, VStack } from "@chakra-ui/react";
+import type { ColumnDef } from "@tanstack/react-table";
+import type { TFunction } from "i18next";
+import { useTranslation } from "react-i18next";
+import { useSearchParams } from "react-router-dom";
+
+import { useJobServiceGetJobs } from "openapi/queries";
+import type { JobResponse, TaskInstanceState } from
"openapi/requests/types.gen";
+import { DataTable } from "src/components/DataTable";
+import { useTableURLState } from "src/components/DataTable/useTableUrlState";
+import { ErrorAlert } from "src/components/ErrorAlert";
+import { FilterBar } from "src/components/FilterBar";
+import { StateBadge } from "src/components/StateBadge";
+import Time from "src/components/Time";
+import { SearchParamsKeys } from "src/constants/searchParams";
+import { useFiltersHandler, type FilterableSearchParamsKeys } from "src/utils";
+
+const createColumns = (translate: TFunction): Array<ColumnDef<JobResponse>> =>
[
+ {
+ accessorKey: "id",
+ header: translate("jobs.columns.id"),
+ },
+ {
+ accessorKey: "job_type",
+ header: translate("jobs.columns.jobType"),
+ },
+ {
+ accessorKey: "state",
+ cell: ({
+ row: {
+ original: { state },
+ },
+ }) =>
+ // Job states (running, success, failed, restarting) are a subset of
TaskInstanceState
+ state === null ? undefined : (
+ <StateBadge state={state as
TaskInstanceState}>{translate(`common:states.${state}`)}</StateBadge>
+ ),
+ header: translate("common:state"),
+ },
+ {
+ accessorKey: "hostname",
+ header: translate("jobs.columns.hostname"),
+ },
+ {
+ accessorKey: "start_date",
+ cell: ({ row: { original } }) => <Time datetime={original.start_date} />,
+ header: translate("common:startDate"),
+ },
+ {
+ accessorKey: "end_date",
+ cell: ({ row: { original } }) => <Time datetime={original.end_date} />,
+ header: translate("common:endDate"),
+ },
+ {
+ accessorKey: "latest_heartbeat",
+ cell: ({ row: { original } }) => <Time
datetime={original.latest_heartbeat} />,
+ header: translate("jobs.columns.latestHeartbeat"),
+ },
+ {
+ accessorKey: "executor_class",
+ header: translate("jobs.columns.executorClass"),
+ },
+ {
+ accessorKey: "unixname",
+ header: translate("jobs.columns.unixname"),
+ },
+];
+
+const jobsFilterKeys: Array<FilterableSearchParamsKeys> = [
+ SearchParamsKeys.JOB_STATE,
+ SearchParamsKeys.JOB_TYPE,
+ SearchParamsKeys.HOSTNAME,
+ SearchParamsKeys.EXECUTOR_CLASS,
+ SearchParamsKeys.START_DATE_RANGE,
+ SearchParamsKeys.END_DATE_RANGE,
+];
+
+export const Jobs = () => {
+ const { t: translate } = useTranslation(["admin", "common"]);
+ const { setTableURLState, tableURLState } = useTableURLState();
+ const [searchParams] = useSearchParams();
+
+ const { filterConfigs, handleFiltersChange, initialValues } =
useFiltersHandler(jobsFilterKeys);
+
+ const columns = createColumns(translate);
+
+ const { pagination, sorting } = tableURLState;
+ const [sort] = sorting;
+ const orderBy = sort ? [`${sort.desc ? "-" : ""}${sort.id}`] : undefined;
+
+ const filteredJobState = searchParams.get(SearchParamsKeys.JOB_STATE);
+ const filteredJobType = searchParams.get(SearchParamsKeys.JOB_TYPE);
+ const filteredHostname = searchParams.get(SearchParamsKeys.HOSTNAME);
+ const filteredExecutorClass =
searchParams.get(SearchParamsKeys.EXECUTOR_CLASS);
+ const filteredStartDateGte =
searchParams.get(SearchParamsKeys.START_DATE_GTE);
+ const filteredStartDateLte =
searchParams.get(SearchParamsKeys.START_DATE_LTE);
+ const filteredEndDateGte = searchParams.get(SearchParamsKeys.END_DATE_GTE);
+ const filteredEndDateLte = searchParams.get(SearchParamsKeys.END_DATE_LTE);
+
+ const { data, error, isFetching, isLoading } = useJobServiceGetJobs({
+ endDateGte: filteredEndDateGte ?? undefined,
+ endDateLte: filteredEndDateLte ?? undefined,
+ executorClass: filteredExecutorClass ?? undefined,
+ hostname: filteredHostname ?? undefined,
+ jobState: filteredJobState ?? undefined,
+ jobType: filteredJobType ?? undefined,
+ limit: pagination.pageSize,
+ offset: pagination.pageIndex * pagination.pageSize,
+ orderBy,
+ startDateGte: filteredStartDateGte ?? undefined,
+ startDateLte: filteredStartDateLte ?? undefined,
+ });
+
+ return (
+ <Box p={2}>
+ <Heading>{translate("common:browse.jobs")}</Heading>
+ <VStack align="start" gap={4} paddingY="4px">
+ <FilterBar
+ configs={filterConfigs}
+ initialValues={initialValues}
+ onFiltersChange={handleFiltersChange}
+ />
+ </VStack>
+ <DataTable
+ columns={columns}
+ data={data?.jobs ?? []}
+ errorMessage={<ErrorAlert error={error} />}
+ initialState={tableURLState}
+ isFetching={isFetching}
+ isLoading={isLoading}
+ modelName="common:browse.jobs"
+ onStateChange={setTableURLState}
+ total={data?.total_entries}
+ />
+ </Box>
+ );
+};
diff --git a/airflow-core/src/airflow/ui/src/router.tsx
b/airflow-core/src/airflow/ui/src/router.tsx
index 5c3ddc76174..e7bcd4bdfbf 100644
--- a/airflow-core/src/airflow/ui/src/router.tsx
+++ b/airflow-core/src/airflow/ui/src/router.tsx
@@ -42,6 +42,7 @@ import { Events } from "src/pages/Events";
import { ExternalView } from "src/pages/ExternalView";
import { GroupTaskInstance } from "src/pages/GroupTaskInstance";
import { HITLTaskInstances } from "src/pages/HITLTaskInstances";
+import { Jobs } from "src/pages/Jobs";
import { MappedTaskInstance } from "src/pages/MappedTaskInstance";
import { Plugins } from "src/pages/Plugins";
import { Pools } from "src/pages/Pools";
@@ -156,6 +157,10 @@ export const routerConfig = [
element: <Connections />,
path: "connections",
},
+ {
+ element: <Jobs />,
+ path: "jobs",
+ },
pluginRoute,
{
children: [
diff --git a/airflow-core/src/airflow/ui/src/utils/useFiltersHandler.ts
b/airflow-core/src/airflow/ui/src/utils/useFiltersHandler.ts
index 24b703285ce..30a046eb156 100644
--- a/airflow-core/src/airflow/ui/src/utils/useFiltersHandler.ts
+++ b/airflow-core/src/airflow/ui/src/utils/useFiltersHandler.ts
@@ -66,8 +66,13 @@ export type FilterableSearchParamsKeys =
| SearchParamsKeys.DAG_VERSION
| SearchParamsKeys.DURATION_GTE
| SearchParamsKeys.DURATION_LTE
+ | SearchParamsKeys.END_DATE_RANGE
| SearchParamsKeys.EVENT_DATE_RANGE
| SearchParamsKeys.EVENT_TYPE
+ | SearchParamsKeys.EXECUTOR_CLASS
+ | SearchParamsKeys.HOSTNAME
+ | SearchParamsKeys.JOB_STATE
+ | SearchParamsKeys.JOB_TYPE
| SearchParamsKeys.KEY_PATTERN
| SearchParamsKeys.LOGICAL_DATE_RANGE
| SearchParamsKeys.MAP_INDEX
@@ -81,6 +86,7 @@ export type FilterableSearchParamsKeys =
| SearchParamsKeys.RUN_ID
| SearchParamsKeys.RUN_ID_PATTERN
| SearchParamsKeys.RUN_TYPE
+ | SearchParamsKeys.START_DATE_RANGE
| SearchParamsKeys.STATE
| SearchParamsKeys.SUBJECT_SEARCH
| SearchParamsKeys.TASK_ID
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 b312bd36920..c5874c815d8 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
@@ -134,7 +134,7 @@ class TestGetJobs(TestJobEndpoint):
(TESTCASE_ONE_SCHEDULER, {}, 200, 1),
(TESTCASE_ONE_SCHEDULER_WITH_HOSTNAME, {"hostname": "HOSTNAME"},
200, 1),
(TESTCASE_HA_SCHEDULERS, {"limit": 100}, 200, 3),
- (TESTCASE_IGNORE_NOT_RUNNING, {}, 200, 0),
+ (TESTCASE_IGNORE_NOT_RUNNING, {}, 200, 3),
(TESTCASE_MULTIPLE_SCHEDULERS_ON_ONE_HOST, {"limit": 100}, 200, 3),
],
)
@@ -151,19 +151,21 @@ class TestGetJobs(TestJobEndpoint):
response_json = response.json()
assert response_json["total_entries"] == expected_total_entries
- for idx, resp_job in enumerate(response_json["jobs"]):
+ for resp_job in response_json["jobs"]:
+ matched = [j for j in self.scheduler_jobs if j.id ==
resp_job["id"]]
+ assert len(matched) == 1
expected_job = {
- "id": self.scheduler_jobs[idx].id,
+ "id": matched[0].id,
"dag_display_name": None,
"dag_id": None,
- "state": "running",
+ "state": matched[0].state,
"job_type": "SchedulerJob",
- "start_date":
from_datetime_to_zulu(self.scheduler_jobs[idx].start_date),
+ "start_date": from_datetime_to_zulu(matched[0].start_date),
"end_date": None,
- "latest_heartbeat":
from_datetime_to_zulu(self.scheduler_jobs[idx].latest_heartbeat),
+ "latest_heartbeat":
from_datetime_to_zulu(matched[0].latest_heartbeat),
"executor_class": None,
- "hostname": self.scheduler_jobs[idx].hostname,
- "unixname": self.scheduler_jobs[idx].unixname,
+ "hostname": matched[0].hostname,
+ "unixname": matched[0].unixname,
}
assert resp_job == expected_job