feat(native-filters): Implement filter cards (#18874)

* feat(native-filters): Filter cards first pass

* Add dependencies row and lots of improvements

* Display tooltip correctly

* Styling fix

* Remove accidentaly added changes

* Address comments

* Fix type

* Fixes
This commit is contained in:
Kamil Gabryjelski
2022-02-23 16:59:40 +01:00
committed by GitHub
parent 324601e0bb
commit 0922c3ff2d
15 changed files with 797 additions and 5 deletions

View File

@@ -44,10 +44,6 @@ const Wrapper = styled.div`
padding: ${({ theme }) => theme.gridUnit * 4}px;
// 108px padding to make room for buttons with position: absolute
padding-bottom: ${({ theme }) => theme.gridUnit * 27}px;
&:hover {
cursor: pointer;
}
`;
type FilterControlsProps = {

View File

@@ -0,0 +1,113 @@
/**
* 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, { useCallback, useMemo, useRef } from 'react';
import { useDispatch } from 'react-redux';
import { css, t, useTheme } from '@superset-ui/core';
import { setDirectPathToChild } from 'src/dashboard/actions/dashboardState';
import Icons from 'src/components/Icons';
import {
DependencyItem,
Row,
RowLabel,
RowTruncationCount,
RowValue,
TooltipList,
} from './Styles';
import { useFilterDependencies } from './useFilterDependencies';
import { useTruncation } from './useTruncation';
import { DependencyValueProps, FilterCardRowProps } from './types';
import { TooltipWithTruncation } from './TooltipWithTruncation';
const DependencyValue = ({ dependency, label }: DependencyValueProps) => {
const dispatch = useDispatch();
const handleClick = useCallback(() => {
dispatch(setDirectPathToChild([dependency.id]));
}, [dependency.id, dispatch]);
return (
<DependencyItem role="button" onClick={handleClick} tabIndex={0}>
{label}
</DependencyItem>
);
};
export const DependenciesRow = React.memo(({ filter }: FilterCardRowProps) => {
const dependencies = useFilterDependencies(filter);
const dependenciesRef = useRef<HTMLDivElement>(null);
const [elementsTruncated, hasHiddenElements] = useTruncation(dependenciesRef);
const theme = useTheme();
const tooltipText = useMemo(
() =>
elementsTruncated > 0 && dependencies ? (
<TooltipList>
{dependencies.map(dependency => (
<li>
<DependencyValue
dependency={dependency}
label={dependency.name}
/>
</li>
))}
</TooltipList>
) : null,
[elementsTruncated, dependencies],
);
if (!Array.isArray(dependencies) || dependencies.length === 0) {
return null;
}
return (
<Row>
<RowLabel
css={css`
display: inline-flex;
align-items: center;
`}
>
{t('Dependent on')}{' '}
<TooltipWithTruncation
title={t(
'Filter only displays values relevant to selections made in other filters.',
)}
>
<Icons.Info
iconSize="s"
iconColor={theme.colors.grayscale.light1}
css={css`
margin-left: ${theme.gridUnit}px;
`}
/>
</TooltipWithTruncation>
</RowLabel>
<TooltipWithTruncation title={tooltipText}>
<RowValue ref={dependenciesRef}>
{dependencies.map((dependency, index) => (
<DependencyValue
dependency={dependency}
label={index === 0 ? dependency.name : `, ${dependency.name}`}
/>
))}
</RowValue>
{hasHiddenElements && (
<RowTruncationCount>+{elementsTruncated}</RowTruncationCount>
)}
</TooltipWithTruncation>
</Row>
);
});

View File

@@ -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 React from 'react';
import { Filter } from '@superset-ui/core';
import { ScopeRow } from './ScopeRow';
import { DependenciesRow } from './DependenciesRow';
import { NameRow } from './NameRow';
import { TypeRow } from './TypeRow';
export const FilterCardContent = ({ filter }: { filter: Filter }) => (
<div>
<NameRow filter={filter} />
<TypeRow filter={filter} />
<ScopeRow filter={filter} />
<DependenciesRow filter={filter} />
</div>
);

View File

@@ -0,0 +1,50 @@
/**
* 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, { useRef } from 'react';
import { css, SupersetTheme } from '@superset-ui/core';
import Icons from 'src/components/Icons';
import { Row, FilterName } from './Styles';
import { FilterCardRowProps } from './types';
import { useTruncation } from './useTruncation';
import { TooltipWithTruncation } from './TooltipWithTruncation';
export const NameRow = ({ filter }: FilterCardRowProps) => {
const filterNameRef = useRef<HTMLElement>(null);
const [elementsTruncated] = useTruncation(filterNameRef);
return (
<Row
css={(theme: SupersetTheme) =>
css`
margin-bottom: ${theme.gridUnit * 3}px;
`
}
>
<Icons.FilterSmall
css={(theme: SupersetTheme) =>
css`
margin-right: ${theme.gridUnit}px;
`
}
/>
<TooltipWithTruncation title={elementsTruncated ? filter.name : null}>
<FilterName ref={filterNameRef}>{filter.name}</FilterName>
</TooltipWithTruncation>
</Row>
);
};

View File

@@ -0,0 +1,68 @@
/**
* 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, { useMemo, useRef } from 'react';
import { t } from '@superset-ui/core';
import { useFilterScope } from './useFilterScope';
import {
Row,
RowLabel,
RowTruncationCount,
RowValue,
TooltipList,
} from './Styles';
import { useTruncation } from './useTruncation';
import { FilterCardRowProps } from './types';
import { TooltipWithTruncation } from './TooltipWithTruncation';
export const ScopeRow = React.memo(({ filter }: FilterCardRowProps) => {
const scope = useFilterScope(filter);
const scopeRef = useRef<HTMLDivElement>(null);
const [elementsTruncated, hasHiddenElements] = useTruncation(scopeRef);
const tooltipText = useMemo(
() =>
elementsTruncated > 0 && scope ? (
<TooltipList>
{scope.map(val => (
<li>{val}</li>
))}
</TooltipList>
) : null,
[elementsTruncated, scope],
);
if (!Array.isArray(scope) || scope.length === 0) {
return null;
}
return (
<Row>
<RowLabel>{t('Scope')}</RowLabel>
<TooltipWithTruncation title={tooltipText}>
<RowValue ref={scopeRef}>
{scope.map((element, index) => (
<span>{index === 0 ? element : `, ${element}`}</span>
))}
</RowValue>
{hasHiddenElements > 0 && (
<RowTruncationCount>+{elementsTruncated}</RowTruncationCount>
)}
</TooltipWithTruncation>
</Row>
);
});

View File

@@ -0,0 +1,90 @@
/**
* 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 { css, styled } from '@superset-ui/core';
export const Row = styled.div`
${({ theme }) => css`
display: flex;
align-items: center;
margin: ${theme.gridUnit}px 0;
font-size: ${theme.typography.sizes.s}px;
&:first-of-type {
margin-top: 0;
}
&:last-of-type {
margin-bottom: 0;
}
& .ant-tooltip-open {
display: inline-flex;
}
`};
`;
export const RowLabel = styled.span`
${({ theme }) => css`
color: ${theme.colors.grayscale.base};
padding-right: ${theme.gridUnit * 4}px;
margin-right: auto;
text-transform: uppercase;
white-space: nowrap;
`};
`;
export const RowValue = styled.div`
${({ theme }) => css`
color: ${theme.colors.grayscale.dark1};
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: inline;
`};
`;
export const FilterName = styled.span`
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`;
export const DependencyItem = styled.span`
text-decoration: underline;
cursor: pointer;
`;
export const RowTruncationCount = styled.span`
${({ theme }) => css`
color: ${theme.colors.primary.base};
`}
`;
export const TooltipList = styled.ul`
${({ theme }) => css`
padding-left: ${theme.gridUnit * 3}px;
margin-bottom: 0;
`};
`;
export const TooltipTrigger = styled.div`
min-width: 0;
display: inline-flex;
white-space: nowrap;
`;

View File

@@ -0,0 +1,37 @@
/**
* 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 from 'react';
import { TooltipProps } from 'antd/lib/tooltip';
import { Tooltip } from 'src/components/Tooltip';
import { TooltipTrigger } from './Styles';
export const TooltipWithTruncation = ({
title,
children,
...props
}: TooltipProps) => (
<Tooltip
title={title}
placement="bottom"
overlayClassName="filter-card-tooltip"
{...props}
>
<TooltipTrigger>{children}</TooltipTrigger>
</Tooltip>
);

View File

@@ -0,0 +1,35 @@
/**
* 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, { useMemo } from 'react';
import { getChartMetadataRegistry, t } from '@superset-ui/core';
import { Row, RowLabel, RowValue } from './Styles';
import { FilterCardRowProps } from './types';
export const TypeRow = ({ filter }: FilterCardRowProps) => {
const metadata = useMemo(
() => getChartMetadataRegistry().get(filter.filterType),
[filter.filterType],
);
return (
<Row>
<RowLabel>{t('Filter type')}</RowLabel>
<RowValue>{metadata?.name}</RowValue>
</Row>
);
};

View File

@@ -0,0 +1,50 @@
/**
* 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 Popover from 'src/components/Popover';
import { FilterCardContent } from './FilterCardContent';
import { FilterCardProps } from './types';
export const FilterCard = ({
children,
filter,
getPopupContainer,
isVisible: externalIsVisible = true,
}: FilterCardProps) => {
const [internalIsVisible, setInternalIsVisible] = useState(false);
return (
<Popover
placement="right"
overlayClassName="filter-card-popover"
mouseEnterDelay={0.2}
mouseLeaveDelay={0.2}
onVisibleChange={visible => {
setInternalIsVisible(visible);
}}
visible={externalIsVisible && internalIsVisible}
content={<FilterCardContent filter={filter} />}
getPopupContainer={getPopupContainer ?? (() => document.body)}
destroyTooltipOnHide
>
{children}
</Popover>
);
};

View File

@@ -0,0 +1,36 @@
/**
* 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 { ReactNode } from 'react';
import { Filter } from '@superset-ui/core';
export interface FilterCardProps {
children: ReactNode;
filter: Filter;
getPopupContainer?: (node: HTMLElement) => HTMLElement;
isVisible?: boolean;
}
export interface FilterCardRowProps {
filter: Filter;
}
export interface DependencyValueProps {
dependency: Filter;
label: string;
}

View File

@@ -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 { ensureIsArray, Filter } from '@superset-ui/core';
import { useSelector } from 'react-redux';
import { RootState } from 'src/dashboard/types';
export const useFilterDependencies = (filter: Filter) => {
const filterDependencyIds = ensureIsArray(filter.cascadeParentIds);
return useSelector<RootState, Filter[]>(state => {
if (filterDependencyIds.length === 0) {
return [];
}
return filterDependencyIds.reduce((acc: Filter[], filterDependencyId) => {
acc.push(state.nativeFilters.filters[filterDependencyId]);
return acc;
}, []);
});
};

View File

@@ -0,0 +1,143 @@
/**
* 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 { Filter, t } from '@superset-ui/core';
import { useSelector } from 'react-redux';
import {
ChartsState,
Layout,
LayoutItem,
RootState,
} from 'src/dashboard/types';
import { DASHBOARD_ROOT_ID } from 'src/dashboard/util/constants';
import { CHART_TYPE } from 'src/dashboard/util/componentTypes';
import { useMemo } from 'react';
const extractTabLabel = (tab?: LayoutItem) =>
tab?.meta?.text || tab?.meta?.defaultText;
const extractChartLabel = (chart?: LayoutItem) =>
chart?.meta?.sliceNameOverride || chart?.meta?.sliceName || chart?.id;
const useCharts = () => {
const charts = useSelector<RootState, ChartsState>(state => state.charts);
return useMemo(() => Object.values(charts), [charts]);
};
export const useFilterScope = (filter: Filter) => {
const layout = useSelector<RootState, Layout>(
state => state.dashboardLayout.present,
);
const charts = useCharts();
return useMemo(() => {
let topLevelTabs: string[] | undefined;
const topElementId = layout[DASHBOARD_ROOT_ID].children[0];
if (topElementId.startsWith('TABS-')) {
topLevelTabs = layout[topElementId].children;
}
// no charts in scope
if (filter.scope.rootPath.length === 0) {
return undefined;
}
// all charts in scope
// no charts excluded and no top level tabs
// OR no charts excluded and every top level tab is in rootPath
if (
filter.scope.excluded.length === 0 &&
(filter.scope.rootPath[0] === DASHBOARD_ROOT_ID ||
(topLevelTabs &&
topLevelTabs.every(topLevelTab =>
filter.scope.rootPath.includes(topLevelTab),
)))
) {
return [t('All charts')];
}
// no charts excluded and not every top level tab in scope
// returns "TAB1, TAB2"
if (filter.scope.excluded.length === 0 && topLevelTabs) {
return filter.scope.rootPath.map(tabId => extractTabLabel(layout[tabId]));
}
const layoutCharts = Object.values(layout).filter(
layoutElement => layoutElement.type === CHART_TYPE,
);
// no top level tabs, charts excluded
// returns "CHART1, CHART2"
if (filter.scope.rootPath[0] === DASHBOARD_ROOT_ID) {
return charts
.filter(chart => !filter.scope.excluded.includes(chart.id))
.map(chart => {
const layoutElement = layoutCharts.find(
layoutChart => layoutChart.meta.chartId === chart.id,
);
return extractChartLabel(layoutElement);
})
.filter(Boolean);
}
// top level tabs, charts excluded
// returns "TAB1, TAB2, CHART1"
if (topLevelTabs) {
// We start assuming that all charts are in scope for all tabs in the root path
const topLevelTabsInFullScope = [...filter.scope.rootPath];
const layoutChartElementsInTabsInScope = layoutCharts.filter(element =>
element.parents.some(parent =>
topLevelTabsInFullScope.includes(parent),
),
);
// Exclude the tabs that contain excluded charts
filter.scope.excluded.forEach(chartId => {
const excludedIndex = topLevelTabsInFullScope.findIndex(tabId =>
layoutChartElementsInTabsInScope
.find(chart => chart.meta.chartId === chartId)
?.parents.includes(tabId),
);
if (excludedIndex > -1) {
topLevelTabsInFullScope.splice(excludedIndex, 1);
}
});
// Handle charts that are in scope but belong to excluded tabs.
const chartsInExcludedTabs = charts
.filter(chart => !filter.scope.excluded.includes(chart.id))
.reduce((acc: LayoutItem[], chart) => {
const layoutChartElementInExcludedTab =
layoutChartElementsInTabsInScope.find(
element =>
element.meta.chartId === chart.id &&
element.parents.every(
parent => !topLevelTabsInFullScope.includes(parent),
),
);
if (layoutChartElementInExcludedTab) {
acc.push(layoutChartElementInExcludedTab);
}
return acc;
}, []);
// Join tab names and chart names
return topLevelTabsInFullScope
.map(tabId => extractTabLabel(layout[tabId]))
.concat(chartsInExcludedTabs.map(extractChartLabel));
}
return undefined;
}, [charts, filter.scope.excluded, filter.scope.rootPath, layout]);
};

View File

@@ -0,0 +1,58 @@
/**
* 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 { RefObject, useLayoutEffect, useState } from 'react';
export const useTruncation = (elementRef: RefObject<HTMLElement>) => {
const [elementsTruncated, setElementsTruncated] = useState(0);
const [hasHiddenElements, setHasHiddenElements] = useState(false);
useLayoutEffect(() => {
const currentElement = elementRef.current;
if (!currentElement) {
return;
}
const { scrollWidth, clientWidth, childNodes } = currentElement;
if (scrollWidth > clientWidth) {
// "..." is around 6px wide
const maxWidth = clientWidth - 6;
const elementsCount = childNodes.length;
let width = 0;
let i = 0;
while (width < maxWidth) {
width += (childNodes[i] as HTMLElement).offsetWidth;
i += 1;
}
if (i === elementsCount) {
setElementsTruncated(1);
setHasHiddenElements(false);
} else {
setElementsTruncated(elementsCount - i);
setHasHiddenElements(true);
}
} else {
setElementsTruncated(0);
}
}, [
elementRef.current?.offsetWidth,
elementRef.current?.clientWidth,
elementRef,
]);
return [elementsTruncated, hasHiddenElements];
};

View File

@@ -17,8 +17,9 @@
* under the License.
*/
import React, { FC, useRef, useEffect, useState } from 'react';
import { FeatureFlag, isFeatureEnabled, t } from '@superset-ui/core';
import { FeatureFlag, isFeatureEnabled, t, useTheme } from '@superset-ui/core';
import { useDispatch, useSelector } from 'react-redux';
import { Global } from '@emotion/react';
import { useParams } from 'react-router-dom';
import { useToasts } from 'src/components/MessageToasts/withToasts';
import Loading from 'src/components/Loading';
@@ -49,6 +50,7 @@ import { getUrlParam } from 'src/utils/urlUtils';
import { canUserEditDashboard } from 'src/dashboard/util/findPermission';
import { getFilterSets } from '../actions/nativeFilters';
import { getFilterValue } from '../components/nativeFilters/FilterBar/keyValue';
import { filterCardPopoverStyle } from '../styles';
export const MigrationContext = React.createContext(
FILTER_BOX_MIGRATION_STATES.NOOP,
@@ -68,6 +70,7 @@ const originalDocumentTitle = document.title;
const DashboardPage: FC = () => {
const dispatch = useDispatch();
const theme = useTheme();
const user = useSelector<any, UserWithPermissionsAndRoles>(
state => state.user,
);
@@ -226,6 +229,7 @@ const DashboardPage: FC = () => {
return (
<>
<Global styles={filterCardPopoverStyle(theme)} />
<FilterBoxMigrationModal
show={filterboxMigrationState === FILTER_BOX_MIGRATION_STATES.UNDECIDED}
hideFooter={!isMigrationEnabled}

View File

@@ -0,0 +1,44 @@
/**
* 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 { css, SupersetTheme } from '@superset-ui/core';
export const filterCardPopoverStyle = (theme: SupersetTheme) => css`
.filter-card-popover {
width: 240px;
padding: 0;
border-radius: 4px;
.ant-popover-inner-content {
padding: ${theme.gridUnit * 4}px;
}
.ant-popover-arrow {
display: none;
}
}
.filter-card-tooltip {
&.ant-tooltip-placement-bottom {
padding-top: 0;
& .ant-tooltip-arrow {
top: -13px;
}
}
}
`;