From b14849298447360a436df1c39b2785670797bf2e Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Thu, 22 Aug 2019 17:38:16 +0200 Subject: [PATCH 01/10] simplify types and add documentation to operations framework --- .../dimension_panel/dimension_panel.test.tsx | 2 +- .../dimension_panel/dimension_panel.tsx | 2 +- .../dimension_panel/popover_editor.tsx | 3 +- .../indexpattern_suggestions.ts | 2 +- .../public/indexpattern_plugin/operations.ts | 200 ----------------- .../{ => operations}/__mocks__/operations.ts | 2 +- .../definitions}/count.tsx | 21 +- .../definitions}/date_histogram.test.tsx | 2 +- .../definitions}/date_histogram.tsx | 26 +-- .../definitions}/filter_ratio.test.tsx | 4 +- .../definitions}/filter_ratio.tsx | 25 +-- .../operations/definitions/index.ts | 173 +++++++++++++++ .../definitions}/metrics.tsx | 49 ++--- .../definitions}/terms.test.tsx | 44 ++-- .../definitions}/terms.tsx | 21 +- .../indexpattern_plugin/operations/index.ts | 7 + .../{ => operations}/operations.test.ts | 10 +- .../operations/operations.ts | 208 ++++++++++++++++++ .../indexpattern_plugin/state_helpers.test.ts | 2 +- .../indexpattern_plugin/state_helpers.ts | 13 +- .../indexpattern_plugin/to_expression.ts | 10 +- 21 files changed, 488 insertions(+), 338 deletions(-) delete mode 100644 x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations.ts rename x-pack/legacy/plugins/lens/public/indexpattern_plugin/{ => operations}/__mocks__/operations.ts (90%) rename x-pack/legacy/plugins/lens/public/indexpattern_plugin/{operation_definitions => operations/definitions}/count.tsx (74%) rename x-pack/legacy/plugins/lens/public/indexpattern_plugin/{operation_definitions => operations/definitions}/date_histogram.test.tsx (99%) rename x-pack/legacy/plugins/lens/public/indexpattern_plugin/{operation_definitions => operations/definitions}/date_histogram.tsx (93%) rename x-pack/legacy/plugins/lens/public/indexpattern_plugin/{operation_definitions => operations/definitions}/filter_ratio.test.tsx (97%) rename x-pack/legacy/plugins/lens/public/indexpattern_plugin/{operation_definitions => operations/definitions}/filter_ratio.tsx (90%) create mode 100644 x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/index.ts rename x-pack/legacy/plugins/lens/public/indexpattern_plugin/{operation_definitions => operations/definitions}/metrics.tsx (83%) rename x-pack/legacy/plugins/lens/public/indexpattern_plugin/{operation_definitions => operations/definitions}/terms.test.tsx (95%) rename x-pack/legacy/plugins/lens/public/indexpattern_plugin/{operation_definitions => operations/definitions}/terms.tsx (94%) create mode 100644 x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/index.ts rename x-pack/legacy/plugins/lens/public/indexpattern_plugin/{ => operations}/operations.test.ts (98%) create mode 100644 x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/operations.ts diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.test.tsx index f45b674e0c19b..aaeeb63f0934b 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.test.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.test.tsx @@ -20,7 +20,7 @@ import { Storage } from 'ui/storage'; jest.mock('ui/new_platform'); jest.mock('../loader'); jest.mock('../state_helpers'); -jest.mock('../operations'); +jest.mock('../operations/operations'); // Used by indexpattern plugin, which is a dependency of a dependency jest.mock('ui/chrome'); diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.tsx index f6d80b38c6ab9..3cafbb17d270a 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.tsx @@ -18,7 +18,7 @@ import { OperationType, } from '../indexpattern'; -import { getAvailableOperationsByMetadata, buildColumn } from '../operations'; +import { getAvailableOperationsByMetadata, buildColumn } from '../operations/operations'; import { PopoverEditor } from './popover_editor'; import { DragContextState, ChildDragDropProvider, DragDrop } from '../../drag_drop'; import { changeColumn, deleteColumn } from '../state_helpers'; diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/popover_editor.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/popover_editor.tsx index ebf87fb1bdebb..35c06ec180b06 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/popover_editor.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/popover_editor.tsx @@ -28,11 +28,12 @@ import { IndexPatternField, } from '../indexpattern'; import { IndexPatternDimensionPanelProps, OperationFieldSupportMatrix } from './dimension_panel'; -import { operationDefinitionMap, getOperationDisplay, buildColumn } from '../operations'; +import { getOperationDisplay, buildColumn } from '../operations/operations'; import { deleteColumn, changeColumn } from '../state_helpers'; import { FieldSelect } from './field_select'; import { hasField } from '../utils'; import { BucketNestingEditor } from './bucket_nesting_editor'; +import { operationDefinitionMap } from '../operations'; const operationPanels = getOperationDisplay(); diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern_suggestions.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern_suggestions.ts index a3a04802b0a31..e22ebd0e227da 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern_suggestions.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern_suggestions.ts @@ -14,7 +14,7 @@ import { IndexPatternPrivateState, IndexPattern, } from './indexpattern'; -import { buildColumn, getOperationTypesForField } from './operations'; +import { buildColumn, getOperationTypesForField } from './operations/operations'; import { hasField } from './utils'; function buildSuggestion({ diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations.ts deleted file mode 100644 index ea36b8243e911..0000000000000 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations.ts +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import _ from 'lodash'; -import { Storage } from 'ui/storage'; -import { UiSettingsClientContract } from 'src/core/public'; -import { DimensionPriority, OperationMetadata, StateSetter } from '../types'; -import { - IndexPatternColumn, - IndexPatternField, - IndexPatternPrivateState, - OperationType, - BaseIndexPatternColumn, - IndexPattern, -} from './indexpattern'; -import { termsOperation } from './operation_definitions/terms'; -import { - minOperation, - averageOperation, - sumOperation, - maxOperation, -} from './operation_definitions/metrics'; -import { dateHistogramOperation } from './operation_definitions/date_histogram'; -import { countOperation } from './operation_definitions/count'; -import { filterRatioOperation } from './operation_definitions/filter_ratio'; - -type PossibleOperationDefinition< - U extends IndexPatternColumn = IndexPatternColumn -> = U extends IndexPatternColumn ? OperationDefinition : never; - -type PossibleOperationDefinitionMapEntyries< - U extends PossibleOperationDefinition = PossibleOperationDefinition -> = U extends PossibleOperationDefinition ? { [K in U['type']]: U } : never; - -type UnionToIntersection = (U extends U ? (k: U) => void : never) extends ((k: infer I) => void) - ? I - : never; - -// this type makes sure that there is an operation definition for each column type -export type AllOperationDefinitions = UnionToIntersection; - -export const operationDefinitionMap: AllOperationDefinitions = { - terms: termsOperation, - date_histogram: dateHistogramOperation, - min: minOperation, - max: maxOperation, - avg: averageOperation, - sum: sumOperation, - count: countOperation, - filter_ratio: filterRatioOperation, -}; -const operationDefinitions: PossibleOperationDefinition[] = Object.values(operationDefinitionMap); - -export function getOperations(): OperationType[] { - return Object.keys(operationDefinitionMap) as OperationType[]; -} - -export interface ParamEditorProps { - state: IndexPatternPrivateState; - setState: StateSetter; - columnId: string; - layerId: string; - uiSettings: UiSettingsClientContract; - storage: Storage; -} - -export interface OperationDefinition { - type: C['operationType']; - displayName: string; - getPossibleOperationsForDocument: (indexPattern: IndexPattern) => OperationMetadata[]; - getPossibleOperationsForField: (field: IndexPatternField) => OperationMetadata[]; - buildColumn: (arg: { - suggestedPriority: DimensionPriority | undefined; - layerId: string; - columns: Partial>; - field?: IndexPatternField; - }) => C; - onOtherColumnChanged?: ( - currentColumn: C, - columns: Partial> - ) => C; - paramEditor?: React.ComponentType; - toEsAggsConfig: (column: C, columnId: string) => unknown; - isTransferable: (column: C, newIndexPattern: IndexPattern) => boolean; - transfer?: (column: C, newIndexPattern: IndexPattern) => C; -} - -export function isColumnTransferable(column: IndexPatternColumn, newIndexPattern: IndexPattern) { - return (operationDefinitionMap[column.operationType] as OperationDefinition< - IndexPatternColumn - >).isTransferable(column, newIndexPattern); -} - -export function getOperationDisplay() { - const display = {} as Record< - OperationType, - { - type: OperationType; - displayName: string; - } - >; - operationDefinitions.forEach(({ type, displayName }) => { - display[type] = { - type, - displayName, - }; - }); - return display; -} - -export function getOperationTypesForField(field: IndexPatternField) { - return operationDefinitions - .filter( - operationDefinition => operationDefinition.getPossibleOperationsForField(field).length > 0 - ) - .map(({ type }) => type); -} - -type OperationFieldTuple = - | { type: 'field'; operationType: OperationType; field: string } - | { operationType: OperationType; type: 'document' }; - -export function getAvailableOperationsByMetadata(indexPattern: IndexPattern) { - const operationByMetadata: Record< - string, - { operationMetaData: OperationMetadata; operations: OperationFieldTuple[] } - > = {}; - - const addToMap = (operation: OperationFieldTuple) => (operationMetadata: OperationMetadata) => { - const key = JSON.stringify(operationMetadata); - - if (operationByMetadata[key]) { - operationByMetadata[key].operations.push(operation); - } else { - operationByMetadata[key] = { - operationMetaData: operationMetadata, - operations: [operation], - }; - } - }; - - operationDefinitions.forEach(operationDefinition => { - operationDefinition - .getPossibleOperationsForDocument(indexPattern) - .forEach(addToMap({ type: 'document', operationType: operationDefinition.type })); - - indexPattern.fields.forEach(field => { - operationDefinition.getPossibleOperationsForField(field).forEach( - addToMap({ - type: 'field', - operationType: operationDefinition.type, - field: field.name, - }) - ); - }); - }); - - return Object.values(operationByMetadata); -} - -export function buildColumn({ - op, - columns, - field, - layerId, - indexPattern, - suggestedPriority, - asDocumentOperation, -}: { - op?: OperationType; - columns: Partial>; - suggestedPriority: DimensionPriority | undefined; - layerId: string; - indexPattern: IndexPattern; - field?: IndexPatternField; - asDocumentOperation?: boolean; -}): IndexPatternColumn { - let operationDefinition: PossibleOperationDefinition; - - if (op) { - operationDefinition = operationDefinitionMap[op]; - } else if (asDocumentOperation) { - operationDefinition = operationDefinitions.find( - definition => definition.getPossibleOperationsForDocument(indexPattern).length !== 0 - )!; - } else if (field) { - operationDefinition = operationDefinitions.find( - definition => definition.getPossibleOperationsForField(field).length !== 0 - )!; - } - return operationDefinition!.buildColumn({ - columns, - suggestedPriority, - field, - layerId, - }); -} diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/__mocks__/operations.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/__mocks__/operations.ts similarity index 90% rename from x-pack/legacy/plugins/lens/public/indexpattern_plugin/__mocks__/operations.ts rename to x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/__mocks__/operations.ts index a7ab5fc2c6faa..eeaac6659baf8 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/__mocks__/operations.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/__mocks__/operations.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -const actual = jest.requireActual('../operations'); +const actual = jest.requireActual('../../operations/operations'); jest.spyOn(actual.operationDefinitionMap.date_histogram, 'paramEditor'); jest.spyOn(actual.operationDefinitionMap.terms, 'onOtherColumnChanged'); diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/count.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/count.tsx similarity index 74% rename from x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/count.tsx rename to x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/count.tsx index 304999ca8f5bc..6f0a753e4f7b2 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/count.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/count.tsx @@ -5,24 +5,21 @@ */ import { i18n } from '@kbn/i18n'; -import { CountIndexPatternColumn } from '../indexpattern'; -import { OperationDefinition } from '../operations'; +import { CountIndexPatternColumn } from '../../indexpattern'; +import { OperationDefinition } from '.'; export const countOperation: OperationDefinition = { type: 'count', displayName: i18n.translate('xpack.lens.indexPattern.count', { defaultMessage: 'Count', }), - getPossibleOperationsForField: () => [], - getPossibleOperationsForDocument: () => { - return [ - { - dataType: 'number', - isBucketed: false, - isMetric: true, - scale: 'ratio', - }, - ]; + getPossibleOperationForDocument: () => { + return { + dataType: 'number', + isBucketed: false, + isMetric: true, + scale: 'ratio', + }; }, buildColumn({ suggestedPriority }) { return { diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/date_histogram.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.test.tsx similarity index 99% rename from x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/date_histogram.test.tsx rename to x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.test.tsx index 299d5ca8250c7..c3ad693aab388 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/date_histogram.test.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { dateHistogramOperation } from './date_histogram'; import { shallow } from 'enzyme'; -import { DateHistogramIndexPatternColumn, IndexPatternPrivateState } from '../indexpattern'; +import { DateHistogramIndexPatternColumn, IndexPatternPrivateState } from '../../indexpattern'; import { EuiRange } from '@elastic/eui'; import { UiSettingsClientContract } from 'src/core/public'; import { Storage } from 'ui/storage'; diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/date_histogram.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.tsx similarity index 93% rename from x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/date_histogram.tsx rename to x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.tsx index 8f0b0ff0393d9..c91efc0588243 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/date_histogram.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.tsx @@ -8,10 +8,10 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiForm, EuiFormRow, EuiRange } from '@elastic/eui'; -import { IndexPatternField, DateHistogramIndexPatternColumn } from '../indexpattern'; -import { DimensionPriority } from '../../types'; -import { OperationDefinition } from '../operations'; -import { updateColumnParam } from '../state_helpers'; +import { IndexPatternField, DateHistogramIndexPatternColumn } from '../../indexpattern'; +import { DimensionPriority } from '../../../types'; +import { updateColumnParam } from '../../state_helpers'; +import { OperationDefinition } from '.'; type PropType = C extends React.ComponentType ? P : unknown; @@ -39,23 +39,19 @@ export const dateHistogramOperation: OperationDefinition [], - getPossibleOperationsForField: ({ aggregationRestrictions, aggregatable, type }) => { + getPossibleOperationForField: ({ aggregationRestrictions, aggregatable, type }) => { if ( type === 'date' && aggregatable && (!aggregationRestrictions || aggregationRestrictions.date_histogram) ) { - return [ - { - dataType: 'date', - isBucketed: true, - isMetric: false, - scale: 'interval', - }, - ]; + return { + dataType: 'date', + isBucketed: true, + isMetric: false, + scale: 'interval', + }; } - return []; }, buildColumn({ suggestedPriority, diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/filter_ratio.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/filter_ratio.test.tsx similarity index 97% rename from x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/filter_ratio.test.tsx rename to x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/filter_ratio.test.tsx index 5300d7c69c6fb..a5f6129b7f656 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/filter_ratio.test.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/filter_ratio.test.tsx @@ -8,10 +8,10 @@ import React from 'react'; import { shallowWithIntl } from 'test_utils/enzyme_helpers'; import { act } from 'react-dom/test-utils'; import { filterRatioOperation } from './filter_ratio'; -import { FilterRatioIndexPatternColumn, IndexPatternPrivateState } from '../indexpattern'; +import { FilterRatioIndexPatternColumn, IndexPatternPrivateState } from '../../indexpattern'; import { Storage } from 'ui/storage'; import { UiSettingsClientContract } from 'src/core/public'; -import { QueryBarInput } from '../../../../../../../src/legacy/core_plugins/data/public/query'; +import { QueryBarInput } from '../../../../../../../../src/legacy/core_plugins/data/public/query'; jest.mock('ui/new_platform'); diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/filter_ratio.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/filter_ratio.tsx similarity index 90% rename from x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/filter_ratio.tsx rename to x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/filter_ratio.tsx index 7caada3c9f7a5..13b9d72754be7 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/filter_ratio.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/filter_ratio.tsx @@ -11,26 +11,23 @@ import { EuiButton, EuiFormRow } from '@elastic/eui'; import { Query, QueryBarInput, -} from '../../../../../../../src/legacy/core_plugins/data/public/query'; -import { FilterRatioIndexPatternColumn } from '../indexpattern'; -import { OperationDefinition } from '../operations'; -import { updateColumnParam } from '../state_helpers'; +} from '../../../../../../../../src/legacy/core_plugins/data/public/query'; +import { FilterRatioIndexPatternColumn } from '../../indexpattern'; +import { updateColumnParam } from '../../state_helpers'; +import { OperationDefinition } from '.'; export const filterRatioOperation: OperationDefinition = { type: 'filter_ratio', displayName: i18n.translate('xpack.lens.indexPattern.filterRatio', { defaultMessage: 'Filter Ratio', }), - getPossibleOperationsForField: () => [], - getPossibleOperationsForDocument: () => { - return [ - { - dataType: 'number', - isBucketed: false, - isMetric: true, - scale: 'ratio', - }, - ]; + getPossibleOperationForDocument: () => { + return { + dataType: 'number', + isBucketed: false, + isMetric: true, + scale: 'ratio', + }; }, buildColumn({ suggestedPriority }) { return { diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/index.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/index.ts new file mode 100644 index 0000000000000..397e6027357ca --- /dev/null +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/index.ts @@ -0,0 +1,173 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Storage } from 'ui/storage'; +import { UiSettingsClientContract } from 'src/core/public'; +import { termsOperation } from './terms'; +import { minOperation, averageOperation, sumOperation, maxOperation } from './metrics'; +import { dateHistogramOperation } from './date_histogram'; +import { countOperation } from './count'; +import { filterRatioOperation } from './filter_ratio'; +import { + IndexPatternColumn, + OperationType, + BaseIndexPatternColumn, + IndexPatternField, + IndexPatternPrivateState, + IndexPattern, + FieldBasedIndexPatternColumn, +} from '../../indexpattern'; +import { DimensionPriority, StateSetter, OperationMetadata } from '../../../types'; + +/** + * Properties passed to the operation-specific part of the popover editor + */ +export interface ParamEditorProps { + state: IndexPatternPrivateState; + setState: StateSetter; + columnId: string; + layerId: string; + uiSettings: UiSettingsClientContract; + storage: Storage; +} + +interface BaseOperationDefinitionProps { + type: C['operationType']; + /** + * The name of the operation shown to the user (e.g. in the popover editor). + * Should be i18n-ified. + */ + displayName: string; + // TODO move into field specific part + buildColumn: (arg: { + suggestedPriority: DimensionPriority | undefined; + layerId: string; + columns: Partial>; + field?: IndexPatternField; + }) => C; + /** + * This function is called if another column in the same layer changed or got removed. + * Can be used to update references to other columns (e.g. for sorting). + * Based on the current column and the other updated columns, this function has to + * return an updated column. If not implemented, the `id` function is used instead. + */ + onOtherColumnChanged?: ( + currentColumn: C, + columns: Partial> + ) => C; + /** + * React component for operation specific settings shown in the popover editor + */ + paramEditor?: React.ComponentType; + /** + * Function turning a column into an agg config passed to the `esaggs` function + * together with the agg configs returned from other columns. + */ + toEsAggsConfig: (column: C, columnId: string) => unknown; + /** + * Returns true if the `column` can also be used on `newIndexPattern`. + * If this function returns false, the column is removed when switching index pattern + * for a layer + */ + isTransferable: (column: C, newIndexPattern: IndexPattern) => boolean; + /** + * Transfering a column to another index pattern. This can be used to + * adjust operation specific settings such as reacting to aggregation restrictions + * present on the new index pattern. + */ + transfer?: (column: C, newIndexPattern: IndexPattern) => C; +} + +/** + * Shape of an operation definition. If the type parameter of the definition + * indicates a field based column, `getPossibleOperationForField` has to be + * specified, otherwise `getPossibleOperationForDocument` has to be defined. + */ +export type OperationDefinition< + C extends BaseIndexPatternColumn +> = C extends FieldBasedIndexPatternColumn + ? BaseOperationDefinitionProps & { + /** + * Returns the meta data of the operation if applied to the given field. Undefined + * if the field is not applicable to the operation. + */ + getPossibleOperationForField: (field: IndexPatternField) => OperationMetadata | undefined; + } + : BaseOperationDefinitionProps & { + /** + * Returns the meta data of the operation if applied to documents of the given index pattern. + * Undefined if the operation is not applicable to the index pattern. + */ + getPossibleOperationForDocument: ( + indexPattern: IndexPattern + ) => OperationMetadata | undefined; + }; + +// This type is like mapping over a union of types +// Each possible column type out of the `IndexPatternColumn` union type +// is wrapped into an `OperationDefinition`. The resulting type +// is a union type of all operation definitions +type PossibleOperationDefinition< + U extends IndexPatternColumn = IndexPatternColumn +> = U extends IndexPatternColumn ? OperationDefinition : never; + +// This type once again maps over the union of all operation definitions +// and wraps them in an object with the definition as a single property. The key of +// the property is the operation type of the definition. +// E.g. the terms operation definition maps to `{ terms: OperationDefinition }` +// and so on. +type PossibleOperationDefinitionMapEntries< + U extends PossibleOperationDefinition = PossibleOperationDefinition +> = U extends PossibleOperationDefinition ? { [K in U['type']]: U } : never; + +// This is a helper type changing a union (`a | b | c`) into an intersection `a & b & c` +type UnionToIntersection = (U extends U ? (k: U) => void : never) extends ((k: infer I) => void) + ? I + : never; + +// Here, the helper from above is used to turn the union of all partial operation definition entries +// into an intersection - `{ terms: OperationDefinition, avg: OperationDefinition, ...} +// This type makes sure that +// a) there is an operation definition for each column type +// b) Each operation definition maps to the correct key in the map and there are no mix-ups +type AllOperationDefinitions = UnionToIntersection; + +// This is the map of all available operation definitions. If you are adding a new +// operation and column type, you have to add it to this list, otherwise typescript +// will throw an error. +const internalOperationDefinitionMap: AllOperationDefinitions = { + terms: termsOperation, + date_histogram: dateHistogramOperation, + min: minOperation, + max: maxOperation, + avg: averageOperation, + sum: sumOperation, + count: countOperation, + filter_ratio: filterRatioOperation, +}; + +/** + * List of all available operation definitions + */ +export const operationDefinitions: PossibleOperationDefinition[] = Object.values( + internalOperationDefinitionMap +); + +/** + * Map of all operation visible to consumers (e.g. the dimension panel). + * This simplifies the type of the map and makes it a simple list of unspecified + * operations definitions, because typescript can't infer the type correctly in most + * situations. + * + * If you need a specifically typed version of an operation (e.g. explicitly working with terms), + * you should import the terms definition directly from the sub folder. This map is + * intended to be used in situations where the operation type is not known during compile + * time. + */ +export const operationDefinitionMap = (internalOperationDefinitionMap as unknown) as Record< + OperationType, + OperationDefinition +>; diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/metrics.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/metrics.tsx similarity index 83% rename from x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/metrics.tsx rename to x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/metrics.tsx index 0e45411d90f1b..22def483c24dd 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/metrics.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/metrics.tsx @@ -11,34 +11,30 @@ import { SumIndexPatternColumn, AvgIndexPatternColumn, MaxIndexPatternColumn, -} from '../indexpattern'; -import { OperationDefinition } from '../operations'; +} from '../../indexpattern'; +import { OperationDefinition } from '.'; function buildMetricOperation( type: T['operationType'], displayName: string, ofName: (name: string) => string ) { - const operationDefinition: OperationDefinition = { + return { type, displayName, - getPossibleOperationsForDocument: () => [], - getPossibleOperationsForField: ({ aggregationRestrictions, aggregatable, type: fieldType }) => { + getPossibleOperationForField: ({ aggregationRestrictions, aggregatable, type: fieldType }) => { if ( fieldType === 'number' && aggregatable && (!aggregationRestrictions || aggregationRestrictions[type]) ) { - return [ - { - dataType: 'number', - isBucketed: false, - isMetric: true, - scale: 'ratio', - }, - ]; + return { + dataType: 'number', + isBucketed: false, + isMetric: true, + scale: 'ratio', + }; } - return []; }, isTransferable: (column, newIndexPattern) => { const newField = newIndexPattern.fields.find(field => field.name === column.sourceField); @@ -50,7 +46,16 @@ function buildMetricOperation( (!newField.aggregationRestrictions || newField.aggregationRestrictions![type]) ); }, - buildColumn({ suggestedPriority, field }): T { + toEsAggsConfig: (column, columnId) => ({ + id: columnId, + enabled: true, + type: column.operationType, + schema: 'metric', + params: { + field: column.sourceField, + }, + }), + buildColumn: ({ suggestedPriority, field }) => { if (!field) { throw new Error(`Invariant: A ${type} operation can only be built with a field`); } @@ -63,19 +68,9 @@ function buildMetricOperation( isBucketed: false, isMetric: true, scale: 'ratio', - } as T; + }; }, - toEsAggsConfig: (column, columnId) => ({ - id: columnId, - enabled: true, - type: column.operationType, - schema: 'metric', - params: { - field: column.sourceField, - }, - }), - }; - return operationDefinition; + } as OperationDefinition; } export const minOperation = buildMetricOperation( diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/terms.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/terms.test.tsx similarity index 95% rename from x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/terms.test.tsx rename to x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/terms.test.tsx index def66db01a7fe..52e658b9cc0ad 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/terms.test.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/terms.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { termsOperation } from './terms'; import { shallow } from 'enzyme'; -import { IndexPatternPrivateState, TermsIndexPatternColumn } from '../indexpattern'; +import { IndexPatternPrivateState, TermsIndexPatternColumn } from '../../indexpattern'; import { EuiRange, EuiSelect } from '@elastic/eui'; import { UiSettingsClientContract } from 'src/core/public'; import { Storage } from 'ui/storage'; @@ -75,10 +75,10 @@ describe('terms', () => { }); }); - describe('getPossibleOperationsForField', () => { + describe('getPossibleOperationForField', () => { it('should return operation with the right type', () => { expect( - termsOperation.getPossibleOperationsForField({ + termsOperation.getPossibleOperationForField({ aggregatable: true, searchable: true, name: 'test', @@ -89,51 +89,47 @@ describe('terms', () => { }, }, }) - ).toEqual([ - { - dataType: 'string', - isBucketed: true, - isMetric: false, - scale: 'ordinal', - }, - ]); + ).toEqual({ + dataType: 'string', + isBucketed: true, + isMetric: false, + scale: 'ordinal', + }); expect( - termsOperation.getPossibleOperationsForField({ + termsOperation.getPossibleOperationForField({ aggregatable: true, searchable: true, name: 'test', type: 'boolean', }) - ).toEqual([ - { - dataType: 'boolean', - isBucketed: true, - isMetric: false, - scale: 'ordinal', - }, - ]); + ).toEqual({ + dataType: 'boolean', + isBucketed: true, + isMetric: false, + scale: 'ordinal', + }); }); it('should not return an operation if restrictions prevent terms', () => { expect( - termsOperation.getPossibleOperationsForField({ + termsOperation.getPossibleOperationForField({ aggregatable: false, searchable: true, name: 'test', type: 'string', }) - ).toEqual([]); + ).toEqual(undefined); expect( - termsOperation.getPossibleOperationsForField({ + termsOperation.getPossibleOperationForField({ aggregatable: true, aggregationRestrictions: {}, searchable: true, name: 'test', type: 'string', }) - ).toEqual([]); + ).toEqual(undefined); }); }); diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/terms.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/terms.tsx similarity index 94% rename from x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/terms.tsx rename to x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/terms.tsx index a11b49f6ab784..f20963c66648f 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/terms.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/terms.tsx @@ -7,10 +7,10 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { EuiForm, EuiFormRow, EuiRange, EuiSelect } from '@elastic/eui'; -import { TermsIndexPatternColumn, IndexPatternColumn } from '../indexpattern'; -import { OperationDefinition } from '../operations'; -import { updateColumnParam } from '../state_helpers'; -import { DataType } from '../../types'; +import { TermsIndexPatternColumn, IndexPatternColumn } from '../../indexpattern'; +import { updateColumnParam } from '../../state_helpers'; +import { DataType } from '../../../types'; +import { OperationDefinition } from '.'; type PropType = C extends React.ComponentType ? P : unknown; @@ -42,23 +42,14 @@ export const termsOperation: OperationDefinition = { displayName: i18n.translate('xpack.lens.indexPattern.terms', { defaultMessage: 'Top Values', }), - getPossibleOperationsForDocument: () => [], - getPossibleOperationsForField: ({ aggregationRestrictions, aggregatable, type }) => { + getPossibleOperationForField: ({ aggregationRestrictions, aggregatable, type }) => { if ( (type === 'string' || type === 'boolean') && aggregatable && (!aggregationRestrictions || aggregationRestrictions.terms) ) { - return [ - { - dataType: type, - isBucketed: true, - isMetric: false, - scale: 'ordinal', - }, - ]; + return { dataType: type, isBucketed: true, isMetric: false, scale: 'ordinal' }; } - return []; }, isTransferable: (column, newIndexPattern) => { const newField = newIndexPattern.fields.find(field => field.name === column.sourceField); diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/index.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/index.ts new file mode 100644 index 0000000000000..7fc07384cd9fc --- /dev/null +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './operations'; diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations.test.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/operations.test.ts similarity index 98% rename from x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations.test.ts rename to x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/operations.test.ts index 49719c35a09c2..c186aef6f06e4 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations.test.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/operations.test.ts @@ -4,20 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - getOperationTypesForField, - getAvailableOperationsByMetadata, - buildColumn, -} from './operations'; +import { getOperationTypesForField, getAvailableOperationsByMetadata, buildColumn } from '.'; import { IndexPatternPrivateState, AvgIndexPatternColumn, MinIndexPatternColumn, CountIndexPatternColumn, -} from './indexpattern'; +} from '../indexpattern'; jest.mock('ui/new_platform'); -jest.mock('./loader'); +jest.mock('../loader'); const expectedIndexPatterns = { 1: { diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/operations.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/operations.ts new file mode 100644 index 0000000000000..3ac5d38d9ca30 --- /dev/null +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/operations.ts @@ -0,0 +1,208 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import _ from 'lodash'; +import { DimensionPriority, OperationMetadata } from '../../types'; +import { + IndexPatternColumn, + IndexPatternField, + OperationType, + IndexPattern, +} from '../indexpattern'; +import { operationDefinitionMap, operationDefinitions, OperationDefinition } from './definitions'; + +/** + * Returns all available operation types as a list at runtime. + * This will be an array of each member of the union type `OperationType` + * without any guaranteed order + */ +export function getOperations(): OperationType[] { + return Object.keys(operationDefinitionMap) as OperationType[]; +} + +/** + * Returns true if the given column can be applied to the given index pattern + */ +export function isColumnTransferable(column: IndexPatternColumn, newIndexPattern: IndexPattern) { + return operationDefinitionMap[column.operationType].isTransferable(column, newIndexPattern); +} + +/** + * Returns a list of the display names of all operations with any guaranteed order. + */ +export function getOperationDisplay() { + const display = {} as Record< + OperationType, + { + type: OperationType; + displayName: string; + } + >; + operationDefinitions.forEach(({ type, displayName }) => { + display[type] = { + type, + displayName, + }; + }); + return display; +} + +/** + * Returns all `OperationType`s that can build a column using `buildColumn` based on the + * passed in field. + */ +export function getOperationTypesForField(field: IndexPatternField) { + return operationDefinitions + .filter( + operationDefinition => + 'getPossibleOperationForField' in operationDefinition && + operationDefinition.getPossibleOperationForField(field) + ) + .map(({ type }) => type); +} + +type OperationFieldTuple = + | { type: 'field'; operationType: OperationType; field: string } + | { type: 'document'; operationType: OperationType }; + +/** + * Returns all possible operations (matches between operations and fields of the index + * pattern plus matches for operations and documents of the index pattern) indexed by the + * meta data of the operation. + * + * The resulting list is filtered down by the `filterOperations` function passed in by + * the current visualization to determine which operations and field are applicable for + * a given dimension. + * + * Example output: + * ``` + * [ + * { + * operationMetaData: { dataType: 'string', isBucketed: true }, + * operations: ['terms'] + * }, + * { + * operationMetaData: { dataType: 'number', isBucketed: false }, + * operations: ['avg', 'min', 'max'] + * }, + * ] + * ``` + */ +export function getAvailableOperationsByMetadata(indexPattern: IndexPattern) { + const operationByMetadata: Record< + string, + { operationMetaData: OperationMetadata; operations: OperationFieldTuple[] } + > = {}; + + const addToMap = ( + operation: OperationFieldTuple, + operationMetadata: OperationMetadata | undefined | false + ) => { + if (!operationMetadata) return; + const key = JSON.stringify(operationMetadata); + + if (operationByMetadata[key]) { + operationByMetadata[key].operations.push(operation); + } else { + operationByMetadata[key] = { + operationMetaData: operationMetadata, + operations: [operation], + }; + } + }; + + operationDefinitions.forEach(operationDefinition => { + addToMap( + { type: 'document', operationType: operationDefinition.type }, + getPossibleOperationForDocument(operationDefinition, indexPattern) + ); + + indexPattern.fields.forEach(field => { + addToMap( + { + type: 'field', + operationType: operationDefinition.type, + field: field.name, + }, + getPossibleOperationForField(operationDefinition, field) + ); + }); + }); + + return Object.values(operationByMetadata); +} + +function getPossibleOperationForDocument( + operationDefinition: OperationDefinition, + indexPattern: IndexPattern +): OperationMetadata | undefined { + return 'getPossibleOperationForDocument' in operationDefinition + ? operationDefinition.getPossibleOperationForDocument(indexPattern) + : undefined; +} + +function getPossibleOperationForField( + operationDefinition: OperationDefinition, + field: IndexPatternField +): OperationMetadata | undefined { + return 'getPossibleOperationForField' in operationDefinition + ? operationDefinition.getPossibleOperationForField(field) + : undefined; +} + +/** + * Builds a column object based on the context passed in. It tries + * to find the applicable operation definition and then calls the `buildColumn` + * function of that definition. It passes in the given `field` (if available), + * `suggestedPriority`, `layerId` and the currently existing `columns`. + * * If `op` is specified, the specified operation definition is used directly. + * * If `asDocumentOperation` is true, the first matching document-operation is used. + * * If `field` is specified, the first matching field based operation applicable to the field is used. + */ +export function buildColumn({ + op, + columns, + field, + layerId, + indexPattern, + suggestedPriority, + asDocumentOperation, +}: { + op?: OperationType; + columns: Partial>; + suggestedPriority: DimensionPriority | undefined; + layerId: string; + indexPattern: IndexPattern; + field?: IndexPatternField; + asDocumentOperation?: boolean; +}): IndexPatternColumn { + let operationDefinition: OperationDefinition | undefined; + + if (op) { + operationDefinition = operationDefinitionMap[op] as OperationDefinition; + } else if (asDocumentOperation) { + operationDefinition = operationDefinitions.find(definition => + getPossibleOperationForDocument(definition, indexPattern) + ); + } else if (field) { + operationDefinition = operationDefinitions.find(definition => + getPossibleOperationForField(definition, field) + ); + } + + if (!operationDefinition) { + throw new Error('No suitable operation found for given parameters'); + } + + return operationDefinition.buildColumn({ + columns, + suggestedPriority, + field, + layerId, + }); +} + +export { operationDefinitionMap } from './definitions'; diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/state_helpers.test.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/state_helpers.test.ts index 897af8bbc28ff..fb0facd7f23f9 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/state_helpers.test.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/state_helpers.test.ts @@ -22,7 +22,7 @@ import { import { operationDefinitionMap } from './operations'; jest.mock('ui/new_platform'); -jest.mock('./operations'); +jest.mock('./operations/operations'); describe('state_helpers', () => { describe('deleteColumn', () => { diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/state_helpers.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/state_helpers.ts index e4031071e448c..315a7bdfec912 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/state_helpers.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/state_helpers.ts @@ -12,7 +12,8 @@ import { IndexPatternLayer, IndexPattern, } from './indexpattern'; -import { operationDefinitionMap, OperationDefinition, isColumnTransferable } from './operations'; +import { isColumnTransferable } from './operations/operations'; +import { operationDefinitionMap } from './operations'; export function updateColumnParam< C extends BaseIndexPatternColumn & { params: object }, @@ -61,9 +62,7 @@ function adjustColumnReferencesForChangedColumn( Object.keys(newColumns).forEach(currentColumnId => { if (currentColumnId !== columnId) { const currentColumn = newColumns[currentColumnId] as BaseIndexPatternColumn; - const operationDefinition = operationDefinitionMap[ - currentColumn.operationType - ] as OperationDefinition; + const operationDefinition = operationDefinitionMap[currentColumn.operationType]; newColumns[currentColumnId] = (operationDefinition.onOtherColumnChanged ? operationDefinition.onOtherColumnChanged(currentColumn, newColumns) : currentColumn) as IndexPatternColumn; @@ -175,11 +174,9 @@ export function updateLayerIndexPattern( isColumnTransferable(column, newIndexPattern) ); const newColumns: IndexPatternLayer['columns'] = _.mapValues(keptColumns, column => { - const operationDefinition = operationDefinitionMap[column.operationType] as OperationDefinition< - IndexPatternColumn - >; + const operationDefinition = operationDefinitionMap[column.operationType]; return operationDefinition.transfer - ? operationDefinition.transfer(column, newIndexPattern) + ? (operationDefinition.transfer(column, newIndexPattern) as IndexPatternColumn) : column; }); const newColumnOrder = layer.columnOrder.filter(columnId => newColumns[columnId]); diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/to_expression.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/to_expression.ts index ab06f94117163..781a01bd7fe8f 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/to_expression.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/to_expression.ts @@ -7,7 +7,8 @@ import _ from 'lodash'; import { IndexPatternPrivateState, IndexPatternColumn, IndexPattern } from './indexpattern'; -import { operationDefinitionMap, OperationDefinition, buildColumn } from './operations'; +import { buildColumn } from './operations/operations'; +import { operationDefinitionMap } from './operations'; function getExpressionForLayer( indexPattern: IndexPattern, @@ -20,12 +21,7 @@ function getExpressionForLayer( } function getEsAggsConfig(column: C, columnId: string) { - // Typescript is not smart enough to infer that definitionMap[C['operationType']] is always OperationDefinition, - // but this is made sure by the typing of the operation map - const operationDefinition = (operationDefinitionMap[ - column.operationType - ] as unknown) as OperationDefinition; - return operationDefinition.toEsAggsConfig(column, columnId); + return operationDefinitionMap[column.operationType].toEsAggsConfig(column, columnId); } const columnEntries = columnOrder.map( From 8d380b72cd48bc2c64d9045ebe7e06a7b22e706a Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Thu, 22 Aug 2019 18:19:22 +0200 Subject: [PATCH 02/10] make field mandatory in buildColumn of field based operation definitions --- .../operations/definitions/date_histogram.tsx | 14 +---- .../operations/definitions/index.ts | 56 +++++++++++-------- .../operations/definitions/metrics.tsx | 25 ++++----- .../operations/definitions/terms.tsx | 3 - .../operations/operations.ts | 19 ++++++- 5 files changed, 62 insertions(+), 55 deletions(-) diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.tsx index c91efc0588243..b298ce26c974f 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.tsx @@ -8,8 +8,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiForm, EuiFormRow, EuiRange } from '@elastic/eui'; -import { IndexPatternField, DateHistogramIndexPatternColumn } from '../../indexpattern'; -import { DimensionPriority } from '../../../types'; +import { DateHistogramIndexPatternColumn } from '../../indexpattern'; import { updateColumnParam } from '../../state_helpers'; import { OperationDefinition } from '.'; @@ -53,16 +52,7 @@ export const dateHistogramOperation: OperationDefinition { */ displayName: string; // TODO move into field specific part - buildColumn: (arg: { - suggestedPriority: DimensionPriority | undefined; - layerId: string; - columns: Partial>; - field?: IndexPatternField; - }) => C; /** * This function is called if another column in the same layer changed or got removed. * Can be used to update references to other columns (e.g. for sorting). @@ -81,6 +75,38 @@ interface BaseOperationDefinitionProps { transfer?: (column: C, newIndexPattern: IndexPattern) => C; } +interface BaseBuildColumnArgs { + suggestedPriority: DimensionPriority | undefined; + layerId: string; + columns: Partial>; +} + +type FieldBasedOperationDefinition = BaseOperationDefinitionProps< + C +> & { + /** + * Returns the meta data of the operation if applied to the given field. Undefined + * if the field is not applicable to the operation. + */ + getPossibleOperationForField: (field: IndexPatternField) => OperationMetadata | undefined; + buildColumn: ( + arg: BaseBuildColumnArgs & { + field: IndexPatternField; + } + ) => C; +}; + +type DocumentBasedOperationDefinition< + C extends BaseIndexPatternColumn +> = BaseOperationDefinitionProps & { + /** + * Returns the meta data of the operation if applied to documents of the given index pattern. + * Undefined if the operation is not applicable to the index pattern. + */ + getPossibleOperationForDocument: (indexPattern: IndexPattern) => OperationMetadata | undefined; + buildColumn: (arg: BaseBuildColumnArgs) => C; +}; + /** * Shape of an operation definition. If the type parameter of the definition * indicates a field based column, `getPossibleOperationForField` has to be @@ -89,22 +115,8 @@ interface BaseOperationDefinitionProps { export type OperationDefinition< C extends BaseIndexPatternColumn > = C extends FieldBasedIndexPatternColumn - ? BaseOperationDefinitionProps & { - /** - * Returns the meta data of the operation if applied to the given field. Undefined - * if the field is not applicable to the operation. - */ - getPossibleOperationForField: (field: IndexPatternField) => OperationMetadata | undefined; - } - : BaseOperationDefinitionProps & { - /** - * Returns the meta data of the operation if applied to documents of the given index pattern. - * Undefined if the operation is not applicable to the index pattern. - */ - getPossibleOperationForDocument: ( - indexPattern: IndexPattern - ) => OperationMetadata | undefined; - }; + ? FieldBasedOperationDefinition + : DocumentBasedOperationDefinition; // This type is like mapping over a union of types // Each possible column type out of the `IndexPatternColumn` union type diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/metrics.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/metrics.tsx index 22def483c24dd..7aea4a719b8ec 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/metrics.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/metrics.tsx @@ -55,21 +55,16 @@ function buildMetricOperation( field: column.sourceField, }, }), - buildColumn: ({ suggestedPriority, field }) => { - if (!field) { - throw new Error(`Invariant: A ${type} operation can only be built with a field`); - } - return { - label: ofName(field ? field.name : ''), - dataType: 'number', - operationType: type, - suggestedPriority, - sourceField: field ? field.name : '', - isBucketed: false, - isMetric: true, - scale: 'ratio', - }; - }, + buildColumn: ({ suggestedPriority, field }) => ({ + label: ofName(field ? field.name : ''), + dataType: 'number', + operationType: type, + suggestedPriority, + sourceField: field ? field.name : '', + isBucketed: false, + isMetric: true, + scale: 'ratio', + }), } as OperationDefinition; } diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/terms.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/terms.tsx index f20963c66648f..8b612282249a6 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/terms.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/terms.tsx @@ -62,9 +62,6 @@ export const termsOperation: OperationDefinition = { ); }, buildColumn({ suggestedPriority, columns, field }) { - if (!field) { - throw new Error('Invariant error: terms operation requires field'); - } const existingMetricColumn = Object.entries(columns) .filter(([_columnId, column]) => column && isSortableByColumn(column)) .map(([id]) => id)[0]; diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/operations.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/operations.ts index 3ac5d38d9ca30..1dc0693bff395 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/operations.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/operations.ts @@ -197,12 +197,25 @@ export function buildColumn({ throw new Error('No suitable operation found for given parameters'); } - return operationDefinition.buildColumn({ + const baseOptions = { columns, suggestedPriority, - field, layerId, - }); + }; + + // check for the operation for field getter to determine whether + // this is a field based operation type + if ('getPossibleOperationForField' in operationDefinition) { + if (!field) { + throw new Error(`Invariant error: ${operationDefinition.type} operation requires field`); + } + return operationDefinition.buildColumn({ + ...baseOptions, + field, + }); + } else { + return operationDefinition.buildColumn(baseOptions); + } } export { operationDefinitionMap } from './definitions'; From 694e845732c2595a89bb9ec6f63c11edb7028d0d Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Fri, 23 Aug 2019 16:29:14 +0200 Subject: [PATCH 03/10] handle state in app --- .../plugins/lens/public/app_plugin/app.tsx | 28 ++++++++++++++++++- .../editor_frame/index.scss | 6 ++-- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx b/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx index 95bbda1102242..68734c2296922 100644 --- a/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx +++ b/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx @@ -27,6 +27,25 @@ interface State { query: Query; indexPatternTitles: string[]; persistedDoc?: Document; + localQueryBarState: { + query?: Query; + dateRange?: { + from: string; + to: string; + }; + }; +} + +function isLocalStateDirty( + localState: State['localQueryBarState'], + query: Query, + dateRange: State['dateRange'] +) { + return Boolean( + (localState.query && query && localState.query.query !== query.query) || + (localState.dateRange && dateRange.fromDate !== localState.dateRange.from) || + (localState.dateRange && dateRange.toDate !== localState.dateRange.to) + ); } export function App({ @@ -57,6 +76,7 @@ export function App({ toDate: timeDefaults.to, }, indexPatternTitles: [], + localQueryBarState: {}, }); const lastKnownDocRef = useRef(undefined); @@ -152,7 +172,8 @@ export function App({ { + onSubmit={payload => { + const { dateRange, query } = payload; setState({ ...state, dateRange: { @@ -160,8 +181,13 @@ export function App({ toDate: dateRange.to, }, query: query || state.query, + localQueryBarState: payload, }); }} + onChange={uncommitedQueryBarState => { + setState({ ...state, localQueryBarState: uncommitedQueryBarState }); + }} + isDirty={isLocalStateDirty(state.localQueryBarState, state.query, state.dateRange)} appName={'lens'} indexPatterns={state.indexPatternTitles} store={store} diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/index.scss b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/index.scss index 72b5f1eb79638..33571793a721c 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/index.scss +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/index.scss @@ -115,12 +115,14 @@ $lnsPanelMinWidth: $euiSize * 18; width: 100%; height: 100%; display: flex; - align-items: center; - justify-content: center; overflow-x: hidden; padding: $euiSize; } +.lnsExpressionOutput > * { + flex: 1; +} + .lnsTitleInput { width: 100%; min-width: 100%; From 54ad77cf24b49854df1846bcae00cf746ecce581 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Fri, 23 Aug 2019 16:43:16 +0200 Subject: [PATCH 04/10] fix isMetric usages --- .../operation_definitions/date_histogram.test.tsx | 3 +++ .../indexpattern_plugin/operation_definitions/terms.test.tsx | 1 + 2 files changed, 4 insertions(+) diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/date_histogram.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/date_histogram.test.tsx index 123a728f51b6f..f7a758febd8d2 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/date_histogram.test.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/date_histogram.test.tsx @@ -98,6 +98,7 @@ describe('date_histogram', () => { label: 'Value of timestamp', dataType: 'date', isBucketed: true, + isMetric: false, // Private operationType: 'date_histogram', @@ -197,6 +198,7 @@ describe('date_histogram', () => { sourceField: 'timestamp', label: 'Date over timestamp', isBucketed: true, + isMetric: false, dataType: 'date', params: { interval: 'd', @@ -217,6 +219,7 @@ describe('date_histogram', () => { sourceField: 'timestamp', label: 'Date over timestamp', isBucketed: true, + isMetric: false, dataType: 'date', params: { interval: 'auto', diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/terms.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/terms.test.tsx index 7514e5688f9e2..4ce5d4bd32223 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/terms.test.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/terms.test.tsx @@ -84,6 +84,7 @@ describe('terms', () => { label: 'Top values of source', isBucketed: true, dataType: 'string', + isMetric: false, params: { size: 5, orderBy: { From 82a4fc86a596d8b87f68c834e8473621ff1399cc Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Fri, 23 Aug 2019 16:53:08 +0200 Subject: [PATCH 05/10] fix integration tests --- .../test/api_integration/apis/xpack_main/features/features.ts | 1 + .../test/saved_object_api_integration/common/suites/export.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/api_integration/apis/xpack_main/features/features.ts b/x-pack/test/api_integration/apis/xpack_main/features/features.ts index d803dcad90ac1..6fc7c842697ef 100644 --- a/x-pack/test/api_integration/apis/xpack_main/features/features.ts +++ b/x-pack/test/api_integration/apis/xpack_main/features/features.ts @@ -130,6 +130,7 @@ export default function({ getService }: FtrProviderContext) { 'canvas', 'code', 'infrastructure', + 'lens', 'logs', 'maps', 'uptime', diff --git a/x-pack/test/saved_object_api_integration/common/suites/export.ts b/x-pack/test/saved_object_api_integration/common/suites/export.ts index d109f47da3f52..d7d1a99e63e02 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/export.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/export.ts @@ -60,7 +60,7 @@ export function exportTestSuiteFactory(esArchiver: any, supertest: SuperTest Date: Fri, 23 Aug 2019 17:39:46 +0200 Subject: [PATCH 06/10] eslint --- .../dimension_panel/dimension_panel.tsx | 6 +----- .../operations/definitions/index.ts | 3 ++- .../indexpattern_plugin/operations/operations.ts | 12 +++++++++--- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.tsx index 478d5c4b7ac68..0076a9f599bb9 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.tsx @@ -18,11 +18,7 @@ import { OperationType, } from '../indexpattern'; -import { - getAvailableOperationsByMetadata, - buildColumn, - changeField, -} from '../operations'; +import { getAvailableOperationsByMetadata, buildColumn, changeField } from '../operations'; import { PopoverEditor } from './popover_editor'; import { DragContextState, ChildDragDropProvider, DragDrop } from '../../drag_drop'; import { changeColumn, deleteColumn } from '../state_helpers'; diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/index.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/index.ts index 2ab87ad7675ea..168550cf5c445 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/index.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/index.ts @@ -199,5 +199,6 @@ export const operationDefinitions: PossibleOperationDefinition[] = Object.values */ export const operationDefinitionMap = (internalOperationDefinitionMap as unknown) as Record< OperationType, - FieldBasedOperationDefinition | DocumentBasedOperationDefinition + | FieldBasedOperationDefinition + | DocumentBasedOperationDefinition >; diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/operations.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/operations.ts index a485f627d5526..9c1f998f8b45b 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/operations.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/operations.ts @@ -160,11 +160,17 @@ function getPossibleOperationForField( * @param indexPattern The index pattern associated to the layer of the column * @param newField The new field the column should be switched to */ -export function changeField(column: IndexPatternColumn, indexPattern: IndexPattern, newField: IndexPatternField) { +export function changeField( + column: IndexPatternColumn, + indexPattern: IndexPattern, + newField: IndexPatternField +) { const operationDefinition = operationDefinitionMap[column.operationType]; - if(!('onFieldChange' in operationDefinition)) { - throw new Error('Invariant error: Cannot change field if operation isn\'t a field based operaiton'); + if (!('onFieldChange' in operationDefinition)) { + throw new Error( + "Invariant error: Cannot change field if operation isn't a field based operaiton" + ); } // This has to be casted back to an index pattern column, because the `operationDefinitionMap` only From 73cdade85e070aa864087256990fd55e35af4ae1 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Mon, 26 Aug 2019 12:07:13 +0200 Subject: [PATCH 07/10] improve typings --- .../dimension_panel/popover_editor.tsx | 1 + .../indexpattern_plugin/indexpattern.tsx | 66 +-------- .../operations/__mocks__/operations.ts | 1 + .../operations/definitions/column_types.ts | 29 ++++ .../operations/definitions/count.tsx | 7 +- .../definitions/date_histogram.test.tsx | 11 +- .../operations/definitions/date_histogram.tsx | 31 ++-- .../definitions/filter_ratio.test.tsx | 9 +- .../operations/definitions/filter_ratio.tsx | 42 ++---- .../operations/definitions/index.ts | 136 +++++++++--------- .../operations/definitions/metrics.tsx | 15 +- .../operations/definitions/terms.test.tsx | 12 +- .../operations/definitions/terms.tsx | 15 +- .../indexpattern_plugin/operations/index.ts | 1 + .../operations/operations.test.ts | 9 +- .../operations/operations.ts | 23 ++- .../indexpattern_plugin/state_helpers.test.ts | 12 +- .../indexpattern_plugin/state_helpers.ts | 30 ++-- .../indexpattern_plugin/to_expression.ts | 4 +- .../lens/public/indexpattern_plugin/utils.ts | 6 +- 20 files changed, 220 insertions(+), 240 deletions(-) create mode 100644 x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/column_types.ts diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/popover_editor.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/popover_editor.tsx index 838b86b4c8d30..960e81b98699b 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/popover_editor.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/popover_editor.tsx @@ -351,6 +351,7 @@ export function PopoverEditor(props: PopoverEditorProps) { state={state} setState={setState} columnId={columnId} + currentColumn={state.layers[layerId].columns[columnId]} storage={props.storage} uiSettings={props.uiSettings} layerId={layerId} diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.tsx index 388d4de25a792..a7cbdd59c6e0f 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.tsx @@ -12,11 +12,9 @@ import { Storage } from 'ui/storage'; import { DatasourceDimensionPanelProps, DatasourceDataPanelProps, - DimensionPriority, Operation, DatasourceLayerPanelProps, } from '../types'; -import { Query } from '../../../../../../src/legacy/core_plugins/data/public/query'; import { getIndexPatterns } from './loader'; import { toExpression } from './to_expression'; import { IndexPatternDimensionPanel } from './dimension_panel'; @@ -29,70 +27,10 @@ import { import { isDraggedField } from './utils'; import { LayerPanel } from './layerpanel'; +import { IndexPatternColumn } from './operations'; import { Datasource } from '..'; -export type OperationType = IndexPatternColumn['operationType']; - -export type IndexPatternColumn = - | DateHistogramIndexPatternColumn - | TermsIndexPatternColumn - | SumIndexPatternColumn - | AvgIndexPatternColumn - | MinIndexPatternColumn - | MaxIndexPatternColumn - | CountIndexPatternColumn - | FilterRatioIndexPatternColumn; - -export interface BaseIndexPatternColumn extends Operation { - // Private - operationType: OperationType; - suggestedPriority?: DimensionPriority; -} - -type Omit = Pick>; -type ParameterlessIndexPatternColumn< - TOperationType extends OperationType, - TBase extends BaseIndexPatternColumn = FieldBasedIndexPatternColumn -> = Omit & { operationType: TOperationType }; - -export interface FieldBasedIndexPatternColumn extends BaseIndexPatternColumn { - sourceField: string; - suggestedPriority?: DimensionPriority; -} - -export interface DateHistogramIndexPatternColumn extends FieldBasedIndexPatternColumn { - operationType: 'date_histogram'; - params: { - interval: string; - timeZone?: string; - }; -} - -export interface TermsIndexPatternColumn extends FieldBasedIndexPatternColumn { - operationType: 'terms'; - params: { - size: number; - orderBy: { type: 'alphabetical' } | { type: 'column'; columnId: string }; - orderDirection: 'asc' | 'desc'; - }; -} - -export interface FilterRatioIndexPatternColumn extends BaseIndexPatternColumn { - operationType: 'filter_ratio'; - params: { - numerator: Query; - denominator: Query; - }; -} - -export type CountIndexPatternColumn = ParameterlessIndexPatternColumn< - 'count', - BaseIndexPatternColumn ->; -export type SumIndexPatternColumn = ParameterlessIndexPatternColumn<'sum'>; -export type AvgIndexPatternColumn = ParameterlessIndexPatternColumn<'avg'>; -export type MinIndexPatternColumn = ParameterlessIndexPatternColumn<'min'>; -export type MaxIndexPatternColumn = ParameterlessIndexPatternColumn<'max'>; +export { OperationType, IndexPatternColumn } from './operations'; export interface IndexPattern { id: string; diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/__mocks__/operations.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/__mocks__/operations.ts index 3a2413ca05b35..db0ffa3221efc 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/__mocks__/operations.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/__mocks__/operations.ts @@ -17,6 +17,7 @@ export const { getOperationTypesForField, getOperationResultType, operationDefinitionMap, + operationDefinitions, isColumnTransferable, changeField, } = actual; diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/column_types.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/column_types.ts new file mode 100644 index 0000000000000..f5af25072c183 --- /dev/null +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/column_types.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Operation, DimensionPriority } from '../../../types'; + +/** + * This is the root type of a column. If you are implementing a new + * operation, extend your column type on `BaseIndexPatternColumn` to make + * sure it's matching all the basic requirements. + */ +export interface BaseIndexPatternColumn extends Operation { + // Private + operationType: string; + suggestedPriority?: DimensionPriority; +} + +type Omit = Pick>; +export type ParameterlessIndexPatternColumn< + TOperationType extends string, + TBase extends BaseIndexPatternColumn = FieldBasedIndexPatternColumn +> = Omit & { operationType: TOperationType }; + +export interface FieldBasedIndexPatternColumn extends BaseIndexPatternColumn { + sourceField: string; + suggestedPriority?: DimensionPriority; +} diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/count.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/count.tsx index d252bf33cf73a..8a47e68a279ad 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/count.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/count.tsx @@ -5,13 +5,18 @@ */ import { i18n } from '@kbn/i18n'; -import { CountIndexPatternColumn } from '../../indexpattern'; import { OperationDefinition } from '.'; +import { ParameterlessIndexPatternColumn, BaseIndexPatternColumn } from './column_types'; const countLabel = i18n.translate('xpack.lens.indexPattern.countOf', { defaultMessage: 'Count of documents', }); +export type CountIndexPatternColumn = ParameterlessIndexPatternColumn< + 'count', + BaseIndexPatternColumn +>; + export const countOperation: OperationDefinition = { type: 'count', displayName: i18n.translate('xpack.lens.indexPattern.count', { diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.test.tsx index 51a0a388d17dd..12015a281eaa9 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.test.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.test.tsx @@ -5,9 +5,10 @@ */ import React from 'react'; -import { dateHistogramOperation } from './date_histogram'; +import { DateHistogramIndexPatternColumn } from './date_histogram'; +import { dateHistogramOperation } from '.'; import { shallow } from 'enzyme'; -import { DateHistogramIndexPatternColumn, IndexPatternPrivateState } from '../../indexpattern'; +import { IndexPatternPrivateState } from '../../indexpattern'; import { EuiRange, EuiSwitch } from '@elastic/eui'; import { UiSettingsClientContract } from 'src/core/public'; import { Storage } from 'ui/storage'; @@ -325,6 +326,7 @@ describe('date_histogram', () => { setState={setStateSpy} columnId="col1" layerId="first" + currentColumn={state.layers.first.columns.col1 as DateHistogramIndexPatternColumn} storage={{} as Storage} uiSettings={{} as UiSettingsClientContract} /> @@ -340,6 +342,7 @@ describe('date_histogram', () => { state={state} setState={setStateSpy} columnId="col2" + currentColumn={state.layers.second.columns.col2 as DateHistogramIndexPatternColumn} layerId="second" storage={{} as Storage} uiSettings={{} as UiSettingsClientContract} @@ -355,6 +358,7 @@ describe('date_histogram', () => { state={state} setState={jest.fn()} columnId="col1" + currentColumn={state.layers.third.columns.col1 as DateHistogramIndexPatternColumn} layerId="third" storage={{} as Storage} uiSettings={{} as UiSettingsClientContract} @@ -372,6 +376,7 @@ describe('date_histogram', () => { setState={setStateSpy} columnId="col1" layerId="third" + currentColumn={state.layers.third.columns.col1 as DateHistogramIndexPatternColumn} storage={{} as Storage} uiSettings={{} as UiSettingsClientContract} /> @@ -392,6 +397,7 @@ describe('date_histogram', () => { setState={setStateSpy} columnId="col1" layerId="first" + currentColumn={state.layers.first.columns.col1 as DateHistogramIndexPatternColumn} storage={{} as Storage} uiSettings={{} as UiSettingsClientContract} /> @@ -449,6 +455,7 @@ describe('date_histogram', () => { setState={setStateSpy} columnId="col1" layerId="first" + currentColumn={state.layers.first.columns.col1 as DateHistogramIndexPatternColumn} storage={{} as Storage} uiSettings={{} as UiSettingsClientContract} /> diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.tsx index c6716e6bd9331..62061c05b2e64 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.tsx @@ -8,9 +8,10 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiForm, EuiFormRow, EuiRange, EuiSwitch } from '@elastic/eui'; -import { DateHistogramIndexPatternColumn, IndexPattern } from '../../indexpattern'; +import { IndexPattern } from '../../indexpattern'; import { updateColumnParam } from '../../state_helpers'; import { OperationDefinition } from '.'; +import { FieldBasedIndexPatternColumn } from './column_types'; type PropType = C extends React.ComponentType ? P : unknown; @@ -39,6 +40,14 @@ function supportsAutoInterval(fieldName: string, indexPattern: IndexPattern): bo return indexPattern.timeFieldName ? indexPattern.timeFieldName === fieldName : false; } +export interface DateHistogramIndexPatternColumn extends FieldBasedIndexPatternColumn { + operationType: 'date_histogram'; + params: { + interval: string; + timeZone?: string; + }; +} + export const dateHistogramOperation: OperationDefinition = { type: 'date_histogram', displayName: i18n.translate('xpack.lens.indexPattern.dateHistogram', { @@ -157,13 +166,11 @@ export const dateHistogramOperation: OperationDefinition { - const column = state.layers[layerId].columns[columnId] as DateHistogramIndexPatternColumn; - + paramEditor: ({ state, setState, currentColumn: currentColumn, layerId }) => { const field = - column && + currentColumn && state.indexPatterns[state.layers[layerId].indexPatternId].fields.find( - currentField => currentField.name === column.sourceField + currentField => currentField.name === currentColumn.sourceField ); const intervalIsRestricted = field!.aggregationRestrictions && field!.aggregationRestrictions.date_histogram; @@ -180,7 +187,7 @@ export const dateHistogramOperation: OperationDefinition) { const interval = ev.target.checked ? defaultCustomInterval : autoInterval; - setState(updateColumnParam(state, layerId, column, 'interval', interval)); + setState(updateColumnParam(state, layerId, currentColumn, 'interval', interval)); } return ( @@ -191,12 +198,12 @@ export const dateHistogramOperation: OperationDefinition )} - {column.params.interval !== autoInterval && ( + {currentColumn.params.interval !== autoInterval && ( ) : ( @@ -215,7 +222,7 @@ export const dateHistogramOperation: OperationDefinition ({ label: interval, @@ -226,7 +233,7 @@ export const dateHistogramOperation: OperationDefinition { state={state} setState={jest.fn()} columnId="col1" + currentColumn={state.layers.first.columns.col1 as FilterRatioIndexPatternColumn} storage={storageMock} uiSettings={{} as UiSettingsClientContract} /> @@ -118,6 +120,7 @@ describe('filter_ratio', () => { state={state} setState={jest.fn()} columnId="col1" + currentColumn={state.layers.first.columns.col1 as FilterRatioIndexPatternColumn} storage={storageMock} uiSettings={{} as UiSettingsClientContract} /> @@ -135,6 +138,7 @@ describe('filter_ratio', () => { state={state} setState={setState} columnId="col1" + currentColumn={state.layers.first.columns.col1 as FilterRatioIndexPatternColumn} storage={storageMock} uiSettings={{} as UiSettingsClientContract} /> @@ -173,6 +177,7 @@ describe('filter_ratio', () => { state={state} setState={setState} columnId="col1" + currentColumn={state.layers.first.columns.col1 as FilterRatioIndexPatternColumn} storage={storageMock} uiSettings={{} as UiSettingsClientContract} /> diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/filter_ratio.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/filter_ratio.tsx index 888f5ae5a0b15..4a942f85bab6d 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/filter_ratio.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/filter_ratio.tsx @@ -12,14 +12,22 @@ import { Query, QueryBarInput, } from '../../../../../../../../src/legacy/core_plugins/data/public/query'; -import { FilterRatioIndexPatternColumn } from '../../indexpattern'; import { updateColumnParam } from '../../state_helpers'; import { OperationDefinition } from '.'; +import { BaseIndexPatternColumn } from './column_types'; const filterRatioLabel = i18n.translate('xpack.lens.indexPattern.filterRatio', { defaultMessage: 'Filter Ratio', }); +export interface FilterRatioIndexPatternColumn extends BaseIndexPatternColumn { + operationType: 'filter_ratio'; + params: { + numerator: Query; + denominator: Query; + }; +} + export const filterRatioOperation: OperationDefinition = { type: 'filter_ratio', displayName: i18n.translate('xpack.lens.indexPattern.filterRatio', { @@ -70,7 +78,7 @@ export const filterRatioOperation: OperationDefinition { + paramEditor: ({ state, setState, currentColumn, uiSettings, storage, layerId }) => { const [hasDenominator, setDenominator] = useState(false); return ( @@ -83,23 +91,12 @@ export const filterRatioOperation: OperationDefinition { - setState( - updateColumnParam( - state, - layerId, - state.layers[layerId].columns[currentColumnId] as FilterRatioIndexPatternColumn, - 'numerator', - newQuery - ) - ); + setState(updateColumnParam(state, layerId, currentColumn, 'numerator', newQuery)); }} /> @@ -113,23 +110,12 @@ export const filterRatioOperation: OperationDefinition { - setState( - updateColumnParam( - state, - layerId, - state.layers[layerId].columns[currentColumnId] as FilterRatioIndexPatternColumn, - 'denominator', - newQuery - ) - ); + setState(updateColumnParam(state, layerId, currentColumn, 'denominator', newQuery)); }} /> ) : ( diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/index.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/index.ts index 168550cf5c445..b7ea189beb2c9 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/index.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/index.ts @@ -11,21 +11,35 @@ import { minOperation, averageOperation, sumOperation, maxOperation } from './me import { dateHistogramOperation } from './date_histogram'; import { countOperation } from './count'; import { filterRatioOperation } from './filter_ratio'; -import { - IndexPatternColumn, - OperationType, - BaseIndexPatternColumn, - IndexPatternField, - IndexPatternPrivateState, - IndexPattern, - FieldBasedIndexPatternColumn, -} from '../../indexpattern'; import { DimensionPriority, StateSetter, OperationMetadata } from '../../../types'; +import { BaseIndexPatternColumn, FieldBasedIndexPatternColumn } from './column_types'; +import { IndexPatternPrivateState, IndexPattern, IndexPatternField } from '../../indexpattern'; + +// List of all operation definitions registered to this data source. +// If you want to implement a new operation, add it to this array and +// its type will get propagated to everything else +const internalOperationDefinitions = [ + termsOperation, + dateHistogramOperation, + minOperation, + maxOperation, + averageOperation, + sumOperation, + countOperation, + filterRatioOperation, +]; + +export { termsOperation } from './terms'; +export { dateHistogramOperation } from './date_histogram'; +export { minOperation, averageOperation, sumOperation, maxOperation } from './metrics'; +export { countOperation } from './count'; +export { filterRatioOperation } from './filter_ratio'; /** * Properties passed to the operation-specific part of the popover editor */ -export interface ParamEditorProps { +export interface ParamEditorProps { + currentColumn: C; state: IndexPatternPrivateState; setState: StateSetter; columnId: string; @@ -41,7 +55,6 @@ interface BaseOperationDefinitionProps { * Should be i18n-ified. */ displayName: string; - // TODO move into field specific part /** * This function is called if another column in the same layer changed or got removed. * Can be used to update references to other columns (e.g. for sorting). @@ -55,7 +68,7 @@ interface BaseOperationDefinitionProps { /** * React component for operation specific settings shown in the popover editor */ - paramEditor?: React.ComponentType; + paramEditor?: React.ComponentType>; /** * Function turning a column into an agg config passed to the `esaggs` function * together with the agg configs returned from other columns. @@ -82,14 +95,16 @@ interface BaseBuildColumnArgs { indexPattern: IndexPattern; } -type FieldBasedOperationDefinition = BaseOperationDefinitionProps< - C -> & { +interface FieldBasedOperationDefinition + extends BaseOperationDefinitionProps { /** * Returns the meta data of the operation if applied to the given field. Undefined * if the field is not applicable to the operation. */ getPossibleOperationForField: (field: IndexPatternField) => OperationMetadata | undefined; + /** + * Builds the column object for the given parameters. Should include default p + */ buildColumn: ( arg: BaseBuildColumnArgs & { field: IndexPatternField; @@ -112,18 +127,17 @@ type FieldBasedOperationDefinition = BaseOpera * @param field The field that the user changed to. */ onFieldChange: (oldColumn: C, indexPattern: IndexPattern, field: IndexPatternField) => C; -}; +} -type DocumentBasedOperationDefinition< - C extends BaseIndexPatternColumn -> = BaseOperationDefinitionProps & { +interface DocumentBasedOperationDefinition + extends BaseOperationDefinitionProps { /** * Returns the meta data of the operation if applied to documents of the given index pattern. * Undefined if the operation is not applicable to the index pattern. */ getPossibleOperationForDocument: (indexPattern: IndexPattern) => OperationMetadata | undefined; buildColumn: (arg: BaseBuildColumnArgs) => C; -}; +} /** * Shape of an operation definition. If the type parameter of the definition @@ -136,55 +150,38 @@ export type OperationDefinition< ? FieldBasedOperationDefinition : DocumentBasedOperationDefinition; -// This type is like mapping over a union of types -// Each possible column type out of the `IndexPatternColumn` union type -// is wrapped into an `OperationDefinition`. The resulting type -// is a union type of all operation definitions -type PossibleOperationDefinition< - U extends IndexPatternColumn = IndexPatternColumn -> = U extends IndexPatternColumn ? OperationDefinition : never; - -// This type once again maps over the union of all operation definitions -// and wraps them in an object with the definition as a single property. The key of -// the property is the operation type of the definition. -// E.g. the terms operation definition maps to `{ terms: OperationDefinition }` -// and so on. -type PossibleOperationDefinitionMapEntries< - U extends PossibleOperationDefinition = PossibleOperationDefinition -> = U extends PossibleOperationDefinition ? { [K in U['type']]: U } : never; +// Helper to to infer the column type out of the operation definition. +// This is done to avoid it to have to list out the column types along with +// the operation definition types +type ColumnFromOperationDefinition = D extends OperationDefinition ? C : never; -// This is a helper type changing a union (`a | b | c`) into an intersection `a & b & c` -type UnionToIntersection = (U extends U ? (k: U) => void : never) extends ((k: infer I) => void) - ? I - : never; +/** + * A union type of all available column types. If a column is of an unknown type somewhere + * withing the indexpattern data source it should be typed as `IndexPatternColumn` to make + * typeguards possible that consider all available column types. + */ +export type IndexPatternColumn = ColumnFromOperationDefinition< + (typeof internalOperationDefinitions)[number] +>; -// Here, the helper from above is used to turn the union of all partial operation definition entries -// into an intersection - `{ terms: OperationDefinition, avg: OperationDefinition, ...} -// This type makes sure that -// a) there is an operation definition for each column type -// b) Each operation definition maps to the correct key in the map and there are no mix-ups -type AllOperationDefinitions = UnionToIntersection; +/** + * A union type of all available operation types. The operation type is a unique id of an operation. + * Each column is assigned to exactly one operation type. + */ +export type OperationType = (typeof internalOperationDefinitions)[number]['type']; -// This is the map of all available operation definitions. If you are adding a new -// operation and column type, you have to add it to this list, otherwise typescript -// will throw an error. -const internalOperationDefinitionMap: AllOperationDefinitions = { - terms: termsOperation, - date_histogram: dateHistogramOperation, - min: minOperation, - max: maxOperation, - avg: averageOperation, - sum: sumOperation, - count: countOperation, - filter_ratio: filterRatioOperation, -}; +/** + * This is an operation definition of an unspecified column out of all possible + * column types. It + */ +export type GenericOperationDefinition = + | FieldBasedOperationDefinition + | DocumentBasedOperationDefinition; /** * List of all available operation definitions */ -export const operationDefinitions: PossibleOperationDefinition[] = Object.values( - internalOperationDefinitionMap -); +export const operationDefinitions = internalOperationDefinitions as GenericOperationDefinition[]; /** * Map of all operation visible to consumers (e.g. the dimension panel). @@ -193,12 +190,11 @@ export const operationDefinitions: PossibleOperationDefinition[] = Object.values * situations. * * If you need a specifically typed version of an operation (e.g. explicitly working with terms), - * you should import the terms definition directly from the sub folder. This map is - * intended to be used in situations where the operation type is not known during compile - * time. + * you should import the definition directly from this file + * (e.g. `import { termsOperation } from './operations/definitions'`). This map is + * intended to be used in situations where the operation type is not known during compile time. */ -export const operationDefinitionMap = (internalOperationDefinitionMap as unknown) as Record< - OperationType, - | FieldBasedOperationDefinition - | DocumentBasedOperationDefinition ->; +export const operationDefinitionMap = internalOperationDefinitions.reduce( + (definitionMap, definition) => ({ ...definitionMap, [definition.type]: definition }), + {} +) as Record; diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/metrics.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/metrics.tsx index 561fe92b977f5..f33bd1cfd3967 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/metrics.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/metrics.tsx @@ -5,16 +5,10 @@ */ import { i18n } from '@kbn/i18n'; -import { - FieldBasedIndexPatternColumn, - MinIndexPatternColumn, - SumIndexPatternColumn, - AvgIndexPatternColumn, - MaxIndexPatternColumn, -} from '../../indexpattern'; import { OperationDefinition } from '.'; +import { ParameterlessIndexPatternColumn } from './column_types'; -function buildMetricOperation( +function buildMetricOperation>( type: T['operationType'], displayName: string, ofName: (name: string) => string @@ -75,6 +69,11 @@ function buildMetricOperation( } as OperationDefinition; } +export type SumIndexPatternColumn = ParameterlessIndexPatternColumn<'sum'>; +export type AvgIndexPatternColumn = ParameterlessIndexPatternColumn<'avg'>; +export type MinIndexPatternColumn = ParameterlessIndexPatternColumn<'min'>; +export type MaxIndexPatternColumn = ParameterlessIndexPatternColumn<'max'>; + export const minOperation = buildMetricOperation( 'min', i18n.translate('xpack.lens.indexPattern.min', { diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/terms.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/terms.test.tsx index 479a6a1503af2..b6883a0cc3709 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/terms.test.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/terms.test.tsx @@ -5,13 +5,14 @@ */ import React from 'react'; -import { termsOperation } from './terms'; import { shallow } from 'enzyme'; -import { IndexPatternPrivateState, TermsIndexPatternColumn } from '../../indexpattern'; +import { IndexPatternPrivateState } from '../../indexpattern'; import { EuiRange, EuiSelect } from '@elastic/eui'; import { UiSettingsClientContract } from 'src/core/public'; import { Storage } from 'ui/storage'; import { createMockedIndexPattern } from '../../mocks'; +import { TermsIndexPatternColumn } from './terms'; +import { termsOperation } from '.'; jest.mock('ui/new_platform'); @@ -317,6 +318,7 @@ describe('terms', () => { state={state} setState={setStateSpy} columnId="col1" + currentColumn={state.layers.first.columns.col1 as TermsIndexPatternColumn} layerId="first" storage={{} as Storage} uiSettings={{} as UiSettingsClientContract} @@ -364,6 +366,7 @@ describe('terms', () => { setState={setStateSpy} columnId="col1" layerId="first" + currentColumn={state.layers.first.columns.col1 as TermsIndexPatternColumn} storage={{} as Storage} uiSettings={{} as UiSettingsClientContract} /> @@ -381,6 +384,7 @@ describe('terms', () => { state={state} setState={setStateSpy} columnId="col1" + currentColumn={state.layers.first.columns.col1 as TermsIndexPatternColumn} layerId="first" storage={{} as Storage} uiSettings={{} as UiSettingsClientContract} @@ -427,6 +431,7 @@ describe('terms', () => { setState={setStateSpy} columnId="col1" layerId="first" + currentColumn={state.layers.first.columns.col1 as TermsIndexPatternColumn} storage={{} as Storage} uiSettings={{} as UiSettingsClientContract} /> @@ -448,6 +453,7 @@ describe('terms', () => { setState={setStateSpy} columnId="col1" layerId="first" + currentColumn={state.layers.first.columns.col1 as TermsIndexPatternColumn} storage={{} as Storage} uiSettings={{} as UiSettingsClientContract} /> @@ -490,6 +496,7 @@ describe('terms', () => { setState={setStateSpy} columnId="col1" layerId="first" + currentColumn={state.layers.first.columns.col1 as TermsIndexPatternColumn} storage={{} as Storage} uiSettings={{} as UiSettingsClientContract} /> @@ -506,6 +513,7 @@ describe('terms', () => { setState={setStateSpy} columnId="col1" layerId="first" + currentColumn={state.layers.first.columns.col1 as TermsIndexPatternColumn} storage={{} as Storage} uiSettings={{} as UiSettingsClientContract} /> diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/terms.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/terms.tsx index a45f6e755452e..fed760ac9d146 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/terms.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/terms.tsx @@ -7,10 +7,11 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { EuiForm, EuiFormRow, EuiRange, EuiSelect } from '@elastic/eui'; -import { TermsIndexPatternColumn, IndexPatternColumn } from '../../indexpattern'; +import { IndexPatternColumn } from '../../indexpattern'; import { updateColumnParam } from '../../state_helpers'; import { DataType } from '../../../types'; import { OperationDefinition } from '.'; +import { FieldBasedIndexPatternColumn } from './column_types'; type PropType = C extends React.ComponentType ? P : unknown; @@ -37,6 +38,15 @@ function isSortableByColumn(column: IndexPatternColumn) { const DEFAULT_SIZE = 3; +export interface TermsIndexPatternColumn extends FieldBasedIndexPatternColumn { + operationType: 'terms'; + params: { + size: number; + orderBy: { type: 'alphabetical' } | { type: 'column'; columnId: string }; + orderDirection: 'asc' | 'desc'; + }; +} + export const termsOperation: OperationDefinition = { type: 'terms', displayName: i18n.translate('xpack.lens.indexPattern.terms', { @@ -125,8 +135,7 @@ export const termsOperation: OperationDefinition = { } return currentColumn; }, - paramEditor: ({ state, setState, columnId: currentColumnId, layerId }) => { - const currentColumn = state.layers[layerId].columns[currentColumnId] as TermsIndexPatternColumn; + paramEditor: ({ state, setState, currentColumn, columnId: currentColumnId, layerId }) => { const SEPARATOR = '$$$'; function toValue(orderBy: TermsIndexPatternColumn['params']['orderBy']) { if (orderBy.type === 'alphabetical') { diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/index.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/index.ts index 7fc07384cd9fc..1e2bc5dcb6b62 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/index.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/index.ts @@ -5,3 +5,4 @@ */ export * from './operations'; +export { OperationType, IndexPatternColumn } from './definitions'; diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/operations.test.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/operations.test.ts index c186aef6f06e4..15a3b8ab19296 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/operations.test.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/operations.test.ts @@ -5,12 +5,9 @@ */ import { getOperationTypesForField, getAvailableOperationsByMetadata, buildColumn } from '.'; -import { - IndexPatternPrivateState, - AvgIndexPatternColumn, - MinIndexPatternColumn, - CountIndexPatternColumn, -} from '../indexpattern'; +import { IndexPatternPrivateState } from '../indexpattern'; +import { AvgIndexPatternColumn, MinIndexPatternColumn } from './definitions/metrics'; +import { CountIndexPatternColumn } from './definitions/count'; jest.mock('ui/new_platform'); jest.mock('../loader'); diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/operations.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/operations.ts index 9c1f998f8b45b..2c9d8a6a8d9eb 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/operations.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/operations.ts @@ -6,13 +6,14 @@ import _ from 'lodash'; import { DimensionPriority, OperationMetadata } from '../../types'; +import { IndexPatternField, IndexPattern } from '../indexpattern'; import { - IndexPatternColumn, - IndexPatternField, + operationDefinitionMap, + operationDefinitions, + GenericOperationDefinition, OperationType, - IndexPattern, -} from '../indexpattern'; -import { operationDefinitionMap, operationDefinitions, OperationDefinition } from './definitions'; + IndexPatternColumn, +} from './definitions'; /** * Returns all available operation types as a list at runtime. @@ -136,7 +137,7 @@ export function getAvailableOperationsByMetadata(indexPattern: IndexPattern) { } function getPossibleOperationForDocument( - operationDefinition: OperationDefinition, + operationDefinition: GenericOperationDefinition, indexPattern: IndexPattern ): OperationMetadata | undefined { return 'getPossibleOperationForDocument' in operationDefinition @@ -145,7 +146,7 @@ function getPossibleOperationForDocument( } function getPossibleOperationForField( - operationDefinition: OperationDefinition, + operationDefinition: GenericOperationDefinition, field: IndexPatternField ): OperationMetadata | undefined { return 'getPossibleOperationForField' in operationDefinition @@ -173,9 +174,7 @@ export function changeField( ); } - // This has to be casted back to an index pattern column, because the `operationDefinitionMap` only - // works on the base type `BaseIndexPatternColumn` - return operationDefinition.onFieldChange(column, indexPattern, newField) as IndexPatternColumn; + return operationDefinition.onFieldChange(column, indexPattern, newField); } /** @@ -204,10 +203,10 @@ export function buildColumn({ field?: IndexPatternField; asDocumentOperation?: boolean; }): IndexPatternColumn { - let operationDefinition: OperationDefinition | undefined; + let operationDefinition: GenericOperationDefinition | undefined; if (op) { - operationDefinition = operationDefinitionMap[op] as OperationDefinition; + operationDefinition = operationDefinitionMap[op]; } else if (asDocumentOperation) { operationDefinition = operationDefinitions.find(definition => getPossibleOperationForDocument(definition, indexPattern) diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/state_helpers.test.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/state_helpers.test.ts index fb0facd7f23f9..3988431573637 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/state_helpers.test.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/state_helpers.test.ts @@ -11,15 +11,11 @@ import { deleteColumn, updateLayerIndexPattern, } from './state_helpers'; -import { - IndexPatternPrivateState, - DateHistogramIndexPatternColumn, - TermsIndexPatternColumn, - AvgIndexPatternColumn, - IndexPattern, - IndexPatternLayer, -} from './indexpattern'; +import { IndexPatternPrivateState, IndexPattern, IndexPatternLayer } from './indexpattern'; import { operationDefinitionMap } from './operations'; +import { TermsIndexPatternColumn } from './operations/definitions/terms'; +import { DateHistogramIndexPatternColumn } from './operations/definitions/date_histogram'; +import { AvgIndexPatternColumn } from './operations/definitions/metrics'; jest.mock('ui/new_platform'); jest.mock('./operations/operations'); diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/state_helpers.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/state_helpers.ts index f0bae9cb47615..9da7293b04cf9 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/state_helpers.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/state_helpers.ts @@ -5,18 +5,12 @@ */ import _ from 'lodash'; -import { - IndexPatternPrivateState, - IndexPatternColumn, - BaseIndexPatternColumn, - IndexPatternLayer, - IndexPattern, -} from './indexpattern'; +import { IndexPatternPrivateState, IndexPatternLayer, IndexPattern } from './indexpattern'; import { isColumnTransferable } from './operations/operations'; -import { operationDefinitionMap } from './operations'; +import { operationDefinitionMap, IndexPatternColumn } from './operations'; export function updateColumnParam< - C extends BaseIndexPatternColumn & { params: object }, + C extends IndexPatternColumn & { params: object }, K extends keyof C['params'] >( state: IndexPatternPrivateState, @@ -41,13 +35,13 @@ export function updateColumnParam< ...state.layers[layerId], columns: { ...state.layers[layerId].columns, - [columnId]: ({ + [columnId]: { ...currentColumn, params: { ...currentColumn.params, [paramName]: value, }, - } as unknown) as IndexPatternColumn, + }, }, }, }, @@ -61,17 +55,17 @@ function adjustColumnReferencesForChangedColumn( const newColumns = { ...columns }; Object.keys(newColumns).forEach(currentColumnId => { if (currentColumnId !== columnId) { - const currentColumn = newColumns[currentColumnId] as BaseIndexPatternColumn; + const currentColumn = newColumns[currentColumnId]; const operationDefinition = operationDefinitionMap[currentColumn.operationType]; - newColumns[currentColumnId] = (operationDefinition.onOtherColumnChanged + newColumns[currentColumnId] = operationDefinition.onOtherColumnChanged ? operationDefinition.onOtherColumnChanged(currentColumn, newColumns) - : currentColumn) as IndexPatternColumn; + : currentColumn; } }); return newColumns; } -export function changeColumn({ +export function changeColumn({ state, layerId, columnId, @@ -81,7 +75,7 @@ export function changeColumn({ state: IndexPatternPrivateState; layerId: string; columnId: string; - newColumn: IndexPatternColumn; + newColumn: C; keepParams?: boolean; }): IndexPatternPrivateState { const oldColumn = state.layers[layerId].columns[columnId]; @@ -91,7 +85,7 @@ export function changeColumn({ oldColumn && oldColumn.operationType === newColumn.operationType && 'params' in oldColumn - ? ({ ...newColumn, params: oldColumn.params } as IndexPatternColumn) + ? { ...newColumn, params: oldColumn.params } : newColumn; const newColumns = adjustColumnReferencesForChangedColumn( @@ -176,7 +170,7 @@ export function updateLayerIndexPattern( const newColumns: IndexPatternLayer['columns'] = _.mapValues(keptColumns, column => { const operationDefinition = operationDefinitionMap[column.operationType]; return operationDefinition.transfer - ? (operationDefinition.transfer(column, newIndexPattern) as IndexPatternColumn) + ? operationDefinition.transfer(column, newIndexPattern) : column; }); const newColumnOrder = layer.columnOrder.filter(columnId => newColumns[columnId]); diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/to_expression.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/to_expression.ts index 781a01bd7fe8f..d64944baa8dab 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/to_expression.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/to_expression.ts @@ -24,9 +24,7 @@ function getExpressionForLayer( return operationDefinitionMap[column.operationType].toEsAggsConfig(column, columnId); } - const columnEntries = columnOrder.map( - colId => [colId, columns[colId]] as [string, IndexPatternColumn] - ); + const columnEntries = columnOrder.map(colId => [colId, columns[colId]] as const); if (columnEntries.length) { const aggs = columnEntries.map(([colId, col]) => { diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/utils.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/utils.ts index 4871b10cbd99c..aab991a27856a 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/utils.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/utils.ts @@ -5,7 +5,11 @@ */ import _ from 'lodash'; -import { BaseIndexPatternColumn, FieldBasedIndexPatternColumn, DraggedField } from './indexpattern'; +import { DraggedField } from './indexpattern'; +import { + BaseIndexPatternColumn, + FieldBasedIndexPatternColumn, +} from './operations/definitions/column_types'; export function hasField(column: BaseIndexPatternColumn): column is FieldBasedIndexPatternColumn { return 'sourceField' in column; From 3a6a23cad57afac6239db1cf45a87a25051b11af Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Mon, 26 Aug 2019 12:22:30 +0200 Subject: [PATCH 08/10] fix date handling on submit --- .../plugins/lens/public/app_plugin/app.tsx | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx b/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx index 68734c2296922..e198d72f31f41 100644 --- a/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx +++ b/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx @@ -76,7 +76,13 @@ export function App({ toDate: timeDefaults.to, }, indexPatternTitles: [], - localQueryBarState: {}, + localQueryBarState: { + query: { query: '', language }, + dateRange: { + from: timeDefaults.from, + to: timeDefaults.to, + }, + }, }); const lastKnownDocRef = useRef(undefined); @@ -184,8 +190,8 @@ export function App({ localQueryBarState: payload, }); }} - onChange={uncommitedQueryBarState => { - setState({ ...state, localQueryBarState: uncommitedQueryBarState }); + onChange={localQueryBarState => { + setState({ ...state, localQueryBarState }); }} isDirty={isLocalStateDirty(state.localQueryBarState, state.query, state.dateRange)} appName={'lens'} @@ -193,9 +199,13 @@ export function App({ store={store} showDatePicker={true} showQueryInput={true} - query={state.query} - dateRangeFrom={state.dateRange && state.dateRange.fromDate} - dateRangeTo={state.dateRange && state.dateRange.toDate} + query={state.localQueryBarState.query} + dateRangeFrom={ + state.localQueryBarState.dateRange && state.localQueryBarState.dateRange.from + } + dateRangeTo={ + state.localQueryBarState.dateRange && state.localQueryBarState.dateRange.to + } uiSettings={uiSettings} /> From 4729ae4af958f2623f26eb1a5919ea77bd3bc699 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Mon, 26 Aug 2019 14:19:46 +0200 Subject: [PATCH 09/10] fix test --- x-pack/legacy/plugins/lens/public/app_plugin/app.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx b/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx index e198d72f31f41..2b768e621c17d 100644 --- a/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx +++ b/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx @@ -98,6 +98,10 @@ export function App({ isLoading: false, persistedDoc: doc, query: doc.state.query, + localQueryBarState: { + ...state.localQueryBarState, + query: doc.state.query, + }, indexPatternTitles: doc.state.datasourceMetaData.filterableIndexPatterns.map( ({ title }) => title ), From 2b91dfe74dbfb0069a3c01ae1ed2d6809caf95c3 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Tue, 27 Aug 2019 12:36:56 +0200 Subject: [PATCH 10/10] move to options object for changeParam and use top level imports instead of deep imports for operations --- .../dimension_panel/dimension_panel.test.tsx | 2 +- .../indexpattern_suggestions.ts | 2 +- .../__mocks__/{operations.ts => index.ts} | 2 +- .../operations/definitions/column_types.ts | 15 +++++++++-- .../operations/definitions/date_histogram.tsx | 12 +++++---- .../operations/definitions/filter_ratio.tsx | 20 +++++++++++++-- .../operations/definitions/terms.tsx | 25 +++++++++++++------ .../indexpattern_plugin/state_helpers.test.ts | 10 ++++++-- .../indexpattern_plugin/state_helpers.ts | 22 ++++++++++------ .../indexpattern_plugin/to_expression.ts | 3 +-- 10 files changed, 82 insertions(+), 31 deletions(-) rename x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/__mocks__/{operations.ts => index.ts} (90%) diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.test.tsx index aaeeb63f0934b..f45b674e0c19b 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.test.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.test.tsx @@ -20,7 +20,7 @@ import { Storage } from 'ui/storage'; jest.mock('ui/new_platform'); jest.mock('../loader'); jest.mock('../state_helpers'); -jest.mock('../operations/operations'); +jest.mock('../operations'); // Used by indexpattern plugin, which is a dependency of a dependency jest.mock('ui/chrome'); diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern_suggestions.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern_suggestions.ts index e22ebd0e227da..a3a04802b0a31 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern_suggestions.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern_suggestions.ts @@ -14,7 +14,7 @@ import { IndexPatternPrivateState, IndexPattern, } from './indexpattern'; -import { buildColumn, getOperationTypesForField } from './operations/operations'; +import { buildColumn, getOperationTypesForField } from './operations'; import { hasField } from './utils'; function buildSuggestion({ diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/__mocks__/operations.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/__mocks__/index.ts similarity index 90% rename from x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/__mocks__/operations.ts rename to x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/__mocks__/index.ts index db0ffa3221efc..eeb19bba24006 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/__mocks__/operations.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/__mocks__/index.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -const actual = jest.requireActual('../../operations/operations'); +const actual = jest.requireActual('../../operations'); jest.spyOn(actual.operationDefinitionMap.date_histogram, 'paramEditor'); jest.spyOn(actual.operationDefinitionMap.terms, 'onOtherColumnChanged'); diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/column_types.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/column_types.ts index f5af25072c183..ed0e2fb3c96c5 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/column_types.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/column_types.ts @@ -17,11 +17,22 @@ export interface BaseIndexPatternColumn extends Operation { suggestedPriority?: DimensionPriority; } -type Omit = Pick>; +/** + * Base type for a column that doesn't have additional parameter. + * + * * `TOperationType` should be a string type containing just the type + * of the operation (e.g. `"sum"`). + * * `TBase` is the base column interface the operation type is set for - + * by default this is `FieldBasedIndexPatternColumn`, so + * `ParameterlessIndexPatternColumn<'foo'>` will give you a column type + * for an operation named foo that operates on a field. + * By passing in another `TBase` (e.g. just `BaseIndexPatternColumn`), + * you can also create other column types. + */ export type ParameterlessIndexPatternColumn< TOperationType extends string, TBase extends BaseIndexPatternColumn = FieldBasedIndexPatternColumn -> = Omit & { operationType: TOperationType }; +> = TBase & { operationType: TOperationType }; export interface FieldBasedIndexPatternColumn extends BaseIndexPatternColumn { sourceField: string; diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.tsx index 62061c05b2e64..6c75141388514 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.tsx @@ -187,7 +187,9 @@ export const dateHistogramOperation: OperationDefinition) { const interval = ev.target.checked ? defaultCustomInterval : autoInterval; - setState(updateColumnParam(state, layerId, currentColumn, 'interval', interval)); + setState( + updateColumnParam({ state, layerId, currentColumn, paramName: 'interval', value: interval }) + ); } return ( @@ -230,13 +232,13 @@ export const dateHistogramOperation: OperationDefinition) => setState( - updateColumnParam( + updateColumnParam({ state, layerId, currentColumn, - 'interval', - numericToInterval(Number(e.target.value)) - ) + paramName: 'interval', + value: numericToInterval(Number(e.target.value)), + }) ) } aria-label={i18n.translate('xpack.lens.indexPattern.dateHistogram.interval', { diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/filter_ratio.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/filter_ratio.tsx index 4a942f85bab6d..6dee938b1fce9 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/filter_ratio.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/filter_ratio.tsx @@ -96,7 +96,15 @@ export const filterRatioOperation: OperationDefinition { - setState(updateColumnParam(state, layerId, currentColumn, 'numerator', newQuery)); + setState( + updateColumnParam({ + state, + layerId, + currentColumn, + paramName: 'numerator', + value: newQuery, + }) + ); }} /> @@ -115,7 +123,15 @@ export const filterRatioOperation: OperationDefinition { - setState(updateColumnParam(state, layerId, currentColumn, 'denominator', newQuery)); + setState( + updateColumnParam({ + state, + layerId, + currentColumn, + paramName: 'denominator', + value: newQuery, + }) + ); }} /> ) : ( diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/terms.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/terms.tsx index fed760ac9d146..c13479c04c0c5 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/terms.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/terms.tsx @@ -184,7 +184,13 @@ export const termsOperation: OperationDefinition = { showInput onChange={(e: React.ChangeEvent) => setState( - updateColumnParam(state, layerId, currentColumn, 'size', Number(e.target.value)) + updateColumnParam({ + state, + layerId, + currentColumn, + paramName: 'size', + value: Number(e.target.value), + }) ) } aria-label={i18n.translate('xpack.lens.indexPattern.terms.size', { @@ -203,13 +209,13 @@ export const termsOperation: OperationDefinition = { value={toValue(currentColumn.params.orderBy)} onChange={(e: React.ChangeEvent) => setState( - updateColumnParam( + updateColumnParam({ state, layerId, currentColumn, - 'orderBy', - fromValue(e.target.value) - ) + paramName: 'orderBy', + value: fromValue(e.target.value), + }) ) } aria-label={i18n.translate('xpack.lens.indexPattern.terms.orderBy', { @@ -241,8 +247,13 @@ export const termsOperation: OperationDefinition = { value={currentColumn.params.orderDirection} onChange={(e: React.ChangeEvent) => setState( - updateColumnParam(state, layerId, currentColumn, 'orderDirection', e.target - .value as 'asc' | 'desc') + updateColumnParam({ + state, + layerId, + currentColumn, + paramName: 'orderDirection', + value: e.target.value as 'asc' | 'desc', + }) ) } aria-label={i18n.translate('xpack.lens.indexPattern.terms.orderBy', { diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/state_helpers.test.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/state_helpers.test.ts index 3988431573637..1d366b931b89b 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/state_helpers.test.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/state_helpers.test.ts @@ -18,7 +18,7 @@ import { DateHistogramIndexPatternColumn } from './operations/definitions/date_h import { AvgIndexPatternColumn } from './operations/definitions/metrics'; jest.mock('ui/new_platform'); -jest.mock('./operations/operations'); +jest.mock('./operations'); describe('state_helpers', () => { describe('deleteColumn', () => { @@ -152,7 +152,13 @@ describe('state_helpers', () => { }; expect( - updateColumnParam(state, 'first', currentColumn, 'interval', 'M').layers.first.columns.col1 + updateColumnParam({ + state, + layerId: 'first', + currentColumn, + paramName: 'interval', + value: 'M', + }).layers.first.columns.col1 ).toEqual({ ...currentColumn, params: { interval: 'M' }, diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/state_helpers.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/state_helpers.ts index 9da7293b04cf9..f2b55bbcb0dd5 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/state_helpers.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/state_helpers.ts @@ -6,19 +6,25 @@ import _ from 'lodash'; import { IndexPatternPrivateState, IndexPatternLayer, IndexPattern } from './indexpattern'; -import { isColumnTransferable } from './operations/operations'; +import { isColumnTransferable } from './operations'; import { operationDefinitionMap, IndexPatternColumn } from './operations'; export function updateColumnParam< C extends IndexPatternColumn & { params: object }, K extends keyof C['params'] ->( - state: IndexPatternPrivateState, - layerId: string, - currentColumn: C, - paramName: K, - value: C['params'][K] -): IndexPatternPrivateState { +>({ + state, + layerId, + currentColumn, + paramName, + value, +}: { + state: IndexPatternPrivateState; + layerId: string; + currentColumn: C; + paramName: K; + value: C['params'][K]; +}): IndexPatternPrivateState { const columnId = Object.entries(state.layers[layerId].columns).find( ([_columnId, column]) => column === currentColumn )![0]; diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/to_expression.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/to_expression.ts index d64944baa8dab..9bd68aac90403 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/to_expression.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/to_expression.ts @@ -7,8 +7,7 @@ import _ from 'lodash'; import { IndexPatternPrivateState, IndexPatternColumn, IndexPattern } from './indexpattern'; -import { buildColumn } from './operations/operations'; -import { operationDefinitionMap } from './operations'; +import { buildColumn, operationDefinitionMap } from './operations'; function getExpressionForLayer( indexPattern: IndexPattern,