From 4e4905c08940944959e0830c7c576a78626958ef Mon Sep 17 00:00:00 2001 From: Zacqary Xeper Date: Wed, 3 Feb 2021 12:54:34 -0600 Subject: [PATCH] [Metrics UI] Fix saving/loading saved views from URL --- .../saved_views/toolbar_control.tsx | 6 +- .../containers/saved_view/saved_view.tsx | 64 +++++++++++++++---- .../infra/public/pages/metrics/index.tsx | 6 +- .../pages/metrics/inventory_view/index.tsx | 6 +- 4 files changed, 60 insertions(+), 22 deletions(-) diff --git a/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx b/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx index 698b0d3ad0caf..e1c1a482055a1 100644 --- a/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx +++ b/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx @@ -5,7 +5,7 @@ */ import { EuiFlexGroup } from '@elastic/eui'; -import React, { useCallback, useState, useEffect, useContext } from 'react'; +import React, { useCallback, useState, useEffect } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { @@ -20,7 +20,7 @@ import { SavedViewCreateModal } from './create_modal'; import { SavedViewUpdateModal } from './update_modal'; import { SavedViewManageViewsFlyout } from './manage_views_flyout'; import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; -import { SavedView } from '../../containers/saved_view/saved_view'; +import { useSavedViewContext } from '../../containers/saved_view/saved_view'; import { SavedViewListModal } from './view_list_modal'; interface Props { @@ -46,7 +46,7 @@ export function SavedViewsToolbarControls(props: Props) { updatedView, currentView, setCurrentView, - } = useContext(SavedView.Context); + } = useSavedViewContext(); const [modalOpen, setModalOpen] = useState(false); const [viewListModalOpen, setViewListModalOpen] = useState(false); const [isInvalid, setIsInvalid] = useState(false); diff --git a/x-pack/plugins/infra/public/containers/saved_view/saved_view.tsx b/x-pack/plugins/infra/public/containers/saved_view/saved_view.tsx index 3f38d8b97a296..f6cb01945a549 100644 --- a/x-pack/plugins/infra/public/containers/saved_view/saved_view.tsx +++ b/x-pack/plugins/infra/public/containers/saved_view/saved_view.tsx @@ -4,9 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ import createContainer from 'constate'; +import * as rt from 'io-ts'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { fold } from 'fp-ts/lib/Either'; +import { constant, identity } from 'fp-ts/lib/function'; import { useCallback, useMemo, useState, useEffect, useContext } from 'react'; import { i18n } from '@kbn/i18n'; import { SimpleSavedObject, SavedObjectAttributes } from 'kibana/public'; +import { useUrlState } from '../../utils/use_url_state'; import { useFindSavedObject } from '../../hooks/use_find_saved_object'; import { useCreateSavedObject } from '../../hooks/use_create_saved_object'; import { useDeleteSavedObject } from '../../hooks/use_delete_saved_object'; @@ -37,6 +42,14 @@ interface Props { shouldLoadDefault: boolean; } +const savedViewUrlStateRT = rt.type({ + viewId: rt.string, +}); +type SavedViewUrlState = rt.TypeOf; +const DEFAULT_SAVED_VIEW_STATE: SavedViewUrlState = { + viewId: '0', +}; + export const useSavedView = (props: Props) => { const { source, @@ -50,6 +63,13 @@ export const useSavedView = (props: Props) => { const { data, loading, find, error: errorOnFind, hasView } = useFindSavedObject< SavedViewSavedObject >(viewType); + const [urlState, setUrlState] = useUrlState({ + defaultState: DEFAULT_SAVED_VIEW_STATE, + decodeUrlState, + encodeUrlState, + urlStateKey: 'savedView', + }); + const [shouldLoadDefault] = useState(props.shouldLoadDefault); const [currentView, setCurrentView] = useState | null>(null); const [loadingDefaultView, setLoadingDefaultView] = useState(null); @@ -210,25 +230,35 @@ export const useSavedView = (props: Props) => { }); }, [setCurrentView, defaultViewId, defaultViewState]); - useEffect(() => { - if (loadingDefaultView || currentView || !shouldLoadDefault) { - return; - } - + const loadDefaultViewIfSet = useCallback(() => { if (defaultViewId !== '0') { loadDefaultView(); } else { setDefault(); setLoadingDefaultView(false); } - }, [ - loadDefaultView, - shouldLoadDefault, - setDefault, - loadingDefaultView, - currentView, - defaultViewId, - ]); + }, [defaultViewId, loadDefaultView, setDefault, setLoadingDefaultView]); + + useEffect(() => { + if (loadingDefaultView || currentView || !shouldLoadDefault) { + return; + } + + loadDefaultViewIfSet(); + }, [loadDefaultViewIfSet, loadingDefaultView, currentView, shouldLoadDefault]); + + useEffect(() => { + if (currentView && urlState.viewId !== currentView.id && data) + setUrlState({ viewId: currentView.id }); + }, [urlState, setUrlState, currentView, defaultViewId, data]); + + useEffect(() => { + if (!currentView && !loading && data) { + const viewToSet = views.find((v) => v.id === urlState.viewId); + if (viewToSet) setCurrentView(viewToSet); + else loadDefaultViewIfSet(); + } + }, [loading, currentView, data, views, setCurrentView, loadDefaultViewIfSet, urlState.viewId]); return { views, @@ -258,3 +288,11 @@ export const useSavedView = (props: Props) => { export const SavedView = createContainer(useSavedView); export const [SavedViewProvider, useSavedViewContext] = SavedView; + +const encodeUrlState = (state: SavedViewUrlState) => { + return savedViewUrlStateRT.encode(state); +}; +const decodeUrlState = (value: unknown) => { + const state = pipe(savedViewUrlStateRT.decode(value), fold(constant(undefined), identity)); + return state; +}; diff --git a/x-pack/plugins/infra/public/pages/metrics/index.tsx b/x-pack/plugins/infra/public/pages/metrics/index.tsx index 24f9598484d71..b3a655424ae59 100644 --- a/x-pack/plugins/infra/public/pages/metrics/index.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/index.tsx @@ -35,7 +35,7 @@ import { WaffleFiltersProvider } from './inventory_view/hooks/use_waffle_filters import { InventoryAlertDropdown } from '../../alerting/inventory/components/alert_dropdown'; import { MetricsAlertDropdown } from '../../alerting/metric_threshold/components/alert_dropdown'; -import { SavedView } from '../../containers/saved_view/saved_view'; +import { SavedViewProvider } from '../../containers/saved_view/saved_view'; import { SourceConfigurationFields } from '../../graphql/types'; import { AlertPrefillProvider } from '../../alerting/use_alert_prefill'; import { InfraMLCapabilitiesProvider } from '../../containers/ml/infra_ml_capabilities'; @@ -196,7 +196,7 @@ const PageContent = (props: { const { options } = useContext(MetricsExplorerOptionsContainer.Context); return ( - - + ); }; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/index.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/index.tsx index 976fead10f7b6..2f5d36702afb0 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/index.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/index.tsx @@ -22,7 +22,7 @@ import { useTrackPageview } from '../../../../../observability/public'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; import { Layout } from './components/layout'; import { useLinkProps } from '../../../hooks/use_link_props'; -import { SavedView } from '../../../containers/saved_view/saved_view'; +import { SavedViewProvider } from '../../../containers/saved_view/saved_view'; import { DEFAULT_WAFFLE_VIEW_STATE } from './hooks/use_waffle_view_state'; import { useWaffleOptionsContext } from './hooks/use_waffle_options'; @@ -63,13 +63,13 @@ export const SnapshotPage = () => { ) : metricIndicesExist ? ( <> - - + ) : hasFailedLoadingSource ? (