Repository navigation
UI: Allow scoping plugin tabs to specific Dags and tasks - #69148
Conversation
There was a problem hiding this comment.
It is possible to pass all those attributes to the plugin already. (dagId, taskInstanceId, runId).
Always displaying the tab, and let the Plugin display 'nothing' or a specific message depending on the relevant parameters, so everything is still self contained in the plugin, and the plugin remains responsible for displaying whatever it feels like depending on the params.
This adds complexity just for the sake of 'hiding the tab', I don't think it's worth it.
|
Actually I think this can make sense. If you want a plugin to only work for one dag or task. Then adding an empty tab to hundreds of other tasks or dags is not a great UX. I can definitely see that a plugin would be only relevant to a certain operator. |
|
That's possible and make sense, the thing I don't like is that it reverses the dependency from core to plugin, to decide what to do instead of having plugin self contained, deciding for themselves what they should display. And that's added complexity and code for something that isn't really harmfull. (showing a plugin tab that will render, "Plugin not available for this operator...." is fine to me. (instead of having people wonder where the plugin tab is, why it's not displaying there, I find it actually more confusing) But if we want this in I'm fine. |
|
The dependency order is a fair point. I also want to think more if the filter patterns in this PR are the way we want to go. I fear that it could quickly becoming unruly with too many specific ways to filter a dag or task, each one being its own optional parameter. |
|
Hi @pierrejeambrun, @bbovenzi In my case I have 6-7 plugins (and growing) which are dag specific or tag specific and I dont want to display these tabs for unrelated dags. Hence my approach. Im happy to take suggestions in alternate ways of implementation. Please let me know |
|
Thanks for the feedback, both points landed. Proposal to address them: Instead of three flat parameters (which would keep growing — I also want task-level scoping), collapse everything into a single optional "applies_to": {
"dag_tags": ["ml"], # Dag carries any of these tags
"dag_ids": ["train_pipeline"], # exact dag_id
"task_ids": ["train_model"], # exact task_id
"operators": ["KubernetesPodOperator"], # task's operator class
}Semantics like Kubernetes label selectors: OR within a key, AND across keys, omit On the concerns raised:
I'd also split the "pass If this shape looks reasonable I'll rework the branch. Drafted-by: Claude Code (Fable 5); reviewed by @dheerajturaga before posting |
4e70b01 to
bdb30a8
Compare
bbovenzi
left a comment
There was a problem hiding this comment.
We should add some validation to the _get_ui_plugins() of plugins_manager.py. So we can log error messages if the applies_to are incorrect. Or log a warning that a task filter is applied to the dag level and therefore won't do anything.
bdb30a8 to
7ffccc8
Compare
Plugin external views and React apps that target the Dag, Dag run, task, or task instance pages appeared on every Dag, with no way to limit a tab to the Dags and tasks it is relevant to. A deployment running several Dag-specific plugins ends up with tabs that are dead weight on unrelated Dags, and a plugin cannot hide its own tab from inside itself. Scoping lives in a single optional `applies_to` block rather than one parameter per criterion, so future criteria become keys inside one model instead of new top-level fields. Criteria are AND-ed only across the keys the current page can evaluate, which lets one block be shared by a plugin's Dag- and task-level destinations rather than needing one per destination. This gates the tab affordance only. It is not an authorization boundary: a user who knows the url_route can still reach the view directly.
Addresses review feedback on the plugin tab scoping feature. Malformed or mis-targeted applies_to blocks were accepted silently, and a single operators key conflated the operator class name with its display name — unlike every other operator filter in the UI. Plugin authors also paid for Dag/task lookups on pages where nothing was scoped. Now the plugin loader warns about (and strips) malformed applies_to and warns when a destination cannot honour a criterion, the API model rejects unknown keys so a typo no longer drops the whole plugin, operators and operator_names are matched separately, and the context queries run only when some view for the destination is actually scoped.
Some test files had manual line-wrapping that didn't match prettier's output, tripping the ts-compile-lint-ui static check in CI.
Historical task instance pages can refer to older Dag versions, while malformed plugin metadata should not prevent UI plugins from loading. Keeping the examples aligned avoids configurations that appear valid but cannot filter their destination.
49d95ec to
a9c6a37
Compare
|
The review-level validation request is addressed in Drafted-by: Codex (GPT-5) (no human review before posting) |
Backport failed to create: airflow-ctl/v0-1-test. View the failure log Run detailsNote: As of Merging PRs targeted for Airflow 3.X In matter of doubt please ask in #release-management Slack channel.
You can attempt to backport this manually by running: cherry_picker c855392 airflow-ctl/v0-1-testThis should apply the commit to the airflow-ctl/v0-1-test branch and leave the commit in conflict state marking After you have resolved the conflicts, you can continue the backport process by running: cherry_picker --continueIf you don't have cherry-picker installed, see the installation guide. |
|
@bbovenzi really appreciate your review on this! This implementation will facilitate operator scoped plugins allowing for features such as AIP-115 https://cwiki.apache.org/confluence/spaces/AIRFLOW/pages/440305022/AIP-115+On-Demand+Task+Sections |
|
@dheerajturaga Of course. And I'll add the plugin actions soon too so if you want to surface that task run action without a full tab you can do that too. |
* UI: Allow scoping plugin tabs to specific Dags and tasks Plugin external views and React apps that target the Dag, Dag run, task, or task instance pages appeared on every Dag, with no way to limit a tab to the Dags and tasks it is relevant to. A deployment running several Dag-specific plugins ends up with tabs that are dead weight on unrelated Dags, and a plugin cannot hide its own tab from inside itself. Scoping lives in a single optional `applies_to` block rather than one parameter per criterion, so future criteria become keys inside one model instead of new top-level fields. Criteria are AND-ed only across the keys the current page can evaluate, which lets one block be shared by a plugin's Dag- and task-level destinations rather than needing one per destination. This gates the tab affordance only. It is not an authorization boundary: a user who knows the url_route can still reach the view directly. * UI: Validate plugin applies_to and split operator scoping Addresses review feedback on the plugin tab scoping feature. Malformed or mis-targeted applies_to blocks were accepted silently, and a single operators key conflated the operator class name with its display name — unlike every other operator filter in the UI. Plugin authors also paid for Dag/task lookups on pages where nothing was scoped. Now the plugin loader warns about (and strips) malformed applies_to and warns when a destination cannot honour a criterion, the API model rejects unknown keys so a typo no longer drops the whole plugin, operators and operator_names are matched separately, and the context queries run only when some view for the destination is actually scoped. * UI: Fix prettier formatting in plugin scoping tests Some test files had manual line-wrapping that didn't match prettier's output, tripping the ts-compile-lint-ui static check in CI. * UI: Fix plugin scoping edge cases Historical task instance pages can refer to older Dag versions, while malformed plugin metadata should not prevent UI plugins from loading. Keeping the examples aligned avoids configurations that appear valid but cannot filter their destination.
Plugin external views and React apps that target the Dag, Dag run, task, or task instance pages appear on every Dag, with no way to limit a tab to the Dags and tasks it is relevant to. A deployment running several Dag-specific plugins ends up with tabs that are dead weight on unrelated Dags, and a plugin cannot hide its own tab from inside itself.
This adds a single optional
applies_toblock:All keys are optional; omitting
applies_toshows the item everywhere, which is the existing behaviour.operatorsmatches operator class names only.operator_namesmatches UI/display names (custom_operator_name), including decorator names such as@task.bash.Criteria combine like Kubernetes label selectors — OR within a key, AND across keys — but the AND applies only across the keys the current page can evaluate. A
task_idscriterion cannot be judged on a Dag-level page, so it is skipped there rather than failing the match. That lets oneapplies_toblock be shared by a plugin's Dag- and task-level destinations instead of needing one per destination.Scoping is also applied to the
dag_overviewandtask_overviewReact app destinations, which filter locally rather than going throughusePluginTabs— without that, a scoped plugin still rendered its panel on every Dag.Plugin showing on all Dags:

Plugins showing only for the tutorial Dag:

Plugins showing for

example_*Dags:On the feedback in this thread:
asset_groupsfor asset pages) become keys insidePluginAppliesToResponserather than new top-level parameters. I've deliberately kept this PR to the Dag/task axis so the model can be reviewed on its own; assets are a natural follow-up.dag_tagsputs the opt-in on the Dag side: the Dag author tags their Dag to receive the tab.operatorsis the case where a plugin legitimately knows what it applies to.dag_idsandtask_idscover deployments where the Airflow operator, not the plugin author, knows the mapping.applies_togates the tab affordance, which a plugin cannot do from inside itself — that is the remaining gap.dag_id_patternfrom the earlier revision is dropped: glob matching was the most bespoke part and is not needed.This is display gating, not an authorization boundary — a user who knows the
url_routecan still reach the view directly. Noted in the docs.Was generative AI tooling used to co-author this PR?
Generated-by: Claude Code (Opus 5) following the guidelines