* [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 <dosantappdev@gmail.com> * 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 <anton.dosov@elastic.co> Co-authored-by: Anton Dosov <dosantappdev@gmail.com> Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> # Conflicts: # src/plugins/data/public/public.api.md * docs
This commit is contained in:
parent
9ed82273d6
commit
25ceed986b
|
@ -9,9 +9,10 @@ Helper to setup two-way syncing of global data and a state container
|
||||||
<b>Signature:</b>
|
<b>Signature:</b>
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
connectToQueryState: <S extends QueryState>({ timefilter: { timefilter }, filterManager, state$, }: Pick<QueryStart | QuerySetup, 'timefilter' | 'filterManager' | 'state$'>, stateContainer: BaseStateContainer<S>, syncConfig: {
|
connectToQueryState: <S extends QueryState>({ timefilter: { timefilter }, filterManager, queryString, state$, }: Pick<QueryStart | QuerySetup, 'timefilter' | 'filterManager' | 'queryString' | 'state$'>, stateContainer: BaseStateContainer<S>, syncConfig: {
|
||||||
time?: boolean;
|
time?: boolean;
|
||||||
refreshInterval?: boolean;
|
refreshInterval?: boolean;
|
||||||
filters?: FilterStateStore | boolean;
|
filters?: FilterStateStore | boolean;
|
||||||
|
query?: boolean;
|
||||||
}) => () => void
|
}) => () => void
|
||||||
```
|
```
|
||||||
|
|
|
@ -17,6 +17,7 @@ export interface QueryState
|
||||||
| Property | Type | Description |
|
| Property | Type | Description |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| [filters](./kibana-plugin-plugins-data-public.querystate.filters.md) | <code>Filter[]</code> | |
|
| [filters](./kibana-plugin-plugins-data-public.querystate.filters.md) | <code>Filter[]</code> | |
|
||||||
|
| [query](./kibana-plugin-plugins-data-public.querystate.query.md) | <code>Query</code> | |
|
||||||
| [refreshInterval](./kibana-plugin-plugins-data-public.querystate.refreshinterval.md) | <code>RefreshInterval</code> | |
|
| [refreshInterval](./kibana-plugin-plugins-data-public.querystate.refreshinterval.md) | <code>RefreshInterval</code> | |
|
||||||
| [time](./kibana-plugin-plugins-data-public.querystate.time.md) | <code>TimeRange</code> | |
|
| [time](./kibana-plugin-plugins-data-public.querystate.time.md) | <code>TimeRange</code> | |
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||||
|
|
||||||
|
[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
|
||||||
|
|
||||||
|
<b>Signature:</b>
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
query?: Query;
|
||||||
|
```
|
|
@ -9,7 +9,7 @@ Helper to setup syncing of global data with the URL
|
||||||
<b>Signature:</b>
|
<b>Signature:</b>
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
syncQueryStateWithUrl: (query: Pick<QueryStart | QuerySetup, 'filterManager' | 'timefilter' | 'state$'>, kbnUrlStateStorage: IKbnUrlStateStorage) => {
|
syncQueryStateWithUrl: (query: Pick<QueryStart | QuerySetup, 'filterManager' | 'timefilter' | 'queryString' | 'state$'>, kbnUrlStateStorage: IKbnUrlStateStorage) => {
|
||||||
stop: () => void;
|
stop: () => void;
|
||||||
hasInheritedQueryFromUrl: boolean;
|
hasInheritedQueryFromUrl: boolean;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useEffect, useRef, useState, useCallback } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import { History } from 'history';
|
import { History } from 'history';
|
||||||
import { FormattedMessage, I18nProvider } from '@kbn/i18n/react';
|
import { FormattedMessage, I18nProvider } from '@kbn/i18n/react';
|
||||||
import { Router } from 'react-router-dom';
|
import { Router } from 'react-router-dom';
|
||||||
|
@ -85,16 +85,9 @@ const App = ({ navigation, data, history, kbnUrlStateStorage }: StateDemoAppDeps
|
||||||
useGlobalStateSyncing(data.query, kbnUrlStateStorage);
|
useGlobalStateSyncing(data.query, kbnUrlStateStorage);
|
||||||
useAppStateSyncing(appStateContainer, data.query, kbnUrlStateStorage);
|
useAppStateSyncing(appStateContainer, data.query, kbnUrlStateStorage);
|
||||||
|
|
||||||
const onQuerySubmit = useCallback(
|
|
||||||
({ query }) => {
|
|
||||||
appStateContainer.set({ ...appState, query });
|
|
||||||
},
|
|
||||||
[appStateContainer, appState]
|
|
||||||
);
|
|
||||||
|
|
||||||
const indexPattern = useIndexPattern(data);
|
const indexPattern = useIndexPattern(data);
|
||||||
if (!indexPattern)
|
if (!indexPattern)
|
||||||
return <div>No index pattern found. Please create an intex patter before loading...</div>;
|
return <div>No index pattern found. Please create an index patter before loading...</div>;
|
||||||
|
|
||||||
// Render the application DOM.
|
// Render the application DOM.
|
||||||
// Note that `navigation.ui.TopNavMenu` is a stateful component exported on the `navigation` plugin's start contract.
|
// 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}
|
showSearchBar={true}
|
||||||
indexPatterns={[indexPattern]}
|
indexPatterns={[indexPattern]}
|
||||||
useDefaultBehaviors={true}
|
useDefaultBehaviors={true}
|
||||||
onQuerySubmit={onQuerySubmit}
|
|
||||||
query={appState.query}
|
|
||||||
showSaveQuery={true}
|
showSaveQuery={true}
|
||||||
/>
|
/>
|
||||||
<EuiPage restrictWidth="1000px">
|
<EuiPage restrictWidth="1000px">
|
||||||
|
@ -200,7 +191,7 @@ function useAppStateSyncing<AppState extends QueryState>(
|
||||||
const stopSyncingQueryAppStateWithStateContainer = connectToQueryState(
|
const stopSyncingQueryAppStateWithStateContainer = connectToQueryState(
|
||||||
query,
|
query,
|
||||||
appStateContainer,
|
appStateContainer,
|
||||||
{ filters: esFilters.FilterStateStore.APP_STATE }
|
{ filters: esFilters.FilterStateStore.APP_STATE, query: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
// sets up syncing app state container with url
|
// sets up syncing app state container with url
|
||||||
|
|
|
@ -52,7 +52,10 @@ export interface DashboardAppScope extends ng.IScope {
|
||||||
expandedPanel?: string;
|
expandedPanel?: string;
|
||||||
getShouldShowEditHelp: () => boolean;
|
getShouldShowEditHelp: () => boolean;
|
||||||
getShouldShowViewHelp: () => boolean;
|
getShouldShowViewHelp: () => boolean;
|
||||||
updateQueryAndFetch: ({ query, dateRange }: { query: Query; dateRange?: TimeRange }) => void;
|
handleRefresh: (
|
||||||
|
{ query, dateRange }: { query?: Query; dateRange: TimeRange },
|
||||||
|
isUpdate?: boolean
|
||||||
|
) => void;
|
||||||
topNavMenu: any;
|
topNavMenu: any;
|
||||||
showAddPanel: any;
|
showAddPanel: any;
|
||||||
showSaveQuery: boolean;
|
showSaveQuery: boolean;
|
||||||
|
|
|
@ -25,12 +25,11 @@ import React, { useState, ReactElement } from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import angular from 'angular';
|
import angular from 'angular';
|
||||||
|
|
||||||
import { Observable, pipe, Subscription } from 'rxjs';
|
import { Observable, pipe, Subscription, merge } from 'rxjs';
|
||||||
import { filter, map, mapTo, startWith, switchMap } from 'rxjs/operators';
|
import { filter, map, debounceTime, mapTo, startWith, switchMap } from 'rxjs/operators';
|
||||||
import { History } from 'history';
|
import { History } from 'history';
|
||||||
import { SavedObjectSaveOpts } from 'src/plugins/saved_objects/public';
|
import { SavedObjectSaveOpts } from 'src/plugins/saved_objects/public';
|
||||||
import { NavigationPublicPluginStart as NavigationStart } from 'src/plugins/navigation/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 { DashboardEmptyScreen, DashboardEmptyScreenProps } from './dashboard_empty_screen';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -38,11 +37,9 @@ import {
|
||||||
esFilters,
|
esFilters,
|
||||||
IndexPattern,
|
IndexPattern,
|
||||||
IndexPatternsContract,
|
IndexPatternsContract,
|
||||||
Query,
|
|
||||||
QueryState,
|
QueryState,
|
||||||
SavedQuery,
|
SavedQuery,
|
||||||
syncQueryStateWithUrl,
|
syncQueryStateWithUrl,
|
||||||
UI_SETTINGS,
|
|
||||||
} from '../../../data/public';
|
} from '../../../data/public';
|
||||||
import { getSavedObjectFinder, SaveResult, showSaveModal } from '../../../saved_objects/public';
|
import { getSavedObjectFinder, SaveResult, showSaveModal } from '../../../saved_objects/public';
|
||||||
|
|
||||||
|
@ -81,8 +78,8 @@ import {
|
||||||
addFatalError,
|
addFatalError,
|
||||||
AngularHttpError,
|
AngularHttpError,
|
||||||
KibanaLegacyStart,
|
KibanaLegacyStart,
|
||||||
migrateLegacyQuery,
|
|
||||||
subscribeWithScope,
|
subscribeWithScope,
|
||||||
|
migrateLegacyQuery,
|
||||||
} from '../../../kibana_legacy/public';
|
} from '../../../kibana_legacy/public';
|
||||||
|
|
||||||
export interface DashboardAppControllerDependencies extends RenderDeps {
|
export interface DashboardAppControllerDependencies extends RenderDeps {
|
||||||
|
@ -127,7 +124,6 @@ export class DashboardAppController {
|
||||||
$route,
|
$route,
|
||||||
$routeParams,
|
$routeParams,
|
||||||
dashboardConfig,
|
dashboardConfig,
|
||||||
localStorage,
|
|
||||||
indexPatterns,
|
indexPatterns,
|
||||||
savedQueryService,
|
savedQueryService,
|
||||||
embeddable,
|
embeddable,
|
||||||
|
@ -153,8 +149,8 @@ export class DashboardAppController {
|
||||||
navigation,
|
navigation,
|
||||||
}: DashboardAppControllerDependencies) {
|
}: DashboardAppControllerDependencies) {
|
||||||
const filterManager = queryService.filterManager;
|
const filterManager = queryService.filterManager;
|
||||||
const queryFilter = filterManager;
|
|
||||||
const timefilter = queryService.timefilter.timefilter;
|
const timefilter = queryService.timefilter.timefilter;
|
||||||
|
const queryStringManager = queryService.queryString;
|
||||||
const isEmbeddedExternally = Boolean($routeParams.embed);
|
const isEmbeddedExternally = Boolean($routeParams.embed);
|
||||||
|
|
||||||
// url param rules should only apply when embedded (e.g. url?embed=true)
|
// 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
|
// sync initial app filters from state to filterManager
|
||||||
// if there is an existing similar global filter, then leave it as global
|
// if there is an existing similar global filter, then leave it as global
|
||||||
filterManager.setAppFilters(_.cloneDeep(dashboardStateManager.appState.filters));
|
filterManager.setAppFilters(_.cloneDeep(dashboardStateManager.appState.filters));
|
||||||
|
queryStringManager.setQuery(migrateLegacyQuery(dashboardStateManager.appState.query));
|
||||||
|
|
||||||
// setup syncing of app filters between appState and filterManager
|
// setup syncing of app filters between appState and filterManager
|
||||||
const stopSyncingAppFilters = connectToQueryState(
|
const stopSyncingAppFilters = connectToQueryState(
|
||||||
queryService,
|
queryService,
|
||||||
{
|
{
|
||||||
set: ({ filters }) => dashboardStateManager.setFilters(filters || []),
|
set: ({ filters, query }) => {
|
||||||
get: () => ({ filters: dashboardStateManager.appState.filters }),
|
dashboardStateManager.setFilters(filters || []);
|
||||||
|
dashboardStateManager.setQuery(query || queryStringManager.getDefaultQuery());
|
||||||
|
},
|
||||||
|
get: () => ({
|
||||||
|
filters: dashboardStateManager.appState.filters,
|
||||||
|
query: dashboardStateManager.getQuery(),
|
||||||
|
}),
|
||||||
state$: dashboardStateManager.appState$.pipe(
|
state$: dashboardStateManager.appState$.pipe(
|
||||||
map((state) => ({
|
map((state) => ({
|
||||||
filters: state.filters,
|
filters: state.filters,
|
||||||
|
query: queryStringManager.formatQuery(state.query),
|
||||||
}))
|
}))
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
filters: esFilters.FilterStateStore.APP_STATE,
|
filters: esFilters.FilterStateStore.APP_STATE,
|
||||||
|
query: true,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -331,7 +337,7 @@ export class DashboardAppController {
|
||||||
const isEmptyInReadonlyMode = shouldShowUnauthorizedEmptyState();
|
const isEmptyInReadonlyMode = shouldShowUnauthorizedEmptyState();
|
||||||
return {
|
return {
|
||||||
id: dashboardStateManager.savedDashboard.id || '',
|
id: dashboardStateManager.savedDashboard.id || '',
|
||||||
filters: queryFilter.getFilters(),
|
filters: filterManager.getFilters(),
|
||||||
hidePanelTitles: dashboardStateManager.getHidePanelTitles(),
|
hidePanelTitles: dashboardStateManager.getHidePanelTitles(),
|
||||||
query: $scope.model.query,
|
query: $scope.model.query,
|
||||||
timeRange: {
|
timeRange: {
|
||||||
|
@ -356,7 +362,7 @@ export class DashboardAppController {
|
||||||
// https://github.com/angular/angular.js/wiki/Understanding-Scopes
|
// https://github.com/angular/angular.js/wiki/Understanding-Scopes
|
||||||
$scope.model = {
|
$scope.model = {
|
||||||
query: dashboardStateManager.getQuery(),
|
query: dashboardStateManager.getQuery(),
|
||||||
filters: queryFilter.getFilters(),
|
filters: filterManager.getFilters(),
|
||||||
timeRestore: dashboardStateManager.getTimeRestore(),
|
timeRestore: dashboardStateManager.getTimeRestore(),
|
||||||
title: dashboardStateManager.getTitle(),
|
title: dashboardStateManager.getTitle(),
|
||||||
description: dashboardStateManager.getDescription(),
|
description: dashboardStateManager.getDescription(),
|
||||||
|
@ -420,12 +426,12 @@ export class DashboardAppController {
|
||||||
if (
|
if (
|
||||||
!esFilters.compareFilters(
|
!esFilters.compareFilters(
|
||||||
container.getInput().filters,
|
container.getInput().filters,
|
||||||
queryFilter.getFilters(),
|
filterManager.getFilters(),
|
||||||
esFilters.COMPARE_ALL_OPTIONS
|
esFilters.COMPARE_ALL_OPTIONS
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
// Add filters modifies the object passed to it, hence the clone deep.
|
// 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(
|
dashboardStateManager.applyFilters(
|
||||||
$scope.model.query,
|
$scope.model.query,
|
||||||
|
@ -487,13 +493,8 @@ export class DashboardAppController {
|
||||||
});
|
});
|
||||||
|
|
||||||
dashboardStateManager.applyFilters(
|
dashboardStateManager.applyFilters(
|
||||||
dashboardStateManager.getQuery() || {
|
dashboardStateManager.getQuery() || queryStringManager.getDefaultQuery(),
|
||||||
query: '',
|
filterManager.getFilters()
|
||||||
language:
|
|
||||||
localStorage.get('kibana.userQueryLanguage') ||
|
|
||||||
uiSettings.get(UI_SETTINGS.SEARCH_QUERY_LANGUAGE),
|
|
||||||
},
|
|
||||||
queryFilter.getFilters()
|
|
||||||
);
|
);
|
||||||
|
|
||||||
timefilter.disableTimeRangeSelector();
|
timefilter.disableTimeRangeSelector();
|
||||||
|
@ -567,21 +568,13 @@ export class DashboardAppController {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.updateQueryAndFetch = function ({ query, dateRange }) {
|
$scope.handleRefresh = function (_payload, isUpdate) {
|
||||||
if (dateRange) {
|
if (isUpdate === false) {
|
||||||
timefilter.setTime(dateRange);
|
|
||||||
}
|
|
||||||
|
|
||||||
const oldQuery = $scope.model.query;
|
|
||||||
if (_.isEqual(oldQuery, query)) {
|
|
||||||
// The user can still request a reload in the query bar, even if the
|
// 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
|
// query is the same, and in that case, we have to explicitly ask for
|
||||||
// a reload, since no state changes will cause it.
|
// a reload, since no state changes will cause it.
|
||||||
lastReloadRequestTime = new Date().getTime();
|
lastReloadRequestTime = new Date().getTime();
|
||||||
refreshDashboardContainer();
|
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.
|
// Making this method sync broke the updates.
|
||||||
// Temporary fix, until we fix the complex state in this file.
|
// Temporary fix, until we fix the complex state in this file.
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
queryFilter.setFilters(allFilters);
|
filterManager.setFilters(allFilters);
|
||||||
}, 0);
|
}, 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -633,11 +626,6 @@ export class DashboardAppController {
|
||||||
|
|
||||||
$scope.indexPatterns = [];
|
$scope.indexPatterns = [];
|
||||||
|
|
||||||
$scope.$watch('model.query', (newQuery: Query) => {
|
|
||||||
const query = migrateLegacyQuery(newQuery) as Query;
|
|
||||||
$scope.updateQueryAndFetch({ query });
|
|
||||||
});
|
|
||||||
|
|
||||||
$scope.$watch(
|
$scope.$watch(
|
||||||
() => dashboardCapabilities.saveQuery,
|
() => dashboardCapabilities.saveQuery,
|
||||||
(newCapability) => {
|
(newCapability) => {
|
||||||
|
@ -678,18 +666,11 @@ export class DashboardAppController {
|
||||||
showFilterBar,
|
showFilterBar,
|
||||||
indexPatterns: $scope.indexPatterns,
|
indexPatterns: $scope.indexPatterns,
|
||||||
showSaveQuery: $scope.showSaveQuery,
|
showSaveQuery: $scope.showSaveQuery,
|
||||||
query: $scope.model.query,
|
|
||||||
savedQuery: $scope.savedQuery,
|
savedQuery: $scope.savedQuery,
|
||||||
onSavedQueryIdChange,
|
onSavedQueryIdChange,
|
||||||
savedQueryId: dashboardStateManager.getSavedQueryId(),
|
savedQueryId: dashboardStateManager.getSavedQueryId(),
|
||||||
useDefaultBehaviors: true,
|
useDefaultBehaviors: true,
|
||||||
onQuerySubmit: (payload: { dateRange: TimeRange; query?: Query }): void => {
|
onQuerySubmit: $scope.handleRefresh,
|
||||||
if (!payload.query) {
|
|
||||||
$scope.updateQueryAndFetch({ query: $scope.model.query, dateRange: payload.dateRange });
|
|
||||||
} else {
|
|
||||||
$scope.updateQueryAndFetch({ query: payload.query, dateRange: payload.dateRange });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
const dashboardNavBar = document.getElementById('dashboardChrome');
|
const dashboardNavBar = document.getElementById('dashboardChrome');
|
||||||
|
@ -704,25 +685,11 @@ export class DashboardAppController {
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.timefilterSubscriptions$ = new Subscription();
|
$scope.timefilterSubscriptions$ = new Subscription();
|
||||||
|
const timeChanges$ = merge(timefilter.getRefreshIntervalUpdate$(), timefilter.getTimeUpdate$());
|
||||||
$scope.timefilterSubscriptions$.add(
|
$scope.timefilterSubscriptions$.add(
|
||||||
subscribeWithScope(
|
subscribeWithScope(
|
||||||
$scope,
|
$scope,
|
||||||
timefilter.getRefreshIntervalUpdate$(),
|
timeChanges$,
|
||||||
{
|
|
||||||
next: () => {
|
|
||||||
updateState();
|
|
||||||
refreshDashboardContainer();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
(error: AngularHttpError | Error | string) => addFatalError(fatalErrors, error)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
$scope.timefilterSubscriptions$.add(
|
|
||||||
subscribeWithScope(
|
|
||||||
$scope,
|
|
||||||
timefilter.getTimeUpdate$(),
|
|
||||||
{
|
{
|
||||||
next: () => {
|
next: () => {
|
||||||
updateState();
|
updateState();
|
||||||
|
@ -1095,13 +1062,21 @@ export class DashboardAppController {
|
||||||
|
|
||||||
updateViewMode(dashboardStateManager.getViewMode());
|
updateViewMode(dashboardStateManager.getViewMode());
|
||||||
|
|
||||||
|
const filterChanges = merge(filterManager.getUpdates$(), queryStringManager.getUpdates$()).pipe(
|
||||||
|
debounceTime(100)
|
||||||
|
);
|
||||||
|
|
||||||
// update root source when filters update
|
// update root source when filters update
|
||||||
const updateSubscription = queryFilter.getUpdates$().subscribe({
|
const updateSubscription = filterChanges.subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
$scope.model.filters = queryFilter.getFilters();
|
$scope.model.filters = filterManager.getFilters();
|
||||||
|
$scope.model.query = queryStringManager.getQuery();
|
||||||
dashboardStateManager.applyFilters($scope.model.query, $scope.model.filters);
|
dashboardStateManager.applyFilters($scope.model.query, $scope.model.filters);
|
||||||
if (dashboardContainer) {
|
if (dashboardContainer) {
|
||||||
dashboardContainer.updateInput({ filters: $scope.model.filters });
|
dashboardContainer.updateInput({
|
||||||
|
filters: $scope.model.filters,
|
||||||
|
query: $scope.model.query,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -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)
|
// Warning: (ae-missing-release-tag) "connectToQueryState" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||||
//
|
//
|
||||||
// @public
|
// @public
|
||||||
export const connectToQueryState: <S extends QueryState>({ timefilter: { timefilter }, filterManager, state$, }: Pick<QueryStart | QuerySetup, 'timefilter' | 'filterManager' | 'state$'>, stateContainer: BaseStateContainer<S>, syncConfig: {
|
export const connectToQueryState: <S extends QueryState>({ timefilter: { timefilter }, filterManager, queryString, state$, }: Pick<QueryStart | QuerySetup, 'timefilter' | 'filterManager' | 'queryString' | 'state$'>, stateContainer: BaseStateContainer<S>, syncConfig: {
|
||||||
time?: boolean;
|
time?: boolean;
|
||||||
refreshInterval?: boolean;
|
refreshInterval?: boolean;
|
||||||
filters?: FilterStateStore | boolean;
|
filters?: FilterStateStore | boolean;
|
||||||
|
query?: boolean;
|
||||||
}) => () => void;
|
}) => () => void;
|
||||||
|
|
||||||
// Warning: (ae-missing-release-tag) "createSavedQueryService" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
// 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)
|
// (undocumented)
|
||||||
filters?: Filter[];
|
filters?: Filter[];
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
|
query?: Query;
|
||||||
|
// (undocumented)
|
||||||
refreshInterval?: RefreshInterval;
|
refreshInterval?: RefreshInterval;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
time?: TimeRange;
|
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)
|
// Warning: (ae-missing-release-tag) "syncQueryStateWithUrl" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||||
//
|
//
|
||||||
// @public
|
// @public
|
||||||
export const syncQueryStateWithUrl: (query: Pick<QueryStart | QuerySetup, 'filterManager' | 'timefilter' | 'state$'>, kbnUrlStateStorage: IKbnUrlStateStorage) => {
|
export const syncQueryStateWithUrl: (query: Pick<QueryStart | QuerySetup, 'filterManager' | 'timefilter' | 'queryString' | 'state$'>, kbnUrlStateStorage: IKbnUrlStateStorage) => {
|
||||||
stop: () => void;
|
stop: () => void;
|
||||||
hasInheritedQueryFromUrl: boolean;
|
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: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: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/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: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: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
|
// 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
|
||||||
|
|
|
@ -21,6 +21,7 @@ import { Observable } from 'rxjs';
|
||||||
import { QueryService, QuerySetup, QueryStart } from '.';
|
import { QueryService, QuerySetup, QueryStart } from '.';
|
||||||
import { timefilterServiceMock } from './timefilter/timefilter_service.mock';
|
import { timefilterServiceMock } from './timefilter/timefilter_service.mock';
|
||||||
import { createFilterManagerMock } from './filter_manager/filter_manager.mock';
|
import { createFilterManagerMock } from './filter_manager/filter_manager.mock';
|
||||||
|
import { queryStringManagerMock } from './query_string/query_string_manager.mock';
|
||||||
|
|
||||||
type QueryServiceClientContract = PublicMethodsOf<QueryService>;
|
type QueryServiceClientContract = PublicMethodsOf<QueryService>;
|
||||||
|
|
||||||
|
@ -28,6 +29,7 @@ const createSetupContractMock = () => {
|
||||||
const setupContract: jest.Mocked<QuerySetup> = {
|
const setupContract: jest.Mocked<QuerySetup> = {
|
||||||
filterManager: createFilterManagerMock(),
|
filterManager: createFilterManagerMock(),
|
||||||
timefilter: timefilterServiceMock.createSetupContract(),
|
timefilter: timefilterServiceMock.createSetupContract(),
|
||||||
|
queryString: queryStringManagerMock.createSetupContract(),
|
||||||
state$: new Observable(),
|
state$: new Observable(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -38,6 +40,7 @@ const createStartContractMock = () => {
|
||||||
const startContract: jest.Mocked<QueryStart> = {
|
const startContract: jest.Mocked<QueryStart> = {
|
||||||
addToQueryLog: jest.fn(),
|
addToQueryLog: jest.fn(),
|
||||||
filterManager: createFilterManagerMock(),
|
filterManager: createFilterManagerMock(),
|
||||||
|
queryString: queryStringManagerMock.createStartContract(),
|
||||||
savedQueries: jest.fn() as any,
|
savedQueries: jest.fn() as any,
|
||||||
state$: new Observable(),
|
state$: new Observable(),
|
||||||
timefilter: timefilterServiceMock.createStartContract(),
|
timefilter: timefilterServiceMock.createStartContract(),
|
||||||
|
|
|
@ -25,6 +25,7 @@ import { createAddToQueryLog } from './lib';
|
||||||
import { TimefilterService, TimefilterSetup } from './timefilter';
|
import { TimefilterService, TimefilterSetup } from './timefilter';
|
||||||
import { createSavedQueryService } from './saved_query/saved_query_service';
|
import { createSavedQueryService } from './saved_query/saved_query_service';
|
||||||
import { createQueryStateObservable } from './state_sync/create_global_query_observable';
|
import { createQueryStateObservable } from './state_sync/create_global_query_observable';
|
||||||
|
import { QueryStringManager, QueryStringContract } from './query_string';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Query Service
|
* Query Service
|
||||||
|
@ -45,6 +46,7 @@ interface QueryServiceStartDependencies {
|
||||||
export class QueryService {
|
export class QueryService {
|
||||||
filterManager!: FilterManager;
|
filterManager!: FilterManager;
|
||||||
timefilter!: TimefilterSetup;
|
timefilter!: TimefilterSetup;
|
||||||
|
queryStringManager!: QueryStringContract;
|
||||||
|
|
||||||
state$!: ReturnType<typeof createQueryStateObservable>;
|
state$!: ReturnType<typeof createQueryStateObservable>;
|
||||||
|
|
||||||
|
@ -57,14 +59,18 @@ export class QueryService {
|
||||||
storage,
|
storage,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.queryStringManager = new QueryStringManager(storage, uiSettings);
|
||||||
|
|
||||||
this.state$ = createQueryStateObservable({
|
this.state$ = createQueryStateObservable({
|
||||||
filterManager: this.filterManager,
|
filterManager: this.filterManager,
|
||||||
timefilter: this.timefilter,
|
timefilter: this.timefilter,
|
||||||
|
queryString: this.queryStringManager,
|
||||||
}).pipe(share());
|
}).pipe(share());
|
||||||
|
|
||||||
return {
|
return {
|
||||||
filterManager: this.filterManager,
|
filterManager: this.filterManager,
|
||||||
timefilter: this.timefilter,
|
timefilter: this.timefilter,
|
||||||
|
queryString: this.queryStringManager,
|
||||||
state$: this.state$,
|
state$: this.state$,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -76,6 +82,7 @@ export class QueryService {
|
||||||
uiSettings,
|
uiSettings,
|
||||||
}),
|
}),
|
||||||
filterManager: this.filterManager,
|
filterManager: this.filterManager,
|
||||||
|
queryString: this.queryStringManager,
|
||||||
savedQueries: createSavedQueryService(savedObjectsClient),
|
savedQueries: createSavedQueryService(savedObjectsClient),
|
||||||
state$: this.state$,
|
state$: this.state$,
|
||||||
timefilter: this.timefilter,
|
timefilter: this.timefilter,
|
||||||
|
|
20
src/plugins/data/public/query/query_string/index.ts
Normal file
20
src/plugins/data/public/query/query_string/index.ts
Normal file
|
@ -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';
|
|
@ -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<QueryStringContract> = {
|
||||||
|
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,
|
||||||
|
};
|
|
@ -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<Query>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly storage: IStorageWrapper,
|
||||||
|
private readonly uiSettings: CoreStart['uiSettings']
|
||||||
|
) {
|
||||||
|
this.query$ = new BehaviorSubject<Query>(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<QueryStringManager>;
|
|
@ -48,6 +48,8 @@ setupMock.uiSettings.get.mockImplementation((key: string) => {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case UI_SETTINGS.FILTERS_PINNED_BY_DEFAULT:
|
case UI_SETTINGS.FILTERS_PINNED_BY_DEFAULT:
|
||||||
return true;
|
return true;
|
||||||
|
case UI_SETTINGS.SEARCH_QUERY_LANGUAGE:
|
||||||
|
return 'kuery';
|
||||||
case 'timepicker:timeDefaults':
|
case 'timepicker:timeDefaults':
|
||||||
return { from: 'now-15m', to: 'now' };
|
return { from: 'now-15m', to: 'now' };
|
||||||
case UI_SETTINGS.TIMEPICKER_REFRESH_INTERVAL_DEFAULTS:
|
case UI_SETTINGS.TIMEPICKER_REFRESH_INTERVAL_DEFAULTS:
|
||||||
|
|
|
@ -35,15 +35,24 @@ export const connectToQueryState = <S extends QueryState>(
|
||||||
{
|
{
|
||||||
timefilter: { timefilter },
|
timefilter: { timefilter },
|
||||||
filterManager,
|
filterManager,
|
||||||
|
queryString,
|
||||||
state$,
|
state$,
|
||||||
}: Pick<QueryStart | QuerySetup, 'timefilter' | 'filterManager' | 'state$'>,
|
}: Pick<QueryStart | QuerySetup, 'timefilter' | 'filterManager' | 'queryString' | 'state$'>,
|
||||||
stateContainer: BaseStateContainer<S>,
|
stateContainer: BaseStateContainer<S>,
|
||||||
syncConfig: { time?: boolean; refreshInterval?: boolean; filters?: FilterStateStore | boolean }
|
syncConfig: {
|
||||||
|
time?: boolean;
|
||||||
|
refreshInterval?: boolean;
|
||||||
|
filters?: FilterStateStore | boolean;
|
||||||
|
query?: boolean;
|
||||||
|
}
|
||||||
) => {
|
) => {
|
||||||
const syncKeys: Array<keyof QueryStateChange> = [];
|
const syncKeys: Array<keyof QueryStateChange> = [];
|
||||||
if (syncConfig.time) {
|
if (syncConfig.time) {
|
||||||
syncKeys.push('time');
|
syncKeys.push('time');
|
||||||
}
|
}
|
||||||
|
if (syncConfig.query) {
|
||||||
|
syncKeys.push('query');
|
||||||
|
}
|
||||||
if (syncConfig.refreshInterval) {
|
if (syncConfig.refreshInterval) {
|
||||||
syncKeys.push('refreshInterval');
|
syncKeys.push('refreshInterval');
|
||||||
}
|
}
|
||||||
|
@ -133,6 +142,9 @@ export const connectToQueryState = <S extends QueryState>(
|
||||||
if (syncConfig.time && changes.time) {
|
if (syncConfig.time && changes.time) {
|
||||||
newState.time = timefilter.getTime();
|
newState.time = timefilter.getTime();
|
||||||
}
|
}
|
||||||
|
if (syncConfig.query && changes.query) {
|
||||||
|
newState.query = queryString.getQuery();
|
||||||
|
}
|
||||||
if (syncConfig.refreshInterval && changes.refreshInterval) {
|
if (syncConfig.refreshInterval && changes.refreshInterval) {
|
||||||
newState.refreshInterval = timefilter.getRefreshInterval();
|
newState.refreshInterval = timefilter.getRefreshInterval();
|
||||||
}
|
}
|
||||||
|
@ -173,6 +185,13 @@ export const connectToQueryState = <S extends QueryState>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (syncConfig.query) {
|
||||||
|
const curQuery = state.query || queryString.getQuery();
|
||||||
|
if (!_.isEqual(curQuery, queryString.getQuery())) {
|
||||||
|
queryString.setQuery(_.cloneDeep(curQuery));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (syncConfig.filters) {
|
if (syncConfig.filters) {
|
||||||
const filters = state.filters || [];
|
const filters = state.filters || [];
|
||||||
if (syncConfig.filters === true) {
|
if (syncConfig.filters === true) {
|
||||||
|
|
|
@ -24,23 +24,31 @@ import { FilterManager } from '../filter_manager';
|
||||||
import { QueryState, QueryStateChange } from './index';
|
import { QueryState, QueryStateChange } from './index';
|
||||||
import { createStateContainer } from '../../../../kibana_utils/public';
|
import { createStateContainer } from '../../../../kibana_utils/public';
|
||||||
import { isFilterPinned, compareFilters, COMPARE_ALL_OPTIONS } from '../../../common';
|
import { isFilterPinned, compareFilters, COMPARE_ALL_OPTIONS } from '../../../common';
|
||||||
|
import { QueryStringContract } from '../query_string';
|
||||||
|
|
||||||
export function createQueryStateObservable({
|
export function createQueryStateObservable({
|
||||||
timefilter: { timefilter },
|
timefilter: { timefilter },
|
||||||
filterManager,
|
filterManager,
|
||||||
|
queryString,
|
||||||
}: {
|
}: {
|
||||||
timefilter: TimefilterSetup;
|
timefilter: TimefilterSetup;
|
||||||
filterManager: FilterManager;
|
filterManager: FilterManager;
|
||||||
|
queryString: QueryStringContract;
|
||||||
}): Observable<{ changes: QueryStateChange; state: QueryState }> {
|
}): Observable<{ changes: QueryStateChange; state: QueryState }> {
|
||||||
return new Observable((subscriber) => {
|
return new Observable((subscriber) => {
|
||||||
const state = createStateContainer<QueryState>({
|
const state = createStateContainer<QueryState>({
|
||||||
time: timefilter.getTime(),
|
time: timefilter.getTime(),
|
||||||
refreshInterval: timefilter.getRefreshInterval(),
|
refreshInterval: timefilter.getRefreshInterval(),
|
||||||
filters: filterManager.getFilters(),
|
filters: filterManager.getFilters(),
|
||||||
|
query: queryString.getQuery(),
|
||||||
});
|
});
|
||||||
|
|
||||||
let currentChange: QueryStateChange = {};
|
let currentChange: QueryStateChange = {};
|
||||||
const subs: Subscription[] = [
|
const subs: Subscription[] = [
|
||||||
|
queryString.getUpdates$().subscribe(() => {
|
||||||
|
currentChange.query = true;
|
||||||
|
state.set({ ...state.get(), query: queryString.getQuery() });
|
||||||
|
}),
|
||||||
timefilter.getTimeUpdate$().subscribe(() => {
|
timefilter.getTimeUpdate$().subscribe(() => {
|
||||||
currentChange.time = true;
|
currentChange.time = true;
|
||||||
state.set({ ...state.get(), time: timefilter.getTime() });
|
state.set({ ...state.get(), time: timefilter.getTime() });
|
||||||
|
|
|
@ -43,6 +43,8 @@ setupMock.uiSettings.get.mockImplementation((key: string) => {
|
||||||
return true;
|
return true;
|
||||||
case 'timepicker:timeDefaults':
|
case 'timepicker:timeDefaults':
|
||||||
return { from: 'now-15m', to: 'now' };
|
return { from: 'now-15m', to: 'now' };
|
||||||
|
case 'search:queryLanguage':
|
||||||
|
return 'kuery';
|
||||||
case UI_SETTINGS.TIMEPICKER_REFRESH_INTERVAL_DEFAULTS:
|
case UI_SETTINGS.TIMEPICKER_REFRESH_INTERVAL_DEFAULTS:
|
||||||
return { pause: false, value: 0 };
|
return { pause: false, value: 0 };
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -35,7 +35,7 @@ const GLOBAL_STATE_STORAGE_KEY = '_g';
|
||||||
* @param kbnUrlStateStorage to use for syncing
|
* @param kbnUrlStateStorage to use for syncing
|
||||||
*/
|
*/
|
||||||
export const syncQueryStateWithUrl = (
|
export const syncQueryStateWithUrl = (
|
||||||
query: Pick<QueryStart | QuerySetup, 'filterManager' | 'timefilter' | 'state$'>,
|
query: Pick<QueryStart | QuerySetup, 'filterManager' | 'timefilter' | 'queryString' | 'state$'>,
|
||||||
kbnUrlStateStorage: IKbnUrlStateStorage
|
kbnUrlStateStorage: IKbnUrlStateStorage
|
||||||
) => {
|
) => {
|
||||||
const {
|
const {
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Filter, RefreshInterval, TimeRange } from '../../../common';
|
import { Filter, RefreshInterval, TimeRange, Query } from '../../../common';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All query state service state
|
* All query state service state
|
||||||
|
@ -26,6 +26,7 @@ export interface QueryState {
|
||||||
time?: TimeRange;
|
time?: TimeRange;
|
||||||
refreshInterval?: RefreshInterval;
|
refreshInterval?: RefreshInterval;
|
||||||
filters?: Filter[];
|
filters?: Filter[];
|
||||||
|
query?: Query;
|
||||||
}
|
}
|
||||||
|
|
||||||
type QueryStateChangePartial = {
|
type QueryStateChangePartial = {
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, { useState, useEffect, useRef } from 'react';
|
import React, { useEffect, useRef } from 'react';
|
||||||
import { CoreStart } from 'src/core/public';
|
import { CoreStart } from 'src/core/public';
|
||||||
import { IStorageWrapper } from 'src/plugins/kibana_utils/public';
|
import { IStorageWrapper } from 'src/plugins/kibana_utils/public';
|
||||||
import { KibanaContextProvider } from '../../../../kibana_react/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 { useTimefilter } from './lib/use_timefilter';
|
||||||
import { useSavedQuery } from './lib/use_saved_query';
|
import { useSavedQuery } from './lib/use_saved_query';
|
||||||
import { DataPublicPluginStart } from '../../types';
|
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 {
|
interface StatefulSearchBarDeps {
|
||||||
core: CoreStart;
|
core: CoreStart;
|
||||||
|
@ -65,8 +66,7 @@ const defaultOnRefreshChange = (queryService: QueryStart) => {
|
||||||
const defaultOnQuerySubmit = (
|
const defaultOnQuerySubmit = (
|
||||||
props: StatefulSearchBarProps,
|
props: StatefulSearchBarProps,
|
||||||
queryService: QueryStart,
|
queryService: QueryStart,
|
||||||
currentQuery: Query,
|
currentQuery: Query
|
||||||
setQueryStringState: Function
|
|
||||||
) => {
|
) => {
|
||||||
if (!props.useDefaultBehaviors) return props.onQuerySubmit;
|
if (!props.useDefaultBehaviors) return props.onQuerySubmit;
|
||||||
|
|
||||||
|
@ -78,7 +78,11 @@ const defaultOnQuerySubmit = (
|
||||||
!_.isEqual(payload.query, currentQuery);
|
!_.isEqual(payload.query, currentQuery);
|
||||||
if (isUpdate) {
|
if (isUpdate) {
|
||||||
timefilter.setTime(payload.dateRange);
|
timefilter.setTime(payload.dateRange);
|
||||||
setQueryStringState(payload.query);
|
if (payload.query) {
|
||||||
|
queryService.queryString.setQuery(payload.query);
|
||||||
|
} else {
|
||||||
|
queryService.queryString.clearQuery();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Refresh button triggered for an update
|
// Refresh button triggered for an update
|
||||||
if (props.onQuerySubmit)
|
if (props.onQuerySubmit)
|
||||||
|
@ -121,30 +125,7 @@ export function createSearchBar({ core, storage, data }: StatefulSearchBarDeps)
|
||||||
return (props: StatefulSearchBarProps) => {
|
return (props: StatefulSearchBarProps) => {
|
||||||
const { useDefaultBehaviors } = props;
|
const { useDefaultBehaviors } = props;
|
||||||
// Handle queries
|
// Handle queries
|
||||||
const queryRef = useRef(props.query);
|
|
||||||
const onQuerySubmitRef = useRef(props.onQuerySubmit);
|
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<Query>(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.
|
// handle service state updates.
|
||||||
// i.e. filters being added from a visualization directly to filterManager.
|
// 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,
|
filters: props.filters,
|
||||||
filterManager: data.query.filterManager,
|
filterManager: data.query.filterManager,
|
||||||
});
|
});
|
||||||
|
const { query } = useQueryStringManager({
|
||||||
|
query: props.query,
|
||||||
|
queryStringManager: data.query.queryString,
|
||||||
|
});
|
||||||
const { timeRange, refreshInterval } = useTimefilter({
|
const { timeRange, refreshInterval } = useTimefilter({
|
||||||
dateRangeFrom: props.dateRangeFrom,
|
dateRangeFrom: props.dateRangeFrom,
|
||||||
dateRangeTo: props.dateRangeTo,
|
dateRangeTo: props.dateRangeTo,
|
||||||
|
@ -163,10 +148,8 @@ export function createSearchBar({ core, storage, data }: StatefulSearchBarDeps)
|
||||||
// Fetch and update UI from saved query
|
// Fetch and update UI from saved query
|
||||||
const { savedQuery, setSavedQuery, clearSavedQuery } = useSavedQuery({
|
const { savedQuery, setSavedQuery, clearSavedQuery } = useSavedQuery({
|
||||||
queryService: data.query,
|
queryService: data.query,
|
||||||
setQuery,
|
|
||||||
savedQueryId: props.savedQueryId,
|
savedQueryId: props.savedQueryId,
|
||||||
notifications: core.notifications,
|
notifications: core.notifications,
|
||||||
defaultLanguage: defaultQuery.language,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fire onQuerySubmit on query or timerange change
|
// Fire onQuerySubmit on query or timerange change
|
||||||
|
@ -210,7 +193,7 @@ export function createSearchBar({ core, storage, data }: StatefulSearchBarDeps)
|
||||||
onFiltersUpdated={defaultFiltersUpdated(data.query)}
|
onFiltersUpdated={defaultFiltersUpdated(data.query)}
|
||||||
onRefreshChange={defaultOnRefreshChange(data.query)}
|
onRefreshChange={defaultOnRefreshChange(data.query)}
|
||||||
savedQuery={savedQuery}
|
savedQuery={savedQuery}
|
||||||
onQuerySubmit={defaultOnQuerySubmit(props, data.query, query, setQuery)}
|
onQuerySubmit={defaultOnQuerySubmit(props, data.query, query)}
|
||||||
onClearSavedQuery={defaultOnClearSavedQuery(props, clearSavedQuery)}
|
onClearSavedQuery={defaultOnClearSavedQuery(props, clearSavedQuery)}
|
||||||
onSavedQueryUpdated={defaultOnSavedQueryUpdated(props, setSavedQuery)}
|
onSavedQueryUpdated={defaultOnSavedQueryUpdated(props, setSavedQuery)}
|
||||||
onSaved={defaultOnSavedQueryUpdated(props, setSavedQuery)}
|
onSaved={defaultOnSavedQueryUpdated(props, setSavedQuery)}
|
||||||
|
|
|
@ -21,10 +21,8 @@ import { clearStateFromSavedQuery } from './clear_saved_query';
|
||||||
|
|
||||||
import { dataPluginMock } from '../../../mocks';
|
import { dataPluginMock } from '../../../mocks';
|
||||||
import { DataPublicPluginStart } from '../../../types';
|
import { DataPublicPluginStart } from '../../../types';
|
||||||
import { Query } from '../../..';
|
|
||||||
|
|
||||||
describe('clearStateFromSavedQuery', () => {
|
describe('clearStateFromSavedQuery', () => {
|
||||||
const DEFAULT_LANGUAGE = 'banana';
|
|
||||||
let dataMock: jest.Mocked<DataPublicPluginStart>;
|
let dataMock: jest.Mocked<DataPublicPluginStart>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
@ -32,19 +30,9 @@ describe('clearStateFromSavedQuery', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should clear filters and query', async () => {
|
it('should clear filters and query', async () => {
|
||||||
const setQueryState = jest.fn();
|
|
||||||
dataMock.query.filterManager.removeAll = jest.fn();
|
dataMock.query.filterManager.removeAll = jest.fn();
|
||||||
clearStateFromSavedQuery(dataMock.query, setQueryState, DEFAULT_LANGUAGE);
|
clearStateFromSavedQuery(dataMock.query);
|
||||||
expect(setQueryState).toHaveBeenCalled();
|
expect(dataMock.query.queryString.clearQuery).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);
|
|
||||||
expect(dataMock.query.filterManager.removeAll).toHaveBeenCalled();
|
expect(dataMock.query.filterManager.removeAll).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -18,14 +18,7 @@
|
||||||
*/
|
*/
|
||||||
import { QueryStart } from '../../../query';
|
import { QueryStart } from '../../../query';
|
||||||
|
|
||||||
export const clearStateFromSavedQuery = (
|
export const clearStateFromSavedQuery = (queryService: QueryStart) => {
|
||||||
queryService: QueryStart,
|
|
||||||
setQueryStringState: Function,
|
|
||||||
defaultLanguage: string
|
|
||||||
) => {
|
|
||||||
queryService.filterManager.removeAll();
|
queryService.filterManager.removeAll();
|
||||||
setQueryStringState({
|
queryService.queryString.clearQuery();
|
||||||
query: '',
|
|
||||||
language: defaultLanguage,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -47,37 +47,34 @@ describe('populateStateFromSavedQuery', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set query', async () => {
|
it('should set query', async () => {
|
||||||
const setQueryState = jest.fn();
|
|
||||||
const savedQuery: SavedQuery = {
|
const savedQuery: SavedQuery = {
|
||||||
...baseSavedQuery,
|
...baseSavedQuery,
|
||||||
};
|
};
|
||||||
populateStateFromSavedQuery(dataMock.query, setQueryState, savedQuery);
|
populateStateFromSavedQuery(dataMock.query, savedQuery);
|
||||||
expect(setQueryState).toHaveBeenCalled();
|
expect(dataMock.query.queryString.setQuery).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set filters', async () => {
|
it('should set filters', async () => {
|
||||||
const setQueryState = jest.fn();
|
|
||||||
const savedQuery: SavedQuery = {
|
const savedQuery: SavedQuery = {
|
||||||
...baseSavedQuery,
|
...baseSavedQuery,
|
||||||
};
|
};
|
||||||
const f1 = getFilter(FilterStateStore.APP_STATE, false, false, 'age', 34);
|
const f1 = getFilter(FilterStateStore.APP_STATE, false, false, 'age', 34);
|
||||||
savedQuery.attributes.filters = [f1];
|
savedQuery.attributes.filters = [f1];
|
||||||
populateStateFromSavedQuery(dataMock.query, setQueryState, savedQuery);
|
populateStateFromSavedQuery(dataMock.query, savedQuery);
|
||||||
expect(setQueryState).toHaveBeenCalled();
|
expect(dataMock.query.queryString.setQuery).toHaveBeenCalled();
|
||||||
expect(dataMock.query.filterManager.setFilters).toHaveBeenCalledWith([f1]);
|
expect(dataMock.query.filterManager.setFilters).toHaveBeenCalledWith([f1]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should preserve global filters', async () => {
|
it('should preserve global filters', async () => {
|
||||||
const globalFilter = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'age', 34);
|
const globalFilter = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'age', 34);
|
||||||
dataMock.query.filterManager.getGlobalFilters = jest.fn().mockReturnValue([globalFilter]);
|
dataMock.query.filterManager.getGlobalFilters = jest.fn().mockReturnValue([globalFilter]);
|
||||||
const setQueryState = jest.fn();
|
|
||||||
const savedQuery: SavedQuery = {
|
const savedQuery: SavedQuery = {
|
||||||
...baseSavedQuery,
|
...baseSavedQuery,
|
||||||
};
|
};
|
||||||
const f1 = getFilter(FilterStateStore.APP_STATE, false, false, 'age', 34);
|
const f1 = getFilter(FilterStateStore.APP_STATE, false, false, 'age', 34);
|
||||||
savedQuery.attributes.filters = [f1];
|
savedQuery.attributes.filters = [f1];
|
||||||
populateStateFromSavedQuery(dataMock.query, setQueryState, savedQuery);
|
populateStateFromSavedQuery(dataMock.query, savedQuery);
|
||||||
expect(setQueryState).toHaveBeenCalled();
|
expect(dataMock.query.queryString.setQuery).toHaveBeenCalled();
|
||||||
expect(dataMock.query.filterManager.setFilters).toHaveBeenCalledWith([globalFilter, f1]);
|
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.setTime = jest.fn();
|
||||||
dataMock.query.timefilter.timefilter.setRefreshInterval = 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({
|
expect(dataMock.query.timefilter.timefilter.setTime).toHaveBeenCalledWith({
|
||||||
from: savedQuery.attributes.timefilter.from,
|
from: savedQuery.attributes.timefilter.from,
|
||||||
|
|
|
@ -19,14 +19,11 @@
|
||||||
|
|
||||||
import { QueryStart, SavedQuery } from '../../../query';
|
import { QueryStart, SavedQuery } from '../../../query';
|
||||||
|
|
||||||
export const populateStateFromSavedQuery = (
|
export const populateStateFromSavedQuery = (queryService: QueryStart, savedQuery: SavedQuery) => {
|
||||||
queryService: QueryStart,
|
|
||||||
setQueryStringState: Function,
|
|
||||||
savedQuery: SavedQuery
|
|
||||||
) => {
|
|
||||||
const {
|
const {
|
||||||
timefilter: { timefilter },
|
timefilter: { timefilter },
|
||||||
filterManager,
|
filterManager,
|
||||||
|
queryString,
|
||||||
} = queryService;
|
} = queryService;
|
||||||
// timefilter
|
// timefilter
|
||||||
if (savedQuery.attributes.timefilter) {
|
if (savedQuery.attributes.timefilter) {
|
||||||
|
@ -40,7 +37,7 @@ export const populateStateFromSavedQuery = (
|
||||||
}
|
}
|
||||||
|
|
||||||
// query string
|
// query string
|
||||||
setQueryStringState(savedQuery.attributes.query);
|
queryString.setQuery(savedQuery.attributes.query);
|
||||||
|
|
||||||
// filters
|
// filters
|
||||||
const savedQueryFilters = savedQuery.attributes.filters || [];
|
const savedQueryFilters = savedQuery.attributes.filters || [];
|
||||||
|
|
|
@ -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<Query>(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 };
|
||||||
|
};
|
|
@ -27,10 +27,8 @@ import { clearStateFromSavedQuery } from './clear_saved_query';
|
||||||
|
|
||||||
interface UseSavedQueriesProps {
|
interface UseSavedQueriesProps {
|
||||||
queryService: DataPublicPluginStart['query'];
|
queryService: DataPublicPluginStart['query'];
|
||||||
setQuery: Function;
|
|
||||||
notifications: CoreStart['notifications'];
|
notifications: CoreStart['notifications'];
|
||||||
savedQueryId?: string;
|
savedQueryId?: string;
|
||||||
defaultLanguage: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UseSavedQueriesReturn {
|
interface UseSavedQueriesReturn {
|
||||||
|
@ -41,7 +39,6 @@ interface UseSavedQueriesReturn {
|
||||||
|
|
||||||
export const useSavedQuery = (props: UseSavedQueriesProps): UseSavedQueriesReturn => {
|
export const useSavedQuery = (props: UseSavedQueriesProps): UseSavedQueriesReturn => {
|
||||||
// Handle saved queries
|
// Handle saved queries
|
||||||
const defaultLanguage = props.defaultLanguage;
|
|
||||||
const [savedQuery, setSavedQuery] = useState<SavedQuery | undefined>();
|
const [savedQuery, setSavedQuery] = useState<SavedQuery | undefined>();
|
||||||
|
|
||||||
// Effect is used to convert a saved query id into an object
|
// 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
|
// Make sure we set the saved query to the most recent one
|
||||||
if (newSavedQuery && newSavedQuery.id === savedQueryId) {
|
if (newSavedQuery && newSavedQuery.id === savedQueryId) {
|
||||||
setSavedQuery(newSavedQuery);
|
setSavedQuery(newSavedQuery);
|
||||||
populateStateFromSavedQuery(props.queryService, props.setQuery, newSavedQuery);
|
populateStateFromSavedQuery(props.queryService, newSavedQuery);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Clear saved query
|
// Clear saved query
|
||||||
setSavedQuery(undefined);
|
setSavedQuery(undefined);
|
||||||
clearStateFromSavedQuery(props.queryService, props.setQuery, defaultLanguage);
|
clearStateFromSavedQuery(props.queryService);
|
||||||
// notify of saving error
|
// notify of saving error
|
||||||
props.notifications.toasts.addWarning({
|
props.notifications.toasts.addWarning({
|
||||||
title: i18n.translate('data.search.unableToGetSavedQueryToastTitle', {
|
title: i18n.translate('data.search.unableToGetSavedQueryToastTitle', {
|
||||||
|
@ -73,23 +70,21 @@ export const useSavedQuery = (props: UseSavedQueriesProps): UseSavedQueriesRetur
|
||||||
if (props.savedQueryId) fetchSavedQuery(props.savedQueryId);
|
if (props.savedQueryId) fetchSavedQuery(props.savedQueryId);
|
||||||
else setSavedQuery(undefined);
|
else setSavedQuery(undefined);
|
||||||
}, [
|
}, [
|
||||||
defaultLanguage,
|
|
||||||
props.notifications.toasts,
|
props.notifications.toasts,
|
||||||
props.queryService,
|
props.queryService,
|
||||||
props.queryService.savedQueries,
|
props.queryService.savedQueries,
|
||||||
props.savedQueryId,
|
props.savedQueryId,
|
||||||
props.setQuery,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
savedQuery,
|
savedQuery,
|
||||||
setSavedQuery: (q: SavedQuery) => {
|
setSavedQuery: (q: SavedQuery) => {
|
||||||
setSavedQuery(q);
|
setSavedQuery(q);
|
||||||
populateStateFromSavedQuery(props.queryService, props.setQuery, q);
|
populateStateFromSavedQuery(props.queryService, q);
|
||||||
},
|
},
|
||||||
clearSavedQuery: () => {
|
clearSavedQuery: () => {
|
||||||
setSavedQuery(undefined);
|
setSavedQuery(undefined);
|
||||||
clearStateFromSavedQuery(props.queryService, props.setQuery, defaultLanguage);
|
clearStateFromSavedQuery(props.queryService);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,9 +6,8 @@
|
||||||
app-name="'discover'"
|
app-name="'discover'"
|
||||||
config="topNavMenu"
|
config="topNavMenu"
|
||||||
index-patterns="[indexPattern]"
|
index-patterns="[indexPattern]"
|
||||||
on-query-submit="updateQuery"
|
on-query-submit="handleRefresh"
|
||||||
on-saved-query-id-change="updateSavedQueryId"
|
on-saved-query-id-change="updateSavedQueryId"
|
||||||
query="state.query"
|
|
||||||
saved-query-id="state.savedQuery"
|
saved-query-id="state.savedQuery"
|
||||||
screen-title="screenTitle"
|
screen-title="screenTitle"
|
||||||
show-date-picker="indexPattern.isTimeBased()"
|
show-date-picker="indexPattern.isTimeBased()"
|
||||||
|
|
|
@ -70,9 +70,7 @@ import {
|
||||||
indexPatterns as indexPatternsUtils,
|
indexPatterns as indexPatternsUtils,
|
||||||
connectToQueryState,
|
connectToQueryState,
|
||||||
syncQueryStateWithUrl,
|
syncQueryStateWithUrl,
|
||||||
getDefaultQuery,
|
|
||||||
search,
|
search,
|
||||||
UI_SETTINGS,
|
|
||||||
} from '../../../../data/public';
|
} from '../../../../data/public';
|
||||||
import { getIndexPatternId } from '../helpers/get_index_pattern_id';
|
import { getIndexPatternId } from '../helpers/get_index_pattern_id';
|
||||||
import { addFatalError } from '../../../../kibana_legacy/public';
|
import { addFatalError } from '../../../../kibana_legacy/public';
|
||||||
|
@ -191,16 +189,7 @@ app.directive('discoverApp', function () {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
function discoverController(
|
function discoverController($element, $route, $scope, $timeout, $window, Promise, uiCapabilities) {
|
||||||
$element,
|
|
||||||
$route,
|
|
||||||
$scope,
|
|
||||||
$timeout,
|
|
||||||
$window,
|
|
||||||
Promise,
|
|
||||||
localStorage,
|
|
||||||
uiCapabilities
|
|
||||||
) {
|
|
||||||
const { isDefault: isDefaultType } = indexPatternsUtils;
|
const { isDefault: isDefaultType } = indexPatternsUtils;
|
||||||
const subscriptions = new Subscription();
|
const subscriptions = new Subscription();
|
||||||
const $fetchObservable = new Subject();
|
const $fetchObservable = new Subject();
|
||||||
|
@ -246,11 +235,15 @@ function discoverController(
|
||||||
|
|
||||||
// sync initial app filters from state to filterManager
|
// sync initial app filters from state to filterManager
|
||||||
filterManager.setAppFilters(_.cloneDeep(appStateContainer.getState().filters));
|
filterManager.setAppFilters(_.cloneDeep(appStateContainer.getState().filters));
|
||||||
|
data.query.queryString.setQuery(appStateContainer.getState().query);
|
||||||
|
|
||||||
const stopSyncingQueryAppStateWithStateContainer = connectToQueryState(
|
const stopSyncingQueryAppStateWithStateContainer = connectToQueryState(
|
||||||
data.query,
|
data.query,
|
||||||
appStateContainer,
|
appStateContainer,
|
||||||
{ filters: esFilters.FilterStateStore.APP_STATE }
|
{
|
||||||
|
filters: esFilters.FilterStateStore.APP_STATE,
|
||||||
|
query: true,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const appStateUnsubscribe = appStateContainer.subscribe(async (newState) => {
|
const appStateUnsubscribe = appStateContainer.subscribe(async (newState) => {
|
||||||
|
@ -262,7 +255,7 @@ function discoverController(
|
||||||
$scope.state = { ...newState };
|
$scope.state = { ...newState };
|
||||||
|
|
||||||
// detect changes that should trigger fetching of new data
|
// 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])
|
(prop) => !_.isEqual(newStatePartial[prop], oldStatePartial[prop])
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -593,12 +586,7 @@ function discoverController(
|
||||||
};
|
};
|
||||||
|
|
||||||
function getStateDefaults() {
|
function getStateDefaults() {
|
||||||
const query =
|
const query = $scope.searchSource.getField('query') || data.query.queryString.getDefaultQuery();
|
||||||
$scope.searchSource.getField('query') ||
|
|
||||||
getDefaultQuery(
|
|
||||||
localStorage.get('kibana.userQueryLanguage') ||
|
|
||||||
config.get(UI_SETTINGS.SEARCH_QUERY_LANGUAGE)
|
|
||||||
);
|
|
||||||
return {
|
return {
|
||||||
query,
|
query,
|
||||||
sort: getSortArray(savedSearch.sort, $scope.indexPattern),
|
sort: getSortArray(savedSearch.sort, $scope.indexPattern),
|
||||||
|
@ -635,12 +623,7 @@ function discoverController(
|
||||||
|
|
||||||
const init = _.once(() => {
|
const init = _.once(() => {
|
||||||
$scope.updateDataSource().then(async () => {
|
$scope.updateDataSource().then(async () => {
|
||||||
const searchBarChanges = merge(
|
const searchBarChanges = merge(data.query.state$, $fetchObservable).pipe(debounceTime(100));
|
||||||
timefilter.getAutoRefreshFetch$(),
|
|
||||||
timefilter.getFetch$(),
|
|
||||||
filterManager.getFetches$(),
|
|
||||||
$fetchObservable
|
|
||||||
).pipe(debounceTime(100));
|
|
||||||
|
|
||||||
subscriptions.add(
|
subscriptions.add(
|
||||||
subscribeWithScope(
|
subscribeWithScope(
|
||||||
|
@ -824,9 +807,8 @@ function discoverController(
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.updateQuery = function ({ query }, isUpdate = true) {
|
$scope.handleRefresh = function (_payload, isUpdate) {
|
||||||
if (!_.isEqual(query, appStateContainer.getState().query) || isUpdate === false) {
|
if (isUpdate === false) {
|
||||||
setAppState({ query });
|
|
||||||
$fetchObservable.next();
|
$fetchObservable.next();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -976,7 +958,7 @@ function discoverController(
|
||||||
config.get(SORT_DEFAULT_ORDER_SETTING)
|
config.get(SORT_DEFAULT_ORDER_SETTING)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.setField('query', $scope.state.query || null)
|
.setField('query', data.query.queryString.getQuery() || null)
|
||||||
.setField('filter', filterManager.getFilters());
|
.setField('filter', filterManager.getFilters());
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
};
|
};
|
||||||
|
|
|
@ -35,12 +35,7 @@ import {
|
||||||
import { FormattedMessage } from '@kbn/i18n/react';
|
import { FormattedMessage } from '@kbn/i18n/react';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
|
|
||||||
import {
|
import { esQuery, IndexPattern, Query } from '../../../../../../../plugins/data/public';
|
||||||
esQuery,
|
|
||||||
IndexPattern,
|
|
||||||
Query,
|
|
||||||
UI_SETTINGS,
|
|
||||||
} from '../../../../../../../plugins/data/public';
|
|
||||||
import { context as contextType } from '../../../../../../kibana_react/public';
|
import { context as contextType } from '../../../../../../kibana_react/public';
|
||||||
import { IndexPatternManagmentContextValue } from '../../../../types';
|
import { IndexPatternManagmentContextValue } from '../../../../types';
|
||||||
import { ExecuteScript } from '../../types';
|
import { ExecuteScript } from '../../types';
|
||||||
|
@ -248,10 +243,7 @@ export class TestScript extends Component<TestScriptProps, TestScriptState> {
|
||||||
showFilterBar={false}
|
showFilterBar={false}
|
||||||
showDatePicker={false}
|
showDatePicker={false}
|
||||||
showQueryInput={true}
|
showQueryInput={true}
|
||||||
query={{
|
query={this.context.services.data.query.queryString.getDefaultQuery()}
|
||||||
language: this.context.services.uiSettings.get(UI_SETTINGS.SEARCH_QUERY_LANGUAGE),
|
|
||||||
query: '',
|
|
||||||
}}
|
|
||||||
onQuerySubmit={this.previewScript}
|
onQuerySubmit={this.previewScript}
|
||||||
indexPatterns={[this.props.indexPattern]}
|
indexPatterns={[this.props.indexPattern]}
|
||||||
customSubmitButton={
|
customSubmitButton={
|
||||||
|
|
|
@ -23,7 +23,7 @@ import { htmlIdGenerator, EuiButton, EuiSpacer } from '@elastic/eui';
|
||||||
import { FormattedMessage } from '@kbn/i18n/react';
|
import { FormattedMessage } from '@kbn/i18n/react';
|
||||||
import { useMount } from 'react-use';
|
import { useMount } from 'react-use';
|
||||||
|
|
||||||
import { Query, UI_SETTINGS } from '../../../../data/public';
|
import { Query } from '../../../../data/public';
|
||||||
import { useKibana } from '../../../../kibana_react/public';
|
import { useKibana } from '../../../../kibana_react/public';
|
||||||
import { FilterRow } from './filter';
|
import { FilterRow } from './filter';
|
||||||
import { AggParamEditorProps } from '../agg_param_props';
|
import { AggParamEditorProps } from '../agg_param_props';
|
||||||
|
@ -70,7 +70,7 @@ function FiltersParamEditor({ agg, value = [], setValue }: AggParamEditorProps<F
|
||||||
updateFilters([
|
updateFilters([
|
||||||
...filters,
|
...filters,
|
||||||
{
|
{
|
||||||
input: { query: '', language: services.uiSettings.get(UI_SETTINGS.SEARCH_QUERY_LANGUAGE) },
|
input: services.data.query.queryString.getDefaultQuery(),
|
||||||
label: '',
|
label: '',
|
||||||
id: generateId(),
|
id: generateId(),
|
||||||
},
|
},
|
||||||
|
|
|
@ -18,10 +18,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { memo, useCallback, useMemo, useState, useEffect } from 'react';
|
import React, { memo, useCallback, useMemo, useState, useEffect } from 'react';
|
||||||
import { isEqual } from 'lodash';
|
|
||||||
|
|
||||||
import { OverlayRef } from 'kibana/public';
|
import { OverlayRef } from 'kibana/public';
|
||||||
import { Query } from 'src/plugins/data/public';
|
|
||||||
import { useKibana } from '../../../../kibana_react/public';
|
import { useKibana } from '../../../../kibana_react/public';
|
||||||
import {
|
import {
|
||||||
VisualizeServices,
|
VisualizeServices,
|
||||||
|
@ -68,15 +66,13 @@ const TopNav = ({
|
||||||
setInspectorSession(session);
|
setInspectorSession(session);
|
||||||
}, [embeddableHandler]);
|
}, [embeddableHandler]);
|
||||||
|
|
||||||
const updateQuery = useCallback(
|
const handleRefresh = useCallback(
|
||||||
({ query }: { query?: Query }) => {
|
(_payload: any, isUpdate?: boolean) => {
|
||||||
if (!isEqual(currentAppState.query, query)) {
|
if (isUpdate === false) {
|
||||||
stateContainer.transitions.set('query', query || currentAppState.query);
|
|
||||||
} else {
|
|
||||||
savedVisInstance.embeddableHandler.reload();
|
savedVisInstance.embeddableHandler.reload();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[currentAppState.query, savedVisInstance.embeddableHandler, stateContainer.transitions]
|
[savedVisInstance.embeddableHandler]
|
||||||
);
|
);
|
||||||
|
|
||||||
const config = useMemo(() => {
|
const config = useMemo(() => {
|
||||||
|
@ -149,8 +145,7 @@ const TopNav = ({
|
||||||
<TopNavMenu
|
<TopNavMenu
|
||||||
appName={APP_NAME}
|
appName={APP_NAME}
|
||||||
config={config}
|
config={config}
|
||||||
query={currentAppState.query}
|
onQuerySubmit={handleRefresh}
|
||||||
onQuerySubmit={updateQuery}
|
|
||||||
savedQueryId={currentAppState.savedQuery}
|
savedQueryId={currentAppState.savedQuery}
|
||||||
onSavedQueryIdChange={stateContainer.transitions.updateSavedQuery}
|
onSavedQueryIdChange={stateContainer.transitions.updateSavedQuery}
|
||||||
indexPatterns={indexPattern ? [indexPattern] : undefined}
|
indexPatterns={indexPattern ? [indexPattern] : undefined}
|
||||||
|
|
|
@ -105,10 +105,16 @@ describe('useEditorUpdates', () => {
|
||||||
to: 'now',
|
to: 'now',
|
||||||
};
|
};
|
||||||
mockFilters = ['mockFilters'];
|
mockFilters = ['mockFilters'];
|
||||||
|
const mockQuery = {
|
||||||
|
query: '',
|
||||||
|
language: 'kuery',
|
||||||
|
};
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
mockServices.data.query.timefilter.timefilter.getTime.mockImplementation(() => timeRange);
|
mockServices.data.query.timefilter.timefilter.getTime.mockImplementation(() => timeRange);
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
mockServices.data.query.filterManager.getFilters.mockImplementation(() => mockFilters);
|
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', () => {
|
test('should set up current app state and render the editor', () => {
|
||||||
|
|
|
@ -20,9 +20,7 @@
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { isEqual } from 'lodash';
|
import { isEqual } from 'lodash';
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import { merge } from 'rxjs';
|
|
||||||
|
|
||||||
import { migrateLegacyQuery } from '../../../../../kibana_legacy/public';
|
|
||||||
import {
|
import {
|
||||||
VisualizeServices,
|
VisualizeServices,
|
||||||
VisualizeAppState,
|
VisualizeAppState,
|
||||||
|
@ -47,6 +45,8 @@ export const useEditorUpdates = (
|
||||||
const {
|
const {
|
||||||
timefilter: { timefilter },
|
timefilter: { timefilter },
|
||||||
filterManager,
|
filterManager,
|
||||||
|
queryString,
|
||||||
|
state$,
|
||||||
} = services.data.query;
|
} = services.data.query;
|
||||||
const { embeddableHandler, savedVis, savedSearch, vis } = savedVisInstance;
|
const { embeddableHandler, savedVis, savedSearch, vis } = savedVisInstance;
|
||||||
const initialState = appState.getState();
|
const initialState = appState.getState();
|
||||||
|
@ -60,7 +60,7 @@ export const useEditorUpdates = (
|
||||||
uiState: vis.uiState,
|
uiState: vis.uiState,
|
||||||
timeRange: timefilter.getTime(),
|
timeRange: timefilter.getTime(),
|
||||||
filters: filterManager.getFilters(),
|
filters: filterManager.getFilters(),
|
||||||
query: appState.getState().query,
|
query: queryString.getQuery(),
|
||||||
linked: !!vis.data.savedSearchId,
|
linked: !!vis.data.savedSearchId,
|
||||||
savedSearch,
|
savedSearch,
|
||||||
});
|
});
|
||||||
|
@ -68,17 +68,12 @@ export const useEditorUpdates = (
|
||||||
embeddableHandler.updateInput({
|
embeddableHandler.updateInput({
|
||||||
timeRange: timefilter.getTime(),
|
timeRange: timefilter.getTime(),
|
||||||
filters: filterManager.getFilters(),
|
filters: filterManager.getFilters(),
|
||||||
query: appState.getState().query,
|
query: queryString.getQuery(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const subscriptions = merge(
|
const subscriptions = state$.subscribe({
|
||||||
timefilter.getTimeUpdate$(),
|
|
||||||
timefilter.getAutoRefreshFetch$(),
|
|
||||||
timefilter.getFetch$(),
|
|
||||||
filterManager.getFetches$()
|
|
||||||
).subscribe({
|
|
||||||
next: reloadVisualization,
|
next: reloadVisualization,
|
||||||
error: services.fatalErrors.add,
|
error: services.fatalErrors.add,
|
||||||
});
|
});
|
||||||
|
@ -116,10 +111,6 @@ export const useEditorUpdates = (
|
||||||
// and initializing different visualizations
|
// and initializing different visualizations
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const newQuery = migrateLegacyQuery(state.query);
|
|
||||||
if (!isEqual(state.query, newQuery)) {
|
|
||||||
appState.transitions.set('query', newQuery);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isEqual(state.uiState, vis.uiState.getChanges())) {
|
if (!isEqual(state.uiState, vis.uiState.getChanges())) {
|
||||||
vis.uiState.set(state.uiState);
|
vis.uiState.set(state.uiState);
|
||||||
|
|
|
@ -96,6 +96,7 @@ describe('useVisualizeAppState', () => {
|
||||||
);
|
);
|
||||||
expect(connectToQueryState).toHaveBeenCalledWith(mockServices.data.query, expect.any(Object), {
|
expect(connectToQueryState).toHaveBeenCalledWith(mockServices.data.query, expect.any(Object), {
|
||||||
filters: 'appState',
|
filters: 'appState',
|
||||||
|
query: true,
|
||||||
});
|
});
|
||||||
expect(result.current).toEqual({
|
expect(result.current).toEqual({
|
||||||
appState: stateContainer,
|
appState: stateContainer,
|
||||||
|
|
|
@ -24,6 +24,7 @@ import { EventEmitter } from 'events';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
|
|
||||||
import { MarkdownSimple, toMountPoint } from '../../../../../kibana_react/public';
|
import { MarkdownSimple, toMountPoint } from '../../../../../kibana_react/public';
|
||||||
|
import { migrateLegacyQuery } from '../../../../../kibana_legacy/public';
|
||||||
import { esFilters, connectToQueryState } from '../../../../../data/public';
|
import { esFilters, connectToQueryState } from '../../../../../data/public';
|
||||||
import { VisualizeServices, VisualizeAppStateContainer, SavedVisInstance } from '../../types';
|
import { VisualizeServices, VisualizeAppStateContainer, SavedVisInstance } from '../../types';
|
||||||
import { visStateToEditorState } from '../utils';
|
import { visStateToEditorState } from '../utils';
|
||||||
|
@ -61,19 +62,35 @@ export const useVisualizeAppState = (
|
||||||
|
|
||||||
eventEmitter.on('dirtyStateChange', onDirtyStateChange);
|
eventEmitter.on('dirtyStateChange', onDirtyStateChange);
|
||||||
|
|
||||||
const { filterManager } = services.data.query;
|
const { filterManager, queryString } = services.data.query;
|
||||||
// sync initial app filters from state to filterManager
|
// sync initial app state from state to managers
|
||||||
filterManager.setAppFilters(cloneDeep(stateContainer.getState().filters));
|
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(
|
const stopSyncingAppFilters = connectToQueryState(
|
||||||
services.data.query,
|
services.data.query,
|
||||||
{
|
{
|
||||||
set: ({ filters }) => stateContainer.transitions.set('filters', filters),
|
set: ({ filters, query }) => {
|
||||||
get: () => ({ filters: stateContainer.getState().filters }),
|
stateContainer.transitions.set('filters', filters);
|
||||||
state$: stateContainer.state$.pipe(map((state) => ({ filters: state.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,
|
filters: esFilters.FilterStateStore.APP_STATE,
|
||||||
|
query: true,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
|
|
||||||
import { ChromeStart, DocLinksStart } from 'kibana/public';
|
import { ChromeStart, DocLinksStart } from 'kibana/public';
|
||||||
import { Filter, UI_SETTINGS } from '../../../../data/public';
|
import { Filter } from '../../../../data/public';
|
||||||
import { VisualizeServices, SavedVisInstance } from '../types';
|
import { VisualizeServices, SavedVisInstance } from '../types';
|
||||||
|
|
||||||
export const addHelpMenuToAppChrome = (chrome: ChromeStart, docLinks: DocLinksStart) => {
|
export const addHelpMenuToAppChrome = (chrome: ChromeStart, docLinks: DocLinksStart) => {
|
||||||
|
@ -49,12 +49,9 @@ export const addBadgeToAppChrome = (chrome: ChromeStart) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getDefaultQuery = ({ localStorage, uiSettings }: VisualizeServices) => ({
|
export const getDefaultQuery = ({ data }: VisualizeServices) => {
|
||||||
query: '',
|
return data.query.queryString.getDefaultQuery();
|
||||||
language:
|
};
|
||||||
localStorage.get('kibana.userQueryLanguage') ||
|
|
||||||
uiSettings.get(UI_SETTINGS.SEARCH_QUERY_LANGUAGE),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const visStateToEditorState = (
|
export const visStateToEditorState = (
|
||||||
{ vis, savedVis }: SavedVisInstance,
|
{ vis, savedVis }: SavedVisInstance,
|
||||||
|
|
|
@ -563,6 +563,10 @@ export default function ({ getService, getPageObjects }) {
|
||||||
|
|
||||||
it('should display updated scaled label text after time range is changed', async () => {
|
it('should display updated scaled label text after time range is changed', async () => {
|
||||||
await PageObjects.visEditor.setInterval('Millisecond');
|
await PageObjects.visEditor.setInterval('Millisecond');
|
||||||
|
|
||||||
|
// Apply interval
|
||||||
|
await testSubjects.clickWhenNotDisabled('visualizeEditorRenderButton');
|
||||||
|
|
||||||
const isHelperScaledLabelExists = await find.existsByCssSelector(
|
const isHelperScaledLabelExists = await find.existsByCssSelector(
|
||||||
'[data-test-subj="currentlyScaledText"]'
|
'[data-test-subj="currentlyScaledText"]'
|
||||||
);
|
);
|
||||||
|
|
|
@ -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() {
|
function createMockTimefilter() {
|
||||||
const unsubscribe = jest.fn();
|
const unsubscribe = jest.fn();
|
||||||
|
|
||||||
|
@ -148,6 +156,7 @@ describe('Lens App', () => {
|
||||||
timefilter: {
|
timefilter: {
|
||||||
timefilter: createMockTimefilter(),
|
timefilter: createMockTimefilter(),
|
||||||
},
|
},
|
||||||
|
queryString: createMockQueryString(),
|
||||||
state$: new Observable(),
|
state$: new Observable(),
|
||||||
},
|
},
|
||||||
indexPatterns: {
|
indexPatterns: {
|
||||||
|
|
|
@ -36,7 +36,6 @@ import {
|
||||||
IndexPattern as IndexPatternInstance,
|
IndexPattern as IndexPatternInstance,
|
||||||
IndexPatternsContract,
|
IndexPatternsContract,
|
||||||
SavedQuery,
|
SavedQuery,
|
||||||
UI_SETTINGS,
|
|
||||||
} from '../../../../../src/plugins/data/public';
|
} from '../../../../../src/plugins/data/public';
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
|
@ -83,17 +82,13 @@ export function App({
|
||||||
onAppLeave: AppMountParameters['onAppLeave'];
|
onAppLeave: AppMountParameters['onAppLeave'];
|
||||||
history: History;
|
history: History;
|
||||||
}) {
|
}) {
|
||||||
const language =
|
|
||||||
storage.get('kibana.userQueryLanguage') ||
|
|
||||||
core.uiSettings.get(UI_SETTINGS.SEARCH_QUERY_LANGUAGE);
|
|
||||||
|
|
||||||
const [state, setState] = useState<State>(() => {
|
const [state, setState] = useState<State>(() => {
|
||||||
const currentRange = data.query.timefilter.timefilter.getTime();
|
const currentRange = data.query.timefilter.timefilter.getTime();
|
||||||
return {
|
return {
|
||||||
isLoading: !!docId,
|
isLoading: !!docId,
|
||||||
isSaveModalVisible: false,
|
isSaveModalVisible: false,
|
||||||
indexPatternsForTopNav: [],
|
indexPatternsForTopNav: [],
|
||||||
query: { query: '', language },
|
query: data.query.queryString.getDefaultQuery(),
|
||||||
dateRange: {
|
dateRange: {
|
||||||
fromDate: currentRange.from,
|
fromDate: currentRange.from,
|
||||||
toDate: currentRange.to,
|
toDate: currentRange.to,
|
||||||
|
@ -473,12 +468,7 @@ export function App({
|
||||||
...s,
|
...s,
|
||||||
savedQuery: undefined,
|
savedQuery: undefined,
|
||||||
filters: data.query.filterManager.getGlobalFilters(),
|
filters: data.query.filterManager.getGlobalFilters(),
|
||||||
query: {
|
query: data.query.queryString.getDefaultQuery(),
|
||||||
query: '',
|
|
||||||
language:
|
|
||||||
storage.get('kibana.userQueryLanguage') ||
|
|
||||||
core.uiSettings.get(UI_SETTINGS.SEARCH_QUERY_LANGUAGE),
|
|
||||||
},
|
|
||||||
}));
|
}));
|
||||||
}}
|
}}
|
||||||
query={state.query}
|
query={state.query}
|
||||||
|
|
|
@ -20,8 +20,7 @@ import {
|
||||||
|
|
||||||
import { FormattedMessage } from '@kbn/i18n/react';
|
import { FormattedMessage } from '@kbn/i18n/react';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { UI_SETTINGS } from '../../../../../../../src/plugins/data/public';
|
import { getIndexPatternService, getData } from '../../../kibana_services';
|
||||||
import { getIndexPatternService, getUiSettings, getData } from '../../../kibana_services';
|
|
||||||
import { GlobalFilterCheckbox } from '../../../components/global_filter_checkbox';
|
import { GlobalFilterCheckbox } from '../../../components/global_filter_checkbox';
|
||||||
|
|
||||||
export class FilterEditor extends Component {
|
export class FilterEditor extends Component {
|
||||||
|
@ -82,7 +81,6 @@ export class FilterEditor extends Component {
|
||||||
|
|
||||||
_renderQueryPopover() {
|
_renderQueryPopover() {
|
||||||
const layerQuery = this.props.layer.getQuery();
|
const layerQuery = this.props.layer.getQuery();
|
||||||
const uiSettings = getUiSettings();
|
|
||||||
const { SearchBar } = getData().ui;
|
const { SearchBar } = getData().ui;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -99,11 +97,7 @@ export class FilterEditor extends Component {
|
||||||
showFilterBar={false}
|
showFilterBar={false}
|
||||||
showDatePicker={false}
|
showDatePicker={false}
|
||||||
showQueryInput={true}
|
showQueryInput={true}
|
||||||
query={
|
query={layerQuery ? layerQuery : getData().query.queryString.getDefaultQuery()}
|
||||||
layerQuery
|
|
||||||
? layerQuery
|
|
||||||
: { language: uiSettings.get(UI_SETTINGS.SEARCH_QUERY_LANGUAGE), query: '' }
|
|
||||||
}
|
|
||||||
onQuerySubmit={this._onQueryChange}
|
onQuerySubmit={this._onQueryChange}
|
||||||
indexPatterns={this.state.indexPatterns}
|
indexPatterns={this.state.indexPatterns}
|
||||||
customSubmitButton={
|
customSubmitButton={
|
||||||
|
|
|
@ -8,8 +8,7 @@ import React, { Component } from 'react';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { EuiButton, EuiPopover, EuiExpression, EuiFormHelpText } from '@elastic/eui';
|
import { EuiButton, EuiPopover, EuiExpression, EuiFormHelpText } from '@elastic/eui';
|
||||||
import { FormattedMessage } from '@kbn/i18n/react';
|
import { FormattedMessage } from '@kbn/i18n/react';
|
||||||
import { UI_SETTINGS } from '../../../../../../../../src/plugins/data/public';
|
import { getData } from '../../../../kibana_services';
|
||||||
import { getUiSettings, getData } from '../../../../kibana_services';
|
|
||||||
|
|
||||||
export class WhereExpression extends Component {
|
export class WhereExpression extends Component {
|
||||||
state = {
|
state = {
|
||||||
|
@ -77,11 +76,7 @@ export class WhereExpression extends Component {
|
||||||
showFilterBar={false}
|
showFilterBar={false}
|
||||||
showDatePicker={false}
|
showDatePicker={false}
|
||||||
showQueryInput={true}
|
showQueryInput={true}
|
||||||
query={
|
query={whereQuery ? whereQuery : getData().query.queryString.getDefaultQuery()}
|
||||||
whereQuery
|
|
||||||
? whereQuery
|
|
||||||
: { language: getUiSettings().get(UI_SETTINGS.SEARCH_QUERY_LANGUAGE), query: '' }
|
|
||||||
}
|
|
||||||
onQuerySubmit={this._onQueryChange}
|
onQuerySubmit={this._onQueryChange}
|
||||||
indexPatterns={[indexPattern]}
|
indexPatterns={[indexPattern]}
|
||||||
customSubmitButton={
|
customSubmitButton={
|
||||||
|
|
|
@ -4,12 +4,9 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { getUiSettings } from '../../kibana_services';
|
import { getData } from '../../kibana_services';
|
||||||
import { UI_SETTINGS } from '../../../../../../src/plugins/data/public';
|
|
||||||
|
|
||||||
export function getInitialQuery({ mapStateJSON, appState = {}, userQueryLanguage }) {
|
|
||||||
const settings = getUiSettings();
|
|
||||||
|
|
||||||
|
export function getInitialQuery({ mapStateJSON, appState = {} }) {
|
||||||
if (appState.query) {
|
if (appState.query) {
|
||||||
return appState.query;
|
return appState.query;
|
||||||
}
|
}
|
||||||
|
@ -21,8 +18,5 @@ export function getInitialQuery({ mapStateJSON, appState = {}, userQueryLanguage
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return getData().query.queryString.getDefaultQuery();
|
||||||
query: '',
|
|
||||||
language: userQueryLanguage || settings.get(UI_SETTINGS.SEARCH_QUERY_LANGUAGE),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,6 @@ import {
|
||||||
getToasts,
|
getToasts,
|
||||||
getCoreI18n,
|
getCoreI18n,
|
||||||
getData,
|
getData,
|
||||||
getUiSettings,
|
|
||||||
} from '../../../kibana_services';
|
} from '../../../kibana_services';
|
||||||
import {
|
import {
|
||||||
SavedObjectSaveModal,
|
SavedObjectSaveModal,
|
||||||
|
@ -46,16 +45,13 @@ export function MapsTopNavMenu({
|
||||||
isOpenSettingsDisabled,
|
isOpenSettingsDisabled,
|
||||||
}) {
|
}) {
|
||||||
const { TopNavMenu } = getNavigation().ui;
|
const { TopNavMenu } = getNavigation().ui;
|
||||||
const { filterManager } = getData().query;
|
const { filterManager, queryString } = getData().query;
|
||||||
const showSaveQuery = getMapsCapabilities().saveQuery;
|
const showSaveQuery = getMapsCapabilities().saveQuery;
|
||||||
const onClearSavedQuery = () => {
|
const onClearSavedQuery = () => {
|
||||||
onQuerySaved(undefined);
|
onQuerySaved(undefined);
|
||||||
onQueryChange({
|
onQueryChange({
|
||||||
filters: filterManager.getGlobalFilters(),
|
filters: filterManager.getGlobalFilters(),
|
||||||
query: {
|
query: queryString.getDefaultQuery(),
|
||||||
query: '',
|
|
||||||
language: getUiSettings().get('search:queryLanguage'),
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,6 @@ import {
|
||||||
getIndexPatternService,
|
getIndexPatternService,
|
||||||
getToasts,
|
getToasts,
|
||||||
getData,
|
getData,
|
||||||
getUiSettings,
|
|
||||||
getCoreChrome,
|
getCoreChrome,
|
||||||
} from '../../../kibana_services';
|
} from '../../../kibana_services';
|
||||||
import { copyPersistentState } from '../../../reducers/util';
|
import { copyPersistentState } from '../../../reducers/util';
|
||||||
|
@ -274,6 +273,7 @@ export class MapsAppView extends React.Component {
|
||||||
|
|
||||||
_initQueryTimeRefresh() {
|
_initQueryTimeRefresh() {
|
||||||
const { setRefreshConfig, savedMap } = this.props;
|
const { setRefreshConfig, savedMap } = this.props;
|
||||||
|
const { queryString } = getData().query;
|
||||||
// TODO: Handle null when converting to TS
|
// TODO: Handle null when converting to TS
|
||||||
const globalState = getGlobalState();
|
const globalState = getGlobalState();
|
||||||
const mapStateJSON = savedMap ? savedMap.mapStateJSON : undefined;
|
const mapStateJSON = savedMap ? savedMap.mapStateJSON : undefined;
|
||||||
|
@ -281,7 +281,6 @@ export class MapsAppView extends React.Component {
|
||||||
query: getInitialQuery({
|
query: getInitialQuery({
|
||||||
mapStateJSON,
|
mapStateJSON,
|
||||||
appState: this._appStateManager.getAppState(),
|
appState: this._appStateManager.getAppState(),
|
||||||
userQueryLanguage: getUiSettings().get('search:queryLanguage'),
|
|
||||||
}),
|
}),
|
||||||
time: getInitialTimeFilters({
|
time: getInitialTimeFilters({
|
||||||
mapStateJSON,
|
mapStateJSON,
|
||||||
|
@ -292,6 +291,8 @@ export class MapsAppView extends React.Component {
|
||||||
globalState,
|
globalState,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (newState.query) queryString.setQuery(newState.query);
|
||||||
this.setState({ query: newState.query, time: newState.time });
|
this.setState({ query: newState.query, time: newState.time });
|
||||||
updateGlobalState(
|
updateGlobalState(
|
||||||
{
|
{
|
||||||
|
|
|
@ -31,6 +31,7 @@ export function useAppStateSyncing(appStateManager) {
|
||||||
};
|
};
|
||||||
const stopSyncingQueryAppStateWithStateContainer = connectToQueryState(query, stateContainer, {
|
const stopSyncingQueryAppStateWithStateContainer = connectToQueryState(query, stateContainer, {
|
||||||
filters: esFilters.FilterStateStore.APP_STATE,
|
filters: esFilters.FilterStateStore.APP_STATE,
|
||||||
|
query: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// sets up syncing app state container with url
|
// sets up syncing app state container with url
|
||||||
|
|
Loading…
Reference in a new issue