diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/execution_log_columns.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/execution_log_columns.tsx index 610277ddcd0765..f67f538513b732 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/execution_log_columns.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/execution_log_columns.tsx @@ -6,8 +6,15 @@ */ import React from 'react'; +import { css } from '@emotion/react'; import type { EuiBasicTableColumn } from '@elastic/eui'; -import { EuiLink, EuiText } from '@elastic/eui'; +import { + EuiLink, + EuiText, + EuiButtonIcon, + EuiScreenReaderOnly, + RIGHT_ALIGNMENT, +} from '@elastic/eui'; import type { DocLinksStart } from '@kbn/core/public'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -25,6 +32,37 @@ import { RuleDurationFormat } from './rule_duration_format'; import * as i18n from './translations'; +type TableColumn = EuiBasicTableColumn; + +interface UseColumnsArgs { + toggleRowExpanded: (item: RuleExecutionResult) => void; + isRowExpanded: (item: RuleExecutionResult) => boolean; +} + +export const expanderColumn = ({ + toggleRowExpanded, + isRowExpanded, +}: UseColumnsArgs): TableColumn => { + return { + align: RIGHT_ALIGNMENT, + width: '40px', + isExpander: true, + name: ( + + {i18n.EXPAND_ROW} + + ), + render: (item: RuleExecutionResult) => + item.security_status === 'succeeded' ? null : ( + toggleRowExpanded(item)} + aria-label={isRowExpanded(item) ? i18n.COLLAPSE : i18n.EXPAND} + iconType={isRowExpanded(item) ? 'arrowUp' : 'arrowDown'} + /> + ), + }; +}; + export const EXECUTION_LOG_COLUMNS: Array> = [ { name: ( @@ -69,22 +107,39 @@ export const EXECUTION_LOG_COLUMNS: Array - ), - render: (value: string) => <>{value}, - sortable: false, - truncateText: false, - width: '35%', - }, ]; -export const GET_EXECUTION_LOG_METRICS_COLUMNS = ( +export const getMessageColumn = (width: string) => ({ + field: 'security_message', + name: ( + + ), + render: (value: string, record: RuleExecutionResult) => { + if (record.security_status === 'succeeded') { + return value; + } + + return ( +
+ {value} +
+ ); + }, + sortable: false, + width, +}); + +export const getExecutionLogMetricsColumns = ( docLinks: DocLinksStart ): Array> => [ { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/execution_log_table.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/execution_log_table.tsx index 072d3b6f581745..0c65399792dfdd 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/execution_log_table.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/execution_log_table.tsx @@ -20,6 +20,7 @@ import { EuiSwitch, EuiBasicTable, EuiButton, + EuiDescriptionList, } from '@elastic/eui'; import type { Filter, Query } from '@kbn/es-query'; @@ -60,8 +61,15 @@ import { isAbsoluteTimeRange, isRelativeTimeRange } from '../../../../../common/ import { SourcererScopeName } from '../../../../../common/store/sourcerer/model'; import { useExecutionResults } from '../../../../rule_monitoring'; import { useRuleDetailsContext } from '../rule_details_context'; +import { useExpandableRows } from '../../../../rule_monitoring/components/basic/tables/use_expandable_rows'; +import { TextBlock } from '../../../../rule_monitoring/components/basic/text/text_block'; import * as i18n from './translations'; -import { EXECUTION_LOG_COLUMNS, GET_EXECUTION_LOG_METRICS_COLUMNS } from './execution_log_columns'; +import { + EXECUTION_LOG_COLUMNS, + getMessageColumn, + getExecutionLogMetricsColumns, + expanderColumn, +} from './execution_log_columns'; import { ExecutionLogSearchBar } from './execution_log_search_bar'; const EXECUTION_UUID_FIELD_NAME = 'kibana.alert.rule.execution.uuid'; @@ -361,7 +369,7 @@ const ExecutionLogTableComponent: React.FC = ({ { field: EXECUTION_UUID_FIELD_NAME, name: i18n.COLUMN_ACTIONS, - width: '5%', + width: '64px', actions: [ { name: 'Edit', @@ -386,14 +394,50 @@ const ExecutionLogTableComponent: React.FC = ({ [onFilterByExecutionIdCallback] ); - const executionLogColumns = useMemo( - () => - showMetricColumns - ? [...EXECUTION_LOG_COLUMNS, ...GET_EXECUTION_LOG_METRICS_COLUMNS(docLinks), ...actions] - : [...EXECUTION_LOG_COLUMNS, ...actions], - [actions, docLinks, showMetricColumns] + const getItemId = useCallback((item: RuleExecutionResult): string => { + return `${item.execution_uuid}`; + }, []); + + const renderExpandedItem = useCallback( + (item: RuleExecutionResult) => ( + , + }, + ]} + /> + ), + [] ); + const rows = useExpandableRows({ + getItemId, + renderItem: renderExpandedItem, + }); + + const executionLogColumns = useMemo(() => { + const columns = [...EXECUTION_LOG_COLUMNS]; + + if (showMetricColumns) { + columns.push(getMessageColumn('20%'), ...getExecutionLogMetricsColumns(docLinks)); + } else { + columns.push(getMessageColumn('50%')); + } + + columns.push( + ...actions, + expanderColumn({ + toggleRowExpanded: rows.toggleRowExpanded, + isRowExpanded: rows.isRowExpanded, + }) + ); + + return columns; + }, [actions, docLinks, showMetricColumns, rows.toggleRowExpanded, rows.isRowExpanded]); + return ( {/* Filter bar */} @@ -478,6 +522,9 @@ const ExecutionLogTableComponent: React.FC = ({ sorting={sorting} pagination={pagination} onChange={onTableChangeCallback} + itemId={getItemId} + itemIdToExpandedRowMap={rows.itemIdToExpandedRowMap} + isExpandable={true} /> ); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/translations.ts index 114faa77f871e1..0b4e91c5934af8 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/translations.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/translations.ts @@ -229,3 +229,31 @@ export const GREATER_THAN_YEAR = i18n.translate( defaultMessage: '> 1 Year', } ); + +export const ROW_DETAILS_MESSAGE = i18n.translate( + 'xpack.securitySolution.detectionEngine.ruleDetails.ruleExecutionLog.fullMessage', + { + defaultMessage: 'Full message', + } +); + +export const EXPAND_ROW = i18n.translate( + 'xpack.securitySolution.detectionEngine.ruleDetails.ruleExecutionLog.expandRow', + { + defaultMessage: 'Expand rows', + } +); + +export const EXPAND = i18n.translate( + 'xpack.securitySolution.detectionEngine.ruleDetails.ruleExecutionLog.expand', + { + defaultMessage: 'Expand', + } +); + +export const COLLAPSE = i18n.translate( + 'xpack.securitySolution.detectionEngine.ruleDetails.ruleExecutionLog.collapse', + { + defaultMessage: 'Collapse', + } +); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_execution_status/rule_status_failed_callout.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_execution_status/rule_status_failed_callout.tsx index e2dc4a842303d6..25a6ae0be5c9f5 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_execution_status/rule_status_failed_callout.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_execution_status/rule_status_failed_callout.tsx @@ -7,7 +7,8 @@ import React from 'react'; -import { EuiCallOut } from '@elastic/eui'; +import { EuiCallOut, EuiCodeBlock } from '@elastic/eui'; + import { FormattedDate } from '../../../../common/components/formatted_date'; import { RuleExecutionStatus } from '../../../../../common/detection_engine/rule_monitoring'; @@ -30,20 +31,36 @@ const RuleStatusFailedCallOutComponent: React.FC = } return ( - - {title} - - } - color={color} - iconType="warning" - data-test-subj="ruleStatusFailedCallOut" +
- {message.split('\n').map((line) => ( -

{line}

- ))} - + + {title} + + } + color={color} + iconType="warning" + data-test-subj="ruleStatusFailedCallOut" + > + + {message} + + +
); };