From dc3040738836b33da02ef1f7db5a53f24ea809bd Mon Sep 17 00:00:00 2001 From: Wylie Conlon <wylieconlon@gmail.com> Date: Tue, 2 Jul 2019 11:30:28 -0400 Subject: [PATCH 01/10] WIP filter ratio --- .../query/query_bar/components/query_bar.tsx | 1 - .../ui/public/agg_types/buckets/filters.js | 2 +- .../public/persisted_log/recently_accessed.ts | 2 +- x-pack/dev-tools/jest/create_jest_config.js | 1 + .../editor_frame/index.scss | 1 - .../editor_frame/workspace_panel.tsx | 51 +++--- x-pack/legacy/plugins/lens/public/index.ts | 2 + .../indexpattern_plugin/__mocks__/loader.ts | 6 + .../dimension_panel/field_select.tsx | 4 +- .../indexpattern_plugin/filter_ratio.test.ts | 73 +++++++++ .../indexpattern_plugin/filter_ratio.ts | 70 +++++++++ .../indexpattern_plugin/indexpattern.test.tsx | 2 - .../indexpattern_plugin/indexpattern.tsx | 25 ++- .../lens/public/indexpattern_plugin/mocks.ts | 21 +++ .../operation_definitions/filter_ratio.tsx | 145 ++++++++++++++++++ .../indexpattern_plugin/operations.test.ts | 1 + .../public/indexpattern_plugin/operations.ts | 2 + .../public/indexpattern_plugin/plugin.tsx | 8 +- .../indexpattern_plugin/to_expression.ts | 36 ++++- .../plugins/lens/public/interpreter_types.ts | 1 - 20 files changed, 412 insertions(+), 42 deletions(-) create mode 100644 x-pack/legacy/plugins/lens/public/indexpattern_plugin/filter_ratio.test.ts create mode 100644 x-pack/legacy/plugins/lens/public/indexpattern_plugin/filter_ratio.ts create mode 100644 x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/filter_ratio.tsx diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar.tsx b/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar.tsx index e1df6286bd120..a2eaff8ca36f0 100644 --- a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar.tsx +++ b/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar.tsx @@ -381,5 +381,4 @@ export class QueryBarUI extends Component<Props, State> { } } -// @ts-ignore export const QueryBar = injectI18n(QueryBarUI); diff --git a/src/legacy/ui/public/agg_types/buckets/filters.js b/src/legacy/ui/public/agg_types/buckets/filters.js index ee1a81b3ed2d5..3c2df83569b8a 100644 --- a/src/legacy/ui/public/agg_types/buckets/filters.js +++ b/src/legacy/ui/public/agg_types/buckets/filters.js @@ -57,7 +57,7 @@ export const filtersBucketAgg = new BucketAggType({ const outFilters = _.transform(inFilters, function (filters, filter) { let input = _.cloneDeep(filter.input); - if (!input || !input.query) { + if (!input || typeof input.query !== 'string') { console.log('malformed filter agg params, missing "input" query'); // eslint-disable-line no-console return; } diff --git a/src/legacy/ui/public/persisted_log/recently_accessed.ts b/src/legacy/ui/public/persisted_log/recently_accessed.ts index 6d984d155d551..0387686753677 100644 --- a/src/legacy/ui/public/persisted_log/recently_accessed.ts +++ b/src/legacy/ui/public/persisted_log/recently_accessed.ts @@ -17,6 +17,6 @@ * under the License. */ -import { npStart } from '../new_platform'; +import { npStart } from 'ui/new_platform'; export const recentlyAccessed = npStart.core.chrome.recentlyAccessed; diff --git a/x-pack/dev-tools/jest/create_jest_config.js b/x-pack/dev-tools/jest/create_jest_config.js index fa8cae2b6b86e..006e5c1d31243 100644 --- a/x-pack/dev-tools/jest/create_jest_config.js +++ b/x-pack/dev-tools/jest/create_jest_config.js @@ -46,6 +46,7 @@ export function createJestConfig({ ], transform: { '^.+\\.(js|tsx?)$': `${kibanaDirectory}/src/dev/jest/babel_transform.js`, + '^.+\\.html?$': 'jest-raw-loader', }, transformIgnorePatterns: [ // ignore all node_modules except @elastic/eui which requires babel transforms to handle dynamic import() 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 b57fe73adb8b2..f2a19e40ce4a2 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 @@ -26,7 +26,6 @@ margin: 0; flex: 1 0 18%; min-width: ($euiSize * 16); - height: 100%; display: flex; flex-direction: column; } diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.tsx index 796fce7ddbcf5..1b89f04cb5a12 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.tsx +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.tsx @@ -8,6 +8,7 @@ import React, { useState, useEffect, useMemo, useContext } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiCodeBlock, EuiSpacer } from '@elastic/eui'; import { toExpression } from '@kbn/interpreter/common'; +import { throttle } from 'lodash'; import { ExpressionRenderer } from '../../../../../../../src/legacy/core_plugins/data/public'; import { Action } from './state_management'; import { Datasource, Visualization, DatasourcePublicAPI } from '../../types'; @@ -15,6 +16,12 @@ import { DragDrop, DragContext } from '../../drag_drop'; import { getSuggestions, toSwitchAction } from './suggestion_helpers'; import { buildExpression } from './expression_helpers'; +function useThrottle(fn: Function, wait = 500) { + const newFn = throttle(fn, wait); + + return newFn(); +} + export interface WorkspacePanelProps { activeDatasource: Datasource; datasourceState: unknown; @@ -78,27 +85,29 @@ export function WorkspacePanel({ const activeVisualization = activeVisualizationId ? visualizationMap[activeVisualizationId] : null; - const expression = useMemo( - () => { - try { - return buildExpression( - activeVisualization, - visualizationState, - activeDatasource, - datasourceState, - datasourcePublicAPI - ); - } catch (e) { - setExpressionError(e.toString()); - } - }, - [ - activeVisualization, - visualizationState, - activeDatasource, - datasourceState, - datasourcePublicAPI, - ] + const expression = useThrottle(() => + useMemo( + () => { + try { + return buildExpression( + activeVisualization, + visualizationState, + activeDatasource, + datasourceState, + datasourcePublicAPI + ); + } catch (e) { + setExpressionError(e.toString()); + } + }, + [ + activeVisualization, + visualizationState, + activeDatasource, + datasourceState, + datasourcePublicAPI, + ] + ) ); useEffect( diff --git a/x-pack/legacy/plugins/lens/public/index.ts b/x-pack/legacy/plugins/lens/public/index.ts index 532b6e66d2b27..47f6b0220c459 100644 --- a/x-pack/legacy/plugins/lens/public/index.ts +++ b/x-pack/legacy/plugins/lens/public/index.ts @@ -7,6 +7,8 @@ export * from './types'; import 'ui/autoload/all'; +// Used for kuery autocomplete +import 'uiExports/autocompleteProviders'; // Used to run esaggs queries import 'uiExports/fieldFormats'; import 'uiExports/search'; diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/__mocks__/loader.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/__mocks__/loader.ts index 7823768896d64..3484092108e2a 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/__mocks__/loader.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/__mocks__/loader.ts @@ -17,18 +17,21 @@ export function getIndexPatterns() { type: 'date', aggregatable: true, searchable: true, + filterable: true, }, { name: 'bytes', type: 'number', aggregatable: true, searchable: true, + filterable: true, }, { name: 'source', type: 'string', aggregatable: true, searchable: true, + filterable: true, }, ], }, @@ -42,18 +45,21 @@ export function getIndexPatterns() { type: 'date', aggregatable: true, searchable: true, + filterable: true, }, { name: 'bytes', type: 'number', aggregatable: true, searchable: true, + filterable: true, }, { name: 'source', type: 'string', aggregatable: true, searchable: true, + filterable: true, }, ], typeMeta: { diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/field_select.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/field_select.tsx index 9cb3232aeb295..fbf3f934b42cb 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/field_select.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/field_select.tsx @@ -54,7 +54,9 @@ export function FieldSelect({ } const fieldOptions = []; - const fieldLessColumn = filteredColumns.find(column => !hasField(column)); + const fieldLessColumn = filteredColumns.find( + column => !hasField(column) && isCompatibleWithCurrentOperation(column) + ); if (fieldLessColumn) { fieldOptions.push({ label: i18n.translate('xpack.lens.indexPattern.documentField', { diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/filter_ratio.test.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/filter_ratio.test.ts new file mode 100644 index 0000000000000..11102cf7a5a07 --- /dev/null +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/filter_ratio.test.ts @@ -0,0 +1,73 @@ +/* + * 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 { calculateFilterRatio } from './filter_ratio'; +import { KibanaDatatable } from 'src/legacy/core_plugins/interpreter/types'; + +describe('calculate_filter_ratio', () => { + it('should collapse two rows and columns into a single row and column', () => { + const input: KibanaDatatable = { + type: 'kibana_datatable', + columns: [{ id: 'bucket', name: 'A' }, { id: 'filter-ratio', name: 'B' }], + rows: [{ bucket: 'a', 'filter-ratio': 5 }, { bucket: 'b', 'filter-ratio': 10 }], + }; + + expect(calculateFilterRatio.fn(input, { id: 'bucket' }, {})).toEqual({ + columns: [{ id: 'bucket', name: 'A' }], + rows: [{ bucket: 0.5 }], + type: 'kibana_datatable', + }); + }); + + it('should return 0 when the denominator is undefined', () => { + const input: KibanaDatatable = { + type: 'kibana_datatable', + columns: [{ id: 'bucket', name: 'A' }, { id: 'filter-ratio', name: 'B' }], + rows: [{ bucket: 'a', 'filter-ratio': 5 }, { bucket: 'b' }], + }; + + expect(calculateFilterRatio.fn(input, { id: 'bucket' }, {})).toEqual({ + columns: [{ id: 'bucket', name: 'A' }], + rows: [{ bucket: 0 }], + type: 'kibana_datatable', + }); + }); + + it('should return 0 when the denominator is 0', () => { + const input: KibanaDatatable = { + type: 'kibana_datatable', + columns: [{ id: 'bucket', name: 'A' }, { id: 'filter-ratio', name: 'B' }], + rows: [{ bucket: 'a', 'filter-ratio': 5 }, { bucket: 'b', 'filter-ratio': 0 }], + }; + + expect(calculateFilterRatio.fn(input, { id: 'bucket' }, {})).toEqual({ + columns: [{ id: 'bucket', name: 'A' }], + rows: [{ bucket: 0 }], + type: 'kibana_datatable', + }); + }); + + it('should keep columns which are not mapped', () => { + const input: KibanaDatatable = { + type: 'kibana_datatable', + columns: [ + { id: 'bucket', name: 'A' }, + { id: 'filter-ratio', name: 'B' }, + { id: 'extra', name: 'C' }, + ], + rows: [ + { bucket: 'a', 'filter-ratio': 5, extra: 'first' }, + { bucket: 'b', 'filter-ratio': 10, extra: 'second' }, + ], + }; + + expect(calculateFilterRatio.fn(input, { id: 'bucket' }, {})).toEqual({ + columns: [{ id: 'bucket', name: 'A' }, { id: 'extra', name: 'C' }], + rows: [{ bucket: 0.5, extra: 'first' }], + type: 'kibana_datatable', + }); + }); +}); diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/filter_ratio.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/filter_ratio.ts new file mode 100644 index 0000000000000..943e1ace89605 --- /dev/null +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/filter_ratio.ts @@ -0,0 +1,70 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { ExpressionFunction } from 'src/legacy/core_plugins/interpreter/types'; +import { KibanaDatatable } from '../types'; + +interface FilterRatioKey { + id: string; +} + +export const calculateFilterRatio: ExpressionFunction< + 'lens_calculate_filter_ratio', + KibanaDatatable, + FilterRatioKey, + KibanaDatatable +> = { + name: 'lens_calculate_filter_ratio', + type: 'kibana_datatable', + help: i18n.translate('xpack.lens.functions.calculateFilterRatio.help', { + defaultMessage: 'A helper to collapse two filter ratio rows into a single row', + }), + args: { + id: { + types: ['string'], + help: i18n.translate('xpack.lens.functions.calculateFilterRatio.id.help', { + defaultMessage: 'The column ID which has the filter ratio', + }), + }, + }, + context: { + types: ['kibana_datatable'], + }, + fn(data: KibanaDatatable, { id }: FilterRatioKey) { + const newRows: KibanaDatatable['rows'] = []; + + if (data.rows.length === 0) { + return data; + } + + const entries = Object.entries(data.rows[0]); + const [[ratioKey]] = entries.filter(([key]) => key === id); + const [[valueKey]] = entries.filter(([key]) => key.includes('filter-ratio')); + + for (let i = 0; i < data.rows.length; i += 2) { + const row1 = data.rows[i]; + const row2 = data.rows[i + 1]; + + const calculatedRatio = row2[valueKey] + ? (row1[valueKey] as number) / (row2[valueKey] as number) + : 0; + + const result = { ...row1 }; + delete result[valueKey]; + + result[id] = calculatedRatio; + + newRows.push(result); + } + + return { + type: 'kibana_datatable', + rows: newRows, + columns: data.columns.filter(col => !col.id.includes('filter-ratio')), + }; + }, +}; diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.test.tsx index c3610ab2cf95d..1204fd90cc234 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.test.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.test.tsx @@ -16,8 +16,6 @@ import { import { DatasourcePublicAPI, Operation, Datasource } from '../types'; import { createMockedDragDropContext } from './mocks'; -jest.mock('./loader'); - const expectedIndexPatterns = { 1: { id: '1', 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 75fd323c6048f..2d8f4af2c9e46 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.tsx @@ -11,6 +11,7 @@ import { Chrome } from 'ui/chrome'; import { ToastNotifications } from 'ui/notify/toasts/toast_notifications'; import { EuiComboBox } from '@elastic/eui'; import uuid from 'uuid'; +import { I18nProvider } from '@kbn/i18n/react'; import { DatasourceDimensionPanelProps, DatasourceDataPanelProps, @@ -33,7 +34,8 @@ export type IndexPatternColumn = | AvgIndexPatternColumn | MinIndexPatternColumn | MaxIndexPatternColumn - | CountIndexPatternColumn; + | CountIndexPatternColumn + | FilterRatioIndexPatternColumn; export interface BaseIndexPatternColumn { // Public @@ -74,6 +76,14 @@ export interface TermsIndexPatternColumn extends FieldBasedIndexPatternColumn { }; } +export interface FilterRatioIndexPatternColumn extends BaseIndexPatternColumn { + operationType: 'filter_ratio'; + params: { + numerator: { language: string; query: string }; + denominator: { language: string; query: string }; + }; +} + export type CountIndexPatternColumn = ParameterlessIndexPatternColumn< 'count', BaseIndexPatternColumn @@ -96,6 +106,7 @@ export interface IndexPatternField { esTypes?: string[]; aggregatable: boolean; searchable: boolean; + filterable: boolean; aggregationRestrictions?: Partial< Record< string, @@ -269,11 +280,13 @@ export function getIndexPatternDatasource(chrome: Chrome, toastNotifications: To renderDimensionPanel: (domElement: Element, props: DatasourceDimensionPanelProps) => { render( - <IndexPatternDimensionPanel - state={state} - setState={newState => setState(newState)} - {...props} - />, + <I18nProvider> + <IndexPatternDimensionPanel + state={state} + setState={newState => setState(newState)} + {...props} + /> + </I18nProvider>, domElement ); }, diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/mocks.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/mocks.ts index b24d53e0f552f..67707b0ffd103 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/mocks.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/mocks.ts @@ -12,3 +12,24 @@ export function createMockedDragDropContext(): jest.Mocked<DragContextState> { setDragging: jest.fn(), }; } + +jest.mock('ui/new_platform'); + +jest.mock('ui/storage', () => { + return { + Storage: jest.fn().mockImplementation(() => ({ + get: jest.fn(), + getItem: jest.fn(), + })), + }; +}); + +jest.mock('../../../../../../src/legacy/core_plugins/data/public/setup', () => ({ + data: { + query: { + ui: { + QueryBarInput: jest.fn(), + }, + }, + }, +})); 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/operation_definitions/filter_ratio.tsx new file mode 100644 index 0000000000000..bbcee8ca51b7c --- /dev/null +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/filter_ratio.tsx @@ -0,0 +1,145 @@ +/* + * 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 React, { useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { Storage } from 'ui/storage'; +import { EuiButton, EuiFormRow } from '@elastic/eui'; +import { data as dataSetup } from '../../../../../../../src/legacy/core_plugins/data/public/setup'; +import { FilterRatioIndexPatternColumn } from '../indexpattern'; +import { DimensionPriority } from '../../types'; +import { OperationDefinition } from '../operations'; +import { updateColumnParam } from '../state_helpers'; + +const localStorage = new Storage(window.localStorage); + +const { QueryBarInput } = dataSetup.query.ui; + +export const filterRatioOperation: OperationDefinition<FilterRatioIndexPatternColumn> = { + type: 'filter_ratio', + displayName: i18n.translate('xpack.lens.indexPattern.filterRatio', { + defaultMessage: 'Filter Ratio', + }), + isApplicableWithoutField: true, + isApplicableForField: () => false, + buildColumn( + operationId: string, + suggestedOrder?: DimensionPriority + ): FilterRatioIndexPatternColumn { + return { + operationId, + label: i18n.translate('xpack.lens.indexPattern.filterRatio', { + defaultMessage: 'Filter Ratio', + }), + dataType: 'number', + operationType: 'filter_ratio', + suggestedOrder, + isBucketed: false, + params: { + // numerator: { language: 'kuery', query: '' }, + numerator: { language: 'kuery', query: 'geo.src : "CN"' }, + denominator: { language: 'kuery', query: '*' }, + }, + }; + }, + toEsAggsConfig: (column, columnId) => ({ + id: columnId, + enabled: true, + type: 'filters', + schema: 'segment', + params: { + filters: [ + { + input: column.params.numerator, + label: '', + }, + { + input: column.params.denominator, + label: '', + }, + ], + }, + }), + paramEditor: ({ state, setState, columnId: currentColumnId }) => { + const [hasDenominator, setDenominator] = useState(false); + + return ( + <div> + <EuiFormRow + label={i18n.translate('xpack.lens.indexPattern.filterRatioNumeratorLabel', { + defaultMessage: 'Count of documents matching query:', + })} + > + <QueryBarInput + appName={'lens'} + indexPatterns={[state.indexPatterns[state.currentIndexPatternId]]} + query={ + (state.columns[currentColumnId] as FilterRatioIndexPatternColumn).params.numerator + } + screenTitle={''} + store={localStorage} + onChange={(newQuery: { language: string; query: string }) => { + setState( + updateColumnParam( + state, + state.columns[currentColumnId] as FilterRatioIndexPatternColumn, + 'numerator', + newQuery + ) + ); + }} + /> + </EuiFormRow> + + <EuiFormRow + label={i18n.translate('xpack.lens.indexPattern.filterRatioDividesLabel', { + defaultMessage: 'Divided by:', + })} + > + {hasDenominator ? ( + <QueryBarInput + appName={'lens'} + indexPatterns={[state.indexPatterns[state.currentIndexPatternId]]} + query={ + (state.columns[currentColumnId] as FilterRatioIndexPatternColumn).params.denominator + } + screenTitle={''} + store={localStorage} + onChange={(newQuery: { language: string; query: string }) => { + setState( + updateColumnParam( + state, + state.columns[currentColumnId] as FilterRatioIndexPatternColumn, + 'denominator', + newQuery + ) + ); + }} + /> + ) : ( + <> + <FormattedMessage + id="xpack.lens.indexPattern.filterRatioDefaultDenominator" + defaultMessage="Count of documents" + /> + + <EuiFormRow> + <EuiButton fill onClick={() => setDenominator(true)}> + <FormattedMessage + id="xpack.lens.indexPattern.filterRatioUseDenominatorButton" + defaultMessage="Divide by filter instead" + /> + </EuiButton> + </EuiFormRow> + </> + )} + </EuiFormRow> + </div> + ); + }, +}; diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations.test.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations.test.ts index c42ae4def66e1..b4ac16f3a361b 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations.test.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations.test.ts @@ -7,6 +7,7 @@ import { getOperationTypesForField, getPotentialColumns } from './operations'; import { IndexPatternPrivateState } from './indexpattern'; import { hasField } from './state_helpers'; +import './mocks'; const expectedIndexPatterns = { 1: { diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations.ts index b5a0591084599..cf98c735684e2 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations.ts @@ -21,6 +21,7 @@ import { } from './operation_definitions/metrics'; import { dateHistogramOperation } from './operation_definitions/date_histogram'; import { countOperation } from './operation_definitions/count'; +import { filterRatioOperation } from './operation_definitions/filter_ratio'; import { sortByField } from './state_helpers'; type PossibleOperationDefinitions< @@ -46,6 +47,7 @@ export const operationDefinitionMap: AllOperationDefinitions = { avg: averageOperation, sum: sumOperation, count: countOperation, + filter_ratio: filterRatioOperation, }; const operationDefinitions: PossibleOperationDefinitions[] = Object.values(operationDefinitionMap); diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/plugin.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/plugin.tsx index 7c186f35dc71a..8899d9bdf00c9 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/plugin.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/plugin.tsx @@ -10,12 +10,11 @@ import chrome from 'ui/chrome'; import { toastNotifications } from 'ui/notify'; import { getIndexPatternDatasource } from './indexpattern'; -import { - functionsRegistry, - // @ts-ignore untyped dependency -} from '../../../../../../src/legacy/core_plugins/interpreter/public/registries'; import { ExpressionFunction } from '../../../../../../src/legacy/core_plugins/interpreter/public'; +// @ts-ignore untyped dependency +import { functionsRegistry } from '../../../../../../src/legacy/core_plugins/interpreter/public/registries'; import { renameColumns } from './rename_columns'; +import { calculateFilterRatio } from './filter_ratio'; // TODO these are intermediary types because interpreter is not typed yet // They can get replaced by references to the real interfaces as soon as they @@ -37,6 +36,7 @@ class IndexPatternDatasourcePlugin { setup(_core: CoreSetup | null, { interpreter }: IndexPatternDatasourcePluginPlugins) { interpreter.functionsRegistry.register(() => renameColumns); + interpreter.functionsRegistry.register(() => calculateFilterRatio); return getIndexPatternDatasource(chrome, toastNotifications); } 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 6f39b521d20d8..e2daa6db0b7a6 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,11 @@ import _ from 'lodash'; import { IndexPatternPrivateState, IndexPatternColumn } from './indexpattern'; -import { operationDefinitionMap, OperationDefinition } from './operations'; +import { + buildColumnForOperationType, + operationDefinitionMap, + OperationDefinition, +} from './operations'; export function toExpression(state: IndexPatternPrivateState) { if (state.columnOrder.length === 0) { @@ -32,6 +36,9 @@ export function toExpression(state: IndexPatternPrivateState) { return getEsAggsConfig(col, colId); }); + // TODO: The filters will generate extra columns which are actually going to affect the aggregation order. They need + // to be added right before all the metrics, and then removed + const idMap = columnEntries.reduce( (currentIdMap, [colId], index) => { return { @@ -42,11 +49,34 @@ export function toExpression(state: IndexPatternPrivateState) { {} as Record<string, string> ); - return `esaggs + const expression = `esaggs index="${state.currentIndexPatternId}" metricsAtAllLevels=false partialRows=false - aggConfigs='${JSON.stringify(aggs)}' | lens_rename_columns idMap='${JSON.stringify(idMap)}'`; + aggConfigs='${JSON.stringify(aggs)}' | lens_rename_columns idMap='${JSON.stringify( + idMap + )}' | clog`; + + const filterRatios = columnEntries.filter( + ([colId, col]) => col.operationType === 'filter_ratio' + ); + + if (filterRatios.length) { + const countColumn = buildColumnForOperationType(columnEntries.length, 'count', 2); + aggs.push(getEsAggsConfig(countColumn, 'filter-ratio')); + + return `esaggs + index="${state.currentIndexPatternId}" + metricsAtAllLevels=false + partialRows=false + aggConfigs='${JSON.stringify(aggs)}' | lens_rename_columns idMap='${JSON.stringify( + idMap + )}' | ${filterRatios + .map(([id]) => `lens_calculate_filter_ratio id=${id}`) + .join(' | ')} | clog`; + } + + return expression; } return null; diff --git a/x-pack/legacy/plugins/lens/public/interpreter_types.ts b/x-pack/legacy/plugins/lens/public/interpreter_types.ts index b24f39080f827..fe02ab11757cc 100644 --- a/x-pack/legacy/plugins/lens/public/interpreter_types.ts +++ b/x-pack/legacy/plugins/lens/public/interpreter_types.ts @@ -5,7 +5,6 @@ */ import { Registry } from '@kbn/interpreter/target/common'; -// @ts-ignore untyped module import { ExpressionFunction } from '../../../../../src/legacy/core_plugins/interpreter/public'; // TODO these are intermediary types because interpreter is not typed yet From cfce82f7f9b48cc0327e640c792fe4a79031d21c Mon Sep 17 00:00:00 2001 From: Wylie Conlon <wylieconlon@gmail.com> Date: Tue, 2 Jul 2019 17:40:49 -0400 Subject: [PATCH 02/10] Fix test issues --- .../public/index_patterns/_index_pattern.d.ts | 1 + .../datatable_visualization_plugin/plugin.tsx | 1 - .../__snapshots__/indexpattern.test.tsx.snap | 3 ++ .../dimension_panel/dimension_panel.test.tsx | 23 +++++++++++++-- .../dimension_panel/field_select.tsx | 28 ++++++++++++++----- .../indexpattern_plugin/filter_ratio.ts | 1 - .../indexpattern_plugin/indexpattern.test.tsx | 17 +++++++++-- .../indexpattern_plugin/indexpattern.tsx | 7 +++-- .../lens/public/indexpattern_plugin/mocks.ts | 21 -------------- .../date_histogram.test.tsx | 3 ++ .../operation_definitions/filter_ratio.tsx | 9 +++--- .../indexpattern_plugin/operations.test.ts | 20 ++++++++++++- .../public/indexpattern_plugin/operations.ts | 7 +++++ .../public/indexpattern_plugin/plugin.tsx | 1 - .../public/xy_visualization_plugin/plugin.tsx | 1 - 15 files changed, 99 insertions(+), 44 deletions(-) diff --git a/src/legacy/ui/public/index_patterns/_index_pattern.d.ts b/src/legacy/ui/public/index_patterns/_index_pattern.d.ts index d3856b79b1d09..bfdc3f26ad53e 100644 --- a/src/legacy/ui/public/index_patterns/_index_pattern.d.ts +++ b/src/legacy/ui/public/index_patterns/_index_pattern.d.ts @@ -45,6 +45,7 @@ export interface StaticIndexPatternField { type: string; aggregatable: boolean; searchable: boolean; + filterable: boolean; } export interface StaticIndexPattern { diff --git a/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/plugin.tsx b/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/plugin.tsx index 356e18ddc8419..7bcddae13e1ac 100644 --- a/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/plugin.tsx +++ b/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/plugin.tsx @@ -11,7 +11,6 @@ import { datatableVisualization } from './visualization'; import { renderersRegistry, functionsRegistry, - // @ts-ignore untyped dependency } from '../../../../../../src/legacy/core_plugins/interpreter/public/registries'; import { InterpreterSetup, RenderFunction } from '../interpreter_types'; import { datatable, datatableColumns, datatableRenderer } from './expression'; diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/__snapshots__/indexpattern.test.tsx.snap b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/__snapshots__/indexpattern.test.tsx.snap index 5b260ec1b7458..da4175283122b 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/__snapshots__/indexpattern.test.tsx.snap +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/__snapshots__/indexpattern.test.tsx.snap @@ -45,6 +45,7 @@ exports[`IndexPattern Data Source #renderDataPanel should match snapshot 1`] = ` value={ Object { "aggregatable": true, + "filterable": true, "name": "timestamp", "searchable": true, "type": "date", @@ -59,6 +60,7 @@ exports[`IndexPattern Data Source #renderDataPanel should match snapshot 1`] = ` value={ Object { "aggregatable": true, + "filterable": true, "name": "bytes", "searchable": true, "type": "number", @@ -73,6 +75,7 @@ exports[`IndexPattern Data Source #renderDataPanel should match snapshot 1`] = ` value={ Object { "aggregatable": true, + "filterable": true, "name": "source", "searchable": true, "type": "string", 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 b5bd083472566..e73561a840538 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 @@ -15,6 +15,11 @@ import { DropHandler, DragContextState } from '../../drag_drop'; import { createMockedDragDropContext } from '../mocks'; import { mountWithIntl as mount, shallowWithIntl as shallow } from 'test_utils/enzyme_helpers'; +jest.mock('../loader'); +jest.mock('ui/new_platform'); +jest.mock('ui/chrome'); +jest.mock('plugins/data/setup', () => ({ data: { query: { ui: {} } } })); + jest.mock('../state_helpers'); jest.mock('../operations'); @@ -29,24 +34,28 @@ const expectedIndexPatterns = { type: 'date', aggregatable: true, searchable: true, + filterable: true, }, { name: 'bytes', type: 'number', aggregatable: true, searchable: true, + filterable: true, }, { name: 'memory', type: 'number', aggregatable: true, searchable: true, + filterable: true, }, { name: 'source', type: 'string', aggregatable: true, searchable: true, + filterable: true, }, ], }, @@ -164,6 +173,8 @@ describe('IndexPatternDimensionPanel', () => { const options = wrapper.find(EuiComboBox).prop('options'); + expect(options).toHaveLength(2); + expect(options![0].label).toEqual('Document'); expect(options![1].options!.map(({ label }) => label)).toEqual([ @@ -541,7 +552,7 @@ describe('IndexPatternDimensionPanel', () => { .find(EuiSideNav) .prop('items')[0] .items.map(({ name }) => name) - ).toEqual(['Count', 'Maximum', 'Average', 'Sum', 'Minimum']); + ).toEqual(['Maximum', 'Average', 'Sum', 'Minimum', 'Count', 'Filter Ratio']); }); it('should add a column on selection of a field', () => { @@ -642,7 +653,15 @@ describe('IndexPatternDimensionPanel', () => { foo: { id: 'foo', title: 'Foo pattern', - fields: [{ aggregatable: true, name: 'bar', searchable: true, type: 'number' }], + fields: [ + { + aggregatable: true, + name: 'bar', + searchable: true, + filterable: true, + type: 'number', + }, + ], }, }, }; diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/field_select.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/field_select.tsx index fbf3f934b42cb..e88d31d6f1a3a 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/field_select.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/field_select.tsx @@ -48,27 +48,41 @@ export function FieldSelect({ ); function isCompatibleWithCurrentOperation(col: BaseIndexPatternColumn) { - return incompatibleSelectedOperationType - ? col.operationType === incompatibleSelectedOperationType - : !selectedColumn || col.operationType === selectedColumn.operationType; + if (incompatibleSelectedOperationType) { + return col.operationType === incompatibleSelectedOperationType; + } + return !selectedColumn || col.operationType === selectedColumn.operationType; } const fieldOptions = []; - const fieldLessColumn = filteredColumns.find( + const bestFieldLessColumn = filteredColumns.find( column => !hasField(column) && isCompatibleWithCurrentOperation(column) ); - if (fieldLessColumn) { + if (bestFieldLessColumn) { fieldOptions.push({ label: i18n.translate('xpack.lens.indexPattern.documentField', { defaultMessage: 'Document', }), - value: fieldLessColumn.operationId, + value: bestFieldLessColumn.operationId, className: classNames({ 'lnsConfigPanel__fieldOption--incompatible': !isCompatibleWithCurrentOperation( - fieldLessColumn + bestFieldLessColumn ), }), }); + } else { + const anyFieldLessColumn = filteredColumns.find(column => !hasField(column)); + if (anyFieldLessColumn) { + fieldOptions.push({ + label: i18n.translate('xpack.lens.indexPattern.documentField', { + defaultMessage: 'Document', + }), + value: anyFieldLessColumn.operationId, + className: classNames({ + 'lnsConfigPanel__fieldOption--incompatible': true, + }), + }); + } } if (uniqueColumnsByField.length > 0) { diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/filter_ratio.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/filter_ratio.ts index 943e1ace89605..589bb37c843bd 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/filter_ratio.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/filter_ratio.ts @@ -42,7 +42,6 @@ export const calculateFilterRatio: ExpressionFunction< } const entries = Object.entries(data.rows[0]); - const [[ratioKey]] = entries.filter(([key]) => key === id); const [[valueKey]] = entries.filter(([key]) => key.includes('filter-ratio')); for (let i = 0; i < data.rows.length; i += 2) { diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.test.tsx index 1204fd90cc234..e36d8605c004f 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.test.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.test.tsx @@ -16,6 +16,11 @@ import { import { DatasourcePublicAPI, Operation, Datasource } from '../types'; import { createMockedDragDropContext } from './mocks'; +jest.mock('./loader'); +jest.mock('ui/new_platform'); +jest.mock('ui/chrome'); +jest.mock('plugins/data/setup', () => ({ data: { query: { ui: {} } } })); + const expectedIndexPatterns = { 1: { id: '1', @@ -27,18 +32,21 @@ const expectedIndexPatterns = { type: 'date', aggregatable: true, searchable: true, + filterable: true, }, { name: 'bytes', type: 'number', aggregatable: true, searchable: true, + filterable: true, }, { name: 'source', type: 'string', aggregatable: true, searchable: true, + filterable: true, }, ], }, @@ -52,6 +60,7 @@ const expectedIndexPatterns = { type: 'date', aggregatable: true, searchable: true, + filterable: true, aggregationRestrictions: { date_histogram: { agg: 'date_histogram', @@ -66,6 +75,7 @@ const expectedIndexPatterns = { type: 'number', aggregatable: true, searchable: true, + filterable: true, aggregationRestrictions: { // Ignored in the UI histogram: { @@ -91,6 +101,7 @@ const expectedIndexPatterns = { type: 'string', aggregatable: true, searchable: true, + filterable: true, aggregationRestrictions: { terms: { agg: 'terms', @@ -106,8 +117,7 @@ describe('IndexPattern Data Source', () => { let indexPatternDatasource: Datasource<IndexPatternPrivateState, IndexPatternPersistedState>; beforeEach(() => { - // @ts-ignore - indexPatternDatasource = getIndexPatternDatasource(); + indexPatternDatasource = (getIndexPatternDatasource as any)(); persistedState = { currentIndexPatternId: '1', @@ -245,7 +255,7 @@ describe('IndexPattern Data Source', () => { index=\\"1\\" metricsAtAllLevels=false partialRows=false - aggConfigs='[{\\"id\\":\\"col1\\",\\"enabled\\":true,\\"type\\":\\"count\\",\\"schema\\":\\"metric\\",\\"params\\":{}},{\\"id\\":\\"col2\\",\\"enabled\\":true,\\"type\\":\\"date_histogram\\",\\"schema\\":\\"segment\\",\\"params\\":{\\"field\\":\\"timestamp\\",\\"timeRange\\":{\\"from\\":\\"now-1d\\",\\"to\\":\\"now\\"},\\"useNormalizedEsInterval\\":true,\\"interval\\":\\"1d\\",\\"drop_partials\\":false,\\"min_doc_count\\":1,\\"extended_bounds\\":{}}}]' | lens_rename_columns idMap='{\\"col-0-col1\\":\\"col1\\",\\"col-1-col2\\":\\"col2\\"}'" + aggConfigs='[{\\"id\\":\\"col1\\",\\"enabled\\":true,\\"type\\":\\"count\\",\\"schema\\":\\"metric\\",\\"params\\":{}},{\\"id\\":\\"col2\\",\\"enabled\\":true,\\"type\\":\\"date_histogram\\",\\"schema\\":\\"segment\\",\\"params\\":{\\"field\\":\\"timestamp\\",\\"timeRange\\":{\\"from\\":\\"now-1d\\",\\"to\\":\\"now\\"},\\"useNormalizedEsInterval\\":true,\\"interval\\":\\"1d\\",\\"drop_partials\\":false,\\"min_doc_count\\":1,\\"extended_bounds\\":{}}}]' | lens_rename_columns idMap='{\\"col-0-col1\\":\\"col1\\",\\"col-1-col2\\":\\"col2\\"}' | clog" `); }); }); @@ -389,6 +399,7 @@ describe('IndexPattern Data Source', () => { type: 'number', aggregatable: true, searchable: true, + filterable: true, }, ], }, 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 2d8f4af2c9e46..44d4d168bbc58 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.tsx @@ -18,6 +18,7 @@ import { DimensionPriority, DatasourceSuggestion, } from '../types'; +import { Query } from '../../../../../../src/legacy/core_plugins/data/public/query'; import { getIndexPatterns } from './loader'; import { ChildDragDropProvider, DragDrop } from '../drag_drop'; import { toExpression } from './to_expression'; @@ -79,8 +80,8 @@ export interface TermsIndexPatternColumn extends FieldBasedIndexPatternColumn { export interface FilterRatioIndexPatternColumn extends BaseIndexPatternColumn { operationType: 'filter_ratio'; params: { - numerator: { language: string; query: string }; - denominator: { language: string; query: string }; + numerator: Query; + denominator: Query; }; } @@ -100,6 +101,8 @@ export interface IndexPattern { timeFieldName?: string | null; } +// Represents the computed field information for use by Lens +// Filterable should be computed based on logic in index_patterns/_field.js export interface IndexPatternField { name: string; type: string; diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/mocks.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/mocks.ts index 67707b0ffd103..b24d53e0f552f 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/mocks.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/mocks.ts @@ -12,24 +12,3 @@ export function createMockedDragDropContext(): jest.Mocked<DragContextState> { setDragging: jest.fn(), }; } - -jest.mock('ui/new_platform'); - -jest.mock('ui/storage', () => { - return { - Storage: jest.fn().mockImplementation(() => ({ - get: jest.fn(), - getItem: jest.fn(), - })), - }; -}); - -jest.mock('../../../../../../src/legacy/core_plugins/data/public/setup', () => ({ - data: { - query: { - ui: { - QueryBarInput: jest.fn(), - }, - }, - }, -})); 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 59b463a545651..4a2643c4748b1 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 @@ -27,6 +27,7 @@ describe('date_histogram', () => { esTypes: ['date'], aggregatable: true, searchable: true, + filterable: true, }, ], }, @@ -59,6 +60,7 @@ describe('date_histogram', () => { esTypes: ['date'], aggregatable: true, searchable: true, + filterable: true, }); expect(column.params.interval).toEqual('h'); }); @@ -70,6 +72,7 @@ describe('date_histogram', () => { esTypes: ['date'], aggregatable: true, searchable: true, + filterable: true, aggregationRestrictions: { date_histogram: { agg: 'date_histogram', 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/operation_definitions/filter_ratio.tsx index bbcee8ca51b7c..a4e49425a746f 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/filter_ratio.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/filter_ratio.tsx @@ -10,6 +10,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { Storage } from 'ui/storage'; import { EuiButton, EuiFormRow } from '@elastic/eui'; +import { Query } from '../../../../../../../src/legacy/core_plugins/data/public/query'; import { data as dataSetup } from '../../../../../../../src/legacy/core_plugins/data/public/setup'; import { FilterRatioIndexPatternColumn } from '../indexpattern'; import { DimensionPriority } from '../../types'; @@ -77,13 +78,13 @@ export const filterRatioOperation: OperationDefinition<FilterRatioIndexPatternCo > <QueryBarInput appName={'lens'} - indexPatterns={[state.indexPatterns[state.currentIndexPatternId]]} + indexPatterns={[state.currentIndexPatternId]} query={ (state.columns[currentColumnId] as FilterRatioIndexPatternColumn).params.numerator } screenTitle={''} store={localStorage} - onChange={(newQuery: { language: string; query: string }) => { + onChange={(newQuery: Query) => { setState( updateColumnParam( state, @@ -104,13 +105,13 @@ export const filterRatioOperation: OperationDefinition<FilterRatioIndexPatternCo {hasDenominator ? ( <QueryBarInput appName={'lens'} - indexPatterns={[state.indexPatterns[state.currentIndexPatternId]]} + indexPatterns={[state.currentIndexPatternId]} query={ (state.columns[currentColumnId] as FilterRatioIndexPatternColumn).params.denominator } screenTitle={''} store={localStorage} - onChange={(newQuery: { language: string; query: string }) => { + onChange={(newQuery: Query) => { setState( updateColumnParam( state, diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations.test.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations.test.ts index b4ac16f3a361b..5e123df2b0a54 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations.test.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations.test.ts @@ -7,7 +7,11 @@ import { getOperationTypesForField, getPotentialColumns } from './operations'; import { IndexPatternPrivateState } from './indexpattern'; import { hasField } from './state_helpers'; -import './mocks'; + +jest.mock('./loader'); +jest.mock('ui/new_platform'); +jest.mock('ui/chrome'); +jest.mock('plugins/data/setup', () => ({ data: { query: { ui: {} } } })); const expectedIndexPatterns = { 1: { @@ -20,18 +24,21 @@ const expectedIndexPatterns = { type: 'date', aggregatable: true, searchable: true, + filterable: true, }, { name: 'bytes', type: 'number', aggregatable: true, searchable: true, + filterable: true, }, { name: 'source', type: 'string', aggregatable: true, searchable: true, + filterable: true, }, ], }, @@ -46,6 +53,7 @@ describe('getOperationTypesForField', () => { name: 'a', aggregatable: true, searchable: true, + filterable: true, }) ).toEqual(expect.arrayContaining(['terms'])); }); @@ -57,6 +65,7 @@ describe('getOperationTypesForField', () => { name: 'a', aggregatable: true, searchable: true, + filterable: true, }) ).toEqual(expect.arrayContaining(['avg', 'sum', 'min', 'max'])); }); @@ -68,6 +77,7 @@ describe('getOperationTypesForField', () => { name: 'a', aggregatable: true, searchable: true, + filterable: true, }) ).toEqual(expect.arrayContaining(['date_histogram'])); }); @@ -79,6 +89,7 @@ describe('getOperationTypesForField', () => { name: 'a', aggregatable: true, searchable: true, + filterable: true, }) ).toEqual([]); }); @@ -92,6 +103,7 @@ describe('getOperationTypesForField', () => { name: 'a', aggregatable: true, searchable: true, + filterable: true, aggregationRestrictions: { terms: { agg: 'terms', @@ -108,6 +120,7 @@ describe('getOperationTypesForField', () => { name: 'a', aggregatable: true, searchable: true, + filterable: true, aggregationRestrictions: { min: { agg: 'min', @@ -127,6 +140,7 @@ describe('getOperationTypesForField', () => { name: 'a', aggregatable: true, searchable: true, + filterable: true, aggregationRestrictions: { date_histogram: { agg: 'date_histogram', @@ -207,6 +221,10 @@ Array [ "timestamp", "date_histogram", ], + Array [ + "_documents_", + "filter_ratio", + ], ] `); }); diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations.ts index cf98c735684e2..533e2fb8c9b5b 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations.ts @@ -50,8 +50,15 @@ export const operationDefinitionMap: AllOperationDefinitions = { filter_ratio: filterRatioOperation, }; const operationDefinitions: PossibleOperationDefinitions[] = Object.values(operationDefinitionMap); +// const operationDefinitions: PossibleOperationDefinitions[] = (Object.entries( +// operationDefinitionMap +// ) as Array<[string, PossibleOperationDefinitions]>) +// .sort(([a], [b]) => a.localeCompare(b)) +// .sort(([a], [b]) => (a === 'count' ? -1 : 1)) +// .map(([_, val]) => val); export function getOperations(): OperationType[] { + // return ['count', 'filter_ratio', 'date_histogram', 'terms', 'avg', 'max', 'min', 'sum']; return Object.keys(operationDefinitionMap) as OperationType[]; } diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/plugin.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/plugin.tsx index 8899d9bdf00c9..ad2eb6458fecd 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/plugin.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/plugin.tsx @@ -11,7 +11,6 @@ import { toastNotifications } from 'ui/notify'; import { getIndexPatternDatasource } from './indexpattern'; import { ExpressionFunction } from '../../../../../../src/legacy/core_plugins/interpreter/public'; -// @ts-ignore untyped dependency import { functionsRegistry } from '../../../../../../src/legacy/core_plugins/interpreter/public/registries'; import { renameColumns } from './rename_columns'; import { calculateFilterRatio } from './filter_ratio'; diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/plugin.tsx b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/plugin.tsx index bb89646715645..9c9482a05e8f1 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/plugin.tsx +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/plugin.tsx @@ -10,7 +10,6 @@ import { xyVisualization } from './xy_visualization'; import { renderersRegistry, functionsRegistry, - // @ts-ignore untyped dependency } from '../../../../../../src/legacy/core_plugins/interpreter/public/registries'; import { InterpreterSetup, RenderFunction } from '../interpreter_types'; import { xyChart, xyChartRenderer } from './xy_expression'; From 6014686c8dfba61bf5ce048e30caeb57645ab462 Mon Sep 17 00:00:00 2001 From: Wylie Conlon <wylieconlon@gmail.com> Date: Wed, 3 Jul 2019 15:49:07 -0400 Subject: [PATCH 03/10] Pass dependencies through plugin like new platform --- .../public/index_patterns/_index_pattern.d.ts | 1 - .../dimension_panel/dimension_panel.test.tsx | 5 +++ .../dimension_panel/dimension_panel.tsx | 4 +++ .../dimension_panel/popover_editor.tsx | 8 ++++- .../indexpattern_plugin/indexpattern.test.tsx | 15 ++++++++- .../indexpattern_plugin/indexpattern.tsx | 12 +++++-- .../public/indexpattern_plugin/operations.ts | 11 +++---- .../public/indexpattern_plugin/plugin.tsx | 31 ++++++++++++++++--- 8 files changed, 69 insertions(+), 18 deletions(-) diff --git a/src/legacy/ui/public/index_patterns/_index_pattern.d.ts b/src/legacy/ui/public/index_patterns/_index_pattern.d.ts index bfdc3f26ad53e..d3856b79b1d09 100644 --- a/src/legacy/ui/public/index_patterns/_index_pattern.d.ts +++ b/src/legacy/ui/public/index_patterns/_index_pattern.d.ts @@ -45,7 +45,6 @@ export interface StaticIndexPatternField { type: string; aggregatable: boolean; searchable: boolean; - filterable: boolean; } export interface StaticIndexPattern { 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 e73561a840538..9c13cef388ed6 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 @@ -7,6 +7,8 @@ import { ReactWrapper, ShallowWrapper } from 'enzyme'; import React from 'react'; import { EuiComboBox, EuiSideNav, EuiPopover } from '@elastic/eui'; +import { data } from '../../../../../../../src/legacy/core_plugins/data/public/setup'; +import { localStorage } from 'ui/storage/storage_service'; import { IndexPatternPrivateState } from '../indexpattern'; import { changeColumn } from '../state_helpers'; import { getPotentialColumns } from '../operations'; @@ -18,6 +20,7 @@ import { mountWithIntl as mount, shallowWithIntl as shallow } from 'test_utils/e jest.mock('../loader'); jest.mock('ui/new_platform'); jest.mock('ui/chrome'); +jest.mock('ui/storage/storage_service'); jest.mock('plugins/data/setup', () => ({ data: { query: { ui: {} } } })); jest.mock('../state_helpers'); @@ -107,6 +110,8 @@ describe('IndexPatternDimensionPanel', () => { setState, columnId: 'col1', filterOperations: () => true, + dataPlugin: data, + storage: localStorage, }; jest.clearAllMocks(); 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 2671da17bfd3a..55c7cddd7f527 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 @@ -7,7 +7,9 @@ import _ from 'lodash'; import React from 'react'; import { EuiFlexItem, EuiFlexGroup, EuiButtonIcon } from '@elastic/eui'; +import { Storage } from 'ui/storage'; import { i18n } from '@kbn/i18n'; +import { DataSetup } from '../../../../../../../src/legacy/core_plugins/data/public'; import { DatasourceDimensionPanelProps } from '../../types'; import { IndexPatternColumn, @@ -25,6 +27,8 @@ export type IndexPatternDimensionPanelProps = DatasourceDimensionPanelProps & { state: IndexPatternPrivateState; setState: (newState: IndexPatternPrivateState) => void; dragDropContext: DragContextState; + dataPlugin: DataSetup; + storage: Storage; }; export function IndexPatternDimensionPanel(props: IndexPatternDimensionPanelProps) { 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 2254f1b474644..053c8e08349b1 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 @@ -179,7 +179,13 @@ export function PopoverEditor(props: PopoverEditorProps) { </EuiCallOut> )} {!incompatibleSelectedOperationType && ParamEditor && ( - <ParamEditor state={state} setState={setState} columnId={columnId} /> + <ParamEditor + state={state} + setState={setState} + columnId={columnId} + storage={props.storage} + dataPlugin={props.dataPlugin} + /> )} {!incompatibleSelectedOperationType && selectedColumn && ( <EuiFormRow label="Label"> diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.test.tsx index e36d8605c004f..2b62eeb0b0d7d 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.test.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.test.tsx @@ -7,6 +7,11 @@ import { shallow } from 'enzyme'; import React from 'react'; import { EuiComboBox } from '@elastic/eui'; +import chromeMock from 'ui/chrome'; +import { data as dataMock } from '../../../../../../src/legacy/core_plugins/data/public/setup'; +import { localStorage as storageMock } from 'ui/storage/storage_service'; +import { functionsRegistry } from '../../../../../../src/legacy/core_plugins/interpreter/public/registries'; +import { toastNotifications as notificationsMock } from 'ui/notify'; import { getIndexPatternDatasource, IndexPatternPersistedState, @@ -19,6 +24,8 @@ import { createMockedDragDropContext } from './mocks'; jest.mock('./loader'); jest.mock('ui/new_platform'); jest.mock('ui/chrome'); +jest.mock('ui/notify'); +jest.mock('ui/storage/storage_service'); jest.mock('plugins/data/setup', () => ({ data: { query: { ui: {} } } })); const expectedIndexPatterns = { @@ -117,7 +124,13 @@ describe('IndexPattern Data Source', () => { let indexPatternDatasource: Datasource<IndexPatternPrivateState, IndexPatternPersistedState>; beforeEach(() => { - indexPatternDatasource = (getIndexPatternDatasource as any)(); + indexPatternDatasource = getIndexPatternDatasource({ + chrome: chromeMock, + storage: storageMock, + interpreter: { functionsRegistry }, + toastNotifications: notificationsMock, + data: dataMock, + }); persistedState = { currentIndexPatternId: '1', 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 44d4d168bbc58..5430e06a7b5c9 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.tsx @@ -7,8 +7,6 @@ import _ from 'lodash'; import React from 'react'; import { render } from 'react-dom'; -import { Chrome } from 'ui/chrome'; -import { ToastNotifications } from 'ui/notify/toasts/toast_notifications'; import { EuiComboBox } from '@elastic/eui'; import uuid from 'uuid'; import { I18nProvider } from '@kbn/i18n/react'; @@ -24,6 +22,7 @@ import { ChildDragDropProvider, DragDrop } from '../drag_drop'; import { toExpression } from './to_expression'; import { IndexPatternDimensionPanel } from './dimension_panel'; import { buildColumnForOperationType, getOperationTypesForField } from './operations'; +import { IndexPatternDatasourcePluginPlugins } from './plugin'; import { Datasource, DataType } from '..'; export type OperationType = IndexPatternColumn['operationType']; @@ -225,7 +224,12 @@ function addRestrictionsToFields( }; } -export function getIndexPatternDatasource(chrome: Chrome, toastNotifications: ToastNotifications) { +export function getIndexPatternDatasource({ + chrome, + toastNotifications, + data, + storage, +}: IndexPatternDatasourcePluginPlugins) { // Not stateful. State is persisted to the frame const indexPatternDatasource: Datasource<IndexPatternPrivateState, IndexPatternPersistedState> = { async initialize(state?: IndexPatternPersistedState) { @@ -287,6 +291,8 @@ export function getIndexPatternDatasource(chrome: Chrome, toastNotifications: To <IndexPatternDimensionPanel state={state} setState={newState => setState(newState)} + dataPlugin={data} + storage={storage} {...props} /> </I18nProvider>, diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations.ts index 533e2fb8c9b5b..8471e2b0ad8f4 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Storage } from 'ui/storage'; +import { DataSetup } from '../../../../../../src/legacy/core_plugins/data/public'; import { DimensionPriority } from '../types'; import { IndexPatternColumn, @@ -50,15 +52,8 @@ export const operationDefinitionMap: AllOperationDefinitions = { filter_ratio: filterRatioOperation, }; const operationDefinitions: PossibleOperationDefinitions[] = Object.values(operationDefinitionMap); -// const operationDefinitions: PossibleOperationDefinitions[] = (Object.entries( -// operationDefinitionMap -// ) as Array<[string, PossibleOperationDefinitions]>) -// .sort(([a], [b]) => a.localeCompare(b)) -// .sort(([a], [b]) => (a === 'count' ? -1 : 1)) -// .map(([_, val]) => val); export function getOperations(): OperationType[] { - // return ['count', 'filter_ratio', 'date_histogram', 'terms', 'avg', 'max', 'min', 'sum']; return Object.keys(operationDefinitionMap) as OperationType[]; } @@ -66,6 +61,8 @@ export interface ParamEditorProps { state: IndexPatternPrivateState; setState: (newState: IndexPatternPrivateState) => void; columnId: string; + dataPlugin?: DataSetup; + storage?: Storage; } export interface OperationDefinition<C extends BaseIndexPatternColumn> { diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/plugin.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/plugin.tsx index ad2eb6458fecd..ea969219b3e1f 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/plugin.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/plugin.tsx @@ -6,12 +6,16 @@ import { Registry } from '@kbn/interpreter/target/common'; import { CoreSetup } from 'src/core/public'; -import chrome from 'ui/chrome'; +// The following dependencies on ui/* and src/legacy/core_plugins must be mocked when testing +import chrome, { Chrome } from 'ui/chrome'; import { toastNotifications } from 'ui/notify'; -import { getIndexPatternDatasource } from './indexpattern'; - +import { Storage } from 'ui/storage'; +import { localStorage } from 'ui/storage/storage_service'; +import { DataSetup } from '../../../../../../src/legacy/core_plugins/data/public'; +import { data as dataSetup } from '../../../../../../src/legacy/core_plugins/data/public/setup'; import { ExpressionFunction } from '../../../../../../src/legacy/core_plugins/interpreter/public'; import { functionsRegistry } from '../../../../../../src/legacy/core_plugins/interpreter/public/registries'; +import { getIndexPatternDatasource } from './indexpattern'; import { renameColumns } from './rename_columns'; import { calculateFilterRatio } from './filter_ratio'; @@ -20,7 +24,11 @@ import { calculateFilterRatio } from './filter_ratio'; // are available export interface IndexPatternDatasourcePluginPlugins { + chrome: Chrome; interpreter: InterpreterSetup; + data: DataSetup; + storage: Storage; + toastNotifications: typeof toastNotifications; } export interface InterpreterSetup { @@ -33,10 +41,19 @@ export interface InterpreterSetup { class IndexPatternDatasourcePlugin { constructor() {} - setup(_core: CoreSetup | null, { interpreter }: IndexPatternDatasourcePluginPlugins) { + setup( + _core: CoreSetup | null, + { interpreter, data, storage, toastNotifications: toast }: IndexPatternDatasourcePluginPlugins + ) { interpreter.functionsRegistry.register(() => renameColumns); interpreter.functionsRegistry.register(() => calculateFilterRatio); - return getIndexPatternDatasource(chrome, toastNotifications); + return getIndexPatternDatasource({ + chrome, + interpreter, + toastNotifications: toast, + data, + storage, + }); } stop() {} @@ -46,8 +63,12 @@ const plugin = new IndexPatternDatasourcePlugin(); export const indexPatternDatasourceSetup = () => plugin.setup(null, { + chrome, interpreter: { functionsRegistry, }, + data: dataSetup, + storage: localStorage, + toastNotifications, }); export const indexPatternDatasourceStop = () => plugin.stop(); From 01d1ce448d9c8a991ed0cdd483d9470e07f3300f Mon Sep 17 00:00:00 2001 From: Wylie Conlon <wylieconlon@gmail.com> Date: Wed, 3 Jul 2019 17:36:16 -0400 Subject: [PATCH 04/10] Pass props into filter ratio popover editor --- src/plugins/data/common/index.ts | 1 + src/plugins/data/common/query/index.ts | 20 ++++ .../filter_ratio.test.tsx | 101 ++++++++++++++++++ .../operation_definitions/filter_ratio.tsx | 24 ++--- .../public/indexpattern_plugin/operations.ts | 1 + 5 files changed, 135 insertions(+), 12 deletions(-) create mode 100644 src/plugins/data/common/query/index.ts create mode 100644 x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/filter_ratio.test.tsx diff --git a/src/plugins/data/common/index.ts b/src/plugins/data/common/index.ts index af870208f4865..782d7545803a2 100644 --- a/src/plugins/data/common/index.ts +++ b/src/plugins/data/common/index.ts @@ -18,3 +18,4 @@ */ export * from './expressions'; +export * from './query'; diff --git a/src/plugins/data/common/query/index.ts b/src/plugins/data/common/query/index.ts new file mode 100644 index 0000000000000..d8f7b5091eb8f --- /dev/null +++ b/src/plugins/data/common/query/index.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './types'; 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/operation_definitions/filter_ratio.test.tsx new file mode 100644 index 0000000000000..8d5ad1d15e133 --- /dev/null +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/filter_ratio.test.tsx @@ -0,0 +1,101 @@ +/* + * 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 React from 'react'; +import { filterRatioOperation } from './filter_ratio'; +import { shallow } from 'enzyme'; +import { FilterRatioIndexPatternColumn, IndexPatternPrivateState } from '../indexpattern'; +import { data as dataMock } from '../../../../../../../src/legacy/core_plugins/data/public/setup'; +import { localStorage as storageMock } from 'ui/storage/storage_service'; + +jest.mock('../loader'); +jest.mock('ui/new_platform'); +jest.mock('ui/chrome'); +jest.mock('ui/storage/storage_service'); +jest.mock('plugins/data/setup', () => ({ data: { query: { ui: {} } } })); + +describe('filter_ratio', () => { + let state: IndexPatternPrivateState; + const InlineOptions = filterRatioOperation.paramEditor!; + + beforeEach(() => { + state = { + indexPatterns: { + 1: { + id: '1', + title: 'Mock Indexpattern', + fields: [], + }, + }, + currentIndexPatternId: '1', + columnOrder: ['col1'], + columns: { + col1: { + operationId: 'op1', + label: 'Filter Ratio', + dataType: 'number', + isBucketed: false, + + // Private + operationType: 'filter_ratio', + params: { + numerator: { query: '', language: 'kuery' }, + denominator: { query: '*', language: 'kuery' }, + }, + }, + }, + }; + }); + + describe('buildColumn', () => { + it('should create column object with default params', () => { + const column = filterRatioOperation.buildColumn('op', 0); + expect(column.params.numerator).toEqual({ query: '', language: 'kuery' }); + expect(column.params.denominator).toEqual({ query: '*', language: 'kuery' }); + }); + }); + + describe('toEsAggsConfig', () => { + it('should reflect params correctly', () => { + const esAggsConfig = filterRatioOperation.toEsAggsConfig( + state.columns.col1 as FilterRatioIndexPatternColumn, + 'col1' + ); + expect(esAggsConfig).toEqual( + expect.objectContaining({ + params: expect.objectContaining({ + filters: [ + { + input: { query: '', language: 'kuery' }, + label: '', + }, + { + input: { query: '*', language: 'kuery' }, + label: '', + }, + ], + }), + }) + ); + }); + }); + + describe('param editor', () => { + it('should render current value', () => { + expect(() => { + shallow( + <InlineOptions + state={state} + setState={jest.fn()} + columnId="col1" + storage={storageMock} + dataPlugin={dataMock} + /> + ); + }).not.toThrow(); + }); + }); +}); 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/operation_definitions/filter_ratio.tsx index a4e49425a746f..cc21ebb659e7c 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/filter_ratio.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/filter_ratio.tsx @@ -7,20 +7,13 @@ import React, { useState } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; - -import { Storage } from 'ui/storage'; import { EuiButton, EuiFormRow } from '@elastic/eui'; import { Query } from '../../../../../../../src/legacy/core_plugins/data/public/query'; -import { data as dataSetup } from '../../../../../../../src/legacy/core_plugins/data/public/setup'; import { FilterRatioIndexPatternColumn } from '../indexpattern'; import { DimensionPriority } from '../../types'; import { OperationDefinition } from '../operations'; import { updateColumnParam } from '../state_helpers'; -const localStorage = new Storage(window.localStorage); - -const { QueryBarInput } = dataSetup.query.ui; - export const filterRatioOperation: OperationDefinition<FilterRatioIndexPatternColumn> = { type: 'filter_ratio', displayName: i18n.translate('xpack.lens.indexPattern.filterRatio', { @@ -42,8 +35,7 @@ export const filterRatioOperation: OperationDefinition<FilterRatioIndexPatternCo suggestedOrder, isBucketed: false, params: { - // numerator: { language: 'kuery', query: '' }, - numerator: { language: 'kuery', query: 'geo.src : "CN"' }, + numerator: { language: 'kuery', query: '' }, denominator: { language: 'kuery', query: '*' }, }, }; @@ -66,9 +58,17 @@ export const filterRatioOperation: OperationDefinition<FilterRatioIndexPatternCo ], }, }), - paramEditor: ({ state, setState, columnId: currentColumnId }) => { + paramEditor: ({ state, setState, columnId: currentColumnId, dataPlugin, storage }) => { const [hasDenominator, setDenominator] = useState(false); + // const localStorage = new Storage(window.localStorage); + + if (!dataPlugin || !storage) { + return null; + } + + const { QueryBarInput } = dataPlugin.query.ui; + return ( <div> <EuiFormRow @@ -83,7 +83,7 @@ export const filterRatioOperation: OperationDefinition<FilterRatioIndexPatternCo (state.columns[currentColumnId] as FilterRatioIndexPatternColumn).params.numerator } screenTitle={''} - store={localStorage} + store={storage} onChange={(newQuery: Query) => { setState( updateColumnParam( @@ -110,7 +110,7 @@ export const filterRatioOperation: OperationDefinition<FilterRatioIndexPatternCo (state.columns[currentColumnId] as FilterRatioIndexPatternColumn).params.denominator } screenTitle={''} - store={localStorage} + store={storage} onChange={(newQuery: Query) => { setState( updateColumnParam( diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations.ts index 8471e2b0ad8f4..b5bb9f0921801 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations.ts @@ -49,6 +49,7 @@ export const operationDefinitionMap: AllOperationDefinitions = { avg: averageOperation, sum: sumOperation, count: countOperation, + // TODO: Inject into this filter_ratio: filterRatioOperation, }; const operationDefinitions: PossibleOperationDefinitions[] = Object.values(operationDefinitionMap); From e21b8a33f0a503c0fdc1c752f948a678cc65604c Mon Sep 17 00:00:00 2001 From: Wylie Conlon <wylieconlon@gmail.com> Date: Fri, 5 Jul 2019 13:57:05 -0400 Subject: [PATCH 05/10] Provide mocks to filter_ratio popover test --- .../editor_frame/workspace_panel.tsx | 59 +++++++------------ .../filter_ratio.test.tsx | 46 +++++++++++---- .../operation_definitions/filter_ratio.tsx | 2 - 3 files changed, 58 insertions(+), 49 deletions(-) diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.tsx index 1b89f04cb5a12..58817e7c19ad1 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.tsx +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.tsx @@ -8,7 +8,6 @@ import React, { useState, useEffect, useMemo, useContext } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiCodeBlock, EuiSpacer } from '@elastic/eui'; import { toExpression } from '@kbn/interpreter/common'; -import { throttle } from 'lodash'; import { ExpressionRenderer } from '../../../../../../../src/legacy/core_plugins/data/public'; import { Action } from './state_management'; import { Datasource, Visualization, DatasourcePublicAPI } from '../../types'; @@ -16,12 +15,6 @@ import { DragDrop, DragContext } from '../../drag_drop'; import { getSuggestions, toSwitchAction } from './suggestion_helpers'; import { buildExpression } from './expression_helpers'; -function useThrottle(fn: Function, wait = 500) { - const newFn = throttle(fn, wait); - - return newFn(); -} - export interface WorkspacePanelProps { activeDatasource: Datasource; datasourceState: unknown; @@ -85,40 +78,32 @@ export function WorkspacePanel({ const activeVisualization = activeVisualizationId ? visualizationMap[activeVisualizationId] : null; - const expression = useThrottle(() => - useMemo( - () => { - try { - return buildExpression( - activeVisualization, - visualizationState, - activeDatasource, - datasourceState, - datasourcePublicAPI - ); - } catch (e) { - setExpressionError(e.toString()); - } - }, - [ + const expression = useMemo(() => { + try { + return buildExpression( activeVisualization, visualizationState, activeDatasource, datasourceState, - datasourcePublicAPI, - ] - ) - ); - - useEffect( - () => { - // reset expression error if component attempts to run it again - if (expressionError) { - setExpressionError(undefined); - } - }, - [expression] - ); + datasourcePublicAPI + ); + } catch (e) { + setExpressionError(e.toString()); + } + }, [ + activeVisualization, + visualizationState, + activeDatasource, + datasourceState, + datasourcePublicAPI, + ]); + + useEffect(() => { + // reset expression error if component attempts to run it again + if (expressionError) { + setExpressionError(undefined); + } + }, [expression]); if (expression === null) { return renderEmptyWorkspace(); 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/operation_definitions/filter_ratio.test.tsx index 8d5ad1d15e133..7ef403a53d550 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/operation_definitions/filter_ratio.test.tsx @@ -6,19 +6,14 @@ import React from 'react'; import { filterRatioOperation } from './filter_ratio'; -import { shallow } from 'enzyme'; +import { shallowWithIntl } from 'test_utils/enzyme_helpers'; import { FilterRatioIndexPatternColumn, IndexPatternPrivateState } from '../indexpattern'; -import { data as dataMock } from '../../../../../../../src/legacy/core_plugins/data/public/setup'; -import { localStorage as storageMock } from 'ui/storage/storage_service'; - -jest.mock('../loader'); -jest.mock('ui/new_platform'); -jest.mock('ui/chrome'); -jest.mock('ui/storage/storage_service'); -jest.mock('plugins/data/setup', () => ({ data: { query: { ui: {} } } })); +import { act } from 'react-dom/test-utils'; describe('filter_ratio', () => { let state: IndexPatternPrivateState; + let storageMock: any; + let dataMock: any; const InlineOptions = filterRatioOperation.paramEditor!; beforeEach(() => { @@ -48,6 +43,23 @@ describe('filter_ratio', () => { }, }, }; + + class QueryBarInput { + props: any; + constructor(props: any) { + this.props = props; + } + render() { + return <></>; + } + } + + storageMock = { + getItem() {}, + }; + dataMock = { + query: { ui: { QueryBarInput } }, + }; }); describe('buildColumn', () => { @@ -86,7 +98,7 @@ describe('filter_ratio', () => { describe('param editor', () => { it('should render current value', () => { expect(() => { - shallow( + shallowWithIntl( <InlineOptions state={state} setState={jest.fn()} @@ -97,5 +109,19 @@ describe('filter_ratio', () => { ); }).not.toThrow(); }); + + it('should call the query bar properly', () => { + const wrapper = shallowWithIntl( + <InlineOptions + state={state} + setState={jest.fn()} + columnId="col1" + storage={storageMock} + dataPlugin={dataMock} + /> + ); + + expect(wrapper.find('QueryBarInput').prop('indexPatterns')).toEqual(['1']); + }); }); }); 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/operation_definitions/filter_ratio.tsx index cc21ebb659e7c..2f64ad9886596 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/filter_ratio.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/filter_ratio.tsx @@ -61,8 +61,6 @@ export const filterRatioOperation: OperationDefinition<FilterRatioIndexPatternCo paramEditor: ({ state, setState, columnId: currentColumnId, dataPlugin, storage }) => { const [hasDenominator, setDenominator] = useState(false); - // const localStorage = new Storage(window.localStorage); - if (!dataPlugin || !storage) { return null; } From 087aafbc270c53b50e072a69ef7749f57c91571a Mon Sep 17 00:00:00 2001 From: Wylie Conlon <wylieconlon@gmail.com> Date: Fri, 5 Jul 2019 15:12:23 -0400 Subject: [PATCH 06/10] Add another test --- .../dimension_panel/dimension_panel.test.tsx | 107 ++++++++++++------ .../filter_ratio.test.tsx | 81 ++++++++++++- .../operation_definitions/filter_ratio.tsx | 6 +- 3 files changed, 154 insertions(+), 40 deletions(-) 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 9c13cef388ed6..1434430fe938e 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 @@ -6,6 +6,7 @@ import { ReactWrapper, ShallowWrapper } from 'enzyme'; import React from 'react'; +import { act } from 'react-dom/test-utils'; import { EuiComboBox, EuiSideNav, EuiPopover } from '@elastic/eui'; import { data } from '../../../../../../../src/legacy/core_plugins/data/public/setup'; import { localStorage } from 'ui/storage/storage_service'; @@ -289,7 +290,9 @@ describe('IndexPatternDimensionPanel', () => { const comboBox = wrapper.find(EuiComboBox)!; const option = comboBox.prop('options')![1].options!.find(({ label }) => label === 'memory')!; - comboBox.prop('onChange')!([option]); + act(() => { + comboBox.prop('onChange')!([option]); + }); expect(setState).toHaveBeenCalledWith({ ...initialState, @@ -312,7 +315,9 @@ describe('IndexPatternDimensionPanel', () => { const comboBox = wrapper.find(EuiComboBox)!; const option = comboBox.prop('options')![1].options!.find(({ label }) => label === 'source')!; - comboBox.prop('onChange')!([option]); + act(() => { + comboBox.prop('onChange')!([option]); + }); expect(setState).toHaveBeenCalledWith({ ...state, @@ -352,7 +357,9 @@ describe('IndexPatternDimensionPanel', () => { openPopover(); - wrapper.find('button[data-test-subj="lns-indexPatternDimension-min"]').simulate('click'); + act(() => { + wrapper.find('button[data-test-subj="lns-indexPatternDimension-min"]').simulate('click'); + }); expect(setState).toHaveBeenCalledWith({ ...state, @@ -372,9 +379,11 @@ describe('IndexPatternDimensionPanel', () => { openPopover(); - wrapper - .find('button[data-test-subj="lns-indexPatternDimension-date_histogram"]') - .simulate('click'); + act(() => { + wrapper + .find('button[data-test-subj="lns-indexPatternDimension-date_histogram"]') + .simulate('click'); + }); expect(setState).not.toHaveBeenCalled(); }); @@ -384,9 +393,11 @@ describe('IndexPatternDimensionPanel', () => { openPopover(); - wrapper - .find('input[data-test-subj="indexPattern-label-edit"]') - .simulate('change', { target: { value: 'New Label' } }); + act(() => { + wrapper + .find('input[data-test-subj="indexPattern-label-edit"]') + .simulate('change', { target: { value: 'New Label' } }); + }); expect(setState).toHaveBeenCalledWith({ ...state, @@ -406,7 +417,9 @@ describe('IndexPatternDimensionPanel', () => { openPopover(); - wrapper.find('button[data-test-subj="lns-indexPatternDimension-terms"]').simulate('click'); + act(() => { + wrapper.find('button[data-test-subj="lns-indexPatternDimension-terms"]').simulate('click'); + }); expect(setState).not.toHaveBeenCalled(); }); @@ -444,7 +457,9 @@ describe('IndexPatternDimensionPanel', () => { wrapper.find('button[data-test-subj="lns-indexPatternDimension-terms"]').simulate('click'); - wrapper.find(EuiPopover).prop('closePopover')!(); + act(() => { + wrapper.find(EuiPopover).prop('closePopover')!(); + }); openPopover(); @@ -475,12 +490,16 @@ describe('IndexPatternDimensionPanel', () => { openPopover(); - wrapper.find('button[data-test-subj="lns-indexPatternDimension-terms"]').simulate('click'); + act(() => { + wrapper.find('button[data-test-subj="lns-indexPatternDimension-terms"]').simulate('click'); + }); const comboBox = wrapper.find(EuiComboBox)!; const option = comboBox.prop('options')![1].options!.find(({ label }) => label === 'source')!; - comboBox.prop('onChange')!([option]); + act(() => { + comboBox.prop('onChange')!([option]); + }); expect(setState).toHaveBeenCalledWith({ ...state, @@ -504,7 +523,9 @@ describe('IndexPatternDimensionPanel', () => { const comboBox = wrapper.find(EuiComboBox); const options = comboBox.prop('options'); - comboBox.prop('onChange')!([options![1].options![0]]); + act(() => { + comboBox.prop('onChange')!([options![1].options![0]]); + }); expect(setState).toHaveBeenCalledWith({ ...state, @@ -568,7 +589,9 @@ describe('IndexPatternDimensionPanel', () => { const comboBox = wrapper.find(EuiComboBox)!; const option = comboBox.prop('options')![1].options![0]; - comboBox.prop('onChange')!([option]); + act(() => { + comboBox.prop('onChange')!([option]); + }); expect(setState).toHaveBeenCalledWith({ ...state, @@ -604,10 +627,12 @@ describe('IndexPatternDimensionPanel', () => { openPopover(); - wrapper - .find('[data-test-subj="lns-indexPatternDimension-min"]') - .first() - .prop('onClick')!({} as React.MouseEvent<{}, MouseEvent>); + act(() => { + wrapper + .find('[data-test-subj="lns-indexPatternDimension-min"]') + .first() + .prop('onClick')!({} as React.MouseEvent<{}, MouseEvent>); + }); expect(changeColumn).toHaveBeenCalledWith( initialState, @@ -626,7 +651,9 @@ describe('IndexPatternDimensionPanel', () => { 'EuiButtonIcon[data-test-subj="indexPattern-dimensionPopover-remove"]' ); - clearButton.simulate('click'); + act(() => { + clearButton.simulate('click'); + }); expect(setState).toHaveBeenCalledWith({ ...state, @@ -640,7 +667,9 @@ describe('IndexPatternDimensionPanel', () => { openPopover(); - wrapper.find(EuiComboBox).prop('onChange')!([]); + act(() => { + wrapper.find(EuiComboBox).prop('onChange')!([]); + }); expect(setState).toHaveBeenCalledWith({ ...state, @@ -761,12 +790,14 @@ describe('IndexPatternDimensionPanel', () => { /> ); - const onDrop = wrapper - .find('[data-test-subj="indexPattern-dropTarget"]') - .first() - .prop('onDrop') as DropHandler; + act(() => { + const onDrop = wrapper + .find('[data-test-subj="indexPattern-dropTarget"]') + .first() + .prop('onDrop') as DropHandler; - onDrop(dragging); + onDrop(dragging); + }); expect(setState).toBeCalledTimes(1); expect(setState).toHaveBeenCalledWith( @@ -797,12 +828,14 @@ describe('IndexPatternDimensionPanel', () => { /> ); - const onDrop = wrapper - .find('[data-test-subj="indexPattern-dropTarget"]') - .first() - .prop('onDrop') as DropHandler; + act(() => { + const onDrop = wrapper + .find('[data-test-subj="indexPattern-dropTarget"]') + .first() + .prop('onDrop') as DropHandler; - onDrop(dragging); + onDrop(dragging); + }); expect(setState).toBeCalledTimes(1); expect(setState).toHaveBeenCalledWith( @@ -832,12 +865,14 @@ describe('IndexPatternDimensionPanel', () => { /> ); - const onDrop = wrapper - .find('[data-test-subj="indexPattern-dropTarget"]') - .first() - .prop('onDrop') as DropHandler; + act(() => { + const onDrop = wrapper + .find('[data-test-subj="indexPattern-dropTarget"]') + .first() + .prop('onDrop') as DropHandler; - onDrop(dragging); + onDrop(dragging); + }); expect(setState).not.toBeCalled(); }); 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/operation_definitions/filter_ratio.test.tsx index 7ef403a53d550..7102c10d9762e 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/operation_definitions/filter_ratio.test.tsx @@ -5,10 +5,10 @@ */ import React from 'react'; -import { filterRatioOperation } from './filter_ratio'; import { shallowWithIntl } from 'test_utils/enzyme_helpers'; -import { FilterRatioIndexPatternColumn, IndexPatternPrivateState } from '../indexpattern'; import { act } from 'react-dom/test-utils'; +import { filterRatioOperation } from './filter_ratio'; +import { FilterRatioIndexPatternColumn, IndexPatternPrivateState } from '../indexpattern'; describe('filter_ratio', () => { let state: IndexPatternPrivateState; @@ -110,7 +110,7 @@ describe('filter_ratio', () => { }).not.toThrow(); }); - it('should call the query bar properly', () => { + it('should show only the numerator by default', () => { const wrapper = shallowWithIntl( <InlineOptions state={state} @@ -121,7 +121,82 @@ describe('filter_ratio', () => { /> ); + expect(wrapper.find('QueryBarInput')).toHaveLength(1); expect(wrapper.find('QueryBarInput').prop('indexPatterns')).toEqual(['1']); }); + + it('should update the state when typing into the query bar', () => { + const setState = jest.fn(); + const wrapper = shallowWithIntl( + <InlineOptions + state={state} + setState={setState} + columnId="col1" + storage={storageMock} + dataPlugin={dataMock} + /> + ); + + wrapper.find('QueryBarInput').prop('onChange')!({ + query: 'geo.src : "US"', + language: 'kuery', + } as any); + + expect(setState).toHaveBeenCalledWith({ + ...state, + columns: { + col1: { + ...state.columns.col1, + params: { + numerator: { query: 'geo.src : "US"', language: 'kuery' }, + denominator: { query: '*', language: 'kuery' }, + }, + }, + }, + }); + }); + + it('should allow editing the denominator', () => { + const setState = jest.fn(); + const wrapper = shallowWithIntl( + <InlineOptions + state={state} + setState={setState} + columnId="col1" + storage={storageMock} + dataPlugin={dataMock} + /> + ); + + act(() => { + wrapper + .find('[data-test-subj="lns-indexPatternFilterRatio-showDenominatorButton"]') + .first() + .simulate('click'); + }); + + expect(wrapper.find('QueryBarInput')).toHaveLength(2); + + wrapper + .find('QueryBarInput') + .at(1) + .prop('onChange')!({ + query: 'geo.src : "US"', + language: 'kuery', + } as any); + + expect(setState).toHaveBeenCalledWith({ + ...state, + columns: { + col1: { + ...state.columns.col1, + params: { + numerator: { query: '', language: 'kuery' }, + denominator: { query: 'geo.src : "US"', language: 'kuery' }, + }, + }, + }, + }); + }); }); }); 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/operation_definitions/filter_ratio.tsx index 2f64ad9886596..dc17fa472ccbe 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/filter_ratio.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/filter_ratio.tsx @@ -128,7 +128,11 @@ export const filterRatioOperation: OperationDefinition<FilterRatioIndexPatternCo /> <EuiFormRow> - <EuiButton fill onClick={() => setDenominator(true)}> + <EuiButton + data-test-subj="lns-indexPatternFilterRatio-showDenominatorButton" + fill + onClick={() => setDenominator(true)} + > <FormattedMessage id="xpack.lens.indexPattern.filterRatioUseDenominatorButton" defaultMessage="Divide by filter instead" From 2a9ba855fb340b701f5d23c7f6bdbe563871f4ed Mon Sep 17 00:00:00 2001 From: Wylie Conlon <wylieconlon@gmail.com> Date: Mon, 8 Jul 2019 13:43:19 -0400 Subject: [PATCH 07/10] Clean up to prepare for review --- .../public/persisted_log/recently_accessed.ts | 2 +- .../indexpattern_plugin/__mocks__/loader.ts | 6 ------ .../__snapshots__/indexpattern.test.tsx.snap | 3 --- .../dimension_panel/dimension_panel.test.tsx | 15 ++++++------- .../indexpattern_plugin/indexpattern.test.tsx | 13 ++++-------- .../indexpattern_plugin/indexpattern.tsx | 3 --- .../date_histogram.test.tsx | 3 --- .../indexpattern_plugin/operations.test.ts | 13 ------------ .../indexpattern_plugin/to_expression.ts | 21 ++++++------------- 9 files changed, 17 insertions(+), 62 deletions(-) diff --git a/src/legacy/ui/public/persisted_log/recently_accessed.ts b/src/legacy/ui/public/persisted_log/recently_accessed.ts index 0387686753677..6d984d155d551 100644 --- a/src/legacy/ui/public/persisted_log/recently_accessed.ts +++ b/src/legacy/ui/public/persisted_log/recently_accessed.ts @@ -17,6 +17,6 @@ * under the License. */ -import { npStart } from 'ui/new_platform'; +import { npStart } from '../new_platform'; export const recentlyAccessed = npStart.core.chrome.recentlyAccessed; diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/__mocks__/loader.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/__mocks__/loader.ts index 3484092108e2a..7823768896d64 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/__mocks__/loader.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/__mocks__/loader.ts @@ -17,21 +17,18 @@ export function getIndexPatterns() { type: 'date', aggregatable: true, searchable: true, - filterable: true, }, { name: 'bytes', type: 'number', aggregatable: true, searchable: true, - filterable: true, }, { name: 'source', type: 'string', aggregatable: true, searchable: true, - filterable: true, }, ], }, @@ -45,21 +42,18 @@ export function getIndexPatterns() { type: 'date', aggregatable: true, searchable: true, - filterable: true, }, { name: 'bytes', type: 'number', aggregatable: true, searchable: true, - filterable: true, }, { name: 'source', type: 'string', aggregatable: true, searchable: true, - filterable: true, }, ], typeMeta: { diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/__snapshots__/indexpattern.test.tsx.snap b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/__snapshots__/indexpattern.test.tsx.snap index da4175283122b..5b260ec1b7458 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/__snapshots__/indexpattern.test.tsx.snap +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/__snapshots__/indexpattern.test.tsx.snap @@ -45,7 +45,6 @@ exports[`IndexPattern Data Source #renderDataPanel should match snapshot 1`] = ` value={ Object { "aggregatable": true, - "filterable": true, "name": "timestamp", "searchable": true, "type": "date", @@ -60,7 +59,6 @@ exports[`IndexPattern Data Source #renderDataPanel should match snapshot 1`] = ` value={ Object { "aggregatable": true, - "filterable": true, "name": "bytes", "searchable": true, "type": "number", @@ -75,7 +73,6 @@ exports[`IndexPattern Data Source #renderDataPanel should match snapshot 1`] = ` value={ Object { "aggregatable": true, - "filterable": true, "name": "source", "searchable": true, "type": "string", 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 1434430fe938e..0f0ba36fbf8ee 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 @@ -19,14 +19,16 @@ import { createMockedDragDropContext } from '../mocks'; import { mountWithIntl as mount, shallowWithIntl as shallow } from 'test_utils/enzyme_helpers'; jest.mock('../loader'); -jest.mock('ui/new_platform'); +jest.mock('../state_helpers'); +jest.mock('../operations'); + +// Used by indexpattern plugin, which is a dependency of a dependency jest.mock('ui/chrome'); jest.mock('ui/storage/storage_service'); +// Contains old and new platform data plugins, used for interpreter and filter ratio +jest.mock('ui/new_platform'); jest.mock('plugins/data/setup', () => ({ data: { query: { ui: {} } } })); -jest.mock('../state_helpers'); -jest.mock('../operations'); - const expectedIndexPatterns = { 1: { id: '1', @@ -38,28 +40,24 @@ const expectedIndexPatterns = { type: 'date', aggregatable: true, searchable: true, - filterable: true, }, { name: 'bytes', type: 'number', aggregatable: true, searchable: true, - filterable: true, }, { name: 'memory', type: 'number', aggregatable: true, searchable: true, - filterable: true, }, { name: 'source', type: 'string', aggregatable: true, searchable: true, - filterable: true, }, ], }, @@ -692,7 +690,6 @@ describe('IndexPatternDimensionPanel', () => { aggregatable: true, name: 'bar', searchable: true, - filterable: true, type: 'number', }, ], diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.test.tsx index 2b62eeb0b0d7d..e895892ee5570 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.test.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.test.tsx @@ -22,10 +22,12 @@ import { DatasourcePublicAPI, Operation, Datasource } from '../types'; import { createMockedDragDropContext } from './mocks'; jest.mock('./loader'); -jest.mock('ui/new_platform'); +// chrome, notify, storage are used by ./plugin jest.mock('ui/chrome'); jest.mock('ui/notify'); jest.mock('ui/storage/storage_service'); +// Contains old and new platform data plugins, used for interpreter and filter ratio +jest.mock('ui/new_platform'); jest.mock('plugins/data/setup', () => ({ data: { query: { ui: {} } } })); const expectedIndexPatterns = { @@ -39,21 +41,18 @@ const expectedIndexPatterns = { type: 'date', aggregatable: true, searchable: true, - filterable: true, }, { name: 'bytes', type: 'number', aggregatable: true, searchable: true, - filterable: true, }, { name: 'source', type: 'string', aggregatable: true, searchable: true, - filterable: true, }, ], }, @@ -67,7 +66,6 @@ const expectedIndexPatterns = { type: 'date', aggregatable: true, searchable: true, - filterable: true, aggregationRestrictions: { date_histogram: { agg: 'date_histogram', @@ -82,7 +80,6 @@ const expectedIndexPatterns = { type: 'number', aggregatable: true, searchable: true, - filterable: true, aggregationRestrictions: { // Ignored in the UI histogram: { @@ -108,7 +105,6 @@ const expectedIndexPatterns = { type: 'string', aggregatable: true, searchable: true, - filterable: true, aggregationRestrictions: { terms: { agg: 'terms', @@ -268,7 +264,7 @@ describe('IndexPattern Data Source', () => { index=\\"1\\" metricsAtAllLevels=false partialRows=false - aggConfigs='[{\\"id\\":\\"col1\\",\\"enabled\\":true,\\"type\\":\\"count\\",\\"schema\\":\\"metric\\",\\"params\\":{}},{\\"id\\":\\"col2\\",\\"enabled\\":true,\\"type\\":\\"date_histogram\\",\\"schema\\":\\"segment\\",\\"params\\":{\\"field\\":\\"timestamp\\",\\"timeRange\\":{\\"from\\":\\"now-1d\\",\\"to\\":\\"now\\"},\\"useNormalizedEsInterval\\":true,\\"interval\\":\\"1d\\",\\"drop_partials\\":false,\\"min_doc_count\\":1,\\"extended_bounds\\":{}}}]' | lens_rename_columns idMap='{\\"col-0-col1\\":\\"col1\\",\\"col-1-col2\\":\\"col2\\"}' | clog" + aggConfigs='[{\\"id\\":\\"col1\\",\\"enabled\\":true,\\"type\\":\\"count\\",\\"schema\\":\\"metric\\",\\"params\\":{}},{\\"id\\":\\"col2\\",\\"enabled\\":true,\\"type\\":\\"date_histogram\\",\\"schema\\":\\"segment\\",\\"params\\":{\\"field\\":\\"timestamp\\",\\"timeRange\\":{\\"from\\":\\"now-1d\\",\\"to\\":\\"now\\"},\\"useNormalizedEsInterval\\":true,\\"interval\\":\\"1d\\",\\"drop_partials\\":false,\\"min_doc_count\\":1,\\"extended_bounds\\":{}}}]' | lens_rename_columns idMap='{\\"col-0-col1\\":\\"col1\\",\\"col-1-col2\\":\\"col2\\"}'" `); }); }); @@ -412,7 +408,6 @@ describe('IndexPattern Data Source', () => { type: 'number', aggregatable: true, searchable: true, - filterable: true, }, ], }, 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 5430e06a7b5c9..a9e96a12faef6 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.tsx @@ -100,15 +100,12 @@ export interface IndexPattern { timeFieldName?: string | null; } -// Represents the computed field information for use by Lens -// Filterable should be computed based on logic in index_patterns/_field.js export interface IndexPatternField { name: string; type: string; esTypes?: string[]; aggregatable: boolean; searchable: boolean; - filterable: boolean; aggregationRestrictions?: Partial< Record< string, 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 4a2643c4748b1..59b463a545651 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 @@ -27,7 +27,6 @@ describe('date_histogram', () => { esTypes: ['date'], aggregatable: true, searchable: true, - filterable: true, }, ], }, @@ -60,7 +59,6 @@ describe('date_histogram', () => { esTypes: ['date'], aggregatable: true, searchable: true, - filterable: true, }); expect(column.params.interval).toEqual('h'); }); @@ -72,7 +70,6 @@ describe('date_histogram', () => { esTypes: ['date'], aggregatable: true, searchable: true, - filterable: true, aggregationRestrictions: { date_histogram: { agg: 'date_histogram', diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations.test.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations.test.ts index 5e123df2b0a54..f016515db32ea 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations.test.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations.test.ts @@ -9,9 +9,6 @@ import { IndexPatternPrivateState } from './indexpattern'; import { hasField } from './state_helpers'; jest.mock('./loader'); -jest.mock('ui/new_platform'); -jest.mock('ui/chrome'); -jest.mock('plugins/data/setup', () => ({ data: { query: { ui: {} } } })); const expectedIndexPatterns = { 1: { @@ -24,21 +21,18 @@ const expectedIndexPatterns = { type: 'date', aggregatable: true, searchable: true, - filterable: true, }, { name: 'bytes', type: 'number', aggregatable: true, searchable: true, - filterable: true, }, { name: 'source', type: 'string', aggregatable: true, searchable: true, - filterable: true, }, ], }, @@ -53,7 +47,6 @@ describe('getOperationTypesForField', () => { name: 'a', aggregatable: true, searchable: true, - filterable: true, }) ).toEqual(expect.arrayContaining(['terms'])); }); @@ -65,7 +58,6 @@ describe('getOperationTypesForField', () => { name: 'a', aggregatable: true, searchable: true, - filterable: true, }) ).toEqual(expect.arrayContaining(['avg', 'sum', 'min', 'max'])); }); @@ -77,7 +69,6 @@ describe('getOperationTypesForField', () => { name: 'a', aggregatable: true, searchable: true, - filterable: true, }) ).toEqual(expect.arrayContaining(['date_histogram'])); }); @@ -89,7 +80,6 @@ describe('getOperationTypesForField', () => { name: 'a', aggregatable: true, searchable: true, - filterable: true, }) ).toEqual([]); }); @@ -103,7 +93,6 @@ describe('getOperationTypesForField', () => { name: 'a', aggregatable: true, searchable: true, - filterable: true, aggregationRestrictions: { terms: { agg: 'terms', @@ -120,7 +109,6 @@ describe('getOperationTypesForField', () => { name: 'a', aggregatable: true, searchable: true, - filterable: true, aggregationRestrictions: { min: { agg: 'min', @@ -140,7 +128,6 @@ describe('getOperationTypesForField', () => { name: 'a', aggregatable: true, searchable: true, - filterable: true, aggregationRestrictions: { date_histogram: { agg: 'date_histogram', 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 e2daa6db0b7a6..67887ef186f09 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 @@ -36,9 +36,6 @@ export function toExpression(state: IndexPatternPrivateState) { return getEsAggsConfig(col, colId); }); - // TODO: The filters will generate extra columns which are actually going to affect the aggregation order. They need - // to be added right before all the metrics, and then removed - const idMap = columnEntries.reduce( (currentIdMap, [colId], index) => { return { @@ -49,14 +46,6 @@ export function toExpression(state: IndexPatternPrivateState) { {} as Record<string, string> ); - const expression = `esaggs - index="${state.currentIndexPatternId}" - metricsAtAllLevels=false - partialRows=false - aggConfigs='${JSON.stringify(aggs)}' | lens_rename_columns idMap='${JSON.stringify( - idMap - )}' | clog`; - const filterRatios = columnEntries.filter( ([colId, col]) => col.operationType === 'filter_ratio' ); @@ -71,12 +60,14 @@ export function toExpression(state: IndexPatternPrivateState) { partialRows=false aggConfigs='${JSON.stringify(aggs)}' | lens_rename_columns idMap='${JSON.stringify( idMap - )}' | ${filterRatios - .map(([id]) => `lens_calculate_filter_ratio id=${id}`) - .join(' | ')} | clog`; + )}' | ${filterRatios.map(([id]) => `lens_calculate_filter_ratio id=${id}`).join(' | ')}`; } - return expression; + return `esaggs + index="${state.currentIndexPatternId}" + metricsAtAllLevels=false + partialRows=false + aggConfigs='${JSON.stringify(aggs)}' | lens_rename_columns idMap='${JSON.stringify(idMap)}'`; } return null; From 8358d6dc66816ffb9f63628593fb28697a4eacbd Mon Sep 17 00:00:00 2001 From: Wylie Conlon <wylieconlon@gmail.com> Date: Mon, 8 Jul 2019 14:00:50 -0400 Subject: [PATCH 08/10] Clean up unnecessary changes --- .../lens/public/editor_frame_plugin/editor_frame/index.scss | 1 + .../legacy/plugins/lens/public/indexpattern_plugin/operations.ts | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) 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 f2a19e40ce4a2..b57fe73adb8b2 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 @@ -26,6 +26,7 @@ margin: 0; flex: 1 0 18%; min-width: ($euiSize * 16); + height: 100%; display: flex; flex-direction: column; } diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations.ts index b5bb9f0921801..8471e2b0ad8f4 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations.ts @@ -49,7 +49,6 @@ export const operationDefinitionMap: AllOperationDefinitions = { avg: averageOperation, sum: sumOperation, count: countOperation, - // TODO: Inject into this filter_ratio: filterRatioOperation, }; const operationDefinitions: PossibleOperationDefinitions[] = Object.values(operationDefinitionMap); From 86e61051c669206023f8dbccbc8ad7ed2e846ece Mon Sep 17 00:00:00 2001 From: Wylie Conlon <wylieconlon@gmail.com> Date: Tue, 9 Jul 2019 17:54:23 -0400 Subject: [PATCH 09/10] Respond to review comments --- .../dimension_panel/field_select.tsx | 26 +++++-------------- .../indexpattern_plugin/filter_ratio.ts | 9 +++++-- .../operation_definitions/filter_ratio.tsx | 8 ++---- 3 files changed, 16 insertions(+), 27 deletions(-) diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/field_select.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/field_select.tsx index e88d31d6f1a3a..215419cd3e999 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/field_select.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/field_select.tsx @@ -55,34 +55,22 @@ export function FieldSelect({ } const fieldOptions = []; - const bestFieldLessColumn = filteredColumns.find( - column => !hasField(column) && isCompatibleWithCurrentOperation(column) - ); - if (bestFieldLessColumn) { + const fieldlessColumn = + filteredColumns.find(column => !hasField(column) && isCompatibleWithCurrentOperation(column)) || + filteredColumns.find(column => !hasField(column)); + + if (fieldlessColumn) { fieldOptions.push({ label: i18n.translate('xpack.lens.indexPattern.documentField', { defaultMessage: 'Document', }), - value: bestFieldLessColumn.operationId, + value: fieldlessColumn.operationId, className: classNames({ 'lnsConfigPanel__fieldOption--incompatible': !isCompatibleWithCurrentOperation( - bestFieldLessColumn + fieldlessColumn ), }), }); - } else { - const anyFieldLessColumn = filteredColumns.find(column => !hasField(column)); - if (anyFieldLessColumn) { - fieldOptions.push({ - label: i18n.translate('xpack.lens.indexPattern.documentField', { - defaultMessage: 'Document', - }), - value: anyFieldLessColumn.operationId, - className: classNames({ - 'lnsConfigPanel__fieldOption--incompatible': true, - }), - }); - } } if (uniqueColumnsByField.length > 0) { diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/filter_ratio.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/filter_ratio.ts index 589bb37c843bd..1fe57f42fc987 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/filter_ratio.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/filter_ratio.ts @@ -41,8 +41,13 @@ export const calculateFilterRatio: ExpressionFunction< return data; } - const entries = Object.entries(data.rows[0]); - const [[valueKey]] = entries.filter(([key]) => key.includes('filter-ratio')); + if (data.rows.length % 2 === 1) { + throw new Error('Cannot divide an odd number of rows'); + } + + const [[valueKey]] = Object.entries(data.rows[0]).filter(([key]) => + key.includes('filter-ratio') + ); for (let i = 0; i < data.rows.length; i += 2) { const row1 = data.rows[i]; 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/operation_definitions/filter_ratio.tsx index dc17fa472ccbe..56a5cd77c5afd 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/filter_ratio.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operation_definitions/filter_ratio.tsx @@ -36,7 +36,7 @@ export const filterRatioOperation: OperationDefinition<FilterRatioIndexPatternCo isBucketed: false, params: { numerator: { language: 'kuery', query: '' }, - denominator: { language: 'kuery', query: '*' }, + denominator: { language: 'kuery', query: '' }, }, }; }, @@ -61,11 +61,7 @@ export const filterRatioOperation: OperationDefinition<FilterRatioIndexPatternCo paramEditor: ({ state, setState, columnId: currentColumnId, dataPlugin, storage }) => { const [hasDenominator, setDenominator] = useState(false); - if (!dataPlugin || !storage) { - return null; - } - - const { QueryBarInput } = dataPlugin.query.ui; + const { QueryBarInput } = dataPlugin!.query.ui; return ( <div> From 45071f335c4241483c47d88c9081c8b045370f06 Mon Sep 17 00:00:00 2001 From: Wylie Conlon <wylieconlon@gmail.com> Date: Wed, 10 Jul 2019 13:45:38 -0400 Subject: [PATCH 10/10] Fix tests --- .../operation_definitions/filter_ratio.test.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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/operation_definitions/filter_ratio.test.tsx index 7102c10d9762e..c48a381426664 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/operation_definitions/filter_ratio.test.tsx @@ -38,7 +38,7 @@ describe('filter_ratio', () => { operationType: 'filter_ratio', params: { numerator: { query: '', language: 'kuery' }, - denominator: { query: '*', language: 'kuery' }, + denominator: { query: '', language: 'kuery' }, }, }, }, @@ -66,7 +66,7 @@ describe('filter_ratio', () => { it('should create column object with default params', () => { const column = filterRatioOperation.buildColumn('op', 0); expect(column.params.numerator).toEqual({ query: '', language: 'kuery' }); - expect(column.params.denominator).toEqual({ query: '*', language: 'kuery' }); + expect(column.params.denominator).toEqual({ query: '', language: 'kuery' }); }); }); @@ -85,7 +85,7 @@ describe('filter_ratio', () => { label: '', }, { - input: { query: '*', language: 'kuery' }, + input: { query: '', language: 'kuery' }, label: '', }, ], @@ -149,7 +149,7 @@ describe('filter_ratio', () => { ...state.columns.col1, params: { numerator: { query: 'geo.src : "US"', language: 'kuery' }, - denominator: { query: '*', language: 'kuery' }, + denominator: { query: '', language: 'kuery' }, }, }, },