From b7a7b35cb3d9497bb620c0913b8deaa108db706f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 18:11:21 +0000 Subject: [PATCH 1/2] chore(deps): bump react-ace from 14.0.1 to 15.0.0 in /superset-frontend Bumps [react-ace](https://github.com/securingsincity/react-ace) from 14.0.1 to 15.0.0. - [Release notes](https://github.com/securingsincity/react-ace/releases) - [Changelog](https://github.com/securingsincity/react-ace/blob/main/CHANGELOG.md) - [Commits](https://github.com/securingsincity/react-ace/compare/v14.0.1...v15.0.0) --- updated-dependencies: - dependency-name: react-ace dependency-version: 15.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- superset-frontend/package-lock.json | 26 +++++++++++++------ .../packages/superset-ui-core/package.json | 2 +- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/superset-frontend/package-lock.json b/superset-frontend/package-lock.json index 3525bf96ace1..6d9e18c9723a 100644 --- a/superset-frontend/package-lock.json +++ b/superset-frontend/package-lock.json @@ -28640,7 +28640,8 @@ "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.isempty": { "version": "4.4.0", @@ -28652,7 +28653,8 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.ismatch": { "version": "4.4.0", @@ -44057,7 +44059,7 @@ "math-expression-evaluator": "^2.0.7", "parse-ms": "^4.0.0", "re-resizable": "^6.11.2", - "react-ace": "^14.0.1", + "react-ace": "^15.0.0", "react-draggable": "^4.7.1", "react-error-boundary": "^6.1.2", "react-js-cron": "^6.0.2", @@ -44168,16 +44170,24 @@ "@types/trusted-types": "^2.0.7" } }, + "packages/superset-ui-core/node_modules/fast-equals": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.1.tgz", + "integrity": "sha512-DjlFSM5Pk9cGcL0q5QXl66eGzx0N6szNgaswwc5ZphlBohjTVJSnGgI+rJVOgOi65qUoQnDZN4nDqi33udtydQ==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, "packages/superset-ui-core/node_modules/react-ace": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/react-ace/-/react-ace-14.0.1.tgz", - "integrity": "sha512-z6YAZ20PNf/FqmYEic//G/UK6uw0rn21g58ASgHJHl9rfE4nITQLqthr9rHMVQK4ezwohJbp2dGrZpkq979PYQ==", + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/react-ace/-/react-ace-15.0.0.tgz", + "integrity": "sha512-gdmS5ftF0hsbkcrAjZQqYbXwFg5JrHuxjki8mP6Bn9kfa6lHKfZk9sU9EfS0ifQ1CpGCmRxF/VC7GRvlJMBuZw==", "license": "MIT", "dependencies": { "ace-builds": "^1.36.3", "diff-match-patch": "^1.0.5", - "lodash.get": "^4.4.2", - "lodash.isequal": "^4.5.0", + "fast-equals": "^5.3.3", "prop-types": "^15.8.1" }, "peerDependencies": { diff --git a/superset-frontend/packages/superset-ui-core/package.json b/superset-frontend/packages/superset-ui-core/package.json index 78a335d02b81..21ab1134ef4c 100644 --- a/superset-frontend/packages/superset-ui-core/package.json +++ b/superset-frontend/packages/superset-ui-core/package.json @@ -77,7 +77,7 @@ "math-expression-evaluator": "^2.0.7", "parse-ms": "^4.0.0", "re-resizable": "^6.11.2", - "react-ace": "^14.0.1", + "react-ace": "^15.0.0", "react-draggable": "^4.7.1", "react-error-boundary": "^6.1.2", "react-js-cron": "^6.0.2", From 285ab2778c5cba656001003a4d846562e560e634 Mon Sep 17 00:00:00 2001 From: Claude Code Date: Fri, 7 Aug 2026 21:23:52 -0700 Subject: [PATCH 2/2] test(AsyncAceEditor): guard react-ace 15's fast-equals annotation diffing react-ace 15.0.0 replaces its internal componentDidUpdate deep-equality check (lodash.isequal -> fast-equals's deepEqual) for deciding whether to re-apply the annotations/markers/setOptions/scrollMargin props. Verified via the published package that it is still deepEqual (not shallowEqual), so the semantic intent is preserved -- but there was no test pinning this behavior for the one prop Superset actually relies on it for: AceEditorProvider, EditorWrapper, and several modals (DatabaseModal, AnnotationModal, ThemeModal, PropertiesModal) all pass a freshly mapped annotations array on every render. Adds a regression test asserting both directions: a same-content, different-reference annotations array must not re-trigger session.setAnnotations() (or the editor would thrash on every keystroke-driven re-render), while genuinely different content must still update it. Verified this actually catches a regression by temporarily breaking each direction in the installed react-ace 15.0.0 package and confirming the test fails accordingly, then restoring it. markers itself is never used anywhere in the Superset codebase (only annotations is), so it does not need its own coverage here. --- .../AsyncAceEditor/AsyncAceEditor.test.tsx | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/superset-frontend/packages/superset-ui-core/src/components/AsyncAceEditor/AsyncAceEditor.test.tsx b/superset-frontend/packages/superset-ui-core/src/components/AsyncAceEditor/AsyncAceEditor.test.tsx index db5ac8da798d..0af0e62cff2a 100644 --- a/superset-frontend/packages/superset-ui-core/src/components/AsyncAceEditor/AsyncAceEditor.test.tsx +++ b/superset-frontend/packages/superset-ui-core/src/components/AsyncAceEditor/AsyncAceEditor.test.tsx @@ -604,6 +604,61 @@ test('cleans up event listeners on unmount', async () => { offSpy.mockRestore(); }); +test('re-applies annotations only when their content actually changes across renders (react-ace 15 fast-equals regression guard)', async () => { + // react-ace's componentDidUpdate decides whether to call + // session.setAnnotations() by deep-comparing the new/old `annotations` + // prop (lib/ace.js, using an internal deep-equality helper -- lodash's + // isEqual through react-ace 14.x, fast-equals's deepEqual from 15.0.0 + // onward). Superset's own AceEditorProvider/EditorWrapper always pass a + // freshly `.map()`-derived annotations array on every render, so this + // guards the actual behavior Superset relies on: a same-content-but- + // different-reference array must NOT re-trigger setAnnotations (or the + // editor would thrash on every keystroke-driven re-render), while a + // genuinely different array must still update the editor. + const ref = createRef(); + const annotationsV1 = [{ row: 0, column: 0, type: 'error', text: 'oops' }]; + + const { rerender, container } = render( + } annotations={annotationsV1} />, + ); + + await waitFor(() => { + expect(container.querySelector(selector)).toBeInTheDocument(); + }); + + const session = ref.current?.editor?.getSession(); + expect(session).toBeDefined(); + if (!session) return; + + // The initial mount already applies annotations via componentDidMount, + // not componentDidUpdate, so start observing only from the first update. + const setAnnotationsSpy = jest.spyOn(session, 'setAnnotations'); + + // Same content, new array/object references -- must be a no-op. + const annotationsV1SameContent = [ + { row: 0, column: 0, type: 'error', text: 'oops' }, + ]; + rerender( + } + annotations={annotationsV1SameContent} + />, + ); + expect(setAnnotationsSpy).not.toHaveBeenCalled(); + + // Genuinely different content -- must update, with the new value. + const annotationsV2 = [ + { row: 1, column: 2, type: 'warning', text: 'different' }, + ]; + rerender( + } annotations={annotationsV2} />, + ); + expect(setAnnotationsSpy).toHaveBeenCalledTimes(1); + expect(setAnnotationsSpy).toHaveBeenCalledWith(annotationsV2); + + setAnnotationsSpy.mockRestore(); +}); + test('does not move autocomplete popup if target container is document.body', async () => { const ref = createRef(); const { container } = render(} />);