Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[data.search] Add search session methods to search service contract #87966

Merged
merged 32 commits into from
Feb 3, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
c22024f
[data.search] Add search session methods to search service contract
lukasolson Jan 12, 2021
1046c54
Merge branch 'master' into search-session-api
lukasolson Jan 15, 2021
7040716
Fix types
lukasolson Jan 15, 2021
eeddf84
Fix tests and switch to cancel
lukasolson Jan 15, 2021
f09eb38
Update docs
lukasolson Jan 15, 2021
e6124af
Merge branch 'master' into search-session-api
lukasolson Jan 19, 2021
e160019
Fix types/tests
lukasolson Jan 19, 2021
3c08c3d
Fix tests
lukasolson Jan 19, 2021
0cd92ed
Update status of SO before cancelling search requests
lukasolson Jan 19, 2021
f1a1d0b
Add API integration test
lukasolson Jan 19, 2021
552e545
Merge branch 'master' into search-session-api
lukasolson Jan 20, 2021
ee6e337
Merge branch 'master' into search-session-api
lukasolson Jan 25, 2021
32315b7
Fix types
lukasolson Jan 25, 2021
542de95
Update expiration route to use config defaultExpiration
lukasolson Jan 25, 2021
5b22249
Fix test
lukasolson Jan 25, 2021
66420b4
Update docs
lukasolson Jan 25, 2021
8a5e3d3
New logic for extend
lukasolson Jan 26, 2021
71582c7
Remove declare module
lukasolson Jan 26, 2021
65840b3
Review feedback
lukasolson Jan 26, 2021
fdb940b
Merge branch 'master' of github.com:elastic/kibana into pr/87966
Jan 27, 2021
b2bce4b
fix ts
Jan 27, 2021
50081b5
Merge branch 'master' into search-session-api
lukasolson Jan 28, 2021
e24181c
Remove test that is no longer valid
lukasolson Jan 28, 2021
08ed572
Merge remote-tracking branch 'origin/search-session-api' into search-…
lukasolson Jan 28, 2021
44ef260
Merge branch 'master' into search-session-api
lukasolson Jan 28, 2021
99b4ca0
Merge branch 'master' into search-session-api
lukasolson Feb 1, 2021
99dc347
Fix undefined bug
lukasolson Feb 1, 2021
e582910
Merge branch 'master' into search-session-api
lukasolson Feb 2, 2021
fbfc1b1
Fix types
lukasolson Feb 2, 2021
5c564c2
Merge branch 'master' of github.com:elastic/kibana into pr/87966
Feb 3, 2021
3eb8f01
Use DataRequestHandlerContext in maps
Feb 3, 2021
6462da6
ts
Feb 3, 2021
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
6 changes: 6 additions & 0 deletions src/plugins/data/public/search/session/sessions_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ export class SessionsClient {
});
}

public extend(sessionId: string, keepAlive: string): Promise<SavedObjectsFindResponse> {
return this.http!.post(`/internal/session/${encodeURIComponent(sessionId)}/_extend`, {
body: JSON.stringify({ keepAlive }),
lukasolson marked this conversation as resolved.
Show resolved Hide resolved
});
}

public delete(sessionId: string): Promise<void> {
return this.http!.delete(`/internal/session/${encodeURIComponent(sessionId)}`);
}
Expand Down
4 changes: 2 additions & 2 deletions src/plugins/data/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,8 +244,8 @@ export {
searchUsageObserver,
shimAbortSignal,
SearchUsage,
SessionService,
ISessionService,
SearchSessionService,
ISearchSessionService,
} from './search';

// Search namespace
Expand Down
144 changes: 100 additions & 44 deletions src/plugins/data/server/search/search_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* under the License.
*/

import { BehaviorSubject, Observable } from 'rxjs';
import { BehaviorSubject, from, Observable } from 'rxjs';
import { pick } from 'lodash';
import {
CoreSetup,
Expand All @@ -29,10 +29,11 @@ import {
SharedGlobalConfig,
StartServicesAccessor,
} from 'src/core/server';
import { catchError, first, map } from 'rxjs/operators';
import { catchError, first, map, switchMap } from 'rxjs/operators';
import { BfetchServerSetup } from 'src/plugins/bfetch/server';
import { ExpressionsServerSetup } from 'src/plugins/expressions/server';
import {
IScopedSearchClient,
ISearchSetup,
ISearchStart,
ISearchStrategy,
Expand All @@ -56,7 +57,6 @@ import {
IEsSearchResponse,
IKibanaSearchRequest,
IKibanaSearchResponse,
ISearchClient,
ISearchOptions,
kibana,
kibanaContext,
Expand All @@ -72,12 +72,13 @@ import {
} from '../../common/search/aggs/buckets/shard_delay';
import { aggShardDelay } from '../../common/search/aggs/buckets/shard_delay_fn';
import { ConfigSchema } from '../../config';
import { SessionService, IScopedSessionService, ISessionService } from './session';
import { ISearchSessionService, SearchSessionService } from './session';
import { KbnServerError } from '../../../kibana_utils/server';
import { tapFirst } from '../../common';

declare module 'src/core/server' {
lukasolson marked this conversation as resolved.
Show resolved Hide resolved
interface RequestHandlerContext {
search?: ISearchClient & { session: IScopedSessionService };
search?: IScopedSearchClient;
lizozom marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand Down Expand Up @@ -107,14 +108,14 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
private readonly searchSourceService = new SearchSourceService();
private defaultSearchStrategyName: string = ES_SEARCH_STRATEGY;
private searchStrategies: StrategyMap = {};
private sessionService: ISessionService;
private coreStart?: CoreStart;
private sessionService: ISearchSessionService;
private asScoped!: (request: KibanaRequest) => IScopedSearchClient;

constructor(
private initializerContext: PluginInitializerContext<ConfigSchema>,
private readonly logger: Logger
) {
this.sessionService = new SessionService();
this.sessionService = new SearchSessionService();
}

public setup(
Expand All @@ -131,14 +132,8 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
registerSearchRoute(router);
registerMsearchRoute(router, routeDependencies);

core.getStartServices().then(([coreStart]) => {
this.coreStart = coreStart;
});

core.http.registerRouteHandlerContext('search', async (context, request) => {
const search = this.asScopedProvider(this.coreStart!)(request);
const session = this.sessionService.asScopedProvider(this.coreStart!)(request);
return { ...search, session };
return this.asScoped(request);
});

this.registerSearchStrategy(
Expand All @@ -154,7 +149,7 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
{ request: IKibanaSearchResponse; options?: ISearchOptions },
any
>('/internal/bsearch', (request) => {
const search = this.asScopedProvider(this.coreStart!)(request);
const search = this.asScoped(request);

return {
onBatchItem: async ({ request: requestData, options }) => {
Expand Down Expand Up @@ -229,15 +224,15 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
{ fieldFormats, indexPatterns }: SearchServiceStartDependencies
): ISearchStart {
const { elasticsearch, savedObjects, uiSettings } = core;
const asScoped = this.asScopedProvider(core);
this.asScoped = this.asScopedProvider(core);
return {
aggs: this.aggsService.start({
fieldFormats,
uiSettings,
indexPatterns,
}),
getSearchStrategy: this.getSearchStrategy,
asScoped,
asScoped: this.asScoped,
searchSource: {
asScoped: async (request: KibanaRequest) => {
const esClient = elasticsearch.client.asScoped(request);
Expand All @@ -256,7 +251,7 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {

const searchSourceDependencies: SearchSourceDependencies = {
getConfig: <T = any>(key: string): T => uiSettingsCache[key],
search: asScoped(request).search,
search: this.asScoped(request).search,
onResponse: (req, res) => shimHitsTotal(res),
legacy: {
callMsearch: getCallMsearch({
Expand Down Expand Up @@ -289,22 +284,50 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
this.searchStrategies[name] = strategy;
};

private search = <
private getSearchStrategy = <
SearchStrategyRequest extends IKibanaSearchRequest = IEsSearchRequest,
SearchStrategyResponse extends IKibanaSearchResponse = IEsSearchResponse
>(
session: IScopedSessionService,
name: string = this.defaultSearchStrategyName
): ISearchStrategy<SearchStrategyRequest, SearchStrategyResponse> => {
this.logger.debug(`Get strategy ${name}`);
const strategy = this.searchStrategies[name];
if (!strategy) {
throw new KbnServerError(`Search strategy ${name} not found`, 404);
}
return strategy;
};

private search = <
SearchStrategyRequest extends IKibanaSearchRequest,
SearchStrategyResponse extends IKibanaSearchResponse
>(
deps: SearchStrategyDependencies,
request: SearchStrategyRequest,
options: ISearchOptions,
deps: SearchStrategyDependencies
options: ISearchOptions
) => {
const strategy = this.getSearchStrategy<SearchStrategyRequest, SearchStrategyResponse>(
options.strategy
);
return session.search(strategy, request, options, deps);

const getSearchRequest = async () =>
!options.sessionId || !options.isRestore || request.id
? request
: {
...request,
id: await deps.searchSessionsClient.getId(request, options),
};

return from(getSearchRequest()).pipe(
switchMap((searchRequest) => strategy.search(searchRequest, options, deps)),
tapFirst((response) => {
if (request.id || !options.sessionId || !response.id || options.isRestore) return;
deps.searchSessionsClient.trackId(request, response.id, options);
})
);
};

private cancel = (id: string, options: ISearchOptions, deps: SearchStrategyDependencies) => {
private cancel = (deps: SearchStrategyDependencies, id: string, options: ISearchOptions = {}) => {
const strategy = this.getSearchStrategy(options.strategy);
if (!strategy.cancel) {
throw new KbnServerError(
Expand All @@ -316,10 +339,10 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
};

private extend = (
deps: SearchStrategyDependencies,
id: string,
keepAlive: string,
options: ISearchOptions,
deps: SearchStrategyDependencies
options: ISearchOptions = {}
) => {
const strategy = this.getSearchStrategy(options.strategy);
if (!strategy.extend) {
Expand All @@ -328,36 +351,69 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
return strategy.extend(id, keepAlive, options, deps);
};

private getSearchStrategy = <
SearchStrategyRequest extends IKibanaSearchRequest = IEsSearchRequest,
SearchStrategyResponse extends IKibanaSearchResponse = IEsSearchResponse
>(
name: string = this.defaultSearchStrategyName
): ISearchStrategy<SearchStrategyRequest, SearchStrategyResponse> => {
this.logger.debug(`Get strategy ${name}`);
const strategy = this.searchStrategies[name];
if (!strategy) {
throw new KbnServerError(`Search strategy ${name} not found`, 404);
private deleteSession = async (deps: SearchStrategyDependencies, sessionId: string) => {
lizozom marked this conversation as resolved.
Show resolved Hide resolved
const searchIdMapping = await deps.searchSessionsClient.getSearchIdMapping(sessionId);

for (const [searchId, strategyName] of searchIdMapping.entries()) {
lukasolson marked this conversation as resolved.
Show resolved Hide resolved
const searchOptions = {
sessionId,
strategy: strategyName,
isStored: true,
};
await this.cancel(deps, searchId, searchOptions);
}
return strategy;

return deps.searchSessionsClient.delete(sessionId);
};

private extendSession = async (
deps: SearchStrategyDependencies,
sessionId: string,
keepAlive: string
) => {
const searchIdMapping = await deps.searchSessionsClient.getSearchIdMapping(sessionId);

for (const [searchId, strategyName] of searchIdMapping.entries()) {
const searchOptions = {
sessionId,
strategy: strategyName,
isStored: true,
};
await this.extend(deps, searchId, keepAlive, searchOptions);
}

return deps.searchSessionsClient.extend(sessionId, keepAlive);
};

private asScopedProvider = (core: CoreStart) => {
const { elasticsearch, savedObjects, uiSettings } = core;
const getSessionAsScoped = this.sessionService.asScopedProvider(core);
return (request: KibanaRequest): ISearchClient => {
const scopedSession = getSessionAsScoped(request);
return (request: KibanaRequest): IScopedSearchClient => {
const savedObjectsClient = savedObjects.getScopedClient(request);
const searchSessionsClient = getSessionAsScoped(request);
const deps = {
searchSessionsClient,
savedObjectsClient,
esClient: elasticsearch.client.asScoped(request),
uiSettingsClient: uiSettings.asScopedToClient(savedObjectsClient),
};
return {
search: (searchRequest, options = {}) =>
this.search(scopedSession, searchRequest, options, deps),
cancel: (id, options = {}) => this.cancel(id, options, deps),
extend: (id, keepAlive, options = {}) => this.extend(id, keepAlive, options, deps),
search: <
SearchStrategyRequest extends IKibanaSearchRequest = IEsSearchRequest,
SearchStrategyResponse extends IKibanaSearchResponse = IEsSearchResponse
>(
searchRequest: SearchStrategyRequest,
options: ISearchOptions = {}
) =>
this.search<SearchStrategyRequest, SearchStrategyResponse>(deps, searchRequest, options),
cancel: this.cancel.bind(this, deps),
extend: this.extend.bind(this, deps),
saveSession: searchSessionsClient.save,
getSession: searchSessionsClient.get,
lizozom marked this conversation as resolved.
Show resolved Hide resolved
findSessions: searchSessionsClient.find,
updateSession: searchSessionsClient.update,
extendSession: this.extendSession.bind(this, deps),
deleteSession: this.deleteSession.bind(this, deps),
};
};
};
Expand Down
20 changes: 2 additions & 18 deletions src/plugins/data/server/search/session/session_service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,6 @@
* under the License.
*/

import { of } from 'rxjs';
import { SearchStrategyDependencies } from '../types';
import { SessionService } from './session_service';
// import { SearchSessionService } from './session_service';

describe('SessionService', () => {
it('search invokes `strategy.search`', async () => {
const service = new SessionService();
const mockSearch = jest.fn().mockReturnValue(of({}));
const mockStrategy = { search: mockSearch };
const mockRequest = { id: 'bar' };
const mockOptions = { sessionId: '1234' };
const mockDeps = { savedObjectsClient: {} } as SearchStrategyDependencies;

await service.search(mockStrategy, mockRequest, mockOptions, mockDeps);

expect(mockSearch).toHaveBeenCalled();
expect(mockSearch).toHaveBeenCalledWith(mockRequest, mockOptions, mockDeps);
});
});
describe('SearchSessionService', () => {});
45 changes: 29 additions & 16 deletions src/plugins/data/server/search/session/session_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,40 @@
* under the License.
*/

import { CoreStart, KibanaRequest } from 'kibana/server';
import { IKibanaSearchRequest, IKibanaSearchResponse } from '../../../common';
import { ISearchStrategy } from '../types';
import { ISessionService } from './types';
import { ISearchSessionService } from './types';

/**
* The OSS session service. See data_enhanced in X-Pack for the search session service.
* The OSS session service, which leaves most search session-related logic unimplemented.
* @see x-pack/plugins/data_enhanced/server/search/session/session_service.ts
lukasolson marked this conversation as resolved.
Show resolved Hide resolved
*/
export class SessionService implements ISessionService {
export class SearchSessionService implements ISearchSessionService {
constructor() {}

public search<Request extends IKibanaSearchRequest, Response extends IKibanaSearchResponse>(
strategy: ISearchStrategy<Request, Response>,
...args: Parameters<ISearchStrategy<Request, Response>['search']>
) {
return strategy.search(...args);
}

public asScopedProvider(core: CoreStart) {
return (request: KibanaRequest) => ({
search: this.search,
public asScopedProvider() {
return () => ({
getId: () => {
throw new Error('getId not implemented in OSS search session service');
},
trackId: async () => {},
getSearchIdMapping: async () => new Map<string, string>(),
save: async () => {
throw new Error('save not implemented in OSS search session service');
},
get: async () => {
throw new Error('get not implemented in OSS search session service');
},
find: async () => {
throw new Error('find not implemented in OSS search session service');
},
update: async () => {
throw new Error('update not implemented in OSS search session service');
},
extend: async () => {
throw new Error('extend not implemented in OSS search session service');
},
delete: async () => {
throw new Error('delete not implemented in OSS search session service');
},
});
}
}
Loading