mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[RAM] Update Rule Status - UI Changes (#144466)
## Summary Parent issue for updating rule status: https://github.com/elastic/kibana/issues/136039 Frontend issue: https://github.com/elastic/kibana/issues/145191 Backend PR: https://github.com/elastic/kibana/pull/140882 Updates the rules list and rules details page to support the new consolidated statuses. With E2E and unit testing. Rules list: - Table cell values - Last response filter - Table cell filtering - Status aggregations Rule details: - Rule status summary - KPI headers renaming - Event log cells renaming   ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios Co-authored-by: Xavier Mouligneau <xavier.mouligneau@elastic.co> Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
9cd5c0d2da
commit
256e1f8bd5
41 changed files with 1255 additions and 289 deletions
|
@ -40,7 +40,7 @@ const getMockRule = () => {
|
|||
error: null,
|
||||
},
|
||||
monitoring: {
|
||||
execution: {
|
||||
run: {
|
||||
history: [
|
||||
{
|
||||
success: true,
|
||||
|
|
|
@ -88,6 +88,7 @@ export const StorybookContextDecorator: React.FC<StorybookContextDecoratorProps>
|
|||
ruleTagFilter: true,
|
||||
ruleStatusFilter: true,
|
||||
rulesDetailLogs: true,
|
||||
ruleLastRunOutcome: true,
|
||||
},
|
||||
});
|
||||
return (
|
||||
|
|
|
@ -17,6 +17,7 @@ export const allowedExperimentalValues = Object.freeze({
|
|||
ruleTagFilter: true,
|
||||
ruleStatusFilter: true,
|
||||
rulesDetailLogs: true,
|
||||
ruleLastRunOutcome: true,
|
||||
});
|
||||
|
||||
type ExperimentalConfigKeys = Array<keyof ExperimentalFeatures>;
|
||||
|
|
|
@ -77,6 +77,7 @@ interface UseBulkEditSelectProps {
|
|||
actionTypesFilter?: string[];
|
||||
tagsFilter?: string[];
|
||||
ruleExecutionStatusesFilter?: string[];
|
||||
ruleLastRunOutcomesFilter?: string[];
|
||||
ruleStatusesFilter?: RuleStatus[];
|
||||
searchText?: string;
|
||||
}
|
||||
|
@ -89,6 +90,7 @@ export function useBulkEditSelect(props: UseBulkEditSelectProps) {
|
|||
actionTypesFilter,
|
||||
tagsFilter,
|
||||
ruleExecutionStatusesFilter,
|
||||
ruleLastRunOutcomesFilter,
|
||||
ruleStatusesFilter,
|
||||
searchText,
|
||||
} = props;
|
||||
|
@ -187,6 +189,7 @@ export function useBulkEditSelect(props: UseBulkEditSelectProps) {
|
|||
actionTypesFilter,
|
||||
tagsFilter,
|
||||
ruleExecutionStatusesFilter,
|
||||
ruleLastRunOutcomesFilter,
|
||||
ruleStatusesFilter,
|
||||
searchText,
|
||||
});
|
||||
|
@ -208,6 +211,7 @@ export function useBulkEditSelect(props: UseBulkEditSelectProps) {
|
|||
actionTypesFilter,
|
||||
tagsFilter,
|
||||
ruleExecutionStatusesFilter,
|
||||
ruleLastRunOutcomesFilter,
|
||||
ruleStatusesFilter,
|
||||
searchText,
|
||||
]
|
||||
|
|
|
@ -7,11 +7,21 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useState, useCallback, useMemo } from 'react';
|
||||
import { RuleExecutionStatusValues } from '@kbn/alerting-plugin/common';
|
||||
import { RuleExecutionStatusValues, RuleLastRunOutcomeValues } from '@kbn/alerting-plugin/common';
|
||||
import type { LoadRuleAggregationsProps } from '../lib/rule_api';
|
||||
import { loadRuleAggregationsWithKueryFilter } from '../lib/rule_api/aggregate_kuery_filter';
|
||||
import { useKibana } from '../../common/lib/kibana';
|
||||
|
||||
const initializeAggregationResult = (values: readonly string[]) => {
|
||||
return values.reduce<Record<string, number>>(
|
||||
(prev: Record<string, number>, status: string) => ({
|
||||
...prev,
|
||||
[status]: 0,
|
||||
}),
|
||||
{}
|
||||
);
|
||||
};
|
||||
|
||||
type UseLoadRuleAggregationsProps = Omit<LoadRuleAggregationsProps, 'http'> & {
|
||||
onError: (message: string) => void;
|
||||
};
|
||||
|
@ -21,6 +31,7 @@ export function useLoadRuleAggregations({
|
|||
typesFilter,
|
||||
actionTypesFilter,
|
||||
ruleExecutionStatusesFilter,
|
||||
ruleLastRunOutcomesFilter,
|
||||
ruleStatusesFilter,
|
||||
tagsFilter,
|
||||
onError,
|
||||
|
@ -28,15 +39,13 @@ export function useLoadRuleAggregations({
|
|||
const { http } = useKibana().services;
|
||||
|
||||
const [rulesStatusesTotal, setRulesStatusesTotal] = useState<Record<string, number>>(
|
||||
RuleExecutionStatusValues.reduce<Record<string, number>>(
|
||||
(prev: Record<string, number>, status: string) => ({
|
||||
...prev,
|
||||
[status]: 0,
|
||||
}),
|
||||
{}
|
||||
)
|
||||
initializeAggregationResult(RuleExecutionStatusValues)
|
||||
);
|
||||
|
||||
const [rulesLastRunOutcomesTotal, setRulesLastRunOutcomesTotal] = useState<
|
||||
Record<string, number>
|
||||
>(initializeAggregationResult(RuleLastRunOutcomeValues));
|
||||
|
||||
const internalLoadRuleAggregations = useCallback(async () => {
|
||||
try {
|
||||
const rulesAggs = await loadRuleAggregationsWithKueryFilter({
|
||||
|
@ -45,12 +54,16 @@ export function useLoadRuleAggregations({
|
|||
typesFilter,
|
||||
actionTypesFilter,
|
||||
ruleExecutionStatusesFilter,
|
||||
ruleLastRunOutcomesFilter,
|
||||
ruleStatusesFilter,
|
||||
tagsFilter,
|
||||
});
|
||||
if (rulesAggs?.ruleExecutionStatus) {
|
||||
setRulesStatusesTotal(rulesAggs.ruleExecutionStatus);
|
||||
}
|
||||
if (rulesAggs?.ruleLastRunOutcome) {
|
||||
setRulesLastRunOutcomesTotal(rulesAggs.ruleLastRunOutcome);
|
||||
}
|
||||
} catch (e) {
|
||||
onError(
|
||||
i18n.translate(
|
||||
|
@ -67,18 +80,28 @@ export function useLoadRuleAggregations({
|
|||
typesFilter,
|
||||
actionTypesFilter,
|
||||
ruleExecutionStatusesFilter,
|
||||
ruleLastRunOutcomesFilter,
|
||||
ruleStatusesFilter,
|
||||
tagsFilter,
|
||||
onError,
|
||||
setRulesStatusesTotal,
|
||||
setRulesLastRunOutcomesTotal,
|
||||
]);
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
loadRuleAggregations: internalLoadRuleAggregations,
|
||||
rulesStatusesTotal,
|
||||
rulesLastRunOutcomesTotal,
|
||||
setRulesStatusesTotal,
|
||||
setRulesLastRunOutcomesTotal,
|
||||
}),
|
||||
[internalLoadRuleAggregations, rulesStatusesTotal, setRulesStatusesTotal]
|
||||
[
|
||||
internalLoadRuleAggregations,
|
||||
rulesStatusesTotal,
|
||||
rulesLastRunOutcomesTotal,
|
||||
setRulesStatusesTotal,
|
||||
setRulesLastRunOutcomesTotal,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
|
@ -89,6 +89,7 @@ export function useLoadRules({
|
|||
typesFilter,
|
||||
actionTypesFilter,
|
||||
ruleExecutionStatusesFilter,
|
||||
ruleLastRunOutcomesFilter,
|
||||
ruleStatusesFilter,
|
||||
tagsFilter,
|
||||
sort,
|
||||
|
@ -120,6 +121,7 @@ export function useLoadRules({
|
|||
typesFilter,
|
||||
actionTypesFilter,
|
||||
ruleExecutionStatusesFilter,
|
||||
ruleLastRunOutcomesFilter,
|
||||
ruleStatusesFilter,
|
||||
tagsFilter,
|
||||
sort,
|
||||
|
@ -144,6 +146,7 @@ export function useLoadRules({
|
|||
hasEmptyTypesFilter &&
|
||||
isEmpty(actionTypesFilter) &&
|
||||
isEmpty(ruleExecutionStatusesFilter) &&
|
||||
isEmpty(ruleLastRunOutcomesFilter) &&
|
||||
isEmpty(ruleStatusesFilter) &&
|
||||
isEmpty(tagsFilter)
|
||||
);
|
||||
|
@ -168,6 +171,7 @@ export function useLoadRules({
|
|||
typesFilter,
|
||||
actionTypesFilter,
|
||||
ruleExecutionStatusesFilter,
|
||||
ruleLastRunOutcomesFilter,
|
||||
ruleStatusesFilter,
|
||||
tagsFilter,
|
||||
sort,
|
||||
|
|
|
@ -15,6 +15,7 @@ export interface RuleTagsAggregations {
|
|||
|
||||
export const rewriteBodyRes: RewriteRequestCase<RuleAggregations> = ({
|
||||
rule_execution_status: ruleExecutionStatus,
|
||||
rule_last_run_outcome: ruleLastRunOutcome,
|
||||
rule_enabled_status: ruleEnabledStatus,
|
||||
rule_muted_status: ruleMutedStatus,
|
||||
rule_snoozed_status: ruleSnoozedStatus,
|
||||
|
@ -26,6 +27,7 @@ export const rewriteBodyRes: RewriteRequestCase<RuleAggregations> = ({
|
|||
ruleEnabledStatus,
|
||||
ruleMutedStatus,
|
||||
ruleSnoozedStatus,
|
||||
ruleLastRunOutcome,
|
||||
ruleTags,
|
||||
});
|
||||
|
||||
|
@ -41,6 +43,7 @@ export interface LoadRuleAggregationsProps {
|
|||
typesFilter?: string[];
|
||||
actionTypesFilter?: string[];
|
||||
ruleExecutionStatusesFilter?: string[];
|
||||
ruleLastRunOutcomesFilter?: string[];
|
||||
ruleStatusesFilter?: RuleStatus[];
|
||||
tagsFilter?: string[];
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
import { RuleExecutionStatus } from '@kbn/alerting-plugin/common';
|
||||
import { AsApiContract, RewriteRequestCase } from '@kbn/actions-plugin/common';
|
||||
import { Rule, RuleAction, ResolvedRule } from '../../../types';
|
||||
import { Rule, RuleAction, ResolvedRule, RuleLastRun } from '../../../types';
|
||||
|
||||
const transformAction: RewriteRequestCase<RuleAction> = ({
|
||||
group,
|
||||
|
@ -30,6 +30,16 @@ const transformExecutionStatus: RewriteRequestCase<RuleExecutionStatus> = ({
|
|||
...rest,
|
||||
});
|
||||
|
||||
const transformLastRun: RewriteRequestCase<RuleLastRun> = ({
|
||||
outcome_msg: outcomeMsg,
|
||||
alerts_count: alertsCount,
|
||||
...rest
|
||||
}) => ({
|
||||
outcomeMsg,
|
||||
alertsCount,
|
||||
...rest,
|
||||
});
|
||||
|
||||
export const transformRule: RewriteRequestCase<Rule> = ({
|
||||
rule_type_id: ruleTypeId,
|
||||
created_by: createdBy,
|
||||
|
@ -46,6 +56,8 @@ export const transformRule: RewriteRequestCase<Rule> = ({
|
|||
snooze_schedule: snoozeSchedule,
|
||||
is_snoozed_until: isSnoozedUntil,
|
||||
active_snoozes: activeSnoozes,
|
||||
last_run: lastRun,
|
||||
next_run: nextRun,
|
||||
...rest
|
||||
}: any) => ({
|
||||
ruleTypeId,
|
||||
|
@ -65,6 +77,8 @@ export const transformRule: RewriteRequestCase<Rule> = ({
|
|||
scheduledTaskId,
|
||||
isSnoozedUntil,
|
||||
activeSnoozes,
|
||||
...(lastRun ? { lastRun: transformLastRun(lastRun) } : {}),
|
||||
...(nextRun ? { nextRun } : {}),
|
||||
...rest,
|
||||
});
|
||||
|
||||
|
|
|
@ -12,7 +12,13 @@ import { transformRule } from './common_transformations';
|
|||
|
||||
type RuleCreateBody = Omit<
|
||||
RuleUpdates,
|
||||
'createdBy' | 'updatedBy' | 'muteAll' | 'mutedInstanceIds' | 'executionStatus'
|
||||
| 'createdBy'
|
||||
| 'updatedBy'
|
||||
| 'muteAll'
|
||||
| 'mutedInstanceIds'
|
||||
| 'executionStatus'
|
||||
| 'lastRun'
|
||||
| 'nextRun'
|
||||
>;
|
||||
const rewriteBodyRequest: RewriteResponseCase<RuleCreateBody> = ({
|
||||
ruleTypeId,
|
||||
|
|
|
@ -57,6 +57,30 @@ describe('mapFiltersToKueryNode', () => {
|
|||
);
|
||||
});
|
||||
|
||||
test('should handle ruleLastRunOutcomesFilter', () => {
|
||||
expect(
|
||||
toElasticsearchQuery(
|
||||
mapFiltersToKueryNode({
|
||||
ruleLastRunOutcomesFilter: ['succeeded'],
|
||||
}) as KueryNode
|
||||
)
|
||||
).toEqual(
|
||||
toElasticsearchQuery(fromKueryExpression('alert.attributes.lastRun.outcome: succeeded'))
|
||||
);
|
||||
|
||||
expect(
|
||||
toElasticsearchQuery(
|
||||
mapFiltersToKueryNode({
|
||||
ruleLastRunOutcomesFilter: ['succeeded', 'failed', 'warning'],
|
||||
}) as KueryNode
|
||||
)
|
||||
).toEqual(
|
||||
toElasticsearchQuery(
|
||||
fromKueryExpression('alert.attributes.lastRun.outcome: (succeeded or failed or warning)')
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
test('should handle ruleStatusesFilter', () => {
|
||||
expect(
|
||||
toElasticsearchQuery(
|
||||
|
@ -260,6 +284,7 @@ describe('mapFiltersToKueryNode', () => {
|
|||
typesFilter: ['type', 'filter'],
|
||||
actionTypesFilter: ['action', 'types', 'filter'],
|
||||
ruleExecutionStatusesFilter: ['alert', 'statuses', 'filter'],
|
||||
ruleLastRunOutcomesFilter: ['warning', 'failed'],
|
||||
tagsFilter: ['a', 'b', 'c'],
|
||||
}) as KueryNode
|
||||
)
|
||||
|
@ -271,6 +296,7 @@ describe('mapFiltersToKueryNode', () => {
|
|||
alert.attributes.actions:{ actionTypeId:types } OR
|
||||
alert.attributes.actions:{ actionTypeId:filter }) and
|
||||
alert.attributes.executionStatus.status:(alert or statuses or filter) and
|
||||
alert.attributes.lastRun.outcome:(warning or failed) and
|
||||
alert.attributes.tags:(a or b or c)`
|
||||
)
|
||||
)
|
||||
|
|
|
@ -12,6 +12,7 @@ export const mapFiltersToKueryNode = ({
|
|||
typesFilter,
|
||||
actionTypesFilter,
|
||||
ruleExecutionStatusesFilter,
|
||||
ruleLastRunOutcomesFilter,
|
||||
ruleStatusesFilter,
|
||||
tagsFilter,
|
||||
searchText,
|
||||
|
@ -20,6 +21,7 @@ export const mapFiltersToKueryNode = ({
|
|||
actionTypesFilter?: string[];
|
||||
tagsFilter?: string[];
|
||||
ruleExecutionStatusesFilter?: string[];
|
||||
ruleLastRunOutcomesFilter?: string[];
|
||||
ruleStatusesFilter?: RuleStatus[];
|
||||
searchText?: string;
|
||||
}): KueryNode | null => {
|
||||
|
@ -51,6 +53,16 @@ export const mapFiltersToKueryNode = ({
|
|||
);
|
||||
}
|
||||
|
||||
if (ruleLastRunOutcomesFilter && ruleLastRunOutcomesFilter.length) {
|
||||
filterKueryNode.push(
|
||||
nodeBuilder.or(
|
||||
ruleLastRunOutcomesFilter.map((resf) =>
|
||||
nodeBuilder.is('alert.attributes.lastRun.outcome', resf)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (ruleStatusesFilter && ruleStatusesFilter.length) {
|
||||
const snoozedFilter = nodeBuilder.or([
|
||||
fromKueryExpression('alert.attributes.muteAll: true'),
|
||||
|
|
|
@ -18,6 +18,7 @@ export interface LoadRulesProps {
|
|||
actionTypesFilter?: string[];
|
||||
tagsFilter?: string[];
|
||||
ruleExecutionStatusesFilter?: string[];
|
||||
ruleLastRunOutcomesFilter?: string[];
|
||||
ruleStatusesFilter?: RuleStatus[];
|
||||
sort?: Sorting;
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ export async function loadRulesWithKueryFilter({
|
|||
typesFilter,
|
||||
actionTypesFilter,
|
||||
ruleExecutionStatusesFilter,
|
||||
ruleLastRunOutcomesFilter,
|
||||
ruleStatusesFilter,
|
||||
tagsFilter,
|
||||
sort = { field: 'name', direction: 'asc' },
|
||||
|
@ -32,6 +33,7 @@ export async function loadRulesWithKueryFilter({
|
|||
actionTypesFilter,
|
||||
tagsFilter,
|
||||
ruleExecutionStatusesFilter,
|
||||
ruleLastRunOutcomesFilter,
|
||||
ruleStatusesFilter,
|
||||
searchText,
|
||||
});
|
||||
|
|
|
@ -8,11 +8,7 @@
|
|||
import React, { lazy } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiSpacer, EuiFlexGroup, EuiFlexItem, EuiTabbedContent } from '@elastic/eui';
|
||||
import {
|
||||
ActionGroup,
|
||||
RuleExecutionStatusErrorReasons,
|
||||
AlertStatusValues,
|
||||
} from '@kbn/alerting-plugin/common';
|
||||
import { ActionGroup, AlertStatusValues } from '@kbn/alerting-plugin/common';
|
||||
import { useKibana } from '../../../../common/lib/kibana';
|
||||
import { Rule, RuleSummary, AlertStatus, RuleType } from '../../../../types';
|
||||
import {
|
||||
|
@ -20,15 +16,14 @@ import {
|
|||
withBulkRuleOperations,
|
||||
} from '../../common/components/with_bulk_rule_api_operations';
|
||||
import './rule.scss';
|
||||
import { getHealthColor } from '../../rules_list/components/rule_execution_status_filter';
|
||||
import {
|
||||
rulesStatusesTranslationsMapping,
|
||||
ALERT_STATUS_LICENSE_ERROR,
|
||||
} from '../../rules_list/translations';
|
||||
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';
|
||||
import {
|
||||
getRuleHealthColor,
|
||||
getRuleStatusMessage,
|
||||
} from '../../../../common/lib/rule_status_helpers';
|
||||
import RuleStatusPanelWithApi from './rule_status_panel';
|
||||
|
||||
const RuleEventLogList = lazy(() => import('./rule_event_log_list'));
|
||||
|
@ -78,12 +73,8 @@ export function RuleComponent({
|
|||
requestRefresh();
|
||||
};
|
||||
|
||||
const healthColor = getHealthColor(rule.executionStatus.status);
|
||||
const isLicenseError =
|
||||
rule.executionStatus.error?.reason === RuleExecutionStatusErrorReasons.License;
|
||||
const statusMessage = isLicenseError
|
||||
? ALERT_STATUS_LICENSE_ERROR
|
||||
: rulesStatusesTranslationsMapping[rule.executionStatus.status];
|
||||
const healthColor = getRuleHealthColor(rule);
|
||||
const statusMessage = getRuleStatusMessage(rule);
|
||||
|
||||
const renderRuleAlertList = () => {
|
||||
return suspendedComponentWithProps(
|
||||
|
|
|
@ -30,6 +30,7 @@ import {
|
|||
executionLogSortableColumns,
|
||||
ExecutionLogSortFields,
|
||||
} from '@kbn/alerting-plugin/common';
|
||||
import { getIsExperimentalFeatureEnabled } from '../../../../common/get_experimental_features';
|
||||
import { RuleEventLogListCellRenderer, ColumnId } from './rule_event_log_list_cell_renderer';
|
||||
import { RuleEventLogPaginationStatus } from './rule_event_log_pagination_status';
|
||||
import { RuleActionErrorBadge } from './rule_action_error_badge';
|
||||
|
@ -174,6 +175,8 @@ export const RuleEventLogDataGrid = (props: RuleEventLogDataGrid) => {
|
|||
|
||||
const { euiTheme } = useEuiTheme();
|
||||
|
||||
const isRuleLastRunOutcomeEnabled = getIsExperimentalFeatureEnabled('ruleLastRunOutcome');
|
||||
|
||||
const getPaginatedRowIndex = useCallback(
|
||||
(rowIndex: number) => {
|
||||
const { pageIndex, pageSize } = pagination;
|
||||
|
@ -621,6 +624,7 @@ export const RuleEventLogDataGrid = (props: RuleEventLogDataGrid) => {
|
|||
dateFormat={dateFormat}
|
||||
ruleId={ruleId}
|
||||
spaceIds={spaceIds}
|
||||
lastRunOutcomeEnabled={isRuleLastRunOutcomeEnabled}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -32,10 +32,19 @@ interface RuleEventLogListCellRendererProps {
|
|||
dateFormat?: string;
|
||||
ruleId?: string;
|
||||
spaceIds?: string[];
|
||||
lastRunOutcomeEnabled?: boolean;
|
||||
}
|
||||
|
||||
export const RuleEventLogListCellRenderer = (props: RuleEventLogListCellRendererProps) => {
|
||||
const { columnId, value, version, dateFormat = DEFAULT_DATE_FORMAT, ruleId, spaceIds } = props;
|
||||
const {
|
||||
columnId,
|
||||
value,
|
||||
version,
|
||||
dateFormat = DEFAULT_DATE_FORMAT,
|
||||
ruleId,
|
||||
spaceIds,
|
||||
lastRunOutcomeEnabled = false,
|
||||
} = props;
|
||||
const spacesData = useSpacesData();
|
||||
const { http } = useKibana().services;
|
||||
|
||||
|
@ -87,7 +96,12 @@ export const RuleEventLogListCellRenderer = (props: RuleEventLogListCellRenderer
|
|||
}
|
||||
|
||||
if (columnId === 'status') {
|
||||
return <RuleEventLogListStatus status={value as RuleAlertingOutcome} />;
|
||||
return (
|
||||
<RuleEventLogListStatus
|
||||
status={value as RuleAlertingOutcome}
|
||||
lastRunOutcomeEnabled={lastRunOutcomeEnabled}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (columnId === 'timestamp') {
|
||||
|
|
|
@ -11,6 +11,7 @@ import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers';
|
|||
import { loadExecutionKPIAggregations } from '../../../lib/rule_api/load_execution_kpi_aggregations';
|
||||
import { loadGlobalExecutionKPIAggregations } from '../../../lib/rule_api/load_global_execution_kpi_aggregations';
|
||||
import { RuleEventLogListKPI } from './rule_event_log_list_kpi';
|
||||
import { getIsExperimentalFeatureEnabled } from '../../../../common/get_experimental_features';
|
||||
|
||||
jest.mock('../../../../common/lib/kibana', () => ({
|
||||
useKibana: jest.fn().mockReturnValue({
|
||||
|
@ -28,6 +29,10 @@ jest.mock('../../../lib/rule_api/load_global_execution_kpi_aggregations', () =>
|
|||
loadGlobalExecutionKPIAggregations: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../../../common/get_experimental_features', () => ({
|
||||
getIsExperimentalFeatureEnabled: jest.fn(),
|
||||
}));
|
||||
|
||||
const mockKpiResponse = {
|
||||
success: 4,
|
||||
unknown: 0,
|
||||
|
@ -48,6 +53,7 @@ const loadGlobalExecutionKPIAggregationsMock =
|
|||
describe('rule_event_log_list_kpi', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
(getIsExperimentalFeatureEnabled as jest.Mock<any, any>).mockImplementation(() => false);
|
||||
loadExecutionKPIAggregationsMock.mockResolvedValue(mockKpiResponse);
|
||||
loadGlobalExecutionKPIAggregationsMock.mockResolvedValue(mockKpiResponse);
|
||||
});
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
ComponentOpts as RuleApis,
|
||||
withBulkRuleOperations,
|
||||
} from '../../common/components/with_bulk_rule_api_operations';
|
||||
import { getIsExperimentalFeatureEnabled } from '../../../../common/get_experimental_features';
|
||||
import { useKibana } from '../../../../common/lib/kibana';
|
||||
import { RuleEventLogListStatus } from './rule_event_log_list_status';
|
||||
|
||||
|
@ -104,6 +105,7 @@ export const RuleEventLogListKPI = (props: RuleEventLogListKPIProps) => {
|
|||
} = useKibana().services;
|
||||
|
||||
const isInitialized = useRef(false);
|
||||
const isRuleLastRunOutcomeEnabled = getIsExperimentalFeatureEnabled('ruleLastRunOutcome');
|
||||
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [kpi, setKpi] = useState<IExecutionKPIResult>();
|
||||
|
@ -168,7 +170,12 @@ export const RuleEventLogListKPI = (props: RuleEventLogListKPIProps) => {
|
|||
<EuiFlexItem>
|
||||
<EuiStat
|
||||
data-test-subj="ruleEventLogKpi-successOutcome"
|
||||
description={getStatDescription(<RuleEventLogListStatus status="success" />)}
|
||||
description={getStatDescription(
|
||||
<RuleEventLogListStatus
|
||||
status="success"
|
||||
lastRunOutcomeEnabled={isRuleLastRunOutcomeEnabled}
|
||||
/>
|
||||
)}
|
||||
titleSize="s"
|
||||
title={kpi?.success ?? 0}
|
||||
isLoading={isLoadingData}
|
||||
|
@ -177,7 +184,12 @@ export const RuleEventLogListKPI = (props: RuleEventLogListKPIProps) => {
|
|||
<EuiFlexItem>
|
||||
<EuiStat
|
||||
data-test-subj="ruleEventLogKpi-warningOutcome"
|
||||
description={getStatDescription(<RuleEventLogListStatus status="warning" />)}
|
||||
description={getStatDescription(
|
||||
<RuleEventLogListStatus
|
||||
status="warning"
|
||||
lastRunOutcomeEnabled={isRuleLastRunOutcomeEnabled}
|
||||
/>
|
||||
)}
|
||||
titleSize="s"
|
||||
title={kpi?.warning ?? 0}
|
||||
isLoading={isLoadingData}
|
||||
|
@ -186,7 +198,12 @@ export const RuleEventLogListKPI = (props: RuleEventLogListKPIProps) => {
|
|||
<EuiFlexItem>
|
||||
<EuiStat
|
||||
data-test-subj="ruleEventLogKpi-failureOutcome"
|
||||
description={getStatDescription(<RuleEventLogListStatus status="failure" />)}
|
||||
description={getStatDescription(
|
||||
<RuleEventLogListStatus
|
||||
status="failure"
|
||||
lastRunOutcomeEnabled={isRuleLastRunOutcomeEnabled}
|
||||
/>
|
||||
)}
|
||||
titleSize="s"
|
||||
title={kpi?.failure ?? 0}
|
||||
isLoading={isLoadingData}
|
||||
|
|
|
@ -5,12 +5,19 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { EuiIcon } from '@elastic/eui';
|
||||
import { RuleAlertingOutcome } from '@kbn/alerting-plugin/common';
|
||||
import {
|
||||
RULE_LAST_RUN_OUTCOME_SUCCEEDED,
|
||||
RULE_LAST_RUN_OUTCOME_FAILED,
|
||||
RULE_LAST_RUN_OUTCOME_WARNING,
|
||||
ALERT_STATUS_UNKNOWN,
|
||||
} from '../../rules_list/translations';
|
||||
|
||||
interface RuleEventLogListStatusProps {
|
||||
status: RuleAlertingOutcome;
|
||||
lastRunOutcomeEnabled?: boolean;
|
||||
}
|
||||
|
||||
const statusContainerStyles = {
|
||||
|
@ -30,14 +37,28 @@ const STATUS_TO_COLOR: Record<RuleAlertingOutcome, string> = {
|
|||
warning: 'warning',
|
||||
};
|
||||
|
||||
const STATUS_TO_OUTCOME: Record<RuleAlertingOutcome, string> = {
|
||||
success: RULE_LAST_RUN_OUTCOME_SUCCEEDED,
|
||||
failure: RULE_LAST_RUN_OUTCOME_FAILED,
|
||||
warning: RULE_LAST_RUN_OUTCOME_WARNING,
|
||||
unknown: ALERT_STATUS_UNKNOWN,
|
||||
};
|
||||
|
||||
export const RuleEventLogListStatus = (props: RuleEventLogListStatusProps) => {
|
||||
const { status } = props;
|
||||
const { status, lastRunOutcomeEnabled = false } = props;
|
||||
const color = STATUS_TO_COLOR[status] || 'gray';
|
||||
|
||||
const statusString = useMemo(() => {
|
||||
if (lastRunOutcomeEnabled) {
|
||||
return STATUS_TO_OUTCOME[status].toLocaleLowerCase();
|
||||
}
|
||||
return status;
|
||||
}, [lastRunOutcomeEnabled, status]);
|
||||
|
||||
return (
|
||||
<div style={statusContainerStyles}>
|
||||
<EuiIcon type="dot" color={color} style={iconStyles} />
|
||||
{status}
|
||||
{statusString}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -9,6 +9,15 @@ import React from 'react';
|
|||
import { mountWithIntl } from '@kbn/test-jest-helpers';
|
||||
import { EuiFilterButton, EuiFilterSelectItem } from '@elastic/eui';
|
||||
import { RuleEventLogListStatusFilter } from './rule_event_log_list_status_filter';
|
||||
import { getIsExperimentalFeatureEnabled } from '../../../../common/get_experimental_features';
|
||||
|
||||
jest.mock('../../../../common/get_experimental_features', () => ({
|
||||
getIsExperimentalFeatureEnabled: jest.fn(),
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
(getIsExperimentalFeatureEnabled as jest.Mock<any, any>).mockImplementation(() => false);
|
||||
});
|
||||
|
||||
const onChangeMock = jest.fn();
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import React, { useState, useCallback } from 'react';
|
|||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { RuleAlertingOutcome } from '@kbn/alerting-plugin/common';
|
||||
import { EuiFilterButton, EuiPopover, EuiFilterGroup, EuiFilterSelectItem } from '@elastic/eui';
|
||||
import { getIsExperimentalFeatureEnabled } from '../../../../common/get_experimental_features';
|
||||
import { RuleEventLogListStatus } from './rule_event_log_list_status';
|
||||
|
||||
const statusFilters: RuleAlertingOutcome[] = ['success', 'failure', 'warning', 'unknown'];
|
||||
|
@ -21,6 +22,8 @@ interface RuleEventLogListStatusFilterProps {
|
|||
export const RuleEventLogListStatusFilter = (props: RuleEventLogListStatusFilterProps) => {
|
||||
const { selectedOptions = [], onChange = () => {} } = props;
|
||||
|
||||
const isRuleLastRunOutcomeEnabled = getIsExperimentalFeatureEnabled('ruleLastRunOutcome');
|
||||
|
||||
const [isPopoverOpen, setIsPopoverOpen] = useState<boolean>(false);
|
||||
|
||||
const onFilterItemClick = useCallback(
|
||||
|
@ -68,7 +71,10 @@ export const RuleEventLogListStatusFilter = (props: RuleEventLogListStatusFilter
|
|||
onClick={onFilterItemClick(status)}
|
||||
checked={selectedOptions.includes(status) ? 'on' : undefined}
|
||||
>
|
||||
<RuleEventLogListStatus status={status} />
|
||||
<RuleEventLogListStatus
|
||||
status={status}
|
||||
lastRunOutcomeEnabled={isRuleLastRunOutcomeEnabled}
|
||||
/>
|
||||
</EuiFilterSelectItem>
|
||||
);
|
||||
})}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import datemath from '@kbn/datemath';
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import React, { useState, useEffect, useCallback, useMemo } from 'react';
|
||||
import moment from 'moment';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import {
|
||||
|
@ -32,7 +32,7 @@ export interface RuleStatusPanelProps {
|
|||
isEditable: boolean;
|
||||
requestRefresh: () => void;
|
||||
healthColor: string;
|
||||
statusMessage: string;
|
||||
statusMessage?: string | null;
|
||||
}
|
||||
|
||||
type ComponentOpts = Pick<
|
||||
|
@ -68,6 +68,20 @@ export const RuleStatusPanel: React.FC<ComponentOpts> = ({
|
|||
[rule, unsnoozeRule]
|
||||
);
|
||||
|
||||
const statusMessageDisplay = useMemo(() => {
|
||||
if (!statusMessage) {
|
||||
return (
|
||||
<EuiStat
|
||||
titleSize="xs"
|
||||
title="--"
|
||||
description=""
|
||||
isLoading={!rule.lastRun?.outcome && !rule.nextRun}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return statusMessage;
|
||||
}, [rule, statusMessage]);
|
||||
|
||||
const getLastNumberOfExecutions = useCallback(async () => {
|
||||
try {
|
||||
const result = await loadExecutionLogAggregations({
|
||||
|
@ -142,7 +156,7 @@ export const RuleStatusPanel: React.FC<ComponentOpts> = ({
|
|||
color={healthColor}
|
||||
style={{ fontWeight: 400 }}
|
||||
>
|
||||
{statusMessage}
|
||||
{statusMessageDisplay}
|
||||
</EuiHealth>
|
||||
}
|
||||
description={i18n.translate(
|
||||
|
|
|
@ -10,6 +10,7 @@ import { FormattedMessage } from '@kbn/i18n-react';
|
|||
import { EuiPopover, EuiFilterButton, EuiFilterSelectItem, EuiHealth } from '@elastic/eui';
|
||||
import { RuleExecutionStatuses, RuleExecutionStatusValues } from '@kbn/alerting-plugin/common';
|
||||
import { rulesStatusesTranslationsMapping } from '../translations';
|
||||
import { getExecutionStatusHealthColor } from '../../../../common/lib';
|
||||
|
||||
interface RuleExecutionStatusFilterProps {
|
||||
selectedStatuses: string[];
|
||||
|
@ -66,7 +67,7 @@ export const RuleExecutionStatusFilter: React.FunctionComponent<RuleExecutionSta
|
|||
>
|
||||
<div className="euiFilterSelect__items">
|
||||
{sortedRuleExecutionStatusValues.map((item: RuleExecutionStatuses) => {
|
||||
const healthColor = getHealthColor(item);
|
||||
const healthColor = getExecutionStatusHealthColor(item);
|
||||
return (
|
||||
<EuiFilterSelectItem
|
||||
key={item}
|
||||
|
@ -91,19 +92,4 @@ export const RuleExecutionStatusFilter: React.FunctionComponent<RuleExecutionSta
|
|||
);
|
||||
};
|
||||
|
||||
export function getHealthColor(status: RuleExecutionStatuses) {
|
||||
switch (status) {
|
||||
case 'active':
|
||||
return 'success';
|
||||
case 'error':
|
||||
return 'danger';
|
||||
case 'ok':
|
||||
return 'primary';
|
||||
case 'pending':
|
||||
return 'accent';
|
||||
case 'warning':
|
||||
return 'warning';
|
||||
default:
|
||||
return 'subdued';
|
||||
}
|
||||
}
|
||||
export { getExecutionStatusHealthColor as getHealthColor };
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* 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, { useCallback, useState } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { EuiPopover, EuiFilterButton, EuiFilterSelectItem, EuiHealth } from '@elastic/eui';
|
||||
import { RuleLastRunOutcomes, RuleLastRunOutcomeValues } from '@kbn/alerting-plugin/common';
|
||||
import { rulesLastRunOutcomeTranslationMapping } from '../translations';
|
||||
import { getOutcomeHealthColor } from '../../../../common/lib';
|
||||
|
||||
const sortedRuleLastRunOutcomeValues = [...RuleLastRunOutcomeValues].sort();
|
||||
|
||||
interface RuleLastRunOutcomeFilterProps {
|
||||
selectedOutcomes: string[];
|
||||
onChange?: (selectedRuleOutcomeIds: string[]) => void;
|
||||
}
|
||||
|
||||
export const RuleLastRunOutcomeFilter: React.FunctionComponent<RuleLastRunOutcomeFilterProps> = ({
|
||||
selectedOutcomes,
|
||||
onChange,
|
||||
}: RuleLastRunOutcomeFilterProps) => {
|
||||
const [isPopoverOpen, setIsPopoverOpen] = useState<boolean>(false);
|
||||
|
||||
const onTogglePopover = useCallback(() => {
|
||||
setIsPopoverOpen((prevIsPopoverOpen) => !prevIsPopoverOpen);
|
||||
}, [setIsPopoverOpen]);
|
||||
|
||||
const onClosePopover = useCallback(() => {
|
||||
setIsPopoverOpen(false);
|
||||
}, [setIsPopoverOpen]);
|
||||
|
||||
const onFilterSelectItem = useCallback(
|
||||
(filterItem: string) => () => {
|
||||
const isPreviouslyChecked = selectedOutcomes.includes(filterItem);
|
||||
if (isPreviouslyChecked) {
|
||||
onChange?.(selectedOutcomes.filter((val) => val !== filterItem));
|
||||
} else {
|
||||
onChange?.(selectedOutcomes.concat(filterItem));
|
||||
}
|
||||
},
|
||||
[onChange, selectedOutcomes]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiPopover
|
||||
isOpen={isPopoverOpen}
|
||||
closePopover={onClosePopover}
|
||||
button={
|
||||
<EuiFilterButton
|
||||
iconType="arrowDown"
|
||||
hasActiveFilters={selectedOutcomes.length > 0}
|
||||
numActiveFilters={selectedOutcomes.length}
|
||||
numFilters={selectedOutcomes.length}
|
||||
onClick={onTogglePopover}
|
||||
data-test-subj="ruleLastRunOutcomeFilterButton"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.rulesList.ruleLastRunOutcomeFilterLabel"
|
||||
defaultMessage="Last response"
|
||||
/>
|
||||
</EuiFilterButton>
|
||||
}
|
||||
>
|
||||
<div className="euiFilterSelect__items">
|
||||
{sortedRuleLastRunOutcomeValues.map((item: RuleLastRunOutcomes) => {
|
||||
const healthColor = getOutcomeHealthColor(item);
|
||||
return (
|
||||
<EuiFilterSelectItem
|
||||
key={item}
|
||||
style={{ textTransform: 'capitalize' }}
|
||||
onClick={onFilterSelectItem(item)}
|
||||
checked={selectedOutcomes.includes(item) ? 'on' : undefined}
|
||||
data-test-subj={`ruleLastRunOutcome${item}FilterOption`}
|
||||
>
|
||||
<EuiHealth color={healthColor}>
|
||||
{rulesLastRunOutcomeTranslationMapping[item]}
|
||||
</EuiHealth>
|
||||
</EuiFilterSelectItem>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</EuiPopover>
|
||||
);
|
||||
};
|
||||
|
||||
export { getOutcomeHealthColor as getHealthColor };
|
|
@ -358,6 +358,14 @@ describe('rules_list component with props', () => {
|
|||
});
|
||||
|
||||
describe('Last response filter', () => {
|
||||
beforeEach(() => {
|
||||
(getIsExperimentalFeatureEnabled as jest.Mock<any, any>).mockImplementation(() => true);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
(getIsExperimentalFeatureEnabled as jest.Mock<any, any>).mockImplementation(() => false);
|
||||
});
|
||||
|
||||
let wrapper: ReactWrapper<any>;
|
||||
async function setup(editable: boolean = true) {
|
||||
loadRulesWithKueryFilter.mockResolvedValue({
|
||||
|
@ -408,7 +416,7 @@ describe('rules_list component with props', () => {
|
|||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
useKibanaMock().services.actionTypeRegistry = actionTypeRegistry;
|
||||
wrapper = mountWithIntl(
|
||||
<RulesList lastResponseFilter={['error']} onLastResponseFilterChange={jest.fn()} />
|
||||
<RulesList lastRunOutcomeFilter={['failed']} onLastRunOutcomeFilterChange={jest.fn()} />
|
||||
);
|
||||
await act(async () => {
|
||||
await nextTick();
|
||||
|
@ -420,49 +428,48 @@ describe('rules_list component with props', () => {
|
|||
expect(loadRuleAggregationsWithKueryFilter).toHaveBeenCalled();
|
||||
}
|
||||
it('can filter by last response', async () => {
|
||||
(getIsExperimentalFeatureEnabled as jest.Mock<any, any>).mockImplementation(() => true);
|
||||
loadRulesWithKueryFilter.mockReset();
|
||||
await setup();
|
||||
|
||||
expect(loadRulesWithKueryFilter).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
ruleExecutionStatusesFilter: ['error'],
|
||||
ruleLastRunOutcomesFilter: ['failed'],
|
||||
})
|
||||
);
|
||||
|
||||
wrapper.find('[data-test-subj="ruleExecutionStatusFilterButton"] button').simulate('click');
|
||||
wrapper.find('[data-test-subj="ruleLastRunOutcomeFilterButton"] button').simulate('click');
|
||||
|
||||
wrapper
|
||||
.find('[data-test-subj="ruleExecutionStatusactiveFilterOption"]')
|
||||
.find('[data-test-subj="ruleLastRunOutcomesucceededFilterOption"]')
|
||||
.first()
|
||||
.simulate('click');
|
||||
|
||||
expect(loadRulesWithKueryFilter).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
ruleExecutionStatusesFilter: ['error', 'active'],
|
||||
ruleLastRunOutcomesFilter: ['failed', 'succeeded'],
|
||||
})
|
||||
);
|
||||
|
||||
expect(wrapper.prop('onLastResponseFilterChange')).toHaveBeenCalled();
|
||||
expect(wrapper.prop('onLastResponseFilterChange')).toHaveBeenLastCalledWith([
|
||||
'error',
|
||||
'active',
|
||||
expect(wrapper.prop('onLastRunOutcomeFilterChange')).toHaveBeenCalled();
|
||||
expect(wrapper.prop('onLastRunOutcomeFilterChange')).toHaveBeenLastCalledWith([
|
||||
'failed',
|
||||
'succeeded',
|
||||
]);
|
||||
|
||||
wrapper.find('[data-test-subj="ruleExecutionStatusFilterButton"] button').simulate('click');
|
||||
wrapper.find('[data-test-subj="ruleLastRunOutcomeFilterButton"] button').simulate('click');
|
||||
wrapper
|
||||
.find('[data-test-subj="ruleExecutionStatuserrorFilterOption"]')
|
||||
.find('[data-test-subj="ruleLastRunOutcomefailedFilterOption"]')
|
||||
.first()
|
||||
.simulate('click');
|
||||
|
||||
expect(loadRulesWithKueryFilter).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
ruleExecutionStatusesFilter: ['active'],
|
||||
ruleLastRunOutcomesFilter: ['succeeded'],
|
||||
})
|
||||
);
|
||||
|
||||
expect(wrapper.prop('onLastResponseFilterChange')).toHaveBeenCalled();
|
||||
expect(wrapper.prop('onLastResponseFilterChange')).toHaveBeenLastCalledWith(['active']);
|
||||
expect(wrapper.prop('onLastRunOutcomeFilterChange')).toHaveBeenCalled();
|
||||
expect(wrapper.prop('onLastRunOutcomeFilterChange')).toHaveBeenLastCalledWith(['succeeded']);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -844,6 +851,11 @@ describe('rules_list component with items', () => {
|
|||
loadRuleTypes.mockResolvedValue([ruleTypeFromApi]);
|
||||
loadAllActions.mockResolvedValue([]);
|
||||
loadRuleAggregationsWithKueryFilter.mockResolvedValue({
|
||||
ruleLastRunOutcome: {
|
||||
succeeded: 3,
|
||||
failed: 3,
|
||||
warning: 6,
|
||||
},
|
||||
ruleEnabledStatus: { enabled: 2, disabled: 0 },
|
||||
ruleExecutionStatus: { ok: 1, active: 2, error: 3, pending: 4, unknown: 5, warning: 6 },
|
||||
ruleMutedStatus: { muted: 0, unmuted: 2 },
|
||||
|
@ -1005,11 +1017,9 @@ describe('rules_list component with items', () => {
|
|||
expect(
|
||||
wrapper.find('EuiTableRowCell[data-test-subj="rulesTableCell-lastResponse"]').length
|
||||
).toEqual(mockedRulesData.length);
|
||||
expect(wrapper.find('EuiHealth[data-test-subj="ruleStatus-active"]').length).toEqual(1);
|
||||
expect(wrapper.find('EuiHealth[data-test-subj="ruleStatus-ok"]').length).toEqual(1);
|
||||
expect(wrapper.find('EuiHealth[data-test-subj="ruleStatus-pending"]').length).toEqual(1);
|
||||
expect(wrapper.find('EuiHealth[data-test-subj="ruleStatus-unknown"]').length).toEqual(0);
|
||||
expect(wrapper.find('EuiHealth[data-test-subj="ruleStatus-error"]').length).toEqual(2);
|
||||
|
||||
expect(wrapper.find('EuiHealth[data-test-subj="ruleStatus-succeeded"]').length).toEqual(2);
|
||||
expect(wrapper.find('EuiHealth[data-test-subj="ruleStatus-failed"]').length).toEqual(2);
|
||||
expect(wrapper.find('EuiHealth[data-test-subj="ruleStatus-warning"]').length).toEqual(1);
|
||||
expect(wrapper.find('[data-test-subj="ruleStatus-error-tooltip"]').length).toEqual(2);
|
||||
expect(
|
||||
|
@ -1018,10 +1028,10 @@ describe('rules_list component with items', () => {
|
|||
|
||||
expect(wrapper.find('[data-test-subj="rulesListAutoRefresh"]').exists()).toBeTruthy();
|
||||
|
||||
expect(wrapper.find('EuiHealth[data-test-subj="ruleStatus-error"]').first().text()).toEqual(
|
||||
expect(wrapper.find('EuiHealth[data-test-subj="ruleStatus-failed"]').first().text()).toEqual(
|
||||
'Error'
|
||||
);
|
||||
expect(wrapper.find('EuiHealth[data-test-subj="ruleStatus-error"]').last().text()).toEqual(
|
||||
expect(wrapper.find('EuiHealth[data-test-subj="ruleStatus-failed"]').last().text()).toEqual(
|
||||
'License Error'
|
||||
);
|
||||
});
|
||||
|
@ -1042,7 +1052,7 @@ describe('rules_list component with items', () => {
|
|||
mockedRulesData.forEach((rule, index) => {
|
||||
if (rule.monitoring) {
|
||||
expect(ratios.at(index).text()).toEqual(
|
||||
`${rule.monitoring.execution.calculated_metrics.success_ratio * 100}%`
|
||||
`${rule.monitoring.run.calculated_metrics.success_ratio * 100}%`
|
||||
);
|
||||
} else {
|
||||
expect(ratios.at(index).text()).toEqual(`N/A`);
|
||||
|
@ -1060,10 +1070,10 @@ describe('rules_list component with items', () => {
|
|||
);
|
||||
|
||||
mockedRulesData.forEach((rule, index) => {
|
||||
if (typeof rule.monitoring?.execution.calculated_metrics.p50 === 'number') {
|
||||
if (typeof rule.monitoring?.run.calculated_metrics.p50 === 'number') {
|
||||
// Ensure the table cells are getting the correct values
|
||||
expect(percentiles.at(index).text()).toEqual(
|
||||
getFormattedDuration(rule.monitoring.execution.calculated_metrics.p50)
|
||||
getFormattedDuration(rule.monitoring.run.calculated_metrics.p50)
|
||||
);
|
||||
// Ensure the tooltip is showing the correct content
|
||||
expect(
|
||||
|
@ -1073,7 +1083,7 @@ describe('rules_list component with items', () => {
|
|||
)
|
||||
.at(index)
|
||||
.props().content
|
||||
).toEqual(getFormattedMilliseconds(rule.monitoring.execution.calculated_metrics.p50));
|
||||
).toEqual(getFormattedMilliseconds(rule.monitoring.run.calculated_metrics.p50));
|
||||
} else {
|
||||
expect(percentiles.at(index).text()).toEqual('N/A');
|
||||
}
|
||||
|
@ -1149,9 +1159,9 @@ describe('rules_list component with items', () => {
|
|||
);
|
||||
|
||||
mockedRulesData.forEach((rule, index) => {
|
||||
if (typeof rule.monitoring?.execution.calculated_metrics.p95 === 'number') {
|
||||
if (typeof rule.monitoring?.run.calculated_metrics.p95 === 'number') {
|
||||
expect(percentiles.at(index).text()).toEqual(
|
||||
getFormattedDuration(rule.monitoring.execution.calculated_metrics.p95)
|
||||
getFormattedDuration(rule.monitoring.run.calculated_metrics.p95)
|
||||
);
|
||||
} else {
|
||||
expect(percentiles.at(index).text()).toEqual('N/A');
|
||||
|
@ -1270,21 +1280,19 @@ describe('rules_list component with items', () => {
|
|||
});
|
||||
|
||||
it('renders brief', async () => {
|
||||
(getIsExperimentalFeatureEnabled as jest.Mock<any, any>).mockImplementation(() => true);
|
||||
await setup();
|
||||
|
||||
// { ok: 1, active: 2, error: 3, pending: 4, unknown: 5, warning: 6 }
|
||||
expect(wrapper.find('EuiHealth[data-test-subj="totalOkRulesCount"]').text()).toEqual('Ok: 1');
|
||||
expect(wrapper.find('EuiHealth[data-test-subj="totalActiveRulesCount"]').text()).toEqual(
|
||||
'Active: 2'
|
||||
// ruleLastRunOutcome: {
|
||||
// succeeded: 3,
|
||||
// failed: 3,
|
||||
// warning: 6,
|
||||
// }
|
||||
expect(wrapper.find('EuiHealth[data-test-subj="totalSucceededRulesCount"]').text()).toEqual(
|
||||
'Succeeded: 3'
|
||||
);
|
||||
expect(wrapper.find('EuiHealth[data-test-subj="totalErrorRulesCount"]').text()).toEqual(
|
||||
'Error: 3'
|
||||
);
|
||||
expect(wrapper.find('EuiHealth[data-test-subj="totalPendingRulesCount"]').text()).toEqual(
|
||||
'Pending: 4'
|
||||
);
|
||||
expect(wrapper.find('EuiHealth[data-test-subj="totalUnknownRulesCount"]').text()).toEqual(
|
||||
'Unknown: 5'
|
||||
expect(wrapper.find('EuiHealth[data-test-subj="totalFailedRulesCount"]').text()).toEqual(
|
||||
'Failed: 3'
|
||||
);
|
||||
expect(wrapper.find('EuiHealth[data-test-subj="totalWarningRulesCount"]').text()).toEqual(
|
||||
'Warning: 6'
|
||||
|
|
|
@ -22,13 +22,10 @@ import {
|
|||
EuiSpacer,
|
||||
EuiLink,
|
||||
EuiEmptyPrompt,
|
||||
EuiHealth,
|
||||
EuiTableSortingType,
|
||||
EuiButtonIcon,
|
||||
EuiSelectableOption,
|
||||
EuiIcon,
|
||||
EuiDescriptionList,
|
||||
EuiCallOut,
|
||||
} from '@elastic/eui';
|
||||
import { EuiSelectableOptionCheckedType } from '@elastic/eui/src/components/selectable/selectable_option';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
@ -37,6 +34,7 @@ import {
|
|||
RuleExecutionStatus,
|
||||
ALERTS_FEATURE_ID,
|
||||
RuleExecutionStatusErrorReasons,
|
||||
RuleLastRunOutcomeValues,
|
||||
} from '@kbn/alerting-plugin/common';
|
||||
import { AlertingConnectorFeatureId } from '@kbn/actions-plugin/common';
|
||||
import { ruleDetailsRoute as commonRuleDetailsRoute } from '@kbn/rule-data-utils';
|
||||
|
@ -56,9 +54,12 @@ import { RuleAdd, RuleEdit } from '../../rule_form';
|
|||
import { BulkOperationPopover } from '../../common/components/bulk_operation_popover';
|
||||
import { RuleQuickEditButtonsWithApi as RuleQuickEditButtons } from '../../common/components/rule_quick_edit_buttons';
|
||||
import { CollapsedItemActionsWithApi as CollapsedItemActions } from './collapsed_item_actions';
|
||||
import { RulesListStatuses } from './rules_list_statuses';
|
||||
import { TypeFilter } from './type_filter';
|
||||
import { ActionTypeFilter } from './action_type_filter';
|
||||
import { RuleExecutionStatusFilter } from './rule_execution_status_filter';
|
||||
import { RuleLastRunOutcomeFilter } from './rule_last_run_outcome_filter';
|
||||
import { RulesListErrorBanner } from './rules_list_error_banner';
|
||||
import {
|
||||
loadRuleTypes,
|
||||
disableRule,
|
||||
|
@ -118,6 +119,8 @@ export interface RulesListProps {
|
|||
onStatusFilterChange?: (status: RuleStatus[]) => RulesPageContainerState;
|
||||
lastResponseFilter?: string[];
|
||||
onLastResponseFilterChange?: (lastResponse: string[]) => RulesPageContainerState;
|
||||
lastRunOutcomeFilter?: string[];
|
||||
onLastRunOutcomeFilterChange?: (lastRunOutcome: string[]) => RulesPageContainerState;
|
||||
refresh?: Date;
|
||||
rulesListKey?: string;
|
||||
visibleColumns?: RulesListVisibleColumns[];
|
||||
|
@ -130,9 +133,9 @@ interface RuleTypeState {
|
|||
}
|
||||
|
||||
export const percentileFields = {
|
||||
[Percentiles.P50]: 'monitoring.execution.calculated_metrics.p50',
|
||||
[Percentiles.P95]: 'monitoring.execution.calculated_metrics.p95',
|
||||
[Percentiles.P99]: 'monitoring.execution.calculated_metrics.p99',
|
||||
[Percentiles.P50]: 'monitoring.run.calculated_metrics.p50',
|
||||
[Percentiles.P95]: 'monitoring.run.calculated_metrics.p95',
|
||||
[Percentiles.P99]: 'monitoring.run.calculated_metrics.p99',
|
||||
};
|
||||
|
||||
const initialPercentileOptions = Object.values(Percentiles).map((percentile) => ({
|
||||
|
@ -150,6 +153,8 @@ export const RulesList = ({
|
|||
onStatusFilterChange,
|
||||
lastResponseFilter,
|
||||
onLastResponseFilterChange,
|
||||
lastRunOutcomeFilter,
|
||||
onLastRunOutcomeFilterChange,
|
||||
refresh,
|
||||
rulesListKey,
|
||||
visibleColumns,
|
||||
|
@ -176,6 +181,9 @@ export const RulesList = ({
|
|||
const [ruleExecutionStatusesFilter, setRuleExecutionStatusesFilter] = useState<string[]>(
|
||||
lastResponseFilter || []
|
||||
);
|
||||
const [ruleLastRunOutcomesFilter, setRuleLastRunOutcomesFilter] = useState<string[]>(
|
||||
lastRunOutcomeFilter || []
|
||||
);
|
||||
const [ruleStatusesFilter, setRuleStatusesFilter] = useState<RuleStatus[]>(statusFilter || []);
|
||||
|
||||
const [tagsFilter, setTagsFilter] = useState<string[]>([]);
|
||||
|
@ -190,6 +198,7 @@ export const RulesList = ({
|
|||
|
||||
const isRuleTagFilterEnabled = getIsExperimentalFeatureEnabled('ruleTagFilter');
|
||||
const isRuleStatusFilterEnabled = getIsExperimentalFeatureEnabled('ruleStatusFilter');
|
||||
const isRuleLastRunOutcomeEnabled = getIsExperimentalFeatureEnabled('ruleLastRunOutcome');
|
||||
|
||||
const cloneRuleId = useRef<null | string>(null);
|
||||
|
||||
|
@ -282,6 +291,7 @@ export const RulesList = ({
|
|||
typesFilter: rulesTypesFilter,
|
||||
actionTypesFilter,
|
||||
ruleExecutionStatusesFilter,
|
||||
ruleLastRunOutcomesFilter,
|
||||
ruleStatusesFilter,
|
||||
tagsFilter,
|
||||
sort,
|
||||
|
@ -294,15 +304,17 @@ export const RulesList = ({
|
|||
onError,
|
||||
});
|
||||
|
||||
const { loadRuleAggregations, rulesStatusesTotal } = useLoadRuleAggregations({
|
||||
searchText,
|
||||
typesFilter,
|
||||
actionTypesFilter,
|
||||
ruleExecutionStatusesFilter,
|
||||
ruleStatusesFilter,
|
||||
tagsFilter,
|
||||
onError,
|
||||
});
|
||||
const { loadRuleAggregations, rulesStatusesTotal, rulesLastRunOutcomesTotal } =
|
||||
useLoadRuleAggregations({
|
||||
searchText,
|
||||
typesFilter: rulesTypesFilter,
|
||||
actionTypesFilter,
|
||||
ruleExecutionStatusesFilter,
|
||||
ruleLastRunOutcomesFilter,
|
||||
ruleStatusesFilter,
|
||||
tagsFilter,
|
||||
onError,
|
||||
});
|
||||
|
||||
const onRuleEdit = (ruleItem: RuleTableItem) => {
|
||||
setEditFlyoutVisibility(true);
|
||||
|
@ -419,12 +431,24 @@ export const RulesList = ({
|
|||
}
|
||||
}, [lastResponseFilter]);
|
||||
|
||||
useEffect(() => {
|
||||
if (lastRunOutcomeFilter) {
|
||||
setRuleLastRunOutcomesFilter(lastRunOutcomeFilter);
|
||||
}
|
||||
}, [lastResponseFilter]);
|
||||
|
||||
useEffect(() => {
|
||||
if (onLastResponseFilterChange) {
|
||||
onLastResponseFilterChange(ruleExecutionStatusesFilter);
|
||||
}
|
||||
}, [ruleExecutionStatusesFilter]);
|
||||
|
||||
useEffect(() => {
|
||||
if (onLastRunOutcomeFilterChange) {
|
||||
onLastRunOutcomeFilterChange(ruleLastRunOutcomesFilter);
|
||||
}
|
||||
}, [ruleLastRunOutcomesFilter]);
|
||||
|
||||
// Clear bulk selection anytime the filters change
|
||||
useEffect(() => {
|
||||
onClearSelection();
|
||||
|
@ -433,6 +457,7 @@ export const RulesList = ({
|
|||
rulesTypesFilter,
|
||||
actionTypesFilter,
|
||||
ruleExecutionStatusesFilter,
|
||||
ruleLastRunOutcomesFilter,
|
||||
ruleStatusesFilter,
|
||||
tagsFilter,
|
||||
hasDefaultRuleTypesFiltersOn,
|
||||
|
@ -493,7 +518,11 @@ export const RulesList = ({
|
|||
setShowErrors((prevValue) => {
|
||||
if (!prevValue) {
|
||||
const rulesToExpand = rulesState.data.reduce((acc, ruleItem) => {
|
||||
if (ruleItem.executionStatus.status === 'error') {
|
||||
// Check both outcome and executionStatus for now until we deprecate executionStatus
|
||||
if (
|
||||
ruleItem.lastRun?.outcome === RuleLastRunOutcomeValues[2] ||
|
||||
ruleItem.executionStatus.status === 'error'
|
||||
) {
|
||||
return {
|
||||
...acc,
|
||||
[ruleItem.id]: (
|
||||
|
@ -556,6 +585,25 @@ export const RulesList = ({
|
|||
return null;
|
||||
};
|
||||
|
||||
const getRuleOutcomeOrStatusFilter = () => {
|
||||
if (isRuleLastRunOutcomeEnabled) {
|
||||
return [
|
||||
<RuleLastRunOutcomeFilter
|
||||
key="rule-last-run-outcome-filter"
|
||||
selectedOutcomes={ruleLastRunOutcomesFilter}
|
||||
onChange={setRuleLastRunOutcomesFilter}
|
||||
/>,
|
||||
];
|
||||
}
|
||||
return [
|
||||
<RuleExecutionStatusFilter
|
||||
key="rule-status-filter"
|
||||
selectedStatuses={ruleExecutionStatusesFilter}
|
||||
onChange={setRuleExecutionStatusesFilter}
|
||||
/>,
|
||||
];
|
||||
};
|
||||
|
||||
const onDisableRule = (rule: RuleTableItem) => {
|
||||
return disableRule({ http, id: rule.id });
|
||||
};
|
||||
|
@ -599,11 +647,7 @@ export const RulesList = ({
|
|||
filters={typesFilter}
|
||||
/>
|
||||
),
|
||||
<RuleExecutionStatusFilter
|
||||
key="rule-status-filter"
|
||||
selectedStatuses={ruleExecutionStatusesFilter}
|
||||
onChange={setRuleExecutionStatusesFilter}
|
||||
/>,
|
||||
...getRuleOutcomeOrStatusFilter(),
|
||||
...getRuleTagFilter(),
|
||||
];
|
||||
|
||||
|
@ -625,6 +669,7 @@ export const RulesList = ({
|
|||
typesFilter: rulesTypesFilter,
|
||||
actionTypesFilter,
|
||||
ruleExecutionStatusesFilter,
|
||||
ruleLastRunOutcomesFilter,
|
||||
ruleStatusesFilter,
|
||||
tagsFilter,
|
||||
});
|
||||
|
@ -716,34 +761,11 @@ export const RulesList = ({
|
|||
|
||||
const table = (
|
||||
<>
|
||||
{rulesStatusesTotal.error > 0 ? (
|
||||
<>
|
||||
<EuiCallOut color="danger" size="s" data-test-subj="rulesErrorBanner">
|
||||
<p>
|
||||
<EuiIcon color="danger" type="alert" />
|
||||
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.rulesList.attentionBannerTitle"
|
||||
defaultMessage="Error found in {totalStatusesError, plural, one {# rule} other {# rules}}."
|
||||
values={{
|
||||
totalStatusesError: rulesStatusesTotal.error,
|
||||
}}
|
||||
/>
|
||||
|
||||
<EuiLink color="primary" onClick={() => setRuleExecutionStatusesFilter(['error'])}>
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.rulesList.viewBannerButtonLabel"
|
||||
defaultMessage="Show {totalStatusesError, plural, one {rule} other {rules}} with error"
|
||||
values={{
|
||||
totalStatusesError: rulesStatusesTotal.error,
|
||||
}}
|
||||
/>
|
||||
</EuiLink>
|
||||
</p>
|
||||
</EuiCallOut>
|
||||
<EuiSpacer size="s" />
|
||||
</>
|
||||
) : null}
|
||||
<RulesListErrorBanner
|
||||
rulesLastRunOutcomes={rulesLastRunOutcomesTotal}
|
||||
setRuleExecutionStatusesFilter={setRuleExecutionStatusesFilter}
|
||||
setRuleLastRunOutcomesFilter={setRuleLastRunOutcomesFilter}
|
||||
/>
|
||||
<EuiFlexGroup gutterSize="s">
|
||||
{authorizedToCreateAnyRules && showCreateRuleButton ? (
|
||||
<EuiFlexItem grow={false}>
|
||||
|
@ -813,68 +835,10 @@ export const RulesList = ({
|
|||
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween">
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="none">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiHealth color="success" data-test-subj="totalActiveRulesCount">
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.rulesList.totalStatusesActiveDescription"
|
||||
defaultMessage="Active: {totalStatusesActive}"
|
||||
values={{
|
||||
totalStatusesActive: rulesStatusesTotal.active,
|
||||
}}
|
||||
/>
|
||||
</EuiHealth>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiHealth color="danger" data-test-subj="totalErrorRulesCount">
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.rulesList.totalStatusesErrorDescription"
|
||||
defaultMessage="Error: {totalStatusesError}"
|
||||
values={{ totalStatusesError: rulesStatusesTotal.error }}
|
||||
/>
|
||||
</EuiHealth>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiHealth color="warning" data-test-subj="totalWarningRulesCount">
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.rulesList.totalStatusesWarningDescription"
|
||||
defaultMessage="Warning: {totalStatusesWarning}"
|
||||
values={{
|
||||
totalStatusesWarning: rulesStatusesTotal.warning,
|
||||
}}
|
||||
/>
|
||||
</EuiHealth>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiHealth color="primary" data-test-subj="totalOkRulesCount">
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.rulesList.totalStatusesOkDescription"
|
||||
defaultMessage="Ok: {totalStatusesOk}"
|
||||
values={{ totalStatusesOk: rulesStatusesTotal.ok }}
|
||||
/>
|
||||
</EuiHealth>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiHealth color="accent" data-test-subj="totalPendingRulesCount">
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.rulesList.totalStatusesPendingDescription"
|
||||
defaultMessage="Pending: {totalStatusesPending}"
|
||||
values={{
|
||||
totalStatusesPending: rulesStatusesTotal.pending,
|
||||
}}
|
||||
/>
|
||||
</EuiHealth>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiHealth color="subdued" data-test-subj="totalUnknownRulesCount">
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.rulesList.totalStatusesUnknownDescription"
|
||||
defaultMessage="Unknown: {totalStatusesUnknown}"
|
||||
values={{
|
||||
totalStatusesUnknown: rulesStatusesTotal.unknown,
|
||||
}}
|
||||
/>
|
||||
</EuiHealth>
|
||||
</EuiFlexItem>
|
||||
<RulesListStatuses
|
||||
rulesStatuses={rulesStatusesTotal}
|
||||
rulesLastRunOutcomes={rulesLastRunOutcomesTotal}
|
||||
/>
|
||||
<RulesListAutoRefresh lastUpdate={lastUpdate} onRefresh={refreshRules} />
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -36,7 +36,8 @@ export type RulesListVisibleColumns =
|
|||
| 'ruleExecutionPercentile'
|
||||
| 'ruleExecutionSuccessRatio'
|
||||
| 'ruleExecutionStatus'
|
||||
| 'ruleExecutionState';
|
||||
| 'ruleExecutionState'
|
||||
| 'ruleLastRunOutcome';
|
||||
|
||||
const OriginalRulesListVisibleColumns: RulesListVisibleColumns[] = [
|
||||
'ruleName',
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* 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 { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { EuiCallOut, EuiIcon, EuiLink, EuiSpacer } from '@elastic/eui';
|
||||
import { getIsExperimentalFeatureEnabled } from '../../../../common/get_experimental_features';
|
||||
|
||||
interface RulesListErrorBannerProps {
|
||||
rulesLastRunOutcomes: Record<string, number>;
|
||||
setRuleExecutionStatusesFilter: (statuses: string[]) => void;
|
||||
setRuleLastRunOutcomesFilter: (outcomes: string[]) => void;
|
||||
}
|
||||
|
||||
export const RulesListErrorBanner = (props: RulesListErrorBannerProps) => {
|
||||
const { rulesLastRunOutcomes, setRuleExecutionStatusesFilter, setRuleLastRunOutcomesFilter } =
|
||||
props;
|
||||
|
||||
const onClick = () => {
|
||||
const isRuleLastRunOutcomeEnabled = getIsExperimentalFeatureEnabled('ruleLastRunOutcome');
|
||||
if (isRuleLastRunOutcomeEnabled) {
|
||||
setRuleLastRunOutcomesFilter(['failed']);
|
||||
} else {
|
||||
setRuleExecutionStatusesFilter(['error']);
|
||||
}
|
||||
};
|
||||
|
||||
if (rulesLastRunOutcomes.failed === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiCallOut color="danger" size="s" data-test-subj="rulesErrorBanner">
|
||||
<p>
|
||||
<EuiIcon color="danger" type="alert" />
|
||||
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.rulesList.attentionBannerTitle"
|
||||
defaultMessage="Error found in {totalStatusesError, plural, one {# rule} other {# rules}}."
|
||||
values={{
|
||||
totalStatusesError: rulesLastRunOutcomes.failed,
|
||||
}}
|
||||
/>
|
||||
|
||||
<EuiLink color="primary" onClick={onClick}>
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.rulesList.viewBannerButtonLabel"
|
||||
defaultMessage="Show {totalStatusesError, plural, one {rule} other {rules}} with error"
|
||||
values={{
|
||||
totalStatusesError: rulesLastRunOutcomes.failed,
|
||||
}}
|
||||
/>
|
||||
</EuiLink>
|
||||
</p>
|
||||
</EuiCallOut>
|
||||
<EuiSpacer size="s" />
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* 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 { EuiFlexGroup, EuiFlexItem, EuiHealth } from '@elastic/eui';
|
||||
import { getIsExperimentalFeatureEnabled } from '../../../../common/get_experimental_features';
|
||||
|
||||
import {
|
||||
RULE_STATUS_ACTIVE,
|
||||
RULE_STATUS_ERROR,
|
||||
RULE_STATUS_WARNING,
|
||||
RULE_STATUS_OK,
|
||||
RULE_STATUS_PENDING,
|
||||
RULE_STATUS_UNKNOWN,
|
||||
RULE_LAST_RUN_OUTCOME_SUCCEEDED_DESCRIPTION,
|
||||
RULE_LAST_RUN_OUTCOME_WARNING_DESCRIPTION,
|
||||
RULE_LAST_RUN_OUTCOME_FAILED_DESCRIPTION,
|
||||
} from '../translations';
|
||||
|
||||
interface RulesListStatusesProps {
|
||||
rulesStatuses: Record<string, number>;
|
||||
rulesLastRunOutcomes: Record<string, number>;
|
||||
}
|
||||
|
||||
export const RulesListStatuses = (props: RulesListStatusesProps) => {
|
||||
const { rulesStatuses, rulesLastRunOutcomes } = props;
|
||||
|
||||
const isRuleLastRunOutcomeEnabled = getIsExperimentalFeatureEnabled('ruleLastRunOutcome');
|
||||
|
||||
if (isRuleLastRunOutcomeEnabled) {
|
||||
return (
|
||||
<EuiFlexGroup alignItems="center" gutterSize="none">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiHealth color="success" data-test-subj="totalSucceededRulesCount">
|
||||
{RULE_LAST_RUN_OUTCOME_SUCCEEDED_DESCRIPTION(rulesLastRunOutcomes.succeeded)}
|
||||
</EuiHealth>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiHealth color="danger" data-test-subj="totalFailedRulesCount">
|
||||
{RULE_LAST_RUN_OUTCOME_FAILED_DESCRIPTION(rulesLastRunOutcomes.failed)}
|
||||
</EuiHealth>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiHealth color="warning" data-test-subj="totalWarningRulesCount">
|
||||
{RULE_LAST_RUN_OUTCOME_WARNING_DESCRIPTION(rulesLastRunOutcomes.warning)}
|
||||
</EuiHealth>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiFlexGroup alignItems="center" gutterSize="none">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiHealth color="success" data-test-subj="totalActiveRulesCount">
|
||||
{RULE_STATUS_ACTIVE(rulesStatuses.active)}
|
||||
</EuiHealth>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiHealth color="danger" data-test-subj="totalErrorRulesCount">
|
||||
{RULE_STATUS_ERROR(rulesStatuses.error)}
|
||||
</EuiHealth>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiHealth color="warning" data-test-subj="totalWarningRulesCount">
|
||||
{RULE_STATUS_WARNING(rulesStatuses.warning)}
|
||||
</EuiHealth>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiHealth color="primary" data-test-subj="totalOkRulesCount">
|
||||
{RULE_STATUS_OK(rulesStatuses.ok)}
|
||||
</EuiHealth>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiHealth color="accent" data-test-subj="totalPendingRulesCount">
|
||||
{RULE_STATUS_PENDING(rulesStatuses.pending)}
|
||||
</EuiHealth>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiHealth color="subdued" data-test-subj="totalUnknownRulesCount">
|
||||
{RULE_STATUS_UNKNOWN(rulesStatuses.unknown)}
|
||||
</EuiHealth>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -9,7 +9,6 @@ import moment from 'moment';
|
|||
import numeral from '@elastic/numeral';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { AlertConsumers } from '@kbn/rule-data-utils';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { useUiSetting$ } from '@kbn/kibana-react-plugin/public';
|
||||
import {
|
||||
EuiBasicTable,
|
||||
|
@ -18,7 +17,6 @@ import {
|
|||
EuiIconTip,
|
||||
EuiLink,
|
||||
EuiButtonEmpty,
|
||||
EuiHealth,
|
||||
EuiText,
|
||||
EuiToolTip,
|
||||
EuiTableSortingType,
|
||||
|
@ -32,21 +30,17 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import {
|
||||
RuleExecutionStatus,
|
||||
RuleExecutionStatusErrorReasons,
|
||||
formatDuration,
|
||||
parseDuration,
|
||||
MONITORING_HISTORY_LIMIT,
|
||||
} from '@kbn/alerting-plugin/common';
|
||||
|
||||
import {
|
||||
rulesStatusesTranslationsMapping,
|
||||
ALERT_STATUS_LICENSE_ERROR,
|
||||
SELECT_ALL_RULES,
|
||||
CLEAR_SELECTION,
|
||||
TOTAL_RULES,
|
||||
SELECT_ALL_ARIA_LABEL,
|
||||
} from '../translations';
|
||||
import { getHealthColor } from './rule_execution_status_filter';
|
||||
import {
|
||||
Rule,
|
||||
RuleTableItem,
|
||||
|
@ -67,6 +61,8 @@ import { hasAllPrivilege } from '../../../lib/capabilities';
|
|||
import { RuleTagBadge } from './rule_tag_badge';
|
||||
import { RuleStatusDropdown } from './rule_status_dropdown';
|
||||
import { RulesListNotifyBadge } from './rules_list_notify_badge';
|
||||
import { RulesListTableStatusCell } from './rules_list_table_status_cell';
|
||||
import { getIsExperimentalFeatureEnabled } from '../../../../common/get_experimental_features';
|
||||
import {
|
||||
RulesListColumns,
|
||||
RulesListVisibleColumns,
|
||||
|
@ -92,9 +88,9 @@ const percentileOrdinals = {
|
|||
};
|
||||
|
||||
export const percentileFields = {
|
||||
[Percentiles.P50]: 'monitoring.execution.calculated_metrics.p50',
|
||||
[Percentiles.P95]: 'monitoring.execution.calculated_metrics.p95',
|
||||
[Percentiles.P99]: 'monitoring.execution.calculated_metrics.p99',
|
||||
[Percentiles.P50]: 'monitoring.run.calculated_metrics.p50',
|
||||
[Percentiles.P95]: 'monitoring.run.calculated_metrics.p95',
|
||||
[Percentiles.P99]: 'monitoring.run.calculated_metrics.p99',
|
||||
};
|
||||
|
||||
const EMPTY_OBJECT = {};
|
||||
|
@ -219,6 +215,8 @@ export const RulesListTable = (props: RulesListTableProps) => {
|
|||
const [currentlyOpenNotify, setCurrentlyOpenNotify] = useState<string>();
|
||||
const [isLoadingMap, setIsLoadingMap] = useState<Record<string, boolean>>({});
|
||||
|
||||
const isRuleLastRunOutcomeEnabled = getIsExperimentalFeatureEnabled('ruleLastRunOutcome');
|
||||
|
||||
const [defaultNumberFormat] = useUiSetting$<string>(DEFAULT_NUMBER_FORMAT);
|
||||
const { euiTheme } = useEuiTheme();
|
||||
|
||||
|
@ -301,58 +299,6 @@ export const RulesListTable = (props: RulesListTableProps) => {
|
|||
[isRuleTypeEditableInContext, onDisableRule, onEnableRule, onRuleChanged]
|
||||
);
|
||||
|
||||
const renderRuleExecutionStatus = useCallback(
|
||||
(executionStatus: RuleExecutionStatus, rule: RuleTableItem) => {
|
||||
const healthColor = getHealthColor(executionStatus.status);
|
||||
const tooltipMessage =
|
||||
executionStatus.status === 'error' ? `Error: ${executionStatus?.error?.message}` : null;
|
||||
const isLicenseError =
|
||||
executionStatus.error?.reason === RuleExecutionStatusErrorReasons.License;
|
||||
const statusMessage = isLicenseError
|
||||
? ALERT_STATUS_LICENSE_ERROR
|
||||
: rulesStatusesTranslationsMapping[executionStatus.status];
|
||||
|
||||
const health = (
|
||||
<EuiHealth data-test-subj={`ruleStatus-${executionStatus.status}`} color={healthColor}>
|
||||
{statusMessage}
|
||||
</EuiHealth>
|
||||
);
|
||||
|
||||
const healthWithTooltip = tooltipMessage ? (
|
||||
<EuiToolTip
|
||||
data-test-subj="ruleStatus-error-tooltip"
|
||||
position="top"
|
||||
content={tooltipMessage}
|
||||
>
|
||||
{health}
|
||||
</EuiToolTip>
|
||||
) : (
|
||||
health
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="none">
|
||||
<EuiFlexItem>{healthWithTooltip}</EuiFlexItem>
|
||||
{isLicenseError && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
size="xs"
|
||||
data-test-subj="ruleStatus-error-license-fix"
|
||||
onClick={() => onManageLicenseClick(rule)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.rulesList.fixLicenseLink"
|
||||
defaultMessage="Fix"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
},
|
||||
[onManageLicenseClick]
|
||||
);
|
||||
|
||||
const selectionColumn = useMemo(() => {
|
||||
return {
|
||||
id: 'ruleSelection',
|
||||
|
@ -382,6 +328,13 @@ export const RulesListTable = (props: RulesListTableProps) => {
|
|||
};
|
||||
}, [isPageSelected, onSelectPage, onSelectRow, isRowSelected]);
|
||||
|
||||
const ruleOutcomeColumnField = useMemo(() => {
|
||||
if (isRuleLastRunOutcomeEnabled) {
|
||||
return 'lastRun.outcome';
|
||||
}
|
||||
return 'executionStatus.status';
|
||||
}, [isRuleLastRunOutcomeEnabled]);
|
||||
|
||||
const getRulesTableColumns = useCallback((): RulesListColumns[] => {
|
||||
return [
|
||||
{
|
||||
|
@ -684,7 +637,7 @@ export const RulesListTable = (props: RulesListTableProps) => {
|
|||
},
|
||||
{
|
||||
id: 'ruleExecutionSuccessRatio',
|
||||
field: 'monitoring.execution.calculated_metrics.success_ratio',
|
||||
field: 'monitoring.run.calculated_metrics.success_ratio',
|
||||
width: '12%',
|
||||
selectorName: i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.rulesList.rulesListTable.columns.selector.successRatioTitle',
|
||||
|
@ -719,7 +672,7 @@ export const RulesListTable = (props: RulesListTableProps) => {
|
|||
},
|
||||
{
|
||||
id: 'ruleExecutionStatus',
|
||||
field: 'executionStatus.status',
|
||||
field: ruleOutcomeColumnField,
|
||||
name: i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.rulesList.rulesListTable.columns.lastResponseTitle',
|
||||
{ defaultMessage: 'Last response' }
|
||||
|
@ -729,7 +682,9 @@ export const RulesListTable = (props: RulesListTableProps) => {
|
|||
width: '120px',
|
||||
'data-test-subj': 'rulesTableCell-lastResponse',
|
||||
render: (_executionStatus: RuleExecutionStatus, rule: RuleTableItem) => {
|
||||
return renderRuleExecutionStatus(rule.executionStatus, rule);
|
||||
return (
|
||||
<RulesListTableStatusCell rule={rule} onManageLicenseClick={onManageLicenseClick} />
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -827,15 +782,16 @@ export const RulesListTable = (props: RulesListTableProps) => {
|
|||
onRuleEditClick,
|
||||
onSnoozeRule,
|
||||
onUnsnoozeRule,
|
||||
onManageLicenseClick,
|
||||
renderCollapsedItemActions,
|
||||
renderPercentileCellValue,
|
||||
renderPercentileColumnName,
|
||||
renderRuleError,
|
||||
renderRuleExecutionStatus,
|
||||
renderRuleStatusDropdown,
|
||||
ruleTypesState.data,
|
||||
selectedPercentile,
|
||||
tagPopoverOpenIndex,
|
||||
ruleOutcomeColumnField,
|
||||
]);
|
||||
|
||||
const allRuleColumns = useMemo(() => getRulesTableColumns(), [getRulesTableColumns]);
|
||||
|
|
|
@ -0,0 +1,162 @@
|
|||
/*
|
||||
* 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 { render } from '@testing-library/react';
|
||||
import {
|
||||
RulesListTableStatusCell,
|
||||
RulesListTableStatusCellProps,
|
||||
} from './rules_list_table_status_cell';
|
||||
import { RuleTableItem } from '../../../../types';
|
||||
import { getIsExperimentalFeatureEnabled } from '../../../../common/get_experimental_features';
|
||||
import { __IntlProvider as IntlProvider } from '@kbn/i18n-react';
|
||||
|
||||
jest.mock('../../../../common/get_experimental_features', () => ({
|
||||
getIsExperimentalFeatureEnabled: jest.fn(),
|
||||
}));
|
||||
|
||||
const mockRule: RuleTableItem = {
|
||||
id: '1',
|
||||
enabled: true,
|
||||
executionStatus: {
|
||||
status: 'ok',
|
||||
},
|
||||
lastRun: {
|
||||
outcome: 'succeeded',
|
||||
},
|
||||
nextRun: new Date('2020-08-20T19:23:38Z'),
|
||||
} as RuleTableItem;
|
||||
|
||||
const onManageLicenseClickMock = jest.fn();
|
||||
|
||||
const ComponentWithLocale = (props: RulesListTableStatusCellProps) => {
|
||||
return (
|
||||
<IntlProvider locale="en">
|
||||
<RulesListTableStatusCell {...props} />
|
||||
</IntlProvider>
|
||||
);
|
||||
};
|
||||
|
||||
describe('RulesListTableStatusCell', () => {
|
||||
beforeEach(() => {
|
||||
(getIsExperimentalFeatureEnabled as jest.Mock<any, any>).mockImplementation(() => true);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
onManageLicenseClickMock.mockClear();
|
||||
});
|
||||
|
||||
it('should render successful rule outcome', async () => {
|
||||
const { getByTestId } = render(
|
||||
<ComponentWithLocale rule={mockRule} onManageLicenseClick={onManageLicenseClickMock} />
|
||||
);
|
||||
expect(getByTestId('ruleStatus-succeeded')).not.toBe(null);
|
||||
});
|
||||
|
||||
it('should render failed rule outcome', async () => {
|
||||
const { getByTestId } = render(
|
||||
<ComponentWithLocale
|
||||
rule={
|
||||
{
|
||||
...mockRule,
|
||||
executionStatus: {
|
||||
status: 'error',
|
||||
},
|
||||
lastRun: {
|
||||
outcome: 'failed',
|
||||
},
|
||||
} as RuleTableItem
|
||||
}
|
||||
onManageLicenseClick={onManageLicenseClickMock}
|
||||
/>
|
||||
);
|
||||
expect(getByTestId('ruleStatus-failed')).not.toBe(null);
|
||||
});
|
||||
|
||||
it('should render warning rule outcome', async () => {
|
||||
const { getByTestId } = render(
|
||||
<ComponentWithLocale
|
||||
rule={
|
||||
{
|
||||
...mockRule,
|
||||
executionStatus: {
|
||||
status: 'warning',
|
||||
},
|
||||
lastRun: {
|
||||
outcome: 'warning',
|
||||
},
|
||||
} as RuleTableItem
|
||||
}
|
||||
onManageLicenseClick={onManageLicenseClickMock}
|
||||
/>
|
||||
);
|
||||
expect(getByTestId('ruleStatus-warning')).not.toBe(null);
|
||||
});
|
||||
|
||||
it('should render license errors', async () => {
|
||||
const { getByTestId, getByText } = render(
|
||||
<ComponentWithLocale
|
||||
rule={
|
||||
{
|
||||
...mockRule,
|
||||
executionStatus: {
|
||||
status: 'warning',
|
||||
},
|
||||
lastRun: {
|
||||
outcome: 'warning',
|
||||
warning: 'license',
|
||||
},
|
||||
} as RuleTableItem
|
||||
}
|
||||
onManageLicenseClick={onManageLicenseClickMock}
|
||||
/>
|
||||
);
|
||||
expect(getByTestId('ruleStatus-warning')).not.toBe(null);
|
||||
expect(getByText('License Error')).not.toBe(null);
|
||||
});
|
||||
|
||||
it('should render loading indicator for new rules', async () => {
|
||||
const { getByText } = render(
|
||||
<ComponentWithLocale
|
||||
rule={
|
||||
{
|
||||
...mockRule,
|
||||
executionStatus: {
|
||||
status: 'pending',
|
||||
},
|
||||
lastRun: null,
|
||||
nextRun: null,
|
||||
} as RuleTableItem
|
||||
}
|
||||
onManageLicenseClick={onManageLicenseClickMock}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(getByText('Statistic is loading')).not.toBe(null);
|
||||
});
|
||||
|
||||
it('should render rule with no last run', async () => {
|
||||
const { queryByText, getAllByText } = render(
|
||||
<ComponentWithLocale
|
||||
rule={
|
||||
{
|
||||
...mockRule,
|
||||
executionStatus: {
|
||||
status: 'unknown',
|
||||
},
|
||||
lastRun: null,
|
||||
} as RuleTableItem
|
||||
}
|
||||
onManageLicenseClick={onManageLicenseClickMock}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(queryByText('Statistic is loading')).toBe(null);
|
||||
expect(getAllByText('--')).not.toBe(null);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* 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 { FormattedMessage } from '@kbn/i18n-react';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiButtonEmpty,
|
||||
EuiHealth,
|
||||
EuiToolTip,
|
||||
EuiStat,
|
||||
} from '@elastic/eui';
|
||||
import { RuleTableItem } from '../../../../types';
|
||||
import {
|
||||
getRuleHealthColor,
|
||||
getIsLicenseError,
|
||||
getRuleStatusMessage,
|
||||
} from '../../../../common/lib/rule_status_helpers';
|
||||
|
||||
export interface RulesListTableStatusCellProps {
|
||||
rule: RuleTableItem;
|
||||
onManageLicenseClick: (rule: RuleTableItem) => void;
|
||||
}
|
||||
|
||||
export const RulesListTableStatusCell = (props: RulesListTableStatusCellProps) => {
|
||||
const { rule, onManageLicenseClick } = props;
|
||||
const { lastRun } = rule;
|
||||
|
||||
const isLicenseError = getIsLicenseError(rule);
|
||||
const healthColor = getRuleHealthColor(rule);
|
||||
const statusMessage = getRuleStatusMessage(rule);
|
||||
const tooltipMessage = lastRun?.outcome === 'failed' ? `Error: ${lastRun?.outcomeMsg}` : null;
|
||||
|
||||
if (!statusMessage) {
|
||||
return (
|
||||
<EuiStat
|
||||
titleSize="xs"
|
||||
title="--"
|
||||
description=""
|
||||
isLoading={!lastRun?.outcome && !rule.nextRun}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const health = (
|
||||
<EuiHealth
|
||||
data-test-subj={`ruleStatus-${lastRun?.outcome || 'pending'}`}
|
||||
color={healthColor || 'default'}
|
||||
>
|
||||
{statusMessage}
|
||||
</EuiHealth>
|
||||
);
|
||||
|
||||
const healthWithTooltip = tooltipMessage ? (
|
||||
<EuiToolTip data-test-subj="ruleStatus-error-tooltip" position="top" content={tooltipMessage}>
|
||||
{health}
|
||||
</EuiToolTip>
|
||||
) : (
|
||||
health
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="none">
|
||||
<EuiFlexItem>{healthWithTooltip}</EuiFlexItem>
|
||||
{isLicenseError && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
size="xs"
|
||||
data-test-subj="ruleStatus-error-license-fix"
|
||||
onClick={() => onManageLicenseClick(rule)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.rulesList.fixLicenseLink"
|
||||
defaultMessage="Fix"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -36,7 +36,7 @@ export const mockedRulesData = [
|
|||
error: null,
|
||||
},
|
||||
monitoring: {
|
||||
execution: {
|
||||
run: {
|
||||
history: [
|
||||
{
|
||||
success: true,
|
||||
|
@ -57,8 +57,18 @@ export const mockedRulesData = [
|
|||
p95: 300000,
|
||||
p99: 300000,
|
||||
},
|
||||
last_run: {
|
||||
timestamp: '2020-08-20T19:23:38Z',
|
||||
metrics: {
|
||||
duration: 500,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
lastRun: {
|
||||
outcome: 'succeeded',
|
||||
alertsCount: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
|
@ -83,7 +93,7 @@ export const mockedRulesData = [
|
|||
error: null,
|
||||
},
|
||||
monitoring: {
|
||||
execution: {
|
||||
run: {
|
||||
history: [
|
||||
{
|
||||
success: true,
|
||||
|
@ -100,8 +110,18 @@ export const mockedRulesData = [
|
|||
p95: 100000,
|
||||
p99: 500000,
|
||||
},
|
||||
last_run: {
|
||||
timestamp: '2020-08-20T19:23:38Z',
|
||||
metrics: {
|
||||
duration: 61000,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
lastRun: {
|
||||
outcome: 'succeeded',
|
||||
alertsCount: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
|
@ -126,11 +146,17 @@ export const mockedRulesData = [
|
|||
error: null,
|
||||
},
|
||||
monitoring: {
|
||||
execution: {
|
||||
run: {
|
||||
history: [{ success: false, duration: 100 }],
|
||||
calculated_metrics: {
|
||||
success_ratio: 0,
|
||||
},
|
||||
last_run: {
|
||||
timestamp: '2020-08-20T19:23:38Z',
|
||||
metrics: {
|
||||
duration: 30234,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -159,6 +185,11 @@ export const mockedRulesData = [
|
|||
message: 'test',
|
||||
},
|
||||
},
|
||||
lastRun: {
|
||||
outcome: 'failed',
|
||||
outcomeMsg: 'test',
|
||||
warning: RuleExecutionStatusErrorReasons.Unknown,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
|
@ -185,6 +216,11 @@ export const mockedRulesData = [
|
|||
message: 'test',
|
||||
},
|
||||
},
|
||||
lastRun: {
|
||||
outcome: 'failed',
|
||||
outcomeMsg: 'test',
|
||||
warning: RuleExecutionStatusErrorReasons.License,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
|
@ -211,6 +247,11 @@ export const mockedRulesData = [
|
|||
message: 'test',
|
||||
},
|
||||
},
|
||||
lastRun: {
|
||||
outcome: 'warning',
|
||||
outcomeMsg: 'test',
|
||||
warning: RuleExecutionStatusWarningReasons.MAX_EXECUTABLE_ACTIONS,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
@ -55,6 +55,26 @@ export const ALERT_STATUS_WARNING = i18n.translate(
|
|||
defaultMessage: 'Warning',
|
||||
}
|
||||
);
|
||||
export const RULE_LAST_RUN_OUTCOME_SUCCEEDED = i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.rulesList.ruleLastRunOutcomeSucceeded',
|
||||
{
|
||||
defaultMessage: 'Succeeded',
|
||||
}
|
||||
);
|
||||
|
||||
export const RULE_LAST_RUN_OUTCOME_WARNING = i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.rulesList.ruleLastRunOutcomeWarning',
|
||||
{
|
||||
defaultMessage: 'Warning',
|
||||
}
|
||||
);
|
||||
|
||||
export const RULE_LAST_RUN_OUTCOME_FAILED = i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.rulesList.ruleLastRunOutcomeFailed',
|
||||
{
|
||||
defaultMessage: 'Failed',
|
||||
}
|
||||
);
|
||||
|
||||
export const rulesStatusesTranslationsMapping = {
|
||||
ok: ALERT_STATUS_OK,
|
||||
|
@ -65,6 +85,12 @@ export const rulesStatusesTranslationsMapping = {
|
|||
warning: ALERT_STATUS_WARNING,
|
||||
};
|
||||
|
||||
export const rulesLastRunOutcomeTranslationMapping = {
|
||||
succeeded: RULE_LAST_RUN_OUTCOME_SUCCEEDED,
|
||||
warning: RULE_LAST_RUN_OUTCOME_WARNING,
|
||||
failed: RULE_LAST_RUN_OUTCOME_FAILED,
|
||||
};
|
||||
|
||||
export const ALERT_ERROR_UNKNOWN_REASON = i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.rulesList.ruleErrorReasonUnknown',
|
||||
{
|
||||
|
@ -199,6 +225,93 @@ export const CLEAR_SELECTION = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const RULE_STATUS_ACTIVE = (total: number) => {
|
||||
return i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.rulesList.totalStatusesActiveDescription',
|
||||
{
|
||||
defaultMessage: 'Active: {totalStatusesActive}',
|
||||
values: { totalStatusesActive: total },
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export const RULE_STATUS_ERROR = (total: number) => {
|
||||
return i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.rulesList.totalStatusesErrorDescription',
|
||||
{
|
||||
defaultMessage: 'Error: {totalStatusesError}',
|
||||
values: { totalStatusesError: total },
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export const RULE_STATUS_WARNING = (total: number) => {
|
||||
return i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.rulesList.totalStatusesWarningDescription',
|
||||
{
|
||||
defaultMessage: 'Warning: {totalStatusesWarning}',
|
||||
values: { totalStatusesWarning: total },
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export const RULE_STATUS_OK = (total: number) => {
|
||||
return i18n.translate('xpack.triggersActionsUI.sections.rulesList.totalStatusesOkDescription', {
|
||||
defaultMessage: 'Ok: {totalStatusesOk}',
|
||||
values: { totalStatusesOk: total },
|
||||
});
|
||||
};
|
||||
|
||||
export const RULE_STATUS_PENDING = (total: number) => {
|
||||
return i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.rulesList.totalStatusesPendingDescription',
|
||||
{
|
||||
defaultMessage: 'Pending: {totalStatusesPending}',
|
||||
values: { totalStatusesPending: total },
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export const RULE_STATUS_UNKNOWN = (total: number) => {
|
||||
return i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.rulesList.totalStatusesUnknownDescription',
|
||||
{
|
||||
defaultMessage: 'Unknown: {totalStatusesUnknown}',
|
||||
values: { totalStatusesUnknown: total },
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export const RULE_LAST_RUN_OUTCOME_SUCCEEDED_DESCRIPTION = (total: number) => {
|
||||
return i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.rulesList.lastRunOutcomeSucceededDescription',
|
||||
{
|
||||
defaultMessage: 'Succeeded: {total}',
|
||||
values: { total },
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export const RULE_LAST_RUN_OUTCOME_WARNING_DESCRIPTION = (total: number) => {
|
||||
return i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.rulesList.lastRunOutcomeWarningDescription',
|
||||
{
|
||||
defaultMessage: 'Warning: {total}',
|
||||
values: { total },
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export const RULE_LAST_RUN_OUTCOME_FAILED_DESCRIPTION = (total: number) => {
|
||||
return i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.rulesList.lastRunOutcomeFailedDescription',
|
||||
{
|
||||
defaultMessage: 'Failed: {total}',
|
||||
values: { total },
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export const SINGLE_RULE_TITLE = i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.rulesList.singleTitle',
|
||||
{
|
||||
|
|
|
@ -20,6 +20,7 @@ describe('getIsExperimentalFeatureEnabled', () => {
|
|||
rulesDetailLogs: true,
|
||||
ruleTagFilter: true,
|
||||
ruleStatusFilter: true,
|
||||
ruleLastRunOutcome: true,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -43,6 +44,10 @@ describe('getIsExperimentalFeatureEnabled', () => {
|
|||
|
||||
expect(result).toEqual(true);
|
||||
|
||||
result = getIsExperimentalFeatureEnabled('ruleLastRunOutcome');
|
||||
|
||||
expect(result).toEqual(true);
|
||||
|
||||
expect(() => getIsExperimentalFeatureEnabled('doesNotExist' as any)).toThrowError(
|
||||
`Invalid enable value doesNotExist. Allowed values are: ${allowedExperimentalValueKeys.join(
|
||||
', '
|
||||
|
|
|
@ -6,4 +6,11 @@
|
|||
*/
|
||||
|
||||
export { getTimeFieldOptions, getTimeOptions } from './get_time_options';
|
||||
export {
|
||||
getOutcomeHealthColor,
|
||||
getExecutionStatusHealthColor,
|
||||
getRuleHealthColor,
|
||||
getIsLicenseError,
|
||||
getRuleStatusMessage,
|
||||
} from './rule_status_helpers';
|
||||
export { useKibana } from './kibana';
|
||||
|
|
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* 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 { getRuleHealthColor, getRuleStatusMessage } from './rule_status_helpers';
|
||||
import { RuleTableItem } from '../../types';
|
||||
|
||||
import { getIsExperimentalFeatureEnabled } from '../get_experimental_features';
|
||||
|
||||
jest.mock('../get_experimental_features', () => ({
|
||||
getIsExperimentalFeatureEnabled: jest.fn(),
|
||||
}));
|
||||
|
||||
const mockRule = {
|
||||
id: '1',
|
||||
enabled: true,
|
||||
executionStatus: {
|
||||
status: 'active',
|
||||
},
|
||||
lastRun: {
|
||||
outcome: 'succeeded',
|
||||
},
|
||||
} as RuleTableItem;
|
||||
|
||||
const warningRule = {
|
||||
...mockRule,
|
||||
executionStatus: {
|
||||
status: 'warning',
|
||||
},
|
||||
lastRun: {
|
||||
outcome: 'warning',
|
||||
},
|
||||
} as RuleTableItem;
|
||||
|
||||
const failedRule = {
|
||||
...mockRule,
|
||||
executionStatus: {
|
||||
status: 'error',
|
||||
},
|
||||
lastRun: {
|
||||
outcome: 'failed',
|
||||
},
|
||||
} as RuleTableItem;
|
||||
|
||||
const licenseErrorRule = {
|
||||
...mockRule,
|
||||
executionStatus: {
|
||||
status: 'error',
|
||||
error: {
|
||||
reason: 'license',
|
||||
},
|
||||
},
|
||||
lastRun: {
|
||||
outcome: 'failed',
|
||||
warning: 'license',
|
||||
},
|
||||
} as RuleTableItem;
|
||||
|
||||
beforeEach(() => {
|
||||
(getIsExperimentalFeatureEnabled as jest.Mock<any, any>).mockImplementation(() => true);
|
||||
});
|
||||
|
||||
describe('getRuleHealthColor', () => {
|
||||
it('should return the correct color for successful rule', () => {
|
||||
let color = getRuleHealthColor(mockRule);
|
||||
expect(color).toEqual('success');
|
||||
|
||||
(getIsExperimentalFeatureEnabled as jest.Mock<any, any>).mockImplementation(() => false);
|
||||
|
||||
color = getRuleHealthColor(mockRule);
|
||||
expect(color).toEqual('success');
|
||||
});
|
||||
|
||||
it('should return the correct color for warning rule', () => {
|
||||
let color = getRuleHealthColor(warningRule);
|
||||
expect(color).toEqual('warning');
|
||||
|
||||
(getIsExperimentalFeatureEnabled as jest.Mock<any, any>).mockImplementation(() => false);
|
||||
|
||||
color = getRuleHealthColor(warningRule);
|
||||
expect(color).toEqual('warning');
|
||||
});
|
||||
|
||||
it('should return the correct color for failed rule', () => {
|
||||
let color = getRuleHealthColor(failedRule);
|
||||
expect(color).toEqual('danger');
|
||||
|
||||
(getIsExperimentalFeatureEnabled as jest.Mock<any, any>).mockImplementation(() => false);
|
||||
|
||||
color = getRuleHealthColor(failedRule);
|
||||
expect(color).toEqual('danger');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getRuleStatusMessage', () => {
|
||||
it('should get the status message for a successful rule', () => {
|
||||
let statusMessage = getRuleStatusMessage(mockRule);
|
||||
expect(statusMessage).toEqual('Succeeded');
|
||||
|
||||
(getIsExperimentalFeatureEnabled as jest.Mock<any, any>).mockImplementation(() => false);
|
||||
statusMessage = getRuleStatusMessage(mockRule);
|
||||
expect(statusMessage).toEqual('Active');
|
||||
});
|
||||
|
||||
it('should get the status message for a warning rule', () => {
|
||||
let statusMessage = getRuleStatusMessage(warningRule);
|
||||
expect(statusMessage).toEqual('Warning');
|
||||
|
||||
(getIsExperimentalFeatureEnabled as jest.Mock<any, any>).mockImplementation(() => false);
|
||||
statusMessage = getRuleStatusMessage(warningRule);
|
||||
expect(statusMessage).toEqual('Warning');
|
||||
});
|
||||
|
||||
it('should get the status message for a failed rule', () => {
|
||||
let statusMessage = getRuleStatusMessage(failedRule);
|
||||
expect(statusMessage).toEqual('Failed');
|
||||
|
||||
(getIsExperimentalFeatureEnabled as jest.Mock<any, any>).mockImplementation(() => false);
|
||||
statusMessage = getRuleStatusMessage(failedRule);
|
||||
expect(statusMessage).toEqual('Error');
|
||||
});
|
||||
|
||||
it('should get the status message for a license error rule', () => {
|
||||
let statusMessage = getRuleStatusMessage(licenseErrorRule);
|
||||
expect(statusMessage).toEqual('License Error');
|
||||
|
||||
(getIsExperimentalFeatureEnabled as jest.Mock<any, any>).mockImplementation(() => false);
|
||||
statusMessage = getRuleStatusMessage(licenseErrorRule);
|
||||
expect(statusMessage).toEqual('License Error');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* 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 {
|
||||
RuleLastRunOutcomes,
|
||||
RuleExecutionStatuses,
|
||||
RuleExecutionStatusErrorReasons,
|
||||
} from '@kbn/alerting-plugin/common';
|
||||
import { getIsExperimentalFeatureEnabled } from '../get_experimental_features';
|
||||
import { Rule } from '../../types';
|
||||
import {
|
||||
rulesLastRunOutcomeTranslationMapping,
|
||||
rulesStatusesTranslationsMapping,
|
||||
ALERT_STATUS_LICENSE_ERROR,
|
||||
} from '../../application/sections/rules_list/translations';
|
||||
|
||||
export const getOutcomeHealthColor = (status: RuleLastRunOutcomes) => {
|
||||
switch (status) {
|
||||
case 'succeeded':
|
||||
return 'success';
|
||||
case 'failed':
|
||||
return 'danger';
|
||||
case 'warning':
|
||||
return 'warning';
|
||||
default:
|
||||
return 'subdued';
|
||||
}
|
||||
};
|
||||
|
||||
export const getExecutionStatusHealthColor = (status: RuleExecutionStatuses) => {
|
||||
switch (status) {
|
||||
case 'active':
|
||||
return 'success';
|
||||
case 'error':
|
||||
return 'danger';
|
||||
case 'ok':
|
||||
return 'primary';
|
||||
case 'pending':
|
||||
return 'accent';
|
||||
case 'warning':
|
||||
return 'warning';
|
||||
default:
|
||||
return 'subdued';
|
||||
}
|
||||
};
|
||||
|
||||
export const getRuleHealthColor = (rule: Rule) => {
|
||||
const isRuleLastRunOutcomeEnabled = getIsExperimentalFeatureEnabled('ruleLastRunOutcome');
|
||||
if (isRuleLastRunOutcomeEnabled) {
|
||||
return (rule.lastRun && getOutcomeHealthColor(rule.lastRun.outcome)) || 'subdued';
|
||||
}
|
||||
return getExecutionStatusHealthColor(rule.executionStatus.status);
|
||||
};
|
||||
|
||||
export const getIsLicenseError = (rule: Rule) => {
|
||||
return (
|
||||
rule.lastRun?.warning === RuleExecutionStatusErrorReasons.License ||
|
||||
rule.executionStatus.error?.reason === RuleExecutionStatusErrorReasons.License
|
||||
);
|
||||
};
|
||||
|
||||
export const getRuleStatusMessage = (rule: Rule) => {
|
||||
const isLicenseError = getIsLicenseError(rule);
|
||||
const isRuleLastRunOutcomeEnabled = getIsExperimentalFeatureEnabled('ruleLastRunOutcome');
|
||||
|
||||
if (isLicenseError) {
|
||||
return ALERT_STATUS_LICENSE_ERROR;
|
||||
}
|
||||
if (isRuleLastRunOutcomeEnabled) {
|
||||
return rule.lastRun && rulesLastRunOutcomeTranslationMapping[rule.lastRun.outcome];
|
||||
}
|
||||
return rulesStatusesTranslationsMapping[rule.executionStatus.status];
|
||||
};
|
|
@ -40,6 +40,7 @@ import {
|
|||
RuleTypeParams,
|
||||
ActionVariable,
|
||||
RuleType as CommonRuleType,
|
||||
RuleLastRun,
|
||||
} from '@kbn/alerting-plugin/common';
|
||||
import type { BulkOperationError } from '@kbn/alerting-plugin/server';
|
||||
import { RuleRegistrySearchRequestPagination } from '@kbn/rule-registry-plugin/common';
|
||||
|
@ -106,6 +107,7 @@ export type {
|
|||
RuleStatusDropdownProps,
|
||||
RuleTagFilterProps,
|
||||
RuleStatusFilterProps,
|
||||
RuleLastRun,
|
||||
RuleTagBadgeProps,
|
||||
RuleTagBadgeOptions,
|
||||
RuleEventLogListProps,
|
||||
|
@ -301,7 +303,7 @@ export interface RuleType<
|
|||
|
||||
export type SanitizedRuleType = Omit<RuleType, 'apiKey'>;
|
||||
|
||||
export type RuleUpdates = Omit<Rule, 'id' | 'executionStatus'>;
|
||||
export type RuleUpdates = Omit<Rule, 'id' | 'executionStatus' | 'lastRun' | 'nextRun'>;
|
||||
|
||||
export interface RuleTableItem extends Rule {
|
||||
ruleType: RuleType['name'];
|
||||
|
|
|
@ -368,19 +368,22 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
await refreshAlertsList();
|
||||
await find.waitForDeletedByCssSelector('.euiBasicTable-loading');
|
||||
const refreshResults = await pageObjects.triggersActionsUI.getAlertsListWithStatus();
|
||||
expect(refreshResults.map((item: any) => item.status).sort()).to.eql(['Error', 'Ok']);
|
||||
expect(refreshResults.map((item: any) => item.status).sort()).to.eql([
|
||||
'Failed',
|
||||
'Succeeded',
|
||||
]);
|
||||
});
|
||||
await refreshAlertsList();
|
||||
await find.waitForDeletedByCssSelector('.euiBasicTable-loading');
|
||||
await testSubjects.click('ruleExecutionStatusFilterButton');
|
||||
await testSubjects.click('ruleExecutionStatuserrorFilterOption'); // select Error status filter
|
||||
await testSubjects.click('ruleLastRunOutcomeFilterButton');
|
||||
await testSubjects.click('ruleLastRunOutcomefailedFilterOption'); // select Error status filter
|
||||
await retry.try(async () => {
|
||||
const filterErrorOnlyResults =
|
||||
await pageObjects.triggersActionsUI.getAlertsListWithStatus();
|
||||
expect(filterErrorOnlyResults.length).to.equal(1);
|
||||
expect(filterErrorOnlyResults[0].name).to.equal(`${failingAlert.name}Test: Failing`);
|
||||
expect(filterErrorOnlyResults[0].interval).to.equal('30 sec');
|
||||
expect(filterErrorOnlyResults[0].status).to.equal('Error');
|
||||
expect(filterErrorOnlyResults[0].status).to.equal('Failed');
|
||||
expect(filterErrorOnlyResults[0].duration).to.match(/\d{2,}:\d{2}/);
|
||||
});
|
||||
});
|
||||
|
@ -393,7 +396,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
expect(refreshResults.length).to.equal(1);
|
||||
expect(refreshResults[0].name).to.equal(`${createdAlert.name}Test: Noop`);
|
||||
expect(refreshResults[0].interval).to.equal('1 min');
|
||||
expect(refreshResults[0].status).to.equal('Ok');
|
||||
expect(refreshResults[0].status).to.equal('Succeeded');
|
||||
expect(refreshResults[0].duration).to.match(/\d{2,}:\d{2}/);
|
||||
});
|
||||
|
||||
|
@ -417,11 +420,9 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
await retry.try(async () => {
|
||||
await refreshAlertsList();
|
||||
expect(await testSubjects.getVisibleText('totalRulesCount')).to.be('2 rules');
|
||||
expect(await testSubjects.getVisibleText('totalActiveRulesCount')).to.be('Active: 0');
|
||||
expect(await testSubjects.getVisibleText('totalOkRulesCount')).to.be('Ok: 1');
|
||||
expect(await testSubjects.getVisibleText('totalErrorRulesCount')).to.be('Error: 1');
|
||||
expect(await testSubjects.getVisibleText('totalPendingRulesCount')).to.be('Pending: 0');
|
||||
expect(await testSubjects.getVisibleText('totalUnknownRulesCount')).to.be('Unknown: 0');
|
||||
expect(await testSubjects.getVisibleText('totalSucceededRulesCount')).to.be('Succeeded: 1');
|
||||
expect(await testSubjects.getVisibleText('totalFailedRulesCount')).to.be('Failed: 1');
|
||||
expect(await testSubjects.getVisibleText('totalWarningRulesCount')).to.be('Warning: 0');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -433,7 +434,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
expect(refreshResults.length).to.equal(1);
|
||||
expect(refreshResults[0].name).to.equal(`${createdAlert.name}Test: Noop`);
|
||||
expect(refreshResults[0].interval).to.equal('1 min');
|
||||
expect(refreshResults[0].status).to.equal('Ok');
|
||||
expect(refreshResults[0].status).to.equal('Succeeded');
|
||||
expect(refreshResults[0].duration).to.match(/\d{2,}:\d{2}/);
|
||||
});
|
||||
|
||||
|
|
|
@ -93,6 +93,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
|||
'internalAlertsTable',
|
||||
'ruleTagFilter',
|
||||
'ruleStatusFilter',
|
||||
'ruleLastRunOutcome',
|
||||
])}`,
|
||||
`--xpack.alerting.rules.minimumScheduleInterval.value="2s"`,
|
||||
`--xpack.actions.enabledActionTypes=${JSON.stringify(enabledActionTypes)}`,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue