[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:
parent
02822a66fa
commit
15c7bd0192
|
@ -143,6 +143,7 @@ readonly links: {
|
||||||
readonly ruleChangeLog: string;
|
readonly ruleChangeLog: string;
|
||||||
readonly detectionsReq: string;
|
readonly detectionsReq: string;
|
||||||
readonly networkMap: string;
|
readonly networkMap: string;
|
||||||
|
readonly troubleshootGaps: string;
|
||||||
};
|
};
|
||||||
readonly query: {
|
readonly query: {
|
||||||
readonly eql: string;
|
readonly eql: string;
|
||||||
|
|
|
@ -25,6 +25,7 @@ export class DocLinksService {
|
||||||
const FLEET_DOCS = `${ELASTIC_WEBSITE_URL}guide/en/fleet/${DOC_LINK_VERSION}/`;
|
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 PLUGIN_DOCS = `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/plugins/${DOC_LINK_VERSION}/`;
|
||||||
const APM_DOCS = `${ELASTIC_WEBSITE_URL}guide/en/apm/`;
|
const APM_DOCS = `${ELASTIC_WEBSITE_URL}guide/en/apm/`;
|
||||||
|
const SECURITY_SOLUTION_DOCS = `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/`;
|
||||||
|
|
||||||
return deepFreeze({
|
return deepFreeze({
|
||||||
DOC_LINK_VERSION,
|
DOC_LINK_VERSION,
|
||||||
|
@ -223,13 +224,14 @@ export class DocLinksService {
|
||||||
typesRemoval: `${ELASTICSEARCH_DOCS}removal-of-types.html`,
|
typesRemoval: `${ELASTICSEARCH_DOCS}removal-of-types.html`,
|
||||||
},
|
},
|
||||||
siem: {
|
siem: {
|
||||||
guide: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/index.html`,
|
guide: `${SECURITY_SOLUTION_DOCS}index.html`,
|
||||||
gettingStarted: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/index.html`,
|
gettingStarted: `${SECURITY_SOLUTION_DOCS}index.html`,
|
||||||
privileges: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/sec-requirements.html`,
|
privileges: `${SECURITY_SOLUTION_DOCS}sec-requirements.html`,
|
||||||
ml: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/machine-learning.html`,
|
ml: `${SECURITY_SOLUTION_DOCS}machine-learning.html`,
|
||||||
ruleChangeLog: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/prebuilt-rules-changelog.html`,
|
ruleChangeLog: `${SECURITY_SOLUTION_DOCS}prebuilt-rules-changelog.html`,
|
||||||
detectionsReq: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/detections-permissions-section.html`,
|
detectionsReq: `${SECURITY_SOLUTION_DOCS}detections-permissions-section.html`,
|
||||||
networkMap: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/conf-map-ui.html`,
|
networkMap: `${SECURITY_SOLUTION_DOCS}conf-map-ui.html`,
|
||||||
|
troubleshootGaps: `${SECURITY_SOLUTION_DOCS}alerts-ui-monitor.html#troubleshoot-gaps`,
|
||||||
},
|
},
|
||||||
query: {
|
query: {
|
||||||
eql: `${ELASTICSEARCH_DOCS}eql.html`,
|
eql: `${ELASTICSEARCH_DOCS}eql.html`,
|
||||||
|
@ -636,6 +638,7 @@ export interface DocLinksStart {
|
||||||
readonly ruleChangeLog: string;
|
readonly ruleChangeLog: string;
|
||||||
readonly detectionsReq: string;
|
readonly detectionsReq: string;
|
||||||
readonly networkMap: string;
|
readonly networkMap: string;
|
||||||
|
readonly troubleshootGaps: string;
|
||||||
};
|
};
|
||||||
readonly query: {
|
readonly query: {
|
||||||
readonly eql: string;
|
readonly eql: string;
|
||||||
|
|
|
@ -612,6 +612,7 @@ export interface DocLinksStart {
|
||||||
readonly ruleChangeLog: string;
|
readonly ruleChangeLog: string;
|
||||||
readonly detectionsReq: string;
|
readonly detectionsReq: string;
|
||||||
readonly networkMap: string;
|
readonly networkMap: string;
|
||||||
|
readonly troubleshootGaps: string;
|
||||||
};
|
};
|
||||||
readonly query: {
|
readonly query: {
|
||||||
readonly eql: string;
|
readonly eql: string;
|
||||||
|
|
|
@ -11,9 +11,12 @@ import {
|
||||||
EuiText,
|
EuiText,
|
||||||
EuiHealth,
|
EuiHealth,
|
||||||
EuiToolTip,
|
EuiToolTip,
|
||||||
|
EuiIcon,
|
||||||
|
EuiLink,
|
||||||
} from '@elastic/eui';
|
} from '@elastic/eui';
|
||||||
import { FormattedRelative } from '@kbn/i18n/react';
|
import { FormattedMessage, FormattedRelative } from '@kbn/i18n/react';
|
||||||
import * as H from 'history';
|
import * as H from 'history';
|
||||||
|
import { sum } from 'lodash';
|
||||||
import React, { Dispatch } from 'react';
|
import React, { Dispatch } from 'react';
|
||||||
|
|
||||||
import { isMlRule } from '../../../../../../common/machine_learning/helpers';
|
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 { LocalizedDateTooltip } from '../../../../../common/components/localized_date_tooltip';
|
||||||
import { LinkAnchor } from '../../../../../common/components/links';
|
import { LinkAnchor } from '../../../../../common/components/links';
|
||||||
import { getToolTipContent, canEditRuleWithActions } from '../../../../../common/utils/privileges';
|
import { getToolTipContent, canEditRuleWithActions } from '../../../../../common/utils/privileges';
|
||||||
|
import { PopoverTooltip } from './popover_tooltip';
|
||||||
import { TagsDisplay } from './tag_display';
|
import { TagsDisplay } from './tag_display';
|
||||||
import { getRuleStatusText } from '../../../../../../common/detection_engine/utils';
|
import { getRuleStatusText } from '../../../../../../common/detection_engine/utils';
|
||||||
import { APP_ID, SecurityPageName } from '../../../../../../common/constants';
|
import { APP_ID, SecurityPageName } from '../../../../../../common/constants';
|
||||||
import { NavigateToAppOptions } from '../../../../../../../../../src/core/public';
|
import { DocLinksStart, NavigateToAppOptions } from '../../../../../../../../../src/core/public';
|
||||||
|
|
||||||
export const getActions = (
|
export const getActions = (
|
||||||
dispatch: React.Dispatch<RulesTableAction>,
|
dispatch: React.Dispatch<RulesTableAction>,
|
||||||
|
@ -312,7 +316,8 @@ export const getColumns = ({
|
||||||
|
|
||||||
export const getMonitoringColumns = (
|
export const getMonitoringColumns = (
|
||||||
navigateToApp: (appId: string, options?: NavigateToAppOptions | undefined) => Promise<void>,
|
navigateToApp: (appId: string, options?: NavigateToAppOptions | undefined) => Promise<void>,
|
||||||
formatUrl: FormatUrl
|
formatUrl: FormatUrl,
|
||||||
|
docLinks: DocLinksStart
|
||||||
): RulesStatusesColumns[] => {
|
): RulesStatusesColumns[] => {
|
||||||
const cols: RulesStatusesColumns[] = [
|
const cols: RulesStatusesColumns[] = [
|
||||||
{
|
{
|
||||||
|
@ -344,12 +349,17 @@ export const getMonitoringColumns = (
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'current_status.bulk_create_time_durations',
|
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']) => (
|
render: (value: RuleStatus['current_status']['bulk_create_time_durations']) => (
|
||||||
<EuiText data-test-subj="bulk_create_time_durations" size="s">
|
<EuiText data-test-subj="bulk_create_time_durations" size="s">
|
||||||
{value != null && value.length > 0
|
{value?.length ? sum(value.map(Number)).toFixed() : getEmptyTagValue()}
|
||||||
? Math.max(...value?.map((item) => Number.parseFloat(item)))
|
|
||||||
: getEmptyTagValue()}
|
|
||||||
</EuiText>
|
</EuiText>
|
||||||
),
|
),
|
||||||
truncateText: true,
|
truncateText: true,
|
||||||
|
@ -357,12 +367,17 @@ export const getMonitoringColumns = (
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'current_status.search_after_time_durations',
|
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']) => (
|
render: (value: RuleStatus['current_status']['search_after_time_durations']) => (
|
||||||
<EuiText data-test-subj="search_after_time_durations" size="s">
|
<EuiText data-test-subj="search_after_time_durations" size="s">
|
||||||
{value != null && value.length > 0
|
{value?.length ? sum(value.map(Number)).toFixed() : getEmptyTagValue()}
|
||||||
? Math.max(...value?.map((item) => Number.parseFloat(item)))
|
|
||||||
: getEmptyTagValue()}
|
|
||||||
</EuiText>
|
</EuiText>
|
||||||
),
|
),
|
||||||
truncateText: true,
|
truncateText: true,
|
||||||
|
@ -370,7 +385,28 @@ export const getMonitoringColumns = (
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'current_status.gap',
|
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']) => (
|
render: (value: RuleStatus['current_status']['gap']) => (
|
||||||
<EuiText data-test-subj="gap" size="s">
|
<EuiText data-test-subj="gap" size="s">
|
||||||
{value ?? getEmptyTagValue()}
|
{value ?? getEmptyTagValue()}
|
||||||
|
|
|
@ -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';
|
|
@ -96,6 +96,7 @@ export const RulesTables = React.memo<RulesTableProps>(
|
||||||
setRefreshRulesData,
|
setRefreshRulesData,
|
||||||
selectedTab,
|
selectedTab,
|
||||||
}) => {
|
}) => {
|
||||||
|
const docLinks = useKibana().services.docLinks;
|
||||||
const [initLoading, setInitLoading] = useState(true);
|
const [initLoading, setInitLoading] = useState(true);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
@ -299,8 +300,8 @@ export const RulesTables = React.memo<RulesTableProps>(
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const monitoringColumns = useMemo(
|
const monitoringColumns = useMemo(
|
||||||
() => getMonitoringColumns(navigateToApp, formatUrl),
|
() => getMonitoringColumns(navigateToApp, formatUrl, docLinks),
|
||||||
[navigateToApp, formatUrl]
|
[navigateToApp, formatUrl, docLinks]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
@ -13,6 +13,11 @@ export const BACK_TO_DETECTIONS = i18n.translate(
|
||||||
defaultMessage: 'Back to detections',
|
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(
|
export const IMPORT_RULE = i18n.translate(
|
||||||
'xpack.securitySolution.detectionEngine.rules.importRuleTitle',
|
'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(
|
export const COLUMN_QUERY_TIMES = i18n.translate(
|
||||||
'xpack.securitySolution.detectionEngine.rules.allRules.columns.queryTimes',
|
'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(
|
export const COLUMN_GAP = i18n.translate(
|
||||||
'xpack.securitySolution.detectionEngine.rules.allRules.columns.gap',
|
'xpack.securitySolution.detectionEngine.rules.allRules.columns.gap',
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in a new issue