[Maps] fix Kibana maps should not override the sort field if not provided by the user (#150400)

Fixes https://github.com/elastic/kibana/issues/150184

PR updates vector tile search request body generation to only populate
`sort` property when its provided. PR also cleans up some `any` types
with types from elasticsearch client.
This commit is contained in:
Nathan Reese 2023-02-07 07:31:21 -07:00 committed by GitHub
parent b96d46c06b
commit 0958c59f0b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 84 additions and 22 deletions

View file

@ -5,6 +5,7 @@
* 2.0.
*/
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import {
decodeMvtResponseBody,
encodeMvtResponseBody,
@ -23,7 +24,7 @@ describe('decodeMvtResponseBody', () => {
stored_fields: ['geopoint'],
runtime_mappings: {
'day of week': {
type: 'keyword',
type: 'keyword' as estypes.MappingRuntimeFieldType,
script: {
source:
"ZonedDateTime input = doc['ISSUE_DATE'].value;\nString output = input.format(DateTimeFormatter.ofPattern('e')) + ' ' + input.format(DateTimeFormatter.ofPattern('E'));\nemit(output);",
@ -64,7 +65,7 @@ describe('decodeMvtResponseBody', () => {
_source: false,
runtime_mappings: {
price_as_number: {
type: 'keyword',
type: 'keyword' as estypes.MappingRuntimeFieldType,
script: {
source: runtimeFieldScript,
},
@ -124,4 +125,43 @@ describe('getHitsTileRequest', () => {
});
expect(path).toEqual('/my%20index/_mvt/my%20location/0/0/0');
});
describe('sort', () => {
test(`Should include sort`, () => {
const searchRequest = {
size: 10000,
runtime_mappings: {},
query: {},
sort: ['timestamp'],
};
const { body } = getHitsTileRequest({
encodedRequestBody: encodeMvtResponseBody(searchRequest),
geometryFieldName: 'my location',
hasLabels: true,
index: 'my index',
x: 0,
y: 0,
z: 0,
});
expect(body).toHaveProperty('sort');
});
test(`Should not include sort when sort not provided`, () => {
const searchRequest = {
size: 10000,
runtime_mappings: {},
query: {},
};
const { body } = getHitsTileRequest({
encodedRequestBody: encodeMvtResponseBody(searchRequest),
geometryFieldName: 'my location',
hasLabels: true,
index: 'my index',
x: 0,
y: 0,
z: 0,
});
expect(body).not.toHaveProperty('sort');
});
});
});

View file

@ -6,13 +6,16 @@
*/
import rison from '@kbn/rison';
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { RENDER_AS } from './constants';
export function decodeMvtResponseBody(encodedRequestBody: string): object {
return rison.decode(decodeURIComponent(encodedRequestBody).replace('%25', '%')) as object;
export function decodeMvtResponseBody(encodedRequestBody: string): estypes.SearchRequest['body'] {
return rison.decode(
decodeURIComponent(encodedRequestBody).replace('%25', '%')
) as estypes.SearchRequest['body'];
}
export function encodeMvtResponseBody(unencodedRequestBody: object): string {
export function encodeMvtResponseBody(unencodedRequestBody: estypes.SearchRequest['body']): string {
// URL encoding replaces unsafe ASCII characters with a '%' followed by two hexadecimal digits
// encodeURIComponent does not encode '%'
// This causes preexisting '%' to break decoding because they are not valid URL encoding
@ -41,7 +44,10 @@ export function getAggsTileRequest({
y: number;
z: number;
}) {
const requestBody = decodeMvtResponseBody(encodedRequestBody) as any;
const requestBody = decodeMvtResponseBody(encodedRequestBody);
if (!requestBody) {
throw new Error('Required requestBody parameter not provided');
}
return {
path: `/${encodeURIComponent(index)}/_mvt/${encodeURIComponent(
geometryFieldName
@ -58,7 +64,7 @@ export function getAggsTileRequest({
fields: requestBody.fields ? requestBody.fields : [],
runtime_mappings: requestBody.runtime_mappings,
with_labels: hasLabels,
},
} as estypes.SearchMvtRequest['body'],
};
}
@ -79,21 +85,30 @@ export function getHitsTileRequest({
y: number;
z: number;
}) {
const requestBody = decodeMvtResponseBody(encodedRequestBody) as any;
const requestBody = decodeMvtResponseBody(encodedRequestBody);
if (!requestBody) {
throw new Error('Required requestBody parameter not provided');
}
const tileRequestBody = {
grid_precision: 0, // no aggs
exact_bounds: true,
extent: 4096, // full resolution,
query: requestBody.query,
runtime_mappings: requestBody.runtime_mappings,
track_total_hits: typeof requestBody.size === 'number' ? requestBody.size + 1 : false,
with_labels: hasLabels,
} as estypes.SearchMvtRequest['body'];
if (requestBody.fields) {
// @ts-expect-error SearchRequest['body'].fields and SearchMvtRequest['body'].fields types do not allign, even though they do in implemenation
tileRequestBody.fields = requestBody.fields;
}
if (requestBody.sort) {
tileRequestBody!.sort = requestBody.sort;
}
return {
path: `/${encodeURIComponent(index)}/_mvt/${encodeURIComponent(
geometryFieldName
)}/${z}/${x}/${y}`,
body: {
grid_precision: 0, // no aggs
exact_bounds: true,
extent: 4096, // full resolution,
query: requestBody.query,
fields: requestBody.fields ? requestBody.fields : [],
runtime_mappings: requestBody.runtime_mappings,
sort: requestBody.sort ? requestBody.sort : [],
track_total_hits: typeof requestBody.size === 'number' ? requestBody.size + 1 : false,
with_labels: hasLabels,
},
body: tileRequestBody,
};
}

View file

@ -13,6 +13,7 @@ import { CoreStart, KibanaRequest, KibanaResponseFactory, Logger } from '@kbn/co
import { IRouter } from '@kbn/core/server';
import type { DataRequestHandlerContext } from '@kbn/data-plugin/server';
import { errors } from '@elastic/elasticsearch';
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import {
MVT_GETTILE_API_PATH,
API_ROOT_PATH,
@ -61,7 +62,10 @@ export function initMVTRoutes({
const y = parseInt((params as any).y, 10) as number;
const z = parseInt((params as any).z, 10) as number;
let tileRequest: { path: string; body: object } | undefined;
let tileRequest: { path: string; body: estypes.SearchMvtRequest['body'] } = {
path: '',
body: {},
};
try {
tileRequest = getHitsTileRequest({
encodedRequestBody: query.requestBody as string,
@ -123,7 +127,10 @@ export function initMVTRoutes({
const y = parseInt((params as any).y, 10) as number;
const z = parseInt((params as any).z, 10) as number;
let tileRequest: { path: string; body: object } | undefined;
let tileRequest: { path: string; body: estypes.SearchMvtRequest['body'] } = {
path: '',
body: {},
};
try {
tileRequest = getAggsTileRequest({
encodedRequestBody: query.requestBody as string,
@ -168,7 +175,7 @@ async function getTile({
path,
}: {
abortController: AbortController;
body: object;
body: estypes.SearchMvtRequest['body'];
context: DataRequestHandlerContext;
core: CoreStart;
executionContext: KibanaExecutionContext;