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][Detections] Update rules count indicator in the Rules table to show pagination info #138902

Merged
merged 22 commits into from
Aug 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import {
RULES_TABLE,
RULE_SWITCH,
SEVERITY,
SHOWING_RULES_TEXT,
} from '../../screens/alerts_detection_rules';
import {
ABOUT_CONTINUE_BTN,
Expand Down Expand Up @@ -218,18 +217,21 @@ describe('Custom query rules', () => {
const initialNumberOfRules = rules.length;
const expectedNumberOfRulesAfterDeletion = initialNumberOfRules - 1;

cy.get(SHOWING_RULES_TEXT).should('have.text', `Showing ${initialNumberOfRules} rules`);
cy.request({ url: '/api/detection_engine/rules/_find' }).then(({ body }) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: we could introduce a constant for this path.

The constant would be defined here:

/**
* Detection engine routes
*/
export const DETECTION_ENGINE_URL = '/api/detection_engine' as const;
export const DETECTION_ENGINE_RULES_URL = `${DETECTION_ENGINE_URL}/rules` as const;
export const DETECTION_ENGINE_PREPACKAGED_URL =
`${DETECTION_ENGINE_RULES_URL}/prepackaged` as const;
export const DETECTION_ENGINE_PRIVILEGES_URL = `${DETECTION_ENGINE_URL}/privileges` as const;
export const DETECTION_ENGINE_INDEX_URL = `${DETECTION_ENGINE_URL}/index` as const;

We would use it in the tests and in the route handler:

const numberOfRules = body.data.length;
expect(numberOfRules).to.eql(initialNumberOfRules);
});

deleteFirstRule();
waitForRulesTableToBeRefreshed();

cy.get(RULES_TABLE)
.find(RULES_ROW)
.should('have.length', expectedNumberOfRulesAfterDeletion);
cy.get(SHOWING_RULES_TEXT).should(
'have.text',
`Showing ${expectedNumberOfRulesAfterDeletion} rules`
);
cy.request({ url: '/api/detection_engine/rules/_find' }).then(({ body }) => {
const numberOfRules = body.data.length;
expect(numberOfRules).to.eql(expectedNumberOfRulesAfterDeletion);
});
cy.get(CUSTOM_RULES_BTN).should(
'have.text',
`Custom rules (${expectedNumberOfRulesAfterDeletion})`
Expand All @@ -253,10 +255,10 @@ describe('Custom query rules', () => {
cy.get(RULES_TABLE)
.find(RULES_ROW)
.should('have.length', expectedNumberOfRulesAfterDeletion);
cy.get(SHOWING_RULES_TEXT).should(
'have.text',
`Showing ${expectedNumberOfRulesAfterDeletion} rule`
);
cy.request({ url: '/api/detection_engine/rules/_find' }).then(({ body }) => {
const numberOfRules = body.data.length;
expect(numberOfRules).to.eql(expectedNumberOfRulesAfterDeletion);
});
cy.get(CUSTOM_RULES_BTN).should(
'have.text',
`Custom rules (${expectedNumberOfRulesAfterDeletion})`
Expand All @@ -281,10 +283,10 @@ describe('Custom query rules', () => {
cy.get(RULES_TABLE)
.find(RULES_ROW)
.should('have.length', expectedNumberOfRulesAfterDeletion);
cy.get(SHOWING_RULES_TEXT).should(
'have.text',
`Showing ${expectedNumberOfRulesAfterDeletion} rules`
);
cy.request({ url: '/api/detection_engine/rules/_find' }).then(({ body }) => {
const numberOfRules = body.data.length;
expect(numberOfRules).to.eql(expectedNumberOfRulesAfterDeletion);
});
cy.get(CUSTOM_RULES_BTN).should(
'have.text',
`Custom rules (${expectedNumberOfRulesAfterDeletion})`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,18 @@
* 2.0.
*/

import { rawRules } from '../../../server/lib/detection_engine/rules/prepackaged_rules';
import {
COLLAPSED_ACTION_BTN,
ELASTIC_RULES_BTN,
pageSelector,
RELOAD_PREBUILT_RULES_BTN,
RULES_EMPTY_PROMPT,
RULE_SWITCH,
SHOWING_RULES_TEXT,
RULES_MONITORING_TABLE,
SELECT_ALL_RULES_ON_PAGE_CHECKBOX,
RULE_NAME,
} from '../../screens/alerts_detection_rules';

import {
deleteFirstRule,
deleteSelectedRules,
Expand Down Expand Up @@ -59,7 +59,16 @@ describe('Prebuilt rules', () => {

changeRowsPerPageTo(rowsPerPage);

cy.get(SHOWING_RULES_TEXT).should('have.text', `Showing ${expectedNumberOfRules} rules`);
cy.request({ url: '/api/detection_engine/rules/_find' }).then(({ body }) => {
// Assert the total number of loaded rules equals the expected number of in-memory rules
expect(body.total).to.equal(rawRules.length);
banderror marked this conversation as resolved.
Show resolved Hide resolved
// Assert the table was refreshed with the rules returned by the API request
const ruleNames = rawRules.map((rule) => rule.name);
cy.get(RULE_NAME).each(($item) => {
expect($item.text()).to.be.oneOf(ruleNames);
});
Comment on lines +65 to +69
Copy link
Contributor

Choose a reason for hiding this comment

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

Thank you for adding the comments. They are very useful 🙏

});

cy.get(pageSelector(expectedNumberOfPages)).should('exist');
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import { useFormatUrl } from '../../../../../../common/components/link_to';
import { Loader } from '../../../../../../common/components/loader';

import * as i18n from './translations';
import { AllRulesUtilityBar } from '../utility_bar';
import { ExceptionsTableUtilityBar } from './exceptions_table_utility_bar';
import type { AllExceptionListsColumns } from './columns';
import { getAllExceptionListsColumns } from './columns';
import { useAllExceptionLists } from './use_all_exception_lists';
Expand Down Expand Up @@ -378,11 +378,8 @@ export const ExceptionListsTable = React.memo(() => {
<EuiLoadingContent data-test-subj="initialLoadingPanelAllRulesTable" lines={10} />
) : (
<>
<AllRulesUtilityBar
hasBulkActions={false}
canBulkEdit={hasPermissions}
paginationTotal={exceptionListsWithRuleRefs.length ?? 0}
numberSelectedItems={0}
<ExceptionsTableUtilityBar
totalExceptionLists={exceptionListsWithRuleRefs.length}
onRefresh={handleRefresh}
/>
<EuiBasicTable<ExceptionsTableItem>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* 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 React from 'react';
import { TestProviders } from '../../../../../../common/mock';
import { render, screen, within } from '@testing-library/react';
import { ExceptionsTableUtilityBar } from './exceptions_table_utility_bar';

describe('ExceptionsTableUtilityBar', () => {
banderror marked this conversation as resolved.
Show resolved Hide resolved
it('displays correct exception lists label and refresh rules action button', () => {
const EXCEPTION_LISTS_NUMBER = 25;
render(
<TestProviders>
<ExceptionsTableUtilityBar
onRefresh={jest.fn()}
totalExceptionLists={EXCEPTION_LISTS_NUMBER}
/>
</TestProviders>
);

expect(screen.getByTestId('showingExceptionLists')).toBeInTheDocument();
expect(screen.getByTestId('refreshRulesAction')).toBeInTheDocument();
expect(screen.getByText(`Showing ${EXCEPTION_LISTS_NUMBER} lists`)).toBeInTheDocument();
});

it('invokes refresh on refresh action click', () => {
const mockRefresh = jest.fn();
render(
<TestProviders>
<ExceptionsTableUtilityBar onRefresh={mockRefresh} totalExceptionLists={1} />
</TestProviders>
);

const buttonWrapper = screen.getByTestId('refreshRulesAction');
within(buttonWrapper).getByRole('button').click();

expect(mockRefresh).toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* 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 React from 'react';

import {
UtilityBar,
UtilityBarAction,
UtilityBarGroup,
UtilityBarSection,
UtilityBarText,
} from '../../../../../../common/components/utility_bar';
import * as i18n from './translations';

interface ExceptionsTableUtilityBarProps {
onRefresh?: () => void;
totalExceptionLists: number;
}

export const ExceptionsTableUtilityBar: React.FC<ExceptionsTableUtilityBarProps> = ({
onRefresh,
totalExceptionLists,
}) => {
return (
<UtilityBar border>
<UtilityBarSection>
<UtilityBarGroup>
<UtilityBarText dataTestSubj="showingExceptionLists">
{i18n.SHOWING_EXCEPTION_LISTS(totalExceptionLists)}
banderror marked this conversation as resolved.
Show resolved Hide resolved
</UtilityBarText>
</UtilityBarGroup>
<UtilityBarGroup>
<UtilityBarAction
dataTestSubj="refreshRulesAction"
iconSide="left"
iconType="refresh"
onClick={onRefresh}
>
{i18n.REFRESH_EXCEPTIONS_TABLE}
</UtilityBarAction>
</UtilityBarGroup>
</UtilityBarSection>
</UtilityBar>
);
};

ExceptionsTableUtilityBar.displayName = 'ExceptionsTableUtilityBar';
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ export const EXCEPTION_LIST_ACTIONS = i18n.translate(
}
);

export const SHOWING_EXCEPTION_LISTS = (totalLists: number) =>
i18n.translate(
'xpack.securitySolution.detectionEngine.rules.all.exceptions.showingExceptionLists',
{
values: { totalLists },
defaultMessage: 'Showing {totalLists} {totalLists, plural, =1 {list} other {lists}}',
}
);

export const RULES_ASSIGNED_TO_TITLE = i18n.translate(
'xpack.securitySolution.detectionEngine.rules.all.exceptions.rulesAssignedTitle',
{
Expand Down Expand Up @@ -151,3 +160,10 @@ export const EXCEPTION_LIST_SEARCH_PLACEHOLDER = i18n.translate(
defaultMessage: 'e.g. Example List Name',
}
);

export const REFRESH_EXCEPTIONS_TABLE = i18n.translate(
'xpack.securitySolution.detectionEngine.rules.all.exceptions.refresh',
{
defaultMessage: 'Refresh',
}
);
Loading