diff --git a/x-pack/plugins/alerting/common/rule.ts b/x-pack/plugins/alerting/common/rule.ts index 3bfddd79af10d..2ecd530d9d6fb 100644 --- a/x-pack/plugins/alerting/common/rule.ts +++ b/x-pack/plugins/alerting/common/rule.ts @@ -35,6 +35,12 @@ export type RuleExecutionStatuses = typeof RuleExecutionStatusValues[number]; export const RuleLastRunOutcomeValues = ['succeeded', 'warning', 'failed'] as const; export type RuleLastRunOutcomes = typeof RuleLastRunOutcomeValues[number]; +export const RuleLastRunOutcomeOrderMap: Record = { + succeeded: 0, + warning: 10, + failed: 20, +}; + export enum RuleExecutionStatusErrorReasons { Read = 'read', Decrypt = 'decrypt', @@ -93,6 +99,7 @@ export interface RuleAggregations { export interface RuleLastRun { outcome: RuleLastRunOutcomes; + outcomeOrder?: number; warning?: RuleExecutionStatusErrorReasons | RuleExecutionStatusWarningReasons | null; outcomeMsg?: string[] | null; alertsCount: { diff --git a/x-pack/plugins/alerting/public/lib/common_transformations.test.ts b/x-pack/plugins/alerting/public/lib/common_transformations.test.ts index 4747ad98412c9..cef3cf8804f40 100644 --- a/x-pack/plugins/alerting/public/lib/common_transformations.test.ts +++ b/x-pack/plugins/alerting/public/lib/common_transformations.test.ts @@ -81,6 +81,7 @@ describe('common_transformations', () => { }, last_run: { outcome: RuleLastRunOutcomeValues[2], + outcome_order: 20, outcome_msg: ['this is just a test'], warning: RuleExecutionStatusErrorReasons.Unknown, alerts_count: { @@ -137,6 +138,7 @@ describe('common_transformations', () => { "outcomeMsg": Array [ "this is just a test", ], + "outcomeOrder": 20, "warning": "unknown", }, "monitoring": Object { @@ -255,6 +257,7 @@ describe('common_transformations', () => { }, last_run: { outcome: 'failed', + outcome_order: 20, outcome_msg: ['this is just a test'], warning: RuleExecutionStatusErrorReasons.Unknown, alerts_count: { @@ -300,6 +303,7 @@ describe('common_transformations', () => { "outcomeMsg": Array [ "this is just a test", ], + "outcomeOrder": 20, "warning": "unknown", }, "monitoring": Object { diff --git a/x-pack/plugins/alerting/public/lib/common_transformations.ts b/x-pack/plugins/alerting/public/lib/common_transformations.ts index 6e3888db451b8..6a89b1ce9958b 100644 --- a/x-pack/plugins/alerting/public/lib/common_transformations.ts +++ b/x-pack/plugins/alerting/public/lib/common_transformations.ts @@ -63,10 +63,16 @@ function transformMonitoring(input: RuleMonitoring): RuleMonitoring { } function transformLastRun(input: AsApiContract): RuleLastRun { - const { outcome_msg: outcomeMsg, alerts_count: alertsCount, ...rest } = input; + const { + outcome_msg: outcomeMsg, + alerts_count: alertsCount, + outcome_order: outcomeOrder, + ...rest + } = input; return { outcomeMsg, alertsCount, + outcomeOrder, ...rest, }; } diff --git a/x-pack/plugins/alerting/server/lib/last_run_status.ts b/x-pack/plugins/alerting/server/lib/last_run_status.ts index d2bc22cadb285..a007d8637eac0 100644 --- a/x-pack/plugins/alerting/server/lib/last_run_status.ts +++ b/x-pack/plugins/alerting/server/lib/last_run_status.ts @@ -8,7 +8,7 @@ import { RuleTaskStateAndMetrics } from '../task_runner/types'; import { getReasonFromError } from './error_with_reason'; import { getEsErrorMessage } from './errors'; -import { ActionsCompletion, RuleLastRunOutcomes } from '../../common'; +import { ActionsCompletion, RuleLastRunOutcomeOrderMap, RuleLastRunOutcomes } from '../../common'; import { RuleLastRunOutcomeValues, RuleExecutionStatusWarningReasons, @@ -65,6 +65,7 @@ export const lastRunFromState = ( return { lastRun: { outcome, + outcomeOrder: RuleLastRunOutcomeOrderMap[outcome], outcomeMsg: outcomeMsg.length > 0 ? outcomeMsg : null, warning: warning || null, alertsCount: { @@ -80,9 +81,11 @@ export const lastRunFromState = ( export const lastRunFromError = (error: Error): ILastRun => { const esErrorMessage = getEsErrorMessage(error); + const outcome = RuleLastRunOutcomeValues[2]; return { lastRun: { - outcome: RuleLastRunOutcomeValues[2], + outcome, + outcomeOrder: RuleLastRunOutcomeOrderMap[outcome], warning: getReasonFromError(error), outcomeMsg: esErrorMessage ? [esErrorMessage] : null, alertsCount: {}, @@ -104,5 +107,6 @@ export const lastRunToRaw = (lastRun: ILastRun['lastRun']): RawRuleLastRun => { }, warning: warning ?? null, outcomeMsg: outcomeMsg && !Array.isArray(outcomeMsg) ? [outcomeMsg] : outcomeMsg, + outcomeOrder: RuleLastRunOutcomeOrderMap[lastRun.outcome], }; }; diff --git a/x-pack/plugins/alerting/server/routes/lib/rewrite_rule.ts b/x-pack/plugins/alerting/server/routes/lib/rewrite_rule.ts index 0cc910a4af2f0..bbcb489a9597e 100644 --- a/x-pack/plugins/alerting/server/routes/lib/rewrite_rule.ts +++ b/x-pack/plugins/alerting/server/routes/lib/rewrite_rule.ts @@ -9,10 +9,11 @@ import { omit } from 'lodash'; import { RuleTypeParams, SanitizedRule, RuleLastRun } from '../../types'; export const rewriteRuleLastRun = (lastRun: RuleLastRun) => { - const { outcomeMsg, alertsCount, ...rest } = lastRun; + const { outcomeMsg, outcomeOrder, alertsCount, ...rest } = lastRun; return { alerts_count: alertsCount, outcome_msg: outcomeMsg, + outcome_order: outcomeOrder, ...rest, }; }; diff --git a/x-pack/plugins/alerting/server/saved_objects/migrations/8.7/index.ts b/x-pack/plugins/alerting/server/saved_objects/migrations/8.7/index.ts index b2bd0b01f7f61..1b49f606cb6b5 100644 --- a/x-pack/plugins/alerting/server/saved_objects/migrations/8.7/index.ts +++ b/x-pack/plugins/alerting/server/saved_objects/migrations/8.7/index.ts @@ -14,7 +14,7 @@ import { isLogThresholdRuleType, pipeMigrations, } from '../utils'; -import { RawRule } from '../../../types'; +import { RawRule, RuleLastRunOutcomeOrderMap } from '../../../types'; function addGroupByToEsQueryRule( doc: SavedObjectUnsanitizedDoc @@ -70,9 +70,29 @@ function addLogViewRefToLogThresholdRule( return doc; } +function addOutcomeOrder( + doc: SavedObjectUnsanitizedDoc +): SavedObjectUnsanitizedDoc { + if (!doc.attributes.lastRun) { + return doc; + } + + const outcome = doc.attributes.lastRun.outcome; + return { + ...doc, + attributes: { + ...doc.attributes, + lastRun: { + ...doc.attributes.lastRun, + outcomeOrder: RuleLastRunOutcomeOrderMap[outcome], + }, + }, + }; +} + export const getMigrations870 = (encryptedSavedObjects: EncryptedSavedObjectsPluginSetup) => createEsoMigration( encryptedSavedObjects, (doc: SavedObjectUnsanitizedDoc): doc is SavedObjectUnsanitizedDoc => true, - pipeMigrations(addGroupByToEsQueryRule, addLogViewRefToLogThresholdRule) + pipeMigrations(addGroupByToEsQueryRule, addLogViewRefToLogThresholdRule, addOutcomeOrder) ); diff --git a/x-pack/plugins/alerting/server/saved_objects/migrations/index.test.ts b/x-pack/plugins/alerting/server/saved_objects/migrations/index.test.ts index 5043cad69b696..7ecf7596259fa 100644 --- a/x-pack/plugins/alerting/server/saved_objects/migrations/index.test.ts +++ b/x-pack/plugins/alerting/server/saved_objects/migrations/index.test.ts @@ -2606,6 +2606,28 @@ describe('successful migrations', () => { expect(migratedAlert870.references).toEqual([]); }); }); + + test('migrates last run outcome order', () => { + const migration870 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)['8.7.0']; + + // Failed rule + const failedRule = getMockData({ lastRun: { outcome: 'failed' } }); + const failedRule870 = migration870(failedRule, migrationContext); + expect(failedRule870.attributes.lastRun).toEqual({ outcome: 'failed', outcomeOrder: 20 }); + + // Rule with warnings + const warningRule = getMockData({ lastRun: { outcome: 'warning' } }); + const warningRule870 = migration870(warningRule, migrationContext); + expect(warningRule870.attributes.lastRun).toEqual({ outcome: 'warning', outcomeOrder: 10 }); + + // Succeeded rule + const succeededRule = getMockData({ lastRun: { outcome: 'succeeded' } }); + const succeededRule870 = migration870(succeededRule, migrationContext); + expect(succeededRule870.attributes.lastRun).toEqual({ + outcome: 'succeeded', + outcomeOrder: 0, + }); + }); }); describe('Metrics Inventory Threshold rule', () => { diff --git a/x-pack/plugins/alerting/server/task_runner/fixtures.ts b/x-pack/plugins/alerting/server/task_runner/fixtures.ts index 7784247ca75f3..7acb36c34bd9c 100644 --- a/x-pack/plugins/alerting/server/task_runner/fixtures.ts +++ b/x-pack/plugins/alerting/server/task_runner/fixtures.ts @@ -6,7 +6,14 @@ */ import { TaskStatus } from '@kbn/task-manager-plugin/server'; -import { Rule, RuleTypeParams, RecoveredActionGroup, RuleMonitoring } from '../../common'; +import { + Rule, + RuleTypeParams, + RecoveredActionGroup, + RuleMonitoring, + RuleLastRunOutcomeOrderMap, + RuleLastRunOutcomes, +} from '../../common'; import { getDefaultMonitoring } from '../lib/monitoring'; import { UntypedNormalizedRuleType } from '../rule_type_registry'; import { EVENT_LOG_ACTIONS } from '../plugin'; @@ -74,7 +81,7 @@ export const generateSavedObjectParams = ({ error?: null | { reason: string; message: string }; warning?: null | { reason: string; message: string }; status?: string; - outcome?: string; + outcome?: RuleLastRunOutcomes; nextRun?: string | null; successRatio?: number; history?: RuleMonitoring['run']['history']; @@ -110,6 +117,7 @@ export const generateSavedObjectParams = ({ }, lastRun: { outcome, + outcomeOrder: RuleLastRunOutcomeOrderMap[outcome], outcomeMsg: (error?.message && [error?.message]) || (warning?.message && [warning?.message]) || null, warning: error?.reason || warning?.reason || null, diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts index d840a9397e248..8b7284bb8ffd4 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts @@ -279,7 +279,7 @@ describe('Task Runner', () => { ); expect(logger.debug).nthCalledWith( 3, - 'ruleRunStatus for test:1: {"outcome":"succeeded","outcomeMsg":null,"warning":null,"alertsCount":{"active":0,"new":0,"recovered":0,"ignored":0}}' + 'ruleRunStatus for test:1: {"outcome":"succeeded","outcomeOrder":0,"outcomeMsg":null,"warning":null,"alertsCount":{"active":0,"new":0,"recovered":0,"ignored":0}}' ); expect(logger.debug).nthCalledWith( 4, @@ -360,7 +360,7 @@ describe('Task Runner', () => { ); expect(logger.debug).nthCalledWith( 4, - 'ruleRunStatus for test:1: {"outcome":"succeeded","outcomeMsg":null,"warning":null,"alertsCount":{"active":1,"new":1,"recovered":0,"ignored":0}}' + 'ruleRunStatus for test:1: {"outcome":"succeeded","outcomeOrder":0,"outcomeMsg":null,"warning":null,"alertsCount":{"active":1,"new":1,"recovered":0,"ignored":0}}' ); expect(logger.debug).nthCalledWith( 5, @@ -447,7 +447,7 @@ describe('Task Runner', () => { ); expect(logger.debug).nthCalledWith( 5, - 'ruleRunStatus for test:1: {"outcome":"succeeded","outcomeMsg":null,"warning":null,"alertsCount":{"active":1,"new":1,"recovered":0,"ignored":0}}' + 'ruleRunStatus for test:1: {"outcome":"succeeded","outcomeOrder":0,"outcomeMsg":null,"warning":null,"alertsCount":{"active":1,"new":1,"recovered":0,"ignored":0}}' ); expect(logger.debug).nthCalledWith( 6, @@ -627,7 +627,7 @@ describe('Task Runner', () => { ); expect(logger.debug).nthCalledWith( 5, - 'ruleRunStatus for test:1: {"outcome":"succeeded","outcomeMsg":null,"warning":null,"alertsCount":{"active":2,"new":2,"recovered":0,"ignored":0}}' + 'ruleRunStatus for test:1: {"outcome":"succeeded","outcomeOrder":0,"outcomeMsg":null,"warning":null,"alertsCount":{"active":2,"new":2,"recovered":0,"ignored":0}}' ); expect(logger.debug).nthCalledWith( 6, @@ -1066,7 +1066,7 @@ describe('Task Runner', () => { ); expect(logger.debug).nthCalledWith( 5, - 'ruleRunStatus for test:1: {"outcome":"succeeded","outcomeMsg":null,"warning":null,"alertsCount":{"active":1,"new":0,"recovered":1,"ignored":0}}' + 'ruleRunStatus for test:1: {"outcome":"succeeded","outcomeOrder":0,"outcomeMsg":null,"warning":null,"alertsCount":{"active":1,"new":0,"recovered":1,"ignored":0}}' ); expect(logger.debug).nthCalledWith( 6, @@ -1192,7 +1192,7 @@ describe('Task Runner', () => { ); expect(logger.debug).nthCalledWith( 5, - 'ruleRunStatus for test:1: {"outcome":"succeeded","outcomeMsg":null,"warning":null,"alertsCount":{"active":1,"new":0,"recovered":1,"ignored":0}}' + 'ruleRunStatus for test:1: {"outcome":"succeeded","outcomeOrder":0,"outcomeMsg":null,"warning":null,"alertsCount":{"active":1,"new":0,"recovered":1,"ignored":0}}' ); expect(logger.debug).nthCalledWith( 6, @@ -2330,7 +2330,7 @@ describe('Task Runner', () => { ); expect(logger.debug).nthCalledWith( 3, - 'ruleRunStatus for test:1: {"outcome":"succeeded","outcomeMsg":null,"warning":null,"alertsCount":{"active":0,"new":0,"recovered":0,"ignored":0}}' + 'ruleRunStatus for test:1: {"outcome":"succeeded","outcomeOrder":0,"outcomeMsg":null,"warning":null,"alertsCount":{"active":0,"new":0,"recovered":0,"ignored":0}}' ); expect(logger.debug).nthCalledWith( 4, diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.ts index 808fe89baa7c8..801bd91c23b33 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.ts @@ -45,6 +45,7 @@ import { RuleTypeState, parseDuration, RawAlertInstance, + RuleLastRunOutcomeOrderMap, } from '../../common'; import { NormalizedRuleType, UntypedNormalizedRuleType } from '../rule_type_registry'; import { getEsErrorMessage } from '../lib/errors'; @@ -821,10 +822,12 @@ export class TaskRunner< this.logger.debug( `Updating rule task for ${this.ruleType.id} rule with id ${ruleId} - execution error due to timeout` ); + const outcome = 'failed'; await this.updateRuleSavedObjectPostRun(ruleId, namespace, { executionStatus: ruleExecutionStatusToRaw(executionStatus), lastRun: { - outcome: 'failed', + outcome, + outcomeOrder: RuleLastRunOutcomeOrderMap[outcome], warning: RuleExecutionStatusErrorReasons.Timeout, outcomeMsg, alertsCount: {}, diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner_cancel.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner_cancel.test.ts index 6c094d330bf16..84ee0f101d8b8 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner_cancel.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner_cancel.test.ts @@ -235,6 +235,7 @@ describe('Task Runner Cancel', () => { outcomeMsg: [ 'test:1: execution cancelled due to timeout - exceeded rule type timeout of 5m', ], + outcomeOrder: 20, warning: 'timeout', }, monitoring: { diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/request_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/request_schema.test.ts index 67a3d045d746d..d0fc36831ad23 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/request_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/request_schema.test.ts @@ -29,7 +29,7 @@ describe('Find rules request schema', () => { const payload: FindRulesRequestQuery = { per_page: 5, page: 1, - sort_field: 'some field', + sort_field: 'name', fields: ['field 1', 'field 2'], filter: 'some filter', sort_order: 'asc', @@ -80,14 +80,14 @@ describe('Find rules request schema', () => { test('sort_field validates', () => { const payload: FindRulesRequestQuery = { - sort_field: 'value', + sort_field: 'name', }; const decoded = FindRulesRequestQuery.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as FindRulesRequestQuery).sort_field).toEqual('value'); + expect((message.schema as FindRulesRequestQuery).sort_field).toEqual('name'); }); test('fields validates with a string', () => { @@ -173,7 +173,7 @@ describe('Find rules request schema', () => { test('sort_order validates with desc and sort_field', () => { const payload: FindRulesRequestQuery = { sort_order: 'desc', - sort_field: 'some field', + sort_field: 'name', }; const decoded = FindRulesRequestQuery.decode(payload); @@ -187,7 +187,7 @@ describe('Find rules request schema', () => { test('sort_order does not validate with a string other than asc and desc', () => { const payload: Omit & { sort_order: string } = { sort_order: 'some other string', - sort_field: 'some field', + sort_field: 'name', }; const decoded = FindRulesRequestQuery.decode(payload); diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/request_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/request_schema.ts index 9b321d443b2de..08d05a54a5753 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/request_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/request_schema.ts @@ -8,7 +8,28 @@ import * as t from 'io-ts'; import { DefaultPerPage, DefaultPage } from '@kbn/securitysolution-io-ts-alerting-types'; import type { PerPage, Page } from '../../../../schemas/common'; -import { queryFilter, fields, SortField, SortOrder } from '../../../../schemas/common'; +import { queryFilter, fields, SortOrder } from '../../../../schemas/common'; + +export type FindRulesSortField = t.TypeOf; +export const FindRulesSortField = t.union([ + t.literal('created_at'), + t.literal('createdAt'), // Legacy notation, keeping for backwards compatibility + t.literal('enabled'), + t.literal('execution_summary.last_execution.date'), + t.literal('execution_summary.last_execution.metrics.execution_gap_duration_s'), + t.literal('execution_summary.last_execution.metrics.total_indexing_duration_ms'), + t.literal('execution_summary.last_execution.metrics.total_search_duration_ms'), + t.literal('execution_summary.last_execution.status'), + t.literal('name'), + t.literal('risk_score'), + t.literal('riskScore'), // Legacy notation, keeping for backwards compatibility + t.literal('severity'), + t.literal('updated_at'), + t.literal('updatedAt'), // Legacy notation, keeping for backwards compatibility +]); + +export type FindRulesSortFieldOrUndefined = t.TypeOf; +export const FindRulesSortFieldOrUndefined = t.union([FindRulesSortField, t.undefined]); /** * Query string parameters of the API route. @@ -18,7 +39,7 @@ export const FindRulesRequestQuery = t.exact( t.partial({ fields, filter: queryFilter, - sort_field: SortField, + sort_field: FindRulesSortField, sort_order: SortOrder, page: DefaultPage, // defaults to 1 per_page: DefaultPerPage, // defaults to 20 diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/request_schema_validation.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/request_schema_validation.test.ts index 862bf7cc1a350..6d0cd10b21700 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/request_schema_validation.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/request_schema_validation.test.ts @@ -18,7 +18,7 @@ describe('Find rules request schema, additional validation', () => { test('You can have both a sort_field and and a sort_order', () => { const schema: FindRulesRequestQuery = { - sort_field: 'some field', + sort_field: 'name', sort_order: 'asc', }; const errors = validateFindRulesRequestQuery(schema); @@ -27,7 +27,7 @@ describe('Find rules request schema, additional validation', () => { test('You cannot have sort_field without sort_order', () => { const schema: FindRulesRequestQuery = { - sort_field: 'some field', + sort_field: 'name', }; const errors = validateFindRulesRequestQuery(schema); expect(errors).toEqual([ diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/sorting.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/sorting.ts index 2cf1712e5ffbc..8aa8cf2831ea1 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/sorting.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/sorting.ts @@ -9,12 +9,6 @@ import * as t from 'io-ts'; import type { Either } from 'fp-ts/lib/Either'; import { capitalize } from 'lodash'; -export type SortField = t.TypeOf; -export const SortField = t.string; - -export type SortFieldOrUndefined = t.TypeOf; -export const SortFieldOrUndefined = t.union([SortField, t.undefined]); - export type SortOrder = t.TypeOf; export const SortOrder = t.keyof({ asc: null, desc: null }); diff --git a/x-pack/plugins/security_solution/public/common/lib/apm/user_actions.ts b/x-pack/plugins/security_solution/public/common/lib/apm/user_actions.ts index 44703b4f5707c..64c11e06ae8d1 100644 --- a/x-pack/plugins/security_solution/public/common/lib/apm/user_actions.ts +++ b/x-pack/plugins/security_solution/public/common/lib/apm/user_actions.ts @@ -30,8 +30,6 @@ export const RULES_TABLE_ACTIONS = { REFRESH: `${APP_UI_ID} rulesTable refresh`, FILTER: `${APP_UI_ID} rulesTable filter`, LOAD_PREBUILT: `${APP_UI_ID} rulesTable loadPrebuilt`, - PREVIEW_ON: `${APP_UI_ID} rulesTable technicalPreview on`, - PREVIEW_OFF: `${APP_UI_ID} rulesTable technicalPreview off`, }; export const TIMELINE_ACTIONS = { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_rules_options/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_rules_options/index.test.tsx index fe65b3ce0384e..6515d8f5243e3 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_rules_options/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_rules_options/index.test.tsx @@ -10,17 +10,15 @@ import { mountWithIntl } from '@kbn/test-jest-helpers'; import { ExceptionsAddToRulesOptions } from '.'; import { TestProviders } from '../../../../../common/mock'; -import { useFindRulesInMemory } from '../../../../rule_management_ui/components/rules_table/rules_table/use_find_rules_in_memory'; +import { useFindRules } from '../../../../rule_management/logic/use_find_rules'; import { getRulesSchemaMock } from '../../../../../../common/detection_engine/rule_schema/mocks'; import type { Rule } from '../../../../rule_management/logic/types'; -jest.mock( - '../../../../rule_management_ui/components/rules_table/rules_table/use_find_rules_in_memory' -); +jest.mock('../../../../rule_management/logic/use_find_rules'); describe('ExceptionsAddToRulesOptions', () => { beforeEach(() => { - (useFindRulesInMemory as jest.Mock).mockReturnValue({ + (useFindRules as jest.Mock).mockReturnValue({ data: { rules: [getRulesSchemaMock(), { ...getRulesSchemaMock(), id: '345', name: 'My rule' }], total: 0, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_rules_table/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_rules_table/index.test.tsx index 7d56fcc298f90..4be59efe2e927 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_rules_table/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_rules_table/index.test.tsx @@ -10,18 +10,15 @@ import { mountWithIntl } from '@kbn/test-jest-helpers'; import { ExceptionsAddToRulesTable } from '.'; import { TestProviders } from '../../../../../common/mock'; -import { useFindRulesInMemory } from '../../../../rule_management_ui/components/rules_table/rules_table/use_find_rules_in_memory'; +import { useFindRules } from '../../../../rule_management/logic/use_find_rules'; import { getRulesSchemaMock } from '../../../../../../common/detection_engine/rule_schema/mocks'; import type { Rule } from '../../../../rule_management/logic/types'; -jest.mock( - '../../../../rule_management_ui/components/rules_table/rules_table/use_find_rules_in_memory' -); -// TODO convert this test to RTL +jest.mock('../../../../rule_management/logic/use_find_rules'); describe('ExceptionsAddToRulesTable', () => { it('it displays loading state while fetching rules', () => { - (useFindRulesInMemory as jest.Mock).mockReturnValue({ + (useFindRules as jest.Mock).mockReturnValue({ data: { rules: [], total: 0 }, isFetched: false, }); @@ -37,7 +34,7 @@ describe('ExceptionsAddToRulesTable', () => { }); it.skip('it displays fetched rules', () => { - (useFindRulesInMemory as jest.Mock).mockReturnValue({ + (useFindRules as jest.Mock).mockReturnValue({ data: { rules: [getRulesSchemaMock(), { ...getRulesSchemaMock(), id: '345', name: 'My rule' }], total: 0, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_rules_table/use_add_to_rules_table.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_rules_table/use_add_to_rules_table.tsx index dc37a480afcd4..e18cc71802c31 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_rules_table/use_add_to_rules_table.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_rules_table/use_add_to_rules_table.tsx @@ -15,10 +15,10 @@ import { i18n } from '@kbn/i18n'; import * as myI18n from './translations'; import * as commonI18n from '../translations'; -import { useFindRulesInMemory } from '../../../../rule_management_ui/components/rules_table/rules_table/use_find_rules_in_memory'; import type { Rule } from '../../../../rule_management/logic/types'; import { getRulesTableColumn } from '../utils'; import { LinkRuleSwitch } from './link_rule_switch'; +import { useFindRules } from '../../../../rule_management/logic/use_find_rules'; export interface ExceptionsAddToRulesComponentProps { initiallySelectedRules?: Rule[]; @@ -28,8 +28,7 @@ export const useAddToRulesTable = ({ initiallySelectedRules, onRuleSelectionChange, }: ExceptionsAddToRulesComponentProps) => { - const { data: { rules } = { rules: [], total: 0 }, isFetched } = useFindRulesInMemory({ - isInMemorySorting: true, + const { data: { rules } = { rules: [], total: 0 }, isFetched } = useFindRules({ filterOptions: { filter: '', showCustomRules: false, @@ -38,7 +37,6 @@ export const useAddToRulesTable = ({ }, sortingOptions: undefined, pagination: undefined, - refetchInterval: false, }); const [pagination, setPagination] = useState({ diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.test.ts index a1d59efa4c30a..fe111c13debec 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.test.ts @@ -287,7 +287,7 @@ describe('Detections Rules API', () => { tags: ['hello', 'world'], }, sortingOptions: { - field: 'updated_at', + field: 'updatedAt', order: 'desc', }, signal: abortCtrl.signal, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts index aec165167db59..87334a121c993 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { camelCase } from 'lodash'; import type { CreateRuleExceptionListItemSchema, ExceptionListItemSchema, @@ -152,15 +151,10 @@ export const fetchRules = async ({ }: FetchRulesProps): Promise => { const filterString = convertRulesFilterToKQL(filterOptions); - // Sort field is camel cased because we use that in our mapping, but display snake case on the front end - const getFieldNameForSortField = (field: string) => { - return field === 'name' ? `${field}.keyword` : camelCase(field); - }; - const query = { page: pagination.page, per_page: pagination.perPage, - sort_field: getFieldNameForSortField(sortingOptions.field), + sort_field: sortingOptions.field, sort_order: sortingOptions.order, ...(filterString !== '' ? { filter: filterString } : {}), }; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts index 246a000356761..1eadf14b02b8c 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts @@ -73,6 +73,7 @@ import { } from '../../../../common/detection_engine/rule_schema'; import type { PatchRuleRequestBody } from '../../../../common/detection_engine/rule_management'; +import { FindRulesSortField } from '../../../../common/detection_engine/rule_management'; import type { RuleCreateProps, RuleUpdateProps, @@ -217,25 +218,9 @@ export interface FetchRulesProps { signal?: AbortSignal; } -export type RulesSortingFields = t.TypeOf; -export const RulesSortingFields = t.union([ - t.literal('created_at'), - t.literal('enabled'), - t.literal('execution_summary.last_execution.date'), - t.literal('execution_summary.last_execution.metrics.execution_gap_duration_s'), - t.literal('execution_summary.last_execution.metrics.total_indexing_duration_ms'), - t.literal('execution_summary.last_execution.metrics.total_search_duration_ms'), - t.literal('execution_summary.last_execution.status'), - t.literal('name'), - t.literal('risk_score'), - t.literal('severity'), - t.literal('updated_at'), - t.literal('version'), -]); - export type SortingOptions = t.TypeOf; export const SortingOptions = t.type({ - field: RulesSortingFields, + field: FindRulesSortField, order: SortOrder, }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_find_rules.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_find_rules.ts index 653f1de8e3b3f..df8a79aaafcbc 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_find_rules.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_find_rules.ts @@ -27,7 +27,12 @@ export interface RulesQueryData { */ export const useFindRules = ( requestArgs: FindRulesQueryArgs, - options: UseQueryOptions + options?: UseQueryOptions< + RulesQueryData, + Error, + RulesQueryData, + [...string[], FindRulesQueryArgs] + > ) => { const { addError } = useAppToasts(); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/__mocks__/rules_table_context.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/__mocks__/rules_table_context.tsx index 1a03370ea5915..688b90c860ceb 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/__mocks__/rules_table_context.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/__mocks__/rules_table_context.tsx @@ -32,7 +32,6 @@ export const useRulesTableContextMock = { isAllSelected: false, isFetched: true, isFetching: false, - isInMemorySorting: false, isLoading: false, isRefetching: false, isRefreshOn: true, @@ -46,7 +45,6 @@ export const useRulesTableContextMock = { reFetchRules: jest.fn(), setFilterOptions: jest.fn(), setIsAllSelected: jest.fn(), - setIsInMemorySorting: jest.fn(), setIsRefreshOn: jest.fn(), setLoadingRules: jest.fn(), setPage: jest.fn(), diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/rules_table_context.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/rules_table_context.test.tsx index 552ca7b0cbd88..cb2eb08c5381b 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/rules_table_context.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/rules_table_context.test.tsx @@ -9,6 +9,7 @@ import { renderHook } from '@testing-library/react-hooks'; import type { PropsWithChildren } from 'react'; import React from 'react'; import { useUiSetting$ } from '../../../../../common/lib/kibana'; +import { useFindRules } from '../../../../rule_management/logic/use_find_rules'; import type { RulesTableState } from './rules_table_context'; import { RulesTableContextProvider, useRulesTableContext } from './rules_table_context'; import { @@ -18,17 +19,16 @@ import { DEFAULT_SORTING_OPTIONS, } from './rules_table_defaults'; import { RuleSource } from './rules_table_saved_state'; -import { useFindRulesInMemory } from './use_find_rules_in_memory'; import { useRulesTableSavedState } from './use_rules_table_saved_state'; jest.mock('../../../../../common/lib/kibana'); -jest.mock('./use_find_rules_in_memory'); +jest.mock('../../../../rule_management/logic/use_find_rules'); jest.mock('./use_rules_table_saved_state'); function renderUseRulesTableContext( savedState: ReturnType ): RulesTableState { - (useFindRulesInMemory as jest.Mock).mockReturnValue({ + (useFindRules as jest.Mock).mockReturnValue({ data: { rules: [], total: 0 }, refetch: jest.fn(), dataUpdatedAt: 0, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/rules_table_context.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/rules_table_context.tsx index 5667b544b7b72..f6ec9a3714f66 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/rules_table_context.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/rules_table_context.tsx @@ -5,38 +5,37 @@ * 2.0. */ +import { isEqual } from 'lodash'; import React, { createContext, useCallback, useContext, useEffect, useMemo, - useState, useRef, + useState, } from 'react'; -import { isEqual } from 'lodash'; import { DEFAULT_RULES_TABLE_REFRESH_SETTING } from '../../../../../../common/constants'; import { invariant } from '../../../../../../common/utils/invariant'; -import { useReplaceUrlParams } from '../../../../../common/utils/global_query_string/helpers'; -import { useKibana, useUiSetting$ } from '../../../../../common/lib/kibana'; import { URL_PARAM_KEY } from '../../../../../common/hooks/use_url_state'; +import { useKibana, useUiSetting$ } from '../../../../../common/lib/kibana'; +import { useReplaceUrlParams } from '../../../../../common/utils/global_query_string/helpers'; import type { FilterOptions, PaginationOptions, Rule, SortingOptions, } from '../../../../rule_management/logic/types'; +import { useFindRules } from '../../../../rule_management/logic/use_find_rules'; +import { RULES_TABLE_STATE_STORAGE_KEY } from '../constants'; import { + DEFAULT_FILTER_OPTIONS, DEFAULT_PAGE, DEFAULT_RULES_PER_PAGE, - DEFAULT_FILTER_OPTIONS, DEFAULT_SORTING_OPTIONS, } from './rules_table_defaults'; import { RuleSource } from './rules_table_saved_state'; -import { useFindRulesInMemory } from './use_find_rules_in_memory'; import { useRulesTableSavedState } from './use_rules_table_saved_state'; -import { getRulesComparator } from './utils'; -import { RULES_TABLE_STATE_STORAGE_KEY } from '../constants'; export interface RulesTableState { /** @@ -63,10 +62,6 @@ export interface RulesTableState { * Is true whenever a request is in-flight, which includes initial loading as well as background refetches. */ isFetching: boolean; - /** - * Is true when we store and sort all rules in-memory. - */ - isInMemorySorting: boolean; /** * Is true then there is no cached data and the query is currently fetching. */ @@ -129,10 +124,9 @@ export interface LoadingRules { } export interface RulesTableActions { - reFetchRules: ReturnType['refetch']; + reFetchRules: ReturnType['refetch']; setFilterOptions: (newFilter: Partial) => void; setIsAllSelected: React.Dispatch>; - setIsInMemorySorting: (value: boolean) => void; setIsPreflightInProgress: React.Dispatch>; /** * enable/disable rules table auto refresh @@ -169,24 +163,19 @@ interface RulesTableContextProviderProps { children: React.ReactNode; } -const IN_MEMORY_STORAGE_KEY = 'detection-rules-table-in-memory'; - export const RulesTableContextProvider = ({ children }: RulesTableContextProviderProps) => { const [autoRefreshSettings] = useUiSetting$<{ on: boolean; value: number; idleTimeout: number; }>(DEFAULT_RULES_TABLE_REFRESH_SETTING); - const { storage, sessionStorage } = useKibana().services; + const { sessionStorage } = useKibana().services; const { filter: savedFilter, sorting: savedSorting, pagination: savedPagination, } = useRulesTableSavedState(); - const [isInMemorySorting, setIsInMemorySorting] = useState( - storage.get(IN_MEMORY_STORAGE_KEY) ?? false - ); const [filterOptions, setFilterOptions] = useState({ filter: savedFilter?.searchTerm ?? DEFAULT_FILTER_OPTIONS.filter, tags: savedFilter?.tags ?? DEFAULT_FILTER_OPTIONS.tags, @@ -200,6 +189,7 @@ export const RulesTableContextProvider = ({ children }: RulesTableContextProvide field: savedSorting?.field ?? DEFAULT_SORTING_OPTIONS.field, order: savedSorting?.order ?? DEFAULT_SORTING_OPTIONS.order, }); + const [isAllSelected, setIsAllSelected] = useState(false); const [isRefreshOn, setIsRefreshOn] = useState(autoRefreshSettings.on); const [loadingRules, setLoadingRules] = useState({ @@ -212,19 +202,6 @@ export const RulesTableContextProvider = ({ children }: RulesTableContextProvide const [selectedRuleIds, setSelectedRuleIds] = useState([]); const autoRefreshBeforePause = useRef(null); - const toggleInMemorySorting = useCallback( - (value: boolean) => { - setIsInMemorySorting(value); // Update state so the table gets re-rendered - storage.set(IN_MEMORY_STORAGE_KEY, value); // Persist new value in the local storage - - // Reset sorting options when switching to server-side implementation as currently selected sorting might not be supported - if (value === false) { - setSortingOptions(DEFAULT_SORTING_OPTIONS); - } - }, - [storage] - ); - const isActionInProgress = loadingRules.ids.length > 0; const pagination = useMemo(() => ({ page, perPage }), [page, perPage]); @@ -285,25 +262,23 @@ export const RulesTableContextProvider = ({ children }: RulesTableContextProvide isFetching, isLoading, isRefetching, - } = useFindRulesInMemory({ - isInMemorySorting, - filterOptions, - sortingOptions, - pagination, - refetchInterval: isRefreshOn && !isActionInProgress && autoRefreshSettings.value, - }); - - // Paginate and sort rules - const rulesToDisplay = isInMemorySorting - ? rules.sort(getRulesComparator(sortingOptions)).slice((page - 1) * perPage, page * perPage) - : rules; + } = useFindRules( + { + filterOptions, + sortingOptions, + pagination, + }, + { + refetchInterval: isRefreshOn && !isActionInProgress && autoRefreshSettings.value, + keepPreviousData: true, // Use this option so that the state doesn't jump between "success" and "loading" on page change + } + ); const actions = useMemo( () => ({ reFetchRules: refetch, setFilterOptions: handleFilterOptionsChange, setIsAllSelected, - setIsInMemorySorting: toggleInMemorySorting, setIsRefreshOn, setLoadingRules, setPage, @@ -318,7 +293,6 @@ export const RulesTableContextProvider = ({ children }: RulesTableContextProvide refetch, handleFilterOptionsChange, setIsAllSelected, - toggleInMemorySorting, setIsRefreshOn, setLoadingRules, setPage, @@ -334,7 +308,7 @@ export const RulesTableContextProvider = ({ children }: RulesTableContextProvide const providerValue = useMemo( () => ({ state: { - rules: rulesToDisplay, + rules, pagination: { page, perPage, @@ -346,7 +320,6 @@ export const RulesTableContextProvider = ({ children }: RulesTableContextProvide isAllSelected, isFetched, isFetching, - isInMemorySorting, isLoading, isRefetching, isRefreshOn, @@ -358,17 +331,15 @@ export const RulesTableContextProvider = ({ children }: RulesTableContextProvide isDefault: isDefaultState(filterOptions, sortingOptions, { page, perPage, - total: isInMemorySorting ? rules.length : total, + total, }), }, actions, }), [ - rulesToDisplay, + rules, page, perPage, - isInMemorySorting, - rules.length, total, filterOptions, isPreflightInProgress, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/use_find_rules_in_memory.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/use_find_rules_in_memory.ts deleted file mode 100644 index c1429fddbd312..0000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/use_find_rules_in_memory.ts +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { FindRulesQueryArgs } from '../../../../rule_management/api/hooks/use_find_rules_query'; -import { useFindRules } from '../../../../rule_management/logic/use_find_rules'; - -interface UseFindRulesArgs extends FindRulesQueryArgs { - isInMemorySorting: boolean; - refetchInterval: number | false; -} - -const MAX_RULES_PER_PAGE = 10000; - -/** - * This hook is used to fetch detection rules. Under the hood, it implements a - * "feature switch" that allows switching from an in-memory implementation to a - * backend-based implementation on the fly. - * - * @param args - find rules arguments - * @returns rules query result - */ -export const useFindRulesInMemory = (args: UseFindRulesArgs) => { - const { pagination, filterOptions, sortingOptions, isInMemorySorting, refetchInterval } = args; - - // Use this query result when isInMemorySorting = true - const allRules = useFindRules( - { pagination: { page: 1, perPage: MAX_RULES_PER_PAGE }, filterOptions }, - { - refetchInterval, - enabled: isInMemorySorting, - keepPreviousData: true, // Use this option so that the state doesn't jump between "success" and "loading" on page change - } - ); - - // Use this query result when isInMemorySorting = false - const pagedRules = useFindRules( - { pagination, filterOptions, sortingOptions }, - { - refetchInterval, - enabled: !isInMemorySorting, - keepPreviousData: true, // Use this option so that the state doesn't jump between "success" and "loading" on page change - } - ); - - return isInMemorySorting ? allRules : pagedRules; -}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/utils.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/utils.ts deleted file mode 100644 index 5382a740f8213..0000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/utils.ts +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { get } from 'lodash'; -import type { Rule, SortingOptions } from '../../../../rule_management/logic/types'; - -/** - * Returns a comparator function to be used with .sort() - * - * @param sortingOptions SortingOptions - */ -export function getRulesComparator(sortingOptions: SortingOptions) { - return (ruleA: Rule, ruleB: Rule): number => { - const { field, order } = sortingOptions; - const direction = order === 'asc' ? 1 : -1; - - switch (field) { - case 'enabled': { - const a = get(ruleA, field); - const b = get(ruleB, field); - - return compareNumbers(Number(a), Number(b), direction); - } - case 'version': - case 'risk_score': - case 'execution_summary.last_execution.metrics.execution_gap_duration_s': - case 'execution_summary.last_execution.metrics.total_indexing_duration_ms': - case 'execution_summary.last_execution.metrics.total_search_duration_ms': { - const a = get(ruleA, field) ?? -Infinity; - const b = get(ruleB, field) ?? -Infinity; - - return compareNumbers(a, b, direction); - } - case 'updated_at': - case 'created_at': - case 'execution_summary.last_execution.date': { - const a = get(ruleA, field); - const b = get(ruleB, field); - - return compareNumbers( - a == null ? 0 : new Date(a).getTime(), - b == null ? 0 : new Date(b).getTime(), - direction - ); - } - case 'execution_summary.last_execution.status': - case 'severity': - case 'name': { - const a = get(ruleA, field); - const b = get(ruleB, field); - return (a || '').localeCompare(b || '') * direction; - } - } - }; -} - -/** - * A helper to compare two numbers. - * - * @param a - first number - * @param b - second number - * @param direction - comparison direction +1 for asc or -1 for desc - * @returns comparison result - */ -const compareNumbers = (a: number, b: number, direction: number) => { - // We cannot use `return (a - b);` here as it might result in NaN if one of inputs is Infinity. - if (a > b) { - return direction; - } else if (a < b) { - return -direction; - } - return 0; -}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table_toolbar.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table_toolbar.tsx index 46ab8d50998b8..562177371de58 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table_toolbar.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table_toolbar.tsx @@ -5,23 +5,9 @@ * 2.0. */ -import type { EuiSwitchEvent } from '@elastic/eui'; -import { EuiSwitch, EuiToolTip } from '@elastic/eui'; -import React, { useCallback, useMemo } from 'react'; -import styled from 'styled-components'; +import React, { useMemo } from 'react'; import { TabNavigationWithBreadcrumbs } from '../../../../common/components/navigation/tab_navigation_with_breadcrumbs'; -import { useRulesTableContext } from './rules_table/rules_table_context'; import * as i18n from '../../../../detections/pages/detection_engine/rules/translations'; -import { RULES_TABLE_ACTIONS } from '../../../../common/lib/apm/user_actions'; -import { useStartTransaction } from '../../../../common/lib/apm/use_start_transaction'; - -const ToolbarLayout = styled.div` - display: grid; - grid-template-columns: 1fr auto; - align-items: center; - grid-gap: 16px; - box-shadow: inset 0 -1px 0 ${({ theme }) => theme.eui.euiBorderColor}; -`; export enum AllRulesTabs { management = 'management', @@ -29,11 +15,6 @@ export enum AllRulesTabs { } export const RulesTableToolbar = React.memo(() => { - const { - state: { isInMemorySorting }, - actions: { setIsInMemorySorting }, - } = useRulesTableContext(); - const { startTransaction } = useStartTransaction(); const ruleTabs = useMemo( () => ({ [AllRulesTabs.management]: { @@ -52,33 +33,7 @@ export const RulesTableToolbar = React.memo(() => { [] ); - const handleInMemorySwitch = useCallback( - (e: EuiSwitchEvent) => { - startTransaction({ - name: isInMemorySorting ? RULES_TABLE_ACTIONS.PREVIEW_OFF : RULES_TABLE_ACTIONS.PREVIEW_ON, - }); - setIsInMemorySorting(e.target.checked); - }, - [isInMemorySorting, setIsInMemorySorting, startTransaction] - ); - - return ( - - - - - - - ); + return ; }); RulesTableToolbar.displayName = 'RulesTableToolbar'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_tables.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_tables.tsx index 5d426bfc8ed7e..bbe5659e7481b 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_tables.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_tables.tsx @@ -17,7 +17,7 @@ import { Loader } from '../../../../common/components/loader'; import { useBoolState } from '../../../../common/hooks/use_bool_state'; import { useValueChanged } from '../../../../common/hooks/use_value_changed'; import { PrePackagedRulesPrompt } from '../../../../detections/components/rules/pre_packaged_rules/load_empty_prompt'; -import type { Rule, RulesSortingFields } from '../../../rule_management/logic'; +import type { Rule } from '../../../rule_management/logic'; import * as i18n from '../../../../detections/pages/detection_engine/rules/translations'; import type { EuiBasicTableOnChange } from '../../../../detections/pages/detection_engine/rules/types'; import { BulkActionDryRunConfirmation } from './bulk_actions/bulk_action_dry_run_confirmation'; @@ -39,6 +39,7 @@ import { BulkActionDuplicateExceptionsConfirmation } from './bulk_actions/bulk_d import { useStartMlJobs } from '../../../rule_management/logic/use_start_ml_jobs'; import { RULES_TABLE_PAGE_SIZE_OPTIONS } from './constants'; import { useRuleManagementFilters } from '../../../rule_management/logic/use_rule_management_filters'; +import type { FindRulesSortField } from '../../../../../common/detection_engine/rule_management'; const INITIAL_SORT_FIELD = 'enabled'; @@ -141,7 +142,7 @@ export const RulesTables = React.memo(({ selectedTab }) => { const tableOnChangeCallback = useCallback( ({ page, sort }: EuiBasicTableOnChange) => { setSortingOptions({ - field: (sort?.field as RulesSortingFields) ?? INITIAL_SORT_FIELD, // Narrowing EuiBasicTable sorting types + field: (sort?.field as FindRulesSortField) ?? INITIAL_SORT_FIELD, // Narrowing EuiBasicTable sorting types order: sort?.direction ?? 'desc', }); setPage(page.index + 1); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx index 5f7bdb2a2745c..fdc9f5f3a5b43 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx @@ -235,7 +235,6 @@ export const useRulesColumns = ({ }: UseColumnsProps): TableColumn[] => { const actionsColumn = useActionsColumn({ showExceptionsDuplicateConfirmation }); const ruleNameColumn = useRuleNameColumn(); - const { isInMemorySorting } = useRulesTableContext().state; const [showRelatedIntegrations] = useUiSetting$(SHOW_RELATED_INTEGRATIONS_SETTING); const enabledColumn = useEnabledColumn({ hasCRUDPermissions, @@ -244,7 +243,7 @@ export const useRulesColumns = ({ startMlJobs, }); const executionStatusColumn = useRuleExecutionStatusColumn({ - sortable: !!isInMemorySorting, + sortable: true, width: '16%', isLoadingJobs, mlJobs, @@ -290,7 +289,7 @@ export const useRulesColumns = ({ /> ); }, - sortable: !!isInMemorySorting, + sortable: true, truncateText: true, width: '16%', }, @@ -314,22 +313,6 @@ export const useRulesColumns = ({ width: '18%', truncateText: true, }, - { - field: 'version', - name: i18n.COLUMN_VERSION, - render: (value: Rule['version']) => { - return value == null ? ( - getEmptyTagValue() - ) : ( - - {value} - - ); - }, - sortable: !!isInMemorySorting, - truncateText: true, - width: '65px', - }, enabledColumn, ...(hasCRUDPermissions ? [actionsColumn] : []), ], @@ -338,7 +321,6 @@ export const useRulesColumns = ({ enabledColumn, executionStatusColumn, hasCRUDPermissions, - isInMemorySorting, ruleNameColumn, showRelatedIntegrations, ] @@ -355,7 +337,6 @@ export const useMonitoringColumns = ({ const docLinks = useKibana().services.docLinks; const actionsColumn = useActionsColumn({ showExceptionsDuplicateConfirmation }); const ruleNameColumn = useRuleNameColumn(); - const { isInMemorySorting } = useRulesTableContext().state; const [showRelatedIntegrations] = useUiSetting$(SHOW_RELATED_INTEGRATIONS_SETTING); const enabledColumn = useEnabledColumn({ hasCRUDPermissions, @@ -364,7 +345,7 @@ export const useMonitoringColumns = ({ startMlJobs, }); const executionStatusColumn = useRuleExecutionStatusColumn({ - sortable: !!isInMemorySorting, + sortable: true, width: '12%', isLoadingJobs, mlJobs, @@ -391,7 +372,7 @@ export const useMonitoringColumns = ({ {value != null ? value.toFixed() : getEmptyTagValue()} ), - sortable: !!isInMemorySorting, + sortable: true, truncateText: true, width: '16%', }, @@ -408,7 +389,7 @@ export const useMonitoringColumns = ({ {value != null ? value.toFixed() : getEmptyTagValue()} ), - sortable: !!isInMemorySorting, + sortable: true, truncateText: true, width: '14%', }, @@ -448,7 +429,7 @@ export const useMonitoringColumns = ({ {value != null ? moment.duration(value, 'seconds').humanize() : getEmptyTagValue()} ), - sortable: !!isInMemorySorting, + sortable: true, truncateText: true, width: '14%', }, @@ -468,7 +449,7 @@ export const useMonitoringColumns = ({ /> ); }, - sortable: !!isInMemorySorting, + sortable: true, truncateText: true, width: '16%', }, @@ -481,7 +462,6 @@ export const useMonitoringColumns = ({ enabledColumn, executionStatusColumn, hasCRUDPermissions, - isInMemorySorting, ruleNameColumn, showRelatedIntegrations, ] diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts index c71cc4cf267a4..9511138f78f34 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts @@ -46,28 +46,6 @@ export const PAGE_TITLE = i18n.translate('xpack.securitySolution.detectionEngine defaultMessage: 'Rules', }); -export const EXPERIMENTAL_ON = i18n.translate( - 'xpack.securitySolution.detectionEngine.rules.experimentalOn', - { - defaultMessage: 'Advanced sorting', - } -); - -export const EXPERIMENTAL_OFF = i18n.translate( - 'xpack.securitySolution.detectionEngine.rules.experimentalOff', - { - defaultMessage: 'Advanced sorting', - } -); - -export const EXPERIMENTAL_DESCRIPTION = i18n.translate( - 'xpack.securitySolution.detectionEngine.rules.experimentalDescription', - { - defaultMessage: - 'Turn this on to enable sorting for all table columns. You can turn it off if you encounter table performance issues.', - } -); - export const ADD_PAGE_TITLE = i18n.translate( 'xpack.securitySolution.detectionEngine.rules.addPageTitle', { @@ -561,13 +539,6 @@ export const COLUMN_LAST_RESPONSE = i18n.translate( } ); -export const COLUMN_VERSION = i18n.translate( - 'xpack.securitySolution.detectionEngine.rules.allRules.columns.versionTitle', - { - defaultMessage: 'Version', - } -); - export const COLUMN_TAGS = i18n.translate( 'xpack.securitySolution.detectionEngine.rules.allRules.columns.tagsTitle', { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/find_rules/route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/find_rules/route.test.ts index e34dff9733794..76d63ddcd54b0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/find_rules/route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/find_rules/route.test.ts @@ -67,7 +67,7 @@ describe('Find rules route', () => { query: { page: 2, per_page: 20, - sort_field: 'timestamp', + sort_field: 'name', fields: ['field1', 'field2'], }, }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/search/find_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/search/find_rules.ts index bb46831417d71..d72e3e6900be3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/search/find_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/search/find_rules.ts @@ -6,24 +6,25 @@ */ import type { FindResult, RulesClient } from '@kbn/alerting-plugin/server'; +import type { FindRulesSortFieldOrUndefined } from '../../../../../../common/detection_engine/rule_management'; import type { FieldsOrUndefined, PageOrUndefined, PerPageOrUndefined, QueryFilterOrUndefined, - SortFieldOrUndefined, SortOrderOrUndefined, } from '../../../../../../common/detection_engine/schemas/common'; import type { RuleParams } from '../../../rule_schema'; import { enrichFilterWithRuleTypeMapping } from './enrich_filter_with_rule_type_mappings'; +import { transformSortField } from './transform_sort_field'; export interface FindRuleOptions { rulesClient: RulesClient; filter: QueryFilterOrUndefined; fields: FieldsOrUndefined; - sortField: SortFieldOrUndefined; + sortField: FindRulesSortFieldOrUndefined; sortOrder: SortOrderOrUndefined; page: PageOrUndefined; perPage: PerPageOrUndefined; @@ -45,7 +46,7 @@ export const findRules = ({ perPage, filter: enrichFilterWithRuleTypeMapping(filter), sortOrder, - sortField, + sortField: transformSortField(sortField), }, }); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/search/transform_sort_field.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/search/transform_sort_field.ts new file mode 100644 index 0000000000000..dc94afa07ddb0 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/search/transform_sort_field.ts @@ -0,0 +1,82 @@ +/* + * 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 type { FindRulesSortFieldOrUndefined } from '../../../../../../common/detection_engine/rule_management'; +import { assertUnreachable } from '../../../../../../common/utility_types'; + +/** + * Transform the sort field name from the request to the Alerting framework + * schema. + * + * It could be a bit confusing what field is used for sorting rules as it is + * hidden under several layers of abstraction. + * + * Sort field as defined in the UI rule schema -> API transformation (this + * function) -> Alerting framework transformation -> Saved Object Client + * transformation -> Elasticsearch field + * + * The alerting framework applies the following transformations to the sort + * field: + * - Some sort fields like `name` are converted to `name.keyword` by the + * Alerting framework automatically + * - Sort fields that have corresponding mapped params, like `riskScore`, will + * be prefixed with `mapped_params.` and converted to a snake case + * automatically. So `riskScore` will become `mapped_params.risk_score` + * - Other fields will be passed to the saved object client as is. + * + * Saved objects client also applies some transformations to the sort field: + * - First, it appends the saved object type to the sort field. So `name` will + * become `alert.name` + * - If the sort field doesn't exist in saved object mappings, then it tries the + * field without prefixes, e.g., just `name`. + * + * @param sortField Sort field parameter from the request + * @returns Sort field matching the Alerting framework schema + */ +export function transformSortField(sortField: FindRulesSortFieldOrUndefined): string | undefined { + if (!sortField) { + return undefined; + } + + switch (sortField) { + // Without this conversion, rules will be sorted by the top-level SO fields. + // That is seldom what is expected from sorting. After conversion, the + // fields will be treated as `alert.updatedAt` and `alert.createdAt` + case 'created_at': + return 'createdAt'; + case 'updated_at': + return 'updatedAt'; + + // Convert front-end representation to the field names that match the Alerting framework mappings + case 'execution_summary.last_execution.date': + return 'monitoring.run.last_run.timestamp'; + case 'execution_summary.last_execution.metrics.execution_gap_duration_s': + return 'monitoring.run.last_run.metrics.gap_duration_s'; + case 'execution_summary.last_execution.metrics.total_indexing_duration_ms': + return 'monitoring.run.last_run.metrics.total_indexing_duration_ms'; + case 'execution_summary.last_execution.metrics.total_search_duration_ms': + return 'monitoring.run.last_run.metrics.total_search_duration_ms'; + case 'execution_summary.last_execution.status': + return 'lastRun.outcomeOrder'; + + // Pass these fields as is. They will be converted to `alert.` by the saved object client + case 'createdAt': + case 'updatedAt': + case 'enabled': + case 'name': + return sortField; + + // Mapped fields will be converted to `mapped_params.risk_score` by the Alerting framework automatically + case 'riskScore': + case 'risk_score': + case 'severity': + return `params.${sortField}`; + + default: + assertUnreachable(sortField); + } +} diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 69a2dfeda1909..ece5a126c4ad2 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -28765,7 +28765,6 @@ "xpack.securitySolution.detectionEngine.rules.allRules.columns.ruleTitle": "Règle", "xpack.securitySolution.detectionEngine.rules.allRules.columns.severityTitle": "Sévérité", "xpack.securitySolution.detectionEngine.rules.allRules.columns.tagsTitle": "Balises", - "xpack.securitySolution.detectionEngine.rules.allRules.columns.versionTitle": "Version", "xpack.securitySolution.detectionEngine.rules.allRules.exportFilenameTitle": "rules_export", "xpack.securitySolution.detectionEngine.rules.allRules.featureTour.nextStepLabel": "Aller à l'étape suivante", "xpack.securitySolution.detectionEngine.rules.allRules.featureTour.previousStepLabel": "Revenir à l'étape précédente", @@ -28789,9 +28788,6 @@ "xpack.securitySolution.detectionEngine.rules.defineRuleTitle": "Définir la règle", "xpack.securitySolution.detectionEngine.rules.deleteDescription": "Supprimer", "xpack.securitySolution.detectionEngine.rules.editPageTitle": "Modifier", - "xpack.securitySolution.detectionEngine.rules.experimentalDescription": "Activez cette option pour autoriser le tri sur toutes les colonnes du tableau. Vous pouvez la désactiver si vous rencontrez des problèmes de performances dans votre tableau.", - "xpack.securitySolution.detectionEngine.rules.experimentalOff": "Tri avancé", - "xpack.securitySolution.detectionEngine.rules.experimentalOn": "Tri avancé", "xpack.securitySolution.detectionEngine.rules.guidedOnboarding.installPrebuiltRules.content": "Pour commencer, vous devez charger les règles prédéfinies d'Elastic.", "xpack.securitySolution.detectionEngine.rules.guidedOnboarding.installPrebuiltRules.title": "Charger les règles prédéfinies d'Elastic", "xpack.securitySolution.detectionEngine.rules.guidedOnboarding.nextButton": "Suivant", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index aafb384cf9400..2a66fc568237c 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -28737,7 +28737,6 @@ "xpack.securitySolution.detectionEngine.rules.allRules.columns.ruleTitle": "ルール", "xpack.securitySolution.detectionEngine.rules.allRules.columns.severityTitle": "深刻度", "xpack.securitySolution.detectionEngine.rules.allRules.columns.tagsTitle": "タグ", - "xpack.securitySolution.detectionEngine.rules.allRules.columns.versionTitle": "バージョン", "xpack.securitySolution.detectionEngine.rules.allRules.exportFilenameTitle": "rules_export", "xpack.securitySolution.detectionEngine.rules.allRules.featureTour.nextStepLabel": "次のステップに進む", "xpack.securitySolution.detectionEngine.rules.allRules.featureTour.previousStepLabel": "前のステップに戻る", @@ -28761,9 +28760,6 @@ "xpack.securitySolution.detectionEngine.rules.defineRuleTitle": "ルールの定義", "xpack.securitySolution.detectionEngine.rules.deleteDescription": "削除", "xpack.securitySolution.detectionEngine.rules.editPageTitle": "編集", - "xpack.securitySolution.detectionEngine.rules.experimentalDescription": "オンにすると、すべてのテーブル列の並べ替えが有効になります。テーブルのパフォーマンスの問題が発生した場合は、オフにできます。", - "xpack.securitySolution.detectionEngine.rules.experimentalOff": "高度な並べ替え", - "xpack.securitySolution.detectionEngine.rules.experimentalOn": "高度な並べ替え", "xpack.securitySolution.detectionEngine.rules.guidedOnboarding.installPrebuiltRules.content": "開始するには、Elasticの構築済みルールを読み込む必要があります。", "xpack.securitySolution.detectionEngine.rules.guidedOnboarding.installPrebuiltRules.title": "Elastic事前構築済みルールを読み込む", "xpack.securitySolution.detectionEngine.rules.guidedOnboarding.nextButton": "次へ", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index ea907ebb37ab9..19ab1bbb574c3 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -28771,7 +28771,6 @@ "xpack.securitySolution.detectionEngine.rules.allRules.columns.ruleTitle": "规则", "xpack.securitySolution.detectionEngine.rules.allRules.columns.severityTitle": "严重性", "xpack.securitySolution.detectionEngine.rules.allRules.columns.tagsTitle": "标签", - "xpack.securitySolution.detectionEngine.rules.allRules.columns.versionTitle": "版本", "xpack.securitySolution.detectionEngine.rules.allRules.exportFilenameTitle": "rules_export", "xpack.securitySolution.detectionEngine.rules.allRules.featureTour.nextStepLabel": "前往下一步", "xpack.securitySolution.detectionEngine.rules.allRules.featureTour.previousStepLabel": "前往上一步", @@ -28795,9 +28794,6 @@ "xpack.securitySolution.detectionEngine.rules.defineRuleTitle": "定义规则", "xpack.securitySolution.detectionEngine.rules.deleteDescription": "删除", "xpack.securitySolution.detectionEngine.rules.editPageTitle": "编辑", - "xpack.securitySolution.detectionEngine.rules.experimentalDescription": "打开此项可为所有表列启用排序。如果遇到表性能问题,可以将其关闭。", - "xpack.securitySolution.detectionEngine.rules.experimentalOff": "高级排序", - "xpack.securitySolution.detectionEngine.rules.experimentalOn": "高级排序", "xpack.securitySolution.detectionEngine.rules.guidedOnboarding.installPrebuiltRules.content": "要开始使用,您需要加载 Elastic 预构建规则。", "xpack.securitySolution.detectionEngine.rules.guidedOnboarding.installPrebuiltRules.title": "加载 Elastic 预构建规则", "xpack.securitySolution.detectionEngine.rules.guidedOnboarding.nextButton": "下一步", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/common_transformations.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/common_transformations.ts index 430ebb8c3fdf6..f8a9639bb30bd 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/common_transformations.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/common_transformations.ts @@ -42,10 +42,12 @@ const transformExecutionStatus: RewriteRequestCase = ({ const transformLastRun: RewriteRequestCase = ({ outcome_msg: outcomeMsg, + outcome_order: outcomeOrder, alerts_count: alertsCount, ...rest }) => ({ outcomeMsg, + outcomeOrder, alertsCount, ...rest, }); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/clone.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/clone.ts index 9bb87db3c7e30..e473394f26b8e 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/clone.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/clone.ts @@ -173,6 +173,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { }, outcome: 'succeeded', outcome_msg: null, + outcome_order: 0, warning: null, }, next_run: response.body.next_run, diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/migrations.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/migrations.ts index 35ad717ad8096..20980f390c79b 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/migrations.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/migrations.ts @@ -560,6 +560,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { expect(alert?.lastRun).to.eql({ outcome: 'succeeded', outcomeMsg: null, + outcomeOrder: 0, warning: null, alertsCount: {}, });