From b7bdb58e7c8a9321bf86d0f24d35435feb8aac6d Mon Sep 17 00:00:00 2001 From: Christos Nasikas Date: Tue, 14 Nov 2023 18:20:22 +0200 Subject: [PATCH 1/7] [Cases] Disabling editing ESQL visualizations (#171191) ## Summary This PR disables editing an ESQL visualization from within Cases. Fixes: https://github.com/elastic/kibana/issues/171154 ### Checklist Delete any items that are not applicable to this PR. - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios ### For maintainers - [x] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --- .../lens/use_lens_open_visualization.tsx | 32 +++++++++++++------ .../visualizations/attachment.test.tsx | 5 ++- .../visualizations/open_lens_button.test.tsx | 15 +++++++++ .../visualizations/open_lens_button.tsx | 4 ++- 4 files changed, 45 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/use_lens_open_visualization.tsx b/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/use_lens_open_visualization.tsx index d9e78330113ee..d4f6ddee3a657 100644 --- a/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/use_lens_open_visualization.tsx +++ b/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/use_lens_open_visualization.tsx @@ -8,6 +8,7 @@ import { useCallback } from 'react'; import type { TypedLensByValueInput } from '@kbn/lens-plugin/public'; +import { isOfAggregateQueryType } from '@kbn/es-query'; import { AttachmentActionType } from '../../../../client/attachment_framework/types'; import { useKibana } from '../../../../common/lib/kibana'; import { @@ -24,6 +25,8 @@ export const useLensOpenVisualization = ({ comment }: { comment: string }) => { lens: { navigateToPrefilledEditor, canUseEditor }, } = useKibana().services; + const hasLensPermissions = canUseEditor(); + const handleClick = useCallback(() => { navigateToPrefilledEditor( { @@ -38,15 +41,26 @@ export const useLensOpenVisualization = ({ comment }: { comment: string }) => { ); }, [lensVisualization, navigateToPrefilledEditor]); + if (!lensVisualization.length || lensVisualization?.[0]?.attributes == null) { + return { canUseEditor: hasLensPermissions, actionConfig: null }; + } + + const lensAttributes = lensVisualization[0] + .attributes as unknown as TypedLensByValueInput['attributes']; + + const isESQLQuery = isOfAggregateQueryType(lensAttributes.state.query); + + if (isESQLQuery) { + return { canUseEditor: hasLensPermissions, actionConfig: null }; + } + return { - canUseEditor: canUseEditor(), - actionConfig: !lensVisualization.length - ? null - : { - type: AttachmentActionType.BUTTON as const, - iconType: 'lensApp', - label: OPEN_IN_VISUALIZATION, - onClick: handleClick, - }, + canUseEditor: hasLensPermissions, + actionConfig: { + type: AttachmentActionType.BUTTON as const, + iconType: 'lensApp', + label: OPEN_IN_VISUALIZATION, + onClick: handleClick, + }, }; }; diff --git a/x-pack/plugins/cases/public/components/visualizations/attachment.test.tsx b/x-pack/plugins/cases/public/components/visualizations/attachment.test.tsx index 4c5acc11372d8..ab22f3133b49f 100644 --- a/x-pack/plugins/cases/public/components/visualizations/attachment.test.tsx +++ b/x-pack/plugins/cases/public/components/visualizations/attachment.test.tsx @@ -24,7 +24,10 @@ describe('getVisualizationAttachmentType', () => { const attachmentViewProps: PersistableStateAttachmentViewProps = { persistableStateAttachmentTypeId: LENS_ATTACHMENT_TYPE, - persistableStateAttachmentState: { attributes: {}, timeRange: {} }, + persistableStateAttachmentState: { + attributes: { state: { query: {} } }, + timeRange: {}, + }, attachmentId: 'test', caseData: { title: basicCase.title, id: basicCase.id }, }; diff --git a/x-pack/plugins/cases/public/components/visualizations/open_lens_button.test.tsx b/x-pack/plugins/cases/public/components/visualizations/open_lens_button.test.tsx index ed3908c279ac0..4bd996baa1687 100644 --- a/x-pack/plugins/cases/public/components/visualizations/open_lens_button.test.tsx +++ b/x-pack/plugins/cases/public/components/visualizations/open_lens_button.test.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import { set } from 'lodash'; import React from 'react'; import { screen } from '@testing-library/react'; import type { AppMockRenderer } from '../../common/mock'; @@ -60,4 +61,18 @@ describe('OpenLensButton', () => { expect(screen.queryByText('Open visualization')).not.toBeInTheDocument(); }); + + it('does not show the button if the query is an ESQL', () => { + const esqlProps = { + attachmentId: 'test', + ...lensVisualization, + }; + + set(esqlProps, 'attributes.state.query', { esql: '' }); + + // @ts-expect-error: props are correct + appMockRender.render(); + + expect(screen.queryByText('Open visualization')).not.toBeInTheDocument(); + }); }); diff --git a/x-pack/plugins/cases/public/components/visualizations/open_lens_button.tsx b/x-pack/plugins/cases/public/components/visualizations/open_lens_button.tsx index 365f2134a0d0a..c6a49bb285a2f 100644 --- a/x-pack/plugins/cases/public/components/visualizations/open_lens_button.tsx +++ b/x-pack/plugins/cases/public/components/visualizations/open_lens_button.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import { isOfAggregateQueryType } from '@kbn/es-query'; import { EuiButtonEmpty } from '@elastic/eui'; import React, { useCallback } from 'react'; import { useKibana } from '../../common/lib/kibana'; @@ -32,8 +33,9 @@ const OpenLensButtonComponent: React.FC = ({ attachmentId, attributes, ti }, [attachmentId, attributes, navigateToPrefilledEditor, timeRange]); const hasLensPermissions = canUseEditor(); + const isESQLQuery = isOfAggregateQueryType(attributes.state.query); - if (!hasLensPermissions) { + if (!hasLensPermissions || isESQLQuery) { return null; } From 0d5f83da2b30a7202ec75680f4e14f3c5167be1a Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Tue, 14 Nov 2023 09:30:08 -0700 Subject: [PATCH 2/7] [maps] fix blank screen when switching to Full Screen (#170915) Fixes https://github.com/elastic/kibana/issues/170467 ### test instructions 1. create new map 2. Click "Full screen" 3. Verify map is displayed --- x-pack/plugins/maps/public/_main.scss | 5 +++++ x-pack/test/functional/apps/maps/group1/full_screen_mode.js | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/x-pack/plugins/maps/public/_main.scss b/x-pack/plugins/maps/public/_main.scss index 61de65dd4bf6f..02298872d4f7d 100644 --- a/x-pack/plugins/maps/public/_main.scss +++ b/x-pack/plugins/maps/public/_main.scss @@ -10,6 +10,11 @@ overflow: hidden; } +.mapFullScreen { + // sass-lint:disable no-important + height: 100vh !important; +} + #react-maps-root { flex-grow: 1; display: flex; diff --git a/x-pack/test/functional/apps/maps/group1/full_screen_mode.js b/x-pack/test/functional/apps/maps/group1/full_screen_mode.js index 2c87bb4df7c3c..b0ed1311d439a 100644 --- a/x-pack/test/functional/apps/maps/group1/full_screen_mode.js +++ b/x-pack/test/functional/apps/maps/group1/full_screen_mode.js @@ -11,6 +11,7 @@ export default function ({ getService, getPageObjects }) { const PageObjects = getPageObjects(['maps', 'common']); const retry = getService('retry'); const security = getService('security'); + const testSubjects = getService('testSubjects'); describe('maps full screen mode', () => { before(async () => { @@ -38,6 +39,10 @@ export default function ({ getService, getPageObjects }) { }); }); + it('layer control is visible', async () => { + expect(await testSubjects.isDisplayed('addLayerButton')).to.be(true); + }); + it('displays exit full screen logo button', async () => { const exists = await PageObjects.maps.exitFullScreenLogoButtonExists(); expect(exists).to.be(true); From 830e8b81509a04bba47f435fe1f51ce385fe6adf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yulia=20=C4=8Cech?= <6585477+yuliacech@users.noreply.github.com> Date: Tue, 14 Nov 2023 17:32:18 +0100 Subject: [PATCH 3/7] [Console] Fix flaky tests for xjson (#170775) ## Summary Fixes https://github.com/elastic/kibana/issues/152008 Fixes https://github.com/elastic/kibana/issues/146518 Fixes https://github.com/elastic/kibana/issues/158484 This PR adds a sleep period before accessing elements via class names as suggested in this [comment](https://github.com/elastic/kibana/issues/146518#issuecomment-1335338803). I run the flaky test runner 200, 200 and 50 times without any failures ([runner](https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds?branch=refs%2Fpull%2F170775%2Fhead)). ### Checklist Delete any items that are not applicable to this PR. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### Risk Matrix Delete this section if it is not applicable to this PR. Before closing this PR, invite QA, stakeholders, and other developers to identify risks that should be tested prior to the change/feature release. When forming the risk matrix, consider some of the following examples and how they may potentially impact the change: | Risk | Probability | Severity | Mitigation/Notes | |---------------------------|-------------|----------|-------------------------| | Multiple Spaces—unexpected behavior in non-default Kibana Space. | Low | High | Integration tests will verify that all features are still supported in non-default Kibana Space and when user switches between spaces. | | Multiple nodes—Elasticsearch polling might have race conditions when multiple Kibana nodes are polling for the same tasks. | High | Low | Tasks are idempotent, so executing them multiple times will not result in logical error, but will degrade performance. To test for this case we add plenty of unit tests around this logic and document manual testing procedure. | | Code should gracefully handle cases when feature X or plugin Y are disabled. | Medium | High | Unit tests will verify that any feature flag or plugin combination still results in our service operational. | | [See more potential risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) | ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --- test/functional/apps/console/_xjson.ts | 3 +-- test/functional/page_objects/console_page.ts | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/test/functional/apps/console/_xjson.ts b/test/functional/apps/console/_xjson.ts index 445bfa5d42f0a..1535337a2a848 100644 --- a/test/functional/apps/console/_xjson.ts +++ b/test/functional/apps/console/_xjson.ts @@ -15,8 +15,7 @@ export default ({ getService, getPageObjects }: FtrProviderContext) => { const log = getService('log'); const PageObjects = getPageObjects(['common', 'console', 'header']); - // FLAKY: https://github.com/elastic/kibana/issues/145477 - describe.skip('XJSON', function testXjson() { + describe('XJSON', function testXjson() { this.tags('includeFirefox'); before(async () => { await PageObjects.common.navigateToApp('console'); diff --git a/test/functional/page_objects/console_page.ts b/test/functional/page_objects/console_page.ts index a48578a60bc17..2fabe4222ee4e 100644 --- a/test/functional/page_objects/console_page.ts +++ b/test/functional/page_objects/console_page.ts @@ -500,6 +500,7 @@ export class ConsolePageObject extends FtrService { } public async getRequestQueryParams() { + await this.sleepForDebouncePeriod(); const requestEditor = await this.getRequestEditor(); const requestQueryParams = await requestEditor.findAllByCssSelector('.ace_url.ace_param'); @@ -531,6 +532,7 @@ export class ConsolePageObject extends FtrService { } public async getRequestLineHighlighting() { + await this.sleepForDebouncePeriod(); const requestEditor = await this.getRequestEditor(); const requestLine = await requestEditor.findAllByCssSelector('.ace_line > *'); const line = []; From a46923cae317c47b460b3504aad7e84199d8579e Mon Sep 17 00:00:00 2001 From: Achyut Jhunjhunwala Date: Tue, 14 Nov 2023 19:18:33 +0100 Subject: [PATCH 4/7] [Logs Explorer] Add generic commands to Readme (#171208) ## Summary PR adds the list of regular commands we use in the project --- .../observability_log_explorer/README.md | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/x-pack/plugins/observability_log_explorer/README.md b/x-pack/plugins/observability_log_explorer/README.md index 604b33dd2b288..8e6f910f2da4e 100644 --- a/x-pack/plugins/observability_log_explorer/README.md +++ b/x-pack/plugins/observability_log_explorer/README.md @@ -1,3 +1,71 @@ # Observability Log Explorer This plugin provides an app based on the `LogExplorer` component from the `log_explorer` plugin, but adds observability-specific affordances. + +## Testing + +### Stateful + +#### FTR Server +``` +yarn test:ftr:server --config ./x-pack/test/functional/apps/observability_log_explorer/config.ts +``` + +#### FTR Runner +``` +yarn test:ftr:runner --config ./x-pack/test/functional/apps/observability_log_explorer/config.ts --include ./x-pack/test/functional/apps/observability_log_explorer/index.ts +``` + +#### Running Individual Tests +``` +yarn test:ftr:runner --config ./x-pack/test/functional/apps/observability_log_explorer/config.ts --include ./x-pack/test/functional/apps/observability_log_explorer/$1 +``` + +### Serverless + +#### Server +``` +yarn test:ftr:server --config ./x-pack/test_serverless/functional/test_suites/observability/config.ts +``` + +#### Runner +``` +yarn test:ftr:runner --config ./x-pack/test_serverless/functional/test_suites/observability/config.ts --include ./x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/index.ts +``` +#### Running Individual Tests +``` +yarn test:ftr:runner --config ./x-pack/test_serverless/functional/test_suites/observability/config.ts --include ./x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/$1 +``` + +## Checktypes + +#### Log Explorer +``` +node scripts/type_check.js --project x-pack/plugins/log_explorer/tsconfig.json +``` +#### Observability Log Explorer +``` +node scripts/type_check.js --project x-pack/plugins/observability_log_explorer/tsconfig.json +``` + +### Generating Data using Synthtrace + +#### Logs Data only +``` +node scripts/synthtrace simple_logs.ts --clean [--live] +``` + +#### Logs and Metrics Data +``` +node scripts/synthtrace logs_and_metrics.ts --clean [--live] +``` + +### General Issues + +#### Kibana CI broken due to `kbn/optimiser` issue ? + +The limit is done to protect us in case we add some dependency that heavily impacts the bundle size, so this is not to be intended as a fix, but as a conscious update after double-checking the bundle size increase and see if it can be reduced + +``` +node scripts/build_kibana_platform_plugins --focus logExplorer --update-limits +``` \ No newline at end of file From 603a0454a965a8bd9e51fdd3e1643a96eb1be3db Mon Sep 17 00:00:00 2001 From: Zacqary Adam Xeper Date: Tue, 14 Nov 2023 12:39:30 -0600 Subject: [PATCH 5/7] [RAM] Add toggle for AAD fields in alert templating (#170162) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Reopen of https://github.com/elastic/kibana/pull/161213 Closes https://github.com/elastic/kibana/issues/160838 Feature can be activated via feature flag: `xpack.trigger_actions_ui.enableExperimental: ['isMustacheAutocompleteOn', 'showMustacheAutocompleteSwitch']` Screenshot 2023-10-30 at 5 52 39 PM Screenshot 2023-10-30 at 5 52 22 PM ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: Julian Gernun <17549662+jcger@users.noreply.github.com> Co-authored-by: Xavier Mouligneau Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../routes/rule/apis/create/schemas/v1.ts | 1 + x-pack/plugins/alerting/common/rule.ts | 1 + x-pack/plugins/alerting/server/alert/alert.ts | 19 ++- .../create/schemas/create_rule_data_schema.ts | 1 + .../rule/schemas/action_schemas.ts | 2 + .../server/routes/lib/actions_schema.ts | 1 + .../server/routes/lib/rewrite_actions.ts | 59 ++++--- .../bulk_edit/bulk_edit_rules_route.test.ts | 1 + .../apis/create/create_rule_route.test.ts | 5 + .../transforms/transform_create_body/v1.ts | 46 +++--- .../apis/resolve/resolve_rule_route.test.ts | 2 + .../transform_rule_to_rule_response/v1.ts | 12 +- .../server/routes/update_rule.test.ts | 1 + .../server/task_runner/execution_handler.ts | 72 ++++++--- .../task_runner/transform_action_params.ts | 69 ++++---- .../common/experimental_features.ts | 2 +- .../connector_types/email/email_params.tsx | 12 +- .../connector_types/opsgenie/params.test.tsx | 1 - .../server_log/server_log_params.tsx | 2 +- .../slack/action_form.test.tsx | 14 +- x-pack/plugins/stack_connectors/tsconfig.json | 3 +- .../common/experimental_features.ts | 5 +- .../text_area_with_autocomplete/index.tsx} | 5 +- ...ilter_suggestions_for_autocomplete.test.ts | 0 .../filter_suggestions_for_autocomplete.ts | 0 .../lib/template_action_variable.test.ts | 0 .../lib/template_action_variable.ts | 0 .../text_area_with_message_variables.tsx | 16 +- .../hooks/use_rule_aad_template_fields.ts | 69 ++++++++ .../application/lib/rule_api/clone.test.ts | 1 + .../lib/rule_api/common_transformations.ts | 2 + .../public/application/lib/rule_api/create.ts | 25 +-- .../public/application/lib/rule_api/update.ts | 27 ++-- .../action_connector_form/action_form.tsx | 5 +- .../action_type_form.test.tsx | 73 ++++++++- .../action_type_form.tsx | 153 ++++++++++++++---- .../sections/rule_form/rule_form.tsx | 3 + .../triggers_actions_ui/public/types.ts | 1 + .../plugins/triggers_actions_ui/tsconfig.json | 3 +- .../group1/tests/alerting/create.ts | 1 + .../group3/tests/alerting/bulk_edit.ts | 1 + .../tests/alerting/group1/create.ts | 4 + 42 files changed, 537 insertions(+), 183 deletions(-) rename x-pack/plugins/{stack_connectors/public/components/text_area_with_autocomplete.tsx => triggers_actions_ui/public/application/components/text_area_with_autocomplete/index.tsx} (98%) rename x-pack/plugins/{stack_connectors/public => triggers_actions_ui/public/application/components/text_area_with_autocomplete}/lib/filter_suggestions_for_autocomplete.test.ts (100%) rename x-pack/plugins/{stack_connectors/public => triggers_actions_ui/public/application/components/text_area_with_autocomplete}/lib/filter_suggestions_for_autocomplete.ts (100%) rename x-pack/plugins/{stack_connectors/public => triggers_actions_ui/public/application/components/text_area_with_autocomplete}/lib/template_action_variable.test.ts (100%) rename x-pack/plugins/{stack_connectors/public => triggers_actions_ui/public/application/components/text_area_with_autocomplete}/lib/template_action_variable.ts (100%) create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/hooks/use_rule_aad_template_fields.ts diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/create/schemas/v1.ts b/x-pack/plugins/alerting/common/routes/rule/apis/create/schemas/v1.ts index e471dd3cea530..6f8420c8cc4da 100644 --- a/x-pack/plugins/alerting/common/routes/rule/apis/create/schemas/v1.ts +++ b/x-pack/plugins/alerting/common/routes/rule/apis/create/schemas/v1.ts @@ -63,6 +63,7 @@ export const actionSchema = schema.object({ params: schema.recordOf(schema.string(), schema.maybe(schema.any()), { defaultValue: {} }), frequency: schema.maybe(actionFrequencySchema), alerts_filter: schema.maybe(actionAlertsFilterSchema), + use_alert_data_for_template: schema.maybe(schema.boolean()), }); export const createBodySchema = schema.object({ diff --git a/x-pack/plugins/alerting/common/rule.ts b/x-pack/plugins/alerting/common/rule.ts index ff74752df8695..da3788d534598 100644 --- a/x-pack/plugins/alerting/common/rule.ts +++ b/x-pack/plugins/alerting/common/rule.ts @@ -116,6 +116,7 @@ export interface RuleAction { params: RuleActionParams; frequency?: RuleActionFrequency; alertsFilter?: AlertsFilter; + useAlertDataForTemplate?: boolean; } export interface RuleLastRun { diff --git a/x-pack/plugins/alerting/server/alert/alert.ts b/x-pack/plugins/alerting/server/alert/alert.ts index ee8c48a18447b..835b8db17a893 100644 --- a/x-pack/plugins/alerting/server/alert/alert.ts +++ b/x-pack/plugins/alerting/server/alert/alert.ts @@ -6,6 +6,7 @@ */ import { v4 as uuidV4 } from 'uuid'; +import { AADAlert } from '@kbn/alerts-as-data-utils'; import { get, isEmpty } from 'lodash'; import { MutableAlertInstanceMeta } from '@kbn/alerting-state-types'; import { ALERT_UUID } from '@kbn/rule-data-utils'; @@ -36,7 +37,7 @@ export type PublicAlert< Context extends AlertInstanceContext = AlertInstanceContext, ActionGroupIds extends string = DefaultActionGroupId > = Pick< - Alert, + Alert, | 'getContext' | 'getState' | 'getUuid' @@ -50,13 +51,15 @@ export type PublicAlert< export class Alert< State extends AlertInstanceState = AlertInstanceState, Context extends AlertInstanceContext = AlertInstanceContext, - ActionGroupIds extends string = never + ActionGroupIds extends string = never, + AlertAsData extends AADAlert = AADAlert > { private scheduledExecutionOptions?: ScheduledExecutionOptions; private meta: MutableAlertInstanceMeta; private state: State; private context: Context; private readonly id: string; + private alertAsData: AlertAsData | undefined; constructor(id: string, { state, meta = {} }: RawAlertInstance = {}) { this.id = id; @@ -78,6 +81,18 @@ export class Alert< return this.meta.uuid!; } + isAlertAsData() { + return this.alertAsData !== undefined; + } + + setAlertAsData(alertAsData: AlertAsData) { + this.alertAsData = alertAsData; + } + + getAlertAsData() { + return this.alertAsData; + } + getStart(): string | null { return this.state.start ? `${this.state.start}` : null; } diff --git a/x-pack/plugins/alerting/server/application/rule/methods/create/schemas/create_rule_data_schema.ts b/x-pack/plugins/alerting/server/application/rule/methods/create/schemas/create_rule_data_schema.ts index ce51e88c5ce73..b7e5591996957 100644 --- a/x-pack/plugins/alerting/server/application/rule/methods/create/schemas/create_rule_data_schema.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/create/schemas/create_rule_data_schema.ts @@ -35,6 +35,7 @@ export const createRuleDataSchema = schema.object({ ), uuid: schema.maybe(schema.string()), alertsFilter: schema.maybe(actionAlertsFilterSchema), + useAlertDataForTemplate: schema.maybe(schema.boolean()), }), { defaultValue: [] } ), diff --git a/x-pack/plugins/alerting/server/application/rule/schemas/action_schemas.ts b/x-pack/plugins/alerting/server/application/rule/schemas/action_schemas.ts index f123466eca1ab..fa8e05f837c25 100644 --- a/x-pack/plugins/alerting/server/application/rule/schemas/action_schemas.ts +++ b/x-pack/plugins/alerting/server/application/rule/schemas/action_schemas.ts @@ -65,6 +65,7 @@ export const actionDomainSchema = schema.object({ params: actionParamsSchema, frequency: schema.maybe(actionFrequencySchema), alertsFilter: schema.maybe(actionDomainAlertsFilterSchema), + useAlertDataAsTemplate: schema.maybe(schema.boolean()), }); /** @@ -89,4 +90,5 @@ export const actionSchema = schema.object({ params: actionParamsSchema, frequency: schema.maybe(actionFrequencySchema), alertsFilter: schema.maybe(actionAlertsFilterSchema), + useAlertDataForTemplate: schema.maybe(schema.boolean()), }); diff --git a/x-pack/plugins/alerting/server/routes/lib/actions_schema.ts b/x-pack/plugins/alerting/server/routes/lib/actions_schema.ts index 9d6f89e070c3a..57a089bc9933a 100644 --- a/x-pack/plugins/alerting/server/routes/lib/actions_schema.ts +++ b/x-pack/plugins/alerting/server/routes/lib/actions_schema.ts @@ -68,6 +68,7 @@ export const actionsSchema = schema.arrayOf( ), }) ), + use_alert_data_for_template: schema.maybe(schema.boolean()), }), { defaultValue: [] } ); diff --git a/x-pack/plugins/alerting/server/routes/lib/rewrite_actions.ts b/x-pack/plugins/alerting/server/routes/lib/rewrite_actions.ts index 2c2b0853d181b..16309485c18a1 100644 --- a/x-pack/plugins/alerting/server/routes/lib/rewrite_actions.ts +++ b/x-pack/plugins/alerting/server/routes/lib/rewrite_actions.ts @@ -15,20 +15,28 @@ export const rewriteActionsReq = ( ): NormalizedAlertAction[] => { if (!actions) return []; - return actions.map(({ frequency, alerts_filter: alertsFilter, ...action }) => { - return { - ...action, - ...(frequency - ? { - frequency: { - ...omit(frequency, 'notify_when'), - notifyWhen: frequency.notify_when, - }, - } - : {}), - ...(alertsFilter ? { alertsFilter } : {}), - }; - }); + return actions.map( + ({ + frequency, + alerts_filter: alertsFilter, + use_alert_data_for_template: useAlertDataForTemplate, + ...action + }) => { + return { + ...action, + useAlertDataForTemplate, + ...(frequency + ? { + frequency: { + ...omit(frequency, 'notify_when'), + notifyWhen: frequency.notify_when, + }, + } + : {}), + ...(alertsFilter ? { alertsFilter } : {}), + }; + } + ); }; export const rewriteActionsRes = (actions?: RuleAction[]) => { @@ -37,14 +45,17 @@ export const rewriteActionsRes = (actions?: RuleAction[]) => { notify_when: notifyWhen, }); if (!actions) return []; - return actions.map(({ actionTypeId, frequency, alertsFilter, ...action }) => ({ - ...action, - connector_type_id: actionTypeId, - ...(frequency ? { frequency: rewriteFrequency(frequency) } : {}), - ...(alertsFilter - ? { - alerts_filter: alertsFilter, - } - : {}), - })); + return actions.map( + ({ actionTypeId, frequency, alertsFilter, useAlertDataForTemplate, ...action }) => ({ + ...action, + connector_type_id: actionTypeId, + use_alert_data_for_template: useAlertDataForTemplate, + ...(frequency ? { frequency: rewriteFrequency(frequency) } : {}), + ...(alertsFilter + ? { + alerts_filter: alertsFilter, + } + : {}), + }) + ); }; diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/bulk_edit/bulk_edit_rules_route.test.ts b/x-pack/plugins/alerting/server/routes/rule/apis/bulk_edit/bulk_edit_rules_route.test.ts index 1b1ed454c5207..9a77d46fb9208 100644 --- a/x-pack/plugins/alerting/server/routes/rule/apis/bulk_edit/bulk_edit_rules_route.test.ts +++ b/x-pack/plugins/alerting/server/routes/rule/apis/bulk_edit/bulk_edit_rules_route.test.ts @@ -114,6 +114,7 @@ describe('bulkEditRulesRoute', () => { foo: true, }, uuid: '123-456', + use_alert_data_for_template: false, }, ], }), diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/create/create_rule_route.test.ts b/x-pack/plugins/alerting/server/routes/rule/apis/create/create_rule_route.test.ts index e952a72ec3859..a12b8ee5e177c 100644 --- a/x-pack/plugins/alerting/server/routes/rule/apis/create/create_rule_route.test.ts +++ b/x-pack/plugins/alerting/server/routes/rule/apis/create/create_rule_route.test.ts @@ -124,6 +124,7 @@ describe('createRuleRoute', () => { }, connector_type_id: 'test', uuid: '123-456', + use_alert_data_for_template: false, }, ], }; @@ -198,6 +199,7 @@ describe('createRuleRoute', () => { "params": Object { "foo": true, }, + "useAlertDataForTemplate": undefined, }, ], "alertTypeId": "1", @@ -314,6 +316,7 @@ describe('createRuleRoute', () => { "params": Object { "foo": true, }, + "useAlertDataForTemplate": undefined, }, ], "alertTypeId": "1", @@ -431,6 +434,7 @@ describe('createRuleRoute', () => { "params": Object { "foo": true, }, + "useAlertDataForTemplate": undefined, }, ], "alertTypeId": "1", @@ -548,6 +552,7 @@ describe('createRuleRoute', () => { "params": Object { "foo": true, }, + "useAlertDataForTemplate": undefined, }, ], "alertTypeId": "1", diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/create/transforms/transform_create_body/v1.ts b/x-pack/plugins/alerting/server/routes/rule/apis/create/transforms/transform_create_body/v1.ts index c6b29f4577f4c..a9e9a25d05c95 100644 --- a/x-pack/plugins/alerting/server/routes/rule/apis/create/transforms/transform_create_body/v1.ts +++ b/x-pack/plugins/alerting/server/routes/rule/apis/create/transforms/transform_create_body/v1.ts @@ -15,25 +15,33 @@ import type { RuleParams } from '../../../../../../application/rule/types'; const transformCreateBodyActions = (actions: CreateRuleActionV1[]): CreateRuleData['actions'] => { if (!actions) return []; - return actions.map(({ frequency, alerts_filter: alertsFilter, ...action }) => { - return { - group: action.group, - id: action.id, - params: action.params, - actionTypeId: action.actionTypeId, - ...(action.uuid ? { uuid: action.uuid } : {}), - ...(frequency - ? { - frequency: { - summary: frequency.summary, - throttle: frequency.throttle, - notifyWhen: frequency.notify_when, - }, - } - : {}), - ...(alertsFilter ? { alertsFilter } : {}), - }; - }); + return actions.map( + ({ + frequency, + alerts_filter: alertsFilter, + use_alert_data_for_template: useAlertDataForTemplate, + ...action + }) => { + return { + group: action.group, + id: action.id, + params: action.params, + actionTypeId: action.actionTypeId, + useAlertDataForTemplate, + ...(action.uuid ? { uuid: action.uuid } : {}), + ...(frequency + ? { + frequency: { + summary: frequency.summary, + throttle: frequency.throttle, + notifyWhen: frequency.notify_when, + }, + } + : {}), + ...(alertsFilter ? { alertsFilter } : {}), + }; + } + ); }; export const transformCreateBody = ( diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/resolve/resolve_rule_route.test.ts b/x-pack/plugins/alerting/server/routes/rule/apis/resolve/resolve_rule_route.test.ts index e83cd05b02edd..929dc6ad2ccc1 100644 --- a/x-pack/plugins/alerting/server/routes/rule/apis/resolve/resolve_rule_route.test.ts +++ b/x-pack/plugins/alerting/server/routes/rule/apis/resolve/resolve_rule_route.test.ts @@ -45,6 +45,7 @@ describe('resolveRuleRoute', () => { foo: true, }, uuid: '123-456', + useAlertDataForTemplate: false, }, ], consumer: 'bar', @@ -101,6 +102,7 @@ describe('resolveRuleRoute', () => { params: mockedRule.actions[0].params, connector_type_id: mockedRule.actions[0].actionTypeId, uuid: mockedRule.actions[0].uuid, + use_alert_data_for_template: mockedRule.actions[0].useAlertDataForTemplate, }, ], outcome: 'aliasMatch', diff --git a/x-pack/plugins/alerting/server/routes/rule/transforms/transform_rule_to_rule_response/v1.ts b/x-pack/plugins/alerting/server/routes/rule/transforms/transform_rule_to_rule_response/v1.ts index 74508cec9a640..3968e0e271620 100644 --- a/x-pack/plugins/alerting/server/routes/rule/transforms/transform_rule_to_rule_response/v1.ts +++ b/x-pack/plugins/alerting/server/routes/rule/transforms/transform_rule_to_rule_response/v1.ts @@ -49,11 +49,21 @@ export const transformRuleToRuleResponse = ( consumer: rule.consumer, schedule: rule.schedule, actions: rule.actions.map( - ({ group, id, actionTypeId, params, frequency, uuid, alertsFilter }) => ({ + ({ + group, + id, + actionTypeId, + params, + frequency, + uuid, + alertsFilter, + useAlertDataForTemplate, + }) => ({ group, id, params, connector_type_id: actionTypeId, + use_alert_data_for_template: useAlertDataForTemplate ?? false, ...(frequency ? { frequency: { diff --git a/x-pack/plugins/alerting/server/routes/update_rule.test.ts b/x-pack/plugins/alerting/server/routes/update_rule.test.ts index 5a4b3a19c0d7c..87901feab64cf 100644 --- a/x-pack/plugins/alerting/server/routes/update_rule.test.ts +++ b/x-pack/plugins/alerting/server/routes/update_rule.test.ts @@ -132,6 +132,7 @@ describe('updateRuleRoute', () => { "params": Object { "baz": true, }, + "useAlertDataForTemplate": undefined, "uuid": "1234-5678", }, ], diff --git a/x-pack/plugins/alerting/server/task_runner/execution_handler.ts b/x-pack/plugins/alerting/server/task_runner/execution_handler.ts index 288a4126c25a2..dd1caa21a240c 100644 --- a/x-pack/plugins/alerting/server/task_runner/execution_handler.ts +++ b/x-pack/plugins/alerting/server/task_runner/execution_handler.ts @@ -20,12 +20,16 @@ import { ActionsClient } from '@kbn/actions-plugin/server/actions_client'; import { chunk } from 'lodash'; import { GetSummarizedAlertsParams, IAlertsClient } from '../alerts_client/types'; import { AlertingEventLogger } from '../lib/alerting_event_logger/alerting_event_logger'; -import { parseDuration, CombinedSummarizedAlerts, ThrottledActions } from '../types'; +import { AlertHit, parseDuration, CombinedSummarizedAlerts, ThrottledActions } from '../types'; import { RuleRunMetricsStore } from '../lib/rule_run_metrics_store'; import { injectActionParams } from './inject_action_params'; import { Executable, ExecutionHandlerOptions, RuleTaskInstance } from './types'; import { TaskRunnerContext } from './task_runner_factory'; -import { transformActionParams, transformSummaryActionParams } from './transform_action_params'; +import { + transformActionParams, + TransformActionParamsOptions, + transformSummaryActionParams, +} from './transform_action_params'; import { Alert } from '../alert'; import { NormalizedRuleType } from '../rule_type_registry'; import { @@ -292,33 +296,40 @@ export class ExecutionHandler< }; } else { const ruleUrl = this.buildRuleUrl(spaceId); + const executableAlert = alert!; + const transformActionParamsOptions: TransformActionParamsOptions = { + actionsPlugin, + alertId: ruleId, + alertType: this.ruleType.id, + actionTypeId, + alertName: this.rule.name, + spaceId, + tags: this.rule.tags, + alertInstanceId: executableAlert.getId(), + alertUuid: executableAlert.getUuid(), + alertActionGroup: actionGroup, + alertActionGroupName: this.ruleTypeActionGroups!.get(actionGroup)!, + context: executableAlert.getContext(), + actionId: action.id, + state: executableAlert.getState(), + kibanaBaseUrl: this.taskRunnerContext.kibanaBaseUrl, + alertParams: this.rule.params, + actionParams: action.params, + flapping: executableAlert.getFlapping(), + ruleUrl: ruleUrl?.absoluteUrl, + }; + + if (executableAlert.isAlertAsData()) { + transformActionParamsOptions.aadAlert = executableAlert.getAlertAsData(); + } + const actionToRun = { ...action, params: injectActionParams({ actionTypeId, ruleUrl, ruleName: this.rule.name, - actionParams: transformActionParams({ - actionsPlugin, - alertId: ruleId, - alertType: this.ruleType.id, - actionTypeId, - alertName: this.rule.name, - spaceId, - tags: this.rule.tags, - alertInstanceId: alert.getId(), - alertUuid: alert.getUuid(), - alertActionGroup: actionGroup, - alertActionGroupName: this.ruleTypeActionGroups!.get(actionGroup)!, - context: alert.getContext(), - actionId: action.id, - state: alert.getState(), - kibanaBaseUrl: this.taskRunnerContext.kibanaBaseUrl, - alertParams: this.rule.params, - actionParams: action.params, - flapping: alert.getFlapping(), - ruleUrl: ruleUrl?.absoluteUrl, - }), + actionParams: transformActionParams(transformActionParamsOptions), }), }; @@ -570,7 +581,6 @@ export class ExecutionHandler< for (const action of this.rule.actions) { const alertsArray = Object.entries(alerts); let summarizedAlerts = null; - if (this.shouldGetSummarizedAlerts({ action, throttledSummaryActions })) { summarizedAlerts = await this.getSummarizedAlerts({ action, @@ -634,6 +644,15 @@ export class ExecutionHandler< continue; } + if (summarizedAlerts) { + const alertAsData = summarizedAlerts.all.data.find( + (alertHit: AlertHit) => alertHit._id === alert.getUuid() + ); + if (alertAsData) { + alert.setAlertAsData(alertAsData); + } + } + if (action.group === actionGroup && !this.isAlertMuted(alertId)) { if ( this.isRecoveredAlert(action.group) || @@ -667,12 +686,13 @@ export class ExecutionHandler< } return false; } - + if (action.useAlertDataForTemplate) { + return true; + } // we fetch summarizedAlerts to filter alerts in memory as well if (!isSummaryAction(action) && !action.alertsFilter) { return false; } - if ( isSummaryAction(action) && isSummaryActionThrottled({ diff --git a/x-pack/plugins/alerting/server/task_runner/transform_action_params.ts b/x-pack/plugins/alerting/server/task_runner/transform_action_params.ts index 7076e1fc7e35e..c9c55e07c3d75 100644 --- a/x-pack/plugins/alerting/server/task_runner/transform_action_params.ts +++ b/x-pack/plugins/alerting/server/task_runner/transform_action_params.ts @@ -6,6 +6,7 @@ */ import { PluginStartContract as ActionsPluginStartContract } from '@kbn/actions-plugin/server'; +import { AADAlert } from '@kbn/alerts-as-data-utils'; import { mapKeys, snakeCase } from 'lodash/fp'; import { RuleActionParams, @@ -15,7 +16,7 @@ import { SanitizedRule, } from '../types'; -interface TransformActionParamsOptions { +export interface TransformActionParamsOptions { actionsPlugin: ActionsPluginStartContract; alertId: string; alertType: string; @@ -35,6 +36,7 @@ interface TransformActionParamsOptions { context: AlertInstanceContext; ruleUrl?: string; flapping: boolean; + aadAlert?: AADAlert; } interface SummarizedAlertsWithAll { @@ -76,40 +78,45 @@ export function transformActionParams({ alertParams, ruleUrl, flapping, + aadAlert, }: TransformActionParamsOptions): RuleActionParams { // when the list of variables we pass in here changes, // the UI will need to be updated as well; see: // x-pack/plugins/triggers_actions_ui/public/application/lib/action_variables.ts - const variables = { - alertId, - alertName, - spaceId, - tags, - alertInstanceId, - alertActionGroup, - alertActionGroupName, - context, - date: new Date().toISOString(), - state, - kibanaBaseUrl, - params: alertParams, - rule: { - params: alertParams, - id: alertId, - name: alertName, - type: alertType, - spaceId, - tags, - url: ruleUrl, - }, - alert: { - id: alertInstanceId, - uuid: alertUuid, - actionGroup: alertActionGroup, - actionGroupName: alertActionGroupName, - flapping, - }, - }; + const variables = + aadAlert !== undefined + ? aadAlert + : { + alertId, + alertName, + spaceId, + tags, + alertInstanceId, + alertActionGroup, + alertActionGroupName, + context, + date: new Date().toISOString(), + state, + kibanaBaseUrl, + params: alertParams, + rule: { + params: alertParams, + id: alertId, + name: alertName, + type: alertType, + spaceId, + tags, + url: ruleUrl, + }, + alert: { + id: alertInstanceId, + uuid: alertUuid, + actionGroup: alertActionGroup, + actionGroupName: alertActionGroupName, + flapping, + }, + }; + return actionsPlugin.renderActionParameterTemplates( actionTypeId, actionId, diff --git a/x-pack/plugins/stack_connectors/common/experimental_features.ts b/x-pack/plugins/stack_connectors/common/experimental_features.ts index 4ac02dd9f06db..b7f1fe2c1b87b 100644 --- a/x-pack/plugins/stack_connectors/common/experimental_features.ts +++ b/x-pack/plugins/stack_connectors/common/experimental_features.ts @@ -12,7 +12,7 @@ export type ExperimentalFeatures = typeof allowedExperimentalValues; * This object is then used to validate and parse the value entered. */ export const allowedExperimentalValues = Object.freeze({ - isMustacheAutocompleteOn: false, + isMustacheAutocompleteOn: true, sentinelOneConnectorOn: false, }); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/email/email_params.tsx b/x-pack/plugins/stack_connectors/public/connector_types/email/email_params.tsx index 2100e2b0d823c..7da7ae0ce417d 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/email/email_params.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/email/email_params.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useState, useEffect, useMemo } from 'react'; +import React, { useState, useEffect } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiComboBox, EuiButtonEmpty, EuiFormRow } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -15,8 +15,6 @@ import { TextAreaWithMessageVariables, } from '@kbn/triggers-actions-ui-plugin/public'; import { EmailActionParams } from '../types'; -import { getIsExperimentalFeatureEnabled } from '../../common/get_experimental_features'; -import { TextAreaWithAutocomplete } from '../../components/text_area_with_autocomplete'; const noop = () => {}; @@ -32,8 +30,8 @@ export const EmailParamsFields = ({ onBlur = noop, showEmailSubjectAndMessage = true, useDefaultMessage, + ruleTypeId, }: ActionParamsProps) => { - const isMustacheAutocompleteOn = getIsExperimentalFeatureEnabled('isMustacheAutocompleteOn'); const { to, cc, bcc, subject, message } = actionParams; const toOptions = to ? to.map((label: string) => ({ label })) : []; const ccOptions = cc ? cc.map((label: string) => ({ label })) : []; @@ -64,10 +62,6 @@ export const EmailParamsFields = ({ const isBCCInvalid: boolean = errors.bcc !== undefined && errors.bcc.length > 0 && bcc !== undefined; - const TextAreaComponent = useMemo(() => { - return isMustacheAutocompleteOn ? TextAreaWithAutocomplete : TextAreaWithMessageVariables; - }, [isMustacheAutocompleteOn]); - return ( <> )} {showEmailSubjectAndMessage && ( - { diff --git a/x-pack/plugins/stack_connectors/public/connector_types/server_log/server_log_params.tsx b/x-pack/plugins/stack_connectors/public/connector_types/server_log/server_log_params.tsx index 7c74245289833..95314c5fb7126 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/server_log/server_log_params.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/server_log/server_log_params.tsx @@ -37,7 +37,7 @@ export const ServerLogParamsFields: React.FunctionComponent< editAction('level', 'info', index); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, [actionParams.level]); const [[isUsingDefault, defaultMessageUsed], setDefaultMessageUsage] = useState< [boolean, string | undefined] diff --git a/x-pack/plugins/stack_connectors/public/connector_types/slack/action_form.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/slack/action_form.test.tsx index 4e86a25ed26d8..ed36daef3abf4 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/slack/action_form.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/slack/action_form.test.tsx @@ -16,6 +16,7 @@ import { IToasts } from '@kbn/core/public'; import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import { getConnectorType as getSlackConnectorType } from './slack'; import { getSlackApiConnectorType } from '../slack_api'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; jest.mock('@kbn/triggers-actions-ui-plugin/public/common/lib/kibana'); jest.mock('@kbn/kibana-react-plugin/public/ui_settings/use_ui_setting', () => ({ @@ -51,6 +52,14 @@ const { loadActionTypes } = jest.requireMock( '@kbn/triggers-actions-ui-plugin/public/application/lib/action_connector_api/connector_types' ); const useKibanaMock = useKibana as jest.Mocked; +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + cacheTime: 0, + }, + }, +}); // GET api/actions/connector_types?feature_id=alerting loadActionTypes.mockResolvedValue([ @@ -95,6 +104,7 @@ actionTypeRegistry.register(getSlackApiConnectorType()); const baseProps = { actions: [], defaultActionGroupId: 'metrics.inventory_threshold.fired', + ruleTypeId: 'metrics.inventory_threshold', hasAlertsMappings: true, featureId: 'alerting', recoveryActionGroup: 'recovered', @@ -170,7 +180,9 @@ describe('ActionForm - Slack API Connector', () => { render( - + + + ); diff --git a/x-pack/plugins/stack_connectors/tsconfig.json b/x-pack/plugins/stack_connectors/tsconfig.json index 4e47b703bf433..e577ce018bdf3 100644 --- a/x-pack/plugins/stack_connectors/tsconfig.json +++ b/x-pack/plugins/stack_connectors/tsconfig.json @@ -8,7 +8,7 @@ // have to declare *.json explicitly due to https://github.com/microsoft/TypeScript/issues/25636 "server/**/*.json", "common/**/*", - "public/**/*" + "public/**/*", ], "kbn_references": [ "@kbn/core", @@ -33,7 +33,6 @@ "@kbn/core-saved-objects-common", "@kbn/core-http-browser-mocks", "@kbn/core-saved-objects-api-server-mocks", - "@kbn/alerts-ui-shared", "@kbn/alerting-plugin", "@kbn/securitysolution-ecs", "@kbn/ui-theme", diff --git a/x-pack/plugins/triggers_actions_ui/common/experimental_features.ts b/x-pack/plugins/triggers_actions_ui/common/experimental_features.ts index 501d9b7f22239..066487d5af6a4 100644 --- a/x-pack/plugins/triggers_actions_ui/common/experimental_features.ts +++ b/x-pack/plugins/triggers_actions_ui/common/experimental_features.ts @@ -19,6 +19,8 @@ export const allowedExperimentalValues = Object.freeze({ rulesDetailLogs: true, ruleUseExecutionStatus: false, ruleKqlBar: false, + isMustacheAutocompleteOn: false, + showMustacheAutocompleteSwitch: false, }); type ExperimentalConfigKeys = Array; @@ -29,7 +31,8 @@ const allowedKeys = Object.keys(allowedExperimentalValues) as Readonly void; @@ -291,7 +291,6 @@ export const TextAreaWithAutocomplete: React.FunctionComponent closeList(), [closeList]); - const onScroll = useCallback( (evt) => { // FUTURE ENGINEER -> we need to make sure to not close the autocomplete option list diff --git a/x-pack/plugins/stack_connectors/public/lib/filter_suggestions_for_autocomplete.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/text_area_with_autocomplete/lib/filter_suggestions_for_autocomplete.test.ts similarity index 100% rename from x-pack/plugins/stack_connectors/public/lib/filter_suggestions_for_autocomplete.test.ts rename to x-pack/plugins/triggers_actions_ui/public/application/components/text_area_with_autocomplete/lib/filter_suggestions_for_autocomplete.test.ts diff --git a/x-pack/plugins/stack_connectors/public/lib/filter_suggestions_for_autocomplete.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/text_area_with_autocomplete/lib/filter_suggestions_for_autocomplete.ts similarity index 100% rename from x-pack/plugins/stack_connectors/public/lib/filter_suggestions_for_autocomplete.ts rename to x-pack/plugins/triggers_actions_ui/public/application/components/text_area_with_autocomplete/lib/filter_suggestions_for_autocomplete.ts diff --git a/x-pack/plugins/stack_connectors/public/lib/template_action_variable.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/text_area_with_autocomplete/lib/template_action_variable.test.ts similarity index 100% rename from x-pack/plugins/stack_connectors/public/lib/template_action_variable.test.ts rename to x-pack/plugins/triggers_actions_ui/public/application/components/text_area_with_autocomplete/lib/template_action_variable.test.ts diff --git a/x-pack/plugins/stack_connectors/public/lib/template_action_variable.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/text_area_with_autocomplete/lib/template_action_variable.ts similarity index 100% rename from x-pack/plugins/stack_connectors/public/lib/template_action_variable.ts rename to x-pack/plugins/triggers_actions_ui/public/application/components/text_area_with_autocomplete/lib/template_action_variable.ts diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/text_area_with_message_variables.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/text_area_with_message_variables.tsx index c19ac416b4695..e666082638ea7 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/text_area_with_message_variables.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/text_area_with_message_variables.tsx @@ -9,6 +9,8 @@ import React, { useState } from 'react'; import { EuiTextArea, EuiFormRow } from '@elastic/eui'; import { ActionVariable } from '@kbn/alerting-plugin/common'; import { AddMessageVariables } from '@kbn/alerts-ui-shared'; +import { getIsExperimentalFeatureEnabled } from '../../common/get_experimental_features'; +import { TextAreaWithAutocomplete } from './text_area_with_autocomplete'; import { templateActionVariable } from '../lib'; interface Props { @@ -23,7 +25,7 @@ interface Props { errors?: string[]; } -export const TextAreaWithMessageVariables: React.FunctionComponent = ({ +const TextAreaWithMessageVariablesLegacy: React.FunctionComponent = ({ messageVariables, paramsProperty, index, @@ -87,3 +89,15 @@ export const TextAreaWithMessageVariables: React.FunctionComponent = ({ ); }; + +export const TextAreaWithMessageVariables = (props: Props) => { + let isMustacheAutocompleteOn; + try { + isMustacheAutocompleteOn = getIsExperimentalFeatureEnabled('isMustacheAutocompleteOn'); + } catch (e) { + isMustacheAutocompleteOn = false; + } + + if (isMustacheAutocompleteOn) return TextAreaWithAutocomplete(props); + return TextAreaWithMessageVariablesLegacy(props); +}; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_rule_aad_template_fields.ts b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_rule_aad_template_fields.ts new file mode 100644 index 0000000000000..99be4c4b118b8 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_rule_aad_template_fields.ts @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { HttpStart } from '@kbn/core-http-browser'; +import { DataViewField } from '@kbn/data-views-plugin/common'; +import { BASE_RAC_ALERTS_API_PATH } from '@kbn/rule-registry-plugin/common'; +import { ActionVariable } from '@kbn/alerting-plugin/common'; +import { useEffect, useMemo, useState } from 'react'; +import { EcsFlat } from '@kbn/ecs'; +import { EcsMetadata } from '@kbn/alerts-as-data-utils/src/field_maps/types'; +import { isEmpty } from 'lodash'; + +export const getDescription = (fieldName: string, ecsFlat: Record) => { + let ecsField = ecsFlat[fieldName]; + if (isEmpty(ecsField?.description ?? '') && fieldName.includes('kibana.alert.')) { + ecsField = ecsFlat[fieldName.replace('kibana.alert.', '')]; + } + return ecsField?.description ?? ''; +}; + +async function loadRuleTypeAadTemplateFields({ + http, + ruleTypeId, +}: { + http: HttpStart; + ruleTypeId: string; +}): Promise { + if (!ruleTypeId || !http) return []; + const fields = await http.get(`${BASE_RAC_ALERTS_API_PATH}/aad_fields`, { + query: { ruleTypeId }, + }); + + return fields; +} + +export function useRuleTypeAadTemplateFields( + http: HttpStart, + ruleTypeId: string | undefined, + enabled: boolean +): { isLoading: boolean; fields: ActionVariable[] } { + // Reimplement useQuery here; this hook is sometimes called in contexts without a QueryClientProvider + const [isLoading, setIsLoading] = useState(false); + const [data, setData] = useState([]); + + useEffect(() => { + if (enabled && data.length === 0 && ruleTypeId) { + setIsLoading(true); + loadRuleTypeAadTemplateFields({ http, ruleTypeId }).then((res) => { + setData(res); + setIsLoading(false); + }); + } + }, [data, enabled, http, ruleTypeId]); + + return useMemo( + () => ({ + isLoading, + fields: data.map((d) => ({ + name: d.name, + description: getDescription(d.name, EcsFlat), + })), + }), + [data, isLoading] + ); +} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/clone.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/clone.test.ts index 54e987d18c917..cc9b1ddab0218 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/clone.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/clone.test.ts @@ -75,6 +75,7 @@ describe('cloneRule', () => { "level": "info", "message": "alert ", }, + "useAlertDataForTemplate": undefined, "uuid": "123456", }, ], diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/common_transformations.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/common_transformations.ts index e659cfbdfe4bd..da0cd8419e078 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/common_transformations.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/common_transformations.ts @@ -16,11 +16,13 @@ const transformAction: RewriteRequestCase = ({ params, frequency, alerts_filter: alertsFilter, + use_alert_data_for_template: useAlertDataForTemplate, }) => ({ group, id, params, actionTypeId, + useAlertDataForTemplate, ...(frequency ? { frequency: { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/create.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/create.ts index d8b8c85ad26a5..a2ab9c5b4dc3c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/create.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/create.ts @@ -27,17 +27,20 @@ const rewriteBodyRequest: RewriteResponseCase = ({ }): any => ({ ...res, rule_type_id: ruleTypeId, - actions: actions.map(({ group, id, params, frequency, alertsFilter }) => ({ - group, - id, - params, - frequency: { - notify_when: frequency!.notifyWhen, - throttle: frequency!.throttle, - summary: frequency!.summary, - }, - alerts_filter: alertsFilter, - })), + actions: actions.map( + ({ group, id, params, frequency, alertsFilter, useAlertDataForTemplate }) => ({ + group, + id, + params, + frequency: { + notify_when: frequency!.notifyWhen, + throttle: frequency!.throttle, + summary: frequency!.summary, + }, + alerts_filter: alertsFilter, + use_alert_data_for_template: useAlertDataForTemplate, + }) + ), }); export async function createRule({ diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/update.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/update.ts index ee2418d22262b..0d6371cfa1fdb 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/update.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/update.ts @@ -17,18 +17,21 @@ type RuleUpdatesBody = Pick< >; const rewriteBodyRequest: RewriteResponseCase = ({ actions, ...res }): any => ({ ...res, - actions: actions.map(({ group, id, params, frequency, uuid, alertsFilter }) => ({ - group, - id, - params, - frequency: { - notify_when: frequency!.notifyWhen, - throttle: frequency!.throttle, - summary: frequency!.summary, - }, - alerts_filter: alertsFilter, - ...(uuid && { uuid }), - })), + actions: actions.map( + ({ group, id, params, frequency, uuid, alertsFilter, useAlertDataForTemplate }) => ({ + group, + id, + params, + frequency: { + notify_when: frequency!.notifyWhen, + throttle: frequency!.throttle, + summary: frequency!.summary, + }, + alerts_filter: alertsFilter, + use_alert_data_for_template: useAlertDataForTemplate, + ...(uuid && { uuid }), + }) + ), }); export async function updateRule({ diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx index d515cd9510cc6..6d91e1837b830 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx @@ -60,6 +60,7 @@ export interface ActionAccordionFormProps { defaultActionMessage?: string; setActionIdByIndex: (id: string, index: number) => void; setActionGroupIdByIndex?: (group: string, index: number) => void; + setActionUseAlertDataForTemplate?: (enabled: boolean, index: number) => void; setActions: (actions: RuleAction[]) => void; setActionParamsProperty: (key: string, value: RuleActionParam, index: number) => void; setActionFrequencyProperty: (key: string, value: RuleActionParam, index: number) => void; @@ -70,6 +71,7 @@ export interface ActionAccordionFormProps { ) => void; featureId: string; producerId: string; + ruleTypeId?: string; messageVariables?: ActionVariables; summaryMessageVariables?: ActionVariables; setHasActionsDisabled?: (value: boolean) => void; @@ -84,7 +86,6 @@ export interface ActionAccordionFormProps { minimumThrottleInterval?: [number | undefined, string]; notifyWhenSelectOptions?: NotifyWhenSelectOptions[]; defaultRuleFrequency?: RuleActionFrequency; - ruleTypeId?: string; hasFieldsForAAD?: boolean; disableErrorMessages?: boolean; } @@ -99,6 +100,7 @@ export const ActionForm = ({ defaultActionGroupId, setActionIdByIndex, setActionGroupIdByIndex, + setActionUseAlertDataForTemplate, setActions, setActionParamsProperty, setActionFrequencyProperty, @@ -437,6 +439,7 @@ export const ActionForm = ({ actionConnector={actionConnector} index={index} key={`action-form-action-at-${actionItem.uuid}`} + setActionUseAlertDataForTemplate={setActionUseAlertDataForTemplate} setActionParamsProperty={setActionParamsProperty} setActionFrequencyProperty={setActionFrequencyProperty} setActionAlertsFilterProperty={setActionAlertsFilterProperty} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.test.tsx index cc0594b365b9e..8b774325af985 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.test.tsx @@ -64,6 +64,19 @@ jest.mock('@kbn/kibana-react-plugin/public/ui_settings/use_ui_setting', () => ({ useUiSetting: jest.fn().mockImplementation((_, defaultValue) => defaultValue), })); +jest.mock('../../../common/get_experimental_features', () => ({ + getIsExperimentalFeatureEnabled() { + return true; + }, +})); + +jest.mock('../../hooks/use_rule_aad_template_fields', () => ({ + useRuleTypeAadTemplateFields: () => ({ + isLoading: false, + fields: [], + }), +})); + describe('action_type_form', () => { afterEach(() => { jest.clearAllMocks(); @@ -402,6 +415,59 @@ describe('action_type_form', () => { ]); }); + it('clears the default message when the user toggles the "Use template fields from alerts index" switch ', async () => { + const setActionParamsProperty = jest.fn(); + const actionType = actionTypeRegistryMock.createMockActionTypeModel({ + id: '.pagerduty', + iconClass: 'test', + selectMessage: 'test', + validateParams: (): Promise> => { + const validationResult = { errors: {} }; + return Promise.resolve(validationResult); + }, + actionConnectorFields: null, + actionParamsFields: mockedActionParamsFields, + defaultActionParams: { + dedupKey: '{{rule.id}}:{{alert.id}}', + eventAction: 'resolve', + }, + }); + actionTypeRegistry.get.mockReturnValue(actionType); + + const wrapper = render( + + {getActionTypeForm({ + index: 1, + ruleTypeId: 'test', + setActionParamsProperty, + actionItem: { + id: '123', + actionTypeId: '.pagerduty', + group: 'recovered', + params: { + eventAction: 'recovered', + dedupKey: '232323', + summary: '2323', + source: 'source', + severity: '1', + timestamp: new Date().toISOString(), + component: 'test', + group: 'group', + class: 'test class', + }, + }, + })} + + ); + + expect(wrapper.getByTestId('mustacheAutocompleteSwitch')).toBeTruthy(); + + await act(async () => { + wrapper.getByTestId('mustacheAutocompleteSwitch').click(); + }); + expect(setActionParamsProperty).toHaveBeenCalledWith('dedupKey', '', 1); + }); + describe('Customize notify when options', () => { it('should not have "On status changes" notify when option for summary actions', async () => { const actionType = actionTypeRegistryMock.createMockActionTypeModel({ @@ -523,6 +589,7 @@ function getActionTypeForm({ onAddConnector, onDeleteAction, onConnectorSelected, + setActionParamsProperty, setActionFrequencyProperty, setActionAlertsFilterProperty, hasAlertsMappings = true, @@ -530,6 +597,7 @@ function getActionTypeForm({ summaryMessageVariables = { context: [], state: [], params: [] }, notifyWhenSelectOptions, defaultNotifyWhenValue, + ruleTypeId, }: { index?: number; actionConnector?: ActionConnector, Record>; @@ -541,12 +609,14 @@ function getActionTypeForm({ onDeleteAction?: () => void; onConnectorSelected?: (id: string) => void; setActionFrequencyProperty?: () => void; + setActionParamsProperty?: () => void; setActionAlertsFilterProperty?: () => void; hasAlertsMappings?: boolean; messageVariables?: ActionVariables; summaryMessageVariables?: ActionVariables; notifyWhenSelectOptions?: NotifyWhenSelectOptions[]; defaultNotifyWhenValue?: RuleNotifyWhenType; + ruleTypeId?: string; }) { const actionConnectorDefault = { actionTypeId: '.pagerduty', @@ -628,7 +698,7 @@ function getActionTypeForm({ onDeleteAction={onDeleteAction ?? jest.fn()} onConnectorSelected={onConnectorSelected ?? jest.fn()} defaultActionGroupId={defaultActionGroupId ?? 'default'} - setActionParamsProperty={jest.fn()} + setActionParamsProperty={setActionParamsProperty ?? jest.fn()} setActionFrequencyProperty={setActionFrequencyProperty ?? jest.fn()} setActionAlertsFilterProperty={setActionAlertsFilterProperty ?? jest.fn()} index={index ?? 1} @@ -641,6 +711,7 @@ function getActionTypeForm({ defaultNotifyWhenValue={defaultNotifyWhenValue} producerId="infrastructure" featureId="infrastructure" + ruleTypeId={ruleTypeId} /> ); } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx index 3f0982b1b24f3..83ad089c8e691 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx @@ -29,6 +29,7 @@ import { EuiSplitPanel, useEuiTheme, EuiCallOut, + EuiSwitch, } from '@elastic/eui'; import { isEmpty, partition, some } from 'lodash'; import { @@ -42,6 +43,8 @@ import { getDurationUnitValue, parseDuration, } from '@kbn/alerting-plugin/common/parse_duration'; +import { SavedObjectAttribute } from '@kbn/core-saved-objects-api-server'; +import { getIsExperimentalFeatureEnabled } from '../../../common/get_experimental_features'; import { betaBadgeProps } from './beta_badge_props'; import { IErrorObject, @@ -64,6 +67,7 @@ import { validateParamsForWarnings } from '../../lib/validate_params_for_warning import { ActionAlertsFilterTimeframe } from './action_alerts_filter_timeframe'; import { ActionAlertsFilterQuery } from './action_alerts_filter_query'; import { validateActionFilterQuery } from '../../lib/value_validators'; +import { useRuleTypeAadTemplateFields } from '../../hooks/use_rule_aad_template_fields'; export type ActionTypeFormProps = { actionItem: RuleAction; @@ -72,6 +76,7 @@ export type ActionTypeFormProps = { onAddConnector: () => void; onConnectorSelected: (id: string) => void; onDeleteAction: () => void; + setActionUseAlertDataForTemplate?: (enabled: boolean, index: number) => void; setActionParamsProperty: (key: string, value: RuleActionParam, index: number) => void; setActionFrequencyProperty: (key: string, value: RuleActionParam, index: number) => void; setActionAlertsFilterProperty: ( @@ -120,6 +125,7 @@ export const ActionTypeForm = ({ onAddConnector, onConnectorSelected, onDeleteAction, + setActionUseAlertDataForTemplate, setActionParamsProperty, setActionFrequencyProperty, setActionAlertsFilterProperty, @@ -148,7 +154,7 @@ export const ActionTypeForm = ({ }: ActionTypeFormProps) => { const { application: { capabilities }, - http: { basePath }, + http, } = useKibana().services; const { euiTheme } = useEuiTheme(); const [isOpen, setIsOpen] = useState(true); @@ -178,6 +184,53 @@ export const ActionTypeForm = ({ const isSummaryAction = actionItem.frequency?.summary; + const [useAadTemplateFields, setUseAadTemplateField] = useState( + actionItem?.useAlertDataForTemplate ?? false + ); + const [storedActionParamsForAadToggle, setStoredActionParamsForAadToggle] = useState< + Record + >({}); + + const { fields: aadTemplateFields } = useRuleTypeAadTemplateFields( + http, + ruleTypeId, + useAadTemplateFields + ); + + const templateFields = useMemo( + () => (useAadTemplateFields ? aadTemplateFields : availableActionVariables), + [aadTemplateFields, availableActionVariables, useAadTemplateFields] + ); + + let showMustacheAutocompleteSwitch; + try { + showMustacheAutocompleteSwitch = + getIsExperimentalFeatureEnabled('showMustacheAutocompleteSwitch') && ruleTypeId; + } catch (e) { + showMustacheAutocompleteSwitch = false; + } + + const handleUseAadTemplateFields = useCallback(() => { + setUseAadTemplateField((prevVal) => { + if (setActionUseAlertDataForTemplate) { + setActionUseAlertDataForTemplate(!prevVal, index); + } + return !prevVal; + }); + const currentActionParams = { ...actionItem.params }; + for (const key of Object.keys(currentActionParams)) { + setActionParamsProperty(key, storedActionParamsForAadToggle[key] ?? '', index); + } + setStoredActionParamsForAadToggle(currentActionParams); + }, [ + setActionUseAlertDataForTemplate, + storedActionParamsForAadToggle, + setStoredActionParamsForAadToggle, + setActionParamsProperty, + actionItem.params, + index, + ]); + const getDefaultParams = async () => { const connectorType = await actionTypeRegistry.get(actionItem.actionTypeId); let defaultParams; @@ -227,9 +280,15 @@ export const ActionTypeForm = ({ const defaultParams = await getDefaultParams(); if (defaultParams) { for (const [key, paramValue] of Object.entries(defaultParams)) { + const defaultAADParams: typeof defaultParams = {}; if (actionItem.params[key] === undefined || actionItem.params[key] === null) { setActionParamsProperty(key, paramValue, index); + // Add default param to AAD defaults only if it does not contain any template code + if (typeof paramValue !== 'string' || !paramValue.match(/{{.*?}}/g)) { + defaultAADParams[key] = paramValue; + } } + setStoredActionParamsForAadToggle(defaultAADParams); } } })(); @@ -240,9 +299,14 @@ export const ActionTypeForm = ({ (async () => { const defaultParams = await getDefaultParams(); if (defaultParams && actionGroup) { + const defaultAADParams: typeof defaultParams = {}; for (const [key, paramValue] of Object.entries(defaultParams)) { setActionParamsProperty(key, paramValue, index); + if (!paramValue.match(/{{.*?}}/g)) { + defaultAADParams[key] = paramValue; + } } + setStoredActionParamsForAadToggle(defaultAADParams); } })(); // eslint-disable-next-line react-hooks/exhaustive-deps @@ -273,6 +337,12 @@ export const ActionTypeForm = ({ })(); }, [actionItem, disableErrorMessages]); + useEffect(() => { + if (isEmpty(storedActionParamsForAadToggle) && actionItem.params.subAction) { + setStoredActionParamsForAadToggle(actionItem.params); + } + }, [actionItem.params, storedActionParamsForAadToggle]); + const canSave = hasSaveActionsCapability(capabilities); const actionGroupDisplay = ( @@ -463,39 +533,54 @@ export const ActionTypeForm = ({ {ParamsFieldsComponent ? ( - - { - setWarning( - validateParamsForWarnings( - value, - basePath.publicBaseUrl, - availableActionVariables - ) - ); - setActionParamsProperty(key, value, i); - }} - messageVariables={availableActionVariables} - defaultMessage={ - // if action is a summary action, show the default summary message - isSummaryAction - ? defaultSummaryMessage - : selectedActionGroup?.defaultActionMessage ?? defaultActionMessage - } - useDefaultMessage={useDefaultMessage} - actionConnector={actionConnector} - executionMode={ActionConnectorMode.ActionForm} - /> - {warning ? ( - <> - - - - ) : null} - + + {showMustacheAutocompleteSwitch && ( + + + + )} + + + { + setWarning( + validateParamsForWarnings( + value, + http.basePath.publicBaseUrl, + availableActionVariables + ) + ); + setActionParamsProperty(key, value, i); + }} + messageVariables={templateFields} + defaultMessage={ + // if action is a summary action, show the default summary message + isSummaryAction + ? defaultSummaryMessage + : selectedActionGroup?.defaultActionMessage ?? defaultActionMessage + } + useDefaultMessage={useDefaultMessage} + actionConnector={actionConnector} + executionMode={ActionConnectorMode.ActionForm} + ruleTypeId={ruleTypeId} + /> + {warning ? ( + <> + + + + ) : null} + + + ) : null} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx index c88e3beba0cad..c321ba5a01d7a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx @@ -839,6 +839,9 @@ export const RuleForm = ({ : { ...actionGroup, defaultActionMessage: ruleTypeModel?.defaultActionMessage } )} recoveryActionGroup={recoveryActionGroup} + setActionUseAlertDataForTemplate={(enabled: boolean, index: number) => { + setActionProperty('useAlertDataForTemplate', enabled, index); + }} setActionIdByIndex={(id: string, index: number) => setActionProperty('id', id, index)} setActionGroupIdByIndex={(group: string, index: number) => setActionProperty('group', group, index) diff --git a/x-pack/plugins/triggers_actions_ui/public/types.ts b/x-pack/plugins/triggers_actions_ui/public/types.ts index fbcfbc0d38297..bc0ce10d461e6 100644 --- a/x-pack/plugins/triggers_actions_ui/public/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/types.ts @@ -217,6 +217,7 @@ export interface ActionParamsProps { index: number; editAction: (key: string, value: RuleActionParam, index: number) => void; errors: IErrorObject; + ruleTypeId?: string; messageVariables?: ActionVariable[]; defaultMessage?: string; useDefaultMessage?: boolean; diff --git a/x-pack/plugins/triggers_actions_ui/tsconfig.json b/x-pack/plugins/triggers_actions_ui/tsconfig.json index d2d91cca3486b..600c5b6cdc7d5 100644 --- a/x-pack/plugins/triggers_actions_ui/tsconfig.json +++ b/x-pack/plugins/triggers_actions_ui/tsconfig.json @@ -55,7 +55,8 @@ "@kbn/dashboard-plugin", "@kbn/licensing-plugin", "@kbn/expressions-plugin", - "@kbn/serverless", + "@kbn/core-saved-objects-api-server", + "@kbn/serverless" ], "exclude": ["target/**/*"] } diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/create.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/create.ts index b7cf41d07e823..375eeaaffc44a 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/create.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/create.ts @@ -102,6 +102,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { group: 'default', params: {}, uuid: response.body.actions[0].uuid, + use_alert_data_for_template: false, }, ], enabled: true, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/bulk_edit.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/bulk_edit.ts index c948791e5ea49..ffd11e3fabd38 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/bulk_edit.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/bulk_edit.ts @@ -116,6 +116,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { params: {}, connector_type_id: 'test.noop', uuid: response.body.rules[0].actions[0].uuid, + use_alert_data_for_template: false, }, ]); expect(response.statusCode).to.eql(200); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/create.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/create.ts index eb9f90cb41f2a..0577425103a8c 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/create.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/create.ts @@ -78,6 +78,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { group: 'default', params: {}, uuid: response.body.actions[0].uuid, + use_alert_data_for_template: false, }, ], enabled: true, @@ -181,6 +182,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { group: 'default', params: {}, uuid: response.body.actions[0].uuid, + use_alert_data_for_template: false, }, { id: 'my-slack1', @@ -190,6 +192,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { message: 'something important happened!', }, uuid: response.body.actions[1].uuid, + use_alert_data_for_template: false, }, { id: 'system-connector-test.system-action', @@ -197,6 +200,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { connector_type_id: 'test.system-action', params: {}, uuid: response.body.actions[2].uuid, + use_alert_data_for_template: false, }, ], enabled: true, From 87ec1440dcc1d25938795925237ca5194ee6551e Mon Sep 17 00:00:00 2001 From: Davis Plumlee <56367316+dplumlee@users.noreply.github.com> Date: Tue, 14 Nov 2023 14:04:09 -0500 Subject: [PATCH 6/7] [Security Solution] Changes coverage overview subtechnique display to base off active filters (#170988) --- .../mitre_subtechnique.test.ts | 82 +++++++++++++++++++ .../coverage_overview/mitre_subtechnique.ts | 25 ++++++ .../coverage_overview/mitre_technique.test.ts | 61 ++++++++++---- .../coverage_overview/mitre_technique.ts | 4 + .../pages/coverage_overview/helpers.test.ts | 48 +---------- .../pages/coverage_overview/helpers.ts | 8 -- .../pages/coverage_overview/tactic_panel.tsx | 2 +- .../technique_panel_popover.tsx | 13 ++- 8 files changed, 166 insertions(+), 77 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/model/coverage_overview/mitre_subtechnique.test.ts diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/model/coverage_overview/mitre_subtechnique.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/model/coverage_overview/mitre_subtechnique.test.ts new file mode 100644 index 0000000000000..7129448e762cb --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/model/coverage_overview/mitre_subtechnique.test.ts @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { CoverageOverviewRuleActivity } from '../../../../../common/api/detection_engine'; +import { getNumOfCoveredSubtechniques } from './mitre_subtechnique'; +import type { CoverageOverviewMitreTechnique } from './mitre_technique'; +import { + getMockCoverageOverviewMitreSubTechnique, + getMockCoverageOverviewMitreTechnique, +} from './__mocks__'; + +describe('mitre_subtechniques', () => { + describe('getNumOfCoveredSubtechniques', () => { + it('returns 0 when no subtechniques are present', () => { + const payload: CoverageOverviewMitreTechnique = getMockCoverageOverviewMitreTechnique(); + expect(getNumOfCoveredSubtechniques(payload)).toEqual(0); + }); + + it('returns total number of unique enabled and disabled subtechniques when no filter is passed', () => { + const payload: CoverageOverviewMitreTechnique = { + ...getMockCoverageOverviewMitreTechnique(), + subtechniques: [ + getMockCoverageOverviewMitreSubTechnique(), + { ...getMockCoverageOverviewMitreSubTechnique(), id: 'test-id' }, + ], + }; + expect(getNumOfCoveredSubtechniques(payload)).toEqual(2); + }); + + it('returns total number of unique enabled and disabled subtechniques when both filters are passed', () => { + const payload: CoverageOverviewMitreTechnique = { + ...getMockCoverageOverviewMitreTechnique(), + subtechniques: [ + getMockCoverageOverviewMitreSubTechnique(), + { ...getMockCoverageOverviewMitreSubTechnique(), id: 'test-id' }, + ], + }; + expect( + getNumOfCoveredSubtechniques(payload, [ + CoverageOverviewRuleActivity.Enabled, + CoverageOverviewRuleActivity.Disabled, + ]) + ).toEqual(2); + }); + + it('returns total number of enabled subtechniques when enabled filter is passed', () => { + const payload: CoverageOverviewMitreTechnique = { + ...getMockCoverageOverviewMitreTechnique(), + subtechniques: [ + { + ...getMockCoverageOverviewMitreSubTechnique(), + enabledRules: [], + }, + getMockCoverageOverviewMitreSubTechnique(), + ], + }; + expect(getNumOfCoveredSubtechniques(payload, [CoverageOverviewRuleActivity.Enabled])).toEqual( + 1 + ); + }); + + it('returns total number of disabled subtechniques when disabled filter is passed', () => { + const payload: CoverageOverviewMitreTechnique = { + ...getMockCoverageOverviewMitreTechnique(), + subtechniques: [ + { + ...getMockCoverageOverviewMitreSubTechnique(), + disabledRules: [], + }, + getMockCoverageOverviewMitreSubTechnique(), + ], + }; + expect( + getNumOfCoveredSubtechniques(payload, [CoverageOverviewRuleActivity.Disabled]) + ).toEqual(1); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/model/coverage_overview/mitre_subtechnique.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/model/coverage_overview/mitre_subtechnique.ts index 622213c7e7a6f..0b0e8af2d8a99 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/model/coverage_overview/mitre_subtechnique.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/model/coverage_overview/mitre_subtechnique.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { CoverageOverviewRuleActivity } from '../../../../../common/api/detection_engine'; +import type { CoverageOverviewMitreTechnique } from './mitre_technique'; import type { CoverageOverviewRule } from './rule'; export interface CoverageOverviewMitreSubTechnique { @@ -18,3 +20,26 @@ export interface CoverageOverviewMitreSubTechnique { disabledRules: CoverageOverviewRule[]; availableRules: CoverageOverviewRule[]; } + +export const getNumOfCoveredSubtechniques = ( + technique: CoverageOverviewMitreTechnique, + activity?: CoverageOverviewRuleActivity[] +): number => { + const coveredSubtechniques = new Set(); + for (const subtechnique of technique.subtechniques) { + if ( + (!activity || activity.includes(CoverageOverviewRuleActivity.Enabled)) && + subtechnique.enabledRules.length + ) { + coveredSubtechniques.add(subtechnique.id); + } + + if ( + (!activity || activity.includes(CoverageOverviewRuleActivity.Disabled)) && + subtechnique.disabledRules.length + ) { + coveredSubtechniques.add(subtechnique.id); + } + } + return coveredSubtechniques.size; +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/model/coverage_overview/mitre_technique.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/model/coverage_overview/mitre_technique.test.ts index 01794b34c8ee7..9a89867225ba8 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/model/coverage_overview/mitre_technique.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/model/coverage_overview/mitre_technique.test.ts @@ -6,27 +6,52 @@ */ import { CoverageOverviewRuleActivity } from '../../../../../common/api/detection_engine'; -import { getTotalRuleCount } from './mitre_technique'; -import { getMockCoverageOverviewMitreTechnique } from './__mocks__'; +import type { CoverageOverviewMitreTactic } from './mitre_tactic'; +import type { CoverageOverviewMitreTechnique } from './mitre_technique'; +import { getNumOfCoveredTechniques, getTotalRuleCount } from './mitre_technique'; +import { + getMockCoverageOverviewMitreTactic, + getMockCoverageOverviewMitreTechnique, +} from './__mocks__'; -describe('getTotalRuleCount', () => { - it('returns count of all rules when no activity filter is present', () => { - const payload = getMockCoverageOverviewMitreTechnique(); - expect(getTotalRuleCount(payload)).toEqual(2); - }); +describe('mitre_technique', () => { + describe('getTotalRuleCount', () => { + it('returns count of all rules when no activity filter is present', () => { + const payload: CoverageOverviewMitreTechnique = getMockCoverageOverviewMitreTechnique(); + expect(getTotalRuleCount(payload)).toEqual(2); + }); + + it('returns count of one rule type when an activity filter is present', () => { + const payload: CoverageOverviewMitreTechnique = getMockCoverageOverviewMitreTechnique(); + expect(getTotalRuleCount(payload, [CoverageOverviewRuleActivity.Disabled])).toEqual(1); + }); - it('returns count of one rule type when an activity filter is present', () => { - const payload = getMockCoverageOverviewMitreTechnique(); - expect(getTotalRuleCount(payload, [CoverageOverviewRuleActivity.Disabled])).toEqual(1); + it('returns count of multiple rule type when multiple activity filter is present', () => { + const payload = getMockCoverageOverviewMitreTechnique(); + expect( + getTotalRuleCount(payload, [ + CoverageOverviewRuleActivity.Enabled, + CoverageOverviewRuleActivity.Disabled, + ]) + ).toEqual(2); + }); }); - it('returns count of multiple rule type when multiple activity filter is present', () => { - const payload = getMockCoverageOverviewMitreTechnique(); - expect( - getTotalRuleCount(payload, [ - CoverageOverviewRuleActivity.Enabled, - CoverageOverviewRuleActivity.Disabled, - ]) - ).toEqual(2); + describe('getNumOfCoveredTechniques', () => { + it('returns 0 when no techniques are present', () => { + const payload: CoverageOverviewMitreTactic = getMockCoverageOverviewMitreTactic(); + expect(getNumOfCoveredTechniques(payload)).toEqual(0); + }); + + it('returns number of techniques when present', () => { + const payload: CoverageOverviewMitreTactic = { + ...getMockCoverageOverviewMitreTactic(), + techniques: [ + getMockCoverageOverviewMitreTechnique(), + getMockCoverageOverviewMitreTechnique(), + ], + }; + expect(getNumOfCoveredTechniques(payload)).toEqual(2); + }); }); }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/model/coverage_overview/mitre_technique.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/model/coverage_overview/mitre_technique.ts index 589629d643810..23f57497d7d7d 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/model/coverage_overview/mitre_technique.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/model/coverage_overview/mitre_technique.ts @@ -7,6 +7,7 @@ import { CoverageOverviewRuleActivity } from '../../../../../common/api/detection_engine'; import type { CoverageOverviewMitreSubTechnique } from './mitre_subtechnique'; +import type { CoverageOverviewMitreTactic } from './mitre_tactic'; import type { CoverageOverviewRule } from './rule'; export interface CoverageOverviewMitreTechnique { @@ -38,3 +39,6 @@ export const getTotalRuleCount = ( } return totalRuleCount; }; + +export const getNumOfCoveredTechniques = (tactic: CoverageOverviewMitreTactic): number => + tactic.techniques.filter((technique) => technique.enabledRules.length !== 0).length; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/helpers.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/helpers.test.ts index 5a1aee424352a..39e0b9d7bbb53 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/helpers.test.ts @@ -7,56 +7,10 @@ import type { CoverageOverviewRuleActivity } from '../../../../../common/api/detection_engine'; import { getCoverageOverviewFilterMock } from '../../../../../common/api/detection_engine/rule_management/coverage_overview/coverage_overview_route.mock'; -import { - getMockCoverageOverviewMitreSubTechnique, - getMockCoverageOverviewMitreTactic, - getMockCoverageOverviewMitreTechnique, -} from '../../../rule_management/model/coverage_overview/__mocks__'; import { ruleActivityFilterDefaultOptions } from './constants'; -import { - extractSelected, - getNumOfCoveredSubtechniques, - getNumOfCoveredTechniques, - populateSelected, -} from './helpers'; +import { extractSelected, populateSelected } from './helpers'; describe('helpers', () => { - describe('getNumOfCoveredTechniques', () => { - it('returns 0 when no techniques are present', () => { - const payload = getMockCoverageOverviewMitreTactic(); - expect(getNumOfCoveredTechniques(payload)).toEqual(0); - }); - - it('returns number of techniques when present', () => { - const payload = { - ...getMockCoverageOverviewMitreTactic(), - techniques: [ - getMockCoverageOverviewMitreTechnique(), - getMockCoverageOverviewMitreTechnique(), - ], - }; - expect(getNumOfCoveredTechniques(payload)).toEqual(2); - }); - }); - - describe('getNumOfCoveredSubtechniques', () => { - it('returns 0 when no subtechniques are present', () => { - const payload = getMockCoverageOverviewMitreTechnique(); - expect(getNumOfCoveredSubtechniques(payload)).toEqual(0); - }); - - it('returns number of subtechniques when present', () => { - const payload = { - ...getMockCoverageOverviewMitreTechnique(), - subtechniques: [ - getMockCoverageOverviewMitreSubTechnique(), - getMockCoverageOverviewMitreSubTechnique(), - ], - }; - expect(getNumOfCoveredSubtechniques(payload)).toEqual(2); - }); - }); - describe('extractSelected', () => { it('returns empty array when no options are checked', () => { const payload = ruleActivityFilterDefaultOptions; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/helpers.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/helpers.ts index 82d50e7b9721b..ecd67546e7627 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/helpers.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/helpers.ts @@ -10,16 +10,8 @@ import type { CoverageOverviewRuleActivity, CoverageOverviewRuleSource, } from '../../../../../common/api/detection_engine'; -import type { CoverageOverviewMitreTactic } from '../../../rule_management/model/coverage_overview/mitre_tactic'; -import type { CoverageOverviewMitreTechnique } from '../../../rule_management/model/coverage_overview/mitre_technique'; import { coverageOverviewCardColorThresholds } from './constants'; -export const getNumOfCoveredTechniques = (tactic: CoverageOverviewMitreTactic): number => - tactic.techniques.filter((technique) => technique.enabledRules.length !== 0).length; - -export const getNumOfCoveredSubtechniques = (technique: CoverageOverviewMitreTechnique): number => - technique.subtechniques.filter((subtechnique) => subtechnique.enabledRules.length !== 0).length; - export const getCardBackgroundColor = (value: number) => { for (const { threshold, color } of coverageOverviewCardColorThresholds) { if (value >= threshold) { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/tactic_panel.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/tactic_panel.tsx index e1d1749ca264f..64ef73c8259c9 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/tactic_panel.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/tactic_panel.tsx @@ -11,9 +11,9 @@ import React, { memo, useMemo } from 'react'; import { euiThemeVars } from '@kbn/ui-theme'; import type { CoverageOverviewMitreTactic } from '../../../rule_management/model/coverage_overview/mitre_tactic'; import { coverageOverviewPanelWidth } from './constants'; -import { getNumOfCoveredTechniques } from './helpers'; import * as i18n from './translations'; import { CoverageOverviewPanelRuleStats } from './shared_components/panel_rule_stats'; +import { getNumOfCoveredTechniques } from '../../../rule_management/model/coverage_overview/mitre_technique'; export interface CoverageOverviewTacticPanelProps { tactic: CoverageOverviewMitreTactic; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/technique_panel_popover.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/technique_panel_popover.tsx index f5fc71b08b055..9e026e9912b46 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/technique_panel_popover.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/technique_panel_popover.tsx @@ -23,12 +23,12 @@ import { css, cx } from '@emotion/css'; import React, { memo, useCallback, useMemo, useState } from 'react'; import { useUserData } from '../../../../detections/components/user_info'; import type { CoverageOverviewMitreTechnique } from '../../../rule_management/model/coverage_overview/mitre_technique'; -import { getNumOfCoveredSubtechniques } from './helpers'; import { CoverageOverviewRuleListHeader } from './shared_components/popover_list_header'; import { CoverageOverviewMitreTechniquePanel } from './technique_panel'; import * as i18n from './translations'; import { RuleLink } from '../../components/rules_table/use_columns'; import { useCoverageOverviewDashboardContext } from './coverage_overview_dashboard_context'; +import { getNumOfCoveredSubtechniques } from '../../../rule_management/model/coverage_overview/mitre_subtechnique'; export interface CoverageOverviewMitreTechniquePanelPopoverProps { technique: CoverageOverviewMitreTechnique; @@ -41,7 +41,6 @@ const CoverageOverviewMitreTechniquePanelPopoverComponent = ({ const [isPopoverOpen, setIsPopoverOpen] = useState(false); const [isLoading, setIsLoading] = useState(false); const closePopover = useCallback(() => setIsPopoverOpen(false), []); - const coveredSubtechniques = useMemo(() => getNumOfCoveredSubtechniques(technique), [technique]); const isEnableButtonDisabled = useMemo( () => !canUserCRUD || technique.disabledRules.length === 0, [canUserCRUD, technique.disabledRules.length] @@ -53,10 +52,18 @@ const CoverageOverviewMitreTechniquePanelPopoverComponent = ({ ); const { - state: { showExpandedCells }, + state: { + showExpandedCells, + filter: { activity }, + }, actions: { enableAllDisabled }, } = useCoverageOverviewDashboardContext(); + const coveredSubtechniques = useMemo( + () => getNumOfCoveredSubtechniques(technique, activity), + [activity, technique] + ); + const handleEnableAllDisabled = useCallback(async () => { setIsLoading(true); const ruleIds = technique.disabledRules.map((rule) => rule.id); From 11b47c461fb2596fcf3ec6e17b3ab2d9d2c490d1 Mon Sep 17 00:00:00 2001 From: Saarika Bhasi <55930906+saarikabhasi@users.noreply.github.com> Date: Tue, 14 Nov 2023 14:55:56 -0500 Subject: [PATCH 7/7] [Search] Remove stray parantheses on the indices page (#171056) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Remove stray parenthesis on the indices page. Screenshot 2023-11-10 at 4 12 42 PM --- .../components/search_indices/search_indices.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/search_indices.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/search_indices.tsx index 7085784d660f2..8e778228857e1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/search_indices.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/search_indices.tsx @@ -319,7 +319,6 @@ export const SearchIndices: React.FC = () => { )} - ) ); };