') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); Fix Link to Dag in Plugin by jscheffl · Pull Request #55642 · apache/airflow · GitHub
Skip to content

Fix Link to Dag in Plugin - #55642

Merged
jscheffl merged 6 commits into
apache:mainfrom
jscheffl:bugfix/fix-internal-link
Oct 24, 2025
Merged

Fix Link to Dag in Plugin#55642
jscheffl merged 6 commits into
apache:mainfrom
jscheffl:bugfix/fix-internal-link

Conversation

@jscheffl

Copy link
Copy Markdown
Contributor

There is a teething problem that the React Router DOM "Link" component (similar like #55641) is not working in Edge React UI - pointers to outside the plugins do not work. I need some React Expertise to fix it seems.

@shubhamraj-git / @bbovenzi / @pierrejeambrun HELP :-D

To make this reproducible I made small modifications to revert my current workaround:
Switch back to the "right" internal link from "react-router-dom"

How to see the error:

  1. Take this branch
  2. Run it locally via breeze start-airflow --python 3.12 --backend postgres --executor EdgeExecutor --load-example-dags
  3. Start a Dag to have some workload
  4. Navigate to "Admin" -> "Edge Worker Jobs" (Which is the React Plugin)
  5. Click on the task list in the column on the worker, see that the UI navigates to "Edge Worker Hosts" tab and ... if you have many would scroll to the right one
  6. Flip back to "Edge Worker Jobs" and click on a Task/Dag or Run entry
  7. See the page flips to a empty content. But see the URL is correct. If you take the URL and reload the target is correct

Is this a limitation of the plugin concept or a bug in my Plugin? How can the Toaster be made working?
On main I use an "external link" with href from Chakra which leads to a full page realod which is nasty and slow.
Is there a way from within the Plugin to link to the Dag run/grid view w/o page reload?

@boring-cyborgboring-cyborgBot added area:providers provider:edge Edge Executor / Worker (AIP-69) / edge3 labels Sep 13, 2025
@jscheffl
jschefflforce-pushed the bugfix/fix-internal-link branch from df249ad to c24bf0dCompareSeptember 14, 2025 20:42
@pierrejeambrun

pierrejeambrun commented Sep 29, 2025

Copy link
Copy Markdown
Member

@jscheffl I just pushed a commit that should solve your problems

Screen.Recording.2025-09-29.at.12.59.41.mov

I'm creating a PR for the 'bootstraping tool'. (pre-commit hook is not happy, but the error message seems unrelated to my change, let me know if you manage to fix it)

@pierrejeambrun

Copy link
Copy Markdown
Member

Here is main PR: #56205

@jscheffl

Copy link
Copy Markdown
ContributorAuthor

Here is main PR: #56205

Cool!

I was thinking about we probably also need to pseudo assume/build the bootstrapping tool and have dependabot running over it such that packages are upgraded regularly... still one of my items on my bcuket list...

@jscheffl
jschefflforce-pushed the bugfix/fix-internal-link branch from 4940dce to 81e8f98CompareOctober 5, 2025 14:25
@jscheffl

jscheffl commented Oct 5, 2025

Copy link
Copy Markdown
ContributorAuthor

@pierrejeambrun thanks for the hints! I attempted to apply your proposals to Edge Executor UI Plugin and it seems to work.... but also needed to realize that https://github.com/apache/airflow/pull/55642/files#diff-72d1ca6b75418dccf2e70084d6d3b9840da702fa127e3848f7fd66198f9d406eR68 was missing on the PR to main.

Unfortunately w/o this it is still not working with main.

As proposed in https://apache-airflow.slack.com/archives/C0809U4S1Q9/p1759237779941759?thread_ts=1759181599.821269&cid=C0809U4S1Q9 I implemented a detection in "InternalLink" component which captures the diff.

...unfortunately I assume due to missing property in airflow-core/src/airflow/ui/src/router.tsx the changes break the functionality in 3.1.0, if I start via breeze start-airflow --python 3.12 --load-example-dags --backend postgres --executor EdgeExecutor --answer y --use-airflow-version 3.1.0 --use-distributions-from-dist then I get the following error navigating into plugin. So the changes fix the link in 3.1.1/3.2++ but it breaks with 3.1.0 :-(

image

Any idea how I can model a workaround keeping compatibility with 3.1.0 and later versions?

UPDATE: After fiddeling and triaging for ~2 hours it seems this is a real breaking change, mainly caused by https://github.com/apache/airflow/pull/55642/files#diff-11195c94c2a05c1187b0517dfe1437a2914f4b22a5b2a9d721cb253ba16e4f79R41 - I can make it either working on 3.1.0 or 3.1.0++ but not on both because the vite.config.ts seems to be mutually exclusive?

@pierrejeambrunpierrejeambrun left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed, I think it will be a substantial effort to make it work between new/old plugins version and <=3.1.1 >=3.1.1 core versions

I think we should leverage the experimental tag of the react app plugin feature, and add a lower bound on airflow core for the new edge provider version plugin. (have airflow >=3.1.1 in new edge provider version) if that's acceptable. That would save us some ugly backward compatibility code. (if we manage to make it work)

@potiuk

Copy link
Copy Markdown
Member

if that's acceptable.

Yep. We can make such exception and our tooling support it, though, it's rather uncommon to have >= patchlevel. More likely >= 3.2.0 ?

@jscheffl
jschefflforce-pushed the bugfix/fix-internal-link branch from 81e8f98 to cb2448aCompareOctober 22, 2025 20:55
@jscheffljscheffl changed the title [DO NOT MERGE] Attempt to fix Link to Dag in PluginFix Link to Dag in PluginOct 22, 2025
@jscheffl

Copy link
Copy Markdown
ContributorAuthor

@pierrejeambrun@kaxil@eladkal FYI seems this is needed to make Edge UI Plugin working in 3.1.1 release. The previous releases (incl 1.4.0rc1) are "broken" as UI routes are having a breaking change... good that the tagged the React plugins "experimental" :-D

@jscheffl
jscheffl marked this pull request as ready for review October 22, 2025 22:03

@pierrejeambrunpierrejeambrun left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice thanks, just a couple of comments.

Comment threadproviders/edge3/pyproject.toml
Comment threaddev/breeze/src/airflow_breeze/global_constants.py
@dheerajturaga

Copy link
Copy Markdown
Member

@jscheffl I texted this PR with base url sub path and a nginx redirect. Its working as expected.
Also, looks like this PR is now critical to have edge3 working on 3.1.1 as 1.3.1 and 1.4.0rc1/2 are NOT working with 3.1.1

@jscheffl

Copy link
Copy Markdown
ContributorAuthor

Thanks for testing as well!

...this PR is now critical to have edge3 working on 3.1.1 as 1.3.1 and 1.4.0rc1

Not working "at all" or just UI Plugin broken? I saw it working in general just w/o proper UI plugin support.

@dheerajturaga

dheerajturaga commented Oct 24, 2025

Copy link
Copy Markdown
Member

Thanks for testing as well!

...this PR is now critical to have edge3 working on 3.1.1 as 1.3.1 and 1.4.0rc1

Not working "at all" or just UI Plugin broken? I saw it working in general just w/o proper UI plugin support.

You are right! I shall correct myself, egde executor and workers are working. The UI plugin is broken

@pierrejeambrunpierrejeambrun left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good to me, thanks Jens.

@jscheffl
jscheffl merged commit c54922e into apache:mainOct 24, 2025
139 of 144 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:providersprovider:edgeEdge Executor / Worker (AIP-69) / edge3

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@jscheffl@pierrejeambrun@potiuk@dheerajturaga