Skip to content

Commit

Permalink
[Lens] Fieldless operations (#78080)
Browse files Browse the repository at this point in the history
* [Lens] Fieldless operations

* Overhaul types

* Fix invalid state and add tests

* Fix types

* Small cleanup

* Add additional error message

* Reset field selector to empty state when invalid

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
  • Loading branch information
Wylie Conlon and elasticmachine committed Sep 28, 2020
1 parent db78d70 commit 0ebaf92
Show file tree
Hide file tree
Showing 20 changed files with 569 additions and 558 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
} from '@elastic/eui';
import { EuiFormLabel } from '@elastic/eui';
import { IndexPatternColumn, OperationType } from '../indexpattern';
import { IndexPatternDimensionEditorProps, OperationFieldSupportMatrix } from './dimension_panel';
import { IndexPatternDimensionEditorProps, OperationSupportMatrix } from './dimension_panel';
import {
operationDefinitionMap,
getOperationDisplay,
Expand All @@ -36,7 +36,7 @@ const operationPanels = getOperationDisplay();

export interface DimensionEditorProps extends IndexPatternDimensionEditorProps {
selectedColumn?: IndexPatternColumn;
operationFieldSupportMatrix: OperationFieldSupportMatrix;
operationSupportMatrix: OperationSupportMatrix;
currentIndexPattern: IndexPattern;
}

Expand Down Expand Up @@ -90,22 +90,24 @@ const LabelInput = ({ value, onChange }: { value: string; onChange: (value: stri
export function DimensionEditor(props: DimensionEditorProps) {
const {
selectedColumn,
operationFieldSupportMatrix,
operationSupportMatrix,
state,
columnId,
setState,
layerId,
currentIndexPattern,
hideGrouping,
} = props;
const { operationByField, fieldByOperation } = operationFieldSupportMatrix;
const { operationByField, fieldByOperation } = operationSupportMatrix;
const [
incompatibleSelectedOperationType,
setInvalidOperationType,
] = useState<OperationType | null>(null);

const ParamEditor =
selectedColumn && operationDefinitionMap[selectedColumn.operationType].paramEditor;
const selectedOperationDefinition =
selectedColumn && operationDefinitionMap[selectedColumn.operationType];

const ParamEditor = selectedOperationDefinition?.paramEditor;

const fieldMap: Record<string, IndexPatternField> = useMemo(() => {
const fields: Record<string, IndexPatternField> = {};
Expand All @@ -129,6 +131,10 @@ export function DimensionEditor(props: DimensionEditorProps) {
[
...asOperationOptions(validOperationTypes, true),
...asOperationOptions(possibleOperationTypes, false),
...asOperationOptions(
operationSupportMatrix.operationWithoutField,
!selectedColumn || !hasField(selectedColumn)
),
],
'operationType'
);
Expand Down Expand Up @@ -166,12 +172,30 @@ export function DimensionEditor(props: DimensionEditorProps) {
compatibleWithCurrentField ? '' : ' incompatible'
}`,
onClick() {
// todo: when moving from terms agg to filters, we want to create a filter `$field.name : *`
// it probably has to be re-thought when removing the field name.
const isTermsToFilters =
selectedColumn?.operationType === 'terms' && operationType === 'filters';

if (!selectedColumn || !compatibleWithCurrentField) {
if (operationDefinitionMap[operationType].input === 'none') {
// Clear invalid state because we are creating a valid column
setInvalidOperationType(null);
if (selectedColumn?.operationType === operationType) {
return;
}
setState(
changeColumn({
state,
layerId,
columnId,
newColumn: buildColumn({
columns: props.state.layers[props.layerId].columns,
suggestedPriority: props.suggestedPriority,
layerId: props.layerId,
op: operationType,
indexPattern: currentIndexPattern,
previousColumn: selectedColumn,
}),
})
);
trackUiEvent(`indexpattern_dimension_operation_${operationType}`);
return;
} else if (!selectedColumn || !compatibleWithCurrentField) {
const possibleFields = fieldByOperation[operationType] || [];

if (possibleFields.length === 1) {
Expand All @@ -197,19 +221,20 @@ export function DimensionEditor(props: DimensionEditorProps) {
trackUiEvent(`indexpattern_dimension_operation_${operationType}`);
return;
}
if (incompatibleSelectedOperationType && !isTermsToFilters) {
setInvalidOperationType(null);
}
if (selectedColumn.operationType === operationType) {

setInvalidOperationType(null);

if (selectedColumn?.operationType === operationType) {
return;
}

const newColumn: IndexPatternColumn = buildColumn({
columns: props.state.layers[props.layerId].columns,
suggestedPriority: props.suggestedPriority,
layerId: props.layerId,
op: operationType,
indexPattern: currentIndexPattern,
field: fieldMap[selectedColumn.sourceField],
field: hasField(selectedColumn) ? fieldMap[selectedColumn.sourceField] : undefined,
previousColumn: selectedColumn,
});

Expand Down Expand Up @@ -244,93 +269,101 @@ export function DimensionEditor(props: DimensionEditorProps) {
</div>
<EuiSpacer size="s" />
<div className="lnsIndexPatternDimensionEditor__section lnsIndexPatternDimensionEditor__section--shaded">
<EuiFormRow
data-test-subj="indexPattern-field-selection-row"
label={i18n.translate('xpack.lens.indexPattern.chooseField', {
defaultMessage: 'Choose a field',
})}
fullWidth
isInvalid={Boolean(incompatibleSelectedOperationType)}
error={
selectedColumn
? i18n.translate('xpack.lens.indexPattern.invalidOperationLabel', {
defaultMessage: 'To use this function, select a different field.',
})
: undefined
}
>
<FieldSelect
currentIndexPattern={currentIndexPattern}
existingFields={state.existingFields}
fieldMap={fieldMap}
operationFieldSupportMatrix={operationFieldSupportMatrix}
selectedColumnOperationType={selectedColumn && selectedColumn.operationType}
selectedColumnSourceField={
selectedColumn && hasField(selectedColumn) ? selectedColumn.sourceField : undefined
{!selectedColumn ||
selectedOperationDefinition?.input === 'field' ||
(incompatibleSelectedOperationType &&
operationDefinitionMap[incompatibleSelectedOperationType].input === 'field') ? (
<EuiFormRow
data-test-subj="indexPattern-field-selection-row"
label={i18n.translate('xpack.lens.indexPattern.chooseField', {
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
}
incompatibleSelectedOperationType={incompatibleSelectedOperationType}
onDeleteColumn={() => {
setState(
deleteColumn({
state,
layerId,
columnId,
})
);
}}
onChoose={(choice) => {
let column: IndexPatternColumn;
if (
!incompatibleSelectedOperationType &&
selectedColumn &&
'field' in choice &&
choice.operationType === selectedColumn.operationType
) {
// If we just changed the field are not in an error state and the operation didn't change,
// we use the operations onFieldChange method to calculate the new column.
column = changeField(selectedColumn, currentIndexPattern, fieldMap[choice.field]);
} else {
// Otherwise we'll use the buildColumn method to calculate a new column
const compatibleOperations =
('field' in choice &&
operationFieldSupportMatrix.operationByField[choice.field]) ||
[];
let operation;
if (compatibleOperations.length > 0) {
operation =
incompatibleSelectedOperationType &&
compatibleOperations.includes(incompatibleSelectedOperationType)
? incompatibleSelectedOperationType
: compatibleOperations[0];
} else if ('field' in choice) {
operation = choice.operationType;
}
column = buildColumn({
columns: props.state.layers[props.layerId].columns,
field: fieldMap[choice.field],
indexPattern: currentIndexPattern,
layerId: props.layerId,
suggestedPriority: props.suggestedPriority,
op: operation as OperationType,
previousColumn: selectedColumn,
});
>
<FieldSelect
currentIndexPattern={currentIndexPattern}
existingFields={state.existingFields}
fieldMap={fieldMap}
operationSupportMatrix={operationSupportMatrix}
selectedColumnOperationType={selectedColumn && selectedColumn.operationType}
selectedColumnSourceField={
selectedColumn && hasField(selectedColumn) ? selectedColumn.sourceField : undefined
}
incompatibleSelectedOperationType={incompatibleSelectedOperationType}
onDeleteColumn={() => {
setState(
deleteColumn({
state,
layerId,
columnId,
})
);
}}
onChoose={(choice) => {
let column: IndexPatternColumn;
if (
!incompatibleSelectedOperationType &&
selectedColumn &&
'field' in choice &&
choice.operationType === selectedColumn.operationType
) {
// If we just changed the field are not in an error state and the operation didn't change,
// we use the operations onFieldChange method to calculate the new column.
column = changeField(selectedColumn, currentIndexPattern, fieldMap[choice.field]);
} else {
// Otherwise we'll use the buildColumn method to calculate a new column
const compatibleOperations =
('field' in choice && operationSupportMatrix.operationByField[choice.field]) ||
[];
let operation;
if (compatibleOperations.length > 0) {
operation =
incompatibleSelectedOperationType &&
compatibleOperations.includes(incompatibleSelectedOperationType)
? incompatibleSelectedOperationType
: compatibleOperations[0];
} else if ('field' in choice) {
operation = choice.operationType;
}
column = buildColumn({
columns: props.state.layers[props.layerId].columns,
field: fieldMap[choice.field],
indexPattern: currentIndexPattern,
layerId: props.layerId,
suggestedPriority: props.suggestedPriority,
op: operation as OperationType,
previousColumn: selectedColumn,
});
}

setState(
changeColumn({
state,
layerId,
columnId,
newColumn: column,
keepParams: false,
})
);
setInvalidOperationType(null);
}}
/>
</EuiFormRow>
setState(
changeColumn({
state,
layerId,
columnId,
newColumn: column,
keepParams: false,
})
);
setInvalidOperationType(null);
}}
/>
</EuiFormRow>
) : null}

{!incompatibleSelectedOperationType && ParamEditor && (
{!incompatibleSelectedOperationType && selectedColumn && ParamEditor && (
<>
<ParamEditor
state={state}
Expand Down
Loading

0 comments on commit 0ebaf92

Please sign in to comment.