diff --git a/RESOURCES/FEATURE_FLAGS.md b/RESOURCES/FEATURE_FLAGS.md index b668845a88..5df7c0de71 100644 --- a/RESOURCES/FEATURE_FLAGS.md +++ b/RESOURCES/FEATURE_FLAGS.md @@ -73,8 +73,6 @@ These features flags are **safe for production**. They have been tested and will - DRUID_JOINS - EMBEDDABLE_CHARTS - EMBEDDED_SUPERSET -- ENABLE_DND_WITH_CLICK_UX -- ENABLE_EXPLORE_DRAG_AND_DROP - ENABLE_TEMPLATE_PROCESSING - ESCAPE_MARKDOWN_HTML - LISTVIEWS_DEFAULT_CARD_VIEW @@ -95,6 +93,7 @@ These features flags currently default to True and **will be removed in a future - DASHBOARD_NATIVE_FILTERS - DASHBOARD_NATIVE_FILTERS_SET - DISABLE_DATASET_SOURCE_EDIT +- ENABLE_EXPLORE_DRAG_AND_DROP - ENABLE_EXPLORE_JSON_CSRF_PROTECTION - GENERIC_CHART_AXES - REMOVE_SLICE_LEVEL_LABEL_COLORS diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/dndControls.tsx b/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/dndControls.tsx index 840c5c3611..c915e8a89d 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/dndControls.tsx +++ b/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/dndControls.tsx @@ -244,8 +244,7 @@ export const dndGranularitySqlaControl: typeof dndSeriesControl = { default: (c: Control) => c.default, clearable: false, canDelete: false, - ghostButtonText: t('Drop temporal column here'), - clickEnabledGhostButtonText: t('Drop a temporal column here or click'), + ghostButtonText: t('Drop a temporal column here or click'), optionRenderer: (c: ColumnMeta) => , valueRenderer: (c: ColumnMeta) => , valueKey: 'column_name', diff --git a/superset-frontend/packages/superset-ui-core/src/utils/featureFlags.ts b/superset-frontend/packages/superset-ui-core/src/utils/featureFlags.ts index fbb1efd340..2cf67b080c 100644 --- a/superset-frontend/packages/superset-ui-core/src/utils/featureFlags.ts +++ b/superset-frontend/packages/superset-ui-core/src/utils/featureFlags.ts @@ -40,7 +40,6 @@ export enum FeatureFlag { EMBEDDABLE_CHARTS = 'EMBEDDABLE_CHARTS', EMBEDDED_SUPERSET = 'EMBEDDED_SUPERSET', ENABLE_ADVANCED_DATA_TYPES = 'ENABLE_ADVANCED_DATA_TYPES', - ENABLE_DND_WITH_CLICK_UX = 'ENABLE_DND_WITH_CLICK_UX', ENABLE_EXPLORE_DRAG_AND_DROP = 'ENABLE_EXPLORE_DRAG_AND_DROP', ENABLE_JAVASCRIPT_CONTROLS = 'ENABLE_JAVASCRIPT_CONTROLS', ENABLE_TEMPLATE_PROCESSING = 'ENABLE_TEMPLATE_PROCESSING', diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelect.test.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelect.test.tsx index 5cf5c59fd9..8eb068c022 100644 --- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelect.test.tsx +++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelect.test.tsx @@ -45,7 +45,9 @@ test('renders with default props', async () => { useDnd: true, useRedux: true, }); - expect(await screen.findByText('Drop columns here')).toBeInTheDocument(); + expect( + await screen.findByText('Drop columns here or click'), + ).toBeInTheDocument(); }); test('renders with value', async () => { diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelect.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelect.tsx index 071fdbd424..673861458d 100644 --- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelect.tsx +++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelect.tsx @@ -19,8 +19,6 @@ import React, { useCallback, useMemo, useState } from 'react'; import { AdhocColumn, - FeatureFlag, - isFeatureEnabled, tn, QueryFormColumn, t, @@ -54,7 +52,6 @@ function DndColumnSelect(props: DndColumnSelectProps) { onChange, canDelete = true, ghostButtonText, - clickEnabledGhostButtonText, name, label, isTemporal, @@ -108,11 +105,6 @@ function DndColumnSelect(props: DndColumnSelectProps) { [onChange, optionSelector], ); - const clickEnabled = useMemo( - () => isFeatureEnabled(FeatureFlag.ENABLE_DND_WITH_CLICK_UX), - [], - ); - const valuesRenderer = useCallback( () => optionSelector.values.map((column, idx) => { @@ -120,7 +112,7 @@ function DndColumnSelect(props: DndColumnSelectProps) { isAdhocColumn(column) && column.datasourceWarning ? t('This column might be incompatible with current dataset') : undefined; - return clickEnabled ? ( + return ( - ) : ( - ); }), [ canDelete, - clickEnabled, isTemporal, label, name, @@ -198,24 +178,16 @@ function DndColumnSelect(props: DndColumnSelectProps) { togglePopover(true); }, [togglePopover]); - const labelGhostButtonText = useMemo(() => { - if (clickEnabled) { - return ( - clickEnabledGhostButtonText ?? - ghostButtonText ?? - tn( - 'Drop a column here or click', - 'Drop columns here or click', - multi ? 2 : 1, - ) - ); - } - - return ( + const labelGhostButtonText = useMemo( + () => ghostButtonText ?? - tn('Drop column here', 'Drop columns here', multi ? 2 : 1) - ); - }, [clickEnabled, clickEnabledGhostButtonText, ghostButtonText, multi]); + tn( + 'Drop a column here or click', + 'Drop columns here or click', + multi ? 2 : 1, + ), + [ghostButtonText, multi], + ); return (
@@ -226,7 +198,7 @@ function DndColumnSelect(props: DndColumnSelectProps) { accept={DndItemType.Column} displayGhostButton={multi || optionSelector.values.length === 0} ghostButtonText={labelGhostButtonText} - onClickGhostButton={clickEnabled ? openPopover : undefined} + onClickGhostButton={openPopover} {...props} /> { render(setup(), { useDnd: true }); expect( - await screen.findByText('Drop columns or metrics here'), + await screen.findByText('Drop columns/metrics here or click'), ).toBeInTheDocument(); }); @@ -122,7 +122,7 @@ test('renders options with saved metric', async () => { }, ); expect( - await screen.findByText('Drop columns or metrics here'), + await screen.findByText('Drop columns/metrics here or click'), ).toBeInTheDocument(); }); @@ -143,7 +143,7 @@ test('renders options with column', async () => { }, ); expect( - await screen.findByText('Drop columns or metrics here'), + await screen.findByText('Drop columns/metrics here or click'), ).toBeInTheDocument(); }); @@ -165,6 +165,6 @@ test('renders options with adhoc metric', async () => { }, ); expect( - await screen.findByText('Drop columns or metrics here'), + await screen.findByText('Drop columns/metrics here or click'), ).toBeInTheDocument(); }); diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndFilterSelect.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndFilterSelect.tsx index da9410fcf2..387f484904 100644 --- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndFilterSelect.tsx +++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndFilterSelect.tsx @@ -18,9 +18,7 @@ */ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { - FeatureFlag, hasGenericChartAxes, - isFeatureEnabled, logging, Metric, QueryFormData, @@ -397,10 +395,6 @@ const DndFilterSelect = (props: DndFilterSelectProps) => { [controlName, togglePopover], ); - const ghostButtonText = isFeatureEnabled(FeatureFlag.ENABLE_DND_WITH_CLICK_UX) - ? t('Drop columns/metrics here or click') - : t('Drop columns or metrics here'); - return ( <> { canDrop={canDrop} valuesRenderer={valuesRenderer} accept={DND_ACCEPTED_TYPES} - ghostButtonText={ghostButtonText} - onClickGhostButton={ - isFeatureEnabled(FeatureFlag.ENABLE_DND_WITH_CLICK_UX) - ? handleClickGhostButton - : undefined - } + ghostButtonText={t('Drop columns/metrics here or click')} + onClickGhostButton={handleClickGhostButton} {...props} /> { test('renders with default props', () => { render(, { useDnd: true }); - expect(screen.getByText('Drop column or metric here')).toBeInTheDocument(); + expect( + screen.getByText('Drop a column/metric here or click'), + ).toBeInTheDocument(); }); test('renders with default props and multi = true', () => { render(, { useDnd: true }); - expect(screen.getByText('Drop columns or metrics here')).toBeInTheDocument(); + expect( + screen.getByText('Drop columns/metrics here or click'), + ).toBeInTheDocument(); }); test('render selected metrics correctly', () => { @@ -159,7 +163,7 @@ test('remove selected custom metric when metric gets removed from dataset for si expect(screen.getByText('Metric B')).toBeVisible(); expect( - screen.queryByText('Drop column or metric here'), + screen.queryByText('Drop a column/metric here or click'), ).not.toBeInTheDocument(); const newPropsWithRemovedMetric = { @@ -182,7 +186,7 @@ test('remove selected custom metric when metric gets removed from dataset for si ); expect(screen.queryByText('Metric B')).not.toBeInTheDocument(); - expect(screen.getByText('Drop column or metric here')).toBeVisible(); + expect(screen.getByText('Drop a column/metric here or click')).toBeVisible(); }); test('remove selected adhoc metric when column gets removed from dataset', async () => { diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndMetricSelect.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndMetricSelect.tsx index 30904a07ec..afa0a32f0c 100644 --- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndMetricSelect.tsx +++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndMetricSelect.tsx @@ -20,10 +20,8 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { ensureIsArray, - FeatureFlag, GenericDataType, isAdhocMetricSimple, - isFeatureEnabled, isSavedMetric, Metric, QueryFormMetric, @@ -329,17 +327,11 @@ const DndMetricSelect = (props: any) => { return new AdhocMetric({}); }, [droppedItem]); - const ghostButtonText = isFeatureEnabled(FeatureFlag.ENABLE_DND_WITH_CLICK_UX) - ? tn( - 'Drop a column/metric here or click', - 'Drop columns/metrics here or click', - multi ? 2 : 1, - ) - : tn( - 'Drop column or metric here', - 'Drop columns or metrics here', - multi ? 2 : 1, - ); + const ghostButtonText = tn( + 'Drop a column/metric here or click', + 'Drop columns/metrics here or click', + multi ? 2 : 1, + ); return (
@@ -350,11 +342,7 @@ const DndMetricSelect = (props: any) => { accept={DND_ACCEPTED_TYPES} ghostButtonText={ghostButtonText} displayGhostButton={multi || value.length === 0} - onClickGhostButton={ - isFeatureEnabled(FeatureFlag.ENABLE_DND_WITH_CLICK_UX) - ? handleClickGhostButton - : undefined - } + onClickGhostButton={handleClickGhostButton} {...props} /> false, valuesRenderer: () => , + ghostButtonText: 'Drop columns here or click', + onClickGhostButton: jest.fn(), }; -test('renders with default props', async () => { +test('renders with default props', () => { render(, { useDnd: true }); - expect(await screen.findByText('Drop columns here')).toBeInTheDocument(); + expect(screen.getByText('Drop columns here or click')).toBeInTheDocument(); }); -test('renders ghost button when empty', async () => { +test('renders ghost button when empty', () => { const ghostButtonText = 'Ghost button text'; render( , { useDnd: true }, ); - expect(await screen.findByText(ghostButtonText)).toBeInTheDocument(); + expect(screen.getByText(ghostButtonText)).toBeInTheDocument(); }); -test('renders values', async () => { +test('renders values', () => { const values = 'Values'; const valuesRenderer = () => {values}; render(, { useDnd: true, }); - expect(await screen.findByText(values)).toBeInTheDocument(); + expect(screen.getByText(values)).toBeInTheDocument(); +}); + +test('Handles ghost button click', () => { + render(, { useDnd: true }); + userEvent.click(screen.getByText('Drop columns here or click')); + expect(defaultProps.onClickGhostButton).toHaveBeenCalled(); }); diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndSelectLabel.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndSelectLabel.tsx index 467a440a4c..397d6f54e0 100644 --- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndSelectLabel.tsx +++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndSelectLabel.tsx @@ -35,14 +35,14 @@ import { DndItemType } from '../../DndItemType'; export type DndSelectLabelProps = { name: string; accept: DndItemType | DndItemType[]; - ghostButtonText?: string; + ghostButtonText: string; onDrop: (item: DatasourcePanelDndItem) => void; canDrop: (item: DatasourcePanelDndItem) => boolean; canDropValue?: (value: DndItemValue) => boolean; onDropValue?: (value: DndItemValue) => void; valuesRenderer: () => ReactNode; displayGhostButton?: boolean; - onClickGhostButton?: () => void; + onClickGhostButton: () => void; }; export default function DndSelectLabel({ @@ -80,7 +80,7 @@ export default function DndSelectLabel({ onClick={props.onClickGhostButton} > - {t(props.ghostButtonText || 'Drop columns here')} + {t(props.ghostButtonText)} ); } diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/types.ts b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/types.ts index f1658b5ae3..9f445a607d 100644 --- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/types.ts +++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/types.ts @@ -47,7 +47,6 @@ export type DndControlProps = multi?: boolean; canDelete?: boolean; ghostButtonText?: string; - clickEnabledGhostButtonText?: string; onChange: (value: ValueType | ValueType[] | null | undefined) => void; }; diff --git a/superset/config.py b/superset/config.py index 267ad64391..ea602338d3 100644 --- a/superset/config.py +++ b/superset/config.py @@ -458,9 +458,8 @@ DEFAULT_FEATURE_FLAGS: Dict[str, bool] = { # Enables Alerts and reports new implementation "ALERT_REPORTS": False, "DASHBOARD_RBAC": False, - "ENABLE_EXPLORE_DRAG_AND_DROP": True, + "ENABLE_EXPLORE_DRAG_AND_DROP": True, # deprecated "ENABLE_ADVANCED_DATA_TYPES": False, - "ENABLE_DND_WITH_CLICK_UX": True, # Enabling ALERTS_ATTACH_REPORTS, the system sends email and slack message # with screenshot and link # Disables ALERTS_ATTACH_REPORTS, the system DOES NOT generate screenshot