mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Infrastructure UI] Save selected tab for Hosts View into url state (#151975)
## 📓 Summary Closes #150856 This PR refactor the existing implementation of the tabs, extracting this piece of logic into its own hook `useTabId` which stores the selected tab in the URL and allows sharing this preference with others. To keep the optimization performed in [[Infrastructure UI] Add Alerts tab into Hosts View](https://github.com/elastic/kibana/pull/149579), has been necessary to push down the memoization of the AlertSummary component. This PR also hide the count of alerts when it is equal to `0`. ## 🧪 Testing - Navigate to the hosts' view - Create an Inventory rule to generate alerts for all our hosts (it can be something like cpu-usage > 0%) - Refresh the hosts' view page and start switching between tabs. - Refresh again the page to verify the selected tab is preserved and its value is stored in the URL. https://user-images.githubusercontent.com/34506779/220871592-8a4ae1b7-3b9c-4ab1-be3f-4bd673ab9f2f.mov --------- Co-authored-by: Marco Antonio Ghiani <marcoantonio.ghiani@elastic.co> Co-authored-by: Carlos Crespo <crespocarlos@users.noreply.github.com>
This commit is contained in:
parent
a70de0387b
commit
4a775b83a6
7 changed files with 111 additions and 38 deletions
16
x-pack/plugins/infra/public/hooks/use_lazy_ref.ts
Normal file
16
x-pack/plugins/infra/public/hooks/use_lazy_ref.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* 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 { useRef, MutableRefObject } from 'react';
|
||||
|
||||
export const useLazyRef = <Type>(initializer: () => Type) => {
|
||||
const ref = useRef<Type | null>(null);
|
||||
if (ref.current === null) {
|
||||
ref.current = initializer();
|
||||
}
|
||||
return ref as MutableRefObject<Type>;
|
||||
};
|
|
@ -27,35 +27,26 @@ import {
|
|||
DEFAULT_INTERVAL,
|
||||
infraAlertFeatureIds,
|
||||
} from '../config';
|
||||
import { useAlertsQuery } from '../../../hooks/use_alerts_query';
|
||||
import { AlertsEsQuery, useAlertsQuery } from '../../../hooks/use_alerts_query';
|
||||
import AlertsStatusFilter from './alerts_status_filter';
|
||||
import { HostsState } from '../../../hooks/use_unified_search_url_state';
|
||||
|
||||
export const AlertsTabContent = React.memo(() => {
|
||||
export const AlertsTabContent = () => {
|
||||
const { services } = useKibana<InfraClientCoreStart & InfraClientStartDeps>();
|
||||
|
||||
const { alertStatus, setAlertStatus, alertsEsQueryByStatus } = useAlertsQuery();
|
||||
|
||||
const { unifiedSearchDateRange } = useUnifiedSearchContext();
|
||||
|
||||
const summaryTimeRange = useSummaryTimeRange(unifiedSearchDateRange);
|
||||
const { application, cases, triggersActionsUi } = services;
|
||||
|
||||
const { application, cases, charts, triggersActionsUi } = services;
|
||||
|
||||
const {
|
||||
alertsTableConfigurationRegistry,
|
||||
getAlertsStateTable: AlertsStateTable,
|
||||
getAlertSummaryWidget: AlertSummaryWidget,
|
||||
} = triggersActionsUi;
|
||||
const { alertsTableConfigurationRegistry, getAlertsStateTable: AlertsStateTable } =
|
||||
triggersActionsUi;
|
||||
|
||||
const CasesContext = cases.ui.getCasesContext();
|
||||
const uiCapabilities = application?.capabilities;
|
||||
const casesCapabilities = cases.helpers.getUICapabilities(uiCapabilities.observabilityCases);
|
||||
|
||||
const chartThemes = {
|
||||
theme: charts.theme.useChartsTheme(),
|
||||
baseTheme: charts.theme.useChartsBaseTheme(),
|
||||
};
|
||||
|
||||
return (
|
||||
<HeightRetainer>
|
||||
<EuiFlexGroup direction="column" gutterSize="m">
|
||||
|
@ -65,12 +56,9 @@ export const AlertsTabContent = React.memo(() => {
|
|||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<AlertSummaryWidget
|
||||
chartThemes={chartThemes}
|
||||
featureIds={infraAlertFeatureIds}
|
||||
filter={alertsEsQueryByStatus}
|
||||
fullSize
|
||||
timeRange={summaryTimeRange}
|
||||
<MemoAlertSummaryWidget
|
||||
alertsQuery={alertsEsQueryByStatus}
|
||||
dateRange={unifiedSearchDateRange}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
{alertsEsQueryByStatus && (
|
||||
|
@ -97,7 +85,38 @@ export const AlertsTabContent = React.memo(() => {
|
|||
</EuiFlexGroup>
|
||||
</HeightRetainer>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
interface MemoAlertSummaryWidgetProps {
|
||||
alertsQuery: AlertsEsQuery;
|
||||
dateRange: HostsState['dateRange'];
|
||||
}
|
||||
|
||||
const MemoAlertSummaryWidget = React.memo(
|
||||
({ alertsQuery, dateRange }: MemoAlertSummaryWidgetProps) => {
|
||||
const { services } = useKibana<InfraClientStartDeps>();
|
||||
|
||||
const summaryTimeRange = useSummaryTimeRange(dateRange);
|
||||
|
||||
const { charts, triggersActionsUi } = services;
|
||||
const { getAlertSummaryWidget: AlertSummaryWidget } = triggersActionsUi;
|
||||
|
||||
const chartThemes = {
|
||||
theme: charts.theme.useChartsTheme(),
|
||||
baseTheme: charts.theme.useChartsBaseTheme(),
|
||||
};
|
||||
|
||||
return (
|
||||
<AlertSummaryWidget
|
||||
chartThemes={chartThemes}
|
||||
featureIds={infraAlertFeatureIds}
|
||||
filter={alertsQuery}
|
||||
fullSize
|
||||
timeRange={summaryTimeRange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
const useSummaryTimeRange = (unifiedSearchDateRange: TimeRange) => {
|
||||
const timeBuckets = useTimeBuckets();
|
||||
|
|
|
@ -36,9 +36,12 @@ export const AlertsTabBadge = () => {
|
|||
);
|
||||
}
|
||||
|
||||
return (
|
||||
const shouldRenderBadge =
|
||||
typeof alertsCount?.activeAlertCount === 'number' && alertsCount.activeAlertCount > 0;
|
||||
|
||||
return shouldRenderBadge ? (
|
||||
<EuiNotificationBadge className="eui-alignCenter" size="m">
|
||||
{alertsCount?.activeAlertCount}
|
||||
</EuiNotificationBadge>
|
||||
);
|
||||
) : null;
|
||||
};
|
||||
|
|
|
@ -5,14 +5,15 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useRef, useState } from 'react';
|
||||
import React from 'react';
|
||||
import { EuiTabs, EuiTab, EuiSpacer } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useLazyRef } from '../../../../../hooks/use_lazy_ref';
|
||||
import { MetricsGrid } from './metrics/metrics_grid';
|
||||
import { AlertsTabContent } from './alerts';
|
||||
|
||||
import { AlertsTabBadge } from './alerts_tab_badge';
|
||||
import { TabIds } from '../../types';
|
||||
import { TabIds, useTabId } from '../../hooks/use_tab_id';
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
|
@ -32,14 +33,11 @@ const tabs = [
|
|||
},
|
||||
];
|
||||
|
||||
const initialRenderedTabsSet = new Set([tabs[0].id]);
|
||||
|
||||
export const Tabs = () => {
|
||||
const [selectedTabId, setSelectedTabId] = useTabId(tabs[0].id);
|
||||
// This map allow to keep 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 = useRef(initialRenderedTabsSet);
|
||||
|
||||
const [selectedTabId, setSelectedTabId] = useState(tabs[0].id);
|
||||
const renderedTabsSet = useLazyRef(() => new Set([selectedTabId]));
|
||||
|
||||
const tabEntries = tabs.map((tab, index) => (
|
||||
<EuiTab
|
||||
|
|
|
@ -8,7 +8,7 @@ import { useCallback, useMemo, useState } from 'react';
|
|||
import createContainer from 'constate';
|
||||
import { getTime } from '@kbn/data-plugin/common';
|
||||
import { TIMESTAMP } from '@kbn/rule-data-utils';
|
||||
import { buildEsQuery, Filter, Query } from '@kbn/es-query';
|
||||
import { BoolQuery, buildEsQuery, Filter, Query } from '@kbn/es-query';
|
||||
import { SnapshotNode } from '../../../../../common/http_api';
|
||||
import { useUnifiedSearchContext } from './use_unified_search';
|
||||
import { HostsState } from './use_unified_search_url_state';
|
||||
|
@ -16,6 +16,10 @@ import { useHostsView } from './use_hosts_view';
|
|||
import { AlertStatus } from '../types';
|
||||
import { ALERT_STATUS_QUERY } from '../constants';
|
||||
|
||||
export interface AlertsEsQuery {
|
||||
bool: BoolQuery;
|
||||
}
|
||||
|
||||
export const useAlertsQueryImpl = () => {
|
||||
const { hostNodes } = useHostsView();
|
||||
|
||||
|
@ -60,7 +64,7 @@ const createAlertsEsQuery = ({
|
|||
dateRange: HostsState['dateRange'];
|
||||
hostNodes: SnapshotNode[];
|
||||
status?: AlertStatus;
|
||||
}) => {
|
||||
}): AlertsEsQuery => {
|
||||
const alertStatusQuery = createAlertStatusQuery(status);
|
||||
|
||||
const dateFilter = createDateFilter(dateRange);
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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 * 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 { useUrlState } from '../../../../utils/use_url_state';
|
||||
|
||||
const TAB_ID_URL_STATE_KEY = 'tabId';
|
||||
|
||||
export const useTabId = (initialValue: TabId = TabIds.METRICS): [TabId, TabIdUpdater] => {
|
||||
return useUrlState<TabId>({
|
||||
defaultState: initialValue,
|
||||
decodeUrlState: makeDecodeUrlState(initialValue),
|
||||
encodeUrlState,
|
||||
urlStateKey: TAB_ID_URL_STATE_KEY,
|
||||
});
|
||||
};
|
||||
|
||||
const TabIdRT = rt.union([rt.literal('alerts'), rt.literal('metrics')]);
|
||||
|
||||
export enum TabIds {
|
||||
ALERTS = 'alerts',
|
||||
METRICS = 'metrics',
|
||||
}
|
||||
|
||||
type TabId = rt.TypeOf<typeof TabIdRT>;
|
||||
type TabIdUpdater = (tabId: TabId) => void;
|
||||
|
||||
const encodeUrlState = TabIdRT.encode;
|
||||
const makeDecodeUrlState = (initialValue: TabId) => (value: unknown) => {
|
||||
return pipe(TabIdRT.decode(value), fold(constant(initialValue), identity));
|
||||
};
|
|
@ -8,11 +8,6 @@
|
|||
import { ALERT_STATUS_ACTIVE, ALERT_STATUS_RECOVERED } from '@kbn/rule-data-utils';
|
||||
import { ALERT_STATUS_ALL } from './constants';
|
||||
|
||||
export enum TabIds {
|
||||
ALERTS = 'alerts',
|
||||
METRICS = 'metrics',
|
||||
}
|
||||
|
||||
export type AlertStatus =
|
||||
| typeof ALERT_STATUS_ACTIVE
|
||||
| typeof ALERT_STATUS_RECOVERED
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue