mirror of https://github.com/apache/superset.git
fix(explore): Prevent shared controls from checking feature flags outside React render (#21315)
This commit is contained in:
parent
59ca7861c0
commit
2285ebe72e
|
@ -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';
|
||||
|
|
|
@ -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) => <ColumnOption showType column={c} />,
|
||||
valueRenderer: (c: ColumnMeta) => <ColumnOption column={c} />,
|
||||
valueKey: 'column_name',
|
||||
allowAll: true,
|
||||
filterOption: ({ data: opt }: FilterOption<ColumnMeta>, 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) => <ColumnOption showType column={c} />,
|
||||
valueRenderer: (c: ColumnMeta) => <ColumnOption column={c} />,
|
||||
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<any>,
|
||||
FallbackComponent: React.ComponentType<any>,
|
||||
) {
|
||||
return function DndControl(props: any) {
|
||||
const enableExploreDnd = useMemo(
|
||||
() => isFeatureEnabled(FeatureFlag.ENABLE_EXPLORE_DRAG_AND_DROP),
|
||||
[],
|
||||
);
|
||||
|
||||
return enableExploreDnd ? (
|
||||
<DndComponent {...props} />
|
||||
) : (
|
||||
<FallbackComponent {...props} />
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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 => <ColumnOption showType column={c} />,
|
||||
valueRenderer: c => <ColumnOption column={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 => <ColumnOption showType column={c} />,
|
||||
valueRenderer: c => <ColumnOption column={c} />,
|
||||
valueKey: 'column_name',
|
||||
mapStateToProps: state => {
|
||||
const props: Partial<SelectControlConfig<ColumnMeta | QueryColumn>> = {};
|
||||
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 };
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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'),
|
||||
};
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 = [
|
||||
|
|
|
@ -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'),
|
||||
},
|
||||
|
|
|
@ -64,7 +64,7 @@ const config: ControlPanelConfig = {
|
|||
controlState,
|
||||
) || {};
|
||||
timeserieslimitProps.value = state.controls?.limit?.value
|
||||
? controlState.value
|
||||
? controlState?.value
|
||||
: [];
|
||||
return timeserieslimitProps;
|
||||
},
|
||||
|
|
|
@ -345,7 +345,7 @@ const config: ControlPanelConfig = {
|
|||
controlState,
|
||||
) || {};
|
||||
timeserieslimitProps.value = state.controls?.limit?.value
|
||||
? controlState.value
|
||||
? controlState?.value
|
||||
: [];
|
||||
return timeserieslimitProps;
|
||||
},
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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'],
|
||||
|
|
|
@ -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 => <ColumnOption showType column={c} />,
|
||||
valueRenderer: c => <ColumnOption column={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'],
|
||||
|
|
|
@ -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(<DndColumnSelect {...defaultProps} />, {
|
||||
useDnd: true,
|
||||
|
@ -42,7 +49,7 @@ test('renders with default props', async () => {
|
|||
});
|
||||
|
||||
test('renders with value', async () => {
|
||||
render(<DndColumnSelect {...defaultProps} value="string" />, {
|
||||
render(<DndColumnSelect {...defaultProps} value="Column A" />, {
|
||||
useDnd: true,
|
||||
useRedux: true,
|
||||
});
|
||||
|
|
|
@ -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<QueryFormColumn> & {
|
||||
options: Record<string, ColumnMeta>;
|
||||
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 ? (
|
||||
<ColumnSelectPopoverTrigger
|
||||
key={idx}
|
||||
columns={popoverOptions}
|
||||
columns={options}
|
||||
onColumnEdit={newColumn => {
|
||||
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 (
|
||||
<div>
|
||||
|
@ -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}
|
||||
/>
|
||||
<ColumnSelectPopoverTrigger
|
||||
columns={popoverOptions}
|
||||
columns={options}
|
||||
onColumnEdit={addNewColumnWithPopover}
|
||||
isControlledComponent
|
||||
togglePopover={togglePopover}
|
||||
|
@ -245,3 +264,10 @@ export function DndColumnSelect(props: DndColumnSelectProps) {
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const DndColumnSelectWithFallback = withDndFallback(
|
||||
DndColumnSelect,
|
||||
SelectControl,
|
||||
);
|
||||
|
||||
export { DndColumnSelectWithFallback as DndColumnSelect };
|
||||
|
|
|
@ -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(<DndFilterSelect {...defaultProps} />, { useDnd: true });
|
||||
expect(
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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(<DndMetricSelect {...defaultProps} />, { useDnd: true });
|
||||
expect(screen.getByText('Drop column or metric here')).toBeInTheDocument();
|
||||
|
|
|
@ -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) => {
|
|||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const DndMetricSelectWithFallback = withDndFallback(
|
||||
DndMetricSelect,
|
||||
MetricsControl,
|
||||
);
|
||||
|
||||
export { DndMetricSelectWithFallback as DndMetricSelect };
|
||||
|
|
|
@ -46,6 +46,7 @@ export type DndControlProps<ValueType extends JsonValue> =
|
|||
multi?: boolean;
|
||||
canDelete?: boolean;
|
||||
ghostButtonText?: string;
|
||||
clickEnabledGhostButtonText?: string;
|
||||
onChange: (value: ValueType | ValueType[] | null | undefined) => void;
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue