Support for number, date and IP range data types (#76971) (#94001)

* Support for number, date and IP ranges

* Update tests

* Ranges don't work with range agg

* Fix test case

* Allow Discover to create range filters

* Supports ranges in Visualize, KQL, remove Lens support

* Fix test mappings

* Bring back field cache to work around bug

* Fix some tests

* Fix test expectation

* Respond to review comments

* Fix type error

* Remove added sample data

* Fix api_docs

* Fix test
This commit is contained in:
Wylie Conlon 2021-03-08 18:03:26 -05:00 committed by GitHub
parent b34542b46a
commit 5c001f8443
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
49 changed files with 532 additions and 59 deletions

View file

@ -11393,7 +11393,7 @@
"description": [],
"source": {
"path": "src/plugins/data/common/kbn_field_types/types.ts",
"lineNumber": 57
"lineNumber": 64
},
"initialIsOpen": false
},
@ -20561,7 +20561,7 @@
"description": [],
"source": {
"path": "src/plugins/data/common/kbn_field_types/types.ts",
"lineNumber": 57
"lineNumber": 64
},
"initialIsOpen": false
},
@ -27151,7 +27151,7 @@
"description": [],
"source": {
"path": "src/plugins/data/common/kbn_field_types/types.ts",
"lineNumber": 57
"lineNumber": 64
},
"initialIsOpen": false
}

View file

@ -3306,7 +3306,7 @@
"description": [],
"source": {
"path": "src/plugins/data/common/field_formats/converters/string.ts",
"lineNumber": 80
"lineNumber": 83
},
"signature": [
"({ kind: boolean; text: string; } | { kind: string; text: string; })[]"
@ -3325,7 +3325,7 @@
"returnComment": [],
"source": {
"path": "src/plugins/data/common/field_formats/converters/string.ts",
"lineNumber": 82
"lineNumber": 85
}
},
{
@ -3342,7 +3342,7 @@
"description": [],
"source": {
"path": "src/plugins/data/common/field_formats/converters/string.ts",
"lineNumber": 102
"lineNumber": 105
}
}
],
@ -3353,7 +3353,7 @@
"label": "textConvert",
"source": {
"path": "src/plugins/data/common/field_formats/converters/string.ts",
"lineNumber": 102
"lineNumber": 105
},
"tags": [],
"returnComment": []

View file

@ -887,7 +887,7 @@
"description": [],
"source": {
"path": "src/plugins/kibana_react/public/field_icon/field_icon.tsx",
"lineNumber": 53
"lineNumber": 59
}
}
],
@ -895,7 +895,7 @@
"returnComment": [],
"source": {
"path": "src/plugins/kibana_react/public/field_icon/field_icon.tsx",
"lineNumber": 53
"lineNumber": 59
},
"initialIsOpen": false
},
@ -2066,7 +2066,7 @@
"description": [],
"source": {
"path": "src/plugins/kibana_react/public/field_icon/field_icon.tsx",
"lineNumber": 27
"lineNumber": 30
},
"signature": [
"string | undefined"
@ -2080,7 +2080,7 @@
"description": [],
"source": {
"path": "src/plugins/kibana_react/public/field_icon/field_icon.tsx",
"lineNumber": 28
"lineNumber": 31
},
"signature": [
"boolean | undefined"
@ -3357,7 +3357,7 @@
"description": [],
"source": {
"path": "src/plugins/kibana_react/public/field_icon/field_icon.tsx",
"lineNumber": 35
"lineNumber": 38
}
}
],
@ -3365,7 +3365,7 @@
"label": "boolean",
"source": {
"path": "src/plugins/kibana_react/public/field_icon/field_icon.tsx",
"lineNumber": 35
"lineNumber": 38
}
},
{
@ -3381,7 +3381,7 @@
"description": [],
"source": {
"path": "src/plugins/kibana_react/public/field_icon/field_icon.tsx",
"lineNumber": 37
"lineNumber": 40
}
},
{
@ -3392,7 +3392,7 @@
"description": [],
"source": {
"path": "src/plugins/kibana_react/public/field_icon/field_icon.tsx",
"lineNumber": 37
"lineNumber": 40
}
},
{
@ -3403,7 +3403,7 @@
"description": [],
"source": {
"path": "src/plugins/kibana_react/public/field_icon/field_icon.tsx",
"lineNumber": 37
"lineNumber": 40
},
"signature": [
"\"square\""
@ -3416,7 +3416,7 @@
"label": "conflict",
"source": {
"path": "src/plugins/kibana_react/public/field_icon/field_icon.tsx",
"lineNumber": 37
"lineNumber": 40
}
},
{
@ -3432,7 +3432,7 @@
"description": [],
"source": {
"path": "src/plugins/kibana_react/public/field_icon/field_icon.tsx",
"lineNumber": 38
"lineNumber": 41
}
}
],
@ -3440,7 +3440,31 @@
"label": "date",
"source": {
"path": "src/plugins/kibana_react/public/field_icon/field_icon.tsx",
"lineNumber": 38
"lineNumber": 41
}
},
{
"id": "def-public.typeToEuiIconMap.date_range",
"type": "Object",
"tags": [],
"children": [
{
"tags": [],
"id": "def-public.typeToEuiIconMap.date_range.iconType",
"type": "string",
"label": "iconType",
"description": [],
"source": {
"path": "src/plugins/kibana_react/public/field_icon/field_icon.tsx",
"lineNumber": 42
}
}
],
"description": [],
"label": "date_range",
"source": {
"path": "src/plugins/kibana_react/public/field_icon/field_icon.tsx",
"lineNumber": 42
}
},
{
@ -3456,7 +3480,7 @@
"description": [],
"source": {
"path": "src/plugins/kibana_react/public/field_icon/field_icon.tsx",
"lineNumber": 39
"lineNumber": 43
}
}
],
@ -3464,7 +3488,7 @@
"label": "geo_point",
"source": {
"path": "src/plugins/kibana_react/public/field_icon/field_icon.tsx",
"lineNumber": 39
"lineNumber": 43
}
},
{
@ -3480,7 +3504,7 @@
"description": [],
"source": {
"path": "src/plugins/kibana_react/public/field_icon/field_icon.tsx",
"lineNumber": 40
"lineNumber": 44
}
}
],
@ -3488,7 +3512,7 @@
"label": "geo_shape",
"source": {
"path": "src/plugins/kibana_react/public/field_icon/field_icon.tsx",
"lineNumber": 40
"lineNumber": 44
}
},
{
@ -3504,7 +3528,7 @@
"description": [],
"source": {
"path": "src/plugins/kibana_react/public/field_icon/field_icon.tsx",
"lineNumber": 41
"lineNumber": 45
}
}
],
@ -3512,7 +3536,31 @@
"label": "ip",
"source": {
"path": "src/plugins/kibana_react/public/field_icon/field_icon.tsx",
"lineNumber": 41
"lineNumber": 45
}
},
{
"id": "def-public.typeToEuiIconMap.ip_range",
"type": "Object",
"tags": [],
"children": [
{
"tags": [],
"id": "def-public.typeToEuiIconMap.ip_range.iconType",
"type": "string",
"label": "iconType",
"description": [],
"source": {
"path": "src/plugins/kibana_react/public/field_icon/field_icon.tsx",
"lineNumber": 46
}
}
],
"description": [],
"label": "ip_range",
"source": {
"path": "src/plugins/kibana_react/public/field_icon/field_icon.tsx",
"lineNumber": 46
}
},
{
@ -3528,7 +3576,7 @@
"description": [],
"source": {
"path": "src/plugins/kibana_react/public/field_icon/field_icon.tsx",
"lineNumber": 43
"lineNumber": 48
}
}
],
@ -3538,7 +3586,7 @@
"label": "murmur3",
"source": {
"path": "src/plugins/kibana_react/public/field_icon/field_icon.tsx",
"lineNumber": 43
"lineNumber": 48
}
},
{
@ -3554,7 +3602,7 @@
"description": [],
"source": {
"path": "src/plugins/kibana_react/public/field_icon/field_icon.tsx",
"lineNumber": 44
"lineNumber": 49
}
}
],
@ -3562,7 +3610,31 @@
"label": "number",
"source": {
"path": "src/plugins/kibana_react/public/field_icon/field_icon.tsx",
"lineNumber": 44
"lineNumber": 49
}
},
{
"id": "def-public.typeToEuiIconMap.number_range",
"type": "Object",
"tags": [],
"children": [
{
"tags": [],
"id": "def-public.typeToEuiIconMap.number_range.iconType",
"type": "string",
"label": "iconType",
"description": [],
"source": {
"path": "src/plugins/kibana_react/public/field_icon/field_icon.tsx",
"lineNumber": 50
}
}
],
"description": [],
"label": "number_range",
"source": {
"path": "src/plugins/kibana_react/public/field_icon/field_icon.tsx",
"lineNumber": 50
}
},
{
@ -3578,7 +3650,7 @@
"description": [],
"source": {
"path": "src/plugins/kibana_react/public/field_icon/field_icon.tsx",
"lineNumber": 45
"lineNumber": 51
}
},
{
@ -3589,7 +3661,7 @@
"description": [],
"source": {
"path": "src/plugins/kibana_react/public/field_icon/field_icon.tsx",
"lineNumber": 45
"lineNumber": 51
}
}
],
@ -3597,7 +3669,7 @@
"label": "_source",
"source": {
"path": "src/plugins/kibana_react/public/field_icon/field_icon.tsx",
"lineNumber": 45
"lineNumber": 51
}
},
{
@ -3613,7 +3685,7 @@
"description": [],
"source": {
"path": "src/plugins/kibana_react/public/field_icon/field_icon.tsx",
"lineNumber": 46
"lineNumber": 52
}
}
],
@ -3621,7 +3693,7 @@
"label": "string",
"source": {
"path": "src/plugins/kibana_react/public/field_icon/field_icon.tsx",
"lineNumber": 46
"lineNumber": 52
}
},
{
@ -3637,7 +3709,7 @@
"description": [],
"source": {
"path": "src/plugins/kibana_react/public/field_icon/field_icon.tsx",
"lineNumber": 47
"lineNumber": 53
}
}
],
@ -3645,7 +3717,7 @@
"label": "nested",
"source": {
"path": "src/plugins/kibana_react/public/field_icon/field_icon.tsx",
"lineNumber": 47
"lineNumber": 53
}
}
],
@ -3653,7 +3725,7 @@
"label": "typeToEuiIconMap",
"source": {
"path": "src/plugins/kibana_react/public/field_icon/field_icon.tsx",
"lineNumber": 34
"lineNumber": 37
},
"initialIsOpen": false
}

View file

@ -79,7 +79,7 @@
"description": [],
"source": {
"path": "x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/cardinality.tsx",
"lineNumber": 34
"lineNumber": 43
},
"signature": [
"\"cardinality\""
@ -88,7 +88,7 @@
],
"source": {
"path": "x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/cardinality.tsx",
"lineNumber": 31
"lineNumber": 40
},
"initialIsOpen": false
},

View file

@ -25,16 +25,22 @@ export declare enum ES_FIELD_TYPES
| BYTE | <code>&quot;byte&quot;</code> | |
| DATE | <code>&quot;date&quot;</code> | |
| DATE\_NANOS | <code>&quot;date_nanos&quot;</code> | |
| DATE\_RANGE | <code>&quot;date_range&quot;</code> | |
| DOUBLE | <code>&quot;double&quot;</code> | |
| DOUBLE\_RANGE | <code>&quot;double_range&quot;</code> | |
| FLOAT | <code>&quot;float&quot;</code> | |
| FLOAT\_RANGE | <code>&quot;float_range&quot;</code> | |
| GEO\_POINT | <code>&quot;geo_point&quot;</code> | |
| GEO\_SHAPE | <code>&quot;geo_shape&quot;</code> | |
| HALF\_FLOAT | <code>&quot;half_float&quot;</code> | |
| HISTOGRAM | <code>&quot;histogram&quot;</code> | |
| INTEGER | <code>&quot;integer&quot;</code> | |
| INTEGER\_RANGE | <code>&quot;integer_range&quot;</code> | |
| IP | <code>&quot;ip&quot;</code> | |
| IP\_RANGE | <code>&quot;ip_range&quot;</code> | |
| KEYWORD | <code>&quot;keyword&quot;</code> | |
| LONG | <code>&quot;long&quot;</code> | |
| LONG\_RANGE | <code>&quot;long_range&quot;</code> | |
| MURMUR3 | <code>&quot;murmur3&quot;</code> | |
| NESTED | <code>&quot;nested&quot;</code> | |
| OBJECT | <code>&quot;object&quot;</code> | |

View file

@ -21,13 +21,16 @@ export declare enum KBN_FIELD_TYPES
| BOOLEAN | <code>&quot;boolean&quot;</code> | |
| CONFLICT | <code>&quot;conflict&quot;</code> | |
| DATE | <code>&quot;date&quot;</code> | |
| DATE\_RANGE | <code>&quot;date_range&quot;</code> | |
| GEO\_POINT | <code>&quot;geo_point&quot;</code> | |
| GEO\_SHAPE | <code>&quot;geo_shape&quot;</code> | |
| HISTOGRAM | <code>&quot;histogram&quot;</code> | |
| IP | <code>&quot;ip&quot;</code> | |
| IP\_RANGE | <code>&quot;ip_range&quot;</code> | |
| MURMUR3 | <code>&quot;murmur3&quot;</code> | |
| NESTED | <code>&quot;nested&quot;</code> | |
| NUMBER | <code>&quot;number&quot;</code> | |
| NUMBER\_RANGE | <code>&quot;number_range&quot;</code> | |
| OBJECT | <code>&quot;object&quot;</code> | |
| STRING | <code>&quot;string&quot;</code> | |
| UNKNOWN | <code>&quot;unknown&quot;</code> | |

View file

@ -25,16 +25,22 @@ export declare enum ES_FIELD_TYPES
| BYTE | <code>&quot;byte&quot;</code> | |
| DATE | <code>&quot;date&quot;</code> | |
| DATE\_NANOS | <code>&quot;date_nanos&quot;</code> | |
| DATE\_RANGE | <code>&quot;date_range&quot;</code> | |
| DOUBLE | <code>&quot;double&quot;</code> | |
| DOUBLE\_RANGE | <code>&quot;double_range&quot;</code> | |
| FLOAT | <code>&quot;float&quot;</code> | |
| FLOAT\_RANGE | <code>&quot;float_range&quot;</code> | |
| GEO\_POINT | <code>&quot;geo_point&quot;</code> | |
| GEO\_SHAPE | <code>&quot;geo_shape&quot;</code> | |
| HALF\_FLOAT | <code>&quot;half_float&quot;</code> | |
| HISTOGRAM | <code>&quot;histogram&quot;</code> | |
| INTEGER | <code>&quot;integer&quot;</code> | |
| INTEGER\_RANGE | <code>&quot;integer_range&quot;</code> | |
| IP | <code>&quot;ip&quot;</code> | |
| IP\_RANGE | <code>&quot;ip_range&quot;</code> | |
| KEYWORD | <code>&quot;keyword&quot;</code> | |
| LONG | <code>&quot;long&quot;</code> | |
| LONG\_RANGE | <code>&quot;long_range&quot;</code> | |
| MURMUR3 | <code>&quot;murmur3&quot;</code> | |
| NESTED | <code>&quot;nested&quot;</code> | |
| OBJECT | <code>&quot;object&quot;</code> | |

View file

@ -21,13 +21,16 @@ export declare enum KBN_FIELD_TYPES
| BOOLEAN | <code>&quot;boolean&quot;</code> | |
| CONFLICT | <code>&quot;conflict&quot;</code> | |
| DATE | <code>&quot;date&quot;</code> | |
| DATE\_RANGE | <code>&quot;date_range&quot;</code> | |
| GEO\_POINT | <code>&quot;geo_point&quot;</code> | |
| GEO\_SHAPE | <code>&quot;geo_shape&quot;</code> | |
| HISTOGRAM | <code>&quot;histogram&quot;</code> | |
| IP | <code>&quot;ip&quot;</code> | |
| IP\_RANGE | <code>&quot;ip_range&quot;</code> | |
| MURMUR3 | <code>&quot;murmur3&quot;</code> | |
| NESTED | <code>&quot;nested&quot;</code> | |
| NUMBER | <code>&quot;number&quot;</code> | |
| NUMBER\_RANGE | <code>&quot;number_range&quot;</code> | |
| OBJECT | <code>&quot;object&quot;</code> | |
| STRING | <code>&quot;string&quot;</code> | |
| UNKNOWN | <code>&quot;unknown&quot;</code> | |

View file

@ -70,6 +70,8 @@ function buildBaseFilter(
case 'range':
const newParams = { gte: params.from, lt: params.to };
return buildRangeFilter(field, newParams, indexPattern);
case 'range_from_value':
return buildRangeFilter(field, params, indexPattern);
case 'exists':
return buildExistsFilter(field, indexPattern);
default:

View file

@ -35,6 +35,7 @@ export enum FILTERS {
MISSING = 'missing',
QUERY_STRING = 'query_string',
RANGE = 'range',
RANGE_FROM_VALUE = 'range_from_value',
GEO_BOUNDING_BOX = 'geo_bounding_box',
GEO_POLYGON = 'geo_polygon',
SPATIAL_FILTER = 'spatial_filter',

View file

@ -66,9 +66,12 @@ export class StringFormat extends FieldFormat {
});
static fieldType = [
KBN_FIELD_TYPES.NUMBER,
KBN_FIELD_TYPES.NUMBER_RANGE,
KBN_FIELD_TYPES.BOOLEAN,
KBN_FIELD_TYPES.DATE,
KBN_FIELD_TYPES.DATE_RANGE,
KBN_FIELD_TYPES.IP,
KBN_FIELD_TYPES.IP_RANGE,
KBN_FIELD_TYPES.ATTACHMENT,
KBN_FIELD_TYPES.GEO_POINT,
KBN_FIELD_TYPES.GEO_SHAPE,

View file

@ -65,4 +65,12 @@ export const stubFields: IFieldType[] = [
searchable: true,
filterable: true,
},
{
name: 'bytes_range',
type: 'number_range',
esTypes: ['integer_range'],
aggregatable: true,
searchable: true,
filterable: true,
},
];

View file

@ -75,13 +75,16 @@ describe('utils/kbn_field_types', () => {
KBN_FIELD_TYPES.BOOLEAN,
KBN_FIELD_TYPES.CONFLICT,
KBN_FIELD_TYPES.DATE,
KBN_FIELD_TYPES.DATE_RANGE,
KBN_FIELD_TYPES.GEO_POINT,
KBN_FIELD_TYPES.GEO_SHAPE,
KBN_FIELD_TYPES.HISTOGRAM,
KBN_FIELD_TYPES.IP,
KBN_FIELD_TYPES.IP_RANGE,
KBN_FIELD_TYPES.MURMUR3,
KBN_FIELD_TYPES.NESTED,
KBN_FIELD_TYPES.NUMBER,
KBN_FIELD_TYPES.NUMBER_RANGE,
KBN_FIELD_TYPES.OBJECT,
KBN_FIELD_TYPES.STRING,
KBN_FIELD_TYPES.UNKNOWN,

View file

@ -43,18 +43,41 @@ export const createKbnFieldTypes = (): KbnFieldType[] => [
ES_FIELD_TYPES.TOKEN_COUNT,
],
}),
new KbnFieldType({
name: KBN_FIELD_TYPES.NUMBER_RANGE,
sortable: true,
filterable: true,
esTypes: [
ES_FIELD_TYPES.FLOAT_RANGE,
ES_FIELD_TYPES.DOUBLE_RANGE,
ES_FIELD_TYPES.INTEGER_RANGE,
ES_FIELD_TYPES.LONG_RANGE,
],
}),
new KbnFieldType({
name: KBN_FIELD_TYPES.DATE,
sortable: true,
filterable: true,
esTypes: [ES_FIELD_TYPES.DATE, ES_FIELD_TYPES.DATE_NANOS],
}),
new KbnFieldType({
name: KBN_FIELD_TYPES.DATE_RANGE,
sortable: true,
filterable: true,
esTypes: [ES_FIELD_TYPES.DATE_RANGE],
}),
new KbnFieldType({
name: KBN_FIELD_TYPES.IP,
sortable: true,
filterable: true,
esTypes: [ES_FIELD_TYPES.IP],
}),
new KbnFieldType({
name: KBN_FIELD_TYPES.IP_RANGE,
sortable: true,
filterable: true,
esTypes: [ES_FIELD_TYPES.IP_RANGE],
}),
new KbnFieldType({
name: KBN_FIELD_TYPES.BOOLEAN,
sortable: true,

View file

@ -30,6 +30,7 @@ export enum ES_FIELD_TYPES {
DATE = 'date',
DATE_NANOS = 'date_nanos',
DATE_RANGE = 'date_range',
GEO_POINT = 'geo_point',
GEO_SHAPE = 'geo_shape',
@ -43,9 +44,15 @@ export enum ES_FIELD_TYPES {
SHORT = 'short',
UNSIGNED_LONG = 'unsigned_long',
FLOAT_RANGE = 'float_range',
DOUBLE_RANGE = 'double_range',
INTEGER_RANGE = 'integer_range',
LONG_RANGE = 'long_range',
NESTED = 'nested',
BYTE = 'byte',
IP = 'ip',
IP_RANGE = 'ip_range',
ATTACHMENT = 'attachment',
TOKEN_COUNT = 'token_count',
MURMUR3 = 'murmur3',
@ -59,11 +66,14 @@ export enum KBN_FIELD_TYPES {
ATTACHMENT = 'attachment',
BOOLEAN = 'boolean',
DATE = 'date',
DATE_RANGE = 'date_range',
GEO_POINT = 'geo_point',
GEO_SHAPE = 'geo_shape',
IP = 'ip',
IP_RANGE = 'ip_range',
MURMUR3 = 'murmur3',
NUMBER = 'number',
NUMBER_RANGE = 'number_range',
STRING = 'string',
UNKNOWN = 'unknown',
CONFLICT = 'conflict',

View file

@ -137,7 +137,7 @@ export const getDateHistogramBucketAgg = ({
{
name: 'field',
type: 'field',
filterFieldTypes: KBN_FIELD_TYPES.DATE,
filterFieldTypes: [KBN_FIELD_TYPES.DATE, KBN_FIELD_TYPES.DATE_RANGE],
default(agg: IBucketDateHistogramAggConfig) {
return agg.getIndexPattern().getTimeField?.()?.name;
},

View file

@ -61,7 +61,7 @@ export const getDateRangeBucketAgg = ({
{
name: 'field',
type: 'field',
filterFieldTypes: KBN_FIELD_TYPES.DATE,
filterFieldTypes: [KBN_FIELD_TYPES.DATE, KBN_FIELD_TYPES.DATE_RANGE],
default(agg: IBucketAggConfig) {
return agg.getIndexPattern().timeFieldName;
},

View file

@ -85,7 +85,7 @@ export const getHistogramBucketAgg = ({
{
name: 'field',
type: 'field',
filterFieldTypes: KBN_FIELD_TYPES.NUMBER,
filterFieldTypes: [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.NUMBER_RANGE],
},
{
/*
@ -105,6 +105,11 @@ export const getHistogramBucketAgg = ({
options: any
) {
const field = aggConfig.getField();
if (field?.type === 'number_range') {
// Can't scale number_histogram requests
return;
}
const aggBody = field.scripted
? { script: { source: field.script, lang: field.lang } }
: { field: field.name };
@ -163,7 +168,9 @@ export const getHistogramBucketAgg = ({
{
name: 'maxBars',
shouldShow(agg) {
return isAutoInterval(get(agg, 'params.interval'));
const field = agg.getField();
// Show this for empty field and number field, but not range
return field?.type !== 'number_range' && isAutoInterval(get(agg, 'params.interval'));
},
write: () => {},
},

View file

@ -85,6 +85,7 @@ export const getRangeBucketAgg = ({ getFieldFormatsStart }: RangeBucketAggDepend
{
name: 'field',
type: 'field',
// number_range is not supported by Elasticsearch
filterFieldTypes: [KBN_FIELD_TYPES.NUMBER],
},
{

View file

@ -640,10 +640,16 @@ export enum ES_FIELD_TYPES {
// (undocumented)
DATE_NANOS = "date_nanos",
// (undocumented)
DATE_RANGE = "date_range",
// (undocumented)
DOUBLE = "double",
// (undocumented)
DOUBLE_RANGE = "double_range",
// (undocumented)
FLOAT = "float",
// (undocumented)
FLOAT_RANGE = "float_range",
// (undocumented)
GEO_POINT = "geo_point",
// (undocumented)
GEO_SHAPE = "geo_shape",
@ -658,12 +664,18 @@ export enum ES_FIELD_TYPES {
// (undocumented)
INTEGER = "integer",
// (undocumented)
INTEGER_RANGE = "integer_range",
// (undocumented)
IP = "ip",
// (undocumented)
IP_RANGE = "ip_range",
// (undocumented)
KEYWORD = "keyword",
// (undocumented)
LONG = "long",
// (undocumented)
LONG_RANGE = "long_range",
// (undocumented)
MURMUR3 = "murmur3",
// (undocumented)
NESTED = "nested",
@ -1753,6 +1765,8 @@ export enum KBN_FIELD_TYPES {
// (undocumented)
DATE = "date",
// (undocumented)
DATE_RANGE = "date_range",
// (undocumented)
GEO_POINT = "geo_point",
// (undocumented)
GEO_SHAPE = "geo_shape",
@ -1761,12 +1775,16 @@ export enum KBN_FIELD_TYPES {
// (undocumented)
IP = "ip",
// (undocumented)
IP_RANGE = "ip_range",
// (undocumented)
MURMUR3 = "murmur3",
// (undocumented)
NESTED = "nested",
// (undocumented)
NUMBER = "number",
// (undocumented)
NUMBER_RANGE = "number_range",
// (undocumented)
OBJECT = "object",
// (undocumented)
_SOURCE = "_source",

View file

@ -17,6 +17,8 @@ import {
buildExistsFilter,
PhraseFilter,
isPhraseFilter,
RangeFilter,
isRangeFilter,
} from '../../../../common';
const INDEX_NAME = 'my-index';
@ -102,6 +104,53 @@ describe('Generate filters', () => {
});
});
it('should create range filter when provided complex range datatype', () => {
const filters = generateFilters(
mockFilterManager,
{
name: 'my-field',
type: 'ip_range',
} as IFieldType,
{
gt: '192.168.0.0',
lte: '192.168.255.255',
},
'+',
INDEX_NAME
);
expect(filters).toHaveLength(1);
expect(filters[0].meta.index === INDEX_NAME);
expect(filters[0].meta.negate).toBeFalsy();
expect(isRangeFilter(filters[0])).toBeTruthy();
expect((filters[0] as RangeFilter).range).toEqual({
[FIELD.name]: {
gt: '192.168.0.0',
lte: '192.168.255.255',
},
});
});
it('should create a phrase filter on a simple range datatype', () => {
const filters = generateFilters(
mockFilterManager,
{
name: 'my-field',
type: 'number_range',
} as IFieldType,
10000,
'+',
INDEX_NAME
);
expect(filters).toHaveLength(1);
expect(filters[0].meta.index === INDEX_NAME);
expect(filters[0].meta.negate).toBeFalsy();
expect(isPhraseFilter(filters[0])).toBeTruthy();
expect((filters[0] as PhraseFilter).query.match_phrase).toEqual({
[FIELD.name]: 10000,
});
});
it('should create multiple phrase filters', () => {
const ANOTHER_PHRASE = 'another-value';
const filters = generateFilters(

View file

@ -90,6 +90,21 @@ export function generateFilters(
if (existing) {
updateExistingFilter(existing, negate);
filter = existing;
} else if (fieldObj.type?.includes('range') && value && typeof value === 'object') {
// When dealing with range fields, the filter type depends on the data passed in. If it's an
// object we assume that it's a min/max value
const tmpIndexPattern = { id: index } as IIndexPattern;
filter = buildFilter(
tmpIndexPattern,
fieldObj,
FILTERS.RANGE_FROM_VALUE,
false,
false,
value,
null,
FilterStateStore.APP_STATE
);
} else {
const tmpIndexPattern = { id: index } as IIndexPattern;
// exists filter special case: fieldname = '_exists' and value = fieldname

View file

@ -123,6 +123,13 @@ describe('Filter editor utils', () => {
const rangeOperator = operatorOptions.find((operator) => operator.type === 'range');
expect(rangeOperator).toBeUndefined();
});
it('does not return phrase for number_range fields', () => {
const [field] = stubFields.filter(({ type }) => type === 'string');
const operatorOptions = getOperatorOptions(field);
const rangeOperator = operatorOptions.find((operator) => operator.type === 'range');
expect(rangeOperator).toBeUndefined();
});
});
describe('isFilterValid', () => {

View file

@ -56,7 +56,7 @@ export const isBetweenOperator = {
}),
type: FILTERS.RANGE,
negate: false,
fieldTypes: ['number', 'date', 'ip'],
fieldTypes: ['number', 'number_range', 'date', 'date_range', 'ip', 'ip_range'],
};
export const isNotBetweenOperator = {
@ -65,7 +65,7 @@ export const isNotBetweenOperator = {
}),
type: FILTERS.RANGE,
negate: true,
fieldTypes: ['number', 'date', 'ip'],
fieldTypes: ['number', 'number_range', 'date', 'date_range', 'ip', 'ip_range'],
};
export const existsOperator = {

View file

@ -42,6 +42,7 @@ class ValueInputTypeUI extends Component<Props> {
);
break;
case 'number':
case 'number_range':
inputElement = (
<EuiFieldNumber
fullWidth={this.props.fullWidth}
@ -54,6 +55,7 @@ class ValueInputTypeUI extends Component<Props> {
);
break;
case 'date':
case 'date_range':
inputElement = (
<EuiFieldText
fullWidth={this.props.fullWidth}
@ -68,6 +70,7 @@ class ValueInputTypeUI extends Component<Props> {
);
break;
case 'ip':
case 'ip_range':
inputElement = (
<EuiFieldText
fullWidth={this.props.fullWidth}

View file

@ -329,10 +329,16 @@ export enum ES_FIELD_TYPES {
// (undocumented)
DATE_NANOS = "date_nanos",
// (undocumented)
DATE_RANGE = "date_range",
// (undocumented)
DOUBLE = "double",
// (undocumented)
DOUBLE_RANGE = "double_range",
// (undocumented)
FLOAT = "float",
// (undocumented)
FLOAT_RANGE = "float_range",
// (undocumented)
GEO_POINT = "geo_point",
// (undocumented)
GEO_SHAPE = "geo_shape",
@ -347,12 +353,18 @@ export enum ES_FIELD_TYPES {
// (undocumented)
INTEGER = "integer",
// (undocumented)
INTEGER_RANGE = "integer_range",
// (undocumented)
IP = "ip",
// (undocumented)
IP_RANGE = "ip_range",
// (undocumented)
KEYWORD = "keyword",
// (undocumented)
LONG = "long",
// (undocumented)
LONG_RANGE = "long_range",
// (undocumented)
MURMUR3 = "murmur3",
// (undocumented)
NESTED = "nested",
@ -1038,6 +1050,8 @@ export enum KBN_FIELD_TYPES {
// (undocumented)
DATE = "date",
// (undocumented)
DATE_RANGE = "date_range",
// (undocumented)
GEO_POINT = "geo_point",
// (undocumented)
GEO_SHAPE = "geo_shape",
@ -1046,12 +1060,16 @@ export enum KBN_FIELD_TYPES {
// (undocumented)
IP = "ip",
// (undocumented)
IP_RANGE = "ip_range",
// (undocumented)
MURMUR3 = "murmur3",
// (undocumented)
NESTED = "nested",
// (undocumented)
NUMBER = "number",
// (undocumented)
NUMBER_RANGE = "number_range",
// (undocumented)
OBJECT = "object",
// (undocumented)
_SOURCE = "_source",

View file

@ -117,6 +117,9 @@ export const fieldMappings = {
ip: {
type: 'ip',
},
ip_range: {
type: 'ip_range',
},
timestamp: {
type: 'date',
},
@ -124,6 +127,9 @@ export const fieldMappings = {
type: 'alias',
path: 'timestamp',
},
timestamp_range: {
type: 'date_range',
},
phpmemory: {
type: 'long',
},

View file

@ -917,14 +917,26 @@ exports[`FieldEditor should show deprecated lang warning 1`] = `
"text": "number",
"value": "number",
},
Object {
"text": "number_range",
"value": "number_range",
},
Object {
"text": "date",
"value": "date",
},
Object {
"text": "date_range",
"value": "date_range",
},
Object {
"text": "ip",
"value": "ip",
},
Object {
"text": "ip_range",
"value": "ip_range",
},
Object {
"text": "boolean",
"value": "boolean",

View file

@ -65,6 +65,16 @@ exports[`FieldIcon renders known field types date is rendered 1`] = `
/>
`;
exports[`FieldIcon renders known field types date_range is rendered 1`] = `
<EuiToken
aria-label="date_range"
className="kbnFieldIcon"
iconType="tokenDate"
size="s"
title="date_range"
/>
`;
exports[`FieldIcon renders known field types geo_point is rendered 1`] = `
<EuiToken
aria-label="geo_point"
@ -95,6 +105,16 @@ exports[`FieldIcon renders known field types ip is rendered 1`] = `
/>
`;
exports[`FieldIcon renders known field types ip_range is rendered 1`] = `
<EuiToken
aria-label="ip_range"
className="kbnFieldIcon"
iconType="tokenIP"
size="s"
title="ip_range"
/>
`;
exports[`FieldIcon renders known field types murmur3 is rendered 1`] = `
<EuiToken
aria-label="murmur3"
@ -125,6 +145,16 @@ exports[`FieldIcon renders known field types number is rendered 1`] = `
/>
`;
exports[`FieldIcon renders known field types number_range is rendered 1`] = `
<EuiToken
aria-label="number_range"
className="kbnFieldIcon"
iconType="tokenNumber"
size="s"
title="number_range"
/>
`;
exports[`FieldIcon renders known field types string is rendered 1`] = `
<EuiToken
aria-label="string"

View file

@ -15,11 +15,14 @@ export interface FieldIconProps extends Omit<EuiTokenProps, 'iconType'> {
| 'boolean'
| 'conflict'
| 'date'
| 'date_range'
| 'geo_point'
| 'geo_shape'
| 'ip'
| 'ip_range'
| 'murmur3'
| 'number'
| 'number_range'
| '_source'
| 'string'
| string
@ -36,12 +39,15 @@ export const typeToEuiIconMap: Partial<Record<string, EuiTokenProps>> = {
// icon for an index pattern mapping conflict in discover
conflict: { iconType: 'alert', color: 'euiColorVis9', shape: 'square' },
date: { iconType: 'tokenDate' },
date_range: { iconType: 'tokenDate' },
geo_point: { iconType: 'tokenGeo' },
geo_shape: { iconType: 'tokenGeo' },
ip: { iconType: 'tokenIP' },
ip_range: { iconType: 'tokenIP' },
// is a plugin's data type https://www.elastic.co/guide/en/elasticsearch/plugins/current/mapper-murmur3-usage.html
murmur3: { iconType: 'tokenFile' },
number: { iconType: 'tokenNumber' },
number_range: { iconType: 'tokenNumber' },
_source: { iconType: 'editorCodeBlock', color: 'gray' },
string: { iconType: 'tokenString' },
nested: { iconType: 'tokenNested' },

View file

@ -73,11 +73,12 @@ function NumberIntervalParamEditor({
setValidity,
setValue,
}: AggParamEditorProps<string | undefined>) {
const isAutoChecked = isAutoInterval(value);
const field = agg.getField();
const fieldSupportsAuto = !field || field.type === 'number';
const isAutoChecked = fieldSupportsAuto && isAutoInterval(value);
const base: number = get(editorConfig, 'interval.base') as number;
const min = base || 0;
const isValid =
value !== '' && value !== undefined && (isAutoInterval(value) || Number(value) >= min);
const isValid = value !== '' && value !== undefined && (isAutoChecked || Number(value) >= min);
useEffect(() => {
setValidity(isValid);
@ -112,6 +113,7 @@ function NumberIntervalParamEditor({
onChange={onAutoSwitchChange}
checked={isAutoChecked}
compressed
disabled={!fieldSupportsAuto}
data-test-subj={`visEditorIntervalSwitch${agg.id}`}
/>
</EuiFlexItem>

View file

@ -27,7 +27,7 @@ import {
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
import { KBN_FIELD_TYPES } from '../../../../../../plugins/data/public';
const RESTRICT_FIELDS = [KBN_FIELD_TYPES.NUMBER];
const RESTRICT_FIELDS = KBN_FIELD_TYPES.NUMBER;
const StandardDeviationAggUi = (props) => {
const { series, panel, fields, intl } = props;

View file

@ -443,7 +443,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
);
const errorMessage = await fieldErrorMessage.getVisibleText();
expect(errorMessage).to.be(
'The index pattern test_index* does not contain any of the following compatible field types: date'
'The index pattern test_index* does not contain any of the following compatible field types: date or date_range'
);
});
});

View file

@ -71,5 +71,16 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
});
});
});
describe('autoBounds are not set for number_range data', function () {
it('should use provided value when number of generated buckets is less than histogram:maxBars', async function () {
log.debug('Field = machine.ram_range');
await PageObjects.visEditor.selectField('machine.ram_range');
await retry.waitFor('interval to be set', async () => {
return Boolean(await PageObjects.visEditor.getNumericInterval());
});
expect(await PageObjects.visEditor.getNumericInterval()).to.eql(100);
});
});
});
}

View file

@ -127,6 +127,9 @@
},
"ram": {
"type": "long"
},
"ram_range": {
"type": "long_range"
}
}
},
@ -500,6 +503,9 @@
},
"ram": {
"type": "long"
},
"ram_range": {
"type": "long_range"
}
}
},
@ -873,6 +879,9 @@
},
"ram": {
"type": "long"
},
"ram_range": {
"type": "long_range"
}
}
},
@ -1115,4 +1124,4 @@
}
}
}
}
}

View file

@ -12,6 +12,16 @@
"aggregatable": true,
"readFromDocValues": true
},
{
"name": "bytes_range",
"type": "number_range",
"esTypes": ["long_range"],
"count": 10,
"scripted": false,
"searchable": true,
"aggregatable": true,
"readFromDocValues": true
},
{
"name": "ssl",
"type": "boolean",
@ -42,6 +52,16 @@
"aggregatable": true,
"readFromDocValues": true
},
{
"name": "time_range",
"type": "date_range",
"esTypes": ["date_range"],
"count": 10,
"scripted": false,
"searchable": true,
"aggregatable": true,
"readFromDocValues": true
},
{
"name": "@tags",
"type": "string",
@ -82,6 +102,16 @@
"aggregatable": true,
"readFromDocValues": true
},
{
"name": "ip_range",
"type": "ip_range",
"esTypes": ["ip_range"],
"count": 0,
"scripted": false,
"searchable": true,
"aggregatable": true,
"readFromDocValues": true
},
{
"name": "request_body",
"type": "attachment",

View file

@ -70,6 +70,36 @@ describe('Kuery operator suggestions', () => {
expect(suggestions.find(({ text }) => text === '< ')).toBeDefined();
});
test('should return numeric operators for numeric range fields', async () => {
const suggestions = await getSuggestions(
querySuggestionsArgs,
mockKueryNode({ fieldName: 'bytes_range' })
);
expect(suggestions.find(({ text }) => text === ': ')).toBeDefined();
expect(suggestions.find(({ text }) => text === '< ')).toBeDefined();
});
test('should return range operators for date range fields', async () => {
const suggestions = await getSuggestions(
querySuggestionsArgs,
mockKueryNode({ fieldName: 'time_range' })
);
expect(suggestions.find(({ text }) => text === ': ')).toBeDefined();
expect(suggestions.find(({ text }) => text === '< ')).toBeDefined();
});
test('should return range operators for ip range fields', async () => {
const suggestions = await getSuggestions(
querySuggestionsArgs,
mockKueryNode({ fieldName: 'ip_range' })
);
expect(suggestions.find(({ text }) => text === ': ')).toBeDefined();
expect(suggestions.find(({ text }) => text === '< ')).toBeDefined();
});
test('should have descriptions', async () => {
const suggestions = await getSuggestions(
querySuggestionsArgs,

View file

@ -67,7 +67,18 @@ const operators = {
'xpack.data.kueryAutocomplete.equalOperatorDescription.equalsText' for 'equals' part."
/>
),
fieldTypes: ['string', 'number', 'date', 'ip', 'geo_point', 'geo_shape', 'boolean'],
fieldTypes: [
'string',
'number',
'number_range',
'date',
'date_range',
'ip',
'ip_range',
'geo_point',
'geo_shape',
'boolean',
],
},
'<=': {
description: (
@ -83,7 +94,7 @@ const operators = {
'xpack.data.kueryAutocomplete.lessThanOrEqualOperatorDescription.lessThanOrEqualToText' for 'less than or equal to' part."
/>
),
fieldTypes: ['number', 'date', 'ip'],
fieldTypes: ['number', 'number_range', 'date', 'date_range', 'ip', 'ip_range'],
},
'>=': {
description: (
@ -99,7 +110,7 @@ const operators = {
'xpack.data.kueryAutocomplete.greaterThanOrEqualOperatorDescription.greaterThanOrEqualToText' for 'greater than or equal to' part."
/>
),
fieldTypes: ['number', 'date', 'ip'],
fieldTypes: ['number', 'number_range', 'date', 'date_range', 'ip', 'ip_range'],
},
'<': {
description: (
@ -111,7 +122,7 @@ const operators = {
'xpack.data.kueryAutocomplete.lessThanOperatorDescription.lessThanText' for 'less than' part."
/>
),
fieldTypes: ['number', 'date', 'ip'],
fieldTypes: ['number', 'number_range', 'date', 'date_range', 'ip', 'ip_range'],
},
'>': {
description: (
@ -125,7 +136,7 @@ const operators = {
'xpack.data.kueryAutocomplete.greaterThanOperatorDescription.greaterThanText' for 'greater than' part."
/>
),
fieldTypes: ['number', 'date', 'ip'],
fieldTypes: ['number', 'number_range', 'date', 'date_range', 'ip', 'ip_range'],
},
': *': {
description: (

View file

@ -63,6 +63,9 @@ const supportedFieldTypes = new Set([
'boolean',
'date',
'ip',
'number_range',
'date_range',
'ip_range',
'histogram',
'document',
]);

View file

@ -71,6 +71,13 @@ describe('IndexPattern Field Item', () => {
aggregatable: true,
searchable: true,
},
{
name: 'ip_range',
displayName: 'ip_range',
type: 'ip_range',
aggregatable: true,
searchable: true,
},
documentField,
],
} as IndexPattern;
@ -234,4 +241,25 @@ describe('IndexPattern Field Item', () => {
expect(wrapper.find(EuiPopover).prop('isOpen')).toEqual(true);
expect(wrapper.find(EuiLoadingSpinner)).toHaveLength(0);
});
it('should not request field stats for range fields', async () => {
const wrapper = mountWithIntl(
<InnerFieldItem
{...defaultProps}
field={{
name: 'ip_range',
displayName: 'ip_range',
type: 'ip_range',
aggregatable: true,
searchable: true,
}}
/>
);
await act(async () => {
clickField(wrapper, 'ip_range');
});
expect(core.http.post).not.toHaveBeenCalled();
});
});

View file

@ -122,7 +122,8 @@ export const InnerFieldItem = function InnerFieldItem(props: FieldItemProps) {
});
function fetchData() {
if (state.isLoading || field.type === 'document') {
// Range types don't have any useful stats we can show
if (state.isLoading || field.type === 'document' || field.type.includes('range')) {
return;
}
@ -376,6 +377,18 @@ function FieldItemPopoverContents(props: State & FieldItemProps) {
if (props.isLoading) {
return <EuiLoadingSpinner />;
} else if (field.type.includes('range')) {
return (
<>
<EuiPopoverTitle>{panelHeader}</EuiPopoverTitle>
<EuiText size="s">
{i18n.translate('xpack.lens.indexPattern.fieldStatsLimited', {
defaultMessage: `Summary information is not available for range type fields.`,
})}
</EuiText>
</>
);
} else if (
(!props.histogram || props.histogram.buckets.length === 0) &&
(!props.topValues || props.topValues.buckets.length === 0)

View file

@ -63,6 +63,13 @@ const fieldsOne = [
aggregatable: true,
searchable: true,
},
{
name: 'bytes_range',
displayName: 'bytes_range',
type: 'number_range',
aggregatable: true,
searchable: true,
},
documentField,
];

View file

@ -13,7 +13,16 @@ import { FormattedIndexPatternColumn, FieldBasedIndexPatternColumn } from './col
import { getFormatFromPreviousColumn, getInvalidFieldMessage, getSafeName } from './helpers';
const supportedTypes = new Set(['string', 'boolean', 'number', 'ip', 'date']);
const supportedTypes = new Set([
'string',
'boolean',
'number',
'number_range',
'ip',
'ip_range',
'date',
'date_range',
]);
const SCALE = 'ratio';
const OPERATION_TYPE = 'cardinality';

View file

@ -63,7 +63,7 @@ export const dateHistogramOperation: OperationDefinition<
getHelpMessage: (props) => <AutoDateHistogramPopover {...props} />,
getPossibleOperationForField: ({ aggregationRestrictions, aggregatable, type }) => {
if (
type === 'date' &&
(type === 'date' || type === 'date_range') &&
aggregatable &&
(!aggregationRestrictions || aggregationRestrictions.date_histogram)
) {

View file

@ -93,6 +93,10 @@ export async function initFieldsRoute(setup: CoreSetup<PluginStartContract>) {
return result;
};
if (field.type.includes('range')) {
return res.ok({ body: {} });
}
if (field.type === 'histogram') {
return res.ok({
body: await getNumberHistogram(search, field, false),

View file

@ -46,6 +46,7 @@ const fieldsWithData = [
'machine.os',
'machine.os.raw',
'machine.ram',
'machine.ram_range',
'memory',
'phpmemory',
'referer',

View file

@ -133,6 +133,9 @@
},
"ram": {
"type": "long"
},
"ram_range": {
"type": "long_range"
}
}
},