From 878ba134e96245b038a9765148ad48a36bb2aa4b Mon Sep 17 00:00:00 2001 From: Maxim Palenov Date: Sat, 28 Sep 2024 00:07:12 +0300 Subject: [PATCH] [Security Solution] Integrate state and components for Prebuilt Rule Update Workflow (#193531) **Epic:** https://github.com/elastic/kibana/issues/174168 **Addresses:** https://github.com/elastic/kibana/issues/171520 ## Summary This PR introduces a new `Update` tab allowing users to resolve rule upgrade conflicts. It's a result of combination of read-only components implemented in https://github.com/elastic/kibana/pull/193261 and rule upgrade state implemented in https://github.com/elastic/kibana/pull/191721. ## Details The goal of this PR is to provide intermediate integration between rule upgrade state ([PR](https://github.com/elastic/kibana/pull/191721)) and components displaying the diff and read-only state ([PR](https://github.com/elastic/kibana/pull/193261)). It will facilitate further development of rule field editable components and streamline rule upgrade functionality developing. ## How to test? The functionality is hidden under `prebuiltRulesCustomizationEnabled` feature flag. Add the following to your Kibana config ```yaml xpack.securitySolution.enableExperimental: - prebuiltRulesCustomizationEnabled ``` When the above feature flag enabled the new `Update` tab is displayed instead of the old one. ## Screenshots Suggested components design ![image](https://github.com/user-attachments/assets/b5aaf571-286a-4595-9bd4-fdaf9a423b03) New `Update` tab image --- .../components/split_accordion/index.ts | 8 ++ .../split_accordion/split_accordion.tsx | 47 ++++++++++ .../diff_components/field_diff.tsx | 20 +++-- .../rule_details/diff_components/index.ts | 1 - .../diff_components/panel_wrapper.tsx | 45 ---------- .../comparison_side/comparison_side.tsx | 15 ++-- .../three_way_diff/comparison_side/utils.ts | 2 +- .../field_upgrade_conflicts_resolver.tsx | 67 +++++++++++++++ ...ield_upgrade_conflicts_resolver_header.tsx | 25 ++++++ .../rule_upgrade_conflicts_resolver.tsx | 40 +++++++++ .../components/rule_upgrade_info_bar.tsx | 47 ++++++++++ .../three_way_diff/components/side_header.tsx | 29 +++++++ .../components/translations.tsx | 62 ++++++++++++++ .../three_way_diff/final_side/final_side.tsx | 35 ++++++++ .../final_side/final_side_help_info.tsx | 43 ++++++++++ .../three_way_diff/final_side/translations.ts | 15 ++++ .../rule_upgrade_conflicts_resolver_tab.tsx | 37 ++++++++ .../rule_details/three_way_diff_tab.tsx | 22 ----- .../components/rule_details/translations.ts | 7 -- .../upgrade_prebuilt_rules_table_context.tsx | 85 ++++++++++--------- .../use_prebuilt_rules_upgrade_state.ts | 27 +++--- 21 files changed, 538 insertions(+), 141 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/common/components/split_accordion/index.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/split_accordion/split_accordion.tsx delete mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/diff_components/panel_wrapper.tsx create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/components/field_upgrade_conflicts_resolver.tsx create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/components/field_upgrade_conflicts_resolver_header.tsx create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/components/rule_upgrade_conflicts_resolver.tsx create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/components/rule_upgrade_info_bar.tsx create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/components/side_header.tsx create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/components/translations.tsx create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_side/final_side.tsx create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_side/final_side_help_info.tsx create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_side/translations.ts create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/rule_upgrade_conflicts_resolver_tab.tsx delete mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff_tab.tsx diff --git a/x-pack/plugins/security_solution/public/common/components/split_accordion/index.ts b/x-pack/plugins/security_solution/public/common/components/split_accordion/index.ts new file mode 100644 index 0000000000000..6b05d7594e20a --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/split_accordion/index.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export * from './split_accordion'; diff --git a/x-pack/plugins/security_solution/public/common/components/split_accordion/split_accordion.tsx b/x-pack/plugins/security_solution/public/common/components/split_accordion/split_accordion.tsx new file mode 100644 index 0000000000000..668473919b97b --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/split_accordion/split_accordion.tsx @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiAccordion, EuiSplitPanel, useEuiTheme, useGeneratedHtmlId } from '@elastic/eui'; +import { css } from '@emotion/css'; +import type { PropsWithChildren } from 'react'; + +interface SplitAccordionProps { + header: React.ReactNode; + initialIsOpen?: boolean; + 'data-test-subj'?: string; +} + +export const SplitAccordion = ({ + header, + initialIsOpen, + 'data-test-subj': dataTestSubj, + children, +}: PropsWithChildren) => { + const accordionId = useGeneratedHtmlId(); + const { euiTheme } = useEuiTheme(); + + return ( + + + + {children} + + + + ); +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/diff_components/field_diff.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/diff_components/field_diff.tsx index 86c85b7b7a89b..7051c7c496e45 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/diff_components/field_diff.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/diff_components/field_diff.tsx @@ -8,8 +8,8 @@ import { EuiFlexGroup, EuiHorizontalRule, EuiTitle } from '@elastic/eui'; import { camelCase, startCase } from 'lodash'; import React from 'react'; +import { SplitAccordion } from '../../../../../common/components/split_accordion'; import { DiffView } from '../json_diff/diff_view'; -import { RuleDiffPanelWrapper } from './panel_wrapper'; import type { FormattedFieldDiff, FieldDiff } from '../../../model/rule_details/rule_field_diff'; import { fieldToDisplayNameMap } from './translations'; @@ -46,14 +46,24 @@ export const FieldGroupDiffComponent = ({ fieldsGroupName, }: FieldDiffComponentProps) => { const { fieldDiffs, shouldShowSubtitles } = ruleDiffs; + return ( - + +
{fieldToDisplayNameMap[fieldsGroupName] ?? startCase(camelCase(fieldsGroupName))}
+ + } + initialIsOpen={true} + data-test-subj="ruleUpgradePerFieldDiff" + > {fieldDiffs.map(({ currentVersion, targetVersion, fieldName: specificFieldName }, index) => { - const shouldShowSeparator = index !== fieldDiffs.length - 1; + const isLast = index === fieldDiffs.length - 1; + return ( ); })} -
+ ); }; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/diff_components/index.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/diff_components/index.ts index 6effbcf3af931..cef0491a53e22 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/diff_components/index.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/diff_components/index.ts @@ -7,5 +7,4 @@ export * from './field_diff'; export * from './header_bar'; -export * from './panel_wrapper'; export * from './rule_diff_section'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/diff_components/panel_wrapper.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/diff_components/panel_wrapper.tsx deleted file mode 100644 index f0c86a68cafaf..0000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/diff_components/panel_wrapper.tsx +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { EuiAccordion, EuiSplitPanel, EuiTitle, useEuiTheme } from '@elastic/eui'; -import { camelCase, startCase } from 'lodash'; -import { css } from '@emotion/css'; -import React from 'react'; -import { fieldToDisplayNameMap } from './translations'; - -interface RuleDiffPanelWrapperProps { - fieldName: string; - children: React.ReactNode; -} - -export const RuleDiffPanelWrapper = ({ fieldName, children }: RuleDiffPanelWrapperProps) => { - const { euiTheme } = useEuiTheme(); - - return ( - - -
{fieldToDisplayNameMap[fieldName] ?? startCase(camelCase(fieldName))}
- - } - > - - {children} - -
-
- ); -}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/comparison_side.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/comparison_side.tsx index 3a6f3e366d848..9ef207b0bb998 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/comparison_side.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/comparison_side.tsx @@ -6,7 +6,6 @@ */ import React, { useState } from 'react'; -import { EuiSpacer } from '@elastic/eui'; import { VersionsPicker } from '../versions_picker/versions_picker'; import type { Version } from '../versions_picker/constants'; import { SelectedVersions } from '../versions_picker/constants'; @@ -17,6 +16,7 @@ import type { } from '../../../../../../../common/api/detection_engine'; import { getSubfieldChanges } from './get_subfield_changes'; import { SubfieldChanges } from './subfield_changes'; +import { SideHeader } from '../components/side_header'; interface ComparisonSideProps { fieldName: FieldName; @@ -42,12 +42,13 @@ export function ComparisonSide({ return ( <> - - + + + ); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/utils.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/utils.ts index fd16366f1a76e..dd52da04982f3 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/utils.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/utils.ts @@ -26,7 +26,7 @@ export function pickFieldValueForVersion { + fieldName: FieldName; + fieldThreeWayDiff: RuleFieldsDiff[FieldName]; + finalDiffableRule: DiffableRule; +} + +export function FieldUpgradeConflictsResolver({ + fieldName, + fieldThreeWayDiff, + finalDiffableRule, +}: FieldUpgradeConflictsResolverProps): JSX.Element { + const { euiTheme } = useEuiTheme(); + const hasConflict = fieldThreeWayDiff.conflict !== ThreeWayDiffConflict.NONE; + + return ( + <> + } + initialIsOpen={hasConflict} + data-test-subj="ruleUpgradePerFieldDiff" + > + + + } + resolvedValue={finalDiffableRule[fieldName] as DiffableAllFields[FieldName]} + /> + + + + + + + + + + ); +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/components/field_upgrade_conflicts_resolver_header.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/components/field_upgrade_conflicts_resolver_header.tsx new file mode 100644 index 0000000000000..2821a0a179b91 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/components/field_upgrade_conflicts_resolver_header.tsx @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { camelCase, startCase } from 'lodash'; +import { EuiTitle } from '@elastic/eui'; +import { fieldToDisplayNameMap } from '../../diff_components/translations'; + +interface FieldUpgradeConflictsResolverHeaderProps { + fieldName: string; +} + +export function FieldUpgradeConflictsResolverHeader({ + fieldName, +}: FieldUpgradeConflictsResolverHeaderProps): JSX.Element { + return ( + +
{fieldToDisplayNameMap[fieldName] ?? startCase(camelCase(fieldName))}
+
+ ); +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/components/rule_upgrade_conflicts_resolver.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/components/rule_upgrade_conflicts_resolver.tsx new file mode 100644 index 0000000000000..57af1b340c776 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/components/rule_upgrade_conflicts_resolver.tsx @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import type { + RuleUpgradeState, + SetRuleFieldResolvedValueFn, +} from '../../../../../rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_prebuilt_rules_upgrade_state'; +import { FieldUpgradeConflictsResolver } from './field_upgrade_conflicts_resolver'; + +interface RuleUpgradeConflictsResolverProps { + ruleUpgradeState: RuleUpgradeState; + setRuleFieldResolvedValue: SetRuleFieldResolvedValueFn; +} + +export function RuleUpgradeConflictsResolver({ + ruleUpgradeState, + setRuleFieldResolvedValue, +}: RuleUpgradeConflictsResolverProps): JSX.Element { + const fieldDiffEntries = Object.entries(ruleUpgradeState.diff.fields) as Array< + [ + keyof typeof ruleUpgradeState.diff.fields, + Required[keyof typeof ruleUpgradeState.diff.fields] + ] + >; + const fields = fieldDiffEntries.map(([fieldName, fieldDiff]) => ( + + )); + + return <>{fields}; +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/components/rule_upgrade_info_bar.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/components/rule_upgrade_info_bar.tsx new file mode 100644 index 0000000000000..7ecde8059cc2f --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/components/rule_upgrade_info_bar.tsx @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import type { RuleUpgradeState } from '../../../../../rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_prebuilt_rules_upgrade_state'; +import { + UtilityBar, + UtilityBarGroup, + UtilityBarSection, + UtilityBarText, +} from '../../../../../../common/components/utility_bar'; +import * as i18n from './translations'; + +interface UpgradeInfoBarProps { + ruleUpgradeState: RuleUpgradeState; +} + +export function RuleUpgradeInfoBar({ ruleUpgradeState }: UpgradeInfoBarProps): JSX.Element { + const numOfFieldsWithUpdates = ruleUpgradeState.diff.num_fields_with_updates; + const numOfConflicts = ruleUpgradeState.diff.num_fields_with_conflicts; + + return ( + + + + + {i18n.NUM_OF_FIELDS_WITH_UPDATES(numOfFieldsWithUpdates)} + + + + + {i18n.NUM_OF_CONFLICTS(numOfConflicts)} + + + + + + + + + + ); +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/components/side_header.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/components/side_header.tsx new file mode 100644 index 0000000000000..574e3f526f856 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/components/side_header.tsx @@ -0,0 +1,29 @@ +/* + * 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 { PropsWithChildren } from 'react'; +import React from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, useEuiTheme } from '@elastic/eui'; +import { css } from '@emotion/css'; + +export function SideHeader({ children }: PropsWithChildren<{}>) { + const { euiTheme } = useEuiTheme(); + + return ( + <> + + {children} + + + + ); +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/components/translations.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/components/translations.tsx new file mode 100644 index 0000000000000..620b3ac1c0ba8 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/components/translations.tsx @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiLink } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { useKibana } from '../../../../../../common/lib/kibana/kibana_react'; + +export const NUM_OF_FIELDS_WITH_UPDATES = (count: number) => + i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.upgradeRules.diffTab.fieldsWithUpdates', + { + values: { count }, + defaultMessage: 'Upgrade has {count} {count, plural, one {field} other {fields}}', + } + ); + +export const NUM_OF_CONFLICTS = (count: number) => + i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.upgradeRules.diffTab.numOfConflicts', + { + values: { count }, + defaultMessage: '{count} {count, plural, one {conflict} other {conflicts}}', + } + ); + +const UPGRADE_RULES_DOCS_LINK = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.upgradeRules.updateYourRulesDocsLink', + { + defaultMessage: 'update your rules', + } +); + +export function RuleUpgradeHelper(): JSX.Element { + const { + docLinks: { + links: { + securitySolution: { manageDetectionRules }, + }, + }, + } = useKibana().services; + const manageDetectionRulesSnoozeSection = `${manageDetectionRules}#edit-rules-settings`; + + return ( + + {UPGRADE_RULES_DOCS_LINK} + + ), + }} + /> + ); +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_side/final_side.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_side/final_side.tsx new file mode 100644 index 0000000000000..0685d064b32d0 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_side/final_side.tsx @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiTitle } from '@elastic/eui'; +import type { DiffableRule } from '../../../../../../../common/api/detection_engine'; +import { FieldReadOnly } from '../final_readonly/field_readonly'; +import { SideHeader } from '../components/side_header'; +import { FinalSideHelpInfo } from './final_side_help_info'; +import * as i18n from './translations'; + +interface FinalSideProps { + fieldName: string; + finalDiffableRule: DiffableRule; +} + +export function FinalSide({ fieldName, finalDiffableRule }: FinalSideProps): JSX.Element { + return ( + <> + + +

+ {i18n.UPGRADED_VERSION} + +

+
+
+ + + ); +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_side/final_side_help_info.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_side/final_side_help_info.tsx new file mode 100644 index 0000000000000..766692e9efecd --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_side/final_side_help_info.tsx @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { useToggle } from 'react-use'; +import { EuiPopover, EuiText, EuiButtonIcon } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; + +/** + * Theme doesn't expose width variables. Using provided size variables will require + * multiplying it by another magic constant. + * + * 320px width looks + * like a [commonly used width in EUI](https://github.com/search?q=repo%3Aelastic%2Feui%20320&type=code). + */ +const POPOVER_WIDTH = 320; + +export function FinalSideHelpInfo(): JSX.Element { + const [isPopoverOpen, togglePopover] = useToggle(false); + + const button = ( + + ); + + return ( + + + + + + ); +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_side/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_side/translations.ts new file mode 100644 index 0000000000000..aa9b4885a964d --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_side/translations.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const UPGRADED_VERSION = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.upgradeRules.upgradedVersion', + { + defaultMessage: 'Upgraded version', + } +); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/rule_upgrade_conflicts_resolver_tab.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/rule_upgrade_conflicts_resolver_tab.tsx new file mode 100644 index 0000000000000..10823b8045c96 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/rule_upgrade_conflicts_resolver_tab.tsx @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiSpacer } from '@elastic/eui'; +import type { + RuleUpgradeState, + SetRuleFieldResolvedValueFn, +} from '../../../../rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_prebuilt_rules_upgrade_state'; +import { RuleUpgradeInfoBar } from './components/rule_upgrade_info_bar'; +import { RuleUpgradeConflictsResolver } from './components/rule_upgrade_conflicts_resolver'; + +interface RuleUpgradeConflictsResolverTabProps { + ruleUpgradeState: RuleUpgradeState; + setRuleFieldResolvedValue: SetRuleFieldResolvedValueFn; +} + +export function RuleUpgradeConflictsResolverTab({ + ruleUpgradeState, + setRuleFieldResolvedValue, +}: RuleUpgradeConflictsResolverTabProps): JSX.Element { + return ( + <> + + + + + + ); +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff_tab.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff_tab.tsx deleted file mode 100644 index 5117fa2d7b93b..0000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff_tab.tsx +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import type { DiffableRule } from '../../../../../common/api/detection_engine'; -import type { SetFieldResolvedValueFn } from '../../../rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_prebuilt_rules_upgrade_state'; - -interface ThreeWayDiffTabProps { - finalDiffableRule: DiffableRule; - setFieldResolvedValue: SetFieldResolvedValueFn; -} - -export function ThreeWayDiffTab({ - finalDiffableRule, - setFieldResolvedValue, -}: ThreeWayDiffTabProps): JSX.Element { - return <>{JSON.stringify(finalDiffableRule)}; -} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/translations.ts index f0b96bad1aff5..89c22a285e327 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/translations.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/translations.ts @@ -28,13 +28,6 @@ export const UPDATES_TAB_LABEL = i18n.translate( } ); -export const DIFF_TAB_LABEL = i18n.translate( - 'xpack.securitySolution.detectionEngine.ruleDetails.diffTabLabel', - { - defaultMessage: 'Diff', - } -); - export const JSON_VIEW_UPDATES_TAB_LABEL = i18n.translate( 'xpack.securitySolution.detectionEngine.ruleDetails.jsonViewUpdatesTabLabel', { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_context.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_context.tsx index efd63e5da6872..997f85d3ddce0 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_context.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_context.tsx @@ -9,7 +9,7 @@ import type { Dispatch, SetStateAction } from 'react'; import React, { createContext, useCallback, useContext, useMemo, useState } from 'react'; import { EuiButton, EuiToolTip } from '@elastic/eui'; import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; -import { ThreeWayDiffTab } from '../../../../rule_management/components/rule_details/three_way_diff_tab'; +import { RuleUpgradeConflictsResolverTab } from '../../../../rule_management/components/rule_details/three_way_diff/rule_upgrade_conflicts_resolver_tab'; import { PerFieldRuleDiffTab } from '../../../../rule_management/components/rule_details/per_field_rule_diff_tab'; import { useIsUpgradingSecurityPackages } from '../../../../rule_management/logic/use_upgrade_security_packages'; import { useInstalledSecurityJobs } from '../../../../../common/components/ml/hooks/use_installed_security_jobs'; @@ -140,7 +140,7 @@ export const UpgradePrebuiltRulesTableContextProvider = ({ filterOptions, rules: ruleUpgradeInfos, }); - const { rulesUpgradeState, setFieldResolvedValue } = + const { rulesUpgradeState, setRuleFieldResolvedValue } = usePrebuiltRulesUpgradeState(filteredRuleUpgradeInfos); // Wrapper to add confirmation modal for users who may be running older ML Jobs that would @@ -225,7 +225,46 @@ export const UpgradePrebuiltRulesTableContextProvider = ({ return []; } - const extraTabs = [ + const jsonViewUpdates = { + id: 'jsonViewUpdates', + name: ( + + <>{ruleDetailsI18n.JSON_VIEW_UPDATES_TAB_LABEL} + + ), + content: ( + + + + ), + }; + + if (isPrebuiltRulesCustomizationEnabled) { + return [ + { + id: 'updates', + name: ( + + <>{ruleDetailsI18n.UPDATES_TAB_LABEL} + + ), + content: ( + + + + ), + }, + jsonViewUpdates, + ]; + } + + return [ { id: 'updates', name: ( @@ -239,46 +278,10 @@ export const UpgradePrebuiltRulesTableContextProvider = ({ ), }, - { - id: 'jsonViewUpdates', - name: ( - - <>{ruleDetailsI18n.JSON_VIEW_UPDATES_TAB_LABEL} - - ), - content: ( - - - - ), - }, + jsonViewUpdates, ]; - - if (isPrebuiltRulesCustomizationEnabled) { - extraTabs.unshift({ - id: 'diff', - name: ( - - <>{ruleDetailsI18n.DIFF_TAB_LABEL} - - ), - content: ( - - - - ), - }); - } - - return extraTabs; }, - [rulesUpgradeState, setFieldResolvedValue, isPrebuiltRulesCustomizationEnabled] + [rulesUpgradeState, setRuleFieldResolvedValue, isPrebuiltRulesCustomizationEnabled] ); const filteredRules = useMemo( () => filteredRuleUpgradeInfos.map((rule) => rule.target_rule), diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_prebuilt_rules_upgrade_state.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_prebuilt_rules_upgrade_state.ts index 86f2293d312fa..feafc1a6948ab 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_prebuilt_rules_upgrade_state.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_prebuilt_rules_upgrade_state.ts @@ -28,7 +28,7 @@ export interface RuleUpgradeState extends RuleUpgradeInfoForReview { hasUnresolvedConflicts: boolean; } export type RulesUpgradeState = Record; -export type SetFieldResolvedValueFn< +export type SetRuleFieldResolvedValueFn< FieldName extends keyof DiffableAllFields = keyof DiffableAllFields > = (params: { ruleId: RuleObjectId; @@ -41,22 +41,25 @@ type RulesResolvedConflicts = Record; interface UseRulesUpgradeStateResult { rulesUpgradeState: RulesUpgradeState; - setFieldResolvedValue: SetFieldResolvedValueFn; + setRuleFieldResolvedValue: SetRuleFieldResolvedValueFn; } export function usePrebuiltRulesUpgradeState( ruleUpgradeInfos: RuleUpgradeInfoForReview[] ): UseRulesUpgradeStateResult { const [rulesResolvedConflicts, setRulesResolvedConflicts] = useState({}); - const setFieldResolvedValue = useCallback((...[params]: Parameters) => { - setRulesResolvedConflicts((prevRulesResolvedConflicts) => ({ - ...prevRulesResolvedConflicts, - [params.ruleId]: { - ...(prevRulesResolvedConflicts[params.ruleId] ?? {}), - [params.fieldName]: params.resolvedValue, - }, - })); - }, []); + const setRuleFieldResolvedValue = useCallback( + (...[params]: Parameters) => { + setRulesResolvedConflicts((prevRulesResolvedConflicts) => ({ + ...prevRulesResolvedConflicts, + [params.ruleId]: { + ...(prevRulesResolvedConflicts[params.ruleId] ?? {}), + [params.fieldName]: params.resolvedValue, + }, + })); + }, + [] + ); const rulesUpgradeState = useMemo(() => { const state: RulesUpgradeState = {}; @@ -80,7 +83,7 @@ export function usePrebuiltRulesUpgradeState( return { rulesUpgradeState, - setFieldResolvedValue, + setRuleFieldResolvedValue, }; }