discover query cancellation (#176202)

This commit is contained in:
Peter Pisljar 2024-02-13 15:54:51 +01:00 committed by GitHub
parent 4010b318d9
commit 721f48cad3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 128 additions and 10 deletions

View file

@ -80,6 +80,7 @@ export const DiscoverHistogramLayout = ({
container={container}
css={histogramLayoutCss}
renderCustomChartToggleActions={renderCustomChartToggleActions}
abortController={stateContainer.dataState.getAbortController()}
>
<DiscoverMainContent
{...mainContentProps}

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import './discover_layout.scss';
import React, { useCallback, useEffect, useMemo, useRef, useState, ReactElement } from 'react';
import React, { ReactElement, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { EuiPage, EuiPageBody, EuiPanel, useEuiBackgroundColor } from '@elastic/eui';
import { css } from '@emotion/react';
import { i18n } from '@kbn/i18n';
@ -46,6 +46,7 @@ import { addLog } from '../../../../utils/add_log';
import { DiscoverResizableLayout } from './discover_resizable_layout';
import { ESQLTechPreviewCallout } from './esql_tech_preview_callout';
import { PanelsToggle, PanelsToggleProps } from '../../../../components/panels_toggle';
import { sendErrorMsg } from '../../hooks/use_saved_search_messages';
const SidebarMemoized = React.memo(DiscoverSidebarResponsive);
const TopNavMemoized = React.memo(DiscoverTopNav);
@ -240,6 +241,16 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) {
panelsToggle,
]);
const isLoading =
documentState.fetchStatus === FetchStatus.LOADING ||
documentState.fetchStatus === FetchStatus.PARTIAL;
const onCancelClick = useCallback(() => {
stateContainer.dataState.cancel();
sendErrorMsg(stateContainer.dataState.data$.documents$);
sendErrorMsg(stateContainer.dataState.data$.main$);
}, [stateContainer.dataState]);
return (
<EuiPage
className={classNames('dscPage', { 'dscPage--serverless': serverless })}
@ -271,6 +282,8 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) {
textBasedLanguageModeErrors={textBasedLanguageModeErrors}
textBasedLanguageModeWarning={textBasedLanguageModeWarning}
onFieldEdited={onFieldEdited}
isLoading={isLoading}
onCancelClick={onCancelClick}
/>
<EuiPageBody className="dscPageBody" aria-describedby="savedSearchTitle">
<div

View file

@ -7,8 +7,8 @@
*/
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
import type { Query, TimeRange, AggregateQuery } from '@kbn/es-query';
import { DataViewType, type DataView } from '@kbn/data-views-plugin/public';
import type { AggregateQuery, Query, TimeRange } from '@kbn/es-query';
import { type DataView, DataViewType } from '@kbn/data-views-plugin/public';
import type { DataViewPickerProps } from '@kbn/unified-search-plugin/public';
import { ENABLE_ESQL } from '@kbn/discover-utils';
import { useSavedSearchInitial } from '../../services/discover_state_provider';
@ -33,6 +33,8 @@ export interface DiscoverTopNavProps {
textBasedLanguageModeErrors?: Error;
textBasedLanguageModeWarning?: string;
onFieldEdited: () => Promise<void>;
isLoading?: boolean;
onCancelClick?: () => void;
}
export const DiscoverTopNav = ({
@ -42,6 +44,8 @@ export const DiscoverTopNav = ({
textBasedLanguageModeErrors,
textBasedLanguageModeWarning,
onFieldEdited,
isLoading,
onCancelClick,
}: DiscoverTopNavProps) => {
const query = useAppStateSelector((state) => state.query);
const adHocDataViews = useInternalStateSelector((state) => state.adHocDataViews);
@ -201,6 +205,8 @@ export const DiscoverTopNav = ({
appName="discover"
indexPatterns={[dataView]}
onQuerySubmit={updateQuery}
onCancel={onCancelClick}
isLoading={isLoading}
onSavedQueryIdChange={updateSavedQueryId}
query={query}
savedQueryId={savedQuery}

View file

@ -115,7 +115,7 @@ export function sendLoadingMoreFinishedMsg(
/**
* Send ERROR message
*/
export function sendErrorMsg(data$: DataMain$ | DataDocuments$ | DataTotalHits$, error: Error) {
export function sendErrorMsg(data$: DataMain$ | DataDocuments$ | DataTotalHits$, error?: Error) {
const recordRawType = data$.getValue().recordRawType;
data$.next({
fetchStatus: FetchStatus.ERROR,

View file

@ -123,6 +123,17 @@ export interface DiscoverDataStateContainer {
* resetting all data observable to initial state
*/
reset: (savedSearch: SavedSearch) => void;
/**
* cancels the running queries
*/
cancel: () => void;
/**
* gets active AbortController for running queries
*/
getAbortController: () => AbortController;
/**
* Available Inspector Adaptor allowing to get details about recent requests to ES
*/
@ -314,6 +325,15 @@ export function getDataStateContainer({
sendResetMsg(dataSubjects, getInitialFetchStatus(), recordType);
};
const cancel = () => {
abortController?.abort();
abortControllerFetchMore?.abort();
};
const getAbortController = () => {
return abortController;
};
return {
fetch: fetchQuery,
fetchMore,
@ -324,5 +344,7 @@ export function getDataStateContainer({
reset,
inspectorAdapters,
getInitialFetchStatus,
cancel,
getAbortController,
};
}

View file

@ -0,0 +1,72 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import expect from '@kbn/expect';
import { FtrProviderContext } from '../ftr_provider_context';
export default function ({ getService, getPageObjects }: FtrProviderContext) {
const retry = getService('retry');
const esArchiver = getService('esArchiver');
const kibanaServer = getService('kibanaServer');
const filterBar = getService('filterBar');
const testSubjects = getService('testSubjects');
const PageObjects = getPageObjects(['common', 'discover', 'timePicker', 'header']);
describe('Discover request cancellation', () => {
before(async () => {
await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover');
await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional');
await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings();
});
after(async () => {
await kibanaServer.importExport.unload('test/functional/fixtures/kbn_archiver/discover');
await esArchiver.unload('test/functional/fixtures/es_archiver/logstash_functional');
await kibanaServer.savedObjects.cleanStandardList();
await PageObjects.timePicker.resetDefaultAbsoluteRangeViaUiSettings();
});
beforeEach(async () => {
await PageObjects.common.navigateToApp('discover');
});
it('should allow cancelling active requests', async () => {
await PageObjects.discover.selectIndexPattern('logstash-*');
await PageObjects.header.waitUntilLoadingHasFinished();
expect(await PageObjects.discover.hasNoResults()).to.be(false);
await testSubjects.existOrFail('querySubmitButton');
await testSubjects.missingOrFail('queryCancelButton');
await filterBar.addDslFilter(
JSON.stringify({
error_query: {
indices: [
{
error_type: 'none',
name: 'logstash-*',
stall_time_seconds: 30,
},
],
},
}),
false
);
await retry.try(async () => {
await testSubjects.missingOrFail('querySubmitButton');
await testSubjects.existOrFail('queryCancelButton');
});
await testSubjects.click('queryCancelButton');
await retry.try(async () => {
expect(await PageObjects.discover.hasNoResults()).to.be(true);
await testSubjects.existOrFail('querySubmitButton');
await testSubjects.missingOrFail('queryCancelButton');
});
await filterBar.removeAllFilters();
});
});
}

View file

@ -34,5 +34,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) {
loadTestFile(require.resolve('./_hide_announcements'));
loadTestFile(require.resolve('./_data_view_edit'));
loadTestFile(require.resolve('./_field_list_new_fields'));
loadTestFile(require.resolve('./_request_cancellation'));
});
}

View file

@ -199,7 +199,7 @@ export class DiscoverPageObject extends FtrService {
}
public async clickHistogramBar() {
await this.elasticChart.waitForRenderComplete();
await this.elasticChart.waitForRenderComplete(undefined, 5000);
const el = await this.elasticChart.getCanvas();
await this.browser.getActions().move({ x: 0, y: 0, origin: el._webElement }).click().perform();

View file

@ -321,7 +321,7 @@ export class FilterBarService extends FtrService {
await this.addFilterAndSelectDataView(null, filter);
}
public async addDslFilter(value: string) {
public async addDslFilter(value: string, waitUntilLoadingHasFinished = true) {
await this.testSubjects.click('addFilter');
await this.testSubjects.click('editQueryDSL');
await this.monacoEditor.waitCodeEditorReady('addFilterPopover');
@ -331,7 +331,9 @@ export class FilterBarService extends FtrService {
await this.retry.try(async () => {
await this.testSubjects.waitForDeleted('saveFilter');
});
await this.header.waitUntilLoadingHasFinished();
if (waitUntilLoadingHasFinished) {
await this.header.waitUntilLoadingHasFinished();
}
}
/**

View file

@ -40,10 +40,11 @@ export class ElasticChartService extends FtrService {
return await this.find.existsByCssSelector('.echChart canvas:last-of-type');
}
public async waitForRenderComplete(dataTestSubj?: string) {
const chart = await this.getChart(dataTestSubj);
public async waitForRenderComplete(dataTestSubj?: string, timeout?: number) {
const chart = await this.getChart(dataTestSubj, timeout);
const rendered = await chart.findAllByCssSelector(
'.echChartStatus[data-ech-render-complete=true]'
'.echChartStatus[data-ech-render-complete=true]',
timeout
);
expect(rendered.length).to.equal(1);
}