mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Cloud Security] Filters for Contextual Flyout Datagrid (#201708)
## Summary https://github.com/user-attachments/assets/59ace35f-62b8-4c08-bf2c-eed200db791d This PR is for adding Filters for Contextual Flytout Datagrid
This commit is contained in:
parent
08c3bc217f
commit
2f62cdebfc
17 changed files with 480 additions and 78 deletions
|
@ -47,3 +47,8 @@ export const VULNERABILITIES_SEVERITY: Record<VulnSeverity, VulnSeverity> = {
|
|||
CRITICAL: 'CRITICAL',
|
||||
UNKNOWN: 'UNKNOWN',
|
||||
};
|
||||
|
||||
export const MISCONFIGURATION_STATUS: Record<string, string> = {
|
||||
PASSED: 'passed',
|
||||
FAILED: 'failed',
|
||||
};
|
||||
|
|
|
@ -28,7 +28,9 @@ export * from './constants';
|
|||
export {
|
||||
extractErrorMessage,
|
||||
buildMutedRulesFilter,
|
||||
buildEntityFlyoutPreviewQuery,
|
||||
buildGenericEntityFlyoutPreviewQuery,
|
||||
buildMisconfigurationEntityFlyoutPreviewQuery,
|
||||
buildVulnerabilityEntityFlyoutPreviewQuery,
|
||||
} from './utils/helpers';
|
||||
export { getAbbreviatedNumber } from './utils/get_abbreviated_number';
|
||||
export { UiMetricService } from './utils/ui_metrics';
|
||||
|
|
|
@ -9,8 +9,10 @@ import {
|
|||
extractErrorMessage,
|
||||
defaultErrorMessage,
|
||||
buildMutedRulesFilter,
|
||||
buildEntityFlyoutPreviewQuery,
|
||||
buildEntityAlertsQuery,
|
||||
buildGenericEntityFlyoutPreviewQuery,
|
||||
buildMisconfigurationEntityFlyoutPreviewQuery,
|
||||
buildVulnerabilityEntityFlyoutPreviewQuery,
|
||||
} from './helpers';
|
||||
|
||||
const fallbackMessage = 'thisIsAFallBackMessage';
|
||||
|
@ -145,7 +147,7 @@ describe('test helper methods', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('buildEntityFlyoutPreviewQueryTest', () => {
|
||||
describe('buildGenericEntityFlyoutPreviewQuery', () => {
|
||||
it('should return the correct query when given field and query', () => {
|
||||
const field = 'host.name';
|
||||
const query = 'exampleHost';
|
||||
|
@ -162,10 +164,10 @@ describe('test helper methods', () => {
|
|||
},
|
||||
};
|
||||
|
||||
expect(buildEntityFlyoutPreviewQuery(field, query)).toEqual(expectedQuery);
|
||||
expect(buildGenericEntityFlyoutPreviewQuery(field, query)).toEqual(expectedQuery);
|
||||
});
|
||||
|
||||
it('should return the correct query when given field and empty query', () => {
|
||||
it('should return the correct query when given field and empty query and empty status', () => {
|
||||
const field = 'host.name';
|
||||
const expectedQuery = {
|
||||
bool: {
|
||||
|
@ -180,12 +182,143 @@ describe('test helper methods', () => {
|
|||
},
|
||||
};
|
||||
|
||||
expect(buildEntityFlyoutPreviewQuery(field)).toEqual(expectedQuery);
|
||||
expect(buildGenericEntityFlyoutPreviewQuery(field)).toEqual(expectedQuery);
|
||||
});
|
||||
|
||||
it('should return the correct query when given field and queryValue and status but empty queryField', () => {
|
||||
const field = 'host.name';
|
||||
const query = 'exampleHost';
|
||||
const status = 'pass';
|
||||
const expectedQuery = {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
bool: {
|
||||
should: [{ term: { 'host.name': 'exampleHost' } }],
|
||||
minimum_should_match: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
expect(buildGenericEntityFlyoutPreviewQuery(field, query, status)).toEqual(expectedQuery);
|
||||
});
|
||||
|
||||
it('should return the correct query when given field and queryValue and queryField but empty status', () => {
|
||||
const field = 'host.name';
|
||||
const query = 'exampleHost';
|
||||
const emptyStatus = undefined;
|
||||
const queryField = 'some.field';
|
||||
const expectedQuery = {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
bool: {
|
||||
should: [{ term: { 'host.name': 'exampleHost' } }],
|
||||
minimum_should_match: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
expect(buildGenericEntityFlyoutPreviewQuery(field, query, emptyStatus, queryField)).toEqual(
|
||||
expectedQuery
|
||||
);
|
||||
});
|
||||
|
||||
it('should return the correct query when given all the parameters', () => {
|
||||
const field = 'host.name';
|
||||
const query = 'exampleHost';
|
||||
const emptyStatus = 'some.status';
|
||||
const queryField = 'some.field';
|
||||
const expectedQuery = {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
bool: {
|
||||
should: [{ term: { 'host.name': 'exampleHost' } }],
|
||||
minimum_should_match: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
bool: {
|
||||
should: [{ term: { 'some.field': 'some.status' } }],
|
||||
minimum_should_match: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
expect(buildGenericEntityFlyoutPreviewQuery(field, query, emptyStatus, queryField)).toEqual(
|
||||
expectedQuery
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildMisconfigurationEntityFlyoutPreviewQuery', () => {
|
||||
it('should return the correct query when given field, queryValue, status and queryType Misconfiguration', () => {
|
||||
const field = 'host.name';
|
||||
const queryValue = 'exampleHost';
|
||||
const status = 'pass';
|
||||
const expectedQuery = {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
bool: {
|
||||
should: [{ term: { 'host.name': 'exampleHost' } }],
|
||||
minimum_should_match: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
bool: {
|
||||
should: [{ term: { 'result.evaluation': 'pass' } }],
|
||||
minimum_should_match: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
expect(buildMisconfigurationEntityFlyoutPreviewQuery(field, queryValue, status)).toEqual(
|
||||
expectedQuery
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('buildVulnerabilityEntityFlyoutPreviewQuery', () => {
|
||||
it('should return the correct query when given field, queryValue, status and queryType Vulnerability', () => {
|
||||
const field = 'host.name';
|
||||
const queryValue = 'exampleHost';
|
||||
const status = 'low';
|
||||
const expectedQuery = {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
bool: {
|
||||
should: [{ term: { 'host.name': 'exampleHost' } }],
|
||||
minimum_should_match: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
bool: {
|
||||
should: [{ term: { 'vulnerability.severity': 'low' } }],
|
||||
minimum_should_match: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
expect(buildVulnerabilityEntityFlyoutPreviewQuery(field, queryValue, status)).toEqual(
|
||||
expectedQuery
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildEntityAlertsQuery', () => {
|
||||
const getExpectedAlertsQuery = (size?: number) => {
|
||||
const getExpectedAlertsQuery = (size?: number, severity?: string) => {
|
||||
return {
|
||||
size: size || 0,
|
||||
_source: false,
|
||||
|
@ -202,20 +335,30 @@ describe('test helper methods', () => {
|
|||
filter: [
|
||||
{
|
||||
bool: {
|
||||
must: [],
|
||||
filter: [
|
||||
should: [
|
||||
{
|
||||
match_phrase: {
|
||||
'host.name': {
|
||||
query: 'exampleHost',
|
||||
},
|
||||
term: {
|
||||
'host.name': 'exampleHost',
|
||||
},
|
||||
},
|
||||
],
|
||||
should: [],
|
||||
must_not: [],
|
||||
minimum_should_match: 1,
|
||||
},
|
||||
},
|
||||
severity
|
||||
? {
|
||||
bool: {
|
||||
should: [
|
||||
{
|
||||
term: {
|
||||
'kibana.alert.severity': 'low',
|
||||
},
|
||||
},
|
||||
],
|
||||
minimum_should_match: 1,
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
|
@ -229,7 +372,7 @@ describe('test helper methods', () => {
|
|||
'kibana.alert.workflow_status': ['open', 'acknowledged'],
|
||||
},
|
||||
},
|
||||
],
|
||||
].filter(Boolean),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -256,5 +399,18 @@ describe('test helper methods', () => {
|
|||
|
||||
expect(buildEntityAlertsQuery(field, to, from, query)).toEqual(getExpectedAlertsQuery(size));
|
||||
});
|
||||
|
||||
it('should return the correct query when given severity query', () => {
|
||||
const field = 'host.name';
|
||||
const query = 'exampleHost';
|
||||
const to = 'Tomorrow';
|
||||
const from = 'Today';
|
||||
const size = undefined;
|
||||
const severity = 'low';
|
||||
|
||||
expect(buildEntityAlertsQuery(field, to, from, query, size, severity)).toEqual(
|
||||
getExpectedAlertsQuery(size, 'low')
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -43,7 +43,12 @@ export const buildMutedRulesFilter = (
|
|||
return mutedRulesFilterQuery;
|
||||
};
|
||||
|
||||
export const buildEntityFlyoutPreviewQuery = (field: string, queryValue?: string) => {
|
||||
export const buildGenericEntityFlyoutPreviewQuery = (
|
||||
field: string,
|
||||
queryValue?: string,
|
||||
status?: string,
|
||||
queryField?: string
|
||||
) => {
|
||||
return {
|
||||
bool: {
|
||||
filter: [
|
||||
|
@ -59,17 +64,52 @@ export const buildEntityFlyoutPreviewQuery = (field: string, queryValue?: string
|
|||
minimum_should_match: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
status && queryField
|
||||
? {
|
||||
bool: {
|
||||
should: [
|
||||
{
|
||||
term: {
|
||||
[queryField]: status,
|
||||
},
|
||||
},
|
||||
],
|
||||
minimum_should_match: 1,
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
].filter(Boolean),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
// Higher-order function for Misconfiguration
|
||||
export const buildMisconfigurationEntityFlyoutPreviewQuery = (
|
||||
field: string,
|
||||
queryValue?: string,
|
||||
status?: string
|
||||
) => {
|
||||
const queryField = 'result.evaluation';
|
||||
return buildGenericEntityFlyoutPreviewQuery(field, queryValue, status, queryField);
|
||||
};
|
||||
|
||||
// Higher-order function for Vulnerability
|
||||
export const buildVulnerabilityEntityFlyoutPreviewQuery = (
|
||||
field: string,
|
||||
queryValue?: string,
|
||||
status?: string
|
||||
) => {
|
||||
const queryField = 'vulnerability.severity';
|
||||
return buildGenericEntityFlyoutPreviewQuery(field, queryValue, status, queryField);
|
||||
};
|
||||
|
||||
export const buildEntityAlertsQuery = (
|
||||
field: string,
|
||||
to: string,
|
||||
from: string,
|
||||
queryValue?: string,
|
||||
size?: number
|
||||
size?: number,
|
||||
severity?: string
|
||||
) => {
|
||||
return {
|
||||
size: size || 0,
|
||||
|
@ -87,20 +127,30 @@ export const buildEntityAlertsQuery = (
|
|||
filter: [
|
||||
{
|
||||
bool: {
|
||||
must: [],
|
||||
filter: [
|
||||
should: [
|
||||
{
|
||||
match_phrase: {
|
||||
[field]: {
|
||||
query: queryValue,
|
||||
},
|
||||
term: {
|
||||
[field]: `${queryValue || ''}`,
|
||||
},
|
||||
},
|
||||
],
|
||||
should: [],
|
||||
must_not: [],
|
||||
minimum_should_match: 1,
|
||||
},
|
||||
},
|
||||
severity
|
||||
? {
|
||||
bool: {
|
||||
should: [
|
||||
{
|
||||
term: {
|
||||
'kibana.alert.severity': severity,
|
||||
},
|
||||
},
|
||||
],
|
||||
minimum_should_match: 1,
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
|
@ -114,7 +164,7 @@ export const buildEntityAlertsQuery = (
|
|||
'kibana.alert.workflow_status': ['open', 'acknowledged'],
|
||||
},
|
||||
},
|
||||
],
|
||||
].filter(Boolean),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -11,3 +11,6 @@ export const statusColors = {
|
|||
failed: euiThemeVars.euiColorVis9,
|
||||
unknown: euiThemeVars.euiColorLightShade,
|
||||
};
|
||||
|
||||
export const HOST_NAME = 'host.name';
|
||||
export const USER_NAME = 'user.name';
|
||||
|
|
|
@ -5,12 +5,12 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { buildEntityFlyoutPreviewQuery } from '@kbn/cloud-security-posture-common';
|
||||
import { buildGenericEntityFlyoutPreviewQuery } from '@kbn/cloud-security-posture-common';
|
||||
import { useMisconfigurationPreview } from './use_misconfiguration_preview';
|
||||
|
||||
export const useHasMisconfigurations = (field: 'host.name' | 'user.name', value: string) => {
|
||||
const { data } = useMisconfigurationPreview({
|
||||
query: buildEntityFlyoutPreviewQuery(field, value),
|
||||
query: buildGenericEntityFlyoutPreviewQuery(field, value),
|
||||
sort: [],
|
||||
enabled: true,
|
||||
pageSize: 1,
|
||||
|
|
|
@ -5,13 +5,13 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { buildEntityFlyoutPreviewQuery } from '@kbn/cloud-security-posture-common';
|
||||
import { buildGenericEntityFlyoutPreviewQuery } from '@kbn/cloud-security-posture-common';
|
||||
import { useVulnerabilitiesPreview } from './use_vulnerabilities_preview';
|
||||
import { hasVulnerabilitiesData } from '../utils/vulnerability_helpers';
|
||||
|
||||
export const useHasVulnerabilities = (field: 'host.name' | 'user.name', value: string) => {
|
||||
const { data: vulnerabilitiesData } = useVulnerabilitiesPreview({
|
||||
query: buildEntityFlyoutPreviewQuery(field, value),
|
||||
query: buildGenericEntityFlyoutPreviewQuery(field, value),
|
||||
sort: [],
|
||||
enabled: true,
|
||||
pageSize: 1,
|
||||
|
|
|
@ -58,7 +58,7 @@ export interface CspClientPluginStartDeps {
|
|||
export interface CspBaseEsQuery {
|
||||
query?: {
|
||||
bool: {
|
||||
filter: estypes.QueryDslQueryContainer[];
|
||||
filter: Array<estypes.QueryDslQueryContainer | undefined> | undefined;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import { getVulnerabilityStats } from './vulnerability_helpers';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
describe('getVulnerabilitiesAggregationCount', () => {
|
||||
const mockFilterFunction = jest.fn();
|
||||
it('should return empty array when all severity count is 0', () => {
|
||||
const result = getVulnerabilityStats({ critical: 0, high: 0, medium: 0, low: 0, none: 0 });
|
||||
expect(result).toEqual([]);
|
||||
|
@ -17,8 +18,12 @@ describe('getVulnerabilitiesAggregationCount', () => {
|
|||
|
||||
it('should return stats for low, medium, high, and critical vulnerabilities', () => {
|
||||
const result = getVulnerabilityStats({ critical: 1, high: 2, medium: 3, low: 4, none: 5 });
|
||||
const resultWithoutFunctions = result.map((item) => {
|
||||
const { filter, reset, ...rest } = item;
|
||||
return rest;
|
||||
});
|
||||
|
||||
expect(result).toEqual([
|
||||
expect(resultWithoutFunctions).toEqual([
|
||||
{
|
||||
key: i18n.translate(
|
||||
'xpack.securitySolution.flyout.right.insights.vulnerabilities.noneVulnerabilitiesText',
|
||||
|
@ -28,6 +33,7 @@ describe('getVulnerabilitiesAggregationCount', () => {
|
|||
),
|
||||
count: 5,
|
||||
color: '#aaa',
|
||||
isCurrentFilter: false,
|
||||
},
|
||||
{
|
||||
key: i18n.translate(
|
||||
|
@ -38,6 +44,7 @@ describe('getVulnerabilitiesAggregationCount', () => {
|
|||
),
|
||||
count: 4,
|
||||
color: euiThemeVars.euiColorVis0,
|
||||
isCurrentFilter: false,
|
||||
},
|
||||
{
|
||||
key: i18n.translate(
|
||||
|
@ -48,6 +55,7 @@ describe('getVulnerabilitiesAggregationCount', () => {
|
|||
),
|
||||
count: 3,
|
||||
color: euiThemeVars.euiColorVis5_behindText,
|
||||
isCurrentFilter: false,
|
||||
},
|
||||
{
|
||||
key: i18n.translate(
|
||||
|
@ -58,6 +66,7 @@ describe('getVulnerabilitiesAggregationCount', () => {
|
|||
),
|
||||
count: 2,
|
||||
color: euiThemeVars.euiColorVis9_behindText,
|
||||
isCurrentFilter: false,
|
||||
},
|
||||
{
|
||||
key: i18n.translate(
|
||||
|
@ -68,7 +77,43 @@ describe('getVulnerabilitiesAggregationCount', () => {
|
|||
),
|
||||
count: 1,
|
||||
color: euiThemeVars.euiColorDanger,
|
||||
isCurrentFilter: false,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return correct stats with correct onClick functions', () => {
|
||||
const result = getVulnerabilityStats(
|
||||
{ critical: 1, high: 2, medium: 3, low: 4, none: 5 },
|
||||
mockFilterFunction
|
||||
);
|
||||
const event = { stopPropagation: jest.fn() } as unknown as React.MouseEvent<
|
||||
SVGElement,
|
||||
MouseEvent
|
||||
>;
|
||||
result[1].filter?.();
|
||||
expect(mockFilterFunction).toHaveBeenCalledWith('LOW');
|
||||
result[1].reset?.(event);
|
||||
expect(mockFilterFunction).toHaveBeenCalledWith('');
|
||||
});
|
||||
|
||||
it('should identify correct currentFilter', () => {
|
||||
const currentFilter = 'LOW';
|
||||
const result = getVulnerabilityStats(
|
||||
{ critical: 1, high: 2, medium: 3, low: 4, none: 5 },
|
||||
mockFilterFunction,
|
||||
currentFilter
|
||||
);
|
||||
|
||||
// Make sure that Low is set to Current Filter
|
||||
expect(result[1].isCurrentFilter).toBe(true);
|
||||
expect(result[1].key).toBe('Low');
|
||||
|
||||
// Make sure only Low is set as Current Filter and no other severity
|
||||
result.forEach((item) => {
|
||||
if (item.key !== 'Low') {
|
||||
expect(item.isCurrentFilter).toBe(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -14,6 +14,9 @@ interface VulnerabilitiesDistributionBarProps {
|
|||
key: string;
|
||||
count: number;
|
||||
color: string;
|
||||
isCurrentFilter?: boolean;
|
||||
filter?: () => void;
|
||||
reset?: (event: any) => void;
|
||||
}
|
||||
|
||||
interface VulnerabilityCounts {
|
||||
|
@ -30,7 +33,9 @@ export const hasVulnerabilitiesData = (counts: VulnerabilityCounts): boolean =>
|
|||
};
|
||||
|
||||
export const getVulnerabilityStats = (
|
||||
counts: VulnerabilityCounts
|
||||
counts: VulnerabilityCounts,
|
||||
filterFunction?: (filter: string) => void,
|
||||
currentFilter?: string
|
||||
): VulnerabilitiesDistributionBarProps[] => {
|
||||
const vulnerabilityStats: VulnerabilitiesDistributionBarProps[] = [];
|
||||
|
||||
|
@ -50,6 +55,14 @@ export const getVulnerabilityStats = (
|
|||
),
|
||||
count: counts.none,
|
||||
color: getSeverityStatusColor(VULNERABILITIES_SEVERITY.UNKNOWN),
|
||||
filter: () => {
|
||||
filterFunction?.(VULNERABILITIES_SEVERITY.UNKNOWN);
|
||||
},
|
||||
isCurrentFilter: currentFilter === VULNERABILITIES_SEVERITY.UNKNOWN,
|
||||
reset: (event: React.MouseEvent<SVGElement, MouseEvent>) => {
|
||||
filterFunction?.('');
|
||||
event?.stopPropagation();
|
||||
},
|
||||
});
|
||||
if (counts.low > 0)
|
||||
vulnerabilityStats.push({
|
||||
|
@ -61,6 +74,14 @@ export const getVulnerabilityStats = (
|
|||
),
|
||||
count: counts.low,
|
||||
color: getSeverityStatusColor(VULNERABILITIES_SEVERITY.LOW),
|
||||
filter: () => {
|
||||
filterFunction?.(VULNERABILITIES_SEVERITY.LOW);
|
||||
},
|
||||
isCurrentFilter: currentFilter === VULNERABILITIES_SEVERITY.LOW,
|
||||
reset: (event: React.MouseEvent<SVGElement, MouseEvent>) => {
|
||||
filterFunction?.('');
|
||||
event?.stopPropagation();
|
||||
},
|
||||
});
|
||||
|
||||
if (counts.medium > 0)
|
||||
|
@ -73,6 +94,14 @@ export const getVulnerabilityStats = (
|
|||
),
|
||||
count: counts.medium,
|
||||
color: getSeverityStatusColor(VULNERABILITIES_SEVERITY.MEDIUM),
|
||||
filter: () => {
|
||||
filterFunction?.(VULNERABILITIES_SEVERITY.MEDIUM);
|
||||
},
|
||||
isCurrentFilter: currentFilter === VULNERABILITIES_SEVERITY.MEDIUM,
|
||||
reset: (event: React.MouseEvent<SVGElement, MouseEvent>) => {
|
||||
filterFunction?.('');
|
||||
event?.stopPropagation();
|
||||
},
|
||||
});
|
||||
if (counts.high > 0)
|
||||
vulnerabilityStats.push({
|
||||
|
@ -84,6 +113,14 @@ export const getVulnerabilityStats = (
|
|||
),
|
||||
count: counts.high,
|
||||
color: getSeverityStatusColor(VULNERABILITIES_SEVERITY.HIGH),
|
||||
filter: () => {
|
||||
filterFunction?.(VULNERABILITIES_SEVERITY.HIGH);
|
||||
},
|
||||
isCurrentFilter: currentFilter === VULNERABILITIES_SEVERITY.HIGH,
|
||||
reset: (event: React.MouseEvent<SVGElement, MouseEvent>) => {
|
||||
filterFunction?.('');
|
||||
event?.stopPropagation();
|
||||
},
|
||||
});
|
||||
if (counts.critical > 0)
|
||||
vulnerabilityStats.push({
|
||||
|
@ -95,6 +132,14 @@ export const getVulnerabilityStats = (
|
|||
),
|
||||
count: counts.critical,
|
||||
color: getSeverityStatusColor(VULNERABILITIES_SEVERITY.CRITICAL),
|
||||
filter: () => {
|
||||
filterFunction?.(VULNERABILITIES_SEVERITY.CRITICAL);
|
||||
},
|
||||
isCurrentFilter: currentFilter === VULNERABILITIES_SEVERITY.CRITICAL,
|
||||
reset: (event: React.MouseEvent<SVGElement, MouseEvent>) => {
|
||||
filterFunction?.('');
|
||||
event?.stopPropagation();
|
||||
},
|
||||
});
|
||||
|
||||
return vulnerabilityStats;
|
||||
|
|
|
@ -12,7 +12,15 @@ import { css } from '@emotion/react';
|
|||
/** DistributionBar component props */
|
||||
export interface DistributionBarProps {
|
||||
/** distribution data points */
|
||||
stats: Array<{ key: string; count: number; color: string; label?: React.ReactNode }>;
|
||||
stats: Array<{
|
||||
key: string;
|
||||
count: number;
|
||||
color: string;
|
||||
label?: React.ReactNode;
|
||||
isCurrentFilter?: boolean;
|
||||
filter?: () => void;
|
||||
reset?: (event: React.MouseEvent<SVGElement, MouseEvent>) => void;
|
||||
}>;
|
||||
/** hide the label above the bar at first render */
|
||||
hideLastTooltip?: boolean;
|
||||
/** data-test-subj used for querying the component in tests */
|
||||
|
@ -156,7 +164,19 @@ export const DistributionBar: React.FC<DistributionBarProps> = React.memo(functi
|
|||
const prettyNumber = numeral(stat.count).format('0,0a');
|
||||
|
||||
return (
|
||||
<div key={stat.key} css={partStyle} data-test-subj={`${dataTestSubj}__part`}>
|
||||
<div
|
||||
key={stat.key}
|
||||
css={partStyle}
|
||||
data-test-subj={`${dataTestSubj}__part`}
|
||||
onClick={stat.filter}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
stat.filter?.();
|
||||
}
|
||||
}}
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
>
|
||||
<div css={styles.tooltip}>
|
||||
<EuiFlexGroup
|
||||
gutterSize={'none'}
|
||||
|
@ -176,6 +196,11 @@ export const DistributionBar: React.FC<DistributionBarProps> = React.memo(functi
|
|||
<EuiIcon type={'dot'} size={'s'} color={stat.color} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>{stat.label ? stat.label : stat.key}</EuiFlexItem>
|
||||
{stat.isCurrentFilter ? (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon type="cross" size="m" onClick={stat.reset} />
|
||||
</EuiFlexItem>
|
||||
) : undefined}
|
||||
</EuiFlexGroup>
|
||||
</EuiBadge>
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -19,6 +19,8 @@ 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 type { AlertsByStatus } from '../../../overview/components/detection_response/alerts_by_status/types';
|
||||
import { DETECTION_RESPONSE_ALERTS_BY_STATUS_ID } from '../../../overview/components/detection_response/alerts_by_status/types';
|
||||
import {
|
||||
OPEN_IN_ALERTS_TITLE_HOSTNAME,
|
||||
OPEN_IN_ALERTS_TITLE_STATUS,
|
||||
|
@ -34,6 +36,7 @@ import { getSeverityColor } from '../../../detections/components/alerts_kpis/sev
|
|||
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';
|
||||
import { useNonClosedAlerts } from '../../hooks/use_non_closed_alerts';
|
||||
|
||||
type AlertSeverity = 'low' | 'medium' | 'high' | 'critical';
|
||||
|
||||
|
@ -68,6 +71,8 @@ export const AlertsDetailsTable = memo(
|
|||
);
|
||||
}, []);
|
||||
|
||||
const [currentFilter, setCurrentFilter] = useState<string>('');
|
||||
|
||||
const [pageIndex, setPageIndex] = useState(0);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
|
||||
|
@ -89,12 +94,46 @@ export const AlertsDetailsTable = memo(
|
|||
|
||||
const { to, from } = useGlobalTime();
|
||||
const { signalIndexName } = useSignalIndex();
|
||||
const { data } = useQueryAlerts({
|
||||
query: buildEntityAlertsQuery(field, to, from, value, 500),
|
||||
const { data, setQuery } = useQueryAlerts({
|
||||
query: buildEntityAlertsQuery(field, to, from, value, 500, ''),
|
||||
queryName: ALERTS_QUERY_NAMES.BY_RULE_BY_STATUS,
|
||||
indexName: signalIndexName,
|
||||
});
|
||||
|
||||
const { filteredAlertsData: alertsData } = useNonClosedAlerts({
|
||||
field,
|
||||
value,
|
||||
to,
|
||||
from,
|
||||
queryId: `${DETECTION_RESPONSE_ALERTS_BY_STATUS_ID}`,
|
||||
});
|
||||
|
||||
const severityMap = new Map<string, number>();
|
||||
(Object.keys(alertsData || {}) as AlertsByStatus[]).forEach((status) => {
|
||||
if (alertsData?.[status]?.severities) {
|
||||
alertsData?.[status]?.severities.forEach((severity) => {
|
||||
const currentSeverity = severityMap.get(severity.key) || 0;
|
||||
severityMap.set(severity.key, currentSeverity + severity.value);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const alertStats = Array.from(severityMap, ([key, count]) => ({
|
||||
key: capitalize(key),
|
||||
count,
|
||||
color: getSeverityColor(key),
|
||||
filter: () => {
|
||||
setCurrentFilter(key);
|
||||
setQuery(buildEntityAlertsQuery(field, to, from, value, 500, key));
|
||||
},
|
||||
isCurrentFilter: currentFilter === key,
|
||||
reset: (event: React.MouseEvent<SVGElement, MouseEvent>) => {
|
||||
setCurrentFilter('');
|
||||
setQuery(buildEntityAlertsQuery(field, to, from, value, 500, ''));
|
||||
event?.stopPropagation();
|
||||
},
|
||||
}));
|
||||
|
||||
const alertDataResults = (data?.hits?.hits as AlertsDetailsFields[])?.map(
|
||||
(item: AlertsDetailsFields) => {
|
||||
return {
|
||||
|
@ -108,19 +147,6 @@ export const AlertsDetailsTable = memo(
|
|||
}
|
||||
);
|
||||
|
||||
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 = {
|
||||
|
|
|
@ -10,8 +10,12 @@ import type { Criteria, EuiBasicTableColumn } from '@elastic/eui';
|
|||
import { EuiSpacer, EuiPanel, EuiText, EuiBasicTable, EuiIcon } from '@elastic/eui';
|
||||
import { useMisconfigurationFindings } from '@kbn/cloud-security-posture/src/hooks/use_misconfiguration_findings';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { CspFinding, CspFindingResult } from '@kbn/cloud-security-posture-common';
|
||||
import { buildEntityFlyoutPreviewQuery } from '@kbn/cloud-security-posture-common';
|
||||
import {
|
||||
MISCONFIGURATION_STATUS,
|
||||
type CspFinding,
|
||||
type CspFindingResult,
|
||||
buildMisconfigurationEntityFlyoutPreviewQuery,
|
||||
} from '@kbn/cloud-security-posture-common';
|
||||
import { euiThemeVars } from '@kbn/ui-theme';
|
||||
import { DistributionBar } from '@kbn/security-solution-distribution-bar';
|
||||
import type { CspBenchmarkRuleMetadata } from '@kbn/cloud-security-posture-common/schema/rules/latest';
|
||||
|
@ -25,11 +29,17 @@ import {
|
|||
import { METRIC_TYPE } from '@kbn/analytics';
|
||||
import { useGetNavigationUrlParams } from '@kbn/cloud-security-posture/src/hooks/use_get_navigation_url_params';
|
||||
import { SecurityPageName } from '@kbn/deeplinks-security';
|
||||
import { useHasMisconfigurations } from '@kbn/cloud-security-posture/src/hooks/use_has_misconfigurations';
|
||||
import { SecuritySolutionLinkAnchor } from '../../../common/components/links';
|
||||
|
||||
type MisconfigurationFindingDetailFields = Pick<CspFinding, 'result' | 'rule' | 'resource'>;
|
||||
|
||||
const getFindingsStats = (passedFindingsStats: number, failedFindingsStats: number) => {
|
||||
const getFindingsStats = (
|
||||
passedFindingsStats: number,
|
||||
failedFindingsStats: number,
|
||||
filterFunction: (filter: string) => void,
|
||||
currentFilter: string
|
||||
) => {
|
||||
if (passedFindingsStats === 0 && failedFindingsStats === 0) return [];
|
||||
return [
|
||||
{
|
||||
|
@ -41,6 +51,14 @@ const getFindingsStats = (passedFindingsStats: number, failedFindingsStats: numb
|
|||
),
|
||||
count: passedFindingsStats,
|
||||
color: euiThemeVars.euiColorSuccess,
|
||||
filter: () => {
|
||||
filterFunction(MISCONFIGURATION_STATUS.PASSED);
|
||||
},
|
||||
isCurrentFilter: currentFilter === MISCONFIGURATION_STATUS.PASSED,
|
||||
reset: (event: React.MouseEvent<SVGElement, MouseEvent>) => {
|
||||
filterFunction('');
|
||||
event?.stopPropagation();
|
||||
},
|
||||
},
|
||||
{
|
||||
key: i18n.translate(
|
||||
|
@ -51,6 +69,14 @@ const getFindingsStats = (passedFindingsStats: number, failedFindingsStats: numb
|
|||
),
|
||||
count: failedFindingsStats,
|
||||
color: euiThemeVars.euiColorVis9,
|
||||
filter: () => {
|
||||
filterFunction(MISCONFIGURATION_STATUS.FAILED);
|
||||
},
|
||||
isCurrentFilter: currentFilter === MISCONFIGURATION_STATUS.FAILED,
|
||||
reset: (event: React.MouseEvent<SVGElement, MouseEvent>) => {
|
||||
filterFunction('');
|
||||
event?.stopPropagation();
|
||||
},
|
||||
},
|
||||
];
|
||||
};
|
||||
|
@ -67,15 +93,16 @@ export const MisconfigurationFindingsDetailsTable = memo(
|
|||
);
|
||||
}, []);
|
||||
|
||||
const [currentFilter, setCurrentFilter] = useState<string>('');
|
||||
|
||||
const { data } = useMisconfigurationFindings({
|
||||
query: buildEntityFlyoutPreviewQuery(field, value),
|
||||
query: buildMisconfigurationEntityFlyoutPreviewQuery(field, value, currentFilter),
|
||||
sort: [],
|
||||
enabled: true,
|
||||
pageSize: 1,
|
||||
});
|
||||
|
||||
const passedFindings = data?.count.passed || 0;
|
||||
const failedFindings = data?.count.failed || 0;
|
||||
const { passedFindings, failedFindings } = useHasMisconfigurations(field, value);
|
||||
|
||||
const [pageIndex, setPageIndex] = useState(0);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
|
@ -129,6 +156,13 @@ export const MisconfigurationFindingsDetailsTable = memo(
|
|||
const linkWidth = 40;
|
||||
const resultWidth = 74;
|
||||
|
||||
const misconfgurationStats = getFindingsStats(
|
||||
passedFindings,
|
||||
failedFindings,
|
||||
setCurrentFilter,
|
||||
currentFilter
|
||||
);
|
||||
|
||||
const columns: Array<EuiBasicTableColumn<MisconfigurationFindingDetailFields>> = [
|
||||
{
|
||||
field: 'rule',
|
||||
|
@ -202,7 +236,7 @@ export const MisconfigurationFindingsDetailsTable = memo(
|
|||
<EuiIcon type={'popout'} />
|
||||
</SecuritySolutionLinkAnchor>
|
||||
<EuiSpacer size="xl" />
|
||||
<DistributionBar stats={getFindingsStats(passedFindings, failedFindings)} />
|
||||
<DistributionBar stats={misconfgurationStats} />
|
||||
<EuiSpacer size="l" />
|
||||
<EuiBasicTable
|
||||
items={pageOfItems || []}
|
||||
|
|
|
@ -9,8 +9,10 @@ import React, { memo, useEffect, useState } from 'react';
|
|||
import type { Criteria, EuiBasicTableColumn } from '@elastic/eui';
|
||||
import { EuiSpacer, EuiPanel, EuiText, EuiBasicTable, EuiIcon } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { VulnSeverity } from '@kbn/cloud-security-posture-common';
|
||||
import { buildEntityFlyoutPreviewQuery } from '@kbn/cloud-security-posture-common';
|
||||
import {
|
||||
buildVulnerabilityEntityFlyoutPreviewQuery,
|
||||
type VulnSeverity,
|
||||
} from '@kbn/cloud-security-posture-common';
|
||||
import { DistributionBar } from '@kbn/security-solution-distribution-bar';
|
||||
import { useVulnerabilitiesFindings } from '@kbn/cloud-security-posture/src/hooks/use_vulnerabilities_findings';
|
||||
import type {
|
||||
|
@ -30,6 +32,7 @@ import {
|
|||
import { METRIC_TYPE } from '@kbn/analytics';
|
||||
import { SecurityPageName } from '@kbn/deeplinks-security';
|
||||
import { useGetNavigationUrlParams } from '@kbn/cloud-security-posture/src/hooks/use_get_navigation_url_params';
|
||||
import { useHasVulnerabilities } from '@kbn/cloud-security-posture/src/hooks/use_has_vulnerabilities';
|
||||
import { SecuritySolutionLinkAnchor } from '../../../common/components/links';
|
||||
|
||||
type VulnerabilitiesFindingDetailFields = Pick<
|
||||
|
@ -52,14 +55,18 @@ export const VulnerabilitiesFindingsDetailsTable = memo(({ value }: { value: str
|
|||
);
|
||||
}, []);
|
||||
|
||||
const [currentFilter, setCurrentFilter] = useState<string>('');
|
||||
|
||||
const { data } = useVulnerabilitiesFindings({
|
||||
query: buildEntityFlyoutPreviewQuery('host.name', value),
|
||||
query: buildVulnerabilityEntityFlyoutPreviewQuery('host.name', value, currentFilter),
|
||||
sort: [],
|
||||
enabled: true,
|
||||
pageSize: 1,
|
||||
});
|
||||
|
||||
const { CRITICAL = 0, HIGH = 0, MEDIUM = 0, LOW = 0, NONE = 0 } = data?.count || {};
|
||||
const { counts } = useHasVulnerabilities('host.name', value);
|
||||
|
||||
const { critical = 0, high = 0, medium = 0, low = 0, none = 0 } = counts || {};
|
||||
|
||||
const [pageIndex, setPageIndex] = useState(0);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
|
@ -120,6 +127,18 @@ export const VulnerabilitiesFindingsDetailsTable = memo(({ value }: { value: str
|
|||
);
|
||||
};
|
||||
|
||||
const vulnerabilityStats = getVulnerabilityStats(
|
||||
{
|
||||
critical,
|
||||
high,
|
||||
medium,
|
||||
low,
|
||||
none,
|
||||
},
|
||||
setCurrentFilter,
|
||||
currentFilter
|
||||
);
|
||||
|
||||
const columns: Array<EuiBasicTableColumn<VulnerabilitiesFindingDetailFields>> = [
|
||||
{
|
||||
field: 'vulnerability',
|
||||
|
@ -220,15 +239,7 @@ export const VulnerabilitiesFindingsDetailsTable = memo(({ value }: { value: str
|
|||
<EuiIcon type={'popout'} />
|
||||
</SecuritySolutionLinkAnchor>
|
||||
<EuiSpacer size="xl" />
|
||||
<DistributionBar
|
||||
stats={getVulnerabilityStats({
|
||||
critical: CRITICAL,
|
||||
high: HIGH,
|
||||
medium: MEDIUM,
|
||||
low: LOW,
|
||||
none: NONE,
|
||||
})}
|
||||
/>
|
||||
<DistributionBar stats={vulnerabilityStats} />
|
||||
<EuiSpacer size="l" />
|
||||
<EuiBasicTable
|
||||
items={pageOfItems || []}
|
||||
|
|
|
@ -13,7 +13,7 @@ import { FormattedMessage } from '@kbn/i18n-react';
|
|||
import { DistributionBar } from '@kbn/security-solution-distribution-bar';
|
||||
import { useVulnerabilitiesPreview } from '@kbn/cloud-security-posture/src/hooks/use_vulnerabilities_preview';
|
||||
import {
|
||||
buildEntityFlyoutPreviewQuery,
|
||||
buildGenericEntityFlyoutPreviewQuery,
|
||||
getAbbreviatedNumber,
|
||||
} from '@kbn/cloud-security-posture-common';
|
||||
import { getVulnerabilityStats, hasVulnerabilitiesData } from '@kbn/cloud-security-posture';
|
||||
|
@ -73,7 +73,7 @@ export const VulnerabilitiesPreview = ({
|
|||
}, []);
|
||||
|
||||
const { data } = useVulnerabilitiesPreview({
|
||||
query: buildEntityFlyoutPreviewQuery(field, value),
|
||||
query: buildGenericEntityFlyoutPreviewQuery(field, value),
|
||||
sort: [],
|
||||
enabled: true,
|
||||
pageSize: 1,
|
||||
|
|
|
@ -10,7 +10,7 @@ import { EuiFlexItem, type EuiFlexGroupProps, useEuiTheme } from '@elastic/eui';
|
|||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { css } from '@emotion/css';
|
||||
import { useMisconfigurationPreview } from '@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview';
|
||||
import { buildEntityFlyoutPreviewQuery } from '@kbn/cloud-security-posture-common';
|
||||
import { buildGenericEntityFlyoutPreviewQuery } from '@kbn/cloud-security-posture-common';
|
||||
import {
|
||||
MISCONFIGURATION_INSIGHT,
|
||||
uiMetricService,
|
||||
|
@ -58,7 +58,7 @@ export const MisconfigurationsInsight: React.FC<MisconfigurationsInsightProps> =
|
|||
const { scopeId, isPreview } = useDocumentDetailsContext();
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const { data } = useMisconfigurationPreview({
|
||||
query: buildEntityFlyoutPreviewQuery(fieldName, name),
|
||||
query: buildGenericEntityFlyoutPreviewQuery(fieldName, name),
|
||||
sort: [],
|
||||
enabled: true,
|
||||
pageSize: 1,
|
||||
|
|
|
@ -10,7 +10,7 @@ import { EuiFlexItem, type EuiFlexGroupProps, useEuiTheme } from '@elastic/eui';
|
|||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { css } from '@emotion/css';
|
||||
import { useVulnerabilitiesPreview } from '@kbn/cloud-security-posture/src/hooks/use_vulnerabilities_preview';
|
||||
import { buildEntityFlyoutPreviewQuery } from '@kbn/cloud-security-posture-common';
|
||||
import { buildGenericEntityFlyoutPreviewQuery } from '@kbn/cloud-security-posture-common';
|
||||
import { getVulnerabilityStats, hasVulnerabilitiesData } from '@kbn/cloud-security-posture';
|
||||
import {
|
||||
uiMetricService,
|
||||
|
@ -53,7 +53,7 @@ export const VulnerabilitiesInsight: React.FC<VulnerabilitiesInsightProps> = ({
|
|||
const { scopeId, isPreview } = useDocumentDetailsContext();
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const { data } = useVulnerabilitiesPreview({
|
||||
query: buildEntityFlyoutPreviewQuery('host.name', hostName),
|
||||
query: buildGenericEntityFlyoutPreviewQuery('host.name', hostName),
|
||||
sort: [],
|
||||
enabled: true,
|
||||
pageSize: 1,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue