Skip to content

Commit

Permalink
[Discover] A bunch of navigation fixes (opensearch-project#5168)
Browse files Browse the repository at this point in the history
* Discover: Fixes state persistence after nav
* Fixed breadcrumbs and navigation
* fixes mobile view

---------

Signed-off-by: Ashwin P Chandran <ashwinpc@amazon.com>

(cherry picked from commit cb6e0f0)
Signed-off-by: Miki <miki@amazon.com>
  • Loading branch information
ashwin-pc authored and AMoo-Miki committed Oct 4, 2023
1 parent abed99a commit 28e47d8
Show file tree
Hide file tree
Showing 15 changed files with 200 additions and 173 deletions.
32 changes: 31 additions & 1 deletion src/plugins/data_explorer/public/utils/state_management/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ import { reducer as metadataReducer } from './metadata_slice';
import { loadReduxState, persistReduxState } from './redux_persistence';
import { DataExplorerServices } from '../../types';

const HYDRATE = 'HYDRATE';

export const hydrate = (newState: RootState) => ({
type: HYDRATE,
payload: newState,
});

const commonReducers = {
metadata: metadataReducer,
};
Expand All @@ -22,9 +29,20 @@ let dynamicReducers: {

const rootReducer = combineReducers(dynamicReducers);

const createRootReducer = (): Reducer<RootState> => {
const combinedReducer = combineReducers(dynamicReducers);

return (state: RootState | undefined, action: any): RootState => {
if (action.type === HYDRATE) {
return action.payload;
}
return combinedReducer(state, action);
};
};

export const configurePreloadedStore = (preloadedState: PreloadedState<RootState>) => {
// After registering the slices the root reducer needs to be updated
const updatedRootReducer = combineReducers(dynamicReducers);
const updatedRootReducer = createRootReducer();

return configureStore({
reducer: updatedRootReducer,
Expand Down Expand Up @@ -62,6 +80,18 @@ export const getPreloadedStore = async (services: DataExplorerServices) => {
// the store subscriber will automatically detect changes and call handleChange function
const unsubscribe = store.subscribe(handleChange);

// This is necessary because browser navigation updates URL state that isnt reflected in the redux state
services.scopedHistory.listen(async (location, action) => {
const urlState = await loadReduxState(services);
const currentState = store.getState();

// If the url state is different from the current state, then we need to update the store
// the state should have a view property if it was loaded from the url
if (action === 'POP' && urlState.metadata?.view && !isEqual(urlState, currentState)) {
store.dispatch(hydrate(urlState as RootState));
}
});

const onUnsubscribe = () => {
dynamicReducers = {
...commonReducers,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export function SurroundingDocsApp() {

useEffect(() => {
chrome.setBreadcrumbs([
...getRootBreadcrumbs(baseUrl),
...getRootBreadcrumbs(),
{
text: i18n.translate('discover.context.breadcrumb', {
defaultMessage: `Context of #{docId}`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export const SurroundingDocsView = ({ id, indexPattern }: SurroundingDocsViewPar
field,
values,
operation,
indexPattern.id
indexPattern.id || ''
);
return filterManager.addFilters(newFilters);
},
Expand All @@ -115,23 +115,25 @@ export const SurroundingDocsView = ({ id, indexPattern }: SurroundingDocsViewPar
[onAddFilter, rows, indexPattern, setContextAppState, contextQueryState, contextAppState]
);

if (isLoading) {
return null;
}

return (
!isLoading && (
<Fragment>
<TopNavMenu
appName={'discover.context.surroundingDocs.topNavMenu'}
showSearchBar={true}
showQueryBar={false}
showDatePicker={false}
indexPatterns={[indexPattern]}
useDefaultBehaviors={true}
/>
<EuiPage className="discover.context.appPage">
<EuiPageContent paddingSize="s" className="dscDocsContent">
{contextAppMemoized}
</EuiPageContent>
</EuiPage>
</Fragment>
)
<Fragment>
<TopNavMenu
appName={'discover.context.surroundingDocs.topNavMenu'}
showSearchBar={true}
showQueryBar={false}
showDatePicker={false}
indexPatterns={[indexPattern]}
useDefaultBehaviors={true}
/>
<EuiPage className="discover.context.appPage">
<EuiPageContent paddingSize="s" className="dscDocsContent">
{contextAppMemoized}
</EuiPageContent>
</EuiPage>
</Fragment>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,8 @@
&:focus {
opacity: 1;
}

@include ouiBreakpoint("xs", "s", "m") {
opacity: 1;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
import { DiscoverState, setSavedSearchId } from '../../utils/state_management';
import { DOC_HIDE_TIME_COLUMN_SETTING, SORT_DEFAULT_ORDER_SETTING } from '../../../../common';
import { getSortForSearchSource } from '../../view_components/utils/get_sort_for_search_source';
import { getRootBreadcrumbs } from '../../helpers/breadcrumbs';

export const getTopNavLinks = (
services: DiscoverViewServices,
Expand All @@ -45,11 +46,9 @@ export const getTopNavLinks = (
defaultMessage: 'New Search',
}),
run() {
setTimeout(() => {
history().push('/');
// TODO: figure out why a history push doesn't update the app state. The page reload is a hack around it
window.location.reload();
}, 0);
core.application.navigateToApp('discover', {
path: '#/',
});
},
testId: 'discoverNewButton',
};
Expand Down Expand Up @@ -103,15 +102,7 @@ export const getTopNavLinks = (
history().push(`/view/${encodeURIComponent(id)}`);
} else {
chrome.docTitle.change(savedSearch.lastSavedTitle);
chrome.setBreadcrumbs([
{
text: i18n.translate('discover.discoverBreadcrumbTitle', {
defaultMessage: 'Discover',
}),
href: '#/',
},
{ text: savedSearch.title },
]);
chrome.setBreadcrumbs([...getRootBreadcrumbs(), { text: savedSearch.title }]);
}

// set App state to clean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ interface Props {
export function OpenSearchPanel({ onClose, makeUrl }: Props) {
const {
services: {
core: { uiSettings, savedObjects },
core: { uiSettings, savedObjects, application },
addBasePath,
},
} = useOpenSearchDashboards<DiscoverViewServices>();
Expand Down Expand Up @@ -90,12 +90,8 @@ export function OpenSearchPanel({ onClose, makeUrl }: Props) {
},
]}
onChoose={(id) => {
setTimeout(() => {
window.location.assign(makeUrl(id));
// TODO: figure out why a history push doesn't update the app state. The page reload is a hack around it
window.location.reload();
onClose();
}, 0);
application.navigateToApp('discover', { path: `#/view/${id}` });
onClose();
}}
uiSettings={uiSettings}
savedObjects={savedObjects}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,17 @@
*/

import { i18n } from '@osd/i18n';
import { EuiBreadcrumb } from '@elastic/eui';
import { getServices } from '../../opensearch_dashboards_services';

export function getRootBreadcrumbs(baseUrl: string) {
export function getRootBreadcrumbs(): EuiBreadcrumb[] {
const { core } = getServices();
return [
{
text: i18n.translate('discover.rootBreadcrumb', {
defaultMessage: 'Discover',
}),
href: baseUrl,
onClick: () => core.application.navigateToApp('discover'),
},
];
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { RootState, DefaultViewState } from '../../../../../data_explorer/public
import { buildColumns } from '../columns';
import * as utils from './common';
import { SortOrder } from '../../../saved_searches/types';
import { PLUGIN_ID } from '../../../../common';

export interface DiscoverState {
/**
Expand Down Expand Up @@ -79,6 +80,7 @@ export const getPreloadedState = async ({
preloadedState.root = {
metadata: {
indexPattern: indexPatternId,
view: PLUGIN_ID,
},
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { DiscoverChart } from '../../components/chart/chart';

export const DiscoverChartContainer = ({ hits, bucketInterval, chartData }: SearchData) => {
const { services } = useOpenSearchDashboards<DiscoverViewServices>();
const { uiSettings, data } = services;
const { uiSettings, data, core } = services;
const { indexPattern, savedSearch } = useDiscoverContext();

const isTimeBased = useMemo(() => (indexPattern ? indexPattern.isTimeBased() : false), [
Expand All @@ -30,8 +30,7 @@ export const DiscoverChartContainer = ({ hits, bucketInterval, chartData }: Sear
data={data}
hits={hits}
resetQuery={() => {
window.location.href = `#/view/${savedSearch?.id}`;
window.location.reload();
core.application.navigateToApp('discover', { path: `#/view/${savedSearch?.id}` });
}}
services={services}
showResetButton={!!savedSearch && !!savedSearch.id}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

import React, { useEffect, useState, useRef, useCallback } from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui';
import { EuiPanel } from '@elastic/eui';
import { TopNav } from './top_nav';
import { ViewProps } from '../../../../../data_explorer/public';
import { DiscoverTable } from './discover_table';
Expand All @@ -20,6 +20,7 @@ import { useOpenSearchDashboards } from '../../../../../opensearch_dashboards_re
import { filterColumns } from '../utils/filter_columns';
import { DEFAULT_COLUMNS_SETTING } from '../../../../common';
import './discover_canvas.scss';

// eslint-disable-next-line import/no-default-export
export default function DiscoverCanvas({ setHeaderActionMenu, history }: ViewProps) {
const { data$, refetch$, indexPattern } = useDiscoverContext();
Expand Down Expand Up @@ -77,38 +78,36 @@ export default function DiscoverCanvas({ setHeaderActionMenu, history }: ViewPro
const timeField = indexPattern?.timeFieldName ? indexPattern.timeFieldName : undefined;

return (
<EuiFlexGroup direction="column" gutterSize="none" className="dscCanvas">
<EuiFlexItem grow={false}>
<TopNav
opts={{
setHeaderActionMenu,
onQuerySubmit,
}}
/>
</EuiFlexItem>
<EuiPanel
hasBorder={false}
hasShadow={false}
color="transparent"
paddingSize="none"
className="dscCanvas"
>
<TopNav
opts={{
setHeaderActionMenu,
onQuerySubmit,
}}
/>
{status === ResultStatus.NO_RESULTS && (
<EuiFlexItem>
<DiscoverNoResults timeFieldName={timeField} queryLanguage={''} />
</EuiFlexItem>
<DiscoverNoResults timeFieldName={timeField} queryLanguage={''} />
)}
{status === ResultStatus.UNINITIALIZED && (
<DiscoverUninitialized onRefresh={() => refetch$.next()} />
)}
{status === ResultStatus.LOADING && <LoadingSpinner />}
{status === ResultStatus.READY && (
<>
<EuiFlexItem grow={false}>
<EuiPanel hasBorder={false} hasShadow={false} color="transparent" paddingSize="s">
<EuiPanel>
<DiscoverChartContainer {...fetchState} />
</EuiPanel>
<EuiPanel hasBorder={false} hasShadow={false} color="transparent" paddingSize="s">
<EuiPanel>
<DiscoverChartContainer {...fetchState} />
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem>
<DiscoverTable history={history} />
</EuiFlexItem>
</EuiPanel>
<DiscoverTable history={history} />
</>
)}
</EuiFlexGroup>
</EuiPanel>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,9 @@ export const TopNav = ({ opts }: TopNavProps) => {
chrome.docTitle.change(`Discover${pageTitleSuffix}`);

if (savedSearch?.id) {
chrome.setBreadcrumbs([
...getRootBreadcrumbs(getUrlForApp(PLUGIN_ID)),
{ text: savedSearch.title },
]);
chrome.setBreadcrumbs([...getRootBreadcrumbs(), { text: savedSearch.title }]);
} else {
chrome.setBreadcrumbs([...getRootBreadcrumbs(getUrlForApp(PLUGIN_ID))]);
chrome.setBreadcrumbs([...getRootBreadcrumbs()]);
}
}, [chrome, getUrlForApp, savedSearch?.id, savedSearch?.title]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ const SearchContext = React.createContext<SearchContextValue>({} as SearchContex

// eslint-disable-next-line import/no-default-export
export default function DiscoverContext({ children }: React.PropsWithChildren<ViewProps>) {
const { services: deServices } = useOpenSearchDashboards<DataExplorerServices>();
const services = getServices();
const searchParams = useSearch(services);
const searchParams = useSearch({
...deServices,
...services,
});

const {
services: { osdUrlStateStorage },
} = useOpenSearchDashboards<DataExplorerServices>();
const { osdUrlStateStorage } = deServices;

// Connect the query service to the url state
useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { i18n } from '@osd/i18n';
import { useEffect } from 'react';
import { cloneDeep } from 'lodash';
import { RequestAdapter } from '../../../../../inspector/public';
import { DiscoverServices } from '../../../build_services';
import { DiscoverViewServices } from '../../../build_services';
import { search } from '../../../../../data/public';
import { validateTimeRange } from '../../helpers/validate_time_range';
import { updateSearchSource } from './update_search_source';
Expand Down Expand Up @@ -66,7 +66,7 @@ export type RefetchSubject = Subject<SearchRefetch>;
* return () => subscription.unsubscribe();
* }, [data$]);
*/
export const useSearch = (services: DiscoverServices) => {
export const useSearch = (services: DiscoverViewServices) => {
const initalSearchComplete = useRef(false);
const [savedSearch, setSavedSearch] = useState<SavedSearch | undefined>(undefined);
const { savedSearch: savedSearchId, sort, interval } = useSelector((state) => state.discover);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,7 @@ export const [getScopedHistory, setScopedHistory] = createGetterSetter<ScopedHis

export const { getRequestInspectorStats, getResponseInspectorStats, tabifyAggResponse } = search;
export { unhashUrl, redirectWhenMissing } from '../../opensearch_dashboards_utils/public';
export {
formatMsg,
formatStack,
subscribeWithScope,
} from '../../opensearch_dashboards_legacy/public';
export { formatMsg, formatStack } from '../../opensearch_dashboards_legacy/public';

// EXPORT types
export {
Expand Down

0 comments on commit 28e47d8

Please sign in to comment.