[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>
This commit is contained in:
Lukas Olson 2021-10-26 12:30:11 -07:00 committed by GitHub
parent 1211149038
commit eb74b86ca4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 411 additions and 188 deletions

View file

@ -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 }),
};
},
});

View file

@ -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 {};
}

View file

@ -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,
}),
});
}, [

View file

@ -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<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
@ -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 } = {},

View file

@ -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<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 }),
}),
};
}
@ -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;

View file

@ -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);
});

View file

@ -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<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.)
*/

View file

@ -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(),

View file

@ -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: {},
}),

View file

@ -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<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
@ -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;
}>;
}
@ -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<string, unknown>,
initialState: initialState as unknown as Record<string, unknown>,
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<ID extends UrlGeneratorId = UrlGeneratorId>(
searchSessionInfoProvider: SearchSessionInfoProvider<ID>,
public enableStorage<P extends SerializableRecord>(
searchSessionInfoProvider: SearchSessionInfoProvider<P>,
searchSessionIndicatorUiConfig?: SearchSessionIndicatorUiConfig
) {
this.searchSessionInfoProvider = {

View file

@ -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,
}),
});
}

View file

@ -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,
}
`);
});
});
});

View file

@ -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(),

View file

@ -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

View file

@ -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 <IntlProvider locale="en">{children}</IntlProvider>;
}
export const mockUrls = {
getUrlGenerator: (id: string) => ({ createUrl: () => `hello-cool-${id}-url` }),
} as unknown as UrlGeneratorsStart;

View file

@ -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,
});

View file

@ -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<CoreSetup>;
let mockCoreStart: MockedKeys<CoreStart>;
let mockShareStart: jest.Mocked<SharePluginStart>;
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,
});

View file

@ -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<CoreSetup>;
let mockCoreStart: CoreStart;
let mockShareStart: jest.Mocked<SharePluginStart>;
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,
});

View file

@ -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<CoreSetup>;
let mockCoreStart: MockedKeys<CoreStart>;
let mockShareStart: jest.Mocked<SharePluginStart>;
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,
});

View file

@ -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<string, unknown>
) {
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<PersistedSearchSessionSavedObjectAttributes>
): Promise<UISession> => {
@ -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<PersistedSearchSessionSavedObjectAttributes>
>;
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

View file

@ -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<CoreSetup>;
let mockCoreStart: CoreStart;
let mockShareStart: jest.Mocked<SharePluginStart>;
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,
});

View file

@ -21,7 +21,7 @@ export type PersistedSearchSessionSavedObjectAttributes = SearchSessionSavedObje
Required<
Pick<
SearchSessionSavedObjectAttributes,
'name' | 'appId' | 'urlGeneratorId' | 'initialState' | 'restoreState'
'name' | 'appId' | 'locatorId' | 'initialState' | 'restoreState'
>
>;

View file

@ -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,
});

View file

@ -42,7 +42,7 @@ export const searchSessionSavedObjectType: SavedObjectsType = {
appId: {
type: 'keyword',
},
urlGeneratorId: {
locatorId: {
type: 'keyword',
},
initialState: {

View file

@ -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<SearchSessionSavedObjectAttributesPre$8$0$0> = {
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<SearchSessionSavedObjectAttributesPre$8$0$0> = {
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<SearchSessionSavedObjectAttributesPre$8$0$0> = {
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<SearchSessionSavedObjectAttributesPre$8$0$0> = {
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"`
);
});
});

View file

@ -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<SearchSessionSavedObjectAttributesPre$7$13$0>
@ -60,4 +78,14 @@ export const searchSessionSavedObjectMigrations: SavedObjectMigrationMap = {
},
};
},
'8.0.0': (
doc: SavedObjectUnsanitizedDoc<SearchSessionSavedObjectAttributesPre$8$0$0>
): SavedObjectUnsanitizedDoc<SearchSessionSavedObjectAttributesLatest> => {
const {
attributes: { urlGeneratorId, ...otherAttrs },
} = doc;
const locatorId = getLocatorId(urlGeneratorId);
const attributes = { ...otherAttrs, locatorId };
return { ...doc, attributes };
},
};

View file

@ -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',
}
);

View file

@ -287,7 +287,7 @@ export class SearchSessionService
{
name,
appId,
urlGeneratorId,
locatorId,
initialState = {},
restoreState = {},
}: Partial<SearchSessionSavedObjectAttributes>
@ -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,

View file

@ -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);