Skip to content

Commit

Permalink
Merge branch 'main' into chrome-nav/detect-active-nav-route
Browse files Browse the repository at this point in the history
  • Loading branch information
sebelga authored Jun 21, 2023
2 parents 105368e + de3f8fc commit 3a47dcd
Show file tree
Hide file tree
Showing 15 changed files with 197 additions and 60 deletions.
2 changes: 1 addition & 1 deletion docs/api-generated/cases/case-apis-passthru.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -556,7 +556,7 @@ Any modifications made to this file will be overwritten.
<div class="param-desc"><span class="param-type">Query Parameter</span> &mdash; The page number to return. default: 1 </div><div class="param">perPage (optional)</div>
<div class="param-desc"><span class="param-type">Query Parameter</span> &mdash; The number of items to return. default: 20 </div><div class="param">sortOrder (optional)</div>
<div class="param-desc"><span class="param-type">Query Parameter</span> &mdash; The number of items to return. Limited to 100 items. default: 20 </div><div class="param">sortOrder (optional)</div>
<div class="param-desc"><span class="param-type">Query Parameter</span> &mdash; Determines the sort order. default: desc </div>
</div> <!-- field-items -->
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/cases/common/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ export const MAX_DOCS_PER_PAGE = 10000 as const;
export const MAX_BULK_GET_ATTACHMENTS = MAX_DOCS_PER_PAGE;
export const MAX_CONCURRENT_SEARCHES = 10 as const;
export const MAX_BULK_GET_CASES = 1000 as const;
export const MAX_COMMENTS_PER_PAGE = 100 as const;
export const MAX_CATEGORY_FILTER_LENGTH = 100 as const;

/**
Expand Down
10 changes: 9 additions & 1 deletion x-pack/plugins/cases/docs/openapi/bundled.json
Original file line number Diff line number Diff line change
Expand Up @@ -1852,7 +1852,15 @@
"$ref": "#/components/parameters/page_index"
},
{
"$ref": "#/components/parameters/page_size"
"name": "perPage",
"in": "query",
"description": "The number of items to return. Limited to 100 items.",
"required": false,
"schema": {
"type": "integer",
"default": 20,
"maximum": 100
}
},
{
"$ref": "#/components/parameters/sort_order"
Expand Down
9 changes: 8 additions & 1 deletion x-pack/plugins/cases/docs/openapi/bundled.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1156,7 +1156,14 @@ paths:
parameters:
- $ref: '#/components/parameters/case_id'
- $ref: '#/components/parameters/page_index'
- $ref: '#/components/parameters/page_size'
- name: perPage
in: query
description: The number of items to return. Limited to 100 items.
required: false
schema:
type: integer
default: 20
maximum: 100
- $ref: '#/components/parameters/sort_order'
- $ref: '#/components/parameters/space_id'
responses:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,14 @@ get:
parameters:
- $ref: '../components/parameters/case_id.yaml'
- $ref: '../components/parameters/page_index.yaml'
- $ref: '../components/parameters/page_size.yaml'
- name: perPage
in: query
description: The number of items to return. Limited to 100 items.
required: false
schema:
type: integer
default: 20
maximum: 100
- $ref: '../components/parameters/sort_order.yaml'
- $ref: '../components/parameters/space_id.yaml'
responses:
Expand Down
10 changes: 9 additions & 1 deletion x-pack/plugins/cases/server/client/attachments/get.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,20 @@ describe('get', () => {

it('Invalid total items results in error', async () => {
await expect(() =>
findComment({ caseID: 'mock-id', findQueryParams: { page: 2, perPage: 9001 } }, clientArgs)
findComment({ caseID: 'mock-id', findQueryParams: { page: 209, perPage: 100 } }, clientArgs)
).rejects.toThrowErrorMatchingInlineSnapshot(
`"Failed to find comments case id: mock-id: Error: The number of documents is too high. Paginating through more than 10,000 documents is not possible."`
);
});

it('Invalid perPage items results in error', async () => {
await expect(() =>
findComment({ caseID: 'mock-id', findQueryParams: { page: 2, perPage: 9001 } }, clientArgs)
).rejects.toThrowErrorMatchingInlineSnapshot(
`"Failed to find comments case id: mock-id: Error: The provided perPage value was too high. The maximum allowed perPage value is 100."`
);
});

it('throws with excess fields', async () => {
await expect(
findComment(
Expand Down
19 changes: 16 additions & 3 deletions x-pack/plugins/cases/server/client/attachments/validators.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@
*/

import { validateFindCommentsPagination } from './validators';
import { MAX_COMMENTS_PER_PAGE } from '../../../common/constants';

const ERROR_MSG =
'The number of documents is too high. Paginating through more than 10,000 documents is not possible.';

const ERROR_MSG_PER_PAGE = `The provided perPage value was too high. The maximum allowed perPage value is ${MAX_COMMENTS_PER_PAGE}.`;

describe('validators', () => {
describe('validateFindCommentsPagination', () => {
it('does not throw if only page is undefined', () => {
Expand All @@ -20,20 +23,30 @@ describe('validators', () => {
expect(() => validateFindCommentsPagination({ page: 100 })).not.toThrowError();
});

it('does not throw if page and perPage are defined and valid', () => {
expect(() => validateFindCommentsPagination({ page: 2, perPage: 100 })).not.toThrowError();
});

it('returns if page and perPage are undefined', () => {
expect(() => validateFindCommentsPagination({})).not.toThrowError();
});

it('returns if perPage < 0', () => {
expect(() => validateFindCommentsPagination({ perPage: -1 })).not.toThrowError();
});

it('throws if page > 10k', () => {
expect(() => validateFindCommentsPagination({ page: 10001 })).toThrow(ERROR_MSG);
});

it('throws if perPage > 10k', () => {
expect(() => validateFindCommentsPagination({ perPage: 10001 })).toThrowError(ERROR_MSG);
it('throws if perPage > 100', () => {
expect(() =>
validateFindCommentsPagination({ perPage: MAX_COMMENTS_PER_PAGE + 1 })
).toThrowError(ERROR_MSG_PER_PAGE);
});

it('throws if page * perPage > 10k', () => {
expect(() => validateFindCommentsPagination({ page: 10, perPage: 1001 })).toThrow(ERROR_MSG);
expect(() => validateFindCommentsPagination({ page: 101, perPage: 100 })).toThrow(ERROR_MSG);
});
});
});
10 changes: 8 additions & 2 deletions x-pack/plugins/cases/server/client/attachments/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/

import Boom from '@hapi/boom';
import { MAX_DOCS_PER_PAGE } from '../../../common/constants';
import { MAX_DOCS_PER_PAGE, MAX_COMMENTS_PER_PAGE } from '../../../common/constants';
import {
isCommentRequestTypeExternalReference,
isCommentRequestTypePersistableState,
Expand Down Expand Up @@ -51,7 +51,13 @@ export const validateFindCommentsPagination = (params?: FindCommentsQueryParams)
const pageAsNumber = params.page ?? 0;
const perPageAsNumber = params.perPage ?? 0;

if (Math.max(pageAsNumber, perPageAsNumber, pageAsNumber * perPageAsNumber) > MAX_DOCS_PER_PAGE) {
if (perPageAsNumber > MAX_COMMENTS_PER_PAGE) {
throw Boom.badRequest(
`The provided perPage value was too high. The maximum allowed perPage value is ${MAX_COMMENTS_PER_PAGE}.`
);
}

if (Math.max(pageAsNumber, pageAsNumber * perPageAsNumber) > MAX_DOCS_PER_PAGE) {
throw Boom.badRequest(
'The number of documents is too high. Paginating through more than 10,000 documents is not possible.'
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@ import { getNewRule } from '../../../objects/rule';
import {
CONTROL_FRAMES,
CONTROL_FRAME_TITLE,
CONTROL_POPOVER,
FILTER_GROUP_CHANGED_BANNER,
FILTER_GROUP_EDIT_CONTROL_PANEL_ITEMS,
OPTION_IGNORED,
OPTION_LIST_LABELS,
OPTION_LIST_VALUES,
OPTION_SELECTABLE,
OPTION_SELECTABLE_COUNT,
FILTER_GROUP_CONTROL_ACTION_EDIT,
FILTER_GROUP_EDIT_CONTROL_PANEL_ITEMS,
} from '../../../screens/common/filter_group';
import { createRule } from '../../../tasks/api_calls/rules';
import { cleanKibana } from '../../../tasks/common';
Expand All @@ -26,15 +29,17 @@ import { formatPageFilterSearchParam } from '../../../../common/utils/format_pag
import {
closePageFilterPopover,
markAcknowledgedFirstAlert,
openFirstAlert,
openPageFilterPopover,
resetFilters,
selectCountTable,
togglePageFilterPopover,
visitAlertsPageWithCustomFilters,
waitForAlerts,
waitForPageFilters,
} from '../../../tasks/alerts';
import { ALERTS_COUNT } from '../../../screens/alerts';
import { navigateFromHeaderTo } from '../../../tasks/security_header';
import { ALERTS_COUNT, ALERTS_REFRESH_BTN } from '../../../screens/alerts';
import { kqlSearch, navigateFromHeaderTo } from '../../../tasks/security_header';
import { ALERTS, CASES } from '../../../screens/security_header';
import {
addNewFilterGroupControlValues,
Expand All @@ -45,6 +50,9 @@ import {
editFilterGroupControls,
saveFilterGroupControls,
} from '../../../tasks/common/filter_group';
import { TOASTER } from '../../../screens/alerts_detection_rules';
import { setEndDate, setStartDate } from '../../../tasks/date_picker';
import { fillAddFilterForm, openAddFilterPopover } from '../../../tasks/search_bar';

const customFilters = [
{
Expand Down Expand Up @@ -233,13 +241,21 @@ describe('Detections : Page Filters', () => {
markAcknowledgedFirstAlert();
waitForAlerts();
cy.get(OPTION_LIST_VALUES(0)).click();
cy.get(OPTION_SELECTABLE(0, 'acknowledged')).should('be.visible');
cy.get(OPTION_SELECTABLE(0, 'acknowledged')).should('be.visible').trigger('click');
cy.get(ALERTS_COUNT)
.invoke('text')
.should((newAlertCount) => {
expect(newAlertCount.split(' ')[0]).eq(String(parseInt(originalAlertCount, 10) - 1));
});
});

// cleanup
// revert the changes so that data does not change for further tests.
// It would make sure that tests can run in any order.
cy.get(OPTION_SELECTABLE(0, 'open')).trigger('click');
togglePageFilterPopover(0);
openFirstAlert();
waitForAlerts();
});

it(`URL is updated when filters are updated`, () => {
Expand All @@ -256,14 +272,14 @@ describe('Detections : Page Filters', () => {

openPageFilterPopover(1);
cy.get(OPTION_SELECTABLE(1, 'high')).should('be.visible');
cy.get(OPTION_SELECTABLE(1, 'high')).click({ force: true });
cy.get(OPTION_SELECTABLE(1, 'high')).click({});
closePageFilterPopover(1);
});

it(`Filters are restored from localstorage when user navigates back to the page.`, () => {
cy.get(OPTION_LIST_VALUES(1)).click();
cy.get(OPTION_SELECTABLE(1, 'high')).should('be.visible');
cy.get(OPTION_SELECTABLE(1, 'high')).click({ force: true });
cy.get(OPTION_SELECTABLE(1, 'high')).click({});

// high should be scuccessfully selected.
cy.get(OPTION_LIST_VALUES(1)).contains('high');
Expand Down Expand Up @@ -311,6 +327,57 @@ describe('Detections : Page Filters', () => {
cy.get(FILTER_GROUP_CHANGED_BANNER).should('not.exist');
});

context('Impact of inputs', () => {
afterEach(() => {
resetFilters();
});
it('should recover from invalide kql Query result', () => {
// do an invalid search
//
kqlSearch('\\');
cy.get(ALERTS_REFRESH_BTN).trigger('click');
waitForPageFilters();
cy.get(TOASTER).should('contain.text', 'KQLSyntaxError');
togglePageFilterPopover(0);
cy.get(OPTION_SELECTABLE(0, 'open')).should('be.visible');
cy.get(OPTION_SELECTABLE(0, 'open')).should('contain.text', 'open');
cy.get(OPTION_SELECTABLE(0, 'open')).get(OPTION_SELECTABLE_COUNT).should('have.text', 2);
});

it('should take kqlQuery into account', () => {
kqlSearch('kibana.alert.workflow_status: "nothing"');
cy.get(ALERTS_REFRESH_BTN).trigger('click');
waitForPageFilters();
togglePageFilterPopover(0);
cy.get(CONTROL_POPOVER(0)).should('contain.text', 'No options found');
cy.get(OPTION_IGNORED(0, 'open')).should('be.visible');
});

it('should take filters into account', () => {
openAddFilterPopover();
fillAddFilterForm({
key: 'kibana.alert.workflow_status',
value: 'invalid',
});
waitForPageFilters();
togglePageFilterPopover(0);
cy.get(CONTROL_POPOVER(0)).should('contain.text', 'No options found');
cy.get(OPTION_IGNORED(0, 'open')).should('be.visible');
});
it('should take timeRange into account', () => {
const startDateWithZeroAlerts = 'Jan 1, 2002 @ 00:00:00.000';
const endDateWithZeroAlerts = 'Jan 1, 2010 @ 00:00:00.000';

setStartDate(startDateWithZeroAlerts);
setEndDate(endDateWithZeroAlerts);

cy.get(ALERTS_REFRESH_BTN).trigger('click');
waitForPageFilters();
togglePageFilterPopover(0);
cy.get(CONTROL_POPOVER(0)).should('contain.text', 'No options found');
cy.get(OPTION_IGNORED(0, 'open')).should('be.visible');
});
});
it('Number fields are not visible in field edit panel', () => {
const idx = 3;
const { FILTER_FIELD_TYPE, FIELD_TYPES } = FILTER_GROUP_EDIT_CONTROL_PANEL_ITEMS;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ export const OPTION_SELECTABLE = (popoverIndex: number, value: string) =>
export const OPTION_IGNORED = (popoverIndex: number, value: string) =>
`#control-popover-${popoverIndex} [data-test-subj="optionsList-control-ignored-selection-${value}"]`;

export const OPTION_SELECTABLE_COUNT = getDataTestSubjectSelector(
'optionsList-document-count-badge'
);

export const CONTROL_POPOVER = (popoverIdx: number) => `#control-popover-${popoverIdx}`;

export const DETECTION_PAGE_FILTER_GROUP_WRAPPER = '.filter-group__wrapper';

export const DETECTION_PAGE_FILTERS_LOADING = '.securityPageWrapper .controlFrame--controlLoading';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ const mockedDataViewServiceGetter = jest.fn(() => {
} as unknown as DataView);
});

const mockDataViewCreator = jest.fn();

const getKibanaServiceWithMockedGetter = (
mockedDataViewGetter: DataViewsServicePublic['get'] = mockedDataViewServiceGetter
) => {
Expand All @@ -42,6 +44,7 @@ const getKibanaServiceWithMockedGetter = (
...basicKibanaServicesMock.dataViews,
clearInstanceCache: jest.fn(),
get: mockedDataViewGetter,
create: mockDataViewCreator,
},
};
};
Expand All @@ -55,7 +58,6 @@ const TestComponent = (props: Partial<ComponentProps<typeof DetectionPageFilterS
<TestProviders>
<DetectionPageFilterSet
chainingSystem="HIERARCHICAL"
dataViewId=""
onFilterChange={onFilterChangeMock}
{...props}
/>
Expand Down Expand Up @@ -84,29 +86,32 @@ describe('Detection Page Filters', () => {
});
});

it('should check all the fields till any absent field is found', async () => {
it('should create dataview on render', async () => {
render(<TestComponent />);
expect(screen.getByTestId(TEST_IDS.FILTER_LOADING)).toBeInTheDocument();

await waitFor(() => {
expect(getFieldByNameMock).toHaveBeenCalledTimes(4);
expect(kibanaServiceDefaultMock.dataViews.clearInstanceCache).toHaveBeenCalledTimes(0);
expect(mockDataViewCreator).toHaveBeenCalledTimes(1);
expect(mockDataViewCreator).toHaveBeenCalledWith(
expect.objectContaining({
id: 'security_solution_alerts_dv',
name: 'Security Solution Alerts DataView',
allowNoIndex: true,
title: '.siem-signals-spacename',
})
);
});
});

it('should stop checking fields if blank field is found and clear the cache', async () => {
const getFieldByNameLocalMock = jest.fn(() => false);
const mockGetter = jest.fn(() =>
Promise.resolve({ getFieldByName: getFieldByNameLocalMock } as unknown as DataView)
);
const modifiedKibanaServicesMock = getKibanaServiceWithMockedGetter(mockGetter);
(useKibana as jest.Mock).mockReturnValueOnce({ services: modifiedKibanaServicesMock });
it('should clear cache on unmount', async () => {
const { unmount } = render(<TestComponent />);

render(<TestComponent />);
expect(screen.getByTestId(TEST_IDS.FILTER_LOADING)).toBeInTheDocument();
await waitFor(() => {
expect(getFieldByNameLocalMock).toHaveBeenCalledTimes(1);
expect(modifiedKibanaServicesMock.dataViews.clearInstanceCache).toHaveBeenCalledTimes(1);
expect(screen.getByTestId(TEST_IDS.MOCKED_CONTROL)).toBeInTheDocument();
// wait for the document to completely load.
unmount();
});

await waitFor(() => {
expect(kibanaServiceDefaultMock.dataViews.clearInstanceCache).toHaveBeenCalledTimes(1);
});
});

Expand Down
Loading

0 comments on commit 3a47dcd

Please sign in to comment.