Skip to content

Commit a40ea08

Browse files
committed
Initial URL state implementation
1 parent dbe99d9 commit a40ea08

21 files changed

Lines changed: 505 additions & 25 deletions

File tree

src/platform/plugins/shared/discover/common/app_locator.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import type { VIEW_MODE, NEW_TAB_ID } from './constants';
1919

2020
export const DISCOVER_APP_LOCATOR = 'DISCOVER_APP_LOCATOR';
2121

22+
export type DiscoverProfileUrlState = Record<string, SerializableRecord>;
23+
2224
export interface DiscoverAppLocatorParams extends SerializableRecord {
2325
/**
2426
* Optionally set saved search ID.
@@ -75,6 +77,11 @@ export interface DiscoverAppLocatorParams extends SerializableRecord {
7577
*/
7678
tab?: { id: typeof NEW_TAB_ID | string; label?: string };
7779

80+
/**
81+
* Optionally set URL-synced profile state.
82+
*/
83+
profileUrlState?: DiscoverProfileUrlState;
84+
7885
/**
7986
* Columns displayed in the table
8087
*/

src/platform/plugins/shared/discover/common/app_locator_get_location.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,19 @@ import type { GlobalQueryStateFromUrl } from '@kbn/data-plugin/public';
1111
import { isFilterPinned, isOfAggregateQueryType } from '@kbn/es-query';
1212
import type { setStateToKbnUrl as setStateToKbnUrlCommon } from '@kbn/kibana-utils-plugin/common';
1313
import type {
14+
DiscoverProfileUrlState,
1415
DiscoverAppLocatorGetLocation,
1516
DiscoverAppLocatorParams,
1617
MainHistoryLocationState,
1718
} from './app_locator';
1819
import type { DiscoverAppState } from '../public';
1920
import { createDataViewDataSource, createEsqlDataSource } from './data_sources';
20-
import { APP_STATE_URL_KEY, GLOBAL_STATE_URL_KEY, TAB_STATE_URL_KEY } from './constants';
21+
import {
22+
APP_STATE_URL_KEY,
23+
GLOBAL_STATE_URL_KEY,
24+
PROFILE_STATE_URL_KEY,
25+
TAB_STATE_URL_KEY,
26+
} from './constants';
2127

2228
export const appLocatorGetLocationCommon = async (
2329
{
@@ -38,7 +44,7 @@ export const appLocatorGetLocationCommon = async (
3844
path = `${path}?searchSessionId=${searchSessionId}`;
3945
}
4046

41-
const { appState, globalState, state } = parseAppLocatorParams(params);
47+
const { appState, globalState, profileUrlState, state } = parseAppLocatorParams(params);
4248

4349
if (Object.keys(globalState).length) {
4450
path = setStateToKbnUrl<GlobalQueryStateFromUrl>(
@@ -53,6 +59,10 @@ export const appLocatorGetLocationCommon = async (
5359
path = setStateToKbnUrl(APP_STATE_URL_KEY, appState, { useHash }, path);
5460
}
5561

62+
if (profileUrlState && Object.keys(profileUrlState).length) {
63+
path = setStateToKbnUrl(PROFILE_STATE_URL_KEY, profileUrlState, { useHash }, path);
64+
}
65+
5666
if (tab?.id) {
5767
path = setStateToKbnUrl(
5868
TAB_STATE_URL_KEY,
@@ -91,10 +101,13 @@ export const parseAppLocatorParams = (params: DiscoverAppLocatorParams) => {
91101
sampleSize,
92102
isAlertResults,
93103
esqlControls,
104+
profileUrlState,
94105
} = params;
95106

96107
const appState: Partial<DiscoverAppState> = {};
97108
const globalState: GlobalQueryStateFromUrl = {};
109+
const parsedProfileUrlState: DiscoverProfileUrlState | undefined =
110+
profileUrlState && Object.keys(profileUrlState).length > 0 ? profileUrlState : undefined;
98111

99112
if (query) appState.query = query;
100113
if (filters && filters.length) appState.filters = filters?.filter((f) => !isFilterPinned(f));
@@ -123,5 +136,5 @@ export const parseAppLocatorParams = (params: DiscoverAppLocatorParams) => {
123136
if (isAlertResults) state.isAlertResults = isAlertResults;
124137
if (esqlControls) state.esqlControls = esqlControls;
125138

126-
return { appState, globalState, state };
139+
return { appState, globalState, profileUrlState: parsedProfileUrlState, state };
127140
};

src/platform/plugins/shared/discover/common/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export const NEW_TAB_ID = 'new' as const;
4141
*/
4242
export const APP_STATE_URL_KEY = '_a';
4343
export const GLOBAL_STATE_URL_KEY = '_g';
44+
export const PROFILE_STATE_URL_KEY = '_p';
4445
export const TAB_STATE_URL_KEY = '_tab'; // `_t` is already used by Kibana for time, so we use `_tab` here
4546

4647
/**

src/platform/plugins/shared/discover/common/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,16 @@
1010
export const PLUGIN_ID = 'discover';
1111
export const APP_ICON = 'discoverApp';
1212

13-
export { APP_STATE_URL_KEY, EMBEDDABLE_TRANSFORMS_FEATURE_FLAG_KEY } from './constants';
13+
export {
14+
APP_STATE_URL_KEY,
15+
EMBEDDABLE_TRANSFORMS_FEATURE_FLAG_KEY,
16+
PROFILE_STATE_URL_KEY,
17+
} from './constants';
1418
export { DISCOVER_APP_LOCATOR } from './app_locator';
1519
export type {
1620
DiscoverAppLocator,
1721
DiscoverAppLocatorParams,
22+
DiscoverProfileUrlState,
1823
MainHistoryLocationState,
1924
} from './app_locator';
2025

src/platform/plugins/shared/discover/public/application/main/components/top_nav/app_menu_actions/get_share.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ interface BuildShareOptionsParams {
3333
discoverParams: AppMenuDiscoverParams;
3434
services: DiscoverServices;
3535
currentTab: TabState;
36+
profileUrlState?: DiscoverAppLocatorParams['profileUrlState'];
3637
persistedDiscoverSession: DiscoverSession | undefined;
3738
totalHitsState: DataTotalHitsMsg;
3839
hasUnsavedChanges: boolean;
@@ -50,6 +51,7 @@ export const buildShareOptions = async ({
5051
discoverParams,
5152
services,
5253
currentTab,
54+
profileUrlState,
5355
persistedDiscoverSession,
5456
totalHitsState,
5557
hasUnsavedChanges,
@@ -84,6 +86,7 @@ export const buildShareOptions = async ({
8486
...(dataView?.isPersisted()
8587
? { dataViewId: dataView?.id }
8688
: { dataViewSpec: dataView?.toMinimalSpec() }),
89+
...(profileUrlState ? { profileUrlState } : {}),
8790
filters,
8891
timeRange,
8992
refreshInterval,
@@ -256,6 +259,7 @@ export const getShareAppMenuItem = ({
256259
hasIntegrations,
257260
hasUnsavedChanges,
258261
currentTab,
262+
profileUrlState,
259263
persistedDiscoverSession,
260264
totalHitsState,
261265
intl,
@@ -265,6 +269,7 @@ export const getShareAppMenuItem = ({
265269
hasIntegrations: boolean;
266270
hasUnsavedChanges: boolean;
267271
currentTab: TabState;
272+
profileUrlState?: DiscoverAppLocatorParams['profileUrlState'];
268273
persistedDiscoverSession: DiscoverSession | undefined;
269274
totalHitsState: DataTotalHitsMsg;
270275
intl: IntlShape;
@@ -278,6 +283,7 @@ export const getShareAppMenuItem = ({
278283
discoverParams,
279284
services,
280285
currentTab,
286+
profileUrlState,
281287
persistedDiscoverSession,
282288
totalHitsState,
283289
hasUnsavedChanges,
@@ -306,6 +312,7 @@ export const getShareAppMenuItem = ({
306312
discoverParams,
307313
services,
308314
currentTab,
315+
profileUrlState,
309316
persistedDiscoverSession,
310317
totalHitsState,
311318
hasUnsavedChanges,

src/platform/plugins/shared/discover/public/application/main/components/top_nav/use_top_nav_links.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import {
4242
import { useProfileAccessor } from '../../../../context_awareness';
4343
import {
4444
internalStateActions,
45+
selectTabRuntimeState,
4546
selectTabSavedSearchByValueAttributes,
4647
useCurrentDataView,
4748
useCurrentTabSelector,
@@ -52,6 +53,7 @@ import {
5253
useRuntimeStateManager,
5354
} from '../../state_management/redux';
5455
import type { DiscoverAppState } from '../../state_management/redux';
56+
import { getProfileUrlState } from '../../state_management/utils/get_profile_url_state';
5557
import { useCurrentTabMenuActions } from '../../hooks/use_current_tab_menu_actions';
5658
import { useDataState } from '../../hooks/use_data_state';
5759
import { TransferAction } from '../../../../plugin_imports/embeddable_editor_service';
@@ -146,6 +148,16 @@ export const useTopNavLinks = ({
146148

147149
const canCreateESQLRule = !!services.capabilities.alertingVTwo;
148150
const showCreateRuleV2 = isEsqlMode && canCreateESQLRule;
151+
const activeProfileStateDefinition = selectTabRuntimeState(runtimeStateManager, currentTab.id)
152+
.scopedProfilesManager$.getValue()
153+
.getContexts().dataSourceContext.profileState;
154+
const profileUrlState = activeProfileStateDefinition
155+
? getProfileUrlState({
156+
definition: activeProfileStateDefinition,
157+
profileState: currentTab.profileState,
158+
profileStateRegistry: services.profileStateRegistry,
159+
})
160+
: undefined;
149161

150162
const appMenuItems: DiscoverAppMenuItemType[] = useMemo(() => {
151163
const items: DiscoverAppMenuItemType[] = [];
@@ -227,6 +239,7 @@ export const useTopNavLinks = ({
227239
hasIntegrations: hasShareIntegration,
228240
hasUnsavedChanges,
229241
currentTab,
242+
profileUrlState,
230243
persistedDiscoverSession,
231244
totalHitsState,
232245
intl,
@@ -288,6 +301,7 @@ export const useTopNavLinks = ({
288301
currentDataView,
289302
currentTab,
290303
isDataViewMode,
304+
profileUrlState,
291305
openInspector,
292306
persistedDiscoverSession,
293307
hasShareIntegration,

src/platform/plugins/shared/discover/public/application/main/state_management/discover_data_state_container.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import { fetchAll, type CommonFetchParams, fetchMoreDocuments } from '../data_fe
4545
import { sendResetMsg } from '../hooks/use_saved_search_messages';
4646
import { getFetch$ } from '../data_fetching/get_fetch_observable';
4747
import { getDefaultProfileState, getProfileStateSnapshot } from './utils/default_profile_state';
48+
import { getRestoredProfileUrlState } from './utils/get_profile_url_state';
4849
import type { InternalStateStore, RuntimeStateManager, TabActionInjector, TabState } from './redux';
4950
import { internalStateActions, selectTabRuntimeState } from './redux';
5051
import { buildEsqlFetchSubscribe } from './utils/build_esql_fetch_subscribe';
@@ -383,7 +384,39 @@ export function getDataStateContainer({
383384

384385
// If the data source profile changed, we may need to restore previous profile state
385386
if (didProfileChange) {
386-
const profileId = scopedProfilesManager.getContexts().dataSourceContext.profileId;
387+
const { dataSourceContext } = scopedProfilesManager.getContexts();
388+
const profileId = dataSourceContext.profileId;
389+
const activeProfileStateDefinition = dataSourceContext.profileState;
390+
const initialProfileUrlState = getCurrentTab().initialProfileUrlState;
391+
392+
if (isFirstResolution && initialProfileUrlState) {
393+
if (activeProfileStateDefinition) {
394+
const restoredProfileUrlState = getRestoredProfileUrlState({
395+
definition: activeProfileStateDefinition,
396+
profileStateRegistry: services.profileStateRegistry,
397+
profileUrlState: initialProfileUrlState,
398+
});
399+
400+
if (restoredProfileUrlState) {
401+
internalState.dispatch(
402+
injectCurrentTab(internalStateActions.setProfileState)({
403+
key: activeProfileStateDefinition.key,
404+
profileState: {
405+
...(getCurrentTab().profileState[activeProfileStateDefinition.key] ?? {}),
406+
...restoredProfileUrlState,
407+
},
408+
})
409+
);
410+
}
411+
}
412+
413+
internalState.dispatch(
414+
injectCurrentTab(internalStateActions.setInitialProfileUrlState)({
415+
initialProfileUrlState: undefined,
416+
})
417+
);
418+
}
419+
387420
const profileStateSnapshot =
388421
getCurrentTab().defaultProfileState.snapshotsByProfileId[profileId];
389422
const profileStateUpdate = getProfileStateSnapshot(
@@ -428,6 +461,10 @@ export function getDataStateContainer({
428461
injectCurrentTab(internalStateActions.syncProfileStateSnapshot)({})
429462
);
430463
}
464+
465+
await internalState.dispatch(
466+
injectCurrentTab(internalStateActions.updateProfileUrlStateAndReplaceUrl)()
467+
);
431468
}
432469

433470
const dataView = currentDataView$.getValue();

src/platform/plugins/shared/discover/public/application/main/state_management/redux/actions/initialize_single_tab.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@ import { loadAndResolveDataView } from '../../utils/resolve_data_view';
2525
import { isDataViewSource } from '../../../../../../common/data_sources';
2626
import { isRefreshIntervalValid, isTimeRangeValid } from '../../../../../utils/validate_time';
2727
import { getValidFilters } from '../../../../../utils/get_valid_filters';
28-
import { APP_STATE_URL_KEY } from '../../../../../../common';
28+
import { APP_STATE_URL_KEY, type DiscoverProfileUrlState } from '../../../../../../common';
2929
import { selectTabRuntimeState } from '../runtime_state';
3030
import type { ConnectedCustomizationService } from '../../../../../customizations';
3131
import { selectTab } from '../selectors';
3232
import type { TabState, TabStateGlobalState } from '../types';
33-
import { GLOBAL_STATE_URL_KEY } from '../../../../../../common/constants';
33+
import { GLOBAL_STATE_URL_KEY, PROFILE_STATE_URL_KEY } from '../../../../../../common/constants';
3434
import { fromSavedObjectTabToSearchSource } from '../tab_mapping_utils';
3535
import { createInternalStateAsyncThunk, extractEsqlVariables } from '../utils';
3636
import { fetchData, updateAttributes } from './tab_state';
@@ -111,12 +111,21 @@ export const initializeSingleTab = createInternalStateAsyncThunk(
111111
// to avoid race conditions if the URL changes during tab initialization,
112112
// e.g. if the user quickly switches tabs
113113
const urlGlobalState = urlStateStorage.get<GlobalQueryStateFromUrl>(GLOBAL_STATE_URL_KEY);
114+
const urlProfileState =
115+
urlStateStorage.get<DiscoverProfileUrlState>(PROFILE_STATE_URL_KEY) ?? undefined;
114116
const urlAppState = {
115117
...tabInitialAppState,
116118
...(defaultUrlState ??
117119
cleanupUrlState(urlStateStorage.get<AppStateUrl>(APP_STATE_URL_KEY), services.uiSettings)),
118120
};
119121

122+
dispatch(
123+
internalStateSlice.actions.setInitialProfileUrlState({
124+
tabId,
125+
initialProfileUrlState: urlProfileState,
126+
})
127+
);
128+
120129
const discoverTabLoadTracker = scopedEbtManager$
121130
.getValue()
122131
.trackPerformanceEvent('discoverLoadSavedSearch');

src/platform/plugins/shared/discover/public/application/main/state_management/redux/actions/tab_state.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,11 @@ import {
1818
isOfQueryType,
1919
} from '@kbn/es-query';
2020
import { getInitialESQLQuery } from '@kbn/esql-utils';
21-
import { GLOBAL_STATE_URL_KEY } from '../../../../../../common/constants';
21+
import { GLOBAL_STATE_URL_KEY, PROFILE_STATE_URL_KEY } from '../../../../../../common/constants';
2222
import { APP_STATE_URL_KEY } from '../../../../../../common';
2323
import { DataSourceType } from '../../../../../../common/data_sources';
2424
import { isEqualState } from '../../utils/state_comparators';
25+
import { getCurrentTabProfileUrlState } from '../../utils/get_profile_url_state';
2526
import {
2627
internalStateSlice,
2728
type InternalStateThunkActionCreator,
@@ -213,9 +214,36 @@ export const pushCurrentTabStateToUrl: InternalStateThunkActionCreator<
213214
await Promise.all([
214215
dispatch(updateGlobalStateAndReplaceUrl({ tabId, globalState: {} })),
215216
dispatch(updateAppStateAndReplaceUrl({ tabId, appState: {} })),
217+
dispatch(updateProfileUrlStateAndReplaceUrl({ tabId })),
216218
]);
217219
};
218220

221+
export const updateProfileUrlStateAndReplaceUrl: InternalStateThunkActionCreator<
222+
[TabActionPayload],
223+
Promise<void>
224+
> = ({ tabId }) =>
225+
async function updateProfileUrlStateAndReplaceUrlThunkFn(
226+
_dispatch,
227+
getState,
228+
{ runtimeStateManager, services, urlStateStorage }
229+
) {
230+
const currentState = getState();
231+
232+
if (currentState.tabs.unsafeCurrentId !== tabId) {
233+
return;
234+
}
235+
236+
const profileUrlState =
237+
getCurrentTabProfileUrlState({
238+
getState,
239+
runtimeStateManager,
240+
profileStateRegistry: services.profileStateRegistry,
241+
tabId,
242+
}) ?? null;
243+
244+
await urlStateStorage.set(PROFILE_STATE_URL_KEY, profileUrlState, { replace: true });
245+
};
246+
219247
/**
220248
* Triggered when transitioning from ESQL to Dataview
221249
* Clean ups the ES|QL query and moves to the dataview mode

0 commit comments

Comments
 (0)