From 25ceed986b082deb9749fef9521b96086b3a233f Mon Sep 17 00:00:00 2001 From: Liza Katz Date: Wed, 29 Jul 2020 13:47:55 +0300 Subject: [PATCH] [7.x] [Data] Query Input String manager (#72093) (#73634) * [Data] Query Input String manager (#72093) * improve test stability * query string input manager (needed for search demo) * docs * dashboard * Fix jest * mock fix * Allow restoring a saved query * sync url * Luke's fix to test * cleanup * lens jest tests * docs * use queryStringManager.getDefaultQuery Don't sync query to global state * Update app.test.tsx lens mock * jest fix * jest * use new api in the example * Rename state param to query to match url state * Apply changes to discover * Update src/plugins/data/public/query/query_string/index.ts Co-authored-by: Anton Dosov * Improve query string state manager * Cleanup dashboard code * Handle refresh button * Set initial dashboard state * visualize state * remove unused * docs * fix example * fix jest * fix filter app state in discover * fix maps test * jest Co-authored-by: Anton Dosov Co-authored-by: Anton Dosov Co-authored-by: Elastic Machine # Conflicts: # src/plugins/data/public/public.api.md * docs --- ...plugins-data-public.connecttoquerystate.md | 3 +- ...a-plugin-plugins-data-public.querystate.md | 1 + ...in-plugins-data-public.querystate.query.md | 11 ++ ...ugins-data-public.syncquerystatewithurl.md | 2 +- .../with_data_services/components/app.tsx | 15 +-- .../public/application/dashboard_app.tsx | 5 +- .../application/dashboard_app_controller.tsx | 103 +++++++----------- src/plugins/data/public/public.api.md | 9 +- src/plugins/data/public/query/mocks.ts | 3 + .../data/public/query/query_service.ts | 7 ++ .../data/public/query/query_string/index.ts | 20 ++++ .../query_string/query_string_manager.mock.ts | 37 +++++++ .../query_string/query_string_manager.ts | 90 +++++++++++++++ .../state_sync/connect_to_query_state.test.ts | 2 + .../state_sync/connect_to_query_state.ts | 23 +++- .../create_global_query_observable.ts | 8 ++ .../state_sync/sync_state_with_url.test.ts | 2 + .../query/state_sync/sync_state_with_url.ts | 2 +- .../data/public/query/state_sync/types.ts | 3 +- .../ui/search_bar/create_search_bar.tsx | 45 +++----- .../search_bar/lib/clear_saved_query.test.ts | 16 +-- .../ui/search_bar/lib/clear_saved_query.ts | 11 +- .../populate_state_from_saved_query.test.ts | 17 ++- .../lib/populate_state_from_saved_query.ts | 9 +- .../lib/use_query_string_manager.ts | 51 +++++++++ .../ui/search_bar/lib/use_saved_query.ts | 13 +-- .../public/application/angular/discover.html | 3 +- .../public/application/angular/discover.js | 42 ++----- .../components/scripting_help/test_script.tsx | 12 +- .../public/components/controls/filters.tsx | 4 +- .../components/visualize_top_nav.tsx | 15 +-- .../utils/use/use_editor_updates.test.ts | 6 + .../utils/use/use_editor_updates.ts | 19 +--- .../utils/use/use_visualize_app_state.test.ts | 1 + .../utils/use/use_visualize_app_state.tsx | 29 ++++- .../public/application/utils/utils.ts | 11 +- test/functional/apps/visualize/_area_chart.js | 4 + .../lens/public/app_plugin/app.test.tsx | 9 ++ x-pack/plugins/lens/public/app_plugin/app.tsx | 14 +-- .../filter_editor/filter_editor.js | 10 +- .../join_editor/resources/where_expression.js | 9 +- .../routing/bootstrap/get_initial_query.js | 12 +- .../top_nav_menu/top_nav_menu.js | 8 +- .../routing/routes/maps_app/maps_app_view.js | 5 +- .../public/routing/state_syncing/app_sync.js | 1 + 45 files changed, 432 insertions(+), 290 deletions(-) create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystate.query.md create mode 100644 src/plugins/data/public/query/query_string/index.ts create mode 100644 src/plugins/data/public/query/query_string/query_string_manager.mock.ts create mode 100644 src/plugins/data/public/query/query_string/query_string_manager.ts create mode 100644 src/plugins/data/public/ui/search_bar/lib/use_query_string_manager.ts diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.connecttoquerystate.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.connecttoquerystate.md index a6731e5ef8de..7c937b39cda8 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.connecttoquerystate.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.connecttoquerystate.md @@ -9,9 +9,10 @@ Helper to setup two-way syncing of global data and a state container Signature: ```typescript -connectToQueryState: ({ timefilter: { timefilter }, filterManager, state$, }: Pick, stateContainer: BaseStateContainer, syncConfig: { +connectToQueryState: ({ timefilter: { timefilter }, filterManager, queryString, state$, }: Pick, stateContainer: BaseStateContainer, syncConfig: { time?: boolean; refreshInterval?: boolean; filters?: FilterStateStore | boolean; + query?: boolean; }) => () => void ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystate.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystate.md index cc489a0cb036..021d808afecb 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystate.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystate.md @@ -17,6 +17,7 @@ export interface QueryState | Property | Type | Description | | --- | --- | --- | | [filters](./kibana-plugin-plugins-data-public.querystate.filters.md) | Filter[] | | +| [query](./kibana-plugin-plugins-data-public.querystate.query.md) | Query | | | [refreshInterval](./kibana-plugin-plugins-data-public.querystate.refreshinterval.md) | RefreshInterval | | | [time](./kibana-plugin-plugins-data-public.querystate.time.md) | TimeRange | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystate.query.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystate.query.md new file mode 100644 index 000000000000..b0ac376a358d --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystate.query.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [QueryState](./kibana-plugin-plugins-data-public.querystate.md) > [query](./kibana-plugin-plugins-data-public.querystate.query.md) + +## QueryState.query property + +Signature: + +```typescript +query?: Query; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.syncquerystatewithurl.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.syncquerystatewithurl.md index f6f8bed8cb91..1aafa022f969 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.syncquerystatewithurl.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.syncquerystatewithurl.md @@ -9,7 +9,7 @@ Helper to setup syncing of global data with the URL Signature: ```typescript -syncQueryStateWithUrl: (query: Pick, kbnUrlStateStorage: IKbnUrlStateStorage) => { +syncQueryStateWithUrl: (query: Pick, kbnUrlStateStorage: IKbnUrlStateStorage) => { stop: () => void; hasInheritedQueryFromUrl: boolean; } diff --git a/examples/state_containers_examples/public/with_data_services/components/app.tsx b/examples/state_containers_examples/public/with_data_services/components/app.tsx index 04bdb53efa50..d007cfd97edc 100644 --- a/examples/state_containers_examples/public/with_data_services/components/app.tsx +++ b/examples/state_containers_examples/public/with_data_services/components/app.tsx @@ -17,7 +17,7 @@ * under the License. */ -import React, { useEffect, useRef, useState, useCallback } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import { History } from 'history'; import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; import { Router } from 'react-router-dom'; @@ -85,16 +85,9 @@ const App = ({ navigation, data, history, kbnUrlStateStorage }: StateDemoAppDeps useGlobalStateSyncing(data.query, kbnUrlStateStorage); useAppStateSyncing(appStateContainer, data.query, kbnUrlStateStorage); - const onQuerySubmit = useCallback( - ({ query }) => { - appStateContainer.set({ ...appState, query }); - }, - [appStateContainer, appState] - ); - const indexPattern = useIndexPattern(data); if (!indexPattern) - return
No index pattern found. Please create an intex patter before loading...
; + return
No index pattern found. Please create an index patter before loading...
; // Render the application DOM. // Note that `navigation.ui.TopNavMenu` is a stateful component exported on the `navigation` plugin's start contract. @@ -107,8 +100,6 @@ const App = ({ navigation, data, history, kbnUrlStateStorage }: StateDemoAppDeps showSearchBar={true} indexPatterns={[indexPattern]} useDefaultBehaviors={true} - onQuerySubmit={onQuerySubmit} - query={appState.query} showSaveQuery={true} /> @@ -200,7 +191,7 @@ function useAppStateSyncing( const stopSyncingQueryAppStateWithStateContainer = connectToQueryState( query, appStateContainer, - { filters: esFilters.FilterStateStore.APP_STATE } + { filters: esFilters.FilterStateStore.APP_STATE, query: true } ); // sets up syncing app state container with url diff --git a/src/plugins/dashboard/public/application/dashboard_app.tsx b/src/plugins/dashboard/public/application/dashboard_app.tsx index f101935b9288..6690ae318fc8 100644 --- a/src/plugins/dashboard/public/application/dashboard_app.tsx +++ b/src/plugins/dashboard/public/application/dashboard_app.tsx @@ -52,7 +52,10 @@ export interface DashboardAppScope extends ng.IScope { expandedPanel?: string; getShouldShowEditHelp: () => boolean; getShouldShowViewHelp: () => boolean; - updateQueryAndFetch: ({ query, dateRange }: { query: Query; dateRange?: TimeRange }) => void; + handleRefresh: ( + { query, dateRange }: { query?: Query; dateRange: TimeRange }, + isUpdate?: boolean + ) => void; topNavMenu: any; showAddPanel: any; showSaveQuery: boolean; diff --git a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx index afccf8deaa21..3a4e49968626 100644 --- a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx +++ b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx @@ -25,12 +25,11 @@ import React, { useState, ReactElement } from 'react'; import ReactDOM from 'react-dom'; import angular from 'angular'; -import { Observable, pipe, Subscription } from 'rxjs'; -import { filter, map, mapTo, startWith, switchMap } from 'rxjs/operators'; +import { Observable, pipe, Subscription, merge } from 'rxjs'; +import { filter, map, debounceTime, mapTo, startWith, switchMap } from 'rxjs/operators'; import { History } from 'history'; import { SavedObjectSaveOpts } from 'src/plugins/saved_objects/public'; import { NavigationPublicPluginStart as NavigationStart } from 'src/plugins/navigation/public'; -import { TimeRange } from 'src/plugins/data/public'; import { DashboardEmptyScreen, DashboardEmptyScreenProps } from './dashboard_empty_screen'; import { @@ -38,11 +37,9 @@ import { esFilters, IndexPattern, IndexPatternsContract, - Query, QueryState, SavedQuery, syncQueryStateWithUrl, - UI_SETTINGS, } from '../../../data/public'; import { getSavedObjectFinder, SaveResult, showSaveModal } from '../../../saved_objects/public'; @@ -81,8 +78,8 @@ import { addFatalError, AngularHttpError, KibanaLegacyStart, - migrateLegacyQuery, subscribeWithScope, + migrateLegacyQuery, } from '../../../kibana_legacy/public'; export interface DashboardAppControllerDependencies extends RenderDeps { @@ -127,7 +124,6 @@ export class DashboardAppController { $route, $routeParams, dashboardConfig, - localStorage, indexPatterns, savedQueryService, embeddable, @@ -153,8 +149,8 @@ export class DashboardAppController { navigation, }: DashboardAppControllerDependencies) { const filterManager = queryService.filterManager; - const queryFilter = filterManager; const timefilter = queryService.timefilter.timefilter; + const queryStringManager = queryService.queryString; const isEmbeddedExternally = Boolean($routeParams.embed); // url param rules should only apply when embedded (e.g. url?embed=true) @@ -188,20 +184,30 @@ export class DashboardAppController { // sync initial app filters from state to filterManager // if there is an existing similar global filter, then leave it as global filterManager.setAppFilters(_.cloneDeep(dashboardStateManager.appState.filters)); + queryStringManager.setQuery(migrateLegacyQuery(dashboardStateManager.appState.query)); + // setup syncing of app filters between appState and filterManager const stopSyncingAppFilters = connectToQueryState( queryService, { - set: ({ filters }) => dashboardStateManager.setFilters(filters || []), - get: () => ({ filters: dashboardStateManager.appState.filters }), + set: ({ filters, query }) => { + dashboardStateManager.setFilters(filters || []); + dashboardStateManager.setQuery(query || queryStringManager.getDefaultQuery()); + }, + get: () => ({ + filters: dashboardStateManager.appState.filters, + query: dashboardStateManager.getQuery(), + }), state$: dashboardStateManager.appState$.pipe( map((state) => ({ filters: state.filters, + query: queryStringManager.formatQuery(state.query), })) ), }, { filters: esFilters.FilterStateStore.APP_STATE, + query: true, } ); @@ -331,7 +337,7 @@ export class DashboardAppController { const isEmptyInReadonlyMode = shouldShowUnauthorizedEmptyState(); return { id: dashboardStateManager.savedDashboard.id || '', - filters: queryFilter.getFilters(), + filters: filterManager.getFilters(), hidePanelTitles: dashboardStateManager.getHidePanelTitles(), query: $scope.model.query, timeRange: { @@ -356,7 +362,7 @@ export class DashboardAppController { // https://github.com/angular/angular.js/wiki/Understanding-Scopes $scope.model = { query: dashboardStateManager.getQuery(), - filters: queryFilter.getFilters(), + filters: filterManager.getFilters(), timeRestore: dashboardStateManager.getTimeRestore(), title: dashboardStateManager.getTitle(), description: dashboardStateManager.getDescription(), @@ -420,12 +426,12 @@ export class DashboardAppController { if ( !esFilters.compareFilters( container.getInput().filters, - queryFilter.getFilters(), + filterManager.getFilters(), esFilters.COMPARE_ALL_OPTIONS ) ) { // Add filters modifies the object passed to it, hence the clone deep. - queryFilter.addFilters(_.cloneDeep(container.getInput().filters)); + filterManager.addFilters(_.cloneDeep(container.getInput().filters)); dashboardStateManager.applyFilters( $scope.model.query, @@ -487,13 +493,8 @@ export class DashboardAppController { }); dashboardStateManager.applyFilters( - dashboardStateManager.getQuery() || { - query: '', - language: - localStorage.get('kibana.userQueryLanguage') || - uiSettings.get(UI_SETTINGS.SEARCH_QUERY_LANGUAGE), - }, - queryFilter.getFilters() + dashboardStateManager.getQuery() || queryStringManager.getDefaultQuery(), + filterManager.getFilters() ); timefilter.disableTimeRangeSelector(); @@ -567,21 +568,13 @@ export class DashboardAppController { } }; - $scope.updateQueryAndFetch = function ({ query, dateRange }) { - if (dateRange) { - timefilter.setTime(dateRange); - } - - const oldQuery = $scope.model.query; - if (_.isEqual(oldQuery, query)) { + $scope.handleRefresh = function (_payload, isUpdate) { + if (isUpdate === false) { // The user can still request a reload in the query bar, even if the // query is the same, and in that case, we have to explicitly ask for // a reload, since no state changes will cause it. lastReloadRequestTime = new Date().getTime(); refreshDashboardContainer(); - } else { - $scope.model.query = query; - dashboardStateManager.applyFilters($scope.model.query, $scope.model.filters); } }; @@ -600,7 +593,7 @@ export class DashboardAppController { // Making this method sync broke the updates. // Temporary fix, until we fix the complex state in this file. setTimeout(() => { - queryFilter.setFilters(allFilters); + filterManager.setFilters(allFilters); }, 0); }; @@ -633,11 +626,6 @@ export class DashboardAppController { $scope.indexPatterns = []; - $scope.$watch('model.query', (newQuery: Query) => { - const query = migrateLegacyQuery(newQuery) as Query; - $scope.updateQueryAndFetch({ query }); - }); - $scope.$watch( () => dashboardCapabilities.saveQuery, (newCapability) => { @@ -678,18 +666,11 @@ export class DashboardAppController { showFilterBar, indexPatterns: $scope.indexPatterns, showSaveQuery: $scope.showSaveQuery, - query: $scope.model.query, savedQuery: $scope.savedQuery, onSavedQueryIdChange, savedQueryId: dashboardStateManager.getSavedQueryId(), useDefaultBehaviors: true, - onQuerySubmit: (payload: { dateRange: TimeRange; query?: Query }): void => { - if (!payload.query) { - $scope.updateQueryAndFetch({ query: $scope.model.query, dateRange: payload.dateRange }); - } else { - $scope.updateQueryAndFetch({ query: payload.query, dateRange: payload.dateRange }); - } - }, + onQuerySubmit: $scope.handleRefresh, }; }; const dashboardNavBar = document.getElementById('dashboardChrome'); @@ -704,25 +685,11 @@ export class DashboardAppController { }; $scope.timefilterSubscriptions$ = new Subscription(); - + const timeChanges$ = merge(timefilter.getRefreshIntervalUpdate$(), timefilter.getTimeUpdate$()); $scope.timefilterSubscriptions$.add( subscribeWithScope( $scope, - timefilter.getRefreshIntervalUpdate$(), - { - next: () => { - updateState(); - refreshDashboardContainer(); - }, - }, - (error: AngularHttpError | Error | string) => addFatalError(fatalErrors, error) - ) - ); - - $scope.timefilterSubscriptions$.add( - subscribeWithScope( - $scope, - timefilter.getTimeUpdate$(), + timeChanges$, { next: () => { updateState(); @@ -1095,13 +1062,21 @@ export class DashboardAppController { updateViewMode(dashboardStateManager.getViewMode()); + const filterChanges = merge(filterManager.getUpdates$(), queryStringManager.getUpdates$()).pipe( + debounceTime(100) + ); + // update root source when filters update - const updateSubscription = queryFilter.getUpdates$().subscribe({ + const updateSubscription = filterChanges.subscribe({ next: () => { - $scope.model.filters = queryFilter.getFilters(); + $scope.model.filters = filterManager.getFilters(); + $scope.model.query = queryStringManager.getQuery(); dashboardStateManager.applyFilters($scope.model.query, $scope.model.filters); if (dashboardContainer) { - dashboardContainer.updateInput({ filters: $scope.model.filters }); + dashboardContainer.updateInput({ + filters: $scope.model.filters, + query: $scope.model.query, + }); } }, }); diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 23e530898875..789905dfefc5 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -199,10 +199,11 @@ export const castEsToKbnFieldTypeName: (esType: ES_FIELD_TYPES | string) => KBN_ // Warning: (ae-missing-release-tag) "connectToQueryState" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public -export const connectToQueryState: ({ timefilter: { timefilter }, filterManager, state$, }: Pick, stateContainer: BaseStateContainer, syncConfig: { +export const connectToQueryState: ({ timefilter: { timefilter }, filterManager, queryString, state$, }: Pick, stateContainer: BaseStateContainer, syncConfig: { time?: boolean; refreshInterval?: boolean; filters?: FilterStateStore | boolean; + query?: boolean; }) => () => void; // Warning: (ae-missing-release-tag) "createSavedQueryService" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -1397,6 +1398,8 @@ export interface QueryState { // (undocumented) filters?: Filter[]; // (undocumented) + query?: Query; + // (undocumented) refreshInterval?: RefreshInterval; // (undocumented) time?: TimeRange; @@ -1771,7 +1774,7 @@ export type StatefulSearchBarProps = SearchBarOwnProps & { // Warning: (ae-missing-release-tag) "syncQueryStateWithUrl" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public -export const syncQueryStateWithUrl: (query: Pick, kbnUrlStateStorage: IKbnUrlStateStorage) => { +export const syncQueryStateWithUrl: (query: Pick, kbnUrlStateStorage: IKbnUrlStateStorage) => { stop: () => void; hasInheritedQueryFromUrl: boolean; }; @@ -1919,7 +1922,7 @@ export const UI_SETTINGS: { // src/plugins/data/public/index.ts:391:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:392:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:395:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/query/state_sync/connect_to_query_state.ts:41:60 - (ae-forgotten-export) The symbol "FilterStateStore" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/query/state_sync/connect_to_query_state.ts:45:5 - (ae-forgotten-export) The symbol "FilterStateStore" needs to be exported by the entry point index.d.ts // src/plugins/data/public/types.ts:54:5 - (ae-forgotten-export) The symbol "createFiltersFromValueClickAction" needs to be exported by the entry point index.d.ts // src/plugins/data/public/types.ts:55:5 - (ae-forgotten-export) The symbol "createFiltersFromRangeSelectAction" needs to be exported by the entry point index.d.ts // src/plugins/data/public/types.ts:63:5 - (ae-forgotten-export) The symbol "IndexPatternSelectProps" needs to be exported by the entry point index.d.ts diff --git a/src/plugins/data/public/query/mocks.ts b/src/plugins/data/public/query/mocks.ts index 41896107bb86..8c15d9d6d015 100644 --- a/src/plugins/data/public/query/mocks.ts +++ b/src/plugins/data/public/query/mocks.ts @@ -21,6 +21,7 @@ import { Observable } from 'rxjs'; import { QueryService, QuerySetup, QueryStart } from '.'; import { timefilterServiceMock } from './timefilter/timefilter_service.mock'; import { createFilterManagerMock } from './filter_manager/filter_manager.mock'; +import { queryStringManagerMock } from './query_string/query_string_manager.mock'; type QueryServiceClientContract = PublicMethodsOf; @@ -28,6 +29,7 @@ const createSetupContractMock = () => { const setupContract: jest.Mocked = { filterManager: createFilterManagerMock(), timefilter: timefilterServiceMock.createSetupContract(), + queryString: queryStringManagerMock.createSetupContract(), state$: new Observable(), }; @@ -38,6 +40,7 @@ const createStartContractMock = () => { const startContract: jest.Mocked = { addToQueryLog: jest.fn(), filterManager: createFilterManagerMock(), + queryString: queryStringManagerMock.createStartContract(), savedQueries: jest.fn() as any, state$: new Observable(), timefilter: timefilterServiceMock.createStartContract(), diff --git a/src/plugins/data/public/query/query_service.ts b/src/plugins/data/public/query/query_service.ts index eb1f985fa51d..da514c0e24ea 100644 --- a/src/plugins/data/public/query/query_service.ts +++ b/src/plugins/data/public/query/query_service.ts @@ -25,6 +25,7 @@ import { createAddToQueryLog } from './lib'; import { TimefilterService, TimefilterSetup } from './timefilter'; import { createSavedQueryService } from './saved_query/saved_query_service'; import { createQueryStateObservable } from './state_sync/create_global_query_observable'; +import { QueryStringManager, QueryStringContract } from './query_string'; /** * Query Service @@ -45,6 +46,7 @@ interface QueryServiceStartDependencies { export class QueryService { filterManager!: FilterManager; timefilter!: TimefilterSetup; + queryStringManager!: QueryStringContract; state$!: ReturnType; @@ -57,14 +59,18 @@ export class QueryService { storage, }); + this.queryStringManager = new QueryStringManager(storage, uiSettings); + this.state$ = createQueryStateObservable({ filterManager: this.filterManager, timefilter: this.timefilter, + queryString: this.queryStringManager, }).pipe(share()); return { filterManager: this.filterManager, timefilter: this.timefilter, + queryString: this.queryStringManager, state$: this.state$, }; } @@ -76,6 +82,7 @@ export class QueryService { uiSettings, }), filterManager: this.filterManager, + queryString: this.queryStringManager, savedQueries: createSavedQueryService(savedObjectsClient), state$: this.state$, timefilter: this.timefilter, diff --git a/src/plugins/data/public/query/query_string/index.ts b/src/plugins/data/public/query/query_string/index.ts new file mode 100644 index 000000000000..6ea87fde69ac --- /dev/null +++ b/src/plugins/data/public/query/query_string/index.ts @@ -0,0 +1,20 @@ +/* + * 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. + */ + +export { QueryStringContract, QueryStringManager } from './query_string_manager'; diff --git a/src/plugins/data/public/query/query_string/query_string_manager.mock.ts b/src/plugins/data/public/query/query_string/query_string_manager.mock.ts new file mode 100644 index 000000000000..427662cb01eb --- /dev/null +++ b/src/plugins/data/public/query/query_string/query_string_manager.mock.ts @@ -0,0 +1,37 @@ +/* + * 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 { QueryStringContract } from '.'; + +const createSetupContractMock = () => { + const queryStringManagerMock: jest.Mocked = { + getQuery: jest.fn(), + setQuery: jest.fn(), + getUpdates$: jest.fn(), + getDefaultQuery: jest.fn(), + formatQuery: jest.fn(), + clearQuery: jest.fn(), + }; + return queryStringManagerMock; +}; + +export const queryStringManagerMock = { + createSetupContract: createSetupContractMock, + createStartContract: createSetupContractMock, +}; diff --git a/src/plugins/data/public/query/query_string/query_string_manager.ts b/src/plugins/data/public/query/query_string/query_string_manager.ts new file mode 100644 index 000000000000..bd02830f4aed --- /dev/null +++ b/src/plugins/data/public/query/query_string/query_string_manager.ts @@ -0,0 +1,90 @@ +/* + * 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 _ from 'lodash'; +import { BehaviorSubject } from 'rxjs'; +import { CoreStart } from 'kibana/public'; +import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; +import { Query, UI_SETTINGS } from '../../../common'; + +export class QueryStringManager { + private query$: BehaviorSubject; + + constructor( + private readonly storage: IStorageWrapper, + private readonly uiSettings: CoreStart['uiSettings'] + ) { + this.query$ = new BehaviorSubject(this.getDefaultQuery()); + } + + private getDefaultLanguage() { + return ( + this.storage.get('kibana.userQueryLanguage') || + this.uiSettings.get(UI_SETTINGS.SEARCH_QUERY_LANGUAGE) + ); + } + + public getDefaultQuery() { + return { + query: '', + language: this.getDefaultLanguage(), + }; + } + + public formatQuery(query: Query | string | undefined): Query { + if (!query) { + return this.getDefaultQuery(); + } else if (typeof query === 'string') { + return { + query, + language: this.getDefaultLanguage(), + }; + } else { + return query; + } + } + + public getUpdates$ = () => { + return this.query$.asObservable(); + }; + + public getQuery = (): Query => { + return this.query$.getValue(); + }; + + /** + * Updates the query. + * @param {Query} query + */ + public setQuery = (query: Query) => { + const curQuery = this.query$.getValue(); + if (query?.language !== curQuery.language || query?.query !== curQuery.query) { + this.query$.next(query); + } + }; + + /** + * Resets the query to the default one. + */ + public clearQuery = () => { + this.setQuery(this.getDefaultQuery()); + }; +} + +export type QueryStringContract = PublicMethodsOf; diff --git a/src/plugins/data/public/query/state_sync/connect_to_query_state.test.ts b/src/plugins/data/public/query/state_sync/connect_to_query_state.test.ts index cf98c87b1826..307d1fe1b2b0 100644 --- a/src/plugins/data/public/query/state_sync/connect_to_query_state.test.ts +++ b/src/plugins/data/public/query/state_sync/connect_to_query_state.test.ts @@ -48,6 +48,8 @@ setupMock.uiSettings.get.mockImplementation((key: string) => { switch (key) { case UI_SETTINGS.FILTERS_PINNED_BY_DEFAULT: return true; + case UI_SETTINGS.SEARCH_QUERY_LANGUAGE: + return 'kuery'; case 'timepicker:timeDefaults': return { from: 'now-15m', to: 'now' }; case UI_SETTINGS.TIMEPICKER_REFRESH_INTERVAL_DEFAULTS: diff --git a/src/plugins/data/public/query/state_sync/connect_to_query_state.ts b/src/plugins/data/public/query/state_sync/connect_to_query_state.ts index 2e62dac87f6e..55edd04b5dab 100644 --- a/src/plugins/data/public/query/state_sync/connect_to_query_state.ts +++ b/src/plugins/data/public/query/state_sync/connect_to_query_state.ts @@ -35,15 +35,24 @@ export const connectToQueryState = ( { timefilter: { timefilter }, filterManager, + queryString, state$, - }: Pick, + }: Pick, stateContainer: BaseStateContainer, - syncConfig: { time?: boolean; refreshInterval?: boolean; filters?: FilterStateStore | boolean } + syncConfig: { + time?: boolean; + refreshInterval?: boolean; + filters?: FilterStateStore | boolean; + query?: boolean; + } ) => { const syncKeys: Array = []; if (syncConfig.time) { syncKeys.push('time'); } + if (syncConfig.query) { + syncKeys.push('query'); + } if (syncConfig.refreshInterval) { syncKeys.push('refreshInterval'); } @@ -133,6 +142,9 @@ export const connectToQueryState = ( if (syncConfig.time && changes.time) { newState.time = timefilter.getTime(); } + if (syncConfig.query && changes.query) { + newState.query = queryString.getQuery(); + } if (syncConfig.refreshInterval && changes.refreshInterval) { newState.refreshInterval = timefilter.getRefreshInterval(); } @@ -173,6 +185,13 @@ export const connectToQueryState = ( } } + if (syncConfig.query) { + const curQuery = state.query || queryString.getQuery(); + if (!_.isEqual(curQuery, queryString.getQuery())) { + queryString.setQuery(_.cloneDeep(curQuery)); + } + } + if (syncConfig.filters) { const filters = state.filters || []; if (syncConfig.filters === true) { diff --git a/src/plugins/data/public/query/state_sync/create_global_query_observable.ts b/src/plugins/data/public/query/state_sync/create_global_query_observable.ts index 87032925294c..5e2c575c74af 100644 --- a/src/plugins/data/public/query/state_sync/create_global_query_observable.ts +++ b/src/plugins/data/public/query/state_sync/create_global_query_observable.ts @@ -24,23 +24,31 @@ import { FilterManager } from '../filter_manager'; import { QueryState, QueryStateChange } from './index'; import { createStateContainer } from '../../../../kibana_utils/public'; import { isFilterPinned, compareFilters, COMPARE_ALL_OPTIONS } from '../../../common'; +import { QueryStringContract } from '../query_string'; export function createQueryStateObservable({ timefilter: { timefilter }, filterManager, + queryString, }: { timefilter: TimefilterSetup; filterManager: FilterManager; + queryString: QueryStringContract; }): Observable<{ changes: QueryStateChange; state: QueryState }> { return new Observable((subscriber) => { const state = createStateContainer({ time: timefilter.getTime(), refreshInterval: timefilter.getRefreshInterval(), filters: filterManager.getFilters(), + query: queryString.getQuery(), }); let currentChange: QueryStateChange = {}; const subs: Subscription[] = [ + queryString.getUpdates$().subscribe(() => { + currentChange.query = true; + state.set({ ...state.get(), query: queryString.getQuery() }); + }), timefilter.getTimeUpdate$().subscribe(() => { currentChange.time = true; state.set({ ...state.get(), time: timefilter.getTime() }); diff --git a/src/plugins/data/public/query/state_sync/sync_state_with_url.test.ts b/src/plugins/data/public/query/state_sync/sync_state_with_url.test.ts index 122eb2ff6a34..0b4a3f663eb6 100644 --- a/src/plugins/data/public/query/state_sync/sync_state_with_url.test.ts +++ b/src/plugins/data/public/query/state_sync/sync_state_with_url.test.ts @@ -43,6 +43,8 @@ setupMock.uiSettings.get.mockImplementation((key: string) => { return true; case 'timepicker:timeDefaults': return { from: 'now-15m', to: 'now' }; + case 'search:queryLanguage': + return 'kuery'; case UI_SETTINGS.TIMEPICKER_REFRESH_INTERVAL_DEFAULTS: return { pause: false, value: 0 }; default: diff --git a/src/plugins/data/public/query/state_sync/sync_state_with_url.ts b/src/plugins/data/public/query/state_sync/sync_state_with_url.ts index 4d3da7b9313a..46be800fbb55 100644 --- a/src/plugins/data/public/query/state_sync/sync_state_with_url.ts +++ b/src/plugins/data/public/query/state_sync/sync_state_with_url.ts @@ -35,7 +35,7 @@ const GLOBAL_STATE_STORAGE_KEY = '_g'; * @param kbnUrlStateStorage to use for syncing */ export const syncQueryStateWithUrl = ( - query: Pick, + query: Pick, kbnUrlStateStorage: IKbnUrlStateStorage ) => { const { diff --git a/src/plugins/data/public/query/state_sync/types.ts b/src/plugins/data/public/query/state_sync/types.ts index 747d4d45fe29..2354db8cad11 100644 --- a/src/plugins/data/public/query/state_sync/types.ts +++ b/src/plugins/data/public/query/state_sync/types.ts @@ -17,7 +17,7 @@ * under the License. */ -import { Filter, RefreshInterval, TimeRange } from '../../../common'; +import { Filter, RefreshInterval, TimeRange, Query } from '../../../common'; /** * All query state service state @@ -26,6 +26,7 @@ export interface QueryState { time?: TimeRange; refreshInterval?: RefreshInterval; filters?: Filter[]; + query?: Query; } type QueryStateChangePartial = { diff --git a/src/plugins/data/public/ui/search_bar/create_search_bar.tsx b/src/plugins/data/public/ui/search_bar/create_search_bar.tsx index f8b7e4f48091..9f0ba2378592 100644 --- a/src/plugins/data/public/ui/search_bar/create_search_bar.tsx +++ b/src/plugins/data/public/ui/search_bar/create_search_bar.tsx @@ -18,7 +18,7 @@ */ import _ from 'lodash'; -import React, { useState, useEffect, useRef } from 'react'; +import React, { useEffect, useRef } from 'react'; import { CoreStart } from 'src/core/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { KibanaContextProvider } from '../../../../kibana_react/public'; @@ -28,7 +28,8 @@ import { useFilterManager } from './lib/use_filter_manager'; import { useTimefilter } from './lib/use_timefilter'; import { useSavedQuery } from './lib/use_saved_query'; import { DataPublicPluginStart } from '../../types'; -import { Filter, Query, TimeRange, UI_SETTINGS } from '../../../common'; +import { Filter, Query, TimeRange } from '../../../common'; +import { useQueryStringManager } from './lib/use_query_string_manager'; interface StatefulSearchBarDeps { core: CoreStart; @@ -65,8 +66,7 @@ const defaultOnRefreshChange = (queryService: QueryStart) => { const defaultOnQuerySubmit = ( props: StatefulSearchBarProps, queryService: QueryStart, - currentQuery: Query, - setQueryStringState: Function + currentQuery: Query ) => { if (!props.useDefaultBehaviors) return props.onQuerySubmit; @@ -78,7 +78,11 @@ const defaultOnQuerySubmit = ( !_.isEqual(payload.query, currentQuery); if (isUpdate) { timefilter.setTime(payload.dateRange); - setQueryStringState(payload.query); + if (payload.query) { + queryService.queryString.setQuery(payload.query); + } else { + queryService.queryString.clearQuery(); + } } else { // Refresh button triggered for an update if (props.onQuerySubmit) @@ -121,30 +125,7 @@ export function createSearchBar({ core, storage, data }: StatefulSearchBarDeps) return (props: StatefulSearchBarProps) => { const { useDefaultBehaviors } = props; // Handle queries - const queryRef = useRef(props.query); const onQuerySubmitRef = useRef(props.onQuerySubmit); - const defaultQuery = { - query: '', - language: - storage.get('kibana.userQueryLanguage') || - core.uiSettings.get(UI_SETTINGS.SEARCH_QUERY_LANGUAGE), - }; - const [query, setQuery] = useState(props.query || defaultQuery); - - useEffect(() => { - if (props.query !== queryRef.current) { - queryRef.current = props.query; - setQuery(props.query || defaultQuery); - } - /* eslint-disable-next-line react-hooks/exhaustive-deps */ - }, [defaultQuery, props.query]); - - useEffect(() => { - if (props.onQuerySubmit !== onQuerySubmitRef.current) { - onQuerySubmitRef.current = props.onQuerySubmit; - } - /* eslint-disable-next-line react-hooks/exhaustive-deps */ - }, [props.onQuerySubmit]); // handle service state updates. // i.e. filters being added from a visualization directly to filterManager. @@ -152,6 +133,10 @@ export function createSearchBar({ core, storage, data }: StatefulSearchBarDeps) filters: props.filters, filterManager: data.query.filterManager, }); + const { query } = useQueryStringManager({ + query: props.query, + queryStringManager: data.query.queryString, + }); const { timeRange, refreshInterval } = useTimefilter({ dateRangeFrom: props.dateRangeFrom, dateRangeTo: props.dateRangeTo, @@ -163,10 +148,8 @@ export function createSearchBar({ core, storage, data }: StatefulSearchBarDeps) // Fetch and update UI from saved query const { savedQuery, setSavedQuery, clearSavedQuery } = useSavedQuery({ queryService: data.query, - setQuery, savedQueryId: props.savedQueryId, notifications: core.notifications, - defaultLanguage: defaultQuery.language, }); // Fire onQuerySubmit on query or timerange change @@ -210,7 +193,7 @@ export function createSearchBar({ core, storage, data }: StatefulSearchBarDeps) onFiltersUpdated={defaultFiltersUpdated(data.query)} onRefreshChange={defaultOnRefreshChange(data.query)} savedQuery={savedQuery} - onQuerySubmit={defaultOnQuerySubmit(props, data.query, query, setQuery)} + onQuerySubmit={defaultOnQuerySubmit(props, data.query, query)} onClearSavedQuery={defaultOnClearSavedQuery(props, clearSavedQuery)} onSavedQueryUpdated={defaultOnSavedQueryUpdated(props, setSavedQuery)} onSaved={defaultOnSavedQueryUpdated(props, setSavedQuery)} diff --git a/src/plugins/data/public/ui/search_bar/lib/clear_saved_query.test.ts b/src/plugins/data/public/ui/search_bar/lib/clear_saved_query.test.ts index ccfe5464b959..10520fc3714d 100644 --- a/src/plugins/data/public/ui/search_bar/lib/clear_saved_query.test.ts +++ b/src/plugins/data/public/ui/search_bar/lib/clear_saved_query.test.ts @@ -21,10 +21,8 @@ import { clearStateFromSavedQuery } from './clear_saved_query'; import { dataPluginMock } from '../../../mocks'; import { DataPublicPluginStart } from '../../../types'; -import { Query } from '../../..'; describe('clearStateFromSavedQuery', () => { - const DEFAULT_LANGUAGE = 'banana'; let dataMock: jest.Mocked; beforeEach(() => { @@ -32,19 +30,9 @@ describe('clearStateFromSavedQuery', () => { }); it('should clear filters and query', async () => { - const setQueryState = jest.fn(); dataMock.query.filterManager.removeAll = jest.fn(); - clearStateFromSavedQuery(dataMock.query, setQueryState, DEFAULT_LANGUAGE); - expect(setQueryState).toHaveBeenCalled(); - expect(dataMock.query.filterManager.removeAll).toHaveBeenCalled(); - }); - - it('should use search:queryLanguage', async () => { - const setQueryState = jest.fn(); - dataMock.query.filterManager.removeAll = jest.fn(); - clearStateFromSavedQuery(dataMock.query, setQueryState, DEFAULT_LANGUAGE); - expect(setQueryState).toHaveBeenCalled(); - expect((setQueryState.mock.calls[0][0] as Query).language).toBe(DEFAULT_LANGUAGE); + clearStateFromSavedQuery(dataMock.query); + expect(dataMock.query.queryString.clearQuery).toHaveBeenCalled(); expect(dataMock.query.filterManager.removeAll).toHaveBeenCalled(); }); }); diff --git a/src/plugins/data/public/ui/search_bar/lib/clear_saved_query.ts b/src/plugins/data/public/ui/search_bar/lib/clear_saved_query.ts index b2c777261c25..06ee56e9e438 100644 --- a/src/plugins/data/public/ui/search_bar/lib/clear_saved_query.ts +++ b/src/plugins/data/public/ui/search_bar/lib/clear_saved_query.ts @@ -18,14 +18,7 @@ */ import { QueryStart } from '../../../query'; -export const clearStateFromSavedQuery = ( - queryService: QueryStart, - setQueryStringState: Function, - defaultLanguage: string -) => { +export const clearStateFromSavedQuery = (queryService: QueryStart) => { queryService.filterManager.removeAll(); - setQueryStringState({ - query: '', - language: defaultLanguage, - }); + queryService.queryString.clearQuery(); }; diff --git a/src/plugins/data/public/ui/search_bar/lib/populate_state_from_saved_query.test.ts b/src/plugins/data/public/ui/search_bar/lib/populate_state_from_saved_query.test.ts index 1db900053e07..660aa2333d49 100644 --- a/src/plugins/data/public/ui/search_bar/lib/populate_state_from_saved_query.test.ts +++ b/src/plugins/data/public/ui/search_bar/lib/populate_state_from_saved_query.test.ts @@ -47,37 +47,34 @@ describe('populateStateFromSavedQuery', () => { }); it('should set query', async () => { - const setQueryState = jest.fn(); const savedQuery: SavedQuery = { ...baseSavedQuery, }; - populateStateFromSavedQuery(dataMock.query, setQueryState, savedQuery); - expect(setQueryState).toHaveBeenCalled(); + populateStateFromSavedQuery(dataMock.query, savedQuery); + expect(dataMock.query.queryString.setQuery).toHaveBeenCalled(); }); it('should set filters', async () => { - const setQueryState = jest.fn(); const savedQuery: SavedQuery = { ...baseSavedQuery, }; const f1 = getFilter(FilterStateStore.APP_STATE, false, false, 'age', 34); savedQuery.attributes.filters = [f1]; - populateStateFromSavedQuery(dataMock.query, setQueryState, savedQuery); - expect(setQueryState).toHaveBeenCalled(); + populateStateFromSavedQuery(dataMock.query, savedQuery); + expect(dataMock.query.queryString.setQuery).toHaveBeenCalled(); expect(dataMock.query.filterManager.setFilters).toHaveBeenCalledWith([f1]); }); 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 setQueryState = jest.fn(); const savedQuery: SavedQuery = { ...baseSavedQuery, }; const f1 = getFilter(FilterStateStore.APP_STATE, false, false, 'age', 34); savedQuery.attributes.filters = [f1]; - populateStateFromSavedQuery(dataMock.query, setQueryState, savedQuery); - expect(setQueryState).toHaveBeenCalled(); + populateStateFromSavedQuery(dataMock.query, savedQuery); + expect(dataMock.query.queryString.setQuery).toHaveBeenCalled(); expect(dataMock.query.filterManager.setFilters).toHaveBeenCalledWith([globalFilter, f1]); }); @@ -97,7 +94,7 @@ describe('populateStateFromSavedQuery', () => { dataMock.query.timefilter.timefilter.setTime = jest.fn(); dataMock.query.timefilter.timefilter.setRefreshInterval = jest.fn(); - populateStateFromSavedQuery(dataMock.query, jest.fn(), savedQuery); + populateStateFromSavedQuery(dataMock.query, savedQuery); expect(dataMock.query.timefilter.timefilter.setTime).toHaveBeenCalledWith({ from: savedQuery.attributes.timefilter.from, diff --git a/src/plugins/data/public/ui/search_bar/lib/populate_state_from_saved_query.ts b/src/plugins/data/public/ui/search_bar/lib/populate_state_from_saved_query.ts index 7ae6726b36df..bb4b97cc4a9f 100644 --- a/src/plugins/data/public/ui/search_bar/lib/populate_state_from_saved_query.ts +++ b/src/plugins/data/public/ui/search_bar/lib/populate_state_from_saved_query.ts @@ -19,14 +19,11 @@ import { QueryStart, SavedQuery } from '../../../query'; -export const populateStateFromSavedQuery = ( - queryService: QueryStart, - setQueryStringState: Function, - savedQuery: SavedQuery -) => { +export const populateStateFromSavedQuery = (queryService: QueryStart, savedQuery: SavedQuery) => { const { timefilter: { timefilter }, filterManager, + queryString, } = queryService; // timefilter if (savedQuery.attributes.timefilter) { @@ -40,7 +37,7 @@ export const populateStateFromSavedQuery = ( } // query string - setQueryStringState(savedQuery.attributes.query); + queryString.setQuery(savedQuery.attributes.query); // filters const savedQueryFilters = savedQuery.attributes.filters || []; diff --git a/src/plugins/data/public/ui/search_bar/lib/use_query_string_manager.ts b/src/plugins/data/public/ui/search_bar/lib/use_query_string_manager.ts new file mode 100644 index 000000000000..e28129f20bb8 --- /dev/null +++ b/src/plugins/data/public/ui/search_bar/lib/use_query_string_manager.ts @@ -0,0 +1,51 @@ +/* + * 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 { Subscription } from 'rxjs'; +import { Query } from '../../..'; +import { QueryStringContract } from '../../../query/query_string'; + +interface UseQueryStringProps { + query?: Query; + queryStringManager: QueryStringContract; +} + +export const useQueryStringManager = (props: UseQueryStringProps) => { + // Filters should be either what's passed in the initial state or the current state of the filter manager + const [query, setQuery] = useState(props.query || props.queryStringManager.getQuery()); + useEffect(() => { + const subscriptions = new Subscription(); + + subscriptions.add( + props.queryStringManager.getUpdates$().subscribe({ + next: () => { + const newQuery = props.queryStringManager.getQuery(); + setQuery(newQuery); + }, + }) + ); + + return () => { + subscriptions.unsubscribe(); + }; + }, [props.queryStringManager]); + + return { query }; +}; diff --git a/src/plugins/data/public/ui/search_bar/lib/use_saved_query.ts b/src/plugins/data/public/ui/search_bar/lib/use_saved_query.ts index 79aee3438d7a..9f73a401f563 100644 --- a/src/plugins/data/public/ui/search_bar/lib/use_saved_query.ts +++ b/src/plugins/data/public/ui/search_bar/lib/use_saved_query.ts @@ -27,10 +27,8 @@ import { clearStateFromSavedQuery } from './clear_saved_query'; interface UseSavedQueriesProps { queryService: DataPublicPluginStart['query']; - setQuery: Function; notifications: CoreStart['notifications']; savedQueryId?: string; - defaultLanguage: string; } interface UseSavedQueriesReturn { @@ -41,7 +39,6 @@ interface UseSavedQueriesReturn { export const useSavedQuery = (props: UseSavedQueriesProps): UseSavedQueriesReturn => { // Handle saved queries - const defaultLanguage = props.defaultLanguage; const [savedQuery, setSavedQuery] = useState(); // Effect is used to convert a saved query id into an object @@ -53,12 +50,12 @@ export const useSavedQuery = (props: UseSavedQueriesProps): UseSavedQueriesRetur // Make sure we set the saved query to the most recent one if (newSavedQuery && newSavedQuery.id === savedQueryId) { setSavedQuery(newSavedQuery); - populateStateFromSavedQuery(props.queryService, props.setQuery, newSavedQuery); + populateStateFromSavedQuery(props.queryService, newSavedQuery); } } catch (error) { // Clear saved query setSavedQuery(undefined); - clearStateFromSavedQuery(props.queryService, props.setQuery, defaultLanguage); + clearStateFromSavedQuery(props.queryService); // notify of saving error props.notifications.toasts.addWarning({ title: i18n.translate('data.search.unableToGetSavedQueryToastTitle', { @@ -73,23 +70,21 @@ export const useSavedQuery = (props: UseSavedQueriesProps): UseSavedQueriesRetur if (props.savedQueryId) fetchSavedQuery(props.savedQueryId); else setSavedQuery(undefined); }, [ - defaultLanguage, props.notifications.toasts, props.queryService, props.queryService.savedQueries, props.savedQueryId, - props.setQuery, ]); return { savedQuery, setSavedQuery: (q: SavedQuery) => { setSavedQuery(q); - populateStateFromSavedQuery(props.queryService, props.setQuery, q); + populateStateFromSavedQuery(props.queryService, q); }, clearSavedQuery: () => { setSavedQuery(undefined); - clearStateFromSavedQuery(props.queryService, props.setQuery, defaultLanguage); + clearStateFromSavedQuery(props.queryService); }, }; }; diff --git a/src/plugins/discover/public/application/angular/discover.html b/src/plugins/discover/public/application/angular/discover.html index 48a8442b0631..d3d4f524873d 100644 --- a/src/plugins/discover/public/application/angular/discover.html +++ b/src/plugins/discover/public/application/angular/discover.html @@ -6,9 +6,8 @@ app-name="'discover'" config="topNavMenu" index-patterns="[indexPattern]" - on-query-submit="updateQuery" + on-query-submit="handleRefresh" on-saved-query-id-change="updateSavedQueryId" - query="state.query" saved-query-id="state.savedQuery" screen-title="screenTitle" show-date-picker="indexPattern.isTimeBased()" diff --git a/src/plugins/discover/public/application/angular/discover.js b/src/plugins/discover/public/application/angular/discover.js index c791bdd85015..4a27f261a622 100644 --- a/src/plugins/discover/public/application/angular/discover.js +++ b/src/plugins/discover/public/application/angular/discover.js @@ -70,9 +70,7 @@ import { indexPatterns as indexPatternsUtils, connectToQueryState, syncQueryStateWithUrl, - getDefaultQuery, search, - UI_SETTINGS, } from '../../../../data/public'; import { getIndexPatternId } from '../helpers/get_index_pattern_id'; import { addFatalError } from '../../../../kibana_legacy/public'; @@ -191,16 +189,7 @@ app.directive('discoverApp', function () { }; }); -function discoverController( - $element, - $route, - $scope, - $timeout, - $window, - Promise, - localStorage, - uiCapabilities -) { +function discoverController($element, $route, $scope, $timeout, $window, Promise, uiCapabilities) { const { isDefault: isDefaultType } = indexPatternsUtils; const subscriptions = new Subscription(); const $fetchObservable = new Subject(); @@ -246,11 +235,15 @@ function discoverController( // sync initial app filters from state to filterManager filterManager.setAppFilters(_.cloneDeep(appStateContainer.getState().filters)); + data.query.queryString.setQuery(appStateContainer.getState().query); const stopSyncingQueryAppStateWithStateContainer = connectToQueryState( data.query, appStateContainer, - { filters: esFilters.FilterStateStore.APP_STATE } + { + filters: esFilters.FilterStateStore.APP_STATE, + query: true, + } ); const appStateUnsubscribe = appStateContainer.subscribe(async (newState) => { @@ -262,7 +255,7 @@ function discoverController( $scope.state = { ...newState }; // detect changes that should trigger fetching of new data - const changes = ['interval', 'sort', 'query'].filter( + const changes = ['interval', 'sort'].filter( (prop) => !_.isEqual(newStatePartial[prop], oldStatePartial[prop]) ); @@ -593,12 +586,7 @@ function discoverController( }; function getStateDefaults() { - const query = - $scope.searchSource.getField('query') || - getDefaultQuery( - localStorage.get('kibana.userQueryLanguage') || - config.get(UI_SETTINGS.SEARCH_QUERY_LANGUAGE) - ); + const query = $scope.searchSource.getField('query') || data.query.queryString.getDefaultQuery(); return { query, sort: getSortArray(savedSearch.sort, $scope.indexPattern), @@ -635,12 +623,7 @@ function discoverController( const init = _.once(() => { $scope.updateDataSource().then(async () => { - const searchBarChanges = merge( - timefilter.getAutoRefreshFetch$(), - timefilter.getFetch$(), - filterManager.getFetches$(), - $fetchObservable - ).pipe(debounceTime(100)); + const searchBarChanges = merge(data.query.state$, $fetchObservable).pipe(debounceTime(100)); subscriptions.add( subscribeWithScope( @@ -824,9 +807,8 @@ function discoverController( }); }; - $scope.updateQuery = function ({ query }, isUpdate = true) { - if (!_.isEqual(query, appStateContainer.getState().query) || isUpdate === false) { - setAppState({ query }); + $scope.handleRefresh = function (_payload, isUpdate) { + if (isUpdate === false) { $fetchObservable.next(); } }; @@ -976,7 +958,7 @@ function discoverController( config.get(SORT_DEFAULT_ORDER_SETTING) ) ) - .setField('query', $scope.state.query || null) + .setField('query', data.query.queryString.getQuery() || null) .setField('filter', filterManager.getFilters()); return Promise.resolve(); }; diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/scripting_help/test_script.tsx b/src/plugins/index_pattern_management/public/components/field_editor/components/scripting_help/test_script.tsx index c97f19f59d34..cb1d5a25c01a 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/scripting_help/test_script.tsx +++ b/src/plugins/index_pattern_management/public/components/field_editor/components/scripting_help/test_script.tsx @@ -35,12 +35,7 @@ import { import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { - esQuery, - IndexPattern, - Query, - UI_SETTINGS, -} from '../../../../../../../plugins/data/public'; +import { esQuery, IndexPattern, Query } from '../../../../../../../plugins/data/public'; import { context as contextType } from '../../../../../../kibana_react/public'; import { IndexPatternManagmentContextValue } from '../../../../types'; import { ExecuteScript } from '../../types'; @@ -248,10 +243,7 @@ export class TestScript extends Component { showFilterBar={false} showDatePicker={false} showQueryInput={true} - query={{ - language: this.context.services.uiSettings.get(UI_SETTINGS.SEARCH_QUERY_LANGUAGE), - query: '', - }} + query={this.context.services.data.query.queryString.getDefaultQuery()} onQuerySubmit={this.previewScript} indexPatterns={[this.props.indexPattern]} customSubmitButton={ diff --git a/src/plugins/vis_default_editor/public/components/controls/filters.tsx b/src/plugins/vis_default_editor/public/components/controls/filters.tsx index 04d0df27927f..fc676e25ff6d 100644 --- a/src/plugins/vis_default_editor/public/components/controls/filters.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/filters.tsx @@ -23,7 +23,7 @@ import { htmlIdGenerator, EuiButton, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { useMount } from 'react-use'; -import { Query, UI_SETTINGS } from '../../../../data/public'; +import { Query } from '../../../../data/public'; import { useKibana } from '../../../../kibana_react/public'; import { FilterRow } from './filter'; import { AggParamEditorProps } from '../agg_param_props'; @@ -70,7 +70,7 @@ function FiltersParamEditor({ agg, value = [], setValue }: AggParamEditorProps { - if (!isEqual(currentAppState.query, query)) { - stateContainer.transitions.set('query', query || currentAppState.query); - } else { + const handleRefresh = useCallback( + (_payload: any, isUpdate?: boolean) => { + if (isUpdate === false) { savedVisInstance.embeddableHandler.reload(); } }, - [currentAppState.query, savedVisInstance.embeddableHandler, stateContainer.transitions] + [savedVisInstance.embeddableHandler] ); const config = useMemo(() => { @@ -149,8 +145,7 @@ const TopNav = ({ { to: 'now', }; mockFilters = ['mockFilters']; + const mockQuery = { + query: '', + language: 'kuery', + }; // @ts-expect-error mockServices.data.query.timefilter.timefilter.getTime.mockImplementation(() => timeRange); // @ts-expect-error mockServices.data.query.filterManager.getFilters.mockImplementation(() => mockFilters); + // @ts-expect-error + mockServices.data.query.queryString.getQuery.mockImplementation(() => mockQuery); }); test('should set up current app state and render the editor', () => { diff --git a/src/plugins/visualize/public/application/utils/use/use_editor_updates.ts b/src/plugins/visualize/public/application/utils/use/use_editor_updates.ts index 360e7560b193..0f4b2d34e8e8 100644 --- a/src/plugins/visualize/public/application/utils/use/use_editor_updates.ts +++ b/src/plugins/visualize/public/application/utils/use/use_editor_updates.ts @@ -20,9 +20,7 @@ import { useEffect, useState } from 'react'; import { isEqual } from 'lodash'; import { EventEmitter } from 'events'; -import { merge } from 'rxjs'; -import { migrateLegacyQuery } from '../../../../../kibana_legacy/public'; import { VisualizeServices, VisualizeAppState, @@ -47,6 +45,8 @@ export const useEditorUpdates = ( const { timefilter: { timefilter }, filterManager, + queryString, + state$, } = services.data.query; const { embeddableHandler, savedVis, savedSearch, vis } = savedVisInstance; const initialState = appState.getState(); @@ -60,7 +60,7 @@ export const useEditorUpdates = ( uiState: vis.uiState, timeRange: timefilter.getTime(), filters: filterManager.getFilters(), - query: appState.getState().query, + query: queryString.getQuery(), linked: !!vis.data.savedSearchId, savedSearch, }); @@ -68,17 +68,12 @@ export const useEditorUpdates = ( embeddableHandler.updateInput({ timeRange: timefilter.getTime(), filters: filterManager.getFilters(), - query: appState.getState().query, + query: queryString.getQuery(), }); } }; - const subscriptions = merge( - timefilter.getTimeUpdate$(), - timefilter.getAutoRefreshFetch$(), - timefilter.getFetch$(), - filterManager.getFetches$() - ).subscribe({ + const subscriptions = state$.subscribe({ next: reloadVisualization, error: services.fatalErrors.add, }); @@ -116,10 +111,6 @@ export const useEditorUpdates = ( // and initializing different visualizations return; } - const newQuery = migrateLegacyQuery(state.query); - if (!isEqual(state.query, newQuery)) { - appState.transitions.set('query', newQuery); - } if (!isEqual(state.uiState, vis.uiState.getChanges())) { vis.uiState.set(state.uiState); diff --git a/src/plugins/visualize/public/application/utils/use/use_visualize_app_state.test.ts b/src/plugins/visualize/public/application/utils/use/use_visualize_app_state.test.ts index e885067c5818..8bde9a049c49 100644 --- a/src/plugins/visualize/public/application/utils/use/use_visualize_app_state.test.ts +++ b/src/plugins/visualize/public/application/utils/use/use_visualize_app_state.test.ts @@ -96,6 +96,7 @@ describe('useVisualizeAppState', () => { ); expect(connectToQueryState).toHaveBeenCalledWith(mockServices.data.query, expect.any(Object), { filters: 'appState', + query: true, }); expect(result.current).toEqual({ appState: stateContainer, diff --git a/src/plugins/visualize/public/application/utils/use/use_visualize_app_state.tsx b/src/plugins/visualize/public/application/utils/use/use_visualize_app_state.tsx index e4d891472fbf..c44f67df3729 100644 --- a/src/plugins/visualize/public/application/utils/use/use_visualize_app_state.tsx +++ b/src/plugins/visualize/public/application/utils/use/use_visualize_app_state.tsx @@ -24,6 +24,7 @@ import { EventEmitter } from 'events'; import { i18n } from '@kbn/i18n'; import { MarkdownSimple, toMountPoint } from '../../../../../kibana_react/public'; +import { migrateLegacyQuery } from '../../../../../kibana_legacy/public'; import { esFilters, connectToQueryState } from '../../../../../data/public'; import { VisualizeServices, VisualizeAppStateContainer, SavedVisInstance } from '../../types'; import { visStateToEditorState } from '../utils'; @@ -61,19 +62,35 @@ export const useVisualizeAppState = ( eventEmitter.on('dirtyStateChange', onDirtyStateChange); - const { filterManager } = services.data.query; - // sync initial app filters from state to filterManager + const { filterManager, queryString } = services.data.query; + // sync initial app state from state to managers filterManager.setAppFilters(cloneDeep(stateContainer.getState().filters)); - // setup syncing of app filters between appState and filterManager + queryString.setQuery(migrateLegacyQuery(stateContainer.getState().query)); + + // setup syncing of app filters between appState and query services const stopSyncingAppFilters = connectToQueryState( services.data.query, { - set: ({ filters }) => stateContainer.transitions.set('filters', filters), - get: () => ({ filters: stateContainer.getState().filters }), - state$: stateContainer.state$.pipe(map((state) => ({ filters: state.filters }))), + set: ({ filters, query }) => { + stateContainer.transitions.set('filters', filters); + stateContainer.transitions.set('query', query); + }, + get: () => { + return { + filters: stateContainer.getState().filters, + query: stateContainer.getState().query, + }; + }, + state$: stateContainer.state$.pipe( + map((state) => ({ + filters: state.filters, + query: state.query, + })) + ), }, { filters: esFilters.FilterStateStore.APP_STATE, + query: true, } ); diff --git a/src/plugins/visualize/public/application/utils/utils.ts b/src/plugins/visualize/public/application/utils/utils.ts index 9f32da3f785b..532d87985a0b 100644 --- a/src/plugins/visualize/public/application/utils/utils.ts +++ b/src/plugins/visualize/public/application/utils/utils.ts @@ -20,7 +20,7 @@ import { i18n } from '@kbn/i18n'; import { ChromeStart, DocLinksStart } from 'kibana/public'; -import { Filter, UI_SETTINGS } from '../../../../data/public'; +import { Filter } from '../../../../data/public'; import { VisualizeServices, SavedVisInstance } from '../types'; export const addHelpMenuToAppChrome = (chrome: ChromeStart, docLinks: DocLinksStart) => { @@ -49,12 +49,9 @@ export const addBadgeToAppChrome = (chrome: ChromeStart) => { }); }; -export const getDefaultQuery = ({ localStorage, uiSettings }: VisualizeServices) => ({ - query: '', - language: - localStorage.get('kibana.userQueryLanguage') || - uiSettings.get(UI_SETTINGS.SEARCH_QUERY_LANGUAGE), -}); +export const getDefaultQuery = ({ data }: VisualizeServices) => { + return data.query.queryString.getDefaultQuery(); +}; export const visStateToEditorState = ( { vis, savedVis }: SavedVisInstance, diff --git a/test/functional/apps/visualize/_area_chart.js b/test/functional/apps/visualize/_area_chart.js index 4321f0df8925..9ac2160a359d 100644 --- a/test/functional/apps/visualize/_area_chart.js +++ b/test/functional/apps/visualize/_area_chart.js @@ -563,6 +563,10 @@ export default function ({ getService, getPageObjects }) { it('should display updated scaled label text after time range is changed', async () => { await PageObjects.visEditor.setInterval('Millisecond'); + + // Apply interval + await testSubjects.clickWhenNotDisabled('visualizeEditorRenderButton'); + const isHelperScaledLabelExists = await find.existsByCssSelector( '[data-test-subj="currentlyScaledText"]' ); diff --git a/x-pack/plugins/lens/public/app_plugin/app.test.tsx b/x-pack/plugins/lens/public/app_plugin/app.test.tsx index 3bd12a87456a..a72f4f429a1b 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.test.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.test.tsx @@ -95,6 +95,14 @@ function createMockFilterManager() { }; } +function createMockQueryString() { + return { + getQuery: jest.fn(() => ({ query: '', language: 'kuery' })), + setQuery: jest.fn(), + getDefaultQuery: jest.fn(() => ({ query: '', language: 'kuery' })), + }; +} + function createMockTimefilter() { const unsubscribe = jest.fn(); @@ -148,6 +156,7 @@ describe('Lens App', () => { timefilter: { timefilter: createMockTimefilter(), }, + queryString: createMockQueryString(), state$: new Observable(), }, indexPatterns: { diff --git a/x-pack/plugins/lens/public/app_plugin/app.tsx b/x-pack/plugins/lens/public/app_plugin/app.tsx index 082a3afcd513..2a7eaff32fa0 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.tsx @@ -36,7 +36,6 @@ import { IndexPattern as IndexPatternInstance, IndexPatternsContract, SavedQuery, - UI_SETTINGS, } from '../../../../../src/plugins/data/public'; interface State { @@ -83,17 +82,13 @@ export function App({ onAppLeave: AppMountParameters['onAppLeave']; history: History; }) { - const language = - storage.get('kibana.userQueryLanguage') || - core.uiSettings.get(UI_SETTINGS.SEARCH_QUERY_LANGUAGE); - const [state, setState] = useState(() => { const currentRange = data.query.timefilter.timefilter.getTime(); return { isLoading: !!docId, isSaveModalVisible: false, indexPatternsForTopNav: [], - query: { query: '', language }, + query: data.query.queryString.getDefaultQuery(), dateRange: { fromDate: currentRange.from, toDate: currentRange.to, @@ -473,12 +468,7 @@ export function App({ ...s, savedQuery: undefined, filters: data.query.filterManager.getGlobalFilters(), - query: { - query: '', - language: - storage.get('kibana.userQueryLanguage') || - core.uiSettings.get(UI_SETTINGS.SEARCH_QUERY_LANGUAGE), - }, + query: data.query.queryString.getDefaultQuery(), })); }} query={state.query} diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/filter_editor/filter_editor.js b/x-pack/plugins/maps/public/connected_components/layer_panel/filter_editor/filter_editor.js index 45c7507160e9..d2652fac5bd2 100644 --- a/x-pack/plugins/maps/public/connected_components/layer_panel/filter_editor/filter_editor.js +++ b/x-pack/plugins/maps/public/connected_components/layer_panel/filter_editor/filter_editor.js @@ -20,8 +20,7 @@ import { import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { UI_SETTINGS } from '../../../../../../../src/plugins/data/public'; -import { getIndexPatternService, getUiSettings, getData } from '../../../kibana_services'; +import { getIndexPatternService, getData } from '../../../kibana_services'; import { GlobalFilterCheckbox } from '../../../components/global_filter_checkbox'; export class FilterEditor extends Component { @@ -82,7 +81,6 @@ export class FilterEditor extends Component { _renderQueryPopover() { const layerQuery = this.props.layer.getQuery(); - const uiSettings = getUiSettings(); const { SearchBar } = getData().ui; return ( @@ -99,11 +97,7 @@ export class FilterEditor extends Component { showFilterBar={false} showDatePicker={false} showQueryInput={true} - query={ - layerQuery - ? layerQuery - : { language: uiSettings.get(UI_SETTINGS.SEARCH_QUERY_LANGUAGE), query: '' } - } + query={layerQuery ? layerQuery : getData().query.queryString.getDefaultQuery()} onQuerySubmit={this._onQueryChange} indexPatterns={this.state.indexPatterns} customSubmitButton={ diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/resources/where_expression.js b/x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/resources/where_expression.js index 8fdb71de2dfe..60151219a994 100644 --- a/x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/resources/where_expression.js +++ b/x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/resources/where_expression.js @@ -8,8 +8,7 @@ import React, { Component } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiButton, EuiPopover, EuiExpression, EuiFormHelpText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { UI_SETTINGS } from '../../../../../../../../src/plugins/data/public'; -import { getUiSettings, getData } from '../../../../kibana_services'; +import { getData } from '../../../../kibana_services'; export class WhereExpression extends Component { state = { @@ -77,11 +76,7 @@ export class WhereExpression extends Component { showFilterBar={false} showDatePicker={false} showQueryInput={true} - query={ - whereQuery - ? whereQuery - : { language: getUiSettings().get(UI_SETTINGS.SEARCH_QUERY_LANGUAGE), query: '' } - } + query={whereQuery ? whereQuery : getData().query.queryString.getDefaultQuery()} onQuerySubmit={this._onQueryChange} indexPatterns={[indexPattern]} customSubmitButton={ diff --git a/x-pack/plugins/maps/public/routing/bootstrap/get_initial_query.js b/x-pack/plugins/maps/public/routing/bootstrap/get_initial_query.js index dfc3a1c9de96..1f2cf2707762 100644 --- a/x-pack/plugins/maps/public/routing/bootstrap/get_initial_query.js +++ b/x-pack/plugins/maps/public/routing/bootstrap/get_initial_query.js @@ -4,12 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getUiSettings } from '../../kibana_services'; -import { UI_SETTINGS } from '../../../../../../src/plugins/data/public'; - -export function getInitialQuery({ mapStateJSON, appState = {}, userQueryLanguage }) { - const settings = getUiSettings(); +import { getData } from '../../kibana_services'; +export function getInitialQuery({ mapStateJSON, appState = {} }) { if (appState.query) { return appState.query; } @@ -21,8 +18,5 @@ export function getInitialQuery({ mapStateJSON, appState = {}, userQueryLanguage } } - return { - query: '', - language: userQueryLanguage || settings.get(UI_SETTINGS.SEARCH_QUERY_LANGUAGE), - }; + return getData().query.queryString.getDefaultQuery(); } diff --git a/x-pack/plugins/maps/public/routing/page_elements/top_nav_menu/top_nav_menu.js b/x-pack/plugins/maps/public/routing/page_elements/top_nav_menu/top_nav_menu.js index ac2dec0db59c..2340e3716547 100644 --- a/x-pack/plugins/maps/public/routing/page_elements/top_nav_menu/top_nav_menu.js +++ b/x-pack/plugins/maps/public/routing/page_elements/top_nav_menu/top_nav_menu.js @@ -14,7 +14,6 @@ import { getToasts, getCoreI18n, getData, - getUiSettings, } from '../../../kibana_services'; import { SavedObjectSaveModal, @@ -46,16 +45,13 @@ export function MapsTopNavMenu({ isOpenSettingsDisabled, }) { const { TopNavMenu } = getNavigation().ui; - const { filterManager } = getData().query; + const { filterManager, queryString } = getData().query; const showSaveQuery = getMapsCapabilities().saveQuery; const onClearSavedQuery = () => { onQuerySaved(undefined); onQueryChange({ filters: filterManager.getGlobalFilters(), - query: { - query: '', - language: getUiSettings().get('search:queryLanguage'), - }, + query: queryString.getDefaultQuery(), }); }; diff --git a/x-pack/plugins/maps/public/routing/routes/maps_app/maps_app_view.js b/x-pack/plugins/maps/public/routing/routes/maps_app/maps_app_view.js index aa7f24155ab4..bccfdbf2467d 100644 --- a/x-pack/plugins/maps/public/routing/routes/maps_app/maps_app_view.js +++ b/x-pack/plugins/maps/public/routing/routes/maps_app/maps_app_view.js @@ -13,7 +13,6 @@ import { getIndexPatternService, getToasts, getData, - getUiSettings, getCoreChrome, } from '../../../kibana_services'; import { copyPersistentState } from '../../../reducers/util'; @@ -274,6 +273,7 @@ export class MapsAppView extends React.Component { _initQueryTimeRefresh() { const { setRefreshConfig, savedMap } = this.props; + const { queryString } = getData().query; // TODO: Handle null when converting to TS const globalState = getGlobalState(); const mapStateJSON = savedMap ? savedMap.mapStateJSON : undefined; @@ -281,7 +281,6 @@ export class MapsAppView extends React.Component { query: getInitialQuery({ mapStateJSON, appState: this._appStateManager.getAppState(), - userQueryLanguage: getUiSettings().get('search:queryLanguage'), }), time: getInitialTimeFilters({ mapStateJSON, @@ -292,6 +291,8 @@ export class MapsAppView extends React.Component { globalState, }), }; + + if (newState.query) queryString.setQuery(newState.query); this.setState({ query: newState.query, time: newState.time }); updateGlobalState( { diff --git a/x-pack/plugins/maps/public/routing/state_syncing/app_sync.js b/x-pack/plugins/maps/public/routing/state_syncing/app_sync.js index 36b20174f243..69d6dbbe0c4d 100644 --- a/x-pack/plugins/maps/public/routing/state_syncing/app_sync.js +++ b/x-pack/plugins/maps/public/routing/state_syncing/app_sync.js @@ -31,6 +31,7 @@ export function useAppStateSyncing(appStateManager) { }; const stopSyncingQueryAppStateWithStateContainer = connectToQueryState(query, stateContainer, { filters: esFilters.FilterStateStore.APP_STATE, + query: true, }); // sets up syncing app state container with url