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),
};