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

maximebeauchemin pushed a commit to branch default_chart_settings
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 5a31a1b2da96c907d4864ff10e2d848458d68762
Author: Maxime Beauchemin <maximebeauche...@gmail.com>
AuthorDate: Sun Aug 3 21:42:47 2025 -0700

    feat(datasets): Replace chart defaults with Explore controls
    
    - Replace time range Select with DateFilterControl for rich time selection 
UI
    - Make time grains database-aware, fetching options from database engine
    - Add AdhocFilterControl for setting default filters on datasets
    - Update hydrateExplore to apply default filters for new charts
    
    These changes give users the same powerful controls they use in Explore
    when setting chart defaults, ensuring consistency and familiarity.
    
    🤖 Generated with [Claude Code](https://claude.ai/code)
    
    Co-Authored-By: Claude <nore...@anthropic.com>
---
 .../src/components/Datasource/DatasourceEditor.jsx | 195 ++++++++-------------
 .../src/components/Datasource/DatasourceModal.tsx  |  24 +--
 .../src/explore/actions/hydrateExplore.ts          |   8 +
 3 files changed, 87 insertions(+), 140 deletions(-)

diff --git a/superset-frontend/src/components/Datasource/DatasourceEditor.jsx 
b/superset-frontend/src/components/Datasource/DatasourceEditor.jsx
index 31ad3a7b89..dd5c18337b 100644
--- a/superset-frontend/src/components/Datasource/DatasourceEditor.jsx
+++ b/superset-frontend/src/components/Datasource/DatasourceEditor.jsx
@@ -70,6 +70,8 @@ import {
   resetDatabaseState,
 } from 'src/database/actions';
 import Mousetrap from 'mousetrap';
+import DateFilterLabel from 
'src/explore/components/controls/DateFilterControl/DateFilterLabel';
+import AdhocFilterControl from 
'src/explore/components/controls/FilterControl/AdhocFilterControl';
 import { DatabaseSelector } from '../DatabaseSelector';
 import CollectionTable from './CollectionTable';
 import Fieldset from './Fieldset';
@@ -80,15 +82,11 @@ const extensionsRegistry = getExtensionsRegistry();
 
 // Helper function to safely parse extra field
 const parseExtra = extra => {
-  console.log('parseExtra called with:', extra, typeof extra);
   if (!extra) return {};
   if (typeof extra === 'object') return extra;
   try {
-    const parsed = JSON.parse(extra);
-    console.log('parseExtra parsed JSON:', parsed);
-    return parsed;
+    return JSON.parse(extra);
   } catch {
-    console.log('parseExtra failed to parse JSON, returning empty object');
     return {};
   }
 };
@@ -636,11 +634,6 @@ const ResultTable =
 class DatasourceEditor extends PureComponent {
   constructor(props) {
     super(props);
-    console.log(
-      'DatasourceEditor constructor - props.datasource.extra:',
-      props.datasource.extra,
-      typeof props.datasource.extra,
-    );
     this.state = {
       datasource: {
         ...props.datasource,
@@ -707,10 +700,6 @@ class DatasourceEditor extends PureComponent {
     // Currently the logic to know whether the source is
     // physical or virtual is based on whether SQL is empty or not.
     const { datasourceType, datasource } = this.state;
-    console.log(
-      'onChange() - this.state.datasource.extra:',
-      this.state.datasource.extra,
-    );
     const sql =
       datasourceType === DATASOURCE_TYPES.physical.key ? '' : datasource.sql;
     const newDatasource = {
@@ -718,7 +707,6 @@ class DatasourceEditor extends PureComponent {
       sql,
       columns: [...this.state.databaseColumns, 
...this.state.calculatedColumns],
     };
-    console.log('onChange() - newDatasource.extra:', newDatasource.extra);
     this.props.onChange(newDatasource, this.state.errors);
   }
 
@@ -728,43 +716,30 @@ class DatasourceEditor extends PureComponent {
   }
 
   onDatasourceChange(datasource, callback = this.validateAndChange) {
-    console.log(
-      'onDatasourceChange sending to modal - datasource.extra:',
-      datasource.extra,
-    );
     this.setState({ datasource }, callback);
   }
 
   onDatasourcePropChange(attr, value) {
-    console.log(`onDatasourcePropChange: ${attr} =`, value);
     if (value === undefined) return; // if value is undefined do not update 
state
     const datasource = { ...this.state.datasource, [attr]: value };
     this.setState(
       prevState => ({
         datasource: { ...prevState.datasource, [attr]: value },
       }),
-      () => {
-        console.log('Updated datasource.extra:', this.state.datasource.extra);
-        if (attr === 'table_name') {
-          this.onDatasourceChange(datasource, this.tableChangeAndSyncMetadata);
-        } else {
-          this.onDatasourceChange(datasource, this.validateAndChange);
-        }
-      },
+      attr === 'table_name'
+        ? this.onDatasourceChange(datasource, this.tableChangeAndSyncMetadata)
+        : this.onDatasourceChange(datasource, this.validateAndChange),
     );
   }
 
   // Helper method to update chart defaults in extra field
   onChartDefaultChange(defaultKey, value) {
-    console.log(`onChartDefaultChange: ${defaultKey} =`, value);
     const extra = { ...parseExtra(this.state.datasource.extra) };
     if (!extra.default_chart_metadata) {
       extra.default_chart_metadata = {};
     }
     extra.default_chart_metadata[defaultKey] = value;
-    const stringified = JSON.stringify(extra);
-    console.log('onChartDefaultChange - saving extra as:', stringified);
-    this.onDatasourcePropChange('extra', stringified);
+    this.onDatasourcePropChange('extra', JSON.stringify(extra));
   }
 
   onDatasourceTypeChange(datasourceType) {
@@ -1106,10 +1081,9 @@ class DatasourceEditor extends PureComponent {
               parseExtra(datasource.extra).default_chart_metadata
                 ?.default_metric
             }
-            onChange={(fieldKey, value) => {
-              console.log('Field onChange called with:', fieldKey, value);
-              this.onChartDefaultChange('default_metric', value);
-            }}
+            onChange={(fieldKey, value) =>
+              this.onChartDefaultChange('default_metric', value)
+            }
             control={
               <Select
                 name="default_metric"
@@ -1130,6 +1104,13 @@ class DatasourceEditor extends PureComponent {
             description={t(
               'Pre-populate this dimension/groupby when creating new charts 
from this dataset',
             )}
+            value={
+              parseExtra(datasource.extra).default_chart_metadata
+                ?.default_dimension
+            }
+            onChange={(fieldKey, value) =>
+              this.onChartDefaultChange('default_dimension', value)
+            }
             control={
               <Select
                 name="default_dimension"
@@ -1141,18 +1122,6 @@ class DatasourceEditor extends PureComponent {
                       label: column.verbose_name || column.column_name,
                     })) || []
                 }
-                value={
-                  parseExtra(datasource.extra).default_chart_metadata
-                    ?.default_dimension
-                }
-                onChange={value => {
-                  const extra = { ...parseExtra(datasource.extra) };
-                  if (!extra.default_chart_metadata) {
-                    extra.default_chart_metadata = {};
-                  }
-                  extra.default_chart_metadata.default_dimension = value;
-                  this.onDatasourcePropChange('extra', JSON.stringify(extra));
-                }}
                 placeholder={t('Select a dimension')}
                 allowClear
               />
@@ -1162,103 +1131,93 @@ class DatasourceEditor extends PureComponent {
             fieldKey="default_time_grain"
             label={t('Default Time Grain')}
             description={t(
-              'Pre-populate this time grain when creating new charts from this 
dataset',
+              'Pre-populate this time grain when creating new charts from this 
dataset. ' +
+                'Options are database-specific.',
             )}
+            value={
+              parseExtra(datasource.extra).default_chart_metadata
+                ?.default_time_grain
+            }
+            onChange={(fieldKey, value) =>
+              this.onChartDefaultChange('default_time_grain', value)
+            }
             control={
               <Select
                 name="default_time_grain"
-                options={[
-                  { value: 'PT1S', label: t('Second') },
-                  { value: 'PT1M', label: t('Minute') },
-                  { value: 'PT5M', label: t('5 Minutes') },
-                  { value: 'PT10M', label: t('10 Minutes') },
-                  { value: 'PT15M', label: t('15 Minutes') },
-                  { value: 'PT30M', label: t('30 Minutes') },
-                  { value: 'PT1H', label: t('Hour') },
-                  { value: 'P1D', label: t('Day') },
-                  { value: 'P1W', label: t('Week') },
-                  { value: 'P1M', label: t('Month') },
-                  { value: 'P1Y', label: t('Year') },
-                ]}
-                value={
-                  parseExtra(datasource.extra).default_chart_metadata
-                    ?.default_time_grain
+                options={
+                  datasource.time_grain_sqla?.map(([value, label]) => ({
+                    value,
+                    label,
+                  })) || []
                 }
-                onChange={value => {
-                  const extra = { ...parseExtra(datasource.extra) };
-                  if (!extra.default_chart_metadata) {
-                    extra.default_chart_metadata = {};
-                  }
-                  extra.default_chart_metadata.default_time_grain = value;
-                  this.onDatasourcePropChange('extra', JSON.stringify(extra));
-                }}
                 placeholder={t('Select a time grain')}
                 allowClear
               />
             }
           />
-          <Field
-            fieldKey="default_time_range"
+          <Form.Item
             label={t('Default Time Range')}
-            description={t(
+            extra={t(
               'Pre-populate this time range when creating new charts from this 
dataset',
             )}
-            control={
-              <Select
-                name="default_time_range"
-                options={[
-                  { value: 'Last day', label: t('Last day') },
-                  { value: 'Last 7 days', label: t('Last 7 days') },
-                  { value: 'Last 30 days', label: t('Last 30 days') },
-                  { value: 'Last 90 days', label: t('Last 90 days') },
-                  { value: 'Last year', label: t('Last year') },
-                  { value: 'No filter', label: t('No filter') },
-                ]}
-                value={
-                  parseExtra(datasource.extra).default_chart_metadata
-                    ?.default_time_range
-                }
-                onChange={value => {
-                  const extra = { ...parseExtra(datasource.extra) };
-                  if (!extra.default_chart_metadata) {
-                    extra.default_chart_metadata = {};
-                  }
-                  extra.default_chart_metadata.default_time_range = value;
-                  this.onDatasourcePropChange('extra', JSON.stringify(extra));
-                }}
-                placeholder={t('Select a time range')}
-                allowClear
-              />
-            }
-          />
+          >
+            <DateFilterLabel
+              name="default_time_range"
+              onChange={value =>
+                this.onChartDefaultChange('default_time_range', value)
+              }
+              value={
+                parseExtra(datasource.extra).default_chart_metadata
+                  ?.default_time_range || 'No filter'
+              }
+              overlayStyle="Modal"
+            />
+          </Form.Item>
           <Field
             fieldKey="default_row_limit"
             label={t('Default Row Limit')}
             description={t(
               'Pre-populate this row limit when creating new charts from this 
dataset',
             )}
+            value={
+              parseExtra(datasource.extra).default_chart_metadata
+                ?.default_row_limit
+            }
+            onChange={(fieldKey, value) =>
+              this.onChartDefaultChange(
+                'default_row_limit',
+                value ? parseInt(value, 10) : null,
+              )
+            }
             control={
               <TextControl
                 name="default_row_limit"
-                value={
-                  parseExtra(datasource.extra).default_chart_metadata
-                    ?.default_row_limit
-                }
-                onChange={value => {
-                  const extra = { ...parseExtra(datasource.extra) };
-                  if (!extra.default_chart_metadata) {
-                    extra.default_chart_metadata = {};
-                  }
-                  extra.default_chart_metadata.default_row_limit = value
-                    ? parseInt(value, 10)
-                    : null;
-                  this.onDatasourcePropChange('extra', JSON.stringify(extra));
-                }}
                 placeholder={t('e.g., 1000')}
                 type="number"
               />
             }
           />
+          <Form.Item
+            label={t('Default Filters')}
+            extra={t(
+              'Pre-populate these filters when creating new charts from this 
dataset',
+            )}
+            style={{ marginTop: '16px' }}
+          >
+            <AdhocFilterControl
+              name="default_filters"
+              onChange={filters =>
+                this.onChartDefaultChange('default_filters', filters)
+              }
+              value={
+                parseExtra(datasource.extra).default_chart_metadata
+                  ?.default_filters || []
+              }
+              datasource={datasource}
+              columns={datasource.columns}
+              savedMetrics={datasource.metrics}
+            />
+          </Form.Item>
         </Form.Item>
       </>
     );
diff --git a/superset-frontend/src/components/Datasource/DatasourceModal.tsx 
b/superset-frontend/src/components/Datasource/DatasourceModal.tsx
index 3556830b16..cb78a538e4 100644
--- a/superset-frontend/src/components/Datasource/DatasourceModal.tsx
+++ b/superset-frontend/src/components/Datasource/DatasourceModal.tsx
@@ -106,11 +106,6 @@ const DatasourceModal: 
FunctionComponent<DatasourceModalProps> = ({
   const dialog = useRef<any>(null);
   const [modal, contextHolder] = Modal.useModal();
   const buildPayload = (datasource: Record<string, any>) => {
-    console.log(
-      'buildPayload - datasource.extra:',
-      datasource.extra,
-      typeof datasource.extra,
-    );
     const payload: Record<string, any> = {
       table_name: datasource.table_name,
       database_id: datasource.database?.id,
@@ -187,10 +182,6 @@ const DatasourceModal: 
FunctionComponent<DatasourceModalProps> = ({
   };
   const onConfirmSave = async () => {
     // Pull out extra fields into the extra object
-    console.log(
-      'Before save - currentDatasource.extra:',
-      currentDatasource.extra,
-    );
     setIsSaving(true);
     const overrideColumns = datasource.sql !== currentDatasource.sql;
     try {
@@ -202,11 +193,6 @@ const DatasourceModal: 
FunctionComponent<DatasourceModalProps> = ({
       const { json } = await SupersetClient.get({
         endpoint: `/api/v1/dataset/${currentDatasource?.id}`,
       });
-      console.log(
-        'After save GET - json.result.extra:',
-        json.result.extra,
-        typeof json.result.extra,
-      );
       addSuccessToast(t('The dataset has been saved'));
       // eslint-disable-next-line no-param-reassign
       json.result.type = 'table';
@@ -242,19 +228,13 @@ const DatasourceModal: 
FunctionComponent<DatasourceModalProps> = ({
   };
 
   const onDatasourceChange = (data: DatasetObject, err: Array<any>) => {
-    console.log('DatasourceModal received - data.extra:', data.extra);
-    const newDatasource = {
+    setCurrentDatasource({
       ...data,
       metrics: data?.metrics.map((metric: DatasetObject['metrics'][0]) => ({
         ...metric,
         is_certified: metric?.certified_by || metric?.certification_details,
       })),
-    };
-    console.log(
-      'DatasourceModal setting currentDatasource.extra to:',
-      newDatasource.extra,
-    );
-    setCurrentDatasource(newDatasource);
+    });
     setErrors(err);
   };
 
diff --git a/superset-frontend/src/explore/actions/hydrateExplore.ts 
b/superset-frontend/src/explore/actions/hydrateExplore.ts
index 56eea7f745..d1e4e7b494 100644
--- a/superset-frontend/src/explore/actions/hydrateExplore.ts
+++ b/superset-frontend/src/explore/actions/hydrateExplore.ts
@@ -133,6 +133,14 @@ export const hydrateExplore =
         if (chartDefaults.default_row_limit && !initialFormData.row_limit) {
           initialFormData.row_limit = chartDefaults.default_row_limit;
         }
+
+        // Apply default filters
+        if (
+          chartDefaults.default_filters?.length &&
+          !initialFormData.adhoc_filters?.length
+        ) {
+          initialFormData.adhoc_filters = chartDefaults.default_filters;
+        }
       } catch (error) {
         // Silently ignore JSON parsing errors - defaults will not be applied
         console.warn('Failed to parse dataset chart defaults:', error);

Reply via email to