mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[RAM] Stack management/o11y rule details parity (#136778)
* stack management/o11y rule details parity * Hide edit button in stack management * Add tests * Move fetching summary out of o11y * Undo changes to hooks in o11y * Fix test and add new tests * Remove customLoadExecutionLog prop Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
0645a3ba38
commit
cd3d2d79c7
18 changed files with 845 additions and 249 deletions
|
@ -12,11 +12,62 @@ interface SandboxProps {
|
|||
triggersActionsUi: TriggersAndActionsUIPublicPluginStart;
|
||||
}
|
||||
|
||||
function mockRuleType() {
|
||||
return {
|
||||
id: 'test.testRuleType',
|
||||
name: 'My Test Rule Type',
|
||||
actionGroups: [{ id: 'default', name: 'Default Action Group' }],
|
||||
actionVariables: {
|
||||
context: [],
|
||||
state: [],
|
||||
params: [],
|
||||
},
|
||||
defaultActionGroupId: 'default',
|
||||
recoveryActionGroup: { id: 'recovered', name: 'Recovered' },
|
||||
authorizedConsumers: {},
|
||||
producer: 'rules',
|
||||
minimumLicenseRequired: 'basic',
|
||||
enabledInLicense: true,
|
||||
};
|
||||
}
|
||||
|
||||
function mockRuleSummary() {
|
||||
return {
|
||||
id: 'rule-id',
|
||||
name: 'rule-name',
|
||||
tags: ['tag-1', 'tag-2'],
|
||||
ruleTypeId: 'test',
|
||||
consumer: 'rule-consumer',
|
||||
status: 'OK',
|
||||
muteAll: false,
|
||||
throttle: '',
|
||||
enabled: true,
|
||||
errorMessages: [],
|
||||
statusStartDate: '2022-03-21T07:40:46-07:00',
|
||||
statusEndDate: '2022-03-25T07:40:46-07:00',
|
||||
alerts: {
|
||||
foo: {
|
||||
status: 'OK',
|
||||
muted: false,
|
||||
actionGroupId: 'testActionGroup',
|
||||
},
|
||||
},
|
||||
executionDuration: {
|
||||
average: 100,
|
||||
valuesWithTimestamp: {},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const RuleEventLogListSandbox = ({ triggersActionsUi }: SandboxProps) => {
|
||||
const componenProps: any = {
|
||||
rule: {
|
||||
id: 'test',
|
||||
},
|
||||
ruleType: mockRuleType(),
|
||||
ruleSummary: mockRuleSummary(),
|
||||
numberOfExecutions: 60,
|
||||
onChangeDuration: (duration: number) => {},
|
||||
customLoadExecutionLogAggregations: async () => ({
|
||||
total: 1,
|
||||
data: [
|
||||
|
|
|
@ -185,8 +185,9 @@ export function RuleDetailsPage() {
|
|||
defaultMessage: 'Execution history',
|
||||
}),
|
||||
'data-test-subj': 'eventLogListTab',
|
||||
content: getRuleEventLogList({
|
||||
content: getRuleEventLogList<'default'>({
|
||||
rule,
|
||||
ruleType,
|
||||
} as RuleEventLogListProps),
|
||||
},
|
||||
{
|
||||
|
|
|
@ -79,6 +79,7 @@ export const ExecutionDurationChart: React.FunctionComponent<ComponentOpts> = ({
|
|||
<EuiFlexItem grow={false}>
|
||||
<EuiSelect
|
||||
id="select-number-execution-durations"
|
||||
data-test-subj="executionDurationChartPanelSelect"
|
||||
options={NUM_EXECUTIONS_OPTIONS}
|
||||
value={numberOfExecutions}
|
||||
aria-label={i18n.translate(
|
||||
|
|
|
@ -12,9 +12,10 @@ import { act } from 'react-dom/test-utils';
|
|||
import { RuleComponent, alertToListItem } from './rule';
|
||||
import { AlertListItem } from './types';
|
||||
import { RuleAlertList } from './rule_alert_list';
|
||||
import { RuleSummary, AlertStatus, RuleType } from '../../../../types';
|
||||
import { ExecutionDurationChart } from '../../common/components/execution_duration_chart';
|
||||
import { RuleSummary, AlertStatus, RuleType, RuleTypeModel } from '../../../../types';
|
||||
import { mockRule } from './test_helpers';
|
||||
import { ruleTypeRegistryMock } from '../../../rule_type_registry.mock';
|
||||
import { useKibana } from '../../../../common/lib/kibana';
|
||||
|
||||
jest.mock('../../../../common/lib/kibana');
|
||||
|
||||
|
@ -22,6 +23,21 @@ jest.mock('../../../../common/get_experimental_features', () => ({
|
|||
getIsExperimentalFeatureEnabled: jest.fn(),
|
||||
}));
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
const useKibanaMock = useKibana as jest.Mocked<typeof useKibana>;
|
||||
const ruleTypeRegistry = ruleTypeRegistryMock.create();
|
||||
|
||||
import { getIsExperimentalFeatureEnabled } from '../../../../common/get_experimental_features';
|
||||
|
||||
const fakeNow = new Date('2020-02-09T23:15:41.941Z');
|
||||
|
@ -37,6 +53,8 @@ const mockAPIs = {
|
|||
|
||||
beforeAll(() => {
|
||||
jest.clearAllMocks();
|
||||
ruleTypeRegistry.get.mockReturnValue(ruleTypeR);
|
||||
useKibanaMock().services.ruleTypeRegistry = ruleTypeRegistry;
|
||||
global.Date.now = jest.fn(() => fakeNow.getTime());
|
||||
});
|
||||
|
||||
|
@ -316,91 +334,6 @@ describe('execution duration overview', () => {
|
|||
expect(ruleExecutionStatusStat.first().prop('description')).toEqual('Last response');
|
||||
expect(wrapper.find('EuiHealth[data-test-subj="ruleStatus-ok"]').text()).toEqual('Ok');
|
||||
});
|
||||
|
||||
it('renders average execution duration', async () => {
|
||||
const rule = mockRule();
|
||||
const ruleType = mockRuleType({ ruleTaskTimeout: '10m' });
|
||||
const ruleSummary = mockRuleSummary({
|
||||
executionDuration: { average: 60284, valuesWithTimestamp: {} },
|
||||
});
|
||||
|
||||
const wrapper = mountWithIntl(
|
||||
<RuleComponent
|
||||
{...mockAPIs}
|
||||
rule={rule}
|
||||
ruleType={ruleType}
|
||||
readOnly={false}
|
||||
ruleSummary={ruleSummary}
|
||||
/>
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await nextTick();
|
||||
wrapper.update();
|
||||
});
|
||||
|
||||
const avgExecutionDurationPanel = wrapper.find('[data-test-subj="avgExecutionDurationPanel"]');
|
||||
expect(avgExecutionDurationPanel.exists()).toBeTruthy();
|
||||
expect(avgExecutionDurationPanel.first().prop('color')).toEqual('subdued');
|
||||
expect(wrapper.find('EuiStat[data-test-subj="avgExecutionDurationStat"]').text()).toEqual(
|
||||
'Average duration00:01:00.284'
|
||||
);
|
||||
expect(wrapper.find('[data-test-subj="ruleDurationWarning"]').exists()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('renders warning when average execution duration exceeds rule timeout', async () => {
|
||||
const rule = mockRule();
|
||||
const ruleType = mockRuleType({ ruleTaskTimeout: '10m' });
|
||||
const ruleSummary = mockRuleSummary({
|
||||
executionDuration: { average: 60284345, valuesWithTimestamp: {} },
|
||||
});
|
||||
|
||||
const wrapper = mountWithIntl(
|
||||
<RuleComponent
|
||||
{...mockAPIs}
|
||||
rule={rule}
|
||||
ruleType={ruleType}
|
||||
readOnly={false}
|
||||
ruleSummary={ruleSummary}
|
||||
/>
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await nextTick();
|
||||
wrapper.update();
|
||||
});
|
||||
|
||||
const avgExecutionDurationPanel = wrapper.find('[data-test-subj="avgExecutionDurationPanel"]');
|
||||
expect(avgExecutionDurationPanel.exists()).toBeTruthy();
|
||||
expect(avgExecutionDurationPanel.first().prop('color')).toEqual('warning');
|
||||
|
||||
const avgExecutionDurationStat = wrapper
|
||||
.find('EuiStat[data-test-subj="avgExecutionDurationStat"]')
|
||||
.text()
|
||||
.replaceAll('Info', '');
|
||||
expect(avgExecutionDurationStat).toEqual('Average duration16:44:44.345');
|
||||
expect(wrapper.find('[data-test-subj="ruleDurationWarning"]').exists()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('renders execution duration chart', () => {
|
||||
const rule = mockRule();
|
||||
const ruleType = mockRuleType();
|
||||
const ruleSummary = mockRuleSummary();
|
||||
|
||||
expect(
|
||||
shallow(
|
||||
<RuleComponent
|
||||
{...mockAPIs}
|
||||
rule={rule}
|
||||
ruleType={ruleType}
|
||||
ruleSummary={ruleSummary}
|
||||
readOnly={false}
|
||||
/>
|
||||
)
|
||||
.find(ExecutionDurationChart)
|
||||
.exists()
|
||||
).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('disable/enable functionality', () => {
|
||||
|
@ -486,8 +419,8 @@ describe('tabbed content', () => {
|
|||
tabbedContent.update();
|
||||
});
|
||||
|
||||
expect(tabbedContent.find('[aria-labelledby="rule_event_log_list"]').exists()).toBeFalsy();
|
||||
expect(tabbedContent.find('[aria-labelledby="rule_alert_list"]').exists()).toBeTruthy();
|
||||
expect(tabbedContent.find('[aria-labelledby="rule_event_log_list"]').exists()).toBeTruthy();
|
||||
expect(tabbedContent.find('[aria-labelledby="rule_alert_list"]').exists()).toBeFalsy();
|
||||
|
||||
tabbedContent.find('[data-test-subj="eventLogListTab"]').simulate('click');
|
||||
|
||||
|
|
|
@ -7,20 +7,13 @@
|
|||
|
||||
import React, { lazy } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
EuiSpacer,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiPanel,
|
||||
EuiStat,
|
||||
EuiIconTip,
|
||||
EuiTabbedContent,
|
||||
} from '@elastic/eui';
|
||||
import { EuiSpacer, EuiFlexGroup, EuiFlexItem, EuiTabbedContent } from '@elastic/eui';
|
||||
import {
|
||||
ActionGroup,
|
||||
RuleExecutionStatusErrorReasons,
|
||||
AlertStatusValues,
|
||||
} from '@kbn/alerting-plugin/common';
|
||||
import { useKibana } from '../../../../common/lib/kibana';
|
||||
import { Rule, RuleSummary, AlertStatus, RuleType } from '../../../../types';
|
||||
import {
|
||||
ComponentOpts as RuleApis,
|
||||
|
@ -32,12 +25,7 @@ import {
|
|||
rulesStatusesTranslationsMapping,
|
||||
ALERT_STATUS_LICENSE_ERROR,
|
||||
} from '../../rules_list/translations';
|
||||
import {
|
||||
formatMillisForDisplay,
|
||||
shouldShowDurationWarning,
|
||||
} from '../../../lib/execution_duration_utils';
|
||||
import { ExecutionDurationChart } from '../../common/components/execution_duration_chart';
|
||||
// import { RuleEventLogListWithApi } from './rule_event_log_list';
|
||||
import type { RuleEventLogListProps } from './rule_event_log_list';
|
||||
import { AlertListItem } from './types';
|
||||
import { getIsExperimentalFeatureEnabled } from '../../../../common/get_experimental_features';
|
||||
import { suspendedComponentWithProps } from '../../../lib/suspended_component_with_props';
|
||||
|
@ -45,6 +33,7 @@ import RuleStatusPanelWithApi from './rule_status_panel';
|
|||
|
||||
const RuleEventLogListWithApi = lazy(() => import('./rule_event_log_list'));
|
||||
const RuleAlertList = lazy(() => import('./rule_alert_list'));
|
||||
const RuleDefinition = lazy(() => import('./rule_definition'));
|
||||
|
||||
type RuleProps = {
|
||||
rule: Rule;
|
||||
|
@ -76,6 +65,8 @@ export function RuleComponent({
|
|||
durationEpoch = Date.now(),
|
||||
isLoadingChart,
|
||||
}: RuleProps) {
|
||||
const { ruleTypeRegistry, actionTypeRegistry } = useKibana().services;
|
||||
|
||||
const alerts = Object.entries(ruleSummary.alerts)
|
||||
.map(([alertId, alert]) => alertToListItem(durationEpoch, ruleType, alertId, alert))
|
||||
.sort((leftAlert, rightAlert) => leftAlert.sortPriority - rightAlert.sortPriority);
|
||||
|
@ -87,11 +78,6 @@ export function RuleComponent({
|
|||
requestRefresh();
|
||||
};
|
||||
|
||||
const showDurationWarning = shouldShowDurationWarning(
|
||||
ruleType,
|
||||
ruleSummary.executionDuration.average
|
||||
);
|
||||
|
||||
const healthColor = getHealthColor(rule.executionStatus.status);
|
||||
const isLicenseError =
|
||||
rule.executionStatus.error?.reason === RuleExecutionStatusErrorReasons.License;
|
||||
|
@ -111,6 +97,27 @@ export function RuleComponent({
|
|||
};
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
id: EVENT_LOG_LIST_TAB,
|
||||
name: i18n.translate('xpack.triggersActionsUI.sections.ruleDetails.rule.eventLogTabText', {
|
||||
defaultMessage: 'History',
|
||||
}),
|
||||
'data-test-subj': 'eventLogListTab',
|
||||
content: suspendedComponentWithProps<RuleEventLogListProps<'stackManagement'>>(
|
||||
RuleEventLogListWithApi,
|
||||
'xl'
|
||||
)({
|
||||
fetchRuleSummary: false,
|
||||
rule,
|
||||
ruleType,
|
||||
ruleSummary,
|
||||
numberOfExecutions,
|
||||
refreshToken,
|
||||
isLoadingRuleSummary: isLoadingChart,
|
||||
onChangeDuration,
|
||||
requestRefresh,
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: ALERT_LIST_TAB,
|
||||
name: i18n.translate('xpack.triggersActionsUI.sections.ruleDetails.rule.alertsTabText', {
|
||||
|
@ -119,17 +126,6 @@ export function RuleComponent({
|
|||
'data-test-subj': 'ruleAlertListTab',
|
||||
content: renderRuleAlertList(),
|
||||
},
|
||||
{
|
||||
id: EVENT_LOG_LIST_TAB,
|
||||
name: i18n.translate('xpack.triggersActionsUI.sections.ruleDetails.rule.eventLogTabText', {
|
||||
defaultMessage: 'History',
|
||||
}),
|
||||
'data-test-subj': 'eventLogListTab',
|
||||
content: suspendedComponentWithProps(
|
||||
RuleEventLogListWithApi,
|
||||
'xl'
|
||||
)({ requestRefresh, rule, refreshToken }),
|
||||
},
|
||||
];
|
||||
|
||||
const renderTabs = () => {
|
||||
|
@ -142,8 +138,8 @@ export function RuleComponent({
|
|||
|
||||
return (
|
||||
<>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={1}>
|
||||
<EuiFlexGroup gutterSize="s" wrap>
|
||||
<EuiFlexItem grow={2}>
|
||||
<RuleStatusPanelWithApi
|
||||
rule={rule}
|
||||
isEditable={!readOnly}
|
||||
|
@ -152,56 +148,16 @@ export function RuleComponent({
|
|||
requestRefresh={requestRefresh}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={1}>
|
||||
<EuiPanel
|
||||
data-test-subj="avgExecutionDurationPanel"
|
||||
color={showDurationWarning ? 'warning' : 'subdued'}
|
||||
hasBorder={false}
|
||||
>
|
||||
<EuiStat
|
||||
data-test-subj="avgExecutionDurationStat"
|
||||
titleSize="xs"
|
||||
title={
|
||||
<EuiFlexGroup gutterSize="xs">
|
||||
{showDurationWarning && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIconTip
|
||||
data-test-subj="ruleDurationWarning"
|
||||
anchorClassName="ruleDurationWarningIcon"
|
||||
type="alert"
|
||||
color="warning"
|
||||
content={i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.ruleDetails.alertsList.ruleTypeExcessDurationMessage',
|
||||
{
|
||||
defaultMessage: `Duration exceeds the rule's expected run time.`,
|
||||
}
|
||||
)}
|
||||
position="top"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem grow={false}>
|
||||
{formatMillisForDisplay(ruleSummary.executionDuration.average)}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
description={i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.ruleDetails.alertsList.avgDurationDescription',
|
||||
{
|
||||
defaultMessage: `Average duration`,
|
||||
}
|
||||
)}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={2}>
|
||||
<ExecutionDurationChart
|
||||
executionDuration={ruleSummary.executionDuration}
|
||||
numberOfExecutions={numberOfExecutions}
|
||||
onChangeDuration={onChangeDuration}
|
||||
isLoading={isLoadingChart}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
{suspendedComponentWithProps(
|
||||
RuleDefinition,
|
||||
'xl'
|
||||
)({
|
||||
rule,
|
||||
actionTypeRegistry,
|
||||
ruleTypeRegistry,
|
||||
hideEditButton: true,
|
||||
onEditRule: requestRefresh,
|
||||
})}
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiSpacer size="xl" />
|
||||
|
|
|
@ -29,6 +29,7 @@ export const RuleDefinition: React.FunctionComponent<RuleDefinitionProps> = ({
|
|||
actionTypeRegistry,
|
||||
ruleTypeRegistry,
|
||||
onEditRule,
|
||||
hideEditButton = false,
|
||||
filteredRuleTypes,
|
||||
}) => {
|
||||
const {
|
||||
|
@ -68,13 +69,19 @@ export const RuleDefinition: React.FunctionComponent<RuleDefinitionProps> = ({
|
|||
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 =
|
||||
const hasEditButton = useMemo(() => {
|
||||
if (hideEditButton) {
|
||||
return false;
|
||||
}
|
||||
// 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 (
|
||||
canSaveRule &&
|
||||
// is this rule type editable from within Rules Management
|
||||
(ruleTypeRegistry.has(rule.ruleTypeId)
|
||||
? !ruleTypeRegistry.get(rule.ruleTypeId).requiresAppContext
|
||||
: false)
|
||||
);
|
||||
}, [hideEditButton, canSaveRule, ruleTypeRegistry, rule]);
|
||||
return (
|
||||
<EuiFlexItem data-test-subj="ruleSummaryRuleDefinition" grow={3}>
|
||||
<EuiPanel color="subdued" hasBorder={false} paddingSize={'m'}>
|
||||
|
|
|
@ -10,13 +10,14 @@ import uuid from 'uuid';
|
|||
import { act } from 'react-dom/test-utils';
|
||||
import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers';
|
||||
import { useKibana } from '../../../../common/lib/kibana';
|
||||
|
||||
import { ActionGroup, ALERTS_FEATURE_ID } from '@kbn/alerting-plugin/common';
|
||||
import { EuiSuperDatePicker, EuiDataGrid } from '@elastic/eui';
|
||||
import { RuleEventLogListStatusFilter } from './rule_event_log_list_status_filter';
|
||||
import { RuleEventLogList } from './rule_event_log_list';
|
||||
import { RefineSearchPrompt } from '../refine_search_prompt';
|
||||
import { RULE_EXECUTION_DEFAULT_INITIAL_VISIBLE_COLUMNS } from '../../../constants';
|
||||
import { Rule } from '../../../../types';
|
||||
import { mockRule, mockRuleType, mockRuleSummary } from './test_helpers';
|
||||
import { RuleType } from '../../../../types';
|
||||
import { loadActionErrorLog } from '../../../lib/rule_api/load_action_error_log';
|
||||
|
||||
const useKibanaMock = useKibana as jest.Mocked<typeof useKibana>;
|
||||
|
@ -107,31 +108,24 @@ const mockLogResponse: any = {
|
|||
total: 4,
|
||||
};
|
||||
|
||||
const mockRule: Rule = {
|
||||
id: uuid.v4(),
|
||||
enabled: true,
|
||||
name: `rule-${uuid.v4()}`,
|
||||
tags: [],
|
||||
ruleTypeId: '.noop',
|
||||
consumer: 'consumer',
|
||||
schedule: { interval: '1m' },
|
||||
actions: [],
|
||||
params: {},
|
||||
createdBy: null,
|
||||
updatedBy: null,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
apiKeyOwner: null,
|
||||
throttle: null,
|
||||
notifyWhen: null,
|
||||
muteAll: false,
|
||||
mutedInstanceIds: [],
|
||||
executionStatus: {
|
||||
status: 'unknown',
|
||||
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
|
||||
},
|
||||
const loadExecutionLogAggregationsMock = jest.fn();
|
||||
|
||||
const onChangeDurationMock = jest.fn();
|
||||
|
||||
const ruleMock = mockRule();
|
||||
|
||||
const authorizedConsumers = {
|
||||
[ALERTS_FEATURE_ID]: { read: true, all: true },
|
||||
};
|
||||
|
||||
const recoveryActionGroup: ActionGroup<'recovered'> = { id: 'recovered', name: 'Recovered' };
|
||||
|
||||
const ruleType: RuleType = mockRuleType({
|
||||
producer: ALERTS_FEATURE_ID,
|
||||
authorizedConsumers,
|
||||
recoveryActionGroup,
|
||||
});
|
||||
|
||||
const mockErrorLogResponse = {
|
||||
totalErrors: 1,
|
||||
errors: [
|
||||
|
@ -145,8 +139,6 @@ const mockErrorLogResponse = {
|
|||
],
|
||||
};
|
||||
|
||||
const loadExecutionLogAggregationsMock = jest.fn();
|
||||
|
||||
describe('rule_event_log_list', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
@ -168,7 +160,11 @@ describe('rule_event_log_list', () => {
|
|||
it('renders correctly', async () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<RuleEventLogList
|
||||
rule={mockRule}
|
||||
rule={ruleMock}
|
||||
ruleType={ruleType}
|
||||
ruleSummary={mockRuleSummary({ ruleTypeId: ruleMock.ruleTypeId })}
|
||||
numberOfExecutions={60}
|
||||
onChangeDuration={onChangeDurationMock}
|
||||
loadExecutionLogAggregations={loadExecutionLogAggregationsMock}
|
||||
/>
|
||||
);
|
||||
|
@ -178,7 +174,7 @@ describe('rule_event_log_list', () => {
|
|||
|
||||
expect(loadExecutionLogAggregationsMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
id: mockRule.id,
|
||||
id: ruleMock.id,
|
||||
sort: [],
|
||||
outcomeFilter: [],
|
||||
page: 0,
|
||||
|
@ -210,7 +206,11 @@ describe('rule_event_log_list', () => {
|
|||
it('can sort by single and/or multiple column(s)', async () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<RuleEventLogList
|
||||
rule={mockRule}
|
||||
rule={ruleMock}
|
||||
ruleType={ruleType}
|
||||
ruleSummary={mockRuleSummary({ ruleTypeId: ruleMock.ruleTypeId })}
|
||||
numberOfExecutions={60}
|
||||
onChangeDuration={onChangeDurationMock}
|
||||
loadExecutionLogAggregations={loadExecutionLogAggregationsMock}
|
||||
/>
|
||||
);
|
||||
|
@ -238,7 +238,7 @@ describe('rule_event_log_list', () => {
|
|||
|
||||
expect(loadExecutionLogAggregationsMock).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
id: mockRule.id,
|
||||
id: ruleMock.id,
|
||||
message: '',
|
||||
outcomeFilter: [],
|
||||
page: 0,
|
||||
|
@ -262,7 +262,7 @@ describe('rule_event_log_list', () => {
|
|||
|
||||
expect(loadExecutionLogAggregationsMock).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
id: mockRule.id,
|
||||
id: ruleMock.id,
|
||||
sort: [{ timestamp: { order: 'desc' } }],
|
||||
outcomeFilter: [],
|
||||
page: 0,
|
||||
|
@ -292,7 +292,7 @@ describe('rule_event_log_list', () => {
|
|||
|
||||
expect(loadExecutionLogAggregationsMock).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
id: mockRule.id,
|
||||
id: ruleMock.id,
|
||||
sort: [
|
||||
{
|
||||
timestamp: { order: 'desc' },
|
||||
|
@ -311,7 +311,11 @@ describe('rule_event_log_list', () => {
|
|||
it('can filter by execution log outcome status', async () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<RuleEventLogList
|
||||
rule={mockRule}
|
||||
rule={ruleMock}
|
||||
ruleType={ruleType}
|
||||
ruleSummary={mockRuleSummary({ ruleTypeId: ruleMock.ruleTypeId })}
|
||||
numberOfExecutions={60}
|
||||
onChangeDuration={onChangeDurationMock}
|
||||
loadExecutionLogAggregations={loadExecutionLogAggregationsMock}
|
||||
/>
|
||||
);
|
||||
|
@ -333,7 +337,7 @@ describe('rule_event_log_list', () => {
|
|||
|
||||
expect(loadExecutionLogAggregationsMock).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
id: mockRule.id,
|
||||
id: ruleMock.id,
|
||||
sort: [],
|
||||
outcomeFilter: ['success'],
|
||||
page: 0,
|
||||
|
@ -353,7 +357,7 @@ describe('rule_event_log_list', () => {
|
|||
|
||||
expect(loadExecutionLogAggregationsMock).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
id: mockRule.id,
|
||||
id: ruleMock.id,
|
||||
sort: [],
|
||||
outcomeFilter: ['success', 'failure'],
|
||||
page: 0,
|
||||
|
@ -370,7 +374,11 @@ describe('rule_event_log_list', () => {
|
|||
|
||||
const wrapper = mountWithIntl(
|
||||
<RuleEventLogList
|
||||
rule={mockRule}
|
||||
rule={ruleMock}
|
||||
ruleType={ruleType}
|
||||
ruleSummary={mockRuleSummary({ ruleTypeId: ruleMock.ruleTypeId })}
|
||||
numberOfExecutions={60}
|
||||
onChangeDuration={onChangeDurationMock}
|
||||
loadExecutionLogAggregations={loadExecutionLogAggregationsMock}
|
||||
/>
|
||||
);
|
||||
|
@ -392,7 +400,7 @@ describe('rule_event_log_list', () => {
|
|||
|
||||
expect(loadExecutionLogAggregationsMock).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
id: mockRule.id,
|
||||
id: ruleMock.id,
|
||||
sort: [],
|
||||
outcomeFilter: [],
|
||||
page: 1,
|
||||
|
@ -412,7 +420,7 @@ describe('rule_event_log_list', () => {
|
|||
|
||||
expect(loadExecutionLogAggregationsMock).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
id: mockRule.id,
|
||||
id: ruleMock.id,
|
||||
sort: [],
|
||||
outcomeFilter: [],
|
||||
page: 0,
|
||||
|
@ -426,7 +434,11 @@ describe('rule_event_log_list', () => {
|
|||
|
||||
const wrapper = mountWithIntl(
|
||||
<RuleEventLogList
|
||||
rule={mockRule}
|
||||
rule={ruleMock}
|
||||
ruleType={ruleType}
|
||||
ruleSummary={mockRuleSummary({ ruleTypeId: ruleMock.ruleTypeId })}
|
||||
numberOfExecutions={60}
|
||||
onChangeDuration={onChangeDurationMock}
|
||||
loadExecutionLogAggregations={loadExecutionLogAggregationsMock}
|
||||
/>
|
||||
);
|
||||
|
@ -438,7 +450,7 @@ describe('rule_event_log_list', () => {
|
|||
|
||||
expect(loadExecutionLogAggregationsMock).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
id: mockRule.id,
|
||||
id: ruleMock.id,
|
||||
sort: [],
|
||||
outcomeFilter: [],
|
||||
page: 0,
|
||||
|
@ -463,7 +475,7 @@ describe('rule_event_log_list', () => {
|
|||
|
||||
expect(loadExecutionLogAggregationsMock).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
id: mockRule.id,
|
||||
id: ruleMock.id,
|
||||
sort: [],
|
||||
outcomeFilter: [],
|
||||
page: 0,
|
||||
|
@ -479,7 +491,11 @@ describe('rule_event_log_list', () => {
|
|||
it('can save display columns to localStorage', async () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<RuleEventLogList
|
||||
rule={mockRule}
|
||||
rule={ruleMock}
|
||||
ruleType={ruleType}
|
||||
ruleSummary={mockRuleSummary({ ruleTypeId: ruleMock.ruleTypeId })}
|
||||
numberOfExecutions={60}
|
||||
onChangeDuration={onChangeDurationMock}
|
||||
loadExecutionLogAggregations={loadExecutionLogAggregationsMock}
|
||||
/>
|
||||
);
|
||||
|
@ -518,7 +534,11 @@ describe('rule_event_log_list', () => {
|
|||
it('does not show the refine search prompt normally', async () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<RuleEventLogList
|
||||
rule={mockRule}
|
||||
rule={ruleMock}
|
||||
ruleType={ruleType}
|
||||
ruleSummary={mockRuleSummary({ ruleTypeId: ruleMock.ruleTypeId })}
|
||||
numberOfExecutions={60}
|
||||
onChangeDuration={onChangeDurationMock}
|
||||
loadExecutionLogAggregations={loadExecutionLogAggregationsMock}
|
||||
/>
|
||||
);
|
||||
|
@ -539,7 +559,11 @@ describe('rule_event_log_list', () => {
|
|||
|
||||
const wrapper = mountWithIntl(
|
||||
<RuleEventLogList
|
||||
rule={mockRule}
|
||||
rule={ruleMock}
|
||||
ruleType={ruleType}
|
||||
ruleSummary={mockRuleSummary({ ruleTypeId: ruleMock.ruleTypeId })}
|
||||
numberOfExecutions={60}
|
||||
onChangeDuration={onChangeDurationMock}
|
||||
loadExecutionLogAggregations={loadExecutionLogAggregationsMock}
|
||||
/>
|
||||
);
|
||||
|
@ -585,7 +609,11 @@ describe('rule_event_log_list', () => {
|
|||
|
||||
const wrapper = mountWithIntl(
|
||||
<RuleEventLogList
|
||||
rule={mockRule}
|
||||
rule={ruleMock}
|
||||
ruleType={ruleType}
|
||||
ruleSummary={mockRuleSummary({ ruleTypeId: ruleMock.ruleTypeId })}
|
||||
numberOfExecutions={60}
|
||||
onChangeDuration={onChangeDurationMock}
|
||||
loadExecutionLogAggregations={loadExecutionLogAggregationsMock}
|
||||
/>
|
||||
);
|
||||
|
@ -608,7 +636,11 @@ describe('rule_event_log_list', () => {
|
|||
|
||||
const wrapper = mountWithIntl(
|
||||
<RuleEventLogList
|
||||
rule={mockRule}
|
||||
rule={ruleMock}
|
||||
ruleType={ruleType}
|
||||
ruleSummary={mockRuleSummary({ ruleTypeId: ruleMock.ruleTypeId })}
|
||||
numberOfExecutions={60}
|
||||
onChangeDuration={onChangeDurationMock}
|
||||
loadExecutionLogAggregations={loadExecutionLogAggregationsMock}
|
||||
/>
|
||||
);
|
||||
|
@ -631,7 +663,11 @@ describe('rule_event_log_list', () => {
|
|||
|
||||
const wrapper = mountWithIntl(
|
||||
<RuleEventLogList
|
||||
rule={mockRule}
|
||||
rule={ruleMock}
|
||||
ruleType={ruleType}
|
||||
ruleSummary={mockRuleSummary({ ruleTypeId: ruleMock.ruleTypeId })}
|
||||
numberOfExecutions={60}
|
||||
onChangeDuration={onChangeDurationMock}
|
||||
loadExecutionLogAggregations={loadExecutionLogAggregationsMock}
|
||||
/>
|
||||
);
|
||||
|
@ -695,7 +731,11 @@ describe('rule_event_log_list', () => {
|
|||
|
||||
const wrapper = mountWithIntl(
|
||||
<RuleEventLogList
|
||||
rule={mockRule}
|
||||
rule={ruleMock}
|
||||
ruleType={ruleType}
|
||||
ruleSummary={mockRuleSummary({ ruleTypeId: ruleMock.ruleTypeId })}
|
||||
numberOfExecutions={60}
|
||||
onChangeDuration={onChangeDurationMock}
|
||||
loadExecutionLogAggregations={loadExecutionLogAggregationsMock}
|
||||
/>
|
||||
);
|
||||
|
@ -714,4 +754,116 @@ describe('rule_event_log_list', () => {
|
|||
.simulate('click');
|
||||
expect(wrapper.find('[data-test-subj="ruleActionErrorLogFlyout"]').exists()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('shows rule summary and execution duration chart', async () => {
|
||||
loadExecutionLogAggregationsMock.mockResolvedValue({
|
||||
...mockLogResponse,
|
||||
total: 85,
|
||||
});
|
||||
|
||||
const wrapper = mountWithIntl(
|
||||
<RuleEventLogList
|
||||
fetchRuleSummary={false}
|
||||
rule={ruleMock}
|
||||
ruleType={ruleType}
|
||||
ruleSummary={mockRuleSummary({ ruleTypeId: ruleMock.ruleTypeId })}
|
||||
numberOfExecutions={60}
|
||||
onChangeDuration={onChangeDurationMock}
|
||||
loadExecutionLogAggregations={loadExecutionLogAggregationsMock}
|
||||
/>
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await nextTick();
|
||||
wrapper.update();
|
||||
});
|
||||
|
||||
const avgExecutionDurationPanel = wrapper.find('[data-test-subj="avgExecutionDurationPanel"]');
|
||||
expect(avgExecutionDurationPanel.exists()).toBeTruthy();
|
||||
expect(avgExecutionDurationPanel.first().prop('color')).toEqual('subdued');
|
||||
expect(wrapper.find('EuiStat[data-test-subj="avgExecutionDurationStat"]').text()).toEqual(
|
||||
'Average duration00:00:00.100'
|
||||
);
|
||||
expect(wrapper.find('[data-test-subj="ruleDurationWarning"]').exists()).toBeFalsy();
|
||||
|
||||
expect(wrapper.find('[data-test-subj="executionDurationChartPanel"]').exists()).toBeTruthy();
|
||||
expect(wrapper.find('[data-test-subj="avgExecutionDurationPanel"]').exists()).toBeTruthy();
|
||||
expect(wrapper.find('[data-test-subj="ruleEventLogListAvgDuration"]').first().text()).toEqual(
|
||||
'00:00:00.100'
|
||||
);
|
||||
});
|
||||
|
||||
it('renders average execution duration', async () => {
|
||||
const ruleTypeCustom = mockRuleType({ ruleTaskTimeout: '10m' });
|
||||
const ruleSummary = mockRuleSummary({
|
||||
executionDuration: { average: 60284, valuesWithTimestamp: {} },
|
||||
ruleTypeId: ruleMock.ruleTypeId,
|
||||
});
|
||||
|
||||
const wrapper = mountWithIntl(
|
||||
<RuleEventLogList
|
||||
fetchRuleSummary={false}
|
||||
rule={ruleMock}
|
||||
ruleType={ruleTypeCustom}
|
||||
ruleSummary={ruleSummary}
|
||||
numberOfExecutions={60}
|
||||
onChangeDuration={onChangeDurationMock}
|
||||
loadExecutionLogAggregations={loadExecutionLogAggregationsMock}
|
||||
/>
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await nextTick();
|
||||
wrapper.update();
|
||||
});
|
||||
|
||||
const avgExecutionDurationPanel = wrapper.find('[data-test-subj="avgExecutionDurationPanel"]');
|
||||
expect(avgExecutionDurationPanel.exists()).toBeTruthy();
|
||||
expect(avgExecutionDurationPanel.first().prop('color')).toEqual('subdued');
|
||||
expect(wrapper.find('EuiStat[data-test-subj="avgExecutionDurationStat"]').text()).toEqual(
|
||||
'Average duration00:01:00.284'
|
||||
);
|
||||
expect(wrapper.find('[data-test-subj="ruleDurationWarning"]').exists()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('renders warning when average execution duration exceeds rule timeout', async () => {
|
||||
const ruleTypeCustom = mockRuleType({ ruleTaskTimeout: '10m' });
|
||||
const ruleSummary = mockRuleSummary({
|
||||
executionDuration: { average: 60284345, valuesWithTimestamp: {} },
|
||||
ruleTypeId: ruleMock.ruleTypeId,
|
||||
});
|
||||
|
||||
loadExecutionLogAggregationsMock.mockResolvedValue({
|
||||
...mockLogResponse,
|
||||
total: 85,
|
||||
});
|
||||
|
||||
const wrapper = mountWithIntl(
|
||||
<RuleEventLogList
|
||||
fetchRuleSummary={false}
|
||||
rule={ruleMock}
|
||||
ruleType={ruleTypeCustom}
|
||||
ruleSummary={ruleSummary}
|
||||
numberOfExecutions={60}
|
||||
onChangeDuration={onChangeDurationMock}
|
||||
loadExecutionLogAggregations={loadExecutionLogAggregationsMock}
|
||||
/>
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await nextTick();
|
||||
wrapper.update();
|
||||
});
|
||||
|
||||
const avgExecutionDurationPanel = wrapper.find('[data-test-subj="avgExecutionDurationPanel"]');
|
||||
expect(avgExecutionDurationPanel.exists()).toBeTruthy();
|
||||
expect(avgExecutionDurationPanel.first().prop('color')).toEqual('warning');
|
||||
|
||||
const avgExecutionDurationStat = wrapper
|
||||
.find('EuiStat[data-test-subj="avgExecutionDurationStat"]')
|
||||
.text()
|
||||
.replaceAll('Info', '');
|
||||
expect(avgExecutionDurationStat).toEqual('Average duration16:44:44.345');
|
||||
expect(wrapper.find('[data-test-subj="ruleDurationWarning"]').exists()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -25,11 +25,12 @@ import { RULE_EXECUTION_DEFAULT_INITIAL_VISIBLE_COLUMNS, LOCKED_COLUMNS } from '
|
|||
import { RuleEventLogListStatusFilter } from './rule_event_log_list_status_filter';
|
||||
import { RuleEventLogDataGrid } from './rule_event_log_data_grid';
|
||||
import { CenterJustifiedSpinner } from '../../../components/center_justified_spinner';
|
||||
import { RuleExecutionSummaryAndChartWithApi } from './rule_execution_summary_and_chart';
|
||||
import { RuleActionErrorLogFlyout } from './rule_action_error_log_flyout';
|
||||
|
||||
import { RefineSearchPrompt } from '../refine_search_prompt';
|
||||
import { LoadExecutionLogAggregationsProps } from '../../../lib/rule_api';
|
||||
import { Rule } from '../../../../types';
|
||||
import { Rule, RuleSummary, RuleType } from '../../../../types';
|
||||
import {
|
||||
ComponentOpts as RuleApis,
|
||||
withBulkRuleOperations,
|
||||
|
@ -72,19 +73,51 @@ const MAX_RESULTS = 1000;
|
|||
|
||||
const ruleEventListContainerStyle = { minHeight: 400 };
|
||||
|
||||
export type RuleEventLogListProps = {
|
||||
export type RuleEventLogListOptions = 'stackManagement' | 'default';
|
||||
|
||||
export interface RuleEventLogListCommonProps {
|
||||
rule: Rule;
|
||||
ruleType: RuleType;
|
||||
localStorageKey?: string;
|
||||
refreshToken?: number;
|
||||
requestRefresh?: () => Promise<void>;
|
||||
customLoadExecutionLogAggregations?: RuleApis['loadExecutionLogAggregations'];
|
||||
} & Pick<RuleApis, 'loadExecutionLogAggregations'>;
|
||||
loadExecutionLogAggregations?: RuleApis['loadExecutionLogAggregations'];
|
||||
fetchRuleSummary?: boolean;
|
||||
}
|
||||
|
||||
export const RuleEventLogList = (props: RuleEventLogListProps) => {
|
||||
const { rule, localStorageKey = RULE_EVENT_LOG_LIST_STORAGE_KEY, refreshToken } = props;
|
||||
export interface RuleEventLogListStackManagementProps {
|
||||
ruleSummary: RuleSummary;
|
||||
onChangeDuration: (duration: number) => void;
|
||||
numberOfExecutions: number;
|
||||
isLoadingRuleSummary?: boolean;
|
||||
}
|
||||
|
||||
const loadExecutionLogAggregations =
|
||||
props.customLoadExecutionLogAggregations || props.loadExecutionLogAggregations;
|
||||
export type RuleEventLogListProps<T extends RuleEventLogListOptions = 'default'> =
|
||||
T extends 'default'
|
||||
? RuleEventLogListCommonProps
|
||||
: T extends 'stackManagement'
|
||||
? RuleEventLogListStackManagementProps & RuleEventLogListCommonProps
|
||||
: never;
|
||||
|
||||
export const RuleEventLogList = <T extends RuleEventLogListOptions>(
|
||||
props: RuleEventLogListProps<T>
|
||||
) => {
|
||||
const {
|
||||
rule,
|
||||
ruleType,
|
||||
localStorageKey = RULE_EVENT_LOG_LIST_STORAGE_KEY,
|
||||
refreshToken,
|
||||
requestRefresh,
|
||||
fetchRuleSummary = true,
|
||||
loadExecutionLogAggregations,
|
||||
} = props;
|
||||
|
||||
const {
|
||||
ruleSummary,
|
||||
numberOfExecutions,
|
||||
onChangeDuration,
|
||||
isLoadingRuleSummary = false,
|
||||
} = props as RuleEventLogListStackManagementProps;
|
||||
|
||||
const { uiSettings, notifications } = useKibana().services;
|
||||
|
||||
|
@ -144,6 +177,9 @@ export const RuleEventLogList = (props: RuleEventLogListProps) => {
|
|||
}, [sortingColumns]);
|
||||
|
||||
const loadEventLogs = async () => {
|
||||
if (!loadExecutionLogAggregations) {
|
||||
return;
|
||||
}
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const result = await loadExecutionLogAggregations({
|
||||
|
@ -300,8 +336,19 @@ export const RuleEventLogList = (props: RuleEventLogListProps) => {
|
|||
}, [localStorageKey, visibleColumns]);
|
||||
|
||||
return (
|
||||
<div style={ruleEventListContainerStyle}>
|
||||
<div style={ruleEventListContainerStyle} data-test-subj="ruleEventLogListContainer">
|
||||
<EuiSpacer />
|
||||
<RuleExecutionSummaryAndChartWithApi
|
||||
rule={rule}
|
||||
ruleType={ruleType}
|
||||
ruleSummary={ruleSummary}
|
||||
numberOfExecutions={numberOfExecutions}
|
||||
isLoadingRuleSummary={isLoadingRuleSummary}
|
||||
refreshToken={refreshToken}
|
||||
onChangeDuration={onChangeDuration}
|
||||
requestRefresh={requestRefresh}
|
||||
fetchRuleSummary={fetchRuleSummary}
|
||||
/>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFieldSearch
|
||||
|
|
|
@ -0,0 +1,171 @@
|
|||
/*
|
||||
* 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 { act } from 'react-dom/test-utils';
|
||||
import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers';
|
||||
import { ActionGroup, ALERTS_FEATURE_ID } from '@kbn/alerting-plugin/common';
|
||||
import { RuleExecutionSummaryAndChart } from './rule_execution_summary_and_chart';
|
||||
import { useKibana } from '../../../../common/lib/kibana';
|
||||
import { mockRule, mockRuleType, mockRuleSummary } from './test_helpers';
|
||||
import { RuleType } from '../../../../types';
|
||||
|
||||
const useKibanaMock = useKibana as jest.Mocked<typeof useKibana>;
|
||||
jest.mock('../../../../common/lib/kibana');
|
||||
|
||||
const loadRuleSummaryMock = jest.fn();
|
||||
|
||||
const onChangeDurationMock = jest.fn();
|
||||
|
||||
const ruleMock = mockRule();
|
||||
|
||||
const authorizedConsumers = {
|
||||
[ALERTS_FEATURE_ID]: { read: true, all: true },
|
||||
};
|
||||
|
||||
const recoveryActionGroup: ActionGroup<'recovered'> = { id: 'recovered', name: 'Recovered' };
|
||||
|
||||
const ruleType: RuleType = mockRuleType({
|
||||
producer: ALERTS_FEATURE_ID,
|
||||
authorizedConsumers,
|
||||
recoveryActionGroup,
|
||||
});
|
||||
|
||||
describe('rule_execution_summary_and_chart', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
loadRuleSummaryMock.mockResolvedValue(mockRuleSummary());
|
||||
});
|
||||
|
||||
it('becomes a stateless component when "fetchRuleSummary" is false', async () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<RuleExecutionSummaryAndChart
|
||||
rule={ruleMock}
|
||||
ruleType={ruleType}
|
||||
ruleSummary={mockRuleSummary()}
|
||||
numberOfExecutions={60}
|
||||
isLoadingRuleSummary={false}
|
||||
onChangeDuration={onChangeDurationMock}
|
||||
fetchRuleSummary={false}
|
||||
loadRuleSummary={loadRuleSummaryMock}
|
||||
/>
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await nextTick();
|
||||
wrapper.update();
|
||||
});
|
||||
|
||||
// Does not fetch for the rule summary by itself
|
||||
expect(loadRuleSummaryMock).toHaveBeenCalledTimes(0);
|
||||
|
||||
(
|
||||
wrapper
|
||||
.find('[data-test-subj="executionDurationChartPanelSelect"]')
|
||||
.first()
|
||||
.prop('onChange') as any
|
||||
)({
|
||||
target: {
|
||||
value: 30,
|
||||
},
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await nextTick();
|
||||
wrapper.update();
|
||||
});
|
||||
|
||||
// Calls the handler passed in via props
|
||||
expect(onChangeDurationMock).toHaveBeenCalledWith(30);
|
||||
|
||||
const avgExecutionDurationPanel = wrapper.find('[data-test-subj="avgExecutionDurationPanel"]');
|
||||
expect(avgExecutionDurationPanel.exists()).toBeTruthy();
|
||||
expect(avgExecutionDurationPanel.first().prop('color')).toEqual('subdued');
|
||||
expect(wrapper.find('EuiStat[data-test-subj="avgExecutionDurationStat"]').text()).toEqual(
|
||||
'Average duration00:00:00.100'
|
||||
);
|
||||
expect(wrapper.find('[data-test-subj="ruleDurationWarning"]').exists()).toBeFalsy();
|
||||
|
||||
expect(wrapper.find('[data-test-subj="executionDurationChartPanel"]').exists()).toBeTruthy();
|
||||
expect(wrapper.find('[data-test-subj="avgExecutionDurationPanel"]').exists()).toBeTruthy();
|
||||
expect(wrapper.find('[data-test-subj="ruleEventLogListAvgDuration"]').first().text()).toEqual(
|
||||
'00:00:00.100'
|
||||
);
|
||||
});
|
||||
|
||||
it('becomes a container component when "fetchRuleSummary" is true', async () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<RuleExecutionSummaryAndChart
|
||||
rule={ruleMock}
|
||||
ruleType={ruleType}
|
||||
fetchRuleSummary={true}
|
||||
loadRuleSummary={loadRuleSummaryMock}
|
||||
/>
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await nextTick();
|
||||
wrapper.update();
|
||||
});
|
||||
|
||||
// Does not fetch for the rule summary by itself
|
||||
expect(loadRuleSummaryMock).toHaveBeenCalledTimes(1);
|
||||
|
||||
(
|
||||
wrapper
|
||||
.find('[data-test-subj="executionDurationChartPanelSelect"]')
|
||||
.first()
|
||||
.prop('onChange') as any
|
||||
)({
|
||||
target: {
|
||||
value: 30,
|
||||
},
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await nextTick();
|
||||
wrapper.update();
|
||||
});
|
||||
|
||||
// Calls the handler passed in via props
|
||||
expect(onChangeDurationMock).toHaveBeenCalledTimes(0);
|
||||
|
||||
const avgExecutionDurationPanel = wrapper.find('[data-test-subj="avgExecutionDurationPanel"]');
|
||||
expect(avgExecutionDurationPanel.exists()).toBeTruthy();
|
||||
expect(avgExecutionDurationPanel.first().prop('color')).toEqual('subdued');
|
||||
expect(wrapper.find('EuiStat[data-test-subj="avgExecutionDurationStat"]').text()).toEqual(
|
||||
'Average duration00:00:00.100'
|
||||
);
|
||||
expect(wrapper.find('[data-test-subj="ruleDurationWarning"]').exists()).toBeFalsy();
|
||||
|
||||
expect(wrapper.find('[data-test-subj="executionDurationChartPanel"]').exists()).toBeTruthy();
|
||||
expect(wrapper.find('[data-test-subj="avgExecutionDurationPanel"]').exists()).toBeTruthy();
|
||||
expect(wrapper.find('[data-test-subj="ruleEventLogListAvgDuration"]').first().text()).toEqual(
|
||||
'00:00:00.100'
|
||||
);
|
||||
});
|
||||
|
||||
it('should show error if loadRuleSummary fails', async () => {
|
||||
loadRuleSummaryMock.mockRejectedValue('error!');
|
||||
|
||||
const wrapper = mountWithIntl(
|
||||
<RuleExecutionSummaryAndChart
|
||||
rule={ruleMock}
|
||||
ruleType={ruleType}
|
||||
fetchRuleSummary={true}
|
||||
loadRuleSummary={loadRuleSummaryMock}
|
||||
/>
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await nextTick();
|
||||
wrapper.update();
|
||||
});
|
||||
|
||||
expect(useKibanaMock().services.notifications.toasts.addDanger).toHaveBeenCalled();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,206 @@
|
|||
/*
|
||||
* 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, { useMemo, useState, useCallback, useEffect, useRef } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiPanel, EuiStat, EuiFlexItem, EuiFlexGroup, EuiIconTip } from '@elastic/eui';
|
||||
import { Rule, RuleSummary, RuleType } from '../../../../types';
|
||||
import { useKibana } from '../../../../common/lib/kibana';
|
||||
import { CenterJustifiedSpinner } from '../../../components/center_justified_spinner';
|
||||
import { ExecutionDurationChart } from '../../common/components/execution_duration_chart';
|
||||
import {
|
||||
formatMillisForDisplay,
|
||||
shouldShowDurationWarning,
|
||||
} from '../../../lib/execution_duration_utils';
|
||||
import {
|
||||
ComponentOpts as RuleApis,
|
||||
withBulkRuleOperations,
|
||||
} from '../../common/components/with_bulk_rule_api_operations';
|
||||
|
||||
export const DEFAULT_NUMBER_OF_EXECUTIONS = 60;
|
||||
|
||||
type RuleExecutionSummaryAndChartProps = {
|
||||
rule: Rule;
|
||||
ruleType: RuleType;
|
||||
ruleSummary?: RuleSummary;
|
||||
numberOfExecutions?: number;
|
||||
isLoadingRuleSummary?: boolean;
|
||||
refreshToken?: number;
|
||||
onChangeDuration?: (duration: number) => void;
|
||||
requestRefresh?: () => Promise<void>;
|
||||
fetchRuleSummary?: boolean;
|
||||
} & Pick<RuleApis, 'loadRuleSummary'>;
|
||||
|
||||
export const RuleExecutionSummaryAndChart = (props: RuleExecutionSummaryAndChartProps) => {
|
||||
const {
|
||||
rule,
|
||||
ruleType,
|
||||
ruleSummary,
|
||||
refreshToken,
|
||||
fetchRuleSummary = false,
|
||||
numberOfExecutions = DEFAULT_NUMBER_OF_EXECUTIONS,
|
||||
onChangeDuration,
|
||||
loadRuleSummary,
|
||||
isLoadingRuleSummary = false,
|
||||
} = props;
|
||||
|
||||
const {
|
||||
notifications: { toasts },
|
||||
} = useKibana().services;
|
||||
|
||||
const isInitialized = useRef(false);
|
||||
|
||||
const [internalRuleSummary, setInternalRuleSummary] = useState<RuleSummary | null>(null);
|
||||
const [internalNumberOfExecutions, setInternalNumberOfExecutions] = useState(
|
||||
DEFAULT_NUMBER_OF_EXECUTIONS
|
||||
);
|
||||
const [internalIsLoadingRuleSummary, setInternalIsLoadingRuleSummary] = useState(false);
|
||||
|
||||
// Computed values for the separate "mode" where this compute fetches the rule summary by itself
|
||||
const computedRuleSummary = useMemo(() => {
|
||||
if (fetchRuleSummary) {
|
||||
return internalRuleSummary;
|
||||
}
|
||||
return ruleSummary;
|
||||
}, [fetchRuleSummary, ruleSummary, internalRuleSummary]);
|
||||
|
||||
const computedNumberOfExecutions = useMemo(() => {
|
||||
if (fetchRuleSummary) {
|
||||
return internalNumberOfExecutions;
|
||||
}
|
||||
return numberOfExecutions;
|
||||
}, [fetchRuleSummary, numberOfExecutions, internalNumberOfExecutions]);
|
||||
|
||||
const computedIsLoadingRuleSummary = useMemo(() => {
|
||||
if (fetchRuleSummary) {
|
||||
return internalIsLoadingRuleSummary;
|
||||
}
|
||||
return isLoadingRuleSummary;
|
||||
}, [fetchRuleSummary, isLoadingRuleSummary, internalIsLoadingRuleSummary]);
|
||||
|
||||
// Computed duration handlers
|
||||
const internalOnChangeDuration = useCallback(
|
||||
(duration: number) => {
|
||||
setInternalNumberOfExecutions(duration);
|
||||
},
|
||||
[setInternalNumberOfExecutions]
|
||||
);
|
||||
|
||||
const computedOnChangeDuration = useMemo(() => {
|
||||
if (fetchRuleSummary) {
|
||||
return internalOnChangeDuration;
|
||||
}
|
||||
return onChangeDuration || internalOnChangeDuration;
|
||||
}, [fetchRuleSummary, onChangeDuration, internalOnChangeDuration]);
|
||||
|
||||
const getRuleSummary = async () => {
|
||||
if (!fetchRuleSummary) {
|
||||
return;
|
||||
}
|
||||
setInternalIsLoadingRuleSummary(true);
|
||||
try {
|
||||
const loadedSummary = await loadRuleSummary(rule.id, computedNumberOfExecutions);
|
||||
setInternalRuleSummary(loadedSummary);
|
||||
} catch (e) {
|
||||
toasts.addDanger({
|
||||
title: i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.ruleDetails.ruleExecutionSummaryAndChart.loadSummaryError',
|
||||
{
|
||||
defaultMessage: 'Unable to load rule summary: {message}',
|
||||
values: {
|
||||
message: e.message,
|
||||
},
|
||||
}
|
||||
),
|
||||
});
|
||||
}
|
||||
setInternalIsLoadingRuleSummary(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getRuleSummary();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [rule, computedNumberOfExecutions]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isInitialized.current) {
|
||||
getRuleSummary();
|
||||
}
|
||||
isInitialized.current = true;
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [refreshToken]);
|
||||
|
||||
const showDurationWarning = useMemo(() => {
|
||||
if (!computedRuleSummary) {
|
||||
return false;
|
||||
}
|
||||
return shouldShowDurationWarning(ruleType, computedRuleSummary.executionDuration.average);
|
||||
}, [ruleType, computedRuleSummary]);
|
||||
|
||||
if (!computedRuleSummary) {
|
||||
return <CenterJustifiedSpinner />;
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={1}>
|
||||
<EuiPanel
|
||||
data-test-subj="avgExecutionDurationPanel"
|
||||
color={showDurationWarning ? 'warning' : 'subdued'}
|
||||
hasBorder={false}
|
||||
>
|
||||
<EuiStat
|
||||
data-test-subj="avgExecutionDurationStat"
|
||||
titleSize="xs"
|
||||
title={
|
||||
<EuiFlexGroup gutterSize="xs">
|
||||
{showDurationWarning && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIconTip
|
||||
data-test-subj="ruleDurationWarning"
|
||||
anchorClassName="ruleDurationWarningIcon"
|
||||
type="alert"
|
||||
color="warning"
|
||||
content={i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.ruleDetails.alertsList.ruleTypeExcessDurationMessage',
|
||||
{
|
||||
defaultMessage: `Duration exceeds the rule's expected run time.`,
|
||||
}
|
||||
)}
|
||||
position="top"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem grow={false} data-test-subj="ruleEventLogListAvgDuration">
|
||||
{formatMillisForDisplay(computedRuleSummary.executionDuration.average)}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
description={i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.ruleDetails.alertsList.avgDurationDescription',
|
||||
{
|
||||
defaultMessage: `Average duration`,
|
||||
}
|
||||
)}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={2}>
|
||||
<ExecutionDurationChart
|
||||
executionDuration={computedRuleSummary.executionDuration}
|
||||
numberOfExecutions={computedNumberOfExecutions}
|
||||
onChangeDuration={computedOnChangeDuration}
|
||||
isLoading={computedIsLoadingRuleSummary}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export const RuleExecutionSummaryAndChartWithApi = withBulkRuleOperations(
|
||||
RuleExecutionSummaryAndChart
|
||||
);
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import uuid from 'uuid';
|
||||
import { Rule } from '../../../../types';
|
||||
import { Rule, RuleSummary, RuleType } from '../../../../types';
|
||||
|
||||
export function mockRule(overloads: Partial<Rule> = {}): Rule {
|
||||
return {
|
||||
|
@ -35,3 +35,52 @@ export function mockRule(overloads: Partial<Rule> = {}): Rule {
|
|||
...overloads,
|
||||
};
|
||||
}
|
||||
|
||||
export function mockRuleType(overloads: Partial<RuleType> = {}): RuleType {
|
||||
return {
|
||||
id: 'test.testRuleType',
|
||||
name: 'My Test Rule Type',
|
||||
actionGroups: [{ id: 'default', name: 'Default Action Group' }],
|
||||
actionVariables: {
|
||||
context: [],
|
||||
state: [],
|
||||
params: [],
|
||||
},
|
||||
defaultActionGroupId: 'default',
|
||||
recoveryActionGroup: { id: 'recovered', name: 'Recovered' },
|
||||
authorizedConsumers: {},
|
||||
producer: 'rules',
|
||||
minimumLicenseRequired: 'basic',
|
||||
enabledInLicense: true,
|
||||
...overloads,
|
||||
};
|
||||
}
|
||||
|
||||
export function mockRuleSummary(overloads: Partial<RuleSummary> = {}): RuleSummary {
|
||||
const summary: RuleSummary = {
|
||||
id: 'rule-id',
|
||||
name: 'rule-name',
|
||||
tags: ['tag-1', 'tag-2'],
|
||||
ruleTypeId: '123',
|
||||
consumer: 'rule-consumer',
|
||||
status: 'OK',
|
||||
muteAll: false,
|
||||
throttle: '',
|
||||
enabled: true,
|
||||
errorMessages: [],
|
||||
statusStartDate: '2022-03-21T07:40:46-07:00',
|
||||
statusEndDate: '2022-03-25T07:40:46-07:00',
|
||||
alerts: {
|
||||
foo: {
|
||||
status: 'OK',
|
||||
muted: false,
|
||||
actionGroupId: 'testActionGroup',
|
||||
},
|
||||
},
|
||||
executionDuration: {
|
||||
average: 100,
|
||||
valuesWithTimestamp: {},
|
||||
},
|
||||
};
|
||||
return { ...summary, ...overloads };
|
||||
}
|
||||
|
|
|
@ -56,6 +56,7 @@ export interface RulesListNotifyBadgeProps {
|
|||
snoozeRule: (schedule: SnoozeSchedule, muteAll?: boolean) => Promise<void>;
|
||||
unsnoozeRule: (scheduleIds?: string[]) => Promise<void>;
|
||||
showTooltipInline?: boolean;
|
||||
showOnHover?: boolean;
|
||||
}
|
||||
|
||||
const openSnoozePanelAriaLabel = i18n.translate(
|
||||
|
@ -93,6 +94,7 @@ export const RulesListNotifyBadge: React.FunctionComponent<RulesListNotifyBadgeP
|
|||
onRuleChanged,
|
||||
snoozeRule,
|
||||
unsnoozeRule,
|
||||
showOnHover = false,
|
||||
showTooltipInline = false,
|
||||
} = props;
|
||||
|
||||
|
@ -208,6 +210,10 @@ export const RulesListNotifyBadge: React.FunctionComponent<RulesListNotifyBadgeP
|
|||
}, [formattedSnoozeText, isLoading, isEditable, onClick]);
|
||||
|
||||
const unsnoozedButton = useMemo(() => {
|
||||
// This show on hover is needed because we need style sheets to achieve the
|
||||
// show on hover effect in the rules list. However we don't want this to be
|
||||
// a default behaviour of this component.
|
||||
const showOnHoverClass = showOnHover ? 'ruleSidebarItem__action' : '';
|
||||
return (
|
||||
<EuiButtonIcon
|
||||
size="s"
|
||||
|
@ -216,12 +222,12 @@ export const RulesListNotifyBadge: React.FunctionComponent<RulesListNotifyBadgeP
|
|||
display={isLoading ? 'base' : 'empty'}
|
||||
data-test-subj="rulesListNotifyBadge-unsnoozed"
|
||||
aria-label={openSnoozePanelAriaLabel}
|
||||
className={isOpen || isLoading ? '' : 'ruleSidebarItem__action'}
|
||||
className={isOpen || isLoading ? '' : showOnHoverClass}
|
||||
iconType="bell"
|
||||
onClick={onClick}
|
||||
/>
|
||||
);
|
||||
}, [isOpen, isLoading, isEditable, onClick]);
|
||||
}, [isOpen, isLoading, isEditable, showOnHover, onClick]);
|
||||
|
||||
const indefiniteSnoozeButton = useMemo(() => {
|
||||
return (
|
||||
|
|
|
@ -477,6 +477,7 @@ export const RulesListTable = (props: RulesListTableProps) => {
|
|||
}
|
||||
return (
|
||||
<RulesListNotifyBadge
|
||||
showOnHover
|
||||
rule={rule}
|
||||
isLoading={!!isLoadingMap[rule.id]}
|
||||
onLoading={(newIsLoading) => onLoading(rule.id, newIsLoading)}
|
||||
|
|
|
@ -7,8 +7,13 @@
|
|||
|
||||
import React from 'react';
|
||||
import { RuleEventLogList } from '../application/sections';
|
||||
import type { RuleEventLogListProps } from '../application/sections/rule_details/components/rule_event_log_list';
|
||||
import type {
|
||||
RuleEventLogListProps,
|
||||
RuleEventLogListOptions,
|
||||
} from '../application/sections/rule_details/components/rule_event_log_list';
|
||||
|
||||
export const getRuleEventLogListLazy = (props: RuleEventLogListProps) => {
|
||||
export const getRuleEventLogListLazy = <T extends RuleEventLogListOptions = 'default'>(
|
||||
props: RuleEventLogListProps<T>
|
||||
) => {
|
||||
return <RuleEventLogList {...props} />;
|
||||
};
|
||||
|
|
|
@ -24,6 +24,8 @@ import {
|
|||
FieldBrowserProps,
|
||||
RuleTagBadgeOptions,
|
||||
RuleTagBadgeProps,
|
||||
RuleEventLogListOptions,
|
||||
RuleEventLogListProps,
|
||||
} from './types';
|
||||
import { getAlertsTableLazy } from './common/get_alerts_table';
|
||||
import { getRuleStatusDropdownLazy } from './common/get_rule_status_dropdown';
|
||||
|
@ -103,8 +105,8 @@ function createStartMock(): TriggersAndActionsUIPublicPluginStart {
|
|||
getRuleTagBadge: <T extends RuleTagBadgeOptions>(props: RuleTagBadgeProps<T>) => {
|
||||
return getRuleTagBadgeLazy<T>(props);
|
||||
},
|
||||
getRuleEventLogList: (props) => {
|
||||
return getRuleEventLogListLazy(props);
|
||||
getRuleEventLogList: <T extends RuleEventLogListOptions>(props: RuleEventLogListProps<T>) => {
|
||||
return getRuleEventLogListLazy<T>(props);
|
||||
},
|
||||
getRulesListNotifyBadge: (props) => {
|
||||
return getRulesListNotifyBadgeLazy(props);
|
||||
|
|
|
@ -59,6 +59,7 @@ import type {
|
|||
RuleTagBadgeProps,
|
||||
RuleTagBadgeOptions,
|
||||
RuleEventLogListProps,
|
||||
RuleEventLogListOptions,
|
||||
RulesListProps,
|
||||
RulesListNotifyBadgeProps,
|
||||
AlertsTableConfigurationRegistry,
|
||||
|
@ -113,7 +114,9 @@ export interface TriggersAndActionsUIPublicPluginStart {
|
|||
getRuleTagBadge: <T extends RuleTagBadgeOptions>(
|
||||
props: RuleTagBadgeProps<T>
|
||||
) => ReactElement<RuleTagBadgeProps<T>>;
|
||||
getRuleEventLogList: (props: RuleEventLogListProps) => ReactElement<RuleEventLogListProps>;
|
||||
getRuleEventLogList: <T extends RuleEventLogListOptions>(
|
||||
props: RuleEventLogListProps<T>
|
||||
) => ReactElement<RuleEventLogListProps<T>>;
|
||||
getRulesList: (props: RulesListProps) => ReactElement;
|
||||
getRulesListNotifyBadge: (
|
||||
props: RulesListNotifyBadgeProps
|
||||
|
@ -330,7 +333,7 @@ export class Plugin
|
|||
getRuleTagBadge: <T extends RuleTagBadgeOptions>(props: RuleTagBadgeProps<T>) => {
|
||||
return getRuleTagBadgeLazy(props);
|
||||
},
|
||||
getRuleEventLogList: (props: RuleEventLogListProps) => {
|
||||
getRuleEventLogList: <T extends RuleEventLogListOptions>(props: RuleEventLogListProps<T>) => {
|
||||
return getRuleEventLogListLazy(props);
|
||||
},
|
||||
getRulesListNotifyBadge: (props: RulesListNotifyBadgeProps) => {
|
||||
|
|
|
@ -55,7 +55,10 @@ import type {
|
|||
RuleTagBadgeProps,
|
||||
RuleTagBadgeOptions,
|
||||
} from './application/sections/rules_list/components/rule_tag_badge';
|
||||
import type { RuleEventLogListProps } from './application/sections/rule_details/components/rule_event_log_list';
|
||||
import type {
|
||||
RuleEventLogListProps,
|
||||
RuleEventLogListOptions,
|
||||
} from './application/sections/rule_details/components/rule_event_log_list';
|
||||
import type { CreateConnectorFlyoutProps } from './application/sections/action_connector_form/create_connector_flyout';
|
||||
import type { EditConnectorFlyoutProps } from './application/sections/action_connector_form/edit_connector_flyout';
|
||||
import type { RulesListNotifyBadgeProps } from './application/sections/rules_list/components/rules_list_notify_badge';
|
||||
|
@ -105,6 +108,7 @@ export type {
|
|||
RuleTagBadgeProps,
|
||||
RuleTagBadgeOptions,
|
||||
RuleEventLogListProps,
|
||||
RuleEventLogListOptions,
|
||||
RulesListProps,
|
||||
CreateConnectorFlyoutProps,
|
||||
EditConnectorFlyoutProps,
|
||||
|
@ -367,6 +371,7 @@ export interface RuleDefinitionProps {
|
|||
ruleTypeRegistry: RuleTypeRegistryContract;
|
||||
actionTypeRegistry: ActionTypeRegistryContract;
|
||||
onEditRule: () => Promise<void>;
|
||||
hideEditButton?: boolean;
|
||||
filteredRuleTypes?: string[];
|
||||
}
|
||||
|
||||
|
|
|
@ -24,8 +24,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
});
|
||||
|
||||
it('should load from the shareable lazy loader', async () => {
|
||||
await testSubjects.find('ruleEventLogList');
|
||||
const exists = await testSubjects.exists('ruleEventLogList');
|
||||
await testSubjects.find('ruleEventLogListContainer');
|
||||
const exists = await testSubjects.exists('ruleEventLogListContainer');
|
||||
expect(exists).to.be(true);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue