You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by di...@apache.org on 2023/01/12 17:49:45 UTC
[superset] 01/01: Initial implementation
This is an automated email from the ASF dual-hosted git repository.
diegopucci pushed a commit to branch feat/cross-filters
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 9edf35f1c72db9a862af5d267ca8362ba5677c12
Author: geido <di...@gmail.com>
AuthorDate: Thu Jan 12 18:49:27 2023 +0100
Initial implementation
---
.../src/chart/models/ChartProps.ts | 7 +++
.../plugin-chart-echarts/src/Pie/EchartsPie.tsx | 4 +-
.../plugin-chart-echarts/src/Pie/controlPanel.tsx | 2 -
.../plugin-chart-echarts/src/Pie/transformProps.ts | 4 +-
.../plugins/plugin-chart-echarts/src/Pie/types.ts | 2 -
.../plugins/plugin-chart-echarts/src/types.ts | 2 +-
superset-frontend/src/components/Chart/Chart.jsx | 2 +
.../src/components/Chart/ChartRenderer.jsx | 5 +-
.../src/components/Checkbox/Checkbox.tsx | 9 ++-
.../components/DropdownSelectableIcon/index.tsx | 65 ++++++++++++++------
.../dashboard/components/gridComponents/Chart.jsx | 1 +
.../FilterBarSettings.test.tsx} | 4 +-
.../index.tsx | 69 +++++++++++++++++-----
.../nativeFilters/FilterBar/Header/index.tsx | 4 +-
.../nativeFilters/FilterBar/Horizontal.tsx | 4 +-
15 files changed, 134 insertions(+), 50 deletions(-)
diff --git a/superset-frontend/packages/superset-ui-core/src/chart/models/ChartProps.ts b/superset-frontend/packages/superset-ui-core/src/chart/models/ChartProps.ts
index 5e4f044942..e02aeca4f5 100644
--- a/superset-frontend/packages/superset-ui-core/src/chart/models/ChartProps.ts
+++ b/superset-frontend/packages/superset-ui-core/src/chart/models/ChartProps.ts
@@ -144,6 +144,8 @@ export default class ChartProps<FormData extends RawFormData = RawFormData> {
inContextMenu?: boolean;
+ emitCrossFilters?: boolean;
+
theme: SupersetTheme;
constructor(config: ChartPropsConfig & { formData?: FormData } = {}) {
@@ -164,6 +166,7 @@ export default class ChartProps<FormData extends RawFormData = RawFormData> {
isRefreshing,
inputRef,
inContextMenu = false,
+ emitCrossFilters = false,
theme,
} = config;
this.width = width;
@@ -184,6 +187,7 @@ export default class ChartProps<FormData extends RawFormData = RawFormData> {
this.isRefreshing = isRefreshing;
this.inputRef = inputRef;
this.inContextMenu = inContextMenu;
+ this.emitCrossFilters = emitCrossFilters;
this.theme = theme;
}
}
@@ -207,6 +211,7 @@ ChartProps.createSelector = function create(): ChartPropsSelector {
input => input.isRefreshing,
input => input.inputRef,
input => input.inContextMenu,
+ input => input.emitCrossFilters,
input => input.theme,
(
annotationData,
@@ -225,6 +230,7 @@ ChartProps.createSelector = function create(): ChartPropsSelector {
isRefreshing,
inputRef,
inContextMenu,
+ emitCrossFilters,
theme,
) =>
new ChartProps({
@@ -244,6 +250,7 @@ ChartProps.createSelector = function create(): ChartPropsSelector {
isRefreshing,
inputRef,
inContextMenu,
+ emitCrossFilters,
theme,
}),
);
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Pie/EchartsPie.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Pie/EchartsPie.tsx
index 6de4c8423d..9fecfeac0e 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Pie/EchartsPie.tsx
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Pie/EchartsPie.tsx
@@ -30,12 +30,12 @@ export default function EchartsPie(props: PieChartTransformedProps) {
labelMap,
groupby,
selectedValues,
- formData,
refs,
+ emitCrossFilters,
} = props;
const handleChange = useCallback(
(values: string[]) => {
- if (!formData.emitFilter) {
+ if (!emitCrossFilters) {
return;
}
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Pie/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Pie/controlPanel.tsx
index c9f0f79d19..1a2d230b74 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Pie/controlPanel.tsx
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Pie/controlPanel.tsx
@@ -25,7 +25,6 @@ import {
D3_FORMAT_OPTIONS,
D3_TIME_FORMAT_OPTIONS,
sections,
- emitFilterControl,
getStandardizedControls,
} from '@superset-ui/chart-controls';
import { DEFAULT_FORM_DATA } from './types';
@@ -52,7 +51,6 @@ const config: ControlPanelConfig = {
['groupby'],
['metric'],
['adhoc_filters'],
- emitFilterControl,
['row_limit'],
[
{
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Pie/transformProps.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Pie/transformProps.ts
index 4261117082..4db36e864e 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Pie/transformProps.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Pie/transformProps.ts
@@ -144,6 +144,7 @@ export default function transformProps(
width,
theme,
inContextMenu,
+ emitCrossFilters,
} = chartProps;
const { data = [] } = queriesData[0];
const coltypeMapping = getColtypesMapping(queriesData[0]);
@@ -166,7 +167,6 @@ export default function transformProps(
showLabels,
showLegend,
showLabelsThreshold,
- emitFilter,
sliceId,
showTotal,
}: EchartsPieFormData = {
@@ -339,11 +339,11 @@ export default function transformProps(
height,
echartOptions,
setDataMask,
- emitFilter,
labelMap,
groupby,
selectedValues,
onContextMenu,
refs,
+ emitCrossFilters,
};
}
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Pie/types.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Pie/types.ts
index 4b45751a23..d4acbb9517 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Pie/types.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Pie/types.ts
@@ -45,7 +45,6 @@ export type EchartsPieFormData = QueryFormData &
numberFormat: string;
dateFormat: string;
showLabelsThreshold: number;
- emitFilter: boolean;
};
export enum EchartsPieLabelType {
@@ -77,7 +76,6 @@ export const DEFAULT_FORM_DATA: EchartsPieFormData = {
showLabels: true,
labelsOutside: true,
showLabelsThreshold: 5,
- emitFilter: false,
dateFormat: 'smart_date',
};
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/types.ts b/superset-frontend/plugins/plugin-chart-echarts/src/types.ts
index 57ed645839..faffb7a565 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/types.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/types.ts
@@ -129,12 +129,12 @@ export interface BaseTransformedProps<F> {
}
export type CrossFilterTransformedProps = {
- emitFilter: boolean;
groupby: QueryFormColumn[];
labelMap: Record<string, string[]>;
setControlValue?: HandlerFunction;
setDataMask: SetDataMaskHook;
selectedValues: Record<number, string>;
+ emitCrossFilters: boolean;
};
export type ContextMenuTransformedProps = {
diff --git a/superset-frontend/src/components/Chart/Chart.jsx b/superset-frontend/src/components/Chart/Chart.jsx
index 8be3699450..b269187da2 100644
--- a/superset-frontend/src/components/Chart/Chart.jsx
+++ b/superset-frontend/src/components/Chart/Chart.jsx
@@ -77,6 +77,8 @@ const propTypes = {
postTransformProps: PropTypes.func,
datasetsStatus: PropTypes.oneOf(['loading', 'error', 'complete']),
isInView: PropTypes.bool,
+ // cross-filters
+ emitCrossFilters: PropTypes.bool,
};
const BLANK = {};
diff --git a/superset-frontend/src/components/Chart/ChartRenderer.jsx b/superset-frontend/src/components/Chart/ChartRenderer.jsx
index e1d3f7290a..e719a30c07 100644
--- a/superset-frontend/src/components/Chart/ChartRenderer.jsx
+++ b/superset-frontend/src/components/Chart/ChartRenderer.jsx
@@ -62,6 +62,8 @@ const propTypes = {
ownState: PropTypes.object,
postTransformProps: PropTypes.func,
source: PropTypes.oneOf([ChartSource.Dashboard, ChartSource.Explore]),
+ // cross filters
+ emitCrossFilters: PropTypes.bool,
};
const BLANK = {};
@@ -223,7 +225,7 @@ class ChartRenderer extends React.Component {
}
render() {
- const { chartAlert, chartStatus, chartId } = this.props;
+ const { chartAlert, chartStatus, chartId, emitCrossFilters } = this.props;
// Skip chart rendering
if (chartStatus === 'loading' || !!chartAlert || chartStatus === null) {
@@ -341,6 +343,7 @@ class ChartRenderer extends React.Component {
onRenderFailure={this.handleRenderFailure}
noResults={noResultsComponent}
postTransformProps={postTransformProps}
+ emitCrossFilters={emitCrossFilters}
{...drillToDetailProps}
/>
</div>
diff --git a/superset-frontend/src/components/Checkbox/Checkbox.tsx b/superset-frontend/src/components/Checkbox/Checkbox.tsx
index 7162929a96..249fca0184 100644
--- a/superset-frontend/src/components/Checkbox/Checkbox.tsx
+++ b/superset-frontend/src/components/Checkbox/Checkbox.tsx
@@ -24,6 +24,7 @@ export interface CheckboxProps {
checked: boolean;
onChange: (val?: boolean) => void;
style?: React.CSSProperties;
+ className?: string;
}
const Styles = styled.span`
@@ -33,7 +34,12 @@ const Styles = styled.span`
}
`;
-export default function Checkbox({ checked, onChange, style }: CheckboxProps) {
+export default function Checkbox({
+ checked,
+ onChange,
+ style,
+ className,
+}: CheckboxProps) {
return (
<Styles
style={style}
@@ -44,6 +50,7 @@ export default function Checkbox({ checked, onChange, style }: CheckboxProps) {
tabIndex={0}
aria-checked={checked}
aria-label="Checkbox"
+ className={className || ''}
>
{checked ? <CheckboxChecked /> : <CheckboxUnchecked />}
</Styles>
diff --git a/superset-frontend/src/components/DropdownSelectableIcon/index.tsx b/superset-frontend/src/components/DropdownSelectableIcon/index.tsx
index b6c5d89a2e..8d4926995e 100644
--- a/superset-frontend/src/components/DropdownSelectableIcon/index.tsx
+++ b/superset-frontend/src/components/DropdownSelectableIcon/index.tsx
@@ -22,12 +22,21 @@ import Icons from 'src/components/Icons';
import { DropdownButton } from 'src/components/DropdownButton';
import { DropdownButtonProps } from 'antd/lib/dropdown';
import { Menu, MenuProps } from 'src/components/Menu';
+import { css, Global } from '@emotion/react';
+
+const { SubMenu } = Menu;
+
+type SubMenuItemProps = { key: string; label: React.ReactNode };
export interface DropDownSelectableProps extends Pick<MenuProps, 'onSelect'> {
ref?: RefObject<HTMLDivElement>;
icon: React.ReactNode;
info?: string;
- menuItems: { key: string; label: React.ReactNode }[];
+ menuItems: {
+ key: string;
+ label: React.ReactNode;
+ children?: SubMenuItemProps[];
+ }[];
selectedKeys?: string[];
}
@@ -67,17 +76,23 @@ const StyledMenu = styled(Menu)`
color: ${theme.colors.grayscale.dark1};
background-color: ${theme.colors.primary.light5};
}
- .ant-dropdown-menu-item > span.anticon {
- float: right;
- margin-right: 0;
- font-size: ${theme.typography.sizes.xl}px;
- }
`}
`;
export default (props: DropDownSelectableProps) => {
const theme = useTheme();
const { icon, info, menuItems, selectedKeys, onSelect } = props;
+ const menuItem = (label: string | React.ReactNode, key: string) => (
+ <Menu.Item key={key}>
+ {label}
+ {selectedKeys?.includes(key) && (
+ <Icons.Check
+ iconColor={theme.colors.primary.base}
+ className="tick-menu-item"
+ />
+ )}
+ </Menu.Item>
+ );
const overlayMenu = useMemo(
() => (
<StyledMenu selectedKeys={selectedKeys} onSelect={onSelect} selectable>
@@ -86,24 +101,36 @@ export default (props: DropDownSelectableProps) => {
{info}
</div>
)}
- {menuItems.map(m => (
- <Menu.Item key={m.key}>
- {m.label}
- {selectedKeys?.includes(m.key) && (
- <Icons.Check iconColor={theme.colors.primary.base} />
- )}
- </Menu.Item>
- ))}
+ {menuItems.map(m =>
+ m.children?.length ? (
+ <SubMenu title={m.label}>
+ {m.children.map(s => menuItem(s.label, s.key))}
+ </SubMenu>
+ ) : (
+ menuItem(m.label, m.key)
+ ),
+ )}
</StyledMenu>
),
[info, menuItems],
);
return (
- <StyledDropdownButton
- overlay={overlayMenu}
- trigger={['click']}
- icon={icon}
- />
+ <>
+ <Global
+ styles={css`
+ .ant-dropdown-menu .ant-dropdown-menu-item > .tick-menu-item {
+ float: right;
+ margin-right: 0;
+ font-size: ${theme.typography.sizes.xl}px;
+ }
+ `}
+ />
+ <StyledDropdownButton
+ overlay={overlayMenu}
+ trigger={['click']}
+ icon={icon}
+ />
+ </>
);
};
diff --git a/superset-frontend/src/dashboard/components/gridComponents/Chart.jsx b/superset-frontend/src/dashboard/components/gridComponents/Chart.jsx
index f5fa7fa4b3..5891b9fa16 100644
--- a/superset-frontend/src/dashboard/components/gridComponents/Chart.jsx
+++ b/superset-frontend/src/dashboard/components/gridComponents/Chart.jsx
@@ -529,6 +529,7 @@ class Chart extends React.Component {
postTransformProps={postTransformProps}
datasetsStatus={datasetsStatus}
isInView={isInView}
+ emitCrossFilters
/>
</div>
</SliceContainer>
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBarOrientationSelect/FilterBarOrientationSelect.test.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBarSettings/FilterBarSettings.test.tsx
similarity index 98%
rename from superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBarOrientationSelect/FilterBarOrientationSelect.test.tsx
rename to superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBarSettings/FilterBarSettings.test.tsx
index 28a40aad05..5f49076385 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBarOrientationSelect/FilterBarOrientationSelect.test.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBarSettings/FilterBarSettings.test.tsx
@@ -24,7 +24,7 @@ import userEvent from '@testing-library/user-event';
import { render, screen, within } from 'spec/helpers/testing-library';
import { DashboardInfo, FilterBarOrientation } from 'src/dashboard/types';
import * as mockedMessageActions from 'src/components/MessageToasts/actions';
-import FilterBarOrientationSelect from '.';
+import FilterBarSettings from '.';
const initialState: { dashboardInfo: DashboardInfo } = {
dashboardInfo: {
@@ -51,7 +51,7 @@ const initialState: { dashboardInfo: DashboardInfo } = {
};
const setup = (dashboardInfoOverride: Partial<DashboardInfo> = {}) =>
- render(<FilterBarOrientationSelect />, {
+ render(<FilterBarSettings />, {
useRedux: true,
initialState: {
...initialState,
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBarOrientationSelect/index.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBarSettings/index.tsx
similarity index 57%
rename from superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBarOrientationSelect/index.tsx
rename to superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBarSettings/index.tsx
index 50e57fd86e..2c658442d2 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBarOrientationSelect/index.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBarSettings/index.tsx
@@ -17,16 +17,25 @@
* under the License.
*/
-import React, { useCallback, useState } from 'react';
+import React, { useCallback, useMemo, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
-import { t, useTheme } from '@superset-ui/core';
+import { styled, t, useTheme } from '@superset-ui/core';
import { MenuProps } from 'src/components/Menu';
import { FilterBarOrientation, RootState } from 'src/dashboard/types';
import { saveFilterBarOrientation } from 'src/dashboard/actions/dashboardInfo';
import Icons from 'src/components/Icons';
import DropdownSelectableIcon from 'src/components/DropdownSelectableIcon';
+import Checkbox from 'src/components/Checkbox';
-const FilterBarOrientationSelect = () => {
+type SelectedKey = FilterBarOrientation | string | number;
+
+const StyledMenuLabel = styled.span`
+ .enable-cross-filters {
+ vertical-align: middle;
+ }
+`;
+
+const FilterBarSettings = () => {
const dispatch = useDispatch();
const theme = useTheme();
const filterBarOrientation = useSelector<RootState, FilterBarOrientation>(
@@ -34,17 +43,26 @@ const FilterBarOrientationSelect = () => {
);
const [selectedFilterBarOrientation, setSelectedFilterBarOrientation] =
useState(filterBarOrientation);
-
+ const [crossFiltersEnabled, setCrossFiltersEnabled] =
+ useState<boolean>(false);
+ const crossFiltersMenuKey = 'cross-filters-menu-key';
+ const isOrientation = (o: SelectedKey): o is FilterBarOrientation =>
+ o === FilterBarOrientation.VERTICAL ||
+ o === FilterBarOrientation.HORIZONTAL;
const toggleFilterBarOrientation = useCallback(
async (
selection: Parameters<
Required<Pick<MenuProps, 'onSelect'>>['onSelect']
>[0],
) => {
- const selectedKey = selection.key as FilterBarOrientation;
- if (selectedKey !== filterBarOrientation) {
+ const selectedKey: SelectedKey = selection.key;
+ if (selectedKey === crossFiltersMenuKey) {
+ setCrossFiltersEnabled(!crossFiltersEnabled);
+ return;
+ }
+ if (isOrientation(selectedKey) && selectedKey !== filterBarOrientation) {
// set displayed selection in local state for immediate visual response after clicking
- setSelectedFilterBarOrientation(selectedKey);
+ setSelectedFilterBarOrientation(selectedKey as FilterBarOrientation);
try {
// save selection in Redux and backend
await dispatch(
@@ -56,13 +74,26 @@ const FilterBarOrientationSelect = () => {
}
}
},
- [dispatch, filterBarOrientation],
+ [dispatch, crossFiltersEnabled, filterBarOrientation],
+ );
+
+ const crossFiltersMenuItem = useMemo(
+ () => (
+ <StyledMenuLabel>
+ <Checkbox
+ className="enable-cross-filters"
+ checked={crossFiltersEnabled}
+ onChange={checked => setCrossFiltersEnabled(checked || false)}
+ />{' '}
+ {t('Enable cross-filtering')}
+ </StyledMenuLabel>
+ ),
+ [crossFiltersEnabled],
);
return (
<DropdownSelectableIcon
onSelect={toggleFilterBarOrientation}
- info={t('Orientation of filter bar')}
icon={
<Icons.Gear
name="gear"
@@ -72,12 +103,22 @@ const FilterBarOrientationSelect = () => {
}
menuItems={[
{
- key: FilterBarOrientation.VERTICAL,
- label: t('Vertical (Left)'),
+ key: crossFiltersMenuKey,
+ label: crossFiltersMenuItem,
},
{
- key: FilterBarOrientation.HORIZONTAL,
- label: t('Horizontal (Top)'),
+ key: 'placement',
+ label: t('Placement of the filter bar'),
+ children: [
+ {
+ key: FilterBarOrientation.VERTICAL,
+ label: t('Vertical (Left)'),
+ },
+ {
+ key: FilterBarOrientation.HORIZONTAL,
+ label: t('Horizontal (Top)'),
+ },
+ ],
},
]}
selectedKeys={[selectedFilterBarOrientation]}
@@ -85,4 +126,4 @@ const FilterBarOrientationSelect = () => {
);
};
-export default FilterBarOrientationSelect;
+export default FilterBarSettings;
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Header/index.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Header/index.tsx
index 023d86c9ae..4cc45d6d80 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Header/index.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Header/index.tsx
@@ -33,7 +33,7 @@ import FilterConfigurationLink from 'src/dashboard/components/nativeFilters/Filt
import { useFilters } from 'src/dashboard/components/nativeFilters/FilterBar/state';
import { RootState } from 'src/dashboard/types';
import { getFilterBarTestId } from '../utils';
-import FilterBarOrientationSelect from '../FilterBarOrientationSelect';
+import FilterBarSettings from '../FilterBarSettings';
const TitleArea = styled.div`
${({ theme }) => css`
@@ -116,7 +116,7 @@ const Header: FC<HeaderProps> = ({ toggleFiltersBar }) => {
<Wrapper>
<TitleArea>
<span>{t('Filters')}</span>
- {canSetHorizontalFilterBar && <FilterBarOrientationSelect />}
+ {canSetHorizontalFilterBar && <FilterBarSettings />}
<HeaderButton
{...getFilterBarTestId('collapse-button')}
buttonStyle="link"
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Horizontal.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Horizontal.tsx
index 3bb762054b..16d5b2a836 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Horizontal.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Horizontal.tsx
@@ -24,7 +24,7 @@ import Loading from 'src/components/Loading';
import FilterControls from './FilterControls/FilterControls';
import { getFilterBarTestId } from './utils';
import { HorizontalBarProps } from './types';
-import FilterBarOrientationSelect from './FilterBarOrientationSelect';
+import FilterBarSettings from './FilterBarSettings';
import FilterConfigurationLink from './FilterConfigurationLink';
const HorizontalBar = styled.div`
@@ -105,7 +105,7 @@ const HorizontalFilterBar: React.FC<HorizontalBarProps> = ({
<Loading position="inline-centered" />
) : (
<>
- {canEdit && <FilterBarOrientationSelect />}
+ {canEdit && <FilterBarSettings />}
{canEdit && (
<FiltersLinkContainer hasFilters={hasFilters}>
<FilterConfigurationLink