mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Security Solution][Rule details] - Updates rule details page to allow tabs to be a part of url (#139592)
## Summary Similar to how the Hosts page maintains the tab state in the URL, recreating that in the rule details page. This is in anticipation of some of the exceptions UI changes being worked on where this would create a better user experience.
This commit is contained in:
parent
160058a8c1
commit
082a3629b4
13 changed files with 206 additions and 177 deletions
|
@ -63,6 +63,11 @@ describe('URL compatibility', () => {
|
|||
cy.url().should('include', ruleDetailsUrl(RULE_ID));
|
||||
});
|
||||
|
||||
it('Redirects to rule details alerts tab from old Detections rule details URL', () => {
|
||||
visit(ruleDetailsUrl(RULE_ID));
|
||||
cy.url().should('include', `${ruleDetailsUrl(RULE_ID)}/alerts`);
|
||||
});
|
||||
|
||||
it('Redirects to rule edit from old Detections rule edit URL', () => {
|
||||
visit(detectionRuleEditUrl(RULE_ID));
|
||||
cy.url().should('include', ruleEditUrl(RULE_ID));
|
||||
|
|
|
@ -16,7 +16,7 @@ export const ABOUT_DETAILS =
|
|||
|
||||
export const ADDITIONAL_LOOK_BACK_DETAILS = 'Additional look-back time';
|
||||
|
||||
export const ALERTS_TAB = '[data-test-subj="alertsTab"]';
|
||||
export const ALERTS_TAB = '[data-test-subj="navigation-alerts"]';
|
||||
|
||||
export const ANOMALY_SCORE_DETAILS = 'Anomaly score';
|
||||
|
||||
|
@ -31,7 +31,7 @@ export const DETAILS_DESCRIPTION = '.euiDescriptionList__description';
|
|||
|
||||
export const DETAILS_TITLE = '.euiDescriptionList__title';
|
||||
|
||||
export const EXCEPTIONS_TAB = '[data-test-subj="exceptionsTab"]';
|
||||
export const EXCEPTIONS_TAB = '[data-test-subj="navigation-rule_exceptions"]';
|
||||
|
||||
export const FALSE_POSITIVES_DETAILS = 'False positive examples';
|
||||
|
||||
|
|
|
@ -89,12 +89,7 @@ export const goToAlertsTab = () => {
|
|||
};
|
||||
|
||||
export const goToExceptionsTab = () => {
|
||||
cy.root()
|
||||
.pipe(($el) => {
|
||||
$el.find(EXCEPTIONS_TAB).trigger('click');
|
||||
return $el.find(ADD_EXCEPTIONS_BTN);
|
||||
})
|
||||
.should('be.visible');
|
||||
cy.get(EXCEPTIONS_TAB).click();
|
||||
};
|
||||
|
||||
export const editException = () => {
|
||||
|
|
|
@ -14,5 +14,8 @@ export const getRulesUrl = (search?: string) => `${appendSearch(search)}`;
|
|||
export const getRuleDetailsUrl = (detailName: string, search?: string) =>
|
||||
`/id/${detailName}${appendSearch(search)}`;
|
||||
|
||||
export const getRuleDetailsTabUrl = (detailName: string, tabName: string, search?: string) =>
|
||||
`/id/${detailName}/${tabName}${appendSearch(search)}`;
|
||||
|
||||
export const getEditRuleUrl = (detailName: string, search?: string) =>
|
||||
`/id/${detailName}/edit${appendSearch(search)}`;
|
||||
|
|
|
@ -11,7 +11,6 @@ import { FormattedMessage } from '@kbn/i18n-react';
|
|||
import React, { useMemo } from 'react';
|
||||
import { IntegrationsPopover } from '../../../../components/rules/related_integrations/integrations_popover';
|
||||
import {
|
||||
APP_UI_ID,
|
||||
DEFAULT_RELATIVE_DATE_THRESHOLD,
|
||||
SecurityPageName,
|
||||
SHOW_RELATED_INTEGRATIONS_SETTING,
|
||||
|
@ -19,9 +18,7 @@ import {
|
|||
import { isMlRule } from '../../../../../../common/machine_learning/helpers';
|
||||
import { getEmptyTagValue } from '../../../../../common/components/empty_value';
|
||||
import { FormattedRelativePreferenceDate } from '../../../../../common/components/formatted_date';
|
||||
import { LinkAnchor } from '../../../../../common/components/links';
|
||||
import { useFormatUrl } from '../../../../../common/components/link_to';
|
||||
import { getRuleDetailsUrl } from '../../../../../common/components/link_to/redirect_to_detection_engine';
|
||||
import { getRuleDetailsTabUrl } from '../../../../../common/components/link_to/redirect_to_detection_engine';
|
||||
import { PopoverItems } from '../../../../../common/components/popover_items';
|
||||
import { useKibana, useUiSetting$ } from '../../../../../common/lib/kibana';
|
||||
import { canEditRuleWithActions, getToolTipContent } from '../../../../../common/utils/privileges';
|
||||
|
@ -44,6 +41,8 @@ import { useAppToasts } from '../../../../../common/hooks/use_app_toasts';
|
|||
import { useStartTransaction } from '../../../../../common/lib/apm/use_start_transaction';
|
||||
import { useInvalidateRules } from '../../../../containers/detection_engine/rules/use_find_rules_query';
|
||||
import { useInvalidatePrePackagedRulesStatus } from '../../../../containers/detection_engine/rules/use_pre_packaged_rules_status';
|
||||
import { SecuritySolutionLinkAnchor } from '../../../../../common/components/links';
|
||||
import { RuleDetailTabs } from '../details';
|
||||
|
||||
export type TableColumn = EuiBasicTableColumn<Rule> | EuiTableActionsColumnType<Rule>;
|
||||
|
||||
|
@ -90,24 +89,15 @@ const useEnabledColumn = ({ hasPermissions }: ColumnsProps): TableColumn => {
|
|||
};
|
||||
|
||||
export const RuleLink = ({ name, id }: Pick<Rule, 'id' | 'name'>) => {
|
||||
const { formatUrl } = useFormatUrl(SecurityPageName.rules);
|
||||
const { navigateToApp } = useKibana().services.application;
|
||||
|
||||
return (
|
||||
<EuiToolTip content={name} anchorClassName="eui-textTruncate">
|
||||
<LinkAnchor
|
||||
<SecuritySolutionLinkAnchor
|
||||
data-test-subj="ruleName"
|
||||
onClick={(ev: { preventDefault: () => void }) => {
|
||||
ev.preventDefault();
|
||||
navigateToApp(APP_UI_ID, {
|
||||
deepLinkId: SecurityPageName.rules,
|
||||
path: getRuleDetailsUrl(id),
|
||||
});
|
||||
}}
|
||||
href={formatUrl(getRuleDetailsUrl(id))}
|
||||
deepLinkId={SecurityPageName.rules}
|
||||
path={getRuleDetailsTabUrl(id, RuleDetailTabs.alerts)}
|
||||
>
|
||||
{name}
|
||||
</LinkAnchor>
|
||||
</SecuritySolutionLinkAnchor>
|
||||
</EuiToolTip>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -7,10 +7,11 @@
|
|||
|
||||
import React from 'react';
|
||||
import type { RuleDetailsContextType } from '../rule_details_context';
|
||||
import { RuleDetailTabs } from '..';
|
||||
|
||||
export const useRuleDetailsContextMock = {
|
||||
create: (): jest.Mocked<RuleDetailsContextType> => ({
|
||||
executionResults: {
|
||||
[RuleDetailTabs.executionResults]: {
|
||||
state: {
|
||||
superDatePicker: {
|
||||
recentlyUsedRanges: [],
|
||||
|
|
|
@ -14,17 +14,15 @@ import {
|
|||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiSpacer,
|
||||
EuiTab,
|
||||
EuiTabs,
|
||||
EuiToolTip,
|
||||
EuiWindowEvent,
|
||||
} from '@elastic/eui';
|
||||
import { i18n as i18nTranslate } from '@kbn/i18n';
|
||||
|
||||
import { Route } from '@kbn/kibana-react-plugin/public';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { noop } from 'lodash/fp';
|
||||
import { noop, omit } from 'lodash/fp';
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { Switch, useParams } from 'react-router-dom';
|
||||
import type { ConnectedProps } from 'react-redux';
|
||||
import { connect, useDispatch } from 'react-redux';
|
||||
import styled from 'styled-components';
|
||||
|
@ -34,6 +32,7 @@ import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types';
|
|||
import type { Dispatch } from 'redux';
|
||||
import { isTab } from '@kbn/timelines-plugin/public';
|
||||
import type { DataViewListItem } from '@kbn/data-views-plugin/common';
|
||||
import { SecuritySolutionTabNavigation } from '../../../../../common/components/navigation';
|
||||
import { InputsModelId } from '../../../../../common/store/inputs/constants';
|
||||
import {
|
||||
useDeepEqualSelector,
|
||||
|
@ -48,6 +47,7 @@ import {
|
|||
getEditRuleUrl,
|
||||
getRulesUrl,
|
||||
getDetectionEngineUrl,
|
||||
getRuleDetailsTabUrl,
|
||||
} from '../../../../../common/components/link_to/redirect_to_detection_engine';
|
||||
import { SiemSearchBar } from '../../../../../common/components/search_bar';
|
||||
import { SecuritySolutionPageWrapper } from '../../../../../common/components/page_wrapper';
|
||||
|
@ -127,6 +127,7 @@ import {
|
|||
} from '../../../../components/alerts_table/alerts_filter_group';
|
||||
import { useSignalHelpers } from '../../../../../common/containers/sourcerer/use_signal_helpers';
|
||||
import { HeaderPage } from '../../../../../common/components/header_page';
|
||||
import type { NavTab } from '../../../../../common/components/navigation/types';
|
||||
|
||||
/**
|
||||
* Need a 100% height here to account for the graph/analyze tool, which sets no explicit height parameters, but fills the available space.
|
||||
|
@ -146,37 +147,17 @@ const StyledMinHeightTabContainer = styled.div`
|
|||
|
||||
export enum RuleDetailTabs {
|
||||
alerts = 'alerts',
|
||||
exceptions = 'exceptions',
|
||||
executionResults = 'executionResults',
|
||||
executionEvents = 'executionEvents',
|
||||
exceptions = 'rule_exceptions',
|
||||
executionResults = 'execution_results',
|
||||
executionEvents = 'execution_events',
|
||||
}
|
||||
|
||||
const ruleDetailTabs = [
|
||||
{
|
||||
id: RuleDetailTabs.alerts,
|
||||
name: detectionI18n.ALERT,
|
||||
disabled: false,
|
||||
dataTestSubj: 'alertsTab',
|
||||
},
|
||||
{
|
||||
id: RuleDetailTabs.exceptions,
|
||||
name: i18n.EXCEPTIONS_TAB,
|
||||
disabled: false,
|
||||
dataTestSubj: 'exceptionsTab',
|
||||
},
|
||||
{
|
||||
id: RuleDetailTabs.executionResults,
|
||||
name: i18n.EXECUTION_RESULTS_TAB,
|
||||
disabled: false,
|
||||
dataTestSubj: 'executionResultsTab',
|
||||
},
|
||||
{
|
||||
id: RuleDetailTabs.executionEvents,
|
||||
name: i18n.EXECUTION_EVENTS_TAB,
|
||||
disabled: false,
|
||||
dataTestSubj: 'executionEventsTab',
|
||||
},
|
||||
];
|
||||
export const RULE_DETAILS_TAB_NAME: Record<string, string> = {
|
||||
[RuleDetailTabs.alerts]: detectionI18n.ALERT,
|
||||
[RuleDetailTabs.exceptions]: i18n.EXCEPTIONS_TAB,
|
||||
[RuleDetailTabs.executionResults]: i18n.EXECUTION_RESULTS_TAB,
|
||||
[RuleDetailTabs.executionEvents]: i18n.EXECUTION_EVENTS_TAB,
|
||||
};
|
||||
|
||||
type DetectionEngineComponentProps = PropsFromRedux;
|
||||
|
||||
|
@ -241,7 +222,10 @@ const RuleDetailsPageComponent: React.FC<DetectionEngineComponentProps> = ({
|
|||
} = useSourcererDataView(SourcererScopeName.detections);
|
||||
|
||||
const loading = userInfoLoading || listsConfigLoading;
|
||||
const { detailName: ruleId } = useParams<{ detailName: string }>();
|
||||
const { detailName: ruleId } = useParams<{
|
||||
detailName: string;
|
||||
tabName: string;
|
||||
}>();
|
||||
const {
|
||||
rule: maybeRule,
|
||||
refresh: refreshRule,
|
||||
|
@ -251,8 +235,38 @@ const RuleDetailsPageComponent: React.FC<DetectionEngineComponentProps> = ({
|
|||
const { pollForSignalIndex } = useSignalHelpers();
|
||||
const [rule, setRule] = useState<Rule | null>(null);
|
||||
const isLoading = ruleLoading && rule == null;
|
||||
const [ruleDetailTab, setRuleDetailTab] = useState(RuleDetailTabs.alerts);
|
||||
const [pageTabs, setTabs] = useState(ruleDetailTabs);
|
||||
|
||||
const ruleDetailTabs = useMemo(
|
||||
(): Record<RuleDetailTabs, NavTab> => ({
|
||||
[RuleDetailTabs.alerts]: {
|
||||
id: RuleDetailTabs.alerts,
|
||||
name: RULE_DETAILS_TAB_NAME[RuleDetailTabs.alerts],
|
||||
disabled: false,
|
||||
href: `/rules/id/${ruleId}/${RuleDetailTabs.alerts}`,
|
||||
},
|
||||
[RuleDetailTabs.exceptions]: {
|
||||
id: RuleDetailTabs.exceptions,
|
||||
name: RULE_DETAILS_TAB_NAME[RuleDetailTabs.exceptions],
|
||||
disabled: false,
|
||||
href: `/rules/id/${ruleId}/${RuleDetailTabs.exceptions}`,
|
||||
},
|
||||
[RuleDetailTabs.executionResults]: {
|
||||
id: RuleDetailTabs.executionResults,
|
||||
name: RULE_DETAILS_TAB_NAME[RuleDetailTabs.executionResults],
|
||||
disabled: !isExistingRule,
|
||||
href: `/rules/id/${ruleId}/${RuleDetailTabs.executionResults}`,
|
||||
},
|
||||
[RuleDetailTabs.executionEvents]: {
|
||||
id: RuleDetailTabs.executionEvents,
|
||||
name: RULE_DETAILS_TAB_NAME[RuleDetailTabs.executionEvents],
|
||||
disabled: !isExistingRule,
|
||||
href: `/rules/id/${ruleId}/${RuleDetailTabs.executionEvents}`,
|
||||
},
|
||||
}),
|
||||
[isExistingRule, ruleId]
|
||||
);
|
||||
|
||||
const [pageTabs, setTabs] = useState<Partial<Record<RuleDetailTabs, NavTab>>>(ruleDetailTabs);
|
||||
const { aboutRuleData, modifiedAboutRuleDetailsData, defineRuleData, scheduleRuleData } =
|
||||
rule != null
|
||||
? getStepsData({ rule, detailsView: true })
|
||||
|
@ -306,6 +320,13 @@ const RuleDetailsPageComponent: React.FC<DetectionEngineComponentProps> = ({
|
|||
return true;
|
||||
}, [actions, rule?.actions]);
|
||||
|
||||
const navigateToAlertsTab = useCallback(() => {
|
||||
navigateToApp(APP_UI_ID, {
|
||||
deepLinkId: SecurityPageName.rules,
|
||||
path: getRuleDetailsTabUrl(ruleId ?? '', 'alerts', ''),
|
||||
});
|
||||
}, [navigateToApp, ruleId]);
|
||||
|
||||
// persist rule until refresh is complete
|
||||
useEffect(() => {
|
||||
if (maybeRule != null) {
|
||||
|
@ -360,20 +381,19 @@ const RuleDetailsPageComponent: React.FC<DetectionEngineComponentProps> = ({
|
|||
const ruleExecutionSettings = useRuleExecutionSettings();
|
||||
|
||||
useEffect(() => {
|
||||
let visibleTabs = ruleDetailTabs;
|
||||
let currentTab = RuleDetailTabs.alerts;
|
||||
const hiddenTabs = [];
|
||||
|
||||
if (!hasIndexRead) {
|
||||
visibleTabs = visibleTabs.filter(({ id }) => id !== RuleDetailTabs.alerts);
|
||||
currentTab = RuleDetailTabs.exceptions;
|
||||
hiddenTabs.push(RuleDetailTabs.alerts);
|
||||
}
|
||||
if (!ruleExecutionSettings.extendedLogging.isEnabled) {
|
||||
visibleTabs = visibleTabs.filter(({ id }) => id !== RuleDetailTabs.executionEvents);
|
||||
hiddenTabs.push(RuleDetailTabs.executionEvents);
|
||||
}
|
||||
|
||||
setTabs(visibleTabs);
|
||||
setRuleDetailTab(currentTab);
|
||||
}, [hasIndexRead, ruleExecutionSettings]);
|
||||
const tabs = omit<Record<RuleDetailTabs, NavTab>>(hiddenTabs, ruleDetailTabs);
|
||||
|
||||
setTabs(tabs);
|
||||
}, [hasIndexRead, ruleDetailTabs, ruleExecutionSettings]);
|
||||
|
||||
const showUpdating = useMemo(
|
||||
() => isLoadingIndexPattern || isAlertsLoading || loading,
|
||||
|
@ -480,30 +500,6 @@ const RuleDetailsPageComponent: React.FC<DetectionEngineComponentProps> = ({
|
|||
[alertDefaultFilters, filters]
|
||||
);
|
||||
|
||||
const tabs = useMemo(
|
||||
() => (
|
||||
<EuiTabs>
|
||||
{pageTabs.map((tab) => (
|
||||
<EuiTab
|
||||
onClick={() => setRuleDetailTab(tab.id)}
|
||||
isSelected={tab.id === ruleDetailTab}
|
||||
disabled={
|
||||
tab.disabled ||
|
||||
((tab.id === RuleDetailTabs.executionResults ||
|
||||
tab.id === RuleDetailTabs.executionEvents) &&
|
||||
!isExistingRule)
|
||||
}
|
||||
key={tab.id}
|
||||
data-test-subj={tab.dataTestSubj}
|
||||
>
|
||||
{tab.name}
|
||||
</EuiTab>
|
||||
))}
|
||||
</EuiTabs>
|
||||
),
|
||||
[isExistingRule, ruleDetailTab, setRuleDetailTab, pageTabs]
|
||||
);
|
||||
|
||||
const lastExecution = rule?.execution_summary?.last_execution;
|
||||
const lastExecutionStatus = lastExecution?.status;
|
||||
const lastExecutionDate = lastExecution?.date ?? '';
|
||||
|
@ -665,10 +661,6 @@ const RuleDetailsPageComponent: React.FC<DetectionEngineComponentProps> = ({
|
|||
[containerElement, onSkipFocusBeforeEventsTable, onSkipFocusAfterEventsTable]
|
||||
);
|
||||
|
||||
const selectAlertsTabCallback = useCallback(() => {
|
||||
setRuleDetailTab(RuleDetailTabs.alerts);
|
||||
}, []);
|
||||
|
||||
if (
|
||||
redirectToDetections(
|
||||
isSignalIndexExists,
|
||||
|
@ -809,78 +801,80 @@ const RuleDetailsPageComponent: React.FC<DetectionEngineComponentProps> = ({
|
|||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer />
|
||||
{tabs}
|
||||
<SecuritySolutionTabNavigation navTabs={pageTabs} />
|
||||
<EuiSpacer />
|
||||
</Display>
|
||||
<StyledMinHeightTabContainer>
|
||||
{ruleDetailTab === RuleDetailTabs.alerts && hasIndexRead && (
|
||||
<>
|
||||
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<AlertsTableFilterGroup
|
||||
status={filterGroup}
|
||||
onFilterGroupChanged={onFilterGroupChangedCallback}
|
||||
<Switch>
|
||||
<Route path={`/rules/id/:detailName/:tabName(${RuleDetailTabs.alerts})`}>
|
||||
<>
|
||||
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<AlertsTableFilterGroup
|
||||
status={filterGroup}
|
||||
onFilterGroupChanged={onFilterGroupChangedCallback}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
{updatedAt &&
|
||||
timelinesUi.getLastUpdated({
|
||||
updatedAt: updatedAt || Date.now(),
|
||||
showUpdating,
|
||||
})}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="l" />
|
||||
<Display show={!globalFullScreen}>
|
||||
<AlertsHistogramPanel
|
||||
filters={alertMergedFilters}
|
||||
query={query}
|
||||
signalIndexName={signalIndexName}
|
||||
defaultStackByOption={defaultRuleStackByOption}
|
||||
updateDateRange={updateDateRangeCallback}
|
||||
runtimeMappings={runtimeMappings}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
{updatedAt &&
|
||||
timelinesUi.getLastUpdated({
|
||||
updatedAt: updatedAt || Date.now(),
|
||||
showUpdating,
|
||||
})}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="l" />
|
||||
<Display show={!globalFullScreen}>
|
||||
<AlertsHistogramPanel
|
||||
filters={alertMergedFilters}
|
||||
query={query}
|
||||
signalIndexName={signalIndexName}
|
||||
defaultStackByOption={defaultRuleStackByOption}
|
||||
updateDateRange={updateDateRangeCallback}
|
||||
runtimeMappings={runtimeMappings}
|
||||
/>
|
||||
<EuiSpacer />
|
||||
</Display>
|
||||
{ruleId != null && (
|
||||
<AlertsTable
|
||||
filterGroup={filterGroup}
|
||||
timelineId={TimelineId.detectionsRulesDetailsPage}
|
||||
defaultFilters={alertsTableDefaultFilters}
|
||||
hasIndexWrite={hasIndexWrite ?? false}
|
||||
hasIndexMaintenance={hasIndexMaintenance ?? false}
|
||||
from={from}
|
||||
loading={loading}
|
||||
showBuildingBlockAlerts={showBuildingBlockAlerts}
|
||||
showOnlyThreatIndicatorAlerts={showOnlyThreatIndicatorAlerts}
|
||||
onShowBuildingBlockAlertsChanged={onShowBuildingBlockAlertsChangedCallback}
|
||||
onShowOnlyThreatIndicatorAlertsChanged={
|
||||
onShowOnlyThreatIndicatorAlertsCallback
|
||||
}
|
||||
onRuleChange={refreshRule}
|
||||
to={to}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{ruleDetailTab === RuleDetailTabs.exceptions && (
|
||||
<ExceptionsViewer
|
||||
ruleId={ruleId ?? ''}
|
||||
ruleName={rule?.name ?? ''}
|
||||
ruleIndices={rule?.index ?? DEFAULT_INDEX_PATTERN}
|
||||
dataViewId={rule?.data_view_id}
|
||||
availableListTypes={exceptionLists.allowedExceptionListTypes}
|
||||
commentsAccordionId={'ruleDetailsTabExceptions'}
|
||||
exceptionListsMeta={exceptionLists.lists}
|
||||
onRuleChange={refreshRule}
|
||||
/>
|
||||
)}
|
||||
{ruleDetailTab === RuleDetailTabs.executionResults && (
|
||||
<ExecutionLogTable ruleId={ruleId} selectAlertsTab={selectAlertsTabCallback} />
|
||||
)}
|
||||
{ruleDetailTab === RuleDetailTabs.executionEvents && (
|
||||
<ExecutionEventsTable ruleId={ruleId} />
|
||||
)}
|
||||
<EuiSpacer />
|
||||
</Display>
|
||||
{ruleId != null && (
|
||||
<AlertsTable
|
||||
filterGroup={filterGroup}
|
||||
timelineId={TimelineId.detectionsRulesDetailsPage}
|
||||
defaultFilters={alertsTableDefaultFilters}
|
||||
hasIndexWrite={hasIndexWrite ?? false}
|
||||
hasIndexMaintenance={hasIndexMaintenance ?? false}
|
||||
from={from}
|
||||
loading={loading}
|
||||
showBuildingBlockAlerts={showBuildingBlockAlerts}
|
||||
showOnlyThreatIndicatorAlerts={showOnlyThreatIndicatorAlerts}
|
||||
onShowBuildingBlockAlertsChanged={onShowBuildingBlockAlertsChangedCallback}
|
||||
onShowOnlyThreatIndicatorAlertsChanged={
|
||||
onShowOnlyThreatIndicatorAlertsCallback
|
||||
}
|
||||
onRuleChange={refreshRule}
|
||||
to={to}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
</Route>
|
||||
<Route path={`/rules/id/:detailName/:tabName(${RuleDetailTabs.exceptions})`}>
|
||||
<ExceptionsViewer
|
||||
ruleId={ruleId ?? ''}
|
||||
ruleName={rule?.name ?? ''}
|
||||
ruleIndices={rule?.index ?? DEFAULT_INDEX_PATTERN}
|
||||
dataViewId={rule?.data_view_id}
|
||||
availableListTypes={exceptionLists.allowedExceptionListTypes}
|
||||
commentsAccordionId={'ruleDetailsTabExceptions'}
|
||||
exceptionListsMeta={exceptionLists.lists}
|
||||
onRuleChange={refreshRule}
|
||||
/>
|
||||
</Route>
|
||||
<Route path={`/rules/id/:detailName/:tabName(${RuleDetailTabs.executionResults})`}>
|
||||
<ExecutionLogTable ruleId={ruleId} selectAlertsTab={navigateToAlertsTab} />
|
||||
</Route>
|
||||
<Route path={`/rules/id/:detailName/:tabName(${RuleDetailTabs.executionEvents})`}>
|
||||
<ExecutionEventsTable ruleId={ruleId} />
|
||||
</Route>
|
||||
</Switch>
|
||||
</StyledMinHeightTabContainer>
|
||||
</SecuritySolutionPageWrapper>
|
||||
</RuleDetailsContextProvider>
|
||||
|
|
|
@ -36,9 +36,9 @@ export const UNKNOWN = i18n.translate(
|
|||
);
|
||||
|
||||
export const EXCEPTIONS_TAB = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.ruleDetails.exceptionsTab',
|
||||
'xpack.securitySolution.detectionEngine.ruleDetails.ruleExceptionsTab',
|
||||
{
|
||||
defaultMessage: 'Exceptions',
|
||||
defaultMessage: 'Rule exceptions',
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -6,7 +6,10 @@
|
|||
*/
|
||||
|
||||
import type { ChromeBreadcrumb } from '@kbn/core/public';
|
||||
import { getRuleDetailsUrl } from '../../../../common/components/link_to/redirect_to_detection_engine';
|
||||
import {
|
||||
getRuleDetailsTabUrl,
|
||||
getRuleDetailsUrl,
|
||||
} from '../../../../common/components/link_to/redirect_to_detection_engine';
|
||||
import * as i18nRules from './translations';
|
||||
import type { RouteSpyState } from '../../../../common/utils/route/types';
|
||||
import { SecurityPageName } from '../../../../app/types';
|
||||
|
@ -14,6 +17,7 @@ import { RULES_PATH } from '../../../../../common/constants';
|
|||
import type { RuleStepsOrder } from './types';
|
||||
import { RuleStep } from './types';
|
||||
import type { GetSecuritySolutionUrl } from '../../../../common/components/link_to';
|
||||
import { RuleDetailTabs, RULE_DETAILS_TAB_NAME } from './details';
|
||||
|
||||
export const ruleStepsOrder: RuleStepsOrder = [
|
||||
RuleStep.defineRule,
|
||||
|
@ -22,6 +26,10 @@ export const ruleStepsOrder: RuleStepsOrder = [
|
|||
RuleStep.ruleActions,
|
||||
];
|
||||
|
||||
const getRuleDetailsTabName = (tabName: string): string => {
|
||||
return RULE_DETAILS_TAB_NAME[tabName] ?? RULE_DETAILS_TAB_NAME[RuleDetailTabs.alerts];
|
||||
};
|
||||
|
||||
const isRuleCreatePage = (pathname: string) =>
|
||||
pathname.includes(RULES_PATH) && pathname.includes('/create');
|
||||
|
||||
|
@ -47,6 +55,19 @@ export const getTrailingBreadcrumbs = (
|
|||
];
|
||||
}
|
||||
|
||||
if (params.detailName && params.state?.ruleName && params.tabName) {
|
||||
breadcrumb = [
|
||||
...breadcrumb,
|
||||
{
|
||||
text: getRuleDetailsTabName(params.tabName),
|
||||
href: getSecuritySolutionUrl({
|
||||
deepLinkId: SecurityPageName.rules,
|
||||
path: getRuleDetailsTabUrl(params.detailName, params.tabName, ''),
|
||||
}),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
if (isRuleCreatePage(params.pathName)) {
|
||||
breadcrumb = [
|
||||
...breadcrumb,
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { Switch } from 'react-router-dom';
|
||||
import { Redirect, Switch } from 'react-router-dom';
|
||||
|
||||
import { Route } from '@kbn/kibana-react-plugin/public';
|
||||
import { TrackApplicationView } from '@kbn/usage-collection-plugin/public';
|
||||
|
@ -14,10 +14,14 @@ import { RULES_PATH, SecurityPageName } from '../../common/constants';
|
|||
import { NotFoundPage } from '../app/404';
|
||||
import { RulesPage } from '../detections/pages/detection_engine/rules';
|
||||
import { CreateRulePage } from '../detections/pages/detection_engine/rules/create';
|
||||
import { RuleDetailsPage } from '../detections/pages/detection_engine/rules/details';
|
||||
import {
|
||||
RuleDetailsPage,
|
||||
RuleDetailTabs,
|
||||
} from '../detections/pages/detection_engine/rules/details';
|
||||
import { EditRulePage } from '../detections/pages/detection_engine/rules/edit';
|
||||
import { useReadonlyHeader } from '../use_readonly_header';
|
||||
import { PluginTemplateWrapper } from '../common/components/plugin_template_wrapper';
|
||||
import { SpyRoute } from '../common/utils/route/spy_routes';
|
||||
|
||||
const RulesSubRoutes = [
|
||||
{
|
||||
|
@ -26,7 +30,7 @@ const RulesSubRoutes = [
|
|||
exact: true,
|
||||
},
|
||||
{
|
||||
path: '/rules/id/:detailName',
|
||||
path: `/rules/id/:detailName/:tabName(${RuleDetailTabs.alerts}|${RuleDetailTabs.exceptions}|${RuleDetailTabs.executionResults}|${RuleDetailTabs.executionEvents})`,
|
||||
main: RuleDetailsPage,
|
||||
exact: true,
|
||||
},
|
||||
|
@ -49,7 +53,25 @@ const RulesContainerComponent: React.FC = () => {
|
|||
<PluginTemplateWrapper>
|
||||
<TrackApplicationView viewId={SecurityPageName.rules}>
|
||||
<Switch>
|
||||
{RulesSubRoutes.map((route, index) => (
|
||||
<Route // Redirect to first tab if none specified
|
||||
path="/rules/id/:detailName"
|
||||
exact
|
||||
render={({
|
||||
match: {
|
||||
params: { detailName },
|
||||
},
|
||||
location,
|
||||
}) => (
|
||||
<Redirect
|
||||
to={{
|
||||
...location,
|
||||
pathname: `/rules/id/${detailName}/${RuleDetailTabs.alerts}`,
|
||||
search: location.search,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{RulesSubRoutes.map((route) => (
|
||||
<Route
|
||||
key={`rules-route-${route.path}`}
|
||||
path={route.path}
|
||||
|
@ -59,6 +81,7 @@ const RulesContainerComponent: React.FC = () => {
|
|||
</Route>
|
||||
))}
|
||||
<Route component={NotFoundPage} />
|
||||
<SpyRoute pageName={SecurityPageName.rules} />
|
||||
</Switch>
|
||||
</TrackApplicationView>
|
||||
</PluginTemplateWrapper>
|
||||
|
|
|
@ -27148,7 +27148,6 @@
|
|||
"xpack.securitySolution.detectionEngine.ruleDetails.backToRulesButton": "Règles",
|
||||
"xpack.securitySolution.detectionEngine.ruleDetails.deletedRule": "Règle supprimée",
|
||||
"xpack.securitySolution.detectionEngine.ruleDetails.enableRuleLabel": "Activer",
|
||||
"xpack.securitySolution.detectionEngine.ruleDetails.exceptionsTab": "Exceptions",
|
||||
"xpack.securitySolution.detectionEngine.ruleDetails.pageTitle": "Détails de la règle",
|
||||
"xpack.securitySolution.detectionEngine.ruleDetails.ruleExecutionEventsTab": "Événements d’exécution",
|
||||
"xpack.securitySolution.detectionEngine.ruleDetails.ruleExecutionLog.actionFieldNotFoundErrorDescription": "Impossible de trouver le champ \"kibana.alert.rule.execution.uuid\" dans l'index des alertes.",
|
||||
|
|
|
@ -27125,7 +27125,6 @@
|
|||
"xpack.securitySolution.detectionEngine.ruleDetails.backToRulesButton": "ルール",
|
||||
"xpack.securitySolution.detectionEngine.ruleDetails.deletedRule": "削除されたルール",
|
||||
"xpack.securitySolution.detectionEngine.ruleDetails.enableRuleLabel": "有効にする",
|
||||
"xpack.securitySolution.detectionEngine.ruleDetails.exceptionsTab": "例外",
|
||||
"xpack.securitySolution.detectionEngine.ruleDetails.pageTitle": "ルール詳細",
|
||||
"xpack.securitySolution.detectionEngine.ruleDetails.ruleExecutionEventsTab": "実行イベント",
|
||||
"xpack.securitySolution.detectionEngine.ruleDetails.ruleExecutionLog.actionFieldNotFoundErrorDescription": "アラートインデックスにフィールド'kibana.alert.rule.execution.uuid'が見つかりません。",
|
||||
|
|
|
@ -27156,7 +27156,6 @@
|
|||
"xpack.securitySolution.detectionEngine.ruleDetails.backToRulesButton": "规则",
|
||||
"xpack.securitySolution.detectionEngine.ruleDetails.deletedRule": "已删除规则",
|
||||
"xpack.securitySolution.detectionEngine.ruleDetails.enableRuleLabel": "启用",
|
||||
"xpack.securitySolution.detectionEngine.ruleDetails.exceptionsTab": "例外",
|
||||
"xpack.securitySolution.detectionEngine.ruleDetails.pageTitle": "规则详情",
|
||||
"xpack.securitySolution.detectionEngine.ruleDetails.ruleExecutionEventsTab": "执行事件",
|
||||
"xpack.securitySolution.detectionEngine.ruleDetails.ruleExecutionLog.actionFieldNotFoundErrorDescription": "在告警索引中找不到字段“kibana.alert.rule.execution.uuid”。",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue