Skip to content

Commit

Permalink
feat: Implement drag and drop columns for filters (#13340)
Browse files Browse the repository at this point in the history
* Implement DnD feature for filters

* minor refactor

* Fix types

* Fix undefined error

* Refactor

* Fix ts errors

* Fix conflicting dnd types

* Bump superset-ui packages

* Change DndItemType case to PascalCase

* Remove redundant null check

* Fix

* Fix csrf mock api call
  • Loading branch information
kgabryje authored Mar 7, 2021
1 parent 3970d73 commit 7b370e6
Show file tree
Hide file tree
Showing 23 changed files with 1,069 additions and 540 deletions.
670 changes: 366 additions & 304 deletions superset-frontend/package-lock.json

Large diffs are not rendered by default.

54 changes: 27 additions & 27 deletions superset-frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,34 +65,34 @@
"@babel/runtime-corejs3": "^7.12.5",
"@data-ui/sparkline": "^0.0.84",
"@emotion/core": "^10.0.35",
"@superset-ui/chart-controls": "^0.17.13",
"@superset-ui/core": "^0.17.13",
"@superset-ui/legacy-plugin-chart-calendar": "^0.17.13",
"@superset-ui/legacy-plugin-chart-chord": "^0.17.13",
"@superset-ui/legacy-plugin-chart-country-map": "^0.17.13",
"@superset-ui/legacy-plugin-chart-event-flow": "^0.17.13",
"@superset-ui/legacy-plugin-chart-force-directed": "^0.17.13",
"@superset-ui/legacy-plugin-chart-heatmap": "^0.17.13",
"@superset-ui/legacy-plugin-chart-histogram": "^0.17.13",
"@superset-ui/legacy-plugin-chart-horizon": "^0.17.13",
"@superset-ui/legacy-plugin-chart-map-box": "^0.17.13",
"@superset-ui/legacy-plugin-chart-paired-t-test": "^0.17.13",
"@superset-ui/legacy-plugin-chart-parallel-coordinates": "^0.17.13",
"@superset-ui/legacy-plugin-chart-partition": "^0.17.13",
"@superset-ui/legacy-plugin-chart-pivot-table": "^0.17.13",
"@superset-ui/legacy-plugin-chart-rose": "^0.17.13",
"@superset-ui/legacy-plugin-chart-sankey": "^0.17.13",
"@superset-ui/legacy-plugin-chart-sankey-loop": "^0.17.13",
"@superset-ui/legacy-plugin-chart-sunburst": "^0.17.13",
"@superset-ui/legacy-plugin-chart-treemap": "^0.17.13",
"@superset-ui/legacy-plugin-chart-world-map": "^0.17.13",
"@superset-ui/legacy-preset-chart-big-number": "^0.17.13",
"@superset-ui/chart-controls": "^0.17.14",
"@superset-ui/core": "^0.17.14",
"@superset-ui/legacy-plugin-chart-calendar": "^0.17.14",
"@superset-ui/legacy-plugin-chart-chord": "^0.17.14",
"@superset-ui/legacy-plugin-chart-country-map": "^0.17.14",
"@superset-ui/legacy-plugin-chart-event-flow": "^0.17.14",
"@superset-ui/legacy-plugin-chart-force-directed": "^0.17.14",
"@superset-ui/legacy-plugin-chart-heatmap": "^0.17.14",
"@superset-ui/legacy-plugin-chart-histogram": "^0.17.14",
"@superset-ui/legacy-plugin-chart-horizon": "^0.17.14",
"@superset-ui/legacy-plugin-chart-map-box": "^0.17.14",
"@superset-ui/legacy-plugin-chart-paired-t-test": "^0.17.14",
"@superset-ui/legacy-plugin-chart-parallel-coordinates": "^0.17.14",
"@superset-ui/legacy-plugin-chart-partition": "^0.17.14",
"@superset-ui/legacy-plugin-chart-pivot-table": "^0.17.14",
"@superset-ui/legacy-plugin-chart-rose": "^0.17.14",
"@superset-ui/legacy-plugin-chart-sankey": "^0.17.14",
"@superset-ui/legacy-plugin-chart-sankey-loop": "^0.17.14",
"@superset-ui/legacy-plugin-chart-sunburst": "^0.17.14",
"@superset-ui/legacy-plugin-chart-treemap": "^0.17.14",
"@superset-ui/legacy-plugin-chart-world-map": "^0.17.14",
"@superset-ui/legacy-preset-chart-big-number": "^0.17.14",
"@superset-ui/legacy-preset-chart-deckgl": "^0.4.6",
"@superset-ui/legacy-preset-chart-nvd3": "^0.17.13",
"@superset-ui/plugin-chart-echarts": "^0.17.13",
"@superset-ui/plugin-chart-table": "^0.17.13",
"@superset-ui/plugin-chart-word-cloud": "^0.17.13",
"@superset-ui/preset-chart-xy": "^0.17.13",
"@superset-ui/legacy-preset-chart-nvd3": "^0.17.14",
"@superset-ui/plugin-chart-echarts": "^0.17.14",
"@superset-ui/plugin-chart-table": "^0.17.14",
"@superset-ui/plugin-chart-word-cloud": "^0.17.14",
"@superset-ui/preset-chart-xy": "^0.17.14",
"@vx/responsive": "^0.0.195",
"abortcontroller-polyfill": "^1.1.9",
"antd": "^4.9.4",
Expand Down
2 changes: 1 addition & 1 deletion superset-frontend/spec/helpers/setupSupersetClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@ export default function setupSupersetClient() {
// The following is needed to mock out SupersetClient requests
// including CSRF authentication and initialization
global.FormData = window.FormData; // used by SupersetClient
fetchMock.get('glob:*superset/csrf_token/*', { csrf_token: '1234' });
fetchMock.get('glob:*/api/v1/security/csrf_token/*', { result: '1234' });
SupersetClient.configure({ protocol: 'http', host: 'localhost' }).init();
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags';
import { ExploreActions } from 'src/explore/actions/exploreActions';
import Control from 'src/explore/components/Control';
import DatasourcePanelDragWrapper from './DatasourcePanelDragWrapper';
import { DatasourcePanelDndType } from './types';
import { DndItemType } from '../DndItemType';

interface DatasourceControl extends ControlConfig {
datasource?: DatasourceMeta;
Expand Down Expand Up @@ -213,8 +213,8 @@ export default function DataSourcePanel({
<LabelContainer key={m.metric_name} className="column">
{enableExploreDnd ? (
<DatasourcePanelDragWrapper
metricOrColumnName={m.metric_name}
type={DatasourcePanelDndType.METRIC}
value={m}
type={DndItemType.Metric}
>
<MetricOption metric={m} showType />
</DatasourcePanelDragWrapper>
Expand All @@ -235,8 +235,8 @@ export default function DataSourcePanel({
<LabelContainer key={col.column_name} className="column">
{enableExploreDnd ? (
<DatasourcePanelDragWrapper
metricOrColumnName={col.column_name}
type={DatasourcePanelDndType.COLUMN}
value={col}
type={DndItemType.Column}
>
<ColumnOption column={col} showType />
</DatasourcePanelDragWrapper>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export default function DatasourcePanelDragWrapper(
) {
const [, drag] = useDrag({
item: {
metricOrColumnName: props.metricOrColumnName,
value: props.value,
type: props.type,
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,12 @@
* specific language governing permissions and limitations
* under the License.
*/
export enum DatasourcePanelDndType {
// todo: The new `metric` conflicts with the existing metric type
METRIC = 'datasource-panel-metric',
COLUMN = 'column',
}
import { ColumnMeta, Metric } from '@superset-ui/chart-controls';
import { DndItemType } from '../DndItemType';

export type DndItemValue = ColumnMeta | Metric;

export interface DatasourcePanelDndItem {
metricOrColumnName: string;
type:
| typeof DatasourcePanelDndType.METRIC
| typeof DatasourcePanelDndType.COLUMN;
value: DndItemValue;
type: DndItemType;
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,25 @@
* specific language governing permissions and limitations
* under the License.
*/
export const OPTION_TYPES = {
metric: 'metric',
filter: 'filter',
};

/**
* All possible draggable items for the chart controls.
*/
export enum DndItemType {
// an existing column in table
Column = 'column',
// a selected column option in ColumnSelectControl
ColumnOption = 'columnOption',
// an adhoc column option in ColumnSelectControl
AdhocColumnOption = 'adhocColumn',

// a saved metric
Metric = 'metric',
// a selected saved metric in MetricsControl
MetricOption = 'metricOption',
// an adhoc metric option in MetricsControl
AdhocMetricOption = 'adhocMetric',

// an adhoc filter option
FilterOption = 'filterOption',
}
21 changes: 14 additions & 7 deletions superset-frontend/src/explore/components/OptionControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
import { Tooltip } from 'src/common/components/Tooltip';
import Icon from 'src/components/Icon';
import { savedMetricType } from 'src/explore/components/controls/MetricControl/types';
import AdhocMetric from './controls/MetricControl/AdhocMetric';

export const DragContainer = styled.div`
margin-bottom: ${({ theme }) => theme.gridUnit}px;
Expand All @@ -35,7 +36,7 @@ export const DragContainer = styled.div`
`;

export const OptionControlContainer = styled.div<{
isAdhoc?: boolean;
withCaret?: boolean;
}>`
display: flex;
align-items: center;
Expand All @@ -44,7 +45,7 @@ export const OptionControlContainer = styled.div<{
height: ${({ theme }) => theme.gridUnit * 6}px;
background-color: ${({ theme }) => theme.colors.grayscale.light3};
border-radius: 3px;
cursor: ${({ isAdhoc }) => (isAdhoc ? 'pointer' : 'default')};
cursor: ${({ withCaret }) => (withCaret ? 'pointer' : 'default')};
`;

export const Label = styled.div`
Expand Down Expand Up @@ -159,10 +160,11 @@ interface DragItem {
export const OptionControlLabel = ({
label,
savedMetric,
adhocMetric,
onRemove,
onMoveLabel,
onDropLabel,
isAdhoc,
withCaret,
isFunction,
type,
index,
Expand All @@ -171,10 +173,11 @@ export const OptionControlLabel = ({
}: {
label: string | React.ReactNode;
savedMetric?: savedMetricType;
adhocMetric?: AdhocMetric;
onRemove: () => void;
onMoveLabel: (dragIndex: number, hoverIndex: number) => void;
onDropLabel: () => void;
isAdhoc?: boolean;
withCaret?: boolean;
isFunction?: boolean;
isDraggable?: boolean;
type: string;
Expand Down Expand Up @@ -231,7 +234,11 @@ export const OptionControlLabel = ({
},
});
const [, drag] = useDrag({
item: { type, index },
item: {
type,
index,
value: savedMetric?.metric_name ? savedMetric : adhocMetric,
},
collect: monitor => ({
isDragging: monitor.isDragging(),
}),
Expand All @@ -246,7 +253,7 @@ export const OptionControlLabel = ({

const getOptionControlContent = () => (
<OptionControlContainer
isAdhoc={isAdhoc}
withCaret={withCaret}
data-test="option-label"
{...props}
>
Expand All @@ -272,7 +279,7 @@ export const OptionControlLabel = ({
`)}
/>
)}
{isAdhoc && (
{withCaret && (
<CaretContainer>
<Icon name="caret-right" color={theme.colors.grayscale.light1} />
</CaretContainer>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/**
* 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, { useState } from 'react';
import { ColumnMeta, ColumnOption } from '@superset-ui/chart-controls';
import { isEmpty } from 'lodash';
import { LabelProps } from './types';
import DndSelectLabel from './DndSelectLabel';
import OptionWrapper from './components/OptionWrapper';
import { OptionSelector } from './utils';
import { DatasourcePanelDndItem } from '../../DatasourcePanel/types';
import { DndItemType } from '../../DndItemType';

export const DndColumnSelect = (props: LabelProps) => {
const { value, options } = props;
const optionSelector = new OptionSelector(options, value);
const [values, setValues] = useState<ColumnMeta[]>(optionSelector.values);

const onDrop = (item: DatasourcePanelDndItem) => {
const column = item.value as ColumnMeta;
if (!optionSelector.isArray && !isEmpty(optionSelector.values)) {
optionSelector.replace(0, column.column_name);
} else {
optionSelector.add(column.column_name);
}
setValues(optionSelector.values);
props.onChange(optionSelector.getValues());
};

const canDrop = (item: DatasourcePanelDndItem) =>
!optionSelector.has((item.value as ColumnMeta).column_name);

const onClickClose = (index: number) => {
optionSelector.del(index);
setValues(optionSelector.values);
props.onChange(optionSelector.getValues());
};

const onShiftOptions = (dragIndex: number, hoverIndex: number) => {
optionSelector.swap(dragIndex, hoverIndex);
setValues(optionSelector.values);
props.onChange(optionSelector.getValues());
};

const valuesRenderer = () =>
values.map((column, idx) => (
<OptionWrapper
key={idx}
index={idx}
clickClose={onClickClose}
onShiftOptions={onShiftOptions}
type={DndItemType.ColumnOption}
>
<ColumnOption column={column} showType />
</OptionWrapper>
));

return (
<DndSelectLabel<string | string[], ColumnMeta[]>
values={values}
onDrop={onDrop}
canDrop={canDrop}
valuesRenderer={valuesRenderer}
accept={DndItemType.Column}
{...props}
/>
);
};
Loading

0 comments on commit 7b370e6

Please sign in to comment.