mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Security Solution][Investigations] Fix favorite filter behaviour in timeline search (#122265)
* fix: apply correct search options for favorited timelines * test: add timeline overview page tests Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
5316d08c31
commit
ef2610a8f9
6 changed files with 143 additions and 18 deletions
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* 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 {
|
||||
TIMELINES_OVERVIEW_TABLE,
|
||||
TIMELINES_OVERVIEW_ONLY_FAVORITES,
|
||||
TIMELINES_OVERVIEW_SEARCH,
|
||||
} from '../../screens/timelines';
|
||||
|
||||
import {
|
||||
getTimeline,
|
||||
getFavoritedTimeline,
|
||||
sharedTimelineTitleFragment,
|
||||
} from '../../objects/timeline';
|
||||
|
||||
import { cleanKibana } from '../../tasks/common';
|
||||
|
||||
import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login';
|
||||
import { createTimeline, favoriteTimeline } from '../../tasks/api_calls/timelines';
|
||||
|
||||
import { TIMELINES_URL } from '../../urls/navigation';
|
||||
|
||||
describe('timeline overview search', () => {
|
||||
before(() => {
|
||||
cleanKibana();
|
||||
|
||||
createTimeline(getFavoritedTimeline())
|
||||
.then((response) => response.body.data.persistTimeline.timeline.savedObjectId)
|
||||
.then((timelineId) => favoriteTimeline({ timelineId, timelineType: 'default' }));
|
||||
createTimeline(getTimeline());
|
||||
|
||||
loginAndWaitForPageWithoutDateRange(TIMELINES_URL);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.get(TIMELINES_OVERVIEW_SEARCH).clear();
|
||||
});
|
||||
|
||||
it('should show all timelines when no search term was entered', () => {
|
||||
cy.get(TIMELINES_OVERVIEW_TABLE).contains(getTimeline().title);
|
||||
cy.get(TIMELINES_OVERVIEW_TABLE).contains(getFavoritedTimeline().title);
|
||||
});
|
||||
|
||||
it('should show the correct favorite count without search', () => {
|
||||
cy.get(TIMELINES_OVERVIEW_ONLY_FAVORITES).contains(1);
|
||||
});
|
||||
|
||||
it('should show the correct timelines when the favorite filter is activated', () => {
|
||||
cy.get(TIMELINES_OVERVIEW_ONLY_FAVORITES).click(); // enable the filter
|
||||
|
||||
cy.get(TIMELINES_OVERVIEW_TABLE).contains(getTimeline().title).should('not.exist');
|
||||
cy.get(TIMELINES_OVERVIEW_TABLE).contains(getFavoritedTimeline().title);
|
||||
cy.get(TIMELINES_OVERVIEW_ONLY_FAVORITES).contains(1);
|
||||
|
||||
cy.get(TIMELINES_OVERVIEW_ONLY_FAVORITES).click(); // disable the filter
|
||||
});
|
||||
|
||||
it('should find the correct timeline and have the correct favorite count when searching by timeline title', () => {
|
||||
cy.get(TIMELINES_OVERVIEW_SEARCH).type(`"${getTimeline().title}"{enter}`);
|
||||
|
||||
cy.get(TIMELINES_OVERVIEW_TABLE).contains(getFavoritedTimeline().title).should('not.exist');
|
||||
cy.get(TIMELINES_OVERVIEW_TABLE).contains(getTimeline().title);
|
||||
cy.get(TIMELINES_OVERVIEW_ONLY_FAVORITES).contains(0);
|
||||
});
|
||||
|
||||
it('should find the correct timelines when searching for favorited timelines', () => {
|
||||
cy.get(TIMELINES_OVERVIEW_ONLY_FAVORITES).click(); // enable the filter
|
||||
cy.get(TIMELINES_OVERVIEW_SEARCH).type(`"${getFavoritedTimeline().title}"{enter}`);
|
||||
|
||||
cy.get(TIMELINES_OVERVIEW_TABLE).contains(getTimeline().title).should('not.exist');
|
||||
cy.get(TIMELINES_OVERVIEW_TABLE).contains(getFavoritedTimeline().title);
|
||||
cy.get(TIMELINES_OVERVIEW_ONLY_FAVORITES).contains(1);
|
||||
|
||||
cy.get(TIMELINES_OVERVIEW_ONLY_FAVORITES).click(); // disable the filter
|
||||
});
|
||||
|
||||
it('should find the correct timelines when both favorited and non-favorited timelines match', () => {
|
||||
cy.get(TIMELINES_OVERVIEW_SEARCH).type(`"${sharedTimelineTitleFragment}"{enter}`);
|
||||
|
||||
cy.get(TIMELINES_OVERVIEW_TABLE).contains(getTimeline().title);
|
||||
cy.get(TIMELINES_OVERVIEW_TABLE).contains(getFavoritedTimeline().title);
|
||||
cy.get(TIMELINES_OVERVIEW_ONLY_FAVORITES).contains(1);
|
||||
});
|
||||
});
|
|
@ -34,14 +34,24 @@ export const getFilter = (): TimelineFilter => ({
|
|||
value: 'exists',
|
||||
});
|
||||
|
||||
export const sharedTimelineTitleFragment = 'Timeline';
|
||||
|
||||
export const getTimeline = (): CompleteTimeline => ({
|
||||
title: 'Security Timeline',
|
||||
title: `Security ${sharedTimelineTitleFragment}`,
|
||||
description: 'This is the best timeline',
|
||||
query: 'host.name: *',
|
||||
notes: 'Yes, the best timeline',
|
||||
filter: getFilter(),
|
||||
});
|
||||
|
||||
export const getFavoritedTimeline = (): CompleteTimeline => ({
|
||||
title: `Darkest ${sharedTimelineTitleFragment}`,
|
||||
description: 'This is the darkest timeline',
|
||||
query: 'host.name: *',
|
||||
notes: 'Yes, the darkest timeline, you heard me right',
|
||||
filter: getFilter(),
|
||||
});
|
||||
|
||||
export const getIndicatorMatchTimelineTemplate = (): CompleteTimeline => ({
|
||||
...getTimeline(),
|
||||
title: 'Generic Threat Match Timeline',
|
||||
|
|
|
@ -45,3 +45,11 @@ export const TIMELINES_TABLE = '[data-test-subj="timelines-table"]';
|
|||
export const TIMELINES_USERNAME = '[data-test-subj="username"]';
|
||||
|
||||
export const REFRESH_BUTTON = '[data-test-subj="refreshButton-linkIcon"]';
|
||||
|
||||
export const TIMELINES_OVERVIEW = '[data-test-subj="timelines-container"]';
|
||||
|
||||
export const TIMELINES_OVERVIEW_ONLY_FAVORITES = `${TIMELINES_OVERVIEW} [data-test-subj="only-favorites-toggle"]`;
|
||||
|
||||
export const TIMELINES_OVERVIEW_SEARCH = `${TIMELINES_OVERVIEW} [data-test-subj="search-bar"]`;
|
||||
|
||||
export const TIMELINES_OVERVIEW_TABLE = `${TIMELINES_OVERVIEW} [data-test-subj="timelines-table"]`;
|
||||
|
|
|
@ -79,7 +79,7 @@ export const TimelinesPageComponent: React.FC = () => {
|
|||
</EuiFlexGroup>
|
||||
</HeaderPage>
|
||||
|
||||
<TimelinesContainer>
|
||||
<TimelinesContainer data-test-subj="timelines-container">
|
||||
<StatefulOpenTimeline
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
isModal={false}
|
||||
|
|
|
@ -222,11 +222,10 @@ describe('saved_object', () => {
|
|||
test('should send correct options for counts of favorite timeline', async () => {
|
||||
expect(mockFindSavedObject.mock.calls[5][0]).toEqual({
|
||||
filter:
|
||||
'not siem-ui-timeline.attributes.status: draft and not siem-ui-timeline.attributes.status: immutable',
|
||||
'not siem-ui-timeline.attributes.status: draft and not siem-ui-timeline.attributes.status: immutable and siem-ui-timeline.attributes.favorite.keySearch: dXNlcm5hbWU=',
|
||||
page: 1,
|
||||
perPage: 1,
|
||||
search: ' dXNlcm5hbWU=',
|
||||
searchFields: ['title', 'description', 'favorite.keySearch'],
|
||||
searchFields: ['title', 'description'],
|
||||
type: 'siem-ui-timeline',
|
||||
});
|
||||
});
|
||||
|
|
|
@ -161,9 +161,26 @@ const getTimelineTypeFilter = (
|
|||
: `not siem-ui-timeline.attributes.status: ${TimelineStatus.immutable}`;
|
||||
|
||||
const filters = [typeFilter, draftFilter, immutableFilter];
|
||||
return filters.filter((f) => f != null).join(' and ');
|
||||
return combineFilters(filters);
|
||||
};
|
||||
|
||||
const getTimelineFavoriteFilter = ({
|
||||
onlyUserFavorite,
|
||||
request,
|
||||
}: {
|
||||
onlyUserFavorite: boolean | null;
|
||||
request: FrameworkRequest;
|
||||
}) => {
|
||||
if (!onlyUserFavorite) {
|
||||
return null;
|
||||
}
|
||||
const username = request.user?.username ?? UNAUTHENTICATED_USER;
|
||||
return `siem-ui-timeline.attributes.favorite.keySearch: ${convertStringToBase64(username)}`;
|
||||
};
|
||||
|
||||
const combineFilters = (filters: Array<string | null>) =>
|
||||
filters.filter((f) => f != null).join(' and ');
|
||||
|
||||
export const getExistingPrepackagedTimelines = async (
|
||||
request: FrameworkRequest,
|
||||
countsOnly?: boolean,
|
||||
|
@ -197,15 +214,19 @@ export const getAllTimeline = async (
|
|||
status: TimelineStatusLiteralWithNull,
|
||||
timelineType: TimelineTypeLiteralWithNull
|
||||
): Promise<AllTimelinesResponse> => {
|
||||
const searchTerm = search != null ? search : undefined;
|
||||
const searchFields = ['title', 'description'];
|
||||
const filter = combineFilters([
|
||||
getTimelineTypeFilter(timelineType ?? null, status ?? null),
|
||||
getTimelineFavoriteFilter({ onlyUserFavorite, request }),
|
||||
]);
|
||||
const options: SavedObjectsFindOptions = {
|
||||
type: timelineSavedObjectType,
|
||||
perPage: pageInfo.pageSize,
|
||||
page: pageInfo.pageIndex,
|
||||
search: search != null ? search : undefined,
|
||||
searchFields: onlyUserFavorite
|
||||
? ['title', 'description', 'favorite.keySearch']
|
||||
: ['title', 'description'],
|
||||
filter: getTimelineTypeFilter(timelineType ?? null, status ?? null),
|
||||
filter,
|
||||
search: searchTerm,
|
||||
searchFields,
|
||||
sortField: sort != null ? sort.sortField : undefined,
|
||||
sortOrder: sort != null ? sort.sortOrder : undefined,
|
||||
};
|
||||
|
@ -233,10 +254,14 @@ export const getAllTimeline = async (
|
|||
|
||||
const favoriteTimelineOptions = {
|
||||
type: timelineSavedObjectType,
|
||||
searchFields: ['title', 'description', 'favorite.keySearch'],
|
||||
search: searchTerm,
|
||||
searchFields,
|
||||
perPage: 1,
|
||||
page: 1,
|
||||
filter: getTimelineTypeFilter(timelineType ?? null, TimelineStatus.active),
|
||||
filter: combineFilters([
|
||||
getTimelineTypeFilter(timelineType ?? null, TimelineStatus.active),
|
||||
getTimelineFavoriteFilter({ onlyUserFavorite: true, request }),
|
||||
]),
|
||||
};
|
||||
|
||||
const result = await Promise.all([
|
||||
|
@ -623,11 +648,6 @@ const getSavedTimeline = async (request: FrameworkRequest, timelineId: string) =
|
|||
const getAllSavedTimeline = async (request: FrameworkRequest, options: SavedObjectsFindOptions) => {
|
||||
const userName = request.user?.username ?? UNAUTHENTICATED_USER;
|
||||
const savedObjectsClient = request.context.core.savedObjects.client;
|
||||
if (options.searchFields != null && options.searchFields.includes('favorite.keySearch')) {
|
||||
options.search = `${options.search != null ? options.search : ''} ${
|
||||
userName != null ? convertStringToBase64(userName) : null
|
||||
}`;
|
||||
}
|
||||
|
||||
const savedObjects = await savedObjectsClient.find<TimelineWithoutExternalRefs>(options);
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue