Skip to content

Commit

Permalink
Save assets details state to the URL
Browse files Browse the repository at this point in the history
  • Loading branch information
crespocarlos authored and mykolaharmash committed Aug 28, 2023
1 parent 142e625 commit a526204
Show file tree
Hide file tree
Showing 22 changed files with 219 additions and 143 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,12 @@ export const AssetDetails = ({
tabs,
links,
renderMode,
activeTabId,
metricAlias,
...props
}: AssetDetailsProps) => {
return (
<ContextProviders props={{ ...props, renderMode }}>
<TabSwitcherProvider
initialActiveTabId={tabs.length > 0 ? activeTabId ?? tabs[0].id : undefined}
>
<TabSwitcherProvider defaultActiveTabId={tabs[0]?.id}>
<DataViewsProvider metricAlias={metricAlias}>
<ContentTemplate header={{ tabs, links }} renderMode={renderMode} />
</DataViewsProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,12 @@ export class AssetDetailsEmbeddable extends Embeddable<AssetDetailsEmbeddableInp
<EuiThemeProvider>
<div style={{ width: '100%' }}>
<LazyAssetDetailsWrapper
activeTabId={this.input.activeTabId}
dateRange={this.input.dateRange}
asset={this.input.asset}
assetType={this.input.assetType}
overrides={this.input.overrides}
renderMode={this.input.renderMode}
tabs={this.input.tabs}
onTabsStateChange={this.input.onTabsStateChange}
links={this.input.links}
metricAlias={this.input.metricAlias}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const ContextProviders = ({
}: { props: Omit<AssetDetailsProps, 'links' | 'tabs' | 'activeTabId' | 'metricAlias'> } & {
children: React.ReactNode;
}) => {
const { asset, dateRange, overrides, onTabsStateChange, assetType = 'host', renderMode } = props;
const { asset, dateRange, overrides, assetType = 'host', renderMode } = props;
return (
<DateRangeProvider initialDateRange={dateRange}>
<MetadataStateProvider asset={asset} assetType={assetType}>
Expand All @@ -26,7 +26,6 @@ export const ContextProviders = ({
asset,
assetType,
overrides,
onTabsStateChange,
renderMode,
}}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,17 @@

import { EuiSuperDatePicker, type OnTimeChangeProps } from '@elastic/eui';
import React, { useCallback } from 'react';
import { useAssetDetailsStateContext } from '../hooks/use_asset_details_state';
import { useDateRangeProviderContext } from '../hooks/use_date_range';

export const DatePicker = () => {
const { onTabsStateChange } = useAssetDetailsStateContext();
const { dateRange, setDateRange } = useDateRangeProviderContext();
const onTimeChange = useCallback(
({ start, end, isInvalid }: OnTimeChangeProps) => {
if (!isInvalid) {
setDateRange({ from: start, to: end });
if (onTabsStateChange) {
onTabsStateChange({ dateRange: { from: start, to: end } });
}
}
},
[onTabsStateChange, setDateRange]
[setDateRange]
);

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,12 @@ import type { AssetDetailsProps } from '../types';
import { useMetadataStateProviderContext } from './use_metadata_state';

export interface UseAssetDetailsStateProps {
state: Pick<
AssetDetailsProps,
'asset' | 'assetType' | 'overrides' | 'onTabsStateChange' | 'renderMode'
>;
state: Pick<AssetDetailsProps, 'asset' | 'assetType' | 'overrides' | 'renderMode'>;
}

export function useAssetDetailsState({ state }: UseAssetDetailsStateProps) {
const { metadata } = useMetadataStateProviderContext();
const { asset, assetType, onTabsStateChange, overrides, renderMode } = state;
const { asset, assetType, overrides, renderMode } = state;

// When the asset asset.name is known we can load the page faster
// Otherwise we need to use metadata response.
Expand All @@ -30,7 +27,6 @@ export function useAssetDetailsState({ state }: UseAssetDetailsStateProps) {
name: asset.name || metadata?.name || 'asset-name',
},
assetType,
onTabsStateChange,
overrides,
renderMode,
loading,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,28 @@ 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 { FlyoutTabIds } from '../../../../components/asset_details/types';
import { useUrlState } from '../../../../utils/use_url_state';
import { FlyoutTabIds } from '../types';
import { useUrlState } from '../../../utils/use_url_state';

export const DEFAULT_STATE: HostFlyout = {
export const DEFAULT_STATE: AssetDetailsState = {
itemId: '',
tabId: FlyoutTabIds.OVERVIEW,
processSearch: undefined,
metadataSearch: undefined,
};
const HOST_FLYOUT_URL_STATE_KEY = 'flyout';
const ASSET_DETAILS_URL_STATE_KEY = 'asset_details';

type SetHostFlyoutState = (newProp: Payload | null) => void;
type SetAssetDetailsState = (newProp: Payload | null) => void;

export const useHostFlyoutUrlState = (): [HostFlyoutUrl, SetHostFlyoutState] => {
const [urlState, setUrlState] = useUrlState<HostFlyoutUrl>({
export const useAssetDetailsUrlState = (): [AssetDetailsUrl, SetAssetDetailsState] => {
const [urlState, setUrlState] = useUrlState<AssetDetailsUrl>({
defaultState: null,
decodeUrlState,
encodeUrlState,
urlStateKey: HOST_FLYOUT_URL_STATE_KEY,
urlStateKey: ASSET_DETAILS_URL_STATE_KEY,
});

const setHostFlyoutState = (newProps: Payload | null) => {
const setAssetDetailsState = (newProps: Payload | null) => {
if (!newProps) {
setUrlState(DEFAULT_STATE);
} else {
Expand All @@ -41,10 +41,10 @@ export const useHostFlyoutUrlState = (): [HostFlyoutUrl, SetHostFlyoutState] =>
}
};

return [urlState as HostFlyoutUrl, setHostFlyoutState];
return [urlState as AssetDetailsUrl, setAssetDetailsState];
};

const FlyoutTabIdRT = rt.union([
const TabIdRT = rt.union([
rt.literal(FlyoutTabIds.OVERVIEW),
rt.literal(FlyoutTabIds.METADATA),
rt.literal(FlyoutTabIds.PROCESSES),
Expand All @@ -53,10 +53,10 @@ const FlyoutTabIdRT = rt.union([
rt.literal(FlyoutTabIds.OSQUERY),
]);

const HostFlyoutStateRT = rt.intersection([
const AssetDetailsStateRT = rt.intersection([
rt.type({
itemId: rt.string,
tabId: FlyoutTabIdRT,
tabId: TabIdRT,
}),
rt.partial({
dateRange: rt.type({
Expand All @@ -69,14 +69,13 @@ const HostFlyoutStateRT = rt.intersection([
}),
]);

const HostFlyoutUrlRT = rt.union([HostFlyoutStateRT, rt.null]);
const AssetDetailsUrlRT = rt.union([AssetDetailsStateRT, rt.null]);

type HostFlyoutState = rt.TypeOf<typeof HostFlyoutStateRT>;
type HostFlyoutUrl = rt.TypeOf<typeof HostFlyoutUrlRT>;
type Payload = Partial<HostFlyoutState>;
export type HostFlyout = rt.TypeOf<typeof HostFlyoutStateRT>;
export type AssetDetailsState = rt.TypeOf<typeof AssetDetailsStateRT>;
type AssetDetailsUrl = rt.TypeOf<typeof AssetDetailsUrlRT>;
type Payload = Partial<AssetDetailsState>;

const encodeUrlState = HostFlyoutUrlRT.encode;
const encodeUrlState = AssetDetailsUrlRT.encode;
const decodeUrlState = (value: unknown) => {
return pipe(HostFlyoutUrlRT.decode(value), fold(constant(undefined), identity));
return pipe(AssetDetailsUrlRT.decode(value), fold(constant(undefined), identity));
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,31 @@

import type { TimeRange } from '@kbn/es-query';
import createContainer from 'constate';
import { useCallback, useMemo, useState } from 'react';
import { useCallback, useMemo } from 'react';
import { parseDateRange } from '../../../utils/datemath';

import { toTimestampRange } from '../utils';
import { useAssetDetailsUrlState } from './use_asset_details_url_state';

const DEFAULT_DATE_RANGE: TimeRange = {
from: 'now-15m',
to: 'now',
};

export interface UseAssetDetailsStateProps {
export interface UseDateRangeProviderProps {
initialDateRange: TimeRange;
}

export function useDateRangeProvider({ initialDateRange }: UseAssetDetailsStateProps) {
const [dateRange, setDateRange] = useState(initialDateRange);
export function useDateRangeProvider({ initialDateRange }: UseDateRangeProviderProps) {
const [urlState, setUrlState] = useAssetDetailsUrlState();
const dateRange: TimeRange = urlState?.dateRange || initialDateRange;

const setDateRange = useCallback(
(newDateRange: TimeRange) => {
setUrlState({ dateRange: newDateRange });
},
[setUrlState]
);

const parsedDateRange = useMemo(() => {
const { from = DEFAULT_DATE_RANGE.from, to = DEFAULT_DATE_RANGE.to } =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,31 @@
* 2.0.
*/

import { useState } from 'react';
import createContainer from 'constate';
import { useLazyRef } from '../../../hooks/use_lazy_ref';
import type { TabIds } from '../types';
import { useAssetDetailsStateContext } from './use_asset_details_state';
import { AssetDetailsState, useAssetDetailsUrlState } from './use_asset_details_url_state';

interface TabSwitcherParams {
initialActiveTabId?: TabIds;
defaultActiveTabId?: TabIds;
}

export function useTabSwitcher({ initialActiveTabId }: TabSwitcherParams) {
const { onTabsStateChange } = useAssetDetailsStateContext();
const [activeTabId, setActiveTabId] = useState<TabIds | undefined>(initialActiveTabId);
export function useTabSwitcher({ defaultActiveTabId }: TabSwitcherParams) {
const [urlState, setUrlState] = useAssetDetailsUrlState();
const activeTabId: TabIds | undefined = urlState?.tabId || defaultActiveTabId;

// This set keeps track of which tabs content have been rendered the first time.
// We need it in order to load a tab content only if it gets clicked, and then keep it in the DOM for performance improvement.
const renderedTabsSet = useLazyRef(() => new Set([initialActiveTabId]));
const renderedTabsSet = useLazyRef(() => new Set([activeTabId]));

const showTab = (tabId: TabIds) => {
renderedTabsSet.current.add(tabId); // On a tab click, mark the tab content as allowed to be rendered
setActiveTabId(tabId);
// On a tab click, mark the tab content as allowed to be rendered
renderedTabsSet.current.add(tabId);

if (onTabsStateChange) {
onTabsStateChange({ activeTabId: tabId });
}
setUrlState({ tabId: tabId as AssetDetailsState['tabId'] });
};

return {
initialActiveTabId,
activeTabId,
renderedTabsSet,
showTab,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { EuiButtonEmpty } from '@elastic/eui';
import { useLinkProps } from '@kbn/observability-shared-plugin/public';
import { getNodeDetailUrl } from '../../../pages/link_to';
import type { InventoryItemType } from '../../../../common/inventory_models/types';
import type { Asset } from '../types';

export interface LinkToNodeDetailsProps {
dateRangeTimestamp: { from: number; to: number };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,30 +19,29 @@ import { InfraLoadingPanel } from '../../../loading';
import { useAssetDetailsStateContext } from '../../hooks/use_asset_details_state';
import { useDataViewsProviderContext } from '../../hooks/use_data_views';
import { useDateRangeProviderContext } from '../../hooks/use_date_range';
import { useAssetDetailsUrlState } from '../../hooks/use_asset_details_url_state';

const TEXT_QUERY_THROTTLE_INTERVAL_MS = 500;

export const Logs = () => {
const { getDateRangeInTimestamp } = useDateRangeProviderContext();
const { asset, assetType, overrides, onTabsStateChange } = useAssetDetailsStateContext();
const [urlState, setUrlState] = useAssetDetailsUrlState();
const { asset, assetType } = useAssetDetailsStateContext();
const { logs } = useDataViewsProviderContext();

const { query: overrideQuery } = overrides?.logs ?? {};
const { loading: logViewLoading, reference: logViewReference } = logs ?? {};

const { services } = useKibanaContextForPlugin();
const { locators } = services;
const [textQuery, setTextQuery] = useState(overrideQuery ?? '');
const [textQueryDebounced, setTextQueryDebounced] = useState(overrideQuery ?? '');
const [textQuery, setTextQuery] = useState(urlState?.logsSearch ?? '');
const [textQueryDebounced, setTextQueryDebounced] = useState(urlState?.logsSearch ?? '');

const currentTimestamp = getDateRangeInTimestamp().to;
const startTimestamp = currentTimestamp - 60 * 60 * 1000; // 60 minutes

useDebounce(
() => {
if (onTabsStateChange) {
onTabsStateChange({ logs: { query: textQuery } });
}
setUrlState({ logsSearch: textQuery });
setTextQueryDebounced(textQuery);
},
TEXT_QUERY_THROTTLE_INTERVAL_MS,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import { i18n } from '@kbn/i18n';
import { EuiToolTip, EuiButtonIcon } from '@elastic/eui';
import { useMetricsDataViewContext } from '../../../../pages/metrics/hosts/hooks/use_data_view';
import { useKibanaContextForPlugin } from '../../../../hooks/use_kibana';
import { useUnifiedSearchContext } from '../../../../pages/metrics/hosts/hooks/use_unified_search';
import { buildMetadataFilter } from './build_metadata_filter';
import { useUnifiedSearchContext } from '../../../../pages/metrics/hosts/hooks/use_unified_search';

interface AddMetadataFilterButtonProps {
item: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,30 +13,30 @@ import { Table } from './table';
import { getAllFields } from './utils';
import { useMetadataStateProviderContext } from '../../hooks/use_metadata_state';
import { useAssetDetailsStateContext } from '../../hooks/use_asset_details_state';
import { useAssetDetailsUrlState } from '../../hooks/use_asset_details_url_state';

export interface MetadataSearchUrlState {
metadataSearchUrlState: string;
setMetadataSearchUrlState: (metadataSearch: { metadataSearch?: string }) => void;
}

export const Metadata = () => {
const { overrides, onTabsStateChange } = useAssetDetailsStateContext();
const [urlState, setUrlState] = useAssetDetailsUrlState();
const { overrides } = useAssetDetailsStateContext();
const {
metadata,
loading: metadataLoading,
error: fetchMetadataError,
} = useMetadataStateProviderContext();
const { query, showActionsColumn = false } = overrides?.metadata ?? {};
const { showActionsColumn = false } = overrides?.metadata ?? {};

const fields = useMemo(() => getAllFields(metadata), [metadata]);

const onSearchChange = useCallback(
(newQuery: string) => {
if (onTabsStateChange) {
onTabsStateChange({ metadata: { query: newQuery } });
}
setUrlState({ metadataSearch: newQuery });
},
[onTabsStateChange]
[setUrlState]
);

if (fetchMetadataError) {
Expand Down Expand Up @@ -71,7 +71,7 @@ export const Metadata = () => {

return (
<Table
search={query}
search={urlState?.metadataSearch}
onSearchChange={onSearchChange}
showActionsColumn={showActionsColumn}
rows={fields}
Expand Down
Loading

0 comments on commit a526204

Please sign in to comment.