Skip to content

Commit

Permalink
[Infra UI] Add alerts to asset details flyout (elastic#161677)
Browse files Browse the repository at this point in the history
Closes elastic#160371 

## Summary

This PR adds alerts section to the overview tab inside the asset details
flyout component.

Notes: A lot of changes are extracting common components from the alerts
tab to a common folder. The flyout version is not showing the chart so
it's not exactly the same component but a big part of the logic is
reused there. The tooltip content can be found in a [Figma comment
](https://www.figma.com/file/XBVpHX6pOBaTPoGHWhEQJH?node-id=843:435665&mode=design#492130894)


<img width="1616" alt="alerts_section"
src="https://github.com/elastic/kibana/assets/14139027/399dd1ea-e1cb-4e7f-9ed5-917ced7cc490">

## Alerts summary widget changes:
After introducing the `hideChart` prop
[here](elastic#161263) in this PR I
change the spinner type and size in case of no chart we want to have a
smaller section with a smaller spinner:


![image](https://github.com/elastic/kibana/assets/14139027/43a3c611-0404-4c21-a503-22f1a79dc1de)



![image](https://github.com/elastic/kibana/assets/14139027/a870fa9b-5367-4303-9b7d-4da9ff2eae2b)


##  Storybook
I added some changes to make the alerts widget show in the storybook
[[Workaround for
storybook](elastic@d97a2b1)]

<img width="1905" alt="image"
src="https://github.com/elastic/kibana/assets/14139027/539c9443-f977-4301-8d2b-d24f1d01b44e">
 
## Testing
- Go to Hosts view and open the single host flyout - alerts section
should be visible
- Alerts title icon should open a tooltip with links to alerts and
alerts documentation
- Alerts links:
- The Create rule link will open a flyout (on top, not closing the
existing flyout) to create an inventory rule, when closed/saved rule the
single host flyout should remain open
- The Show All link should navigate to alerts and apply time range /
host.name filter selected in the hosts view


https://github.com/elastic/kibana/assets/14139027/b362042a-b9de-460c-86ae-282154b586ff

---------

Co-authored-by: kibanamachine <[email protected]>
  • Loading branch information
2 people authored and Devon Thomson committed Aug 1, 2023
1 parent e0cab80 commit c0bf5ef
Show file tree
Hide file tree
Showing 26 changed files with 612 additions and 156 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const AlertFlyout = ({ options, nodeType, filter, visible, setVisible }:
const { triggersActionsUI } = useContext(TriggerActionsContext);

const { inventoryPrefill } = useAlertPrefillContext();
const { customMetrics } = inventoryPrefill;
const { customMetrics = [] } = inventoryPrefill ?? {};
const onCloseFlyout = useCallback(() => setVisible(false), [setVisible]);
const AddAlertFlyout = useMemo(
() =>
Expand Down
57 changes: 57 additions & 0 deletions x-pack/plugins/infra/public/common/alerts/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* 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 { i18n } from '@kbn/i18n';
import { ALERT_STATUS, ALERT_STATUS_ACTIVE, ALERT_STATUS_RECOVERED } from '@kbn/rule-data-utils';
import type { AlertStatusFilter } from './types';

export const ALERT_STATUS_ALL = 'all';

export const ALL_ALERTS: AlertStatusFilter = {
status: ALERT_STATUS_ALL,
label: i18n.translate('xpack.infra.hostsViewPage.tabs.alerts.alertStatusFilter.showAll', {
defaultMessage: 'Show all',
}),
};

export const ACTIVE_ALERTS: AlertStatusFilter = {
status: ALERT_STATUS_ACTIVE,
query: {
term: {
[ALERT_STATUS]: {
value: ALERT_STATUS_ACTIVE,
},
},
},
label: i18n.translate('xpack.infra.hostsViewPage.tabs.alerts.alertStatusFilter.active', {
defaultMessage: 'Active',
}),
};

export const RECOVERED_ALERTS: AlertStatusFilter = {
status: ALERT_STATUS_RECOVERED,
query: {
term: {
[ALERT_STATUS]: {
value: ALERT_STATUS_RECOVERED,
},
},
},
label: i18n.translate('xpack.infra.hostsViewPage.tabs.alerts.alertStatusFilter.recovered', {
defaultMessage: 'Recovered',
}),
};

export const ALERT_STATUS_QUERY = {
[ACTIVE_ALERTS.status]: ACTIVE_ALERTS.query,
[RECOVERED_ALERTS.status]: RECOVERED_ALERTS.query,
};

export const ALERTS_DOC_HREF =
'https://www.elastic.co/guide/en/observability/current/create-alerts.html';

export const ALERTS_PATH = '/app/observability/alerts';
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* 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 { getTime } from '@kbn/data-plugin/common';
import { ALERT_TIME_RANGE } from '@kbn/rule-data-utils';
import { buildEsQuery, Filter, type TimeRange } from '@kbn/es-query';
import type { AlertStatus } from '@kbn/observability-plugin/common/typings';
import { ALERT_STATUS_QUERY } from './constants';
import { buildCombinedHostsFilter } from '../../utils/filters/build';
import type { AlertsEsQuery } from './types';

export const createAlertsEsQuery = ({
dateRange,
hostNodeNames,
status,
}: {
dateRange: TimeRange;
hostNodeNames: string[];
status?: AlertStatus;
}): AlertsEsQuery => {
const alertStatusFilter = createAlertStatusFilter(status);

const dateFilter = createDateFilter(dateRange);
const hostsFilter = buildCombinedHostsFilter({
field: 'host.name',
values: hostNodeNames,
});

const filters = [alertStatusFilter, dateFilter, hostsFilter].filter(Boolean) as Filter[];

return buildEsQuery(undefined, [], filters);
};

const createDateFilter = (date: TimeRange) =>
getTime(undefined, date, { fieldName: ALERT_TIME_RANGE });

const createAlertStatusFilter = (status: AlertStatus = 'all'): Filter | null =>
ALERT_STATUS_QUERY[status] ? { query: ALERT_STATUS_QUERY[status], meta: {} } : null;
18 changes: 18 additions & 0 deletions x-pack/plugins/infra/public/common/alerts/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* 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 type { BoolQuery, Filter } from '@kbn/es-query';
import type { AlertStatus } from '@kbn/observability-plugin/common/typings';
export interface AlertStatusFilter {
status: AlertStatus;
query?: Filter['query'];
label: string;
}

export interface AlertsEsQuery {
bool: BoolQuery;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* 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.
*/

const summaryResponse = {
activeAlertCount: 3,
recoveredAlertCount: 3,
activeAlerts: [
{
key_as_string: '1689172920000',
key: 1689172920000,
doc_count: 3,
},
{
key_as_string: '1689172980000',
key: 1689172980000,
doc_count: 3,
},
],
recoveredAlerts: [
{
key_as_string: '2023-07-12T14:42:00.000Z',
key: 1689172920000,
doc_count: 3,
},
{
key_as_string: '2023-07-12T14:43:00.000Z',
key: 1689172980000,
doc_count: 3,
},
],
};

export const alertsSummaryHttpResponse = {
default: () => Promise.resolve({ ...summaryResponse }),
};

export type AlertsSummaryHttpMocks = keyof typeof alertsSummaryHttpResponse;
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export {
processesChartHttpResponse,
type ProcessesHttpMocks,
} from './processes';
export { alertsSummaryHttpResponse, type AlertsSummaryHttpMocks } from './alerts';
export { anomaliesHttpResponse, type AnomaliesHttpMocks } from './anomalies';
export { snapshotAPItHttpResponse, type SnapshotAPIHttpMocks } from './snapshot_api';
export { getLogEntries } from './log_entries';
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ import type { HttpStart, HttpHandler } from '@kbn/core/public';
import type { Parameters } from '@storybook/react';
import { INFA_ML_GET_METRICS_HOSTS_ANOMALIES_PATH } from '../../../../../common/http_api/infra_ml';
import {
alertsSummaryHttpResponse,
anomaliesHttpResponse,
metadataHttpResponse,
processesChartHttpResponse,
processesHttpResponse,
snapshotAPItHttpResponse,
type AlertsSummaryHttpMocks,
type AnomaliesHttpMocks,
type MetadataResponseMocks,
type ProcessesHttpMocks,
Expand Down Expand Up @@ -43,6 +45,14 @@ export const getHttp = (params: Parameters): HttpStart => {
return Promise.resolve({});
}
}) as HttpHandler,
post: (async (path: string) => {
switch (path) {
case '/internal/rac/alerts/_alert_summary':
return alertsSummaryHttpResponse[params.mock as AlertsSummaryHttpMocks]();
default:
return Promise.resolve({});
}
}) as HttpHandler,
} as unknown as HttpStart;

return http;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import React from 'react';
import React, { JSXElementConstructor, ReactElement } from 'react';
import { I18nProvider } from '@kbn/i18n-react';
import {
KibanaContextProvider,
Expand All @@ -18,6 +18,9 @@ import { useParameter } from '@storybook/addons';
import type { DeepPartial } from 'utility-types';
import type { LocatorPublic } from '@kbn/share-plugin/public';
import type { IKibanaSearchRequest, ISearchOptions } from '@kbn/data-plugin/public';
import { AlertSummaryWidget } from '@kbn/triggers-actions-ui-plugin/public/application/sections/alert_summary_widget/alert_summary_widget';
import type { Theme } from '@elastic/charts/dist/utils/themes/theme';
import type { AlertSummaryWidgetProps } from '@kbn/triggers-actions-ui-plugin/public/application/sections/alert_summary_widget';
import type { PluginKibanaContextValue } from '../../../hooks/use_kibana';
import { SourceProvider } from '../../../containers/metrics_source';
import { getHttp } from './context/http';
Expand Down Expand Up @@ -66,6 +69,20 @@ export const DecorateWithKibanaContext: DecoratorFn = (story) => {
return Promise.resolve([]);
},
},
uiSettings: {
get: () => ({ key: 'mock', defaultOverride: undefined } as any),
},
triggersActionsUi: {
getAlertSummaryWidget: AlertSummaryWidget as (
props: AlertSummaryWidgetProps
) => ReactElement<AlertSummaryWidgetProps, string | JSXElementConstructor<any>>,
},
charts: {
theme: {
useChartsTheme: () => ({} as Theme),
useChartsBaseTheme: () => ({} as Theme),
},
},
settings: {
client: {
get$: (key: string) => of(getSettings(key)),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* 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 React from 'react';
import { EuiText, EuiLink } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { ALERTS_DOC_HREF } from '../../../common/alerts/constants';
import { LinkToAlertsHomePage } from '../links/link_to_alerts_page';

export const AlertsTooltipContent = React.memo(() => {
const onClick = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
e.stopPropagation();
};

return (
<EuiText size="xs" onClick={onClick}>
<p>
<FormattedMessage
id="xpack.infra.assetDetails.alerts.tooltip.alertsLabel"
defaultMessage="Showing alerts for this host. You can create and manage alerts in {alerts}"
values={{
alerts: <LinkToAlertsHomePage />,
}}
/>
</p>
<p>
<FormattedMessage
id="xpack.infra.assetDetails.alerts.tooltip.documentationLabel"
defaultMessage="See {documentation} for more information"
values={{
documentation: (
<EuiLink
data-test-subj="assetDetailsTooltipDocumentationLink"
href={ALERTS_DOC_HREF}
target="_blank"
>
<FormattedMessage
id="xpack.infra.assetDetails.alerts.tooltip.documentationLink"
defaultMessage="documentation"
/>
</EuiLink>
),
}}
/>
</p>
</EuiText>
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import React from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import { EuiButtonEmpty } from '@elastic/eui';

export interface LinkToAlertsRule {
export interface LinkToAlertsRuleProps {
onClick?: () => void;
}

export const LinkToAlertsRule = ({ onClick }: LinkToAlertsRule) => {
export const LinkToAlertsRule = ({ onClick }: LinkToAlertsRuleProps) => {
return (
<EuiButtonEmpty
data-test-subj="infraNodeContextPopoverCreateInventoryRuleButton"
Expand All @@ -23,8 +23,8 @@ export const LinkToAlertsRule = ({ onClick }: LinkToAlertsRule) => {
iconType="bell"
>
<FormattedMessage
id="xpack.infra.infra.nodeDetails.createAlertLink"
defaultMessage="Create inventory rule"
id="xpack.infra.infra.assetDetails.alerts.createAlertLink"
defaultMessage="Create rule"
/>
</EuiButtonEmpty>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* 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 React from 'react';
import { encode } from '@kbn/rison';
import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app';
import { FormattedMessage } from '@kbn/i18n-react';
import { EuiButtonEmpty, EuiLink } from '@elastic/eui';
import type { TimeRange } from '@kbn/es-query';
import { ALERTS_PATH } from '../../../common/alerts/constants';
import { useKibanaContextForPlugin } from '../../../hooks/use_kibana';

export interface LinkToAlertsPageProps {
nodeName: string;
queryField: string;
dateRange: TimeRange;
}

export const LinkToAlertsPage = ({ nodeName, queryField, dateRange }: LinkToAlertsPageProps) => {
const { services } = useKibanaContextForPlugin();
const { http } = services;

const linkToAlertsPage = http.basePath.prepend(
`${ALERTS_PATH}?_a=${encode({
kuery: `${queryField}:"${nodeName}"`,
rangeFrom: dateRange.from,
rangeTo: dateRange.to,
status: 'all',
})}`
);

return (
<RedirectAppLinks coreStart={services}>
<EuiButtonEmpty
data-test-subj="assetDetails-flyout-alerts-link"
size="xs"
iconSide="right"
iconType="sortRight"
flush="both"
href={linkToAlertsPage}
>
<FormattedMessage
id="xpack.infra.assetDetails.flyout.AlertsPageLinkLabel"
defaultMessage="Show all"
/>
</EuiButtonEmpty>
</RedirectAppLinks>
);
};

export const LinkToAlertsHomePage = () => {
const { services } = useKibanaContextForPlugin();
const { http } = services;

const linkToAlertsPage = http.basePath.prepend(ALERTS_PATH);

return (
<RedirectAppLinks coreStart={services}>
<EuiLink data-test-subj="assetDetailsTooltipDocumentationLink" href={linkToAlertsPage}>
<FormattedMessage
id="xpack.infra.assetDetails.table.tooltip.alertsLink"
defaultMessage="alerts."
/>
</EuiLink>
</RedirectAppLinks>
);
};
Loading

0 comments on commit c0bf5ef

Please sign in to comment.