Skip to content

Commit

Permalink
feat(plugin-chart-pivot-table): enable cross filtering (apache#1083)
Browse files Browse the repository at this point in the history
* feat(plugin-chart-pivot-table): enable cross filtering

* Fix test

* Fix tests

* Replace `.toEqual` with `.toMatchObject` to fix test

* Use jest.fn() to mock setDataMask

* Code review changes

* Bring back setting ownState
  • Loading branch information
kgabryje authored and zhaoyongjie committed Nov 24, 2021
1 parent 48f0516 commit 1e48622
Show file tree
Hide file tree
Showing 7 changed files with 146 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import { styled, AdhocMetric, getNumberFormatter } from '@superset-ui/core';
import React, { useCallback } from 'react';
import { styled, AdhocMetric, getNumberFormatter, DataRecordValue } from '@superset-ui/core';
// @ts-ignore
import PivotTable from '@superset-ui/react-pivottable/PivotTable';
// @ts-ignore
import { sortAs, aggregatorTemplates } from '@superset-ui/react-pivottable/Utilities';
import '@superset-ui/react-pivottable/pivottable.css';
import { PivotTableProps, PivotTableStylesProps } from './types';
import { FilterType, PivotTableProps, PivotTableStylesProps, SelectedFiltersType } from './types';

const Styles = styled.div<PivotTableStylesProps>`
padding: ${({ theme }) => theme.gridUnit * 4}px;
Expand All @@ -33,38 +33,7 @@ const Styles = styled.div<PivotTableStylesProps>`
}
`;

// TODO: remove eslint-disable when click callbacks are implemented
/* eslint-disable @typescript-eslint/no-unused-vars */
const clickCellCallback = (
e: MouseEvent,
value: number,
filters: Record<string, any>,
pivotData: Record<string, any>,
) => {
// TODO: Implement a callback
};

const clickColumnHeaderCallback = (
e: MouseEvent,
value: string,
filters: Record<string, any>,
pivotData: Record<string, any>,
isSubtotal: boolean,
isGrandTotal: boolean,
) => {
// TODO: Implement a callback
};

const clickRowHeaderCallback = (
e: MouseEvent,
value: string,
filters: Record<string, any>,
pivotData: Record<string, any>,
isSubtotal: boolean,
isGrandTotal: boolean,
) => {
// TODO: Implement a callback
};
const METRIC_KEY = 'metric';

export default function PivotTableChart(props: PivotTableProps) {
const {
Expand All @@ -84,6 +53,9 @@ export default function PivotTableChart(props: PivotTableProps) {
colTotals,
rowTotals,
valueFormat,
emitFilter,
setDataMask,
selectedFilters,
} = props;

const adaptiveFormatter = getNumberFormatter(valueFormat);
Expand Down Expand Up @@ -118,16 +90,91 @@ export default function PivotTableChart(props: PivotTableProps) {
...acc,
...metricNames.map((name: string) => ({
...record,
metric: name,
[METRIC_KEY]: name,
value: record[name],
})),
],
[],
);

const [rows, cols] = transposePivot
? [groupbyColumns, ['metric', ...groupbyRows]]
: [groupbyRows, ['metric', ...groupbyColumns]];
? [groupbyColumns, [METRIC_KEY, ...groupbyRows]]
: [groupbyRows, [METRIC_KEY, ...groupbyColumns]];

const handleChange = useCallback(
(filters: SelectedFiltersType) => {
const groupBy = Object.keys(filters);
setDataMask({
extraFormData: {
filters:
groupBy.length === 0
? undefined
: groupBy.map(col => {
const val = filters?.[col];
if (val === null || val === undefined)
return {
col,
op: 'IS NULL',
};
return {
col,
op: 'IN',
val: val as (string | number | boolean)[],
};
}),
},
filterState: {
selectedFilters: filters && Object.keys(filters).length ? filters : null,
},
ownState: {
selectedFilters: filters && Object.keys(filters).length ? filters : null,
},
});
},
[setDataMask],
);

const isActiveFilterValue = useCallback(
(key: string, val: DataRecordValue) => !!selectedFilters && selectedFilters[key]?.includes(val),
[selectedFilters],
);

const toggleFilter = useCallback(
(
e: MouseEvent,
value: string,
filters: FilterType,
pivotData: Record<string, any>,
isSubtotal: boolean,
isGrandTotal: boolean,
) => {
if (isSubtotal || isGrandTotal || !emitFilter) {
return;
}

const filtersCopy = { ...filters };
delete filtersCopy[METRIC_KEY];

const filtersEntries = Object.entries(filtersCopy);
if (filtersEntries.length === 0) {
return;
}

const [key, val] = filtersEntries[filtersEntries.length - 1];

const updatedFilters = { ...(selectedFilters || {}) };
if (selectedFilters && isActiveFilterValue(key, val)) {
updatedFilters[key] = selectedFilters[key].filter((x: DataRecordValue) => x !== val);
} else {
updatedFilters[key] = [...(selectedFilters?.[key] || []), val];
}
if (Array.isArray(updatedFilters[key]) && updatedFilters[key].length === 0) {
delete updatedFilters[key];
}
handleChange(updatedFilters);
},
[emitFilter, selectedFilters, handleChange],
);

return (
<Styles height={height} width={width}>
Expand All @@ -145,11 +192,13 @@ export default function PivotTableChart(props: PivotTableProps) {
metric: sortAs(metricNames),
}}
tableOptions={{
clickCallback: clickCellCallback,
clickRowHeaderCallback,
clickColumnHeaderCallback,
clickRowHeaderCallback: toggleFilter,
clickColumnHeaderCallback: toggleFilter,
colTotals,
rowTotals,
highlightHeaderCellsOnHover: emitFilter,
highlightedHeaderCells: selectedFilters,
omittedHighlightHeaderGroups: [METRIC_KEY],
}}
subtotalOptions={{
colSubtotalDisplay: { displayOnTop: colSubtotalPosition },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import { t, validateNonEmpty } from '@superset-ui/core';
import { FeatureFlag, isFeatureEnabled, t, validateNonEmpty } from '@superset-ui/core';
import {
ControlPanelConfig,
formatSelectOptions,
Expand Down Expand Up @@ -247,6 +247,22 @@ const config: ControlPanelConfig = {
},
},
],
isFeatureEnabled(FeatureFlag.DASHBOARD_CROSS_FILTERS)
? [
{
name: 'emitFilter',
config: {
type: 'CheckboxControl',
label: t('Enable emitting filters'),
renderTrigger: true,
default: false,
description: t(
'Whether to apply filter to dashboards when table cells are clicked',
),
},
},
]
: [],
],
},
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import { t, ChartMetadata, ChartPlugin } from '@superset-ui/core';
import { t, ChartMetadata, ChartPlugin, Behavior } from '@superset-ui/core';
import buildQuery from './buildQuery';
import controlPanel from './controlPanel';
import transformProps from './transformProps';
Expand All @@ -36,6 +36,7 @@ export default class PivotTableChartPlugin extends ChartPlugin<PivotTableQueryFo
*/
constructor() {
const metadata = new ChartMetadata({
behaviors: [Behavior.INTERACTIVE_CHART],
description: 'Pivot Table - experimental',
name: t('Pivot Table v2'),
thumbnail,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,14 @@ export default function transformProps(chartProps: ChartProps) {
* function during development with hot reloading, changes won't
* be seen until restarting the development server.
*/
const { width, height, queriesData, formData } = chartProps;
const {
width,
height,
queriesData,
formData,
hooks: { setDataMask = () => {} },
filterState,
} = chartProps;
const data = queriesData[0].data as DataRecord[];
const {
groupbyRows,
Expand All @@ -64,7 +71,9 @@ export default function transformProps(chartProps: ChartProps) {
colTotals,
rowTotals,
valueFormat,
emitFilter,
} = formData;
const { selectedFilters } = filterState;

return {
width,
Expand All @@ -83,5 +92,8 @@ export default function transformProps(chartProps: ChartProps) {
colTotals,
rowTotals,
valueFormat,
emitFilter,
setDataMask,
selectedFilters,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,22 @@
* specific language governing permissions and limitations
* under the License.
*/
import { QueryFormData, DataRecord, AdhocMetric } from '@superset-ui/core';
import {
QueryFormData,
DataRecord,
AdhocMetric,
SetDataMaskHook,
DataRecordValue,
} from '@superset-ui/core';

export interface PivotTableStylesProps {
height: number;
width: number;
}

export type FilterType = Record<string, DataRecordValue>;
export type SelectedFiltersType = Record<string, DataRecordValue[]>;

interface PivotTableCustomizeProps {
groupbyRows: string[];
groupbyColumns: string[];
Expand All @@ -37,6 +46,9 @@ interface PivotTableCustomizeProps {
colTotals: boolean;
rowTotals: boolean;
valueFormat: string;
setDataMask: SetDataMaskHook;
emitFilter?: boolean;
selectedFilters?: SelectedFiltersType;
}

export type PivotTableQueryFormData = QueryFormData &
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ChartProps } from '@superset-ui/core';
import transformProps from '../../src/plugin/transformProps';

describe('PivotTableChart transformProps', () => {
const setDataMask = jest.fn();
const formData = {
groupbyRows: ['row1', 'row2'],
groupbyColumns: ['col1', 'col2'],
Expand All @@ -16,6 +17,7 @@ describe('PivotTableChart transformProps', () => {
colTotals: true,
rowTotals: true,
valueFormat: 'SMART_NUMBER',
emitFilter: false,
};
const chartProps = new ChartProps({
formData,
Expand All @@ -26,6 +28,8 @@ describe('PivotTableChart transformProps', () => {
data: [{ name: 'Hulk', sum__num: 1, __timestamp: 599616000000 }],
},
],
hooks: { setDataMask },
filterState: { selectedFilters: {} },
});

it('should transform chart props for viz', () => {
Expand All @@ -46,6 +50,9 @@ describe('PivotTableChart transformProps', () => {
rowTotals: true,
valueFormat: 'SMART_NUMBER',
data: [{ name: 'Hulk', sum__num: 1, __timestamp: 599616000000 }],
emitFilter: false,
setDataMask,
selectedFilters: {},
});
});
});
8 changes: 4 additions & 4 deletions superset-frontend/temporary_superset_ui/superset-ui/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4391,10 +4391,10 @@
d3-cloud "^1.2.1"
prop-types "^15.6.2"

"@superset-ui/react-pivottable@^0.12.5":
version "0.12.5"
resolved "https://registry.yarnpkg.com/@superset-ui/react-pivottable/-/react-pivottable-0.12.5.tgz#11448d718f4bf3d9421767c18590604dc436ed1f"
integrity sha512-9Nmj/sRdc6U/OUWBqfqHAaV05MCmNLt65I/Ar39brpLcaYKWRIYpBcUT3n/HOhCr8ALilnsLdQzzfjjaNAuv5w==
"@superset-ui/react-pivottable@^0.12.6":
version "0.12.6"
resolved "https://registry.yarnpkg.com/@superset-ui/react-pivottable/-/react-pivottable-0.12.6.tgz#c1d62a71e5f729381f8be0b1653b9a9e353d3dc3"
integrity sha512-2+81WL4ocv4VFzgkj3wOBcEgejnJfsJ2D08kMvFfeBt6fhqC35nkendeZMAjl4bFBmzSJIFS6H+agjoeOUyq5A==
dependencies:
immutability-helper "^3.1.1"
prop-types "^15.7.2"
Expand Down

0 comments on commit 1e48622

Please sign in to comment.