Skip to content

Commit

Permalink
[8.x] [Security Solution] Integrate state and components for Prebuilt…
Browse files Browse the repository at this point in the history
… Rule Update Workflow (#193531) (#194348)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[Security Solution] Integrate state and components for Prebuilt Rule
Update Workflow
(#193531)](#193531)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Maxim
Palenov","email":"maxim.palenov@elastic.co"},"sourceCommit":{"committedDate":"2024-09-27T21:07:12Z","message":"[Security
Solution] Integrate state and components for Prebuilt Rule Update
Workflow (#193531)\n\n**Epic:**
https://github.com/elastic/kibana/issues/174168\r\n**Addresses:**
https://github.com/elastic/kibana/issues/171520\r\n\r\n##
Summary\r\n\r\nThis 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
#193261 and rule upgrade state
implemented in https://github.com/elastic/kibana/pull/191721.\r\n\r\n##
Details\r\n\r\nThe goal of this PR is to provide intermediate
integration between rule upgrade state
([PR](#191721)) and components
displaying the diff and read-only state
([PR](#193261)). It will
facilitate further development of rule field editable components and
streamline rule upgrade functionality developing.\r\n\r\n## How to
test?\r\n\r\nThe functionality is hidden under
`prebuiltRulesCustomizationEnabled` feature flag. Add the following to
your Kibana
config\r\n\r\n```yaml\r\nxpack.securitySolution.enableExperimental:\r\n
- prebuiltRulesCustomizationEnabled\r\n```\r\n\r\nWhen the above feature
flag enabled the new `Update` tab is displayed instead of the old
one.\r\n\r\n## Screenshots\r\n\r\nSuggested components design
\r\n![image](https://github.com/user-attachments/assets/b5aaf571-286a-4595-9bd4-fdaf9a423b03)\r\n\r\nNew
`Update` tab\r\n<img width=\"1718\" alt=\"image\"
src=\"https://github.com/user-attachments/assets/28aa6bb3-f805-4109-a808-d67e58c7c5b8\">","sha":"878ba134e96245b038a9765148ad48a36bb2aa4b","branchLabelMapping":{"^v9.0.0$":"main","^v8.16.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","Team:Detections
and Resp","Team: SecuritySolution","Team:Detection Rule
Management","backport:prev-minor"],"title":"[Security Solution]
Integrate state and components for Prebuilt Rule Update
Workflow","number":193531,"url":"https://github.com/elastic/kibana/pull/193531","mergeCommit":{"message":"[Security
Solution] Integrate state and components for Prebuilt Rule Update
Workflow (#193531)\n\n**Epic:**
https://github.com/elastic/kibana/issues/174168\r\n**Addresses:**
https://github.com/elastic/kibana/issues/171520\r\n\r\n##
Summary\r\n\r\nThis 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
#193261 and rule upgrade state
implemented in https://github.com/elastic/kibana/pull/191721.\r\n\r\n##
Details\r\n\r\nThe goal of this PR is to provide intermediate
integration between rule upgrade state
([PR](#191721)) and components
displaying the diff and read-only state
([PR](#193261)). It will
facilitate further development of rule field editable components and
streamline rule upgrade functionality developing.\r\n\r\n## How to
test?\r\n\r\nThe functionality is hidden under
`prebuiltRulesCustomizationEnabled` feature flag. Add the following to
your Kibana
config\r\n\r\n```yaml\r\nxpack.securitySolution.enableExperimental:\r\n
- prebuiltRulesCustomizationEnabled\r\n```\r\n\r\nWhen the above feature
flag enabled the new `Update` tab is displayed instead of the old
one.\r\n\r\n## Screenshots\r\n\r\nSuggested components design
\r\n![image](https://github.com/user-attachments/assets/b5aaf571-286a-4595-9bd4-fdaf9a423b03)\r\n\r\nNew
`Update` tab\r\n<img width=\"1718\" alt=\"image\"
src=\"https://github.com/user-attachments/assets/28aa6bb3-f805-4109-a808-d67e58c7c5b8\">","sha":"878ba134e96245b038a9765148ad48a36bb2aa4b"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/193531","number":193531,"mergeCommit":{"message":"[Security
Solution] Integrate state and components for Prebuilt Rule Update
Workflow (#193531)\n\n**Epic:**
https://github.com/elastic/kibana/issues/174168\r\n**Addresses:**
https://github.com/elastic/kibana/issues/171520\r\n\r\n##
Summary\r\n\r\nThis 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
#193261 and rule upgrade state
implemented in https://github.com/elastic/kibana/pull/191721.\r\n\r\n##
Details\r\n\r\nThe goal of this PR is to provide intermediate
integration between rule upgrade state
([PR](#191721)) and components
displaying the diff and read-only state
([PR](#193261)). It will
facilitate further development of rule field editable components and
streamline rule upgrade functionality developing.\r\n\r\n## How to
test?\r\n\r\nThe functionality is hidden under
`prebuiltRulesCustomizationEnabled` feature flag. Add the following to
your Kibana
config\r\n\r\n```yaml\r\nxpack.securitySolution.enableExperimental:\r\n
- prebuiltRulesCustomizationEnabled\r\n```\r\n\r\nWhen the above feature
flag enabled the new `Update` tab is displayed instead of the old
one.\r\n\r\n## Screenshots\r\n\r\nSuggested components design
\r\n![image](https://github.com/user-attachments/assets/b5aaf571-286a-4595-9bd4-fdaf9a423b03)\r\n\r\nNew
`Update` tab\r\n<img width=\"1718\" alt=\"image\"
src=\"https://github.com/user-attachments/assets/28aa6bb3-f805-4109-a808-d67e58c7c5b8\">","sha":"878ba134e96245b038a9765148ad48a36bb2aa4b"}}]}]
BACKPORT-->

Co-authored-by: Maxim Palenov <maxim.palenov@elastic.co>
  • Loading branch information
kibanamachine and maximpn authored Sep 27, 2024
1 parent d90b6a7 commit 424886a
Show file tree
Hide file tree
Showing 21 changed files with 538 additions and 141 deletions.
Original file line number Diff line number Diff line change
@@ -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';
Original file line number Diff line number Diff line change
@@ -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<SplitAccordionProps>) => {
const accordionId = useGeneratedHtmlId();
const { euiTheme } = useEuiTheme();

return (
<EuiSplitPanel.Outer data-test-subj={`${dataTestSubj}Wrapper`} hasBorder>
<EuiAccordion
id={accordionId}
initialIsOpen={initialIsOpen}
css={css`
.euiAccordion__triggerWrapper {
background: ${euiTheme.colors.lightestShade};
padding: ${euiTheme.size.m};
}
`}
buttonContent={header}
>
<EuiSplitPanel.Inner data-test-subj={`${dataTestSubj}Content`} color="transparent">
{children}
</EuiSplitPanel.Inner>
</EuiAccordion>
</EuiSplitPanel.Outer>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -46,21 +46,31 @@ export const FieldGroupDiffComponent = ({
fieldsGroupName,
}: FieldDiffComponentProps) => {
const { fieldDiffs, shouldShowSubtitles } = ruleDiffs;

return (
<RuleDiffPanelWrapper fieldName={fieldsGroupName}>
<SplitAccordion
header={
<EuiTitle data-test-subj="ruleUpgradePerFieldDiffLabel" size="xs">
<h5>{fieldToDisplayNameMap[fieldsGroupName] ?? startCase(camelCase(fieldsGroupName))}</h5>
</EuiTitle>
}
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 (
<SubFieldComponent
key={specificFieldName}
shouldShowSeparator={shouldShowSeparator}
shouldShowSeparator={!isLast}
shouldShowSubtitles={shouldShowSubtitles}
currentVersion={currentVersion}
targetVersion={targetVersion}
fieldName={specificFieldName}
/>
);
})}
</RuleDiffPanelWrapper>
</SplitAccordion>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,4 @@

export * from './field_diff';
export * from './header_bar';
export * from './panel_wrapper';
export * from './rule_diff_section';

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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 extends keyof DiffableAllFields> {
fieldName: FieldName;
Expand All @@ -42,12 +42,13 @@ export function ComparisonSide<FieldName extends keyof DiffableAllFields>({

return (
<>
<VersionsPicker
hasBaseVersion={fieldThreeWayDiff.has_base_version}
selectedVersions={selectedVersions}
onChange={setSelectedVersions}
/>
<EuiSpacer size="m" />
<SideHeader>
<VersionsPicker
hasBaseVersion={fieldThreeWayDiff.has_base_version}
selectedVersions={selectedVersions}
onChange={setSelectedVersions}
/>
</SideHeader>
<SubfieldChanges fieldName={fieldName} subfieldChanges={subfieldChanges} />
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export function pickFieldValueForVersion<FieldName extends keyof DiffableAllFiel
resolvedValue?: DiffableAllFields[FieldName]
): DiffableAllFields[FieldName] | undefined {
if (version === Version.Final) {
return resolvedValue ?? fieldThreeWayDiff.merged_version;
return resolvedValue;
}

const versionFieldToPick = `${version}_version` as const;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* 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 { EuiFlexGroup, EuiFlexItem, EuiSpacer, useEuiTheme } from '@elastic/eui';
import { css } from '@emotion/css';
import { SplitAccordion } from '../../../../../../common/components/split_accordion';
import type {
DiffableAllFields,
DiffableRule,
RuleFieldsDiff,
ThreeWayDiff,
} from '../../../../../../../common/api/detection_engine';
import { ThreeWayDiffConflict } from '../../../../../../../common/api/detection_engine';
import { ComparisonSide } from '../comparison_side/comparison_side';
import { FinalSide } from '../final_side/final_side';
import { FieldUpgradeConflictsResolverHeader } from './field_upgrade_conflicts_resolver_header';

interface FieldUpgradeConflictsResolverProps<FieldName extends keyof RuleFieldsDiff> {
fieldName: FieldName;
fieldThreeWayDiff: RuleFieldsDiff[FieldName];
finalDiffableRule: DiffableRule;
}

export function FieldUpgradeConflictsResolver<FieldName extends keyof RuleFieldsDiff>({
fieldName,
fieldThreeWayDiff,
finalDiffableRule,
}: FieldUpgradeConflictsResolverProps<FieldName>): JSX.Element {
const { euiTheme } = useEuiTheme();
const hasConflict = fieldThreeWayDiff.conflict !== ThreeWayDiffConflict.NONE;

return (
<>
<SplitAccordion
header={<FieldUpgradeConflictsResolverHeader fieldName={fieldName} />}
initialIsOpen={hasConflict}
data-test-subj="ruleUpgradePerFieldDiff"
>
<EuiFlexGroup gutterSize="s" alignItems="flexStart">
<EuiFlexItem grow={1}>
<ComparisonSide
fieldName={fieldName}
fieldThreeWayDiff={fieldThreeWayDiff as ThreeWayDiff<DiffableAllFields[FieldName]>}
resolvedValue={finalDiffableRule[fieldName] as DiffableAllFields[FieldName]}
/>
</EuiFlexItem>
<EuiFlexItem
grow={0}
css={css`
align-self: stretch;
border-right: ${euiTheme.border.thin};
`}
/>
<EuiFlexItem grow={1}>
<FinalSide fieldName={fieldName} finalDiffableRule={finalDiffableRule} />
</EuiFlexItem>
</EuiFlexGroup>
</SplitAccordion>
<EuiSpacer size="s" />
</>
);
}
Original file line number Diff line number Diff line change
@@ -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 (
<EuiTitle data-test-subj="ruleUpgradeFieldDiffLabel" size="xs">
<h5>{fieldToDisplayNameMap[fieldName] ?? startCase(camelCase(fieldName))}</h5>
</EuiTitle>
);
}
Original file line number Diff line number Diff line change
@@ -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<typeof ruleUpgradeState.diff.fields>[keyof typeof ruleUpgradeState.diff.fields]
]
>;
const fields = fieldDiffEntries.map(([fieldName, fieldDiff]) => (
<FieldUpgradeConflictsResolver
key={fieldName}
fieldName={fieldName}
fieldThreeWayDiff={fieldDiff}
finalDiffableRule={ruleUpgradeState.finalRule}
/>
));

return <>{fields}</>;
}
Original file line number Diff line number Diff line change
@@ -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 (
<UtilityBar>
<UtilityBarSection>
<UtilityBarGroup>
<UtilityBarText dataTestSubj="showingRules">
{i18n.NUM_OF_FIELDS_WITH_UPDATES(numOfFieldsWithUpdates)}
</UtilityBarText>
</UtilityBarGroup>
<UtilityBarGroup>
<UtilityBarText dataTestSubj="showingRules">
{i18n.NUM_OF_CONFLICTS(numOfConflicts)}
</UtilityBarText>
</UtilityBarGroup>
</UtilityBarSection>
<UtilityBarSection>
<UtilityBarGroup>
<i18n.RuleUpgradeHelper />
</UtilityBarGroup>
</UtilityBarSection>
</UtilityBar>
);
}
Loading

0 comments on commit 424886a

Please sign in to comment.