This is an automated email from the ASF dual-hosted git repository. EnxDev pushed a commit to branch enxdev/chat-prototype in repository https://gitbox.apache.org/repos/asf/superset.git
commit d3d39e06d73ff9896a91f34e02fcc27af81f37d7 Author: Enzo Martellucci <[email protected]> AuthorDate: Tue May 26 16:10:35 2026 +0200 feat(extensions): admin configuration UI for extensions Adds enable/disable toggles per extension and an active-chatbot selector (shown when multiple chatbot extensions are registered) to the Extensions list view. Settings are persisted via PUT /api/v1/extensions/settings. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> --- .../src/extensions/ExtensionsList.tsx | 107 +++++++++++++++++++-- 1 file changed, 101 insertions(+), 6 deletions(-) diff --git a/superset-frontend/src/extensions/ExtensionsList.tsx b/superset-frontend/src/extensions/ExtensionsList.tsx index 6f4f9c2f56d..e7974c76817 100644 --- a/superset-frontend/src/extensions/ExtensionsList.tsx +++ b/superset-frontend/src/extensions/ExtensionsList.tsx @@ -17,20 +17,31 @@ * under the License. */ import { t } from '@apache-superset/core/translation'; -import { FunctionComponent, useMemo } from 'react'; +import { css } from '@apache-superset/core/theme'; +import { FunctionComponent, useCallback, useEffect, useMemo, useState } from 'react'; +import { SupersetClient } from '@superset-ui/core'; +import { Select } from '@superset-ui/core/components'; +import { Switch } from '@superset-ui/core/components/Switch'; import { useListViewResource } from 'src/views/CRUD/hooks'; import { ListView } from 'src/components'; import SubMenu, { SubMenuProps } from 'src/features/home/SubMenu'; import withToasts from 'src/components/MessageToasts/withToasts'; +import { CHATBOT_LOCATION } from 'src/views/contributions'; +import { getRegisteredViewIds, subscribeToLocation } from 'src/core/views'; const PAGE_SIZE = 25; type Extension = { - id: number; + id: string; name: string; enabled: boolean; }; +type ExtensionSettings = { + active_chatbot_id: string | null; + enabled: Record<string, boolean>; +}; + interface ExtensionsListProps { addDangerToast: (msg: string) => void; addSuccessToast: (msg: string) => void; @@ -50,6 +61,54 @@ const ExtensionsList: FunctionComponent<ExtensionsListProps> = ({ addDangerToast, ); + const [settings, setSettings] = useState<ExtensionSettings>({ + active_chatbot_id: null, + enabled: {}, + }); + + const [chatbotRegistryVersion, setChatbotRegistryVersion] = useState(0); + useEffect( + () => + subscribeToLocation(CHATBOT_LOCATION, () => + setChatbotRegistryVersion(v => v + 1), + ), + [], + ); + + useEffect(() => { + SupersetClient.get({ endpoint: '/api/v1/extensions/settings' }) + .then(({ json }) => setSettings(json.result)) + .catch(() => addDangerToast(t('Failed to load extension settings.'))); + }, [addDangerToast]); + + const saveSettings = useCallback( + (patch: Partial<ExtensionSettings>) => { + const next = { ...settings, ...patch }; + SupersetClient.put({ + endpoint: '/api/v1/extensions/settings', + jsonPayload: next, + }) + .then(({ json }) => { + setSettings(json.result); + addSuccessToast(t('Settings saved.')); + }) + .catch(() => addDangerToast(t('Failed to save extension settings.'))); + }, + [settings, addDangerToast, addSuccessToast], + ); + + const toggleEnabled = useCallback( + (extensionId: string, enabled: boolean) => { + saveSettings({ enabled: { ...settings.enabled, [extensionId]: enabled } }); + }, + [settings, saveSettings], + ); + + const chatbotExtensions = useMemo(() => { + const chatbotIds = new Set(getRegisteredViewIds(CHATBOT_LOCATION)); + return resourceCollection.filter(ext => chatbotIds.has(ext.id)); + }, [resourceCollection, chatbotRegistryVersion]); + const columns = useMemo( () => [ { @@ -58,15 +117,34 @@ const ExtensionsList: FunctionComponent<ExtensionsListProps> = ({ size: 'lg', id: 'name', Cell: ({ - row: { - original: { name }, - }, + row: { original: { name } }, }: any) => name, }, + { + Header: t('Enabled'), + accessor: 'enabled', + size: 'sm', + id: 'enabled', + Cell: ({ + row: { original: { id } }, + }: any) => ( + <Switch + data-test="toggle-enabled" + checked={settings.enabled[id] ?? true} + onClick={(checked: boolean) => toggleEnabled(id, checked)} + size="small" + /> + ), + }, ], - [loading], // We need to monitor loading to avoid stale state in actions + [loading, settings, toggleEnabled], ); + const chatbotOptions = chatbotExtensions.map(ext => ({ + label: ext.name, + value: ext.id, + })); + const menuData: SubMenuProps = { activeChild: 'Extensions', name: t('Extensions'), @@ -76,6 +154,23 @@ const ExtensionsList: FunctionComponent<ExtensionsListProps> = ({ return ( <> <SubMenu {...menuData} /> + {chatbotOptions.length > 1 && ( + <div style={{ padding: '16px 24px' }}> + <label htmlFor="chatbot-select" style={{ marginRight: 8 }}> + {t('Default chatbot')} + </label> + <Select + allowClear + options={chatbotOptions} + value={settings.active_chatbot_id ?? undefined} + onChange={value => + saveSettings({ active_chatbot_id: (value as string) ?? null }) + } + placeholder={t('First registered (automatic)')} + css={css`width: 280px;`} + /> + </div> + )} <ListView<Extension> columns={columns} count={resourceCount}
