Skip to content

Commit

Permalink
[Data] Query Input String manager (#72093)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
4 people committed Jul 29, 2020
1 parent b729446 commit 1414415
Show file tree
Hide file tree
Showing 45 changed files with 478 additions and 337 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
Loading

0 comments on commit 1414415

Please sign in to comment.