Skip to content

Commit

Permalink
Finish initial implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
davismcphee committed Jan 10, 2025
1 parent a923bb8 commit 913f96f
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 81 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { withSuspense } from '@kbn/shared-ux-utility';
import { getInitialESQLQuery } from '@kbn/esql-utils';
import { ESQL_TYPE } from '@kbn/data-view-utils';
import useUnmount from 'react-use/lib/useUnmount';
import useLatest from 'react-use/lib/useLatest';
import { useUrl } from './hooks/use_url';
import { useDiscoverStateContainer } from './hooks/use_discover_state_container';
import { MainHistoryLocationState } from '../../../common';
Expand Down Expand Up @@ -136,7 +137,9 @@ export function DiscoverMainRoute({
stateContainer.actions.loadDataViewList(),
]);

if (!hasUserDataViewValue || !defaultDataViewExists) {
const hasAdHocDataViews = stateContainer.internalState.getState().adHocDataViews.length > 0;

if ((!hasUserDataViewValue || !defaultDataViewExists) && !hasAdHocDataViews) {
setNoDataState({
showNoDataPage: true,
hasESData: hasESDataValue,
Expand Down Expand Up @@ -230,22 +233,70 @@ export function DiscoverMainRoute({
]
);

const rootProfileState = useRootProfile();
const [prevProfileDataViewIds, setPrevProfileDataViewIds] = useState<string[]>([]);

const initializeProfileDataViews = useLatest(async () => {
if (rootProfileState.rootProfileLoading) {
return;
}

const profileDataViewSpecs = rootProfileState.getDefaultAdHocDataViews();
const profileDataViews = await Promise.all(
profileDataViewSpecs.map((spec) => dataViews.create(spec, true))
);
const currentDataViews = stateContainer.internalState.getState().adHocDataViews;
const newDataViews = currentDataViews
.filter((dataView) => !prevProfileDataViewIds.includes(dataView.id!))
.concat(profileDataViews);

for (const prevId of prevProfileDataViewIds) {
dataViews.clearInstanceCache(prevId);
}

setPrevProfileDataViewIds(profileDataViews.map((dataView) => dataView.id!));
stateContainer.internalState.transitions.setAdHocDataViews(newDataViews);
});

useUnmount(() => {
for (const prevId of prevProfileDataViewIds) {
dataViews.clearInstanceCache(prevId);
}
});

useEffect(() => {
if (!isCustomizationServiceInitialized) return;
setLoading(true);
setNoDataState({
hasESData: false,
hasUserDataView: false,
showNoDataPage: false,
});
setError(undefined);
if (savedSearchId) {
loadSavedSearch();
} else {
// restore the previously selected data view for a new state (when a saved search was open)
loadSavedSearch(getLoadParamsForNewSearch(stateContainer));
if (!isCustomizationServiceInitialized || rootProfileState.rootProfileLoading) {
return;
}
}, [isCustomizationServiceInitialized, loadSavedSearch, savedSearchId, stateContainer]);

const load = async () => {
setLoading(true);
setNoDataState({
hasESData: false,
hasUserDataView: false,
showNoDataPage: false,
});
setError(undefined);

await initializeProfileDataViews.current();

if (savedSearchId) {
await loadSavedSearch();
} else {
// restore the previously selected data view for a new state (when a saved search was open)
await loadSavedSearch(getLoadParamsForNewSearch(stateContainer));
}
};

load();
}, [
initializeProfileDataViews,
isCustomizationServiceInitialized,
loadSavedSearch,
rootProfileState.rootProfileLoading,
savedSearchId,
stateContainer,
]);

// secondary fetch: in case URL is set to `/`, used to reset to 'new' state, keeping the current data view
useUrl({
Expand Down Expand Up @@ -342,38 +393,6 @@ export function DiscoverMainRoute({
stateContainer,
]);

const [prevProfileDataViewIds, setPrevProfileDataViewIds] = useState<string[]>([]);
const rootProfileState = useRootProfile({
onRootProfileResolved: async ({ getDefaultAdHocDataViews }) => {
// debugger;
const profileDataViewSpecs = getDefaultAdHocDataViews?.(() => [])?.() ?? [];
const profileDataViews = await Promise.all(
profileDataViewSpecs.map((spec) => dataViews.create(spec, true))
);
const currentDataViews = stateContainer.internalState.getState().adHocDataViews;
const newDataViews = currentDataViews
.filter((dataView) => !prevProfileDataViewIds.includes(dataView.id!))
.concat(profileDataViews);

for (const prevId of prevProfileDataViewIds) {
dataViews.clearInstanceCache(prevId);
}

setPrevProfileDataViewIds(profileDataViews.map((dataView) => dataView.id!));
stateContainer.internalState.transitions.setAdHocDataViews(newDataViews);

// if (!stateContainer.internalState.getState().dataView) {
// await stateContainer.actions.onChangeDataView(newDataViews[0]);
// }
},
});

useUnmount(() => {
for (const prevId of prevProfileDataViewIds) {
dataViews.clearInstanceCache(prevId);
}
});

if (error) {
return <DiscoverError error={error} />;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type { ToastsStart } from '@kbn/core/public';
import { SavedSearch } from '@kbn/saved-search-plugin/public';
import { DiscoverInternalStateContainer } from '../discover_internal_state_container';
import { DiscoverServices } from '../../../../build_services';

interface DataViewData {
/**
* List of existing data views
Expand Down Expand Up @@ -40,11 +41,13 @@ export async function loadDataView({
dataViewSpec,
services,
dataViewList,
adHocDataViews,
}: {
id?: string;
dataViewSpec?: DataViewSpec;
services: DiscoverServices;
dataViewList: DataViewListItem[];
adHocDataViews: DataView[];
}): Promise<DataViewData> {
const { dataViews } = services;

Expand Down Expand Up @@ -102,7 +105,7 @@ export async function loadDataView({
return {
list: dataViewList || [],
// we can be certain that the data view exists due to an earlier hasData check
loaded: fetchedDataView || defaultDataView!,
loaded: fetchedDataView || defaultDataView || adHocDataViews[0],
stateVal: fetchId,
stateValFound: Boolean(fetchId) && Boolean(fetchedDataView),
};
Expand All @@ -112,13 +115,18 @@ export async function loadDataView({
* Check if the given data view is valid, provide a fallback if it doesn't exist
* And message the user in this case with toast notifications
*/
export function resolveDataView(
ip: DataViewData,
savedSearch: SavedSearch | undefined,
toastNotifications: ToastsStart,
isEsqlMode?: boolean
) {
const { loaded: loadedDataView, stateVal, stateValFound } = ip;
export function resolveDataView({
dataViewData,
savedSearch,
toastNotifications,
isEsqlMode,
}: {
dataViewData: DataViewData;
savedSearch: SavedSearch | undefined;
toastNotifications: ToastsStart;
isEsqlMode?: boolean;
}) {
const { loaded: loadedDataView, stateVal, stateValFound } = dataViewData;

const ownDataView = savedSearch?.searchSource.getField('index');

Expand Down Expand Up @@ -149,8 +157,10 @@ export function resolveDataView(
}),
'data-test-subj': 'dscDataViewNotFoundShowSavedWarning',
});

return ownDataView;
}

toastNotifications.addWarning({
title: warningTitle,
text: i18n.translate('discover.showingDefaultDataViewWarningDescription', {
Expand Down Expand Up @@ -187,19 +197,24 @@ export const loadAndResolveDataView = async (
) => {
const { adHocDataViews, savedDataViews } = internalStateContainer.getState();
const adHocDataView = adHocDataViews.find((dataView) => dataView.id === id);
if (adHocDataView) return { fallback: false, dataView: adHocDataView };

if (adHocDataView) {
return { fallback: false, dataView: adHocDataView };
}

const nextDataViewData = await loadDataView({
services,
id,
dataViewSpec,
dataViewList: savedDataViews,
adHocDataViews,
});
const nextDataView = resolveDataView(
nextDataViewData,
const nextDataView = resolveDataView({
dataViewData: nextDataViewData,
savedSearch,
services.toastNotifications,
isEsqlMode
);
toastNotifications: services.toastNotifications,
isEsqlMode,
});

return { fallback: !nextDataViewData.stateValFound, dataView: nextDataView };
};
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,23 @@
import { useEffect, useState } from 'react';
import { distinctUntilChanged, filter, switchMap, tap } from 'rxjs';
import React from 'react';
import useLatest from 'react-use/lib/useLatest';
import { useDiscoverServices } from '../../hooks/use_discover_services';
import type { Profile } from '../types';
import type { ResolveRootProfileResult } from '../profiles_manager';

/**
* Hook to trigger and wait for root profile resolution
* @param options Options object
* @returns The root profile state
*/
export const useRootProfile = ({
onRootProfileResolved: originalOnRootProfileResolved,
}: {
onRootProfileResolved?: (
result: Pick<ResolveRootProfileResult, 'getDefaultAdHocDataViews'>
) => unknown;
} = {}) => {
export const useRootProfile = () => {
const { profilesManager, core } = useDiscoverServices();
const onRootProfileResolved = useLatest(originalOnRootProfileResolved);
const [rootProfileState, setRootProfileState] = useState<
| { rootProfileLoading: true }
| { rootProfileLoading: false; AppWrapper: Profile['getRenderAppWrapper'] }
| {
rootProfileLoading: false;
AppWrapper: Profile['getRenderAppWrapper'];
getDefaultAdHocDataViews: Profile['getDefaultAdHocDataViews'];
}
>({ rootProfileLoading: true });

useEffect(() => {
Expand All @@ -41,15 +36,14 @@ export const useRootProfile = ({
distinctUntilChanged(),
filter((id) => id !== undefined),
tap(() => setRootProfileState({ rootProfileLoading: true })),
switchMap(async (id) => {
const result = await profilesManager.resolveRootProfile({ solutionNavId: id });
await onRootProfileResolved.current?.(result);
return result;
}),
tap(({ getRenderAppWrapper }) =>
switchMap((solutionNavId) => profilesManager.resolveRootProfile({ solutionNavId })),
tap(({ getRenderAppWrapper, getDefaultAdHocDataViews }) =>
setRootProfileState({
rootProfileLoading: false,
AppWrapper: getRenderAppWrapper?.(BaseAppWrapper) ?? BaseAppWrapper,
getDefaultAdHocDataViews:
getDefaultAdHocDataViews?.(baseGetDefaultAdHocDataViews) ??
baseGetDefaultAdHocDataViews,
})
)
)
Expand All @@ -58,9 +52,11 @@ export const useRootProfile = ({
return () => {
subscription.unsubscribe();
};
}, [core.chrome, onRootProfileResolved, profilesManager]);
}, [core.chrome, profilesManager]);

return rootProfileState;
};

export const BaseAppWrapper: Profile['getRenderAppWrapper'] = ({ children }) => <>{children}</>;

const baseGetDefaultAdHocDataViews: Profile['getDefaultAdHocDataViews'] = () => [];
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ export const createObservabilityRootProfileProvider = (
profile: {
getAppMenu: createGetAppMenu(services),
getDefaultAdHocDataViews: (prev) => () => {
// debugger;
return prev().concat({
id: 'all-logs-ad-hoc-data-view',
name: 'All logs',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,9 @@ export interface Profile {
* Gets an array of default ad hoc data views to display in the data view picker (e.g. "All logs")
* @returns The default data views to display in the data view picker
*/
getDefaultAdHocDataViews: () => DataViewSpec[];
getDefaultAdHocDataViews: () => Array<
Omit<DataViewSpec, 'id'> & { id: NonNullable<DataViewSpec['id']> }
>;

/**
* Data grid
Expand Down

0 comments on commit 913f96f

Please sign in to comment.