Skip to content

Commit 6f29f24

Browse files
nickpeihlnreese
authored andcommitted
[Maps] Add support for date fields in dynamic styles (#47903)
* [Maps] retrieve geo_point value from docvalue_fields instead of _source * [Maps] retrieve geo_point value from docvalue_fields instead of _source * Dynamically style by time field * Add epoch_millis to date fields in docvalues * add functional test ensuring _search request only pulls what is needed * Add functional test for dynamic styles by date field * Support dynamic styles in top hits agg * Add getTimeFields fn to vector_source * Retrieve only epoch_millis for date fields in docvalues * use new Fields syntax, simplify docvalue_fields creation * fix comment
1 parent 809a0ef commit 6f29f24

File tree

7 files changed

+124
-9
lines changed

7 files changed

+124
-9
lines changed

x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.js

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,17 @@ export class ESSearchSource extends AbstractESSource {
9797
}
9898
}
9999

100+
async getDateFields() {
101+
try {
102+
const indexPattern = await this._getIndexPattern();
103+
return indexPattern.fields.getByType('date').map(field => {
104+
return { name: field.name, label: field.name };
105+
});
106+
} catch (error) {
107+
return [];
108+
}
109+
}
110+
100111
getMetricFields() {
101112
return [];
102113
}
@@ -158,6 +169,28 @@ export class ESSearchSource extends AbstractESSource {
158169
];
159170
}
160171

172+
async _excludeDateFields(fieldNames) {
173+
const dateFieldNames = _.map(await this.getDateFields(), 'name');
174+
return fieldNames.filter(field => {
175+
return !dateFieldNames.includes(field);
176+
});
177+
}
178+
179+
// Returns docvalue_fields array for the union of indexPattern's dateFields and request's field names.
180+
async _getDateDocvalueFields(searchFields) {
181+
const dateFieldNames = _.map(await this.getDateFields(), 'name');
182+
return searchFields
183+
.filter(fieldName => {
184+
return dateFieldNames.includes(fieldName);
185+
})
186+
.map(fieldName => {
187+
return {
188+
field: fieldName,
189+
format: 'epoch_millis'
190+
};
191+
});
192+
}
193+
161194
async _getTopHits(layerName, searchFilters, registerCancelCallback) {
162195
const {
163196
topHitsSplitField,
@@ -183,16 +216,19 @@ export class ESSearchSource extends AbstractESSource {
183216
const topHits = {
184217
size: topHitsSize,
185218
script_fields: scriptFields,
219+
docvalue_fields: await this._getDateDocvalueFields(searchFilters.fieldNames),
186220
};
221+
const nonDateFieldNames = await this._excludeDateFields(searchFilters.fieldNames);
222+
187223
if (this._hasSort()) {
188224
topHits.sort = this._buildEsSort();
189225
}
190226
if (geoField.type === ES_GEO_FIELD_TYPE.GEO_POINT) {
191227
topHits._source = false;
192-
topHits.docvalue_fields = searchFilters.fieldNames;
228+
topHits.docvalue_fields.push(...nonDateFieldNames);
193229
} else {
194230
topHits._source = {
195-
includes: searchFilters.fieldNames
231+
includes: nonDateFieldNames
196232
};
197233
}
198234

@@ -238,22 +274,23 @@ export class ESSearchSource extends AbstractESSource {
238274
// searchFilters.fieldNames contains geo field and any fields needed for styling features
239275
// Performs Elasticsearch search request being careful to pull back only required fields to minimize response size
240276
async _getSearchHits(layerName, searchFilters, registerCancelCallback) {
277+
const initialSearchContext = {
278+
docvalue_fields: await this._getDateDocvalueFields(searchFilters.fieldNames)
279+
};
241280
const geoField = await this._getGeoField();
242281

243282
let searchSource;
244283
if (geoField.type === ES_GEO_FIELD_TYPE.GEO_POINT) {
245284
// Request geo_point and style fields in docvalue_fields insted of _source
246285
// 1) Returns geo_point in a consistent format regardless of how geo_point is stored in source
247286
// 2) Setting _source to false so we avoid pulling back unneeded fields.
248-
const initialSearchContext = {
249-
docvalue_fields: searchFilters.fieldNames
250-
};
287+
initialSearchContext.docvalue_fields.push(...(await this._excludeDateFields(searchFilters.fieldNames)));
251288
searchSource = await this._makeSearchSource(searchFilters, ES_SIZE_LIMIT, initialSearchContext);
252289
searchSource.setField('source', false); // do not need anything from _source
253290
searchSource.setField('fields', searchFilters.fieldNames); // Setting "fields" filters out unused scripted fields
254291
} else {
255292
// geo_shape fields do not support docvalue_fields yet, so still have to be pulled from _source
256-
searchSource = await this._makeSearchSource(searchFilters, ES_SIZE_LIMIT);
293+
searchSource = await this._makeSearchSource(searchFilters, ES_SIZE_LIMIT, initialSearchContext);
257294
// Setting "fields" instead of "source: { includes: []}"
258295
// because SearchSource automatically adds the following by default
259296
// 1) all scripted fields

x-pack/legacy/plugins/maps/public/layers/sources/es_source.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ export class AbstractESSource extends AbstractVectorSource {
323323
return null;
324324
}
325325

326-
const fieldFromIndexPattern = indexPattern.fields.byName[rawFieldName];
326+
const fieldFromIndexPattern = indexPattern.fields.getByName(rawFieldName);
327327
if (!fieldFromIndexPattern) {
328328
return null;
329329
}

x-pack/legacy/plugins/maps/public/layers/sources/vector_source.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ export class AbstractVectorSource extends AbstractSource {
8080
return null;
8181
}
8282

83+
async getDateFields() {
84+
return [];
85+
}
86+
8387
async getNumberFields() {
8488
return [];
8589
}

x-pack/legacy/plugins/maps/public/layers/styles/vector_style.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,7 @@ export class VectorStyle extends AbstractStyle {
403403
} else if (range.delta === 0) {//values are identical
404404
styleValue = 1;//snap to end of color range
405405
} else {
406-
styleValue = (feature.properties[name] - range.min) / range.delta;
406+
styleValue = (value - range.min) / range.delta;
407407
}
408408
} else {
409409
if (isNaN(value)) {

x-pack/legacy/plugins/maps/public/layers/vector_layer.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,14 @@ export class VectorLayer extends AbstractLayer {
254254
}
255255

256256
async getOrdinalFields() {
257+
const timeFields = await this._source.getDateFields();
258+
const timeFieldOptions = timeFields.map(({ label, name }) => {
259+
return {
260+
label,
261+
name,
262+
origin: SOURCE_DATA_ID_ORIGIN
263+
};
264+
});
257265
const numberFields = await this._source.getNumberFields();
258266
const numberFieldOptions = numberFields.map(({ label, name }) => {
259267
return {
@@ -273,7 +281,7 @@ export class VectorLayer extends AbstractLayer {
273281
joinFields.push(...fields);
274282
});
275283

276-
return [...numberFieldOptions, ...joinFields];
284+
return [...timeFieldOptions, ...numberFieldOptions, ...joinFields];
277285
}
278286

279287
getIndexPatternIds() {

x-pack/test/functional/apps/maps/documents_source/docvalue_fields.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,5 +43,15 @@ export default function ({ getPageObjects, getService }) {
4343
expect(Object.keys(firstHit.fields).join(',')).to.equal('geo.coordinates,bytes,hour_of_day');
4444
});
4545

46+
it('should format date fields as epoch_millis when data driven styling is applied to a date field', async () => {
47+
await PageObjects.maps.loadSavedMap('document example with data driven styles on date field');
48+
const response = await getResponse();
49+
const firstHit = response.hits.hits[0];
50+
expect(Object.keys(firstHit).join(',')).to.equal('_index,_type,_id,_score,fields');
51+
expect(Object.keys(firstHit.fields).join(',')).to.equal('geo.coordinates,bytes,@timestamp');
52+
expect(firstHit.fields['@timestamp']).to.be.an('array');
53+
expect(firstHit.fields['@timestamp'][0]).to.equal('1442709321445');
54+
});
55+
4656
});
4757
}

x-pack/test/functional/es_archives/maps/kibana/data.json

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,62 @@
334334
}
335335
}
336336

337+
{
338+
"type": "doc",
339+
"value": {
340+
"id": "map:c9277dd0-eb8f-11e9-ae47-693d6a50fb9e",
341+
"index": ".kibana",
342+
"source": {
343+
"map": {
344+
"title" : "document example with data driven styles on date field",
345+
"description" : "",
346+
"mapStateJSON": "{\"zoom\":4.1,\"center\":{\"lon\":-100.61091,\"lat\":33.23887},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-20T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000},\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filters\":[]}",
347+
"layerListJSON": "[{\"id\":\"0hmz5\",\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"id\":\"road_map\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"TILE\",\"properties\":{}},\"type\":\"VECTOR_TILE\",\"minZoom\":0,\"maxZoom\":24},{\"id\":\"z52lq\",\"label\":\"logstash\",\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"e1a5e1a6-676c-4a89-8ea9-0d91d64b73c6\",\"type\":\"ES_SEARCH\",\"geoField\":\"geo.coordinates\",\"limit\":2048,\"filterByMapBounds\":true,\"showTooltip\":true,\"tooltipProperties\":[],\"indexPatternRefName\":\"layer_1_source_index_pattern\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"color\":\"Blues\",\"field\":{\"label\":\"@timestamp\",\"name\":\"@timestamp\",\"origin\":\"source\"}}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"DYNAMIC\",\"options\":{\"minSize\":4,\"maxSize\":24,\"field\":{\"label\":\"bytes\",\"name\":\"bytes\",\"origin\":\"source\"}}},\"iconOrientation\":{\"type\":\"STATIC\",\"options\":{\"orientation\":0}},\"symbol\":{\"options\":{\"symbolizeAs\":\"circle\",\"symbolId\":\"airfield\"}}}},\"type\":\"VECTOR\"}]",
348+
"uiStateJSON": "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[]}",
349+
"bounds" : {
350+
"type" : "Polygon",
351+
"coordinates" : [
352+
[
353+
[
354+
-132.42879,
355+
44.5711
356+
],
357+
[
358+
-132.42879,
359+
20.22627
360+
],
361+
[
362+
-68.79303,
363+
20.22627
364+
],
365+
[
366+
-68.79303,
367+
44.5711
368+
],
369+
[
370+
-132.42879,
371+
44.5711
372+
]
373+
]
374+
]
375+
}
376+
},
377+
"type" : "map",
378+
"references" : [
379+
{
380+
"name" : "layer_1_source_index_pattern",
381+
"type" : "index-pattern",
382+
"id" : "c698b940-e149-11e8-a35a-370a8516603a"
383+
}
384+
],
385+
"migrationVersion" : {
386+
"map" : "7.5.0"
387+
},
388+
"updated_at" : "2019-10-10T18:57:19.916Z"
389+
}
390+
}
391+
}
392+
337393
{
338394
"type": "doc",
339395
"value": {

0 commit comments

Comments
 (0)