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

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


The following commit(s) were added to refs/heads/v3-3-test by this push:
     new 44a31fa84d6 [v3-3-test] Show larger dag run and task instance counts 
on the dashboard (#70892) (#71008)
44a31fa84d6 is described below

commit 44a31fa84d6cdd9002b59adc90597c6f52d6aeda
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Tue Aug 4 19:30:00 2026 +0530

    [v3-3-test] Show larger dag run and task instance counts on the dashboard 
(#70892) (#71008)
    
    The dashboard counted each state separately with a limit of 1000, so any 
busy state
    showed "1000+" instead of a real number. On a large install almost every 
state sat at
    the cap, leaving the panel with no usable figures at all.
    
    Count the window in a single scan instead. When it fits, every count is 
exact. When it
    does not, report what was read as a lower bound, rounded down, which stays 
far closer
    to the real volume than a fixed cap. Dag runs and task instances are judged 
separately,
    since a window often holds few enough dag runs to count exactly while 
holding far too
    many task instances.
    
    Counting a bounded number of rows is also cheaper than the old per-state 
limits, which
    scanned the whole window for any state that could not fill its own limit 
(postgres, 12M
    task instances):
    
      window   before      after
      15min       15 ms      4 ms
      24h      3,270 ms     45 ms
      7d      20,752 ms     46 ms
    
    MySQL and SQLite show the same pattern.
    
    The Dags list keeps its previous capped counts and now owns that constant.
    (cherry picked from commit b316afb44dc9686f5c39aab678a9a89cb21d53d2)
    
    Co-authored-by: Jed Cunningham 
<[email protected]>
---
 .../core_api/datamodels/ui/dashboard.py            |  4 +-
 .../api_fastapi/core_api/openapi/_private_ui.yaml  | 12 ++-
 .../airflow/api_fastapi/core_api/routes/ui/dags.py |  3 +
 .../api_fastapi/core_api/routes/ui/dashboard.py    | 88 +++++++++++++---------
 .../airflow/ui/openapi-gen/requests/schemas.gen.ts | 14 +++-
 .../airflow/ui/openapi-gen/requests/types.gen.ts   |  3 +-
 .../Dashboard/HistoricalMetrics/DagRunMetrics.tsx  | 19 +++--
 .../HistoricalMetrics/HistoricalMetrics.tsx        |  4 +-
 .../Dashboard/HistoricalMetrics/MetricSection.tsx  |  5 +-
 .../HistoricalMetrics/TaskInstanceMetrics.tsx      | 14 ++--
 .../core_api/routes/ui/test_dashboard.py           | 66 +++++++++++-----
 11 files changed, 149 insertions(+), 83 deletions(-)

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 46fc051daf3..1235b8d76aa 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
@@ -52,7 +52,9 @@ class HistoricalMetricDataResponse(BaseModel):
 
     dag_run_states: DAGRunStates
     task_instance_states: TaskInstanceStateCount
-    state_count_limit: int
+    # True when the counts above are floors on the real values rather than 
exact figures.
+    dag_run_counts_are_lower_bounds: bool = False
+    task_instance_counts_are_lower_bounds: bool = False
 
 
 class DashboardDagStatsResponse(BaseModel):
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 a8982a80446..c1112a747fb 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
@@ -2972,14 +2972,18 @@ components:
           $ref: '#/components/schemas/DAGRunStates'
         task_instance_states:
           $ref: '#/components/schemas/TaskInstanceStateCount'
-        state_count_limit:
-          type: integer
-          title: State Count Limit
+        dag_run_counts_are_lower_bounds:
+          type: boolean
+          title: Dag Run Counts Are Lower Bounds
+          default: false
+        task_instance_counts_are_lower_bounds:
+          type: boolean
+          title: Task Instance Counts Are Lower Bounds
+          default: false
       type: object
       required:
       - dag_run_states
       - task_instance_states
-      - state_count_limit
       title: HistoricalMetricDataResponse
       description: Historical Metric Data serializer for responses.
     JobResponse:
diff --git a/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/dags.py 
b/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/dags.py
index c9feb22b07e..c031d278fa6 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/dags.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/dags.py
@@ -75,6 +75,9 @@ from airflow.utils.state import TaskInstanceState
 
 dags_router = AirflowRouter(prefix="/dags", tags=["DAG"])
 
+# Per-dag run counts read at most this many rows per state; the UI shows "N+" 
at the cap.
+STATE_COUNT_CAP = 1000
+
 
 @dags_router.get(
     "",
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 0498082a4ed..62c51c79d2e 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
@@ -16,10 +16,11 @@
 # under the License.
 from __future__ import annotations
 
-from typing import cast
+from decimal import ROUND_FLOOR, Context
+from typing import TYPE_CHECKING, cast
 
 from fastapi import Depends, status
-from sqlalchemy import func, literal, select, union_all
+from sqlalchemy import func, select
 from sqlalchemy.sql.expression import case, false
 
 from airflow._shared.timezones import timezone
@@ -38,11 +39,45 @@ from airflow.models.dagrun import DagRun
 from airflow.models.taskinstance import TaskInstance
 from airflow.utils.state import DagRunState, TaskInstanceState
 
+if TYPE_CHECKING:
+    from sqlalchemy.orm import Session
+
 dashboard_router = AirflowRouter(tags=["Dashboard"], prefix="/dashboard")
 
-# Cap for state counts — avoids counting millions of rows.
-# The UI shows "N+" when the returned count equals this value.
-STATE_COUNT_CAP = 1000
+# Rows a single scan reads. Windows that fit are counted exactly; wider ones 
report a floor.
+EXACT_COUNT_LIMIT = 50_000
+
+
+_ROUNDING = Context(prec=2, rounding=ROUND_FLOOR)
+
+
+def _round_down(value: int) -> int:
+    """Round to two significant digits, never upwards: that would claim 
uncounted rows."""
+    return int(_ROUNDING.create_decimal(value))
+
+
+def _compute_state_counts(
+    model, filters, *, session: Session, join=None, null_label: str | None = 
None
+) -> tuple[dict[str, int], bool]:
+    """
+    Per-state counts for the window, and whether they are lower bounds rather 
than exact.
+
+    A scan that stopped early counted only some of the rows, but each count is 
still a floor
+    on the real value.
+    """
+    stmt = select(model.state.label("state")).select_from(model)
+    if join is not None:
+        stmt = stmt.join(join)
+    window = stmt.where(*filters).limit(EXACT_COUNT_LIMIT + 1).subquery()
+    rows = session.execute(select(window.c.state, 
func.count().label("cnt")).group_by(window.c.state)).all()
+    are_lower_bounds = sum(row.cnt for row in rows) > EXACT_COUNT_LIMIT
+    counts: dict[str, int] = {}
+    for row in rows:
+        label = row.state or null_label
+        if label is None:
+            continue
+        counts[label] = _round_down(row.cnt) if are_lower_bounds else row.cnt
+    return counts, are_lower_bounds
 
 
 @dashboard_router.get(
@@ -70,50 +105,31 @@ def historical_metrics(
         DagRun.dag_id.in_(permitted_dag_ids),
     ]
 
-    # Build one LIMIT-capped subquery per state, then UNION ALL them into a
-    # single query.  Every state gets the same treatment: at most 
STATE_COUNT_CAP
-    # rows are read from the index, so even states with millions of rows
-    # (typically "success") are counted in single-digit milliseconds.
-    # Each branch is wrapped in a subquery so LIMIT works on all backends
-    # (SQLite rejects LIMIT inside bare UNION ALL arms).
-    def _capped_state_counts(model, states, label_fn, join=None):
-        branches = []
-        for state in states:
-            stmt = 
select(literal(label_fn(state)).label("state")).select_from(model)
-            if join is not None:
-                stmt = stmt.join(join)
-            branch = (
-                stmt.where(*dag_run_filters)
-                .where(model.state == state if state else 
model.state.is_(None))
-                .limit(STATE_COUNT_CAP)
-                .subquery()
-            )
-            branches.append(select(branch.c.state))
-        capped = union_all(*branches).subquery()
-        return session.execute(
-            select(capped.c.state, 
func.count().label("cnt")).group_by(capped.c.state)
-        ).all()
-
-    dag_run_state_counts = _capped_state_counts(DagRun, list(DagRunState), 
lambda s: s.value)
-    ti_state_counts = _capped_state_counts(
+    # Judged separately: dag runs often fit when task instances do not.
+    dag_run_states, dag_runs_are_lower_bounds = _compute_state_counts(
+        DagRun, dag_run_filters, session=session
+    )
+    task_instance_states, task_instances_are_lower_bounds = 
_compute_state_counts(
         TaskInstance,
-        [None, *TaskInstanceState],
-        lambda s: s.value if s else "no_status",
+        dag_run_filters,
+        session=session,
         join=TaskInstance.dag_run,
+        null_label="no_status",
     )
 
     return HistoricalMetricDataResponse.model_validate(
         {
             "dag_run_states": {
                 **{dag_run_state.value: 0 for dag_run_state in DagRunState},
-                **{row.state: row.cnt for row in dag_run_state_counts},
+                **dag_run_states,
             },
             "task_instance_states": {
                 "no_status": 0,
                 **{ti_state.value: 0 for ti_state in TaskInstanceState},
-                **{row.state: row.cnt for row in ti_state_counts},
+                **task_instance_states,
             },
-            "state_count_limit": STATE_COUNT_CAP,
+            "dag_run_counts_are_lower_bounds": dag_runs_are_lower_bounds,
+            "task_instance_counts_are_lower_bounds": 
task_instances_are_lower_bounds,
         }
     )
 
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 e0a53b170f6..73fd78317ed 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
@@ -9671,13 +9671,19 @@ export const $HistoricalMetricDataResponse = {
         task_instance_states: {
             '$ref': '#/components/schemas/TaskInstanceStateCount'
         },
-        state_count_limit: {
-            type: 'integer',
-            title: 'State Count Limit'
+        dag_run_counts_are_lower_bounds: {
+            type: 'boolean',
+            title: 'Dag Run Counts Are Lower Bounds',
+            default: false
+        },
+        task_instance_counts_are_lower_bounds: {
+            type: 'boolean',
+            title: 'Task Instance Counts Are Lower Bounds',
+            default: false
         }
     },
     type: 'object',
-    required: ['dag_run_states', 'task_instance_states', 'state_count_limit'],
+    required: ['dag_run_states', 'task_instance_states'],
     title: 'HistoricalMetricDataResponse',
     description: 'Historical Metric Data serializer for responses.'
 } 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 0d5ba07e3b9..dd9dd4326c6 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
@@ -2457,7 +2457,8 @@ export type GridTISummaries = {
 export type HistoricalMetricDataResponse = {
     dag_run_states: DAGRunStates;
     task_instance_states: TaskInstanceStateCount;
-    state_count_limit: number;
+    dag_run_counts_are_lower_bounds?: boolean;
+    task_instance_counts_are_lower_bounds?: boolean;
 };
 
 /**
diff --git 
a/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/DagRunMetrics.tsx
 
b/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/DagRunMetrics.tsx
index 7d0bec32d76..6908564e504 100644
--- 
a/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/DagRunMetrics.tsx
+++ 
b/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/DagRunMetrics.tsx
@@ -24,21 +24,26 @@ import { FiBarChart } from "react-icons/fi";
 import { MetricSection } from "./MetricSection";
 
 type DagRunMetricsProps = {
+  readonly countsAreLowerBounds: boolean;
   readonly dagRunStates: DAGRunStates;
   readonly endDate?: string;
   readonly startDate: string;
-  readonly stateCountLimit: number;
 };
 
 const DAGRUN_STATES: Array<keyof DAGRunStates> = ["queued", "running", 
"success", "failed"];
 
-export const DagRunMetrics = ({ dagRunStates, endDate, startDate, 
stateCountLimit }: DagRunMetricsProps) => {
+export const DagRunMetrics = ({
+  countsAreLowerBounds,
+  dagRunStates,
+  endDate,
+  startDate,
+}: DagRunMetricsProps) => {
   const { t: translate } = useTranslation();
   const total = Object.values(dagRunStates).reduce((sum, count) => sum + 
count, 0);
-  // When any state hit the API's STATE_COUNT_CAP, the summed total is only a
-  // lower bound, so per-state percentages computed from it are wrong (#67336).
-  // Suppress percentages for the whole group in that case.
-  const isTotalTruncated = Object.values(dagRunStates).some((count) => count 
>= stateCountLimit);
+  // The total is only a lower bound when the counts are, so percentages would 
be wrong.
+  const isTotalTruncated = countsAreLowerBounds;
+  // "0+" would be meaningless.
+  const isLowerBound = (count: number) => countsAreLowerBounds && count > 0;
 
   return (
     <Box borderRadius={5} borderWidth={1} p={4}>
@@ -50,7 +55,7 @@ export const DagRunMetrics = ({ dagRunStates, endDate, 
startDate, stateCountLimi
       <Stack gap={4}>
         {DAGRUN_STATES.map((state) => (
           <MetricSection
-            capped={dagRunStates[state] >= stateCountLimit}
+            capped={isLowerBound(dagRunStates[state])}
             endDate={endDate}
             isTotalTruncated={isTotalTruncated}
             key={state}
diff --git 
a/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/HistoricalMetrics.tsx
 
b/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/HistoricalMetrics.tsx
index d193522beeb..24174bc9b03 100644
--- 
a/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/HistoricalMetrics.tsx
+++ 
b/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/HistoricalMetrics.tsx
@@ -74,13 +74,13 @@ export const HistoricalMetrics = ({ endDate, startDate }: 
HistoricalMetricsProps
             {!isLoading && data !== undefined && (
               <Box>
                 <DagRunMetrics
+                  countsAreLowerBounds={data.dag_run_counts_are_lower_bounds 
?? false}
                   dagRunStates={data.dag_run_states}
                   startDate={startDate}
-                  stateCountLimit={data.state_count_limit}
                 />
                 <TaskInstanceMetrics
+                  
countsAreLowerBounds={data.task_instance_counts_are_lower_bounds ?? false}
                   startDate={startDate}
-                  stateCountLimit={data.state_count_limit}
                   taskInstanceStates={data.task_instance_states}
                 />
               </Box>
diff --git 
a/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/MetricSection.tsx
 
b/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/MetricSection.tsx
index 20aae19444d..1cc585ffa3f 100644
--- 
a/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/MetricSection.tsx
+++ 
b/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/MetricSection.tsx
@@ -48,6 +48,7 @@ export const MetricSection = ({
   state,
   total,
 }: MetricSectionProps) => {
+  // A lower bound has no known proportion, so it deliberately fills the bar.
   const stateWidth = capped ? BAR_WIDTH : total === 0 ? 0 : (runs / total) * 
BAR_WIDTH;
   const remainingWidth = BAR_WIDTH - stateWidth;
   const hidePercent = isTotalTruncated;
@@ -57,7 +58,7 @@ export const MetricSection = ({
   const searchParams = new URLSearchParams(
     `?${stateParam}=${state}&${SearchParamsKeys.START_DATE_GTE}=${startDate}`,
   );
-  const { t: translate } = useTranslation();
+  const { i18n, t: translate } = useTranslation();
 
   if (endDate !== undefined) {
     searchParams.append(SearchParamsKeys.END_DATE, endDate);
@@ -70,7 +71,7 @@ export const MetricSection = ({
           <RouterLink to={`/${kind}?${searchParams.toString()}`}>
             <StateBadge fontSize="md" state={state === "no_status" ? null : 
state}>
               {}
-              {capped ? `${runs}+` : runs}
+              {`${runs.toLocaleString(i18n.language)}${capped ? "+" : ""}`}
             </StateBadge>
           </RouterLink>
           <Text>{translate(`states.${state}`)}</Text>
diff --git 
a/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/TaskInstanceMetrics.tsx
 
b/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/TaskInstanceMetrics.tsx
index 5cbf5811e17..fa839ed6014 100644
--- 
a/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/TaskInstanceMetrics.tsx
+++ 
b/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/TaskInstanceMetrics.tsx
@@ -24,9 +24,9 @@ import { MdOutlineTask } from "react-icons/md";
 import { MetricSection } from "./MetricSection";
 
 type TaskInstanceMetricsProps = {
+  readonly countsAreLowerBounds: boolean;
   readonly endDate?: string;
   readonly startDate: string;
-  readonly stateCountLimit: number;
   readonly taskInstanceStates: TaskInstanceStateCount;
 };
 
@@ -48,17 +48,17 @@ const TASK_STATES: Array<keyof TaskInstanceStateCount> = [
 ];
 
 export const TaskInstanceMetrics = ({
+  countsAreLowerBounds,
   endDate,
   startDate,
-  stateCountLimit,
   taskInstanceStates,
 }: TaskInstanceMetricsProps) => {
   const { t: translate } = useTranslation();
   const total = Object.values(taskInstanceStates).reduce((sum, count) => sum + 
count, 0);
-  // When any state hit the API's STATE_COUNT_CAP, the summed total is only a
-  // lower bound, so per-state percentages computed from it are wrong (#67336).
-  // Suppress percentages for the whole group in that case.
-  const isTotalTruncated = Object.values(taskInstanceStates).some((count) => 
count >= stateCountLimit);
+  // The total is only a lower bound when the counts are, so percentages would 
be wrong.
+  const isTotalTruncated = countsAreLowerBounds;
+  // "0+" would be meaningless.
+  const isLowerBound = (count: number) => countsAreLowerBounds && count > 0;
 
   return (
     <Box borderRadius={5} borderWidth={1} mt={2} p={4}>
@@ -73,7 +73,7 @@ export const TaskInstanceMetrics = ({
         ).map((state) =>
           taskInstanceStates[state] > 0 ? (
             <MetricSection
-              capped={taskInstanceStates[state] >= stateCountLimit}
+              capped={isLowerBound(taskInstanceStates[state])}
               endDate={endDate}
               isTotalTruncated={isTotalTruncated}
               key={state}
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 50869e0ee9f..6ed4eada4b3 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
@@ -18,13 +18,16 @@
 from __future__ import annotations
 
 from datetime import timedelta
+from types import SimpleNamespace
 from unittest import mock
 
 import pendulum
 import pytest
 
+from airflow.api_fastapi.core_api.routes.ui import dashboard
 from airflow.models.dag import DagModel
 from airflow.models.dagbag import DBDagBag
+from airflow.models.dagrun import DagRun
 from airflow.providers.standard.operators.empty import EmptyOperator
 from airflow.utils.state import DagRunState, TaskInstanceState
 from airflow.utils.types import DagRunType
@@ -265,7 +268,8 @@ class TestHistoricalMetricsDataEndpoint:
                         "up_for_retry": 0,
                         "upstream_failed": 0,
                     },
-                    "state_count_limit": 1000,
+                    "dag_run_counts_are_lower_bounds": False,
+                    "task_instance_counts_are_lower_bounds": False,
                 },
             ),
             (
@@ -288,7 +292,8 @@ class TestHistoricalMetricsDataEndpoint:
                         "up_for_retry": 0,
                         "upstream_failed": 0,
                     },
-                    "state_count_limit": 1000,
+                    "dag_run_counts_are_lower_bounds": False,
+                    "task_instance_counts_are_lower_bounds": False,
                 },
             ),
             (
@@ -311,7 +316,8 @@ class TestHistoricalMetricsDataEndpoint:
                         "up_for_retry": 0,
                         "upstream_failed": 0,
                     },
-                    "state_count_limit": 1000,
+                    "dag_run_counts_are_lower_bounds": False,
+                    "task_instance_counts_are_lower_bounds": False,
                 },
             ),
         ],
@@ -323,29 +329,30 @@ class TestHistoricalMetricsDataEndpoint:
         assert response.status_code == 200
         assert response.json() == expected
 
+    @pytest.mark.parametrize(
+        ("exact_limit", "dag_runs_bounded", "task_instances_bounded"),
+        [
+            pytest.param(8, False, False, id="both-counted-exactly"),
+            pytest.param(4, False, True, 
id="dag-runs-exact-task-instances-bounded"),
+            pytest.param(3, True, True, id="neither-counted-exactly"),
+        ],
+    )
     @pytest.mark.usefixtures("freeze_time_for_dagruns", "make_dag_runs")
-    def test_state_counts_are_capped(self, test_client):
-        """State counts are capped at STATE_COUNT_CAP; fixture creates 4 dag 
runs and 8 TIs."""
-        with 
mock.patch("airflow.api_fastapi.core_api.routes.ui.dashboard.STATE_COUNT_CAP", 
1):
+    def test_exact_limit_applies_per_group(
+        self, test_client, exact_limit, dag_runs_bounded, 
task_instances_bounded
+    ):
+        """The fixture's 4 dag runs and 8 task instances cross 
EXACT_COUNT_LIMIT independently."""
+        with mock.patch.object(dashboard, "EXACT_COUNT_LIMIT", exact_limit):
             response = test_client.get(
                 "/dashboard/historical_metrics_data",
                 params={"start_date": "2023-01-01T00:00", "end_date": 
"2023-08-02T00:00"},
             )
         assert response.status_code == 200
         data = response.json()
-
-        assert data["state_count_limit"] == 1
-
-        dr_states = data["dag_run_states"]
-        assert dr_states["success"] == 1
-        assert dr_states["failed"] == 1
-        assert dr_states["running"] == 1
-        assert dr_states["queued"] == 1
-
-        ti_states = data["task_instance_states"]
-        assert ti_states["success"] == 1
-        assert ti_states["failed"] == 1
-        assert ti_states["no_status"] == 1
+        assert data["dag_run_counts_are_lower_bounds"] is dag_runs_bounded
+        assert data["task_instance_counts_are_lower_bounds"] is 
task_instances_bounded
+        if not dag_runs_bounded:
+            assert data["dag_run_states"] == {"failed": 1, "queued": 1, 
"running": 1, "success": 1}
 
     def test_should_response_401(self, unauthenticated_test_client):
         response = unauthenticated_test_client.get(
@@ -428,3 +435,24 @@ class TestDagStatsEndpoint:
     def test_should_response_403(self, unauthorized_test_client):
         response = unauthorized_test_client.get("/dashboard/dag_stats")
         assert response.status_code == 403
+
+
[email protected](
+    ("limit", "expected_counts", "expected_bounded"),
+    [
+        pytest.param(1_000, {"success": 4_200, "failed": 130}, True, 
id="bounded-counts-rounded-down"),
+        pytest.param(50_000, {"success": 4_250, "failed": 137}, False, 
id="exact-counts-left-alone"),
+    ],
+)
+def test_compute_state_counts_rounds_only_lower_bounds(limit, expected_counts, 
expected_bounded):
+    """Rounding applies to lower bounds and nothing else."""
+    session = mock.MagicMock()
+    session.execute.return_value.all.return_value = [
+        SimpleNamespace(state="success", cnt=4_250),
+        SimpleNamespace(state="failed", cnt=137),
+    ]
+    with mock.patch.object(dashboard, "EXACT_COUNT_LIMIT", limit):
+        counts, are_lower_bounds = dashboard._compute_state_counts(DagRun, [], 
session=session)
+
+    assert are_lower_bounds is expected_bounded
+    assert counts == expected_counts

Reply via email to