Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Security Solution] Alerts Treemap and Multi-Dimensional Alert Grouping #126896

Merged
merged 8 commits into from
Jul 13, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@ import {
waitForAlerts,
markAcknowledgedFirstAlert,
goToAcknowledgedAlerts,
clearGroupByTopInput,
closeAlerts,
closeFirstAlert,
goToClosedAlerts,
goToOpenedAlerts,
openAlerts,
openFirstAlert,
selectCountTable,
} from '../../tasks/alerts';
import { createCustomRuleEnabled } from '../../tasks/api_calls/rules';
import { cleanKibana, deleteAlertsAndRules } from '../../tasks/common';
Expand Down Expand Up @@ -53,6 +55,8 @@ describe('Changing alert status', () => {
cy.get(SELECTED_ALERTS).should('have.text', `Selected 3 alerts`);
closeAlerts();
waitForAlerts();
selectCountTable();
clearGroupByTopInput();
});

it('Open one alert when more than one closed alerts are selected', () => {
Expand Down Expand Up @@ -110,6 +114,8 @@ describe('Changing alert status', () => {
createCustomRuleEnabled(getNewRule());
visit(ALERTS_URL);
waitForAlertsToPopulate();
selectCountTable();
clearGroupByTopInput();
});
it('Mark one alert as acknowledged when more than one open alerts are selected', () => {
cy.get(ALERTS_COUNT)
Expand Down Expand Up @@ -148,6 +154,8 @@ describe('Changing alert status', () => {
createCustomRuleEnabled(getNewRule(), '1', '100m', 100);
visit(ALERTS_URL);
waitForAlertsToPopulate();
selectCountTable();
clearGroupByTopInput();
});
it('Closes and opens alerts', () => {
const numberOfAlertsToBeClosed = 3;
Expand Down Expand Up @@ -298,6 +306,8 @@ describe('Changing alert status', () => {
createCustomRuleEnabled(getNewRule());
visit(ALERTS_URL);
waitForAlertsToPopulate();
selectCountTable();
clearGroupByTopInput();
});
it('Mark one alert as acknowledged when more than one open alerts are selected', () => {
cy.get(ALERTS_COUNT)
Expand Down
6 changes: 6 additions & 0 deletions x-pack/plugins/security_solution/cypress/screens/alerts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ export const ALERTS_COUNT =
export const ALERTS_TREND_SIGNAL_RULE_NAME_PANEL =
'[data-test-subj="render-content-kibana.alert.rule.name"]';

export const CHART_SELECT = '[data-test-subj="chartSelect"]';

export const CLOSE_ALERT_BTN = '[data-test-subj="close-alert-status"]';

export const CLOSE_SELECTED_ALERTS_BTN = '[data-test-subj="close-alert-status"]';
Expand All @@ -45,6 +47,8 @@ export const EMPTY_ALERT_TABLE = '[data-test-subj="tGridEmptyState"]';

export const EXPAND_ALERT_BTN = '[data-test-subj="expand-event"]';

export const GROUP_BY_TOP_INPUT = '[data-test-subj="groupByTop"] [data-test-subj="comboBoxInput"]';

export const HOST_NAME = '[data-test-subj^=formatted-field][data-test-subj$=host\\.name]';

export const ACKNOWLEDGED_ALERTS_FILTER_BTN = '[data-test-subj="acknowledgedAlerts"]';
Expand Down Expand Up @@ -74,6 +78,8 @@ export const RULE_NAME = '[data-test-subj^=formatted-field][data-test-subj$=rule

export const SELECTED_ALERTS = '[data-test-subj="selectedShowBulkActionsButton"]';

export const SELECT_TABLE = '[data-test-subj="table"]';

export const SEND_ALERT_TO_TIMELINE_BTN = '[data-test-subj="send-alert-to-timeline-button"]';

export const SEVERITY = '[data-test-subj^=formatted-field][data-test-subj$=severity]';
Expand Down
14 changes: 13 additions & 1 deletion x-pack/plugins/security_solution/cypress/tasks/alerts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,20 @@ import {
ADD_EXCEPTION_BTN,
ALERT_RISK_SCORE_HEADER,
ALERT_CHECKBOX,
CHART_SELECT,
CLOSE_ALERT_BTN,
CLOSE_SELECTED_ALERTS_BTN,
CLOSED_ALERTS_FILTER_BTN,
EXPAND_ALERT_BTN,
GROUP_BY_TOP_INPUT,
ACKNOWLEDGED_ALERTS_FILTER_BTN,
LOADING_ALERTS_PANEL,
MANAGE_ALERT_DETECTION_RULES_BTN,
MARK_ALERT_ACKNOWLEDGED_BTN,
OPEN_ALERT_BTN,
OPENED_ALERTS_FILTER_BTN,
SEND_ALERT_TO_TIMELINE_BTN,
SELECT_TABLE,
TAKE_ACTION_POPOVER_BTN,
TIMELINE_CONTEXT_MENU_BTN,
} from '../screens/alerts';
Expand Down Expand Up @@ -65,7 +68,6 @@ export const closeAlerts = () => {

export const expandFirstAlertActions = () => {
cy.get(TIMELINE_CONTEXT_MENU_BTN).should('be.visible');
cy.get(TIMELINE_CONTEXT_MENU_BTN).find('svg').should('have.attr', 'data-is-loaded');
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The should allow a user with crud privileges to attach alerts to cases test in attach_alert_to_case.spec.ts invokes the expandFirstAlertActions() test helper.

The test is failing on CI with the following error:

AssertionError: Timed out retrying after 120000ms: expected '<svg.euiIcon.euiButtonIcon__icon.css-e8xmlg-euiIcon-m-inherit>' to have attribute 'data-is-loaded'
    at expandFirstAlertActions (http://localhost:5620/__cypress/tests?p=cypress/integration/cases/attach_alert_to_case.spec.ts:22991:60)
    at Context.eval (http://localhost:5620/__cypress/tests?p=cypress/integration/cases/attach_alert_to_case.spec.ts:21750:50)

The expandFirstAlertActions() test helper in cypress/tasks/alerts.ts was updated ~ 21 hours ago with this change: https://github.com/elastic/kibana/pull/135373/files#diff-9881e669a4305a15a96f3880d5d68906edfc15991e384a71d7ef5498ed04c0c8R68hh

Based on desk testing, the data-is-loaded attribute:

Based on the git history, it looks like the expandFirstAlertActions() test helper didn't include this check:

cy.get(TIMELINE_CONTEXT_MENU_BTN).find('svg').should('have.attr', 'data-is-loaded');

in it's original implementation. The check was first added in this PR: 683463e#diff-9881e669a4305a15a96f3880d5d68906edfc15991e384a71d7ef5498ed04c0c8R68

Since this check only determines whether or not the SVG was loaded in the button icon, and data-is-loaded does not appear to be reilable, I'm removing this cy.get.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good 👍

I was the one that actually added that check, so for context, this is the comment that details the change: #130072 (comment)

When I ran into this I had no issue with it passing locally, but it would fail on CI, so you may have to click-loop and/or wait for the menu if there's still an issue waiting on the table to load before the icon is visible to click.

cy.get(TIMELINE_CONTEXT_MENU_BTN).first().click({ force: true });
};

Expand Down Expand Up @@ -125,6 +127,16 @@ export const openAlerts = () => {
cy.get(OPEN_ALERT_BTN).click();
};

export const selectCountTable = () => {
cy.get(CHART_SELECT).click({ force: true });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q: What does { force: true } do and why do we need it in this context?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The force parameter to Cypress actions bypasses some of the checks that might otherwise prevent Cypress from clicking on an element.

In main, the Cypress tests in this file, alerts.ts:

  • Utilize click({ force: true }) a total of 11 times throughout the file
  • Also utilize click() (without force) a total of 10 times throughout the file

So it's ~ a 50 / 50 split in the existing code in alerts.ts, but many functions in that file also mix the use { force: true } for the first invocation of click in a function, but NOT for the second click:

Example 1:

export const addExceptionFromFirstAlert = () => {
  cy.get(TIMELINE_CONTEXT_MENU_BTN).first().click({ force: true });
  cy.get(ADD_EXCEPTION_BTN).click();
};

Example 2:

export const openFirstAlert = () => {
  cy.get(TIMELINE_CONTEXT_MENU_BTN).first().click({ force: true });
  cy.get(OPEN_ALERT_BTN).click();
};

Example 3:

export const openAlerts = () => {
  cy.get(TAKE_ACTION_POPOVER_BTN).click({ force: true });
  cy.get(OPEN_ALERT_BTN).click();
};

With the aim of reducing flakey test runs, the new functions follow the established convention above and use { force: true } for the first click in a function.

cy.get(SELECT_TABLE).click();
};

export const clearGroupByTopInput = () => {
cy.get(GROUP_BY_TOP_INPUT).focus();
cy.get(GROUP_BY_TOP_INPUT).type('{backspace}');
};

export const goToAcknowledgedAlerts = () => {
cy.get(ACKNOWLEDGED_ALERTS_FILTER_BTN).click();
cy.get(REFRESH_BUTTON).should('not.have.attr', 'aria-label', 'Needs updating');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,9 @@ export const goToExceptionsTab = () => {
};

export const editException = () => {
cy.get(EXCEPTION_ITEM_ACTIONS_BUTTON).click();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Displays version conflict error and Displays missing exception list error tests in exceptions_flyout.spec.ts invoke the editException() test helper.

This editException() helper function was added ~ 4 days ago, via this PR and is failing on CI with the following error:

CypressError: Timed out retrying after 120050ms: `cy.click()` failed because the center of this element is hidden from view:

`<button class="euiButtonIcon euiButtonIcon--primary euiButtonIcon--empty euiButtonIcon--xSmall" type="button" aria-label="Exception item actions menu" data-test-subj="exceptionItemCardHeader-actionButton">...</button>`

Fix this problem, or use `{force: true}` to disable error checking.

The test passes locally. I'm implementing the fix suggested in the error message above.

cy.get(EXCEPTION_ITEM_ACTIONS_BUTTON).click({ force: true });

cy.get(EDIT_EXCEPTION_BTN).click();
cy.get(EDIT_EXCEPTION_BTN).click({ force: true });
};

export const removeException = () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { render, screen } from '@testing-library/react';
import React from 'react';

import { TestProviders } from '../../mock';
import {
mockAlertSearchResponse,
mockNoDataAlertSearchResponse,
} from './lib/mocks/mock_alert_search_response';
import * as i18n from './translations';
import type { Props } from '.';
import { AlertsTreemap } from '.';

const defaultProps: Props = {
data: mockAlertSearchResponse,
maxBuckets: 1000,
minChartHeight: 370,
stackByField0: 'kibana.alert.rule.name',
stackByField1: 'host.name',
};

describe('AlertsTreemap', () => {
describe('when the response has data', () => {
beforeEach(() => {
render(
<TestProviders>
<AlertsTreemap {...defaultProps} />
</TestProviders>
);
});

test('it renders the treemap', () => {
expect(screen.getByTestId('treemap').querySelector('.echChart')).toBeInTheDocument();
});

test('it renders the legend', () => {
expect(screen.getByTestId('draggable-legend')).toBeInTheDocument();
});
});

describe('when the response does NOT have data', () => {
beforeEach(() => {
render(
<TestProviders>
<AlertsTreemap {...defaultProps} data={mockNoDataAlertSearchResponse} />
</TestProviders>
);
});

test('it does NOT render the treemap', () => {
expect(screen.queryByTestId('treemap')).not.toBeInTheDocument();
});

test('it does NOT render the legend', () => {
expect(screen.queryByTestId('draggable-legend')).not.toBeInTheDocument();
});

test('it renders the "no data" message', () => {
expect(screen.getByText(i18n.NO_DATA_LABEL)).toBeInTheDocument();
});
});
});
Loading