From 624cdabfe8198ae05a9a1fafc101aa62d6db3388 Mon Sep 17 00:00:00 2001 From: Devin Hurley Date: Wed, 30 Aug 2023 13:34:33 -0400 Subject: [PATCH 1/7] fixes infosec issue --- .../public/common/components/query_bar/index.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) 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 d86f3de10b549..ad75b14a8bdbb 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 @@ -11,10 +11,11 @@ import deepEqual from 'fast-deep-equal'; import type { DataViewBase, Filter, Query, TimeRange } from '@kbn/es-query'; import type { FilterManager, SavedQuery, SavedQueryTimeFilter } from '@kbn/data-plugin/public'; import { TimeHistory } from '@kbn/data-plugin/public'; -import type { DataView } from '@kbn/data-views-plugin/public'; +import { DataView } from '@kbn/data-views-plugin/public'; import type { SearchBarProps } from '@kbn/unified-search-plugin/public'; import { SearchBar } from '@kbn/unified-search-plugin/public'; import { Storage } from '@kbn/kibana-utils-plugin/public'; +import { useKibana } from '../../lib/kibana'; export interface QueryBarComponentProps { dataTestSubj?: string; @@ -56,6 +57,7 @@ export const QueryBar = memo( displayStyle, isDisabled, }) => { + const { fieldFormats } = useKibana().services; const onQuerySubmit = useCallback( (payload: { dateRange: TimeRange; query?: Query }) => { if (payload.query != null && !deepEqual(payload.query, filterQuery)) { @@ -102,7 +104,10 @@ export const QueryBar = memo( [filterManager] ); - const indexPatterns = useMemo(() => [indexPattern], [indexPattern]); + const indexPatterns = useMemo( + () => [new DataView({ ...indexPattern, fieldFormats })], + [fieldFormats, indexPattern] + ); const timeHistory = useMemo(() => new TimeHistory(new Storage(localStorage)), []); return ( From f55945c9c2eb7e7398fd26d6c94eb38eae173e0e Mon Sep 17 00:00:00 2001 From: Devin Hurley Date: Wed, 30 Aug 2023 16:01:07 -0400 Subject: [PATCH 2/7] updates tests, creates data view correctly --- .../components/query_bar/index.test.tsx | 419 +++++++++++++----- .../common/components/query_bar/index.tsx | 43 +- 2 files changed, 333 insertions(+), 129 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/query_bar/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/query_bar/index.test.tsx index 118c78e290759..b0aa09363507e 100644 --- a/x-pack/plugins/security_solution/public/common/components/query_bar/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/query_bar/index.test.tsx @@ -82,130 +82,303 @@ describe('QueryBar ', () => { ...searchBarProps } = wrapper.find(SearchBar).props(); - expect(searchBarProps).toEqual({ - dataTestSubj: undefined, - dateRangeFrom: 'now/d', - dateRangeTo: 'now/d', - displayStyle: undefined, - filters: [], - indexPatterns: [ - { - fields: [ - { - aggregatable: true, - 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: false, - name: 'nestedField.firstAttributes', - searchable: true, - type: 'string', - }, - { - aggregatable: false, - name: 'nestedField.secondAttributes', - searchable: true, - type: 'string', - }, - ], - title: 'filebeat-*,auditbeat-*,packetbeat-*', + expect(searchBarProps).toMatchInlineSnapshot(` + Object { + "dataTestSubj": undefined, + "dateRangeFrom": "now/d", + "dateRangeTo": "now/d", + "displayStyle": undefined, + "filters": Array [], + "indexPatterns": Array [ + DataView { + "allowNoIndex": false, + "deleteFieldFormat": [Function], + "fieldAttrs": Object {}, + "fieldFormatMap": Object {}, + "fieldFormats": Object {}, + "fields": FldList [ + Object { + "aggregatable": true, + "conflictDescriptions": undefined, + "count": 0, + "customLabel": undefined, + "esTypes": undefined, + "lang": undefined, + "name": "@timestamp", + "readFromDocValues": false, + "script": undefined, + "scripted": false, + "searchable": true, + "subType": undefined, + "type": "date", + }, + Object { + "aggregatable": true, + "conflictDescriptions": undefined, + "count": 0, + "customLabel": undefined, + "esTypes": undefined, + "lang": undefined, + "name": "@version", + "readFromDocValues": false, + "script": undefined, + "scripted": false, + "searchable": true, + "subType": undefined, + "type": "string", + }, + Object { + "aggregatable": true, + "conflictDescriptions": undefined, + "count": 0, + "customLabel": undefined, + "esTypes": undefined, + "lang": undefined, + "name": "agent.ephemeral_id", + "readFromDocValues": false, + "script": undefined, + "scripted": false, + "searchable": true, + "subType": undefined, + "type": "string", + }, + Object { + "aggregatable": true, + "conflictDescriptions": undefined, + "count": 0, + "customLabel": undefined, + "esTypes": undefined, + "lang": undefined, + "name": "agent.hostname", + "readFromDocValues": false, + "script": undefined, + "scripted": false, + "searchable": true, + "subType": undefined, + "type": "string", + }, + Object { + "aggregatable": true, + "conflictDescriptions": undefined, + "count": 0, + "customLabel": undefined, + "esTypes": undefined, + "lang": undefined, + "name": "agent.id", + "readFromDocValues": false, + "script": undefined, + "scripted": false, + "searchable": true, + "subType": undefined, + "type": "string", + }, + Object { + "aggregatable": true, + "conflictDescriptions": undefined, + "count": 0, + "customLabel": undefined, + "esTypes": undefined, + "lang": undefined, + "name": "agent.test1", + "readFromDocValues": false, + "script": undefined, + "scripted": false, + "searchable": true, + "subType": undefined, + "type": "string", + }, + Object { + "aggregatable": true, + "conflictDescriptions": undefined, + "count": 0, + "customLabel": undefined, + "esTypes": undefined, + "lang": undefined, + "name": "agent.test2", + "readFromDocValues": false, + "script": undefined, + "scripted": false, + "searchable": true, + "subType": undefined, + "type": "string", + }, + Object { + "aggregatable": true, + "conflictDescriptions": undefined, + "count": 0, + "customLabel": undefined, + "esTypes": undefined, + "lang": undefined, + "name": "agent.test3", + "readFromDocValues": false, + "script": undefined, + "scripted": false, + "searchable": true, + "subType": undefined, + "type": "string", + }, + Object { + "aggregatable": true, + "conflictDescriptions": undefined, + "count": 0, + "customLabel": undefined, + "esTypes": undefined, + "lang": undefined, + "name": "agent.test4", + "readFromDocValues": false, + "script": undefined, + "scripted": false, + "searchable": true, + "subType": undefined, + "type": "string", + }, + Object { + "aggregatable": true, + "conflictDescriptions": undefined, + "count": 0, + "customLabel": undefined, + "esTypes": undefined, + "lang": undefined, + "name": "agent.test5", + "readFromDocValues": false, + "script": undefined, + "scripted": false, + "searchable": true, + "subType": undefined, + "type": "string", + }, + Object { + "aggregatable": true, + "conflictDescriptions": undefined, + "count": 0, + "customLabel": undefined, + "esTypes": undefined, + "lang": undefined, + "name": "agent.test6", + "readFromDocValues": false, + "script": undefined, + "scripted": false, + "searchable": true, + "subType": undefined, + "type": "string", + }, + Object { + "aggregatable": true, + "conflictDescriptions": undefined, + "count": 0, + "customLabel": undefined, + "esTypes": undefined, + "lang": undefined, + "name": "agent.test7", + "readFromDocValues": false, + "script": undefined, + "scripted": false, + "searchable": true, + "subType": undefined, + "type": "string", + }, + Object { + "aggregatable": true, + "conflictDescriptions": undefined, + "count": 0, + "customLabel": undefined, + "esTypes": undefined, + "lang": undefined, + "name": "agent.test8", + "readFromDocValues": false, + "script": undefined, + "scripted": false, + "searchable": true, + "subType": undefined, + "type": "string", + }, + Object { + "aggregatable": true, + "conflictDescriptions": undefined, + "count": 0, + "customLabel": undefined, + "esTypes": undefined, + "lang": undefined, + "name": "host.name", + "readFromDocValues": false, + "script": undefined, + "scripted": false, + "searchable": true, + "subType": undefined, + "type": "string", + }, + Object { + "aggregatable": false, + "conflictDescriptions": undefined, + "count": 0, + "customLabel": undefined, + "esTypes": undefined, + "lang": undefined, + "name": "nestedField.firstAttributes", + "readFromDocValues": false, + "script": undefined, + "scripted": false, + "searchable": true, + "subType": undefined, + "type": "string", + }, + Object { + "aggregatable": false, + "conflictDescriptions": undefined, + "count": 0, + "customLabel": undefined, + "esTypes": undefined, + "lang": undefined, + "name": "nestedField.secondAttributes", + "readFromDocValues": false, + "script": undefined, + "scripted": false, + "searchable": true, + "subType": undefined, + "type": "string", + }, + ], + "flattenHit": [Function], + "getFieldAttrs": [Function], + "getIndexPattern": [Function], + "getName": [Function], + "getOriginalSavedObjectBody": [Function], + "id": undefined, + "matchedIndices": Array [], + "metaFields": Array [], + "name": "", + "namespaces": Array [], + "originalSavedObjectBody": Object {}, + "resetOriginalSavedObjectBody": [Function], + "runtimeFieldMap": Object {}, + "setFieldFormat": [Function], + "setIndexPattern": [Function], + "shortDotsEnable": false, + "sourceFilters": Array [], + "timeFieldName": undefined, + "title": "filebeat-*,auditbeat-*,packetbeat-*", + "type": undefined, + "typeMeta": undefined, + "version": undefined, + }, + ], + "isDisabled": undefined, + "isLoading": false, + "isRefreshPaused": true, + "query": Object { + "language": "kuery", + "query": "here: query", }, - ], - isLoading: false, - isRefreshPaused: true, - query: { - language: 'kuery', - query: 'here: query', - }, - refreshInterval: undefined, - savedQuery: undefined, - showAutoRefreshOnly: false, - showDatePicker: false, - showFilterBar: true, - showQueryInput: true, - showSaveQuery: true, - showSubmitButton: false, - }); + "refreshInterval": undefined, + "savedQuery": undefined, + "showAutoRefreshOnly": false, + "showDatePicker": false, + "showFilterBar": true, + "showQueryInput": true, + "showSaveQuery": true, + "showSubmitButton": false, + } + `); }); // FLAKY: https://github.com/elastic/kibana/issues/132659 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 ad75b14a8bdbb..df43f71e6d003 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 @@ -15,6 +15,8 @@ import { DataView } from '@kbn/data-views-plugin/public'; import type { SearchBarProps } from '@kbn/unified-search-plugin/public'; import { SearchBar } from '@kbn/unified-search-plugin/public'; import { Storage } from '@kbn/kibana-utils-plugin/public'; +import type { DataViewFieldMap } from '@kbn/data-views-plugin/common'; + import { useKibana } from '../../lib/kibana'; export interface QueryBarComponentProps { @@ -57,7 +59,8 @@ export const QueryBar = memo( displayStyle, isDisabled, }) => { - const { fieldFormats } = useKibana().services; + const { data, fieldFormats } = useKibana().services; + // const [dataView, setDataView] = useState([indexPattern] as DataView[]); const onQuerySubmit = useCallback( (payload: { dateRange: TimeRange; query?: Query }) => { if (payload.query != null && !deepEqual(payload.query, filterQuery)) { @@ -104,10 +107,38 @@ export const QueryBar = memo( [filterManager] ); - const indexPatterns = useMemo( - () => [new DataView({ ...indexPattern, fieldFormats })], - [fieldFormats, indexPattern] - ); + // useEffect(() => { + // if (Object.hasOwn(indexPattern, 'getName')) { + // setDataView([indexPattern] as DataView[]); + // } else { + // const createDataView = async () => { + // const dv = await data.dataViews.create({ title: indexPattern.title }); + // setDataView([dv]); + // }; + // createDataView(); + // } + // }, [data.dataViews, indexPattern]); + + const dataView = useMemo(() => { + if (Object.hasOwn(indexPattern, 'getName')) { + return [indexPattern] as DataView[]; + } + const dv = new DataView({ + spec: { + ...indexPattern, + fields: indexPattern.fields.reduce((acc, field) => { + // @ts-expect-error missing 'searchable' , aggregatable properties + // this is okay because it's better than having the whole app + // explode + acc[field.name] = field; + return acc; + }, {} as DataViewFieldMap), + }, + fieldFormats, + }); + return [dv]; + }, [indexPattern, fieldFormats]); + const timeHistory = useMemo(() => new TimeHistory(new Storage(localStorage)), []); return ( @@ -116,7 +147,7 @@ export const QueryBar = memo( dateRangeFrom={dateRangeFrom} dateRangeTo={dateRangeTo} filters={filters} - indexPatterns={indexPatterns as DataView[]} + indexPatterns={dataView} isLoading={isLoading} isRefreshPaused={isRefreshPaused} query={filterQuery} From 3fe87bf22177737299da80e048dfa7c680198673 Mon Sep 17 00:00:00 2001 From: Devin Hurley Date: Wed, 30 Aug 2023 16:01:49 -0400 Subject: [PATCH 3/7] cleanup --- .../public/common/components/query_bar/index.tsx | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) 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 df43f71e6d003..4ea7287f9ba7f 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 @@ -59,8 +59,7 @@ export const QueryBar = memo( displayStyle, isDisabled, }) => { - const { data, fieldFormats } = useKibana().services; - // const [dataView, setDataView] = useState([indexPattern] as DataView[]); + const { fieldFormats } = useKibana().services; const onQuerySubmit = useCallback( (payload: { dateRange: TimeRange; query?: Query }) => { if (payload.query != null && !deepEqual(payload.query, filterQuery)) { @@ -107,18 +106,6 @@ export const QueryBar = memo( [filterManager] ); - // useEffect(() => { - // if (Object.hasOwn(indexPattern, 'getName')) { - // setDataView([indexPattern] as DataView[]); - // } else { - // const createDataView = async () => { - // const dv = await data.dataViews.create({ title: indexPattern.title }); - // setDataView([dv]); - // }; - // createDataView(); - // } - // }, [data.dataViews, indexPattern]); - const dataView = useMemo(() => { if (Object.hasOwn(indexPattern, 'getName')) { return [indexPattern] as DataView[]; From 7f8a42c91cf163023922b10d596c43d606a5dfb3 Mon Sep 17 00:00:00 2001 From: Devin Hurley Date: Thu, 31 Aug 2023 14:21:12 -0400 Subject: [PATCH 4/7] typeguard data view type --- .../public/common/components/query_bar/index.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) 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 4ea7287f9ba7f..72143ea3c123e 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 @@ -39,6 +39,9 @@ export interface QueryBarComponentProps { isDisabled?: boolean; } +export const isDataView = (obj: unknown): obj is DataView => + obj != null && typeof obj === 'object' && Object.hasOwn(obj, 'getName'); + export const QueryBar = memo( ({ dateRangeFrom, @@ -107,8 +110,8 @@ export const QueryBar = memo( ); const dataView = useMemo(() => { - if (Object.hasOwn(indexPattern, 'getName')) { - return [indexPattern] as DataView[]; + if (isDataView(indexPattern)) { + return [indexPattern]; } const dv = new DataView({ spec: { From a625f582e3c0a4cc59ea2446023582afc9a1a1ea Mon Sep 17 00:00:00 2001 From: Devin Hurley Date: Thu, 31 Aug 2023 17:55:44 -0400 Subject: [PATCH 5/7] utilizes in-memory data view creation, fixes test --- .../components/query_bar/index.test.tsx | 370 +++--------------- .../common/components/query_bar/index.tsx | 43 +- 2 files changed, 75 insertions(+), 338 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/query_bar/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/query_bar/index.test.tsx index b0aa09363507e..3aa6ea6513484 100644 --- a/x-pack/plugins/security_solution/public/common/components/query_bar/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/query_bar/index.test.tsx @@ -16,9 +16,41 @@ import { SearchBar } from '@kbn/unified-search-plugin/public'; import type { QueryBarComponentProps } from '.'; import { QueryBar } from '.'; +import type { DataViewFieldMap } from '@kbn/data-views-plugin/common'; +import { createStubDataView } from '@kbn/data-views-plugin/common/data_view.stub'; +import { fields } from '@kbn/data-views-plugin/common/mocks'; +import { useKibana } from '../../lib/kibana'; + +const getMockIndexPattern = () => ({ + ...createStubDataView({ + spec: { + id: '1234', + title: 'logstash-*', + fields: ((): DataViewFieldMap => { + const fieldMap: DataViewFieldMap = Object.create(null); + for (const field of fields) { + fieldMap[field.name] = { ...field }; + } + return fieldMap; + })(), + }, + }), +}); + const mockUiSettingsForFilterManager = coreMock.createStart().uiSettings; +jest.mock('../../lib/kibana'); describe('QueryBar ', () => { + (useKibana as jest.Mock).mockReturnValue({ + services: { + data: { + dataViews: { + create: jest.fn().mockResolvedValue(getMockIndexPattern()), + clearInstanceCache: jest.fn(), + }, + }, + }, + }); const mockOnChangeQuery = jest.fn(); const mockOnSubmitQuery = jest.fn(); const mockOnSavedQuery = jest.fn(); @@ -52,10 +84,10 @@ describe('QueryBar ', () => { mockOnSavedQuery.mockClear(); }); - test('check if we format the appropriate props to QueryBar', () => { - const wrapper = mount( - - { + await act(async () => { + const wrapper = await getWrapper( + { onSubmitQuery={mockOnSubmitQuery} onSavedQuery={mockOnSavedQuery} /> - - ); - const { - customSubmitButton, - timeHistory, - onClearSavedQuery, - onFiltersUpdated, - onQueryChange, - onQuerySubmit, - onSaved, - onSavedQueryUpdated, - ...searchBarProps - } = wrapper.find(SearchBar).props(); + ); - expect(searchBarProps).toMatchInlineSnapshot(` - Object { - "dataTestSubj": undefined, - "dateRangeFrom": "now/d", - "dateRangeTo": "now/d", - "displayStyle": undefined, - "filters": Array [], - "indexPatterns": Array [ - DataView { - "allowNoIndex": false, - "deleteFieldFormat": [Function], - "fieldAttrs": Object {}, - "fieldFormatMap": Object {}, - "fieldFormats": Object {}, - "fields": FldList [ - Object { - "aggregatable": true, - "conflictDescriptions": undefined, - "count": 0, - "customLabel": undefined, - "esTypes": undefined, - "lang": undefined, - "name": "@timestamp", - "readFromDocValues": false, - "script": undefined, - "scripted": false, - "searchable": true, - "subType": undefined, - "type": "date", - }, - Object { - "aggregatable": true, - "conflictDescriptions": undefined, - "count": 0, - "customLabel": undefined, - "esTypes": undefined, - "lang": undefined, - "name": "@version", - "readFromDocValues": false, - "script": undefined, - "scripted": false, - "searchable": true, - "subType": undefined, - "type": "string", - }, - Object { - "aggregatable": true, - "conflictDescriptions": undefined, - "count": 0, - "customLabel": undefined, - "esTypes": undefined, - "lang": undefined, - "name": "agent.ephemeral_id", - "readFromDocValues": false, - "script": undefined, - "scripted": false, - "searchable": true, - "subType": undefined, - "type": "string", - }, - Object { - "aggregatable": true, - "conflictDescriptions": undefined, - "count": 0, - "customLabel": undefined, - "esTypes": undefined, - "lang": undefined, - "name": "agent.hostname", - "readFromDocValues": false, - "script": undefined, - "scripted": false, - "searchable": true, - "subType": undefined, - "type": "string", - }, - Object { - "aggregatable": true, - "conflictDescriptions": undefined, - "count": 0, - "customLabel": undefined, - "esTypes": undefined, - "lang": undefined, - "name": "agent.id", - "readFromDocValues": false, - "script": undefined, - "scripted": false, - "searchable": true, - "subType": undefined, - "type": "string", - }, - Object { - "aggregatable": true, - "conflictDescriptions": undefined, - "count": 0, - "customLabel": undefined, - "esTypes": undefined, - "lang": undefined, - "name": "agent.test1", - "readFromDocValues": false, - "script": undefined, - "scripted": false, - "searchable": true, - "subType": undefined, - "type": "string", - }, - Object { - "aggregatable": true, - "conflictDescriptions": undefined, - "count": 0, - "customLabel": undefined, - "esTypes": undefined, - "lang": undefined, - "name": "agent.test2", - "readFromDocValues": false, - "script": undefined, - "scripted": false, - "searchable": true, - "subType": undefined, - "type": "string", - }, - Object { - "aggregatable": true, - "conflictDescriptions": undefined, - "count": 0, - "customLabel": undefined, - "esTypes": undefined, - "lang": undefined, - "name": "agent.test3", - "readFromDocValues": false, - "script": undefined, - "scripted": false, - "searchable": true, - "subType": undefined, - "type": "string", - }, - Object { - "aggregatable": true, - "conflictDescriptions": undefined, - "count": 0, - "customLabel": undefined, - "esTypes": undefined, - "lang": undefined, - "name": "agent.test4", - "readFromDocValues": false, - "script": undefined, - "scripted": false, - "searchable": true, - "subType": undefined, - "type": "string", - }, - Object { - "aggregatable": true, - "conflictDescriptions": undefined, - "count": 0, - "customLabel": undefined, - "esTypes": undefined, - "lang": undefined, - "name": "agent.test5", - "readFromDocValues": false, - "script": undefined, - "scripted": false, - "searchable": true, - "subType": undefined, - "type": "string", - }, - Object { - "aggregatable": true, - "conflictDescriptions": undefined, - "count": 0, - "customLabel": undefined, - "esTypes": undefined, - "lang": undefined, - "name": "agent.test6", - "readFromDocValues": false, - "script": undefined, - "scripted": false, - "searchable": true, - "subType": undefined, - "type": "string", - }, - Object { - "aggregatable": true, - "conflictDescriptions": undefined, - "count": 0, - "customLabel": undefined, - "esTypes": undefined, - "lang": undefined, - "name": "agent.test7", - "readFromDocValues": false, - "script": undefined, - "scripted": false, - "searchable": true, - "subType": undefined, - "type": "string", - }, - Object { - "aggregatable": true, - "conflictDescriptions": undefined, - "count": 0, - "customLabel": undefined, - "esTypes": undefined, - "lang": undefined, - "name": "agent.test8", - "readFromDocValues": false, - "script": undefined, - "scripted": false, - "searchable": true, - "subType": undefined, - "type": "string", - }, - Object { - "aggregatable": true, - "conflictDescriptions": undefined, - "count": 0, - "customLabel": undefined, - "esTypes": undefined, - "lang": undefined, - "name": "host.name", - "readFromDocValues": false, - "script": undefined, - "scripted": false, - "searchable": true, - "subType": undefined, - "type": "string", - }, - Object { - "aggregatable": false, - "conflictDescriptions": undefined, - "count": 0, - "customLabel": undefined, - "esTypes": undefined, - "lang": undefined, - "name": "nestedField.firstAttributes", - "readFromDocValues": false, - "script": undefined, - "scripted": false, - "searchable": true, - "subType": undefined, - "type": "string", - }, - Object { - "aggregatable": false, - "conflictDescriptions": undefined, - "count": 0, - "customLabel": undefined, - "esTypes": undefined, - "lang": undefined, - "name": "nestedField.secondAttributes", - "readFromDocValues": false, - "script": undefined, - "scripted": false, - "searchable": true, - "subType": undefined, - "type": "string", - }, - ], - "flattenHit": [Function], - "getFieldAttrs": [Function], - "getIndexPattern": [Function], - "getName": [Function], - "getOriginalSavedObjectBody": [Function], - "id": undefined, - "matchedIndices": Array [], - "metaFields": Array [], - "name": "", - "namespaces": Array [], - "originalSavedObjectBody": Object {}, - "resetOriginalSavedObjectBody": [Function], - "runtimeFieldMap": Object {}, - "setFieldFormat": [Function], - "setIndexPattern": [Function], - "shortDotsEnable": false, - "sourceFilters": Array [], - "timeFieldName": undefined, - "title": "filebeat-*,auditbeat-*,packetbeat-*", - "type": undefined, - "typeMeta": undefined, - "version": undefined, - }, - ], - "isDisabled": undefined, - "isLoading": false, - "isRefreshPaused": true, - "query": Object { - "language": "kuery", - "query": "here: query", - }, - "refreshInterval": undefined, - "savedQuery": undefined, - "showAutoRefreshOnly": false, - "showDatePicker": false, - "showFilterBar": true, - "showQueryInput": true, - "showSaveQuery": true, - "showSubmitButton": false, - } - `); + await waitFor(() => { + wrapper.update(); + const { + customSubmitButton, + timeHistory, + onClearSavedQuery, + onFiltersUpdated, + onQueryChange, + onQuerySubmit, + onSaved, + onSavedQueryUpdated, + ...searchBarProps + } = wrapper.find(SearchBar).props(); + expect((searchBarProps?.indexPatterns ?? [{ id: 'unknown' }])[0].id).toEqual( + getMockIndexPattern().id + ); + }); + }); }); // FLAKY: https://github.com/elastic/kibana/issues/132659 @@ -467,7 +208,6 @@ describe('QueryBar ', () => { const onSubmitQueryRef = searchBarProps.onQuerySubmit; const onSavedQueryRef = searchBarProps.onSavedQueryUpdated; wrapper.setProps({ onSavedQuery: jest.fn() }); - wrapper.update(); expect(onSavedQueryRef).not.toEqual(wrapper.find(SearchBar).props().onSavedQueryUpdated); expect(onChangedQueryRef).toEqual(wrapper.find(SearchBar).props().onQueryChange); 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 72143ea3c123e..aea4874f9a2ef 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 @@ -5,17 +5,16 @@ * 2.0. */ -import React, { memo, useMemo, useCallback } from 'react'; +import React, { memo, useMemo, useCallback, useState, useEffect } from 'react'; import deepEqual from 'fast-deep-equal'; import type { DataViewBase, Filter, Query, TimeRange } from '@kbn/es-query'; import type { FilterManager, SavedQuery, SavedQueryTimeFilter } from '@kbn/data-plugin/public'; import { TimeHistory } from '@kbn/data-plugin/public'; -import { DataView } from '@kbn/data-views-plugin/public'; +import type { DataView } from '@kbn/data-views-plugin/public'; import type { SearchBarProps } from '@kbn/unified-search-plugin/public'; import { SearchBar } from '@kbn/unified-search-plugin/public'; import { Storage } from '@kbn/kibana-utils-plugin/public'; -import type { DataViewFieldMap } from '@kbn/data-views-plugin/common'; import { useKibana } from '../../lib/kibana'; @@ -62,7 +61,8 @@ export const QueryBar = memo( displayStyle, isDisabled, }) => { - const { fieldFormats } = useKibana().services; + const { data } = useKibana().services; + const [dataView, setDataView] = useState(); const onQuerySubmit = useCallback( (payload: { dateRange: TimeRange; query?: Query }) => { if (payload.query != null && !deepEqual(payload.query, filterQuery)) { @@ -109,35 +109,32 @@ export const QueryBar = memo( [filterManager] ); - const dataView = useMemo(() => { + useEffect(() => { if (isDataView(indexPattern)) { - return [indexPattern]; + setDataView(indexPattern); + } else { + const createDataView = async () => { + const dv = await data.dataViews.create({ title: indexPattern.title }); + setDataView(dv); + }; + createDataView(); } - const dv = new DataView({ - spec: { - ...indexPattern, - fields: indexPattern.fields.reduce((acc, field) => { - // @ts-expect-error missing 'searchable' , aggregatable properties - // this is okay because it's better than having the whole app - // explode - acc[field.name] = field; - return acc; - }, {} as DataViewFieldMap), - }, - fieldFormats, - }); - return [dv]; - }, [indexPattern, fieldFormats]); + return () => { + if (dataView?.id) { + data.dataViews.clearInstanceCache(dataView?.id); + } + }; + }, [data.dataViews, dataView?.id, indexPattern]); const timeHistory = useMemo(() => new TimeHistory(new Storage(localStorage)), []); - + const arrDataView = useMemo(() => (dataView != null ? [dataView] : []), [dataView]); return ( Date: Tue, 12 Sep 2023 20:32:47 -0400 Subject: [PATCH 6/7] fixes useEffect --- .../public/common/components/query_bar/index.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) 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 aea4874f9a2ef..9356956c23d56 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 @@ -110,21 +110,22 @@ export const QueryBar = memo( ); useEffect(() => { + let dv: DataView; if (isDataView(indexPattern)) { setDataView(indexPattern); } else { const createDataView = async () => { - const dv = await data.dataViews.create({ title: indexPattern.title }); + dv = await data.dataViews.create({ title: indexPattern.title }); setDataView(dv); }; createDataView(); } return () => { - if (dataView?.id) { - data.dataViews.clearInstanceCache(dataView?.id); + if (dv?.id) { + data.dataViews.clearInstanceCache(dv?.id); } }; - }, [data.dataViews, dataView?.id, indexPattern]); + }, [data.dataViews, indexPattern]); const timeHistory = useMemo(() => new TimeHistory(new Storage(localStorage)), []); const arrDataView = useMemo(() => (dataView != null ? [dataView] : []), [dataView]); From d7d959bff0a23b8fcc33a74d4e79bebd5584c47e Mon Sep 17 00:00:00 2001 From: Devin Hurley Date: Thu, 14 Sep 2023 08:30:26 -0400 Subject: [PATCH 7/7] add expect in unit test for when search bar unmounts, clear cached data view --- .../public/common/components/query_bar/index.test.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/common/components/query_bar/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/query_bar/index.test.tsx index 3aa6ea6513484..934e923d5724d 100644 --- a/x-pack/plugins/security_solution/public/common/components/query_bar/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/query_bar/index.test.tsx @@ -41,12 +41,16 @@ const mockUiSettingsForFilterManager = coreMock.createStart().uiSettings; jest.mock('../../lib/kibana'); describe('QueryBar ', () => { + const mockClearInstanceCache = jest.fn().mockImplementation(({ id }: { id: string }) => { + return id; + }); + (useKibana as jest.Mock).mockReturnValue({ services: { data: { dataViews: { create: jest.fn().mockResolvedValue(getMockIndexPattern()), - clearInstanceCache: jest.fn(), + clearInstanceCache: mockClearInstanceCache, }, }, }, @@ -119,6 +123,9 @@ describe('QueryBar ', () => { getMockIndexPattern().id ); }); + // ensure useEffect cleanup is called correctly after component unmounts + wrapper.unmount(); + expect(mockClearInstanceCache).toHaveBeenCalledWith(getMockIndexPattern().id); }); });