[Security Solution][Detections] Updates Indexing/Query Time columns in Rule Monitoring table to be SUM instead of MAX (#114023)

## 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:
<p align="center">
  <img width="700" src="https://user-images.githubusercontent.com/2946766/136475361-cedd7c6a-6a0e-4a86-8467-c929aed0f16e.png" />
</p>


##### Query Time:
<p align="center">
  <img width="700" src="https://user-images.githubusercontent.com/2946766/136475378-1228dfcf-a921-4c0e-8f1e-7594e9c220d4.png" />
</p>

##### Last Gap:
<p align="center">
  <img width="700" src="https://user-images.githubusercontent.com/2946766/136475412-b54f2419-ced8-43d8-8643-09c8e2cacc44.png" />
</p>



### 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)
This commit is contained in:
Garrett Spong 2021-10-07 20:35:11 -06:00 committed by GitHub
parent 02822a66fa
commit 15c7bd0192
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 129 additions and 21 deletions

View file

@ -143,6 +143,7 @@ readonly links: {
readonly ruleChangeLog: string;
readonly detectionsReq: string;
readonly networkMap: string;
readonly troubleshootGaps: string;
};
readonly query: {
readonly eql: string;

View file

@ -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;

View file

@ -612,6 +612,7 @@ export interface DocLinksStart {
readonly ruleChangeLog: string;
readonly detectionsReq: string;
readonly networkMap: string;
readonly troubleshootGaps: string;
};
readonly query: {
readonly eql: string;

View file

@ -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<RulesTableAction>,
@ -312,7 +316,8 @@ export const getColumns = ({
export const getMonitoringColumns = (
navigateToApp: (appId: string, options?: NavigateToAppOptions | undefined) => Promise<void>,
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}{' '}
<EuiToolTip content={i18n.COLUMN_INDEXING_TIMES_TOOLTIP}>
<EuiIcon size="m" color="subdued" type="questionInCircle" style={{ margin: 4 }} />
</EuiToolTip>
</>
),
render: (value: RuleStatus['current_status']['bulk_create_time_durations']) => (
<EuiText data-test-subj="bulk_create_time_durations" size="s">
{value != null && value.length > 0
? Math.max(...value?.map((item) => Number.parseFloat(item)))
: getEmptyTagValue()}
{value?.length ? sum(value.map(Number)).toFixed() : getEmptyTagValue()}
</EuiText>
),
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}{' '}
<EuiToolTip content={i18n.COLUMN_QUERY_TIMES_TOOLTIP}>
<EuiIcon size="m" color="subdued" type="questionInCircle" style={{ margin: 4 }} />
</EuiToolTip>
</>
),
render: (value: RuleStatus['current_status']['search_after_time_durations']) => (
<EuiText data-test-subj="search_after_time_durations" size="s">
{value != null && value.length > 0
? Math.max(...value?.map((item) => Number.parseFloat(item)))
: getEmptyTagValue()}
{value?.length ? sum(value.map(Number)).toFixed() : getEmptyTagValue()}
</EuiText>
),
truncateText: true,
@ -370,7 +385,28 @@ export const getMonitoringColumns = (
},
{
field: 'current_status.gap',
name: i18n.COLUMN_GAP,
name: (
<>
{i18n.COLUMN_GAP}
<PopoverTooltip columnName={i18n.COLUMN_GAP}>
<EuiText style={{ width: 300 }}>
<p>
<FormattedMessage
defaultMessage="Duration of most recent gap in Rule execution. Adjust Rule look-back or {seeDocs} for mitigating gaps."
id="xpack.securitySolution.detectionEngine.rules.allRules.columns.gapTooltip"
values={{
seeDocs: (
<EuiLink href={`${docLinks.links.siem.troubleshootGaps}`} target="_blank">
{'see documentation'}
</EuiLink>
),
}}
/>
</p>
</EuiText>
</PopoverTooltip>
</>
),
render: (value: RuleStatus['current_status']['gap']) => (
<EuiText data-test-subj="gap" size="s">
{value ?? getEmptyTagValue()}

View file

@ -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 (
<EuiPopover
anchorPosition={'upCenter'}
isOpen={isPopoverOpen}
closePopover={() => setIsPopoverOpen(false)}
button={
<EuiButtonIcon
aria-label={i18n.POPOVER_TOOLTIP_ARIA_LABEL(columnName)}
onClick={() => setIsPopoverOpen(!isPopoverOpen)}
size="s"
color="primary"
iconType="questionInCircle"
/>
}
>
{children}
</EuiPopover>
);
};
export const PopoverTooltip = React.memo(PopoverTooltipComponent);
PopoverTooltip.displayName = 'PopoverTooltip';

View file

@ -96,6 +96,7 @@ export const RulesTables = React.memo<RulesTableProps>(
setRefreshRulesData,
selectedTab,
}) => {
const docLinks = useKibana().services.docLinks;
const [initLoading, setInitLoading] = useState(true);
const {
@ -299,8 +300,8 @@ export const RulesTables = React.memo<RulesTableProps>(
]);
const monitoringColumns = useMemo(
() => getMonitoringColumns(navigateToApp, formatUrl),
[navigateToApp, formatUrl]
() => getMonitoringColumns(navigateToApp, formatUrl, docLinks),
[navigateToApp, formatUrl, docLinks]
);
useEffect(() => {

View file

@ -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',
{