diff --git a/x-pack/plugins/infra/public/components/asset_details/asset_details.tsx b/x-pack/plugins/infra/public/components/asset_details/asset_details.tsx index 95c5b4836a196..8b3a4b2c706d8 100644 --- a/x-pack/plugins/infra/public/components/asset_details/asset_details.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/asset_details.tsx @@ -28,15 +28,12 @@ export const AssetDetails = ({ tabs, links, renderMode, - activeTabId, metricAlias, ...props }: AssetDetailsProps) => { return ( - 0 ? activeTabId ?? tabs[0].id : undefined} - > + diff --git a/x-pack/plugins/infra/public/components/asset_details/asset_details_embeddable.tsx b/x-pack/plugins/infra/public/components/asset_details/asset_details_embeddable.tsx index 3aff0ea4b2548..45bfb7d46a59f 100644 --- a/x-pack/plugins/infra/public/components/asset_details/asset_details_embeddable.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/asset_details_embeddable.tsx @@ -71,14 +71,12 @@ export class AssetDetailsEmbeddable extends Embeddable
diff --git a/x-pack/plugins/infra/public/components/asset_details/context_providers.tsx b/x-pack/plugins/infra/public/components/asset_details/context_providers.tsx index 6af455dff2b02..3561e2e604774 100644 --- a/x-pack/plugins/infra/public/components/asset_details/context_providers.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/context_providers.tsx @@ -17,7 +17,7 @@ export const ContextProviders = ({ }: { props: Omit } & { children: React.ReactNode; }) => { - const { asset, dateRange, overrides, onTabsStateChange, assetType = 'host', renderMode } = props; + const { asset, dateRange, overrides, assetType = 'host', renderMode } = props; return ( @@ -26,7 +26,6 @@ export const ContextProviders = ({ asset, assetType, overrides, - onTabsStateChange, renderMode, }} > diff --git a/x-pack/plugins/infra/public/components/asset_details/date_picker/date_picker.tsx b/x-pack/plugins/infra/public/components/asset_details/date_picker/date_picker.tsx index b4ec1128494d8..c7a90a3601c6c 100644 --- a/x-pack/plugins/infra/public/components/asset_details/date_picker/date_picker.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/date_picker/date_picker.tsx @@ -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 ( diff --git a/x-pack/plugins/infra/public/components/asset_details/hooks/use_asset_details_state.ts b/x-pack/plugins/infra/public/components/asset_details/hooks/use_asset_details_state.ts index 53bb74beef943..fced15b67f2e6 100644 --- a/x-pack/plugins/infra/public/components/asset_details/hooks/use_asset_details_state.ts +++ b/x-pack/plugins/infra/public/components/asset_details/hooks/use_asset_details_state.ts @@ -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; } 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. @@ -30,7 +27,6 @@ export function useAssetDetailsState({ state }: UseAssetDetailsStateProps) { name: asset.name || metadata?.name || 'asset-name', }, assetType, - onTabsStateChange, overrides, renderMode, loading, diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_host_flyout_url_state.ts b/x-pack/plugins/infra/public/components/asset_details/hooks/use_asset_details_url_state.ts similarity index 55% rename from x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_host_flyout_url_state.ts rename to x-pack/plugins/infra/public/components/asset_details/hooks/use_asset_details_url_state.ts index 62d8fdb302d3e..497bbf3a91278 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_host_flyout_url_state.ts +++ b/x-pack/plugins/infra/public/components/asset_details/hooks/use_asset_details_url_state.ts @@ -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({ +export const useAssetDetailsUrlState = (): [AssetDetailsUrl, SetAssetDetailsState] => { + const [urlState, setUrlState] = useUrlState({ 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 { @@ -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), @@ -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({ @@ -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; -type HostFlyoutUrl = rt.TypeOf; -type Payload = Partial; -export type HostFlyout = rt.TypeOf; +export type AssetDetailsState = rt.TypeOf; +type AssetDetailsUrl = rt.TypeOf; +type Payload = Partial; -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)); }; diff --git a/x-pack/plugins/infra/public/components/asset_details/hooks/use_date_range.ts b/x-pack/plugins/infra/public/components/asset_details/hooks/use_date_range.ts index 8253b7e8b5685..4f07b0231f4bb 100644 --- a/x-pack/plugins/infra/public/components/asset_details/hooks/use_date_range.ts +++ b/x-pack/plugins/infra/public/components/asset_details/hooks/use_date_range.ts @@ -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 } = diff --git a/x-pack/plugins/infra/public/components/asset_details/hooks/use_tab_switcher.tsx b/x-pack/plugins/infra/public/components/asset_details/hooks/use_tab_switcher.tsx index 6bdcbca214d37..5de6383099e1d 100644 --- a/x-pack/plugins/infra/public/components/asset_details/hooks/use_tab_switcher.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/hooks/use_tab_switcher.tsx @@ -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(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, diff --git a/x-pack/plugins/infra/public/components/asset_details/links/link_to_node_details.tsx b/x-pack/plugins/infra/public/components/asset_details/links/link_to_node_details.tsx index d834a6ca9e0d7..4b0e8a52d400c 100644 --- a/x-pack/plugins/infra/public/components/asset_details/links/link_to_node_details.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/links/link_to_node_details.tsx @@ -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 }; diff --git a/x-pack/plugins/infra/public/components/asset_details/tabs/logs/logs.tsx b/x-pack/plugins/infra/public/components/asset_details/tabs/logs/logs.tsx index 68f0ea074f292..b96162ef5142b 100644 --- a/x-pack/plugins/infra/public/components/asset_details/tabs/logs/logs.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/tabs/logs/logs.tsx @@ -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, diff --git a/x-pack/plugins/infra/public/components/asset_details/tabs/metadata/add_metadata_filter_button.tsx b/x-pack/plugins/infra/public/components/asset_details/tabs/metadata/add_metadata_filter_button.tsx index a2d04b1c36184..49d912fab3f50 100644 --- a/x-pack/plugins/infra/public/components/asset_details/tabs/metadata/add_metadata_filter_button.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/tabs/metadata/add_metadata_filter_button.tsx @@ -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: { diff --git a/x-pack/plugins/infra/public/components/asset_details/tabs/metadata/metadata.tsx b/x-pack/plugins/infra/public/components/asset_details/tabs/metadata/metadata.tsx index f3b27b9a8caca..0622dcfc63204 100644 --- a/x-pack/plugins/infra/public/components/asset_details/tabs/metadata/metadata.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/tabs/metadata/metadata.tsx @@ -13,6 +13,7 @@ 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; @@ -20,23 +21,22 @@ export interface MetadataSearchUrlState { } 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) { @@ -71,7 +71,7 @@ export const Metadata = () => { return ( ({ value, @@ -39,11 +40,10 @@ const options = Object.entries(STATE_NAMES).map(([value, view]: [string, string] export const Processes = () => { const { getDateRangeInTimestamp } = useDateRangeProviderContext(); - const { asset, assetType, overrides, onTabsStateChange } = useAssetDetailsStateContext(); + const [urlState, setUrlState] = useAssetDetailsUrlState(); + const { asset, assetType } = useAssetDetailsStateContext(); - const { query: overrideQuery } = overrides?.processes ?? {}; - - const [searchText, setSearchText] = useState(overrideQuery ?? ''); + const [searchText, setSearchText] = useState(urlState?.processSearch ?? ''); const [searchBarState, setSearchBarState] = useState(() => searchText ? Query.parse(searchText) : Query.MATCH_ALL ); @@ -69,12 +69,10 @@ export const Processes = () => { const debouncedSearchOnChange = useMemo(() => { return debounce<(queryText: string) => void>((queryText) => { - if (onTabsStateChange) { - onTabsStateChange({ processes: { query: queryText } }); - } + setUrlState({ processSearch: queryText }); setSearchText(queryText); }, 500); - }, [onTabsStateChange]); + }, [setUrlState]); const searchBarOnChange = useCallback( ({ query, queryText }) => { @@ -86,11 +84,9 @@ export const Processes = () => { const clearSearchBar = useCallback(() => { setSearchBarState(Query.MATCH_ALL); - if (onTabsStateChange) { - onTabsStateChange({ processes: { query: '' } }); - } + setUrlState({ processSearch: '' }); setSearchText(''); - }, [onTabsStateChange]); + }, [setUrlState]); return ( @@ -139,6 +135,7 @@ export const Processes = () => { query={searchBarState} onChange={searchBarOnChange} box={{ + 'data-test-subj': 'infraAssetDetailsProcessesSearchBarInput', incremental: true, placeholder: i18n.translate('xpack.infra.metrics.nodeDetails.searchForProcesses', { defaultMessage: 'Search for processes…', diff --git a/x-pack/plugins/infra/public/components/asset_details/template/flyout.tsx b/x-pack/plugins/infra/public/components/asset_details/template/flyout.tsx index 9bc9a60777fad..3673173c5af89 100644 --- a/x-pack/plugins/infra/public/components/asset_details/template/flyout.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/template/flyout.tsx @@ -25,7 +25,7 @@ export const Flyout = ({ }: ContentTemplateProps & { closeFlyout: () => void }) => { const { asset, assetType, loading } = useAssetDetailsStateContext(); const { rightSideItems, tabEntries } = usePageHeader(tabs, links); - const { initialActiveTabId } = useTabSwitcherContext(); + const { activeTabId } = useTabSwitcherContext(); const { services: { telemetry }, } = useKibanaContextForPlugin(); @@ -34,7 +34,7 @@ export const Flyout = ({ telemetry.reportAssetDetailsFlyoutViewed({ componentName: ASSET_DETAILS_FLYOUT_COMPONENT_NAME, assetType, - tabId: initialActiveTabId, + tabId: activeTabId, }); }); diff --git a/x-pack/plugins/infra/public/components/asset_details/types.ts b/x-pack/plugins/infra/public/components/asset_details/types.ts index 839154a4b9d33..dfa6155b0adae 100644 --- a/x-pack/plugins/infra/public/components/asset_details/types.ts +++ b/x-pack/plugins/infra/public/components/asset_details/types.ts @@ -28,21 +28,14 @@ export type TabIds = `${FlyoutTabIds}`; export interface OverridableTabState { metadata?: { - query?: string; showActionsColumn?: boolean; }; - processes?: { - query?: string; - }; anomalies?: { onClose?: () => void; }; alertRule?: { onCreateRuleClick?: () => void; }; - logs?: { - query?: string; - }; } export interface TabState extends OverridableTabState { @@ -72,10 +65,8 @@ export interface AssetDetailsProps { assetType: InventoryItemType; dateRange: TimeRange; tabs: Tab[]; - activeTabId?: TabIds; overrides?: OverridableTabState; renderMode: RenderMode; - onTabsStateChange?: TabsStateChangeFn; links?: LinkOptions[]; // This is temporary. Once we start using the asset details in other plugins, // It will have to retrieve the metricAlias internally rather than receive it via props diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/flyout_wrapper.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/flyout_wrapper.tsx index ee4115a2ed981..065b6739c41fc 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/flyout_wrapper.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/flyout_wrapper.tsx @@ -10,9 +10,9 @@ import React from 'react'; import { useSourceContext } from '../../../../../containers/metrics_source'; import { useUnifiedSearchContext } from '../../hooks/use_unified_search'; import type { HostNodeRow } from '../../hooks/use_hosts_table'; -import { HostFlyout, useHostFlyoutUrlState } from '../../hooks/use_host_flyout_url_state'; import { AssetDetails } from '../../../../../components/asset_details/asset_details'; import { orderedFlyoutTabs } from './tabs'; +import { useAssetDetailsUrlState } from '../../../../../components/asset_details/hooks/use_asset_details_url_state'; export interface Props { node: HostNodeRow; @@ -22,35 +22,18 @@ export interface Props { export const FlyoutWrapper = ({ node: { name }, closeFlyout }: Props) => { const { source } = useSourceContext(); const { parsedDateRange } = useUnifiedSearchContext(); - const [hostFlyoutState, setHostFlyoutState] = useHostFlyoutUrlState(); + const [urlState] = useAssetDetailsUrlState(); return source ? ( - setHostFlyoutState({ - dateRange: state.dateRange, - metadataSearch: state.metadata?.query, - processSearch: state.processes?.query, - logsSearch: state.logs?.query, - tabId: state.activeTabId as HostFlyout['tabId'], - }) - } tabs={orderedFlyoutTabs} links={['apmServices', 'nodeDetails']} renderMode={{ diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx index 22677d692fdb9..fd6b4e825e676 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx @@ -24,15 +24,15 @@ import type { InfraAssetMetricsItem, InfraAssetMetricType, } from '../../../../../common/http_api'; -import { useHostFlyoutUrlState } from './use_host_flyout_url_state'; +import { useAssetDetailsUrlState } from '../../../../components/asset_details/hooks/use_asset_details_url_state'; import { Sorting, useHostsTableUrlState } from './use_hosts_table_url_state'; import { useHostsViewContext } from './use_hosts_view'; -import { useUnifiedSearchContext } from './use_unified_search'; import { useMetricsDataViewContext } from './use_data_view'; import { ColumnHeader } from '../components/table/column_header'; import { TABLE_COLUMN_LABEL } from '../translations'; import { METRICS_TOOLTIP } from '../../../../common/visualizations'; import { buildCombinedHostsFilter } from '../../../../utils/filters/build'; +import { useUnifiedSearchContext } from './use_unified_search'; /** * Columns and items types @@ -140,7 +140,7 @@ export const useHostsTable = () => { } = useKibanaContextForPlugin(); const { dataView } = useMetricsDataViewContext(); - const [hostFlyoutState, setHostFlyoutState] = useHostFlyoutUrlState(); + const [hostFlyoutState, setHostFlyoutState] = useAssetDetailsUrlState(); const popoverContainerRef = useRef(null); const tableRef = useRef(null); diff --git a/x-pack/plugins/infra/public/pages/metrics/metric_detail/asset_detail_page.tsx b/x-pack/plugins/infra/public/pages/metrics/metric_detail/asset_detail_page.tsx index 31559352a67de..2b31047af4ef6 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metric_detail/asset_detail_page.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/metric_detail/asset_detail_page.tsx @@ -5,19 +5,19 @@ * 2.0. */ -import type { TimeRange } from '@kbn/es-query'; -import React, { useCallback, useMemo } from 'react'; +import React, { useMemo } from 'react'; import { useLocation, useRouteMatch } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; +import { TimeRange } from '@kbn/es-query'; import { NoRemoteCluster } from '../../../components/empty_states'; import { SourceErrorPage } from '../../../components/source_error_page'; import { SourceLoadingPage } from '../../../components/source_loading_page'; import { useSourceContext } from '../../../containers/metrics_source'; -import { FlyoutTabIds, type Tab, type TabState } from '../../../components/asset_details/types'; +import { FlyoutTabIds, type Tab } from '../../../components/asset_details/types'; import type { InventoryItemType } from '../../../../common/inventory_models/types'; import { AssetDetails } from '../../../components/asset_details/asset_details'; -import { useMetricsTimeContext } from './hooks/use_metrics_time'; import { MetricsPageTemplate } from '../page_template'; +import { useMetricsTimeContext } from './hooks/use_metrics_time'; const orderedFlyoutTabs: Tab[] = [ { @@ -70,7 +70,7 @@ export const AssetDetailPage = () => { return queryParams.get('assetName') ?? undefined; }, [search]); - const { timeRange, setTimeRange } = useMetricsTimeContext(); + const { timeRange } = useMetricsTimeContext(); const dateRange: TimeRange = useMemo( () => ({ @@ -83,23 +83,6 @@ export const AssetDetailPage = () => { [timeRange.from, timeRange.to] ); - // Retrocompatibility - const handleTabStateChange = useCallback( - ({ dateRange: newDateRange }: TabState) => { - if (newDateRange) { - setTimeRange( - { - from: newDateRange.from, - to: newDateRange.to, - interval: timeRange.interval, - }, - false - ); - } - }, - [setTimeRange, timeRange.interval] - ); - const { metricIndicesExist, remoteClustersExist } = source?.status ?? {}; if (isLoading || !source) return ; @@ -131,8 +114,6 @@ export const AssetDetailPage = () => { }} assetType={nodeType} dateRange={dateRange} - onTabsStateChange={handleTabStateChange} - activeTabId={FlyoutTabIds.OVERVIEW} tabs={orderedFlyoutTabs} links={['apmServices']} renderMode={{ diff --git a/x-pack/test/functional/apps/infra/hosts_view.ts b/x-pack/test/functional/apps/infra/hosts_view.ts index 91270e7ebde58..5eb46a5e0b168 100644 --- a/x-pack/test/functional/apps/infra/hosts_view.ts +++ b/x-pack/test/functional/apps/infra/hosts_view.ts @@ -168,6 +168,16 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { (await pageObjects.infraHostsView.isKPIChartsLoaded()) ); + const refreshPageWithDelay = async () => { + /** + * Delay gives React a chance to finish + * running effects (like updating the URL) before + * refreshing the page. + */ + await pageObjects.common.sleep(1000); + await browser.refresh(); + }; + describe('Hosts View', function () { before(async () => { await Promise.all([ @@ -284,6 +294,16 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); }); + it('preserves selected tab between page reloads', async () => { + await testSubjects.missingOrFail('infraAssetDetailsMetadataTable'); + await pageObjects.assetDetails.clickMetadataTab(); + await pageObjects.assetDetails.metadataTableExists(); + + await refreshPageWithDelay(); + + await pageObjects.assetDetails.metadataTableExists(); + }); + describe('Overview Tab', () => { before(async () => { await pageObjects.assetDetails.clickOverviewTab(); @@ -342,6 +362,17 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await pageObjects.assetDetails.metadataRemovePinExists(); expect(removeFilterShouldNotExist).to.be(false); }); + + it('preserves search term between page reloads', async () => { + const searchInput = await pageObjects.assetDetails.getMetadataSearchField(); + + expect(await searchInput.getAttribute('value')).to.be(''); + + await searchInput.type('test'); + await refreshPageWithDelay(); + + expect(await searchInput.getAttribute('value')).to.be('test'); + }); }); describe('Processes Tab', () => { @@ -352,6 +383,17 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { it('should show processes table', async () => { await pageObjects.assetDetails.processesTableExists(); }); + + it('preserves search term between page reloads', async () => { + const searchInput = await pageObjects.assetDetails.getProcessesSearchField(); + + expect(await searchInput.getAttribute('value')).to.be(''); + + await searchInput.type('test'); + await refreshPageWithDelay(); + + expect(await searchInput.getAttribute('value')).to.be('test'); + }); }); describe('Logs Tab', () => { @@ -362,6 +404,17 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { it('should render logs tab', async () => { await pageObjects.assetDetails.logsExists(); }); + + it('preserves search term between page reloads', async () => { + const searchInput = await pageObjects.assetDetails.getLogsSearchField(); + + expect(await searchInput.getAttribute('value')).to.be(''); + + await searchInput.type('test'); + await refreshPageWithDelay(); + + expect(await searchInput.getAttribute('value')).to.be('test'); + }); }); describe('Flyout links', () => { @@ -459,6 +512,37 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); }); + describe('Host details page navigation', () => { + after(async () => { + await pageObjects.common.navigateToApp(HOSTS_VIEW_PATH); + await pageObjects.header.waitUntilLoadingHasFinished(); + await pageObjects.timePicker.setAbsoluteRange( + START_DATE.format(DATE_PICKER_FORMAT), + END_DATE.format(DATE_PICKER_FORMAT) + ); + + await waitForPageToLoad(); + }); + + it('maintains selected date range when navigating to the individual host details', async () => { + const start = START_HOST_PROCESSES_DATE.format(DATE_PICKER_FORMAT); + const end = END_HOST_PROCESSES_DATE.format(DATE_PICKER_FORMAT); + + await pageObjects.timePicker.setAbsoluteRange(start, end); + + const hostDetailLinks = await pageObjects.infraHostsView.getAllHostDetailLinks(); + expect(hostDetailLinks.length).not.to.equal(0); + + await hostDetailLinks[0].click(); + + expect(await pageObjects.timePicker.timePickerExists()).to.be(true); + + const datePickerValue = await pageObjects.timePicker.getTimeConfig(); + expect(datePickerValue.start).to.equal(start); + expect(datePickerValue.end).to.equal(end); + }); + }); + describe('KPIs', () => { [ { metric: 'hostsCount', value: '6' }, diff --git a/x-pack/test/functional/apps/infra/node_details.ts b/x-pack/test/functional/apps/infra/node_details.ts index 7e2bdba6ecf39..caa0ceae0538a 100644 --- a/x-pack/test/functional/apps/infra/node_details.ts +++ b/x-pack/test/functional/apps/infra/node_details.ts @@ -51,6 +51,16 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { ); }; + const refreshPageWithDelay = async () => { + /** + * Delay gives React a chance to finish + * running effects (like updating the URL) before + * refreshing the page. + */ + await pageObjects.common.sleep(1000); + await browser.refresh(); + }; + describe('Node Details', () => { describe('#With Asset Details', () => { before(async () => { @@ -111,6 +121,19 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); } ); + + it('preserves selected date range between page reloads', async () => { + const start = moment.utc('2023-08-28T10:20:00.000Z').format(DATE_PICKER_FORMAT); + const end = moment.utc('2023-08-28T21:00:00.000Z').format(DATE_PICKER_FORMAT); + + await pageObjects.timePicker.setAbsoluteRange(start, end); + await refreshPageWithDelay(); + + const datePickerValue = await pageObjects.timePicker.getTimeConfig(); + + expect(datePickerValue.start).to.equal(start); + expect(datePickerValue.end).to.equal(end); + }); }); describe('#Asset Type: host', () => { @@ -121,6 +144,16 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { ); }); + it('preserves selected tab between page reloads', async () => { + await testSubjects.missingOrFail('infraAssetDetailsMetadataTable'); + await pageObjects.assetDetails.clickMetadataTab(); + await pageObjects.assetDetails.metadataTableExists(); + + await refreshPageWithDelay(); + + await pageObjects.assetDetails.metadataTableExists(); + }); + describe('Overview Tab', () => { before(async () => { await pageObjects.assetDetails.clickOverviewTab(); diff --git a/x-pack/test/functional/page_objects/asset_details.ts b/x-pack/test/functional/page_objects/asset_details.ts index bcb8738b997bf..c7600d6f1b5f4 100644 --- a/x-pack/test/functional/page_objects/asset_details.ts +++ b/x-pack/test/functional/page_objects/asset_details.ts @@ -77,6 +77,10 @@ export function AssetDetailsProvider({ getService }: FtrProviderContext) { return testSubjects.existOrFail('infraAssetDetailsMetadataTable'); }, + async metadataTableMissing() { + return await testSubjects.missingOrFail('infraAssetDetailsMetadataTable'); + }, + async metadataRemovePinExists() { return testSubjects.exists('infraAssetDetailsMetadataRemovePin'); }, @@ -92,6 +96,10 @@ export function AssetDetailsProvider({ getService }: FtrProviderContext) { return testSubjects.exists('infraAssetDetailsMetadataRemoveFilterButton'); }, + async getMetadataSearchField() { + return await testSubjects.find('infraAssetDetailsMetadataSearchBarInput'); + }, + // Processes async clickProcessesTab() { return testSubjects.click('infraAssetDetailsProcessesTab'); @@ -124,6 +132,10 @@ export function AssetDetailsProvider({ getService }: FtrProviderContext) { return testSubjects.click('infraProcessRowButton'); }, + async getProcessesSearchField() { + return await testSubjects.find('infraAssetDetailsProcessesSearchBarInput'); + }, + // Logs async clickLogsTab() { return testSubjects.click('infraAssetDetailsLogsTab'); @@ -133,6 +145,10 @@ export function AssetDetailsProvider({ getService }: FtrProviderContext) { await testSubjects.existOrFail('infraAssetDetailsLogsTabContent'); }, + async getLogsSearchField() { + return await testSubjects.find('infraAssetDetailsLogsTabFieldSearch'); + }, + // Anomalies async clickAnomaliesTab() { return testSubjects.click('infraAssetDetailsAnomaliesTab'); diff --git a/x-pack/test/functional/page_objects/infra_hosts_view.ts b/x-pack/test/functional/page_objects/infra_hosts_view.ts index e449fb8f05b57..1cd0cf15996ec 100644 --- a/x-pack/test/functional/page_objects/infra_hosts_view.ts +++ b/x-pack/test/functional/page_objects/infra_hosts_view.ts @@ -110,6 +110,10 @@ export function InfraHostsViewProvider({ getService }: FtrProviderContext) { return testSubjects.find('hostsView-metricChart'); }, + async getAllHostDetailLinks() { + return testSubjects.findAll('hostsViewTableEntryTitleLink'); + }, + // Metrics Tab async getMetricsTab() { return testSubjects.find('hostsView-tabs-metrics');