-
Notifications
You must be signed in to change notification settings - Fork 919
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Cleanup OsdUrlStateStorage subscription in TopNav #9167
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
fix: | ||
- Cleanup OsdUrlStateStorage subscription in TopNav ([#9167](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/9167)) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -31,7 +31,6 @@ | |
import { Subscription } from 'rxjs'; | ||
import { filter, map } from 'rxjs/operators'; | ||
import _ from 'lodash'; | ||
import { CoreStart } from 'opensearch-dashboards/public'; | ||
import { | ||
BaseStateContainer, | ||
IOsdUrlStateStorage, | ||
|
@@ -41,27 +40,28 @@ import { QueryState, QueryStateChange } from './types'; | |
import { FilterStateStore, COMPARE_ALL_OPTIONS, compareFilters } from '../../../common'; | ||
import { validateTimeRange } from '../timefilter'; | ||
|
||
export interface ISyncConfig { | ||
filters: FilterStateStore; | ||
query: boolean; | ||
dataset?: boolean; | ||
} | ||
|
||
/** | ||
* Helper function to sync up filter and query services in data plugin | ||
* with a URL state storage so plugins can persist the app filter and query | ||
* values across refresh | ||
* @param QueryService: either setup or start | ||
* @param OsdUrlStateStorage to use for syncing and store data | ||
* @param OsdUrlStateStorage to use for syncing and store data | ||
* @param syncConfig app filter and query | ||
*/ | ||
export const connectStorageToQueryState = async ( | ||
export const connectStorageToQueryState = ( | ||
{ | ||
filterManager, | ||
queryString, | ||
state$, | ||
}: Pick<QueryStart | QuerySetup, 'timefilter' | 'filterManager' | 'queryString' | 'state$'>, | ||
OsdUrlStateStorage: IOsdUrlStateStorage, | ||
syncConfig: { | ||
filters: FilterStateStore; | ||
query: boolean; | ||
dataSet?: boolean; | ||
}, | ||
uiSettings?: CoreStart['uiSettings'] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what about uiSettings? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's a unused variable |
||
syncConfig: ISyncConfig | ||
) => { | ||
try { | ||
const syncKeys: Array<keyof QueryStateChange> = []; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
import { Subscription } from 'rxjs'; | ||
import { FilterManager } from '../filter_manager'; | ||
import { getFilter } from '../filter_manager/test_helpers/get_stub_filter'; | ||
import { | ||
DataStorage, | ||
Filter, | ||
FilterStateStore, | ||
IndexPatternsService, | ||
Query, | ||
} from '../../../common'; | ||
import { coreMock } from '../../../../../core/public/mocks'; | ||
import { | ||
IOsdUrlStateStorage, | ||
createOsdUrlStateStorage, | ||
} from '../../../../opensearch_dashboards_utils/public'; | ||
import { QueryService, QueryStart } from '../query_service'; | ||
import { connectStorageToQueryState } from './connect_to_query_state'; | ||
import { createBrowserHistory, History } from 'history'; | ||
import { QueryStringContract } from '../query_string'; | ||
import { ISearchInterceptor } from '../../search'; | ||
import { renderHook } from '@testing-library/react-hooks'; | ||
import { useConnectStorageToQueryState } from './use_connect_to_query_state'; | ||
|
||
jest.mock('./connect_to_query_state'); | ||
|
||
const setupMock = coreMock.createSetup(); | ||
const startMock = coreMock.createStart(); | ||
|
||
describe('use_connect_storage_to_query_state', () => { | ||
let queryServiceStart: QueryStart; | ||
let queryString: QueryStringContract; | ||
let queryChangeSub: Subscription; | ||
let queryChangeTriggered = jest.fn(); | ||
let filterManager: FilterManager; | ||
let filterManagerChangeSub: Subscription; | ||
let filterManagerChangeTriggered = jest.fn(); | ||
let osdUrlStateStorage: IOsdUrlStateStorage; | ||
let indexPatternsMock: IndexPatternsService; | ||
let history: History; | ||
let gF1: Filter; | ||
let gF2: Filter; | ||
let aF1: Filter; | ||
let aF2: Filter; | ||
let q1: Query; | ||
let mockSearchInterceptor: jest.Mocked<ISearchInterceptor>; | ||
|
||
beforeEach(() => { | ||
const queryService = new QueryService(); | ||
mockSearchInterceptor = {} as jest.Mocked<ISearchInterceptor>; | ||
queryService.setup({ | ||
uiSettings: setupMock.uiSettings, | ||
storage: new DataStorage(window.localStorage, 'opensearch_dashboards.'), | ||
sessionStorage: new DataStorage(window.sessionStorage, 'opensearch_dashboards.'), | ||
defaultSearchInterceptor: mockSearchInterceptor, | ||
application: setupMock.application, | ||
notifications: setupMock.notifications, | ||
}); | ||
queryServiceStart = queryService.start({ | ||
uiSettings: startMock.uiSettings, | ||
storage: new DataStorage(window.localStorage, 'opensearch_dashboards.'), | ||
savedObjectsClient: startMock.savedObjects.client, | ||
indexPatterns: indexPatternsMock, | ||
application: startMock.application, | ||
notifications: startMock.notifications, | ||
}); | ||
indexPatternsMock = ({ | ||
get: jest.fn(), | ||
} as unknown) as IndexPatternsService; | ||
|
||
queryString = queryServiceStart.queryString; | ||
queryChangeTriggered = jest.fn(); | ||
queryChangeSub = queryString.getUpdates$().subscribe(queryChangeTriggered); | ||
|
||
filterManager = queryServiceStart.filterManager; | ||
filterManagerChangeTriggered = jest.fn(); | ||
filterManagerChangeSub = filterManager.getUpdates$().subscribe(filterManagerChangeTriggered); | ||
|
||
window.location.href = '/'; | ||
history = createBrowserHistory(); | ||
osdUrlStateStorage = createOsdUrlStateStorage({ useHash: false, history }); | ||
|
||
gF1 = getFilter(FilterStateStore.GLOBAL_STATE, true, true, 'key1', 'value1'); | ||
gF2 = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'key2', 'value2'); | ||
aF1 = getFilter(FilterStateStore.APP_STATE, true, true, 'key3', 'value3'); | ||
aF2 = getFilter(FilterStateStore.APP_STATE, false, false, 'key4', 'value4'); | ||
|
||
q1 = { | ||
query: 'count is less than 100', | ||
language: 'kuery', | ||
}; | ||
}); | ||
|
||
afterEach(() => { | ||
filterManagerChangeSub.unsubscribe(); | ||
queryChangeSub.unsubscribe(); | ||
}); | ||
|
||
it('Should invoke connectStorageToQueryState', () => { | ||
const { result } = renderHook(() => | ||
useConnectStorageToQueryState(queryServiceStart, osdUrlStateStorage, { | ||
filters: FilterStateStore.APP_STATE, | ||
query: true, | ||
}) | ||
); | ||
expect(connectStorageToQueryState).toHaveBeenCalledTimes(1); | ||
expect(result.current).toBeUndefined(); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
import { useEffect } from 'react'; | ||
import { QuerySetup, QueryStart } from '../query_service'; | ||
import { IOsdUrlStateStorage } from '../../../../opensearch_dashboards_utils/public'; | ||
import { connectStorageToQueryState, ISyncConfig } from './connect_to_query_state'; | ||
|
||
/** | ||
* Hook version of connectStorageToQueryState, automatically unSub | ||
* from OsdUrlStateStorage | ||
* @param QueryService: either setup or start | ||
* @param OsdUrlStateStorage to use for syncing and store data | ||
* @param syncConfig app filter and query | ||
*/ | ||
export const useConnectStorageToQueryState = ( | ||
QueryService: Pick< | ||
QueryStart | QuerySetup, | ||
'timefilter' | 'filterManager' | 'queryString' | 'state$' | ||
>, | ||
OsdUrlStateStorage: IOsdUrlStateStorage, | ||
syncConfig: ISyncConfig | ||
) => { | ||
useEffect(() => { | ||
const unSub = connectStorageToQueryState(QueryService, OsdUrlStateStorage, syncConfig); | ||
return () => { | ||
if (unSub) { | ||
unSub(); | ||
} | ||
}; | ||
}, [OsdUrlStateStorage, QueryService, syncConfig]); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,7 +20,7 @@ import { setEditorState } from '../utils/state_management/metadata_slice'; | |
import { useCanSave } from '../utils/use/use_can_save'; | ||
import { saveStateToSavedObject } from '../../saved_visualizations/transforms'; | ||
import { TopNavMenuData, TopNavMenuItemRenderType } from '../../../../navigation/public'; | ||
import { opensearchFilters, connectStorageToQueryState } from '../../../../data/public'; | ||
import { opensearchFilters, useConnectStorageToQueryState } from '../../../../data/public'; | ||
import { RootState } from '../../../../data_explorer/public'; | ||
|
||
function useDeepEffect(callback, dependencies) { | ||
|
@@ -56,16 +56,17 @@ export const TopNav = () => { | |
|
||
const saveDisabledReason = useCanSave(); | ||
const savedVisBuilderVis = useSavedVisBuilderVis(visualizationIdFromUrl); | ||
connectStorageToQueryState(services.data.query, services.osdUrlStateStorage, { | ||
filters: opensearchFilters.FilterStateStore.APP_STATE, | ||
query: true, | ||
}); | ||
const { selected: indexPattern } = useIndexPatterns(); | ||
const [config, setConfig] = useState<TopNavMenuData[] | undefined>(); | ||
const originatingApp = useTypedSelector((state) => { | ||
return state.metadata.originatingApp; | ||
}); | ||
|
||
useConnectStorageToQueryState(services.data.query, services.osdUrlStateStorage, { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. without diving too deep, do we know if the query state is changed and navigate to visbuilder if this gets set to after the the index patterns does it cause a difference in the selected: indexPattern? |
||
filters: opensearchFilters.FilterStateStore.APP_STATE, | ||
query: true, | ||
}); | ||
|
||
useEffect(() => { | ||
const getConfig = () => { | ||
if (!savedVisBuilderVis || !indexPattern) return; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will this function ever return anything other than the unsub callback function, besides it reaching the
catch
block? Several questions regarding that:useEffect
by the caller?subs
were made and then getting to thecatch
block for some error, don't we want to unsub from thecatch
as well?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the intended behaviour hear is let the scope where invokes
connectStorageToQueryState
to maintain the life cycle of the subscription.It make senses to me to unsub in the
catch
block, but currently the subscription is building at the end of thetry
block, it's unlikely to have any lingering subscription if it reachcatch
block.