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] Add date picker to asset details #164450

Merged
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,65 @@
* 2.0.
*/

import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
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';

export const Content = () => {
return (
<>
<TabPanel activeWhen={FlyoutTabIds.ANOMALIES}>
<Anomalies />
</TabPanel>
<TabPanel activeWhen={FlyoutTabIds.OVERVIEW}>
<Overview />
</TabPanel>
<TabPanel activeWhen={FlyoutTabIds.LOGS}>
<Logs />
</TabPanel>
<TabPanel activeWhen={FlyoutTabIds.METADATA}>
<Metadata />
</TabPanel>
<TabPanel activeWhen={FlyoutTabIds.OSQUERY}>
<Osquery />
</TabPanel>
<TabPanel activeWhen={FlyoutTabIds.PROCESSES}>
<Processes />
</TabPanel>
</>
<EuiFlexGroup direction="column">
<EuiFlexItem grow={false}>
<DatePickerWrapper
visibleFor={[
FlyoutTabIds.OVERVIEW,
FlyoutTabIds.LOGS,
FlyoutTabIds.METADATA,
mykolaharmash marked this conversation as resolved.
Show resolved Hide resolved
FlyoutTabIds.PROCESSES,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are the processes tab results affected by the new dates as in the process list API it will always check for 1-minute interval 🤔

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never mind I saw that we plan to add a tooltip to explain that in this issue so I guess it should be fine.

FlyoutTabIds.ANOMALIES,
]}
>
<DatePicker />
</DatePickerWrapper>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<TabPanel activeWhen={FlyoutTabIds.ANOMALIES}>
<Anomalies />
</TabPanel>
<TabPanel activeWhen={FlyoutTabIds.OVERVIEW}>
<Overview />
</TabPanel>
<TabPanel activeWhen={FlyoutTabIds.LOGS}>
<Logs />
</TabPanel>
<TabPanel activeWhen={FlyoutTabIds.METADATA}>
<Metadata />
</TabPanel>
<TabPanel activeWhen={FlyoutTabIds.OSQUERY}>
<Osquery />
</TabPanel>
<TabPanel activeWhen={FlyoutTabIds.PROCESSES}>
<Processes />
</TabPanel>
</EuiFlexItem>
</EuiFlexGroup>
);
};

const DatePickerWrapper = ({
visibleFor,
children,
}: {
visibleFor: FlyoutTabIds[];
children: React.ReactNode;
}) => {
const { activeTabId } = useTabSwitcherContext();

return <div hidden={!visibleFor.includes(activeTabId as FlyoutTabIds)}>{children}</div>;
};

const TabPanel = ({
activeWhen,
children,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const ContextProviders = ({
}) => {
const { asset, dateRange, overrides, onTabsStateChange, assetType = 'host', renderMode } = props;
return (
<DateRangeProvider dateRange={dateRange}>
<DateRangeProvider initialDateRange={dateRange}>
<MetadataStateProvider asset={asset} assetType={assetType}>
<AssetDetailsStateProvider
state={{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

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]
);

return (
<EuiSuperDatePicker
start={dateRange.from}
end={dateRange.to}
updateButtonProps={{ iconOnly: true }}
onTimeChange={onTimeChange}
width="full"
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

import createContainer from 'constate';
import type { AssetDetailsProps } from '../types';
import { useDateRangeProviderContext } from './use_date_range';
import { useMetadataStateProviderContext } from './use_metadata_state';

export interface UseAssetDetailsStateProps {
Expand All @@ -19,7 +18,6 @@ export interface UseAssetDetailsStateProps {

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

// When the asset asset.name is known we can load the page faster
Expand All @@ -32,8 +30,6 @@ export function useAssetDetailsState({ state }: UseAssetDetailsStateProps) {
name: asset.name || metadata?.name || 'asset-name',
},
assetType,
dateRange,
dateRangeTs,
onTabsStateChange,
overrides,
renderMode,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,40 @@
* 2.0.
*/

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

import { toTimestampRange } from '../utils';
import { useAssetDetailsStateContext } from './use_asset_details_state';

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

export type UseAssetDetailsStateProps = Pick<AssetDetailsProps, 'dateRange'>;
export interface UseAssetDetailsStateProps {
initialDateRange: TimeRange;
}

export function useDateRangeProvider({ initialDateRange }: UseAssetDetailsStateProps) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When we have the date range persisted in the URL (the case of the flyout for example) should we first get the date range from the URL value (passed to the asset details component) and if we don't have it to fallback to default (the value selected in the host view / the 15m default)?

Copy link
Contributor Author

@crespocarlos crespocarlos Aug 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're completely right. I totally forgot to store the date range in the URL state.

const { onTabsStateChange } = useAssetDetailsStateContext();
const [dateRange, setDateRange] = useState(initialDateRange);

export function useDateRangeProvider({ dateRange: rawDateRange }: UseAssetDetailsStateProps) {
const dateRange = useMemo(() => {
const parsedDateRange = useMemo(() => {
const { from = DEFAULT_DATE_RANGE.from, to = DEFAULT_DATE_RANGE.to } =
parseDateRange(rawDateRange);
parseDateRange(dateRange);

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

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

return {
dateRange,
dateRangeTs,
};
return { dateRange, setDateRange, getDateRangeInTimestamp };
}

export const [DateRangeProvider, useDateRangeProviderContext] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { useDateRangeProviderContext } from './use_date_range';
export type UseMetadataProviderProps = Pick<AssetDetailsProps, 'asset' | 'assetType'>;

export function useMetadataProvider({ asset, assetType }: UseMetadataProviderProps) {
const { dateRangeTs } = useDateRangeProviderContext();
const { getDateRangeInTimestamp } = useDateRangeProviderContext();
const inventoryModel = findInventoryModel(assetType);
const { sourceId } = useSourceContext();

Expand All @@ -24,7 +24,7 @@ export function useMetadataProvider({ asset, assetType }: UseMetadataProviderPro
assetType,
inventoryModel.requiredMetrics,
sourceId,
dateRangeTs
getDateRangeInTimestamp()
);

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import { useKibanaContextForPlugin } from '../../../hooks/use_kibana';
import { APM_HOST_FILTER_FIELD } from '../constants';
import { LinkToAlertsRule, LinkToApmServices, LinkToNodeDetails, LinkToUptime } from '../links';
import { FlyoutTabIds, type LinkOptions, type Tab, type TabIds } from '../types';
import { toTimestampRange } from '../utils';
import { useAssetDetailsStateContext } from './use_asset_details_state';
import { useDateRangeProviderContext } from './use_date_range';
import { useTabSwitcherContext } from './use_tab_switcher';
Expand All @@ -31,7 +30,7 @@ export const usePageHeader = (tabs: Tab[], links?: LinkOptions[]) => {
};

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

Expand All @@ -41,7 +40,7 @@ const useRightSideItems = (links?: LinkOptions[]) => {
<LinkToNodeDetails
asset={asset}
assetType={assetType}
currentTimestamp={toTimestampRange(dateRange).to}
currentTimestamp={getDateRangeInTimestamp().to}
/>
),
alertRule: <LinkToAlertsRule onClick={overrides?.alertRule?.onCreateRuleClick} />,
Expand All @@ -58,7 +57,13 @@ const useRightSideItems = (links?: LinkOptions[]) => {
/>
),
}),
[asset, assetType, dateRange, metadata?.info?.host?.ip, overrides?.alertRule?.onCreateRuleClick]
[
asset,
assetType,
getDateRangeInTimestamp,
metadata?.info?.host?.ip,
overrides?.alertRule?.onCreateRuleClick,
]
);

const rightSideItems = useMemo(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,19 @@
import React from 'react';
import { AnomaliesTable } from '../../../../pages/metrics/inventory_view/components/ml/anomaly_detection/anomalies_table/anomalies_table';
import { useAssetDetailsStateContext } from '../../hooks/use_asset_details_state';
import { useDateRangeProviderContext } from '../../hooks/use_date_range';

export const Anomalies = () => {
const { dateRange } = useDateRangeProviderContext();
const { asset, overrides } = useAssetDetailsStateContext();
const { onClose = () => {} } = overrides?.anomalies ?? {};

return <AnomaliesTable closeFlyout={onClose} hostName={asset.name} />;
return (
<AnomaliesTable
closeFlyout={onClose}
hostName={asset.name}
dateRange={dateRange}
hideDatePicker
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,18 @@ import { EuiFieldSearch, EuiFlexGroup, EuiFlexItem, EuiButtonEmpty } from '@elas
import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app';
import { LogStream } from '@kbn/logs-shared-plugin/public';
import { DEFAULT_LOG_VIEW, LogViewReference } from '@kbn/logs-shared-plugin/common';

import { useKibanaContextForPlugin } from '../../../../hooks/use_kibana';
import { findInventoryFields } from '../../../../../common/inventory_models';
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';

const TEXT_QUERY_THROTTLE_INTERVAL_MS = 500;

export const Logs = () => {
const { asset, assetType, overrides, onTabsStateChange, dateRangeTs } =
useAssetDetailsStateContext();
const { getDateRangeInTimestamp } = useDateRangeProviderContext();
const { asset, assetType, overrides, onTabsStateChange } = useAssetDetailsStateContext();
const { logs } = useDataViewsProviderContext();

const { query: overrideQuery } = overrides?.logs ?? {};
Expand All @@ -35,7 +35,7 @@ export const Logs = () => {
const [textQuery, setTextQuery] = useState(overrideQuery ?? '');
const [textQueryDebounced, setTextQueryDebounced] = useState(overrideQuery ?? '');

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

useDebounce(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { EuiFlexGrid, EuiFlexItem, EuiTitle, EuiSpacer, EuiFlexGroup } from '@el
import type { DataView } from '@kbn/data-views-plugin/public';
import type { TimeRange } from '@kbn/es-query';
import { FormattedMessage } from '@kbn/i18n-react';
import { LensEmbeddableInput } from '@kbn/lens-plugin/public';
import {
assetDetailsDashboards,
XY_MISSING_VALUE_DOTTED_LINE_CONFIG,
Expand All @@ -18,6 +19,7 @@ import { buildCombinedHostsFilter } from '../../../../../utils/filters/build';
import { LensChart, HostMetricsExplanationContent } from '../../../../lens';
import { METRIC_CHART_HEIGHT } from '../../../constants';
import { Popover } from '../../common/popover';
import { useDateRangeProviderContext } from '../../../hooks/use_date_range';

type DataViewOrigin = 'logs' | 'metrics';

Expand All @@ -30,6 +32,7 @@ interface Props {

export const MetricsGrid = React.memo(
({ nodeName, metricsDataView, logsDataView, timeRange }: Props) => {
const { setDateRange } = useDateRangeProviderContext();
const getDataView = useCallback(
(dataViewOrigin: DataViewOrigin) => {
return dataViewOrigin === 'metrics' ? metricsDataView : logsDataView;
Expand All @@ -50,6 +53,21 @@ export const MetricsGrid = React.memo(
[getDataView, nodeName]
);

const handleBrushEnd = useCallback(
({
range,
preventDefault,
}: Parameters<NonNullable<LensEmbeddableInput['onBrushEnd']>>[0]) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might make sense to add an alias for this type to make it a bit easier to read

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point.

setDateRange({
from: new Date(range[0]).toISOString(),
to: new Date(range[1]).toISOString(),
});

preventDefault();
},
[setDateRange]
);

return (
<EuiFlexGroup gutterSize="m" direction="column">
<EuiFlexItem grow={false}>
Expand Down Expand Up @@ -77,7 +95,7 @@ export const MetricsGrid = React.memo(
title={title}
overrides={overrides}
visualizationType="lnsXY"
disableTriggers
onBrushEnd={handleBrushEnd}
/>
</EuiFlexItem>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ import { MetricsGrid } from './metrics/metrics_grid';
import { useAssetDetailsStateContext } from '../../hooks/use_asset_details_state';
import { useMetadataStateProviderContext } from '../../hooks/use_metadata_state';
import { useDataViewsProviderContext } from '../../hooks/use_data_views';
import { useDateRangeProviderContext } from '../../hooks/use_date_range';

export const Overview = () => {
const { asset, assetType, dateRange, renderMode } = useAssetDetailsStateContext();
const { dateRange } = useDateRangeProviderContext();
const { asset, assetType, renderMode } = useAssetDetailsStateContext();
const {
metadata,
loading: metadataLoading,
Expand Down
Loading