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 all 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 @@ -239,6 +239,7 @@ export const DiffableNewTermsFields = buildSchema({
* NOTE: Every top-level field in a DiffableRule MUST BE LOGICALLY INDEPENDENT from other
* top-level fields.
*/

export type DiffableRule = t.TypeOf<typeof DiffableRule>;
export const DiffableRule = t.intersection([
DiffableCommonFields,
Expand All @@ -262,6 +263,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 @@ -73,3 +73,26 @@ export interface PartialRuleDiff {
fields: Partial<RuleFieldsDiff>;
has_conflict: boolean;
}

export type RuleFieldsDiffWithDataSource =
| CustomQueryFieldsDiff
| SavedQueryFieldsDiff
| EqlFieldsDiff
| ThreatMatchFieldsDiff
| ThresholdFieldsDiff
| NewTermsFieldsDiff;

export type RuleFieldsDiffWithKqlQuery =
| CustomQueryFieldsDiff
| SavedQueryFieldsDiff
| ThreatMatchFieldsDiff
| ThresholdFieldsDiff
| NewTermsFieldsDiff;

export type RuleFieldsDiffWithEqlQuery = EqlFieldsDiff;

export type RuleFieldsDiffWithEsqlQuery = EsqlFieldsDiff;

export type RuleFieldsDiffWithThreatQuery = ThreatMatchFieldsDiff;

export type RuleFieldsDiffWithThreshold = ThresholdFieldsDiff;
15 changes: 15 additions & 0 deletions x-pack/plugins/security_solution/common/experimental_features.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,13 +178,28 @@ export const allowedExperimentalValues = Object.freeze({
* Enables experimental "Updates" tab in the prebuilt rule upgrade flyout.
* This tab shows the JSON diff between the installed prebuilt rule
* version and the latest available version.
*
* Ticket: https://github.com/elastic/kibana/issues/169160
* Owners: https://github.com/orgs/elastic/teams/security-detection-rule-management
* Added: on Dec 06, 2023 in https://github.com/elastic/kibana/pull/172535
* Turned: on Dec 20, 2023 in https://github.com/elastic/kibana/pull/173368
* Expires: on Feb 20, 2024
*/
jsonPrebuiltRulesDiffingEnabled: true,
/*
* Disables discover esql tab within timeline
*
*/
timelineEsqlTabDisabled: false,

/**
* Enables per-field rule diffs tab in the prebuilt rule upgrade flyout
*
* Ticket: https://github.com/elastic/kibana/issues/166489
* Owners: https://github.com/orgs/elastic/teams/security-detection-rule-management
* Added: on Feb 12, 2023 in https://github.com/elastic/kibana/pull/174564
*/
perFieldPrebuiltRulesDiffingEnabled: false,
});

type ExperimentalConfigKeys = Array<keyof ExperimentalFeatures>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,73 @@
* 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%'];

export const ABOUT_UPGRADE_FIELD_ORDER: Array<keyof DiffableAllFields> = [
'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: Array<keyof DiffableAllFields> = [
'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',
];

export const SCHEDULE_UPGRADE_FIELD_ORDER: Array<keyof DiffableAllFields> = ['rule_schedule'];

export const SETUP_UPGRADE_FIELD_ORDER: Array<keyof DiffableAllFields> = ['setup', 'note'];

/**
* 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
...ABOUT_UPGRADE_FIELD_ORDER,
// Rule Definition fields
...DEFINITION_UPGRADE_FIELD_ORDER,
// Rule Schedule fields
...SCHEDULE_UPGRADE_FIELD_ORDER,
// Rule Setup fields
...SETUP_UPGRADE_FIELD_ORDER,
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* 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, EuiTitle } from '@elastic/eui';
import { camelCase, startCase } from 'lodash';
import React from 'react';
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';

const SubFieldComponent = ({
currentVersion,
targetVersion,
fieldName,
shouldShowSeparator,
shouldShowSubtitles,
}: FieldDiff & {
shouldShowSeparator: boolean;
shouldShowSubtitles: boolean;
}) => (
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexGroup direction="column">
{shouldShowSubtitles ? (
<EuiTitle size="xxxs">
<h4>{fieldToDisplayNameMap[fieldName] ?? startCase(camelCase(fieldName))}</h4>
</EuiTitle>
) : null}
<DiffView oldSource={currentVersion} newSource={targetVersion} />
{shouldShowSeparator ? <EuiHorizontalRule margin="s" size="full" /> : null}
</EuiFlexGroup>
</EuiFlexGroup>
);

export interface FieldDiffComponentProps {
ruleDiffs: FormattedFieldDiff;
fieldsGroupName: string;
}

export const FieldGroupDiffComponent = ({
ruleDiffs,
fieldsGroupName,
}: FieldDiffComponentProps) => {
const { fieldDiffs, shouldShowSubtitles } = ruleDiffs;
return (
<RuleDiffPanelWrapper fieldName={fieldsGroupName}>
{fieldDiffs.map(({ currentVersion, targetVersion, fieldName: specificFieldName }, index) => {
const shouldShowSeparator = index !== fieldDiffs.length - 1;
return (
<SubFieldComponent
key={specificFieldName}
shouldShowSeparator={shouldShowSeparator}
shouldShowSubtitles={shouldShowSubtitles}
currentVersion={currentVersion}
targetVersion={targetVersion}
fieldName={specificFieldName}
/>
);
})}
</RuleDiffPanelWrapper>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* 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};
z-index: 1; // Fixes accordion button displaying above header bug
`}
>
<EuiSpacer size="m" />
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
<EuiFlexGroup alignItems="baseline" gutterSize="xs">
<EuiIconTip
color="subdued"
content={i18n.CURRENT_VERSION_DESCRIPTION}
type="iInCircle"
size="m"
display="block"
/>
<EuiTitle size="xxs">
<h6>{i18n.CURRENT_RULE_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.ELASTIC_UPDATE_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';
import { fieldToDisplayNameMap } from './translations';

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">
<h5>{fieldToDisplayNameMap[fieldName] ?? startCase(camelCase(fieldName))}</h5>
</EuiTitle>
}
>
<EuiSplitPanel.Inner color="transparent">{children}</EuiSplitPanel.Inner>
</EuiAccordion>
</EuiSplitPanel.Outer>
);
};
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, EuiSpacer, EuiTitle } from '@elastic/eui';
import React from 'react';
import { css } from '@emotion/css';
import type { FieldsGroupDiff } from '../../../model/rule_details/rule_field_diff';
import { FieldGroupDiffComponent } from './field_diff';

interface RuleDiffSectionProps {
title: string;
fieldGroups: FieldsGroupDiff[];
}

export const RuleDiffSection = ({ title, fieldGroups }: RuleDiffSectionProps) => (
<>
<EuiSpacer size="m" />
<EuiAccordion
initialIsOpen={true}
id={title}
css={css`
padding-top: 1px; // Fixes border disappearing bug
`}
buttonContent={
<EuiTitle size="s">
<h3>{title}</h3>
</EuiTitle>
}
>
{fieldGroups.map(({ fieldsGroupName, formattedDiffs }) => {
return (
<React.Fragment key={fieldsGroupName}>
<EuiSpacer size="m" />
<FieldGroupDiffComponent ruleDiffs={formattedDiffs} fieldsGroupName={fieldsGroupName} />
</React.Fragment>
);
})}
</EuiAccordion>
</>
);
Loading