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';
|
export * from './components/MetricOption';
|
||||||
|
|
||||||
// React control components
|
// React control components
|
||||||
export {
|
export { default as sharedControls, withDndFallback } from './shared-controls';
|
||||||
sharedControls,
|
|
||||||
dndEntity,
|
|
||||||
dndColumnsControl,
|
|
||||||
} from './shared-controls';
|
|
||||||
export { default as sharedControlComponents } from './shared-controls/components';
|
export { default as sharedControlComponents } from './shared-controls/components';
|
||||||
export { legacySortBy } from './shared-controls/legacySortBy';
|
export { legacySortBy } from './shared-controls/legacySortBy';
|
||||||
export * from './shared-controls/emitFilterControl';
|
export * from './shared-controls/emitFilterControl';
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
import React, { useMemo } from 'react';
|
||||||
import {
|
import {
|
||||||
FeatureFlag,
|
FeatureFlag,
|
||||||
isFeatureEnabled,
|
isFeatureEnabled,
|
||||||
|
@ -25,43 +26,83 @@ import {
|
||||||
t,
|
t,
|
||||||
validateNonEmpty,
|
validateNonEmpty,
|
||||||
} from '@superset-ui/core';
|
} 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 { 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';
|
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',
|
type: 'DndColumnSelect',
|
||||||
label: t('Dimensions'),
|
label: t('Dimensions'),
|
||||||
|
multi: true,
|
||||||
|
freeForm: true,
|
||||||
|
clearable: true,
|
||||||
default: [],
|
default: [],
|
||||||
|
includeTime: false,
|
||||||
description: t(
|
description: t(
|
||||||
'One or many columns to group by. High cardinality groupings should include a series limit ' +
|
'One or many columns to group by. High cardinality groupings should include a sort by metric ' +
|
||||||
'to limit the number of fetched and rendered series.',
|
'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 newState: ExtraControlProps = {};
|
||||||
const { datasource } = state;
|
const { datasource } = state;
|
||||||
if (datasource?.columns[0]?.hasOwnProperty('groupby')) {
|
if (datasource?.columns[0]?.hasOwnProperty('groupby')) {
|
||||||
const options = (datasource as Dataset).columns.filter(c => c.groupby);
|
const options = (datasource as Dataset).columns.filter(c => c.groupby);
|
||||||
if (includeTime) {
|
if (controlState?.includeTime) {
|
||||||
options.unshift(DATASET_TIME_COLUMN_OPTION);
|
options.unshift(DATASET_TIME_COLUMN_OPTION);
|
||||||
}
|
}
|
||||||
newState.options = Object.fromEntries(
|
newState.options = options;
|
||||||
options.map(option => [option.column_name, option]),
|
|
||||||
);
|
|
||||||
newState.savedMetrics = (datasource as Dataset).metrics || [];
|
newState.savedMetrics = (datasource as Dataset).metrics || [];
|
||||||
} else {
|
} else {
|
||||||
const options = datasource?.columns;
|
const options = (datasource?.columns as QueryColumn[]) || [];
|
||||||
if (includeTime) {
|
if (controlState?.includeTime) {
|
||||||
(options as QueryColumn[])?.unshift(QUERY_TIME_COLUMN_OPTION);
|
options.unshift(QUERY_TIME_COLUMN_OPTION);
|
||||||
}
|
}
|
||||||
newState.options = Object.fromEntries(
|
newState.options = options;
|
||||||
(options as QueryColumn[])?.map(option => [option.name, option]),
|
|
||||||
);
|
|
||||||
newState.options = datasource?.columns;
|
|
||||||
}
|
}
|
||||||
return newState;
|
return newState;
|
||||||
},
|
},
|
||||||
|
commaChoosesOption: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const dndColumnsControl: typeof dndGroupByControl = {
|
export const dndColumnsControl: typeof dndGroupByControl = {
|
||||||
|
@ -70,7 +111,7 @@ export const dndColumnsControl: typeof dndGroupByControl = {
|
||||||
description: t('One or many columns to pivot as columns'),
|
description: t('One or many columns to pivot as columns'),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const dndSeries: typeof dndGroupByControl = {
|
export const dndSeriesControl: typeof dndGroupByControl = {
|
||||||
...dndGroupByControl,
|
...dndGroupByControl,
|
||||||
label: t('Dimension'),
|
label: t('Dimension'),
|
||||||
multi: false,
|
multi: false,
|
||||||
|
@ -82,7 +123,7 @@ export const dndSeries: typeof dndGroupByControl = {
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const dndEntity: typeof dndGroupByControl = {
|
export const dndEntityControl: typeof dndGroupByControl = {
|
||||||
...dndGroupByControl,
|
...dndGroupByControl,
|
||||||
label: t('Entity'),
|
label: t('Entity'),
|
||||||
default: null,
|
default: null,
|
||||||
|
@ -91,7 +132,9 @@ export const dndEntity: typeof dndGroupByControl = {
|
||||||
description: t('This defines the element to be plotted on the chart'),
|
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',
|
type: 'DndFilterSelect',
|
||||||
label: t('Filters'),
|
label: t('Filters'),
|
||||||
default: [],
|
default: [],
|
||||||
|
@ -109,7 +152,9 @@ export const dnd_adhoc_filters: SharedControlConfig<'DndFilterSelect'> = {
|
||||||
provideFormDataToProps: true,
|
provideFormDataToProps: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const dnd_adhoc_metrics: SharedControlConfig<'DndMetricSelect'> = {
|
export const dndAdhocMetricsControl: SharedControlConfig<
|
||||||
|
'DndMetricSelect' | 'MetricsControl'
|
||||||
|
> = {
|
||||||
type: 'DndMetricSelect',
|
type: 'DndMetricSelect',
|
||||||
multi: true,
|
multi: true,
|
||||||
label: t('Metrics'),
|
label: t('Metrics'),
|
||||||
|
@ -123,20 +168,23 @@ export const dnd_adhoc_metrics: SharedControlConfig<'DndMetricSelect'> = {
|
||||||
description: t('One or many metrics to display'),
|
description: t('One or many metrics to display'),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const dnd_adhoc_metric: SharedControlConfig<'DndMetricSelect'> = {
|
export const dndAdhocMetricControl: typeof dndAdhocMetricsControl = {
|
||||||
...dnd_adhoc_metrics,
|
...dndAdhocMetricsControl,
|
||||||
multi: false,
|
multi: false,
|
||||||
label: t('Metric'),
|
label: t('Metric'),
|
||||||
description: t('Metric'),
|
description: t('Metric'),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const dnd_adhoc_metric_2: SharedControlConfig<'DndMetricSelect'> = {
|
export const dndAdhocMetricControl2: typeof dndAdhocMetricControl = {
|
||||||
...dnd_adhoc_metric,
|
...dndAdhocMetricControl,
|
||||||
label: t('Right Axis Metric'),
|
label: t('Right Axis Metric'),
|
||||||
|
clearable: true,
|
||||||
description: t('Choose a metric for right axis'),
|
description: t('Choose a metric for right axis'),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const dnd_sort_by: SharedControlConfig<'DndMetricSelect'> = {
|
export const dndSortByControl: SharedControlConfig<
|
||||||
|
'DndMetricSelect' | 'MetricsControl'
|
||||||
|
> = {
|
||||||
type: 'DndMetricSelect',
|
type: 'DndMetricSelect',
|
||||||
label: t('Sort by'),
|
label: t('Sort by'),
|
||||||
default: null,
|
default: null,
|
||||||
|
@ -152,33 +200,37 @@ export const dnd_sort_by: SharedControlConfig<'DndMetricSelect'> = {
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const dnd_size: SharedControlConfig<'DndMetricSelect'> = {
|
export const dndSizeControl: typeof dndAdhocMetricControl = {
|
||||||
...dnd_adhoc_metric,
|
...dndAdhocMetricControl,
|
||||||
label: t('Bubble Size'),
|
label: t('Bubble Size'),
|
||||||
description: t('Metric used to calculate bubble size'),
|
description: t('Metric used to calculate bubble size'),
|
||||||
|
default: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const dnd_x: SharedControlConfig<'DndMetricSelect'> = {
|
export const dndXControl: typeof dndAdhocMetricControl = {
|
||||||
...dnd_adhoc_metric,
|
...dndAdhocMetricControl,
|
||||||
label: t('X Axis'),
|
label: t('X Axis'),
|
||||||
description: t('Metric assigned to the [X] axis'),
|
description: t('Metric assigned to the [X] axis'),
|
||||||
|
default: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const dnd_y: SharedControlConfig<'DndMetricSelect'> = {
|
export const dndYControl: typeof dndAdhocMetricControl = {
|
||||||
...dnd_adhoc_metric,
|
...dndAdhocMetricControl,
|
||||||
label: t('Y Axis'),
|
label: t('Y Axis'),
|
||||||
description: t('Metric assigned to the [Y] axis'),
|
description: t('Metric assigned to the [Y] axis'),
|
||||||
|
default: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const dnd_secondary_metric: SharedControlConfig<'DndMetricSelect'> = {
|
export const dndSecondaryMetricControl: typeof dndAdhocMetricControl = {
|
||||||
...dnd_adhoc_metric,
|
...dndAdhocMetricControl,
|
||||||
label: t('Color Metric'),
|
label: t('Color Metric'),
|
||||||
|
default: null,
|
||||||
validators: [],
|
validators: [],
|
||||||
description: t('A metric to use for color'),
|
description: t('A metric to use for color'),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const dnd_granularity_sqla: typeof dndGroupByControl = {
|
export const dndGranularitySqlaControl: typeof dndSeriesControl = {
|
||||||
...dndSeries,
|
...dndSeriesControl,
|
||||||
label: TIME_FILTER_LABELS.granularity_sqla,
|
label: TIME_FILTER_LABELS.granularity_sqla,
|
||||||
description: t(
|
description: t(
|
||||||
'The time column for the visualization. Note that you ' +
|
'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 ' +
|
'filter below is applied against this column or ' +
|
||||||
'expression',
|
'expression',
|
||||||
),
|
),
|
||||||
|
default: (c: Control) => c.default,
|
||||||
|
clearable: false,
|
||||||
canDelete: false,
|
canDelete: false,
|
||||||
ghostButtonText: t(
|
ghostButtonText: t('Drop temporal column here'),
|
||||||
isFeatureEnabled(FeatureFlag.ENABLE_DND_WITH_CLICK_UX)
|
clickEnabledGhostButtonText: t('Drop a temporal column here or click'),
|
||||||
? 'Drop a temporal column here or click'
|
optionRenderer: (c: ColumnMeta) => <ColumnOption showType column={c} />,
|
||||||
: 'Drop temporal column here',
|
valueRenderer: (c: ColumnMeta) => <ColumnOption column={c} />,
|
||||||
),
|
valueKey: 'column_name',
|
||||||
mapStateToProps: ({ datasource }) => {
|
mapStateToProps: ({ datasource }) => {
|
||||||
if (datasource?.columns[0]?.hasOwnProperty('column_name')) {
|
if (datasource?.columns[0]?.hasOwnProperty('column_name')) {
|
||||||
const temporalColumns =
|
const temporalColumns =
|
||||||
(datasource as Dataset)?.columns?.filter(c => c.is_dttm) ?? [];
|
(datasource as Dataset)?.columns?.filter(c => c.is_dttm) ?? [];
|
||||||
const options = Object.fromEntries(
|
|
||||||
temporalColumns.map(option => [option.column_name, option]),
|
|
||||||
);
|
|
||||||
return {
|
return {
|
||||||
options,
|
options: temporalColumns,
|
||||||
default:
|
default:
|
||||||
(datasource as Dataset)?.main_dttm_col ||
|
(datasource as Dataset)?.main_dttm_col ||
|
||||||
temporalColumns[0]?.column_name ||
|
temporalColumns[0]?.column_name ||
|
||||||
|
@ -209,22 +260,36 @@ export const dnd_granularity_sqla: typeof dndGroupByControl = {
|
||||||
isTemporal: true,
|
isTemporal: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const sortedQueryColumns = (datasource as QueryResponse)?.columns?.sort(
|
const sortedQueryColumns = (datasource as QueryResponse)?.columns?.sort(
|
||||||
query => (query?.is_dttm ? -1 : 1),
|
query => (query?.is_dttm ? -1 : 1),
|
||||||
);
|
);
|
||||||
const options = Object.fromEntries(
|
|
||||||
sortedQueryColumns.map(option => [option.name, option]),
|
|
||||||
);
|
|
||||||
return {
|
return {
|
||||||
options,
|
options: sortedQueryColumns,
|
||||||
default: sortedQueryColumns[0]?.name || null,
|
default: sortedQueryColumns[0]?.name || null,
|
||||||
isTemporal: true,
|
isTemporal: true,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const dnd_x_axis: SharedControlConfig<'DndColumnSelect'> = {
|
export const dndXAxisControl: typeof dndGroupByControl = {
|
||||||
...dndGroupByControl,
|
...dndGroupByControl,
|
||||||
...xAxisControlConfig,
|
...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
|
* here's a list of the keys that are common to all controls, and as a result define the
|
||||||
* control interface.
|
* control interface.
|
||||||
*/
|
*/
|
||||||
import React from 'react';
|
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
import {
|
import {
|
||||||
FeatureFlag,
|
FeatureFlag,
|
||||||
|
@ -43,10 +42,7 @@ import {
|
||||||
isFeatureEnabled,
|
isFeatureEnabled,
|
||||||
SequentialScheme,
|
SequentialScheme,
|
||||||
legacyValidateInteger,
|
legacyValidateInteger,
|
||||||
validateNonEmpty,
|
|
||||||
ComparisionType,
|
ComparisionType,
|
||||||
QueryResponse,
|
|
||||||
QueryColumn,
|
|
||||||
isAdhocColumn,
|
isAdhocColumn,
|
||||||
isPhysicalColumn,
|
isPhysicalColumn,
|
||||||
} from '@superset-ui/core';
|
} from '@superset-ui/core';
|
||||||
|
@ -59,38 +55,29 @@ import {
|
||||||
D3_TIME_FORMAT_DOCS,
|
D3_TIME_FORMAT_DOCS,
|
||||||
DEFAULT_TIME_FORMAT,
|
DEFAULT_TIME_FORMAT,
|
||||||
DEFAULT_NUMBER_FORMAT,
|
DEFAULT_NUMBER_FORMAT,
|
||||||
defineSavedMetrics,
|
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import { TIME_FILTER_LABELS, DATASET_TIME_COLUMN_OPTION } from '../constants';
|
import { TIME_FILTER_LABELS } from '../constants';
|
||||||
import {
|
import { SharedControlConfig, Dataset } from '../types';
|
||||||
Metric,
|
|
||||||
SharedControlConfig,
|
|
||||||
ColumnMeta,
|
|
||||||
ExtraControlProps,
|
|
||||||
SelectControlConfig,
|
|
||||||
Dataset,
|
|
||||||
} from '../types';
|
|
||||||
import { ColumnOption } from '../components/ColumnOption';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
dnd_adhoc_filters,
|
dndAdhocFilterControl,
|
||||||
dnd_adhoc_metric,
|
dndAdhocMetricControl,
|
||||||
dnd_adhoc_metrics,
|
dndAdhocMetricsControl,
|
||||||
dnd_granularity_sqla,
|
dndGranularitySqlaControl,
|
||||||
dnd_sort_by,
|
dndSortByControl,
|
||||||
dnd_secondary_metric,
|
dndSecondaryMetricControl,
|
||||||
dnd_size,
|
dndSizeControl,
|
||||||
dnd_x,
|
dndXControl,
|
||||||
dnd_y,
|
dndYControl,
|
||||||
dndColumnsControl,
|
dndColumnsControl,
|
||||||
dndEntity,
|
dndEntityControl,
|
||||||
dndGroupByControl,
|
dndGroupByControl,
|
||||||
dndSeries,
|
dndSeriesControl,
|
||||||
dnd_adhoc_metric_2,
|
dndAdhocMetricControl2,
|
||||||
dnd_x_axis,
|
dndXAxisControl,
|
||||||
} from './dndControls';
|
} from './dndControls';
|
||||||
import { QUERY_TIME_COLUMN_OPTION } from '..';
|
|
||||||
import { xAxisControlConfig } from './constants';
|
export { withDndFallback } from './dndControls';
|
||||||
|
|
||||||
const categoricalSchemeRegistry = getCategoricalSchemeRegistry();
|
const categoricalSchemeRegistry = getCategoricalSchemeRegistry();
|
||||||
const sequentialSchemeRegistry = getSequentialSchemeRegistry();
|
const sequentialSchemeRegistry = getSequentialSchemeRegistry();
|
||||||
|
@ -105,77 +92,11 @@ const { user } = JSON.parse(
|
||||||
appContainer?.getAttribute('data-bootstrap') || '{}',
|
appContainer?.getAttribute('data-bootstrap') || '{}',
|
||||||
);
|
);
|
||||||
|
|
||||||
type Control = {
|
|
||||||
savedMetrics?: Metric[] | null;
|
|
||||||
default?: unknown;
|
|
||||||
};
|
|
||||||
|
|
||||||
type SelectDefaultOption = {
|
type SelectDefaultOption = {
|
||||||
label: string;
|
label: string;
|
||||||
value: 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'> = {
|
const datasourceControl: SharedControlConfig<'DatasourceControl'> = {
|
||||||
type: 'DatasourceControl',
|
type: 'DatasourceControl',
|
||||||
label: t('Datasource'),
|
label: t('Datasource'),
|
||||||
|
@ -203,13 +124,6 @@ const color_picker: SharedControlConfig<'ColorPickerControl'> = {
|
||||||
renderTrigger: true,
|
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'> = {
|
const linear_color_scheme: SharedControlConfig<'ColorSchemeControl'> = {
|
||||||
type: 'ColorSchemeControl',
|
type: 'ColorSchemeControl',
|
||||||
label: t('Linear Color Scheme'),
|
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'> = {
|
const granularity: SharedControlConfig<'SelectControl'> = {
|
||||||
type: 'SelectControl',
|
type: 'SelectControl',
|
||||||
freeForm: true,
|
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'> = {
|
const time_grain_sqla: SharedControlConfig<'SelectControl'> = {
|
||||||
type: 'SelectControl',
|
type: 'SelectControl',
|
||||||
label: TIME_FILTER_LABELS.time_grain_sqla,
|
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> =
|
const y_axis_format: SharedControlConfig<'SelectControl', SelectDefaultOption> =
|
||||||
{
|
{
|
||||||
type: 'SelectControl',
|
type: 'SelectControl',
|
||||||
|
@ -507,23 +311,6 @@ const x_axis_time_format: SharedControlConfig<
|
||||||
option.label.includes(search) || option.value.includes(search),
|
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'> = {
|
const color_scheme: SharedControlConfig<'ColorSchemeControl'> = {
|
||||||
type: 'ColorSchemeControl',
|
type: 'ColorSchemeControl',
|
||||||
label: t('Color Scheme'),
|
label: t('Color Scheme'),
|
||||||
|
@ -551,51 +338,40 @@ const show_empty_columns: SharedControlConfig<'CheckboxControl'> = {
|
||||||
description: t('Show empty columns'),
|
description: t('Show empty columns'),
|
||||||
};
|
};
|
||||||
|
|
||||||
const x_axis: SharedControlConfig<'SelectControl', ColumnMeta> = {
|
export default {
|
||||||
...groupByControl,
|
metrics: dndAdhocMetricsControl,
|
||||||
...xAxisControlConfig,
|
metric: dndAdhocMetricControl,
|
||||||
};
|
|
||||||
|
|
||||||
const enableExploreDnd = isFeatureEnabled(
|
|
||||||
FeatureFlag.ENABLE_EXPLORE_DRAG_AND_DROP,
|
|
||||||
);
|
|
||||||
|
|
||||||
const sharedControls = {
|
|
||||||
metrics: enableExploreDnd ? dnd_adhoc_metrics : metrics,
|
|
||||||
metric: enableExploreDnd ? dnd_adhoc_metric : metric,
|
|
||||||
datasource: datasourceControl,
|
datasource: datasourceControl,
|
||||||
viz_type,
|
viz_type,
|
||||||
color_picker,
|
color_picker,
|
||||||
metric_2: enableExploreDnd ? dnd_adhoc_metric_2 : metric_2,
|
metric_2: dndAdhocMetricControl2,
|
||||||
linear_color_scheme,
|
linear_color_scheme,
|
||||||
secondary_metric: enableExploreDnd ? dnd_secondary_metric : secondary_metric,
|
secondary_metric: dndSecondaryMetricControl,
|
||||||
groupby: enableExploreDnd ? dndGroupByControl : groupByControl,
|
groupby: dndGroupByControl,
|
||||||
columns: enableExploreDnd ? dndColumnsControl : columnsControl,
|
columns: dndColumnsControl,
|
||||||
granularity,
|
granularity,
|
||||||
granularity_sqla: enableExploreDnd ? dnd_granularity_sqla : granularity_sqla,
|
granularity_sqla: dndGranularitySqlaControl,
|
||||||
time_grain_sqla,
|
time_grain_sqla,
|
||||||
time_range,
|
time_range,
|
||||||
row_limit,
|
row_limit,
|
||||||
limit,
|
limit,
|
||||||
timeseries_limit_metric: enableExploreDnd ? dnd_sort_by : sort_by,
|
timeseries_limit_metric: dndSortByControl,
|
||||||
orderby: enableExploreDnd ? dnd_sort_by : sort_by,
|
orderby: dndSortByControl,
|
||||||
order_desc,
|
order_desc,
|
||||||
series: enableExploreDnd ? dndSeries : series,
|
series: dndSeriesControl,
|
||||||
entity: enableExploreDnd ? dndEntity : entity,
|
entity: dndEntityControl,
|
||||||
x: enableExploreDnd ? dnd_x : x,
|
x: dndXControl,
|
||||||
y: enableExploreDnd ? dnd_y : y,
|
y: dndYControl,
|
||||||
size: enableExploreDnd ? dnd_size : size,
|
size: dndSizeControl,
|
||||||
y_axis_format,
|
y_axis_format,
|
||||||
x_axis_time_format,
|
x_axis_time_format,
|
||||||
adhoc_filters: enableExploreDnd ? dnd_adhoc_filters : adhoc_filters,
|
adhoc_filters: dndAdhocFilterControl,
|
||||||
color_scheme,
|
color_scheme,
|
||||||
series_columns: enableExploreDnd ? dndColumnsControl : columnsControl,
|
series_columns: dndColumnsControl,
|
||||||
series_limit,
|
series_limit,
|
||||||
series_limit_metric: enableExploreDnd ? dnd_sort_by : sort_by,
|
series_limit_metric: dndSortByControl,
|
||||||
legacy_order_by: enableExploreDnd ? dnd_sort_by : sort_by,
|
legacy_order_by: dndSortByControl,
|
||||||
truncate_metric,
|
truncate_metric,
|
||||||
x_axis: enableExploreDnd ? dnd_x_axis : x_axis,
|
x_axis: dndXAxisControl,
|
||||||
show_empty_columns,
|
show_empty_columns,
|
||||||
};
|
};
|
||||||
|
|
||||||
export { sharedControls, dndEntity, dndColumnsControl };
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ import type {
|
||||||
QueryFormMetric,
|
QueryFormMetric,
|
||||||
QueryResponse,
|
QueryResponse,
|
||||||
} from '@superset-ui/core';
|
} from '@superset-ui/core';
|
||||||
import { sharedControls } from './shared-controls';
|
import sharedControls from './shared-controls';
|
||||||
import sharedControlComponents from './shared-controls/components';
|
import sharedControlComponents from './shared-controls/components';
|
||||||
|
|
||||||
export type { Metric } from '@superset-ui/core';
|
export type { Metric } from '@superset-ui/core';
|
||||||
|
@ -238,7 +238,7 @@ export interface BaseControlConfig<
|
||||||
) => boolean;
|
) => boolean;
|
||||||
mapStateToProps?: (
|
mapStateToProps?: (
|
||||||
state: ControlPanelState,
|
state: ControlPanelState,
|
||||||
controlState: ControlState,
|
controlState?: ControlState,
|
||||||
// TODO: add strict `chartState` typing (see superset-frontend/src/explore/types)
|
// TODO: add strict `chartState` typing (see superset-frontend/src/explore/types)
|
||||||
chartState?: AnyDict,
|
chartState?: AnyDict,
|
||||||
) => ExtraControlProps;
|
) => ExtraControlProps;
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import React, { ReactElement } from 'react';
|
import React, { ReactElement } from 'react';
|
||||||
import { sharedControls } from '../shared-controls';
|
import sharedControls from '../shared-controls';
|
||||||
import sharedControlComponents from '../shared-controls/components';
|
import sharedControlComponents from '../shared-controls/components';
|
||||||
import {
|
import {
|
||||||
ControlType,
|
ControlType,
|
||||||
|
|
|
@ -30,7 +30,7 @@ import {
|
||||||
formatSelectOptions,
|
formatSelectOptions,
|
||||||
formatSelectOptionsForRange,
|
formatSelectOptionsForRange,
|
||||||
sections,
|
sections,
|
||||||
dndEntity,
|
sharedControls,
|
||||||
getStandardizedControls,
|
getStandardizedControls,
|
||||||
} from '@superset-ui/chart-controls';
|
} from '@superset-ui/chart-controls';
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ const allColumns = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const dndAllColumns = {
|
const dndAllColumns = {
|
||||||
...dndEntity,
|
...sharedControls.entity,
|
||||||
description: t('Columns to display'),
|
description: t('Columns to display'),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -16,44 +16,28 @@
|
||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import {
|
import { t, validateNonEmpty } from '@superset-ui/core';
|
||||||
FeatureFlag,
|
|
||||||
isFeatureEnabled,
|
|
||||||
t,
|
|
||||||
validateNonEmpty,
|
|
||||||
} from '@superset-ui/core';
|
|
||||||
import {
|
import {
|
||||||
columnChoices,
|
columnChoices,
|
||||||
ControlPanelConfig,
|
ControlPanelConfig,
|
||||||
ControlPanelState,
|
ControlPanelState,
|
||||||
formatSelectOptions,
|
formatSelectOptions,
|
||||||
sections,
|
sections,
|
||||||
dndColumnsControl,
|
|
||||||
getStandardizedControls,
|
getStandardizedControls,
|
||||||
|
sharedControls,
|
||||||
} from '@superset-ui/chart-controls';
|
} from '@superset-ui/chart-controls';
|
||||||
|
|
||||||
const allColumns = {
|
const columnsConfig = {
|
||||||
type: 'SelectControl',
|
...sharedControls.columns,
|
||||||
label: t('Columns'),
|
label: t('Columns'),
|
||||||
default: null,
|
|
||||||
description: t('Select the numeric columns to draw the histogram'),
|
description: t('Select the numeric columns to draw the histogram'),
|
||||||
mapStateToProps: (state: ControlPanelState) => ({
|
mapStateToProps: (state: ControlPanelState) => ({
|
||||||
|
...(sharedControls.columns.mapStateToProps?.(state) || {}),
|
||||||
choices: columnChoices(state.datasource),
|
choices: columnChoices(state.datasource),
|
||||||
}),
|
}),
|
||||||
multi: true,
|
|
||||||
validators: [validateNonEmpty],
|
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 = {
|
const config: ControlPanelConfig = {
|
||||||
controlPanelSections: [
|
controlPanelSections: [
|
||||||
sections.legacyRegularTime,
|
sections.legacyRegularTime,
|
||||||
|
|
|
@ -23,7 +23,7 @@ import {
|
||||||
ControlPanelState,
|
ControlPanelState,
|
||||||
formatSelectOptions,
|
formatSelectOptions,
|
||||||
sections,
|
sections,
|
||||||
dndEntity,
|
sharedControls,
|
||||||
getStandardizedControls,
|
getStandardizedControls,
|
||||||
} from '@superset-ui/chart-controls';
|
} from '@superset-ui/chart-controls';
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ const allColumns = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const columnsConfig = isFeatureEnabled(FeatureFlag.ENABLE_EXPLORE_DRAG_AND_DROP)
|
const columnsConfig = isFeatureEnabled(FeatureFlag.ENABLE_EXPLORE_DRAG_AND_DROP)
|
||||||
? dndEntity
|
? sharedControls.entity
|
||||||
: allColumns;
|
: allColumns;
|
||||||
|
|
||||||
const colorChoices = [
|
const colorChoices = [
|
||||||
|
|
|
@ -18,12 +18,12 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { t } from '@superset-ui/core';
|
import { t } from '@superset-ui/core';
|
||||||
import { dndEntity } from '@superset-ui/chart-controls';
|
import { sharedControls } from '@superset-ui/chart-controls';
|
||||||
|
|
||||||
export const dndLineColumn = {
|
export const dndLineColumn = {
|
||||||
name: 'line_column',
|
name: 'line_column',
|
||||||
config: {
|
config: {
|
||||||
...dndEntity,
|
...sharedControls.entity,
|
||||||
label: t('Lines column'),
|
label: t('Lines column'),
|
||||||
description: t('The database columns that contains lines information'),
|
description: t('The database columns that contains lines information'),
|
||||||
},
|
},
|
||||||
|
@ -32,7 +32,7 @@ export const dndLineColumn = {
|
||||||
export const dndGeojsonColumn = {
|
export const dndGeojsonColumn = {
|
||||||
name: 'geojson',
|
name: 'geojson',
|
||||||
config: {
|
config: {
|
||||||
...dndEntity,
|
...sharedControls.entity,
|
||||||
label: t('GeoJson Column'),
|
label: t('GeoJson Column'),
|
||||||
description: t('Select the geojson column'),
|
description: t('Select the geojson column'),
|
||||||
},
|
},
|
||||||
|
|
|
@ -64,7 +64,7 @@ const config: ControlPanelConfig = {
|
||||||
controlState,
|
controlState,
|
||||||
) || {};
|
) || {};
|
||||||
timeserieslimitProps.value = state.controls?.limit?.value
|
timeserieslimitProps.value = state.controls?.limit?.value
|
||||||
? controlState.value
|
? controlState?.value
|
||||||
: [];
|
: [];
|
||||||
return timeserieslimitProps;
|
return timeserieslimitProps;
|
||||||
},
|
},
|
||||||
|
|
|
@ -345,7 +345,7 @@ const config: ControlPanelConfig = {
|
||||||
controlState,
|
controlState,
|
||||||
) || {};
|
) || {};
|
||||||
timeserieslimitProps.value = state.controls?.limit?.value
|
timeserieslimitProps.value = state.controls?.limit?.value
|
||||||
? controlState.value
|
? controlState?.value
|
||||||
: [];
|
: [];
|
||||||
return timeserieslimitProps;
|
return timeserieslimitProps;
|
||||||
},
|
},
|
||||||
|
|
|
@ -49,7 +49,7 @@ const allColumns: typeof sharedControls.groupby = {
|
||||||
options: datasource?.columns || [],
|
options: datasource?.columns || [],
|
||||||
queryMode: getQueryMode(controls),
|
queryMode: getQueryMode(controls),
|
||||||
externalValidationErrors:
|
externalValidationErrors:
|
||||||
isRawMode({ controls }) && ensureIsArray(controlState.value).length === 0
|
isRawMode({ controls }) && ensureIsArray(controlState?.value).length === 0
|
||||||
? [t('must have a value')]
|
? [t('must have a value')]
|
||||||
: [],
|
: [],
|
||||||
}),
|
}),
|
||||||
|
@ -74,7 +74,7 @@ const dndAllColumns: typeof sharedControls.groupby = {
|
||||||
}
|
}
|
||||||
newState.queryMode = getQueryMode(controls);
|
newState.queryMode = getQueryMode(controls);
|
||||||
newState.externalValidationErrors =
|
newState.externalValidationErrors =
|
||||||
isRawMode({ controls }) && ensureIsArray(controlState.value).length === 0
|
isRawMode({ controls }) && ensureIsArray(controlState?.value).length === 0
|
||||||
? [t('must have a value')]
|
? [t('must have a value')]
|
||||||
: [];
|
: [];
|
||||||
return newState;
|
return newState;
|
||||||
|
|
|
@ -46,7 +46,7 @@ const percentMetrics: typeof sharedControls.metrics = {
|
||||||
externalValidationErrors: validateAggControlValues(controls, [
|
externalValidationErrors: validateAggControlValues(controls, [
|
||||||
controls.groupby?.value,
|
controls.groupby?.value,
|
||||||
controls.metrics?.value,
|
controls.metrics?.value,
|
||||||
controlState.value,
|
controlState?.value,
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
rerender: ['groupby', 'metrics'],
|
rerender: ['groupby', 'metrics'],
|
||||||
|
|
|
@ -40,7 +40,6 @@ import {
|
||||||
sections,
|
sections,
|
||||||
sharedControls,
|
sharedControls,
|
||||||
ControlPanelState,
|
ControlPanelState,
|
||||||
ExtraControlProps,
|
|
||||||
ControlState,
|
ControlState,
|
||||||
emitFilterControl,
|
emitFilterControl,
|
||||||
Dataset,
|
Dataset,
|
||||||
|
@ -96,15 +95,14 @@ const queryMode: ControlConfig<'RadioButtonControl'> = {
|
||||||
rerender: ['all_columns', 'groupby', 'metrics', 'percent_metrics'],
|
rerender: ['all_columns', 'groupby', 'metrics', 'percent_metrics'],
|
||||||
};
|
};
|
||||||
|
|
||||||
const all_columns: typeof sharedControls.groupby = {
|
const allColumnsControl: typeof sharedControls.groupby = {
|
||||||
type: 'SelectControl',
|
...sharedControls.groupby,
|
||||||
label: t('Columns'),
|
label: t('Columns'),
|
||||||
description: t('Columns to display'),
|
description: t('Columns to display'),
|
||||||
multi: true,
|
multi: true,
|
||||||
freeForm: true,
|
freeForm: true,
|
||||||
allowAll: true,
|
allowAll: true,
|
||||||
commaChoosesOption: false,
|
commaChoosesOption: false,
|
||||||
default: [],
|
|
||||||
optionRenderer: c => <ColumnOption showType column={c} />,
|
optionRenderer: c => <ColumnOption showType column={c} />,
|
||||||
valueRenderer: c => <ColumnOption column={c} />,
|
valueRenderer: c => <ColumnOption column={c} />,
|
||||||
valueKey: 'column_name',
|
valueKey: 'column_name',
|
||||||
|
@ -112,7 +110,7 @@ const all_columns: typeof sharedControls.groupby = {
|
||||||
options: datasource?.columns || [],
|
options: datasource?.columns || [],
|
||||||
queryMode: getQueryMode(controls),
|
queryMode: getQueryMode(controls),
|
||||||
externalValidationErrors:
|
externalValidationErrors:
|
||||||
isRawMode({ controls }) && ensureIsArray(controlState.value).length === 0
|
isRawMode({ controls }) && ensureIsArray(controlState?.value).length === 0
|
||||||
? [t('must have a value')]
|
? [t('must have a value')]
|
||||||
: [],
|
: [],
|
||||||
}),
|
}),
|
||||||
|
@ -120,37 +118,12 @@ const all_columns: typeof sharedControls.groupby = {
|
||||||
resetOnHide: false,
|
resetOnHide: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const dnd_all_columns: typeof sharedControls.groupby = {
|
const percentMetricsControl: typeof sharedControls.metrics = {
|
||||||
type: 'DndColumnSelect',
|
...sharedControls.metrics,
|
||||||
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',
|
|
||||||
label: t('Percentage metrics'),
|
label: t('Percentage metrics'),
|
||||||
description: t(
|
description: t(
|
||||||
'Metrics for which percentage of total are to be displayed. Calculated from only data within the row limit.',
|
'Metrics for which percentage of total are to be displayed. Calculated from only data within the row limit.',
|
||||||
),
|
),
|
||||||
multi: true,
|
|
||||||
visibility: isAggMode,
|
visibility: isAggMode,
|
||||||
resetOnHide: false,
|
resetOnHide: false,
|
||||||
mapStateToProps: ({ datasource, controls }, controlState) => ({
|
mapStateToProps: ({ datasource, controls }, controlState) => ({
|
||||||
|
@ -162,7 +135,7 @@ const percent_metrics: typeof sharedControls.metrics = {
|
||||||
externalValidationErrors: validateAggControlValues(controls, [
|
externalValidationErrors: validateAggControlValues(controls, [
|
||||||
controls.groupby?.value,
|
controls.groupby?.value,
|
||||||
controls.metrics?.value,
|
controls.metrics?.value,
|
||||||
controlState.value,
|
controlState?.value,
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
rerender: ['groupby', 'metrics'],
|
rerender: ['groupby', 'metrics'],
|
||||||
|
@ -170,11 +143,6 @@ const percent_metrics: typeof sharedControls.metrics = {
|
||||||
validators: [],
|
validators: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const dnd_percent_metrics = {
|
|
||||||
...percent_metrics,
|
|
||||||
type: 'DndMetricSelect',
|
|
||||||
};
|
|
||||||
|
|
||||||
const config: ControlPanelConfig = {
|
const config: ControlPanelConfig = {
|
||||||
controlPanelSections: [
|
controlPanelSections: [
|
||||||
sections.legacyTimeseriesTime,
|
sections.legacyTimeseriesTime,
|
||||||
|
@ -251,19 +219,13 @@ const config: ControlPanelConfig = {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'all_columns',
|
name: 'all_columns',
|
||||||
config: isFeatureEnabled(FeatureFlag.ENABLE_EXPLORE_DRAG_AND_DROP)
|
config: allColumnsControl,
|
||||||
? dnd_all_columns
|
|
||||||
: all_columns,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
name: 'percent_metrics',
|
name: 'percent_metrics',
|
||||||
config: {
|
config: percentMetricsControl,
|
||||||
...(isFeatureEnabled(FeatureFlag.ENABLE_EXPLORE_DRAG_AND_DROP)
|
|
||||||
? dnd_percent_metrics
|
|
||||||
: percent_metrics),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
['adhoc_filters'],
|
['adhoc_filters'],
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
import { FeatureFlag } from '@superset-ui/core';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { render, screen } from 'spec/helpers/testing-library';
|
import { render, screen } from 'spec/helpers/testing-library';
|
||||||
import {
|
import {
|
||||||
|
@ -27,12 +28,18 @@ const defaultProps: DndColumnSelectProps = {
|
||||||
type: 'DndColumnSelect',
|
type: 'DndColumnSelect',
|
||||||
name: 'Filter',
|
name: 'Filter',
|
||||||
onChange: jest.fn(),
|
onChange: jest.fn(),
|
||||||
options: {
|
options: [{ column_name: 'Column A' }],
|
||||||
string: { column_name: 'Column A' },
|
|
||||||
},
|
|
||||||
actions: { setControlValue: jest.fn() },
|
actions: { setControlValue: jest.fn() },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
window.featureFlags = { [FeatureFlag.ENABLE_EXPLORE_DRAG_AND_DROP]: true };
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
window.featureFlags = {};
|
||||||
|
});
|
||||||
|
|
||||||
test('renders with default props', async () => {
|
test('renders with default props', async () => {
|
||||||
render(<DndColumnSelect {...defaultProps} />, {
|
render(<DndColumnSelect {...defaultProps} />, {
|
||||||
useDnd: true,
|
useDnd: true,
|
||||||
|
@ -42,7 +49,7 @@ test('renders with default props', async () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('renders with value', async () => {
|
test('renders with value', async () => {
|
||||||
render(<DndColumnSelect {...defaultProps} value="string" />, {
|
render(<DndColumnSelect {...defaultProps} value="Column A" />, {
|
||||||
useDnd: true,
|
useDnd: true,
|
||||||
useRedux: true,
|
useRedux: true,
|
||||||
});
|
});
|
||||||
|
|
|
@ -24,7 +24,11 @@ import {
|
||||||
tn,
|
tn,
|
||||||
QueryFormColumn,
|
QueryFormColumn,
|
||||||
} from '@superset-ui/core';
|
} 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 { isEmpty } from 'lodash';
|
||||||
import DndSelectLabel from 'src/explore/components/controls/DndColumnSelectControl/DndSelectLabel';
|
import DndSelectLabel from 'src/explore/components/controls/DndColumnSelectControl/DndSelectLabel';
|
||||||
import OptionWrapper from 'src/explore/components/controls/DndColumnSelectControl/OptionWrapper';
|
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 { useComponentDidUpdate } from 'src/hooks/useComponentDidUpdate';
|
||||||
import ColumnSelectPopoverTrigger from './ColumnSelectPopoverTrigger';
|
import ColumnSelectPopoverTrigger from './ColumnSelectPopoverTrigger';
|
||||||
import { DndControlProps } from './types';
|
import { DndControlProps } from './types';
|
||||||
|
import SelectControl from '../SelectControl';
|
||||||
|
|
||||||
export type DndColumnSelectProps = DndControlProps<QueryFormColumn> & {
|
export type DndColumnSelectProps = DndControlProps<QueryFormColumn> & {
|
||||||
options: Record<string, ColumnMeta>;
|
options: ColumnMeta[];
|
||||||
isTemporal?: boolean;
|
isTemporal?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function DndColumnSelect(props: DndColumnSelectProps) {
|
function DndColumnSelect(props: DndColumnSelectProps) {
|
||||||
const {
|
const {
|
||||||
value,
|
value,
|
||||||
options,
|
options,
|
||||||
|
@ -48,16 +53,20 @@ export function DndColumnSelect(props: DndColumnSelectProps) {
|
||||||
onChange,
|
onChange,
|
||||||
canDelete = true,
|
canDelete = true,
|
||||||
ghostButtonText,
|
ghostButtonText,
|
||||||
|
clickEnabledGhostButtonText,
|
||||||
name,
|
name,
|
||||||
label,
|
label,
|
||||||
isTemporal,
|
isTemporal,
|
||||||
} = props;
|
} = props;
|
||||||
const [newColumnPopoverVisible, setNewColumnPopoverVisible] = useState(false);
|
const [newColumnPopoverVisible, setNewColumnPopoverVisible] = useState(false);
|
||||||
|
|
||||||
const optionSelector = useMemo(
|
const optionSelector = useMemo(() => {
|
||||||
() => new OptionSelector(options, multi, value),
|
const optionsMap = Object.fromEntries(
|
||||||
[multi, options, value],
|
options.map(option => [option.column_name, option]),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return new OptionSelector(optionsMap, multi, value);
|
||||||
|
}, [multi, options, value]);
|
||||||
|
|
||||||
// synchronize values in case of dataset changes
|
// synchronize values in case of dataset changes
|
||||||
const handleOptionsChange = useCallback(() => {
|
const handleOptionsChange = useCallback(() => {
|
||||||
|
@ -126,15 +135,18 @@ export function DndColumnSelect(props: DndColumnSelectProps) {
|
||||||
[onChange, optionSelector],
|
[onChange, optionSelector],
|
||||||
);
|
);
|
||||||
|
|
||||||
const popoverOptions = useMemo(() => Object.values(options), [options]);
|
const clickEnabled = useMemo(
|
||||||
|
() => isFeatureEnabled(FeatureFlag.ENABLE_DND_WITH_CLICK_UX),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
const valuesRenderer = useCallback(
|
const valuesRenderer = useCallback(
|
||||||
() =>
|
() =>
|
||||||
optionSelector.values.map((column, idx) =>
|
optionSelector.values.map((column, idx) =>
|
||||||
isFeatureEnabled(FeatureFlag.ENABLE_DND_WITH_CLICK_UX) ? (
|
clickEnabled ? (
|
||||||
<ColumnSelectPopoverTrigger
|
<ColumnSelectPopoverTrigger
|
||||||
key={idx}
|
key={idx}
|
||||||
columns={popoverOptions}
|
columns={options}
|
||||||
onColumnEdit={newColumn => {
|
onColumnEdit={newColumn => {
|
||||||
if (isColumnMeta(newColumn)) {
|
if (isColumnMeta(newColumn)) {
|
||||||
optionSelector.replace(idx, newColumn.column_name);
|
optionSelector.replace(idx, newColumn.column_name);
|
||||||
|
@ -171,13 +183,15 @@ export function DndColumnSelect(props: DndColumnSelectProps) {
|
||||||
),
|
),
|
||||||
[
|
[
|
||||||
canDelete,
|
canDelete,
|
||||||
|
clickEnabled,
|
||||||
|
isTemporal,
|
||||||
label,
|
label,
|
||||||
name,
|
name,
|
||||||
onChange,
|
onChange,
|
||||||
onClickClose,
|
onClickClose,
|
||||||
onShiftOptions,
|
onShiftOptions,
|
||||||
optionSelector,
|
optionSelector,
|
||||||
popoverOptions,
|
options,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -205,15 +219,24 @@ export function DndColumnSelect(props: DndColumnSelectProps) {
|
||||||
togglePopover(true);
|
togglePopover(true);
|
||||||
}, [togglePopover]);
|
}, [togglePopover]);
|
||||||
|
|
||||||
const defaultGhostButtonText = isFeatureEnabled(
|
const labelGhostButtonText = useMemo(() => {
|
||||||
FeatureFlag.ENABLE_DND_WITH_CLICK_UX,
|
if (clickEnabled) {
|
||||||
)
|
return (
|
||||||
? tn(
|
clickEnabledGhostButtonText ??
|
||||||
'Drop a column here or click',
|
ghostButtonText ??
|
||||||
'Drop columns here or click',
|
tn(
|
||||||
multi ? 2 : 1,
|
'Drop a column here or click',
|
||||||
)
|
'Drop columns here or click',
|
||||||
: tn('Drop column here', 'Drop columns here', multi ? 2 : 1);
|
multi ? 2 : 1,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
ghostButtonText ??
|
||||||
|
tn('Drop column here', 'Drop columns here', multi ? 2 : 1)
|
||||||
|
);
|
||||||
|
}, [clickEnabled, clickEnabledGhostButtonText, ghostButtonText, multi]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
@ -223,16 +246,12 @@ export function DndColumnSelect(props: DndColumnSelectProps) {
|
||||||
valuesRenderer={valuesRenderer}
|
valuesRenderer={valuesRenderer}
|
||||||
accept={DndItemType.Column}
|
accept={DndItemType.Column}
|
||||||
displayGhostButton={multi || optionSelector.values.length === 0}
|
displayGhostButton={multi || optionSelector.values.length === 0}
|
||||||
ghostButtonText={ghostButtonText || defaultGhostButtonText}
|
ghostButtonText={labelGhostButtonText}
|
||||||
onClickGhostButton={
|
onClickGhostButton={clickEnabled ? openPopover : undefined}
|
||||||
isFeatureEnabled(FeatureFlag.ENABLE_DND_WITH_CLICK_UX)
|
|
||||||
? openPopover
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
<ColumnSelectPopoverTrigger
|
<ColumnSelectPopoverTrigger
|
||||||
columns={popoverOptions}
|
columns={options}
|
||||||
onColumnEdit={addNewColumnWithPopover}
|
onColumnEdit={addNewColumnWithPopover}
|
||||||
isControlledComponent
|
isControlledComponent
|
||||||
togglePopover={togglePopover}
|
togglePopover={togglePopover}
|
||||||
|
@ -245,3 +264,10 @@ export function DndColumnSelect(props: DndColumnSelectProps) {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DndColumnSelectWithFallback = withDndFallback(
|
||||||
|
DndColumnSelect,
|
||||||
|
SelectControl,
|
||||||
|
);
|
||||||
|
|
||||||
|
export { DndColumnSelectWithFallback as DndColumnSelect };
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import React from 'react';
|
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 { render, screen } from 'spec/helpers/testing-library';
|
||||||
import AdhocMetric from 'src/explore/components/controls/MetricControl/AdhocMetric';
|
import AdhocMetric from 'src/explore/components/controls/MetricControl/AdhocMetric';
|
||||||
import AdhocFilter, {
|
import AdhocFilter, {
|
||||||
|
@ -48,6 +48,14 @@ const baseFormData = {
|
||||||
datasource: 'table__1',
|
datasource: 'table__1',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
window.featureFlags = { [FeatureFlag.ENABLE_EXPLORE_DRAG_AND_DROP]: true };
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
window.featureFlags = {};
|
||||||
|
});
|
||||||
|
|
||||||
test('renders with default props', async () => {
|
test('renders with default props', async () => {
|
||||||
render(<DndFilterSelect {...defaultProps} />, { useDnd: true });
|
render(<DndFilterSelect {...defaultProps} />, { useDnd: true });
|
||||||
expect(
|
expect(
|
||||||
|
|
|
@ -27,7 +27,7 @@ import {
|
||||||
SupersetClient,
|
SupersetClient,
|
||||||
t,
|
t,
|
||||||
} from '@superset-ui/core';
|
} from '@superset-ui/core';
|
||||||
import { ColumnMeta } from '@superset-ui/chart-controls';
|
import { ColumnMeta, withDndFallback } from '@superset-ui/chart-controls';
|
||||||
import {
|
import {
|
||||||
OPERATOR_ENUM_TO_OPERATOR_TYPE,
|
OPERATOR_ENUM_TO_OPERATOR_TYPE,
|
||||||
Operators,
|
Operators,
|
||||||
|
@ -49,6 +49,7 @@ import {
|
||||||
} from 'src/explore/components/DatasourcePanel/types';
|
} from 'src/explore/components/DatasourcePanel/types';
|
||||||
import { DndItemType } from 'src/explore/components/DndItemType';
|
import { DndItemType } from 'src/explore/components/DndItemType';
|
||||||
import { ControlComponentProps } from 'src/explore/components/Control';
|
import { ControlComponentProps } from 'src/explore/components/Control';
|
||||||
|
import AdhocFilterControl from '../FilterControl/AdhocFilterControl';
|
||||||
|
|
||||||
const EMPTY_OBJECT = {};
|
const EMPTY_OBJECT = {};
|
||||||
const DND_ACCEPTED_TYPES = [
|
const DND_ACCEPTED_TYPES = [
|
||||||
|
@ -69,7 +70,7 @@ export interface DndFilterSelectProps
|
||||||
datasource: Datasource;
|
datasource: Datasource;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DndFilterSelect = (props: DndFilterSelectProps) => {
|
const DndFilterSelect = (props: DndFilterSelectProps) => {
|
||||||
const { datasource, onChange = () => {}, name: controlName } = props;
|
const { datasource, onChange = () => {}, name: controlName } = props;
|
||||||
|
|
||||||
const propsValues = Array.from(props.value ?? []);
|
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 React from 'react';
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
|
import { FeatureFlag } from '@superset-ui/core';
|
||||||
import {
|
import {
|
||||||
render,
|
render,
|
||||||
screen,
|
screen,
|
||||||
|
@ -67,6 +68,14 @@ const adhocMetricB = {
|
||||||
optionName: 'def',
|
optionName: 'def',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
window.featureFlags = { [FeatureFlag.ENABLE_EXPLORE_DRAG_AND_DROP]: true };
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
window.featureFlags = {};
|
||||||
|
});
|
||||||
|
|
||||||
test('renders with default props', () => {
|
test('renders with default props', () => {
|
||||||
render(<DndMetricSelect {...defaultProps} />, { useDnd: true });
|
render(<DndMetricSelect {...defaultProps} />, { useDnd: true });
|
||||||
expect(screen.getByText('Drop column or metric here')).toBeInTheDocument();
|
expect(screen.getByText('Drop column or metric here')).toBeInTheDocument();
|
||||||
|
|
|
@ -27,7 +27,7 @@ import {
|
||||||
QueryFormMetric,
|
QueryFormMetric,
|
||||||
tn,
|
tn,
|
||||||
} from '@superset-ui/core';
|
} from '@superset-ui/core';
|
||||||
import { ColumnMeta } from '@superset-ui/chart-controls';
|
import { ColumnMeta, withDndFallback } from '@superset-ui/chart-controls';
|
||||||
import { isEqual } from 'lodash';
|
import { isEqual } from 'lodash';
|
||||||
import { usePrevious } from 'src/hooks/usePrevious';
|
import { usePrevious } from 'src/hooks/usePrevious';
|
||||||
import AdhocMetric from 'src/explore/components/controls/MetricControl/AdhocMetric';
|
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 DndSelectLabel from 'src/explore/components/controls/DndColumnSelectControl/DndSelectLabel';
|
||||||
import { savedMetricType } from 'src/explore/components/controls/MetricControl/types';
|
import { savedMetricType } from 'src/explore/components/controls/MetricControl/types';
|
||||||
import { AGGREGATES } from 'src/explore/constants';
|
import { AGGREGATES } from 'src/explore/constants';
|
||||||
|
import MetricsControl from '../MetricControl/MetricsControl';
|
||||||
|
|
||||||
const EMPTY_OBJECT = {};
|
const EMPTY_OBJECT = {};
|
||||||
const DND_ACCEPTED_TYPES = [DndItemType.Column, DndItemType.Metric];
|
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 { onChange, multi, columns, savedMetrics } = props;
|
||||||
|
|
||||||
const handleChange = useCallback(
|
const handleChange = useCallback(
|
||||||
|
@ -408,3 +409,10 @@ export const DndMetricSelect = (props: any) => {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const DndMetricSelectWithFallback = withDndFallback(
|
||||||
|
DndMetricSelect,
|
||||||
|
MetricsControl,
|
||||||
|
);
|
||||||
|
|
||||||
|
export { DndMetricSelectWithFallback as DndMetricSelect };
|
||||||
|
|
|
@ -46,6 +46,7 @@ export type DndControlProps<ValueType extends JsonValue> =
|
||||||
multi?: boolean;
|
multi?: boolean;
|
||||||
canDelete?: boolean;
|
canDelete?: boolean;
|
||||||
ghostButtonText?: string;
|
ghostButtonText?: string;
|
||||||
|
clickEnabledGhostButtonText?: string;
|
||||||
onChange: (value: ValueType | ValueType[] | null | undefined) => void;
|
onChange: (value: ValueType | ValueType[] | null | undefined) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue