Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) &gt; [SearchInterceptor](./kibana-plugin-plugins-data-public.searchinterceptor.md) &gt; [getSerializableOptions](./kibana-plugin-plugins-data-public.searchinterceptor.getserializableoptions.md)

## SearchInterceptor.getSerializableOptions() method

<b>Signature:</b>

```typescript
protected getSerializableOptions(options?: ISearchOptions): Pick<ISearchOptions, "strategy" | "sessionId" | "isStored" | "isRestore" | "legacyHitsTotal">;
```

## Parameters

| Parameter | Type | Description |
| --- | --- | --- |
| options | <code>ISearchOptions</code> | |

<b>Returns:</b>

`Pick<ISearchOptions, "strategy" | "sessionId" | "isStored" | "isRestore" | "legacyHitsTotal">`

Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export declare class SearchInterceptor

| Method | Modifiers | Description |
| --- | --- | --- |
| [getSerializableOptions(options)](./kibana-plugin-plugins-data-public.searchinterceptor.getserializableoptions.md) | | |
| [getTimeoutMode()](./kibana-plugin-plugins-data-public.searchinterceptor.gettimeoutmode.md) | | |
| [handleSearchError(e, options, isTimeout)](./kibana-plugin-plugins-data-public.searchinterceptor.handlesearcherror.md) | | |
| [search(request, options)](./kibana-plugin-plugins-data-public.searchinterceptor.search.md) | | Searches using the given <code>search</code> method. Overrides the <code>AbortSignal</code> with one that will abort either when the request times out, or when the original <code>AbortSignal</code> is aborted. Updates <code>pendingCount$</code> when the request is started/finalized. |
Expand Down
1 change: 1 addition & 0 deletions examples/search_examples/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export interface IMyStrategyRequest extends IEsSearchRequest {
}
export interface IMyStrategyResponse extends IEsSearchResponse {
cool: string;
executed_at: number;
}

export const SERVER_SEARCH_ROUTE_PATH = '/api/examples/search';
67 changes: 61 additions & 6 deletions examples/search_examples/public/search/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export const SearchExamplesApp = ({
setSelectedNumericField(fields?.length ? getNumeric(fields)[0] : null);
}, [fields]);

const doAsyncSearch = async (strategy?: string) => {
const doAsyncSearch = async (strategy?: string, sessionId?: string) => {
if (!indexPattern || !selectedNumericField) return;

// Construct the query portion of the search request
Expand All @@ -138,6 +138,7 @@ export const SearchExamplesApp = ({
const searchSubscription$ = data.search
.search(req, {
strategy,
sessionId,
})
.subscribe({
next: (res) => {
Expand All @@ -148,19 +149,30 @@ export const SearchExamplesApp = ({
? // @ts-expect-error @elastic/elasticsearch no way to declare a type for aggregation in the search response
res.rawResponse.aggregations[1].value
: undefined;
const isCool = (res as IMyStrategyResponse).cool;
const executedAt = (res as IMyStrategyResponse).executed_at;
const message = (
<EuiText>
Searched {res.rawResponse.hits.total} documents. <br />
The average of {selectedNumericField!.name} is{' '}
{avgResult ? Math.floor(avgResult) : 0}.
<br />
Is it Cool? {String((res as IMyStrategyResponse).cool)}
{isCool ? `Is it Cool? ${isCool}` : undefined}
<br />
<EuiText data-test-subj="requestExecutedAt">
{executedAt ? `Executed at? ${executedAt}` : undefined}
</EuiText>
</EuiText>
);
notifications.toasts.addSuccess({
title: 'Query result',
text: mountReactNode(message),
});
notifications.toasts.addSuccess(
{
title: 'Query result',
text: mountReactNode(message),
},
{
toastLifeTimeMs: 300000,
}
);
searchSubscription$.unsubscribe();
} else if (isErrorResponse(res)) {
// TODO: Make response error status clearer
Expand Down Expand Up @@ -227,6 +239,10 @@ export const SearchExamplesApp = ({
doAsyncSearch('myStrategy');
};

const onClientSideSessionCacheClickHandler = () => {
doAsyncSearch('myStrategy', data.search.session.getSessionId());
};

const onServerClickHandler = async () => {
if (!indexPattern || !selectedNumericField) return;
try {
Expand Down Expand Up @@ -374,6 +390,45 @@ export const SearchExamplesApp = ({
</EuiButtonEmpty>
</EuiText>
<EuiSpacer />
<EuiTitle size="s">
<h3>Client side search session caching</h3>
</EuiTitle>
<EuiText>
<EuiButtonEmpty
size="xs"
onClick={() => data.search.session.start()}
iconType="alert"
data-test-subj="searchExamplesStartSession"
>
<FormattedMessage
id="searchExamples.startNewSession"
defaultMessage="Start a new session"
/>
</EuiButtonEmpty>
<EuiButtonEmpty
size="xs"
onClick={() => data.search.session.clear()}
iconType="alert"
data-test-subj="searchExamplesClearSession"
>
<FormattedMessage
id="searchExamples.clearSession"
defaultMessage="Clear session"
/>
</EuiButtonEmpty>
<EuiButtonEmpty
size="xs"
onClick={onClientSideSessionCacheClickHandler}
iconType="play"
data-test-subj="searchExamplesCacheSearch"
>
<FormattedMessage
id="searchExamples.myStrategyButtonText"
defaultMessage="Request from low-level client via My Strategy"
/>
</EuiButtonEmpty>
</EuiText>
<EuiSpacer />
<EuiTitle size="s">
<h3>Using search on the server</h3>
</EuiTitle>
Expand Down
1 change: 1 addition & 0 deletions examples/search_examples/server/my_strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const mySearchStrategyProvider = (
map((esSearchRes) => ({
...esSearchRes,
cool: request.get_cool ? 'YES' : 'NOPE',
executed_at: new Date().getTime(),
}))
),
cancel: async (id, options, deps) => {
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/data/common/search/tabify/tabify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ export function tabifyAggResponse(
const write = new TabbedAggResponseWriter(aggConfigs, respOpts || {});
const topLevelBucket: AggResponseBucket = {
...esResponse.aggregations,
doc_count: esResponse.hits.total,
doc_count: esResponse.hits?.total,
};

collectBucket(aggConfigs, write, topLevelBucket, '', 1);
Expand Down
2 changes: 2 additions & 0 deletions src/plugins/data/public/public.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -2361,6 +2361,8 @@ export class SearchInterceptor {
// (undocumented)
protected readonly deps: SearchInterceptorDeps;
// (undocumented)
protected getSerializableOptions(options?: ISearchOptions): Pick<ISearchOptions, "strategy" | "sessionId" | "isStored" | "isRestore" | "legacyHitsTotal">;
// (undocumented)
protected getTimeoutMode(): TimeoutErrorMode;
// Warning: (ae-forgotten-export) The symbol "KibanaServerError" needs to be exported by the entry point index.d.ts
// Warning: (ae-forgotten-export) The symbol "AbortError" needs to be exported by the entry point index.d.ts
Expand Down
28 changes: 17 additions & 11 deletions src/plugins/data/public/search/search_interceptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,20 +113,14 @@ export class SearchInterceptor {
}
}

/**
* @internal
* @throws `AbortError` | `ErrorLike`
*/
protected runSearch(
request: IKibanaSearchRequest,
options?: ISearchOptions
): Promise<IKibanaSearchResponse> {
const { abortSignal, sessionId, ...requestOptions } = options || {};
protected getSerializableOptions(options?: ISearchOptions) {
const { sessionId, ...requestOptions } = options || {};

const serializableOptions: ISearchOptionsSerializable = {};
const combined = {
...requestOptions,
...this.deps.session.getSearchOptions(sessionId),
};
const serializableOptions: ISearchOptionsSerializable = {};

if (combined.sessionId !== undefined) serializableOptions.sessionId = combined.sessionId;
if (combined.isRestore !== undefined) serializableOptions.isRestore = combined.isRestore;
Expand All @@ -135,10 +129,22 @@ export class SearchInterceptor {
if (combined.strategy !== undefined) serializableOptions.strategy = combined.strategy;
if (combined.isStored !== undefined) serializableOptions.isStored = combined.isStored;

return serializableOptions;
}

/**
* @internal
* @throws `AbortError` | `ErrorLike`
*/
protected runSearch(
request: IKibanaSearchRequest,
options?: ISearchOptions
): Promise<IKibanaSearchResponse> {
const { abortSignal } = options || {};
return this.batchedFetch(
{
request,
options: serializableOptions,
options: this.getSerializableOptions(options),
},
abortSignal
);
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/data/public/search/session/session_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export interface SearchSessionIndicatorUiConfig {
}

/**
* Responsible for tracking a current search session. Supports only a single session at a time.
* Responsible for tracking a current search session. Supports a single session at a time.
*/
export class SessionService {
public readonly state$: Observable<SearchSessionState>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,24 @@ describe('search abort controller', () => {
test('immediately aborts when passed an aborted signal in the constructor', () => {
const controller = new AbortController();
controller.abort();
const sac = new SearchAbortController(controller.signal);
const sac = new SearchAbortController();
sac.addAbortSignal(controller.signal);
expect(sac.getSignal().aborted).toBe(true);
});

test('aborts when input signal is aborted', () => {
const controller = new AbortController();
const sac = new SearchAbortController(controller.signal);
const sac = new SearchAbortController();
sac.addAbortSignal(controller.signal);
expect(sac.getSignal().aborted).toBe(false);
controller.abort();
expect(sac.getSignal().aborted).toBe(true);
});

test('aborts when all input signals are aborted', () => {
const controller = new AbortController();
const sac = new SearchAbortController(controller.signal);
const sac = new SearchAbortController();
sac.addAbortSignal(controller.signal);

const controller2 = new AbortController();
sac.addAbortSignal(controller2.signal);
Expand All @@ -48,7 +51,8 @@ describe('search abort controller', () => {

test('aborts explicitly even if all inputs are not aborted', () => {
const controller = new AbortController();
const sac = new SearchAbortController(controller.signal);
const sac = new SearchAbortController();
sac.addAbortSignal(controller.signal);

const controller2 = new AbortController();
sac.addAbortSignal(controller2.signal);
Expand All @@ -60,7 +64,8 @@ describe('search abort controller', () => {

test('doesnt abort, if cleared', () => {
const controller = new AbortController();
const sac = new SearchAbortController(controller.signal);
const sac = new SearchAbortController();
sac.addAbortSignal(controller.signal);
expect(sac.getSignal().aborted).toBe(false);
sac.cleanup();
controller.abort();
Expand All @@ -77,15 +82,15 @@ describe('search abort controller', () => {
});

test('doesnt abort on timeout, if cleared', () => {
const sac = new SearchAbortController(undefined, 100);
const sac = new SearchAbortController(100);
expect(sac.getSignal().aborted).toBe(false);
sac.cleanup();
timeTravel(100);
expect(sac.getSignal().aborted).toBe(false);
});

test('aborts on timeout, even if no signals passed in', () => {
const sac = new SearchAbortController(undefined, 100);
const sac = new SearchAbortController(100);
expect(sac.getSignal().aborted).toBe(false);
timeTravel(100);
expect(sac.getSignal().aborted).toBe(true);
Expand All @@ -94,7 +99,8 @@ describe('search abort controller', () => {

test('aborts on timeout, even if there are unaborted signals', () => {
const controller = new AbortController();
const sac = new SearchAbortController(controller.signal, 100);
const sac = new SearchAbortController(100);
sac.addAbortSignal(controller.signal);

expect(sac.getSignal().aborted).toBe(false);
timeTravel(100);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,7 @@ export class SearchAbortController {
private destroyed = false;
private reason?: AbortReason;

constructor(abortSignal?: AbortSignal, timeout?: number) {
if (abortSignal) {
this.addAbortSignal(abortSignal);
}

constructor(timeout?: number) {
if (timeout) {
this.timeoutSub = timer(timeout).subscribe(() => {
this.reason = AbortReason.Timeout;
Expand All @@ -41,6 +37,7 @@ export class SearchAbortController {
};

public cleanup() {
if (this.destroyed) return;
this.destroyed = true;
this.timeoutSub?.unsubscribe();
this.inputAbortSignals.forEach((abortSignal) => {
Expand Down
Loading