mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Cloud Security] Alerts Datagrids for Contextual Flyout (#199573)
## Summary This PR is for Alerts Datagrid component in Contextual Flyout This PR is for Alerts Datagrid in Contextual Flyout for User name and Host name <img width="1480" alt="Screenshot 2024-11-14 at 9 08 26 AM" src="https://github.com/user-attachments/assets/46a254c8-b7f1-4b63-9637-2b1c281d5502"> --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
16127fcc8f
commit
ab965f75a6
18 changed files with 693 additions and 30 deletions
|
@ -10,6 +10,7 @@ import {
|
|||
defaultErrorMessage,
|
||||
buildMutedRulesFilter,
|
||||
buildEntityFlyoutPreviewQuery,
|
||||
buildEntityAlertsQuery,
|
||||
} from './helpers';
|
||||
|
||||
const fallbackMessage = 'thisIsAFallBackMessage';
|
||||
|
@ -182,4 +183,78 @@ describe('test helper methods', () => {
|
|||
expect(buildEntityFlyoutPreviewQuery(field)).toEqual(expectedQuery);
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildEntityAlertsQuery', () => {
|
||||
const getExpectedAlertsQuery = (size?: number) => {
|
||||
return {
|
||||
size: size || 0,
|
||||
_source: false,
|
||||
fields: [
|
||||
'_id',
|
||||
'_index',
|
||||
'kibana.alert.rule.uuid',
|
||||
'kibana.alert.severity',
|
||||
'kibana.alert.rule.name',
|
||||
'kibana.alert.workflow_status',
|
||||
],
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
bool: {
|
||||
must: [],
|
||||
filter: [
|
||||
{
|
||||
match_phrase: {
|
||||
'host.name': {
|
||||
query: 'exampleHost',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
should: [],
|
||||
must_not: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: 'Today',
|
||||
lte: 'Tomorrow',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
terms: {
|
||||
'kibana.alert.workflow_status': ['open', 'acknowledged'],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
it('should return the correct query when given all params', () => {
|
||||
const field = 'host.name';
|
||||
const query = 'exampleHost';
|
||||
const to = 'Tomorrow';
|
||||
const from = 'Today';
|
||||
const size = 100;
|
||||
|
||||
expect(buildEntityAlertsQuery(field, to, from, query, size)).toEqual(
|
||||
getExpectedAlertsQuery(size)
|
||||
);
|
||||
});
|
||||
|
||||
it('should return the correct query when not given size', () => {
|
||||
const field = 'host.name';
|
||||
const query = 'exampleHost';
|
||||
const to = 'Tomorrow';
|
||||
const from = 'Today';
|
||||
const size = undefined;
|
||||
|
||||
expect(buildEntityAlertsQuery(field, to, from, query)).toEqual(getExpectedAlertsQuery(size));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import { QueryDslQueryContainer } from '@kbn/data-views-plugin/common/types';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { CspBenchmarkRulesStates } from '../schema/rules/latest';
|
||||
|
||||
|
@ -62,3 +63,59 @@ export const buildEntityFlyoutPreviewQuery = (field: string, queryValue?: string
|
|||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const buildEntityAlertsQuery = (
|
||||
field: string,
|
||||
to: string,
|
||||
from: string,
|
||||
queryValue?: string,
|
||||
size?: number
|
||||
) => {
|
||||
return {
|
||||
size: size || 0,
|
||||
_source: false,
|
||||
fields: [
|
||||
'_id',
|
||||
'_index',
|
||||
'kibana.alert.rule.uuid',
|
||||
'kibana.alert.severity',
|
||||
'kibana.alert.rule.name',
|
||||
'kibana.alert.workflow_status',
|
||||
],
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
bool: {
|
||||
must: [],
|
||||
filter: [
|
||||
{
|
||||
match_phrase: {
|
||||
[field]: {
|
||||
query: queryValue,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
should: [],
|
||||
must_not: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: from,
|
||||
lte: to,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
terms: {
|
||||
'kibana.alert.workflow_status': ['open', 'acknowledged'],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -40,10 +40,11 @@ export const useMisconfigurationFindings = (options: UseCspOptions) => {
|
|||
params: buildMisconfigurationsFindingsQuery(options, rulesStates!),
|
||||
})
|
||||
);
|
||||
if (!aggregations) throw new Error('expected aggregations to be defined');
|
||||
if (!aggregations && options.ignore_unavailable === false)
|
||||
throw new Error('expected aggregations to be defined');
|
||||
|
||||
return {
|
||||
count: getMisconfigurationAggregationCount(aggregations.count.buckets),
|
||||
count: getMisconfigurationAggregationCount(aggregations?.count.buckets),
|
||||
rows: hits.hits.map((finding) => ({
|
||||
result: finding._source?.result,
|
||||
rule: finding?._source?.rule,
|
||||
|
|
|
@ -11,6 +11,9 @@ import { AlertsPreview } from './alerts_preview';
|
|||
import { TestProviders } from '../../../common/mock/test_providers';
|
||||
import { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
|
||||
import type { ParsedAlertsData } from '../../../overview/components/detection_response/alerts_by_status/types';
|
||||
import { useMisconfigurationPreview } from '@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview';
|
||||
import { useVulnerabilitiesPreview } from '@kbn/cloud-security-posture/src/hooks/use_vulnerabilities_preview';
|
||||
import { useRiskScore } from '../../../entity_analytics/api/hooks/use_risk_score';
|
||||
|
||||
const mockAlertsData: ParsedAlertsData = {
|
||||
open: {
|
||||
|
@ -29,9 +32,10 @@ const mockAlertsData: ParsedAlertsData = {
|
|||
},
|
||||
};
|
||||
|
||||
jest.mock(
|
||||
'../../../detections/components/alerts_kpis/alerts_summary_charts_panel/use_summary_chart_data'
|
||||
);
|
||||
// Mock hooks
|
||||
jest.mock('@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview');
|
||||
jest.mock('@kbn/cloud-security-posture/src/hooks/use_vulnerabilities_preview');
|
||||
jest.mock('../../../entity_analytics/api/hooks/use_risk_score');
|
||||
jest.mock('@kbn/expandable-flyout');
|
||||
|
||||
describe('AlertsPreview', () => {
|
||||
|
@ -39,6 +43,13 @@ describe('AlertsPreview', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
(useExpandableFlyoutApi as jest.Mock).mockReturnValue({ openLeftPanel: mockOpenLeftPanel });
|
||||
(useVulnerabilitiesPreview as jest.Mock).mockReturnValue({
|
||||
data: { count: { CRITICAL: 0, HIGH: 1, MEDIUM: 1, LOW: 0, UNKNOWN: 0 } },
|
||||
});
|
||||
(useRiskScore as jest.Mock).mockReturnValue({ data: [{ host: { risk: 75 } }] });
|
||||
(useMisconfigurationPreview as jest.Mock).mockReturnValue({
|
||||
data: { count: { passed: 1, failed: 1 } },
|
||||
});
|
||||
});
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
@ -47,17 +58,17 @@ describe('AlertsPreview', () => {
|
|||
it('renders', () => {
|
||||
const { getByTestId } = render(
|
||||
<TestProviders>
|
||||
<AlertsPreview alertsData={mockAlertsData} />
|
||||
<AlertsPreview alertsData={mockAlertsData} name="host1" fieldName="host.name" />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(getByTestId('securitySolutionFlyoutInsightsAlertsTitleText')).toBeInTheDocument();
|
||||
expect(getByTestId('securitySolutionFlyoutInsightsAlertsTitleLink')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders correct alerts number', () => {
|
||||
const { getByTestId } = render(
|
||||
<TestProviders>
|
||||
<AlertsPreview alertsData={mockAlertsData} />
|
||||
<AlertsPreview alertsData={mockAlertsData} name="host1" fieldName="host.name" />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
|
@ -67,7 +78,7 @@ describe('AlertsPreview', () => {
|
|||
it('should render the correct number of distribution bar section based on the number of severities', () => {
|
||||
const { queryAllByTestId } = render(
|
||||
<TestProviders>
|
||||
<AlertsPreview alertsData={mockAlertsData} />
|
||||
<AlertsPreview alertsData={mockAlertsData} name="host1" fieldName="host.name" />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
|
|
|
@ -5,19 +5,40 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { capitalize } from 'lodash';
|
||||
import type { EuiThemeComputed } from '@elastic/eui';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText, EuiTitle, useEuiTheme } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { DistributionBar } from '@kbn/security-solution-distribution-bar';
|
||||
import { getAbbreviatedNumber } from '@kbn/cloud-security-posture-common';
|
||||
import { ExpandablePanel } from '../../../flyout/shared/components/expandable_panel';
|
||||
import { getSeverityColor } from '../../../detections/components/alerts_kpis/severity_level_panel/helpers';
|
||||
import {
|
||||
buildEntityFlyoutPreviewQuery,
|
||||
getAbbreviatedNumber,
|
||||
} from '@kbn/cloud-security-posture-common';
|
||||
import { hasVulnerabilitiesData } from '@kbn/cloud-security-posture';
|
||||
import { useMisconfigurationPreview } from '@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview';
|
||||
import { useVulnerabilitiesPreview } from '@kbn/cloud-security-posture/src/hooks/use_vulnerabilities_preview';
|
||||
import { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
|
||||
import type {
|
||||
AlertsByStatus,
|
||||
ParsedAlertsData,
|
||||
} from '../../../overview/components/detection_response/alerts_by_status/types';
|
||||
import { ExpandablePanel } from '../../../flyout/shared/components/expandable_panel';
|
||||
import { getSeverityColor } from '../../../detections/components/alerts_kpis/severity_level_panel/helpers';
|
||||
import type { HostRiskScore, UserRiskScore } from '../../../../common/search_strategy';
|
||||
import {
|
||||
buildHostNamesFilter,
|
||||
buildUserNamesFilter,
|
||||
RiskScoreEntity,
|
||||
} from '../../../../common/search_strategy';
|
||||
import { useRiskScore } from '../../../entity_analytics/api/hooks/use_risk_score';
|
||||
import { FIRST_RECORD_PAGINATION } from '../../../entity_analytics/common';
|
||||
import { HostDetailsPanelKey } from '../../../flyout/entity_details/host_details_left';
|
||||
import {
|
||||
EntityDetailsLeftPanelTab,
|
||||
CspInsightLeftPanelSubTab,
|
||||
} from '../../../flyout/entity_details/shared/components/left_panel/left_panel_header';
|
||||
import { UserDetailsPanelKey } from '../../../flyout/entity_details/user_details_left';
|
||||
|
||||
const AlertsCount = ({
|
||||
alertsTotal,
|
||||
|
@ -56,9 +77,13 @@ const AlertsCount = ({
|
|||
|
||||
export const AlertsPreview = ({
|
||||
alertsData,
|
||||
fieldName,
|
||||
name,
|
||||
isPreviewMode,
|
||||
}: {
|
||||
alertsData: ParsedAlertsData;
|
||||
fieldName: string;
|
||||
name: string;
|
||||
isPreviewMode?: boolean;
|
||||
}) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
|
@ -82,9 +107,120 @@ export const AlertsPreview = ({
|
|||
|
||||
const totalAlertsCount = alertStats.reduce((total, item) => total + item.count, 0);
|
||||
|
||||
const { data } = useMisconfigurationPreview({
|
||||
query: buildEntityFlyoutPreviewQuery(fieldName, name),
|
||||
sort: [],
|
||||
enabled: true,
|
||||
pageSize: 1,
|
||||
ignore_unavailable: true,
|
||||
});
|
||||
const isUsingHostName = fieldName === 'host.name';
|
||||
const passedFindings = data?.count.passed || 0;
|
||||
const failedFindings = data?.count.failed || 0;
|
||||
|
||||
const hasMisconfigurationFindings = passedFindings > 0 || failedFindings > 0;
|
||||
|
||||
const { data: vulnerabilitiesData } = useVulnerabilitiesPreview({
|
||||
query: buildEntityFlyoutPreviewQuery('host.name', name),
|
||||
sort: [],
|
||||
enabled: true,
|
||||
pageSize: 1,
|
||||
});
|
||||
|
||||
const {
|
||||
CRITICAL = 0,
|
||||
HIGH = 0,
|
||||
MEDIUM = 0,
|
||||
LOW = 0,
|
||||
NONE = 0,
|
||||
} = vulnerabilitiesData?.count || {};
|
||||
|
||||
const hasVulnerabilitiesFindings = hasVulnerabilitiesData({
|
||||
critical: CRITICAL,
|
||||
high: HIGH,
|
||||
medium: MEDIUM,
|
||||
low: LOW,
|
||||
none: NONE,
|
||||
});
|
||||
|
||||
const buildFilterQuery = useMemo(
|
||||
() => (isUsingHostName ? buildHostNamesFilter([name]) : buildUserNamesFilter([name])),
|
||||
[isUsingHostName, name]
|
||||
);
|
||||
|
||||
const riskScoreState = useRiskScore({
|
||||
riskEntity: isUsingHostName ? RiskScoreEntity.host : RiskScoreEntity.user,
|
||||
filterQuery: buildFilterQuery,
|
||||
onlyLatest: false,
|
||||
pagination: FIRST_RECORD_PAGINATION,
|
||||
});
|
||||
|
||||
const { data: hostRisk } = riskScoreState;
|
||||
|
||||
const riskData = hostRisk?.[0];
|
||||
|
||||
const isRiskScoreExist = isUsingHostName
|
||||
? !!(riskData as HostRiskScore)?.host.risk
|
||||
: !!(riskData as UserRiskScore)?.user.risk;
|
||||
|
||||
const hasNonClosedAlerts = totalAlertsCount > 0;
|
||||
|
||||
const { openLeftPanel } = useExpandableFlyoutApi();
|
||||
|
||||
const goToEntityInsightTab = useCallback(() => {
|
||||
openLeftPanel({
|
||||
id: isUsingHostName ? HostDetailsPanelKey : UserDetailsPanelKey,
|
||||
params: isUsingHostName
|
||||
? {
|
||||
name,
|
||||
isRiskScoreExist,
|
||||
hasMisconfigurationFindings,
|
||||
hasVulnerabilitiesFindings,
|
||||
hasNonClosedAlerts,
|
||||
path: {
|
||||
tab: EntityDetailsLeftPanelTab.CSP_INSIGHTS,
|
||||
subTab: CspInsightLeftPanelSubTab.ALERTS,
|
||||
},
|
||||
}
|
||||
: {
|
||||
user: { name },
|
||||
isRiskScoreExist,
|
||||
hasMisconfigurationFindings,
|
||||
hasNonClosedAlerts,
|
||||
path: {
|
||||
tab: EntityDetailsLeftPanelTab.CSP_INSIGHTS,
|
||||
subTab: CspInsightLeftPanelSubTab.ALERTS,
|
||||
},
|
||||
},
|
||||
});
|
||||
}, [
|
||||
hasMisconfigurationFindings,
|
||||
hasNonClosedAlerts,
|
||||
hasVulnerabilitiesFindings,
|
||||
isRiskScoreExist,
|
||||
isUsingHostName,
|
||||
name,
|
||||
openLeftPanel,
|
||||
]);
|
||||
const link = useMemo(
|
||||
() =>
|
||||
!isPreviewMode
|
||||
? {
|
||||
callback: goToEntityInsightTab,
|
||||
tooltip: (
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.flyout.right.insights.alerts.alertsTooltip"
|
||||
defaultMessage="Show all alerts"
|
||||
/>
|
||||
),
|
||||
}
|
||||
: undefined,
|
||||
[isPreviewMode, goToEntityInsightTab]
|
||||
);
|
||||
return (
|
||||
<ExpandablePanel
|
||||
header={{
|
||||
iconType: !isPreviewMode && hasNonClosedAlerts ? 'arrowStart' : '',
|
||||
title: (
|
||||
<EuiText
|
||||
size="xs"
|
||||
|
@ -98,6 +234,7 @@ export const AlertsPreview = ({
|
|||
/>
|
||||
</EuiText>
|
||||
),
|
||||
link: totalAlertsCount > 0 ? link : undefined,
|
||||
}}
|
||||
data-test-subj={'securitySolutionFlyoutInsightsAlerts'}
|
||||
>
|
||||
|
|
|
@ -0,0 +1,265 @@
|
|||
/*
|
||||
* 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, { memo, useCallback, useEffect, useState } from 'react';
|
||||
import { capitalize } from 'lodash';
|
||||
import type { Criteria, EuiBasicTableColumn } from '@elastic/eui';
|
||||
import { EuiSpacer, EuiPanel, EuiText, EuiBasicTable, EuiIcon, EuiLink } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { DistributionBar } from '@kbn/security-solution-distribution-bar';
|
||||
import {
|
||||
ENTITY_FLYOUT_EXPAND_MISCONFIGURATION_VIEW_VISITS,
|
||||
uiMetricService,
|
||||
} from '@kbn/cloud-security-posture-common/utils/ui_metrics';
|
||||
import { METRIC_TYPE } from '@kbn/analytics';
|
||||
import { buildEntityAlertsQuery } from '@kbn/cloud-security-posture-common/utils/helpers';
|
||||
import { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
|
||||
import { TableId } from '@kbn/securitysolution-data-table';
|
||||
import {
|
||||
OPEN_IN_ALERTS_TITLE_HOSTNAME,
|
||||
OPEN_IN_ALERTS_TITLE_STATUS,
|
||||
OPEN_IN_ALERTS_TITLE_USERNAME,
|
||||
} from '../../../overview/components/detection_response/translations';
|
||||
import { useNavigateToAlertsPageWithFilters } from '../../../common/hooks/use_navigate_to_alerts_page_with_filters';
|
||||
import { DocumentDetailsPreviewPanelKey } from '../../../flyout/document_details/shared/constants/panel_keys';
|
||||
import { useGlobalTime } from '../../../common/containers/use_global_time';
|
||||
import { useQueryAlerts } from '../../../detections/containers/detection_engine/alerts/use_query';
|
||||
import { ALERTS_QUERY_NAMES } from '../../../detections/containers/detection_engine/alerts/constants';
|
||||
import { useSignalIndex } from '../../../detections/containers/detection_engine/alerts/use_signal_index';
|
||||
import { getSeverityColor } from '../../../detections/components/alerts_kpis/severity_level_panel/helpers';
|
||||
import { SeverityBadge } from '../../../common/components/severity_badge';
|
||||
import { ALERT_PREVIEW_BANNER } from '../../../flyout/document_details/preview/constants';
|
||||
import { FILTER_OPEN, FILTER_ACKNOWLEDGED } from '../../../../common/types';
|
||||
|
||||
type AlertSeverity = 'low' | 'medium' | 'high' | 'critical';
|
||||
|
||||
interface ResultAlertsField {
|
||||
_id: string[];
|
||||
_index: string[];
|
||||
'kibana.alert.rule.uuid': string[];
|
||||
'kibana.alert.severity': AlertSeverity[];
|
||||
'kibana.alert.rule.name': string[];
|
||||
'kibana.alert.workflow_status': string[];
|
||||
}
|
||||
|
||||
interface ContextualFlyoutAlertsField {
|
||||
id: string;
|
||||
index: string;
|
||||
ruleUuid: string;
|
||||
ruleName: string;
|
||||
severity: AlertSeverity;
|
||||
status: string;
|
||||
}
|
||||
|
||||
interface AlertsDetailsFields {
|
||||
fields: ResultAlertsField;
|
||||
}
|
||||
|
||||
export const AlertsDetailsTable = memo(
|
||||
({ fieldName, queryName }: { fieldName: 'host.name' | 'user.name'; queryName: string }) => {
|
||||
useEffect(() => {
|
||||
uiMetricService.trackUiMetric(
|
||||
METRIC_TYPE.COUNT,
|
||||
ENTITY_FLYOUT_EXPAND_MISCONFIGURATION_VIEW_VISITS
|
||||
);
|
||||
}, []);
|
||||
|
||||
const [pageIndex, setPageIndex] = useState(0);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
|
||||
const alertsPagination = (alerts: ContextualFlyoutAlertsField[]) => {
|
||||
let pageOfItems;
|
||||
|
||||
if (!pageIndex && !pageSize) {
|
||||
pageOfItems = alerts;
|
||||
} else {
|
||||
const startIndex = pageIndex * pageSize;
|
||||
pageOfItems = alerts?.slice(startIndex, Math.min(startIndex + pageSize, alerts?.length));
|
||||
}
|
||||
|
||||
return {
|
||||
pageOfItems,
|
||||
totalItemCount: alerts?.length,
|
||||
};
|
||||
};
|
||||
|
||||
const { to, from } = useGlobalTime();
|
||||
const { signalIndexName } = useSignalIndex();
|
||||
const { data } = useQueryAlerts({
|
||||
query: buildEntityAlertsQuery(fieldName, to, from, queryName, 500),
|
||||
queryName: ALERTS_QUERY_NAMES.BY_RULE_BY_STATUS,
|
||||
indexName: signalIndexName,
|
||||
});
|
||||
|
||||
const alertDataResults = (data?.hits?.hits as AlertsDetailsFields[])?.map(
|
||||
(item: AlertsDetailsFields) => {
|
||||
return {
|
||||
id: item.fields?._id?.[0],
|
||||
index: item.fields?._index?.[0],
|
||||
ruleName: item.fields?.['kibana.alert.rule.name']?.[0],
|
||||
ruleUuid: item.fields?.['kibana.alert.rule.uuid']?.[0],
|
||||
severity: item.fields?.['kibana.alert.severity']?.[0],
|
||||
status: item.fields?.['kibana.alert.workflow_status']?.[0],
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const severitiesMap = alertDataResults?.map((item) => item.severity) || [];
|
||||
|
||||
const alertStats = Object.entries(
|
||||
severitiesMap.reduce((acc: Record<string, number>, item) => {
|
||||
acc[item] = (acc[item] || 0) + 1;
|
||||
return acc;
|
||||
}, {})
|
||||
).map(([key, count]) => ({
|
||||
key: capitalize(key),
|
||||
count,
|
||||
color: getSeverityColor(key),
|
||||
}));
|
||||
|
||||
const { pageOfItems, totalItemCount } = alertsPagination(alertDataResults || []);
|
||||
|
||||
const pagination = {
|
||||
pageIndex,
|
||||
pageSize,
|
||||
totalItemCount,
|
||||
pageSizeOptions: [10, 25, 100],
|
||||
};
|
||||
|
||||
const onTableChange = ({ page }: Criteria<ContextualFlyoutAlertsField>) => {
|
||||
if (page) {
|
||||
const { index, size } = page;
|
||||
setPageIndex(index);
|
||||
setPageSize(size);
|
||||
}
|
||||
};
|
||||
|
||||
const { openPreviewPanel } = useExpandableFlyoutApi();
|
||||
|
||||
const handleOnEventAlertDetailPanelOpened = useCallback(
|
||||
(eventId: string, indexName: string, tableId: string) => {
|
||||
openPreviewPanel({
|
||||
id: DocumentDetailsPreviewPanelKey,
|
||||
params: {
|
||||
id: eventId,
|
||||
indexName,
|
||||
scopeId: tableId,
|
||||
isPreviewMode: true,
|
||||
banner: ALERT_PREVIEW_BANNER,
|
||||
},
|
||||
});
|
||||
},
|
||||
[openPreviewPanel]
|
||||
);
|
||||
|
||||
const tableId = TableId.alertsOnRuleDetailsPage;
|
||||
|
||||
const columns: Array<EuiBasicTableColumn<ContextualFlyoutAlertsField>> = [
|
||||
{
|
||||
field: 'id',
|
||||
name: '',
|
||||
width: '5%',
|
||||
render: (id: string, alert: ContextualFlyoutAlertsField) => (
|
||||
<EuiLink onClick={() => handleOnEventAlertDetailPanelOpened(id, alert.index, tableId)}>
|
||||
<EuiIcon type={'expand'} />
|
||||
</EuiLink>
|
||||
),
|
||||
},
|
||||
{
|
||||
field: 'ruleName',
|
||||
render: (ruleName: string) => <EuiText size="s">{ruleName}</EuiText>,
|
||||
name: i18n.translate(
|
||||
'xpack.securitySolution.flyout.left.insights.alerts.table.ruleNameColumnName',
|
||||
{
|
||||
defaultMessage: 'Rule',
|
||||
}
|
||||
),
|
||||
width: '55%',
|
||||
},
|
||||
{
|
||||
field: 'severity',
|
||||
render: (severity: AlertSeverity) => (
|
||||
<EuiText size="s">
|
||||
<SeverityBadge value={severity} data-test-subj="severityPropertyValue" />
|
||||
</EuiText>
|
||||
),
|
||||
name: i18n.translate(
|
||||
'xpack.securitySolution.flyout.left.insights.alerts.table.severityColumnName',
|
||||
{
|
||||
defaultMessage: 'Severity',
|
||||
}
|
||||
),
|
||||
width: '20%',
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
render: (status: string) => <EuiText size="s">{capitalize(status)}</EuiText>,
|
||||
name: i18n.translate(
|
||||
'xpack.securitySolution.flyout.left.insights.alerts.table.statusColumnName',
|
||||
{
|
||||
defaultMessage: 'Status',
|
||||
}
|
||||
),
|
||||
width: '20%',
|
||||
},
|
||||
];
|
||||
|
||||
const openAlertsPageWithFilters = useNavigateToAlertsPageWithFilters();
|
||||
|
||||
const openAlertsInAlertsPage = useCallback(
|
||||
() =>
|
||||
openAlertsPageWithFilters(
|
||||
[
|
||||
{
|
||||
title:
|
||||
fieldName === 'host.name'
|
||||
? OPEN_IN_ALERTS_TITLE_HOSTNAME
|
||||
: OPEN_IN_ALERTS_TITLE_USERNAME,
|
||||
selectedOptions: [queryName],
|
||||
fieldName,
|
||||
},
|
||||
{
|
||||
title: OPEN_IN_ALERTS_TITLE_STATUS,
|
||||
selectedOptions: [FILTER_OPEN, FILTER_ACKNOWLEDGED],
|
||||
fieldName: 'kibana.alert.workflow_status',
|
||||
},
|
||||
],
|
||||
true
|
||||
),
|
||||
[fieldName, openAlertsPageWithFilters, queryName]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiPanel hasShadow={false}>
|
||||
<EuiLink onClick={() => openAlertsInAlertsPage()}>
|
||||
<h1 data-test-subj={'securitySolutionFlyoutInsightsAlertsCount'}>
|
||||
{i18n.translate('xpack.securitySolution.flyout.left.insights.alerts.tableTitle', {
|
||||
defaultMessage: 'Alerts ',
|
||||
})}
|
||||
<EuiIcon type={'popout'} />
|
||||
</h1>
|
||||
</EuiLink>
|
||||
|
||||
<EuiSpacer size="xl" />
|
||||
<DistributionBar stats={alertStats.reverse()} />
|
||||
<EuiSpacer size="l" />
|
||||
<EuiBasicTable
|
||||
items={pageOfItems || []}
|
||||
rowHeader="result"
|
||||
columns={columns}
|
||||
pagination={pagination}
|
||||
onChange={onTableChange}
|
||||
data-test-subj={'securitySolutionFlyoutMisconfigurationFindingsTable'}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
AlertsDetailsTable.displayName = 'AlertsDetailsTable';
|
|
@ -12,10 +12,10 @@ import { FormattedMessage } from '@kbn/i18n-react';
|
|||
import type { FlyoutPanelProps, PanelPath } from '@kbn/expandable-flyout';
|
||||
import { useExpandableFlyoutState } from '@kbn/expandable-flyout';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
// import type { FlyoutPanels } from '@kbn/expandable-flyout/src/store/state';
|
||||
import { CspInsightLeftPanelSubTab } from '../../../flyout/entity_details/shared/components/left_panel/left_panel_header';
|
||||
import { MisconfigurationFindingsDetailsTable } from './misconfiguration_findings_details_table';
|
||||
import { VulnerabilitiesFindingsDetailsTable } from './vulnerabilities_findings_details_table';
|
||||
import { AlertsDetailsTable } from './alerts_findings_details_table';
|
||||
|
||||
/**
|
||||
* Insights view displayed in the document details expandable flyout left section
|
||||
|
@ -26,6 +26,7 @@ interface CspFlyoutPanelProps extends FlyoutPanelProps {
|
|||
path: PanelPath;
|
||||
hasMisconfigurationFindings: boolean;
|
||||
hasVulnerabilitiesFindings: boolean;
|
||||
hasNonClosedAlerts: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -35,7 +36,8 @@ function isCspFlyoutPanelProps(
|
|||
): panelLeft is CspFlyoutPanelProps {
|
||||
return (
|
||||
!!panelLeft?.params?.hasMisconfigurationFindings ||
|
||||
!!panelLeft?.params?.hasVulnerabilitiesFindings
|
||||
!!panelLeft?.params?.hasVulnerabilitiesFindings ||
|
||||
!!panelLeft?.params?.hasNonClosedAlerts
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -45,12 +47,14 @@ export const InsightsTabCsp = memo(
|
|||
|
||||
let hasMisconfigurationFindings = false;
|
||||
let hasVulnerabilitiesFindings = false;
|
||||
let hasNonClosedAlerts = false;
|
||||
let subTab: string | undefined;
|
||||
|
||||
// Check if panels.left is of type CspFlyoutPanelProps and extract values
|
||||
if (isCspFlyoutPanelProps(panels.left)) {
|
||||
hasMisconfigurationFindings = panels.left.params.hasMisconfigurationFindings;
|
||||
hasVulnerabilitiesFindings = panels.left.params.hasVulnerabilitiesFindings;
|
||||
hasNonClosedAlerts = panels.left.params.hasNonClosedAlerts;
|
||||
subTab = panels.left.params.path?.subTab;
|
||||
}
|
||||
|
||||
|
@ -63,6 +67,8 @@ export const InsightsTabCsp = memo(
|
|||
? CspInsightLeftPanelSubTab.MISCONFIGURATIONS
|
||||
: hasVulnerabilitiesFindings
|
||||
? CspInsightLeftPanelSubTab.VULNERABILITIES
|
||||
: hasNonClosedAlerts
|
||||
? CspInsightLeftPanelSubTab.ALERTS
|
||||
: '';
|
||||
};
|
||||
|
||||
|
@ -71,6 +77,19 @@ export const InsightsTabCsp = memo(
|
|||
const insightsButtons: EuiButtonGroupOptionProps[] = useMemo(() => {
|
||||
const buttons: EuiButtonGroupOptionProps[] = [];
|
||||
|
||||
if (panels.left?.params?.hasNonClosedAlerts) {
|
||||
buttons.push({
|
||||
id: CspInsightLeftPanelSubTab.ALERTS,
|
||||
label: (
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.flyout.left.insights.alertsButtonLabel"
|
||||
defaultMessage="Alerts"
|
||||
/>
|
||||
),
|
||||
'data-test-subj': 'alertsTabDataTestId',
|
||||
});
|
||||
}
|
||||
|
||||
if (panels.left?.params?.hasMisconfigurationFindings) {
|
||||
buttons.push({
|
||||
id: CspInsightLeftPanelSubTab.MISCONFIGURATIONS,
|
||||
|
@ -96,9 +115,11 @@ export const InsightsTabCsp = memo(
|
|||
'data-test-subj': 'vulnerabilitiesTabDataTestId',
|
||||
});
|
||||
}
|
||||
|
||||
return buttons;
|
||||
}, [
|
||||
panels.left?.params?.hasMisconfigurationFindings,
|
||||
panels.left?.params?.hasNonClosedAlerts,
|
||||
panels.left?.params?.hasVulnerabilitiesFindings,
|
||||
]);
|
||||
|
||||
|
@ -130,8 +151,10 @@ export const InsightsTabCsp = memo(
|
|||
<EuiSpacer size="xl" />
|
||||
{activeInsightsId === CspInsightLeftPanelSubTab.MISCONFIGURATIONS ? (
|
||||
<MisconfigurationFindingsDetailsTable fieldName={fieldName} queryName={name} />
|
||||
) : (
|
||||
) : activeInsightsId === CspInsightLeftPanelSubTab.VULNERABILITIES ? (
|
||||
<VulnerabilitiesFindingsDetailsTable queryName={name} />
|
||||
) : (
|
||||
<AlertsDetailsTable fieldName={fieldName} queryName={name} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -94,7 +94,12 @@ export const EntityInsight = <T,>({
|
|||
if (alertsCount > 0) {
|
||||
insightContent.push(
|
||||
<>
|
||||
<AlertsPreview alertsData={filteredAlertsData} isPreviewMode={isPreviewMode} />
|
||||
<AlertsPreview
|
||||
alertsData={filteredAlertsData}
|
||||
fieldName={fieldName}
|
||||
name={name}
|
||||
isPreviewMode={isPreviewMode}
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
</>
|
||||
);
|
||||
|
@ -103,14 +108,23 @@ export const EntityInsight = <T,>({
|
|||
if (hasMisconfigurationFindings)
|
||||
insightContent.push(
|
||||
<>
|
||||
<MisconfigurationsPreview name={name} fieldName={fieldName} isPreviewMode={isPreviewMode} />
|
||||
<MisconfigurationsPreview
|
||||
name={name}
|
||||
fieldName={fieldName}
|
||||
hasNonClosedAlerts={alertsCount > 0}
|
||||
isPreviewMode={isPreviewMode}
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
</>
|
||||
);
|
||||
if (isVulnerabilitiesFindingForHost && hasVulnerabilitiesFindings)
|
||||
insightContent.push(
|
||||
<>
|
||||
<VulnerabilitiesPreview name={name} isPreviewMode={isPreviewMode} />
|
||||
<VulnerabilitiesPreview
|
||||
name={name}
|
||||
isPreviewMode={isPreviewMode}
|
||||
hasNonClosedAlerts={alertsCount > 0}
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -103,10 +103,12 @@ const MisconfigurationPreviewScore = ({
|
|||
export const MisconfigurationsPreview = ({
|
||||
name,
|
||||
fieldName,
|
||||
hasNonClosedAlerts = false,
|
||||
isPreviewMode,
|
||||
}: {
|
||||
name: string;
|
||||
fieldName: 'host.name' | 'user.name';
|
||||
hasNonClosedAlerts?: boolean;
|
||||
isPreviewMode?: boolean;
|
||||
}) => {
|
||||
const { data } = useMisconfigurationPreview({
|
||||
|
@ -180,6 +182,7 @@ export const MisconfigurationsPreview = ({
|
|||
isRiskScoreExist,
|
||||
hasMisconfigurationFindings,
|
||||
hasVulnerabilitiesFindings,
|
||||
hasNonClosedAlerts,
|
||||
path: {
|
||||
tab: EntityDetailsLeftPanelTab.CSP_INSIGHTS,
|
||||
subTab: CspInsightLeftPanelSubTab.MISCONFIGURATIONS,
|
||||
|
@ -189,11 +192,16 @@ export const MisconfigurationsPreview = ({
|
|||
user: { name },
|
||||
isRiskScoreExist,
|
||||
hasMisconfigurationFindings,
|
||||
path: { tab: EntityDetailsLeftPanelTab.CSP_INSIGHTS },
|
||||
hasNonClosedAlerts,
|
||||
path: {
|
||||
tab: EntityDetailsLeftPanelTab.CSP_INSIGHTS,
|
||||
subTab: CspInsightLeftPanelSubTab.MISCONFIGURATIONS,
|
||||
},
|
||||
},
|
||||
});
|
||||
}, [
|
||||
hasMisconfigurationFindings,
|
||||
hasNonClosedAlerts,
|
||||
hasVulnerabilitiesFindings,
|
||||
isRiskScoreExist,
|
||||
isUsingHostName,
|
||||
|
|
|
@ -72,9 +72,11 @@ const VulnerabilitiesCount = ({
|
|||
export const VulnerabilitiesPreview = ({
|
||||
name,
|
||||
isPreviewMode,
|
||||
hasNonClosedAlerts = false,
|
||||
}: {
|
||||
name: string;
|
||||
isPreviewMode?: boolean;
|
||||
hasNonClosedAlerts?: boolean;
|
||||
}) => {
|
||||
useEffect(() => {
|
||||
uiMetricService.trackUiMetric(METRIC_TYPE.CLICK, ENTITY_FLYOUT_WITH_VULNERABILITY_PREVIEW);
|
||||
|
@ -132,11 +134,13 @@ export const VulnerabilitiesPreview = ({
|
|||
isRiskScoreExist,
|
||||
hasMisconfigurationFindings,
|
||||
hasVulnerabilitiesFindings,
|
||||
hasNonClosedAlerts,
|
||||
path: { tab: EntityDetailsLeftPanelTab.CSP_INSIGHTS, subTab: 'vulnerabilitiesTabId' },
|
||||
},
|
||||
});
|
||||
}, [
|
||||
hasMisconfigurationFindings,
|
||||
hasNonClosedAlerts,
|
||||
hasVulnerabilitiesFindings,
|
||||
isRiskScoreExist,
|
||||
name,
|
||||
|
|
|
@ -32,6 +32,7 @@ describe('useNavigateToAlertsPageWithFilters', () => {
|
|||
expect(mockNavigateTo).toHaveBeenCalledWith({
|
||||
deepLinkId: SecurityPageName.alerts,
|
||||
path: "?pageFilters=!((exclude:!f,existsSelected:!f,fieldName:'test field',hideActionBar:!f,selectedOptions:!('test value'),title:'test filter'))",
|
||||
openInNewTab: false,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -63,6 +64,7 @@ describe('useNavigateToAlertsPageWithFilters', () => {
|
|||
expect(mockNavigateTo).toHaveBeenCalledWith({
|
||||
deepLinkId: SecurityPageName.alerts,
|
||||
path: "?pageFilters=!((exclude:!f,existsSelected:!f,fieldName:'test field 1',hideActionBar:!f,selectedOptions:!('test value 1'),title:'test filter 1'),(exclude:!t,existsSelected:!t,fieldName:'test field 2',hideActionBar:!t,selectedOptions:!('test value 2'),title:'test filter 2'))",
|
||||
openInNewTab: false,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ import { URL_PARAM_KEY } from './use_url_state';
|
|||
export const useNavigateToAlertsPageWithFilters = () => {
|
||||
const { navigateTo } = useNavigation();
|
||||
|
||||
return (filterItems: FilterControlConfig | FilterControlConfig[]) => {
|
||||
return (filterItems: FilterControlConfig | FilterControlConfig[], openInNewTab = false) => {
|
||||
const urlFilterParams = encode(
|
||||
formatPageFilterSearchParam(Array.isArray(filterItems) ? filterItems : [filterItems])
|
||||
);
|
||||
|
@ -24,6 +24,7 @@ export const useNavigateToAlertsPageWithFilters = () => {
|
|||
navigateTo({
|
||||
deepLinkId: SecurityPageName.alerts,
|
||||
path: `?${URL_PARAM_KEY.pageFilter}=${urlFilterParams}`,
|
||||
openInNewTab,
|
||||
});
|
||||
};
|
||||
};
|
||||
|
|
|
@ -25,6 +25,7 @@ export interface HostDetailsPanelProps extends Record<string, unknown> {
|
|||
scopeId: string;
|
||||
hasMisconfigurationFindings?: boolean;
|
||||
hasVulnerabilitiesFindings?: boolean;
|
||||
hasNonClosedAlerts?: boolean;
|
||||
path?: {
|
||||
tab?: EntityDetailsLeftPanelTab;
|
||||
subTab?: CspInsightLeftPanelSubTab;
|
||||
|
@ -43,6 +44,7 @@ export const HostDetailsPanel = ({
|
|||
path,
|
||||
hasMisconfigurationFindings,
|
||||
hasVulnerabilitiesFindings,
|
||||
hasNonClosedAlerts,
|
||||
}: HostDetailsPanelProps) => {
|
||||
const [selectedTabId, setSelectedTabId] = useState(
|
||||
path?.tab === EntityDetailsLeftPanelTab.CSP_INSIGHTS
|
||||
|
@ -58,11 +60,18 @@ export const HostDetailsPanel = ({
|
|||
|
||||
// Determine if the Insights tab should be included
|
||||
const insightsTab =
|
||||
hasMisconfigurationFindings || hasVulnerabilitiesFindings
|
||||
hasMisconfigurationFindings || hasVulnerabilitiesFindings || hasNonClosedAlerts
|
||||
? [getInsightsInputTab({ name, fieldName: 'host.name' })]
|
||||
: [];
|
||||
return [[...riskScoreTab, ...insightsTab], EntityDetailsLeftPanelTab.RISK_INPUTS, () => {}];
|
||||
}, [isRiskScoreExist, name, scopeId, hasMisconfigurationFindings, hasVulnerabilitiesFindings]);
|
||||
}, [
|
||||
isRiskScoreExist,
|
||||
name,
|
||||
scopeId,
|
||||
hasMisconfigurationFindings,
|
||||
hasVulnerabilitiesFindings,
|
||||
hasNonClosedAlerts,
|
||||
]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -13,6 +13,8 @@ import { buildEntityFlyoutPreviewQuery } from '@kbn/cloud-security-posture-commo
|
|||
import { useMisconfigurationPreview } from '@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview';
|
||||
import { useVulnerabilitiesPreview } from '@kbn/cloud-security-posture/src/hooks/use_vulnerabilities_preview';
|
||||
import { sum } from 'lodash';
|
||||
import { DETECTION_RESPONSE_ALERTS_BY_STATUS_ID } from '../../../overview/components/detection_response/alerts_by_status/types';
|
||||
import { useAlertsByStatus } from '../../../overview/components/detection_response/alerts_by_status/use_alerts_by_status';
|
||||
import { useRefetchQueryById } from '../../../entity_analytics/api/hooks/use_refetch_query_by_id';
|
||||
import { RISK_INPUTS_TAB_QUERY_ID } from '../../../entity_analytics/components/entity_details_flyout/tabs/risk_inputs/risk_inputs_tab';
|
||||
import type { Refetch } from '../../../common/types';
|
||||
|
@ -35,6 +37,7 @@ import { useObservedHost } from './hooks/use_observed_host';
|
|||
import { HostDetailsPanelKey } from '../host_details_left';
|
||||
import { EntityDetailsLeftPanelTab } from '../shared/components/left_panel/left_panel_header';
|
||||
import { HostPreviewPanelFooter } from '../host_preview/footer';
|
||||
import { useSignalIndex } from '../../../detections/containers/detection_engine/alerts/use_signal_index';
|
||||
import { EntityEventTypes } from '../../../common/lib/telemetry';
|
||||
|
||||
export interface HostPanelProps extends Record<string, unknown> {
|
||||
|
@ -120,6 +123,21 @@ export const HostPanel = ({
|
|||
|
||||
const hasVulnerabilitiesFindings = sum(Object.values(vulnerabilitiesData?.count || {})) > 0;
|
||||
|
||||
const { signalIndexName } = useSignalIndex();
|
||||
|
||||
const entityFilter = useMemo(() => ({ field: 'host.name', value: hostName }), [hostName]);
|
||||
|
||||
const { items: alertsData } = useAlertsByStatus({
|
||||
entityFilter,
|
||||
signalIndexName,
|
||||
queryId: `${DETECTION_RESPONSE_ALERTS_BY_STATUS_ID}HOST_NAME_RIGHT`,
|
||||
to,
|
||||
from,
|
||||
});
|
||||
|
||||
const hasNonClosedAlerts =
|
||||
(alertsData?.acknowledged?.total || 0) + (alertsData?.open?.total || 0) > 0;
|
||||
|
||||
useQueryInspector({
|
||||
deleteQuery,
|
||||
inspect: inspectRiskScore,
|
||||
|
@ -144,6 +162,7 @@ export const HostPanel = ({
|
|||
path: tab ? { tab } : undefined,
|
||||
hasMisconfigurationFindings,
|
||||
hasVulnerabilitiesFindings,
|
||||
hasNonClosedAlerts,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
@ -155,6 +174,7 @@ export const HostPanel = ({
|
|||
isRiskScoreExist,
|
||||
hasMisconfigurationFindings,
|
||||
hasVulnerabilitiesFindings,
|
||||
hasNonClosedAlerts,
|
||||
]
|
||||
);
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ export enum EntityDetailsLeftPanelTab {
|
|||
export enum CspInsightLeftPanelSubTab {
|
||||
MISCONFIGURATIONS = 'misconfigurationTabId',
|
||||
VULNERABILITIES = 'vulnerabilitiesTabId',
|
||||
ALERTS = 'alertsTabId',
|
||||
}
|
||||
|
||||
export interface PanelHeaderProps {
|
||||
|
|
|
@ -29,6 +29,7 @@ export interface UserDetailsPanelProps extends Record<string, unknown> {
|
|||
path?: PanelPath;
|
||||
scopeId: string;
|
||||
hasMisconfigurationFindings?: boolean;
|
||||
hasNonClosedAlerts?: boolean;
|
||||
}
|
||||
export interface UserDetailsExpandableFlyoutProps extends FlyoutPanelProps {
|
||||
key: 'user_details';
|
||||
|
@ -42,6 +43,7 @@ export const UserDetailsPanel = ({
|
|||
path,
|
||||
scopeId,
|
||||
hasMisconfigurationFindings,
|
||||
hasNonClosedAlerts,
|
||||
}: UserDetailsPanelProps) => {
|
||||
const managedUser = useManagedUser(user.name, user.email);
|
||||
const tabs = useTabs(
|
||||
|
@ -49,7 +51,8 @@ export const UserDetailsPanel = ({
|
|||
user.name,
|
||||
isRiskScoreExist,
|
||||
scopeId,
|
||||
hasMisconfigurationFindings
|
||||
hasMisconfigurationFindings,
|
||||
hasNonClosedAlerts
|
||||
);
|
||||
|
||||
const { selectedTabId, setSelectedTabId } = useSelectedTab(
|
||||
|
@ -57,7 +60,8 @@ export const UserDetailsPanel = ({
|
|||
user,
|
||||
tabs,
|
||||
path,
|
||||
hasMisconfigurationFindings
|
||||
hasMisconfigurationFindings,
|
||||
hasNonClosedAlerts
|
||||
);
|
||||
|
||||
if (managedUser.isLoading) return <FlyoutLoading />;
|
||||
|
@ -83,7 +87,8 @@ const useSelectedTab = (
|
|||
user: UserParam,
|
||||
tabs: LeftPanelTabsType,
|
||||
path: PanelPath | undefined,
|
||||
hasMisconfigurationFindings?: boolean
|
||||
hasMisconfigurationFindings?: boolean,
|
||||
hasNonClosedAlerts?: boolean
|
||||
) => {
|
||||
const { openLeftPanel } = useExpandableFlyoutApi();
|
||||
|
||||
|
@ -101,6 +106,7 @@ const useSelectedTab = (
|
|||
user,
|
||||
isRiskScoreExist,
|
||||
hasMisconfigurationFindings,
|
||||
hasNonClosedAlerts,
|
||||
path: {
|
||||
tab: tabId,
|
||||
},
|
||||
|
|
|
@ -30,7 +30,8 @@ export const useTabs = (
|
|||
name: string,
|
||||
isRiskScoreExist: boolean,
|
||||
scopeId: string,
|
||||
hasMisconfigurationFindings?: boolean
|
||||
hasMisconfigurationFindings?: boolean,
|
||||
hasNonClosedAlerts?: boolean
|
||||
): LeftPanelTabsType =>
|
||||
useMemo(() => {
|
||||
const tabs: LeftPanelTabsType = [];
|
||||
|
@ -55,12 +56,19 @@ export const useTabs = (
|
|||
tabs.push(getEntraTab(entraManagedUser));
|
||||
}
|
||||
|
||||
if (hasMisconfigurationFindings) {
|
||||
if (hasMisconfigurationFindings || hasNonClosedAlerts) {
|
||||
tabs.push(getInsightsInputTab({ name, fieldName: 'user.name' }));
|
||||
}
|
||||
|
||||
return tabs;
|
||||
}, [hasMisconfigurationFindings, isRiskScoreExist, managedUser, name, scopeId]);
|
||||
}, [
|
||||
hasMisconfigurationFindings,
|
||||
hasNonClosedAlerts,
|
||||
isRiskScoreExist,
|
||||
managedUser,
|
||||
name,
|
||||
scopeId,
|
||||
]);
|
||||
|
||||
const getOktaTab = (oktaManagedUser: ManagedUserHit) => ({
|
||||
id: EntityDetailsLeftPanelTab.OKTA,
|
||||
|
|
|
@ -33,6 +33,9 @@ import { UserDetailsPanelKey } from '../user_details_left';
|
|||
import { useObservedUser } from './hooks/use_observed_user';
|
||||
import { EntityDetailsLeftPanelTab } from '../shared/components/left_panel/left_panel_header';
|
||||
import { UserPreviewPanelFooter } from '../user_preview/footer';
|
||||
import { useSignalIndex } from '../../../detections/containers/detection_engine/alerts/use_signal_index';
|
||||
import { useAlertsByStatus } from '../../../overview/components/detection_response/alerts_by_status/use_alerts_by_status';
|
||||
import { DETECTION_RESPONSE_ALERTS_BY_STATUS_ID } from '../../../overview/components/detection_response/alerts_by_status/types';
|
||||
import { EntityEventTypes } from '../../../common/lib/telemetry';
|
||||
|
||||
export interface UserPanelProps extends Record<string, unknown> {
|
||||
|
@ -112,6 +115,21 @@ export const UserPanel = ({
|
|||
|
||||
const hasMisconfigurationFindings = passedFindings > 0 || failedFindings > 0;
|
||||
|
||||
const { signalIndexName } = useSignalIndex();
|
||||
|
||||
const entityFilter = useMemo(() => ({ field: 'user.name', value: userName }), [userName]);
|
||||
|
||||
const { items: alertsData } = useAlertsByStatus({
|
||||
entityFilter,
|
||||
signalIndexName,
|
||||
queryId: `${DETECTION_RESPONSE_ALERTS_BY_STATUS_ID}USER_NAME_RIGHT`,
|
||||
to,
|
||||
from,
|
||||
});
|
||||
|
||||
const hasNonClosedAlerts =
|
||||
(alertsData?.acknowledged?.total || 0) + (alertsData?.open?.total || 0) > 0;
|
||||
|
||||
useQueryInspector({
|
||||
deleteQuery,
|
||||
inspect,
|
||||
|
@ -139,6 +157,7 @@ export const UserPanel = ({
|
|||
},
|
||||
path: tab ? { tab } : undefined,
|
||||
hasMisconfigurationFindings,
|
||||
hasNonClosedAlerts,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
@ -150,6 +169,7 @@ export const UserPanel = ({
|
|||
userName,
|
||||
email,
|
||||
hasMisconfigurationFindings,
|
||||
hasNonClosedAlerts,
|
||||
]
|
||||
);
|
||||
const openPanelFirstTab = useCallback(
|
||||
|
@ -191,7 +211,8 @@ export const UserPanel = ({
|
|||
<>
|
||||
<FlyoutNavigation
|
||||
flyoutIsExpandable={
|
||||
!isPreviewMode && (hasUserDetailsData || hasMisconfigurationFindings)
|
||||
!isPreviewMode &&
|
||||
(hasUserDetailsData || hasMisconfigurationFindings || hasNonClosedAlerts)
|
||||
}
|
||||
expandDetails={openPanelFirstTab}
|
||||
/>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue