Skip to content

Commit 11e47b3

Browse files
authored
Rule flyout opening from Findings and Alerts page (opensearch-project#219)
* rule flyout opening from findings and alerts page Signed-off-by: Aleksandar Djindjic <[email protected]> * update cypress test for findings page Signed-off-by: Aleksandar Djindjic <[email protected]> * make code shorter Signed-off-by: Aleksandar Djindjic <[email protected]> * toast error notifications for rule deletion Signed-off-by: Aleksandar Djindjic <[email protected]> * cleanup component state Signed-off-by: Aleksandar Djindjic <[email protected]> * avoid as any in favor of RuleItemInfoBase Signed-off-by: Aleksandar Djindjic <[email protected]> * fix cypress test for rules Signed-off-by: Aleksandar Djindjic <[email protected]> Signed-off-by: Aleksandar Djindjic <[email protected]>
1 parent abfa8b8 commit 11e47b3

File tree

6 files changed

+95
-28
lines changed

6 files changed

+95
-28
lines changed

cypress/integration/2_rules.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ describe('Rules', () => {
229229
);
230230

231231
// Close the flyout
232-
cy.get('[data-test-subj="euiFlyoutCloseButton"]', TWENTY_SECONDS_TIMEOUT).click({
232+
cy.get('[data-test-subj="close-rule-details-flyout"]', TWENTY_SECONDS_TIMEOUT).click({
233233
force: true,
234234
});
235235
});

cypress/integration/4_findings.spec.js

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
import { PLUGIN_NAME } from '../support/constants';
6+
import { PLUGIN_NAME, TWENTY_SECONDS_TIMEOUT } from '../support/constants';
77
import sample_document from '../fixtures/sample_document.json';
88
import sample_index_settings from '../fixtures/sample_index_settings.json';
99
import sample_field_mappings from '../fixtures/sample_field_mappings.json';
@@ -106,14 +106,21 @@ describe('Findings', () => {
106106
// TODO - upon reaching rules page, trigger appropriate rules detail flyout
107107
// see github issue #124 at https://github.com/opensearch-project/security-analytics-dashboards-plugin/issues/124
108108

109-
it('takes user to rules page when rule name inside accordion drop down is clicked', () => {
109+
it('opens rule details flyout when rule name inside accordion drop down is clicked', () => {
110110
// Click rule link
111-
cy.get(`[data-test-subj="finding-details-flyout-USB Device Plugged-details"]`)
112-
.invoke('removeAttr', 'target')
113-
.click({ force: true });
111+
cy.get(`[data-test-subj="finding-details-flyout-USB Device Plugged-details"]`).click({
112+
force: true,
113+
});
114114

115-
// Confirm destination reached
116-
cy.url().should('include', 'opensearch_security_analytics_dashboards#/rules');
115+
// Validate flyout appearance
116+
cy.get('[data-test-subj="rule_flyout_USB Device Plugged"]', TWENTY_SECONDS_TIMEOUT).within(
117+
() => {
118+
cy.get('[data-test-subj="rule_flyout_rule_name"]', TWENTY_SECONDS_TIMEOUT).contains(
119+
'USB Device Plugged',
120+
TWENTY_SECONDS_TIMEOUT
121+
);
122+
}
123+
);
117124
});
118125

119126
after(() => {

public/pages/Findings/components/FindingDetailsFlyout.tsx

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,21 @@ import {
2525
import { capitalizeFirstLetter, renderTime } from '../../../utils/helpers';
2626
import { DEFAULT_EMPTY_DATA, ROUTES } from '../../../utils/constants';
2727
import { Finding, Query } from '../models/interfaces';
28+
import { RuleViewerFlyout } from '../../Rules/components/RuleViewerFlyout/RuleViewerFlyout';
29+
import { RuleTableItem } from '../../Rules/utils/helpers';
30+
import { RuleSource } from '../../../../server/models/interfaces';
31+
import { RuleItemInfoBase } from '../../Rules/models/types';
2832

2933
interface FindingDetailsFlyoutProps {
3034
finding: Finding;
3135
closeFlyout: () => void;
3236
backButton?: React.ReactNode;
33-
allRules: object;
37+
allRules: { [id: string]: RuleSource };
3438
}
3539

3640
interface FindingDetailsFlyoutState {
3741
loading: boolean;
42+
ruleViewerFlyoutData: RuleTableItem | null;
3843
}
3944

4045
export default class FindingDetailsFlyout extends Component<
@@ -45,6 +50,7 @@ export default class FindingDetailsFlyout extends Component<
4550
super(props);
4651
this.state = {
4752
loading: false,
53+
ruleViewerFlyoutData: null,
4854
};
4955
}
5056

@@ -62,6 +68,27 @@ export default class FindingDetailsFlyout extends Component<
6268
);
6369
};
6470

71+
showRuleDetails = (fullRule, ruleId: string) => {
72+
this.setState({
73+
...this.state,
74+
ruleViewerFlyoutData: {
75+
ruleId: ruleId,
76+
title: fullRule.title,
77+
level: fullRule.level,
78+
category: fullRule.category,
79+
description: fullRule.description,
80+
source: fullRule.source,
81+
ruleInfo: {
82+
_source: fullRule,
83+
} as RuleItemInfoBase,
84+
},
85+
});
86+
};
87+
88+
hideRuleDetails = () => {
89+
this.setState({ ...this.state, ruleViewerFlyoutData: null });
90+
};
91+
6592
renderRuleDetails = (rules: Query[] = []) => {
6693
const {
6794
allRules,
@@ -94,8 +121,7 @@ export default class FindingDetailsFlyout extends Component<
94121
{/*//TODO: Refactor EuiLink to filter rules table to the specific rule.*/}
95122
<EuiFormRow label={'Rule name'}>
96123
<EuiLink
97-
href={`#${ROUTES.RULES}`}
98-
target={'_blank'}
124+
onClick={() => this.showRuleDetails(fullRule, rule.id)}
99125
data-test-subj={`finding-details-flyout-${fullRule.title}-details`}
100126
>
101127
{fullRule.title || DEFAULT_EMPTY_DATA}
@@ -208,6 +234,13 @@ export default class FindingDetailsFlyout extends Component<
208234
hideCloseButton
209235
data-test-subj={'finding-details-flyout'}
210236
>
237+
{this.state.ruleViewerFlyoutData && (
238+
<RuleViewerFlyout
239+
hideFlyout={this.hideRuleDetails}
240+
ruleTableItem={this.state.ruleViewerFlyoutData}
241+
/>
242+
)}
243+
211244
<EuiFlyoutHeader hasBorder={true}>
212245
<EuiFlexGroup justifyContent="flexStart" alignItems="center">
213246
<EuiFlexItem>
@@ -222,6 +255,7 @@ export default class FindingDetailsFlyout extends Component<
222255
</EuiFlexItem>
223256
<EuiFlexItem grow={false}>
224257
<EuiButtonIcon
258+
aria-label="close"
225259
iconType="cross"
226260
display="empty"
227261
iconSize="m"

public/pages/Rules/components/RuleViewerFlyout/RuleViewerFlyout.tsx

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,23 @@ import {
1010
EuiFlyoutBody,
1111
EuiFlyoutHeader,
1212
EuiTitle,
13+
EuiButtonIcon,
1314
} from '@elastic/eui';
15+
import { errorNotificationToast } from '../../../../utils/helpers';
1416
import { ROUTES } from '../../../../utils/constants';
1517
import React, { useMemo, useState } from 'react';
1618
import { RouteComponentProps } from 'react-router-dom';
1719
import { RuleTableItem } from '../../utils/helpers';
1820
import { DeleteRuleModal } from '../DeleteModal/DeleteModal';
1921
import { RuleContentViewer } from '../RuleContentViewer/RuleContentViewer';
2022
import { RuleViewerFlyoutHeaderActions } from './RuleViewFlyoutHeaderActions';
21-
import { RuleService } from '../../../../services';
23+
import { RuleService, NotificationsStart } from '../../../../services';
2224

2325
export interface RuleViewerFlyoutProps {
24-
history: RouteComponentProps['history'];
26+
history?: RouteComponentProps['history'];
2527
ruleTableItem: RuleTableItem;
26-
ruleService: RuleService;
28+
ruleService?: RuleService;
29+
notifications?: NotificationsStart;
2730
hideFlyout: (refreshRules?: boolean) => void;
2831
}
2932

@@ -32,6 +35,7 @@ export const RuleViewerFlyout: React.FC<RuleViewerFlyoutProps> = ({
3235
hideFlyout,
3336
ruleTableItem,
3437
ruleService,
38+
notifications,
3539
}) => {
3640
const [actionsPopoverOpen, setActionsPopoverOpen] = useState(false);
3741
const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false);
@@ -42,14 +46,14 @@ export const RuleViewerFlyout: React.FC<RuleViewerFlyoutProps> = ({
4246
setActionsPopoverOpen(false);
4347
};
4448
const duplicateRule = () => {
45-
history.push({
49+
history?.push({
4650
pathname: ROUTES.RULES_DUPLICATE,
4751
state: { ruleItem: ruleTableItem.ruleInfo },
4852
});
4953
};
5054

5155
const editRule = () => {
52-
history.push({
56+
history?.push({
5357
pathname: ROUTES.RULES_EDIT,
5458
state: { ruleItem: ruleTableItem.ruleInfo },
5559
});
@@ -74,14 +78,17 @@ export const RuleViewerFlyout: React.FC<RuleViewerFlyoutProps> = ({
7478
};
7579

7680
const onDeleteRuleConfirmed = async () => {
81+
if (!ruleService) {
82+
return;
83+
}
7784
const deleteRuleRes = await ruleService.deleteRule(ruleTableItem.ruleId);
7885

79-
if (!deleteRuleRes.ok) {
80-
// TODO: show error
86+
if (deleteRuleRes.ok) {
87+
closeDeleteModal();
88+
hideFlyout(true);
89+
} else {
90+
errorNotificationToast(notifications, 'delete', 'rule', deleteRuleRes.error);
8191
}
82-
83-
closeDeleteModal();
84-
hideFlyout(true);
8592
};
8693

8794
const deleteModal = useMemo(
@@ -97,17 +104,35 @@ export const RuleViewerFlyout: React.FC<RuleViewerFlyoutProps> = ({
97104
);
98105

99106
return (
100-
<EuiFlyout onClose={hideFlyout} data-test-subj={`rule_flyout_${ruleTableItem.title}`}>
107+
<EuiFlyout
108+
onClose={hideFlyout}
109+
hideCloseButton
110+
ownFocus={true}
111+
size={'m'}
112+
data-test-subj={`rule_flyout_${ruleTableItem.title}`}
113+
>
101114
{isDeleteModalVisible && deleteModal ? deleteModal : null}
102-
<EuiFlyoutHeader hasBorder>
103-
<EuiFlexGroup>
115+
<EuiFlyoutHeader hasBorder={true}>
116+
<EuiFlexGroup alignItems="center">
104117
<EuiFlexItem>
105118
<EuiTitle size="m">
106119
<h3>{ruleTableItem.title}</h3>
107120
</EuiTitle>
108121
</EuiFlexItem>
109-
<EuiFlexItem grow={false} style={{ marginRight: '50px' }}>
110-
{headerActions}
122+
{ruleService && history && (
123+
<EuiFlexItem grow={false} style={{ marginRight: '50px' }}>
124+
{headerActions}
125+
</EuiFlexItem>
126+
)}
127+
<EuiFlexItem grow={false}>
128+
<EuiButtonIcon
129+
aria-label="close"
130+
iconType="cross"
131+
display="empty"
132+
iconSize="m"
133+
onClick={() => hideFlyout()}
134+
data-test-subj={`close-rule-details-flyout`}
135+
/>
111136
</EuiFlexItem>
112137
</EuiFlexGroup>
113138
</EuiFlyoutHeader>

public/pages/Rules/containers/Rules/Rules.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ export const Rules: React.FC<RulesProps> = (props) => {
9393
history={props.history}
9494
ruleTableItem={flyoutData}
9595
ruleService={services.ruleService}
96+
notifications={props.notifications}
9697
/>
9798
) : null}
9899
<EuiFlexGroup direction="column">

public/utils/helpers.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,10 @@ export function createTextDetailsGroup(
6565
) : (
6666
<>
6767
<EuiFlexGroup>
68-
{data.map(({ label, content, url }) => {
68+
{data.map(({ label, content, url }, index) => {
6969
return (
7070
<EuiFlexItem
71-
key={label}
71+
key={index}
7272
grow={false}
7373
style={{ minWidth: `${100 / (columnNum || data.length)}%` }}
7474
>

0 commit comments

Comments
 (0)