diff --git a/oas_docs/bundle.json b/oas_docs/bundle.json index 325b49428dc56..8ea3da3a6aee7 100644 --- a/oas_docs/bundle.json +++ b/oas_docs/bundle.json @@ -1094,6 +1094,27 @@ "nullable": true, "type": "string" }, + "artifacts": { + "additionalProperties": false, + "properties": { + "dashboards": { + "items": { + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, "consumer": { "description": "The name of the application or feature that owns the rule. For example: `alerts`, `apm`, `discover`, `infrastructure`, `logs`, `metrics`, `ml`, `monitoring`, `securitySolution`, `siem`, `stackAlerts`, or `uptime`.", "type": "string" @@ -2173,6 +2194,27 @@ ], "type": "object" }, + "artifacts": { + "additionalProperties": false, + "properties": { + "dashboards": { + "items": { + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, "consumer": { "description": "The name of the application or feature that owns the rule. For example: `alerts`, `apm`, `discover`, `infrastructure`, `logs`, `metrics`, `ml`, `monitoring`, `securitySolution`, `siem`, `stackAlerts`, or `uptime`.", "type": "string" @@ -2820,6 +2862,27 @@ "nullable": true, "type": "string" }, + "artifacts": { + "additionalProperties": false, + "properties": { + "dashboards": { + "items": { + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, "consumer": { "description": "The name of the application or feature that owns the rule. For example: `alerts`, `apm`, `discover`, `infrastructure`, `logs`, `metrics`, `ml`, `monitoring`, `securitySolution`, `siem`, `stackAlerts`, or `uptime`.", "type": "string" @@ -3742,6 +3805,27 @@ ], "type": "object" }, + "artifacts": { + "additionalProperties": false, + "properties": { + "dashboards": { + "items": { + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, "flapping": { "additionalProperties": false, "description": "When flapping detection is turned on, alerts that switch quickly between active and recovered states are identified as “flapping” and notifications are reduced.", @@ -4120,6 +4204,27 @@ "nullable": true, "type": "string" }, + "artifacts": { + "additionalProperties": false, + "properties": { + "dashboards": { + "items": { + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, "consumer": { "description": "The name of the application or feature that owns the rule. For example: `alerts`, `apm`, `discover`, `infrastructure`, `logs`, `metrics`, `ml`, `monitoring`, `securitySolution`, `siem`, `stackAlerts`, or `uptime`.", "type": "string" @@ -6036,6 +6141,27 @@ "nullable": true, "type": "string" }, + "artifacts": { + "additionalProperties": false, + "properties": { + "dashboards": { + "items": { + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, "consumer": { "description": "The name of the application or feature that owns the rule. For example: `alerts`, `apm`, `discover`, `infrastructure`, `logs`, `metrics`, `ml`, `monitoring`, `securitySolution`, `siem`, `stackAlerts`, or `uptime`.", "type": "string" diff --git a/oas_docs/bundle.serverless.json b/oas_docs/bundle.serverless.json index b145f66ca7e52..7f16d7e32b550 100644 --- a/oas_docs/bundle.serverless.json +++ b/oas_docs/bundle.serverless.json @@ -1094,6 +1094,27 @@ "nullable": true, "type": "string" }, + "artifacts": { + "additionalProperties": false, + "properties": { + "dashboards": { + "items": { + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, "consumer": { "description": "The name of the application or feature that owns the rule. For example: `alerts`, `apm`, `discover`, `infrastructure`, `logs`, `metrics`, `ml`, `monitoring`, `securitySolution`, `siem`, `stackAlerts`, or `uptime`.", "type": "string" @@ -2173,6 +2194,27 @@ ], "type": "object" }, + "artifacts": { + "additionalProperties": false, + "properties": { + "dashboards": { + "items": { + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, "consumer": { "description": "The name of the application or feature that owns the rule. For example: `alerts`, `apm`, `discover`, `infrastructure`, `logs`, `metrics`, `ml`, `monitoring`, `securitySolution`, `siem`, `stackAlerts`, or `uptime`.", "type": "string" @@ -2820,6 +2862,27 @@ "nullable": true, "type": "string" }, + "artifacts": { + "additionalProperties": false, + "properties": { + "dashboards": { + "items": { + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, "consumer": { "description": "The name of the application or feature that owns the rule. For example: `alerts`, `apm`, `discover`, `infrastructure`, `logs`, `metrics`, `ml`, `monitoring`, `securitySolution`, `siem`, `stackAlerts`, or `uptime`.", "type": "string" @@ -3742,6 +3805,27 @@ ], "type": "object" }, + "artifacts": { + "additionalProperties": false, + "properties": { + "dashboards": { + "items": { + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, "flapping": { "additionalProperties": false, "description": "When flapping detection is turned on, alerts that switch quickly between active and recovered states are identified as “flapping” and notifications are reduced.", @@ -4120,6 +4204,27 @@ "nullable": true, "type": "string" }, + "artifacts": { + "additionalProperties": false, + "properties": { + "dashboards": { + "items": { + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, "consumer": { "description": "The name of the application or feature that owns the rule. For example: `alerts`, `apm`, `discover`, `infrastructure`, `logs`, `metrics`, `ml`, `monitoring`, `securitySolution`, `siem`, `stackAlerts`, or `uptime`.", "type": "string" @@ -6036,6 +6141,27 @@ "nullable": true, "type": "string" }, + "artifacts": { + "additionalProperties": false, + "properties": { + "dashboards": { + "items": { + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, "consumer": { "description": "The name of the application or feature that owns the rule. For example: `alerts`, `apm`, `discover`, `infrastructure`, `logs`, `metrics`, `ml`, `monitoring`, `securitySolution`, `siem`, `stackAlerts`, or `uptime`.", "type": "string" diff --git a/oas_docs/output/kibana.serverless.yaml b/oas_docs/output/kibana.serverless.yaml index 319729753016f..754db44659c90 100644 --- a/oas_docs/output/kibana.serverless.yaml +++ b/oas_docs/output/kibana.serverless.yaml @@ -939,6 +939,20 @@ paths: description: The owner of the API key that is associated with the rule and used to run background tasks. nullable: true type: string + artifacts: + additionalProperties: false + type: object + properties: + dashboards: + items: + additionalProperties: false + type: object + properties: + id: + type: string + required: + - id + type: array consumer: description: 'The name of the application or feature that owns the rule. For example: `alerts`, `apm`, `discover`, `infrastructure`, `logs`, `metrics`, `ml`, `monitoring`, `securitySolution`, `siem`, `stackAlerts`, or `uptime`.' type: string @@ -1758,6 +1772,20 @@ paths: type: number required: - active + artifacts: + additionalProperties: false + type: object + properties: + dashboards: + items: + additionalProperties: false + type: object + properties: + id: + type: string + required: + - id + type: array consumer: description: 'The name of the application or feature that owns the rule. For example: `alerts`, `apm`, `discover`, `infrastructure`, `logs`, `metrics`, `ml`, `monitoring`, `securitySolution`, `siem`, `stackAlerts`, or `uptime`.' type: string @@ -2288,6 +2316,20 @@ paths: description: The owner of the API key that is associated with the rule and used to run background tasks. nullable: true type: string + artifacts: + additionalProperties: false + type: object + properties: + dashboards: + items: + additionalProperties: false + type: object + properties: + id: + type: string + required: + - id + type: array consumer: description: 'The name of the application or feature that owns the rule. For example: `alerts`, `apm`, `discover`, `infrastructure`, `logs`, `metrics`, `ml`, `monitoring`, `securitySolution`, `siem`, `stackAlerts`, or `uptime`.' type: string @@ -2983,6 +3025,20 @@ paths: type: number required: - active + artifacts: + additionalProperties: false + type: object + properties: + dashboards: + items: + additionalProperties: false + type: object + properties: + id: + type: string + required: + - id + type: array flapping: additionalProperties: false description: When flapping detection is turned on, alerts that switch quickly between active and recovered states are identified as “flapping” and notifications are reduced. @@ -3274,6 +3330,20 @@ paths: description: The owner of the API key that is associated with the rule and used to run background tasks. nullable: true type: string + artifacts: + additionalProperties: false + type: object + properties: + dashboards: + items: + additionalProperties: false + type: object + properties: + id: + type: string + required: + - id + type: array consumer: description: 'The name of the application or feature that owns the rule. For example: `alerts`, `apm`, `discover`, `infrastructure`, `logs`, `metrics`, `ml`, `monitoring`, `securitySolution`, `siem`, `stackAlerts`, or `uptime`.' type: string @@ -4663,6 +4733,20 @@ paths: description: The owner of the API key that is associated with the rule and used to run background tasks. nullable: true type: string + artifacts: + additionalProperties: false + type: object + properties: + dashboards: + items: + additionalProperties: false + type: object + properties: + id: + type: string + required: + - id + type: array consumer: description: 'The name of the application or feature that owns the rule. For example: `alerts`, `apm`, `discover`, `infrastructure`, `logs`, `metrics`, `ml`, `monitoring`, `securitySolution`, `siem`, `stackAlerts`, or `uptime`.' type: string diff --git a/oas_docs/output/kibana.yaml b/oas_docs/output/kibana.yaml index fedb3a14c3333..e67a1ead75276 100644 --- a/oas_docs/output/kibana.yaml +++ b/oas_docs/output/kibana.yaml @@ -1333,6 +1333,20 @@ paths: description: The owner of the API key that is associated with the rule and used to run background tasks. nullable: true type: string + artifacts: + additionalProperties: false + type: object + properties: + dashboards: + items: + additionalProperties: false + type: object + properties: + id: + type: string + required: + - id + type: array consumer: description: 'The name of the application or feature that owns the rule. For example: `alerts`, `apm`, `discover`, `infrastructure`, `logs`, `metrics`, `ml`, `monitoring`, `securitySolution`, `siem`, `stackAlerts`, or `uptime`.' type: string @@ -2152,6 +2166,20 @@ paths: type: number required: - active + artifacts: + additionalProperties: false + type: object + properties: + dashboards: + items: + additionalProperties: false + type: object + properties: + id: + type: string + required: + - id + type: array consumer: description: 'The name of the application or feature that owns the rule. For example: `alerts`, `apm`, `discover`, `infrastructure`, `logs`, `metrics`, `ml`, `monitoring`, `securitySolution`, `siem`, `stackAlerts`, or `uptime`.' type: string @@ -2682,6 +2710,20 @@ paths: description: The owner of the API key that is associated with the rule and used to run background tasks. nullable: true type: string + artifacts: + additionalProperties: false + type: object + properties: + dashboards: + items: + additionalProperties: false + type: object + properties: + id: + type: string + required: + - id + type: array consumer: description: 'The name of the application or feature that owns the rule. For example: `alerts`, `apm`, `discover`, `infrastructure`, `logs`, `metrics`, `ml`, `monitoring`, `securitySolution`, `siem`, `stackAlerts`, or `uptime`.' type: string @@ -3377,6 +3419,20 @@ paths: type: number required: - active + artifacts: + additionalProperties: false + type: object + properties: + dashboards: + items: + additionalProperties: false + type: object + properties: + id: + type: string + required: + - id + type: array flapping: additionalProperties: false description: When flapping detection is turned on, alerts that switch quickly between active and recovered states are identified as “flapping” and notifications are reduced. @@ -3668,6 +3724,20 @@ paths: description: The owner of the API key that is associated with the rule and used to run background tasks. nullable: true type: string + artifacts: + additionalProperties: false + type: object + properties: + dashboards: + items: + additionalProperties: false + type: object + properties: + id: + type: string + required: + - id + type: array consumer: description: 'The name of the application or feature that owns the rule. For example: `alerts`, `apm`, `discover`, `infrastructure`, `logs`, `metrics`, `ml`, `monitoring`, `securitySolution`, `siem`, `stackAlerts`, or `uptime`.' type: string @@ -5057,6 +5127,20 @@ paths: description: The owner of the API key that is associated with the rule and used to run background tasks. nullable: true type: string + artifacts: + additionalProperties: false + type: object + properties: + dashboards: + items: + additionalProperties: false + type: object + properties: + id: + type: string + required: + - id + type: array consumer: description: 'The name of the application or feature that owns the rule. For example: `alerts`, `apm`, `discover`, `infrastructure`, `logs`, `metrics`, `ml`, `monitoring`, `securitySolution`, `siem`, `stackAlerts`, or `uptime`.' type: string diff --git a/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts b/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts index d8ecd92be2a1a..6c7e4a668503b 100644 --- a/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts +++ b/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts @@ -62,7 +62,7 @@ describe('checking migration metadata changes on all registered SO types', () => "action": "0e6fc0b74c7312a8c11ff6b14437b93a997358b8", "action_task_params": "2e475d8b62e2de50b77f58cda309efb537e1d543", "ad_hoc_run_params": "c7419760e878207231c3c8a25ec4d78360e07bf7", - "alert": "c5a135d2aca71f56103e9ccba00d6675b0586c82", + "alert": "795a00b231c815f3d955637ef23cc48f36548aed", "api_key_pending_invalidation": "8f5554d1984854011b8392d9a6f7ef985bcac03c", "apm-custom-dashboards": "b67128f78160c288bd7efe25b2da6e2afd5e82fc", "apm-indices": "8a2d68d415a4b542b26b0d292034a28ffac6fed4", diff --git a/src/platform/packages/shared/kbn-alerting-types/rule_types.ts b/src/platform/packages/shared/kbn-alerting-types/rule_types.ts index 7f330c46f61ee..2de420ecd4360 100644 --- a/src/platform/packages/shared/kbn-alerting-types/rule_types.ts +++ b/src/platform/packages/shared/kbn-alerting-types/rule_types.ts @@ -215,6 +215,14 @@ export interface Flapping extends SavedObjectAttributes { statusChangeThreshold: number; } +export interface Dashboard { + id: string; +} + +export interface Artifacts { + dashboards?: Dashboard[]; +} + export interface Rule { id: string; enabled: boolean; @@ -251,6 +259,7 @@ export interface Rule { viewInAppRelativeUrl?: string; alertDelay?: AlertDelay | null; flapping?: Flapping | null; + artifacts?: Artifacts | null; } export type SanitizedRule = Omit< diff --git a/x-pack/platform/plugins/shared/alerting/common/index.ts b/x-pack/platform/plugins/shared/alerting/common/index.ts index 48181d509216c..bfe19695d3169 100644 --- a/x-pack/platform/plugins/shared/alerting/common/index.ts +++ b/x-pack/platform/plugins/shared/alerting/common/index.ts @@ -45,6 +45,7 @@ export type { RuleSystemActionKey, SanitizedRuleConfig, RuleMonitoringLastRunMetrics, + Artifacts, } from './rule'; export { RuleExecutionStatusValues, diff --git a/x-pack/platform/plugins/shared/alerting/common/routes/rule/apis/create/schemas/v1.ts b/x-pack/platform/plugins/shared/alerting/common/routes/rule/apis/create/schemas/v1.ts index a297ebf15748a..d270fb8a87e25 100644 --- a/x-pack/platform/plugins/shared/alerting/common/routes/rule/apis/create/schemas/v1.ts +++ b/x-pack/platform/plugins/shared/alerting/common/routes/rule/apis/create/schemas/v1.ts @@ -11,7 +11,7 @@ import { createRuleParamsExamplesV1, } from '@kbn/response-ops-rule-params'; import { validateDurationV1, validateHoursV1, validateTimezoneV1 } from '../../../validation'; -import { notifyWhenSchemaV1, alertDelaySchemaV1 } from '../../../response'; +import { notifyWhenSchemaV1, alertDelaySchemaV1, artifactsSchemaV1 } from '../../../response'; import { alertsFilterQuerySchemaV1 } from '../../../../alerts_filter_query'; import { flappingSchemaV1 } from '../../../common'; @@ -189,6 +189,7 @@ export const createBodySchema = schema.object({ notify_when: schema.maybe(schema.nullable(notifyWhenSchemaV1)), alert_delay: schema.maybe(alertDelaySchemaV1), flapping: schema.maybe(schema.nullable(flappingSchemaV1)), + artifacts: schema.maybe(artifactsSchemaV1), }); export { createRuleParamsExamplesV1 }; diff --git a/x-pack/platform/plugins/shared/alerting/common/routes/rule/apis/create/types/v1.ts b/x-pack/platform/plugins/shared/alerting/common/routes/rule/apis/create/types/v1.ts index 7256a00f45d40..06f5da49b43cb 100644 --- a/x-pack/platform/plugins/shared/alerting/common/routes/rule/apis/create/types/v1.ts +++ b/x-pack/platform/plugins/shared/alerting/common/routes/rule/apis/create/types/v1.ts @@ -32,6 +32,7 @@ export interface CreateRuleRequestBody { notify_when?: CreateBodySchema['notify_when']; alert_delay?: CreateBodySchema['alert_delay']; flapping?: CreateBodySchema['flapping']; + artifacts?: CreateBodySchema['artifacts']; } export interface CreateRuleResponse { diff --git a/x-pack/platform/plugins/shared/alerting/common/routes/rule/apis/update/schemas/v1.ts b/x-pack/platform/plugins/shared/alerting/common/routes/rule/apis/update/schemas/v1.ts index baa1dca0ec5b8..f23340832f102 100644 --- a/x-pack/platform/plugins/shared/alerting/common/routes/rule/apis/update/schemas/v1.ts +++ b/x-pack/platform/plugins/shared/alerting/common/routes/rule/apis/update/schemas/v1.ts @@ -9,7 +9,7 @@ import path from 'node:path'; import { schema } from '@kbn/config-schema'; import { ruleParamsSchemaWithDefaultValueV1 } from '@kbn/response-ops-rule-params'; import { validateDurationV1, validateHoursV1, validateTimezoneV1 } from '../../../validation'; -import { notifyWhenSchemaV1, alertDelaySchemaV1 } from '../../../response'; +import { notifyWhenSchemaV1, alertDelaySchemaV1, artifactsSchemaV1 } from '../../../response'; import { alertsFilterQuerySchemaV1 } from '../../../../alerts_filter_query'; import { flappingSchemaV1 } from '../../../common'; @@ -163,6 +163,7 @@ export const updateBodySchema = schema.object({ notify_when: schema.maybe(schema.nullable(notifyWhenSchemaV1)), alert_delay: schema.maybe(alertDelaySchemaV1), flapping: schema.maybe(schema.nullable(flappingSchemaV1)), + artifacts: schema.maybe(artifactsSchemaV1), }); export const updateParamsSchema = schema.object({ diff --git a/x-pack/platform/plugins/shared/alerting/common/routes/rule/apis/update/types/v1.ts b/x-pack/platform/plugins/shared/alerting/common/routes/rule/apis/update/types/v1.ts index a06c726628796..2ced261dc9770 100644 --- a/x-pack/platform/plugins/shared/alerting/common/routes/rule/apis/update/types/v1.ts +++ b/x-pack/platform/plugins/shared/alerting/common/routes/rule/apis/update/types/v1.ts @@ -31,6 +31,7 @@ export interface UpdateRuleRequestBody { notify_when?: UpdateBodySchema['notify_when']; alert_delay?: UpdateBodySchema['alert_delay']; flapping?: UpdateBodySchema['flapping']; + artifacts?: UpdateBodySchema['artifacts']; } export interface UpdateRuleResponse { diff --git a/x-pack/platform/plugins/shared/alerting/common/routes/rule/response/index.ts b/x-pack/platform/plugins/shared/alerting/common/routes/rule/response/index.ts index 1c7632ad28988..98fb488fec08e 100644 --- a/x-pack/platform/plugins/shared/alerting/common/routes/rule/response/index.ts +++ b/x-pack/platform/plugins/shared/alerting/common/routes/rule/response/index.ts @@ -30,6 +30,7 @@ export { notifyWhenSchema as notifyWhenSchemaV1, scheduleIdsSchema as scheduleIdsSchemaV1, alertDelaySchema as alertDelaySchemaV1, + artifactsSchema as artifactsSchemaV1, } from './schemas/v1'; export type { diff --git a/x-pack/platform/plugins/shared/alerting/common/routes/rule/response/schemas/v1.ts b/x-pack/platform/plugins/shared/alerting/common/routes/rule/response/schemas/v1.ts index 754b9a0637458..5de32c97153c4 100644 --- a/x-pack/platform/plugins/shared/alerting/common/routes/rule/response/schemas/v1.ts +++ b/x-pack/platform/plugins/shared/alerting/common/routes/rule/response/schemas/v1.ts @@ -475,6 +475,12 @@ export const alertDelaySchema = schema.object( } ); +export const dashboardsSchema = schema.arrayOf(schema.object({ id: schema.string() })); + +export const artifactsSchema = schema.object({ + dashboards: schema.maybe(dashboardsSchema), +}); + export const ruleResponseSchema = schema.object({ id: schema.string({ meta: { @@ -639,6 +645,7 @@ export const ruleResponseSchema = schema.object({ ), alert_delay: schema.maybe(alertDelaySchema), flapping: schema.maybe(schema.nullable(flappingSchemaV1)), + artifacts: schema.maybe(artifactsSchema), }); export const scheduleIdsSchema = schema.maybe(schema.arrayOf(schema.string())); diff --git a/x-pack/platform/plugins/shared/alerting/common/routes/rule/response/types/v1.ts b/x-pack/platform/plugins/shared/alerting/common/routes/rule/response/types/v1.ts index 8b305195610b6..b6f9aed370892 100644 --- a/x-pack/platform/plugins/shared/alerting/common/routes/rule/response/types/v1.ts +++ b/x-pack/platform/plugins/shared/alerting/common/routes/rule/response/types/v1.ts @@ -54,4 +54,5 @@ export interface RuleResponse { view_in_app_relative_url?: RuleResponseSchemaType['view_in_app_relative_url']; alert_delay?: RuleResponseSchemaType['alert_delay']; flapping?: RuleResponseSchemaType['flapping']; + artifacts?: RuleResponseSchemaType['artifacts']; } diff --git a/x-pack/platform/plugins/shared/alerting/common/rule.ts b/x-pack/platform/plugins/shared/alerting/common/rule.ts index 48d972d6a0035..cbf11cfda1819 100644 --- a/x-pack/platform/plugins/shared/alerting/common/rule.ts +++ b/x-pack/platform/plugins/shared/alerting/common/rule.ts @@ -45,6 +45,7 @@ export type { AlertsHealth, AlertingFrameworkHealth, ResolvedSanitizedRule, + Artifacts, } from '@kbn/alerting-types'; export { diff --git a/x-pack/platform/plugins/shared/alerting/server/application/backfill/methods/schedule/schedule_backfill.test.ts b/x-pack/platform/plugins/shared/alerting/server/application/backfill/methods/schedule/schedule_backfill.test.ts index 7bae480ade8e8..39ac40a9bba14 100644 --- a/x-pack/platform/plugins/shared/alerting/server/application/backfill/methods/schedule/schedule_backfill.test.ts +++ b/x-pack/platform/plugins/shared/alerting/server/application/backfill/methods/schedule/schedule_backfill.test.ts @@ -426,6 +426,7 @@ describe('scheduleBackfill()', () => { alertTypeId: existingDecryptedRule1.attributes.alertTypeId, apiKey: existingDecryptedRule1.attributes.apiKey, apiKeyCreatedByUser: existingDecryptedRule1.attributes.apiKeyCreatedByUser, + artifacts: { dashboards: [] }, consumer: existingDecryptedRule1.attributes.consumer, createdAt: new Date(existingDecryptedRule1.attributes.createdAt), createdBy: existingDecryptedRule1.attributes.createdBy, @@ -457,6 +458,7 @@ describe('scheduleBackfill()', () => { alertTypeId: existingDecryptedRule2.attributes.alertTypeId, apiKey: existingDecryptedRule2.attributes.apiKey, apiKeyCreatedByUser: existingDecryptedRule2.attributes.apiKeyCreatedByUser, + artifacts: { dashboards: [] }, consumer: existingDecryptedRule2.attributes.consumer, createdAt: new Date(existingDecryptedRule2.attributes.createdAt), createdBy: existingDecryptedRule2.attributes.createdBy, diff --git a/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/bulk_edit/bulk_edit_rules.test.ts b/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/bulk_edit/bulk_edit_rules.test.ts index 2949de3ba3c56..229e853c32344 100644 --- a/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/bulk_edit/bulk_edit_rules.test.ts +++ b/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/bulk_edit/bulk_edit_rules.test.ts @@ -161,6 +161,9 @@ describe('bulkEdit()', () => { throttle: null, notifyWhen: null, actions: [], + artifacts: { + dashboards: [], + }, name: 'my rule name', revision: 0, }, @@ -652,7 +655,6 @@ describe('bulkEdit()', () => { }, ], }); - expect(unsecuredSavedObjectsClient.bulkCreate).toHaveBeenCalledWith( [ { @@ -1095,6 +1097,203 @@ describe('bulkEdit()', () => { }); }); + test('should add system and default actions when there are existing artifacts', async () => { + mockCreatePointInTimeFinderAsInternalUser({ + saved_objects: [ + { + ...existingDecryptedRule, + attributes: { + ...existingDecryptedRule.attributes, + artifacts: { + dashboards: [ + { + refId: 'dashboard_0', + }, + ], + }, + } as any, + references: [ + { + id: 'dashboard-1', + type: 'dashboard', + name: 'dashboard_0', + }, + ] as any, + }, + ], + }); + + const defaultAction = { + frequency: { + notifyWhen: 'onActiveAlert' as const, + summary: false, + throttle: null, + }, + group: 'default', + id: '1', + params: {}, + }; + + const systemAction = { + id: 'system_action-id', + params: {}, + }; + + unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({ + saved_objects: [ + { + ...existingRule, + attributes: { + ...existingRule.attributes, + actions: [ + { + frequency: { + notifyWhen: 'onActiveAlert' as const, + summary: false, + throttle: null, + }, + group: 'default', + params: {}, + actionRef: 'action_0', + actionTypeId: 'test-1', + uuid: '222', + }, + { + params: {}, + actionRef: 'system_action:system_action-id', + actionTypeId: 'test-2', + uuid: '222', + }, + ], + artifacts: { + dashboards: [ + { + refId: 'dashboard_0', + }, + ], + }, + }, + references: [ + { + name: 'action_0', + type: 'action', + id: '1', + }, + { + name: 'dashboard_0', + type: 'dashboard', + id: 'dashboard-1', + }, + ], + }, + ], + }); + + actionsClient.getBulk.mockResolvedValue([ + { + id: '1', + actionTypeId: 'test-1', + config: {}, + isMissingSecrets: false, + name: 'test default connector', + isPreconfigured: false, + isDeprecated: false, + isSystemAction: false, + }, + { + id: 'system_action-id', + actionTypeId: 'test-2', + config: {}, + isMissingSecrets: false, + name: 'system action connector', + isPreconfigured: false, + isDeprecated: false, + isSystemAction: true, + }, + ]); + + const result = await rulesClient.bulkEdit({ + filter: '', + operations: [ + { + field: 'actions', + operation: 'add', + value: [defaultAction, systemAction], + }, + ], + }); + + expect(unsecuredSavedObjectsClient.bulkCreate).toHaveBeenCalledWith( + [ + { + ...existingRule, + attributes: { + ...existingRule.attributes, + actions: [ + { + actionRef: 'action_0', + actionTypeId: 'test-1', + frequency: { notifyWhen: 'onActiveAlert', summary: false, throttle: null }, + group: 'default', + params: {}, + uuid: '105', + }, + { + actionRef: 'system_action:system_action-id', + actionTypeId: 'test-2', + params: {}, + uuid: '106', + }, + ], + artifacts: { + dashboards: [ + { + refId: 'dashboard_0', + }, + ], + }, + apiKey: null, + apiKeyOwner: null, + apiKeyCreatedByUser: null, + meta: { versionApiKeyLastmodified: 'v8.2.0' }, + name: 'my rule name', + enabled: false, + updatedAt: '2019-02-12T21:01:22.479Z', + updatedBy: 'elastic', + tags: ['foo'], + revision: 1, + }, + references: [ + { id: '1', name: 'action_0', type: 'action' }, + { id: 'dashboard-1', name: 'dashboard_0', type: 'dashboard' }, + ], + }, + ], + { overwrite: true } + ); + + expect(result.rules[0]).toEqual({ + ...omit(existingRule.attributes, 'legacyId'), + createdAt: new Date(existingRule.attributes.createdAt), + updatedAt: new Date(existingRule.attributes.updatedAt), + executionStatus: { + ...existingRule.attributes.executionStatus, + lastExecutionDate: new Date(existingRule.attributes.executionStatus.lastExecutionDate), + }, + actions: [{ ...defaultAction, actionTypeId: 'test-1', uuid: '222' }], + systemActions: [{ ...systemAction, actionTypeId: 'test-2', uuid: '222' }], + artifacts: { + dashboards: [ + { + id: 'dashboard-1', + }, + ], + }, + id: existingRule.id, + snoozeSchedule: [], + }); + }); + test('should construct the refs correctly and persist the actions correctly', async () => { const defaultAction = { frequency: { @@ -1195,13 +1394,13 @@ describe('bulkEdit()', () => { frequency: { notifyWhen: 'onActiveAlert', summary: false, throttle: null }, group: 'default', params: {}, - uuid: '105', + uuid: '107', }, { actionRef: 'system_action:system_action-id', actionTypeId: 'test-2', params: {}, - uuid: '106', + uuid: '108', }, ]); }); diff --git a/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/bulk_edit/bulk_edit_rules.ts b/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/bulk_edit/bulk_edit_rules.ts index 49b5fc6285187..228bddc906e5b 100644 --- a/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/bulk_edit/bulk_edit_rules.ts +++ b/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/bulk_edit/bulk_edit_rules.ts @@ -39,6 +39,7 @@ import { getBulkUnsnooze, verifySnoozeScheduleLimit, injectReferencesIntoActions, + injectReferencesIntoArtifacts, } from '../../../../rules_client/common'; import { alertingAuthorizationFilterOpts, @@ -329,7 +330,6 @@ async function bulkEditRulesOcc( ); } await rulesFinder.close(); - const updatedInterval = rules .filter((rule) => rule.attributes.enabled) .map((rule) => rule.attributes.schedule?.interval) @@ -479,6 +479,12 @@ async function updateRuleAttributesAndParamsInMemory( rule.references || [] ); + const ruleArtifacts = injectReferencesIntoArtifacts( + rule.id, + rule.attributes.artifacts, + rule.references + ); + const ruleDomain: RuleDomain = transformRuleAttributesToRuleDomain( rule.attributes, { @@ -545,11 +551,13 @@ async function updateRuleAttributesAndParamsInMemory( references, params: updatedParams, actions: actionsWithRefs, + artifacts: artifactsWithRefs, } = await extractReferences( context, ruleType, updatedRuleActions as NormalizedAlertActionWithGeneratedValues[], - validatedMutatedAlertTypeParams + validatedMutatedAlertTypeParams, + ruleArtifacts ?? {} ); const ruleAttributes = transformRuleDomainToRuleAttributes({ @@ -559,6 +567,7 @@ async function updateRuleAttributesAndParamsInMemory( legacyId: rule.attributes.legacyId, paramsWithRefs: updatedParams, }, + artifactsWithRefs, }); const { apiKeyAttributes } = await prepareApiKeys( diff --git a/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/create/create_rule.test.ts b/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/create/create_rule.test.ts index ab07a488cf427..d2963c25ee2db 100644 --- a/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/create/create_rule.test.ts +++ b/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/create/create_rule.test.ts @@ -432,6 +432,9 @@ describe('create()', () => { }, ], "alertTypeId": "123", + "artifacts": Object { + "dashboards": Array [], + }, "consumer": "bar", "createdAt": 2019-02-12T21:01:22.479Z, "createdBy": "elastic", @@ -482,6 +485,9 @@ describe('create()', () => { "apiKey": null, "apiKeyCreatedByUser": null, "apiKeyOwner": null, + "artifacts": Object { + "dashboards": Array [], + }, "consumer": "bar", "createdAt": "2019-02-12T21:01:22.479Z", "createdBy": "elastic", @@ -712,6 +718,9 @@ describe('create()', () => { "apiKey": null, "apiKeyCreatedByUser": null, "apiKeyOwner": null, + "artifacts": Object { + "dashboards": Array [], + }, "consumer": "bar", "createdAt": "2019-02-12T21:01:22.479Z", "createdBy": "elastic", @@ -933,6 +942,9 @@ describe('create()', () => { }, ], "alertTypeId": "123", + "artifacts": Object { + "dashboards": Array [], + }, "createdAt": 2019-02-12T21:01:22.479Z, "executionStatus": Object { "lastExecutionDate": 2019-02-12T21:01:22.000Z, @@ -1137,6 +1149,9 @@ describe('create()', () => { }, ], "alertTypeId": "123", + "artifacts": Object { + "dashboards": Array [], + }, "createdAt": 2019-02-12T21:01:22.479Z, "executionStatus": Object { "lastExecutionDate": 2019-02-12T21:01:22.000Z, @@ -1191,6 +1206,9 @@ describe('create()', () => { alertTypeId: '123', apiKey: null, apiKeyOwner: null, + artifacts: { + dashboards: [], + }, apiKeyCreatedByUser: null, consumer: 'bar', createdAt: '2019-02-12T21:01:22.479Z', @@ -1389,6 +1407,9 @@ describe('create()', () => { }, ], "alertTypeId": "123", + "artifacts": Object { + "dashboards": Array [], + }, "createdAt": 2019-02-12T21:01:22.479Z, "executionStatus": Object { "lastExecutionDate": 2019-02-12T21:01:22.000Z, @@ -1449,6 +1470,9 @@ describe('create()', () => { apiKey: null, apiKeyCreatedByUser: null, apiKeyOwner: null, + artifacts: { + dashboards: [], + }, consumer: 'bar', createdAt: '2019-02-12T21:01:22.479Z', createdBy: 'elastic', @@ -1562,6 +1586,9 @@ describe('create()', () => { }, ], "alertTypeId": "123", + "artifacts": Object { + "dashboards": Array [], + }, "createdAt": 2019-02-12T21:01:22.479Z, "enabled": false, "executionStatus": Object { @@ -1701,6 +1728,9 @@ describe('create()', () => { apiKey: null, apiKeyOwner: null, apiKeyCreatedByUser: null, + artifacts: { + dashboards: [], + }, consumer: 'bar', createdAt: '2019-02-12T21:01:22.479Z', createdBy: 'elastic', @@ -1756,6 +1786,9 @@ describe('create()', () => { }, ], "alertTypeId": "123", + "artifacts": Object { + "dashboards": Array [], + }, "createdAt": 2019-02-12T21:01:22.479Z, "executionStatus": Object { "lastExecutionDate": 2019-02-12T21:01:22.000Z, @@ -1894,6 +1927,9 @@ describe('create()', () => { apiKey: null, apiKeyOwner: null, apiKeyCreatedByUser: null, + artifacts: { + dashboards: [], + }, legacyId: null, consumer: 'bar', createdAt: '2019-02-12T21:01:22.479Z', @@ -1949,6 +1985,9 @@ describe('create()', () => { }, ], "alertTypeId": "123", + "artifacts": Object { + "dashboards": Array [], + }, "createdAt": 2019-02-12T21:01:22.479Z, "executionStatus": Object { "lastExecutionDate": 2019-02-12T21:01:22.000Z, @@ -2077,6 +2116,9 @@ describe('create()', () => { apiKey: null, apiKeyOwner: null, apiKeyCreatedByUser: null, + artifacts: { + dashboards: [], + }, createdBy: 'elastic', createdAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', @@ -2125,6 +2167,9 @@ describe('create()', () => { }, ], "alertTypeId": "123", + "artifacts": Object { + "dashboards": Array [], + }, "consumer": "bar", "createdAt": 2019-02-12T21:01:22.479Z, "createdBy": "elastic", @@ -2221,6 +2266,9 @@ describe('create()', () => { apiKey: null, apiKeyOwner: null, apiKeyCreatedByUser: null, + artifacts: { + dashboards: [], + }, createdBy: 'elastic', createdAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', @@ -2269,6 +2317,9 @@ describe('create()', () => { }, ], "alertTypeId": "123", + "artifacts": Object { + "dashboards": Array [], + }, "consumer": "bar", "createdAt": 2019-02-12T21:01:22.479Z, "createdBy": "elastic", @@ -2365,6 +2416,9 @@ describe('create()', () => { apiKey: null, apiKeyOwner: null, apiKeyCreatedByUser: null, + artifacts: { + dashboards: [], + }, createdBy: 'elastic', createdAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', @@ -2413,6 +2467,9 @@ describe('create()', () => { }, ], "alertTypeId": "123", + "artifacts": Object { + "dashboards": Array [], + }, "consumer": "bar", "createdAt": 2019-02-12T21:01:22.479Z, "createdBy": "elastic", @@ -2531,6 +2588,9 @@ describe('create()', () => { apiKeyOwner: null, apiKey: null, apiKeyCreatedByUser: null, + artifacts: { + dashboards: [], + }, legacyId: null, createdBy: 'elastic', updatedBy: 'elastic', @@ -2600,6 +2660,9 @@ describe('create()', () => { }, ], "alertTypeId": "123", + "artifacts": Object { + "dashboards": Array [], + }, "consumer": "bar", "createdAt": 2019-02-12T21:01:22.479Z, "createdBy": "elastic", @@ -2927,6 +2990,9 @@ describe('create()', () => { params: { bar: true }, apiKey: Buffer.from('123:abc').toString('base64'), apiKeyOwner: 'elastic', + artifacts: { + dashboards: [], + }, createdBy: 'elastic', createdAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', @@ -3028,6 +3094,9 @@ describe('create()', () => { ], legacyId: null, alertTypeId: '123', + artifacts: { + dashboards: [], + }, consumer: 'bar', name: 'abc', params: { bar: true }, @@ -3785,6 +3854,9 @@ describe('create()', () => { }, ], "alertTypeId": "123", + "artifacts": Object { + "dashboards": Array [], + }, "createdAt": 2019-02-12T21:01:22.479Z, "executionStatus": Object { "lastExecutionDate": 2019-02-12T21:01:22.000Z, @@ -4003,6 +4075,9 @@ describe('create()', () => { apiKey: Buffer.from('123:abc').toString('base64'), apiKeyOwner: 'elastic', apiKeyCreatedByUser: true, + artifacts: { + dashboards: [], + }, createdBy: 'elastic', createdAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', @@ -4181,6 +4256,9 @@ describe('create()', () => { }, ], "alertTypeId": "123", + "artifacts": Object { + "dashboards": Array [], + }, "createdAt": 2019-02-12T21:01:22.479Z, "executionStatus": Object { "lastExecutionDate": 2019-02-12T21:01:22.000Z, @@ -4234,6 +4312,9 @@ describe('create()', () => { apiKey: null, apiKeyOwner: null, apiKeyCreatedByUser: null, + artifacts: { + dashboards: [], + }, consumer: 'bar', createdAt: '2019-02-12T21:01:22.479Z', createdBy: 'elastic', @@ -4483,4 +4564,101 @@ describe('create()', () => { ); }); }); + + describe('artifacts', () => { + test('should create a rule with linked dashboards', async () => { + const dashboards = [ + { + id: '1', + }, + { + id: '2', + }, + ]; + + const data = getMockData({ + name: 'my rule name', + actions: [], + artifacts: { + dashboards, + }, + }); + + unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ + id: '1', + type: RULE_SAVED_OBJECT_TYPE, + attributes: { + enabled: false, + name: 'my rule name', + alertTypeId: '123', + schedule: { interval: 10000 }, + params: { + bar: true, + }, + executionStatus: getRuleExecutionStatusPending(now), + running: false, + createdAt: now, + updatedAt: now, + actions: [], + artifacts: { + dashboards: [ + { + refId: 'dashboard_0', + }, + { + refId: 'dashboard_1', + }, + ], + }, + }, + references: [ + { + id: '1', + name: 'dashboard_0', + type: 'dashboard', + }, + { + id: '2', + name: 'dashboard_1', + type: 'dashboard', + }, + ], + }); + + const result = await rulesClient.create({ data }); + + expect(unsecuredSavedObjectsClient.create).toHaveBeenCalledWith( + RULE_SAVED_OBJECT_TYPE, + expect.objectContaining({ + artifacts: { + dashboards: [ + { + refId: 'dashboard_0', + }, + { + refId: 'dashboard_1', + }, + ], + }, + }), + { + id: 'mock-saved-object-id', + references: [ + { + id: '1', + name: 'dashboard_0', + type: 'dashboard', + }, + { + id: '2', + name: 'dashboard_1', + type: 'dashboard', + }, + ], + } + ); + + expect(result.artifacts?.dashboards).toEqual(dashboards); + }); + }); }); diff --git a/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/create/create_rule.ts b/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/create/create_rule.ts index a4307499a9fbd..673a346f9b987 100644 --- a/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/create/create_rule.ts +++ b/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/create/create_rule.ts @@ -191,13 +191,15 @@ export async function createRule( } const allActions = [...data.actions, ...(data.systemActions ?? [])]; + const artifacts = data.artifacts ?? {}; // Extract saved object references for this rule const { references, params: updatedParams, actions: actionsWithRefs, + artifacts: artifactsWithRefs, } = await withSpan({ name: 'extractReferences', type: 'rules' }, () => - extractReferences(context, ruleType, allActions, validatedRuleTypeParams) + extractReferences(context, ruleType, allActions, validatedRuleTypeParams, artifacts) ); const createTime = Date.now(); @@ -210,6 +212,7 @@ export async function createRule( // Convert domain rule object to ES rule attributes const ruleAttributes = transformRuleDomainToRuleAttributes({ actionsWithRefs, + artifactsWithRefs, rule: { ...restData, // TODO (http-versioning) create a rule domain version of this function diff --git a/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/create/schemas/create_rule_data_schema.ts b/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/create/schemas/create_rule_data_schema.ts index e2cf0da359b0a..e74ad3b8b1bb4 100644 --- a/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/create/schemas/create_rule_data_schema.ts +++ b/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/create/schemas/create_rule_data_schema.ts @@ -14,6 +14,7 @@ import { actionRequestSchema, systemActionRequestSchema, flappingSchema, + artifactsSchema, } from '../../../schemas'; export const createRuleDataSchema = schema.object( @@ -39,6 +40,7 @@ export const createRuleDataSchema = schema.object( notifyWhen: schema.maybe(schema.nullable(notifyWhenSchema)), alertDelay: schema.maybe(alertDelaySchema), flapping: schema.maybe(schema.nullable(flappingSchema)), + artifacts: schema.maybe(artifactsSchema), }, { unknowns: 'allow' } ); diff --git a/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/create/types/create_rule_data.ts b/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/create/types/create_rule_data.ts index 0fd75188fd6cb..eea1e19ddda1d 100644 --- a/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/create/types/create_rule_data.ts +++ b/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/create/types/create_rule_data.ts @@ -25,4 +25,5 @@ export interface CreateRuleData { notifyWhen?: CreateRuleDataType['notifyWhen']; alertDelay?: CreateRuleDataType['alertDelay']; flapping?: CreateRuleDataType['flapping']; + artifacts?: CreateRuleDataType['artifacts']; } diff --git a/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/find/find_rules.test.ts b/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/find/find_rules.test.ts index fa9336a078a32..a85a0e5056db4 100644 --- a/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/find/find_rules.test.ts +++ b/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/find/find_rules.test.ts @@ -200,6 +200,9 @@ describe('find()', () => { }, ], "alertTypeId": "myType", + "artifacts": Object { + "dashboards": Array [], + }, "createdAt": 2019-02-12T21:01:22.479Z, "executionStatus": Object { "lastExecutionDate": 2019-02-12T21:01:22.000Z, @@ -315,6 +318,9 @@ describe('find()', () => { }, ], "alertTypeId": "myType", + "artifacts": Object { + "dashboards": Array [], + }, "createdAt": 2019-02-12T21:01:22.479Z, "executionStatus": Object { "lastExecutionDate": 2019-02-12T21:01:22.000Z, @@ -420,6 +426,9 @@ describe('find()', () => { }, ], "alertTypeId": "myType", + "artifacts": Object { + "dashboards": Array [], + }, "createdAt": 2019-02-12T21:01:22.479Z, "executionStatus": Object { "lastExecutionDate": 2019-02-12T21:01:22.000Z, @@ -708,6 +717,9 @@ describe('find()', () => { }, ], "alertTypeId": "myType", + "artifacts": Object { + "dashboards": Array [], + }, "createdAt": 2019-02-12T21:01:22.479Z, "executionStatus": Object { "lastExecutionDate": 2019-02-12T21:01:22.000Z, @@ -738,6 +750,9 @@ describe('find()', () => { }, ], "alertTypeId": "123", + "artifacts": Object { + "dashboards": Array [], + }, "createdAt": 2019-02-12T21:01:22.479Z, "executionStatus": Object { "lastExecutionDate": 2019-02-12T21:01:22.000Z, @@ -1013,6 +1028,9 @@ describe('find()', () => { "data": Array [ Object { "actions": Array [], + "artifacts": Object { + "dashboards": Array [], + }, "id": "1", "notifyWhen": undefined, "params": undefined, @@ -1179,4 +1197,118 @@ describe('find()', () => { expect(result.data[3]).toEqual(expect.objectContaining({ id: siemRule2.id, migrated: true })); }); }); + + describe('artifacts', () => { + test('finds rules with artifacts', async () => { + unsecuredSavedObjectsClient.find.mockReset(); + + unsecuredSavedObjectsClient.find.mockResolvedValueOnce({ + total: 1, + per_page: 10, + page: 1, + saved_objects: [ + { + id: '1', + type: RULE_SAVED_OBJECT_TYPE, + attributes: { + name: 'fakeRuleName', + alertTypeId: 'myType', + schedule: { interval: '10s' }, + params: { + bar: true, + }, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + executionStatus: { + status: 'pending', + lastExecutionDate: new Date('2019-02-12T21:01:22.479Z'), + }, + notifyWhen: 'onActiveAlert', + actions: [ + { + actionTypeId: 'test-action-id', + group: 'default', + actionRef: 'action_0', + params: { + foo: true, + }, + uuid: 100, + }, + ], + artifacts: { + dashboards: [ + { + refId: 'dashboard_0', + }, + ], + }, + }, + score: 1, + references: [ + { + name: 'action_0', + type: 'action', + id: '1', + }, + { + name: 'dashboard_0', + type: 'dashboard', + id: 'dashboard-1', + }, + ], + }, + ], + }); + + const rulesClient = new RulesClient(rulesClientParams); + const result = await rulesClient.find({ options: {} }); + expect(result).toMatchInlineSnapshot(` + Object { + "data": Array [ + Object { + "actions": Array [ + Object { + "actionTypeId": "test-action-id", + "group": "default", + "id": "1", + "params": Object { + "foo": true, + }, + "uuid": 100, + }, + ], + "alertTypeId": "myType", + "artifacts": Object { + "dashboards": Array [ + Object { + "id": "dashboard-1", + }, + ], + }, + "createdAt": 2019-02-12T21:01:22.479Z, + "executionStatus": Object { + "lastExecutionDate": 2019-02-12T21:01:22.000Z, + "status": "pending", + }, + "id": "1", + "name": "fakeRuleName", + "notifyWhen": "onActiveAlert", + "params": Object { + "bar": true, + }, + "schedule": Object { + "interval": "10s", + }, + "snoozeSchedule": Array [], + "systemActions": Array [], + "updatedAt": 2019-02-12T21:01:22.479Z, + }, + ], + "page": 1, + "perPage": 10, + "total": 1, + } + `); + }); + }); }); diff --git a/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/get/get_rule.test.ts b/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/get/get_rule.test.ts index 71d32b6b42f1f..2f971a1be1cd9 100644 --- a/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/get/get_rule.test.ts +++ b/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/get/get_rule.test.ts @@ -132,6 +132,9 @@ describe('get()', () => { }, ], "alertTypeId": "123", + "artifacts": Object { + "dashboards": Array [], + }, "createdAt": 2019-02-12T21:01:22.479Z, "executionStatus": Object { "lastExecutionDate": 2020-08-20T19:23:38.000Z, @@ -227,6 +230,9 @@ describe('get()', () => { }, ], "alertTypeId": "123", + "artifacts": Object { + "dashboards": Array [], + }, "createdAt": 2019-02-12T21:01:22.479Z, "executionStatus": Object { "lastExecutionDate": 2020-08-20T19:23:38.000Z, @@ -311,6 +317,9 @@ describe('get()', () => { }, ], "alertTypeId": "123", + "artifacts": Object { + "dashboards": Array [], + }, "createdAt": 2019-02-12T21:01:22.479Z, "executionStatus": Object { "lastExecutionDate": 2020-08-20T19:23:38.000Z, @@ -438,6 +447,9 @@ describe('get()', () => { }, ], "alertTypeId": "123", + "artifacts": Object { + "dashboards": Array [], + }, "createdAt": 2019-02-12T21:01:22.479Z, "executionStatus": Object { "lastExecutionDate": 2020-08-20T19:23:38.000Z, diff --git a/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/update/schemas/update_rule_data_schema.ts b/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/update/schemas/update_rule_data_schema.ts index 9c0bf1666f846..5c17a317f9341 100644 --- a/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/update/schemas/update_rule_data_schema.ts +++ b/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/update/schemas/update_rule_data_schema.ts @@ -14,6 +14,7 @@ import { actionRequestSchema, systemActionRequestSchema, flappingSchema, + artifactsSchema, } from '../../../schemas'; export const updateRuleDataSchema = schema.object( @@ -30,6 +31,7 @@ export const updateRuleDataSchema = schema.object( notifyWhen: schema.maybe(schema.nullable(notifyWhenSchema)), alertDelay: schema.maybe(alertDelaySchema), flapping: schema.maybe(schema.nullable(flappingSchema)), + artifacts: schema.maybe(artifactsSchema), }, { unknowns: 'allow' } ); diff --git a/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/update/types/update_rule_data.ts b/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/update/types/update_rule_data.ts index 34ae35ac4c0f9..16d05cdc7ba86 100644 --- a/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/update/types/update_rule_data.ts +++ b/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/update/types/update_rule_data.ts @@ -22,4 +22,5 @@ export interface UpdateRuleData { notifyWhen?: UpdateRuleDataType['notifyWhen']; alertDelay?: UpdateRuleDataType['alertDelay']; flapping?: UpdateRuleDataType['flapping']; + artifacts?: UpdateRuleDataType['artifacts']; } diff --git a/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/update/update_rule.test.ts b/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/update/update_rule.test.ts index b1b5c81e19f84..140630c4ae804 100644 --- a/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/update/update_rule.test.ts +++ b/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/update/update_rule.test.ts @@ -411,6 +411,9 @@ describe('update()', () => { "alertDelay": Object { "active": 5, }, + "artifacts": Object { + "dashboards": Array [], + }, "createdAt": 2019-02-12T21:01:22.479Z, "enabled": true, "executionStatus": Object { @@ -481,6 +484,9 @@ describe('update()', () => { "apiKey": null, "apiKeyCreatedByUser": null, "apiKeyOwner": null, + "artifacts": Object { + "dashboards": Array [], + }, "consumer": "myApp", "createdAt": "2019-02-12T21:01:22.479Z", "createdBy": "elastic", @@ -746,6 +752,9 @@ describe('update()', () => { alertTypeId: 'myType', apiKey: null, apiKeyOwner: null, + artifacts: { + dashboards: [], + }, apiKeyCreatedByUser: null, consumer: 'myApp', enabled: true, @@ -800,6 +809,9 @@ describe('update()', () => { "uuid": undefined, }, ], + "artifacts": Object { + "dashboards": Array [], + }, "createdAt": 2019-02-12T21:01:22.479Z, "enabled": true, "executionStatus": Object { @@ -993,6 +1005,9 @@ describe('update()', () => { alertTypeId: 'myType', apiKey: null, apiKeyOwner: null, + artifacts: { + dashboards: [], + }, apiKeyCreatedByUser: null, consumer: 'myApp', enabled: true, @@ -1029,6 +1044,9 @@ describe('update()', () => { "uuid": undefined, }, ], + "artifacts": Object { + "dashboards": Array [], + }, "createdAt": 2019-02-12T21:01:22.479Z, "enabled": true, "executionStatus": Object { @@ -1204,6 +1222,9 @@ describe('update()', () => { alertTypeId: 'myType', apiKey: null, apiKeyOwner: null, + artifacts: { + dashboards: [], + }, apiKeyCreatedByUser: null, consumer: 'myApp', enabled: true, @@ -1250,6 +1271,9 @@ describe('update()', () => { "uuid": undefined, }, ], + "artifacts": Object { + "dashboards": Array [], + }, "createdAt": 2019-02-12T21:01:22.479Z, "enabled": true, "executionStatus": Object { @@ -1352,6 +1376,9 @@ describe('update()', () => { "uuid": undefined, }, ], + "artifacts": Object { + "dashboards": Array [], + }, "createdAt": 2019-02-12T21:01:22.479Z, "enabled": true, "executionStatus": Object { @@ -1400,6 +1427,9 @@ describe('update()', () => { "apiKey": "MTIzOmFiYw==", "apiKeyCreatedByUser": undefined, "apiKeyOwner": "elastic", + "artifacts": Object { + "dashboards": Array [], + }, "consumer": "myApp", "createdAt": "2019-02-12T21:01:22.479Z", "createdBy": "elastic", @@ -1532,6 +1562,9 @@ describe('update()', () => { "uuid": undefined, }, ], + "artifacts": Object { + "dashboards": Array [], + }, "createdAt": 2019-02-12T21:01:22.479Z, "enabled": false, "executionStatus": Object { @@ -1572,6 +1605,9 @@ describe('update()', () => { "apiKey": null, "apiKeyCreatedByUser": null, "apiKeyOwner": null, + "artifacts": Object { + "dashboards": Array [], + }, "consumer": "myApp", "createdAt": "2019-02-12T21:01:22.479Z", "createdBy": "elastic", @@ -2757,6 +2793,9 @@ describe('update()', () => { alertTypeId: 'myType', apiKey: null, apiKeyOwner: null, + artifacts: { + dashboards: [], + }, apiKeyCreatedByUser: null, consumer: 'myApp', enabled: true, @@ -2793,6 +2832,9 @@ describe('update()', () => { "uuid": undefined, }, ], + "artifacts": Object { + "dashboards": Array [], + }, "createdAt": 2019-02-12T21:01:22.479Z, "enabled": true, "executionStatus": Object { @@ -3350,6 +3392,9 @@ describe('update()', () => { alertTypeId: 'myType', apiKey: null, apiKeyOwner: null, + artifacts: { + dashboards: [], + }, apiKeyCreatedByUser: null, consumer: 'myApp', enabled: true, @@ -3526,6 +3571,9 @@ describe('update()', () => { }, ], "apiKeyCreatedByUser": true, + "artifacts": Object { + "dashboards": Array [], + }, "createdAt": 2019-02-12T21:01:22.479Z, "enabled": true, "executionStatus": Object { @@ -3568,6 +3616,9 @@ describe('update()', () => { "apiKey": "MTIzOmFiYw==", "apiKeyCreatedByUser": true, "apiKeyOwner": "elastic", + "artifacts": Object { + "dashboards": Array [], + }, "consumer": "myApp", "createdAt": "2019-02-12T21:01:22.479Z", "createdBy": "elastic", @@ -3825,6 +3876,9 @@ describe('update()', () => { alertTypeId: 'myType', apiKey: null, apiKeyOwner: null, + artifacts: { + dashboards: [], + }, apiKeyCreatedByUser: null, consumer: 'myApp', enabled: true, @@ -3861,6 +3915,9 @@ describe('update()', () => { "uuid": undefined, }, ], + "artifacts": Object { + "dashboards": Array [], + }, "createdAt": 2019-02-12T21:01:22.479Z, "enabled": true, "executionStatus": Object { @@ -4219,4 +4276,406 @@ describe('update()', () => { ).rejects.toMatchInlineSnapshot(`[Error: Unauthorized to execute actions]`); }); }); + + describe('artifacts', () => { + test('remove an existing dashboard', async () => { + const existingDashboards = [ + { + refId: 'dashboard_0', + }, + { + refId: 'dashboard_1', + }, + ]; + + const existingRule = { + id: '1', + type: RULE_SAVED_OBJECT_TYPE, + attributes: { + name: 'fakeRuleName', + enabled: true, + tags: ['foo'], + alertTypeId: 'myType', + schedule: { interval: '1m' }, + consumer: 'myApp', + revision: 0, + scheduledTaskId: 'task-123', + params: {}, + executionStatus: { + lastExecutionDate: '2019-02-12T21:01:22.479Z', + status: 'pending', + }, + muteAll: false, + legacyId: null, + snoozeSchedule: [], + mutedInstanceIds: [], + createdBy: 'elastic', + createdAt: '2019-02-12T21:01:22.479Z', + updatedBy: 'elastic', + updatedAt: '2019-02-12T21:01:22.479Z', + actions: [], + artifacts: { + dashboards: existingDashboards, + }, + }, + references: [ + { + name: 'dashboard_0', + type: 'dashboard', + id: 'dashboard-1', + }, + { + name: 'dashboard_1', + type: 'dashboard', + id: 'dashboard-2', + }, + ], + version: '123', + }; + + unsecuredSavedObjectsClient.get.mockResolvedValue(existingRule); + + unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ + id: '1', + type: RULE_SAVED_OBJECT_TYPE, + attributes: { + enabled: true, + schedule: { interval: '1m' }, + params: { + bar: true, + }, + actions: [], + artifacts: { + dashboards: [ + { + refId: 'dashboard_0', + }, + ], + }, + executionStatus: { + lastExecutionDate: '2019-02-12T21:01:22.479Z', + status: 'pending', + }, + notifyWhen: 'onActiveAlert', + revision: 1, + scheduledTaskId: 'task-123', + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }, + references: [ + { + name: 'dashboard_0', + type: 'dashboard', + id: 'dashboard-1', + }, + ], + }); + + const result = await rulesClient.update({ + id: '1', + data: { + schedule: { interval: '1m' }, + name: 'abc', + tags: ['foo'], + params: { + bar: true, + }, + throttle: null, + notifyWhen: 'onActiveAlert', + actions: [], + systemActions: [], + artifacts: { + dashboards: [ + { + id: 'dashboard-1', + }, + ], + }, + }, + }); + + expect(unsecuredSavedObjectsClient.create).toHaveBeenNthCalledWith( + 1, + RULE_SAVED_OBJECT_TYPE, + expect.objectContaining({ + artifacts: { + dashboards: [ + { + refId: 'dashboard_0', + }, + ], + }, + }), + { + id: '1', + overwrite: true, + references: [{ id: 'dashboard-1', name: 'dashboard_0', type: 'dashboard' }], + version: '123', + } + ); + + expect(result?.artifacts).toEqual({ + dashboards: [ + { + id: 'dashboard-1', + }, + ], + }); + + expect(result).toMatchInlineSnapshot(` + Object { + "actions": Array [], + "artifacts": Object { + "dashboards": Array [ + Object { + "id": "dashboard-1", + }, + ], + }, + "createdAt": 2019-02-12T21:01:22.479Z, + "enabled": true, + "executionStatus": Object { + "lastExecutionDate": 2019-02-12T21:01:22.479Z, + "status": "pending", + }, + "id": "1", + "notifyWhen": "onActiveAlert", + "params": Object { + "bar": true, + }, + "revision": 1, + "schedule": Object { + "interval": "1m", + }, + "scheduledTaskId": "task-123", + "systemActions": Array [], + "updatedAt": 2019-02-12T21:01:22.479Z, + } + `); + + expect(unsecuredSavedObjectsClient.get).not.toHaveBeenCalled(); + }); + + test('adds a new linked dashboard', async () => { + const existingDashboards = [ + { + refId: 'dashboard_0', + }, + { + refId: 'dashboard_1', + }, + ]; + + const existingRule = { + id: '1', + type: RULE_SAVED_OBJECT_TYPE, + attributes: { + name: 'fakeRuleName', + enabled: true, + tags: ['foo'], + alertTypeId: 'myType', + schedule: { interval: '1m' }, + consumer: 'myApp', + revision: 0, + scheduledTaskId: 'task-123', + params: {}, + executionStatus: { + lastExecutionDate: '2019-02-12T21:01:22.479Z', + status: 'pending', + }, + muteAll: false, + legacyId: null, + snoozeSchedule: [], + mutedInstanceIds: [], + createdBy: 'elastic', + createdAt: '2019-02-12T21:01:22.479Z', + updatedBy: 'elastic', + updatedAt: '2019-02-12T21:01:22.479Z', + actions: [], + artifacts: { + dashboards: existingDashboards, + }, + }, + references: [ + { + name: 'dashboard_0', + type: 'dashboard', + id: 'dashboard-1', + }, + { + name: 'dashboard_1', + type: 'dashboard', + id: 'dashboard-2', + }, + ], + version: '123', + }; + + unsecuredSavedObjectsClient.get.mockResolvedValue(existingRule); + + unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ + id: '1', + type: RULE_SAVED_OBJECT_TYPE, + attributes: { + enabled: true, + schedule: { interval: '1m' }, + params: { + bar: true, + }, + actions: [], + artifacts: { + dashboards: [ + { + refId: 'dashboard_0', + }, + { + refId: 'dashboard_1', + }, + { + refId: 'dashboard_2', + }, + ], + }, + executionStatus: { + lastExecutionDate: '2019-02-12T21:01:22.479Z', + status: 'pending', + }, + notifyWhen: 'onActiveAlert', + revision: 1, + scheduledTaskId: 'task-123', + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }, + references: [ + { + name: 'dashboard_0', + type: 'dashboard', + id: 'dashboard-1', + }, + { + name: 'dashboard_1', + type: 'dashboard', + id: 'dashboard-2', + }, + { + name: 'dashboard_2', + type: 'dashboard', + id: 'dashboard-3', + }, + ], + }); + + const result = await rulesClient.update({ + id: '1', + data: { + schedule: { interval: '1m' }, + name: 'abc', + tags: ['foo'], + params: { + bar: true, + }, + throttle: null, + notifyWhen: 'onActiveAlert', + actions: [], + systemActions: [], + artifacts: { + dashboards: [ + { + id: 'dashboard-1', + }, + { + id: 'dashboard-2', + }, + { + id: 'dashboard-3', + }, + ], + }, + }, + }); + + expect(unsecuredSavedObjectsClient.create).toHaveBeenNthCalledWith( + 1, + RULE_SAVED_OBJECT_TYPE, + expect.objectContaining({ + artifacts: { + dashboards: [ + { + refId: 'dashboard_0', + }, + { + refId: 'dashboard_1', + }, + { + refId: 'dashboard_2', + }, + ], + }, + }), + { + id: '1', + overwrite: true, + references: [ + { id: 'dashboard-1', name: 'dashboard_0', type: 'dashboard' }, + { id: 'dashboard-2', name: 'dashboard_1', type: 'dashboard' }, + { id: 'dashboard-3', name: 'dashboard_2', type: 'dashboard' }, + ], + version: '123', + } + ); + + expect(result?.artifacts).toEqual({ + dashboards: [ + { + id: 'dashboard-1', + }, + { + id: 'dashboard-2', + }, + { + id: 'dashboard-3', + }, + ], + }); + + expect(result).toMatchInlineSnapshot(` + Object { + "actions": Array [], + "artifacts": Object { + "dashboards": Array [ + Object { + "id": "dashboard-1", + }, + Object { + "id": "dashboard-2", + }, + Object { + "id": "dashboard-3", + }, + ], + }, + "createdAt": 2019-02-12T21:01:22.479Z, + "enabled": true, + "executionStatus": Object { + "lastExecutionDate": 2019-02-12T21:01:22.479Z, + "status": "pending", + }, + "id": "1", + "notifyWhen": "onActiveAlert", + "params": Object { + "bar": true, + }, + "revision": 1, + "schedule": Object { + "interval": "1m", + }, + "scheduledTaskId": "task-123", + "systemActions": Array [], + "updatedAt": 2019-02-12T21:01:22.479Z, + } + `); + + expect(unsecuredSavedObjectsClient.get).not.toHaveBeenCalled(); + }); + }); }); diff --git a/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/update/update_rule.ts b/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/update/update_rule.ts index 8cd814f155ddb..187e4da9e8901 100644 --- a/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/update/update_rule.ts +++ b/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/update/update_rule.ts @@ -306,6 +306,7 @@ async function updateRuleAttributes({ let updatedRule = { ...originalRule }; const allActions = [...updateRuleData.actions, ...(updateRuleData.systemActions ?? [])]; + const artifacts = updateRuleData.artifacts ?? {}; const ruleType = context.ruleTypeRegistry.get(updatedRule.alertTypeId); // Extract saved object references for this rule @@ -313,11 +314,13 @@ async function updateRuleAttributes({ references: extractedReferences, params: updatedParams, actions: actionsWithRefs, + artifacts: artifactsWithRefs, } = await extractReferences( context, ruleType, allActions as NormalizedAlertActionWithGeneratedValues[], - validatedRuleTypeParams + validatedRuleTypeParams, + artifacts ); // Increment revision if applicable field has changed @@ -360,7 +363,7 @@ async function updateRuleAttributes({ const updatedRuleAttributes = updateMetaAttributes(context, { ...updatedRule, - ...omit(updateRuleData, 'actions', 'systemActions'), + ...omit(updateRuleData, 'actions', 'systemActions', 'artifacts'), ...apiKeyAttributes, params: updatedParams as RawRule['params'], actions: actionsWithRefs, @@ -368,6 +371,7 @@ async function updateRuleAttributes({ revision, updatedBy: username, updatedAt: new Date().toISOString(), + artifacts: artifactsWithRefs, }); const mappedParams = getMappedParams(updatedParams); diff --git a/x-pack/platform/plugins/shared/alerting/server/application/rule/schemas/artifacts_schema.ts b/x-pack/platform/plugins/shared/alerting/server/application/rule/schemas/artifacts_schema.ts new file mode 100644 index 0000000000000..7a713b3816e11 --- /dev/null +++ b/x-pack/platform/plugins/shared/alerting/server/application/rule/schemas/artifacts_schema.ts @@ -0,0 +1,18 @@ +/* + * 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 { schema } from '@kbn/config-schema'; + +export const dashboardsSchema = schema.arrayOf( + schema.object({ + id: schema.string(), + }) +); + +export const artifactsSchema = schema.object({ + dashboards: schema.maybe(dashboardsSchema), +}); diff --git a/x-pack/platform/plugins/shared/alerting/server/application/rule/schemas/index.ts b/x-pack/platform/plugins/shared/alerting/server/application/rule/schemas/index.ts index dc0d310f36535..e40955d039ca0 100644 --- a/x-pack/platform/plugins/shared/alerting/server/application/rule/schemas/index.ts +++ b/x-pack/platform/plugins/shared/alerting/server/application/rule/schemas/index.ts @@ -9,3 +9,4 @@ export * from './rule_schemas'; export * from './action_schemas'; export * from './notify_when_schema'; export * from './flapping_schema'; +export * from './artifacts_schema'; diff --git a/x-pack/platform/plugins/shared/alerting/server/application/rule/schemas/rule_schemas.ts b/x-pack/platform/plugins/shared/alerting/server/application/rule/schemas/rule_schemas.ts index 134f6f2ff896a..80a7be7e8723d 100644 --- a/x-pack/platform/plugins/shared/alerting/server/application/rule/schemas/rule_schemas.ts +++ b/x-pack/platform/plugins/shared/alerting/server/application/rule/schemas/rule_schemas.ts @@ -18,6 +18,7 @@ import { dateSchema } from './date_schema'; import { notifyWhenSchema } from './notify_when_schema'; import { actionSchema, systemActionSchema } from './action_schemas'; import { flappingSchema } from './flapping_schema'; +import { artifactsSchema } from './artifacts_schema'; export const mappedParamsSchema = schema.recordOf(schema.string(), schema.maybe(schema.any())); @@ -189,6 +190,7 @@ export const ruleDomainSchema = schema.object({ alertDelay: schema.maybe(alertDelaySchema), legacyId: schema.maybe(schema.nullable(schema.string())), flapping: schema.maybe(schema.nullable(flappingSchema)), + artifacts: schema.maybe(artifactsSchema), }); /** @@ -230,4 +232,5 @@ export const ruleSchema = schema.object({ alertDelay: schema.maybe(alertDelaySchema), legacyId: schema.maybe(schema.nullable(schema.string())), flapping: schema.maybe(schema.nullable(flappingSchema)), + artifacts: schema.maybe(artifactsSchema), }); diff --git a/x-pack/platform/plugins/shared/alerting/server/application/rule/transforms/transform_raw_artifacts_to_domain_artifacts.test.ts b/x-pack/platform/plugins/shared/alerting/server/application/rule/transforms/transform_raw_artifacts_to_domain_artifacts.test.ts new file mode 100644 index 0000000000000..4d7ed06ebce7d --- /dev/null +++ b/x-pack/platform/plugins/shared/alerting/server/application/rule/transforms/transform_raw_artifacts_to_domain_artifacts.test.ts @@ -0,0 +1,89 @@ +/* + * 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 { SavedObjectReference } from '@kbn/core/server'; +import type { RawRule } from '../../../types'; +import { transformRawArtifactsToDomainArtifacts } from './transform_raw_artifacts_to_domain_artifacts'; + +describe('transformRawArtifactsToDomainArtifacts', () => { + it('should return default artifacts if rawArtifacts is undefined', () => { + const result = transformRawArtifactsToDomainArtifacts('1', undefined, []); + expect(result).toEqual({ dashboards: [] }); + }); + + it('should return artifacts with injected references', () => { + const rawArtifacts: RawRule['artifacts'] = { + dashboards: [ + { + refId: 'dashboard-1', + }, + { + refId: 'dashboard-2', + }, + ], + }; + const references: SavedObjectReference[] = [ + { + id: 'dashboard_0', + name: 'dashboard-1', + type: 'dashboard', + }, + { + id: 'dashboard_1', + name: 'dashboard-2', + type: 'dashboard', + }, + ]; + const result = transformRawArtifactsToDomainArtifacts('1', rawArtifacts, references); + expect(result).toEqual({ + dashboards: [ + { + id: 'dashboard_0', + }, + { + id: 'dashboard_1', + }, + ], + }); + }); + + it('should return artifacts with empty dashboards array if no dashboards in rawArtifacts', () => { + const rawArtifacts: RawRule['artifacts'] = {}; + const references: SavedObjectReference[] = []; + const result = transformRawArtifactsToDomainArtifacts('1', rawArtifacts, references); + expect(result).toEqual({ dashboards: [] }); + }); + + it('should return artifacts with injected references and empty dashboards array if no dashboards in rawArtifacts', () => { + const rawArtifacts: RawRule['artifacts'] = {}; + const references: SavedObjectReference[] = [ + { + id: 'dashboard-1', + name: 'dashboard_0', + type: 'dashboard', + }, + ]; + const result = transformRawArtifactsToDomainArtifacts('1', rawArtifacts, references); + expect(result).toEqual({ dashboards: [] }); + }); + + it('throws an error if no references found', () => { + const rawArtifacts: RawRule['artifacts'] = { + dashboards: [ + { + refId: 'dashboard-1', + }, + ], + }; + const references: SavedObjectReference[] = []; + expect(() => + transformRawArtifactsToDomainArtifacts('1', rawArtifacts, references) + ).toThrowErrorMatchingInlineSnapshot( + `"Artifacts reference \\"dashboard-1\\" not found in rule id: 1"` + ); + }); +}); diff --git a/x-pack/platform/plugins/shared/alerting/server/application/rule/transforms/transform_raw_artifacts_to_domain_artifacts.ts b/x-pack/platform/plugins/shared/alerting/server/application/rule/transforms/transform_raw_artifacts_to_domain_artifacts.ts new file mode 100644 index 0000000000000..e40c71a3814d7 --- /dev/null +++ b/x-pack/platform/plugins/shared/alerting/server/application/rule/transforms/transform_raw_artifacts_to_domain_artifacts.ts @@ -0,0 +1,19 @@ +/* + * 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 { SavedObjectReference } from '@kbn/core/server'; +import type { RawRule } from '../../../types'; +import type { RuleDomain } from '../types'; +import { injectReferencesIntoArtifacts } from '../../../rules_client/common'; + +export function transformRawArtifactsToDomainArtifacts( + id: string, + rawArtifacts?: RawRule['artifacts'], + references?: SavedObjectReference[] +): Required { + return injectReferencesIntoArtifacts(id, rawArtifacts, references || []); +} diff --git a/x-pack/platform/plugins/shared/alerting/server/application/rule/transforms/transform_rule_attributes_to_rule_domain.test.ts b/x-pack/platform/plugins/shared/alerting/server/application/rule/transforms/transform_rule_attributes_to_rule_domain.test.ts index b24cb5d608219..80fa9034c7082 100644 --- a/x-pack/platform/plugins/shared/alerting/server/application/rule/transforms/transform_rule_attributes_to_rule_domain.test.ts +++ b/x-pack/platform/plugins/shared/alerting/server/application/rule/transforms/transform_rule_attributes_to_rule_domain.test.ts @@ -64,44 +64,44 @@ const isSystemAction = (id: string) => id === 'my-system-action-id'; describe('transformRuleAttributesToRuleDomain', () => { const MOCK_API_KEY = Buffer.from('123:abc').toString('base64'); const logger = loggingSystemMock.create().get(); - const references = [{ name: 'default-action-ref', type: 'action', id: 'default-action-id' }]; - - const rule = { - enabled: false, - tags: ['foo'], - createdBy: 'user', - createdAt: '2019-02-12T21:01:22.479Z', - updatedAt: '2019-02-12T21:01:22.479Z', - legacyId: null, - muteAll: false, - mutedInstanceIds: [], - snoozeSchedule: [], - alertTypeId: 'myType', - schedule: { interval: '1m' }, - consumer: 'myApp', - scheduledTaskId: 'task-123', - executionStatus: { - lastExecutionDate: '2019-02-12T21:01:22.479Z', - status: 'pending' as const, - error: null, - warning: null, - }, - params: {}, - throttle: null, - notifyWhen: null, - actions: [defaultAction, systemAction], - name: 'my rule name', - revision: 0, - updatedBy: 'user', - apiKey: MOCK_API_KEY, - apiKeyOwner: 'user', - flapping: { - lookBackWindow: 20, - statusChangeThreshold: 20, - }, - }; it('transforms the actions correctly', () => { + const references = [{ name: 'default-action-ref', type: 'action', id: 'default-action-id' }]; + + const rule = { + enabled: false, + tags: ['foo'], + createdBy: 'user', + createdAt: '2019-02-12T21:01:22.479Z', + updatedAt: '2019-02-12T21:01:22.479Z', + legacyId: null, + muteAll: false, + mutedInstanceIds: [], + snoozeSchedule: [], + alertTypeId: 'myType', + schedule: { interval: '1m' }, + consumer: 'myApp', + scheduledTaskId: 'task-123', + executionStatus: { + lastExecutionDate: '2019-02-12T21:01:22.479Z', + status: 'pending' as const, + error: null, + warning: null, + }, + params: {}, + throttle: null, + notifyWhen: null, + actions: [defaultAction, systemAction], + name: 'my rule name', + revision: 0, + updatedBy: 'user', + apiKey: MOCK_API_KEY, + apiKeyOwner: 'user', + flapping: { + lookBackWindow: 20, + statusChangeThreshold: 20, + }, + }; const res = transformRuleAttributesToRuleDomain( rule, { @@ -153,4 +153,88 @@ describe('transformRuleAttributesToRuleDomain', () => { ] `); }); + + it('transforms the artifacts correctly', () => { + const artifacts = { + dashboards: [ + { + refId: 'dashboard_0', + }, + { + refId: 'dashboard_1', + }, + ], + }; + + const actionReferences = [ + { name: 'default-action-ref', type: 'action', id: 'default-action-id' }, + ]; + + const artifactsReferences = [ + { name: 'dashboard_0', type: 'dashboard', id: 'dashboard-1' }, + { name: 'dashboard_1', type: 'dashboard', id: 'dashboard-2' }, + ]; + + const references = [...actionReferences, ...artifactsReferences]; + + const rule = { + enabled: false, + tags: ['foo'], + createdBy: 'user', + createdAt: '2019-02-12T21:01:22.479Z', + updatedAt: '2019-02-12T21:01:22.479Z', + legacyId: null, + muteAll: false, + mutedInstanceIds: [], + snoozeSchedule: [], + alertTypeId: 'myType', + schedule: { interval: '1m' }, + consumer: 'myApp', + scheduledTaskId: 'task-123', + executionStatus: { + lastExecutionDate: '2019-02-12T21:01:22.479Z', + status: 'pending' as const, + error: null, + warning: null, + }, + params: {}, + throttle: null, + notifyWhen: null, + actions: [defaultAction, systemAction], + artifacts, + name: 'my rule name', + revision: 0, + updatedBy: 'user', + apiKey: MOCK_API_KEY, + apiKeyOwner: 'user', + flapping: { + lookBackWindow: 20, + statusChangeThreshold: 20, + }, + }; + + const res = transformRuleAttributesToRuleDomain( + rule, + { + id: '1', + logger, + ruleType, + references, + }, + isSystemAction + ); + + expect(res.artifacts).toMatchInlineSnapshot(` + Object { + "dashboards": Array [ + Object { + "id": "dashboard-1", + }, + Object { + "id": "dashboard-2", + }, + ], + } + `); + }); }); diff --git a/x-pack/platform/plugins/shared/alerting/server/application/rule/transforms/transform_rule_attributes_to_rule_domain.ts b/x-pack/platform/plugins/shared/alerting/server/application/rule/transforms/transform_rule_attributes_to_rule_domain.ts index afb0e76bc8526..70489f4377cec 100644 --- a/x-pack/platform/plugins/shared/alerting/server/application/rule/transforms/transform_rule_attributes_to_rule_domain.ts +++ b/x-pack/platform/plugins/shared/alerting/server/application/rule/transforms/transform_rule_attributes_to_rule_domain.ts @@ -18,6 +18,7 @@ import { transformRawActionsToDomainActions, transformRawActionsToDomainSystemActions, } from './transform_raw_actions_to_domain_actions'; +import { transformRawArtifactsToDomainArtifacts } from './transform_raw_artifacts_to_domain_artifacts'; const INITIAL_LAST_RUN_METRICS = { duration: 0, @@ -169,6 +170,11 @@ export const transformRuleAttributesToRuleDomain = ( id, ruleType, @@ -234,6 +240,7 @@ export const transformRuleAttributesToRuleDomain = { }, }); }); + + it('should include artifacts', () => { + const ruleWithArtifacts: RuleDomain<{}> = { + id: 'test', + enabled: false, + name: 'my rule name', + tags: ['foo'], + alertTypeId: 'myType', + consumer: 'myApp', + schedule: { interval: '1m' }, + actions: [defaultAction], + systemActions: [systemAction], + params: {}, + mapped_params: {}, + createdBy: 'user', + createdAt: new Date('2019-02-12T21:01:22.479Z'), + updatedAt: new Date('2019-02-12T21:01:22.479Z'), + legacyId: 'legacyId', + muteAll: false, + mutedInstanceIds: [], + snoozeSchedule: [], + scheduledTaskId: 'task-123', + executionStatus: { + lastExecutionDate: new Date('2019-02-12T21:01:22.479Z'), + status: 'pending' as const, + }, + throttle: null, + notifyWhen: null, + revision: 0, + updatedBy: 'user', + apiKey: MOCK_API_KEY, + apiKeyOwner: 'user', + flapping: { + lookBackWindow: 20, + statusChangeThreshold: 20, + }, + artifacts: { + dashboards: [ + { + id: 'dashboard-1', + }, + ], + }, + }; + const result = transformRuleDomainToRule(ruleWithArtifacts); + + expect(result).toEqual({ + id: 'test', + enabled: false, + name: 'my rule name', + tags: ['foo'], + alertTypeId: 'myType', + consumer: 'myApp', + schedule: { interval: '1m' }, + actions: [defaultAction], + systemActions: [systemAction], + params: {}, + mapped_params: {}, + createdBy: 'user', + createdAt: new Date('2019-02-12T21:01:22.479Z'), + updatedAt: new Date('2019-02-12T21:01:22.479Z'), + muteAll: false, + mutedInstanceIds: [], + snoozeSchedule: [], + scheduledTaskId: 'task-123', + executionStatus: { + lastExecutionDate: new Date('2019-02-12T21:01:22.479Z'), + status: 'pending' as const, + }, + throttle: null, + notifyWhen: null, + revision: 0, + updatedBy: 'user', + apiKeyOwner: 'user', + flapping: { + lookBackWindow: 20, + statusChangeThreshold: 20, + }, + artifacts: { + dashboards: [ + { + id: 'dashboard-1', + }, + ], + }, + }); + }); }); diff --git a/x-pack/platform/plugins/shared/alerting/server/application/rule/transforms/transform_rule_domain_to_rule.ts b/x-pack/platform/plugins/shared/alerting/server/application/rule/transforms/transform_rule_domain_to_rule.ts index 3373052d67b2e..125d7659d6182 100644 --- a/x-pack/platform/plugins/shared/alerting/server/application/rule/transforms/transform_rule_domain_to_rule.ts +++ b/x-pack/platform/plugins/shared/alerting/server/application/rule/transforms/transform_rule_domain_to_rule.ts @@ -54,6 +54,7 @@ export const transformRuleDomainToRule = ( alertDelay: ruleDomain.alertDelay, legacyId: ruleDomain.legacyId, flapping: ruleDomain.flapping, + artifacts: ruleDomain.artifacts, }; if (isPublic) { diff --git a/x-pack/platform/plugins/shared/alerting/server/application/rule/transforms/transform_rule_domain_to_rule_attributes.test.ts b/x-pack/platform/plugins/shared/alerting/server/application/rule/transforms/transform_rule_domain_to_rule_attributes.test.ts index 52246dde4f647..9b102bd1a4be9 100644 --- a/x-pack/platform/plugins/shared/alerting/server/application/rule/transforms/transform_rule_domain_to_rule_attributes.test.ts +++ b/x-pack/platform/plugins/shared/alerting/server/application/rule/transforms/transform_rule_domain_to_rule_attributes.test.ts @@ -19,6 +19,17 @@ describe('transformRuleDomainToRuleAttributes', () => { params: {}, }; + const artifacts = { + dashboards: [ + { + id: 'dashboard-1', + }, + { + id: 'dashboard-2', + }, + ], + }; + const rule: RuleDomain<{}> = { id: 'test', enabled: false, @@ -28,6 +39,7 @@ describe('transformRuleDomainToRuleAttributes', () => { consumer: 'myApp', schedule: { interval: '1m' }, actions: [defaultAction], + artifacts, params: {}, mapped_params: {}, createdBy: 'user', @@ -66,6 +78,13 @@ describe('transformRuleDomainToRuleAttributes', () => { params: {}, }, ], + artifactsWithRefs: { + dashboards: [ + { + refId: 'dashboard_0', + }, + ], + }, params: { legacyId: 'test', paramsWithRefs: {}, @@ -86,6 +105,13 @@ describe('transformRuleDomainToRuleAttributes', () => { "alertTypeId": "myType", "apiKey": "MTIzOmFiYw==", "apiKeyOwner": "user", + "artifacts": Object { + "dashboards": Array [ + Object { + "refId": "dashboard_0", + }, + ], + }, "consumer": "myApp", "createdAt": "2019-02-12T21:01:22.479Z", "createdBy": "user", diff --git a/x-pack/platform/plugins/shared/alerting/server/application/rule/transforms/transform_rule_domain_to_rule_attributes.ts b/x-pack/platform/plugins/shared/alerting/server/application/rule/transforms/transform_rule_domain_to_rule_attributes.ts index 07f6d1c50deaa..05f2b1a29facb 100644 --- a/x-pack/platform/plugins/shared/alerting/server/application/rule/transforms/transform_rule_domain_to_rule_attributes.ts +++ b/x-pack/platform/plugins/shared/alerting/server/application/rule/transforms/transform_rule_domain_to_rule_attributes.ts @@ -7,7 +7,7 @@ import type { RawRule } from '../../../types'; import type { RuleDomain } from '../types'; import { getMappedParams } from '../../../rules_client/common'; -import type { DenormalizedAction } from '../../../rules_client'; +import type { DenormalizedAction, DenormalizedArtifacts } from '../../../rules_client'; interface TransformRuleToEsParams { legacyId: RawRule['legacyId']; @@ -17,10 +17,12 @@ interface TransformRuleToEsParams { export const transformRuleDomainToRuleAttributes = ({ actionsWithRefs, + artifactsWithRefs, rule, params, }: { actionsWithRefs: DenormalizedAction[]; + artifactsWithRefs: DenormalizedArtifacts; rule: Omit; params: TransformRuleToEsParams; }): RawRule => { @@ -81,5 +83,6 @@ export const transformRuleDomainToRuleAttributes = ({ ...(rule.running !== undefined ? { running: rule.running } : {}), ...(rule.alertDelay !== undefined ? { alertDelay: rule.alertDelay } : {}), ...(rule.flapping !== undefined ? { flapping: rule.flapping } : {}), + artifacts: artifactsWithRefs, } as RawRule; }; diff --git a/x-pack/platform/plugins/shared/alerting/server/application/rule/types/rule.ts b/x-pack/platform/plugins/shared/alerting/server/application/rule/types/rule.ts index 103b397caefb5..47131738e6b2d 100644 --- a/x-pack/platform/plugins/shared/alerting/server/application/rule/types/rule.ts +++ b/x-pack/platform/plugins/shared/alerting/server/application/rule/types/rule.ts @@ -85,6 +85,7 @@ export interface Rule { alertDelay?: RuleSchemaType['alertDelay']; legacyId?: RuleSchemaType['legacyId']; flapping?: RuleSchemaType['flapping']; + artifacts?: RuleSchemaType['artifacts']; } export interface RuleDomain { @@ -124,4 +125,5 @@ export interface RuleDomain { alertDelay?: RuleSchemaType['alertDelay']; legacyId?: RuleSchemaType['legacyId']; flapping?: RuleSchemaType['flapping']; + artifacts?: RuleDomainSchemaType['artifacts']; } diff --git a/x-pack/platform/plugins/shared/alerting/server/routes/rule/apis/create/transforms/transform_create_body/v1.ts b/x-pack/platform/plugins/shared/alerting/server/routes/rule/apis/create/transforms/transform_create_body/v1.ts index d8b646f673a64..dc0c707c6b8ad 100644 --- a/x-pack/platform/plugins/shared/alerting/server/routes/rule/apis/create/transforms/transform_create_body/v1.ts +++ b/x-pack/platform/plugins/shared/alerting/server/routes/rule/apis/create/transforms/transform_create_body/v1.ts @@ -103,5 +103,6 @@ export const transformCreateBody = ({ ...(createBody.flapping !== undefined ? { flapping: transformCreateBodyFlapping(createBody.flapping) } : {}), + ...(createBody.artifacts ? { artifacts: createBody.artifacts } : {}), }; }; diff --git a/x-pack/platform/plugins/shared/alerting/server/routes/rule/apis/find/find_internal_rules_route.test.ts b/x-pack/platform/plugins/shared/alerting/server/routes/rule/apis/find/find_internal_rules_route.test.ts index 53b330a8a2044..b4712c393dfdd 100644 --- a/x-pack/platform/plugins/shared/alerting/server/routes/rule/apis/find/find_internal_rules_route.test.ts +++ b/x-pack/platform/plugins/shared/alerting/server/routes/rule/apis/find/find_internal_rules_route.test.ts @@ -111,4 +111,179 @@ describe('findInternalRulesRoute', () => { }, }); }); + + it('returns artifacts in the response', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + findInternalRulesRoute(router, licenseState); + + const [config, handler] = router.post.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/internal/alerting/rules/_find"`); + + const findResult = { + page: 1, + perPage: 1, + total: 0, + data: [ + { + id: '3d534c70-582b-11ec-8995-2b1578a3bc5d', + notifyWhen: 'onActiveAlert' as const, + alertTypeId: '.index-threshold', + name: 'stressing index-threshold 37/200', + consumer: 'alerts', + tags: [], + enabled: true, + throttle: null, + apiKey: null, + apiKeyOwner: '2889684073', + createdBy: 'elastic', + updatedBy: '2889684073', + muteAll: false, + mutedInstanceIds: [], + schedule: { + interval: '1s', + }, + snoozeSchedule: [], + actions: [ + { + actionTypeId: '.server-log', + params: { + message: 'alert 37: {{context.message}}', + }, + group: 'threshold met', + id: '3619a0d0-582b-11ec-8995-2b1578a3bc5d', + uuid: '123-456', + }, + ], + systemActions: [ + { actionTypeId: '.test', id: 'system_action-id', params: {}, uuid: '789' }, + ], + params: { x: 42 }, + updatedAt: new Date('2024-03-21T13:15:00.498Z'), + createdAt: new Date('2024-03-21T13:15:00.498Z'), + scheduledTaskId: '52125fb0-5895-11ec-ae69-bb65d1a71b72', + executionStatus: { + status: 'ok' as const, + lastExecutionDate: new Date('2024-03-21T13:15:00.498Z'), + lastDuration: 1194, + }, + revision: 0, + artifacts: { + dashboards: [ + { + id: 'dashboard-1', + }, + ], + }, + }, + ], + }; + + rulesClient.find.mockResolvedValueOnce(findResult); + + const [context, req, res] = mockHandlerArguments( + { rulesClient }, + { + body: { + per_page: 1, + page: 1, + default_search_operator: 'OR', + rule_type_ids: ['foo'], + consumers: ['bar'], + }, + }, + ['ok'] + ); + + expect(await handler(context, req, res)).toMatchInlineSnapshot(` + Object { + "body": Object { + "data": Array [ + Object { + "actions": Array [ + Object { + "connector_type_id": ".server-log", + "group": "threshold met", + "id": "3619a0d0-582b-11ec-8995-2b1578a3bc5d", + "params": Object { + "message": "alert 37: {{context.message}}", + }, + "uuid": "123-456", + }, + Object { + "connector_type_id": ".test", + "id": "system_action-id", + "params": Object {}, + "uuid": "789", + }, + ], + "api_key_owner": "2889684073", + "artifacts": Object { + "dashboards": Array [ + Object { + "id": "dashboard-1", + }, + ], + }, + "consumer": "alerts", + "created_at": "2024-03-21T13:15:00.498Z", + "created_by": "elastic", + "enabled": true, + "execution_status": Object { + "last_duration": 1194, + "last_execution_date": "2024-03-21T13:15:00.498Z", + "status": "ok", + }, + "id": "3d534c70-582b-11ec-8995-2b1578a3bc5d", + "mute_all": false, + "muted_alert_ids": Array [], + "name": "stressing index-threshold 37/200", + "notify_when": "onActiveAlert", + "params": Object { + "x": 42, + }, + "revision": 0, + "rule_type_id": ".index-threshold", + "schedule": Object { + "interval": "1s", + }, + "scheduled_task_id": "52125fb0-5895-11ec-ae69-bb65d1a71b72", + "snooze_schedule": Array [], + "tags": Array [], + "throttle": null, + "updated_at": "2024-03-21T13:15:00.498Z", + "updated_by": "2889684073", + }, + ], + "page": 1, + "per_page": 1, + "total": 0, + }, + } + `); + + expect(rulesClient.find).toHaveBeenCalledTimes(1); + + expect(rulesClient.find.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "excludeFromPublicApi": false, + "includeSnoozeData": true, + "options": Object { + "consumers": Array [ + "bar", + ], + "defaultSearchOperator": "OR", + "page": 1, + "perPage": 1, + "ruleTypeIds": Array [ + "foo", + ], + }, + }, + ] + `); + }); }); diff --git a/x-pack/platform/plugins/shared/alerting/server/routes/rule/apis/find/find_internal_rules_route.ts b/x-pack/platform/plugins/shared/alerting/server/routes/rule/apis/find/find_internal_rules_route.ts index 43a68e3dfec2d..ac7d2a2dfae65 100644 --- a/x-pack/platform/plugins/shared/alerting/server/routes/rule/apis/find/find_internal_rules_route.ts +++ b/x-pack/platform/plugins/shared/alerting/server/routes/rule/apis/find/find_internal_rules_route.ts @@ -69,7 +69,7 @@ export const findInternalRulesRoute = ( }); const responseBody: FindRulesResponseV1['body'] = - transformFindRulesResponseV1(findResult, options.fields); + transformFindRulesResponseV1(findResult, options.fields, true); return res.ok({ body: responseBody, diff --git a/x-pack/platform/plugins/shared/alerting/server/routes/rule/apis/find/find_rules_route.test.ts b/x-pack/platform/plugins/shared/alerting/server/routes/rule/apis/find/find_rules_route.test.ts index 27769cf237389..97e9f7826b7df 100644 --- a/x-pack/platform/plugins/shared/alerting/server/routes/rule/apis/find/find_rules_route.test.ts +++ b/x-pack/platform/plugins/shared/alerting/server/routes/rule/apis/find/find_rules_route.test.ts @@ -581,4 +581,221 @@ describe('findRulesRoute', () => { }, }); }); + + it('should not return artifacts in the response', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + findRulesRoute(router, licenseState); + + const [config, handler] = router.get.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/api/alerting/rules/_find"`); + + const findResult = { + page: 1, + perPage: 1, + total: 0, + data: [ + { + id: '3d534c70-582b-11ec-8995-2b1578a3bc5d', + notifyWhen: 'onActiveAlert' as const, + alertTypeId: '.index-threshold', + name: 'stressing index-threshold 37/200', + consumer: 'alerts', + tags: [], + enabled: true, + throttle: null, + apiKey: null, + apiKeyOwner: '2889684073', + createdBy: 'elastic', + updatedBy: '2889684073', + muteAll: false, + mutedInstanceIds: [], + schedule: { + interval: '1s', + }, + snoozeSchedule: [], + actions: [ + { + actionTypeId: '.server-log', + params: { + message: 'alert 37: {{context.message}}', + }, + group: 'threshold met', + id: '3619a0d0-582b-11ec-8995-2b1578a3bc5d', + uuid: '123-456', + }, + ], + systemActions: [ + { actionTypeId: '.test', id: 'system_action-id', params: {}, uuid: '789' }, + ], + params: { x: 42 }, + updatedAt: new Date('2024-03-21T13:15:00.498Z'), + createdAt: new Date('2024-03-21T13:15:00.498Z'), + scheduledTaskId: '52125fb0-5895-11ec-ae69-bb65d1a71b72', + executionStatus: { + status: 'ok' as const, + lastExecutionDate: new Date('2024-03-21T13:15:00.498Z'), + lastDuration: 1194, + }, + revision: 0, + artifacts: { + dashboards: [ + { + id: 'dashboard-1', + }, + ], + }, + }, + ], + }; + + rulesClient.find.mockResolvedValueOnce(findResult); + + const [context, req, res] = mockHandlerArguments( + { rulesClient }, + { + query: { + per_page: 1, + page: 1, + default_search_operator: 'OR', + }, + }, + ['ok'] + ); + + expect(await handler(context, req, res)).toMatchInlineSnapshot(` + Object { + "body": Object { + "data": Array [ + Object { + "actions": Array [ + Object { + "connector_type_id": ".server-log", + "group": "threshold met", + "id": "3619a0d0-582b-11ec-8995-2b1578a3bc5d", + "params": Object { + "message": "alert 37: {{context.message}}", + }, + "uuid": "123-456", + }, + Object { + "connector_type_id": ".test", + "id": "system_action-id", + "params": Object {}, + "uuid": "789", + }, + ], + "api_key_owner": "2889684073", + "consumer": "alerts", + "created_at": "2024-03-21T13:15:00.498Z", + "created_by": "elastic", + "enabled": true, + "execution_status": Object { + "last_duration": 1194, + "last_execution_date": "2024-03-21T13:15:00.498Z", + "status": "ok", + }, + "id": "3d534c70-582b-11ec-8995-2b1578a3bc5d", + "mute_all": false, + "muted_alert_ids": Array [], + "name": "stressing index-threshold 37/200", + "notify_when": "onActiveAlert", + "params": Object { + "x": 42, + }, + "revision": 0, + "rule_type_id": ".index-threshold", + "schedule": Object { + "interval": "1s", + }, + "scheduled_task_id": "52125fb0-5895-11ec-ae69-bb65d1a71b72", + "snooze_schedule": Array [], + "tags": Array [], + "throttle": null, + "updated_at": "2024-03-21T13:15:00.498Z", + "updated_by": "2889684073", + }, + ], + "page": 1, + "per_page": 1, + "total": 0, + }, + } + `); + + expect(rulesClient.find).toHaveBeenCalledTimes(1); + expect(rulesClient.find.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "excludeFromPublicApi": true, + "includeSnoozeData": true, + "options": Object { + "defaultSearchOperator": "OR", + "page": 1, + "perPage": 1, + }, + }, + ] + `); + + expect(res.ok).toHaveBeenCalledWith({ + body: { + page: 1, + per_page: 1, + total: 0, + data: [ + { + actions: [ + { + connector_type_id: '.server-log', + group: 'threshold met', + id: '3619a0d0-582b-11ec-8995-2b1578a3bc5d', + params: { + message: 'alert 37: {{context.message}}', + }, + uuid: '123-456', + }, + { + connector_type_id: '.test', + id: 'system_action-id', + params: {}, + uuid: '789', + }, + ], + api_key_owner: '2889684073', + consumer: 'alerts', + created_at: '2024-03-21T13:15:00.498Z', + created_by: 'elastic', + enabled: true, + execution_status: { + last_duration: 1194, + last_execution_date: '2024-03-21T13:15:00.498Z', + status: 'ok', + }, + id: '3d534c70-582b-11ec-8995-2b1578a3bc5d', + mute_all: false, + muted_alert_ids: [], + name: 'stressing index-threshold 37/200', + notify_when: 'onActiveAlert', + params: { + x: 42, + }, + revision: 0, + rule_type_id: '.index-threshold', + schedule: { + interval: '1s', + }, + scheduled_task_id: '52125fb0-5895-11ec-ae69-bb65d1a71b72', + snooze_schedule: [], + tags: [], + throttle: null, + updated_at: '2024-03-21T13:15:00.498Z', + updated_by: '2889684073', + }, + ], + }, + }); + }); }); diff --git a/x-pack/platform/plugins/shared/alerting/server/routes/rule/apis/find/find_rules_route.ts b/x-pack/platform/plugins/shared/alerting/server/routes/rule/apis/find/find_rules_route.ts index 29a4a2da35698..574aa1029619b 100644 --- a/x-pack/platform/plugins/shared/alerting/server/routes/rule/apis/find/find_rules_route.ts +++ b/x-pack/platform/plugins/shared/alerting/server/routes/rule/apis/find/find_rules_route.ts @@ -90,7 +90,7 @@ export const findRulesRoute = ( }); const responseBody: FindRulesResponseV1['body'] = - transformFindRulesResponseV1(findResult, options.fields); + transformFindRulesResponseV1(findResult, options.fields, false); return res.ok({ body: responseBody, diff --git a/x-pack/platform/plugins/shared/alerting/server/routes/rule/apis/find/transforms/transform_find_rules_response/v1.ts b/x-pack/platform/plugins/shared/alerting/server/routes/rule/apis/find/transforms/transform_find_rules_response/v1.ts index 80729ae13b6c0..8eb7738ff7dfb 100644 --- a/x-pack/platform/plugins/shared/alerting/server/routes/rule/apis/find/transforms/transform_find_rules_response/v1.ts +++ b/x-pack/platform/plugins/shared/alerting/server/routes/rule/apis/find/transforms/transform_find_rules_response/v1.ts @@ -21,7 +21,8 @@ import { export const transformPartialRule = ( rule: Partial>, - fields?: string[] + fields?: string[], + includeArtifacts: boolean = false ): Partial> => { const ruleResponse = { ...(rule.id !== undefined ? { id: rule.id } : {}), @@ -80,6 +81,7 @@ export const transformPartialRule = ( : {}), ...(rule.alertDelay !== undefined ? { alert_delay: rule.alertDelay } : {}), ...(rule.flapping !== undefined ? { flapping: transformFlappingV1(rule.flapping) } : {}), + ...(includeArtifacts && rule.artifacts !== undefined ? { artifacts: rule.artifacts } : {}), }; type RuleKeys = keyof RuleResponseV1; @@ -100,14 +102,15 @@ export const transformPartialRule = ( export const transformFindRulesResponse = ( result: FindResult, - fields?: string[] + fields?: string[], + includeArtifacts: boolean = false ): FindRulesResponseV1['body'] => { return { page: result.page, per_page: result.perPage, total: result.total, data: result.data.map((rule) => - transformPartialRule(rule as Partial>, fields) + transformPartialRule(rule as Partial>, fields, includeArtifacts) ), }; }; diff --git a/x-pack/platform/plugins/shared/alerting/server/routes/rule/apis/update/transforms/transform_update_body/v1.ts b/x-pack/platform/plugins/shared/alerting/server/routes/rule/apis/update/transforms/transform_update_body/v1.ts index 48e7d71285be5..00b15ebf4db9a 100644 --- a/x-pack/platform/plugins/shared/alerting/server/routes/rule/apis/update/transforms/transform_update_body/v1.ts +++ b/x-pack/platform/plugins/shared/alerting/server/routes/rule/apis/update/transforms/transform_update_body/v1.ts @@ -104,5 +104,6 @@ export const transformUpdateBody = ({ ...(updateBody.flapping !== undefined ? { flapping: transformUpdateBodyFlapping(updateBody.flapping) } : {}), + ...(updateBody.artifacts ? { artifacts: updateBody.artifacts } : {}), }; }; diff --git a/x-pack/platform/plugins/shared/alerting/server/rules_client/common/index.ts b/x-pack/platform/plugins/shared/alerting/server/rules_client/common/index.ts index a695e0a8bd6e9..90db6e99902cb 100644 --- a/x-pack/platform/plugins/shared/alerting/server/rules_client/common/index.ts +++ b/x-pack/platform/plugins/shared/alerting/server/rules_client/common/index.ts @@ -26,7 +26,11 @@ export { apiKeyAsAlertAttributes, apiKeyAsRuleDomainProperties, } from './api_key_as_alert_attributes'; -export { injectReferencesIntoActions, injectReferencesIntoParams } from './inject_references'; +export { + injectReferencesIntoActions, + injectReferencesIntoParams, + injectReferencesIntoArtifacts, +} from './inject_references'; export { parseDate } from './parse_date'; export { includeFieldsRequiredForAuthentication } from './include_fields_required_for_authentication'; export { getAndValidateCommonBulkOptions } from './get_and_validate_common_bulk_options'; diff --git a/x-pack/platform/plugins/shared/alerting/server/rules_client/common/inject_references.test.ts b/x-pack/platform/plugins/shared/alerting/server/rules_client/common/inject_references.test.ts new file mode 100644 index 0000000000000..02ded7aae93a1 --- /dev/null +++ b/x-pack/platform/plugins/shared/alerting/server/rules_client/common/inject_references.test.ts @@ -0,0 +1,87 @@ +/* + * 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 { SavedObjectReference } from '@kbn/core/server'; +import { injectReferencesIntoArtifacts } from './inject_references'; + +describe('injectReferencesIntoArtifacts', () => { + it('returns default value if no artifacts are provided', () => { + expect(injectReferencesIntoArtifacts('test-id', undefined, [])).toEqual({ dashboards: [] }); + }); + + it('throws an error if references are not provided', () => { + const artifacts = { + dashboards: [ + { + refId: 'dashboard_1', + }, + { + refId: 'dashboard_2', + }, + ], + }; + expect(() => injectReferencesIntoArtifacts('test-id', artifacts)).toThrow( + 'Artifacts reference "dashboard_1" not found in rule id: test-id' + ); + }); + + it('throws an error if the dashboard reference is not found', () => { + const artifacts = { + dashboards: [ + { + refId: 'dashboard_1', + }, + ], + }; + const refs: SavedObjectReference[] = []; + expect(() => injectReferencesIntoArtifacts('test-id', artifacts, refs)).toThrow( + 'Artifacts reference "dashboard_1" not found in rule id: test-id' + ); + }); + + it('returns the artifacts with injected references', () => { + const artifacts = { + dashboards: [ + { + refId: 'dashboard_1', + }, + { + refId: 'dashboard_2', + }, + ], + investigation_guide: { + blob: 'test', + }, + }; + const refs: SavedObjectReference[] = [ + { + id: '123', + name: 'dashboard_1', + type: 'dashboard', + }, + { + id: '456', + name: 'dashboard_2', + type: 'dashboard', + }, + ]; + const result = injectReferencesIntoArtifacts('test-id', artifacts, refs); + expect(result).toEqual({ + dashboards: [ + { + id: '123', + }, + { + id: '456', + }, + ], + investigation_guide: { + blob: 'test', + }, + }); + }); +}); diff --git a/x-pack/platform/plugins/shared/alerting/server/rules_client/common/inject_references.ts b/x-pack/platform/plugins/shared/alerting/server/rules_client/common/inject_references.ts index 9e956c634ade9..4289734f6396f 100644 --- a/x-pack/platform/plugins/shared/alerting/server/rules_client/common/inject_references.ts +++ b/x-pack/platform/plugins/shared/alerting/server/rules_client/common/inject_references.ts @@ -10,6 +10,7 @@ import { omit } from 'lodash'; import type { SavedObjectReference, SavedObjectAttributes } from '@kbn/core/server'; import type { UntypedNormalizedRuleType } from '../../rule_type_registry'; import type { RawRule, RuleTypeParams } from '../../types'; +import type { RuleDomain } from '../../application/rule/types'; import { preconfiguredConnectorActionRefPrefix, extractedSavedObjectParamReferenceNamePrefix, @@ -77,3 +78,31 @@ export function injectReferencesIntoParams< ); } } + +export function injectReferencesIntoArtifacts( + ruleId: string, + artifacts?: RawRule['artifacts'], + references?: SavedObjectReference[] +): Required { + if (!artifacts) { + return { dashboards: [] }; + } + return { + ...artifacts, + dashboards: + artifacts.dashboards?.map((dashboard) => { + const reference = references?.find( + (ref) => ref.name === dashboard.refId && ref.type === 'dashboard' + ); + if (!reference) { + throw new Error( + `Artifacts reference "${dashboard.refId}" not found in rule id: ${ruleId}` + ); + } + return { + ...omit(dashboard, 'refId'), + id: reference.id, + }; + }) ?? [], + }; +} diff --git a/x-pack/platform/plugins/shared/alerting/server/rules_client/lib/denormalize_artifacts.test.ts b/x-pack/platform/plugins/shared/alerting/server/rules_client/lib/denormalize_artifacts.test.ts new file mode 100644 index 0000000000000..b128533fa1f5f --- /dev/null +++ b/x-pack/platform/plugins/shared/alerting/server/rules_client/lib/denormalize_artifacts.test.ts @@ -0,0 +1,54 @@ +/* + * 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 { denormalizeArtifacts } from './denormalize_artifacts'; + +describe('denormalizeArtifacts', () => { + it('returns empty artifacts and references if no artifacts are provided', () => { + const { artifacts, references } = denormalizeArtifacts(undefined); + expect(artifacts).toEqual({ + dashboards: [], + }); + expect(references).toEqual([]); + }); + + it('returns denormalized artifacts and references', () => { + const ruleArtifacts = { + dashboards: [ + { + id: '123', + }, + { + id: '456', + }, + ], + }; + const { artifacts, references } = denormalizeArtifacts(ruleArtifacts); + expect(artifacts).toEqual({ + dashboards: [ + { + refId: 'dashboard_0', + }, + { + refId: 'dashboard_1', + }, + ], + }); + expect(references).toEqual([ + { + id: '123', + name: 'dashboard_0', + type: 'dashboard', + }, + { + id: '456', + name: 'dashboard_1', + type: 'dashboard', + }, + ]); + }); +}); diff --git a/x-pack/platform/plugins/shared/alerting/server/rules_client/lib/denormalize_artifacts.ts b/x-pack/platform/plugins/shared/alerting/server/rules_client/lib/denormalize_artifacts.ts new file mode 100644 index 0000000000000..861a822477780 --- /dev/null +++ b/x-pack/platform/plugins/shared/alerting/server/rules_client/lib/denormalize_artifacts.ts @@ -0,0 +1,40 @@ +/* + * 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 { SavedObjectReference } from '@kbn/core/server'; +import type { Artifacts } from '../../types'; +import type { DenormalizedArtifacts } from '../types'; + +export function denormalizeArtifacts(ruleArtifacts: Artifacts | undefined): { + artifacts: Required; + references: SavedObjectReference[]; +} { + const references: SavedObjectReference[] = []; + const artifacts: Required = { + dashboards: [], + }; + + if (ruleArtifacts && ruleArtifacts.dashboards) { + ruleArtifacts.dashboards.forEach((dashboard, i) => { + const refName = `dashboard_${i}`; + const dashboardRef = { + id: dashboard.id, + name: refName, + type: 'dashboard', + }; + references.push(dashboardRef); + + artifacts.dashboards.push({ + refId: refName, + }); + }); + } + + return { + artifacts, + references, + }; +} diff --git a/x-pack/platform/plugins/shared/alerting/server/rules_client/lib/extract_references.test.ts b/x-pack/platform/plugins/shared/alerting/server/rules_client/lib/extract_references.test.ts new file mode 100644 index 0000000000000..a923f91291927 --- /dev/null +++ b/x-pack/platform/plugins/shared/alerting/server/rules_client/lib/extract_references.test.ts @@ -0,0 +1,87 @@ +/* + * 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 { RecoveredActionGroup } from '../../../common'; +import type { UntypedNormalizedRuleType } from '../../rule_type_registry'; +import { extractReferences } from './extract_references'; +import type { RulesClientContext } from '..'; +import { savedObjectsRepositoryMock } from '@kbn/core-saved-objects-api-server-mocks'; + +const loggerErrorMock = jest.fn(); +const getBulkMock = jest.fn(); + +const ruleType: jest.Mocked = { + id: 'test.rule-type', + name: 'My test rule', + actionGroups: [{ id: 'default', name: 'Default' }, RecoveredActionGroup], + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + isExportable: true, + recoveryActionGroup: RecoveredActionGroup, + executor: jest.fn(), + producer: 'alerts', + solution: 'stack', + cancelAlertsOnRuleTimeout: true, + ruleTaskTimeout: '5m', + autoRecoverAlerts: true, + doesSetRecoveryContext: true, + validate: { + params: { validate: (params) => params }, + }, + alerts: { + context: 'test', + mappings: { fieldMap: { field: { type: 'keyword', required: false } } }, + shouldWrite: true, + }, + category: 'test', + validLegacyConsumers: [], +}; + +const context = { + logger: { error: loggerErrorMock }, + getActionsClient: () => { + return { + getBulk: getBulkMock, + }; + }, + unsecuredSavedObjectsClient: savedObjectsRepositoryMock.create(), + authorization: { ensureAuthorized: async () => {} }, + ruleTypeRegistry: { + ensureRuleTypeEnabled: () => {}, + }, + getUserName: async () => {}, +} as unknown as RulesClientContext; + +describe('extractReferences', () => { + it('returns dashboard artifacts and references', async () => { + const result = await extractReferences( + context, + ruleType, + [], + {}, + { + dashboards: [{ id: '123' }], + } + ); + + expect(result.artifacts).toEqual({ + dashboards: [ + { + refId: 'dashboard_0', + }, + ], + }); + + expect(result.references).toEqual([ + { + id: '123', + name: 'dashboard_0', + type: 'dashboard', + }, + ]); + }); +}); diff --git a/x-pack/platform/plugins/shared/alerting/server/rules_client/lib/extract_references.ts b/x-pack/platform/plugins/shared/alerting/server/rules_client/lib/extract_references.ts index 710719ea45298..ae4322d912e08 100644 --- a/x-pack/platform/plugins/shared/alerting/server/rules_client/lib/extract_references.ts +++ b/x-pack/platform/plugins/shared/alerting/server/rules_client/lib/extract_references.ts @@ -6,12 +6,13 @@ */ import type { SavedObjectReference } from '@kbn/core/server'; -import type { RuleTypeParams } from '../../types'; +import type { RuleTypeParams, Artifacts } from '../../types'; import type { UntypedNormalizedRuleType } from '../../rule_type_registry'; import type { DenormalizedAction, NormalizedAlertActionWithGeneratedValues } from '../types'; import { extractedSavedObjectParamReferenceNamePrefix } from '../common/constants'; -import type { RulesClientContext } from '../types'; +import type { RulesClientContext, DenormalizedArtifacts } from '../types'; import { denormalizeActions } from './denormalize_actions'; +import { denormalizeArtifacts } from './denormalize_artifacts'; export async function extractReferences< Params extends RuleTypeParams, @@ -20,9 +21,11 @@ export async function extractReferences< context: RulesClientContext, ruleType: UntypedNormalizedRuleType, ruleActions: NormalizedAlertActionWithGeneratedValues[], - ruleParams: Params + ruleParams: Params, + ruleArtifacts: Artifacts ): Promise<{ actions: DenormalizedAction[]; + artifacts: Required; params: ExtractedParams; references: SavedObjectReference[]; }> { @@ -32,6 +35,8 @@ export async function extractReferences< ruleActions ); + const { artifacts, references: artifactReferences } = denormalizeArtifacts(ruleArtifacts); + // Extracts any references using configured reference extractor if available const extractedRefsAndParams = ruleType?.useSavedObjectReferences?.extractReferences ? ruleType.useSavedObjectReferences.extractReferences(ruleParams) @@ -45,10 +50,11 @@ export async function extractReferences< name: `${extractedSavedObjectParamReferenceNamePrefix}${reference.name}`, })); - const references = [...actionReferences, ...paramReferences]; + const references = [...actionReferences, ...paramReferences, ...artifactReferences]; return { actions, + artifacts, params, references, }; diff --git a/x-pack/platform/plugins/shared/alerting/server/rules_client/lib/get_alert_from_raw.ts b/x-pack/platform/plugins/shared/alerting/server/rules_client/lib/get_alert_from_raw.ts index 3100f922dbe8c..d09e57c7fc627 100644 --- a/x-pack/platform/plugins/shared/alerting/server/rules_client/lib/get_alert_from_raw.ts +++ b/x-pack/platform/plugins/shared/alerting/server/rules_client/lib/get_alert_from_raw.ts @@ -30,6 +30,7 @@ import { transformRawActionsToDomainSystemActions, } from '../../application/rule/transforms/transform_raw_actions_to_domain_actions'; import { fieldsToExcludeFromPublicApi } from '../rules_client'; +import { transformRawArtifactsToDomainArtifacts } from '../../application/rule/transforms/transform_raw_artifacts_to_domain_artifacts'; export interface GetAlertFromRawParams { id: string; @@ -123,6 +124,7 @@ function getPartialRuleFromRaw( snoozeSchedule, lastRun, isSnoozedUntil: DoNotUseIsSnoozedUntil, + artifacts, ...partialRawRule } = rawRule; @@ -169,6 +171,7 @@ function getPartialRuleFromRaw( omitGeneratedValues, }) : [], + artifacts: transformRawArtifactsToDomainArtifacts(opts.id, artifacts, opts.references), params: injectReferencesIntoParams( opts.id, opts.ruleType, diff --git a/x-pack/platform/plugins/shared/alerting/server/rules_client/tests/resolve.test.ts b/x-pack/platform/plugins/shared/alerting/server/rules_client/tests/resolve.test.ts index df21bae6da71b..1e55ceb2a4583 100644 --- a/x-pack/platform/plugins/shared/alerting/server/rules_client/tests/resolve.test.ts +++ b/x-pack/platform/plugins/shared/alerting/server/rules_client/tests/resolve.test.ts @@ -138,6 +138,9 @@ describe('resolve()', () => { ], "alertTypeId": "123", "alias_target_id": "2", + "artifacts": Object { + "dashboards": Array [], + }, "createdAt": 2019-02-12T21:01:22.479Z, "executionStatus": Object { "lastExecutionDate": 2019-02-12T21:01:22.479Z, @@ -322,6 +325,9 @@ describe('resolve()', () => { ], "alertTypeId": "123", "alias_target_id": "2", + "artifacts": Object { + "dashboards": Array [], + }, "createdAt": 2019-02-12T21:01:22.479Z, "executionStatus": Object { "lastExecutionDate": 2019-02-12T21:01:22.479Z, @@ -517,6 +523,9 @@ describe('resolve()', () => { ], "alertTypeId": "123", "alias_target_id": "2", + "artifacts": Object { + "dashboards": Array [], + }, "createdAt": 2019-02-12T21:01:22.479Z, "executionStatus": Object { "lastExecutionDate": 2019-02-12T21:01:22.479Z, diff --git a/x-pack/platform/plugins/shared/alerting/server/rules_client/tests/test_helpers.ts b/x-pack/platform/plugins/shared/alerting/server/rules_client/tests/test_helpers.ts index 8177f919f80ff..33e091f351aaa 100644 --- a/x-pack/platform/plugins/shared/alerting/server/rules_client/tests/test_helpers.ts +++ b/x-pack/platform/plugins/shared/alerting/server/rules_client/tests/test_helpers.ts @@ -455,6 +455,9 @@ export const returnedRule2 = { export const returnedRuleForBulkOps1 = { actions: [], alertTypeId: 'fakeType', + artifacts: { + dashboards: [], + }, consumer: 'fakeConsumer', enabled: true, id: 'id1', @@ -481,6 +484,9 @@ export const returnedRuleForBulkOps1 = { export const returnedRuleForBulkOps2 = { actions: [], alertTypeId: 'fakeType', + artifacts: { + dashboards: [], + }, consumer: 'fakeConsumer', enabled: true, id: 'id2', @@ -508,6 +514,9 @@ export const returnedRuleForBulkOps3 = { actions: [], alertTypeId: 'fakeType', apiKeyCreatedByUser: true, + artifacts: { + dashboards: [], + }, consumer: 'fakeConsumer', enabled: true, id: 'id3', diff --git a/x-pack/platform/plugins/shared/alerting/server/rules_client/types.ts b/x-pack/platform/plugins/shared/alerting/server/rules_client/types.ts index 43d80ca674130..ee9ab1d45f97b 100644 --- a/x-pack/platform/plugins/shared/alerting/server/rules_client/types.ts +++ b/x-pack/platform/plugins/shared/alerting/server/rules_client/types.ts @@ -185,3 +185,11 @@ export type DenormalizedAction = DistributiveOmit< actionRef: string; actionTypeId: string; }; + +interface DashboardItem { + refId: string; +} + +export interface DenormalizedArtifacts { + dashboards?: DashboardItem[]; +} diff --git a/x-pack/platform/plugins/shared/alerting/server/saved_objects/model_versions/rule_model_versions.ts b/x-pack/platform/plugins/shared/alerting/server/saved_objects/model_versions/rule_model_versions.ts index 1d1ce3f6ad828..bd1673b06dfea 100644 --- a/x-pack/platform/plugins/shared/alerting/server/saved_objects/model_versions/rule_model_versions.ts +++ b/x-pack/platform/plugins/shared/alerting/server/saved_objects/model_versions/rule_model_versions.ts @@ -11,6 +11,7 @@ import { rawRuleSchemaV2, rawRuleSchemaV3, rawRuleSchemaV4, + rawRuleSchemaV5, } from '../schemas/raw_rule'; export const ruleModelVersions: SavedObjectsModelVersionMap = { @@ -42,4 +43,11 @@ export const ruleModelVersions: SavedObjectsModelVersionMap = { create: rawRuleSchemaV4, }, }, + '5': { + changes: [], + schemas: { + forwardCompatibility: rawRuleSchemaV5.extends({}, { unknowns: 'ignore' }), + create: rawRuleSchemaV5, + }, + }, }; diff --git a/x-pack/platform/plugins/shared/alerting/server/saved_objects/schemas/raw_rule/index.ts b/x-pack/platform/plugins/shared/alerting/server/saved_objects/schemas/raw_rule/index.ts index 684e813f7f868..10c7d125b0c59 100644 --- a/x-pack/platform/plugins/shared/alerting/server/saved_objects/schemas/raw_rule/index.ts +++ b/x-pack/platform/plugins/shared/alerting/server/saved_objects/schemas/raw_rule/index.ts @@ -11,3 +11,4 @@ export { rawRuleSchema as rawRuleSchemaV1 } from './v1'; export { rawRuleSchema as rawRuleSchemaV2 } from './v2'; export { rawRuleSchema as rawRuleSchemaV3 } from './v3'; export { rawRuleSchema as rawRuleSchemaV4 } from './v4'; +export { rawRuleSchema as rawRuleSchemaV5 } from './v5'; diff --git a/x-pack/platform/plugins/shared/alerting/server/saved_objects/schemas/raw_rule/latest.ts b/x-pack/platform/plugins/shared/alerting/server/saved_objects/schemas/raw_rule/latest.ts index 35d60b9c39ffb..479245944f3b7 100644 --- a/x-pack/platform/plugins/shared/alerting/server/saved_objects/schemas/raw_rule/latest.ts +++ b/x-pack/platform/plugins/shared/alerting/server/saved_objects/schemas/raw_rule/latest.ts @@ -13,7 +13,8 @@ import type { rawRuleLastRunSchema, } from './v3'; -import type { rawRuleMonitoringSchema, rawRuleSchema } from './v4'; +import type { rawRuleMonitoringSchema } from './v4'; +import type { rawRuleSchema } from './v5'; type Mutable = { -readonly [P in keyof T]: T[P] extends object ? Mutable : T[P] }; diff --git a/x-pack/platform/plugins/shared/alerting/server/saved_objects/schemas/raw_rule/v5.ts b/x-pack/platform/plugins/shared/alerting/server/saved_objects/schemas/raw_rule/v5.ts new file mode 100644 index 0000000000000..f050f100db02d --- /dev/null +++ b/x-pack/platform/plugins/shared/alerting/server/saved_objects/schemas/raw_rule/v5.ts @@ -0,0 +1,23 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { rawRuleSchema as rawRuleSchemaV4 } from './v4'; + +export const rawRuleDashboardsSchema = schema.arrayOf( + schema.object({ + refId: schema.string(), + }) +); + +export const artifactsSchema = schema.object({ + dashboards: schema.maybe(rawRuleDashboardsSchema), +}); + +export const rawRuleSchema = rawRuleSchemaV4.extends({ + artifacts: schema.maybe(artifactsSchema), +}); diff --git a/x-pack/platform/plugins/shared/alerting/server/task_runner/fixtures.ts b/x-pack/platform/plugins/shared/alerting/server/task_runner/fixtures.ts index 65f3c769e7d14..a6a0cbdf2a27a 100644 --- a/x-pack/platform/plugins/shared/alerting/server/task_runner/fixtures.ts +++ b/x-pack/platform/plugins/shared/alerting/server/task_runner/fixtures.ts @@ -298,6 +298,13 @@ export const mockedRule: SanitizedRule } as SanitizedRuleAction; }), isSnoozedUntil: undefined, + artifacts: { + dashboards: [ + { + id: 'dashboard-1', + }, + ], + }, }; export const mockTaskInstance = () => ({ diff --git a/x-pack/platform/plugins/shared/alerting/server/types.ts b/x-pack/platform/plugins/shared/alerting/server/types.ts index e761daa4ac80a..16743724369bd 100644 --- a/x-pack/platform/plugins/shared/alerting/server/types.ts +++ b/x-pack/platform/plugins/shared/alerting/server/types.ts @@ -51,6 +51,7 @@ import type { SanitizedRuleConfig, SanitizedRule, RuleAlertData, + Artifacts, } from '../common'; import type { PublicAlertFactory } from './alert/create_alert_factory'; import type { RulesSettingsFlappingProperties } from '../common/rules_settings'; @@ -59,6 +60,7 @@ import type { GetTimeRangeResult } from './lib/get_time_range'; export type WithoutQueryAndParams = Pick>; export type SpaceIdToNamespaceFunction = (spaceId?: string) => string | undefined; export type { RuleTypeParams }; +export type { Artifacts }; /** * @public */ diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/find_internal.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/find_internal.ts index 484a34b41dd72..363a942a23de7 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/find_internal.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/find_internal.ts @@ -78,7 +78,7 @@ export default function createFindTests({ getService }: FtrProviderContext) { const match = response.body.data.find((obj: any) => obj.id === createdAlert.id); const activeSnoozes = match.active_snoozes; const hasActiveSnoozes = !!(activeSnoozes || []).filter((obj: any) => obj).length; - expect(match).to.eql({ + const expected = { id: createdAlert.id, name: 'abc', tags: ['foo'], @@ -87,6 +87,9 @@ export default function createFindTests({ getService }: FtrProviderContext) { consumer: 'alertsFixture', schedule: { interval: '1m' }, enabled: true, + artifacts: { + dashboards: [], + }, actions: [], params: {}, created_by: 'elastic', @@ -108,7 +111,9 @@ export default function createFindTests({ getService }: FtrProviderContext) { snooze_schedule: match.snooze_schedule, ...(hasActiveSnoozes && { active_snoozes: activeSnoozes }), is_snoozed_until: null, - }); + }; + + expect(match).to.eql(expected); expect(Date.parse(match.created_at)).to.be.greaterThan(0); expect(Date.parse(match.updated_at)).to.be.greaterThan(0); break; @@ -279,6 +284,9 @@ export default function createFindTests({ getService }: FtrProviderContext) { params: {}, created_by: 'elastic', api_key_created_by_user: null, + artifacts: { + dashboards: [], + }, revision: 0, throttle: '1m', updated_by: 'elastic', @@ -370,6 +378,7 @@ export default function createFindTests({ getService }: FtrProviderContext) { expect(omit(matchFirst, 'updatedAt')).to.eql({ id: createdAlert.id, actions: [], + artifacts: { dashboards: [] }, tags: [myTag], snooze_schedule: [], is_snoozed_until: null, @@ -377,6 +386,7 @@ export default function createFindTests({ getService }: FtrProviderContext) { expect(omit(matchSecond, 'updatedAt')).to.eql({ id: createdSecondAlert.id, actions: [], + artifacts: { dashboards: [] }, tags: [myTag], snooze_schedule: [], is_snoozed_until: null, @@ -453,6 +463,7 @@ export default function createFindTests({ getService }: FtrProviderContext) { expect(omit(matchFirst, 'updatedAt')).to.eql({ id: createdAlert.id, actions: [], + artifacts: { dashboards: [] }, tags: [myTag], execution_status: matchFirst.execution_status, snooze_schedule: [], @@ -461,6 +472,7 @@ export default function createFindTests({ getService }: FtrProviderContext) { expect(omit(matchSecond, 'updatedAt')).to.eql({ id: createdSecondAlert.id, actions: [], + artifacts: { dashboards: [] }, tags: [myTag], execution_status: matchSecond.execution_status, snooze_schedule: [], diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/create.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/create.ts index 484880c34737b..8f59d122e67e8 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/create.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/create.ts @@ -753,5 +753,99 @@ export default function createAlertTests({ getService }: FtrProviderContext) { }); }); }); + + describe('artifacts', () => { + describe('create rule with dashboards artifacts correctly', () => { + it('should not return dashboards artifacts in the rule response', async () => { + const response = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send( + getTestRuleData({ + artifacts: { + dashboards: [{ id: 'dashboard-1' }, { id: 'dashboard-2' }], + }, + }) + ); + expect(response.status).to.eql(200); + objectRemover.add(Spaces.space1.id, response.body.id, 'rule', 'alerting'); + + expect(response.body.artifacts).to.be(undefined); + }); + + it('should store references correctly for dashboard artifacts', async () => { + const dashboardId = 'dashboard-1'; + const response = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send( + getTestRuleData({ + artifacts: { + dashboards: [ + { + id: dashboardId, + }, + ], + }, + }) + ); + expect(response.status).to.eql(200); + + objectRemover.add(Spaces.space1.id, response.body.id, 'rule', 'alerting'); + + expect(response.body).to.eql({ + id: response.body.id, + name: 'abc', + tags: ['foo'], + actions: [], + enabled: true, + rule_type_id: 'test.noop', + revision: 0, + running: false, + consumer: 'alertsFixture', + params: {}, + created_by: null, + schedule: { interval: '1m' }, + scheduled_task_id: response.body.scheduled_task_id, + updated_by: null, + api_key_owner: null, + api_key_created_by_user: null, + throttle: '1m', + notify_when: 'onThrottleInterval', + mute_all: false, + muted_alert_ids: [], + created_at: response.body.created_at, + updated_at: response.body.updated_at, + execution_status: response.body.execution_status, + ...(response.body.next_run ? { next_run: response.body.next_run } : {}), + ...(response.body.last_run ? { last_run: response.body.last_run } : {}), + }); + + const esResponse = await es.get>( + { + index: ALERTING_CASES_SAVED_OBJECT_INDEX, + id: `alert:${response.body.id}`, + }, + { meta: true } + ); + + const rawDashboards = (esResponse.body._source as any)?.alert.artifacts.dashboards ?? []; + expect(rawDashboards).to.eql([ + { + refId: 'dashboard_0', + }, + ]); + + const references = esResponse.body._source?.references ?? []; + + expect(references.length).to.eql(1); + expect(references[0]).to.eql({ + id: dashboardId, + name: 'dashboard_0', + type: 'dashboard', + }); + }); + }); + }); }); } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/find.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/find.ts index 5eb3d75973945..8cd08304f0d70 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/find.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/find.ts @@ -336,5 +336,35 @@ export default function createFindTests({ getService }: FtrProviderContext) { }); }); }); + + describe('artifacts', () => { + it('does not return artifacts when present', async () => { + const expectedArtifacts = { + artifacts: { + dashboards: [{ id: 'dashboard-1' }], + }, + }; + + const { body: createdAlert } = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestRuleData(expectedArtifacts)) + .expect(200); + + objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); + + const { id } = createdAlert; + + const response = await supertest.get( + `${getUrlPrefix(Spaces.space1.id)}/api/alerting/rules/_find` + ); + + expect(response.status).to.eql(200); + + const foundAlert = response.body.data.find((obj: any) => obj.id === id); + expect(foundAlert).not.to.be(undefined); + expect(foundAlert.artifacts).to.be(undefined); + }); + }); }); } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/find_internal.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/find_internal.ts index d0608a95651d5..652b5b61d0f60 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/find_internal.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/find_internal.ts @@ -119,6 +119,9 @@ export default function createFindTests({ getService }: FtrProviderContext) { params: {}, created_by: null, api_key_owner: null, + artifacts: { + dashboards: [], + }, api_key_created_by_user: null, scheduled_task_id: match.scheduled_task_id, updated_by: null, diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/update.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/update.ts index 596e49bed0bcd..962b161c23888 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/update.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/update.ts @@ -7,6 +7,9 @@ import expect from '@kbn/expect'; import { RULE_SAVED_OBJECT_TYPE } from '@kbn/alerting-plugin/server'; +import { ALERTING_CASES_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; +import type { SavedObject } from '@kbn/core/server'; +import type { RawRule } from '@kbn/alerting-plugin/server/types'; import { Spaces } from '../../../scenarios'; import { checkAAD, @@ -20,6 +23,7 @@ import type { FtrProviderContext } from '../../../../common/ftr_provider_context // eslint-disable-next-line import/no-default-export export default function createUpdateTests({ getService }: FtrProviderContext) { const supertest = getService('supertest'); + const es = getService('es'); describe('update', () => { const objectRemover = new ObjectRemover(supertest); @@ -453,5 +457,76 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { .expect(400); }); }); + + describe('artifacts', () => { + it('should not return dashboards in the response', async () => { + const expectedArtifacts = { + artifacts: { + dashboards: [ + { + id: 'dashboard-1', + }, + ], + }, + }; + + const createResponse = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestRuleData(expectedArtifacts)) + .expect(200); + + const esResponse = await es.get>( + { + index: ALERTING_CASES_SAVED_OBJECT_INDEX, + id: `alert:${createResponse.body.id}`, + }, + { meta: true } + ); + + expect((esResponse.body._source as any)?.alert.artifacts.dashboards ?? []).to.eql([ + { + refId: 'dashboard_0', + }, + ]); + + const updateResponse = await supertest + .put(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule/${createResponse.body.id}`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'bcd', + tags: ['foo'], + params: { + foo: true, + }, + schedule: { interval: '12s' }, + actions: [], + throttle: '1m', + notify_when: 'onThrottleInterval', + artifacts: { + dashboards: [{ id: 'dashboard-1' }, { id: 'dashboard-2' }], + }, + }) + .expect(200); + + expect(updateResponse.body.artifacts).to.be(undefined); + + const esUpdateResponse = await es.get>( + { + index: ALERTING_CASES_SAVED_OBJECT_INDEX, + id: `alert:${updateResponse.body.id}`, + }, + { meta: true } + ); + expect((esUpdateResponse.body._source as any)?.alert.artifacts.dashboards ?? {}).to.eql([ + { + refId: 'dashboard_0', + }, + { + refId: 'dashboard_1', + }, + ]); + }); + }); }); } diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/synthetics/enable_default_alerting.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/synthetics/enable_default_alerting.ts index 231195be88e44..419006911da9e 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/synthetics/enable_default_alerting.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/synthetics/enable_default_alerting.ts @@ -274,6 +274,9 @@ const defaultAlertRules = { throttle: null, apiKeyOwner: 'any', apiKeyCreatedByUser: true, + artifacts: { + dashboards: [], + }, createdBy: 'any', updatedBy: 'any', muteAll: false, @@ -306,6 +309,9 @@ const defaultAlertRules = { throttle: null, apiKeyOwner: 'elastic_admin', apiKeyCreatedByUser: true, + artifacts: { + dashboards: [], + }, createdBy: 'elastic_admin', updatedBy: 'elastic_admin', muteAll: false,