From f214b55704dd9c300be845530de956a27fd12bab Mon Sep 17 00:00:00 2001 From: Nikita Indik Date: Thu, 18 May 2023 15:38:03 +0200 Subject: [PATCH] [Security Solution] Fix error messages on Rule Details page taking too much space (#157271) ## Summary When a large "last execution" error message is displayed on Rule Details page it takes too much vertical space: https://user-images.githubusercontent.com/2946766/214444343-dde0d9cd-6bc7-4aca-a6cf-07891d206888.png This PR changes the error callout component. Error text is now displayed using EUI's TextBlock component. Users will see a scroll if error text takes too much vertical space. Now it's also possible to view the text fullscreen or copy it to clipboard. Screenshot 2023-05-10 at 16 16 40 --- Another addition is a button to expand a row in the Execution Log to view / copy a full error message. Screenshot 2023-05-10 at 16 15 16 ### Checklist - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [x] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [x] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [x] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [x] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### For maintainers - [x] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) **Known issue**: Copy button is not shown in Execution Log expandable row in Safari. Opened an issue in EUI: https://github.com/elastic/eui/issues/6770 --- .../execution_log_columns.tsx | 85 +++++++++++++++---- .../execution_log_table.tsx | 63 ++++++++++++-- .../execution_log_table/translations.ts | 28 ++++++ .../rule_status_failed_callout.tsx | 45 +++++++--- 4 files changed, 184 insertions(+), 37 deletions(-) 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 610277ddcd076..f67f538513b73 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 072d3b6f58174..0c65399792dfd 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 114faa77f871e..0b4e91c5934af 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 e2dc4a842303d..25a6ae0be5c9f 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} + + +
); };