ktmud commented on a change in pull request #13210:
URL: https://github.com/apache/superset/pull/13210#discussion_r583127546
##########
File path: superset-frontend/src/explore/components/DatasourcePanel.tsx
##########
@@ -28,8 +28,10 @@ import {
import { debounce } from 'lodash';
import { matchSorter, rankings } from 'match-sorter';
import { FAST_DEBOUNCE } from 'src/constants';
-import { ExploreActions } from 'src/explore/actions/exploreActions';
-import Control from 'src/explore/components/Control';
+import { ExploreActions } from '../actions/exploreActions';
Review comment:
```suggestion
import { ExploreActions } from 'src/explore/actions/exploreActions';
```
##########
File path:
superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelectLabel.tsx
##########
@@ -0,0 +1,119 @@
+/**
+ * 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 { useDrop } from 'react-dnd';
+import { isEmpty } from 'lodash';
+import { t, useTheme } from '@superset-ui/core';
+import { BaseControlConfig, ColumnMeta } from '@superset-ui/chart-controls';
+import ControlHeader from 'src/explore/components/ControlHeader';
+import {
+ AddControlLabel,
+ DndLabelsContainer,
+ HeaderContainer,
+} from 'src/explore/components/OptionControls';
+import {
+ DatasourcePanelDndItem,
+ DatasourcePanelDndType,
+} from 'src/explore/components/DatasourcePanel/types';
+import Icon from 'src/components/Icon';
+import OptionWrapper from './components/OptionWrapper';
+import { OptionSelector } from './utils';
+
+interface LabelProps extends BaseControlConfig {
+ name: string;
+ value: string[] | string | null;
+ onChange: (value: string[] | string | null) => void;
+ options: { string: ColumnMeta };
+}
+
+export default function DndColumnSelectLabel(props: LabelProps) {
+ const theme = useTheme();
+ const { value, options } = props;
+ const optionSelector = new OptionSelector(options, value);
+ const [groupByOptions, setGroupByOptions] = useState<ColumnMeta[]>(
+ optionSelector.groupByOptions,
+ );
+
+ const [{ isOver, canDrop }, datasourcePanelDrop] = useDrop({
+ accept: DatasourcePanelDndType.COLUMN,
+
+ drop: (item: DatasourcePanelDndItem) => {
+ if (!optionSelector.isArray && !isEmpty(optionSelector.groupByOptions)) {
+ optionSelector.replace(0, item.metricOrColumnName);
+ } else {
+ optionSelector.add(item.metricOrColumnName);
+ }
+ setGroupByOptions(optionSelector.groupByOptions);
+ props.onChange(optionSelector.getValues());
+ },
+
+ canDrop: (item: DatasourcePanelDndItem) =>
+ !optionSelector.has(item.metricOrColumnName),
+
+ collect: monitor => ({
+ isOver: monitor.isOver(),
+ canDrop: monitor.canDrop(),
+ type: monitor.getItemType(),
+ }),
+ });
+
+ function onClickClose(index: number) {
+ optionSelector.del(index);
+ setGroupByOptions(optionSelector.groupByOptions);
+ props.onChange(optionSelector.getValues());
+ }
+
+ function onShiftOptions(dragIndex: number, hoverIndex: number) {
+ optionSelector.swap(dragIndex, hoverIndex);
+ setGroupByOptions(optionSelector.groupByOptions);
+ props.onChange(optionSelector.getValues());
+ }
+
+ function placeHolderRenderer() {
Review comment:
Nit:
```suggestion
function renderPlaceholder() {
```
`function placeHolderRenderer` feels like the function will return a
renderer itself.
##########
File path:
superset-frontend/src/explore/components/controls/DndColumnSelectControl/utils/optionSelector.ts
##########
@@ -0,0 +1,86 @@
+/**
+ * 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 { ColumnMeta } from '@superset-ui/chart-controls';
+
+export class OptionSelector {
Review comment:
I think it's possible to make this generic, too, so you can support
complex options (adhoc metrics, etc) in the list:
```ts
import { ensureIsArray } from '@superset-ui/core';
/**
* Get value from an option array.
*/
type GetValue<T> = (
option: T | string,
index?: number,
array?: (T | string)[],
) => T | string;
/**
* Get option from a value array.
*/
type GetOption<T> = (
value: T | string,
index?: number,
array?: (T | string)[],
) => T;
/**
* Select options from a known list by option value. Ignores invalid options.
*
* @param options - all known options, a dict of value to option.
* @param selected - value of selected options
* @param getValue - how to get value from each option
*/
export class OptionSelector<T> {
options: { [key: string]: T };
/**
* Selected values, always an array.
* If an item is string, then we look it up from options.
*/
selected: (T | string)[];
getValue: GetValue<T>;
getOption: GetOption<T>;
constructor({
options,
selected,
getValue = x => x,
getOption = x => (typeof x === 'string' ? this.options[x] : x),
}: {
options: { [key: string]: T };
selected: (T | string)[] | T | string | null;
getValue?: GetValue<T>;
getOption?: GetOption<T>;
}) {
this.options = options;
this.selected = ensureIsArray(selected)
.map(getValue)
.filter(x => (typeof x === 'string' ? x in options : Boolean(x)));
this.getOption = getOption;
this.getValue = getValue;
}
add(option: T) {
const value = this.getValue(option);
if (typeof value === 'string' || value in this.options) {
this.selected.push(value);
}
}
has(option: T): boolean {
return this.selected.includes(this.getValue(option));
}
replace(idx: number, value: T | string) {
this.selected[idx] = value;
}
del(idx: number) {
this.selected.splice(idx, 1);
}
swap(a: number, b: number) {
[this.selected[a], this.selected[b]] = [this.selected[b],
this.selected[a]];
}
/**
* Return selected options from value.
*/
selectedOptions(): T[] {
return this.selected.map(this.getOption);
}
}
// since `isMulti` should be managed in the control state:
isMulti ? optionSelector.selectedOptions() :
optionSelector.selectedOptions()[0];
```
Just a rough prototype but you get the idea.
##########
File path:
superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelectLabel.tsx
##########
@@ -0,0 +1,119 @@
+/**
+ * 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 { useDrop } from 'react-dnd';
+import { isEmpty } from 'lodash';
+import { t, useTheme } from '@superset-ui/core';
+import { BaseControlConfig, ColumnMeta } from '@superset-ui/chart-controls';
+import ControlHeader from 'src/explore/components/ControlHeader';
+import {
+ AddControlLabel,
+ DndLabelsContainer,
+ HeaderContainer,
+} from 'src/explore/components/OptionControls';
+import {
+ DatasourcePanelDndItem,
+ DatasourcePanelDndType,
+} from 'src/explore/components/DatasourcePanel/types';
+import Icon from 'src/components/Icon';
+import OptionWrapper from './components/OptionWrapper';
+import { OptionSelector } from './utils';
+
+interface LabelProps extends BaseControlConfig {
+ name: string;
+ value: string[] | string | null;
+ onChange: (value: string[] | string | null) => void;
+ options: { string: ColumnMeta };
+}
+
+export default function DndColumnSelectLabel(props: LabelProps) {
+ const theme = useTheme();
+ const { value, options } = props;
+ const optionSelector = new OptionSelector(options, value);
+ const [groupByOptions, setGroupByOptions] = useState<ColumnMeta[]>(
+ optionSelector.groupByOptions,
+ );
+
+ const [{ isOver, canDrop }, datasourcePanelDrop] = useDrop({
+ accept: DatasourcePanelDndType.COLUMN,
+
+ drop: (item: DatasourcePanelDndItem) => {
+ if (!optionSelector.isArray && !isEmpty(optionSelector.groupByOptions)) {
+ optionSelector.replace(0, item.metricOrColumnName);
+ } else {
+ optionSelector.add(item.metricOrColumnName);
+ }
+ setGroupByOptions(optionSelector.groupByOptions);
+ props.onChange(optionSelector.getValues());
+ },
+
+ canDrop: (item: DatasourcePanelDndItem) =>
+ !optionSelector.has(item.metricOrColumnName),
Review comment:
Currently only columns have a `canDrop` indicator when dropping to a
column select control, if you drag a metric, nothing will happen. It would be
nice to allow drag & drop anything to any drop area while showing a useful
`can't drop` warning when appropriate.
E.g.
<img width="1184" alt="Screen Shot 2021-02-25 at 10 32 42 AM"
src="https://user-images.githubusercontent.com/335541/109213860-f7ef7600-7765-11eb-93ad-afdf068a05af.png">
Example error messages:
- Column already added
- Cannot group by a metric
##########
File path:
superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelectLabel.tsx
##########
@@ -0,0 +1,119 @@
+/**
+ * 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 { useDrop } from 'react-dnd';
+import { isEmpty } from 'lodash';
+import { t, useTheme } from '@superset-ui/core';
+import { BaseControlConfig, ColumnMeta } from '@superset-ui/chart-controls';
+import ControlHeader from 'src/explore/components/ControlHeader';
+import {
+ AddControlLabel,
+ DndLabelsContainer,
+ HeaderContainer,
+} from 'src/explore/components/OptionControls';
+import {
+ DatasourcePanelDndItem,
+ DatasourcePanelDndType,
+} from 'src/explore/components/DatasourcePanel/types';
+import Icon from 'src/components/Icon';
+import OptionWrapper from './components/OptionWrapper';
+import { OptionSelector } from './utils';
+
+interface LabelProps extends BaseControlConfig {
+ name: string;
+ value: string[] | string | null;
Review comment:
I think you may still need a `isMulti` flag because `null` could
represents empty select for multiple selects as well.
##########
File path:
superset-frontend/src/explore/components/controls/DndColumnSelectControl/types.ts
##########
@@ -0,0 +1,34 @@
+/**
+ * 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 { ColumnMeta } from '@superset-ui/chart-controls';
+
+export interface OptionProps {
+ column: ColumnMeta;
+ index: number;
+ clickClose: (index: number) => void;
+ onShiftOptions: (dragIndex: number, hoverIndex: number) => void;
+ withCaret?: boolean;
+}
+
+export const GroupByItemType = 'groupByItem';
Review comment:
One way to make this more generic:
```suggestion
/**
* 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',
}
```
- If a `DndItemType.metric` or `DndItemType.metricOption` is dropped to a
group by (column select) control, reject and show a warning;
- if a `DndItemType.column` or `columnOption` is dropped to metric control,
create an adhoc metric using this column.
- If a `metric`, `metricOption`, `column`, or `columnOption` is dragged to
the adhoc filter control or sort by control, create a new filter using the
metric or column.
##########
File path: superset-frontend/src/explore/components/DatasourcePanel/types.ts
##########
@@ -0,0 +1,30 @@
+/**
+ * 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.
+ */
+export enum DatasourcePanelDndType {
+ // todo: The new `metric` conflicts with the existing metric type
+ METRIC = 'datasource-panel-metric',
+ COLUMN = 'column',
+}
+
+export interface DatasourcePanelDndItem {
+ metricOrColumnName: string;
+ type:
+ | typeof DatasourcePanelDndType.METRIC
+ | typeof DatasourcePanelDndType.COLUMN;
Review comment:
This should also work:
```suggestion
type: DatasourcePanelDndType;
```
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]