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.session] Use locators instead of URL generators #115681

Merged
merged 12 commits into from
Oct 26, 2021
10 changes: 5 additions & 5 deletions dev_docs/tutorials/data/search.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ tags: ['kibana', 'onboarding', 'dev', 'tutorials', 'search', 'sessions', 'search

Searching data stored in Elasticsearch can be done in various ways, for example using the Elasticsearch REST API or using an `Elasticsearch Client` for low level access.

However, the recommended and easist way to search Elasticsearch is by using the low level search service. The service is exposed from the `data` plugin, and by using it, you not only gain access to the data you stored, but also to capabilities, such as Custom Search Strategies, Asynchronous Search, Partial Results, Search Sessions, and more.
However, the recommended and easiest way to search Elasticsearch is by using the low level search service. The service is exposed from the `data` plugin, and by using it, you not only gain access to the data you stored, but also to capabilities, such as Custom Search Strategies, Asynchronous Search, Partial Results, Search Sessions, and more.

Here is a basic example for using the `data.search` service from a custom plugin:

Expand Down Expand Up @@ -418,11 +418,11 @@ export class MyPlugin implements Plugin {
// return the name you want to give the saved Search Session
return `MyApp_${Math.random()}`;
},
getUrlGeneratorData: async () => {
getLocatorData: async () => {
return {
urlGeneratorId: MY_URL_GENERATOR,
initialState: getUrlGeneratorState({ ...deps, shouldRestoreSearchSession: false }),
restoreState: getUrlGeneratorState({ ...deps, shouldRestoreSearchSession: true }),
id: MY_LOCATOR,
initialState: getLocatorParams({ ...deps, shouldRestoreSearchSession: false }),
restoreState: getLocatorParams({ ...deps, shouldRestoreSearchSession: true }),
};
},
});
Expand Down
20 changes: 8 additions & 12 deletions examples/search_examples/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@

import {
AppMountParameters,
AppNavLinkStatus,
CoreSetup,
CoreStart,
Plugin,
AppNavLinkStatus,
} from '../../../src/core/public';
import {
SearchExamplesPluginSetup,
SearchExamplesPluginStart,
AppPluginSetupDependencies,
AppPluginStartDependencies,
SearchExamplesPluginSetup,
SearchExamplesPluginStart,
} from './types';
import { createSearchSessionsExampleUrlGenerator } from './search_sessions/url_generator';
import { SearchSessionsExamplesAppLocatorDefinition } from './search_sessions/app_locator';
import { PLUGIN_NAME } from '../common';
import img from './search_examples.png';

Expand Down Expand Up @@ -67,14 +67,10 @@ export class SearchExamplesPlugin
],
});

// we need an URL generator for search session examples for restoring a search session
share.urlGenerators.registerUrlGenerator(
createSearchSessionsExampleUrlGenerator(() => {
return core
.getStartServices()
.then(([coreStart]) => ({ appBasePath: coreStart.http.basePath.get() }));
})
);
// we need an locator for search session examples for restoring a search session
const getAppBasePath = () =>
core.getStartServices().then(([coreStart]) => coreStart.http.basePath.get());
share.url.locators.create(new SearchSessionsExamplesAppLocatorDefinition(getAppBasePath));

return {};
}
Expand Down
14 changes: 5 additions & 9 deletions examples/search_examples/public/search_sessions/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,7 @@ import {
createStateContainer,
useContainerState,
} from '../../../../src/plugins/kibana_utils/public';
import {
getInitialStateFromUrl,
SEARCH_SESSIONS_EXAMPLES_APP_URL_GENERATOR,
SearchSessionExamplesUrlGeneratorState,
} from './url_generator';
import { getInitialStateFromUrl, SEARCH_SESSIONS_EXAMPLES_APP_LOCATOR } from './app_locator';

interface SearchSessionsExampleAppDeps {
notifications: CoreStart['notifications'];
Expand Down Expand Up @@ -140,23 +136,23 @@ export const SearchSessionsExampleApp = ({
const enableSessionStorage = useCallback(() => {
data.search.session.enableStorage({
getName: async () => 'Search sessions example',
getUrlGeneratorData: async () => ({
getLocatorData: async () => ({
initialState: {
time: data.query.timefilter.timefilter.getTime(),
filters: data.query.filterManager.getFilters(),
query: data.query.queryString.getQuery(),
indexPatternId: indexPattern?.id,
numericFieldName,
} as SearchSessionExamplesUrlGeneratorState,
},
restoreState: {
time: data.query.timefilter.timefilter.getAbsoluteTime(),
filters: data.query.filterManager.getFilters(),
query: data.query.queryString.getQuery(),
indexPatternId: indexPattern?.id,
numericFieldName,
searchSessionId: data.search.session.getSessionId(),
} as SearchSessionExamplesUrlGeneratorState,
urlGeneratorId: SEARCH_SESSIONS_EXAMPLES_APP_URL_GENERATOR,
},
id: SEARCH_SESSIONS_EXAMPLES_APP_LOCATOR,
}),
});
}, [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,50 +6,50 @@
* Side Public License, v 1.
*/

import { TimeRange, Filter, Query, esFilters } from '../../../../src/plugins/data/public';
import { SerializableRecord } from '@kbn/utility-types';
import { esFilters, Filter, Query, TimeRange } from '../../../../src/plugins/data/public';
import { getStatesFromKbnUrl, setStateToKbnUrl } from '../../../../src/plugins/kibana_utils/public';
import { UrlGeneratorsDefinition } from '../../../../src/plugins/share/public';
import { LocatorDefinition } from '../../../../src/plugins/share/common';

export const STATE_STORAGE_KEY = '_a';
export const GLOBAL_STATE_STORAGE_KEY = '_g';

export const SEARCH_SESSIONS_EXAMPLES_APP_URL_GENERATOR =
'SEARCH_SESSIONS_EXAMPLES_APP_URL_GENERATOR';
export const SEARCH_SESSIONS_EXAMPLES_APP_LOCATOR = 'SEARCH_SESSIONS_EXAMPLES_APP_LOCATOR';

export interface AppUrlState {
export interface AppUrlState extends SerializableRecord {
filters?: Filter[];
query?: Query;
indexPatternId?: string;
numericFieldName?: string;
searchSessionId?: string;
}

export interface GlobalUrlState {
export interface GlobalUrlState extends SerializableRecord {
filters?: Filter[];
time?: TimeRange;
}

export type SearchSessionExamplesUrlGeneratorState = AppUrlState & GlobalUrlState;
export type SearchSessionsExamplesAppLocatorParams = AppUrlState & GlobalUrlState;

export const createSearchSessionsExampleUrlGenerator = (
getStartServices: () => Promise<{
appBasePath: string;
}>
): UrlGeneratorsDefinition<typeof SEARCH_SESSIONS_EXAMPLES_APP_URL_GENERATOR> => ({
id: SEARCH_SESSIONS_EXAMPLES_APP_URL_GENERATOR,
createUrl: async (state: SearchSessionExamplesUrlGeneratorState) => {
const startServices = await getStartServices();
const appBasePath = startServices.appBasePath;
const path = `${appBasePath}/app/searchExamples/search-sessions`;
export class SearchSessionsExamplesAppLocatorDefinition
implements LocatorDefinition<SearchSessionsExamplesAppLocatorParams>
{
public readonly id = SEARCH_SESSIONS_EXAMPLES_APP_LOCATOR;

constructor(protected readonly getAppBasePath: () => Promise<string>) {}

public readonly getLocation = async (params: SearchSessionsExamplesAppLocatorParams) => {
const appBasePath = await this.getAppBasePath();
const path = `${appBasePath}/search-sessions`;

let url = setStateToKbnUrl<AppUrlState>(
STATE_STORAGE_KEY,
{
query: state.query,
filters: state.filters?.filter((f) => !esFilters.isFilterPinned(f)),
indexPatternId: state.indexPatternId,
numericFieldName: state.numericFieldName,
searchSessionId: state.searchSessionId,
query: params.query,
filters: params.filters?.filter((f) => !esFilters.isFilterPinned(f)),
indexPatternId: params.indexPatternId,
numericFieldName: params.numericFieldName,
searchSessionId: params.searchSessionId,
} as AppUrlState,
{ useHash: false, storeInHashQuery: false },
path
Expand All @@ -58,18 +58,22 @@ export const createSearchSessionsExampleUrlGenerator = (
url = setStateToKbnUrl<GlobalUrlState>(
GLOBAL_STATE_STORAGE_KEY,
{
time: state.time,
filters: state.filters?.filter((f) => esFilters.isFilterPinned(f)),
time: params.time,
filters: params.filters?.filter((f) => esFilters.isFilterPinned(f)),
} as GlobalUrlState,
{ useHash: false, storeInHashQuery: false },
url
);

return url;
},
});
return {
app: 'searchExamples',
path: url,
state: {},
};
};
}

export function getInitialStateFromUrl(): SearchSessionExamplesUrlGeneratorState {
export function getInitialStateFromUrl(): SearchSessionsExamplesAppLocatorParams {
const {
_a: { numericFieldName, indexPatternId, searchSessionId, filters: aFilters, query } = {},
_g: { filters: gFilters, time } = {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,19 @@
*/

import { History } from 'history';
import { DashboardConstants } from '../..';
import { DashboardAppLocatorParams, DashboardConstants } from '../..';
import { DashboardState } from '../../types';
import { getDashboardTitle } from '../../dashboard_strings';
import { DashboardSavedObject } from '../../saved_dashboards';
import { getQueryParams } from '../../services/kibana_utils';
import { createQueryParamObservable } from '../../../../kibana_utils/public';
import { DASHBOARD_APP_URL_GENERATOR, DashboardUrlGeneratorState } from '../../url_generator';
import {
DataPublicPluginStart,
noSearchSessionStorageCapabilityMessage,
SearchSessionInfoProvider,
} from '../../services/data';
import { stateToRawDashboardState } from './convert_dashboard_state';
import { DASHBOARD_APP_LOCATOR } from '../../locator';

export const getSearchSessionIdFromURL = (history: History): string | undefined =>
getQueryParams(history.location)[DashboardConstants.SEARCH_SESSION_ID] as string | undefined;
Expand All @@ -32,16 +33,14 @@ export function createSessionRestorationDataProvider(deps: {
getAppState: () => DashboardState;
getDashboardTitle: () => string;
getDashboardId: () => string;
}) {
}): SearchSessionInfoProvider<DashboardAppLocatorParams> {
return {
getName: async () => deps.getDashboardTitle(),
getUrlGeneratorData: async () => {
return {
urlGeneratorId: DASHBOARD_APP_URL_GENERATOR,
initialState: getUrlGeneratorState({ ...deps, shouldRestoreSearchSession: false }),
restoreState: getUrlGeneratorState({ ...deps, shouldRestoreSearchSession: true }),
};
},
getLocatorData: async () => ({
id: DASHBOARD_APP_LOCATOR,
initialState: getLocatorParams({ ...deps, shouldRestoreSearchSession: false }),
restoreState: getLocatorParams({ ...deps, shouldRestoreSearchSession: true }),
}),
};
}

Expand Down Expand Up @@ -93,7 +92,7 @@ export function enableDashboardSearchSessions({
* Fetches the state to store when a session is saved so that this dashboard can be recreated exactly
* as it was.
*/
function getUrlGeneratorState({
function getLocatorParams({
data,
getAppState,
kibanaVersion,
Expand All @@ -105,7 +104,7 @@ function getUrlGeneratorState({
getAppState: () => DashboardState;
getDashboardId: () => string;
shouldRestoreSearchSession: boolean;
}): DashboardUrlGeneratorState {
}): DashboardAppLocatorParams {
const appState = stateToRawDashboardState({ state: getAppState(), version: kibanaVersion });
const { filterManager, queryString } = data.query;
const { timefilter } = data.query.timefilter;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe('createSessionRestorationDataProvider', () => {
(mockDataPlugin.search.session.getSessionId as jest.Mock).mockImplementation(
() => searchSessionId
);
const { initialState, restoreState } = await searchSessionInfoProvider.getUrlGeneratorData();
const { initialState, restoreState } = await searchSessionInfoProvider.getLocatorData();
expect(initialState.searchSessionId).toBeUndefined();
expect(restoreState.searchSessionId).toBe(searchSessionId);
});
Expand All @@ -48,13 +48,13 @@ describe('createSessionRestorationDataProvider', () => {
(mockDataPlugin.query.timefilter.timefilter.getAbsoluteTime as jest.Mock).mockImplementation(
() => absoluteTime
);
const { initialState, restoreState } = await searchSessionInfoProvider.getUrlGeneratorData();
const { initialState, restoreState } = await searchSessionInfoProvider.getLocatorData();
expect(initialState.timeRange).toBe(relativeTime);
expect(restoreState.timeRange).toBe(absoluteTime);
});

test('restoreState has refreshInterval paused', async () => {
const { initialState, restoreState } = await searchSessionInfoProvider.getUrlGeneratorData();
const { initialState, restoreState } = await searchSessionInfoProvider.getLocatorData();
expect(initialState.refreshInterval).toBeUndefined();
expect(restoreState.refreshInterval?.pause).toBe(true);
});
Expand Down
9 changes: 5 additions & 4 deletions src/plugins/data/common/search/session/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* Side Public License, v 1.
*/

import { SerializableRecord } from '@kbn/utility-types';
import { SearchSessionStatus } from './status';

export const SEARCH_SESSION_TYPE = 'search-session';
Expand Down Expand Up @@ -43,19 +44,19 @@ export interface SearchSessionSavedObjectAttributes {
*/
status: SearchSessionStatus;
/**
* urlGeneratorId
* locatorId (see share.url.locators service)
*/
urlGeneratorId?: string;
locatorId?: string;
/**
* The application state that was used to create the session.
* Should be used, for example, to re-load an expired search session.
*/
initialState?: Record<string, unknown>;
initialState?: SerializableRecord;
/**
* Application state that should be used to restore the session.
* For example, relative dates are conveted to absolute ones.
*/
restoreState?: Record<string, unknown>;
restoreState?: SerializableRecord;
/**
* Mapping of search request hashes to their corresponsing info (async search id, etc.)
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const mockSavedObject: SearchSessionSavedObject = {
attributes: {
name: 'my_name',
appId: 'my_app_id',
urlGeneratorId: 'my_url_generator_id',
locatorId: 'my_url_generator_id',
idMapping: {},
sessionId: 'session_id',
touched: new Date().toISOString(),
Expand Down
Loading