Skip to content

Commit

Permalink
[inspector] show request method, path, and querystring (#166565)
Browse files Browse the repository at this point in the history
Closes #45931

PR updates bsearch service to return request params from
elasticsearch-js client requests. This provides inspector with the exact
details used to fetch data from elasticsearch, ensuring inspector
displays request exactly as used by elasticsearch-js client.

**ESQL** This PR makes it possible to open ESQL searches in console.
<img width="500" alt="Screen Shot 2023-09-16 at 4 19 58 PM"
src="https://github.com/elastic/kibana/assets/373691/56019fb5-ca88-46cf-a42f-86f5f51edfcc">

---------

Co-authored-by: kibanamachine <[email protected]>
  • Loading branch information
nreese and kibanamachine authored Sep 21, 2023
1 parent 969bbb0 commit d61a5a0
Show file tree
Hide file tree
Showing 26 changed files with 627 additions and 44 deletions.
16 changes: 8 additions & 8 deletions src/plugins/data/common/search/expressions/esdsl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ export const getEsdslFn = ({
});

try {
const { rawResponse } = await lastValueFrom(
const finalResponse = await lastValueFrom(
search(
{
params: {
Expand All @@ -141,14 +141,14 @@ export const getEsdslFn = ({

const stats: RequestStatistics = {};

if (rawResponse?.took) {
if (finalResponse.rawResponse?.took) {
stats.queryTime = {
label: i18n.translate('data.search.es_search.queryTimeLabel', {
defaultMessage: 'Query time',
}),
value: i18n.translate('data.search.es_search.queryTimeValue', {
defaultMessage: '{queryTime}ms',
values: { queryTime: rawResponse.took },
values: { queryTime: finalResponse.rawResponse.took },
}),
description: i18n.translate('data.search.es_search.queryTimeDescription', {
defaultMessage:
Expand All @@ -158,12 +158,12 @@ export const getEsdslFn = ({
};
}

if (rawResponse?.hits) {
if (finalResponse.rawResponse?.hits) {
stats.hitsTotal = {
label: i18n.translate('data.search.es_search.hitsTotalLabel', {
defaultMessage: 'Hits (total)',
}),
value: `${rawResponse.hits.total}`,
value: `${finalResponse.rawResponse.hits.total}`,
description: i18n.translate('data.search.es_search.hitsTotalDescription', {
defaultMessage: 'The number of documents that match the query.',
}),
Expand All @@ -173,19 +173,19 @@ export const getEsdslFn = ({
label: i18n.translate('data.search.es_search.hitsLabel', {
defaultMessage: 'Hits',
}),
value: `${rawResponse.hits.hits.length}`,
value: `${finalResponse.rawResponse.hits.hits.length}`,
description: i18n.translate('data.search.es_search.hitsDescription', {
defaultMessage: 'The number of documents returned by the query.',
}),
};
}

request.stats(stats).ok({ json: rawResponse });
request.stats(stats).ok({ json: finalResponse });
request.json(dsl);

return {
type: 'es_raw_response',
body: rawResponse,
body: finalResponse.rawResponse,
};
} catch (e) {
request.error({ json: e });
Expand Down
8 changes: 4 additions & 4 deletions src/plugins/data/common/search/expressions/esql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,24 +210,24 @@ export const getEsqlFn = ({ getStartDependencies }: EsqlFnArguments) => {
return throwError(() => error);
}),
tap({
next({ rawResponse }) {
next(finalResponse) {
logInspectorRequest()
.stats({
hits: {
label: i18n.translate('data.search.es_search.hitsLabel', {
defaultMessage: 'Hits',
}),
value: `${rawResponse.values.length}`,
value: `${finalResponse.rawResponse.values.length}`,
description: i18n.translate('data.search.es_search.hitsDescription', {
defaultMessage: 'The number of documents returned by the query.',
}),
},
})
.json(params)
.ok({ json: rawResponse });
.ok({ json: finalResponse });
},
error(error) {
logInspectorRequest().error({ json: error });
logInspectorRequest().json(params).error({ json: error });
},
})
);
Expand Down
10 changes: 5 additions & 5 deletions src/plugins/data/common/search/expressions/essql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,14 +217,14 @@ export const getEssqlFn = ({ getStartDependencies }: EssqlFnArguments) => {
return throwError(() => error);
}),
tap({
next({ rawResponse, took }) {
next(finalResponse) {
logInspectorRequest()
.stats({
hits: {
label: i18n.translate('data.search.es_search.hitsLabel', {
defaultMessage: 'Hits',
}),
value: `${rawResponse.rows.length}`,
value: `${finalResponse.rawResponse.rows.length}`,
description: i18n.translate('data.search.es_search.hitsDescription', {
defaultMessage: 'The number of documents returned by the query.',
}),
Expand All @@ -235,7 +235,7 @@ export const getEssqlFn = ({ getStartDependencies }: EssqlFnArguments) => {
}),
value: i18n.translate('data.search.es_search.queryTimeValue', {
defaultMessage: '{queryTime}ms',
values: { queryTime: took },
values: { queryTime: finalResponse.took },
}),
description: i18n.translate('data.search.es_search.queryTimeDescription', {
defaultMessage:
Expand All @@ -245,10 +245,10 @@ export const getEssqlFn = ({ getStartDependencies }: EssqlFnArguments) => {
},
})
.json(params)
.ok({ json: rawResponse });
.ok({ json: finalResponse });
},
error(error) {
logInspectorRequest().error({ json: error });
logInspectorRequest().json(params).error({ json: error });
},
})
);
Expand Down
6 changes: 6 additions & 0 deletions src/plugins/data/common/search/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* Side Public License, v 1.
*/

import type { ConnectionRequestParams } from '@elastic/transport';
import type { TransportRequestOptions } from '@elastic/elasticsearch';
import type { KibanaExecutionContext } from '@kbn/core/public';
import type { DataView } from '@kbn/data-views-plugin/common';
Expand Down Expand Up @@ -86,6 +87,11 @@ export interface IKibanaSearchResponse<RawResponse = any> {
* The raw response returned by the internal search method (usually the raw ES response)
*/
rawResponse: RawResponse;

/**
* HTTP request parameters from elasticsearch transport client t
*/
requestParams?: ConnectionRequestParams;
}

export interface IKibanaSearchRequest<Params = any> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
takeUntil,
tap,
} from 'rxjs/operators';
import type { ConnectionRequestParams } from '@elastic/transport';
import { PublicMethodsOf } from '@kbn/utility-types';
import type { HttpSetup, IHttpFetchError } from '@kbn/core-http-browser';
import { BfetchRequestError } from '@kbn/bfetch-plugin/public';
Expand Down Expand Up @@ -304,18 +305,38 @@ export class SearchInterceptor {

const cancel = () => id && !isSavedToBackground && sendCancelRequest();

// Async search requires a series of requests
// 1) POST /<index pattern>/_async_search/
// 2..n) GET /_async_search/<async search identifier>
//
// First request contains useful request params for tools like Inspector.
// Preserve and project first request params into responses.
let firstRequestParams: ConnectionRequestParams;

return pollSearch(search, cancel, {
pollInterval: this.deps.searchConfig.asyncSearch.pollInterval,
...options,
abortSignal: searchAbortController.getSignal(),
}).pipe(
tap((response) => {
if (!firstRequestParams && response.requestParams) {
firstRequestParams = response.requestParams;
}

id = response.id;

if (isCompleteResponse(response)) {
searchTracker?.complete();
}
}),
map((response) => {
return firstRequestParams
? {
...response,
requestParams: firstRequestParams,
}
: response;
}),
catchError((e: Error) => {
searchTracker?.error();
cancel();
Expand Down
7 changes: 7 additions & 0 deletions src/plugins/data/server/search/routes/bsearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import { firstValueFrom } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { errors } from '@elastic/elasticsearch';
import { BfetchServerSetup } from '@kbn/bfetch-plugin/server';
import type { ExecutionContextSetup } from '@kbn/core/server';
import apm from 'elastic-apm-node';
Expand Down Expand Up @@ -47,6 +48,12 @@ export function registerBsearchRoute(
message: err.message,
statusCode: err.statusCode,
attributes: err.errBody?.error,
// TODO remove 'instanceof errors.ResponseError' check when
// eql strategy throws KbnServerError (like all of the other strategies)
requestParams:
err instanceof errors.ResponseError
? err.meta?.meta?.request?.params
: err.requestParams,
};
})
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,10 @@ export const eqlSearchStrategyProvider = (
meta: true,
});

return toEqlKibanaSearchResponse(response as TransportResult<EqlSearchResponse>);
return toEqlKibanaSearchResponse(
response as TransportResult<EqlSearchResponse>,
(response as TransportResult<EqlSearchResponse>).meta?.request?.params
);
};

const cancel = async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* Side Public License, v 1.
*/

import type { ConnectionRequestParams } from '@elastic/transport';
import type { TransportResult } from '@elastic/elasticsearch';
import { EqlSearchResponse } from './types';
import { EqlSearchStrategyResponse } from '../../../../common';
Expand All @@ -15,12 +16,14 @@ import { EqlSearchStrategyResponse } from '../../../../common';
* (EQL does not provide _shard info, so total/loaded cannot be calculated.)
*/
export function toEqlKibanaSearchResponse(
response: TransportResult<EqlSearchResponse>
response: TransportResult<EqlSearchResponse>,
requestParams?: ConnectionRequestParams
): EqlSearchStrategyResponse {
return {
id: response.body.id,
rawResponse: response,
isPartial: response.body.is_partial,
isRunning: response.body.is_running,
...(requestParams ? { requestParams } : {}),
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ describe('ES search strategy', () => {
)
);
const [, searchOptions] = esClient.search.mock.calls[0];
expect(searchOptions).toEqual({ signal: undefined, maxRetries: 5 });
expect(searchOptions).toEqual({ signal: undefined, maxRetries: 5, meta: true });
});

it('can be aborted', async () => {
Expand All @@ -131,7 +131,10 @@ describe('ES search strategy', () => {
...params,
track_total_hits: true,
});
expect(esClient.search.mock.calls[0][1]).toEqual({ signal: expect.any(AbortSignal) });
expect(esClient.search.mock.calls[0][1]).toEqual({
signal: expect.any(AbortSignal),
meta: true,
});
});

it('throws normalized error if ResponseError is thrown', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,13 @@ export const esSearchStrategyProvider = (
...(terminateAfter ? { terminate_after: terminateAfter } : {}),
...requestParams,
};
const body = await esClient.asCurrentUser.search(params, {
const { body, meta } = await esClient.asCurrentUser.search(params, {
signal: abortSignal,
...transport,
meta: true,
});
const response = shimHitsTotal(body, options);
return toKibanaSearchResponse(response);
return toKibanaSearchResponse(response, meta?.request?.params);
} catch (e) {
throw getKbnServerError(e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* Side Public License, v 1.
*/

import type { ConnectionRequestParams } from '@elastic/transport';
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { ISearchOptions } from '../../../../common';

Expand All @@ -24,11 +25,15 @@ export function getTotalLoaded(response: estypes.SearchResponse<unknown>) {
* Get the Kibana representation of this response (see `IKibanaSearchResponse`).
* @internal
*/
export function toKibanaSearchResponse(rawResponse: estypes.SearchResponse<unknown>) {
export function toKibanaSearchResponse(
rawResponse: estypes.SearchResponse<unknown>,
requestParams?: ConnectionRequestParams
) {
return {
rawResponse,
isPartial: false,
isRunning: false,
...(requestParams ? { requestParams } : {}),
...getTotalLoaded(rawResponse),
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export const enhancedEsSearchStrategyProvider = (
...(await getDefaultAsyncSubmitParams(uiSettingsClient, searchConfig, options)),
...request.params,
};
const { body, headers } = id
const { body, headers, meta } = id
? await client.asyncSearch.get(
{ ...params, id },
{ ...options.transport, signal: options.abortSignal, meta: true }
Expand All @@ -78,7 +78,11 @@ export const enhancedEsSearchStrategyProvider = (

const response = shimHitsTotal(body.response, options);

return toAsyncKibanaSearchResponse({ ...body, response }, headers?.warning);
return toAsyncKibanaSearchResponse(
{ ...body, response },
headers?.warning,
meta?.request?.params
);
};

const cancel = async () => {
Expand Down Expand Up @@ -131,8 +135,10 @@ export const enhancedEsSearchStrategyProvider = (
);

const response = esResponse.body as estypes.SearchResponse<any>;
const requestParams = esResponse.meta?.request?.params;
return {
rawResponse: shimHitsTotal(response, options),
...(requestParams ? { requestParams } : {}),
...getTotalLoaded(response),
};
} catch (e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,25 @@
* Side Public License, v 1.
*/

import type { ConnectionRequestParams } from '@elastic/transport';
import type { AsyncSearchResponse } from './types';
import { getTotalLoaded } from '../es_search';

/**
* Get the Kibana representation of an async search response (see `IKibanaSearchResponse`).
*/
export function toAsyncKibanaSearchResponse(response: AsyncSearchResponse, warning?: string) {
export function toAsyncKibanaSearchResponse(
response: AsyncSearchResponse,
warning?: string,
requestParams?: ConnectionRequestParams
) {
return {
id: response.id,
rawResponse: response.response,
isPartial: response.is_partial,
isRunning: response.is_running,
...(warning ? { warning } : {}),
...(requestParams ? { requestParams } : {}),
...getTotalLoaded(response.response),
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const esqlSearchStrategyProvider = (
const search = async () => {
try {
const { terminateAfter, ...requestParams } = request.params ?? {};
const { headers, body } = await esClient.asCurrentUser.transport.request(
const { headers, body, meta } = await esClient.asCurrentUser.transport.request(
{
method: 'POST',
path: '/_query',
Expand All @@ -45,10 +45,12 @@ export const esqlSearchStrategyProvider = (
meta: true,
}
);
const transportRequestParams = meta?.request?.params;
return {
rawResponse: body,
isPartial: false,
isRunning: false,
...(transportRequestParams ? { requestParams: transportRequestParams } : {}),
warning: headers?.warning,
};
} catch (e) {
Expand Down
Loading

0 comments on commit d61a5a0

Please sign in to comment.