Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Security Solution] Rule Diff Phase 2 components #174564

Merged
merged 27 commits into from
Feb 12, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
95e1e59
working poc
dplumlee Jan 10, 2024
d6b55b3
adds feature flag
dplumlee Jan 10, 2024
78a7911
adds inline per field display
dplumlee Jan 10, 2024
3992626
adds field sort
dplumlee Jan 17, 2024
58803ca
updates ui
dplumlee Jan 18, 2024
e1ce9f3
updates ui
dplumlee Jan 23, 2024
3e01b6b
addresses comments and changes some types
dplumlee Jan 26, 2024
9405654
Merge remote-tracking branch 'upstream/main' into rule-upgrade-diff-p…
dplumlee Jan 26, 2024
c4fba65
fixes bugs and removes console logs
dplumlee Feb 1, 2024
35e99bd
changes header language
dplumlee Feb 5, 2024
57a92a6
Refactored getFormattedFieldDiff
jpdjere Feb 2, 2024
7bc88ff
Refactored per group util
jpdjere Feb 2, 2024
c0240f3
Renamed props
jpdjere Feb 2, 2024
da1375b
Fixed typing
jpdjere Feb 2, 2024
05070e6
Replace N/A with empty string for non existing fields
jpdjere Feb 2, 2024
1feb961
Merge remote-tracking branch 'upstream/main' into rule-upgrade-diff-p…
dplumlee Feb 5, 2024
0e34460
updates field name render dictionary
dplumlee Feb 5, 2024
2687192
fixes small bugs and adds code comments
dplumlee Feb 7, 2024
c92da27
turns off feature flag
dplumlee Feb 7, 2024
977eae3
typo
dplumlee Feb 7, 2024
18e0db2
turns off feature flag
dplumlee Feb 7, 2024
bc20d45
Merge remote-tracking branch 'upstream/main' into rule-upgrade-diff-p…
dplumlee Feb 7, 2024
95e5155
Merge remote-tracking branch 'upstream/main' into rule-upgrade-diff-p…
dplumlee Feb 8, 2024
4f7171e
adds remaining field groupings
dplumlee Feb 8, 2024
94b4cd8
addresses comments
dplumlee Feb 12, 2024
773501e
Merge remote-tracking branch 'upstream/main' into rule-upgrade-diff-p…
dplumlee Feb 12, 2024
faaec7f
addresses comments
dplumlee Feb 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ export type DiffableAllFields = DiffableCommonFields &
Omit<DiffableCustomQueryFields, 'type'> &
Omit<DiffableSavedQueryFields, 'type'> &
Omit<DiffableEqlFields, 'type'> &
Omit<DiffableEsqlFields, 'type'> &
Omit<DiffableThreatMatchFields, 'type'> &
Omit<DiffableThresholdFields, 'type'> &
Omit<DiffableMachineLearningFields, 'type'> &
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,11 @@ export const allowedExperimentalValues = Object.freeze({
*
*/
timelineEsqlTabDisabled: false,

/**
* Enables per-field rule diffs tab in the prebuilt rule upgrade flyout
*/
perFieldPrebuiltRulesDiffingEnabled: true,
});

type ExperimentalConfigKeys = Array<keyof ExperimentalFeatures>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,114 @@
* 2.0.
*/

import type { DiffableAllFields } from '../../../../../common/api/detection_engine';

export const DEFAULT_DESCRIPTION_LIST_COLUMN_WIDTHS: [string, string] = ['50%', '50%'];
export const LARGE_DESCRIPTION_LIST_COLUMN_WIDTHS: [string, string] = ['30%', '70%'];

/**
* This order is derived from a combination of the Rule Details Flyout display order
* and the `DiffableRule` type that is returned from the rule diff API endpoint
*/
export const UPGRADE_FIELD_ORDER: Array<keyof DiffableAllFields> = [
// Rule About fields
'version',
'name',
'description',
'author',
dplumlee marked this conversation as resolved.
Show resolved Hide resolved
'building_block',
'severity',
'severity_mapping',
'risk_score',
'risk_score_mapping',
'references',
'false_positives',
'license',
'rule_name_override',
'threat',
'threat_indicator_path',
'timestamp_override',
'tags',
// Rule Definition fields
'data_source',
'type',
'kql_query',
'eql_query',
'event_category_override',
'timestamp_field',
'tiebreaker_field',
'esql_query',
'anomaly_threshold',
'machine_learning_job_id',
'related_integrations',
'required_fields',
'timeline_template',
'threshold',
'threat_index',
'threat_mapping',
'threat_query',
'threat_indicator_path',
'concurrent_searches',
'items_per_search',
'alert_suppression',
'new_terms_fields',
'history_window_start',
'max_signals',
// Rule Schedule fields
'rule_schedule',
// Rule Setup fields
'setup',
'note',
];

export const ABOUT_UPGRADE_FIELD_ORDER = [
dplumlee marked this conversation as resolved.
Show resolved Hide resolved
'version',
'name',
'description',
'author',
'building_block',
'severity',
'severity_mapping',
'risk_score',
'risk_score_mapping',
'references',
'false_positives',
'license',
'rule_name_override',
'threat',
'threat_indicator_path',
'timestamp_override',
'tags',
];

export const DEFINITION_UPGRADE_FIELD_ORDER = [
'data_source',
'type',
'kql_query',
'eql_query',
'event_category_override',
'timestamp_field',
'tiebreaker_field',
'esql_query',
'anomaly_threshold',
'machine_learning_job_id',
'related_integrations',
'required_fields',
'timeline_template',
'threshold',
'threat_index',
'threat_mapping',
'threat_filters',
dplumlee marked this conversation as resolved.
Show resolved Hide resolved
'threat_query',
'threat_indicator_path',
'concurrent_searches',
'items_per_search',
'alert_suppression',
'new_terms_fields',
'history_window_start',
'max_signals',
];

export const SCHEDULE_UPGRADE_FIELD_ORDER = ['rule_schedule'];

export const SETUP_UPGRADE_FIELD_ORDER = ['setup', 'note'];
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* 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';
import { EuiFlexGroup, EuiHorizontalRule, EuiTitle } from '@elastic/eui';
import { camelCase, startCase } from 'lodash';
import React from 'react';
import { DiffView } from '../json_diff/diff_view';
import { DiffMethod } from '../json_diff/mark_edits';
import { RuleDiffPanelWrapper } from './panel_wrapper';

export interface FieldDiffComponentProps {
ruleDiffs: Array<{ currentVersion: unknown; targetVersion: unknown; fieldName: string }>;
fieldName: string;
}

const sortAndStringifyJson = (jsObject: unknown): string => {
if (typeof jsObject === 'string') {
return jsObject;
}
return stringify(jsObject, { space: 2 });
};

export const FieldDiffComponent = ({ ruleDiffs, fieldName }: FieldDiffComponentProps) => {
console.log('here: ', ruleDiffs);
return (
<RuleDiffPanelWrapper fieldName={fieldName}>
{ruleDiffs.length === 1 ? (
<EuiFlexGroup justifyContent="spaceBetween">
<DiffView
oldSource={sortAndStringifyJson(ruleDiffs[0].currentVersion)}
newSource={sortAndStringifyJson(ruleDiffs[0].targetVersion)}
diffMethod={DiffMethod.WORDS}
/>
</EuiFlexGroup>
) : (
ruleDiffs.map(({ currentVersion, targetVersion, fieldName: specificFieldName }) => {
const formattedCurrentVersion = sortAndStringifyJson(currentVersion);
const formattedTargetVersion = sortAndStringifyJson(targetVersion);
return (
<EuiFlexGroup key={specificFieldName} justifyContent="spaceBetween">
{formattedCurrentVersion !== formattedTargetVersion ? (
<EuiFlexGroup direction="column">
<EuiTitle size="xxxs">
<h4>{startCase(camelCase(specificFieldName))}</h4>
</EuiTitle>
<DiffView
oldSource={formattedCurrentVersion}
newSource={formattedTargetVersion}
diffMethod={DiffMethod.WORDS}
/>
<EuiHorizontalRule margin="s" size="full" />
</EuiFlexGroup>
) : null}
</EuiFlexGroup>
);
})
)}
</RuleDiffPanelWrapper>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* 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 {
EuiFlexGroup,
EuiHorizontalRule,
EuiIconTip,
EuiSpacer,
EuiTitle,
useEuiTheme,
} from '@elastic/eui';
import React from 'react';
import { css } from '@emotion/css';
import * as i18n from '../json_diff/translations';

export const RuleDiffHeaderBar = () => {
const { euiTheme } = useEuiTheme();
return (
<div
css={css`
position: sticky;
top: 0;
background: ${euiTheme.colors.emptyShade};
`}
>
<EuiSpacer size="m" />
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
<EuiFlexGroup alignItems="baseline" gutterSize="xs">
<EuiIconTip
color="subdued"
content={i18n.BASE_VERSION_DESCRIPTION}
type="iInCircle"
size="m"
display="block"
/>
<EuiTitle size="xxs">
<h6>{i18n.BASE_VERSION}</h6>
</EuiTitle>
</EuiFlexGroup>
<EuiFlexGroup alignItems="baseline" gutterSize="xs">
<EuiIconTip
color="subdued"
content={i18n.UPDATED_VERSION_DESCRIPTION}
type="iInCircle"
size="m"
/>
<EuiTitle size="xxs">
<h6>{i18n.UPDATED_VERSION}</h6>
</EuiTitle>
</EuiFlexGroup>
</EuiFlexGroup>
<EuiHorizontalRule margin="s" size="full" />
</div>
);
};
Original file line number Diff line number Diff line change
@@ -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.
*/

export * from './field_diff';
dplumlee marked this conversation as resolved.
Show resolved Hide resolved
export * from './header_bar';
export * from './panel_wrapper';
export * from './rule_diff_section';
Original file line number Diff line number Diff line change
@@ -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 { EuiAccordion, EuiSplitPanel, EuiTitle, useEuiTheme } from '@elastic/eui';
import { camelCase, startCase } from 'lodash';
import { css } from '@emotion/css';
import React from 'react';

interface RuleDiffPanelWrapperProps {
fieldName: string;
children: React.ReactNode;
}

export const RuleDiffPanelWrapper = ({ fieldName, children }: RuleDiffPanelWrapperProps) => {
const { euiTheme } = useEuiTheme();

return (
<EuiSplitPanel.Outer hasBorder>
<EuiAccordion
initialIsOpen={true}
css={css`
.euiAccordion__triggerWrapper {
background: ${euiTheme.colors.lightestShade};
padding: ${euiTheme.size.m};
}
`}
id={fieldName}
buttonContent={
<EuiTitle size="xs">
{/* TODO: replace with i18n translations */}
<h5>{startCase(camelCase(fieldName))}</h5>
</EuiTitle>
}
>
<EuiSplitPanel.Inner color="transparent">{children}</EuiSplitPanel.Inner>
</EuiAccordion>
</EuiSplitPanel.Outer>
);
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I get the current folder structure and where what components are supposed to be kept:

/rule_management/components/rule_details
  /diff_components/*
  /json_diff/*
  /per_field_diff/*
  /per_field_rule_diff_tab.tsx
  /rule_diff_tab.tsx

I'd like to propose a structure that would represent the actual components' hierarchy. Something like that:

/rule_management/components/rule_details
  /sections
    /rule_about_section.tsx
    /rule_definition_section.tsx
    /etc
  /flyout
    /rule_details_flyout.tsx
    /use_rule_details_flyout.tsx
    /diffs
      /common diff components and utils go here
    /tabs
      /json_diff_tab
      /per_field_diff_tab
      /overview_tab

If that makes sense, I'd suggest to do it in a separate PR.

Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* 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, EuiSpacer, EuiTitle } from '@elastic/eui';
import React from 'react';
import type { RuleFieldDiff } from '../../../model/rule_details/rule_field_diff';
import { FieldDiffComponent } from './field_diff';

interface RuleDiffSectionProps {
title: string;
fields: RuleFieldDiff[];
}

export const RuleDiffSection = ({ title, fields }: RuleDiffSectionProps) => (
<>
<EuiSpacer size="m" />
<EuiAccordion
initialIsOpen={true}
id={title}
buttonContent={
<EuiTitle size="s">
<h3>{title}</h3>
</EuiTitle>
}
>
{fields.map(({ fieldName, formattedDiffs }) => {
return (
<>
<EuiSpacer size="m" />
<FieldDiffComponent ruleDiffs={formattedDiffs} fieldName={fieldName} />
</>
);
})}
</EuiAccordion>
</>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* 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 { RuleFieldDiff } from '../../model/rule_details/rule_field_diff';
import {
ABOUT_UPGRADE_FIELD_ORDER,
DEFINITION_UPGRADE_FIELD_ORDER,
SCHEDULE_UPGRADE_FIELD_ORDER,
SETUP_UPGRADE_FIELD_ORDER,
} from './constants';

export const getSectionedFieldDiffs = (fields: RuleFieldDiff[]) => {
const aboutFields = [];
const definitionFields = [];
const scheduleFields = [];
const setupFields = [];
for (const field of fields) {
if (ABOUT_UPGRADE_FIELD_ORDER.includes(field.fieldName)) {
aboutFields.push(field);
} else if (DEFINITION_UPGRADE_FIELD_ORDER.includes(field.fieldName)) {
definitionFields.push(field);
} else if (SCHEDULE_UPGRADE_FIELD_ORDER.includes(field.fieldName)) {
scheduleFields.push(field);
} else if (SETUP_UPGRADE_FIELD_ORDER.includes(field.fieldName)) {
setupFields.push(field);
}
}
return {
aboutFields,
definitionFields,
scheduleFields,
setupFields,
};
};
Loading