diff --git a/packages/kbn-securitysolution-autocomplete/src/field/index.tsx b/packages/kbn-securitysolution-autocomplete/src/field/index.tsx index 69408e919bb1e..a89e0a096b673 100644 --- a/packages/kbn-securitysolution-autocomplete/src/field/index.tsx +++ b/packages/kbn-securitysolution-autocomplete/src/field/index.tsx @@ -8,7 +8,7 @@ import React, { useCallback, useMemo, useState } from 'react'; import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui'; -import { IndexPatternBase, IndexPatternFieldBase } from '@kbn/es-query'; +import { DataViewBase, DataViewFieldBase } from '@kbn/es-query'; import { getGenericComboBoxProps, @@ -20,14 +20,14 @@ const AS_PLAIN_TEXT = { asPlainText: true }; interface OperatorProps { fieldInputWidth?: number; fieldTypeFilter?: string[]; - indexPattern: IndexPatternBase | undefined; + indexPattern: DataViewBase | undefined; isClearable: boolean; isDisabled: boolean; isLoading: boolean; isRequired?: boolean; - onChange: (a: IndexPatternFieldBase[]) => void; + onChange: (a: DataViewFieldBase[]) => void; placeholder: string; - selectedField: IndexPatternFieldBase | undefined; + selectedField: DataViewFieldBase | undefined; } export const FieldComponent: React.FC = ({ @@ -56,7 +56,7 @@ export const FieldComponent: React.FC = ({ const handleValuesChange = useCallback( (newOptions: EuiComboBoxOptionOption[]): void => { - const newValues: IndexPatternFieldBase[] = newOptions.map( + const newValues: DataViewFieldBase[] = newOptions.map( ({ label }) => availableFields[labels.indexOf(label)] ); onChange(newValues); @@ -94,13 +94,13 @@ export const FieldComponent: React.FC = ({ FieldComponent.displayName = 'Field'; interface ComboBoxFields { - availableFields: IndexPatternFieldBase[]; - selectedFields: IndexPatternFieldBase[]; + availableFields: DataViewFieldBase[]; + selectedFields: DataViewFieldBase[]; } const getComboBoxFields = ( - indexPattern: IndexPatternBase | undefined, - selectedField: IndexPatternFieldBase | undefined, + indexPattern: DataViewBase | undefined, + selectedField: DataViewFieldBase | undefined, fieldTypeFilter: string[] ): ComboBoxFields => { const existingFields = getExistingFields(indexPattern); @@ -113,29 +113,27 @@ const getComboBoxFields = ( const getComboBoxProps = (fields: ComboBoxFields): GetGenericComboBoxPropsReturn => { const { availableFields, selectedFields } = fields; - return getGenericComboBoxProps({ + return getGenericComboBoxProps({ getLabel: (field) => field.name, options: availableFields, selectedOptions: selectedFields, }); }; -const getExistingFields = (indexPattern: IndexPatternBase | undefined): IndexPatternFieldBase[] => { +const getExistingFields = (indexPattern: DataViewBase | undefined): DataViewFieldBase[] => { return indexPattern != null ? indexPattern.fields : []; }; -const getSelectedFields = ( - selectedField: IndexPatternFieldBase | undefined -): IndexPatternFieldBase[] => { +const getSelectedFields = (selectedField: DataViewFieldBase | undefined): DataViewFieldBase[] => { return selectedField ? [selectedField] : []; }; const getAvailableFields = ( - existingFields: IndexPatternFieldBase[], - selectedFields: IndexPatternFieldBase[], + existingFields: DataViewFieldBase[], + selectedFields: DataViewFieldBase[], fieldTypeFilter: string[] -): IndexPatternFieldBase[] => { - const fieldsByName = new Map(); +): DataViewFieldBase[] => { + const fieldsByName = new Map(); existingFields.forEach((f) => fieldsByName.set(f.name, f)); selectedFields.forEach((f) => fieldsByName.set(f.name, f)); diff --git a/x-pack/plugins/security_solution/common/detection_engine/get_query_filter.ts b/x-pack/plugins/security_solution/common/detection_engine/get_query_filter.ts index 033e979d2814c..42c10614975eb 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/get_query_filter.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/get_query_filter.ts @@ -11,7 +11,7 @@ import type { CreateExceptionListItemSchema, } from '@kbn/securitysolution-io-ts-list-types'; import { buildExceptionFilter } from '@kbn/securitysolution-list-utils'; -import { Filter, EsQueryConfig, IndexPatternBase, buildEsQuery } from '@kbn/es-query'; +import { Filter, EsQueryConfig, DataViewBase, buildEsQuery } from '@kbn/es-query'; import { ESBoolQuery } from '../typed_json'; import { Query, Index, TimestampOverrideOrUndefined } from './schemas/common/schemas'; @@ -24,7 +24,7 @@ export const getQueryFilter = ( lists: Array, excludeExceptions: boolean = true ): ESBoolQuery => { - const indexPattern: IndexPatternBase = { + const indexPattern: DataViewBase = { fields: [], title: index.join(), }; diff --git a/x-pack/plugins/security_solution/common/search_strategy/index_fields/index.ts b/x-pack/plugins/security_solution/common/search_strategy/index_fields/index.ts index 8a0dad0aa2456..35fd5e81b096f 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/index_fields/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/index_fields/index.ts @@ -15,5 +15,5 @@ export { BrowserFields, EMPTY_BROWSER_FIELDS, EMPTY_DOCVALUE_FIELD, - EMPTY_INDEX_PATTERN, + EMPTY_INDEX_FIELDS, } from '../../../../timelines/common'; diff --git a/x-pack/plugins/security_solution/common/types/timeline/index.ts b/x-pack/plugins/security_solution/common/types/timeline/index.ts index 3dff7ed5cff28..60fd126e6fd85 100644 --- a/x-pack/plugins/security_solution/common/types/timeline/index.ts +++ b/x-pack/plugins/security_solution/common/types/timeline/index.ts @@ -306,7 +306,7 @@ export type SavedTimelineNote = runtimeTypes.TypeOf; +export type TimelineWithoutExternalRefs = Omit; /* * Timeline IDs diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts index 803ff4b4d0d80..033a12dd9de3e 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts @@ -9,6 +9,7 @@ import { ALERT_FLYOUT, CELL_TEXT, JSON_TEXT, TABLE_ROWS } from '../../screens/al import { expandFirstAlert, + refreshAlerts, waitForAlertsIndexToBeCreated, waitForAlertsPanelToBeLoaded, } from '../../tasks/alerts'; @@ -32,6 +33,7 @@ describe('Alert details with unmapped fields', () => { createCustomRuleActivated(getUnmappedRule()); loginAndWaitForPageWithoutDateRange(ALERTS_URL); waitForAlertsPanelToBeLoaded(); + refreshAlerts(); expandFirstAlert(); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/ml/ml_conditional_links.spec.ts b/x-pack/plugins/security_solution/cypress/integration/ml/ml_conditional_links.spec.ts index 5bda14441cd80..f9d78ba12a5ea 100644 --- a/x-pack/plugins/security_solution/cypress/integration/ml/ml_conditional_links.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/ml/ml_conditional_links.spec.ts @@ -98,7 +98,7 @@ describe('ml conditional links', () => { loginAndWaitForPageWithoutDateRange(mlNetworkSingleIpNullKqlQuery); cy.url().should( 'include', - '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,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)))' ); }); @@ -106,7 +106,7 @@ describe('ml conditional links', () => { loginAndWaitForPageWithoutDateRange(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,selectedPatterns:!(%27auditbeat-*%27)))' + '/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)))' ); }); @@ -114,7 +114,7 @@ describe('ml conditional links', () => { loginAndWaitForPageWithoutDateRange(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,selectedPatterns:!(%27auditbeat-*%27)))' + '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)))' ); }); @@ -122,7 +122,7 @@ describe('ml conditional links', () => { loginAndWaitForPageWithoutDateRange(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,selectedPatterns:!(%27auditbeat-*%27)))' + '/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)))' ); }); @@ -130,7 +130,7 @@ describe('ml conditional links', () => { loginAndWaitForPageWithoutDateRange(mlNetworkNullKqlQuery); cy.url().should( 'include', - '/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,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)))' ); }); @@ -139,7 +139,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,selectedPatterns:!(%27auditbeat-*%27)))` + `/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)))` ); }); @@ -147,7 +147,7 @@ describe('ml conditional links', () => { loginAndWaitForPageWithoutDateRange(mlHostSingleHostNullKqlQuery); cy.url().should( 'include', - '/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,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)))' ); }); @@ -155,7 +155,7 @@ describe('ml conditional links', () => { loginAndWaitForPageWithoutDateRange(mlHostSingleHostKqlQueryVariable); cy.url().should( 'include', - '/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,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)))' ); }); @@ -163,7 +163,7 @@ describe('ml conditional links', () => { loginAndWaitForPageWithoutDateRange(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,selectedPatterns:!(%27auditbeat-*%27)))' + '/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)))' ); }); @@ -171,7 +171,7 @@ describe('ml conditional links', () => { loginAndWaitForPageWithoutDateRange(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,selectedPatterns:!(%27auditbeat-*%27)))' + '/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)))' ); }); @@ -179,7 +179,7 @@ describe('ml conditional links', () => { loginAndWaitForPageWithoutDateRange(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,selectedPatterns:!(%27auditbeat-*%27)))' + '/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)))' ); }); @@ -187,7 +187,7 @@ describe('ml conditional links', () => { loginAndWaitForPageWithoutDateRange(mlHostVariableHostNullKqlQuery); cy.url().should( 'include', - '/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,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)))' ); }); @@ -195,7 +195,7 @@ describe('ml conditional links', () => { loginAndWaitForPageWithoutDateRange(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,selectedPatterns:!(%27auditbeat-*%27)))' + '/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)))' ); }); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/urls/state.spec.ts b/x-pack/plugins/security_solution/cypress/integration/urls/state.spec.ts index a6303c3b9b04d..28fe1294e6f01 100644 --- a/x-pack/plugins/security_solution/cypress/integration/urls/state.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/urls/state.spec.ts @@ -185,7 +185,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')&sourcerer=(default:(id:security-solution,selectedPatterns:!('auditbeat-*')))&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')))` + `/app/security/network?query=(language:kuery,query:'source.ip:%20%2210.142.0.9%22%20')&sourcerer=(default:(id:security-solution-default,selectedPatterns:!('auditbeat-*')))&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')))` ); }); @@ -198,12 +198,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')&sourcerer=(default:(id:security-solution,selectedPatterns:!('auditbeat-*')))&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')))` + `/app/security/hosts?query=(language:kuery,query:'host.name:%20%22siem-kibana%22%20')&sourcerer=(default:(id:security-solution-default,selectedPatterns:!('auditbeat-*')))&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')))` ); cy.get(NETWORK).should( 'have.attr', 'href', - `/app/security/network?query=(language:kuery,query:'host.name:%20%22siem-kibana%22%20')&sourcerer=(default:(id:security-solution,selectedPatterns:!('auditbeat-*')))&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')))` + `/app/security/network?query=(language:kuery,query:'host.name:%20%22siem-kibana%22%20')&sourcerer=(default:(id:security-solution-default,selectedPatterns:!('auditbeat-*')))&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')))` ); cy.get(HOSTS_NAMES).first().should('have.text', 'siem-kibana'); @@ -214,21 +214,21 @@ describe('url state', () => { cy.get(ANOMALIES_TAB).should( 'have.attr', 'href', - "/app/security/hosts/siem-kibana/anomalies?sourcerer=(default:(id:security-solution,selectedPatterns:!('auditbeat-*')))&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')))&query=(language:kuery,query:'agent.type:%20%22auditbeat%22%20')" + "/app/security/hosts/siem-kibana/anomalies?sourcerer=(default:(id:security-solution-default,selectedPatterns:!('auditbeat-*')))&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')))&query=(language:kuery,query:'agent.type:%20%22auditbeat%22%20')" ); cy.get(BREADCRUMBS) .eq(1) .should( 'have.attr', 'href', - `/app/security/hosts?query=(language:kuery,query:'agent.type:%20%22auditbeat%22%20')&sourcerer=(default:(id:security-solution,selectedPatterns:!('auditbeat-*')))&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')))` + `/app/security/hosts?query=(language:kuery,query:'agent.type:%20%22auditbeat%22%20')&sourcerer=(default:(id:security-solution-default,selectedPatterns:!('auditbeat-*')))&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')))` ); cy.get(BREADCRUMBS) .eq(2) .should( 'have.attr', 'href', - `/app/security/hosts/siem-kibana?query=(language:kuery,query:'agent.type:%20%22auditbeat%22%20')&sourcerer=(default:(id:security-solution,selectedPatterns:!('auditbeat-*')))&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')))` + `/app/security/hosts/siem-kibana?query=(language:kuery,query:'agent.type:%20%22auditbeat%22%20')&sourcerer=(default:(id:security-solution-default,selectedPatterns:!('auditbeat-*')))&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')))` ); }); diff --git a/x-pack/plugins/security_solution/cypress/tasks/alerts.ts b/x-pack/plugins/security_solution/cypress/tasks/alerts.ts index 220c0fbdaa4cf..f4b84b83284db 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/alerts.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/alerts.ts @@ -102,6 +102,12 @@ export const goToOpenedAlerts = () => { cy.get(LOADING_INDICATOR).should('not.exist'); }; +export const refreshAlerts = () => { + // ensure we've refetched fields the first time index is defined + cy.get(REFRESH_BUTTON).should('have.text', 'Refresh'); + cy.get(REFRESH_BUTTON).first().click({ force: true }); +}; + export const openFirstAlert = () => { cy.get(TIMELINE_CONTEXT_MENU_BTN).first().click({ force: true }); cy.get(OPEN_ALERT_BTN).click(); diff --git a/x-pack/plugins/security_solution/public/app/home/index.tsx b/x-pack/plugins/security_solution/public/app/home/index.tsx index e7f825691c58d..3a02adc155e6e 100644 --- a/x-pack/plugins/security_solution/public/app/home/index.tsx +++ b/x-pack/plugins/security_solution/public/app/home/index.tsx @@ -16,8 +16,8 @@ import { UseUrlState } from '../../common/components/url_state'; import { navTabs } from './home_navigations'; import { useInitSourcerer, - useSourcererScope, getScopeFromPath, + useSourcererDataView, } from '../../common/containers/sourcerer'; import { useUpgradeSecurityPackages } from '../../common/hooks/use_upgrade_security_packages'; import { GlobalHeader } from './global_header'; @@ -38,8 +38,7 @@ const HomePageComponent: React.FC = ({ useInitSourcerer(getScopeFromPath(pathname)); - const { browserFields, indexPattern } = useSourcererScope(getScopeFromPath(pathname)); - + const { browserFields, indexPattern } = useSourcererDataView(getScopeFromPath(pathname)); // side effect: this will attempt to upgrade the endpoint package if it is not up to date // this will run when a user navigates to the Security Solution app and when they navigate between // tabs in the app. This is useful for keeping the endpoint package as up to date as possible until diff --git a/x-pack/plugins/security_solution/public/app/home/template_wrapper/bottom_bar/index.tsx b/x-pack/plugins/security_solution/public/app/home/template_wrapper/bottom_bar/index.tsx index 8e972b92c2fa1..c283bb10c7928 100644 --- a/x-pack/plugins/security_solution/public/app/home/template_wrapper/bottom_bar/index.tsx +++ b/x-pack/plugins/security_solution/public/app/home/template_wrapper/bottom_bar/index.tsx @@ -8,25 +8,23 @@ /* eslint-disable react/display-name */ import React from 'react'; -import { useLocation } from 'react-router-dom'; import { KibanaPageTemplateProps } from '../../../../../../../../src/plugins/kibana_react/public'; import { AppLeaveHandler } from '../../../../../../../../src/core/public'; import { useShowTimeline } from '../../../../common/utils/timeline/use_show_timeline'; -import { useSourcererScope, getScopeFromPath } from '../../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../../../common/containers/sourcerer'; import { TimelineId } from '../../../../../common/types/timeline'; import { AutoSaveWarningMsg } from '../../../../timelines/components/timeline/auto_save_warning'; import { Flyout } from '../../../../timelines/components/flyout'; import { useResolveRedirect } from '../../../../common/hooks/use_resolve_redirect'; +import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; export const BOTTOM_BAR_CLASSNAME = 'timeline-bottom-bar'; export const SecuritySolutionBottomBar = React.memo( ({ onAppLeave }: { onAppLeave: (handler: AppLeaveHandler) => void }) => { - const { pathname } = useLocation(); - const [showTimeline] = useShowTimeline(); - const { indicesExist } = useSourcererScope(getScopeFromPath(pathname)); + const { indicesExist } = useSourcererDataView(SourcererScopeName.timeline); useResolveRedirect(); return indicesExist && showTimeline ? ( diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/helpers.ts b/x-pack/plugins/security_solution/public/cases/components/case_view/helpers.ts index 94813862a983a..bfaa5fb7652d4 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_view/helpers.ts +++ b/x-pack/plugins/security_solution/public/cases/components/case_view/helpers.ts @@ -7,7 +7,7 @@ import { isObject, get, isString, isNumber } from 'lodash'; import { useMemo } from 'react'; -import { useSourcererScope } from '../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../../common/containers/sourcerer'; import { SourcererScopeName } from '../../../common/store/sourcerer/model'; import { useQueryAlerts } from '../../../detections/containers/detection_engine/alerts/use_query'; import { Ecs } from '../../../../../cases/common'; @@ -102,7 +102,7 @@ export interface Alert { [key: string]: unknown; } export const useFetchAlertData = (alertIds: string[]): [boolean, Record] => { - const { selectedPatterns } = useSourcererScope(SourcererScopeName.detections); + const { selectedPatterns } = useSourcererDataView(SourcererScopeName.detections); const alertsQuery = useMemo(() => buildAlertsQuery(alertIds), [alertIds]); const { loading: isLoadingAlerts, data: alertsData } = useQueryAlerts({ diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx index 778578702ee79..fc65d5c370bae 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx @@ -21,7 +21,7 @@ import { SecurityPageName } from '../../../app/types'; import { useKibana } from '../../../common/lib/kibana'; import { APP_UI_ID } from '../../../../common/constants'; import { timelineActions } from '../../../timelines/store/timeline'; -import { useSourcererScope } from '../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../../common/containers/sourcerer'; import { SourcererScopeName } from '../../../common/store/sourcerer/model'; import { DetailsPanel } from '../../../timelines/components/side_panel'; import { InvestigateInTimelineAction } from '../../../detections/components/alerts_table/timeline_actions/investigate_in_timeline_action'; @@ -53,10 +53,9 @@ export interface CaseProps extends Props { } const TimelineDetailsPanel = () => { - const { browserFields, docValueFields, runtimeMappings } = useSourcererScope( + const { browserFields, docValueFields, runtimeMappings } = useSourcererDataView( SourcererScopeName.detections ); - return ( { isObjectArray: false, name: 'message', originalValue: ['Endpoint network event'], + readFromDocValues: false, searchable: true, type: 'string', values: ['Endpoint network event'], @@ -117,6 +119,7 @@ describe('FieldValueCell', () => { format: '', indexes: ['auditbeat-*', 'filebeat-*', 'logs-*', 'winlogbeat-*'], name: 'message', + readFromDocValues: false, searchable: true, type: 'string', }; @@ -156,6 +159,7 @@ describe('FieldValueCell', () => { format: '', indexes: ['auditbeat-*', 'filebeat-*', 'logs-*', 'winlogbeat-*'], name: 'host.ip', + readFromDocValues: false, searchable: true, type: 'ip', }; diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx index a5ab4e537d1b7..a611d140f61d1 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx @@ -16,7 +16,7 @@ import { mockEventViewerResponse, mockEventViewerResponseWithEvents } from './mo import { StatefulEventsViewer } from '.'; import { EventsViewer } from './events_viewer'; import { defaultHeaders } from './default_headers'; -import { useSourcererScope } from '../../containers/sourcerer'; +import { useSourcererDataView } from '../../containers/sourcerer'; import { mockBrowserFields, mockDocValueFields, @@ -95,7 +95,7 @@ jest.mock('../../../timelines/containers', () => ({ jest.mock('../../components/url_state/normalize_time_range.ts'); -const mockUseSourcererScope: jest.Mock = useSourcererScope as jest.Mock; +const mockUseSourcererDataView: jest.Mock = useSourcererDataView as jest.Mock; jest.mock('../../containers/sourcerer'); const mockUseResizeObserver: jest.Mock = useResizeObserver as jest.Mock; @@ -175,7 +175,7 @@ describe('EventsViewer', () => { mockUseTimelineEvents.mockReset(); }); beforeAll(() => { - mockUseSourcererScope.mockImplementation(() => defaultMocks); + mockUseSourcererDataView.mockImplementation(() => defaultMocks); }); describe('event details', () => { @@ -284,7 +284,7 @@ describe('EventsViewer', () => { describe('loading', () => { beforeAll(() => { - mockUseSourcererScope.mockImplementation(() => ({ ...defaultMocks, loading: true })); + mockUseSourcererDataView.mockImplementation(() => ({ ...defaultMocks, loading: true })); }); beforeEach(() => { mockUseTimelineEvents.mockReturnValue([false, mockEventViewerResponse]); diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx index 6a09a6df81e6f..5a3aa2e6dc38a 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx @@ -11,6 +11,7 @@ import styled from 'styled-components'; import deepEqual from 'fast-deep-equal'; import { useDispatch } from 'react-redux'; +import { DataViewBase, Filter, Query } from '@kbn/es-query'; import { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { Direction } from '../../../../common/search_strategy'; import { BrowserFields, DocValueFields } from '../../containers/source'; @@ -31,12 +32,7 @@ import { import { TimelineRefetch } from '../../../timelines/components/timeline/refetch_timeline'; import { EventDetailsWidthProvider } from './event_details_width_context'; import * as i18n from './translations'; -import { - Filter, - esQuery, - IIndexPattern, - Query, -} from '../../../../../../../src/plugins/data/public'; +import { esQuery } from '../../../../../../../src/plugins/data/public'; import { inputsModel } from '../../store'; import { ExitFullScreen } from '../exit_full_screen'; import { useGlobalFullScreen } from '../../containers/use_full_screen'; @@ -124,7 +120,7 @@ interface Props { headerFilterGroup?: React.ReactNode; id: TimelineId; indexNames: string[]; - indexPattern: IIndexPattern; + indexPattern: DataViewBase; isLive: boolean; isLoadingIndexPattern: boolean; itemsPerPage: number; diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx index f1d53f3511c5b..5be966ab64980 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx @@ -22,7 +22,7 @@ import { InspectButtonContainer } from '../inspect'; import { useGlobalFullScreen } from '../../containers/use_full_screen'; import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features'; import { SourcererScopeName } from '../../store/sourcerer/model'; -import { useSourcererScope } from '../../containers/sourcerer'; +import { useSourcererDataView } from '../../containers/sourcerer'; import type { EntityType } from '../../../../../timelines/common'; import { TGridCellAction } from '../../../../../timelines/common/types'; import { DetailsPanel } from '../../../timelines/components/side_panel'; @@ -119,9 +119,10 @@ const StatefulEventsViewerComponent: React.FC = ({ indexPattern, runtimeMappings, selectedPatterns, - selectedDataViewId, + dataViewId: selectedDataViewId, loading: isLoadingIndexPattern, - } = useSourcererScope(scopeId); + } = useSourcererDataView(scopeId); + const { globalFullScreen } = useGlobalFullScreen(); // TODO: Once we are past experimental phase this code should be removed const tGridEnabled = useIsExperimentalFeatureEnabled('tGridEnabled'); diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx index a90ec21f992f8..37f250ffd2d4d 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx @@ -42,13 +42,13 @@ import { getCommentsArrayMock } from '../../../../../lists/common/schemas/types/ import { fields } from '../../../../../../../src/plugins/data/common/mocks'; import { ENTRIES, OLD_DATE_RELATIVE_TO_DATE_NOW } from '../../../../../lists/common/constants.mock'; import { CodeSignature } from '../../../../common/ecs/file'; -import { IndexPatternBase } from '@kbn/es-query'; +import { DataViewBase } from '@kbn/es-query'; jest.mock('uuid', () => ({ v4: jest.fn().mockReturnValue('123'), })); -const getMockIndexPattern = (): IndexPatternBase => ({ +const getMockIndexPattern = (): DataViewBase => ({ fields, id: '1234', title: 'logstash-*', @@ -364,7 +364,7 @@ describe('Exception helpers', () => { name: 'nested.field', }, ], - } as IndexPatternBase; + } as DataViewBase; test('it should return false with an empty array', () => { const payload: ExceptionListItemSchema[] = []; diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx index 58da977fcb8f0..e14c151ed367c 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx @@ -33,7 +33,7 @@ import { addIdToEntries, ExceptionsBuilderExceptionItem, } from '@kbn/securitysolution-list-utils'; -import { IndexPatternBase } from '@kbn/es-query'; +import { DataViewBase } from '@kbn/es-query'; import * as i18n from './translations'; import { AlertData, Flattened } from './types'; @@ -46,10 +46,10 @@ import exceptionableEndpointFields from './exceptionable_endpoint_fields.json'; import exceptionableEndpointEventFields from './exceptionable_endpoint_event_fields.json'; export const filterIndexPatterns = ( - patterns: IndexPatternBase, + patterns: DataViewBase, type: ExceptionListType, osTypes?: OsTypeArray -): IndexPatternBase => { +): DataViewBase => { switch (type) { case 'endpoint': const osFilterForEndpoint: (name: string) => boolean = osTypes?.includes('linux') @@ -752,7 +752,7 @@ export const getPrepopulatedBehaviorException = ({ */ export const entryHasNonEcsType = ( exceptionItems: Array, - indexPatterns: IndexPatternBase + indexPatterns: DataViewBase ): boolean => { const doesFieldNameExist = (exceptionEntry: Entry): boolean => { return indexPatterns.fields.some(({ name }) => name === exceptionEntry.field); diff --git a/x-pack/plugins/security_solution/public/common/components/hover_actions/actions/show_top_n.tsx b/x-pack/plugins/security_solution/public/common/components/hover_actions/actions/show_top_n.tsx index 738103f02dcdf..40b978cfe20fb 100644 --- a/x-pack/plugins/security_solution/public/common/components/hover_actions/actions/show_top_n.tsx +++ b/x-pack/plugins/security_solution/public/common/components/hover_actions/actions/show_top_n.tsx @@ -17,7 +17,7 @@ import { i18n } from '@kbn/i18n'; import { StatefulTopN } from '../../top_n'; import { TimelineId } from '../../../../../common/types/timeline'; import { SourcererScopeName } from '../../../store/sourcerer/model'; -import { useSourcererScope } from '../../../containers/sourcerer'; +import { useSourcererDataView } from '../../../containers/sourcerer'; import { TooltipWithKeyboardShortcut } from '../../accessibility'; import { getAdditionalScreenReaderOnlyContext } from '../utils'; import { SHOW_TOP_N_KEYBOARD_SHORTCUT } from '../keyboard_shortcut_constants'; @@ -85,7 +85,8 @@ export const ShowTopNButton: React.FC = React.memo( ) ? SourcererScopeName.detections : SourcererScopeName.default; - const { browserFields, indexPattern } = useSourcererScope(activeScope); + const { browserFields, indexPattern } = useSourcererDataView(activeScope); + const icon = iconType ?? 'visBarVertical'; const side = iconSide ?? 'left'; const buttonTitle = title ?? SHOW_TOP(field); diff --git a/x-pack/plugins/security_solution/public/common/components/hover_actions/use_hover_action_items.test.tsx b/x-pack/plugins/security_solution/public/common/components/hover_actions/use_hover_action_items.test.tsx index 0abcbefc71954..1f6436f59602c 100644 --- a/x-pack/plugins/security_solution/public/common/components/hover_actions/use_hover_action_items.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/hover_actions/use_hover_action_items.test.tsx @@ -13,7 +13,7 @@ import { DataProvider } from '../../../../common/types/timeline'; jest.mock('../../lib/kibana'); jest.mock('../../hooks/use_selector'); jest.mock('../../containers/sourcerer', () => ({ - useSourcererScope: jest.fn().mockReturnValue({ browserFields: {} }), + useSourcererDataView: jest.fn().mockReturnValue({ browserFields: {} }), })); describe('useHoverActionItems', () => { diff --git a/x-pack/plugins/security_solution/public/common/components/hover_actions/use_hover_action_items.tsx b/x-pack/plugins/security_solution/public/common/components/hover_actions/use_hover_action_items.tsx index 61cef7930a3a5..44321d6846769 100644 --- a/x-pack/plugins/security_solution/public/common/components/hover_actions/use_hover_action_items.tsx +++ b/x-pack/plugins/security_solution/public/common/components/hover_actions/use_hover_action_items.tsx @@ -17,7 +17,7 @@ import { allowTopN } from '../drag_and_drop/helpers'; import { useDeepEqualSelector } from '../../hooks/use_selector'; import { ColumnHeaderOptions, DataProvider, TimelineId } from '../../../../common/types/timeline'; import { SourcererScopeName } from '../../store/sourcerer/model'; -import { useSourcererScope } from '../../containers/sourcerer'; +import { useSourcererDataView } from '../../containers/sourcerer'; import { timelineSelectors } from '../../../timelines/store/timeline'; import { ShowTopNButton } from './actions/show_top_n'; import { FilterManager } from '../../../../../../../src/plugins/data/public'; @@ -116,7 +116,7 @@ export const useHoverActionItems = ({ ) ? SourcererScopeName.detections : SourcererScopeName.default; - const { browserFields } = useSourcererScope(activeScope); + const { browserFields } = useSourcererDataView(activeScope); /* * In the case of `DisableOverflowButton`, we show filters only when topN is NOT opened. As after topN button is clicked, the chart panel replace current hover actions in the hover actions' popover, so we have to hide all the actions. diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/helpers.ts b/x-pack/plugins/security_solution/public/common/components/navigation/helpers.ts index 2674150ff5d1c..610a3c086fc2c 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/helpers.ts +++ b/x-pack/plugins/security_solution/public/common/components/navigation/helpers.ts @@ -20,7 +20,7 @@ import { import { Query, Filter } from '../../../../../../../src/plugins/data/public'; import { SearchNavTab } from './types'; -import { SourcererScopePatterns } from '../../store/sourcerer/model'; +import { SourcererUrlState } from '../../store/sourcerer/model'; export const getSearch = (tab: SearchNavTab, urlState: UrlState): string => { if (tab && tab.urlKey != null && !isAdministration(tab.urlKey)) { @@ -29,7 +29,7 @@ export const getSearch = (tab: SearchNavTab, urlState: UrlState): string => { let urlStateToReplace: | Filter[] | Query - | SourcererScopePatterns + | SourcererUrlState | TimelineUrl | UrlInputsModel | string = ''; diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/types.ts b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/types.ts index c99d50698db2b..9a066037b2768 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/types.ts @@ -7,7 +7,7 @@ import { UrlInputsModel } from '../../../store/inputs/model'; import { CONSTANTS } from '../../url_state/constants'; -import { SourcererScopePatterns } from '../../../store/sourcerer/model'; +import { SourcererUrlState } from '../../../store/sourcerer/model'; import { TimelineUrl } from '../../../../timelines/store/timeline/model'; import { Filter, Query } from '../../../../../../../../src/plugins/data/public'; @@ -21,7 +21,7 @@ export interface TabNavigationProps extends SecuritySolutionTabNavigationProps { [CONSTANTS.appQuery]?: Query; [CONSTANTS.filters]?: Filter[]; [CONSTANTS.savedQuery]?: string; - [CONSTANTS.sourcerer]: SourcererScopePatterns; + [CONSTANTS.sourcerer]: SourcererUrlState; [CONSTANTS.timerange]: UrlInputsModel; [CONSTANTS.timeline]: TimelineUrl; } diff --git a/x-pack/plugins/security_solution/public/common/components/query_bar/index.tsx b/x-pack/plugins/security_solution/public/common/components/query_bar/index.tsx index f8738d827e73f..253f2c3862e0c 100644 --- a/x-pack/plugins/security_solution/public/common/components/query_bar/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/query_bar/index.tsx @@ -8,11 +8,9 @@ import React, { memo, useMemo, useCallback } from 'react'; import deepEqual from 'fast-deep-equal'; +import { DataViewBase, Filter, Query } from '@kbn/es-query'; import { - Filter, - IIndexPattern, FilterManager, - Query, TimeHistory, TimeRange, SavedQuery, @@ -26,7 +24,7 @@ export interface QueryBarComponentProps { dateRangeFrom?: string; dateRangeTo?: string; hideSavedQuery?: boolean; - indexPattern: IIndexPattern; + indexPattern: DataViewBase; isLoading?: boolean; isRefreshPaused?: boolean; filterQuery: Query; diff --git a/x-pack/plugins/security_solution/public/common/components/search_bar/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/search_bar/index.test.tsx index 8f82733314361..749156425bb1b 100644 --- a/x-pack/plugins/security_solution/public/common/components/search_bar/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/search_bar/index.test.tsx @@ -6,13 +6,42 @@ */ import React from 'react'; -import { mount } from 'enzyme'; - +import { render, fireEvent } from '@testing-library/react'; import { InputsModelId } from '../../store/inputs/constants'; import { SearchBarComponent } from '.'; import { TestProviders } from '../../mock'; +import { FilterManager } from '../../../../../../../src/plugins/data/public'; +import { coreMock } from '../../../../../../../src/core/public/mocks'; -jest.mock('../../lib/kibana'); +const mockFilterManager = new FilterManager(coreMock.createStart().uiSettings); +jest.mock('../../lib/kibana', () => { + const original = jest.requireActual('../../lib/kibana'); + return { + useKibana: () => ({ + services: { + ...original.useKibana().services, + data: { + ...original.useKibana().services.data, + query: { + ...original.useKibana().services.data.query, + filterManager: mockFilterManager, + }, + ui: { + SearchBar: jest.fn().mockImplementation((props) => ( + + )), + }, + }, + }, + }), + }; +}); describe('SearchBarComponent', () => { const props = { @@ -37,9 +66,38 @@ describe('SearchBarComponent', () => { savedQuery: undefined, }; + const pollForSignalIndex = jest.fn(); + beforeEach(() => { + jest.resetAllMocks(); + }); + it('calls setSearchBarFilter on mount', () => { - mount(, { wrappingComponent: TestProviders }); + render( + + + + ); expect(props.setSearchBarFilter).toHaveBeenCalled(); }); + + it('calls pollForSignalIndex on Refresh button click', () => { + const { getByTestId } = render( + + + + ); + fireEvent.click(getByTestId('querySubmitButton')); + expect(pollForSignalIndex).toHaveBeenCalled(); + }); + + it('does not call pollForSignalIndex on Refresh button click if pollForSignalIndex not passed', () => { + const { getByTestId } = render( + + + + ); + fireEvent.click(getByTestId('querySubmitButton')); + expect(pollForSignalIndex).not.toHaveBeenCalled(); + }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/search_bar/index.tsx b/x-pack/plugins/security_solution/public/common/components/search_bar/index.tsx index baaffdf239dc8..9531443e98514 100644 --- a/x-pack/plugins/security_solution/public/common/components/search_bar/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/search_bar/index.tsx @@ -13,14 +13,9 @@ import { Dispatch } from 'redux'; import { Subscription } from 'rxjs'; import styled from 'styled-components'; import deepEqual from 'fast-deep-equal'; -import { - FilterManager, - IIndexPattern, - TimeRange, - Query, - Filter, - SavedQuery, -} from 'src/plugins/data/public'; + +import { DataViewBase, Filter, Query } from '@kbn/es-query'; +import { FilterManager, TimeRange, SavedQuery } from 'src/plugins/data/public'; import { OnTimeChangeProps } from '@elastic/eui'; @@ -48,7 +43,8 @@ const APP_STATE_STORAGE_KEY = 'securitySolution.searchBar.appState'; interface SiemSearchBarProps { id: InputsModelId; - indexPattern: IIndexPattern; + indexPattern: DataViewBase; + pollForSignalIndex?: () => void; timelineId?: string; dataTestSubj?: string; } @@ -67,6 +63,7 @@ export const SearchBarComponent = memo( id, indexPattern, isLoading = false, + pollForSignalIndex, queries, savedQuery, setSavedQuery, @@ -100,6 +97,11 @@ export const SearchBarComponent = memo( const onQuerySubmit = useCallback( (payload: { dateRange: TimeRange; query?: Query }) => { + // if the function is there, call it to check if the signals index exists yet + // in order to update the index fields + if (pollForSignalIndex != null) { + pollForSignalIndex(); + } const isQuickSelection = payload.dateRange.from.includes('now') || payload.dateRange.to.includes('now'); let updateSearchBar: UpdateReduxSearchBar = { @@ -144,7 +146,18 @@ export const SearchBarComponent = memo( window.setTimeout(() => updateSearch(updateSearchBar), 0); }, - [id, toStr, end, fromStr, start, filterManager, filterQuery, queries, updateSearch] + [ + id, + pollForSignalIndex, + toStr, + end, + fromStr, + start, + filterManager, + filterQuery, + queries, + updateSearch, + ] ); const onRefresh = useCallback( diff --git a/x-pack/plugins/security_solution/public/common/components/sourcerer/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/sourcerer/index.test.tsx index dcc6998561f06..1b23d23c5eb62 100644 --- a/x-pack/plugins/security_solution/public/common/components/sourcerer/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/sourcerer/index.test.tsx @@ -123,12 +123,14 @@ describe('Sourcerer component', () => { sourcerer: { ...state.sourcerer, defaultDataView: { + ...state.sourcerer.defaultDataView, id: '1234', title: 'filebeat-*,auditbeat-*,auditbeat-*,auditbeat-*,auditbeat-*', patternList: ['filebeat-*', 'auditbeat-*'], }, kibanaDataViews: [ { + ...state.sourcerer.defaultDataView, id: '1234', title: 'filebeat-*,auditbeat-*,auditbeat-*,auditbeat-*,auditbeat-*', patternList: ['filebeat-*', 'auditbeat-*'], @@ -172,12 +174,14 @@ describe('Sourcerer component', () => { sourcerer: { ...state.sourcerer, defaultDataView: { + ...state.sourcerer.defaultDataView, id: '1234', title: 'filebeat-*,auditbeat-*,fakebeat-*', patternList: ['filebeat-*', 'auditbeat-*'], }, kibanaDataViews: [ { + ...state.sourcerer.defaultDataView, id: '1234', title: 'filebeat-*,auditbeat-*,fakebeat-*', patternList: ['filebeat-*', 'auditbeat-*'], @@ -222,8 +226,18 @@ describe('Sourcerer component', () => { ...mockGlobalState.sourcerer, kibanaDataViews: [ state.sourcerer.defaultDataView, - { id: '1234', title: 'auditbeat-*', patternList: ['auditbeat-*'] }, - { id: '12347', title: 'packetbeat-*', patternList: ['packetbeat-*'] }, + { + ...state.sourcerer.defaultDataView, + id: '1234', + title: 'auditbeat-*', + patternList: ['auditbeat-*'], + }, + { + ...state.sourcerer.defaultDataView, + id: '12347', + title: 'packetbeat-*', + patternList: ['packetbeat-*'], + }, ], sourcererScopes: { ...mockGlobalState.sourcerer.sourcererScopes, @@ -259,8 +273,18 @@ describe('Sourcerer component', () => { ...mockGlobalState.sourcerer, kibanaDataViews: [ state.sourcerer.defaultDataView, - { id: '1234', title: 'auditbeat-*', patternList: ['auditbeat-*'] }, - { id: '12347', title: 'packetbeat-*', patternList: ['packetbeat-*'] }, + { + ...state.sourcerer.defaultDataView, + id: '1234', + title: 'auditbeat-*', + patternList: ['auditbeat-*'], + }, + { + ...state.sourcerer.defaultDataView, + id: '12347', + title: 'packetbeat-*', + patternList: ['packetbeat-*'], + }, ], sourcererScopes: { ...mockGlobalState.sourcerer.sourcererScopes, @@ -297,7 +321,12 @@ describe('Sourcerer component', () => { ...state.sourcerer, kibanaDataViews: [ state.sourcerer.defaultDataView, - { id: '1234', title: 'filebeat-*', patternList: ['filebeat-*'] }, + { + ...state.sourcerer.defaultDataView, + id: '1234', + title: 'filebeat-*', + patternList: ['filebeat-*'], + }, ], sourcererScopes: { ...state.sourcerer.sourcererScopes, @@ -383,7 +412,12 @@ describe('Sourcerer component', () => { ...state.sourcerer, kibanaDataViews: [ state.sourcerer.defaultDataView, - { id: '1234', title: 'auditbeat-*', patternList: ['auditbeat-*'] }, + { + ...state.sourcerer.defaultDataView, + id: '1234', + title: 'auditbeat-*', + patternList: ['auditbeat-*'], + }, ], }, }, @@ -407,7 +441,12 @@ describe('Sourcerer component', () => { ...mockGlobalState.sourcerer, kibanaDataViews: [ state.sourcerer.defaultDataView, - { id: '1234', title: 'fakebeat-*,neatbeat-*', patternList: ['fakebeat-*'] }, + { + ...state.sourcerer.defaultDataView, + id: '1234', + title: 'fakebeat-*,neatbeat-*', + patternList: ['fakebeat-*'], + }, ], sourcererScopes: { ...mockGlobalState.sourcerer.sourcererScopes, @@ -453,8 +492,18 @@ describe('Sourcerer component', () => { ...mockGlobalState.sourcerer, kibanaDataViews: [ state.sourcerer.defaultDataView, - { id: '1234', title: 'auditbeat-*', patternList: ['auditbeat-*'] }, - { id: '12347', title: 'packetbeat-*', patternList: ['packetbeat-*'] }, + { + ...state.sourcerer.defaultDataView, + id: '1234', + title: 'auditbeat-*', + patternList: ['auditbeat-*'], + }, + { + ...state.sourcerer.defaultDataView, + id: '12347', + title: 'packetbeat-*', + patternList: ['packetbeat-*'], + }, ], sourcererScopes: { ...mockGlobalState.sourcerer.sourcererScopes, @@ -488,8 +537,18 @@ describe('Sourcerer component', () => { ...mockGlobalState.sourcerer, kibanaDataViews: [ state.sourcerer.defaultDataView, - { id: '1234', title: 'auditbeat-*', patternList: ['auditbeat-*'] }, - { id: '12347', title: 'packetbeat-*', patternList: ['packetbeat-*'] }, + { + ...state.sourcerer.defaultDataView, + id: '1234', + title: 'auditbeat-*', + patternList: ['auditbeat-*'], + }, + { + ...state.sourcerer.defaultDataView, + id: '12347', + title: 'packetbeat-*', + patternList: ['packetbeat-*'], + }, ], sourcererScopes: { ...mockGlobalState.sourcerer.sourcererScopes, diff --git a/x-pack/plugins/security_solution/public/common/components/sourcerer/index.tsx b/x-pack/plugins/security_solution/public/common/components/sourcerer/index.tsx index 1e43797e1c3ee..6f32282c53040 100644 --- a/x-pack/plugins/security_solution/public/common/components/sourcerer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/sourcerer/index.tsx @@ -22,14 +22,13 @@ import { } from '@elastic/eui'; import deepEqual from 'fast-deep-equal'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; +import { useDispatch } from 'react-redux'; import styled from 'styled-components'; import * as i18n from './translations'; -import { sourcererActions, sourcererModel } from '../../store/sourcerer'; -import { State } from '../../store'; -import { getSourcererScopeSelector, SourcererScopeSelector } from './selectors'; +import { sourcererActions, sourcererModel, sourcererSelectors } from '../../store/sourcerer'; import { getScopePatternListSelection } from '../../store/sourcerer/helpers'; +import { useDeepEqualSelector } from '../../hooks/use_selector'; import { SourcererScopeName } from '../../store/sourcerer/model'; const PopoverContent = styled.div` @@ -50,12 +49,14 @@ const getPatternListWithoutSignals = ( export const Sourcerer = React.memo(({ scope: scopeId }) => { const dispatch = useDispatch(); - const sourcererScopeSelector = useMemo(getSourcererScopeSelector, []); - const { defaultDataView, kibanaDataViews, signalIndexName, sourcererScope } = useSelector< - State, - SourcererScopeSelector - >((state) => sourcererScopeSelector(state, scopeId), deepEqual); - const { selectedDataViewId, selectedPatterns, loading } = sourcererScope; + const sourcererScopeSelector = useMemo(() => sourcererSelectors.getSourcererScopeSelector(), []); + const { + defaultDataView, + kibanaDataViews, + signalIndexName, + sourcererScope: { selectedDataViewId, selectedPatterns, loading }, + } = useDeepEqualSelector((state) => sourcererScopeSelector(state, scopeId)); + const [isPopoverOpen, setPopoverIsOpen] = useState(false); const [dataViewId, setDataViewId] = useState(selectedDataViewId ?? defaultDataView.id); diff --git a/x-pack/plugins/security_solution/public/common/components/sourcerer/selectors.tsx b/x-pack/plugins/security_solution/public/common/components/sourcerer/selectors.tsx deleted file mode 100644 index 520bab0386bd9..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/sourcerer/selectors.tsx +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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 { State } from '../../store'; -import { sourcererSelectors } from '../../store/sourcerer'; -import { ManageScope, SourcererModel, SourcererScopeName } from '../../store/sourcerer/model'; - -export interface SourcererScopeSelector extends Omit { - sourcererScope: ManageScope; -} - -export const getSourcererScopeSelector = () => { - const getKibanaDataViewsSelector = sourcererSelectors.kibanaDataViewsSelector(); - const getDefaultDataViewSelector = sourcererSelectors.defaultDataViewSelector(); - const getSignalIndexNameSelector = sourcererSelectors.signalIndexNameSelector(); - const getScopeSelector = sourcererSelectors.scopeIdSelector(); - - return (state: State, scopeId: SourcererScopeName): SourcererScopeSelector => { - const kibanaDataViews = getKibanaDataViewsSelector(state); - const defaultDataView = getDefaultDataViewSelector(state); - const signalIndexName = getSignalIndexNameSelector(state); - const scope = getScopeSelector(state, scopeId); - - return { - defaultDataView, - kibanaDataViews, - signalIndexName, - sourcererScope: scope, - }; - }; -}; diff --git a/x-pack/plugins/security_solution/public/common/components/threat_match/entry_item.test.tsx b/x-pack/plugins/security_solution/public/common/components/threat_match/entry_item.test.tsx index 4b153f0c75775..0550ef389585e 100644 --- a/x-pack/plugins/security_solution/public/common/components/threat_match/entry_item.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/threat_match/entry_item.test.tsx @@ -11,7 +11,7 @@ import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui'; import { EntryItem } from './entry_item'; import { fields, getField } from '../../../../../../../src/plugins/data/common/mocks'; -import { IndexPattern } from 'src/plugins/data/public'; +import { DataViewBase } from '@kbn/es-query'; jest.mock('../../../common/lib/kibana'); @@ -31,7 +31,7 @@ describe('EntryItem', () => { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } showLabel={true} onChange={jest.fn()} @@ -40,7 +40,7 @@ describe('EntryItem', () => { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } /> ); @@ -64,14 +64,14 @@ describe('EntryItem', () => { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } threatIndexPatterns={ { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } showLabel={false} onChange={mockOnChange} @@ -111,14 +111,14 @@ describe('EntryItem', () => { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } threatIndexPatterns={ { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } showLabel={false} onChange={mockOnChange} diff --git a/x-pack/plugins/security_solution/public/common/components/threat_match/entry_item.tsx b/x-pack/plugins/security_solution/public/common/components/threat_match/entry_item.tsx index 99c5b37046505..777ef2069ff4f 100644 --- a/x-pack/plugins/security_solution/public/common/components/threat_match/entry_item.tsx +++ b/x-pack/plugins/security_solution/public/common/components/threat_match/entry_item.tsx @@ -10,16 +10,15 @@ import { EuiFormRow, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import styled from 'styled-components'; import { FieldComponent } from '@kbn/securitysolution-autocomplete'; -import { IndexPatternFieldBase } from '@kbn/es-query'; -import { IndexPattern } from '../../../../../../../src/plugins/data/common'; +import { DataViewBase, DataViewFieldBase } from '@kbn/es-query'; import { FormattedEntry, Entry } from './types'; import * as i18n from './translations'; import { getEntryOnFieldChange, getEntryOnThreatFieldChange } from './helpers'; interface EntryItemProps { entry: FormattedEntry; - indexPattern: IndexPattern; - threatIndexPatterns: IndexPattern; + indexPattern: DataViewBase; + threatIndexPatterns: DataViewBase; showLabel: boolean; onChange: (arg: Entry, i: number) => void; } @@ -41,7 +40,7 @@ export const EntryItem: React.FC = ({ onChange, }): JSX.Element => { const handleFieldChange = useCallback( - ([newField]: IndexPatternFieldBase[]): void => { + ([newField]: DataViewFieldBase[]): void => { const { updatedEntry, index } = getEntryOnFieldChange(entry, newField); onChange(updatedEntry, index); }, @@ -49,7 +48,7 @@ export const EntryItem: React.FC = ({ ); const handleThreatFieldChange = useCallback( - ([newField]: IndexPatternFieldBase[]): void => { + ([newField]: DataViewFieldBase[]): void => { const { updatedEntry, index } = getEntryOnThreatFieldChange(entry, newField); onChange(updatedEntry, index); }, diff --git a/x-pack/plugins/security_solution/public/common/components/threat_match/helpers.test.tsx b/x-pack/plugins/security_solution/public/common/components/threat_match/helpers.test.tsx index 0e5f379a18767..9a5051fe0cafb 100644 --- a/x-pack/plugins/security_solution/public/common/components/threat_match/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/threat_match/helpers.test.tsx @@ -7,7 +7,8 @@ import { fields, getField } from '../../../../../../../src/plugins/data/common/mocks'; import { Entry, EmptyEntry, ThreatMapEntries, FormattedEntry } from './types'; -import { FieldSpec, IndexPattern } from '../../../../../../../src/plugins/data/common'; +import { FieldSpec } from '../../../../../../../src/plugins/data/common'; +import { DataViewBase } from '@kbn/es-query'; import moment from 'moment-timezone'; import { @@ -24,12 +25,12 @@ jest.mock('uuid', () => ({ v4: jest.fn().mockReturnValue('123'), })); -const getMockIndexPattern = (): IndexPattern => +const getMockIndexPattern = (): DataViewBase => ({ id: '1234', title: 'logstash-*', fields, - } as IndexPattern); + } as DataViewBase); const getMockEntry = (): FormattedEntry => ({ id: '123', @@ -51,7 +52,7 @@ describe('Helpers', () => { describe('#getFormattedEntry', () => { test('it returns entry with a value when "item.field" is of type "text" and matching keyword field exists', () => { - const payloadIndexPattern: IndexPattern = { + const payloadIndexPattern: DataViewBase = { ...getMockIndexPattern(), fields: [ ...fields, @@ -66,7 +67,7 @@ describe('Helpers', () => { readFromDocValues: true, }, ], - } as IndexPattern; + } as DataViewBase; const payloadItem: Entry = { field: 'machine.os.raw.text', type: 'mapping', @@ -171,7 +172,7 @@ describe('Helpers', () => { }); test('it returns formatted entries', () => { - const payloadIndexPattern: IndexPattern = getMockIndexPattern(); + const payloadIndexPattern: DataViewBase = getMockIndexPattern(); const payloadItems: Entry[] = [ { field: 'machine.os', type: 'mapping', value: 'machine.os' }, { field: 'ip', type: 'mapping', value: 'ip' }, diff --git a/x-pack/plugins/security_solution/public/common/components/threat_match/helpers.tsx b/x-pack/plugins/security_solution/public/common/components/threat_match/helpers.tsx index 3f8e5b1602db8..7b0385ab6902f 100644 --- a/x-pack/plugins/security_solution/public/common/components/threat_match/helpers.tsx +++ b/x-pack/plugins/security_solution/public/common/components/threat_match/helpers.tsx @@ -10,8 +10,7 @@ import { i18n } from '@kbn/i18n'; import { addIdToItem } from '@kbn/securitysolution-utils'; import { ThreatMap, threatMap, ThreatMapping } from '@kbn/securitysolution-io-ts-alerting-types'; -import { IndexPatternFieldBase } from '@kbn/es-query'; -import { IndexPattern } from '../../../../../../../src/plugins/data/common'; +import { DataViewBase, DataViewFieldBase } from '@kbn/es-query'; import { Entry, FormattedEntry, ThreatMapEntries, EmptyEntry } from './types'; import { ValidationFunc } from '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib'; import { ERROR_CODE } from '../../../../../../../src/plugins/es_ui_shared/static/forms/helpers/field_validators/types'; @@ -19,13 +18,13 @@ import { ERROR_CODE } from '../../../../../../../src/plugins/es_ui_shared/static /** * Formats the entry into one that is easily usable for the UI. * - * @param patterns IndexPattern containing available fields on rule index + * @param patterns DataViewBase containing available fields on rule index * @param item item entry * @param itemIndex entry index */ export const getFormattedEntry = ( - indexPattern: IndexPattern, - threatIndexPatterns: IndexPattern, + indexPattern: DataViewBase, + threatIndexPatterns: DataViewBase, item: Entry, itemIndex: number, uuidGen: () => string = uuid.v4 @@ -51,12 +50,12 @@ export const getFormattedEntry = ( /** * Formats the entries to be easily usable for the UI * - * @param patterns IndexPattern containing available fields on rule index + * @param patterns DataViewBase containing available fields on rule index * @param entries item entries */ export const getFormattedEntries = ( - indexPattern: IndexPattern, - threatIndexPatterns: IndexPattern, + indexPattern: DataViewBase, + threatIndexPatterns: DataViewBase, entries: Entry[] ): FormattedEntry[] => { return entries.reduce((acc, item, index) => { @@ -91,7 +90,7 @@ export const getUpdatedEntriesOnDelete = ( */ export const getEntryOnFieldChange = ( item: FormattedEntry, - newField: IndexPatternFieldBase + newField: DataViewFieldBase ): { updatedEntry: Entry; index: number } => { const { entryIndex } = item; return { @@ -114,7 +113,7 @@ export const getEntryOnFieldChange = ( */ export const getEntryOnThreatFieldChange = ( item: FormattedEntry, - newField: IndexPatternFieldBase + newField: DataViewFieldBase ): { updatedEntry: Entry; index: number } => { const { entryIndex } = item; return { diff --git a/x-pack/plugins/security_solution/public/common/components/threat_match/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/threat_match/index.test.tsx index 872f78d91eebb..e26c889b7e94a 100644 --- a/x-pack/plugins/security_solution/public/common/components/threat_match/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/threat_match/index.test.tsx @@ -16,7 +16,7 @@ import { useKibana } from '../../../common/lib/kibana'; import { ThreatMatchComponent } from './'; import { ThreatMapEntries } from './types'; -import { IndexPattern } from 'src/plugins/data/public'; +import { DataViewBase } from '@kbn/es-query'; import { getMockTheme } from '../../lib/kibana/kibana_react.mock'; const mockTheme = getMockTheme({ @@ -65,14 +65,14 @@ describe('ThreatMatchComponent', () => { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } threatIndexPatterns={ { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } onChange={jest.fn()} /> @@ -94,14 +94,14 @@ describe('ThreatMatchComponent', () => { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } threatIndexPatterns={ { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } onChange={jest.fn()} /> @@ -123,14 +123,14 @@ describe('ThreatMatchComponent', () => { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } threatIndexPatterns={ { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } onChange={jest.fn()} /> @@ -151,14 +151,14 @@ describe('ThreatMatchComponent', () => { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } threatIndexPatterns={ { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } onChange={jest.fn()} /> @@ -188,14 +188,14 @@ describe('ThreatMatchComponent', () => { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } threatIndexPatterns={ { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } onChange={jest.fn()} /> @@ -225,14 +225,14 @@ describe('ThreatMatchComponent', () => { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } threatIndexPatterns={ { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } onChange={jest.fn()} /> @@ -255,14 +255,14 @@ describe('ThreatMatchComponent', () => { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } threatIndexPatterns={ { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } onChange={jest.fn()} /> @@ -286,14 +286,14 @@ describe('ThreatMatchComponent', () => { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } threatIndexPatterns={ { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } onChange={jest.fn()} /> diff --git a/x-pack/plugins/security_solution/public/common/components/threat_match/index.tsx b/x-pack/plugins/security_solution/public/common/components/threat_match/index.tsx index 3e4f0283145e4..5ea17939ed780 100644 --- a/x-pack/plugins/security_solution/public/common/components/threat_match/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/threat_match/index.tsx @@ -8,10 +8,9 @@ import React, { useCallback, useEffect, useReducer } from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import styled from 'styled-components'; - +import { DataViewBase } from '@kbn/es-query'; import { ThreatMapping } from '@kbn/securitysolution-io-ts-alerting-types'; import { ListItemComponent } from './list_item'; -import { IndexPattern } from '../../../../../../../src/plugins/data/common'; import { AndOrBadge } from '../and_or_badge'; import { LogicButtons } from './logic_buttons'; import { ThreatMapEntries } from './types'; @@ -45,8 +44,8 @@ interface OnChangeProps { interface ThreatMatchComponentProps { listItems: ThreatMapEntries[]; - indexPatterns: IndexPattern; - threatIndexPatterns: IndexPattern; + indexPatterns: DataViewBase; + threatIndexPatterns: DataViewBase; onChange: (arg: OnChangeProps) => void; } diff --git a/x-pack/plugins/security_solution/public/common/components/threat_match/list_item.test.tsx b/x-pack/plugins/security_solution/public/common/components/threat_match/list_item.test.tsx index d6704b3a9cf17..e2325ace26591 100644 --- a/x-pack/plugins/security_solution/public/common/components/threat_match/list_item.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/threat_match/list_item.test.tsx @@ -14,7 +14,7 @@ import { fields } from '../../../../../../../src/plugins/data/common/mocks'; import { ListItemComponent } from './list_item'; import { ThreatMapEntries } from './types'; -import { IndexPattern } from 'src/plugins/data/public'; +import { DataViewBase } from '@kbn/es-query'; import { getMockTheme } from '../../lib/kibana/kibana_react.mock'; const mockTheme = getMockTheme({ @@ -81,14 +81,14 @@ describe('ListItemComponent', () => { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } threatIndexPatterns={ { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } andLogicIncluded={true} isOnlyItem={false} @@ -114,7 +114,7 @@ describe('ListItemComponent', () => { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } andLogicIncluded={true} isOnlyItem={false} @@ -125,7 +125,7 @@ describe('ListItemComponent', () => { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } /> @@ -145,14 +145,14 @@ describe('ListItemComponent', () => { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } threatIndexPatterns={ { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } andLogicIncluded={true} isOnlyItem={false} @@ -178,14 +178,14 @@ describe('ListItemComponent', () => { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } threatIndexPatterns={ { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } andLogicIncluded={false} isOnlyItem={false} @@ -219,14 +219,14 @@ describe('ListItemComponent', () => { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } threatIndexPatterns={ { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } andLogicIncluded={false} isOnlyItem={true} @@ -250,14 +250,14 @@ describe('ListItemComponent', () => { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } threatIndexPatterns={ { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } andLogicIncluded={false} isOnlyItem={false} @@ -281,14 +281,14 @@ describe('ListItemComponent', () => { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } threatIndexPatterns={ { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } andLogicIncluded={false} // if entryItemIndex is not 0, wouldn't make sense for @@ -314,14 +314,14 @@ describe('ListItemComponent', () => { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } threatIndexPatterns={ { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } andLogicIncluded={false} isOnlyItem={true} @@ -346,14 +346,14 @@ describe('ListItemComponent', () => { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } threatIndexPatterns={ { id: '1234', title: 'logstash-*', fields, - } as IndexPattern + } as DataViewBase } andLogicIncluded={false} isOnlyItem={true} diff --git a/x-pack/plugins/security_solution/public/common/components/threat_match/list_item.tsx b/x-pack/plugins/security_solution/public/common/components/threat_match/list_item.tsx index eda12ce3b0f5a..e01c28ecb54eb 100644 --- a/x-pack/plugins/security_solution/public/common/components/threat_match/list_item.tsx +++ b/x-pack/plugins/security_solution/public/common/components/threat_match/list_item.tsx @@ -9,7 +9,7 @@ import React, { useMemo, useCallback } from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import styled from 'styled-components'; -import { IndexPattern } from '../../../../../../../src/plugins/data/common'; +import { DataViewBase } from '@kbn/es-query'; import { getFormattedEntries, getUpdatedEntriesOnDelete } from './helpers'; import { FormattedEntry, ThreatMapEntries, Entry } from './types'; import { EntryItem } from './entry_item'; @@ -24,8 +24,8 @@ const MyOverflowContainer = styled(EuiFlexItem)` interface ListItemProps { listItem: ThreatMapEntries; listItemIndex: number; - indexPattern: IndexPattern; - threatIndexPatterns: IndexPattern; + indexPattern: DataViewBase; + threatIndexPatterns: DataViewBase; andLogicIncluded: boolean; isOnlyItem: boolean; onDeleteEntryItem: (item: ThreatMapEntries, index: number) => void; diff --git a/x-pack/plugins/security_solution/public/common/components/threat_match/types.ts b/x-pack/plugins/security_solution/public/common/components/threat_match/types.ts index 3d3a18b425e01..852e68aa259c8 100644 --- a/x-pack/plugins/security_solution/public/common/components/threat_match/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/threat_match/types.ts @@ -5,14 +5,14 @@ * 2.0. */ -import { IndexPatternFieldBase } from '@kbn/es-query'; +import { DataViewFieldBase } from '@kbn/es-query'; import { ThreatMap, ThreatMapEntry } from '@kbn/securitysolution-io-ts-alerting-types'; export interface FormattedEntry { id: string; - field: IndexPatternFieldBase | undefined; + field: DataViewFieldBase | undefined; type: 'mapping'; - value: IndexPatternFieldBase | undefined; + value: DataViewFieldBase | undefined; entryIndex: number; } diff --git a/x-pack/plugins/security_solution/public/common/components/top_n/index.tsx b/x-pack/plugins/security_solution/public/common/components/top_n/index.tsx index 1eef44d587ed0..ac9acb62c5834 100644 --- a/x-pack/plugins/security_solution/public/common/components/top_n/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/top_n/index.tsx @@ -8,15 +8,11 @@ import React, { useMemo } from 'react'; import { connect, ConnectedProps } from 'react-redux'; +import { DataViewBase, Filter, Query } from '@kbn/es-query'; import { useGlobalTime } from '../../containers/use_global_time'; import { BrowserFields } from '../../containers/source'; import { useKibana } from '../../lib/kibana'; -import { - esQuery, - Filter, - Query, - IIndexPattern, -} from '../../../../../../../src/plugins/data/public'; +import { esQuery } from '../../../../../../../src/plugins/data/public'; import { inputsModel, inputsSelectors, State } from '../../store'; import { timelineDefaults } from '../../../timelines/store/timeline/defaults'; import { timelineSelectors } from '../../../timelines/store/timeline'; @@ -77,7 +73,7 @@ const connector = connect(makeMapStateToProps); export interface OwnProps { browserFields: BrowserFields; field: string; - indexPattern: IIndexPattern; + indexPattern: DataViewBase; timelineId?: string; toggleTopN: () => void; onFilterAdded?: () => void; diff --git a/x-pack/plugins/security_solution/public/common/components/top_n/top_n.tsx b/x-pack/plugins/security_solution/public/common/components/top_n/top_n.tsx index fac43b33dbc48..9c99189e1b79e 100644 --- a/x-pack/plugins/security_solution/public/common/components/top_n/top_n.tsx +++ b/x-pack/plugins/security_solution/public/common/components/top_n/top_n.tsx @@ -11,10 +11,10 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useSelector } from 'react-redux'; import styled from 'styled-components'; +import { DataViewBase, Filter, Query } from '@kbn/es-query'; import { GlobalTimeArgs } from '../../containers/use_global_time'; import { EventsByDataset } from '../../../overview/components/events_by_dataset'; import { SignalsByCategory } from '../../../overview/components/signals_by_category'; -import { Filter, IIndexPattern, Query } from '../../../../../../../src/plugins/data/public'; import { InputsModelId } from '../../store/inputs/constants'; import { TimelineEventsType } from '../../../../common/types/timeline'; @@ -54,7 +54,7 @@ export interface Props extends Pick pageName === SecurityPageName.alerts || @@ -156,7 +156,7 @@ export const makeMapStateToProps = () => { } const sourcerer = getSourcererScopes(state); const activeScopes: SourcererScopeName[] = Object.keys(sourcerer) as SourcererScopeName[]; - const selectedPatterns: SourcererScopePatterns = activeScopes + const selectedPatterns: SourcererUrlState = activeScopes .filter((scope) => scope === SourcererScopeName.default) .reduce( (acc, scope) => ({ diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/initialize_redux_by_url.tsx b/x-pack/plugins/security_solution/public/common/components/url_state/initialize_redux_by_url.tsx index 4b181e6ef8e44..11312e942c1eb 100644 --- a/x-pack/plugins/security_solution/public/common/components/url_state/initialize_redux_by_url.tsx +++ b/x-pack/plugins/security_solution/public/common/components/url_state/initialize_redux_by_url.tsx @@ -28,7 +28,7 @@ import { queryTimelineById, dispatchUpdateTimeline, } from '../../../timelines/components/open_timeline/helpers'; -import { SourcererScopeName, SourcererScopePatterns } from '../../store/sourcerer/model'; +import { SourcererScopeName, SourcererUrlState } from '../../store/sourcerer/model'; import { timelineActions } from '../../../timelines/store/timeline'; export const useSetInitialStateFromUrl = () => { @@ -55,7 +55,7 @@ export const useSetInitialStateFromUrl = () => { updateTimerange(newUrlStateString, dispatch); } if (urlKey === CONSTANTS.sourcerer) { - const sourcererState = decodeRisonUrlState(newUrlStateString); + const sourcererState = decodeRisonUrlState(newUrlStateString); if (sourcererState != null) { const activeScopes: SourcererScopeName[] = Object.keys(sourcererState).filter( (key) => !(key === SourcererScopeName.default && isDetectionsPages(pageName)) diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/test_dependencies.ts b/x-pack/plugins/security_solution/public/common/components/url_state/test_dependencies.ts index f0bd69b21c617..f722c5ec0da00 100644 --- a/x-pack/plugins/security_solution/public/common/components/url_state/test_dependencies.ts +++ b/x-pack/plugins/security_solution/public/common/components/url_state/test_dependencies.ts @@ -84,9 +84,7 @@ export const defaultProps: UrlStateContainerPropTypes = { indexPattern: { fields: [ { - aggregatable: true, name: '@timestamp', - searchable: true, type: 'date', }, ], diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/types.ts b/x-pack/plugins/security_solution/public/common/components/url_state/types.ts index 06ed33ac69f6e..2626f4fb03c23 100644 --- a/x-pack/plugins/security_solution/public/common/components/url_state/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/url_state/types.ts @@ -5,21 +5,15 @@ * 2.0. */ -import { - Filter, - FilterManager, - IIndexPattern, - Query, - SavedQueryService, -} from 'src/plugins/data/public'; - +import { Filter, FilterManager, Query, SavedQueryService } from 'src/plugins/data/public'; +import { DataViewBase } from '@kbn/es-query'; import { UrlInputsModel } from '../../store/inputs/model'; import { TimelineUrl } from '../../../timelines/store/timeline/model'; import { RouteSpyState } from '../../utils/route/types'; import { SecurityNav } from '../navigation/types'; import { CONSTANTS, UrlStateType } from './constants'; -import { SourcererScopePatterns } from '../../store/sourcerer/model'; +import { SourcererUrlState } from '../../store/sourcerer/model'; export const ALL_URL_STATE_KEYS: KeyUrlState[] = [ CONSTANTS.appQuery, @@ -48,7 +42,7 @@ export interface UrlState { [CONSTANTS.appQuery]?: Query; [CONSTANTS.filters]?: Filter[]; [CONSTANTS.savedQuery]?: string; - [CONSTANTS.sourcerer]: SourcererScopePatterns; + [CONSTANTS.sourcerer]: SourcererUrlState; [CONSTANTS.timerange]: UrlInputsModel; [CONSTANTS.timeline]: TimelineUrl; } @@ -58,7 +52,7 @@ export type ValueUrlState = UrlState[keyof UrlState]; export interface UrlStateProps { navTabs: SecurityNav; - indexPattern?: IIndexPattern; + indexPattern?: DataViewBase; mapToUrlState?: (value: string) => UrlState; onChange?: (urlState: UrlState, previousUrlState: UrlState) => void; onInitialize?: (urlState: UrlState) => void; @@ -89,7 +83,7 @@ export interface UrlStateToRedux { export interface SetInitialStateFromUrl { filterManager: FilterManager; - indexPattern: IIndexPattern | undefined; + indexPattern: DataViewBase | undefined; pageName: string; savedQueries: SavedQueryService; urlStateToUpdate: UrlStateToRedux[]; diff --git a/x-pack/plugins/security_solution/public/common/containers/kuery_autocompletion/index.tsx b/x-pack/plugins/security_solution/public/common/containers/kuery_autocompletion/index.tsx deleted file mode 100644 index 7ed4ba6944eb7..0000000000000 --- a/x-pack/plugins/security_solution/public/common/containers/kuery_autocompletion/index.tsx +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { useState } from 'react'; -import { QuerySuggestion, IIndexPattern } from '../../../../../../../src/plugins/data/public'; -import { useKibana } from '../../lib/kibana'; - -type RendererResult = React.ReactElement | null; -type RendererFunction = (args: RenderArgs) => Result; - -interface KueryAutocompletionLifecycleProps { - children: RendererFunction<{ - isLoadingSuggestions: boolean; - loadSuggestions: (expression: string, cursorPosition: number, maxSuggestions?: number) => void; - suggestions: QuerySuggestion[]; - }>; - indexPattern: IIndexPattern; -} - -interface KueryAutocompletionCurrentRequest { - expression: string; - cursorPosition: number; -} - -export const KueryAutocompletion = React.memo( - ({ children, indexPattern }) => { - const [currentRequest, setCurrentRequest] = useState( - null - ); - const [suggestions, setSuggestions] = useState([]); - const kibana = useKibana(); - const loadSuggestions = async ( - expression: string, - cursorPosition: number, - maxSuggestions?: number - ) => { - const language = 'kuery'; - - if (!kibana.services.data.autocomplete.hasQuerySuggestions(language)) { - return; - } - - const futureRequest = { - expression, - cursorPosition, - }; - setCurrentRequest({ - expression, - cursorPosition, - }); - setSuggestions([]); - - if ( - futureRequest && - futureRequest.expression !== (currentRequest && currentRequest.expression) && - futureRequest.cursorPosition !== (currentRequest && currentRequest.cursorPosition) - ) { - const newSuggestions = - (await kibana.services.data.autocomplete.getQuerySuggestions({ - language: 'kuery', - indexPatterns: [indexPattern], - boolFilter: [], - query: expression, - selectionStart: cursorPosition, - selectionEnd: cursorPosition, - })) || []; - - setCurrentRequest(null); - setSuggestions(maxSuggestions ? newSuggestions.slice(0, maxSuggestions) : newSuggestions); - } - }; - - return children({ - isLoadingSuggestions: currentRequest !== null, - loadSuggestions, - suggestions, - }); - } -); - -KueryAutocompletion.displayName = 'KueryAutocompletion'; diff --git a/x-pack/plugins/security_solution/public/common/containers/source/index.test.tsx b/x-pack/plugins/security_solution/public/common/containers/source/index.test.tsx index d334236189f6c..865d118159f62 100644 --- a/x-pack/plugins/security_solution/public/common/containers/source/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/source/index.test.tsx @@ -6,7 +6,8 @@ */ import { IndexField } from '../../../../common/search_strategy/index_fields'; -import { getBrowserFields, useIndexFields } from '.'; +import { getBrowserFields } from '.'; +import { useDataView } from './use_data_view'; import { mockBrowserFields, mocksSource } from './mock'; import { SourcererScopeName } from '../../store/sourcerer/model'; import { createStore, State } from '../../store'; @@ -51,7 +52,7 @@ describe('source/index.tsx', () => { expect(fields).toEqual(mockBrowserFields); }); }); - describe('useIndexFields hook', () => { + describe('useDataView hook', () => { const sourcererState = mockGlobalState.sourcerer; const state: State = { ...mockGlobalState, @@ -60,6 +61,7 @@ describe('source/index.tsx', () => { kibanaDataViews: [ ...sourcererState.kibanaDataViews, { + ...sourcererState.defaultDataView, id: 'something-random', title: 'something,random', patternList: ['something', 'random'], @@ -69,24 +71,12 @@ describe('source/index.tsx', () => { ...sourcererState.sourcererScopes, [SourcererScopeName.default]: { ...sourcererState.sourcererScopes[SourcererScopeName.default], - indexPattern: { - fields: [], - title: '', - }, }, [SourcererScopeName.detections]: { ...sourcererState.sourcererScopes[SourcererScopeName.detections], - indexPattern: { - fields: [], - title: '', - }, }, [SourcererScopeName.timeline]: { ...sourcererState.sourcererScopes[SourcererScopeName.timeline], - indexPattern: { - fields: [], - title: '', - }, }, }, }, @@ -99,7 +89,7 @@ describe('source/index.tsx', () => { runtimeMappings: {}, }; const { storage } = createSecuritySolutionStorageMock(); - let store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage); + const store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage); beforeEach(() => { jest.clearAllMocks(); @@ -124,204 +114,177 @@ describe('source/index.tsx', () => { }, }); }); - it('sets source for default scope', async () => { - await act(async () => { - const { rerender, waitForNextUpdate } = renderHook( - () => useIndexFields(SourcererScopeName.default), - { - wrapper: ({ children }) => {children}, - } - ); - await waitForNextUpdate(); - rerender(); - expect(mockDispatch.mock.calls[0][0]).toEqual({ - type: 'x-pack/security_solution/local/sourcerer/SET_SOURCERER_SCOPE_LOADING', - payload: { id: SourcererScopeName.default, loading: true }, - }); - const { - type: sourceType, - payload: { payload }, - } = mockDispatch.mock.calls[1][0]; - expect(sourceType).toEqual('x-pack/security_solution/local/sourcerer/SET_SOURCE'); - expect(payload.id).toEqual(SourcererScopeName.default); - expect(payload.indicesExist).toEqual(true); - expect(payload.indexPattern.title).toEqual( - 'apm-*-transaction*,auditbeat-*,endgame-*,filebeat-*,logs-*,packetbeat-*,traces-apm*,winlogbeat-*' - ); - }); - }); - - it('sets source for detections scope', async () => { - await act(async () => { - const { rerender, waitForNextUpdate } = renderHook( - () => useIndexFields(SourcererScopeName.detections), - { - wrapper: ({ children }) => {children}, - } - ); - await waitForNextUpdate(); - rerender(); - expect(mockDispatch.mock.calls[0][0]).toEqual({ - type: 'x-pack/security_solution/local/sourcerer/SET_SOURCERER_SCOPE_LOADING', - payload: { id: SourcererScopeName.detections, loading: true }, - }); - const { - type: sourceType, - payload: { payload }, - } = mockDispatch.mock.calls[1][0]; - expect(sourceType).toEqual('x-pack/security_solution/local/sourcerer/SET_SOURCE'); - expect(payload.id).toEqual(SourcererScopeName.detections); - expect(payload.indicesExist).toEqual(true); - expect(payload.indexPattern.title).toEqual(sourcererState.signalIndexName); - }); - }); - it('sets source for timelines scope', async () => { - await act(async () => { - const { rerender, waitForNextUpdate } = renderHook( - () => useIndexFields(SourcererScopeName.timeline), - { - wrapper: ({ children }) => {children}, - } - ); - await waitForNextUpdate(); - rerender(); - expect(mockDispatch.mock.calls[0][0]).toEqual({ - type: 'x-pack/security_solution/local/sourcerer/SET_SOURCERER_SCOPE_LOADING', - payload: { id: SourcererScopeName.timeline, loading: true }, - }); - const { - type: sourceType, - payload: { payload }, - } = mockDispatch.mock.calls[1][0]; - expect(sourceType).toEqual('x-pack/security_solution/local/sourcerer/SET_SOURCE'); - expect(payload.id).toEqual(SourcererScopeName.timeline); - expect(payload.indicesExist).toEqual(true); - expect(payload.indexPattern.title).toEqual( - `${sourcererState.signalIndexName},apm-*-transaction*,auditbeat-*,endgame-*,filebeat-*,logs-*,packetbeat-*,traces-apm*,winlogbeat-*` - ); - }); - }); - it('sets source for detections scope when signalIndexName is updated', async () => { - store = createStore( - { ...state, sourcerer: { ...state.sourcerer, signalIndexName: null } }, - SUB_PLUGINS_REDUCER, - kibanaObservable, - storage - ); + it('sets field data for data view', async () => { await act(async () => { - const { result, rerender, waitForNextUpdate } = renderHook< + const { rerender, waitForNextUpdate, result } = renderHook< string, - { indexFieldsSearch: (selectedDataViewId: string, newSignalsIndex?: string) => void } - >(() => useIndexFields(SourcererScopeName.detections), { + { indexFieldsSearch: (id: string) => void } + >(() => useDataView(), { wrapper: ({ children }) => {children}, }); await waitForNextUpdate(); rerender(); - act(() => { - result.current.indexFieldsSearch( - sourcererState.defaultDataView.id, - `${sourcererState.signalIndexName}` - ); - }); - - expect(mockDispatch.mock.calls[0][0]).toEqual({ - type: 'x-pack/security_solution/local/sourcerer/SET_SOURCERER_SCOPE_LOADING', - payload: { id: SourcererScopeName.detections, loading: true }, - }); - expect(mockDispatch.mock.calls[1][0].payload.payload.indicesExist).toEqual(false); - expect(mockDispatch.mock.calls[2][0]).toEqual({ - type: 'x-pack/security_solution/local/sourcerer/SET_SOURCERER_SCOPE_LOADING', - payload: { id: SourcererScopeName.detections, loading: true }, - }); - expect(mockDispatch.mock.calls[3][0].payload.payload.indicesExist).toEqual(true); - }); - }); - it('when selectedPatterns=[], defaults to the patternList of the selected dataView', async () => { - await act(async () => { - store = createStore( - { - ...state, - sourcerer: { - ...state.sourcerer, - sourcererScopes: { - ...state.sourcerer.sourcererScopes, - [SourcererScopeName.default]: { - ...state.sourcerer.sourcererScopes[SourcererScopeName.default], - selectedDataViewId: 'something-random', - selectedPatterns: [], - }, - }, - }, - }, - SUB_PLUGINS_REDUCER, - kibanaObservable, - storage - ); - const { rerender, waitForNextUpdate } = renderHook( - () => useIndexFields(SourcererScopeName.default), - { - wrapper: ({ children }) => {children}, - } - ); - await waitForNextUpdate(); - rerender(); - expect(mockDispatch.mock.calls[0][0]).toEqual({ - type: 'x-pack/security_solution/local/sourcerer/SET_SOURCERER_SCOPE_LOADING', - payload: { id: SourcererScopeName.default, loading: true }, - }); - const { - type: sourceType, - payload: { payload }, - } = mockDispatch.mock.calls[1][0]; - expect(sourceType).toEqual('x-pack/security_solution/local/sourcerer/SET_SOURCE'); - expect(payload.id).toEqual(SourcererScopeName.default); - expect(payload.indicesExist).toEqual(true); - expect(payload.indexPattern.title).toEqual('random,something'); - }); - }); - it('when selectedPatterns=[] and selectedDataViewId=security-solution, runs getScopePatternListSelection', async () => { - await act(async () => { - store = createStore( - { - ...state, - sourcerer: { - ...state.sourcerer, - sourcererScopes: { - ...state.sourcerer.sourcererScopes, - [SourcererScopeName.default]: { - ...state.sourcerer.sourcererScopes[SourcererScopeName.default], - selectedPatterns: [], - }, - }, - }, - }, - SUB_PLUGINS_REDUCER, - kibanaObservable, - storage - ); - const { rerender, waitForNextUpdate } = renderHook( - () => useIndexFields(SourcererScopeName.default), - { - wrapper: ({ children }) => {children}, - } - ); - await waitForNextUpdate(); - rerender(); + act(() => result.current.indexFieldsSearch('neato')); expect(mockDispatch.mock.calls[0][0]).toEqual({ - type: 'x-pack/security_solution/local/sourcerer/SET_SOURCERER_SCOPE_LOADING', - payload: { id: SourcererScopeName.default, loading: true }, + type: 'x-pack/security_solution/local/sourcerer/SET_DATA_VIEW_LOADING', + payload: { id: 'neato', loading: true }, }); - const { - type: sourceType, - payload: { payload }, - } = mockDispatch.mock.calls[1][0]; - expect(sourceType).toEqual('x-pack/security_solution/local/sourcerer/SET_SOURCE'); - expect(payload.id).toEqual(SourcererScopeName.default); - expect(payload.indicesExist).toEqual(true); - expect(payload.indexPattern.title).toEqual( - 'apm-*-transaction*,auditbeat-*,endgame-*,filebeat-*,logs-*,packetbeat-*,traces-apm*,winlogbeat-*' - ); + const { type: sourceType, payload } = mockDispatch.mock.calls[1][0]; + expect(sourceType).toEqual('x-pack/security_solution/local/sourcerer/SET_DATA_VIEW'); + expect(payload.id).toEqual('neato'); + expect(Object.keys(payload.browserFields)).toHaveLength(10); + expect(payload.docValueFields).toEqual([{ field: '@timestamp' }]); }); }); + + // TODO: Steph/sourcerer make these tests good again + // it('sets source for timelines scope', async () => { + // await act(async () => { + // const { rerender, waitForNextUpdate } = renderHook( + // () => useIndexFields(SourcererScopeName.timeline), + // { + // wrapper: ({ children }) => {children}, + // } + // ); + // await waitForNextUpdate(); + // rerender(); + // expect(mockDispatch.mock.calls[0][0]).toEqual({ + // type: 'x-pack/security_solution/local/sourcerer/SET_SOURCERER_SCOPE_LOADING', + // payload: { id: SourcererScopeName.timeline, loading: true }, + // }); + // const { + // type: sourceType, + // payload: { payload }, + // } = mockDispatch.mock.calls[1][0]; + // expect(sourceType).toEqual('x-pack/security_solution/local/sourcerer/SET_SOURCE'); + // expect(payload.id).toEqual(SourcererScopeName.timeline); + // expect(payload.indicesExist).toEqual(true); + // expect(payload.indexPattern.title).toEqual( + // `${sourcererState.signalIndexName},apm-*-transaction*,auditbeat-*,endgame-*,filebeat-*,logs-*,packetbeat-*,traces-apm*,winlogbeat-*` + // ); + // }); + // }); + // it('sets source for detections scope when signalIndexName is updated', async () => { + // store = createStore( + // { ...state, sourcerer: { ...state.sourcerer, signalIndexName: null } }, + // SUB_PLUGINS_REDUCER, + // kibanaObservable, + // storage + // ); + // await act(async () => { + // const { result, rerender, waitForNextUpdate } = renderHook< + // string, + // { indexFieldsSearch: (selectedDataViewId: string, newSignalsIndex?: string) => void } + // >(() => useIndexFields(SourcererScopeName.detections), { + // wrapper: ({ children }) => {children}, + // }); + // await waitForNextUpdate(); + // rerender(); + // act(() => { + // result.current.indexFieldsSearch( + // sourcererState.defaultDataView.id, + // `${sourcererState.signalIndexName}` + // ); + // }); + // + // expect(mockDispatch.mock.calls[0][0]).toEqual({ + // type: 'x-pack/security_solution/local/sourcerer/SET_SOURCERER_SCOPE_LOADING', + // payload: { id: SourcererScopeName.detections, loading: true }, + // }); + // expect(mockDispatch.mock.calls[1][0].payload.payload.indicesExist).toEqual(false); + // expect(mockDispatch.mock.calls[2][0]).toEqual({ + // type: 'x-pack/security_solution/local/sourcerer/SET_SOURCERER_SCOPE_LOADING', + // payload: { id: SourcererScopeName.detections, loading: true }, + // }); + // expect(mockDispatch.mock.calls[3][0].payload.payload.indicesExist).toEqual(true); + // }); + // }); + // it('when selectedPatterns=[], defaults to the patternList of the selected dataView', async () => { + // await act(async () => { + // store = createStore( + // { + // ...state, + // sourcerer: { + // ...state.sourcerer, + // sourcererScopes: { + // ...state.sourcerer.sourcererScopes, + // [SourcererScopeName.default]: { + // ...state.sourcerer.sourcererScopes[SourcererScopeName.default], + // selectedDataViewId: 'something-random', + // selectedPatterns: [], + // }, + // }, + // }, + // }, + // SUB_PLUGINS_REDUCER, + // kibanaObservable, + // storage + // ); + // const { rerender, waitForNextUpdate } = renderHook( + // () => useIndexFields(SourcererScopeName.default), + // { + // wrapper: ({ children }) => {children}, + // } + // ); + // await waitForNextUpdate(); + // rerender(); + // expect(mockDispatch.mock.calls[0][0]).toEqual({ + // type: 'x-pack/security_solution/local/sourcerer/SET_SOURCERER_SCOPE_LOADING', + // payload: { id: SourcererScopeName.default, loading: true }, + // }); + // const { + // type: sourceType, + // payload: { payload }, + // } = mockDispatch.mock.calls[1][0]; + // expect(sourceType).toEqual('x-pack/security_solution/local/sourcerer/SET_SOURCE'); + // expect(payload.id).toEqual(SourcererScopeName.default); + // expect(payload.indicesExist).toEqual(true); + // expect(payload.indexPattern.title).toEqual('random,something'); + // }); + // }); + // it('when selectedPatterns=[] and selectedDataViewId=security-solution, runs getScopePatternListSelection', async () => { + // await act(async () => { + // store = createStore( + // { + // ...state, + // sourcerer: { + // ...state.sourcerer, + // sourcererScopes: { + // ...state.sourcerer.sourcererScopes, + // [SourcererScopeName.default]: { + // ...state.sourcerer.sourcererScopes[SourcererScopeName.default], + // selectedPatterns: [], + // }, + // }, + // }, + // }, + // SUB_PLUGINS_REDUCER, + // kibanaObservable, + // storage + // ); + // const { rerender, waitForNextUpdate } = renderHook( + // () => useIndexFields(SourcererScopeName.default), + // { + // wrapper: ({ children }) => {children}, + // } + // ); + // await waitForNextUpdate(); + // rerender(); + // expect(mockDispatch.mock.calls[0][0]).toEqual({ + // type: 'x-pack/security_solution/local/sourcerer/SET_SOURCERER_SCOPE_LOADING', + // payload: { id: SourcererScopeName.default, loading: true }, + // }); + // const { + // type: sourceType, + // payload: { payload }, + // } = mockDispatch.mock.calls[1][0]; + // expect(sourceType).toEqual('x-pack/security_solution/local/sourcerer/SET_SOURCE'); + // expect(payload.id).toEqual(SourcererScopeName.default); + // expect(payload.indicesExist).toEqual(true); + // expect(payload.indexPattern.title).toEqual( + // 'apm-*-transaction*,auditbeat-*,endgame-*,filebeat-*,logs-*,packetbeat-*,traces-apm*,winlogbeat-*' + // ); + // }); + // }); }); }); diff --git a/x-pack/plugins/security_solution/public/common/containers/source/index.tsx b/x-pack/plugins/security_solution/public/common/containers/source/index.tsx index f681d0869aef2..cb5489035e258 100644 --- a/x-pack/plugins/security_solution/public/common/containers/source/index.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/source/index.tsx @@ -7,28 +7,22 @@ import { isEmpty, isEqual, isUndefined, keyBy, pick } from 'lodash/fp'; import memoizeOne from 'memoize-one'; -import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { useDispatch } from 'react-redux'; -import { IIndexPattern } from 'src/plugins/data/public'; +import { useCallback, useEffect, useRef, useState } from 'react'; +import { DataViewBase } from '@kbn/es-query'; import { Subscription } from 'rxjs'; import { useKibana } from '../../lib/kibana'; import { BrowserField, BrowserFields, - DELETED_SECURITY_SOLUTION_DATA_VIEW, DocValueFields, IndexField, IndexFieldsStrategyRequest, IndexFieldsStrategyResponse, } from '../../../../../timelines/common'; import { isCompleteResponse, isErrorResponse } from '../../../../../../../src/plugins/data/common'; -import { useDeepEqualSelector } from '../../../common/hooks/use_selector'; import * as i18n from './translations'; -import { SourcererScopeName } from '../../store/sourcerer/model'; -import { sourcererActions, sourcererSelectors } from '../../store/sourcerer'; import { useAppToasts } from '../../hooks/use_app_toasts'; -import { SelectedDataView } from '../../store/sourcerer/selectors'; export { BrowserField, BrowserFields, DocValueFields }; @@ -47,7 +41,7 @@ export const getAllFieldsByName = ( keyBy('name', getAllBrowserFields(browserFields)); export const getIndexFields = memoizeOne( - (title: string, fields: IndexField[]): IIndexPattern => + (title: string, fields: IndexField[]): DataViewBase => fields && fields.length > 0 ? { fields: fields.map((field) => @@ -120,7 +114,7 @@ interface FetchIndexReturn { docValueFields: DocValueFields[]; indexes: string[]; indexExists: boolean; - indexPatterns: IIndexPattern; + indexPatterns: DataViewBase; } /** @@ -166,8 +160,6 @@ export const useFetchIndex = ( previousIndexesName.current = response.indicesExist; setLoading(false); - // TODO: Steph/sourcerer all the below formatting should be happening serverside - // https://github.com/elastic/security-team/issues/1730 setState({ browserFields: getBrowserFields(stringifyIndices, response.indexFields), docValueFields: getDocValueFields(stringifyIndices, response.indexFields), @@ -211,169 +203,3 @@ export const useFetchIndex = ( return [isLoading, state]; }; - -/** - * Sourcerer specific index fields hook/request - * sets redux state, returns nothing - */ -export const useIndexFields = ( - sourcererScopeName: SourcererScopeName -): { indexFieldsSearch: (selectedDataViewId: string, newSignalsIndex?: string) => void } => { - const { data } = useKibana().services; - const abortCtrl = useRef(new AbortController()); - const searchSubscription$ = useRef(new Subscription()); - const dispatch = useDispatch(); - const getSelectedDataView = useMemo(() => sourcererSelectors.getSelectedDataViewSelector(), []); - const { dataViewId, patternList, selectedPatterns } = useDeepEqualSelector( - (state) => getSelectedDataView(state, sourcererScopeName) - ); - const { addError, addWarning } = useAppToasts(); - - const setLoading = useCallback( - (loading: boolean) => { - dispatch(sourcererActions.setSourcererScopeLoading({ id: sourcererScopeName, loading })); - }, - [dispatch, sourcererScopeName] - ); - - const getSignalIndexNameSelector = useMemo( - () => sourcererSelectors.signalIndexNameSelector(), - [] - ); - const signalIndexNameSelector = useDeepEqualSelector(getSignalIndexNameSelector); - - const indexFieldsSearch = useCallback( - (selectedDataViewId: string, newSignalsIndex?: string) => { - const asyncSearch = async () => { - abortCtrl.current = new AbortController(); - setLoading(true); - searchSubscription$.current = data.search - .search, IndexFieldsStrategyResponse>( - { - dataViewId: selectedDataViewId, - onlyCheckIfIndicesExist: false, - }, - { - abortSignal: abortCtrl.current.signal, - strategy: 'indexFields', - } - ) - .subscribe({ - next: (response) => { - if (isCompleteResponse(response)) { - const signalIndexName = signalIndexNameSelector - ? signalIndexNameSelector - : newSignalsIndex ?? ''; - const newSelectedPatterns = selectedPatterns.filter((pattern) => - patternList.includes(pattern) - ); - const patternString = newSelectedPatterns.sort().join(); - - if (newSignalsIndex != null) { - // if new signal index name is set, there wasn't one before so we need to update detections specifically - // technically, we need to update all scopes as there can be new fields in signals index - // once fields are moved to sourcerer.kibanaDataViews we only need to do this for detections scope - // TODO: Steph/sourcerer https://github.com/elastic/security-team/issues/1730 to be done before 7.16 feature freeze - dispatch( - sourcererActions.setSource({ - id: SourcererScopeName.detections, - payload: { - browserFields: getBrowserFields(patternString, response.indexFields), - docValueFields: getDocValueFields(patternString, response.indexFields), - errorMessage: null, - id: SourcererScopeName.detections, - indexPattern: getIndexFields(patternString, response.indexFields), - indicesExist: response.indicesExist.includes(signalIndexName), - loading: false, - runtimeMappings: response.runtimeMappings, - }, - }) - ); - } else { - dispatch( - sourcererActions.setSource({ - id: sourcererScopeName, - payload: { - // TODO: Steph/sourcerer all the below formatting should be happening serverside - // https://github.com/elastic/security-team/issues/1730 - browserFields: getBrowserFields(patternString, response.indexFields), - docValueFields: getDocValueFields(patternString, response.indexFields), - errorMessage: null, - id: sourcererScopeName, - indexPattern: getIndexFields(patternString, response.indexFields), - indicesExist: - sourcererScopeName === SourcererScopeName.detections - ? response.indicesExist.includes(signalIndexName) - : sourcererScopeName === SourcererScopeName.default - ? response.indicesExist.filter((i) => i !== signalIndexName).length > 0 - : response.indicesExist.length > 0, - loading: false, - runtimeMappings: response.runtimeMappings, - }, - }) - ); - } - searchSubscription$.current.unsubscribe(); - } else if (isErrorResponse(response)) { - setLoading(false); - addWarning(i18n.ERROR_BEAT_FIELDS); - searchSubscription$.current.unsubscribe(); - } - }, - error: (msg) => { - if (msg.message === DELETED_SECURITY_SOLUTION_DATA_VIEW) { - // reload app if security solution data view is deleted - return location.reload(); - } - setLoading(false); - addError(msg, { - title: i18n.FAIL_BEAT_FIELDS, - }); - searchSubscription$.current.unsubscribe(); - }, - }); - }; - searchSubscription$.current.unsubscribe(); - abortCtrl.current.abort(); - asyncSearch(); - }, - [ - addError, - addWarning, - data.search, - dispatch, - patternList, - selectedPatterns, - setLoading, - signalIndexNameSelector, - sourcererScopeName, - ] - ); - const refDataViewId = useRef(''); - const refSelectedPatterns = useRef([] as string[]); - const refSourcererScopeName = useRef(''); - - useEffect(() => { - if ( - (dataViewId != null && - // remove this when https://github.com/elastic/kibana/pull/114907 is merged - sourcererScopeName !== refSourcererScopeName.current) || - (dataViewId !== refDataViewId.current && selectedPatterns.length > 0) || - (selectedPatterns.length > 0 && refSelectedPatterns.current.length === 0) - ) { - indexFieldsSearch(dataViewId); - } - refSourcererScopeName.current = sourcererScopeName; - refSelectedPatterns.current = selectedPatterns; - refDataViewId.current = dataViewId; - }, [dataViewId, indexFieldsSearch, selectedPatterns, sourcererScopeName]); - - useEffect(() => { - return () => { - searchSubscription$.current.unsubscribe(); - abortCtrl.current.abort(); - }; - }, []); - - return { indexFieldsSearch }; -}; diff --git a/x-pack/plugins/security_solution/public/common/containers/source/mock.ts b/x-pack/plugins/security_solution/public/common/containers/source/mock.ts index 908524542ed48..e34972f5226f3 100644 --- a/x-pack/plugins/security_solution/public/common/containers/source/mock.ts +++ b/x-pack/plugins/security_solution/public/common/containers/source/mock.ts @@ -23,6 +23,7 @@ export const mocksSource = { searchable: true, type: 'date', aggregatable: true, + readFromDocValues: true, }, { category: 'agent', @@ -331,7 +332,13 @@ export const mocksSource = { }; export const mockIndexFields = [ - { aggregatable: true, name: '@timestamp', searchable: true, type: 'date' }, + { + aggregatable: true, + name: '@timestamp', + searchable: true, + type: 'date', + readFromDocValues: true, + }, { aggregatable: true, name: 'agent.ephemeral_id', searchable: true, type: 'string' }, { aggregatable: true, name: 'agent.hostname', searchable: true, type: 'string' }, { aggregatable: true, name: 'agent.id', searchable: true, type: 'string' }, @@ -460,6 +467,7 @@ export const mockBrowserFields: BrowserFields = { name: '@timestamp', searchable: true, type: 'date', + readFromDocValues: true, }, }, }, diff --git a/x-pack/plugins/security_solution/public/common/containers/source/use_data_view.tsx b/x-pack/plugins/security_solution/public/common/containers/source/use_data_view.tsx new file mode 100644 index 0000000000000..a0178d45a9e07 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/containers/source/use_data_view.tsx @@ -0,0 +1,120 @@ +/* + * 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 { useCallback, useEffect, useRef } from 'react'; +import { Subscription } from 'rxjs'; +import { useDispatch } from 'react-redux'; +import memoizeOne from 'memoize-one'; +import { pick } from 'lodash/fp'; +import { useKibana } from '../../lib/kibana'; +import { useAppToasts } from '../../hooks/use_app_toasts'; +import { sourcererActions } from '../../store/sourcerer'; +import { + DELETED_SECURITY_SOLUTION_DATA_VIEW, + IndexField, + IndexFieldsStrategyRequest, + IndexFieldsStrategyResponse, +} from '../../../../../timelines/common'; +import { + FieldSpec, + isCompleteResponse, + isErrorResponse, +} from '../../../../../../../src/plugins/data/common'; +import * as i18n from './translations'; +import { getBrowserFields, getDocValueFields } from './'; + +const getEsFields = memoizeOne( + (fields: IndexField[]): FieldSpec[] => + fields && fields.length > 0 + ? fields.map((field) => + pick(['name', 'searchable', 'type', 'aggregatable', 'esTypes', 'subType'], field) + ) + : [], + (newArgs, lastArgs) => newArgs[0].length === lastArgs[0].length +); + +export const useDataView = (): { indexFieldsSearch: (selectedDataViewId: string) => void } => { + const { data } = useKibana().services; + const abortCtrl = useRef(new AbortController()); + const searchSubscription$ = useRef(new Subscription()); + const dispatch = useDispatch(); + const { addError, addWarning } = useAppToasts(); + + const setLoading = useCallback( + ({ id, loading }: { id: string; loading: boolean }) => { + dispatch(sourcererActions.setDataViewLoading({ id, loading })); + }, + [dispatch] + ); + + const indexFieldsSearch = useCallback( + (selectedDataViewId: string) => { + const asyncSearch = async () => { + abortCtrl.current = new AbortController(); + setLoading({ id: selectedDataViewId, loading: true }); + searchSubscription$.current = data.search + .search, IndexFieldsStrategyResponse>( + { + dataViewId: selectedDataViewId, + onlyCheckIfIndicesExist: false, + }, + { + abortSignal: abortCtrl.current.signal, + strategy: 'indexFields', + } + ) + .subscribe({ + next: (response) => { + if (isCompleteResponse(response)) { + const patternString = response.indicesExist.sort().join(); + + dispatch( + sourcererActions.setDataView({ + browserFields: getBrowserFields(patternString, response.indexFields), + docValueFields: getDocValueFields(patternString, response.indexFields), + id: selectedDataViewId, + indexFields: getEsFields(response.indexFields), + loading: false, + runtimeMappings: response.runtimeMappings, + }) + ); + searchSubscription$.current.unsubscribe(); + } else if (isErrorResponse(response)) { + setLoading({ id: selectedDataViewId, loading: false }); + addWarning(i18n.ERROR_BEAT_FIELDS); + searchSubscription$.current.unsubscribe(); + } + }, + error: (msg) => { + if (msg.message === DELETED_SECURITY_SOLUTION_DATA_VIEW) { + // reload app if security solution data view is deleted + return location.reload(); + } + setLoading({ id: selectedDataViewId, loading: false }); + addError(msg, { + title: i18n.FAIL_BEAT_FIELDS, + }); + searchSubscription$.current.unsubscribe(); + }, + }); + }; + searchSubscription$.current.unsubscribe(); + abortCtrl.current.abort(); + asyncSearch(); + }, + [addError, addWarning, data.search, dispatch, setLoading] + ); + + useEffect(() => { + return () => { + searchSubscription$.current.unsubscribe(); + abortCtrl.current.abort(); + }; + }, []); + + return { indexFieldsSearch }; +}; diff --git a/x-pack/plugins/security_solution/public/common/containers/sourcerer/api.ts b/x-pack/plugins/security_solution/public/common/containers/sourcerer/api.ts index ca2588ebd00bc..f510d92a673e4 100644 --- a/x-pack/plugins/security_solution/public/common/containers/sourcerer/api.ts +++ b/x-pack/plugins/security_solution/public/common/containers/sourcerer/api.ts @@ -7,7 +7,7 @@ import { KibanaServices } from '../../lib/kibana'; import { SOURCERER_API_URL } from '../../../../common/constants'; -import { KibanaDataView } from '../../store/sourcerer/model'; +import { SourcererDataView } from '../../store/sourcerer/model'; export interface GetSourcererDataView { signal: AbortSignal; @@ -16,15 +16,15 @@ export interface GetSourcererDataView { }; } -interface SourcererDataView { - defaultDataView: KibanaDataView; - kibanaDataViews: KibanaDataView[]; +interface SecurityDataView { + defaultDataView: SourcererDataView; + kibanaDataViews: SourcererDataView[]; } export const postSourcererDataView = async ({ body, signal, -}: GetSourcererDataView): Promise => +}: GetSourcererDataView): Promise => KibanaServices.get().http.fetch(SOURCERER_API_URL, { method: 'POST', body: JSON.stringify(body), diff --git a/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.test.tsx b/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.test.tsx index d012dccecfd81..2e060973c431f 100644 --- a/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.test.tsx @@ -7,13 +7,14 @@ import React from 'react'; import { act, renderHook } from '@testing-library/react-hooks'; +import { waitFor } from '@testing-library/react'; import { Provider } from 'react-redux'; -import { getScopeFromPath, useInitSourcerer } from '.'; +import { getScopeFromPath, useInitSourcerer, useSourcererDataView } from '.'; import { mockPatterns } from './mocks'; import { RouteSpyState } from '../../utils/route/types'; import { DEFAULT_INDEX_PATTERN, SecurityPageName } from '../../../../common/constants'; -import { createStore, State } from '../../store'; +import { createStore } from '../../store'; import { useUserInfo, initialState as userInfoState, @@ -25,7 +26,8 @@ import { SUB_PLUGINS_REDUCER, mockSourcererState, } from '../../mock'; -import { SourcererScopeName } from '../../store/sourcerer/model'; +import { SelectedDataView, SourcererScopeName } from '../../store/sourcerer/model'; +import { postSourcererDataView } from './api'; const mockRouteSpy: RouteSpyState = { pageName: SecurityPageName.overview, @@ -37,6 +39,7 @@ const mockRouteSpy: RouteSpyState = { const mockDispatch = jest.fn(); const mockUseUserInfo = useUserInfo as jest.Mock; jest.mock('../../../detections/components/user_info'); +jest.mock('./api'); jest.mock('react-redux', () => { const original = jest.requireActual('react-redux'); @@ -48,6 +51,7 @@ jest.mock('react-redux', () => { jest.mock('../../utils/route/use_route_spy', () => ({ useRouteSpy: () => [mockRouteSpy], })); + jest.mock('../../lib/kibana', () => ({ useToasts: jest.fn().mockReturnValue({ addError: jest.fn(), @@ -84,36 +88,13 @@ jest.mock('../../lib/kibana', () => ({ })); describe('Sourcerer Hooks', () => { - const state: State = { - ...mockGlobalState, - sourcerer: { - ...mockGlobalState.sourcerer, - sourcererScopes: { - ...mockGlobalState.sourcerer.sourcererScopes, - [SourcererScopeName.default]: { - ...mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.default], - indexPattern: { - fields: [], - title: '', - }, - }, - [SourcererScopeName.timeline]: { - ...mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.timeline], - indexPattern: { - fields: [], - title: '', - }, - }, - }, - }, - }; const { storage } = createSecuritySolutionStorageMock(); - let store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage); + let store: ReturnType; beforeEach(() => { jest.clearAllMocks(); jest.restoreAllMocks(); - store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage); + store = createStore(mockGlobalState, SUB_PLUGINS_REDUCER, kibanaObservable, storage); mockUseUserInfo.mockImplementation(() => userInfoState); }); it('initializes loading default and timeline index patterns', async () => { @@ -123,16 +104,12 @@ describe('Sourcerer Hooks', () => { }); await waitForNextUpdate(); rerender(); - expect(mockDispatch).toBeCalledTimes(3); + expect(mockDispatch).toBeCalledTimes(2); expect(mockDispatch.mock.calls[0][0]).toEqual({ - type: 'x-pack/security_solution/local/sourcerer/SET_SOURCERER_SCOPE_LOADING', - payload: { id: 'default', loading: true }, + type: 'x-pack/security_solution/local/sourcerer/SET_DATA_VIEW_LOADING', + payload: { id: 'security-solution', loading: true }, }); expect(mockDispatch.mock.calls[1][0]).toEqual({ - type: 'x-pack/security_solution/local/sourcerer/SET_SOURCERER_SCOPE_LOADING', - payload: { id: 'timeline', loading: true }, - }); - expect(mockDispatch.mock.calls[2][0]).toEqual({ type: 'x-pack/security_solution/local/sourcerer/SET_SELECTED_DATA_VIEW', payload: { id: 'timeline', @@ -153,14 +130,20 @@ describe('Sourcerer Hooks', () => { }); }); it('sets signal index name', async () => { + const mockNewDataViews = { + defaultDataView: mockSourcererState.defaultDataView, + kibanaDataViews: [mockSourcererState.defaultDataView], + }; + (postSourcererDataView as jest.Mock).mockResolvedValue(mockNewDataViews); + store = createStore( { - ...state, + ...mockGlobalState, sourcerer: { - ...state.sourcerer, + ...mockGlobalState.sourcerer, signalIndexName: null, defaultDataView: { - id: mockSourcererState.defaultDataView.id, + ...mockGlobalState.sourcerer.defaultDataView, title: DEFAULT_INDEX_PATTERN.join(','), patternList: DEFAULT_INDEX_PATTERN, }, @@ -181,26 +164,30 @@ describe('Sourcerer Hooks', () => { }); await waitForNextUpdate(); rerender(); - expect(mockDispatch.mock.calls[3][0]).toEqual({ - type: 'x-pack/security_solution/local/sourcerer/SET_SOURCERER_SCOPE_LOADING', - payload: { loading: true }, - }); - expect(mockDispatch.mock.calls[2][0]).toEqual({ - type: 'x-pack/security_solution/local/sourcerer/SET_SELECTED_DATA_VIEW', - payload: { - id: 'timeline', - selectedDataViewId: mockSourcererState.defaultDataView.id, - selectedPatterns: [ - mockSourcererState.signalIndexName, - ...mockSourcererState.defaultDataView.patternList.filter( - (p) => p !== mockSourcererState.signalIndexName - ), - ].sort(), - }, - }); - expect(mockDispatch.mock.calls[4][0]).toEqual({ - type: 'x-pack/security_solution/local/sourcerer/SET_SIGNAL_INDEX_NAME', - payload: { signalIndexName: mockSourcererState.signalIndexName }, + await waitFor(() => { + expect(mockDispatch.mock.calls[2][0]).toEqual({ + type: 'x-pack/security_solution/local/sourcerer/SET_SOURCERER_SCOPE_LOADING', + payload: { loading: true }, + }); + expect(mockDispatch.mock.calls[3][0]).toEqual({ + type: 'x-pack/security_solution/local/sourcerer/SET_SIGNAL_INDEX_NAME', + payload: { signalIndexName: mockSourcererState.signalIndexName }, + }); + expect(mockDispatch.mock.calls[4][0]).toEqual({ + type: 'x-pack/security_solution/local/sourcerer/SET_DATA_VIEW_LOADING', + payload: { + id: mockSourcererState.defaultDataView.id, + loading: true, + }, + }); + expect(mockDispatch.mock.calls[5][0]).toEqual({ + type: 'x-pack/security_solution/local/sourcerer/SET_SOURCERER_DATA_VIEWS', + payload: mockNewDataViews, + }); + expect(mockDispatch.mock.calls[6][0]).toEqual({ + type: 'x-pack/security_solution/local/sourcerer/SET_SOURCERER_SCOPE_LOADING', + payload: { loading: false }, + }); }); }); }); @@ -219,7 +206,7 @@ describe('Sourcerer Hooks', () => { ); await waitForNextUpdate(); rerender(); - expect(mockDispatch.mock.calls[3][0]).toEqual({ + expect(mockDispatch.mock.calls[2][0]).toEqual({ type: 'x-pack/security_solution/local/sourcerer/SET_SELECTED_DATA_VIEW', payload: { id: 'detections', @@ -229,6 +216,123 @@ describe('Sourcerer Hooks', () => { }); }); }); + + describe('useSourcererDataView', () => { + it('Should exclude elastic cloud alias when selected patterns include "logs-*" as an alias', async () => { + await act(async () => { + const { result, rerender, waitForNextUpdate } = renderHook< + SourcererScopeName, + SelectedDataView + >(() => useSourcererDataView(), { + wrapper: ({ children }) => {children}, + }); + await waitForNextUpdate(); + rerender(); + expect(result.current.selectedPatterns).toEqual([ + '-*elastic-cloud-logs-*', + ...mockGlobalState.sourcerer.sourcererScopes.default.selectedPatterns, + ]); + }); + }); + + it('Should NOT exclude elastic cloud alias when selected patterns does NOT include "logs-*" as an alias', async () => { + await act(async () => { + store = createStore( + { + ...mockGlobalState, + sourcerer: { + ...mockGlobalState.sourcerer, + sourcererScopes: { + ...mockGlobalState.sourcerer.sourcererScopes, + [SourcererScopeName.default]: { + ...mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.default], + selectedPatterns: [ + 'apm-*-transaction*', + 'auditbeat-*', + 'endgame-*', + 'filebeat-*', + 'packetbeat-*', + 'traces-apm*', + 'winlogbeat-*', + ], + }, + }, + }, + }, + SUB_PLUGINS_REDUCER, + kibanaObservable, + storage + ); + const { result, rerender, waitForNextUpdate } = renderHook< + SourcererScopeName, + SelectedDataView + >(() => useSourcererDataView(), { + wrapper: ({ children }) => {children}, + }); + await waitForNextUpdate(); + rerender(); + expect(result.current.selectedPatterns).toEqual([ + 'apm-*-transaction*', + 'auditbeat-*', + 'endgame-*', + 'filebeat-*', + 'packetbeat-*', + 'traces-apm*', + 'winlogbeat-*', + ]); + }); + }); + + it('Should NOT exclude elastic cloud alias when selected patterns include "logs-endpoint.event-*" as an alias', async () => { + await act(async () => { + store = createStore( + { + ...mockGlobalState, + sourcerer: { + ...mockGlobalState.sourcerer, + sourcererScopes: { + ...mockGlobalState.sourcerer.sourcererScopes, + [SourcererScopeName.default]: { + ...mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.default], + selectedPatterns: [ + 'apm-*-transaction*', + 'auditbeat-*', + 'endgame-*', + 'filebeat-*', + 'packetbeat-*', + 'traces-apm*', + 'winlogbeat-*', + 'logs-endpoint.event-*', + ], + }, + }, + }, + }, + SUB_PLUGINS_REDUCER, + kibanaObservable, + storage + ); + const { result, rerender, waitForNextUpdate } = renderHook< + SourcererScopeName, + SelectedDataView + >(() => useSourcererDataView(), { + wrapper: ({ children }) => {children}, + }); + await waitForNextUpdate(); + rerender(); + expect(result.current.selectedPatterns).toEqual([ + 'apm-*-transaction*', + 'auditbeat-*', + 'endgame-*', + 'filebeat-*', + 'logs-endpoint.event-*', + 'packetbeat-*', + 'traces-apm*', + 'winlogbeat-*', + ]); + }); + }); + }); }); describe('getScopeFromPath', () => { diff --git a/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.tsx b/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.tsx index 3cd102ff3e6c7..4ca8bf037261a 100644 --- a/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.tsx @@ -10,8 +10,7 @@ import { useDispatch } from 'react-redux'; import { i18n } from '@kbn/i18n'; import { matchPath } from 'react-router-dom'; import { sourcererActions, sourcererSelectors } from '../../store/sourcerer'; -import { SourcererScopeName } from '../../store/sourcerer/model'; -import { useIndexFields } from '../source'; +import { SelectedDataView, SourcererScopeName } from '../../store/sourcerer/model'; import { useUserInfo } from '../../../detections/components/user_info'; import { timelineSelectors } from '../../../timelines/store/timeline'; import { ALERTS_PATH, CASES_PATH, RULES_PATH, UEBA_PATH } from '../../../../common/constants'; @@ -20,6 +19,7 @@ import { useDeepEqualSelector } from '../../hooks/use_selector'; import { getScopePatternListSelection } from '../../store/sourcerer/helpers'; import { useAppToasts } from '../../hooks/use_app_toasts'; import { postSourcererDataView } from './api'; +import { useDataView } from '../source/use_data_view'; export const useInitSourcerer = ( scopeId: SourcererScopeName.default | SourcererScopeName.detections = SourcererScopeName.default @@ -38,10 +38,8 @@ export const useInitSourcerer = ( const { addError } = useAppToasts(); useEffect(() => { - const { id, ...rest } = defaultDataView; - if (id === null) { - // if id is null, rest is error - addError(rest, { + if (defaultDataView.error != null) { + addError(defaultDataView.error, { title: i18n.translate('xpack.securitySolution.sourcerer.permissions.title', { defaultMessage: 'Write role required to generate data', }), @@ -51,30 +49,45 @@ export const useInitSourcerer = ( }), }); } - }, [addError, defaultDataView]); + }, [addError, defaultDataView.error]); const getSignalIndexNameSelector = useMemo( () => sourcererSelectors.signalIndexNameSelector(), [] ); - const signalIndexNameSelector = useDeepEqualSelector(getSignalIndexNameSelector); + const signalIndexNameSourcerer = useDeepEqualSelector(getSignalIndexNameSelector); const getTimelineSelector = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); const activeTimeline = useDeepEqualSelector((state) => getTimelineSelector(state, TimelineId.active) ); + const scopeIdSelector = useMemo(() => sourcererSelectors.scopeIdSelector(), []); + const { selectedDataViewId: scopeDataViewId } = useDeepEqualSelector((state) => + scopeIdSelector(state, scopeId) + ); + const { selectedDataViewId: timelineDataViewId } = useDeepEqualSelector((state) => + scopeIdSelector(state, SourcererScopeName.timeline) + ); + const activeDataViewIds = useMemo( + () => [...new Set([scopeDataViewId, timelineDataViewId])], + [scopeDataViewId, timelineDataViewId] + ); + const { indexFieldsSearch } = useDataView(); - const { indexFieldsSearch } = useIndexFields(scopeId); - useIndexFields(SourcererScopeName.timeline); + useEffect( + () => activeDataViewIds.forEach((id) => id != null && id.length > 0 && indexFieldsSearch(id)), + [activeDataViewIds, indexFieldsSearch] + ); // Related to timeline useEffect(() => { if ( !loadingSignalIndex && signalIndexName != null && - signalIndexNameSelector == null && + signalIndexNameSourcerer == null && (activeTimeline == null || activeTimeline.savedObjectId == null) && - initialTimelineSourcerer.current + initialTimelineSourcerer.current && + defaultDataView.id.length > 0 ) { initialTimelineSourcerer.current = false; dispatch( @@ -90,9 +103,10 @@ export const useInitSourcerer = ( }) ); } else if ( - signalIndexNameSelector != null && + signalIndexNameSourcerer != null && (activeTimeline == null || activeTimeline.savedObjectId == null) && - initialTimelineSourcerer.current + initialTimelineSourcerer.current && + defaultDataView.id.length > 0 ) { initialTimelineSourcerer.current = false; dispatch( @@ -102,7 +116,7 @@ export const useInitSourcerer = ( selectedPatterns: getScopePatternListSelection( defaultDataView, SourcererScopeName.timeline, - signalIndexNameSelector, + signalIndexNameSourcerer, true ), }) @@ -114,52 +128,9 @@ export const useInitSourcerer = ( dispatch, loadingSignalIndex, signalIndexName, - signalIndexNameSelector, + signalIndexNameSourcerer, ]); - const pollForSignalIndex = useCallback( - (newPatternList: string[], newSignalsIndex: string, ms: number) => { - let doesIndexExist = false; - const asyncSearch = async () => { - abortCtrl.current = new AbortController(); - try { - const response = await postSourcererDataView({ - body: { patternList: newPatternList }, - signal: abortCtrl.current.signal, - }); - - if (response.defaultDataView.patternList.includes(newSignalsIndex)) { - // first time signals is defined and validated in the sourcerer - // redo indexFieldsSearch - indexFieldsSearch(response.defaultDataView.id, newSignalsIndex); - dispatch(sourcererActions.setSourcererDataViews(response)); - doesIndexExist = true; - } - } catch (err) { - addError(err, { - title: i18n.translate('xpack.securitySolution.sourcerer.error.title', { - defaultMessage: 'Error updating Security Data View', - }), - toastMessage: i18n.translate('xpack.securitySolution.sourcerer.error.toastMessage', { - defaultMessage: 'Refresh the page', - }), - }); - } - }; - - const poll = () => { - abortCtrl.current.abort(); - asyncSearch(); - setTimeout(function () { - if (!doesIndexExist) { - poll(); - } - }, ms); - }; - poll(); - }, - [addError, dispatch, indexFieldsSearch] - ); const updateSourcererDataView = useCallback( (newSignalsIndex: string) => { const asyncSearch = async (newPatternList: string[]) => { @@ -175,10 +146,7 @@ export const useInitSourcerer = ( if (response.defaultDataView.patternList.includes(newSignalsIndex)) { // first time signals is defined and validated in the sourcerer // redo indexFieldsSearch - indexFieldsSearch(response.defaultDataView.id, newSignalsIndex); - } else { - // signals index does not yet exist, check every 10 seconds - pollForSignalIndex(newPatternList, newSignalsIndex, 10000); + indexFieldsSearch(response.defaultDataView.id); } dispatch(sourcererActions.setSourcererDataViews(response)); dispatch(sourcererActions.setSourcererScopeLoading({ loading: false })); @@ -200,10 +168,15 @@ export const useInitSourcerer = ( asyncSearch([...defaultDataView.title.split(','), newSignalsIndex]); } }, - [defaultDataView.title, dispatch, indexFieldsSearch, pollForSignalIndex, addError] + [defaultDataView.title, dispatch, indexFieldsSearch, addError] ); useEffect(() => { - if (!loadingSignalIndex && signalIndexName != null && signalIndexNameSelector == null) { + if ( + !loadingSignalIndex && + signalIndexName != null && + signalIndexNameSourcerer == null && + defaultDataView.id.length > 0 + ) { // update signal name also updates sourcerer // we hit this the first time signal index is created updateSourcererDataView(signalIndexName); @@ -216,7 +189,7 @@ export const useInitSourcerer = ( isSignalIndexExists, loadingSignalIndex, signalIndexName, - signalIndexNameSelector, + signalIndexNameSourcerer, updateSourcererDataView, ]); // Related to the detection page @@ -225,7 +198,8 @@ export const useInitSourcerer = ( scopeId === SourcererScopeName.detections && isSignalIndexExists && signalIndexName != null && - initialDetectionSourcerer.current + initialDetectionSourcerer.current && + defaultDataView.id.length > 0 ) { initialDetectionSourcerer.current = false; dispatch( @@ -242,8 +216,9 @@ export const useInitSourcerer = ( ); } else if ( scopeId === SourcererScopeName.detections && - signalIndexNameSelector != null && - initialTimelineSourcerer.current + signalIndexNameSourcerer != null && + initialTimelineSourcerer.current && + defaultDataView.id.length > 0 ) { initialDetectionSourcerer.current = false; sourcererActions.setSelectedDataView({ @@ -252,7 +227,7 @@ export const useInitSourcerer = ( selectedPatterns: getScopePatternListSelection( defaultDataView, SourcererScopeName.detections, - signalIndexNameSelector, + signalIndexNameSourcerer, true ), }); @@ -263,13 +238,57 @@ export const useInitSourcerer = ( isSignalIndexExists, scopeId, signalIndexName, - signalIndexNameSelector, + signalIndexNameSourcerer, ]); }; -export const useSourcererScope = (scope: SourcererScopeName = SourcererScopeName.default) => { +const LOGS_WILDCARD_INDEX = 'logs-*'; +export const EXCLUDE_ELASTIC_CLOUD_INDEX = '-*elastic-cloud-logs-*'; + +export const useSourcererDataView = ( + scopeId: SourcererScopeName = SourcererScopeName.default +): SelectedDataView => { const sourcererScopeSelector = useMemo(() => sourcererSelectors.getSourcererScopeSelector(), []); - return useDeepEqualSelector((state) => sourcererScopeSelector(state, scope)); + const { + signalIndexName, + sourcererDataView: selectedDataView, + sourcererScope: { selectedPatterns: scopeSelectedPatterns, loading }, + }: sourcererSelectors.SourcererScopeSelector = useDeepEqualSelector((state) => + sourcererScopeSelector(state, scopeId) + ); + + const selectedPatterns = useMemo( + () => + scopeSelectedPatterns.some((index) => index === LOGS_WILDCARD_INDEX) + ? [...scopeSelectedPatterns, EXCLUDE_ELASTIC_CLOUD_INDEX] + : scopeSelectedPatterns, + [scopeSelectedPatterns] + ); + + return useMemo( + () => ({ + browserFields: selectedDataView.browserFields, + dataViewId: selectedDataView.id, + docValueFields: selectedDataView.docValueFields, + indexPattern: { + fields: selectedDataView.indexFields, + title: selectedPatterns.join(','), + }, + indicesExist: + scopeId === SourcererScopeName.detections + ? selectedDataView.patternList.includes(`${signalIndexName}`) + : scopeId === SourcererScopeName.default + ? selectedDataView.patternList.filter((i) => i !== signalIndexName).length > 0 + : selectedDataView.patternList.length > 0, + loading: loading || selectedDataView.loading, + runtimeMappings: selectedDataView.runtimeMappings, + // all active & inactive patterns in DATA_VIEW + patternList: selectedDataView.title.split(','), + // selected patterns in DATA_VIEW + selectedPatterns: selectedPatterns.sort(), + }), + [loading, selectedPatterns, signalIndexName, scopeId, selectedDataView] + ); }; export const getScopeFromPath = ( diff --git a/x-pack/plugins/security_solution/public/common/containers/sourcerer/use_signal_helpers.test.tsx b/x-pack/plugins/security_solution/public/common/containers/sourcerer/use_signal_helpers.test.tsx new file mode 100644 index 0000000000000..a387bd2211a66 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/containers/sourcerer/use_signal_helpers.test.tsx @@ -0,0 +1,95 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { + createSecuritySolutionStorageMock, + kibanaObservable, + mockGlobalState, + SUB_PLUGINS_REDUCER, + TestProviders, +} from '../../mock'; +import { act, renderHook } from '@testing-library/react-hooks'; +import { useSignalHelpers } from './use_signal_helpers'; +import { createStore, State } from '../../store'; + +describe('useSignalHelpers', () => { + const wrapperContainer: React.FC<{ children?: React.ReactNode }> = ({ children }) => ( + {children} + ); + + test('Default state, does not need init and does not need poll', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => useSignalHelpers(), { + wrapper: wrapperContainer, + }); + await waitForNextUpdate(); + expect(result.current.signalIndexNeedsInit).toEqual(false); + expect(result.current.pollForSignalIndex).toEqual(undefined); + }); + }); + test('Needs init and does not need poll when signal index is not yet in default data view', async () => { + const state: State = { + ...mockGlobalState, + sourcerer: { + ...mockGlobalState.sourcerer, + defaultDataView: { + ...mockGlobalState.sourcerer.defaultDataView, + title: `auditbeat-*,packetbeat-*`, + patternList: ['packetbeat-*', 'auditbeat-*'], + }, + kibanaDataViews: [ + { + ...mockGlobalState.sourcerer.defaultDataView, + title: `auditbeat-*,packetbeat-*`, + patternList: ['packetbeat-*', 'auditbeat-*'], + }, + ], + }, + }; + const { storage } = createSecuritySolutionStorageMock(); + const store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage); + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => useSignalHelpers(), { + wrapper: ({ children }) => {children}, + }); + await waitForNextUpdate(); + expect(result.current.signalIndexNeedsInit).toEqual(true); + expect(result.current.pollForSignalIndex).toEqual(undefined); + }); + }); + test('Init happened and signal index does not have data yet, poll function becomes available', async () => { + const state: State = { + ...mockGlobalState, + sourcerer: { + ...mockGlobalState.sourcerer, + defaultDataView: { + ...mockGlobalState.sourcerer.defaultDataView, + title: `auditbeat-*,packetbeat-*,${mockGlobalState.sourcerer.signalIndexName}`, + patternList: ['packetbeat-*', 'auditbeat-*'], + }, + kibanaDataViews: [ + { + ...mockGlobalState.sourcerer.defaultDataView, + title: `auditbeat-*,packetbeat-*,${mockGlobalState.sourcerer.signalIndexName}`, + patternList: ['packetbeat-*', 'auditbeat-*'], + }, + ], + }, + }; + const { storage } = createSecuritySolutionStorageMock(); + const store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage); + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => useSignalHelpers(), { + wrapper: ({ children }) => {children}, + }); + await waitForNextUpdate(); + expect(result.current.signalIndexNeedsInit).toEqual(false); + expect(result.current.pollForSignalIndex).not.toEqual(undefined); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/containers/sourcerer/use_signal_helpers.tsx b/x-pack/plugins/security_solution/public/common/containers/sourcerer/use_signal_helpers.tsx new file mode 100644 index 0000000000000..602997361f38b --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/containers/sourcerer/use_signal_helpers.tsx @@ -0,0 +1,92 @@ +/* + * 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 { useCallback, useMemo, useRef } from 'react'; +import { i18n } from '@kbn/i18n'; +import { useDispatch } from 'react-redux'; +import { sourcererSelectors } from '../../store'; +import { useDeepEqualSelector } from '../../hooks/use_selector'; +import { useSourcererDataView } from '.'; +import { SourcererScopeName } from '../../store/sourcerer/model'; +import { postSourcererDataView } from './api'; +import { sourcererActions } from '../../store/sourcerer'; +import { useDataView } from '../source/use_data_view'; +import { useAppToasts } from '../../hooks/use_app_toasts'; + +export const useSignalHelpers = (): { + /* when defined, signal index has been initiated but does not exist */ + pollForSignalIndex?: () => void; + /* when false, signal index has been initiated */ + signalIndexNeedsInit: boolean; +} => { + const { indicesExist } = useSourcererDataView(SourcererScopeName.detections); + const { indexFieldsSearch } = useDataView(); + const dispatch = useDispatch(); + const { addError } = useAppToasts(); + const abortCtrl = useRef(new AbortController()); + + const getDefaultDataViewSelector = useMemo( + () => sourcererSelectors.defaultDataViewSelector(), + [] + ); + const getSignalIndexNameSelector = useMemo( + () => sourcererSelectors.signalIndexNameSelector(), + [] + ); + const signalIndexNameSourcerer = useDeepEqualSelector(getSignalIndexNameSelector); + const defaultDataView = useDeepEqualSelector(getDefaultDataViewSelector); + + const signalIndexNeedsInit = useMemo( + () => !defaultDataView.title.includes(`${signalIndexNameSourcerer}`), + [defaultDataView.title, signalIndexNameSourcerer] + ); + const shouldWePollForIndex = useMemo( + () => !indicesExist && !signalIndexNeedsInit, + [indicesExist, signalIndexNeedsInit] + ); + + const pollForSignalIndex = useCallback(() => { + const asyncSearch = async () => { + abortCtrl.current = new AbortController(); + try { + const response = await postSourcererDataView({ + body: { patternList: defaultDataView.title.split(',') }, + signal: abortCtrl.current.signal, + }); + + if ( + signalIndexNameSourcerer !== null && + response.defaultDataView.patternList.includes(signalIndexNameSourcerer) + ) { + // first time signals is defined and validated in the sourcerer + // redo indexFieldsSearch + indexFieldsSearch(response.defaultDataView.id); + dispatch(sourcererActions.setSourcererDataViews(response)); + } + } catch (err) { + addError(err, { + title: i18n.translate('xpack.securitySolution.sourcerer.error.title', { + defaultMessage: 'Error updating Security Data View', + }), + toastMessage: i18n.translate('xpack.securitySolution.sourcerer.error.toastMessage', { + defaultMessage: 'Refresh the page', + }), + }); + } + }; + + if (signalIndexNameSourcerer !== null) { + abortCtrl.current.abort(); + asyncSearch(); + } + }, [addError, defaultDataView.title, dispatch, indexFieldsSearch, signalIndexNameSourcerer]); + + return { + ...(shouldWePollForIndex ? { pollForSignalIndex } : {}), + signalIndexNeedsInit, + }; +}; diff --git a/x-pack/plugins/security_solution/public/common/lib/keury/index.ts b/x-pack/plugins/security_solution/public/common/lib/keury/index.ts index 1e7574cdae26d..b9f2f9a297bce 100644 --- a/x-pack/plugins/security_solution/public/common/lib/keury/index.ts +++ b/x-pack/plugins/security_solution/public/common/lib/keury/index.ts @@ -14,12 +14,12 @@ import { buildEsQuery, toElasticsearchQuery, fromKueryExpression, - IndexPatternBase, + DataViewBase, } from '@kbn/es-query'; export const convertKueryToElasticSearchQuery = ( kueryExpression: string, - indexPattern?: IndexPatternBase + indexPattern?: DataViewBase ) => { try { return kueryExpression @@ -30,10 +30,7 @@ export const convertKueryToElasticSearchQuery = ( } }; -export const convertKueryToDslFilter = ( - kueryExpression: string, - indexPattern: IndexPatternBase -) => { +export const convertKueryToDslFilter = (kueryExpression: string, indexPattern: DataViewBase) => { try { return kueryExpression ? toElasticsearchQuery(fromKueryExpression(kueryExpression), indexPattern) @@ -75,7 +72,7 @@ export const convertToBuildEsQuery = ({ filters, }: { config: EsQueryConfig; - indexPattern: IndexPatternBase; + indexPattern: DataViewBase; queries: Query[]; filters: Filter[]; }): [string, undefined] | [undefined, Error] => { diff --git a/x-pack/plugins/security_solution/public/common/mock/global_state.ts b/x-pack/plugins/security_solution/public/common/mock/global_state.ts index 0a0dddbd8e3f6..2d804392580d0 100644 --- a/x-pack/plugins/security_solution/public/common/mock/global_state.ts +++ b/x-pack/plugins/security_solution/public/common/mock/global_state.ts @@ -35,18 +35,28 @@ import { TimelineType, TimelineStatus, TimelineTabs } from '../../../common/type import { mockManagementState } from '../../management/store/reducer'; import { ManagementState } from '../../management/types'; import { initialSourcererState, SourcererScopeName } from '../store/sourcerer/model'; -import { mockBrowserFields, mockDocValueFields } from '../containers/source/mock'; -import { mockIndexPattern } from './index_pattern'; import { allowedExperimentalValues } from '../../../common/experimental_features'; import { getScopePatternListSelection } from '../store/sourcerer/helpers'; +import { + mockBrowserFields, + mockDocValueFields, + mockIndexFields, + mockRuntimeMappings, +} from '../containers/source/mock'; export const mockSourcererState = { ...initialSourcererState, signalIndexName: `${DEFAULT_SIGNALS_INDEX}-spacename`, defaultDataView: { + ...initialSourcererState.defaultDataView, + browserFields: mockBrowserFields, + docValueFields: mockDocValueFields, id: DEFAULT_DATA_VIEW_ID, - title: [...DEFAULT_INDEX_PATTERN, `${DEFAULT_SIGNALS_INDEX}-spacename`].join(','), + indexFields: mockIndexFields, + loading: false, patternList: [...DEFAULT_INDEX_PATTERN, `${DEFAULT_SIGNALS_INDEX}-spacename`], + runtimeMappings: mockRuntimeMappings, + title: [...DEFAULT_INDEX_PATTERN, `${DEFAULT_SIGNALS_INDEX}-spacename`].join(','), }, }; @@ -330,9 +340,6 @@ export const mockGlobalState: State = { mockSourcererState.signalIndexName, true ), - browserFields: mockBrowserFields, - indexPattern: mockIndexPattern, - docValueFields: mockDocValueFields, }, [SourcererScopeName.detections]: { ...mockSourcererState.sourcererScopes[SourcererScopeName.detections], @@ -343,9 +350,6 @@ export const mockGlobalState: State = { mockSourcererState.signalIndexName, true ), - browserFields: mockBrowserFields, - indexPattern: mockIndexPattern, - docValueFields: mockDocValueFields, }, [SourcererScopeName.timeline]: { ...mockSourcererState.sourcererScopes[SourcererScopeName.timeline], @@ -356,9 +360,6 @@ export const mockGlobalState: State = { mockSourcererState.signalIndexName, true ), - browserFields: mockBrowserFields, - indexPattern: mockIndexPattern, - docValueFields: mockDocValueFields, }, }, }, diff --git a/x-pack/plugins/security_solution/public/common/mock/index_pattern.ts b/x-pack/plugins/security_solution/public/common/mock/index_pattern.ts index d19d6ee734654..a03976f0f7e76 100644 --- a/x-pack/plugins/security_solution/public/common/mock/index_pattern.ts +++ b/x-pack/plugins/security_solution/public/common/mock/index_pattern.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { IIndexPattern } from '../../../../../../src/plugins/data/common'; +import { SecuritySolutionDataViewBase } from '../types'; -export const mockIndexPattern: IIndexPattern = { +export const mockIndexPattern: SecuritySolutionDataViewBase = { fields: [ { name: '@timestamp', diff --git a/x-pack/plugins/security_solution/public/common/store/reducer.test.ts b/x-pack/plugins/security_solution/public/common/store/reducer.test.ts index 02de9187d4537..1cbf08c354b33 100644 --- a/x-pack/plugins/security_solution/public/common/store/reducer.test.ts +++ b/x-pack/plugins/security_solution/public/common/store/reducer.test.ts @@ -8,8 +8,12 @@ import { parseExperimentalConfigValue } from '../../../common/experimental_features'; import { SecuritySubPlugins } from '../../app/types'; import { createInitialState } from './reducer'; -import { DEFAULT_INDEX_PATTERN, DEFAULT_DATA_VIEW_ID } from '../../../common/constants'; +import { mockSourcererState } from '../mock'; +import { useSourcererDataView } from '../containers/sourcerer'; +import { useDeepEqualSelector } from '../hooks/use_selector'; +import { renderHook } from '@testing-library/react-hooks'; +jest.mock('../hooks/use_selector'); jest.mock('../lib/kibana', () => ({ KibanaServices: { get: jest.fn(() => ({ uiSettings: { get: () => ({ from: 'now-24h', to: 'now' }) } })), @@ -23,35 +27,37 @@ describe('createInitialState', () => { 'app' | 'dragAndDrop' | 'inputs' | 'sourcerer' >; const defaultState = { - defaultDataView: { - id: DEFAULT_DATA_VIEW_ID, - title: DEFAULT_INDEX_PATTERN.join(','), - patternList: DEFAULT_INDEX_PATTERN, - }, + defaultDataView: mockSourcererState.defaultDataView, enableExperimental: parseExperimentalConfigValue([]), - kibanaDataViews: [ - { - id: DEFAULT_DATA_VIEW_ID, - title: DEFAULT_INDEX_PATTERN.join(','), - patternList: DEFAULT_INDEX_PATTERN, - }, - ], + kibanaDataViews: [mockSourcererState.defaultDataView], signalIndexName: 'siem-signals-default', }; + const initState = createInitialState(mockPluginState, defaultState); + beforeEach(() => { + (useDeepEqualSelector as jest.Mock).mockImplementation((cb) => cb(initState)); + }); + afterEach(() => { + (useDeepEqualSelector as jest.Mock).mockClear(); + }); - test('indicesExist should be TRUE if configIndexPatterns is NOT empty', () => { - const initState = createInitialState(mockPluginState, defaultState); - - expect(initState.sourcerer?.sourcererScopes.default.indicesExist).toEqual(true); + test('indicesExist should be TRUE if configIndexPatterns is NOT empty', async () => { + const { result } = renderHook(() => useSourcererDataView()); + expect(result.current.indicesExist).toEqual(true); }); test('indicesExist should be FALSE if configIndexPatterns is empty', () => { - const initState = createInitialState(mockPluginState, { + const state = createInitialState(mockPluginState, { ...defaultState, - defaultDataView: { id: '', title: '', patternList: [] }, + defaultDataView: { + ...defaultState.defaultDataView, + id: '', + title: '', + patternList: [], + }, }); - - expect(initState.sourcerer?.sourcererScopes.default.indicesExist).toEqual(false); + (useDeepEqualSelector as jest.Mock).mockImplementation((cb) => cb(state)); + const { result } = renderHook(() => useSourcererDataView()); + expect(result.current.indicesExist).toEqual(false); }); }); }); diff --git a/x-pack/plugins/security_solution/public/common/store/reducer.ts b/x-pack/plugins/security_solution/public/common/store/reducer.ts index 31d4470a5fd0b..9faa00ae20bb8 100644 --- a/x-pack/plugins/security_solution/public/common/store/reducer.ts +++ b/x-pack/plugins/security_solution/public/common/store/reducer.ts @@ -21,7 +21,7 @@ import { SecuritySubPlugins } from '../../app/types'; import { ManagementPluginReducer } from '../../management'; import { State } from './types'; import { AppAction } from './actions'; -import { KibanaDataView, SourcererScopeName } from './sourcerer/model'; +import { initDataView, SourcererModel, SourcererScopeName } from './sourcerer/model'; import { ExperimentalFeatures } from '../../../common/experimental_features'; import { getScopePatternListSelection } from './sourcerer/helpers'; @@ -44,9 +44,9 @@ export const createInitialState = ( signalIndexName, enableExperimental, }: { - defaultDataView: KibanaDataView; - kibanaDataViews: KibanaDataView[]; - signalIndexName: string | null; + defaultDataView: SourcererModel['defaultDataView']; + kibanaDataViews: SourcererModel['kibanaDataViews']; + signalIndexName: SourcererModel['signalIndexName']; enableExperimental: ExperimentalFeatures; } ): State => { @@ -84,26 +84,20 @@ export const createInitialState = ( ...sourcererModel.initialSourcererState.sourcererScopes.default, selectedDataViewId: defaultDataView.id, selectedPatterns: initialPatterns[SourcererScopeName.default], - indicesExist: initialPatterns[SourcererScopeName.default].length > 0, }, [SourcererScopeName.detections]: { ...sourcererModel.initialSourcererState.sourcererScopes.detections, selectedDataViewId: defaultDataView.id, selectedPatterns: initialPatterns[SourcererScopeName.detections], - indicesExist: initialPatterns[SourcererScopeName.detections].length > 0, }, [SourcererScopeName.timeline]: { ...sourcererModel.initialSourcererState.sourcererScopes.timeline, selectedDataViewId: defaultDataView.id, selectedPatterns: initialPatterns[SourcererScopeName.timeline], - indicesExist: initialPatterns[SourcererScopeName.timeline].length > 0, }, }, - defaultDataView: { - ...sourcererModel.initialSourcererState.defaultDataView, - ...defaultDataView, - }, - kibanaDataViews, + defaultDataView, + kibanaDataViews: kibanaDataViews.map((dataView) => ({ ...initDataView, ...dataView })), signalIndexName, }, }; diff --git a/x-pack/plugins/security_solution/public/common/store/sourcerer/actions.ts b/x-pack/plugins/security_solution/public/common/store/sourcerer/actions.ts index 0d122dda7942d..92f33c6f184ad 100644 --- a/x-pack/plugins/security_solution/public/common/store/sourcerer/actions.ts +++ b/x-pack/plugins/security_solution/public/common/store/sourcerer/actions.ts @@ -8,21 +8,30 @@ import actionCreatorFactory from 'typescript-fsa'; import { TimelineEventsType } from '../../../../common/types/timeline'; -import { KibanaDataView, ManageScopeInit, SourcererScopeName } from './model'; +import { SourcererDataView, SourcererScopeName } from './model'; const actionCreator = actionCreatorFactory('x-pack/security_solution/local/sourcerer'); -export const setSource = actionCreator<{ - id: SourcererScopeName; - payload: ManageScopeInit; -}>('SET_SOURCE'); +export const setDataView = actionCreator<{ + browserFields: SourcererDataView['browserFields']; + docValueFields: SourcererDataView['docValueFields']; + id: SourcererDataView['id']; + indexFields: SourcererDataView['indexFields']; + loading: SourcererDataView['loading']; + runtimeMappings: SourcererDataView['runtimeMappings']; +}>('SET_DATA_VIEW'); + +export const setDataViewLoading = actionCreator<{ + id: string; + loading: boolean; +}>('SET_DATA_VIEW_LOADING'); export const setSignalIndexName = actionCreator<{ signalIndexName: string }>('SET_SIGNAL_INDEX_NAME'); export const setSourcererDataViews = actionCreator<{ - defaultDataView: KibanaDataView; - kibanaDataViews: KibanaDataView[]; + defaultDataView: SourcererDataView; + kibanaDataViews: SourcererDataView[]; }>('SET_SOURCERER_DATA_VIEWS'); export const setSourcererScopeLoading = actionCreator<{ diff --git a/x-pack/plugins/security_solution/public/common/store/sourcerer/helpers.test.ts b/x-pack/plugins/security_solution/public/common/store/sourcerer/helpers.test.ts index 513e36a9c6a23..83a556442f849 100644 --- a/x-pack/plugins/security_solution/public/common/store/sourcerer/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/common/store/sourcerer/helpers.test.ts @@ -16,7 +16,7 @@ import { const signalIndexName = mockGlobalState.sourcerer.signalIndexName; const dataView = { - id: mockGlobalState.sourcerer.defaultDataView.id, + ...mockGlobalState.sourcerer.defaultDataView, title: `auditbeat-*,packetbeat-*,${signalIndexName}`, patternList: ['packetbeat-*', 'auditbeat-*', `${signalIndexName}`], }; diff --git a/x-pack/plugins/security_solution/public/common/store/sourcerer/helpers.ts b/x-pack/plugins/security_solution/public/common/store/sourcerer/helpers.ts index c23e08887ef4d..1b4efa72127f3 100644 --- a/x-pack/plugins/security_solution/public/common/store/sourcerer/helpers.ts +++ b/x-pack/plugins/security_solution/public/common/store/sourcerer/helpers.ts @@ -6,7 +6,7 @@ */ import { isEmpty } from 'lodash'; -import { KibanaDataView, SourcererModel, SourcererScopeById, SourcererScopeName } from './model'; +import { SourcererDataView, SourcererModel, SourcererScopeById, SourcererScopeName } from './model'; import { TimelineEventsType } from '../../../../common'; import { SelectedDataViewPayload } from './actions'; @@ -18,7 +18,7 @@ export interface Args { } export const getScopePatternListSelection = ( - theDataView: KibanaDataView | undefined, + theDataView: SourcererDataView | undefined, sourcererScope: SourcererScopeName, signalIndexName: SourcererModel['signalIndexName'], isDefaultDataView: boolean @@ -83,7 +83,7 @@ export const validateSelectedPatterns = ( } } - // TO DO: If dataView is still undefined here, create temporary dataView + // TO DO: Steph/sourcerer If dataView is still undefined here, create temporary dataView // and prompt user to go create this dataView // currently UI will take the undefined dataView and default to defaultDataView anyways // this is a "strategically merged" bug ;) diff --git a/x-pack/plugins/security_solution/public/common/store/sourcerer/model.ts b/x-pack/plugins/security_solution/public/common/store/sourcerer/model.ts index 2bc0b35b3972d..e670a9c802779 100644 --- a/x-pack/plugins/security_solution/public/common/store/sourcerer/model.ts +++ b/x-pack/plugins/security_solution/public/common/store/sourcerer/model.ts @@ -6,83 +6,127 @@ */ import { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { IIndexPattern } from '../../../../../../../src/plugins/data/common'; import { BrowserFields, DocValueFields, EMPTY_BROWSER_FIELDS, EMPTY_DOCVALUE_FIELD, - EMPTY_INDEX_PATTERN, + EMPTY_INDEX_FIELDS, } from '../../../../../timelines/common'; - -export type ErrorModel = Error[]; - +import { SecuritySolutionDataViewBase } from '../../types'; +/** Uniquely identifies a Sourcerer Scope */ export enum SourcererScopeName { default = 'default', detections = 'detections', timeline = 'timeline', } -export interface ManageScope { - browserFields: BrowserFields; - docValueFields: DocValueFields[]; - errorMessage: string | null; +/** + * Data related to each sourcerer scope + */ +export interface SourcererScope { + /** Uniquely identifies a Sourcerer Scope */ id: SourcererScopeName; - // the index pattern value passed to the search to make the query - // includes fields and a title with active index names - indexPattern: Omit; - indicesExist: boolean | undefined | null; + /** is an update being made to the sourcerer data view */ loading: boolean; - // Remove once issue resolved: https://github.com/elastic/kibana/issues/111762 - runtimeMappings: MappingRuntimeFields; + /** selected data view id */ selectedDataViewId: string; + /** selected patterns within the data view */ selectedPatterns: string[]; } -export interface ManageScopeInit extends Partial { - id: SourcererScopeName; -} - -export type SourcererScopeById = Record; - -export interface KibanaDataView { - /** Uniquely identifies a Kibana Data View */ +export type SourcererScopeById = Record; +/** + * DataView from Kibana + timelines/index_fields enhanced field data + */ +export interface SourcererDataView { + /** we need this for @timestamp data */ + browserFields: BrowserFields; + /** we need this for @timestamp data */ + docValueFields: DocValueFields[]; + /** Uniquely identifies a Kibana Data View */ id: string; + /** comes from dataView.fields.toSpec() */ + indexFields: SecuritySolutionDataViewBase['fields']; + /** set when data view fields are fetched */ + loading: boolean; /** list of active patterns that return data */ patternList: string[]; /** * title of Kibana Data View * title also serves as "all pattern list", including inactive + * comma separated string */ title: string; + /** + * Needed to pass to search strategy + * Remove once issue resolved: https://github.com/elastic/kibana/issues/111762 + */ + runtimeMappings: MappingRuntimeFields; +} + +/** + * Combined data from SourcererDataView and SourcererScope to create + * selected data view state + */ +export interface SelectedDataView { + browserFields: SourcererDataView['browserFields']; + dataViewId: SourcererDataView['id']; + docValueFields: SourcererDataView['docValueFields']; + /** + * DataViewBase with enhanced index fields used in timelines + */ + indexPattern: SecuritySolutionDataViewBase; + /** do the selected indices exist */ + indicesExist: boolean; + /** is an update being made to the data view */ + loading: boolean; + /** all active & inactive patterns from SourcererDataView['title'] */ + patternList: string[]; + runtimeMappings: SourcererDataView['runtimeMappings']; + /** all selected patterns from SourcererScope['selectedPatterns'] */ + selectedPatterns: string[]; } -// ManageSourcerer +/** + * sourcerer model for redux + */ export interface SourcererModel { - defaultDataView: KibanaDataView; - kibanaDataViews: KibanaDataView[]; + /** default security-solution data view */ + defaultDataView: SourcererDataView & { error?: unknown }; + /** all Kibana data views, including security-solution */ + kibanaDataViews: SourcererDataView[]; + /** security solution signals index name */ signalIndexName: string | null; + /** sourcerer scope data by id */ sourcererScopes: SourcererScopeById; } -export const initSourcererScope: Omit = { +export type SourcererUrlState = Partial<{ + [id in SourcererScopeName]: { + id: string; + selectedPatterns: string[]; + }; +}>; + +export const initSourcererScope: Omit = { + loading: false, + selectedDataViewId: '', + selectedPatterns: [], +}; +export const initDataView = { browserFields: EMPTY_BROWSER_FIELDS, docValueFields: EMPTY_DOCVALUE_FIELD, - errorMessage: null, - indexPattern: EMPTY_INDEX_PATTERN, - indicesExist: true, + id: '', + indexFields: EMPTY_INDEX_FIELDS, loading: false, + patternList: [], runtimeMappings: {}, - selectedDataViewId: '', - selectedPatterns: [], + title: '', }; export const initialSourcererState: SourcererModel = { - defaultDataView: { - id: '', - title: '', - patternList: [], - }, + defaultDataView: initDataView, kibanaDataViews: [], signalIndexName: null, sourcererScopes: { @@ -100,11 +144,3 @@ export const initialSourcererState: SourcererModel = { }, }, }; - -export type FSourcererScopePatterns = { - [id in SourcererScopeName]: { - id: string; - selectedPatterns: string[]; - }; -}; -export type SourcererScopePatterns = Partial; diff --git a/x-pack/plugins/security_solution/public/common/store/sourcerer/reducer.ts b/x-pack/plugins/security_solution/public/common/store/sourcerer/reducer.ts index deafa5185318a..c93e8466a1d0d 100644 --- a/x-pack/plugins/security_solution/public/common/store/sourcerer/reducer.ts +++ b/x-pack/plugins/security_solution/public/common/store/sourcerer/reducer.ts @@ -12,7 +12,8 @@ import { setSourcererScopeLoading, setSelectedDataView, setSignalIndexName, - setSource, + setDataView, + setDataViewLoading, } from './actions'; import { initialSourcererState, SourcererModel, SourcererScopeName } from './model'; import { validateSelectedPatterns } from './helpers'; @@ -24,10 +25,25 @@ export const sourcererReducer = reducerWithInitialState(initialSourcererState) ...state, signalIndexName, })) + .case(setDataViewLoading, (state, { id, loading }) => ({ + ...state, + ...(id === state.defaultDataView.id + ? { + defaultDataView: { ...state.defaultDataView, loading }, + } + : {}), + kibanaDataViews: state.kibanaDataViews.map((dv) => (dv.id === id ? { ...dv, loading } : dv)), + })) .case(setSourcererDataViews, (state, { defaultDataView, kibanaDataViews }) => ({ ...state, - defaultDataView, - kibanaDataViews, + defaultDataView: { + ...state.defaultDataView, + ...defaultDataView, + }, + kibanaDataViews: kibanaDataViews.map((dataView) => ({ + ...(kibanaDataViews.find(({ id }) => id === dataView.id) ?? {}), + ...dataView, + })), })) .case(setSourcererScopeLoading, (state, { id, loading }) => ({ ...state, @@ -63,14 +79,15 @@ export const sourcererReducer = reducerWithInitialState(initialSourcererState) ...validateSelectedPatterns(state, payload), }, })) - .case(setSource, (state, { id, payload }) => ({ + .case(setDataView, (state, dataView) => ({ ...state, - sourcererScopes: { - ...state.sourcererScopes, - [id]: { - ...state.sourcererScopes[id], - ...payload, - }, - }, + ...(dataView.id === state.defaultDataView.id + ? { + defaultDataView: { ...state.defaultDataView, ...dataView }, + } + : {}), + kibanaDataViews: state.kibanaDataViews.map((dv) => + dv.id === dataView.id ? { ...dv, ...dataView } : dv + ), })) .build(); diff --git a/x-pack/plugins/security_solution/public/common/store/sourcerer/selectors.test.ts b/x-pack/plugins/security_solution/public/common/store/sourcerer/selectors.test.ts deleted file mode 100644 index 3e29ce27654a3..0000000000000 --- a/x-pack/plugins/security_solution/public/common/store/sourcerer/selectors.test.ts +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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 { cloneDeep } from 'lodash/fp'; -import { mockGlobalState } from '../../mock'; -import { SourcererScopeName } from './model'; -import { getSourcererScopeSelector } from './selectors'; - -describe('Sourcerer selectors', () => { - describe('getSourcererScopeSelector', () => { - it('Should exclude elastic cloud alias when selected patterns include "logs-*" as an alias', () => { - const mapStateToProps = getSourcererScopeSelector(); - expect(mapStateToProps(mockGlobalState, SourcererScopeName.default).selectedPatterns).toEqual( - [ - ...mockGlobalState.sourcerer.sourcererScopes.default.selectedPatterns, - '-*elastic-cloud-logs-*', - ] - ); - }); - - it('Should NOT exclude elastic cloud alias when selected patterns does NOT include "logs-*" as an alias', () => { - const mapStateToProps = getSourcererScopeSelector(); - const myMockGlobalState = cloneDeep(mockGlobalState); - myMockGlobalState.sourcerer.sourcererScopes.default.selectedPatterns = [ - 'apm-*-transaction*', - 'auditbeat-*', - 'endgame-*', - 'filebeat-*', - 'packetbeat-*', - 'traces-apm*', - 'winlogbeat-*', - ]; - expect( - mapStateToProps(myMockGlobalState, SourcererScopeName.default).selectedPatterns - ).toEqual([ - 'apm-*-transaction*', - 'auditbeat-*', - 'endgame-*', - 'filebeat-*', - 'packetbeat-*', - 'traces-apm*', - 'winlogbeat-*', - ]); - }); - - it('Should NOT exclude elastic cloud alias when selected patterns include "logs-endpoint.event-*" as an alias', () => { - const mapStateToProps = getSourcererScopeSelector(); - const myMockGlobalState = cloneDeep(mockGlobalState); - myMockGlobalState.sourcerer.sourcererScopes.default.selectedPatterns = [ - 'apm-*-transaction*', - 'auditbeat-*', - 'endgame-*', - 'filebeat-*', - 'packetbeat-*', - 'traces-apm*', - 'winlogbeat-*', - 'logs-endpoint.event-*', - ]; - expect( - mapStateToProps(myMockGlobalState, SourcererScopeName.default).selectedPatterns - ).toEqual([ - 'apm-*-transaction*', - 'auditbeat-*', - 'endgame-*', - 'filebeat-*', - 'logs-endpoint.event-*', - 'packetbeat-*', - 'traces-apm*', - 'winlogbeat-*', - ]); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/common/store/sourcerer/selectors.ts b/x-pack/plugins/security_solution/public/common/store/sourcerer/selectors.ts index 46b586f88189d..b72d7bfde2dcc 100644 --- a/x-pack/plugins/security_solution/public/common/store/sourcerer/selectors.ts +++ b/x-pack/plugins/security_solution/public/common/store/sourcerer/selectors.ts @@ -5,25 +5,34 @@ * 2.0. */ -import memoizeOne from 'memoize-one'; import { createSelector } from 'reselect'; import { State } from '../types'; -import { KibanaDataView, ManageScope, SourcererScopeById, SourcererScopeName } from './model'; -import { getScopePatternListSelection } from './helpers'; - -export const sourcererKibanaDataViewsSelector = ({ sourcerer }: State): KibanaDataView[] => - sourcerer.kibanaDataViews; +import { + SourcererDataView, + SourcererModel, + SourcererScope, + SourcererScopeById, + SourcererScopeName, +} from './model'; + +export const sourcererKibanaDataViewsSelector = ({ + sourcerer, +}: State): SourcererModel['kibanaDataViews'] => sourcerer.kibanaDataViews; export const sourcererSignalIndexNameSelector = ({ sourcerer }: State): string | null => sourcerer.signalIndexName; -export const sourcererDefaultDataViewSelector = ({ sourcerer }: State): KibanaDataView => - sourcerer.defaultDataView; +export const sourcererDefaultDataViewSelector = ({ + sourcerer, +}: State): SourcererModel['defaultDataView'] => sourcerer.defaultDataView; + +export const dataViewSelector = ({ sourcerer }: State, id: string): SourcererDataView => + sourcerer.kibanaDataViews.find((dataView) => dataView.id === id) ?? sourcerer.defaultDataView; export const sourcererScopeIdSelector = ( { sourcerer }: State, scopeId: SourcererScopeName -): ManageScope => sourcerer.sourcererScopes[scopeId]; +): SourcererScope => sourcerer.sourcererScopes[scopeId]; export const scopeIdSelector = () => createSelector(sourcererScopeIdSelector, (scope) => scope); @@ -33,90 +42,42 @@ export const sourcererScopesSelector = ({ sourcerer }: State): SourcererScopeByI export const scopesSelector = () => createSelector(sourcererScopesSelector, (scopes) => scopes); export const kibanaDataViewsSelector = () => - createSelector(sourcererKibanaDataViewsSelector, (patterns) => patterns); + createSelector(sourcererKibanaDataViewsSelector, (dataViews) => dataViews); export const signalIndexNameSelector = () => createSelector(sourcererSignalIndexNameSelector, (signalIndexName) => signalIndexName); export const defaultDataViewSelector = () => - createSelector(sourcererDefaultDataViewSelector, (patterns) => patterns); + createSelector(sourcererDefaultDataViewSelector, (dataViews) => dataViews); + +export const sourcererDataViewSelector = () => + createSelector(dataViewSelector, (dataView) => dataView); -export interface SelectedDataView { - dataViewId: string; - patternList: string[]; - selectedPatterns: string[]; +export interface SourcererScopeSelector extends Omit { + sourcererDataView: SourcererDataView; + sourcererScope: SourcererScope; } -// tested via containers/source/index.test.tsx -export const getSelectedDataViewSelector = () => { - const getScopeSelector = scopeIdSelector(); +export const getSourcererScopeSelector = () => { + const getKibanaDataViewsSelector = kibanaDataViewsSelector(); const getDefaultDataViewSelector = defaultDataViewSelector(); const getSignalIndexNameSelector = signalIndexNameSelector(); - const getKibanaDataViewsSelector = kibanaDataViewsSelector(); + const getSourcererDataViewSelector = sourcererDataViewSelector(); + const getScopeSelector = scopeIdSelector(); - return (state: State, scopeId: SourcererScopeName): SelectedDataView => { - const scope = getScopeSelector(state, scopeId); - const defaultDataView = getDefaultDataViewSelector(state); + return (state: State, scopeId: SourcererScopeName): SourcererScopeSelector => { const kibanaDataViews = getKibanaDataViewsSelector(state); + const defaultDataView = getDefaultDataViewSelector(state); const signalIndexName = getSignalIndexNameSelector(state); - const dataViewId = - scope.selectedDataViewId === null ? defaultDataView.id : scope.selectedDataViewId; - const theDataView = kibanaDataViews.find((dataView) => dataView.id === dataViewId); - - const patternList = theDataView != null ? theDataView.title.split(',') : []; - - let selectedPatterns: string[] = scope.selectedPatterns; - if (selectedPatterns.length === 0) { - if (scopeId === SourcererScopeName.detections && signalIndexName != null) { - selectedPatterns = [signalIndexName]; - } else if (scopeId !== SourcererScopeName.detections && theDataView != null) { - selectedPatterns = getScopePatternListSelection( - theDataView, - scopeId, - signalIndexName, - theDataView.id === defaultDataView.id - ); - } - } - - return { - dataViewId, - // all patterns in DATA_VIEW - patternList, - // selected patterns in DATA_VIEW - selectedPatterns, - }; - }; -}; - -const EXCLUDE_ELASTIC_CLOUD_INDEX = '-*elastic-cloud-logs-*'; - -export const getSourcererScopeSelector = () => { - const getScopeIdSelector = scopeIdSelector(); - const getSelectedPatterns = memoizeOne((selectedPatternsStr: string[]): string[] => { - const selectedPatterns = selectedPatternsStr.length > 0 ? selectedPatternsStr.sort() : []; - return selectedPatterns.some((index) => index === 'logs-*') - ? [...selectedPatterns, EXCLUDE_ELASTIC_CLOUD_INDEX] - : selectedPatterns; - }); - - const getDataView = memoizeOne( - (indexPattern, title) => ({ - ...indexPattern, - title, - }), - (newArgs, lastArgs) => newArgs[0] === lastArgs[0] && newArgs[1].length === lastArgs[1].length - ); - - return (state: State, scopeId: SourcererScopeName): ManageScope => { - const scope = getScopeIdSelector(state, scopeId); - const selectedPatterns = getSelectedPatterns(scope.selectedPatterns); - const indexPattern = getDataView(scope.indexPattern, selectedPatterns.join()); + const scope = getScopeSelector(state, scopeId); + const sourcererDataView = getSourcererDataViewSelector(state, scope.selectedDataViewId); return { - ...scope, - selectedPatterns, - indexPattern, + defaultDataView, + kibanaDataViews, + signalIndexName, + sourcererDataView, + sourcererScope: scope, }; }; }; diff --git a/x-pack/plugins/security_solution/public/common/types.ts b/x-pack/plugins/security_solution/public/common/types.ts index 7d7dab6e4fc3c..83a93769603e7 100644 --- a/x-pack/plugins/security_solution/public/common/types.ts +++ b/x-pack/plugins/security_solution/public/common/types.ts @@ -6,6 +6,9 @@ */ import { ResponseErrorAttributes } from 'kibana/server'; +import { DataViewBase } from '@kbn/es-query'; +import { FieldSpec } from '../../../../../src/plugins/data_views/common'; + export interface ServerApiError { statusCode: number; error: string; @@ -16,3 +19,10 @@ export interface ServerApiError { export interface SecuritySolutionUiConfigType { enableExperimental: string[]; } + +/** + * DataViewBase with enhanced index fields used in timelines + */ +export interface SecuritySolutionDataViewBase extends DataViewBase { + fields: FieldSpec[]; +} diff --git a/x-pack/plugins/security_solution/public/common/utils/empty_view/use_show_pages_with_empty_view.test.tsx b/x-pack/plugins/security_solution/public/common/utils/empty_view/use_show_pages_with_empty_view.test.tsx index 981f7e9e876ea..01dbfbed6a0c2 100644 --- a/x-pack/plugins/security_solution/public/common/utils/empty_view/use_show_pages_with_empty_view.test.tsx +++ b/x-pack/plugins/security_solution/public/common/utils/empty_view/use_show_pages_with_empty_view.test.tsx @@ -16,7 +16,7 @@ jest.mock('../route/use_route_spy', () => ({ .mockImplementationOnce(() => [{ pageName: 'network' }]), })); jest.mock('../../../common/containers/sourcerer', () => ({ - useSourcererScope: jest + useSourcererDataView: jest .fn() .mockImplementationOnce(() => [{ indicesExist: false }]) .mockImplementationOnce(() => [{ indicesExist: false }]) diff --git a/x-pack/plugins/security_solution/public/common/utils/empty_view/use_show_pages_with_empty_view.tsx b/x-pack/plugins/security_solution/public/common/utils/empty_view/use_show_pages_with_empty_view.tsx index 10cc4be10a61b..f863cecffe3d6 100644 --- a/x-pack/plugins/security_solution/public/common/utils/empty_view/use_show_pages_with_empty_view.tsx +++ b/x-pack/plugins/security_solution/public/common/utils/empty_view/use_show_pages_with_empty_view.tsx @@ -8,7 +8,7 @@ import { useState, useEffect } from 'react'; import { useRouteSpy } from '../route/use_route_spy'; import { SecurityPageName } from '../../../app/types'; -import { useSourcererScope } from '../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../containers/sourcerer'; // Used to detect if we're on a top level page that is empty and set page background color to match the subdued Empty State const isPageNameWithEmptyView = (currentName: string) => { @@ -23,7 +23,7 @@ const isPageNameWithEmptyView = (currentName: string) => { export const useShowPagesWithEmptyView = () => { const [{ pageName }] = useRouteSpy(); - const { indicesExist } = useSourcererScope(); + const { indicesExist } = useSourcererDataView(); const shouldShowEmptyState = isPageNameWithEmptyView(pageName) && !indicesExist; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx index 4bd516d0b1338..7777d075a4b9c 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx @@ -18,7 +18,7 @@ import { displaySuccessToast, useStateToaster, } from '../../../common/components/toasters'; -import { useSourcererScope } from '../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../../common/containers/sourcerer'; import { useAppToasts } from '../../../common/hooks/use_app_toasts'; import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; import { useInvalidFilterQuery } from '../../../common/hooks/use_invalid_filter_query'; @@ -100,7 +100,7 @@ export const AlertsTableComponent: React.FC = ({ indexPattern: indexPatterns, loading: indexPatternsLoading, selectedPatterns, - } = useSourcererScope(SourcererScopeName.detections); + } = useSourcererDataView(SourcererScopeName.detections); const kibana = useKibana(); const [, dispatchToaster] = useStateToaster(); const { addWarning } = useAppToasts(); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/autocomplete_field/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/autocomplete_field/index.tsx index 22fabcd15b194..91dbadbb209e1 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/autocomplete_field/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/autocomplete_field/index.tsx @@ -8,14 +8,14 @@ import React, { useCallback, useMemo } from 'react'; import { EuiFormRow } from '@elastic/eui'; import { FieldComponent } from '@kbn/securitysolution-autocomplete'; -import { IndexPatternBase, IndexPatternFieldBase } from '@kbn/es-query'; +import { DataViewBase, DataViewFieldBase } from '@kbn/es-query'; import { FieldHook } from '../../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib'; interface AutocompleteFieldProps { dataTestSubj: string; field: FieldHook; idAria: string; - indices: IndexPatternBase; + indices: DataViewBase; isDisabled: boolean; fieldType: string; placeholder?: string; @@ -31,7 +31,7 @@ export const AutocompleteField = ({ placeholder, }: AutocompleteFieldProps) => { const handleFieldChange = useCallback( - ([newField]: IndexPatternFieldBase[]): void => { + ([newField]: DataViewFieldBase[]): void => { // TODO: Update onChange type in FieldComponent as newField can be undefined field.setValue(newField?.name ?? ''); }, diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.test.tsx index 62e787952aa73..1ab9f92bc6d80 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.test.tsx @@ -14,8 +14,8 @@ import { esFilters, FilterManager, UI_SETTINGS, - IndexPattern, } from '../../../../../../../../src/plugins/data/public'; +import { DataViewBase } from '@kbn/es-query'; import { SeverityBadge } from '../severity_badge'; import * as i18n from './translations'; @@ -146,7 +146,7 @@ describe('helpers', () => { fields: [{ name: 'event.category', type: 'test type' }], title: 'test title', getFormatterForField: () => ({ convert: (val: unknown) => val }), - } as unknown as IndexPattern, + } as unknown as DataViewBase, }); const wrapper = shallow(result[0].description as React.ReactElement); const filterLabelComponent = wrapper.find(esFilters.FilterLabel).at(0); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.tsx index ab84f1a65cc4b..e403da9e49090 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.tsx @@ -11,12 +11,8 @@ import React, { memo, useState } from 'react'; import styled from 'styled-components'; import { ThreatMapping, Threats, Type } from '@kbn/securitysolution-io-ts-alerting-types'; -import { - IIndexPattern, - Filter, - esFilters, - FilterManager, -} from '../../../../../../../../src/plugins/data/public'; +import { DataViewBase } from '@kbn/es-query'; +import { Filter, esFilters, FilterManager } from '../../../../../../../../src/plugins/data/public'; import { DEFAULT_TIMELINE_TITLE } from '../../../../timelines/components/timeline/translations'; import { useKibana } from '../../../../common/lib/kibana'; import { AboutStepRiskScore, AboutStepSeverity } from '../../../pages/detection_engine/rules/types'; @@ -55,7 +51,7 @@ const DescriptionListContainer = styled(EuiDescriptionList)` interface StepRuleDescriptionProps { columns?: 'multi' | 'single' | 'singleSplit'; data: unknown; - indexPatterns?: IIndexPattern; + indexPatterns?: DataViewBase; schema: FormSchema; } @@ -129,7 +125,7 @@ export const buildListItems = ( data: unknown, schema: FormSchema, filterManager: FilterManager, - indexPatterns?: IIndexPattern + indexPatterns?: DataViewBase ): ListItems[] => Object.keys(schema).reduce( (acc, field) => [ @@ -161,7 +157,7 @@ export const getDescriptionItem = ( label: string, data: unknown, filterManager: FilterManager, - indexPatterns?: IIndexPattern + indexPatterns?: DataViewBase ): ListItems[] => { if (field === 'queryBar') { const filters = addFilterStateIfNotThere(get('queryBar.filters', data) ?? []); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/types.ts b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/types.ts index dc5e7fea8e771..02c25e5c12bee 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/types.ts +++ b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/types.ts @@ -7,12 +7,8 @@ import { ReactNode } from 'react'; import { Threats } from '@kbn/securitysolution-io-ts-alerting-types'; - -import { - IIndexPattern, - Filter, - FilterManager, -} from '../../../../../../../../src/plugins/data/public'; +import { DataViewBase } from '@kbn/es-query'; +import { Filter, FilterManager } from '../../../../../../../../src/plugins/data/public'; export interface ListItems { title: NonNullable; @@ -25,7 +21,7 @@ export interface BuildQueryBarDescription { filterManager: FilterManager; query: string; savedId: string; - indexPatterns?: IIndexPattern; + indexPatterns?: DataViewBase; queryLabel?: string; } diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/query_bar/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/query_bar/index.tsx index 6bda4a0e0f6b8..42f92f8cb6ce1 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/query_bar/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/query_bar/index.tsx @@ -10,14 +10,8 @@ import React, { useCallback, useEffect, useState } from 'react'; import { Subscription } from 'rxjs'; import styled from 'styled-components'; import deepEqual from 'fast-deep-equal'; - -import { - Filter, - IIndexPattern, - Query, - FilterManager, - SavedQuery, -} from '../../../../../../../../src/plugins/data/public'; +import { DataViewBase, Filter, Query } from '@kbn/es-query'; +import { FilterManager, SavedQuery } from '../../../../../../../../src/plugins/data/public'; import { BrowserFields } from '../../../../common/containers/source'; import { OpenTimelineModal } from '../../../../timelines/components/open_timeline/open_timeline_modal'; @@ -43,7 +37,7 @@ interface QueryBarDefineRuleProps { field: FieldHook; idAria: string; isLoading: boolean; - indexPattern: IIndexPattern; + indexPattern: DataViewBase; onCloseTimelineSearch: () => void; openTimelineSearch: boolean; resizeParentContainer?: (height: number) => void; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/risk_score_mapping/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/risk_score_mapping/index.tsx index e9cbcb6c38268..fbeea64cae7da 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/risk_score_mapping/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/risk_score_mapping/index.tsx @@ -21,7 +21,7 @@ import styled from 'styled-components'; import { noop } from 'lodash/fp'; import { RiskScoreMapping } from '@kbn/securitysolution-io-ts-alerting-types'; import { FieldComponent } from '@kbn/securitysolution-autocomplete'; -import { IndexPatternBase, IndexPatternFieldBase } from '@kbn/es-query'; +import { DataViewBase, DataViewFieldBase } from '@kbn/es-query'; import * as i18n from './translations'; import { FieldHook } from '../../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib'; import { AboutStepRiskScore } from '../../../pages/detection_engine/rules/types'; @@ -46,7 +46,7 @@ interface RiskScoreFieldProps { dataTestSubj: string; field: FieldHook; idAria: string; - indices: IndexPatternBase; + indices: DataViewBase; isDisabled: boolean; placeholder?: string; } @@ -78,7 +78,7 @@ export const RiskScoreField = ({ ); const handleRiskScoreMappingChange = useCallback( - ([newField]: IndexPatternFieldBase[]): void => { + ([newField]: DataViewFieldBase[]): void => { setValue({ value, isMappingChecked, @@ -231,8 +231,8 @@ export const RiskScoreField = ({ }; /** - * Looks for field metadata (IndexPatternFieldBase) in existing index pattern. - * If specified field doesn't exist, returns a stub IndexPatternFieldBase created based on the mapping -- + * Looks for field metadata (DataViewFieldBase) in existing index pattern. + * If specified field doesn't exist, returns a stub DataViewFieldBase created based on the mapping -- * because the field might not have been indexed yet, but we still need to display the mapping. * * @param mapping Mapping of a specified field name to risk score. @@ -240,8 +240,8 @@ export const RiskScoreField = ({ */ const getFieldTypeByMapping = ( mapping: RiskScoreMapping, - pattern: IndexPatternBase -): IndexPatternFieldBase => { + pattern: DataViewBase +): DataViewFieldBase => { const field = mapping?.[0]?.field ?? ''; const [knownFieldType] = pattern.fields.filter(({ name }) => field != null && field === name); return knownFieldType ?? { name: field, type: 'number' }; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/severity_mapping/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/severity_mapping/index.tsx index 4eca6f32d0c1f..f6578fab0c5fb 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/severity_mapping/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/severity_mapping/index.tsx @@ -29,7 +29,7 @@ import { AutocompleteFieldMatchComponent, } from '@kbn/securitysolution-autocomplete'; -import { IndexPatternBase, IndexPatternFieldBase } from '@kbn/es-query'; +import { DataViewBase, DataViewFieldBase } from '@kbn/es-query'; import * as i18n from './translations'; import { FieldHook } from '../../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib'; import { SeverityOptionItem } from '../step_about_rule/data'; @@ -56,7 +56,7 @@ interface SeverityFieldProps { dataTestSubj: string; field: FieldHook; idAria: string; - indices: IndexPatternBase; + indices: DataViewBase; isDisabled: boolean; options: SeverityOptionItem[]; } @@ -85,7 +85,7 @@ export const SeverityField = ({ ); const handleFieldChange = useCallback( - (index: number, severity: Severity, [newField]: IndexPatternFieldBase[]): void => { + (index: number, severity: Severity, [newField]: DataViewFieldBase[]): void => { const newMappingItems: SeverityMapping = [ { ...mapping[index], @@ -295,8 +295,8 @@ export const SeverityField = ({ }; /** - * Looks for field metadata (IndexPatternFieldBase) in existing index pattern. - * If specified field doesn't exist, returns a stub IndexPatternFieldBase created based on the mapping -- + * Looks for field metadata (DataViewFieldBase) in existing index pattern. + * If specified field doesn't exist, returns a stub DataViewFieldBase created based on the mapping -- * because the field might not have been indexed yet, but we still need to display the mapping. * * @param mapping Mapping of a specified field name + value to a certain severity value. @@ -304,8 +304,8 @@ export const SeverityField = ({ */ const getFieldTypeByMapping = ( mapping: SeverityMappingItem, - pattern: IndexPatternBase -): IndexPatternFieldBase => { + pattern: DataViewBase +): DataViewFieldBase => { const { field } = mapping; const [knownFieldType] = pattern.fields.filter(({ name }) => field === name); return knownFieldType ?? { name: field, type: 'string' }; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx index 65528d86a4027..6471faf657fa0 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx @@ -10,7 +10,6 @@ import React, { FC, memo, useCallback, useState, useEffect, useMemo } from 'reac import styled from 'styled-components'; import { isEqual } from 'lodash'; -import { IndexPattern } from 'src/plugins/data/public'; import { DEFAULT_INDEX_KEY, DEFAULT_THREAT_INDEX_KEY, @@ -324,10 +323,10 @@ const StepDefineRuleComponent: FC = ({ ({ threatMapping }) => ( >>; - threatIndexPatterns: IndexPattern; - indexPatterns: IndexPattern; + threatIndexPatterns: DataViewBase; + indexPatterns: DataViewBase; threatIndexPatternsLoading: boolean; threatIndexModified: boolean; handleResetThreatIndices: () => void; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx index 0d0c51bc540b4..eeb22a2aa071c 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx @@ -19,7 +19,7 @@ import { } from '../../../common/mock'; import { DetectionEnginePage } from './detection_engine'; import { useUserData } from '../../components/user_info'; -import { useSourcererScope } from '../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../../common/containers/sourcerer'; import { createStore, State } from '../../../common/store'; import { mockHistory, Router } from '../../../common/mock/router'; import { mockTimelines } from '../../../common/mock/mock_timelines_plugin'; @@ -110,7 +110,7 @@ describe('DetectionEnginePageComponent', () => { canUserREAD: true, }, ]); - (useSourcererScope as jest.Mock).mockReturnValue({ + (useSourcererDataView as jest.Mock).mockReturnValue({ indicesExist: true, indexPattern: {}, }); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx index 540fc0e95729c..204387f4a241b 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx @@ -66,7 +66,9 @@ import { buildShowBuildingBlockFilterRuleRegistry, buildThreatMatchFilter, } from '../../components/alerts_table/default_config'; -import { useSourcererScope } from '../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../../common/containers/sourcerer'; +import { useSignalHelpers } from '../../../common/containers/sourcerer/use_signal_helpers'; + import { SourcererScopeName } from '../../../common/store/sourcerer/model'; import { NeedAdminForUpdateRulesCallOut } from '../../components/callouts/need_admin_for_update_callout'; import { MissingPrivilegesCallOut } from '../../components/callouts/missing_privileges_callout'; @@ -78,8 +80,6 @@ import { FILTER_OPEN, } from '../../components/alerts_table/alerts_filter_group'; import { EmptyPage } from '../../../common/components/empty_page'; -import { sourcererSelectors } from '../../../common/store'; - /** * Need a 100% height here to account for the graph/analyze tool, which sets no explicit height parameters, but fills the available space. */ @@ -236,27 +236,9 @@ const DetectionEnginePageComponent: React.FC = ({ [setShowOnlyThreatIndicatorAlerts] ); - const { indicesExist, indexPattern } = useSourcererScope(SourcererScopeName.detections); + const { indexPattern } = useSourcererDataView(SourcererScopeName.detections); - // TODO: Steph/sourcerer, get below values from useSourcererDataView in issue#1730 - const getDefaultDataViewSelector = useMemo( - () => sourcererSelectors.defaultDataViewSelector(), - [] - ); - const getSignalIndexNameSelector = useMemo( - () => sourcererSelectors.signalIndexNameSelector(), - [] - ); - const signalIndexNameSourcerer = useDeepEqualSelector(getSignalIndexNameSelector); - const defaultDataView = useDeepEqualSelector(getDefaultDataViewSelector); - const { isSignalIndexNeedsInit, showResults } = useMemo( - () => ({ - isSignalIndexNeedsInit: - indicesExist === false && !defaultDataView.title.includes(`${signalIndexNameSourcerer}`), - showResults: indicesExist || defaultDataView.title.includes(`${signalIndexNameSourcerer}`), - }), - [defaultDataView.title, indicesExist, signalIndexNameSourcerer] - ); + const { signalIndexNeedsInit, pollForSignalIndex } = useSignalHelpers(); const onSkipFocusBeforeEventsTable = useCallback(() => { focusUtilityBarAction(containerElement.current); @@ -312,12 +294,12 @@ const DetectionEnginePageComponent: React.FC = ({ ); } - if ((!loading && isSignalIndexNeedsInit) || needsListsConfiguration) { + if ((!loading && signalIndexNeedsInit) || needsListsConfiguration) { return ( @@ -328,18 +310,22 @@ const DetectionEnginePageComponent: React.FC = ({ {hasEncryptionKey != null && !hasEncryptionKey && } - {showResults && (hasIndexRead === false || canUserREAD === false) ? ( + {!signalIndexNeedsInit && (hasIndexRead === false || canUserREAD === false) ? ( - ) : showResults && hasIndexRead && canUserREAD ? ( + ) : !signalIndexNeedsInit && hasIndexRead && canUserREAD ? ( - + { beforeAll(() => { (useUserData as jest.Mock).mockReturnValue([{}]); (useParams as jest.Mock).mockReturnValue({}); - (useSourcererScope as jest.Mock).mockReturnValue({ + (useSourcererDataView as jest.Mock).mockReturnValue({ indicesExist: true, indexPattern: {}, }); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx index 609ba2916ce40..9f56467043dd2 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx @@ -103,7 +103,7 @@ import { } from '../../../../../timelines/components/timeline/helpers'; import { timelineActions, timelineSelectors } from '../../../../../timelines/store/timeline'; import { timelineDefaults } from '../../../../../timelines/store/timeline/defaults'; -import { useSourcererScope } from '../../../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../../../../common/containers/sourcerer'; import { SourcererScopeName } from '../../../../../common/store/sourcerer/model'; import { getToolTipContent, @@ -126,6 +126,7 @@ import { AlertsTableFilterGroup, FILTER_OPEN, } from '../../../../components/alerts_table/alerts_filter_group'; +import { useSignalHelpers } from '../../../../../common/containers/sourcerer/use_signal_helpers'; /** * Need a 100% height here to account for the graph/analyze tool, which sets no explicit height parameters, but fills the available space. @@ -217,6 +218,7 @@ const RuleDetailsPageComponent: React.FC = ({ loading: ruleLoading, isExistingRule, } = useRuleWithFallback(ruleId); + const { pollForSignalIndex } = useSignalHelpers(); const [loadingStatus, ruleStatus, fetchRuleStatus] = useRuleStatus(ruleId); const [currentStatus, setCurrentStatus] = useState( ruleStatus?.current_status ?? null @@ -622,8 +624,7 @@ const RuleDetailsPageComponent: React.FC = ({ [setShowOnlyThreatIndicatorAlerts] ); - const { indexPattern } = useSourcererScope(SourcererScopeName.detections); - + const { indexPattern } = useSourcererDataView(SourcererScopeName.detections); const exceptionLists = useMemo((): { lists: ExceptionListIdentifiers[]; allowedExceptionListTypes: ExceptionListTypeEnum[]; @@ -698,7 +699,11 @@ const RuleDetailsPageComponent: React.FC = ({ - + diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx b/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx index 9bf046f40edb4..55c799f3bda43 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx @@ -47,11 +47,11 @@ import { Display } from '../display'; import { timelineSelectors } from '../../../timelines/store/timeline'; import { TimelineId } from '../../../../common/types/timeline'; import { timelineDefaults } from '../../../timelines/store/timeline/defaults'; -import { useSourcererScope } from '../../../common/containers/sourcerer'; import { useDeepEqualSelector, useShallowEqualSelector } from '../../../common/hooks/use_selector'; import { ID, useHostDetails } from '../../containers/hosts/details'; import { manageQuery } from '../../../common/components/page/manage_query'; import { useInvalidFilterQuery } from '../../../common/hooks/use_invalid_filter_query'; +import { useSourcererDataView } from '../../../common/containers/sourcerer'; const HostOverviewManage = manageQuery(HostOverview); @@ -97,7 +97,7 @@ const HostDetailsComponent: React.FC = ({ detailName, hostDeta [dispatch] ); - const { docValueFields, indicesExist, indexPattern, selectedPatterns } = useSourcererScope(); + const { docValueFields, indexPattern, indicesExist, selectedPatterns } = useSourcererDataView(); const [loading, { inspect, hostDetails: hostOverview, id, refetch }] = useHostDetails({ endDate: to, startDate: from, diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/types.ts b/x-pack/plugins/security_solution/public/hosts/pages/details/types.ts index 21924fe929320..cd9ee9ce01869 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/details/types.ts +++ b/x-pack/plugins/security_solution/public/hosts/pages/details/types.ts @@ -6,7 +6,7 @@ */ import { ActionCreator } from 'typescript-fsa'; -import { Query, IIndexPattern, Filter } from 'src/plugins/data/public'; +import { DataViewBase, Filter, Query } from '@kbn/es-query'; import { InputsModelId } from '../../../common/store/inputs/constants'; import { HostsTableType } from '../../store/model'; import { HostsQueryProps } from '../types'; @@ -62,7 +62,7 @@ export type HostDetailsTabsProps = HostBodyComponentDispatchProps & indexNames: string[]; pageFilters?: Filter[]; filterQuery?: string; - indexPattern: IIndexPattern; + indexPattern: DataViewBase; type: hostsModel.HostsType; }; diff --git a/x-pack/plugins/security_solution/public/hosts/pages/hosts.test.tsx b/x-pack/plugins/security_solution/public/hosts/pages/hosts.test.tsx index d05b091381cca..51dd9d230324f 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/hosts.test.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/hosts.test.tsx @@ -23,7 +23,7 @@ import { inputsActions } from '../../common/store/inputs'; import { State, createStore } from '../../common/store'; import { Hosts } from './hosts'; import { HostsTabs } from './hosts_tabs'; -import { useSourcererScope } from '../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../common/containers/sourcerer'; jest.mock('../../common/containers/sourcerer'); @@ -57,10 +57,10 @@ const mockHistory = { createHref: jest.fn(), listen: jest.fn(), }; -const mockUseSourcererScope = useSourcererScope as jest.Mock; +const mockUseSourcererDataView = useSourcererDataView as jest.Mock; describe('Hosts - rendering', () => { test('it renders the Setup Instructions text when no index is available', async () => { - mockUseSourcererScope.mockReturnValue({ + mockUseSourcererDataView.mockReturnValue({ indicesExist: false, }); @@ -75,7 +75,7 @@ describe('Hosts - rendering', () => { }); test('it DOES NOT render the Setup Instructions text when an index is available', async () => { - mockUseSourcererScope.mockReturnValue({ + mockUseSourcererDataView.mockReturnValue({ indicesExist: true, indexPattern: {}, }); @@ -90,7 +90,7 @@ describe('Hosts - rendering', () => { }); test('it should render tab navigation', async () => { - mockUseSourcererScope.mockReturnValue({ + mockUseSourcererDataView.mockReturnValue({ indicesExist: true, indexPattern: {}, }); @@ -137,7 +137,7 @@ describe('Hosts - rendering', () => { }, }, ]; - mockUseSourcererScope.mockReturnValue({ + mockUseSourcererDataView.mockReturnValue({ indicesExist: true, indexPattern: { fields: [], title: 'title' }, }); diff --git a/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx b/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx index b1c7c25c794c1..8417e8f3ae8b3 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx @@ -50,7 +50,7 @@ import { } from '../../timelines/components/timeline/helpers'; import { timelineSelectors } from '../../timelines/store/timeline'; import { timelineDefaults } from '../../timelines/store/timeline/defaults'; -import { useSourcererScope } from '../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../common/containers/sourcerer'; import { useDeepEqualSelector, useShallowEqualSelector } from '../../common/hooks/use_selector'; import { useInvalidFilterQuery } from '../../common/hooks/use_invalid_filter_query'; import { ID } from '../containers/hosts'; @@ -111,7 +111,7 @@ const HostsComponent = () => { }, [dispatch] ); - const { docValueFields, indicesExist, indexPattern, selectedPatterns } = useSourcererScope(); + const { docValueFields, indicesExist, indexPattern, selectedPatterns } = useSourcererDataView(); const [filterQuery, kqlError] = useMemo( () => convertToBuildEsQuery({ diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts index 05d28c1cbf27f..6e9c1f69cb060 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts @@ -7,6 +7,7 @@ import { Action } from 'redux'; import { EuiSuperDatePickerRecentRange } from '@elastic/eui'; +import { DataViewBase } from '@kbn/es-query'; import { HostResultList, HostInfo, @@ -17,7 +18,6 @@ import { import { ServerApiError } from '../../../../common/types'; import { GetPolicyListResponse } from '../../policy/types'; import { EndpointState } from '../types'; -import { IIndexPattern } from '../../../../../../../../src/plugins/data/public'; export interface ServerReturnedEndpointList { type: 'serverReturnedEndpointList'; @@ -96,7 +96,7 @@ export interface ServerReturnedEndpointExistValue { export interface ServerReturnedMetadataPatterns { type: 'serverReturnedMetadataPatterns'; - payload: IIndexPattern[]; + payload: DataViewBase[]; } export interface ServerFailedToReturnMetadataPatterns { diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts index 3506d2c22ce3f..ec9672b645ca3 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts @@ -9,6 +9,7 @@ import { Dispatch } from 'redux'; import semverGte from 'semver/functions/gte'; import { CoreStart, HttpStart } from 'kibana/public'; +import { DataViewBase, Query } from '@kbn/es-query'; import { ActivityLog, GetHostPolicyResponse, @@ -67,7 +68,6 @@ import { metadataCurrentIndexPattern, METADATA_UNITED_INDEX, } from '../../../../../common/endpoint/constants'; -import { IIndexPattern, Query } from '../../../../../../../../src/plugins/data/public'; import { createFailedResourceState, createLoadedResourceState, @@ -94,7 +94,7 @@ export const endpointMiddlewareFactory: ImmutableMiddlewareFactory - ): Promise { + ): Promise { const packageVersion = endpointPackageVersion(state) ?? ''; const parsedPackageVersion = packageVersion.includes('-') ? packageVersion.substring(0, packageVersion.indexOf('-')) @@ -108,7 +108,7 @@ export const endpointMiddlewareFactory: ImmutableMiddlewareFactory; coreStart: CoreStart; - fetchIndexPatterns: (state: ImmutableObject) => Promise; + fetchIndexPatterns: (state: ImmutableObject) => Promise; }) { const { getState, dispatch } = store; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts index 0fa96fe00fd2c..1a87223cdd01f 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts @@ -6,6 +6,7 @@ */ import { EuiSuperDatePickerRecentRange } from '@elastic/eui'; +import { DataViewBase } from '@kbn/es-query'; import { ActivityLog, HostInfo, @@ -20,7 +21,6 @@ import { } from '../../../../common/endpoint/types'; import { ServerApiError } from '../../../common/types'; import { GetPackagesResponse } from '../../../../../fleet/common'; -import { IIndexPattern } from '../../../../../../../src/plugins/data/public'; import { AsyncResourceState } from '../../state'; import { TRANSFORM_STATES } from '../../../../common/constants'; @@ -86,7 +86,7 @@ export interface EndpointState { /** Tracks whether hosts exist and helps control if onboarding should be visible */ endpointsExist: boolean; /** index patterns for query bar */ - patterns: IIndexPattern[]; + patterns: DataViewBase[]; /** api error from retrieving index patters for query bar */ patternsError?: ServerApiError; /** Is auto-refresh enabled */ diff --git a/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map.tsx b/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map.tsx index 3f0a910849b07..15a0f870d7bda 100644 --- a/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map.tsx +++ b/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map.tsx @@ -11,7 +11,6 @@ import React, { useEffect, useState, useMemo } from 'react'; import { createPortalNode, InPortal } from 'react-reverse-portal'; import styled, { css } from 'styled-components'; -import { useSelector } from 'react-redux'; import { ErrorEmbeddable, isErrorEmbeddable, @@ -30,12 +29,9 @@ import { MapEmbeddable } from '../../../../../../plugins/maps/public/embeddable' import { Query, Filter } from '../../../../../../../src/plugins/data/public'; import { useKibana } from '../../../common/lib/kibana'; import { getLayerList } from './map_config'; -import { - getSourcererScopeSelector, - SourcererScopeSelector, -} from '../../../timelines/components/timeline/search_or_filter/selectors'; -import { State } from '../../../common/store'; +import { sourcererSelectors } from '../../../common/store/sourcerer'; import { SourcererScopeName } from '../../../common/store/sourcerer/model'; +import { useDeepEqualSelector } from '../../../common/hooks/use_selector'; interface EmbeddableMapProps { maintainRatio?: boolean; @@ -100,11 +96,11 @@ export const EmbeddedMapComponent = ({ const [isIndexError, setIsIndexError] = useState(false); const [, dispatchToaster] = useStateToaster(); - const sourcererScopeSelector = useMemo(getSourcererScopeSelector, []); - const { kibanaDataViews, sourcererScope } = useSelector( - (state) => sourcererScopeSelector(state, SourcererScopeName.default), - deepEqual - ); + + const sourcererScopeSelector = useMemo(() => sourcererSelectors.getSourcererScopeSelector(), []); + const { kibanaDataViews, sourcererScope }: sourcererSelectors.SourcererScopeSelector = + useDeepEqualSelector((state) => sourcererScopeSelector(state, SourcererScopeName.default)); + const [mapIndexPatterns, setMapIndexPatterns] = useState( kibanaDataViews.filter((dataView) => sourcererScope.selectedPatterns.includes(dataView.title)) ); diff --git a/x-pack/plugins/security_solution/public/network/components/network_top_countries_table/columns.tsx b/x-pack/plugins/security_solution/public/network/components/network_top_countries_table/columns.tsx index 28953730ce3cf..4c50a3a0f49c5 100644 --- a/x-pack/plugins/security_solution/public/network/components/network_top_countries_table/columns.tsx +++ b/x-pack/plugins/security_solution/public/network/components/network_top_countries_table/columns.tsx @@ -8,8 +8,7 @@ import { get } from 'lodash/fp'; import numeral from '@elastic/numeral'; import React from 'react'; -import { IIndexPattern } from 'src/plugins/data/public'; - +import { DataViewBase } from '@kbn/es-query'; import { CountryFlagAndName } from '../source_destination/country_flag'; import { FlowTargetSourceDest, @@ -47,7 +46,7 @@ export type NetworkTopCountriesColumnsNetworkDetails = [ ]; export const getNetworkTopCountriesColumns = ( - indexPattern: IIndexPattern, + indexPattern: DataViewBase, flowTarget: FlowTargetSourceDest, type: networkModel.NetworkType, tableId: string @@ -161,7 +160,7 @@ export const getNetworkTopCountriesColumns = ( ]; export const getCountriesColumnsCurated = ( - indexPattern: IIndexPattern, + indexPattern: DataViewBase, flowTarget: FlowTargetSourceDest, type: networkModel.NetworkType, tableId: string diff --git a/x-pack/plugins/security_solution/public/network/components/network_top_countries_table/index.tsx b/x-pack/plugins/security_solution/public/network/components/network_top_countries_table/index.tsx index d4a611c7f557f..23b453c0f7384 100644 --- a/x-pack/plugins/security_solution/public/network/components/network_top_countries_table/index.tsx +++ b/x-pack/plugins/security_solution/public/network/components/network_top_countries_table/index.tsx @@ -9,7 +9,7 @@ import { last } from 'lodash/fp'; import React, { useCallback, useMemo } from 'react'; import { useDispatch } from 'react-redux'; import deepEqual from 'fast-deep-equal'; -import { IIndexPattern } from 'src/plugins/data/public'; +import { DataViewBase } from '@kbn/es-query'; import { networkActions, networkModel, networkSelectors } from '../../store'; import { @@ -31,7 +31,7 @@ interface NetworkTopCountriesTableProps { fakeTotalCount: number; flowTargeted: FlowTargetSourceDest; id: string; - indexPattern: IIndexPattern; + indexPattern: DataViewBase; isInspect: boolean; loading: boolean; loadPage: (newActivePage: number) => void; diff --git a/x-pack/plugins/security_solution/public/network/pages/details/index.test.tsx b/x-pack/plugins/security_solution/public/network/pages/details/index.test.tsx index 612acdc813c92..7d8389d95f33e 100644 --- a/x-pack/plugins/security_solution/public/network/pages/details/index.test.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/details/index.test.tsx @@ -10,7 +10,7 @@ import { Router, useParams } from 'react-router-dom'; import '../../../common/mock/match_media'; -import { useSourcererScope } from '../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../../common/containers/sourcerer'; import { mockGlobalState, TestProviders, @@ -87,7 +87,7 @@ const getMockHistory = (ip: string) => ({ describe('Network Details', () => { const mount = useMountAppended(); beforeAll(() => { - (useSourcererScope as jest.Mock).mockReturnValue({ + (useSourcererDataView as jest.Mock).mockReturnValue({ indicesExist: false, indexPattern: {}, }); @@ -131,7 +131,7 @@ describe('Network Details', () => { test('it renders ipv6 headline', async () => { const ip = 'fe80--24ce-f7ff-fede-a571'; - (useSourcererScope as jest.Mock).mockReturnValue({ + (useSourcererDataView as jest.Mock).mockReturnValue({ indicesExist: true, indexPattern: {}, }); diff --git a/x-pack/plugins/security_solution/public/network/pages/details/index.tsx b/x-pack/plugins/security_solution/public/network/pages/details/index.tsx index 5aee4022a7f30..e0ede2d23846e 100644 --- a/x-pack/plugins/security_solution/public/network/pages/details/index.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/details/index.tsx @@ -48,7 +48,7 @@ import { AnomaliesQueryTabBody } from '../../../common/containers/anomalies/anom import { esQuery } from '../../../../../../../src/plugins/data/public'; import { networkModel } from '../../store'; import { SecurityPageName } from '../../../app/types'; -import { useSourcererScope } from '../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../../common/containers/sourcerer'; import { useInvalidFilterQuery } from '../../../common/hooks/use_invalid_filter_query'; export { getBreadcrumbs } from './utils'; @@ -92,7 +92,7 @@ const NetworkDetailsComponent: React.FC = () => { dispatch(setNetworkDetailsTablesActivePageToZero()); }, [detailName, dispatch]); - const { docValueFields, indicesExist, indexPattern, selectedPatterns } = useSourcererScope(); + const { docValueFields, indicesExist, indexPattern, selectedPatterns } = useSourcererDataView(); const ip = decodeIpv6(detailName); const [filterQuery, kqlError] = convertToBuildEsQuery({ config: esQuery.getEsQueryConfig(uiSettings), diff --git a/x-pack/plugins/security_solution/public/network/pages/details/types.ts b/x-pack/plugins/security_solution/public/network/pages/details/types.ts index b91a22cbd5fc3..02722f4709bcc 100644 --- a/x-pack/plugins/security_solution/public/network/pages/details/types.ts +++ b/x-pack/plugins/security_solution/public/network/pages/details/types.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { IIndexPattern } from 'src/plugins/data/public'; +import { DataViewBase } from '@kbn/es-query'; import { ESTermQuery } from '../../../../common/typed_json'; import { NetworkType } from '../../store/model'; @@ -38,5 +38,5 @@ export type TlsQueryTableComponentProps = OwnProps & { export type NetworkWithIndexComponentsQueryTableProps = OwnProps & { flowTarget: FlowTargetSourceDest; - indexPattern: IIndexPattern; + indexPattern: DataViewBase; }; diff --git a/x-pack/plugins/security_solution/public/network/pages/navigation/types.ts b/x-pack/plugins/security_solution/public/network/pages/navigation/types.ts index 075aa46637a07..96b72caf03300 100644 --- a/x-pack/plugins/security_solution/public/network/pages/navigation/types.ts +++ b/x-pack/plugins/security_solution/public/network/pages/navigation/types.ts @@ -5,8 +5,8 @@ * 2.0. */ +import { DataViewBase } from '@kbn/es-query'; import { ESTermQuery } from '../../../../common/typed_json'; -import { IIndexPattern } from '../../../../../../../src/plugins/data/common'; import { NavTab } from '../../../common/components/navigation/types'; import { FlowTargetSourceDest } from '../../../../common/search_strategy/security_solution/network'; @@ -32,7 +32,7 @@ export type NetworkComponentQueryProps = QueryTabBodyProps & { }; export type IPsQueryTabBodyProps = QueryTabBodyProps & { - indexPattern: IIndexPattern; + indexPattern: DataViewBase; flowTarget: FlowTargetSourceDest; }; @@ -49,7 +49,7 @@ export type NetworkRoutesProps = GlobalTimeArgs & { docValueFields: DocValueFields[]; type: networkModel.NetworkType; filterQuery?: string | ESTermQuery; - indexPattern: IIndexPattern; + indexPattern: DataViewBase; indexNames: string[]; setAbsoluteRangeDatePicker: SetAbsoluteRangeDatePicker; }; diff --git a/x-pack/plugins/security_solution/public/network/pages/network.test.tsx b/x-pack/plugins/security_solution/public/network/pages/network.test.tsx index 764b8fcd0444b..d0fff882cdc86 100644 --- a/x-pack/plugins/security_solution/public/network/pages/network.test.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/network.test.tsx @@ -11,7 +11,7 @@ import { Router } from 'react-router-dom'; import { waitFor } from '@testing-library/react'; import '../../common/mock/match_media'; import { Filter } from '../../../../../../src/plugins/data/common/es_query'; -import { useSourcererScope } from '../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../common/containers/sourcerer'; import { TestProviders, mockGlobalState, @@ -70,10 +70,10 @@ const mockProps = { capabilitiesFetched: true, hasMlUserPermissions: true, }; -const mockUseSourcererScope = useSourcererScope as jest.Mock; +const mockUseSourcererDataView = useSourcererDataView as jest.Mock; describe('Network page - rendering', () => { test('it renders the Setup Instructions text when no index is available', () => { - mockUseSourcererScope.mockReturnValue({ + mockUseSourcererDataView.mockReturnValue({ selectedPatterns: [], indicesExist: false, }); @@ -89,7 +89,7 @@ describe('Network page - rendering', () => { }); test('it DOES NOT render the Setup Instructions text when an index is available', async () => { - mockUseSourcererScope.mockReturnValue({ + mockUseSourcererDataView.mockReturnValue({ selectedPatterns: [], indicesExist: true, indexPattern: {}, @@ -138,7 +138,7 @@ describe('Network page - rendering', () => { }, }, ]; - mockUseSourcererScope.mockReturnValue({ + mockUseSourcererDataView.mockReturnValue({ selectedPatterns: [], indicesExist: true, indexPattern: { fields: [], title: 'title' }, diff --git a/x-pack/plugins/security_solution/public/network/pages/network.tsx b/x-pack/plugins/security_solution/public/network/pages/network.tsx index af7f513afd744..f38a26da00599 100644 --- a/x-pack/plugins/security_solution/public/network/pages/network.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/network.tsx @@ -49,7 +49,7 @@ import { import { timelineSelectors } from '../../timelines/store/timeline'; import { TimelineId } from '../../../common/types/timeline'; import { timelineDefaults } from '../../timelines/store/timeline/defaults'; -import { useSourcererScope } from '../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../common/containers/sourcerer'; import { useDeepEqualSelector, useShallowEqualSelector } from '../../common/hooks/use_selector'; import { useInvalidFilterQuery } from '../../common/hooks/use_invalid_filter_query'; /** @@ -109,7 +109,7 @@ const NetworkComponent = React.memo( [dispatch] ); - const { docValueFields, indicesExist, indexPattern, selectedPatterns } = useSourcererScope(); + const { docValueFields, indicesExist, indexPattern, selectedPatterns } = useSourcererDataView(); const onSkipFocusBeforeEventsTable = useCallback(() => { containerElement.current diff --git a/x-pack/plugins/security_solution/public/overview/components/alerts_by_category/index.tsx b/x-pack/plugins/security_solution/public/overview/components/alerts_by_category/index.tsx index e74e8f82d8244..bce9dd9fa9d0c 100644 --- a/x-pack/plugins/security_solution/public/overview/components/alerts_by_category/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/alerts_by_category/index.tsx @@ -9,17 +9,13 @@ import numeral from '@elastic/numeral'; import React, { useEffect, useMemo, useCallback } from 'react'; import { Position } from '@elastic/charts'; +import { DataViewBase, Filter, Query } from '@kbn/es-query'; import { DEFAULT_NUMBER_FORMAT, APP_UI_ID } from '../../../../common/constants'; import { SHOWING, UNIT } from '../../../common/components/alerts_viewer/translations'; import { MatrixHistogram } from '../../../common/components/matrix_histogram'; import { useKibana, useUiSetting$ } from '../../../common/lib/kibana'; import { convertToBuildEsQuery } from '../../../common/lib/keury'; -import { - Filter, - esQuery, - IIndexPattern, - Query, -} from '../../../../../../../src/plugins/data/public'; +import { esQuery } from '../../../../../../../src/plugins/data/public'; import { HostsTableType } from '../../../hosts/store/model'; import * as i18n from '../../pages/translations'; @@ -42,7 +38,7 @@ const DEFAULT_STACK_BY = 'event.module'; interface Props extends Pick { filters: Filter[]; hideHeaderChildren?: boolean; - indexPattern: IIndexPattern; + indexPattern: DataViewBase; indexNames: string[]; query: Query; } diff --git a/x-pack/plugins/security_solution/public/overview/components/event_counts/index.tsx b/x-pack/plugins/security_solution/public/overview/components/event_counts/index.tsx index 919057d6b5eab..8ec38549477fa 100644 --- a/x-pack/plugins/security_solution/public/overview/components/event_counts/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/event_counts/index.tsx @@ -9,6 +9,7 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import React, { useMemo } from 'react'; import styled from 'styled-components'; +import { DataViewBase, Filter, Query } from '@kbn/es-query'; import { ID as OverviewHostQueryId } from '../../containers/overview_host'; import { OverviewHost } from '../overview_host'; import { OverviewNetwork } from '../overview_network'; @@ -16,12 +17,7 @@ import { filterHostData } from '../../../hosts/pages/navigation/alerts_query_tab import { useKibana } from '../../../common/lib/kibana'; import { convertToBuildEsQuery } from '../../../common/lib/keury'; import { filterNetworkData } from '../../../network/pages/navigation/alerts_query_tab_body'; -import { - Filter, - esQuery, - IIndexPattern, - Query, -} from '../../../../../../../src/plugins/data/public'; +import { esQuery } from '../../../../../../../src/plugins/data/public'; import { GlobalTimeArgs } from '../../../common/containers/use_global_time'; import { useInvalidFilterQuery } from '../../../common/hooks/use_invalid_filter_query'; @@ -32,7 +28,7 @@ const HorizontalSpacer = styled(EuiFlexItem)` interface Props extends Pick { filters: Filter[]; indexNames: string[]; - indexPattern: IIndexPattern; + indexPattern: DataViewBase; query: Query; } diff --git a/x-pack/plugins/security_solution/public/overview/components/events_by_dataset/index.tsx b/x-pack/plugins/security_solution/public/overview/components/events_by_dataset/index.tsx index 562d1d1fd7aad..a184ba572d77c 100644 --- a/x-pack/plugins/security_solution/public/overview/components/events_by_dataset/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/events_by_dataset/index.tsx @@ -10,6 +10,7 @@ import numeral from '@elastic/numeral'; import React, { useEffect, useMemo, useCallback } from 'react'; import uuid from 'uuid'; +import { DataViewBase, Filter, Query } from '@kbn/es-query'; import { DEFAULT_NUMBER_FORMAT, APP_UI_ID } from '../../../../common/constants'; import { SHOWING, UNIT } from '../../../common/components/events_viewer/translations'; import { getTabsOnHostsUrl } from '../../../common/components/link_to/redirect_to_hosts'; @@ -22,12 +23,7 @@ import { eventsStackByOptions } from '../../../hosts/pages/navigation'; import { convertToBuildEsQuery } from '../../../common/lib/keury'; import { useKibana, useUiSetting$ } from '../../../common/lib/kibana'; import { histogramConfigs } from '../../../hosts/pages/navigation/events_query_tab_body'; -import { - Filter, - esQuery, - IIndexPattern, - Query, -} from '../../../../../../../src/plugins/data/public'; +import { esQuery } from '../../../../../../../src/plugins/data/public'; import { HostsTableType } from '../../../hosts/store/model'; import { InputsModelId } from '../../../common/store/inputs/constants'; import { GlobalTimeArgs } from '../../../common/containers/use_global_time'; @@ -46,7 +42,7 @@ interface Props extends Pick { clearAllMessages: () => undefined, }; }; -const mockUseSourcererScope = useSourcererScope as jest.Mock; +const mockUseSourcererDataView = useSourcererDataView as jest.Mock; const mockUseUserPrivileges = useUserPrivileges as jest.Mock; const mockUseFetchIndex = useFetchIndex as jest.Mock; const mockUseMessagesStorage: jest.Mock = useMessagesStorage as jest.Mock; @@ -141,7 +141,7 @@ describe('Overview', () => { describe('rendering', () => { test('it DOES NOT render the Getting started text when an index is available', () => { - mockUseSourcererScope.mockReturnValue({ + mockUseSourcererDataView.mockReturnValue({ selectedPatterns: [], indicesExist: true, indexPattern: {}, @@ -168,7 +168,7 @@ describe('Overview', () => { indexExists: false, }, ]); - mockUseSourcererScope.mockReturnValue({ + mockUseSourcererDataView.mockReturnValue({ selectedPatterns: [], indicesExist: true, indexPattern: {}, @@ -195,7 +195,7 @@ describe('Overview', () => { indexExists: false, }, ]); - mockUseSourcererScope.mockReturnValueOnce({ + mockUseSourcererDataView.mockReturnValueOnce({ selectedPatterns: [], indicesExist: true, indexPattern: {}, @@ -216,7 +216,7 @@ describe('Overview', () => { }); test('it does NOT render the Endpoint banner when the endpoint index is available AND storage is set', () => { - mockUseSourcererScope.mockReturnValue({ + mockUseSourcererDataView.mockReturnValue({ selectedPatterns: [], indexExists: true, indexPattern: {}, @@ -237,7 +237,7 @@ describe('Overview', () => { }); test('it does NOT render the Endpoint banner when an index IS available but storage is NOT set', () => { - mockUseSourcererScope.mockReturnValue({ + mockUseSourcererDataView.mockReturnValue({ selectedPatterns: [], indicesExist: true, indexPattern: {}, @@ -258,7 +258,7 @@ describe('Overview', () => { }); test('it does NOT render the Endpoint banner when Ingest is NOT available', () => { - mockUseSourcererScope.mockReturnValue({ + mockUseSourcererDataView.mockReturnValue({ selectedPatterns: [], indicesExist: true, indexPattern: {}, @@ -281,7 +281,7 @@ describe('Overview', () => { describe('when no index is available', () => { beforeEach(() => { - mockUseSourcererScope.mockReturnValue({ + mockUseSourcererDataView.mockReturnValue({ selectedPatterns: [], indicesExist: false, }); diff --git a/x-pack/plugins/security_solution/public/overview/pages/overview.tsx b/x-pack/plugins/security_solution/public/overview/pages/overview.tsx index 10fa4e4c4e925..3a98f062db65d 100644 --- a/x-pack/plugins/security_solution/public/overview/pages/overview.tsx +++ b/x-pack/plugins/security_solution/public/overview/pages/overview.tsx @@ -27,7 +27,7 @@ import { SecurityPageName } from '../../app/types'; import { EndpointNotice } from '../components/endpoint_notice'; import { useMessagesStorage } from '../../common/containers/local_storage/use_messages_storage'; import { ENDPOINT_METADATA_INDEX } from '../../../common/constants'; -import { useSourcererScope } from '../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../common/containers/sourcerer'; import { Sourcerer } from '../../common/components/sourcerer'; import { SourcererScopeName } from '../../common/store/sourcerer/model'; import { useDeepEqualSelector } from '../../common/hooks/use_selector'; @@ -56,7 +56,7 @@ const OverviewComponent = () => { const filters = useDeepEqualSelector(getGlobalFiltersQuerySelector); const { from, deleteQuery, setQuery, to } = useGlobalTime(); - const { indicesExist, indexPattern, selectedPatterns } = useSourcererScope(); + const { indicesExist, indexPattern, selectedPatterns } = useSourcererDataView(); const endpointMetadataIndex = useMemo(() => { return [ENDPOINT_METADATA_INDEX]; diff --git a/x-pack/plugins/security_solution/public/plugin.tsx b/x-pack/plugins/security_solution/public/plugin.tsx index 54626eee1ffdc..bc3823bf417dd 100644 --- a/x-pack/plugins/security_solution/public/plugin.tsx +++ b/x-pack/plugins/security_solution/public/plugin.tsx @@ -62,7 +62,7 @@ import { } from '../common/experimental_features'; import type { TimelineState } from '../../timelines/public'; import { LazyEndpointCustomAssetsExtension } from './management/pages/policy/view/ingest_manager_integration/lazy_endpoint_custom_assets_extension'; -import { KibanaDataView } from './common/store/sourcerer/model'; +import { initDataView, SourcererModel, SourcererDataView } from './common/store/sourcerer/model'; export class Plugin implements IPlugin { readonly kibanaVersion: string; @@ -337,15 +337,7 @@ export class Plugin implements IPlugin ({ + ...initDataView, + ...dataView, + })); } catch (error) { - defaultDataView = { id: null, ...error }; + defaultDataView = { ...initDataView, error }; kibanaDataViews = []; } const { createStore, createInitialState } = await this.lazyApplicationDependencies(); diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.test.tsx index 206fcb2dc087c..7b103fae483b1 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.test.tsx @@ -12,12 +12,12 @@ import { TestProviders, mockIndexNames, mockIndexPattern } from '../../../../com import { TimelineId } from '../../../../../common/types/timeline'; import { useTimelineKpis } from '../../../containers/kpis'; import { FlyoutHeader } from '.'; -import { useSourcererScope } from '../../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../../../common/containers/sourcerer'; import { mockBrowserFields, mockDocValueFields } from '../../../../common/containers/source/mock'; import { useMountAppended } from '../../../../common/utils/use_mount_appended'; import { getEmptyValue } from '../../../../common/components/empty_value'; -const mockUseSourcererScope: jest.Mock = useSourcererScope as jest.Mock; +const mockUseSourcererDataView: jest.Mock = useSourcererDataView as jest.Mock; jest.mock('../../../../common/containers/sourcerer'); const mockUseTimelineKpis: jest.Mock = useTimelineKpis as jest.Mock; @@ -62,7 +62,7 @@ describe('header', () => { beforeEach(() => { // Mocking these services is required for the header component to render. - mockUseSourcererScope.mockImplementation(() => defaultMocks); + mockUseSourcererDataView.mockImplementation(() => defaultMocks); useKibanaMock().services.application.capabilities = { navLinks: {}, management: {}, diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx index 1fdfb744f3071..2f54cd6ce2962 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx @@ -40,7 +40,7 @@ import { useGetUserCasesPermissions, useKibana } from '../../../../common/lib/ki import { InspectButton } from '../../../../common/components/inspect'; import { useTimelineKpis } from '../../../containers/kpis'; import { esQuery } from '../../../../../../../../src/plugins/data/public'; -import { useSourcererScope } from '../../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../../../common/containers/sourcerer'; import { TimelineModel } from '../../../../timelines/store/timeline/model'; import { startSelector, @@ -76,7 +76,7 @@ const ActiveTimelinesContainer = styled(EuiFlexItem)` const FlyoutHeaderPanelComponent: React.FC = ({ timelineId }) => { const dispatch = useDispatch(); - const { indexPattern, browserFields } = useSourcererScope(SourcererScopeName.timeline); + const { browserFields, indexPattern } = useSourcererDataView(SourcererScopeName.timeline); const { uiSettings } = useKibana().services; const esQueryConfig = useMemo(() => esQuery.getEsQueryConfig(uiSettings), [uiSettings]); const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); @@ -345,7 +345,7 @@ const TimelineStatusInfoComponent: React.FC = ({ timelineId } const TimelineStatusInfo = React.memo(TimelineStatusInfoComponent); const FlyoutHeaderComponent: React.FC = ({ timelineId }) => { - const { selectedPatterns, indexPattern, docValueFields, browserFields } = useSourcererScope( + const { selectedPatterns, indexPattern, docValueFields, browserFields } = useSourcererDataView( SourcererScopeName.timeline ); const getStartSelector = useMemo(() => startSelector(), []); diff --git a/x-pack/plugins/security_solution/public/timelines/components/graph_overlay/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/graph_overlay/index.test.tsx index d672a3c699707..c16da16002265 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/graph_overlay/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/graph_overlay/index.test.tsx @@ -13,15 +13,10 @@ import { useGlobalFullScreen, useTimelineFullScreen, } from '../../../common/containers/use_full_screen'; -import { mockTimelineModel, TestProviders } from '../../../common/mock'; +import { TestProviders } from '../../../common/mock'; import { TimelineId } from '../../../../common/types/timeline'; import { GraphOverlay } from '.'; -jest.mock('../../../common/hooks/use_selector', () => ({ - useShallowEqualSelector: jest.fn().mockReturnValue(mockTimelineModel.savedObjectId), - useDeepEqualSelector: jest.fn().mockReturnValue(mockTimelineModel), -})); - jest.mock('../../../common/containers/use_full_screen', () => ({ useGlobalFullScreen: jest.fn(), useTimelineFullScreen: jest.fn(), diff --git a/x-pack/plugins/security_solution/public/timelines/components/graph_overlay/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/graph_overlay/index.tsx index 41e190263edc6..31a40c46fc0bf 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/graph_overlay/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/graph_overlay/index.tsx @@ -30,7 +30,6 @@ import { TimelineId } from '../../../../common/types/timeline'; import { timelineSelectors } from '../../store/timeline'; import { timelineDefaults } from '../../store/timeline/defaults'; import { isFullScreen } from '../timeline/body/column_headers'; -import { sourcererSelectors } from '../../../common/store'; import { updateTimelineGraphEventId } from '../../../timelines/store/timeline/actions'; import { Resolver } from '../../../resolver/view'; import { @@ -40,7 +39,7 @@ import { } from '../../../common/components/super_date_picker/selectors'; import * as i18n from './translations'; import { SourcererScopeName } from '../../../common/store/sourcerer/model'; -import { SelectedDataView } from '../../../common/store/sourcerer/selectors'; +import { useSourcererDataView } from '../../../common/containers/sourcerer'; const OverlayContainer = styled.div` display: flex; @@ -182,10 +181,7 @@ const GraphOverlayComponent: React.FC = ({ timelineId }) => { globalFullScreen, ]); - const getSelectedDataView = useMemo(() => sourcererSelectors.getSelectedDataViewSelector(), []); - const { selectedPatterns } = useDeepEqualSelector((state) => - getSelectedDataView(state, SourcererScopeName.timeline) - ); + const { selectedPatterns } = useSourcererDataView(SourcererScopeName.timeline); if (fullScreen && !isInTimeline) { return ( diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.tsx index 2aee9cc06a1dc..f12731396c053 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.tsx @@ -8,8 +8,7 @@ import React, { useEffect, useState, useCallback, useMemo } from 'react'; import { useDispatch } from 'react-redux'; -import { sourcererSelectors } from '../../../common/store'; -import { useShallowEqualSelector, useDeepEqualSelector } from '../../../common/hooks/use_selector'; +import { useShallowEqualSelector } from '../../../common/hooks/use_selector'; import { SortFieldTimeline, TimelineId } from '../../../../common/types/timeline'; import { TimelineModel } from '../../../timelines/store/timeline/model'; import { timelineSelectors } from '../../../timelines/store/timeline'; @@ -46,8 +45,8 @@ import { useTimelineTypes } from './use_timeline_types'; import { useTimelineStatus } from './use_timeline_status'; import { deleteTimelinesByIds } from '../../containers/api'; import { Direction } from '../../../../common/search_strategy'; -import { SelectedDataView } from '../../../common/store/sourcerer/selectors'; import { SourcererScopeName } from '../../../common/store/sourcerer/model'; +import { useSourcererDataView } from '../../../common/containers/sourcerer'; interface OwnProps { /** Displays open timeline in modal */ @@ -110,10 +109,7 @@ export const StatefulOpenTimelineComponent = React.memo( (state) => getTimeline(state, TimelineId.active)?.savedObjectId ?? '' ); - const getSelectedDataView = useMemo(() => sourcererSelectors.getSelectedDataViewSelector(), []); - const { dataViewId, selectedPatterns } = useDeepEqualSelector((state) => - getSelectedDataView(state, SourcererScopeName.timeline) - ); + const { dataViewId, selectedPatterns } = useSourcererDataView(SourcererScopeName.timeline); const updateTimeline = useMemo(() => dispatchUpdateTimeline(dispatch), [dispatch]); const updateIsLoading = useCallback( diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/index.tsx index 4274076c6a08e..1016a430807be 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/index.tsx @@ -26,10 +26,9 @@ import { NOTE_CONTENT_CLASS_NAME } from '../../timeline/body/helpers'; import * as i18n from './translations'; import { TimelineTabs } from '../../../../../common/types/timeline'; import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; -import { sourcererSelectors } from '../../../../common/store'; import { SaveTimelineButton } from '../../timeline/header/save_timeline_button'; -import { SelectedDataView } from '../../../../common/store/sourcerer/selectors'; import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; +import { useSourcererDataView } from '../../../../common/containers/sourcerer'; export const NotePreviewsContainer = styled.section` padding-top: ${({ theme }) => `${theme.eui.euiSizeS}`}; @@ -47,10 +46,7 @@ const ToggleEventDetailsButtonComponent: React.FC timelineId, }) => { const dispatch = useDispatch(); - const getSelectedDataView = useMemo(() => sourcererSelectors.getSelectedDataViewSelector(), []); - const { selectedPatterns } = useDeepEqualSelector((state) => - getSelectedDataView(state, SourcererScopeName.timeline) - ); + const { selectedPatterns } = useSourcererDataView(SourcererScopeName.timeline); const handleClick = useCallback(() => { dispatch( diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/__snapshots__/expandable_host.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/__snapshots__/expandable_host.test.tsx.snap index 38daabc9c2714..43efd8cd824e6 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/__snapshots__/expandable_host.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/__snapshots__/expandable_host.test.tsx.snap @@ -105,6 +105,7 @@ exports[`Expandable Host Component ExpandableHostDetails: rendering it should re id="hostsDetailsQuery" indexNames={ Array [ + "-*elastic-cloud-logs-*", "apm-*-transaction*", "auditbeat-*", "endgame-*", diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/expandable_host.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/expandable_host.test.tsx index 2b914dd023883..5efeacee15a37 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/expandable_host.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/expandable_host.test.tsx @@ -9,27 +9,11 @@ import { mount } from 'enzyme'; import React from 'react'; import '../../../../common/mock/match_media'; -import { - mockGlobalState, - TestProviders, - SUB_PLUGINS_REDUCER, - kibanaObservable, - createSecuritySolutionStorageMock, -} from '../../../../common/mock'; -import { createStore, State } from '../../../../common/store'; +import { mockGlobalState, TestProviders } from '../../../../common/mock'; import { ExpandableHostDetails } from './expandable_host'; +import { EXCLUDE_ELASTIC_CLOUD_INDEX } from '../../../../common/containers/sourcerer'; describe('Expandable Host Component', () => { - const state: State = { - ...mockGlobalState, - sourcerer: { - ...mockGlobalState.sourcerer, - }, - }; - - const { storage } = createSecuritySolutionStorageMock(); - const store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage); - const mockProps = { contextID: 'text-context', hostName: 'testHostName', @@ -38,7 +22,7 @@ describe('Expandable Host Component', () => { describe('ExpandableHostDetails: rendering', () => { test('it should render the HostOverview of the ExpandableHostDetails', () => { const wrapper = mount( - + ); @@ -48,14 +32,15 @@ describe('Expandable Host Component', () => { test('it should render the HostOverview of the ExpandableHostDetails with the correct indices', () => { const wrapper = mount( - + ); - expect(wrapper.find('HostOverview').prop('indexNames')).toStrictEqual( - mockGlobalState.sourcerer.sourcererScopes.default.selectedPatterns - ); + expect(wrapper.find('HostOverview').prop('indexNames')).toStrictEqual([ + EXCLUDE_ELASTIC_CLOUD_INDEX, + ...mockGlobalState.sourcerer.sourcererScopes.default.selectedPatterns, + ]); }); }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/expandable_host.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/expandable_host.tsx index 23030a6e8229b..dae1f7add11e3 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/expandable_host.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/expandable_host.tsx @@ -5,14 +5,13 @@ * 2.0. */ -import React, { useMemo } from 'react'; +import React from 'react'; import styled from 'styled-components'; import { i18n } from '@kbn/i18n'; import { EuiTitle } from '@elastic/eui'; -import { sourcererSelectors } from '../../../../common/store/sourcerer'; import { HostDetailsLink } from '../../../../common/components/links'; import { useGlobalTime } from '../../../../common/containers/use_global_time'; -import { useSourcererScope } from '../../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../../../common/containers/sourcerer'; import { HostOverview } from '../../../../overview/components/host_overview'; import { setAbsoluteRangeDatePicker } from '../../../../common/store/inputs/actions'; import { HostItem } from '../../../../../common/search_strategy'; @@ -20,9 +19,6 @@ import { AnomalyTableProvider } from '../../../../common/components/ml/anomaly/a import { hostToCriteria } from '../../../../common/components/ml/criteria/host_to_criteria'; import { scoreIntervalToDateTime } from '../../../../common/components/ml/score/score_interval_to_datetime'; import { useHostDetails, ID } from '../../../../hosts/containers/hosts/details'; -import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; -import { SelectedDataView } from '../../../../common/store/sourcerer/selectors'; -import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; interface ExpandableHostProps { hostName: string; @@ -59,9 +55,8 @@ export const ExpandableHostDetails = ({ isDraggable = false, }: ExpandableHostProps & { contextID: string; isDraggable?: boolean }) => { const { to, from, isInitializing } = useGlobalTime(); - const { docValueFields } = useSourcererScope(); /* - Normally `selectedPatterns` from useSourcerScope would be where we obtain the indices, + Normally `selectedPatterns` from useSourcererDataView would be where we obtain the indices, but those indices are only loaded when viewing the pages where the sourcerer is initialized (i.e. Hosts and Overview) When a user goes directly to the detections page, the patterns have not been loaded yet as that information isn't used for the detections page. With this details component being accessible @@ -69,10 +64,7 @@ export const ExpandableHostDetails = ({ Otherwise, an empty array is defaulted for the `indexNames` in the query which leads to inconsistencies in the data returned (i.e. extraneous endpoint data is retrieved from the backend leading to endpoint data not being returned) */ - const getSelectedDataView = useMemo(() => sourcererSelectors.getSelectedDataViewSelector(), []); - const { selectedPatterns } = useDeepEqualSelector((state) => - getSelectedDataView(state, SourcererScopeName.default) - ); + const { docValueFields, selectedPatterns } = useSourcererDataView(); const [loading, { hostDetails: hostOverview }] = useHostDetails({ endDate: to, diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/network_details/expandable_network.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/network_details/expandable_network.tsx index 184379cd366b1..5b13f3f2a3b1b 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/network_details/expandable_network.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/network_details/expandable_network.tsx @@ -24,7 +24,7 @@ import { inputsSelectors } from '../../../../common/store'; import { setAbsoluteRangeDatePicker } from '../../../../common/store/inputs/actions'; import { OverviewEmpty } from '../../../../overview/components/overview_empty'; import { esQuery } from '../../../../../../../../src/plugins/data/public'; -import { useSourcererScope } from '../../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../../../common/containers/sourcerer'; import { useNetworkDetails } from '../../../../network/containers/details'; import { networkModel } from '../../../../network/store'; import { useAnomaliesTableData } from '../../../../common/components/ml/anomaly/use_anomalies_table_data'; @@ -98,7 +98,7 @@ export const ExpandableNetworkDetails = ({ services: { uiSettings }, } = useKibana(); - const { docValueFields, indicesExist, indexPattern, selectedPatterns } = useSourcererScope(); + const { docValueFields, indicesExist, indexPattern, selectedPatterns } = useSourcererDataView(); const [filterQuery, kqlError] = convertToBuildEsQuery({ config: esQuery.getEsQueryConfig(uiSettings), indexPattern, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap index 25d5104a98d95..11f3d6d795d75 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap @@ -126,6 +126,7 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` "packetbeat", ], "name": "@timestamp", + "readFromDocValues": true, "searchable": true, "type": "date", }, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/helpers.test.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/helpers.test.ts index 85e884703c592..c59a80ae078b2 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/helpers.test.ts @@ -64,6 +64,7 @@ describe('helpers', () => { id: '@timestamp', indexes: ['auditbeat', 'filebeat', 'packetbeat'], name: '@timestamp', + readFromDocValues: true, searchable: true, type: 'date', initialWidth: 190, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/__snapshots__/suricata_row_renderer.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/__snapshots__/suricata_row_renderer.test.tsx.snap index eeb8786b2cfa3..990f3a0af87c0 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/__snapshots__/suricata_row_renderer.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/__snapshots__/suricata_row_renderer.test.tsx.snap @@ -127,6 +127,7 @@ exports[`suricata_row_renderer renders correctly against snapshot 1`] = ` "packetbeat", ], "name": "@timestamp", + "readFromDocValues": true, "searchable": true, "type": "date", }, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/__snapshots__/zeek_details.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/__snapshots__/zeek_details.test.tsx.snap index 64ca766e4dee6..546b9a31843ac 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/__snapshots__/zeek_details.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/__snapshots__/zeek_details.test.tsx.snap @@ -125,6 +125,7 @@ exports[`ZeekDetails rendering it renders the default ZeekDetails 1`] = ` "packetbeat", ], "name": "@timestamp", + "readFromDocValues": true, "searchable": true, "type": "date", }, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/__snapshots__/zeek_row_renderer.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/__snapshots__/zeek_row_renderer.test.tsx.snap index 94cbe43e93d2d..e20f63a7b9e79 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/__snapshots__/zeek_row_renderer.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/__snapshots__/zeek_row_renderer.test.tsx.snap @@ -127,6 +127,7 @@ exports[`zeek_row_renderer renders correctly against snapshot 1`] = ` "packetbeat", ], "name": "@timestamp", + "readFromDocValues": true, "searchable": true, "type": "date", }, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/index.test.tsx index ef04c1177dcd6..ddcc2df231a00 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/index.test.tsx @@ -12,14 +12,6 @@ import { useMountAppended } from '../../../../common/utils/use_mount_appended'; import { DataProviders } from '.'; -jest.mock('../../../../common/hooks/use_selector', () => { - const actual = jest.requireActual('../../../../common/hooks/use_selector'); - return { - ...actual, - useDeepEqualSelector: jest.fn().mockReturnValue([]), - }; -}); - describe('DataProviders', () => { const mount = useMountAppended(); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/index.tsx index f642ec35d4306..0a3a40d8f6f06 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/index.tsx @@ -12,7 +12,7 @@ import uuid from 'uuid'; import { IS_DRAGGING_CLASS_NAME } from '@kbn/securitysolution-t-grid'; import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; -import { useSourcererScope } from '../../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../../../common/containers/sourcerer'; import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; import { DroppableWrapper } from '../../../../common/components/drag_and_drop/droppable_wrapper'; import { droppableTimelineProvidersPrefix } from '../../../../common/components/drag_and_drop/helpers'; @@ -85,7 +85,7 @@ const getDroppableId = (id: string): string => * the data pro section. */ export const DataProviders = React.memo(({ timelineId }) => { - const { browserFields } = useSourcererScope(SourcererScopeName.timeline); + const { browserFields } = useSourcererDataView(SourcererScopeName.timeline); const getManageTimeline = useMemo(() => timelineSelectors.getManageTimelineById(), []); const { isLoading } = useDeepEqualSelector((state) => getManageTimeline(state, timelineId)); const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/index.test.tsx index ad141829858ae..553a3d0e82d29 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/index.test.tsx @@ -20,7 +20,7 @@ import { useMountAppended } from '../../../../common/utils/use_mount_appended'; import { TimelineId, TimelineTabs } from '../../../../../common/types/timeline'; import { useTimelineEvents } from '../../../containers/index'; import { useTimelineEventsDetails } from '../../../containers/details/index'; -import { useSourcererScope } from '../../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../../../common/containers/sourcerer'; import { mockSourcererScope } from '../../../../common/containers/sourcerer/mocks'; import { useDraggableKeyboardWrapper as mockUseDraggableKeyboardWrapper } from '../../../../../../timelines/public/components'; @@ -88,7 +88,7 @@ describe('Timeline', () => { ]); (useTimelineEventsDetails as jest.Mock).mockReturnValue([false, {}]); - (useSourcererScope as jest.Mock).mockReturnValue(mockSourcererScope); + (useSourcererDataView as jest.Mock).mockReturnValue(mockSourcererScope); props = { columns: defaultHeaders, @@ -176,7 +176,7 @@ describe('Timeline', () => { }); test('it does render the timeline table when the source is loading with no events', () => { - (useSourcererScope as jest.Mock).mockReturnValue({ + (useSourcererDataView as jest.Mock).mockReturnValue({ browserFields: {}, docValueFields: [], loading: true, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/index.tsx index 8b4215724dbad..6cb0e6f2e7982 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/index.tsx @@ -47,7 +47,7 @@ import { inputsModel, inputsSelectors, State } from '../../../../common/store'; import { sourcererActions } from '../../../../common/store/sourcerer'; import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; import { timelineDefaults } from '../../../../timelines/store/timeline/defaults'; -import { useSourcererScope } from '../../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../../../common/containers/sourcerer'; import { useEqlEventsCountPortal } from '../../../../common/hooks/use_timeline_events_count'; import { TimelineModel } from '../../../../timelines/store/timeline/model'; import { TimelineDatePickerLock } from '../date_picker_lock'; @@ -180,7 +180,7 @@ export const EqlTabContentComponent: React.FC = ({ loading: loadingSourcerer, runtimeMappings, selectedPatterns, - } = useSourcererScope(SourcererScopeName.timeline); + } = useSourcererDataView(SourcererScopeName.timeline); const isBlankTimeline: boolean = isEmpty(eqlQuery); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.tsx index 2c85f1547dbeb..547f005ab61ab 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.tsx @@ -8,7 +8,7 @@ import { isEmpty, get } from 'lodash/fp'; import memoizeOne from 'memoize-one'; -import { EsQueryConfig, Filter, Query } from '@kbn/es-query'; +import { DataViewBase, EsQueryConfig, Filter, Query } from '@kbn/es-query'; import { handleSkipFocus, elementOrChildrenHasFocus, @@ -25,7 +25,6 @@ import { EXISTS_OPERATOR, } from './data_providers/data_provider'; import { BrowserFields } from '../../../common/containers/source'; -import { IIndexPattern } from '../../../../../../../src/plugins/data/public'; import { EVENTS_TABLE_CLASS_NAME } from './styles'; @@ -151,7 +150,7 @@ export const combineQueries = ({ }: { config: EsQueryConfig; dataProviders: DataProvider[]; - indexPattern: IIndexPattern; + indexPattern: DataViewBase; browserFields: BrowserFields; filters: Filter[]; kqlQuery: Query; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/index.test.tsx index c91673e5f931c..bb1281c0eff2e 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/index.test.tsx @@ -59,7 +59,7 @@ jest.mock('../../../common/containers/sourcerer', () => { return { ...originalModule, - useSourcererScope: jest.fn().mockReturnValue({ + useSourcererDataView: jest.fn().mockReturnValue({ browserFields: mockBrowserFields, docValueFields: mockDocValueFields, loading: false, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/index.tsx index 10ad67775b009..48f9274fa563f 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/index.tsx @@ -16,7 +16,7 @@ import { timelineActions, timelineSelectors } from '../../store/timeline'; import { timelineDefaults } from '../../../timelines/store/timeline/defaults'; import { defaultHeaders } from './body/column_headers/default_headers'; import { CellValueElementProps } from './cell_rendering'; -import { useSourcererScope } from '../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../../common/containers/sourcerer'; import { SourcererScopeName } from '../../../common/store/sourcerer/model'; import { FlyoutHeader, FlyoutHeaderPanel } from '../flyout/header'; import { TimelineType, TimelineId, RowRenderer } from '../../../../common/types/timeline'; @@ -62,7 +62,8 @@ const StatefulTimelineComponent: React.FC = ({ const dispatch = useDispatch(); const containerElement = useRef(null); const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); - const { selectedDataViewId, selectedPatterns } = useSourcererScope(SourcererScopeName.timeline); + const { dataViewId, selectedPatterns } = useSourcererDataView(SourcererScopeName.timeline); + const { graphEventId, savedObjectId, timelineType, description } = useDeepEqualSelector((state) => pick( ['graphEventId', 'savedObjectId', 'timelineType', 'description'], @@ -77,7 +78,7 @@ const StatefulTimelineComponent: React.FC = ({ timelineActions.createTimeline({ id: timelineId, columns: defaultHeaders, - dataViewId: selectedDataViewId, + dataViewId, indexNames: selectedPatterns, expandedDetail: activeTimeline.getExpandedDetail(), show: false, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/notes_tab_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/notes_tab_content/index.tsx index 5fdf9ff18808a..e4d97abc0433a 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/notes_tab_content/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/notes_tab_content/index.tsx @@ -21,7 +21,7 @@ import React, { Fragment, useCallback, useMemo, useState } from 'react'; import { useDispatch } from 'react-redux'; import styled from 'styled-components'; -import { useSourcererScope } from '../../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../../../common/containers/sourcerer'; import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; import { timelineActions } from '../../../store/timeline'; import { @@ -145,8 +145,7 @@ const NotesTabContentComponent: React.FC = ({ timelineId } noteIds, status: timelineStatus, } = useDeepEqualSelector((state) => getTimelineNotes(state, timelineId)); - - const { browserFields, docValueFields, runtimeMappings } = useSourcererScope( + const { browserFields, docValueFields, runtimeMappings } = useSourcererDataView( SourcererScopeName.timeline ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.test.tsx index cb41d0c69bc97..8707bb33da08c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.test.tsx @@ -19,7 +19,7 @@ import { useMountAppended } from '../../../../common/utils/use_mount_appended'; import { TimelineId, TimelineTabs } from '../../../../../common/types/timeline'; import { useTimelineEvents } from '../../../containers/index'; import { useTimelineEventsDetails } from '../../../containers/details/index'; -import { useSourcererScope } from '../../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../../../common/containers/sourcerer'; import { mockSourcererScope } from '../../../../common/containers/sourcerer/mocks'; import { PinnedTabContentComponent, Props as PinnedTabContentComponentProps } from '.'; import { Direction } from '../../../../../common/search_strategy'; @@ -93,7 +93,7 @@ describe('PinnedTabContent', () => { ]); (useTimelineEventsDetails as jest.Mock).mockReturnValue([false, {}]); - (useSourcererScope as jest.Mock).mockReturnValue(mockSourcererScope); + (useSourcererDataView as jest.Mock).mockReturnValue(mockSourcererScope); props = { columns: defaultHeaders, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.tsx index f8b6fe6561391..fbb1269b05b27 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.tsx @@ -22,10 +22,9 @@ import { StatefulBody } from '../body'; import { Footer, footerHeight } from '../footer'; import { requiredFieldsForActions } from '../../../../detections/components/alerts_table/default_config'; import { EventDetailsWidthProvider } from '../../../../common/components/events_viewer/event_details_width_context'; -import { sourcererSelectors } from '../../../../common/store/sourcerer'; import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; import { timelineDefaults } from '../../../store/timeline/defaults'; -import { useSourcererScope } from '../../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../../../common/containers/sourcerer'; import { useTimelineFullScreen } from '../../../../common/containers/use_full_screen'; import { TimelineModel } from '../../../store/timeline/model'; import { State } from '../../../../common/store'; @@ -37,10 +36,8 @@ import { ToggleDetailPanel, } from '../../../../../common/types/timeline'; import { DetailsPanel } from '../../side_panel'; -import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; import { ExitFullScreen } from '../../../../common/components/exit_full_screen'; import { defaultControlColumn } from '../body/control_columns'; -import { SelectedDataView } from '../../../../common/store/sourcerer/selectors'; const StyledEuiFlyoutBody = styled(EuiFlyoutBody)` overflow-y: hidden; @@ -121,14 +118,10 @@ export const PinnedTabContentComponent: React.FC = ({ docValueFields, loading: loadingSourcerer, runtimeMappings, - } = useSourcererScope(SourcererScopeName.timeline); + selectedPatterns, + } = useSourcererDataView(SourcererScopeName.timeline); const { setTimelineFullScreen, timelineFullScreen } = useTimelineFullScreen(); - const getSelectedDataView = useMemo(() => sourcererSelectors.getSelectedDataViewSelector(), []); - const { selectedPatterns } = useDeepEqualSelector((state) => - getSelectedDataView(state, SourcererScopeName.timeline) - ); - const filterQuery = useMemo(() => { if (isEmpty(pinnedEventIds)) { return ''; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_create_timeline.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_create_timeline.test.tsx index 4d9931c5f9cb8..a4dd57768372e 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_create_timeline.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_create_timeline.test.tsx @@ -22,13 +22,6 @@ jest.mock('react-redux', () => { return { ...actual, useDispatch: () => mockDispatch, - useSelector: () => ({ - kind: 'relative', - fromStr: 'now-24h', - toStr: 'now', - from: '2020-07-07T08:20:18.966Z', - to: '2020-07-08T08:20:18.966Z', - }), }; }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_create_timeline.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_create_timeline.tsx index d3c836028f693..6b10ff20008dc 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_create_timeline.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_create_timeline.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useCallback, useMemo } from 'react'; +import React, { useCallback } from 'react'; import { useDispatch } from 'react-redux'; import { EuiButton, EuiButtonEmpty } from '@elastic/eui'; @@ -19,10 +19,10 @@ import { } from '../../../../../common/types/timeline'; import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; import { inputsActions, inputsSelectors } from '../../../../common/store/inputs'; -import { sourcererActions, sourcererSelectors } from '../../../../common/store/sourcerer'; +import { sourcererActions } from '../../../../common/store/sourcerer'; import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; import { appActions } from '../../../../common/store/app'; -import { SelectedDataView } from '../../../../common/store/sourcerer/selectors'; +import { useSourcererDataView } from '../../../../common/containers/sourcerer'; interface Props { timelineId?: string; @@ -32,10 +32,7 @@ interface Props { export const useCreateTimeline = ({ timelineId, timelineType, closeGearMenu }: Props) => { const dispatch = useDispatch(); - const getSelectedDataView = useMemo(() => sourcererSelectors.getSelectedDataViewSelector(), []); - const { dataViewId, selectedPatterns } = useDeepEqualSelector((state) => - getSelectedDataView(state, SourcererScopeName.timeline) - ); + const { dataViewId, selectedPatterns } = useSourcererDataView(SourcererScopeName.timeline); const { timelineFullScreen, setTimelineFullScreen } = useTimelineFullScreen(); const globalTimeRange = useDeepEqualSelector(inputsSelectors.globalTimeRangeSelector); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx index b65e432126283..f045f735ae168 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx @@ -11,7 +11,7 @@ import { useDispatch } from 'react-redux'; import styled from 'styled-components'; import { FieldsEqlOptions } from '../../../../../../common/search_strategy'; -import { useSourcererScope } from '../../../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../../../../common/containers/sourcerer'; import { useDeepEqualSelector } from '../../../../../common/hooks/use_selector'; import { SourcererScopeName } from '../../../../../common/store/sourcerer/model'; import { EqlQueryBar } from '../../../../../detections/components/rules/eql_query_bar'; @@ -71,7 +71,7 @@ export const EqlQueryBarTimeline = memo(({ timelineId }: { timelineId: string }) loading: indexPatternsLoading, indexPattern, selectedPatterns, - } = useSourcererScope(SourcererScopeName.timeline); + } = useSourcererDataView(SourcererScopeName.timeline); const initialState = { ...defaultValues, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/index.tsx index daafec3005eb8..450a43b43ef5f 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/index.tsx @@ -11,7 +11,7 @@ import { useDispatch } from 'react-redux'; import { Subscription } from 'rxjs'; import deepEqual from 'fast-deep-equal'; -import { useSourcererScope } from '../../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../../../common/containers/sourcerer'; import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; import { Query, @@ -81,8 +81,7 @@ export const QueryBarTimeline = memo( const [dateRangeTo, setDateRangTo] = useState( toStr != null ? toStr : new Date(to).toISOString() ); - const { browserFields, indexPattern } = useSourcererScope(SourcererScopeName.timeline); - + const { browserFields, indexPattern } = useSourcererDataView(SourcererScopeName.timeline); const [savedQuery, setSavedQuery] = useState(undefined); const [filterQueryConverted, setFilterQueryConverted] = useState({ query: filterQuery != null ? filterQuery.expression : '', diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx index c9bb14dfc0c67..23f0fa0b9f289 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx @@ -22,7 +22,7 @@ import { useMountAppended } from '../../../../common/utils/use_mount_appended'; import { TimelineId, TimelineStatus, TimelineTabs } from '../../../../../common/types/timeline'; import { useTimelineEvents } from '../../../containers/index'; import { useTimelineEventsDetails } from '../../../containers/details/index'; -import { useSourcererScope } from '../../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../../../common/containers/sourcerer'; import { mockSourcererScope } from '../../../../common/containers/sourcerer/mocks'; import { Direction } from '../../../../../common/search_strategy'; import * as helpers from '../helpers'; @@ -102,7 +102,7 @@ describe('Timeline', () => { ]); (useTimelineEventsDetails as jest.Mock).mockReturnValue([false, {}]); - (useSourcererScope as jest.Mock).mockReturnValue(mockSourcererScope); + (useSourcererDataView as jest.Mock).mockReturnValue(mockSourcererScope); props = { columns: defaultHeaders, @@ -187,7 +187,7 @@ describe('Timeline', () => { }); test('it does render the timeline table when the source is loading with no events', () => { - (useSourcererScope as jest.Mock).mockReturnValue({ + (useSourcererDataView as jest.Mock).mockReturnValue({ browserFields: {}, docValueFields: [], loading: true, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx index 986bf2481b185..5f6f2796d4ba9 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx @@ -51,7 +51,7 @@ import { inputsModel, inputsSelectors, State } from '../../../../common/store'; import { sourcererActions } from '../../../../common/store/sourcerer'; import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; import { timelineDefaults } from '../../../../timelines/store/timeline/defaults'; -import { useSourcererScope } from '../../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../../../common/containers/sourcerer'; import { useTimelineEventsCountPortal } from '../../../../common/hooks/use_timeline_events_count'; import { TimelineModel } from '../../../../timelines/store/timeline/model'; import { TimelineDatePickerLock } from '../date_picker_lock'; @@ -192,8 +192,7 @@ export const QueryTabContentComponent: React.FC = ({ indexPattern, runtimeMappings, selectedPatterns, - } = useSourcererScope(SourcererScopeName.timeline); - + } = useSourcererDataView(SourcererScopeName.timeline); const { uiSettings } = useKibana().services; const getManageTimeline = useMemo(() => timelineSelectors.getManageTimelineById(), []); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/pick_events.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/pick_events.test.tsx index d2892bfcb450b..47ea0f781f7c3 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/pick_events.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/pick_events.test.tsx @@ -125,12 +125,14 @@ describe('Pick Events/Timeline Sourcerer', () => { sourcerer: { ...mockGlobalState.sourcerer, defaultDataView: { + ...mockGlobalState.sourcerer.defaultDataView, id: '1234', title: 'filebeat-*,auditbeat-*,auditbeat-*,auditbeat-*,auditbeat-*', patternList: ['filebeat-*', 'auditbeat-*'], }, kibanaDataViews: [ { + ...mockGlobalState.sourcerer.defaultDataView, id: '1234', title: 'filebeat-*,auditbeat-*,auditbeat-*,auditbeat-*,auditbeat-*', patternList: ['filebeat-*', 'auditbeat-*'], diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/pick_events.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/pick_events.tsx index ffa5d2201f56b..791993d67135d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/pick_events.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/pick_events.tsx @@ -25,16 +25,15 @@ import { } from '@elastic/eui'; import deepEqual from 'fast-deep-equal'; import React, { memo, useCallback, useEffect, useMemo, useState } from 'react'; -import { useSelector } from 'react-redux'; import styled from 'styled-components'; -import { State } from '../../../../common/store'; +import { sourcererSelectors } from '../../../../common/store'; import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; import { TimelineEventsType } from '../../../../../common'; -import { getSourcererScopeSelector, SourcererScopeSelector } from './selectors'; import * as i18n from './translations'; import { getScopePatternListSelection } from '../../../../common/store/sourcerer/helpers'; import { SIEM_DATA_VIEW_LABEL } from '../../../../common/components/sourcerer/translations'; +import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; const PopoverContent = styled.div` width: 600px; @@ -135,15 +134,14 @@ const PickEventTypeComponents: React.FC = ({ const [isPopoverOpen, setPopover] = useState(false); const [showAdvanceSettings, setAdvanceSettings] = useState(eventType === 'custom'); const [filterEventType, setFilterEventType] = useState(eventType); - const sourcererScopeSelector = useMemo(getSourcererScopeSelector, []); + const sourcererScopeSelector = useMemo(() => sourcererSelectors.getSourcererScopeSelector(), []); const { defaultDataView, kibanaDataViews, signalIndexName, sourcererScope: { loading, selectedPatterns, selectedDataViewId }, - } = useSelector( - (state) => sourcererScopeSelector(state, SourcererScopeName.timeline), - deepEqual + }: sourcererSelectors.SourcererScopeSelector = useDeepEqualSelector((state) => + sourcererScopeSelector(state, SourcererScopeName.timeline) ); const [dataViewId, setDataViewId] = useState(selectedDataViewId ?? ''); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/selectors.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/selectors.tsx deleted file mode 100644 index 53ca6489aea5d..0000000000000 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/selectors.tsx +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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 { State } from '../../../../common/store'; -import { sourcererSelectors } from '../../../../common/store/selectors'; -import { - KibanaDataView, - ManageScope, - SourcererScopeName, -} from '../../../../common/store/sourcerer/model'; - -export interface SourcererScopeSelector { - defaultDataView: KibanaDataView; - kibanaDataViews: KibanaDataView[]; - signalIndexName: string | null; - sourcererScope: ManageScope; -} - -export const getSourcererScopeSelector = () => { - const getKibanaDataViewsSelector = sourcererSelectors.kibanaDataViewsSelector(); - const getDefaultDataViewSelector = sourcererSelectors.defaultDataViewSelector(); - const getScopeIdSelector = sourcererSelectors.scopeIdSelector(); - const getSignalIndexNameSelector = sourcererSelectors.signalIndexNameSelector(); - - return (state: State, scopeId: SourcererScopeName): SourcererScopeSelector => { - const kibanaDataViews = getKibanaDataViewsSelector(state); - const defaultDataView = getDefaultDataViewSelector(state); - const scope = getScopeIdSelector(state, scopeId); - const signalIndexName = getSignalIndexNameSelector(state); - - return { - defaultDataView, - kibanaDataViews, - signalIndexName, - sourcererScope: scope, - }; - }; -}; diff --git a/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.test.tsx b/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.test.tsx index 597ff25246d94..7a36f7296eff3 100644 --- a/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.test.tsx @@ -27,7 +27,7 @@ jest.mock('../../common/containers/sourcerer', () => { return { ...originalModule, - useSourcererScope: jest.fn().mockReturnValue({ + useSourcererDataView: jest.fn().mockReturnValue({ indicesExist: true, }), }; diff --git a/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.tsx b/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.tsx index f79d513380349..3a9b9b0d2693e 100644 --- a/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.tsx +++ b/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.tsx @@ -22,7 +22,7 @@ import { NewTemplateTimeline } from '../components/timeline/properties/new_templ import { NewTimeline } from '../components/timeline/properties/helpers'; import * as i18n from './translations'; import { SecurityPageName } from '../../app/types'; -import { useSourcererScope } from '../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../common/containers/sourcerer'; const TimelinesContainer = styled.div` width: 100%; @@ -36,7 +36,7 @@ export const TimelinesPageComponent: React.FC = () => { const onImportTimelineBtnClick = useCallback(() => { setImportDataModalToggle(true); }, [setImportDataModalToggle]); - const { indicesExist } = useSourcererScope(); + const { indicesExist } = useSourcererDataView(); const capabilitiesCanUserCRUD: boolean = !!useKibana().services.application.capabilities.siem.crud; diff --git a/x-pack/plugins/security_solution/public/ueba/pages/details/index.tsx b/x-pack/plugins/security_solution/public/ueba/pages/details/index.tsx index b7a994ffa7ca8..72122aba3c4aa 100644 --- a/x-pack/plugins/security_solution/public/ueba/pages/details/index.tsx +++ b/x-pack/plugins/security_solution/public/ueba/pages/details/index.tsx @@ -39,7 +39,7 @@ import { Display } from '../display'; import { timelineSelectors } from '../../../timelines/store/timeline'; import { TimelineId } from '../../../../common/types/timeline'; import { timelineDefaults } from '../../../timelines/store/timeline/defaults'; -import { useSourcererScope } from '../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../../common/containers/sourcerer'; import { useDeepEqualSelector, useShallowEqualSelector } from '../../../common/hooks/use_selector'; import { useInvalidFilterQuery } from '../../../common/hooks/use_invalid_filter_query'; import { SourcererScopeName } from '../../../common/store/sourcerer/model'; @@ -69,7 +69,7 @@ const UebaDetailsComponent: React.FC = ({ detailName, uebaDeta ); const getFilters = () => [...uebaDetailsPageFilters, ...filters]; - const { docValueFields, indicesExist, indexPattern, selectedPatterns } = useSourcererScope( + const { docValueFields, indicesExist, indexPattern, selectedPatterns } = useSourcererDataView( SourcererScopeName.detections ); diff --git a/x-pack/plugins/security_solution/public/ueba/pages/details/types.ts b/x-pack/plugins/security_solution/public/ueba/pages/details/types.ts index 976b033db5f5a..e46a128e15f85 100644 --- a/x-pack/plugins/security_solution/public/ueba/pages/details/types.ts +++ b/x-pack/plugins/security_solution/public/ueba/pages/details/types.ts @@ -6,7 +6,7 @@ */ import { ActionCreator } from 'typescript-fsa'; -import { Query, IIndexPattern, Filter } from 'src/plugins/data/public'; +import { DataViewBase, Filter, Query } from '@kbn/es-query'; import { InputsModelId } from '../../../common/store/inputs/constants'; import { UebaTableType } from '../../store/model'; import { UebaQueryProps } from '../types'; @@ -54,7 +54,7 @@ export type UebaDetailsTabsProps = HostBodyComponentDispatchProps & indexNames: string[]; pageFilters?: Filter[]; filterQuery?: string; - indexPattern: IIndexPattern; + indexPattern: DataViewBase; type: uebaModel.UebaType; }; diff --git a/x-pack/plugins/security_solution/public/ueba/pages/ueba.tsx b/x-pack/plugins/security_solution/public/ueba/pages/ueba.tsx index 4e0041a98454c..93e22efdba3dd 100644 --- a/x-pack/plugins/security_solution/public/ueba/pages/ueba.tsx +++ b/x-pack/plugins/security_solution/public/ueba/pages/ueba.tsx @@ -43,7 +43,7 @@ import { } from '../../timelines/components/timeline/helpers'; import { timelineSelectors } from '../../timelines/store/timeline'; import { timelineDefaults } from '../../timelines/store/timeline/defaults'; -import { useSourcererScope } from '../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../common/containers/sourcerer'; import { useDeepEqualSelector, useShallowEqualSelector } from '../../common/hooks/use_selector'; import { useInvalidFilterQuery } from '../../common/hooks/use_invalid_filter_query'; @@ -78,7 +78,7 @@ const UebaComponent = () => { const { uiSettings } = useKibana().services; const tabsFilters = filters; - const { docValueFields, indicesExist, indexPattern, selectedPatterns } = useSourcererScope(); + const { docValueFields, indicesExist, indexPattern, selectedPatterns } = useSourcererDataView(); const [filterQuery, kqlError] = useMemo( () => convertToBuildEsQuery({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/detections_role.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/detections_role.json index a3e277f084a71..e6fbef08d25ee 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/detections_role.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/detections_role.json @@ -34,8 +34,8 @@ "ml": ["all"], "siem": ["all", "read_alerts", "crud_alerts"], "securitySolutionCases": ["all"], - "indexPatterns": ["read"], - "savedObjectsManagement": ["read"], + "indexPatterns": ["all"], + "savedObjectsManagement": ["all"], "actions": ["read"], "builtInAlerts": ["all"], "dev_tools": ["all"] diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/index.ts index 44d12e0112bc2..c18e23b7a3cdf 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/index.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/index.ts @@ -16,8 +16,5 @@ export * from './t2_analyst'; // TODO: Steph/sourcerer remove from detections_role.json once we have our internal saved object client // https://github.com/elastic/security-team/issues/1978 -// the detections tests actually all pass without these, -// but I set them here to avoid the error message mentioned in the issue -// and avoid possible failures that will be cleared when the issue is fixed // "indexPatterns": ["read"], // "savedObjectsManagement": ["read"], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/detections_role.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/detections_role.json index 28e4736f1da53..18effae645c42 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/detections_role.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/detections_role.json @@ -34,8 +34,8 @@ "ml": ["all"], "siem": ["all", "read_alerts", "crud_alerts"], "securitySolutionCases": ["all"], - "indexPatterns": ["read"], - "savedObjectsManagement": ["read"], + "indexPatterns": ["all"], + "savedObjectsManagement": ["all"], "actions": ["all"], "builtInAlerts": ["all"] }, diff --git a/x-pack/plugins/security_solution/server/lib/sourcerer/routes/index.ts b/x-pack/plugins/security_solution/server/lib/sourcerer/routes/index.ts index 87aeeeff8106b..cdaaedf364eb6 100644 --- a/x-pack/plugins/security_solution/server/lib/sourcerer/routes/index.ts +++ b/x-pack/plugins/security_solution/server/lib/sourcerer/routes/index.ts @@ -63,11 +63,24 @@ export const createSourcererDataViewRoute = ( const defaultDataView = { ...siemDataView, id: siemDataView.id ?? '' }; const wholeDataView = await dataViewService.get(defaultDataView.id); wholeDataView.title = patternListAsTitle; - await dataViewService.updateSavedObject(wholeDataView); + let didUpdate = true; + await dataViewService.updateSavedObject(wholeDataView).catch((err) => { + const error = transformError(err); + if (error.statusCode === 403) { + didUpdate = false; + // user doesnt have permissions to update, use existing pattern + wholeDataView.title = defaultDataView.title; + return; + } + throw err; + }); + // update the data view in allDataViews - allDataViews = allDataViews.map((v) => - v.id === dataViewId ? { ...v, title: patternListAsTitle } : v - ); + if (didUpdate) { + allDataViews = allDataViews.map((v) => + v.id === dataViewId ? { ...v, title: patternListAsTitle } : v + ); + } } const patternLists: string[][] = allDataViews.map(({ title }) => title.split(',')); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/constants.ts b/x-pack/plugins/security_solution/server/lib/timeline/constants.ts index 916fe726210cd..bd9a827e768dc 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/constants.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/constants.ts @@ -10,6 +10,11 @@ */ export const SAVED_QUERY_ID_REF_NAME = 'savedQueryId'; +/** + * The reference name for the saved query ID field within the timeline saved object definition + */ +export const DATA_VIEW_ID_REF_NAME = 'dataViewId'; + /** * This needs to match the type of the saved query saved object. That type is defined here: * https://github.com/elastic/kibana/blob/main/src/plugins/data/public/query/saved_query/saved_query_service.ts#L54 diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/field_migrator.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/field_migrator.ts index bb6667f81ed8f..87d3aa4c6f908 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/field_migrator.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/field_migrator.ts @@ -5,13 +5,14 @@ * 2.0. */ -import { SAVED_QUERY_ID_REF_NAME, SAVED_QUERY_TYPE } from '../../constants'; +import { DATA_VIEW_ID_REF_NAME, SAVED_QUERY_ID_REF_NAME, SAVED_QUERY_TYPE } from '../../constants'; import { FieldMigrator } from '../../utils/migrator'; - +import { DATA_VIEW_SAVED_OBJECT_TYPE } from '../../../../../../../../src/plugins/data/common'; /** * A migrator to handle moving specific fields that reference other saved objects to the references field within a saved * object. */ export const timelineFieldsMigrator = new FieldMigrator([ { path: 'savedQueryId', type: SAVED_QUERY_TYPE, name: SAVED_QUERY_ID_REF_NAME }, + { path: 'dataViewId', type: DATA_VIEW_SAVED_OBJECT_TYPE, name: DATA_VIEW_ID_REF_NAME }, ]); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.test.ts index 112796df527fa..8af3cf7cfb1cf 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.test.ts @@ -26,6 +26,8 @@ import { mockResolvedTimeline, mockResolveTimelineResponse, } from '../../__mocks__/resolve_timeline'; +import { DATA_VIEW_ID_REF_NAME, SAVED_QUERY_ID_REF_NAME, SAVED_QUERY_TYPE } from '../../constants'; +import { DATA_VIEW_SAVED_OBJECT_TYPE } from '../../../../../../../../src/plugins/data_views/common'; jest.mock('./convert_saved_object_to_savedtimeline', () => ({ convertSavedObjectToSavedTimeline: jest.fn(), @@ -309,4 +311,54 @@ describe('saved_object', () => { expect(result).toEqual(mockResolveTimelineResponse); }); }); + describe('field migrator', () => { + let mockResolveSavedObject: jest.Mock; + const convertSavedObjectToSavedTimelineMock: jest.Mock = + convertSavedObjectToSavedTimeline as jest.Mock; + let mockRequest: FrameworkRequest; + beforeEach(async () => { + jest.clearAllMocks(); + convertSavedObjectToSavedTimelineMock.mockReturnValue(mockResolvedTimeline); + mockResolveSavedObject = jest.fn().mockReturnValue({ + ...mockResolvedSavedObject, + saved_object: { + ...mockResolvedSavedObject.saved_object, + references: [ + { + id: 'boo', + name: SAVED_QUERY_ID_REF_NAME, + type: SAVED_QUERY_TYPE, + }, + { + id: 'also-boo', + name: DATA_VIEW_ID_REF_NAME, + type: DATA_VIEW_SAVED_OBJECT_TYPE, + }, + ], + }, + }); + mockRequest = { + user: { + username: 'username', + }, + context: { + core: { + savedObjects: { + client: { + resolve: mockResolveSavedObject, + }, + }, + }, + }, + } as unknown as FrameworkRequest; + + await resolveTimelineOrNull(mockRequest, '760d3d20-2142-11ec-a46f-051cb8e3154c'); + }); + + test('the fields we track in references are converted to attributes when SO is requested', () => { + const { attributes } = convertSavedObjectToSavedTimelineMock.mock.calls[0][0]; + expect(attributes.dataViewId).toEqual('also-boo'); + expect(attributes.savedQueryId).toEqual('boo'); + }); + }); }); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/pick_saved_timeline.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/pick_saved_timeline.ts index 00ec453b9a852..25a4c98f90ee6 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/pick_saved_timeline.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/pick_saved_timeline.ts @@ -18,7 +18,7 @@ export const pickSavedTimeline = ( timelineId: string | null, savedTimeline: SavedTimelineWithSavedObjectId, userInfo: AuthenticatedUser | null -): Omit => { +): SavedTimelineWithSavedObjectId => { const dateNow = new Date().valueOf(); if (timelineId == null) { diff --git a/x-pack/plugins/timelines/common/search_strategy/index_fields/index.ts b/x-pack/plugins/timelines/common/search_strategy/index_fields/index.ts index 65e6d8f1a2d6c..3579a45d2fcbb 100644 --- a/x-pack/plugins/timelines/common/search_strategy/index_fields/index.ts +++ b/x-pack/plugins/timelines/common/search_strategy/index_fields/index.ts @@ -10,9 +10,9 @@ import type { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesW import type { IEsSearchRequest, IEsSearchResponse, - IIndexPattern, } from '../../../../../../src/plugins/data/common'; import type { DocValueFields, Maybe } from '../common'; +import { FieldSpec } from '../../../../../../src/plugins/data/common'; export type BeatFieldsFactoryQueryType = 'beatFields'; @@ -25,28 +25,16 @@ export interface FieldInfo { type?: string; } -export interface IndexField { +export interface IndexField extends Omit { /** Where the field belong */ category: string; /** Example of field's value */ example?: Maybe; /** whether the field's belong to an alias index */ indexes: Array>; - /** The name of the field */ - name: string; - /** The type of the field's values as recognized by Kibana */ - type: string; - /** Whether the field's values can be efficiently searched for */ - searchable: boolean; - /** Whether the field's values can be aggregated */ - aggregatable: boolean; /** Description of the field */ description?: Maybe; format?: Maybe; - /** the elastic type as mapped in the index */ - esTypes?: string[]; - subType?: IFieldSubType; - readFromDocValues: boolean; } export type BeatFields = Record; @@ -82,13 +70,11 @@ export interface BrowserField { searchable: boolean; type: string; subType?: IFieldSubType; + readFromDocValues: boolean; } export type BrowserFields = Readonly>>; export const EMPTY_BROWSER_FIELDS = {}; export const EMPTY_DOCVALUE_FIELD: DocValueFields[] = []; -export const EMPTY_INDEX_PATTERN: IIndexPattern = { - fields: [], - title: '', -}; +export const EMPTY_INDEX_FIELDS: FieldSpec[] = []; diff --git a/x-pack/plugins/timelines/public/components/t_grid/helpers.tsx b/x-pack/plugins/timelines/public/components/t_grid/helpers.tsx index e2eb1d4d04547..617c166c37384 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/helpers.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/helpers.tsx @@ -6,7 +6,7 @@ */ import type { Filter, EsQueryConfig, Query } from '@kbn/es-query'; -import { FilterStateStore } from '@kbn/es-query'; +import { DataViewBase, FilterStateStore } from '@kbn/es-query'; import { isEmpty, get } from 'lodash/fp'; import memoizeOne from 'memoize-one'; import { ALERT_WORKFLOW_STATUS } from '@kbn/rule-data-utils'; @@ -17,7 +17,6 @@ import { handleSkipFocus, stopPropagationAndPreventDefault, } from '../../../common'; -import { IIndexPattern } from '../../../../../../src/plugins/data/public'; import type { BrowserFields } from '../../../common/search_strategy/index_fields'; import { DataProviderType, EXISTS_OPERATOR } from '../../../common/types/timeline'; import type { DataProvider, DataProvidersAnd } from '../../../common/types/timeline'; @@ -138,7 +137,7 @@ export const buildGlobalQuery = (dataProviders: DataProvider[], browserFields: B interface CombineQueries { config: EsQueryConfig; dataProviders: DataProvider[]; - indexPattern: IIndexPattern; + indexPattern: DataViewBase; browserFields: BrowserFields; filters: Filter[]; kqlQuery: Query; diff --git a/x-pack/plugins/timelines/public/components/t_grid/integrated/index.tsx b/x-pack/plugins/timelines/public/components/t_grid/integrated/index.tsx index 21e3c9b5c1dd2..614857c26cbb1 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/integrated/index.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/integrated/index.tsx @@ -15,6 +15,7 @@ import styled from 'styled-components'; import { useDispatch } from 'react-redux'; import { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { DataViewBase, Filter, Query } from '@kbn/es-query'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; import { Direction, EntityType } from '../../../../common/search_strategy'; import type { DocValueFields } from '../../../../common/search_strategy'; @@ -35,13 +36,7 @@ import type { RowRenderer, AlertStatus, } from '../../../../common/types/timeline'; -import { - esQuery, - Filter, - IIndexPattern, - Query, - DataPublicPluginStart, -} from '../../../../../../../src/plugins/data/public'; +import { esQuery, DataPublicPluginStart } from '../../../../../../../src/plugins/data/public'; import { useDeepEqualSelector } from '../../../hooks/use_selector'; import { defaultHeaders } from '../body/column_headers/default_headers'; import { buildCombinedQuery, getCombinedFilterQuery, resolverIsShowing } from '../helpers'; @@ -120,7 +115,7 @@ export interface TGridIntegratedProps { height?: number; id: TimelineId; indexNames: string[]; - indexPattern: IIndexPattern; + indexPattern: DataViewBase; isLive: boolean; isLoadingIndexPattern: boolean; itemsPerPage: number; diff --git a/x-pack/plugins/timelines/public/components/utils/keury/index.ts b/x-pack/plugins/timelines/public/components/utils/keury/index.ts index 3eb03cc63543e..776f5883a57d5 100644 --- a/x-pack/plugins/timelines/public/components/utils/keury/index.ts +++ b/x-pack/plugins/timelines/public/components/utils/keury/index.ts @@ -11,14 +11,14 @@ import { EsQueryConfig, Filter, fromKueryExpression, - IndexPatternBase, + DataViewBase, Query, toElasticsearchQuery, } from '@kbn/es-query'; export const convertKueryToElasticSearchQuery = ( kueryExpression: string, - indexPattern?: IndexPatternBase + indexPattern?: DataViewBase ) => { try { return kueryExpression @@ -29,10 +29,7 @@ export const convertKueryToElasticSearchQuery = ( } }; -export const convertKueryToDslFilter = ( - kueryExpression: string, - indexPattern: IndexPatternBase -) => { +export const convertKueryToDslFilter = (kueryExpression: string, indexPattern: DataViewBase) => { try { return kueryExpression ? toElasticsearchQuery(fromKueryExpression(kueryExpression), indexPattern) @@ -74,7 +71,7 @@ export const convertToBuildEsQuery = ({ filters, }: { config: EsQueryConfig; - indexPattern: IndexPatternBase; + indexPattern: DataViewBase; queries: Query[]; filters: Filter[]; }) => { diff --git a/x-pack/plugins/timelines/public/container/source/index.tsx b/x-pack/plugins/timelines/public/container/source/index.tsx index 93b02c411f050..f66eedb94f881 100644 --- a/x-pack/plugins/timelines/public/container/source/index.tsx +++ b/x-pack/plugins/timelines/public/container/source/index.tsx @@ -10,6 +10,7 @@ import { isEmpty, isEqual, pick } from 'lodash/fp'; import { Subscription } from 'rxjs/internal/Subscription'; import memoizeOne from 'memoize-one'; +import { DataViewBase } from '@kbn/es-query'; import { BrowserField, BrowserFields, @@ -21,7 +22,6 @@ import { import * as i18n from './translations'; import { - IIndexPattern, DataPublicPluginStart, isCompleteResponse, isErrorResponse, @@ -37,7 +37,7 @@ interface FetchIndexReturn { docValueFields: DocValueFields[]; indexes: string[]; indexExists: boolean; - indexPatterns: IIndexPattern; + indexPatterns: DataViewBase; } /** @@ -90,7 +90,7 @@ export const getDocValueFields = memoizeOne( ); export const getIndexFields = memoizeOne( - (title: string, fields: IndexField[]): IIndexPattern => + (title: string, fields: IndexField[]): DataViewBase => fields && fields.length > 0 ? { fields: fields.map((field) => diff --git a/x-pack/plugins/timelines/public/mock/index_pattern.ts b/x-pack/plugins/timelines/public/mock/index_pattern.ts index 361dbf71bd6f4..893ed1e889925 100644 --- a/x-pack/plugins/timelines/public/mock/index_pattern.ts +++ b/x-pack/plugins/timelines/public/mock/index_pattern.ts @@ -5,105 +5,73 @@ * 2.0. */ -import { IIndexPattern } from '../../../../../src/plugins/data/public'; +import { DataViewBase } from '@kbn/es-query'; -export const mockIndexPattern: IIndexPattern = { +export const mockIndexPattern: DataViewBase = { fields: [ { name: '@timestamp', - searchable: true, type: 'date', - aggregatable: true, }, { name: '@version', - searchable: true, type: 'string', - aggregatable: true, }, { name: 'agent.ephemeral_id', - searchable: true, type: 'string', - aggregatable: true, }, { name: 'agent.hostname', - searchable: true, type: 'string', - aggregatable: true, }, { name: 'agent.id', - searchable: true, type: 'string', - aggregatable: true, }, { name: 'agent.test1', - searchable: true, type: 'string', - aggregatable: true, }, { name: 'agent.test2', - searchable: true, type: 'string', - aggregatable: true, }, { name: 'agent.test3', - searchable: true, type: 'string', - aggregatable: true, }, { name: 'agent.test4', - searchable: true, type: 'string', - aggregatable: true, }, { name: 'agent.test5', - searchable: true, type: 'string', - aggregatable: true, }, { name: 'agent.test6', - searchable: true, type: 'string', - aggregatable: true, }, { name: 'agent.test7', - searchable: true, type: 'string', - aggregatable: true, }, { name: 'agent.test8', - searchable: true, type: 'string', - aggregatable: true, }, { name: 'host.name', - searchable: true, type: 'string', - aggregatable: true, }, { name: 'nestedField.firstAttributes', - searchable: true, type: 'string', - aggregatable: false, }, { name: 'nestedField.secondAttributes', - searchable: true, type: 'string', - aggregatable: false, }, ], title: 'filebeat-*,auditbeat-*,packetbeat-*', diff --git a/x-pack/plugins/timelines/server/search_strategy/index_fields/index.ts b/x-pack/plugins/timelines/server/search_strategy/index_fields/index.ts index 294929862018a..2427ec8bb6071 100644 --- a/x-pack/plugins/timelines/server/search_strategy/index_fields/index.ts +++ b/x-pack/plugins/timelines/server/search_strategy/index_fields/index.ts @@ -13,7 +13,6 @@ import { IndexPatternsFetcher, ISearchStrategy, SearchStrategyDependencies, - FieldDescriptor, } from '../../../../../../src/plugins/data/server'; // TODO cleanup path @@ -25,6 +24,7 @@ import { DELETED_SECURITY_SOLUTION_DATA_VIEW, } from '../../../common'; import { StartPlugins } from '../../types'; +import { FieldSpec } from '../../../../../../src/plugins/data_views/common'; const apmIndexPattern = 'apm-*-transaction*'; const apmDataStreamsPattern = 'traces-apm*'; @@ -122,8 +122,7 @@ export const requestIndexFieldSearch = async ( ); if (!request.onlyCheckIfIndicesExist) { const dataViewSpec = dataView.toSpec(); - // type cast because index pattern type is FieldSpec and timeline type is FieldDescriptor, same diff - const fieldDescriptor = [Object.values(dataViewSpec.fields ?? {}) as FieldDescriptor[]]; + const fieldDescriptor = [Object.values(dataViewSpec.fields ?? {})]; runtimeMappings = dataViewSpec.runtimeFieldMap ?? {}; indexFields = await formatIndexFields(beatFields, fieldDescriptor, patternList); } @@ -188,7 +187,7 @@ export const dedupeIndexName = (indices: string[]) => return acc; }, []); -const missingFields: FieldDescriptor[] = [ +const missingFields: FieldSpec[] = [ { name: '_id', type: 'string', @@ -221,7 +220,7 @@ const missingFields: FieldDescriptor[] = [ export const createFieldItem = ( beatFields: BeatFields, indexesAlias: string[], - index: FieldDescriptor, + index: FieldSpec, indexesAliasIdx: number ): IndexField => { const alias = indexesAlias[indexesAliasIdx]; @@ -237,6 +236,9 @@ export const createFieldItem = ( return { ...beatIndex, ...index, + // the format type on FieldSpec is SerializedFieldFormat + // and is a string on beatIndex + format: index.format?.id ?? beatIndex.format, indexes: [alias], }; }; @@ -251,24 +253,24 @@ export const createFieldItem = ( * This intentionally waits for the next tick on the event loop to process as the large 4.7 megs * has already consumed a lot of the event loop processing up to this function and we want to give * I/O opportunity to occur by scheduling this on the next loop. - * @param responsesIndexFields The response index fields to loop over + * @param responsesFieldSpec The response index fields to loop over * @param indexesAlias The index aliases such as filebeat-* */ export const formatFirstFields = async ( beatFields: BeatFields, - responsesIndexFields: FieldDescriptor[][], + responsesFieldSpec: FieldSpec[][], indexesAlias: string[] ): Promise => { return new Promise((resolve) => { setTimeout(() => { resolve( - responsesIndexFields.reduce( - (accumulator: IndexField[], indexFields: FieldDescriptor[], indexesAliasIdx: number) => { + responsesFieldSpec.reduce( + (accumulator: IndexField[], fieldSpec: FieldSpec[], indexesAliasIdx: number) => { missingFields.forEach((index) => { const item = createFieldItem(beatFields, indexesAlias, index, indexesAliasIdx); accumulator.push(item); }); - indexFields.forEach((index) => { + fieldSpec.forEach((index) => { const item = createFieldItem(beatFields, indexesAlias, index, indexesAliasIdx); accumulator.push(item); }); @@ -325,15 +327,15 @@ export const formatSecondFields = async (fields: IndexField[]): Promise => { - const fields = await formatFirstFields(beatFields, responsesIndexFields, indexesAlias); + const fields = await formatFirstFields(beatFields, responsesFieldSpec, indexesAlias); const secondFields = await formatSecondFields(fields); return secondFields; };