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}

Reply via email to