Skip to content

Commit abfa8b8

Browse files
authored
Rule YAML preview (#209)
* remove unused service Signed-off-by: Aleksandar Djindjic <[email protected]> * refactor form state Signed-off-by: Aleksandar Djindjic <[email protected]> * extract model and mappers Signed-off-by: Aleksandar Djindjic <[email protected]> * Extract Visual Rule Editor Component Signed-off-by: Aleksandar Djindjic <[email protected]> * fix missing default id Signed-off-by: Aleksandar Djindjic <[email protected]> * yaml editor Signed-off-by: Aleksandar Djindjic <[email protected]> * yaml rule editor mappings Signed-off-by: Aleksandar Djindjic <[email protected]> * more mapping guards Signed-off-by: Aleksandar Djindjic <[email protected]> * remove console.log's Signed-off-by: Aleksandar Djindjic <[email protected]> * YAML editor - cypress test Signed-off-by: Aleksandar Djindjic <[email protected]> * yaml editor snapshot test Signed-off-by: Aleksandar Djindjic <[email protected]> * rename model Signed-off-by: Aleksandar Djindjic <[email protected]> * more validations on yaml editor Signed-off-by: Aleksandar Djindjic <[email protected]> * use eui form validation error box Signed-off-by: Aleksandar Djindjic <[email protected]> * re-generate snapshot Signed-off-by: Aleksandar Djindjic <[email protected]> * 153: rule yaml preview Signed-off-by: Aleksandar Djindjic <[email protected]> * cypress test for rule yaml preview Signed-off-by: Aleksandar Djindjic <[email protected]> * update snapshot test Signed-off-by: Aleksandar Djindjic <[email protected]> * propagate ruleId to rule viewers Signed-off-by: Aleksandar Djindjic <[email protected]> Signed-off-by: Aleksandar Djindjic <[email protected]>
1 parent 34d3a57 commit abfa8b8

File tree

9 files changed

+1117
-191
lines changed

9 files changed

+1117
-191
lines changed

cypress/integration/2_rules.spec.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,19 @@ describe('Rules', () => {
215215
.contains(line, TWENTY_SECONDS_TIMEOUT)
216216
);
217217

218+
cy.get(
219+
'[data-test-subj="change-editor-type"] label:nth-child(2)',
220+
TWENTY_SECONDS_TIMEOUT
221+
).click({
222+
force: true,
223+
});
224+
225+
YAML_RULE_LINES.forEach((line) =>
226+
cy
227+
.get('[data-test-subj="rule_flyout_yaml_rule"]', TWENTY_SECONDS_TIMEOUT)
228+
.contains(line, TWENTY_SECONDS_TIMEOUT)
229+
);
230+
218231
// Close the flyout
219232
cy.get('[data-test-subj="euiFlyoutCloseButton"]', TWENTY_SECONDS_TIMEOUT).click({
220233
force: true,
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import React from 'react';
7+
import { render } from '@testing-library/react';
8+
import { RuleContentViewer } from './RuleContentViewer';
9+
10+
describe('<RuleContentViewer /> spec', () => {
11+
it('renders the component', () => {
12+
const { container } = render(
13+
<RuleContentViewer
14+
rule={
15+
{
16+
prePackaged: false,
17+
_source: {
18+
category: 'dns',
19+
title: 'My Rule',
20+
log_source: 'dns',
21+
description: 'My Rule',
22+
references: [],
23+
tags: [],
24+
level: 'high',
25+
false_positives: [],
26+
author: 'aleksandar',
27+
status: 'stable',
28+
last_update_time: '2022-11-22T23:00:00.000Z',
29+
queries: [
30+
{
31+
value: 'EventID: 4800',
32+
},
33+
],
34+
rule:
35+
'id: 25b9c01c-350d-4b95-bed1-836d04a4f324\ntitle: My Rule\ndescription: My Rule\nstatus: stable\nauthor: aleksandar\ndate: 2022/11/23\nmodified: 2022/11/23\nlogsource:\n category: dns\nlevel: high\ndetection:\n selection:\n EventID: 4800\n condition: selection\n',
36+
detection: 'selection:\n EventID: 4800\ncondition: selection\n',
37+
},
38+
} as any
39+
}
40+
></RuleContentViewer>
41+
);
42+
expect(container.firstChild).toMatchSnapshot();
43+
});
44+
});

public/pages/Rules/components/RuleContentViewer/RuleContentViewer.tsx

Lines changed: 161 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -14,144 +14,183 @@ import {
1414
EuiModalBody,
1515
EuiSpacer,
1616
EuiText,
17+
EuiButtonGroup,
1718
} from '@elastic/eui';
1819
import { DEFAULT_EMPTY_DATA } from '../../../../utils/constants';
19-
import React from 'react';
20+
import React, { useState } from 'react';
2021
import { RuleItemInfoBase } from '../../models/types';
22+
import { RuleContentYamlViewer } from './RuleContentYamlViewer';
2123

2224
export interface RuleContentViewerProps {
2325
rule: RuleItemInfoBase;
2426
}
2527

28+
const editorTypes = [
29+
{
30+
id: 'visual',
31+
label: 'Visual',
32+
},
33+
{
34+
id: 'yaml',
35+
label: 'YAML',
36+
},
37+
];
38+
2639
export const RuleContentViewer: React.FC<RuleContentViewerProps> = ({
27-
rule: { prePackaged, _source: ruleData },
40+
rule: { prePackaged, _source: ruleData, _id: ruleId },
2841
}) => {
42+
if (!ruleData.id) {
43+
ruleData.id = ruleId;
44+
}
45+
const [selectedEditorType, setSelectedEditorType] = useState('visual');
46+
47+
const onEditorTypeChange = (optionId: string) => {
48+
setSelectedEditorType(optionId);
49+
};
50+
2951
return (
3052
<EuiModalBody>
31-
<EuiFlexGroup justifyContent="flexEnd">
32-
<EuiFlexItem>
33-
<EuiFormLabel>Rule Name</EuiFormLabel>
34-
<EuiText data-test-subj={'rule_flyout_rule_name'}>{ruleData.title}</EuiText>
35-
</EuiFlexItem>
36-
<EuiFlexItem>
37-
<EuiFormLabel>Log Type</EuiFormLabel>
38-
<EuiText data-test-subj={'rule_flyout_rule_log_type'}>{ruleData.category}</EuiText>
39-
</EuiFlexItem>
40-
</EuiFlexGroup>
41-
42-
<EuiSpacer />
43-
44-
<EuiFormLabel>Description</EuiFormLabel>
45-
<EuiText data-test-subj={'rule_flyout_rule_description'}>
46-
{ruleData.description || DEFAULT_EMPTY_DATA}
47-
</EuiText>
48-
<EuiSpacer />
49-
50-
<EuiFlexGroup justifyContent="flexEnd">
51-
<EuiFlexItem>
52-
<EuiFormLabel>Last Updated</EuiFormLabel>
53-
{ruleData.last_update_time}
54-
</EuiFlexItem>
55-
<EuiFlexItem data-test-subj={'rule_flyout_rule_author'}>
56-
<EuiFormLabel>Author</EuiFormLabel>
57-
{ruleData.author}
58-
</EuiFlexItem>
59-
</EuiFlexGroup>
60-
61-
<EuiSpacer />
62-
63-
<EuiFlexGroup justifyContent="flexEnd">
64-
<EuiFlexItem data-test-subj={'rule_flyout_rule_source'}>
65-
<EuiFormLabel>Source</EuiFormLabel>
66-
{prePackaged ? 'Sigma' : 'Custom'}
67-
</EuiFlexItem>
68-
{prePackaged ? (
69-
<EuiFlexItem>
70-
<EuiFormLabel>License</EuiFormLabel>
71-
<EuiLink
72-
target={'_blank'}
73-
href={'https://github.com/SigmaHQ/sigma/blob/master/LICENSE.Detection.Rules.md'}
74-
>
75-
Detection Rule License (DLR)
76-
</EuiLink>
77-
</EuiFlexItem>
78-
) : null}
79-
</EuiFlexGroup>
80-
81-
<EuiSpacer />
82-
83-
<EuiFlexGroup justifyContent="flexEnd">
84-
<EuiFlexItem data-test-subj={'rule_flyout_rule_severity'}>
85-
<EuiFormLabel>Rule level</EuiFormLabel>
86-
{ruleData.level}
87-
</EuiFlexItem>
88-
</EuiFlexGroup>
89-
90-
<EuiSpacer />
91-
92-
<EuiFormLabel>Tags</EuiFormLabel>
93-
{ruleData.tags.length > 0 ? (
94-
<EuiFlexGroup direction="row" data-test-subj={'rule_flyout_rule_tags'}>
95-
{ruleData.tags.map((tag: any, i: number) => (
96-
<EuiFlexItem grow={false} key={i}>
97-
<EuiBadge color={'#DDD'}>{tag.value}</EuiBadge>
53+
<EuiButtonGroup
54+
data-test-subj="change-editor-type"
55+
legend="This is editor type selector"
56+
options={editorTypes}
57+
idSelected={selectedEditorType}
58+
onChange={(id) => onEditorTypeChange(id)}
59+
/>
60+
<EuiSpacer size="xl" />
61+
{selectedEditorType === 'visual' && (
62+
<>
63+
<EuiFlexGroup justifyContent="flexEnd">
64+
<EuiFlexItem>
65+
<EuiFormLabel>Rule Name</EuiFormLabel>
66+
<EuiText data-test-subj={'rule_flyout_rule_name'}>{ruleData.title}</EuiText>
9867
</EuiFlexItem>
99-
))}
100-
</EuiFlexGroup>
101-
) : (
102-
<div>{DEFAULT_EMPTY_DATA}</div>
103-
)}
68+
<EuiFlexItem>
69+
<EuiFormLabel>Log Type</EuiFormLabel>
70+
<EuiText data-test-subj={'rule_flyout_rule_log_type'}>{ruleData.category}</EuiText>
71+
</EuiFlexItem>
72+
</EuiFlexGroup>
73+
74+
<EuiSpacer />
75+
76+
<EuiFormLabel>Description</EuiFormLabel>
77+
<EuiText data-test-subj={'rule_flyout_rule_description'}>
78+
{ruleData.description || DEFAULT_EMPTY_DATA}
79+
</EuiText>
80+
<EuiSpacer />
81+
82+
<EuiFlexGroup justifyContent="flexEnd">
83+
<EuiFlexItem>
84+
<EuiFormLabel>Last Updated</EuiFormLabel>
85+
{ruleData.last_update_time}
86+
</EuiFlexItem>
87+
<EuiFlexItem data-test-subj={'rule_flyout_rule_author'}>
88+
<EuiFormLabel>Author</EuiFormLabel>
89+
{ruleData.author}
90+
</EuiFlexItem>
91+
</EuiFlexGroup>
10492

105-
<EuiSpacer />
106-
<EuiSpacer />
107-
108-
<EuiFormLabel>References</EuiFormLabel>
109-
{ruleData.references.length > 0 ? (
110-
ruleData.references.map((reference: any, i: number) => (
111-
<div key={i}>
112-
<EuiLink
113-
href={reference.value}
114-
target="_blank"
115-
key={reference}
116-
data-test-subj={'rule_flyout_rule_references'}
117-
>
118-
{reference.value}
119-
</EuiLink>
120-
<EuiSpacer />
93+
<EuiSpacer />
94+
95+
<EuiFlexGroup justifyContent="flexEnd">
96+
<EuiFlexItem data-test-subj={'rule_flyout_rule_source'}>
97+
<EuiFormLabel>Source</EuiFormLabel>
98+
{prePackaged ? 'Sigma' : 'Custom'}
99+
</EuiFlexItem>
100+
{prePackaged ? (
101+
<EuiFlexItem>
102+
<EuiFormLabel>License</EuiFormLabel>
103+
<EuiLink
104+
target={'_blank'}
105+
href={'https://github.com/SigmaHQ/sigma/blob/master/LICENSE.Detection.Rules.md'}
106+
>
107+
Detection Rule License (DLR)
108+
</EuiLink>
109+
</EuiFlexItem>
110+
) : null}
111+
</EuiFlexGroup>
112+
113+
<EuiSpacer />
114+
115+
<EuiFlexGroup justifyContent="flexEnd">
116+
<EuiFlexItem data-test-subj={'rule_flyout_rule_severity'}>
117+
<EuiFormLabel>Rule level</EuiFormLabel>
118+
{ruleData.level}
119+
</EuiFlexItem>
120+
</EuiFlexGroup>
121+
122+
<EuiSpacer />
123+
124+
<EuiFormLabel>Tags</EuiFormLabel>
125+
{ruleData.tags.length > 0 ? (
126+
<EuiFlexGroup direction="row" data-test-subj={'rule_flyout_rule_tags'}>
127+
{ruleData.tags.map((tag: any, i: number) => (
128+
<EuiFlexItem grow={false} key={i}>
129+
<EuiBadge color={'#DDD'}>{tag.value}</EuiBadge>
130+
</EuiFlexItem>
131+
))}
132+
</EuiFlexGroup>
133+
) : (
134+
<div>{DEFAULT_EMPTY_DATA}</div>
135+
)}
136+
137+
<EuiSpacer />
138+
<EuiSpacer />
139+
140+
<EuiFormLabel>References</EuiFormLabel>
141+
{ruleData.references.length > 0 ? (
142+
ruleData.references.map((reference: any, i: number) => (
143+
<div key={i}>
144+
<EuiLink
145+
href={reference.value}
146+
target="_blank"
147+
key={reference}
148+
data-test-subj={'rule_flyout_rule_references'}
149+
>
150+
{reference.value}
151+
</EuiLink>
152+
<EuiSpacer />
153+
</div>
154+
))
155+
) : (
156+
<div>{DEFAULT_EMPTY_DATA}</div>
157+
)}
158+
159+
<EuiSpacer />
160+
161+
<EuiFormLabel>False positive cases</EuiFormLabel>
162+
<div data-test-subj={'rule_flyout_rule_false_positives'}>
163+
{ruleData.false_positives.length > 0 ? (
164+
ruleData.false_positives.map((falsepositive: any, i: number) => (
165+
<div key={i}>
166+
{falsepositive.value}
167+
<EuiSpacer />
168+
</div>
169+
))
170+
) : (
171+
<div>{DEFAULT_EMPTY_DATA}</div>
172+
)}
121173
</div>
122-
))
123-
) : (
124-
<div>{DEFAULT_EMPTY_DATA}</div>
125-
)}
126174

127-
<EuiSpacer />
128-
129-
<EuiFormLabel>False positive cases</EuiFormLabel>
130-
<div data-test-subj={'rule_flyout_rule_false_positives'}>
131-
{ruleData.false_positives.length > 0 ? (
132-
ruleData.false_positives.map((falsepositive: any, i: number) => (
133-
<div key={i}>
134-
{falsepositive.value}
135-
<EuiSpacer />
136-
</div>
137-
))
138-
) : (
139-
<div>{DEFAULT_EMPTY_DATA}</div>
140-
)}
141-
</div>
142-
143-
<EuiSpacer />
144-
145-
<EuiFormLabel>Rule Status</EuiFormLabel>
146-
<div data-test-subj={'rule_flyout_rule_status'}>{ruleData.status}</div>
147-
148-
<EuiSpacer />
149-
150-
<EuiFormRow label="Detection" fullWidth>
151-
<EuiCodeBlock language="yaml" data-test-subj={'rule_flyout_rule_detection'}>
152-
{ruleData.detection}
153-
</EuiCodeBlock>
154-
</EuiFormRow>
175+
<EuiSpacer />
176+
177+
<EuiFormLabel>Rule Status</EuiFormLabel>
178+
<div data-test-subj={'rule_flyout_rule_status'}>{ruleData.status}</div>
179+
180+
<EuiSpacer />
181+
182+
<EuiFormRow label="Detection" fullWidth>
183+
<EuiCodeBlock language="yaml" data-test-subj={'rule_flyout_rule_detection'}>
184+
{ruleData.detection}
185+
</EuiCodeBlock>
186+
</EuiFormRow>
187+
</>
188+
)}
189+
{selectedEditorType === 'yaml' && (
190+
<EuiFormRow label="Rule" fullWidth>
191+
<RuleContentYamlViewer rule={ruleData} />
192+
</EuiFormRow>
193+
)}
155194
</EuiModalBody>
156195
);
157196
};

0 commit comments

Comments
 (0)