This is an automated email from the ASF dual-hosted git repository. EnxDev pushed a commit to branch test/chatbot-local in repository https://gitbox.apache.org/repos/asf/superset.git
commit a74684b06250087261eb65e1dbdc7fb13969133f Author: Enzo Martellucci <[email protected]> AuthorDate: Wed May 27 12:10:16 2026 +0200 fix(extensions): port CodeAnt fixes — page-type guards, deactivation cleanup, settings race, feature flag gate Co-Authored-By: Claude Sonnet 4.6 <[email protected]> --- superset-frontend/src/core/dashboard/index.ts | 2 ++ superset-frontend/src/core/explore/index.ts | 2 ++ .../src/extensions/ExtensionsList.tsx | 19 ++++++++----- .../src/extensions/ExtensionsLoader.ts | 31 +++++++++++++++++----- superset-frontend/src/views/App.tsx | 3 ++- 5 files changed, 43 insertions(+), 14 deletions(-) diff --git a/superset-frontend/src/core/dashboard/index.ts b/superset-frontend/src/core/dashboard/index.ts index fbf17f3fa62..611a4514f0b 100644 --- a/superset-frontend/src/core/dashboard/index.ts +++ b/superset-frontend/src/core/dashboard/index.ts @@ -34,11 +34,13 @@ import { import { store, RootState } from 'src/views/store'; import { AnyListenerPredicate } from '@reduxjs/toolkit'; import { createActionListener } from '../utils'; +import { navigation } from '../navigation'; type DashboardContext = dashboardApi.DashboardContext; type FilterValue = dashboardApi.FilterValue; function buildDashboardContext(): DashboardContext | undefined { + if (navigation.getPageType() !== 'dashboard') return undefined; const state = store.getState(); const info = (state as any).dashboardInfo; if (!info?.id) return undefined; diff --git a/superset-frontend/src/core/explore/index.ts b/superset-frontend/src/core/explore/index.ts index 46b6b2ef84e..732ae975caa 100644 --- a/superset-frontend/src/core/explore/index.ts +++ b/superset-frontend/src/core/explore/index.ts @@ -34,10 +34,12 @@ import { SET_DATASOURCE } from 'src/explore/actions/datasourcesActions'; import { store, RootState } from 'src/views/store'; import { AnyListenerPredicate } from '@reduxjs/toolkit'; import { createActionListener } from '../utils'; +import { navigation } from '../navigation'; type ChartContext = exploreApi.ChartContext; function buildChartContext(): ChartContext | undefined { + if (navigation.getPageType() !== 'explore') return undefined; const state = store.getState(); const exploreState = (state as any).explore; if (!exploreState) return undefined; diff --git a/superset-frontend/src/extensions/ExtensionsList.tsx b/superset-frontend/src/extensions/ExtensionsList.tsx index 5daceb60dc2..f57cefba970 100644 --- a/superset-frontend/src/extensions/ExtensionsList.tsx +++ b/superset-frontend/src/extensions/ExtensionsList.tsx @@ -79,6 +79,10 @@ const ExtensionsList: FunctionComponent<ExtensionsListProps> = ({ active_chatbot_id: null, enabled: {}, }); + // Always holds the latest settings value so saveSettings never closes over + // a stale snapshot when rapid toggles race against each other. + const settingsRef = useRef(settings); + settingsRef.current = settings; const [chatbotRegistryVersion, setChatbotRegistryVersion] = useState(0); useEffect( @@ -97,7 +101,8 @@ const ExtensionsList: FunctionComponent<ExtensionsListProps> = ({ const saveSettings = useCallback( (patch: Partial<ExtensionSettings>) => { - const next = { ...settings, ...patch }; + const next = { ...settingsRef.current, ...patch }; + setSettings(next); SupersetClient.put({ endpoint: '/api/v1/extensions/settings', jsonPayload: next, @@ -109,25 +114,27 @@ const ExtensionsList: FunctionComponent<ExtensionsListProps> = ({ }) .catch(() => addDangerToast(t('Failed to save extension settings.'))); }, - [settings, addDangerToast, addSuccessToast], + [addDangerToast, addSuccessToast], ); const toggleEnabled = useCallback( (extensionId: string, enabled: boolean) => { saveSettings({ - enabled: { ...settings.enabled, [extensionId]: enabled }, + enabled: { ...settingsRef.current.enabled, [extensionId]: enabled }, }); }, - [settings, saveSettings], + [saveSettings], ); const setDefaultChatbot = useCallback( (extensionId: string) => { const next = - settings.active_chatbot_id === extensionId ? null : extensionId; + settingsRef.current.active_chatbot_id === extensionId + ? null + : extensionId; saveSettings({ active_chatbot_id: next }); }, - [settings, saveSettings], + [saveSettings], ); const chatbotIds = useMemo( diff --git a/superset-frontend/src/extensions/ExtensionsLoader.ts b/superset-frontend/src/extensions/ExtensionsLoader.ts index cd99a27dce2..f96091e9d2b 100644 --- a/superset-frontend/src/extensions/ExtensionsLoader.ts +++ b/superset-frontend/src/extensions/ExtensionsLoader.ts @@ -177,17 +177,34 @@ class ExtensionsLoader { // collect the Disposables and drive cleanup on deactivation. const collected: (() => void)[] = []; const originalSuperset = window.superset; + + const wrap = + <TArgs extends unknown[]>( + fn: (...args: TArgs) => { dispose(): void }, + ): ((...args: TArgs) => { dispose(): void }) => + (...args: TArgs) => { + const disposable = fn(...args); + collected.push(() => disposable.dispose()); + return disposable; + }; + window.superset = { ...originalSuperset, + commands: { + ...originalSuperset.commands, + registerCommand: wrap(originalSuperset.commands.registerCommand), + }, + menus: { + ...originalSuperset.menus, + registerMenuItem: wrap(originalSuperset.menus.registerMenuItem), + }, + editors: { + ...originalSuperset.editors, + registerEditor: wrap(originalSuperset.editors.registerEditor), + }, views: { ...originalSuperset.views, - registerView: ( - ...args: Parameters<typeof originalSuperset.views.registerView> - ) => { - const disposable = originalSuperset.views.registerView(...args); - collected.push(() => disposable.dispose()); - return disposable; - }, + registerView: wrap(originalSuperset.views.registerView), }, }; diff --git a/superset-frontend/src/views/App.tsx b/superset-frontend/src/views/App.tsx index d84b111ca84..d70aa0f86f9 100644 --- a/superset-frontend/src/views/App.tsx +++ b/superset-frontend/src/views/App.tsx @@ -38,6 +38,7 @@ import { Logger, LOG_ACTIONS_SPA_NAVIGATION } from 'src/logger/LogUtils'; import setupCodeOverrides from 'src/setup/setupCodeOverrides'; import { logEvent } from 'src/logger/actions'; import { store } from 'src/views/store'; +import { FeatureFlag, isFeatureEnabled } from '@superset-ui/core'; import ExtensionsStartup from 'src/extensions/ExtensionsStartup'; import ChatbotMount from 'src/components/ChatbotMount'; import { RootContextProviders } from './RootContextProviders'; @@ -119,7 +120,7 @@ const App = () => ( loaded and registered, but outside the Switch so the bubble persists across route changes (SIP §3.2). */} - <ChatbotMount /> + {isFeatureEnabled(FeatureFlag.EnableExtensions) && <ChatbotMount />} </ExtensionsStartup> <ToastContainer /> </RootContextProviders>
