mirror of
https://github.com/apache/superset.git
synced 2026-04-18 23:55:00 +00:00
feat(explore): UX improvements for drag'n'dropping time column (#15740)
This commit is contained in:
committed by
GitHub
parent
e9383e6d00
commit
4234031cba
@@ -16,14 +16,14 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { styled, t } from '@superset-ui/core';
|
||||
import Collapse from 'src/components/Collapse';
|
||||
import { ControlConfig, DatasourceMeta } from '@superset-ui/chart-controls';
|
||||
import { debounce } from 'lodash';
|
||||
import { matchSorter, rankings } from 'match-sorter';
|
||||
import { FAST_DEBOUNCE } from 'src/constants';
|
||||
import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags';
|
||||
import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags';
|
||||
import { ExploreActions } from 'src/explore/actions/exploreActions';
|
||||
import Control from 'src/explore/components/Control';
|
||||
import DatasourcePanelDragWrapper from './DatasourcePanelDragWrapper';
|
||||
@@ -119,7 +119,23 @@ export default function DataSourcePanel({
|
||||
controls: { datasource: datasourceControl },
|
||||
actions,
|
||||
}: Props) {
|
||||
const { columns, metrics } = datasource;
|
||||
const { columns: _columns, metrics } = datasource;
|
||||
|
||||
// display temporal column first
|
||||
const columns = useMemo(
|
||||
() =>
|
||||
[..._columns].sort((col1, col2) => {
|
||||
if (col1.is_dttm && !col2.is_dttm) {
|
||||
return -1;
|
||||
}
|
||||
if (col2.is_dttm && !col1.is_dttm) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}),
|
||||
[_columns],
|
||||
);
|
||||
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
const [lists, setList] = useState({
|
||||
columns,
|
||||
|
||||
@@ -29,7 +29,14 @@ import { DndItemType } from 'src/explore/components/DndItemType';
|
||||
import { StyledColumnOption } from 'src/explore/components/optionRenderers';
|
||||
|
||||
export const DndColumnSelect = (props: LabelProps) => {
|
||||
const { value, options, multi = true, onChange } = props;
|
||||
const {
|
||||
value,
|
||||
options,
|
||||
multi = true,
|
||||
onChange,
|
||||
canDelete = true,
|
||||
ghostButtonText,
|
||||
} = props;
|
||||
const optionSelector = new OptionSelector(options, multi, value);
|
||||
|
||||
// synchronize values in case of dataset changes
|
||||
@@ -66,9 +73,12 @@ export const DndColumnSelect = (props: LabelProps) => {
|
||||
onChange(optionSelector.getValues());
|
||||
};
|
||||
|
||||
const canDrop = (item: DatasourcePanelDndItem) =>
|
||||
(multi || optionSelector.values.length === 0) &&
|
||||
!optionSelector.has((item.value as ColumnMeta).column_name);
|
||||
const canDrop = (item: DatasourcePanelDndItem) => {
|
||||
const columnName = (item.value as ColumnMeta).column_name;
|
||||
return (
|
||||
columnName in optionSelector.options && !optionSelector.has(columnName)
|
||||
);
|
||||
};
|
||||
|
||||
const onClickClose = (index: number) => {
|
||||
optionSelector.del(index);
|
||||
@@ -88,6 +98,7 @@ export const DndColumnSelect = (props: LabelProps) => {
|
||||
clickClose={onClickClose}
|
||||
onShiftOptions={onShiftOptions}
|
||||
type={DndItemType.ColumnOption}
|
||||
canDelete={canDelete}
|
||||
>
|
||||
<StyledColumnOption column={column} showType />
|
||||
</OptionWrapper>
|
||||
@@ -100,7 +111,9 @@ export const DndColumnSelect = (props: LabelProps) => {
|
||||
valuesRenderer={valuesRenderer}
|
||||
accept={DndItemType.Column}
|
||||
displayGhostButton={multi || optionSelector.values.length === 0}
|
||||
ghostButtonText={tn('Drop column', 'Drop columns', multi ? 2 : 1)}
|
||||
ghostButtonText={
|
||||
ghostButtonText || tn('Drop column', 'Drop columns', multi ? 2 : 1)
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -157,11 +157,11 @@ export const DndMetricSelect = (props: any) => {
|
||||
const canDrop = (item: DatasourcePanelDndItem) => {
|
||||
const isMetricAlreadyInValues =
|
||||
item.type === 'metric' ? value.includes(item.value.metric_name) : false;
|
||||
return (props.multi || value.length === 0) && !isMetricAlreadyInValues;
|
||||
return !isMetricAlreadyInValues;
|
||||
};
|
||||
|
||||
const onNewMetric = (newMetric: Metric) => {
|
||||
const newValue = [...value, newMetric];
|
||||
const newValue = props.isMulti ? [...value, newMetric] : [newMetric];
|
||||
setValue(newValue);
|
||||
handleChange(newValue);
|
||||
};
|
||||
|
||||
@@ -32,22 +32,28 @@ const StyledInfoTooltipWithTrigger = styled(InfoTooltipWithTrigger)`
|
||||
margin: 0 ${({ theme }) => theme.gridUnit}px;
|
||||
`;
|
||||
|
||||
export default function Option(props: OptionProps) {
|
||||
export default function Option({
|
||||
children,
|
||||
index,
|
||||
clickClose,
|
||||
withCaret,
|
||||
isExtra,
|
||||
canDelete = true,
|
||||
}: OptionProps) {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<OptionControlContainer
|
||||
data-test="option-label"
|
||||
withCaret={props.withCaret}
|
||||
>
|
||||
<CloseContainer
|
||||
role="button"
|
||||
data-test="remove-control-button"
|
||||
onClick={() => props.clickClose(props.index)}
|
||||
>
|
||||
<Icons.XSmall iconColor={theme.colors.grayscale.light1} />
|
||||
</CloseContainer>
|
||||
<Label data-test="control-label">{props.children}</Label>
|
||||
{props.isExtra && (
|
||||
<OptionControlContainer data-test="option-label" withCaret={withCaret}>
|
||||
{canDelete && (
|
||||
<CloseContainer
|
||||
role="button"
|
||||
data-test="remove-control-button"
|
||||
onClick={() => clickClose(index)}
|
||||
>
|
||||
<Icons.XSmall iconColor={theme.colors.grayscale.light1} />
|
||||
</CloseContainer>
|
||||
)}
|
||||
<Label data-test="control-label">{children}</Label>
|
||||
{isExtra && (
|
||||
<StyledInfoTooltipWithTrigger
|
||||
icon="exclamation-triangle"
|
||||
placement="top"
|
||||
@@ -58,7 +64,7 @@ export default function Option(props: OptionProps) {
|
||||
`)}
|
||||
/>
|
||||
)}
|
||||
{props.withCaret && (
|
||||
{withCaret && (
|
||||
<CaretContainer>
|
||||
<Icons.CaretRight iconColor={theme.colors.grayscale.light1} />
|
||||
</CaretContainer>
|
||||
|
||||
@@ -44,6 +44,7 @@ export default function OptionWrapper(
|
||||
clickClose,
|
||||
withCaret,
|
||||
isExtra,
|
||||
canDelete = true,
|
||||
children,
|
||||
...rest
|
||||
} = props;
|
||||
@@ -113,6 +114,7 @@ export default function OptionWrapper(
|
||||
clickClose={clickClose}
|
||||
withCaret={withCaret}
|
||||
isExtra={isExtra}
|
||||
canDelete={canDelete}
|
||||
>
|
||||
{children}
|
||||
</Option>
|
||||
|
||||
@@ -28,6 +28,7 @@ export interface OptionProps {
|
||||
clickClose: (index: number) => void;
|
||||
withCaret?: boolean;
|
||||
isExtra?: boolean;
|
||||
canDelete?: boolean;
|
||||
}
|
||||
|
||||
export interface OptionItemInterface {
|
||||
@@ -41,6 +42,8 @@ export interface LabelProps<T = string[] | string> {
|
||||
onChange: (value?: T) => void;
|
||||
options: { string: ColumnMeta };
|
||||
multi?: boolean;
|
||||
canDelete?: boolean;
|
||||
ghostButtonText?: string;
|
||||
}
|
||||
|
||||
export interface DndColumnSelectProps<
|
||||
|
||||
Reference in New Issue
Block a user