[Timelion] Cancel discarded searches (#125255)

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Alexey Antonov 2022-03-01 13:33:44 +03:00 committed by GitHub
parent 7bea08f1a4
commit 25b97bbac1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 76 additions and 31 deletions

View file

@ -83,11 +83,17 @@ function TimelionExpressionInput({ value, setValue }: TimelionExpressionInputPro
);
useEffect(() => {
const abortController = new AbortController();
if (kibana.services.http) {
kibana.services.http.get<ITimelionFunction[]>('../api/timelion/functions').then((data) => {
functionList.current = data;
});
kibana.services.http
.get<ITimelionFunction[]>('../api/timelion/functions', { signal: abortController.signal })
.then((data) => {
functionList.current = data;
});
}
return () => {
abortController.abort();
};
}, [kibana.services.http]);
return (

View file

@ -54,7 +54,10 @@ export function getTimelionRequestHandler({
uiSettings,
http,
timefilter,
}: TimelionVisDependencies) {
expressionAbortSignal,
}: TimelionVisDependencies & {
expressionAbortSignal: AbortSignal;
}) {
const timezone = getTimezone(uiSettings);
return async function ({
@ -74,6 +77,12 @@ export function getTimelionRequestHandler({
}): Promise<TimelionSuccessResponse> {
const dataSearch = getDataSearch();
const expression = visParams.expression;
const abortController = new AbortController();
const expressionAbortHandler = function () {
abortController.abort();
};
expressionAbortSignal.addEventListener('abort', expressionAbortHandler);
if (!expression) {
throw new Error(
@ -98,9 +107,7 @@ export function getTimelionRequestHandler({
const untrackSearch =
dataSearch.session.isCurrentSession(searchSessionId) &&
dataSearch.session.trackSearch({
abort: () => {
// TODO: support search cancellations
},
abort: () => abortController.abort(),
});
try {
@ -124,6 +131,7 @@ export function getTimelionRequestHandler({
}),
}),
context: executionContext,
signal: abortController.signal,
});
} catch (e) {
if (e && e.body) {
@ -142,6 +150,7 @@ export function getTimelionRequestHandler({
// call `untrack` if this search still belongs to current session
untrackSearch();
}
expressionAbortSignal.removeEventListener('abort', expressionAbortHandler);
}
};
}

View file

@ -18,7 +18,7 @@ import { KibanaContext, Query, TimeRange } from '../../../data/public';
type Input = KibanaContext | null;
type Output = Promise<Render<TimelionRenderValue>>;
export interface TimelionRenderValue {
visData: TimelionSuccessResponse;
visData?: TimelionSuccessResponse;
visType: 'timelion';
visParams: TimelionVisParams;
}
@ -65,10 +65,12 @@ export const getTimelionVisualizationConfig = (
required: false,
},
},
async fn(input, args, { getSearchSessionId, getExecutionContext, variables }) {
async fn(
input,
args,
{ getSearchSessionId, getExecutionContext, variables, abortSignal: expressionAbortSignal }
) {
const { getTimelionRequestHandler } = await import('./async_services');
const timelionRequestHandler = getTimelionRequestHandler(dependencies);
const visParams = {
expression: args.expression,
interval: args.interval,
@ -77,17 +79,25 @@ export const getTimelionVisualizationConfig = (
(variables?.embeddableTitle as string) ??
getExecutionContext?.()?.description,
};
let visData: TimelionRenderValue['visData'];
const response = await timelionRequestHandler({
timeRange: get(input, 'timeRange') as TimeRange,
query: get(input, 'query') as Query,
filters: get(input, 'filters') as Filter[],
visParams,
searchSessionId: getSearchSessionId(),
executionContext: getExecutionContext(),
});
if (!expressionAbortSignal.aborted) {
const timelionRequestHandler = getTimelionRequestHandler({
...dependencies,
expressionAbortSignal,
});
response.visType = TIMELION_VIS_NAME;
visData = await timelionRequestHandler({
timeRange: get(input, 'timeRange') as TimeRange,
query: get(input, 'query') as Query,
filters: get(input, 'filters') as Filter[],
visParams,
searchSessionId: getSearchSessionId(),
executionContext: getExecutionContext(),
});
visData.visType = TIMELION_VIS_NAME;
}
return {
type: 'render',
@ -95,7 +105,7 @@ export const getTimelionVisualizationConfig = (
value: {
visParams,
visType: TIMELION_VIS_NAME,
visData: response,
visData,
},
};
},

View file

@ -33,7 +33,7 @@ export const getTimelionVisRenderer: (
unmountComponentAtNode(domNode);
});
const [seriesList] = visData.sheet;
const seriesList = visData?.sheet[0];
const showNoResult = !seriesList || !seriesList.list.length;
const VisComponent = deps.uiSettings.get(UI_SETTINGS.LEGACY_CHARTS_LIBRARY, false)
@ -62,13 +62,15 @@ export const getTimelionVisRenderer: (
<VisualizationContainer handlers={handlers} showNoResult={showNoResult}>
<KibanaThemeProvider theme$={deps.theme.theme$}>
<KibanaContextProvider services={{ ...deps }}>
<VisComponent
interval={visParams.interval}
ariaLabel={visParams.ariaLabel}
seriesList={seriesList}
renderComplete={handlers.done}
onBrushEvent={onBrushEvent}
/>
{seriesList && (
<VisComponent
interval={visParams.interval}
ariaLabel={visParams.ariaLabel}
seriesList={seriesList}
renderComplete={handlers.done}
onBrushEvent={onBrushEvent}
/>
)}
</KibanaContextProvider>
</KibanaThemeProvider>
</VisualizationContainer>,

View file

@ -28,6 +28,12 @@ describe('es', () => {
getIndexPatternsService: () => ({
find: async () => [],
}),
request: {
events: {
aborted$: of(),
},
body: {},
},
};
}
@ -46,9 +52,11 @@ describe('es', () => {
});
test('should call data search with sessionId, isRestore and isStored', async () => {
const baseTlConfig = stubRequestAndServer({ rawResponse: esResponse });
tlConfig = {
...stubRequestAndServer({ rawResponse: esResponse }),
...baseTlConfig,
request: {
...baseTlConfig.request,
body: {
searchSession: {
sessionId: '1',

View file

@ -12,6 +12,12 @@ import Datasource from '../../lib/classes/datasource';
import buildRequest from './lib/build_request';
import toSeriesList from './lib/agg_response_to_series_list';
function getRequestAbortedSignal(aborted$) {
const controller = new AbortController();
aborted$.subscribe(() => controller.abort());
return controller.signal;
}
export default new Datasource('es', {
hideFitArg: true,
args: [
@ -107,13 +113,17 @@ export default new Datasource('es', {
const body = buildRequest(config, tlConfig, scriptFields, runtimeFields, esShardTimeout);
// User may abort the request without waiting for the results
// we need to handle this scenario by aborting underlying server requests
const abortSignal = getRequestAbortedSignal(tlConfig.request.events.aborted$);
const resp = await tlConfig.context.search
.search(
body,
{
...tlConfig.request?.body.searchSession,
},
tlConfig.context
{ ...tlConfig.context, abortSignal }
)
.toPromise();