From 292b24e30d91daf323f30d75af796160448f96ce Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Mon, 27 Jun 2022 15:13:19 +0200 Subject: [PATCH] Piechart: Implements series override -> hide in area for the legend or tooltip (#51297) * piechart: support series override for hiding legend item * piechart: add support for hiding values in multi tooltip * tidy up variable names * piechart: typing for hideFrom variable * betterer fixes --- .betterer.results | 2 +- .../app/plugins/panel/piechart/PieChart.tsx | 21 +- .../panel/piechart/PieChartPanel.test.tsx | 206 ++++++++++++++++++ .../plugins/panel/piechart/PieChartPanel.tsx | 24 +- 4 files changed, 236 insertions(+), 17 deletions(-) create mode 100644 public/app/plugins/panel/piechart/PieChartPanel.test.tsx diff --git a/.betterer.results b/.betterer.results index 1e56fc20c86..638674a8a46 100644 --- a/.betterer.results +++ b/.betterer.results @@ -11874,7 +11874,7 @@ exports[`better eslint`] = { [194, 16, 3, "Unexpected any. Specify a different type.", "193409811"], [256, 16, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/plugins/panel/piechart/PieChart.tsx:287896203": [ + "public/app/plugins/panel/piechart/PieChart.tsx:3082793434": [ [201, 12, 3, "Unexpected any. Specify a different type.", "193409811"], [217, 12, 3, "Unexpected any. Specify a different type.", "193409811"] ], diff --git a/public/app/plugins/panel/piechart/PieChart.tsx b/public/app/plugins/panel/piechart/PieChart.tsx index 71a0c63c2db..26f448e0280 100644 --- a/public/app/plugins/panel/piechart/PieChart.tsx +++ b/public/app/plugins/panel/piechart/PieChart.tsx @@ -314,14 +314,19 @@ function getTooltipData( tooltipOptions: VizTooltipOptions ) { if (tooltipOptions.mode === 'multi') { - return pie.arcs.map((pieArc) => { - return { - color: pieArc.data.display.color ?? FALLBACK_COLOR, - label: pieArc.data.display.title, - value: formattedValueToString(pieArc.data.display), - isActive: pieArc.index === arc.index, - }; - }); + return pie.arcs + .filter((pa) => { + const field = pa.data.field; + return field && !field.custom?.hideFrom?.tooltip && !field.custom?.hideFrom?.viz; + }) + .map((pieArc) => { + return { + color: pieArc.data.display.color ?? FALLBACK_COLOR, + label: pieArc.data.display.title, + value: formattedValueToString(pieArc.data.display), + isActive: pieArc.index === arc.index, + }; + }); } return [ { diff --git a/public/app/plugins/panel/piechart/PieChartPanel.test.tsx b/public/app/plugins/panel/piechart/PieChartPanel.test.tsx new file mode 100644 index 00000000000..fcaef98151b --- /dev/null +++ b/public/app/plugins/panel/piechart/PieChartPanel.test.tsx @@ -0,0 +1,206 @@ +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React, { ComponentProps } from 'react'; + +import { + FieldConfigSource, + toDataFrame, + FieldType, + VizOrientation, + LoadingState, + getDefaultTimeRange, + EventBusSrv, +} from '@grafana/data'; +import { LegendDisplayMode, SortOrder, TooltipDisplayMode } from '@grafana/schema'; + +import { PieChartPanel } from './PieChartPanel'; +import { PieChartOptions, PieChartType, PieChartLegendValues } from './types'; + +type PieChartPanelProps = ComponentProps; + +describe('PieChartPanel', () => { + beforeEach(() => { + Object.defineProperty(global, 'ResizeObserver', { + writable: true, + value: jest.fn().mockImplementation(() => ({ + observe: jest.fn(), + unobserve: jest.fn(), + disconnect: jest.fn(), + })), + }); + }); + + describe('series overrides - Hide in area', () => { + const defaultConfig = { + custom: { + hideFrom: { + legend: false, + viz: false, + tooltip: false, + }, + }, + }; + + describe('when no hiding', () => { + const seriesWithNoOverrides = [ + toDataFrame({ + fields: [ + { name: 'Chrome', config: defaultConfig, type: FieldType.number, values: [60] }, + { name: 'Firefox', config: defaultConfig, type: FieldType.number, values: [20] }, + { name: 'Safari', config: defaultConfig, type: FieldType.number, values: [20] }, + ], + }), + ]; + + it('should not filter out any slices or legend items', () => { + setup({ data: { series: seriesWithNoOverrides } }); + + const slices = screen.queryAllByLabelText('Pie Chart Slice'); + expect(slices.length).toBe(3); + expect(screen.queryByText(/Chrome/i)).toBeInTheDocument(); + expect(screen.queryByText(/Firefox/i)).toBeInTheDocument(); + expect(screen.queryByText(/Safari/i)).toBeInTheDocument(); + }); + }); + + describe('when series override to hide viz', () => { + const hideVizConfig = { + custom: { + hideFrom: { + legend: false, + viz: true, + tooltip: false, + }, + }, + }; + + const seriesWithFirefoxOverride = [ + toDataFrame({ + fields: [ + { name: 'Chrome', config: defaultConfig, type: FieldType.number, values: [60] }, + { name: 'Firefox', config: hideVizConfig, type: FieldType.number, values: [20] }, + { name: 'Safari', config: defaultConfig, type: FieldType.number, values: [20] }, + ], + }), + ]; + + it('should filter out the Firefox pie chart slice but not the legend', () => { + setup({ data: { series: seriesWithFirefoxOverride } }); + + const slices = screen.queryAllByLabelText('Pie Chart Slice'); + expect(slices.length).toBe(2); + expect(screen.queryByText(/Firefox/i)).toBeInTheDocument(); + }); + }); + + describe('when series override to hide tooltip', () => { + const hideTooltipConfig = { + custom: { + hideFrom: { + legend: false, + viz: false, + tooltip: true, + }, + }, + }; + + const seriesWithFirefoxOverride = [ + toDataFrame({ + fields: [ + { name: 'Chrome', config: defaultConfig, type: FieldType.number, values: [600] }, + { name: 'Firefox', config: hideTooltipConfig, type: FieldType.number, values: [190] }, + { name: 'Safari', config: defaultConfig, type: FieldType.number, values: [210] }, + ], + }), + ]; + + it('should filter out the Firefox series with value 190 from the multi tooltip', async () => { + setup({ data: { series: seriesWithFirefoxOverride } }); + + await userEvent.hover(screen.getAllByLabelText('Pie Chart Slice')[0]); + expect(screen.queryByText(/600/i)).toBeInTheDocument(); + expect(screen.queryByText(/190/i)).not.toBeInTheDocument(); + expect(screen.queryByText(/210/i)).toBeInTheDocument(); + + expect(screen.queryByText(/Firefox/i)).toBeInTheDocument(); + const slices = screen.queryAllByLabelText('Pie Chart Slice'); + expect(slices.length).toBe(3); + }); + }); + + describe('when series override to hide legend', () => { + const hideLegendConfig = { + custom: { + hideFrom: { + legend: true, + viz: false, + tooltip: false, + }, + }, + }; + + const seriesWithFirefoxOverride = [ + toDataFrame({ + fields: [ + { name: 'Chrome', config: defaultConfig, type: FieldType.number, values: [60] }, + { name: 'Firefox', config: hideLegendConfig, type: FieldType.number, values: [20] }, + { name: 'Safari', config: defaultConfig, type: FieldType.number, values: [20] }, + ], + }), + ]; + + it('should filter out the series from the legend but not the slice', () => { + setup({ data: { series: seriesWithFirefoxOverride } }); + + expect(screen.queryByText(/Firefox/i)).not.toBeInTheDocument(); + const slices = screen.queryAllByLabelText('Pie Chart Slice'); + expect(slices.length).toBe(3); + }); + }); + }); +}); + +const setup = (propsOverrides?: {}) => { + const fieldConfig: FieldConfigSource = { + defaults: {}, + overrides: [], + }; + + const options: PieChartOptions = { + pieType: PieChartType.Pie, + displayLabels: [], + legend: { + displayMode: LegendDisplayMode.List, + placement: 'right', + calcs: [], + values: [PieChartLegendValues.Percent], + }, + reduceOptions: { + calcs: [], + }, + orientation: VizOrientation.Auto, + tooltip: { mode: TooltipDisplayMode.Multi, sort: SortOrder.Ascending }, + }; + + const props: PieChartPanelProps = { + id: 1, + data: { state: LoadingState.Done, timeRange: getDefaultTimeRange(), series: [] }, + timeZone: 'utc', + options: options, + fieldConfig: fieldConfig, + width: 532, + height: 250, + renderCounter: 0, + title: 'A pie chart', + transparent: false, + onFieldConfigChange: () => {}, + onOptionsChange: () => {}, + onChangeTimeRange: () => {}, + replaceVariables: (s: string) => s, + eventBus: new EventBusSrv(), + timeRange: getDefaultTimeRange(), + ...propsOverrides, + }; + + return render(); +}; diff --git a/public/app/plugins/panel/piechart/PieChartPanel.tsx b/public/app/plugins/panel/piechart/PieChartPanel.tsx index c5dbd94bf75..9fb348ded96 100644 --- a/public/app/plugins/panel/piechart/PieChartPanel.tsx +++ b/public/app/plugins/panel/piechart/PieChartPanel.tsx @@ -11,7 +11,7 @@ import { PanelProps, } from '@grafana/data'; import { PanelDataErrorView } from '@grafana/runtime'; -import { LegendDisplayMode } from '@grafana/schema'; +import { HideSeriesConfig, LegendDisplayMode } from '@grafana/schema'; import { SeriesVisibilityChangeBehavior, usePanelContext, @@ -82,7 +82,7 @@ function getLegend(props: Props, displayValues: FieldDisplay[]) { } const total = displayValues.filter(filterDisplayItems).reduce(sumDisplayItemsReducer, 0); - const legendItems = displayValues + const legendItems: VizLegendItem[] = displayValues // Since the pie chart is always sorted, let's sort the legend as well. .sort((a, b) => { if (isNaN(a.display.numeric)) { @@ -93,14 +93,21 @@ function getLegend(props: Props, displayValues: FieldDisplay[]) { return b.display.numeric - a.display.numeric; } }) - .map((value, idx) => { - const hidden = value.field.custom.hideFrom.viz; + .map((value: FieldDisplay, idx: number) => { + const hideFrom: HideSeriesConfig = value.field.custom?.hideFrom ?? {}; + + if (hideFrom.legend) { + return undefined; + } + + const hideFromViz = Boolean(hideFrom.viz); + const display = value.display; return { label: display.title ?? '', color: display.color ?? FALLBACK_COLOR, yAxis: 1, - disabled: hidden, + disabled: hideFromViz, getItemKey: () => (display.title ?? '') + idx, getDisplayValues: () => { const valuesToShow = legendOptions.values ?? []; @@ -111,14 +118,14 @@ function getLegend(props: Props, displayValues: FieldDisplay[]) { } if (valuesToShow.includes(PieChartLegendValues.Percent)) { - const fractionOfTotal = hidden ? 0 : display.numeric / total; + const fractionOfTotal = hideFromViz ? 0 : display.numeric / total; const percentOfTotal = fractionOfTotal * 100; displayValues.push({ numeric: fractionOfTotal, percent: percentOfTotal, text: - hidden || isNaN(fractionOfTotal) + hideFromViz || isNaN(fractionOfTotal) ? props.fieldConfig.defaults.noValue ?? '-' : percentOfTotal.toFixed(value.field.decimals ?? 0) + '%', title: valuesToShow.length > 1 ? 'Percent' : '', @@ -128,7 +135,8 @@ function getLegend(props: Props, displayValues: FieldDisplay[]) { return displayValues; }, }; - }); + }) + .filter((i): i is VizLegendItem => !!i); return (