[ResponseOps][Rules] Use rule form instead of rule flyout in observability solution (#206774)

## Summary

Resolves https://github.com/elastic/kibana/issues/195574

This PR updates observability solution to use new rule form to `create`
and `edit` rules same as `stack management > rules` page.
It removes usage of rule flyout form o11y solution. Also updated
functional tests.

### Checklist

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios


### How to test
- Create a rule in o11y, verify it works as expected
- Edit rule in o11y via different options (from rule details page, rule
list table, alert details page etc.) verify it works as expected
- Verify the same in serverless o11y project

### Release Note
Use rule form to create or edit rules in observability.

---------

Co-authored-by: Maryam Saeidi <maryam.saeidi@elastic.co>
This commit is contained in:
Janki Salvi 2025-01-24 13:31:56 +00:00 committed by GitHub
parent 1ca4d967d9
commit dd6376d3be
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 469 additions and 61 deletions

View file

@ -44,6 +44,7 @@ export interface CreateRuleFormProps {
shouldUseRuleProducer?: boolean;
canShowConsumerSelection?: boolean;
showMustacheAutocompleteSwitch?: boolean;
isServerless?: boolean;
onCancel?: () => void;
onSubmit?: (ruleId: string) => void;
}
@ -60,6 +61,7 @@ export const CreateRuleForm = (props: CreateRuleFormProps) => {
shouldUseRuleProducer = false,
canShowConsumerSelection = true,
showMustacheAutocompleteSwitch = false,
isServerless = false,
onCancel,
onSubmit,
} = props;
@ -195,6 +197,7 @@ export const CreateRuleForm = (props: CreateRuleFormProps) => {
validConsumers,
ruleType,
ruleTypes,
isServerless,
}),
}}
>

View file

@ -10,6 +10,7 @@
import React, { useMemo } from 'react';
import { EuiEmptyPrompt, EuiText } from '@elastic/eui';
import { QueryClientProvider, QueryClient } from '@tanstack/react-query';
import { type RuleCreationValidConsumer } from '@kbn/rule-data-utils';
import { useParams } from 'react-router-dom';
import { CreateRuleForm } from './create_rule_form';
import { EditRuleForm } from './edit_rule_form';
@ -27,10 +28,20 @@ export interface RuleFormProps {
plugins: RuleFormPlugins;
onCancel?: () => void;
onSubmit?: (ruleId: string) => void;
validConsumers?: RuleCreationValidConsumer[];
multiConsumerSelection?: RuleCreationValidConsumer | null;
isServerless?: boolean;
}
export const RuleForm = (props: RuleFormProps) => {
const { plugins: _plugins, onCancel, onSubmit } = props;
const {
plugins: _plugins,
onCancel,
onSubmit,
validConsumers,
multiConsumerSelection,
isServerless,
} = props;
const { id, ruleTypeId } = useParams<{
id?: string;
ruleTypeId?: string;
@ -80,6 +91,9 @@ export const RuleForm = (props: RuleFormProps) => {
plugins={plugins}
onCancel={onCancel}
onSubmit={onSubmit}
validConsumers={validConsumers}
multiConsumerSelection={multiConsumerSelection}
isServerless={isServerless}
/>
);
}
@ -112,6 +126,9 @@ export const RuleForm = (props: RuleFormProps) => {
actionTypeRegistry,
id,
ruleTypeId,
validConsumers,
multiConsumerSelection,
isServerless,
onCancel,
onSubmit,
]);

View file

@ -0,0 +1,247 @@
/*
* 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 { RuleTypeWithDescription } from '@kbn/alerts-ui-shared';
import { getInitialMultiConsumer } from './get_initial_multi_consumer';
describe('getInitialMultiConsumer', () => {
const ruleType = {
id: '.es-query',
name: 'Test',
actionGroups: [
{
id: 'testActionGroup',
name: 'Test Action Group',
},
{
id: 'recovered',
name: 'Recovered',
},
],
defaultActionGroupId: 'testActionGroup',
minimumLicenseRequired: 'basic',
recoveryActionGroup: {
id: 'recovered',
name: 'Recovered',
},
producer: 'logs',
authorizedConsumers: {
alerting: { read: true, all: true },
test: { read: true, all: true },
stackAlerts: { read: true, all: true },
logs: { read: true, all: true },
},
actionVariables: {
params: [],
state: [],
},
enabledInLicense: true,
category: 'test',
} as RuleTypeWithDescription;
const ruleTypes = [
{
id: '.es-query',
name: 'Test',
actionGroups: [
{
id: 'testActionGroup',
name: 'Test Action Group',
},
{
id: 'recovered',
name: 'Recovered',
},
],
defaultActionGroupId: 'testActionGroup',
minimumLicenseRequired: 'basic',
recoveryActionGroup: {
id: 'recovered',
},
producer: 'logs',
authorizedConsumers: {
alerting: { read: true, all: true },
test: { read: true, all: true },
stackAlerts: { read: true, all: true },
logs: { read: true, all: true },
},
actionVariables: {
params: [],
state: [],
},
enabledInLicense: true,
},
{
enabledInLicense: true,
recoveryActionGroup: {
id: 'recovered',
name: 'Recovered',
},
actionGroups: [],
defaultActionGroupId: 'threshold met',
minimumLicenseRequired: 'basic',
authorizedConsumers: {
stackAlerts: {
read: true,
all: true,
},
},
actionVariables: {
params: [],
state: [],
},
id: '.index-threshold',
name: 'Index threshold',
category: 'management',
producer: 'stackAlerts',
},
] as RuleTypeWithDescription[];
test('should return null when rule type id does not match', () => {
const res = getInitialMultiConsumer({
multiConsumerSelection: null,
validConsumers: ['logs', 'observability'],
ruleType: {
...ruleType,
id: 'test',
},
ruleTypes,
isServerless: false,
});
expect(res).toBe(null);
});
test('should return null when no valid consumers', () => {
const res = getInitialMultiConsumer({
multiConsumerSelection: null,
validConsumers: [],
ruleType,
ruleTypes,
isServerless: false,
});
expect(res).toBe(null);
});
test('should return same valid consumer when only one valid consumer', () => {
const res = getInitialMultiConsumer({
multiConsumerSelection: null,
validConsumers: ['alerts'],
ruleType,
ruleTypes,
isServerless: false,
});
expect(res).toBe('alerts');
});
test('should not return observability consumer for non serverless', () => {
const res = getInitialMultiConsumer({
multiConsumerSelection: null,
validConsumers: ['logs', 'infrastructure', 'observability'],
ruleType,
ruleTypes,
isServerless: false,
});
expect(res).toBe('logs');
});
test('should return observability consumer for serverless', () => {
const res = getInitialMultiConsumer({
multiConsumerSelection: null,
validConsumers: ['logs', 'infrastructure', 'observability'],
ruleType,
ruleTypes,
isServerless: true,
});
expect(res).toBe('observability');
});
test('should return null when there is no authorized consumers', () => {
const res = getInitialMultiConsumer({
multiConsumerSelection: null,
validConsumers: ['alerts', 'infrastructure'],
ruleType: {
...ruleType,
authorizedConsumers: {},
},
ruleTypes,
isServerless: false,
});
expect(res).toBe(null);
});
test('should return null when multiConsumerSelection is null', () => {
const res = getInitialMultiConsumer({
multiConsumerSelection: null,
validConsumers: ['stackAlerts', 'logs'],
ruleType: {
...ruleType,
authorizedConsumers: {
stackAlerts: { read: true, all: true },
},
},
ruleTypes,
isServerless: false,
});
expect(res).toBe(null);
});
test('should return valid multi consumer correctly', () => {
const res = getInitialMultiConsumer({
multiConsumerSelection: 'logs',
validConsumers: ['stackAlerts', 'logs'],
ruleType: {
...ruleType,
authorizedConsumers: {
stackAlerts: { read: true, all: true },
},
},
ruleTypes,
isServerless: false,
});
expect(res).toBe('logs');
});
test('should return stackAlerts correctly', () => {
const res = getInitialMultiConsumer({
multiConsumerSelection: 'alerts',
validConsumers: ['stackAlerts', 'logs'],
ruleType: {
...ruleType,
authorizedConsumers: {},
},
ruleTypes,
isServerless: false,
});
expect(res).toBe('stackAlerts');
});
test('should return null valid consumer correctly', () => {
const res = getInitialMultiConsumer({
multiConsumerSelection: 'alerts',
validConsumers: ['infrastructure', 'logs'],
ruleType: {
...ruleType,
authorizedConsumers: {},
},
ruleTypes: [],
isServerless: false,
});
expect(res).toBe(null);
});
});

View file

@ -35,11 +35,13 @@ export const getInitialMultiConsumer = ({
validConsumers,
ruleType,
ruleTypes,
isServerless,
}: {
multiConsumerSelection?: RuleCreationValidConsumer | null;
validConsumers: RuleCreationValidConsumer[];
ruleType: RuleTypeWithDescription;
ruleTypes: RuleTypeWithDescription[];
isServerless?: boolean;
}): RuleCreationValidConsumer | null => {
// If rule type doesn't support multi-consumer or no valid consumers exists,
// return nothing
@ -52,8 +54,8 @@ export const getInitialMultiConsumer = ({
return validConsumers[0];
}
// If o11y is in the valid consumers, just use that
if (validConsumers.includes(AlertConsumers.OBSERVABILITY)) {
// If o11y is in the valid consumers and it is serverless, just use that
if (isServerless && validConsumers.includes(AlertConsumers.OBSERVABILITY)) {
return AlertConsumers.OBSERVABILITY;
}

View file

@ -15,6 +15,7 @@ export const EXPLORATORY_VIEW_PATH = '/exploratory-view' as const; // has been m
export const RULES_PATH = '/alerts/rules' as const;
export const RULES_LOGS_PATH = '/alerts/rules/logs' as const;
export const RULE_DETAIL_PATH = '/alerts/rules/:ruleId' as const;
export const CREATE_RULE_PATH = '/alerts/rules/create/:ruleTypeId' as const;
export const CASES_PATH = '/cases' as const;
export const ANNOTATIONS_PATH = '/annotations' as const;
export const SETTINGS_PATH = '/slos/settings' as const;
@ -37,6 +38,8 @@ export const paths = {
rules: `${OBSERVABILITY_BASE_PATH}${RULES_PATH}`,
ruleDetails: (ruleId: string) =>
`${OBSERVABILITY_BASE_PATH}${RULES_PATH}/${encodeURIComponent(ruleId)}`,
createRule: (ruleTypeId: string) =>
`${OBSERVABILITY_BASE_PATH}${RULES_PATH}/create/${encodeURIComponent(ruleTypeId)}`,
},
};

View file

@ -0,0 +1,102 @@
/*
* 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 { i18n } from '@kbn/i18n';
import React from 'react';
import { RuleForm } from '@kbn/response-ops-rule-form';
import { useLocation } from 'react-router-dom';
import { AlertConsumers } from '@kbn/rule-data-utils';
import { useBreadcrumbs } from '@kbn/observability-shared-plugin/public';
import { HeaderMenu } from '../overview/components/header_menu/header_menu';
import { useKibana } from '../../utils/kibana_react';
import { paths } from '../../../common/locators/paths';
import { observabilityRuleCreationValidConsumers } from '../../../common/constants';
import { usePluginContext } from '../../hooks/use_plugin_context';
export function RulePage() {
const {
http,
docLinks,
observabilityAIAssistant,
application,
notifications,
charts,
settings,
data,
dataViews,
unifiedSearch,
serverless,
actionTypeRegistry,
ruleTypeRegistry,
chrome,
...startServices
} = useKibana().services;
const { ObservabilityPageTemplate } = usePluginContext();
const location = useLocation<{ returnApp?: string; returnPath?: string }>();
const { returnApp, returnPath } = location.state || {};
useBreadcrumbs(
[
{
text: i18n.translate('xpack.observability.breadcrumbs.alertsLinkText', {
defaultMessage: 'Alerts',
}),
href: http.basePath.prepend(paths.observability.alerts),
deepLinkId: 'observability-overview:alerts',
},
{
href: http.basePath.prepend(paths.observability.rules),
text: i18n.translate('xpack.observability.breadcrumbs.rulesLinkText', {
defaultMessage: 'Rules',
}),
},
{
text: i18n.translate('xpack.observability.breadcrumbs.createLinkText', {
defaultMessage: 'Create',
}),
},
],
{ serverless }
);
return (
<ObservabilityPageTemplate data-test-subj="rulePage">
<HeaderMenu />
<RuleForm
plugins={{
http,
application,
notifications,
charts,
settings,
data,
dataViews,
unifiedSearch,
docLinks,
ruleTypeRegistry,
actionTypeRegistry,
...startServices,
}}
validConsumers={observabilityRuleCreationValidConsumers}
multiConsumerSelection={AlertConsumers.LOGS}
isServerless={!!serverless}
onCancel={() => {
if (returnApp && returnPath) {
application.navigateToApp(returnApp, { path: returnPath });
} else {
return application.navigateToUrl(http.basePath.prepend(paths.observability.rules));
}
}}
onSubmit={(ruleId) => {
return application.navigateToUrl(
http.basePath.prepend(paths.observability.ruleDetails(ruleId))
);
}}
/>
</ObservabilityPageTemplate>
);
}

View file

@ -5,14 +5,16 @@
* 2.0.
*/
import React from 'react';
import { fireEvent, render, waitFor } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { useLocation } from 'react-router-dom';
import { ALERTING_FEATURE_ID } from '@kbn/alerting-plugin/common';
import { AppMountParameters, CoreStart } from '@kbn/core/public';
import { __IntlProvider as IntlProvider } from '@kbn/i18n-react';
import { observabilityAIAssistantPluginMock } from '@kbn/observability-ai-assistant-plugin/public/mock';
import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template';
import { render } from '@testing-library/react';
import React from 'react';
import { useLocation } from 'react-router-dom';
import { RuleTypeModalProps } from '@kbn/response-ops-rule-form/src/rule_type_modal/components/rule_type_modal';
import * as pluginContext from '../../hooks/use_plugin_context';
import { ObservabilityPublicPluginsStart } from '../../plugin';
import { createObservabilityRuleTypeRegistryMock } from '../../rules/observability_rule_type_registry_mock';
@ -21,6 +23,12 @@ import { RulesPage } from './rules';
const mockUseKibanaReturnValue = kibanaStartMock.startContract();
const mockObservabilityAIAssistant = observabilityAIAssistantPluginMock.createStartContract();
const mockApplication = {
navigateToApp: jest.fn(),
navigateToUrl: jest.fn(),
};
const queryClient = new QueryClient();
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
@ -34,6 +42,11 @@ jest.mock('../../utils/kibana_react', () => ({
services: {
...mockUseKibanaReturnValue.services,
observabilityAIAssistant: mockObservabilityAIAssistant,
application: {
...mockUseKibanaReturnValue.services.application,
navigateToApp: mockApplication.navigateToApp,
navigateToUrl: mockApplication.navigateToUrl,
},
},
})),
}));
@ -48,6 +61,15 @@ jest.mock('@kbn/triggers-actions-ui-plugin/public', () => ({
useLoadRuleTypesQuery: jest.fn(),
}));
jest.mock('@kbn/response-ops-rule-form/src/rule_type_modal', () => ({
RuleTypeModal: ({ onSelectRuleType }: RuleTypeModalProps) => (
<div data-test-subj="ruleTypeModal">
RuleTypeModal
<button onClick={() => onSelectRuleType('1')}>Rule type 1</button>
</div>
),
}));
const useLocationMock = useLocation as jest.Mock;
jest.spyOn(pluginContext, 'usePluginContext').mockImplementation(() => ({
@ -130,13 +152,17 @@ describe('RulesPage with all capabilities', () => {
useLoadRuleTypesQuery.mockReturnValue({
ruleTypesState: {
isLoading: false,
isInitialLoading: false,
data: ruleTypeIndex,
},
});
return render(
<IntlProvider locale="en">
<RulesPage />
<QueryClientProvider client={queryClient}>
<RulesPage />
</QueryClientProvider>
</IntlProvider>
);
}
@ -155,6 +181,21 @@ describe('RulesPage with all capabilities', () => {
const wrapper = await setup();
expect(wrapper.getByTestId('createRuleButton')).not.toBeDisabled();
});
it('navigates to create rule form correctly', async () => {
const wrapper = await setup();
expect(wrapper.getByTestId('createRuleButton')).toBeInTheDocument();
fireEvent.click(wrapper.getByTestId('createRuleButton'));
expect(await wrapper.findByTestId('ruleTypeModal')).toBeInTheDocument();
fireEvent.click(await wrapper.findByText('Rule type 1'));
await waitFor(() => {
expect(mockApplication.navigateToUrl).toHaveBeenCalledWith(
'/app/observability/alerts/rules/create/1'
);
});
});
});
describe('RulesPage with show only capability', () => {

View file

@ -11,12 +11,10 @@ import { ALERTING_FEATURE_ID } from '@kbn/alerting-plugin/common';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { useBreadcrumbs } from '@kbn/observability-shared-plugin/public';
import { AlertConsumers } from '@kbn/rule-data-utils';
import { useLoadRuleTypesQuery } from '@kbn/triggers-actions-ui-plugin/public';
import React, { lazy, useEffect, useState } from 'react';
import { useHistory } from 'react-router-dom';
import { observabilityRuleCreationValidConsumers } from '../../../common/constants';
import { RULES_LOGS_PATH, RULES_PATH } from '../../../common/locators/paths';
import { RULES_LOGS_PATH, RULES_PATH, paths } from '../../../common/locators/paths';
import { useGetFilteredRuleTypes } from '../../hooks/use_get_filtered_rule_types';
import { usePluginContext } from '../../hooks/use_plugin_context';
import { useKibana } from '../../utils/kibana_react';
@ -37,18 +35,13 @@ export function RulesPage({ activeTab = RULES_TAB_NAME }: RulesPageProps) {
docLinks,
notifications: { toasts },
observabilityAIAssistant,
triggersActionsUi: {
ruleTypeRegistry,
getAddRuleFlyout: AddRuleFlyout,
getRulesSettingsLink: RulesSettingsLink,
},
application,
triggersActionsUi: { ruleTypeRegistry, getRulesSettingsLink: RulesSettingsLink },
serverless,
} = useKibana().services;
const { ObservabilityPageTemplate } = usePluginContext();
const history = useHistory();
const [ruleTypeModalVisibility, setRuleTypeModalVisibility] = useState<boolean>(false);
const [ruleTypeIdToCreate, setRuleTypeIdToCreate] = useState<string | undefined>(undefined);
const [addRuleFlyoutVisibility, setAddRuleFlyoutVisibility] = useState(false);
const [stateRefresh, setRefresh] = useState(new Date());
useBreadcrumbs(
@ -188,9 +181,10 @@ export function RulesPage({ activeTab = RULES_TAB_NAME }: RulesPageProps) {
<RuleTypeModal
onClose={() => setRuleTypeModalVisibility(false)}
onSelectRuleType={(ruleTypeId) => {
setRuleTypeIdToCreate(ruleTypeId);
setRuleTypeModalVisibility(false);
setAddRuleFlyoutVisibility(true);
return application.navigateToUrl(
http.basePath.prepend(paths.observability.createRule(ruleTypeId))
);
}}
http={http}
toasts={toasts}
@ -198,26 +192,6 @@ export function RulesPage({ activeTab = RULES_TAB_NAME }: RulesPageProps) {
filteredRuleTypes={filteredRuleTypes}
/>
)}
{addRuleFlyoutVisibility && (
<AddRuleFlyout
ruleTypeId={ruleTypeIdToCreate}
canChangeTrigger={false}
consumer={ALERTING_FEATURE_ID}
filteredRuleTypes={filteredRuleTypes}
validConsumers={observabilityRuleCreationValidConsumers}
initialSelectedConsumer={AlertConsumers.LOGS}
onClose={() => {
setAddRuleFlyoutVisibility(false);
}}
onSave={() => {
setRefresh(new Date());
return Promise.resolve();
}}
hideGrouping
useRuleProducer
/>
)}
</ObservabilityPageTemplate>
);
}

View file

@ -17,6 +17,7 @@ import { LandingPage } from '../pages/landing/landing';
import { OverviewPage } from '../pages/overview/overview';
import { RulesPage } from '../pages/rules/rules';
import { RuleDetailsPage } from '../pages/rule_details/rule_details';
import { RulePage } from '../pages/rules/rule';
import {
ALERTS_PATH,
ALERT_DETAIL_PATH,
@ -34,6 +35,7 @@ import {
OLD_SLOS_OUTDATED_DEFINITIONS_PATH,
OLD_SLO_DETAIL_PATH,
OLD_SLO_EDIT_PATH,
CREATE_RULE_PATH,
} from '../../common/locators/paths';
import { HasDataContextProvider } from '../context/has_data_context/has_data_context';
@ -133,6 +135,13 @@ export const routes = {
params: {},
exact: true,
},
[CREATE_RULE_PATH]: {
handler: () => {
return <RulePage />;
},
params: {},
exact: true,
},
[ALERT_DETAIL_PATH]: {
handler: () => {
return <AlertDetails />;

View file

@ -9,7 +9,7 @@ import { Key } from 'selenium-webdriver';
import expect from 'expect';
import { FtrProviderContext } from '../../../../ftr_provider_context';
export default ({ getService }: FtrProviderContext) => {
export default ({ getService, getPageObjects }: FtrProviderContext) => {
const esArchiver = getService('esArchiver');
const testSubjects = getService('testSubjects');
const kibanaServer = getService('kibanaServer');
@ -17,9 +17,10 @@ export default ({ getService }: FtrProviderContext) => {
const find = getService('find');
const logger = getService('log');
const retry = getService('retry');
const toasts = getService('toasts');
const PageObjects = getPageObjects(['header']);
// FLAKY: https://github.com/elastic/kibana/issues/196766
describe.skip('Custom threshold rule', function () {
describe('Custom threshold rule', function () {
this.tags('includeFirefox');
const observability = getService('observability');
@ -58,13 +59,16 @@ export default ({ getService }: FtrProviderContext) => {
it('shows the custom threshold rule in the observability section', async () => {
await observability.alerts.rulesPage.clickCreateRuleButton();
await PageObjects.header.waitUntilLoadingHasFinished();
await observability.alerts.rulesPage.clickOnObservabilityCategory();
await observability.alerts.rulesPage.clickOnCustomThresholdRule();
});
it('can add name and tags', async () => {
await testSubjects.setValue('ruleNameInput', 'test custom threshold rule');
await testSubjects.setValue('comboBoxSearchInput', 'tag1');
await testSubjects.setValue('ruleDetailsNameInput', 'test custom threshold rule', {
clearWithKeyboard: true,
});
await testSubjects.setValue('ruleDetailsTagsInput', 'tag1');
});
it('can add data view', async () => {
@ -204,9 +208,11 @@ export default ({ getService }: FtrProviderContext) => {
});
it('can save the rule', async () => {
await testSubjects.click('saveRuleButton');
await testSubjects.click('rulePageFooterSaveButton');
await testSubjects.click('confirmModalConfirmButton');
await find.byCssSelector('button[title="test custom threshold rule"]');
const title = await toasts.getTitleAndDismiss();
expect(title).toEqual(`Created rule "test custom threshold rule"`);
});
it('saved the rule correctly', async () => {
@ -220,6 +226,7 @@ export default ({ getService }: FtrProviderContext) => {
expect.objectContaining({
name: 'test custom threshold rule',
tags: ['tag1'],
consumer: 'logs',
params: expect.objectContaining({
alertOnGroupDisappear: false,
alertOnNoData: false,

View file

@ -51,7 +51,7 @@ export default ({ getService, getPageObject }: FtrProviderContext) => {
});
it('does render the correct error message', async () => {
await testSubjects.setValue('ruleNameInput', 'test custom threshold rule');
await testSubjects.setValue('ruleDetailsNameInput', 'test custom threshold rule');
await testSubjects.click('customEquation');
const customEquationField = await find.byCssSelector(

View file

@ -64,11 +64,11 @@ export default ({ getService, getPageObjects }: FtrProviderContext) => {
const selectAndFillInEsQueryRule = async (ruleName: string) => {
await testSubjects.click(`.es-query-SelectOption`);
await retry.waitFor(
'Create Rule flyout is visible',
async () => await testSubjects.exists('addRuleFlyoutTitle')
'Create Rule form is visible',
async () => await testSubjects.exists('createRuleForm')
);
await testSubjects.setValue('ruleNameInput', ruleName);
await testSubjects.setValue('ruleDetailsNameInput', ruleName);
await testSubjects.click('queryFormType_esQuery');
await testSubjects.click('selectIndexExpression');
const indexComboBox = await find.byCssSelector('#indexSelectSearchBox');
@ -90,7 +90,7 @@ export default ({ getService, getPageObjects }: FtrProviderContext) => {
const observability = getService('observability');
const navigateAndOpenCreateRuleFlyout = async () => {
const navigateAndOpenRuleTypeModal = async () => {
await observability.alerts.common.navigateToRulesPage();
await retry.waitFor(
'Create Rule button is visible',
@ -128,11 +128,11 @@ export default ({ getService, getPageObjects }: FtrProviderContext) => {
describe('Create rule button', () => {
it('Show Rule Type Modal when Create Rule button is clicked', async () => {
await navigateAndOpenCreateRuleFlyout();
await navigateAndOpenRuleTypeModal();
});
});
describe('Create rules flyout', () => {
describe('Create rules form', () => {
const ruleName = 'esQueryRule';
afterEach(async () => {
@ -151,13 +151,15 @@ export default ({ getService, getPageObjects }: FtrProviderContext) => {
infrastructure: ['all'],
})
);
await navigateAndOpenCreateRuleFlyout();
await navigateAndOpenRuleTypeModal();
await selectAndFillInEsQueryRule(ruleName);
await testSubjects.click('saveRuleButton');
await testSubjects.click('rulePageFooterSaveButton');
await PageObjects.header.waitUntilLoadingHasFinished();
await observability.alerts.common.navigateToRulesPage();
const tableRows = await find.allByCssSelector('.euiTableRow');
const rows = await getRulesList(tableRows);
expect(rows.length).to.be(1);
@ -174,13 +176,14 @@ export default ({ getService, getPageObjects }: FtrProviderContext) => {
logs: ['all'],
})
);
await navigateAndOpenCreateRuleFlyout();
await navigateAndOpenRuleTypeModal();
await selectAndFillInEsQueryRule(ruleName);
await testSubjects.click('saveRuleButton');
await testSubjects.click('rulePageFooterSaveButton');
await PageObjects.header.waitUntilLoadingHasFinished();
await observability.alerts.common.navigateToRulesPage();
const tableRows = await find.allByCssSelector('.euiTableRow');
const rows = await getRulesList(tableRows);
expect(rows.length).to.be(1);
@ -196,17 +199,17 @@ export default ({ getService, getPageObjects }: FtrProviderContext) => {
})
);
await navigateAndOpenCreateRuleFlyout();
await navigateAndOpenRuleTypeModal();
await selectAndFillInEsQueryRule(ruleName);
await retry.waitFor('consumer select modal is visible', async () => {
return await testSubjects.exists('ruleFormConsumerSelect');
return await testSubjects.exists('ruleConsumerSelection');
});
const consumerSelect = await testSubjects.find('ruleFormConsumerSelect');
const consumerSelect = await testSubjects.find('ruleConsumerSelection');
await consumerSelect.click();
const consumerOptionsList = await testSubjects.find(
'comboBoxOptionsList ruleFormConsumerSelect-optionsList'
'comboBoxOptionsList ruleConsumerSelectionInput-optionsList'
);
const consumerOptions = await consumerOptionsList.findAllByClassName(
'euiComboBoxOption__content'