Skip to content

Commit 2ef2d67

Browse files
authored
Rule details flyout on detector view page (opensearch-project#292)
* Rule details flyout on detector view page Signed-off-by: Aleksandar Djindjic <[email protected]> * use RulesViewModelActor on detector view page Signed-off-by: Aleksandar Djindjic <[email protected]> * migrate create detector to RulesViewModelActor Signed-off-by: Aleksandar Djindjic <[email protected]> * migrate update detector rules to RulesViewModelActor Signed-off-by: Aleksandar Djindjic <[email protected]> * fix create detector 4th step Signed-off-by: Aleksandar Djindjic <[email protected]> Signed-off-by: Aleksandar Djindjic <[email protected]>
1 parent 9dba6c1 commit 2ef2d67

File tree

4 files changed

+90
-188
lines changed

4 files changed

+90
-188
lines changed

public/pages/CreateDetector/containers/CreateDetector.tsx

Lines changed: 14 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,13 @@ import {
2929
RuleItem,
3030
RuleItemInfo,
3131
} from '../components/DefineDetector/components/DetectionRules/types/interfaces';
32-
import { RuleInfo } from '../../../../server/models/interfaces';
3332
import { NotificationsStart } from 'opensearch-dashboards/public';
3433
import {
3534
errorNotificationToast,
3635
successNotificationToast,
3736
getPlugins,
3837
} from '../../../utils/helpers';
38+
import { RulesViewModelActor } from '../../Rules/models/RulesViewModelActor';
3939

4040
interface CreateDetectorProps extends RouteComponentProps {
4141
isEdit: boolean;
@@ -55,9 +55,11 @@ interface CreateDetectorState {
5555

5656
export default class CreateDetector extends Component<CreateDetectorProps, CreateDetectorState> {
5757
static contextType = CoreServicesContext;
58+
private rulesViewModelActor: RulesViewModelActor;
5859

5960
constructor(props: CreateDetectorProps) {
6061
super(props);
62+
this.rulesViewModelActor = new RulesViewModelActor(props.services.ruleService);
6163
this.state = {
6264
currentStep: DetectorCreationStep.DEFINE_DETECTOR,
6365
detector: EMPTY_DEFAULT_DETECTOR,
@@ -177,13 +179,21 @@ export default class CreateDetector extends Component<CreateDetectorProps, Creat
177179
}
178180

179181
async setupRulesState() {
180-
const prePackagedRules = await this.getRules(true);
181-
const customRules = await this.getRules(false);
182+
const { detector_type } = this.state.detector;
183+
184+
const allRules = await this.rulesViewModelActor.fetchRules(undefined, {
185+
bool: {
186+
must: [{ match: { 'rule.category': `${detector_type}` } }],
187+
},
188+
});
189+
190+
const prePackagedRules = allRules.filter((rule) => rule.prePackaged);
191+
const customRules = allRules.filter((rule) => !rule.prePackaged);
182192

183193
this.setState({
184194
rulesState: {
185195
...this.state.rulesState,
186-
allRules: customRules.concat(prePackagedRules),
196+
allRules: customRules.concat(prePackagedRules).map((rule) => ({ ...rule, enabled: true })),
187197
page: {
188198
index: 0,
189199
},
@@ -210,59 +220,6 @@ export default class CreateDetector extends Component<CreateDetectorProps, Creat
210220
this.setState({ plugins });
211221
}
212222

213-
async getRules(prePackaged: boolean): Promise<RuleItemInfo[]> {
214-
try {
215-
const { detector_type } = this.state.detector;
216-
217-
if (!detector_type) {
218-
return [];
219-
}
220-
221-
const rulesRes = await this.props.services.ruleService.getRules(prePackaged, {
222-
from: 0,
223-
size: 5000,
224-
query: {
225-
nested: {
226-
path: 'rule',
227-
query: {
228-
bool: {
229-
must: [{ match: { 'rule.category': `${detector_type}` } }],
230-
},
231-
},
232-
},
233-
},
234-
});
235-
236-
if (rulesRes.ok) {
237-
const rules: RuleItemInfo[] = rulesRes.response.hits.hits.map((ruleInfo: RuleInfo) => {
238-
return {
239-
...ruleInfo,
240-
enabled: true,
241-
prePackaged,
242-
};
243-
});
244-
245-
return rules;
246-
} else {
247-
errorNotificationToast(
248-
this.props.notifications,
249-
'retrieve',
250-
`${prePackaged ? 'pre-packaged' : 'custom'}`,
251-
rulesRes.error
252-
);
253-
return [];
254-
}
255-
} catch (error: any) {
256-
errorNotificationToast(
257-
this.props.notifications,
258-
'retrieve',
259-
`${prePackaged ? 'pre-packaged' : 'custom'}`,
260-
error
261-
);
262-
return [];
263-
}
264-
}
265-
266223
onPageChange = (page: { index: number; size: number }) => {
267224
this.setState({
268225
rulesState: {

public/pages/Detectors/components/DetectorRulesView/DetectorRulesView.tsx

Lines changed: 27 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@
44
*/
55

66
import { ContentPanel } from '../../../../components/ContentPanel';
7-
import React, { useContext, useEffect, useState } from 'react';
8-
import { EuiAccordion, EuiButton, EuiInMemoryTable, EuiSpacer, EuiText } from '@elastic/eui';
7+
import React, { useContext, useEffect, useState, useMemo } from 'react';
8+
import { EuiAccordion, EuiButton, EuiSpacer, EuiText } from '@elastic/eui';
99
import { RuleItem } from '../../../CreateDetector/components/DefineDetector/components/DetectionRules/types/interfaces';
10-
import { getRulesColumns } from '../../../CreateDetector/components/DefineDetector/components/DetectionRules/utils/constants';
1110
import { ServicesContext } from '../../../../services';
1211
import { Detector } from '../../../../../models/interfaces';
1312
import { RuleInfo } from '../../../../../server/models/interfaces';
@@ -16,6 +15,7 @@ import { NotificationsStart } from 'opensearch-dashboards/public';
1615
import { RulesTable } from '../../../Rules/components/RulesTable/RulesTable';
1716
import { RuleTableItem } from '../../../Rules/utils/helpers';
1817
import { RuleViewerFlyout } from '../../../Rules/components/RuleViewerFlyout/RuleViewerFlyout';
18+
import { RulesViewModelActor } from '../../../Rules/models/RulesViewModelActor';
1919

2020
export interface DetectorRulesViewProps {
2121
detector: Detector;
@@ -59,33 +59,12 @@ export const DetectorRulesView: React.FC<DetectorRulesViewProps> = (props) => {
5959
];
6060
const services = useContext(ServicesContext);
6161

62-
useEffect(() => {
63-
const getRules = async (prePackaged: boolean): Promise<RuleInfo[]> => {
64-
const getRulesRes = await services?.ruleService.getRules(prePackaged, {
65-
from: 0,
66-
size: 5000,
67-
query: {
68-
nested: {
69-
path: 'rule',
70-
query: {
71-
bool: {
72-
must: [
73-
{ match: { 'rule.category': `${props.detector.detector_type.toLowerCase()}` } },
74-
],
75-
},
76-
},
77-
},
78-
},
79-
});
80-
81-
if (getRulesRes?.ok) {
82-
return getRulesRes.response.hits.hits;
83-
} else {
84-
errorNotificationToast(props.notifications, 'retrieve', 'rules', getRulesRes?.error);
85-
return [];
86-
}
87-
};
62+
const rulesViewModelActor = useMemo(
63+
() => (services ? new RulesViewModelActor(services.ruleService) : null),
64+
[services]
65+
);
8866

67+
useEffect(() => {
8968
const updateRulesState = async () => {
9069
setLoading(true);
9170
const enabledPrePackagedRuleIds = new Set(
@@ -95,26 +74,32 @@ export const DetectorRulesView: React.FC<DetectorRulesViewProps> = (props) => {
9574
props.detector.inputs[0].detector_input.custom_rules.map((ruleInfo) => ruleInfo.id)
9675
);
9776

98-
const prePackagedRules = await getRules(true);
99-
const customRules = await getRules(false);
77+
const allRules = await rulesViewModelActor?.fetchRules(undefined, {
78+
bool: {
79+
must: [{ match: { 'rule.category': `${props.detector.detector_type.toLowerCase()}` } }],
80+
},
81+
});
10082

101-
const enabledPrePackagedRules = prePackagedRules.filter((hit: RuleInfo) => {
83+
const prePackagedRules = allRules?.filter((rule) => rule.prePackaged);
84+
const customRules = allRules?.filter((rule) => !rule.prePackaged);
85+
86+
const enabledPrePackagedRules = prePackagedRules?.filter((hit: RuleInfo) => {
10287
return enabledPrePackagedRuleIds.has(hit._id);
10388
});
10489

105-
const enabledCustomRules = customRules.filter((hit: RuleInfo) => {
90+
const enabledCustomRules = customRules?.filter((hit: RuleInfo) => {
10691
return enabledCustomRuleIds.has(hit._id);
10792
});
10893

10994
const enabledRuleItems = translateToRuleItems(
110-
enabledPrePackagedRules,
111-
enabledCustomRules,
95+
enabledPrePackagedRules || [],
96+
enabledCustomRules || [],
11297
props.detector.detector_type,
11398
() => true
11499
);
115100
const allRuleItems = translateToRuleItems(
116-
prePackagedRules,
117-
customRules,
101+
prePackagedRules || [],
102+
customRules || [],
118103
props.detector.detector_type,
119104
(ruleInfo) =>
120105
enabledPrePackagedRuleIds.has(ruleInfo._id) || enabledCustomRuleIds.has(ruleInfo._id)
@@ -129,16 +114,6 @@ export const DetectorRulesView: React.FC<DetectorRulesViewProps> = (props) => {
129114
});
130115
}, [services, props.detector]);
131116

132-
const rules = (
133-
<EuiInMemoryTable
134-
columns={getRulesColumns(false)}
135-
items={enabledRuleItems}
136-
itemId={(item: RuleItem) => `${item.name}`}
137-
pagination
138-
loading={loading}
139-
/>
140-
);
141-
142117
const getDetectionRulesTitle = () => `View detection rules (${totalSelected})`;
143118

144119
const onShowRuleDetails = (rule: RuleTableItem) => {
@@ -172,11 +147,14 @@ export const DetectorRulesView: React.FC<DetectorRulesViewProps> = (props) => {
172147
ruleItems={enabledRuleItems.map((i) => mapRuleItemToRuleTableItem(i))}
173148
showRuleDetails={onShowRuleDetails}
174149
/>
175-
{rules}
176150
</EuiAccordion>
177151
) : (
178152
<ContentPanel title={`Active rules (${totalSelected})`} actions={actions}>
179-
{rules}
153+
<RulesTable
154+
loading={loading}
155+
ruleItems={enabledRuleItems.map((i) => mapRuleItemToRuleTableItem(i))}
156+
showRuleDetails={onShowRuleDetails}
157+
/>
180158
</ContentPanel>
181159
)}
182160
</>

public/pages/Detectors/components/UpdateRules/UpdateRules.tsx

Lines changed: 40 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,10 @@
66
import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle } from '@elastic/eui';
77
import {
88
DetectorHit,
9-
GetRulesResponse,
109
SearchDetectorsResponse,
1110
UpdateDetectorResponse,
1211
} from '../../../../../server/models/interfaces';
13-
import React, { useCallback, useContext, useEffect, useState } from 'react';
12+
import React, { useCallback, useContext, useEffect, useState, useMemo } from 'react';
1413
import { RouteComponentProps } from 'react-router-dom';
1514
import { RuleItem } from '../../../CreateDetector/components/DefineDetector/components/DetectionRules/types/interfaces';
1615
import { Detector } from '../../../../../models/interfaces';
@@ -22,6 +21,7 @@ import { NotificationsStart } from 'opensearch-dashboards/public';
2221
import { errorNotificationToast, successNotificationToast } from '../../../../utils/helpers';
2322
import { RuleTableItem } from '../../../Rules/utils/helpers';
2423
import { RuleViewerFlyout } from '../../../Rules/components/RuleViewerFlyout/RuleViewerFlyout';
24+
import { RulesViewModelActor } from '../../../Rules/models/RulesViewModelActor';
2525

2626
export interface UpdateDetectorRulesProps
2727
extends RouteComponentProps<
@@ -42,6 +42,11 @@ export const UpdateDetectorRules: React.FC<UpdateDetectorRulesProps> = (props) =
4242
const detectorId = props.location.pathname.replace(`${ROUTES.EDIT_DETECTOR_RULES}/`, '');
4343
const [flyoutData, setFlyoutData] = useState<RuleTableItem | null>(null);
4444

45+
const rulesViewModelActor = useMemo(
46+
() => (services ? new RulesViewModelActor(services.ruleService) : null),
47+
[services]
48+
);
49+
4550
useEffect(() => {
4651
const getDetector = async () => {
4752
setLoading(true);
@@ -62,83 +67,41 @@ export const UpdateDetectorRules: React.FC<UpdateDetectorRulesProps> = (props) =
6267
};
6368

6469
const getRules = async (detector: Detector) => {
65-
const prePackagedResponse = (await services?.ruleService.getRules(true, {
66-
from: 0,
67-
size: 5000,
68-
query: {
69-
nested: {
70-
path: 'rule',
71-
query: {
72-
bool: {
73-
must: [{ match: { 'rule.category': `${detector.detector_type.toLowerCase()}` } }],
74-
},
75-
},
76-
},
77-
},
78-
})) as ServerResponse<GetRulesResponse>;
79-
if (prePackagedResponse.ok) {
80-
const ruleInfos = prePackagedResponse.response.hits.hits;
81-
const enabledRuleIds = detector.inputs[0].detector_input.pre_packaged_rules.map(
82-
(rule) => rule.id
83-
);
84-
const ruleItems = ruleInfos.map((rule) => ({
85-
name: rule._source.title,
86-
id: rule._id,
87-
severity: rule._source.level,
88-
logType: rule._source.category,
89-
library: 'Sigma',
90-
description: rule._source.description,
91-
active: enabledRuleIds.includes(rule._id),
92-
ruleInfo: rule,
93-
}));
94-
setPrePackagedRuleItems(ruleItems);
95-
} else {
96-
errorNotificationToast(
97-
props.notifications,
98-
'retrieve',
99-
'pre-packaged rules',
100-
prePackagedResponse.error
101-
);
102-
}
70+
const enabledRuleIds = detector.inputs[0].detector_input.pre_packaged_rules.map(
71+
(rule) => rule.id
72+
);
10373

104-
const customResponse = (await services?.ruleService.getRules(false, {
105-
from: 0,
106-
size: 5000,
107-
query: {
108-
nested: {
109-
path: 'rule',
110-
query: {
111-
bool: {
112-
must: [{ match: { 'rule.category': `${detector.detector_type.toLowerCase()}` } }],
113-
},
114-
},
115-
},
74+
const allRules = await rulesViewModelActor?.fetchRules(undefined, {
75+
bool: {
76+
must: [{ match: { 'rule.category': `${detector.detector_type.toLowerCase()}` } }],
11677
},
117-
})) as ServerResponse<GetRulesResponse>;
118-
if (customResponse.ok) {
119-
const ruleInfos = customResponse.response.hits.hits;
120-
const enabledRuleIds = detector.inputs[0].detector_input.custom_rules.map(
121-
(rule) => rule.id
122-
);
123-
const ruleItems = ruleInfos.map((rule) => ({
124-
name: rule._source.title,
125-
id: rule._id,
126-
severity: rule._source.level,
127-
logType: rule._source.category,
128-
library: 'Custom',
129-
description: rule._source.description,
130-
active: enabledRuleIds.includes(rule._id),
131-
ruleInfo: rule,
132-
}));
133-
setCustomRuleItems(ruleItems);
134-
} else {
135-
errorNotificationToast(
136-
props.notifications,
137-
'retrieve',
138-
'custom rules',
139-
customResponse.error
140-
);
141-
}
78+
});
79+
80+
const prePackagedRules = allRules?.filter((rule) => rule.prePackaged);
81+
const prePackagedRuleItems = prePackagedRules?.map((rule) => ({
82+
name: rule._source.title,
83+
id: rule._id,
84+
severity: rule._source.level,
85+
logType: rule._source.category,
86+
library: 'Sigma',
87+
description: rule._source.description,
88+
active: enabledRuleIds.includes(rule._id),
89+
ruleInfo: rule,
90+
}));
91+
setPrePackagedRuleItems(prePackagedRuleItems || []);
92+
93+
const customRules = allRules?.filter((rule) => !rule.prePackaged);
94+
const customRuleItems = customRules?.map((rule) => ({
95+
name: rule._source.title,
96+
id: rule._id,
97+
severity: rule._source.level,
98+
logType: rule._source.category,
99+
library: 'Custom',
100+
description: rule._source.description,
101+
active: enabledRuleIds.includes(rule._id),
102+
ruleInfo: rule,
103+
}));
104+
setCustomRuleItems(customRuleItems || []);
142105
};
143106

144107
const execute = async () => {

0 commit comments

Comments
 (0)