mirror of
https://github.com/apache/superset.git
synced 2024-09-17 11:09:47 -04:00
refactor(explore): improve typing for Dnd controls (#16362)
This commit is contained in:
parent
18be181946
commit
ec087507e5
@ -57,12 +57,15 @@ export type DashboardLayoutState = { present: DashboardLayout };
|
|||||||
export type DashboardState = {
|
export type DashboardState = {
|
||||||
preselectNativeFilters?: JsonObject;
|
preselectNativeFilters?: JsonObject;
|
||||||
editMode: boolean;
|
editMode: boolean;
|
||||||
|
isPublished: boolean;
|
||||||
directPathToChild: string[];
|
directPathToChild: string[];
|
||||||
activeTabs: ActiveTabs;
|
activeTabs: ActiveTabs;
|
||||||
fullSizeChartId: number | null;
|
fullSizeChartId: number | null;
|
||||||
isRefreshing: boolean;
|
isRefreshing: boolean;
|
||||||
|
hasUnsavedChanges: boolean;
|
||||||
};
|
};
|
||||||
export type DashboardInfo = {
|
export type DashboardInfo = {
|
||||||
|
id: number;
|
||||||
common: {
|
common: {
|
||||||
flash_messages: string[];
|
flash_messages: string[];
|
||||||
conf: JsonObject;
|
conf: JsonObject;
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
import React, { ReactNode, useCallback, useState } from 'react';
|
import React, { ReactNode, useCallback, useState } from 'react';
|
||||||
import { ControlType } from '@superset-ui/chart-controls';
|
import { ControlType } from '@superset-ui/chart-controls';
|
||||||
|
import { ControlComponentProps as BaseControlComponentProps } from '@superset-ui/chart-controls/lib/shared-controls/components/types';
|
||||||
import { JsonValue, QueryFormData } from '@superset-ui/core';
|
import { JsonValue, QueryFormData } from '@superset-ui/core';
|
||||||
import ErrorBoundary from 'src/components/ErrorBoundary';
|
import ErrorBoundary from 'src/components/ErrorBoundary';
|
||||||
import { ExploreActions } from 'src/explore/actions/exploreActions';
|
import { ExploreActions } from 'src/explore/actions/exploreActions';
|
||||||
@ -43,6 +44,13 @@ export type ControlProps = {
|
|||||||
renderTrigger?: boolean;
|
renderTrigger?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type ControlComponentProps<
|
||||||
|
ValueType extends JsonValue = JsonValue
|
||||||
|
> = Omit<ControlProps, 'value'> & BaseControlComponentProps<ValueType>;
|
||||||
|
|
||||||
export default function Control(props: ControlProps) {
|
export default function Control(props: ControlProps) {
|
||||||
const {
|
const {
|
||||||
actions: { setControlValue },
|
actions: { setControlValue },
|
||||||
|
@ -26,8 +26,8 @@ import Icons from 'src/components/Icons';
|
|||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
name: PropTypes.string,
|
name: PropTypes.string,
|
||||||
label: PropTypes.string,
|
label: PropTypes.node,
|
||||||
description: PropTypes.string,
|
description: PropTypes.node,
|
||||||
validationErrors: PropTypes.array,
|
validationErrors: PropTypes.array,
|
||||||
renderTrigger: PropTypes.bool,
|
renderTrigger: PropTypes.bool,
|
||||||
rightNode: PropTypes.node,
|
rightNode: PropTypes.node,
|
||||||
|
@ -45,7 +45,7 @@ const datasource = {
|
|||||||
datasource_name: 'table1',
|
datasource_name: 'table1',
|
||||||
description: 'desc',
|
description: 'desc',
|
||||||
};
|
};
|
||||||
const props = {
|
const props: DatasourcePanelProps = {
|
||||||
datasource,
|
datasource,
|
||||||
controls: {
|
controls: {
|
||||||
datasource: {
|
datasource: {
|
||||||
@ -57,12 +57,7 @@ const props = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
setControlValue: () => ({
|
setControlValue: jest.fn(),
|
||||||
type: 'type',
|
|
||||||
controlName: 'control',
|
|
||||||
value: 'val',
|
|
||||||
validationErrors: [],
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -16,13 +16,7 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import React, {
|
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useMemo,
|
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
} from 'react';
|
|
||||||
import { ControlConfig, DatasourceMeta } from '@superset-ui/chart-controls';
|
import { ControlConfig, DatasourceMeta } from '@superset-ui/chart-controls';
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
import { matchSorter, rankings } from 'match-sorter';
|
import { matchSorter, rankings } from 'match-sorter';
|
||||||
@ -193,7 +187,8 @@ export default function DataSourcePanel({
|
|||||||
const DEFAULT_MAX_COLUMNS_LENGTH = 50;
|
const DEFAULT_MAX_COLUMNS_LENGTH = 50;
|
||||||
const DEFAULT_MAX_METRICS_LENGTH = 50;
|
const DEFAULT_MAX_METRICS_LENGTH = 50;
|
||||||
|
|
||||||
const search = useCallback(
|
const search = useMemo(
|
||||||
|
() =>
|
||||||
debounce((value: string) => {
|
debounce((value: string) => {
|
||||||
if (value === '') {
|
if (value === '') {
|
||||||
setList({ columns, metrics });
|
setList({ columns, metrics });
|
||||||
|
@ -18,13 +18,17 @@
|
|||||||
*/
|
*/
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { render, screen } from 'spec/helpers/testing-library';
|
import { render, screen } from 'spec/helpers/testing-library';
|
||||||
import { LabelProps } from 'src/explore/components/controls/DndColumnSelectControl/types';
|
import {
|
||||||
import { DndColumnSelect } from 'src/explore/components/controls/DndColumnSelectControl/DndColumnSelect';
|
DndColumnSelect,
|
||||||
|
DndColumnSelectProps,
|
||||||
|
} from 'src/explore/components/controls/DndColumnSelectControl/DndColumnSelect';
|
||||||
|
|
||||||
const defaultProps: LabelProps = {
|
const defaultProps: DndColumnSelectProps = {
|
||||||
|
type: 'DndColumnSelect',
|
||||||
name: 'Filter',
|
name: 'Filter',
|
||||||
onChange: jest.fn(),
|
onChange: jest.fn(),
|
||||||
options: { string: { column_name: 'Column A' } },
|
options: { string: { column_name: 'Column A' } },
|
||||||
|
actions: { setControlValue: jest.fn() },
|
||||||
};
|
};
|
||||||
|
|
||||||
test('renders with default props', () => {
|
test('renders with default props', () => {
|
||||||
|
@ -20,7 +20,6 @@ import React, { useCallback, useMemo, useState } from 'react';
|
|||||||
import { FeatureFlag, isFeatureEnabled, tn } from '@superset-ui/core';
|
import { FeatureFlag, isFeatureEnabled, tn } from '@superset-ui/core';
|
||||||
import { ColumnMeta } from '@superset-ui/chart-controls';
|
import { ColumnMeta } from '@superset-ui/chart-controls';
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
import { LabelProps } from 'src/explore/components/controls/DndColumnSelectControl/types';
|
|
||||||
import DndSelectLabel from 'src/explore/components/controls/DndColumnSelectControl/DndSelectLabel';
|
import DndSelectLabel from 'src/explore/components/controls/DndColumnSelectControl/DndSelectLabel';
|
||||||
import OptionWrapper from 'src/explore/components/controls/DndColumnSelectControl/OptionWrapper';
|
import OptionWrapper from 'src/explore/components/controls/DndColumnSelectControl/OptionWrapper';
|
||||||
import { OptionSelector } from 'src/explore/components/controls/DndColumnSelectControl/utils';
|
import { OptionSelector } from 'src/explore/components/controls/DndColumnSelectControl/utils';
|
||||||
@ -28,8 +27,13 @@ import { DatasourcePanelDndItem } from 'src/explore/components/DatasourcePanel/t
|
|||||||
import { DndItemType } from 'src/explore/components/DndItemType';
|
import { DndItemType } from 'src/explore/components/DndItemType';
|
||||||
import { useComponentDidUpdate } from 'src/common/hooks/useComponentDidUpdate';
|
import { useComponentDidUpdate } from 'src/common/hooks/useComponentDidUpdate';
|
||||||
import ColumnSelectPopoverTrigger from './ColumnSelectPopoverTrigger';
|
import ColumnSelectPopoverTrigger from './ColumnSelectPopoverTrigger';
|
||||||
|
import { DndControlProps } from './types';
|
||||||
|
|
||||||
export const DndColumnSelect = (props: LabelProps) => {
|
export type DndColumnSelectProps = DndControlProps<string> & {
|
||||||
|
options: Record<string, ColumnMeta>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function DndColumnSelect(props: DndColumnSelectProps) {
|
||||||
const {
|
const {
|
||||||
value,
|
value,
|
||||||
options,
|
options,
|
||||||
@ -68,6 +72,7 @@ export const DndColumnSelect = (props: LabelProps) => {
|
|||||||
) {
|
) {
|
||||||
onChange(optionSelectorValues);
|
onChange(optionSelectorValues);
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [JSON.stringify(value), JSON.stringify(optionSelector.getValues())]);
|
}, [JSON.stringify(value), JSON.stringify(optionSelector.getValues())]);
|
||||||
|
|
||||||
// useComponentDidUpdate to avoid running this for the first render, to avoid
|
// useComponentDidUpdate to avoid running this for the first render, to avoid
|
||||||
@ -203,7 +208,7 @@ export const DndColumnSelect = (props: LabelProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<DndSelectLabel<string | string[], ColumnMeta[]>
|
<DndSelectLabel
|
||||||
onDrop={onDrop}
|
onDrop={onDrop}
|
||||||
canDrop={canDrop}
|
canDrop={canDrop}
|
||||||
valuesRenderer={valuesRenderer}
|
valuesRenderer={valuesRenderer}
|
||||||
@ -229,4 +234,4 @@ export const DndColumnSelect = (props: LabelProps) => {
|
|||||||
</ColumnSelectPopoverTrigger>
|
</ColumnSelectPopoverTrigger>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
@ -23,17 +23,24 @@ import AdhocMetric from 'src/explore/components/controls/MetricControl/AdhocMetr
|
|||||||
import AdhocFilter, {
|
import AdhocFilter, {
|
||||||
EXPRESSION_TYPES,
|
EXPRESSION_TYPES,
|
||||||
} from 'src/explore/components/controls/FilterControl/AdhocFilter';
|
} from 'src/explore/components/controls/FilterControl/AdhocFilter';
|
||||||
import { DndFilterSelect } from 'src/explore/components/controls/DndColumnSelectControl/DndFilterSelect';
|
import {
|
||||||
|
DndFilterSelect,
|
||||||
|
DndFilterSelectProps,
|
||||||
|
} from 'src/explore/components/controls/DndColumnSelectControl/DndFilterSelect';
|
||||||
|
import { PLACEHOLDER_DATASOURCE } from 'src/dashboard/constants';
|
||||||
|
import { DEFAULT_FORM_DATA } from '@superset-ui/plugin-chart-echarts/lib/Timeseries/types';
|
||||||
|
|
||||||
const defaultProps = {
|
const defaultProps: DndFilterSelectProps = {
|
||||||
|
type: 'DndFilterSelect',
|
||||||
name: 'Filter',
|
name: 'Filter',
|
||||||
value: [],
|
value: [],
|
||||||
columns: [],
|
columns: [],
|
||||||
datasource: {},
|
datasource: PLACEHOLDER_DATASOURCE,
|
||||||
formData: {},
|
formData: null,
|
||||||
savedMetrics: [],
|
savedMetrics: [],
|
||||||
|
selectedMetrics: [],
|
||||||
onChange: jest.fn(),
|
onChange: jest.fn(),
|
||||||
options: { string: { column_name: 'Column' } },
|
actions: { setControlValue: jest.fn() },
|
||||||
};
|
};
|
||||||
|
|
||||||
test('renders with default props', () => {
|
test('renders with default props', () => {
|
||||||
@ -53,9 +60,15 @@ test('renders with value', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('renders options with saved metric', () => {
|
test('renders options with saved metric', () => {
|
||||||
render(<DndFilterSelect {...defaultProps} formData={['saved_metric']} />, {
|
render(
|
||||||
|
<DndFilterSelect
|
||||||
|
{...defaultProps}
|
||||||
|
formData={{ ...DEFAULT_FORM_DATA, metrics: ['saved_metric'] }}
|
||||||
|
/>,
|
||||||
|
{
|
||||||
useDnd: true,
|
useDnd: true,
|
||||||
});
|
},
|
||||||
|
);
|
||||||
expect(screen.getByText('Drop columns or metrics here')).toBeInTheDocument();
|
expect(screen.getByText('Drop columns or metrics here')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -84,8 +97,14 @@ test('renders options with adhoc metric', () => {
|
|||||||
expression: 'AVG(birth_names.num)',
|
expression: 'AVG(birth_names.num)',
|
||||||
metric_name: 'avg__num',
|
metric_name: 'avg__num',
|
||||||
});
|
});
|
||||||
render(<DndFilterSelect {...defaultProps} formData={[adhocMetric]} />, {
|
render(
|
||||||
|
<DndFilterSelect
|
||||||
|
{...defaultProps}
|
||||||
|
formData={{ ...DEFAULT_FORM_DATA, metrics: [adhocMetric] }}
|
||||||
|
/>,
|
||||||
|
{
|
||||||
useDnd: true,
|
useDnd: true,
|
||||||
});
|
},
|
||||||
|
);
|
||||||
expect(screen.getByText('Drop columns or metrics here')).toBeInTheDocument();
|
expect(screen.getByText('Drop columns or metrics here')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
@ -22,6 +22,8 @@ import {
|
|||||||
isFeatureEnabled,
|
isFeatureEnabled,
|
||||||
logging,
|
logging,
|
||||||
Metric,
|
Metric,
|
||||||
|
QueryFormData,
|
||||||
|
QueryFormMetric,
|
||||||
SupersetClient,
|
SupersetClient,
|
||||||
t,
|
t,
|
||||||
} from '@superset-ui/core';
|
} from '@superset-ui/core';
|
||||||
@ -30,11 +32,8 @@ import {
|
|||||||
OPERATOR_ENUM_TO_OPERATOR_TYPE,
|
OPERATOR_ENUM_TO_OPERATOR_TYPE,
|
||||||
Operators,
|
Operators,
|
||||||
} from 'src/explore/constants';
|
} from 'src/explore/constants';
|
||||||
import { OptionSortType } from 'src/explore/types';
|
import { Datasource, OptionSortType } from 'src/explore/types';
|
||||||
import {
|
import { OptionValueType } from 'src/explore/components/controls/DndColumnSelectControl/types';
|
||||||
DndFilterSelectProps,
|
|
||||||
OptionValueType,
|
|
||||||
} from 'src/explore/components/controls/DndColumnSelectControl/types';
|
|
||||||
import AdhocFilterPopoverTrigger from 'src/explore/components/controls/FilterControl/AdhocFilterPopoverTrigger';
|
import AdhocFilterPopoverTrigger from 'src/explore/components/controls/FilterControl/AdhocFilterPopoverTrigger';
|
||||||
import OptionWrapper from 'src/explore/components/controls/DndColumnSelectControl/OptionWrapper';
|
import OptionWrapper from 'src/explore/components/controls/DndColumnSelectControl/OptionWrapper';
|
||||||
import DndSelectLabel from 'src/explore/components/controls/DndColumnSelectControl/DndSelectLabel';
|
import DndSelectLabel from 'src/explore/components/controls/DndColumnSelectControl/DndSelectLabel';
|
||||||
@ -48,6 +47,7 @@ import {
|
|||||||
DndItemValue,
|
DndItemValue,
|
||||||
} from 'src/explore/components/DatasourcePanel/types';
|
} from 'src/explore/components/DatasourcePanel/types';
|
||||||
import { DndItemType } from 'src/explore/components/DndItemType';
|
import { DndItemType } from 'src/explore/components/DndItemType';
|
||||||
|
import { ControlComponentProps } from 'src/explore/components/Control';
|
||||||
|
|
||||||
const DND_ACCEPTED_TYPES = [
|
const DND_ACCEPTED_TYPES = [
|
||||||
DndItemType.Column,
|
DndItemType.Column,
|
||||||
@ -59,8 +59,16 @@ const DND_ACCEPTED_TYPES = [
|
|||||||
const isDictionaryForAdhocFilter = (value: OptionValueType) =>
|
const isDictionaryForAdhocFilter = (value: OptionValueType) =>
|
||||||
!(value instanceof AdhocFilter) && value?.expressionType;
|
!(value instanceof AdhocFilter) && value?.expressionType;
|
||||||
|
|
||||||
|
export interface DndFilterSelectProps
|
||||||
|
extends ControlComponentProps<OptionValueType[]> {
|
||||||
|
columns: ColumnMeta[];
|
||||||
|
savedMetrics: Metric[];
|
||||||
|
selectedMetrics: QueryFormMetric[];
|
||||||
|
datasource: Datasource;
|
||||||
|
}
|
||||||
|
|
||||||
export const DndFilterSelect = (props: DndFilterSelectProps) => {
|
export const DndFilterSelect = (props: DndFilterSelectProps) => {
|
||||||
const { datasource, onChange } = props;
|
const { datasource, onChange = () => {}, name: controlName } = props;
|
||||||
|
|
||||||
const propsValues = Array.from(props.value ?? []);
|
const propsValues = Array.from(props.value ?? []);
|
||||||
const [values, setValues] = useState(
|
const [values, setValues] = useState(
|
||||||
@ -74,7 +82,7 @@ export const DndFilterSelect = (props: DndFilterSelectProps) => {
|
|||||||
|
|
||||||
const optionsForSelect = (
|
const optionsForSelect = (
|
||||||
columns: ColumnMeta[],
|
columns: ColumnMeta[],
|
||||||
formData: Record<string, any>,
|
formData: QueryFormData | null | undefined,
|
||||||
) => {
|
) => {
|
||||||
const options: OptionSortType[] = [
|
const options: OptionSortType[] = [
|
||||||
...columns,
|
...columns,
|
||||||
@ -369,7 +377,7 @@ export const DndFilterSelect = (props: DndFilterSelectProps) => {
|
|||||||
setDroppedItem(item.value);
|
setDroppedItem(item.value);
|
||||||
togglePopover(true);
|
togglePopover(true);
|
||||||
},
|
},
|
||||||
[togglePopover],
|
[controlName, togglePopover],
|
||||||
);
|
);
|
||||||
|
|
||||||
const ghostButtonText = isFeatureEnabled(FeatureFlag.ENABLE_DND_WITH_CLICK_UX)
|
const ghostButtonText = isFeatureEnabled(FeatureFlag.ENABLE_DND_WITH_CLICK_UX)
|
||||||
@ -378,7 +386,7 @@ export const DndFilterSelect = (props: DndFilterSelectProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DndSelectLabel<OptionValueType, OptionValueType[]>
|
<DndSelectLabel
|
||||||
onDrop={handleDrop}
|
onDrop={handleDrop}
|
||||||
canDrop={canDrop}
|
canDrop={canDrop}
|
||||||
valuesRenderer={valuesRenderer}
|
valuesRenderer={valuesRenderer}
|
||||||
|
@ -19,11 +19,13 @@
|
|||||||
|
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
|
DatasourceType,
|
||||||
ensureIsArray,
|
ensureIsArray,
|
||||||
FeatureFlag,
|
FeatureFlag,
|
||||||
GenericDataType,
|
GenericDataType,
|
||||||
isFeatureEnabled,
|
isFeatureEnabled,
|
||||||
Metric,
|
Metric,
|
||||||
|
QueryFormMetric,
|
||||||
tn,
|
tn,
|
||||||
} from '@superset-ui/core';
|
} from '@superset-ui/core';
|
||||||
import { ColumnMeta } from '@superset-ui/chart-controls';
|
import { ColumnMeta } from '@superset-ui/chart-controls';
|
||||||
@ -32,12 +34,12 @@ import { usePrevious } from 'src/common/hooks/usePrevious';
|
|||||||
import AdhocMetric from 'src/explore/components/controls/MetricControl/AdhocMetric';
|
import AdhocMetric from 'src/explore/components/controls/MetricControl/AdhocMetric';
|
||||||
import AdhocMetricPopoverTrigger from 'src/explore/components/controls/MetricControl/AdhocMetricPopoverTrigger';
|
import AdhocMetricPopoverTrigger from 'src/explore/components/controls/MetricControl/AdhocMetricPopoverTrigger';
|
||||||
import MetricDefinitionValue from 'src/explore/components/controls/MetricControl/MetricDefinitionValue';
|
import MetricDefinitionValue from 'src/explore/components/controls/MetricControl/MetricDefinitionValue';
|
||||||
import { OptionValueType } from 'src/explore/components/controls/DndColumnSelectControl/types';
|
|
||||||
import { DatasourcePanelDndItem } from 'src/explore/components/DatasourcePanel/types';
|
import { DatasourcePanelDndItem } from 'src/explore/components/DatasourcePanel/types';
|
||||||
import { DndItemType } from 'src/explore/components/DndItemType';
|
import { DndItemType } from 'src/explore/components/DndItemType';
|
||||||
import DndSelectLabel from 'src/explore/components/controls/DndColumnSelectControl/DndSelectLabel';
|
import DndSelectLabel from 'src/explore/components/controls/DndColumnSelectControl/DndSelectLabel';
|
||||||
import { savedMetricType } from 'src/explore/components/controls/MetricControl/types';
|
import { savedMetricType } from 'src/explore/components/controls/MetricControl/types';
|
||||||
import { AGGREGATES } from 'src/explore/constants';
|
import { AGGREGATES } from 'src/explore/constants';
|
||||||
|
import { DndControlProps } from './types';
|
||||||
|
|
||||||
const EMPTY_OBJECT = {};
|
const EMPTY_OBJECT = {};
|
||||||
const DND_ACCEPTED_TYPES = [DndItemType.Column, DndItemType.Metric];
|
const DND_ACCEPTED_TYPES = [DndItemType.Column, DndItemType.Metric];
|
||||||
@ -75,10 +77,12 @@ const getOptionsForSavedMetrics = (
|
|||||||
: savedMetric,
|
: savedMetric,
|
||||||
) ?? [];
|
) ?? [];
|
||||||
|
|
||||||
|
type ValueType = Metric | AdhocMetric | QueryFormMetric;
|
||||||
|
|
||||||
const columnsContainAllMetrics = (
|
const columnsContainAllMetrics = (
|
||||||
value: (string | AdhocMetric | ColumnMeta)[],
|
value: ValueType | ValueType[] | null | undefined,
|
||||||
columns: ColumnMeta[],
|
columns: ColumnMeta[],
|
||||||
savedMetrics: savedMetricType[],
|
savedMetrics: (savedMetricType | Metric)[],
|
||||||
) => {
|
) => {
|
||||||
const columnNames = new Set(
|
const columnNames = new Set(
|
||||||
[...(columns || []), ...(savedMetrics || [])]
|
[...(columns || []), ...(savedMetrics || [])]
|
||||||
@ -104,6 +108,12 @@ const columnsContainAllMetrics = (
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type DndMetricSelectProps = DndControlProps<ValueType> & {
|
||||||
|
savedMetrics: savedMetricType[];
|
||||||
|
columns: ColumnMeta[];
|
||||||
|
datasourceType?: DatasourceType;
|
||||||
|
};
|
||||||
|
|
||||||
export const DndMetricSelect = (props: any) => {
|
export const DndMetricSelect = (props: any) => {
|
||||||
const { onChange, multi, columns, savedMetrics } = props;
|
const { onChange, multi, columns, savedMetrics } = props;
|
||||||
|
|
||||||
@ -130,7 +140,7 @@ export const DndMetricSelect = (props: any) => {
|
|||||||
[multi, onChange],
|
[multi, onChange],
|
||||||
);
|
);
|
||||||
|
|
||||||
const [value, setValue] = useState<(AdhocMetric | Metric | string)[]>(
|
const [value, setValue] = useState<ValueType[]>(
|
||||||
coerceAdhocMetrics(props.value),
|
coerceAdhocMetrics(props.value),
|
||||||
);
|
);
|
||||||
const [droppedItem, setDroppedItem] = useState<DatasourcePanelDndItem | null>(
|
const [droppedItem, setDroppedItem] = useState<DatasourcePanelDndItem | null>(
|
||||||
@ -176,7 +186,9 @@ export const DndMetricSelect = (props: any) => {
|
|||||||
|
|
||||||
const onNewMetric = useCallback(
|
const onNewMetric = useCallback(
|
||||||
(newMetric: Metric) => {
|
(newMetric: Metric) => {
|
||||||
const newValue = props.multi ? [...value, newMetric] : [newMetric];
|
const newValue = props.multi
|
||||||
|
? [...value, newMetric.metric_name]
|
||||||
|
: [newMetric.metric_name];
|
||||||
setValue(newValue);
|
setValue(newValue);
|
||||||
handleChange(newValue);
|
handleChange(newValue);
|
||||||
},
|
},
|
||||||
@ -191,7 +203,7 @@ export const DndMetricSelect = (props: any) => {
|
|||||||
const newValue = value.map(value => {
|
const newValue = value.map(value => {
|
||||||
if (
|
if (
|
||||||
// compare saved metrics
|
// compare saved metrics
|
||||||
value === (oldMetric as Metric).metric_name ||
|
('metric_name' in oldMetric && value === oldMetric.metric_name) ||
|
||||||
// compare adhoc metrics
|
// compare adhoc metrics
|
||||||
typeof (value as AdhocMetric).optionName !== 'undefined'
|
typeof (value as AdhocMetric).optionName !== 'undefined'
|
||||||
? (value as AdhocMetric).optionName ===
|
? (value as AdhocMetric).optionName ===
|
||||||
@ -254,7 +266,7 @@ export const DndMetricSelect = (props: any) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const valueRenderer = useCallback(
|
const valueRenderer = useCallback(
|
||||||
(option: Metric | AdhocMetric | string, index: number) => (
|
(option: ValueType, index: number) => (
|
||||||
<MetricDefinitionValue
|
<MetricDefinitionValue
|
||||||
key={index}
|
key={index}
|
||||||
index={index}
|
index={index}
|
||||||
@ -353,7 +365,7 @@ export const DndMetricSelect = (props: any) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="metrics-select">
|
<div className="metrics-select">
|
||||||
<DndSelectLabel<OptionValueType, OptionValueType[]>
|
<DndSelectLabel
|
||||||
onDrop={handleDrop}
|
onDrop={handleDrop}
|
||||||
canDrop={canDrop}
|
canDrop={canDrop}
|
||||||
valuesRenderer={valuesRenderer}
|
valuesRenderer={valuesRenderer}
|
||||||
|
@ -19,16 +19,16 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { render, screen } from 'spec/helpers/testing-library';
|
import { render, screen } from 'spec/helpers/testing-library';
|
||||||
import { DndItemType } from 'src/explore/components/DndItemType';
|
import { DndItemType } from 'src/explore/components/DndItemType';
|
||||||
import DndSelectLabel from 'src/explore/components/controls/DndColumnSelectControl/DndSelectLabel';
|
import DndSelectLabel, {
|
||||||
|
DndSelectLabelProps,
|
||||||
|
} from 'src/explore/components/controls/DndColumnSelectControl/DndSelectLabel';
|
||||||
|
|
||||||
const defaultProps = {
|
const defaultProps: DndSelectLabelProps = {
|
||||||
name: 'Column',
|
name: 'Column',
|
||||||
accept: 'Column' as DndItemType,
|
accept: 'Column' as DndItemType,
|
||||||
onDrop: jest.fn(),
|
onDrop: jest.fn(),
|
||||||
canDrop: () => false,
|
canDrop: () => false,
|
||||||
valuesRenderer: () => <span />,
|
valuesRenderer: () => <span />,
|
||||||
onChange: jest.fn(),
|
|
||||||
options: { string: { column_name: 'Column' } },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
test('renders with default props', async () => {
|
test('renders with default props', async () => {
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import React from 'react';
|
import React, { ReactNode } from 'react';
|
||||||
import { useDrop } from 'react-dnd';
|
import { useDrop } from 'react-dnd';
|
||||||
import { t, useTheme } from '@superset-ui/core';
|
import { t, useTheme } from '@superset-ui/core';
|
||||||
import ControlHeader from 'src/explore/components/ControlHeader';
|
import ControlHeader from 'src/explore/components/ControlHeader';
|
||||||
@ -25,18 +25,35 @@ import {
|
|||||||
DndLabelsContainer,
|
DndLabelsContainer,
|
||||||
HeaderContainer,
|
HeaderContainer,
|
||||||
} from 'src/explore/components/controls/OptionControls';
|
} from 'src/explore/components/controls/OptionControls';
|
||||||
import { DatasourcePanelDndItem } from 'src/explore/components/DatasourcePanel/types';
|
import {
|
||||||
|
DatasourcePanelDndItem,
|
||||||
|
DndItemValue,
|
||||||
|
} from 'src/explore/components/DatasourcePanel/types';
|
||||||
import Icons from 'src/components/Icons';
|
import Icons from 'src/components/Icons';
|
||||||
import { DndColumnSelectProps } from './types';
|
import { DndItemType } from '../../DndItemType';
|
||||||
|
|
||||||
export default function DndSelectLabel<T, O>({
|
export type DndSelectLabelProps = {
|
||||||
|
name: string;
|
||||||
|
accept: DndItemType | DndItemType[];
|
||||||
|
ghostButtonText?: string;
|
||||||
|
onDrop: (item: DatasourcePanelDndItem) => void;
|
||||||
|
canDrop: (item: DatasourcePanelDndItem) => boolean;
|
||||||
|
canDropValue?: (value: DndItemValue) => boolean;
|
||||||
|
onDropValue?: (value: DndItemValue) => void;
|
||||||
|
valuesRenderer: () => ReactNode;
|
||||||
|
displayGhostButton?: boolean;
|
||||||
|
onClickGhostButton?: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function DndSelectLabel({
|
||||||
displayGhostButton = true,
|
displayGhostButton = true,
|
||||||
|
accept,
|
||||||
...props
|
...props
|
||||||
}: DndColumnSelectProps<T, O>) {
|
}: DndSelectLabelProps) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const [{ isOver, canDrop }, datasourcePanelDrop] = useDrop({
|
const [{ isOver, canDrop }, datasourcePanelDrop] = useDrop({
|
||||||
accept: props.accept,
|
accept,
|
||||||
|
|
||||||
drop: (item: DatasourcePanelDndItem) => {
|
drop: (item: DatasourcePanelDndItem) => {
|
||||||
props.onDrop(item);
|
props.onDrop(item);
|
||||||
|
@ -17,13 +17,9 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import { Metric } from '@superset-ui/core';
|
import { JsonValue } from '@superset-ui/core';
|
||||||
|
import { ControlComponentProps } from 'src/explore/components/Control';
|
||||||
import { ColumnMeta } from '@superset-ui/chart-controls';
|
import { ColumnMeta } from '@superset-ui/chart-controls';
|
||||||
import {
|
|
||||||
DatasourcePanelDndItem,
|
|
||||||
DndItemValue,
|
|
||||||
} from '../../DatasourcePanel/types';
|
|
||||||
import { DndItemType } from '../../DndItemType';
|
|
||||||
|
|
||||||
export interface OptionProps {
|
export interface OptionProps {
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
@ -42,40 +38,16 @@ export interface OptionItemInterface {
|
|||||||
dragIndex: number;
|
dragIndex: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LabelProps<T = string[] | string> {
|
/**
|
||||||
name: string;
|
* Shared control props for all DnD control.
|
||||||
value?: T;
|
*/
|
||||||
onChange: (value?: T) => void;
|
export type DndControlProps<
|
||||||
options: { string: ColumnMeta };
|
ValueType extends JsonValue
|
||||||
|
> = ControlComponentProps<ValueType | ValueType[] | null> & {
|
||||||
multi?: boolean;
|
multi?: boolean;
|
||||||
canDelete?: boolean;
|
canDelete?: boolean;
|
||||||
ghostButtonText?: string;
|
ghostButtonText?: string;
|
||||||
label?: string;
|
onChange: (value: ValueType | ValueType[] | null | undefined) => void;
|
||||||
}
|
};
|
||||||
|
|
||||||
export interface DndColumnSelectProps<
|
|
||||||
T = string[] | string,
|
|
||||||
O = string[] | string
|
|
||||||
> extends LabelProps<T> {
|
|
||||||
onDrop: (item: DatasourcePanelDndItem) => void;
|
|
||||||
canDrop: (item: DatasourcePanelDndItem) => boolean;
|
|
||||||
canDropValue?: (value: DndItemValue) => boolean;
|
|
||||||
onDropValue?: (value: DndItemValue) => void;
|
|
||||||
valuesRenderer: () => ReactNode;
|
|
||||||
accept: DndItemType | DndItemType[];
|
|
||||||
ghostButtonText?: string;
|
|
||||||
displayGhostButton?: boolean;
|
|
||||||
onClickGhostButton?: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type OptionValueType = Record<string, any>;
|
export type OptionValueType = Record<string, any>;
|
||||||
export interface DndFilterSelectProps {
|
|
||||||
name: string;
|
|
||||||
value: OptionValueType[];
|
|
||||||
columns: ColumnMeta[];
|
|
||||||
datasource: Record<string, any>;
|
|
||||||
formData: Record<string, any>;
|
|
||||||
savedMetrics: Metric[];
|
|
||||||
onChange: (filters: OptionValueType[]) => void;
|
|
||||||
options: { string: ColumnMeta };
|
|
||||||
}
|
|
||||||
|
@ -22,25 +22,25 @@ import { ensureIsArray } from '@superset-ui/core';
|
|||||||
export class OptionSelector {
|
export class OptionSelector {
|
||||||
values: ColumnMeta[];
|
values: ColumnMeta[];
|
||||||
|
|
||||||
options: { string: ColumnMeta };
|
options: Record<string, ColumnMeta>;
|
||||||
|
|
||||||
multi: boolean;
|
multi: boolean;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
options: { string: ColumnMeta },
|
options: Record<string, ColumnMeta>,
|
||||||
multi: boolean,
|
multi: boolean,
|
||||||
initialValues?: string[] | string,
|
initialValues?: string[] | string | null,
|
||||||
) {
|
) {
|
||||||
this.options = options;
|
this.options = options;
|
||||||
this.multi = multi;
|
this.multi = multi;
|
||||||
this.values = ensureIsArray(initialValues)
|
this.values = ensureIsArray(initialValues)
|
||||||
.map(value => {
|
.map(value => {
|
||||||
if (value in options) {
|
if (value && value in options) {
|
||||||
return options[value];
|
return options[value];
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
})
|
})
|
||||||
.filter(Boolean);
|
.filter(Boolean) as ColumnMeta[];
|
||||||
}
|
}
|
||||||
|
|
||||||
add(value: string) {
|
add(value: string) {
|
||||||
|
@ -22,7 +22,8 @@ import {
|
|||||||
AnnotationData,
|
AnnotationData,
|
||||||
AdhocMetric,
|
AdhocMetric,
|
||||||
} from '@superset-ui/core';
|
} from '@superset-ui/core';
|
||||||
import { ColumnMeta } from '@superset-ui/chart-controls';
|
import { ColumnMeta, DatasourceMeta } from '@superset-ui/chart-controls';
|
||||||
|
import { DatabaseObject } from 'src/views/CRUD/types';
|
||||||
|
|
||||||
export { Slice, Chart } from 'src/types/Chart';
|
export { Slice, Chart } from 'src/types/Chart';
|
||||||
|
|
||||||
@ -54,3 +55,10 @@ export interface ChartState {
|
|||||||
export type OptionSortType = Partial<
|
export type OptionSortType = Partial<
|
||||||
ColumnMeta & AdhocMetric & { saved_metric_name: string }
|
ColumnMeta & AdhocMetric & { saved_metric_name: string }
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
export type Datasource = DatasourceMeta & {
|
||||||
|
database?: DatabaseObject;
|
||||||
|
datasource?: string;
|
||||||
|
schema?: string;
|
||||||
|
is_sqllab_view?: boolean;
|
||||||
|
};
|
||||||
|
29
superset-frontend/src/types/Database.ts
Normal file
29
superset-frontend/src/types/Database.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* 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 default interface Database {
|
||||||
|
id: number;
|
||||||
|
allow_run_async: boolean;
|
||||||
|
database_name: string;
|
||||||
|
encrypted_extra: string;
|
||||||
|
extra: string;
|
||||||
|
impersonate_user: boolean;
|
||||||
|
server_cert: string;
|
||||||
|
sqlalchemy_uri: string;
|
||||||
|
}
|
@ -17,6 +17,7 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { User } from 'src/types/bootstrapTypes';
|
import { User } from 'src/types/bootstrapTypes';
|
||||||
|
import Database from 'src/types/Database';
|
||||||
import Owner from 'src/types/Owner';
|
import Owner from 'src/types/Owner';
|
||||||
|
|
||||||
export type FavoriteStatus = {
|
export type FavoriteStatus = {
|
||||||
@ -137,12 +138,5 @@ export type ImportResourceName =
|
|||||||
| 'dataset'
|
| 'dataset'
|
||||||
| 'saved_query';
|
| 'saved_query';
|
||||||
|
|
||||||
export type DatabaseObject = {
|
export type DatabaseObject = Partial<Database> &
|
||||||
allow_run_async?: boolean;
|
Pick<Database, 'sqlalchemy_uri'>;
|
||||||
database_name?: string;
|
|
||||||
encrypted_extra?: string;
|
|
||||||
extra?: string;
|
|
||||||
impersonate_user?: boolean;
|
|
||||||
server_cert?: string;
|
|
||||||
sqlalchemy_uri: string;
|
|
||||||
};
|
|
||||||
|
Loading…
Reference in New Issue
Block a user