diff --git a/cypress/integration/2_rules.spec.js b/cypress/integration/2_rules.spec.js index 21f5f271f..57f808a58 100644 --- a/cypress/integration/2_rules.spec.js +++ b/cypress/integration/2_rules.spec.js @@ -216,7 +216,7 @@ describe('Rules', () => { ); // Close the flyout - cy.get('[data-test-subj="euiFlyoutCloseButton"]', TWENTY_SECONDS_TIMEOUT).click({ + cy.get('[data-test-subj="close-rule-details-flyout"]', TWENTY_SECONDS_TIMEOUT).click({ force: true, }); }); diff --git a/cypress/integration/4_findings.spec.js b/cypress/integration/4_findings.spec.js index a3aad7d23..b5d897d18 100644 --- a/cypress/integration/4_findings.spec.js +++ b/cypress/integration/4_findings.spec.js @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { PLUGIN_NAME } from '../support/constants'; +import { PLUGIN_NAME, TWENTY_SECONDS_TIMEOUT } from '../support/constants'; import sample_document from '../fixtures/sample_document.json'; import sample_index_settings from '../fixtures/sample_index_settings.json'; import sample_field_mappings from '../fixtures/sample_field_mappings.json'; @@ -106,14 +106,21 @@ describe('Findings', () => { // TODO - upon reaching rules page, trigger appropriate rules detail flyout // see github issue #124 at https://github.com/opensearch-project/security-analytics-dashboards-plugin/issues/124 - it('takes user to rules page when rule name inside accordion drop down is clicked', () => { + it('opens rule details flyout when rule name inside accordion drop down is clicked', () => { // Click rule link - cy.get(`[data-test-subj="finding-details-flyout-USB Device Plugged-details"]`) - .invoke('removeAttr', 'target') - .click({ force: true }); + cy.get(`[data-test-subj="finding-details-flyout-USB Device Plugged-details"]`).click({ + force: true, + }); - // Confirm destination reached - cy.url().should('include', 'opensearch_security_analytics_dashboards#/rules'); + // Validate flyout appearance + cy.get('[data-test-subj="rule_flyout_USB Device Plugged"]', TWENTY_SECONDS_TIMEOUT).within( + () => { + cy.get('[data-test-subj="rule_flyout_rule_name"]', TWENTY_SECONDS_TIMEOUT).contains( + 'USB Device Plugged', + TWENTY_SECONDS_TIMEOUT + ); + } + ); }); after(() => { diff --git a/public/pages/Findings/components/FindingDetailsFlyout.tsx b/public/pages/Findings/components/FindingDetailsFlyout.tsx index 0a89d06ec..8f140b519 100644 --- a/public/pages/Findings/components/FindingDetailsFlyout.tsx +++ b/public/pages/Findings/components/FindingDetailsFlyout.tsx @@ -25,16 +25,21 @@ import { import { capitalizeFirstLetter, renderTime } from '../../../utils/helpers'; import { DEFAULT_EMPTY_DATA, ROUTES } from '../../../utils/constants'; import { Finding, Query } from '../models/interfaces'; +import { RuleViewerFlyout } from '../../Rules/components/RuleViewerFlyout/RuleViewerFlyout'; +import { RuleTableItem } from '../../Rules/utils/helpers'; +import { RuleSource } from '../../../../server/models/interfaces'; +import { RuleItemInfoBase } from '../../Rules/models/types'; interface FindingDetailsFlyoutProps { finding: Finding; closeFlyout: () => void; backButton?: React.ReactNode; - allRules: object; + allRules: { [id: string]: RuleSource }; } interface FindingDetailsFlyoutState { loading: boolean; + ruleViewerFlyoutData: RuleTableItem | null; } export default class FindingDetailsFlyout extends Component< @@ -45,6 +50,7 @@ export default class FindingDetailsFlyout extends Component< super(props); this.state = { loading: false, + ruleViewerFlyoutData: null, }; } @@ -62,6 +68,27 @@ export default class FindingDetailsFlyout extends Component< ); }; + showRuleDetails = (fullRule, ruleId: string) => { + this.setState({ + ...this.state, + ruleViewerFlyoutData: { + ruleId: ruleId, + title: fullRule.title, + level: fullRule.level, + category: fullRule.category, + description: fullRule.description, + source: fullRule.source, + ruleInfo: { + _source: fullRule, + } as RuleItemInfoBase, + }, + }); + }; + + hideRuleDetails = () => { + this.setState({ ...this.state, ruleViewerFlyoutData: null }); + }; + renderRuleDetails = (rules: Query[] = []) => { const { allRules, @@ -94,8 +121,7 @@ export default class FindingDetailsFlyout extends Component< {/*//TODO: Refactor EuiLink to filter rules table to the specific rule.*/} this.showRuleDetails(fullRule, rule.id)} data-test-subj={`finding-details-flyout-${fullRule.title}-details`} > {fullRule.title || DEFAULT_EMPTY_DATA} @@ -208,6 +234,13 @@ export default class FindingDetailsFlyout extends Component< hideCloseButton data-test-subj={'finding-details-flyout'} > + {this.state.ruleViewerFlyoutData && ( + + )} + @@ -222,6 +255,7 @@ export default class FindingDetailsFlyout extends Component< void; } @@ -32,6 +35,7 @@ export const RuleViewerFlyout: React.FC = ({ hideFlyout, ruleTableItem, ruleService, + notifications, }) => { const [actionsPopoverOpen, setActionsPopoverOpen] = useState(false); const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); @@ -42,14 +46,14 @@ export const RuleViewerFlyout: React.FC = ({ setActionsPopoverOpen(false); }; const duplicateRule = () => { - history.push({ + history?.push({ pathname: ROUTES.RULES_DUPLICATE, state: { ruleItem: ruleTableItem.ruleInfo }, }); }; const editRule = () => { - history.push({ + history?.push({ pathname: ROUTES.RULES_EDIT, state: { ruleItem: ruleTableItem.ruleInfo }, }); @@ -74,14 +78,17 @@ export const RuleViewerFlyout: React.FC = ({ }; const onDeleteRuleConfirmed = async () => { + if (!ruleService) { + return; + } const deleteRuleRes = await ruleService.deleteRule(ruleTableItem.ruleId); - if (!deleteRuleRes.ok) { - // TODO: show error + if (deleteRuleRes.ok) { + closeDeleteModal(); + hideFlyout(true); + } else { + errorNotificationToast(notifications, 'delete', 'rule', deleteRuleRes.error); } - - closeDeleteModal(); - hideFlyout(true); }; const deleteModal = useMemo( @@ -97,17 +104,35 @@ export const RuleViewerFlyout: React.FC = ({ ); return ( - + {isDeleteModalVisible && deleteModal ? deleteModal : null} - - + +

{ruleTableItem.title}

- - {headerActions} + {ruleService && history && ( + + {headerActions} + + )} + + hideFlyout()} + data-test-subj={`close-rule-details-flyout`} + />
diff --git a/public/pages/Rules/containers/Rules/Rules.tsx b/public/pages/Rules/containers/Rules/Rules.tsx index 8b77c6db1..843a530e2 100644 --- a/public/pages/Rules/containers/Rules/Rules.tsx +++ b/public/pages/Rules/containers/Rules/Rules.tsx @@ -93,6 +93,7 @@ export const Rules: React.FC = (props) => { history={props.history} ruleTableItem={flyoutData} ruleService={services.ruleService} + notifications={props.notifications} /> ) : null} diff --git a/public/utils/helpers.tsx b/public/utils/helpers.tsx index 7e7b6b50f..7b754a8f6 100644 --- a/public/utils/helpers.tsx +++ b/public/utils/helpers.tsx @@ -65,10 +65,10 @@ export function createTextDetailsGroup( ) : ( <> - {data.map(({ label, content, url }) => { + {data.map(({ label, content, url }, index) => { return (