nathadfield opened a new issue, #67541:
URL: https://github.com/apache/airflow/issues/67541

   ### Under which category would you file this issue?
   
   Airflow Core
   
   ### Apache Airflow version
   
   main (development)
   
   ### What happened and how to reproduce it?
   
   **Issue Description**
   
   The grid API endpoint serializes Python `None` task-instance states as the 
JSON dict key `"None"` (Pydantic conversion of `dict[TaskInstanceState | None, 
int]` in `LightGridTaskInstanceSummary.child_states`). Three UI surfaces 
introduced by #61854 iterate over `child_states` directly and render the raw 
`"None"` key, producing tokenless / untranslated output:
   
   1. 
`airflow-core/src/airflow/ui/src/components/Graph/SegmentedStateBar.tsx:44` — 
`<Box bg={`${state}.solid`}>` per entry; `"None.solid"` does not resolve to a 
Chakra theme token, so the no-status slice renders without a colour.
   2. 
`airflow-core/src/airflow/ui/src/components/TaskInstanceTooltip.tsx:146-149` — 
per-state breakdown row uses the same `bg={`${state}.solid`}` swatch 
(tokenless) and `translate(`common:states.${state}`)` (no translation for 
`"None"`), so the row shows a colourless swatch beside the literal key 
`common:states.None`.
   3. 
`airflow-core/src/airflow/ui/src/pages/GroupTaskInstance/Header.tsx:35-40` and 
`airflow-core/src/airflow/ui/src/pages/MappedTaskInstance/Header.tsx:37-43` — 
Header stats translate each `child_states` key directly with no normalization; 
same untranslated `states.None` label.
   
   **Minimal reproducer**
   
   ```python
   import time
   from datetime import datetime
   
   from airflow.sdk import DAG, task
   
   
   with DAG(
       dag_id="none_child_state_demo",
       description="Demonstrates frontend rendering bug for serialized 
no-status child_states key",
       start_date=datetime(2024, 1, 1),
       schedule=None,
       catchup=False,
   ) as dag:
   
       @task
       def make_args():
           # Sleep so the mapped consumer stays unexpanded long enough to
           # observe the bug in the UI.
           time.sleep(60)
           return ["a", "b", "c"]
   
       @task
       def consume(item):
           return item
   
       # Until `make_args` returns, `consume` has no rows and the grid API
       # reports child_states={"None": 1} for it.
       consume.expand(item=make_args())
   ```
   
   **Steps to reproduce**
   
   1. Save the DAG above as `none_child_state_demo.py` in your `dags` folder.
   2. Trigger the DAG manually.
   3. While `make_args` is still running (≤60s window), open the grid view and 
locate the `consume` mapped task.
   4. Observe:
      - **Segmented state bar (graph view, collapsed group containing 
`consume`):** the no-status slice renders without a colour token.
      - **Tooltip on the `consume` cell:** breakdown row shows `1 
common:states.None` with no colour swatch.
      - **Click into the mapped task details page:** Header stats row shows 
`Total states.None`.
   5. Confirm the API payload by querying 
`/grid/ti_summaries/none_child_state_demo?run_ids=<run_id>` during the same 
window — the `consume` entry will include `"child_states": {"None": 1}`. The 
wire format is also asserted in 
`airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_grid.py:839,848`.
   
   ### What you think should happen instead?
   
   Each render site should normalize the serialized `"None"` key to the 
existing `"none"` / `"no_status"` UI convention so the no-status slice shows 
the same neutral colour and the "No status" translated label that the rest of 
the UI uses for tasks without a state.
   
   Suggested approach: add a single helper (e.g. `normalizeStateKey(key: 
string): string`) to `airflow-core/src/airflow/ui/src/utils/stateUtils.ts` that 
maps `"None"` → `"none"`, and call it at the four render sites:
   
   - For `bg={`${state}.solid`}` → use `normalizeStateKey(state)` (mirrors the 
existing pattern at 
`airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/MetricSection.tsx:79`,
 which already does `state === "no_status" ? "none" : state`).
   - For `translate(`common:states.${state}`)` → map `"None"` to `"no_status"` 
so it picks up the existing localized label.
   
   **Acceptance criteria**
   
   - Group with `child_states={"None": 2, "success": 1}` shows a coloured and 
labelled no-status slice in the segmented bar.
   - Tooltip breakdown row for `"None": N` shows the localized "No status" 
label beside a visible colour swatch.
   - Group / Mapped Header stats row for no-status children uses the "No 
status" label, not `states.None`.
   - Existing labels (`success`, `running`, etc.) and colours unchanged.
   - Add unit coverage for the helper and at least one render site (suggested: 
the tooltip breakdown, since it's the cheapest surface to assert on).
   
   ### Operating System
   
   Not Applicable (frontend-only)
   
   ### Deployment
   
   Other
   
   ### Apache Airflow Provider(s)
   
   _None — not provider-related._
   
   ### Versions of Apache Airflow Providers
   
   Not Applicable
   
   ### Official Helm Chart version
   
   Not Applicable
   
   ### Kubernetes Version
   
   Not Applicable
   
   ### Helm Chart configuration
   
   Not Applicable
   
   ### Docker Image customizations
   
   Not Applicable
   
   ### Anything else?
   
   This is a deferred follow-up identified during review of work to wire up 
`getDisplayState` — a helper that fixes the dominant-state colouring of 
collapsed groups / mapped tasks across the badge / border / MiniMap / state 
filter, all of which correctly fall back to `null` when `"None"` is the 
dominant child key. That work deliberately scopes itself to the dominant-state 
surfaces; the three breakdown / segmented-bar / Header-stats sites listed above 
iterate `child_states` directly and remain on the pre-existing rendering path. 
The fix is small but cross-cuts four UI files and a new helper + tests — clean 
isolated scope for a separate PR.
   
   Original feature PR: #61854.
   
   ---
   Drafted-by: Claude Code (Opus 4.7); reviewed by @nathadfield before posting
   
   ### Are you willing to submit PR?
   
   - [X] Yes I am willing to submit a PR!
   
   ### Code of Conduct
   
   - [X] I agree to follow this project's [Code of 
Conduct](https://github.com/apache/airflow/blob/main/CODE_OF_CONDUCT.md)


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to