From 2285ebe72ec4edded6d195052740b7f9f13d1f1b Mon Sep 17 00:00:00 2001 From: Cody Leff Date: Wed, 14 Sep 2022 14:41:47 -0400 Subject: [PATCH] fix(explore): Prevent shared controls from checking feature flags outside React render (#21315) --- .../superset-ui-chart-controls/src/index.ts | 6 +- .../src/shared-controls/dndControls.tsx | 167 +++++++--- .../src/shared-controls/index.tsx | 298 +++--------------- .../superset-ui-chart-controls/src/types.ts | 4 +- .../src/utils/expandControlConfig.tsx | 2 +- .../src/controlPanel.tsx | 4 +- .../src/controlPanel.ts | 26 +- .../src/controlPanel.ts | 4 +- .../src/utilities/sharedDndControls.jsx | 6 +- .../src/Bar/controlPanel.ts | 2 +- .../Timeseries/Regular/Bar/controlPanel.tsx | 2 +- .../src/plugin/controls/columns.tsx | 4 +- .../src/plugin/controls/metrics.tsx | 2 +- .../plugin-chart-table/src/controlPanel.tsx | 54 +--- .../DndColumnSelect.test.tsx | 15 +- .../DndColumnSelect.tsx | 80 +++-- .../DndFilterSelect.test.tsx | 10 +- .../DndFilterSelect.tsx | 12 +- .../DndMetricSelect.test.tsx | 9 + .../DndMetricSelect.tsx | 12 +- .../controls/DndColumnSelectControl/types.ts | 1 + 21 files changed, 285 insertions(+), 435 deletions(-) diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/index.ts b/superset-frontend/packages/superset-ui-chart-controls/src/index.ts index 8a151d10e2a4..5ed1768e6983 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/src/index.ts +++ b/superset-frontend/packages/superset-ui-chart-controls/src/index.ts @@ -31,11 +31,7 @@ export * from './components/ColumnTypeLabel/ColumnTypeLabel'; export * from './components/MetricOption'; // React control components -export { - sharedControls, - dndEntity, - dndColumnsControl, -} from './shared-controls'; +export { default as sharedControls, withDndFallback } from './shared-controls'; export { default as sharedControlComponents } from './shared-controls/components'; export { legacySortBy } from './shared-controls/legacySortBy'; export * from './shared-controls/emitFilterControl'; diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/dndControls.tsx b/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/dndControls.tsx index 63aa64bf461c..679ac940a5be 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/dndControls.tsx +++ b/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/dndControls.tsx @@ -17,6 +17,7 @@ * specific language governing permissions and limitations * under the License. */ +import React, { useMemo } from 'react'; import { FeatureFlag, isFeatureEnabled, @@ -25,43 +26,83 @@ import { t, validateNonEmpty, } from '@superset-ui/core'; -import { ExtraControlProps, SharedControlConfig, Dataset } from '../types'; +import { + ExtraControlProps, + SharedControlConfig, + Dataset, + Metric, +} from '../types'; import { DATASET_TIME_COLUMN_OPTION, TIME_FILTER_LABELS } from '../constants'; -import { QUERY_TIME_COLUMN_OPTION, defineSavedMetrics } from '..'; +import { + QUERY_TIME_COLUMN_OPTION, + defineSavedMetrics, + ColumnOption, + ColumnMeta, + FilterOption, +} from '..'; import { xAxisControlConfig } from './constants'; -export const dndGroupByControl: SharedControlConfig<'DndColumnSelect'> = { +type Control = { + savedMetrics?: Metric[] | null; + default?: unknown; +}; + +/* + * Note: Previous to the commit that introduced this comment, the shared controls module + * would check feature flags at module execution time and expose a different control + * configuration (component + props) depending on the status of drag-and-drop feature + * flags. This commit combines those configs, merging the required props for both the + * drag-and-drop and non-drag-and-drop components, and renders a wrapper component that + * checks feature flags at component render time to avoid race conditions between when + * feature flags are set and when they're checked. + */ + +export const dndGroupByControl: SharedControlConfig< + 'DndColumnSelect' | 'SelectControl', + ColumnMeta +> = { type: 'DndColumnSelect', label: t('Dimensions'), + multi: true, + freeForm: true, + clearable: true, default: [], + includeTime: false, description: t( - 'One or many columns to group by. High cardinality groupings should include a series limit ' + - 'to limit the number of fetched and rendered series.', + 'One or many columns to group by. High cardinality groupings should include a sort by metric ' + + 'and series limit to limit the number of fetched and rendered series.', ), - mapStateToProps(state, { includeTime }) { + optionRenderer: (c: ColumnMeta) => , + valueRenderer: (c: ColumnMeta) => , + valueKey: 'column_name', + allowAll: true, + filterOption: ({ data: opt }: FilterOption, text: string) => + (opt.column_name && + opt.column_name.toLowerCase().includes(text.toLowerCase())) || + (opt.verbose_name && + opt.verbose_name.toLowerCase().includes(text.toLowerCase())) || + false, + promptTextCreator: (label: unknown) => label, + mapStateToProps(state, controlState) { const newState: ExtraControlProps = {}; const { datasource } = state; if (datasource?.columns[0]?.hasOwnProperty('groupby')) { const options = (datasource as Dataset).columns.filter(c => c.groupby); - if (includeTime) { + if (controlState?.includeTime) { options.unshift(DATASET_TIME_COLUMN_OPTION); } - newState.options = Object.fromEntries( - options.map(option => [option.column_name, option]), - ); + newState.options = options; newState.savedMetrics = (datasource as Dataset).metrics || []; } else { - const options = datasource?.columns; - if (includeTime) { - (options as QueryColumn[])?.unshift(QUERY_TIME_COLUMN_OPTION); + const options = (datasource?.columns as QueryColumn[]) || []; + if (controlState?.includeTime) { + options.unshift(QUERY_TIME_COLUMN_OPTION); } - newState.options = Object.fromEntries( - (options as QueryColumn[])?.map(option => [option.name, option]), - ); - newState.options = datasource?.columns; + newState.options = options; } return newState; }, + commaChoosesOption: false, }; export const dndColumnsControl: typeof dndGroupByControl = { @@ -70,7 +111,7 @@ export const dndColumnsControl: typeof dndGroupByControl = { description: t('One or many columns to pivot as columns'), }; -export const dndSeries: typeof dndGroupByControl = { +export const dndSeriesControl: typeof dndGroupByControl = { ...dndGroupByControl, label: t('Dimension'), multi: false, @@ -82,7 +123,7 @@ export const dndSeries: typeof dndGroupByControl = { ), }; -export const dndEntity: typeof dndGroupByControl = { +export const dndEntityControl: typeof dndGroupByControl = { ...dndGroupByControl, label: t('Entity'), default: null, @@ -91,7 +132,9 @@ export const dndEntity: typeof dndGroupByControl = { description: t('This defines the element to be plotted on the chart'), }; -export const dnd_adhoc_filters: SharedControlConfig<'DndFilterSelect'> = { +export const dndAdhocFilterControl: SharedControlConfig< + 'DndFilterSelect' | 'AdhocFilterControl' +> = { type: 'DndFilterSelect', label: t('Filters'), default: [], @@ -109,7 +152,9 @@ export const dnd_adhoc_filters: SharedControlConfig<'DndFilterSelect'> = { provideFormDataToProps: true, }; -export const dnd_adhoc_metrics: SharedControlConfig<'DndMetricSelect'> = { +export const dndAdhocMetricsControl: SharedControlConfig< + 'DndMetricSelect' | 'MetricsControl' +> = { type: 'DndMetricSelect', multi: true, label: t('Metrics'), @@ -123,20 +168,23 @@ export const dnd_adhoc_metrics: SharedControlConfig<'DndMetricSelect'> = { description: t('One or many metrics to display'), }; -export const dnd_adhoc_metric: SharedControlConfig<'DndMetricSelect'> = { - ...dnd_adhoc_metrics, +export const dndAdhocMetricControl: typeof dndAdhocMetricsControl = { + ...dndAdhocMetricsControl, multi: false, label: t('Metric'), description: t('Metric'), }; -export const dnd_adhoc_metric_2: SharedControlConfig<'DndMetricSelect'> = { - ...dnd_adhoc_metric, +export const dndAdhocMetricControl2: typeof dndAdhocMetricControl = { + ...dndAdhocMetricControl, label: t('Right Axis Metric'), + clearable: true, description: t('Choose a metric for right axis'), }; -export const dnd_sort_by: SharedControlConfig<'DndMetricSelect'> = { +export const dndSortByControl: SharedControlConfig< + 'DndMetricSelect' | 'MetricsControl' +> = { type: 'DndMetricSelect', label: t('Sort by'), default: null, @@ -152,33 +200,37 @@ export const dnd_sort_by: SharedControlConfig<'DndMetricSelect'> = { }), }; -export const dnd_size: SharedControlConfig<'DndMetricSelect'> = { - ...dnd_adhoc_metric, +export const dndSizeControl: typeof dndAdhocMetricControl = { + ...dndAdhocMetricControl, label: t('Bubble Size'), description: t('Metric used to calculate bubble size'), + default: null, }; -export const dnd_x: SharedControlConfig<'DndMetricSelect'> = { - ...dnd_adhoc_metric, +export const dndXControl: typeof dndAdhocMetricControl = { + ...dndAdhocMetricControl, label: t('X Axis'), description: t('Metric assigned to the [X] axis'), + default: null, }; -export const dnd_y: SharedControlConfig<'DndMetricSelect'> = { - ...dnd_adhoc_metric, +export const dndYControl: typeof dndAdhocMetricControl = { + ...dndAdhocMetricControl, label: t('Y Axis'), description: t('Metric assigned to the [Y] axis'), + default: null, }; -export const dnd_secondary_metric: SharedControlConfig<'DndMetricSelect'> = { - ...dnd_adhoc_metric, +export const dndSecondaryMetricControl: typeof dndAdhocMetricControl = { + ...dndAdhocMetricControl, label: t('Color Metric'), + default: null, validators: [], description: t('A metric to use for color'), }; -export const dnd_granularity_sqla: typeof dndGroupByControl = { - ...dndSeries, +export const dndGranularitySqlaControl: typeof dndSeriesControl = { + ...dndSeriesControl, label: TIME_FILTER_LABELS.granularity_sqla, description: t( 'The time column for the visualization. Note that you ' + @@ -187,21 +239,20 @@ export const dnd_granularity_sqla: typeof dndGroupByControl = { 'filter below is applied against this column or ' + 'expression', ), + default: (c: Control) => c.default, + clearable: false, canDelete: false, - ghostButtonText: t( - isFeatureEnabled(FeatureFlag.ENABLE_DND_WITH_CLICK_UX) - ? 'Drop a temporal column here or click' - : 'Drop temporal column here', - ), + ghostButtonText: t('Drop temporal column here'), + clickEnabledGhostButtonText: t('Drop a temporal column here or click'), + optionRenderer: (c: ColumnMeta) => , + valueRenderer: (c: ColumnMeta) => , + valueKey: 'column_name', mapStateToProps: ({ datasource }) => { if (datasource?.columns[0]?.hasOwnProperty('column_name')) { const temporalColumns = (datasource as Dataset)?.columns?.filter(c => c.is_dttm) ?? []; - const options = Object.fromEntries( - temporalColumns.map(option => [option.column_name, option]), - ); return { - options, + options: temporalColumns, default: (datasource as Dataset)?.main_dttm_col || temporalColumns[0]?.column_name || @@ -209,22 +260,36 @@ export const dnd_granularity_sqla: typeof dndGroupByControl = { isTemporal: true, }; } - const sortedQueryColumns = (datasource as QueryResponse)?.columns?.sort( query => (query?.is_dttm ? -1 : 1), ); - const options = Object.fromEntries( - sortedQueryColumns.map(option => [option.name, option]), - ); return { - options, + options: sortedQueryColumns, default: sortedQueryColumns[0]?.name || null, isTemporal: true, }; }, }; -export const dnd_x_axis: SharedControlConfig<'DndColumnSelect'> = { +export const dndXAxisControl: typeof dndGroupByControl = { ...dndGroupByControl, ...xAxisControlConfig, }; + +export function withDndFallback( + DndComponent: React.ComponentType, + FallbackComponent: React.ComponentType, +) { + return function DndControl(props: any) { + const enableExploreDnd = useMemo( + () => isFeatureEnabled(FeatureFlag.ENABLE_EXPLORE_DRAG_AND_DROP), + [], + ); + + return enableExploreDnd ? ( + + ) : ( + + ); + }; +} diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/index.tsx b/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/index.tsx index d46606e6bee7..a8a5bea37d69 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/index.tsx +++ b/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/index.tsx @@ -33,7 +33,6 @@ * here's a list of the keys that are common to all controls, and as a result define the * control interface. */ -import React from 'react'; import { isEmpty } from 'lodash'; import { FeatureFlag, @@ -43,10 +42,7 @@ import { isFeatureEnabled, SequentialScheme, legacyValidateInteger, - validateNonEmpty, ComparisionType, - QueryResponse, - QueryColumn, isAdhocColumn, isPhysicalColumn, } from '@superset-ui/core'; @@ -59,38 +55,29 @@ import { D3_TIME_FORMAT_DOCS, DEFAULT_TIME_FORMAT, DEFAULT_NUMBER_FORMAT, - defineSavedMetrics, } from '../utils'; -import { TIME_FILTER_LABELS, DATASET_TIME_COLUMN_OPTION } from '../constants'; -import { - Metric, - SharedControlConfig, - ColumnMeta, - ExtraControlProps, - SelectControlConfig, - Dataset, -} from '../types'; -import { ColumnOption } from '../components/ColumnOption'; +import { TIME_FILTER_LABELS } from '../constants'; +import { SharedControlConfig, Dataset } from '../types'; import { - dnd_adhoc_filters, - dnd_adhoc_metric, - dnd_adhoc_metrics, - dnd_granularity_sqla, - dnd_sort_by, - dnd_secondary_metric, - dnd_size, - dnd_x, - dnd_y, + dndAdhocFilterControl, + dndAdhocMetricControl, + dndAdhocMetricsControl, + dndGranularitySqlaControl, + dndSortByControl, + dndSecondaryMetricControl, + dndSizeControl, + dndXControl, + dndYControl, dndColumnsControl, - dndEntity, + dndEntityControl, dndGroupByControl, - dndSeries, - dnd_adhoc_metric_2, - dnd_x_axis, + dndSeriesControl, + dndAdhocMetricControl2, + dndXAxisControl, } from './dndControls'; -import { QUERY_TIME_COLUMN_OPTION } from '..'; -import { xAxisControlConfig } from './constants'; + +export { withDndFallback } from './dndControls'; const categoricalSchemeRegistry = getCategoricalSchemeRegistry(); const sequentialSchemeRegistry = getSequentialSchemeRegistry(); @@ -105,77 +92,11 @@ const { user } = JSON.parse( appContainer?.getAttribute('data-bootstrap') || '{}', ); -type Control = { - savedMetrics?: Metric[] | null; - default?: unknown; -}; - type SelectDefaultOption = { label: string; value: string; }; -const groupByControl: SharedControlConfig<'SelectControl', ColumnMeta> = { - type: 'SelectControl', - label: t('Dimensions'), - multi: true, - freeForm: true, - clearable: true, - default: [], - includeTime: false, - description: t( - 'One or many columns to group by. High cardinality groupings should include a sort by metric ' + - 'and series limit to limit the number of fetched and rendered series.', - ), - optionRenderer: c => , - valueRenderer: c => , - valueKey: 'column_name', - allowAll: true, - filterOption: ({ data: opt }, text: string) => - (opt.column_name && - opt.column_name.toLowerCase().includes(text.toLowerCase())) || - (opt.verbose_name && - opt.verbose_name.toLowerCase().includes(text.toLowerCase())) || - false, - promptTextCreator: (label: unknown) => label, - mapStateToProps(state, { includeTime }) { - const newState: ExtraControlProps = {}; - const { datasource } = state; - if (datasource?.columns[0]?.hasOwnProperty('groupby')) { - const options = (datasource as Dataset).columns.filter(c => c.groupby); - if (includeTime) options.unshift(DATASET_TIME_COLUMN_OPTION); - newState.options = options; - } else { - const options = (datasource as QueryResponse).columns; - if (includeTime) options.unshift(QUERY_TIME_COLUMN_OPTION); - newState.options = options; - } - return newState; - }, - commaChoosesOption: false, -}; - -const metrics: SharedControlConfig<'MetricsControl'> = { - type: 'MetricsControl', - multi: true, - label: t('Metrics'), - validators: [validateNonEmpty], - mapStateToProps: ({ datasource }) => ({ - columns: datasource?.columns || [], - savedMetrics: defineSavedMetrics(datasource), - datasource, - datasourceType: datasource?.type, - }), - description: t('One or many metrics to display'), -}; - -const metric: SharedControlConfig<'MetricsControl'> = { - ...metrics, - multi: false, - label: t('Metric'), - description: t('Metric'), -}; - const datasourceControl: SharedControlConfig<'DatasourceControl'> = { type: 'DatasourceControl', label: t('Datasource'), @@ -203,13 +124,6 @@ const color_picker: SharedControlConfig<'ColorPickerControl'> = { renderTrigger: true, }; -const metric_2: SharedControlConfig<'MetricsControl'> = { - ...metric, - label: t('Right Axis Metric'), - clearable: true, - description: t('Choose a metric for right axis'), -}; - const linear_color_scheme: SharedControlConfig<'ColorSchemeControl'> = { type: 'ColorSchemeControl', label: t('Linear Color Scheme'), @@ -229,20 +143,6 @@ const linear_color_scheme: SharedControlConfig<'ColorSchemeControl'> = { }), }; -const secondary_metric: SharedControlConfig<'MetricsControl'> = { - ...metric, - label: t('Color Metric'), - default: null, - validators: [], - description: t('A metric to use for color'), -}; - -const columnsControl: typeof groupByControl = { - ...groupByControl, - label: t('Columns'), - description: t('One or many columns to pivot as columns'), -}; - const granularity: SharedControlConfig<'SelectControl'> = { type: 'SelectControl', freeForm: true, @@ -273,44 +173,6 @@ const granularity: SharedControlConfig<'SelectControl'> = { ), }; -const granularity_sqla: SharedControlConfig<'SelectControl', ColumnMeta> = { - type: 'SelectControl', - label: TIME_FILTER_LABELS.granularity_sqla, - description: t( - 'The time column for the visualization. Note that you ' + - 'can define arbitrary expression that return a DATETIME ' + - 'column in the table. Also note that the ' + - 'filter below is applied against this column or ' + - 'expression', - ), - default: (c: Control) => c.default, - clearable: false, - optionRenderer: c => , - valueRenderer: c => , - valueKey: 'column_name', - mapStateToProps: state => { - const props: Partial> = {}; - const { datasource } = state; - if (datasource?.hasOwnProperty('main_dttm_col')) { - const dataset = datasource as Dataset; - props.options = dataset.columns.filter((c: ColumnMeta) => c.is_dttm); - props.default = null; - if (dataset.main_dttm_col) { - props.default = dataset.main_dttm_col; - } else if (props?.options) { - props.default = (props.options[0] as ColumnMeta)?.column_name; - } - } else { - const sortedQueryColumns = (datasource as QueryResponse)?.columns?.sort( - query => (query?.is_dttm ? -1 : 1), - ); - props.options = sortedQueryColumns; - if (props?.options) props.default = props.options[0]?.name; - } - return props; - }, -}; - const time_grain_sqla: SharedControlConfig<'SelectControl'> = { type: 'SelectControl', label: TIME_FILTER_LABELS.time_grain_sqla, @@ -411,64 +273,6 @@ const series_limit: SharedControlConfig<'SelectControl'> = { ), }; -const sort_by: SharedControlConfig<'MetricsControl'> = { - type: 'MetricsControl', - label: t('Sort by'), - default: null, - description: t( - 'Metric used to define how the top series are sorted if a series or row limit is present. ' + - 'If undefined reverts to the first metric (where appropriate).', - ), - mapStateToProps: ({ datasource }) => ({ - columns: datasource?.columns || [], - savedMetrics: defineSavedMetrics(datasource), - datasource, - datasourceType: datasource?.type, - }), -}; - -const series: typeof groupByControl = { - ...groupByControl, - label: t('Dimensions'), - multi: false, - default: null, - description: t( - 'Defines the grouping of entities. ' + - 'Each series is shown as a specific color on the chart and ' + - 'has a legend toggle', - ), -}; - -const entity: typeof groupByControl = { - ...groupByControl, - label: t('Entity'), - default: null, - multi: false, - validators: [validateNonEmpty], - description: t('This defines the element to be plotted on the chart'), -}; - -const x: SharedControlConfig<'MetricsControl'> = { - ...metric, - label: t('X Axis'), - description: t('Metric assigned to the [X] axis'), - default: null, -}; - -const y: SharedControlConfig<'MetricsControl'> = { - ...metric, - label: t('Y Axis'), - default: null, - description: t('Metric assigned to the [Y] axis'), -}; - -const size: SharedControlConfig<'MetricsControl'> = { - ...metric, - label: t('Bubble Size'), - description: t('Metric used to calculate bubble size'), - default: null, -}; - const y_axis_format: SharedControlConfig<'SelectControl', SelectDefaultOption> = { type: 'SelectControl', @@ -507,23 +311,6 @@ const x_axis_time_format: SharedControlConfig< option.label.includes(search) || option.value.includes(search), }; -const adhoc_filters: SharedControlConfig<'AdhocFilterControl'> = { - type: 'AdhocFilterControl', - label: t('Filters'), - default: [], - description: '', - mapStateToProps: ({ datasource, form_data }) => ({ - columns: datasource?.columns[0]?.hasOwnProperty('filterable') - ? (datasource as Dataset)?.columns.filter(c => c.filterable) - : datasource?.columns || [], - savedMetrics: defineSavedMetrics(datasource), - // current active adhoc metrics - selectedMetrics: - form_data.metrics || (form_data.metric ? [form_data.metric] : []), - datasource, - }), -}; - const color_scheme: SharedControlConfig<'ColorSchemeControl'> = { type: 'ColorSchemeControl', label: t('Color Scheme'), @@ -551,51 +338,40 @@ const show_empty_columns: SharedControlConfig<'CheckboxControl'> = { description: t('Show empty columns'), }; -const x_axis: SharedControlConfig<'SelectControl', ColumnMeta> = { - ...groupByControl, - ...xAxisControlConfig, -}; - -const enableExploreDnd = isFeatureEnabled( - FeatureFlag.ENABLE_EXPLORE_DRAG_AND_DROP, -); - -const sharedControls = { - metrics: enableExploreDnd ? dnd_adhoc_metrics : metrics, - metric: enableExploreDnd ? dnd_adhoc_metric : metric, +export default { + metrics: dndAdhocMetricsControl, + metric: dndAdhocMetricControl, datasource: datasourceControl, viz_type, color_picker, - metric_2: enableExploreDnd ? dnd_adhoc_metric_2 : metric_2, + metric_2: dndAdhocMetricControl2, linear_color_scheme, - secondary_metric: enableExploreDnd ? dnd_secondary_metric : secondary_metric, - groupby: enableExploreDnd ? dndGroupByControl : groupByControl, - columns: enableExploreDnd ? dndColumnsControl : columnsControl, + secondary_metric: dndSecondaryMetricControl, + groupby: dndGroupByControl, + columns: dndColumnsControl, granularity, - granularity_sqla: enableExploreDnd ? dnd_granularity_sqla : granularity_sqla, + granularity_sqla: dndGranularitySqlaControl, time_grain_sqla, time_range, row_limit, limit, - timeseries_limit_metric: enableExploreDnd ? dnd_sort_by : sort_by, - orderby: enableExploreDnd ? dnd_sort_by : sort_by, + timeseries_limit_metric: dndSortByControl, + orderby: dndSortByControl, order_desc, - series: enableExploreDnd ? dndSeries : series, - entity: enableExploreDnd ? dndEntity : entity, - x: enableExploreDnd ? dnd_x : x, - y: enableExploreDnd ? dnd_y : y, - size: enableExploreDnd ? dnd_size : size, + series: dndSeriesControl, + entity: dndEntityControl, + x: dndXControl, + y: dndYControl, + size: dndSizeControl, y_axis_format, x_axis_time_format, - adhoc_filters: enableExploreDnd ? dnd_adhoc_filters : adhoc_filters, + adhoc_filters: dndAdhocFilterControl, color_scheme, - series_columns: enableExploreDnd ? dndColumnsControl : columnsControl, + series_columns: dndColumnsControl, series_limit, - series_limit_metric: enableExploreDnd ? dnd_sort_by : sort_by, - legacy_order_by: enableExploreDnd ? dnd_sort_by : sort_by, + series_limit_metric: dndSortByControl, + legacy_order_by: dndSortByControl, truncate_metric, - x_axis: enableExploreDnd ? dnd_x_axis : x_axis, + x_axis: dndXAxisControl, show_empty_columns, }; - -export { sharedControls, dndEntity, dndColumnsControl }; diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/types.ts b/superset-frontend/packages/superset-ui-chart-controls/src/types.ts index 5dda10f54cc1..99ea6a5325e9 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/src/types.ts +++ b/superset-frontend/packages/superset-ui-chart-controls/src/types.ts @@ -29,7 +29,7 @@ import type { QueryFormMetric, QueryResponse, } from '@superset-ui/core'; -import { sharedControls } from './shared-controls'; +import sharedControls from './shared-controls'; import sharedControlComponents from './shared-controls/components'; export type { Metric } from '@superset-ui/core'; @@ -238,7 +238,7 @@ export interface BaseControlConfig< ) => boolean; mapStateToProps?: ( state: ControlPanelState, - controlState: ControlState, + controlState?: ControlState, // TODO: add strict `chartState` typing (see superset-frontend/src/explore/types) chartState?: AnyDict, ) => ExtraControlProps; 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 d06ad0d5127d..161dd5ad07aa 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 @@ -17,7 +17,7 @@ * under the License. */ import React, { ReactElement } from 'react'; -import { sharedControls } from '../shared-controls'; +import sharedControls from '../shared-controls'; import sharedControlComponents from '../shared-controls/components'; import { ControlType, diff --git a/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/controlPanel.tsx b/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/controlPanel.tsx index 3ba3548fc763..09df0427e208 100644 --- a/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/controlPanel.tsx +++ b/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/controlPanel.tsx @@ -30,7 +30,7 @@ import { formatSelectOptions, formatSelectOptionsForRange, sections, - dndEntity, + sharedControls, getStandardizedControls, } from '@superset-ui/chart-controls'; @@ -52,7 +52,7 @@ const allColumns = { }; const dndAllColumns = { - ...dndEntity, + ...sharedControls.entity, description: t('Columns to display'), }; diff --git a/superset-frontend/plugins/legacy-plugin-chart-histogram/src/controlPanel.ts b/superset-frontend/plugins/legacy-plugin-chart-histogram/src/controlPanel.ts index 6c5a13ce6a79..d976b16bd242 100644 --- a/superset-frontend/plugins/legacy-plugin-chart-histogram/src/controlPanel.ts +++ b/superset-frontend/plugins/legacy-plugin-chart-histogram/src/controlPanel.ts @@ -16,44 +16,28 @@ * specific language governing permissions and limitations * under the License. */ -import { - FeatureFlag, - isFeatureEnabled, - t, - validateNonEmpty, -} from '@superset-ui/core'; +import { t, validateNonEmpty } from '@superset-ui/core'; import { columnChoices, ControlPanelConfig, ControlPanelState, formatSelectOptions, sections, - dndColumnsControl, getStandardizedControls, + sharedControls, } from '@superset-ui/chart-controls'; -const allColumns = { - type: 'SelectControl', +const columnsConfig = { + ...sharedControls.columns, label: t('Columns'), - default: null, description: t('Select the numeric columns to draw the histogram'), mapStateToProps: (state: ControlPanelState) => ({ + ...(sharedControls.columns.mapStateToProps?.(state) || {}), choices: columnChoices(state.datasource), }), - multi: true, - validators: [validateNonEmpty], -}; - -const dndAllColumns = { - ...dndColumnsControl, - description: t('Select the numeric columns to draw the histogram'), validators: [validateNonEmpty], }; -const columnsConfig = isFeatureEnabled(FeatureFlag.ENABLE_EXPLORE_DRAG_AND_DROP) - ? dndAllColumns - : allColumns; - const config: ControlPanelConfig = { controlPanelSections: [ sections.legacyRegularTime, diff --git a/superset-frontend/plugins/legacy-plugin-chart-map-box/src/controlPanel.ts b/superset-frontend/plugins/legacy-plugin-chart-map-box/src/controlPanel.ts index c5ee94aaf73c..8d05cda07828 100644 --- a/superset-frontend/plugins/legacy-plugin-chart-map-box/src/controlPanel.ts +++ b/superset-frontend/plugins/legacy-plugin-chart-map-box/src/controlPanel.ts @@ -23,7 +23,7 @@ import { ControlPanelState, formatSelectOptions, sections, - dndEntity, + sharedControls, getStandardizedControls, } from '@superset-ui/chart-controls'; @@ -36,7 +36,7 @@ const allColumns = { }; const columnsConfig = isFeatureEnabled(FeatureFlag.ENABLE_EXPLORE_DRAG_AND_DROP) - ? dndEntity + ? sharedControls.entity : allColumns; const colorChoices = [ diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utilities/sharedDndControls.jsx b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utilities/sharedDndControls.jsx index 6027b87f1020..30ffd27287ad 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utilities/sharedDndControls.jsx +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utilities/sharedDndControls.jsx @@ -18,12 +18,12 @@ */ import { t } from '@superset-ui/core'; -import { dndEntity } from '@superset-ui/chart-controls'; +import { sharedControls } from '@superset-ui/chart-controls'; export const dndLineColumn = { name: 'line_column', config: { - ...dndEntity, + ...sharedControls.entity, label: t('Lines column'), description: t('The database columns that contains lines information'), }, @@ -32,7 +32,7 @@ export const dndLineColumn = { export const dndGeojsonColumn = { name: 'geojson', config: { - ...dndEntity, + ...sharedControls.entity, label: t('GeoJson Column'), description: t('Select the geojson column'), }, diff --git a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Bar/controlPanel.ts b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Bar/controlPanel.ts index 45c145d82b1f..47fbbd442247 100644 --- a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Bar/controlPanel.ts +++ b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Bar/controlPanel.ts @@ -64,7 +64,7 @@ const config: ControlPanelConfig = { controlState, ) || {}; timeserieslimitProps.value = state.controls?.limit?.value - ? controlState.value + ? controlState?.value : []; return timeserieslimitProps; }, diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Bar/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Bar/controlPanel.tsx index 160f981084b8..91b0b3ca60e2 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Bar/controlPanel.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Bar/controlPanel.tsx @@ -345,7 +345,7 @@ const config: ControlPanelConfig = { controlState, ) || {}; timeserieslimitProps.value = state.controls?.limit?.value - ? controlState.value + ? controlState?.value : []; return timeserieslimitProps; }, diff --git a/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/columns.tsx b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/columns.tsx index 3aec61dc4060..3c2434ac4063 100644 --- a/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/columns.tsx +++ b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/columns.tsx @@ -49,7 +49,7 @@ const allColumns: typeof sharedControls.groupby = { options: datasource?.columns || [], queryMode: getQueryMode(controls), externalValidationErrors: - isRawMode({ controls }) && ensureIsArray(controlState.value).length === 0 + isRawMode({ controls }) && ensureIsArray(controlState?.value).length === 0 ? [t('must have a value')] : [], }), @@ -74,7 +74,7 @@ const dndAllColumns: typeof sharedControls.groupby = { } newState.queryMode = getQueryMode(controls); newState.externalValidationErrors = - isRawMode({ controls }) && ensureIsArray(controlState.value).length === 0 + isRawMode({ controls }) && ensureIsArray(controlState?.value).length === 0 ? [t('must have a value')] : []; return newState; diff --git a/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/metrics.tsx b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/metrics.tsx index 96eab55f9297..c6aad5e320ba 100644 --- a/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/metrics.tsx +++ b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/metrics.tsx @@ -46,7 +46,7 @@ const percentMetrics: typeof sharedControls.metrics = { externalValidationErrors: validateAggControlValues(controls, [ controls.groupby?.value, controls.metrics?.value, - controlState.value, + controlState?.value, ]), }), rerender: ['groupby', 'metrics'], diff --git a/superset-frontend/plugins/plugin-chart-table/src/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-table/src/controlPanel.tsx index ee9ec5c1f39a..03082cb21c07 100644 --- a/superset-frontend/plugins/plugin-chart-table/src/controlPanel.tsx +++ b/superset-frontend/plugins/plugin-chart-table/src/controlPanel.tsx @@ -40,7 +40,6 @@ import { sections, sharedControls, ControlPanelState, - ExtraControlProps, ControlState, emitFilterControl, Dataset, @@ -96,15 +95,14 @@ const queryMode: ControlConfig<'RadioButtonControl'> = { rerender: ['all_columns', 'groupby', 'metrics', 'percent_metrics'], }; -const all_columns: typeof sharedControls.groupby = { - type: 'SelectControl', +const allColumnsControl: typeof sharedControls.groupby = { + ...sharedControls.groupby, label: t('Columns'), description: t('Columns to display'), multi: true, freeForm: true, allowAll: true, commaChoosesOption: false, - default: [], optionRenderer: c => , valueRenderer: c => , valueKey: 'column_name', @@ -112,7 +110,7 @@ const all_columns: typeof sharedControls.groupby = { options: datasource?.columns || [], queryMode: getQueryMode(controls), externalValidationErrors: - isRawMode({ controls }) && ensureIsArray(controlState.value).length === 0 + isRawMode({ controls }) && ensureIsArray(controlState?.value).length === 0 ? [t('must have a value')] : [], }), @@ -120,37 +118,12 @@ const all_columns: typeof sharedControls.groupby = { resetOnHide: false, }; -const dnd_all_columns: typeof sharedControls.groupby = { - type: 'DndColumnSelect', - label: t('Columns'), - description: t('Columns to display'), - default: [], - mapStateToProps({ datasource, controls }, controlState) { - const newState: ExtraControlProps = {}; - if (datasource?.columns[0]?.hasOwnProperty('column_name')) { - const options = (datasource as Dataset).columns; - newState.options = Object.fromEntries( - options.map((option: ColumnMeta) => [option.column_name, option]), - ); - } else newState.options = datasource?.columns; - newState.queryMode = getQueryMode(controls); - newState.externalValidationErrors = - isRawMode({ controls }) && ensureIsArray(controlState.value).length === 0 - ? [t('must have a value')] - : []; - return newState; - }, - visibility: isRawMode, - resetOnHide: false, -}; - -const percent_metrics: typeof sharedControls.metrics = { - type: 'MetricsControl', +const percentMetricsControl: typeof sharedControls.metrics = { + ...sharedControls.metrics, label: t('Percentage metrics'), description: t( 'Metrics for which percentage of total are to be displayed. Calculated from only data within the row limit.', ), - multi: true, visibility: isAggMode, resetOnHide: false, mapStateToProps: ({ datasource, controls }, controlState) => ({ @@ -162,7 +135,7 @@ const percent_metrics: typeof sharedControls.metrics = { externalValidationErrors: validateAggControlValues(controls, [ controls.groupby?.value, controls.metrics?.value, - controlState.value, + controlState?.value, ]), }), rerender: ['groupby', 'metrics'], @@ -170,11 +143,6 @@ const percent_metrics: typeof sharedControls.metrics = { validators: [], }; -const dnd_percent_metrics = { - ...percent_metrics, - type: 'DndMetricSelect', -}; - const config: ControlPanelConfig = { controlPanelSections: [ sections.legacyTimeseriesTime, @@ -251,19 +219,13 @@ const config: ControlPanelConfig = { }, { name: 'all_columns', - config: isFeatureEnabled(FeatureFlag.ENABLE_EXPLORE_DRAG_AND_DROP) - ? dnd_all_columns - : all_columns, + config: allColumnsControl, }, ], [ { name: 'percent_metrics', - config: { - ...(isFeatureEnabled(FeatureFlag.ENABLE_EXPLORE_DRAG_AND_DROP) - ? dnd_percent_metrics - : percent_metrics), - }, + config: percentMetricsControl, }, ], ['adhoc_filters'], diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelect.test.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelect.test.tsx index 13996091e105..5cf5c59fd99f 100644 --- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelect.test.tsx +++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelect.test.tsx @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ +import { FeatureFlag } from '@superset-ui/core'; import React from 'react'; import { render, screen } from 'spec/helpers/testing-library'; import { @@ -27,12 +28,18 @@ const defaultProps: DndColumnSelectProps = { type: 'DndColumnSelect', name: 'Filter', onChange: jest.fn(), - options: { - string: { column_name: 'Column A' }, - }, + options: [{ column_name: 'Column A' }], actions: { setControlValue: jest.fn() }, }; +beforeAll(() => { + window.featureFlags = { [FeatureFlag.ENABLE_EXPLORE_DRAG_AND_DROP]: true }; +}); + +afterAll(() => { + window.featureFlags = {}; +}); + test('renders with default props', async () => { render(, { useDnd: true, @@ -42,7 +49,7 @@ test('renders with default props', async () => { }); test('renders with value', async () => { - render(, { + render(, { useDnd: true, useRedux: true, }); diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelect.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelect.tsx index e7b25908cf11..f254eece75cc 100644 --- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelect.tsx +++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelect.tsx @@ -24,7 +24,11 @@ import { tn, QueryFormColumn, } from '@superset-ui/core'; -import { ColumnMeta, isColumnMeta } from '@superset-ui/chart-controls'; +import { + ColumnMeta, + isColumnMeta, + withDndFallback, +} from '@superset-ui/chart-controls'; import { isEmpty } from 'lodash'; import DndSelectLabel from 'src/explore/components/controls/DndColumnSelectControl/DndSelectLabel'; import OptionWrapper from 'src/explore/components/controls/DndColumnSelectControl/OptionWrapper'; @@ -34,13 +38,14 @@ import { DndItemType } from 'src/explore/components/DndItemType'; import { useComponentDidUpdate } from 'src/hooks/useComponentDidUpdate'; import ColumnSelectPopoverTrigger from './ColumnSelectPopoverTrigger'; import { DndControlProps } from './types'; +import SelectControl from '../SelectControl'; export type DndColumnSelectProps = DndControlProps & { - options: Record; + options: ColumnMeta[]; isTemporal?: boolean; }; -export function DndColumnSelect(props: DndColumnSelectProps) { +function DndColumnSelect(props: DndColumnSelectProps) { const { value, options, @@ -48,16 +53,20 @@ export function DndColumnSelect(props: DndColumnSelectProps) { onChange, canDelete = true, ghostButtonText, + clickEnabledGhostButtonText, name, label, isTemporal, } = props; const [newColumnPopoverVisible, setNewColumnPopoverVisible] = useState(false); - const optionSelector = useMemo( - () => new OptionSelector(options, multi, value), - [multi, options, value], - ); + const optionSelector = useMemo(() => { + const optionsMap = Object.fromEntries( + options.map(option => [option.column_name, option]), + ); + + return new OptionSelector(optionsMap, multi, value); + }, [multi, options, value]); // synchronize values in case of dataset changes const handleOptionsChange = useCallback(() => { @@ -126,15 +135,18 @@ export function DndColumnSelect(props: DndColumnSelectProps) { [onChange, optionSelector], ); - const popoverOptions = useMemo(() => Object.values(options), [options]); + const clickEnabled = useMemo( + () => isFeatureEnabled(FeatureFlag.ENABLE_DND_WITH_CLICK_UX), + [], + ); const valuesRenderer = useCallback( () => optionSelector.values.map((column, idx) => - isFeatureEnabled(FeatureFlag.ENABLE_DND_WITH_CLICK_UX) ? ( + clickEnabled ? ( { if (isColumnMeta(newColumn)) { optionSelector.replace(idx, newColumn.column_name); @@ -171,13 +183,15 @@ export function DndColumnSelect(props: DndColumnSelectProps) { ), [ canDelete, + clickEnabled, + isTemporal, label, name, onChange, onClickClose, onShiftOptions, optionSelector, - popoverOptions, + options, ], ); @@ -205,15 +219,24 @@ export function DndColumnSelect(props: DndColumnSelectProps) { togglePopover(true); }, [togglePopover]); - const defaultGhostButtonText = isFeatureEnabled( - FeatureFlag.ENABLE_DND_WITH_CLICK_UX, - ) - ? tn( - 'Drop a column here or click', - 'Drop columns here or click', - multi ? 2 : 1, - ) - : tn('Drop column here', 'Drop columns here', multi ? 2 : 1); + const labelGhostButtonText = useMemo(() => { + if (clickEnabled) { + return ( + clickEnabledGhostButtonText ?? + ghostButtonText ?? + tn( + 'Drop a column here or click', + 'Drop columns here or click', + multi ? 2 : 1, + ) + ); + } + + return ( + ghostButtonText ?? + tn('Drop column here', 'Drop columns here', multi ? 2 : 1) + ); + }, [clickEnabled, clickEnabledGhostButtonText, ghostButtonText, multi]); return (
@@ -223,16 +246,12 @@ export function DndColumnSelect(props: DndColumnSelectProps) { valuesRenderer={valuesRenderer} accept={DndItemType.Column} displayGhostButton={multi || optionSelector.values.length === 0} - ghostButtonText={ghostButtonText || defaultGhostButtonText} - onClickGhostButton={ - isFeatureEnabled(FeatureFlag.ENABLE_DND_WITH_CLICK_UX) - ? openPopover - : undefined - } + ghostButtonText={labelGhostButtonText} + onClickGhostButton={clickEnabled ? openPopover : undefined} {...props} /> ); } + +const DndColumnSelectWithFallback = withDndFallback( + DndColumnSelect, + SelectControl, +); + +export { DndColumnSelectWithFallback as DndColumnSelect }; diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndFilterSelect.test.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndFilterSelect.test.tsx index 92a1597dcdc5..5e9816e50e8a 100644 --- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndFilterSelect.test.tsx +++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndFilterSelect.test.tsx @@ -17,7 +17,7 @@ * under the License. */ import React from 'react'; -import { GenericDataType } from '@superset-ui/core'; +import { FeatureFlag, GenericDataType } from '@superset-ui/core'; import { render, screen } from 'spec/helpers/testing-library'; import AdhocMetric from 'src/explore/components/controls/MetricControl/AdhocMetric'; import AdhocFilter, { @@ -48,6 +48,14 @@ const baseFormData = { datasource: 'table__1', }; +beforeAll(() => { + window.featureFlags = { [FeatureFlag.ENABLE_EXPLORE_DRAG_AND_DROP]: true }; +}); + +afterAll(() => { + window.featureFlags = {}; +}); + test('renders with default props', async () => { render(, { useDnd: true }); expect( diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndFilterSelect.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndFilterSelect.tsx index b0eb601643f6..285c73132373 100644 --- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndFilterSelect.tsx +++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndFilterSelect.tsx @@ -27,7 +27,7 @@ import { SupersetClient, t, } from '@superset-ui/core'; -import { ColumnMeta } from '@superset-ui/chart-controls'; +import { ColumnMeta, withDndFallback } from '@superset-ui/chart-controls'; import { OPERATOR_ENUM_TO_OPERATOR_TYPE, Operators, @@ -49,6 +49,7 @@ import { } from 'src/explore/components/DatasourcePanel/types'; import { DndItemType } from 'src/explore/components/DndItemType'; import { ControlComponentProps } from 'src/explore/components/Control'; +import AdhocFilterControl from '../FilterControl/AdhocFilterControl'; const EMPTY_OBJECT = {}; const DND_ACCEPTED_TYPES = [ @@ -69,7 +70,7 @@ export interface DndFilterSelectProps datasource: Datasource; } -export const DndFilterSelect = (props: DndFilterSelectProps) => { +const DndFilterSelect = (props: DndFilterSelectProps) => { const { datasource, onChange = () => {}, name: controlName } = props; const propsValues = Array.from(props.value ?? []); @@ -407,3 +408,10 @@ export const DndFilterSelect = (props: DndFilterSelectProps) => { ); }; + +const DndFilterSelectWithFallback = withDndFallback( + DndFilterSelect, + AdhocFilterControl, +); + +export { DndFilterSelectWithFallback as DndFilterSelect }; diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndMetricSelect.test.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndMetricSelect.test.tsx index e16c1d0d2557..69727d17fe92 100644 --- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndMetricSelect.test.tsx +++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndMetricSelect.test.tsx @@ -18,6 +18,7 @@ */ import React from 'react'; import userEvent from '@testing-library/user-event'; +import { FeatureFlag } from '@superset-ui/core'; import { render, screen, @@ -67,6 +68,14 @@ const adhocMetricB = { optionName: 'def', }; +beforeAll(() => { + window.featureFlags = { [FeatureFlag.ENABLE_EXPLORE_DRAG_AND_DROP]: true }; +}); + +afterAll(() => { + window.featureFlags = {}; +}); + test('renders with default props', () => { render(, { useDnd: true }); expect(screen.getByText('Drop column or metric here')).toBeInTheDocument(); diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndMetricSelect.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndMetricSelect.tsx index 0d9591a0c5ae..501e814b9a8e 100644 --- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndMetricSelect.tsx +++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndMetricSelect.tsx @@ -27,7 +27,7 @@ import { QueryFormMetric, tn, } from '@superset-ui/core'; -import { ColumnMeta } from '@superset-ui/chart-controls'; +import { ColumnMeta, withDndFallback } from '@superset-ui/chart-controls'; import { isEqual } from 'lodash'; import { usePrevious } from 'src/hooks/usePrevious'; import AdhocMetric from 'src/explore/components/controls/MetricControl/AdhocMetric'; @@ -41,6 +41,7 @@ import { DndItemType } from 'src/explore/components/DndItemType'; import DndSelectLabel from 'src/explore/components/controls/DndColumnSelectControl/DndSelectLabel'; import { savedMetricType } from 'src/explore/components/controls/MetricControl/types'; import { AGGREGATES } from 'src/explore/constants'; +import MetricsControl from '../MetricControl/MetricsControl'; const EMPTY_OBJECT = {}; const DND_ACCEPTED_TYPES = [DndItemType.Column, DndItemType.Metric]; @@ -125,7 +126,7 @@ const getMetricsMatchingCurrentDataset = ( }, []); }; -export const DndMetricSelect = (props: any) => { +const DndMetricSelect = (props: any) => { const { onChange, multi, columns, savedMetrics } = props; const handleChange = useCallback( @@ -408,3 +409,10 @@ export const DndMetricSelect = (props: any) => {
); }; + +const DndMetricSelectWithFallback = withDndFallback( + DndMetricSelect, + MetricsControl, +); + +export { DndMetricSelectWithFallback as DndMetricSelect }; diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/types.ts b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/types.ts index b2c9bd953b98..b324689c6709 100644 --- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/types.ts +++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/types.ts @@ -46,6 +46,7 @@ export type DndControlProps = multi?: boolean; canDelete?: boolean; ghostButtonText?: string; + clickEnabledGhostButtonText?: string; onChange: (value: ValueType | ValueType[] | null | undefined) => void; };