diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index b62b728beca35f..05a4141483587f 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -293,6 +293,7 @@ import { convertIPRangeToString, intervalOptions, // only used in Discover isDateHistogramBucketAggConfig, + isNumberType, isStringType, isType, parentPipelineType, @@ -392,6 +393,7 @@ export const search = { InvalidEsCalendarIntervalError, InvalidEsIntervalFormatError, isDateHistogramBucketAggConfig, + isNumberType, isStringType, isType, isValidEsInterval, diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 427c4f7864554d..6383f618641469 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -1545,8 +1545,9 @@ export const search: { InvalidEsCalendarIntervalError: typeof InvalidEsCalendarIntervalError; InvalidEsIntervalFormatError: typeof InvalidEsIntervalFormatError; isDateHistogramBucketAggConfig: typeof isDateHistogramBucketAggConfig; + isNumberType: (agg: import("./search").AggConfig) => boolean; isStringType: (agg: import("./search").AggConfig) => boolean; - isType: (type: string) => (agg: import("./search").AggConfig) => boolean; + isType: (...types: string[]) => (agg: import("./search").AggConfig) => boolean; isValidEsInterval: typeof isValidEsInterval; isValidInterval: typeof isValidInterval; parentPipelineType: string; @@ -1874,21 +1875,21 @@ export type TSearchStrategyProvider = (context: ISearc // src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "getRoutes" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:382:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:382:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:382:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:382:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:387:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:388:1 - (ae-forgotten-export) The symbol "convertDateRangeToString" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:390:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:399:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:400:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:401:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:404:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:405:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:408:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:409:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:412:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:383:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:383:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:383:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:383:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:388:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:389:1 - (ae-forgotten-export) The symbol "convertDateRangeToString" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:391:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:400:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:401:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:402:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:406:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:407:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:410:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:411:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:414:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts // src/plugins/data/public/query/state_sync/connect_to_query_state.ts:33:33 - (ae-forgotten-export) The symbol "FilterStateStore" needs to be exported by the entry point index.d.ts // src/plugins/data/public/query/state_sync/connect_to_query_state.ts:37:1 - (ae-forgotten-export) The symbol "QueryStateChange" needs to be exported by the entry point index.d.ts // src/plugins/data/public/types.ts:52:5 - (ae-forgotten-export) The symbol "createFiltersFromEvent" needs to be exported by the entry point index.d.ts diff --git a/src/plugins/data/public/search/aggs/buckets/migrate_include_exclude_format.ts b/src/plugins/data/public/search/aggs/buckets/migrate_include_exclude_format.ts index 0beeb1c3722752..116f8cfad60f6d 100644 --- a/src/plugins/data/public/search/aggs/buckets/migrate_include_exclude_format.ts +++ b/src/plugins/data/public/search/aggs/buckets/migrate_include_exclude_format.ts @@ -21,20 +21,22 @@ import { isString, isObject } from 'lodash'; import { IBucketAggConfig, BucketAggType, BucketAggParam } from './bucket_agg_type'; import { IAggConfig } from '../agg_config'; -export const isType = (type: string) => { +export const isType = (...types: string[]) => { return (agg: IAggConfig): boolean => { const field = agg.params.field; - return field && field.type === type; + return types.some(type => field && field.type === type); }; }; +export const isNumberType = isType('number'); export const isStringType = isType('string'); +export const isStringOrNumberType = isType('string', 'number'); export const migrateIncludeExcludeFormat = { serialize(this: BucketAggParam, value: any, agg: IBucketAggConfig) { if (this.shouldShow && !this.shouldShow(agg)) return; - if (!value || isString(value)) return value; + if (!value || isString(value) || Array.isArray(value)) return value; else return value.pattern; }, write( @@ -44,7 +46,12 @@ export const migrateIncludeExcludeFormat = { ) { const value = aggConfig.getParam(this.name); - if (isObject(value)) { + if (Array.isArray(value) && value.length > 0 && isNumberType(aggConfig)) { + const parsedValue = value.filter((val): val is number => Number.isFinite(val)); + if (parsedValue.length) { + output.params[this.name] = parsedValue; + } + } else if (isObject(value)) { output.params[this.name] = value.pattern; } else if (value && isStringType(aggConfig)) { output.params[this.name] = value; diff --git a/src/plugins/data/public/search/aggs/buckets/terms.test.ts b/src/plugins/data/public/search/aggs/buckets/terms.test.ts index 0dc052bd1fdf6d..9769efb6da7494 100644 --- a/src/plugins/data/public/search/aggs/buckets/terms.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/terms.test.ts @@ -75,5 +75,65 @@ describe('Terms Agg', () => { expect(params.include).toBe('404'); expect(params.exclude).toBe('400'); }); + + test('accepts string from string field type and writes this value', () => { + const aggConfigs = getAggConfigs({ + include: 'include value', + exclude: 'exclude value', + field: { + name: 'string_field', + type: 'string', + }, + orderAgg: { + type: 'count', + }, + }); + + const { [BUCKET_TYPES.TERMS]: params } = aggConfigs.aggs[0].toDsl(); + + expect(params.field).toBe('string_field'); + expect(params.include).toBe('include value'); + expect(params.exclude).toBe('exclude value'); + }); + + test('accepts empty array from number field type and does not write a value', () => { + const aggConfigs = getAggConfigs({ + include: [], + exclude: [], + field: { + name: 'empty_number_field', + type: 'number', + }, + orderAgg: { + type: 'count', + }, + }); + + const { [BUCKET_TYPES.TERMS]: params } = aggConfigs.aggs[0].toDsl(); + + expect(params.field).toBe('empty_number_field'); + expect(params.include).toBe(undefined); + expect(params.exclude).toBe(undefined); + }); + + test('filters array with empty strings from number field type and writes only numbers', () => { + const aggConfigs = getAggConfigs({ + include: [1.1, 2, '', 3.33, ''], + exclude: ['', 4, 5.555, '', 6], + field: { + name: 'number_field', + type: 'number', + }, + orderAgg: { + type: 'count', + }, + }); + + const { [BUCKET_TYPES.TERMS]: params } = aggConfigs.aggs[0].toDsl(); + + expect(params.field).toBe('number_field'); + expect(params.include).toStrictEqual([1.1, 2, 3.33]); + expect(params.exclude).toStrictEqual([4, 5.555, 6]); + }); }); }); diff --git a/src/plugins/data/public/search/aggs/buckets/terms.ts b/src/plugins/data/public/search/aggs/buckets/terms.ts index 5baa38af0e8d61..698e0dfb1d3404 100644 --- a/src/plugins/data/public/search/aggs/buckets/terms.ts +++ b/src/plugins/data/public/search/aggs/buckets/terms.ts @@ -22,7 +22,10 @@ import { i18n } from '@kbn/i18n'; import { BucketAggType, IBucketAggConfig } from './bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; import { createFilterTerms } from './create_filter/terms'; -import { isStringType, migrateIncludeExcludeFormat } from './migrate_include_exclude_format'; +import { + isStringOrNumberType, + migrateIncludeExcludeFormat, +} from './migrate_include_exclude_format'; import { IAggConfigs } from '../agg_configs'; import { Adapters } from '../../../../../inspector/public'; @@ -266,7 +269,7 @@ export const getTermsBucketAgg = ({ getInternalStartServices }: TermsBucketAggDe }), type: 'string', advanced: true, - shouldShow: isStringType, + shouldShow: isStringOrNumberType, ...migrateIncludeExcludeFormat, }, { @@ -276,7 +279,7 @@ export const getTermsBucketAgg = ({ getInternalStartServices }: TermsBucketAggDe }), type: 'string', advanced: true, - shouldShow: isStringType, + shouldShow: isStringOrNumberType, ...migrateIncludeExcludeFormat, }, ], diff --git a/src/plugins/vis_default_editor/public/components/agg_params_map.ts b/src/plugins/vis_default_editor/public/components/agg_params_map.ts index 5af3cfc5b09289..9bc3146b9903bb 100644 --- a/src/plugins/vis_default_editor/public/components/agg_params_map.ts +++ b/src/plugins/vis_default_editor/public/components/agg_params_map.ts @@ -58,6 +58,8 @@ const buckets = { size: controls.SizeParamEditor, }, [BUCKET_TYPES.TERMS]: { + include: controls.IncludeExcludeParamEditor, + exclude: controls.IncludeExcludeParamEditor, orderBy: controls.OrderByParamEditor, orderAgg: controls.OrderAggParamEditor, order: wrapWithInlineComp(controls.OrderParamEditor), diff --git a/src/plugins/vis_default_editor/public/components/controls/components/number_list/utils.ts b/src/plugins/vis_default_editor/public/components/controls/components/number_list/utils.ts index dac86249ebbb99..5cb594ade8dba7 100644 --- a/src/plugins/vis_default_editor/public/components/controls/components/number_list/utils.ts +++ b/src/plugins/vis_default_editor/public/components/controls/components/number_list/utils.ts @@ -119,7 +119,7 @@ function getNextModel(list: NumberRowModel[], range: NumberListRange): NumberRow }; } -function getInitModelList(list: Array): NumberRowModel[] { +function getInitModelList(list: Array): NumberRowModel[] { return list.length ? list.map(num => ({ value: (num === undefined ? EMPTY_STRING : num) as NumberRowModel['value'], diff --git a/src/plugins/vis_default_editor/public/components/controls/components/simple_number_list.tsx b/src/plugins/vis_default_editor/public/components/controls/components/simple_number_list.tsx new file mode 100644 index 00000000000000..becf8e47ef5736 --- /dev/null +++ b/src/plugins/vis_default_editor/public/components/controls/components/simple_number_list.tsx @@ -0,0 +1,140 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { Fragment, useCallback, useEffect, useMemo, useState } from 'react'; +import { isArray } from 'lodash'; +import { EuiButtonEmpty, EuiFlexItem, EuiFormRow, EuiSpacer, htmlIdGenerator } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EMPTY_STRING, getInitModelList, getRange, parse } from './number_list/utils'; +import { NumberRow, NumberRowModel } from './number_list/number_row'; +import { AggParamEditorProps } from '../../agg_param_props'; + +const generateId = htmlIdGenerator(); + +function SimpleNumberList({ + agg, + aggParam, + value, + setValue, + setTouched, +}: AggParamEditorProps>) { + const [numbers, setNumbers] = useState( + getInitModelList(value && isArray(value) ? value : [EMPTY_STRING]) + ); + const numberRange = useMemo(() => getRange('[-Infinity,Infinity]'), []); + + // This useEffect is needed to discard changes, it sets numbers a mapped value if they are different + useEffect(() => { + if ( + isArray(value) && + (value.length !== numbers.length || + !value.every((numberValue, index) => numberValue === numbers[index].value)) + ) { + setNumbers( + value.map(numberValue => ({ + id: generateId(), + value: numberValue, + isInvalid: false, + })) + ); + } + }, [numbers, value]); + + const onUpdate = useCallback( + (numberList: NumberRowModel[]) => { + setNumbers(numberList); + setValue(numberList.map(({ value: numberValue }) => numberValue)); + }, + [setValue] + ); + + const onChangeValue = useCallback( + (numberField: { id: string; value: string }) => { + onUpdate( + numbers.map(number => + number.id === numberField.id + ? { + id: numberField.id, + value: parse(numberField.value), + isInvalid: false, + } + : number + ) + ); + }, + [numbers, onUpdate] + ); + + // Add an item to the end of the list + const onAdd = useCallback(() => { + const newArray = [ + ...numbers, + { + id: generateId(), + value: EMPTY_STRING as '', + isInvalid: false, + }, + ]; + onUpdate(newArray); + }, [numbers, onUpdate]); + + const onDelete = useCallback( + (id: string) => onUpdate(numbers.filter(number => number.id !== id)), + [numbers, onUpdate] + ); + + return ( + + <> + {numbers.map((number, arrayIndex) => ( + + + {numbers.length - 1 !== arrayIndex && } + + ))} + + + + + + + + + ); +} + +export { SimpleNumberList }; diff --git a/src/plugins/vis_default_editor/public/components/controls/include_exclude.tsx b/src/plugins/vis_default_editor/public/components/controls/include_exclude.tsx new file mode 100644 index 00000000000000..f60f6ce7ce249b --- /dev/null +++ b/src/plugins/vis_default_editor/public/components/controls/include_exclude.tsx @@ -0,0 +1,49 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { useEffect } from 'react'; +import { AggParamEditorProps } from '../agg_param_props'; +import { StringParamEditor } from './string'; +import { search } from '../../../../data/public'; +import { SimpleNumberList } from './components/simple_number_list'; +const { isNumberType } = search.aggs; + +export function IncludeExcludeParamEditor(props: AggParamEditorProps>) { + const { agg, value, setValue } = props; + const isAggOfNumberType = isNumberType(agg); + + // This useEffect converts value from string type to number and back when the field type is changed + useEffect(() => { + if (isAggOfNumberType && !Array.isArray(value) && value !== undefined) { + const numberArray = value + .split('|') + .map(item => parseFloat(item)) + .filter(number => Number.isFinite(number)); + setValue(numberArray.length ? numberArray : ['']); + } else if (!isAggOfNumberType && Array.isArray(value) && value !== undefined) { + setValue(value.filter(item => item !== '').join('|')); + } + }, [isAggOfNumberType, setValue, value]); + + return isAggOfNumberType ? ( + } /> + ) : ( + + ); +} diff --git a/src/plugins/vis_default_editor/public/components/controls/index.ts b/src/plugins/vis_default_editor/public/components/controls/index.ts index e8944aa667853f..cfb236e5e22e37 100644 --- a/src/plugins/vis_default_editor/public/components/controls/index.ts +++ b/src/plugins/vis_default_editor/public/components/controls/index.ts @@ -24,6 +24,7 @@ export { ExtendedBoundsParamEditor } from './extended_bounds'; export { FieldParamEditor } from './field'; export { FiltersParamEditor } from './filters'; export { HasExtendedBoundsParamEditor } from './has_extended_bounds'; +export { IncludeExcludeParamEditor } from './include_exclude'; export { IpRangesParamEditor } from './ip_ranges'; export { IpRangeTypeParamEditor } from './ip_range_type'; export { IsFilteredByCollarParamEditor } from './is_filtered_by_collar'; diff --git a/src/plugins/vis_type_timeseries/server/saved_objects/index.ts b/src/plugins/vis_type_timeseries/server/saved_objects/index.ts new file mode 100644 index 00000000000000..5f7f5767f423de --- /dev/null +++ b/src/plugins/vis_type_timeseries/server/saved_objects/index.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { tsvbTelemetrySavedObjectType } from './tsvb_telemetry'; diff --git a/src/plugins/vis_type_timeseries/server/validation_telemetry/saved_object_type.ts b/src/plugins/vis_type_timeseries/server/saved_objects/tsvb_telemetry.ts similarity index 100% rename from src/plugins/vis_type_timeseries/server/validation_telemetry/saved_object_type.ts rename to src/plugins/vis_type_timeseries/server/saved_objects/tsvb_telemetry.ts diff --git a/src/plugins/vis_type_timeseries/server/validation_telemetry/validation_telemetry_service.ts b/src/plugins/vis_type_timeseries/server/validation_telemetry/validation_telemetry_service.ts index 779d9441df2fd9..e4b8ca19094e48 100644 --- a/src/plugins/vis_type_timeseries/server/validation_telemetry/validation_telemetry_service.ts +++ b/src/plugins/vis_type_timeseries/server/validation_telemetry/validation_telemetry_service.ts @@ -19,7 +19,7 @@ import { APICaller, CoreSetup, Plugin, PluginInitializerContext } from 'kibana/server'; import { UsageCollectionSetup } from '../../../usage_collection/server'; -import { tsvbTelemetrySavedObjectType } from './saved_object_type'; +import { tsvbTelemetrySavedObjectType } from '../saved_objects'; export interface ValidationTelemetryServiceSetup { logFailedValidation: () => void;