From 15c7bd019249654d0778ba29ac51f0e7e902d683 Mon Sep 17 00:00:00 2001 From: Garrett Spong Date: Thu, 7 Oct 2021 20:35:11 -0600 Subject: [PATCH] [Security Solution][Detections] Updates Indexing/Query Time columns in Rule Monitoring table to be SUM instead of MAX (#114023) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Updates the `Indexing Time` & `Query Time` columns in the `Rule Monitoring` table to be `SUM` instead of `MAX`, thus showing the total duration of indexing/querying phases within a Rule's execution rather than just the phase that took the longest. Also adds tooltips to columns for better understanding these metrics. ~Note: Wanted to add a link to documentation for `Last Gap` column, but cannot add links within `EuiToolTip` and didn't want to mis-align design of other column tooltips by introducing a popover. @elastic/security-design please advise on desired action or copy changes here -- thanks!~ 🙂 Update: As guided by design, changed `Last Gap` tooltip to be `EuiPopover` and added link to docs. ##### Indexing Time:

##### Query Time:

##### Last Gap:

### Checklist Delete any items that are not applicable to this PR. - [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/master/packages/kbn-i18n/README.md) --- ...-plugin-core-public.doclinksstart.links.md | 1 + .../public/doc_links/doc_links_service.ts | 17 +++--- src/core/public/public.api.md | 1 + .../detection_engine/rules/all/columns.tsx | 60 +++++++++++++++---- .../rules/all/popover_tooltip.tsx | 47 +++++++++++++++ .../rules/all/rules_tables.tsx | 5 +- .../detection_engine/rules/translations.ts | 19 ++++++ 7 files changed, 129 insertions(+), 21 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/popover_tooltip.tsx diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md index c8be6a439f35..fc7d2e976b57 100644 --- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md +++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md @@ -143,6 +143,7 @@ readonly links: { readonly ruleChangeLog: string; readonly detectionsReq: string; readonly networkMap: string; + readonly troubleshootGaps: string; }; readonly query: { readonly eql: string; diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index 221b586ac154..6c34693b6052 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -25,6 +25,7 @@ export class DocLinksService { const FLEET_DOCS = `${ELASTIC_WEBSITE_URL}guide/en/fleet/${DOC_LINK_VERSION}/`; const PLUGIN_DOCS = `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/plugins/${DOC_LINK_VERSION}/`; const APM_DOCS = `${ELASTIC_WEBSITE_URL}guide/en/apm/`; + const SECURITY_SOLUTION_DOCS = `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/`; return deepFreeze({ DOC_LINK_VERSION, @@ -223,13 +224,14 @@ export class DocLinksService { typesRemoval: `${ELASTICSEARCH_DOCS}removal-of-types.html`, }, siem: { - guide: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/index.html`, - gettingStarted: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/index.html`, - privileges: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/sec-requirements.html`, - ml: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/machine-learning.html`, - ruleChangeLog: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/prebuilt-rules-changelog.html`, - detectionsReq: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/detections-permissions-section.html`, - networkMap: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/conf-map-ui.html`, + guide: `${SECURITY_SOLUTION_DOCS}index.html`, + gettingStarted: `${SECURITY_SOLUTION_DOCS}index.html`, + privileges: `${SECURITY_SOLUTION_DOCS}sec-requirements.html`, + ml: `${SECURITY_SOLUTION_DOCS}machine-learning.html`, + ruleChangeLog: `${SECURITY_SOLUTION_DOCS}prebuilt-rules-changelog.html`, + detectionsReq: `${SECURITY_SOLUTION_DOCS}detections-permissions-section.html`, + networkMap: `${SECURITY_SOLUTION_DOCS}conf-map-ui.html`, + troubleshootGaps: `${SECURITY_SOLUTION_DOCS}alerts-ui-monitor.html#troubleshoot-gaps`, }, query: { eql: `${ELASTICSEARCH_DOCS}eql.html`, @@ -636,6 +638,7 @@ export interface DocLinksStart { readonly ruleChangeLog: string; readonly detectionsReq: string; readonly networkMap: string; + readonly troubleshootGaps: string; }; readonly query: { readonly eql: string; diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index b3474a669e70..7170c43f36e7 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -612,6 +612,7 @@ export interface DocLinksStart { readonly ruleChangeLog: string; readonly detectionsReq: string; readonly networkMap: string; + readonly troubleshootGaps: string; }; readonly query: { readonly eql: string; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx index ad5a85a03464..6b05ee6403db 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx @@ -11,9 +11,12 @@ import { EuiText, EuiHealth, EuiToolTip, + EuiIcon, + EuiLink, } from '@elastic/eui'; -import { FormattedRelative } from '@kbn/i18n/react'; +import { FormattedMessage, FormattedRelative } from '@kbn/i18n/react'; import * as H from 'history'; +import { sum } from 'lodash'; import React, { Dispatch } from 'react'; import { isMlRule } from '../../../../../../common/machine_learning/helpers'; @@ -36,10 +39,11 @@ import { RulesTableAction } from '../../../../containers/detection_engine/rules/ import { LocalizedDateTooltip } from '../../../../../common/components/localized_date_tooltip'; import { LinkAnchor } from '../../../../../common/components/links'; import { getToolTipContent, canEditRuleWithActions } from '../../../../../common/utils/privileges'; +import { PopoverTooltip } from './popover_tooltip'; import { TagsDisplay } from './tag_display'; import { getRuleStatusText } from '../../../../../../common/detection_engine/utils'; import { APP_ID, SecurityPageName } from '../../../../../../common/constants'; -import { NavigateToAppOptions } from '../../../../../../../../../src/core/public'; +import { DocLinksStart, NavigateToAppOptions } from '../../../../../../../../../src/core/public'; export const getActions = ( dispatch: React.Dispatch, @@ -312,7 +316,8 @@ export const getColumns = ({ export const getMonitoringColumns = ( navigateToApp: (appId: string, options?: NavigateToAppOptions | undefined) => Promise, - formatUrl: FormatUrl + formatUrl: FormatUrl, + docLinks: DocLinksStart ): RulesStatusesColumns[] => { const cols: RulesStatusesColumns[] = [ { @@ -344,12 +349,17 @@ export const getMonitoringColumns = ( }, { field: 'current_status.bulk_create_time_durations', - name: i18n.COLUMN_INDEXING_TIMES, + name: ( + <> + {i18n.COLUMN_INDEXING_TIMES}{' '} + + + + + ), render: (value: RuleStatus['current_status']['bulk_create_time_durations']) => ( - {value != null && value.length > 0 - ? Math.max(...value?.map((item) => Number.parseFloat(item))) - : getEmptyTagValue()} + {value?.length ? sum(value.map(Number)).toFixed() : getEmptyTagValue()} ), truncateText: true, @@ -357,12 +367,17 @@ export const getMonitoringColumns = ( }, { field: 'current_status.search_after_time_durations', - name: i18n.COLUMN_QUERY_TIMES, + name: ( + <> + {i18n.COLUMN_QUERY_TIMES}{' '} + + + + + ), render: (value: RuleStatus['current_status']['search_after_time_durations']) => ( - {value != null && value.length > 0 - ? Math.max(...value?.map((item) => Number.parseFloat(item))) - : getEmptyTagValue()} + {value?.length ? sum(value.map(Number)).toFixed() : getEmptyTagValue()} ), truncateText: true, @@ -370,7 +385,28 @@ export const getMonitoringColumns = ( }, { field: 'current_status.gap', - name: i18n.COLUMN_GAP, + name: ( + <> + {i18n.COLUMN_GAP} + + +

+ + {'see documentation'} + + ), + }} + /> +

+
+
+ + ), render: (value: RuleStatus['current_status']['gap']) => ( {value ?? getEmptyTagValue()} diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/popover_tooltip.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/popover_tooltip.tsx new file mode 100644 index 000000000000..5cf0a0c0b28f --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/popover_tooltip.tsx @@ -0,0 +1,47 @@ +/* + * 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, { useState } from 'react'; +import { EuiPopover, EuiButtonIcon } from '@elastic/eui'; +import * as i18n from '../translations'; + +interface PopoverTooltipProps { + columnName: string; + children: React.ReactNode; +} + +/** + * Table column tooltip component utilizing EuiPopover for rich content like documentation links + * @param columnName string Name of column to use as aria-label of button + * @param children React.ReactNode of content to display in popover tooltip + */ +const PopoverTooltipComponent = ({ columnName, children }: PopoverTooltipProps) => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + + return ( + setIsPopoverOpen(false)} + button={ + setIsPopoverOpen(!isPopoverOpen)} + size="s" + color="primary" + iconType="questionInCircle" + /> + } + > + {children} + + ); +}; + +export const PopoverTooltip = React.memo(PopoverTooltipComponent); + +PopoverTooltip.displayName = 'PopoverTooltip'; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx index f32321a0a03d..9d9425cdabe6 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx @@ -96,6 +96,7 @@ export const RulesTables = React.memo( setRefreshRulesData, selectedTab, }) => { + const docLinks = useKibana().services.docLinks; const [initLoading, setInitLoading] = useState(true); const { @@ -299,8 +300,8 @@ export const RulesTables = React.memo( ]); const monitoringColumns = useMemo( - () => getMonitoringColumns(navigateToApp, formatUrl), - [navigateToApp, formatUrl] + () => getMonitoringColumns(navigateToApp, formatUrl, docLinks), + [navigateToApp, formatUrl, docLinks] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts index 28ed14774acf..53efe28cba49 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts @@ -13,6 +13,11 @@ export const BACK_TO_DETECTIONS = i18n.translate( defaultMessage: 'Back to detections', } ); +export const POPOVER_TOOLTIP_ARIA_LABEL = (columnName: string) => + i18n.translate('xpack.securitySolution.detectionEngine.rules.popoverTooltip.ariaLabel', { + defaultMessage: 'Tooltip for column: {columnName}', + values: { columnName }, + }); export const IMPORT_RULE = i18n.translate( 'xpack.securitySolution.detectionEngine.rules.importRuleTitle', @@ -364,6 +369,13 @@ export const COLUMN_INDEXING_TIMES = i18n.translate( } ); +export const COLUMN_INDEXING_TIMES_TOOLTIP = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.columns.indexingTimesTooltip', + { + defaultMessage: 'Total time spent indexing alerts during last Rule execution', + } +); + export const COLUMN_QUERY_TIMES = i18n.translate( 'xpack.securitySolution.detectionEngine.rules.allRules.columns.queryTimes', { @@ -371,6 +383,13 @@ export const COLUMN_QUERY_TIMES = i18n.translate( } ); +export const COLUMN_QUERY_TIMES_TOOLTIP = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.columns.queryTimesTooltip', + { + defaultMessage: 'Total time spent querying source indices during last Rule execution', + } +); + export const COLUMN_GAP = i18n.translate( 'xpack.securitySolution.detectionEngine.rules.allRules.columns.gap', {