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' },
             },
           },
         },