From 6eebc134651a48888d74cf9993e44d871e3f8be5 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Fri, 23 Aug 2024 14:34:54 -0700 Subject: [PATCH 1/3] [ES|QL] Only log requests to Inspector on request complete --- .../data/common/search/expressions/esql.ts | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/plugins/data/common/search/expressions/esql.ts b/src/plugins/data/common/search/expressions/esql.ts index d7af8ac5ffed1..28e3f5fe63f88 100644 --- a/src/plugins/data/common/search/expressions/esql.ts +++ b/src/plugins/data/common/search/expressions/esql.ts @@ -9,20 +9,26 @@ import type { KibanaRequest } from '@kbn/core/server'; import { esFieldTypeToKibanaFieldType } from '@kbn/field-types'; import { i18n } from '@kbn/i18n'; -import type { IKibanaSearchResponse, IKibanaSearchRequest } from '@kbn/search-types'; +import type { + IKibanaSearchRequest, + IKibanaSearchResponse, + ISearchGeneric, +} from '@kbn/search-types'; import type { Datatable, ExpressionFunctionDefinition } from '@kbn/expressions-plugin/common'; import { RequestAdapter } from '@kbn/inspector-plugin/common'; import { getStartEndParams } from '@kbn/esql-utils'; - import { zipObject } from 'lodash'; -import { Observable, defer, throwError } from 'rxjs'; -import { catchError, map, switchMap, tap } from 'rxjs'; +import { catchError, defer, map, Observable, switchMap, tap, throwError } from 'rxjs'; import { buildEsQuery } from '@kbn/es-query'; -import type { ISearchGeneric } from '@kbn/search-types'; -import type { ESQLSearchResponse, ESQLSearchParams } from '@kbn/es-types'; +import type { ESQLSearchParams, ESQLSearchResponse } from '@kbn/es-types'; import { getEsQueryConfig } from '../../es_query'; import { getTime } from '../../query'; -import { ESQL_ASYNC_SEARCH_STRATEGY, KibanaContext, ESQL_TABLE_TYPE } from '..'; +import { + ESQL_ASYNC_SEARCH_STRATEGY, + ESQL_TABLE_TYPE, + isRunningResponse, + type KibanaContext, +} from '..'; import { UiSettingsCommon } from '../..'; type Input = KibanaContext | null; @@ -222,7 +228,9 @@ export const getEsqlFn = ({ getStartDependencies }: EsqlFnArguments) => { return throwError(() => error); }), tap({ - next({ rawResponse, requestParams }) { + next(response) { + if (isRunningResponse(response)) return; + const { rawResponse, requestParams } = response; logInspectorRequest() .stats({ hits: { From 9d2ca1667f762b2379fe69692321adb858ad7efa Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Tue, 27 Aug 2024 08:59:39 -0700 Subject: [PATCH 2/3] [ES|QL] Add mechanism to simulate long queries --- .../data/common/search/expressions/esql.ts | 57 ++++++++++++++----- 1 file changed, 43 insertions(+), 14 deletions(-) diff --git a/src/plugins/data/common/search/expressions/esql.ts b/src/plugins/data/common/search/expressions/esql.ts index d7af8ac5ffed1..34ac9a1ccffc5 100644 --- a/src/plugins/data/common/search/expressions/esql.ts +++ b/src/plugins/data/common/search/expressions/esql.ts @@ -9,22 +9,32 @@ import type { KibanaRequest } from '@kbn/core/server'; import { esFieldTypeToKibanaFieldType } from '@kbn/field-types'; import { i18n } from '@kbn/i18n'; -import type { IKibanaSearchResponse, IKibanaSearchRequest } from '@kbn/search-types'; +import type { + IKibanaSearchRequest, + IKibanaSearchResponse, + ISearchGeneric, +} from '@kbn/search-types'; import type { Datatable, ExpressionFunctionDefinition } from '@kbn/expressions-plugin/common'; import { RequestAdapter } from '@kbn/inspector-plugin/common'; import { getStartEndParams } from '@kbn/esql-utils'; - import { zipObject } from 'lodash'; -import { Observable, defer, throwError } from 'rxjs'; -import { catchError, map, switchMap, tap } from 'rxjs'; -import { buildEsQuery } from '@kbn/es-query'; -import type { ISearchGeneric } from '@kbn/search-types'; -import type { ESQLSearchResponse, ESQLSearchParams } from '@kbn/es-types'; +import { catchError, defer, map, Observable, switchMap, tap, throwError } from 'rxjs'; +import { buildEsQuery, type Filter } from '@kbn/es-query'; +import type { ESQLSearchParams, ESQLSearchResponse } from '@kbn/es-types'; import { getEsQueryConfig } from '../../es_query'; import { getTime } from '../../query'; -import { ESQL_ASYNC_SEARCH_STRATEGY, KibanaContext, ESQL_TABLE_TYPE } from '..'; +import { ESQL_ASYNC_SEARCH_STRATEGY, ESQL_TABLE_TYPE, KibanaContext } from '..'; import { UiSettingsCommon } from '../..'; +declare global { + interface Window { + /** + * Debug setting to make requests complete slower than normal. Only available on snapshots where `error_query` is enabled in ES. + */ + ELASTIC_ESQL_DELAY_SECONDS?: number; + } +} + type Input = KibanaContext | null; type Output = Observable; @@ -166,12 +176,31 @@ export const getEsqlFn = ({ getStartDependencies }: EsqlFnArguments) => { fieldName: timeField, }); - params.filter = buildEsQuery( - undefined, - input.query || [], - [...(input.filters ?? []), ...(timeFilter ? [timeFilter] : [])], - esQueryConfigs - ); + // Used for debugging & inside automated tests to simulate a slow query + const delayFilter: Filter | undefined = window.ELASTIC_ESQL_DELAY_SECONDS + ? { + meta: {}, + query: { + error_query: { + indices: [ + { + name: '*', + error_type: 'warning', + stall_time_seconds: window.ELASTIC_ESQL_DELAY_SECONDS, + }, + ], + }, + }, + } + : undefined; + + const filters = [ + ...(input.filters ?? []), + ...(timeFilter ? [timeFilter] : []), + ...(delayFilter ? [delayFilter] : []), + ]; + + params.filter = buildEsQuery(undefined, input.query || [], filters, esQueryConfigs); } let startTime = Date.now(); From f4da14dc803b0848f292f71acd2bce65aec8206f Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Wed, 28 Aug 2024 08:22:34 -0700 Subject: [PATCH 3/3] Add functional test --- .../requests/components/request_selector.tsx | 1 + .../apps/discover/esql/_esql_view.ts | 25 +++++++++++++++++++ test/functional/services/inspector.ts | 5 ++++ 3 files changed, 31 insertions(+) diff --git a/src/plugins/inspector/public/views/requests/components/request_selector.tsx b/src/plugins/inspector/public/views/requests/components/request_selector.tsx index 0a3a113f07664..84aa916c2fe9f 100644 --- a/src/plugins/inspector/public/views/requests/components/request_selector.tsx +++ b/src/plugins/inspector/public/views/requests/components/request_selector.tsx @@ -114,6 +114,7 @@ export class RequestSelector extends Component { } > diff --git a/test/functional/apps/discover/esql/_esql_view.ts b/test/functional/apps/discover/esql/_esql_view.ts index 11ea6a9ac4b22..7bdcb1bfe17fb 100644 --- a/test/functional/apps/discover/esql/_esql_view.ts +++ b/test/functional/apps/discover/esql/_esql_view.ts @@ -325,6 +325,31 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(requestNames).to.contain('Visualization'); }); }); + + describe('with slow queries', () => { + it('should show only one entry in inspector for table/visualization', async function () { + await PageObjects.discover.selectTextBaseLang(); + const testQuery = `from kibana_sample_data_flights | limit 10`; + await monacoEditor.setCodeEditorValue(testQuery); + + await browser.execute(() => { + window.ELASTIC_ESQL_DELAY_SECONDS = 5; + }); + await testSubjects.click('querySubmitButton'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await browser.execute(() => { + window.ELASTIC_ESQL_DELAY_SECONDS = undefined; + }); + + await inspector.open(); + const requestNames = (await inspector.getRequestNames()).split(','); + const requestTotalTime = await inspector.getRequestTotalTime(); + expect(requestTotalTime).to.be.greaterThan(5000); + expect(requestNames.length).to.be(2); + expect(requestNames).to.contain('Table'); + expect(requestNames).to.contain('Visualization'); + }); + }); }); describe('query history', () => { diff --git a/test/functional/services/inspector.ts b/test/functional/services/inspector.ts index 348f37281156e..af695ad0a308a 100644 --- a/test/functional/services/inspector.ts +++ b/test/functional/services/inspector.ts @@ -328,4 +328,9 @@ export class InspectorService extends FtrService { return value === comboBoxOptions; } + + public async getRequestTotalTime() { + const [ms] = (await this.testSubjects.getVisibleText('inspectorRequestTotalTime')).split('ms'); + return parseFloat(ms); + } }