From 5c71f21d3dc0481b42a68df499bfed6aab4d6af7 Mon Sep 17 00:00:00 2001 From: Guido Modarelli Date: Mon, 30 Sep 2024 12:15:24 -0300 Subject: [PATCH 01/18] Add APP_STATE_URL_KEY and GLOBAL_STATE_URL_KEY constants --- plugins/main/common/constants.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/main/common/constants.ts b/plugins/main/common/constants.ts index 33e6bac766..bc051170f0 100644 --- a/plugins/main/common/constants.ts +++ b/plugins/main/common/constants.ts @@ -529,3 +529,6 @@ export const SEARCH_BAR_DEBOUNCE_UPDATE_TIME = 400; // ID used to refer the createOsdUrlStateStorage state export const OSD_URL_STATE_STORAGE_ID = 'state:storeInSessionStorage'; + +export const APP_STATE_URL_KEY = '_a'; +export const GLOBAL_STATE_URL_KEY = '_g'; From 2711749292e002a810eb5fae83676699a5363cf0 Mon Sep 17 00:00:00 2001 From: Guido Modarelli Date: Mon, 30 Sep 2024 12:15:57 -0300 Subject: [PATCH 02/18] Add OsdUrlStateStorage for managing app state in URL --- .../public/react-services/state-storage.ts | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 plugins/main/public/react-services/state-storage.ts diff --git a/plugins/main/public/react-services/state-storage.ts b/plugins/main/public/react-services/state-storage.ts new file mode 100644 index 0000000000..d0ca3d63dc --- /dev/null +++ b/plugins/main/public/react-services/state-storage.ts @@ -0,0 +1,42 @@ +import { DataPublicPluginStart } from '../../../../src/plugins/data/public'; +import { + createStateContainer, + IOsdUrlStateStorage, + syncState as _syncState, +} from '../../../../src/plugins/opensearch_dashboards_utils/public'; +import { AppState } from './app-state'; +import { migrateLegacyQuery } from '../utils/migrate_legacy_query'; +import { APP_STATE_URL_KEY } from '../../common/constants'; + +const OsdUrlStateStorage = ( + data: DataPublicPluginStart, + osdUrlStateStorage: IOsdUrlStateStorage, +) => { + const getAppStateFromUrl = () => { + return osdUrlStateStorage.get(APP_STATE_URL_KEY) as AppState; + }; + + const getAppStateContainer = () => { + const defaultQuery = migrateLegacyQuery( + data.query.queryString.getDefaultQuery(), + ); + let initialAppState = { + query: defaultQuery, + ...getAppStateFromUrl(), + }; + return createStateContainer(initialAppState); + }; + + const replaceUrlAppState = async (newPartial: AppState = {}) => { + const state = { ...getAppStateContainer().getState(), ...newPartial }; + await osdUrlStateStorage.set(APP_STATE_URL_KEY, state, { replace: true }); + }; + + return { + getAppStateFromUrl, + getAppStateContainer, + replaceUrlAppState, + }; +}; + +export default OsdUrlStateStorage; From f70fea6f4f097262086e17dcfa90d45e81365c6b Mon Sep 17 00:00:00 2001 From: Guido Modarelli Date: Mon, 30 Sep 2024 12:16:08 -0300 Subject: [PATCH 03/18] Add state storage to exported services --- plugins/main/public/react-services/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/main/public/react-services/index.ts b/plugins/main/public/react-services/index.ts index 8c4e47644b..5a14164e9d 100644 --- a/plugins/main/public/react-services/index.ts +++ b/plugins/main/public/react-services/index.ts @@ -22,3 +22,4 @@ export * from './wz-user-permissions'; export * from './query-config'; export * from './elastic_helpers'; export * from './check-index'; +export * from './state-storage'; From 0415371e5f63e4c8744bf98bb13f6165acd57787 Mon Sep 17 00:00:00 2001 From: Guido Modarelli Date: Mon, 30 Sep 2024 12:16:17 -0300 Subject: [PATCH 04/18] Remove unused "fixedFilters" variable assignment --- .../components/common/data-source/hooks/use-data-source.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/main/public/components/common/data-source/hooks/use-data-source.ts b/plugins/main/public/components/common/data-source/hooks/use-data-source.ts index 6a026e7b3a..0fec9b7db9 100644 --- a/plugins/main/public/components/common/data-source/hooks/use-data-source.ts +++ b/plugins/main/public/components/common/data-source/hooks/use-data-source.ts @@ -98,7 +98,6 @@ export function useDataSource< const { filters: initialFilters = [...defaultFilters], fetchFilters: initialFetchFilters = [], - fixedFilters: initialFixedFilters = [], DataSource: DataSourceConstructor, repository, factory: injectedFactory, From 1636dc2b30374bf6b77e4a3cc107a25ccba28a38 Mon Sep 17 00:00:00 2001 From: Guido Modarelli Date: Mon, 30 Sep 2024 12:16:29 -0300 Subject: [PATCH 05/18] Update button permissions props interface --- .../components/common/permissions/button.tsx | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/plugins/main/public/components/common/permissions/button.tsx b/plugins/main/public/components/common/permissions/button.tsx index 6033960584..5f4d920e69 100644 --- a/plugins/main/public/components/common/permissions/button.tsx +++ b/plugins/main/public/components/common/permissions/button.tsx @@ -18,18 +18,19 @@ import { EuiButtonEmpty, EuiButtonIcon, EuiLink, + EuiButtonProps, } from '@elastic/eui'; import { IWzElementPermissionsProps, WzElementPermissions } from './element'; -interface IWzButtonPermissionsProps - extends Omit< - IWzElementPermissionsProps, - 'children' | 'additionalPropsFunction' - > { - buttonType?: 'default' | 'empty' | 'icon' | 'link' | 'switch'; - rest: any; -} +type IWzButtonPermissionsProps = Omit< + IWzElementPermissionsProps, + 'children' | 'additionalPropsFunction' +> & + React.ButtonHTMLAttributes & + EuiButtonProps & { + buttonType?: 'default' | 'empty' | 'icon' | 'link' | 'switch'; + }; export const WzButtonPermissions = ({ buttonType = 'default', From b8485d46c95dd908e43e9e3a8037d4cddbd6f842 Mon Sep 17 00:00:00 2001 From: Guido Modarelli Date: Mon, 30 Sep 2024 12:16:43 -0300 Subject: [PATCH 06/18] Add allowJs option to compilerOptions in tsconfig.json --- plugins/main/tsconfig.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/main/tsconfig.json b/plugins/main/tsconfig.json index 63e20a2cf0..c7b209c369 100644 --- a/plugins/main/tsconfig.json +++ b/plugins/main/tsconfig.json @@ -1,4 +1,7 @@ { "extends": "../../tsconfig.json", - "include": ["./**/*"] + "include": ["./**/*"], + "compilerOptions": { + "allowJs": true + } } From 2e81ed8343c334916591d4333ff9c8cfecb8701b Mon Sep 17 00:00:00 2001 From: Guido Modarelli Date: Mon, 30 Sep 2024 12:16:51 -0300 Subject: [PATCH 07/18] Update import path for 'opensearch_dashboards/public'. --- plugins/main/public/kibana-services.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/main/public/kibana-services.ts b/plugins/main/public/kibana-services.ts index 1c536dc5e1..f8d96e0301 100644 --- a/plugins/main/public/kibana-services.ts +++ b/plugins/main/public/kibana-services.ts @@ -8,7 +8,7 @@ import { ScopedHistory, ToastsStart, AppMountParameters, -} from 'opensearch_dashboards/public'; +} from '../../../src/core/public'; import { createGetterSetter } from '../../../src/plugins/opensearch_dashboards_utils/common'; import { DataPublicPluginStart } from '../../../src/plugins/data/public'; import { VisualizationsStart } from '../../../src/plugins/visualizations/public'; From da700cb0be597ec8be07b13e2e3e023063531d31 Mon Sep 17 00:00:00 2001 From: Guido Modarelli Date: Mon, 30 Sep 2024 12:16:59 -0300 Subject: [PATCH 08/18] Update getFileContent method signature and parameters --- .../components/management/common/resources-handler.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/main/public/controllers/management/components/management/common/resources-handler.ts b/plugins/main/public/controllers/management/components/management/common/resources-handler.ts index f0b57c0625..5832153eff 100644 --- a/plugins/main/public/controllers/management/components/management/common/resources-handler.ts +++ b/plugins/main/public/controllers/management/components/management/common/resources-handler.ts @@ -63,9 +63,8 @@ export class ResourcesHandler { /** * Get the content of any type of file Rules, Decoders, CDB lists... - * @param {String} fileName */ - async getFileContent(fileName, relativeDirname) { + async getFileContent(fileName: string, relativeDirname?: string) { try { const result: any = await WzRequest.apiReq( 'GET', From 8e0afb65bfbfe60473bc1b81137c7057d4a534ec Mon Sep 17 00:00:00 2001 From: Guido Modarelli Date: Mon, 30 Sep 2024 12:17:04 -0300 Subject: [PATCH 09/18] Add unit tests for clearStateFromSavedQuery function --- .../saved_query/clear_saved_query.test.ts | 48 +++++++++++++++++++ .../hooks/saved_query/clear_saved_query.ts | 35 ++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 plugins/main/public/components/common/hooks/saved_query/clear_saved_query.test.ts create mode 100644 plugins/main/public/components/common/hooks/saved_query/clear_saved_query.ts diff --git a/plugins/main/public/components/common/hooks/saved_query/clear_saved_query.test.ts b/plugins/main/public/components/common/hooks/saved_query/clear_saved_query.test.ts new file mode 100644 index 0000000000..549c978e41 --- /dev/null +++ b/plugins/main/public/components/common/hooks/saved_query/clear_saved_query.test.ts @@ -0,0 +1,48 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { clearStateFromSavedQuery } from './clear_saved_query'; + +import { dataPluginMock } from '../../../../../../../src/plugins/data/public/mocks'; +import { DataPublicPluginStart } from '../../../../../../../src/plugins/data/public'; + +describe('clearStateFromSavedQuery', () => { + let dataMock: jest.Mocked; + + beforeEach(() => { + dataMock = dataPluginMock.createStartContract(); + }); + + it('should clear filters and query', async () => { + dataMock.query.filterManager.removeAll = jest.fn(); + clearStateFromSavedQuery(dataMock.query); + expect(dataMock.query.queryString.clearQuery).toHaveBeenCalled(); + }); +}); diff --git a/plugins/main/public/components/common/hooks/saved_query/clear_saved_query.ts b/plugins/main/public/components/common/hooks/saved_query/clear_saved_query.ts new file mode 100644 index 0000000000..bf48943d5d --- /dev/null +++ b/plugins/main/public/components/common/hooks/saved_query/clear_saved_query.ts @@ -0,0 +1,35 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { QueryStart } from '../../../../../../../src/plugins/data/public'; + +export const clearStateFromSavedQuery = (queryService: QueryStart) => { + queryService.queryString.clearQuery(); +}; From f7476ebe1a1a43f62a05e58657ea5d0e5c089379 Mon Sep 17 00:00:00 2001 From: Guido Modarelli Date: Mon, 30 Sep 2024 12:17:09 -0300 Subject: [PATCH 10/18] Add constant for saved query --- .../main/public/components/common/hooks/saved_query/constants.ts | 1 + 1 file changed, 1 insertion(+) create mode 100644 plugins/main/public/components/common/hooks/saved_query/constants.ts diff --git a/plugins/main/public/components/common/hooks/saved_query/constants.ts b/plugins/main/public/components/common/hooks/saved_query/constants.ts new file mode 100644 index 0000000000..59d4f85684 --- /dev/null +++ b/plugins/main/public/components/common/hooks/saved_query/constants.ts @@ -0,0 +1 @@ +export const SAVED_QUERY = 'savedQuery'; From f61b2991b36ffcebdd23a92ebf875adc0432813d Mon Sep 17 00:00:00 2001 From: Guido Modarelli Date: Mon, 30 Sep 2024 12:17:15 -0300 Subject: [PATCH 11/18] Add test for populating state from saved query --- .../populate_state_from_saved_query.test.ts | 126 ++++++++++++++++++ .../populate_state_from_saved_query.ts | 60 +++++++++ 2 files changed, 186 insertions(+) create mode 100644 plugins/main/public/components/common/hooks/saved_query/populate_state_from_saved_query.test.ts create mode 100644 plugins/main/public/components/common/hooks/saved_query/populate_state_from_saved_query.ts diff --git a/plugins/main/public/components/common/hooks/saved_query/populate_state_from_saved_query.test.ts b/plugins/main/public/components/common/hooks/saved_query/populate_state_from_saved_query.test.ts new file mode 100644 index 0000000000..4f530715c3 --- /dev/null +++ b/plugins/main/public/components/common/hooks/saved_query/populate_state_from_saved_query.test.ts @@ -0,0 +1,126 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { populateStateFromSavedQuery } from './populate_state_from_saved_query'; + +import { dataPluginMock } from '../../../../../../../src/plugins/data/public/mocks'; +import { DataPublicPluginStart } from '../../../../../../../src/plugins/data/public'; +import { SavedQuery } from '../../../../../../../src/plugins/data/public/query/saved_query/types'; +import { FilterStateStore } from '../../../../../../../src/plugins/data/common/opensearch_query/filters/meta_filter'; +import { getFilter } from '../../../../../../../src/plugins/data/public/query/filter_manager/test_helpers/get_stub_filter'; + +describe('populateStateFromSavedQuery', () => { + let dataMock: jest.Mocked; + + const baseSavedQuery: SavedQuery = { + id: 'test', + attributes: { + title: 'test', + description: 'test', + query: { + query: 'test', + language: 'kuery', + }, + }, + }; + + beforeEach(() => { + dataMock = dataPluginMock.createStartContract(); + dataMock.query.filterManager.setFilters = jest.fn(); + dataMock.query.filterManager.getGlobalFilters = jest + .fn() + .mockReturnValue([]); + }); + + it('should set query', async () => { + const savedQuery: SavedQuery = { + ...baseSavedQuery, + }; + populateStateFromSavedQuery(dataMock.query, savedQuery); + expect(dataMock.query.queryString.setQuery).toHaveBeenCalled(); + }); + + it('should set filters', async () => { + const savedQuery: SavedQuery = { + ...baseSavedQuery, + }; + const f1 = getFilter(FilterStateStore.APP_STATE, false, false, 'age', 34); + savedQuery.attributes.filters = [f1]; + populateStateFromSavedQuery(dataMock.query, savedQuery); + expect(dataMock.query.queryString.setQuery).toHaveBeenCalled(); + }); + + it('should preserve global filters', async () => { + const globalFilter = getFilter( + FilterStateStore.GLOBAL_STATE, + false, + false, + 'age', + 34, + ); + dataMock.query.filterManager.getGlobalFilters = jest + .fn() + .mockReturnValue([globalFilter]); + const savedQuery: SavedQuery = { + ...baseSavedQuery, + }; + const f1 = getFilter(FilterStateStore.APP_STATE, false, false, 'age', 34); + savedQuery.attributes.filters = [f1]; + populateStateFromSavedQuery(dataMock.query, savedQuery); + expect(dataMock.query.queryString.setQuery).toHaveBeenCalled(); + }); + + it('should update timefilter', async () => { + const savedQuery: SavedQuery = { + ...baseSavedQuery, + }; + savedQuery.attributes.timefilter = { + from: '2018', + to: '2019', + refreshInterval: { + pause: true, + value: 10, + }, + }; + + dataMock.query.timefilter.timefilter.setTime = jest.fn(); + dataMock.query.timefilter.timefilter.setRefreshInterval = jest.fn(); + + populateStateFromSavedQuery(dataMock.query, savedQuery); + + expect(dataMock.query.timefilter.timefilter.setTime).toHaveBeenCalledWith({ + from: savedQuery.attributes.timefilter.from, + to: savedQuery.attributes.timefilter.to, + }); + expect( + dataMock.query.timefilter.timefilter.setRefreshInterval, + ).toHaveBeenCalledWith(savedQuery.attributes.timefilter.refreshInterval); + }); +}); diff --git a/plugins/main/public/components/common/hooks/saved_query/populate_state_from_saved_query.ts b/plugins/main/public/components/common/hooks/saved_query/populate_state_from_saved_query.ts new file mode 100644 index 0000000000..ae20cf295f --- /dev/null +++ b/plugins/main/public/components/common/hooks/saved_query/populate_state_from_saved_query.ts @@ -0,0 +1,60 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { + Filter, + QueryStart, + SavedQuery, +} from '../../../../../../../src/plugins/data/public'; + +export const populateStateFromSavedQuery = ( + queryService: QueryStart, + savedQuery: SavedQuery, +) => { + const { + timefilter: { timefilter }, + queryString, + } = queryService; + // timefilter + if (savedQuery.attributes.timefilter) { + timefilter.setTime({ + from: savedQuery.attributes.timefilter.from, + to: savedQuery.attributes.timefilter.to, + }); + if (savedQuery.attributes.timefilter.refreshInterval) { + timefilter.setRefreshInterval( + savedQuery.attributes.timefilter.refreshInterval, + ); + } + } + + // query string + queryString.setQuery(savedQuery.attributes.query); +}; From 159cf0119258fa8b3d1f16b07fc51ae58a4152fa Mon Sep 17 00:00:00 2001 From: Guido Modarelli Date: Mon, 30 Sep 2024 12:18:02 -0300 Subject: [PATCH 12/18] Add NavigationService and createHashHistory in useSearchBar.test --- .../common/search-bar/use-search-bar.test.ts | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/plugins/main/public/components/common/search-bar/use-search-bar.test.ts b/plugins/main/public/components/common/search-bar/use-search-bar.test.ts index 6b0fc60ab1..012d76567f 100644 --- a/plugins/main/public/components/common/search-bar/use-search-bar.test.ts +++ b/plugins/main/public/components/common/search-bar/use-search-bar.test.ts @@ -16,6 +16,8 @@ import { getDataPlugin } from '../../../kibana-services'; import * as timeFilterHook from '../hooks/use-time-filter'; import * as queryManagerHook from '../hooks/use-query'; import { AppState } from '../../../react-services/app-state'; +import NavigationService from '../../../react-services/navigation-service'; +import { createHashHistory, History } from 'history'; /** * Mocking Data Plugin @@ -23,6 +25,9 @@ import { AppState } from '../../../react-services/app-state'; jest.mock('../../../kibana-services', () => { return { getDataPlugin: jest.fn(), + getUiSettings: jest.fn().mockImplementation(() => ({ + get: () => true, + })), }; }); /* using osd mock utils */ @@ -55,6 +60,9 @@ const mockedDefaultIndexPatternData: Partial = { }; describe('[hook] useSearchBarConfiguration', () => { + let history: History; + let navigationService: NavigationService; + beforeAll(() => { /***** mock use-time-filter hook *****/ const spyUseTimeFilter = jest.spyOn(timeFilterHook, 'useTimeFilter'); @@ -76,6 +84,11 @@ describe('[hook] useSearchBarConfiguration', () => { spyUseQueryManager.mockImplementation(() => [mockQueryResult, jest.fn()]); }); + beforeEach(() => { + history = createHashHistory(); + navigationService = NavigationService.getInstance(history); + }); + it('should return default app index pattern when not receiving a default index pattern', async () => { jest .spyOn(AppState, 'getCurrentPattern') @@ -143,7 +156,7 @@ describe('[hook] useSearchBarConfiguration', () => { const { result, waitForNextUpdate } = renderHook(() => useSearchBar({ indexPattern: mockedExampleIndexPatternData as IndexPattern, - setFilters: jest.fn() + setFilters: jest.fn(), }), ); expect(result.current.searchBarProps.indexPatterns).toMatchObject([ @@ -174,11 +187,11 @@ describe('[hook] useSearchBarConfiguration', () => { .mockReturnValue([]); const { result, waitForNextUpdate, rerender } = renderHook( // @ts-ignore - (props) => useSearchBar(props), + props => useSearchBar(props), { initialProps: { indexPattern: mockedExampleIndexPatternData as IndexPattern, - setFilters: jest.fn() + setFilters: jest.fn(), }, }, ); @@ -195,10 +208,10 @@ describe('[hook] useSearchBarConfiguration', () => { .mockResolvedValue(newExampleIndexPatternData); rerender({ indexPattern: newExampleIndexPatternData as IndexPattern, - setFilters: jest.fn() + setFilters: jest.fn(), }); expect(result.current.searchBarProps.indexPatterns).toMatchObject([ newExampleIndexPatternData, ]); - }) + }); }); From a654b40ada5d2aa568924a6e0292556ab0a17460 Mon Sep 17 00:00:00 2001 From: Guido Modarelli Date: Mon, 30 Sep 2024 12:18:24 -0300 Subject: [PATCH 13/18] Update OSD_URL_STATE_STORAGE_ID to APP_STATE_URL_KEY --- .../main/public/components/overview/overview.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/plugins/main/public/components/overview/overview.tsx b/plugins/main/public/components/overview/overview.tsx index 12476ad0a7..03f236f705 100644 --- a/plugins/main/public/components/overview/overview.tsx +++ b/plugins/main/public/components/overview/overview.tsx @@ -4,7 +4,10 @@ import { Stats } from '../../controllers/overview/components/stats'; import { AppState, WzRequest } from '../../react-services'; import { OverviewWelcome } from '../common/welcome/overview-welcome'; import { MainModule } from '../common/modules/main'; -import { OSD_URL_STATE_STORAGE_ID } from '../../../common/constants'; +import { + APP_STATE_URL_KEY, + OSD_URL_STATE_STORAGE_ID, +} from '../../../common/constants'; import { WzCurrentOverviewSectionWrapper } from '../common/modules/overview-current-section-wrapper'; import { connectToQueryState, @@ -56,7 +59,9 @@ export const Overview: React.FC = withRouteResolvers({ history: history, }); - const appStateFromUrl = osdUrlStateStorage.get('_a') as AppState; + const appStateFromUrl = osdUrlStateStorage.get( + APP_STATE_URL_KEY, + ) as AppState; let initialAppState = { query: migrateLegacyQuery(data.query.queryString.getDefaultQuery()), ...appStateFromUrl, @@ -73,11 +78,11 @@ export const Overview: React.FC = withRouteResolvers({ const replaceUrlAppState = async (newPartial: AppState = {}) => { const state = { ...appStateContainer.getState(), ...newPartial }; - await osdUrlStateStorage.set('_a', state, { replace: true }); + await osdUrlStateStorage.set(APP_STATE_URL_KEY, state, { replace: true }); }; const { start, stop } = syncState({ - storageKey: '_a', + storageKey: APP_STATE_URL_KEY, stateContainer: appStateContainerModified, stateStorage: osdUrlStateStorage, }); From ee63aaf66d92baf3820fa9a5f114eac851b77ca1 Mon Sep 17 00:00:00 2001 From: Guido Modarelli Date: Mon, 30 Sep 2024 12:18:32 -0300 Subject: [PATCH 14/18] Add useSavedQuery hook for handling saved queries --- .../hooks/saved_query/use_saved_query.ts | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 plugins/main/public/components/common/hooks/saved_query/use_saved_query.ts diff --git a/plugins/main/public/components/common/hooks/saved_query/use_saved_query.ts b/plugins/main/public/components/common/hooks/saved_query/use_saved_query.ts new file mode 100644 index 0000000000..754808f016 --- /dev/null +++ b/plugins/main/public/components/common/hooks/saved_query/use_saved_query.ts @@ -0,0 +1,114 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { useState, useEffect } from 'react'; +import { + DataPublicPluginStart, + SavedQuery, +} from '../../../../../../../src/plugins/data/public'; +import { clearStateFromSavedQuery } from './clear_saved_query'; +import { populateStateFromSavedQuery } from './populate_state_from_saved_query'; +import NavigationService from '../../../../react-services/navigation-service'; +import { OSD_URL_STATE_STORAGE_ID } from '../../../../../common/constants'; +import { getDataPlugin, getUiSettings } from '../../../../kibana-services'; +import { createOsdUrlStateStorage } from '../../../../../../../src/plugins/opensearch_dashboards_utils/public'; +import OsdUrlStateStorage from '../../../../react-services/state-storage'; + +interface UseSavedQueriesProps { + queryService: DataPublicPluginStart['query']; +} + +interface UseSavedQueriesReturn { + savedQuery?: SavedQuery; + setSavedQuery: (savedQuery: SavedQuery) => void; + clearSavedQuery: () => void; +} + +export const useSavedQuery = ( + props: UseSavedQueriesProps, +): UseSavedQueriesReturn => { + // Handle saved queries + const [savedQuery, setSavedQuery] = useState(); + const data = getDataPlugin(); + const config = getUiSettings(); + const history = NavigationService.getInstance().getHistory(); + const osdUrlStateStorage = createOsdUrlStateStorage({ + useHash: config.get(OSD_URL_STATE_STORAGE_ID), + history: history, + }); + + const saveSavedQuery = async (savedQueryId?: string) => { + await OsdUrlStateStorage(data, osdUrlStateStorage).replaceUrlAppState({ + savedQuery: savedQueryId, + filters: props.queryService.filterManager.getAppFilters(), + }); + }; + + const updateSavedQuery = async (savedQuery: SavedQuery) => { + setSavedQuery(savedQuery); + saveSavedQuery(savedQuery.id); + populateStateFromSavedQuery(props.queryService, savedQuery); + }; + + const clearSavedQuery = () => { + setSavedQuery(undefined); + // remove saved query from url + saveSavedQuery(undefined); + clearStateFromSavedQuery(props.queryService); + }; + + // Effect is used to convert a saved query id into an object + useEffect(() => { + const fetchSavedQuery = async () => { + try { + const savedQueryId = OsdUrlStateStorage( + data, + osdUrlStateStorage, + ).getAppStateFromUrl().savedQuery as string; + if (!savedQueryId) return; + // fetch saved query + const savedQuery = await props.queryService.savedQueries.getSavedQuery( + savedQueryId, + ); + updateSavedQuery(savedQuery); + } catch (error) { + clearSavedQuery(); + } + }; + + fetchSavedQuery(); + }, [props.queryService, props.queryService.savedQueries]); + + return { + savedQuery, + setSavedQuery: updateSavedQuery, + clearSavedQuery, + }; +}; From 0c3e8cd6cdda17f5d74b111e923cff94b23c2300 Mon Sep 17 00:00:00 2001 From: Guido Modarelli Date: Mon, 30 Sep 2024 12:18:36 -0300 Subject: [PATCH 15/18] Add useSavedQuery hook in search-bar configuration --- .../common/search-bar/use-search-bar.ts | 41 +++++++++++++++++-- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/plugins/main/public/components/common/search-bar/use-search-bar.ts b/plugins/main/public/components/common/search-bar/use-search-bar.ts index b11c0e5113..e228eb9af2 100644 --- a/plugins/main/public/components/common/search-bar/use-search-bar.ts +++ b/plugins/main/public/components/common/search-bar/use-search-bar.ts @@ -6,9 +6,11 @@ import { Filter, IndexPattern, IndexPatternsContract, + SavedQuery, } from '../../../../../../src/plugins/data/public'; import { getDataPlugin } from '../../../kibana-services'; import { useQueryManager, useTimeFilter } from '../hooks'; +import { useSavedQuery } from '../hooks/saved_query/use_saved_query'; import { transformDateRange } from './search-bar-service'; // Input - types @@ -16,7 +18,7 @@ type tUseSearchBarCustomInputs = { indexPattern: IndexPattern; setFilters: (filters: Filter[]) => void; setTimeFilter?: (timeRange: TimeRange) => void; - setQuery?: (query: Query) => void; + setQuery?: (query?: Query) => void; onFiltersUpdated?: (filters: Filter[]) => void; onQuerySubmitted?: ( payload: { dateRange: TimeRange; query?: Query }, @@ -44,7 +46,7 @@ type tUserSearchBarResponse = { const useSearchBarConfiguration = ( props: tUseSearchBarProps, ): tUserSearchBarResponse => { - const { indexPattern, filters: defaultFilters, setFilters } = props; + const { indexPattern, filters = [], setFilters } = props; // dependencies const { @@ -52,7 +54,6 @@ const useSearchBarConfiguration = ( timeHistory, setTimeFilter: setGlobalTimeFilter, } = useTimeFilter(); - const filters = defaultFilters ?? []; const [timeFilter, setTimeFilter] = useState(globalTimeFilter); const [query, setQuery] = props?.setQuery ? useState(props?.query || { query: '', language: 'kuery' }) @@ -63,6 +64,14 @@ const useSearchBarConfiguration = ( const [absoluteDateRange, setAbsoluteDateRange] = useState( transformDateRange(globalTimeFilter), ); + const [refreshInterval, setRefreshInterval] = useState( + props?.refreshInterval, + ); + const [isPaused, setIsPaused] = useState(props?.isRefreshPaused); + const { query: queryService } = getDataPlugin(); + const { savedQuery, setSavedQuery, clearSavedQuery } = useSavedQuery({ + queryService, + }); // states const [isLoading, setIsLoading] = useState(true); const [indexPatternSelected, setIndexPatternSelected] = @@ -106,6 +115,7 @@ const useSearchBarConfiguration = ( /** * Search bar properties necessary to render and initialize the osd search bar component */ + const searchBarProps: Partial< SearchBarProps & { useDefaultBehaviors: boolean; @@ -126,6 +136,15 @@ const useSearchBarConfiguration = ( : console.warn('setFilters function is not defined'); props?.onFiltersUpdated && props?.onFiltersUpdated(userFilters); }, + refreshInterval, + isRefreshPaused: isPaused, + onRefreshChange: ({ refreshInterval, isPaused }) => { + setRefreshInterval(refreshInterval); + setIsPaused(isPaused); + }, + onRefresh: ({ dateRange }) => { + setAbsoluteDateRange(transformDateRange(dateRange)); + }, onQuerySubmit: ( payload: { dateRange: TimeRange; query?: Query }, _isUpdate?: boolean, @@ -143,7 +162,21 @@ const useSearchBarConfiguration = ( setQuery(query); }, // its necessary to use saved queries. if not, the load saved query not work - useDefaultBehaviors: true, + onClearSavedQuery: () => { + setFilters([]); + clearSavedQuery(); + }, + onSaved: (savedQuery: SavedQuery) => { + const savedQueryFilters = savedQuery.attributes.filters || []; + setFilters([...savedQueryFilters]); + setSavedQuery(savedQuery); + }, + onSavedQueryUpdated: (savedQuery: SavedQuery) => { + const savedQueryFilters = savedQuery.attributes.filters || []; + setFilters([...savedQueryFilters]); + setSavedQuery(savedQuery); + }, + savedQuery, }; return { From 2de0c1837f78a2c4b42a752beb6b79c322428e5b Mon Sep 17 00:00:00 2001 From: Guido Modarelli Date: Mon, 30 Sep 2024 13:17:33 -0300 Subject: [PATCH 16/18] Update useSearchBar to set refresh interval directly --- .../components/common/search-bar/use-search-bar.ts | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/plugins/main/public/components/common/search-bar/use-search-bar.ts b/plugins/main/public/components/common/search-bar/use-search-bar.ts index e228eb9af2..86aede82e6 100644 --- a/plugins/main/public/components/common/search-bar/use-search-bar.ts +++ b/plugins/main/public/components/common/search-bar/use-search-bar.ts @@ -64,10 +64,6 @@ const useSearchBarConfiguration = ( const [absoluteDateRange, setAbsoluteDateRange] = useState( transformDateRange(globalTimeFilter), ); - const [refreshInterval, setRefreshInterval] = useState( - props?.refreshInterval, - ); - const [isPaused, setIsPaused] = useState(props?.isRefreshPaused); const { query: queryService } = getDataPlugin(); const { savedQuery, setSavedQuery, clearSavedQuery } = useSavedQuery({ queryService, @@ -136,11 +132,10 @@ const useSearchBarConfiguration = ( : console.warn('setFilters function is not defined'); props?.onFiltersUpdated && props?.onFiltersUpdated(userFilters); }, - refreshInterval, - isRefreshPaused: isPaused, - onRefreshChange: ({ refreshInterval, isPaused }) => { - setRefreshInterval(refreshInterval); - setIsPaused(isPaused); + refreshInterval: queryService.timefilter.timefilter.getRefreshInterval().value, + isRefreshPaused: queryService.timefilter.timefilter.getRefreshInterval().pause, + onRefreshChange: (opts) => { + queryService.timefilter.timefilter.setRefreshInterval(opts); }, onRefresh: ({ dateRange }) => { setAbsoluteDateRange(transformDateRange(dateRange)); From b0a2f15578dd5b1003ef8e4cd044179ee28ae4b5 Mon Sep 17 00:00:00 2001 From: Guido Modarelli Date: Mon, 30 Sep 2024 13:18:11 -0300 Subject: [PATCH 17/18] Fix Prettier issues --- .../public/components/common/search-bar/use-search-bar.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/plugins/main/public/components/common/search-bar/use-search-bar.ts b/plugins/main/public/components/common/search-bar/use-search-bar.ts index 86aede82e6..cef8f941ce 100644 --- a/plugins/main/public/components/common/search-bar/use-search-bar.ts +++ b/plugins/main/public/components/common/search-bar/use-search-bar.ts @@ -132,9 +132,11 @@ const useSearchBarConfiguration = ( : console.warn('setFilters function is not defined'); props?.onFiltersUpdated && props?.onFiltersUpdated(userFilters); }, - refreshInterval: queryService.timefilter.timefilter.getRefreshInterval().value, - isRefreshPaused: queryService.timefilter.timefilter.getRefreshInterval().pause, - onRefreshChange: (opts) => { + refreshInterval: + queryService.timefilter.timefilter.getRefreshInterval().value, + isRefreshPaused: + queryService.timefilter.timefilter.getRefreshInterval().pause, + onRefreshChange: opts => { queryService.timefilter.timefilter.setRefreshInterval(opts); }, onRefresh: ({ dateRange }) => { From 0afc562c2999bdfdc1c22b4d92b5e75ade6722be Mon Sep 17 00:00:00 2001 From: Guido Modarelli Date: Mon, 30 Sep 2024 13:43:22 -0300 Subject: [PATCH 18/18] Update timefilter configuration in use-search-bar.test.ts --- .../components/common/search-bar/use-search-bar.test.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/plugins/main/public/components/common/search-bar/use-search-bar.test.ts b/plugins/main/public/components/common/search-bar/use-search-bar.test.ts index 012d76567f..d4465c11b2 100644 --- a/plugins/main/public/components/common/search-bar/use-search-bar.test.ts +++ b/plugins/main/public/components/common/search-bar/use-search-bar.test.ts @@ -47,6 +47,15 @@ mockedGetDataPlugin.mockImplementation( unsubscribe: jest.fn(), })), }, + timefilter: { + ...mockDataPlugin.query.queryString.timefilter, + timefilter: { + getRefreshInterval: jest.fn().mockImplementation(() => ({ + value: 0, + pause: false, + })), + }, + }, }, }, } as Start),