This is an automated email from the ASF dual-hosted git repository.

rusackas pushed a commit to branch move-controls
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 204b32e4a06f1c855a3a4e5485d581619431a01f
Author: Evan Rusackas <[email protected]>
AuthorDate: Sun Aug 17 10:12:06 2025 -0700

    feat: Add infrastructure for modern React control panels
    
    - Modified expandControlConfig to handle modern panel components
    - Added ModernControlPanelRenderer component for bridging
    - Updated ControlPanelsContainer to render modern panels directly
    - Modified getAllControlsState to process controlOverrides from modern 
panels
    - Updated getSectionsToRender to handle modern control panels
    - Created PieControlPanelSimple with React-based controls, tooltips, and 
dynamic rendering
    
    This sets up the foundation for migrating from config-based to React 
component-based control panels.
---
 .../src/utils/expandControlConfig.tsx              |   5 +
 .../src/Pie/PieControlPanel.tsx                    | 335 +++++++++------
 .../src/Pie/PieControlPanelSimple.tsx              | 477 +++++++++++++++++++++
 .../plugins/plugin-chart-echarts/src/Pie/index.ts  |  16 +-
 .../explore/components/ControlPanelsContainer.tsx  |  91 +++-
 .../components/ModernControlPanelRenderer.tsx      |  87 ++--
 .../src/explore/controlUtils/getControlState.ts    |  36 +-
 .../explore/controlUtils/getSectionsToRender.ts    |  49 ++-
 8 files changed, 906 insertions(+), 190 deletions(-)

diff --git 
a/superset-frontend/packages/superset-ui-chart-controls/src/utils/expandControlConfig.tsx
 
b/superset-frontend/packages/superset-ui-chart-controls/src/utils/expandControlConfig.tsx
index 8f7a54f183..ae03a26fca 100644
--- 
a/superset-frontend/packages/superset-ui-chart-controls/src/utils/expandControlConfig.tsx
+++ 
b/superset-frontend/packages/superset-ui-chart-controls/src/utils/expandControlConfig.tsx
@@ -45,6 +45,11 @@ export function expandControlConfig(
   if (!control || isValidElement(control)) {
     return control as ReactElement;
   }
+  // Check if it's a modern panel component (function with isModernPanel flag)
+  if (typeof control === 'function' && (control as any).isModernPanel) {
+    console.log('expandControlConfig - Found modern panel, returning as-is');
+    return control as any;
+  }
   // String controls are no longer supported - they must be migrated to React 
components
   if (typeof control === 'string') {
     throw new Error(
diff --git 
a/superset-frontend/plugins/plugin-chart-echarts/src/Pie/PieControlPanel.tsx 
b/superset-frontend/plugins/plugin-chart-echarts/src/Pie/PieControlPanel.tsx
index a3fce3338f..e072f8d6e2 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Pie/PieControlPanel.tsx
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Pie/PieControlPanel.tsx
@@ -19,7 +19,7 @@
 import { FC } from 'react';
 import { t } from '@superset-ui/core';
 import { Collapse, Row, Col, Typography } from 'antd';
-import { 
+import {
   ControlPanelConfig,
   D3_FORMAT_OPTIONS,
   D3_NUMBER_FORMAT_DESCRIPTION_VALUES_TEXT,
@@ -32,6 +32,8 @@ import controlMap from 'src/explore/components/controls';
 
 import { DEFAULT_FORM_DATA } from './types';
 
+console.log('PieControlPanel.tsx - Loading file');
+
 const { Panel } = Collapse;
 const { Title } = Typography;
 
@@ -49,139 +51,214 @@ interface PieControlPanelProps {
  * No legacy controlPanelSections, no controlSetRows, no config objects.
  * Just pure React components with Ant Design layout.
  */
-export const PieControlPanel: FC<PieControlPanelProps> = ({ 
-  onChange, 
-  value, 
+export const PieControlPanel: FC<PieControlPanelProps> = ({
+  onChange,
+  value,
   datasource,
   form_data,
+  actions,
+  controls,
 }) => {
+  console.log('PieControlPanel rendering with:', {
+    value,
+    datasource,
+    form_data,
+    controls,
+  });
+
+  // If no valid data yet, show loading state
+  if (!datasource || !form_data) {
+    return <div>Loading control panel...</div>;
+  }
+
   // Get control components from controlMap
-  const DndColumnSelect = controlMap.DndColumnSelect;
-  const DndMetricSelect = controlMap.DndMetricSelect;
-  const AdhocFilterControl = controlMap.AdhocFilterControl;
-  const CheckboxControl = controlMap.CheckboxControl;
-  const SelectControl = controlMap.SelectControl;
-  const TextControl = controlMap.TextControl;
-  const SliderControl = controlMap.SliderControl;
-  const ColorSchemeControl = controlMap.ColorSchemeControl;
-  
-  // Helper to handle control changes
+  const { DndColumnSelect } = controlMap;
+  const { DndMetricSelect } = controlMap;
+  const { AdhocFilterControl } = controlMap;
+  const { CheckboxControl } = controlMap;
+  const { SelectControl } = controlMap;
+  const { TextControl } = controlMap;
+  const { SliderControl } = controlMap;
+  const { ColorSchemeControl } = controlMap;
+
+  // Helper to handle control changes using actions if available
   const handleChange = (field: string) => (val: any) => {
-    onChange(field, val);
+    console.log('Control change:', field, val);
+    if (actions?.setControlValue) {
+      actions.setControlValue(field, val);
+    } else if (onChange) {
+      onChange(field, val);
+    }
   };
 
+  // Make sure we have valid values or defaults
+  const formValues = form_data || value || {};
+
   return (
-    <div style={{ padding: '16px' }}>
-      <Collapse defaultActiveKey={['query', 'chart', 'labels', 'pie', 
'legend']} ghost>
+    <div
+      style={{
+        padding: '16px',
+        width: '100%',
+        background: '#f0f0f0',
+        minHeight: '500px',
+      }}
+    >
+      <h2>🎉 Pie Control Panel - Pure React Based! 🎉</h2>
+      <p>This is a TRUE React component control panel with:</p>
+      <ul>
+        <li>✅ No controlPanelSections</li>
+        <li>✅ No controlSetRows</li>
+        <li>✅ No config objects</li>
+        <li>✅ Just pure React + Ant Design</li>
+      </ul>
+      <Collapse
+        defaultActiveKey={['query', 'chart', 'labels', 'pie', 'legend']}
+        ghost
+      >
         {/* Query Section */}
         <Panel header={<Title level={5}>{t('Query')}</Title>} key="query">
           <Row gutter={[16, 16]}>
             <Col span={24}>
-              <DndColumnSelect
-                name="groupby"
-                label={t('Group by')}
-                onChange={handleChange('groupby')}
-                value={value.groupby || []}
-                datasource={datasource}
-                multi
-              />
+              <div
+                style={{
+                  padding: '10px',
+                  border: '1px dashed #999',
+                  borderRadius: '4px',
+                }}
+              >
+                <strong>Group by</strong>
+                <p>Current value: {JSON.stringify(formValues.groupby)}</p>
+                <button
+                  onClick={() => handleChange('groupby')(['test_column'])}
+                >
+                  Set test value
+                </button>
+              </div>
             </Col>
           </Row>
-          
+
           <Row gutter={[16, 16]} style={{ marginTop: 16 }}>
             <Col span={24}>
-              <DndMetricSelect
-                name="metric"
-                label={t('Metric')}
-                onChange={handleChange('metric')}
-                value={value.metric}
-                datasource={datasource}
-                multi={false}
-              />
+              <div
+                style={{
+                  padding: '10px',
+                  border: '1px dashed #999',
+                  borderRadius: '4px',
+                }}
+              >
+                <strong>Metric</strong>
+                <p>Current value: {JSON.stringify(formValues.metric)}</p>
+                <button onClick={() => handleChange('metric')('COUNT(*)')}>
+                  Set COUNT(*)
+                </button>
+              </div>
             </Col>
           </Row>
-          
+
           <Row gutter={[16, 16]} style={{ marginTop: 16 }}>
             <Col span={24}>
-              <AdhocFilterControl
-                name="adhoc_filters"
-                label={t('Filters')}
-                onChange={handleChange('adhoc_filters')}
-                value={value.adhoc_filters || []}
-                datasource={datasource}
-              />
+              <div
+                style={{
+                  padding: '10px',
+                  border: '1px dashed #999',
+                  borderRadius: '4px',
+                }}
+              >
+                <strong>Filters</strong>
+                <p>Current value: 
{JSON.stringify(formValues.adhoc_filters)}</p>
+              </div>
             </Col>
           </Row>
-          
+
           <Row gutter={[16, 16]} style={{ marginTop: 16 }}>
             <Col span={12}>
-              <TextControl
-                label={t('Row limit')}
-                onChange={handleChange('row_limit')}
-                value={value.row_limit || 100}
-                controlId="row_limit"
-                renderTrigger
-              />
+              <div
+                style={{
+                  padding: '10px',
+                  border: '1px dashed #999',
+                  borderRadius: '4px',
+                }}
+              >
+                <strong>Row limit</strong>
+                <input
+                  type="number"
+                  value={formValues.row_limit || 100}
+                  onChange={e =>
+                    handleChange('row_limit')(parseInt(e.target.value))
+                  }
+                />
+              </div>
             </Col>
             <Col span={12}>
-              <CheckboxControl
-                label={t('Sort by metric')}
-                onChange={handleChange('sort_by_metric')}
-                value={value.sort_by_metric ?? true}
-                controlId="sort_by_metric"
-                renderTrigger
-              />
+              <div
+                style={{
+                  padding: '10px',
+                  border: '1px dashed #999',
+                  borderRadius: '4px',
+                }}
+              >
+                <strong>Sort by metric</strong>
+                <input
+                  type="checkbox"
+                  checked={formValues.sort_by_metric ?? true}
+                  onChange={e =>
+                    handleChange('sort_by_metric')(e.target.checked)
+                  }
+                />
+              </div>
             </Col>
           </Row>
         </Panel>
-        
+
         {/* Chart Options */}
-        <Panel header={<Title level={5}>{t('Chart Options')}</Title>} 
key="chart">
+        <Panel
+          header={<Title level={5}>{t('Chart Options')}</Title>}
+          key="chart"
+        >
           <Row gutter={[16, 16]}>
             <Col span={24}>
-              <ColorSchemeControl
-                name="color_scheme"
-                label={t('Color scheme')}
-                onChange={handleChange('color_scheme')}
-                value={value.color_scheme || 'supersetColors'}
-                schemes={() => {}}
-                isLinear={false}
-              />
+              <div
+                style={{
+                  padding: '10px',
+                  border: '1px dashed #999',
+                  borderRadius: '4px',
+                }}
+              >
+                <strong>Color scheme</strong>
+                <p>
+                  Current value: {formValues.color_scheme || 'supersetColors'}
+                </p>
+              </div>
             </Col>
           </Row>
-          
+
           <Row gutter={[16, 16]} style={{ marginTop: 16 }}>
             <Col span={12}>
               <TextControl
                 label={t('Percentage threshold')}
-                
                 onChange={handleChange('show_labels_threshold')}
-                value={value.show_labels_threshold ?? 5}
+                value={formValues.show_labels_threshold ?? 5}
                 controlId="show_labels_threshold"
                 renderTrigger
-                
               />
             </Col>
             <Col span={12}>
               <TextControl
                 label={t('Threshold for Other')}
-                
                 onChange={handleChange('threshold_for_other')}
-                value={value.threshold_for_other ?? 0}
+                value={formValues.threshold_for_other ?? 0}
                 controlId="threshold_for_other"
                 renderTrigger
-                
               />
             </Col>
           </Row>
-          
+
           <Row gutter={[16, 16]} style={{ marginTop: 16 }}>
             <Col span={12}>
               <SelectControl
                 label={t('Rose Type')}
-                
                 onChange={handleChange('roseType')}
-                value={value.roseType || null}
+                value={formValues.roseType || null}
                 choices={[
                   ['area', t('Area')],
                   ['radius', t('Radius')],
@@ -192,16 +269,15 @@ export const PieControlPanel: FC<PieControlPanelProps> = 
({
             </Col>
           </Row>
         </Panel>
-        
+
         {/* Labels Section */}
         <Panel header={<Title level={5}>{t('Labels')}</Title>} key="labels">
           <Row gutter={[16, 16]}>
             <Col span={24}>
               <SelectControl
                 label={t('Label Type')}
-                
                 onChange={handleChange('label_type')}
-                value={value.label_type || 'key'}
+                value={formValues.label_type || 'key'}
                 choices={[
                   ['key', t('Category Name')],
                   ['value', t('Value')],
@@ -216,28 +292,27 @@ export const PieControlPanel: FC<PieControlPanelProps> = 
({
               />
             </Col>
           </Row>
-          
-          {value.label_type === 'template' && (
+
+          {formValues.label_type === 'template' && (
             <Row gutter={[16, 16]} style={{ marginTop: 16 }}>
               <Col span={24}>
                 <TextControl
                   label={t('Label Template')}
                   onChange={handleChange('label_template')}
-                  value={value.label_template || ''}
+                  value={formValues.label_template || ''}
                   controlId="label_template"
                   renderTrigger
                 />
               </Col>
             </Row>
           )}
-          
+
           <Row gutter={[16, 16]} style={{ marginTop: 16 }}>
             <Col span={12}>
               <SelectControl
                 label={t('Number format')}
-                
                 onChange={handleChange('number_format')}
-                value={value.number_format || 'SMART_NUMBER'}
+                value={formValues.number_format || 'SMART_NUMBER'}
                 choices={D3_FORMAT_OPTIONS}
                 freeForm
               />
@@ -245,22 +320,20 @@ export const PieControlPanel: FC<PieControlPanelProps> = 
({
             <Col span={12}>
               <SelectControl
                 label={t('Date format')}
-                
                 onChange={handleChange('date_format')}
-                value={value.date_format || 'smart_date'}
+                value={formValues.date_format || 'smart_date'}
                 choices={D3_TIME_FORMAT_OPTIONS}
                 freeForm
               />
             </Col>
           </Row>
-          
+
           <Row gutter={[16, 16]} style={{ marginTop: 16 }}>
             <Col span={8}>
               <CheckboxControl
                 label={t('Show Labels')}
-                
                 onChange={handleChange('show_labels')}
-                value={value.show_labels ?? DEFAULT_FORM_DATA.showLabels}
+                value={formValues.show_labels ?? DEFAULT_FORM_DATA.showLabels}
                 controlId="show_labels"
                 renderTrigger
               />
@@ -268,9 +341,10 @@ export const PieControlPanel: FC<PieControlPanelProps> = ({
             <Col span={8}>
               <CheckboxControl
                 label={t('Put labels outside')}
-                
                 onChange={handleChange('labels_outside')}
-                value={value.labels_outside ?? DEFAULT_FORM_DATA.labelsOutside}
+                value={
+                  formValues.labels_outside ?? DEFAULT_FORM_DATA.labelsOutside
+                }
                 controlId="labels_outside"
                 renderTrigger
               />
@@ -278,29 +352,27 @@ export const PieControlPanel: FC<PieControlPanelProps> = 
({
             <Col span={8}>
               <CheckboxControl
                 label={t('Label Line')}
-                
                 onChange={handleChange('label_line')}
-                value={value.label_line ?? DEFAULT_FORM_DATA.labelLine}
+                value={formValues.label_line ?? DEFAULT_FORM_DATA.labelLine}
                 controlId="label_line"
                 renderTrigger
               />
             </Col>
           </Row>
-          
+
           <Row gutter={[16, 16]} style={{ marginTop: 16 }}>
             <Col span={12}>
               <CheckboxControl
                 label={t('Show Total')}
-                
                 onChange={handleChange('show_total')}
-                value={value.show_total ?? false}
+                value={formValues.show_total ?? false}
                 controlId="show_total"
                 renderTrigger
               />
             </Col>
           </Row>
         </Panel>
-        
+
         {/* Pie Shape Section */}
         <Panel header={<Title level={5}>{t('Pie shape')}</Title>} key="pie">
           <Row gutter={[16, 16]}>
@@ -308,61 +380,60 @@ export const PieControlPanel: FC<PieControlPanelProps> = 
({
               <SliderControl
                 label={t('Outer Radius')}
                 onChange={handleChange('outerRadius')}
-                value={value.outerRadius ?? DEFAULT_FORM_DATA.outerRadius}
+                value={formValues.outerRadius ?? DEFAULT_FORM_DATA.outerRadius}
               />
             </Col>
           </Row>
-          
+
           <Row gutter={[16, 16]} style={{ marginTop: 16 }}>
             <Col span={12}>
               <CheckboxControl
                 label={t('Donut')}
-                
                 onChange={handleChange('donut')}
-                value={value.donut ?? DEFAULT_FORM_DATA.donut}
+                value={formValues.donut ?? DEFAULT_FORM_DATA.donut}
                 controlId="donut"
                 renderTrigger
               />
             </Col>
           </Row>
-          
-          {value.donut && (
+
+          {formValues.donut && (
             <Row gutter={[16, 16]} style={{ marginTop: 16 }}>
               <Col span={24}>
                 <SliderControl
                   label={t('Inner Radius')}
                   onChange={handleChange('innerRadius')}
-                  value={value.innerRadius ?? DEFAULT_FORM_DATA.innerRadius}
+                  value={
+                    formValues.innerRadius ?? DEFAULT_FORM_DATA.innerRadius
+                  }
                 />
               </Col>
             </Row>
           )}
         </Panel>
-        
+
         {/* Legend Section */}
         <Panel header={<Title level={5}>{t('Legend')}</Title>} key="legend">
           <Row gutter={[16, 16]}>
             <Col span={24}>
               <CheckboxControl
                 label={t('Show legend')}
-                
                 onChange={handleChange('show_legend')}
-                value={value.show_legend ?? true}
+                value={formValues.show_legend ?? true}
                 controlId="show_legend"
                 renderTrigger
               />
             </Col>
           </Row>
-          
-          {value.show_legend && (
+
+          {formValues.show_legend && (
             <>
               <Row gutter={[16, 16]} style={{ marginTop: 16 }}>
                 <Col span={12}>
                   <SelectControl
                     label={t('Legend type')}
-                    
                     onChange={handleChange('legendType')}
-                    value={value.legendType || 'scroll'}
+                    value={formValues.legendType || 'scroll'}
                     choices={[
                       ['scroll', t('Scroll')],
                       ['plain', t('Plain')],
@@ -373,9 +444,8 @@ export const PieControlPanel: FC<PieControlPanelProps> = ({
                 <Col span={12}>
                   <SelectControl
                     label={t('Legend orientation')}
-                    
                     onChange={handleChange('legendOrientation')}
-                    value={value.legendOrientation || 'top'}
+                    value={formValues.legendOrientation || 'top'}
                     choices={[
                       ['top', t('Top')],
                       ['bottom', t('Bottom')],
@@ -386,17 +456,15 @@ export const PieControlPanel: FC<PieControlPanelProps> = 
({
                   />
                 </Col>
               </Row>
-              
+
               <Row gutter={[16, 16]} style={{ marginTop: 16 }}>
                 <Col span={24}>
                   <TextControl
                     label={t('Legend margin')}
-                    
                     onChange={handleChange('legendMargin')}
-                    value={value.legendMargin || 0}
+                    value={formValues.legendMargin || 0}
                     controlId="legendMargin"
                     renderTrigger
-                    
                   />
                 </Col>
               </Row>
@@ -409,25 +477,14 @@ export const PieControlPanel: FC<PieControlPanelProps> = 
({
 };
 
 /**
- * Export as a ControlPanelConfig that just contains our React component.
- * This is the bridge between the old system and our new approach.
+ * Mark this component as a modern panel so the renderer knows how to handle it
  */
-const config: ControlPanelConfig = {
-  controlPanelSections: [
-    {
-      label: t('React Control Panel'),
-      expanded: true,
-      controlSetRows: [
-        [
-          <PieControlPanel
-            onChange={() => {}}
-            value={{}}
-            datasource={{}}
-          />,
-        ],
-      ],
-    },
-  ],
-};
+(PieControlPanel as any).isModernPanel = true;
+
+console.log(
+  'PieControlPanel.tsx - Component defined, isModernPanel:',
+  (PieControlPanel as any).isModernPanel,
+);
 
-export default config;
\ No newline at end of file
+// Export the component directly as the default export
+export default PieControlPanel;
diff --git 
a/superset-frontend/plugins/plugin-chart-echarts/src/Pie/PieControlPanelSimple.tsx
 
b/superset-frontend/plugins/plugin-chart-echarts/src/Pie/PieControlPanelSimple.tsx
new file mode 100644
index 0000000000..b45443343a
--- /dev/null
+++ 
b/superset-frontend/plugins/plugin-chart-echarts/src/Pie/PieControlPanelSimple.tsx
@@ -0,0 +1,477 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { FC } from 'react';
+import { t } from '@superset-ui/core';
+// Import the real drag-and-drop controls
+import { ColorSchemeControl as SharedColorSchemeControl } from 
'@superset-ui/chart-controls';
+import { DndColumnSelect } from 
'../../../../src/explore/components/controls/DndColumnSelectControl/DndColumnSelect';
+import { DndMetricSelect } from 
'../../../../src/explore/components/controls/DndColumnSelectControl/DndMetricSelect';
+import { DndFilterSelect } from 
'../../../../src/explore/components/controls/DndColumnSelectControl/DndFilterSelect';
+import TextControl from 
'../../../../src/explore/components/controls/TextControl';
+import CheckboxControl from 
'../../../../src/explore/components/controls/CheckboxControl';
+import SliderControl from 
'../../../../src/explore/components/controls/SliderControl';
+import ControlHeader from '../../../../src/explore/components/ControlHeader';
+import Control from '../../../../src/explore/components/Control';
+
+console.log('PieControlPanelSimple.tsx - Loading file');
+
+interface PieControlPanelProps {
+  onChange?: (field: string, value: any) => void;
+  value?: Record<string, any>;
+  datasource?: any;
+  actions?: any;
+  controls?: any;
+  form_data?: any;
+}
+
+/**
+ * A TRUE React component-based control panel for Pie charts.
+ * This uses the real drag-and-drop controls from Superset.
+ */
+export const PieControlPanel: FC<PieControlPanelProps> = ({
+  onChange,
+  value,
+  datasource,
+  form_data,
+  actions,
+  controls,
+}) => {
+  console.log('PieControlPanel rendering with:', {
+    value,
+    datasource,
+    form_data,
+    controls,
+  });
+  console.log('Datasource type:', typeof datasource);
+  console.log('Datasource columns:', datasource?.columns);
+  console.log('Datasource metrics:', datasource?.metrics);
+
+  // If no valid data yet, show loading state
+  if (!datasource || !form_data) {
+    return <div>Loading control panel...</div>;
+  }
+
+  // Ensure datasource has the expected structure with arrays
+  const safeColumns = Array.isArray(datasource?.columns)
+    ? datasource.columns
+    : [];
+  const safeMetrics = Array.isArray(datasource?.metrics)
+    ? datasource.metrics
+    : [];
+
+  const safeDataSource = {
+    ...datasource,
+    columns: safeColumns,
+    metrics: safeMetrics,
+  };
+
+  console.log('Safe datasource:', safeDataSource);
+
+  // Helper to handle control changes using actions if available
+  const handleChange =
+    (field: string, renderTrigger = false) =>
+    (val: any) => {
+      console.log(
+        'Control change:',
+        field,
+        val,
+        'renderTrigger:',
+        renderTrigger,
+      );
+      if (actions?.setControlValue) {
+        actions.setControlValue(field, val);
+        // If renderTrigger is true and we have chart update capability, 
trigger it
+        if (renderTrigger && actions?.updateQueryFormData) {
+          actions.updateQueryFormData({ [field]: val }, false);
+        }
+      } else if (onChange) {
+        onChange(field, val);
+      }
+    };
+
+  // Make sure we have valid values or defaults
+  const formValues = form_data || value || {};
+
+  return (
+    <div style={{ padding: '16px', width: '100%' }}>
+      <div>
+        {/* Query Section */}
+        <div style={{ marginBottom: 24 }}>
+          <h4 style={{ marginBottom: 16 }}>{t('Query')}</h4>
+          <div style={{ marginBottom: 16 }}>
+            <div>
+              <div style={{ marginBottom: '8px' }}>
+                <strong>{t('Group by')}</strong>
+              </div>
+              {safeColumns.length > 0 ? (
+                <DndColumnSelect
+                  value={formValues.groupby || []}
+                  onChange={handleChange('groupby')}
+                  options={safeColumns}
+                  name="groupby"
+                  label={t('Group by')}
+                  multi
+                  canDelete
+                  ghostButtonText={t('Add dimension')}
+                  type="DndColumnSelect"
+                  actions={actions}
+                />
+              ) : (
+                <div
+                  style={{
+                    padding: '10px',
+                    borderRadius: '4px',
+                  }}
+                >
+                  {t('No columns available. Please select a dataset first.')}
+                </div>
+              )}
+            </div>
+          </div>
+
+          <div style={{ marginTop: 16, display: 'flex', gap: 16 }}>
+            <div style={{ flex: 1 }}>
+              <div style={{ marginBottom: '8px' }}>
+                <strong>{t('Metric')}</strong>
+              </div>
+              {safeDataSource && safeDataSource.columns ? (
+                <DndMetricSelect
+                  value={formValues.metric}
+                  onChange={handleChange('metric')}
+                  datasource={safeDataSource}
+                  name="metric"
+                  label={t('Metric')}
+                  multi={false}
+                  savedMetrics={safeMetrics}
+                />
+              ) : (
+                <div
+                  style={{
+                    padding: '10px',
+                    borderRadius: '4px',
+                  }}
+                >
+                  {t('No metrics available.')}
+                </div>
+              )}
+            </div>
+          </div>
+
+          <div style={{ marginTop: 16, display: 'flex', gap: 16 }}>
+            <div style={{ flex: 1 }}>
+              <div style={{ marginBottom: '8px' }}>
+                <strong>{t('Filters')}</strong>
+              </div>
+              {safeDataSource && safeColumns.length > 0 ? (
+                <DndFilterSelect
+                  value={formValues.adhoc_filters || []}
+                  onChange={handleChange('adhoc_filters')}
+                  datasource={safeDataSource}
+                  columns={safeColumns}
+                  formData={formValues}
+                  name="adhoc_filters"
+                  savedMetrics={safeMetrics}
+                  selectedMetrics={[]}
+                  type="DndFilterSelect"
+                  actions={actions}
+                />
+              ) : (
+                <div
+                  style={{
+                    padding: '10px',
+                    borderRadius: '4px',
+                  }}
+                >
+                  {t('No columns available for filtering.')}
+                </div>
+              )}
+            </div>
+          </div>
+
+          <div style={{ marginTop: 16, display: 'flex', gap: 16 }}>
+            <div style={{ flex: 1 }}>
+              <ControlHeader
+                label={t('Row limit')}
+                description={t('Limit the number of rows that are returned')}
+                hovered
+              />
+              <TextControl
+                value={formValues.row_limit}
+                onChange={handleChange('row_limit')}
+                isInt
+                placeholder="10000"
+                controlId="row_limit"
+              />
+            <div style={{ flex: 1 }}>
+              <CheckboxControl
+                label={t('Sort by metric')}
+                description={t(
+                  'Whether to sort results by the selected metric in 
descending order',
+                )}
+                value={formValues.sort_by_metric ?? true}
+                onChange={handleChange('sort_by_metric')}
+                hovered
+              />
+            </div>
+          </div>
+        </div>
+
+        {/* Chart Options */}
+        <div style={{ marginBottom: 24 }}>
+          <h4 style={{ marginBottom: 16 }}>{t('Chart Options')}</h4>
+          <div style={{ display: 'flex', gap: 16 }}>
+            <div style={{ flex: 1 }}>
+              {(() => {
+                const colorSchemeControl = SharedColorSchemeControl();
+                const { hidden, ...cleanConfig } = colorSchemeControl.config 
|| {};
+                return (
+                  <Control
+                    {...cleanConfig}
+                    name="color_scheme"
+                    value={formValues.color_scheme}
+                    actions={{
+                      ...actions,
+                      setControlValue: (field: string, val: any) => {
+                        handleChange('color_scheme', true)(val);
+                      },
+                    }}
+                    renderTrigger
+                    label={t('Color Scheme')}
+                    description={t('Select color scheme for the chart')}
+                  />
+                );
+              })()}
+            </div>
+          </div>
+
+          <div style={{ marginTop: 16, display: 'flex', gap: 16 }}>
+            <div style={{ flex: 1 }}>
+              <ControlHeader
+                label={t('Outer Radius')}
+                description={t('Outer edge of the pie/donut')}
+                renderTrigger
+                hovered
+              />
+              <SliderControl
+                value={formValues.outerRadius || 70}
+                onChange={handleChange('outerRadius', true)}
+                name="outerRadius"
+                renderTrigger
+                {...{ min: 10, max: 100, step: 1 }}
+              />
+            </div>
+          </div>
+
+          <div style={{ marginTop: 16, display: 'flex', gap: 16 }}>
+            <div style={{ flex: 1 }}>
+              <CheckboxControl
+                label={t('Donut')}
+                description={t('Do you want a donut or a pie?')}
+                value={formValues.donut || false}
+                onChange={handleChange('donut', true)}
+                renderTrigger
+                hovered
+              />
+            </div>
+          </div>
+
+          {formValues.donut && (
+            <div style={{ marginTop: 16, display: 'flex', gap: 16 }}>
+              <div style={{ flex: 1 }}>
+                <ControlHeader
+                  label={t('Inner Radius')}
+                  description={t('Inner radius of donut hole')}
+                  renderTrigger
+                  hovered
+                />
+                <SliderControl
+                  value={formValues.innerRadius || 30}
+                  onChange={handleChange('innerRadius', true)}
+                  name="innerRadius"
+                  renderTrigger
+                  {...{ min: 0, max: 100, step: 1 }}
+                />
+            )}
+
+          <div style={{ marginTop: 16, display: 'flex', gap: 16 }}>
+            <div style={{ flex: 1 }}>
+              <CheckboxControl
+                label={t('Show Labels')}
+                description={t('Whether to display labels on the pie slices')}
+                value={formValues.show_labels ?? true}
+                onChange={handleChange('show_labels', true)}
+                renderTrigger
+                hovered
+              />
+            </div>
+          </div>
+
+          {formValues.show_labels && (
+            <>
+              <div style={{ marginTop: 16, display: 'flex', gap: 16 }}>
+                <div style={{ flex: 1 }}>
+                  <CheckboxControl
+                    label={t('Put Labels Outside')}
+                    description={t('Put the labels outside of the pie slices')}
+                    value={formValues.labels_outside ?? true}
+                    onChange={handleChange('labels_outside', true)}
+                    renderTrigger
+                    hovered
+                  />
+
+              <div style={{ marginTop: 16, display: 'flex', gap: 16 }}>
+                <div style={{ flex: 1 }}>
+                  <CheckboxControl
+                    label={t('Label Line')}
+                    description={t('Draw a line from the label to the slice')}
+                    value={formValues.label_line ?? false}
+                    onChange={handleChange('label_line', true)}
+                    renderTrigger
+                    hovered
+                  />
+                  </>
+          )}
+
+          <div style={{ marginTop: 16, display: 'flex', gap: 16 }}>
+            <div style={{ flex: 1 }}>
+              <CheckboxControl
+                label={t('Show Legend')}
+                description={t('Whether to display a legend for the chart')}
+                value={formValues.show_legend ?? true}
+                onChange={handleChange('show_legend', true)}
+                renderTrigger
+                hovered
+              />
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+};
+
+/**
+ * Mark this component as a modern panel so the renderer knows how to handle it
+ */
+(PieControlPanel as any).isModernPanel = true;
+
+console.log(
+  'PieControlPanelSimple.tsx - Component defined, isModernPanel:',
+  (PieControlPanel as any).isModernPanel,
+);
+
+// For now, we need to provide a minimal config structure to prevent errors
+// This is a temporary bridge until the system fully supports pure React panels
+const config = {
+  controlPanelSections: [
+    {
+      label: null,
+      expanded: true,
+      controlSetRows: [[PieControlPanel as any]],
+    },
+  ],
+  // Provide default control overrides to prevent undefined errors
+  controlOverrides: {
+    groupby: {
+      default: [],
+      label: t('Group by'),
+      description: t('Columns to group by'),
+    },
+    metric: {
+      default: null,
+      label: t('Metric'),
+      description: t('Metric to calculate'),
+    },
+    adhoc_filters: {
+      default: [],
+      label: t('Filters'),
+      description: t('Filters to apply'),
+    },
+    row_limit: {
+      default: 100,
+      label: t('Row limit'),
+      description: t('Number of rows to display'),
+    },
+    sort_by_metric: {
+      default: true,
+      label: t('Sort by metric'),
+      description: t('Sort results by metric value'),
+    },
+    color_scheme: {
+      default: 'supersetColors',
+      label: t('Color scheme'),
+      description: t('Color scheme for the chart'),
+      renderTrigger: true,
+    },
+    // Add more control defaults that Pie chart might expect
+    donut: {
+      default: false,
+      label: t('Donut'),
+      renderTrigger: true,
+    },
+    show_labels: {
+      default: true,
+      label: t('Show labels'),
+      renderTrigger: true,
+    },
+    labels_outside: {
+      default: true,
+      label: t('Put labels outside'),
+      renderTrigger: true,
+    },
+    label_type: {
+      default: 'key',
+      label: t('Label type'),
+      renderTrigger: true,
+    },
+    label_line: {
+      default: false,
+      label: t('Label line'),
+      renderTrigger: true,
+    },
+    show_legend: {
+      default: true,
+      label: t('Show legend'),
+      renderTrigger: true,
+    },
+    legendType: {
+      default: 'scroll',
+      label: t('Legend type'),
+      renderTrigger: true,
+    },
+    legendOrientation: {
+      default: 'top',
+      label: t('Legend orientation'),
+      renderTrigger: true,
+    },
+    outerRadius: {
+      default: 70,
+      label: t('Outer radius'),
+      renderTrigger: true,
+    },
+    innerRadius: {
+      default: 30,
+      label: t('Inner radius'),
+      renderTrigger: true,
+    },
+  },
+};
+
+// Export the config with the component embedded
+export default config;
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Pie/index.ts 
b/superset-frontend/plugins/plugin-chart-echarts/src/Pie/index.ts
index 53e0319929..45b608ebc8 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Pie/index.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Pie/index.ts
@@ -18,8 +18,8 @@
  */
 import { Behavior, t } from '@superset-ui/core';
 import buildQuery from './buildQuery';
-// Use the TRUE React component-based control panel
-import controlPanel from './PieControlPanel';
+// Use the simplified TRUE React component-based control panel
+import controlPanel from './PieControlPanelSimple';
 import transformProps from './transformProps';
 import thumbnail from './images/thumbnail.png';
 import example1 from './images/Pie1.jpg';
@@ -44,6 +44,18 @@ export default class EchartsPieChartPlugin extends 
EchartsChartPlugin<
    * (pivoting, rolling aggregations, sorting etc) or submitting multiple 
queries.
    */
   constructor() {
+    console.log(
+      'EchartsPieChartPlugin constructor - controlPanel:',
+      controlPanel,
+    );
+    console.log(
+      'EchartsPieChartPlugin constructor - typeof controlPanel:',
+      typeof controlPanel,
+    );
+    console.log(
+      'EchartsPieChartPlugin constructor - isModernPanel?:',
+      (controlPanel as any)?.isModernPanel,
+    );
     super({
       buildQuery,
       controlPanel,
diff --git 
a/superset-frontend/src/explore/components/ControlPanelsContainer.tsx 
b/superset-frontend/src/explore/components/ControlPanelsContainer.tsx
index 0d50ef93fa..f9ceac3000 100644
--- a/superset-frontend/src/explore/components/ControlPanelsContainer.tsx
+++ b/superset-frontend/src/explore/components/ControlPanelsContainer.tsx
@@ -191,10 +191,33 @@ function getState(
   const querySections: ControlPanelSectionConfig[] = [];
   const customizeSections: ControlPanelSectionConfig[] = [];
 
-  getSectionsToRender(vizType, datasourceType).forEach(section => {
-    // if at least one control in the section is not `renderTrigger`
-    // or asks to be displayed at the Data tab
-    if (
+  console.log(
+    'getState - vizType:',
+    vizType,
+    'datasourceType:',
+    datasourceType,
+  );
+  const sections = getSectionsToRender(vizType, datasourceType);
+  console.log('getState - sections:', sections);
+
+  sections.forEach(section => {
+    // Check if this section contains a modern panel
+    const hasModernPanel = section.controlSetRows.some(rows =>
+      rows.some(
+        control =>
+          typeof control === 'function' && (control as any).isModernPanel,
+      ),
+    );
+
+    if (hasModernPanel) {
+      // Modern panels should show in the data tab
+      console.log(
+        'getState - Found modern panel section, adding to querySections',
+      );
+      querySections.push(section);
+    } else if (
+      // if at least one control in the section is not `renderTrigger`
+      // or asks to be displayed at the Data tab
       section.tabOverride === 'data' ||
       section.controlSetRows.some(rows =>
         rows.some(
@@ -375,15 +398,21 @@ export const ControlPanelsContainer = (props: 
ControlPanelsContainerProps) => {
     expandedCustomizeSections,
     querySections,
     customizeSections,
-  } = useMemo(
-    () =>
-      getState(
-        form_data.viz_type,
-        props.exploreState.datasource,
-        props.datasource_type,
-      ),
-    [props.exploreState.datasource, form_data.viz_type, props.datasource_type],
-  );
+  } = useMemo(() => {
+    console.log(
+      'ControlPanelsContainer - Computing sections for viz_type:',
+      form_data.viz_type,
+    );
+    return getState(
+      form_data.viz_type,
+      props.exploreState.datasource,
+      props.datasource_type,
+    );
+  }, [
+    props.exploreState.datasource,
+    form_data.viz_type,
+    props.datasource_type,
+  ]);
 
   const resetTransferredControls = useCallback(() => {
     ensureIsArray(props.exploreState.controlsTransferred).forEach(controlName 
=>
@@ -531,6 +560,7 @@ export const ControlPanelsContainer = (props: 
ControlPanelsContainerProps) => {
   ) => {
     const { controls } = props;
     const { label, description, visibility } = section;
+    console.log('renderControlPanelSection - section:', section);
 
     // Section label can be a ReactNode but in some places we want to
     // have a string ID. Using forced type conversion for now,
@@ -603,12 +633,37 @@ export const ControlPanelsContainer = (props: 
ControlPanelsContainerProps) => {
         {isVisible && (
           <>
             {section.controlSetRows.map((controlSets, i) => {
+              console.log('Processing controlSetRow', i, ':', controlSets);
               const renderedControls = controlSets
-                .map(controlItem => {
+                .map((controlItem, j) => {
+                  console.log(
+                    `Processing control item [${i}][${j}]:`,
+                    typeof controlItem,
+                    controlItem,
+                  );
                   if (!controlItem) {
                     // When the item is invalid
                     return null;
                   }
+                  // Check if it's a modern panel component (function with 
isModernPanel flag)
+                  if (
+                    typeof controlItem === 'function' &&
+                    (controlItem as any).isModernPanel
+                  ) {
+                    console.log(
+                      'ControlPanelsContainer - Found modern panel in 
controlSetRows!!! 🎉',
+                    );
+                    return (
+                      <ModernControlPanelRenderer
+                        element={controlItem}
+                        formData={props.form_data}
+                        controls={props.controls}
+                        actions={props.actions}
+                        datasource={props.exploreState.datasource}
+                        validationErrors={props.controls}
+                      />
+                    );
+                  }
                   if (isValidElement(controlItem)) {
                     // When the item is a React element
                     // Check if it's a modern control panel
@@ -733,12 +788,20 @@ export const ControlPanelsContainer = (props: 
ControlPanelsContainerProps) => {
   ]);
 
   const controlPanelRegistry = getChartControlPanelRegistry();
+  console.log('ControlPanelsContainer - viz_type:', form_data.viz_type);
+  console.log(
+    'ControlPanelsContainer - controlPanel:',
+    controlPanelRegistry.get(form_data.viz_type),
+  );
   if (!controlPanelRegistry.has(form_data.viz_type) && pluginContext.loading) {
     return <Loading />;
   }
 
   const showCustomizeTab = customizeSections.length > 0;
 
+  console.log('ControlPanelsContainer - querySections:', querySections);
+  console.log('ControlPanelsContainer - customizeSections:', 
customizeSections);
+
   return (
     <Styles ref={containerRef}>
       <Tabs
diff --git 
a/superset-frontend/src/explore/components/ModernControlPanelRenderer.tsx 
b/superset-frontend/src/explore/components/ModernControlPanelRenderer.tsx
index 323d4036c2..afae83d6ac 100644
--- a/superset-frontend/src/explore/components/ModernControlPanelRenderer.tsx
+++ b/superset-frontend/src/explore/components/ModernControlPanelRenderer.tsx
@@ -61,36 +61,69 @@ export const ModernControlPanelRenderer: FC<
   datasource,
   validationErrors,
 }) => {
-  // Check if this is a modern control panel component
-  // Modern panels will have specific prop expectations
-  const elementType = element.type as any;
-  const isModernPanel =
-    element.props &&
-    ('value' in element.props ||
-      'onChange' in element.props ||
-      elementType?.name?.includes('PieControlPanel') ||
-      elementType?.name?.includes('Modern'));
-
-  if (!isModernPanel) {
-    // If it's not a modern panel, render as-is
-    return element;
+  console.log('ModernControlPanelRenderer - element:', element);
+  console.log('ModernControlPanelRenderer - typeof element:', typeof element);
+  console.log(
+    'ModernControlPanelRenderer - isModernPanel?:',
+    (element as any)?.isModernPanel,
+  );
+
+  // Check if this is a modern control panel component constructor (not an 
instance)
+  if (typeof element === 'function' && (element as any).isModernPanel) {
+    console.log('ModernControlPanelRenderer - Rendering modern panel');
+    const ModernPanel = element as FC<ModernControlPanelProps>;
+
+    // Create the modern props for the component
+    const modernProps = {
+      value: formData,
+      onChange: (name: string, value: JsonValue) => {
+        actions.setControlValue(name, value);
+      },
+      datasource,
+      form_data: formData,
+      controls,
+      actions,
+      validationErrors,
+    };
+
+    return <ModernPanel {...modernProps} />;
   }
 
-  // Create the modern props adapter for the new naming convention
-  const modernProps = {
-    value: formData,
-    onChange: (name: string, value: JsonValue) => {
-      actions.setControlValue(name, value);
-    },
-    datasource,
-    controls,
-    formData,
-    actions,
-    validationErrors,
-  };
+  // Check if this is already a React element instance
+  if (isValidElement(element)) {
+    const elementType = element.type as any;
+    const isModernPanel =
+      element.props &&
+      ('value' in element.props ||
+        'onChange' in element.props ||
+        elementType?.name?.includes('PieControlPanel') ||
+        elementType?.name?.includes('Modern') ||
+        elementType?.isModernPanel);
+
+    if (!isModernPanel) {
+      // If it's not a modern panel, render as-is
+      return element;
+    }
+
+    // Create the modern props adapter for the new naming convention
+    const modernProps = {
+      value: formData,
+      onChange: (name: string, value: JsonValue) => {
+        actions.setControlValue(name, value);
+      },
+      datasource,
+      controls,
+      formData,
+      actions,
+      validationErrors,
+    };
+
+    // Clone the element with the modern props
+    return cloneElement(element, modernProps);
+  }
 
-  // Clone the element with the modern props
-  return cloneElement(element, modernProps);
+  // If it's neither a function nor an element, just return it
+  return element as ReactElement;
 };
 
 /**
diff --git a/superset-frontend/src/explore/controlUtils/getControlState.ts 
b/superset-frontend/src/explore/controlUtils/getControlState.ts
index 1fb72e959a..0b4c61a093 100644
--- a/superset-frontend/src/explore/controlUtils/getControlState.ts
+++ b/superset-frontend/src/explore/controlUtils/getControlState.ts
@@ -22,6 +22,7 @@ import {
   ensureIsArray,
   JsonValue,
   QueryFormData,
+  getChartControlPanelRegistry,
 } from '@superset-ui/core';
 import {
   ControlConfig,
@@ -171,16 +172,41 @@ export function getAllControlsState(
   formData: QueryFormData,
 ) {
   const controlsState: Record<string, ControlState<any> | null> = {};
+
+  // Get the control panel config to check for controlOverrides
+  const controlPanelRegistry = getChartControlPanelRegistry();
+  const controlPanel = controlPanelRegistry.get(vizType);
+  const controlOverrides = (controlPanel as any)?.controlOverrides || {};
+
+  // First, apply controlOverrides if they exist (for modern panels with 
default values)
+  Object.entries(controlOverrides).forEach(([name, config]: [string, any]) => {
+    console.log('getAllControlsState - Processing override for:', name);
+    controlsState[name] = getControlStateFromControlConfig(
+      config as ControlConfig<any>,
+      state,
+      formData[name],
+    );
+  });
+
+  // Then process regular controls from sections
   getSectionsToRender(vizType, datasourceType).forEach(section =>
     section.controlSetRows.forEach(fieldsetRow =>
       fieldsetRow.forEach((field: CustomControlItem) => {
+        // Skip modern panel components - they manage their own state
+        if (typeof field === 'function' && (field as any).isModernPanel) {
+          console.log('getAllControlsState - Skipping modern panel component');
+          return;
+        }
         if (field?.config && field.name) {
           const { config, name } = field;
-          controlsState[name] = getControlStateFromControlConfig(
-            config,
-            state,
-            formData[name],
-          );
+          // Only add if not already in controlsState from overrides
+          if (!controlsState[name]) {
+            controlsState[name] = getControlStateFromControlConfig(
+              config,
+              state,
+              formData[name],
+            );
+          }
         }
       }),
     ),
diff --git a/superset-frontend/src/explore/controlUtils/getSectionsToRender.ts 
b/superset-frontend/src/explore/controlUtils/getSectionsToRender.ts
index 5913a7df09..b1cd44fcdc 100644
--- a/superset-frontend/src/explore/controlUtils/getSectionsToRender.ts
+++ b/superset-frontend/src/explore/controlUtils/getSectionsToRender.ts
@@ -84,8 +84,51 @@ export function getSectionsToRender(
   vizType: string,
   datasourceType: DatasourceType,
 ) {
-  const controlPanelConfig =
-    // TODO: update `chartControlPanelRegistry` type to use ControlPanelConfig
-    (getChartControlPanelRegistry().get(vizType) as ControlPanelConfig) || {};
+  const controlPanel = getChartControlPanelRegistry().get(vizType);
+  console.log('getSectionsToRender - vizType:', vizType);
+  console.log('getSectionsToRender - controlPanel:', controlPanel);
+
+  // Check if the control panel has our modern component embedded
+  if (controlPanel && controlPanel.controlPanelSections) {
+    const firstSection = controlPanel.controlPanelSections[0];
+    if (
+      firstSection &&
+      firstSection.controlSetRows &&
+      firstSection.controlSetRows[0]
+    ) {
+      const firstControl = firstSection.controlSetRows[0][0];
+      console.log('First control in panel:', typeof firstControl, 
firstControl);
+      if (
+        typeof firstControl === 'function' &&
+        (firstControl as any).isModernPanel
+      ) {
+        console.log('Found embedded modern panel! 🎉');
+        // Return the existing structure which already has our modern panel
+        return getMemoizedSectionsToRender(
+          datasourceType,
+          controlPanel as ControlPanelConfig,
+        );
+      }
+    }
+  }
+
+  // Check if this is a modern React component at the top level
+  if (
+    typeof controlPanel === 'function' &&
+    (controlPanel as any).isModernPanel
+  ) {
+    // For modern panels, return a single section containing the component
+    console.log('Returning modern panel section (top level)');
+    return [
+      {
+        label: null,
+        expanded: true,
+        controlSetRows: [[controlPanel as any]],
+      },
+    ];
+  }
+
+  // Otherwise, treat it as a traditional ControlPanelConfig
+  const controlPanelConfig = (controlPanel as ControlPanelConfig) || {};
   return getMemoizedSectionsToRender(datasourceType, controlPanelConfig);
 }

Reply via email to