Skip to content

Commit

Permalink
[Security Solution] ThreeWayDiff UI: Migrate to using DiffableRule
Browse files Browse the repository at this point in the history
…TS type in `FieldReadOnly` component (#192342)

**Partially addresses: #171520
**Is a follow-up PR to: #191499

This is the 2nd of the 3 PRs for `FieldReadOnly`.
- The 1st [PR](#191499) added the
`FieldReadOnly` and a bunch of field components.
- This (2nd) PR moves away from using `DiffableAllFields` type in favour
of `DiffableRule` and splits the large `FieldReadOnly` component into
smaller ones for readability.
 - Next (3rd) PR will add the remaining field components.

## Summary
This PR changes the TS type (`DiffableAllFields` -> `DiffableRule`) used
by the `FieldReadOnly` component. This component displays a read-only
view of a particular rule field, similar to how fields are shown on the
Rule Details page. Using `DiffableRule` type makes the component
compatible with the flyout context and is safer to use than
`DiffableAllFields`.

### Changes
- TS type used in the `FieldReadOnly` component and Storybook stories
changed to `DiffableRule`.
- `FieldReadOnly` field rendering was split into multiple files by rule
type to make it more readable.
- Added rule-mocking functions to Storybook to allow creation of
`DiffableRule` mocks.
 - Added field components for `name`, `description` and `tags` fields.
- Rewrote type narrowing for `Filters` component to a type guard
(`isFilters`).
 - Fixed a couple of outdated code comments.


### Running
`FinalReadOnly` and its field components are not yet integrated into the
flyout, but you can view components in Storybook.
1. Run Storybook: `yarn storybook security_solution`
2. Go to `http://localhost:9001` in browser.

<img width="1062" alt="Scherm­afbeelding 2024-09-03 om 13 05 11"
src="https://github.com/user-attachments/assets/13b227d4-1321-47d9-a0a7-93868c9f4a15">
nikitaindik authored Sep 18, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent 02ce1b9 commit 70b7d26
Showing 43 changed files with 992 additions and 239 deletions.
Original file line number Diff line number Diff line change
@@ -131,6 +131,7 @@ export interface ThreeWayDiff<TValue> {
* True if:
* - base=A, current=A, target=B
* - base=A, current=B, target=C
* - base=<missing>, current=A, target=B
*/
has_update: boolean;

Original file line number Diff line number Diff line change
@@ -7,8 +7,8 @@

/**
* Enum of possible conflict outcomes of a three-way diff:
* - NON_SOLVABLE_CONFLICT: current != target and we couldn't automatically resolve the conflict between them
* - SOLVABLE_CONFLICT: current != target and we automatically resolved the conflict between them
* - NON_SOLVABLE: current != target and we couldn't automatically resolve the conflict between them
* - SOLVABLE: current != target and we automatically resolved the conflict between them
* - NO_CONFLICT:
* - current == target (value won't change)
* - current != target && current == base (stock rule will get a new value)
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@
* 2.0.
*/

import { isPlainObject } from 'lodash';
import type { Filter } from '@kbn/es-query';
import type {
DiffableAllFields,
@@ -77,16 +78,12 @@ export function getQueryLanguageLabel(language: string) {
}

/**
* Assigns type `Filter` to items that have a `meta` property. Removes any other items.
* Assigns type `Filter[]` to an array if every item in it has a `meta` property.
*/
export function typeCheckFilters(filters: unknown[]): Filter[] {
return filters.filter((f) => {
if (typeof f === 'object' && f !== null && 'meta' in f) {
return true;
}

return false;
}) as Filter[];
export function isFilters(maybeFilters: unknown[]): maybeFilters is Filter[] {
return maybeFilters.every(
(f) => typeof f === 'object' && f !== null && 'meta' in f && isPlainObject(f.meta)
);
}

type DataSourceProps =
Original file line number Diff line number Diff line change
@@ -256,7 +256,7 @@ interface TagsProps {
tags: string[];
}

const Tags = ({ tags }: TagsProps) => (
export const Tags = ({ tags }: TagsProps) => (
<BadgeList badges={tags} data-test-subj="tagsPropertyValue" />
);

Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* 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 {
DiffableCommonFields,
DiffableRule,
} from '../../../../../../../common/api/detection_engine';
import { RelatedIntegrationsReadOnly } from './fields/related_integrations/related_integrations';
import { RequiredFieldsReadOnly } from './fields/required_fields/required_fields';
import { SeverityMappingReadOnly } from './fields/severity_mapping/severity_mapping';
import { RiskScoreMappingReadOnly } from './fields/risk_score_mapping/risk_score_mapping';
import { ThreatReadOnly } from './fields/threat/threat';
import { NameReadOnly } from './fields/name/name';
import { TagsReadOnly } from './fields/tags/tags';
import { DescriptionReadOnly } from './fields/description/description';
import { assertUnreachable } from '../../../../../../../common/utility_types';

interface CommonRuleFieldReadOnlyProps {
fieldName: keyof DiffableCommonFields;
finalDiffableRule: DiffableRule;
}

// eslint-disable-next-line complexity
export function CommonRuleFieldReadOnly({
fieldName,
finalDiffableRule,
}: CommonRuleFieldReadOnlyProps) {
switch (fieldName) {
case 'author':
return null;
case 'building_block':
return null;
case 'description':
return <DescriptionReadOnly description={finalDiffableRule.description} />;
case 'exceptions_list':
return null;
case 'investigation_fields':
return null;
case 'false_positives':
return null;
case 'license':
return null;
case 'max_signals':
return null;
case 'name':
return <NameReadOnly name={finalDiffableRule.name} />;
case 'note':
return null;
case 'related_integrations':
return (
<RelatedIntegrationsReadOnly relatedIntegrations={finalDiffableRule.related_integrations} />
);
case 'required_fields':
return <RequiredFieldsReadOnly requiredFields={finalDiffableRule.required_fields} />;
case 'risk_score_mapping':
return <RiskScoreMappingReadOnly riskScoreMapping={finalDiffableRule.risk_score_mapping} />;
case 'rule_schedule':
return null;
case 'severity_mapping':
return <SeverityMappingReadOnly severityMapping={finalDiffableRule.severity_mapping} />;
case 'tags':
return <TagsReadOnly tags={finalDiffableRule.tags} />;
case 'threat':
return <ThreatReadOnly threat={finalDiffableRule.threat} />;
case 'references':
return null;
case 'risk_score':
return null;
case 'rule_id':
return null;
case 'rule_name_override':
return null;
case 'setup':
return null;
case 'severity':
return null;
case 'timestamp_override':
return null;
case 'timeline_template':
return null;
case 'version':
return null;
default:
return assertUnreachable(fieldName);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* 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 { DiffableCustomQueryFields } from '../../../../../../../common/api/detection_engine';
import { DataSourceReadOnly } from './fields/data_source/data_source';
import { KqlQueryReadOnly } from './fields/kql_query';

interface CustomQueryRuleFieldReadOnlyProps {
fieldName: keyof DiffableCustomQueryFields;
finalDiffableRule: DiffableCustomQueryFields;
}

export function CustomQueryRuleFieldReadOnly({
fieldName,
finalDiffableRule,
}: CustomQueryRuleFieldReadOnlyProps) {
switch (fieldName) {
case 'data_source':
return <DataSourceReadOnly dataSource={finalDiffableRule.data_source} />;
case 'kql_query':
return (
<KqlQueryReadOnly
kqlQuery={finalDiffableRule.kql_query}
dataSource={finalDiffableRule.data_source}
ruleType={finalDiffableRule.type}
/>
);
default:
return null; // Will replace with `assertUnreachable(fieldName)` once all fields are implemented
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* 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 { DiffableEqlFields } from '../../../../../../../common/api/detection_engine';
import { DataSourceReadOnly } from './fields/data_source/data_source';
import { EqlQueryReadOnly } from './fields/eql_query/eql_query';

interface EqlRuleFieldReadOnlyProps {
fieldName: keyof DiffableEqlFields;
finalDiffableRule: DiffableEqlFields;
}

export function EqlRuleFieldReadOnly({ fieldName, finalDiffableRule }: EqlRuleFieldReadOnlyProps) {
switch (fieldName) {
case 'data_source':
return <DataSourceReadOnly dataSource={finalDiffableRule.data_source} />;
case 'eql_query':
return (
<EqlQueryReadOnly
eqlQuery={finalDiffableRule.eql_query}
dataSource={finalDiffableRule.data_source}
/>
);
case 'type':
return null;
default:
return null; // Will replace with `assertUnreachable(fieldName)` once all fields are implemented
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* 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 { DiffableEsqlFields } from '../../../../../../../common/api/detection_engine';
import { EsqlQueryReadOnly } from './fields/esql_query/esql_query';

interface EsqlRuleFieldReadOnlyProps {
fieldName: keyof DiffableEsqlFields;
finalDiffableRule: DiffableEsqlFields;
}

export function EsqlRuleFieldReadOnly({
fieldName,
finalDiffableRule,
}: EsqlRuleFieldReadOnlyProps) {
switch (fieldName) {
case 'esql_query':
return <EsqlQueryReadOnly esqlQuery={finalDiffableRule.esql_query} />;
case 'type':
return null;
default:
return null; // Will replace with `assertUnreachable(fieldName)` once all fields are implemented
}
}
Loading

0 comments on commit 70b7d26

Please sign in to comment.