Skip to content

Commit

Permalink
[7.x] [Data] Query Input String manager (elastic#72093) (elastic#73634)
Browse files Browse the repository at this point in the history
* [Data] Query Input String manager (elastic#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 <[email protected]>

* 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 <[email protected]>
Co-authored-by: Anton Dosov <[email protected]>
Co-authored-by: Elastic Machine <[email protected]>
# Conflicts:
#	src/plugins/data/public/public.api.md

* docs
  • Loading branch information
lizozom authored Jul 29, 2020
1 parent 9ed8227 commit 25ceed9
Show file tree
Hide file tree
Showing 45 changed files with 432 additions and 290 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ Helper to setup two-way syncing of global data and a state container
<b>Signature:</b>

```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;
refreshInterval?: boolean;
filters?: FilterStateStore | boolean;
query?: boolean;
}) => () => void
```
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface QueryState
| Property | Type | Description |
| --- | --- | --- |
| [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> | |
| [time](./kibana-plugin-plugins-data-public.querystate.time.md) | <code>TimeRange</code> | |

Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) &gt; [QueryState](./kibana-plugin-plugins-data-public.querystate.md) &gt; [query](./kibana-plugin-plugins-data-public.querystate.query.md)

## QueryState.query property

<b>Signature:</b>

```typescript
query?: Query;
```
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Helper to setup syncing of global data with the URL
<b>Signature:</b>

```typescript
syncQueryStateWithUrl: (query: Pick<QueryStart | QuerySetup, 'filterManager' | 'timefilter' | 'state$'>, kbnUrlStateStorage: IKbnUrlStateStorage) => {
syncQueryStateWithUrl: (query: Pick<QueryStart | QuerySetup, 'filterManager' | 'timefilter' | 'queryString' | 'state$'>, kbnUrlStateStorage: IKbnUrlStateStorage) => {
stop: () => void;
hasInheritedQueryFromUrl: boolean;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* under the License.
*/

import React, { useEffect, useRef, useState, useCallback } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import { History } from 'history';
import { FormattedMessage, I18nProvider } from '@kbn/i18n/react';
import { Router } from 'react-router-dom';
Expand Down Expand Up @@ -85,16 +85,9 @@ const App = ({ navigation, data, history, kbnUrlStateStorage }: StateDemoAppDeps
useGlobalStateSyncing(data.query, kbnUrlStateStorage);
useAppStateSyncing(appStateContainer, data.query, kbnUrlStateStorage);

const onQuerySubmit = useCallback(
({ query }) => {
appStateContainer.set({ ...appState, query });
},
[appStateContainer, appState]
);

const indexPattern = useIndexPattern(data);
if (!indexPattern)
return <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.
// Note that `navigation.ui.TopNavMenu` is a stateful component exported on the `navigation` plugin's start contract.
Expand All @@ -107,8 +100,6 @@ const App = ({ navigation, data, history, kbnUrlStateStorage }: StateDemoAppDeps
showSearchBar={true}
indexPatterns={[indexPattern]}
useDefaultBehaviors={true}
onQuerySubmit={onQuerySubmit}
query={appState.query}
showSaveQuery={true}
/>
<EuiPage restrictWidth="1000px">
Expand Down Expand Up @@ -200,7 +191,7 @@ function useAppStateSyncing<AppState extends QueryState>(
const stopSyncingQueryAppStateWithStateContainer = connectToQueryState(
query,
appStateContainer,
{ filters: esFilters.FilterStateStore.APP_STATE }
{ filters: esFilters.FilterStateStore.APP_STATE, query: true }
);

// sets up syncing app state container with url
Expand Down
5 changes: 4 additions & 1 deletion src/plugins/dashboard/public/application/dashboard_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,10 @@ export interface DashboardAppScope extends ng.IScope {
expandedPanel?: string;
getShouldShowEditHelp: () => boolean;
getShouldShowViewHelp: () => boolean;
updateQueryAndFetch: ({ query, dateRange }: { query: Query; dateRange?: TimeRange }) => void;
handleRefresh: (
{ query, dateRange }: { query?: Query; dateRange: TimeRange },
isUpdate?: boolean
) => void;
topNavMenu: any;
showAddPanel: any;
showSaveQuery: boolean;
Expand Down
103 changes: 39 additions & 64 deletions src/plugins/dashboard/public/application/dashboard_app_controller.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,24 +25,21 @@ import React, { useState, ReactElement } from 'react';
import ReactDOM from 'react-dom';
import angular from 'angular';

import { Observable, pipe, Subscription } from 'rxjs';
import { filter, map, mapTo, startWith, switchMap } from 'rxjs/operators';
import { Observable, pipe, Subscription, merge } from 'rxjs';
import { filter, map, debounceTime, mapTo, startWith, switchMap } from 'rxjs/operators';
import { History } from 'history';
import { SavedObjectSaveOpts } from 'src/plugins/saved_objects/public';
import { NavigationPublicPluginStart as NavigationStart } from 'src/plugins/navigation/public';
import { TimeRange } from 'src/plugins/data/public';
import { DashboardEmptyScreen, DashboardEmptyScreenProps } from './dashboard_empty_screen';

import {
connectToQueryState,
esFilters,
IndexPattern,
IndexPatternsContract,
Query,
QueryState,
SavedQuery,
syncQueryStateWithUrl,
UI_SETTINGS,
} from '../../../data/public';
import { getSavedObjectFinder, SaveResult, showSaveModal } from '../../../saved_objects/public';

Expand Down Expand Up @@ -81,8 +78,8 @@ import {
addFatalError,
AngularHttpError,
KibanaLegacyStart,
migrateLegacyQuery,
subscribeWithScope,
migrateLegacyQuery,
} from '../../../kibana_legacy/public';

export interface DashboardAppControllerDependencies extends RenderDeps {
Expand Down Expand Up @@ -127,7 +124,6 @@ export class DashboardAppController {
$route,
$routeParams,
dashboardConfig,
localStorage,
indexPatterns,
savedQueryService,
embeddable,
Expand All @@ -153,8 +149,8 @@ export class DashboardAppController {
navigation,
}: DashboardAppControllerDependencies) {
const filterManager = queryService.filterManager;
const queryFilter = filterManager;
const timefilter = queryService.timefilter.timefilter;
const queryStringManager = queryService.queryString;
const isEmbeddedExternally = Boolean($routeParams.embed);

// url param rules should only apply when embedded (e.g. url?embed=true)
Expand Down Expand Up @@ -188,20 +184,30 @@ export class DashboardAppController {
// sync initial app filters from state to filterManager
// if there is an existing similar global filter, then leave it as global
filterManager.setAppFilters(_.cloneDeep(dashboardStateManager.appState.filters));
queryStringManager.setQuery(migrateLegacyQuery(dashboardStateManager.appState.query));

// setup syncing of app filters between appState and filterManager
const stopSyncingAppFilters = connectToQueryState(
queryService,
{
set: ({ filters }) => dashboardStateManager.setFilters(filters || []),
get: () => ({ filters: dashboardStateManager.appState.filters }),
set: ({ filters, query }) => {
dashboardStateManager.setFilters(filters || []);
dashboardStateManager.setQuery(query || queryStringManager.getDefaultQuery());
},
get: () => ({
filters: dashboardStateManager.appState.filters,
query: dashboardStateManager.getQuery(),
}),
state$: dashboardStateManager.appState$.pipe(
map((state) => ({
filters: state.filters,
query: queryStringManager.formatQuery(state.query),
}))
),
},
{
filters: esFilters.FilterStateStore.APP_STATE,
query: true,
}
);

Expand Down Expand Up @@ -331,7 +337,7 @@ export class DashboardAppController {
const isEmptyInReadonlyMode = shouldShowUnauthorizedEmptyState();
return {
id: dashboardStateManager.savedDashboard.id || '',
filters: queryFilter.getFilters(),
filters: filterManager.getFilters(),
hidePanelTitles: dashboardStateManager.getHidePanelTitles(),
query: $scope.model.query,
timeRange: {
Expand All @@ -356,7 +362,7 @@ export class DashboardAppController {
// https://github.com/angular/angular.js/wiki/Understanding-Scopes
$scope.model = {
query: dashboardStateManager.getQuery(),
filters: queryFilter.getFilters(),
filters: filterManager.getFilters(),
timeRestore: dashboardStateManager.getTimeRestore(),
title: dashboardStateManager.getTitle(),
description: dashboardStateManager.getDescription(),
Expand Down Expand Up @@ -420,12 +426,12 @@ export class DashboardAppController {
if (
!esFilters.compareFilters(
container.getInput().filters,
queryFilter.getFilters(),
filterManager.getFilters(),
esFilters.COMPARE_ALL_OPTIONS
)
) {
// Add filters modifies the object passed to it, hence the clone deep.
queryFilter.addFilters(_.cloneDeep(container.getInput().filters));
filterManager.addFilters(_.cloneDeep(container.getInput().filters));

dashboardStateManager.applyFilters(
$scope.model.query,
Expand Down Expand Up @@ -487,13 +493,8 @@ export class DashboardAppController {
});

dashboardStateManager.applyFilters(
dashboardStateManager.getQuery() || {
query: '',
language:
localStorage.get('kibana.userQueryLanguage') ||
uiSettings.get(UI_SETTINGS.SEARCH_QUERY_LANGUAGE),
},
queryFilter.getFilters()
dashboardStateManager.getQuery() || queryStringManager.getDefaultQuery(),
filterManager.getFilters()
);

timefilter.disableTimeRangeSelector();
Expand Down Expand Up @@ -567,21 +568,13 @@ export class DashboardAppController {
}
};

$scope.updateQueryAndFetch = function ({ query, dateRange }) {
if (dateRange) {
timefilter.setTime(dateRange);
}

const oldQuery = $scope.model.query;
if (_.isEqual(oldQuery, query)) {
$scope.handleRefresh = function (_payload, isUpdate) {
if (isUpdate === false) {
// The user can still request a reload in the query bar, even if the
// query is the same, and in that case, we have to explicitly ask for
// a reload, since no state changes will cause it.
lastReloadRequestTime = new Date().getTime();
refreshDashboardContainer();
} else {
$scope.model.query = query;
dashboardStateManager.applyFilters($scope.model.query, $scope.model.filters);
}
};

Expand All @@ -600,7 +593,7 @@ export class DashboardAppController {
// Making this method sync broke the updates.
// Temporary fix, until we fix the complex state in this file.
setTimeout(() => {
queryFilter.setFilters(allFilters);
filterManager.setFilters(allFilters);
}, 0);
};

Expand Down Expand Up @@ -633,11 +626,6 @@ export class DashboardAppController {

$scope.indexPatterns = [];

$scope.$watch('model.query', (newQuery: Query) => {
const query = migrateLegacyQuery(newQuery) as Query;
$scope.updateQueryAndFetch({ query });
});

$scope.$watch(
() => dashboardCapabilities.saveQuery,
(newCapability) => {
Expand Down Expand Up @@ -678,18 +666,11 @@ export class DashboardAppController {
showFilterBar,
indexPatterns: $scope.indexPatterns,
showSaveQuery: $scope.showSaveQuery,
query: $scope.model.query,
savedQuery: $scope.savedQuery,
onSavedQueryIdChange,
savedQueryId: dashboardStateManager.getSavedQueryId(),
useDefaultBehaviors: true,
onQuerySubmit: (payload: { dateRange: TimeRange; query?: Query }): void => {
if (!payload.query) {
$scope.updateQueryAndFetch({ query: $scope.model.query, dateRange: payload.dateRange });
} else {
$scope.updateQueryAndFetch({ query: payload.query, dateRange: payload.dateRange });
}
},
onQuerySubmit: $scope.handleRefresh,
};
};
const dashboardNavBar = document.getElementById('dashboardChrome');
Expand All @@ -704,25 +685,11 @@ export class DashboardAppController {
};

$scope.timefilterSubscriptions$ = new Subscription();

const timeChanges$ = merge(timefilter.getRefreshIntervalUpdate$(), timefilter.getTimeUpdate$());
$scope.timefilterSubscriptions$.add(
subscribeWithScope(
$scope,
timefilter.getRefreshIntervalUpdate$(),
{
next: () => {
updateState();
refreshDashboardContainer();
},
},
(error: AngularHttpError | Error | string) => addFatalError(fatalErrors, error)
)
);

$scope.timefilterSubscriptions$.add(
subscribeWithScope(
$scope,
timefilter.getTimeUpdate$(),
timeChanges$,
{
next: () => {
updateState();
Expand Down Expand Up @@ -1095,13 +1062,21 @@ export class DashboardAppController {

updateViewMode(dashboardStateManager.getViewMode());

const filterChanges = merge(filterManager.getUpdates$(), queryStringManager.getUpdates$()).pipe(
debounceTime(100)
);

// update root source when filters update
const updateSubscription = queryFilter.getUpdates$().subscribe({
const updateSubscription = filterChanges.subscribe({
next: () => {
$scope.model.filters = queryFilter.getFilters();
$scope.model.filters = filterManager.getFilters();
$scope.model.query = queryStringManager.getQuery();
dashboardStateManager.applyFilters($scope.model.query, $scope.model.filters);
if (dashboardContainer) {
dashboardContainer.updateInput({ filters: $scope.model.filters });
dashboardContainer.updateInput({
filters: $scope.model.filters,
query: $scope.model.query,
});
}
},
});
Expand Down
9 changes: 6 additions & 3 deletions src/plugins/data/public/public.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,10 +199,11 @@ export const castEsToKbnFieldTypeName: (esType: ES_FIELD_TYPES | string) => KBN_
// Warning: (ae-missing-release-tag) "connectToQueryState" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public
export const connectToQueryState: <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;
refreshInterval?: boolean;
filters?: FilterStateStore | boolean;
query?: boolean;
}) => () => void;

// Warning: (ae-missing-release-tag) "createSavedQueryService" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
Expand Down Expand Up @@ -1397,6 +1398,8 @@ export interface QueryState {
// (undocumented)
filters?: Filter[];
// (undocumented)
query?: Query;
// (undocumented)
refreshInterval?: RefreshInterval;
// (undocumented)
time?: TimeRange;
Expand Down Expand Up @@ -1771,7 +1774,7 @@ export type StatefulSearchBarProps = SearchBarOwnProps & {
// Warning: (ae-missing-release-tag) "syncQueryStateWithUrl" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public
export const syncQueryStateWithUrl: (query: Pick<QueryStart | QuerySetup, 'filterManager' | 'timefilter' | 'state$'>, kbnUrlStateStorage: IKbnUrlStateStorage) => {
export const syncQueryStateWithUrl: (query: Pick<QueryStart | QuerySetup, 'filterManager' | 'timefilter' | 'queryString' | 'state$'>, kbnUrlStateStorage: IKbnUrlStateStorage) => {
stop: () => void;
hasInheritedQueryFromUrl: boolean;
};
Expand Down Expand Up @@ -1919,7 +1922,7 @@ export const UI_SETTINGS: {
// src/plugins/data/public/index.ts:391:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:392:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:395:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/query/state_sync/connect_to_query_state.ts:41:60 - (ae-forgotten-export) The symbol "FilterStateStore" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/query/state_sync/connect_to_query_state.ts:45:5 - (ae-forgotten-export) The symbol "FilterStateStore" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/types.ts:54:5 - (ae-forgotten-export) The symbol "createFiltersFromValueClickAction" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/types.ts:55:5 - (ae-forgotten-export) The symbol "createFiltersFromRangeSelectAction" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/types.ts:63:5 - (ae-forgotten-export) The symbol "IndexPatternSelectProps" needs to be exported by the entry point index.d.ts
Expand Down
Loading

0 comments on commit 25ceed9

Please sign in to comment.