Skip to content

Commit

Permalink
[Timelion] Cancel discarded searches (#125255)
Browse files Browse the repository at this point in the history
Co-authored-by: Kibana Machine <[email protected]>
  • Loading branch information
alexwizp and kibanamachine authored Mar 1, 2022
1 parent 7bea08f commit 25b97bb
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,10 @@ export function getTimelionRequestHandler({
uiSettings,
http,
timefilter,
}: TimelionVisDependencies) {
expressionAbortSignal,
}: TimelionVisDependencies & {
expressionAbortSignal: AbortSignal;
}) {
const timezone = getTimezone(uiSettings);

return async function ({
Expand All @@ -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(
Expand All @@ -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 {
Expand All @@ -124,6 +131,7 @@ export function getTimelionRequestHandler({
}),
}),
context: executionContext,
signal: abortController.signal,
});
} catch (e) {
if (e && e.body) {
Expand All @@ -142,6 +150,7 @@ export function getTimelionRequestHandler({
// call `untrack` if this search still belongs to current session
untrackSearch();
}
expressionAbortSignal.removeEventListener('abort', expressionAbortHandler);
}
};
}
38 changes: 24 additions & 14 deletions src/plugins/vis_types/timelion/public/timelion_vis_fn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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,
Expand All @@ -77,25 +79,33 @@ export const getTimelionVisualizationConfig = (
(variables?.embeddableTitle as string) ??
getExecutionContext?.()?.description,
};
let visData: TimelionRenderValue['visData'];

if (!expressionAbortSignal.aborted) {
const timelionRequestHandler = getTimelionRequestHandler({
...dependencies,
expressionAbortSignal,
});

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(),
});
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(),
});

response.visType = TIMELION_VIS_NAME;
visData.visType = TIMELION_VIS_NAME;
}

return {
type: 'render',
as: 'timelion_vis',
value: {
visParams,
visType: TIMELION_VIS_NAME,
visData: response,
visData,
},
};
},
Expand Down
18 changes: 10 additions & 8 deletions src/plugins/vis_types/timelion/public/timelion_vis_renderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ describe('es', () => {
getIndexPatternsService: () => ({
find: async () => [],
}),
request: {
events: {
aborted$: of(),
},
body: {},
},
};
}

Expand All @@ -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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand Down Expand Up @@ -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();

Expand Down

0 comments on commit 25b97bb

Please sign in to comment.