[Lens] Prevent editor crash on histograms datatype mix (#98453)

This commit is contained in:
Marco Liberati 2021-04-27 14:31:04 +02:00 committed by GitHub
parent 61d57370fa
commit 088212b8db
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 109 additions and 2 deletions

View file

@ -818,6 +818,60 @@ describe('xy_visualization', () => {
},
]);
});
it('should return an error if two incompatible xAccessors (multiple layers) are used', () => {
// current incompatibility is only for date and numeric histograms as xAccessors
const datasourceLayers = {
first: mockDatasource.publicAPIMock,
second: createMockDatasource('testDatasource').publicAPIMock,
};
datasourceLayers.first.getOperationForColumnId = jest.fn((id: string) =>
id === 'a'
? (({
dataType: 'date',
scale: 'interval',
} as unknown) as Operation)
: null
);
datasourceLayers.second.getOperationForColumnId = jest.fn((id: string) =>
id === 'e'
? (({
dataType: 'number',
scale: 'interval',
} as unknown) as Operation)
: null
);
expect(
xyVisualization.getErrorMessages(
{
...exampleState(),
layers: [
{
layerId: 'first',
seriesType: 'area',
splitAccessor: 'd',
xAccessor: 'a',
accessors: ['b'],
},
{
layerId: 'second',
seriesType: 'area',
splitAccessor: 'd',
xAccessor: 'e',
accessors: ['b'],
},
],
},
datasourceLayers
)
).toEqual([
{
shortMessage: 'Wrong data type for Horizontal axis.',
longMessage:
'Data type mismatch for the Horizontal axis. Cannot mix date and number interval types.',
},
]);
});
});
describe('#getWarningMessages', () => {

View file

@ -15,8 +15,14 @@ import { PaletteRegistry } from 'src/plugins/charts/public';
import { DataPublicPluginStart } from 'src/plugins/data/public';
import { getSuggestions } from './xy_suggestions';
import { LayerContextMenu, XyToolbar, DimensionEditor } from './xy_config_panel';
import { Visualization, OperationMetadata, VisualizationType, AccessorConfig } from '../types';
import { State, SeriesType, visualizationTypes, XYLayerConfig } from './types';
import {
Visualization,
OperationMetadata,
VisualizationType,
AccessorConfig,
DatasourcePublicAPI,
} from '../types';
import { State, SeriesType, visualizationTypes, XYLayerConfig, XYState } from './types';
import { isHorizontalChart } from './state_helpers';
import { toExpression, toPreviewExpression, getSortedAccessors } from './to_expression';
import { LensIconChartBarStacked } from '../assets/chart_bar_stacked';
@ -374,6 +380,9 @@ export const getXyVisualization = ({
}
if (datasourceLayers && state) {
// temporary fix for #87068
errors.push(...checkXAccessorCompatibility(state, datasourceLayers));
for (const layer of state.layers) {
const datasourceAPI = datasourceLayers[layer.layerId];
if (datasourceAPI) {
@ -517,3 +526,47 @@ function newLayerState(seriesType: SeriesType, layerId: string): XYLayerConfig {
accessors: [],
};
}
// min requirement for the bug:
// * 2 or more layers
// * at least one with date histogram
// * at least one with interval function
function checkXAccessorCompatibility(
state: XYState,
datasourceLayers: Record<string, DatasourcePublicAPI>
) {
const errors = [];
const hasDateHistogramSet = state.layers.some(checkIntervalOperation('date', datasourceLayers));
const hasNumberHistogram = state.layers.some(checkIntervalOperation('number', datasourceLayers));
if (state.layers.length > 1 && hasDateHistogramSet && hasNumberHistogram) {
errors.push({
shortMessage: i18n.translate('xpack.lens.xyVisualization.dataTypeFailureXShort', {
defaultMessage: `Wrong data type for {axis}.`,
values: {
axis: getAxisName('x', { isHorizontal: isHorizontalChart(state.layers) }),
},
}),
longMessage: i18n.translate('xpack.lens.xyVisualization.dataTypeFailureXLong', {
defaultMessage: `Data type mismatch for the {axis}. Cannot mix date and number interval types.`,
values: {
axis: getAxisName('x', { isHorizontal: isHorizontalChart(state.layers) }),
},
}),
});
}
return errors;
}
function checkIntervalOperation(
dataType: 'date' | 'number',
datasourceLayers: Record<string, DatasourcePublicAPI>
) {
return (layer: XYLayerConfig) => {
const datasourceAPI = datasourceLayers[layer.layerId];
if (!layer.xAccessor) {
return false;
}
const operation = datasourceAPI?.getOperationForColumnId(layer.xAccessor);
return Boolean(operation?.dataType === dataType && operation.scale === 'interval');
};
}