mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Security Solution] Reduce dropdown options and improve tooltip texts in Rule Upgrade flyout (#203222)
**Partially addresses: #171520** ## Summary This PR updates the tooltips for the ‘Diff view’ and ‘Final update’ sections in the prebuilt rule upgrade flyout. It also streamlines the version picker by removing redundant options, making the UI simpler and clearer for users. ## Changes - Reduced the number of version picker items based on the diff outcome. Updated item names for better clarity. - Revised the tooltip text for the ‘Diff view’ section to better explain the available dropdown options. The tooltip now describes only the options in the dropdown to avoid overwhelming the user with unrelated information. - Updated the tooltip text for the ‘Final update’ section. ## Screenshots <img width="922" alt="Schermafbeelding 2024-12-11 om 11 54 48" src="https://github.com/user-attachments/assets/124e76a1-99dc-48d8-be54-f6c8f2079451"> <img width="640" alt="Schermafbeelding 2024-12-11 om 11 55 32" src="https://github.com/user-attachments/assets/45655dd2-6503-46b7-b28b-0df7bf0e6fa3"> <img width="433" alt="Schermafbeelding 2024-12-11 om 11 55 58" src="https://github.com/user-attachments/assets/d845ff52-4678-4245-8bdd-b9957f0c1d13"> Work started on 06-Dec-2024. --------- Co-authored-by: Maxim Palenov <maxim.palenov@elastic.co>
This commit is contained in:
parent
9a8ed0d135
commit
90e35a04bc
13 changed files with 366 additions and 166 deletions
|
@ -9,14 +9,10 @@ import React from 'react';
|
|||
import useToggle from 'react-use/lib/useToggle';
|
||||
import { EuiPopover, EuiText, EuiButtonIcon } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { TITLE } from './translations';
|
||||
import {
|
||||
BASE_VERSION,
|
||||
CURRENT_VERSION,
|
||||
FINAL_VERSION,
|
||||
TARGET_VERSION,
|
||||
} from './versions_picker/translations';
|
||||
import type { VersionsPickerOptionEnum } from './versions_picker/versions_picker';
|
||||
import { useFieldUpgradeContext } from '../rule_upgrade/field_upgrade_context';
|
||||
import { getOptionDetails } from './utils';
|
||||
|
||||
/**
|
||||
* Theme doesn't expose width variables. Using provided size variables will require
|
||||
|
@ -27,9 +23,18 @@ import {
|
|||
*/
|
||||
const POPOVER_WIDTH = 320;
|
||||
|
||||
export function ComparisonSideHelpInfo(): JSX.Element {
|
||||
interface ComparisonSideHelpInfoProps {
|
||||
options: VersionsPickerOptionEnum[];
|
||||
}
|
||||
|
||||
export function ComparisonSideHelpInfo({ options }: ComparisonSideHelpInfoProps): JSX.Element {
|
||||
const [isPopoverOpen, togglePopover] = useToggle(false);
|
||||
|
||||
const { hasResolvedValueDifferentFromSuggested } = useFieldUpgradeContext();
|
||||
const optionsWithDescriptions = options.map((option) =>
|
||||
getOptionDetails(option, hasResolvedValueDifferentFromSuggested)
|
||||
);
|
||||
|
||||
const button = (
|
||||
<EuiButtonIcon
|
||||
iconType="questionInCircle"
|
||||
|
@ -43,25 +48,20 @@ export function ComparisonSideHelpInfo(): JSX.Element {
|
|||
<EuiText style={{ width: POPOVER_WIDTH }} size="s">
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.detectionEngine.rules.upgradeRules.comparisonSide.upgradeHelpText"
|
||||
defaultMessage="{title} shows field's JSON diff between prebuilt rule field versions affecting the rule update process. {versions}"
|
||||
defaultMessage="The {title} lets you compare the values of a field across different versions of a rule: {versions} Differences are shown as JSON, with red lines showing what was removed, green lines showing additions, and bold text highlighting changes. Use {title} to review and understand changes across versions."
|
||||
values={{
|
||||
title: <strong>{TITLE}</strong>,
|
||||
versions: (
|
||||
<>
|
||||
<br />
|
||||
<ul>
|
||||
<li>
|
||||
<strong>{BASE_VERSION}</strong> {'-'} {BASE_VERSION_EXPLANATION}
|
||||
</li>
|
||||
<li>
|
||||
<strong>{CURRENT_VERSION}</strong> {'-'} {CURRENT_VERSION_EXPLANATION}
|
||||
</li>
|
||||
<li>
|
||||
<strong>{TARGET_VERSION}</strong> {'-'} {TARGET_VERSION_EXPLANATION}
|
||||
</li>
|
||||
<li>
|
||||
<strong>{FINAL_VERSION}</strong> {'-'} {FINAL_VERSION_EXPLANATION}
|
||||
</li>
|
||||
{optionsWithDescriptions.map(
|
||||
({ title: displayName, description: explanation }) => (
|
||||
<li>
|
||||
<strong>{displayName}</strong> {'-'} {explanation}
|
||||
</li>
|
||||
)
|
||||
)}
|
||||
</ul>
|
||||
</>
|
||||
),
|
||||
|
@ -71,35 +71,3 @@ export function ComparisonSideHelpInfo(): JSX.Element {
|
|||
</EuiPopover>
|
||||
);
|
||||
}
|
||||
|
||||
const BASE_VERSION_EXPLANATION = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.rules.upgradeRules.versions.baseVersionExplanation',
|
||||
{
|
||||
defaultMessage: 'version originally installed from Elastic prebuilt rules package',
|
||||
}
|
||||
);
|
||||
|
||||
const CURRENT_VERSION_EXPLANATION = (
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.detectionEngine.rules.upgradeRules.currentVersionExplanation"
|
||||
defaultMessage="current version including modification made after prebuilt rule installation. With lack of modifications it matches with {base}."
|
||||
values={{
|
||||
base: <strong>{BASE_VERSION}</strong>,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const TARGET_VERSION_EXPLANATION = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.rules.upgradeRules.versions.targetVersionExplanation',
|
||||
{
|
||||
defaultMessage: 'version coming from a new version of Elastic prebuilt rules package',
|
||||
}
|
||||
);
|
||||
|
||||
const FINAL_VERSION_EXPLANATION = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.rules.upgradeRules.versions.finalVersionExplanation',
|
||||
{
|
||||
defaultMessage:
|
||||
'version used to the update the rule. Initial value is suggested by the diff algorithm.',
|
||||
}
|
||||
);
|
||||
|
|
|
@ -5,35 +5,56 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui';
|
||||
import { VersionsPicker } from './versions_picker/versions_picker';
|
||||
import type { Version } from './versions_picker/constants';
|
||||
import { SelectedVersions } from './versions_picker/constants';
|
||||
import { isEqual } from 'lodash';
|
||||
import usePrevious from 'react-use/lib/usePrevious';
|
||||
import { VersionsPicker, VersionsPickerOptionEnum } from './versions_picker/versions_picker';
|
||||
import { FieldUpgradeSideHeader } from '../field_upgrade_side_header';
|
||||
import { useFieldUpgradeContext } from '../rule_upgrade/field_upgrade_context';
|
||||
import { pickFieldValueForVersion } from './utils';
|
||||
import {
|
||||
getComparisonOptionsForDiffOutcome,
|
||||
getVersionsForComparison,
|
||||
pickFieldValueForVersion,
|
||||
} from './utils';
|
||||
import { getSubfieldChanges } from './get_subfield_changes';
|
||||
import { SubfieldChanges } from './subfield_changes';
|
||||
import { ComparisonSideHelpInfo } from './comparison_side_help_info';
|
||||
import * as i18n from './translations';
|
||||
|
||||
export function FieldComparisonSide(): JSX.Element {
|
||||
const { fieldName, fieldDiff, finalDiffableRule } = useFieldUpgradeContext();
|
||||
const { fieldName, fieldDiff, finalDiffableRule, hasResolvedValueDifferentFromSuggested } =
|
||||
useFieldUpgradeContext();
|
||||
const resolvedValue = finalDiffableRule[fieldName];
|
||||
|
||||
const [selectedVersions, setSelectedVersions] = useState<SelectedVersions>(
|
||||
SelectedVersions.CurrentFinal
|
||||
const options = getComparisonOptionsForDiffOutcome(
|
||||
fieldDiff.diff_outcome,
|
||||
fieldDiff.conflict,
|
||||
hasResolvedValueDifferentFromSuggested
|
||||
);
|
||||
const [selectedOption, setSelectedOption] = useState<VersionsPickerOptionEnum>(options[0]);
|
||||
|
||||
const [oldVersionType, newVersionType] = getVersionsForComparison(
|
||||
selectedOption,
|
||||
fieldDiff.has_base_version
|
||||
);
|
||||
|
||||
const [oldVersionType, newVersionType] = selectedVersions.split('_') as [Version, Version];
|
||||
|
||||
const oldFieldValue = pickFieldValueForVersion(oldVersionType, fieldDiff, resolvedValue);
|
||||
|
||||
const newFieldValue = pickFieldValueForVersion(newVersionType, fieldDiff, resolvedValue);
|
||||
|
||||
const subfieldChanges = getSubfieldChanges(fieldName, oldFieldValue, newFieldValue);
|
||||
|
||||
/* Change selected option to "My changes" if user has modified resolved value */
|
||||
const prevResolvedValue = usePrevious(resolvedValue);
|
||||
useEffect(() => {
|
||||
if (
|
||||
selectedOption !== VersionsPickerOptionEnum.MyChanges &&
|
||||
!isEqual(prevResolvedValue, resolvedValue)
|
||||
) {
|
||||
setSelectedOption(VersionsPickerOptionEnum.MyChanges);
|
||||
}
|
||||
}, [hasResolvedValueDifferentFromSuggested, selectedOption, prevResolvedValue, resolvedValue]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<FieldUpgradeSideHeader>
|
||||
|
@ -42,15 +63,16 @@ export function FieldComparisonSide(): JSX.Element {
|
|||
<EuiTitle size="xxs">
|
||||
<h3>
|
||||
{i18n.TITLE}
|
||||
<ComparisonSideHelpInfo />
|
||||
<ComparisonSideHelpInfo options={options} />
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<VersionsPicker
|
||||
hasBaseVersion={fieldDiff.has_base_version}
|
||||
selectedVersions={selectedVersions}
|
||||
onChange={setSelectedVersions}
|
||||
options={options}
|
||||
selectedOption={selectedOption}
|
||||
onChange={setSelectedOption}
|
||||
hasResolvedValueDifferentFromSuggested={hasResolvedValueDifferentFromSuggested}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FINAL_UPDATE } from '../field_final_side/components/translations';
|
||||
|
||||
export const TITLE = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.rules.upgradeRules.comparisonSide.title',
|
||||
|
@ -20,3 +21,75 @@ export const NO_CHANGES = i18n.translate(
|
|||
defaultMessage: 'No changes',
|
||||
}
|
||||
);
|
||||
|
||||
export const UPDATE_FROM_ELASTIC_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.rules.upgradeRules.versions.updateFromElasticTitle',
|
||||
{
|
||||
defaultMessage: 'Update from Elastic',
|
||||
}
|
||||
);
|
||||
|
||||
export const UPDATE_FROM_ELASTIC_EXPLANATION = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.rules.upgradeRules.versions.updateFromElasticExplanation',
|
||||
{
|
||||
defaultMessage: 'view the changes in Elastic’s latest update',
|
||||
}
|
||||
);
|
||||
|
||||
export const MY_CHANGES_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.rules.upgradeRules.versions.myChangesTitle',
|
||||
{
|
||||
defaultMessage: 'My changes',
|
||||
}
|
||||
);
|
||||
|
||||
export const MY_CHANGES_EXPLANATION = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.rules.upgradeRules.versions.myChangesExplanation',
|
||||
{
|
||||
defaultMessage: `view what you have changed in your installed rule and in the {finalUpdateSectionLabel} section`,
|
||||
values: {
|
||||
finalUpdateSectionLabel: FINAL_UPDATE,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export const MY_CHANGES_IN_RULE_UPGRADE_WORKFLOW_EXPLANATION = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.rules.upgradeRules.versions.myChangesFinalUpdateOnlyExplanation',
|
||||
{
|
||||
defaultMessage: `view the changes you made in the {finalUpdateSectionLabel} section`,
|
||||
values: {
|
||||
finalUpdateSectionLabel: FINAL_UPDATE,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export const MERGED_CHANGES_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.rules.upgradeRules.versions.mergedChangesTitle',
|
||||
{
|
||||
defaultMessage: 'My changes merged with Elastic’s',
|
||||
}
|
||||
);
|
||||
|
||||
export const MERGED_CHANGES_EXPLANATION = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.rules.upgradeRules.versions.mergedChangesExplanation',
|
||||
{
|
||||
defaultMessage: 'view an update suggestion that combines your changes with Elastic’s',
|
||||
}
|
||||
);
|
||||
|
||||
export const MY_ORIGINAL_CHANGES_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.rules.upgradeRules.versions.myOriginalChangesTitle',
|
||||
{
|
||||
defaultMessage: 'My original changes',
|
||||
}
|
||||
);
|
||||
|
||||
export const MY_ORIGINAL_CHANGES_EXPLANATION = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.rules.upgradeRules.versions.myCustomizationExplanation',
|
||||
{
|
||||
defaultMessage: `view what you have changed in your installed rule. Doesn’t include changes made in the {finalUpdateSectionLabel} section.`,
|
||||
values: {
|
||||
finalUpdateSectionLabel: FINAL_UPDATE,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
|
|
@ -7,7 +7,14 @@
|
|||
|
||||
import stringify from 'json-stable-stringify';
|
||||
import { Version } from './versions_picker/constants';
|
||||
import type { ThreeWayDiff } from '../../../../../../../common/api/detection_engine';
|
||||
import {
|
||||
ThreeWayDiffOutcome,
|
||||
type ThreeWayDiff,
|
||||
ThreeWayDiffConflict,
|
||||
} from '../../../../../../../common/api/detection_engine';
|
||||
import { VersionsPickerOptionEnum } from './versions_picker/versions_picker';
|
||||
import { assertUnreachable } from '../../../../../../../common/utility_types';
|
||||
import * as i18n from './translations';
|
||||
|
||||
/**
|
||||
* Picks the field value for a given version either from a three-way diff object or from a user-set resolved value.
|
||||
|
@ -44,3 +51,129 @@ export const stringifyToSortedJson = (fieldValue: unknown): string => {
|
|||
|
||||
return stringify(fieldValue, { space: 2 });
|
||||
};
|
||||
|
||||
interface OptionDetails {
|
||||
title: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the title and description for a given versions picker option.
|
||||
*/
|
||||
export function getOptionDetails(
|
||||
option: VersionsPickerOptionEnum,
|
||||
hasResolvedValueDifferentFromSuggested: boolean
|
||||
): OptionDetails {
|
||||
switch (option) {
|
||||
case VersionsPickerOptionEnum.MyChanges:
|
||||
return hasResolvedValueDifferentFromSuggested
|
||||
? {
|
||||
title: i18n.MY_CHANGES_TITLE,
|
||||
description: i18n.MY_CHANGES_IN_RULE_UPGRADE_WORKFLOW_EXPLANATION,
|
||||
}
|
||||
: {
|
||||
title: i18n.MY_CHANGES_TITLE,
|
||||
description: i18n.MY_CHANGES_EXPLANATION,
|
||||
};
|
||||
case VersionsPickerOptionEnum.MyOriginalChanges:
|
||||
return {
|
||||
title: i18n.MY_ORIGINAL_CHANGES_TITLE,
|
||||
description: i18n.MY_ORIGINAL_CHANGES_EXPLANATION,
|
||||
};
|
||||
case VersionsPickerOptionEnum.UpdateFromElastic:
|
||||
return {
|
||||
title: i18n.UPDATE_FROM_ELASTIC_TITLE,
|
||||
description: i18n.UPDATE_FROM_ELASTIC_EXPLANATION,
|
||||
};
|
||||
case VersionsPickerOptionEnum.Merged:
|
||||
return {
|
||||
title: i18n.MERGED_CHANGES_TITLE,
|
||||
description: i18n.MERGED_CHANGES_EXPLANATION,
|
||||
};
|
||||
default:
|
||||
return assertUnreachable(option);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the versions to be compared based on the selected versions picker option.
|
||||
*/
|
||||
export function getVersionsForComparison(
|
||||
selectedOption: VersionsPickerOptionEnum,
|
||||
hasBaseVersion: boolean
|
||||
): [Version, Version] {
|
||||
switch (selectedOption) {
|
||||
case VersionsPickerOptionEnum.MyChanges:
|
||||
return hasBaseVersion ? [Version.Base, Version.Final] : [Version.Current, Version.Final];
|
||||
case VersionsPickerOptionEnum.MyOriginalChanges:
|
||||
return [Version.Base, Version.Current];
|
||||
case VersionsPickerOptionEnum.UpdateFromElastic:
|
||||
return hasBaseVersion ? [Version.Base, Version.Target] : [Version.Current, Version.Target];
|
||||
case VersionsPickerOptionEnum.Merged:
|
||||
return [Version.Base, Version.Target];
|
||||
default:
|
||||
return assertUnreachable(selectedOption);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the versions picker options available for a given field diff outcome.
|
||||
*/
|
||||
export const getComparisonOptionsForDiffOutcome = (
|
||||
diffOutcome: ThreeWayDiffOutcome,
|
||||
conflict: ThreeWayDiffConflict,
|
||||
hasResolvedValueDifferentFromSuggested: boolean
|
||||
): VersionsPickerOptionEnum[] => {
|
||||
switch (diffOutcome) {
|
||||
case ThreeWayDiffOutcome.StockValueCanUpdate: {
|
||||
const options = [];
|
||||
|
||||
if (hasResolvedValueDifferentFromSuggested) {
|
||||
options.push(VersionsPickerOptionEnum.MyChanges);
|
||||
}
|
||||
options.push(VersionsPickerOptionEnum.UpdateFromElastic);
|
||||
|
||||
return options;
|
||||
}
|
||||
case ThreeWayDiffOutcome.CustomizedValueNoUpdate:
|
||||
return [VersionsPickerOptionEnum.MyChanges];
|
||||
case ThreeWayDiffOutcome.CustomizedValueSameUpdate:
|
||||
return [VersionsPickerOptionEnum.MyChanges, VersionsPickerOptionEnum.UpdateFromElastic];
|
||||
case ThreeWayDiffOutcome.CustomizedValueCanUpdate: {
|
||||
if (conflict === ThreeWayDiffConflict.SOLVABLE) {
|
||||
return [
|
||||
hasResolvedValueDifferentFromSuggested
|
||||
? VersionsPickerOptionEnum.MyChanges
|
||||
: VersionsPickerOptionEnum.Merged,
|
||||
VersionsPickerOptionEnum.UpdateFromElastic,
|
||||
VersionsPickerOptionEnum.MyOriginalChanges,
|
||||
];
|
||||
}
|
||||
|
||||
if (conflict === ThreeWayDiffConflict.NON_SOLVABLE) {
|
||||
const options = [
|
||||
VersionsPickerOptionEnum.MyChanges,
|
||||
VersionsPickerOptionEnum.UpdateFromElastic,
|
||||
];
|
||||
|
||||
if (hasResolvedValueDifferentFromSuggested) {
|
||||
options.push(VersionsPickerOptionEnum.MyOriginalChanges);
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
}
|
||||
case ThreeWayDiffOutcome.MissingBaseCanUpdate: {
|
||||
const options = [];
|
||||
|
||||
if (hasResolvedValueDifferentFromSuggested) {
|
||||
options.push(VersionsPickerOptionEnum.MyChanges);
|
||||
}
|
||||
options.push(VersionsPickerOptionEnum.UpdateFromElastic);
|
||||
|
||||
return options;
|
||||
}
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
|
|
@ -5,54 +5,9 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { EuiSelectOption } from '@elastic/eui';
|
||||
import * as i18n from './translations';
|
||||
|
||||
export enum Version {
|
||||
Base = 'base',
|
||||
Current = 'current',
|
||||
Target = 'target',
|
||||
Final = 'final',
|
||||
}
|
||||
|
||||
export enum SelectedVersions {
|
||||
BaseTarget = 'base_target',
|
||||
BaseCurrent = 'base_current',
|
||||
BaseFinal = 'base_final',
|
||||
CurrentTarget = 'current_target',
|
||||
CurrentFinal = 'current_final',
|
||||
TargetFinal = 'target_final',
|
||||
}
|
||||
|
||||
export const CURRENT_OPTIONS: EuiSelectOption[] = [
|
||||
{
|
||||
value: SelectedVersions.CurrentFinal,
|
||||
text: i18n.VERSION1_VS_VERSION2(i18n.CURRENT_VERSION, i18n.FINAL_VERSION),
|
||||
},
|
||||
{
|
||||
value: SelectedVersions.CurrentTarget,
|
||||
text: i18n.VERSION1_VS_VERSION2(i18n.CURRENT_VERSION, i18n.TARGET_VERSION),
|
||||
},
|
||||
];
|
||||
|
||||
export const TARGET_OPTIONS: EuiSelectOption[] = [
|
||||
{
|
||||
value: SelectedVersions.TargetFinal,
|
||||
text: i18n.VERSION1_VS_VERSION2(i18n.TARGET_VERSION, i18n.FINAL_VERSION),
|
||||
},
|
||||
];
|
||||
|
||||
export const BASE_OPTIONS: EuiSelectOption[] = [
|
||||
{
|
||||
value: SelectedVersions.BaseFinal,
|
||||
text: i18n.VERSION1_VS_VERSION2(i18n.BASE_VERSION, i18n.FINAL_VERSION),
|
||||
},
|
||||
{
|
||||
value: SelectedVersions.BaseTarget,
|
||||
text: i18n.VERSION1_VS_VERSION2(i18n.BASE_VERSION, i18n.TARGET_VERSION),
|
||||
},
|
||||
{
|
||||
value: SelectedVersions.BaseCurrent,
|
||||
text: i18n.VERSION1_VS_VERSION2(i18n.BASE_VERSION, i18n.CURRENT_VERSION),
|
||||
},
|
||||
];
|
||||
|
|
|
@ -6,42 +6,23 @@
|
|||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import type { Story } from '@storybook/react';
|
||||
import { VersionsPicker } from './versions_picker';
|
||||
import { SelectedVersions } from './constants';
|
||||
import { VersionsPicker, VersionsPickerOptionEnum } from './versions_picker';
|
||||
|
||||
export default {
|
||||
component: VersionsPicker,
|
||||
title: 'Rule Management/Prebuilt Rules/Upgrade Flyout/ThreeWayDiff/VersionsPicker',
|
||||
argTypes: {
|
||||
hasBaseVersion: {
|
||||
control: 'boolean',
|
||||
description: 'Indicates whether the base version of a field is available',
|
||||
defaultValue: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const Template: Story<{ hasBaseVersion: boolean }> = (args) => {
|
||||
const [selectedVersions, setSelectedVersions] = useState<SelectedVersions>(
|
||||
SelectedVersions.CurrentFinal
|
||||
);
|
||||
export const Default = () => {
|
||||
const options = [VersionsPickerOptionEnum.MyChanges, VersionsPickerOptionEnum.UpdateFromElastic];
|
||||
const [selectedOption, setSelectedOption] = useState<VersionsPickerOptionEnum>(options[0]);
|
||||
|
||||
return (
|
||||
<VersionsPicker
|
||||
hasBaseVersion={args.hasBaseVersion}
|
||||
selectedVersions={selectedVersions}
|
||||
onChange={setSelectedVersions}
|
||||
options={options}
|
||||
selectedOption={selectedOption}
|
||||
onChange={setSelectedOption}
|
||||
hasResolvedValueDifferentFromSuggested={false}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
hasBaseVersion: true,
|
||||
};
|
||||
|
||||
export const NoBaseVersion = Template.bind({});
|
||||
NoBaseVersion.args = {
|
||||
hasBaseVersion: false,
|
||||
};
|
||||
|
|
|
@ -5,32 +5,48 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import { css } from '@emotion/css';
|
||||
import { EuiSelect } from '@elastic/eui';
|
||||
import type { EuiSelectOption } from '@elastic/eui';
|
||||
import { BASE_OPTIONS, CURRENT_OPTIONS, TARGET_OPTIONS, SelectedVersions } from './constants';
|
||||
import { getOptionDetails } from '../utils';
|
||||
import * as i18n from './translations';
|
||||
|
||||
export enum VersionsPickerOptionEnum {
|
||||
MyChanges = 'MY_CHANGES',
|
||||
MyOriginalChanges = 'MY_ORIGINAL_CHANGES',
|
||||
UpdateFromElastic = 'UPDATE_FROM_ELASTIC',
|
||||
Merged = 'MERGED',
|
||||
}
|
||||
|
||||
interface VersionsPickerProps {
|
||||
hasBaseVersion: boolean;
|
||||
selectedVersions: SelectedVersions;
|
||||
onChange: (pickedVersions: SelectedVersions) => void;
|
||||
options: VersionsPickerOptionEnum[];
|
||||
selectedOption: VersionsPickerOptionEnum;
|
||||
onChange: (selectedOption: VersionsPickerOptionEnum) => void;
|
||||
hasResolvedValueDifferentFromSuggested: boolean;
|
||||
}
|
||||
|
||||
export function VersionsPicker({
|
||||
hasBaseVersion,
|
||||
selectedVersions = SelectedVersions.CurrentFinal,
|
||||
options,
|
||||
selectedOption,
|
||||
onChange,
|
||||
hasResolvedValueDifferentFromSuggested,
|
||||
}: VersionsPickerProps) {
|
||||
const options: EuiSelectOption[] = useMemo(
|
||||
() => [...CURRENT_OPTIONS, ...TARGET_OPTIONS, ...(hasBaseVersion ? BASE_OPTIONS : [])],
|
||||
[hasBaseVersion]
|
||||
);
|
||||
const euiSelectOptions = options.map((option) => {
|
||||
const { title: displayName, description: explanation } = getOptionDetails(
|
||||
option,
|
||||
hasResolvedValueDifferentFromSuggested
|
||||
);
|
||||
|
||||
return {
|
||||
value: option,
|
||||
text: displayName,
|
||||
title: explanation,
|
||||
};
|
||||
});
|
||||
|
||||
const handleChange = useCallback(
|
||||
(changeEvent: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
onChange(changeEvent.target.value as SelectedVersions);
|
||||
onChange(changeEvent.target.value as VersionsPickerOptionEnum);
|
||||
},
|
||||
[onChange]
|
||||
);
|
||||
|
@ -38,8 +54,8 @@ export function VersionsPicker({
|
|||
return (
|
||||
<EuiSelect
|
||||
className={VERSIONS_PICKER_STYLES}
|
||||
options={options}
|
||||
value={selectedVersions}
|
||||
options={euiSelectOptions}
|
||||
value={selectedOption}
|
||||
onChange={handleChange}
|
||||
aria-label={i18n.VERSION_PICKER_ARIA_LABEL}
|
||||
/>
|
||||
|
@ -49,5 +65,5 @@ export function VersionsPicker({
|
|||
const VERSIONS_PICKER_STYLES = css`
|
||||
// Set min-width a bit wider than default
|
||||
// to make English text in narrow screens readable
|
||||
min-width: 220px;
|
||||
min-width: 300px;
|
||||
`;
|
||||
|
|
|
@ -9,6 +9,7 @@ import React from 'react';
|
|||
import useToggle from 'react-use/lib/useToggle';
|
||||
import { EuiPopover, EuiText, EuiButtonIcon } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import * as i18n from '../../../../../../rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/translations';
|
||||
|
||||
/**
|
||||
* Theme doesn't expose width variables. Using provided size variables will require
|
||||
|
@ -34,8 +35,11 @@ export function FieldFinalSideHelpInfo(): JSX.Element {
|
|||
<EuiPopover button={button} isOpen={isPopoverOpen} closePopover={togglePopover}>
|
||||
<EuiText style={{ width: POPOVER_WIDTH }} size="s">
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.detectionEngine.rules.upgradeRules.finalSide.upgradeHelpText"
|
||||
defaultMessage="Choose field values used in the upgraded rule. "
|
||||
id="xpack.securitySolution.detectionEngine.rules.upgradeRules.upgradeHelpText"
|
||||
defaultMessage="The Final Update section lets you preview and edit the final value of a field. This is the value the rule will have after you click {updateButtonLabel}."
|
||||
values={{
|
||||
updateButtonLabel: <strong>{i18n.UPDATE_BUTTON_LABEL}</strong>,
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiPopover>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import React, { createContext, useContext, useMemo } from 'react';
|
||||
import { isEqual } from 'lodash';
|
||||
import { useBoolean } from '@kbn/react-hooks';
|
||||
import { assertUnreachable } from '../../../../../../../common/utility_types';
|
||||
import {
|
||||
|
@ -39,8 +40,14 @@ interface FieldUpgradeContextType {
|
|||
* Whether the field has an unresolved conflict. This state is derived from `fieldUpgradeState`.
|
||||
*/
|
||||
hasConflict: boolean;
|
||||
/**
|
||||
* Whether field value is different from Elastic's suggestion.
|
||||
* It's true only if user has made changes to the suggested field value.
|
||||
*/
|
||||
hasResolvedValueDifferentFromSuggested: boolean;
|
||||
/**
|
||||
* Whether the field was changed after prebuilt rule installation, i.e. customized
|
||||
* It's true only if user has made changes to the suggested field value.
|
||||
*/
|
||||
isCustomized: boolean;
|
||||
/**
|
||||
|
@ -97,6 +104,8 @@ export function FieldUpgradeContextProvider({
|
|||
|
||||
invariant(fieldDiff, `Field diff is not found for ${fieldName}.`);
|
||||
|
||||
const finalDiffableRule = calcFinalDiffableRule(ruleUpgradeState);
|
||||
|
||||
const contextValue: FieldUpgradeContextType = useMemo(
|
||||
() => ({
|
||||
fieldName,
|
||||
|
@ -104,9 +113,17 @@ export function FieldUpgradeContextProvider({
|
|||
hasConflict:
|
||||
fieldUpgradeState === FieldUpgradeStateEnum.SolvableConflict ||
|
||||
fieldUpgradeState === FieldUpgradeStateEnum.NonSolvableConflict,
|
||||
/*
|
||||
Initially, we prefill the resolved value with the merged version.
|
||||
If the current resolved value differs from the merged version, it indicates that the user has modified the suggestion.
|
||||
*/
|
||||
hasResolvedValueDifferentFromSuggested: !isEqual(
|
||||
fieldDiff.merged_version,
|
||||
finalDiffableRule[fieldName]
|
||||
),
|
||||
isCustomized: calcIsCustomized(fieldDiff),
|
||||
fieldDiff,
|
||||
finalDiffableRule: calcFinalDiffableRule(ruleUpgradeState),
|
||||
finalDiffableRule,
|
||||
rightSideMode: editing ? FieldFinalSideMode.Edit : FieldFinalSideMode.Readonly,
|
||||
setRuleFieldResolvedValue,
|
||||
setReadOnlyMode,
|
||||
|
@ -116,7 +133,7 @@ export function FieldUpgradeContextProvider({
|
|||
fieldName,
|
||||
fieldUpgradeState,
|
||||
fieldDiff,
|
||||
ruleUpgradeState,
|
||||
finalDiffableRule,
|
||||
editing,
|
||||
setRuleFieldResolvedValue,
|
||||
setReadOnlyMode,
|
||||
|
|
|
@ -28,6 +28,13 @@ export function FieldUpgradeStateInfo({ state }: FieldUpgradeStateInfoProps): JS
|
|||
description: i18n.NO_UPDATE_DESCRIPTION,
|
||||
};
|
||||
|
||||
case FieldUpgradeStateEnum.SameUpdate:
|
||||
return {
|
||||
color: 'success',
|
||||
title: i18n.SAME_UPDATE,
|
||||
description: i18n.SAME_UPDATE_DESCRIPTION,
|
||||
};
|
||||
|
||||
case FieldUpgradeStateEnum.NoConflict:
|
||||
return {
|
||||
color: 'success',
|
||||
|
|
|
@ -22,6 +22,21 @@ export const NO_UPDATE_DESCRIPTION = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const SAME_UPDATE = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.rules.upgradeRules.fieldUpgradeState.sameUpdate',
|
||||
{
|
||||
defaultMessage: 'Matching update',
|
||||
}
|
||||
);
|
||||
|
||||
export const SAME_UPDATE_DESCRIPTION = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.rules.upgradeRules.fieldUpgradeState.sameUpdateDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'The field was modified after rule installation, and your changes are the same as the update from Elastic.',
|
||||
}
|
||||
);
|
||||
|
||||
export const NO_CONFLICT = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.rules.upgradeRules.fieldUpgradeState.noConflict',
|
||||
{
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
export enum FieldUpgradeStateEnum {
|
||||
NoUpdate = 'NO_UPDATE',
|
||||
SameUpdate = 'SAME_UPDATE',
|
||||
NoConflict = 'NO_CONFLICT',
|
||||
Accepted = 'ACCEPTED',
|
||||
SolvableConflict = 'SOLVABLE_CONFLICT',
|
||||
|
|
|
@ -20,6 +20,7 @@ import {
|
|||
ThreeWayDiffConflict,
|
||||
type RuleSignatureId,
|
||||
NON_UPGRADEABLE_DIFFABLE_FIELDS,
|
||||
ThreeWayDiffOutcome,
|
||||
} from '../../../../../../common/api/detection_engine';
|
||||
import { assertUnreachable } from '../../../../../../common/utility_types';
|
||||
|
||||
|
@ -104,11 +105,18 @@ function calcFieldsState(
|
|||
|
||||
switch (fieldDiff.conflict) {
|
||||
case ThreeWayDiffConflict.NONE:
|
||||
fieldsState[fieldName] = {
|
||||
state: fieldDiff.has_update
|
||||
? FieldUpgradeStateEnum.NoConflict
|
||||
: FieldUpgradeStateEnum.NoUpdate,
|
||||
};
|
||||
if (fieldDiff.has_update) {
|
||||
fieldsState[fieldName] = {
|
||||
state: FieldUpgradeStateEnum.NoConflict,
|
||||
};
|
||||
} else {
|
||||
fieldsState[fieldName] = {
|
||||
state:
|
||||
fieldDiff.diff_outcome === ThreeWayDiffOutcome.CustomizedValueSameUpdate
|
||||
? FieldUpgradeStateEnum.SameUpdate
|
||||
: FieldUpgradeStateEnum.NoUpdate,
|
||||
};
|
||||
}
|
||||
break;
|
||||
|
||||
case ThreeWayDiffConflict.SOLVABLE:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue