diff --git a/src/platform/packages/shared/response-ops/rule_form/src/constants/index.ts b/src/platform/packages/shared/response-ops/rule_form/src/constants/index.ts
index d9359c0138acf..01a081cb95cd2 100644
--- a/src/platform/packages/shared/response-ops/rule_form/src/constants/index.ts
+++ b/src/platform/packages/shared/response-ops/rule_form/src/constants/index.ts
@@ -80,3 +80,5 @@ export enum RuleFormStepId {
ACTIONS = 'rule-actions',
DETAILS = 'rule-details',
}
+
+export const MAX_ARTIFACTS_INVESTIGATION_GUIDE_LENGTH = 1000;
diff --git a/src/platform/packages/shared/response-ops/rule_form/src/rule_definition/rule_definition.test.tsx b/src/platform/packages/shared/response-ops/rule_form/src/rule_definition/rule_definition.test.tsx
index 9ee39ca93f1be..2d274a02d5ff4 100644
--- a/src/platform/packages/shared/response-ops/rule_form/src/rule_definition/rule_definition.test.tsx
+++ b/src/platform/packages/shared/response-ops/rule_form/src/rule_definition/rule_definition.test.tsx
@@ -7,8 +7,8 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
-import React from 'react';
-import { fireEvent, render, screen } from '@testing-library/react';
+import React, { type ReactNode } from 'react';
+import { fireEvent, render as rtlRender, screen } from '@testing-library/react';
import type { ChartsPluginSetup } from '@kbn/charts-plugin/public';
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
@@ -112,6 +112,8 @@ const { useRuleFormState, useRuleFormDispatch } = jest.requireMock('../hooks');
const mockOnChange = jest.fn();
+const render = (toRender: ReactNode) => rtlRender(toRender, { wrapper: IntlProvider });
+
describe('Rule Definition', () => {
beforeEach(() => {
useRuleFormDispatch.mockReturnValue(mockOnChange);
diff --git a/src/platform/packages/shared/response-ops/rule_form/src/rule_details/rule_details.test.tsx b/src/platform/packages/shared/response-ops/rule_form/src/rule_details/rule_details.test.tsx
index 0348b89262147..5e2117ed54f46 100644
--- a/src/platform/packages/shared/response-ops/rule_form/src/rule_details/rule_details.test.tsx
+++ b/src/platform/packages/shared/response-ops/rule_form/src/rule_details/rule_details.test.tsx
@@ -8,13 +8,12 @@
*/
import React from 'react';
-import { fireEvent, render, screen, within } from '@testing-library/react';
+import { fireEvent, render as rtlRender, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import type { ContentManagementPublicStart } from '@kbn/content-management-plugin/public';
+import { __IntlProvider as IntlProvider } from '@kbn/i18n-react';
import { RuleDetails } from './rule_details';
-const mockOnChange = jest.fn();
-
jest.mock('../hooks', () => ({
useRuleFormState: jest.fn(),
useRuleFormDispatch: jest.fn(),
@@ -22,6 +21,13 @@ jest.mock('../hooks', () => ({
const { useRuleFormState, useRuleFormDispatch } = jest.requireMock('../hooks');
+const render = (toRender: React.ReactElement) =>
+ rtlRender(toRender, {
+ wrapper: ({ children }) => {children},
+ });
+
+const mockOnChange = jest.fn();
+
describe('RuleDetails', () => {
beforeEach(() => {
useRuleFormState.mockReturnValue({
@@ -88,4 +94,57 @@ describe('RuleDetails', () => {
expect(screen.getByText('name is invalid')).toBeInTheDocument();
expect(screen.getByText('tags is invalid')).toBeInTheDocument();
});
+
+ test('should call dispatch with artifacts object when investigation guide is added', async () => {
+ useRuleFormState.mockReturnValue({
+ plugins: {
+ contentManagement: {} as ContentManagementPublicStart,
+ },
+ formData: {
+ id: 'test-id',
+ params: {},
+ schedule: {
+ interval: '1m',
+ },
+ alertDelay: {
+ active: 5,
+ },
+ notifyWhen: null,
+ consumer: 'stackAlerts',
+ ruleTypeId: '.es-query',
+ },
+ canShowConsumerSelection: true,
+ validConsumers: ['logs', 'stackAlerts'],
+ });
+ render();
+
+ const investigationGuideEditor = screen.getByTestId('investigationGuideEditor');
+ const investigationGuideTextArea = screen.getByLabelText(
+ 'Add guidelines for addressing alerts created by this rule'
+ );
+ expect(investigationGuideEditor).toBeInTheDocument();
+ expect(investigationGuideEditor).toBeVisible();
+ expect(
+ screen.getByPlaceholderText('Add guidelines for addressing alerts created by this rule')
+ );
+
+ fireEvent.change(investigationGuideTextArea, {
+ target: {
+ value: '# Example investigation guide',
+ },
+ });
+
+ expect(mockOnChange).toHaveBeenCalledWith({
+ type: 'setRuleProperty',
+ payload: {
+ property: 'artifacts',
+ value: {
+ investigation_guide: {
+ blob: '# Example investigation guide',
+ },
+ },
+ },
+ });
+ expect(mockOnChange).toHaveBeenCalledTimes(1);
+ });
});
diff --git a/src/platform/packages/shared/response-ops/rule_form/src/rule_details/rule_details.tsx b/src/platform/packages/shared/response-ops/rule_form/src/rule_details/rule_details.tsx
index 4c0be541f9f9e..8d0267acdda46 100644
--- a/src/platform/packages/shared/response-ops/rule_form/src/rule_details/rule_details.tsx
+++ b/src/platform/packages/shared/response-ops/rule_form/src/rule_details/rule_details.tsx
@@ -15,11 +15,24 @@ import {
EuiComboBoxOptionOption,
EuiFlexGroup,
EuiFlexItem,
+ EuiSpacer,
+ EuiIconTip,
} from '@elastic/eui';
-import { RULE_NAME_INPUT_TITLE, RULE_TAG_INPUT_TITLE, RULE_TAG_PLACEHOLDER } from '../translations';
+import { i18n } from '@kbn/i18n';
+
+import {
+ RULE_INVESTIGATION_GUIDE_LABEL,
+ RULE_NAME_INPUT_TITLE,
+ RULE_TAG_INPUT_TITLE,
+ RULE_TAG_PLACEHOLDER,
+} from '../translations';
import { useRuleFormState, useRuleFormDispatch } from '../hooks';
import { OptionalFieldLabel } from '../optional_field_label';
+import { InvestigationGuideEditor } from './rule_investigation_guide_editor';
import { RuleDashboards } from './rule_dashboards';
+import { MAX_ARTIFACTS_INVESTIGATION_GUIDE_LENGTH } from '../constants';
+
+export const RULE_DETAIL_MIN_ROW_WIDTH = 600;
export const RuleDetails = () => {
const { formData, baseErrors, plugins } = useRuleFormState();
@@ -72,6 +85,19 @@ export const RuleDetails = () => {
}
}, [dispatch, tags]);
+ const onSetArtifacts = useCallback(
+ (value: object) => {
+ dispatch({
+ type: 'setRuleProperty',
+ payload: {
+ property: 'artifacts',
+ value: formData.artifacts ? { ...formData.artifacts, ...value } : value,
+ },
+ });
+ },
+ [dispatch, formData.artifacts]
+ );
+
return (
<>
@@ -113,7 +139,43 @@ export const RuleDetails = () => {
+
+
+ {RULE_INVESTIGATION_GUIDE_LABEL}
+
+
+ {i18n.translate(
+ 'responseOpsRuleForm.ruleDetails.investigationGuideFormRow.toolTip.content',
+ {
+ defaultMessage:
+ 'These details will be included in a new tab on the alert details page for every alert triggered by this rule.',
+ }
+ )}
+
+ }
+ />
+
+
+ }
+ labelAppend={OptionalFieldLabel}
+ isInvalid={
+ (formData.artifacts?.investigation_guide?.blob?.length ?? 0) >
+ MAX_ARTIFACTS_INVESTIGATION_GUIDE_LENGTH
+ }
+ >
+
+
{contentManagement && }
+
>
);
};
diff --git a/src/platform/packages/shared/response-ops/rule_form/src/rule_details/rule_investigation_guide_editor.test.tsx b/src/platform/packages/shared/response-ops/rule_form/src/rule_details/rule_investigation_guide_editor.test.tsx
new file mode 100644
index 0000000000000..8605a68a94093
--- /dev/null
+++ b/src/platform/packages/shared/response-ops/rule_form/src/rule_details/rule_investigation_guide_editor.test.tsx
@@ -0,0 +1,46 @@
+/*
+ * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
+ * Public License v 1"; you may not use this file except in compliance with, at
+ * your election, the "Elastic License 2.0", the "GNU Affero General Public
+ * License v3.0 only", or the "Server Side Public License, v 1".
+ */
+
+import React from 'react';
+import { render as rtlRender, screen } from '@testing-library/react';
+import { __IntlProvider as IntlProvider } from '@kbn/i18n-react';
+import { InvestigationGuideEditor } from './rule_investigation_guide_editor';
+import { userEvent } from '@testing-library/user-event';
+
+const render = (toRender: any) => rtlRender(toRender, { wrapper: IntlProvider });
+
+describe('RuleInvestigationGuide', () => {
+ it('should render the investigation guide when provided', () => {
+ const setRuleParams = jest.fn();
+ render();
+ const editorElement = screen.getByLabelText(
+ 'Add guidelines for addressing alerts created by this rule'
+ );
+ expect(editorElement).toBeInTheDocument();
+ });
+
+ it('should call setRuleParams when the value changes', async () => {
+ const setRuleParams = jest.fn();
+ render();
+ const editorElement = screen.getByLabelText(
+ 'Add guidelines for addressing alerts created by this rule'
+ );
+ expect(editorElement).toBeInTheDocument();
+ expect(editorElement).toHaveValue('# Markdown Summary');
+ expect(setRuleParams).toHaveBeenCalledTimes(0);
+
+ await userEvent.type(editorElement!, '!');
+
+ expect(setRuleParams).toHaveBeenCalled();
+ expect(setRuleParams.mock.calls[0]).toHaveLength(1);
+ expect(setRuleParams.mock.calls[0][0]).toEqual({
+ investigation_guide: { blob: '# Markdown Summary!' },
+ });
+ });
+});
diff --git a/src/platform/packages/shared/response-ops/rule_form/src/rule_details/rule_investigation_guide_editor.tsx b/src/platform/packages/shared/response-ops/rule_form/src/rule_details/rule_investigation_guide_editor.tsx
new file mode 100644
index 0000000000000..ad4bfcd9319df
--- /dev/null
+++ b/src/platform/packages/shared/response-ops/rule_form/src/rule_details/rule_investigation_guide_editor.tsx
@@ -0,0 +1,68 @@
+/*
+ * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
+ * Public License v 1"; you may not use this file except in compliance with, at
+ * your election, the "Elastic License 2.0", the "GNU Affero General Public
+ * License v3.0 only", or the "Server Side Public License, v 1".
+ */
+
+import { EuiMarkdownAstNode, EuiMarkdownEditor, EuiMarkdownParseError } from '@elastic/eui';
+import { css } from '@emotion/react';
+import { i18n } from '@kbn/i18n';
+import React, { useCallback } from 'react';
+import { MAX_ARTIFACTS_INVESTIGATION_GUIDE_LENGTH } from '../constants';
+
+interface Props {
+ setRuleParams: (v: { investigation_guide: { blob: string } }) => void;
+ value: string;
+}
+
+export function InvestigationGuideEditor({ setRuleParams, value }: Props) {
+ const [errorMessages, setErrorMessages] = React.useState([]);
+ const onParse = useCallback(
+ (_: EuiMarkdownParseError | null, { ast }: { ast: EuiMarkdownAstNode }) => {
+ const length = ast.position?.end.offset ?? 0;
+ if (length > MAX_ARTIFACTS_INVESTIGATION_GUIDE_LENGTH) {
+ setErrorMessages([
+ i18n.translate('responseOpsRuleForm.investigationGuide.editor.errorMessage', {
+ defaultMessage:
+ 'The Investigation Guide is too long. Please shorten it.\nCurrent length: {length}.\nMax length: {maxLength}.',
+ values: { length, maxLength: MAX_ARTIFACTS_INVESTIGATION_GUIDE_LENGTH },
+ }),
+ ]);
+ } else if (errorMessages.length) {
+ setErrorMessages([]);
+ }
+ },
+ [errorMessages]
+ );
+ return (
+ setRuleParams({ investigation_guide: { blob } })}
+ onParse={onParse}
+ errors={errorMessages}
+ height={200}
+ data-test-subj="investigationGuideEditor"
+ initialViewMode="editing"
+ />
+ );
+}
diff --git a/src/platform/packages/shared/response-ops/rule_form/src/translations.ts b/src/platform/packages/shared/response-ops/rule_form/src/translations.ts
index df3e1d8e8c5f3..b3eb526a973e5 100644
--- a/src/platform/packages/shared/response-ops/rule_form/src/translations.ts
+++ b/src/platform/packages/shared/response-ops/rule_form/src/translations.ts
@@ -227,6 +227,13 @@ export const RULE_ALERT_DELAY_BELOW_MINIMUM_TEXT = i18n.translate(
}
);
+export const RULE_INVESTIGATION_GUIDE_TOO_LONG_TEXT = (length: number, maxLength: number) =>
+ i18n.translate('responseOpsRuleForm.ruleForm.error.investigationGuideTooLongText', {
+ defaultMessage:
+ 'Investigation guide is too long. Current length: {length}. Max length: {maxLength}.',
+ values: { length, maxLength },
+ });
+
export const INTERVAL_MINIMUM_TEXT = (minimum: string) =>
i18n.translate('responseOpsRuleForm.ruleForm.error.belowMinimumText', {
defaultMessage: 'Interval must be at least {minimum}.',
@@ -297,6 +304,13 @@ export const RULE_TAG_PLACEHOLDER = i18n.translate(
}
);
+export const RULE_INVESTIGATION_GUIDE_LABEL = i18n.translate(
+ 'responseOpsRuleForm.ruleForm.ruleDetails.investigationGuide.editor.title',
+ {
+ defaultMessage: 'Investigation guide',
+ }
+);
+
export const RULE_NAME_ARIA_LABEL_TEXT = i18n.translate(
'responseOpsRuleForm.ruleForm.rulePage.ruleNameAriaLabelText',
{
diff --git a/src/platform/packages/shared/response-ops/rule_form/src/validation/validate_form.ts b/src/platform/packages/shared/response-ops/rule_form/src/validation/validate_form.ts
index 6139d3504f918..6abf6828ac4fd 100644
--- a/src/platform/packages/shared/response-ops/rule_form/src/validation/validate_form.ts
+++ b/src/platform/packages/shared/response-ops/rule_form/src/validation/validate_form.ts
@@ -18,6 +18,7 @@ import {
INTERVAL_REQUIRED_TEXT,
INTERVAL_MINIMUM_TEXT,
RULE_ALERT_DELAY_BELOW_MINIMUM_TEXT,
+ RULE_INVESTIGATION_GUIDE_TOO_LONG_TEXT,
} from '../translations';
import type {
MinimumScheduleInterval,
@@ -27,6 +28,7 @@ import type {
RuleTypeModel,
RuleUiAction,
} from '../common';
+import { MAX_ARTIFACTS_INVESTIGATION_GUIDE_LENGTH } from '../constants';
export const validateAction = ({ action }: { action: RuleUiAction }): RuleFormActionsErrors => {
const errors = {
@@ -64,6 +66,7 @@ export function validateRuleBase({
actionConnectors: new Array(),
alertDelay: new Array(),
tags: new Array(),
+ artifacts: new Array(),
};
if (!formData.name) {
@@ -94,6 +97,16 @@ export function validateRuleBase({
errors.alertDelay.push(RULE_ALERT_DELAY_BELOW_MINIMUM_TEXT);
}
+ const investigationGuideLength = formData.artifacts?.investigation_guide?.blob.length ?? 0;
+ if (investigationGuideLength > MAX_ARTIFACTS_INVESTIGATION_GUIDE_LENGTH) {
+ errors.artifacts.push(
+ RULE_INVESTIGATION_GUIDE_TOO_LONG_TEXT(
+ investigationGuideLength,
+ MAX_ARTIFACTS_INVESTIGATION_GUIDE_LENGTH
+ )
+ );
+ }
+
return errors;
}
diff --git a/x-pack/platform/plugins/shared/alerting/server/routes/rule/apis/update/transforms/transform_update_body/v1.test.ts b/x-pack/platform/plugins/shared/alerting/server/routes/rule/apis/update/transforms/transform_update_body/v1.test.ts
new file mode 100644
index 0000000000000..7dd15a4c913ec
--- /dev/null
+++ b/x-pack/platform/plugins/shared/alerting/server/routes/rule/apis/update/transforms/transform_update_body/v1.test.ts
@@ -0,0 +1,200 @@
+/*
+ * 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 type { UpdateRuleRequestBodyV1 } from '../../../../../../../common/routes/rule/apis/update';
+import { transformUpdateBody } from './v1';
+
+describe('transformUpdateBody', () => {
+ let baseUpdateBody: UpdateRuleRequestBodyV1<{}>;
+ let baseActions: UpdateRuleRequestBodyV1<{}>['actions'];
+ let baseSystemActions: UpdateRuleRequestBodyV1<{}>['actions'];
+ beforeEach(() => {
+ baseUpdateBody = {
+ name: 'Test Rule',
+ tags: ['tag1', 'tag2'],
+ throttle: '1m',
+ params: { param1: 'value1' },
+ schedule: { interval: '1m' },
+ notify_when: 'onActionGroupChange' as 'onActionGroupChange',
+ alert_delay: { active: 5 },
+ flapping: {
+ look_back_window: 10,
+ status_change_threshold: 5,
+ },
+ artifacts: {
+ dashboards: [{ id: 'dashboard1' }],
+ investigation_guide: { blob: 'guide-content' },
+ },
+ actions: [],
+ };
+ baseActions = [
+ {
+ group: 'default',
+ id: 'action1',
+ params: { key: 'value' },
+ frequency: {
+ notify_when: 'onThrottleInterval',
+ throttle: '1m',
+ summary: true,
+ },
+ alerts_filter: {},
+ use_alert_data_for_template: true,
+ },
+ ];
+ baseSystemActions = [
+ {
+ id: 'systemAction1',
+ params: { key: 'value' },
+ },
+ ];
+ });
+
+ it('should transform the update body with all fields populated', () => {
+ const result = transformUpdateBody({
+ updateBody: baseUpdateBody,
+ actions: baseActions,
+ systemActions: baseSystemActions,
+ });
+
+ expect(result).toEqual({
+ name: 'Test Rule',
+ tags: ['tag1', 'tag2'],
+ throttle: '1m',
+ params: { param1: 'value1' },
+ schedule: { interval: '1m' },
+ notifyWhen: 'onActionGroupChange',
+ alertDelay: { active: 5 },
+ flapping: {
+ lookBackWindow: 10,
+ statusChangeThreshold: 5,
+ },
+ artifacts: {
+ dashboards: [{ id: 'dashboard1' }],
+ investigation_guide: { blob: 'guide-content' },
+ },
+ actions: [
+ {
+ group: 'default',
+ id: 'action1',
+ params: { key: 'value' },
+ frequency: {
+ throttle: '1m',
+ summary: true,
+ notifyWhen: 'onThrottleInterval',
+ },
+ alertsFilter: {},
+ useAlertDataForTemplate: true,
+ },
+ ],
+ systemActions: [
+ {
+ id: 'systemAction1',
+ params: { key: 'value' },
+ },
+ ],
+ });
+ });
+
+ it('should handle missing optional fields', () => {
+ const result = transformUpdateBody({
+ updateBody: {
+ ...baseUpdateBody,
+ name: 'Test Rule',
+ tags: ['tag1'],
+ params: { param1: 'value1' },
+ schedule: { interval: '1m' },
+ },
+ actions: [],
+ systemActions: [],
+ });
+
+ expect(result).toMatchInlineSnapshot(`
+ Object {
+ "actions": Array [],
+ "alertDelay": Object {
+ "active": 5,
+ },
+ "artifacts": Object {
+ "dashboards": Array [
+ Object {
+ "id": "dashboard1",
+ },
+ ],
+ "investigation_guide": Object {
+ "blob": "guide-content",
+ },
+ },
+ "flapping": Object {
+ "lookBackWindow": 10,
+ "statusChangeThreshold": 5,
+ },
+ "name": "Test Rule",
+ "notifyWhen": "onActionGroupChange",
+ "params": Object {
+ "param1": "value1",
+ },
+ "schedule": Object {
+ "interval": "1m",
+ },
+ "systemActions": Array [],
+ "tags": Array [
+ "tag1",
+ ],
+ "throttle": "1m",
+ }
+ `);
+ });
+
+ it('should omit flapping when undefined', () => {
+ const result = transformUpdateBody({
+ updateBody: {
+ ...baseUpdateBody,
+ name: 'Test Rule',
+ tags: ['tag1'],
+ params: { param1: 'value1' },
+ schedule: { interval: '1m' },
+ flapping: undefined,
+ },
+ actions: [],
+ systemActions: [],
+ });
+
+ expect(result.flapping).not.toBeDefined();
+ });
+
+ it('should handle missing frequency in actions', () => {
+ const result = transformUpdateBody({
+ updateBody: {
+ ...baseUpdateBody,
+ name: 'Test Rule',
+ tags: ['tag1'],
+ params: { param1: 'value1' },
+ schedule: { interval: '1m' },
+ },
+ actions: [
+ {
+ group: 'default',
+ id: 'action1',
+ params: { key: 'value' },
+ },
+ ],
+ systemActions: baseSystemActions,
+ });
+
+ expect(result.actions).toMatchInlineSnapshot(`
+ Array [
+ Object {
+ "group": "default",
+ "id": "action1",
+ "params": Object {
+ "key": "value",
+ },
+ },
+ ]
+ `);
+ });
+});
diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/alert_details.tsx b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/alert_details.tsx
index 612197669380d..e5bb0c2333502 100644
--- a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/alert_details.tsx
+++ b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/alert_details.tsx
@@ -20,6 +20,7 @@ import {
EuiTabbedContentTab,
useEuiTheme,
EuiFlexGroup,
+ EuiMarkdownFormat,
EuiNotificationBadge,
} from '@elastic/eui';
import {
@@ -75,9 +76,14 @@ export const METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID = 'metrics.alert.inventory
const OVERVIEW_TAB_ID = 'overview';
const METADATA_TAB_ID = 'metadata';
const RELATED_ALERTS_TAB_ID = 'related_alerts';
+const INVESTIGATION_GUIDE_TAB_ID = 'investigation_guide';
const ALERT_DETAILS_TAB_URL_STORAGE_KEY = 'tabId';
const RELATED_DASHBOARDS_TAB_ID = 'related_dashboards';
-type TabId = typeof OVERVIEW_TAB_ID | typeof METADATA_TAB_ID | typeof RELATED_ALERTS_TAB_ID;
+type TabId =
+ | typeof OVERVIEW_TAB_ID
+ | typeof METADATA_TAB_ID
+ | typeof RELATED_ALERTS_TAB_ID
+ | typeof INVESTIGATION_GUIDE_TAB_ID;
export const getPageTitle = (ruleCategory: string) => {
return i18n.translate('xpack.observability.pages.alertDetails.pageTitle.title', {
@@ -123,7 +129,13 @@ export function AlertDetails() {
const searchParams = new URLSearchParams(search);
const urlTabId = searchParams.get(ALERT_DETAILS_TAB_URL_STORAGE_KEY);
- return urlTabId && [OVERVIEW_TAB_ID, METADATA_TAB_ID, RELATED_ALERTS_TAB_ID].includes(urlTabId)
+ return urlTabId &&
+ [
+ OVERVIEW_TAB_ID,
+ METADATA_TAB_ID,
+ RELATED_ALERTS_TAB_ID,
+ INVESTIGATION_GUIDE_TAB_ID,
+ ].includes(urlTabId)
? (urlTabId as TabId)
: OVERVIEW_TAB_ID;
});
@@ -317,6 +329,29 @@ export function AlertDetails() {
'data-test-subj': 'metadataTab',
content: metadataTab,
},
+ {
+ id: 'investigation_guide',
+ name: (
+
+ ),
+ 'data-test-subj': 'investigationGuideTab',
+ disabled: !rule?.artifacts?.investigation_guide?.blob,
+ content: (
+ <>
+
+
+ {rule?.artifacts?.investigation_guide?.blob ?? ''}
+
+ >
+ ),
+ },
{
id: RELATED_ALERTS_TAB_ID,
name: (
diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts
index e9e35657e242e..7496381f65214 100644
--- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts
+++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts
@@ -411,13 +411,15 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
);
await firstDropdown.click();
await firstDropdown.type('kibana.alert.action_group');
- await find.clickByButtonText('kibana.alert.action_group');
+ const filterKeyOptionsList = await find.byCssSelector('.euiComboBoxOptionsList');
+ await find.clickByButtonText('kibana.alert.action_group', filterKeyOptionsList);
const secondDropdown = await find.byCssSelector(
'[data-test-subj="filter-0.1"] [data-test-subj="filterOperatorList"] [data-test-subj="comboBoxSearchInput"]'
);
await secondDropdown.click();
await secondDropdown.type('exists');
- await find.clickByButtonText('exists');
+ const filterOperationOptionsList = await find.byCssSelector('.euiComboBoxOptionsList');
+ await find.clickByButtonText('exists', filterOperationOptionsList);
await testSubjects.click('saveFilter');
await testSubjects.setValue('queryInput', '_id: *');