[Lens] Handle missing fields gracefully (#78173)
This commit is contained in:
parent
cbc83003d3
commit
8d7f2d0828
|
@ -9,6 +9,7 @@ import { i18n } from '@kbn/i18n';
|
|||
import { EuiFormRow, EuiHorizontalRule, EuiRadio, EuiSelect, htmlIdGenerator } from '@elastic/eui';
|
||||
import { IndexPatternLayer, IndexPatternField } from '../types';
|
||||
import { hasField } from '../utils';
|
||||
import { IndexPatternColumn } from '../operations';
|
||||
|
||||
const generator = htmlIdGenerator('lens-nesting');
|
||||
|
||||
|
@ -21,6 +22,10 @@ function nestColumn(columnOrder: string[], outer: string, inner: string) {
|
|||
return result;
|
||||
}
|
||||
|
||||
function getFieldName(fieldMap: Record<string, IndexPatternField>, column: IndexPatternColumn) {
|
||||
return hasField(column) ? fieldMap[column.sourceField]?.displayName || column.sourceField : '';
|
||||
}
|
||||
|
||||
export function BucketNestingEditor({
|
||||
columnId,
|
||||
layer,
|
||||
|
@ -39,7 +44,7 @@ export function BucketNestingEditor({
|
|||
.map(([value, c]) => ({
|
||||
value,
|
||||
text: c.label,
|
||||
fieldName: hasField(c) ? fieldMap[c.sourceField].displayName : '',
|
||||
fieldName: getFieldName(fieldMap, c),
|
||||
operationType: c.operationType,
|
||||
}));
|
||||
|
||||
|
@ -47,7 +52,7 @@ export function BucketNestingEditor({
|
|||
return null;
|
||||
}
|
||||
|
||||
const fieldName = hasField(column) ? fieldMap[column.sourceField].displayName : '';
|
||||
const fieldName = getFieldName(fieldMap, column);
|
||||
|
||||
const prevColumn = layer.columnOrder[layer.columnOrder.indexOf(columnId) - 1];
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ import {
|
|||
} from '../operations';
|
||||
import { deleteColumn, changeColumn, updateColumnParam } from '../state_helpers';
|
||||
import { FieldSelect } from './field_select';
|
||||
import { hasField } from '../utils';
|
||||
import { hasField, fieldIsInvalid } from '../utils';
|
||||
import { BucketNestingEditor } from './bucket_nesting_editor';
|
||||
import { IndexPattern, IndexPatternField } from '../types';
|
||||
import { trackUiEvent } from '../../lens_ui_telemetry';
|
||||
|
@ -132,6 +132,15 @@ export function DimensionEditor(props: DimensionEditorProps) {
|
|||
};
|
||||
});
|
||||
|
||||
const selectedColumnSourceField =
|
||||
selectedColumn && 'sourceField' in selectedColumn ? selectedColumn.sourceField : undefined;
|
||||
|
||||
const currentFieldIsInvalid = useMemo(
|
||||
() =>
|
||||
fieldIsInvalid(selectedColumnSourceField, selectedColumn?.operationType, currentIndexPattern),
|
||||
[selectedColumnSourceField, selectedColumn?.operationType, currentIndexPattern]
|
||||
);
|
||||
|
||||
const sideNavItems: EuiListGroupItemProps[] = operationsWithCompatibility.map(
|
||||
({ operationType, compatibleWithCurrentField }) => {
|
||||
const isActive = Boolean(
|
||||
|
@ -271,20 +280,16 @@ export function DimensionEditor(props: DimensionEditorProps) {
|
|||
defaultMessage: 'Choose a field',
|
||||
})}
|
||||
fullWidth
|
||||
isInvalid={Boolean(incompatibleSelectedOperationType)}
|
||||
error={
|
||||
selectedColumn && incompatibleSelectedOperationType
|
||||
? selectedOperationDefinition?.input === 'field'
|
||||
? i18n.translate('xpack.lens.indexPattern.invalidOperationLabel', {
|
||||
defaultMessage: 'To use this function, select a different field.',
|
||||
})
|
||||
: i18n.translate('xpack.lens.indexPattern.chooseFieldLabel', {
|
||||
defaultMessage: 'To use this function, select a field.',
|
||||
})
|
||||
: undefined
|
||||
}
|
||||
isInvalid={Boolean(incompatibleSelectedOperationType || currentFieldIsInvalid)}
|
||||
error={getErrorMessage(
|
||||
selectedColumn,
|
||||
Boolean(incompatibleSelectedOperationType),
|
||||
selectedOperationDefinition?.input,
|
||||
currentFieldIsInvalid
|
||||
)}
|
||||
>
|
||||
<FieldSelect
|
||||
fieldIsInvalid={currentFieldIsInvalid}
|
||||
currentIndexPattern={currentIndexPattern}
|
||||
existingFields={state.existingFields}
|
||||
fieldMap={fieldMap}
|
||||
|
@ -355,90 +360,117 @@ export function DimensionEditor(props: DimensionEditorProps) {
|
|||
</EuiFormRow>
|
||||
) : null}
|
||||
|
||||
{!incompatibleSelectedOperationType && selectedColumn && ParamEditor && (
|
||||
<>
|
||||
<ParamEditor
|
||||
state={state}
|
||||
setState={setState}
|
||||
columnId={columnId}
|
||||
currentColumn={state.layers[layerId].columns[columnId]}
|
||||
storage={props.storage}
|
||||
uiSettings={props.uiSettings}
|
||||
savedObjectsClient={props.savedObjectsClient}
|
||||
layerId={layerId}
|
||||
http={props.http}
|
||||
dateRange={props.dateRange}
|
||||
data={props.data}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{!currentFieldIsInvalid &&
|
||||
!incompatibleSelectedOperationType &&
|
||||
selectedColumn &&
|
||||
ParamEditor && (
|
||||
<>
|
||||
<ParamEditor
|
||||
state={state}
|
||||
setState={setState}
|
||||
columnId={columnId}
|
||||
currentColumn={state.layers[layerId].columns[columnId]}
|
||||
storage={props.storage}
|
||||
uiSettings={props.uiSettings}
|
||||
savedObjectsClient={props.savedObjectsClient}
|
||||
layerId={layerId}
|
||||
http={props.http}
|
||||
dateRange={props.dateRange}
|
||||
data={props.data}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<div className="lnsIndexPatternDimensionEditor__section">
|
||||
{!incompatibleSelectedOperationType && selectedColumn && (
|
||||
<LabelInput
|
||||
value={selectedColumn.label}
|
||||
onChange={(value) => {
|
||||
setState({
|
||||
...state,
|
||||
layers: {
|
||||
...state.layers,
|
||||
[layerId]: {
|
||||
...state.layers[layerId],
|
||||
columns: {
|
||||
...state.layers[layerId].columns,
|
||||
[columnId]: {
|
||||
...selectedColumn,
|
||||
label: value,
|
||||
customLabel: true,
|
||||
{!currentFieldIsInvalid && (
|
||||
<div className="lnsIndexPatternDimensionEditor__section">
|
||||
{!incompatibleSelectedOperationType && selectedColumn && (
|
||||
<LabelInput
|
||||
value={selectedColumn.label}
|
||||
onChange={(value) => {
|
||||
setState({
|
||||
...state,
|
||||
layers: {
|
||||
...state.layers,
|
||||
[layerId]: {
|
||||
...state.layers[layerId],
|
||||
columns: {
|
||||
...state.layers[layerId].columns,
|
||||
[columnId]: {
|
||||
...selectedColumn,
|
||||
label: value,
|
||||
customLabel: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!hideGrouping && (
|
||||
<BucketNestingEditor
|
||||
fieldMap={fieldMap}
|
||||
layer={state.layers[props.layerId]}
|
||||
columnId={props.columnId}
|
||||
setColumns={(columnOrder) => {
|
||||
setState({
|
||||
...state,
|
||||
layers: {
|
||||
...state.layers,
|
||||
[props.layerId]: {
|
||||
...state.layers[props.layerId],
|
||||
columnOrder,
|
||||
{!hideGrouping && (
|
||||
<BucketNestingEditor
|
||||
fieldMap={fieldMap}
|
||||
layer={state.layers[props.layerId]}
|
||||
columnId={props.columnId}
|
||||
setColumns={(columnOrder) => {
|
||||
setState({
|
||||
...state,
|
||||
layers: {
|
||||
...state.layers,
|
||||
[props.layerId]: {
|
||||
...state.layers[props.layerId],
|
||||
columnOrder,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{selectedColumn && selectedColumn.dataType === 'number' ? (
|
||||
<FormatSelector
|
||||
selectedColumn={selectedColumn}
|
||||
onChange={(newFormat) => {
|
||||
setState(
|
||||
updateColumnParam({
|
||||
state,
|
||||
layerId,
|
||||
currentColumn: selectedColumn,
|
||||
paramName: 'format',
|
||||
value: newFormat,
|
||||
})
|
||||
);
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
{selectedColumn && selectedColumn.dataType === 'number' ? (
|
||||
<FormatSelector
|
||||
selectedColumn={selectedColumn}
|
||||
onChange={(newFormat) => {
|
||||
setState(
|
||||
updateColumnParam({
|
||||
state,
|
||||
layerId,
|
||||
currentColumn: selectedColumn,
|
||||
paramName: 'format',
|
||||
value: newFormat,
|
||||
})
|
||||
);
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
function getErrorMessage(
|
||||
selectedColumn: IndexPatternColumn | undefined,
|
||||
incompatibleSelectedOperationType: boolean,
|
||||
input: 'none' | 'field' | undefined,
|
||||
fieldInvalid: boolean
|
||||
) {
|
||||
if (selectedColumn && incompatibleSelectedOperationType) {
|
||||
if (input === 'field') {
|
||||
return i18n.translate('xpack.lens.indexPattern.invalidOperationLabel', {
|
||||
defaultMessage: 'To use this function, select a different field.',
|
||||
});
|
||||
}
|
||||
return i18n.translate('xpack.lens.indexPattern.chooseFieldLabel', {
|
||||
defaultMessage: 'To use this function, select a field.',
|
||||
});
|
||||
}
|
||||
if (fieldInvalid) {
|
||||
return i18n.translate('xpack.lens.indexPattern.invalidFieldLabel', {
|
||||
defaultMessage: 'Invalid field. Check your index pattern or pick another field.',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import { IndexPatternPrivateState } from '../types';
|
|||
import { IndexPatternColumn } from '../operations';
|
||||
import { documentField } from '../document_field';
|
||||
import { OperationMetadata } from '../../types';
|
||||
import { DateHistogramIndexPatternColumn } from '../operations/definitions/date_histogram';
|
||||
|
||||
jest.mock('../loader');
|
||||
jest.mock('../state_helpers');
|
||||
|
@ -801,6 +802,35 @@ describe('IndexPatternDimensionEditorPanel', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should render invalid field if field reference is broken', () => {
|
||||
wrapper = mount(
|
||||
<IndexPatternDimensionEditorComponent
|
||||
{...defaultProps}
|
||||
state={{
|
||||
...defaultProps.state,
|
||||
layers: {
|
||||
first: {
|
||||
...defaultProps.state.layers.first,
|
||||
columns: {
|
||||
col1: {
|
||||
...defaultProps.state.layers.first.columns.col1,
|
||||
sourceField: 'nonexistent',
|
||||
} as DateHistogramIndexPatternColumn,
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(wrapper.find(EuiComboBox).prop('selectedOptions')).toEqual([
|
||||
{
|
||||
label: 'nonexistent',
|
||||
value: { type: 'field', field: 'nonexistent' },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should support selecting the operation before the field', () => {
|
||||
wrapper = mount(<IndexPatternDimensionEditorComponent {...defaultProps} columnId={'col2'} />);
|
||||
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import React, { memo } from 'react';
|
||||
import React, { memo, useMemo } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiLink } from '@elastic/eui';
|
||||
import { EuiLink, EuiIcon, EuiToolTip, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { IUiSettingsClient, SavedObjectsClientContract, HttpSetup } from 'kibana/public';
|
||||
import { IStorageWrapper } from 'src/plugins/kibana_utils/public';
|
||||
import {
|
||||
|
@ -22,7 +22,7 @@ import { IndexPatternColumn, OperationType } from '../indexpattern';
|
|||
import { getAvailableOperationsByMetadata, buildColumn, changeField } from '../operations';
|
||||
import { DimensionEditor } from './dimension_editor';
|
||||
import { changeColumn } from '../state_helpers';
|
||||
import { isDraggedField, hasField } from '../utils';
|
||||
import { isDraggedField, hasField, fieldIsInvalid } from '../utils';
|
||||
import { IndexPatternPrivateState, IndexPatternField } from '../types';
|
||||
import { trackUiEvent } from '../../lens_ui_telemetry';
|
||||
import { DateRange } from '../../../common';
|
||||
|
@ -233,14 +233,63 @@ export const IndexPatternDimensionTriggerComponent = function IndexPatternDimens
|
|||
props: IndexPatternDimensionTriggerProps
|
||||
) {
|
||||
const layerId = props.layerId;
|
||||
const layer = props.state.layers[layerId];
|
||||
const selectedColumn: IndexPatternColumn | null = layer.columns[props.columnId] || null;
|
||||
const currentIndexPattern = props.state.indexPatterns[layer.indexPatternId];
|
||||
|
||||
const selectedColumn: IndexPatternColumn | null =
|
||||
props.state.layers[layerId].columns[props.columnId] || null;
|
||||
const selectedColumnSourceField =
|
||||
selectedColumn && 'sourceField' in selectedColumn ? selectedColumn.sourceField : undefined;
|
||||
const currentFieldIsInvalid = useMemo(
|
||||
() =>
|
||||
fieldIsInvalid(selectedColumnSourceField, selectedColumn?.operationType, currentIndexPattern),
|
||||
[selectedColumnSourceField, selectedColumn?.operationType, currentIndexPattern]
|
||||
);
|
||||
|
||||
const { columnId, uniqueLabel } = props;
|
||||
if (!selectedColumn) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (currentFieldIsInvalid) {
|
||||
return (
|
||||
<EuiToolTip
|
||||
content={
|
||||
<p>
|
||||
{i18n.translate('xpack.lens.configure.invalidConfigTooltip', {
|
||||
defaultMessage: 'Invalid configuration.',
|
||||
})}
|
||||
<br />
|
||||
{i18n.translate('xpack.lens.configure.invalidConfigTooltipClick', {
|
||||
defaultMessage: 'Click for more details.',
|
||||
})}
|
||||
</p>
|
||||
}
|
||||
anchorClassName="lnsLayerPanel__anchor"
|
||||
>
|
||||
<EuiLink
|
||||
color="danger"
|
||||
id={columnId}
|
||||
className="lnsLayerPanel__triggerLink"
|
||||
onClick={props.onClick}
|
||||
data-test-subj="lns-dimensionTrigger"
|
||||
aria-label={i18n.translate('xpack.lens.configure.editConfig', {
|
||||
defaultMessage: 'Edit configuration',
|
||||
})}
|
||||
title={i18n.translate('xpack.lens.configure.editConfig', {
|
||||
defaultMessage: 'Edit configuration',
|
||||
})}
|
||||
>
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon size="s" type="alert" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={true}>{selectedColumn.label}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiLink>
|
||||
</EuiToolTip>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiLink
|
||||
id={columnId}
|
||||
|
|
|
@ -41,6 +41,7 @@ export interface FieldSelectProps extends EuiComboBoxProps<{}> {
|
|||
onChoose: (choice: FieldChoice) => void;
|
||||
onDeleteColumn: () => void;
|
||||
existingFields: IndexPatternPrivateState['existingFields'];
|
||||
fieldIsInvalid: boolean;
|
||||
}
|
||||
|
||||
export function FieldSelect({
|
||||
|
@ -53,6 +54,7 @@ export function FieldSelect({
|
|||
onChoose,
|
||||
onDeleteColumn,
|
||||
existingFields,
|
||||
fieldIsInvalid,
|
||||
...rest
|
||||
}: FieldSelectProps) {
|
||||
const { operationByField } = operationSupportMatrix;
|
||||
|
@ -171,12 +173,14 @@ export function FieldSelect({
|
|||
defaultMessage: 'Field',
|
||||
})}
|
||||
options={(memoizedFieldOptions as unknown) as EuiComboBoxOptionOption[]}
|
||||
isInvalid={Boolean(incompatibleSelectedOperationType)}
|
||||
isInvalid={Boolean(incompatibleSelectedOperationType || fieldIsInvalid)}
|
||||
selectedOptions={
|
||||
((selectedColumnOperationType && selectedColumnSourceField
|
||||
? [
|
||||
{
|
||||
label: fieldMap[selectedColumnSourceField].displayName,
|
||||
label: fieldIsInvalid
|
||||
? selectedColumnSourceField
|
||||
: fieldMap[selectedColumnSourceField]?.displayName,
|
||||
value: { type: 'field', field: selectedColumnSourceField },
|
||||
},
|
||||
]
|
||||
|
|
|
@ -147,7 +147,7 @@ function testInitialState(): IndexPatternPrivateState {
|
|||
|
||||
// Private
|
||||
operationType: 'terms',
|
||||
sourceField: 'op',
|
||||
sourceField: 'dest',
|
||||
params: {
|
||||
size: 5,
|
||||
orderBy: { type: 'alphabetical' },
|
||||
|
@ -1115,7 +1115,7 @@ describe('IndexPattern Data Source suggestions', () => {
|
|||
|
||||
// Private
|
||||
operationType: 'terms',
|
||||
sourceField: 'op',
|
||||
sourceField: 'dest',
|
||||
params: {
|
||||
size: 5,
|
||||
orderBy: { type: 'alphabetical' },
|
||||
|
@ -1615,7 +1615,7 @@ describe('IndexPattern Data Source suggestions', () => {
|
|||
isBucketed: true,
|
||||
|
||||
operationType: 'date_histogram',
|
||||
sourceField: 'field2',
|
||||
sourceField: 'timestamp',
|
||||
params: {
|
||||
interval: 'd',
|
||||
},
|
||||
|
@ -1626,7 +1626,7 @@ describe('IndexPattern Data Source suggestions', () => {
|
|||
isBucketed: true,
|
||||
|
||||
operationType: 'terms',
|
||||
sourceField: 'field1',
|
||||
sourceField: 'dest',
|
||||
params: { size: 5, orderBy: { type: 'alphabetical' }, orderDirection: 'asc' },
|
||||
},
|
||||
id3: {
|
||||
|
@ -1635,7 +1635,7 @@ describe('IndexPattern Data Source suggestions', () => {
|
|||
isBucketed: false,
|
||||
|
||||
operationType: 'avg',
|
||||
sourceField: 'field1',
|
||||
sourceField: 'bytes',
|
||||
},
|
||||
},
|
||||
columnOrder: ['id1', 'id2', 'id3'],
|
||||
|
@ -1652,6 +1652,38 @@ describe('IndexPattern Data Source suggestions', () => {
|
|||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('does not generate suggestions if invalid fields are referenced', () => {
|
||||
const initialState = testInitialState();
|
||||
const state: IndexPatternPrivateState = {
|
||||
indexPatternRefs: [],
|
||||
existingFields: {},
|
||||
currentIndexPatternId: '1',
|
||||
indexPatterns: expectedIndexPatterns,
|
||||
isFirstExistenceFetch: false,
|
||||
layers: {
|
||||
first: {
|
||||
...initialState.layers.first,
|
||||
columns: {
|
||||
...initialState.layers.first.columns,
|
||||
col2: {
|
||||
label: 'Top 5',
|
||||
dataType: 'string',
|
||||
isBucketed: true,
|
||||
|
||||
operationType: 'terms',
|
||||
sourceField: 'nonExistingField',
|
||||
params: { size: 5, orderBy: { type: 'alphabetical' }, orderDirection: 'asc' },
|
||||
},
|
||||
},
|
||||
columnOrder: ['col1', 'col2'],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const suggestions = getDatasourceSuggestionsFromCurrentState(state);
|
||||
expect(suggestions).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ import {
|
|||
} from './operations';
|
||||
import { operationDefinitions } from './operations/definitions';
|
||||
import { TermsIndexPatternColumn } from './operations/definitions/terms';
|
||||
import { hasField } from './utils';
|
||||
import { hasField, hasInvalidReference } from './utils';
|
||||
import {
|
||||
IndexPattern,
|
||||
IndexPatternPrivateState,
|
||||
|
@ -90,6 +90,7 @@ export function getDatasourceSuggestionsForField(
|
|||
indexPatternId: string,
|
||||
field: IndexPatternField
|
||||
): IndexPatternSugestion[] {
|
||||
if (hasInvalidReference(state)) return [];
|
||||
const layers = Object.keys(state.layers);
|
||||
const layerIds = layers.filter((id) => state.layers[id].indexPatternId === indexPatternId);
|
||||
|
||||
|
@ -380,6 +381,7 @@ function createNewLayerWithMetricAggregation(
|
|||
export function getDatasourceSuggestionsFromCurrentState(
|
||||
state: IndexPatternPrivateState
|
||||
): Array<DatasourceSuggestion<IndexPatternPrivateState>> {
|
||||
if (hasInvalidReference(state)) return [];
|
||||
const layers = Object.entries(state.layers || {});
|
||||
if (layers.length > 1) {
|
||||
// Return suggestions that reduce the data to each layer individually
|
||||
|
|
|
@ -5,11 +5,13 @@
|
|||
*/
|
||||
|
||||
import { DataType } from '../types';
|
||||
import { IndexPatternPrivateState, IndexPattern } from './types';
|
||||
import { DraggedField } from './indexpattern';
|
||||
import {
|
||||
BaseIndexPatternColumn,
|
||||
FieldBasedIndexPatternColumn,
|
||||
} from './operations/definitions/column_types';
|
||||
import { operationDefinitionMap, OperationType } from './operations';
|
||||
|
||||
/**
|
||||
* Normalizes the specified operation type. (e.g. document operations
|
||||
|
@ -40,3 +42,37 @@ export function isDraggedField(fieldCandidate: unknown): fieldCandidate is Dragg
|
|||
'indexPatternId' in fieldCandidate
|
||||
);
|
||||
}
|
||||
|
||||
export function hasInvalidReference(state: IndexPatternPrivateState) {
|
||||
return Object.values(state.layers).some((layer) => {
|
||||
return layer.columnOrder.some((columnId) => {
|
||||
const column = layer.columns[columnId];
|
||||
return (
|
||||
hasField(column) &&
|
||||
fieldIsInvalid(
|
||||
column.sourceField,
|
||||
column.operationType,
|
||||
state.indexPatterns[layer.indexPatternId]
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function fieldIsInvalid(
|
||||
sourceField: string | undefined,
|
||||
operationType: OperationType | undefined,
|
||||
indexPattern: IndexPattern
|
||||
) {
|
||||
const operationDefinition = operationType && operationDefinitionMap[operationType];
|
||||
return Boolean(
|
||||
sourceField &&
|
||||
operationDefinition &&
|
||||
!indexPattern.fields.some(
|
||||
(field) =>
|
||||
field.name === sourceField &&
|
||||
operationDefinition.input === 'field' &&
|
||||
operationDefinition.getPossibleOperationForField(field) !== undefined
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue