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

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


The following commit(s) were added to refs/heads/master by this push:
     new 203c311  feat(explore): make dnd controls clickable (#16119)
203c311 is described below

commit 203c311ca30cba616bff5253ca561438da21c03e
Author: Kamil Gabryjelski <[email protected]>
AuthorDate: Tue Aug 17 14:56:39 2021 +0200

    feat(explore): make dnd controls clickable (#16119)
    
    * Make ghost buttons clickable
    
    * Popover for column control
    
    * Make column dnd ghost button clickable
    
    * Prefill operator only if column is defined
    
    * Remove data-tests
    
    * lint fix
    
    * Hide new features behind a feature flag
    
    * Change ghost button texts
    
    * Remove caret for non clickable columns
---
 .../DndColumnSelectControl/ColumnSelectPopover.tsx | 223 +++++++++++++++++++++
 .../ColumnSelectPopoverTrigger.tsx                 |  99 +++++++++
 .../DndColumnSelectControl/DndColumnSelect.tsx     | 134 ++++++++++---
 .../DndColumnSelectControl/DndFilterSelect.tsx     |  18 +-
 .../DndColumnSelectControl/DndMetricSelect.tsx     |  28 ++-
 .../DndColumnSelectControl/DndSelectLabel.tsx      |   5 +-
 .../controls/DndColumnSelectControl/types.ts       |   1 +
 superset/config.py                                 |   1 +
 8 files changed, 475 insertions(+), 34 deletions(-)

diff --git 
a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopover.tsx
 
b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopover.tsx
new file mode 100644
index 0000000..c58b3d3
--- /dev/null
+++ 
b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopover.tsx
@@ -0,0 +1,223 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/* eslint-disable camelcase */
+import React, { useCallback, useMemo, useState } from 'react';
+import Tabs from 'src/components/Tabs';
+import Button from 'src/components/Button';
+import { NativeSelect as Select } from 'src/components/Select';
+import { t, styled } from '@superset-ui/core';
+
+import { Form, FormItem } from 'src/components/Form';
+import { StyledColumnOption } from 'src/explore/components/optionRenderers';
+import { ColumnMeta } from '@superset-ui/chart-controls';
+
+const StyledSelect = styled(Select)`
+  .metric-option {
+    & > svg {
+      min-width: ${({ theme }) => `${theme.gridUnit * 4}px`};
+    }
+    & > .option-label {
+      overflow: hidden;
+      text-overflow: ellipsis;
+    }
+  }
+`;
+
+interface ColumnSelectPopoverProps {
+  columns: ColumnMeta[];
+  editedColumn?: ColumnMeta;
+  onChange: (column: ColumnMeta) => void;
+  onClose: () => void;
+}
+
+const ColumnSelectPopover = ({
+  columns,
+  editedColumn,
+  onChange,
+  onClose,
+}: ColumnSelectPopoverProps) => {
+  const [
+    initialCalculatedColumn,
+    initialSimpleColumn,
+  ] = editedColumn?.expression
+    ? [editedColumn, undefined]
+    : [undefined, editedColumn];
+  const [selectedCalculatedColumn, setSelectedCalculatedColumn] = useState(
+    initialCalculatedColumn,
+  );
+  const [selectedSimpleColumn, setSelectedSimpleColumn] = useState(
+    initialSimpleColumn,
+  );
+
+  const [calculatedColumns, simpleColumns] = useMemo(
+    () =>
+      columns?.reduce(
+        (acc: [ColumnMeta[], ColumnMeta[]], column: ColumnMeta) => {
+          if (column.expression) {
+            acc[0].push(column);
+          } else {
+            acc[1].push(column);
+          }
+          return acc;
+        },
+        [[], []],
+      ),
+    [columns],
+  );
+
+  const onCalculatedColumnChange = useCallback(
+    selectedColumnName => {
+      const selectedColumn = calculatedColumns.find(
+        col => col.column_name === selectedColumnName,
+      );
+      setSelectedCalculatedColumn(selectedColumn);
+      setSelectedSimpleColumn(undefined);
+    },
+    [calculatedColumns],
+  );
+
+  const onSimpleColumnChange = useCallback(
+    selectedColumnName => {
+      const selectedColumn = simpleColumns.find(
+        col => col.column_name === selectedColumnName,
+      );
+      setSelectedCalculatedColumn(undefined);
+      setSelectedSimpleColumn(selectedColumn);
+    },
+    [simpleColumns],
+  );
+
+  const defaultActiveTabKey =
+    initialSimpleColumn || calculatedColumns.length === 0 ? 'simple' : 'saved';
+
+  const onSave = useCallback(() => {
+    const selectedColumn = selectedCalculatedColumn || selectedSimpleColumn;
+    if (!selectedColumn) {
+      return;
+    }
+    onChange(selectedColumn);
+    onClose();
+  }, [onChange, onClose, selectedCalculatedColumn, selectedSimpleColumn]);
+
+  const onResetStateAndClose = useCallback(() => {
+    setSelectedCalculatedColumn(initialCalculatedColumn);
+    setSelectedSimpleColumn(initialSimpleColumn);
+    onClose();
+  }, [initialCalculatedColumn, initialSimpleColumn, onClose]);
+
+  const stateIsValid = selectedCalculatedColumn || selectedSimpleColumn;
+  const hasUnsavedChanges =
+    selectedCalculatedColumn?.column_name !==
+      initialCalculatedColumn?.column_name ||
+    selectedSimpleColumn?.column_name !== initialSimpleColumn?.column_name;
+
+  const filterOption = useCallback(
+    (input, option) =>
+      option?.filterBy.toLowerCase().indexOf(input.toLowerCase()) >= 0,
+    [],
+  );
+
+  const getPopupContainer = useCallback(
+    (triggerNode: any) => triggerNode.parentNode,
+    [],
+  );
+
+  return (
+    <Form layout="vertical" id="metrics-edit-popover">
+      <Tabs
+        id="adhoc-metric-edit-tabs"
+        defaultActiveKey={defaultActiveTabKey}
+        className="adhoc-metric-edit-tabs"
+        allowOverflow
+      >
+        <Tabs.TabPane key="saved" tab={t('Saved')}>
+          <FormItem label={t('Saved expressions')}>
+            <StyledSelect
+              value={selectedCalculatedColumn?.column_name}
+              getPopupContainer={getPopupContainer}
+              onChange={onCalculatedColumnChange}
+              allowClear
+              showSearch
+              autoFocus={!selectedCalculatedColumn}
+              filterOption={filterOption}
+              placeholder={t('%s column(s)', calculatedColumns.length)}
+            >
+              {calculatedColumns.map(calculatedColumn => (
+                <Select.Option
+                  value={calculatedColumn.column_name}
+                  filterBy={
+                    calculatedColumn.verbose_name ||
+                    calculatedColumn.column_name
+                  }
+                  key={calculatedColumn.column_name}
+                >
+                  <StyledColumnOption column={calculatedColumn} showType />
+                </Select.Option>
+              ))}
+            </StyledSelect>
+          </FormItem>
+        </Tabs.TabPane>
+        <Tabs.TabPane key="simple" tab={t('Simple')}>
+          <FormItem label={t('Column')}>
+            <Select
+              value={selectedSimpleColumn?.column_name}
+              getPopupContainer={getPopupContainer}
+              onChange={onSimpleColumnChange}
+              allowClear
+              showSearch
+              autoFocus={!selectedSimpleColumn}
+              filterOption={filterOption}
+              placeholder={t('%s column(s)', simpleColumns.length)}
+            >
+              {simpleColumns.map(simpleColumn => (
+                <Select.Option
+                  value={simpleColumn.column_name}
+                  filterBy={
+                    simpleColumn.verbose_name || simpleColumn.column_name
+                  }
+                  key={simpleColumn.column_name}
+                >
+                  <StyledColumnOption column={simpleColumn} showType />
+                </Select.Option>
+              ))}
+            </Select>
+          </FormItem>
+        </Tabs.TabPane>
+      </Tabs>
+      <div>
+        <Button buttonSize="small" onClick={onResetStateAndClose} cta>
+          {t('Close')}
+        </Button>
+        <Button
+          disabled={!stateIsValid}
+          buttonStyle={
+            hasUnsavedChanges && stateIsValid ? 'primary' : 'default'
+          }
+          buttonSize="small"
+          onClick={onSave}
+          cta
+        >
+          {t('Save')}
+        </Button>
+      </div>
+    </Form>
+  );
+};
+
+export default ColumnSelectPopover;
diff --git 
a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopoverTrigger.tsx
 
b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopoverTrigger.tsx
new file mode 100644
index 0000000..4caf086
--- /dev/null
+++ 
b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopoverTrigger.tsx
@@ -0,0 +1,99 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import React, { useCallback, useMemo, useState } from 'react';
+import { ColumnMeta } from '@superset-ui/chart-controls';
+import Popover from 'src/components/Popover';
+import { ExplorePopoverContent } from 
'src/explore/components/ExploreContentPopover';
+import ColumnSelectPopover from './ColumnSelectPopover';
+
+interface ColumnSelectPopoverTriggerProps {
+  columns: ColumnMeta[];
+  editedColumn?: ColumnMeta;
+  onColumnEdit: (editedColumn: ColumnMeta) => void;
+  isControlledComponent?: boolean;
+  visible?: boolean;
+  togglePopover?: (visible: boolean) => void;
+  closePopover?: () => void;
+  children: React.ReactNode;
+}
+
+const ColumnSelectPopoverTrigger = ({
+  columns,
+  editedColumn,
+  onColumnEdit,
+  isControlledComponent,
+  children,
+  ...props
+}: ColumnSelectPopoverTriggerProps) => {
+  const [popoverVisible, setPopoverVisible] = useState(false);
+
+  const togglePopover = useCallback((visible: boolean) => {
+    setPopoverVisible(visible);
+  }, []);
+
+  const closePopover = useCallback(() => {
+    setPopoverVisible(false);
+  }, []);
+
+  const {
+    visible,
+    handleTogglePopover,
+    handleClosePopover,
+  } = isControlledComponent
+    ? {
+        visible: props.visible,
+        handleTogglePopover: props.togglePopover!,
+        handleClosePopover: props.closePopover!,
+      }
+    : {
+        visible: popoverVisible,
+        handleTogglePopover: togglePopover,
+        handleClosePopover: closePopover,
+      };
+
+  const overlayContent = useMemo(
+    () => (
+      <ExplorePopoverContent>
+        <ColumnSelectPopover
+          editedColumn={editedColumn}
+          columns={columns}
+          onClose={handleClosePopover}
+          onChange={onColumnEdit}
+        />
+      </ExplorePopoverContent>
+    ),
+    [columns, editedColumn, handleClosePopover, onColumnEdit],
+  );
+
+  return (
+    <Popover
+      placement="right"
+      trigger="click"
+      content={overlayContent}
+      defaultVisible={visible}
+      visible={visible}
+      onVisibleChange={handleTogglePopover}
+      destroyTooltipOnHide
+    >
+      {children}
+    </Popover>
+  );
+};
+
+export default ColumnSelectPopoverTrigger;
diff --git 
a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelect.tsx
 
b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelect.tsx
index 788b4e2..69cc644 100644
--- 
a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelect.tsx
+++ 
b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelect.tsx
@@ -16,8 +16,8 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import React, { useCallback, useMemo } from 'react';
-import { tn } from '@superset-ui/core';
+import React, { useCallback, useMemo, useState } from 'react';
+import { FeatureFlag, isFeatureEnabled, tn } from '@superset-ui/core';
 import { ColumnMeta } from '@superset-ui/chart-controls';
 import { isEmpty } from 'lodash';
 import { LabelProps } from 
'src/explore/components/controls/DndColumnSelectControl/types';
@@ -27,6 +27,7 @@ import { OptionSelector } from 
'src/explore/components/controls/DndColumnSelectC
 import { DatasourcePanelDndItem } from 
'src/explore/components/DatasourcePanel/types';
 import { DndItemType } from 'src/explore/components/DndItemType';
 import { useComponentDidUpdate } from 'src/common/hooks/useComponentDidUpdate';
+import ColumnSelectPopoverTrigger from './ColumnSelectPopoverTrigger';
 
 export const DndColumnSelect = (props: LabelProps) => {
   const {
@@ -39,6 +40,8 @@ export const DndColumnSelect = (props: LabelProps) => {
     name,
     label,
   } = props;
+  const [newColumnPopoverVisible, setNewColumnPopoverVisible] = 
useState(false);
+
   const optionSelector = useMemo(
     () => new OptionSelector(options, multi, value),
     [multi, options, value],
@@ -110,41 +113,120 @@ export const DndColumnSelect = (props: LabelProps) => {
     [onChange, optionSelector],
   );
 
+  const popoverOptions = useMemo(
+    () =>
+      Object.values(options).filter(
+        col =>
+          !optionSelector.values
+            .map(val => val.column_name)
+            .includes(col.column_name),
+      ),
+    [optionSelector.values, options],
+  );
+
   const valuesRenderer = useCallback(
     () =>
-      optionSelector.values.map((column, idx) => (
-        <OptionWrapper
-          key={idx}
-          index={idx}
-          clickClose={onClickClose}
-          onShiftOptions={onShiftOptions}
-          type={`${DndItemType.ColumnOption}_${name}_${label}`}
-          canDelete={canDelete}
-          column={column}
-        />
-      )),
+      optionSelector.values.map((column, idx) =>
+        isFeatureEnabled(FeatureFlag.ENABLE_DND_WITH_CLICK_UX) ? (
+          <ColumnSelectPopoverTrigger
+            columns={popoverOptions}
+            onColumnEdit={newColumn => {
+              optionSelector.replace(idx, newColumn.column_name);
+              onChange(optionSelector.getValues());
+            }}
+            editedColumn={column}
+          >
+            <OptionWrapper
+              key={idx}
+              index={idx}
+              clickClose={onClickClose}
+              onShiftOptions={onShiftOptions}
+              type={`${DndItemType.ColumnOption}_${name}_${label}`}
+              canDelete={canDelete}
+              column={column}
+              withCaret
+            />
+          </ColumnSelectPopoverTrigger>
+        ) : (
+          <OptionWrapper
+            key={idx}
+            index={idx}
+            clickClose={onClickClose}
+            onShiftOptions={onShiftOptions}
+            type={`${DndItemType.ColumnOption}_${name}_${label}`}
+            canDelete={canDelete}
+            column={column}
+          />
+        ),
+      ),
     [
       canDelete,
       label,
       name,
+      onChange,
       onClickClose,
       onShiftOptions,
-      optionSelector.values,
+      optionSelector,
+      popoverOptions,
     ],
   );
 
+  const addNewColumnWithPopover = useCallback(
+    (newColumn: ColumnMeta) => {
+      optionSelector.add(newColumn.column_name);
+      onChange(optionSelector.getValues());
+    },
+    [onChange, optionSelector],
+  );
+
+  const togglePopover = useCallback((visible: boolean) => {
+    setNewColumnPopoverVisible(visible);
+  }, []);
+
+  const closePopover = useCallback(() => {
+    togglePopover(false);
+  }, [togglePopover]);
+
+  const openPopover = useCallback(() => {
+    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);
+
   return (
-    <DndSelectLabel<string | string[], ColumnMeta[]>
-      onDrop={onDrop}
-      canDrop={canDrop}
-      valuesRenderer={valuesRenderer}
-      accept={DndItemType.Column}
-      displayGhostButton={multi || optionSelector.values.length === 0}
-      ghostButtonText={
-        ghostButtonText ||
-        tn('Drop column here', 'Drop columns here', multi ? 2 : 1)
-      }
-      {...props}
-    />
+    <div>
+      <DndSelectLabel<string | string[], ColumnMeta[]>
+        onDrop={onDrop}
+        canDrop={canDrop}
+        valuesRenderer={valuesRenderer}
+        accept={DndItemType.Column}
+        displayGhostButton={multi || optionSelector.values.length === 0}
+        ghostButtonText={ghostButtonText || defaultGhostButtonText}
+        onClickGhostButton={
+          isFeatureEnabled(FeatureFlag.ENABLE_DND_WITH_CLICK_UX)
+            ? openPopover
+            : undefined
+        }
+        {...props}
+      />
+      <ColumnSelectPopoverTrigger
+        columns={popoverOptions}
+        onColumnEdit={addNewColumnWithPopover}
+        isControlledComponent
+        togglePopover={togglePopover}
+        closePopover={closePopover}
+        visible={newColumnPopoverVisible}
+      >
+        <div />
+      </ColumnSelectPopoverTrigger>
+    </div>
   );
 };
diff --git 
a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndFilterSelect.tsx
 
b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndFilterSelect.tsx
index 1e2eba8..97842f5 100644
--- 
a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndFilterSelect.tsx
+++ 
b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndFilterSelect.tsx
@@ -333,6 +333,11 @@ export const DndFilterSelect = (props: 
DndFilterSelectProps) => {
     ],
   );
 
+  const handleClickGhostButton = useCallback(() => {
+    setDroppedItem(null);
+    togglePopover(true);
+  }, [togglePopover]);
+
   const adhocFilter = useMemo(() => {
     if (droppedItem?.metric_name) {
       return new AdhocFilter({
@@ -351,7 +356,7 @@ export const DndFilterSelect = (props: 
DndFilterSelectProps) => {
     const config: Partial<AdhocFilter> = {
       subject: (droppedItem as ColumnMeta)?.column_name,
     };
-    if (isFeatureEnabled(FeatureFlag.UX_BETA)) {
+    if (config.subject && isFeatureEnabled(FeatureFlag.UX_BETA)) {
       config.operator = OPERATOR_ENUM_TO_OPERATOR_TYPE[Operators.IN].operation;
       config.operatorId = Operators.IN;
     }
@@ -367,6 +372,10 @@ export const DndFilterSelect = (props: 
DndFilterSelectProps) => {
     [togglePopover],
   );
 
+  const ghostButtonText = 
isFeatureEnabled(FeatureFlag.ENABLE_DND_WITH_CLICK_UX)
+    ? t('Drop columns/metrics here or click')
+    : t('Drop columns or metrics here');
+
   return (
     <>
       <DndSelectLabel<OptionValueType, OptionValueType[]>
@@ -374,7 +383,12 @@ export const DndFilterSelect = (props: 
DndFilterSelectProps) => {
         canDrop={canDrop}
         valuesRenderer={valuesRenderer}
         accept={DND_ACCEPTED_TYPES}
-        ghostButtonText={t('Drop columns or metrics here')}
+        ghostButtonText={ghostButtonText}
+        onClickGhostButton={
+          isFeatureEnabled(FeatureFlag.ENABLE_DND_WITH_CLICK_UX)
+            ? handleClickGhostButton
+            : undefined
+        }
         {...props}
       />
       <AdhocFilterPopoverTrigger
diff --git 
a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndMetricSelect.tsx
 
b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndMetricSelect.tsx
index f44934a..5967f33 100644
--- 
a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndMetricSelect.tsx
+++ 
b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndMetricSelect.tsx
@@ -312,6 +312,11 @@ export const DndMetricSelect = (props: any) => {
     [onNewMetric, togglePopover],
   );
 
+  const handleClickGhostButton = useCallback(() => {
+    setDroppedItem(null);
+    togglePopover(true);
+  }, [togglePopover]);
+
   const adhocMetric = useMemo(() => {
     if (droppedItem?.type === DndItemType.Column) {
       const itemValue = droppedItem?.value as ColumnMeta;
@@ -334,6 +339,18 @@ export const DndMetricSelect = (props: any) => {
     return new AdhocMetric({ isNew: true });
   }, [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,
+      );
+
   return (
     <div className="metrics-select">
       <DndSelectLabel<OptionValueType, OptionValueType[]>
@@ -341,12 +358,13 @@ export const DndMetricSelect = (props: any) => {
         canDrop={canDrop}
         valuesRenderer={valuesRenderer}
         accept={DND_ACCEPTED_TYPES}
-        ghostButtonText={tn(
-          'Drop column or metric here',
-          'Drop columns or metrics here',
-          multi ? 2 : 1,
-        )}
+        ghostButtonText={ghostButtonText}
         displayGhostButton={multi || value.length === 0}
+        onClickGhostButton={
+          isFeatureEnabled(FeatureFlag.ENABLE_DND_WITH_CLICK_UX)
+            ? handleClickGhostButton
+            : undefined
+        }
         {...props}
       />
       <AdhocMetricPopoverTrigger
diff --git 
a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndSelectLabel.tsx
 
b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndSelectLabel.tsx
index d5d365f..d3bf95d 100644
--- 
a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndSelectLabel.tsx
+++ 
b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndSelectLabel.tsx
@@ -55,7 +55,10 @@ export default function DndSelectLabel<T, O>({
 
   function renderGhostButton() {
     return (
-      <AddControlLabel cancelHover>
+      <AddControlLabel
+        cancelHover={!props.onClickGhostButton}
+        onClick={props.onClickGhostButton}
+      >
         <Icons.PlusSmall iconColor={theme.colors.grayscale.light1} />
         {t(props.ghostButtonText || 'Drop columns here')}
       </AddControlLabel>
diff --git 
a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/types.ts
 
b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/types.ts
index 6a8a687..b77e9d2 100644
--- 
a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/types.ts
+++ 
b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/types.ts
@@ -65,6 +65,7 @@ export interface DndColumnSelectProps<
   accept: DndItemType | DndItemType[];
   ghostButtonText?: string;
   displayGhostButton?: boolean;
+  onClickGhostButton?: () => void;
 }
 
 export type OptionValueType = Record<string, any>;
diff --git a/superset/config.py b/superset/config.py
index 58d9693..f18fcca 100644
--- a/superset/config.py
+++ b/superset/config.py
@@ -388,6 +388,7 @@ DEFAULT_FEATURE_FLAGS: Dict[str, bool] = {
     "OMNIBAR": False,
     "DASHBOARD_RBAC": False,
     "ENABLE_EXPLORE_DRAG_AND_DROP": False,
+    "ENABLE_DND_WITH_CLICK_UX": False,
     # 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

Reply via email to