Skip to content

Commit

Permalink
Propagate flyout state to node details page and some fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
crespocarlos committed Sep 7, 2023
1 parent d6a2bd7 commit 0ccb795
Show file tree
Hide file tree
Showing 30 changed files with 339 additions and 252 deletions.
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,13 @@ 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';

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

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

Expand All @@ -44,34 +42,31 @@ 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([
rt.type({
tabId: TabIdRT,
const AssetDetailsUrlStateRT = rt.partial({
tabId: TabIdRT,
name: rt.string,
dateRange: rt.type({
from: rt.string,
to: rt.string,
}),
rt.partial({
dateRange: rt.type({
from: rt.string,
to: rt.string,
}),
processSearch: rt.string,
metadataSearch: rt.string,
logsSearch: 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 @@ -7,42 +7,55 @@

import type { TimeRange } from '@kbn/es-query';
import createContainer from 'constate';
import { useCallback, useMemo } from 'react';
import { useCallback, useState } from 'react';
import useEffectOnce from 'react-use/lib/useEffectOnce';
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',
};
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 interface UseDateRangeProviderProps {
initialDateRange: TimeRange;
}

export function useDateRangeProvider({ initialDateRange }: UseDateRangeProviderProps) {
const [urlState, setUrlState] = useAssetDetailsUrlState();
const dateRange: TimeRange = urlState?.dateRange ?? initialDateRange;
const dateRange: TimeRange = urlState?.dateRange ?? initialDateRange ?? getDefaultDateRange();
const [parsedDateRange, setParsedDateRange] = useState(parseDateRange(dateRange));

useEffectOnce(() => {
const { from, to } = getParsedDateRange();

// forces the date picker to initiallize with absolute dates.
setUrlState({ dateRange: { from, to } });
});

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

const parsedDateRange = useMemo(() => {
const { from = DEFAULT_DATE_RANGE.from, to = DEFAULT_DATE_RANGE.to } =
parseDateRange(dateRange);
const getParsedDateRange = useCallback(() => {
const { from = dateRange.from, to = dateRange.to } = parsedDateRange;

return { from, to };
}, [dateRange]);
}, [dateRange, parsedDateRange]);

const getDateRangeInTimestamp = useCallback(
() => toTimestampRange(parsedDateRange),
[parsedDateRange]
() => toTimestampRange(getParsedDateRange()),
[getParsedDateRange]
);

return { dateRange, setDateRange, getDateRangeInTimestamp };
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,10 +21,10 @@ 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';
import { useAssetDetailsUrlState } from './use_asset_details_url_state';

type TabItem = NonNullable<Pick<EuiPageHeaderProps, 'tabs'>['tabs']>[number];

Expand Down Expand Up @@ -60,7 +60,7 @@ export const useTemplateHeaderBreadcrumbs = () => {
const breadcrumbs: EuiBreadcrumbsProps['breadcrumbs'] =
// If there is a state object in location, it's persisted in case the page is opened in a new tab or after page refresh
// With that, we can show the return button. Otherwise, it will be hidden (ex: the user opened a shared URL or opened the page from their bookmarks)
location.state || history.length > 1
location.state || location.key
? [
{
text: (
Expand Down Expand Up @@ -89,22 +89,16 @@ export const useTemplateHeaderBreadcrumbs = () => {
};

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

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

const rightSideItems = useMemo(
Expand Down Expand Up @@ -157,7 +151,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
Loading

0 comments on commit 0ccb795

Please sign in to comment.