mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[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:
parent
1211149038
commit
eb74b86ca4
29 changed files with 411 additions and 188 deletions
|
@ -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 }),
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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 {};
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}),
|
||||
});
|
||||
}, [
|
||||
|
|
|
@ -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 } = {},
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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.)
|
||||
*/
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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: {},
|
||||
}),
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -21,7 +21,7 @@ export type PersistedSearchSessionSavedObjectAttributes = SearchSessionSavedObje
|
|||
Required<
|
||||
Pick<
|
||||
SearchSessionSavedObjectAttributes,
|
||||
'name' | 'appId' | 'urlGeneratorId' | 'initialState' | 'restoreState'
|
||||
'name' | 'appId' | 'locatorId' | 'initialState' | 'restoreState'
|
||||
>
|
||||
>;
|
||||
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -42,7 +42,7 @@ export const searchSessionSavedObjectType: SavedObjectsType = {
|
|||
appId: {
|
||||
type: 'keyword',
|
||||
},
|
||||
urlGeneratorId: {
|
||||
locatorId: {
|
||||
type: 'keyword',
|
||||
},
|
||||
initialState: {
|
||||
|
|
|
@ -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"`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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 };
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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',
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue