Skip to content

Commit

Permalink
Rule details flyout on detector view page (opensearch-project#292) (o…
Browse files Browse the repository at this point in the history
…pensearch-project#315)

* 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]>
(cherry picked from commit 2ef2d67)

Co-authored-by: Aleksandar Djindjic <[email protected]>
Signed-off-by: AWSHurneyt <[email protected]>
  • Loading branch information
2 people authored and AWSHurneyt committed Oct 12, 2023
1 parent 6cc940d commit 3196a87
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 188 deletions.
71 changes: 14 additions & 57 deletions public/pages/CreateDetector/containers/CreateDetector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ import {
RuleItem,
RuleItemInfo,
} from '../components/DefineDetector/components/DetectionRules/types/interfaces';
import { RuleInfo } from '../../../../server/models/interfaces';
import { NotificationsStart } from 'opensearch-dashboards/public';
import {
errorNotificationToast,
successNotificationToast,
getPlugins,
} from '../../../utils/helpers';
import { RulesViewModelActor } from '../../Rules/models/RulesViewModelActor';

interface CreateDetectorProps extends RouteComponentProps {
isEdit: boolean;
Expand All @@ -55,9 +55,11 @@ interface CreateDetectorState {

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

constructor(props: CreateDetectorProps) {
super(props);
this.rulesViewModelActor = new RulesViewModelActor(props.services.ruleService);
this.state = {
currentStep: DetectorCreationStep.DEFINE_DETECTOR,
detector: EMPTY_DEFAULT_DETECTOR,
Expand Down Expand Up @@ -177,13 +179,21 @@ export default class CreateDetector extends Component<CreateDetectorProps, Creat
}

async setupRulesState() {
const prePackagedRules = await this.getRules(true);
const customRules = await this.getRules(false);
const { detector_type } = this.state.detector;

const allRules = await this.rulesViewModelActor.fetchRules(undefined, {
bool: {
must: [{ match: { 'rule.category': `${detector_type}` } }],
},
});

const prePackagedRules = allRules.filter((rule) => rule.prePackaged);
const customRules = allRules.filter((rule) => !rule.prePackaged);

this.setState({
rulesState: {
...this.state.rulesState,
allRules: customRules.concat(prePackagedRules),
allRules: customRules.concat(prePackagedRules).map((rule) => ({ ...rule, enabled: true })),
page: {
index: 0,
},
Expand All @@ -210,59 +220,6 @@ export default class CreateDetector extends Component<CreateDetectorProps, Creat
this.setState({ plugins });
}

async getRules(prePackaged: boolean): Promise<RuleItemInfo[]> {
try {
const { detector_type } = this.state.detector;

if (!detector_type) {
return [];
}

const rulesRes = await this.props.services.ruleService.getRules(prePackaged, {
from: 0,
size: 5000,
query: {
nested: {
path: 'rule',
query: {
bool: {
must: [{ match: { 'rule.category': `${detector_type}` } }],
},
},
},
},
});

if (rulesRes.ok) {
const rules: RuleItemInfo[] = rulesRes.response.hits.hits.map((ruleInfo: RuleInfo) => {
return {
...ruleInfo,
enabled: true,
prePackaged,
};
});

return rules;
} else {
errorNotificationToast(
this.props.notifications,
'retrieve',
`${prePackaged ? 'pre-packaged' : 'custom'}`,
rulesRes.error
);
return [];
}
} catch (error: any) {
errorNotificationToast(
this.props.notifications,
'retrieve',
`${prePackaged ? 'pre-packaged' : 'custom'}`,
error
);
return [];
}
}

onPageChange = (page: { index: number; size: number }) => {
this.setState({
rulesState: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@
*/

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

export interface DetectorRulesViewProps {
detector: Detector;
Expand Down Expand Up @@ -59,33 +59,12 @@ export const DetectorRulesView: React.FC<DetectorRulesViewProps> = (props) => {
];
const services = useContext(ServicesContext);

useEffect(() => {
const getRules = async (prePackaged: boolean): Promise<RuleInfo[]> => {
const getRulesRes = await services?.ruleService.getRules(prePackaged, {
from: 0,
size: 5000,
query: {
nested: {
path: 'rule',
query: {
bool: {
must: [
{ match: { 'rule.category': `${props.detector.detector_type.toLowerCase()}` } },
],
},
},
},
},
});

if (getRulesRes?.ok) {
return getRulesRes.response.hits.hits;
} else {
errorNotificationToast(props.notifications, 'retrieve', 'rules', getRulesRes?.error);
return [];
}
};
const rulesViewModelActor = useMemo(
() => (services ? new RulesViewModelActor(services.ruleService) : null),
[services]
);

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

const prePackagedRules = await getRules(true);
const customRules = await getRules(false);
const allRules = await rulesViewModelActor?.fetchRules(undefined, {
bool: {
must: [{ match: { 'rule.category': `${props.detector.detector_type.toLowerCase()}` } }],
},
});

const enabledPrePackagedRules = prePackagedRules.filter((hit: RuleInfo) => {
const prePackagedRules = allRules?.filter((rule) => rule.prePackaged);
const customRules = allRules?.filter((rule) => !rule.prePackaged);

const enabledPrePackagedRules = prePackagedRules?.filter((hit: RuleInfo) => {
return enabledPrePackagedRuleIds.has(hit._id);
});

const enabledCustomRules = customRules.filter((hit: RuleInfo) => {
const enabledCustomRules = customRules?.filter((hit: RuleInfo) => {
return enabledCustomRuleIds.has(hit._id);
});

const enabledRuleItems = translateToRuleItems(
enabledPrePackagedRules,
enabledCustomRules,
enabledPrePackagedRules || [],
enabledCustomRules || [],
props.detector.detector_type,
() => true
);
const allRuleItems = translateToRuleItems(
prePackagedRules,
customRules,
prePackagedRules || [],
customRules || [],
props.detector.detector_type,
(ruleInfo) =>
enabledPrePackagedRuleIds.has(ruleInfo._id) || enabledCustomRuleIds.has(ruleInfo._id)
Expand All @@ -129,16 +114,6 @@ export const DetectorRulesView: React.FC<DetectorRulesViewProps> = (props) => {
});
}, [services, props.detector]);

const rules = (
<EuiInMemoryTable
columns={getRulesColumns(false)}
items={enabledRuleItems}
itemId={(item: RuleItem) => `${item.name}`}
pagination
loading={loading}
/>
);

const getDetectionRulesTitle = () => `View detection rules (${totalSelected})`;

const onShowRuleDetails = (rule: RuleTableItem) => {
Expand Down Expand Up @@ -172,11 +147,14 @@ export const DetectorRulesView: React.FC<DetectorRulesViewProps> = (props) => {
ruleItems={enabledRuleItems.map((i) => mapRuleItemToRuleTableItem(i))}
showRuleDetails={onShowRuleDetails}
/>
{rules}
</EuiAccordion>
) : (
<ContentPanel title={`Active rules (${totalSelected})`} actions={actions}>
{rules}
<RulesTable
loading={loading}
ruleItems={enabledRuleItems.map((i) => mapRuleItemToRuleTableItem(i))}
showRuleDetails={onShowRuleDetails}
/>
</ContentPanel>
)}
</>
Expand Down
117 changes: 40 additions & 77 deletions public/pages/Detectors/components/UpdateRules/UpdateRules.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@
import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle } from '@elastic/eui';
import {
DetectorHit,
GetRulesResponse,
SearchDetectorsResponse,
UpdateDetectorResponse,
} from '../../../../../server/models/interfaces';
import React, { useCallback, useContext, useEffect, useState } from 'react';
import React, { useCallback, useContext, useEffect, useState, useMemo } from 'react';
import { RouteComponentProps } from 'react-router-dom';
import { RuleItem } from '../../../CreateDetector/components/DefineDetector/components/DetectionRules/types/interfaces';
import { Detector } from '../../../../../models/interfaces';
Expand All @@ -22,6 +21,7 @@ import { NotificationsStart } from 'opensearch-dashboards/public';
import { errorNotificationToast, successNotificationToast } from '../../../../utils/helpers';
import { RuleTableItem } from '../../../Rules/utils/helpers';
import { RuleViewerFlyout } from '../../../Rules/components/RuleViewerFlyout/RuleViewerFlyout';
import { RulesViewModelActor } from '../../../Rules/models/RulesViewModelActor';

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

const rulesViewModelActor = useMemo(
() => (services ? new RulesViewModelActor(services.ruleService) : null),
[services]
);

useEffect(() => {
const getDetector = async () => {
setLoading(true);
Expand All @@ -62,83 +67,41 @@ export const UpdateDetectorRules: React.FC<UpdateDetectorRulesProps> = (props) =
};

const getRules = async (detector: Detector) => {
const prePackagedResponse = (await services?.ruleService.getRules(true, {
from: 0,
size: 5000,
query: {
nested: {
path: 'rule',
query: {
bool: {
must: [{ match: { 'rule.category': `${detector.detector_type.toLowerCase()}` } }],
},
},
},
},
})) as ServerResponse<GetRulesResponse>;
if (prePackagedResponse.ok) {
const ruleInfos = prePackagedResponse.response.hits.hits;
const enabledRuleIds = detector.inputs[0].detector_input.pre_packaged_rules.map(
(rule) => rule.id
);
const ruleItems = ruleInfos.map((rule) => ({
name: rule._source.title,
id: rule._id,
severity: rule._source.level,
logType: rule._source.category,
library: 'Sigma',
description: rule._source.description,
active: enabledRuleIds.includes(rule._id),
ruleInfo: rule,
}));
setPrePackagedRuleItems(ruleItems);
} else {
errorNotificationToast(
props.notifications,
'retrieve',
'pre-packaged rules',
prePackagedResponse.error
);
}
const enabledRuleIds = detector.inputs[0].detector_input.pre_packaged_rules.map(
(rule) => rule.id
);

const customResponse = (await services?.ruleService.getRules(false, {
from: 0,
size: 5000,
query: {
nested: {
path: 'rule',
query: {
bool: {
must: [{ match: { 'rule.category': `${detector.detector_type.toLowerCase()}` } }],
},
},
},
const allRules = await rulesViewModelActor?.fetchRules(undefined, {
bool: {
must: [{ match: { 'rule.category': `${detector.detector_type.toLowerCase()}` } }],
},
})) as ServerResponse<GetRulesResponse>;
if (customResponse.ok) {
const ruleInfos = customResponse.response.hits.hits;
const enabledRuleIds = detector.inputs[0].detector_input.custom_rules.map(
(rule) => rule.id
);
const ruleItems = ruleInfos.map((rule) => ({
name: rule._source.title,
id: rule._id,
severity: rule._source.level,
logType: rule._source.category,
library: 'Custom',
description: rule._source.description,
active: enabledRuleIds.includes(rule._id),
ruleInfo: rule,
}));
setCustomRuleItems(ruleItems);
} else {
errorNotificationToast(
props.notifications,
'retrieve',
'custom rules',
customResponse.error
);
}
});

const prePackagedRules = allRules?.filter((rule) => rule.prePackaged);
const prePackagedRuleItems = prePackagedRules?.map((rule) => ({
name: rule._source.title,
id: rule._id,
severity: rule._source.level,
logType: rule._source.category,
library: 'Sigma',
description: rule._source.description,
active: enabledRuleIds.includes(rule._id),
ruleInfo: rule,
}));
setPrePackagedRuleItems(prePackagedRuleItems || []);

const customRules = allRules?.filter((rule) => !rule.prePackaged);
const customRuleItems = customRules?.map((rule) => ({
name: rule._source.title,
id: rule._id,
severity: rule._source.level,
logType: rule._source.category,
library: 'Custom',
description: rule._source.description,
active: enabledRuleIds.includes(rule._id),
ruleInfo: rule,
}));
setCustomRuleItems(customRuleItems || []);
};

const execute = async () => {
Expand Down
Loading

0 comments on commit 3196a87

Please sign in to comment.