From fe5f9b093e1bddba0366501c19ff49f0b817d1f5 Mon Sep 17 00:00:00 2001 From: Kamil Gabryjelski Date: Fri, 16 Jul 2021 14:16:41 +0200 Subject: [PATCH] feat(plugin-chart-pivot-table): column, date and conditional formatting (#1217) * feat(plugin-chart-pivot-table): implement conditional and date formatting * Use custom icons for expand/collapse * Fix tests * Revert changes to ControlForm * Fix tests * Rename variable --- .../plugin-chart-pivot-table/package.json | 5 +- .../src/PivotTableChart.tsx | 150 ++++++++++++------ .../src/plugin/controlPanel.ts | 71 +++++++-- .../src/plugin/index.ts | 14 +- .../src/plugin/transformProps.ts | 62 +++++++- .../plugin-chart-pivot-table/src/types.ts | 10 +- .../test/plugin/buildQuery.test.ts | 6 + .../test/plugin/transformProps.test.ts | 17 +- .../superset-ui/yarn.lock | 8 +- 9 files changed, 265 insertions(+), 78 deletions(-) diff --git a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-pivot-table/package.json b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-pivot-table/package.json index 8cdd1a54ffaa..6eaa662c6b58 100644 --- a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-pivot-table/package.json +++ b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-pivot-table/package.json @@ -28,10 +28,11 @@ "dependencies": { "@superset-ui/chart-controls": "0.17.67", "@superset-ui/core": "0.17.64", - "@superset-ui/react-pivottable": "^0.12.8" + "@superset-ui/react-pivottable": "^0.12.9" }, "peerDependencies": { - "react": "^16.13.1" + "react": "^16.13.1", + "@ant-design/icons": "^4.2.2" }, "devDependencies": { "@babel/types": "^7.13.12", diff --git a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-pivot-table/src/PivotTableChart.tsx b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-pivot-table/src/PivotTableChart.tsx index f29892968f89..bf782628d3e3 100644 --- a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-pivot-table/src/PivotTableChart.tsx +++ b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-pivot-table/src/PivotTableChart.tsx @@ -16,8 +16,15 @@ * specific language governing permissions and limitations * under the License. */ -import React, { useCallback } from 'react'; -import { styled, AdhocMetric, getNumberFormatter, DataRecordValue } from '@superset-ui/core'; +import React, { useCallback, useMemo } from 'react'; +import { PlusSquareOutlined, MinusSquareOutlined } from '@ant-design/icons'; +import { + styled, + AdhocMetric, + getNumberFormatter, + DataRecordValue, + NumberFormatter, +} from '@superset-ui/core'; // @ts-ignore import PivotTable from '@superset-ui/react-pivottable/PivotTable'; // @ts-ignore @@ -40,6 +47,52 @@ const Styles = styled.div` `; const METRIC_KEY = 'metric'; +const iconStyle = { stroke: 'black', strokeWidth: '16px' }; + +const aggregatorsFactory = (formatter: NumberFormatter) => ({ + Count: aggregatorTemplates.count(formatter), + 'Count Unique Values': aggregatorTemplates.countUnique(formatter), + 'List Unique Values': aggregatorTemplates.listUnique(', '), + Sum: aggregatorTemplates.sum(formatter), + Average: aggregatorTemplates.average(formatter), + Median: aggregatorTemplates.median(formatter), + 'Sample Variance': aggregatorTemplates.var(1, formatter), + 'Sample Standard Deviation': aggregatorTemplates.stdev(1, formatter), + Minimum: aggregatorTemplates.min(formatter), + Maximum: aggregatorTemplates.max(formatter), + First: aggregatorTemplates.first(), + Last: aggregatorTemplates.last(formatter), + 'Sum as Fraction of Total': aggregatorTemplates.fractionOf( + aggregatorTemplates.sum(), + 'total', + formatter, + ), + 'Sum as Fraction of Rows': aggregatorTemplates.fractionOf( + aggregatorTemplates.sum(), + 'row', + formatter, + ), + 'Sum as Fraction of Columns': aggregatorTemplates.fractionOf( + aggregatorTemplates.sum(), + 'col', + formatter, + ), + 'Count as Fraction of Total': aggregatorTemplates.fractionOf( + aggregatorTemplates.count(), + 'total', + formatter, + ), + 'Count as Fraction of Rows': aggregatorTemplates.fractionOf( + aggregatorTemplates.count(), + 'row', + formatter, + ), + 'Count as Fraction of Columns': aggregatorTemplates.fractionOf( + aggregatorTemplates.count(), + 'col', + formatter, + ), +}); export default function PivotTableChart(props: PivotTableProps) { const { @@ -49,11 +102,11 @@ export default function PivotTableChart(props: PivotTableProps) { groupbyRows, groupbyColumns, metrics, - tableRenderer, colOrder, rowOrder, aggregateFunction, transposePivot, + combineMetric, rowSubtotalPosition, colSubtotalPosition, colTotals, @@ -63,54 +116,51 @@ export default function PivotTableChart(props: PivotTableProps) { setDataMask, selectedFilters, verboseMap, + columnFormats, metricsLayout, + metricColorFormatters, + dateFormatters, } = props; - const adaptiveFormatter = getNumberFormatter(valueFormat); - - const aggregators = (tpl => ({ - Count: tpl.count(adaptiveFormatter), - 'Count Unique Values': tpl.countUnique(adaptiveFormatter), - 'List Unique Values': tpl.listUnique(', '), - Sum: tpl.sum(adaptiveFormatter), - Average: tpl.average(adaptiveFormatter), - Median: tpl.median(adaptiveFormatter), - 'Sample Variance': tpl.var(1, adaptiveFormatter), - 'Sample Standard Deviation': tpl.stdev(1, adaptiveFormatter), - Minimum: tpl.min(adaptiveFormatter), - Maximum: tpl.max(adaptiveFormatter), - First: tpl.first(adaptiveFormatter), - Last: tpl.last(adaptiveFormatter), - 'Sum as Fraction of Total': tpl.fractionOf(tpl.sum(), 'total', adaptiveFormatter), - 'Sum as Fraction of Rows': tpl.fractionOf(tpl.sum(), 'row', adaptiveFormatter), - 'Sum as Fraction of Columns': tpl.fractionOf(tpl.sum(), 'col', adaptiveFormatter), - 'Count as Fraction of Total': tpl.fractionOf(tpl.count(), 'total', adaptiveFormatter), - 'Count as Fraction of Rows': tpl.fractionOf(tpl.count(), 'row', adaptiveFormatter), - 'Count as Fraction of Columns': tpl.fractionOf(tpl.count(), 'col', adaptiveFormatter), - }))(aggregatorTemplates); - - const metricNames = metrics.map((metric: string | AdhocMetric) => - typeof metric === 'string' ? metric : (metric.label as string), + const defaultFormatter = getNumberFormatter(valueFormat); + const columnFormatsArray = Object.entries(columnFormats); + const hasCustomMetricFormatters = columnFormatsArray.length > 0; + const metricFormatters = + hasCustomMetricFormatters && + Object.fromEntries( + columnFormatsArray.map(([metric, format]) => [metric, getNumberFormatter(format)]), + ); + + const metricNames = useMemo( + () => + metrics.map((metric: string | AdhocMetric) => + typeof metric === 'string' ? metric : (metric.label as string), + ), + [metrics], ); - const unpivotedData = data.reduce( - (acc: Record[], record: Record) => [ - ...acc, - ...metricNames.map((name: string) => ({ - ...record, - [METRIC_KEY]: name, - value: record[name], - })), - ], - [], + const unpivotedData = useMemo( + () => + data.reduce( + (acc: Record[], record: Record) => [ + ...acc, + ...metricNames.map((name: string) => ({ + ...record, + [METRIC_KEY]: name, + value: record[name], + })), + ], + [], + ), + [data, metricNames], ); let [rows, cols] = transposePivot ? [groupbyColumns, groupbyRows] : [groupbyRows, groupbyColumns]; if (metricsLayout === MetricsLayoutEnum.ROWS) { - rows = [METRIC_KEY, ...rows]; + rows = combineMetric ? [...rows, METRIC_KEY] : [METRIC_KEY, ...rows]; } else { - cols = [METRIC_KEY, ...cols]; + cols = combineMetric ? [...cols, METRIC_KEY] : [METRIC_KEY, ...cols]; } const handleChange = useCallback( @@ -144,11 +194,6 @@ export default function PivotTableChart(props: PivotTableProps) { [setDataMask], ); - const isActiveFilterValue = useCallback( - (key: string, val: DataRecordValue) => !!selectedFilters && selectedFilters[key]?.includes(val), - [selectedFilters], - ); - const toggleFilter = useCallback( ( e: MouseEvent, @@ -162,6 +207,9 @@ export default function PivotTableChart(props: PivotTableProps) { return; } + const isActiveFilterValue = (key: string, val: DataRecordValue) => + !!selectedFilters && selectedFilters[key]?.includes(val); + const filtersCopy = { ...filters }; delete filtersCopy[METRIC_KEY]; @@ -201,10 +249,14 @@ export default function PivotTableChart(props: PivotTableProps) { data={unpivotedData} rows={rows} cols={cols} - aggregators={aggregators} + aggregatorsFactory={aggregatorsFactory} + defaultFormatter={defaultFormatter} + customFormatters={ + hasCustomMetricFormatters ? { [METRIC_KEY]: metricFormatters } : undefined + } aggregatorName={aggregateFunction} vals={['value']} - rendererName={tableRenderer} + rendererName="Table With Subtotal" colOrder={colOrder} rowOrder={rowOrder} sorters={{ @@ -218,10 +270,14 @@ export default function PivotTableChart(props: PivotTableProps) { highlightHeaderCellsOnHover: emitFilter, highlightedHeaderCells: selectedFilters, omittedHighlightHeaderGroups: [METRIC_KEY], + cellColorFormatters: { [METRIC_KEY]: metricColorFormatters }, + dateFormatters, }} subtotalOptions={{ colSubtotalDisplay: { displayOnTop: colSubtotalPosition }, rowSubtotalDisplay: { displayOnTop: rowSubtotalPosition }, + arrowCollapsed: , + arrowExpanded: , }} namesMapping={verboseMap} /> diff --git a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-pivot-table/src/plugin/controlPanel.ts b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-pivot-table/src/plugin/controlPanel.ts index 54d3f52cc016..5f5cd604a363 100644 --- a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-pivot-table/src/plugin/controlPanel.ts +++ b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-pivot-table/src/plugin/controlPanel.ts @@ -16,9 +16,17 @@ * specific language governing permissions and limitations * under the License. */ -import { FeatureFlag, isFeatureEnabled, t, validateNonEmpty } from '@superset-ui/core'; +import { + FeatureFlag, + isFeatureEnabled, + QueryFormMetric, + smartDateFormatter, + t, + validateNonEmpty, +} from '@superset-ui/core'; import { ControlPanelConfig, + D3_TIME_FORMAT_OPTIONS, formatSelectOptions, sections, sharedControls, @@ -140,6 +148,21 @@ const config: ControlPanelConfig = { }, }, ], + [ + { + name: 'combineMetric', + config: { + type: 'CheckboxControl', + label: t('Combine metrics'), + default: false, + description: t( + 'Display metrics side by side within each column, as ' + + 'opposed to each column being displayed side by side for each metric.', + ), + renderTrigger: true, + }, + }, + ], ], }, { @@ -154,23 +177,16 @@ const config: ControlPanelConfig = { ], [ { - name: 'tableRenderer', + name: 'date_format', config: { type: 'SelectControl', - label: t('Pivot table type'), - default: 'Table With Subtotal', - choices: [ - // [value, label] - ['Table With Subtotal', t('Table')], - ['Table With Subtotal Heatmap', t('Table Heatmap')], - ['Table With Subtotal Col Heatmap', t('Table Col Heatmap')], - ['Table With Subtotal Row Heatmap', t('Table Row Heatmap')], - ['Table With Subtotal Barchart', t('Table Barchart')], - ['Table With Subtotal Col Barchart', t('Table Col Barchart')], - ['Table With Subtotal Row Barchart', t('Table Row Barchart')], - ], + freeForm: true, + label: t('Date format'), + default: smartDateFormatter.id, renderTrigger: true, - description: t('The type of pivot table visualization'), + clearable: false, + choices: D3_TIME_FORMAT_OPTIONS, + description: t('D3 time format for datetime columns'), }, }, ], @@ -280,6 +296,31 @@ const config: ControlPanelConfig = { }, ] : [], + [ + { + name: 'conditional_formatting', + config: { + type: 'ConditionalFormattingControl', + renderTrigger: true, + label: t('Customize metrics'), + description: t('Apply conditional color formatting to metrics'), + mapStateToProps(explore) { + const values = (explore?.controls?.metrics?.value as QueryFormMetric[]) ?? []; + const verboseMap = explore?.datasource?.verbose_map ?? {}; + const metricColumn = values.map(value => { + if (typeof value === 'string') { + return { value, label: verboseMap[value] ?? value }; + } + return { value: value.label, label: value.label }; + }); + return { + columnOptions: metricColumn, + verboseMap, + }; + }, + }, + }, + ], ], }, ], diff --git a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-pivot-table/src/plugin/index.ts b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-pivot-table/src/plugin/index.ts index 41b81c084227..0bc087b35d57 100644 --- a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-pivot-table/src/plugin/index.ts +++ b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-pivot-table/src/plugin/index.ts @@ -16,14 +16,24 @@ * specific language governing permissions and limitations * under the License. */ -import { t, ChartMetadata, ChartPlugin, Behavior } from '@superset-ui/core'; +import { + t, + ChartMetadata, + ChartPlugin, + Behavior, + ChartProps, + QueryFormData, +} from '@superset-ui/core'; import buildQuery from './buildQuery'; import controlPanel from './controlPanel'; import transformProps from './transformProps'; import thumbnail from '../images/thumbnail.png'; import { PivotTableQueryFormData } from '../types'; -export default class PivotTableChartPlugin extends ChartPlugin { +export default class PivotTableChartPlugin extends ChartPlugin< + PivotTableQueryFormData, + ChartProps +> { /** * The constructor is used to pass relevant metadata and callbacks that get * registered in respective registries that are used throughout the library diff --git a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-pivot-table/src/plugin/transformProps.ts b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-pivot-table/src/plugin/transformProps.ts index 84be1b589721..892a36a54d68 100644 --- a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-pivot-table/src/plugin/transformProps.ts +++ b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-pivot-table/src/plugin/transformProps.ts @@ -16,9 +16,30 @@ * specific language governing permissions and limitations * under the License. */ -import { ChartProps, DataRecord } from '@superset-ui/core'; +import { + ChartProps, + DataRecord, + extractTimegrain, + GenericDataType, + getTimeFormatter, + getTimeFormatterForGranularity, + QueryFormData, + smartDateFormatter, + TimeFormats, +} from '@superset-ui/core'; +import { getColorFormatters } from '@superset-ui/chart-controls'; +import { DateFormatter } from '../types'; -export default function transformProps(chartProps: ChartProps) { +const { DATABASE_DATETIME } = TimeFormats; +const TIME_COLUMN = '__timestamp'; + +function isNumeric(key: string, data: DataRecord[] = []) { + return data.every( + record => record[key] === null || record[key] === undefined || typeof record[key] === 'number', + ); +} + +export default function transformProps(chartProps: ChartProps) { /** * This function is called after a successful response has been * received from the chart data endpoint, and is used to transform @@ -53,11 +74,12 @@ export default function transformProps(chartProps: ChartProps) { height, queriesData, formData, + rawFormData, hooks: { setDataMask = () => {} }, filterState, - datasource: { verboseMap = {} }, + datasource: { verboseMap = {}, columnFormats = {} }, } = chartProps; - const data = queriesData[0].data as DataRecord[]; + const { data, colnames, coltypes } = queriesData[0]; const { groupbyRows, groupbyColumns, @@ -67,15 +89,43 @@ export default function transformProps(chartProps: ChartProps) { rowOrder, aggregateFunction, transposePivot, + combineMetric, rowSubtotalPosition, colSubtotalPosition, colTotals, rowTotals, valueFormat, + dateFormat, emitFilter, metricsLayout, + conditionalFormatting, } = formData; const { selectedFilters } = filterState; + const granularity = extractTimegrain(rawFormData); + + const dateFormatters = colnames + .filter((colname: string, index: number) => coltypes[index] === GenericDataType.TEMPORAL) + .reduce((acc: Record, temporalColname: string) => { + let formatter: DateFormatter | undefined; + if (dateFormat === smartDateFormatter.id) { + if (temporalColname === TIME_COLUMN) { + // time column use formats based on granularity + formatter = getTimeFormatterForGranularity(granularity); + } else if (isNumeric(temporalColname, data)) { + formatter = getTimeFormatter(DATABASE_DATETIME); + } else { + // if no column-specific format, print cell as is + formatter = String; + } + } else if (dateFormat) { + formatter = getTimeFormatter(dateFormat); + } + if (formatter) { + acc[temporalColname] = formatter; + } + return acc; + }, {}); + const metricColorFormatters = getColorFormatters(conditionalFormatting, data); return { width, @@ -89,6 +139,7 @@ export default function transformProps(chartProps: ChartProps) { rowOrder, aggregateFunction, transposePivot, + combineMetric, rowSubtotalPosition, colSubtotalPosition, colTotals, @@ -98,6 +149,9 @@ export default function transformProps(chartProps: ChartProps) { setDataMask, selectedFilters, verboseMap, + columnFormats, metricsLayout, + metricColorFormatters, + dateFormatters, }; } diff --git a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-pivot-table/src/types.ts b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-pivot-table/src/types.ts index 66e9d966fd1e..88451082e13b 100644 --- a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-pivot-table/src/types.ts +++ b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-pivot-table/src/types.ts @@ -23,7 +23,10 @@ import { SetDataMaskHook, DataRecordValue, JsonObject, + TimeFormatter, + NumberFormatter, } from '@superset-ui/core'; +import { ColorFormatters } from '@superset-ui/chart-controls'; export interface PivotTableStylesProps { height: number; @@ -33,6 +36,7 @@ export interface PivotTableStylesProps { export type FilterType = Record; export type SelectedFiltersType = Record; +export type DateFormatter = TimeFormatter | NumberFormatter | ((value: DataRecordValue) => string); export enum MetricsLayoutEnum { ROWS = 'ROWS', COLUMNS = 'COLUMNS', @@ -47,6 +51,7 @@ interface PivotTableCustomizeProps { rowOrder: string; aggregateFunction: string; transposePivot: boolean; + combineMetric: boolean; rowSubtotalPosition: boolean; colSubtotalPosition: boolean; colTotals: boolean; @@ -55,8 +60,11 @@ interface PivotTableCustomizeProps { setDataMask: SetDataMaskHook; emitFilter?: boolean; selectedFilters?: SelectedFiltersType; - verboseMap?: JsonObject; + verboseMap: JsonObject; + columnFormats: JsonObject; metricsLayout?: MetricsLayoutEnum; + metricColorFormatters: ColorFormatters; + dateFormatters: Record; } export type PivotTableQueryFormData = QueryFormData & diff --git a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-pivot-table/test/plugin/buildQuery.test.ts b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-pivot-table/test/plugin/buildQuery.test.ts index 419234353efd..90ba148de60a 100644 --- a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-pivot-table/test/plugin/buildQuery.test.ts +++ b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-pivot-table/test/plugin/buildQuery.test.ts @@ -19,6 +19,12 @@ describe('PivotTableChart buildQuery', () => { viz_type: 'my_chart', width: 800, height: 600, + combineMetric: false, + verboseMap: {}, + columnFormats: {}, + metricColorFormatters: [], + dateFormatters: {}, + setDataMask: () => {}, }; it('should build groupby with series in form data', () => { diff --git a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-pivot-table/test/plugin/transformProps.test.ts b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-pivot-table/test/plugin/transformProps.test.ts index 969136e19a59..e8c5a0ca0965 100644 --- a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-pivot-table/test/plugin/transformProps.test.ts +++ b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-pivot-table/test/plugin/transformProps.test.ts @@ -1,4 +1,4 @@ -import { ChartProps } from '@superset-ui/core'; +import { ChartProps, QueryFormData } from '@superset-ui/core'; import transformProps from '../../src/plugin/transformProps'; import { MetricsLayoutEnum } from '../../src/types'; @@ -13,6 +13,7 @@ describe('PivotTableChart transformProps', () => { rowOrder: 'key_a_to_z', aggregateFunction: 'Sum', transposePivot: true, + combineMetric: true, rowSubtotalPosition: true, colSubtotalPosition: true, colTotals: true, @@ -20,19 +21,25 @@ describe('PivotTableChart transformProps', () => { valueFormat: 'SMART_NUMBER', emitFilter: false, metricsLayout: MetricsLayoutEnum.COLUMNS, + viz_type: '', + datasource: '', + conditionalFormatting: [], + dateFormat: '', }; - const chartProps = new ChartProps({ + const chartProps = new ChartProps({ formData, width: 800, height: 600, queriesData: [ { data: [{ name: 'Hulk', sum__num: 1, __timestamp: 599616000000 }], + colnames: ['name', 'sum__num', '__timestamp'], + coltypes: [1, 0, 2], }, ], hooks: { setDataMask }, filterState: { selectedFilters: {} }, - datasource: { verboseMap: {} }, + datasource: { verboseMap: {}, columnFormats: {} }, }); it('should transform chart props for viz', () => { @@ -47,6 +54,7 @@ describe('PivotTableChart transformProps', () => { rowOrder: 'key_a_to_z', aggregateFunction: 'Sum', transposePivot: true, + combineMetric: true, rowSubtotalPosition: true, colSubtotalPosition: true, colTotals: true, @@ -58,6 +66,9 @@ describe('PivotTableChart transformProps', () => { selectedFilters: {}, verboseMap: {}, metricsLayout: MetricsLayoutEnum.COLUMNS, + metricColorFormatters: [], + dateFormatters: {}, + columnFormats: {}, }); }); }); diff --git a/superset-frontend/temporary_superset_ui/superset-ui/yarn.lock b/superset-frontend/temporary_superset_ui/superset-ui/yarn.lock index a9811ab6fe66..5d344489d7a9 100644 --- a/superset-frontend/temporary_superset_ui/superset-ui/yarn.lock +++ b/superset-frontend/temporary_superset_ui/superset-ui/yarn.lock @@ -4396,10 +4396,10 @@ d3-cloud "^1.2.1" prop-types "^15.6.2" -"@superset-ui/react-pivottable@^0.12.8": - version "0.12.8" - resolved "https://registry.yarnpkg.com/@superset-ui/react-pivottable/-/react-pivottable-0.12.8.tgz#3b7d6cd32719d6510b88b2334afacbb1735afee5" - integrity sha512-7DRxX/w1uSQE1pibSe64t1o+fmiP7ZWT2FJkjK510bSJm8NUIPCXtmpK+NKtNZuCteE9sqE7bQxd54SSq2xWKw== +"@superset-ui/react-pivottable@^0.12.9": + version "0.12.9" + resolved "https://registry.yarnpkg.com/@superset-ui/react-pivottable/-/react-pivottable-0.12.9.tgz#f46ceef940c2f99c197db4acd7487efc48be05bf" + integrity sha512-wztcGEGg4Fc/zxHTDSP2ANyE9aZYiDPHf01FSx6rBapxtXP04NvyLPJl3RKNN/D5l98o5bj5xO/5KSailM9LjQ== dependencies: immutability-helper "^3.1.1" prop-types "^15.7.2"