diff --git a/src/plugins/vis_type_timeseries/common/calculate_label.test.ts b/src/plugins/vis_type_timeseries/common/calculate_label.test.ts index d5277623a136d..eab9665436c01 100644 --- a/src/plugins/vis_type_timeseries/common/calculate_label.test.ts +++ b/src/plugins/vis_type_timeseries/common/calculate_label.test.ts @@ -8,6 +8,7 @@ import { calculateLabel } from './calculate_label'; import type { MetricsItemsSchema } from './types'; +import { SanitizedFieldType } from './types'; describe('calculateLabel(metric, metrics)', () => { test('returns the metric.alias if set', () => { @@ -82,4 +83,26 @@ describe('calculateLabel(metric, metrics)', () => { expect(label).toEqual('Derivative of Outbound Traffic'); }); + + test('should throw an error if field not found', () => { + const metric = ({ id: 2, type: 'max', field: 3 } as unknown) as MetricsItemsSchema; + const metrics = ([ + { id: 1, type: 'max', field: 'network.out.bytes', alias: 'Outbound Traffic' }, + metric, + ] as unknown) as MetricsItemsSchema[]; + const fields: SanitizedFieldType[] = [{ name: '2', label: '2', type: 'field' }]; + + expect(() => calculateLabel(metric, metrics, fields)).toThrowError('Field "3" not found'); + }); + + test('should not throw an error if field not found (isThrowErrorOnFieldNotFound is false)', () => { + const metric = ({ id: 2, type: 'max', field: 3 } as unknown) as MetricsItemsSchema; + const metrics = ([ + { id: 1, type: 'max', field: 'network.out.bytes', alias: 'Outbound Traffic' }, + metric, + ] as unknown) as MetricsItemsSchema[]; + const fields: SanitizedFieldType[] = [{ name: '2', label: '2', type: 'field' }]; + + expect(calculateLabel(metric, metrics, fields, false)).toBe('Max of 3'); + }); }); diff --git a/src/plugins/vis_type_timeseries/common/calculate_label.ts b/src/plugins/vis_type_timeseries/common/calculate_label.ts index 73b5d3f652644..bd1482e14f4f4 100644 --- a/src/plugins/vis_type_timeseries/common/calculate_label.ts +++ b/src/plugins/vis_type_timeseries/common/calculate_label.ts @@ -10,6 +10,7 @@ import { includes, startsWith } from 'lodash'; import { i18n } from '@kbn/i18n'; import { lookup } from './agg_lookup'; import { MetricsItemsSchema, SanitizedFieldType } from './types'; +import { extractFieldLabel } from './fields_utils'; const paths = [ 'cumulative_sum', @@ -26,14 +27,11 @@ const paths = [ 'positive_only', ]; -export const extractFieldLabel = (fields: SanitizedFieldType[], name: string) => { - return fields.find((f) => f.name === name)?.label ?? name; -}; - export const calculateLabel = ( metric: MetricsItemsSchema, metrics: MetricsItemsSchema[] = [], - fields: SanitizedFieldType[] = [] + fields: SanitizedFieldType[] = [], + isThrowErrorOnFieldNotFound: boolean = true ): string => { if (!metric) { return i18n.translate('visTypeTimeseries.calculateLabel.unknownLabel', { @@ -71,7 +69,7 @@ export const calculateLabel = ( if (metric.type === 'positive_rate') { return i18n.translate('visTypeTimeseries.calculateLabel.positiveRateLabel', { defaultMessage: 'Counter Rate of {field}', - values: { field: extractFieldLabel(fields, metric.field!) }, + values: { field: extractFieldLabel(fields, metric.field!, isThrowErrorOnFieldNotFound) }, }); } if (metric.type === 'static') { @@ -115,7 +113,7 @@ export const calculateLabel = ( defaultMessage: '{lookupMetricType} of {metricField}', values: { lookupMetricType: lookup[metric.type], - metricField: extractFieldLabel(fields, metric.field!), + metricField: extractFieldLabel(fields, metric.field!, isThrowErrorOnFieldNotFound), }, }); }; diff --git a/src/plugins/vis_type_timeseries/common/constants.ts b/src/plugins/vis_type_timeseries/common/constants.ts index 66617c8518985..1debfaf951e99 100644 --- a/src/plugins/vis_type_timeseries/common/constants.ts +++ b/src/plugins/vis_type_timeseries/common/constants.ts @@ -13,3 +13,4 @@ export const ROUTES = { VIS_DATA: '/api/metrics/vis/data', FIELDS: '/api/metrics/fields', }; +export const USE_KIBANA_INDEXES_KEY = 'use_kibana_indexes'; diff --git a/src/plugins/vis_type_timeseries/common/fields_utils.test.ts b/src/plugins/vis_type_timeseries/common/fields_utils.test.ts index d1036aab2dc3e..9550697e22851 100644 --- a/src/plugins/vis_type_timeseries/common/fields_utils.test.ts +++ b/src/plugins/vis_type_timeseries/common/fields_utils.test.ts @@ -7,7 +7,7 @@ */ import { toSanitizedFieldType } from './fields_utils'; -import type { FieldSpec, RuntimeField } from '../../data/common'; +import type { FieldSpec } from '../../data/common'; describe('fields_utils', () => { describe('toSanitizedFieldType', () => { @@ -34,17 +34,6 @@ describe('fields_utils', () => { `); }); - test('should filter runtime fields', async () => { - const fields: FieldSpec[] = [ - { - ...mockedField, - runtimeField: {} as RuntimeField, - }, - ]; - - expect(toSanitizedFieldType(fields)).toMatchInlineSnapshot(`Array []`); - }); - test('should filter non-aggregatable fields', async () => { const fields: FieldSpec[] = [ { diff --git a/src/plugins/vis_type_timeseries/common/fields_utils.ts b/src/plugins/vis_type_timeseries/common/fields_utils.ts index 04499d5320ab8..6a83dd323b3fd 100644 --- a/src/plugins/vis_type_timeseries/common/fields_utils.ts +++ b/src/plugins/vis_type_timeseries/common/fields_utils.ts @@ -6,17 +6,60 @@ * Side Public License, v 1. */ +import { i18n } from '@kbn/i18n'; import { FieldSpec } from '../../data/common'; import { isNestedField } from '../../data/common'; -import { SanitizedFieldType } from './types'; +import { FetchedIndexPattern, SanitizedFieldType } from './types'; -export const toSanitizedFieldType = (fields: FieldSpec[]) => { - return fields - .filter( - (field) => - // Make sure to only include mapped fields, e.g. no index pattern runtime fields - !field.runtimeField && field.aggregatable && !isNestedField(field) - ) +export class FieldNotFoundError extends Error { + constructor(name: string) { + super( + i18n.translate('visTypeTimeseries.fields.fieldNotFound', { + defaultMessage: `Field "{field}" not found`, + values: { field: name }, + }) + ); + } + + public get name() { + return this.constructor.name; + } + + public get body() { + return this.message; + } +} + +export const extractFieldLabel = ( + fields: SanitizedFieldType[], + name: string, + isThrowErrorOnFieldNotFound: boolean = true +) => { + if (fields.length && name) { + const field = fields.find((f) => f.name === name); + + if (field) { + return field.label || field.name; + } + if (isThrowErrorOnFieldNotFound) { + throw new FieldNotFoundError(name); + } + } + return name; +}; + +export function validateField(name: string, index: FetchedIndexPattern) { + if (name && index.indexPattern) { + const field = index.indexPattern.fields.find((f) => f.name === name); + if (!field) { + throw new FieldNotFoundError(name); + } + } +} + +export const toSanitizedFieldType = (fields: FieldSpec[]) => + fields + .filter((field) => field.aggregatable && !isNestedField(field)) .map( (field) => ({ @@ -25,4 +68,3 @@ export const toSanitizedFieldType = (fields: FieldSpec[]) => { type: field.type, } as SanitizedFieldType) ); -}; diff --git a/src/plugins/vis_type_timeseries/common/index_patterns_utils.test.ts b/src/plugins/vis_type_timeseries/common/index_patterns_utils.test.ts index 0428e6e80ae78..1111a9c525243 100644 --- a/src/plugins/vis_type_timeseries/common/index_patterns_utils.test.ts +++ b/src/plugins/vis_type_timeseries/common/index_patterns_utils.test.ts @@ -81,7 +81,7 @@ describe('fetchIndexPattern', () => { }); describe('text-based index', () => { - test('should return the Kibana index if it exists', async () => { + test('should return the Kibana index if it exists (fetchKibabaIndexForStringIndexes is true)', async () => { mockedIndices = [ { id: 'indexId', @@ -89,7 +89,9 @@ describe('fetchIndexPattern', () => { }, ] as IndexPattern[]; - const value = await fetchIndexPattern('indexTitle', indexPatternsService); + const value = await fetchIndexPattern('indexTitle', indexPatternsService, { + fetchKibabaIndexForStringIndexes: true, + }); expect(value).toMatchInlineSnapshot(` Object { @@ -102,8 +104,10 @@ describe('fetchIndexPattern', () => { `); }); - test('should return only indexPatternString if Kibana index does not exist', async () => { - const value = await fetchIndexPattern('indexTitle', indexPatternsService); + test('should return only indexPatternString if Kibana index does not exist (fetchKibabaIndexForStringIndexes is true)', async () => { + const value = await fetchIndexPattern('indexTitle', indexPatternsService, { + fetchKibabaIndexForStringIndexes: true, + }); expect(value).toMatchInlineSnapshot(` Object { diff --git a/src/plugins/vis_type_timeseries/common/index_patterns_utils.ts b/src/plugins/vis_type_timeseries/common/index_patterns_utils.ts index af9f0750b2604..5dacad338e7a8 100644 --- a/src/plugins/vis_type_timeseries/common/index_patterns_utils.ts +++ b/src/plugins/vis_type_timeseries/common/index_patterns_utils.ts @@ -52,7 +52,12 @@ export const extractIndexPatternValues = ( export const fetchIndexPattern = async ( indexPatternValue: IndexPatternValue | undefined, - indexPatternsService: Pick + indexPatternsService: Pick, + options: { + fetchKibabaIndexForStringIndexes: boolean; + } = { + fetchKibabaIndexForStringIndexes: false, + } ): Promise => { let indexPattern: FetchedIndexPattern['indexPattern']; let indexPatternString: string = ''; @@ -61,13 +66,16 @@ export const fetchIndexPattern = async ( indexPattern = await indexPatternsService.getDefault(); } else { if (isStringTypeIndexPattern(indexPatternValue)) { - indexPattern = (await indexPatternsService.find(indexPatternValue)).find( - (index) => index.title === indexPatternValue - ); - + if (options.fetchKibabaIndexForStringIndexes) { + indexPattern = (await indexPatternsService.find(indexPatternValue)).find( + (index) => index.title === indexPatternValue + ); + } if (!indexPattern) { indexPatternString = indexPatternValue; } + + indexPatternString = indexPatternValue; } else if (indexPatternValue.id) { indexPattern = await indexPatternsService.get(indexPatternValue.id); } diff --git a/src/plugins/vis_type_timeseries/common/types.ts b/src/plugins/vis_type_timeseries/common/types.ts index 74e247b7af06d..240b3e68cf65d 100644 --- a/src/plugins/vis_type_timeseries/common/types.ts +++ b/src/plugins/vis_type_timeseries/common/types.ts @@ -46,6 +46,7 @@ interface TableData { export type SeriesData = { type: Exclude; uiRestrictions: TimeseriesUIRestrictions; + error?: string; } & { [key: string]: PanelSeries; }; @@ -56,7 +57,7 @@ interface PanelSeries { }; id: string; series: PanelData[]; - error?: unknown; + error?: string; } export interface PanelData { @@ -66,6 +67,7 @@ export interface PanelData { seriesId: string; splitByLabel: string; isSplitByTerms: boolean; + error?: string; } export const isVisTableData = (data: TimeseriesVisData): data is TableData => diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/field_select.tsx b/src/plugins/vis_type_timeseries/public/application/components/aggs/field_select.tsx index 82989cc15d6c9..7d42eb3f40ac5 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/field_select.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/field_select.tsx @@ -5,19 +5,26 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ - -import React from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiComboBox, EuiComboBoxProps, EuiComboBoxOptionOption } from '@elastic/eui'; -import { METRIC_TYPES } from '../../../../common/metric_types'; +import React, { ReactNode, useContext } from 'react'; +import { + EuiComboBox, + EuiComboBoxProps, + EuiComboBoxOptionOption, + EuiFormRow, + htmlIdGenerator, +} from '@elastic/eui'; import { getIndexPatternKey } from '../../../../common/index_patterns_utils'; import type { SanitizedFieldType, IndexPatternValue } from '../../../../common/types'; import type { TimeseriesUIRestrictions } from '../../../../common/ui_restrictions'; // @ts-ignore import { isFieldEnabled } from '../../lib/check_ui_restrictions'; +import { PanelModelContext } from '../../contexts/panel_model_context'; +import { USE_KIBANA_INDEXES_KEY } from '../../../../common/constants'; interface FieldSelectProps { + label: string | ReactNode; type: string; fields: Record; indexPattern: IndexPatternValue; @@ -45,6 +52,7 @@ const sortByLabel = (a: EuiComboBoxOptionOption, b: EuiComboBoxOptionOpt }; export function FieldSelect({ + label, type, fields, indexPattern = '', @@ -56,11 +64,10 @@ export function FieldSelect({ uiRestrictions, 'data-test-subj': dataTestSubj = 'metricsIndexPatternFieldsSelect', }: FieldSelectProps) { - if (type === METRIC_TYPES.COUNT) { - return null; - } + const panelModel = useContext(PanelModelContext); + const htmlId = htmlIdGenerator(); - const selectedOptions: Array> = []; + let selectedOptions: Array> = []; let newPlaceholder = placeholder; const fieldsSelector = getIndexPatternKey(indexPattern); @@ -112,19 +119,43 @@ export function FieldSelect({ } }); - if (value && !selectedOptions.length) { - onChange([]); + let isInvalid; + + if (Boolean(panelModel?.[USE_KIBANA_INDEXES_KEY])) { + isInvalid = Boolean(value && fields[fieldsSelector] && !selectedOptions.length); + + if (value && !selectedOptions.length) { + selectedOptions = [{ label: value!, id: 'INVALID_FIELD' }]; + } + } else { + if (value && !selectedOptions.length) { + onChange([]); + } } return ( - + + + ); } 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 90353f9af8e35..c380b0e09e7d3 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 @@ -153,24 +153,20 @@ export const FilterRatioAgg = (props) => { {model.metric_agg !== 'count' ? ( - } - > - - + fields={fields} + type={model.metric_agg} + restrict={getSupportedFieldsByMetricType(model.metric_agg)} + indexPattern={indexPattern} + value={model.field} + onChange={handleSelectChange('field')} + /> ) : null} diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/metric_select.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/metric_select.js index 964017cf886ec..7ce432a3bf676 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/metric_select.js +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/metric_select.js @@ -70,7 +70,7 @@ export function MetricSelect(props) { const percentileOptions = siblings .filter((row) => /^percentile/.test(row.type)) .reduce((acc, row) => { - const label = calculateLabel(row, calculatedMetrics, fields); + const label = calculateLabel(row, calculatedMetrics, fields, false); switch (row.type) { case METRIC_TYPES.PERCENTILE_RANK: @@ -100,7 +100,7 @@ export function MetricSelect(props) { }, []); const options = siblings.filter(filterRows(includeSiblings)).map((row) => { - const label = calculateLabel(row, calculatedMetrics, fields); + const label = calculateLabel(row, calculatedMetrics, fields, false); return { value: row.id, label }; }); const allOptions = [...options, ...additionalOptions, ...percentileOptions]; diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/percentile.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/percentile.js index 77b2e2f020307..45bb5387c5cd3 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/percentile.js +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/percentile.js @@ -78,24 +78,20 @@ export function PercentileAgg(props) { /> - } - > - - + fields={fields} + type={model.type} + restrict={RESTRICT_FIELDS} + indexPattern={indexPattern} + value={model.field} + onChange={handleSelectChange('field')} + /> { /> - } - > - - + fields={fields} + type={model.type} + restrict={RESTRICT_FIELDS} + indexPattern={indexPattern} + value={model.field ?? ''} + onChange={handleSelectChange('field')} + /> diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/positive_rate.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/positive_rate.js index 4b1528ca27081..09d9f2f1a62f2 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/positive_rate.js +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/positive_rate.js @@ -99,27 +99,22 @@ export const PositiveRateAgg = (props) => { /> - } + fields={props.fields} + type={model.type} + restrict={[KBN_FIELD_TYPES.NUMBER]} + indexPattern={indexPattern} + value={model.field} + onChange={handleSelectChange('field')} + uiRestrictions={props.uiRestrictions} fullWidth - > - - + /> - } + fields={fields} + type={model.type} + restrict={restrictFields} + indexPattern={indexPattern} + value={model.field} + onChange={handleSelectChange('field')} + uiRestrictions={uiRestrictions} fullWidth - > - - + /> ) : null} diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/std_deviation.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/std_deviation.js index 749a97fa79f28..d4caa8a94652f 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/std_deviation.js +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/std_deviation.js @@ -107,24 +107,20 @@ const StandardDeviationAggUi = (props) => { /> - } - > - - + fields={fields} + type={model.type} + restrict={RESTRICT_FIELDS} + indexPattern={indexPattern} + value={model.field} + onChange={handleSelectChange('field')} + /> { /> - } - > - - + fields={fields} + type={model.type} + restrict={aggWithOptionsRestrictFields} + indexPattern={indexPattern} + value={model.field} + onChange={handleSelectChange('field')} + /> @@ -223,23 +219,19 @@ const TopHitAggUi = (props) => { - } - > - - + restrict={ORDER_DATE_RESTRICT_FIELDS} + value={model.order_by} + onChange={handleSelectChange('order_by')} + indexPattern={indexPattern} + fields={fields} + /> - } + restrict={RESTRICT_FIELDS} + value={model.time_field} + onChange={this.handleChange(model, 'time_field')} + indexPattern={model.index_pattern} + fields={this.props.fields} fullWidth - > - - + /> diff --git a/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js b/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js index 5a991238d10f8..e7a34c6e6596d 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js +++ b/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js @@ -77,8 +77,8 @@ export const IndexPattern = ({ const intervalName = `${prefix}interval`; const maxBarsName = `${prefix}max_bars`; const dropBucketName = `${prefix}drop_last_bucket`; - const defaultIndex = useContext(DefaultIndexPatternContext); const updateControlValidity = useContext(FormValidationContext); + const defaultIndex = useContext(DefaultIndexPatternContext); const uiRestrictions = get(useContext(VisDataContext), 'uiRestrictions'); const maxBarsUiSettings = config.get(UI_SETTINGS.HISTOGRAM_MAX_BARS); @@ -192,22 +192,18 @@ export const IndexPattern = ({ /> - - - + restrict={RESTRICT_FIELDS} + value={model[timeFieldName]} + disabled={disabled} + onChange={handleSelectChange(timeFieldName)} + indexPattern={model[indexPatternName]} + fields={fields} + placeholder={!model[indexPatternName] ? defaultIndex?.timeFieldName : undefined} + /> - } - > - - + fields={this.props.fields} + value={model.pivot_id} + indexPattern={model.index_pattern} + onChange={this.handlePivotChange} + uiRestrictions={this.context.uiRestrictions} + type={BUCKET_TYPES.TERMS} + /> diff --git a/src/plugins/vis_type_timeseries/public/application/components/splits/__snapshots__/terms.test.js.snap b/src/plugins/vis_type_timeseries/public/application/components/splits/__snapshots__/terms.test.js.snap index 09cd6d550fd96..562c463f6c83c 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/splits/__snapshots__/terms.test.js.snap +++ b/src/plugins/vis_type_timeseries/public/application/components/splits/__snapshots__/terms.test.js.snap @@ -26,13 +26,25 @@ exports[`src/legacy/core_plugins/metrics/public/components/splits/terms.test.js - } - labelType="label" - > - - + onChange={[Function]} + type="terms" + value="OriginCityName" + /> diff --git a/src/plugins/vis_type_timeseries/public/application/components/splits/terms.js b/src/plugins/vis_type_timeseries/public/application/components/splits/terms.js index ab5342e925bd7..7db6a75e2392c 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/splits/terms.js +++ b/src/plugins/vis_type_timeseries/public/application/components/splits/terms.js @@ -110,8 +110,7 @@ export const SplitByTermsUI = ({ - } - > - - + data-test-subj="groupByField" + indexPattern={indexPattern} + onChange={handleSelectChange('terms_field')} + value={model.terms_field} + fields={fields} + uiRestrictions={uiRestrictions} + type={'terms'} + /> diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/config.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/config.js index 0ba8d3e855365..1940ac8b2e9b9 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/config.js +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/config.js @@ -186,20 +186,16 @@ export class TableSeriesConfig extends Component { - } - > - - + fields={this.props.fields} + indexPattern={this.props.panel.index_pattern} + value={model.aggregate_by} + onChange={handleSelectChange('aggregate_by')} + fullWidth + /> { }); describe('text-based index', () => { - test('should return the Kibana index if it exists', async () => { - mockedIndices = [ - { - id: 'indexId', - title: 'indexTitle', - }, - ] as IndexPattern[]; - - const value = await cachedIndexPatternFetcher('indexTitle'); - - expect(value).toMatchInlineSnapshot(` - Object { - "indexPattern": Object { - "id": "indexId", - "title": "indexTitle", - }, - "indexPatternString": "indexTitle", - } - `); - }); - - test('should return only indexPatternString if Kibana index does not exist', async () => { + test('should return only indexPatternString', async () => { const value = await cachedIndexPatternFetcher('indexTitle'); expect(value).toMatchInlineSnapshot(` diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/fields_fetcher.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/fields_fetcher.ts index 9003eb7fc2ced..4b13e62430c47 100644 --- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/fields_fetcher.ts +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/fields_fetcher.ts @@ -6,10 +6,13 @@ * Side Public License, v 1. */ +import { getIndexPatternKey } from '../../../../common/index_patterns_utils'; + import type { VisTypeTimeseriesVisDataRequest } from '../../../types'; import type { AbstractSearchStrategy, DefaultSearchCapabilities } from '../index'; import type { IndexPatternsService } from '../../../../../data/common'; import type { CachedIndexPatternFetcher } from './cached_index_pattern_fetcher'; +import type { IndexPatternValue } from '../../../../common/types'; export interface FieldsFetcherServices { indexPatternsService: IndexPatternsService; @@ -29,11 +32,13 @@ export const createFieldsFetcher = ( ) => { const fieldsCacheMap = new Map(); - return async (index: string) => { - if (fieldsCacheMap.has(index)) { - return fieldsCacheMap.get(index); + return async (indexPatternValue: IndexPatternValue) => { + const key = getIndexPatternKey(indexPatternValue); + + if (fieldsCacheMap.has(key)) { + return fieldsCacheMap.get(key); } - const fetchedIndex = await cachedIndexPatternFetcher(index); + const fetchedIndex = await cachedIndexPatternFetcher(indexPatternValue); const fields = await searchStrategy.getFieldsForWildcard( fetchedIndex, @@ -41,7 +46,7 @@ export const createFieldsFetcher = ( capabilities ); - fieldsCacheMap.set(index, fields); + fieldsCacheMap.set(key, fields); return fields; }; diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/annotations/build_request_body.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/annotations/build_request_body.ts index 5a84598bb5ed2..1350e56b68f59 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/annotations/build_request_body.ts +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/annotations/build_request_body.ts @@ -7,8 +7,8 @@ */ import { IUiSettingsClient } from 'kibana/server'; -import { EsQueryConfig, IndexPattern } from 'src/plugins/data/server'; -import { AnnotationItemsSchema, PanelSchema } from '../../../../common/types'; +import { EsQueryConfig } from 'src/plugins/data/server'; +import { AnnotationItemsSchema, FetchedIndexPattern, PanelSchema } from '../../../../common/types'; import { VisTypeTimeseriesVisDataRequest } from '../../../types'; import { DefaultSearchCapabilities } from '../../search_strategies'; import { buildProcessorFunction } from '../build_processor_function'; @@ -17,16 +17,6 @@ import { processors } from '../request_processors/annotations'; /** * Builds annotation request body - * - * @param {...args}: [ - * req: {Object} - a request object, - * panel: {Object} - a panel object, - * annotation: {Object} - an annotation object, - * esQueryConfig: {Object} - es query config object, - * indexPatternObject: {Object} - an index pattern object, - * capabilities: {Object} - a search capabilities object - * ] - * @returns {Object} doc - processed body */ export async function buildAnnotationRequest( ...args: [ @@ -34,7 +24,7 @@ export async function buildAnnotationRequest( PanelSchema, AnnotationItemsSchema, EsQueryConfig, - IndexPattern | null | undefined, + FetchedIndexPattern, DefaultSearchCapabilities, IUiSettingsClient ] diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/annotations/get_request_params.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/annotations/get_request_params.ts index 32086fbf4f5b4..40f1b4f2cc051 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/annotations/get_request_params.ts +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/annotations/get_request_params.ts @@ -33,24 +33,23 @@ export async function getAnnotationRequestParams( cachedIndexPatternFetcher, }: AnnotationServices ) { - const { indexPattern, indexPatternString } = await cachedIndexPatternFetcher( - annotation.index_pattern - ); + const annotationIndex = await cachedIndexPatternFetcher(annotation.index_pattern); const request = await buildAnnotationRequest( req, panel, annotation, esQueryConfig, - indexPattern, + annotationIndex, capabilities, uiSettings ); return { - index: indexPatternString, + index: annotationIndex.indexPatternString, body: { ...request, + runtime_mappings: annotationIndex.indexPattern?.getComputedFields().runtimeFields ?? {}, timeout: esShardTimeout > 0 ? `${esShardTimeout}ms` : undefined, }, }; diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.test.ts similarity index 68% rename from src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.test.js rename to src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.test.ts index ceb867e4e6d1e..7c0a0f5deb601 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.test.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.test.ts @@ -7,25 +7,30 @@ */ import { getIntervalAndTimefield } from './get_interval_and_timefield'; +import { FetchedIndexPattern, PanelSchema, SeriesItemsSchema } from '../../../common/types'; describe('getIntervalAndTimefield(panel, series)', () => { + const index: FetchedIndexPattern = {} as FetchedIndexPattern; + test('returns the panel interval and timefield', () => { - const panel = { time_field: '@timestamp', interval: 'auto' }; - const series = {}; - expect(getIntervalAndTimefield(panel, series)).toEqual({ + const panel = { time_field: '@timestamp', interval: 'auto' } as PanelSchema; + const series = {} as SeriesItemsSchema; + + expect(getIntervalAndTimefield(panel, series, index)).toEqual({ timeField: '@timestamp', interval: 'auto', }); }); test('returns the series interval and timefield', () => { - const panel = { time_field: '@timestamp', interval: 'auto' }; - const series = { + const panel = { time_field: '@timestamp', interval: 'auto' } as PanelSchema; + const series = ({ override_index_pattern: true, series_interval: '1m', series_time_field: 'time', - }; - expect(getIntervalAndTimefield(panel, series)).toEqual({ + } as unknown) as SeriesItemsSchema; + + expect(getIntervalAndTimefield(panel, series, index)).toEqual({ timeField: 'time', interval: '1m', }); diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.ts similarity index 57% rename from src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.js rename to src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.ts index ebab984ff25aa..e3d0cec1a6939 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.ts @@ -7,28 +7,31 @@ */ import { AUTO_INTERVAL } from '../../../common/constants'; +import { FetchedIndexPattern, PanelSchema, SeriesItemsSchema } from '../../../common/types'; +import { validateField } from '../../../common/fields_utils'; -const DEFAULT_TIME_FIELD = '@timestamp'; - -export function getIntervalAndTimefield(panel, series = {}, indexPattern) { - const getDefaultTimeField = () => indexPattern?.timeFieldName ?? DEFAULT_TIME_FIELD; - +export function getIntervalAndTimefield( + panel: PanelSchema, + series: SeriesItemsSchema, + index: FetchedIndexPattern +) { const timeField = - (series.override_index_pattern && series.series_time_field) || - panel.time_field || - getDefaultTimeField(); + (series.override_index_pattern ? series.series_time_field : panel.time_field) || + index.indexPattern?.timeFieldName; + + validateField(timeField!, index); let interval = panel.interval; let maxBars = panel.max_bars; if (series.override_index_pattern) { - interval = series.series_interval; + interval = series.series_interval || AUTO_INTERVAL; maxBars = series.series_max_bars; } return { + maxBars, timeField, interval: interval || AUTO_INTERVAL, - maxBars, }; } diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.ts index 0cc1188086b7b..b50fdb6b8226d 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.ts +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.ts @@ -18,7 +18,7 @@ import { handleErrorResponse } from './handle_error_response'; import { processBucket } from './table/process_bucket'; import { createFieldsFetcher } from '../search_strategies/lib/fields_fetcher'; -import { extractFieldLabel } from '../../../common/calculate_label'; +import { extractFieldLabel } from '../../../common/fields_utils'; import type { VisTypeTimeseriesRequestHandlerContext, VisTypeTimeseriesRequestServices, @@ -58,8 +58,8 @@ export async function getTableData( }); const calculatePivotLabel = async () => { - if (panel.pivot_id && panelIndex.indexPattern?.title) { - const fields = await extractFields(panelIndex.indexPattern.title); + if (panel.pivot_id && panelIndex.indexPattern?.id) { + const fields = await extractFields({ id: panelIndex.indexPattern.id }); return extractFieldLabel(fields, panel.pivot_id); } @@ -68,7 +68,6 @@ export async function getTableData( const meta = { type: panel.type, - pivot_label: panel.pivot_label || (await calculatePivotLabel()), uiRestrictions: capabilities.uiRestrictions, }; @@ -77,14 +76,17 @@ export async function getTableData( req, panel, services.esQueryConfig, - panelIndex.indexPattern, + panelIndex, capabilities, services.uiSettings ); const [resp] = await searchStrategy.search(requestContext, req, [ { - body, + body: { + ...body, + runtime_mappings: panelIndex.indexPattern?.getComputedFields().runtimeFields ?? {}, + }, index: panelIndex.indexPatternString, }, ]); @@ -101,6 +103,7 @@ export async function getTableData( return { ...meta, + pivot_label: panel.pivot_label || (await calculatePivotLabel()), series, }; } catch (err) { diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_splits.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_splits.js index 268c26115233e..27e7c5c908b9a 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_splits.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_splits.js @@ -23,9 +23,8 @@ export async function getSplits(resp, panel, series, meta, extractFields) { const color = new Color(series.color); const metric = getLastMetric(series); const buckets = _.get(resp, `aggregations.${series.id}.buckets`); - - const fieldsForMetaIndex = meta.index ? await extractFields(meta.index) : []; - const splitByLabel = calculateLabel(metric, series.metrics, fieldsForMetaIndex); + const fieldsForSeries = meta.index ? await extractFields({ id: meta.index }) : []; + const splitByLabel = calculateLabel(metric, series.metrics, fieldsForSeries); if (buckets) { if (Array.isArray(buckets)) { diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/date_histogram.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/date_histogram.js index 22a475a9997a7..f3ee416be81a8 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/date_histogram.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/date_histogram.js @@ -10,6 +10,7 @@ import { overwrite } from '../../helpers'; import { getBucketSize } from '../../helpers/get_bucket_size'; import { getTimerange } from '../../helpers/get_timerange'; import { search, UI_SETTINGS } from '../../../../../../../plugins/data/server'; +import { validateField } from '../../../../../common/fields_utils'; const { dateHistogramInterval } = search.aggs; @@ -18,13 +19,16 @@ export function dateHistogram( panel, annotation, esQueryConfig, - indexPattern, + annotationIndex, capabilities, uiSettings ) { return (next) => async (doc) => { const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET); const timeField = annotation.time_field; + + validateField(timeField, annotationIndex); + const { bucketSize, intervalString } = getBucketSize( req, 'auto', diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/query.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/query.js index e7270371a3fdc..46a3c369e548d 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/query.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/query.js @@ -9,26 +9,30 @@ import { getBucketSize } from '../../helpers/get_bucket_size'; import { getTimerange } from '../../helpers/get_timerange'; import { esQuery, UI_SETTINGS } from '../../../../../../data/server'; +import { validateField } from '../../../../../common/fields_utils'; export function query( req, panel, annotation, esQueryConfig, - indexPattern, + annotationIndex, capabilities, uiSettings ) { return (next) => async (doc) => { const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET); - const timeField = annotation.time_field; + const timeField = (annotation.time_field || annotationIndex.indexPattern?.timeField) ?? ''; + + validateField(timeField, annotationIndex); + const { bucketSize } = getBucketSize(req, 'auto', capabilities, barTargetUiSettings); const { from, to } = getTimerange(req); doc.size = 0; const queries = !annotation.ignore_global_filters ? req.body.query : []; const filters = !annotation.ignore_global_filters ? req.body.filters : []; - doc.query = esQuery.buildEsQuery(indexPattern, queries, filters, esQueryConfig); + doc.query = esQuery.buildEsQuery(annotationIndex.indexPattern, queries, filters, esQueryConfig); const timerange = { range: { [timeField]: { @@ -42,13 +46,18 @@ export function query( if (annotation.query_string) { doc.query.bool.must.push( - esQuery.buildEsQuery(indexPattern, [annotation.query_string], [], esQueryConfig) + esQuery.buildEsQuery( + annotationIndex.indexPattern, + [annotation.query_string], + [], + esQueryConfig + ) ); } if (!annotation.ignore_panel_filters && panel.filter) { doc.query.bool.must.push( - esQuery.buildEsQuery(indexPattern, [panel.filter], [], esQueryConfig) + esQuery.buildEsQuery(annotationIndex.indexPattern, [panel.filter], [], esQueryConfig) ); } diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/top_hits.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/top_hits.js index 2e759cb6b8b74..1b4434c4867c8 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/top_hits.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/top_hits.js @@ -7,12 +7,15 @@ */ import { overwrite } from '../../helpers'; +import { validateField } from '../../../../../common/fields_utils'; -export function topHits(req, panel, annotation) { +export function topHits(req, panel, annotation, esQueryConfig, annotationIndex) { return (next) => (doc) => { const fields = (annotation.fields && annotation.fields.split(/[,\s]+/)) || []; const timeField = annotation.time_field; + validateField(timeField, annotationIndex); + overwrite(doc, `aggs.${annotation.id}.aggs.hits.top_hits`, { sort: [ { diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.js index a9b4f99fdb693..41ed472c31936 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.js @@ -12,6 +12,7 @@ import { offsetTime } from '../../offset_time'; import { getIntervalAndTimefield } from '../../get_interval_and_timefield'; import { isLastValueTimerangeMode } from '../../helpers/get_timerange_mode'; import { search, UI_SETTINGS } from '../../../../../../../plugins/data/server'; + const { dateHistogramInterval } = search.aggs; export function dateHistogram( @@ -19,7 +20,7 @@ export function dateHistogram( panel, series, esQueryConfig, - indexPattern, + seriesIndex, capabilities, uiSettings ) { @@ -27,7 +28,7 @@ export function dateHistogram( const maxBarsUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_MAX_BARS); const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET); - const { timeField, interval, maxBars } = getIntervalAndTimefield(panel, series, indexPattern); + const { timeField, interval, maxBars } = getIntervalAndTimefield(panel, series, seriesIndex); const { bucketSize, intervalString } = getBucketSize( req, interval, @@ -64,9 +65,9 @@ export function dateHistogram( overwrite(doc, `aggs.${series.id}.meta`, { timeField, intervalString, - index: indexPattern?.title, bucketSize, seriesId: series.id, + index: seriesIndex.indexPattern?.id, }); return next(doc); 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 4639af9db83b8..d45943f6f21ac 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 @@ -12,19 +12,19 @@ import { esQuery } from '../../../../../../data/server'; const filter = (metric) => metric.type === 'filter_ratio'; -export function ratios(req, panel, series, esQueryConfig, indexPattern) { +export function ratios(req, panel, series, esQueryConfig, seriesIndex) { 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`, - esQuery.buildEsQuery(indexPattern, metric.numerator, [], esQueryConfig) + esQuery.buildEsQuery(seriesIndex.indexPattern, metric.numerator, [], esQueryConfig) ); overwrite( doc, `aggs.${series.id}.aggs.timeseries.aggs.${metric.id}-denominator.filter`, - esQuery.buildEsQuery(indexPattern, metric.denominator, [], esQueryConfig) + esQuery.buildEsQuery(seriesIndex.indexPattern, metric.denominator, [], esQueryConfig) ); let numeratorPath = `${metric.id}-numerator>_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 345488ec01d5e..a93827ba82cd6 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 @@ -8,7 +8,7 @@ import { ratios } from './filter_ratios'; -describe('ratios(req, panel, series, esQueryConfig, indexPatternObject)', () => { +describe('ratios(req, panel, series, esQueryConfig, seriesIndex)', () => { let panel; let series; let req; diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/metric_buckets.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/metric_buckets.js index 86b691f6496c9..29a11bf163e0b 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/metric_buckets.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/metric_buckets.js @@ -17,14 +17,14 @@ export function metricBuckets( panel, series, esQueryConfig, - indexPattern, + seriesIndex, capabilities, uiSettings ) { return (next) => async (doc) => { const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET); - const { interval } = getIntervalAndTimefield(panel, series, indexPattern); + const { interval } = getIntervalAndTimefield(panel, series, seriesIndex); const { intervalString } = getBucketSize(req, interval, capabilities, barTargetUiSettings); series.metrics diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/positive_rate.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/positive_rate.js index ce61374c0b124..208321a98737e 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/positive_rate.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/positive_rate.js @@ -56,14 +56,14 @@ export function positiveRate( panel, series, esQueryConfig, - indexPattern, + seriesIndex, capabilities, uiSettings ) { return (next) => async (doc) => { const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET); - const { interval } = getIntervalAndTimefield(panel, series, indexPattern); + const { interval } = getIntervalAndTimefield(panel, series, seriesIndex); const { intervalString } = getBucketSize(req, interval, capabilities, barTargetUiSettings); if (series.metrics.some(filter)) { diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.js index d0e92c9157cb5..a5f4e17289e06 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.js @@ -10,16 +10,16 @@ import { offsetTime } from '../../offset_time'; import { getIntervalAndTimefield } from '../../get_interval_and_timefield'; import { esQuery } from '../../../../../../data/server'; -export function query(req, panel, series, esQueryConfig, indexPattern) { +export function query(req, panel, series, esQueryConfig, seriesIndex) { return (next) => (doc) => { - const { timeField } = getIntervalAndTimefield(panel, series, indexPattern); + const { timeField } = getIntervalAndTimefield(panel, series, seriesIndex); const { from, to } = offsetTime(req, series.offset_time); doc.size = 0; const ignoreGlobalFilter = panel.ignore_global_filter || series.ignore_global_filter; const queries = !ignoreGlobalFilter ? req.body.query : []; const filters = !ignoreGlobalFilter ? req.body.filters : []; - doc.query = esQuery.buildEsQuery(indexPattern, queries, filters, esQueryConfig); + doc.query = esQuery.buildEsQuery(seriesIndex.indexPattern, queries, filters, esQueryConfig); const timerange = { range: { @@ -34,13 +34,13 @@ export function query(req, panel, series, esQueryConfig, indexPattern) { if (panel.filter) { doc.query.bool.must.push( - esQuery.buildEsQuery(indexPattern, [panel.filter], [], esQueryConfig) + esQuery.buildEsQuery(seriesIndex.indexPattern, [panel.filter], [], esQueryConfig) ); } if (series.filter) { doc.query.bool.must.push( - esQuery.buildEsQuery(indexPattern, [series.filter], [], esQueryConfig) + esQuery.buildEsQuery(seriesIndex.indexPattern, [series.filter], [], esQueryConfig) ); } diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.test.js index 2772aed822517..b3e88dbf1c6b9 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.test.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.test.js @@ -8,15 +8,17 @@ import { query } from './query'; -describe('query(req, panel, series)', () => { +describe('query', () => { let panel; let series; let req; + let seriesIndex; const config = { allowLeadingWildcards: true, queryStringOptions: { analyze_wildcard: true }, }; + beforeEach(() => { req = { body: { @@ -32,17 +34,18 @@ describe('query(req, panel, series)', () => { interval: '10s', }; series = { id: 'test' }; + seriesIndex = {}; }); test('calls next when finished', () => { const next = jest.fn(); - query(req, panel, series, config)(next)({}); + query(req, panel, series, config, seriesIndex)(next)({}); expect(next.mock.calls.length).toEqual(1); }); test('returns doc with query for timerange', () => { const next = (doc) => doc; - const doc = query(req, panel, series, config)(next)({}); + const doc = query(req, panel, series, config, seriesIndex)(next)({}); expect(doc).toEqual({ size: 0, query: { @@ -69,7 +72,7 @@ describe('query(req, panel, series)', () => { test('returns doc with query for timerange (offset by 1h)', () => { series.offset_time = '1h'; const next = (doc) => doc; - const doc = query(req, panel, series, config)(next)({}); + const doc = query(req, panel, series, config, seriesIndex)(next)({}); expect(doc).toEqual({ size: 0, query: { @@ -108,7 +111,7 @@ describe('query(req, panel, series)', () => { }, ]; const next = (doc) => doc; - const doc = query(req, panel, series, config)(next)({}); + const doc = query(req, panel, series, config, seriesIndex)(next)({}); expect(doc).toEqual({ size: 0, query: { @@ -147,7 +150,7 @@ describe('query(req, panel, series)', () => { test('returns doc with series filter', () => { series.filter = { query: 'host:web-server', language: 'lucene' }; const next = (doc) => doc; - const doc = query(req, panel, series, config)(next)({}); + const doc = query(req, panel, series, config, seriesIndex)(next)({}); expect(doc).toEqual({ size: 0, query: { @@ -201,7 +204,7 @@ describe('query(req, panel, series)', () => { ]; panel.filter = { query: 'host:web-server', language: 'lucene' }; const next = (doc) => doc; - const doc = query(req, panel, series, config)(next)({}); + const doc = query(req, panel, series, config, seriesIndex)(next)({}); expect(doc).toEqual({ size: 0, query: { @@ -269,7 +272,7 @@ describe('query(req, panel, series)', () => { panel.filter = { query: 'host:web-server', language: 'lucene' }; panel.ignore_global_filter = true; const next = (doc) => doc; - const doc = query(req, panel, series, config)(next)({}); + const doc = query(req, panel, series, config, seriesIndex)(next)({}); expect(doc).toEqual({ size: 0, query: { @@ -325,7 +328,7 @@ describe('query(req, panel, series)', () => { panel.filter = { query: 'host:web-server', language: 'lucene' }; series.ignore_global_filter = true; const next = (doc) => doc; - const doc = query(req, panel, series, config)(next)({}); + const doc = query(req, panel, series, config, seriesIndex)(next)({}); expect(doc).toEqual({ size: 0, query: { diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/sibling_buckets.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/sibling_buckets.js index 401344d48f865..dbeb3b1393bd5 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/sibling_buckets.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/sibling_buckets.js @@ -17,13 +17,13 @@ export function siblingBuckets( panel, series, esQueryConfig, - indexPattern, + seriesIndex, capabilities, uiSettings ) { return (next) => async (doc) => { const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET); - const { interval } = getIntervalAndTimefield(panel, series, indexPattern); + const { interval } = getIntervalAndTimefield(panel, series, seriesIndex); const { bucketSize } = getBucketSize(req, interval, capabilities, barTargetUiSettings); series.metrics diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filter.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filter.js index 25d62d4f7fe07..01e1b9f8d1dce 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filter.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filter.js @@ -9,7 +9,7 @@ import { overwrite } from '../../helpers'; import { esQuery } from '../../../../../../data/server'; -export function splitByFilter(req, panel, series, esQueryConfig, indexPattern) { +export function splitByFilter(req, panel, series, esQueryConfig, seriesIndex) { return (next) => (doc) => { if (series.split_mode !== 'filter') { return next(doc); @@ -18,7 +18,7 @@ export function splitByFilter(req, panel, series, esQueryConfig, indexPattern) { overwrite( doc, `aggs.${series.id}.filter`, - esQuery.buildEsQuery(indexPattern, [series.filter], [], esQueryConfig) + esQuery.buildEsQuery(seriesIndex.indexPattern, [series.filter], [], esQueryConfig) ); return next(doc); diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filter.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filter.test.js index ad6e84dbc7842..9722833837167 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filter.test.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filter.test.js @@ -12,8 +12,12 @@ describe('splitByFilter(req, panel, series)', () => { let panel; let series; let req; + let config; + let seriesIndex; + beforeEach(() => { panel = {}; + config = {}; series = { id: 'test', split_mode: 'filter', @@ -27,17 +31,18 @@ describe('splitByFilter(req, panel, series)', () => { }, }, }; + seriesIndex = {}; }); test('calls next when finished', () => { const next = jest.fn(); - splitByFilter(req, panel, series)(next)({}); + splitByFilter(req, panel, series, config, seriesIndex)(next)({}); expect(next.mock.calls.length).toEqual(1); }); test('returns a valid filter with a query_string', () => { const next = (doc) => doc; - const doc = splitByFilter(req, panel, series)(next)({}); + const doc = splitByFilter(req, panel, series, config, seriesIndex)(next)({}); expect(doc).toEqual({ aggs: { test: { @@ -63,7 +68,7 @@ describe('splitByFilter(req, panel, series)', () => { test('calls next and does not add a filter', () => { series.split_mode = 'terms'; const next = jest.fn((doc) => doc); - const doc = splitByFilter(req, panel, series)(next)({}); + const doc = splitByFilter(req, panel, series, config, seriesIndex)(next)({}); expect(next.mock.calls.length).toEqual(1); expect(doc).toEqual({}); }); diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filters.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filters.js index 237ed16e5a8b6..77b9ccc5880fe 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filters.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filters.js @@ -9,11 +9,16 @@ import { overwrite } from '../../helpers'; import { esQuery } from '../../../../../../data/server'; -export function splitByFilters(req, panel, series, esQueryConfig, indexPattern) { +export function splitByFilters(req, panel, series, esQueryConfig, seriesIndex) { return (next) => (doc) => { if (series.split_mode === 'filters' && series.split_filters) { series.split_filters.forEach((filter) => { - const builtEsQuery = esQuery.buildEsQuery(indexPattern, [filter.filter], [], esQueryConfig); + const builtEsQuery = esQuery.buildEsQuery( + seriesIndex.indexPattern, + [filter.filter], + [], + esQueryConfig + ); overwrite(doc, `aggs.${series.id}.filters.filters.${filter.id}`, builtEsQuery); }); diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filters.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filters.test.js index fdcdfe45d2fd2..2a44bf2538a4b 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filters.test.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filters.test.js @@ -12,7 +12,11 @@ describe('splitByFilters(req, panel, series)', () => { let panel; let series; let req; + let config; + let seriesIndex; + beforeEach(() => { + config = {}; panel = { time_field: 'timestamp', }; @@ -43,17 +47,18 @@ describe('splitByFilters(req, panel, series)', () => { }, }, }; + seriesIndex = {}; }); test('calls next when finished', () => { const next = jest.fn(); - splitByFilters(req, panel, series)(next)({}); + splitByFilters(req, panel, series, config, seriesIndex)(next)({}); expect(next.mock.calls.length).toEqual(1); }); test('returns a valid terms agg', () => { const next = (doc) => doc; - const doc = splitByFilters(req, panel, series)(next)({}); + const doc = splitByFilters(req, panel, series, config, seriesIndex)(next)({}); expect(doc).toEqual({ aggs: { test: { @@ -97,7 +102,7 @@ describe('splitByFilters(req, panel, series)', () => { test('calls next and does not add a terms agg', () => { series.split_mode = 'everything'; const next = jest.fn((doc) => doc); - const doc = splitByFilters(req, panel, series)(next)({}); + const doc = splitByFilters(req, panel, series, config, seriesIndex)(next)({}); expect(next.mock.calls.length).toEqual(1); expect(doc).toEqual({}); }); diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_terms.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_terms.js index 8f72bd2d12951..9c2bdbe03f886 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_terms.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_terms.js @@ -10,13 +10,16 @@ import { overwrite } from '../../helpers'; import { basicAggs } from '../../../../../common/basic_aggs'; import { getBucketsPath } from '../../helpers/get_buckets_path'; import { bucketTransform } from '../../helpers/bucket_transform'; +import { validateField } from '../../../../../common/fields_utils'; -export function splitByTerms(req, panel, series) { +export function splitByTerms(req, panel, series, esQueryConfig, seriesIndex) { return (next) => (doc) => { if (series.split_mode === 'terms' && series.terms_field) { const termsField = series.terms_field; const orderByTerms = series.terms_order_by; + validateField(termsField, seriesIndex); + const direction = series.terms_direction || 'desc'; const metric = series.metrics.find((item) => item.id === orderByTerms); overwrite(doc, `aggs.${series.id}.terms.field`, termsField); diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_terms.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_terms.test.js index 37d188c00eee3..984eb385ca4a6 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_terms.test.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_terms.test.js @@ -8,11 +8,18 @@ import { splitByTerms } from './split_by_terms'; -describe('splitByTerms(req, panel, series)', () => { +describe('splitByTerms', () => { let panel; let series; let req; + let config; + let seriesIndex; + beforeEach(() => { + config = { + allowLeadingWildcards: true, + queryStringOptions: { analyze_wildcard: true }, + }; panel = { time_field: 'timestamp', }; @@ -31,17 +38,18 @@ describe('splitByTerms(req, panel, series)', () => { }, }, }; + seriesIndex = {}; }); test('calls next when finished', () => { const next = jest.fn(); - splitByTerms(req, panel, series)(next)({}); + splitByTerms(req, panel, series, config, seriesIndex)(next)({}); expect(next.mock.calls.length).toEqual(1); }); test('returns a valid terms agg', () => { const next = (doc) => doc; - const doc = splitByTerms(req, panel, series)(next)({}); + const doc = splitByTerms(req, panel, series, config, seriesIndex)(next)({}); expect(doc).toEqual({ aggs: { test: { @@ -61,7 +69,7 @@ describe('splitByTerms(req, panel, series)', () => { const next = (doc) => doc; series.terms_order_by = '_key'; series.terms_direction = 'asc'; - const doc = splitByTerms(req, panel, series)(next)({}); + const doc = splitByTerms(req, panel, series, config, seriesIndex)(next)({}); expect(doc).toEqual({ aggs: { test: { @@ -80,7 +88,7 @@ describe('splitByTerms(req, panel, series)', () => { test('returns a valid terms agg with custom sort', () => { series.terms_order_by = 'avgmetric'; const next = (doc) => doc; - const doc = splitByTerms(req, panel, series)(next)({}); + const doc = splitByTerms(req, panel, series, config, seriesIndex)(next)({}); expect(doc).toEqual({ aggs: { test: { @@ -106,7 +114,7 @@ describe('splitByTerms(req, panel, series)', () => { test('calls next and does not add a terms agg', () => { series.split_mode = 'everything'; const next = jest.fn((doc) => doc); - const doc = splitByTerms(req, panel, series)(next)({}); + const doc = splitByTerms(req, panel, series, config, seriesIndex)(next)({}); expect(next.mock.calls.length).toEqual(1); expect(doc).toEqual({}); }); diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/date_histogram.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/date_histogram.js index aff1bd5041be5..4840e625383ca 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/date_histogram.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/date_histogram.js @@ -13,15 +13,17 @@ import { getIntervalAndTimefield } from '../../get_interval_and_timefield'; import { getTimerange } from '../../helpers/get_timerange'; import { calculateAggRoot } from './calculate_agg_root'; import { search, UI_SETTINGS } from '../../../../../../../plugins/data/server'; + const { dateHistogramInterval } = search.aggs; -export function dateHistogram(req, panel, esQueryConfig, indexPattern, capabilities, uiSettings) { +export function dateHistogram(req, panel, esQueryConfig, seriesIndex, capabilities, uiSettings) { return (next) => async (doc) => { const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET); - const { timeField, interval } = getIntervalAndTimefield(panel, {}, indexPattern); + const { timeField, interval } = getIntervalAndTimefield(panel, {}, seriesIndex); + const meta = { timeField, - index: indexPattern?.title, + index: seriesIndex.indexPattern?.id, }; const getDateHistogramForLastBucketMode = () => { diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/filter_ratios.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/filter_ratios.js index abb5971908771..e15330334639f 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/filter_ratios.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/filter_ratios.js @@ -13,7 +13,7 @@ import { calculateAggRoot } from './calculate_agg_root'; const filter = (metric) => metric.type === 'filter_ratio'; -export function ratios(req, panel, esQueryConfig, indexPattern) { +export function ratios(req, panel, esQueryConfig, seriesIndex) { return (next) => (doc) => { panel.series.forEach((column) => { const aggRoot = calculateAggRoot(doc, column); @@ -22,12 +22,12 @@ export function ratios(req, panel, esQueryConfig, indexPattern) { overwrite( doc, `${aggRoot}.timeseries.aggs.${metric.id}-numerator.filter`, - esQuery.buildEsQuery(indexPattern, metric.numerator, [], esQueryConfig) + esQuery.buildEsQuery(seriesIndex.indexPattern, metric.numerator, [], esQueryConfig) ); overwrite( doc, `${aggRoot}.timeseries.aggs.${metric.id}-denominator.filter`, - esQuery.buildEsQuery(indexPattern, metric.denominator, [], esQueryConfig) + esQuery.buildEsQuery(seriesIndex.indexPattern, metric.denominator, [], esQueryConfig) ); let numeratorPath = `${metric.id}-numerator>_count`; diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/metric_buckets.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/metric_buckets.js index 5ce508bd9b279..421f9d2d75f0c 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/metric_buckets.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/metric_buckets.js @@ -13,10 +13,10 @@ import { getIntervalAndTimefield } from '../../get_interval_and_timefield'; import { calculateAggRoot } from './calculate_agg_root'; import { UI_SETTINGS } from '../../../../../../data/common'; -export function metricBuckets(req, panel, esQueryConfig, indexPattern, capabilities, uiSettings) { +export function metricBuckets(req, panel, esQueryConfig, seriesIndex, capabilities, uiSettings) { return (next) => async (doc) => { const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET); - const { interval } = getIntervalAndTimefield(panel, {}, indexPattern); + const { interval } = getIntervalAndTimefield(panel, {}, seriesIndex); const { intervalString } = getBucketSize(req, interval, capabilities, barTargetUiSettings); panel.series.forEach((column) => { diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/positive_rate.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/positive_rate.js index 176721e7b563a..3390362b56115 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/positive_rate.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/positive_rate.js @@ -12,10 +12,10 @@ import { calculateAggRoot } from './calculate_agg_root'; import { createPositiveRate, filter } from '../series/positive_rate'; import { UI_SETTINGS } from '../../../../../../data/common'; -export function positiveRate(req, panel, esQueryConfig, indexPattern, capabilities, uiSettings) { +export function positiveRate(req, panel, esQueryConfig, seriesIndex, capabilities, uiSettings) { return (next) => async (doc) => { const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET); - const { interval } = getIntervalAndTimefield(panel, {}, indexPattern); + const { interval } = getIntervalAndTimefield(panel, {}, seriesIndex); const { intervalString } = getBucketSize(req, interval, capabilities, barTargetUiSettings); panel.series.forEach((column) => { diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/query.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/query.js index 76df07b76e80e..66783e0cdfaef 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/query.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/query.js @@ -10,16 +10,16 @@ import { getTimerange } from '../../helpers/get_timerange'; import { getIntervalAndTimefield } from '../../get_interval_and_timefield'; import { esQuery } from '../../../../../../data/server'; -export function query(req, panel, esQueryConfig, indexPattern) { +export function query(req, panel, esQueryConfig, seriesIndex) { return (next) => (doc) => { - const { timeField } = getIntervalAndTimefield(panel, {}, indexPattern); + const { timeField } = getIntervalAndTimefield(panel, {}, seriesIndex); const { from, to } = getTimerange(req); doc.size = 0; const queries = !panel.ignore_global_filter ? req.body.query : []; const filters = !panel.ignore_global_filter ? req.body.filters : []; - doc.query = esQuery.buildEsQuery(indexPattern, queries, filters, esQueryConfig); + doc.query = esQuery.buildEsQuery(seriesIndex.indexPattern, queries, filters, esQueryConfig); const timerange = { range: { @@ -33,7 +33,7 @@ export function query(req, panel, esQueryConfig, indexPattern) { doc.query.bool.must.push(timerange); if (panel.filter) { doc.query.bool.must.push( - esQuery.buildEsQuery(indexPattern, [panel.filter], [], esQueryConfig) + esQuery.buildEsQuery(seriesIndex.indexPattern, [panel.filter], [], esQueryConfig) ); } diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/sibling_buckets.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/sibling_buckets.js index 5539f16df41e0..9b4b0f244fc2c 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/sibling_buckets.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/sibling_buckets.js @@ -13,10 +13,10 @@ import { getIntervalAndTimefield } from '../../get_interval_and_timefield'; import { calculateAggRoot } from './calculate_agg_root'; import { UI_SETTINGS } from '../../../../../../data/common'; -export function siblingBuckets(req, panel, esQueryConfig, indexPattern, capabilities, uiSettings) { +export function siblingBuckets(req, panel, esQueryConfig, seriesIndex, capabilities, uiSettings) { return (next) => async (doc) => { const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET); - const { interval } = getIntervalAndTimefield(panel, {}, indexPattern); + const { interval } = getIntervalAndTimefield(panel, {}, seriesIndex); const { bucketSize } = getBucketSize(req, interval, capabilities, barTargetUiSettings); panel.series.forEach((column) => { diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/split_by_everything.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/split_by_everything.js index 595d49ebbd836..cda022294507f 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/split_by_everything.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/split_by_everything.js @@ -9,7 +9,7 @@ import { overwrite } from '../../helpers'; import { esQuery } from '../../../../../../data/server'; -export function splitByEverything(req, panel, esQueryConfig, indexPattern) { +export function splitByEverything(req, panel, esQueryConfig, seriesIndex) { return (next) => (doc) => { panel.series .filter((c) => !(c.aggregate_by && c.aggregate_function)) @@ -18,7 +18,7 @@ export function splitByEverything(req, panel, esQueryConfig, indexPattern) { overwrite( doc, `aggs.pivot.aggs.${column.id}.filter`, - esQuery.buildEsQuery(indexPattern, [column.filter], [], esQueryConfig) + esQuery.buildEsQuery(seriesIndex.indexPattern, [column.filter], [], esQueryConfig) ); } else { overwrite(doc, `aggs.pivot.aggs.${column.id}.filter.match_all`, {}); diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/split_by_terms.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/split_by_terms.js index b4e07455be0fb..b3afc334ac2dd 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/split_by_terms.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/split_by_terms.js @@ -9,7 +9,7 @@ import { overwrite } from '../../helpers'; import { esQuery } from '../../../../../../data/server'; -export function splitByTerms(req, panel, esQueryConfig, indexPattern) { +export function splitByTerms(req, panel, esQueryConfig, seriesIndex) { return (next) => (doc) => { panel.series .filter((c) => c.aggregate_by && c.aggregate_function) @@ -21,7 +21,7 @@ export function splitByTerms(req, panel, esQueryConfig, indexPattern) { overwrite( doc, `aggs.pivot.aggs.${column.id}.column_filter.filter`, - esQuery.buildEsQuery(indexPattern, [column.filter], [], esQueryConfig) + esQuery.buildEsQuery(seriesIndex.indexPattern, [column.filter], [], esQueryConfig) ); } }); diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/series_agg.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/series_agg.js index ba0271ba286a1..a803439c7581f 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/series_agg.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/series_agg.js @@ -5,9 +5,8 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ - +import { last, first } from 'lodash'; import { SeriesAgg } from './_series_agg'; -import _ from 'lodash'; import { getDefaultDecoration } from '../../helpers/get_default_decoration'; import { calculateLabel } from '../../../../../common/calculate_label'; @@ -33,15 +32,14 @@ export function seriesAgg(resp, panel, series, meta, extractFields) { return (fn && fn(acc)) || acc; }, targetSeries); - const fieldsForMetaIndex = meta.index ? await extractFields(meta.index) : []; + const fieldsForSeries = meta.index ? await extractFields({ id: meta.index }) : []; results.push({ id: `${series.id}`, label: - series.label || - calculateLabel(_.last(series.metrics), series.metrics, fieldsForMetaIndex), + series.label || calculateLabel(last(series.metrics), series.metrics, fieldsForSeries), color: series.color, - data: _.first(data), + data: first(data), ...decoration, }); } diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/table/series_agg.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/table/series_agg.js index 9af05afd41182..ae4968e007b1d 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/table/series_agg.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/table/series_agg.js @@ -7,7 +7,7 @@ */ import { SeriesAgg } from './_series_agg'; -import _ from 'lodash'; +import { last, first } from 'lodash'; import { calculateLabel } from '../../../../../common/calculate_label'; export function seriesAgg(resp, panel, series, meta, extractFields) { @@ -25,15 +25,13 @@ export function seriesAgg(resp, panel, series, meta, extractFields) { }); const fn = SeriesAgg[series.aggregate_function]; const data = fn(targetSeries); - - const fieldsForMetaIndex = meta.index ? await extractFields(meta.index) : []; + const fieldsForSeries = meta.index ? await extractFields({ id: meta.index }) : []; results.push({ id: `${series.id}`, label: - series.label || - calculateLabel(_.last(series.metrics), series.metrics, fieldsForMetaIndex), - data: _.first(data), + series.label || calculateLabel(last(series.metrics), series.metrics, fieldsForSeries), + data: first(data), }); } return next(results); diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/series/build_request_body.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/series/build_request_body.ts index bab3abe13bcb0..bc046cbdcf8aa 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/series/build_request_body.ts +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/series/build_request_body.ts @@ -18,7 +18,7 @@ import { processors } from '../request_processors/series/index'; * panel: {Object} - a panel object, * series: {Object} - an series object, * esQueryConfig: {Object} - es query config object, - * indexPatternObject: {Object} - an index pattern object, + * seriesIndex: {Object} - an index pattern object, * capabilities: {Object} - a search capabilities object * ] * @returns {Object} doc - processed body diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/series/get_request_params.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/series/get_request_params.ts index 1f2735da8fb06..827df30dacf6d 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/series/get_request_params.ts +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/series/get_request_params.ts @@ -39,7 +39,7 @@ export async function getSeriesRequestParams( panel, series, esQueryConfig, - seriesIndex.indexPattern, + seriesIndex, capabilities, uiSettings ); @@ -48,6 +48,7 @@ export async function getSeriesRequestParams( index: seriesIndex.indexPatternString, body: { ...request, + runtime_mappings: seriesIndex.indexPattern?.getComputedFields().runtimeFields ?? {}, timeout: esShardTimeout > 0 ? `${esShardTimeout}ms` : undefined, }, }; diff --git a/src/plugins/visualizations/public/components/visualization_container.tsx b/src/plugins/visualizations/public/components/visualization_container.tsx index 3081c39530d75..063715b6438eb 100644 --- a/src/plugins/visualizations/public/components/visualization_container.tsx +++ b/src/plugins/visualizations/public/components/visualization_container.tsx @@ -10,6 +10,7 @@ import React, { ReactNode, Suspense } from 'react'; import { EuiLoadingChart } from '@elastic/eui'; import classNames from 'classnames'; import { VisualizationNoResults } from './visualization_noresults'; +import { VisualizationError } from './visualization_error'; import { IInterpreterRenderHandlers } from '../../../expressions/common'; interface VisualizationContainerProps { @@ -18,6 +19,7 @@ interface VisualizationContainerProps { children: ReactNode; handlers: IInterpreterRenderHandlers; showNoResult?: boolean; + error?: string; } export const VisualizationContainer = ({ @@ -26,6 +28,7 @@ export const VisualizationContainer = ({ children, handlers, showNoResult = false, + error, }: VisualizationContainerProps) => { const classes = classNames('visualization', className); @@ -38,7 +41,13 @@ export const VisualizationContainer = ({ return (
- {showNoResult ? handlers.done()} /> : children} + {error ? ( + handlers.done()} error={error} /> + ) : showNoResult ? ( + handlers.done()} /> + ) : ( + children + )}
); diff --git a/src/plugins/visualizations/public/components/visualization_error.tsx b/src/plugins/visualizations/public/components/visualization_error.tsx new file mode 100644 index 0000000000000..81600a4e3601c --- /dev/null +++ b/src/plugins/visualizations/public/components/visualization_error.tsx @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { EuiEmptyPrompt } from '@elastic/eui'; +import React from 'react'; + +interface VisualizationNoResultsProps { + onInit?: () => void; + error: string; +} + +export class VisualizationError extends React.Component { + public render() { + return ( + {this.props.error}

} + /> + ); + } + + public componentDidMount() { + this.afterRender(); + } + + public componentDidUpdate() { + this.afterRender(); + } + + private afterRender() { + if (this.props.onInit) { + this.props.onInit(); + } + } +} diff --git a/x-pack/test/functional/apps/rollup_job/tsvb.js b/x-pack/test/functional/apps/rollup_job/tsvb.js index d0c7c86d6d5c3..891805acb3256 100644 --- a/x-pack/test/functional/apps/rollup_job/tsvb.js +++ b/x-pack/test/functional/apps/rollup_job/tsvb.js @@ -83,6 +83,7 @@ export default function ({ getService, getPageObjects }) { ); await PageObjects.visualBuilder.clickPanelOptions('metric'); await PageObjects.visualBuilder.setIndexPatternValue(rollupTargetIndexName, false); + await PageObjects.visualBuilder.selectIndexPatternTimeField('@timestamp'); await PageObjects.visualBuilder.setMetricsDataTimerangeMode('Last value'); await PageObjects.visualBuilder.setIntervalValue('1d'); await PageObjects.visualBuilder.setDropLastBucket(false);