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
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
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
29 changes: 14 additions & 15 deletions src/plugins/data/public/search/session/session_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* Side Public License, v 1.
*/

import { PublicContract } from '@kbn/utility-types';
import { PublicContract, SerializableRecord } from '@kbn/utility-types';
import { distinctUntilChanged, map, startWith } from 'rxjs/operators';
import { Observable, Subscription } from 'rxjs';
import {
Expand All @@ -15,14 +15,13 @@ import {
ToastsStart as ToastService,
} from 'kibana/public';
import { i18n } from '@kbn/i18n';
import { UrlGeneratorId, UrlGeneratorStateMapping } from '../../../../share/public/';
import { ConfigSchema } from '../../../config';
import {
createSessionStateContainer,
SearchSessionState,
SessionStateInternal,
SessionMeta,
SessionStateContainer,
SessionStateInternal,
} from './search_session_state';
import { ISessionsClient } from './sessions_client';
import { ISearchOptions } from '../../../common';
Expand All @@ -44,7 +43,7 @@ export type SessionSnapshot = SessionStateInternal<TrackSearchDescriptor>;
/**
* Provide info about current search session to be stored in the Search Session saved object
*/
export interface SearchSessionInfoProvider<ID extends UrlGeneratorId = UrlGeneratorId> {
export interface SearchSessionInfoProvider<P extends SerializableRecord = SerializableRecord> {
/**
* User-facing name of the session.
* e.g. will be displayed in saved Search Sessions management list
Expand All @@ -57,10 +56,10 @@ export interface SearchSessionInfoProvider<ID extends UrlGeneratorId = UrlGenera
*/
appendSessionStartTimeToName?: boolean;

getUrlGeneratorData: () => Promise<{
urlGeneratorId: ID;
initialState: UrlGeneratorStateMapping[ID]['State'];
restoreState: UrlGeneratorStateMapping[ID]['State'];
getLocatorData: () => Promise<{
id: string;
initialState: P;
restoreState: P;
}>;
}

Expand Down Expand Up @@ -316,9 +315,9 @@ export class SessionService {
if (!this.hasAccess()) throw new Error('No access to search sessions');
const currentSessionInfoProvider = this.searchSessionInfoProvider;
if (!currentSessionInfoProvider) throw new Error('No info provider for current session');
const [name, { initialState, restoreState, urlGeneratorId }] = await Promise.all([
const [name, { initialState, restoreState, id: locatorId }] = await Promise.all([
currentSessionInfoProvider.getName(),
currentSessionInfoProvider.getUrlGeneratorData(),
currentSessionInfoProvider.getLocatorData(),
]);

const formattedName = formatSessionName(name, {
Expand All @@ -329,9 +328,9 @@ export class SessionService {
const searchSessionSavedObject = await this.sessionsClient.create({
name: formattedName,
appId: currentSessionApp,
restoreState: restoreState as unknown as Record<string, unknown>,
initialState: initialState as unknown as Record<string, unknown>,
urlGeneratorId,
locatorId,
restoreState,
initialState,
sessionId,
});

Expand Down Expand Up @@ -411,8 +410,8 @@ export class SessionService {
* @param searchSessionInfoProvider - info provider for saving a search session
* @param searchSessionIndicatorUiConfig - config for "Search session indicator" UI
*/
public enableStorage<ID extends UrlGeneratorId = UrlGeneratorId>(
searchSessionInfoProvider: SearchSessionInfoProvider<ID>,
public enableStorage<P extends SerializableRecord>(
searchSessionInfoProvider: SearchSessionInfoProvider<P>,
searchSessionIndicatorUiConfig?: SearchSessionIndicatorUiConfig
) {
this.searchSessionInfoProvider = {
Expand Down
8 changes: 4 additions & 4 deletions src/plugins/data/public/search/session/sessions_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,26 +37,26 @@ export class SessionsClient {
public create({
name,
appId,
urlGeneratorId,
locatorId,
initialState,
restoreState,
sessionId,
}: {
name: string;
appId: string;
locatorId: string;
initialState: Record<string, unknown>;
restoreState: Record<string, unknown>;
urlGeneratorId: string;
sessionId: string;
}): Promise<SearchSessionSavedObject> {
return this.http.post(`/internal/session`, {
body: JSON.stringify({
name,
appId,
locatorId,
initialState,
restoreState,
sessionId,
appId,
urlGeneratorId,
}),
});
}
Expand Down
Loading