Skip to content

Commit ff80d90

Browse files
[Lens] Fix for Percentage and Metric suggestions/visualizations on 0 or empty vlaues (#79309)
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
1 parent ae84bb2 commit ff80d90

File tree

4 files changed

+165
-12
lines changed

4 files changed

+165
-12
lines changed

x-pack/plugins/lens/public/metric_visualization/expression.test.tsx

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,5 +179,89 @@ describe('metric_expression', () => {
179179
</VisualizationContainer>
180180
`);
181181
});
182+
183+
test('it renders an EmptyPlaceholder when no tables is passed as data', () => {
184+
const { data, noAttributesArgs } = sampleArgs();
185+
186+
expect(
187+
shallow(
188+
<MetricChart
189+
data={{ ...data, tables: {} }}
190+
args={noAttributesArgs}
191+
formatFactory={(x) => x as IFieldFormat}
192+
/>
193+
)
194+
).toMatchInlineSnapshot(`
195+
<EmptyPlaceholder
196+
icon={[Function]}
197+
/>
198+
`);
199+
});
200+
201+
test('it renders an EmptyPlaceholder when null value is passed as data', () => {
202+
const { data, noAttributesArgs } = sampleArgs();
203+
204+
data.tables.l1.rows[0].a = null;
205+
206+
expect(
207+
shallow(
208+
<MetricChart
209+
data={data}
210+
args={noAttributesArgs}
211+
formatFactory={(x) => x as IFieldFormat}
212+
/>
213+
)
214+
).toMatchInlineSnapshot(`
215+
<EmptyPlaceholder
216+
icon={[Function]}
217+
/>
218+
`);
219+
});
220+
221+
test('it renders 0 value', () => {
222+
const { data, noAttributesArgs } = sampleArgs();
223+
224+
data.tables.l1.rows[0].a = 0;
225+
226+
expect(
227+
shallow(
228+
<MetricChart
229+
data={data}
230+
args={noAttributesArgs}
231+
formatFactory={(x) => x as IFieldFormat}
232+
/>
233+
)
234+
).toMatchInlineSnapshot(`
235+
<VisualizationContainer
236+
className="lnsMetricExpression__container"
237+
reportDescription=""
238+
reportTitle=""
239+
>
240+
<AutoScale>
241+
<div
242+
data-test-subj="lns_metric_value"
243+
style={
244+
Object {
245+
"fontSize": "60pt",
246+
"fontWeight": 600,
247+
}
248+
}
249+
>
250+
0
251+
</div>
252+
<div
253+
data-test-subj="lns_metric_title"
254+
style={
255+
Object {
256+
"fontSize": "24pt",
257+
}
258+
}
259+
>
260+
My fanci metric chart
261+
</div>
262+
</AutoScale>
263+
</VisualizationContainer>
264+
`);
265+
});
182266
});
183267
});

x-pack/plugins/lens/public/metric_visualization/expression.tsx

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import { MetricConfig } from './types';
1717
import { FormatFactory, LensMultiTable } from '../types';
1818
import { AutoScale } from './auto_scale';
1919
import { VisualizationContainer } from '../visualization_container';
20+
import { EmptyPlaceholder } from '../shared_components';
21+
import { LensIconChartMetric } from '../assets/chart_metric';
2022

2123
export interface MetricChartProps {
2224
data: LensMultiTable;
@@ -107,7 +109,6 @@ export function MetricChart({
107109
formatFactory,
108110
}: MetricChartProps & { formatFactory: FormatFactory }) {
109111
const { metricTitle, title, description, accessor, mode } = args;
110-
let value = '-';
111112
const firstTable = Object.values(data.tables)[0];
112113
if (!accessor) {
113114
return (
@@ -119,17 +120,26 @@ export function MetricChart({
119120
);
120121
}
121122

122-
if (firstTable) {
123-
const column = firstTable.columns[0];
124-
const row = firstTable.rows[0];
125-
if (row[accessor]) {
126-
value =
127-
column && column.formatHint
128-
? formatFactory(column.formatHint).convert(row[accessor])
129-
: Number(Number(row[accessor]).toFixed(3)).toString();
130-
}
123+
if (!firstTable) {
124+
return <EmptyPlaceholder icon={LensIconChartMetric} />;
131125
}
132126

127+
const column = firstTable.columns[0];
128+
const row = firstTable.rows[0];
129+
130+
// NOTE: Cardinality and Sum never receives "null" as value, but always 0, even for empty dataset.
131+
// Mind falsy values here as 0!
132+
const shouldShowResults = row[accessor] != null;
133+
134+
if (!shouldShowResults) {
135+
return <EmptyPlaceholder icon={LensIconChartMetric} />;
136+
}
137+
138+
const value =
139+
column && column.formatHint
140+
? formatFactory(column.formatHint).convert(row[accessor])
141+
: Number(Number(row[accessor]).toFixed(3)).toString();
142+
133143
return (
134144
<VisualizationContainer
135145
reportTitle={title}

x-pack/plugins/lens/public/xy_visualization/expression.test.tsx

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import {
3535
import { createMockExecutionContext } from '../../../../../src/plugins/expressions/common/mocks';
3636
import { mountWithIntl } from 'test_utils/enzyme_helpers';
3737
import { chartPluginMock } from '../../../../../src/plugins/charts/public/mocks';
38+
import { EmptyPlaceholder } from '../shared_components/empty_placeholder';
3839

3940
const onClickValue = jest.fn();
4041
const onSelectRange = jest.fn();
@@ -721,6 +722,29 @@ describe('xy_expression', () => {
721722
expect(component.find(Settings).prop('rotation')).toEqual(90);
722723
});
723724

725+
test('it renders regular bar empty placeholder for no results', () => {
726+
const { data, args } = sampleArgs();
727+
728+
// send empty data to the chart
729+
data.tables.first.rows = [];
730+
731+
const component = shallow(
732+
<XYChart
733+
data={data}
734+
args={args}
735+
formatFactory={getFormatSpy}
736+
timeZone="UTC"
737+
chartsThemeService={chartsThemeService}
738+
histogramBarTarget={50}
739+
onClickValue={onClickValue}
740+
onSelectRange={onSelectRange}
741+
/>
742+
);
743+
744+
expect(component.find(BarSeries)).toHaveLength(0);
745+
expect(component.find(EmptyPlaceholder).prop('icon')).toBeDefined();
746+
});
747+
724748
test('onBrushEnd returns correct context data for date histogram data', () => {
725749
const { args } = sampleArgs();
726750

@@ -957,6 +981,36 @@ describe('xy_expression', () => {
957981
expect(component.find(Settings).prop('rotation')).toEqual(90);
958982
});
959983

984+
test('it renders stacked bar empty placeholder for no results', () => {
985+
const { data, args } = sampleArgs();
986+
987+
const component = shallow(
988+
<XYChart
989+
data={data}
990+
args={{
991+
...args,
992+
layers: [
993+
{
994+
...args.layers[0],
995+
xAccessor: undefined,
996+
splitAccessor: 'e',
997+
seriesType: 'bar_stacked',
998+
},
999+
],
1000+
}}
1001+
formatFactory={getFormatSpy}
1002+
timeZone="UTC"
1003+
chartsThemeService={chartsThemeService}
1004+
histogramBarTarget={50}
1005+
onClickValue={onClickValue}
1006+
onSelectRange={onSelectRange}
1007+
/>
1008+
);
1009+
1010+
expect(component.find(BarSeries)).toHaveLength(0);
1011+
expect(component.find(EmptyPlaceholder).prop('icon')).toBeDefined();
1012+
});
1013+
9601014
test('it passes time zone to the series', () => {
9611015
const { data, args } = sampleArgs();
9621016
const component = shallow(

x-pack/plugins/lens/public/xy_visualization/expression.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -248,12 +248,17 @@ export function XYChart({
248248
const chartTheme = chartsThemeService.useChartsTheme();
249249
const chartBaseTheme = chartsThemeService.useChartsBaseTheme();
250250

251-
const filteredLayers = layers.filter(({ layerId, xAccessor, accessors }) => {
251+
const filteredLayers = layers.filter(({ layerId, xAccessor, accessors, splitAccessor }) => {
252252
return !(
253253
!accessors.length ||
254254
!data.tables[layerId] ||
255255
data.tables[layerId].rows.length === 0 ||
256-
(xAccessor && data.tables[layerId].rows.every((row) => typeof row[xAccessor] === 'undefined'))
256+
(xAccessor &&
257+
data.tables[layerId].rows.every((row) => typeof row[xAccessor] === 'undefined')) ||
258+
// stacked percentage bars have no xAccessors but splitAccessor with undefined values in them when empty
259+
(!xAccessor &&
260+
splitAccessor &&
261+
data.tables[layerId].rows.every((row) => typeof row[splitAccessor] === 'undefined'))
257262
);
258263
});
259264

0 commit comments

Comments
 (0)