Skip to content

Commit

Permalink
[Lens] Add Multi terms support to Top Values (#118600)
Browse files Browse the repository at this point in the history
  • Loading branch information
dej611 authored Dec 8, 2021
1 parent dca141c commit 5857cfa
Show file tree
Hide file tree
Showing 52 changed files with 3,000 additions and 358 deletions.
7 changes: 7 additions & 0 deletions src/plugins/data/common/search/aggs/buckets/multi_terms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export interface AggParamsMultiTerms extends BaseAggParams {
size?: number;
otherBucket?: boolean;
otherBucketLabel?: string;
separatorLabel?: string;
}

export const getMultiTermsBucketAgg = () => {
Expand Down Expand Up @@ -83,6 +84,7 @@ export const getMultiTermsBucketAgg = () => {
params: {
otherBucketLabel: params.otherBucketLabel,
paramsPerField: formats,
separator: agg.params.separatorLabel,
},
};
},
Expand Down Expand Up @@ -142,6 +144,11 @@ export const getMultiTermsBucketAgg = () => {
shouldShow: (agg) => agg.getParam('otherBucket'),
write: noop,
},
{
name: 'separatorLabel',
type: 'string',
write: noop,
},
],
});
};
6 changes: 6 additions & 0 deletions src/plugins/data/common/search/aggs/buckets/multi_terms_fn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@ export const aggMultiTerms = (): FunctionDefinition => ({
defaultMessage: 'Represents a custom label for this aggregation',
}),
},
separatorLabel: {
types: ['string'],
help: i18n.translate('data.search.aggs.buckets.multiTerms.separatorLabel.help', {
defaultMessage: 'The separator label used to join each term combination',
}),
},
},
fn: (input, args) => {
const { id, enabled, schema, ...rest } = args;
Expand Down
32 changes: 32 additions & 0 deletions src/plugins/data/common/search/aggs/utils/get_aggs_formats.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
IFieldFormat,
SerializedFieldFormat,
} from '../../../../../field_formats/common';
import { MultiFieldKey } from '../buckets/multi_field_key';
import { getAggsFormats } from './get_aggs_formats';

const getAggFormat = (
Expand Down Expand Up @@ -119,4 +120,35 @@ describe('getAggsFormats', () => {
expect(format.convert('__missing__')).toBe(mapping.params.missingBucketLabel);
expect(getFormat).toHaveBeenCalledTimes(3);
});

test('uses a default separator for multi terms', () => {
const terms = ['source', 'geo.src', 'geo.dest'];
const mapping = {
id: 'multi_terms',
params: {
paramsPerField: Array(terms.length).fill({ id: 'terms' }),
},
};

const format = getAggFormat(mapping, getFormat);

expect(format.convert(new MultiFieldKey({ key: terms }))).toBe('source › geo.src › geo.dest');
expect(getFormat).toHaveBeenCalledTimes(terms.length);
});

test('uses a custom separator for multi terms when passed', () => {
const terms = ['source', 'geo.src', 'geo.dest'];
const mapping = {
id: 'multi_terms',
params: {
paramsPerField: Array(terms.length).fill({ id: 'terms' }),
separator: ' - ',
},
};

const format = getAggFormat(mapping, getFormat);

expect(format.convert(new MultiFieldKey({ key: terms }))).toBe('source - geo.src - geo.dest');
expect(getFormat).toHaveBeenCalledTimes(terms.length);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -143,9 +143,11 @@ export function getAggsFormats(getFieldFormat: GetFieldFormat): FieldFormatInsta
return params.otherBucketLabel;
}

const joinTemplate = params.separator ?? ' › ';

return (val as MultiFieldKey).keys
.map((valPart, i) => formats[i].convert(valPart, type))
.join(' › ');
.join(joinTemplate);
};
getConverterFor = (type: FieldFormatsContentType) => (val: string) => this.convert(val, type);
},
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/lens/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ module.exports = {
coverageDirectory: '<rootDir>/target/kibana-coverage/jest/x-pack/plugins/lens',
coverageReporters: ['text', 'html'],
collectCoverageFrom: ['<rootDir>/x-pack/plugins/lens/{common,public,server}/**/*.{ts,tsx}'],
setupFiles: ['jest-canvas-mock'],
};
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,7 @@ describe('editor_frame', () => {
datasourceId: 'testDatasource',
getOperationForColumnId: jest.fn(),
getTableSpec: jest.fn(),
getVisualDefaults: jest.fn(),
};
mockDatasource.getPublicAPI.mockReturnValue(updatedPublicAPI);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,7 @@ describe('suggestion helpers', () => {
getTableSpec: () => [{ columnId: 'col1' }],
datasourceId: '',
getOperationForColumnId: jest.fn(),
getVisualDefaults: jest.fn(),
},
},
{ activeId: 'testVis', state: {} },
Expand Down Expand Up @@ -597,6 +598,7 @@ describe('suggestion helpers', () => {
getTableSpec: () => [],
datasourceId: '',
getOperationForColumnId: jest.fn(),
getVisualDefaults: jest.fn(),
},
};
mockVisualization1.getSuggestions.mockReturnValue([]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { i18n } from '@kbn/i18n';
import type { VisualizationToolbarProps } from '../types';
import { LegendSettingsPopover, ToolbarPopover, ValueLabelsSettings } from '../shared_components';
import type { HeatmapVisualizationState } from './types';
import { getDefaultVisualValuesForLayer } from '../shared_components/datasource_default_values';

const legendOptions: Array<{ id: string; value: 'auto' | 'show' | 'hide'; label: string }> = [
{
Expand All @@ -32,9 +33,13 @@ const legendOptions: Array<{ id: string; value: 'auto' | 'show' | 'hide'; label:

export const HeatmapToolbar = memo(
(props: VisualizationToolbarProps<HeatmapVisualizationState>) => {
const { state, setState } = props;
const { state, setState, frame } = props;

const legendMode = state.legend.isVisible ? 'show' : 'hide';
const defaultTruncationValue = getDefaultVisualValuesForLayer(
state,
frame.datasourceLayers
).truncateText;

return (
<EuiFlexGroup gutterSize="m" justifyContent="spaceBetween" responsive={false}>
Expand Down Expand Up @@ -90,9 +95,9 @@ export const HeatmapToolbar = memo(
legend: { ...state.legend, maxLines: val },
});
}}
shouldTruncate={state?.legend.shouldTruncate ?? true}
shouldTruncate={state?.legend.shouldTruncate ?? defaultTruncationValue}
onTruncateLegendChange={() => {
const current = state.legend.shouldTruncate ?? true;
const current = state.legend.shouldTruncate ?? defaultTruncationValue;
setState({
...state,
legend: { ...state.legend, shouldTruncate: !current },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ describe('heatmap', () => {
position: Position.Right,
type: LEGEND_FUNCTION,
maxLines: 1,
shouldTruncate: true,
},
gridConfig: {
type: HEATMAP_GRID_FUNCTION,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ function getInitialState(): Omit<HeatmapVisualizationState, 'layerId' | 'layerTy
isVisible: true,
position: Position.Right,
maxLines: 1,
shouldTruncate: true,
type: LEGEND_FUNCTION,
},
gridConfig: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import React from 'react';
import { i18n } from '@kbn/i18n';
import { EuiFormRow, EuiSwitch, EuiSelect } from '@elastic/eui';
import { IndexPatternLayer, IndexPatternField } from '../types';
import { hasField } from '../utils';
import { hasField } from '../pure_utils';
import { GenericIndexPatternColumn } from '../operations';

function nestColumn(columnOrder: string[], outer: string, inner: string) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,15 @@ import React, { useState, useMemo, useCallback } from 'react';
import { i18n } from '@kbn/i18n';
import {
EuiListGroup,
EuiFormRow,
EuiSpacer,
EuiListGroupItemProps,
EuiFormLabel,
EuiToolTip,
EuiText,
} from '@elastic/eui';
import { IndexPatternDimensionEditorProps } from './dimension_panel';
import { OperationSupportMatrix } from './operation_support';
import { GenericIndexPatternColumn } from '../indexpattern';
import type { IndexPatternDimensionEditorProps } from './dimension_panel';
import type { OperationSupportMatrix } from './operation_support';
import type { GenericIndexPatternColumn } from '../indexpattern';
import {
operationDefinitionMap,
getOperationDisplay,
Expand All @@ -33,18 +32,18 @@ import {
DEFAULT_TIME_SCALE,
} from '../operations';
import { mergeLayer } from '../state_helpers';
import { FieldSelect } from './field_select';
import { hasField, fieldIsInvalid } from '../utils';
import { hasField } from '../pure_utils';
import { fieldIsInvalid } from '../utils';
import { BucketNestingEditor } from './bucket_nesting_editor';
import { IndexPattern, IndexPatternLayer } from '../types';
import type { IndexPattern, IndexPatternLayer } from '../types';
import { trackUiEvent } from '../../lens_ui_telemetry';
import { FormatSelector } from './format_selector';
import { ReferenceEditor } from './reference_editor';
import { setTimeScaling, TimeScaling } from './time_scaling';
import { defaultFilter, Filtering, setFilter } from './filtering';
import { AdvancedOptions } from './advanced_options';
import { setTimeShift, TimeShift } from './time_shift';
import { LayerType } from '../../../common';
import type { LayerType } from '../../../common';
import {
quickFunctionsName,
staticValueOperationName,
Expand All @@ -54,10 +53,10 @@ import {
DimensionEditorTabs,
CalloutWarning,
LabelInput,
getErrorMessage,
DimensionEditorTab,
} from './dimensions_editor_helpers';
import type { TemporaryState } from './dimensions_editor_helpers';
import { FieldInput } from './field_input';

const operationPanels = getOperationDisplay();

Expand Down Expand Up @@ -402,24 +401,14 @@ export function DimensionEditor(props: DimensionEditorProps) {
}
);

// Need to workout early on the error to decide whether to show this or an help text
const fieldErrorMessage =
((selectedOperationDefinition?.input !== 'fullReference' &&
selectedOperationDefinition?.input !== 'managedReference') ||
(incompleteOperation && operationDefinitionMap[incompleteOperation].input === 'field')) &&
getErrorMessage(
selectedColumn,
Boolean(incompleteOperation),
selectedOperationDefinition?.input,
currentFieldIsInvalid
);

const shouldDisplayExtraOptions =
!currentFieldIsInvalid &&
!incompleteInfo &&
selectedColumn &&
isQuickFunction(selectedColumn.operationType);

const FieldInputComponent = selectedOperationDefinition?.renderFieldInput || FieldInput;

const quickFunctions = (
<>
<div className="lnsIndexPatternDimensionEditor__section lnsIndexPatternDimensionEditor__section--padded lnsIndexPatternDimensionEditor__section--shaded">
Expand Down Expand Up @@ -491,69 +480,47 @@ export function DimensionEditor(props: DimensionEditorProps) {

{!selectedColumn ||
selectedOperationDefinition?.input === 'field' ||
(incompleteOperation && operationDefinitionMap[incompleteOperation].input === 'field') ||
(incompleteOperation && operationDefinitionMap[incompleteOperation]?.input === 'field') ||
temporaryQuickFunction ? (
<EuiFormRow
data-test-subj="indexPattern-field-selection-row"
label={i18n.translate('xpack.lens.indexPattern.chooseField', {
defaultMessage: 'Select a field',
})}
fullWidth
isInvalid={Boolean(incompleteOperation || currentFieldIsInvalid)}
error={fieldErrorMessage}
labelAppend={
!fieldErrorMessage &&
selectedOperationDefinition?.getHelpMessage?.({
data: props.data,
uiSettings: props.uiSettings,
currentColumn: state.layers[layerId].columns[columnId],
})
}
>
<FieldSelect
fieldIsInvalid={currentFieldIsInvalid}
currentIndexPattern={currentIndexPattern}
existingFields={state.existingFields}
operationSupportMatrix={operationSupportMatrix}
selectedOperationType={
// Allows operation to be selected before creating a valid column
selectedColumn ? selectedColumn.operationType : incompleteOperation
}
selectedField={
// Allows field to be selected
incompleteField
? incompleteField
: (selectedColumn as FieldBasedIndexPatternColumn)?.sourceField
<FieldInputComponent
layer={state.layers[layerId]}
selectedColumn={selectedColumn as FieldBasedIndexPatternColumn}
columnId={columnId}
indexPattern={currentIndexPattern}
existingFields={state.existingFields}
operationSupportMatrix={operationSupportMatrix}
updateLayer={(newLayer) => {
if (temporaryQuickFunction) {
setTemporaryState('none');
}
incompleteOperation={incompleteOperation}
onChoose={(choice) => {
if (temporaryQuickFunction) {
setTemporaryState('none');
}
setStateWrapper(
insertOrReplaceColumn({
layer: state.layers[layerId],
columnId,
indexPattern: currentIndexPattern,
op: choice.operationType,
field: currentIndexPattern.getFieldByName(choice.field),
visualizationGroups: dimensionGroups,
targetGroup: props.groupId,
incompleteParams,
}),
{ forceRender: temporaryQuickFunction }
);
}}
/>
</EuiFormRow>
setStateWrapper(newLayer, { forceRender: temporaryQuickFunction });
}}
incompleteField={incompleteField}
incompleteOperation={incompleteOperation}
incompleteParams={incompleteParams}
currentFieldIsInvalid={currentFieldIsInvalid}
helpMessage={selectedOperationDefinition?.getHelpMessage?.({
data: props.data,
uiSettings: props.uiSettings,
currentColumn: state.layers[layerId].columns[columnId],
})}
dimensionGroups={dimensionGroups}
groupId={props.groupId}
operationDefinitionMap={operationDefinitionMap}
/>
) : null}

{shouldDisplayExtraOptions && ParamEditor && (
<ParamEditor
layer={state.layers[layerId]}
layerId={layerId}
activeData={props.activeData}
updateLayer={setStateWrapper}
updateLayer={(setter) => {
if (temporaryQuickFunction) {
setTemporaryState('none');
}
setStateWrapper(setter, { forceRender: temporaryQuickFunction });
}}
columnId={columnId}
currentColumn={state.layers[layerId].columns[columnId]}
dateRange={dateRange}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
*/

import { ReactWrapper, ShallowWrapper } from 'enzyme';
import 'jest-canvas-mock';
import React from 'react';
import { act } from 'react-dom/test-utils';
import {
Expand Down
Loading

0 comments on commit 5857cfa

Please sign in to comment.