[Security Solutions] Refactor search bar to use global query string (#135210)

* Refactor search bar to use global query string

* Fix ml redirect bug where query string was overwritten

* Fix cypress test

* Fix pinned filters removed on security solution
This commit is contained in:
Pablo Machado 2022-07-11 13:02:30 +02:00 committed by GitHub
parent 17a2bcc828
commit c3c41c9d18
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 798 additions and 362 deletions

View file

@ -0,0 +1,45 @@
/*
* 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 { login, visitWithoutDateRange } from '../../tasks/login';
import {
GLOBAL_SEARCH_BAR_FILTER_ITEM,
GLOBAL_SEARCH_BAR_PINNED_FILTER,
} from '../../screens/search_bar';
import { DISCOVER_WITH_FILTER_URL, DISCOVER_WITH_PINNED_FILTER_URL } from '../../urls/navigation';
import {
navigateFromKibanaCollapsibleTo,
openKibanaNavigation,
} from '../../tasks/kibana_navigation';
import { ALERTS_PAGE } from '../../screens/kibana_navigation';
describe('pinned filters', () => {
before(() => {
login();
});
it('show pinned filters on security', () => {
visitWithoutDateRange(DISCOVER_WITH_PINNED_FILTER_URL);
cy.get(GLOBAL_SEARCH_BAR_FILTER_ITEM).find(GLOBAL_SEARCH_BAR_PINNED_FILTER).should('exist');
openKibanaNavigation();
navigateFromKibanaCollapsibleTo(ALERTS_PAGE);
cy.get(GLOBAL_SEARCH_BAR_FILTER_ITEM).should('have.text', 'host.name: test-host');
});
it('does not show discover filters on security', () => {
visitWithoutDateRange(DISCOVER_WITH_FILTER_URL);
cy.get(GLOBAL_SEARCH_BAR_FILTER_ITEM).should('exist');
openKibanaNavigation();
navigateFromKibanaCollapsibleTo(ALERTS_PAGE);
cy.get(GLOBAL_SEARCH_BAR_FILTER_ITEM).should('not.exist');
});
});

View file

@ -105,7 +105,7 @@ describe('ml conditional links', () => {
visitWithoutDateRange(mlNetworkSingleIpKqlQuery);
cy.url().should(
'include',
'/app/security/network/ip/127.0.0.1/source?query=(language:kuery,query:%27(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!(%27auditbeat-*%27)))'
'/app/security/network/ip/127.0.0.1/source?timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!(%27auditbeat-*%27)))&query=(language:kuery,query:%27(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)%27)'
);
});
@ -113,7 +113,7 @@ describe('ml conditional links', () => {
visitWithoutDateRange(mlNetworkMultipleIpNullKqlQuery);
cy.url().should(
'include',
'app/security/network/flows?query=(language:kuery,query:%27((source.ip:%20%22127.0.0.1%22%20or%20destination.ip:%20%22127.0.0.1%22)%20or%20(source.ip:%20%22127.0.0.2%22%20or%20destination.ip:%20%22127.0.0.2%22))%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!(%27auditbeat-*%27)))'
'app/security/network/flows?timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!(%27auditbeat-*%27)))&query=(language:kuery,query:%27((source.ip:%20%22127.0.0.1%22%20or%20destination.ip:%20%22127.0.0.1%22)%20or%20(source.ip:%20%22127.0.0.2%22%20or%20destination.ip:%20%22127.0.0.2%22))%27)'
);
});
@ -121,7 +121,7 @@ describe('ml conditional links', () => {
visitWithoutDateRange(mlNetworkMultipleIpKqlQuery);
cy.url().should(
'include',
'/app/security/network/flows?query=(language:kuery,query:%27((source.ip:%20%22127.0.0.1%22%20or%20destination.ip:%20%22127.0.0.1%22)%20or%20(source.ip:%20%22127.0.0.2%22%20or%20destination.ip:%20%22127.0.0.2%22))%20and%20((process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22))%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!(%27auditbeat-*%27)))'
'/app/security/network/flows?timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!(%27auditbeat-*%27)))&query=(language:kuery,query:%27((source.ip:%20%22127.0.0.1%22%20or%20destination.ip:%20%22127.0.0.1%22)%20or%20(source.ip:%20%22127.0.0.2%22%20or%20destination.ip:%20%22127.0.0.2%22))%20and%20((process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22))%27)'
);
});
@ -138,7 +138,7 @@ describe('ml conditional links', () => {
cy.url().should(
'include',
`/app/security/network/flows?query=(language:kuery,query:%27(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!(%27auditbeat-*%27)))`
`/app/security/network/flows?timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!(%27auditbeat-*%27)))&query=(language:kuery,query:%27(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)%27)`
);
});
@ -162,7 +162,7 @@ describe('ml conditional links', () => {
visitWithoutDateRange(mlHostSingleHostKqlQuery);
cy.url().should(
'include',
'/app/security/hosts/siem-windows/anomalies?query=(language:kuery,query:%27(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!(%27auditbeat-*%27)))'
'/app/security/hosts/siem-windows/anomalies?timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!(%27auditbeat-*%27)))&query=(language:kuery,query:%27(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)%27)'
);
});
@ -170,7 +170,7 @@ describe('ml conditional links', () => {
visitWithoutDateRange(mlHostMultiHostNullKqlQuery);
cy.url().should(
'include',
'/app/security/hosts/anomalies?query=(language:kuery,query:%27(host.name:%20%22siem-windows%22%20or%20host.name:%20%22siem-suricata%22)%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!(%27auditbeat-*%27)))'
'/app/security/hosts/anomalies?timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!(%27auditbeat-*%27)))&query=(language:kuery,query:%27(host.name:%20%22siem-windows%22%20or%20host.name:%20%22siem-suricata%22)%27)'
);
});
@ -178,7 +178,7 @@ describe('ml conditional links', () => {
visitWithoutDateRange(mlHostMultiHostKqlQuery);
cy.url().should(
'include',
'/app/security/hosts/anomalies?query=(language:kuery,query:%27(host.name:%20%22siem-windows%22%20or%20host.name:%20%22siem-suricata%22)%20and%20((process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22))%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!(%27auditbeat-*%27)))'
'/app/security/hosts/anomalies?timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!(%27auditbeat-*%27)))&query=(language:kuery,query:%27(host.name:%20%22siem-windows%22%20or%20host.name:%20%22siem-suricata%22)%20and%20((process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22))%27)'
);
});
@ -194,7 +194,7 @@ describe('ml conditional links', () => {
visitWithoutDateRange(mlHostVariableHostKqlQuery);
cy.url().should(
'include',
'/app/security/hosts/anomalies?query=(language:kuery,query:%27(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!(%27auditbeat-*%27)))'
'/app/security/hosts/anomalies?timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!(%27auditbeat-*%27)))&query=(language:kuery,query:%27(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)%27)'
);
});
});

View file

@ -30,7 +30,12 @@ import { openFirstHostDetails, waitForAllHostsToBeLoaded } from '../../tasks/hos
import { openAllHosts } from '../../tasks/hosts/main';
import { waitForIpsTableToBeLoaded } from '../../tasks/network/flows';
import { clearSearchBar, kqlSearch, navigateFromHeaderTo } from '../../tasks/security_header';
import {
clearSearchBar,
kqlSearch,
navigateFromHeaderTo,
saveQuery,
} from '../../tasks/security_header';
import { openTimelineUsingToggle } from '../../tasks/security_main';
import { addNameToTimeline, closeTimeline, populateTimeline } from '../../tasks/timeline';
@ -39,6 +44,10 @@ import { ABSOLUTE_DATE_RANGE } from '../../urls/state';
import { getTimeline } from '../../objects/timeline';
import { TIMELINE } from '../../screens/create_new_case';
import {
GLOBAL_SEARCH_BAR_FILTER_ITEM_AT,
GLOBAL_SEARCH_BAR_PINNED_FILTER,
} from '../../screens/search_bar';
const ABSOLUTE_DATE = {
endTime: 'Aug 1, 2019 @ 20:33:29.186',
@ -58,6 +67,29 @@ describe('url state', () => {
login();
});
it('sets filters from the url', () => {
visitWithoutDateRange(ABSOLUTE_DATE_RANGE.urlFiltersHostsHosts);
cy.get(GLOBAL_SEARCH_BAR_FILTER_ITEM_AT(0)).should('have.text', 'host.name: test-host');
cy.get(GLOBAL_SEARCH_BAR_FILTER_ITEM_AT(0))
.find(GLOBAL_SEARCH_BAR_PINNED_FILTER)
.should('exist');
cy.get(GLOBAL_SEARCH_BAR_FILTER_ITEM_AT(1)).should('have.text', 'host.os.name: test-os');
});
it('sets saved query from the url', () => {
visitWithoutDateRange(ABSOLUTE_DATE_RANGE.urlFiltersHostsHosts);
saveQuery('test-query');
// refresh the page to force loading the saved query from the URL
cy.reload();
cy.get(GLOBAL_SEARCH_BAR_FILTER_ITEM_AT(0)).should('have.text', 'host.name: test-host');
cy.get(GLOBAL_SEARCH_BAR_FILTER_ITEM_AT(0))
.find(GLOBAL_SEARCH_BAR_PINNED_FILTER)
.should('exist');
cy.get(GLOBAL_SEARCH_BAR_FILTER_ITEM_AT(1)).should('have.text', 'host.os.name: test-os');
});
it('sets the global start and end dates from the url', () => {
visitWithoutDateRange(ABSOLUTE_DATE_RANGE.url);
cy.get(DATE_PICKER_START_DATE_POPOVER_BUTTON).should(
@ -184,7 +216,7 @@ describe('url state', () => {
cy.get(NETWORK).should(
'have.attr',
'href',
`/app/security/network?query=(language:kuery,query:'source.ip:%20%2210.142.0.9%22%20')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2019-08-01T20:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2019-08-01T20:33:29.186Z')))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!('auditbeat-*')))`
`/app/security/network?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2019-08-01T20:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2019-08-01T20:33:29.186Z')))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!('auditbeat-*')))&query=(language:kuery,query:'source.ip:%20%2210.142.0.9%22%20')`
);
});
@ -197,12 +229,12 @@ describe('url state', () => {
cy.get(HOSTS).should(
'have.attr',
'href',
`/app/security/hosts?query=(language:kuery,query:'host.name:%20%22siem-kibana%22%20')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2023-01-01T21:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2023-01-01T21:33:29.186Z')))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!('auditbeat-*')))`
`/app/security/hosts?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2023-01-01T21:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2023-01-01T21:33:29.186Z')))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!('auditbeat-*')))&query=(language:kuery,query:'host.name:%20%22siem-kibana%22%20')`
);
cy.get(NETWORK).should(
'have.attr',
'href',
`/app/security/network?query=(language:kuery,query:'host.name:%20%22siem-kibana%22%20')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2023-01-01T21:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2023-01-01T21:33:29.186Z')))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!('auditbeat-*')))`
`/app/security/network?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2023-01-01T21:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2023-01-01T21:33:29.186Z')))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!('auditbeat-*')))&query=(language:kuery,query:'host.name:%20%22siem-kibana%22%20')`
);
cy.get(HOSTS_NAMES).first().should('have.text', 'siem-kibana');
@ -221,14 +253,14 @@ describe('url state', () => {
.should(
'have.attr',
'href',
`/app/security/hosts?query=(language:kuery,query:'agent.type:%20%22auditbeat%22%20')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2023-01-01T21:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2023-01-01T21:33:29.186Z')))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!('auditbeat-*')))`
`/app/security/hosts?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2023-01-01T21:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2023-01-01T21:33:29.186Z')))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!('auditbeat-*')))&query=(language:kuery,query:'agent.type:%20%22auditbeat%22%20')`
);
cy.get(BREADCRUMBS)
.eq(2)
.should(
'have.attr',
'href',
`/app/security/hosts/siem-kibana?query=(language:kuery,query:'agent.type:%20%22auditbeat%22%20')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2023-01-01T21:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2023-01-01T21:33:29.186Z')))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!('auditbeat-*')))`
`/app/security/hosts/siem-kibana?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2023-01-01T21:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2023-01-01T21:33:29.186Z')))&sourcerer=(default:(id:security-solution-default,selectedPatterns:!('auditbeat-*')))&query=(language:kuery,query:'agent.type:%20%22auditbeat%22%20')`
);
});

View file

@ -28,3 +28,7 @@ export const ADD_FILTER_FORM_FILTER_VALUE_INPUT = '[data-test-subj="filterParams
export const ADD_FILTER_FORM_SAVE_BUTTON = '[data-test-subj="saveFilter"]';
export const GLOBAL_SEARCH_BAR_FILTER_ITEM = '#popoverFor_filter0';
export const GLOBAL_SEARCH_BAR_FILTER_ITEM_AT = (value: number) => `#popoverFor_filter${value}`;
export const GLOBAL_SEARCH_BAR_PINNED_FILTER = '.globalFilterItem-isPinned';

View file

@ -5,6 +5,7 @@
* 2.0.
*/
import { TOASTER } from '../screens/alerts_detection_rules';
import { KQL_INPUT, REFRESH_BUTTON } from '../screens/security_header';
export const clearSearchBar = () => {
@ -25,3 +26,14 @@ export const refreshPage = () => {
.click({ force: true })
.should('not.have.attr', 'aria-label', 'Needs updating');
};
export const saveQuery = (name: string) => {
const random = Math.floor(Math.random() * 100000);
const queryName = `${name}-${random}`;
cy.get('div[data-test-subj="globalDatePicker"] [data-test-subj="queryBarMenuPopover"]').click();
cy.get('[data-test-subj="saved-query-management-save-button"]').click();
cy.get('[data-test-subj="saveQueryFormTitle"]').type(queryName);
cy.get('[data-test-subj="savedQueryFormSaveButton"]').click();
cy.get(TOASTER).should('have.text', `Your query "${queryName}" was saved`);
return queryName;
};

View file

@ -50,3 +50,8 @@ export const RULE_CREATION = 'app/security/rules/create';
export const TIMELINES_URL = '/app/security/timelines';
export const TIMELINE_TEMPLATES_URL = '/app/security/timelines/template';
export const LOGOUT_URL = '/logout';
export const DISCOVER_WITH_FILTER_URL =
"/app/discover#/?_g=(filters:!(),refreshInterval:(pause:!t,value:0),time:(from:now%2Fd,to:now%2Fd))&_a=(columns:!(),filters:!(('$state':(store:appState),meta:(alias:!n,disabled:!f,index:security-solution-default,key:host.name,negate:!f,params:(query:test-host),type:phrase),query:(match_phrase:(host.name:test-host)))),index:security-solution-default,interval:auto,query:(language:kuery,query:''),sort:!(!('@timestamp',desc)))";
export const DISCOVER_WITH_PINNED_FILTER_URL =
"/app/discover#/?_g=(filters:!(('$state':(store:globalState),meta:(alias:!n,disabled:!f,index:security-solution-default,key:host.name,negate:!f,params:(query:test-host),type:phrase),query:(match_phrase:(host.name:test-host)))),refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))&_a=(columns:!(),filters:!(),index:security-solution-default,interval:auto,query:(language:kuery,query:''),sort:!(!('@timestamp',desc)))";

View file

@ -20,4 +20,7 @@ export const ABSOLUTE_DATE_RANGE = {
'/app/security/hosts/allHosts?timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-01T20:03:29.186Z%27,kind:absolute,to:%272019-08-01T20:33:29.186Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-01T20:03:29.186Z%27,kind:absolute,to:%272019-08-01T20:33:29.186Z%27)))',
urlHostNew:
'/app/security/hosts/allHosts?timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-01T20:03:29.186Z%27,kind:absolute,to:%272023-01-01T21:33:29.186Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-01T20:03:29.186Z%27,kind:absolute,to:%272023-01-01T21:33:29.186Z%27)))',
urlFiltersHostsHosts:
'/app/security/hosts/allHosts?filters=!((%27$state%27:(store:globalState),meta:(alias:!n,disabled:!f,key:host.name,negate:!f,params:(query:test-host),type:phrase),query:(match_phrase:(host.name:(query:test-host)))),(%27$state%27:(store:appState),meta:(alias:!n,disabled:!f,key:host.os.name,negate:!f,params:(query:test-os),type:phrase),query:(match_phrase:(host.os.name:(query:test-os)))))',
};

View file

@ -0,0 +1,294 @@
/*
* 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, waitFor } from '@testing-library/react';
import React from 'react';
import { HomePage } from '.';
import type { SavedQuery } from '@kbn/data-plugin/public';
import { FilterManager } from '@kbn/data-plugin/public';
import { CONSTANTS } from '../../common/components/url_state/constants';
import {
createSecuritySolutionStorageMock,
kibanaObservable,
mockGlobalState,
SUB_PLUGINS_REDUCER,
TestProviders,
} from '../../common/mock';
import { inputsActions } from '../../common/store/inputs';
import { setSearchBarFilter } from '../../common/store/inputs/actions';
import { coreMock } from '@kbn/core/public/mocks';
import type { Filter } from '@kbn/es-query';
import { createStore } from '../../common/store';
jest.mock('../../common/store/inputs/actions');
const DummyComponent = ({ children }: { children: React.ReactNode }) => <>{children}</>;
const mockedUseInitializeUrlParam = jest.fn();
const mockUseInitializeUrlParam = (urlParamKey: string, state: unknown) => {
mockedUseInitializeUrlParam.mockImplementation((key, fn) => {
if (urlParamKey === key) {
fn(state);
}
});
};
jest.mock('../../common/utils/global_query_string', () => {
const original = jest.requireActual('../../common/utils/global_query_string');
return {
...original,
useInitializeUrlParam: (...params: unknown[]) => mockedUseInitializeUrlParam(...params),
useSyncGlobalQueryString: jest.fn(),
};
});
jest.mock('../../common/components/drag_and_drop/drag_drop_context_wrapper', () => ({
DragDropContextWrapper: DummyComponent,
}));
jest.mock('./template_wrapper', () => ({
SecuritySolutionTemplateWrapper: DummyComponent,
}));
jest.mock('react-router-dom', () => {
const original = jest.requireActual('react-router-dom');
return {
...original,
useLocation: jest.fn().mockReturnValue({ pathname: '/test', search: '?' }),
};
});
const mockedFilterManager = new FilterManager(coreMock.createStart().uiSettings);
const mockGetSavedQuery = jest.fn();
const dummyFilter: Filter = {
meta: {
alias: null,
negate: false,
disabled: false,
type: 'phrase',
key: 'dummy',
params: {
query: 'value',
},
},
query: {
term: {
dummy: 'value',
},
},
};
jest.mock('../../common/lib/kibana', () => {
const original = jest.requireActual('../../common/lib/kibana');
return {
...original,
useKibana: () => ({
...original.useKibana(),
services: {
...original.useKibana().services,
data: {
...original.useKibana().services.data,
query: {
...original.useKibana().services.data.query,
filterManager: mockedFilterManager,
savedQueries: { getSavedQuery: mockGetSavedQuery },
},
},
},
}),
};
});
const mockDispatch = jest.fn();
jest.mock('react-redux', () => {
const original = jest.requireActual('react-redux');
return {
...original,
useDispatch: () => mockDispatch,
};
});
describe('HomePage', () => {
beforeEach(() => {
jest.clearAllMocks();
mockedFilterManager.setFilters([]);
});
it('calls useInitializeUrlParam for appQuery, filters and savedQuery', () => {
render(
<TestProviders>
<HomePage onAppLeave={jest.fn()} setHeaderActionMenu={jest.fn()}>
<span />
</HomePage>
</TestProviders>
);
expect(mockedUseInitializeUrlParam).toHaveBeenCalledWith(
CONSTANTS.appQuery,
expect.any(Function)
);
expect(mockedUseInitializeUrlParam).toHaveBeenCalledWith(
CONSTANTS.filters,
expect.any(Function)
);
expect(mockedUseInitializeUrlParam).toHaveBeenCalledWith(
CONSTANTS.savedQuery,
expect.any(Function)
);
});
it('dispatches setFilterQuery when initializing appQuery', () => {
const state = { query: 'testQuery', language: 'en' };
mockUseInitializeUrlParam(CONSTANTS.appQuery, state);
render(
<TestProviders>
<HomePage onAppLeave={jest.fn()} setHeaderActionMenu={jest.fn()}>
<span />
</HomePage>
</TestProviders>
);
expect(mockDispatch).toHaveBeenCalledWith(
inputsActions.setFilterQuery({
id: 'global',
query: state.query,
language: state.language,
})
);
});
it('initializes saved query store', async () => {
const state = 'test-query-id';
const savedQueryData: SavedQuery = {
id: 'testSavedquery',
attributes: {
title: 'testtitle',
description: 'testDescription',
query: { query: 'testQuery', language: 'testLanguage' },
filters: [
{
meta: {
alias: null,
negate: false,
disabled: false,
},
query: {},
},
],
},
};
mockGetSavedQuery.mockResolvedValue(savedQueryData);
mockUseInitializeUrlParam(CONSTANTS.savedQuery, state);
render(
<TestProviders>
<HomePage onAppLeave={jest.fn()} setHeaderActionMenu={jest.fn()}>
<span />
</HomePage>
</TestProviders>
);
await waitFor(() => {
expect(mockDispatch).toHaveBeenCalledWith(
inputsActions.setSavedQuery({ id: 'global', savedQuery: savedQueryData })
);
expect(mockDispatch).toHaveBeenCalledWith(
inputsActions.setFilterQuery({
id: 'global',
...savedQueryData.attributes.query,
})
);
expect(setSearchBarFilter).toHaveBeenCalledWith({
id: 'global',
filters: savedQueryData.attributes.filters,
});
});
});
describe('Filters', () => {
it('sets filter initial value in the store and filterManager', () => {
const state = [{ testFilter: 'test' }];
mockUseInitializeUrlParam(CONSTANTS.filters, state);
const spySetFilters = jest.spyOn(mockedFilterManager, 'setFilters');
render(
<TestProviders>
<HomePage onAppLeave={jest.fn()} setHeaderActionMenu={jest.fn()}>
<span />
</HomePage>
</TestProviders>
);
expect(setSearchBarFilter).toHaveBeenCalledWith({
id: 'global',
filters: state,
});
expect(spySetFilters).toHaveBeenCalledWith(state);
});
it('sets filter from store when URL param has no value', () => {
const state = null;
mockUseInitializeUrlParam(CONSTANTS.filters, state);
const spySetAppFilters = jest.spyOn(mockedFilterManager, 'setAppFilters');
const { storage } = createSecuritySolutionStorageMock();
const mockstate = {
...mockGlobalState,
inputs: {
...mockGlobalState.inputs,
global: {
...mockGlobalState.inputs.global,
filters: [dummyFilter],
},
},
};
const mockStore = createStore(mockstate, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
render(
<TestProviders store={mockStore}>
<HomePage onAppLeave={jest.fn()} setHeaderActionMenu={jest.fn()}>
<span />
</HomePage>
</TestProviders>
);
expect(spySetAppFilters).toHaveBeenCalledWith([dummyFilter]);
});
it('preserves pinned filters when URL param has no value', () => {
const state = null;
mockUseInitializeUrlParam(CONSTANTS.filters, state);
// pin filter
mockedFilterManager.setGlobalFilters([dummyFilter]);
render(
<TestProviders>
<HomePage onAppLeave={jest.fn()} setHeaderActionMenu={jest.fn()}>
<span />
</HomePage>
</TestProviders>
);
expect(mockedFilterManager.getFilters()).toEqual([
{
...dummyFilter,
$state: {
store: 'globalState',
},
},
]);
});
});
});

View file

@ -24,6 +24,8 @@ import { GlobalHeader } from './global_header';
import { SecuritySolutionTemplateWrapper } from './template_wrapper';
import { ConsoleManager } from '../../management/components/console/components/console_manager';
import { useSyncGlobalQueryString } from '../../common/utils/global_query_string';
import { useInitSearchBarUrlParams } from '../../common/hooks/search_bar/use_init_search_bar_url_params';
interface HomePageProps {
children: React.ReactNode;
onAppLeave: (handler: AppLeaveHandler) => void;
@ -38,6 +40,7 @@ const HomePageComponent: React.FC<HomePageProps> = ({
const { pathname } = useLocation();
useSyncGlobalQueryString();
useInitSourcerer(getScopeFromPath(pathname));
useInitSearchBarUrlParams();
const { browserFields, indexPattern } = useSourcererDataView(getScopeFromPath(pathname));
// side effect: this will attempt to upgrade the endpoint package if it is not up to date

View file

@ -5,7 +5,6 @@
* 2.0.
*/
import { isEmpty } from 'lodash/fp';
import type { Location } from 'history';
import type { Filter, Query } from '@kbn/es-query';
@ -47,19 +46,7 @@ export const getUrlStateSearch = (urlState: UrlState): string =>
(myLocation: Location, urlKey: KeyUrlState) => {
let urlStateToReplace: Filter[] | Query | TimelineUrl | UrlInputsModel | string = '';
if (urlKey === CONSTANTS.appQuery && urlState.query != null) {
if (urlState.query.query === '') {
urlStateToReplace = '';
} else {
urlStateToReplace = urlState.query;
}
} else if (urlKey === CONSTANTS.filters && urlState.filters != null) {
if (isEmpty(urlState.filters)) {
urlStateToReplace = '';
} else {
urlStateToReplace = urlState.filters;
}
} else if (urlKey === CONSTANTS.timerange) {
if (urlKey === CONSTANTS.timerange) {
urlStateToReplace = urlState[CONSTANTS.timerange];
} else if (urlKey === CONSTANTS.timeline && urlState[CONSTANTS.timeline] != null) {
const timeline = urlState[CONSTANTS.timeline];

View file

@ -91,8 +91,6 @@ describe('SIEM Navigation', () => {
linkTo: ['global'],
},
},
[CONSTANTS.appQuery]: { query: '', language: 'kuery' },
[CONSTANTS.filters]: [],
[CONSTANTS.timeline]: {
activeTab: TimelineTabs.query,
id: '',

View file

@ -78,13 +78,10 @@ export const TabNavigationComponent: React.FC<
return (
<TabNavigation
query={urlState.query}
display={display}
filters={urlState.filters}
navTabs={navTabs}
pageName={pageName}
pathName={pathName}
savedQuery={urlState.savedQuery}
tabName={tabName}
timeline={urlState.timeline}
timerange={urlState.timerange}

View file

@ -88,8 +88,6 @@ describe('Table Navigation', () => {
linkTo: ['global'],
},
},
[CONSTANTS.appQuery]: { query: 'host.name:"siem-es"', language: 'kuery' },
[CONSTANTS.filters]: [],
[CONSTANTS.timeline]: {
activeTab: TimelineTabs.query,
id: '',

View file

@ -5,11 +5,9 @@
* 2.0.
*/
import type { Filter, Query } from '@kbn/es-query';
import type { UrlInputsModel } from '../../../store/inputs/model';
import type { CONSTANTS } from '../../url_state/constants';
import type { TimelineUrl } from '../../../../timelines/store/timeline/model';
import type { SecuritySolutionTabNavigationProps } from '../types';
import type { SiemRouteType } from '../../../utils/route/types';
@ -17,9 +15,6 @@ export interface TabNavigationProps extends SecuritySolutionTabNavigationProps {
pathName: string;
pageName: string;
tabName: SiemRouteType | undefined;
[CONSTANTS.appQuery]?: Query;
[CONSTANTS.filters]?: Filter[];
[CONSTANTS.savedQuery]?: string;
[CONSTANTS.timerange]: UrlInputsModel;
[CONSTANTS.timeline]: TimelineUrl;
}

View file

@ -8,10 +8,10 @@ Object {
"id": "main",
"items": Array [
Object {
"data-href": "securitySolutionUI/get_started?query=(language:kuery,query:'host.name:%22security-solution-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
"data-href": "securitySolutionUI/get_started?timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
"data-test-subj": "navigation-get_started",
"disabled": false,
"href": "securitySolutionUI/get_started?query=(language:kuery,query:'host.name:%22security-solution-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
"href": "securitySolutionUI/get_started?timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
"id": "get_started",
"isSelected": false,
"name": "Get started",
@ -24,20 +24,20 @@ Object {
"id": "dashboards",
"items": Array [
Object {
"data-href": "securitySolutionUI/overview?query=(language:kuery,query:'host.name:%22security-solution-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
"data-href": "securitySolutionUI/overview?timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
"data-test-subj": "navigation-overview",
"disabled": false,
"href": "securitySolutionUI/overview?query=(language:kuery,query:'host.name:%22security-solution-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
"href": "securitySolutionUI/overview?timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
"id": "overview",
"isSelected": false,
"name": "Overview",
"onClick": [Function],
},
Object {
"data-href": "securitySolutionUI/detection_response?query=(language:kuery,query:'host.name:%22security-solution-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
"data-href": "securitySolutionUI/detection_response?timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
"data-test-subj": "navigation-detection_response",
"disabled": false,
"href": "securitySolutionUI/detection_response?query=(language:kuery,query:'host.name:%22security-solution-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
"href": "securitySolutionUI/detection_response?timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
"id": "detection_response",
"isSelected": false,
"name": "Detection & Response",
@ -50,30 +50,30 @@ Object {
"id": "detect",
"items": Array [
Object {
"data-href": "securitySolutionUI/alerts?query=(language:kuery,query:'host.name:%22security-solution-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
"data-href": "securitySolutionUI/alerts?timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
"data-test-subj": "navigation-alerts",
"disabled": false,
"href": "securitySolutionUI/alerts?query=(language:kuery,query:'host.name:%22security-solution-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
"href": "securitySolutionUI/alerts?timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
"id": "alerts",
"isSelected": false,
"name": "Alerts",
"onClick": [Function],
},
Object {
"data-href": "securitySolutionUI/rules?query=(language:kuery,query:'host.name:%22security-solution-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
"data-href": "securitySolutionUI/rules?timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
"data-test-subj": "navigation-rules",
"disabled": false,
"href": "securitySolutionUI/rules?query=(language:kuery,query:'host.name:%22security-solution-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
"href": "securitySolutionUI/rules?timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
"id": "rules",
"isSelected": false,
"name": "Rules",
"onClick": [Function],
},
Object {
"data-href": "securitySolutionUI/exceptions?query=(language:kuery,query:'host.name:%22security-solution-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
"data-href": "securitySolutionUI/exceptions?timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
"data-test-subj": "navigation-exceptions",
"disabled": false,
"href": "securitySolutionUI/exceptions?query=(language:kuery,query:'host.name:%22security-solution-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
"href": "securitySolutionUI/exceptions?timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
"id": "exceptions",
"isSelected": false,
"name": "Exception lists",
@ -86,30 +86,30 @@ Object {
"id": "explore",
"items": Array [
Object {
"data-href": "securitySolutionUI/hosts?query=(language:kuery,query:'host.name:%22security-solution-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
"data-href": "securitySolutionUI/hosts?timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
"data-test-subj": "navigation-hosts",
"disabled": false,
"href": "securitySolutionUI/hosts?query=(language:kuery,query:'host.name:%22security-solution-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
"href": "securitySolutionUI/hosts?timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
"id": "hosts",
"isSelected": true,
"name": "Hosts",
"onClick": [Function],
},
Object {
"data-href": "securitySolutionUI/network?query=(language:kuery,query:'host.name:%22security-solution-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
"data-href": "securitySolutionUI/network?timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
"data-test-subj": "navigation-network",
"disabled": false,
"href": "securitySolutionUI/network?query=(language:kuery,query:'host.name:%22security-solution-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
"href": "securitySolutionUI/network?timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
"id": "network",
"isSelected": false,
"name": "Network",
"onClick": [Function],
},
Object {
"data-href": "securitySolutionUI/users?query=(language:kuery,query:'host.name:%22security-solution-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
"data-href": "securitySolutionUI/users?timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
"data-test-subj": "navigation-users",
"disabled": false,
"href": "securitySolutionUI/users?query=(language:kuery,query:'host.name:%22security-solution-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
"href": "securitySolutionUI/users?timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
"id": "users",
"isSelected": false,
"name": "Users",
@ -122,20 +122,20 @@ Object {
"id": "investigate",
"items": Array [
Object {
"data-href": "securitySolutionUI/timelines?query=(language:kuery,query:'host.name:%22security-solution-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
"data-href": "securitySolutionUI/timelines?timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
"data-test-subj": "navigation-timelines",
"disabled": false,
"href": "securitySolutionUI/timelines?query=(language:kuery,query:'host.name:%22security-solution-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
"href": "securitySolutionUI/timelines?timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
"id": "timelines",
"isSelected": false,
"name": "Timelines",
"onClick": [Function],
},
Object {
"data-href": "securitySolutionUI/cases?query=(language:kuery,query:'host.name:%22security-solution-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
"data-href": "securitySolutionUI/cases?timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
"data-test-subj": "navigation-cases",
"disabled": false,
"href": "securitySolutionUI/cases?query=(language:kuery,query:'host.name:%22security-solution-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
"href": "securitySolutionUI/cases?timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
"id": "cases",
"isSelected": false,
"name": "Cases",

View file

@ -37,8 +37,6 @@ jest.mock('../../../../management/pages/host_isolation_exceptions/view/hooks');
describe('useSecuritySolutionNavigation', () => {
const mockUrlState = {
[CONSTANTS.appQuery]: { query: 'host.name:"security-solution-es"', language: 'kuery' },
[CONSTANTS.savedQuery]: '',
[CONSTANTS.timeline]: {
activeTab: TimelineTabs.query,
id: '',
@ -171,10 +169,10 @@ describe('useSecuritySolutionNavigation', () => {
);
expect(caseNavItem).toMatchInlineSnapshot(`
Object {
"data-href": "securitySolutionUI/cases?query=(language:kuery,query:'host.name:%22security-solution-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
"data-href": "securitySolutionUI/cases?timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
"data-test-subj": "navigation-cases",
"disabled": false,
"href": "securitySolutionUI/cases?query=(language:kuery,query:'host.name:%22security-solution-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
"href": "securitySolutionUI/cases?timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
"id": "cases",
"isSelected": false,
"name": "Cases",

View file

@ -72,11 +72,8 @@ export const useSecuritySolutionNavigation = () => {
]);
return usePrimaryNavigation({
query: urlState.query,
filters: urlState.filters,
navTabs: enabledNavTabs,
pageName,
savedQuery: urlState.savedQuery,
tabName,
timeline: urlState.timeline,
timerange: urlState.timerange,

View file

@ -19,11 +19,8 @@ const translatedNavTitle = i18n.translate('xpack.securitySolution.navigation.mai
});
export const usePrimaryNavigation = ({
filters,
query,
navTabs,
pageName,
savedQuery,
tabName,
timeline,
timerange,
@ -49,9 +46,6 @@ export const usePrimaryNavigation = ({
const navItems = usePrimaryNavigationItems({
navTabs,
selectedTabId,
filters,
query,
savedQuery,
timeline,
timerange,
});

View file

@ -6,14 +6,25 @@
*/
import React from 'react';
import { render, fireEvent } from '@testing-library/react';
import {
createSecuritySolutionStorageMock,
kibanaObservable,
mockGlobalState,
SUB_PLUGINS_REDUCER,
TestProviders,
} from '../../mock';
import { render, fireEvent, waitFor } from '@testing-library/react';
import type { InputsModelId } from '../../store/inputs/constants';
import { SearchBarComponent } from '.';
import { TestProviders } from '../../mock';
import type { SavedQuery } from '@kbn/data-plugin/public';
import { FilterManager } from '@kbn/data-plugin/public';
import { coreMock } from '@kbn/core/public/mocks';
import { createStore } from '../../store';
import { inputsActions } from '../../store/inputs';
const mockSetAppFilters = jest.fn();
const mockFilterManager = new FilterManager(coreMock.createStart().uiSettings);
mockFilterManager.setAppFilters = mockSetAppFilters;
jest.mock('../../lib/kibana', () => {
const original = jest.requireActual('../../lib/kibana');
return {
@ -45,6 +56,11 @@ jest.mock('../../lib/kibana', () => {
};
});
const mockUpdateUrlParam = jest.fn();
jest.mock('../../utils/global_query_string', () => ({
useUpdateUrlParam: () => mockUpdateUrlParam,
}));
describe('SearchBarComponent', () => {
const props = {
id: 'global' as InputsModelId,
@ -73,16 +89,6 @@ describe('SearchBarComponent', () => {
jest.resetAllMocks();
});
it('calls setSearchBarFilter on mount', () => {
render(
<TestProviders>
<SearchBarComponent {...props} />
</TestProviders>
);
expect(props.setSearchBarFilter).toHaveBeenCalled();
});
it('calls pollForSignalIndex on Refresh button click', () => {
const { getByTestId } = render(
<TestProviders>
@ -102,4 +108,174 @@ describe('SearchBarComponent', () => {
fireEvent.click(getByTestId('querySubmitButton'));
expect(pollForSignalIndex).not.toHaveBeenCalled();
});
it('calls useUpdateUrlParam for filter and query', () => {
const query = { query: 'testQuery', language: 'kuery' };
const filters = [
{
meta: {
negate: false,
alias: null,
disabled: false,
type: 'phrase',
key: 'host.name',
},
query: { match_phrase: { 'host.name': 'testValue' } },
},
];
const state = {
...mockGlobalState,
inputs: {
...mockGlobalState.inputs,
global: {
...mockGlobalState.inputs.global,
filters,
query,
},
},
};
const { storage } = createSecuritySolutionStorageMock();
const store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
render(
<TestProviders store={store}>
<SearchBarComponent {...props} />
</TestProviders>
);
expect(mockUpdateUrlParam).toHaveBeenCalledWith(filters);
expect(mockUpdateUrlParam).toHaveBeenCalledWith(query);
// For updateSavedQueryUrlParam
expect(mockUpdateUrlParam).toHaveBeenCalledWith(null);
});
it('calls useUpdateUrlParam for savedQuery', () => {
const savedQuery: SavedQuery = {
id: 'testSavedquery',
attributes: {
title: 'testtitle',
description: 'testDescription',
query: { query: 'testQuery', language: 'kuery' },
},
};
const state = {
...mockGlobalState,
inputs: {
...mockGlobalState.inputs,
global: {
...mockGlobalState.inputs.global,
savedQuery,
},
},
};
const { storage } = createSecuritySolutionStorageMock();
const store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
render(
<TestProviders store={store}>
<SearchBarComponent {...props} />
</TestProviders>
);
// For filters and query
expect(mockUpdateUrlParam).toHaveBeenNthCalledWith(2, null);
expect(mockUpdateUrlParam).toHaveBeenCalledWith(savedQuery.id);
});
it('calls useUpdateUrlParam when query state changes', async () => {
const { storage } = createSecuritySolutionStorageMock();
const store = createStore(mockGlobalState, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
render(
<TestProviders store={store}>
<SearchBarComponent {...props} />
</TestProviders>
);
jest.clearAllMocks();
const newQuery = { query: 'testQuery', language: 'new testLanguage' };
store.dispatch(
inputsActions.setFilterQuery({
id: 'global',
...newQuery,
})
);
await waitFor(() => {
expect(mockUpdateUrlParam).toHaveBeenCalledWith(newQuery);
});
});
it('calls useUpdateUrlParam when filters change', async () => {
const { storage } = createSecuritySolutionStorageMock();
const store = createStore(mockGlobalState, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
render(
<TestProviders store={store}>
<SearchBarComponent {...props} />
</TestProviders>
);
jest.clearAllMocks();
const filters = [
{
meta: {
negate: false,
alias: null,
disabled: false,
type: 'phrase',
key: 'host.name',
},
query: { match_phrase: { 'host.name': 'testValue' } },
},
];
store.dispatch(
inputsActions.setSearchBarFilter({
id: 'global',
filters,
})
);
await waitFor(() => {
expect(mockUpdateUrlParam).toHaveBeenCalledWith(filters);
});
});
it('calls useUpdateUrlParam when savedQuery changes', async () => {
const { storage } = createSecuritySolutionStorageMock();
const store = createStore(mockGlobalState, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
render(
<TestProviders store={store}>
<SearchBarComponent {...props} />
</TestProviders>
);
jest.clearAllMocks();
const savedQuery: SavedQuery = {
id: 'testSavedQuery123',
attributes: {
title: 'testtitle',
description: 'testDescription',
query: { query: 'testQuery', language: 'kuery' },
},
};
store.dispatch(
inputsActions.setSavedQuery({
id: 'global',
savedQuery,
})
);
await waitFor(() => {
expect(mockUpdateUrlParam).toHaveBeenCalledWith(savedQuery.id);
});
});
});

View file

@ -40,8 +40,7 @@ import { useKibana } from '../../lib/kibana';
import { usersActions } from '../../../users/store';
import { hostsActions } from '../../../hosts/store';
import { networkActions } from '../../../network/store';
const APP_STATE_STORAGE_KEY = 'securitySolution.searchBar.appState';
import { useSyncSearchBarUrlParams } from '../../hooks/search_bar/use_sync_search_bar_url_param';
interface SiemSearchBarProps {
id: InputsModelId;
@ -80,7 +79,6 @@ export const SearchBarComponent = memo<SiemSearchBarProps & PropsFromRedux>(
filterManager,
},
},
storage,
unifiedSearch: {
ui: { SearchBar },
},
@ -93,6 +91,8 @@ export const SearchBarComponent = memo<SiemSearchBarProps & PropsFromRedux>(
dispatch(networkActions.setNetworkTablesActivePageToZero());
}, [dispatch]);
useSyncSearchBarUrlParams();
useEffect(() => {
if (fromStr != null && toStr != null) {
timefilter.setTime({ from: fromStr, to: toStr });
@ -269,16 +269,6 @@ export const SearchBarComponent = memo<SiemSearchBarProps & PropsFromRedux>(
setTablesActivePageToZero,
]);
const saveAppStateToStorage = useCallback(
(filters: Filter[]) => storage.set(APP_STATE_STORAGE_KEY, filters),
[storage]
);
const getAppStateFromStorage = useCallback(
() => storage.get(APP_STATE_STORAGE_KEY) ?? [],
[storage]
);
useEffect(() => {
let isSubscribed = true;
const subscriptions = new Subscription();
@ -287,25 +277,15 @@ export const SearchBarComponent = memo<SiemSearchBarProps & PropsFromRedux>(
filterManager.getUpdates$().subscribe({
next: () => {
if (isSubscribed) {
saveAppStateToStorage(filterManager.getAppFilters());
setSearchBarFilter({
id,
filters: filterManager.getFilters(),
});
const filters = filterManager.getFilters();
setSearchBarFilter({ id, filters });
setTablesActivePageToZero();
}
},
})
);
// for the initial state
filterManager.setAppFilters(getAppStateFromStorage());
setSearchBarFilter({
id,
filters: filterManager.getFilters(),
});
return () => {
isSubscribed = false;
subscriptions.unsubscribe();
@ -405,8 +385,8 @@ export const dispatchUpdateSearch =
savedQuery,
start,
timelineId,
filterManager,
updateTime = false,
filterManager,
setTablesActivePageToZero,
}: UpdateReduxSearchBar): void => {
if (updateTime) {
@ -465,6 +445,7 @@ export const dispatchUpdateSearch =
if (filters != null) {
filterManager.setFilters(filters);
}
if (savedQuery != null || resetSavedQuery) {
dispatch(inputsActions.setSavedQuery({ id, savedQuery }));
}

View file

@ -35,42 +35,6 @@ describe('Helpers Url_State', () => {
});
describe('isQueryStateEmpty', () => {
test('returns true if queryState is undefined', () => {
const result = isQueryStateEmpty(undefined, CONSTANTS.savedQuery);
expect(result).toBeTruthy();
});
test('returns true if queryState is null', () => {
const result = isQueryStateEmpty(null, CONSTANTS.savedQuery);
expect(result).toBeTruthy();
});
test('returns true if url key is "query" and queryState is empty string', () => {
const result = isQueryStateEmpty('', CONSTANTS.appQuery);
expect(result).toBeTruthy();
});
test('returns false if url key is "query" and queryState is not empty', () => {
const result = isQueryStateEmpty(
{ query: { query: '*:*' }, language: 'kuery' },
CONSTANTS.appQuery
);
expect(result).toBeFalsy();
});
test('returns true if url key is "filters" and queryState is empty', () => {
const result = isQueryStateEmpty([], CONSTANTS.filters);
expect(result).toBeTruthy();
});
test('returns false if url key is "filters" and queryState is not empty', () => {
const result = isQueryStateEmpty(
[{ query: { query: '*:*' }, meta: { key: '123' } }],
CONSTANTS.filters
);
expect(result).toBeFalsy();
});
// TODO: Is this a bug, or intended?
test('returns false if url key is "timeline" and queryState is empty', () => {
const result = isQueryStateEmpty({} as ValueUrlState, CONSTANTS.timeline);

View file

@ -5,13 +5,10 @@
* 2.0.
*/
import { isEmpty } from 'lodash/fp';
import { parse, stringify } from 'query-string';
import { decode, encode } from 'rison-node';
import type * as H from 'history';
import type { Filter, Query } from '@kbn/es-query';
import { url } from '@kbn/kibana-utils-plugin/public';
import { TimelineId, TimelineTabs } from '../../../../common/types/timeline';
@ -127,9 +124,6 @@ export const getTitle = (pageName: string, navTabs: Record<string, NavTab>): str
export const makeMapStateToProps = () => {
const getInputsSelector = inputsSelectors.inputsSelector();
const getGlobalQuerySelector = inputsSelectors.globalQuerySelector();
const getGlobalFiltersQuerySelector = inputsSelectors.globalFiltersQuerySelector();
const getGlobalSavedQuerySelector = inputsSelectors.globalSavedQuerySelector();
const getTimeline = timelineSelectors.getTimelineByIdSelector();
const mapStateToProps = (state: State) => {
const inputState = getInputsSelector(state);
@ -147,24 +141,8 @@ export const makeMapStateToProps = () => {
}
: { id: '', isOpen: false, activeTab: TimelineTabs.query, graphEventId: '' };
let searchAttr: {
[CONSTANTS.appQuery]?: Query;
[CONSTANTS.filters]?: Filter[];
[CONSTANTS.savedQuery]?: string;
} = {
[CONSTANTS.appQuery]: getGlobalQuerySelector(state),
[CONSTANTS.filters]: getGlobalFiltersQuerySelector(state),
};
const savedQuery = getGlobalSavedQuerySelector(state);
if (savedQuery != null && savedQuery.id !== '') {
searchAttr = {
[CONSTANTS.savedQuery]: savedQuery.id,
};
}
return {
urlState: {
...searchAttr,
[CONSTANTS.timerange]: {
global: {
[CONSTANTS.timerange]: globalTimerange,
@ -204,10 +182,7 @@ export const isQueryStateEmpty = (
queryState: ValueUrlState | undefined | null,
urlKey: KeyUrlState
): boolean =>
queryState == null ||
(urlKey === CONSTANTS.appQuery && isEmpty((queryState as Query).query)) ||
(urlKey === CONSTANTS.filters && isEmpty(queryState)) ||
(urlKey === CONSTANTS.timeline && (queryState as TimelineUrl).id === '');
queryState == null || (urlKey === CONSTANTS.timeline && (queryState as TimelineUrl).id === '');
export const replaceStatesInLocation = (
states: ReplaceStateInLocation[],

View file

@ -15,7 +15,6 @@ import { CONSTANTS } from './constants';
import {
getMockPropsObj,
mockHistory,
mockSetFilterQuery,
mockSetAbsoluteRangeDatePicker,
mockSetRelativeRangeDatePicker,
testCases,
@ -181,62 +180,6 @@ describe('UrlStateContainer', () => {
}
);
});
describe('appQuery action is called with correct data on component mount', () => {
test.each(testCases.slice(0, 4))(
' %o',
(page, namespaceLower, namespaceUpper, examplePath, type, pageName, detailName) => {
mockProps = getMockPropsObj({ page, examplePath, namespaceLower, pageName, detailName })
.relativeTimeSearch.undefinedQuery;
(useLocation as jest.Mock).mockReturnValue({
pathname: mockProps.pathName,
search: mockProps.search,
});
mount(<HookWrapper hookProps={mockProps} hook={(args) => useUrlStateHooks(args)} />);
expect(mockSetFilterQuery.mock.calls[0][0]).toEqual({
id: 'global',
language: 'kuery',
query: 'host.name:"siem-es"',
});
}
);
});
});
describe('Redux updates URL state', () => {
describe('appQuery url state is set from redux data on component mount', () => {
test.each(testCases)(
'%o',
(page, namespaceLower, namespaceUpper, examplePath, type, pageName, detailName) => {
mockProps = getMockPropsObj({
page,
examplePath,
namespaceLower,
pageName,
detailName,
}).noSearch.definedQuery;
(useLocation as jest.Mock).mockReturnValue({
pathname: mockProps.pathName,
search: mockProps.search,
});
mount(<HookWrapper hookProps={mockProps} hook={(args) => useUrlStateHooks(args)} />);
expect(
mockHistory.replace.mock.calls[mockHistory.replace.mock.calls.length - 1][0]
).toEqual({
hash: '',
pathname: examplePath,
search: `?query=(language:kuery,query:'host.name:%22siem-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))`,
state: '',
});
}
);
});
});
it("it doesn't update URL state when pathName and browserPAth are out of sync", () => {
@ -298,32 +241,6 @@ describe('UrlStateContainer', () => {
expect(mockHistory.replace.mock.calls[0][0].search).toBe('?');
});
it('it removes empty AppQuery state from URL', () => {
mockProps = {
...getMockProps(
{
hash: '',
pathname: '/network',
search: "?query=(query:'')",
state: '',
},
CONSTANTS.networkPage,
null,
SecurityPageName.network,
undefined
),
};
(useLocation as jest.Mock).mockReturnValue({
pathname: mockProps.pathName,
search: mockProps.search,
});
mount(<HookWrapper hookProps={mockProps} hook={(args) => useUrlStateHooks(args)} />);
expect(mockHistory.replace.mock.calls[0][0].search).not.toContain('query=');
});
it('it removes empty timeline state from URL', () => {
mockProps = {
...getMockProps(

View file

@ -126,43 +126,9 @@ describe('UrlStateContainer - lodash.throttle mocked to test update url', () =>
expect(mockHistory.replace.mock.calls[1][0]).toStrictEqual({
hash: '',
pathname: '/network',
search:
"?query=(language:kuery,query:'host.name:%22siem-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
state: '',
});
});
test('kql query redux state updates the url', () => {
mockProps = getMockPropsObj({
page: CONSTANTS.networkPage,
examplePath: '/network',
namespaceLower: 'network',
pageName: SecurityPageName.network,
detailName: undefined,
}).noSearch.undefinedQuery;
(useLocation as jest.Mock).mockReturnValue({
pathname: mockProps.pathName,
search: mockProps.search,
});
const wrapper = mount(
<HookWrapper hookProps={mockProps} hook={(args) => useUrlStateHooks(args)} />
);
const newUrlState = {
...mockProps.urlState,
[CONSTANTS.appQuery]: getFilterQuery(),
};
wrapper.setProps({
hookProps: { ...mockProps, urlState: newUrlState, isInitializing: false },
});
wrapper.update();
expect(mockHistory.replace.mock.calls[1][0]).toStrictEqual({
hash: '',
pathname: '/network',
search:
"?query=(language:kuery,query:'host.name:%22siem-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))",
search: expect.stringContaining(
"timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))"
),
state: '',
});
});
@ -427,7 +393,7 @@ describe('UrlStateContainer - lodash.throttle mocked to test update url', () =>
wrapper.update();
expect(mockHistory.replace.mock.calls[1][0].search).toEqual(
"?query=(language:kuery,query:'host.name:%22siem-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))"
"?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))"
);
});

View file

@ -10,7 +10,6 @@ import type { Dispatch } from 'redux';
import { useCallback, useMemo } from 'react';
import { useDispatch } from 'react-redux';
import type { Filter, Query } from '@kbn/es-query';
import { inputsActions } from '../../store/actions';
import type { InputsModelId, TimeRangeKinds } from '../../store/inputs/constants';
import type {
@ -42,52 +41,12 @@ export const useSetInitialStateFromUrl = () => {
);
const setInitialStateFromUrl = useCallback(
({
filterManager,
indexPattern,
pageName,
savedQueries,
urlStateToUpdate,
}: SetInitialStateFromUrl) => {
({ urlStateToUpdate }: SetInitialStateFromUrl) => {
urlStateToUpdate.forEach(({ urlKey, newUrlStateString }) => {
if (urlKey === CONSTANTS.timerange) {
updateTimerange(newUrlStateString, dispatch);
}
if (urlKey === CONSTANTS.appQuery && indexPattern != null) {
const appQuery = decodeRisonUrlState<Query>(newUrlStateString);
if (appQuery != null) {
dispatch(
inputsActions.setFilterQuery({
id: 'global',
query: appQuery.query,
language: appQuery.language,
})
);
}
}
if (urlKey === CONSTANTS.filters) {
const filters = decodeRisonUrlState<Filter[]>(newUrlStateString);
filterManager.setFilters(filters || []);
}
if (urlKey === CONSTANTS.savedQuery) {
const savedQueryId = decodeRisonUrlState<string>(newUrlStateString);
if (savedQueryId != null && savedQueryId !== '') {
savedQueries.getSavedQuery(savedQueryId).then((savedQueryData) => {
filterManager.setFilters(savedQueryData.attributes.filters || []);
dispatch(
inputsActions.setFilterQuery({
id: 'global',
...savedQueryData.attributes.query,
})
);
dispatch(inputsActions.setSavedQuery({ id: 'global', savedQuery: savedQueryData }));
});
}
}
if (urlKey === CONSTANTS.timeline) {
const timeline = decodeRisonUrlState<TimelineUrl>(newUrlStateString);
if (timeline != null && timeline.id !== '') {

View file

@ -113,8 +113,6 @@ export const defaultProps: UrlStateContainerPropTypes = {
linkTo: ['global'],
},
},
[CONSTANTS.appQuery]: { query: '', language: 'kuery' },
[CONSTANTS.filters]: [],
[CONSTANTS.timeline]: {
activeTab: TimelineTabs.query,
id: '',
@ -137,7 +135,6 @@ export const getMockProps = (
...defaultProps,
urlState: {
...defaultProps.urlState,
[CONSTANTS.appQuery]: kqlQueryValue || { query: '', language: 'kuery' },
},
history: {
...mockHistory,

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import type { DataViewBase, Filter, Query } from '@kbn/es-query';
import type { DataViewBase } from '@kbn/es-query';
import type { FilterManager, SavedQueryService } from '@kbn/data-plugin/public';
import type { UrlInputsModel } from '../../store/inputs/model';
import type { TimelineUrl } from '../../../timelines/store/timeline/model';
@ -15,13 +15,7 @@ import type { SecurityNav } from '../navigation/types';
import type { UrlStateType } from './constants';
import { CONSTANTS } from './constants';
export const ALL_URL_STATE_KEYS: KeyUrlState[] = [
CONSTANTS.appQuery,
CONSTANTS.filters,
CONSTANTS.savedQuery,
CONSTANTS.timerange,
CONSTANTS.timeline,
];
export const ALL_URL_STATE_KEYS: KeyUrlState[] = [CONSTANTS.timerange, CONSTANTS.timeline];
export const isAdministration = (urlKey: UrlStateType): boolean => 'administration' === urlKey;
@ -39,9 +33,6 @@ export type LocationTypes =
| CONSTANTS.unknown;
export interface UrlState {
[CONSTANTS.appQuery]?: Query;
[CONSTANTS.filters]?: Filter[];
[CONSTANTS.savedQuery]?: string;
[CONSTANTS.timerange]: UrlInputsModel;
[CONSTANTS.timeline]: TimelineUrl;
}

View file

@ -0,0 +1,97 @@
/*
* 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 { useDispatch, useSelector } from 'react-redux';
import { useCallback, useMemo } from 'react';
import type { Filter, Query } from '@kbn/es-query';
import { useKibana } from '../../lib/kibana';
import { inputsSelectors } from '../../store';
import { inputsActions } from '../../store/inputs';
import { useInitializeUrlParam } from '../../utils/global_query_string';
import { CONSTANTS } from '../../components/url_state/constants';
export const useInitSearchBarUrlParams = () => {
const dispatch = useDispatch();
const { filterManager, savedQueries } = useKibana().services.data.query;
const getGlobalFiltersQuerySelector = useMemo(
() => inputsSelectors.globalFiltersQuerySelector(),
[]
);
const filtersFromStore = useSelector(getGlobalFiltersQuerySelector);
const onInitializeAppQueryUrlParam = useCallback(
(initialState: Query | null) => {
if (initialState != null) {
dispatch(
inputsActions.setFilterQuery({
id: 'global',
query: initialState.query,
language: initialState.language,
})
);
}
},
[dispatch]
);
const onInitializeFiltersUrlParam = useCallback(
(initialState: Filter[] | null) => {
if (initialState != null) {
filterManager.setFilters(initialState);
dispatch(
inputsActions.setSearchBarFilter({
id: 'global',
filters: initialState,
})
);
} else {
// Clear app filters and preserve pinned filters. It ensures that other App filters don't leak into security solution.
filterManager.setAppFilters(filtersFromStore);
dispatch(
inputsActions.setSearchBarFilter({
id: 'global',
filters: filterManager.getFilters(),
})
);
}
},
[filterManager, dispatch, filtersFromStore]
);
const onInitializeSavedQueryUrlParam = useCallback(
(savedQueryId: string | null) => {
if (savedQueryId != null && savedQueryId !== '') {
savedQueries.getSavedQuery(savedQueryId).then((savedQueryData) => {
const filters = savedQueryData.attributes.filters || [];
const query = savedQueryData.attributes.query;
filterManager.setFilters(filters);
dispatch(
inputsActions.setSearchBarFilter({
id: 'global',
filters,
})
);
dispatch(
inputsActions.setFilterQuery({
id: 'global',
...query,
})
);
dispatch(inputsActions.setSavedQuery({ id: 'global', savedQuery: savedQueryData }));
});
}
},
[dispatch, filterManager, savedQueries]
);
useInitializeUrlParam(CONSTANTS.appQuery, onInitializeAppQueryUrlParam);
useInitializeUrlParam(CONSTANTS.filters, onInitializeFiltersUrlParam);
useInitializeUrlParam(CONSTANTS.savedQuery, onInitializeSavedQueryUrlParam);
};

View file

@ -0,0 +1,50 @@
/*
* 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 { useSelector } from 'react-redux';
import { useMemo, useEffect } from 'react';
import type { Filter, Query } from '@kbn/es-query';
import { isEmpty } from 'lodash/fp';
import { inputsSelectors } from '../../store';
import { CONSTANTS } from '../../components/url_state/constants';
import { useUpdateUrlParam } from '../../utils/global_query_string';
export const useSyncSearchBarUrlParams = () => {
const updateSavedQueryUrlParam = useUpdateUrlParam<string>(CONSTANTS.savedQuery);
const updateAppQueryUrlParam = useUpdateUrlParam<Query>(CONSTANTS.appQuery);
const updateFilterUrlParam = useUpdateUrlParam<Filter[]>(CONSTANTS.filters);
const getGlobalQuerySelector = useMemo(() => inputsSelectors.globalQuerySelector(), []);
const getGlobalFiltersQuerySelector = useMemo(
() => inputsSelectors.globalFiltersQuerySelector(),
[]
);
const getGlobalSavedQuerySelector = useMemo(() => inputsSelectors.globalSavedQuerySelector(), []);
const query = useSelector(getGlobalQuerySelector);
const filters = useSelector(getGlobalFiltersQuerySelector);
const savedQuery = useSelector(getGlobalSavedQuerySelector);
useEffect(() => {
if (savedQuery != null && savedQuery.id !== '') {
updateSavedQueryUrlParam(savedQuery?.id ?? null);
updateAppQueryUrlParam(null);
updateFilterUrlParam(null);
} else {
updateSavedQueryUrlParam(null);
updateAppQueryUrlParam(isEmpty(query.query) ? null : query);
updateFilterUrlParam(isEmpty(filters) ? null : filters);
}
}, [
savedQuery,
query,
filters,
updateSavedQueryUrlParam,
updateAppQueryUrlParam,
updateFilterUrlParam,
]);
};

View file

@ -37,12 +37,9 @@ jest.mock('react-redux', () => {
};
});
const mockLocation = jest.fn();
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useHistory: () => mockHistory,
useLocation: () => mockLocation(),
}));
const defaultLinkInfo: LinkInfo = {
@ -95,7 +92,7 @@ describe('global query string', () => {
describe('useInitializeUrlParam', () => {
it('calls onInitialize with decoded URL param value', () => {
const urlParamKey = 'testKey';
mockLocation.mockReturnValue({ search: '?testKey=(test:(value:123))' });
window.location.search = '?testKey=(test:(value:123))';
const onInitialize = jest.fn();
@ -108,7 +105,7 @@ describe('global query string', () => {
it('deregister during unmount', () => {
const urlParamKey = 'testKey';
mockLocation.mockReturnValue({ search: "?testKey='123'" });
window.location.search = "?testKey='123'";
const { unmount } = renderHook(() => useInitializeUrlParam(urlParamKey, () => {}), {
wrapper: makeWrapper(),
@ -125,7 +122,7 @@ describe('global query string', () => {
it('calls registerUrlParam global URL param action', () => {
const urlParamKey = 'testKey';
const initialValue = 123;
mockLocation.mockReturnValue({ search: `?testKey=${initialValue}` });
window.location.search = `?testKey=${initialValue}`;
renderHook(() => useInitializeUrlParam(urlParamKey, () => {}), {
wrapper: makeWrapper(),

View file

@ -12,7 +12,7 @@ import { useCallback, useEffect, useMemo } from 'react';
import { url } from '@kbn/kibana-utils-plugin/public';
import { isEmpty, pickBy } from 'lodash/fp';
import { useHistory, useLocation } from 'react-router-dom';
import { useHistory } from 'react-router-dom';
import { useDispatch } from 'react-redux';
import {
decodeRisonUrlState,
@ -32,7 +32,7 @@ import { getLinkInfo } from '../../links';
* So it is only called when the application starts instead of on every page.
*
* @param urlParamKey Must not change.
* @param onInitialize Called once when initializing.
* @param onInitialize Called once when initializing. It must not change.
*/
export const useInitializeUrlParam = <State>(
urlParamKey: string,
@ -42,10 +42,14 @@ export const useInitializeUrlParam = <State>(
onInitialize: (state: State | null) => void
) => {
const dispatch = useDispatch();
const { search } = useLocation();
useEffect(() => {
const initialValue = getParamFromQueryString(getQueryStringFromLocation(search), urlParamKey);
// window.location.search provides the most updated representation of the url search.
// It also guarantees that we don't overwrite URL param managed outside react-router.
const initialValue = getParamFromQueryString(
getQueryStringFromLocation(window.location.search),
urlParamKey
);
dispatch(
globalUrlParamActions.registerUrlParam({