mirror of
https://github.com/apache/superset.git
synced 2026-07-13 18:25:58 +00:00
Compare commits
1 Commits
upgrade-sq
...
elizabeth/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eeb9edd7e2 |
@@ -21,6 +21,7 @@ import {
|
|||||||
ReactNode,
|
ReactNode,
|
||||||
useContext,
|
useContext,
|
||||||
useEffect,
|
useEffect,
|
||||||
|
useMemo,
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
@@ -40,6 +41,8 @@ import { getSliceHeaderTooltip } from 'src/dashboard/util/getSliceHeaderTooltip'
|
|||||||
import { DashboardPageIdContext } from 'src/dashboard/containers/DashboardPage';
|
import { DashboardPageIdContext } from 'src/dashboard/containers/DashboardPage';
|
||||||
import RowCountLabel from 'src/components/RowCountLabel';
|
import RowCountLabel from 'src/components/RowCountLabel';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
import { useAppliedFilterIndicators } from 'src/dashboard/hooks/useAppliedFilterIndicators';
|
||||||
|
import { getFilterValueForDisplay } from 'src/dashboard/components/nativeFilters/utils';
|
||||||
|
|
||||||
const extensionsRegistry = getExtensionsRegistry();
|
const extensionsRegistry = getExtensionsRegistry();
|
||||||
|
|
||||||
@@ -197,20 +200,81 @@ const SliceHeader = forwardRef<HTMLDivElement, SliceHeaderProps>(
|
|||||||
|
|
||||||
const canExplore = !editMode && supersetCanExplore;
|
const canExplore = !editMode && supersetCanExplore;
|
||||||
|
|
||||||
|
// Get applied filter indicators for this chart
|
||||||
|
const { appliedIndicators, appliedCrossFilterIndicators, filterCount } =
|
||||||
|
useAppliedFilterIndicators(slice.slice_id);
|
||||||
|
|
||||||
|
// Build the filter list for the tooltip
|
||||||
|
const filterListContent = useMemo(() => {
|
||||||
|
const allFilters = [
|
||||||
|
...appliedCrossFilterIndicators,
|
||||||
|
...appliedIndicators,
|
||||||
|
];
|
||||||
|
if (allFilters.length === 0) return null;
|
||||||
|
|
||||||
|
return allFilters.map(indicator => {
|
||||||
|
const filterValue = getFilterValueForDisplay(indicator.value);
|
||||||
|
const columnLabel = indicator.customColumnLabel || indicator.column;
|
||||||
|
return `• ${indicator.name}${columnLabel ? ` (${columnLabel})` : ''}${filterValue ? `: ${filterValue}` : ''}`;
|
||||||
|
});
|
||||||
|
}, [appliedIndicators, appliedCrossFilterIndicators]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const headerElement = headerRef.current;
|
const headerElement = headerRef.current;
|
||||||
if (canExplore) {
|
const isTruncated =
|
||||||
setHeaderTooltip(getSliceHeaderTooltip(sliceName));
|
|
||||||
} else if (
|
|
||||||
headerElement &&
|
headerElement &&
|
||||||
(headerElement.scrollWidth > headerElement.offsetWidth ||
|
(headerElement.scrollWidth > headerElement.offsetWidth ||
|
||||||
headerElement.scrollHeight > headerElement.offsetHeight)
|
headerElement.scrollHeight > headerElement.offsetHeight);
|
||||||
) {
|
|
||||||
setHeaderTooltip(sliceName ?? null);
|
// Build the tooltip content
|
||||||
} else {
|
let tooltipContent: ReactNode = null;
|
||||||
setHeaderTooltip(null);
|
|
||||||
|
if (canExplore) {
|
||||||
|
tooltipContent = getSliceHeaderTooltip(sliceName);
|
||||||
|
} else if (isTruncated) {
|
||||||
|
tooltipContent = sliceName ?? null;
|
||||||
}
|
}
|
||||||
}, [sliceName, width, height, canExplore]);
|
|
||||||
|
// Add filter information to tooltip when title is truncated and filters are applied
|
||||||
|
if (isTruncated && filterCount > 0 && filterListContent) {
|
||||||
|
const filterInfo = (
|
||||||
|
<div>
|
||||||
|
{tooltipContent && <div>{tooltipContent}</div>}
|
||||||
|
<div
|
||||||
|
css={css`
|
||||||
|
margin-top: ${tooltipContent ? '8px' : '0'};
|
||||||
|
border-top: ${tooltipContent
|
||||||
|
? '1px solid rgba(255,255,255,0.2)'
|
||||||
|
: 'none'};
|
||||||
|
padding-top: ${tooltipContent ? '8px' : '0'};
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
css={css`
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{t('%s filter(s) applied to this chart', filterCount)}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
css={css`
|
||||||
|
font-size: 12px;
|
||||||
|
opacity: 0.9;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{filterListContent.map((filter, index) => (
|
||||||
|
<div key={index}>{filter}</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
setHeaderTooltip(filterInfo);
|
||||||
|
} else {
|
||||||
|
setHeaderTooltip(tooltipContent);
|
||||||
|
}
|
||||||
|
}, [sliceName, width, height, canExplore, filterCount, filterListContent]);
|
||||||
|
|
||||||
const exploreUrl = `/explore/?dashboard_page_id=${dashboardPageId}&slice_id=${slice.slice_id}`;
|
const exploreUrl = `/explore/?dashboard_page_id=${dashboardPageId}&slice_id=${slice.slice_id}`;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,220 @@
|
|||||||
|
/**
|
||||||
|
* 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 { useEffect, useMemo, useState } from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { uniqWith } from 'lodash';
|
||||||
|
import {
|
||||||
|
DataMaskStateWithId,
|
||||||
|
Filters,
|
||||||
|
JsonObject,
|
||||||
|
usePrevious,
|
||||||
|
} from '@superset-ui/core';
|
||||||
|
import { useChartLayoutItems } from 'src/dashboard/util/useChartLayoutItems';
|
||||||
|
import {
|
||||||
|
Indicator,
|
||||||
|
IndicatorStatus,
|
||||||
|
selectIndicatorsForChart,
|
||||||
|
selectNativeIndicatorsForChart,
|
||||||
|
} from 'src/dashboard/components/nativeFilters/selectors';
|
||||||
|
import { Chart, RootState } from 'src/dashboard/types';
|
||||||
|
|
||||||
|
const sortByStatus = (indicators: Indicator[]): Indicator[] => {
|
||||||
|
const statuses = [
|
||||||
|
IndicatorStatus.Applied,
|
||||||
|
IndicatorStatus.Unset,
|
||||||
|
IndicatorStatus.Incompatible,
|
||||||
|
];
|
||||||
|
return indicators.sort(
|
||||||
|
(a, b) =>
|
||||||
|
statuses.indexOf(a.status as IndicatorStatus) -
|
||||||
|
statuses.indexOf(b.status as IndicatorStatus),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const indicatorsInitialState: Indicator[] = [];
|
||||||
|
|
||||||
|
export interface AppliedFilterIndicators {
|
||||||
|
appliedIndicators: Indicator[];
|
||||||
|
appliedCrossFilterIndicators: Indicator[];
|
||||||
|
filterCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook to get applied filter indicators for a specific chart.
|
||||||
|
* Extracts the filter indicator logic from FiltersBadge for reuse.
|
||||||
|
*/
|
||||||
|
export const useAppliedFilterIndicators = (
|
||||||
|
chartId: number,
|
||||||
|
): AppliedFilterIndicators => {
|
||||||
|
// Using 'any' type for these selectors to match FiltersBadge implementation
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const datasources = useSelector<RootState, any>(state => state.datasources);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const dashboardFilters = useSelector<RootState, any>(
|
||||||
|
state => state.dashboardFilters,
|
||||||
|
);
|
||||||
|
const nativeFilters = useSelector<RootState, Filters>(
|
||||||
|
state => state.nativeFilters?.filters,
|
||||||
|
);
|
||||||
|
const chartConfiguration = useSelector<RootState, JsonObject>(
|
||||||
|
state => state.dashboardInfo.metadata?.chart_configuration,
|
||||||
|
);
|
||||||
|
const chart = useSelector<RootState, Chart>(state => state.charts[chartId]);
|
||||||
|
const chartLayoutItems = useChartLayoutItems();
|
||||||
|
const dataMask = useSelector<RootState, DataMaskStateWithId>(
|
||||||
|
state => state.dataMask,
|
||||||
|
);
|
||||||
|
|
||||||
|
const [nativeIndicators, setNativeIndicators] = useState<Indicator[]>(
|
||||||
|
indicatorsInitialState,
|
||||||
|
);
|
||||||
|
const [dashboardIndicators, setDashboardIndicators] = useState<Indicator[]>(
|
||||||
|
indicatorsInitialState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const prevChart = usePrevious(chart);
|
||||||
|
const prevChartStatus = prevChart?.chartStatus;
|
||||||
|
const prevDashboardFilters = usePrevious(dashboardFilters);
|
||||||
|
const prevDatasources = usePrevious(datasources);
|
||||||
|
const showIndicators =
|
||||||
|
chart?.chartStatus && ['rendered', 'success'].includes(chart.chartStatus);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!showIndicators && dashboardIndicators.length > 0) {
|
||||||
|
setDashboardIndicators(indicatorsInitialState);
|
||||||
|
} else if (prevChartStatus !== 'success') {
|
||||||
|
if (
|
||||||
|
chart?.queriesResponse?.[0]?.rejected_filters !==
|
||||||
|
prevChart?.queriesResponse?.[0]?.rejected_filters ||
|
||||||
|
chart?.queriesResponse?.[0]?.applied_filters !==
|
||||||
|
prevChart?.queriesResponse?.[0]?.applied_filters ||
|
||||||
|
dashboardFilters !== prevDashboardFilters ||
|
||||||
|
datasources !== prevDatasources
|
||||||
|
) {
|
||||||
|
setDashboardIndicators(
|
||||||
|
selectIndicatorsForChart(
|
||||||
|
chartId,
|
||||||
|
dashboardFilters,
|
||||||
|
datasources,
|
||||||
|
chart,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
chart,
|
||||||
|
chartId,
|
||||||
|
dashboardFilters,
|
||||||
|
dashboardIndicators.length,
|
||||||
|
datasources,
|
||||||
|
prevChart?.queriesResponse,
|
||||||
|
prevChartStatus,
|
||||||
|
prevDashboardFilters,
|
||||||
|
prevDatasources,
|
||||||
|
showIndicators,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const prevNativeFilters = usePrevious(nativeFilters);
|
||||||
|
const prevChartLayoutItems = usePrevious(chartLayoutItems);
|
||||||
|
const prevDataMask = usePrevious(dataMask);
|
||||||
|
const prevChartConfig = usePrevious(chartConfiguration);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!showIndicators && nativeIndicators.length > 0) {
|
||||||
|
setNativeIndicators(indicatorsInitialState);
|
||||||
|
} else if (prevChartStatus !== 'success') {
|
||||||
|
if (
|
||||||
|
chart?.queriesResponse?.[0]?.rejected_filters !==
|
||||||
|
prevChart?.queriesResponse?.[0]?.rejected_filters ||
|
||||||
|
chart?.queriesResponse?.[0]?.applied_filters !==
|
||||||
|
prevChart?.queriesResponse?.[0]?.applied_filters ||
|
||||||
|
nativeFilters !== prevNativeFilters ||
|
||||||
|
chartLayoutItems !== prevChartLayoutItems ||
|
||||||
|
dataMask !== prevDataMask ||
|
||||||
|
prevChartConfig !== chartConfiguration
|
||||||
|
) {
|
||||||
|
setNativeIndicators(
|
||||||
|
selectNativeIndicatorsForChart(
|
||||||
|
nativeFilters,
|
||||||
|
dataMask,
|
||||||
|
chartId,
|
||||||
|
chart,
|
||||||
|
chartLayoutItems,
|
||||||
|
chartConfiguration,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
chart,
|
||||||
|
chartId,
|
||||||
|
chartConfiguration,
|
||||||
|
dataMask,
|
||||||
|
nativeFilters,
|
||||||
|
nativeIndicators.length,
|
||||||
|
prevChart?.queriesResponse,
|
||||||
|
prevChartConfig,
|
||||||
|
prevChartStatus,
|
||||||
|
prevDataMask,
|
||||||
|
prevNativeFilters,
|
||||||
|
showIndicators,
|
||||||
|
chartLayoutItems,
|
||||||
|
prevChartLayoutItems,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const indicators = useMemo(
|
||||||
|
() =>
|
||||||
|
uniqWith(
|
||||||
|
sortByStatus([...dashboardIndicators, ...nativeIndicators]),
|
||||||
|
(ind1, ind2) =>
|
||||||
|
ind1.column === ind2.column &&
|
||||||
|
ind1.name === ind2.name &&
|
||||||
|
(ind1.status !== IndicatorStatus.Applied ||
|
||||||
|
ind2.status !== IndicatorStatus.Applied),
|
||||||
|
),
|
||||||
|
[dashboardIndicators, nativeIndicators],
|
||||||
|
);
|
||||||
|
|
||||||
|
const appliedCrossFilterIndicators = useMemo(
|
||||||
|
() =>
|
||||||
|
indicators.filter(
|
||||||
|
indicator => indicator.status === IndicatorStatus.CrossFilterApplied,
|
||||||
|
),
|
||||||
|
[indicators],
|
||||||
|
);
|
||||||
|
|
||||||
|
const appliedIndicators = useMemo(
|
||||||
|
() =>
|
||||||
|
indicators.filter(
|
||||||
|
indicator => indicator.status === IndicatorStatus.Applied,
|
||||||
|
),
|
||||||
|
[indicators],
|
||||||
|
);
|
||||||
|
|
||||||
|
const filterCount =
|
||||||
|
appliedIndicators.length + appliedCrossFilterIndicators.length;
|
||||||
|
|
||||||
|
return {
|
||||||
|
appliedIndicators,
|
||||||
|
appliedCrossFilterIndicators,
|
||||||
|
filterCount,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useAppliedFilterIndicators;
|
||||||
Reference in New Issue
Block a user