diff --git a/src/plugins/vis_type_timeseries/common/vis_schema.ts b/src/plugins/vis_type_timeseries/common/vis_schema.ts index c1730e6a1543..d3d863df8617 100644 --- a/src/plugins/vis_type_timeseries/common/vis_schema.ts +++ b/src/plugins/vis_type_timeseries/common/vis_schema.ts @@ -80,8 +80,8 @@ export const metricsItems = schema.object({ field: stringOptionalNullable, id: stringRequired, metric_agg: stringOptionalNullable, - numerator: stringOptionalNullable, - denominator: stringOptionalNullable, + numerator: schema.maybe(queryObject), + denominator: schema.maybe(queryObject), sigma: stringOptionalNullable, unit: stringOptionalNullable, model_type: stringOptionalNullable, @@ -128,12 +128,7 @@ export const metricsItems = schema.object({ const splitFiltersItems = schema.object({ id: stringOptionalNullable, color: stringOptionalNullable, - filter: schema.maybe( - schema.object({ - language: schema.string(), - query: schema.string(), - }) - ), + filter: schema.maybe(queryObject), label: stringOptionalNullable, }); diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/filter_ratio.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/filter_ratio.js index 2aa994c09a2a..1c7ab65ecd29 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/filter_ratio.js +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/filter_ratio.js @@ -18,25 +18,25 @@ */ import PropTypes from 'prop-types'; -import React from 'react'; +import React, { useCallback, useMemo } from 'react'; import { AggSelect } from './agg_select'; import { FieldSelect } from './field_select'; import { AggRow } from './agg_row'; import { createChangeHandler } from '../lib/create_change_handler'; import { createSelectHandler } from '../lib/create_select_handler'; -import { createTextHandler } from '../lib/create_text_handler'; import { htmlIdGenerator, EuiFlexGroup, EuiFlexItem, EuiFormLabel, - EuiFieldText, EuiSpacer, EuiFormRow, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { KBN_FIELD_TYPES } from '../../../../../../plugins/data/public'; import { getSupportedFieldsByMetricType } from '../lib/get_supported_fields_by_metric_type'; +import { getDataStart } from '../../../services'; +import { QueryBarWrapper } from '../query_bar_wrapper'; const isFieldHistogram = (fields, indexPattern, field) => { const indexFields = fields[indexPattern]; @@ -49,15 +49,24 @@ const isFieldHistogram = (fields, indexPattern, field) => { export const FilterRatioAgg = (props) => { const { series, fields, panel } = props; - const handleChange = createChangeHandler(props.onChange, props.model); + const handleChange = useMemo(() => createChangeHandler(props.onChange, props.model), [ + props.model, + props.onChange, + ]); const handleSelectChange = createSelectHandler(handleChange); - const handleTextChange = createTextHandler(handleChange); + const handleNumeratorQueryChange = useCallback((query) => handleChange({ numerator: query }), [ + handleChange, + ]); + const handleDenominatorQueryChange = useCallback( + (query) => handleChange({ denominator: query }), + [handleChange] + ); const indexPattern = (series.override_index_pattern && series.series_index_pattern) || panel.index_pattern; const defaults = { - numerator: '*', - denominator: '*', + numerator: getDataStart().query.queryString.getDefaultQuery(), + denominator: getDataStart().query.queryString.getDefaultQuery(), metric_agg: 'count', }; @@ -101,7 +110,11 @@ export const FilterRatioAgg = (props) => { /> } > - + @@ -115,7 +128,11 @@ export const FilterRatioAgg = (props) => { /> } > - + diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/filter_ratio.test.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/filter_ratio.test.js index 275aa5a25247..f25cd310f635 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/filter_ratio.test.js +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/filter_ratio.test.js @@ -22,8 +22,16 @@ import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { FilterRatioAgg } from './filter_ratio'; import { FIELDS, METRIC, SERIES, PANEL } from '../../../test_utils'; import { EuiComboBox } from '@elastic/eui'; +import { dataPluginMock } from '../../../../../data/public/mocks'; +import { setDataStart } from '../../../services'; + +jest.mock('../query_bar_wrapper', () => ({ + QueryBarWrapper: jest.fn(() => null), +})); describe('TSVB Filter Ratio', () => { + beforeAll(() => setDataStart(dataPluginMock.createStartContract())); + const setup = (metric) => { const series = { ...SERIES, metrics: [metric] }; const panel = { ...PANEL, series }; diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/histogram_support.test.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/histogram_support.test.js index 7af33ba11f24..aa4afda028a2 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/histogram_support.test.js +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/histogram_support.test.js @@ -22,6 +22,13 @@ import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { Agg } from './agg'; import { FieldSelect } from './field_select'; import { FIELDS, METRIC, SERIES, PANEL } from '../../../test_utils'; +import { setDataStart } from '../../../services'; +import { dataPluginMock } from '../../../../../data/public/mocks'; + +jest.mock('../query_bar_wrapper', () => ({ + QueryBarWrapper: jest.fn(() => null), +})); + const runTest = (aggType, name, test, additionalProps = {}) => { describe(aggType, () => { const metric = { @@ -55,6 +62,8 @@ const runTest = (aggType, name, test, additionalProps = {}) => { }; describe('Histogram Types', () => { + beforeAll(() => setDataStart(dataPluginMock.createStartContract())); + describe('supported', () => { const shouldHaveHistogramSupport = (aggType, additionalProps = {}) => { runTest( diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.js index 0706ab6be96e..6c1699912f76 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.js @@ -20,17 +20,22 @@ const filter = (metric) => metric.type === 'filter_ratio'; import { bucketTransform } from '../../helpers/bucket_transform'; import { overwrite } from '../../helpers'; +import { esQuery } from '../../../../../../data/server'; -export function ratios(req, panel, series) { +export function ratios(req, panel, series, esQueryConfig, indexPatternObject) { return (next) => (doc) => { if (series.metrics.some(filter)) { series.metrics.filter(filter).forEach((metric) => { - overwrite(doc, `aggs.${series.id}.aggs.timeseries.aggs.${metric.id}-numerator.filter`, { - query_string: { query: metric.numerator || '*', analyze_wildcard: true }, - }); - overwrite(doc, `aggs.${series.id}.aggs.timeseries.aggs.${metric.id}-denominator.filter`, { - query_string: { query: metric.denominator || '*', analyze_wildcard: true }, - }); + overwrite( + doc, + `aggs.${series.id}.aggs.timeseries.aggs.${metric.id}-numerator.filter`, + esQuery.buildEsQuery(indexPatternObject, metric.numerator, [], esQueryConfig) + ); + overwrite( + doc, + `aggs.${series.id}.aggs.timeseries.aggs.${metric.id}-denominator.filter`, + esQuery.buildEsQuery(indexPatternObject, metric.denominator, [], esQueryConfig) + ); let numeratorPath = `${metric.id}-numerator>_count`; let denominatorPath = `${metric.id}-denominator>_count`; diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.test.js index e15f04598983..d0216ebab777 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.test.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.test.js @@ -19,10 +19,12 @@ import { ratios } from './filter_ratios'; -describe('ratios(req, panel, series)', () => { +describe('ratios(req, panel, series, esQueryConfig, indexPatternObject)', () => { let panel; let series; let req; + let esQueryConfig; + let indexPatternObject; beforeEach(() => { panel = { time_field: 'timestamp', @@ -36,8 +38,8 @@ describe('ratios(req, panel, series)', () => { { id: 'metric-1', type: 'filter_ratio', - numerator: 'errors', - denominator: '*', + numerator: { query: 'errors', language: 'lucene' }, + denominator: { query: 'warnings', language: 'lucene' }, metric_agg: 'avg', field: 'cpu', }, @@ -51,17 +53,23 @@ describe('ratios(req, panel, series)', () => { }, }, }; + esQueryConfig = { + allowLeadingWildcards: true, + queryStringOptions: { analyze_wildcard: true }, + ignoreFilterIfFieldNotInIndex: false, + }; + indexPatternObject = {}; }); test('calls next when finished', () => { const next = jest.fn(); - ratios(req, panel, series)(next)({}); + ratios(req, panel, series, esQueryConfig, indexPatternObject)(next)({}); expect(next.mock.calls.length).toEqual(1); }); test('returns filter ratio aggs', () => { const next = (doc) => doc; - const doc = ratios(req, panel, series)(next)({}); + const doc = ratios(req, panel, series, esQueryConfig, indexPatternObject)(next)({}); expect(doc).toEqual({ aggs: { test: { @@ -88,9 +96,18 @@ describe('ratios(req, panel, series)', () => { }, }, filter: { - query_string: { - analyze_wildcard: true, - query: '*', + bool: { + must: [ + { + query_string: { + query: 'warnings', + analyze_wildcard: true, + }, + }, + ], + filter: [], + should: [], + must_not: [], }, }, }, @@ -103,9 +120,18 @@ describe('ratios(req, panel, series)', () => { }, }, filter: { - query_string: { - analyze_wildcard: true, - query: 'errors', + bool: { + must: [ + { + query_string: { + query: 'errors', + analyze_wildcard: true, + }, + }, + ], + filter: [], + should: [], + must_not: [], }, }, }, @@ -120,7 +146,7 @@ describe('ratios(req, panel, series)', () => { test('returns empty object when field is not set', () => { delete series.metrics[0].field; const next = (doc) => doc; - const doc = ratios(req, panel, series)(next)({}); + const doc = ratios(req, panel, series, esQueryConfig, indexPatternObject)(next)({}); expect(doc).toEqual({ aggs: { test: { @@ -141,18 +167,36 @@ describe('ratios(req, panel, series)', () => { 'metric-1-denominator': { aggs: { metric: {} }, filter: { - query_string: { - analyze_wildcard: true, - query: '*', + bool: { + must: [ + { + query_string: { + query: 'warnings', + analyze_wildcard: true, + }, + }, + ], + filter: [], + should: [], + must_not: [], }, }, }, 'metric-1-numerator': { aggs: { metric: {} }, filter: { - query_string: { - analyze_wildcard: true, - query: 'errors', + bool: { + must: [ + { + query_string: { + analyze_wildcard: true, + query: 'errors', + }, + }, + ], + filter: [], + should: [], + must_not: [], }, }, }, diff --git a/src/plugins/visualizations/server/saved_objects/visualization_migrations.test.ts b/src/plugins/visualizations/server/saved_objects/visualization_migrations.test.ts index d27d021465dc..05f00e12c172 100644 --- a/src/plugins/visualizations/server/saved_objects/visualization_migrations.test.ts +++ b/src/plugins/visualizations/server/saved_objects/visualization_migrations.test.ts @@ -1544,4 +1544,38 @@ describe('migration visualization', () => { expect(series[0].split_color_mode).toBeUndefined(); }); }); + + describe('7.10.0 tsvb filter_ratio migration', () => { + const migrate = (doc: any) => + visualizationSavedObjectTypeMigrations['7.10.0']( + doc as Parameters[0], + savedObjectMigrationContext + ); + + const testDoc1 = { + attributes: { + title: 'My Vis', + description: 'This is my super cool vis.', + visState: `{"type":"metrics","params":{"id":"61ca57f0-469d-11e7-af02-69e470af7417","type":"timeseries", + "series":[{"id":"61ca57f1-469d-11e7-af02-69e470af7417","metrics":[{"id":"61ca57f2-469d-11e7-af02-69e470af7417", + "type":"filter_ratio","numerator":"Filter Bytes Test:>1000","denominator":"Filter Bytes Test:<1000"}]}]}}`, + }, + }; + + it('should replace numerator string with a query object', () => { + const migratedTestDoc1 = migrate(testDoc1); + const metric = JSON.parse(migratedTestDoc1.attributes.visState).params.series[0].metrics[0]; + + expect(metric.numerator).toHaveProperty('query'); + expect(metric.numerator).toHaveProperty('language'); + }); + + it('should replace denominator string with a query object', () => { + const migratedTestDoc1 = migrate(testDoc1); + const metric = JSON.parse(migratedTestDoc1.attributes.visState).params.series[0].metrics[0]; + + expect(metric.denominator).toHaveProperty('query'); + expect(metric.denominator).toHaveProperty('language'); + }); + }); }); diff --git a/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts b/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts index 74881b9d99ae..64491d02aa0a 100644 --- a/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts +++ b/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts @@ -99,6 +99,45 @@ const migratePercentileRankAggregation: SavedObjectMigrationFn = (doc) return doc; }; +// [TSVB] Replace string query with object +const migrateFilterRatioQuery: SavedObjectMigrationFn = (doc) => { + const visStateJSON = get(doc, 'attributes.visState'); + let visState; + + if (visStateJSON) { + try { + visState = JSON.parse(visStateJSON); + } catch (e) { + // Let it go, the data is invalid and we'll leave it as is + } + if (visState && visState.type === 'metrics') { + const series: any[] = get(visState, 'params.series') || []; + + series.forEach((part) => { + (part.metrics || []).forEach((metric: any) => { + if (metric.type === 'filter_ratio') { + if (typeof metric.numerator === 'string') { + metric.numerator = { query: metric.numerator, language: 'lucene' }; + } + if (typeof metric.denominator === 'string') { + metric.denominator = { query: metric.denominator, language: 'lucene' }; + } + } + }); + }); + + return { + ...doc, + attributes: { + ...doc.attributes, + visState: JSON.stringify(visState), + }, + }; + } + } + return doc; +}; + // [TSVB] Remove stale opperator key const migrateOperatorKeyTypo: SavedObjectMigrationFn = (doc) => { const visStateJSON = get(doc, 'attributes.visState'); @@ -713,4 +752,5 @@ export const visualizationSavedObjectTypeMigrations = { '7.4.2': flow(transformSplitFiltersStringToQueryObject), '7.7.0': flow(migrateOperatorKeyTypo, migrateSplitByChartRow), '7.8.0': flow(migrateTsvbDefaultColorPalettes), + '7.10.0': flow(migrateFilterRatioQuery), };