diff --git a/.buildkite/pipelines/esql_grammar_sync.yml b/.buildkite/pipelines/esql_grammar_sync.yml index 2bf14af24b20e..6337dc051ef11 100644 --- a/.buildkite/pipelines/esql_grammar_sync.yml +++ b/.buildkite/pipelines/esql_grammar_sync.yml @@ -8,9 +8,9 @@ steps: provider: gcp machineType: n2-standard-2 preemptible: true - - command: .buildkite/scripts/steps/esql_generate_function_definitions.sh - label: Generate Function Definitions - timeout_in_minutes: 10 + - command: .buildkite/scripts/steps/esql_generate_function_metadata.sh + label: Generate Function Metadata + timeout_in_minutes: 15 agents: image: family/kibana-ubuntu-2004 imageProject: elastic-images-prod diff --git a/.buildkite/scripts/steps/esql_generate_function_definitions.sh b/.buildkite/scripts/steps/esql_generate_function_metadata.sh similarity index 66% rename from .buildkite/scripts/steps/esql_generate_function_definitions.sh rename to .buildkite/scripts/steps/esql_generate_function_metadata.sh index ecb5b996df4c2..30e0fef47c100 100755 --- a/.buildkite/scripts/steps/esql_generate_function_definitions.sh +++ b/.buildkite/scripts/steps/esql_generate_function_metadata.sh @@ -1,6 +1,10 @@ #!/usr/bin/env bash set -euo pipefail +VALIDATION_PACKAGE_DIR="packages/kbn-esql-validation-autocomplete" +EDITOR_PACKAGE_DIR="packages/kbn-text-based-editor" +GIT_SCOPE="$VALIDATION_PACKAGE_DIR/**/* $EDITOR_PACKAGE_DIR/**/*" + report_main_step () { echo "--- $1" } @@ -19,7 +23,7 @@ main () { .buildkite/scripts/bootstrap.sh - cd "$KIBANA_DIR/packages/kbn-esql-validation-autocomplete" + cd "$KIBANA_DIR/$VALIDATION_PACKAGE_DIR" report_main_step "Generate function definitions" @@ -29,9 +33,21 @@ main () { yarn make:tests + report_main_step "Generate inline function docs" + + cd "$KIBANA_DIR/$EDITOR_PACKAGE_DIR" + + yarn make:docs $PARENT_DIR/elasticsearch + + report_main_step "Run i18n check" + + cd "$KIBANA_DIR" + + node scripts/i18n_check.js --fix + # Check for differences set +e - git diff --exit-code --quiet . + git diff --exit-code --quiet $GIT_SCOPE if [ $? -eq 0 ]; then echo "No differences found. Our work is done here." exit @@ -44,8 +60,8 @@ main () { git config --global user.name "$KIBANA_MACHINE_USERNAME" git config --global user.email '42973632+kibanamachine@users.noreply.github.com' - PR_TITLE='[ES|QL] Update function definitions' - PR_BODY='This PR updates the function definitions based on the latest metadata from Elasticsearch.' + PR_TITLE='[ES|QL] Update function metadata' + PR_BODY='This PR updates the function definitions and inline docs based on the latest metadata from Elasticsearch.' # Check if a PR already exists pr_search_result=$(gh pr list --search "$PR_TITLE" --state open --author "$KIBANA_MACHINE_USERNAME" --limit 1 --json title -q ".[].title") @@ -58,12 +74,12 @@ main () { echo "No existing PR found. Committing changes." # Make a commit - BRANCH_NAME="esql_generate_function_definitions_$(date +%s)" + BRANCH_NAME="esql_generate_function_metadata_$(date +%s)" git checkout -b "$BRANCH_NAME" - git add ./**/* - git commit -m "Update function definitions" + git add $GIT_SCOPE + git commit -m "Update function metadata" report_main_step "Changes committed. Creating pull request." diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 93ec4be707181..adcf03b5c653c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1224,7 +1224,7 @@ x-pack/test/observability_ai_assistant_functional @elastic/obs-ai-assistant /WORKSPACE.bazel @elastic/kibana-operations /.buildkite/ @elastic/kibana-operations /.buildkite/scripts/steps/esql_grammar_sync.sh @elastic/kibana-esql -/.buildkite/scripts/steps/esql_generate_function_definitions.sh @elastic/kibana-esql +/.buildkite/scripts/steps/esql_generate_function_metadata.sh @elastic/kibana-esql /.buildkite/pipelines/esql_grammar_sync.yml @elastic/kibana-esql /kbn_pm/ @elastic/kibana-operations /x-pack/dev-tools @elastic/kibana-operations diff --git a/packages/kbn-alerting-types/alerting_framework_health_types.ts b/packages/kbn-alerting-types/alerting_framework_health_types.ts new file mode 100644 index 0000000000000..8bba85300fa2a --- /dev/null +++ b/packages/kbn-alerting-types/alerting_framework_health_types.ts @@ -0,0 +1,34 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export enum HealthStatus { + OK = 'ok', + Warning = 'warn', + Error = 'error', +} + +export interface AlertsHealth { + decryptionHealth: { + status: HealthStatus; + timestamp: string; + }; + executionHealth: { + status: HealthStatus; + timestamp: string; + }; + readHealth: { + status: HealthStatus; + timestamp: string; + }; +} + +export interface AlertingFrameworkHealth { + isSufficientlySecure: boolean; + hasPermanentEncryptionKey: boolean; + alertingFrameworkHealth: AlertsHealth; +} diff --git a/packages/kbn-alerting-types/index.ts b/packages/kbn-alerting-types/index.ts index 8d9ecf1422806..1b82df61427e1 100644 --- a/packages/kbn-alerting-types/index.ts +++ b/packages/kbn-alerting-types/index.ts @@ -13,4 +13,5 @@ export * from './alert_type'; export * from './rule_notify_when_type'; export * from './r_rule_types'; export * from './rule_types'; +export * from './alerting_framework_health_types'; export * from './action_variable'; diff --git a/packages/kbn-alerting-types/rule_types.ts b/packages/kbn-alerting-types/rule_types.ts index c6fc66788cb0d..c0cb500740cb8 100644 --- a/packages/kbn-alerting-types/rule_types.ts +++ b/packages/kbn-alerting-types/rule_types.ts @@ -6,12 +6,17 @@ * Side Public License, v 1. */ -import type { SavedObjectAttributes } from '@kbn/core/server'; +import type { + SavedObjectAttribute, + SavedObjectAttributes, + SavedObjectsResolveResponse, +} from '@kbn/core/server'; import type { Filter } from '@kbn/es-query'; import type { RuleNotifyWhenType, RRuleParams } from '.'; export type RuleTypeParams = Record; export type RuleActionParams = SavedObjectAttributes; +export type RuleActionParam = SavedObjectAttribute; export const ISO_WEEKDAYS = [1, 2, 3, 4, 5, 6, 7] as const; export type IsoWeekday = typeof ISO_WEEKDAYS[number]; @@ -239,3 +244,9 @@ export type SanitizedRule = Omit< Rule, 'apiKey' | 'actions' > & { actions: SanitizedRuleAction[] }; + +export type ResolvedSanitizedRule = SanitizedRule & + Omit & { + outcome: string; + alias_target_id?: string; + }; diff --git a/packages/kbn-alerts-ui-shared/index.ts b/packages/kbn-alerts-ui-shared/index.ts index eb3eae1496144..a93fbd476165b 100644 --- a/packages/kbn-alerts-ui-shared/index.ts +++ b/packages/kbn-alerts-ui-shared/index.ts @@ -18,4 +18,4 @@ export type { AlertsSearchBarProps } from './src/alerts_search_bar/types'; export * from './src/alert_fields_table'; export * from './src/alert_filter_controls/types'; -export * from './src/common/hooks'; +export * from './src/common/types'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/create.test.ts b/packages/kbn-alerts-ui-shared/src/common/apis/create_rule/create_rule.test.ts similarity index 89% rename from x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/create.test.ts rename to packages/kbn-alerts-ui-shared/src/common/apis/create_rule/create_rule.test.ts index cf32f04a2bc23..8735a9bebca73 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/create.test.ts +++ b/packages/kbn-alerts-ui-shared/src/common/apis/create_rule/create_rule.test.ts @@ -1,13 +1,15 @@ /* * 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. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import { httpServiceMock } from '@kbn/core/public/mocks'; -import { RuleUpdates } from '../../../types'; -import { createRule } from './create'; +import { RuleTypeParams } from '../../types'; +import { createRule } from './create_rule'; +import { CreateRuleBody } from './types'; const http = httpServiceMock.createStartContract(); @@ -62,10 +64,7 @@ describe('createRule', () => { }, }; - const ruleToCreate: Omit< - RuleUpdates, - 'createdBy' | 'updatedBy' | 'muteAll' | 'mutedInstanceIds' | 'executionStatus' - > = { + const ruleToCreate: CreateRuleBody = { params: { aggType: 'count', termSize: 5, @@ -106,17 +105,14 @@ describe('createRule', () => { actionTypeId: '.system-action', }, ], - createdAt: new Date('2021-04-01T21:33:13.247Z'), - updatedAt: new Date('2021-04-01T21:33:13.247Z'), - apiKeyOwner: '', - revision: 0, + notifyWhen: 'onActionGroupChange', alertDelay: { active: 10, }, }; http.post.mockResolvedValueOnce(resolvedValue); - const result = await createRule({ http, rule: ruleToCreate }); + const result = await createRule({ http, rule: ruleToCreate as CreateRuleBody }); expect(result).toEqual({ actions: [ { diff --git a/packages/kbn-alerts-ui-shared/src/common/apis/create_rule/create_rule.ts b/packages/kbn-alerts-ui-shared/src/common/apis/create_rule/create_rule.ts new file mode 100644 index 0000000000000..556d0beb74a1f --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/apis/create_rule/create_rule.ts @@ -0,0 +1,27 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { HttpSetup } from '@kbn/core/public'; +import type { AsApiContract } from '@kbn/actions-types'; +import type { Rule } from '../../types'; +import { CreateRuleBody, transformCreateRuleBody } from '.'; +import { BASE_ALERTING_API_PATH } from '../../constants'; +import { transformRule } from '../../transformations'; + +export async function createRule({ + http, + rule, +}: { + http: HttpSetup; + rule: CreateRuleBody; +}): Promise { + const res = await http.post>(`${BASE_ALERTING_API_PATH}/rule`, { + body: JSON.stringify(transformCreateRuleBody(rule)), + }); + return transformRule(res); +} diff --git a/packages/kbn-alerts-ui-shared/src/common/apis/create_rule/index.ts b/packages/kbn-alerts-ui-shared/src/common/apis/create_rule/index.ts new file mode 100644 index 0000000000000..8bf74c5def039 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/apis/create_rule/index.ts @@ -0,0 +1,11 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './types'; +export * from './create_rule'; +export * from './transform_create_rule_body'; diff --git a/packages/kbn-alerts-ui-shared/src/common/apis/create_rule/transform_create_rule_body.test.ts b/packages/kbn-alerts-ui-shared/src/common/apis/create_rule/transform_create_rule_body.test.ts new file mode 100644 index 0000000000000..8b41e38d14ec1 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/apis/create_rule/transform_create_rule_body.test.ts @@ -0,0 +1,103 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { transformCreateRuleBody } from './transform_create_rule_body'; +import type { RuleTypeParams } from '../../types'; +import type { CreateRuleBody } from './types'; + +const ruleToCreate: CreateRuleBody = { + params: { + aggType: 'count', + termSize: 5, + thresholdComparator: '>', + timeWindowSize: 5, + timeWindowUnit: 'm', + groupBy: 'all', + threshold: [1000], + index: ['.kibana'], + timeField: 'alert.executionStatus.lastExecutionDate', + }, + consumer: 'alerts', + schedule: { interval: '1m' }, + tags: [], + name: 'test', + enabled: true, + throttle: null, + ruleTypeId: '.index-threshold', + actions: [ + { + group: 'threshold met', + id: '83d4d860-9316-11eb-a145-93ab369a4461', + params: { + level: 'info', + message: 'test-message', + }, + actionTypeId: '.server-log', + frequency: { + notifyWhen: 'onActionGroupChange', + throttle: null, + summary: false, + }, + useAlertDataForTemplate: true, + }, + { + id: '.test-system-action', + params: {}, + actionTypeId: '.system-action', + }, + ], + notifyWhen: 'onActionGroupChange', + alertDelay: { + active: 10, + }, +}; + +describe('transformCreateRuleBody', () => { + test('should transform create rule body', () => { + expect(transformCreateRuleBody(ruleToCreate)).toEqual({ + params: { + aggType: 'count', + termSize: 5, + thresholdComparator: '>', + timeWindowSize: 5, + timeWindowUnit: 'm', + groupBy: 'all', + threshold: [1000], + index: ['.kibana'], + timeField: 'alert.executionStatus.lastExecutionDate', + }, + consumer: 'alerts', + schedule: { interval: '1m' }, + tags: [], + name: 'test', + enabled: true, + throttle: null, + notifyWhen: 'onActionGroupChange', + rule_type_id: '.index-threshold', + actions: [ + { + group: 'threshold met', + id: '83d4d860-9316-11eb-a145-93ab369a4461', + params: { + level: 'info', + message: 'test-message', + }, + frequency: { + notify_when: 'onActionGroupChange', + summary: false, + throttle: null, + }, + use_alert_data_for_template: true, + }, + { id: '.test-system-action', params: {} }, + ], + + alert_delay: { active: 10 }, + }); + }); +}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/create.ts b/packages/kbn-alerts-ui-shared/src/common/apis/create_rule/transform_create_rule_body.ts similarity index 55% rename from x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/create.ts rename to packages/kbn-alerts-ui-shared/src/common/apis/create_rule/transform_create_rule_body.ts index ad4768dc9fee3..261641d2c18a9 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/create.ts +++ b/packages/kbn-alerts-ui-shared/src/common/apis/create_rule/transform_create_rule_body.ts @@ -1,26 +1,15 @@ /* * 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. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ -import { HttpSetup } from '@kbn/core/public'; -import { AsApiContract, RewriteResponseCase } from '@kbn/actions-plugin/common'; -import { Rule, RuleUpdates } from '../../../types'; -import { BASE_ALERTING_API_PATH } from '../../constants'; -import { transformRule } from './common_transformations'; -type RuleCreateBody = Omit< - RuleUpdates, - | 'createdBy' - | 'updatedBy' - | 'muteAll' - | 'mutedInstanceIds' - | 'executionStatus' - | 'lastRun' - | 'nextRun' ->; -export const rewriteBodyRequest: RewriteResponseCase = ({ +import { RewriteResponseCase } from '@kbn/actions-types'; +import { CreateRuleBody } from './types'; + +export const transformCreateRuleBody: RewriteResponseCase = ({ ruleTypeId, actions, alertDelay, @@ -56,16 +45,3 @@ export const rewriteBodyRequest: RewriteResponseCase = ({ }), ...(alertDelay ? { alert_delay: alertDelay } : {}), }); - -export async function createRule({ - http, - rule, -}: { - http: HttpSetup; - rule: RuleCreateBody; -}): Promise { - const res = await http.post>(`${BASE_ALERTING_API_PATH}/rule`, { - body: JSON.stringify(rewriteBodyRequest(rule)), - }); - return transformRule(res); -} diff --git a/packages/kbn-alerts-ui-shared/src/common/apis/create_rule/types.ts b/packages/kbn-alerts-ui-shared/src/common/apis/create_rule/types.ts new file mode 100644 index 0000000000000..dfea6fb77de32 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/apis/create_rule/types.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { Rule, RuleTypeParams } from '../../types'; + +export interface CreateRuleBody { + name: Rule['name']; + ruleTypeId: Rule['ruleTypeId']; + enabled: Rule['enabled']; + consumer: Rule['consumer']; + tags: Rule['tags']; + throttle?: Rule['throttle']; + params: Rule['params']; + schedule: Rule['schedule']; + actions: Rule['actions']; + notifyWhen?: Rule['notifyWhen']; + alertDelay?: Rule['alertDelay']; +} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/health.test.ts b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_alerting_framework_health/fetch_alerting_framework_health.test.ts similarity index 74% rename from x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/health.test.ts rename to packages/kbn-alerts-ui-shared/src/common/apis/fetch_alerting_framework_health/fetch_alerting_framework_health.test.ts index 84a9b86fc55d3..b67e16e2d1a41 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/health.test.ts +++ b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_alerting_framework_health/fetch_alerting_framework_health.test.ts @@ -1,16 +1,17 @@ /* * 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. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import { httpServiceMock } from '@kbn/core/public/mocks'; -import { alertingFrameworkHealth } from './health'; +import { fetchAlertingFrameworkHealth } from '.'; -describe('alertingFrameworkHealth', () => { +describe('fetchAlertingFrameworkHealth', () => { const http = httpServiceMock.createStartContract(); - test('should call alertingFrameworkHealth API', async () => { + test('should call fetchAlertingFrameworkHealth API', async () => { http.get.mockResolvedValueOnce({ is_sufficiently_secure: true, has_permanent_encryption_key: true, @@ -20,7 +21,7 @@ describe('alertingFrameworkHealth', () => { read_health: { status: 'ok', timestamp: '2021-04-01T21:29:22.991Z' }, }, }); - const result = await alertingFrameworkHealth({ http }); + const result = await fetchAlertingFrameworkHealth({ http }); expect(result).toEqual({ alertingFrameworkHealth: { decryptionHealth: { status: 'ok', timestamp: '2021-04-01T21:29:22.991Z' }, diff --git a/packages/kbn-alerts-ui-shared/src/common/apis/fetch_alerting_framework_health/fetch_alerting_framework_health.ts b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_alerting_framework_health/fetch_alerting_framework_health.ts new file mode 100644 index 0000000000000..44d717730e2c9 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_alerting_framework_health/fetch_alerting_framework_health.ts @@ -0,0 +1,30 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { HttpSetup } from '@kbn/core/public'; +import { AsApiContract } from '@kbn/actions-types'; +import { AlertingFrameworkHealth, AlertsHealth } from '@kbn/alerting-types'; +import { BASE_ALERTING_API_PATH } from '../../constants'; +import { transformAlertingFrameworkHealthResponse, transformAlertsHealthResponse } from '.'; + +export async function fetchAlertingFrameworkHealth({ + http, +}: { + http: HttpSetup; +}): Promise { + const res = await http.get>( + `${BASE_ALERTING_API_PATH}/_health` + ); + const alertingFrameworkHealthRewrited = transformAlertsHealthResponse( + res.alerting_framework_health as unknown as AsApiContract + ); + return { + ...transformAlertingFrameworkHealthResponse(res), + alertingFrameworkHealth: alertingFrameworkHealthRewrited, + }; +} diff --git a/packages/kbn-alerts-ui-shared/src/common/apis/fetch_alerting_framework_health/index.ts b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_alerting_framework_health/index.ts new file mode 100644 index 0000000000000..c73e798034a51 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_alerting_framework_health/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './fetch_alerting_framework_health'; +export * from './transform_alerting_framework_health_response'; diff --git a/packages/kbn-alerts-ui-shared/src/common/apis/fetch_alerting_framework_health/transform_alerting_framework_health.test.ts b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_alerting_framework_health/transform_alerting_framework_health.test.ts new file mode 100644 index 0000000000000..3710b6cdcdf73 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_alerting_framework_health/transform_alerting_framework_health.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { HealthStatus } from '@kbn/alerting-types'; +import { + transformAlertsHealthResponse, + transformAlertingFrameworkHealthResponse, +} from './transform_alerting_framework_health_response'; + +describe('transformAlertingFrameworkHealth', () => { + test('should transform alerts health response', () => { + expect( + transformAlertsHealthResponse({ + decryption_health: { + status: HealthStatus.OK, + timestamp: new Date('01-01-2024').toISOString(), + }, + execution_health: { + status: HealthStatus.OK, + timestamp: new Date('01-02-2024').toISOString(), + }, + read_health: { + status: HealthStatus.OK, + timestamp: new Date('01-03-2024').toISOString(), + }, + }) + ).toEqual({ + decryptionHealth: { + status: 'ok', + timestamp: new Date('01-01-2024').toISOString(), + }, + executionHealth: { + status: 'ok', + timestamp: new Date('01-02-2024').toISOString(), + }, + readHealth: { + status: 'ok', + timestamp: new Date('01-03-2024').toISOString(), + }, + }); + }); + + test('should transform alerting framework health response', () => { + expect( + transformAlertingFrameworkHealthResponse({ + is_sufficiently_secure: true, + has_permanent_encryption_key: true, + alerting_framework_health: { + decryptionHealth: { + status: HealthStatus.OK, + timestamp: new Date('01-01-2024').toISOString(), + }, + executionHealth: { + status: HealthStatus.OK, + timestamp: new Date('01-02-2024').toISOString(), + }, + readHealth: { + status: HealthStatus.OK, + timestamp: new Date('01-03-2024').toISOString(), + }, + }, + }) + ).toEqual({ + alertingFrameworkHealth: { + decryptionHealth: { + status: 'ok', + timestamp: new Date('01-01-2024').toISOString(), + }, + executionHealth: { + status: 'ok', + timestamp: new Date('01-02-2024').toISOString(), + }, + readHealth: { + status: 'ok', + timestamp: new Date('01-03-2024').toISOString(), + }, + }, + hasPermanentEncryptionKey: true, + isSufficientlySecure: true, + }); + }); +}); diff --git a/packages/kbn-alerts-ui-shared/src/common/apis/fetch_alerting_framework_health/transform_alerting_framework_health_response.ts b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_alerting_framework_health/transform_alerting_framework_health_response.ts new file mode 100644 index 0000000000000..29e054b7e94d1 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_alerting_framework_health/transform_alerting_framework_health_response.ts @@ -0,0 +1,37 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { AsApiContract, RewriteRequestCase } from '@kbn/actions-types'; +import { AlertingFrameworkHealth, AlertsHealth } from '@kbn/alerting-types'; + +export const transformAlertsHealthResponse: RewriteRequestCase = ({ + decryption_health: decryptionHealth, + execution_health: executionHealth, + read_health: readHealth, + ...res +}: AsApiContract) => ({ + decryptionHealth, + executionHealth, + readHealth, + ...res, +}); + +export const transformAlertingFrameworkHealthResponse: RewriteRequestCase< + AlertingFrameworkHealth +> = ({ + is_sufficiently_secure: isSufficientlySecure, + has_permanent_encryption_key: hasPermanentEncryptionKey, + + alerting_framework_health: alertingFrameworkHealth, + ...res +}: AsApiContract) => ({ + isSufficientlySecure, + hasPermanentEncryptionKey, + alertingFrameworkHealth, + ...res, +}); diff --git a/x-pack/plugins/triggers_actions_ui/public/common/lib/config_api.test.ts b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_ui_config/fetch_ui_config.test.ts similarity index 57% rename from x-pack/plugins/triggers_actions_ui/public/common/lib/config_api.test.ts rename to packages/kbn-alerts-ui-shared/src/common/apis/fetch_ui_config/fetch_ui_config.test.ts index 9f117b51c9cf8..0fcdcab8ab296 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/lib/config_api.test.ts +++ b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_ui_config/fetch_ui_config.test.ts @@ -1,18 +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. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import { httpServiceMock } from '@kbn/core/public/mocks'; -import { triggersActionsUiConfig } from './config_api'; +import { fetchUiConfig } from '.'; -describe('triggersActionsUiConfig', () => { +describe('fetchUiConfig', () => { const http = httpServiceMock.createStartContract(); - test('should call triggersActionsUiConfig API', async () => { - const result = await triggersActionsUiConfig({ http }); + test('should call fetchUiConfig API', async () => { + const result = await fetchUiConfig({ http }); expect(result).toEqual(undefined); expect(http.get.mock.calls).toMatchInlineSnapshot(` Array [ diff --git a/packages/kbn-alerts-ui-shared/src/common/apis/fetch_ui_config/fetch_ui_config.ts b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_ui_config/fetch_ui_config.ts new file mode 100644 index 0000000000000..bcf724bb11226 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_ui_config/fetch_ui_config.ts @@ -0,0 +1,15 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { HttpStart } from '@kbn/core-http-browser'; +import { BASE_TRIGGERS_ACTIONS_UI_API_PATH } from '../../constants'; +import { UiConfig } from '.'; + +export const fetchUiConfig = async ({ http }: { http: HttpStart }): Promise => { + return http.get(`${BASE_TRIGGERS_ACTIONS_UI_API_PATH}/_config`); +}; diff --git a/packages/kbn-alerts-ui-shared/src/common/apis/fetch_ui_config/index.ts b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_ui_config/index.ts new file mode 100644 index 0000000000000..e0ad7744f20d4 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_ui_config/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './types'; +export * from './fetch_ui_config'; diff --git a/packages/kbn-alerts-ui-shared/src/common/apis/fetch_ui_config/types.ts b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_ui_config/types.ts new file mode 100644 index 0000000000000..8bc4245f713c5 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_ui_config/types.ts @@ -0,0 +1,15 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export interface UiConfig { + isUsingSecurity: boolean; + minimumScheduleInterval?: { + value: string; + enforce: boolean; + }; +} diff --git a/x-pack/plugins/triggers_actions_ui/public/common/lib/health_api.test.ts b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_ui_health_status/fetch_ui_health_status.test.ts similarity index 56% rename from x-pack/plugins/triggers_actions_ui/public/common/lib/health_api.test.ts rename to packages/kbn-alerts-ui-shared/src/common/apis/fetch_ui_health_status/fetch_ui_health_status.test.ts index b668d58f5b7da..84388fb63305e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/lib/health_api.test.ts +++ b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_ui_health_status/fetch_ui_health_status.test.ts @@ -1,18 +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. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import { httpServiceMock } from '@kbn/core/public/mocks'; -import { triggersActionsUiHealth } from './health_api'; +import { fetchUiHealthStatus } from '.'; -describe('triggersActionsUiHealth', () => { +describe('fetchUiHealthStatus', () => { const http = httpServiceMock.createStartContract(); - test('should call triggersActionsUiHealth API', async () => { - const result = await triggersActionsUiHealth({ http }); + test('should call fetchUiHealthStatus API', async () => { + const result = await fetchUiHealthStatus({ http }); expect(result).toEqual(undefined); expect(http.get.mock.calls).toMatchInlineSnapshot(` Array [ diff --git a/packages/kbn-alerts-ui-shared/src/common/apis/fetch_ui_health_status/fetch_ui_health_status.ts b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_ui_health_status/fetch_ui_health_status.ts new file mode 100644 index 0000000000000..28eb211ac056c --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_ui_health_status/fetch_ui_health_status.ts @@ -0,0 +1,27 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { HttpStart } from '@kbn/core-http-browser'; +import type { UiHealthCheck, UiHealthCheckResponse } from './types'; +import { BASE_TRIGGERS_ACTIONS_UI_API_PATH } from '../../constants'; + +export const fetchUiHealthStatus = async ({ + http, +}: { + http: HttpStart; +}): Promise => { + const result = await http.get( + `${BASE_TRIGGERS_ACTIONS_UI_API_PATH}/_health` + ); + if (result) { + return { + isRulesAvailable: result.isAlertsAvailable, + }; + } + return result; +}; diff --git a/packages/kbn-alerts-ui-shared/src/common/apis/fetch_ui_health_status/index.ts b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_ui_health_status/index.ts new file mode 100644 index 0000000000000..ec5fa3329dc53 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_ui_health_status/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './types'; +export * from './fetch_ui_health_status'; diff --git a/packages/kbn-alerts-ui-shared/src/common/types.ts b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_ui_health_status/types.ts similarity index 60% rename from packages/kbn-alerts-ui-shared/src/common/types.ts rename to packages/kbn-alerts-ui-shared/src/common/apis/fetch_ui_health_status/types.ts index d7808487772c4..bf0ee678d8949 100644 --- a/packages/kbn-alerts-ui-shared/src/common/types.ts +++ b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_ui_health_status/types.ts @@ -6,8 +6,10 @@ * Side Public License, v 1. */ -import { RuleType } from '@kbn/triggers-actions-ui-types'; +export interface UiHealthCheck { + isRulesAvailable: boolean; +} -export type RuleTypeWithDescription = RuleType & { description?: string }; - -export type RuleTypeIndexWithDescriptions = Map; +export interface UiHealthCheckResponse { + isAlertsAvailable: boolean; +} diff --git a/packages/kbn-alerts-ui-shared/src/common/apis/resolve_rule/index.ts b/packages/kbn-alerts-ui-shared/src/common/apis/resolve_rule/index.ts new file mode 100644 index 0000000000000..aef77ab4a00f1 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/apis/resolve_rule/index.ts @@ -0,0 +1,9 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './resolve_rule'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/resolve_rule.test.ts b/packages/kbn-alerts-ui-shared/src/common/apis/resolve_rule/resolve_rule.test.ts similarity index 93% rename from x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/resolve_rule.test.ts rename to packages/kbn-alerts-ui-shared/src/common/apis/resolve_rule/resolve_rule.test.ts index 0803f14f4a7f1..ffedc60aac0d6 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/resolve_rule.test.ts +++ b/packages/kbn-alerts-ui-shared/src/common/apis/resolve_rule/resolve_rule.test.ts @@ -1,8 +1,9 @@ /* * 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. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import { httpServiceMock } from '@kbn/core/public/mocks'; @@ -64,7 +65,7 @@ describe('resolveRule', () => { }; http.get.mockResolvedValueOnce(resolvedValue); - expect(await resolveRule({ http, ruleId })).toEqual({ + expect(await resolveRule({ http, id: ruleId })).toEqual({ id: '1/', params: { aggType: 'count', diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/resolve_rule.ts b/packages/kbn-alerts-ui-shared/src/common/apis/resolve_rule/resolve_rule.ts similarity index 53% rename from x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/resolve_rule.ts rename to packages/kbn-alerts-ui-shared/src/common/apis/resolve_rule/resolve_rule.ts index 1842ad2d8e01a..153b5d8cc763f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/resolve_rule.ts +++ b/packages/kbn-alerts-ui-shared/src/common/apis/resolve_rule/resolve_rule.ts @@ -1,24 +1,26 @@ /* * 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. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ -import { HttpSetup } from '@kbn/core/public'; -import { AsApiContract } from '@kbn/actions-plugin/common'; -import { ResolvedRule } from '../../../types'; + +import { HttpSetup } from '@kbn/core-http-browser'; +import { AsApiContract } from '@kbn/actions-types'; import { INTERNAL_BASE_ALERTING_API_PATH } from '../../constants'; -import { transformResolvedRule } from './common_transformations'; +import { transformResolvedRule } from '../../transformations'; +import { ResolvedRule } from '../../types'; export async function resolveRule({ http, - ruleId, + id, }: { http: HttpSetup; - ruleId: string; + id: string; }): Promise { const res = await http.get>( - `${INTERNAL_BASE_ALERTING_API_PATH}/rule/${encodeURIComponent(ruleId)}/_resolve` + `${INTERNAL_BASE_ALERTING_API_PATH}/rule/${encodeURIComponent(id)}/_resolve` ); return transformResolvedRule(res); } diff --git a/packages/kbn-alerts-ui-shared/src/common/apis/update_rule/index.ts b/packages/kbn-alerts-ui-shared/src/common/apis/update_rule/index.ts new file mode 100644 index 0000000000000..de7e98d72bd78 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/apis/update_rule/index.ts @@ -0,0 +1,11 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './types'; +export * from './transform_update_rule_body'; +export * from './update_rule'; diff --git a/packages/kbn-alerts-ui-shared/src/common/apis/update_rule/transform_update_rule_body.test.ts b/packages/kbn-alerts-ui-shared/src/common/apis/update_rule/transform_update_rule_body.test.ts new file mode 100644 index 0000000000000..c30807884a646 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/apis/update_rule/transform_update_rule_body.test.ts @@ -0,0 +1,103 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { RuleTypeParams } from '../../types'; +import { transformUpdateRuleBody } from './transform_update_rule_body'; +import { UpdateRuleBody } from './types'; + +const ruleToUpdate: UpdateRuleBody = { + params: { + aggType: 'count', + termSize: 5, + thresholdComparator: '>', + timeWindowSize: 5, + timeWindowUnit: 'm', + groupBy: 'all', + threshold: [1000], + index: ['.kibana'], + timeField: 'alert.executionStatus.lastExecutionDate', + }, + schedule: { interval: '1m' }, + tags: [], + name: 'test', + throttle: null, + actions: [ + { + group: 'threshold met', + id: '83d4d860-9316-11eb-a145-93ab369a4461', + params: { + level: 'info', + message: 'test-message', + }, + actionTypeId: '.server-log', + frequency: { + notifyWhen: 'onActionGroupChange', + throttle: null, + summary: false, + }, + useAlertDataForTemplate: true, + }, + { + id: '.test-system-action', + params: {}, + actionTypeId: '.system-action', + }, + ], + notifyWhen: 'onActionGroupChange', + alertDelay: { + active: 10, + }, +}; + +describe('transformUpdateRuleBody', () => { + test('should transform update rule body', () => { + expect(transformUpdateRuleBody(ruleToUpdate)).toEqual({ + actions: [ + { + frequency: { + notify_when: 'onActionGroupChange', + summary: false, + throttle: null, + }, + group: 'threshold met', + id: '83d4d860-9316-11eb-a145-93ab369a4461', + params: { + level: 'info', + message: 'test-message', + }, + use_alert_data_for_template: true, + }, + { + id: '.test-system-action', + params: {}, + }, + ], + alert_delay: { + active: 10, + }, + name: 'test', + notifyWhen: 'onActionGroupChange', + params: { + aggType: 'count', + groupBy: 'all', + index: ['.kibana'], + termSize: 5, + threshold: [1000], + thresholdComparator: '>', + timeField: 'alert.executionStatus.lastExecutionDate', + timeWindowSize: 5, + timeWindowUnit: 'm', + }, + schedule: { + interval: '1m', + }, + tags: [], + throttle: null, + }); + }); +}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/update.ts b/packages/kbn-alerts-ui-shared/src/common/apis/update_rule/transform_update_rule_body.ts similarity index 50% rename from x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/update.ts rename to packages/kbn-alerts-ui-shared/src/common/apis/update_rule/transform_update_rule_body.ts index 40b24be836dc6..f0493a1c164f2 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/update.ts +++ b/packages/kbn-alerts-ui-shared/src/common/apis/update_rule/transform_update_rule_body.ts @@ -1,30 +1,15 @@ /* * 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. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ -import { HttpSetup } from '@kbn/core/public'; -import { pick } from 'lodash'; -import { RewriteResponseCase, AsApiContract } from '@kbn/actions-plugin/common'; -import { BASE_ALERTING_API_PATH } from '../../constants'; -import { Rule, RuleUpdates } from '../../../types'; -import { transformRule } from './common_transformations'; -type RuleUpdatesBody = Pick< - RuleUpdates, - 'name' | 'tags' | 'schedule' | 'actions' | 'params' | 'alertDelay' ->; -export const UPDATE_FIELDS: Array = [ - 'name', - 'tags', - 'schedule', - 'params', - 'actions', - 'alertDelay', -]; +import { RewriteResponseCase } from '@kbn/actions-types'; +import { UpdateRuleBody } from './types'; -export const rewriteBodyRequest: RewriteResponseCase = ({ +export const transformUpdateRuleBody: RewriteResponseCase = ({ actions, alertDelay, ...res @@ -57,21 +42,3 @@ export const rewriteBodyRequest: RewriteResponseCase = ({ }), ...(alertDelay ? { alert_delay: alertDelay } : {}), }); - -export async function updateRule({ - http, - rule, - id, -}: { - http: HttpSetup; - rule: RuleUpdatesBody; - id: string; -}): Promise { - const res = await http.put>( - `${BASE_ALERTING_API_PATH}/rule/${encodeURIComponent(id)}`, - { - body: JSON.stringify(rewriteBodyRequest(pick(rule, UPDATE_FIELDS))), - } - ); - return transformRule(res); -} diff --git a/packages/kbn-alerts-ui-shared/src/common/apis/update_rule/types.ts b/packages/kbn-alerts-ui-shared/src/common/apis/update_rule/types.ts new file mode 100644 index 0000000000000..bca55de97dc29 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/apis/update_rule/types.ts @@ -0,0 +1,20 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { Rule, RuleTypeParams } from '../../types'; + +export interface UpdateRuleBody { + name: Rule['name']; + tags: Rule['tags']; + schedule: Rule['schedule']; + throttle?: Rule['throttle']; + params: Rule['params']; + actions: Rule['actions']; + notifyWhen?: Rule['notifyWhen']; + alertDelay?: Rule['alertDelay']; +} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/update.test.ts b/packages/kbn-alerts-ui-shared/src/common/apis/update_rule/update_rule.test.ts similarity index 91% rename from x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/update.test.ts rename to packages/kbn-alerts-ui-shared/src/common/apis/update_rule/update_rule.test.ts index ebe187ba88ed0..64f9bf686ba2d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/update.test.ts +++ b/packages/kbn-alerts-ui-shared/src/common/apis/update_rule/update_rule.test.ts @@ -1,13 +1,14 @@ /* * 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. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ -import { Rule } from '../../../types'; import { httpServiceMock } from '@kbn/core/public/mocks'; -import { updateRule } from './update'; +import { Rule } from '../../types'; +import { updateRule, UpdateRuleBody } from '.'; const http = httpServiceMock.createStartContract(); @@ -89,7 +90,7 @@ describe('updateRule', () => { ], }); - const result = await updateRule({ http, id: '12/3', rule: ruleToUpdate }); + const result = await updateRule({ http, id: '12/3', rule: ruleToUpdate as UpdateRuleBody }); expect(result).toEqual({ ...resolvedValue, diff --git a/packages/kbn-alerts-ui-shared/src/common/apis/update_rule/update_rule.ts b/packages/kbn-alerts-ui-shared/src/common/apis/update_rule/update_rule.ts new file mode 100644 index 0000000000000..bae2fc52f5180 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/apis/update_rule/update_rule.ts @@ -0,0 +1,43 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { HttpSetup } from '@kbn/core/public'; +import { pick } from 'lodash'; +import { AsApiContract } from '@kbn/actions-types'; +import { UpdateRuleBody } from './types'; +import { BASE_ALERTING_API_PATH } from '../../constants'; +import { transformUpdateRuleBody } from './transform_update_rule_body'; +import { transformRule } from '../../transformations'; +import { Rule } from '../../types'; + +export const UPDATE_FIELDS: Array = [ + 'name', + 'tags', + 'schedule', + 'params', + 'alertDelay', + 'actions', +]; + +export async function updateRule({ + http, + rule, + id, +}: { + http: HttpSetup; + rule: UpdateRuleBody; + id: string; +}): Promise { + const res = await http.put>( + `${BASE_ALERTING_API_PATH}/rule/${encodeURIComponent(id)}`, + { + body: JSON.stringify(transformUpdateRuleBody(pick(rule, UPDATE_FIELDS))), + } + ); + return transformRule(res); +} diff --git a/packages/kbn-alerts-ui-shared/src/common/constants.ts b/packages/kbn-alerts-ui-shared/src/common/constants.ts index de25fbe1e76f9..fcbf498988a66 100644 --- a/packages/kbn-alerts-ui-shared/src/common/constants.ts +++ b/packages/kbn-alerts-ui-shared/src/common/constants.ts @@ -8,4 +8,6 @@ export const ALERTS_FEATURE_ID = 'alerts'; export const BASE_ALERTING_API_PATH = '/api/alerting'; +export const INTERNAL_BASE_ALERTING_API_PATH = '/internal/alerting'; export const BASE_RAC_ALERTS_API_PATH = '/internal/rac/alerts'; +export const BASE_TRIGGERS_ACTIONS_UI_API_PATH = '/internal/triggers_actions_ui'; diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/index.ts b/packages/kbn-alerts-ui-shared/src/common/hooks/index.ts index 027c825d1cee6..322d466b715ab 100644 --- a/packages/kbn-alerts-ui-shared/src/common/hooks/index.ts +++ b/packages/kbn-alerts-ui-shared/src/common/hooks/index.ts @@ -7,3 +7,9 @@ */ export * from './use_load_rule_types_query'; +export * from './use_load_ui_config'; +export * from './use_load_ui_health'; +export * from './use_load_alerting_framework_health'; +export * from './use_create_rule'; +export * from './use_update_rule'; +export * from './use_resolve_rule'; diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/use_create_rule.test.tsx b/packages/kbn-alerts-ui-shared/src/common/hooks/use_create_rule.test.tsx new file mode 100644 index 0000000000000..a32fec7185b46 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/hooks/use_create_rule.test.tsx @@ -0,0 +1,132 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { renderHook } from '@testing-library/react-hooks/dom'; +import { waitFor } from '@testing-library/react'; +import type { HttpStart } from '@kbn/core-http-browser'; + +import { useCreateRule } from './use_create_rule'; +import { CreateRuleBody } from '../apis/create_rule'; +import { RuleTypeParams } from '../types'; + +const ruleToCreate: CreateRuleBody = { + params: { + aggType: 'count', + termSize: 5, + thresholdComparator: '>', + timeWindowSize: 5, + timeWindowUnit: 'm', + groupBy: 'all', + threshold: [1000], + index: ['.kibana'], + timeField: 'alert.executionStatus.lastExecutionDate', + }, + consumer: 'alerts', + schedule: { interval: '1m' }, + tags: [], + name: 'test', + enabled: true, + throttle: null, + ruleTypeId: '.index-threshold', + actions: [ + { + group: 'threshold met', + id: '83d4d860-9316-11eb-a145-93ab369a4461', + params: { + level: 'info', + message: 'test-message', + }, + actionTypeId: '.server-log', + frequency: { + notifyWhen: 'onActionGroupChange', + throttle: null, + summary: false, + }, + useAlertDataForTemplate: true, + }, + { + id: '.test-system-action', + params: {}, + actionTypeId: '.system-action', + }, + ], + notifyWhen: 'onActionGroupChange', + alertDelay: { + active: 10, + }, +}; + +const queryClient = new QueryClient(); + +const wrapper = ({ children }: { children: Node }) => ( + {children} +); + +jest.mock('../apis/create_rule/create_rule', () => ({ + createRule: jest.fn(), +})); + +const { createRule } = jest.requireMock('../apis/create_rule/create_rule'); + +const httpMock = jest.fn(); +const onSuccessMock = jest.fn(); +const onErrorMock = jest.fn(); + +describe('useCreateRule', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + test('should call onSuccess if request succeeds', async () => { + createRule.mockResolvedValueOnce({}); + + const { result } = renderHook( + () => { + return useCreateRule({ + http: httpMock as unknown as HttpStart, + onSuccess: onSuccessMock, + onError: onErrorMock, + }); + }, + { wrapper } + ); + + result.current.mutate({ formData: ruleToCreate }); + + await waitFor(() => { + return expect(createRule).toHaveBeenCalledWith({ http: httpMock, rule: ruleToCreate }); + }); + + expect(onSuccessMock).toHaveBeenCalled(); + }); + + test('should call onError if request fails', async () => { + createRule.mockRejectedValueOnce({}); + + const { result } = renderHook( + () => { + return useCreateRule({ + http: httpMock as unknown as HttpStart, + onSuccess: onSuccessMock, + onError: onErrorMock, + }); + }, + { wrapper } + ); + + result.current.mutate({ formData: ruleToCreate }); + + await waitFor(() => { + return expect(createRule).toHaveBeenCalledWith({ http: httpMock, rule: ruleToCreate }); + }); + + expect(onErrorMock).toHaveBeenCalled(); + }); +}); diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/use_create_rule.ts b/packages/kbn-alerts-ui-shared/src/common/hooks/use_create_rule.ts new file mode 100644 index 0000000000000..012e9b50e2807 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/hooks/use_create_rule.ts @@ -0,0 +1,35 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { useMutation } from '@tanstack/react-query'; +import type { HttpStart, IHttpFetchError } from '@kbn/core-http-browser'; +import { createRule, CreateRuleBody } from '../apis/create_rule'; + +export interface UseCreateRuleProps { + http: HttpStart; + onSuccess?: (formData: CreateRuleBody) => void; + onError?: (error: IHttpFetchError<{ message: string }>) => void; +} + +export const useCreateRule = (props: UseCreateRuleProps) => { + const { http, onSuccess, onError } = props; + + const mutationFn = ({ formData }: { formData: CreateRuleBody }) => { + return createRule({ + http, + rule: formData, + }); + }; + + return useMutation({ + mutationKey: ['useUpdateRule'], + mutationFn, + onSuccess, + onError, + }); +}; diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/use_load_alerting_framework_health.ts b/packages/kbn-alerts-ui-shared/src/common/hooks/use_load_alerting_framework_health.ts new file mode 100644 index 0000000000000..547e2457ff9ea --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/hooks/use_load_alerting_framework_health.ts @@ -0,0 +1,38 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { useQuery } from '@tanstack/react-query'; +import type { HttpStart } from '@kbn/core-http-browser'; +import { fetchAlertingFrameworkHealth } from '../apis/fetch_alerting_framework_health'; + +export interface UseLoadAlertingFrameworkHealthProps { + http: HttpStart; +} + +export const useLoadAlertingFrameworkHealth = (props: UseLoadAlertingFrameworkHealthProps) => { + const { http } = props; + + const queryFn = () => { + return fetchAlertingFrameworkHealth({ http }); + }; + + const { data, isSuccess, isFetching, isLoading, isInitialLoading, isError, error } = useQuery({ + queryKey: ['useLoadAlertingFrameworkHealth'], + queryFn, + refetchOnWindowFocus: false, + }); + + return { + data, + isLoading: isLoading || isFetching, + isInitialLoading, + isSuccess, + isError, + error, + }; +}; diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/use_load_ui_config.ts b/packages/kbn-alerts-ui-shared/src/common/hooks/use_load_ui_config.ts new file mode 100644 index 0000000000000..565ac558dfd7e --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/hooks/use_load_ui_config.ts @@ -0,0 +1,38 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { useQuery } from '@tanstack/react-query'; +import type { HttpStart } from '@kbn/core-http-browser'; +import { fetchUiConfig } from '../apis/fetch_ui_config'; + +export interface UseLoadUiConfigProps { + http: HttpStart; +} + +export const useLoadUiConfig = (props: UseLoadUiConfigProps) => { + const { http } = props; + + const queryFn = () => { + return fetchUiConfig({ http }); + }; + + const { data, isSuccess, isLoading, isFetching, isInitialLoading, isError, error } = useQuery({ + queryKey: ['useLoadUiConfig'], + queryFn, + refetchOnWindowFocus: false, + }); + + return { + data, + isLoading: isLoading || isFetching, + isInitialLoading, + isSuccess, + isError, + error, + }; +}; diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/use_load_ui_health.ts b/packages/kbn-alerts-ui-shared/src/common/hooks/use_load_ui_health.ts new file mode 100644 index 0000000000000..c4efc95575f2d --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/hooks/use_load_ui_health.ts @@ -0,0 +1,38 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { useQuery } from '@tanstack/react-query'; +import type { HttpStart } from '@kbn/core-http-browser'; +import { fetchUiHealthStatus } from '../apis/fetch_ui_health_status'; + +export interface UseLoadUiHealthProps { + http: HttpStart; +} + +export const useLoadUiHealth = (props: UseLoadUiHealthProps) => { + const { http } = props; + + const queryFn = () => { + return fetchUiHealthStatus({ http }); + }; + + const { data, isSuccess, isLoading, isFetching, isInitialLoading, isError, error } = useQuery({ + queryKey: ['useLoadUiHealth'], + queryFn, + refetchOnWindowFocus: false, + }); + + return { + data, + isLoading: isLoading || isFetching, + isInitialLoading, + isSuccess, + isError, + error, + }; +}; diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/use_resolve_rule.test.tsx b/packages/kbn-alerts-ui-shared/src/common/hooks/use_resolve_rule.test.tsx new file mode 100644 index 0000000000000..aeaabe64ef97f --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/hooks/use_resolve_rule.test.tsx @@ -0,0 +1,72 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { renderHook } from '@testing-library/react-hooks/dom'; +import { waitFor } from '@testing-library/react'; +import type { HttpStart } from '@kbn/core-http-browser'; + +import { useResolveRule } from './use_resolve_rule'; + +const queryClient = new QueryClient(); + +const wrapper = ({ children }: { children: Node }) => ( + {children} +); + +jest.mock('../apis/resolve_rule/resolve_rule', () => ({ + resolveRule: jest.fn(), +})); + +const { resolveRule } = jest.requireMock('../apis/resolve_rule/resolve_rule'); + +const httpMock = jest.fn(); + +describe('useResolveRule', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + test('should call resolve rule API if ID is passed in', async () => { + resolveRule.mockResolvedValueOnce({}); + const { result } = renderHook( + () => { + return useResolveRule({ + id: 'test-id', + http: httpMock as unknown as HttpStart, + }); + }, + { wrapper } + ); + + await waitFor(() => { + return expect(result.current.isInitialLoading).toBeFalsy(); + }); + expect(result.current.data).not.toBeFalsy(); + expect(resolveRule).toHaveBeenCalled(); + }); + + test('should not call resolve rule API if ID is not passed in', async () => { + resolveRule.mockResolvedValueOnce({}); + const { result } = renderHook( + () => { + return useResolveRule({ + http: httpMock as unknown as HttpStart, + }); + }, + { wrapper } + ); + + await waitFor(() => { + return expect(result.current.isInitialLoading).toBeFalsy(); + }); + expect(result.current.data).toBeFalsy(); + expect(resolveRule).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/use_resolve_rule.ts b/packages/kbn-alerts-ui-shared/src/common/hooks/use_resolve_rule.ts new file mode 100644 index 0000000000000..3f70cb8388d0a --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/hooks/use_resolve_rule.ts @@ -0,0 +1,53 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { useQuery } from '@tanstack/react-query'; +import type { HttpStart } from '@kbn/core-http-browser'; +import { resolveRule } from '../apis/resolve_rule'; +import { RuleFormData } from '../../rule_form'; + +export interface UseResolveProps { + http: HttpStart; + id?: string; +} + +export const useResolveRule = (props: UseResolveProps) => { + const { id, http } = props; + + const queryFn = () => { + if (id) { + return resolveRule({ http, id }); + } + }; + + const { data, isSuccess, isFetching, isLoading, isInitialLoading, isError, error } = useQuery({ + queryKey: ['useResolveRule', id], + queryFn, + enabled: !!id, + select: (rule): RuleFormData | null => { + if (!rule) { + return null; + } + return { + ...rule, + ...(rule.alertDelay ? { alertDelay: rule.alertDelay } : {}), + ...(rule.notifyWhen ? { notifyWhen: rule.notifyWhen } : {}), + }; + }, + refetchOnWindowFocus: false, + }); + + return { + data, + isLoading: isLoading || isFetching, + isInitialLoading, + isSuccess, + isError, + error, + }; +}; diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/use_update_rule.test.tsx b/packages/kbn-alerts-ui-shared/src/common/hooks/use_update_rule.test.tsx new file mode 100644 index 0000000000000..7941fa86c5825 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/hooks/use_update_rule.test.tsx @@ -0,0 +1,137 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { renderHook } from '@testing-library/react-hooks/dom'; +import { waitFor } from '@testing-library/react'; +import type { HttpStart } from '@kbn/core-http-browser'; + +import { useUpdateRule } from './use_update_rule'; +import { UpdateRuleBody } from '../apis/update_rule'; +import { RuleTypeParams } from '../types'; + +const ruleToUpdate: UpdateRuleBody = { + params: { + aggType: 'count', + termSize: 5, + thresholdComparator: '>', + timeWindowSize: 5, + timeWindowUnit: 'm', + groupBy: 'all', + threshold: [1000], + index: ['.kibana'], + timeField: 'alert.executionStatus.lastExecutionDate', + }, + schedule: { interval: '1m' }, + tags: [], + name: 'test', + throttle: null, + actions: [ + { + group: 'threshold met', + id: '83d4d860-9316-11eb-a145-93ab369a4461', + params: { + level: 'info', + message: 'test-message', + }, + actionTypeId: '.server-log', + frequency: { + notifyWhen: 'onActionGroupChange', + throttle: null, + summary: false, + }, + useAlertDataForTemplate: true, + }, + { + id: '.test-system-action', + params: {}, + actionTypeId: '.system-action', + }, + ], + notifyWhen: 'onActionGroupChange', + alertDelay: { + active: 10, + }, +}; + +const queryClient = new QueryClient(); + +const wrapper = ({ children }: { children: Node }) => ( + {children} +); + +jest.mock('../apis/update_rule/update_rule', () => ({ + updateRule: jest.fn(), +})); + +const { updateRule } = jest.requireMock('../apis/update_rule/update_rule'); + +const httpMock = jest.fn(); +const onSuccessMock = jest.fn(); +const onErrorMock = jest.fn(); + +describe('useUpdateRule', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + test('should call onSuccess if request succeeds', async () => { + updateRule.mockResolvedValueOnce({}); + + const { result } = renderHook( + () => { + return useUpdateRule({ + http: httpMock as unknown as HttpStart, + onSuccess: onSuccessMock, + onError: onErrorMock, + }); + }, + { wrapper } + ); + + result.current.mutate({ id: 'test-rule', formData: ruleToUpdate }); + + await waitFor(() => { + return expect(updateRule).toHaveBeenCalledWith({ + http: httpMock, + id: 'test-rule', + rule: ruleToUpdate, + }); + }); + + expect(onSuccessMock).toHaveBeenCalled(); + }); + + test('should call onError if request fails', async () => { + updateRule.mockRejectedValueOnce({}); + + const { result } = renderHook( + () => { + return useUpdateRule({ + http: httpMock as unknown as HttpStart, + onSuccess: onSuccessMock, + onError: onErrorMock, + }); + }, + { wrapper } + ); + + result.current.mutate({ id: 'test-rule', formData: ruleToUpdate }); + + await waitFor(() => { + return expect(updateRule).toHaveBeenCalledWith({ + http: httpMock, + id: 'test-rule', + rule: ruleToUpdate, + }); + }); + + expect(onErrorMock).toHaveBeenCalled(); + }); +}); diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/use_update_rule.ts b/packages/kbn-alerts-ui-shared/src/common/hooks/use_update_rule.ts new file mode 100644 index 0000000000000..ae480a9dd205a --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/hooks/use_update_rule.ts @@ -0,0 +1,36 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { useMutation } from '@tanstack/react-query'; +import type { HttpStart, IHttpFetchError } from '@kbn/core-http-browser'; +import { updateRule, UpdateRuleBody } from '../apis/update_rule'; + +export interface UseUpdateRuleProps { + http: HttpStart; + onSuccess?: (formData: UpdateRuleBody) => void; + onError?: (error: IHttpFetchError<{ message: string }>) => void; +} + +export const useUpdateRule = (props: UseUpdateRuleProps) => { + const { http, onSuccess, onError } = props; + + const mutationFn = ({ id, formData }: { id: string; formData: UpdateRuleBody }) => { + return updateRule({ + id, + http, + rule: formData, + }); + }; + + return useMutation({ + mutationKey: ['useUpdateRule'], + mutationFn, + onSuccess, + onError, + }); +}; diff --git a/packages/kbn-alerts-ui-shared/src/common/index.ts b/packages/kbn-alerts-ui-shared/src/common/index.ts new file mode 100644 index 0000000000000..12594660136d8 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/index.ts @@ -0,0 +1,9 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './types'; diff --git a/packages/kbn-alerts-ui-shared/src/common/test_utils/action_type_registry.mock.ts b/packages/kbn-alerts-ui-shared/src/common/test_utils/action_type_registry.mock.ts new file mode 100644 index 0000000000000..e9d6928a29490 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/test_utils/action_type_registry.mock.ts @@ -0,0 +1,45 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { lazy } from 'react'; +import { v4 as uuidv4 } from 'uuid'; +import { ActionTypeModel, ActionTypeRegistryContract } from '../types'; + +const createActionTypeRegistryMock = () => { + const mocked: jest.Mocked = { + has: jest.fn((x) => true), + register: jest.fn(), + get: jest.fn(), + list: jest.fn(), + }; + return mocked; +}; + +const mockedActionParamsFields = lazy(async () => ({ + default() { + return React.createElement(React.Fragment); + }, +})); + +const createMockActionTypeModel = (actionType: Partial = {}): ActionTypeModel => { + const id = uuidv4(); + return { + id, + iconClass: `iconClass-${id}`, + selectMessage: `selectMessage-${id}`, + validateParams: jest.fn(), + actionConnectorFields: null, + actionParamsFields: mockedActionParamsFields, + ...actionType, + }; +}; + +export const actionTypeRegistryMock = { + create: createActionTypeRegistryMock, + createMockActionTypeModel, +}; diff --git a/packages/kbn-alerts-ui-shared/src/common/transformations/index.ts b/packages/kbn-alerts-ui-shared/src/common/transformations/index.ts new file mode 100644 index 0000000000000..58e55db753aca --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/transformations/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './transform_action'; +export * from './transform_rule'; diff --git a/packages/kbn-alerts-ui-shared/src/common/transformations/transform_action.ts b/packages/kbn-alerts-ui-shared/src/common/transformations/transform_action.ts new file mode 100644 index 0000000000000..af6cf4c939388 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/transformations/transform_action.ts @@ -0,0 +1,37 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { RewriteRequestCase } from '@kbn/actions-types'; +import { RuleUiAction } from '..'; + +export const transformAction: RewriteRequestCase = (action) => { + const { uuid, id, connector_type_id: actionTypeId, params } = action; + return { + ...('group' in action && action.group ? { group: action.group } : {}), + id, + params, + actionTypeId, + ...('use_alert_data_for_template' in action && + typeof action.use_alert_data_for_template !== 'undefined' + ? { useAlertDataForTemplate: action.use_alert_data_for_template } + : {}), + ...('frequency' in action && action.frequency + ? { + frequency: { + summary: action.frequency.summary, + notifyWhen: action.frequency.notify_when, + throttle: action.frequency.throttle, + }, + } + : {}), + ...('alerts_filter' in action && action.alerts_filter + ? { alertsFilter: action.alerts_filter } + : {}), + ...(uuid && { uuid }), + }; +}; diff --git a/packages/kbn-alerts-ui-shared/src/common/transformations/transform_rule.ts b/packages/kbn-alerts-ui-shared/src/common/transformations/transform_rule.ts new file mode 100644 index 0000000000000..46717052d70b2 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/transformations/transform_rule.ts @@ -0,0 +1,96 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { AsApiContract, RewriteRequestCase } from '@kbn/actions-types'; +import { RuleExecutionStatus, RuleLastRun } from '@kbn/alerting-types'; +import type { ResolvedRule, RuleUiAction, Rule } from '..'; +import { transformAction } from '.'; + +const transformExecutionStatus: RewriteRequestCase = ({ + last_execution_date: lastExecutionDate, + last_duration: lastDuration, + ...rest +}) => ({ + lastExecutionDate, + lastDuration, + ...rest, +}); + +const transformLastRun: RewriteRequestCase = ({ + outcome_msg: outcomeMsg, + outcome_order: outcomeOrder, + alerts_count: alertsCount, + ...rest +}) => ({ + outcomeMsg, + outcomeOrder, + alertsCount, + ...rest, +}); + +export const transformRule: RewriteRequestCase = ({ + rule_type_id: ruleTypeId, + created_by: createdBy, + updated_by: updatedBy, + created_at: createdAt, + updated_at: updatedAt, + api_key_owner: apiKeyOwner, + api_key_created_by_user: apiKeyCreatedByUser, + notify_when: notifyWhen, + mute_all: muteAll, + muted_alert_ids: mutedInstanceIds, + scheduled_task_id: scheduledTaskId, + execution_status: executionStatus, + actions: actions, + snooze_schedule: snoozeSchedule, + is_snoozed_until: isSnoozedUntil, + active_snoozes: activeSnoozes, + last_run: lastRun, + next_run: nextRun, + alert_delay: alertDelay, + ...rest +}: any) => ({ + ruleTypeId, + createdBy, + updatedBy, + createdAt, + updatedAt, + apiKeyOwner, + notifyWhen, + muteAll, + mutedInstanceIds, + snoozeSchedule, + executionStatus: executionStatus ? transformExecutionStatus(executionStatus) : undefined, + actions: actions + ? actions.map((action: AsApiContract) => transformAction(action)) + : [], + scheduledTaskId, + isSnoozedUntil, + activeSnoozes, + ...(lastRun ? { lastRun: transformLastRun(lastRun) } : {}), + ...(nextRun ? { nextRun } : {}), + ...(apiKeyCreatedByUser !== undefined ? { apiKeyCreatedByUser } : {}), + ...(alertDelay ? { alertDelay } : {}), + ...rest, +}); + +export const transformResolvedRule: RewriteRequestCase = ({ + // eslint-disable-next-line @typescript-eslint/naming-convention + alias_target_id, + // eslint-disable-next-line @typescript-eslint/naming-convention + alias_purpose, + outcome, + ...rest +}: any) => { + return { + ...transformRule(rest), + alias_target_id, + alias_purpose, + outcome, + }; +}; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/type_registry.test.ts b/packages/kbn-alerts-ui-shared/src/common/type_registry.test.ts similarity index 91% rename from x-pack/plugins/triggers_actions_ui/public/application/type_registry.test.ts rename to packages/kbn-alerts-ui-shared/src/common/type_registry.test.ts index 1d30eaab2a234..53ee8b3e0a354 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/type_registry.test.ts +++ b/packages/kbn-alerts-ui-shared/src/common/type_registry.test.ts @@ -1,18 +1,14 @@ /* * 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. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import { TypeRegistry } from './type_registry'; -import { - ValidationResult, - RuleTypeModel, - ActionTypeModel, - GenericValidationResult, -} from '../types'; -import { actionTypeRegistryMock } from './action_type_registry.mock'; +import { ActionTypeModel, GenericValidationResult, RuleTypeModel, ValidationResult } from './types'; +import { actionTypeRegistryMock } from './test_utils/action_type_registry.mock'; export const ExpressionComponent: React.FunctionComponent = () => { return null; diff --git a/packages/kbn-alerts-ui-shared/src/common/type_registry.ts b/packages/kbn-alerts-ui-shared/src/common/type_registry.ts new file mode 100644 index 0000000000000..1a8cbbd1e0f8a --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/type_registry.ts @@ -0,0 +1,62 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { i18n } from '@kbn/i18n'; + +interface BaseObjectType { + id: string; +} + +export class TypeRegistry { + private readonly objectTypes: Map = new Map(); + + /** + * Returns if the object type registry has the given type registered + */ + public has(id: string) { + return this.objectTypes.has(id); + } + + /** + * Registers an object type to the type registry + */ + public register(objectType: T) { + if (this.has(objectType.id)) { + throw new Error( + i18n.translate('alertsUIShared.typeRegistry.register.duplicateObjectTypeErrorMessage', { + defaultMessage: 'Object type "{id}" is already registered.', + values: { + id: objectType.id, + }, + }) + ); + } + this.objectTypes.set(objectType.id, objectType); + } + + /** + * Returns an object type, throw error if not registered + */ + public get(id: string): T { + if (!this.has(id)) { + throw new Error( + i18n.translate('alertsUIShared.typeRegistry.get.missingActionTypeErrorMessage', { + defaultMessage: 'Object type "{id}" is not registered.', + values: { + id, + }, + }) + ); + } + return this.objectTypes.get(id)!; + } + + public list() { + return Array.from(this.objectTypes).map(([id, objectType]) => objectType); + } +} diff --git a/packages/kbn-alerts-ui-shared/src/common/types/action_types.ts b/packages/kbn-alerts-ui-shared/src/common/types/action_types.ts new file mode 100644 index 0000000000000..9fd39aebfc270 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/types/action_types.ts @@ -0,0 +1,135 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { ComponentType, ReactNode } from 'react'; +import type { RuleActionParam, ActionVariable } from '@kbn/alerting-types'; +import { IconType, RecursivePartial } from '@elastic/eui'; +import { PublicMethodsOf } from '@kbn/utility-types'; +import { TypeRegistry } from '../type_registry'; +import { RuleFormErrors } from '.'; + +export interface GenericValidationResult { + errors: Record, string[] | unknown>; +} + +export interface ConnectorValidationError { + message: ReactNode; +} + +export type ConnectorValidationFunc = () => Promise; + +export interface ActionConnectorFieldsProps { + readOnly: boolean; + isEdit: boolean; + registerPreSubmitValidator: (validator: ConnectorValidationFunc) => void; +} + +export interface ActionConnectorProps { + secrets: Secrets; + id: string; + actionTypeId: string; + name: string; + referencedByCount?: number; + config: Config; + isPreconfigured: boolean; + isDeprecated: boolean; + isSystemAction: boolean; + isMissingSecrets?: boolean; +} + +export type SystemAction = Omit, 'config' | 'secrets'> & { + isSystemAction: true; + isPreconfigured: false; +}; + +export type PreConfiguredActionConnector = Omit< + ActionConnectorProps, + 'config' | 'secrets' +> & { + isPreconfigured: true; + isSystemAction: false; +}; + +export type UserConfiguredActionConnector = ActionConnectorProps< + Config, + Secrets +> & { + isPreconfigured: false; + isSystemAction: false; +}; + +export type ActionConnector, Secrets = Record> = + | PreConfiguredActionConnector + | SystemAction + | UserConfiguredActionConnector; + +export enum ActionConnectorMode { + Test = 'test', + ActionForm = 'actionForm', +} + +export interface ActionParamsProps { + actionParams: Partial; + index: number; + editAction: (key: string, value: RuleActionParam, index: number) => void; + errors: RuleFormErrors; + ruleTypeId?: string; + messageVariables?: ActionVariable[]; + defaultMessage?: string; + useDefaultMessage?: boolean; + actionConnector?: ActionConnector; + isLoading?: boolean; + isDisabled?: boolean; + selectedActionGroupId?: string; + showEmailSubjectAndMessage?: boolean; + executionMode?: ActionConnectorMode; + onBlur?: (field?: string) => void; + producerId?: string; +} + +export interface ActionReadOnlyElementProps { + connectorId: string; + connectorName: string; +} + +export interface CustomConnectorSelectionItem { + getText: (actionConnector: ActionConnector) => string; + getComponent: ( + actionConnector: ActionConnector + ) => React.LazyExoticComponent> | undefined; +} + +export interface ActionTypeModel { + id: string; + iconClass: IconType; + selectMessage: string; + actionTypeTitle?: string; + validateParams: ( + actionParams: ActionParams + ) => Promise | unknown>>; + actionConnectorFields: React.LazyExoticComponent< + ComponentType + > | null; + actionParamsFields: React.LazyExoticComponent>>; + actionReadOnlyExtraComponent?: React.LazyExoticComponent< + ComponentType + >; + defaultActionParams?: RecursivePartial; + defaultRecoveredActionParams?: RecursivePartial; + customConnectorSelectItem?: CustomConnectorSelectionItem; + isExperimental?: boolean; + subtype?: Array<{ id: string; name: string }>; + convertParamsBetweenGroups?: (params: ActionParams) => ActionParams | {}; + hideInUi?: boolean; + modalWidth?: number; + isSystemActionType?: boolean; +} + +export type ActionTypeRegistryContract = PublicMethodsOf< + TypeRegistry> +>; diff --git a/packages/kbn-alerts-ui-shared/src/common/types/index.ts b/packages/kbn-alerts-ui-shared/src/common/types/index.ts new file mode 100644 index 0000000000000..08756c70290b1 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/types/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './rule_types'; +export * from './action_types'; diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/types/index.ts b/packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts similarity index 77% rename from packages/kbn-alerts-ui-shared/src/rule_form/types/index.ts rename to packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts index ed3241974fa79..26c854d95b00e 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/types/index.ts +++ b/packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts @@ -16,9 +16,17 @@ import type { RuleNotifyWhenType, ActionGroup, SanitizedRule as AlertingSanitizedRule, - RuleAction, + SanitizedRuleAction as RuleAction, RuleSystemAction, + ResolvedSanitizedRule, } from '@kbn/alerting-types'; +import { RuleType } from '@kbn/triggers-actions-ui-types'; +import { PublicMethodsOf } from '@kbn/utility-types'; +import { TypeRegistry } from '../type_registry'; + +export type RuleTypeWithDescription = RuleType & { description?: string }; + +export type RuleTypeIndexWithDescriptions = Map; export type RuleTypeParams = Record; @@ -35,11 +43,11 @@ export interface ValidationResult { errors: Record; } -type RuleUiAction = RuleAction | RuleSystemAction; +export type RuleUiAction = RuleAction | RuleSystemAction; // In Triggers and Actions we treat all `Alert`s as `SanitizedRule` // so the `Params` is a black-box of Record -type SanitizedRule = Omit< +export type SanitizedRule = Omit< AlertingSanitizedRule, 'alertTypeId' | 'actions' | 'systemActions' > & { @@ -47,10 +55,15 @@ type SanitizedRule = Omit< actions: RuleUiAction[]; }; -type Rule = SanitizedRule; +export type ResolvedRule = Omit< + ResolvedSanitizedRule, + 'alertTypeId' | 'actions' | 'systemActions' +> & { + ruleTypeId: ResolvedSanitizedRule['alertTypeId']; + actions: RuleUiAction[]; +}; -export type InitialRule = Partial & - Pick; +export type Rule = SanitizedRule; export interface RuleTypeParamsExpressionProps< Params extends RuleTypeParams = RuleTypeParams, @@ -95,3 +108,5 @@ export interface RuleTypeModel { | React.FunctionComponent | React.LazyExoticComponent>; } + +export type RuleTypeRegistryContract = PublicMethodsOf>; diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_alert_delay.tsx b/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_alert_delay.tsx index 7418215c71755..7d2a226d073b6 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_alert_delay.tsx +++ b/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_alert_delay.tsx @@ -8,15 +8,14 @@ import React, { useCallback } from 'react'; import { EuiFieldNumber, EuiFormRow } from '@elastic/eui'; -import type { SanitizedRule, RuleTypeParams } from '@kbn/alerting-types'; import { ALERT_DELAY_TITLE_PREFIX, ALERT_DELAY_TITLE_SUFFIX } from '../translations'; -import { RuleFormErrors } from '../types'; +import { RuleFormErrors, Rule, RuleTypeParams } from '../../common'; const INTEGER_REGEX = /^[1-9][0-9]*$/; const INVALID_KEYS = ['-', '+', '.', 'e', 'E']; export interface RuleAlertDelayProps { - alertDelay?: SanitizedRule['alertDelay'] | null; + alertDelay?: Rule['alertDelay'] | null; errors?: RuleFormErrors; onChange: (property: string, value: unknown) => void; } diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_consumer_selection.tsx b/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_consumer_selection.tsx index 957d5c0152220..e8bc0993734d3 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_consumer_selection.tsx +++ b/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_consumer_selection.tsx @@ -10,7 +10,7 @@ import React, { useMemo, useCallback } from 'react'; import { EuiComboBox, EuiFormRow, EuiComboBoxOptionOption } from '@elastic/eui'; import { AlertConsumers, RuleCreationValidConsumer } from '@kbn/rule-data-utils'; import { FEATURE_NAME_MAP, CONSUMER_SELECT_COMBO_BOX_TITLE } from '../translations'; -import { RuleFormErrors } from '../types'; +import { RuleFormErrors } from '../../common'; export const VALID_CONSUMERS: RuleCreationValidConsumer[] = [ AlertConsumers.LOGS, diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_definition.test.tsx b/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_definition.test.tsx index 9ff8a704a728a..a0070e8025ad9 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_definition.test.tsx +++ b/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_definition.test.tsx @@ -16,8 +16,8 @@ import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/ import type { DocLinksStart } from '@kbn/core-doc-links-browser'; import { RuleDefinition } from './rule_definition'; -import { RuleTypeModel } from '../types'; -import { RuleType } from '@kbn/alerting-types'; +import { RuleTypeModel } from '../../common'; +import { RuleType } from '@kbn/triggers-actions-ui-types'; import { ALERT_DELAY_TITLE } from '../translations'; const ruleType = { diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_definition.tsx b/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_definition.tsx index adec6e0966cbd..08f74ba8fc46b 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_definition.tsx +++ b/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_definition.tsx @@ -33,9 +33,14 @@ import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import type { DocLinksStart } from '@kbn/core-doc-links-browser'; -import type { SanitizedRule, RuleTypeParams } from '@kbn/alerting-types'; import type { RuleType } from '@kbn/triggers-actions-ui-types'; -import type { RuleTypeModel, RuleFormErrors, MinimumScheduleInterval } from '../types'; +import type { + RuleTypeModel, + RuleFormErrors, + MinimumScheduleInterval, + Rule, + RuleTypeParams, +} from '../../common'; import { DOC_LINK_TITLE, LOADING_RULE_TYPE_PARAMS_TITLE, @@ -68,12 +73,12 @@ interface RuleDefinitionProps { docLinks: DocLinksStart; }; formValues: { - id?: SanitizedRule['id']; - params: SanitizedRule['params']; - schedule: SanitizedRule['schedule']; - alertDelay?: SanitizedRule['alertDelay']; - notifyWhen?: SanitizedRule['notifyWhen']; - consumer?: SanitizedRule['consumer']; + id?: Rule['id']; + params: Rule['params']; + schedule: Rule['schedule']; + alertDelay?: Rule['alertDelay']; + notifyWhen?: Rule['notifyWhen']; + consumer?: Rule['consumer']; }; minimumScheduleInterval?: MinimumScheduleInterval; errors?: RuleFormErrors; diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_schedule.tsx b/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_schedule.tsx index 5af00de31b695..8bdadc28cee45 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_schedule.tsx +++ b/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_schedule.tsx @@ -15,7 +15,7 @@ import { getDurationNumberInItsUnit, } from '../utils/parse_duration'; import { getTimeOptions } from '../utils/get_time_options'; -import { MinimumScheduleInterval, RuleFormErrors } from '../types'; +import { MinimumScheduleInterval, RuleFormErrors } from '../../common'; import { SCHEDULE_TITLE_PREFIX, INTERVAL_MINIMUM_TEXT, diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/rule_details/rule_details.tsx b/packages/kbn-alerts-ui-shared/src/rule_form/rule_details/rule_details.tsx index a2cd9b6b02bcf..30af5dfa16ed5 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/rule_details/rule_details.tsx +++ b/packages/kbn-alerts-ui-shared/src/rule_form/rule_details/rule_details.tsx @@ -15,8 +15,7 @@ import { EuiComboBoxOptionOption, EuiText, } from '@elastic/eui'; -import type { SanitizedRule, RuleTypeParams } from '@kbn/alerting-types'; -import type { RuleFormErrors } from '../types'; +import type { RuleFormErrors, Rule, RuleTypeParams } from '../../common'; import { RULE_DETAILS_TITLE, RULE_DETAILS_DESCRIPTION, @@ -26,8 +25,8 @@ import { export interface RuleDetailsProps { formValues: { - tags?: SanitizedRule['tags']; - name: SanitizedRule['name']; + tags?: Rule['tags']; + name: Rule['name']; }; errors?: RuleFormErrors; onChange: (property: string, value: unknown) => void; diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/types.ts b/packages/kbn-alerts-ui-shared/src/rule_form/types.ts new file mode 100644 index 0000000000000..dff86d5ce61fd --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/rule_form/types.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { Rule, RuleTypeParams } from '../common'; + +export interface RuleFormData { + name: Rule['name']; + tags: Rule['tags']; + params: Rule['params']; + schedule: Rule['schedule']; + consumer: Rule['consumer']; + alertDelay?: Rule['alertDelay']; + notifyWhen?: Rule['notifyWhen']; + ruleTypeId?: Rule['ruleTypeId']; +} + +export type InitialRule = Partial & + Pick; diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/utils/get_errors.ts b/packages/kbn-alerts-ui-shared/src/rule_form/utils/get_errors.ts index 445d900d3f56c..b0ff1f1fd067b 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/utils/get_errors.ts +++ b/packages/kbn-alerts-ui-shared/src/rule_form/utils/get_errors.ts @@ -7,12 +7,11 @@ */ import { - InitialRule, RuleTypeModel, RuleFormErrors, ValidationResult, MinimumScheduleInterval, -} from '../types'; +} from '../../common'; import { parseDuration, formatDuration } from './parse_duration'; import { NAME_REQUIRED_TEXT, @@ -22,6 +21,7 @@ import { INTERVAL_MINIMUM_TEXT, RULE_ALERT_DELAY_BELOW_MINIMUM_TEXT, } from '../translations'; +import { InitialRule } from '../types'; export function validateBaseProperties({ rule, diff --git a/packages/kbn-alerts-ui-shared/tsconfig.json b/packages/kbn-alerts-ui-shared/tsconfig.json index dc2e78cbd49bb..c22c1ac1beac7 100644 --- a/packages/kbn-alerts-ui-shared/tsconfig.json +++ b/packages/kbn-alerts-ui-shared/tsconfig.json @@ -37,5 +37,6 @@ "@kbn/core-doc-links-browser", "@kbn/charts-plugin", "@kbn/data-plugin", + "@kbn/utility-types", ] } diff --git a/packages/kbn-esql-validation-autocomplete/package.json b/packages/kbn-esql-validation-autocomplete/package.json index 51d2e86a26fd9..38470d679ff32 100644 --- a/packages/kbn-esql-validation-autocomplete/package.json +++ b/packages/kbn-esql-validation-autocomplete/package.json @@ -8,9 +8,8 @@ "make:tests": "ts-node --transpileOnly ./scripts/generate_function_validation_tests.ts", "postmake:tests": "yarn run lint:fix", "make:defs": "ts-node --transpileOnly ./scripts/generate_function_definitions.ts", - "postmake:defs": "yarn run lint:fix && yarn run i18n:fix", + "postmake:defs": "yarn run lint:fix", "lint:fix": "cd ../.. && node ./scripts/eslint --fix ./packages/kbn-esql-validation-autocomplete/src/**/*.ts", - "i18n:fix": "cd ../.. && node ./scripts/i18n_check.js --fix", "test:validation": "cd ../.. && yarn test:jest ./packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts" } } diff --git a/packages/kbn-reporting/public/share/share_context_menu/register_csv_modal_reporting.tsx b/packages/kbn-reporting/public/share/share_context_menu/register_csv_modal_reporting.tsx index 6877ed1c2b384..f9e4286333cf7 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/register_csv_modal_reporting.tsx +++ b/packages/kbn-reporting/public/share/share_context_menu/register_csv_modal_reporting.tsx @@ -142,7 +142,7 @@ export const reportingCsvShareProvider = ({ const relativePath = apiClient.getReportingPublicJobPath( reportType, - apiClient.getDecoratedJobParams(getJobParams()) + apiClient.getDecoratedJobParams(getJobParams(true)) ); const absoluteUrl = new URL(relativePath, window.location.href).toString(); diff --git a/packages/kbn-text-based-editor/package.json b/packages/kbn-text-based-editor/package.json index 93c1c949a10d1..c585b638268ce 100644 --- a/packages/kbn-text-based-editor/package.json +++ b/packages/kbn-text-based-editor/package.json @@ -8,8 +8,7 @@ ], "scripts": { "make:docs": "ts-node --transpileOnly scripts/generate_esql_docs.ts", - "postmake:docs": "yarn run lint:fix && yarn run i18n:fix", - "lint:fix": "cd ../.. && node ./scripts/eslint --fix ./packages/kbn-text-based-editor/src/esql_documentation_sections.tsx", - "i18n:fix": "cd ../.. && node ./scripts/i18n_check.js --fix" + "postmake:docs": "yarn run lint:fix", + "lint:fix": "cd ../.. && node ./scripts/eslint --fix ./packages/kbn-text-based-editor/src/esql_documentation_sections.tsx" } } diff --git a/src/plugins/share/README.mdx b/src/plugins/share/README.mdx index 5c32853b79895..16fa4ac2d7c63 100644 --- a/src/plugins/share/README.mdx +++ b/src/plugins/share/README.mdx @@ -13,10 +13,14 @@ The `share` plugin contains various utilities for displaying sharing context men generating deep links to other apps using *locators*, and creating short URLs. -## Sharing context menu +## Sharing context menu and modal -You can register an item into sharing context menu (which is displayed in -Dashboard, Discover, and Visualize apps). +You can register an item into sharing context modal or menu (which is displayed in +Dashboard, Discover, and Visualize apps). In early 2024, the Shared UX team created a tabbed share modal redesign. +Canvas still is using the older share context menu, but Dashboard, Discover, and Visualize apps are since using +the new modal implementation. This change was made for a less cluttered UI and streamlined user experience. +Copy links default to short links based on user feedback. Reports/Exports have been consolidated into one tab called +Exports, versus the separated panels in the share context menu. ## Locators @@ -204,15 +208,3 @@ const url = await shortUrls.create({ To resolve the short URL, navigate to `/r/s/` in the browser. -### Redesign of Share Context menu -April 2024 the share context menu changed from using EUI panels to a tabbed modal. One of the goals -was to streamline the user experience and remove areas of confusion. For instance, the saved object -and snapshot radio options in the Link portion was confusing to users. The following was implemented -in the redesign: - -When user clicks the ‘copy link’ button -For dashboard: copy the “snapshot” URL to user clipboard -For lens: copy the “saved object” URL to user clipboard. -If lens is not saved to library you cannot copy (show unsaved changed error as in figma) -For discover: discover is saved: copy the “snapshot” URL to user clipboard -Default to short URL where possible diff --git a/src/plugins/share/public/components/tabs/export/export_content.tsx b/src/plugins/share/public/components/tabs/export/export_content.tsx index b1106b7df7774..61c58986477bd 100644 --- a/src/plugins/share/public/components/tabs/export/export_content.tsx +++ b/src/plugins/share/public/components/tabs/export/export_content.tsx @@ -133,6 +133,7 @@ const ExportContentUi = ({ iconType="copyClipboard" onClick={copy} data-test-subj="shareReportingCopyURL" + data-share-url={absoluteUrl} > (''); const [urlParams] = useState(undefined); const [isTextCopied, setTextCopied] = useState(false); - const [, setShortUrlCache] = useState(undefined); + const [shortUrlCache, setShortUrlCache] = useState(undefined); const getUrlWithUpdatedParams = useCallback( (tempUrl: string): string => { @@ -72,7 +72,6 @@ export const LinkContent = ({ // persist updated url to state setUrl(urlWithUpdatedParams); - return urlWithUpdatedParams; }, [urlParams] @@ -93,6 +92,7 @@ export const LinkContent = ({ const snapshotUrl = getSnapshotUrl(); const shortUrl = await urlService.shortUrls.get(null).createFromLongUrl(snapshotUrl); setShortUrlCache(shortUrl.url); + return shortUrl.url; } }, [shareableUrlLocatorParams, urlService.shortUrls, getSnapshotUrl, setShortUrlCache]); @@ -111,8 +111,14 @@ export const LinkContent = ({ copyToClipboard(urlToCopy); setUrl(urlToCopy); setTextCopied(true); + return urlToCopy; }, [url, delegatedShareUrlHandler, allowShortUrl, createShortUrl, getSnapshotUrl]); + const handleTestUrl = useMemo(() => { + if (objectType !== 'search' || !allowShortUrl) return getSnapshotUrl(); + else if (objectType === 'search' && allowShortUrl) return shortUrlCache; + return copyUrlHelper(); + }, [objectType, getSnapshotUrl, allowShortUrl, shortUrlCache, copyUrlHelper]); return ( <> @@ -154,7 +160,7 @@ export const LinkContent = ({ (objectType === 'lens' && isDirty ? null : setTextCopied(false))} onClick={copyUrlHelper} color={objectType === 'lens' && isDirty ? 'warning' : 'primary'} diff --git a/test/functional/apps/dashboard/group3/dashboard_state.ts b/test/functional/apps/dashboard/group3/dashboard_state.ts index df80d35ce2a64..2c1ebee573599 100644 --- a/test/functional/apps/dashboard/group3/dashboard_state.ts +++ b/test/functional/apps/dashboard/group3/dashboard_state.ts @@ -185,8 +185,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.dashboard.waitForRenderComplete(); }; - // FLAKY: https://github.com/elastic/kibana/issues/139762 - describe.skip('Directly modifying url updates dashboard state', () => { + describe('Directly modifying url updates dashboard state', () => { before(async () => { await PageObjects.dashboard.gotoDashboardLandingPage(); await PageObjects.dashboard.clickNewDashboard(); diff --git a/test/functional/apps/dashboard/group5/share.ts b/test/functional/apps/dashboard/group5/share.ts index dfcb2e56aef36..43af46a238589 100644 --- a/test/functional/apps/dashboard/group5/share.ts +++ b/test/functional/apps/dashboard/group5/share.ts @@ -49,13 +49,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.share.clickShareTopNavButton(); return await PageObjects.share.isShareMenuOpen(); }); - // if (mode === 'savedObject') { - // await PageObjects.share.exportAsSavedObject(); - // } - return PageObjects.share.getSharedUrl(); + return await PageObjects.share.getSharedUrl(); }; - describe.skip('share dashboard', () => { + describe('share dashboard', () => { const testFilterState = async (mode: TestingModes) => { it('should not have "filters" state in either app or global state when no filters', async () => { expect(await getSharedUrl(mode)).to.not.contain('filters'); @@ -120,7 +117,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.common.unsetTime(); }); - describe.skip('snapshot share', async () => { + describe('snapshot share', async () => { describe('test local state', async () => { it('should not have "panels" state when not in unsaved changes state', async () => { await testSubjects.missingOrFail('dashboardUnsavedChangesBadge'); @@ -147,7 +144,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); - describe.skip('test filter state', async () => { + describe('test filter state', async () => { await testFilterState('snapshot'); }); @@ -158,7 +155,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); - describe.skip('saved object share', async () => { + describe('saved object share', async () => { describe('test filter state', async () => { await testFilterState('savedObject'); }); diff --git a/test/functional/apps/discover/group5/_shared_links.ts b/test/functional/apps/discover/group5/_shared_links.ts index 168702fd86487..3aeda46316c59 100644 --- a/test/functional/apps/discover/group5/_shared_links.ts +++ b/test/functional/apps/discover/group5/_shared_links.ts @@ -61,11 +61,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); describe('permalink', function () { - it('should allow for copying the snapshot URL as a short URL', async function () { - const re = new RegExp(baseUrl + '/app/r/s/.+$'); + it('should allow for copying the snapshot URL', async function () { + const re = new RegExp(baseUrl + '/app/r.+$'); await retry.try(async () => { const actualUrl = await PageObjects.share.getSharedUrl(); - expect(actualUrl).to.match(re); + expect(actualUrl).match(re); }); }); @@ -104,12 +104,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await teardown(); }); - it('should allow for copying the snapshot URL as a short URL and should open it', async function () { - const re = new RegExp(baseUrl + '/app/r/s/.+$'); + it('should allow for copying the snapshot URL and should open it', async function () { + const re = new RegExp(baseUrl + '/app/r.*$'); let actualUrl: string = ''; await retry.try(async () => { actualUrl = await PageObjects.share.getSharedUrl(); - expect(actualUrl).to.match(re); + expect(actualUrl).match(re); }); const actualTime = await PageObjects.timePicker.getTimeConfig(); diff --git a/x-pack/plugins/alerting/common/index.ts b/x-pack/plugins/alerting/common/index.ts index 23d2d48ab5e5b..6c072654cf3bd 100644 --- a/x-pack/plugins/alerting/common/index.ts +++ b/x-pack/plugins/alerting/common/index.ts @@ -7,9 +7,6 @@ // TODO: https://github.com/elastic/kibana/issues/110895 /* eslint-disable @kbn/eslint/no_export_all */ - -import { AlertsHealth } from './rule'; - export * from './rule'; export * from './rules_settings'; export * from './rule_type'; @@ -72,12 +69,6 @@ export { contextToSchemaName, } from './alert_schema'; -export interface AlertingFrameworkHealth { - isSufficientlySecure: boolean; - hasPermanentEncryptionKey: boolean; - alertingFrameworkHealth: AlertsHealth; -} - export const LEGACY_BASE_ALERT_API_PATH = '/api/alerts'; export const BASE_ALERTING_API_PATH = '/api/alerting'; export const INTERNAL_BASE_ALERTING_API_PATH = '/internal/alerting' as const; diff --git a/x-pack/plugins/alerting/common/rule.ts b/x-pack/plugins/alerting/common/rule.ts index 7d28c5fce8b54..d7f2c6920f4f4 100644 --- a/x-pack/plugins/alerting/common/rule.ts +++ b/x-pack/plugins/alerting/common/rule.ts @@ -5,11 +5,7 @@ * 2.0. */ -import type { - SavedObjectAttribute, - SavedObjectAttributes, - SavedObjectsResolveResponse, -} from '@kbn/core/server'; +import type { SavedObjectAttributes } from '@kbn/core/server'; import type { SanitizedRule, @@ -17,7 +13,7 @@ import type { AlertsFilterTimeframe, RuleAction, RuleSystemAction, - RuleTypeParams, + RuleActionParam, } from '@kbn/alerting-types'; export type { @@ -26,6 +22,7 @@ export type { SanitizedRule, RuleTypeParams, RuleActionParams, + RuleActionParam, IntervalSchedule, RuleActionFrequency, AlertsFilterTimeframe, @@ -45,6 +42,9 @@ export type { AlertDelay, SanitizedAlertsFilter, SanitizedRuleAction, + AlertsHealth, + AlertingFrameworkHealth, + ResolvedSanitizedRule, } from '@kbn/alerting-types'; export { @@ -68,19 +68,11 @@ export const RuleLastRunOutcomeOrderMap: Record = { export type RuleAlertingOutcome = 'failure' | 'success' | 'unknown' | 'warning'; -export type RuleActionParam = SavedObjectAttribute; - export type RuleActionAlertsFilterProperty = AlertsFilterTimeframe | RuleActionParam; export type RuleActionKey = keyof RuleAction; export type RuleSystemActionKey = keyof RuleSystemAction; -export type ResolvedSanitizedRule = SanitizedRule & - Omit & { - outcome: string; - alias_target_id?: string; - }; - export type SanitizedRuleConfig = Pick< SanitizedRule, | 'id' @@ -106,27 +98,6 @@ export type SanitizedRuleConfig = Pick< ruleTypeName: string; }; -export enum HealthStatus { - OK = 'ok', - Warning = 'warn', - Error = 'error', -} - -export interface AlertsHealth { - decryptionHealth: { - status: HealthStatus; - timestamp: string; - }; - executionHealth: { - status: HealthStatus; - timestamp: string; - }; - readHealth: { - status: HealthStatus; - timestamp: string; - }; -} - export interface RuleMonitoringLastRunMetrics extends SavedObjectAttributes { duration?: number; total_search_duration_ms?: number | null; diff --git a/x-pack/plugins/alerting/server/health/get_health.test.ts b/x-pack/plugins/alerting/server/health/get_health.test.ts index 51e03dc890e98..c733179c2cf2b 100644 --- a/x-pack/plugins/alerting/server/health/get_health.test.ts +++ b/x-pack/plugins/alerting/server/health/get_health.test.ts @@ -6,8 +6,9 @@ */ import { savedObjectsRepositoryMock, savedObjectsServiceMock } from '@kbn/core/server/mocks'; +import { HealthStatus } from '@kbn/alerting-types'; import { RULE_SAVED_OBJECT_TYPE } from '../saved_objects'; -import { RuleExecutionStatusErrorReasons, HealthStatus } from '../types'; +import { RuleExecutionStatusErrorReasons } from '../types'; import { getAlertingHealthStatus, getHealth } from './get_health'; const savedObjectsRepository = savedObjectsRepositoryMock.create(); diff --git a/x-pack/plugins/alerting/server/health/get_health.ts b/x-pack/plugins/alerting/server/health/get_health.ts index f4b727170a4b9..f29a88dec6489 100644 --- a/x-pack/plugins/alerting/server/health/get_health.ts +++ b/x-pack/plugins/alerting/server/health/get_health.ts @@ -6,8 +6,9 @@ */ import { ISavedObjectsRepository, SavedObjectsServiceStart } from '@kbn/core/server'; +import { AlertsHealth, HealthStatus } from '@kbn/alerting-types'; import { RULE_SAVED_OBJECT_TYPE } from '../saved_objects'; -import { AlertsHealth, HealthStatus, RawRule, RuleExecutionStatusErrorReasons } from '../types'; +import { RawRule, RuleExecutionStatusErrorReasons } from '../types'; import type { LatestTaskStateSchema } from './task_state'; export const getHealth = async ( diff --git a/x-pack/plugins/alerting/server/health/task.ts b/x-pack/plugins/alerting/server/health/task.ts index 7fb315d72deac..fb22fcf63c3fe 100644 --- a/x-pack/plugins/alerting/server/health/task.ts +++ b/x-pack/plugins/alerting/server/health/task.ts @@ -11,9 +11,9 @@ import { TaskManagerSetupContract, TaskManagerStartContract, } from '@kbn/task-manager-plugin/server'; +import { HealthStatus } from '@kbn/alerting-types'; import { AlertingConfig } from '../config'; import { AlertingPluginsStart } from '../plugin'; -import { HealthStatus } from '../types'; import { getAlertingHealthStatus } from './get_health'; import { stateSchemaByVersion, emptyState, type LatestTaskStateSchema } from './task_state'; diff --git a/x-pack/plugins/alerting/server/health/task_state.ts b/x-pack/plugins/alerting/server/health/task_state.ts index 8646abc17f712..fcb950f59d2c7 100644 --- a/x-pack/plugins/alerting/server/health/task_state.ts +++ b/x-pack/plugins/alerting/server/health/task_state.ts @@ -6,7 +6,7 @@ */ import { schema, TypeOf } from '@kbn/config-schema'; -import { HealthStatus } from '../types'; +import { HealthStatus } from '@kbn/alerting-types'; /** * WARNING: Do not modify the existing versioned schema(s) below, instead define a new version (ex: 2, 3, 4). diff --git a/x-pack/plugins/alerting/server/routes/health.test.ts b/x-pack/plugins/alerting/server/routes/health.test.ts index d72d4fe1cf2a2..28117eaeeb55b 100644 --- a/x-pack/plugins/alerting/server/routes/health.test.ts +++ b/x-pack/plugins/alerting/server/routes/health.test.ts @@ -7,12 +7,12 @@ import { healthRoute } from './health'; import { httpServiceMock } from '@kbn/core/server/mocks'; +import { HealthStatus } from '@kbn/alerting-types'; import { mockHandlerArguments } from './_mock_handler_arguments'; import { verifyApiAccess } from '../lib/license_api_access'; import { licenseStateMock } from '../lib/license_state.mock'; import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks'; import { rulesClientMock } from '../rules_client.mock'; -import { HealthStatus } from '../types'; import { alertsMock } from '../mocks'; import { RecoveredActionGroup } from '../../common'; import { RegistryAlertTypeWithAuth } from '../authorization'; diff --git a/x-pack/plugins/alerting/server/routes/legacy/health.test.ts b/x-pack/plugins/alerting/server/routes/legacy/health.test.ts index 8c3702ca3c833..d582a8e0ec480 100644 --- a/x-pack/plugins/alerting/server/routes/legacy/health.test.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/health.test.ts @@ -5,13 +5,14 @@ * 2.0. */ import { usageCountersServiceMock } from '@kbn/usage-collection-plugin/server/usage_counters/usage_counters_service.mock'; +import { HealthStatus } from '@kbn/alerting-types'; import { healthRoute } from './health'; import { httpServiceMock } from '@kbn/core/server/mocks'; import { mockHandlerArguments } from '../_mock_handler_arguments'; import { licenseStateMock } from '../../lib/license_state.mock'; import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks'; import { rulesClientMock } from '../../rules_client.mock'; -import { HealthStatus, RecoveredActionGroup } from '../../types'; +import { RecoveredActionGroup } from '../../types'; import { alertsMock } from '../../mocks'; import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage'; import { RegistryAlertTypeWithAuth } from '../../authorization'; diff --git a/x-pack/plugins/alerting/server/types.ts b/x-pack/plugins/alerting/server/types.ts index d09bda0bc0cb8..342c3070379c5 100644 --- a/x-pack/plugins/alerting/server/types.ts +++ b/x-pack/plugins/alerting/server/types.ts @@ -28,6 +28,7 @@ import type { DefaultAlert, FieldMap } from '@kbn/alerts-as-data-utils'; import { Alert } from '@kbn/alerts-as-data-utils'; import { Filter } from '@kbn/es-query'; import { ActionsApiRequestHandlerContext } from '@kbn/actions-plugin/server'; +import { AlertsHealth } from '@kbn/alerting-types'; import { RuleTypeRegistry as OrigruleTypeRegistry } from './rule_type_registry'; import { PluginSetupContract, PluginStartContract } from './plugin'; import { RulesClient } from './rules_client'; @@ -50,7 +51,6 @@ import { ActionGroup, AlertInstanceContext, AlertInstanceState, - AlertsHealth, WithoutReservedActionGroups, ActionVariable, SanitizedRuleConfig, diff --git a/x-pack/plugins/fleet/common/constants/agent_policy.ts b/x-pack/plugins/fleet/common/constants/agent_policy.ts index 9f1bc458ec363..b6e32f86ac514 100644 --- a/x-pack/plugins/fleet/common/constants/agent_policy.ts +++ b/x-pack/plugins/fleet/common/constants/agent_policy.ts @@ -38,11 +38,5 @@ export const DEFAULT_MAX_AGENT_POLICIES_WITH_INACTIVITY_TIMEOUT = 750; export const AGENTLESS_POLICY_ID = 'agentless'; // the policy id defined here: https://github.com/elastic/project-controller/blob/main/internal/project/security/security_kibana_config.go#L86 -export const AGENT_LOG_LEVELS = { - info: 'info', - debug: 'debug', - warning: 'warning', - error: 'error', -}; - -export const DEFAULT_LOG_LEVEL = AGENT_LOG_LEVELS.info; +export const AGENT_LOG_LEVELS = ['error', 'warning', 'info', 'debug'] as const; +export const DEFAULT_LOG_LEVEL = 'info' as const; diff --git a/x-pack/plugins/fleet/common/settings/agent_policy_settings.tsx b/x-pack/plugins/fleet/common/settings/agent_policy_settings.tsx index ed23ea121eae6..9d15f7a9e6e68 100644 --- a/x-pack/plugins/fleet/common/settings/agent_policy_settings.tsx +++ b/x-pack/plugins/fleet/common/settings/agent_policy_settings.tsx @@ -148,6 +148,6 @@ export const AGENT_POLICY_ADVANCED_SETTINGS: SettingsConfig[] = [ }, learnMoreLink: 'https://www.elastic.co/guide/en/fleet/current/agent-policy.html#agent-policy-log-level', - schema: z.nativeEnum(AGENT_LOG_LEVELS).default(DEFAULT_LOG_LEVEL), + schema: z.enum(AGENT_LOG_LEVELS).default(DEFAULT_LOG_LEVEL), }, ]; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/index.tsx index c91b98e82312b..92ceb2073a773 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/index.tsx @@ -63,33 +63,28 @@ settingComponentRegistry.set(ZodFirstPartyTypeKind.ZodString, ({ disabled, ...se ); }); -settingComponentRegistry.set( - ZodFirstPartyTypeKind.ZodNativeEnum, - ({ disabled, ...settingsConfig }) => { - return ( - ( - ({ - text: key, - value: value as string, - }) - )} - /> - )} - /> - ); - } -); +settingComponentRegistry.set(ZodFirstPartyTypeKind.ZodEnum, ({ disabled, ...settingsConfig }) => { + return ( + ( + ({ + text: value, + value, + }))} + /> + )} + /> + ); +}); export function ConfiguredSettings({ configuredSettings, diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/filter_log_level.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/filter_log_level.tsx index e565ddccc4b70..13c50b3770bcc 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/filter_log_level.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/filter_log_level.tsx @@ -12,8 +12,6 @@ import { i18n } from '@kbn/i18n'; import { AGENT_LOG_LEVELS } from '../../../../../../../../common/constants'; -const LEVEL_VALUES = Object.values(AGENT_LOG_LEVELS); - export const LogLevelFilter: React.FunctionComponent<{ selectedLevels: string[]; onToggleLevel: (level: string) => void; @@ -24,7 +22,7 @@ export const LogLevelFilter: React.FunctionComponent<{ const closePopover = useCallback(() => setIsOpen(false), []); const [options, setOptions] = useState( - LEVEL_VALUES.map((level) => ({ + AGENT_LOG_LEVELS.map((level) => ({ label: level, checked: selectedLevels.includes(level) ? 'on' : undefined, key: level, @@ -39,7 +37,7 @@ export const LogLevelFilter: React.FunctionComponent<{ iconType="arrowDown" onClick={togglePopover} isSelected={isOpen} - numFilters={LEVEL_VALUES.length} + numFilters={options.length} hasActiveFilters={selectedLevels.length > 0} numActiveFilters={selectedLevels.length} > diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/select_log_level.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/select_log_level.tsx index c1f7afe710435..ad69e7ce3b22a 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/select_log_level.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/select_log_level.tsx @@ -129,9 +129,9 @@ export const SelectLogLevel: React.FC<{ agent: Agent; agentPolicyLogLevel?: stri onChange={(event) => { setSelectedLogLevel(event.target.value); }} - options={Object.entries(AGENT_LOG_LEVELS).map(([key, value]) => ({ - value, - text: key, + options={AGENT_LOG_LEVELS.map((level) => ({ + value: level, + text: level, }))} /> diff --git a/x-pack/plugins/fleet/server/services/metrics/fleet_metrics_task.test.ts b/x-pack/plugins/fleet/server/services/metrics/fleet_metrics_task.test.ts index 649fa6fc848c5..f8fe92890d429 100644 --- a/x-pack/plugins/fleet/server/services/metrics/fleet_metrics_task.test.ts +++ b/x-pack/plugins/fleet/server/services/metrics/fleet_metrics_task.test.ts @@ -9,6 +9,7 @@ import { coreMock } from '@kbn/core/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import type { TaskManagerSetupContract } from '@kbn/task-manager-plugin/server'; import { TaskStatus } from '@kbn/task-manager-plugin/server'; +import { getDeleteTaskRunResult } from '@kbn/task-manager-plugin/server/task'; import type { CoreSetup } from '@kbn/core/server'; import type { ElasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; @@ -197,6 +198,18 @@ describe('fleet metrics task', () => { }); }); + it('should not run if task is outdated', async () => { + const result = await runTask({ ...MOCK_TASK_INSTANCE, id: 'old-id' }); + + expect(esClient.index).not.toHaveBeenCalled(); + expect(esClient.bulk).not.toHaveBeenCalled(); + + expect(appContextService.getLogger().info).toHaveBeenCalledWith( + 'Outdated task version: Got [old-id] from task instance. Current version is [Fleet-Metrics-Task:1.1.1]' + ); + expect(result).toEqual(getDeleteTaskRunResult()); + }); + it('should log errors from bulk create', async () => { esClient.bulk.mockResolvedValue({ errors: true, diff --git a/x-pack/plugins/fleet/server/services/metrics/fleet_metrics_task.ts b/x-pack/plugins/fleet/server/services/metrics/fleet_metrics_task.ts index 0d1c0bd142169..e99b6d1fce68c 100644 --- a/x-pack/plugins/fleet/server/services/metrics/fleet_metrics_task.ts +++ b/x-pack/plugins/fleet/server/services/metrics/fleet_metrics_task.ts @@ -4,12 +4,12 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { getDeleteTaskRunResult } from '@kbn/task-manager-plugin/server/task'; import type { ConcreteTaskInstance, TaskManagerStartContract, TaskManagerSetupContract, } from '@kbn/task-manager-plugin/server'; -import { throwUnrecoverableError } from '@kbn/task-manager-plugin/server'; import type { ElasticsearchClient } from '@kbn/core/server'; import { withSpan } from '@kbn/apm-utils'; @@ -68,8 +68,12 @@ export class FleetMetricsTask { } // Check that this task is current if (taskInstance.id !== this.taskId) { - throwUnrecoverableError(new Error('Outdated task version for task: ' + taskInstance.id)); - return; + appContextService + .getLogger() + .info( + `Outdated task version: Got [${taskInstance.id}] from task instance. Current version is [${this.taskId}]` + ); + return getDeleteTaskRunResult(); } if (!this.esClient) { appContextService.getLogger().debug('esClient not set, skipping Fleet metrics task'); diff --git a/x-pack/plugins/fleet/server/services/telemetry/fleet_usage_sender.ts b/x-pack/plugins/fleet/server/services/telemetry/fleet_usage_sender.ts index ca23ef3bb8fe0..2d986b1e7bc6c 100644 --- a/x-pack/plugins/fleet/server/services/telemetry/fleet_usage_sender.ts +++ b/x-pack/plugins/fleet/server/services/telemetry/fleet_usage_sender.ts @@ -4,12 +4,12 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { getDeleteTaskRunResult } from '@kbn/task-manager-plugin/server/task'; import type { ConcreteTaskInstance, TaskManagerStartContract, TaskManagerSetupContract, } from '@kbn/task-manager-plugin/server'; -import { throwUnrecoverableError } from '@kbn/task-manager-plugin/server'; import type { CoreSetup } from '@kbn/core/server'; import { withSpan } from '@kbn/apm-utils'; @@ -70,8 +70,12 @@ export class FleetUsageSender { } // Check that this task is current if (taskInstance.id !== this.taskId) { - throwUnrecoverableError(new Error('Outdated task version for task: ' + taskInstance.id)); - return; + appContextService + .getLogger() + .info( + `Outdated task version: Got [${taskInstance.id}] from task instance. Current version is [${this.taskId}]` + ); + return getDeleteTaskRunResult(); } appContextService.getLogger().info('Running Fleet Usage telemetry send task'); diff --git a/x-pack/plugins/fleet/server/tasks/check_deleted_files_task.test.ts b/x-pack/plugins/fleet/server/tasks/check_deleted_files_task.test.ts index 909a78f74ae34..fc426e3574ae6 100644 --- a/x-pack/plugins/fleet/server/tasks/check_deleted_files_task.test.ts +++ b/x-pack/plugins/fleet/server/tasks/check_deleted_files_task.test.ts @@ -9,6 +9,7 @@ import { coreMock } from '@kbn/core/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import type { TaskManagerSetupContract } from '@kbn/task-manager-plugin/server'; import { TaskStatus } from '@kbn/task-manager-plugin/server'; +import { getDeleteTaskRunResult } from '@kbn/task-manager-plugin/server/task'; import type { CoreSetup } from '@kbn/core/server'; import type { ElasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; import { loggingSystemMock } from '@kbn/core/server/mocks'; @@ -206,5 +207,14 @@ describe('check deleted files task', () => { { signal: abortController.signal } ); }); + + it('should not run if task is outdated', async () => { + const result = await runTask({ ...MOCK_TASK_INSTANCE, id: 'old-id' }); + + expect(esClient.search).not.toHaveBeenCalled(); + expect(esClient.updateByQuery).not.toHaveBeenCalled(); + + expect(result).toEqual(getDeleteTaskRunResult()); + }); }); }); diff --git a/x-pack/plugins/fleet/server/tasks/check_deleted_files_task.ts b/x-pack/plugins/fleet/server/tasks/check_deleted_files_task.ts index a7611d73cd313..b4541fbf21084 100644 --- a/x-pack/plugins/fleet/server/tasks/check_deleted_files_task.ts +++ b/x-pack/plugins/fleet/server/tasks/check_deleted_files_task.ts @@ -11,7 +11,7 @@ import type { TaskManagerSetupContract, TaskManagerStartContract, } from '@kbn/task-manager-plugin/server'; -import { throwUnrecoverableError } from '@kbn/task-manager-plugin/server'; +import { getDeleteTaskRunResult } from '@kbn/task-manager-plugin/server/task'; import type { LoggerFactory } from '@kbn/core/server'; import { errors } from '@elastic/elasticsearch'; @@ -102,7 +102,10 @@ export class CheckDeletedFilesTask { // Check that this task is current if (taskInstance.id !== this.taskId) { - throwUnrecoverableError(new Error('Outdated task version')); + this.logger.info( + `Outdated task version: Got [${taskInstance.id}] from task instance. Current version is [${this.taskId}]` + ); + return getDeleteTaskRunResult(); } this.logger.info(`[runTask()] started`); diff --git a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx index 8ebd12cd00c63..da083413ea92f 100644 --- a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx +++ b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx @@ -625,7 +625,9 @@ export const LensTopNavMenu = ({ allowEmbed: false, allowShortUrl: false, delegatedShareUrlHandler: () => { - return isCurrentStateDirty ? shareableUrl! : savedObjectURL.href; + return isCurrentStateDirty || !currentDoc?.savedObjectId + ? shareableUrl! + : savedObjectURL.href; }, objectId: currentDoc?.savedObjectId, objectType: 'lens', @@ -636,7 +638,7 @@ export const LensTopNavMenu = ({ }, sharingData, // only want to know about changes when savedObjectURL.href - isDirty: isCurrentStateDirty, + isDirty: isCurrentStateDirty || !currentDoc?.savedObjectId, // disable the menu if both shortURL permission and the visualization has not been saved // TODO: improve here the disabling state with more specific checks disabledShareUrl: Boolean(!shareUrlEnabled && !currentDoc?.savedObjectId), diff --git a/x-pack/plugins/observability_solution/apm/docs/openapi/apm.yaml b/x-pack/plugins/observability_solution/apm/docs/openapi/apm.yaml index c110abbfafa92..60c8a74d75b88 100644 --- a/x-pack/plugins/observability_solution/apm/docs/openapi/apm.yaml +++ b/x-pack/plugins/observability_solution/apm/docs/openapi/apm.yaml @@ -2,11 +2,21 @@ openapi: 3.0.0 info: title: APM UI version: 1.0.0 +tags: + - name: APM agent keys + description: > + Configure APM agent keys to authorize requests from APM agents to the APM Server. + - name: APM annotations + description: > + Annotate visualizations in the APM app with significant events. + Annotations enable you to easily see how events are impacting the performance of your applications. paths: /api/apm/agent_keys: post: summary: Create an APM agent key description: Create a new agent key for APM. + tags: + - APM agent keys requestBody: required: true content: @@ -46,6 +56,8 @@ paths: get: summary: Search for annotations description: Search for annotations related to a specific service. + tags: + - APM annotations parameters: - name: serviceName in: path @@ -98,6 +110,8 @@ paths: post: summary: Create a service annotation description: Create a new annotation for a specific service. + tags: + - APM annotations parameters: - name: serviceName in: path diff --git a/x-pack/plugins/observability_solution/apm/server/routes/agent_keys/route.ts b/x-pack/plugins/observability_solution/apm/server/routes/agent_keys/route.ts index 14bc1e730a27b..f0e10eade9aa5 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/agent_keys/route.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/agent_keys/route.ts @@ -91,7 +91,7 @@ const invalidateAgentKeyRoute = createApmServerRoute({ const createAgentKeyRoute = createApmServerRoute({ endpoint: 'POST /api/apm/agent_keys 2023-10-31', - options: { tags: ['access:apm', 'access:apm_write'] }, + options: { tags: ['access:apm', 'access:apm_write', 'oas-tag:APM agent keys'] }, params: t.type({ body: t.type({ name: t.string, diff --git a/x-pack/plugins/observability_solution/apm/server/routes/services/route.ts b/x-pack/plugins/observability_solution/apm/server/routes/services/route.ts index 080b1100d98bf..b95c4a5384cb1 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/services/route.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/services/route.ts @@ -372,7 +372,7 @@ const serviceAnnotationsRoute = createApmServerRoute({ }), query: t.intersection([environmentRt, rangeRt]), }), - options: { tags: ['access:apm'] }, + options: { tags: ['access:apm', 'oas-tag:APM annotations'] }, handler: async (resources): Promise => { const apmEventClient = await getApmEventClient(resources); const { params, plugins, context, request, logger, config } = resources; @@ -416,7 +416,7 @@ const serviceAnnotationsRoute = createApmServerRoute({ const serviceAnnotationsCreateRoute = createApmServerRoute({ endpoint: 'POST /api/apm/services/{serviceName}/annotation 2023-10-31', options: { - tags: ['access:apm', 'access:apm_write'], + tags: ['access:apm', 'access:apm_write', 'oas-tag:APM annotations'], }, params: t.type({ path: t.type({ diff --git a/x-pack/plugins/observability_solution/apm/server/routes/typings.ts b/x-pack/plugins/observability_solution/apm/server/routes/typings.ts index c8d9cc1477926..8ee9a3849a6fb 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/typings.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/typings.ts @@ -56,6 +56,8 @@ export interface APMRouteCreateOptions { | 'access:ml:canCreateJob' | 'access:ml:canCloseJob' | 'access:ai_assistant' + | 'oas-tag:APM agent keys' + | 'oas-tag:APM annotations' >; body?: { accepts: Array<'application/json' | 'multipart/form-data'> }; disableTelemetry?: boolean; diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/metadata/check_metadata_transforms_task.test.ts b/x-pack/plugins/security_solution/server/endpoint/lib/metadata/check_metadata_transforms_task.test.ts index c5b641853e4db..29a9d5e547825 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/metadata/check_metadata_transforms_task.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/metadata/check_metadata_transforms_task.test.ts @@ -18,6 +18,7 @@ import { coreMock } from '@kbn/core/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import type { TaskManagerSetupContract } from '@kbn/task-manager-plugin/server'; import { TaskStatus } from '@kbn/task-manager-plugin/server'; +import { getDeleteTaskRunResult } from '@kbn/task-manager-plugin/server/task'; import type { CoreSetup } from '@kbn/core/server'; import type { ElasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; import { TRANSFORM_STATES } from '../../../../common/constants'; @@ -126,6 +127,16 @@ describe('check metadata transforms task', () => { }, } as unknown as TransportResult); + it('should not run if task is outdated', async () => { + const result = await runTask({ ...MOCK_TASK_INSTANCE, id: 'old-id' }); + + expect(esClient.transform.getTransformStats).not.toHaveBeenCalled(); + expect(esClient.transform.stopTransform).not.toHaveBeenCalled(); + expect(esClient.transform.startTransform).not.toHaveBeenCalled(); + + expect(result).toEqual(getDeleteTaskRunResult()); + }); + describe('transforms restart', () => { it('should stop task if transform stats response fails', async () => { esClient.transform.getTransformStats.mockRejectedValue({}); diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/metadata/check_metadata_transforms_task.ts b/x-pack/plugins/security_solution/server/endpoint/lib/metadata/check_metadata_transforms_task.ts index 048db4b4c6c37..cf5f19ab9741f 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/metadata/check_metadata_transforms_task.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/metadata/check_metadata_transforms_task.ts @@ -16,7 +16,7 @@ import type { TaskManagerSetupContract, TaskManagerStartContract, } from '@kbn/task-manager-plugin/server'; -import { throwUnrecoverableError } from '@kbn/task-manager-plugin/server'; +import { getDeleteTaskRunResult } from '@kbn/task-manager-plugin/server/task'; import { ElasticsearchAssetType, FLEET_ENDPOINT_PACKAGE } from '@kbn/fleet-plugin/common'; import type { EndpointAppContext } from '../../types'; import { METADATA_TRANSFORMS_PATTERN } from '../../../../common/endpoint/constants'; @@ -105,7 +105,12 @@ export class CheckMetadataTransformsTask { // Check that this task is current if (taskInstance.id !== this.getTaskId()) { // old task, die - throwUnrecoverableError(new Error('Outdated task version')); + this.logger.info( + `Outdated task version: Got [${ + taskInstance.id + }] from task instance. Current version is [${this.getTaskId()}]` + ); + return getDeleteTaskRunResult(); } const [{ elasticsearch }] = await core.getStartServices(); diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/response_actions/complete_external_actions_task.ts b/x-pack/plugins/security_solution/server/endpoint/lib/response_actions/complete_external_actions_task.ts index fd823928b6631..f7d2f58720743 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/response_actions/complete_external_actions_task.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/response_actions/complete_external_actions_task.ts @@ -11,7 +11,6 @@ import type { } from '@kbn/task-manager-plugin/server'; import type { Logger } from '@kbn/logging'; import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import { throwUnrecoverableError } from '@kbn/task-manager-plugin/server'; import { EndpointError } from '../../../../common/endpoint/errors'; import { CompleteExternalActionsTaskRunner } from './complete_external_actions_task_runner'; import type { EndpointAppContext } from '../../types'; @@ -90,14 +89,6 @@ export class CompleteExternalResponseActionsTask { ); } - if (taskInstance.id !== this.taskId) { - throwUnrecoverableError( - new EndpointError( - `Outdated task version. Got [${taskInstance.id}] from task instance. Current version is [${this.taskId}]` - ) - ); - } - const { id: taskId, taskType } = taskInstance; return new CompleteExternalActionsTaskRunner( diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/response_actions/complete_external_actions_task_runner.test.ts b/x-pack/plugins/security_solution/server/endpoint/lib/response_actions/complete_external_actions_task_runner.test.ts index 9497f24c8fc42..d37929c15a3f4 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/response_actions/complete_external_actions_task_runner.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/response_actions/complete_external_actions_task_runner.test.ts @@ -14,6 +14,11 @@ import { EndpointActionGenerator } from '../../../../common/endpoint/data_genera import { ENDPOINT_ACTION_RESPONSES_INDEX } from '../../../../common/endpoint/constants'; import { waitFor } from '@testing-library/react'; import { ResponseActionsConnectorNotConfiguredError } from '../../services/actions/clients/errors'; +import { + COMPLETE_EXTERNAL_RESPONSE_ACTIONS_TASK_TYPE, + COMPLETE_EXTERNAL_RESPONSE_ACTIONS_TASK_VERSION, +} from './complete_external_actions_task'; +import { getDeleteTaskRunResult } from '@kbn/task-manager-plugin/server/task'; describe('CompleteExternalTaskRunner class', () => { let endpointContextServicesMock: ReturnType; @@ -25,7 +30,9 @@ describe('CompleteExternalTaskRunner class', () => { esClientMock = elasticsearchServiceMock.createElasticsearchClient(); runnerInstance = new CompleteExternalActionsTaskRunner( endpointContextServicesMock, - esClientMock + esClientMock, + '60s', + `${COMPLETE_EXTERNAL_RESPONSE_ACTIONS_TASK_TYPE}-${COMPLETE_EXTERNAL_RESPONSE_ACTIONS_TASK_VERSION}` ); const actionGenerator = new EndpointActionGenerator('seed'); @@ -53,6 +60,22 @@ describe('CompleteExternalTaskRunner class', () => { ); }); + it('should do nothing if task instance id is outdated', async () => { + runnerInstance = new CompleteExternalActionsTaskRunner( + endpointContextServicesMock, + esClientMock, + '60s', + 'old-id' + ); + const result = await runnerInstance.run(); + + expect(result).toEqual(getDeleteTaskRunResult()); + + expect(endpointContextServicesMock.createLogger().info).toHaveBeenCalledWith( + `Outdated task version. Got [old-id] from task instance. Current version is [endpoint:complete-external-response-actions-1.0.0]` + ); + }); + it('should NOT log an error if agentType is not configured with a connector', async () => { (endpointContextServicesMock.getInternalResponseActionsClient as jest.Mock).mockImplementation( () => { diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/response_actions/complete_external_actions_task_runner.ts b/x-pack/plugins/security_solution/server/endpoint/lib/response_actions/complete_external_actions_task_runner.ts index bb6fcc0c897d7..dbc57c2a55a84 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/response_actions/complete_external_actions_task_runner.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/response_actions/complete_external_actions_task_runner.ts @@ -6,6 +6,7 @@ */ import type { CancellableTask, RunContext, RunResult } from '@kbn/task-manager-plugin/server/task'; +import { getDeleteTaskRunResult } from '@kbn/task-manager-plugin/server/task'; import type { Logger, ElasticsearchClient } from '@kbn/core/server'; import type { BulkRequest } from '@elastic/elasticsearch/lib/api/types'; import { ResponseActionsConnectorNotConfiguredError } from '../../services/actions/clients/errors'; @@ -17,6 +18,10 @@ import { QueueProcessor } from '../../utils/queue_processor'; import type { LogsEndpointActionResponse } from '../../../../common/endpoint/types'; import type { EndpointAppContextService } from '../../endpoint_app_context_services'; import { ENDPOINT_ACTION_RESPONSES_INDEX } from '../../../../common/endpoint/constants'; +import { + COMPLETE_EXTERNAL_RESPONSE_ACTIONS_TASK_TYPE, + COMPLETE_EXTERNAL_RESPONSE_ACTIONS_TASK_VERSION, +} from './complete_external_actions_task'; /** * A task manager runner responsible for checking the status of and completing pending actions @@ -34,7 +39,7 @@ export class CompleteExternalActionsTaskRunner private readonly endpointContextServices: EndpointAppContextService, private readonly esClient: ElasticsearchClient, private readonly nextRunInterval: string = '60s', - private readonly taskId?: string, + private readonly taskInstanceId?: string, private readonly taskType?: string ) { this.log = this.endpointContextServices.createLogger( @@ -49,6 +54,10 @@ export class CompleteExternalActionsTaskRunner }); } + private get taskId(): string { + return `${COMPLETE_EXTERNAL_RESPONSE_ACTIONS_TASK_TYPE}-${COMPLETE_EXTERNAL_RESPONSE_ACTIONS_TASK_VERSION}`; + } + private async queueBatchProcessor({ batch, data, @@ -94,6 +103,13 @@ export class CompleteExternalActionsTaskRunner } public async run(): Promise { + if (this.taskInstanceId !== this.taskId) { + this.log.info( + `Outdated task version. Got [${this.taskInstanceId}] from task instance. Current version is [${this.taskId}]` + ); + return getDeleteTaskRunResult(); + } + this.log.debug(`Started: Checking status of external response actions`); this.abortController = new AbortController(); @@ -118,7 +134,7 @@ export class CompleteExternalActionsTaskRunner this.endpointContextServices.getInternalResponseActionsClient({ agentType, taskType: this.taskType, - taskId: this.taskId, + taskId: this.taskInstanceId, }); return agentTypeActionsClient diff --git a/x-pack/plugins/security_solution_serverless/server/task_manager/nlp_cleanup_task/nlp_cleanup_task.ts b/x-pack/plugins/security_solution_serverless/server/task_manager/nlp_cleanup_task/nlp_cleanup_task.ts index 5a3ba158d6194..011e80bf02824 100644 --- a/x-pack/plugins/security_solution_serverless/server/task_manager/nlp_cleanup_task/nlp_cleanup_task.ts +++ b/x-pack/plugins/security_solution_serverless/server/task_manager/nlp_cleanup_task/nlp_cleanup_task.ts @@ -11,7 +11,7 @@ import type { TaskManagerSetupContract, TaskManagerStartContract, } from '@kbn/task-manager-plugin/server'; -import { throwUnrecoverableError } from '@kbn/task-manager-plugin/server'; +import { getDeleteTaskRunResult } from '@kbn/task-manager-plugin/server/task'; import type { Tier } from '../../types'; import { ProductTier } from '../../../common/product'; import { NLP_CLEANUP_TASK_EVENT } from '../../telemetry/event_based_telemetry'; @@ -78,9 +78,10 @@ export class NLPCleanupTask { return { run: async () => { if (this.productTier === ProductTier.complete) { - throwUnrecoverableError( - new Error('Task no longer needed for current productTier, disabling...') + this.logger.info( + `Task ${taskInstance.id} no longer needed for current productTier, disabling...` ); + return getDeleteTaskRunResult(); } return this.runTask(taskInstance, core); }, @@ -134,7 +135,7 @@ export class NLPCleanupTask { // Check that this task is current if (taskInstance.id !== this.taskId) { // old task, return - throwUnrecoverableError(new Error('Outdated task version')); + return getDeleteTaskRunResult(); } const [{ elasticsearch }] = await core.getStartServices(); diff --git a/x-pack/plugins/security_solution_serverless/server/task_manager/usage_reporting_task.test.ts b/x-pack/plugins/security_solution_serverless/server/task_manager/usage_reporting_task.test.ts index 1b5871e6f2a88..5d5a8524b5733 100644 --- a/x-pack/plugins/security_solution_serverless/server/task_manager/usage_reporting_task.test.ts +++ b/x-pack/plugins/security_solution_serverless/server/task_manager/usage_reporting_task.test.ts @@ -14,6 +14,7 @@ import type { } from '@kbn/task-manager-plugin/server'; import type { CloudSetup } from '@kbn/cloud-plugin/server'; import { TaskStatus } from '@kbn/task-manager-plugin/server'; +import { getDeleteTaskRunResult } from '@kbn/task-manager-plugin/server/task'; import { coreMock } from '@kbn/core/server/mocks'; import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; @@ -201,6 +202,15 @@ describe('SecurityUsageReportingTask', () => { ); }); + it('should do nothing if task instance id is outdated', async () => { + const result = await runTask({ ...buildMockTaskInstance(), id: 'old-id' }); + + expect(result).toEqual(getDeleteTaskRunResult()); + + expect(reportUsageSpy).not.toHaveBeenCalled(); + expect(meteringCallbackMock).not.toHaveBeenCalled(); + }); + describe('lastSuccessfulReport', () => { it('should set lastSuccessfulReport correctly if report success', async () => { reportUsageSpy.mockResolvedValueOnce({ status: 201 }); diff --git a/x-pack/plugins/security_solution_serverless/server/task_manager/usage_reporting_task.ts b/x-pack/plugins/security_solution_serverless/server/task_manager/usage_reporting_task.ts index 2d612708f5bf3..eb36adc77874e 100644 --- a/x-pack/plugins/security_solution_serverless/server/task_manager/usage_reporting_task.ts +++ b/x-pack/plugins/security_solution_serverless/server/task_manager/usage_reporting_task.ts @@ -8,8 +8,8 @@ import type { Response } from 'node-fetch'; import type { CoreSetup, Logger } from '@kbn/core/server'; import type { ConcreteTaskInstance } from '@kbn/task-manager-plugin/server'; +import { getDeleteTaskRunResult } from '@kbn/task-manager-plugin/server/task'; import type { CloudSetup } from '@kbn/cloud-plugin/server'; -import { throwUnrecoverableError } from '@kbn/task-manager-plugin/server'; import { usageReportingService } from '../common/services'; import type { @@ -114,7 +114,10 @@ export class SecurityUsageReportingTask { // Check that this task is current if (taskInstance.id !== this.taskId) { // old task, die - throwUnrecoverableError(new Error('Outdated task version')); + this.logger.info( + `Outdated task version: Got [${taskInstance.id}] from task instance. Current version is [${this.taskId}]` + ); + return getDeleteTaskRunResult(); } const [{ elasticsearch }] = await core.getStartServices(); diff --git a/x-pack/plugins/task_manager/server/task.ts b/x-pack/plugins/task_manager/server/task.ts index c239263a872b5..89c950dabf687 100644 --- a/x-pack/plugins/task_manager/server/task.ts +++ b/x-pack/plugins/task_manager/server/task.ts @@ -56,6 +56,7 @@ export type SuccessfulRunResult = { state: Record; taskRunError?: DecoratedError; shouldValidate?: boolean; + shouldDeleteTask?: boolean; } & ( | // ensure a SuccessfulRunResult can either specify a new `runAt` or a new `schedule`, but not both { @@ -88,6 +89,11 @@ export type FailedRunResult = SuccessfulRunResult & { export type RunResult = FailedRunResult | SuccessfulRunResult; +export const getDeleteTaskRunResult = () => ({ + state: {}, + shouldDeleteTask: true, +}); + export const isFailedRunResult = (result: unknown): result is FailedRunResult => !!((result as FailedRunResult)?.error ?? false); @@ -205,6 +211,7 @@ export enum TaskStatus { Claiming = 'claiming', Running = 'running', Failed = 'failed', + ShouldDelete = 'should_delete', Unrecognized = 'unrecognized', DeadLetter = 'dead_letter', } diff --git a/x-pack/plugins/task_manager/server/task_running/task_runner.test.ts b/x-pack/plugins/task_manager/server/task_running/task_runner.test.ts index c68a06af0b1f0..9274e0583547e 100644 --- a/x-pack/plugins/task_manager/server/task_running/task_runner.test.ts +++ b/x-pack/plugins/task_manager/server/task_running/task_runner.test.ts @@ -24,7 +24,7 @@ import { TaskPersistence, asTaskManagerStatEvent, } from '../task_events'; -import { ConcreteTaskInstance, TaskStatus } from '../task'; +import { ConcreteTaskInstance, getDeleteTaskRunResult, TaskStatus } from '../task'; import { SavedObjectsErrorHelpers } from '@kbn/core/server'; import moment from 'moment'; import { TaskDefinitionRegistry, TaskTypeDictionary } from '../task_type_dictionary'; @@ -1140,6 +1140,58 @@ describe('TaskManagerRunner', () => { expect(onTaskEvent).toHaveBeenCalledTimes(2); }); + test(`doesn't reschedule recurring tasks that return shouldDeleteTask = true`, async () => { + const id = _.random(1, 20).toString(); + const onTaskEvent = jest.fn(); + const { + runner, + store, + instance: originalInstance, + } = await readyToRunStageSetup({ + onTaskEvent, + instance: { + id, + schedule: { interval: '20m' }, + status: TaskStatus.Running, + startedAt: new Date(), + enabled: true, + }, + definitions: { + bar: { + title: 'Bar!', + createTaskRunner: () => ({ + async run() { + return getDeleteTaskRunResult(); + }, + }), + }, + }, + }); + + await runner.run(); + + expect(store.remove).toHaveBeenCalled(); + expect(store.update).not.toHaveBeenCalled(); + + expect(onTaskEvent).toHaveBeenCalledWith( + withAnyTiming( + asTaskRunEvent( + id, + asOk({ + persistence: TaskPersistence.Recurring, + task: originalInstance, + result: TaskRunResult.Deleted, + isExpired: false, + }) + ) + ) + ); + expect(onTaskEvent).toHaveBeenCalledWith( + asTaskManagerStatEvent('runDelay', asOk(expect.any(Number))) + ); + expect(onTaskEvent).toHaveBeenCalledTimes(2); + }); + test('tasks that return runAt override the schedule', async () => { const runAt = minutesFromNow(_.random(5)); const { runner, store } = await readyToRunStageSetup({ diff --git a/x-pack/plugins/task_manager/server/task_running/task_runner.ts b/x-pack/plugins/task_manager/server/task_running/task_runner.ts index 0e0c1e1dfded0..fb73f295cf372 100644 --- a/x-pack/plugins/task_manager/server/task_running/task_runner.ts +++ b/x-pack/plugins/task_manager/server/task_running/task_runner.ts @@ -119,6 +119,8 @@ export enum TaskRunResult { RetryScheduled = 'RetryScheduled', // Task has failed Failed = 'Failed', + // Task deleted + Deleted = 'Deleted', } // A ConcreteTaskInstance which we *know* has a `startedAt` Date on it @@ -620,7 +622,13 @@ export class TaskManagerRunner implements TaskRunner { schedule: reschedule, state, attempts = 0, + shouldDeleteTask, }: SuccessfulRunResult & { attempts: number }) => { + if (shouldDeleteTask) { + // set the status to failed so task will get deleted + return asOk({ status: TaskStatus.ShouldDelete }); + } + const { startedAt, schedule } = this.instance.task; return asOk({ @@ -642,7 +650,10 @@ export class TaskManagerRunner implements TaskRunner { counterType: 'taskManagerTaskRunner', incrementBy: 1, }); - } else if (fieldUpdates.status === TaskStatus.Failed) { + } else if ( + fieldUpdates.status === TaskStatus.Failed || + fieldUpdates.status === TaskStatus.ShouldDelete + ) { // Delete the SO instead so it doesn't remain in the index forever this.instance = asRan(this.instance.task); await this.removeTask(); @@ -667,6 +678,8 @@ export class TaskManagerRunner implements TaskRunner { return fieldUpdates.status === TaskStatus.Failed ? TaskRunResult.Failed + : fieldUpdates.status === TaskStatus.ShouldDelete + ? TaskRunResult.Deleted : hasTaskRunFailed ? TaskRunResult.SuccessRescheduled : TaskRunResult.RetryScheduled; diff --git a/x-pack/plugins/task_manager/server/task_store.test.ts b/x-pack/plugins/task_manager/server/task_store.test.ts index 69179366d574f..68cc11b7da28e 100644 --- a/x-pack/plugins/task_manager/server/task_store.test.ts +++ b/x-pack/plugins/task_manager/server/task_store.test.ts @@ -910,7 +910,7 @@ describe('TaskStore', () => { describe('getLifecycle', () => { test('returns the task status if the task exists ', async () => { - expect.assertions(6); + expect.assertions(7); return Promise.all( Object.values(TaskStatus).map(async (status) => { const task = { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/action_type_registry.mock.ts b/x-pack/plugins/triggers_actions_ui/public/application/action_type_registry.mock.ts index ad1bc979ba239..b169d37614ab3 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/action_type_registry.mock.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/action_type_registry.mock.ts @@ -5,40 +5,4 @@ * 2.0. */ -import React, { lazy } from 'react'; -import { v4 as uuidv4 } from 'uuid'; -import { ActionTypeModel, ActionTypeRegistryContract } from '../types'; - -const createActionTypeRegistryMock = () => { - const mocked: jest.Mocked = { - has: jest.fn((x) => true), - register: jest.fn(), - get: jest.fn(), - list: jest.fn(), - }; - return mocked; -}; - -const mockedActionParamsFields = lazy(async () => ({ - default() { - return React.createElement(React.Fragment); - }, -})); - -const createMockActionTypeModel = (actionType: Partial = {}): ActionTypeModel => { - const id = uuidv4(); - return { - id, - iconClass: `iconClass-${id}`, - selectMessage: `selectMessage-${id}`, - validateParams: jest.fn(), - actionConnectorFields: null, - actionParamsFields: mockedActionParamsFields, - ...actionType, - }; -}; - -export const actionTypeRegistryMock = { - create: createActionTypeRegistryMock, - createMockActionTypeModel, -}; +export { actionTypeRegistryMock } from '@kbn/alerts-ui-shared/src/common/test_utils/action_type_registry.mock'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.tsx index 909b347b38289..2dd71c7ee1773 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.tsx @@ -15,13 +15,13 @@ import { i18n } from '@kbn/i18n'; import { EuiEmptyPrompt } from '@elastic/eui'; import { DocLinksStart, HttpSetup } from '@kbn/core/public'; -import { AlertingFrameworkHealth } from '@kbn/alerting-plugin/common'; +import { AlertingFrameworkHealth } from '@kbn/alerting-types'; import './health_check.scss'; +import { fetchUiHealthStatus as triggersActionsUiHealth } from '@kbn/alerts-ui-shared/src/common/apis/fetch_ui_health_status'; +import { fetchAlertingFrameworkHealth as alertingFrameworkHealth } from '@kbn/alerts-ui-shared/src/common/apis/fetch_alerting_framework_health'; import { useHealthContext } from '../context/health_context'; import { useKibana } from '../../common/lib/kibana'; import { CenterJustifiedSpinner } from './center_justified_spinner'; -import { triggersActionsUiHealth } from '../../common/lib/health_api'; -import { alertingFrameworkHealth } from '../lib/rule_api/health'; interface Props { inFlyout?: boolean; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_config_query.ts b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_config_query.ts index d852bde4c91f7..bc3420ecf346c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_config_query.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_config_query.ts @@ -5,8 +5,8 @@ * 2.0. */ import { useQuery } from '@tanstack/react-query'; +import { fetchUiConfig as triggersActionsUiConfig } from '@kbn/alerts-ui-shared/src/common/apis/fetch_ui_config'; import { useKibana } from '../../common/lib/kibana'; -import { triggersActionsUiConfig } from '../../common/lib/config_api'; export const useLoadConfigQuery = () => { const { http } = useKibana().services; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/health.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/health.ts deleted file mode 100644 index 80962de1b17f8..0000000000000 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/health.ts +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import { HttpSetup } from '@kbn/core/public'; -import { AsApiContract, RewriteRequestCase } from '@kbn/actions-plugin/common'; -import { AlertingFrameworkHealth, AlertsHealth } from '@kbn/alerting-plugin/common'; -import { BASE_ALERTING_API_PATH } from '../../constants'; - -const rewriteAlertingFrameworkHealth: RewriteRequestCase = ({ - decryption_health: decryptionHealth, - execution_health: executionHealth, - read_health: readHealth, - ...res -}: AsApiContract) => ({ - decryptionHealth, - executionHealth, - readHealth, - ...res, -}); - -const rewriteBodyRes: RewriteRequestCase = ({ - is_sufficiently_secure: isSufficientlySecure, - has_permanent_encryption_key: hasPermanentEncryptionKey, - // eslint-disable-next-line @typescript-eslint/no-shadow - alerting_framework_health: alertingFrameworkHealth, - ...res -}: AsApiContract) => ({ - isSufficientlySecure, - hasPermanentEncryptionKey, - alertingFrameworkHealth, - ...res, -}); - -export async function alertingFrameworkHealth({ - http, -}: { - http: HttpSetup; -}): Promise { - const res = await http.get>( - `${BASE_ALERTING_API_PATH}/_health` - ); - const alertingFrameworkHealthRewrited = rewriteAlertingFrameworkHealth( - res.alerting_framework_health as unknown as AsApiContract - ); - return { - ...rewriteBodyRes(res), - alertingFrameworkHealth: alertingFrameworkHealthRewrited, - }; -} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/test_connector_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/test_connector_form.tsx index e04a0d6e42a1f..a565f2ae56982 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/test_connector_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/test_connector_form.tsx @@ -60,7 +60,7 @@ export const TestConnectorForm = ({ (async () => { const res = (await actionTypeModel?.validateParams(actionParams)).errors as IErrorObject; setActionErrors({ ...res }); - setHasErrors(!!Object.values(res).find((errors) => errors.length > 0)); + setHasErrors(!!Object.values(res).find((errors) => (errors.length as number) > 0)); })(); }, [actionTypeModel, actionParams]); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_rule_api_operations.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_rule_api_operations.test.tsx index 5a283ff60ec72..17f9fe336609e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_rule_api_operations.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_rule_api_operations.test.tsx @@ -40,7 +40,7 @@ jest.mock('../../../lib/rule_api/bulk_disable', () => ({ jest.mock('../../../lib/rule_api/get_rule', () => ({ loadRule: jest.fn(), })); -jest.mock('../../../lib/rule_api/resolve_rule', () => ({ +jest.mock('@kbn/alerts-ui-shared/src/common/apis/resolve_rule', () => ({ resolveRule: jest.fn(), })); jest.mock('../../../lib/rule_api/rule_types', () => ({ @@ -59,7 +59,7 @@ const { bulkDeleteRules } = jest.requireMock('../../../lib/rule_api/bulk_delete' const { bulkEnableRules } = jest.requireMock('../../../lib/rule_api/bulk_enable'); const { bulkDisableRules } = jest.requireMock('../../../lib/rule_api/bulk_disable'); const { loadRule } = jest.requireMock('../../../lib/rule_api/get_rule'); -const { resolveRule } = jest.requireMock('../../../lib/rule_api/resolve_rule'); +const { resolveRule } = jest.requireMock('@kbn/alerts-ui-shared/src/common/apis/resolve_rule'); const { loadRuleTypes } = jest.requireMock('../../../lib/rule_api/rule_types'); const { loadActionErrorLog } = jest.requireMock('../../../lib/rule_api/load_action_error_log'); @@ -283,7 +283,7 @@ describe('with_bulk_rule_api_operations', () => { component.find('button').simulate('click'); expect(resolveRule).toHaveBeenCalledTimes(1); - expect(resolveRule).toHaveBeenCalledWith({ ruleId, http }); + expect(resolveRule).toHaveBeenCalledWith({ id: ruleId, http }); }); it('loadRuleTypes calls the loadRuleTypes api', () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_rule_api_operations.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_rule_api_operations.tsx index 81a4f27ef5e1c..60ceeaf24d516 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_rule_api_operations.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_rule_api_operations.tsx @@ -12,12 +12,14 @@ import { IExecutionErrorsResult, IExecutionKPIResult, } from '@kbn/alerting-plugin/common'; +import { AlertingFrameworkHealth } from '@kbn/alerting-types'; +import { fetchAlertingFrameworkHealth as alertingFrameworkHealth } from '@kbn/alerts-ui-shared/src/common/apis/fetch_alerting_framework_health'; +import { resolveRule } from '@kbn/alerts-ui-shared/src/common/apis/resolve_rule'; import { Rule, RuleType, RuleTaskState, RuleSummary, - AlertingFrameworkHealth, ResolvedRule, SnoozeSchedule, BulkEditResponse, @@ -34,7 +36,6 @@ import type { LoadGlobalExecutionKPIAggregationsProps, BulkUnsnoozeRulesProps, } from '../../../lib/rule_api'; -import { alertingFrameworkHealth } from '../../../lib/rule_api/health'; import { cloneRule } from '../../../lib/rule_api/clone'; import { loadRule } from '../../../lib/rule_api/get_rule'; import { loadRuleSummary } from '../../../lib/rule_api/rule_summary'; @@ -51,7 +52,6 @@ import { loadExecutionKPIAggregations } from '../../../lib/rule_api/load_executi import { loadGlobalExecutionKPIAggregations } from '../../../lib/rule_api/load_global_execution_kpi_aggregations'; import { loadActionErrorLog } from '../../../lib/rule_api/load_action_error_log'; import { unmuteAlertInstance } from '../../../lib/rule_api/unmute_alert'; -import { resolveRule } from '../../../lib/rule_api/resolve_rule'; import { snoozeRule, bulkSnoozeRules } from '../../../lib/rule_api/snooze'; import { unsnoozeRule, bulkUnsnoozeRules } from '../../../lib/rule_api/unsnooze'; import { bulkDeleteRules } from '../../../lib/rule_api/bulk_delete'; @@ -177,7 +177,7 @@ export function withBulkRuleOperations( http, }) } - resolveRule={async (ruleId: Rule['id']) => resolveRule({ http, ruleId })} + resolveRule={async (ruleId: Rule['id']) => resolveRule({ http, id: ruleId })} getHealth={async () => alertingFrameworkHealth({ http })} snoozeRule={async (rule: Rule, snoozeSchedule: SnoozeSchedule) => { return await snoozeRule({ http, id: rule.id, snoozeSchedule }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.test.tsx index b9cac4bd33b08..b5d12cd6b9caa 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.test.tsx @@ -23,8 +23,8 @@ import { ruleTypeRegistryMock } from '../../../rule_type_registry.mock'; jest.mock('../../../../common/lib/kibana'); -jest.mock('../../../../common/lib/config_api', () => ({ - triggersActionsUiConfig: jest +jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_ui_config', () => ({ + fetchUiConfig: jest .fn() .mockResolvedValue({ minimumScheduleInterval: { value: '1m', enforce: false } }), })); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.tsx index 7421bec047e03..a9e0be99d42ba 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.tsx @@ -27,6 +27,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { toMountPoint } from '@kbn/react-kibana-mount'; import { RuleExecutionStatusErrorReasons, parseDuration } from '@kbn/alerting-plugin/common'; import { getRuleDetailsRoute } from '@kbn/rule-data-utils'; +import { fetchUiConfig as triggersActionsUiConfig } from '@kbn/alerts-ui-shared/src/common/apis/fetch_ui_config'; import { UpdateApiKeyModalConfirmation } from '../../../components/update_api_key_modal_confirmation'; import { bulkUpdateAPIKey } from '../../../lib/rule_api/update_api_key'; import { RulesDeleteModalConfirmation } from '../../../components/rules_delete_modal_confirmation'; @@ -60,7 +61,6 @@ import { import { useKibana } from '../../../../common/lib/kibana'; import { getRuleReducer } from '../../rule_form/rule_reducer'; import { loadAllActions as loadConnectors } from '../../../lib/action_connector_api'; -import { triggersActionsUiConfig } from '../../../../common/lib/config_api'; import { runRule } from '../../../lib/run_rule'; import { getConfirmDeletionButtonText, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details_route.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details_route.test.tsx index a04e0b2e14df7..885b7fc0ec67e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details_route.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details_route.test.tsx @@ -19,8 +19,8 @@ import { spacesPluginMock } from '@kbn/spaces-plugin/public/mocks'; import { useKibana } from '../../../../common/lib/kibana'; jest.mock('../../../../common/lib/kibana'); -jest.mock('../../../../common/lib/config_api', () => ({ - triggersActionsUiConfig: jest +jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_ui_config', () => ({ + fetchUiConfig: jest .fn() .mockResolvedValue({ minimumScheduleInterval: { value: '1m', enforce: false } }), })); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_add.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_add.test.tsx index 043d102b4cf90..5c5e8b8ca7d67 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_add.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_add.test.tsx @@ -13,8 +13,9 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { EuiFormLabel } from '@elastic/eui'; import { coreMock } from '@kbn/core/public/mocks'; import RuleAdd from './rule_add'; -import { createRule } from '../../lib/rule_api/create'; -import { alertingFrameworkHealth } from '../../lib/rule_api/health'; +import { createRule } from '@kbn/alerts-ui-shared/src/common/apis/create_rule'; + +import { fetchAlertingFrameworkHealth as fetchAlertingFrameworkHealth } from '@kbn/alerts-ui-shared/src/common/apis/fetch_alerting_framework_health'; import { actionTypeRegistryMock } from '../../action_type_registry.mock'; import { AlertConsumers, OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/rule-data-utils'; import { @@ -31,8 +32,9 @@ import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { ReactWrapper } from 'enzyme'; import { ALERTING_FEATURE_ID } from '@kbn/alerting-plugin/common'; import { useKibana } from '../../../common/lib/kibana'; -import { triggersActionsUiConfig } from '../../../common/lib/config_api'; -import { triggersActionsUiHealth } from '../../../common/lib/health_api'; + +import { fetchUiConfig } from '@kbn/alerts-ui-shared/src/common/apis/fetch_ui_config'; +import { fetchUiHealthStatus } from '@kbn/alerts-ui-shared/src/common/apis/fetch_ui_health_status'; import { loadActionTypes, loadAllActions } from '../../lib/action_connector_api'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { waitFor } from '@testing-library/react'; @@ -41,22 +43,22 @@ jest.mock('../../../common/lib/kibana'); jest.mock('../../lib/rule_api/rule_types', () => ({ loadRuleTypes: jest.fn(), })); -jest.mock('../../lib/rule_api/create', () => ({ +jest.mock('@kbn/alerts-ui-shared/src/common/apis/create_rule', () => ({ createRule: jest.fn(), })); -jest.mock('../../lib/rule_api/health', () => ({ - alertingFrameworkHealth: jest.fn(() => ({ +jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_alerting_framework_health', () => ({ + fetchAlertingFrameworkHealth: jest.fn(() => ({ isSufficientlySecure: true, hasPermanentEncryptionKey: true, })), })); -jest.mock('../../../common/lib/config_api', () => ({ - triggersActionsUiConfig: jest.fn(), +jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_ui_config', () => ({ + fetchUiConfig: jest.fn(), })); -jest.mock('../../../common/lib/health_api', () => ({ - triggersActionsUiHealth: jest.fn(() => ({ isRulesAvailable: true })), +jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_ui_health_status', () => ({ + fetchUiHealthStatus: jest.fn(() => ({ isRulesAvailable: true })), })); jest.mock('../../lib/action_connector_api', () => ({ @@ -219,7 +221,7 @@ describe.skip('rule_add', () => { } it('renders rule add flyout', async () => { - (triggersActionsUiConfig as jest.Mock).mockResolvedValue({ + (fetchUiConfig as jest.Mock).mockResolvedValue({ minimumScheduleInterval: { value: '1m', enforce: false }, }); const onClose = jest.fn(); @@ -245,7 +247,7 @@ describe.skip('rule_add', () => { }); it('renders selection of rule types to pick in the modal', async () => { - (triggersActionsUiConfig as jest.Mock).mockResolvedValue({ + (fetchUiConfig as jest.Mock).mockResolvedValue({ minimumScheduleInterval: { value: '1m', enforce: false }, }); const onClose = jest.fn(); @@ -271,7 +273,7 @@ describe.skip('rule_add', () => { }); it('renders a confirm close modal if the flyout is closed after inputs have changed', async () => { - (triggersActionsUiConfig as jest.Mock).mockResolvedValue({ + (fetchUiConfig as jest.Mock).mockResolvedValue({ minimumScheduleInterval: { value: '1m', enforce: false }, }); const onClose = jest.fn(); @@ -303,7 +305,7 @@ describe.skip('rule_add', () => { }); it('renders rule add flyout with initial values', async () => { - (triggersActionsUiConfig as jest.Mock).mockResolvedValue({ + (fetchUiConfig as jest.Mock).mockResolvedValue({ minimumScheduleInterval: { value: '1m', enforce: false }, }); const onClose = jest.fn(); @@ -326,7 +328,7 @@ describe.skip('rule_add', () => { }); it('renders rule add flyout with DEFAULT_RULE_INTERVAL if no initialValues specified and no minimumScheduleInterval', async () => { - (triggersActionsUiConfig as jest.Mock).mockResolvedValue({}); + (fetchUiConfig as jest.Mock).mockResolvedValue({}); await setup({ ruleTypeId: 'my-rule-type' }); expect(wrapper.find('[data-test-subj="intervalInput"]').first().props().value).toEqual(1); @@ -334,7 +336,7 @@ describe.skip('rule_add', () => { }); it('renders rule add flyout with minimumScheduleInterval if minimumScheduleInterval is greater than DEFAULT_RULE_INTERVAL', async () => { - (triggersActionsUiConfig as jest.Mock).mockResolvedValue({ + (fetchUiConfig as jest.Mock).mockResolvedValue({ minimumScheduleInterval: { value: '5m', enforce: false }, }); await setup({ ruleTypeId: 'my-rule-type' }); @@ -344,7 +346,7 @@ describe.skip('rule_add', () => { }); it('emit an onClose event when the rule is saved', async () => { - (triggersActionsUiConfig as jest.Mock).mockResolvedValue({ + (fetchUiConfig as jest.Mock).mockResolvedValue({ minimumScheduleInterval: { value: '1m', enforce: false }, }); const onClose = jest.fn(); @@ -379,7 +381,7 @@ describe.skip('rule_add', () => { }); it('should set consumer automatically if only 1 authorized consumer exists', async () => { - (triggersActionsUiConfig as jest.Mock).mockResolvedValue({ + (fetchUiConfig as jest.Mock).mockResolvedValue({ minimumScheduleInterval: { value: '1m', enforce: false }, }); const onClose = jest.fn(); @@ -460,7 +462,7 @@ describe.skip('rule_add', () => { }); it('should enforce any default interval', async () => { - (triggersActionsUiConfig as jest.Mock).mockResolvedValue({ + (fetchUiConfig as jest.Mock).mockResolvedValue({ minimumScheduleInterval: { value: '1m', enforce: false }, }); await setup({ @@ -490,7 +492,7 @@ describe.skip('rule_add', () => { }); it('should load connectors and connector types when there is a pre-selected rule type', async () => { - (triggersActionsUiConfig as jest.Mock).mockResolvedValue({ + (fetchUiConfig as jest.Mock).mockResolvedValue({ minimumScheduleInterval: { value: '1m', enforce: false }, }); @@ -508,18 +510,18 @@ describe.skip('rule_add', () => { }); await waitFor(() => { - expect(triggersActionsUiHealth).toHaveBeenCalledTimes(1); - expect(alertingFrameworkHealth).toHaveBeenCalledTimes(1); + expect(fetchUiHealthStatus).toHaveBeenCalledTimes(1); + expect(fetchAlertingFrameworkHealth).toHaveBeenCalledTimes(1); expect(loadActionTypes).toHaveBeenCalledTimes(1); expect(loadAllActions).toHaveBeenCalledTimes(1); }); }); it('should not load connectors and connector types when there is not an encryptionKey', async () => { - (triggersActionsUiConfig as jest.Mock).mockResolvedValue({ + (fetchUiConfig as jest.Mock).mockResolvedValue({ minimumScheduleInterval: { value: '1m', enforce: false }, }); - (alertingFrameworkHealth as jest.Mock).mockResolvedValue({ + (fetchAlertingFrameworkHealth as jest.Mock).mockResolvedValue({ isSufficientlySecure: true, hasPermanentEncryptionKey: false, }); @@ -538,8 +540,8 @@ describe.skip('rule_add', () => { }); await waitFor(() => { - expect(triggersActionsUiHealth).toHaveBeenCalledTimes(1); - expect(alertingFrameworkHealth).toHaveBeenCalledTimes(1); + expect(fetchUiHealthStatus).toHaveBeenCalledTimes(1); + expect(fetchAlertingFrameworkHealth).toHaveBeenCalledTimes(1); expect(loadActionTypes).not.toHaveBeenCalled(); expect(loadAllActions).not.toHaveBeenCalled(); expect(wrapper.find('[data-test-subj="actionNeededEmptyPrompt"]').first().text()).toContain( diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_add.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_add.tsx index d932a62659483..a623c04eeea6f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_add.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_add.tsx @@ -12,6 +12,8 @@ import { i18n } from '@kbn/i18n'; import { isEmpty } from 'lodash'; import { toMountPoint } from '@kbn/react-kibana-mount'; import { parseRuleCircuitBreakerErrorMessage } from '@kbn/alerting-plugin/common'; +import { createRule, CreateRuleBody } from '@kbn/alerts-ui-shared/src/common/apis/create_rule'; +import { fetchUiConfig as triggersActionsUiConfig } from '@kbn/alerts-ui-shared/src/common/apis/fetch_ui_config'; import { Rule, RuleTypeParams, @@ -27,7 +29,6 @@ import { import { RuleForm } from './rule_form'; import { getRuleActionErrors, getRuleErrors, isValidRule } from './rule_errors'; import { InitialRule, getRuleReducer } from './rule_reducer'; -import { createRule } from '../../lib/rule_api/create'; import { loadRuleTypes } from '../../lib/rule_api/rule_types'; import { HealthCheck } from '../../components/health_check'; import { ConfirmRuleSave } from './confirm_rule_save'; @@ -39,7 +40,6 @@ import { useKibana } from '../../../common/lib/kibana'; import { hasRuleChanged, haveRuleParamsChanged } from './has_rule_changed'; import { getRuleWithInvalidatedFields } from '../../lib/value_validators'; import { DEFAULT_RULE_INTERVAL, MULTI_CONSUMER_RULE_TYPE_IDS } from '../../constants'; -import { triggersActionsUiConfig } from '../../../common/lib/config_api'; import { getInitialInterval } from './get_initial_interval'; import { ToastWithCircuitBreakerContent } from '../../components/toast_with_circuit_breaker_content'; import { ShowRequestModal } from './show_request_modal'; @@ -258,7 +258,7 @@ const RuleAdd = < rule: { ...rule, ...(selectableConsumer && selectedConsumer ? { consumer: selectedConsumer } : {}), - } as RuleUpdates, + } as CreateRuleBody, }); toasts.addSuccess( i18n.translate('xpack.triggersActionsUI.sections.ruleAdd.saveSuccessNotificationText', { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_edit.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_edit.test.tsx index 5bf56aec01977..0109ba0acec19 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_edit.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_edit.test.tsx @@ -25,18 +25,18 @@ const useKibanaMock = useKibana as jest.Mocked; jest.mock('../../lib/rule_api/rule_types', () => ({ loadRuleTypes: jest.fn(), })); -jest.mock('../../lib/rule_api/update', () => ({ +jest.mock('@kbn/alerts-ui-shared/src/common/apis/update_rule', () => ({ updateRule: jest.fn().mockRejectedValue({ body: { message: 'Fail message' } }), })); -jest.mock('../../lib/rule_api/health', () => ({ - alertingFrameworkHealth: jest.fn(() => ({ +jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_alerting_framework_health', () => ({ + fetchAlertingFrameworkHealth: jest.fn(() => ({ isSufficientlySecure: true, hasPermanentEncryptionKey: true, })), })); -jest.mock('../../../common/lib/config_api', () => ({ - triggersActionsUiConfig: jest.fn().mockResolvedValue({ +jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_ui_config', () => ({ + fetchUiConfig: jest.fn().mockResolvedValue({ isUsingSecurity: true, minimumScheduleInterval: { value: '1m', enforce: false }, }), @@ -59,8 +59,8 @@ jest.mock('./rule_errors', () => ({ isValidRule: jest.fn(), })); -jest.mock('../../../common/lib/health_api', () => ({ - triggersActionsUiHealth: jest.fn(() => ({ isRulesAvailable: true })), +jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_ui_health_status', () => ({ + fetchUiHealthStatus: jest.fn(() => ({ isRulesAvailable: true })), })); describe('rule_edit', () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_edit.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_edit.tsx index 17e595d51498e..409f3cf39400a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_edit.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_edit.tsx @@ -28,6 +28,8 @@ import { cloneDeep, omit } from 'lodash'; import { i18n } from '@kbn/i18n'; import { toMountPoint } from '@kbn/react-kibana-mount'; import { parseRuleCircuitBreakerErrorMessage } from '@kbn/alerting-plugin/common'; +import { updateRule } from '@kbn/alerts-ui-shared/src/common/apis/update_rule'; +import { fetchUiConfig as triggersActionsUiConfig } from '@kbn/alerts-ui-shared/src/common/apis/fetch_ui_config'; import { Rule, RuleFlyoutCloseReason, @@ -44,7 +46,6 @@ import { import { RuleForm } from './rule_form'; import { getRuleActionErrors, getRuleErrors, isValidRule } from './rule_errors'; import { getRuleReducer } from './rule_reducer'; -import { updateRule } from '../../lib/rule_api/update'; import { loadRuleTypes } from '../../lib/rule_api/rule_types'; import { HealthCheck } from '../../components/health_check'; import { HealthContextProvider } from '../../context/health_context'; @@ -52,7 +53,6 @@ import { useKibana } from '../../../common/lib/kibana'; import { ConfirmRuleClose } from './confirm_rule_close'; import { hasRuleChanged } from './has_rule_changed'; import { getRuleWithInvalidatedFields } from '../../lib/value_validators'; -import { triggersActionsUiConfig } from '../../../common/lib/config_api'; import { ToastWithCircuitBreakerContent } from '../../components/toast_with_circuit_breaker_content'; import { ShowRequestModal } from './show_request_modal'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/show_request_modal.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/show_request_modal.tsx index 864899cf93d9d..f987243b436db 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/show_request_modal.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/show_request_modal.tsx @@ -18,14 +18,14 @@ import { EuiFlexGroup, EuiFlexItem, } from '@elastic/eui'; +import { + transformUpdateRuleBody as rewriteUpdateBodyRequest, + UPDATE_FIELDS, +} from '@kbn/alerts-ui-shared/src/common/apis/update_rule'; +import { transformCreateRuleBody as rewriteCreateBodyRequest } from '@kbn/alerts-ui-shared/src/common/apis/create_rule'; import * as i18n from '../translations'; import { RuleUpdates } from '../../../types'; import { BASE_ALERTING_API_PATH } from '../../constants'; -import { rewriteBodyRequest as rewriteCreateBodyRequest } from '../../lib/rule_api/create'; -import { - rewriteBodyRequest as rewriteUpdateBodyRequest, - UPDATE_FIELDS, -} from '../../lib/rule_api/update'; const stringify = (rule: RuleUpdates, edit: boolean): string => { try { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.test.tsx index 33d7ec7a11ca2..363c348407bfe 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.test.tsx @@ -81,8 +81,8 @@ jest.mock('../../../lib/rule_api/bulk_delete', () => ({ jest.mock('../../../lib/rule_api/update_api_key', () => ({ bulkUpdateAPIKey: jest.fn(), })); -jest.mock('../../../lib/rule_api/health', () => ({ - alertingFrameworkHealth: jest.fn(() => ({ +jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_alerting_framework_health', () => ({ + fetchAlertingFrameworkHealth: jest.fn(() => ({ isSufficientlySecure: true, hasPermanentEncryptionKey: true, })), @@ -91,11 +91,11 @@ jest.mock('../../../lib/rule_api/health', () => ({ jest.mock('../../../lib/rule_api/aggregate_kuery_filter'); jest.mock('../../../lib/rule_api/rules_kuery_filter'); -jest.mock('../../../../common/lib/health_api', () => ({ - triggersActionsUiHealth: jest.fn(() => ({ isRulesAvailable: true })), +jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_ui_health_status', () => ({ + fetchUiHealthStatus: jest.fn(() => ({ isRulesAvailable: true })), })); -jest.mock('../../../../common/lib/config_api', () => ({ - triggersActionsUiConfig: jest +jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_ui_config', () => ({ + fetchUiConfig: jest .fn() .mockResolvedValue({ minimumScheduleInterval: { value: '1m', enforce: false } }), })); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_delete.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_delete.test.tsx index f5cf07b7d0dac..8dc84a6ffc5dd 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_delete.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_delete.test.tsx @@ -56,8 +56,8 @@ jest.mock('../../../lib/rule_api/aggregate', () => ({ jest.mock('../../../lib/rule_api/bulk_delete', () => ({ bulkDeleteRules: jest.fn().mockResolvedValue({ errors: [], total: 10 }), })); -jest.mock('../../../lib/rule_api/health', () => ({ - alertingFrameworkHealth: jest.fn(() => ({ +jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_alerting_framework_health', () => ({ + fetchAlertingFrameworkHealth: jest.fn(() => ({ isSufficientlySecure: true, hasPermanentEncryptionKey: true, })), @@ -66,11 +66,11 @@ jest.mock('../../../lib/rule_api/health', () => ({ jest.mock('../../../lib/rule_api/aggregate_kuery_filter'); jest.mock('../../../lib/rule_api/rules_kuery_filter'); -jest.mock('../../../../common/lib/health_api', () => ({ - triggersActionsUiHealth: jest.fn(() => ({ isRulesAvailable: true })), +jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_ui_health_status', () => ({ + fetchUiHealthStatus: jest.fn(() => ({ isRulesAvailable: true })), })); -jest.mock('../../../../common/lib/config_api', () => ({ - triggersActionsUiConfig: jest +jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_ui_config', () => ({ + fetchUiConfig: jest .fn() .mockResolvedValue({ minimumScheduleInterval: { value: '1m', enforce: false } }), })); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_disable.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_disable.test.tsx index 40aad0464e4f2..de5cfa4f74bd8 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_disable.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_disable.test.tsx @@ -55,19 +55,19 @@ jest.mock('../../../lib/rule_api/aggregate', () => ({ jest.mock('../../../lib/rule_api/bulk_disable', () => ({ bulkDisableRules: jest.fn().mockResolvedValue({ errors: [], total: 10 }), })); -jest.mock('../../../lib/rule_api/health', () => ({ - alertingFrameworkHealth: jest.fn(() => ({ +jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_alerting_framework_health', () => ({ + fetchAlertingFrameworkHealth: jest.fn(() => ({ isSufficientlySecure: true, hasPermanentEncryptionKey: true, })), })); jest.mock('../../../lib/rule_api/aggregate_kuery_filter'); jest.mock('../../../lib/rule_api/rules_kuery_filter'); -jest.mock('../../../../common/lib/health_api', () => ({ - triggersActionsUiHealth: jest.fn(() => ({ isRulesAvailable: true })), +jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_ui_health_status', () => ({ + fetchUiHealthStatus: jest.fn(() => ({ isRulesAvailable: true })), })); -jest.mock('../../../../common/lib/config_api', () => ({ - triggersActionsUiConfig: jest +jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_ui_config', () => ({ + fetchUiConfig: jest .fn() .mockResolvedValue({ minimumScheduleInterval: { value: '1m', enforce: false } }), })); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_edit.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_edit.test.tsx index 44d6e11d7ab95..48e16c053f51f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_edit.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_edit.test.tsx @@ -54,19 +54,19 @@ jest.mock('../../../lib/rule_api/unsnooze', () => ({ jest.mock('../../../lib/rule_api/update_api_key', () => ({ bulkUpdateAPIKey: jest.fn(), })); -jest.mock('../../../lib/rule_api/health', () => ({ - alertingFrameworkHealth: jest.fn(() => ({ +jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_alerting_framework_health', () => ({ + fetchAlertingFrameworkHealth: jest.fn(() => ({ isSufficientlySecure: true, hasPermanentEncryptionKey: true, })), })); jest.mock('../../../lib/rule_api/aggregate_kuery_filter'); jest.mock('../../../lib/rule_api/rules_kuery_filter'); -jest.mock('../../../../common/lib/health_api', () => ({ - triggersActionsUiHealth: jest.fn(() => ({ isRulesAvailable: true })), +jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_ui_health_status', () => ({ + fetchUiHealthStatus: jest.fn(() => ({ isRulesAvailable: true })), })); -jest.mock('../../../../common/lib/config_api', () => ({ - triggersActionsUiConfig: jest +jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_ui_config', () => ({ + fetchUiConfig: jest .fn() .mockResolvedValue({ minimumScheduleInterval: { value: '1m', enforce: false } }), })); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_enable.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_enable.test.tsx index e8e1bd8b99899..09ac26cf9d98b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_enable.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_enable.test.tsx @@ -55,19 +55,19 @@ jest.mock('../../../lib/rule_api/aggregate', () => ({ jest.mock('../../../lib/rule_api/bulk_enable', () => ({ bulkEnableRules: jest.fn().mockResolvedValue({ errors: [], total: 10 }), })); -jest.mock('../../../lib/rule_api/health', () => ({ - alertingFrameworkHealth: jest.fn(() => ({ +jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_alerting_framework_health', () => ({ + fetchAlertingFrameworkHealth: jest.fn(() => ({ isSufficientlySecure: true, hasPermanentEncryptionKey: true, })), })); jest.mock('../../../lib/rule_api/aggregate_kuery_filter'); jest.mock('../../../lib/rule_api/rules_kuery_filter'); -jest.mock('../../../../common/lib/health_api', () => ({ - triggersActionsUiHealth: jest.fn(() => ({ isRulesAvailable: true })), +jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_ui_health_status', () => ({ + fetchUiHealthStatus: jest.fn(() => ({ isRulesAvailable: true })), })); -jest.mock('../../../../common/lib/config_api', () => ({ - triggersActionsUiConfig: jest +jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_ui_config', () => ({ + fetchUiConfig: jest .fn() .mockResolvedValue({ minimumScheduleInterval: { value: '1m', enforce: false } }), })); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/type_registry.ts b/x-pack/plugins/triggers_actions_ui/public/application/type_registry.ts index 379470eca99d6..7c47b5d41e13f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/type_registry.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/type_registry.ts @@ -5,60 +5,4 @@ * 2.0. */ -import { i18n } from '@kbn/i18n'; - -interface BaseObjectType { - id: string; -} - -export class TypeRegistry { - private readonly objectTypes: Map = new Map(); - - /** - * Returns if the object type registry has the given type registered - */ - public has(id: string) { - return this.objectTypes.has(id); - } - - /** - * Registers an object type to the type registry - */ - public register(objectType: T) { - if (this.has(objectType.id)) { - throw new Error( - i18n.translate( - 'xpack.triggersActionsUI.typeRegistry.register.duplicateObjectTypeErrorMessage', - { - defaultMessage: 'Object type "{id}" is already registered.', - values: { - id: objectType.id, - }, - } - ) - ); - } - this.objectTypes.set(objectType.id, objectType); - } - - /** - * Returns an object type, throw error if not registered - */ - public get(id: string): T { - if (!this.has(id)) { - throw new Error( - i18n.translate('xpack.triggersActionsUI.typeRegistry.get.missingActionTypeErrorMessage', { - defaultMessage: 'Object type "{id}" is not registered.', - values: { - id, - }, - }) - ); - } - return this.objectTypes.get(id)!; - } - - public list() { - return Array.from(this.objectTypes).map(([id, objectType]) => objectType); - } -} +export { TypeRegistry } from '@kbn/alerts-ui-shared/src/common/type_registry'; diff --git a/x-pack/plugins/triggers_actions_ui/public/common/lib/config_api.ts b/x-pack/plugins/triggers_actions_ui/public/common/lib/config_api.ts deleted file mode 100644 index ceeae9271b5ba..0000000000000 --- a/x-pack/plugins/triggers_actions_ui/public/common/lib/config_api.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { HttpSetup } from '@kbn/core/public'; -import { BASE_TRIGGERS_ACTIONS_UI_API_PATH } from '../../../common'; -import { TriggersActionsUiConfig } from '../../types'; - -export async function triggersActionsUiConfig({ - http, -}: { - http: HttpSetup; -}): Promise { - return await http.get(`${BASE_TRIGGERS_ACTIONS_UI_API_PATH}/_config`); -} diff --git a/x-pack/plugins/triggers_actions_ui/public/common/lib/health_api.ts b/x-pack/plugins/triggers_actions_ui/public/common/lib/health_api.ts deleted file mode 100644 index 24a7324262316..0000000000000 --- a/x-pack/plugins/triggers_actions_ui/public/common/lib/health_api.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { HttpSetup } from '@kbn/core/public'; -import { BASE_TRIGGERS_ACTIONS_UI_API_PATH } from '../../../common'; - -interface TriggersActionsUiHealth { - isRulesAvailable: boolean; -} - -interface TriggersActionsServerHealth { - isAlertsAvailable: boolean; -} - -export async function triggersActionsUiHealth({ - http, -}: { - http: HttpSetup; -}): Promise { - const result = await http.get( - `${BASE_TRIGGERS_ACTIONS_UI_API_PATH}/_health` - ); - if (result) { - return { - isRulesAvailable: result.isAlertsAvailable, - }; - } - return result; -} diff --git a/x-pack/plugins/triggers_actions_ui/public/mocks.ts b/x-pack/plugins/triggers_actions_ui/public/mocks.ts index 5224a2e0d505f..e6d98240d2c5f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/mocks.ts +++ b/x-pack/plugins/triggers_actions_ui/public/mocks.ts @@ -7,6 +7,7 @@ import { RuleAction } from '@kbn/alerting-plugin/common'; import { chartPluginMock } from '@kbn/charts-plugin/public/mocks'; +import { TypeRegistry } from '@kbn/alerts-ui-shared/src/common/type_registry'; import { getAlertsTableDefaultAlertActionsLazy } from './common/get_alerts_table_default_row_actions'; import type { TriggersAndActionsUIPublicPluginStart } from './plugin'; @@ -14,7 +15,6 @@ import { getAddConnectorFlyoutLazy } from './common/get_add_connector_flyout'; import { getEditConnectorFlyoutLazy } from './common/get_edit_connector_flyout'; import { getAddRuleFlyoutLazy } from './common/get_add_rule_flyout'; import { getEditRuleFlyoutLazy } from './common/get_edit_rule_flyout'; -import { TypeRegistry } from './application/type_registry'; import { ActionTypeModel, RuleTypeModel, diff --git a/x-pack/plugins/triggers_actions_ui/public/plugin.ts b/x-pack/plugins/triggers_actions_ui/public/plugin.ts index 829e0bf364562..f3fd01e66856f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/plugin.ts +++ b/x-pack/plugins/triggers_actions_ui/public/plugin.ts @@ -31,10 +31,10 @@ import { ServerlessPluginStart } from '@kbn/serverless/public'; import { FieldFormatsRegistry } from '@kbn/field-formats-plugin/common'; import { LensPublicStart } from '@kbn/lens-plugin/public'; import { RuleAction } from '@kbn/alerting-plugin/common'; +import { TypeRegistry } from '@kbn/alerts-ui-shared/src/common/type_registry'; import { getAlertsTableDefaultAlertActionsLazy } from './common/get_alerts_table_default_row_actions'; import type { AlertActionsProps, RuleUiAction } from './types'; import type { AlertsSearchBarProps } from './application/sections/alerts_search_bar'; -import { TypeRegistry } from './application/type_registry'; import { getAddConnectorFlyoutLazy } from './common/get_add_connector_flyout'; import { getEditConnectorFlyoutLazy } from './common/get_edit_connector_flyout'; diff --git a/x-pack/plugins/triggers_actions_ui/public/types.ts b/x-pack/plugins/triggers_actions_ui/public/types.ts index f2a8d86eeb88a..ae57e56f3b6a2 100644 --- a/x-pack/plugins/triggers_actions_ui/public/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/types.ts @@ -15,8 +15,6 @@ import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import { FieldFormatsRegistry } from '@kbn/field-formats-plugin/common'; import type { - IconType, - RecursivePartial, EuiDataGridCellValueElementProps, EuiDataGridToolBarAdditionalControlsOptions, EuiDataGridProps, @@ -43,7 +41,6 @@ import { } from '@kbn/actions-plugin/common'; import { ActionGroup, - RuleActionParam, SanitizedRule as AlertingSanitizedRule, ResolvedSanitizedRule, RuleSystemAction, @@ -52,11 +49,9 @@ import { ExecutionDuration, AlertStatus, RawAlertInstance, - AlertingFrameworkHealth, RuleNotifyWhenType, RuleTypeParams, RuleTypeMetaData, - ActionVariable, RuleLastRun, MaintenanceWindow, SanitizedRuleAction as RuleAction, @@ -71,7 +66,13 @@ import { import React from 'react'; import { ActionsPublicPluginSetup } from '@kbn/actions-plugin/public'; import type { RuleType, RuleTypeIndex } from '@kbn/triggers-actions-ui-types'; -import { TypeRegistry } from './application/type_registry'; +import { + ValidationResult, + UserConfiguredActionConnector, + ActionConnector, + ActionTypeRegistryContract, +} from '@kbn/alerts-ui-shared/src/common/types'; +import { TypeRegistry } from '@kbn/alerts-ui-shared/src/common/type_registry'; import type { ComponentOpts as RuleStatusDropdownProps } from './application/sections/rules_list/components/rule_status_dropdown'; import type { RuleTagFilterProps } from './application/sections/rules_list/components/rule_tag_filter'; import type { RuleStatusFilterProps } from './application/sections/rules_list/components/rule_status_filter'; @@ -101,6 +102,26 @@ import type { RulesListNotifyBadgePropsWithApi } from './application/sections/ru import { Case } from './application/sections/alerts_table/hooks/apis/bulk_get_cases'; import { AlertTableConfigRegistry } from './application/alert_table_config_registry'; +export type { + GenericValidationResult, + ValidationResult, + ConnectorValidationError, + ConnectorValidationFunc, + ActionConnectorFieldsProps, + ActionConnectorProps, + SystemAction, + PreConfiguredActionConnector, + UserConfiguredActionConnector, + ActionConnector, + ActionParamsProps, + ActionReadOnlyElementProps, + CustomConnectorSelectionItem, + ActionTypeModel, + ActionTypeRegistryContract, +} from '@kbn/alerts-ui-shared/src/common/types'; + +export { ActionConnectorMode } from '@kbn/alerts-ui-shared/src/common/types'; + export type { ActionVariables, RuleType, RuleTypeIndex } from '@kbn/triggers-actions-ui-types'; export { @@ -139,7 +160,6 @@ export type { ExecutionDuration, AlertStatus, RawAlertInstance, - AlertingFrameworkHealth, RuleNotifyWhenType, RuleTypeParams, RuleTypeMetaData, @@ -175,28 +195,9 @@ export { }; export type ActionTypeIndex = Record; -export type ActionTypeRegistryContract< - ActionConnector = unknown, - ActionParams = unknown -> = PublicMethodsOf>>; export type RuleTypeRegistryContract = PublicMethodsOf>; export type AlertsTableConfigurationRegistryContract = PublicMethodsOf; -export interface ConnectorValidationError { - message: ReactNode; -} - -export type ConnectorValidationFunc = () => Promise; -export interface ActionConnectorFieldsProps { - readOnly: boolean; - isEdit: boolean; - registerPreSubmitValidator: (validator: ConnectorValidationFunc) => void; -} -export interface ActionReadOnlyElementProps { - connectorId: string; - connectorName: string; -} - export enum RuleFlyoutCloseReason { SAVED, CANCELED, @@ -208,11 +209,6 @@ export interface BulkEditResponse { total: number; } -export enum ActionConnectorMode { - Test = 'test', - ActionForm = 'actionForm', -} - export interface BulkOperationResponse { rules: Rule[]; errors: BulkOperationError[]; @@ -243,25 +239,6 @@ export type BulkDisableParams = BulkDisableParamsWithoutHttp & { http: HttpSetup; }; -export interface ActionParamsProps { - actionParams: Partial; - index: number; - editAction: (key: string, value: RuleActionParam, index: number) => void; - errors: IErrorObject; - ruleTypeId?: string; - messageVariables?: ActionVariable[]; - defaultMessage?: string; - useDefaultMessage?: boolean; - actionConnector?: ActionConnector; - isLoading?: boolean; - isDisabled?: boolean; - selectedActionGroupId?: string; - showEmailSubjectAndMessage?: boolean; - executionMode?: ActionConnectorMode; - onBlur?: (field?: string) => void; - producerId?: string; -} - export interface Pagination { index: number; size: number; @@ -272,86 +249,6 @@ export interface Sorting { direction: string; } -interface CustomConnectorSelectionItem { - getText: (actionConnector: ActionConnector) => string; - getComponent: ( - actionConnector: ActionConnector - ) => React.LazyExoticComponent> | undefined; -} - -export interface ActionTypeModel { - id: string; - iconClass: IconType; - selectMessage: string; - actionTypeTitle?: string; - validateParams: ( - actionParams: ActionParams - ) => Promise | unknown>>; - actionConnectorFields: React.LazyExoticComponent< - ComponentType - > | null; - actionParamsFields: React.LazyExoticComponent>>; - actionReadOnlyExtraComponent?: React.LazyExoticComponent< - ComponentType - >; - defaultActionParams?: RecursivePartial; - defaultRecoveredActionParams?: RecursivePartial; - customConnectorSelectItem?: CustomConnectorSelectionItem; - isExperimental?: boolean; - subtype?: Array<{ id: string; name: string }>; - convertParamsBetweenGroups?: (params: ActionParams) => ActionParams | {}; - hideInUi?: boolean; - modalWidth?: number; - isSystemActionType?: boolean; -} - -export interface GenericValidationResult { - errors: Record, string[] | unknown>; -} - -export interface ValidationResult { - errors: Record; -} - -export interface ActionConnectorProps { - secrets: Secrets; - id: string; - actionTypeId: string; - name: string; - referencedByCount?: number; - config: Config; - isPreconfigured: boolean; - isDeprecated: boolean; - isSystemAction: boolean; - isMissingSecrets?: boolean; -} - -export type PreConfiguredActionConnector = Omit< - ActionConnectorProps, - 'config' | 'secrets' -> & { - isPreconfigured: true; - isSystemAction: false; -}; - -export type UserConfiguredActionConnector = ActionConnectorProps< - Config, - Secrets -> & { - isPreconfigured: false; - isSystemAction: false; -}; - -export type SystemAction = Omit, 'config' | 'secrets'> & { - isSystemAction: true; - isPreconfigured: false; -}; - -export type ActionConnector, Secrets = Record> = - | PreConfiguredActionConnector - | SystemAction - | UserConfiguredActionConnector; - export type ActionConnectorWithoutId< Config = Record, Secrets = Record diff --git a/x-pack/test/functional/apps/discover/feature_controls/discover_security.ts b/x-pack/test/functional/apps/discover/feature_controls/discover_security.ts index e543116b956c2..bfd732622b785 100644 --- a/x-pack/test/functional/apps/discover/feature_controls/discover_security.ts +++ b/x-pack/test/functional/apps/discover/feature_controls/discover_security.ts @@ -5,7 +5,9 @@ * 2.0. */ +import { DISCOVER_APP_LOCATOR } from '@kbn/discover-plugin/common'; import expect from '@kbn/expect'; +import { decompressFromBase64 } from 'lz-string'; import { FtrProviderContext } from '../../../ftr_provider_context'; import { getSavedQuerySecurityUtils } from '../../saved_query_management/utils/saved_query_security'; @@ -35,6 +37,7 @@ export default function (ctx: FtrProviderContext) { const testSubjects = getService('testSubjects'); const appsMenu = getService('appsMenu'); const kibanaServer = getService('kibanaServer'); + const deployment = getService('deployment'); const logstashIndexName = 'logstash-2015.09.22'; async function setDiscoverTimeRange() { @@ -119,6 +122,19 @@ export default function (ctx: FtrProviderContext) { await globalNav.badgeMissingOrFail(); }); + it('Shows short urls for users with the right privileges', async () => { + let actualUrl: string = ''; + await PageObjects.share.clickShareTopNavButton(); + const re = new RegExp( + deployment.getHostPort().replace(':80', '').replace(':443', '') + '/app/r.*$' + ); + await retry.try(async () => { + actualUrl = await PageObjects.share.getSharedUrl(); + expect(actualUrl).to.match(re); + await PageObjects.share.closeShareModal(); + }); + }); + it('shows CSV reports', async () => { await PageObjects.share.clickShareTopNavButton(); await PageObjects.share.clickTab('Export'); @@ -190,6 +206,44 @@ export default function (ctx: FtrProviderContext) { await PageObjects.unifiedFieldList.expectMissingFieldListItemVisualize('bytes'); }); + it('should allow for copying the snapshot URL', async function () { + await PageObjects.share.clickShareTopNavButton(); + const actualUrl = await PageObjects.share.getSharedUrl(); + expect(actualUrl).to.contain(`?l=${DISCOVER_APP_LOCATOR}`); + const urlSearchParams = new URLSearchParams(actualUrl); + expect(JSON.parse(decompressFromBase64(urlSearchParams.get('lz')!)!)).to.eql({ + query: { + language: 'kuery', + query: '', + }, + sort: [['@timestamp', 'desc']], + columns: [], + interval: 'auto', + filters: [], + dataViewId: 'logstash-*', + timeRange: { + from: '2015-09-19T06:31:44.000Z', + to: '2015-09-23T18:31:44.000Z', + }, + refreshInterval: { + value: 60000, + pause: true, + }, + }); + await PageObjects.share.closeShareModal(); + }); + + it(`Doesn't show short urls for users without those privileges`, async () => { + await PageObjects.share.clickShareTopNavButton(); + let actualUrl: string = ''; + + await retry.try(async () => { + actualUrl = await PageObjects.share.getSharedUrl(); + // only shows in long urls + expect(actualUrl).to.contain(DISCOVER_APP_LOCATOR); + await PageObjects.share.closeShareModal(); + }); + }); savedQuerySecurityUtils.shouldDisallowSavingButAllowLoadingSavedQueries(); }); @@ -253,6 +307,19 @@ export default function (ctx: FtrProviderContext) { await PageObjects.unifiedFieldList.expectMissingFieldListItemVisualize('bytes'); }); + it('Shows short urls for users with the right privileges', async () => { + await PageObjects.share.clickShareTopNavButton(); + let actualUrl: string = ''; + const re = new RegExp( + deployment.getHostPort().replace(':80', '').replace(':443', '') + '/app/r.*$' + ); + await retry.try(async () => { + actualUrl = await PageObjects.share.getSharedUrl(); + expect(actualUrl).to.match(re); + await PageObjects.share.closeShareModal(); + }); + }); + savedQuerySecurityUtils.shouldDisallowSavingButAllowLoadingSavedQueries(); }); diff --git a/x-pack/test/functional/apps/discover/reporting.ts b/x-pack/test/functional/apps/discover/reporting.ts index bb5721b9c99fc..41882ef130790 100644 --- a/x-pack/test/functional/apps/discover/reporting.ts +++ b/x-pack/test/functional/apps/discover/reporting.ts @@ -6,8 +6,8 @@ */ import expect from '@kbn/expect'; -import { Key } from 'selenium-webdriver'; import moment from 'moment'; +import { Key } from 'selenium-webdriver'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { @@ -103,8 +103,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.selectIndexPattern('ecommerce'); }); - // Discover defaults to short urls - is this test helpful? Clarify in separate PR - xit('generates a report with single timefilter', async () => { + it('generates a report with single timefilter', async () => { await PageObjects.discover.clickNewSearchButton(); await PageObjects.timePicker.setCommonlyUsedTime('Last_24 hours'); await PageObjects.discover.saveSearch('single-timefilter-search'); @@ -116,22 +115,23 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.share.clickShareTopNavButton(); await PageObjects.reporting.openExportTab(); const copyButton = await testSubjects.find('shareReportingCopyURL'); - const reportURL = (await copyButton.getAttribute('data-share-url')) ?? ''; + const reportURL = decodeURIComponent( + (await copyButton.getAttribute('data-share-url')) ?? '' + ); // get number of filters in URLs const timeFiltersNumberInReportURL = - decodeURIComponent(reportURL).split( - 'query:(range:(order_date:(format:strict_date_optional_time' - ).length - 1; + reportURL.split('query:(range:(order_date:(format:strict_date_optional_time').length - 1; const timeFiltersNumberInSharedURL = sharedURL.split('time:').length - 1; expect(timeFiltersNumberInSharedURL).to.be(1); expect(sharedURL.includes('time:(from:now-24h%2Fh,to:now))')).to.be(true); expect(timeFiltersNumberInReportURL).to.be(1); + expect( - decodeURIComponent(reportURL).includes( - 'query:(range:(order_date:(format:strict_date_optional_time' + reportURL.includes( + `query:(range:(order_date:(format:strict_date_optional_time,gte:now-24h/h,lte:now))))` ) ).to.be(true); @@ -298,6 +298,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await setupPage(); }); + afterEach(async () => { + await PageObjects.reporting.checkForReportingToasts(); + }); + it('generates a report with data', async () => { await PageObjects.discover.loadSavedSearch('Ecommerce Data'); await retry.try(async () => { diff --git a/x-pack/test/functional/apps/lens/group4/dashboard.ts b/x-pack/test/functional/apps/lens/group4/dashboard.ts index 33e3277855d2f..776a4416d7d4c 100644 --- a/x-pack/test/functional/apps/lens/group4/dashboard.ts +++ b/x-pack/test/functional/apps/lens/group4/dashboard.ts @@ -321,5 +321,37 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // check the success state await PageObjects.dashboard.verifyNoRenderErrors(); }); + + it('should work in lens with by-value charts', async () => { + // create a new dashboard, then a new visualization in Lens. + await PageObjects.dashboard.navigateToApp(); + await PageObjects.dashboard.clickNewDashboard(); + await testSubjects.click('dashboardEditorMenuButton'); + await testSubjects.click('visType-lens'); + // Configure it and save to return to the dashboard. + await PageObjects.lens.waitForField('@timestamp'); + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'average', + field: 'bytes', + }); + await PageObjects.lens.save('test', true); + // Edit the visualization now and get back to Lens editor + await testSubjects.click('embeddablePanelToggleMenuIcon'); + await testSubjects.click('embeddablePanelAction-ACTION_CONFIGURE_IN_LENS'); + await testSubjects.click('navigateToLensEditorLink'); + // Click on Share, then Copy link and paste the link in a new tab. + const url = await PageObjects.lens.getUrl(); + await browser.openNewTab(); + await browser.navigateTo(url); + expect(await PageObjects.lens.getTitle()).to.be('test'); + // need to make sure there aren't extra tabs or it will impact future test suites + // close any new tabs that were opened + const windowHandlers = await browser.getAllWindowHandles(); + if (windowHandlers.length > 1) { + await browser.closeCurrentWindow(); + await browser.switchToWindow(windowHandlers[0]); + } + }); }); } diff --git a/x-pack/test/functional/apps/lens/group4/share.ts b/x-pack/test/functional/apps/lens/group4/share.ts index 9e99c4d3c328f..ac3c4a3b22b78 100644 --- a/x-pack/test/functional/apps/lens/group4/share.ts +++ b/x-pack/test/functional/apps/lens/group4/share.ts @@ -61,12 +61,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(await PageObjects.lens.isShareActionEnabled('link')); }); - xit('should preserve filter and query when sharing', async () => { + it('should preserve filter and query when sharing', async () => { await filterBarService.addFilter({ field: 'bytes', operation: 'is', value: '1' }); await queryBar.setQuery('host.keyword www.elastic.co'); await queryBar.submitQuery(); - await PageObjects.lens.waitForVisualization('xyVisChart'); - + await PageObjects.lens.save('new'); const url = await PageObjects.lens.getUrl(); await PageObjects.lens.closeShareModal(); await browser.openNewTab(); diff --git a/x-pack/test/plugin_api_integration/plugins/sample_task_plugin/server/plugin.ts b/x-pack/test/plugin_api_integration/plugins/sample_task_plugin/server/plugin.ts index 0f14b6f10fef0..809f7307ab8aa 100644 --- a/x-pack/test/plugin_api_integration/plugins/sample_task_plugin/server/plugin.ts +++ b/x-pack/test/plugin_api_integration/plugins/sample_task_plugin/server/plugin.ts @@ -18,7 +18,7 @@ import { EphemeralTask, } from '@kbn/task-manager-plugin/server'; import { DEFAULT_MAX_WORKERS } from '@kbn/task-manager-plugin/server/config'; -import { TaskPriority } from '@kbn/task-manager-plugin/server/task'; +import { getDeleteTaskRunResult, TaskPriority } from '@kbn/task-manager-plugin/server/task'; import { initRoutes } from './init_routes'; // this plugin's dependendencies @@ -167,6 +167,45 @@ export class SampleTaskManagerFixturePlugin }, }), }, + sampleRecurringTaskThatDeletesItself: { + title: 'Sample Recurring Task that Times Out', + description: 'A sample task that requests deletion.', + stateSchemaByVersion: { + 1: { + up: (state: Record) => ({ count: state.count }), + schema: schema.object({ + count: schema.maybe(schema.number()), + }), + }, + }, + createTaskRunner: ({ taskInstance }: { taskInstance: ConcreteTaskInstance }) => ({ + async run() { + const { state } = taskInstance; + const prevState = state || { count: 0 }; + + const count = (prevState.count || 0) + 1; + + const [{ elasticsearch }] = await core.getStartServices(); + await elasticsearch.client.asInternalUser.index({ + index: '.kibana_task_manager_test_result', + body: { + type: 'task', + taskId: taskInstance.id, + state: JSON.stringify(state), + ranAt: new Date(), + }, + refresh: true, + }); + + if (count === 5) { + return getDeleteTaskRunResult(); + } + return { + state: { count }, + }; + }, + }), + }, sampleAdHocTaskTimingOut: { title: 'Sample Ad-Hoc Task that Times Out', description: 'A sample task that times out.', diff --git a/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts b/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts index ff650c32de9af..26f4df962231f 100644 --- a/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts +++ b/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts @@ -25,6 +25,7 @@ export default function ({ getService }: FtrProviderContext) { 'sampleOneTimeTaskThrowingError', 'sampleRecurringTaskTimingOut', 'sampleRecurringTaskWhichHangs', + 'sampleRecurringTaskThatDeletesItself', 'sampleTask', 'sampleTaskWithLimitedConcurrency', 'sampleTaskWithSingleConcurrency', diff --git a/x-pack/test/plugin_api_integration/test_suites/task_manager/task_management.ts b/x-pack/test/plugin_api_integration/test_suites/task_manager/task_management.ts index a430993933c8b..148939a58f910 100644 --- a/x-pack/test/plugin_api_integration/test_suites/task_manager/task_management.ts +++ b/x-pack/test/plugin_api_integration/test_suites/task_manager/task_management.ts @@ -296,6 +296,20 @@ export default function ({ getService }: FtrProviderContext) { }); }); + it('should remove recurring task if task requests deletion', async () => { + await scheduleTask({ + taskType: 'sampleRecurringTaskThatDeletesItself', + schedule: { interval: '1s' }, + params: {}, + }); + + await retry.try(async () => { + const history = await historyDocs(); + expect(history.length).to.eql(5); + expect((await currentTasks()).docs).to.eql([]); + }); + }); + it('should use a given ID as the task document ID', async () => { const result = await scheduleTask({ id: 'test-task-for-sample-task-plugin-to-test-task-manager', diff --git a/x-pack/test_serverless/functional/test_suites/common/discover/x_pack/reporting.ts b/x-pack/test_serverless/functional/test_suites/common/discover/x_pack/reporting.ts index 5a2129d75ec77..76c95ebbd890e 100644 --- a/x-pack/test_serverless/functional/test_suites/common/discover/x_pack/reporting.ts +++ b/x-pack/test_serverless/functional/test_suites/common/discover/x_pack/reporting.ts @@ -6,7 +6,6 @@ */ import expect from '@kbn/expect'; -import { Key } from 'selenium-webdriver'; import moment from 'moment'; import { FtrProviderContext } from '../../../../ftr_provider_context'; @@ -27,7 +26,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'share', ]); const filterBar = getService('filterBar'); - const testSubjects = getService('testSubjects'); const toasts = getService('toasts'); const setFieldsFromSource = async (setValue: boolean) => { @@ -115,42 +113,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.selectIndexPattern('ecommerce'); }); - // this test does not pass because of discover using short urls - investigate in separate PR - xit('generates a report with single timefilter', async () => { - await PageObjects.discover.clickNewSearchButton(); - await PageObjects.timePicker.setCommonlyUsedTime('Last_24 hours'); - await PageObjects.discover.saveSearch('single-timefilter-search'); - - // get shared URL value - const sharedURL = await browser.getCurrentUrl(); - - // click 'Copy POST URL' - await PageObjects.share.clickShareTopNavButton(); - await PageObjects.reporting.openExportTab(); - const copyButton = await testSubjects.find('shareReportingCopyURL'); - const reportURL = (await copyButton.getAttribute('data-share-url')) ?? ''; - - // get number of filters in URLs - const timeFiltersNumberInReportURL = - decodeURIComponent(reportURL).split( - 'query:(range:(order_date:(format:strict_date_optional_time' - ).length - 1; - const timeFiltersNumberInSharedURL = sharedURL.split('time:').length - 1; - - expect(timeFiltersNumberInSharedURL).to.be(1); - expect(sharedURL.includes('time:(from:now-24h%2Fh,to:now))')).to.be(true); - - expect(timeFiltersNumberInReportURL).to.be(1); - expect( - decodeURIComponent(reportURL).includes( - 'query:(range:(order_date:(format:strict_date_optional_time' - ) - ).to.be(true); - - // return keyboard state - await browser.getActions().keyUp(Key.CONTROL).perform(); - await browser.getActions().keyUp('v').perform(); - }); it('generates a report from a new search with data: default', async () => { await PageObjects.discover.clickNewSearchButton(); await PageObjects.reporting.setTimepickerInEcommerceDataRange();