Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Infra UI] Propagate flyout state to node details page and some fixes #165956

Original file line number Diff line number Diff line change
Expand Up @@ -6,42 +6,42 @@
*/

import { i18n } from '@kbn/i18n';
import { type AssetDetailsProps, FlyoutTabIds, type Tab } from '../../../types';
import { type AssetDetailsProps, ContentTabIds, type Tab } from '../../../types';

const links: AssetDetailsProps['links'] = ['alertRule', 'nodeDetails', 'apmServices'];
const tabs: Tab[] = [
{
id: FlyoutTabIds.OVERVIEW,
id: ContentTabIds.OVERVIEW,
name: i18n.translate('xpack.infra.nodeDetails.tabs.overview.title', {
defaultMessage: 'Overview',
}),
},
{
id: FlyoutTabIds.LOGS,
id: ContentTabIds.LOGS,
name: i18n.translate('xpack.infra.nodeDetails.tabs.logs', {
defaultMessage: 'Logs',
}),
},
{
id: FlyoutTabIds.METADATA,
id: ContentTabIds.METADATA,
name: i18n.translate('xpack.infra.metrics.nodeDetails.tabs.metadata', {
defaultMessage: 'Metadata',
}),
},
{
id: FlyoutTabIds.PROCESSES,
id: ContentTabIds.PROCESSES,
name: i18n.translate('xpack.infra.metrics.nodeDetails.tabs.processes', {
defaultMessage: 'Processes',
}),
},
{
id: FlyoutTabIds.ANOMALIES,
id: ContentTabIds.ANOMALIES,
name: i18n.translate('xpack.infra.nodeDetails.tabs.anomalies', {
defaultMessage: 'Anomalies',
}),
},
{
id: FlyoutTabIds.LINK_TO_APM,
id: ContentTabIds.LINK_TO_APM,
name: i18n.translate('xpack.infra.infra.nodeDetails.apmTabLabel', {
defaultMessage: 'APM',
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@
export const ASSET_DETAILS_FLYOUT_COMPONENT_NAME = 'infraAssetDetailsFlyout';
export const METRIC_CHART_HEIGHT = 300;
export const APM_HOST_FILTER_FIELD = 'host.hostname';

export const ASSET_DETAILS_URL_STATE_KEY = 'assetDetails';
Original file line number Diff line number Diff line change
Expand Up @@ -10,51 +10,51 @@ import React from 'react';
import { DatePicker } from '../date_picker/date_picker';
import { useTabSwitcherContext } from '../hooks/use_tab_switcher';
import { Anomalies, Metadata, Processes, Osquery, Logs, Overview } from '../tabs';
import { FlyoutTabIds } from '../types';
import { ContentTabIds } from '../types';

export const Content = () => {
return (
<EuiFlexGroup direction="column">
<EuiFlexItem grow={false}>
<DatePickerWrapper
visibleFor={[
FlyoutTabIds.OVERVIEW,
FlyoutTabIds.LOGS,
FlyoutTabIds.METADATA,
FlyoutTabIds.PROCESSES,
FlyoutTabIds.ANOMALIES,
ContentTabIds.OVERVIEW,
ContentTabIds.LOGS,
ContentTabIds.METADATA,
ContentTabIds.PROCESSES,
ContentTabIds.ANOMALIES,
]}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<TabPanel activeWhen={FlyoutTabIds.ANOMALIES}>
<TabPanel activeWhen={ContentTabIds.ANOMALIES}>
<Anomalies />
</TabPanel>
<TabPanel activeWhen={FlyoutTabIds.OVERVIEW}>
<TabPanel activeWhen={ContentTabIds.OVERVIEW}>
<Overview />
</TabPanel>
<TabPanel activeWhen={FlyoutTabIds.LOGS}>
<TabPanel activeWhen={ContentTabIds.LOGS}>
<Logs />
</TabPanel>
<TabPanel activeWhen={FlyoutTabIds.METADATA}>
<TabPanel activeWhen={ContentTabIds.METADATA}>
<Metadata />
</TabPanel>
<TabPanel activeWhen={FlyoutTabIds.OSQUERY}>
<TabPanel activeWhen={ContentTabIds.OSQUERY}>
<Osquery />
</TabPanel>
<TabPanel activeWhen={FlyoutTabIds.PROCESSES}>
<TabPanel activeWhen={ContentTabIds.PROCESSES}>
<Processes />
</TabPanel>
</EuiFlexItem>
</EuiFlexGroup>
);
};

const DatePickerWrapper = ({ visibleFor }: { visibleFor: FlyoutTabIds[] }) => {
const DatePickerWrapper = ({ visibleFor }: { visibleFor: ContentTabIds[] }) => {
const { activeTabId } = useTabSwitcherContext();

return (
<div hidden={!visibleFor.includes(activeTabId as FlyoutTabIds)}>
<div hidden={!visibleFor.includes(activeTabId as ContentTabIds)}>
<DatePicker />
</div>
);
Expand All @@ -64,7 +64,7 @@ const TabPanel = ({
activeWhen,
children,
}: {
activeWhen: FlyoutTabIds;
activeWhen: ContentTabIds;
children: React.ReactNode;
}) => {
const { renderedTabsSet, activeTabId } = useTabSwitcherContext();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,26 @@

import createContainer from 'constate';
import type { AssetDetailsProps } from '../types';
import { useAssetDetailsUrlState } from './use_asset_details_url_state';
import { useMetadataStateProviderContext } from './use_metadata_state';

export interface UseAssetDetailsRenderProps {
props: Pick<AssetDetailsProps, 'asset' | 'assetType' | 'overrides' | 'renderMode'>;
}

export function useAssetDetailsRenderProps({ props }: UseAssetDetailsRenderProps) {
const [urlState] = useAssetDetailsUrlState();
const { metadata } = useMetadataStateProviderContext();
const { asset, assetType, overrides, renderMode } = props;

// When the asset asset.name is known we can load the page faster
// Otherwise we need to use metadata response.
const loading = !asset.name && !metadata?.name;
const loading = !urlState?.name && !asset.name && !metadata?.name;

return {
asset: {
...asset,
name: asset.name || metadata?.name || 'asset-name',
name: urlState?.name || asset.name || metadata?.name || '',
},
assetType,
overrides,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ 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 '../types';
import { ContentTabIds } from '../types';
import { useUrlState } from '../../../utils/use_url_state';
import { ASSET_DETAILS_URL_STATE_KEY } from '../constants';
import { getDefaultDateRange } from '../utils';

export const DEFAULT_STATE: AssetDetailsState = {
tabId: FlyoutTabIds.OVERVIEW,
processSearch: undefined,
metadataSearch: undefined,
export const DEFAULT_STATE: AssetDetailsUrlState = {
tabId: ContentTabIds.OVERVIEW,
dateRange: getDefaultDateRange(),
};
const ASSET_DETAILS_URL_STATE_KEY = 'asset_details';

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

Expand All @@ -44,34 +44,35 @@ export const useAssetDetailsUrlState = (): [AssetDetailsUrl, SetAssetDetailsStat
};

const TabIdRT = rt.union([
rt.literal(FlyoutTabIds.OVERVIEW),
rt.literal(FlyoutTabIds.METADATA),
rt.literal(FlyoutTabIds.PROCESSES),
rt.literal(FlyoutTabIds.LOGS),
rt.literal(FlyoutTabIds.ANOMALIES),
rt.literal(FlyoutTabIds.OSQUERY),
rt.literal(ContentTabIds.OVERVIEW),
rt.literal(ContentTabIds.METADATA),
rt.literal(ContentTabIds.PROCESSES),
rt.literal(ContentTabIds.LOGS),
rt.literal(ContentTabIds.ANOMALIES),
rt.literal(ContentTabIds.OSQUERY),
]);

const AssetDetailsStateRT = rt.intersection([
const AssetDetailsUrlStateRT = rt.intersection([
rt.type({
tabId: TabIdRT,
}),
rt.partial({
dateRange: rt.type({
from: rt.string,
to: rt.string,
}),
}),
rt.partial({
tabId: TabIdRT,
name: rt.string,
processSearch: rt.string,
metadataSearch: rt.string,
logsSearch: rt.string,
}),
]);

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

export type AssetDetailsState = rt.TypeOf<typeof AssetDetailsStateRT>;
export type AssetDetailsUrlState = rt.TypeOf<typeof AssetDetailsUrlStateRT>;
type AssetDetailsUrl = rt.TypeOf<typeof AssetDetailsUrlRT>;
type Payload = Partial<AssetDetailsState>;
type Payload = Partial<AssetDetailsUrlState>;

const encodeUrlState = AssetDetailsUrlRT.encode;
const decodeUrlState = (value: unknown) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,16 @@ import createContainer from 'constate';
import { useCallback, useState } from 'react';
import useEffectOnce from 'react-use/lib/useEffectOnce';
import { parseDateRange } from '../../../utils/datemath';
import { toTimestampRange } from '../utils';
import { getDefaultDateRange, toTimestampRange } from '../utils';
import { useAssetDetailsUrlState } from './use_asset_details_url_state';

export interface UseDateRangeProviderProps {
initialDateRange: TimeRange;
}

const DEFAULT_FROM_IN_MILLISECONDS = 15 * 60000;
const getDefaultDateRange = () => {
const now = Date.now();

return {
from: new Date(now - DEFAULT_FROM_IN_MILLISECONDS).toISOString(),
to: new Date(now).toISOString(),
};
};

export function useDateRangeProvider({ initialDateRange }: UseDateRangeProviderProps) {
export function useDateRangeProvider({
initialDateRange = getDefaultDateRange(),
}: UseDateRangeProviderProps) {
const [urlState, setUrlState] = useAssetDetailsUrlState();
const dateRange: TimeRange = urlState?.dateRange ?? initialDateRange;
const [parsedDateRange, setParsedDateRange] = useState(parseDateRange(dateRange));
Expand Down Expand Up @@ -56,9 +48,10 @@ export function useDateRangeProvider({ initialDateRange }: UseDateRangeProviderP
return { from, to };
}, [parsedDateRange]);

const getDateRangeInTimestamp = useCallback(() => {
return toTimestampRange(getParsedDateRange());
}, [getParsedDateRange]);
const getDateRangeInTimestamp = useCallback(
() => toTimestampRange(getParsedDateRange()),
[getParsedDateRange]
);

return {
dateRange,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,19 @@
* 2.0.
*/

import { useEffect } from 'react';
import createContainer from 'constate';
import { findInventoryModel } from '../../../../common/inventory_models';
import { useSourceContext } from '../../../containers/metrics_source';
import { useMetadata } from './use_metadata';
import { AssetDetailsProps } from '../types';
import { useDateRangeProviderContext } from './use_date_range';
import { useAssetDetailsUrlState } from './use_asset_details_url_state';

export type UseMetadataProviderProps = Pick<AssetDetailsProps, 'asset' | 'assetType'>;

export function useMetadataProvider({ asset, assetType }: UseMetadataProviderProps) {
const [, setUrlState] = useAssetDetailsUrlState();
const { getDateRangeInTimestamp } = useDateRangeProviderContext();
const inventoryModel = findInventoryModel(assetType);
const { sourceId } = useSourceContext();
Expand All @@ -27,6 +30,12 @@ export function useMetadataProvider({ asset, assetType }: UseMetadataProviderPro
getDateRangeInTimestamp()
);

useEffect(() => {
if (metadata?.name) {
setUrlState({ name: metadata.name });
}
}, [metadata?.name, setUrlState]);

return {
loading,
error,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,8 @@ import { FormattedMessage } from '@kbn/i18n-react';
import { useKibanaContextForPlugin } from '../../../hooks/use_kibana';
import { APM_HOST_FILTER_FIELD } from '../constants';
import { LinkToAlertsRule, LinkToApmServices, LinkToNodeDetails } from '../links';
import { FlyoutTabIds, type RouteState, type LinkOptions, type Tab, type TabIds } from '../types';
import { ContentTabIds, type RouteState, type LinkOptions, type Tab, type TabIds } from '../types';
import { useAssetDetailsRenderPropsContext } from './use_asset_details_render_props';
import { useDateRangeProviderContext } from './use_date_range';
import { useTabSwitcherContext } from './use_tab_switcher';

type TabItem = NonNullable<Pick<EuiPageHeaderProps, 'tabs'>['tabs']>[number];
Expand Down Expand Up @@ -89,22 +88,15 @@ export const useTemplateHeaderBreadcrumbs = () => {
};

const useRightSideItems = (links?: LinkOptions[]) => {
const { getDateRangeInTimestamp } = useDateRangeProviderContext();
const { asset, assetType, overrides } = useAssetDetailsRenderPropsContext();

const topCornerLinkComponents: Record<LinkOptions, JSX.Element> = useMemo(
() => ({
nodeDetails: (
<LinkToNodeDetails
asset={asset}
assetType={assetType}
dateRangeTimestamp={getDateRangeInTimestamp()}
/>
),
nodeDetails: <LinkToNodeDetails asset={asset} assetType={assetType} />,
alertRule: <LinkToAlertsRule onClick={overrides?.alertRule?.onCreateRuleClick} />,
apmServices: <LinkToApmServices assetName={asset.name} apmField={APM_HOST_FILTER_FIELD} />,
}),
[asset, assetType, getDateRangeInTimestamp, overrides?.alertRule?.onCreateRuleClick]
[asset, assetType, overrides?.alertRule?.onCreateRuleClick]
);

const rightSideItems = useMemo(
Expand Down Expand Up @@ -157,7 +149,7 @@ const useTabs = (tabs: Tab[]) => {
const tabEntries: TabItem[] = useMemo(
() =>
tabs.map(({ name, ...tab }) => {
if (tab.id === FlyoutTabIds.LINK_TO_APM) {
if (tab.id === ContentTabIds.LINK_TO_APM) {
return getTabToApmTraces(name);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import createContainer from 'constate';
import { useLazyRef } from '../../../hooks/use_lazy_ref';
import type { TabIds } from '../types';
import { AssetDetailsState, useAssetDetailsUrlState } from './use_asset_details_url_state';
import { AssetDetailsUrlState, useAssetDetailsUrlState } from './use_asset_details_url_state';

interface TabSwitcherParams {
defaultActiveTabId?: TabIds;
Expand All @@ -26,7 +26,7 @@ export function useTabSwitcher({ defaultActiveTabId }: TabSwitcherParams) {
// On a tab click, mark the tab content as allowed to be rendered
renderedTabsSet.current.add(tabId);

setUrlState({ tabId: tabId as AssetDetailsState['tabId'] });
setUrlState({ tabId: tabId as AssetDetailsUrlState['tabId'] });
};

return {
Expand Down
Loading