mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Actionable Observability ] - Rule Details Phase 2 - Migrate Rule Definition component to Triggers Actions UI (#134843)
* Migrate Rule Definition component to Triggers Actions UI [Works] * Clean up * Add Rule edit flyout from definition component * WIP Adding tests for Rule Actions * Fix rule actions tests * Fix test mock for getRuleDefinition function * Fix i18n keys * Fix i18n and should be deleted later * Update Actions to RuleActions * Update test name * Update test name * Add Rule definition tests suite * Clean up the moved code to triggersActionsUI * Update RuleAction type * getRuleType inside in useMemo * User formatDuration from alerting * Fix types and import * Fix test * Fix and remove unused i18n * Fix checks * Code review better i18n plural handling
This commit is contained in:
parent
f0965a39b6
commit
eae262edad
19 changed files with 618 additions and 299 deletions
|
@ -1,90 +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 { mount } from 'enzyme';
|
||||
import { nextTick } from '@kbn/test-jest-helpers';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { Actions } from './actions';
|
||||
import { observabilityPublicPluginsStartMock } from '../../../observability_public_plugins_start.mock';
|
||||
import { kibanaStartMock } from '../../../utils/kibana_react.mock';
|
||||
|
||||
const mockUseKibanaReturnValue = kibanaStartMock.startContract();
|
||||
|
||||
jest.mock('../../../utils/kibana_react', () => ({
|
||||
__esModule: true,
|
||||
useKibana: jest.fn(() => mockUseKibanaReturnValue),
|
||||
}));
|
||||
|
||||
jest.mock('@kbn/triggers-actions-ui-plugin/public/application/lib/action_connector_api', () => ({
|
||||
loadAllActions: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('Actions', () => {
|
||||
async function setup() {
|
||||
const ruleActions = [
|
||||
{
|
||||
id: 1,
|
||||
group: 'metrics.inventory_threshold.fired',
|
||||
actionTypeId: '.server-log',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
group: 'metrics.inventory_threshold.fired',
|
||||
actionTypeId: '.slack',
|
||||
},
|
||||
];
|
||||
const { loadAllActions } = jest.requireMock(
|
||||
'@kbn/triggers-actions-ui-plugin/public/application/lib/action_connector_api'
|
||||
);
|
||||
loadAllActions.mockResolvedValueOnce([
|
||||
{
|
||||
id: 'a0d2f6c0-e682-11ec-843b-213c67313f8c',
|
||||
name: 'Email',
|
||||
config: {},
|
||||
actionTypeId: '.email',
|
||||
},
|
||||
{
|
||||
id: 'f57cabc0-e660-11ec-8241-7deb55b17f15',
|
||||
name: 'logs',
|
||||
config: {},
|
||||
actionTypeId: '.server-log',
|
||||
},
|
||||
{
|
||||
id: '05b7ab30-e683-11ec-843b-213c67313f8c',
|
||||
name: 'Slack',
|
||||
actionTypeId: '.slack',
|
||||
},
|
||||
]);
|
||||
|
||||
const actionTypeRegistryMock =
|
||||
observabilityPublicPluginsStartMock.createStart().triggersActionsUi.actionTypeRegistry;
|
||||
actionTypeRegistryMock.list.mockReturnValue([
|
||||
{ id: '.server-log', iconClass: 'logsApp' },
|
||||
{ id: '.slack', iconClass: 'logoSlack' },
|
||||
{ id: '.email', iconClass: 'email' },
|
||||
{ id: '.index', iconClass: 'indexOpen' },
|
||||
]);
|
||||
const wrapper = mount(
|
||||
<Actions ruleActions={ruleActions} actionTypeRegistry={actionTypeRegistryMock} />
|
||||
);
|
||||
await act(async () => {
|
||||
await nextTick();
|
||||
wrapper.update();
|
||||
});
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
it("renders action connector icons for user's selected rule actions", async () => {
|
||||
const wrapper = await setup();
|
||||
wrapper.debug();
|
||||
expect(wrapper.find('[data-euiicon-type]').length).toBe(2);
|
||||
expect(wrapper.find('[data-euiicon-type="logsApp"]').length).toBe(1);
|
||||
expect(wrapper.find('[data-euiicon-type="logoSlack"]').length).toBe(1);
|
||||
expect(wrapper.find('[data-euiicon-type="index"]').length).toBe(0);
|
||||
expect(wrapper.find('[data-euiicon-type="email"]').length).toBe(0);
|
||||
});
|
||||
});
|
|
@ -8,4 +8,3 @@
|
|||
export { PageTitle } from './page_title';
|
||||
export { ItemTitleRuleSummary } from './item_title_rule_summary';
|
||||
export { ItemValueRuleSummary } from './item_value_rule_summary';
|
||||
export { Actions } from './actions';
|
||||
|
|
|
@ -42,6 +42,7 @@ import {
|
|||
import { ALERTS_FEATURE_ID } from '@kbn/alerting-plugin/common';
|
||||
|
||||
import { AlertConsumers } from '@kbn/rule-data-utils';
|
||||
import { RuleDefinitionProps } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import { DeleteModalConfirmation } from '../rules/components/delete_modal_confirmation';
|
||||
import { CenterJustifiedSpinner } from '../rules/components/center_justified_spinner';
|
||||
import { OBSERVABILITY_SOLUTIONS } from '../rules/config';
|
||||
|
@ -50,11 +51,10 @@ import { useBreadcrumbs } from '../../hooks/use_breadcrumbs';
|
|||
import { usePluginContext } from '../../hooks/use_plugin_context';
|
||||
import { useFetchRule } from '../../hooks/use_fetch_rule';
|
||||
import { RULES_BREADCRUMB_TEXT } from '../rules/translations';
|
||||
import { PageTitle, ItemTitleRuleSummary, ItemValueRuleSummary, Actions } from './components';
|
||||
import { PageTitle, ItemTitleRuleSummary, ItemValueRuleSummary } from './components';
|
||||
import { useKibana } from '../../utils/kibana_react';
|
||||
import { useFetchLast24hAlerts } from '../../hooks/use_fetch_last24h_alerts';
|
||||
import { useFetchLast24hRuleExecutionLog } from '../../hooks/use_fetch_last24h_rule_execution_log';
|
||||
import { formatInterval } from './utils';
|
||||
import { hasExecuteActionsCapability, hasAllPrivilege } from './config';
|
||||
import { paths } from '../../config/paths';
|
||||
import { observabilityFeatureId } from '../../../common';
|
||||
|
@ -67,9 +67,9 @@ export function RuleDetailsPage() {
|
|||
ruleTypeRegistry,
|
||||
getRuleStatusDropdown,
|
||||
getEditAlertFlyout,
|
||||
actionTypeRegistry,
|
||||
getRuleEventLogList,
|
||||
getAlertsStateTable,
|
||||
getRuleDefinition,
|
||||
},
|
||||
application: { capabilities, navigateToUrl },
|
||||
notifications: { toasts },
|
||||
|
@ -79,7 +79,7 @@ export function RuleDetailsPage() {
|
|||
const { ObservabilityPageTemplate } = usePluginContext();
|
||||
const { isRuleLoading, rule, errorRule, reloadRule } = useFetchRule({ ruleId, http });
|
||||
const { isLoadingExecutionLog, executionLog } = useFetchLast24hRuleExecutionLog({ http, ruleId });
|
||||
const { ruleTypes, ruleTypeIndex } = useLoadRuleTypes({
|
||||
const { ruleTypes } = useLoadRuleTypes({
|
||||
filteredSolutions: OBSERVABILITY_SOLUTIONS,
|
||||
});
|
||||
|
||||
|
@ -160,19 +160,6 @@ export function RuleDetailsPage() {
|
|||
? !ruleTypeRegistry.get(rule.ruleTypeId).requiresAppContext
|
||||
: false);
|
||||
|
||||
const getRuleConditionsWording = () => {
|
||||
const numberOfConditions = rule?.params.criteria ? (rule?.params.criteria as any[]).length : 0;
|
||||
return (
|
||||
<>
|
||||
{numberOfConditions}
|
||||
{i18n.translate('xpack.observability.ruleDetails.conditions', {
|
||||
defaultMessage: 'condition{s}',
|
||||
values: { s: numberOfConditions > 1 ? 's' : '' },
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const alertStateProps = {
|
||||
alertsTableConfigurationRegistry,
|
||||
configurationId: observabilityFeatureId,
|
||||
|
@ -257,9 +244,6 @@ export function RuleDetailsPage() {
|
|||
unsnoozeRule: async (scheduleIds) => await unsnoozeRule({ http, id: rule.id, scheduleIds }),
|
||||
});
|
||||
|
||||
const getNotifyText = () =>
|
||||
NOTIFY_WHEN_OPTIONS.current.find((option) => option.value === rule?.notifyWhen)?.inputDisplay ||
|
||||
rule.notifyWhen;
|
||||
return (
|
||||
<ObservabilityPageTemplate
|
||||
data-test-subj="ruleDetails"
|
||||
|
@ -412,113 +396,7 @@ export function RuleDetailsPage() {
|
|||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
|
||||
{/* Right side of Rule Summary */}
|
||||
|
||||
<EuiFlexItem data-test-subj="ruleSummaryRuleDefinition" grow={3}>
|
||||
<EuiPanel color="subdued" hasBorder={false} paddingSize={'m'}>
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiTitle size="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
{i18n.translate('xpack.observability.ruleDetails.definition', {
|
||||
defaultMessage: 'Definition',
|
||||
})}
|
||||
</EuiFlexItem>
|
||||
</EuiTitle>
|
||||
{hasEditButton && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty iconType={'pencil'} onClick={() => setEditFlyoutVisible(true)} />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
<EuiFlexGroup alignItems="baseline">
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup>
|
||||
<ItemTitleRuleSummary>
|
||||
{i18n.translate('xpack.observability.ruleDetails.ruleType', {
|
||||
defaultMessage: 'Rule type',
|
||||
})}
|
||||
</ItemTitleRuleSummary>
|
||||
<ItemValueRuleSummary
|
||||
data-test-subj="ruleSummaryRuleType"
|
||||
itemValue={ruleTypeIndex.get(rule.ruleTypeId)?.name || rule.ruleTypeId}
|
||||
/>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
<EuiFlexGroup alignItems="flexStart" responsive={false}>
|
||||
<ItemTitleRuleSummary>
|
||||
{i18n.translate('xpack.observability.ruleDetails.description', {
|
||||
defaultMessage: 'Description',
|
||||
})}
|
||||
</ItemTitleRuleSummary>
|
||||
<ItemValueRuleSummary
|
||||
itemValue={ruleTypeRegistry.get(rule.ruleTypeId).description}
|
||||
/>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
<EuiFlexGroup>
|
||||
<ItemTitleRuleSummary>
|
||||
{i18n.translate('xpack.observability.ruleDetails.conditionsTitle', {
|
||||
defaultMessage: 'Conditions',
|
||||
})}
|
||||
</ItemTitleRuleSummary>
|
||||
<EuiFlexItem grow={3}>
|
||||
<EuiFlexGroup alignItems="center">
|
||||
{hasEditButton ? (
|
||||
<EuiButtonEmpty onClick={() => setEditFlyoutVisible(true)}>
|
||||
<EuiText size="s">{getRuleConditionsWording()}</EuiText>
|
||||
</EuiButtonEmpty>
|
||||
) : (
|
||||
<EuiText size="s">{getRuleConditionsWording()}</EuiText>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup>
|
||||
<ItemTitleRuleSummary>
|
||||
{i18n.translate('xpack.observability.ruleDetails.runsEvery', {
|
||||
defaultMessage: 'Runs every',
|
||||
})}
|
||||
</ItemTitleRuleSummary>
|
||||
|
||||
<ItemValueRuleSummary itemValue={formatInterval(rule.schedule.interval)} />
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
<EuiFlexGroup>
|
||||
<ItemTitleRuleSummary>
|
||||
{i18n.translate('xpack.observability.ruleDetails.notifyWhen', {
|
||||
defaultMessage: 'Notify',
|
||||
})}
|
||||
</ItemTitleRuleSummary>
|
||||
<ItemValueRuleSummary itemValue={String(getNotifyText())} />
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFlexGroup alignItems="baseline">
|
||||
<ItemTitleRuleSummary>
|
||||
{i18n.translate('xpack.observability.ruleDetails.actions', {
|
||||
defaultMessage: 'Actions',
|
||||
})}
|
||||
</ItemTitleRuleSummary>
|
||||
<EuiFlexItem grow={3}>
|
||||
<Actions ruleActions={rule.actions} actionTypeRegistry={actionTypeRegistry} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
{getRuleDefinition({ rule, onEditRule: () => reloadRule() } as RuleDefinitionProps)}
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiSpacer size="l" />
|
||||
|
|
|
@ -12,12 +12,6 @@ export const RULE_LOAD_ERROR = (errorMessage: string) =>
|
|||
values: { message: errorMessage },
|
||||
});
|
||||
|
||||
export const ACTIONS_LOAD_ERROR = (errorMessage: string) =>
|
||||
i18n.translate('xpack.observability.ruleDetails.connectorsLoadError', {
|
||||
defaultMessage: 'Unable to load rule actions connectors. Reason: {message}',
|
||||
values: { message: errorMessage },
|
||||
});
|
||||
|
||||
export const EXECUTION_LOG_ERROR = (errorMessage: string) =>
|
||||
i18n.translate('xpack.observability.ruleDetails.executionLogError', {
|
||||
defaultMessage: 'Unable to load rule execution log. Reason: {message}',
|
||||
|
|
|
@ -1,15 +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 { formatDurationFromTimeUnitChar, TimeUnitChar } from '../../../common';
|
||||
|
||||
export const formatInterval = (ruleInterval: string) => {
|
||||
const interval: string[] | null = ruleInterval.match(/(^\d*)([s|m|h|d])/);
|
||||
if (!interval || interval.length < 3) return ruleInterval;
|
||||
const value: number = +interval[1];
|
||||
const unit = interval[2] as TimeUnitChar;
|
||||
return formatDurationFromTimeUnitChar(value, unit);
|
||||
};
|
|
@ -22409,16 +22409,10 @@
|
|||
"xpack.observability.resources.quick_start": "Vidéos de démarrage rapide",
|
||||
"xpack.observability.resources.title": "Ressources",
|
||||
"xpack.observability.resources.training": "Cours gratuit Observability",
|
||||
"xpack.observability.ruleDetails.actions": "Actions",
|
||||
"xpack.observability.ruleDetails.alerts": "Alertes",
|
||||
"xpack.observability.ruleDetails.byWord": "par",
|
||||
"xpack.observability.ruleDetails.conditions": "condition{s}",
|
||||
"xpack.observability.ruleDetails.conditionsTitle": "Conditions",
|
||||
"xpack.observability.ruleDetails.connectorsLoadError": "Impossible de charger les connecteurs d'actions de règles. Raison : {message}",
|
||||
"xpack.observability.ruleDetails.createdWord": "Créé",
|
||||
"xpack.observability.ruleDetails.definition": "Définition",
|
||||
"xpack.observability.ruleDetails.deleteRule": "Supprimer la règle",
|
||||
"xpack.observability.ruleDetails.description": "Description",
|
||||
"xpack.observability.ruleDetails.editRule": "Modifier la règle",
|
||||
"xpack.observability.ruleDetails.errorPromptBody": "Une erreur s'est produite lors du chargement des détails de la règle.",
|
||||
"xpack.observability.ruleDetails.errorPromptTitle": "Impossible de charger les détails de la règle",
|
||||
|
@ -22427,15 +22421,11 @@
|
|||
"xpack.observability.ruleDetails.last24h": "(dernières 24 h)",
|
||||
"xpack.observability.ruleDetails.lastRun": "Dernière exécution",
|
||||
"xpack.observability.ruleDetails.lastUpdatedMessage": "Dernière mise à jour",
|
||||
"xpack.observability.ruleDetails.noActions": "Aucune action",
|
||||
"xpack.observability.ruleDetails.notifyWhen": "Notifier",
|
||||
"xpack.observability.ruleDetails.onWord": "le",
|
||||
"xpack.observability.ruleDetails.rule.alertsTabText": "Alertes",
|
||||
"xpack.observability.ruleDetails.rule.eventLogTabText": "Historique d'exécution",
|
||||
"xpack.observability.ruleDetails.ruleIs": "La règle est",
|
||||
"xpack.observability.ruleDetails.ruleLoadError": "Impossible de charger la règle. Raison : {message}",
|
||||
"xpack.observability.ruleDetails.ruleType": "Type de règle",
|
||||
"xpack.observability.ruleDetails.runsEvery": "S'exécute toutes les",
|
||||
"xpack.observability.ruleDetails.tagsTitle": "Balises",
|
||||
"xpack.observability.ruleDetails.triggreAction.status": "Statut",
|
||||
"xpack.observability.rules.addRuleButtonLabel": "Créer une règle",
|
||||
|
|
|
@ -22395,16 +22395,10 @@
|
|||
"xpack.observability.resources.quick_start": "クイックスタートビデオ",
|
||||
"xpack.observability.resources.title": "リソース",
|
||||
"xpack.observability.resources.training": "無料のObservabilityコース",
|
||||
"xpack.observability.ruleDetails.actions": "アクション",
|
||||
"xpack.observability.ruleDetails.alerts": "アラート",
|
||||
"xpack.observability.ruleDetails.byWord": "グループ基準",
|
||||
"xpack.observability.ruleDetails.conditions": "条件{s}",
|
||||
"xpack.observability.ruleDetails.conditionsTitle": "条件",
|
||||
"xpack.observability.ruleDetails.connectorsLoadError": "ルールアクションコネクターを読み込めません。理由:{message}",
|
||||
"xpack.observability.ruleDetails.createdWord": "作成済み",
|
||||
"xpack.observability.ruleDetails.definition": "定義",
|
||||
"xpack.observability.ruleDetails.deleteRule": "ルールの削除",
|
||||
"xpack.observability.ruleDetails.description": "説明",
|
||||
"xpack.observability.ruleDetails.editRule": "ルールを編集",
|
||||
"xpack.observability.ruleDetails.errorPromptBody": "ルール詳細の読み込みエラーが発生しました。",
|
||||
"xpack.observability.ruleDetails.errorPromptTitle": "ルール詳細を読み込めません",
|
||||
|
@ -22413,15 +22407,11 @@
|
|||
"xpack.observability.ruleDetails.last24h": "(過去24時間)",
|
||||
"xpack.observability.ruleDetails.lastRun": "前回の実行",
|
||||
"xpack.observability.ruleDetails.lastUpdatedMessage": "最終更新",
|
||||
"xpack.observability.ruleDetails.noActions": "アクションなし",
|
||||
"xpack.observability.ruleDetails.notifyWhen": "通知",
|
||||
"xpack.observability.ruleDetails.onWord": "日付",
|
||||
"xpack.observability.ruleDetails.rule.alertsTabText": "アラート",
|
||||
"xpack.observability.ruleDetails.rule.eventLogTabText": "実行履歴",
|
||||
"xpack.observability.ruleDetails.ruleIs": "ルールは",
|
||||
"xpack.observability.ruleDetails.ruleLoadError": "ルールを読み込めません。理由:{message}",
|
||||
"xpack.observability.ruleDetails.ruleType": "ルールタイプ",
|
||||
"xpack.observability.ruleDetails.runsEvery": "次の間隔で実行",
|
||||
"xpack.observability.ruleDetails.tagsTitle": "タグ",
|
||||
"xpack.observability.ruleDetails.triggreAction.status": "ステータス",
|
||||
"xpack.observability.rules.addRuleButtonLabel": "ルールを作成",
|
||||
|
|
|
@ -22420,15 +22420,10 @@
|
|||
"xpack.observability.resources.quick_start": "快速入门视频",
|
||||
"xpack.observability.resources.title": "资源",
|
||||
"xpack.observability.resources.training": "免费的可观测性课程",
|
||||
"xpack.observability.ruleDetails.actions": "操作",
|
||||
"xpack.observability.ruleDetails.alerts": "告警",
|
||||
"xpack.observability.ruleDetails.byWord": "依据",
|
||||
"xpack.observability.ruleDetails.conditionsTitle": "条件",
|
||||
"xpack.observability.ruleDetails.connectorsLoadError": "无法加载规则操作连接器。原因:{message}",
|
||||
"xpack.observability.ruleDetails.createdWord": "创建时间",
|
||||
"xpack.observability.ruleDetails.definition": "定义",
|
||||
"xpack.observability.ruleDetails.deleteRule": "删除规则",
|
||||
"xpack.observability.ruleDetails.description": "描述",
|
||||
"xpack.observability.ruleDetails.editRule": "编辑规则",
|
||||
"xpack.observability.ruleDetails.errorPromptBody": "加载规则详情时出现错误。",
|
||||
"xpack.observability.ruleDetails.errorPromptTitle": "无法加载规则详情",
|
||||
|
@ -22437,15 +22432,11 @@
|
|||
"xpack.observability.ruleDetails.last24h": "(过去 24 小时)",
|
||||
"xpack.observability.ruleDetails.lastRun": "上次运行",
|
||||
"xpack.observability.ruleDetails.lastUpdatedMessage": "上次更新时间",
|
||||
"xpack.observability.ruleDetails.noActions": "无操作",
|
||||
"xpack.observability.ruleDetails.notifyWhen": "通知",
|
||||
"xpack.observability.ruleDetails.onWord": "在",
|
||||
"xpack.observability.ruleDetails.rule.alertsTabText": "告警",
|
||||
"xpack.observability.ruleDetails.rule.eventLogTabText": "执行历史记录",
|
||||
"xpack.observability.ruleDetails.ruleIs": "规则为",
|
||||
"xpack.observability.ruleDetails.ruleLoadError": "无法加载规则。原因:{message}",
|
||||
"xpack.observability.ruleDetails.ruleType": "规则类型",
|
||||
"xpack.observability.ruleDetails.runsEvery": "运行间隔",
|
||||
"xpack.observability.ruleDetails.tagsTitle": "标签",
|
||||
"xpack.observability.ruleDetails.triggreAction.status": "状态",
|
||||
"xpack.observability.rules.addRuleButtonLabel": "创建规则",
|
||||
|
|
|
@ -6,21 +6,31 @@
|
|||
*/
|
||||
|
||||
import { useEffect, useState, useCallback } from 'react';
|
||||
import { ActionConnector, loadAllActions } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import { intersectionBy } from 'lodash';
|
||||
import { FetchRuleActionConnectorsProps } from '../pages/rule_details/types';
|
||||
import { ACTIONS_LOAD_ERROR } from '../pages/rule_details/translations';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ActionConnector, loadAllActions } from '../..';
|
||||
import { useKibana } from '../../common/lib/kibana';
|
||||
|
||||
const ACTIONS_LOAD_ERROR = (errorMessage: string) =>
|
||||
i18n.translate('xpack.triggersActionsUI.ruleDetails.connectorsLoadError', {
|
||||
defaultMessage: 'Unable to load rule actions connectors. Reason: {message}',
|
||||
values: { message: errorMessage },
|
||||
});
|
||||
interface FetchActionConnectors {
|
||||
isLoadingActionConnectors: boolean;
|
||||
actionConnectors: Array<ActionConnector<Record<string, unknown>>>;
|
||||
errorActionConnectors?: string;
|
||||
}
|
||||
interface FetchRuleActionConnectorsProps {
|
||||
ruleActions: any[];
|
||||
}
|
||||
|
||||
export function useFetchRuleActionConnectors({ ruleActions }: FetchRuleActionConnectorsProps) {
|
||||
const {
|
||||
http,
|
||||
notifications: { toasts },
|
||||
} = useKibana().services;
|
||||
|
||||
export function useFetchRuleActionConnectors({
|
||||
http,
|
||||
ruleActions,
|
||||
}: FetchRuleActionConnectorsProps) {
|
||||
const [actionConnectors, setActionConnector] = useState<FetchActionConnectors>({
|
||||
isLoadingActionConnectors: true,
|
||||
actionConnectors: [] as Array<ActionConnector<Record<string, unknown>>>,
|
||||
|
@ -47,15 +57,17 @@ export function useFetchRuleActionConnectors({
|
|||
actionConnectors: actions,
|
||||
}));
|
||||
} catch (error) {
|
||||
const errorMsg = ACTIONS_LOAD_ERROR(
|
||||
error instanceof Error ? error.message : typeof error === 'string' ? error : ''
|
||||
);
|
||||
setActionConnector((oldState: FetchActionConnectors) => ({
|
||||
...oldState,
|
||||
isLoadingActionConnectors: false,
|
||||
errorActionConnectors: ACTIONS_LOAD_ERROR(
|
||||
error instanceof Error ? error.message : typeof error === 'string' ? error : ''
|
||||
),
|
||||
errorActionConnectors: errorMsg,
|
||||
}));
|
||||
toasts.addDanger({ title: errorMsg });
|
||||
}
|
||||
}, [http, ruleActions]);
|
||||
}, [http, ruleActions, toasts]);
|
||||
useEffect(() => {
|
||||
fetchRuleActionConnectors();
|
||||
}, [fetchRuleActionConnectors]);
|
|
@ -49,6 +49,9 @@ export const RulesList = suspendedComponentWithProps(
|
|||
export const RulesListNotifyBadge = suspendedComponentWithProps(
|
||||
lazy(() => import('./rules_list/components/rules_list_notify_badge'))
|
||||
);
|
||||
export const RuleDefinition = suspendedComponentWithProps(
|
||||
lazy(() => import('./rule_details/components/rule_definition'))
|
||||
);
|
||||
export const RuleTagBadge = suspendedComponentWithProps(
|
||||
lazy(() => import('./rules_list/components/rule_tag_badge'))
|
||||
);
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* 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 { mount } from 'enzyme';
|
||||
import { nextTick } from '@kbn/test-jest-helpers';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { RuleActions } from './rule_actions';
|
||||
import { actionTypeRegistryMock } from '../../../action_type_registry.mock';
|
||||
import { ActionConnector, ActionTypeModel, RuleAction } from '../../../../types';
|
||||
import * as useFetchRuleActionConnectorsHook from '../../../hooks/use_fetch_rule_action_connectors';
|
||||
|
||||
const actionTypeRegistry = actionTypeRegistryMock.create();
|
||||
const mockedUseFetchRuleActionConnectorsHook = jest.spyOn(
|
||||
useFetchRuleActionConnectorsHook,
|
||||
'useFetchRuleActionConnectors'
|
||||
);
|
||||
describe('Rule Actions', () => {
|
||||
async function setup() {
|
||||
const ruleActions = [
|
||||
{
|
||||
id: '1',
|
||||
group: 'metrics.inventory_threshold.fired',
|
||||
actionTypeId: '.server-log',
|
||||
params: {},
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
group: 'metrics.inventory_threshold.fired',
|
||||
actionTypeId: '.slack',
|
||||
params: {},
|
||||
},
|
||||
] as RuleAction[];
|
||||
|
||||
mockedUseFetchRuleActionConnectorsHook.mockReturnValue({
|
||||
isLoadingActionConnectors: false,
|
||||
actionConnectors: [
|
||||
{
|
||||
id: 'f57cabc0-e660-11ec-8241-7deb55b17f15',
|
||||
name: 'logs',
|
||||
config: {},
|
||||
actionTypeId: '.server-log',
|
||||
},
|
||||
{
|
||||
id: '05b7ab30-e683-11ec-843b-213c67313f8c',
|
||||
name: 'Slack',
|
||||
actionTypeId: '.slack',
|
||||
},
|
||||
] as Array<ActionConnector<Record<string, unknown>>>,
|
||||
errorActionConnectors: undefined,
|
||||
reloadRuleActionConnectors: jest.fn(),
|
||||
});
|
||||
|
||||
actionTypeRegistry.list.mockReturnValue([
|
||||
{ id: '.server-log', iconClass: 'logsApp' },
|
||||
{ id: '.slack', iconClass: 'logoSlack' },
|
||||
{ id: '.email', iconClass: 'email' },
|
||||
{ id: '.index', iconClass: 'indexOpen' },
|
||||
] as ActionTypeModel[]);
|
||||
|
||||
const wrapper = mount(
|
||||
<RuleActions ruleActions={ruleActions} actionTypeRegistry={actionTypeRegistry} />
|
||||
);
|
||||
await act(async () => {
|
||||
await nextTick();
|
||||
wrapper.update();
|
||||
});
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
it("renders rule action connector icons for user's selected rule actions", async () => {
|
||||
const wrapper = await setup();
|
||||
expect(mockedUseFetchRuleActionConnectorsHook).toHaveBeenCalledTimes(1);
|
||||
expect(wrapper.find('[data-euiicon-type]').length).toBe(2);
|
||||
expect(wrapper.find('[data-euiicon-type="logsApp"]').length).toBe(1);
|
||||
expect(wrapper.find('[data-euiicon-type="logoSlack"]').length).toBe(1);
|
||||
expect(wrapper.find('[data-euiicon-type="index"]').length).toBe(0);
|
||||
expect(wrapper.find('[data-euiicon-type="email"]').length).toBe(0);
|
||||
});
|
||||
});
|
|
@ -4,7 +4,7 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import React, { useEffect } from 'react';
|
||||
import React from 'react';
|
||||
import {
|
||||
EuiText,
|
||||
EuiSpacer,
|
||||
|
@ -14,33 +14,24 @@ import {
|
|||
IconType,
|
||||
EuiLoadingSpinner,
|
||||
} from '@elastic/eui';
|
||||
import { suspendedComponentWithProps } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ActionsProps } from '../types';
|
||||
import { ActionTypeRegistryContract, RuleAction, suspendedComponentWithProps } from '../../../..';
|
||||
import { useFetchRuleActionConnectors } from '../../../hooks/use_fetch_rule_action_connectors';
|
||||
import { useKibana } from '../../../utils/kibana_react';
|
||||
|
||||
export function Actions({ ruleActions, actionTypeRegistry }: ActionsProps) {
|
||||
const {
|
||||
http,
|
||||
notifications: { toasts },
|
||||
} = useKibana().services;
|
||||
const { isLoadingActionConnectors, actionConnectors, errorActionConnectors } =
|
||||
useFetchRuleActionConnectors({
|
||||
http,
|
||||
ruleActions,
|
||||
});
|
||||
useEffect(() => {
|
||||
if (errorActionConnectors) {
|
||||
toasts.addDanger({ title: errorActionConnectors });
|
||||
}
|
||||
}, [errorActionConnectors, toasts]);
|
||||
export interface RuleActionsProps {
|
||||
ruleActions: RuleAction[];
|
||||
actionTypeRegistry: ActionTypeRegistryContract;
|
||||
}
|
||||
export function RuleActions({ ruleActions, actionTypeRegistry }: RuleActionsProps) {
|
||||
const { isLoadingActionConnectors, actionConnectors } = useFetchRuleActionConnectors({
|
||||
ruleActions,
|
||||
});
|
||||
|
||||
if (!actionConnectors || actionConnectors.length <= 0)
|
||||
return (
|
||||
<EuiFlexItem>
|
||||
<EuiText size="s">
|
||||
{i18n.translate('xpack.observability.ruleDetails.noActions', {
|
||||
{i18n.translate('xpack.triggersActionsUI.ruleDetails.noActions', {
|
||||
defaultMessage: 'No actions',
|
||||
})}
|
||||
</EuiText>
|
|
@ -0,0 +1,222 @@
|
|||
/*
|
||||
* 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 { mount, ReactWrapper } from 'enzyme';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { ALERTS_FEATURE_ID } from '@kbn/alerting-plugin/common';
|
||||
import { nextTick } from '@kbn/test-jest-helpers';
|
||||
import { RuleDefinition } from './rule_definition';
|
||||
import { actionTypeRegistryMock } from '../../../action_type_registry.mock';
|
||||
import { ActionTypeModel, Rule, RuleTypeModel } from '../../../../types';
|
||||
import { ruleTypeRegistryMock } from '../../../rule_type_registry.mock';
|
||||
|
||||
jest.mock('./rule_actions', () => ({
|
||||
RuleActions: () => {
|
||||
return <></>;
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('../../../lib/capabilities', () => ({
|
||||
hasAllPrivilege: jest.fn(() => true),
|
||||
hasSaveRulesCapability: jest.fn(() => true),
|
||||
hasExecuteActionsCapability: jest.fn(() => true),
|
||||
hasManageApiKeysCapability: jest.fn(() => true),
|
||||
}));
|
||||
jest.mock('../../../../common/lib/kibana');
|
||||
jest.mock('../../../..', () => ({
|
||||
useLoadRuleTypes: jest.fn(),
|
||||
}));
|
||||
const { useLoadRuleTypes } = jest.requireMock('../../../..');
|
||||
const ruleTypes = [
|
||||
{
|
||||
id: 'test_rule_type',
|
||||
name: 'some rule type',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
recoveryActionGroup: { id: 'recovered', name: 'Recovered' },
|
||||
actionVariables: { context: [], state: [] },
|
||||
defaultActionGroupId: 'default',
|
||||
producer: ALERTS_FEATURE_ID,
|
||||
minimumLicenseRequired: 'basic',
|
||||
enabledInLicense: true,
|
||||
authorizedConsumers: {
|
||||
[ALERTS_FEATURE_ID]: { read: true, all: false },
|
||||
},
|
||||
ruleTaskTimeout: '1m',
|
||||
},
|
||||
];
|
||||
|
||||
const mockedRuleTypeIndex = new Map(
|
||||
Object.entries({
|
||||
test_rule_type: {
|
||||
enabledInLicense: true,
|
||||
id: 'test_rule_type',
|
||||
name: 'test rule',
|
||||
},
|
||||
'2': {
|
||||
enabledInLicense: true,
|
||||
id: '2',
|
||||
name: 'test rule ok',
|
||||
},
|
||||
'3': {
|
||||
enabledInLicense: true,
|
||||
id: '3',
|
||||
name: 'test rule pending',
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
describe('Rule Definition', () => {
|
||||
let wrapper: ReactWrapper;
|
||||
async function setup() {
|
||||
const actionTypeRegistry = actionTypeRegistryMock.create();
|
||||
const ruleTypeRegistry = ruleTypeRegistryMock.create();
|
||||
const mockedRule = mockRule();
|
||||
jest.mock('../../../lib/capabilities', () => ({
|
||||
hasAllPrivilege: jest.fn(() => true),
|
||||
hasSaveRulesCapability: jest.fn(() => true),
|
||||
hasExecuteActionsCapability: jest.fn(() => true),
|
||||
hasManageApiKeysCapability: jest.fn(() => true),
|
||||
}));
|
||||
ruleTypeRegistry.has.mockReturnValue(true);
|
||||
const ruleTypeR: RuleTypeModel = {
|
||||
id: 'my-rule-type',
|
||||
iconClass: 'test',
|
||||
description: 'Rule when testing',
|
||||
documentationUrl: 'https://localhost.local/docs',
|
||||
validate: () => {
|
||||
return { errors: {} };
|
||||
},
|
||||
ruleParamsExpression: jest.fn(),
|
||||
requiresAppContext: false,
|
||||
};
|
||||
ruleTypeRegistry.get.mockReturnValue(ruleTypeR);
|
||||
actionTypeRegistry.list.mockReturnValue([
|
||||
{ id: '.server-log', iconClass: 'logsApp' },
|
||||
{ id: '.slack', iconClass: 'logoSlack' },
|
||||
{ id: '.email', iconClass: 'email' },
|
||||
{ id: '.index', iconClass: 'indexOpen' },
|
||||
] as ActionTypeModel[]);
|
||||
|
||||
useLoadRuleTypes.mockReturnValue({ ruleTypes, ruleTypeIndex: mockedRuleTypeIndex });
|
||||
|
||||
wrapper = mount(
|
||||
<RuleDefinition
|
||||
rule={mockedRule}
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
onEditRule={jest.fn()}
|
||||
ruleTypeRegistry={ruleTypeRegistry}
|
||||
/>
|
||||
);
|
||||
await act(async () => {
|
||||
await nextTick();
|
||||
wrapper.update();
|
||||
});
|
||||
}
|
||||
|
||||
beforeAll(async () => await setup());
|
||||
|
||||
it('renders rule definition ', async () => {
|
||||
expect(wrapper.find('[data-test-subj="ruleSummaryRuleDefinition"]')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('show rule type name from "useLoadRuleTypes"', async () => {
|
||||
expect(useLoadRuleTypes).toHaveBeenCalledTimes(2);
|
||||
const ruleType = wrapper.find('[data-test-subj="ruleSummaryRuleType"]');
|
||||
expect(ruleType).toBeTruthy();
|
||||
expect(ruleType.find('div.euiText').text()).toEqual(
|
||||
mockedRuleTypeIndex.get(mockRule().ruleTypeId)?.name
|
||||
);
|
||||
});
|
||||
|
||||
it('show rule type description "', async () => {
|
||||
const ruleDescription = wrapper.find('[data-test-subj="ruleSummaryRuleDescription"]');
|
||||
expect(ruleDescription).toBeTruthy();
|
||||
expect(ruleDescription.find('div.euiText').text()).toEqual('Rule when testing');
|
||||
});
|
||||
|
||||
it('show rule conditions "', async () => {
|
||||
const ruleConditions = wrapper.find('[data-test-subj="ruleSummaryRuleConditions"]');
|
||||
expect(ruleConditions).toBeTruthy();
|
||||
expect(ruleConditions.find('div.euiText').text()).toEqual(`0 conditions`);
|
||||
});
|
||||
|
||||
it('show rule interval with human readable value', async () => {
|
||||
const ruleInterval = wrapper.find('[data-test-subj="ruleSummaryRuleInterval"]');
|
||||
expect(ruleInterval).toBeTruthy();
|
||||
expect(ruleInterval.find('div.euiText').text()).toEqual('1 sec');
|
||||
});
|
||||
|
||||
it('show edit button when user has permissions', async () => {
|
||||
const editButton = wrapper.find('[data-test-subj="ruleDetailsEditButton"]');
|
||||
expect(editButton).toBeTruthy();
|
||||
});
|
||||
|
||||
it('hide edit button when user DOES NOT have permissions', async () => {
|
||||
jest.mock('../../../lib/capabilities', () => ({
|
||||
hasAllPrivilege: jest.fn(() => false),
|
||||
hasSaveRulesCapability: jest.fn(() => true),
|
||||
hasExecuteActionsCapability: jest.fn(() => true),
|
||||
hasManageApiKeysCapability: jest.fn(() => true),
|
||||
}));
|
||||
const editButton = wrapper.find('[data-test-subj="ruleDetailsEditButton"]');
|
||||
expect(editButton).toMatchObject({});
|
||||
});
|
||||
});
|
||||
function mockRule(): Rule {
|
||||
return {
|
||||
id: '1',
|
||||
name: 'test rule',
|
||||
tags: ['tag1'],
|
||||
enabled: true,
|
||||
ruleTypeId: 'test_rule_type',
|
||||
schedule: { interval: '1s' },
|
||||
actions: [],
|
||||
params: { name: 'test rule type name' },
|
||||
createdBy: null,
|
||||
updatedBy: null,
|
||||
apiKeyOwner: null,
|
||||
throttle: '1m',
|
||||
muteAll: false,
|
||||
mutedInstanceIds: [],
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
consumer: 'alerts',
|
||||
notifyWhen: 'onActiveAlert',
|
||||
executionStatus: {
|
||||
status: 'active',
|
||||
lastDuration: 500,
|
||||
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
|
||||
},
|
||||
monitoring: {
|
||||
execution: {
|
||||
history: [
|
||||
{
|
||||
success: true,
|
||||
duration: 1000000,
|
||||
timestamp: 1234567,
|
||||
},
|
||||
{
|
||||
success: true,
|
||||
duration: 200000,
|
||||
timestamp: 1234567,
|
||||
},
|
||||
{
|
||||
success: false,
|
||||
duration: 300000,
|
||||
timestamp: 1234567,
|
||||
},
|
||||
],
|
||||
calculated_metrics: {
|
||||
success_ratio: 0.66,
|
||||
p50: 200000,
|
||||
p95: 300000,
|
||||
p99: 300000,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
|
@ -0,0 +1,238 @@
|
|||
/*
|
||||
* 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, { useState, useEffect, useMemo } from 'react';
|
||||
import {
|
||||
EuiText,
|
||||
EuiSpacer,
|
||||
EuiButtonEmpty,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiPanel,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { formatDuration } from '@kbn/alerting-plugin/common';
|
||||
import { RuleDefinitionProps } from '../../../../types';
|
||||
import { RuleType, useLoadRuleTypes } from '../../../..';
|
||||
import { useKibana } from '../../../../common/lib/kibana';
|
||||
import { hasAllPrivilege, hasExecuteActionsCapability } from '../../../lib/capabilities';
|
||||
import { NOTIFY_WHEN_OPTIONS } from '../../rule_form/rule_notify_when';
|
||||
import { RuleActions } from './rule_actions';
|
||||
import { RuleEdit } from '../../rule_form';
|
||||
|
||||
const OBSERVABILITY_SOLUTIONS = ['logs', 'uptime', 'infrastructure', 'apm'];
|
||||
|
||||
export const RuleDefinition: React.FunctionComponent<RuleDefinitionProps> = ({
|
||||
rule,
|
||||
actionTypeRegistry,
|
||||
ruleTypeRegistry,
|
||||
onEditRule,
|
||||
}) => {
|
||||
const {
|
||||
application: { capabilities },
|
||||
} = useKibana().services;
|
||||
|
||||
const [editFlyoutVisible, setEditFlyoutVisible] = useState<boolean>(false);
|
||||
const [ruleType, setRuleType] = useState<RuleType>();
|
||||
const { ruleTypes, ruleTypeIndex } = useLoadRuleTypes({
|
||||
filteredSolutions: OBSERVABILITY_SOLUTIONS,
|
||||
});
|
||||
|
||||
const getRuleType = useMemo(() => {
|
||||
if (ruleTypes.length && rule) {
|
||||
return ruleTypes.find((type) => type.id === rule.ruleTypeId);
|
||||
}
|
||||
}, [rule, ruleTypes]);
|
||||
|
||||
useEffect(() => {
|
||||
setRuleType(getRuleType);
|
||||
}, [getRuleType]);
|
||||
|
||||
const getRuleConditionsWording = () => {
|
||||
const numberOfConditions = rule?.params.criteria ? (rule?.params.criteria as any[]).length : 0;
|
||||
return i18n.translate('xpack.triggersActionsUI.ruleDetails.conditions', {
|
||||
defaultMessage: '{numberOfConditions, plural, one {# condition} other {# conditions}}',
|
||||
values: { numberOfConditions },
|
||||
});
|
||||
};
|
||||
const getNotifyText = () =>
|
||||
NOTIFY_WHEN_OPTIONS.find((options) => options.value === rule?.notifyWhen)?.inputDisplay ||
|
||||
rule?.notifyWhen;
|
||||
|
||||
const canExecuteActions = hasExecuteActionsCapability(capabilities);
|
||||
const canSaveRule =
|
||||
rule &&
|
||||
hasAllPrivilege(rule, ruleType) &&
|
||||
// if the rule has actions, can the user save the rule's action params
|
||||
(canExecuteActions || (!canExecuteActions && rule.actions.length === 0));
|
||||
const hasEditButton =
|
||||
// can the user save the rule
|
||||
canSaveRule &&
|
||||
// is this rule type editable from within Rules Management
|
||||
(ruleTypeRegistry.has(rule.ruleTypeId)
|
||||
? !ruleTypeRegistry.get(rule.ruleTypeId).requiresAppContext
|
||||
: false);
|
||||
return (
|
||||
<EuiFlexItem data-test-subj="ruleSummaryRuleDefinition" grow={3}>
|
||||
<EuiPanel color="subdued" hasBorder={false} paddingSize={'m'}>
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiTitle size="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
{i18n.translate('xpack.triggersActionsUI.ruleDetails.definition', {
|
||||
defaultMessage: 'Definition',
|
||||
})}
|
||||
</EuiFlexItem>
|
||||
</EuiTitle>
|
||||
{hasEditButton && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="ruleDetailsEditButton"
|
||||
iconType={'pencil'}
|
||||
onClick={() => setEditFlyoutVisible(true)}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
<EuiFlexGroup alignItems="baseline">
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup>
|
||||
<ItemTitleRuleSummary>
|
||||
{i18n.translate('xpack.triggersActionsUI.ruleDetails.ruleType', {
|
||||
defaultMessage: 'Rule type',
|
||||
})}
|
||||
</ItemTitleRuleSummary>
|
||||
<ItemValueRuleSummary
|
||||
data-test-subj="ruleSummaryRuleType"
|
||||
itemValue={ruleTypeIndex.get(rule.ruleTypeId)?.name || rule.ruleTypeId}
|
||||
/>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
<EuiFlexGroup alignItems="flexStart" responsive={false}>
|
||||
<ItemTitleRuleSummary>
|
||||
{i18n.translate('xpack.triggersActionsUI.ruleDetails.description', {
|
||||
defaultMessage: 'Description',
|
||||
})}
|
||||
</ItemTitleRuleSummary>
|
||||
<ItemValueRuleSummary
|
||||
data-test-subj="ruleSummaryRuleDescription"
|
||||
itemValue={ruleTypeRegistry.get(rule.ruleTypeId).description}
|
||||
/>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
<EuiFlexGroup>
|
||||
<ItemTitleRuleSummary>
|
||||
{i18n.translate('xpack.triggersActionsUI.ruleDetails.conditionsTitle', {
|
||||
defaultMessage: 'Conditions',
|
||||
})}
|
||||
</ItemTitleRuleSummary>
|
||||
<EuiFlexItem grow={3}>
|
||||
<EuiFlexGroup data-test-subj="ruleSummaryRuleConditions" alignItems="center">
|
||||
{hasEditButton ? (
|
||||
<EuiButtonEmpty onClick={() => setEditFlyoutVisible(true)}>
|
||||
<EuiText size="s">{getRuleConditionsWording()}</EuiText>
|
||||
</EuiButtonEmpty>
|
||||
) : (
|
||||
<EuiText size="s">{getRuleConditionsWording()}</EuiText>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup>
|
||||
<ItemTitleRuleSummary>
|
||||
{i18n.translate('xpack.triggersActionsUI.ruleDetails.runsEvery', {
|
||||
defaultMessage: 'Runs every',
|
||||
})}
|
||||
</ItemTitleRuleSummary>
|
||||
|
||||
<ItemValueRuleSummary
|
||||
data-test-subj="ruleSummaryRuleInterval"
|
||||
itemValue={formatDuration(rule.schedule.interval)}
|
||||
/>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
<EuiFlexGroup>
|
||||
<ItemTitleRuleSummary>
|
||||
{i18n.translate('xpack.triggersActionsUI.ruleDetails.notifyWhen', {
|
||||
defaultMessage: 'Notify',
|
||||
})}
|
||||
</ItemTitleRuleSummary>
|
||||
<ItemValueRuleSummary itemValue={String(getNotifyText())} />
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFlexGroup alignItems="baseline">
|
||||
<ItemTitleRuleSummary>
|
||||
{i18n.translate('xpack.triggersActionsUI.ruleDetails.actions', {
|
||||
defaultMessage: 'Actions',
|
||||
})}
|
||||
</ItemTitleRuleSummary>
|
||||
<EuiFlexItem grow={3}>
|
||||
<RuleActions ruleActions={rule.actions} actionTypeRegistry={actionTypeRegistry} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
{editFlyoutVisible && (
|
||||
<RuleEdit
|
||||
onSave={() => {
|
||||
setEditFlyoutVisible(false);
|
||||
return onEditRule();
|
||||
}}
|
||||
initialRule={rule}
|
||||
onClose={() => setEditFlyoutVisible(false)}
|
||||
ruleTypeRegistry={ruleTypeRegistry}
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
/>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
);
|
||||
};
|
||||
|
||||
export interface ItemTitleRuleSummaryProps {
|
||||
children: string;
|
||||
}
|
||||
export interface ItemValueRuleSummaryProps {
|
||||
itemValue: string;
|
||||
extraSpace?: boolean;
|
||||
}
|
||||
|
||||
function ItemValueRuleSummary({
|
||||
itemValue,
|
||||
extraSpace = true,
|
||||
...otherProps
|
||||
}: ItemValueRuleSummaryProps) {
|
||||
return (
|
||||
<EuiFlexItem grow={extraSpace ? 3 : 1} {...otherProps}>
|
||||
<EuiText size="s">{itemValue}</EuiText>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
}
|
||||
|
||||
function ItemTitleRuleSummary({ children }: ItemTitleRuleSummaryProps) {
|
||||
return (
|
||||
<EuiTitle size="xxs">
|
||||
<EuiFlexItem style={{ whiteSpace: 'nowrap' }} grow={1}>
|
||||
{children}
|
||||
</EuiFlexItem>
|
||||
</EuiTitle>
|
||||
);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export { RuleDefinition as default };
|
|
@ -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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { RuleDefinition } from '../application/sections';
|
||||
import { RuleDefinitionProps } from '../types';
|
||||
export const getRuleDefinitionLazy = (props: RuleDefinitionProps) => <RuleDefinition {...props} />;
|
|
@ -39,6 +39,7 @@ export type {
|
|||
RuleEventLogListProps,
|
||||
AlertTableFlyoutComponent,
|
||||
GetRenderCellValue,
|
||||
RuleDefinitionProps,
|
||||
} from './types';
|
||||
|
||||
export {
|
||||
|
|
|
@ -38,6 +38,7 @@ import { CreateConnectorFlyoutProps } from './application/sections/action_connec
|
|||
import { EditConnectorFlyoutProps } from './application/sections/action_connector_form/edit_connector_flyout';
|
||||
import { getActionFormLazy } from './common/get_action_form';
|
||||
import { ActionAccordionFormProps } from './application/sections/action_connector_form/action_form';
|
||||
import { getRuleDefinitionLazy } from './common/get_rule_definition';
|
||||
import { getRuleStatusPanelLazy } from './common/get_rule_status_panel';
|
||||
|
||||
function createStartMock(): TriggersAndActionsUIPublicPluginStart {
|
||||
|
@ -105,6 +106,9 @@ function createStartMock(): TriggersAndActionsUIPublicPluginStart {
|
|||
getRulesList: () => {
|
||||
return getRulesListLazy({ connectorServices });
|
||||
},
|
||||
getRuleDefinition: (props) => {
|
||||
return getRuleDefinitionLazy({ ...props, actionTypeRegistry, ruleTypeRegistry });
|
||||
},
|
||||
getRuleStatusPanel: (props) => {
|
||||
return getRuleStatusPanelLazy(props);
|
||||
},
|
||||
|
|
|
@ -62,6 +62,7 @@ import type {
|
|||
CreateConnectorFlyoutProps,
|
||||
EditConnectorFlyoutProps,
|
||||
ConnectorServices,
|
||||
RuleDefinitionProps,
|
||||
} from './types';
|
||||
import { TriggersActionsUiConfigType } from '../common/types';
|
||||
import { registerAlertsTableConfiguration } from './application/sections/alerts_table/alerts_page/register_alerts_table_configuration';
|
||||
|
@ -69,6 +70,7 @@ import { PLUGIN_ID } from './common/constants';
|
|||
import type { AlertsTableStateProps } from './application/sections/alerts_table/alerts_table_state';
|
||||
import { getAlertsTableStateLazy } from './common/get_alerts_table_state';
|
||||
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';
|
||||
|
||||
export interface TriggersAndActionsUIPublicPluginSetup {
|
||||
|
@ -109,6 +111,7 @@ export interface TriggersAndActionsUIPublicPluginStart {
|
|||
props: RulesListNotifyBadgeProps
|
||||
) => ReactElement<RulesListNotifyBadgeProps>;
|
||||
getRulesList: () => ReactElement;
|
||||
getRuleDefinition: (props: RuleDefinitionProps) => ReactElement<RuleDefinitionProps>;
|
||||
getRuleStatusPanel: (props: RuleStatusPanelProps) => ReactElement<RuleStatusPanelProps>;
|
||||
}
|
||||
|
||||
|
@ -323,6 +326,15 @@ export class Plugin
|
|||
getRulesList: () => {
|
||||
return getRulesListLazy({ connectorServices: this.connectorServices! });
|
||||
},
|
||||
getRuleDefinition: (
|
||||
props: Omit<RuleDefinitionProps, 'actionTypeRegistry' | 'ruleTypeRegistry'>
|
||||
) => {
|
||||
return getRuleDefinitionLazy({
|
||||
...props,
|
||||
actionTypeRegistry: this.actionTypeRegistry,
|
||||
ruleTypeRegistry: this.ruleTypeRegistry,
|
||||
});
|
||||
},
|
||||
getRuleStatusPanel: (props: RuleStatusPanelProps) => {
|
||||
return getRuleStatusPanelLazy(props);
|
||||
},
|
||||
|
|
|
@ -343,6 +343,12 @@ export interface RuleAddProps<MetaData = Record<string, any>> {
|
|||
ruleTypeIndex?: RuleTypeIndex;
|
||||
filteredSolutions?: string[] | undefined;
|
||||
}
|
||||
export interface RuleDefinitionProps {
|
||||
rule: Rule;
|
||||
ruleTypeRegistry: RuleTypeRegistryContract;
|
||||
actionTypeRegistry: ActionTypeRegistryContract;
|
||||
onEditRule: () => Promise<void>;
|
||||
}
|
||||
|
||||
export enum Percentiles {
|
||||
P50 = 'P50',
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue