[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>
This commit is contained in:
Marco Liberati 2020-10-07 10:36:56 +02:00 committed by GitHub
parent ae84bb2033
commit ff80d90626
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 165 additions and 12 deletions

View file

@ -179,5 +179,89 @@ describe('metric_expression', () => {
</VisualizationContainer>
`);
});
test('it renders an EmptyPlaceholder when no tables is passed as data', () => {
const { data, noAttributesArgs } = sampleArgs();
expect(
shallow(
<MetricChart
data={{ ...data, tables: {} }}
args={noAttributesArgs}
formatFactory={(x) => x as IFieldFormat}
/>
)
).toMatchInlineSnapshot(`
<EmptyPlaceholder
icon={[Function]}
/>
`);
});
test('it renders an EmptyPlaceholder when null value is passed as data', () => {
const { data, noAttributesArgs } = sampleArgs();
data.tables.l1.rows[0].a = null;
expect(
shallow(
<MetricChart
data={data}
args={noAttributesArgs}
formatFactory={(x) => x as IFieldFormat}
/>
)
).toMatchInlineSnapshot(`
<EmptyPlaceholder
icon={[Function]}
/>
`);
});
test('it renders 0 value', () => {
const { data, noAttributesArgs } = sampleArgs();
data.tables.l1.rows[0].a = 0;
expect(
shallow(
<MetricChart
data={data}
args={noAttributesArgs}
formatFactory={(x) => x as IFieldFormat}
/>
)
).toMatchInlineSnapshot(`
<VisualizationContainer
className="lnsMetricExpression__container"
reportDescription=""
reportTitle=""
>
<AutoScale>
<div
data-test-subj="lns_metric_value"
style={
Object {
"fontSize": "60pt",
"fontWeight": 600,
}
}
>
0
</div>
<div
data-test-subj="lns_metric_title"
style={
Object {
"fontSize": "24pt",
}
}
>
My fanci metric chart
</div>
</AutoScale>
</VisualizationContainer>
`);
});
});
});

View file

@ -17,6 +17,8 @@ import { MetricConfig } from './types';
import { FormatFactory, LensMultiTable } from '../types';
import { AutoScale } from './auto_scale';
import { VisualizationContainer } from '../visualization_container';
import { EmptyPlaceholder } from '../shared_components';
import { LensIconChartMetric } from '../assets/chart_metric';
export interface MetricChartProps {
data: LensMultiTable;
@ -107,7 +109,6 @@ export function MetricChart({
formatFactory,
}: MetricChartProps & { formatFactory: FormatFactory }) {
const { metricTitle, title, description, accessor, mode } = args;
let value = '-';
const firstTable = Object.values(data.tables)[0];
if (!accessor) {
return (
@ -119,17 +120,26 @@ export function MetricChart({
);
}
if (firstTable) {
const column = firstTable.columns[0];
const row = firstTable.rows[0];
if (row[accessor]) {
value =
column && column.formatHint
? formatFactory(column.formatHint).convert(row[accessor])
: Number(Number(row[accessor]).toFixed(3)).toString();
}
if (!firstTable) {
return <EmptyPlaceholder icon={LensIconChartMetric} />;
}
const column = firstTable.columns[0];
const row = firstTable.rows[0];
// NOTE: Cardinality and Sum never receives "null" as value, but always 0, even for empty dataset.
// Mind falsy values here as 0!
const shouldShowResults = row[accessor] != null;
if (!shouldShowResults) {
return <EmptyPlaceholder icon={LensIconChartMetric} />;
}
const value =
column && column.formatHint
? formatFactory(column.formatHint).convert(row[accessor])
: Number(Number(row[accessor]).toFixed(3)).toString();
return (
<VisualizationContainer
reportTitle={title}

View file

@ -35,6 +35,7 @@ import {
import { createMockExecutionContext } from '../../../../../src/plugins/expressions/common/mocks';
import { mountWithIntl } from 'test_utils/enzyme_helpers';
import { chartPluginMock } from '../../../../../src/plugins/charts/public/mocks';
import { EmptyPlaceholder } from '../shared_components/empty_placeholder';
const onClickValue = jest.fn();
const onSelectRange = jest.fn();
@ -721,6 +722,29 @@ describe('xy_expression', () => {
expect(component.find(Settings).prop('rotation')).toEqual(90);
});
test('it renders regular bar empty placeholder for no results', () => {
const { data, args } = sampleArgs();
// send empty data to the chart
data.tables.first.rows = [];
const component = shallow(
<XYChart
data={data}
args={args}
formatFactory={getFormatSpy}
timeZone="UTC"
chartsThemeService={chartsThemeService}
histogramBarTarget={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
/>
);
expect(component.find(BarSeries)).toHaveLength(0);
expect(component.find(EmptyPlaceholder).prop('icon')).toBeDefined();
});
test('onBrushEnd returns correct context data for date histogram data', () => {
const { args } = sampleArgs();
@ -957,6 +981,36 @@ describe('xy_expression', () => {
expect(component.find(Settings).prop('rotation')).toEqual(90);
});
test('it renders stacked bar empty placeholder for no results', () => {
const { data, args } = sampleArgs();
const component = shallow(
<XYChart
data={data}
args={{
...args,
layers: [
{
...args.layers[0],
xAccessor: undefined,
splitAccessor: 'e',
seriesType: 'bar_stacked',
},
],
}}
formatFactory={getFormatSpy}
timeZone="UTC"
chartsThemeService={chartsThemeService}
histogramBarTarget={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
/>
);
expect(component.find(BarSeries)).toHaveLength(0);
expect(component.find(EmptyPlaceholder).prop('icon')).toBeDefined();
});
test('it passes time zone to the series', () => {
const { data, args } = sampleArgs();
const component = shallow(

View file

@ -248,12 +248,17 @@ export function XYChart({
const chartTheme = chartsThemeService.useChartsTheme();
const chartBaseTheme = chartsThemeService.useChartsBaseTheme();
const filteredLayers = layers.filter(({ layerId, xAccessor, accessors }) => {
const filteredLayers = layers.filter(({ layerId, xAccessor, accessors, splitAccessor }) => {
return !(
!accessors.length ||
!data.tables[layerId] ||
data.tables[layerId].rows.length === 0 ||
(xAccessor && data.tables[layerId].rows.every((row) => typeof row[xAccessor] === 'undefined'))
(xAccessor &&
data.tables[layerId].rows.every((row) => typeof row[xAccessor] === 'undefined')) ||
// stacked percentage bars have no xAccessors but splitAccessor with undefined values in them when empty
(!xAccessor &&
splitAccessor &&
data.tables[layerId].rows.every((row) => typeof row[splitAccessor] === 'undefined'))
);
});