') + ')', '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); } })(); })(); Consolidate JSON editors to Monaco by guan404ming · Pull Request #62708 · apache/airflow · GitHub
Skip to content

Consolidate JSON editors to Monaco - #62708

Merged
guan404ming merged 2 commits into
apache:mainfrom
guan404ming:consolidate-editors-to-monaco
Mar 7, 2026
Merged

Consolidate JSON editors to Monaco#62708
guan404ming merged 2 commits into
apache:mainfrom
guan404ming:consolidate-editors-to-monaco

Conversation

@guan404ming

@guan404mingguan404ming commented Mar 2, 2026

Copy link
Copy Markdown
Member

Related Issue

#61834

Why

The UI bundled three separate editor libraries (CodeMirror, react-json-view, Monaco) for JSON editing and display, adding unnecessary dependency weight.

How

  • Replace CodeMirror-based JsonEditor and react-json-view-based RenderedJsonField with Monaco
  • Remove @codemirror/lang-json, @uiw/codemirror-themes-all, @uiw/react-codemirror, and react-json-view dependencies
  • Update all consumer components to use the new prop interfaces
Screen.Recording.2026-03-03.at.8.51.41.PM.mov

Was generative AI tooling used to co-author this PR?
  • Yes (please specify the tool below)
    Claude Code with Opus 4.6

  • Read the Pull Request Guidelines for more information. Note: commit author/co-author name and email in commits become permanently public when merged.
  • For fundamental code changes, an Airflow Improvement Proposal (AIP) is needed.
  • When adding dependency, check compliance with the ASF 3rd Party License Policy.
  • For significant user-facing changes create newsfragment: {pr_number}.significant.rst or {issue_number}.significant.rst, in airflow-core/newsfragments.

@boring-cyborgboring-cyborgBot added the area:UI Related to UI/UX. For Frontend Developers. label Mar 2, 2026
@guan404ming
guan404mingforce-pushed the consolidate-editors-to-monaco branch 4 times, most recently from f14d880 to a631974CompareMarch 5, 2026 04:28
@guan404ming
guan404ming marked this pull request as ready for review March 5, 2026 06:05
@guan404ming
guan404mingforce-pushed the consolidate-editors-to-monaco branch 2 times, most recently from 66efec8 to 13a99fbCompareMarch 6, 2026 11:08

@bbovenzibbovenzi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nice!

@bbovenzi

Copy link
Copy Markdown
Contributor

But we need to fix tests and static checks

Signed-off-by: Guan-Ming (Wesley) Chiu <105915352+guan404ming@users.noreply.github.com>
Signed-off-by: Guan-Ming (Wesley) Chiu <105915352+guan404ming@users.noreply.github.com>
@guan404ming
guan404mingforce-pushed the consolidate-editors-to-monaco branch from 13a99fb to 0bba12fCompareMarch 6, 2026 16:45
@guan404ming

guan404ming commented Mar 7, 2026

Copy link
Copy Markdown
MemberAuthor

Thanks for the review. Just fixed and ci looks happy.

@guan404ming
guan404ming merged commit 4aee809 into apache:mainMar 7, 2026
79 checks passed
dominikhei pushed a commit to dominikhei/airflow that referenced this pull request Mar 11, 2026
* Consolidate JSON editors to Monaco
Signed-off-by: Guan-Ming (Wesley) Chiu <105915352+guan404ming@users.noreply.github.com>
* Fix e2e test
Signed-off-by: Guan-Ming (Wesley) Chiu <105915352+guan404ming@users.noreply.github.com>
---------
Signed-off-by: Guan-Ming (Wesley) Chiu <105915352+guan404ming@users.noreply.github.com>
isshishi added a commit to aws/amazon-mwaa-docker-images that referenced this pull request Jul 8, 2026
…ch (#540)
*Issue #, if available:*
[Asana
Task](https://app.asana.com/1/8442528107068/project/1213144794262738/task/1215792656662185)
[Service Team accessible design
doc](https://amazon.sharepoint.com/:fl:/r/contentstorage/CSP_e71a9f97-c26e-4ab3-aae0-958e776f08b7/Document%20Library/LoopAppData/3.2.1%20UI%20loads%20Monaco%20editor%20from%20public%20CDN.loop?d=w7e9f3a5b54eb4656bedea3d0e68b6417&csf=1&web=1&e=xAuOTr&nav=cz0lMkZjb250ZW50c3RvcmFnZSUyRkNTUF9lNzFhOWY5Ny1jMjZlLTRhYjMtYWFlMC05NThlNzc2ZjA4YjcmZD1iJTIxbDU4YTUyN0NzMHFxNEpXT2QyOEl0eURsbVhyZ1lOeFBtblh0X2w0dEVNMnBQMnA3S21CalFJY2dnN1QtYTRQaCZmPTAxS1gySkxWQzNIS1BYNTIyVUtaREw1WFZEMkRUSVdaQVgmYz0lMkYmYT1Mb29wQXBwJnA9JTQwZmx1aWR4JTJGbG9vcC1wYWdlLWNvbnRhaW5lciZ4PSU3QiUyMnclMjIlM0ElMjJUMFJUVUh4aGJXRjZiMjR1YzJoaGNtVndiMmx1ZEM1amIyMThZaUZzTlRoaE5USTNRM013Y1hFMFNsZFBaREk0U1hSNVJHeHRXSEpuV1U1NFVHMXVXSFJmYkRSMFJVMHljRkF5Y0RkTGJVSnFVVWxqWjJjM1ZDMWhORkJvZkRBeFMxZ3lTa3hXUmtVek0wNUtRelZCVVVOT1JVazFTVkpPUlVSV00wNWFXbGslM0QlMjIlMkMlMjJpJTIyJTNBJTIyOWVjMWUzMWItNDNkMS00MGQ3LTkxODEtNWE4NzQwMzQ5NjI0JTIyJTdE)
*Description of changes:*
Airflow 3.2.0/3.2.1 ship a UI that lazy-loads the Monaco code editor
from the public CDN `cdn.jsdelivr.net` at runtime, in the end user's
browser. This breaks the JSON/code editor surfaces (DAG Code, XComs,
Rendered Templates, Connection Edit "extra", Trigger DAG with config,
Audit Log JSON) for air-gapped or restricted-egress environments whose
browsers cannot reach the CDN, and loads unverified third-party JS into
authenticated sessions for everyone else.
Upstream introduced this in 3.2.0 (apache/airflow#62708) and reverted it
in 3.2.2 (apache/airflow#66647). This change backports the effect onto
3.2.1 by vendoring Monaco into the image and serving it same-origin from
the Airflow webserver, so the browser makes zero external requests for
the editor.
## What this changes
Adds one build-time bootstrap step:
`images/airflow/3.2.1/bootstrap/02-airflow/002-vendor-monaco-editor.sh`.
At image-build time it:
1. Downloads `monaco-editor` 0.52.2 from the npm registry and verifies a
pinned sha512.
2. Extracts `min/vs` and the MIT LICENSE into the Airflow UI dist at
`ui/dist/assets/monaco/0.52.2/`
3. Rewrites the one bundled jsDelivr base URL to the same-origin path
`/static/assets/monaco/0.52.2/min/vs`.
4. Installs a small `MonacoEnvironment.getWorkerUrl` shim (referenced
from `index.html`) so Monaco's language Web Workers boot from an
absolute same-origin base at runtime.
5. Fails loud on pre/post-flight checks (the CDN literal must exist
before the rewrite; zero `cdn.jsdelivr.net` references and the vendored
assets must exist after) and is idempotent (an already-rewritten bundle
is a no-op).
## Testing
Local build: `./build.sh docker` builds the 3.2.1 image pass along with
the with in-image checks added;
Runtime: Deployed the patched image and exercised the Monaco surfaces with
`*cdn.jsdelivr.net*` **blocked** in the browser to simulate a
restricted-egress client (PRIVATE_ONLY environments).
All pass with zero `cdn.jsdelivr.net` requests and zero console errors:
- Monaco assets load same-origin from
`/static/assets/monaco/0.52.2/min/vs/...` (HTTP 200).
- Audit Log renders.
- Connection Edit "extra" (JSON): editor renders; invalid JSON shows
live validation squiggles (the language Web Worker loads and runs
same-origin).
- DAG Code: renders with Python syntax highlighting.
- Trigger DAG with config: the JSON editor renders and functions.
Blocking the CDN in the browser reproduces the exact failure condition
for restricted-egress clients (PRIVATE_ONLY environment): the defect and
the fix are entirely browser-side (an external CDN fetch) and image-side
(Monaco vendored at build time and served same-origin from disk), so
this exercises the same code paths a fully air-gapped environment would,
without depending on any particular network topology.
## References
- apache/airflow#66647 (upstream fix / revert in 3.2.2)
- apache/airflow#66020 (upstream bug report)
- apache/airflow#62708 (upstream introduction in 3.2.0)
By submitting this pull request, I confirm that you can use, modify,
copy, and redistribute this contribution, under the terms of your
choice.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:UIRelated to UI/UX. For Frontend Developers.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@guan404ming@bbovenzi