This is an automated email from the ASF dual-hosted git repository.

kaxilnaik pushed a commit to branch v3-0-test
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/v3-0-test by this push:
     new dbb6c5df74b feat: add `stats` ui endpoint (#49985)
dbb6c5df74b is described below

commit dbb6c5df74b3e4cdb80b8a3dcf35c86677d525ef
Author: Guan Ming(Wesley) Chiu <[email protected]>
AuthorDate: Wed Apr 30 21:37:50 2025 +0800

    feat: add `stats` ui endpoint (#49985)
    
    * feat: add `stats` ui endpoint
    
    * fix: cross db sql
    
    (cherry picked from commit e216dd640a30873242fef5c59dce9951d629bf0f)
---
 .../core_api/datamodels/ui/dashboard.py            |   9 ++
 .../api_fastapi/core_api/openapi/_private_ui.yaml  |  38 +++++
 .../api_fastapi/core_api/routes/ui/dashboard.py    |  54 ++++++-
 .../src/airflow/ui/openapi-gen/queries/common.ts   |  10 ++
 .../ui/openapi-gen/queries/ensureQueryData.ts      |  11 ++
 .../src/airflow/ui/openapi-gen/queries/prefetch.ts |  11 ++
 .../src/airflow/ui/openapi-gen/queries/queries.ts  |  19 +++
 .../src/airflow/ui/openapi-gen/queries/suspense.ts |  19 +++
 .../airflow/ui/openapi-gen/requests/schemas.gen.ts |  25 +++
 .../ui/openapi-gen/requests/services.gen.ts        |  14 ++
 .../airflow/ui/openapi-gen/requests/types.gen.ts   |  22 +++
 .../core_api/routes/ui/test_dashboard.py           | 167 +++++++++++++++++++++
 12 files changed, 398 insertions(+), 1 deletion(-)

diff --git 
a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/dashboard.py 
b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/dashboard.py
index ad806858828..bf2afa9fcd3 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/dashboard.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/dashboard.py
@@ -61,3 +61,12 @@ class HistoricalMetricDataResponse(BaseModel):
     dag_run_types: DAGRunTypes
     dag_run_states: DAGRunStates
     task_instance_states: TaskInstanceStateCount
+
+
+class DashboardDagStatsResponse(BaseModel):
+    """Dashboard DAG Stats serializer for responses."""
+
+    active_dag_count: int
+    failed_dag_count: int
+    running_dag_count: int
+    queued_dag_count: int
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 f3237ddbe1f..5eead52583d 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
@@ -296,6 +296,22 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/HTTPValidationError'
+  /ui/dashboard/dag_stats:
+    get:
+      tags:
+      - Dashboard
+      summary: Dag Stats
+      description: Return basic DAG stats with counts of DAGs in various 
states.
+      operationId: dag_stats
+      responses:
+        '200':
+          description: Successful Response
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/DashboardDagStatsResponse'
+      security:
+      - OAuth2PasswordBearer: []
   /ui/structure/structure_data:
     get:
       tags:
@@ -1269,6 +1285,28 @@ components:
       - bundle_url
       title: DagVersionResponse
       description: Dag Version serializer for responses.
+    DashboardDagStatsResponse:
+      properties:
+        active_dag_count:
+          type: integer
+          title: Active Dag Count
+        failed_dag_count:
+          type: integer
+          title: Failed Dag Count
+        running_dag_count:
+          type: integer
+          title: Running Dag Count
+        queued_dag_count:
+          type: integer
+          title: Queued Dag Count
+      type: object
+      required:
+      - active_dag_count
+      - failed_dag_count
+      - running_dag_count
+      - queued_dag_count
+      title: DashboardDagStatsResponse
+      description: Dashboard DAG Stats serializer for responses.
     EdgeResponse:
       properties:
         source_id:
diff --git 
a/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/dashboard.py 
b/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/dashboard.py
index e7f6d42c9d4..1e8ff2e4f03 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/dashboard.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/dashboard.py
@@ -18,14 +18,19 @@ from __future__ import annotations
 
 from fastapi import Depends, status
 from sqlalchemy import func, select
+from sqlalchemy.sql.expression import case, false
 
 from airflow.api_fastapi.auth.managers.models.resource_details import 
DagAccessEntity
 from airflow.api_fastapi.common.db.common import SessionDep
 from airflow.api_fastapi.common.parameters import DateTimeQuery, 
OptionalDateTimeQuery
 from airflow.api_fastapi.common.router import AirflowRouter
-from airflow.api_fastapi.core_api.datamodels.ui.dashboard import 
HistoricalMetricDataResponse
+from airflow.api_fastapi.core_api.datamodels.ui.dashboard import (
+    DashboardDagStatsResponse,
+    HistoricalMetricDataResponse,
+)
 from airflow.api_fastapi.core_api.openapi.exceptions import 
create_openapi_http_exception_doc
 from airflow.api_fastapi.core_api.security import requires_access_dag
+from airflow.models.dag import DagModel
 from airflow.models.dagrun import DagRun, DagRunType
 from airflow.models.taskinstance import TaskInstance
 from airflow.utils import timezone
@@ -97,3 +102,50 @@ def historical_metrics(
     }
 
     return 
HistoricalMetricDataResponse.model_validate(historical_metrics_response)
+
+
+@dashboard_router.get(
+    "/dag_stats",
+    dependencies=[Depends(requires_access_dag(method="GET"))],
+)
+def dag_stats(
+    session: SessionDep,
+) -> DashboardDagStatsResponse:
+    """Return basic DAG stats with counts of DAGs in various states."""
+    latest_dates_subq = (
+        select(DagRun.dag_id, 
func.max(DagRun.logical_date).label("max_logical_date"))
+        .where(DagRun.logical_date.is_not(None))
+        .group_by(DagRun.dag_id)
+        .subquery()
+    )
+
+    latest_runs = (
+        select(
+            DagModel.dag_id,
+            DagModel.is_paused,
+            DagRun.state,
+        )
+        .join(DagModel, DagRun.dag_id == DagModel.dag_id)
+        .join(
+            latest_dates_subq,
+            (DagRun.dag_id == latest_dates_subq.c.dag_id)
+            & (DagRun.logical_date == latest_dates_subq.c.max_logical_date),
+        )
+        .cte()
+    )
+
+    combined_query = select(
+        func.coalesce(func.sum(case((latest_runs.c.is_paused == false(), 1))), 
0).label("active"),
+        func.coalesce(func.sum(case((latest_runs.c.state == 
DagRunState.FAILED, 1))), 0).label("failed"),
+        func.coalesce(func.sum(case((latest_runs.c.state == 
DagRunState.RUNNING, 1))), 0).label("running"),
+        func.coalesce(func.sum(case((latest_runs.c.state == 
DagRunState.QUEUED, 1))), 0).label("queued"),
+    ).select_from(latest_runs)
+
+    counts = session.execute(combined_query).first()
+
+    return DashboardDagStatsResponse(
+        active_dag_count=counts.active,
+        failed_dag_count=counts.failed,
+        running_dag_count=counts.running,
+        queued_dag_count=counts.queued,
+    )
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 378e696b229..a59e98c888b 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
@@ -1711,6 +1711,16 @@ export const UseDashboardServiceHistoricalMetricsKeyFn = 
(
   },
   queryKey?: Array<unknown>,
 ) => [useDashboardServiceHistoricalMetricsKey, ...(queryKey ?? [{ endDate, 
startDate }])];
+export type DashboardServiceDagStatsDefaultResponse = 
Awaited<ReturnType<typeof DashboardService.dagStats>>;
+export type DashboardServiceDagStatsQueryResult<
+  TData = DashboardServiceDagStatsDefaultResponse,
+  TError = unknown,
+> = UseQueryResult<TData, TError>;
+export const useDashboardServiceDagStatsKey = "DashboardServiceDagStats";
+export const UseDashboardServiceDagStatsKeyFn = (queryKey?: Array<unknown>) => 
[
+  useDashboardServiceDagStatsKey,
+  ...(queryKey ?? []),
+];
 export type StructureServiceStructureDataDefaultResponse = Awaited<
   ReturnType<typeof StructureService.structureData>
 >;
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 cd3a7cfa9ed..c97c8ac5c66 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
@@ -2381,6 +2381,17 @@ export const 
ensureUseDashboardServiceHistoricalMetricsData = (
     queryKey: Common.UseDashboardServiceHistoricalMetricsKeyFn({ endDate, 
startDate }),
     queryFn: () => DashboardService.historicalMetrics({ endDate, startDate }),
   });
+/**
+ * Dag Stats
+ * Return basic DAG stats with counts of DAGs in various states.
+ * @returns DashboardDagStatsResponse Successful Response
+ * @throws ApiError
+ */
+export const ensureUseDashboardServiceDagStatsData = (queryClient: 
QueryClient) =>
+  queryClient.ensureQueryData({
+    queryKey: Common.UseDashboardServiceDagStatsKeyFn(),
+    queryFn: () => DashboardService.dagStats(),
+  });
 /**
  * Structure Data
  * Get Structure Data.
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 d3f010ceb9b..c9f715befa4 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
@@ -2381,6 +2381,17 @@ export const 
prefetchUseDashboardServiceHistoricalMetrics = (
     queryKey: Common.UseDashboardServiceHistoricalMetricsKeyFn({ endDate, 
startDate }),
     queryFn: () => DashboardService.historicalMetrics({ endDate, startDate }),
   });
+/**
+ * Dag Stats
+ * Return basic DAG stats with counts of DAGs in various states.
+ * @returns DashboardDagStatsResponse Successful Response
+ * @throws ApiError
+ */
+export const prefetchUseDashboardServiceDagStats = (queryClient: QueryClient) 
=>
+  queryClient.prefetchQuery({
+    queryKey: Common.UseDashboardServiceDagStatsKeyFn(),
+    queryFn: () => DashboardService.dagStats(),
+  });
 /**
  * Structure Data
  * Get Structure Data.
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 0245b93dcb2..ae5dabdcd0d 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts
@@ -2849,6 +2849,25 @@ export const useDashboardServiceHistoricalMetrics = <
     queryFn: () => DashboardService.historicalMetrics({ endDate, startDate }) 
as TData,
     ...options,
   });
+/**
+ * Dag Stats
+ * Return basic DAG stats with counts of DAGs in various states.
+ * @returns DashboardDagStatsResponse Successful Response
+ * @throws ApiError
+ */
+export const useDashboardServiceDagStats = <
+  TData = Common.DashboardServiceDagStatsDefaultResponse,
+  TError = unknown,
+  TQueryKey extends Array<unknown> = unknown[],
+>(
+  queryKey?: TQueryKey,
+  options?: Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">,
+) =>
+  useQuery<TData, TError>({
+    queryKey: Common.UseDashboardServiceDagStatsKeyFn(queryKey),
+    queryFn: () => DashboardService.dagStats() as TData,
+    ...options,
+  });
 /**
  * Structure Data
  * Get Structure Data.
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 ba4593ab925..00cd46c9d0e 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
@@ -2826,6 +2826,25 @@ export const 
useDashboardServiceHistoricalMetricsSuspense = <
     queryFn: () => DashboardService.historicalMetrics({ endDate, startDate }) 
as TData,
     ...options,
   });
+/**
+ * Dag Stats
+ * Return basic DAG stats with counts of DAGs in various states.
+ * @returns DashboardDagStatsResponse Successful Response
+ * @throws ApiError
+ */
+export const useDashboardServiceDagStatsSuspense = <
+  TData = Common.DashboardServiceDagStatsDefaultResponse,
+  TError = unknown,
+  TQueryKey extends Array<unknown> = unknown[],
+>(
+  queryKey?: TQueryKey,
+  options?: Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">,
+) =>
+  useSuspenseQuery<TData, TError>({
+    queryKey: Common.UseDashboardServiceDagStatsKeyFn(queryKey),
+    queryFn: () => DashboardService.dagStats() as TData,
+    ...options,
+  });
 /**
  * Structure Data
  * Get Structure Data.
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 481a5142df7..eb634bb0779 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
@@ -6315,6 +6315,31 @@ export const $DAGWithLatestDagRunsResponse = {
   description: "DAG with latest dag runs response serializer.",
 } as const;
 
+export const $DashboardDagStatsResponse = {
+  properties: {
+    active_dag_count: {
+      type: "integer",
+      title: "Active Dag Count",
+    },
+    failed_dag_count: {
+      type: "integer",
+      title: "Failed Dag Count",
+    },
+    running_dag_count: {
+      type: "integer",
+      title: "Running Dag Count",
+    },
+    queued_dag_count: {
+      type: "integer",
+      title: "Queued Dag Count",
+    },
+  },
+  type: "object",
+  required: ["active_dag_count", "failed_dag_count", "running_dag_count", 
"queued_dag_count"],
+  title: "DashboardDagStatsResponse",
+  description: "Dashboard DAG Stats serializer for responses.",
+} as const;
+
 export const $EdgeResponse = {
   properties: {
     source_id: {
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 385f8256e7f..bfd0077783a 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
@@ -211,6 +211,7 @@ import type {
   GetDependenciesResponse,
   HistoricalMetricsData,
   HistoricalMetricsResponse,
+  DagStatsResponse2,
   StructureDataData,
   StructureDataResponse2,
   GridDataData,
@@ -3499,6 +3500,19 @@ export class DashboardService {
       },
     });
   }
+
+  /**
+   * Dag Stats
+   * Return basic DAG stats with counts of DAGs in various states.
+   * @returns DashboardDagStatsResponse Successful Response
+   * @throws ApiError
+   */
+  public static dagStats(): CancelablePromise<DagStatsResponse2> {
+    return __request(OpenAPI, {
+      method: "GET",
+      url: "/ui/dashboard/dag_stats",
+    });
+  }
 }
 
 export class StructureService {
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 8b626f7ffed..a2370b2cc77 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
@@ -1564,6 +1564,16 @@ export type DAGWithLatestDagRunsResponse = {
   readonly file_token: string;
 };
 
+/**
+ * Dashboard DAG Stats serializer for responses.
+ */
+export type DashboardDagStatsResponse = {
+  active_dag_count: number;
+  failed_dag_count: number;
+  running_dag_count: number;
+  queued_dag_count: number;
+};
+
 /**
  * Edge serializer for responses.
  */
@@ -2633,6 +2643,8 @@ export type HistoricalMetricsData = {
 
 export type HistoricalMetricsResponse = HistoricalMetricDataResponse;
 
+export type DagStatsResponse2 = DashboardDagStatsResponse;
+
 export type StructureDataData = {
   dagId: string;
   externalDependencies?: boolean;
@@ -5446,6 +5458,16 @@ export type $OpenApiTs = {
       };
     };
   };
+  "/ui/dashboard/dag_stats": {
+    get: {
+      res: {
+        /**
+         * Successful Response
+         */
+        200: DashboardDagStatsResponse;
+      };
+    };
+  };
   "/ui/structure/structure_data": {
     get: {
       req: StructureDataData;
diff --git 
a/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_dashboard.py 
b/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_dashboard.py
index 2dd60058c2e..aab0dca59a7 100644
--- a/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_dashboard.py
+++ b/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_dashboard.py
@@ -99,6 +99,108 @@ def make_dag_runs(dag_maker, session, time_machine):
     time_machine.move_to("2023-07-02T00:00:00+00:00", tick=False)
 
 
[email protected]
+def make_failed_dag_runs(dag_maker, session):
+    with dag_maker(
+        dag_id="test_failed_dag",
+        serialized=True,
+        session=session,
+        start_date=pendulum.DateTime(2023, 2, 1, 0, 0, 0, tzinfo=pendulum.UTC),
+    ):
+        EmptyOperator(task_id="task_1") >> EmptyOperator(task_id="task_2")
+
+    date = dag_maker.dag.start_date
+
+    dag_maker.create_dagrun(
+        run_id="run_1",
+        state=DagRunState.FAILED,
+        run_type=DagRunType.SCHEDULED,
+        logical_date=date,
+        start_date=date,
+    )
+
+    dag_maker.sync_dagbag_to_db()
+
+
[email protected]
+def make_queued_dag_runs(dag_maker, session):
+    with dag_maker(
+        dag_id="test_queued_dag",
+        serialized=True,
+        session=session,
+        start_date=pendulum.DateTime(2023, 2, 1, 0, 0, 0, tzinfo=pendulum.UTC),
+    ):
+        EmptyOperator(task_id="task_1") >> EmptyOperator(task_id="task_2")
+
+    date = dag_maker.dag.start_date
+
+    dag_maker.create_dagrun(
+        run_id="run_1",
+        state=DagRunState.QUEUED,
+        run_type=DagRunType.SCHEDULED,
+        logical_date=date,
+        start_date=date,
+    )
+
+    dag_maker.sync_dagbag_to_db()
+
+
[email protected]
+def make_multiple_dags(dag_maker, session):
+    with dag_maker(
+        dag_id="test_running_dag",
+        serialized=True,
+        session=session,
+        start_date=pendulum.DateTime(2023, 2, 1, 0, 0, 0, tzinfo=pendulum.UTC),
+    ):
+        EmptyOperator(task_id="task_1") >> EmptyOperator(task_id="task_2")
+
+    date = dag_maker.dag.start_date
+    dag_maker.create_dagrun(
+        run_id="run_1",
+        state=DagRunState.RUNNING,
+        run_type=DagRunType.SCHEDULED,
+        logical_date=date,
+        start_date=date,
+    )
+
+    with dag_maker(
+        dag_id="test_failed_dag",
+        serialized=True,
+        session=session,
+        start_date=pendulum.DateTime(2023, 2, 1, 0, 0, 0, tzinfo=pendulum.UTC),
+    ):
+        EmptyOperator(task_id="task_1") >> EmptyOperator(task_id="task_2")
+
+    date = dag_maker.dag.start_date
+    dag_maker.create_dagrun(
+        run_id="run_1",
+        state=DagRunState.FAILED,
+        run_type=DagRunType.SCHEDULED,
+        logical_date=date,
+        start_date=date,
+    )
+
+    with dag_maker(
+        dag_id="test_queued_dag",
+        serialized=True,
+        session=session,
+        start_date=pendulum.DateTime(2023, 2, 1, 0, 0, 0, tzinfo=pendulum.UTC),
+    ):
+        EmptyOperator(task_id="task_1") >> EmptyOperator(task_id="task_2")
+
+    date = dag_maker.dag.start_date
+    dag_maker.create_dagrun(
+        run_id="run_1",
+        state=DagRunState.QUEUED,
+        run_type=DagRunType.SCHEDULED,
+        logical_date=date,
+        start_date=date,
+    )
+
+    dag_maker.sync_dagbag_to_db()
+
+
 class TestHistoricalMetricsDataEndpoint:
     @pytest.mark.parametrize(
         "params, expected",
@@ -188,3 +290,68 @@ class TestHistoricalMetricsDataEndpoint:
             "/dashboard/historical_metrics_data", params={"start_date": 
"2023-02-02T00:00"}
         )
         assert response.status_code == 403
+
+
+class TestDagStatsEndpoint:
+    @pytest.mark.usefixtures("freeze_time_for_dagruns", "make_multiple_dags")
+    def test_should_response_200_multiple_dags(self, test_client):
+        response = test_client.get("/dashboard/dag_stats")
+        assert response.status_code == 200
+        assert response.json() == {
+            "active_dag_count": 3,
+            "failed_dag_count": 1,
+            "running_dag_count": 1,
+            "queued_dag_count": 1,
+        }
+
+    @pytest.mark.usefixtures("freeze_time_for_dagruns", "make_dag_runs")
+    def test_should_response_200_single_dag(self, test_client):
+        response = test_client.get("/dashboard/dag_stats")
+        assert response.status_code == 200
+        assert response.json() == {
+            "active_dag_count": 1,
+            "failed_dag_count": 0,
+            "running_dag_count": 1,
+            "queued_dag_count": 0,
+        }
+
+    @pytest.mark.usefixtures("freeze_time_for_dagruns", "make_failed_dag_runs")
+    def test_should_response_200_failed_dag(self, test_client):
+        response = test_client.get("/dashboard/dag_stats")
+        assert response.status_code == 200
+        assert response.json() == {
+            "active_dag_count": 1,
+            "failed_dag_count": 1,
+            "running_dag_count": 0,
+            "queued_dag_count": 0,
+        }
+
+    @pytest.mark.usefixtures("freeze_time_for_dagruns", "make_queued_dag_runs")
+    def test_should_response_200_queued_dag(self, test_client):
+        response = test_client.get("/dashboard/dag_stats")
+        assert response.status_code == 200
+        assert response.json() == {
+            "active_dag_count": 1,
+            "failed_dag_count": 0,
+            "running_dag_count": 0,
+            "queued_dag_count": 1,
+        }
+
+    @pytest.mark.usefixtures("freeze_time_for_dagruns")
+    def test_should_response_200_no_dag_runs(self, test_client):
+        response = test_client.get("/dashboard/dag_stats")
+        assert response.status_code == 200
+        assert response.json() == {
+            "active_dag_count": 0,
+            "failed_dag_count": 0,
+            "running_dag_count": 0,
+            "queued_dag_count": 0,
+        }
+
+    def test_should_response_401(self, unauthenticated_test_client):
+        response = unauthenticated_test_client.get("/dashboard/dag_stats")
+        assert response.status_code == 401
+
+    def test_should_response_403(self, unauthorized_test_client):
+        response = unauthorized_test_client.get("/dashboard/dag_stats")
+        assert response.status_code == 403

Reply via email to