diff --git a/package.json b/package.json
index bb666f88f738d..677575fed98ef 100644
--- a/package.json
+++ b/package.json
@@ -1017,6 +1017,7 @@
"react": "^17.0.2",
"react-ace": "^7.0.5",
"react-color": "^2.13.8",
+ "react-diff-viewer-continued": "^3.3.1",
"react-dom": "^17.0.2",
"react-dropzone": "^4.2.9",
"react-fast-compare": "^2.0.4",
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/json_diff/rule_diff_tab_react_diff_viewer_continued.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/json_diff/rule_diff_tab_react_diff_viewer_continued.tsx
new file mode 100644
index 0000000000000..37d29dbc3f1d7
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/json_diff/rule_diff_tab_react_diff_viewer_continued.tsx
@@ -0,0 +1,160 @@
+/*
+ * 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, { useState, useContext, useMemo } from 'react';
+import ReactDiffViewer, { DiffMethod } from 'react-diff-viewer-continued';
+import { EuiSpacer, EuiSwitch, EuiRadioGroup, useEuiTheme } from '@elastic/eui';
+import type { RuleResponse } from '../../../../../../common/api/detection_engine/model/rule_schema/rule_schemas.gen';
+import { sortAndStringifyJson } from './sort_stringify_json';
+
+const CustomStylesContext = React.createContext({});
+const DiffMethodContext = React.createContext(DiffMethod.CHARS);
+
+interface CustomStylesProps {
+ children: React.ReactNode;
+}
+
+const CustomStyles = ({ children }: CustomStylesProps) => {
+ const { euiTheme } = useEuiTheme();
+ const [enabled, setEnabled] = useState(false);
+
+ const customStyles = {
+ variables: {
+ light: {
+ addedBackground: 'transparent',
+ removedBackground: 'transparent',
+ },
+ },
+ wordAdded: {
+ background: 'transparent',
+ color: euiTheme.colors.successText,
+ },
+ wordRemoved: {
+ background: 'transparent',
+ color: euiTheme.colors.dangerText,
+ textDecoration: 'line-through',
+ },
+ };
+
+ return (
+
+ {
+ setEnabled(!enabled);
+ }}
+ />
+
+ {children}
+
+ );
+};
+
+interface WholeObjectDiffProps {
+ oldRule: RuleResponse;
+ newRule: RuleResponse;
+}
+
+const WholeObjectDiff = ({ oldRule, newRule }: WholeObjectDiffProps) => {
+ const diffMethod = useContext(DiffMethodContext);
+ const styles = useContext(CustomStylesContext);
+
+ const [oldSource, newSource] = useMemo(() => {
+ const oldSrc =
+ diffMethod === DiffMethod.JSON && typeof oldRule === 'object'
+ ? oldRule
+ : sortAndStringifyJson(oldRule);
+
+ const newSrc =
+ diffMethod === DiffMethod.JSON && typeof newRule === 'object'
+ ? newRule
+ : sortAndStringifyJson(newRule);
+
+ return [oldSrc, newSrc];
+ }, [oldRule, newRule, diffMethod]);
+
+ return (
+
+ );
+};
+
+interface RuleDiffTabProps {
+ oldRule: RuleResponse;
+ newRule: RuleResponse;
+}
+
+export const RuleDiffTabReactDiffViewerContinued = ({ oldRule, newRule }: RuleDiffTabProps) => {
+ const options = [
+ {
+ id: DiffMethod.CHARS,
+ label: 'Chars',
+ },
+ {
+ id: DiffMethod.WORDS,
+ label: 'Words',
+ },
+ {
+ id: DiffMethod.LINES,
+ label: 'Lines',
+ },
+ {
+ id: DiffMethod.SENTENCES,
+ label: 'Sentences',
+ },
+ {
+ id: DiffMethod.JSON,
+ label: 'JSON',
+ },
+ ];
+
+ const [compareMethod, setCompareMethod] = useState(DiffMethod.JSON);
+
+ return (
+ <>
+
+ {
+ setCompareMethod(optionId as DiffMethod);
+ }}
+ legend={{
+ children: {'Diffing algorthm'},
+ }}
+ />
+
+
+
+
+
+
+ >
+ );
+};
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/json_diff/sort_stringify_json.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/json_diff/sort_stringify_json.ts
new file mode 100644
index 0000000000000..a5b40bfea7695
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/json_diff/sort_stringify_json.ts
@@ -0,0 +1,11 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import stringify from 'json-stable-stringify';
+
+export const sortAndStringifyJson = (jsObject: Record): string =>
+ stringify(jsObject, { space: 2 });
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_details_flyout.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_details_flyout.tsx
index aa221b6cdb147..8d6ef88d92c74 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_details_flyout.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_details_flyout.tsx
@@ -95,7 +95,7 @@ const tabPaddingClassName = css`
padding: 0 ${euiThemeVars.euiSizeM} ${euiThemeVars.euiSizeXL} ${euiThemeVars.euiSizeM};
`;
-const TabContentPadding: React.FC = ({ children }) => (
+export const TabContentPadding: React.FC = ({ children }) => (
{children}
);
@@ -104,6 +104,7 @@ interface RuleDetailsFlyoutProps {
ruleActions?: React.ReactNode;
dataTestSubj?: string;
closeFlyout: () => void;
+ getRuleTabs?: (rule: RuleResponse, defaultTabs: EuiTabbedContentTab[]) => EuiTabbedContentTab[];
}
export const RuleDetailsFlyout = ({
@@ -111,6 +112,7 @@ export const RuleDetailsFlyout = ({
ruleActions,
dataTestSubj,
closeFlyout,
+ getRuleTabs,
}: RuleDetailsFlyoutProps) => {
const { expandedOverviewSections, toggleOverviewSection } = useOverviewTabSections();
@@ -145,12 +147,13 @@ export const RuleDetailsFlyout = ({
);
const tabs = useMemo(() => {
+ const defaultTabs = [overviewTab];
if (rule.note) {
- return [overviewTab, investigationGuideTab];
- } else {
- return [overviewTab];
+ defaultTabs.push(investigationGuideTab);
}
- }, [overviewTab, investigationGuideTab, rule.note]);
+
+ return getRuleTabs ? getRuleTabs(rule, defaultTabs) : defaultTabs;
+ }, [overviewTab, investigationGuideTab, rule, getRuleTabs]);
const [selectedTabId, setSelectedTabId] = useState(tabs[0].id);
const selectedTab = tabs.find((tab) => tab.id === selectedTabId) ?? tabs[0];
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 290f85ade3a03..0b2092385e173 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
@@ -24,11 +24,16 @@ import type { UpgradePrebuiltRulesTableFilterOptions } from './use_filter_prebui
import { useFilterPrebuiltRulesToUpgrade } from './use_filter_prebuilt_rules_to_upgrade';
import { useAsyncConfirmation } from '../rules_table/use_async_confirmation';
import { useRuleDetailsFlyout } from '../../../../rule_management/components/rule_details/use_rule_details_flyout';
-import { RuleDetailsFlyout } from '../../../../rule_management/components/rule_details/rule_details_flyout';
+import {
+ RuleDetailsFlyout,
+ TabContentPadding,
+} from '../../../../rule_management/components/rule_details/rule_details_flyout';
import * as i18n from './translations';
import { MlJobUpgradeModal } from '../../../../../detections/components/modals/ml_job_upgrade_modal';
+import { RuleDiffTabReactDiffViewerContinued } from '../../../../rule_management/components/rule_details/json_diff/rule_diff_tab_react_diff_viewer_continued';
+
export interface UpgradePrebuiltRulesTableState {
/**
* Rules available to be updated
@@ -286,6 +291,27 @@ export const UpgradePrebuiltRulesTableContextProvider = ({
{i18n.UPDATE_BUTTON_LABEL}
}
+ getRuleTabs={(rule, defaultTabs) => {
+ const activeRule = filteredRules.find(({ id }) => rule.id);
+ if (!activeRule) {
+ return defaultTabs;
+ }
+
+ const diffTabReactDiffViewerContinued = {
+ id: 'react-diff-viewer-continued',
+ name: 'react-diff-viewer-continued',
+ content: (
+
+
+
+ ),
+ };
+
+ return [diffTabReactDiffViewerContinued, ...defaultTabs];
+ }}
/>
)}
>
diff --git a/yarn.lock b/yarn.lock
index e6b6c9eeef668..bc958e8e1c98f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1876,10 +1876,10 @@
"@emotion/utils" "0.11.3"
babel-plugin-emotion "^10.0.27"
-"@emotion/css@^11.11.0":
- version "11.11.0"
- resolved "https://registry.yarnpkg.com/@emotion/css/-/css-11.11.0.tgz#dad6a27a77d5e5cbb0287674c3ace76d762563ca"
- integrity sha512-m4g6nKzZyiKyJ3WOfdwrBdcujVcpaScIWHAnyNKPm/A/xJKwfXPfQAbEVi1kgexWTDakmg+r2aDj0KvnMTo4oQ==
+"@emotion/css@^11.11.0", "@emotion/css@^11.11.2":
+ version "11.11.2"
+ resolved "https://registry.yarnpkg.com/@emotion/css/-/css-11.11.2.tgz#e5fa081d0c6e335352e1bc2b05953b61832dca5a"
+ integrity sha512-VJxe1ucoMYMS7DkiMdC2T7PWNbrEI0a39YRiyDvK2qq4lXwjRbVP/z4lpG+odCsRzadlR+1ywwrTzhdm5HNdew==
dependencies:
"@emotion/babel-plugin" "^11.11.0"
"@emotion/cache" "^11.11.0"
@@ -14888,7 +14888,7 @@ diff-sequences@^29.4.3:
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.4.3.tgz#9314bc1fabe09267ffeca9cbafc457d8499a13f2"
integrity sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==
-diff@5.0.0, diff@^5.0.0:
+diff@5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b"
integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==
@@ -14908,6 +14908,11 @@ diff@^4.0.1:
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
+diff@^5.0.0, diff@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/diff/-/diff-5.1.0.tgz#bc52d298c5ea8df9194800224445ed43ffc87e40"
+ integrity sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==
+
diffie-hellman@^5.0.0:
version "5.0.2"
resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.2.tgz#b5835739270cfe26acf632099fded2a07f209e5e"
@@ -25305,6 +25310,17 @@ react-colorful@^5.1.2:
resolved "https://registry.yarnpkg.com/react-colorful/-/react-colorful-5.5.1.tgz#29d9c4e496f2ca784dd2bb5053a3a4340cfaf784"
integrity sha512-M1TJH2X3RXEt12sWkpa6hLc/bbYS0H6F4rIqjQZ+RxNBstpY67d9TrFXtqdZwhpmBXcCwEi7stKqFue3ZRkiOg==
+react-diff-viewer-continued@^3.3.1:
+ version "3.3.1"
+ resolved "https://registry.yarnpkg.com/react-diff-viewer-continued/-/react-diff-viewer-continued-3.3.1.tgz#1ef6af86fc92ad721a5461f8f3c44f74381ea81d"
+ integrity sha512-YhjWjCUq6cs8k9iErpWh/xB2jFCndigGAz2TKubdqrSTtDH5Ib+tdQgzBWVXMMqgtEwoPLi+WFmSsdSoYbDVpw==
+ dependencies:
+ "@emotion/css" "^11.11.2"
+ classnames "^2.3.2"
+ diff "^5.1.0"
+ memoize-one "^6.0.0"
+ prop-types "^15.8.1"
+
react-docgen-typescript@^2.0.0, react-docgen-typescript@^2.1.1:
version "2.2.2"
resolved "https://registry.yarnpkg.com/react-docgen-typescript/-/react-docgen-typescript-2.2.2.tgz#4611055e569edc071204aadb20e1c93e1ab1659c"