pierrejeambrun opened a new pull request, #66008:
URL: https://github.com/apache/airflow/pull/66008
## Summary
Closes #51820.
Adds a `rendered_map_index` exact-match filter (list-of-strings,
`ANY_EQUAL`) to the public `listMapped` and `listTaskInstances` endpoints,
mirroring the existing `map_index` filter, and surfaces it in the **Task
Instances** filter bar in the UI. Users can now find a dynamically mapped task
by the named mapping that's already shown in the column — no more "search by
what you see" gap.
## Why a hybrid expression on the model
The `TaskInstance.rendered_map_index` hybrid_property's Python accessor has
a fallback:
```python
if self._rendered_map_index is not None:
return self._rendered_map_index
if self.map_index >= 0:
return str(self.map_index)
return None
```
So a mapped task with no explicit `map_index_template` gets
`"rendered_map_index": "1"` in the API response (from the column-NULL
fallback). The hybrid had no `.expression`, which meant SQL-level resolution
silently fell back to the bare `_rendered_map_index` column. Filtering on it
would then miss every row whose displayed value came from the fallback. This PR
adds the matching SQL-level expression:
```python
@rendered_map_index.expression
def rendered_map_index(cls):
return case(
(cls._rendered_map_index.isnot(None), cls._rendered_map_index),
(cls.map_index >= 0, cast(cls.map_index, String)),
else_=None,
)
```
— so SQL and Python now agree on what the value is. Side benefit: ordering
by `rendered_map_index` is now also consistent with the displayed value.
## Indexes
The `rendered_map_index` column is **not indexed by default**, and this PR
doesn't add one. The typical UI path filters within a specific `(dag_id,
run_id, task_id)`, where the existing `task_instance_composite_key` already
narrows the scan to that task's mapped TIs before the CASE/COALESCE predicate
is evaluated — so no extra index is needed. Deployments doing unscoped
cross-DAG filtering on very large `task_instance` tables can add their own
(`CREATE INDEX ti_rendered_map_index ON task_instance (rendered_map_index)`),
but that index can't help the `str(map_index)` fallback branch.
## Tests
- 6 parametrized cases on
`TestGetMappedTaskInstances::test_rendered_map_index_filter`: explicit named
values, multi-value OR'd, exact-match-only (no prefix), missing value, and
**the original customer scenario — filtering by `["7"]` matches a TI whose
`_rendered_map_index` is NULL but `map_index = 7`**.
- 2 cases on `TestGetTaskInstances` (single value + multi-value OR'd) for
the cross-DAG endpoint.
- New `test_rendered_map_index_order` exercising both branches of the CASE
in ORDER BY.
- `test_expand_mapped_task_instance_with_named_index[*-unset]` expectation
updated from `[None, None]` to `["0", "1"]` to reflect the now-consistent
fallback (the Python-level accessor already returned the fallback; only SQL was
inconsistent).
## Test plan
- [x] Unit tests pass (`pytest test_task_instances.py -k
rendered_map_index_filter`)
- [x] TypeScript + ESLint clean
- [x] Manually verified in breeze with the customer-reported curl:
`?rendered_map_index=1` now returns the row whose `_rendered_map_index` is NULL.
---
##### Was generative AI tooling used to co-author this PR?
- [X] Yes — Claude Code (Opus 4.7)
Generated-by: Claude Code (Opus 4.7) following [the
guidelines](https://github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst#gen-ai-assisted-contributions)
--
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]