mirror of
https://github.com/elastic/kibana.git
synced 2025-04-25 02:09:32 -04:00
[Lens] Fix missing formatting bug in "break down by" (#63288)
* [Lens] Fix missing formatting bug in "break down by" * Stop showing UUIDs, make logic more explicit Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
f9e8c1bfff
commit
7cd746bc2f
2 changed files with 255 additions and 64 deletions
|
@ -633,70 +633,242 @@ describe('xy_expression', () => {
|
|||
expect(component.find(BarSeries).prop('enableHistogramMode')).toEqual(false);
|
||||
});
|
||||
|
||||
test('it names the series for multiple accessors', () => {
|
||||
const { data, args } = sampleArgs();
|
||||
|
||||
const component = shallow(
|
||||
<XYChart
|
||||
data={data}
|
||||
args={args}
|
||||
formatFactory={getFormatSpy}
|
||||
timeZone="UTC"
|
||||
chartTheme={{}}
|
||||
executeTriggerActions={executeTriggerActions}
|
||||
/>
|
||||
);
|
||||
const nameFn = component.find(LineSeries).prop('name') as SeriesNameFn;
|
||||
|
||||
expect(
|
||||
nameFn(
|
||||
{
|
||||
seriesKeys: ['a', 'b', 'c', 'd'],
|
||||
key: '',
|
||||
specId: 'a',
|
||||
yAccessor: '',
|
||||
splitAccessors: new Map(),
|
||||
},
|
||||
false
|
||||
)
|
||||
).toEqual('Label A - Label B - c - Label D');
|
||||
});
|
||||
|
||||
test('it names the series for a single accessor', () => {
|
||||
const { data, args } = sampleArgs();
|
||||
|
||||
const component = shallow(
|
||||
<XYChart
|
||||
data={data}
|
||||
args={{
|
||||
...args,
|
||||
layers: [
|
||||
{
|
||||
...args.layers[0],
|
||||
accessors: ['a'],
|
||||
},
|
||||
describe('provides correct series naming', () => {
|
||||
const dataWithoutFormats: LensMultiTable = {
|
||||
type: 'lens_multitable',
|
||||
tables: {
|
||||
first: {
|
||||
type: 'kibana_datatable',
|
||||
columns: [
|
||||
{ id: 'a', name: 'a' },
|
||||
{ id: 'b', name: 'b' },
|
||||
{ id: 'c', name: 'c' },
|
||||
{ id: 'd', name: 'd' },
|
||||
],
|
||||
rows: [
|
||||
{ a: 1, b: 2, c: 'I', d: 'Row 1' },
|
||||
{ a: 1, b: 5, c: 'J', d: 'Row 2' },
|
||||
],
|
||||
}}
|
||||
formatFactory={getFormatSpy}
|
||||
timeZone="UTC"
|
||||
chartTheme={{}}
|
||||
executeTriggerActions={executeTriggerActions}
|
||||
/>
|
||||
);
|
||||
const nameFn = component.find(LineSeries).prop('name') as SeriesNameFn;
|
||||
|
||||
expect(
|
||||
nameFn(
|
||||
{
|
||||
seriesKeys: ['a', 'b', 'c', 'd'],
|
||||
key: '',
|
||||
specId: 'a',
|
||||
yAccessor: '',
|
||||
splitAccessors: new Map(),
|
||||
},
|
||||
false
|
||||
)
|
||||
).toEqual('Label A');
|
||||
},
|
||||
};
|
||||
const dataWithFormats: LensMultiTable = {
|
||||
type: 'lens_multitable',
|
||||
tables: {
|
||||
first: {
|
||||
type: 'kibana_datatable',
|
||||
columns: [
|
||||
{ id: 'a', name: 'a' },
|
||||
{ id: 'b', name: 'b' },
|
||||
{ id: 'c', name: 'c' },
|
||||
{ id: 'd', name: 'd', formatHint: { id: 'custom' } },
|
||||
],
|
||||
rows: [
|
||||
{ a: 1, b: 2, c: 'I', d: 'Row 1' },
|
||||
{ a: 1, b: 5, c: 'J', d: 'Row 2' },
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const nameFnArgs = {
|
||||
seriesKeys: [],
|
||||
key: '',
|
||||
specId: 'a',
|
||||
yAccessor: '',
|
||||
splitAccessors: new Map(),
|
||||
};
|
||||
|
||||
const getRenderedComponent = (data: LensMultiTable, args: XYArgs) => {
|
||||
return shallow(
|
||||
<XYChart
|
||||
data={data}
|
||||
args={args}
|
||||
formatFactory={getFormatSpy}
|
||||
timeZone="UTC"
|
||||
chartTheme={{}}
|
||||
executeTriggerActions={executeTriggerActions}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
test('simplest xy chart without human-readable name', () => {
|
||||
const args = createArgsWithLayers();
|
||||
const newArgs = {
|
||||
...args,
|
||||
layers: [
|
||||
{
|
||||
...args.layers[0],
|
||||
accessors: ['a'],
|
||||
splitAccessor: undefined,
|
||||
columnToLabel: '',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const component = getRenderedComponent(dataWithoutFormats, newArgs);
|
||||
const nameFn = component.find(LineSeries).prop('name') as SeriesNameFn;
|
||||
|
||||
// In this case, the ID is used as the name. This shouldn't happen in practice
|
||||
expect(nameFn({ ...nameFnArgs, seriesKeys: ['a'] }, false)).toEqual('');
|
||||
expect(nameFn({ ...nameFnArgs, seriesKeys: ['nonsense'] }, false)).toEqual('');
|
||||
});
|
||||
|
||||
test('simplest xy chart with empty name', () => {
|
||||
const args = createArgsWithLayers();
|
||||
const newArgs = {
|
||||
...args,
|
||||
layers: [
|
||||
{
|
||||
...args.layers[0],
|
||||
accessors: ['a'],
|
||||
splitAccessor: undefined,
|
||||
columnToLabel: '{"a":""}',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const component = getRenderedComponent(dataWithoutFormats, newArgs);
|
||||
const nameFn = component.find(LineSeries).prop('name') as SeriesNameFn;
|
||||
|
||||
// In this case, the ID is used as the name. This shouldn't happen in practice
|
||||
expect(nameFn({ ...nameFnArgs, seriesKeys: ['a'] }, false)).toEqual('');
|
||||
expect(nameFn({ ...nameFnArgs, seriesKeys: ['nonsense'] }, false)).toEqual('');
|
||||
});
|
||||
|
||||
test('simplest xy chart with human-readable name', () => {
|
||||
const args = createArgsWithLayers();
|
||||
const newArgs = {
|
||||
...args,
|
||||
layers: [
|
||||
{
|
||||
...args.layers[0],
|
||||
accessors: ['a'],
|
||||
splitAccessor: undefined,
|
||||
columnToLabel: '{"a":"Column A"}',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const component = getRenderedComponent(dataWithoutFormats, newArgs);
|
||||
const nameFn = component.find(LineSeries).prop('name') as SeriesNameFn;
|
||||
|
||||
expect(nameFn({ ...nameFnArgs, seriesKeys: ['a'] }, false)).toEqual('Column A');
|
||||
});
|
||||
|
||||
test('multiple y accessors', () => {
|
||||
const args = createArgsWithLayers();
|
||||
const newArgs = {
|
||||
...args,
|
||||
layers: [
|
||||
{
|
||||
...args.layers[0],
|
||||
accessors: ['a', 'b'],
|
||||
splitAccessor: undefined,
|
||||
columnToLabel: '{"a": "Label A"}',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const component = getRenderedComponent(dataWithoutFormats, newArgs);
|
||||
const nameFn = component.find(LineSeries).prop('name') as SeriesNameFn;
|
||||
|
||||
// This accessor has a human-readable name
|
||||
expect(nameFn({ ...nameFnArgs, seriesKeys: ['a'] }, false)).toEqual('Label A');
|
||||
// This accessor does not
|
||||
expect(nameFn({ ...nameFnArgs, seriesKeys: ['b'] }, false)).toEqual('');
|
||||
expect(nameFn({ ...nameFnArgs, seriesKeys: ['nonsense'] }, false)).toEqual('');
|
||||
});
|
||||
|
||||
test('split series without formatting and single y accessor', () => {
|
||||
const args = createArgsWithLayers();
|
||||
const newArgs = {
|
||||
...args,
|
||||
layers: [
|
||||
{
|
||||
...args.layers[0],
|
||||
accessors: ['a'],
|
||||
splitAccessor: 'd',
|
||||
columnToLabel: '{"a": "Label A"}',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const component = getRenderedComponent(dataWithoutFormats, newArgs);
|
||||
const nameFn = component.find(LineSeries).prop('name') as SeriesNameFn;
|
||||
|
||||
expect(nameFn({ ...nameFnArgs, seriesKeys: ['split1', 'a'] }, false)).toEqual('split1');
|
||||
});
|
||||
|
||||
test('split series with formatting and single y accessor', () => {
|
||||
const args = createArgsWithLayers();
|
||||
const newArgs = {
|
||||
...args,
|
||||
layers: [
|
||||
{
|
||||
...args.layers[0],
|
||||
accessors: ['a'],
|
||||
splitAccessor: 'd',
|
||||
columnToLabel: '{"a": "Label A"}',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const component = getRenderedComponent(dataWithFormats, newArgs);
|
||||
const nameFn = component.find(LineSeries).prop('name') as SeriesNameFn;
|
||||
|
||||
convertSpy.mockReturnValueOnce('formatted');
|
||||
expect(nameFn({ ...nameFnArgs, seriesKeys: ['split1', 'a'] }, false)).toEqual('formatted');
|
||||
expect(getFormatSpy).toHaveBeenCalledWith({ id: 'custom' });
|
||||
});
|
||||
|
||||
test('split series without formatting with multiple y accessors', () => {
|
||||
const args = createArgsWithLayers();
|
||||
const newArgs = {
|
||||
...args,
|
||||
layers: [
|
||||
{
|
||||
...args.layers[0],
|
||||
accessors: ['a', 'b'],
|
||||
splitAccessor: 'd',
|
||||
columnToLabel: '{"a": "Label A","b": "Label B"}',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const component = getRenderedComponent(dataWithoutFormats, newArgs);
|
||||
const nameFn = component.find(LineSeries).prop('name') as SeriesNameFn;
|
||||
|
||||
expect(nameFn({ ...nameFnArgs, seriesKeys: ['split1', 'b'] }, false)).toEqual(
|
||||
'split1 - Label B'
|
||||
);
|
||||
});
|
||||
|
||||
test('split series with formatting with multiple y accessors', () => {
|
||||
const args = createArgsWithLayers();
|
||||
const newArgs = {
|
||||
...args,
|
||||
layers: [
|
||||
{
|
||||
...args.layers[0],
|
||||
accessors: ['a', 'b'],
|
||||
splitAccessor: 'd',
|
||||
columnToLabel: '{"a": "Label A","b": "Label B"}',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const component = getRenderedComponent(dataWithFormats, newArgs);
|
||||
const nameFn = component.find(LineSeries).prop('name') as SeriesNameFn;
|
||||
|
||||
convertSpy.mockReturnValueOnce('formatted1').mockReturnValueOnce('formatted2');
|
||||
expect(nameFn({ ...nameFnArgs, seriesKeys: ['split1', 'a'] }, false)).toEqual(
|
||||
'formatted1 - Label A'
|
||||
);
|
||||
expect(nameFn({ ...nameFnArgs, seriesKeys: ['split1', 'b'] }, false)).toEqual(
|
||||
'formatted2 - Label B'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('it set the scale of the x axis according to the args prop', () => {
|
||||
|
|
|
@ -361,12 +361,31 @@ export function XYChart({
|
|||
enableHistogramMode: isHistogram && (seriesType.includes('stacked') || !splitAccessor),
|
||||
timeZone,
|
||||
name(d) {
|
||||
const splitHint = table.columns.find(col => col.id === splitAccessor)?.formatHint;
|
||||
|
||||
// For multiple y series, the name of the operation is used on each, either:
|
||||
// * Key - Y name
|
||||
// * Formatted value - Y name
|
||||
if (accessors.length > 1) {
|
||||
return d.seriesKeys
|
||||
.map((key: string | number) => columnToLabelMap[key] || key)
|
||||
.map((key: string | number, i) => {
|
||||
if (i === 0 && splitHint) {
|
||||
return formatFactory(splitHint).convert(key);
|
||||
}
|
||||
return splitAccessor && i === 0 ? key : columnToLabelMap[key] ?? '';
|
||||
})
|
||||
.join(' - ');
|
||||
}
|
||||
return columnToLabelMap[d.seriesKeys[0]] ?? d.seriesKeys[0];
|
||||
|
||||
// For formatted split series, format the key
|
||||
// This handles splitting by dates, for example
|
||||
if (splitHint) {
|
||||
return formatFactory(splitHint).convert(d.seriesKeys[0]);
|
||||
}
|
||||
// This handles both split and single-y cases:
|
||||
// * If split series without formatting, show the value literally
|
||||
// * If single Y, the seriesKey will be the acccessor, so we show the human-readable name
|
||||
return splitAccessor ? d.seriesKeys[0] : columnToLabelMap[d.seriesKeys[0]] ?? '';
|
||||
},
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue