Skip to content

Commit

Permalink
[ObsUx][Infra] Add collapsible sections in the overview tab (elastic#…
Browse files Browse the repository at this point in the history
…175716)

Closes elastic#175989

## Summary

This PR is a follow-up to
elastic#175558. It adds the active
alerts count next to the alert section title (this will happen after the
alerts widget is loaded) following the rules:

Default behaviour
No alerts at all ==> Collapse and say 'No active alerts'
No active alerts ==> Collapse and say 'No active alerts'
Active alerts ==> Expand fully

Collapsed
No alerts at all ==> Say 'No active alerts'
No active alerts ==> Say 'No active alerts'
Active alerts ==> say "X active alerts"


It adds a change in the `AlertSummaryWidget` to make it possible to get
the alerts count after the widget is loaded using a new prop.

This PR also changes the alerts tab active alert count badge color on
the hosts view to keep it consistent:

| Before | After |
| ------ | ------ |
| <img width="242" alt="image"
src="https://github.com/elastic/kibana/assets/14139027/85beec43-6522-4d58-a808-d3f7ec3e0759">
| <img width="263" alt="image"
src="https://github.com/elastic/kibana/assets/14139027/43983493-3270-471a-8788-40ce38bed334">
|

## Testing
- Open hosts view and select a host with active alerts (flyout or full
page)
   - The alerts section should be expanded showing the alerts widget
<img width="1920" alt="image"
src="https://github.com/elastic/kibana/assets/14139027/f851c914-96cf-475f-bab3-dddc485405ec">fd1a21035a5f)
   - Collapse the alerts section by clicking on the title or the button:
<img width="1917" alt="image"
src="https://github.com/elastic/kibana/assets/14139027/e23e09ed-a781-4961-b592-6f13f539c316">
- Open hosts view and select a host without active alerts (flyout or
full page)
- The alerts section should be collapsed showing the message 'No active
alerts'

![Image](https://github.com/elastic/obs-infraobs-team/assets/14139027/7077d3b3-c020-4be5-a3da-b46dda0d3ae0)


https://github.com/elastic/kibana/assets/14139027/4058ed69-95f5-4b4c-8925-6680ac3791c1
  • Loading branch information
jennypavlova authored and fkanout committed Mar 4, 2024
1 parent 336ad2b commit d8ec6e7
Show file tree
Hide file tree
Showing 9 changed files with 154 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { useMemo } from 'react';
import React, { useMemo, useState } from 'react';

import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { EuiFlexGroup, EuiFlexItem, type EuiAccordionProps } from '@elastic/eui';
import { useSummaryTimeRange } from '@kbn/observability-plugin/public';
import type { TimeRange } from '@kbn/es-query';
import type { InventoryItemType } from '@kbn/metrics-data-access-plugin/common';
Expand All @@ -24,6 +24,8 @@ import { ALERT_STATUS_ALL } from '../../../../common/alerts/constants';
import { AlertsSectionTitle } from '../../components/section_titles';
import { useAssetDetailsRenderPropsContext } from '../../hooks/use_asset_details_render_props';
import { CollapsibleSection } from './section/collapsible_section';
import { AlertsClosedContent } from './alerts_closed_content';
import { type AlertsCount } from '../../../../hooks/use_alerts_count';

export const AlertsSummaryContent = ({
assetName,
Expand All @@ -37,6 +39,9 @@ export const AlertsSummaryContent = ({
const { featureFlags } = usePluginConfig();
const [isAlertFlyoutVisible, { toggle: toggleAlertFlyout }] = useBoolean(false);
const { overrides } = useAssetDetailsRenderPropsContext();
const [collapsibleStatus, setCollapsibleStatus] =
useState<EuiAccordionProps['forceState']>('open');
const [activeAlertsCount, setActiveAlertsCount] = useState<number | undefined>(undefined);

const alertsEsQueryByStatus = useMemo(
() =>
Expand All @@ -48,13 +53,23 @@ export const AlertsSummaryContent = ({
[assetName, dateRange]
);

const onLoaded = (alertsCount?: AlertsCount) => {
const { activeAlertCount = 0 } = alertsCount ?? {};
const hasActiveAlerts = activeAlertCount > 0;

setCollapsibleStatus(hasActiveAlerts ? 'open' : 'closed');
setActiveAlertsCount(alertsCount?.activeAlertCount);
};

return (
<>
<CollapsibleSection
title={AlertsSectionTitle}
collapsible
data-test-subj="infraAssetDetailsAlertsCollapsible"
id="alerts"
closedSectionContent={<AlertsClosedContent activeAlertCount={activeAlertsCount} />}
initialTriggerValue={collapsibleStatus}
extraAction={
<EuiFlexGroup alignItems="center" responsive={false}>
{featureFlags.inventoryThresholdAlertRuleEnabled && (
Expand All @@ -72,9 +87,12 @@ export const AlertsSummaryContent = ({
</EuiFlexGroup>
}
>
<MemoAlertSummaryWidget alertsQuery={alertsEsQueryByStatus} dateRange={dateRange} />
<MemoAlertSummaryWidget
onLoaded={onLoaded}
alertsQuery={alertsEsQueryByStatus}
dateRange={dateRange}
/>
</CollapsibleSection>

{featureFlags.inventoryThresholdAlertRuleEnabled && (
<AlertFlyout
filter={`${findInventoryFields(assetType).name}: "${assetName}"`}
Expand All @@ -91,10 +109,11 @@ export const AlertsSummaryContent = ({
interface MemoAlertSummaryWidgetProps {
alertsQuery: AlertsEsQuery;
dateRange: TimeRange;
onLoaded: (alertsCount?: AlertsCount) => void;
}

const MemoAlertSummaryWidget = React.memo(
({ alertsQuery, dateRange }: MemoAlertSummaryWidgetProps) => {
({ alertsQuery, dateRange, onLoaded }: MemoAlertSummaryWidgetProps) => {
const { services } = useKibanaContextForPlugin();

const summaryTimeRange = useSummaryTimeRange(dateRange);
Expand All @@ -112,6 +131,7 @@ const MemoAlertSummaryWidget = React.memo(
featureIds={infraAlertFeatureIds}
filter={alertsQuery}
timeRange={summaryTimeRange}
onLoaded={onLoaded}
fullSize
hideChart
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* 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 { i18n } from '@kbn/i18n';
import { EuiBadge, EuiToolTip } from '@elastic/eui';

export const AlertsClosedContent = ({ activeAlertCount }: { activeAlertCount?: number }) => {
const shouldRenderAlertsClosedContent = typeof activeAlertCount === 'number';

if (!shouldRenderAlertsClosedContent) {
return null;
}

if (activeAlertCount > 0) {
return (
<EuiToolTip
position="bottom"
content={i18n.translate('xpack.infra.assetDetails.tooltip.activeAlertsExplanation', {
defaultMessage: 'Active alerts',
})}
>
<EuiBadge
data-test-subj="infraAssetDetailsAlertsClosedContentWithAlerts"
iconType="warning"
color="danger"
>
{activeAlertCount}
</EuiBadge>
</EuiToolTip>
);
}

return (
<span data-test-subj="infraAssetDetailsAlertsClosedContentNoAlerts">
{i18n.translate('xpack.infra.assetDetails.noActiveAlertsContentClosedSection', {
defaultMessage: 'No active alerts',
})}
</span>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@
* 2.0.
*/

import React, { useEffect, useState } from 'react';

import {
EuiAccordion,
EuiFlexGroup,
EuiFlexItem,
useGeneratedHtmlId,
type EuiAccordionProps,
} from '@elastic/eui';
import React, { useState } from 'react';

export const CollapsibleSection = ({
title,
Expand All @@ -22,6 +23,7 @@ export const CollapsibleSection = ({
collapsible,
['data-test-subj']: dataTestSubj,
id,
initialTriggerValue,
}: {
title: React.FunctionComponent;
closedSectionContent?: React.ReactNode;
Expand All @@ -31,13 +33,18 @@ export const CollapsibleSection = ({
collapsible: boolean;
['data-test-subj']: string;
id: string;
initialTriggerValue?: EuiAccordionProps['forceState'];
}) => {
const [trigger, setTrigger] = useState<EuiAccordionProps['forceState']>('open');

useEffect(() => {
setTrigger(initialTriggerValue ?? 'open');
}, [initialTriggerValue]);

const Title = title;
const ButtonContent = () =>
closedSectionContent && trigger === 'closed' ? (
<EuiFlexGroup gutterSize="m">
<EuiFlexGroup gutterSize="m" alignItems="center">
<EuiFlexItem grow={false}>
<Title />
</EuiFlexItem>
Expand Down
2 changes: 1 addition & 1 deletion x-pack/plugins/infra/public/hooks/use_alerts_count.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ interface FetchAlertsCountParams {
signal: AbortSignal;
}

interface AlertsCount {
export interface AlertsCount {
activeAlertCount: number;
recoveredAlertCount: number;
}
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 { EuiIcon, EuiLoadingSpinner, EuiNotificationBadge, EuiToolTip } from '@elastic/eui';
import { EuiIcon, EuiLoadingSpinner, EuiBadge, EuiToolTip } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { useAlertsCount } from '../../../../../hooks/use_alerts_count';
import { infraAlertFeatureIds } from './config';
Expand Down Expand Up @@ -40,12 +40,12 @@ export const AlertsTabBadge = () => {
typeof alertsCount?.activeAlertCount === 'number' && alertsCount.activeAlertCount > 0;

return shouldRenderBadge ? (
<EuiNotificationBadge
<EuiBadge
color="danger"
className="eui-alignCenter"
size="m"
data-test-subj="hostsView-tabs-alerts-count"
>
{alertsCount?.activeAlertCount}
</EuiNotificationBadge>
</EuiBadge>
) : null;
};
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ export const AlertSummaryWidget = ({

useEffect(() => {
if (!isLoading && onLoaded) {
onLoaded();
onLoaded({ activeAlertCount, recoveredAlertCount });
}
}, [isLoading, onLoaded]);
}, [activeAlertCount, isLoading, onLoaded, recoveredAlertCount]);

if (isLoading)
return <AlertSummaryWidgetLoader fullSize={fullSize} isLoadingWithoutChart={hideChart} />;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ export interface ChartProps {
onBrushEnd?: BrushEndListener;
}

interface AlertsCount {
activeAlertCount: number;
recoveredAlertCount: number;
}

export interface AlertSummaryWidgetProps {
featureIds?: ValidFeatureId[];
filter?: estypes.QueryDslQueryContainer;
Expand All @@ -38,5 +43,5 @@ export interface AlertSummaryWidgetProps {
timeRange: AlertSummaryTimeRange;
chartProps: ChartProps;
hideChart?: boolean;
onLoaded?: () => void;
onLoaded?: (alertsCount?: AlertsCount) => void;
}
45 changes: 45 additions & 0 deletions x-pack/test/functional/apps/infra/node_details.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,15 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await pageObjects.assetDetails.overviewAlertsTitleExists();
});

it('should show / hide alerts section with no alerts and show / hide closed section content', async () => {
await pageObjects.assetDetails.alertsSectionCollapsibleExist();
// Collapsed by default
await pageObjects.assetDetails.alertsSectionClosedContentNoAlertsExist();
// Expand
await pageObjects.assetDetails.alertsSectionCollapsibleClick();
await pageObjects.assetDetails.alertsSectionClosedContentNoAlertsMissing();
});

it('shows the CPU Profiling prompt if UI setting for Profiling integration is enabled', async () => {
await setInfrastructureProfilingIntegrationUiSetting(true);
await pageObjects.assetDetails.cpuProfilingPromptExists();
Expand All @@ -213,6 +222,42 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await setInfrastructureProfilingIntegrationUiSetting(false);
await pageObjects.assetDetails.cpuProfilingPromptMissing();
});

describe('Alerts Section with alerts', () => {
before(async () => {
await navigateToNodeDetails('demo-stack-apache-01', 'demo-stack-apache-01');
await pageObjects.header.waitUntilLoadingHasFinished();

await pageObjects.timePicker.setAbsoluteRange(
START_HOST_ALERTS_DATE.format(DATE_PICKER_FORMAT),
END_HOST_ALERTS_DATE.format(DATE_PICKER_FORMAT)
);

await pageObjects.assetDetails.clickOverviewTab();
});

after(async () => {
await navigateToNodeDetails('Jennys-MBP.fritz.box', 'Jennys-MBP.fritz.box');
await pageObjects.header.waitUntilLoadingHasFinished();

await pageObjects.timePicker.setAbsoluteRange(
START_HOST_PROCESSES_DATE.format(DATE_PICKER_FORMAT),
END_HOST_PROCESSES_DATE.format(DATE_PICKER_FORMAT)
);
});

it('should show / hide alerts section with active alerts and show / hide closed section content', async () => {
await pageObjects.assetDetails.alertsSectionCollapsibleExist();
// Expanded by default
await pageObjects.assetDetails.alertsSectionClosedContentMissing();
// Collapse
await pageObjects.assetDetails.alertsSectionCollapsibleClick();
await pageObjects.assetDetails.alertsSectionClosedContentExist();
// Expand
await pageObjects.assetDetails.alertsSectionCollapsibleClick();
await pageObjects.assetDetails.alertsSectionClosedContentMissing();
});
});
});

describe('Metadata Tab', () => {
Expand Down
18 changes: 18 additions & 0 deletions x-pack/test/functional/page_objects/asset_details.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,24 @@ export function AssetDetailsProvider({ getService }: FtrProviderContext) {
return await testSubjects.existOrFail('infraAssetDetailsMetricsCollapsible');
},

async alertsSectionCollapsibleClick() {
return await testSubjects.click('infraAssetDetailsAlertsCollapsible');
},

async alertsSectionClosedContentExist() {
return await testSubjects.existOrFail('infraAssetDetailsAlertsClosedContentWithAlerts');
},
async alertsSectionClosedContentMissing() {
return await testSubjects.missingOrFail('infraAssetDetailsAlertsClosedContentWithAlerts');
},

async alertsSectionClosedContentNoAlertsExist() {
return await testSubjects.existOrFail('infraAssetDetailsAlertsClosedContentNoAlerts');
},
async alertsSectionClosedContentNoAlertsMissing() {
return await testSubjects.missingOrFail('infraAssetDetailsAlertsClosedContentNoAlerts');
},

// Metadata
async clickMetadataTab() {
return testSubjects.click('infraAssetDetailsMetadataTab');
Expand Down

0 comments on commit d8ec6e7

Please sign in to comment.