mirror of
https://github.com/apache/superset.git
synced 2026-05-31 21:29:19 +00:00
feat(custom-tooltip): custom tooltip on deck.gl charts (#34276)
This commit is contained in:
committed by
GitHub
parent
bc6859a99d
commit
a66737cb05
@@ -34,6 +34,9 @@ import {
|
||||
styled,
|
||||
css,
|
||||
DatasourceType,
|
||||
Metric,
|
||||
QueryFormMetric,
|
||||
// useTheme,
|
||||
} from '@superset-ui/core';
|
||||
import { ColumnMeta, isSavedExpression } from '@superset-ui/chart-controls';
|
||||
import Tabs from '@superset-ui/core/components/Tabs';
|
||||
@@ -74,10 +77,24 @@ const StyledSelect = styled(Select)`
|
||||
}
|
||||
`;
|
||||
|
||||
const MetricOptionContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const MetricIcon = styled.span`
|
||||
margin-right: ${({ theme }) => theme.sizeUnit * 2}px;
|
||||
color: ${({ theme }) => theme.colorSuccess};
|
||||
`;
|
||||
|
||||
const MetricLabel = styled.span`
|
||||
color: ${({ theme }) => theme.colorText};
|
||||
`;
|
||||
|
||||
export interface ColumnSelectPopoverProps {
|
||||
columns: ColumnMeta[];
|
||||
editedColumn?: ColumnMeta | AdhocColumn;
|
||||
onChange: (column: ColumnMeta | AdhocColumn) => void;
|
||||
onChange: (column: ColumnMeta | AdhocColumn | Metric) => void;
|
||||
onClose: () => void;
|
||||
hasCustomLabel: boolean;
|
||||
setLabel: (title: string) => void;
|
||||
@@ -86,6 +103,8 @@ export interface ColumnSelectPopoverProps {
|
||||
isTemporal?: boolean;
|
||||
setDatasetModal?: Dispatch<SetStateAction<boolean>>;
|
||||
disabledTabs?: Set<string>;
|
||||
metrics?: Metric[];
|
||||
selectedMetrics?: QueryFormMetric[];
|
||||
datasource?: any;
|
||||
}
|
||||
|
||||
@@ -116,8 +135,11 @@ const ColumnSelectPopover = ({
|
||||
setDatasetModal,
|
||||
setLabel,
|
||||
disabledTabs = new Set<'saved' | 'simple' | 'sqlExpression'>(),
|
||||
metrics = [],
|
||||
selectedMetrics = [],
|
||||
datasource,
|
||||
}: ColumnSelectPopoverProps) => {
|
||||
// const theme = useTheme(); // Unused variable
|
||||
const datasourceType = useSelector<ExplorePageState, string | undefined>(
|
||||
state => state.explore.datasource.type,
|
||||
);
|
||||
@@ -134,6 +156,9 @@ const ColumnSelectPopover = ({
|
||||
const [selectedSimpleColumn, setSelectedSimpleColumn] = useState<
|
||||
ColumnMeta | undefined
|
||||
>(initialSimpleColumn);
|
||||
const [selectedMetric, setSelectedMetric] = useState<Metric | undefined>(
|
||||
undefined,
|
||||
);
|
||||
const [selectedTab, setSelectedTab] = useState<string | null>(null);
|
||||
|
||||
const [resizeButton, width, height] = useResizeButton(
|
||||
@@ -159,11 +184,31 @@ const ColumnSelectPopover = ({
|
||||
[columns],
|
||||
);
|
||||
|
||||
// Filter metrics that are already selected in the chart
|
||||
const availableMetrics = useMemo(() => {
|
||||
if (!metrics?.length) return [];
|
||||
const selectedMetricsSet = new Set(selectedMetrics);
|
||||
return metrics.filter(metric => selectedMetricsSet.has(metric.metric_name));
|
||||
}, [metrics, selectedMetrics]);
|
||||
|
||||
const columnMap = useMemo(
|
||||
() => Object.fromEntries(simpleColumns.map(col => [col.column_name, col])),
|
||||
[simpleColumns],
|
||||
);
|
||||
const metricMap = useMemo(
|
||||
() =>
|
||||
Object.fromEntries(
|
||||
availableMetrics.map(metric => [metric.metric_name, metric]),
|
||||
),
|
||||
[availableMetrics],
|
||||
);
|
||||
|
||||
const onSqlExpressionChange = useCallback(
|
||||
sqlExpression => {
|
||||
setAdhocColumn({ label, sqlExpression, expressionType: 'SQL' });
|
||||
setSelectedSimpleColumn(undefined);
|
||||
setSelectedCalculatedColumn(undefined);
|
||||
setSelectedMetric(undefined);
|
||||
},
|
||||
[label],
|
||||
);
|
||||
@@ -175,6 +220,7 @@ const ColumnSelectPopover = ({
|
||||
);
|
||||
setSelectedCalculatedColumn(selectedColumn);
|
||||
setSelectedSimpleColumn(undefined);
|
||||
setSelectedMetric(undefined);
|
||||
setAdhocColumn(undefined);
|
||||
setLabel(
|
||||
selectedColumn?.verbose_name || selectedColumn?.column_name || '',
|
||||
@@ -190,6 +236,7 @@ const ColumnSelectPopover = ({
|
||||
);
|
||||
setSelectedCalculatedColumn(undefined);
|
||||
setSelectedSimpleColumn(selectedColumn);
|
||||
setSelectedMetric(undefined);
|
||||
setAdhocColumn(undefined);
|
||||
setLabel(
|
||||
selectedColumn?.verbose_name || selectedColumn?.column_name || '',
|
||||
@@ -198,6 +245,38 @@ const ColumnSelectPopover = ({
|
||||
[setLabel, simpleColumns],
|
||||
);
|
||||
|
||||
const onSimpleMetricChange = useCallback(
|
||||
selectedMetricName => {
|
||||
const selectedMetric = availableMetrics.find(
|
||||
metric => metric.metric_name === selectedMetricName,
|
||||
);
|
||||
setSelectedCalculatedColumn(undefined);
|
||||
setSelectedSimpleColumn(undefined);
|
||||
setSelectedMetric(selectedMetric);
|
||||
setAdhocColumn(undefined);
|
||||
setLabel(
|
||||
selectedMetric?.verbose_name || selectedMetric?.metric_name || '',
|
||||
);
|
||||
},
|
||||
[setLabel, availableMetrics],
|
||||
);
|
||||
|
||||
const onSimpleItemChange = useCallback(
|
||||
selectedValue => {
|
||||
const selectedColumn = columnMap[selectedValue];
|
||||
if (selectedColumn) {
|
||||
onSimpleColumnChange(selectedValue);
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedMetric = metricMap[selectedValue];
|
||||
if (selectedMetric) {
|
||||
onSimpleMetricChange(selectedValue);
|
||||
}
|
||||
},
|
||||
[columnMap, metricMap, onSimpleColumnChange, onSimpleMetricChange],
|
||||
);
|
||||
|
||||
const defaultActiveTabKey = initialAdhocColumn
|
||||
? 'sqlExpression'
|
||||
: selectedCalculatedColumn
|
||||
@@ -241,10 +320,11 @@ const ColumnSelectPopover = ({
|
||||
}
|
||||
const selectedColumn =
|
||||
adhocColumn || selectedCalculatedColumn || selectedSimpleColumn;
|
||||
if (!selectedColumn) {
|
||||
const selectedItem = selectedColumn || selectedMetric;
|
||||
if (!selectedItem) {
|
||||
return;
|
||||
}
|
||||
onChange(selectedColumn);
|
||||
onChange(selectedItem);
|
||||
onClose();
|
||||
}, [
|
||||
adhocColumn,
|
||||
@@ -253,11 +333,13 @@ const ColumnSelectPopover = ({
|
||||
onClose,
|
||||
selectedCalculatedColumn,
|
||||
selectedSimpleColumn,
|
||||
selectedMetric,
|
||||
]);
|
||||
|
||||
const onResetStateAndClose = useCallback(() => {
|
||||
setSelectedCalculatedColumn(initialCalculatedColumn);
|
||||
setSelectedSimpleColumn(initialSimpleColumn);
|
||||
setSelectedMetric(undefined);
|
||||
setAdhocColumn(initialAdhocColumn);
|
||||
onClose();
|
||||
}, [
|
||||
@@ -285,16 +367,20 @@ const ColumnSelectPopover = ({
|
||||
};
|
||||
|
||||
const stateIsValid =
|
||||
adhocColumn || selectedCalculatedColumn || selectedSimpleColumn;
|
||||
adhocColumn ||
|
||||
selectedCalculatedColumn ||
|
||||
selectedSimpleColumn ||
|
||||
selectedMetric;
|
||||
const hasUnsavedChanges =
|
||||
initialLabel !== label ||
|
||||
selectedCalculatedColumn?.column_name !==
|
||||
initialCalculatedColumn?.column_name ||
|
||||
selectedSimpleColumn?.column_name !== initialSimpleColumn?.column_name ||
|
||||
selectedMetric?.metric_name !== undefined ||
|
||||
adhocColumn?.sqlExpression !== initialAdhocColumn?.sqlExpression;
|
||||
|
||||
const savedExpressionsLabel = t('Saved expressions');
|
||||
const simpleColumnsLabel = t('Column');
|
||||
const simpleColumnsLabel = t('Columns and metrics');
|
||||
const keywords = useMemo(
|
||||
() => sqlKeywords.concat(getColumnKeywords(columns)),
|
||||
[columns],
|
||||
@@ -313,95 +399,103 @@ const ColumnSelectPopover = ({
|
||||
width: ${width}px;
|
||||
`}
|
||||
items={[
|
||||
{
|
||||
key: TABS_KEYS.SAVED,
|
||||
label: t('Saved'),
|
||||
disabled: disabledTabs.has('saved'),
|
||||
children: (
|
||||
<>
|
||||
{calculatedColumns.length > 0 ? (
|
||||
<FormItem label={savedExpressionsLabel}>
|
||||
<StyledSelect
|
||||
ariaLabel={savedExpressionsLabel}
|
||||
value={selectedCalculatedColumn?.column_name}
|
||||
onChange={onCalculatedColumnChange}
|
||||
allowClear
|
||||
autoFocus={!selectedCalculatedColumn}
|
||||
placeholder={t('%s column(s)', calculatedColumns.length)}
|
||||
options={calculatedColumns.map(calculatedColumn => ({
|
||||
value: calculatedColumn.column_name,
|
||||
label: (
|
||||
<StyledColumnOption
|
||||
column={calculatedColumn}
|
||||
showType
|
||||
// Only show Saved tab if not disabled
|
||||
...(disabledTabs.has('saved')
|
||||
? []
|
||||
: [
|
||||
{
|
||||
key: TABS_KEYS.SAVED,
|
||||
label: t('Saved'),
|
||||
children: (
|
||||
<>
|
||||
{calculatedColumns.length > 0 ? (
|
||||
<FormItem label={savedExpressionsLabel}>
|
||||
<StyledSelect
|
||||
ariaLabel={savedExpressionsLabel}
|
||||
value={selectedCalculatedColumn?.column_name}
|
||||
onChange={onCalculatedColumnChange}
|
||||
allowClear
|
||||
autoFocus={!selectedCalculatedColumn}
|
||||
placeholder={t(
|
||||
'%s column(s)',
|
||||
calculatedColumns.length,
|
||||
)}
|
||||
options={calculatedColumns.map(
|
||||
calculatedColumn => ({
|
||||
value: calculatedColumn.column_name,
|
||||
label: (
|
||||
<StyledColumnOption
|
||||
column={calculatedColumn}
|
||||
showType
|
||||
/>
|
||||
),
|
||||
key: calculatedColumn.column_name,
|
||||
}),
|
||||
)}
|
||||
/>
|
||||
),
|
||||
key: calculatedColumn.column_name,
|
||||
}))}
|
||||
/>
|
||||
</FormItem>
|
||||
) : datasourceType === DatasourceType.Table ? (
|
||||
<EmptyState
|
||||
image="empty.svg"
|
||||
size="small"
|
||||
title={
|
||||
isTemporal
|
||||
? t('No temporal columns found')
|
||||
: t('No saved expressions found')
|
||||
}
|
||||
description={
|
||||
isTemporal
|
||||
? t(
|
||||
'Add calculated temporal columns to dataset in "Edit datasource" modal',
|
||||
)
|
||||
: t(
|
||||
'Add calculated columns to dataset in "Edit datasource" modal',
|
||||
)
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<EmptyState
|
||||
image="empty.svg"
|
||||
size="small"
|
||||
title={
|
||||
isTemporal
|
||||
? t('No temporal columns found')
|
||||
: t('No saved expressions found')
|
||||
}
|
||||
description={
|
||||
isTemporal ? (
|
||||
<>
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={setDatasetAndClose}
|
||||
>
|
||||
{t('Create a dataset')}
|
||||
</span>{' '}
|
||||
{t(' to mark a column as a time column')}
|
||||
</>
|
||||
</FormItem>
|
||||
) : datasourceType === DatasourceType.Table ? (
|
||||
<EmptyState
|
||||
image="empty.svg"
|
||||
size="small"
|
||||
title={
|
||||
isTemporal
|
||||
? t('No temporal columns found')
|
||||
: t('No saved expressions found')
|
||||
}
|
||||
description={
|
||||
isTemporal
|
||||
? t(
|
||||
'Add calculated temporal columns to dataset in "Edit datasource" modal',
|
||||
)
|
||||
: t(
|
||||
'Add calculated columns to dataset in "Edit datasource" modal',
|
||||
)
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={setDatasetAndClose}
|
||||
>
|
||||
{t('Create a dataset')}
|
||||
</span>{' '}
|
||||
{t(' to add calculated columns')}
|
||||
</>
|
||||
)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
},
|
||||
<EmptyState
|
||||
image="empty.svg"
|
||||
size="small"
|
||||
title={
|
||||
isTemporal
|
||||
? t('No temporal columns found')
|
||||
: t('No saved expressions found')
|
||||
}
|
||||
description={
|
||||
isTemporal ? (
|
||||
<>
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={setDatasetAndClose}
|
||||
>
|
||||
{t('Create a dataset')}
|
||||
</span>{' '}
|
||||
{t(' to mark a column as a time column')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={setDatasetAndClose}
|
||||
>
|
||||
{t('Create a dataset')}
|
||||
</span>{' '}
|
||||
{t(' to add calculated columns')}
|
||||
</>
|
||||
)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
},
|
||||
]),
|
||||
{
|
||||
key: TABS_KEYS.SIMPLE,
|
||||
label: t('Simple'),
|
||||
disabled: disabledTabs.has('simple'),
|
||||
children: (
|
||||
<>
|
||||
{isTemporal && simpleColumns.length === 0 ? (
|
||||
@@ -432,18 +526,41 @@ const ColumnSelectPopover = ({
|
||||
<FormItem label={simpleColumnsLabel}>
|
||||
<Select
|
||||
ariaLabel={simpleColumnsLabel}
|
||||
value={selectedSimpleColumn?.column_name}
|
||||
onChange={onSimpleColumnChange}
|
||||
value={
|
||||
selectedSimpleColumn?.column_name ||
|
||||
selectedMetric?.metric_name
|
||||
}
|
||||
onChange={onSimpleItemChange}
|
||||
allowClear
|
||||
autoFocus={!selectedSimpleColumn}
|
||||
placeholder={t('%s column(s)', simpleColumns.length)}
|
||||
options={simpleColumns.map(simpleColumn => ({
|
||||
value: simpleColumn.column_name,
|
||||
label: (
|
||||
<StyledColumnOption column={simpleColumn} showType />
|
||||
),
|
||||
key: simpleColumn.column_name,
|
||||
}))}
|
||||
autoFocus={!selectedSimpleColumn && !selectedMetric}
|
||||
placeholder={t(
|
||||
'%s item(s)',
|
||||
simpleColumns.length + availableMetrics.length,
|
||||
)}
|
||||
options={[
|
||||
...simpleColumns.map(simpleColumn => ({
|
||||
value: simpleColumn.column_name,
|
||||
label: (
|
||||
<StyledColumnOption
|
||||
column={simpleColumn}
|
||||
showType
|
||||
/>
|
||||
),
|
||||
key: `column-${simpleColumn.column_name}`,
|
||||
})),
|
||||
...availableMetrics.map(metric => ({
|
||||
value: metric.metric_name,
|
||||
label: (
|
||||
<MetricOptionContainer>
|
||||
<MetricIcon>ƒ</MetricIcon>
|
||||
<MetricLabel>
|
||||
{metric.verbose_name || metric.metric_name}
|
||||
</MetricLabel>
|
||||
</MetricOptionContainer>
|
||||
),
|
||||
key: `metric-${metric.metric_name}`,
|
||||
})),
|
||||
]}
|
||||
/>
|
||||
</FormItem>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user