[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:
Yara Tercero 2022-08-31 13:05:31 -07:00 committed by GitHub
parent 160058a8c1
commit 082a3629b4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 206 additions and 177 deletions

View file

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

View file

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

View file

@ -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 = () => {

View file

@ -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)}`;

View file

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

View file

@ -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: [],

View file

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

View file

@ -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',
}
);

View file

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

View file

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

View file

@ -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 dexécution",
"xpack.securitySolution.detectionEngine.ruleDetails.ruleExecutionLog.actionFieldNotFoundErrorDescription": "Impossible de trouver le champ \"kibana.alert.rule.execution.uuid\" dans l'index des alertes.",

View file

@ -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'が見つかりません。",

View file

@ -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”。",