ryanahamilton opened a new pull request, #71731:
URL: https://github.com/apache/airflow/pull/71731
Every list page in the UI arranged its own controls above the table, so
search
boxes, filter bars, sort selects and "add" buttons landed in a different
place —
and at a different size — on each one. The Dags list went further and had a
filtering implementation entirely of its own.
This makes both consistent.
## Dags list moves onto the shared `FilterBar`
`src/pages/DagsList/DagsFilters/` was a parallel implementation: eight
always-visible bespoke controls, its own `chakra-react-select` wrapper, its
own
URL read/write and pagination reset. It shared no code with `FilterBar`,
which
the other ten list pages use.
Adopting `FilterBar` first required teaching it three things it could not
express:
- **Repeated URL params.** `useFiltersHandler` only ever called `set()`, so
`tags`, `teams`, `owners` and `timetable_type` had no representation. This
also meant the existing `TEAMS` filter could hold a single value even
though
`DagRuns` and `TaskInstances` already read it with `getAll()` — a latent
bug
this fixes.
- **Booleans.** Previously faked as a select over `"true"`/`"false"` strings.
The new `boolean` type activates in one click. Its value is still the
*string*
`"true"`: `isValidFilterValue` treats any non-empty value as set, so a real
boolean would write `needs_review=false` into the URL and reintroduce the
tri-state ambiguity the type exists to remove.
- **Async option lists.** Tags and timetable types need search-as-you-type
over
a paginated endpoint, which a static `options` array cannot express. These
are
supplied via an optional `EditorComponent` on the config, so each editor
owns
its query and only runs it while its pill is mounted — an improvement on
the
old page, which fired both queries on every render whether used or not.
**Full recording of new FilterBar implementation:
https://r.hmlt.in/01a0112e-aaff-7774-8ec3-37b930b0f0c5**
Two defects fall out of the move:
- Pills snapshot their config when created, which for URL-seeded filters
happens
before the i18n namespaces resolve, so deep-linked pills rendered raw
translation keys as labels. This affected every `FilterBar` page.
- The `owners` param filtered the list with no control anywhere — reachable
only
by clicking an owner link, and impossible to clear without editing the
URL. It
now has a removable pill.
Where `hide_paused_dags_by_default` is set, the default is written into the
URL
so it shows as a pill rather than filtering invisibly. It is seeded once per
page load, so removing the pill is how you ask to see paused Dags.
### Default state - before:
<img width="1548" height="398" alt="file-f484ee7d48bc2f46fe02362c3c1bd5ad"
src="https://github.com/user-attachments/assets/96c42b4b-7fd5-4e5d-9e36-1b096905862d"
/>
### Default state - after:
<img width="1548" height="361" alt="file-6300bdf6d25fe278e94f5024d95e4cd5"
src="https://github.com/user-attachments/assets/e9a315f3-d344-405d-af31-fe9ae2fa7884"
/>
### Every filter loses its "all" option
Selecting "all" matches everything, which is the same as not filtering. Now
that
a filter left unset is dropped from the bar, its absence already says so, and
offering the choice as well gave two ways to spell one thing — one of which
still looked like an active filter.
It is gone from every select that had it: run state (last run, any run, and
the
shared state filter), paused state, run type, job state, job type, task
instance
state, deadline status, and the HITL response state.
| Before | After |
|--|--|
|<img width="100%" alt="Zight Recording 2026-08-17 at 03 06 37 PM"
src="https://github.com/user-attachments/assets/fb34dbb4-3d61-40d6-8c76-8103b0eca3ec"
/> |<img width="100%" alt="Zight Recording 2026-08-17 at 03 07 19 PM"
src="https://github.com/user-attachments/assets/c8db304f-bb15-49f7-8db7-63031d9b6040"
/> |
Paused needed more than the option removed. Its "all" existed because
clearing
the param let the `hide_paused_dags_by_default` seeding put the filter
straight
back, so the option was the only route to paused Dags. Seeding now happens
once
per page load, and the list no longer re-applies the default when the param
is
absent, which makes removing the pill mean what it says. A fresh load
re-applies
the default, which is what a deployment setting should do.
The lists in `stateOptions.ts` keep their "all" entries — the graph task
filter
still uses them and is not a FilterBar pill.
## Table controls move into the table header
`DataTable` gains three slots, each documented with the kind of control it
expects, because the distinction is otherwise easy to get wrong:
| Slot | Position | Holds |
|---|---|---|
| `filterActions` | left, under the heading | changes *which* rows come back
— `SearchBar`, `FilterBar` |
| `presentationActions` | right, with the columns menu | changes *how*
returned rows are drawn — sort selects, expand/collapse |
| `primaryActions` | right, level with the heading | the page's calls to
action — "Add Pool", "Add Variable" |
The former `actions` prop is renamed `presentationActions`; the old name
gave no
hint which of the three kinds of control belonged in it. Fourteen pages were
migrated.
Each slot has to be named in the header row's render condition. Missing one
is
invisible until a table has nothing else to put in the row — the row count
heading is suppressed while loading, so the controls would disappear on
exactly
the tables that show least. There are regression tests for all three.
### Notes for reviewers
- **The `TEAMS` single-to-multi conversion is the change most worth a careful
look.** Blast radius is two pages (`DagRunsFilters`,
`TaskInstancesFilter`);
both already read `getAll()` and pass `Array<string>`, and the OpenAPI
type is
`Array<string>`. Existing `?teams=x` URLs keep working; the only visible
change
is that "All Teams" is replaced by removing the pill. It is separable into
its
own commit if preferred.
- I could not exercise `TEAMS` live — `multi_team` is disabled on the
instance I
tested against. It is covered by type-checking and unit tests only.
- New i18n keys are English-only; the other 20 locales fall back at runtime,
which is the sanctioned path here (`check-translations-completeness` is a
warning, and `AGENTS.md` forbids placeholder translations).
- Dropping the "all" options leaves `dags:filters.allStates`, `allRunTypes`
and
the equivalent job keys in place, since `stateOptions.ts` still feeds them
to
the graph task filter.
### Testing
`tsc` and `eslint` clean; 944/944 vitest tests pass, including new coverage
for
multiselect round-tripping in `useFiltersHandler`, the boolean and
custom-editor
paths in `FilterBar`, all three `DataTable` slots, and filters being dropped
when
left unset. Every filter that used to offer "all" was opened in a browser to
confirm the option is gone. The Dags e2e spec passes
apart from one failure that reproduces on unmodified `main`. All twelve
affected
pages were driven in a browser with no console errors.
---
##### Was generative AI tooling used to co-author this PR?
- [X] Yes — Claude Code (Opus 5)
Generated-by: Claude Code (Opus 5) 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]