[Lens] Don't assign dimension until it becomes valid (#84972) (#85244)

Co-authored-by: Wylie Conlon <william.conlon@elastic.co>
This commit is contained in:
Joe Reuter 2020-12-08 13:22:58 +01:00 committed by GitHub
parent f4b79d327c
commit b7a874c7b0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 433 additions and 329 deletions

View file

@ -274,6 +274,69 @@ describe('LayerPanel', () => {
expect(component.find('EuiFlyoutHeader').exists()).toBe(true);
});
it('should not update the visualization if the datasource is incomplete', () => {
(generateId as jest.Mock).mockReturnValueOnce(`newid`);
const updateAll = jest.fn();
const updateDatasource = jest.fn();
mockVisualization.getConfiguration.mockReturnValue({
groups: [
{
groupLabel: 'A',
groupId: 'a',
accessors: [],
filterOperations: () => true,
supportsMoreColumns: true,
dataTestSubj: 'lnsGroup',
},
],
});
const component = mountWithIntl(
<LayerPanel
{...getDefaultProps()}
updateDatasource={updateDatasource}
updateAll={updateAll}
/>
);
act(() => {
component.find('[data-test-subj="lns-empty-dimension"]').first().simulate('click');
});
component.update();
expect(mockDatasource.renderDimensionEditor).toHaveBeenCalledWith(
expect.any(Element),
expect.objectContaining({ columnId: 'newid' })
);
const stateFn =
mockDatasource.renderDimensionEditor.mock.calls[
mockDatasource.renderDimensionEditor.mock.calls.length - 1
][1].setState;
act(() => {
stateFn({
indexPatternId: '1',
columns: {},
columnOrder: [],
incompleteColumns: { newId: { operationType: 'count' } },
});
});
expect(updateAll).not.toHaveBeenCalled();
act(() => {
stateFn(
{
indexPatternId: '1',
columns: {},
columnOrder: [],
},
true
);
});
expect(updateAll).toHaveBeenCalled();
});
it('should close the DimensionContainer when the active visualization changes', () => {
/**
* The ID generation system for new dimensions has been messy before, so

View file

@ -503,17 +503,21 @@ export function LayerPanel(
columnId: activeId,
filterOperations: activeGroup.filterOperations,
dimensionGroups: groups,
setState: (newState: unknown) => {
props.updateAll(
datasourceId,
newState,
activeVisualization.setDimension({
layerId,
groupId: activeGroup.groupId,
columnId: activeId,
prevState: props.visualizationState,
})
);
setState: (newState: unknown, shouldUpdateVisualization?: boolean) => {
if (shouldUpdateVisualization) {
props.updateAll(
datasourceId,
newState,
activeVisualization.setDimension({
layerId,
groupId: activeGroup.groupId,
columnId: activeId,
prevState: props.visualizationState,
})
);
} else {
props.updateDatasource(datasourceId, newState);
}
setActiveDimension({
...activeDimension,
isNew: false,

View file

@ -110,6 +110,10 @@ export function DimensionEditor(props: DimensionEditorProps) {
} = props;
const { fieldByOperation, operationWithoutField } = operationSupportMatrix;
const setStateWrapper = (layer: IndexPatternLayer) => {
setState(mergeLayer({ state, layerId, newLayer: layer }), Boolean(layer.columns[columnId]));
};
const selectedOperationDefinition =
selectedColumn && operationDefinitionMap[selectedColumn.operationType];
@ -194,13 +198,7 @@ export function DimensionEditor(props: DimensionEditorProps) {
if (selectedColumn?.operationType === operationType) {
// Clear invalid state because we are reseting to a valid column
if (incompleteInfo) {
setState(
mergeLayer({
state,
layerId,
newLayer: resetIncomplete(state.layers[layerId], columnId),
})
);
setStateWrapper(resetIncomplete(state.layers[layerId], columnId));
}
return;
}
@ -210,38 +208,30 @@ export function DimensionEditor(props: DimensionEditorProps) {
columnId,
op: operationType,
});
setState(mergeLayer({ state, layerId, newLayer }));
setStateWrapper(newLayer);
trackUiEvent(`indexpattern_dimension_operation_${operationType}`);
return;
} else if (!selectedColumn || !compatibleWithCurrentField) {
const possibleFields = fieldByOperation[operationType] || new Set();
if (possibleFields.size === 1) {
setState(
mergeLayer({
state,
layerId,
newLayer: insertOrReplaceColumn({
layer: props.state.layers[props.layerId],
indexPattern: currentIndexPattern,
columnId,
op: operationType,
field: currentIndexPattern.getFieldByName(possibleFields.values().next().value),
}),
setStateWrapper(
insertOrReplaceColumn({
layer: props.state.layers[props.layerId],
indexPattern: currentIndexPattern,
columnId,
op: operationType,
field: currentIndexPattern.getFieldByName(possibleFields.values().next().value),
})
);
} else {
setState(
mergeLayer({
state,
layerId,
newLayer: insertOrReplaceColumn({
layer: props.state.layers[props.layerId],
indexPattern: currentIndexPattern,
columnId,
op: operationType,
field: undefined,
}),
setStateWrapper(
insertOrReplaceColumn({
layer: props.state.layers[props.layerId],
indexPattern: currentIndexPattern,
columnId,
op: operationType,
field: undefined,
})
);
}
@ -251,13 +241,7 @@ export function DimensionEditor(props: DimensionEditorProps) {
if (selectedColumn.operationType === operationType) {
if (incompleteInfo) {
setState(
mergeLayer({
state,
layerId,
newLayer: resetIncomplete(state.layers[layerId], columnId),
})
);
setStateWrapper(resetIncomplete(state.layers[layerId], columnId));
}
return;
}
@ -271,7 +255,7 @@ export function DimensionEditor(props: DimensionEditorProps) {
? currentIndexPattern.getFieldByName(selectedColumn.sourceField)
: undefined,
});
setState(mergeLayer({ state, layerId, newLayer }));
setStateWrapper(newLayer);
},
};
}
@ -333,26 +317,16 @@ export function DimensionEditor(props: DimensionEditorProps) {
}
incompleteOperation={incompleteOperation}
onDeleteColumn={() => {
setState(
mergeLayer({
state,
layerId,
newLayer: deleteColumn({ layer: state.layers[layerId], columnId }),
})
);
setStateWrapper(deleteColumn({ layer: state.layers[layerId], columnId }));
}}
onChoose={(choice) => {
setState(
mergeLayer({
state,
layerId,
newLayer: insertOrReplaceColumn({
layer: state.layers[layerId],
columnId,
indexPattern: currentIndexPattern,
op: choice.operationType,
field: currentIndexPattern.getFieldByName(choice.field),
}),
setStateWrapper(
insertOrReplaceColumn({
layer: state.layers[layerId],
columnId,
indexPattern: currentIndexPattern,
op: choice.operationType,
field: currentIndexPattern.getFieldByName(choice.field),
})
);
}}
@ -365,9 +339,7 @@ export function DimensionEditor(props: DimensionEditorProps) {
selectedColumn={selectedColumn}
columnId={columnId}
layer={state.layers[layerId]}
updateLayer={(newLayer: IndexPatternLayer) =>
setState(mergeLayer({ layerId, state, newLayer }))
}
updateLayer={setStateWrapper}
/>
)}

View file

@ -367,23 +367,26 @@ describe('IndexPatternDimensionEditorPanel', () => {
comboBox.prop('onChange')!([option]);
});
expect(setState).toHaveBeenCalledWith({
...initialState,
layers: {
first: {
...state.layers.first,
columns: {
...state.layers.first.columns,
col1: expect.objectContaining({
operationType: 'max',
sourceField: 'memory',
params: { format: { id: 'bytes' } },
// Other parts of this don't matter for this test
}),
expect(setState).toHaveBeenCalledWith(
{
...initialState,
layers: {
first: {
...state.layers.first,
columns: {
...state.layers.first.columns,
col1: expect.objectContaining({
operationType: 'max',
sourceField: 'memory',
params: { format: { id: 'bytes' } },
// Other parts of this don't matter for this test
}),
},
},
},
},
});
true
);
});
it('should switch operations when selecting a field that requires another operation', () => {
@ -398,22 +401,25 @@ describe('IndexPatternDimensionEditorPanel', () => {
comboBox.prop('onChange')!([option]);
});
expect(setState).toHaveBeenCalledWith({
...state,
layers: {
first: {
...state.layers.first,
columns: {
...state.layers.first.columns,
col1: expect.objectContaining({
operationType: 'terms',
sourceField: 'source',
// Other parts of this don't matter for this test
}),
expect(setState).toHaveBeenCalledWith(
{
...state,
layers: {
first: {
...state.layers.first,
columns: {
...state.layers.first.columns,
col1: expect.objectContaining({
operationType: 'terms',
sourceField: 'source',
// Other parts of this don't matter for this test
}),
},
},
},
},
});
true
);
});
it('should keep the field when switching to another operation compatible for this field', () => {
@ -428,23 +434,26 @@ describe('IndexPatternDimensionEditorPanel', () => {
wrapper.find('button[data-test-subj="lns-indexPatternDimension-min"]').simulate('click');
});
expect(setState).toHaveBeenCalledWith({
...state,
layers: {
first: {
...state.layers.first,
columns: {
...state.layers.first.columns,
col1: expect.objectContaining({
operationType: 'min',
sourceField: 'bytes',
params: { format: { id: 'bytes' } },
// Other parts of this don't matter for this test
}),
expect(setState).toHaveBeenCalledWith(
{
...state,
layers: {
first: {
...state.layers.first,
columns: {
...state.layers.first.columns,
col1: expect.objectContaining({
operationType: 'min',
sourceField: 'bytes',
params: { format: { id: 'bytes' } },
// Other parts of this don't matter for this test
}),
},
},
},
},
});
true
);
});
it('should not set the state if selecting the currently active operation', () => {
@ -498,20 +507,23 @@ describe('IndexPatternDimensionEditorPanel', () => {
wrapper.find('button[data-test-subj="lns-indexPatternDimension-min"]').simulate('click');
});
expect(setState).toHaveBeenCalledWith({
...state,
layers: {
first: {
...state.layers.first,
columns: {
...state.layers.first.columns,
col1: expect.objectContaining({
label: 'Minimum of bytes',
}),
expect(setState).toHaveBeenCalledWith(
{
...state,
layers: {
first: {
...state.layers.first,
columns: {
...state.layers.first.columns,
col1: expect.objectContaining({
label: 'Minimum of bytes',
}),
},
},
},
},
});
true
);
});
it('should keep the label on operation change if it is custom', () => {
@ -532,21 +544,24 @@ describe('IndexPatternDimensionEditorPanel', () => {
wrapper.find('button[data-test-subj="lns-indexPatternDimension-min"]').simulate('click');
});
expect(setState).toHaveBeenCalledWith({
...state,
layers: {
first: {
...state.layers.first,
columns: {
...state.layers.first.columns,
col1: expect.objectContaining({
label: 'Custom label',
customLabel: true,
}),
expect(setState).toHaveBeenCalledWith(
{
...state,
layers: {
first: {
...state.layers.first,
columns: {
...state.layers.first.columns,
col1: expect.objectContaining({
label: 'Custom label',
customLabel: true,
}),
},
},
},
},
});
true
);
});
describe('transient invalid state', () => {
@ -559,20 +574,23 @@ describe('IndexPatternDimensionEditorPanel', () => {
.simulate('click');
});
expect(setState).toHaveBeenCalledWith({
...state,
layers: {
first: {
...state.layers.first,
columns: {
...state.layers.first.columns,
},
incompleteColumns: {
col1: { operationType: 'terms' },
expect(setState).toHaveBeenCalledWith(
{
...state,
layers: {
first: {
...state.layers.first,
columns: {
...state.layers.first.columns,
},
incompleteColumns: {
col1: { operationType: 'terms' },
},
},
},
},
});
true
);
});
it('should show error message in invalid state', () => {
@ -681,17 +699,20 @@ describe('IndexPatternDimensionEditorPanel', () => {
wrapper = mount(<IndexPatternDimensionEditorComponent {...defaultProps} columnId={'col2'} />);
wrapper.find('button[data-test-subj="lns-indexPatternDimension-avg"]').simulate('click');
expect(setState).toHaveBeenCalledWith({
...state,
layers: {
first: {
...state.layers.first,
incompleteColumns: {
col2: { operationType: 'avg' },
expect(setState).toHaveBeenCalledWith(
{
...state,
layers: {
first: {
...state.layers.first,
incompleteColumns: {
col2: { operationType: 'avg' },
},
},
},
},
});
false
);
const comboBox = wrapper
.find(EuiComboBox)
@ -703,23 +724,26 @@ describe('IndexPatternDimensionEditorPanel', () => {
comboBox.prop('onChange')!([options![1].options![2]]);
});
expect(setState).toHaveBeenLastCalledWith({
...state,
layers: {
first: {
...state.layers.first,
columns: {
...state.layers.first.columns,
col2: expect.objectContaining({
sourceField: 'source',
operationType: 'terms',
// Other parts of this don't matter for this test
}),
expect(setState).toHaveBeenLastCalledWith(
{
...state,
layers: {
first: {
...state.layers.first,
columns: {
...state.layers.first.columns,
col2: expect.objectContaining({
sourceField: 'source',
operationType: 'terms',
// Other parts of this don't matter for this test
}),
},
columnOrder: ['col2', 'col1'],
},
columnOrder: ['col2', 'col1'],
},
},
});
true
);
});
it('should select the Records field when count is selected', () => {
@ -800,21 +824,24 @@ describe('IndexPatternDimensionEditorPanel', () => {
comboBox.prop('onChange')!([option]);
});
expect(setState).toHaveBeenLastCalledWith({
...state,
layers: {
first: {
...state.layers.first,
columns: {
...state.layers.first.columns,
col1: expect.objectContaining({
sourceField: 'source',
operationType: 'terms',
}),
expect(setState).toHaveBeenLastCalledWith(
{
...state,
layers: {
first: {
...state.layers.first,
columns: {
...state.layers.first.columns,
col1: expect.objectContaining({
sourceField: 'source',
operationType: 'terms',
}),
},
},
},
},
});
true
);
});
});
@ -887,21 +914,24 @@ describe('IndexPatternDimensionEditorPanel', () => {
.dive()
.find('[data-test-subj="indexPattern-time-scaling-enable"]')
.prop('onClick')!({} as MouseEvent);
expect(props.setState).toHaveBeenCalledWith({
...props.state,
layers: {
first: {
...props.state.layers.first,
columns: {
...props.state.layers.first.columns,
col2: expect.objectContaining({
timeScale: 's',
label: 'Count of records per second',
}),
expect(props.setState).toHaveBeenCalledWith(
{
...props.state,
layers: {
first: {
...props.state.layers.first,
columns: {
...props.state.layers.first.columns,
col2: expect.objectContaining({
timeScale: 's',
label: 'Count of records per second',
}),
},
},
},
},
});
true
);
});
it('should carry over time scaling to other operation if possible', () => {
@ -915,21 +945,24 @@ describe('IndexPatternDimensionEditorPanel', () => {
wrapper
.find('button[data-test-subj="lns-indexPatternDimension-count incompatible"]')
.simulate('click');
expect(props.setState).toHaveBeenCalledWith({
...props.state,
layers: {
first: {
...props.state.layers.first,
columns: {
...props.state.layers.first.columns,
col2: expect.objectContaining({
timeScale: 'h',
label: 'Count of records per hour',
}),
expect(props.setState).toHaveBeenCalledWith(
{
...props.state,
layers: {
first: {
...props.state.layers.first,
columns: {
...props.state.layers.first.columns,
col2: expect.objectContaining({
timeScale: 'h',
label: 'Count of records per hour',
}),
},
},
},
},
});
true
);
});
it('should not carry over time scaling if the other operation does not support it', () => {
@ -941,21 +974,24 @@ describe('IndexPatternDimensionEditorPanel', () => {
});
wrapper = mount(<IndexPatternDimensionEditorComponent {...props} />);
wrapper.find('button[data-test-subj="lns-indexPatternDimension-avg"]').simulate('click');
expect(props.setState).toHaveBeenCalledWith({
...props.state,
layers: {
first: {
...props.state.layers.first,
columns: {
...props.state.layers.first.columns,
col2: expect.objectContaining({
timeScale: undefined,
label: 'Average of bytes',
}),
expect(props.setState).toHaveBeenCalledWith(
{
...props.state,
layers: {
first: {
...props.state.layers.first,
columns: {
...props.state.layers.first.columns,
col2: expect.objectContaining({
timeScale: undefined,
label: 'Average of bytes',
}),
},
},
},
},
});
true
);
});
it('should allow to change time scaling', () => {
@ -967,21 +1003,24 @@ describe('IndexPatternDimensionEditorPanel', () => {
.prop('onChange')!(({
target: { value: 'h' },
} as unknown) as ChangeEvent<HTMLSelectElement>);
expect(props.setState).toHaveBeenCalledWith({
...props.state,
layers: {
first: {
...props.state.layers.first,
columns: {
...props.state.layers.first.columns,
col2: expect.objectContaining({
timeScale: 'h',
label: 'Count of records per hour',
}),
expect(props.setState).toHaveBeenCalledWith(
{
...props.state,
layers: {
first: {
...props.state.layers.first,
columns: {
...props.state.layers.first.columns,
col2: expect.objectContaining({
timeScale: 'h',
label: 'Count of records per hour',
}),
},
},
},
},
});
true
);
});
it('should not adjust label if it is custom', () => {
@ -993,21 +1032,24 @@ describe('IndexPatternDimensionEditorPanel', () => {
.prop('onChange')!(({
target: { value: 'h' },
} as unknown) as ChangeEvent<HTMLSelectElement>);
expect(props.setState).toHaveBeenCalledWith({
...props.state,
layers: {
first: {
...props.state.layers.first,
columns: {
...props.state.layers.first.columns,
col2: expect.objectContaining({
timeScale: 'h',
label: 'My label',
}),
expect(props.setState).toHaveBeenCalledWith(
{
...props.state,
layers: {
first: {
...props.state.layers.first,
columns: {
...props.state.layers.first.columns,
col2: expect.objectContaining({
timeScale: 'h',
label: 'My label',
}),
},
},
},
},
});
true
);
});
it('should allow to remove time scaling', () => {
@ -1020,21 +1062,24 @@ describe('IndexPatternDimensionEditorPanel', () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
{} as any
);
expect(props.setState).toHaveBeenCalledWith({
...props.state,
layers: {
first: {
...props.state.layers.first,
columns: {
...props.state.layers.first.columns,
col2: expect.objectContaining({
timeScale: undefined,
label: 'Count of records',
}),
expect(props.setState).toHaveBeenCalledWith(
{
...props.state,
layers: {
first: {
...props.state.layers.first,
columns: {
...props.state.layers.first.columns,
col2: expect.objectContaining({
timeScale: undefined,
label: 'Count of records',
}),
},
},
},
},
});
true
);
});
});
@ -1072,19 +1117,22 @@ describe('IndexPatternDimensionEditorPanel', () => {
wrapper.find('button[data-test-subj="lns-indexPatternDimension-avg"]').simulate('click');
expect(setState).toHaveBeenCalledWith({
...state,
layers: {
first: {
...state.layers.first,
incompleteColumns: {
col2: {
operationType: 'avg',
expect(setState).toHaveBeenCalledWith(
{
...state,
layers: {
first: {
...state.layers.first,
incompleteColumns: {
col2: {
operationType: 'avg',
},
},
},
},
},
});
false
);
const comboBox = wrapper
.find(EuiComboBox)
@ -1095,23 +1143,26 @@ describe('IndexPatternDimensionEditorPanel', () => {
comboBox.prop('onChange')!([options![1].options![0]]);
});
expect(setState).toHaveBeenCalledWith({
...state,
layers: {
first: {
...state.layers.first,
columns: {
...state.layers.first.columns,
col2: expect.objectContaining({
sourceField: 'bytes',
operationType: 'avg',
// Other parts of this don't matter for this test
}),
expect(setState).toHaveBeenCalledWith(
{
...state,
layers: {
first: {
...state.layers.first,
columns: {
...state.layers.first.columns,
col2: expect.objectContaining({
sourceField: 'bytes',
operationType: 'avg',
// Other parts of this don't matter for this test
}),
},
columnOrder: ['col1', 'col2'],
},
columnOrder: ['col1', 'col2'],
},
},
});
true
);
});
it('should select operation directly if only one field is possible', () => {
@ -1135,23 +1186,26 @@ describe('IndexPatternDimensionEditorPanel', () => {
wrapper.find('button[data-test-subj="lns-indexPatternDimension-avg"]').simulate('click');
expect(setState).toHaveBeenCalledWith({
...initialState,
layers: {
first: {
...initialState.layers.first,
columns: {
...initialState.layers.first.columns,
col2: expect.objectContaining({
sourceField: 'bytes',
operationType: 'avg',
// Other parts of this don't matter for this test
}),
expect(setState).toHaveBeenCalledWith(
{
...initialState,
layers: {
first: {
...initialState.layers.first,
columns: {
...initialState.layers.first.columns,
col2: expect.objectContaining({
sourceField: 'bytes',
operationType: 'avg',
// Other parts of this don't matter for this test
}),
},
columnOrder: ['col1', 'col2'],
},
columnOrder: ['col1', 'col2'],
},
},
});
true
);
});
it('should select operation directly if only document is possible', () => {
@ -1159,22 +1213,25 @@ describe('IndexPatternDimensionEditorPanel', () => {
wrapper.find('button[data-test-subj="lns-indexPatternDimension-count"]').simulate('click');
expect(setState).toHaveBeenCalledWith({
...state,
layers: {
first: {
...state.layers.first,
columns: {
...state.layers.first.columns,
col2: expect.objectContaining({
operationType: 'count',
// Other parts of this don't matter for this test
}),
expect(setState).toHaveBeenCalledWith(
{
...state,
layers: {
first: {
...state.layers.first,
columns: {
...state.layers.first.columns,
col2: expect.objectContaining({
operationType: 'count',
// Other parts of this don't matter for this test
}),
},
columnOrder: ['col1', 'col2'],
},
columnOrder: ['col1', 'col2'],
},
},
});
true
);
});
it('should indicate compatible fields when selecting the operation first', () => {
@ -1284,23 +1341,26 @@ describe('IndexPatternDimensionEditorPanel', () => {
comboBox.prop('onChange')!([option]);
});
expect(setState).toHaveBeenCalledWith({
...state,
layers: {
first: {
...state.layers.first,
columns: {
...state.layers.first.columns,
col2: expect.objectContaining({
operationType: 'range',
sourceField: 'bytes',
// Other parts of this don't matter for this test
}),
expect(setState).toHaveBeenCalledWith(
{
...state,
layers: {
first: {
...state.layers.first,
columns: {
...state.layers.first.columns,
col2: expect.objectContaining({
operationType: 'range',
sourceField: 'bytes',
// Other parts of this don't matter for this test
}),
},
columnOrder: ['col1', 'col2'],
},
columnOrder: ['col1', 'col2'],
},
},
});
true
);
});
it('should use helper function when changing the function', () => {
@ -1336,17 +1396,20 @@ describe('IndexPatternDimensionEditorPanel', () => {
.prop('onChange')!([]);
});
expect(setState).toHaveBeenCalledWith({
...state,
layers: {
first: {
indexPatternId: '1',
columns: {},
columnOrder: [],
incompleteColumns: {},
expect(setState).toHaveBeenCalledWith(
{
...state,
layers: {
first: {
indexPatternId: '1',
columns: {},
columnOrder: [],
incompleteColumns: {},
},
},
},
});
false
);
});
it('allows custom format', () => {

View file

@ -53,7 +53,7 @@ export const IndexPatternDimensionTriggerComponent = function IndexPatternDimens
[layer, columnId, currentIndexPattern]
);
const selectedColumn: IndexPatternColumn | null = layer.columns[props.columnId] || null;
const selectedColumn: IndexPatternColumn | null = layer.columns[props.columnId] ?? null;
if (!selectedColumn) {
return null;

View file

@ -35,7 +35,7 @@ import {
} from './calculations';
import { countOperation, CountIndexPatternColumn } from './count';
import { lastValueOperation, LastValueIndexPatternColumn } from './last_value';
import { StateSetter, OperationMetadata } from '../../../types';
import { OperationMetadata } from '../../../types';
import type { BaseIndexPatternColumn, ReferenceBasedIndexPatternColumn } from './column_types';
import {
IndexPatternPrivateState,
@ -116,7 +116,7 @@ export {
export interface ParamEditorProps<C> {
currentColumn: C;
state: IndexPatternPrivateState;
setState: StateSetter<IndexPatternPrivateState>;
setState: (newState: IndexPatternPrivateState) => void;
columnId: string;
layerId: string;
uiSettings: IUiSettingsClient;

View file

@ -72,7 +72,8 @@ export function isColumnInvalid(
columnId: string,
indexPattern: IndexPattern
) {
const column = layer.columns[columnId];
const column: IndexPatternColumn | undefined = layer.columns[columnId];
if (!column) return;
const operationDefinition = column.operationType && operationDefinitionMap[column.operationType];
return !!(

View file

@ -240,7 +240,8 @@ export type DatasourceDimensionProps<T> = SharedDimensionProps & {
// The only way a visualization has to restrict the query building
export type DatasourceDimensionEditorProps<T = unknown> = DatasourceDimensionProps<T> & {
setState: StateSetter<T>;
// Not a StateSetter because we have this unique use case of determining valid columns
setState: (newState: Parameters<StateSetter<T>>[0], publishToVisualization?: boolean) => void;
core: Pick<CoreSetup, 'http' | 'notifications' | 'uiSettings'>;
dateRange: DateRange;
dimensionGroups: VisualizationDimensionGroupConfig[];