From eb74b86ca460dcf832851e6eb182e6d9904be3d8 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Tue, 26 Oct 2021 12:30:11 -0700 Subject: [PATCH] [data.search.session] Use locators instead of URL generators (#115681) * [data.search.session] Use locators instead of URL generators * Add migration * Fix/update tests * Fix types * Fix test setup * Update docs * Fix version :) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- dev_docs/tutorials/data/search.mdx | 10 +- examples/search_examples/public/plugin.ts | 20 +- .../public/search_sessions/app.tsx | 14 +- .../{url_generator.ts => app_locator.ts} | 60 +++--- .../lib/dashboard_session_restoration.ts | 23 +-- .../lib/session_restoration.test.ts | 6 +- .../data/common/search/session/types.ts | 9 +- .../session/search_session_state.test.ts | 2 +- .../search/session/session_service.test.ts | 26 +-- .../public/search/session/session_service.ts | 29 ++- .../public/search/session/sessions_client.ts | 8 +- .../apps/main/services/discover_state.test.ts | 13 +- .../apps/main/services/discover_state.ts | 8 +- src/plugins/discover/public/locator.ts | 2 +- .../search/sessions_mgmt/__mocks__/index.tsx | 6 - .../sessions_mgmt/application/index.tsx | 2 +- .../sessions_mgmt/components/main.test.tsx | 8 +- .../components/table/table.test.tsx | 8 +- .../search/sessions_mgmt/lib/api.test.ts | 25 ++- .../public/search/sessions_mgmt/lib/api.ts | 30 ++- .../sessions_mgmt/lib/get_columns.test.tsx | 7 +- .../public/search/sessions_mgmt/types.ts | 2 +- .../data_enhanced/server/routes/session.ts | 6 +- .../server/saved_objects/search_session.ts | 2 +- .../search_session_migration.test.ts | 191 ++++++++++++++++++ .../saved_objects/search_session_migration.ts | 30 ++- .../search/session/session_service.test.ts | 16 +- .../server/search/session/session_service.ts | 6 +- .../api_integration/apis/search/session.ts | 30 +-- 29 files changed, 411 insertions(+), 188 deletions(-) rename examples/search_examples/public/search_sessions/{url_generator.ts => app_locator.ts} (52%) diff --git a/dev_docs/tutorials/data/search.mdx b/dev_docs/tutorials/data/search.mdx index 1585adbdd37bed..425736ddb03bbb 100644 --- a/dev_docs/tutorials/data/search.mdx +++ b/dev_docs/tutorials/data/search.mdx @@ -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: @@ -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 }), }; }, }); diff --git a/examples/search_examples/public/plugin.ts b/examples/search_examples/public/plugin.ts index b00362aef1f5ea..95ea688f49cc24 100644 --- a/examples/search_examples/public/plugin.ts +++ b/examples/search_examples/public/plugin.ts @@ -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'; @@ -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 {}; } diff --git a/examples/search_examples/public/search_sessions/app.tsx b/examples/search_examples/public/search_sessions/app.tsx index 63ab706c945d52..c953da0895ccdb 100644 --- a/examples/search_examples/public/search_sessions/app.tsx +++ b/examples/search_examples/public/search_sessions/app.tsx @@ -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']; @@ -140,14 +136,14 @@ 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(), @@ -155,8 +151,8 @@ export const SearchSessionsExampleApp = ({ indexPatternId: indexPattern?.id, numericFieldName, searchSessionId: data.search.session.getSessionId(), - } as SearchSessionExamplesUrlGeneratorState, - urlGeneratorId: SEARCH_SESSIONS_EXAMPLES_APP_URL_GENERATOR, + }, + id: SEARCH_SESSIONS_EXAMPLES_APP_LOCATOR, }), }); }, [ diff --git a/examples/search_examples/public/search_sessions/url_generator.ts b/examples/search_examples/public/search_sessions/app_locator.ts similarity index 52% rename from examples/search_examples/public/search_sessions/url_generator.ts rename to examples/search_examples/public/search_sessions/app_locator.ts index 69355f9046c46e..1cbd27887c1c36 100644 --- a/examples/search_examples/public/search_sessions/url_generator.ts +++ b/examples/search_examples/public/search_sessions/app_locator.ts @@ -6,17 +6,17 @@ * 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; @@ -24,32 +24,32 @@ export interface AppUrlState { 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 => ({ - 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 +{ + public readonly id = SEARCH_SESSIONS_EXAMPLES_APP_LOCATOR; + + constructor(protected readonly getAppBasePath: () => Promise) {} + + public readonly getLocation = async (params: SearchSessionsExamplesAppLocatorParams) => { + const appBasePath = await this.getAppBasePath(); + const path = `${appBasePath}/search-sessions`; let url = setStateToKbnUrl( 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 @@ -58,18 +58,22 @@ export const createSearchSessionsExampleUrlGenerator = ( url = setStateToKbnUrl( 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 } = {}, diff --git a/src/plugins/dashboard/public/application/lib/dashboard_session_restoration.ts b/src/plugins/dashboard/public/application/lib/dashboard_session_restoration.ts index 7dd2b53a581558..ca4fa85b4b55c9 100644 --- a/src/plugins/dashboard/public/application/lib/dashboard_session_restoration.ts +++ b/src/plugins/dashboard/public/application/lib/dashboard_session_restoration.ts @@ -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; @@ -32,16 +33,14 @@ export function createSessionRestorationDataProvider(deps: { getAppState: () => DashboardState; getDashboardTitle: () => string; getDashboardId: () => string; -}) { +}): SearchSessionInfoProvider { 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 }), + }), }; } @@ -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, @@ -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; diff --git a/src/plugins/dashboard/public/application/lib/session_restoration.test.ts b/src/plugins/dashboard/public/application/lib/session_restoration.test.ts index 571dfb0a8beeba..55366ac50fd2e9 100644 --- a/src/plugins/dashboard/public/application/lib/session_restoration.test.ts +++ b/src/plugins/dashboard/public/application/lib/session_restoration.test.ts @@ -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); }); @@ -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); }); diff --git a/src/plugins/data/common/search/session/types.ts b/src/plugins/data/common/search/session/types.ts index 8e3c298aa9316a..cbe3de9be4c735 100644 --- a/src/plugins/data/common/search/session/types.ts +++ b/src/plugins/data/common/search/session/types.ts @@ -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'; @@ -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; + initialState?: SerializableRecord; /** * Application state that should be used to restore the session. * For example, relative dates are conveted to absolute ones. */ - restoreState?: Record; + restoreState?: SerializableRecord; /** * Mapping of search request hashes to their corresponsing info (async search id, etc.) */ diff --git a/src/plugins/data/public/search/session/search_session_state.test.ts b/src/plugins/data/public/search/session/search_session_state.test.ts index 65b931f23cf2ea..ef18275da12faf 100644 --- a/src/plugins/data/public/search/session/search_session_state.test.ts +++ b/src/plugins/data/public/search/session/search_session_state.test.ts @@ -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(), diff --git a/src/plugins/data/public/search/session/session_service.test.ts b/src/plugins/data/public/search/session/session_service.test.ts index 5c1882248f76a6..4a11cdb38bb7dc 100644 --- a/src/plugins/data/public/search/session/session_service.test.ts +++ b/src/plugins/data/public/search/session/session_service.test.ts @@ -25,7 +25,7 @@ const mockSavedObject: SearchSessionSavedObject = { attributes: { name: 'my_name', appId: 'my_app_id', - urlGeneratorId: 'my_url_generator_id', + locatorId: 'my_locator_id', idMapping: {}, sessionId: 'session_id', touched: new Date().toISOString(), @@ -192,8 +192,8 @@ describe('Session service', () => { sessionService.enableStorage({ getName: async () => 'Name', - getUrlGeneratorData: async () => ({ - urlGeneratorId: 'id', + getLocatorData: async () => ({ + id: 'id', initialState: {}, restoreState: {}, }), @@ -245,8 +245,8 @@ describe('Session service', () => { sessionService.enableStorage({ getName: async () => 'Name', - getUrlGeneratorData: async () => ({ - urlGeneratorId: 'id', + getLocatorData: async () => ({ + id: 'id', initialState: {}, restoreState: {}, }), @@ -299,8 +299,8 @@ describe('Session service', () => { sessionService.enableStorage({ getName: async () => 'Name', - getUrlGeneratorData: async () => ({ - urlGeneratorId: 'id', + getLocatorData: async () => ({ + id: 'id', initialState: {}, restoreState: {}, }), @@ -319,8 +319,8 @@ describe('Session service', () => { sessionService.enableStorage( { getName: async () => 'Name', - getUrlGeneratorData: async () => ({ - urlGeneratorId: 'id', + getLocatorData: async () => ({ + id: 'id', initialState: {}, restoreState: {}, }), @@ -336,10 +336,10 @@ describe('Session service', () => { expect(sessionService.getSearchSessionIndicatorUiConfig().isDisabled().disabled).toBe(false); }); - test('save() throws in case getUrlGeneratorData returns throws', async () => { + test('save() throws in case getLocatorData returns throws', async () => { sessionService.enableStorage({ getName: async () => 'Name', - getUrlGeneratorData: async () => { + getLocatorData: async () => { throw new Error('Haha'); }, }); @@ -373,8 +373,8 @@ describe('Session service', () => { sessionsClient.rename.mockRejectedValue(renameError); sessionService.enableStorage({ getName: async () => 'Name', - getUrlGeneratorData: async () => ({ - urlGeneratorId: 'id', + getLocatorData: async () => ({ + id: 'id', initialState: {}, restoreState: {}, }), diff --git a/src/plugins/data/public/search/session/session_service.ts b/src/plugins/data/public/search/session/session_service.ts index 874fad67c4df14..360e8808c186d0 100644 --- a/src/plugins/data/public/search/session/session_service.ts +++ b/src/plugins/data/public/search/session/session_service.ts @@ -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 { @@ -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'; @@ -44,7 +43,7 @@ export type SessionSnapshot = SessionStateInternal; /** * Provide info about current search session to be stored in the Search Session saved object */ -export interface SearchSessionInfoProvider { +export interface SearchSessionInfoProvider

{ /** * User-facing name of the session. * e.g. will be displayed in saved Search Sessions management list @@ -57,10 +56,10 @@ export interface SearchSessionInfoProvider Promise<{ - urlGeneratorId: ID; - initialState: UrlGeneratorStateMapping[ID]['State']; - restoreState: UrlGeneratorStateMapping[ID]['State']; + getLocatorData: () => Promise<{ + id: string; + initialState: P; + restoreState: P; }>; } @@ -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, { @@ -329,9 +328,9 @@ export class SessionService { const searchSessionSavedObject = await this.sessionsClient.create({ name: formattedName, appId: currentSessionApp, - restoreState: restoreState as unknown as Record, - initialState: initialState as unknown as Record, - urlGeneratorId, + locatorId, + restoreState, + initialState, sessionId, }); @@ -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( - searchSessionInfoProvider: SearchSessionInfoProvider, + public enableStorage

( + searchSessionInfoProvider: SearchSessionInfoProvider

, searchSessionIndicatorUiConfig?: SearchSessionIndicatorUiConfig ) { this.searchSessionInfoProvider = { diff --git a/src/plugins/data/public/search/session/sessions_client.ts b/src/plugins/data/public/search/session/sessions_client.ts index 0b6f1b79f0c63c..d267ba52b024cd 100644 --- a/src/plugins/data/public/search/session/sessions_client.ts +++ b/src/plugins/data/public/search/session/sessions_client.ts @@ -37,26 +37,26 @@ export class SessionsClient { public create({ name, appId, - urlGeneratorId, + locatorId, initialState, restoreState, sessionId, }: { name: string; appId: string; + locatorId: string; initialState: Record; restoreState: Record; - urlGeneratorId: string; sessionId: string; }): Promise { return this.http.post(`/internal/session`, { body: JSON.stringify({ name, + appId, + locatorId, initialState, restoreState, sessionId, - appId, - urlGeneratorId, }), }); } diff --git a/src/plugins/discover/public/application/apps/main/services/discover_state.test.ts b/src/plugins/discover/public/application/apps/main/services/discover_state.test.ts index 9968ca6f1f63fe..7f875be0a42c59 100644 --- a/src/plugins/discover/public/application/apps/main/services/discover_state.test.ts +++ b/src/plugins/discover/public/application/apps/main/services/discover_state.test.ts @@ -183,7 +183,7 @@ describe('createSearchSessionRestorationDataProvider', () => { (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); }); @@ -197,15 +197,20 @@ describe('createSearchSessionRestorationDataProvider', () => { (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 paused autoRefresh', async () => { - const { initialState, restoreState } = await searchSessionInfoProvider.getUrlGeneratorData(); + const { initialState, restoreState } = await searchSessionInfoProvider.getLocatorData(); expect(initialState.refreshInterval).toBe(undefined); - expect(restoreState.refreshInterval?.pause).toBe(true); + expect(restoreState.refreshInterval).toMatchInlineSnapshot(` + Object { + "pause": true, + "value": 0, + } + `); }); }); }); diff --git a/src/plugins/discover/public/application/apps/main/services/discover_state.ts b/src/plugins/discover/public/application/apps/main/services/discover_state.ts index 9a61fdc996e3b7..388d4f19d1c275 100644 --- a/src/plugins/discover/public/application/apps/main/services/discover_state.ts +++ b/src/plugins/discover/public/application/apps/main/services/discover_state.ts @@ -32,9 +32,9 @@ import { } from '../../../../../../data/public'; import { migrateLegacyQuery } from '../../../helpers/migrate_legacy_query'; import { DiscoverGridSettings } from '../../../components/discover_grid/types'; -import { DISCOVER_APP_URL_GENERATOR, DiscoverUrlGeneratorState } from '../../../../url_generator'; import { SavedSearch } from '../../../../saved_searches'; import { handleSourceColumnState } from '../../../helpers/state_helpers'; +import { DISCOVER_APP_LOCATOR, DiscoverAppLocatorParams } from '../../../../locator'; import { VIEW_MODE } from '../components/view_mode_toggle'; export interface AppState { @@ -361,9 +361,9 @@ export function createSearchSessionRestorationDataProvider(deps: { }) ); }, - getUrlGeneratorData: async () => { + getLocatorData: async () => { return { - urlGeneratorId: DISCOVER_APP_URL_GENERATOR, + id: DISCOVER_APP_LOCATOR, initialState: createUrlGeneratorState({ ...deps, getSavedSearchId, @@ -389,7 +389,7 @@ function createUrlGeneratorState({ data: DataPublicPluginStart; getSavedSearchId: () => string | undefined; shouldRestoreSearchSession: boolean; -}): DiscoverUrlGeneratorState { +}): DiscoverAppLocatorParams { const appState = appStateContainer.get(); return { filters: data.query.filterManager.getFilters(), diff --git a/src/plugins/discover/public/locator.ts b/src/plugins/discover/public/locator.ts index bc632c7e1ccb7a..40b62841f19d14 100644 --- a/src/plugins/discover/public/locator.ts +++ b/src/plugins/discover/public/locator.ts @@ -69,7 +69,7 @@ export interface DiscoverAppLocatorParams extends SerializableRecord { /** * Array of the used sorting [[field,direction],...] */ - sort?: string[][] & SerializableRecord; + sort?: string[][]; /** * id of the used saved query diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/__mocks__/index.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/__mocks__/index.tsx index af0a1a583e4475..d21b72b85f8d51 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/__mocks__/index.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/__mocks__/index.tsx @@ -7,13 +7,7 @@ import React, { ReactNode } from 'react'; import { __IntlProvider as IntlProvider } from '@kbn/i18n/react'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { UrlGeneratorsStart } from '../../../../../../../src/plugins/share/public/url_generators'; export function LocaleWrapper({ children }: { children?: ReactNode }) { return {children}; } - -export const mockUrls = { - getUrlGenerator: (id: string) => ({ createUrl: () => `hello-cool-${id}-url` }), -} as unknown as UrlGeneratorsStart; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/index.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/index.tsx index a2d51d7d212481..7f5117740f38cf 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/index.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/index.tsx @@ -49,7 +49,7 @@ export class SearchSessionsMgmtApp { const { sessionsClient } = data.search; const api = new SearchSessionsMgmtAPI(sessionsClient, this.config, { notifications, - urls: share.urlGenerators, + locators: share.url.locators, application, usageCollector: pluginsSetup.data.search.usageCollector, }); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.test.tsx index 4c945e717464ca..b79a4939b3fdd0 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.test.tsx @@ -16,13 +16,16 @@ import { SessionsClient } from 'src/plugins/data/public/search'; import { IManagementSectionsPluginsSetup, SessionsConfigSchema } from '..'; import { SearchSessionsMgmtAPI } from '../lib/api'; import { AsyncSearchIntroDocumentation } from '../lib/documentation'; -import { LocaleWrapper, mockUrls } from '../__mocks__'; +import { LocaleWrapper } from '../__mocks__'; import { SearchSessionsMgmtMain } from './main'; import { dataPluginMock } from '../../../../../../../src/plugins/data/public/mocks'; import { managementPluginMock } from '../../../../../../../src/plugins/management/public/mocks'; +import { SharePluginStart } from '../../../../../../../src/plugins/share/public'; +import { sharePluginMock } from '../../../../../../../src/plugins/share/public/mocks'; let mockCoreSetup: MockedKeys; let mockCoreStart: MockedKeys; +let mockShareStart: jest.Mocked; let mockPluginsSetup: IManagementSectionsPluginsSetup; let mockConfig: SessionsConfigSchema; let sessionsClient: SessionsClient; @@ -32,6 +35,7 @@ describe('Background Search Session Management Main', () => { beforeEach(() => { mockCoreSetup = coreMock.createSetup(); mockCoreStart = coreMock.createStart(); + mockShareStart = sharePluginMock.createStartContract(); mockPluginsSetup = { data: dataPluginMock.createSetupContract(), management: managementPluginMock.createSetupContract(), @@ -49,7 +53,7 @@ describe('Background Search Session Management Main', () => { sessionsClient = new SessionsClient({ http: mockCoreSetup.http }); api = new SearchSessionsMgmtAPI(sessionsClient, mockConfig, { - urls: mockUrls, + locators: mockShareStart.url.locators, notifications: mockCoreStart.notifications, application: mockCoreStart.application, }); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx index f3079155f7eb5d..863e5e85d9ef3e 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx @@ -16,13 +16,16 @@ import { SessionsClient } from 'src/plugins/data/public/search'; import { SearchSessionStatus } from '../../../../../../../../src/plugins/data/common'; import { IManagementSectionsPluginsSetup, SessionsConfigSchema } from '../../'; import { SearchSessionsMgmtAPI } from '../../lib/api'; -import { LocaleWrapper, mockUrls } from '../../__mocks__'; +import { LocaleWrapper } from '../../__mocks__'; import { SearchSessionsMgmtTable } from './table'; import { dataPluginMock } from '../../../../../../../../src/plugins/data/public/mocks'; import { managementPluginMock } from '../../../../../../../../src/plugins/management/public/mocks'; +import { SharePluginStart } from '../../../../../../../../src/plugins/share/public'; +import { sharePluginMock } from '../../../../../../../../src/plugins/share/public/mocks'; let mockCoreSetup: MockedKeys; let mockCoreStart: CoreStart; +let mockShareStart: jest.Mocked; let mockPluginsSetup: IManagementSectionsPluginsSetup; let mockConfig: SessionsConfigSchema; let sessionsClient: SessionsClient; @@ -32,6 +35,7 @@ describe('Background Search Session Management Table', () => { beforeEach(async () => { mockCoreSetup = coreMock.createSetup(); mockCoreStart = coreMock.createStart(); + mockShareStart = sharePluginMock.createStartContract(); mockPluginsSetup = { data: dataPluginMock.createSetupContract(), management: managementPluginMock.createSetupContract(), @@ -48,7 +52,7 @@ describe('Background Search Session Management Table', () => { sessionsClient = new SessionsClient({ http: mockCoreSetup.http }); api = new SearchSessionsMgmtAPI(sessionsClient, mockConfig, { - urls: mockUrls, + locators: mockShareStart.url.locators, notifications: mockCoreStart.notifications, application: mockCoreStart.application, }); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts index a3bc3b51f61bd0..a0b6aa80f25009 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts @@ -14,11 +14,13 @@ import type { SavedObjectsFindResponse } from 'src/core/server'; import { SessionsClient } from 'src/plugins/data/public/search'; import type { SessionsConfigSchema } from '../'; import { SearchSessionStatus } from '../../../../../../../src/plugins/data/common'; -import { mockUrls } from '../__mocks__'; +import { sharePluginMock } from '../../../../../../../src/plugins/share/public/mocks'; +import { SharePluginStart } from '../../../../../../../src/plugins/share/public'; import { SearchSessionsMgmtAPI } from './api'; let mockCoreSetup: MockedKeys; let mockCoreStart: MockedKeys; +let mockShareStart: jest.Mocked; let mockConfig: SessionsConfigSchema; let sessionsClient: SessionsClient; @@ -26,6 +28,7 @@ describe('Search Sessions Management API', () => { beforeEach(() => { mockCoreSetup = coreMock.createSetup(); mockCoreStart = coreMock.createStart(); + mockShareStart = sharePluginMock.createStartContract(); mockConfig = { defaultExpiration: moment.duration('7d'), management: { @@ -60,7 +63,7 @@ describe('Search Sessions Management API', () => { }); const api = new SearchSessionsMgmtAPI(sessionsClient, mockConfig, { - urls: mockUrls, + locators: mockShareStart.url.locators, notifications: mockCoreStart.notifications, application: mockCoreStart.application, }); @@ -80,9 +83,9 @@ describe('Search Sessions Management API', () => { "initialState": Object {}, "name": "Veggie", "numSearches": 0, - "reloadUrl": "hello-cool-undefined-url", + "reloadUrl": undefined, "restoreState": Object {}, - "restoreUrl": "hello-cool-undefined-url", + "restoreUrl": undefined, "status": "complete", "version": undefined, }, @@ -111,7 +114,7 @@ describe('Search Sessions Management API', () => { }); const api = new SearchSessionsMgmtAPI(sessionsClient, mockConfig, { - urls: mockUrls, + locators: mockShareStart.url.locators, notifications: mockCoreStart.notifications, application: mockCoreStart.application, }); @@ -124,7 +127,7 @@ describe('Search Sessions Management API', () => { sessionsClient.find = jest.fn().mockRejectedValue(new Error('implementation is so bad')); const api = new SearchSessionsMgmtAPI(sessionsClient, mockConfig, { - urls: mockUrls, + locators: mockShareStart.url.locators, notifications: mockCoreStart.notifications, application: mockCoreStart.application, }); @@ -153,7 +156,7 @@ describe('Search Sessions Management API', () => { }); const api = new SearchSessionsMgmtAPI(sessionsClient, mockConfig, { - urls: mockUrls, + locators: mockShareStart.url.locators, notifications: mockCoreStart.notifications, application: mockCoreStart.application, }); @@ -181,7 +184,7 @@ describe('Search Sessions Management API', () => { test('send cancel calls the cancel endpoint with a session ID', async () => { const api = new SearchSessionsMgmtAPI(sessionsClient, mockConfig, { - urls: mockUrls, + locators: mockShareStart.url.locators, notifications: mockCoreStart.notifications, application: mockCoreStart.application, }); @@ -196,7 +199,7 @@ describe('Search Sessions Management API', () => { sessionsClient.delete = jest.fn().mockRejectedValue(new Error('implementation is so bad')); const api = new SearchSessionsMgmtAPI(sessionsClient, mockConfig, { - urls: mockUrls, + locators: mockShareStart.url.locators, notifications: mockCoreStart.notifications, application: mockCoreStart.application, }); @@ -225,7 +228,7 @@ describe('Search Sessions Management API', () => { test('send extend throws an error for now', async () => { const api = new SearchSessionsMgmtAPI(sessionsClient, mockConfig, { - urls: mockUrls, + locators: mockShareStart.url.locators, notifications: mockCoreStart.notifications, application: mockCoreStart.application, }); @@ -238,7 +241,7 @@ describe('Search Sessions Management API', () => { test('displays error on reject', async () => { sessionsClient.extend = jest.fn().mockRejectedValue({}); const api = new SearchSessionsMgmtAPI(sessionsClient, mockConfig, { - urls: mockUrls, + locators: mockShareStart.url.locators, notifications: mockCoreStart.notifications, application: mockCoreStart.application, }); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts index 01b64dcaf8a85b..fbd7f472177cb4 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts @@ -11,6 +11,7 @@ import moment from 'moment'; import { from, race, timer } from 'rxjs'; import { mapTo, tap } from 'rxjs/operators'; import type { SharePluginStart } from 'src/plugins/share/public'; +import { SerializableRecord } from '@kbn/utility-types'; import { ISessionsClient, SearchUsageCollector, @@ -24,7 +25,7 @@ import { } from '../types'; import { SessionsConfigSchema } from '..'; -type UrlGeneratorsStart = SharePluginStart['urlGenerators']; +type LocatorsStart = SharePluginStart['url']['locators']; function getActions(status: UISearchSessionState) { const actions: ACTION[] = []; @@ -61,26 +62,21 @@ function getUIStatus(session: PersistedSearchSessionSavedObjectAttributes): UISe return session.status; } -async function getUrlFromState( - urls: UrlGeneratorsStart, - urlGeneratorId: string, - state: Record -) { - let url = '/'; +function getUrlFromState(locators: LocatorsStart, locatorId: string, state: SerializableRecord) { try { - url = await urls.getUrlGenerator(urlGeneratorId).createUrl(state); + const locator = locators.get(locatorId); + return locator?.getRedirectUrl(state); } catch (err) { // eslint-disable-next-line no-console console.error('Could not create URL from restoreState'); // eslint-disable-next-line no-console console.error(err); } - return url; } // Helper: factory for a function to map server objects to UI objects const mapToUISession = - (urls: UrlGeneratorsStart, config: SessionsConfigSchema) => + (locators: LocatorsStart, config: SessionsConfigSchema) => async ( savedObject: SavedObject ): Promise => { @@ -89,7 +85,7 @@ const mapToUISession = appId, created, expires, - urlGeneratorId, + locatorId, initialState, restoreState, idMapping, @@ -102,8 +98,8 @@ const mapToUISession = // TODO: initialState should be saved without the searchSessionID if (initialState) delete initialState.searchSessionId; // derive the URL and add it in - const reloadUrl = await getUrlFromState(urls, urlGeneratorId, initialState); - const restoreUrl = await getUrlFromState(urls, urlGeneratorId, restoreState); + const reloadUrl = await getUrlFromState(locators, locatorId, initialState); + const restoreUrl = await getUrlFromState(locators, locatorId, restoreState); return { id: savedObject.id, @@ -113,8 +109,8 @@ const mapToUISession = expires, status, actions, - restoreUrl, - reloadUrl, + restoreUrl: restoreUrl!, + reloadUrl: reloadUrl!, initialState, restoreState, numSearches: Object.keys(idMapping).length, @@ -123,7 +119,7 @@ const mapToUISession = }; interface SearchSessionManagementDeps { - urls: UrlGeneratorsStart; + locators: LocatorsStart; notifications: NotificationsStart; application: ApplicationStart; usageCollector?: SearchUsageCollector; @@ -174,7 +170,7 @@ export class SearchSessionsMgmtAPI { const savedObjects = result.saved_objects as Array< SavedObject >; - return await Promise.all(savedObjects.map(mapToUISession(this.deps.urls, this.config))); + return await Promise.all(savedObjects.map(mapToUISession(this.deps.locators, this.config))); } } catch (err) { // eslint-disable-next-line no-console diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx index 4764e273e5a686..9578d56e44b1c6 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx @@ -17,14 +17,16 @@ import { IManagementSectionsPluginsSetup, SessionsConfigSchema } from '../'; import { SearchSessionStatus } from '../../../../../../../src/plugins/data/common'; import { OnActionComplete } from '../components'; import { UISession } from '../types'; -import { mockUrls } from '../__mocks__'; import { SearchSessionsMgmtAPI } from './api'; import { getColumns } from './get_columns'; import { dataPluginMock } from '../../../../../../../src/plugins/data/public/mocks'; import { managementPluginMock } from '../../../../../../../src/plugins/management/public/mocks'; +import { SharePluginStart } from '../../../../../../../src/plugins/share/public'; +import { sharePluginMock } from '../../../../../../../src/plugins/share/public/mocks'; let mockCoreSetup: MockedKeys; let mockCoreStart: CoreStart; +let mockShareStart: jest.Mocked; let mockPluginsSetup: IManagementSectionsPluginsSetup; let mockConfig: SessionsConfigSchema; let api: SearchSessionsMgmtAPI; @@ -38,6 +40,7 @@ describe('Search Sessions Management table column factory', () => { beforeEach(async () => { mockCoreSetup = coreMock.createSetup(); mockCoreStart = coreMock.createStart(); + mockShareStart = sharePluginMock.createStartContract(); mockPluginsSetup = { data: dataPluginMock.createSetupContract(), management: managementPluginMock.createSetupContract(), @@ -54,7 +57,7 @@ describe('Search Sessions Management table column factory', () => { sessionsClient = new SessionsClient({ http: mockCoreSetup.http }); api = new SearchSessionsMgmtAPI(sessionsClient, mockConfig, { - urls: mockUrls, + locators: mockShareStart.url.locators, notifications: mockCoreStart.notifications, application: mockCoreStart.application, }); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/types.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/types.ts index f4f928e67e19c4..7489a1ce26aa57 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/types.ts +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/types.ts @@ -21,7 +21,7 @@ export type PersistedSearchSessionSavedObjectAttributes = SearchSessionSavedObje Required< Pick< SearchSessionSavedObjectAttributes, - 'name' | 'appId' | 'urlGeneratorId' | 'initialState' | 'restoreState' + 'name' | 'appId' | 'locatorId' | 'initialState' | 'restoreState' > >; diff --git a/x-pack/plugins/data_enhanced/server/routes/session.ts b/x-pack/plugins/data_enhanced/server/routes/session.ts index 3e293aa82dc837..3f36bd0a757469 100644 --- a/x-pack/plugins/data_enhanced/server/routes/session.ts +++ b/x-pack/plugins/data_enhanced/server/routes/session.ts @@ -22,7 +22,7 @@ export function registerSessionRoutes(router: DataEnhancedPluginRouter, logger: name: schema.string(), appId: schema.string(), expires: schema.maybe(schema.string()), - urlGeneratorId: schema.string(), + locatorId: schema.string(), initialState: schema.maybe(schema.object({}, { unknowns: 'allow' })), restoreState: schema.maybe(schema.object({}, { unknowns: 'allow' })), }), @@ -32,7 +32,7 @@ export function registerSessionRoutes(router: DataEnhancedPluginRouter, logger: }, }, async (context, request, res) => { - const { sessionId, name, expires, initialState, restoreState, appId, urlGeneratorId } = + const { sessionId, name, expires, initialState, restoreState, appId, locatorId } = request.body; try { @@ -40,7 +40,7 @@ export function registerSessionRoutes(router: DataEnhancedPluginRouter, logger: name, appId, expires, - urlGeneratorId, + locatorId, initialState, restoreState, }); diff --git a/x-pack/plugins/data_enhanced/server/saved_objects/search_session.ts b/x-pack/plugins/data_enhanced/server/saved_objects/search_session.ts index 9a359679c0e7af..f921ed78eb247a 100644 --- a/x-pack/plugins/data_enhanced/server/saved_objects/search_session.ts +++ b/x-pack/plugins/data_enhanced/server/saved_objects/search_session.ts @@ -42,7 +42,7 @@ export const searchSessionSavedObjectType: SavedObjectsType = { appId: { type: 'keyword', }, - urlGeneratorId: { + locatorId: { type: 'keyword', }, initialState: { diff --git a/x-pack/plugins/data_enhanced/server/saved_objects/search_session_migration.test.ts b/x-pack/plugins/data_enhanced/server/saved_objects/search_session_migration.test.ts index cdb86772482fe4..aa344da68f9319 100644 --- a/x-pack/plugins/data_enhanced/server/saved_objects/search_session_migration.test.ts +++ b/x-pack/plugins/data_enhanced/server/saved_objects/search_session_migration.test.ts @@ -9,6 +9,7 @@ import { searchSessionSavedObjectMigrations, SearchSessionSavedObjectAttributesPre$7$13$0, SearchSessionSavedObjectAttributesPre$7$14$0, + SearchSessionSavedObjectAttributesPre$8$0$0, } from './search_session_migration'; import { SavedObject } from '../../../../../src/core/types'; import { SEARCH_SESSION_TYPE, SearchSessionStatus } from '../../../../../src/plugins/data/common'; @@ -164,3 +165,193 @@ describe('7.13.0 -> 7.14.0', () => { `); }); }); + +describe('7.14.0 -> 8.0.0', () => { + const migration = searchSessionSavedObjectMigrations['8.0.0']; + + test('Discover app URL generator migrates to locator', () => { + const mockSessionSavedObject: SavedObject = { + id: 'id', + type: SEARCH_SESSION_TYPE, + attributes: { + name: 'my_name', + appId: 'my_app_id', + sessionId: 'sessionId', + urlGeneratorId: 'DISCOVER_APP_URL_GENERATOR', + initialState: {}, + restoreState: {}, + persisted: true, + idMapping: {}, + realmType: 'realmType', + realmName: 'realmName', + username: 'username', + created: '2021-03-26T00:00:00.000Z', + expires: '2021-03-30T00:00:00.000Z', + touched: '2021-03-29T00:00:00.000Z', + completed: '2021-03-29T00:00:00.000Z', + status: SearchSessionStatus.COMPLETE, + version: '7.14.0', + }, + references: [], + }; + + const migratedSession = migration(mockSessionSavedObject, {} as SavedObjectMigrationContext); + + expect(migratedSession.attributes).toMatchInlineSnapshot(` + Object { + "appId": "my_app_id", + "completed": "2021-03-29T00:00:00.000Z", + "created": "2021-03-26T00:00:00.000Z", + "expires": "2021-03-30T00:00:00.000Z", + "idMapping": Object {}, + "initialState": Object {}, + "locatorId": "DISCOVER_APP_LOCATOR", + "name": "my_name", + "persisted": true, + "realmName": "realmName", + "realmType": "realmType", + "restoreState": Object {}, + "sessionId": "sessionId", + "status": "complete", + "touched": "2021-03-29T00:00:00.000Z", + "username": "username", + "version": "7.14.0", + } + `); + }); + + test('Dashboard app URL generator migrates to locator', () => { + const mockSessionSavedObject: SavedObject = { + id: 'id', + type: SEARCH_SESSION_TYPE, + attributes: { + name: 'my_name', + appId: 'my_app_id', + sessionId: 'sessionId', + urlGeneratorId: 'DASHBOARD_APP_URL_GENERATOR', + initialState: {}, + restoreState: {}, + persisted: true, + idMapping: {}, + realmType: 'realmType', + realmName: 'realmName', + username: 'username', + created: '2021-03-26T00:00:00.000Z', + expires: '2021-03-30T00:00:00.000Z', + touched: '2021-03-29T00:00:00.000Z', + completed: '2021-03-29T00:00:00.000Z', + status: SearchSessionStatus.COMPLETE, + version: '7.14.0', + }, + references: [], + }; + + const migratedSession = migration(mockSessionSavedObject, {} as SavedObjectMigrationContext); + + expect(migratedSession.attributes).toMatchInlineSnapshot(` + Object { + "appId": "my_app_id", + "completed": "2021-03-29T00:00:00.000Z", + "created": "2021-03-26T00:00:00.000Z", + "expires": "2021-03-30T00:00:00.000Z", + "idMapping": Object {}, + "initialState": Object {}, + "locatorId": "DASHBOARD_APP_LOCATOR", + "name": "my_name", + "persisted": true, + "realmName": "realmName", + "realmType": "realmType", + "restoreState": Object {}, + "sessionId": "sessionId", + "status": "complete", + "touched": "2021-03-29T00:00:00.000Z", + "username": "username", + "version": "7.14.0", + } + `); + }); + + test('Undefined URL generator returns undefined locator', () => { + const mockSessionSavedObject: SavedObject = { + id: 'id', + type: SEARCH_SESSION_TYPE, + attributes: { + name: 'my_name', + appId: 'my_app_id', + sessionId: 'sessionId', + urlGeneratorId: undefined, + initialState: {}, + restoreState: {}, + persisted: true, + idMapping: {}, + realmType: 'realmType', + realmName: 'realmName', + username: 'username', + created: '2021-03-26T00:00:00.000Z', + expires: '2021-03-30T00:00:00.000Z', + touched: '2021-03-29T00:00:00.000Z', + completed: '2021-03-29T00:00:00.000Z', + status: SearchSessionStatus.COMPLETE, + version: '7.14.0', + }, + references: [], + }; + + const migratedSession = migration(mockSessionSavedObject, {} as SavedObjectMigrationContext); + + expect(migratedSession.attributes).toMatchInlineSnapshot(` + Object { + "appId": "my_app_id", + "completed": "2021-03-29T00:00:00.000Z", + "created": "2021-03-26T00:00:00.000Z", + "expires": "2021-03-30T00:00:00.000Z", + "idMapping": Object {}, + "initialState": Object {}, + "locatorId": undefined, + "name": "my_name", + "persisted": true, + "realmName": "realmName", + "realmType": "realmType", + "restoreState": Object {}, + "sessionId": "sessionId", + "status": "complete", + "touched": "2021-03-29T00:00:00.000Z", + "username": "username", + "version": "7.14.0", + } + `); + }); + + test('Other URL generator throws error', () => { + const mockSessionSavedObject: SavedObject = { + id: 'id', + type: SEARCH_SESSION_TYPE, + attributes: { + name: 'my_name', + appId: 'my_app_id', + sessionId: 'sessionId', + urlGeneratorId: 'my_url_generator_id', + initialState: {}, + restoreState: {}, + persisted: true, + idMapping: {}, + realmType: 'realmType', + realmName: 'realmName', + username: 'username', + created: '2021-03-26T00:00:00.000Z', + expires: '2021-03-30T00:00:00.000Z', + touched: '2021-03-29T00:00:00.000Z', + completed: '2021-03-29T00:00:00.000Z', + status: SearchSessionStatus.COMPLETE, + version: '7.14.0', + }, + references: [], + }; + + expect(() => + migration(mockSessionSavedObject, {} as SavedObjectMigrationContext) + ).toThrowErrorMatchingInlineSnapshot( + `"No migration found for search session URL generator my_url_generator_id"` + ); + }); +}); diff --git a/x-pack/plugins/data_enhanced/server/saved_objects/search_session_migration.ts b/x-pack/plugins/data_enhanced/server/saved_objects/search_session_migration.ts index fa1428b3a3aad9..4fa5964929f7c0 100644 --- a/x-pack/plugins/data_enhanced/server/saved_objects/search_session_migration.ts +++ b/x-pack/plugins/data_enhanced/server/saved_objects/search_session_migration.ts @@ -29,10 +29,28 @@ export type SearchSessionSavedObjectAttributesPre$7$13$0 = Omit< * but what is important for 7.14.0 is that the version is less then "7.14.0" */ export type SearchSessionSavedObjectAttributesPre$7$14$0 = Omit< - SearchSessionSavedObjectAttributesLatest, + SearchSessionSavedObjectAttributesPre$8$0$0, 'version' >; +/** + * In 8.0.0, we migrated from using URL generators to the locators service. As a result, we move + * from using `urlGeneratorId` to `locatorId`. + */ +export type SearchSessionSavedObjectAttributesPre$8$0$0 = Omit< + SearchSessionSavedObjectAttributesLatest, + 'locatorId' +> & { + urlGeneratorId?: string; +}; + +function getLocatorId(urlGeneratorId?: string) { + if (!urlGeneratorId) return; + if (urlGeneratorId === 'DISCOVER_APP_URL_GENERATOR') return 'DISCOVER_APP_LOCATOR'; + if (urlGeneratorId === 'DASHBOARD_APP_URL_GENERATOR') return 'DASHBOARD_APP_LOCATOR'; + throw new Error(`No migration found for search session URL generator ${urlGeneratorId}`); +} + export const searchSessionSavedObjectMigrations: SavedObjectMigrationMap = { '7.13.0': ( doc: SavedObjectUnsanitizedDoc @@ -60,4 +78,14 @@ export const searchSessionSavedObjectMigrations: SavedObjectMigrationMap = { }, }; }, + '8.0.0': ( + doc: SavedObjectUnsanitizedDoc + ): SavedObjectUnsanitizedDoc => { + const { + attributes: { urlGeneratorId, ...otherAttrs }, + } = doc; + const locatorId = getLocatorId(urlGeneratorId); + const attributes = { ...otherAttrs, locatorId }; + return { ...doc, attributes }; + }, }; diff --git a/x-pack/plugins/data_enhanced/server/search/session/session_service.test.ts b/x-pack/plugins/data_enhanced/server/search/session/session_service.test.ts index 4b5e1a1f86a11b..437e146f0d0f72 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/session_service.test.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/session_service.test.ts @@ -57,7 +57,7 @@ describe('SearchSessionService', () => { attributes: { name: 'my_name', appId: 'my_app_id', - urlGeneratorId: 'my_url_generator_id', + locatorId: 'my_locator_id', idMapping: {}, realmType: mockUser1.authentication_realm.type, realmName: mockUser1.authentication_realm.name, @@ -202,13 +202,13 @@ describe('SearchSessionService', () => { ).rejects.toMatchInlineSnapshot(`[Error: AppId is required]`); }); - it('throws if `generator id` is not provided', () => { + it('throws if `locatorId` is not provided', () => { expect( service.save({ savedObjectsClient }, mockUser1, sessionId, { name: 'banana', appId: 'nanana', }) - ).rejects.toMatchInlineSnapshot(`[Error: UrlGeneratorId is required]`); + ).rejects.toMatchInlineSnapshot(`[Error: locatorId is required]`); }); it('saving updates an existing saved object and persists it', async () => { @@ -222,7 +222,7 @@ describe('SearchSessionService', () => { await service.save({ savedObjectsClient }, mockUser1, sessionId, { name: 'banana', appId: 'nanana', - urlGeneratorId: 'panama', + locatorId: 'panama', }); expect(savedObjectsClient.update).toHaveBeenCalled(); @@ -236,7 +236,7 @@ describe('SearchSessionService', () => { expect(callAttributes).toHaveProperty('persisted', true); expect(callAttributes).toHaveProperty('name', 'banana'); expect(callAttributes).toHaveProperty('appId', 'nanana'); - expect(callAttributes).toHaveProperty('urlGeneratorId', 'panama'); + expect(callAttributes).toHaveProperty('locatorId', 'panama'); expect(callAttributes).toHaveProperty('initialState', {}); expect(callAttributes).toHaveProperty('restoreState', {}); }); @@ -255,7 +255,7 @@ describe('SearchSessionService', () => { await service.save({ savedObjectsClient }, mockUser1, sessionId, { name: 'banana', appId: 'nanana', - urlGeneratorId: 'panama', + locatorId: 'panama', }); expect(savedObjectsClient.update).toHaveBeenCalledTimes(1); @@ -271,7 +271,7 @@ describe('SearchSessionService', () => { expect(callAttributes).toHaveProperty('persisted', true); expect(callAttributes).toHaveProperty('name', 'banana'); expect(callAttributes).toHaveProperty('appId', 'nanana'); - expect(callAttributes).toHaveProperty('urlGeneratorId', 'panama'); + expect(callAttributes).toHaveProperty('locatorId', 'panama'); expect(callAttributes).toHaveProperty('initialState', {}); expect(callAttributes).toHaveProperty('restoreState', {}); expect(callAttributes).toHaveProperty('realmType', mockUser1.authentication_realm.type); @@ -300,7 +300,7 @@ describe('SearchSessionService', () => { { name: 'my_name', appId: 'my_app_id', - urlGeneratorId: 'my_url_generator_id', + locatorId: 'my_locator_id', } ); diff --git a/x-pack/plugins/data_enhanced/server/search/session/session_service.ts b/x-pack/plugins/data_enhanced/server/search/session/session_service.ts index 75f404d0f8790e..84266e2545810c 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/session_service.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/session_service.ts @@ -287,7 +287,7 @@ export class SearchSessionService { name, appId, - urlGeneratorId, + locatorId, initialState = {}, restoreState = {}, }: Partial @@ -295,12 +295,12 @@ export class SearchSessionService if (!this.sessionConfig.enabled) throw new Error('Search sessions are disabled'); if (!name) throw new Error('Name is required'); if (!appId) throw new Error('AppId is required'); - if (!urlGeneratorId) throw new Error('UrlGeneratorId is required'); + if (!locatorId) throw new Error('locatorId is required'); return this.updateOrCreate(deps, user, sessionId, { name, appId, - urlGeneratorId, + locatorId, initialState, restoreState, persisted: true, diff --git a/x-pack/test/api_integration/apis/search/session.ts b/x-pack/test/api_integration/apis/search/session.ts index 06be7c6759bc05..1fa65172cdee37 100644 --- a/x-pack/test/api_integration/apis/search/session.ts +++ b/x-pack/test/api_integration/apis/search/session.ts @@ -27,7 +27,7 @@ export default function ({ getService }: FtrProviderContext) { sessionId, appId: 'discover', expires: '123', - urlGeneratorId: 'discover', + locatorId: 'discover', }) .expect(400); }); @@ -42,7 +42,7 @@ export default function ({ getService }: FtrProviderContext) { name: 'My Session', appId: 'discover', expires: '123', - urlGeneratorId: 'discover', + locatorId: 'discover', }) .expect(200); @@ -63,7 +63,7 @@ export default function ({ getService }: FtrProviderContext) { name: 'My Session', appId: 'discover', expires: '123', - urlGeneratorId: 'discover', + locatorId: 'discover', }) .expect(200); @@ -82,7 +82,7 @@ export default function ({ getService }: FtrProviderContext) { name: 'My Session', appId: 'discover', expires: '123', - urlGeneratorId: 'discover', + locatorId: 'discover', }) .expect(200); @@ -114,7 +114,7 @@ export default function ({ getService }: FtrProviderContext) { name: oldName, appId: 'discover', expires: '123', - urlGeneratorId: 'discover', + locatorId: 'discover', }) .expect(200); @@ -165,7 +165,7 @@ export default function ({ getService }: FtrProviderContext) { name: 'My Session', appId: 'discover', expires: '123', - urlGeneratorId: 'discover', + locatorId: 'discover', }) .expect(200); @@ -217,7 +217,7 @@ export default function ({ getService }: FtrProviderContext) { name: 'My Session', appId: 'discover', expires: '123', - urlGeneratorId: 'discover', + locatorId: 'discover', }) .expect(200); @@ -337,7 +337,7 @@ export default function ({ getService }: FtrProviderContext) { name: 'My Session', appId: 'discover', expires: '123', - urlGeneratorId: 'discover', + locatorId: 'discover', }) .expect(200); @@ -463,7 +463,7 @@ export default function ({ getService }: FtrProviderContext) { name: 'My Session', appId: 'discover', expires: '123', - urlGeneratorId: 'discover', + locatorId: 'discover', }) .expect(200); @@ -484,7 +484,7 @@ export default function ({ getService }: FtrProviderContext) { name: 'My Session', appId: 'discover', expires: '123', - urlGeneratorId: 'discover', + locatorId: 'discover', }) .expect(200); @@ -505,7 +505,7 @@ export default function ({ getService }: FtrProviderContext) { name: 'My Session', appId: 'discover', expires: '123', - urlGeneratorId: 'discover', + locatorId: 'discover', }) .expect(200); @@ -526,7 +526,7 @@ export default function ({ getService }: FtrProviderContext) { name: 'My Session', appId: 'discover', expires: '123', - urlGeneratorId: 'discover', + locatorId: 'discover', }) .expect(200); @@ -550,7 +550,7 @@ export default function ({ getService }: FtrProviderContext) { name: 'My Session', appId: 'discover', expires: '123', - urlGeneratorId: 'discover', + locatorId: 'discover', }) .expect(401); }); @@ -591,7 +591,7 @@ export default function ({ getService }: FtrProviderContext) { name: 'My Session', appId: 'discover', expires: '123', - urlGeneratorId: 'discover', + locatorId: 'discover', }) .expect(403); @@ -714,7 +714,7 @@ export default function ({ getService }: FtrProviderContext) { name: 'My Session', appId: 'discover', expires: '123', - urlGeneratorId: 'discover', + locatorId: 'discover', }) .expect(200);