mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
# Backport This will backport the following commits from `main` to `8.x`: - [[Response Ops] [Rule Form] Add Rule Form Flyout v2 (#206685)](https://github.com/elastic/kibana/pull/206685) <!--- Backport version: 9.6.6 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport) <!--BACKPORT [{"author":{"name":"Zacqary Adam Xeper","email":"Zacqary@users.noreply.github.com"},"sourceCommit":{"committedDate":"2025-03-04T14:52:58Z","message":"[Response Ops] [Rule Form] Add Rule Form Flyout v2 (#206685)\n\n## Summary\r\n\r\nPart of #195211\r\n\r\nReplaces the create/edit rule flyout with the new rule flyout\r\n\r\n<img width=\"1032\" alt=\"Screenshot 2025-01-14 at 3 12 30 PM\"\r\nsrc=\"https://github.com/user-attachments/assets/9cbcf4f8-1078-4f7e-a55a-aacc2d877a14\"\r\n/>\r\n<img width=\"1383\" alt=\"Screenshot 2025-01-14 at 3 12 52 PM\"\r\nsrc=\"https://github.com/user-attachments/assets/2270d57b-9462-4898-9dd0-41baefcc02d4\"\r\n/>\r\n\r\nRestores the confirmation prompt before canceling or saving a rule\r\nwithout actions defined.\r\n\r\nAlso fixes most of the design papercuts in the Actions step:\r\n\r\n<img width=\"494\" alt=\"Screenshot 2025-01-14 at 3 11 06 PM\"\r\nsrc=\"https://github.com/user-attachments/assets/3cf21d43-88e0-4250-b290-a545e1ebdbcf\"\r\n/>\r\n<img width=\"494\" alt=\"Screenshot 2025-01-14 at 3 11 01 PM\"\r\nsrc=\"https://github.com/user-attachments/assets/00ef3f95-c91b-4bb7-aead-a3e23c02f7df\"\r\n/>\r\n\r\n\r\n\r\n\r\n\r\n### Checklist\r\n\r\n- [x] Any text added follows [EUI's writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\r\nsentence case text and includes [i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>\r\nCo-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>","sha":"367ff8dbec417eaed30fb5f6fdf491642dcd1ebd","branchLabelMapping":{"^v9.1.0$":"main","^v8.19.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["Team:ResponseOps","release_note:feature","Feature:Alerting/RulesManagement","ci:cloud-deploy","ci:project-deploy-observability","Team:obs-ux-infra_services","Team:obs-ux-management","backport:version","v9.1.0","v8.19.0"],"title":"[Response Ops] [Rule Form] Add Rule Form Flyout v2","number":206685,"url":"https://github.com/elastic/kibana/pull/206685","mergeCommit":{"message":"[Response Ops] [Rule Form] Add Rule Form Flyout v2 (#206685)\n\n## Summary\r\n\r\nPart of #195211\r\n\r\nReplaces the create/edit rule flyout with the new rule flyout\r\n\r\n<img width=\"1032\" alt=\"Screenshot 2025-01-14 at 3 12 30 PM\"\r\nsrc=\"https://github.com/user-attachments/assets/9cbcf4f8-1078-4f7e-a55a-aacc2d877a14\"\r\n/>\r\n<img width=\"1383\" alt=\"Screenshot 2025-01-14 at 3 12 52 PM\"\r\nsrc=\"https://github.com/user-attachments/assets/2270d57b-9462-4898-9dd0-41baefcc02d4\"\r\n/>\r\n\r\nRestores the confirmation prompt before canceling or saving a rule\r\nwithout actions defined.\r\n\r\nAlso fixes most of the design papercuts in the Actions step:\r\n\r\n<img width=\"494\" alt=\"Screenshot 2025-01-14 at 3 11 06 PM\"\r\nsrc=\"https://github.com/user-attachments/assets/3cf21d43-88e0-4250-b290-a545e1ebdbcf\"\r\n/>\r\n<img width=\"494\" alt=\"Screenshot 2025-01-14 at 3 11 01 PM\"\r\nsrc=\"https://github.com/user-attachments/assets/00ef3f95-c91b-4bb7-aead-a3e23c02f7df\"\r\n/>\r\n\r\n\r\n\r\n\r\n\r\n### Checklist\r\n\r\n- [x] Any text added follows [EUI's writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\r\nsentence case text and includes [i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>\r\nCo-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>","sha":"367ff8dbec417eaed30fb5f6fdf491642dcd1ebd"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/206685","number":206685,"mergeCommit":{"message":"[Response Ops] [Rule Form] Add Rule Form Flyout v2 (#206685)\n\n## Summary\r\n\r\nPart of #195211\r\n\r\nReplaces the create/edit rule flyout with the new rule flyout\r\n\r\n<img width=\"1032\" alt=\"Screenshot 2025-01-14 at 3 12 30 PM\"\r\nsrc=\"https://github.com/user-attachments/assets/9cbcf4f8-1078-4f7e-a55a-aacc2d877a14\"\r\n/>\r\n<img width=\"1383\" alt=\"Screenshot 2025-01-14 at 3 12 52 PM\"\r\nsrc=\"https://github.com/user-attachments/assets/2270d57b-9462-4898-9dd0-41baefcc02d4\"\r\n/>\r\n\r\nRestores the confirmation prompt before canceling or saving a rule\r\nwithout actions defined.\r\n\r\nAlso fixes most of the design papercuts in the Actions step:\r\n\r\n<img width=\"494\" alt=\"Screenshot 2025-01-14 at 3 11 06 PM\"\r\nsrc=\"https://github.com/user-attachments/assets/3cf21d43-88e0-4250-b290-a545e1ebdbcf\"\r\n/>\r\n<img width=\"494\" alt=\"Screenshot 2025-01-14 at 3 11 01 PM\"\r\nsrc=\"https://github.com/user-attachments/assets/00ef3f95-c91b-4bb7-aead-a3e23c02f7df\"\r\n/>\r\n\r\n\r\n\r\n\r\n\r\n### Checklist\r\n\r\n- [x] Any text added follows [EUI's writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\r\nsentence case text and includes [i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>\r\nCo-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>","sha":"367ff8dbec417eaed30fb5f6fdf491642dcd1ebd"}},{"branch":"8.x","label":"v8.19.0","branchLabelMappingKey":"^v8.19.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT--> --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
c6ad409763
commit
edc74bab07
153 changed files with 1764 additions and 1008 deletions
|
@ -32,6 +32,7 @@ export type RuleTypeWithDescription = RuleType<string, string> & { description?:
|
|||
export type RuleTypeIndexWithDescriptions = Map<string, RuleTypeWithDescription>;
|
||||
|
||||
export type RuleTypeParams = Record<string, unknown>;
|
||||
export type RuleTypeMetaData = Record<string, unknown>;
|
||||
|
||||
export interface RuleFormBaseErrors {
|
||||
name?: string[];
|
||||
|
@ -104,7 +105,7 @@ export interface RuleTypeParamsExpressionProps<
|
|||
metadata?: MetaData;
|
||||
charts: ChartsPluginSetup;
|
||||
data: DataPublicPluginStart;
|
||||
dataViews: DataViewsPublicPluginStart;
|
||||
dataViews?: DataViewsPublicPluginStart;
|
||||
unifiedSearch: UnifiedSearchPublicPluginStart;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
This export path is only for lazy loading the flyout. Importing `@kbn/response-ops-rule-form` directly generally increases a plugin's bundle size unnecessarily.
|
||||
|
||||
Flyout UI is handled at the root of this component to avoid UI glitches. We want to render loading states and the fully loaded flyout body within the same <EuiFlyout> component, otherwise the user will see multiple flyouts and overlay masks flickering in and out.
|
||||
|
||||
This should be the ONLY export path for contexts that use the rule form as a flyout and not as the full page.
|
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* 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".
|
||||
*/
|
||||
|
||||
export * from './rule_form_flyout';
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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 { EuiFlyoutResizable, EuiLoadingElastic } from '@elastic/eui';
|
||||
import React, { Suspense, lazy, useCallback } from 'react';
|
||||
import type { RuleFormProps } from '../src/rule_form';
|
||||
import type { RuleTypeMetaData } from '../src/types';
|
||||
import {
|
||||
RuleFlyoutUIContextProvider,
|
||||
useRuleFlyoutUIContext,
|
||||
RuleFormErrorPromptWrapper,
|
||||
} from '../lib';
|
||||
|
||||
const RuleForm: React.LazyExoticComponent<React.FC<RuleFormProps<any>>> = lazy(() =>
|
||||
import('../src/rule_form').then((module) => ({ default: module.RuleForm }))
|
||||
);
|
||||
|
||||
const RuleFormFlyoutRenderer = <MetaData extends RuleTypeMetaData>(
|
||||
props: RuleFormProps<MetaData>
|
||||
) => {
|
||||
const { onClickClose, hideCloseButton } = useRuleFlyoutUIContext();
|
||||
const onClose = useCallback(() => {
|
||||
// If onClickClose has been initialized, call it instead of onCancel. onClickClose should be used to
|
||||
// determine if the close confirmation modal should be shown. props.onCancel is passed down the component hierarchy
|
||||
// and will be called 1) by onClickClose, if the confirmation modal doesn't need to be shown, or 2) by the confirm
|
||||
// button on the confirmation modal
|
||||
if (onClickClose) {
|
||||
onClickClose();
|
||||
} else {
|
||||
// ONLY call props.onCancel directly from this level of the component hierarcht if onClickClose has not yet been initialized.
|
||||
// This will only occur if the user tries to close the flyout while the Suspense fallback is still visible
|
||||
props.onCancel?.();
|
||||
}
|
||||
}, [onClickClose, props]);
|
||||
return (
|
||||
<EuiFlyoutResizable
|
||||
ownFocus
|
||||
onClose={onClose}
|
||||
aria-labelledby="flyoutTitle"
|
||||
size={620}
|
||||
minWidth={500}
|
||||
className="ruleFormFlyout__container"
|
||||
hideCloseButton={hideCloseButton}
|
||||
>
|
||||
<Suspense
|
||||
fallback={
|
||||
<RuleFormErrorPromptWrapper hasBorder={false} hasShadow={false}>
|
||||
<EuiLoadingElastic size="xl" />
|
||||
</RuleFormErrorPromptWrapper>
|
||||
}
|
||||
>
|
||||
<RuleForm {...props} isFlyout />
|
||||
</Suspense>
|
||||
</EuiFlyoutResizable>
|
||||
);
|
||||
};
|
||||
|
||||
export const RuleFormFlyout = <MetaData extends RuleTypeMetaData>(
|
||||
props: RuleFormProps<MetaData>
|
||||
) => {
|
||||
return (
|
||||
<RuleFlyoutUIContextProvider>
|
||||
<RuleFormFlyoutRenderer {...props} />
|
||||
</RuleFlyoutUIContextProvider>
|
||||
);
|
||||
};
|
|
@ -10,7 +10,7 @@
|
|||
export * from './src/types';
|
||||
export * from './src/rule_type_modal';
|
||||
|
||||
export { RuleForm } from './src/rule_form';
|
||||
export { RuleForm, type RuleFormProps } from './src/rule_form';
|
||||
|
||||
export {
|
||||
fetchUiConfig,
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
This export path is for utility functions that might be necessary both for plugins that import from `@kbn/response-ops-rule-form` or from `@kbn/response-ops-rule-form/flyout` to keep their bundle size down.
|
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* 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".
|
||||
*/
|
||||
|
||||
export * from './is_valid_rule_form_plugins';
|
||||
export * from './rule_flyout_ui_context';
|
||||
export * from './rule_form_error_prompt_wrapper';
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the "Elastic License
|
||||
* 2.0", 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 type { RuleFormProps } from '../src/rule_form';
|
||||
|
||||
const requiredPluginNames = [
|
||||
'http',
|
||||
'i18n',
|
||||
'theme',
|
||||
'userProfile',
|
||||
'application',
|
||||
'notifications',
|
||||
'charts',
|
||||
'settings',
|
||||
'data',
|
||||
'unifiedSearch',
|
||||
'docLinks',
|
||||
'dataViews',
|
||||
'fieldsMetadata',
|
||||
];
|
||||
|
||||
type RequiredRuleFormPlugins = Omit<
|
||||
RuleFormProps['plugins'],
|
||||
'actionTypeRegistry' | 'ruleTypeRegistry'
|
||||
>;
|
||||
|
||||
export const isValidRuleFormPlugins = (input: unknown): input is RequiredRuleFormPlugins => {
|
||||
if (typeof input !== 'object' || input === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
requiredPluginNames.forEach((pluginName) => {
|
||||
if (!(pluginName in input)) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`RuleForm plugins is missing required plugin: ${pluginName}`);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
};
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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, { createContext, useState } from 'react';
|
||||
|
||||
const initialRuleFlyoutUIContext: {
|
||||
onClickClose: (() => void) | null;
|
||||
hideCloseButton: boolean;
|
||||
setOnClickClose: (onClickClose: () => void) => void;
|
||||
setHideCloseButton: (hideCloseButton: boolean) => void;
|
||||
} = {
|
||||
onClickClose: null,
|
||||
hideCloseButton: false,
|
||||
setOnClickClose: () => {},
|
||||
setHideCloseButton: () => {},
|
||||
};
|
||||
|
||||
export const RuleFlyoutUIContext = createContext(initialRuleFlyoutUIContext);
|
||||
|
||||
export const RuleFlyoutUIContextProvider: React.FC<React.PropsWithChildren> = ({ children }) => {
|
||||
const [onClickClose, setOnClickClose] = useState<(() => void) | null>(null);
|
||||
const [hideCloseButton, setHideCloseButton] = useState<boolean>(false);
|
||||
return (
|
||||
<RuleFlyoutUIContext.Provider
|
||||
value={{
|
||||
onClickClose,
|
||||
hideCloseButton,
|
||||
setOnClickClose,
|
||||
setHideCloseButton,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</RuleFlyoutUIContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useRuleFlyoutUIContext = () => {
|
||||
return React.useContext(RuleFlyoutUIContext);
|
||||
};
|
|
@ -11,6 +11,7 @@ import React from 'react';
|
|||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { waitFor, renderHook } from '@testing-library/react';
|
||||
import { httpServiceMock } from '@kbn/core/public/mocks';
|
||||
import { EcsFlat } from '@elastic/ecs';
|
||||
|
||||
import { useLoadRuleTypeAadTemplateField } from './use_load_rule_type_aad_template_fields';
|
||||
|
||||
|
@ -21,6 +22,14 @@ const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|||
);
|
||||
|
||||
const http = httpServiceMock.createStartContract();
|
||||
const fieldsMetadataMock = {
|
||||
useFieldsMetadata: jest.fn(),
|
||||
getClient: jest.fn(() =>
|
||||
Promise.resolve({
|
||||
find: () => Promise.resolve({ fields: EcsFlat }),
|
||||
})
|
||||
),
|
||||
};
|
||||
|
||||
describe('useLoadRuleTypeAadTemplateFields', () => {
|
||||
beforeEach(() => {
|
||||
|
@ -43,6 +52,7 @@ describe('useLoadRuleTypeAadTemplateFields', () => {
|
|||
() =>
|
||||
useLoadRuleTypeAadTemplateField({
|
||||
http,
|
||||
fieldsMetadata: fieldsMetadataMock,
|
||||
ruleTypeId: 'ruleTypeId',
|
||||
enabled: true,
|
||||
}),
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { EcsFlat } from '@elastic/ecs';
|
||||
import { useRef } from 'react';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { ActionVariable } from '@kbn/alerting-types';
|
||||
import type { HttpStart } from '@kbn/core-http-browser';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
@ -15,21 +16,33 @@ import {
|
|||
fetchRuleTypeAadTemplateFields,
|
||||
getDescription,
|
||||
} from '@kbn/alerts-ui-shared/src/common/apis';
|
||||
import { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public';
|
||||
|
||||
export interface UseLoadRuleTypeAadTemplateFieldProps {
|
||||
http: HttpStart;
|
||||
ruleTypeId?: string;
|
||||
enabled: boolean;
|
||||
cacheTime?: number;
|
||||
fieldsMetadata?: FieldsMetadataPublicStart;
|
||||
}
|
||||
|
||||
export const useLoadRuleTypeAadTemplateField = (props: UseLoadRuleTypeAadTemplateFieldProps) => {
|
||||
const { http, ruleTypeId, enabled, cacheTime } = props;
|
||||
const ecsFlat = useRef<Record<string, any>>({});
|
||||
const { http, ruleTypeId, enabled, cacheTime, fieldsMetadata } = props;
|
||||
|
||||
const queryFn = async () => {
|
||||
if (!ruleTypeId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isEmpty(ecsFlat.current)) {
|
||||
const fmClient = await fieldsMetadata?.getClient();
|
||||
if (fmClient) {
|
||||
const { fields } = await fmClient.find({});
|
||||
ecsFlat.current = fields;
|
||||
}
|
||||
}
|
||||
|
||||
return fetchRuleTypeAadTemplateFields({ http, ruleTypeId });
|
||||
};
|
||||
|
||||
|
@ -44,7 +57,7 @@ export const useLoadRuleTypeAadTemplateField = (props: UseLoadRuleTypeAadTemplat
|
|||
select: (dataViewFields) => {
|
||||
return dataViewFields?.map<ActionVariable>((d) => ({
|
||||
name: d.name,
|
||||
description: getDescription(d.name, EcsFlat),
|
||||
description: getDescription(d.name, ecsFlat.current),
|
||||
}));
|
||||
},
|
||||
cacheTime,
|
||||
|
|
|
@ -14,19 +14,19 @@ import {
|
|||
CONFIRM_RULE_SAVE_CONFIRM_BUTTON_TEXT,
|
||||
CONFIRM_RULE_SAVE_CANCEL_BUTTON_TEXT,
|
||||
CONFIRM_RULE_SAVE_MESSAGE_TEXT,
|
||||
} from '../translations';
|
||||
} from '../../translations';
|
||||
|
||||
export interface RulePageConfirmCreateRuleProps {
|
||||
export interface ConfirmCreateRuleProps {
|
||||
onCancel: () => void;
|
||||
onConfirm: () => void;
|
||||
}
|
||||
|
||||
export const RulePageConfirmCreateRule = (props: RulePageConfirmCreateRuleProps) => {
|
||||
export const ConfirmCreateRule = (props: ConfirmCreateRuleProps) => {
|
||||
const { onCancel, onConfirm } = props;
|
||||
|
||||
return (
|
||||
<EuiConfirmModal
|
||||
data-test-subj="rulePageConfirmCreateRule"
|
||||
data-test-subj="confirmCreateRuleModal"
|
||||
title={CONFIRMATION_RULE_SAVE_TITLE}
|
||||
onCancel={onCancel}
|
||||
onConfirm={onConfirm}
|
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* 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".
|
||||
*/
|
||||
|
||||
export * from './confirm_create_rule';
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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 { EuiConfirmModal, EuiText } from '@elastic/eui';
|
||||
import {
|
||||
RULE_FORM_CANCEL_MODAL_TITLE,
|
||||
RULE_FORM_CANCEL_MODAL_CONFIRM,
|
||||
RULE_FORM_CANCEL_MODAL_CANCEL,
|
||||
RULE_FORM_CANCEL_MODAL_DESCRIPTION,
|
||||
} from '../../translations';
|
||||
|
||||
export interface ConfirmRuleCloseRuleProps {
|
||||
onCancel: () => void;
|
||||
onConfirm: () => void;
|
||||
}
|
||||
|
||||
export const ConfirmRuleClose = (props: ConfirmRuleCloseRuleProps) => {
|
||||
const { onCancel, onConfirm } = props;
|
||||
|
||||
return (
|
||||
<EuiConfirmModal
|
||||
onCancel={onCancel}
|
||||
onConfirm={onConfirm}
|
||||
data-test-subj="confirmRuleCloseModal"
|
||||
buttonColor="danger"
|
||||
defaultFocusedButton="confirm"
|
||||
title={RULE_FORM_CANCEL_MODAL_TITLE}
|
||||
confirmButtonText={RULE_FORM_CANCEL_MODAL_CONFIRM}
|
||||
cancelButtonText={RULE_FORM_CANCEL_MODAL_CANCEL}
|
||||
>
|
||||
<EuiText>
|
||||
<p>{RULE_FORM_CANCEL_MODAL_DESCRIPTION}</p>
|
||||
</EuiText>
|
||||
</EuiConfirmModal>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* 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".
|
||||
*/
|
||||
|
||||
export * from './confirm_rule_close';
|
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* 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".
|
||||
*/
|
||||
|
||||
export * from './request_code_block';
|
||||
export * from './confirm_create_rule';
|
||||
export * from './confirm_rule_close';
|
|
@ -16,11 +16,11 @@ import {
|
|||
UpdateRuleBody,
|
||||
transformCreateRuleBody,
|
||||
transformUpdateRuleBody,
|
||||
} from '../common/apis';
|
||||
import { BASE_ALERTING_API_PATH } from '../constants';
|
||||
import { useRuleFormState } from '../hooks';
|
||||
import { SHOW_REQUEST_MODAL_ERROR } from '../translations';
|
||||
import { RuleFormData } from '../types';
|
||||
} from '../../common/apis';
|
||||
import { BASE_ALERTING_API_PATH } from '../../constants';
|
||||
import { useRuleFormState } from '../../hooks';
|
||||
import { SHOW_REQUEST_MODAL_ERROR } from '../../translations';
|
||||
import { RuleFormData } from '../../types';
|
||||
|
||||
const stringifyBodyRequest = ({
|
||||
formData,
|
|
@ -11,11 +11,12 @@ import React, { useCallback } from 'react';
|
|||
import { EuiLoadingElastic } from '@elastic/eui';
|
||||
import { toMountPoint } from '@kbn/react-kibana-mount';
|
||||
import { type RuleCreationValidConsumer } from '@kbn/rule-data-utils';
|
||||
import type { RuleFormData, RuleFormPlugins } from './types';
|
||||
import type { RuleFormData, RuleFormPlugins, RuleTypeMetaData } from './types';
|
||||
import { DEFAULT_VALID_CONSUMERS, getDefaultFormData } from './constants';
|
||||
import { RuleFormStateProvider } from './rule_form_state';
|
||||
import { useCreateRule } from './common/hooks';
|
||||
import { RulePage } from './rule_page';
|
||||
import { RuleFlyout } from './rule_flyout';
|
||||
import {
|
||||
RuleFormCircuitBreakerError,
|
||||
RuleFormErrorPromptWrapper,
|
||||
|
@ -44,9 +45,13 @@ export interface CreateRuleFormProps {
|
|||
shouldUseRuleProducer?: boolean;
|
||||
canShowConsumerSelection?: boolean;
|
||||
showMustacheAutocompleteSwitch?: boolean;
|
||||
isFlyout?: boolean;
|
||||
isServerless?: boolean;
|
||||
onCancel?: () => void;
|
||||
onSubmit?: (ruleId: string) => void;
|
||||
onChangeMetaData?: (metadata?: RuleTypeMetaData) => void;
|
||||
initialValues?: Partial<RuleFormData>;
|
||||
initialMetadata?: RuleTypeMetaData;
|
||||
}
|
||||
|
||||
export const CreateRuleForm = (props: CreateRuleFormProps) => {
|
||||
|
@ -61,12 +66,16 @@ export const CreateRuleForm = (props: CreateRuleFormProps) => {
|
|||
shouldUseRuleProducer = false,
|
||||
canShowConsumerSelection = true,
|
||||
showMustacheAutocompleteSwitch = false,
|
||||
isFlyout,
|
||||
isServerless = false,
|
||||
onCancel,
|
||||
onSubmit,
|
||||
onChangeMetaData,
|
||||
initialMetadata,
|
||||
initialValues = {},
|
||||
} = props;
|
||||
|
||||
const { http, docLinks, notifications, ruleTypeRegistry, ...deps } = plugins;
|
||||
const { http, docLinks, notifications, ruleTypeRegistry, fieldsMetadata, ...deps } = plugins;
|
||||
const { toasts } = notifications;
|
||||
|
||||
const { mutate, isLoading: isSaving } = useCreateRule({
|
||||
|
@ -112,6 +121,7 @@ export const CreateRuleForm = (props: CreateRuleFormProps) => {
|
|||
validConsumers,
|
||||
filteredRuleTypes,
|
||||
connectorFeatureId,
|
||||
fieldsMetadata,
|
||||
});
|
||||
|
||||
const onSave = useCallback(
|
||||
|
@ -158,11 +168,13 @@ export const CreateRuleForm = (props: CreateRuleFormProps) => {
|
|||
);
|
||||
}
|
||||
|
||||
const RuleFormUIComponent = isFlyout ? RuleFlyout : RulePage;
|
||||
|
||||
return (
|
||||
<div data-test-subj="createRuleForm">
|
||||
<RuleFormStateProvider
|
||||
initialRuleFormState={{
|
||||
formData: getDefaultFormData({
|
||||
<RuleFormStateProvider
|
||||
initialRuleFormState={{
|
||||
formData: {
|
||||
...getDefaultFormData({
|
||||
ruleTypeId,
|
||||
name: `${ruleType.name} rule`,
|
||||
consumer: getInitialConsumer({
|
||||
|
@ -176,33 +188,41 @@ export const CreateRuleForm = (props: CreateRuleFormProps) => {
|
|||
}),
|
||||
actions: [],
|
||||
}),
|
||||
plugins,
|
||||
connectors,
|
||||
connectorTypes,
|
||||
aadTemplateFields,
|
||||
minimumScheduleInterval: uiConfig?.minimumScheduleInterval,
|
||||
selectedRuleTypeModel: ruleTypeModel,
|
||||
selectedRuleType: ruleType,
|
||||
availableRuleTypes: getAvailableRuleTypes({
|
||||
consumer,
|
||||
ruleTypes,
|
||||
ruleTypeRegistry,
|
||||
}).map(({ ruleType: rt }) => rt),
|
||||
...initialValues,
|
||||
},
|
||||
metadata: initialMetadata,
|
||||
plugins,
|
||||
connectors,
|
||||
connectorTypes,
|
||||
aadTemplateFields,
|
||||
minimumScheduleInterval: uiConfig?.minimumScheduleInterval,
|
||||
selectedRuleTypeModel: ruleTypeModel,
|
||||
selectedRuleType: ruleType,
|
||||
availableRuleTypes: getAvailableRuleTypes({
|
||||
consumer,
|
||||
ruleTypes,
|
||||
ruleTypeRegistry,
|
||||
}).map(({ ruleType: rt }) => rt),
|
||||
validConsumers,
|
||||
flappingSettings,
|
||||
canShowConsumerSelection,
|
||||
showMustacheAutocompleteSwitch,
|
||||
multiConsumerSelection: getInitialMultiConsumer({
|
||||
multiConsumerSelection,
|
||||
validConsumers,
|
||||
flappingSettings,
|
||||
canShowConsumerSelection,
|
||||
showMustacheAutocompleteSwitch,
|
||||
multiConsumerSelection: getInitialMultiConsumer({
|
||||
multiConsumerSelection,
|
||||
validConsumers,
|
||||
ruleType,
|
||||
ruleTypes,
|
||||
isServerless,
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<RulePage isEdit={false} isSaving={isSaving} onCancel={onCancel} onSave={onSave} />
|
||||
</RuleFormStateProvider>
|
||||
</div>
|
||||
ruleType,
|
||||
ruleTypes,
|
||||
isServerless,
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<RuleFormUIComponent
|
||||
isEdit={false}
|
||||
isSaving={isSaving}
|
||||
onCancel={onCancel}
|
||||
onSave={onSave}
|
||||
onChangeMetaData={onChangeMetaData}
|
||||
/>
|
||||
</RuleFormStateProvider>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -10,10 +10,11 @@
|
|||
import React, { useCallback, useMemo } from 'react';
|
||||
import { EuiLoadingElastic } from '@elastic/eui';
|
||||
import { toMountPoint } from '@kbn/react-kibana-mount';
|
||||
import type { RuleFormData, RuleFormPlugins } from './types';
|
||||
import type { RuleFormData, RuleFormPlugins, RuleTypeMetaData } from './types';
|
||||
import { RuleFormStateProvider } from './rule_form_state';
|
||||
import { useUpdateRule } from './common/hooks';
|
||||
import { RulePage } from './rule_page';
|
||||
import { RuleFlyout } from './rule_flyout';
|
||||
import { RuleFormHealthCheckError } from './rule_form_errors/rule_form_health_check_error';
|
||||
import { useLoadDependencies } from './hooks/use_load_dependencies';
|
||||
import {
|
||||
|
@ -32,8 +33,11 @@ export interface EditRuleFormProps {
|
|||
plugins: RuleFormPlugins;
|
||||
showMustacheAutocompleteSwitch?: boolean;
|
||||
connectorFeatureId?: string;
|
||||
isFlyout?: boolean;
|
||||
onCancel?: () => void;
|
||||
onSubmit?: (ruleId: string) => void;
|
||||
onChangeMetaData?: (metadata?: RuleTypeMetaData) => void;
|
||||
initialMetadata?: RuleTypeMetaData;
|
||||
}
|
||||
|
||||
export const EditRuleForm = (props: EditRuleFormProps) => {
|
||||
|
@ -44,8 +48,12 @@ export const EditRuleForm = (props: EditRuleFormProps) => {
|
|||
connectorFeatureId = 'alerting',
|
||||
onCancel,
|
||||
onSubmit,
|
||||
isFlyout,
|
||||
onChangeMetaData,
|
||||
initialMetadata,
|
||||
} = props;
|
||||
const { http, notifications, docLinks, ruleTypeRegistry, application, ...deps } = plugins;
|
||||
const { http, notifications, docLinks, ruleTypeRegistry, application, fieldsMetadata, ...deps } =
|
||||
plugins;
|
||||
const { toasts } = notifications;
|
||||
|
||||
const { mutate, isLoading: isSaving } = useUpdateRule({
|
||||
|
@ -89,6 +97,7 @@ export const EditRuleForm = (props: EditRuleFormProps) => {
|
|||
ruleTypeRegistry,
|
||||
id,
|
||||
connectorFeatureId,
|
||||
fieldsMetadata,
|
||||
});
|
||||
|
||||
const onSave = useCallback(
|
||||
|
@ -179,40 +188,47 @@ export const EditRuleForm = (props: EditRuleFormProps) => {
|
|||
return action;
|
||||
});
|
||||
|
||||
const RuleFormUIComponent = isFlyout ? RuleFlyout : RulePage;
|
||||
|
||||
return (
|
||||
<div data-test-subj="editRuleForm">
|
||||
<RuleFormStateProvider
|
||||
initialRuleFormState={{
|
||||
connectors,
|
||||
connectorTypes,
|
||||
aadTemplateFields,
|
||||
formData: {
|
||||
...getDefaultFormData({
|
||||
ruleTypeId: fetchedFormData.ruleTypeId,
|
||||
name: fetchedFormData.name,
|
||||
consumer: fetchedFormData.consumer,
|
||||
actions: fetchedFormData.actions,
|
||||
}),
|
||||
...fetchedFormData,
|
||||
actions: actionsWithFrequency,
|
||||
},
|
||||
id,
|
||||
plugins,
|
||||
minimumScheduleInterval: uiConfig?.minimumScheduleInterval,
|
||||
selectedRuleType: ruleType,
|
||||
selectedRuleTypeModel: ruleTypeModel,
|
||||
availableRuleTypes: getAvailableRuleTypes({
|
||||
<RuleFormStateProvider
|
||||
initialRuleFormState={{
|
||||
connectors,
|
||||
connectorTypes,
|
||||
aadTemplateFields,
|
||||
formData: {
|
||||
...getDefaultFormData({
|
||||
ruleTypeId: fetchedFormData.ruleTypeId,
|
||||
name: fetchedFormData.name,
|
||||
consumer: fetchedFormData.consumer,
|
||||
ruleTypes,
|
||||
ruleTypeRegistry,
|
||||
}).map(({ ruleType: rt }) => rt),
|
||||
flappingSettings,
|
||||
validConsumers: DEFAULT_VALID_CONSUMERS,
|
||||
showMustacheAutocompleteSwitch,
|
||||
}}
|
||||
>
|
||||
<RulePage isEdit={true} isSaving={isSaving} onSave={onSave} onCancel={onCancel} />
|
||||
</RuleFormStateProvider>
|
||||
</div>
|
||||
actions: fetchedFormData.actions,
|
||||
}),
|
||||
...fetchedFormData,
|
||||
actions: actionsWithFrequency,
|
||||
},
|
||||
id,
|
||||
metadata: initialMetadata,
|
||||
plugins,
|
||||
minimumScheduleInterval: uiConfig?.minimumScheduleInterval,
|
||||
selectedRuleType: ruleType,
|
||||
selectedRuleTypeModel: ruleTypeModel,
|
||||
availableRuleTypes: getAvailableRuleTypes({
|
||||
consumer: fetchedFormData.consumer,
|
||||
ruleTypes,
|
||||
ruleTypeRegistry,
|
||||
}).map(({ ruleType: rt }) => rt),
|
||||
flappingSettings,
|
||||
validConsumers: DEFAULT_VALID_CONSUMERS,
|
||||
showMustacheAutocompleteSwitch,
|
||||
}}
|
||||
>
|
||||
<RuleFormUIComponent
|
||||
isEdit
|
||||
isSaving={isSaving}
|
||||
onSave={onSave}
|
||||
onCancel={onCancel}
|
||||
onChangeMetaData={onChangeMetaData}
|
||||
/>
|
||||
</RuleFormStateProvider>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -17,6 +17,7 @@ import {
|
|||
useLoadRuleTypesQuery,
|
||||
useFetchFlappingSettings,
|
||||
} from '@kbn/alerts-ui-shared';
|
||||
import { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public';
|
||||
import {
|
||||
useLoadConnectors,
|
||||
useLoadConnectorTypes,
|
||||
|
@ -38,6 +39,7 @@ export interface UseLoadDependencies {
|
|||
validConsumers?: RuleCreationValidConsumer[];
|
||||
filteredRuleTypes?: string[];
|
||||
connectorFeatureId?: string;
|
||||
fieldsMetadata?: FieldsMetadataPublicStart;
|
||||
}
|
||||
|
||||
export const useLoadDependencies = (props: UseLoadDependencies) => {
|
||||
|
@ -50,6 +52,7 @@ export const useLoadDependencies = (props: UseLoadDependencies) => {
|
|||
capabilities,
|
||||
filteredRuleTypes = [],
|
||||
connectorFeatureId,
|
||||
fieldsMetadata,
|
||||
} = props;
|
||||
|
||||
const canReadConnectors = !!capabilities.actions?.show;
|
||||
|
@ -129,6 +132,7 @@ export const useLoadDependencies = (props: UseLoadDependencies) => {
|
|||
ruleTypeId: computedRuleTypeId,
|
||||
enabled: !!computedRuleTypeId && canReadConnectors,
|
||||
cacheTime: 0,
|
||||
fieldsMetadata,
|
||||
});
|
||||
|
||||
const ruleType = useMemo(() => {
|
||||
|
|
|
@ -131,6 +131,49 @@ describe('useRuleFormSteps', () => {
|
|||
});
|
||||
});
|
||||
|
||||
test('renders actions as incomplete if there are 0 defined actions', async () => {
|
||||
useRuleFormState.mockReturnValue({
|
||||
...ruleFormStateMock,
|
||||
formData: {
|
||||
...formDataMock,
|
||||
actions: [],
|
||||
},
|
||||
});
|
||||
|
||||
const TestComponent = () => {
|
||||
const { steps } = useRuleFormSteps();
|
||||
|
||||
return <EuiSteps steps={steps} />;
|
||||
};
|
||||
|
||||
render(<TestComponent />);
|
||||
|
||||
expect(await screen.getByText('Step 2 is incomplete')).toBeInTheDocument();
|
||||
const step2 = screen.getByTestId('ruleFormStep-rule-actions-reportOnBlur');
|
||||
await fireEvent.blur(step2!);
|
||||
expect(await screen.queryByText('Step 2 has errors')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('renders actions as complete if there are more than 0 defined actions', async () => {
|
||||
useRuleFormState.mockReturnValue({
|
||||
...ruleFormStateMock,
|
||||
formData: {
|
||||
...formDataMock,
|
||||
actions: [{ id: '1', actionTypeId: 'test', name: 'test' }],
|
||||
},
|
||||
});
|
||||
|
||||
const TestComponent = () => {
|
||||
const { steps } = useRuleFormSteps();
|
||||
|
||||
return <EuiSteps steps={steps} />;
|
||||
};
|
||||
|
||||
render(<TestComponent />);
|
||||
expect(await screen.queryByText('Step 2 has errors')).not.toBeInTheDocument();
|
||||
expect(await screen.queryByText('Step 2 is incomplete')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe('useRuleFormHorizontalSteps', () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
|
|
@ -47,11 +47,13 @@ const getStepStatus = ({
|
|||
currentStep,
|
||||
hasErrors,
|
||||
touchedSteps,
|
||||
isIncomplete,
|
||||
}: {
|
||||
step: RuleFormStepId;
|
||||
currentStep?: RuleFormStepId;
|
||||
hasErrors: boolean;
|
||||
touchedSteps: Record<RuleFormStepId, boolean>;
|
||||
isIncomplete?: boolean;
|
||||
}) => {
|
||||
// Only apply the current status if currentStep is being tracked
|
||||
if (currentStep === step) return 'current';
|
||||
|
@ -61,6 +63,11 @@ const getStepStatus = ({
|
|||
// Otherwise just mark it as incomplete
|
||||
return touchedSteps[step] ? 'danger' : 'incomplete';
|
||||
}
|
||||
|
||||
if (isIncomplete) {
|
||||
return 'incomplete';
|
||||
}
|
||||
|
||||
// Only mark this step complete or incomplete if the currentStep flag is being used, otherwise set no status
|
||||
if (currentStep && isStepBefore(step, currentStep)) {
|
||||
return 'complete';
|
||||
|
@ -83,6 +90,7 @@ const useCommonRuleFormSteps = ({
|
|||
paramsErrors = {},
|
||||
actionsErrors = {},
|
||||
actionsParamsErrors = {},
|
||||
formData: { actions },
|
||||
} = useRuleFormState();
|
||||
|
||||
const canReadConnectors = !!application.capabilities.actions?.show;
|
||||
|
@ -121,8 +129,9 @@ const useCommonRuleFormSteps = ({
|
|||
currentStep,
|
||||
hasErrors: hasActionErrors,
|
||||
touchedSteps,
|
||||
isIncomplete: actions.length === 0,
|
||||
}),
|
||||
[hasActionErrors, currentStep, touchedSteps]
|
||||
[hasActionErrors, currentStep, touchedSteps, actions]
|
||||
);
|
||||
|
||||
const ruleDetailsStatus = useMemo(
|
||||
|
@ -144,12 +153,14 @@ const useCommonRuleFormSteps = ({
|
|||
: RULE_FORM_PAGE_RULE_DEFINITION_TITLE,
|
||||
status: ruleDefinitionStatus,
|
||||
children: <RuleDefinition />,
|
||||
'data-test-subj': 'ruleFormStep-definition',
|
||||
},
|
||||
[RuleFormStepId.ACTIONS]: canReadConnectors
|
||||
? {
|
||||
title: RULE_FORM_PAGE_RULE_ACTIONS_TITLE,
|
||||
status: actionsStatus,
|
||||
children: <RuleActions />,
|
||||
'data-test-subj': 'ruleFormStep-actions',
|
||||
}
|
||||
: null,
|
||||
[RuleFormStepId.DETAILS]: {
|
||||
|
@ -158,6 +169,7 @@ const useCommonRuleFormSteps = ({
|
|||
: RULE_FORM_PAGE_RULE_DETAILS_TITLE,
|
||||
status: ruleDetailsStatus,
|
||||
children: <RuleDetails />,
|
||||
'data-test-subj': 'ruleFormStep-details',
|
||||
},
|
||||
}),
|
||||
[ruleDefinitionStatus, canReadConnectors, actionsStatus, ruleDetailsStatus, shortTitles]
|
||||
|
|
|
@ -30,7 +30,11 @@ import {
|
|||
EuiSelectableProps,
|
||||
useCurrentEuiBreakpoint,
|
||||
} from '@elastic/eui';
|
||||
import { ActionConnector, checkActionFormActionTypeEnabled } from '@kbn/alerts-ui-shared';
|
||||
import {
|
||||
ActionConnector,
|
||||
type ActionTypeModel,
|
||||
checkActionFormActionTypeEnabled,
|
||||
} from '@kbn/alerts-ui-shared';
|
||||
import React, { Suspense, useCallback, useMemo, useState } from 'react';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { RuleFormParamsErrors } from '../common/types';
|
||||
|
@ -348,7 +352,13 @@ export const RuleActionsConnectorsBody = ({
|
|||
<EuiFlexGroup direction="column">
|
||||
{filteredConnectors.map((connector) => {
|
||||
const { id, actionTypeId, name } = connector;
|
||||
const actionTypeModel = actionTypeRegistry.get(actionTypeId);
|
||||
let actionTypeModel: ActionTypeModel;
|
||||
try {
|
||||
actionTypeModel = actionTypeRegistry.get(actionTypeId);
|
||||
if (!actionTypeModel) return null;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
const actionType = connectorTypes.find((item) => item.id === actionTypeId);
|
||||
|
||||
if (!actionType) {
|
||||
|
@ -370,6 +380,7 @@ export const RuleActionsConnectorsBody = ({
|
|||
const connectorCard = (
|
||||
<EuiCard
|
||||
data-test-subj="ruleActionsConnectorsModalCard"
|
||||
data-action-type-id={actionTypeId}
|
||||
hasBorder
|
||||
isDisabled={isDisabled}
|
||||
titleSize="xs"
|
||||
|
|
|
@ -557,7 +557,7 @@ export const RuleActionsItem = (props: RuleActionsItemProps) => {
|
|||
}, []);
|
||||
|
||||
const accordionIcon = useMemo(() => {
|
||||
if (!connector) {
|
||||
if (!connector || !actionType) {
|
||||
return (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip content={ACTION_UNABLE_TO_LOAD_CONNECTOR_TITLE}>
|
||||
|
@ -585,35 +585,24 @@ export const RuleActionsItem = (props: RuleActionsItemProps) => {
|
|||
</EuiToolTip>
|
||||
) : (
|
||||
<Suspense fallback={null}>
|
||||
<EuiIcon size="l" type={actionTypeModel.iconClass} />
|
||||
<EuiToolTip content={actionType.name}>
|
||||
<EuiIcon size="l" type={actionTypeModel.iconClass} />
|
||||
</EuiToolTip>
|
||||
</Suspense>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
);
|
||||
}, [connector, showActionGroupErrorIcon, actionTypeModel]);
|
||||
}, [connector, showActionGroupErrorIcon, actionType, actionTypeModel.iconClass]);
|
||||
|
||||
const connectorTitle = useMemo(() => {
|
||||
const title = connector ? ACTION_TITLE(connector) : actionTypeModel.actionTypeTitle;
|
||||
return (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText>{title}</EuiText>
|
||||
<EuiFlexItem grow={false} className=".eui-textBreakWord">
|
||||
<EuiText size="s">{title}</EuiText>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
}, [connector, actionTypeModel]);
|
||||
|
||||
const actionTypeTitle = useMemo(() => {
|
||||
if (!connector || !actionType) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="s" color="subdued">
|
||||
<strong>{actionType.name}</strong>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
}, [connector, actionType]);
|
||||
|
||||
const runWhenTitle = useMemo(() => {
|
||||
if (!connector) {
|
||||
return null;
|
||||
|
@ -624,11 +613,15 @@ export const RuleActionsItem = (props: RuleActionsItemProps) => {
|
|||
if (selectedActionGroup || action.frequency?.summary) {
|
||||
return (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBadge iconType="clock">
|
||||
{action.frequency?.summary
|
||||
? SUMMARY_GROUP_TITLE
|
||||
: RUN_WHEN_GROUP_TITLE(selectedActionGroup!.name.toLocaleLowerCase())}
|
||||
</EuiBadge>
|
||||
<EuiToolTip
|
||||
content={
|
||||
action.frequency?.summary
|
||||
? SUMMARY_GROUP_TITLE
|
||||
: RUN_WHEN_GROUP_TITLE(selectedActionGroup!.name.toLocaleLowerCase())
|
||||
}
|
||||
>
|
||||
<EuiBadge iconType="clock" />
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
}
|
||||
|
@ -644,9 +637,9 @@ export const RuleActionsItem = (props: RuleActionsItemProps) => {
|
|||
if (warning) {
|
||||
return (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBadge data-test-subj="warning-badge" iconType="warning" color="warning">
|
||||
{ACTION_WARNING_TITLE}
|
||||
</EuiBadge>
|
||||
<EuiToolTip content={ACTION_WARNING_TITLE}>
|
||||
<EuiBadge data-test-subj="warning-badge" iconType="warning" color="warning" />
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
}
|
||||
|
@ -695,20 +688,23 @@ export const RuleActionsItem = (props: RuleActionsItemProps) => {
|
|||
<EuiPanel color="subdued" paddingSize="m">
|
||||
<EuiFlexGroup alignItems="center" responsive={false}>
|
||||
{accordionIcon}
|
||||
{connectorTitle}
|
||||
{actionTypeTitle}
|
||||
{runWhenTitle}
|
||||
{warningIcon}
|
||||
{actionTypeModel.isExperimental && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBetaBadge
|
||||
alignment="middle"
|
||||
data-test-subj="ruleActionsSystemActionsItemBetaBadge"
|
||||
label={TECH_PREVIEW_LABEL}
|
||||
tooltipContent={TECH_PREVIEW_DESCRIPTION}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexGroup alignItems="center" gutterSize="xs" responsive={false}>
|
||||
{connectorTitle}
|
||||
{actionTypeModel.isExperimental && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBetaBadge
|
||||
alignment="middle"
|
||||
data-test-subj="ruleActionsSystemActionsItemBetaBadge"
|
||||
label={TECH_PREVIEW_LABEL}
|
||||
tooltipContent={TECH_PREVIEW_DESCRIPTION}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexGroup justifyContent="flexEnd" gutterSize="xs" responsive={false}>
|
||||
{runWhenTitle}
|
||||
{warningIcon}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
}
|
||||
|
|
|
@ -135,7 +135,6 @@ describe('ruleActionsSystemActionsItem', () => {
|
|||
|
||||
expect(screen.getByTestId('ruleActionsSystemActionsItem')).toBeInTheDocument();
|
||||
expect(screen.getByText('connector-1')).toBeInTheDocument();
|
||||
expect(screen.getByText('actionType: 1')).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByTestId('ruleActionsSystemActionsItemAccordionContent')).toBeVisible();
|
||||
expect(screen.getByText('RuleActionsMessage')).toBeInTheDocument();
|
||||
|
|
|
@ -283,35 +283,38 @@ export const RuleActionsSystemActionsItem = (props: RuleActionsSystemActionsItem
|
|||
</EuiToolTip>
|
||||
) : (
|
||||
<Suspense fallback={null}>
|
||||
<EuiIcon size="l" type={actionTypeModel.iconClass} />
|
||||
<EuiToolTip content={actionType?.name}>
|
||||
<EuiIcon size="l" type={actionTypeModel.iconClass} />
|
||||
</EuiToolTip>
|
||||
</Suspense>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText>{connector.name}</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="s" color="subdued">
|
||||
<strong>{actionType?.name}</strong>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
{warning && !isOpen && (
|
||||
<EuiFlexGroup alignItems="center" gutterSize="xs" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBadge data-test-subj="warning-badge" iconType="warning" color="warning">
|
||||
{ACTION_WARNING_TITLE}
|
||||
</EuiBadge>
|
||||
<EuiText size="s">{connector.name}</EuiText>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
{actionTypeModel.isExperimental && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBetaBadge
|
||||
alignment="middle"
|
||||
data-test-subj="ruleActionsSystemActionsItemBetaBadge"
|
||||
label={TECH_PREVIEW_LABEL}
|
||||
tooltipContent={TECH_PREVIEW_DESCRIPTION}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
{actionTypeModel.isExperimental && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBetaBadge
|
||||
size="s"
|
||||
alignment="middle"
|
||||
data-test-subj="ruleActionsSystemActionsItemBetaBadge"
|
||||
iconType="beaker"
|
||||
label={TECH_PREVIEW_LABEL}
|
||||
tooltipContent={TECH_PREVIEW_DESCRIPTION}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexGroup justifyContent="flexEnd" gutterSize="xs" responsive={false}>
|
||||
{warning && !isOpen && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip content={ACTION_WARNING_TITLE}>
|
||||
<EuiBadge data-test-subj="warning-badge" iconType="warning" color="warning" />
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
}
|
||||
|
|
|
@ -121,7 +121,7 @@ export const RuleSchedule = () => {
|
|||
isInvalid={hasIntervalError}
|
||||
error={baseErrors?.interval}
|
||||
>
|
||||
<EuiFlexGroup gutterSize="s">
|
||||
<EuiFlexGroup gutterSize="s" responsive={false}>
|
||||
<EuiFlexItem grow={2}>
|
||||
<EuiFieldNumber
|
||||
fullWidth
|
||||
|
|
|
@ -128,6 +128,9 @@ describe('ruleFlyout', () => {
|
|||
|
||||
fireEvent.click(screen.getByTestId('ruleFlyoutFooterSaveButton'));
|
||||
|
||||
expect(await screen.findByTestId('confirmCreateRuleModal')).toBeInTheDocument();
|
||||
fireEvent.click(screen.getByTestId('confirmModalConfirmButton'));
|
||||
|
||||
expect(onSave).toHaveBeenCalledWith({
|
||||
...formDataMock,
|
||||
consumer: 'logs',
|
||||
|
@ -140,4 +143,33 @@ describe('ruleFlyout', () => {
|
|||
fireEvent.click(screen.getByTestId('ruleFlyoutFooterCancelButton'));
|
||||
expect(onCancel).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should display discard changes modal only if changes are made in the form', () => {
|
||||
useRuleFormState.mockReturnValue({
|
||||
plugins: {
|
||||
application: {
|
||||
navigateToUrl,
|
||||
capabilities: {
|
||||
actions: {
|
||||
show: true,
|
||||
save: true,
|
||||
execute: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
baseErrors: {},
|
||||
paramsErrors: {},
|
||||
touched: true,
|
||||
formData: formDataMock,
|
||||
connectors: [],
|
||||
connectorTypes: [],
|
||||
aadTemplateFields: [],
|
||||
});
|
||||
|
||||
render(<RuleFlyout onCancel={onCancel} onSave={onSave} />);
|
||||
|
||||
fireEvent.click(screen.getByTestId('ruleFlyoutFooterCancelButton'));
|
||||
expect(screen.getByTestId('confirmRuleCloseModal')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,29 +7,40 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { EuiFlyout, EuiPortal } from '@elastic/eui';
|
||||
import React, { useState, useCallback, useMemo } from 'react';
|
||||
import type { RuleFormData } from '../types';
|
||||
import React, { useState, useCallback, useMemo, useEffect } from 'react';
|
||||
import { useRuleFlyoutUIContext } from '../../lib';
|
||||
import type { RuleFormData, RuleTypeMetaData } from '../types';
|
||||
import { RuleFormStepId } from '../constants';
|
||||
import { RuleFlyoutBody } from './rule_flyout_body';
|
||||
import { RuleFlyoutShowRequest } from './rule_flyout_show_request';
|
||||
import { useRuleFormScreenContext } from '../hooks';
|
||||
import { useRuleFormScreenContext, useRuleFormState } from '../hooks';
|
||||
import { RuleFlyoutSelectConnector } from './rule_flyout_select_connector';
|
||||
import { ConfirmRuleClose } from '../components';
|
||||
|
||||
interface RuleFlyoutProps {
|
||||
isEdit?: boolean;
|
||||
isSaving?: boolean;
|
||||
onCancel?: () => void;
|
||||
onSave: (formData: RuleFormData) => void;
|
||||
onChangeMetaData?: (metadata?: RuleTypeMetaData) => void;
|
||||
}
|
||||
|
||||
// This component is only responsible for the CONTENT of the EuiFlyout. See `flyout/rule_form_flyout.tsx` for the
|
||||
// EuiFlyout itself. This separation is necessary so that the flyout code can be lazy-loaded and still present its loading
|
||||
// state and finished state within the same EuiFlyout.
|
||||
|
||||
export const RuleFlyout = ({
|
||||
onSave,
|
||||
isEdit = false,
|
||||
isSaving = false,
|
||||
onCancel = () => {},
|
||||
// Input is named onCancel for consistency with RulePage but rename it to onClose for more clarity on its
|
||||
// function within the flyout. This avoids the compulsion to name a function something like onCancelCancel when
|
||||
// we're displaying the confirmation modal for closing the flyout.
|
||||
onCancel: onClose = () => {},
|
||||
onChangeMetaData = () => {},
|
||||
}: RuleFlyoutProps) => {
|
||||
const [initialStep, setInitialStep] = useState<RuleFormStepId | undefined>(undefined);
|
||||
const [isConfirmCloseModalVisible, setIsConfirmCloseModalVisible] = useState(false);
|
||||
|
||||
const {
|
||||
isConnectorsScreenVisible,
|
||||
|
@ -51,37 +62,52 @@ export const RuleFlyout = ({
|
|||
setIsShowRequestScreenVisible(false);
|
||||
}, [setIsShowRequestScreenVisible]);
|
||||
|
||||
const onCancelClose = useCallback(() => {
|
||||
setIsConfirmCloseModalVisible(false);
|
||||
}, []);
|
||||
|
||||
const hideCloseButton = useMemo(
|
||||
() => isShowRequestScreenVisible || isConnectorsScreenVisible,
|
||||
[isConnectorsScreenVisible, isShowRequestScreenVisible]
|
||||
);
|
||||
|
||||
const { touched, onInteraction } = useRuleFormState();
|
||||
const { setOnClickClose, setHideCloseButton } = useRuleFlyoutUIContext();
|
||||
|
||||
const onClickCloseOrCancelButton = useCallback(() => {
|
||||
if (touched) {
|
||||
setIsConfirmCloseModalVisible(true);
|
||||
} else {
|
||||
onClose();
|
||||
}
|
||||
}, [touched, setIsConfirmCloseModalVisible, onClose]);
|
||||
|
||||
useEffect(() => {
|
||||
setOnClickClose(() => onClickCloseOrCancelButton);
|
||||
setHideCloseButton(hideCloseButton);
|
||||
}, [setOnClickClose, setHideCloseButton, onClickCloseOrCancelButton, hideCloseButton]);
|
||||
|
||||
return (
|
||||
<EuiPortal>
|
||||
<EuiFlyout
|
||||
ownFocus
|
||||
onClose={onCancel}
|
||||
aria-labelledby="flyoutTitle"
|
||||
size="m"
|
||||
maxWidth={500}
|
||||
className="ruleFormFlyout__container"
|
||||
hideCloseButton={hideCloseButton}
|
||||
>
|
||||
{isShowRequestScreenVisible ? (
|
||||
<RuleFlyoutShowRequest isEdit={isEdit} onClose={onCloseShowRequest} />
|
||||
) : isConnectorsScreenVisible ? (
|
||||
<RuleFlyoutSelectConnector onClose={onCloseConnectorsScreen} />
|
||||
) : (
|
||||
<RuleFlyoutBody
|
||||
onSave={onSave}
|
||||
onCancel={onCancel}
|
||||
isEdit={isEdit}
|
||||
isSaving={isSaving}
|
||||
onShowRequest={onOpenShowRequest}
|
||||
initialStep={initialStep}
|
||||
/>
|
||||
)}
|
||||
</EuiFlyout>
|
||||
</EuiPortal>
|
||||
<>
|
||||
{isShowRequestScreenVisible ? (
|
||||
<RuleFlyoutShowRequest isEdit={isEdit} onClose={onCloseShowRequest} />
|
||||
) : isConnectorsScreenVisible ? (
|
||||
<RuleFlyoutSelectConnector onClose={onCloseConnectorsScreen} />
|
||||
) : (
|
||||
<RuleFlyoutBody
|
||||
onSave={onSave}
|
||||
onInteraction={onInteraction}
|
||||
onCancel={onClickCloseOrCancelButton}
|
||||
isEdit={isEdit}
|
||||
isSaving={isSaving}
|
||||
onShowRequest={onOpenShowRequest}
|
||||
initialStep={initialStep}
|
||||
onChangeMetaData={onChangeMetaData}
|
||||
/>
|
||||
)}
|
||||
{isConfirmCloseModalVisible && (
|
||||
<ConfirmRuleClose onCancel={onCancelClose} onConfirm={onClose} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -8,34 +8,38 @@
|
|||
*/
|
||||
|
||||
import {
|
||||
EuiCallOut,
|
||||
EuiFlyoutBody,
|
||||
EuiFlyoutHeader,
|
||||
EuiSpacer,
|
||||
EuiStepsHorizontal,
|
||||
EuiTitle,
|
||||
EuiSpacer,
|
||||
EuiCallOut,
|
||||
} from '@elastic/eui';
|
||||
import { checkActionFormActionTypeEnabled } from '@kbn/alerts-ui-shared';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { isEmpty } from 'lodash';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { RuleFormStepId } from '../constants';
|
||||
import { useRuleFormHorizontalSteps, useRuleFormState } from '../hooks';
|
||||
import {
|
||||
DISABLED_ACTIONS_WARNING_TITLE,
|
||||
RULE_FLYOUT_HEADER_CREATE_TITLE,
|
||||
RULE_FLYOUT_HEADER_EDIT_TITLE,
|
||||
DISABLED_ACTIONS_WARNING_TITLE,
|
||||
} from '../translations';
|
||||
import type { RuleFormData } from '../types';
|
||||
import type { RuleFormData, RuleFormState } from '../types';
|
||||
import { hasRuleErrors } from '../validation';
|
||||
import { RuleFlyoutCreateFooter } from './rule_flyout_create_footer';
|
||||
import { RuleFlyoutEditFooter } from './rule_flyout_edit_footer';
|
||||
import { RuleFlyoutEditTabs } from './rule_flyout_edit_tabs';
|
||||
import { RuleFormStepId } from '../constants';
|
||||
import { ConfirmCreateRule } from '../components';
|
||||
|
||||
interface RuleFlyoutBodyProps {
|
||||
isEdit?: boolean;
|
||||
isSaving?: boolean;
|
||||
onCancel: () => void;
|
||||
onSave: (formData: RuleFormData) => void;
|
||||
onInteraction: () => void;
|
||||
onShowRequest: () => void;
|
||||
onChangeMetaData?: (metadata?: RuleFormState['metadata']) => void;
|
||||
initialStep?: RuleFormStepId;
|
||||
}
|
||||
|
||||
|
@ -45,8 +49,12 @@ export const RuleFlyoutBody = ({
|
|||
initialStep,
|
||||
onCancel,
|
||||
onSave,
|
||||
onInteraction,
|
||||
onShowRequest,
|
||||
onChangeMetaData = () => {},
|
||||
}: RuleFlyoutBodyProps) => {
|
||||
const [showCreateConfirmation, setShowCreateConfirmation] = useState<boolean>(false);
|
||||
|
||||
const {
|
||||
formData,
|
||||
multiConsumerSelection,
|
||||
|
@ -56,8 +64,15 @@ export const RuleFlyoutBody = ({
|
|||
paramsErrors = {},
|
||||
actionsErrors = {},
|
||||
actionsParamsErrors = {},
|
||||
metadata = {},
|
||||
} = useRuleFormState();
|
||||
|
||||
useEffect(() => {
|
||||
if (!isEmpty(metadata)) {
|
||||
onChangeMetaData(metadata);
|
||||
}
|
||||
}, [metadata, onChangeMetaData]);
|
||||
|
||||
const hasErrors = useMemo(() => {
|
||||
const hasBrokenConnectors = formData.actions.some((action) => {
|
||||
return !connectors.find((connector) => connector.id === action.id);
|
||||
|
@ -85,14 +100,6 @@ export const RuleFlyoutBody = ({
|
|||
} = useRuleFormHorizontalSteps(initialStep);
|
||||
|
||||
const { actions } = formData;
|
||||
|
||||
const onSaveInternal = useCallback(() => {
|
||||
onSave({
|
||||
...formData,
|
||||
...(multiConsumerSelection ? { consumer: multiConsumerSelection } : {}),
|
||||
});
|
||||
}, [onSave, formData, multiConsumerSelection]);
|
||||
|
||||
const hasActionsDisabled = useMemo(() => {
|
||||
const preconfiguredConnectors = connectors.filter((connector) => connector.isPreconfigured);
|
||||
return actions.some((action) => {
|
||||
|
@ -108,17 +115,41 @@ export const RuleFlyoutBody = ({
|
|||
});
|
||||
}, [actions, connectors, connectorTypes]);
|
||||
|
||||
const onSaveInternal = useCallback(() => {
|
||||
onSave({
|
||||
...formData,
|
||||
...(multiConsumerSelection ? { consumer: multiConsumerSelection } : {}),
|
||||
});
|
||||
}, [onSave, formData, multiConsumerSelection]);
|
||||
|
||||
const onClickSave = useCallback(() => {
|
||||
if (!hasActionsDisabled && actions.length === 0) {
|
||||
setShowCreateConfirmation(true);
|
||||
} else {
|
||||
onSaveInternal();
|
||||
}
|
||||
}, [actions.length, hasActionsDisabled, onSaveInternal]);
|
||||
|
||||
const onCreateConfirmClick = useCallback(() => {
|
||||
setShowCreateConfirmation(false);
|
||||
onSaveInternal();
|
||||
}, [onSaveInternal]);
|
||||
|
||||
const onCreateCancelClick = useCallback(() => {
|
||||
setShowCreateConfirmation(false);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiFlyoutHeader hasBorder>
|
||||
<EuiTitle size="s" data-test-subj={isEdit ? 'editRuleFlyoutTitle' : 'addRuleFlyoutTitle'}>
|
||||
<h3 id="flyoutTitle">
|
||||
<h3 id="flyoutTitle" data-test-subj="ruleFlyoutTitle">
|
||||
{isEdit ? RULE_FLYOUT_HEADER_EDIT_TITLE : RULE_FLYOUT_HEADER_CREATE_TITLE}
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
{isEdit && <RuleFlyoutEditTabs steps={steps} />}
|
||||
</EuiFlyoutHeader>
|
||||
<EuiFlyoutBody>
|
||||
<EuiFlyoutBody onClick={onInteraction} onKeyDown={onInteraction}>
|
||||
{!isEdit && <EuiStepsHorizontal size="xs" steps={steps} />}
|
||||
{hasActionsDisabled && (
|
||||
<>
|
||||
|
@ -137,7 +168,7 @@ export const RuleFlyoutBody = ({
|
|||
{isEdit ? (
|
||||
<RuleFlyoutEditFooter
|
||||
onCancel={onCancel}
|
||||
onSave={onSaveInternal}
|
||||
onSave={onClickSave}
|
||||
onShowRequest={onShowRequest}
|
||||
isSaving={isSaving}
|
||||
hasErrors={hasErrors}
|
||||
|
@ -145,7 +176,7 @@ export const RuleFlyoutBody = ({
|
|||
) : (
|
||||
<RuleFlyoutCreateFooter
|
||||
onCancel={onCancel}
|
||||
onSave={onSaveInternal}
|
||||
onSave={onClickSave}
|
||||
onShowRequest={onShowRequest}
|
||||
goToNextStep={goToNextStep}
|
||||
goToPreviousStep={goToPreviousStep}
|
||||
|
@ -155,6 +186,9 @@ export const RuleFlyoutBody = ({
|
|||
hasErrors={hasErrors}
|
||||
/>
|
||||
)}
|
||||
{showCreateConfirmation && (
|
||||
<ConfirmCreateRule onConfirm={onCreateConfirmClick} onCancel={onCreateCancelClick} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -39,7 +39,7 @@ export const RuleFlyoutEditFooter = ({
|
|||
<EuiFlyoutFooter>
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty data-test-subj="cancelSaveRuleButton" onClick={onCancel}>
|
||||
<EuiButtonEmpty data-test-subj="ruleFlyoutFooterCancelButton" onClick={onCancel}>
|
||||
{RULE_FLYOUT_FOOTER_CANCEL_TEXT}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
|
@ -60,7 +60,7 @@ export const RuleFlyoutEditFooter = ({
|
|||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
fill
|
||||
data-test-subj="saveRuleButton"
|
||||
data-test-subj="ruleFlyoutFooterSaveButton"
|
||||
type="submit"
|
||||
isDisabled={isSaving || hasErrors}
|
||||
isLoading={isSaving}
|
||||
|
|
|
@ -26,7 +26,7 @@ import {
|
|||
RULE_FLYOUT_FOOTER_BACK_TEXT,
|
||||
RULE_FLYOUT_HEADER_BACK_TEXT,
|
||||
} from '../translations';
|
||||
import { RequestCodeBlock } from '../request_code_block';
|
||||
import { RequestCodeBlock } from '../components';
|
||||
|
||||
interface RuleFlyoutShowRequestProps {
|
||||
isEdit: boolean;
|
||||
|
|
|
@ -7,45 +7,68 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import { EuiEmptyPrompt, EuiText } from '@elastic/eui';
|
||||
import React, { useMemo } from 'react';
|
||||
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';
|
||||
import {
|
||||
RULE_FORM_ROUTE_PARAMS_ERROR_TITLE,
|
||||
RULE_FORM_ROUTE_PARAMS_ERROR_TEXT,
|
||||
} from './translations';
|
||||
import { RuleFormPlugins } from './types';
|
||||
import './rule_form.scss';
|
||||
import { RuleFormScreenContextProvider } from './rule_form_screen_context';
|
||||
import {
|
||||
RULE_FORM_ROUTE_PARAMS_ERROR_TEXT,
|
||||
RULE_FORM_ROUTE_PARAMS_ERROR_TITLE,
|
||||
} from './translations';
|
||||
import { RuleFormData, RuleFormPlugins, RuleTypeMetaData } from './types';
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
export interface RuleFormProps {
|
||||
export interface RuleFormProps<MetaData extends RuleTypeMetaData = RuleTypeMetaData> {
|
||||
plugins: RuleFormPlugins;
|
||||
id?: string;
|
||||
ruleTypeId?: string;
|
||||
isFlyout?: boolean;
|
||||
onCancel?: () => void;
|
||||
onSubmit?: (ruleId: string) => void;
|
||||
validConsumers?: RuleCreationValidConsumer[];
|
||||
onChangeMetaData?: (metadata: MetaData) => void;
|
||||
consumer?: string;
|
||||
connectorFeatureId?: string;
|
||||
multiConsumerSelection?: RuleCreationValidConsumer | null;
|
||||
hideInterval?: boolean;
|
||||
validConsumers?: RuleCreationValidConsumer[];
|
||||
filteredRuleTypes?: string[];
|
||||
shouldUseRuleProducer?: boolean;
|
||||
canShowConsumerSelection?: boolean;
|
||||
showMustacheAutocompleteSwitch?: boolean;
|
||||
initialValues?: Partial<Omit<RuleFormData, 'ruleTypeId'>>;
|
||||
initialMetadata?: MetaData;
|
||||
isServerless?: boolean;
|
||||
}
|
||||
|
||||
export const RuleForm = (props: RuleFormProps) => {
|
||||
export const RuleForm = <MetaData extends RuleTypeMetaData = RuleTypeMetaData>(
|
||||
props: RuleFormProps<MetaData>
|
||||
) => {
|
||||
const {
|
||||
plugins: _plugins,
|
||||
onCancel,
|
||||
onSubmit,
|
||||
validConsumers,
|
||||
onChangeMetaData,
|
||||
id,
|
||||
ruleTypeId,
|
||||
isFlyout,
|
||||
consumer,
|
||||
connectorFeatureId,
|
||||
multiConsumerSelection,
|
||||
hideInterval,
|
||||
validConsumers,
|
||||
filteredRuleTypes,
|
||||
shouldUseRuleProducer,
|
||||
canShowConsumerSelection,
|
||||
showMustacheAutocompleteSwitch,
|
||||
initialValues,
|
||||
initialMetadata,
|
||||
isServerless,
|
||||
} = props;
|
||||
const { id, ruleTypeId } = useParams<{
|
||||
id?: string;
|
||||
ruleTypeId?: string;
|
||||
}>();
|
||||
|
||||
const {
|
||||
http,
|
||||
|
@ -62,6 +85,7 @@ export const RuleForm = (props: RuleFormProps) => {
|
|||
docLinks,
|
||||
ruleTypeRegistry,
|
||||
actionTypeRegistry,
|
||||
fieldsMetadata,
|
||||
} = _plugins;
|
||||
|
||||
const ruleFormComponent = useMemo(() => {
|
||||
|
@ -80,9 +104,27 @@ export const RuleForm = (props: RuleFormProps) => {
|
|||
docLinks,
|
||||
ruleTypeRegistry,
|
||||
actionTypeRegistry,
|
||||
fieldsMetadata,
|
||||
};
|
||||
|
||||
// Passing the MetaData type all the way down the component hierarchy is unnecessary, this type is
|
||||
// only used for the benefit of consumers of the RuleForm component. Retype onChangeMetaData to ignore this type.
|
||||
const retypedOnChangeMetaData = onChangeMetaData as (metadata?: RuleTypeMetaData) => void;
|
||||
|
||||
if (id) {
|
||||
return <EditRuleForm id={id} plugins={plugins} onCancel={onCancel} onSubmit={onSubmit} />;
|
||||
return (
|
||||
<EditRuleForm
|
||||
id={id}
|
||||
plugins={plugins}
|
||||
onCancel={onCancel}
|
||||
onSubmit={onSubmit}
|
||||
onChangeMetaData={retypedOnChangeMetaData}
|
||||
isFlyout={isFlyout}
|
||||
showMustacheAutocompleteSwitch={showMustacheAutocompleteSwitch}
|
||||
connectorFeatureId={connectorFeatureId}
|
||||
initialMetadata={initialMetadata}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (ruleTypeId) {
|
||||
return (
|
||||
|
@ -91,8 +133,19 @@ export const RuleForm = (props: RuleFormProps) => {
|
|||
plugins={plugins}
|
||||
onCancel={onCancel}
|
||||
onSubmit={onSubmit}
|
||||
validConsumers={validConsumers}
|
||||
onChangeMetaData={retypedOnChangeMetaData}
|
||||
isFlyout={isFlyout}
|
||||
consumer={consumer}
|
||||
connectorFeatureId={connectorFeatureId}
|
||||
multiConsumerSelection={multiConsumerSelection}
|
||||
hideInterval={hideInterval}
|
||||
validConsumers={validConsumers}
|
||||
filteredRuleTypes={filteredRuleTypes}
|
||||
shouldUseRuleProducer={shouldUseRuleProducer}
|
||||
canShowConsumerSelection={canShowConsumerSelection}
|
||||
showMustacheAutocompleteSwitch={showMustacheAutocompleteSwitch}
|
||||
initialValues={initialValues}
|
||||
initialMetadata={initialMetadata}
|
||||
isServerless={isServerless}
|
||||
/>
|
||||
);
|
||||
|
@ -124,20 +177,30 @@ export const RuleForm = (props: RuleFormProps) => {
|
|||
docLinks,
|
||||
ruleTypeRegistry,
|
||||
actionTypeRegistry,
|
||||
isServerless,
|
||||
id,
|
||||
ruleTypeId,
|
||||
validConsumers,
|
||||
multiConsumerSelection,
|
||||
isServerless,
|
||||
onCancel,
|
||||
onSubmit,
|
||||
onChangeMetaData,
|
||||
isFlyout,
|
||||
showMustacheAutocompleteSwitch,
|
||||
connectorFeatureId,
|
||||
initialMetadata,
|
||||
consumer,
|
||||
hideInterval,
|
||||
filteredRuleTypes,
|
||||
shouldUseRuleProducer,
|
||||
canShowConsumerSelection,
|
||||
initialValues,
|
||||
fieldsMetadata,
|
||||
]);
|
||||
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<RuleFormScreenContextProvider>
|
||||
<div className="ruleForm__container">{ruleFormComponent}</div>
|
||||
</RuleFormScreenContextProvider>
|
||||
<RuleFormScreenContextProvider>{ruleFormComponent}</RuleFormScreenContextProvider>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -11,5 +11,5 @@ export * from './rule_form_health_check_error';
|
|||
export * from './rule_form_circuit_breaker_error';
|
||||
export * from './rule_form_resolve_rule_error';
|
||||
export * from './rule_form_rule_type_error';
|
||||
export * from './rule_form_error_prompt_wrapper';
|
||||
export * from './rule_form_action_permission_error';
|
||||
export { RuleFormErrorPromptWrapper } from '../../lib';
|
||||
|
|
|
@ -11,7 +11,13 @@ import { createContext } from 'react';
|
|||
import type { RuleFormState } from '../types';
|
||||
import type { RuleFormStateReducerAction } from './rule_form_state_reducer';
|
||||
|
||||
export const RuleFormStateContext = createContext<RuleFormState>({} as RuleFormState);
|
||||
type RuleFormStateWithInteractHandler = RuleFormState & {
|
||||
onInteraction: () => void;
|
||||
};
|
||||
|
||||
export const RuleFormStateContext = createContext<RuleFormStateWithInteractHandler>(
|
||||
{} as RuleFormStateWithInteractHandler
|
||||
);
|
||||
|
||||
export const RuleFormReducerContext = createContext<React.Dispatch<RuleFormStateReducerAction>>(
|
||||
() => {}
|
||||
|
|
|
@ -7,10 +7,10 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import React, { useReducer } from 'react';
|
||||
import React, { useReducer, useState, useCallback } from 'react';
|
||||
import { RuleFormState } from '../types';
|
||||
import { RuleFormStateContext, RuleFormReducerContext } from './rule_form_state_context';
|
||||
import { ruleFormStateReducer } from './rule_form_state_reducer';
|
||||
import { RuleFormStateReducerAction, ruleFormStateReducer } from './rule_form_state_reducer';
|
||||
import { validateRuleBase, validateRuleParams } from '../validation';
|
||||
|
||||
export interface RuleFormStateProviderProps {
|
||||
|
@ -20,6 +20,12 @@ export interface RuleFormStateProviderProps {
|
|||
export const RuleFormStateProvider: React.FC<
|
||||
React.PropsWithChildren<RuleFormStateProviderProps>
|
||||
> = (props) => {
|
||||
// Tracking whether the user has changed the form is unreliable if we base it only on the difference
|
||||
// between initial data and current data, as many rule types will use reducer actions to set their initial data.
|
||||
// We need to track whether the user has actually physically interacted with the form before the ruleFormStateReducer
|
||||
// can accurately determine the `touched` state
|
||||
const [hasUserInteracted, setHasUserInteracted] = useState(false);
|
||||
|
||||
const { children, initialRuleFormState } = props;
|
||||
const {
|
||||
formData,
|
||||
|
@ -27,7 +33,7 @@ export const RuleFormStateProvider: React.FC<
|
|||
minimumScheduleInterval,
|
||||
} = initialRuleFormState;
|
||||
|
||||
const [ruleFormState, dispatch] = useReducer(ruleFormStateReducer, {
|
||||
const [ruleFormState, baseDispatch] = useReducer(ruleFormStateReducer, {
|
||||
...initialRuleFormState,
|
||||
baseErrors: validateRuleBase({
|
||||
formData,
|
||||
|
@ -38,8 +44,25 @@ export const RuleFormStateProvider: React.FC<
|
|||
ruleTypeModel,
|
||||
}),
|
||||
});
|
||||
|
||||
// Prime the dispatch function to set `touched` to true on the next action, but not yet
|
||||
const onInteraction = useCallback(() => {
|
||||
if (!hasUserInteracted) setHasUserInteracted(true);
|
||||
}, [hasUserInteracted]);
|
||||
const dispatch: React.Dispatch<RuleFormStateReducerAction> = useCallback(
|
||||
(...args) => {
|
||||
// If the user has interacted with the form and the `touched` state is false, first update it to be true
|
||||
// before executing the next action
|
||||
if (hasUserInteracted && !ruleFormState.touched) {
|
||||
baseDispatch({ type: 'setTouched' });
|
||||
}
|
||||
baseDispatch(...args);
|
||||
},
|
||||
[baseDispatch, hasUserInteracted, ruleFormState.touched]
|
||||
);
|
||||
|
||||
return (
|
||||
<RuleFormStateContext.Provider value={ruleFormState}>
|
||||
<RuleFormStateContext.Provider value={{ ...ruleFormState, onInteraction }}>
|
||||
<RuleFormReducerContext.Provider value={dispatch}>{children}</RuleFormReducerContext.Provider>
|
||||
</RuleFormStateContext.Provider>
|
||||
);
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
*/
|
||||
|
||||
import { RuleActionParams } from '@kbn/alerting-types';
|
||||
import { isEmpty, omit, isEqual } from 'lodash';
|
||||
import { isEmpty, omit } from 'lodash';
|
||||
import { RuleFormActionsErrors, RuleFormParamsErrors, RuleUiAction } from '../common';
|
||||
import { RuleFormData, RuleFormState } from '../types';
|
||||
import { validateRuleBase, validateRuleParams } from '../validation';
|
||||
|
@ -109,7 +109,8 @@ export type RuleFormStateReducerAction =
|
|||
}
|
||||
| {
|
||||
type: 'runValidation';
|
||||
};
|
||||
}
|
||||
| { type: 'setTouched' };
|
||||
|
||||
const getUpdateWithValidation =
|
||||
(ruleFormState: RuleFormState) =>
|
||||
|
@ -119,7 +120,6 @@ const getUpdateWithValidation =
|
|||
selectedRuleTypeModel,
|
||||
multiConsumerSelection,
|
||||
selectedRuleType,
|
||||
formData: originalFormData,
|
||||
} = ruleFormState;
|
||||
|
||||
const formData = updater();
|
||||
|
@ -150,14 +150,11 @@ const getUpdateWithValidation =
|
|||
}
|
||||
}
|
||||
|
||||
const touched = !isEqual(originalFormData, formData);
|
||||
|
||||
return {
|
||||
...ruleFormState,
|
||||
formData,
|
||||
baseErrors,
|
||||
paramsErrors,
|
||||
touched,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -356,6 +353,12 @@ export const ruleFormStateReducer = (
|
|||
case 'runValidation': {
|
||||
return updateWithValidation(() => formData);
|
||||
}
|
||||
case 'setTouched': {
|
||||
return {
|
||||
...ruleFormState,
|
||||
touched: true,
|
||||
};
|
||||
}
|
||||
default: {
|
||||
return ruleFormState;
|
||||
}
|
||||
|
|
|
@ -10,4 +10,3 @@
|
|||
export * from './rule_page';
|
||||
export * from './rule_page_name_input';
|
||||
export * from './rule_page_footer';
|
||||
export * from './rule_page_confirm_create_rule';
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
import {
|
||||
EuiButtonEmpty,
|
||||
EuiCallOut,
|
||||
EuiConfirmModal,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiPageTemplate,
|
||||
|
@ -21,19 +20,13 @@ import {
|
|||
import { checkActionFormActionTypeEnabled } from '@kbn/alerts-ui-shared';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { useRuleFormScreenContext, useRuleFormState, useRuleFormSteps } from '../hooks';
|
||||
import {
|
||||
DISABLED_ACTIONS_WARNING_TITLE,
|
||||
RULE_FORM_CANCEL_MODAL_CANCEL,
|
||||
RULE_FORM_CANCEL_MODAL_CONFIRM,
|
||||
RULE_FORM_CANCEL_MODAL_DESCRIPTION,
|
||||
RULE_FORM_CANCEL_MODAL_TITLE,
|
||||
RULE_FORM_RETURN_TITLE,
|
||||
} from '../translations';
|
||||
import { DISABLED_ACTIONS_WARNING_TITLE, RULE_FORM_RETURN_TITLE } from '../translations';
|
||||
import type { RuleFormData } from '../types';
|
||||
import { RulePageFooter } from './rule_page_footer';
|
||||
import { RulePageNameInput } from './rule_page_name_input';
|
||||
import { RuleActionsConnectorsModal } from '../rule_actions/rule_actions_connectors_modal';
|
||||
import { RulePageShowRequestModal } from './rule_page_show_request_modal';
|
||||
import { ConfirmRuleClose } from '../components';
|
||||
|
||||
export interface RulePageProps {
|
||||
isEdit?: boolean;
|
||||
|
@ -46,7 +39,7 @@ export const RulePage = (props: RulePageProps) => {
|
|||
const { isEdit = false, isSaving = false, onCancel = () => {}, onSave } = props;
|
||||
const [isCancelModalOpen, setIsCancelModalOpen] = useState<boolean>(false);
|
||||
|
||||
const { formData, multiConsumerSelection, connectorTypes, connectors, touched } =
|
||||
const { formData, multiConsumerSelection, connectorTypes, connectors, touched, onInteraction } =
|
||||
useRuleFormState();
|
||||
|
||||
const { steps } = useRuleFormSteps();
|
||||
|
@ -89,7 +82,16 @@ export const RulePage = (props: RulePageProps) => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<EuiPageTemplate grow bottomBorder offset={0} css={styles}>
|
||||
<EuiPageTemplate
|
||||
grow
|
||||
bottomBorder
|
||||
offset={0}
|
||||
css={styles}
|
||||
onClick={onInteraction}
|
||||
onKeyDown={onInteraction}
|
||||
className="ruleForm__container"
|
||||
data-test-subj="ruleForm"
|
||||
>
|
||||
<EuiPageTemplate.Header>
|
||||
<EuiFlexGroup
|
||||
direction="column"
|
||||
|
@ -140,18 +142,7 @@ export const RulePage = (props: RulePageProps) => {
|
|||
</EuiPageTemplate.Section>
|
||||
</EuiPageTemplate>
|
||||
{isCancelModalOpen && (
|
||||
<EuiConfirmModal
|
||||
onCancel={() => setIsCancelModalOpen(false)}
|
||||
onConfirm={onCancel}
|
||||
data-test-subj="confirmRuleCloseModal"
|
||||
buttonColor="danger"
|
||||
defaultFocusedButton="confirm"
|
||||
title={RULE_FORM_CANCEL_MODAL_TITLE}
|
||||
confirmButtonText={RULE_FORM_CANCEL_MODAL_CONFIRM}
|
||||
cancelButtonText={RULE_FORM_CANCEL_MODAL_CANCEL}
|
||||
>
|
||||
<p>{RULE_FORM_CANCEL_MODAL_DESCRIPTION}</p>
|
||||
</EuiConfirmModal>
|
||||
<ConfirmRuleClose onCancel={() => setIsCancelModalOpen(false)} onConfirm={onCancel} />
|
||||
)}
|
||||
{isConnectorsScreenVisible && <RuleActionsConnectorsModal />}
|
||||
{isShowRequestScreenVisible && <RulePageShowRequestModal isEdit={isEdit} />}
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
/*
|
||||
* 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 { fireEvent, render, screen } from '@testing-library/react';
|
||||
import { RulePageConfirmCreateRule } from './rule_page_confirm_create_rule';
|
||||
import {
|
||||
CONFIRM_RULE_SAVE_CONFIRM_BUTTON_TEXT,
|
||||
CONFIRM_RULE_SAVE_CANCEL_BUTTON_TEXT,
|
||||
CONFIRM_RULE_SAVE_MESSAGE_TEXT,
|
||||
} from '../translations';
|
||||
|
||||
const onConfirmMock = jest.fn();
|
||||
const onCancelMock = jest.fn();
|
||||
|
||||
describe('rulePageConfirmCreateRule', () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test('renders correctly', () => {
|
||||
render(<RulePageConfirmCreateRule onConfirm={onConfirmMock} onCancel={onCancelMock} />);
|
||||
|
||||
expect(screen.getByTestId('rulePageConfirmCreateRule')).toBeInTheDocument();
|
||||
expect(screen.getByText(CONFIRM_RULE_SAVE_CONFIRM_BUTTON_TEXT)).toBeInTheDocument();
|
||||
expect(screen.getByText(CONFIRM_RULE_SAVE_CANCEL_BUTTON_TEXT)).toBeInTheDocument();
|
||||
expect(screen.getByText(CONFIRM_RULE_SAVE_MESSAGE_TEXT)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('can confirm rule creation', () => {
|
||||
render(<RulePageConfirmCreateRule onConfirm={onConfirmMock} onCancel={onCancelMock} />);
|
||||
|
||||
fireEvent.click(screen.getByTestId('confirmModalConfirmButton'));
|
||||
expect(onConfirmMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('can cancel rule creation', () => {
|
||||
render(<RulePageConfirmCreateRule onConfirm={onConfirmMock} onCancel={onCancelMock} />);
|
||||
|
||||
fireEvent.click(screen.getByTestId('confirmModalCancelButton'));
|
||||
expect(onCancelMock).toHaveBeenCalled();
|
||||
});
|
||||
});
|
|
@ -90,7 +90,7 @@ describe('rulePageFooter', () => {
|
|||
render(<RulePageFooter onSave={onSave} onCancel={onCancel} />);
|
||||
|
||||
fireEvent.click(screen.getByTestId('rulePageFooterSaveButton'));
|
||||
expect(screen.getByTestId('rulePageConfirmCreateRule')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('confirmCreateRuleModal')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should not show creat rule confirmation if user cannot read actions', () => {
|
||||
|
@ -113,7 +113,7 @@ describe('rulePageFooter', () => {
|
|||
|
||||
render(<RulePageFooter onSave={onSave} onCancel={onCancel} />);
|
||||
fireEvent.click(screen.getByTestId('rulePageFooterSaveButton'));
|
||||
expect(screen.queryByTestId('rulePageConfirmCreateRule')).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId('confirmCreateRuleModal')).not.toBeInTheDocument();
|
||||
expect(onSave).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
} from '../translations';
|
||||
import { useRuleFormScreenContext, useRuleFormState } from '../hooks';
|
||||
import { hasRuleErrors } from '../validation';
|
||||
import { RulePageConfirmCreateRule } from './rule_page_confirm_create_rule';
|
||||
import { ConfirmCreateRule } from '../components';
|
||||
|
||||
export interface RulePageFooterProps {
|
||||
isEdit?: boolean;
|
||||
|
@ -131,10 +131,7 @@ export const RulePageFooter = (props: RulePageFooterProps) => {
|
|||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
{showCreateConfirmation && (
|
||||
<RulePageConfirmCreateRule
|
||||
onConfirm={onCreateConfirmClick}
|
||||
onCancel={onCreateCancelClick}
|
||||
/>
|
||||
<ConfirmCreateRule onConfirm={onCreateConfirmClick} onCancel={onCreateCancelClick} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -18,7 +18,7 @@ import {
|
|||
EuiTextColor,
|
||||
} from '@elastic/eui';
|
||||
import React, { useCallback } from 'react';
|
||||
import { RequestCodeBlock } from '../request_code_block';
|
||||
import { RequestCodeBlock } from '../components';
|
||||
import { SHOW_REQUEST_MODAL_SUBTITLE, SHOW_REQUEST_MODAL_TITLE } from '../translations';
|
||||
import { useRuleFormScreenContext } from '../hooks';
|
||||
|
||||
|
|
|
@ -638,7 +638,7 @@ export const RULE_FORM_CANCEL_MODAL_CONFIRM = i18n.translate(
|
|||
export const RULE_FORM_CANCEL_MODAL_CANCEL = i18n.translate(
|
||||
'responseOpsRuleForm.ruleForm.ruleFormCancelModalCancel',
|
||||
{
|
||||
defaultMessage: 'Cancel',
|
||||
defaultMessage: 'Keep editing',
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -793,3 +793,9 @@ export const SHOW_REQUEST_MODAL_ERROR = i18n.translate(
|
|||
defaultMessage: 'Sorry about that, something went wrong.',
|
||||
}
|
||||
);
|
||||
|
||||
export const DEFAULT_RULE_NAME = (ruleTypeName: string) =>
|
||||
i18n.translate('responseOpsRuleForm.ruleForm.defaultRuleName', {
|
||||
defaultMessage: `{ruleTypeName} rule`,
|
||||
values: { ruleTypeName },
|
||||
});
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
import { ActionType } from '@kbn/actions-types';
|
||||
import { ActionVariable, RulesSettingsFlapping } from '@kbn/alerting-types';
|
||||
import type { ActionConnector, ActionTypeRegistryContract } from '@kbn/alerts-ui-shared';
|
||||
import type { ChartsPluginSetup } from '@kbn/charts-plugin/public';
|
||||
import type { ApplicationStart } from '@kbn/core-application-browser';
|
||||
import type { DocLinksStart } from '@kbn/core-doc-links-browser';
|
||||
|
@ -16,19 +17,20 @@ import type { HttpStart } from '@kbn/core-http-browser';
|
|||
import type { I18nStart } from '@kbn/core-i18n-browser';
|
||||
import type { NotificationsStart } from '@kbn/core-notifications-browser';
|
||||
import type { ThemeServiceStart } from '@kbn/core-theme-browser';
|
||||
import type { UserProfileService } from '@kbn/core-user-profile-browser';
|
||||
import type { SettingsStart } from '@kbn/core-ui-settings-browser';
|
||||
import type { UserProfileService } from '@kbn/core-user-profile-browser';
|
||||
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||
import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
|
||||
import { RuleCreationValidConsumer } from '@kbn/rule-data-utils';
|
||||
import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public';
|
||||
import type { ActionConnector, ActionTypeRegistryContract } from '@kbn/alerts-ui-shared';
|
||||
import { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public';
|
||||
import {
|
||||
MinimumScheduleInterval,
|
||||
Rule,
|
||||
RuleFormActionsErrors,
|
||||
RuleFormBaseErrors,
|
||||
RuleFormParamsErrors,
|
||||
RuleTypeMetaData,
|
||||
RuleTypeModel,
|
||||
RuleTypeParams,
|
||||
RuleTypeRegistryContract,
|
||||
|
@ -67,9 +69,13 @@ export interface RuleFormPlugins {
|
|||
docLinks: DocLinksStart;
|
||||
ruleTypeRegistry: RuleTypeRegistryContract;
|
||||
actionTypeRegistry: ActionTypeRegistryContract;
|
||||
fieldsMetadata: FieldsMetadataPublicStart;
|
||||
}
|
||||
|
||||
export interface RuleFormState<Params extends RuleTypeParams = RuleTypeParams> {
|
||||
export interface RuleFormState<
|
||||
Params extends RuleTypeParams = RuleTypeParams,
|
||||
MetaData = RuleTypeMetaData
|
||||
> {
|
||||
id?: string;
|
||||
formData: RuleFormData<Params>;
|
||||
plugins: RuleFormPlugins;
|
||||
|
@ -85,7 +91,7 @@ export interface RuleFormState<Params extends RuleTypeParams = RuleTypeParams> {
|
|||
selectedRuleTypeModel: RuleTypeModel<Params>;
|
||||
multiConsumerSelection?: RuleCreationValidConsumer | null;
|
||||
showMustacheAutocompleteSwitch?: boolean;
|
||||
metadata?: Record<string, unknown>;
|
||||
metadata?: MetaData;
|
||||
minimumScheduleInterval?: MinimumScheduleInterval;
|
||||
canShowConsumerSelection?: boolean;
|
||||
validConsumers: RuleCreationValidConsumer[];
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
"@kbn/kibana-react-plugin",
|
||||
"@kbn/core-i18n-browser",
|
||||
"@kbn/core-theme-browser",
|
||||
"@kbn/core-user-profile-browser"
|
||||
"@kbn/core-user-profile-browser",
|
||||
"@kbn/fields-metadata-plugin"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -22,6 +22,8 @@ import {
|
|||
STACK_ALERTS_FEATURE_ID,
|
||||
} from '@kbn/rule-data-utils';
|
||||
import { RuleTypeMetaData } from '@kbn/alerting-plugin/common';
|
||||
import { RuleFormFlyout } from '@kbn/response-ops-rule-form/flyout';
|
||||
import { isValidRuleFormPlugins } from '@kbn/response-ops-rule-form/lib';
|
||||
import { DiscoverStateContainer } from '../../../state_management/discover_state';
|
||||
import { AppMenuDiscoverParams } from './types';
|
||||
import { DiscoverServices } from '../../../../../build_services';
|
||||
|
@ -38,6 +40,8 @@ interface EsQueryAlertMetaData extends RuleTypeMetaData {
|
|||
adHocDataViewList: DataView[];
|
||||
}
|
||||
|
||||
const RuleFormFlyoutWithType = RuleFormFlyout<EsQueryAlertMetaData>;
|
||||
|
||||
const CreateAlertFlyout: React.FC<{
|
||||
discoverParams: AppMenuDiscoverParams;
|
||||
services: DiscoverServices;
|
||||
|
@ -47,7 +51,9 @@ const CreateAlertFlyout: React.FC<{
|
|||
const query = stateContainer.appState.getState().query;
|
||||
|
||||
const { dataView, isEsqlMode, adHocDataViews, onUpdateAdHocDataViews } = discoverParams;
|
||||
const { triggersActionsUi } = services;
|
||||
const {
|
||||
triggersActionsUi: { ruleTypeRegistry, actionTypeRegistry },
|
||||
} = services;
|
||||
const timeField = getTimeField(dataView);
|
||||
|
||||
/**
|
||||
|
@ -79,24 +85,33 @@ const CreateAlertFlyout: React.FC<{
|
|||
[adHocDataViews]
|
||||
);
|
||||
|
||||
return triggersActionsUi?.getAddRuleFlyout({
|
||||
metadata: discoverMetadata,
|
||||
consumer: 'alerts',
|
||||
onClose: (_, metadata) => {
|
||||
onUpdateAdHocDataViews(metadata!.adHocDataViewList);
|
||||
onFinishAction();
|
||||
},
|
||||
onSave: async (metadata) => {
|
||||
onUpdateAdHocDataViews(metadata!.adHocDataViewList);
|
||||
},
|
||||
canChangeTrigger: false,
|
||||
ruleTypeId: ES_QUERY_ID,
|
||||
initialValues: { params: getParams() },
|
||||
validConsumers: EsQueryValidConsumer,
|
||||
useRuleProducer: true,
|
||||
// Default to the Logs consumer if it's available. This should fall back to Stack Alerts if it's not.
|
||||
initialSelectedConsumer: AlertConsumers.LOGS,
|
||||
});
|
||||
// Some of the rule form's required plugins are from x-pack, so make sure they're defined before
|
||||
// rendering the flyout. The alerting plugin is also part of x-pack, so this check should probably never
|
||||
// return false. This is mostly here because Typescript requires us to mark x-pack plugins as optional.
|
||||
if (!isValidRuleFormPlugins(services)) return null;
|
||||
|
||||
return (
|
||||
<RuleFormFlyoutWithType
|
||||
plugins={{
|
||||
...services,
|
||||
ruleTypeRegistry,
|
||||
actionTypeRegistry,
|
||||
}}
|
||||
initialMetadata={discoverMetadata}
|
||||
consumer={'alerts'}
|
||||
onCancel={onFinishAction}
|
||||
onSubmit={onFinishAction}
|
||||
onChangeMetaData={(metadata: EsQueryAlertMetaData) =>
|
||||
onUpdateAdHocDataViews(metadata.adHocDataViewList)
|
||||
}
|
||||
ruleTypeId={ES_QUERY_ID}
|
||||
initialValues={{ params: getParams() }}
|
||||
validConsumers={EsQueryValidConsumer}
|
||||
shouldUseRuleProducer
|
||||
// Default to the Logs consumer if it's available. This should fall back to Stack Alerts if it's not.
|
||||
multiConsumerSelection={AlertConsumers.LOGS}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const getAlertsAppMenuItem = ({
|
||||
|
|
|
@ -7,11 +7,14 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { AppMenuActionId, AppMenuActionType, AppMenuRegistry } from '@kbn/discover-utils';
|
||||
import { DATA_QUALITY_LOCATOR_ID, DataQualityLocatorParams } from '@kbn/deeplinks-observability';
|
||||
import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/rule-data-utils';
|
||||
import { RuleFormFlyout } from '@kbn/response-ops-rule-form/flyout';
|
||||
import { isOfQueryType } from '@kbn/es-query';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { isValidRuleFormPlugins } from '@kbn/response-ops-rule-form/lib';
|
||||
import { AppMenuExtensionParams } from '../../../..';
|
||||
import type { RootProfileProvider } from '../../../../profiles';
|
||||
import { ProfileProviderServices } from '../../../profile_provider_services';
|
||||
|
@ -74,7 +77,11 @@ const registerDatasetQualityLink = (
|
|||
|
||||
const registerCustomThresholdRuleAction = (
|
||||
registry: AppMenuRegistry,
|
||||
{ data, triggersActionsUi }: ProfileProviderServices,
|
||||
{
|
||||
data,
|
||||
triggersActionsUi: { ruleTypeRegistry, actionTypeRegistry },
|
||||
...services
|
||||
}: ProfileProviderServices,
|
||||
{ dataView }: AppMenuExtensionParams
|
||||
) => {
|
||||
registry.registerCustomActionUnderSubmenu(AppMenuActionId.alerts, {
|
||||
|
@ -91,21 +98,34 @@ const registerCustomThresholdRuleAction = (
|
|||
const index = dataView?.toMinimalSpec();
|
||||
const { filters, query } = data.query.getState();
|
||||
|
||||
return triggersActionsUi.getAddRuleFlyout({
|
||||
consumer: 'logs',
|
||||
ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID,
|
||||
canChangeTrigger: false,
|
||||
initialValues: {
|
||||
params: {
|
||||
searchConfiguration: {
|
||||
index,
|
||||
query,
|
||||
filter: filters,
|
||||
// Some of the rule form's required plugins are from x-pack, so make sure they're defined before
|
||||
// rendering the flyout. The alerting plugin is also part of x-pack, so this check should probably never
|
||||
// return false. This is mostly here because Typescript requires us to mark x-pack plugins as optional.
|
||||
const plugins = { ...services, data };
|
||||
if (!isValidRuleFormPlugins(plugins)) return null;
|
||||
|
||||
return (
|
||||
<RuleFormFlyout
|
||||
plugins={{
|
||||
...plugins,
|
||||
ruleTypeRegistry,
|
||||
actionTypeRegistry,
|
||||
}}
|
||||
consumer={'logs'}
|
||||
ruleTypeId={OBSERVABILITY_THRESHOLD_RULE_TYPE_ID}
|
||||
initialValues={{
|
||||
params: {
|
||||
searchConfiguration: {
|
||||
index,
|
||||
query,
|
||||
filter: filters,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
onClose: onFinishAction,
|
||||
});
|
||||
}}
|
||||
onSubmit={onFinishAction}
|
||||
onCancel={onFinishAction}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
|
@ -100,7 +100,8 @@
|
|||
"@kbn/esql-ast",
|
||||
"@kbn/discover-shared-plugin",
|
||||
"@kbn/embeddable-enhanced-plugin",
|
||||
"@kbn/ui-theme"
|
||||
"@kbn/ui-theme",
|
||||
"@kbn/response-ops-rule-form",
|
||||
],
|
||||
"exclude": ["target/**/*"]
|
||||
}
|
||||
|
|
|
@ -14,6 +14,13 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
|||
|
||||
return {
|
||||
...functionalConfig.getAll(),
|
||||
kbnTestServer: {
|
||||
...functionalConfig.get('kbnTestServer'),
|
||||
serverArgs: [
|
||||
...functionalConfig.get('kbnTestServer.serverArgs'),
|
||||
'--xpack.encryptedSavedObjects.encryptionKey="wuGNaIhoMpk5sO4UBxgr3NyW1sFcLgIf"', // required for alerts plugin to work
|
||||
],
|
||||
},
|
||||
testFiles: [require.resolve('.')],
|
||||
};
|
||||
}
|
||||
|
|
|
@ -14,10 +14,11 @@
|
|||
"actions",
|
||||
"kibanaReact",
|
||||
"features",
|
||||
"developerExamples"
|
||||
"developerExamples",
|
||||
"unifiedSearch",
|
||||
"dataViews",
|
||||
"fieldsMetadata"
|
||||
],
|
||||
"requiredBundles": [
|
||||
"kibanaReact"
|
||||
]
|
||||
"requiredBundles": ["kibanaReact"]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,17 +6,34 @@
|
|||
*/
|
||||
|
||||
import React, { useState, useCallback } from 'react';
|
||||
|
||||
import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
|
||||
import { ChartsPluginStart } from '@kbn/charts-plugin/public';
|
||||
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||
import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public';
|
||||
import { CoreStart } from '@kbn/core/public';
|
||||
import { RuleFormFlyout } from '@kbn/response-ops-rule-form/flyout';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { EuiIcon, EuiFlexItem, EuiCard, EuiFlexGroup } from '@elastic/eui';
|
||||
import { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public';
|
||||
|
||||
import { AlertingExampleComponentParams } from '../application';
|
||||
import { ALERTING_EXAMPLE_APP_ID } from '../../common/constants';
|
||||
|
||||
type KibanaDeps = {
|
||||
dataViews: DataViewsPublicPluginStart;
|
||||
charts: ChartsPluginStart;
|
||||
data: DataPublicPluginStart;
|
||||
unifiedSearch: UnifiedSearchPublicPluginStart;
|
||||
fieldsMetadata: FieldsMetadataPublicStart;
|
||||
} & CoreStart;
|
||||
|
||||
export const CreateAlert = ({
|
||||
triggersActionsUi: { getAddRuleFlyout: AddRuleFlyout },
|
||||
triggersActionsUi: { ruleTypeRegistry, actionTypeRegistry },
|
||||
}: Pick<AlertingExampleComponentParams, 'triggersActionsUi'>) => {
|
||||
const [ruleFlyoutVisible, setRuleFlyoutVisibility] = useState<boolean>(false);
|
||||
|
||||
const { services } = useKibana<KibanaDeps>();
|
||||
|
||||
const onCloseAlertFlyout = useCallback(
|
||||
() => setRuleFlyoutVisibility(false),
|
||||
[setRuleFlyoutVisibility]
|
||||
|
@ -34,7 +51,12 @@ export const CreateAlert = ({
|
|||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
{ruleFlyoutVisible ? (
|
||||
<AddRuleFlyout consumer={ALERTING_EXAMPLE_APP_ID} onClose={onCloseAlertFlyout} />
|
||||
<RuleFormFlyout
|
||||
plugins={{ ...services, ruleTypeRegistry, actionTypeRegistry }}
|
||||
consumer={ALERTING_EXAMPLE_APP_ID}
|
||||
onCancel={onCloseAlertFlyout}
|
||||
onSubmit={onCloseAlertFlyout}
|
||||
/>
|
||||
) : null}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -28,5 +28,9 @@
|
|||
"@kbn/shared-ux-router",
|
||||
"@kbn/config-schema",
|
||||
"@kbn/alerts-as-data-utils",
|
||||
"@kbn/data-views-plugin",
|
||||
"@kbn/unified-search-plugin",
|
||||
"@kbn/response-ops-rule-form",
|
||||
"@kbn/fields-metadata-plugin",
|
||||
]
|
||||
}
|
||||
|
|
|
@ -19,7 +19,8 @@
|
|||
"dataViewEditor",
|
||||
"unifiedSearch",
|
||||
"fieldFormats",
|
||||
"licensing"
|
||||
"licensing",
|
||||
"fieldsMetadata"
|
||||
],
|
||||
"optionalPlugins": ["spaces"]
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import { __IntlProvider as IntlProvider } from '@kbn/i18n-react';
|
|||
import { CREATE_RULE_ROUTE, EDIT_RULE_ROUTE, RuleForm } from '@kbn/response-ops-rule-form';
|
||||
import { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
|
||||
import { LicensingPluginStart } from '@kbn/licensing-plugin/public';
|
||||
import { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public';
|
||||
import { TriggersActionsUiExamplePublicStartDeps } from './plugin';
|
||||
|
||||
import { Page } from './components/page';
|
||||
|
@ -58,6 +59,7 @@ export interface TriggersActionsUiExampleComponentParams {
|
|||
unifiedSearch: UnifiedSearchPublicPluginStart;
|
||||
fieldFormats: FieldFormatsStart;
|
||||
licensing: LicensingPluginStart;
|
||||
fieldsMetadata: FieldsMetadataPublicStart;
|
||||
}
|
||||
|
||||
const TriggersActionsUiExampleApp = ({
|
||||
|
@ -284,6 +286,7 @@ export const renderApp = (
|
|||
unifiedSearch={deps.unifiedSearch}
|
||||
fieldFormats={deps.fieldFormats}
|
||||
licensing={deps.licensing}
|
||||
fieldsMetadata={deps.fieldsMetadata}
|
||||
{...core}
|
||||
/>
|
||||
</IntlProvider>
|
||||
|
|
|
@ -19,6 +19,7 @@ import {
|
|||
} from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
|
||||
import { LicensingPluginStart } from '@kbn/licensing-plugin/public';
|
||||
import { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public';
|
||||
import { getConnectorType as getSystemLogExampleConnectorType } from './connector_types/system_log_example/system_log_example';
|
||||
|
||||
export interface TriggersActionsUiExamplePublicSetupDeps {
|
||||
|
@ -37,6 +38,7 @@ export interface TriggersActionsUiExamplePublicStartDeps {
|
|||
unifiedSearch: UnifiedSearchPublicPluginStart;
|
||||
fieldFormats: FieldFormatsStart;
|
||||
licensing: LicensingPluginStart;
|
||||
fieldsMetadata: FieldsMetadataPublicStart;
|
||||
}
|
||||
|
||||
export class TriggersActionsUiExamplePlugin
|
||||
|
|
|
@ -34,5 +34,6 @@
|
|||
"@kbn/field-formats-plugin",
|
||||
"@kbn/licensing-plugin",
|
||||
"@kbn/response-ops-rule-form",
|
||||
"@kbn/fields-metadata-plugin",
|
||||
]
|
||||
}
|
||||
|
|
|
@ -42,7 +42,11 @@ export function KibanaReactStorybookDecorator(Story: ComponentType) {
|
|||
ObservabilityAIAssistantMultipaneFlyoutContext,
|
||||
service: mockService,
|
||||
},
|
||||
triggersActionsUi: { getAddRuleFlyout: {}, getAddConnectorFlyout: {} },
|
||||
triggersActionsUi: {
|
||||
ruleTypeRegistry: {},
|
||||
actionTypeRegistry: {},
|
||||
getAddConnectorFlyout: {},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ObservabilityAIAssistantChatServiceContext.Provider value={mockChatService}>
|
||||
|
|
|
@ -1,18 +1,14 @@
|
|||
{
|
||||
"type": "plugin",
|
||||
"id": "@kbn/monitoring-plugin",
|
||||
"owner": [
|
||||
"@elastic/stack-monitoring"
|
||||
],
|
||||
"owner": ["@elastic/stack-monitoring"],
|
||||
"group": "platform",
|
||||
"visibility": "private",
|
||||
"plugin": {
|
||||
"id": "monitoring",
|
||||
"browser": true,
|
||||
"server": true,
|
||||
"configPath": [
|
||||
"monitoring"
|
||||
],
|
||||
"configPath": ["monitoring"],
|
||||
"requiredPlugins": [
|
||||
"licensing",
|
||||
"features",
|
||||
|
@ -20,23 +16,21 @@
|
|||
"navigation",
|
||||
"dataViews",
|
||||
"unifiedSearch",
|
||||
"share"
|
||||
"share",
|
||||
"fieldsMetadata"
|
||||
],
|
||||
"optionalPlugins": [
|
||||
"usageCollection",
|
||||
"home",
|
||||
"cloud",
|
||||
"triggersActionsUi",
|
||||
"charts",
|
||||
"alerting",
|
||||
"actions",
|
||||
"encryptedSavedObjects",
|
||||
"dashboard",
|
||||
"fleet"
|
||||
],
|
||||
"requiredBundles": [
|
||||
"kibanaUtils",
|
||||
"alerting",
|
||||
"kibanaReact",
|
||||
]
|
||||
"requiredBundles": ["kibanaUtils", "alerting", "kibanaReact"]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,19 +5,37 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { Fragment, useMemo } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiSwitch } from '@elastic/eui';
|
||||
import { BASE_ALERTING_API_PATH } from '@kbn/alerting-plugin/common';
|
||||
import { CommonAlert } from '../../common/types/alerts';
|
||||
import { Legacy } from '../legacy_shims';
|
||||
import { RuleFormFlyout } from '@kbn/response-ops-rule-form/flyout';
|
||||
import { isValidRuleFormPlugins } from '@kbn/response-ops-rule-form/lib';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import React, { Fragment, useCallback, useMemo } from 'react';
|
||||
import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
|
||||
import { ChartsPluginStart } from '@kbn/charts-plugin/public';
|
||||
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||
import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public';
|
||||
import { CoreStart } from '@kbn/core/public';
|
||||
import { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public';
|
||||
import { hideBottomBar, showBottomBar } from '../lib/setup_mode';
|
||||
import { Legacy } from '../legacy_shims';
|
||||
import { CommonAlert } from '../../common/types/alerts';
|
||||
|
||||
interface Props {
|
||||
alert: CommonAlert;
|
||||
compressed?: boolean;
|
||||
}
|
||||
|
||||
type KibanaDeps = {
|
||||
dataViews: DataViewsPublicPluginStart;
|
||||
charts?: ChartsPluginStart;
|
||||
data: DataPublicPluginStart;
|
||||
unifiedSearch: UnifiedSearchPublicPluginStart;
|
||||
fieldsMetadata: FieldsMetadataPublicStart;
|
||||
} & CoreStart;
|
||||
|
||||
export const AlertConfiguration: React.FC<Props> = (props: Props) => {
|
||||
const { alert, compressed } = props;
|
||||
const [showFlyout, setShowFlyout] = React.useState(false);
|
||||
|
@ -25,6 +43,8 @@ export const AlertConfiguration: React.FC<Props> = (props: Props) => {
|
|||
const [isMuted, setIsMuted] = React.useState(alert.muteAll);
|
||||
const [isSaving, setIsSaving] = React.useState(false);
|
||||
|
||||
const { services } = useKibana<KibanaDeps>();
|
||||
|
||||
async function disableAlert() {
|
||||
setIsSaving(true);
|
||||
try {
|
||||
|
@ -82,19 +102,25 @@ export const AlertConfiguration: React.FC<Props> = (props: Props) => {
|
|||
setIsSaving(false);
|
||||
}
|
||||
|
||||
const onClose = useCallback(() => {
|
||||
setShowFlyout(false);
|
||||
showBottomBar();
|
||||
}, []);
|
||||
|
||||
const {
|
||||
triggersActionsUi: { ruleTypeRegistry, actionTypeRegistry },
|
||||
} = Legacy.shims;
|
||||
const flyoutUi = useMemo(
|
||||
() =>
|
||||
showFlyout &&
|
||||
Legacy.shims.triggersActionsUi.getEditRuleFlyout({
|
||||
initialRule: {
|
||||
...alert,
|
||||
ruleTypeId: alert.alertTypeId,
|
||||
},
|
||||
onClose: () => {
|
||||
setShowFlyout(false);
|
||||
showBottomBar();
|
||||
},
|
||||
}),
|
||||
isValidRuleFormPlugins(services) && (
|
||||
<RuleFormFlyout
|
||||
plugins={{ ruleTypeRegistry, actionTypeRegistry, ...services }}
|
||||
id={alert.id}
|
||||
onSubmit={onClose}
|
||||
onCancel={onClose}
|
||||
/>
|
||||
),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[showFlyout]
|
||||
);
|
||||
|
|
|
@ -111,6 +111,7 @@ export class MonitoringPlugin
|
|||
usageCollection: plugins.usageCollection,
|
||||
appMountParameters: params,
|
||||
dataViews: pluginsStart.dataViews,
|
||||
fieldsMetadata: pluginsStart.fieldsMetadata,
|
||||
};
|
||||
|
||||
Legacy.init({
|
||||
|
@ -126,6 +127,7 @@ export class MonitoringPlugin
|
|||
appMountParameters: deps.appMountParameters,
|
||||
dataViews: deps.dataViews,
|
||||
share: deps.share,
|
||||
fieldsMetadata: deps.fieldsMetadata,
|
||||
});
|
||||
|
||||
const config = Object.fromEntries(externalConfig);
|
||||
|
|
|
@ -17,6 +17,7 @@ import { DashboardStart } from '@kbn/dashboard-plugin/public';
|
|||
import { FleetStart } from '@kbn/fleet-plugin/public';
|
||||
import { SharePluginStart } from '@kbn/share-plugin/public';
|
||||
import { ReactNode } from 'react';
|
||||
import { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public';
|
||||
|
||||
export interface MonitoringStartPluginDependencies {
|
||||
navigation: NavigationStart;
|
||||
|
@ -27,6 +28,7 @@ export interface MonitoringStartPluginDependencies {
|
|||
dashboard?: DashboardStart;
|
||||
fleet?: FleetStart;
|
||||
share: SharePluginStart;
|
||||
fieldsMetadata: FieldsMetadataPublicStart;
|
||||
}
|
||||
|
||||
interface LegacyStartDependencies {
|
||||
|
|
|
@ -47,6 +47,9 @@
|
|||
"@kbn/core-elasticsearch-server",
|
||||
"@kbn/share-plugin",
|
||||
"@kbn/analytics",
|
||||
"@kbn/response-ops-rule-form",
|
||||
"@kbn/charts-plugin",
|
||||
"@kbn/fields-metadata-plugin",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -30,7 +30,8 @@
|
|||
"charts",
|
||||
"savedObjectsFinder",
|
||||
"savedObjectsManagement",
|
||||
"contentManagement"
|
||||
"contentManagement",
|
||||
"fieldsMetadata"
|
||||
],
|
||||
"optionalPlugins": [
|
||||
"dataViewEditor",
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import type { FC } from 'react';
|
||||
import React, { createContext, useContext, useMemo } from 'react';
|
||||
import { RuleFormFlyout } from '@kbn/response-ops-rule-form/flyout';
|
||||
import { memoize } from 'lodash';
|
||||
import type { Observable } from 'rxjs';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
@ -32,44 +33,43 @@ export const TransformAlertFlyout: FC<TransformAlertFlyoutProps> = ({
|
|||
onCloseFlyout,
|
||||
onSave,
|
||||
}) => {
|
||||
const { triggersActionsUi } = useAppDependencies();
|
||||
const { triggersActionsUi, ...plugins } = useAppDependencies();
|
||||
|
||||
const AlertFlyout = useMemo(() => {
|
||||
if (!triggersActionsUi) return;
|
||||
|
||||
const { ruleTypeRegistry, actionTypeRegistry } = triggersActionsUi;
|
||||
|
||||
const commonProps = {
|
||||
onClose: () => {
|
||||
plugins: { ...plugins, ruleTypeRegistry, actionTypeRegistry },
|
||||
onCancel: () => {
|
||||
onCloseFlyout();
|
||||
},
|
||||
onSave: async () => {
|
||||
onSubmit: async () => {
|
||||
if (onSave) {
|
||||
onSave();
|
||||
}
|
||||
onCloseFlyout();
|
||||
},
|
||||
};
|
||||
|
||||
if (initialAlert) {
|
||||
return triggersActionsUi.getEditRuleFlyout({
|
||||
...commonProps,
|
||||
initialRule: {
|
||||
...initialAlert,
|
||||
ruleTypeId: initialAlert.alertTypeId,
|
||||
},
|
||||
});
|
||||
return <RuleFormFlyout {...commonProps} id={initialAlert.id} />;
|
||||
}
|
||||
|
||||
return triggersActionsUi.getAddRuleFlyout({
|
||||
...commonProps,
|
||||
consumer: 'stackAlerts',
|
||||
canChangeTrigger: false,
|
||||
ruleTypeId: TRANSFORM_RULE_TYPE.TRANSFORM_HEALTH,
|
||||
metadata: {},
|
||||
initialValues: {
|
||||
params: ruleParams!,
|
||||
},
|
||||
});
|
||||
return (
|
||||
<RuleFormFlyout
|
||||
{...commonProps}
|
||||
consumer={'stackAlerts'}
|
||||
ruleTypeId={TRANSFORM_RULE_TYPE.TRANSFORM_HEALTH}
|
||||
initialMetadata={{}}
|
||||
initialValues={{
|
||||
params: ruleParams!,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
// deps on id to avoid re-rendering on auto-refresh
|
||||
}, [triggersActionsUi, initialAlert, ruleParams, onCloseFlyout, onSave]);
|
||||
}, [triggersActionsUi, plugins, initialAlert, ruleParams, onCloseFlyout, onSave]);
|
||||
|
||||
return <>{AlertFlyout}</>;
|
||||
};
|
||||
|
|
|
@ -28,6 +28,7 @@ import type { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-manag
|
|||
import { settingsServiceMock } from '@kbn/core-ui-settings-browser-mocks';
|
||||
import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks';
|
||||
import { userProfileServiceMock } from '@kbn/core-user-profile-browser-mocks';
|
||||
import { fieldsMetadataPluginPublicMock } from '@kbn/fields-metadata-plugin/public/mocks';
|
||||
|
||||
const coreSetup = coreMock.createSetup();
|
||||
const coreStart = coreMock.createStart();
|
||||
|
@ -103,6 +104,7 @@ const appDependencies: AppDependencies = {
|
|||
settings: settingsServiceMock.createStartContract(),
|
||||
savedSearch: savedSearchPluginMock.createStartContract(),
|
||||
contentManagement: contentManagementMock.createStartContract(),
|
||||
fieldsMetadata: fieldsMetadataPluginPublicMock.createStartContract(),
|
||||
};
|
||||
|
||||
export const useAppDependencies = () => {
|
||||
|
|
|
@ -38,6 +38,7 @@ import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
|
|||
import type { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public';
|
||||
import type { SettingsStart } from '@kbn/core-ui-settings-browser';
|
||||
import type { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public';
|
||||
import type { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public';
|
||||
|
||||
export interface AppDependencies {
|
||||
analytics: AnalyticsServiceStart;
|
||||
|
@ -68,6 +69,7 @@ export interface AppDependencies {
|
|||
savedObjectsManagement: SavedObjectsManagementPluginStart;
|
||||
settings: SettingsStart;
|
||||
contentManagement: ContentManagementPublicStart;
|
||||
fieldsMetadata: FieldsMetadataPublicStart;
|
||||
}
|
||||
|
||||
export const useAppDependencies = () => {
|
||||
|
|
|
@ -58,6 +58,7 @@ export async function mountManagementSection(
|
|||
savedObjectsManagement,
|
||||
savedSearch,
|
||||
contentManagement,
|
||||
fieldsMetadata,
|
||||
} = plugins;
|
||||
const { docTitle } = chrome;
|
||||
|
||||
|
@ -95,6 +96,7 @@ export async function mountManagementSection(
|
|||
savedObjectsManagement,
|
||||
savedSearch,
|
||||
contentManagement,
|
||||
fieldsMetadata,
|
||||
};
|
||||
|
||||
const enabledFeatures: TransformEnabledFeatures = {
|
||||
|
|
|
@ -24,6 +24,7 @@ import type { ContentManagementPublicStart } from '@kbn/content-management-plugi
|
|||
import type { SavedSearchPublicPluginStart } from '@kbn/saved-search-plugin/public';
|
||||
import type { PluginInitializerContext } from '@kbn/core/public';
|
||||
import type { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public';
|
||||
import type { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public';
|
||||
import type { ConfigSchema } from '../server/config';
|
||||
import { registerFeature } from './register_feature';
|
||||
import { getTransformHealthRuleType } from './alerting';
|
||||
|
@ -44,6 +45,7 @@ export interface PluginsDependencies {
|
|||
fieldFormats: FieldFormatsStart;
|
||||
savedObjectsManagement: SavedObjectsManagementPluginStart;
|
||||
contentManagement: ContentManagementPublicStart;
|
||||
fieldsMetadata: FieldsMetadataPublicStart;
|
||||
}
|
||||
|
||||
export class TransformUiPlugin {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"extends": "../../../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "target/types",
|
||||
"outDir": "target/types"
|
||||
},
|
||||
"include": [
|
||||
"common/**/*",
|
||||
|
@ -9,7 +9,7 @@
|
|||
"server/**/*",
|
||||
"../../../../../typings/**/*",
|
||||
// have to declare *.json explicitly due to https://github.com/microsoft/TypeScript/issues/25636
|
||||
"public/**/*.json",
|
||||
"public/**/*.json"
|
||||
],
|
||||
"kbn_references": [
|
||||
"@kbn/core",
|
||||
|
@ -80,9 +80,9 @@
|
|||
"@kbn/ml-field-stats-flyout",
|
||||
"@kbn/ml-validators",
|
||||
"@kbn/core-user-profile-browser-mocks",
|
||||
"@kbn/response-ops-rule-params"
|
||||
"@kbn/response-ops-rule-form",
|
||||
"@kbn/response-ops-rule-params",
|
||||
"@kbn/fields-metadata-plugin"
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
]
|
||||
"exclude": ["target/**/*"]
|
||||
}
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
{
|
||||
"type": "plugin",
|
||||
"id": "@kbn/ml-plugin",
|
||||
"owner": [
|
||||
"@elastic/ml-ui"
|
||||
],
|
||||
"owner": ["@elastic/ml-ui"],
|
||||
"group": "platform",
|
||||
"visibility": "shared",
|
||||
"description": "This plugin provides access to the machine learning features provided by Elastic.",
|
||||
|
@ -11,10 +9,7 @@
|
|||
"id": "ml",
|
||||
"browser": true,
|
||||
"server": true,
|
||||
"configPath": [
|
||||
"xpack",
|
||||
"ml"
|
||||
],
|
||||
"configPath": ["xpack", "ml"],
|
||||
"requiredPlugins": [
|
||||
"aiops",
|
||||
"charts",
|
||||
|
@ -37,7 +32,8 @@
|
|||
"savedObjectsManagement",
|
||||
"savedSearch",
|
||||
"contentManagement",
|
||||
"presentationUtil"
|
||||
"presentationUtil",
|
||||
"fieldsMetadata"
|
||||
],
|
||||
"optionalPlugins": [
|
||||
"alerting",
|
||||
|
@ -50,7 +46,7 @@
|
|||
"spaces",
|
||||
"observabilityAIAssistant",
|
||||
"usageCollection",
|
||||
"cases"
|
||||
"cases",
|
||||
],
|
||||
"requiredBundles": [
|
||||
"cases",
|
||||
|
@ -66,8 +62,6 @@
|
|||
"usageCollection",
|
||||
"alerting"
|
||||
],
|
||||
"extraPublicDirs": [
|
||||
"common"
|
||||
]
|
||||
"extraPublicDirs": ["common"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import type { FC } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { EuiButtonEmpty } from '@elastic/eui';
|
||||
import { RuleFormFlyout } from '@kbn/response-ops-rule-form/flyout';
|
||||
|
||||
import type { Rule } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import type { JobId } from '../../common/types/anomaly_detection_jobs';
|
||||
|
@ -38,47 +39,46 @@ export const MlAnomalyAlertFlyout: FC<MlAnomalyAlertFlyoutProps> = ({
|
|||
onSave,
|
||||
}) => {
|
||||
const {
|
||||
services: { triggersActionsUi },
|
||||
services: { triggersActionsUi, ...services },
|
||||
} = useMlKibana();
|
||||
|
||||
const AlertFlyout = useMemo(() => {
|
||||
if (!triggersActionsUi) return;
|
||||
|
||||
const { ruleTypeRegistry, actionTypeRegistry } = triggersActionsUi;
|
||||
|
||||
const commonProps = {
|
||||
onClose: () => {
|
||||
plugins: { ...services, ruleTypeRegistry, actionTypeRegistry },
|
||||
onCancel: () => {
|
||||
onCloseFlyout();
|
||||
},
|
||||
onSave: async () => {
|
||||
onSubmit: async () => {
|
||||
if (onSave) {
|
||||
onSave();
|
||||
}
|
||||
onCloseFlyout();
|
||||
},
|
||||
};
|
||||
|
||||
if (initialAlert) {
|
||||
return triggersActionsUi.getEditRuleFlyout({
|
||||
...commonProps,
|
||||
initialRule: {
|
||||
...initialAlert,
|
||||
ruleTypeId: initialAlert.ruleTypeId ?? initialAlert.alertTypeId,
|
||||
},
|
||||
});
|
||||
return <RuleFormFlyout {...commonProps} id={initialAlert.id} />;
|
||||
}
|
||||
|
||||
return triggersActionsUi.getAddRuleFlyout({
|
||||
...commonProps,
|
||||
consumer: PLUGIN_ID,
|
||||
canChangeTrigger: false,
|
||||
ruleTypeId: ML_ALERT_TYPES.ANOMALY_DETECTION,
|
||||
metadata: {},
|
||||
initialValues: {
|
||||
params: {
|
||||
jobSelection: {
|
||||
jobIds,
|
||||
return (
|
||||
<RuleFormFlyout
|
||||
{...commonProps}
|
||||
consumer={PLUGIN_ID}
|
||||
ruleTypeId={ML_ALERT_TYPES.ANOMALY_DETECTION}
|
||||
initialMetadata={{}}
|
||||
initialValues={{
|
||||
params: {
|
||||
jobSelection: {
|
||||
jobIds,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
// deps on id to avoid re-rendering on auto-refresh
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [triggersActionsUi, initialAlert?.id, jobIds]);
|
||||
|
|
|
@ -102,6 +102,7 @@ const App: FC<AppProps> = ({
|
|||
usageCollection: deps.usageCollection,
|
||||
mlServices: getMlGlobalServices(coreStart, deps.data.dataViews, deps.usageCollection),
|
||||
spaces: deps.spaces,
|
||||
fieldsMetadata: deps.fieldsMetadata,
|
||||
};
|
||||
}, [deps, coreStart]);
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ import type { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public';
|
|||
import type { UiActionsStart } from '@kbn/ui-actions-plugin/public';
|
||||
import type { FieldFormatsRegistry } from '@kbn/field-formats-plugin/common';
|
||||
import type { LicensingPluginStart } from '@kbn/licensing-plugin/public';
|
||||
import type { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public';
|
||||
import type { MlServicesContext } from '../../app';
|
||||
|
||||
interface StartPlugins {
|
||||
|
@ -61,6 +62,7 @@ interface StartPlugins {
|
|||
uiActions: UiActionsStart;
|
||||
unifiedSearch: UnifiedSearchPublicPluginStart;
|
||||
usageCollection?: UsageCollectionSetup;
|
||||
fieldsMetadata: FieldsMetadataPublicStart;
|
||||
}
|
||||
export type StartServices = CoreStart &
|
||||
StartPlugins & {
|
||||
|
|
|
@ -52,6 +52,7 @@ import type { PresentationUtilPluginStart } from '@kbn/presentation-util-plugin/
|
|||
import type { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public';
|
||||
import type { FieldFormatsRegistry } from '@kbn/field-formats-plugin/common';
|
||||
import { ENABLE_ESQL } from '@kbn/esql-utils';
|
||||
import type { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public';
|
||||
import type { MlSharedServices } from './application/services/get_shared_ml_services';
|
||||
import { getMlSharedServices } from './application/services/get_shared_ml_services';
|
||||
import { registerManagementSection } from './application/management';
|
||||
|
@ -102,6 +103,7 @@ export interface MlStartDependencies {
|
|||
uiActions: UiActionsStart;
|
||||
unifiedSearch: UnifiedSearchPublicPluginStart;
|
||||
telemetry: ITelemetryClient;
|
||||
fieldsMetadata: FieldsMetadataPublicStart;
|
||||
}
|
||||
|
||||
export interface MlSetupDependencies {
|
||||
|
@ -223,6 +225,7 @@ export class MlPlugin implements Plugin<MlPluginSetup, MlPluginStart> {
|
|||
usageCollection: pluginsSetup.usageCollection,
|
||||
spaces: pluginsStart.spaces,
|
||||
telemetry: telemetryClient,
|
||||
fieldsMetadata: pluginsStart.fieldsMetadata,
|
||||
},
|
||||
params,
|
||||
this.isServerless,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"extends": "../../../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "target/types",
|
||||
"outDir": "target/types"
|
||||
},
|
||||
"include": [
|
||||
"common/**/*",
|
||||
|
@ -15,9 +15,7 @@
|
|||
"public/**/*.json",
|
||||
"server/**/*.json"
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
],
|
||||
"exclude": ["target/**/*"],
|
||||
"kbn_references": [
|
||||
"@kbn/core",
|
||||
{
|
||||
|
@ -140,7 +138,9 @@
|
|||
"@kbn/core-security-server",
|
||||
"@kbn/response-ops-rule-params",
|
||||
"@kbn/charts-theme",
|
||||
"@kbn/response-ops-rule-form",
|
||||
"@kbn/unsaved-changes-prompt",
|
||||
"@kbn/core-analytics-browser",
|
||||
"@kbn/fields-metadata-plugin",
|
||||
"@kbn/core-analytics-browser"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -60,10 +60,19 @@ export const EsQueryRuleTypeExpression: React.FunctionComponent<
|
|||
}
|
||||
);
|
||||
|
||||
const errorParam = ALL_EXPRESSION_ERROR_KEYS.find((errorKey) => {
|
||||
// @ts-expect-error upgrade typescript v5.1.6
|
||||
return errors[errorKey]?.length >= 1 && ruleParams[errorKey] !== undefined;
|
||||
});
|
||||
const errorParam =
|
||||
ALL_EXPRESSION_ERROR_KEYS.find((errorKey) => {
|
||||
return (
|
||||
// @ts-expect-error upgrade typescript v5.1.6
|
||||
errors[errorKey]?.length >= 1 && ruleParams[errorKey] !== undefined
|
||||
);
|
||||
}) ||
|
||||
// For search source alerts, if the only error is timeField, show this error even if the param is undefined
|
||||
// timeField is inherently a part of the selectable data view, so if the user selects a data view with no
|
||||
// timeField, this data view is incompatible with the rule.
|
||||
(isSearchSource && !!errors.timeField?.length && !errors.searchConfiguration?.length
|
||||
? 'timeField'
|
||||
: undefined);
|
||||
|
||||
const expressionError = !!errorParam && (
|
||||
<>
|
||||
|
|
|
@ -31,7 +31,8 @@
|
|||
"expressions",
|
||||
"lens",
|
||||
"controls",
|
||||
"embeddable"
|
||||
"embeddable",
|
||||
"fieldsMetadata"
|
||||
],
|
||||
"optionalPlugins": [
|
||||
"cloud",
|
||||
|
|
|
@ -36,6 +36,7 @@ import { QueryClientProvider } from '@tanstack/react-query';
|
|||
import { DashboardStart } from '@kbn/dashboard-plugin/public';
|
||||
import { ExpressionsStart } from '@kbn/expressions-plugin/public';
|
||||
import { CloudSetup } from '@kbn/cloud-plugin/public';
|
||||
import { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public';
|
||||
import { suspendedComponentWithProps } from './lib/suspended_component_with_props';
|
||||
import { ActionTypeRegistryContract, RuleTypeRegistryContract } from '../types';
|
||||
import {
|
||||
|
@ -50,7 +51,6 @@ import { KibanaContextProvider, useKibana } from '../common/lib/kibana';
|
|||
import { ConnectorProvider } from './context/connector_context';
|
||||
import { ALERTS_PAGE_ID, CONNECTORS_PLUGIN_ID } from '../common/constants';
|
||||
import { queryClient } from './query_client';
|
||||
import { getIsExperimentalFeatureEnabled } from '../common/get_experimental_features';
|
||||
|
||||
const TriggersActionsUIHome = lazy(() => import('./home'));
|
||||
const RuleDetailsRoute = lazy(
|
||||
|
@ -85,6 +85,7 @@ export interface TriggersAndActionsUiServices extends CoreStart {
|
|||
isServerless: boolean;
|
||||
fieldFormats: FieldFormatsStart;
|
||||
lens: LensPublicStart;
|
||||
fieldsMetadata: FieldsMetadataPublicStart;
|
||||
}
|
||||
|
||||
export const renderApp = (deps: TriggersAndActionsUiServices) => {
|
||||
|
@ -120,25 +121,19 @@ export const AppWithoutRouter = ({ sectionsRegex }: { sectionsRegex: string }) =
|
|||
application: { navigateToApp },
|
||||
} = useKibana().services;
|
||||
|
||||
const isUsingRuleCreateFlyout = getIsExperimentalFeatureEnabled('isUsingRuleCreateFlyout');
|
||||
|
||||
return (
|
||||
<ConnectorProvider value={{ services: { validateEmailAddresses } }}>
|
||||
<Routes>
|
||||
{!isUsingRuleCreateFlyout && (
|
||||
<Route
|
||||
exact
|
||||
path={createRuleRoute}
|
||||
component={suspendedComponentWithProps(CreateRuleRoute, 'xl')}
|
||||
/>
|
||||
)}
|
||||
{!isUsingRuleCreateFlyout && (
|
||||
<Route
|
||||
exact
|
||||
path={editRuleRoute}
|
||||
component={suspendedComponentWithProps(EditRuleRoute, 'xl')}
|
||||
/>
|
||||
)}
|
||||
<Route
|
||||
exact
|
||||
path={createRuleRoute}
|
||||
component={suspendedComponentWithProps(CreateRuleRoute, 'xl')}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path={editRuleRoute}
|
||||
component={suspendedComponentWithProps(EditRuleRoute, 'xl')}
|
||||
/>
|
||||
<Route
|
||||
path={`/:section(${sectionsRegex})`}
|
||||
component={suspendedComponentWithProps(TriggersActionsUIHome, 'xl')}
|
||||
|
|
|
@ -23,31 +23,24 @@ import { useLoadRuleTypesQuery } from '../../../hooks/use_load_rule_types_query'
|
|||
import { RuleDefinitionProps } from '../../../../types';
|
||||
import { RuleType } from '../../../..';
|
||||
import { useKibana } from '../../../../common/lib/kibana';
|
||||
import { getIsExperimentalFeatureEnabled } from '../../../../common/get_experimental_features';
|
||||
import {
|
||||
hasAllPrivilege,
|
||||
hasExecuteActionsCapability,
|
||||
hasShowActionsCapability,
|
||||
} from '../../../lib/capabilities';
|
||||
import { RuleActions } from './rule_actions';
|
||||
import { RuleEdit } from '../../rule_form';
|
||||
|
||||
export const RuleDefinition: React.FunctionComponent<RuleDefinitionProps> = ({
|
||||
rule,
|
||||
actionTypeRegistry,
|
||||
ruleTypeRegistry,
|
||||
onEditRule,
|
||||
hideEditButton = false,
|
||||
filteredRuleTypes = [],
|
||||
useNewRuleForm = false,
|
||||
}) => {
|
||||
const {
|
||||
application: { capabilities, navigateToApp },
|
||||
} = useKibana().services;
|
||||
|
||||
const isUsingRuleCreateFlyout = getIsExperimentalFeatureEnabled('isUsingRuleCreateFlyout');
|
||||
|
||||
const [editFlyoutVisible, setEditFlyoutVisible] = useState<boolean>(false);
|
||||
const [ruleType, setRuleType] = useState<RuleType>();
|
||||
|
||||
const hasConditions = !!(rule?.params.criteria as any[])?.length;
|
||||
|
@ -110,17 +103,13 @@ export const RuleDefinition: React.FunctionComponent<RuleDefinitionProps> = ({
|
|||
}, [rule, ruleTypeRegistry]);
|
||||
|
||||
const onEditRuleClick = () => {
|
||||
if (!isUsingRuleCreateFlyout && useNewRuleForm) {
|
||||
navigateToApp('management', {
|
||||
path: `insightsAndAlerting/triggersActions/${getEditRuleRoute(rule.id)}`,
|
||||
state: {
|
||||
returnApp: 'management',
|
||||
returnPath: `insightsAndAlerting/triggersActions/${getRuleDetailsRoute(rule.id)}`,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
setEditFlyoutVisible(true);
|
||||
}
|
||||
navigateToApp('management', {
|
||||
path: `insightsAndAlerting/triggersActions/${getEditRuleRoute(rule.id)}`,
|
||||
state: {
|
||||
returnApp: 'management',
|
||||
returnPath: `insightsAndAlerting/triggersActions/${getRuleDetailsRoute(rule.id)}`,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const ruleDefinitionList = [
|
||||
|
@ -239,18 +228,6 @@ export const RuleDefinition: React.FunctionComponent<RuleDefinitionProps> = ({
|
|||
<EuiSpacer size="m" />
|
||||
<EuiDescriptionList compressed={true} type="column" listItems={ruleDefinitionList} />
|
||||
</EuiPanel>
|
||||
{editFlyoutVisible && (
|
||||
<RuleEdit
|
||||
onSave={() => {
|
||||
setEditFlyoutVisible(false);
|
||||
return onEditRule();
|
||||
}}
|
||||
initialRule={rule}
|
||||
onClose={() => setEditFlyoutVisible(false)}
|
||||
ruleTypeRegistry={ruleTypeRegistry}
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
/>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -28,6 +28,7 @@ export const RuleFormRoute = () => {
|
|||
ruleTypeRegistry,
|
||||
actionTypeRegistry,
|
||||
chrome,
|
||||
isServerless,
|
||||
setBreadcrumbs,
|
||||
...startServices
|
||||
} = useKibana().services;
|
||||
|
@ -75,6 +76,9 @@ export const RuleFormRoute = () => {
|
|||
actionTypeRegistry,
|
||||
...startServices,
|
||||
}}
|
||||
isServerless={isServerless}
|
||||
id={id}
|
||||
ruleTypeId={ruleTypeId}
|
||||
onCancel={() => {
|
||||
if (returnApp && returnPath) {
|
||||
application.navigateToApp(returnApp, { path: returnPath });
|
||||
|
|
|
@ -216,7 +216,8 @@ export const RulesList = ({
|
|||
const cloneRuleId = useRef<null | string>(null);
|
||||
|
||||
const isRuleStatusFilterEnabled = getIsExperimentalFeatureEnabled('ruleStatusFilter');
|
||||
const isUsingRuleCreateFlyout = getIsExperimentalFeatureEnabled('isUsingRuleCreateFlyout');
|
||||
// TODO: Remove this when removing the v1 flyout code
|
||||
const isUsingRuleCreateFlyout = false; // getIsExperimentalFeatureEnabled('isUsingRuleCreateFlyout');
|
||||
|
||||
const [percentileOptions, setPercentileOptions] =
|
||||
useState<EuiSelectableOption[]>(initialPercentileOptions);
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { QueryClientProvider } from '@tanstack/react-query';
|
||||
import { ConnectorProvider } from '../application/context/connector_context';
|
||||
import { RuleAdd } from '../application/sections/rule_form';
|
||||
import type { ConnectorServices, RuleAddProps, RuleTypeParams, RuleTypeMetaData } from '../types';
|
||||
import { queryClient } from '../application/query_client';
|
||||
|
||||
export const getAddRuleFlyoutLazy = <
|
||||
Params extends RuleTypeParams = RuleTypeParams,
|
||||
MetaData extends RuleTypeMetaData = RuleTypeMetaData
|
||||
>(
|
||||
props: RuleAddProps<Params, MetaData> & { connectorServices: ConnectorServices }
|
||||
) => {
|
||||
return (
|
||||
<ConnectorProvider value={{ services: props.connectorServices }}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<RuleAdd {...props} />
|
||||
</QueryClientProvider>
|
||||
</ConnectorProvider>
|
||||
);
|
||||
};
|
|
@ -1,28 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { QueryClientProvider } from '@tanstack/react-query';
|
||||
import { ConnectorProvider } from '../application/context/connector_context';
|
||||
import { RuleEdit } from '../application/sections/rule_form';
|
||||
import type { ConnectorServices, RuleEditProps, RuleTypeParams, RuleTypeMetaData } from '../types';
|
||||
import { queryClient } from '../application/query_client';
|
||||
|
||||
export const getEditRuleFlyoutLazy = <
|
||||
Params extends RuleTypeParams = RuleTypeParams,
|
||||
MetaData extends RuleTypeMetaData = RuleTypeMetaData
|
||||
>(
|
||||
props: RuleEditProps<Params, MetaData> & { connectorServices: ConnectorServices }
|
||||
) => {
|
||||
return (
|
||||
<ConnectorProvider value={{ services: props.connectorServices }}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<RuleEdit {...props} />
|
||||
</QueryClientProvider>
|
||||
</ConnectorProvider>
|
||||
);
|
||||
};
|
|
@ -20,6 +20,7 @@ import { TriggersAndActionsUiServices } from '../../../application/rules_app';
|
|||
import { RuleTypeRegistryContract, ActionTypeRegistryContract } from '../../../types';
|
||||
import { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public';
|
||||
import { expressionsPluginMock } from '@kbn/expressions-plugin/public/mocks';
|
||||
import { fieldsMetadataPluginPublicMock } from '@kbn/fields-metadata-plugin/public/mocks';
|
||||
|
||||
export const createStartServicesMock = (): TriggersAndActionsUiServices => {
|
||||
const core = coreMock.createStart();
|
||||
|
@ -68,6 +69,7 @@ export const createStartServicesMock = (): TriggersAndActionsUiServices => {
|
|||
isServerless: false,
|
||||
fieldFormats: fieldFormatsServiceMock.createStartContract(),
|
||||
lens: lensPluginMock.createStartContract(),
|
||||
fieldsMetadata: fieldsMetadataPluginPublicMock.createStartContract(),
|
||||
} as TriggersAndActionsUiServices;
|
||||
};
|
||||
|
||||
|
|
|
@ -13,8 +13,6 @@ import type { TriggersAndActionsUIPublicPluginStart } from './plugin';
|
|||
|
||||
import { getAddConnectorFlyoutLazy } from './common/get_add_connector_flyout';
|
||||
import { getEditConnectorFlyoutLazy } from './common/get_edit_connector_flyout';
|
||||
import { getAddRuleFlyoutLazy } from './common/get_add_rule_flyout';
|
||||
import { getEditRuleFlyoutLazy } from './common/get_edit_rule_flyout';
|
||||
import {
|
||||
ActionTypeModel,
|
||||
RuleTypeModel,
|
||||
|
@ -75,22 +73,6 @@ function createStartMock(): TriggersAndActionsUIPublicPluginStart {
|
|||
connectorServices,
|
||||
});
|
||||
},
|
||||
getAddRuleFlyout: (props) => {
|
||||
return getAddRuleFlyoutLazy({
|
||||
...props,
|
||||
actionTypeRegistry,
|
||||
ruleTypeRegistry,
|
||||
connectorServices,
|
||||
});
|
||||
},
|
||||
getEditRuleFlyout: (props) => {
|
||||
return getEditRuleFlyoutLazy({
|
||||
...props,
|
||||
actionTypeRegistry,
|
||||
ruleTypeRegistry,
|
||||
connectorServices,
|
||||
});
|
||||
},
|
||||
getAlertsSearchBar: (props: AlertsSearchBarProps) => {
|
||||
return getAlertsSearchBarLazy(props);
|
||||
},
|
||||
|
|
|
@ -5,91 +5,87 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { CoreSetup, CoreStart, Plugin as CorePlugin } from '@kbn/core/public';
|
||||
import { Plugin as CorePlugin, CoreSetup, CoreStart } from '@kbn/core/public';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ReactElement } from 'react';
|
||||
import { PluginInitializerContext } from '@kbn/core/public';
|
||||
import { FeaturesPluginStart } from '@kbn/features-plugin/public';
|
||||
import { KibanaFeature } from '@kbn/features-plugin/common';
|
||||
import { ManagementAppMountParams, ManagementSetup } from '@kbn/management-plugin/public';
|
||||
import type { HomePublicPluginSetup } from '@kbn/home-plugin/public';
|
||||
import { ChartsPluginStart } from '@kbn/charts-plugin/public';
|
||||
import { PluginStartContract as AlertingStart } from '@kbn/alerting-plugin/public';
|
||||
import { ActionsPublicPluginSetup } from '@kbn/actions-plugin/public';
|
||||
import { RuleAction } from '@kbn/alerting-plugin/common';
|
||||
import { PluginStartContract as AlertingStart } from '@kbn/alerting-plugin/public';
|
||||
import { TypeRegistry } from '@kbn/alerts-ui-shared/src/common/type_registry';
|
||||
import { ChartsPluginStart } from '@kbn/charts-plugin/public';
|
||||
import { CloudSetup } from '@kbn/cloud-plugin/public';
|
||||
import { PluginInitializerContext } from '@kbn/core/public';
|
||||
import { DashboardStart } from '@kbn/dashboard-plugin/public';
|
||||
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||
import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
|
||||
import type { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public';
|
||||
import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
|
||||
import { ExpressionsStart } from '@kbn/expressions-plugin/public';
|
||||
import { KibanaFeature } from '@kbn/features-plugin/common';
|
||||
import { FeaturesPluginStart } from '@kbn/features-plugin/public';
|
||||
import { FieldFormatsRegistry } from '@kbn/field-formats-plugin/common';
|
||||
import type { HomePublicPluginSetup } from '@kbn/home-plugin/public';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { Storage } from '@kbn/kibana-utils-plugin/public';
|
||||
import { LensPublicStart } from '@kbn/lens-plugin/public';
|
||||
import type { LicensingPluginStart } from '@kbn/licensing-plugin/public';
|
||||
import { ManagementAppMountParams, ManagementSetup } from '@kbn/management-plugin/public';
|
||||
import { triggersActionsRoute } from '@kbn/rule-data-utils';
|
||||
import { ServerlessPluginStart } from '@kbn/serverless/public';
|
||||
import type { SpacesPluginStart } from '@kbn/spaces-plugin/public';
|
||||
import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public';
|
||||
import { triggersActionsRoute } from '@kbn/rule-data-utils';
|
||||
import { DashboardStart } from '@kbn/dashboard-plugin/public';
|
||||
import type { LicensingPluginStart } from '@kbn/licensing-plugin/public';
|
||||
import { ExpressionsStart } from '@kbn/expressions-plugin/public';
|
||||
import { ServerlessPluginStart } from '@kbn/serverless/public';
|
||||
import { FieldFormatsRegistry } from '@kbn/field-formats-plugin/common';
|
||||
import { LensPublicStart } from '@kbn/lens-plugin/public';
|
||||
import { RuleAction } from '@kbn/alerting-plugin/common';
|
||||
import { TypeRegistry } from '@kbn/alerts-ui-shared/src/common/type_registry';
|
||||
import { CloudSetup } from '@kbn/cloud-plugin/public';
|
||||
import type { RuleUiAction } from './types';
|
||||
import { ReactElement } from 'react';
|
||||
import { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public';
|
||||
import type { AlertsSearchBarProps } from './application/sections/alerts_search_bar';
|
||||
import type { RuleUiAction } from './types';
|
||||
|
||||
import { getAddConnectorFlyoutLazy } from './common/get_add_connector_flyout';
|
||||
import { getEditConnectorFlyoutLazy } from './common/get_edit_connector_flyout';
|
||||
import { getAddRuleFlyoutLazy } from './common/get_add_rule_flyout';
|
||||
import { getEditRuleFlyoutLazy } from './common/get_edit_rule_flyout';
|
||||
import { getRuleStatusDropdownLazy } from './common/get_rule_status_dropdown';
|
||||
import { getRuleTagFilterLazy } from './common/get_rule_tag_filter';
|
||||
import { getRuleStatusFilterLazy } from './common/get_rule_status_filter';
|
||||
import { getRuleTagBadgeLazy } from './common/get_rule_tag_badge';
|
||||
import { getRuleEventLogListLazy } from './common/get_rule_event_log_list';
|
||||
import { getRulesListNotifyBadgeLazy } from './common/get_rules_list_notify_badge';
|
||||
import { getRulesListLazy } from './common/get_rules_list';
|
||||
import { getActionFormLazy } from './common/get_action_form';
|
||||
import { getRuleStatusPanelLazy } from './common/get_rule_status_panel';
|
||||
import { ExperimentalFeaturesService } from './common/experimental_features_service';
|
||||
import {
|
||||
ExperimentalFeatures,
|
||||
parseExperimentalConfigValue,
|
||||
} from '../common/experimental_features';
|
||||
import { ExperimentalFeaturesService } from './common/experimental_features_service';
|
||||
import { getActionFormLazy } from './common/get_action_form';
|
||||
import { getAddConnectorFlyoutLazy } from './common/get_add_connector_flyout';
|
||||
import { getEditConnectorFlyoutLazy } from './common/get_edit_connector_flyout';
|
||||
import { getRuleEventLogListLazy } from './common/get_rule_event_log_list';
|
||||
import { getRuleStatusDropdownLazy } from './common/get_rule_status_dropdown';
|
||||
import { getRuleStatusFilterLazy } from './common/get_rule_status_filter';
|
||||
import { getRuleStatusPanelLazy } from './common/get_rule_status_panel';
|
||||
import { getRuleTagBadgeLazy } from './common/get_rule_tag_badge';
|
||||
import { getRuleTagFilterLazy } from './common/get_rule_tag_filter';
|
||||
import { getRulesListLazy } from './common/get_rules_list';
|
||||
import { getRulesListNotifyBadgeLazy } from './common/get_rules_list_notify_badge';
|
||||
|
||||
import { TriggersActionsUiConfigType } from '../common/types';
|
||||
import { ActionAccordionFormProps } from './application/sections/action_connector_form/action_form';
|
||||
import { AlertSummaryWidgetProps } from './application/sections/alert_summary_widget';
|
||||
import { AlertSummaryWidgetDependencies } from './application/sections/alert_summary_widget/types';
|
||||
import { RuleStatusPanelProps } from './application/sections/rule_details/components/rule_status_panel';
|
||||
import { RuleSnoozeModalProps } from './application/sections/rules_list/components/rule_snooze_modal';
|
||||
import { ALERTS_PAGE_ID, CONNECTORS_PLUGIN_ID, PLUGIN_ID } from './common/constants';
|
||||
import { getAlertsSearchBarLazy } from './common/get_alerts_search_bar';
|
||||
import { getGlobalRuleEventLogListLazy } from './common/get_global_rule_event_log_list';
|
||||
import { getAlertSummaryWidgetLazy } from './common/get_rule_alerts_summary';
|
||||
import { getRuleDefinitionLazy } from './common/get_rule_definition';
|
||||
import { getRuleSnoozeModalLazy } from './common/get_rule_snooze_modal';
|
||||
import { getRulesSettingsLinkLazy } from './common/get_rules_settings_link';
|
||||
|
||||
import type {
|
||||
ActionTypeModel,
|
||||
RuleAddProps,
|
||||
RuleEditProps,
|
||||
RuleTypeModel,
|
||||
RuleTypeParams,
|
||||
RuleTypeMetaData,
|
||||
RuleStatusDropdownProps,
|
||||
RuleTagFilterProps,
|
||||
RuleStatusFilterProps,
|
||||
RuleTagBadgeProps,
|
||||
RuleTagBadgeOptions,
|
||||
RuleEventLogListProps,
|
||||
RuleEventLogListOptions,
|
||||
GlobalRuleEventLogListProps,
|
||||
RulesListProps,
|
||||
RulesListNotifyBadgePropsWithApi,
|
||||
ConnectorServices,
|
||||
CreateConnectorFlyoutProps,
|
||||
EditConnectorFlyoutProps,
|
||||
ConnectorServices,
|
||||
GlobalRuleEventLogListProps,
|
||||
RuleDefinitionProps,
|
||||
RuleEventLogListOptions,
|
||||
RuleEventLogListProps,
|
||||
RuleStatusDropdownProps,
|
||||
RuleStatusFilterProps,
|
||||
RuleTagBadgeOptions,
|
||||
RuleTagBadgeProps,
|
||||
RuleTagFilterProps,
|
||||
RuleTypeModel,
|
||||
RulesListNotifyBadgePropsWithApi,
|
||||
RulesListProps,
|
||||
} from './types';
|
||||
import { TriggersActionsUiConfigType } from '../common/types';
|
||||
import { PLUGIN_ID, CONNECTORS_PLUGIN_ID, ALERTS_PAGE_ID } from './common/constants';
|
||||
import { getAlertsSearchBarLazy } from './common/get_alerts_search_bar';
|
||||
import { ActionAccordionFormProps } from './application/sections/action_connector_form/action_form';
|
||||
import { getRuleDefinitionLazy } from './common/get_rule_definition';
|
||||
import { RuleStatusPanelProps } from './application/sections/rule_details/components/rule_status_panel';
|
||||
import { AlertSummaryWidgetProps } from './application/sections/alert_summary_widget';
|
||||
import { getAlertSummaryWidgetLazy } from './common/get_rule_alerts_summary';
|
||||
import { RuleSnoozeModalProps } from './application/sections/rules_list/components/rule_snooze_modal';
|
||||
import { getRuleSnoozeModalLazy } from './common/get_rule_snooze_modal';
|
||||
import { getRulesSettingsLinkLazy } from './common/get_rules_settings_link';
|
||||
import { getGlobalRuleEventLogListLazy } from './common/get_global_rule_event_log_list';
|
||||
import { AlertSummaryWidgetDependencies } from './application/sections/alert_summary_widget/types';
|
||||
|
||||
export interface TriggersAndActionsUIPublicPluginSetup {
|
||||
actionTypeRegistry: TypeRegistry<ActionTypeModel>;
|
||||
|
@ -110,18 +106,6 @@ export interface TriggersAndActionsUIPublicPluginStart {
|
|||
getEditConnectorFlyout: (
|
||||
props: Omit<EditConnectorFlyoutProps, 'actionTypeRegistry'>
|
||||
) => ReactElement<EditConnectorFlyoutProps>;
|
||||
getAddRuleFlyout: <
|
||||
Params extends RuleTypeParams = RuleTypeParams,
|
||||
MetaData extends RuleTypeMetaData = RuleTypeMetaData
|
||||
>(
|
||||
props: Omit<RuleAddProps<Params, MetaData>, 'actionTypeRegistry' | 'ruleTypeRegistry'>
|
||||
) => ReactElement<RuleAddProps<Params, MetaData>>;
|
||||
getEditRuleFlyout: <
|
||||
Params extends RuleTypeParams = RuleTypeParams,
|
||||
MetaData extends RuleTypeMetaData = RuleTypeMetaData
|
||||
>(
|
||||
props: Omit<RuleEditProps<Params, MetaData>, 'actionTypeRegistry' | 'ruleTypeRegistry'>
|
||||
) => ReactElement<RuleEditProps<Params, MetaData>>;
|
||||
getAlertsSearchBar: (props: AlertsSearchBarProps) => ReactElement<AlertsSearchBarProps>;
|
||||
getRuleStatusDropdown: (props: RuleStatusDropdownProps) => ReactElement<RuleStatusDropdownProps>;
|
||||
getRuleTagFilter: (props: RuleTagFilterProps) => ReactElement<RuleTagFilterProps>;
|
||||
|
@ -169,6 +153,7 @@ interface PluginsStart {
|
|||
serverless?: ServerlessPluginStart;
|
||||
fieldFormats: FieldFormatsRegistry;
|
||||
lens: LensPublicStart;
|
||||
fieldsMetadata: FieldsMetadataPublicStart;
|
||||
}
|
||||
|
||||
export class Plugin
|
||||
|
@ -306,6 +291,7 @@ export class Plugin
|
|||
isServerless: !!pluginsStart.serverless,
|
||||
fieldFormats: pluginsStart.fieldFormats,
|
||||
lens: pluginsStart.lens,
|
||||
fieldsMetadata: pluginsStart.fieldsMetadata,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
@ -402,6 +388,7 @@ export class Plugin
|
|||
isServerless: !!pluginsStart.serverless,
|
||||
fieldFormats: pluginsStart.fieldFormats,
|
||||
lens: pluginsStart.lens,
|
||||
fieldsMetadata: pluginsStart.fieldsMetadata,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
@ -456,22 +443,6 @@ export class Plugin
|
|||
connectorServices: this.connectorServices!,
|
||||
});
|
||||
},
|
||||
getAddRuleFlyout: (props) => {
|
||||
return getAddRuleFlyoutLazy({
|
||||
...props,
|
||||
actionTypeRegistry: this.actionTypeRegistry,
|
||||
ruleTypeRegistry: this.ruleTypeRegistry,
|
||||
connectorServices: this.connectorServices!,
|
||||
});
|
||||
},
|
||||
getEditRuleFlyout: (props) => {
|
||||
return getEditRuleFlyoutLazy({
|
||||
...props,
|
||||
actionTypeRegistry: this.actionTypeRegistry,
|
||||
ruleTypeRegistry: this.ruleTypeRegistry,
|
||||
connectorServices: this.connectorServices!,
|
||||
});
|
||||
},
|
||||
getAlertsSearchBar: (props: AlertsSearchBarProps) => {
|
||||
return getAlertsSearchBarLazy(props);
|
||||
},
|
||||
|
|
|
@ -77,7 +77,8 @@
|
|||
"@kbn/charts-theme",
|
||||
"@kbn/rrule",
|
||||
"@kbn/core-notifications-browser-mocks",
|
||||
"@kbn/response-ops-rule-params"
|
||||
"@kbn/response-ops-rule-params",
|
||||
"@kbn/fields-metadata-plugin"
|
||||
],
|
||||
"exclude": ["target/**/*"]
|
||||
}
|
||||
|
|
|
@ -60,6 +60,7 @@ describe('Alerts', () => {
|
|||
describe('when rendered from Service view in APM app', () => {
|
||||
const ruleName = 'Error count threshold';
|
||||
const confirmModalButtonSelector = '.euiModal button[data-test-subj=confirmModalConfirmButton]';
|
||||
const saveButtonSelector = 'button[data-test-subj=ruleFlyoutFooterSaveButton]';
|
||||
|
||||
it('alerts table is rendered correctly', () => {
|
||||
cy.loginAsEditorUser();
|
||||
|
@ -73,15 +74,19 @@ describe('Alerts', () => {
|
|||
// has loaded.
|
||||
cy.contains('for the last');
|
||||
cy.contains('Actions');
|
||||
cy.contains('Save').should('not.be.disabled');
|
||||
cy.contains('Next').should('not.be.disabled');
|
||||
|
||||
// Update "Is above" to "0"
|
||||
cy.contains('is above').click();
|
||||
cy.getByTestSubj('apmIsAboveFieldFieldNumber').clear();
|
||||
cy.contains('is above 0 errors');
|
||||
|
||||
// Navigate to Rule Details step
|
||||
cy.getByTestSubj('ruleFormStep-details').click();
|
||||
cy.get(saveButtonSelector).should('not.be.disabled');
|
||||
|
||||
// Save, with no actions
|
||||
cy.contains('Save').click();
|
||||
cy.get(saveButtonSelector).click();
|
||||
cy.get(confirmModalButtonSelector).click();
|
||||
|
||||
cy.contains(`Created rule "${ruleName}`);
|
||||
|
|
|
@ -60,6 +60,7 @@ describe('Rules', () => {
|
|||
const ruleName = 'Error count threshold';
|
||||
const comboBoxInputSelector = '[data-popover-open] [data-test-subj=comboBoxSearchInput]';
|
||||
const confirmModalButtonSelector = '.euiModal button[data-test-subj=confirmModalConfirmButton]';
|
||||
const saveButtonSelector = 'button[data-test-subj=ruleFlyoutFooterSaveButton]';
|
||||
|
||||
describe('when created from APM', () => {
|
||||
describe('when created from Service Inventory', () => {
|
||||
|
@ -75,10 +76,14 @@ describe('Rules', () => {
|
|||
// has loaded.
|
||||
cy.contains('for the last');
|
||||
cy.contains('Actions');
|
||||
cy.contains('Save').should('not.be.disabled');
|
||||
cy.contains('Next').should('not.be.disabled');
|
||||
|
||||
// Navigate to Rule Details step
|
||||
cy.getByTestSubj('ruleFormStep-details').click();
|
||||
cy.get(saveButtonSelector).should('not.be.disabled');
|
||||
|
||||
// Save, with no actions
|
||||
cy.contains('Save').click();
|
||||
cy.get(saveButtonSelector).click();
|
||||
cy.get(confirmModalButtonSelector).click();
|
||||
|
||||
cy.contains(`Created rule "${ruleName}`);
|
||||
|
|
|
@ -39,7 +39,8 @@
|
|||
"uiActions",
|
||||
"logsDataAccess",
|
||||
"savedSearch",
|
||||
"entityManager"
|
||||
"entityManager",
|
||||
"fieldsMetadata"
|
||||
],
|
||||
"optionalPlugins": [
|
||||
"actions",
|
||||
|
|
|
@ -6,15 +6,16 @@
|
|||
*/
|
||||
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import type { CoreStart } from '@kbn/core/public';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { ApmRuleType } from '@kbn/rule-data-utils';
|
||||
import type { RuleTypeParams } from '@kbn/alerting-plugin/common';
|
||||
import { RuleFormFlyout } from '@kbn/response-ops-rule-form/flyout';
|
||||
import { isValidRuleFormPlugins } from '@kbn/response-ops-rule-form/lib';
|
||||
import { APM_SERVER_FEATURE_ID } from '../../../../../common/rules/apm_rule_types';
|
||||
import { getInitialAlertValues } from '../../utils/get_initial_alert_values';
|
||||
import type { ApmPluginStartDeps } from '../../../../plugin';
|
||||
import { useServiceName } from '../../../../hooks/use_service_name';
|
||||
import { useApmParams } from '../../../../hooks/use_apm_params';
|
||||
import type { AlertMetadata } from '../../utils/helper';
|
||||
import { ENVIRONMENT_ALL } from '../../../../../common/environment_filter_values';
|
||||
import { useTimeRange } from '../../../../hooks/use_time_range';
|
||||
|
||||
|
@ -40,7 +41,12 @@ export function AlertingFlyout(props: Props) {
|
|||
const transactionName = 'transactionName' in query ? query.transactionName : undefined;
|
||||
const errorGroupingKey = 'groupId' in path ? path.groupId : undefined;
|
||||
|
||||
const { services } = useKibana<ApmPluginStartDeps>();
|
||||
const {
|
||||
services: {
|
||||
triggersActionsUi: { ruleTypeRegistry, actionTypeRegistry },
|
||||
...services
|
||||
},
|
||||
} = useKibana<CoreStart & ApmPluginStartDeps>();
|
||||
const initialValues = getInitialAlertValues(ruleType, serviceName);
|
||||
|
||||
const onCloseAddFlyout = useCallback(
|
||||
|
@ -51,29 +57,33 @@ export function AlertingFlyout(props: Props) {
|
|||
const addAlertFlyout = useMemo(
|
||||
() =>
|
||||
ruleType &&
|
||||
services.triggersActionsUi.getAddRuleFlyout<RuleTypeParams, AlertMetadata>({
|
||||
consumer: APM_SERVER_FEATURE_ID,
|
||||
onClose: onCloseAddFlyout,
|
||||
ruleTypeId: ruleType,
|
||||
canChangeTrigger: false,
|
||||
initialValues,
|
||||
metadata: {
|
||||
environment,
|
||||
serviceName,
|
||||
...(ruleType === ApmRuleType.ErrorCount ? {} : { transactionType }),
|
||||
transactionName,
|
||||
errorGroupingKey,
|
||||
start,
|
||||
end,
|
||||
},
|
||||
useRuleProducer: true,
|
||||
}),
|
||||
isValidRuleFormPlugins(services) && (
|
||||
<RuleFormFlyout
|
||||
plugins={{ ...services, ruleTypeRegistry, actionTypeRegistry }}
|
||||
consumer={APM_SERVER_FEATURE_ID}
|
||||
onCancel={onCloseAddFlyout}
|
||||
onSubmit={onCloseAddFlyout}
|
||||
ruleTypeId={ruleType}
|
||||
initialValues={initialValues}
|
||||
initialMetadata={{
|
||||
environment,
|
||||
serviceName,
|
||||
...(ruleType === ApmRuleType.ErrorCount ? {} : { transactionType }),
|
||||
transactionName,
|
||||
errorGroupingKey,
|
||||
start,
|
||||
end,
|
||||
}}
|
||||
shouldUseRuleProducer
|
||||
/>
|
||||
),
|
||||
/* eslint-disable-next-line react-hooks/exhaustive-deps */
|
||||
[
|
||||
ruleType,
|
||||
environment,
|
||||
onCloseAddFlyout,
|
||||
services.triggersActionsUi,
|
||||
ruleTypeRegistry,
|
||||
actionTypeRegistry,
|
||||
serviceName,
|
||||
transactionName,
|
||||
errorGroupingKey,
|
||||
|
|
|
@ -72,6 +72,7 @@ import type { ServerlessPluginStart } from '@kbn/serverless/public';
|
|||
import type { LogsSharedClientStartExports } from '@kbn/logs-shared-plugin/public';
|
||||
import type { LogsDataAccessPluginStart } from '@kbn/logs-data-access-plugin/public';
|
||||
import type { SavedSearchPublicPluginStart } from '@kbn/saved-search-plugin/public';
|
||||
import type { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public';
|
||||
import type { ConfigSchema } from '.';
|
||||
import { registerApmRuleTypes } from './components/alerting/rule_types/register_apm_rule_types';
|
||||
import { registerEmbeddables } from './embeddable/register_embeddables';
|
||||
|
@ -148,6 +149,7 @@ export interface ApmPluginStartDeps {
|
|||
logsShared: LogsSharedClientStartExports;
|
||||
logsDataAccess: LogsDataAccessPluginStart;
|
||||
savedSearch: SavedSearchPublicPluginStart;
|
||||
fieldsMetadata: FieldsMetadataPublicStart;
|
||||
}
|
||||
|
||||
const applicationsTitle = i18n.translate('xpack.apm.navigation.rootTitle', {
|
||||
|
|
|
@ -135,7 +135,9 @@
|
|||
"@kbn/entityManager-plugin",
|
||||
"@kbn/core-http-server-utils",
|
||||
"@kbn/key-value-metadata-table",
|
||||
"@kbn/event-stacktrace"
|
||||
"@kbn/event-stacktrace",
|
||||
"@kbn/response-ops-rule-form",
|
||||
"@kbn/fields-metadata-plugin"
|
||||
],
|
||||
"exclude": ["target/**/*"]
|
||||
}
|
||||
|
|
|
@ -38,7 +38,8 @@
|
|||
"visTypeTimeseries",
|
||||
"apmDataAccess",
|
||||
"logsDataAccess",
|
||||
"entityManager"
|
||||
"entityManager",
|
||||
"fieldsMetadata"
|
||||
],
|
||||
"optionalPlugins": [
|
||||
"spaces",
|
||||
|
|
|
@ -5,16 +5,21 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useContext, useMemo } from 'react';
|
||||
import type { RuleAddProps } from '@kbn/triggers-actions-ui-plugin/public/types';
|
||||
import React from 'react';
|
||||
import type { CoreStart } from '@kbn/core/public';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/rule-data-utils';
|
||||
import { RuleFormFlyout } from '@kbn/response-ops-rule-form/flyout';
|
||||
import { useContext, useMemo } from 'react';
|
||||
import type { InfraClientStartDeps } from '../../../types';
|
||||
import { TriggerActionsContext } from '../../../containers/triggers_actions_context';
|
||||
|
||||
interface Props {
|
||||
onClose: RuleAddProps['onClose'];
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export function AlertFlyout({ onClose }: Props) {
|
||||
const { services } = useKibana<CoreStart & InfraClientStartDeps>();
|
||||
const { triggersActionsUI } = useContext(TriggerActionsContext);
|
||||
|
||||
const addAlertFlyout = useMemo(() => {
|
||||
|
@ -22,22 +27,27 @@ export function AlertFlyout({ onClose }: Props) {
|
|||
return null;
|
||||
}
|
||||
|
||||
return triggersActionsUI.getAddRuleFlyout({
|
||||
consumer: 'infrastructure',
|
||||
onClose,
|
||||
canChangeTrigger: false,
|
||||
ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID,
|
||||
metadata: {
|
||||
currentOptions: {
|
||||
/*
|
||||
const { ruleTypeRegistry, actionTypeRegistry } = triggersActionsUI;
|
||||
|
||||
return (
|
||||
<RuleFormFlyout
|
||||
plugins={{ ...services, ruleTypeRegistry, actionTypeRegistry }}
|
||||
consumer={'infrastructure'}
|
||||
onCancel={onClose}
|
||||
onSubmit={onClose}
|
||||
ruleTypeId={OBSERVABILITY_THRESHOLD_RULE_TYPE_ID}
|
||||
initialMetadata={{
|
||||
currentOptions: {
|
||||
/*
|
||||
Setting the groupBy is currently required in custom threshold
|
||||
rule for it to populate the rule with additional host context.
|
||||
*/
|
||||
groupBy: 'host.name',
|
||||
},
|
||||
},
|
||||
});
|
||||
}, [onClose, triggersActionsUI]);
|
||||
groupBy: 'host.name',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}, [onClose, triggersActionsUI, services]);
|
||||
|
||||
return addAlertFlyout;
|
||||
}
|
||||
|
|
|
@ -7,10 +7,14 @@
|
|||
|
||||
import React, { useCallback, useContext, useMemo } from 'react';
|
||||
|
||||
import { RuleFormFlyout } from '@kbn/response-ops-rule-form/flyout';
|
||||
import type { InventoryItemType } from '@kbn/metrics-data-access-plugin/common';
|
||||
import { TriggerActionsContext } from '../../../containers/triggers_actions_context';
|
||||
import type { CoreStart } from '@kbn/core/public';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import type { InfraClientStartDeps } from '../../../types';
|
||||
import { METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID } from '../../../../common/alerting/metrics';
|
||||
import type { InfraWaffleMapOptions } from '../../../common/inventory/types';
|
||||
import { TriggerActionsContext } from '../../../containers/triggers_actions_context';
|
||||
import { useAlertPrefillContext } from '../../use_alert_prefill';
|
||||
|
||||
interface Props {
|
||||
|
@ -22,29 +26,35 @@ interface Props {
|
|||
}
|
||||
|
||||
export const AlertFlyout = ({ options, nodeType, filter, visible, setVisible }: Props) => {
|
||||
const { services } = useKibana<CoreStart & InfraClientStartDeps>();
|
||||
const { triggersActionsUI } = useContext(TriggerActionsContext);
|
||||
const onCloseFlyout = useCallback(() => setVisible(false), [setVisible]);
|
||||
const { inventoryPrefill } = useAlertPrefillContext();
|
||||
const { customMetrics = [], accountId, region } = inventoryPrefill;
|
||||
|
||||
const AddAlertFlyout = useMemo(
|
||||
() =>
|
||||
triggersActionsUI &&
|
||||
triggersActionsUI.getAddRuleFlyout({
|
||||
consumer: 'infrastructure',
|
||||
onClose: onCloseFlyout,
|
||||
canChangeTrigger: false,
|
||||
ruleTypeId: METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID,
|
||||
metadata: {
|
||||
accountId,
|
||||
options,
|
||||
nodeType,
|
||||
filter,
|
||||
customMetrics,
|
||||
region,
|
||||
},
|
||||
useRuleProducer: true,
|
||||
}),
|
||||
() => {
|
||||
if (!triggersActionsUI) return null;
|
||||
const { ruleTypeRegistry, actionTypeRegistry } = triggersActionsUI;
|
||||
return (
|
||||
<RuleFormFlyout
|
||||
plugins={{ ...services, ruleTypeRegistry, actionTypeRegistry }}
|
||||
consumer={'infrastructure'}
|
||||
onCancel={onCloseFlyout}
|
||||
onSubmit={onCloseFlyout}
|
||||
ruleTypeId={METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID}
|
||||
initialMetadata={{
|
||||
accountId,
|
||||
options,
|
||||
nodeType,
|
||||
filter,
|
||||
customMetrics,
|
||||
region,
|
||||
}}
|
||||
shouldUseRuleProducer
|
||||
/>
|
||||
);
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[triggersActionsUI, visible]
|
||||
);
|
||||
|
|
|
@ -5,9 +5,13 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { RuleFormFlyout } from '@kbn/response-ops-rule-form/flyout';
|
||||
import React, { useCallback, useContext, useMemo } from 'react';
|
||||
import { TriggerActionsContext } from '../../../containers/triggers_actions_context';
|
||||
import type { CoreStart } from '@kbn/core/public';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import type { InfraClientStartDeps } from '../../../types';
|
||||
import { LOG_DOCUMENT_COUNT_RULE_TYPE_ID } from '../../../../common/alerting/logs/log_threshold/types';
|
||||
import { TriggerActionsContext } from '../../../containers/triggers_actions_context';
|
||||
|
||||
interface Props {
|
||||
visible?: boolean;
|
||||
|
@ -15,23 +19,26 @@ interface Props {
|
|||
}
|
||||
|
||||
export const AlertFlyout = (props: Props) => {
|
||||
const { services } = useKibana<CoreStart & InfraClientStartDeps>();
|
||||
const { visible, setVisible } = props;
|
||||
const { triggersActionsUI } = useContext(TriggerActionsContext);
|
||||
const onCloseFlyout = useCallback(() => setVisible(false), [setVisible]);
|
||||
const AddAlertFlyout = useMemo(
|
||||
() =>
|
||||
triggersActionsUI &&
|
||||
triggersActionsUI.getAddRuleFlyout({
|
||||
consumer: 'logs',
|
||||
onClose: onCloseFlyout,
|
||||
canChangeTrigger: false,
|
||||
ruleTypeId: LOG_DOCUMENT_COUNT_RULE_TYPE_ID,
|
||||
metadata: {
|
||||
const AddAlertFlyout = useMemo(() => {
|
||||
if (!triggersActionsUI) return null;
|
||||
const { ruleTypeRegistry, actionTypeRegistry } = triggersActionsUI;
|
||||
return (
|
||||
<RuleFormFlyout
|
||||
plugins={{ ...services, ruleTypeRegistry, actionTypeRegistry }}
|
||||
consumer="logs"
|
||||
onCancel={onCloseFlyout}
|
||||
onSubmit={onCloseFlyout}
|
||||
ruleTypeId={LOG_DOCUMENT_COUNT_RULE_TYPE_ID}
|
||||
initialMetadata={{
|
||||
isInternal: true,
|
||||
},
|
||||
}),
|
||||
[triggersActionsUI, onCloseFlyout]
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}, [triggersActionsUI, services, onCloseFlyout]);
|
||||
|
||||
return <>{visible && AddAlertFlyout}</>;
|
||||
};
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue