mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Lens] Performance refactoring for indexpattern fast lookup and Operation support matrix computation (#82829)
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
6003cadce4
commit
0b99841310
30 changed files with 995 additions and 791 deletions
|
@ -18,6 +18,131 @@ import { ChangeIndexPattern } from './change_indexpattern';
|
|||
import { EuiProgress, EuiLoadingSpinner } from '@elastic/eui';
|
||||
import { documentField } from './document_field';
|
||||
import { chartPluginMock } from '../../../../../src/plugins/charts/public/mocks';
|
||||
import { getFieldByNameFactory } from './pure_helpers';
|
||||
|
||||
const fieldsOne = [
|
||||
{
|
||||
name: 'timestamp',
|
||||
displayName: 'timestampLabel',
|
||||
type: 'date',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'bytes',
|
||||
displayName: 'bytes',
|
||||
type: 'number',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'memory',
|
||||
displayName: 'amemory',
|
||||
type: 'number',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'unsupported',
|
||||
displayName: 'unsupported',
|
||||
type: 'geo',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'source',
|
||||
displayName: 'source',
|
||||
type: 'string',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'client',
|
||||
displayName: 'client',
|
||||
type: 'ip',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
documentField,
|
||||
];
|
||||
|
||||
const fieldsTwo = [
|
||||
{
|
||||
name: 'timestamp',
|
||||
displayName: 'timestampLabel',
|
||||
type: 'date',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
aggregationRestrictions: {
|
||||
date_histogram: {
|
||||
agg: 'date_histogram',
|
||||
fixed_interval: '1d',
|
||||
delay: '7d',
|
||||
time_zone: 'UTC',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'bytes',
|
||||
displayName: 'bytes',
|
||||
type: 'number',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
aggregationRestrictions: {
|
||||
histogram: {
|
||||
agg: 'histogram',
|
||||
interval: 1000,
|
||||
},
|
||||
max: {
|
||||
agg: 'max',
|
||||
},
|
||||
min: {
|
||||
agg: 'min',
|
||||
},
|
||||
sum: {
|
||||
agg: 'sum',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'source',
|
||||
displayName: 'source',
|
||||
type: 'string',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
aggregationRestrictions: {
|
||||
terms: {
|
||||
agg: 'terms',
|
||||
},
|
||||
},
|
||||
},
|
||||
documentField,
|
||||
];
|
||||
|
||||
const fieldsThree = [
|
||||
{
|
||||
name: 'timestamp',
|
||||
displayName: 'timestampLabel',
|
||||
type: 'date',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'bytes',
|
||||
displayName: 'bytes',
|
||||
type: 'number',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'source',
|
||||
displayName: 'source',
|
||||
type: 'string',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
documentField,
|
||||
];
|
||||
|
||||
const initialState: IndexPatternPrivateState = {
|
||||
indexPatternRefs: [],
|
||||
|
@ -85,139 +210,24 @@ const initialState: IndexPatternPrivateState = {
|
|||
title: 'idx1',
|
||||
timeFieldName: 'timestamp',
|
||||
hasRestrictions: false,
|
||||
fields: [
|
||||
{
|
||||
name: 'timestamp',
|
||||
displayName: 'timestampLabel',
|
||||
type: 'date',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'bytes',
|
||||
displayName: 'bytes',
|
||||
type: 'number',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'memory',
|
||||
displayName: 'amemory',
|
||||
type: 'number',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'unsupported',
|
||||
displayName: 'unsupported',
|
||||
type: 'geo',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'source',
|
||||
displayName: 'source',
|
||||
type: 'string',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'client',
|
||||
displayName: 'client',
|
||||
type: 'ip',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
documentField,
|
||||
],
|
||||
fields: fieldsOne,
|
||||
getFieldByName: getFieldByNameFactory(fieldsOne),
|
||||
},
|
||||
'2': {
|
||||
id: '2',
|
||||
title: 'idx2',
|
||||
timeFieldName: 'timestamp',
|
||||
hasRestrictions: true,
|
||||
fields: [
|
||||
{
|
||||
name: 'timestamp',
|
||||
displayName: 'timestampLabel',
|
||||
type: 'date',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
aggregationRestrictions: {
|
||||
date_histogram: {
|
||||
agg: 'date_histogram',
|
||||
fixed_interval: '1d',
|
||||
delay: '7d',
|
||||
time_zone: 'UTC',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'bytes',
|
||||
displayName: 'bytes',
|
||||
type: 'number',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
aggregationRestrictions: {
|
||||
histogram: {
|
||||
agg: 'histogram',
|
||||
interval: 1000,
|
||||
},
|
||||
max: {
|
||||
agg: 'max',
|
||||
},
|
||||
min: {
|
||||
agg: 'min',
|
||||
},
|
||||
sum: {
|
||||
agg: 'sum',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'source',
|
||||
displayName: 'source',
|
||||
type: 'string',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
aggregationRestrictions: {
|
||||
terms: {
|
||||
agg: 'terms',
|
||||
},
|
||||
},
|
||||
},
|
||||
documentField,
|
||||
],
|
||||
fields: fieldsTwo,
|
||||
getFieldByName: getFieldByNameFactory(fieldsTwo),
|
||||
},
|
||||
'3': {
|
||||
id: '3',
|
||||
title: 'idx3',
|
||||
timeFieldName: 'timestamp',
|
||||
hasRestrictions: false,
|
||||
fields: [
|
||||
{
|
||||
name: 'timestamp',
|
||||
displayName: 'timestampLabel',
|
||||
type: 'date',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'bytes',
|
||||
displayName: 'bytes',
|
||||
type: 'number',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'source',
|
||||
displayName: 'source',
|
||||
type: 'string',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
documentField,
|
||||
],
|
||||
fields: fieldsThree,
|
||||
getFieldByName: getFieldByNameFactory(fieldsThree),
|
||||
},
|
||||
},
|
||||
isFirstExistenceFetch: false,
|
||||
|
@ -330,6 +340,7 @@ describe('IndexPattern Data Panel', () => {
|
|||
title: 'aaa',
|
||||
timeFieldName: 'atime',
|
||||
fields: [],
|
||||
getFieldByName: getFieldByNameFactory([]),
|
||||
hasRestrictions: false,
|
||||
},
|
||||
b: {
|
||||
|
@ -337,6 +348,7 @@ describe('IndexPattern Data Panel', () => {
|
|||
title: 'bbb',
|
||||
timeFieldName: 'btime',
|
||||
fields: [],
|
||||
getFieldByName: getFieldByNameFactory([]),
|
||||
hasRestrictions: false,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import './datapanel.scss';
|
||||
import { uniq, keyBy, groupBy } from 'lodash';
|
||||
import { uniq, groupBy } from 'lodash';
|
||||
import React, { useState, memo, useCallback, useMemo } from 'react';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
|
@ -266,9 +266,8 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({
|
|||
const fieldInfoUnavailable = existenceFetchFailed || currentIndexPattern.hasRestrictions;
|
||||
|
||||
const unfilteredFieldGroups: FieldGroups = useMemo(() => {
|
||||
const fieldByName = keyBy(allFields, 'name');
|
||||
const containsData = (field: IndexPatternField) => {
|
||||
const overallField = fieldByName[field.name];
|
||||
const overallField = currentIndexPattern.getFieldByName(field.name);
|
||||
|
||||
return (
|
||||
overallField && fieldExists(existingFields, currentIndexPattern.title, overallField.name)
|
||||
|
|
|
@ -10,12 +10,14 @@ import { BucketNestingEditor } from './bucket_nesting_editor';
|
|||
import { IndexPatternColumn } from '../indexpattern';
|
||||
import { IndexPatternField } from '../types';
|
||||
|
||||
const fieldMap = {
|
||||
const fieldMap: Record<string, IndexPatternField> = {
|
||||
a: { displayName: 'a' } as IndexPatternField,
|
||||
b: { displayName: 'b' } as IndexPatternField,
|
||||
c: { displayName: 'c' } as IndexPatternField,
|
||||
};
|
||||
|
||||
const getFieldByName = (name: string): IndexPatternField | undefined => fieldMap[name];
|
||||
|
||||
describe('BucketNestingEditor', () => {
|
||||
function mockCol(col: Partial<IndexPatternColumn> = {}): IndexPatternColumn {
|
||||
const result = {
|
||||
|
@ -39,7 +41,7 @@ describe('BucketNestingEditor', () => {
|
|||
it('should display the top level grouping when at the root', () => {
|
||||
const component = mount(
|
||||
<BucketNestingEditor
|
||||
fieldMap={fieldMap}
|
||||
getFieldByName={getFieldByName}
|
||||
columnId="a"
|
||||
layer={{
|
||||
columnOrder: ['a', 'b', 'c'],
|
||||
|
@ -61,7 +63,7 @@ describe('BucketNestingEditor', () => {
|
|||
const component = mount(
|
||||
<BucketNestingEditor
|
||||
columnId="a"
|
||||
fieldMap={fieldMap}
|
||||
getFieldByName={getFieldByName}
|
||||
layer={{
|
||||
columnOrder: ['b', 'a', 'c'],
|
||||
columns: {
|
||||
|
@ -83,7 +85,7 @@ describe('BucketNestingEditor', () => {
|
|||
const component = mount(
|
||||
<BucketNestingEditor
|
||||
columnId="a"
|
||||
fieldMap={fieldMap}
|
||||
getFieldByName={getFieldByName}
|
||||
layer={{
|
||||
columnOrder: ['b', 'a', 'c'],
|
||||
columns: {
|
||||
|
@ -128,7 +130,7 @@ describe('BucketNestingEditor', () => {
|
|||
const component = mount(
|
||||
<BucketNestingEditor
|
||||
columnId="a"
|
||||
fieldMap={fieldMap}
|
||||
getFieldByName={getFieldByName}
|
||||
layer={{
|
||||
columnOrder: ['a', 'b', 'c'],
|
||||
columns: {
|
||||
|
@ -149,7 +151,7 @@ describe('BucketNestingEditor', () => {
|
|||
const component = mount(
|
||||
<BucketNestingEditor
|
||||
columnId="a"
|
||||
fieldMap={fieldMap}
|
||||
getFieldByName={getFieldByName}
|
||||
layer={{
|
||||
columnOrder: ['a', 'b', 'c'],
|
||||
columns: {
|
||||
|
@ -170,7 +172,7 @@ describe('BucketNestingEditor', () => {
|
|||
const component = mount(
|
||||
<BucketNestingEditor
|
||||
columnId="a"
|
||||
fieldMap={fieldMap}
|
||||
getFieldByName={getFieldByName}
|
||||
layer={{
|
||||
columnOrder: ['c', 'a', 'b'],
|
||||
columns: {
|
||||
|
@ -194,7 +196,7 @@ describe('BucketNestingEditor', () => {
|
|||
const component = mount(
|
||||
<BucketNestingEditor
|
||||
columnId="a"
|
||||
fieldMap={fieldMap}
|
||||
getFieldByName={getFieldByName}
|
||||
layer={{
|
||||
columnOrder: ['c', 'a', 'b'],
|
||||
columns: {
|
||||
|
@ -221,7 +223,7 @@ describe('BucketNestingEditor', () => {
|
|||
const component = mount(
|
||||
<BucketNestingEditor
|
||||
columnId="a"
|
||||
fieldMap={fieldMap}
|
||||
getFieldByName={getFieldByName}
|
||||
layer={{
|
||||
columnOrder: ['c', 'a', 'b'],
|
||||
columns: {
|
||||
|
@ -247,7 +249,7 @@ describe('BucketNestingEditor', () => {
|
|||
const setColumns = jest.fn();
|
||||
const component = mount(
|
||||
<BucketNestingEditor
|
||||
fieldMap={fieldMap}
|
||||
getFieldByName={getFieldByName}
|
||||
columnId="b"
|
||||
layer={{
|
||||
columnOrder: ['c', 'a', 'b'],
|
||||
|
|
|
@ -20,20 +20,25 @@ 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 : '';
|
||||
function getFieldName(
|
||||
column: IndexPatternColumn,
|
||||
getFieldByName: (name: string) => IndexPatternField | undefined
|
||||
) {
|
||||
return hasField(column)
|
||||
? getFieldByName(column.sourceField)?.displayName || column.sourceField
|
||||
: '';
|
||||
}
|
||||
|
||||
export function BucketNestingEditor({
|
||||
columnId,
|
||||
layer,
|
||||
setColumns,
|
||||
fieldMap,
|
||||
getFieldByName,
|
||||
}: {
|
||||
columnId: string;
|
||||
layer: IndexPatternLayer;
|
||||
setColumns: (columns: string[]) => void;
|
||||
fieldMap: Record<string, IndexPatternField>;
|
||||
getFieldByName: (name: string) => IndexPatternField | undefined;
|
||||
}) {
|
||||
const column = layer.columns[columnId];
|
||||
const columns = Object.entries(layer.columns);
|
||||
|
@ -42,7 +47,7 @@ export function BucketNestingEditor({
|
|||
.map(([value, c]) => ({
|
||||
value,
|
||||
text: c.label,
|
||||
fieldName: getFieldName(fieldMap, c),
|
||||
fieldName: getFieldName(c, getFieldByName),
|
||||
operationType: c.operationType,
|
||||
}));
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ import { deleteColumn, changeColumn, updateColumnParam, mergeLayer } from '../st
|
|||
import { FieldSelect } from './field_select';
|
||||
import { hasField, fieldIsInvalid } from '../utils';
|
||||
import { BucketNestingEditor } from './bucket_nesting_editor';
|
||||
import { IndexPattern, IndexPatternField } from '../types';
|
||||
import { IndexPattern } from '../types';
|
||||
import { trackUiEvent } from '../../lens_ui_telemetry';
|
||||
import { FormatSelector } from './format_selector';
|
||||
|
||||
|
@ -97,23 +97,13 @@ export function DimensionEditor(props: DimensionEditorProps) {
|
|||
|
||||
const ParamEditor = selectedOperationDefinition?.paramEditor;
|
||||
|
||||
const fieldMap: Record<string, IndexPatternField> = useMemo(() => {
|
||||
const fields: Record<string, IndexPatternField> = {};
|
||||
currentIndexPattern.fields.forEach((field) => {
|
||||
fields[field.name] = field;
|
||||
});
|
||||
return fields;
|
||||
}, [currentIndexPattern]);
|
||||
|
||||
const possibleOperations = useMemo(() => {
|
||||
return Object.values(operationDefinitionMap)
|
||||
.sort((op1, op2) => {
|
||||
return op1.displayName.localeCompare(op2.displayName);
|
||||
})
|
||||
.map((def) => def.type)
|
||||
.filter(
|
||||
(type) => fieldByOperation[type]?.length || operationWithoutField.indexOf(type) !== -1
|
||||
);
|
||||
.filter((type) => fieldByOperation[type]?.size || operationWithoutField.has(type));
|
||||
}, [fieldByOperation, operationWithoutField]);
|
||||
|
||||
// Operations are compatible if they match inputs. They are always compatible in
|
||||
|
@ -128,7 +118,7 @@ export function DimensionEditor(props: DimensionEditorProps) {
|
|||
(selectedColumn &&
|
||||
hasField(selectedColumn) &&
|
||||
definition.input === 'field' &&
|
||||
fieldByOperation[operationType]?.indexOf(selectedColumn.sourceField) !== -1) ||
|
||||
fieldByOperation[operationType]?.has(selectedColumn.sourceField)) ||
|
||||
(selectedColumn && !hasField(selectedColumn) && definition.input !== 'field'),
|
||||
};
|
||||
});
|
||||
|
@ -198,9 +188,9 @@ export function DimensionEditor(props: DimensionEditorProps) {
|
|||
trackUiEvent(`indexpattern_dimension_operation_${operationType}`);
|
||||
return;
|
||||
} else if (!selectedColumn || !compatibleWithCurrentField) {
|
||||
const possibleFields = fieldByOperation[operationType] || [];
|
||||
const possibleFields = fieldByOperation[operationType] || new Set();
|
||||
|
||||
if (possibleFields.length === 1) {
|
||||
if (possibleFields.size === 1) {
|
||||
setState(
|
||||
changeColumn({
|
||||
state,
|
||||
|
@ -212,7 +202,7 @@ export function DimensionEditor(props: DimensionEditorProps) {
|
|||
layerId: props.layerId,
|
||||
op: operationType,
|
||||
indexPattern: currentIndexPattern,
|
||||
field: fieldMap[possibleFields[0]],
|
||||
field: currentIndexPattern.getFieldByName(possibleFields.values().next().value),
|
||||
previousColumn: selectedColumn,
|
||||
}),
|
||||
})
|
||||
|
@ -236,7 +226,9 @@ export function DimensionEditor(props: DimensionEditorProps) {
|
|||
layerId: props.layerId,
|
||||
op: operationType,
|
||||
indexPattern: currentIndexPattern,
|
||||
field: hasField(selectedColumn) ? fieldMap[selectedColumn.sourceField] : undefined,
|
||||
field: hasField(selectedColumn)
|
||||
? currentIndexPattern.getFieldByName(selectedColumn.sourceField)
|
||||
: undefined,
|
||||
previousColumn: selectedColumn,
|
||||
});
|
||||
|
||||
|
@ -297,7 +289,6 @@ export function DimensionEditor(props: DimensionEditorProps) {
|
|||
fieldIsInvalid={currentFieldIsInvalid}
|
||||
currentIndexPattern={currentIndexPattern}
|
||||
existingFields={state.existingFields}
|
||||
fieldMap={fieldMap}
|
||||
operationSupportMatrix={operationSupportMatrix}
|
||||
selectedColumnOperationType={selectedColumn && selectedColumn.operationType}
|
||||
selectedColumnSourceField={
|
||||
|
@ -323,25 +314,29 @@ export function DimensionEditor(props: DimensionEditorProps) {
|
|||
) {
|
||||
// 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]);
|
||||
column = changeField(
|
||||
selectedColumn,
|
||||
currentIndexPattern,
|
||||
currentIndexPattern.getFieldByName(choice.field)!
|
||||
);
|
||||
} else {
|
||||
// Otherwise we'll use the buildColumn method to calculate a new column
|
||||
const compatibleOperations =
|
||||
('field' in choice && operationSupportMatrix.operationByField[choice.field]) ||
|
||||
[];
|
||||
new Set();
|
||||
let operation;
|
||||
if (compatibleOperations.length > 0) {
|
||||
if (compatibleOperations.size > 0) {
|
||||
operation =
|
||||
incompatibleSelectedOperationType &&
|
||||
compatibleOperations.includes(incompatibleSelectedOperationType)
|
||||
compatibleOperations.has(incompatibleSelectedOperationType)
|
||||
? incompatibleSelectedOperationType
|
||||
: compatibleOperations[0];
|
||||
: compatibleOperations.values().next().value;
|
||||
} else if ('field' in choice) {
|
||||
operation = choice.operationType;
|
||||
}
|
||||
column = buildColumn({
|
||||
columns: props.state.layers[props.layerId].columns,
|
||||
field: fieldMap[choice.field],
|
||||
field: currentIndexPattern.getFieldByName(choice.field),
|
||||
indexPattern: currentIndexPattern,
|
||||
layerId: props.layerId,
|
||||
suggestedPriority: props.suggestedPriority,
|
||||
|
@ -417,12 +412,12 @@ export function DimensionEditor(props: DimensionEditorProps) {
|
|||
|
||||
{!incompatibleSelectedOperationType && !hideGrouping && (
|
||||
<BucketNestingEditor
|
||||
fieldMap={fieldMap}
|
||||
layer={state.layers[props.layerId]}
|
||||
columnId={props.columnId}
|
||||
setColumns={(columnOrder) =>
|
||||
setState(mergeLayer({ state, layerId, newLayer: { columnOrder } }))
|
||||
}
|
||||
getFieldByName={currentIndexPattern.getFieldByName}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ import { IndexPatternColumn } from '../operations';
|
|||
import { documentField } from '../document_field';
|
||||
import { OperationMetadata } from '../../types';
|
||||
import { DateHistogramIndexPatternColumn } from '../operations/definitions/date_histogram';
|
||||
import { getFieldByNameFactory } from '../pure_helpers';
|
||||
|
||||
jest.mock('../loader');
|
||||
jest.mock('../state_helpers');
|
||||
|
@ -34,6 +35,42 @@ jest.mock('lodash', () => {
|
|||
};
|
||||
});
|
||||
|
||||
const fields = [
|
||||
{
|
||||
name: 'timestamp',
|
||||
displayName: 'timestampLabel',
|
||||
type: 'date',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
exists: true,
|
||||
},
|
||||
{
|
||||
name: 'bytes',
|
||||
displayName: 'bytes',
|
||||
type: 'number',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
exists: true,
|
||||
},
|
||||
{
|
||||
name: 'memory',
|
||||
displayName: 'memory',
|
||||
type: 'number',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
exists: true,
|
||||
},
|
||||
{
|
||||
name: 'source',
|
||||
displayName: 'source',
|
||||
type: 'string',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
exists: true,
|
||||
},
|
||||
documentField,
|
||||
];
|
||||
|
||||
const expectedIndexPatterns = {
|
||||
1: {
|
||||
id: '1',
|
||||
|
@ -41,41 +78,8 @@ const expectedIndexPatterns = {
|
|||
timeFieldName: 'timestamp',
|
||||
hasExistence: true,
|
||||
hasRestrictions: false,
|
||||
fields: [
|
||||
{
|
||||
name: 'timestamp',
|
||||
displayName: 'timestampLabel',
|
||||
type: 'date',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
exists: true,
|
||||
},
|
||||
{
|
||||
name: 'bytes',
|
||||
displayName: 'bytes',
|
||||
type: 'number',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
exists: true,
|
||||
},
|
||||
{
|
||||
name: 'memory',
|
||||
displayName: 'memory',
|
||||
type: 'number',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
exists: true,
|
||||
},
|
||||
{
|
||||
name: 'source',
|
||||
displayName: 'source',
|
||||
type: 'string',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
exists: true,
|
||||
},
|
||||
documentField,
|
||||
],
|
||||
fields,
|
||||
getFieldByName: getFieldByNameFactory(fields),
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -15,9 +15,46 @@ import { IndexPatternPrivateState } from '../types';
|
|||
import { documentField } from '../document_field';
|
||||
import { OperationMetadata } from '../../types';
|
||||
import { IndexPatternColumn } from '../operations';
|
||||
import { getFieldByNameFactory } from '../pure_helpers';
|
||||
|
||||
jest.mock('../state_helpers');
|
||||
|
||||
const fields = [
|
||||
{
|
||||
name: 'timestamp',
|
||||
displayName: 'timestampLabel',
|
||||
type: 'date',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
exists: true,
|
||||
},
|
||||
{
|
||||
name: 'bytes',
|
||||
displayName: 'bytes',
|
||||
type: 'number',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
exists: true,
|
||||
},
|
||||
{
|
||||
name: 'memory',
|
||||
displayName: 'memory',
|
||||
type: 'number',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
exists: true,
|
||||
},
|
||||
{
|
||||
name: 'source',
|
||||
displayName: 'source',
|
||||
type: 'string',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
exists: true,
|
||||
},
|
||||
documentField,
|
||||
];
|
||||
|
||||
const expectedIndexPatterns = {
|
||||
1: {
|
||||
id: '1',
|
||||
|
@ -25,41 +62,8 @@ const expectedIndexPatterns = {
|
|||
timeFieldName: 'timestamp',
|
||||
hasExistence: true,
|
||||
hasRestrictions: false,
|
||||
fields: [
|
||||
{
|
||||
name: 'timestamp',
|
||||
displayName: 'timestampLabel',
|
||||
type: 'date',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
exists: true,
|
||||
},
|
||||
{
|
||||
name: 'bytes',
|
||||
displayName: 'bytes',
|
||||
type: 'number',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
exists: true,
|
||||
},
|
||||
{
|
||||
name: 'memory',
|
||||
displayName: 'memory',
|
||||
type: 'number',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
exists: true,
|
||||
},
|
||||
{
|
||||
name: 'source',
|
||||
displayName: 'source',
|
||||
type: 'string',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
exists: true,
|
||||
},
|
||||
documentField,
|
||||
],
|
||||
fields,
|
||||
getFieldByName: getFieldByNameFactory(fields),
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -177,6 +181,23 @@ describe('IndexPatternDimensionEditorPanel', () => {
|
|||
type: 'string',
|
||||
},
|
||||
],
|
||||
|
||||
getFieldByName: getFieldByNameFactory([
|
||||
{
|
||||
aggregatable: true,
|
||||
name: 'bar',
|
||||
displayName: 'bar',
|
||||
searchable: true,
|
||||
type: 'number',
|
||||
},
|
||||
{
|
||||
aggregatable: true,
|
||||
name: 'mystring',
|
||||
displayName: 'mystring',
|
||||
searchable: true,
|
||||
type: 'string',
|
||||
},
|
||||
]),
|
||||
},
|
||||
},
|
||||
currentIndexPatternId: '1',
|
||||
|
|
|
@ -137,9 +137,9 @@ export function onDrop(props: DatasourceDimensionDropHandlerProps<IndexPatternPr
|
|||
hasField(selectedColumn) &&
|
||||
selectedColumn.sourceField !== droppedItem.field.name &&
|
||||
operationsForNewField &&
|
||||
operationsForNewField.includes(selectedColumn.operationType);
|
||||
operationsForNewField.has(selectedColumn.operationType);
|
||||
|
||||
if (!operationsForNewField || operationsForNewField.length === 0) {
|
||||
if (!operationsForNewField || operationsForNewField.size === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -148,7 +148,7 @@ export function onDrop(props: DatasourceDimensionDropHandlerProps<IndexPatternPr
|
|||
const newColumn = hasFieldChanged
|
||||
? changeField(selectedColumn, currentIndexPattern, droppedItem.field)
|
||||
: buildColumn({
|
||||
op: operationsForNewField[0],
|
||||
op: operationsForNewField.values().next().value,
|
||||
columns: state.layers[layerId].columns,
|
||||
indexPattern: currentIndexPattern,
|
||||
layerId,
|
||||
|
|
|
@ -21,7 +21,7 @@ import { OperationType } from '../indexpattern';
|
|||
import { LensFieldIcon } from '../lens_field_icon';
|
||||
import { DataType } from '../../types';
|
||||
import { OperationSupportMatrix } from './operation_support';
|
||||
import { IndexPattern, IndexPatternField, IndexPatternPrivateState } from '../types';
|
||||
import { IndexPattern, IndexPatternPrivateState } from '../types';
|
||||
import { trackUiEvent } from '../../lens_ui_telemetry';
|
||||
import { fieldExists } from '../pure_helpers';
|
||||
|
||||
|
@ -33,7 +33,6 @@ export interface FieldChoice {
|
|||
|
||||
export interface FieldSelectProps extends EuiComboBoxProps<{}> {
|
||||
currentIndexPattern: IndexPattern;
|
||||
fieldMap: Record<string, IndexPatternField>;
|
||||
incompatibleSelectedOperationType: OperationType | null;
|
||||
selectedColumnOperationType?: OperationType;
|
||||
selectedColumnSourceField?: string;
|
||||
|
@ -46,7 +45,6 @@ export interface FieldSelectProps extends EuiComboBoxProps<{}> {
|
|||
|
||||
export function FieldSelect({
|
||||
currentIndexPattern,
|
||||
fieldMap,
|
||||
incompatibleSelectedOperationType,
|
||||
selectedColumnOperationType,
|
||||
selectedColumnSourceField,
|
||||
|
@ -63,31 +61,32 @@ export function FieldSelect({
|
|||
|
||||
function isCompatibleWithCurrentOperation(fieldName: string) {
|
||||
if (incompatibleSelectedOperationType) {
|
||||
return operationByField[fieldName]!.includes(incompatibleSelectedOperationType);
|
||||
return operationByField[fieldName]!.has(incompatibleSelectedOperationType);
|
||||
}
|
||||
return (
|
||||
!selectedColumnOperationType ||
|
||||
operationByField[fieldName]!.includes(selectedColumnOperationType)
|
||||
operationByField[fieldName]!.has(selectedColumnOperationType)
|
||||
);
|
||||
}
|
||||
|
||||
const [specialFields, normalFields] = _.partition(
|
||||
fields,
|
||||
(field) => fieldMap[field].type === 'document'
|
||||
(field) => currentIndexPattern.getFieldByName(field)?.type === 'document'
|
||||
);
|
||||
|
||||
const containsData = (field: string) =>
|
||||
fieldMap[field].type === 'document' ||
|
||||
currentIndexPattern.getFieldByName(field)?.type === 'document' ||
|
||||
fieldExists(existingFields, currentIndexPattern.title, field);
|
||||
|
||||
function fieldNamesToOptions(items: string[]) {
|
||||
return items
|
||||
.filter((field) => currentIndexPattern.getFieldByName(field)?.displayName)
|
||||
.map((field) => ({
|
||||
label: fieldMap[field].displayName,
|
||||
label: currentIndexPattern.getFieldByName(field)?.displayName,
|
||||
value: {
|
||||
type: 'field',
|
||||
field,
|
||||
dataType: fieldMap[field].type,
|
||||
dataType: currentIndexPattern.getFieldByName(field)?.type,
|
||||
operationType:
|
||||
selectedColumnOperationType && isCompatibleWithCurrentOperation(field)
|
||||
? selectedColumnOperationType
|
||||
|
@ -118,7 +117,10 @@ export function FieldSelect({
|
|||
}));
|
||||
}
|
||||
|
||||
const [metaFields, nonMetaFields] = _.partition(normalFields, (field) => fieldMap[field].meta);
|
||||
const [metaFields, nonMetaFields] = _.partition(
|
||||
normalFields,
|
||||
(field) => currentIndexPattern.getFieldByName(field)?.meta
|
||||
);
|
||||
const [availableFields, emptyFields] = _.partition(nonMetaFields, containsData);
|
||||
|
||||
const constructFieldsOptions = (fieldsArr: string[], label: string) =>
|
||||
|
@ -158,7 +160,6 @@ export function FieldSelect({
|
|||
incompatibleSelectedOperationType,
|
||||
selectedColumnOperationType,
|
||||
currentIndexPattern,
|
||||
fieldMap,
|
||||
operationByField,
|
||||
existingFields,
|
||||
]);
|
||||
|
@ -180,7 +181,7 @@ export function FieldSelect({
|
|||
{
|
||||
label: fieldIsInvalid
|
||||
? selectedColumnSourceField
|
||||
: fieldMap[selectedColumnSourceField]?.displayName,
|
||||
: currentIndexPattern.getFieldByName(selectedColumnSourceField)?.displayName,
|
||||
value: { type: 'field', field: selectedColumnSourceField },
|
||||
},
|
||||
]
|
||||
|
|
|
@ -11,9 +11,9 @@ import { getAvailableOperationsByMetadata } from '../operations';
|
|||
import { IndexPatternPrivateState } from '../types';
|
||||
|
||||
export interface OperationSupportMatrix {
|
||||
operationByField: Partial<Record<string, OperationType[]>>;
|
||||
operationWithoutField: OperationType[];
|
||||
fieldByOperation: Partial<Record<OperationType, string[]>>;
|
||||
operationByField: Partial<Record<string, Set<OperationType>>>;
|
||||
operationWithoutField: Set<OperationType>;
|
||||
fieldByOperation: Partial<Record<OperationType, Set<string>>>;
|
||||
}
|
||||
|
||||
type Props = Pick<
|
||||
|
@ -31,30 +31,30 @@ export const getOperationSupportMatrix = (props: Props): OperationSupportMatrix
|
|||
currentIndexPattern
|
||||
).filter((operation) => props.filterOperations(operation.operationMetaData));
|
||||
|
||||
const supportedOperationsByField: Partial<Record<string, OperationType[]>> = {};
|
||||
const supportedOperationsWithoutField: OperationType[] = [];
|
||||
const supportedFieldsByOperation: Partial<Record<OperationType, string[]>> = {};
|
||||
const supportedOperationsByField: Partial<Record<string, Set<OperationType>>> = {};
|
||||
const supportedOperationsWithoutField: Set<OperationType> = new Set();
|
||||
const supportedFieldsByOperation: Partial<Record<OperationType, Set<string>>> = {};
|
||||
|
||||
filteredOperationsByMetadata.forEach(({ operations }) => {
|
||||
operations.forEach((operation) => {
|
||||
if (operation.type === 'field') {
|
||||
supportedOperationsByField[operation.field] = [
|
||||
...(supportedOperationsByField[operation.field] ?? []),
|
||||
operation.operationType,
|
||||
];
|
||||
if (!supportedOperationsByField[operation.field]) {
|
||||
supportedOperationsByField[operation.field] = new Set();
|
||||
}
|
||||
supportedOperationsByField[operation.field]?.add(operation.operationType);
|
||||
|
||||
supportedFieldsByOperation[operation.operationType] = [
|
||||
...(supportedFieldsByOperation[operation.operationType] ?? []),
|
||||
operation.field,
|
||||
];
|
||||
if (!supportedFieldsByOperation[operation.operationType]) {
|
||||
supportedFieldsByOperation[operation.operationType] = new Set();
|
||||
}
|
||||
supportedFieldsByOperation[operation.operationType]?.add(operation.field);
|
||||
} else if (operation.type === 'none') {
|
||||
supportedOperationsWithoutField.push(operation.operationType);
|
||||
supportedOperationsWithoutField.add(operation.operationType);
|
||||
}
|
||||
});
|
||||
});
|
||||
return {
|
||||
operationByField: _.mapValues(supportedOperationsByField, _.uniq),
|
||||
operationWithoutField: _.uniq(supportedOperationsWithoutField),
|
||||
fieldByOperation: _.mapValues(supportedFieldsByOperation, _.uniq),
|
||||
operationByField: supportedOperationsByField,
|
||||
operationWithoutField: supportedOperationsWithoutField,
|
||||
fieldByOperation: supportedFieldsByOperation,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -12,121 +12,128 @@ import { IndexPatternPersistedState, IndexPatternPrivateState } from './types';
|
|||
import { dataPluginMock } from '../../../../../src/plugins/data/public/mocks';
|
||||
import { Ast } from '@kbn/interpreter/common';
|
||||
import { chartPluginMock } from '../../../../../src/plugins/charts/public/mocks';
|
||||
import { getFieldByNameFactory } from './pure_helpers';
|
||||
|
||||
jest.mock('./loader');
|
||||
jest.mock('../id_generator');
|
||||
|
||||
const fieldsOne = [
|
||||
{
|
||||
name: 'timestamp',
|
||||
displayName: 'timestampLabel',
|
||||
type: 'date',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'start_date',
|
||||
displayName: 'start_date',
|
||||
type: 'date',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'bytes',
|
||||
displayName: 'bytes',
|
||||
type: 'number',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'memory',
|
||||
displayName: 'memory',
|
||||
type: 'number',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'source',
|
||||
displayName: 'source',
|
||||
type: 'string',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'dest',
|
||||
displayName: 'dest',
|
||||
type: 'string',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
];
|
||||
|
||||
const fieldsTwo = [
|
||||
{
|
||||
name: 'timestamp',
|
||||
displayName: 'timestampLabel',
|
||||
type: 'date',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
aggregationRestrictions: {
|
||||
date_histogram: {
|
||||
agg: 'date_histogram',
|
||||
fixed_interval: '1d',
|
||||
delay: '7d',
|
||||
time_zone: 'UTC',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'bytes',
|
||||
displayName: 'bytes',
|
||||
type: 'number',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
aggregationRestrictions: {
|
||||
// Ignored in the UI
|
||||
histogram: {
|
||||
agg: 'histogram',
|
||||
interval: 1000,
|
||||
},
|
||||
avg: {
|
||||
agg: 'avg',
|
||||
},
|
||||
max: {
|
||||
agg: 'max',
|
||||
},
|
||||
min: {
|
||||
agg: 'min',
|
||||
},
|
||||
sum: {
|
||||
agg: 'sum',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'source',
|
||||
displayName: 'source',
|
||||
type: 'string',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
aggregationRestrictions: {
|
||||
terms: {
|
||||
agg: 'terms',
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const expectedIndexPatterns = {
|
||||
1: {
|
||||
id: '1',
|
||||
title: 'my-fake-index-pattern',
|
||||
timeFieldName: 'timestamp',
|
||||
hasRestrictions: false,
|
||||
fields: [
|
||||
{
|
||||
name: 'timestamp',
|
||||
displayName: 'timestampLabel',
|
||||
type: 'date',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'start_date',
|
||||
displayName: 'start_date',
|
||||
type: 'date',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'bytes',
|
||||
displayName: 'bytes',
|
||||
type: 'number',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'memory',
|
||||
displayName: 'memory',
|
||||
type: 'number',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'source',
|
||||
displayName: 'source',
|
||||
type: 'string',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'dest',
|
||||
displayName: 'dest',
|
||||
type: 'string',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
],
|
||||
fields: fieldsOne,
|
||||
getFieldByName: getFieldByNameFactory(fieldsOne),
|
||||
},
|
||||
2: {
|
||||
id: '2',
|
||||
title: 'my-fake-restricted-pattern',
|
||||
timeFieldName: 'timestamp',
|
||||
hasRestrictions: true,
|
||||
fields: [
|
||||
{
|
||||
name: 'timestamp',
|
||||
displayName: 'timestampLabel',
|
||||
type: 'date',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
aggregationRestrictions: {
|
||||
date_histogram: {
|
||||
agg: 'date_histogram',
|
||||
fixed_interval: '1d',
|
||||
delay: '7d',
|
||||
time_zone: 'UTC',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'bytes',
|
||||
displayName: 'bytes',
|
||||
type: 'number',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
aggregationRestrictions: {
|
||||
// Ignored in the UI
|
||||
histogram: {
|
||||
agg: 'histogram',
|
||||
interval: 1000,
|
||||
},
|
||||
avg: {
|
||||
agg: 'avg',
|
||||
},
|
||||
max: {
|
||||
agg: 'max',
|
||||
},
|
||||
min: {
|
||||
agg: 'min',
|
||||
},
|
||||
sum: {
|
||||
agg: 'sum',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'source',
|
||||
displayName: 'source',
|
||||
type: 'string',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
aggregationRestrictions: {
|
||||
terms: {
|
||||
agg: 'terms',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
fields: fieldsTwo,
|
||||
getFieldByName: getFieldByNameFactory(fieldsTwo),
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -12,121 +12,128 @@ import {
|
|||
getDatasourceSuggestionsFromCurrentState,
|
||||
getDatasourceSuggestionsForVisualizeField,
|
||||
} from './indexpattern_suggestions';
|
||||
import { getFieldByNameFactory } from './pure_helpers';
|
||||
|
||||
jest.mock('./loader');
|
||||
jest.mock('../id_generator');
|
||||
|
||||
const fieldsOne = [
|
||||
{
|
||||
name: 'timestamp',
|
||||
displayName: 'timestampLabel',
|
||||
type: 'date',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'start_date',
|
||||
displayName: 'start_date',
|
||||
type: 'date',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'bytes',
|
||||
displayName: 'bytes',
|
||||
type: 'number',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'memory',
|
||||
displayName: 'memory',
|
||||
type: 'number',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'source',
|
||||
displayName: 'source',
|
||||
type: 'string',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'dest',
|
||||
displayName: 'dest',
|
||||
type: 'string',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
];
|
||||
|
||||
const fieldsTwo = [
|
||||
{
|
||||
name: 'timestamp',
|
||||
displayName: 'timestampLabel',
|
||||
type: 'date',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
aggregationRestrictions: {
|
||||
date_histogram: {
|
||||
agg: 'date_histogram',
|
||||
fixed_interval: '1d',
|
||||
delay: '7d',
|
||||
time_zone: 'UTC',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'bytes',
|
||||
displayName: 'bytes',
|
||||
type: 'number',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
aggregationRestrictions: {
|
||||
// Ignored in the UI
|
||||
histogram: {
|
||||
agg: 'histogram',
|
||||
interval: 1000,
|
||||
},
|
||||
avg: {
|
||||
agg: 'avg',
|
||||
},
|
||||
max: {
|
||||
agg: 'max',
|
||||
},
|
||||
min: {
|
||||
agg: 'min',
|
||||
},
|
||||
sum: {
|
||||
agg: 'sum',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'source',
|
||||
displayName: 'source',
|
||||
type: 'string',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
aggregationRestrictions: {
|
||||
terms: {
|
||||
agg: 'terms',
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const expectedIndexPatterns = {
|
||||
1: {
|
||||
id: '1',
|
||||
title: 'my-fake-index-pattern',
|
||||
timeFieldName: 'timestamp',
|
||||
hasRestrictions: false,
|
||||
fields: [
|
||||
{
|
||||
name: 'timestamp',
|
||||
displayName: 'timestampLabel',
|
||||
type: 'date',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'start_date',
|
||||
displayName: 'start_date',
|
||||
type: 'date',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'bytes',
|
||||
displayName: 'bytes',
|
||||
type: 'number',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'memory',
|
||||
displayName: 'memory',
|
||||
type: 'number',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'source',
|
||||
displayName: 'source',
|
||||
type: 'string',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'dest',
|
||||
displayName: 'dest',
|
||||
type: 'string',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
],
|
||||
fields: fieldsOne,
|
||||
getFieldByName: getFieldByNameFactory(fieldsOne),
|
||||
},
|
||||
2: {
|
||||
id: '2',
|
||||
title: 'my-fake-restricted-pattern',
|
||||
hasRestrictions: true,
|
||||
timeFieldName: 'timestamp',
|
||||
fields: [
|
||||
{
|
||||
name: 'timestamp',
|
||||
displayName: 'timestampLabel',
|
||||
type: 'date',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
aggregationRestrictions: {
|
||||
date_histogram: {
|
||||
agg: 'date_histogram',
|
||||
fixed_interval: '1d',
|
||||
delay: '7d',
|
||||
time_zone: 'UTC',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'bytes',
|
||||
displayName: 'bytes',
|
||||
type: 'number',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
aggregationRestrictions: {
|
||||
// Ignored in the UI
|
||||
histogram: {
|
||||
agg: 'histogram',
|
||||
interval: 1000,
|
||||
},
|
||||
avg: {
|
||||
agg: 'avg',
|
||||
},
|
||||
max: {
|
||||
agg: 'max',
|
||||
},
|
||||
min: {
|
||||
agg: 'min',
|
||||
},
|
||||
sum: {
|
||||
agg: 'sum',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'source',
|
||||
displayName: 'source',
|
||||
type: 'string',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
aggregationRestrictions: {
|
||||
terms: {
|
||||
agg: 'terms',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
fields: fieldsTwo,
|
||||
getFieldByName: getFieldByNameFactory(fieldsTwo),
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -335,6 +342,15 @@ describe('IndexPattern Data Source suggestions', () => {
|
|||
searchable: true,
|
||||
},
|
||||
],
|
||||
getFieldByName: getFieldByNameFactory([
|
||||
{
|
||||
name: 'bytes',
|
||||
displayName: 'bytes',
|
||||
type: 'number',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
]),
|
||||
},
|
||||
},
|
||||
layers: {
|
||||
|
@ -546,6 +562,16 @@ describe('IndexPattern Data Source suggestions', () => {
|
|||
searchable: true,
|
||||
},
|
||||
],
|
||||
|
||||
getFieldByName: getFieldByNameFactory([
|
||||
{
|
||||
name: 'bytes',
|
||||
displayName: 'bytes',
|
||||
type: 'number',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
]),
|
||||
},
|
||||
},
|
||||
layers: {
|
||||
|
@ -1531,6 +1557,43 @@ describe('IndexPattern Data Source suggestions', () => {
|
|||
|
||||
it('returns simplified versions of table with more than 2 columns', () => {
|
||||
const initialState = testInitialState();
|
||||
const fields = [
|
||||
{
|
||||
name: 'field1',
|
||||
displayName: 'field1',
|
||||
type: 'string',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'field2',
|
||||
displayName: 'field2',
|
||||
type: 'string',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'field3',
|
||||
displayName: 'field3Label',
|
||||
type: 'string',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'field4',
|
||||
displayName: 'field4',
|
||||
type: 'number',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'field5',
|
||||
displayName: 'field5',
|
||||
type: 'number',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
];
|
||||
const state: IndexPatternPrivateState = {
|
||||
indexPatternRefs: [],
|
||||
existingFields: {},
|
||||
|
@ -1540,43 +1603,8 @@ describe('IndexPattern Data Source suggestions', () => {
|
|||
id: '1',
|
||||
title: 'my-fake-index-pattern',
|
||||
hasRestrictions: false,
|
||||
fields: [
|
||||
{
|
||||
name: 'field1',
|
||||
displayName: 'field1',
|
||||
type: 'string',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'field2',
|
||||
displayName: 'field2',
|
||||
type: 'string',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'field3',
|
||||
displayName: 'field3Label',
|
||||
type: 'string',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'field4',
|
||||
displayName: 'field4',
|
||||
type: 'number',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'field5',
|
||||
displayName: 'field5',
|
||||
type: 'number',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
],
|
||||
fields,
|
||||
getFieldByName: getFieldByNameFactory(fields),
|
||||
},
|
||||
},
|
||||
isFirstExistenceFetch: false,
|
||||
|
@ -1700,6 +1728,23 @@ describe('IndexPattern Data Source suggestions', () => {
|
|||
searchable: true,
|
||||
},
|
||||
],
|
||||
|
||||
getFieldByName: getFieldByNameFactory([
|
||||
{
|
||||
name: 'field1',
|
||||
displayName: 'field1',
|
||||
type: 'number',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'field2',
|
||||
displayName: 'field2',
|
||||
type: 'date',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
]),
|
||||
},
|
||||
},
|
||||
isFirstExistenceFetch: false,
|
||||
|
@ -1756,6 +1801,15 @@ describe('IndexPattern Data Source suggestions', () => {
|
|||
searchable: true,
|
||||
},
|
||||
],
|
||||
getFieldByName: getFieldByNameFactory([
|
||||
{
|
||||
name: 'field1',
|
||||
displayName: 'field1',
|
||||
type: 'number',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
]),
|
||||
},
|
||||
},
|
||||
isFirstExistenceFetch: false,
|
||||
|
|
|
@ -128,7 +128,7 @@ export function getDatasourceSuggestionsForVisualizeField(
|
|||
const layerIds = layers.filter((id) => state.layers[id].indexPatternId === indexPatternId);
|
||||
// Identify the field by the indexPatternId and the fieldName
|
||||
const indexPattern = state.indexPatterns[indexPatternId];
|
||||
const field = indexPattern.fields.find((fld) => fld.name === fieldName);
|
||||
const field = indexPattern.getFieldByName(fieldName);
|
||||
|
||||
if (layerIds.length !== 0 || !field) return [];
|
||||
const newId = generateId();
|
||||
|
@ -371,7 +371,7 @@ function createNewLayerWithMetricAggregation(
|
|||
indexPattern: IndexPattern,
|
||||
field: IndexPatternField
|
||||
): IndexPatternLayer {
|
||||
const dateField = indexPattern.fields.find((f) => f.name === indexPattern.timeFieldName)!;
|
||||
const dateField = indexPattern.getFieldByName(indexPattern.timeFieldName!);
|
||||
|
||||
const column = getMetricColumn(indexPattern, layerId, field);
|
||||
|
||||
|
@ -451,9 +451,8 @@ export function getDatasourceSuggestionsFromCurrentState(
|
|||
(columnId) =>
|
||||
layer.columns[columnId].isBucketed && layer.columns[columnId].dataType === 'date'
|
||||
);
|
||||
const timeField = indexPattern.fields.find(
|
||||
({ name }) => name === indexPattern.timeFieldName
|
||||
);
|
||||
const timeField =
|
||||
indexPattern.timeFieldName && indexPattern.getFieldByName(indexPattern.timeFieldName);
|
||||
|
||||
const hasNumericDimension =
|
||||
buckets.length === 1 &&
|
||||
|
@ -507,17 +506,17 @@ function createChangedNestingSuggestion(state: IndexPatternPrivateState, layerId
|
|||
const layer = state.layers[layerId];
|
||||
const [firstBucket, secondBucket, ...rest] = layer.columnOrder;
|
||||
const updatedLayer = { ...layer, columnOrder: [secondBucket, firstBucket, ...rest] };
|
||||
const currentFields = state.indexPatterns[state.currentIndexPatternId].fields;
|
||||
const indexPattern = state.indexPatterns[state.currentIndexPatternId];
|
||||
const firstBucketColumn = layer.columns[firstBucket];
|
||||
const firstBucketLabel =
|
||||
currentFields.find((field) => {
|
||||
const column = layer.columns[firstBucket];
|
||||
return hasField(column) && column.sourceField === field.name;
|
||||
})?.displayName || '';
|
||||
(hasField(firstBucketColumn) &&
|
||||
indexPattern.getFieldByName(firstBucketColumn.sourceField)?.displayName) ||
|
||||
'';
|
||||
const secondBucketColumn = layer.columns[secondBucket];
|
||||
const secondBucketLabel =
|
||||
currentFields.find((field) => {
|
||||
const column = layer.columns[secondBucket];
|
||||
return hasField(column) && column.sourceField === field.name;
|
||||
})?.displayName || '';
|
||||
(hasField(secondBucketColumn) &&
|
||||
indexPattern.getFieldByName(secondBucketColumn.sourceField)?.displayName) ||
|
||||
'';
|
||||
|
||||
return buildSuggestion({
|
||||
state,
|
||||
|
@ -604,7 +603,10 @@ function createAlternativeMetricSuggestions(
|
|||
if (!hasField(column)) {
|
||||
return;
|
||||
}
|
||||
const field = indexPattern.fields.find(({ name }) => column.sourceField === name)!;
|
||||
const field = indexPattern.getFieldByName(column.sourceField);
|
||||
if (!field) {
|
||||
return;
|
||||
}
|
||||
const alternativeMetricOperations = getOperationTypesForField(field)
|
||||
.map((op) =>
|
||||
buildColumn({
|
||||
|
|
|
@ -11,6 +11,7 @@ import { shallowWithIntl as shallow } from 'test_utils/enzyme_helpers';
|
|||
import { ShallowWrapper } from 'enzyme';
|
||||
import { EuiSelectable } from '@elastic/eui';
|
||||
import { ChangeIndexPattern } from './change_indexpattern';
|
||||
import { getFieldByNameFactory } from './pure_helpers';
|
||||
|
||||
jest.mock('./state_helpers');
|
||||
|
||||
|
@ -19,6 +20,120 @@ interface IndexPatternPickerOption {
|
|||
checked?: 'on' | 'off';
|
||||
}
|
||||
|
||||
const fieldsOne = [
|
||||
{
|
||||
name: 'timestamp',
|
||||
displayName: 'timestampLabel',
|
||||
type: 'date',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'bytes',
|
||||
displayName: 'bytes',
|
||||
type: 'number',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'memory',
|
||||
displayName: 'memory',
|
||||
type: 'number',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'unsupported',
|
||||
displayName: 'unsupported',
|
||||
type: 'geo',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'source',
|
||||
displayName: 'source',
|
||||
type: 'string',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
];
|
||||
|
||||
const fieldsTwo = [
|
||||
{
|
||||
name: 'timestamp',
|
||||
displayName: 'timestampLabel',
|
||||
type: 'date',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
aggregationRestrictions: {
|
||||
date_histogram: {
|
||||
agg: 'date_histogram',
|
||||
fixed_interval: '1d',
|
||||
delay: '7d',
|
||||
time_zone: 'UTC',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'bytes',
|
||||
displayName: 'bytes',
|
||||
type: 'number',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
aggregationRestrictions: {
|
||||
histogram: {
|
||||
agg: 'histogram',
|
||||
interval: 1000,
|
||||
},
|
||||
max: {
|
||||
agg: 'max',
|
||||
},
|
||||
min: {
|
||||
agg: 'min',
|
||||
},
|
||||
sum: {
|
||||
agg: 'sum',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'source',
|
||||
displayName: 'source',
|
||||
type: 'string',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
aggregationRestrictions: {
|
||||
terms: {
|
||||
agg: 'terms',
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const fieldsThree = [
|
||||
{
|
||||
name: 'timestamp',
|
||||
displayName: 'timestampLabel',
|
||||
type: 'date',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'memory',
|
||||
displayName: 'memory',
|
||||
type: 'number',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'source',
|
||||
displayName: 'source',
|
||||
type: 'string',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
];
|
||||
|
||||
const initialState: IndexPatternPrivateState = {
|
||||
indexPatternRefs: [
|
||||
{ id: '1', title: 'my-fake-index-pattern' },
|
||||
|
@ -63,129 +178,24 @@ const initialState: IndexPatternPrivateState = {
|
|||
title: 'my-fake-index-pattern',
|
||||
timeFieldName: 'timestamp',
|
||||
hasRestrictions: false,
|
||||
fields: [
|
||||
{
|
||||
name: 'timestamp',
|
||||
displayName: 'timestampLabel',
|
||||
type: 'date',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'bytes',
|
||||
displayName: 'bytes',
|
||||
type: 'number',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'memory',
|
||||
displayName: 'memory',
|
||||
type: 'number',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'unsupported',
|
||||
displayName: 'unsupported',
|
||||
type: 'geo',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'source',
|
||||
displayName: 'source',
|
||||
type: 'string',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
],
|
||||
fields: fieldsOne,
|
||||
getFieldByName: getFieldByNameFactory(fieldsOne),
|
||||
},
|
||||
'2': {
|
||||
id: '2',
|
||||
title: 'my-fake-restricted-pattern',
|
||||
hasRestrictions: true,
|
||||
timeFieldName: 'timestamp',
|
||||
fields: [
|
||||
{
|
||||
name: 'timestamp',
|
||||
displayName: 'timestampLabel',
|
||||
type: 'date',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
aggregationRestrictions: {
|
||||
date_histogram: {
|
||||
agg: 'date_histogram',
|
||||
fixed_interval: '1d',
|
||||
delay: '7d',
|
||||
time_zone: 'UTC',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'bytes',
|
||||
displayName: 'bytes',
|
||||
type: 'number',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
aggregationRestrictions: {
|
||||
histogram: {
|
||||
agg: 'histogram',
|
||||
interval: 1000,
|
||||
},
|
||||
max: {
|
||||
agg: 'max',
|
||||
},
|
||||
min: {
|
||||
agg: 'min',
|
||||
},
|
||||
sum: {
|
||||
agg: 'sum',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'source',
|
||||
displayName: 'source',
|
||||
type: 'string',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
aggregationRestrictions: {
|
||||
terms: {
|
||||
agg: 'terms',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
fields: fieldsTwo,
|
||||
getFieldByName: getFieldByNameFactory(fieldsTwo),
|
||||
},
|
||||
'3': {
|
||||
id: '3',
|
||||
title: 'my-compatible-pattern',
|
||||
timeFieldName: 'timestamp',
|
||||
hasRestrictions: false,
|
||||
fields: [
|
||||
{
|
||||
name: 'timestamp',
|
||||
displayName: 'timestampLabel',
|
||||
type: 'date',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'memory',
|
||||
displayName: 'memory',
|
||||
type: 'number',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'source',
|
||||
displayName: 'source',
|
||||
type: 'string',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
],
|
||||
fields: fieldsThree,
|
||||
getFieldByName: getFieldByNameFactory(fieldsThree),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -285,15 +285,10 @@ describe('loader', () => {
|
|||
} as unknown) as Pick<IndexPatternsContract, 'get'>,
|
||||
});
|
||||
|
||||
expect(
|
||||
cache.foo.fields.find((f: IndexPatternField) => f.name === 'bytes')!.aggregationRestrictions
|
||||
).toEqual({
|
||||
expect(cache.foo.getFieldByName('bytes')!.aggregationRestrictions).toEqual({
|
||||
sum: { agg: 'sum' },
|
||||
});
|
||||
expect(
|
||||
cache.foo.fields.find((f: IndexPatternField) => f.name === 'timestamp')!
|
||||
.aggregationRestrictions
|
||||
).toEqual({
|
||||
expect(cache.foo.getFieldByName('timestamp')!.aggregationRestrictions).toEqual({
|
||||
date_histogram: { agg: 'date_histogram', fixed_interval: 'm' },
|
||||
});
|
||||
});
|
||||
|
@ -342,9 +337,7 @@ describe('loader', () => {
|
|||
} as unknown) as Pick<IndexPatternsContract, 'get'>,
|
||||
});
|
||||
|
||||
expect(cache.foo.fields.find((f: IndexPatternField) => f.name === 'timestamp')!.meta).toEqual(
|
||||
true
|
||||
);
|
||||
expect(cache.foo.getFieldByName('timestamp')!.meta).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ import {
|
|||
import { VisualizeFieldContext } from '../../../../../src/plugins/ui_actions/public';
|
||||
import { documentField } from './document_field';
|
||||
import { readFromStorage, writeToStorage } from '../settings_storage';
|
||||
import { getFieldByNameFactory } from './pure_helpers';
|
||||
|
||||
type SetState = StateSetter<IndexPatternPrivateState>;
|
||||
type SavedObjectsClient = Pick<SavedObjectsClientContract, 'find'>;
|
||||
|
@ -112,6 +113,7 @@ export async function loadIndexPatterns({
|
|||
])
|
||||
),
|
||||
fields: newFields,
|
||||
getFieldByName: getFieldByNameFactory(newFields),
|
||||
hasRestrictions: !!typeMeta?.aggs,
|
||||
};
|
||||
|
||||
|
|
|
@ -5,14 +5,11 @@
|
|||
*/
|
||||
|
||||
import { DragContextState } from '../drag_drop';
|
||||
import { getFieldByNameFactory } from './pure_helpers';
|
||||
import { IndexPattern } from './types';
|
||||
|
||||
export const createMockedIndexPattern = (): IndexPattern => ({
|
||||
id: '1',
|
||||
title: 'my-fake-index-pattern',
|
||||
timeFieldName: 'timestamp',
|
||||
hasRestrictions: false,
|
||||
fields: [
|
||||
export const createMockedIndexPattern = (): IndexPattern => {
|
||||
const fields = [
|
||||
{
|
||||
name: 'timestamp',
|
||||
displayName: 'timestampLabel',
|
||||
|
@ -74,16 +71,19 @@ export const createMockedIndexPattern = (): IndexPattern => ({
|
|||
lang: 'painless',
|
||||
script: '1234',
|
||||
},
|
||||
],
|
||||
});
|
||||
];
|
||||
return {
|
||||
id: '1',
|
||||
title: 'my-fake-index-pattern',
|
||||
timeFieldName: 'timestamp',
|
||||
hasRestrictions: false,
|
||||
fields,
|
||||
getFieldByName: getFieldByNameFactory(fields),
|
||||
};
|
||||
};
|
||||
|
||||
export const createMockedRestrictedIndexPattern = () => ({
|
||||
id: '2',
|
||||
title: 'my-fake-restricted-pattern',
|
||||
timeFieldName: 'timestamp',
|
||||
hasRestrictions: true,
|
||||
fieldFormatMap: { bytes: { id: 'bytes', params: { pattern: '0.0' } } },
|
||||
fields: [
|
||||
export const createMockedRestrictedIndexPattern = () => {
|
||||
const fields = [
|
||||
{
|
||||
name: 'timestamp',
|
||||
displayName: 'timestampLabel',
|
||||
|
@ -109,54 +109,63 @@ export const createMockedRestrictedIndexPattern = () => ({
|
|||
lang: 'painless',
|
||||
script: '1234',
|
||||
},
|
||||
],
|
||||
typeMeta: {
|
||||
params: {
|
||||
rollup_index: 'my-fake-index-pattern',
|
||||
},
|
||||
aggs: {
|
||||
terms: {
|
||||
source: {
|
||||
agg: 'terms',
|
||||
},
|
||||
];
|
||||
return {
|
||||
id: '2',
|
||||
title: 'my-fake-restricted-pattern',
|
||||
timeFieldName: 'timestamp',
|
||||
hasRestrictions: true,
|
||||
fieldFormatMap: { bytes: { id: 'bytes', params: { pattern: '0.0' } } },
|
||||
fields,
|
||||
getFieldByName: getFieldByNameFactory(fields),
|
||||
typeMeta: {
|
||||
params: {
|
||||
rollup_index: 'my-fake-index-pattern',
|
||||
},
|
||||
date_histogram: {
|
||||
timestamp: {
|
||||
agg: 'date_histogram',
|
||||
fixed_interval: '1d',
|
||||
delay: '7d',
|
||||
time_zone: 'UTC',
|
||||
aggs: {
|
||||
terms: {
|
||||
source: {
|
||||
agg: 'terms',
|
||||
},
|
||||
},
|
||||
},
|
||||
histogram: {
|
||||
bytes: {
|
||||
agg: 'histogram',
|
||||
interval: 1000,
|
||||
date_histogram: {
|
||||
timestamp: {
|
||||
agg: 'date_histogram',
|
||||
fixed_interval: '1d',
|
||||
delay: '7d',
|
||||
time_zone: 'UTC',
|
||||
},
|
||||
},
|
||||
},
|
||||
avg: {
|
||||
bytes: {
|
||||
agg: 'avg',
|
||||
histogram: {
|
||||
bytes: {
|
||||
agg: 'histogram',
|
||||
interval: 1000,
|
||||
},
|
||||
},
|
||||
},
|
||||
max: {
|
||||
bytes: {
|
||||
agg: 'max',
|
||||
avg: {
|
||||
bytes: {
|
||||
agg: 'avg',
|
||||
},
|
||||
},
|
||||
},
|
||||
min: {
|
||||
bytes: {
|
||||
agg: 'min',
|
||||
max: {
|
||||
bytes: {
|
||||
agg: 'max',
|
||||
},
|
||||
},
|
||||
},
|
||||
sum: {
|
||||
bytes: {
|
||||
agg: 'sum',
|
||||
min: {
|
||||
bytes: {
|
||||
agg: 'min',
|
||||
},
|
||||
},
|
||||
sum: {
|
||||
bytes: {
|
||||
agg: 'sum',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
export function createMockedDragDropContext(): jest.Mocked<DragContextState> {
|
||||
return {
|
||||
|
|
|
@ -43,7 +43,7 @@ export const cardinalityOperation: OperationDefinition<CardinalityIndexPatternCo
|
|||
}
|
||||
},
|
||||
isTransferable: (column, newIndexPattern) => {
|
||||
const newField = newIndexPattern.fields.find((field) => field.name === column.sourceField);
|
||||
const newField = newIndexPattern.getFieldByName(column.sourceField);
|
||||
|
||||
return Boolean(
|
||||
newField &&
|
||||
|
|
|
@ -18,6 +18,7 @@ import {
|
|||
} from '../../../../../../../src/plugins/data/public/mocks';
|
||||
import { createMockedIndexPattern } from '../../mocks';
|
||||
import { IndexPatternPrivateState } from '../../types';
|
||||
import { getFieldByNameFactory } from '../../pure_helpers';
|
||||
|
||||
const dataStart = dataPluginMock.createStartContract();
|
||||
dataStart.search.aggs.calculateAutoTimeExpression = getCalculateAutoTimeExpression(
|
||||
|
@ -66,6 +67,17 @@ describe('date_histogram', () => {
|
|||
searchable: true,
|
||||
},
|
||||
],
|
||||
|
||||
getFieldByName: getFieldByNameFactory([
|
||||
{
|
||||
name: 'timestamp',
|
||||
displayName: 'timestampLabel',
|
||||
type: 'date',
|
||||
esTypes: ['date'],
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
]),
|
||||
},
|
||||
2: {
|
||||
id: '2',
|
||||
|
@ -81,6 +93,16 @@ describe('date_histogram', () => {
|
|||
searchable: true,
|
||||
},
|
||||
],
|
||||
getFieldByName: getFieldByNameFactory([
|
||||
{
|
||||
name: 'other_timestamp',
|
||||
displayName: 'other_timestamp',
|
||||
type: 'date',
|
||||
esTypes: ['date'],
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
]),
|
||||
},
|
||||
},
|
||||
layers: {
|
||||
|
@ -267,6 +289,22 @@ describe('date_histogram', () => {
|
|||
},
|
||||
},
|
||||
],
|
||||
getFieldByName: getFieldByNameFactory([
|
||||
{
|
||||
name: 'timestamp',
|
||||
displayName: 'timestamp',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
type: 'date',
|
||||
aggregationRestrictions: {
|
||||
date_histogram: {
|
||||
agg: 'date_histogram',
|
||||
time_zone: 'UTC',
|
||||
calendar_interval: '42w',
|
||||
},
|
||||
},
|
||||
},
|
||||
]),
|
||||
}
|
||||
);
|
||||
expect(esAggsConfig).toEqual(
|
||||
|
@ -294,7 +332,7 @@ describe('date_histogram', () => {
|
|||
},
|
||||
};
|
||||
const indexPattern = createMockedIndexPattern();
|
||||
const newDateField = indexPattern.fields.find((i) => i.name === 'start_date')!;
|
||||
const newDateField = indexPattern.getFieldByName('start_date')!;
|
||||
|
||||
const column = dateHistogramOperation.onFieldChange(oldColumn, indexPattern, newDateField);
|
||||
expect(column).toHaveProperty('sourceField', 'start_date');
|
||||
|
@ -314,7 +352,7 @@ describe('date_histogram', () => {
|
|||
},
|
||||
};
|
||||
const indexPattern = createMockedIndexPattern();
|
||||
const newDateField = indexPattern.fields.find((i) => i.name === 'start_date')!;
|
||||
const newDateField = indexPattern.getFieldByName('start_date')!;
|
||||
|
||||
const column = dateHistogramOperation.onFieldChange(oldColumn, indexPattern, newDateField);
|
||||
expect(column).toHaveProperty('sourceField', 'start_date');
|
||||
|
@ -356,6 +394,22 @@ describe('date_histogram', () => {
|
|||
},
|
||||
},
|
||||
],
|
||||
getFieldByName: getFieldByNameFactory([
|
||||
{
|
||||
name: 'dateField',
|
||||
displayName: 'dateField',
|
||||
type: 'date',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
aggregationRestrictions: {
|
||||
date_histogram: {
|
||||
agg: 'date_histogram',
|
||||
time_zone: 'CET',
|
||||
calendar_interval: 'w',
|
||||
},
|
||||
},
|
||||
},
|
||||
]),
|
||||
}
|
||||
);
|
||||
expect(transferedColumn).toEqual(
|
||||
|
@ -393,6 +447,15 @@ describe('date_histogram', () => {
|
|||
searchable: true,
|
||||
},
|
||||
],
|
||||
getFieldByName: getFieldByNameFactory([
|
||||
{
|
||||
name: 'dateField',
|
||||
displayName: 'dateField',
|
||||
type: 'date',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
]),
|
||||
}
|
||||
);
|
||||
expect(transferedColumn).toEqual(
|
||||
|
@ -609,6 +672,18 @@ describe('date_histogram', () => {
|
|||
},
|
||||
},
|
||||
],
|
||||
getFieldByName: getFieldByNameFactory([
|
||||
{
|
||||
...state.indexPatterns[1].fields[0],
|
||||
aggregationRestrictions: {
|
||||
date_histogram: {
|
||||
agg: 'date_histogram',
|
||||
time_zone: 'UTC',
|
||||
calendar_interval: '1h',
|
||||
},
|
||||
},
|
||||
},
|
||||
]),
|
||||
},
|
||||
},
|
||||
}}
|
||||
|
|
|
@ -81,7 +81,7 @@ export const dateHistogramOperation: OperationDefinition<
|
|||
};
|
||||
},
|
||||
isTransferable: (column, newIndexPattern) => {
|
||||
const newField = newIndexPattern.fields.find((field) => field.name === column.sourceField);
|
||||
const newField = newIndexPattern.getFieldByName(column.sourceField);
|
||||
|
||||
return Boolean(
|
||||
newField &&
|
||||
|
@ -91,12 +91,9 @@ export const dateHistogramOperation: OperationDefinition<
|
|||
);
|
||||
},
|
||||
transfer: (column, newIndexPattern) => {
|
||||
const newField = newIndexPattern.fields.find((field) => field.name === column.sourceField);
|
||||
if (
|
||||
newField &&
|
||||
newField.aggregationRestrictions &&
|
||||
newField.aggregationRestrictions.date_histogram
|
||||
) {
|
||||
const newField = newIndexPattern.getFieldByName(column.sourceField);
|
||||
|
||||
if (newField?.aggregationRestrictions?.date_histogram) {
|
||||
const restrictions = newField.aggregationRestrictions.date_histogram;
|
||||
|
||||
return {
|
||||
|
@ -123,7 +120,7 @@ export const dateHistogramOperation: OperationDefinition<
|
|||
};
|
||||
},
|
||||
toEsAggsConfig: (column, columnId, indexPattern) => {
|
||||
const usedField = indexPattern.fields.find((field) => field.name === column.sourceField);
|
||||
const usedField = indexPattern.getFieldByName(column.sourceField);
|
||||
return {
|
||||
id: columnId,
|
||||
enabled: true,
|
||||
|
@ -132,7 +129,7 @@ export const dateHistogramOperation: OperationDefinition<
|
|||
params: {
|
||||
field: column.sourceField,
|
||||
time_zone: column.params.timeZone,
|
||||
useNormalizedEsInterval: !usedField || !usedField.aggregationRestrictions?.date_histogram,
|
||||
useNormalizedEsInterval: !usedField?.aggregationRestrictions?.date_histogram,
|
||||
interval: column.params.interval,
|
||||
drop_partials: false,
|
||||
min_doc_count: 0,
|
||||
|
@ -143,8 +140,8 @@ export const dateHistogramOperation: OperationDefinition<
|
|||
paramEditor: ({ state, setState, currentColumn, layerId, dateRange, data }) => {
|
||||
const field =
|
||||
currentColumn &&
|
||||
state.indexPatterns[state.layers[layerId].indexPatternId].fields.find(
|
||||
(currentField) => currentField.name === currentColumn.sourceField
|
||||
state.indexPatterns[state.layers[layerId].indexPatternId].getFieldByName(
|
||||
currentColumn.sourceField
|
||||
);
|
||||
const intervalIsRestricted =
|
||||
field!.aggregationRestrictions && field!.aggregationRestrictions.date_histogram;
|
||||
|
|
|
@ -43,7 +43,7 @@ function buildMetricOperation<T extends MetricColumn<string>>({
|
|||
}
|
||||
},
|
||||
isTransferable: (column, newIndexPattern) => {
|
||||
const newField = newIndexPattern.fields.find((field) => field.name === column.sourceField);
|
||||
const newField = newIndexPattern.getFieldByName(column.sourceField);
|
||||
|
||||
return Boolean(
|
||||
newField &&
|
||||
|
|
|
@ -30,6 +30,7 @@ import {
|
|||
} from './constants';
|
||||
import { RangePopover } from './advanced_editor';
|
||||
import { DragDropBuckets } from '../shared_components';
|
||||
import { getFieldByNameFactory } from '../../../pure_helpers';
|
||||
|
||||
const dataPluginMockValue = dataPluginMock.createStartContract();
|
||||
// need to overwrite the formatter field first
|
||||
|
@ -96,6 +97,9 @@ describe('ranges', () => {
|
|||
title: 'my_index_pattern',
|
||||
hasRestrictions: false,
|
||||
fields: [{ name: sourceField, type: 'number', displayName: sourceField }],
|
||||
getFieldByName: getFieldByNameFactory([
|
||||
{ name: sourceField, type: 'number', displayName: sourceField },
|
||||
]),
|
||||
},
|
||||
},
|
||||
existingFields: {},
|
||||
|
|
|
@ -140,7 +140,7 @@ export const rangeOperation: OperationDefinition<RangeIndexPatternColumn, 'field
|
|||
};
|
||||
},
|
||||
isTransferable: (column, newIndexPattern) => {
|
||||
const newField = newIndexPattern.fields.find((field) => field.name === column.sourceField);
|
||||
const newField = newIndexPattern.getFieldByName(column.sourceField);
|
||||
|
||||
return Boolean(
|
||||
newField &&
|
||||
|
@ -168,9 +168,7 @@ export const rangeOperation: OperationDefinition<RangeIndexPatternColumn, 'field
|
|||
},
|
||||
paramEditor: ({ state, setState, currentColumn, layerId, columnId, uiSettings, data }) => {
|
||||
const indexPattern = state.indexPatterns[state.layers[layerId].indexPatternId];
|
||||
const currentField = indexPattern.fields.find(
|
||||
(field) => field.name === currentColumn.sourceField
|
||||
);
|
||||
const currentField = indexPattern.getFieldByName(currentColumn.sourceField);
|
||||
const numberFormat = currentColumn.params.format;
|
||||
const numberFormatterPattern =
|
||||
numberFormat &&
|
||||
|
|
|
@ -61,7 +61,7 @@ export const termsOperation: OperationDefinition<TermsIndexPatternColumn, 'field
|
|||
}
|
||||
},
|
||||
isTransferable: (column, newIndexPattern) => {
|
||||
const newField = newIndexPattern.fields.find((field) => field.name === column.sourceField);
|
||||
const newField = newIndexPattern.getFieldByName(column.sourceField);
|
||||
|
||||
return Boolean(
|
||||
newField &&
|
||||
|
|
|
@ -103,7 +103,7 @@ describe('terms', () => {
|
|||
},
|
||||
};
|
||||
const indexPattern = createMockedIndexPattern();
|
||||
const newNumberField = indexPattern.fields.find((i) => i.name === 'bytes')!;
|
||||
const newNumberField = indexPattern.getFieldByName('bytes')!;
|
||||
|
||||
const column = termsOperation.onFieldChange(oldColumn, indexPattern, newNumberField);
|
||||
expect(column).toHaveProperty('dataType', 'number');
|
||||
|
|
|
@ -8,38 +8,42 @@ import { getOperationTypesForField, getAvailableOperationsByMetadata, buildColum
|
|||
import { AvgIndexPatternColumn } from './definitions/metrics';
|
||||
import { IndexPatternPrivateState } from '../types';
|
||||
import { documentField } from '../document_field';
|
||||
import { getFieldByNameFactory } from '../pure_helpers';
|
||||
|
||||
jest.mock('../loader');
|
||||
|
||||
const fields = [
|
||||
{
|
||||
name: 'timestamp',
|
||||
displayName: 'timestamp',
|
||||
type: 'date',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'bytes',
|
||||
displayName: 'bytes',
|
||||
type: 'number',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'source',
|
||||
displayName: 'source',
|
||||
type: 'string',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
];
|
||||
|
||||
const expectedIndexPatterns = {
|
||||
1: {
|
||||
id: '1',
|
||||
title: 'my-fake-index-pattern',
|
||||
timeFieldName: 'timestamp',
|
||||
hasRestrictions: false,
|
||||
fields: [
|
||||
{
|
||||
name: 'timestamp',
|
||||
displayName: 'timestamp',
|
||||
type: 'date',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'bytes',
|
||||
displayName: 'bytes',
|
||||
type: 'number',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
name: 'source',
|
||||
displayName: 'source',
|
||||
type: 'string',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
],
|
||||
fields,
|
||||
getFieldByName: getFieldByNameFactory(fields),
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { IndexPatternPrivateState } from './types';
|
||||
import { keyBy } from 'lodash';
|
||||
import { IndexPatternField, IndexPatternPrivateState } from './types';
|
||||
|
||||
export function fieldExists(
|
||||
existingFields: IndexPatternPrivateState['existingFields'],
|
||||
|
@ -13,3 +14,8 @@ export function fieldExists(
|
|||
) {
|
||||
return existingFields[indexPatternTitle] && existingFields[indexPatternTitle][fieldName];
|
||||
}
|
||||
|
||||
export function getFieldByNameFactory(newFields: IndexPatternField[]) {
|
||||
const fieldsLookup = keyBy(newFields, 'name');
|
||||
return (name: string) => fieldsLookup[name];
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import { TermsIndexPatternColumn } from './operations/definitions/terms';
|
|||
import { DateHistogramIndexPatternColumn } from './operations/definitions/date_histogram';
|
||||
import { AvgIndexPatternColumn } from './operations/definitions/metrics';
|
||||
import { IndexPattern, IndexPatternPrivateState, IndexPatternLayer } from './types';
|
||||
import { getFieldByNameFactory } from './pure_helpers';
|
||||
|
||||
jest.mock('./operations');
|
||||
|
||||
|
@ -585,59 +586,61 @@ describe('state_helpers', () => {
|
|||
});
|
||||
|
||||
describe('updateLayerIndexPattern', () => {
|
||||
const fields = [
|
||||
{
|
||||
name: 'fieldA',
|
||||
displayName: 'fieldA',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
name: 'fieldB',
|
||||
displayName: 'fieldB',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
type: 'number',
|
||||
aggregationRestrictions: {
|
||||
avg: {
|
||||
agg: 'avg',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'fieldC',
|
||||
displayName: 'fieldC',
|
||||
aggregatable: false,
|
||||
searchable: true,
|
||||
type: 'date',
|
||||
},
|
||||
{
|
||||
name: 'fieldD',
|
||||
displayName: 'fieldD',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
type: 'date',
|
||||
aggregationRestrictions: {
|
||||
date_histogram: {
|
||||
agg: 'date_histogram',
|
||||
time_zone: 'CET',
|
||||
calendar_interval: 'w',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'fieldE',
|
||||
displayName: 'fieldE',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
type: 'date',
|
||||
},
|
||||
];
|
||||
const indexPattern: IndexPattern = {
|
||||
id: 'test',
|
||||
title: '',
|
||||
hasRestrictions: true,
|
||||
fields: [
|
||||
{
|
||||
name: 'fieldA',
|
||||
displayName: 'fieldA',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
name: 'fieldB',
|
||||
displayName: 'fieldB',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
type: 'number',
|
||||
aggregationRestrictions: {
|
||||
avg: {
|
||||
agg: 'avg',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'fieldC',
|
||||
displayName: 'fieldC',
|
||||
aggregatable: false,
|
||||
searchable: true,
|
||||
type: 'date',
|
||||
},
|
||||
{
|
||||
name: 'fieldD',
|
||||
displayName: 'fieldD',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
type: 'date',
|
||||
aggregationRestrictions: {
|
||||
date_histogram: {
|
||||
agg: 'date_histogram',
|
||||
time_zone: 'CET',
|
||||
calendar_interval: 'w',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'fieldE',
|
||||
displayName: 'fieldE',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
type: 'date',
|
||||
},
|
||||
],
|
||||
getFieldByName: getFieldByNameFactory(fields),
|
||||
fields,
|
||||
};
|
||||
|
||||
it('should switch index pattern id in layer', () => {
|
||||
|
|
|
@ -11,6 +11,7 @@ import { IndexPatternAggRestrictions } from '../../../../../src/plugins/data/pub
|
|||
export interface IndexPattern {
|
||||
id: string;
|
||||
fields: IndexPatternField[];
|
||||
getFieldByName(name: string): IndexPatternField | undefined;
|
||||
title: string;
|
||||
timeFieldName?: string;
|
||||
fieldFormatMap?: Record<
|
||||
|
|
|
@ -87,15 +87,15 @@ export function fieldIsInvalid(
|
|||
indexPattern: IndexPattern
|
||||
) {
|
||||
const operationDefinition = operationType && operationDefinitionMap[operationType];
|
||||
const field = sourceField ? indexPattern.getFieldByName(sourceField) : undefined;
|
||||
|
||||
return Boolean(
|
||||
sourceField &&
|
||||
operationDefinition &&
|
||||
!indexPattern.fields.some(
|
||||
(field) =>
|
||||
field.name === sourceField &&
|
||||
operationDefinition?.input === 'field' &&
|
||||
operationDefinition.getPossibleOperationForField(field) !== undefined
|
||||
!(
|
||||
field &&
|
||||
operationDefinition?.input === 'field' &&
|
||||
operationDefinition.getPossibleOperationForField(field) !== undefined
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue