mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
Merge pull request #8624 from elastic/jasper/backport/8208/5.0
[backport] PR #8208 to 5.0
This commit is contained in:
commit
772889f464
14 changed files with 206 additions and 10 deletions
|
@ -17,7 +17,6 @@ import uiRoutes from 'ui/routes';
|
|||
import uiModules from 'ui/modules';
|
||||
import editorTemplate from 'plugins/kibana/visualize/editor/editor.html';
|
||||
|
||||
|
||||
uiRoutes
|
||||
.when('/visualize/create', {
|
||||
template: editorTemplate,
|
||||
|
@ -77,16 +76,29 @@ function VisEditor($scope, $route, timefilter, AppState, $location, kbnUrl, $tim
|
|||
let stateMonitor;
|
||||
const $appStatus = this.appStatus = {};
|
||||
|
||||
// Retrieve the resolved SavedVis instance.
|
||||
const savedVis = $route.current.locals.savedVis;
|
||||
|
||||
// Instance of src/ui/public/vis/vis.js.
|
||||
const vis = savedVis.vis;
|
||||
|
||||
// Clone the _vis instance.
|
||||
const editableVis = vis.createEditableVis();
|
||||
|
||||
// We intend to keep editableVis and vis in sync with one another, so calling `requesting` on
|
||||
// vis should call it on both.
|
||||
vis.requesting = function () {
|
||||
const requesting = editableVis.requesting;
|
||||
// Invoking requesting() calls onRequest on each agg's type param. When a vis is marked as being
|
||||
// requested, the bounds of that vis are updated and new data is fetched using the new bounds.
|
||||
requesting.call(vis);
|
||||
|
||||
// We need to keep editableVis in sync with vis.
|
||||
requesting.call(editableVis);
|
||||
};
|
||||
|
||||
// SearchSource is a promise-based stream of search results that can inherit from other search
|
||||
// sources.
|
||||
const searchSource = savedVis.searchSource;
|
||||
|
||||
$scope.topNavMenu = [{
|
||||
|
@ -115,6 +127,8 @@ function VisEditor($scope, $route, timefilter, AppState, $location, kbnUrl, $tim
|
|||
docTitle.change(savedVis.title);
|
||||
}
|
||||
|
||||
// Extract visualization state with filtered aggs. You can see these filtered aggs in the URL.
|
||||
// Consists of things like aggs, params, listeners, title, type, etc.
|
||||
const savedVisState = vis.getState();
|
||||
const stateDefaults = {
|
||||
uiState: savedVis.uiStateJSON ? JSON.parse(savedVis.uiStateJSON) : {},
|
||||
|
@ -124,12 +138,17 @@ function VisEditor($scope, $route, timefilter, AppState, $location, kbnUrl, $tim
|
|||
vis: savedVisState
|
||||
};
|
||||
|
||||
// Instance of app_state.js.
|
||||
let $state = $scope.$state = (function initState() {
|
||||
$state = new AppState(stateDefaults);
|
||||
// This is used to sync visualization state with the url when `appState.save()` is called.
|
||||
const appState = new AppState(stateDefaults);
|
||||
|
||||
if (!angular.equals($state.vis, savedVisState)) {
|
||||
// The savedVis is pulled from elasticsearch, but the appState is pulled from the url, with the
|
||||
// defaults applied. If the url was from a previous session which included modifications to the
|
||||
// appState then they won't be equal.
|
||||
if (!angular.equals(appState.vis, savedVisState)) {
|
||||
Promise.try(function () {
|
||||
editableVis.setState($state.vis);
|
||||
editableVis.setState(appState.vis);
|
||||
vis.setState(editableVis.getEnabledState());
|
||||
})
|
||||
.catch(courier.redirectWhenMissing({
|
||||
|
@ -137,7 +156,7 @@ function VisEditor($scope, $route, timefilter, AppState, $location, kbnUrl, $tim
|
|||
}));
|
||||
}
|
||||
|
||||
return $state;
|
||||
return appState;
|
||||
}());
|
||||
|
||||
function init() {
|
||||
|
@ -148,10 +167,16 @@ function VisEditor($scope, $route, timefilter, AppState, $location, kbnUrl, $tim
|
|||
$scope.indexPattern = vis.indexPattern;
|
||||
$scope.editableVis = editableVis;
|
||||
$scope.state = $state;
|
||||
|
||||
// Create a PersistedState instance.
|
||||
$scope.uiState = $state.makeStateful('uiState');
|
||||
$scope.appStatus = $appStatus;
|
||||
|
||||
// Associate PersistedState instance with the Vis instance, so that
|
||||
// `uiStateVal` can be called on it. Currently this is only used to extract
|
||||
// map-specific information (e.g. mapZoom, mapCenter).
|
||||
vis.setUiState($scope.uiState);
|
||||
|
||||
$scope.timefilter = timefilter;
|
||||
$scope.opts = _.pick($scope, 'doSave', 'savedVis', 'shareData', 'timefilter');
|
||||
|
||||
|
@ -258,6 +283,9 @@ function VisEditor($scope, $route, timefilter, AppState, $location, kbnUrl, $tim
|
|||
kbnUrl.change('/visualize', {});
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when the user clicks "Save" button.
|
||||
*/
|
||||
$scope.doSave = function () {
|
||||
savedVis.id = savedVis.title;
|
||||
// vis.title was not bound and it's needed to reflect title into visState
|
||||
|
|
|
@ -1,6 +1,15 @@
|
|||
/**
|
||||
* @name SavedVis
|
||||
*
|
||||
* @extends SavedObject.
|
||||
*
|
||||
* NOTE: It's a type of SavedObject, but specific to visualizations.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import VisProvider from 'ui/vis';
|
||||
import uiModules from 'ui/modules';
|
||||
|
||||
uiModules
|
||||
.get('app/visualize')
|
||||
.factory('SavedVis', function (config, $injector, courier, Promise, savedSearches, Private, Notifier) {
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
/**
|
||||
* @name _doc_send_to_es
|
||||
*
|
||||
* NOTE: Depends upon the es object to make ES requests, and also interacts
|
||||
* with courier objects.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
|
||||
import errors from 'ui/errors';
|
||||
|
|
|
@ -1,3 +1,13 @@
|
|||
/**
|
||||
* @name DocSource
|
||||
*
|
||||
* NOTE: This class is tightly coupled with _doc_send_to_es. Its primary
|
||||
* methods (`doUpdate`, `doIndex`, `doCreate`) are all proxies for methods
|
||||
* exposed by _doc_send_to_es (`update`, `index`, `create`). These methods are
|
||||
* called with DocSource as the context. When called, they depend on “private”
|
||||
* DocSource methods within their execution.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
|
||||
import 'ui/es';
|
||||
|
|
|
@ -1,3 +1,55 @@
|
|||
/**
|
||||
* @name SearchSource
|
||||
*
|
||||
* @description A promise-based stream of search results that can inherit from other search sources.
|
||||
*
|
||||
* Because filters/queries in Kibana have different levels of persistence and come from different
|
||||
* places, it is important to keep track of where filters come from for when they are saved back to
|
||||
* the savedObject store in the Kibana index. To do this, we create trees of searchSource objects
|
||||
* that can have associated query parameters (index, query, filter, etc) which can also inherit from
|
||||
* other searchSource objects.
|
||||
*
|
||||
* At query time, all of the searchSource objects that have subscribers are "flattened", at which
|
||||
* point the query params from the searchSource are collected while traversing up the inheritance
|
||||
* chain. At each link in the chain a decision about how to merge the query params is made until a
|
||||
* single set of query parameters is created for each active searchSource (a searchSource with
|
||||
* subscribers).
|
||||
*
|
||||
* That set of query parameters is then sent to elasticsearch. This is how the filter hierarchy
|
||||
* works in Kibana.
|
||||
*
|
||||
* Visualize, starting from a new search:
|
||||
*
|
||||
* - the `savedVis.searchSource` is set as the `appSearchSource`.
|
||||
* - The `savedVis.searchSource` would normally inherit from the `appSearchSource`, but now it is
|
||||
* upgraded to inherit from the `rootSearchSource`.
|
||||
* - Any interaction with the visualization will still apply filters to the `appSearchSource`, so
|
||||
* they will be stored directly on the `savedVis.searchSource`.
|
||||
* - Any interaction with the time filter will be written to the `rootSearchSource`, so those
|
||||
* filters will not be saved by the `savedVis`.
|
||||
* - When the `savedVis` is saved to elasticsearch, it takes with it all the filters that are
|
||||
* defined on it directly, but none of the ones that it inherits from other places.
|
||||
*
|
||||
* Visualize, starting from an existing search:
|
||||
*
|
||||
* - The `savedVis` loads the `savedSearch` on which it is built.
|
||||
* - The `savedVis.searchSource` is set to inherit from the `saveSearch.searchSource` and set as
|
||||
* the `appSearchSource`.
|
||||
* - The `savedSearch.searchSource`, is set to inherit from the `rootSearchSource`.
|
||||
* - Then the `savedVis` is written to elasticsearch it will be flattened and only include the
|
||||
* filters created in the visualize application and will reconnect the filters from the
|
||||
* `savedSearch` at runtime to prevent losing the relationship
|
||||
*
|
||||
* Dashboard search sources:
|
||||
*
|
||||
* - Each panel in a dashboard has a search source.
|
||||
* - The `savedDashboard` also has a searchsource, and it is set as the `appSearchSource`.
|
||||
* - Each panel's search source inherits from the `appSearchSource`, meaning that they inherit from
|
||||
* the dashboard search source.
|
||||
* - When a filter is added to the search box, or via a visualization, it is written to the
|
||||
* `appSearchSource`.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
|
||||
import NormalizeSortRequestProvider from './_normalize_sort_request';
|
||||
|
|
|
@ -1,3 +1,14 @@
|
|||
/**
|
||||
* @name SavedObject
|
||||
*
|
||||
* NOTE: SavedObject seems to track a reference to an object in ES,
|
||||
* and surface methods for CRUD functionality (save and delete). This seems
|
||||
* similar to how Backbone Models work.
|
||||
*
|
||||
* This class seems to interface with ES primarily through the es Angular
|
||||
* service and a DocSource instance.
|
||||
*/
|
||||
|
||||
import angular from 'angular';
|
||||
import _ from 'lodash';
|
||||
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
/**
|
||||
* @name es
|
||||
*
|
||||
* @description This is the result of calling esFactory. esFactory is exposed by the
|
||||
* elasticsearch.angular.js client.
|
||||
*/
|
||||
|
||||
import 'elasticsearch-browser';
|
||||
import _ from 'lodash';
|
||||
import uiModules from 'ui/modules';
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
/**
|
||||
* @name Events
|
||||
*
|
||||
* @extends SimpleEmitter
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import Notifier from 'ui/notify/notifier';
|
||||
import SimpleEmitter from 'ui/utils/simple_emitter';
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
/**
|
||||
* @name PersistedState
|
||||
*
|
||||
* @extends Events
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import toPath from 'lodash/internal/toPath';
|
||||
import errors from 'ui/errors';
|
||||
|
@ -269,4 +275,4 @@ export default function (Private) {
|
|||
};
|
||||
|
||||
return PersistedState;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,3 +1,13 @@
|
|||
/**
|
||||
* @name AppState
|
||||
*
|
||||
* @extends State
|
||||
*
|
||||
* @description Inherits State, which inherits Events. This class seems to be
|
||||
* concerned with mapping "props" to PersistedState instances, and surfacing the
|
||||
* ability to destroy those mappings.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import modules from 'ui/modules';
|
||||
import StateManagementStateProvider from 'ui/state_management/state';
|
||||
|
@ -12,7 +22,13 @@ function AppStateProvider(Private, $rootScope, $location) {
|
|||
|
||||
_.class(AppState).inherits(State);
|
||||
function AppState(defaults) {
|
||||
// Initialize persistedStates. This object maps "prop" names to
|
||||
// PersistedState instances. These are used to make properties "stateful".
|
||||
persistedStates = {};
|
||||
|
||||
// Initialize eventUnsubscribers. These will be called in `destroy`, to
|
||||
// remove handlers for the 'change' and 'fetch_with_changes' events which
|
||||
// are dispatched via the rootScope.
|
||||
eventUnsubscribers = [];
|
||||
|
||||
AppState.Super.call(this, urlParam, defaults);
|
||||
|
@ -28,6 +44,9 @@ function AppStateProvider(Private, $rootScope, $location) {
|
|||
_.callEach(eventUnsubscribers);
|
||||
};
|
||||
|
||||
/**
|
||||
* @returns PersistedState instance.
|
||||
*/
|
||||
AppState.prototype.makeStateful = function (prop) {
|
||||
if (persistedStates[prop]) return persistedStates[prop];
|
||||
let self = this;
|
||||
|
@ -38,8 +57,8 @@ function AppStateProvider(Private, $rootScope, $location) {
|
|||
// update the app state when the stateful instance changes
|
||||
let updateOnChange = function () {
|
||||
let replaceState = false; // TODO: debouncing logic
|
||||
|
||||
self[prop] = persistedStates[prop].getChanges();
|
||||
// Save state to the URL.
|
||||
self.save(replaceState);
|
||||
};
|
||||
let handlerOnChange = (method) => persistedStates[prop][method]('change', updateOnChange);
|
||||
|
|
|
@ -1,3 +1,11 @@
|
|||
/**
|
||||
* @name State
|
||||
*
|
||||
* @extends Events
|
||||
*
|
||||
* @description Persists generic "state" to and reads it from the URL.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import angular from 'angular';
|
||||
import rison from 'rison-node';
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
/**
|
||||
* @name AggConfig
|
||||
*
|
||||
* @description This class represents an aggregation, which is displayed in the left-hand nav of
|
||||
* the Visualize app.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import RegistryFieldFormatsProvider from 'ui/registry/field_formats';
|
||||
export default function AggConfigFactory(Private, fieldTypeFilter) {
|
||||
|
@ -177,7 +184,7 @@ export default function AggConfigFactory(Private, fieldTypeFilter) {
|
|||
|
||||
/**
|
||||
* Hook into param onRequest handling, and tell the aggConfig that it
|
||||
* is being sent to elasticsearc.
|
||||
* is being sent to elasticsearch.
|
||||
*
|
||||
* @return {[type]} [description]
|
||||
*/
|
||||
|
@ -189,7 +196,7 @@ export default function AggConfigFactory(Private, fieldTypeFilter) {
|
|||
};
|
||||
|
||||
/**
|
||||
* Convert this aggConfig to it's dsl syntax.
|
||||
* Convert this aggConfig to its dsl syntax.
|
||||
*
|
||||
* Adds params and adhoc subaggs to a pojo, then returns it
|
||||
*
|
||||
|
|
|
@ -1,3 +1,12 @@
|
|||
/**
|
||||
* @name AggConfig
|
||||
*
|
||||
* @extends IndexedArray
|
||||
*
|
||||
* @description A "data structure"-like class with methods for indexing and
|
||||
* accessing instances of AggConfig.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import IndexedArray from 'ui/indexed_array';
|
||||
import VisAggConfigProvider from 'ui/vis/agg_config';
|
||||
|
|
|
@ -1,3 +1,13 @@
|
|||
/**
|
||||
* @name Vis
|
||||
*
|
||||
* @description This class consists of aggs, params, listeners, title, and type.
|
||||
* - Aggs: Instances of AggConfig.
|
||||
* - Params: The settings in the Options tab.
|
||||
*
|
||||
* Not to be confused with vislib/vis.js.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import AggTypesIndexProvider from 'ui/agg_types/index';
|
||||
import RegistryVisTypesProvider from 'ui/registry/vis_types';
|
||||
|
@ -24,7 +34,6 @@ export default function VisFactory(Notifier, Private) {
|
|||
|
||||
this.indexPattern = indexPattern;
|
||||
|
||||
// http://aphyr.com/data/posts/317/state.gif
|
||||
this.setState(state);
|
||||
this.setUiState(uiState);
|
||||
}
|
||||
|
@ -36,6 +45,8 @@ export default function VisFactory(Notifier, Private) {
|
|||
|
||||
let schemas = type.schemas;
|
||||
|
||||
// This was put in place to do migrations at runtime. It's used to support people who had saved
|
||||
// visualizations during the 4.0 betas.
|
||||
let aggs = _.transform(oldState, function (newConfigs, oldConfigs, oldGroupName) {
|
||||
let schema = schemas.all.byName[oldGroupName];
|
||||
|
||||
|
@ -119,6 +130,7 @@ export default function VisFactory(Notifier, Private) {
|
|||
};
|
||||
|
||||
Vis.prototype.requesting = function () {
|
||||
// Invoke requesting() on each agg. Aggs is an instance of AggConfigs.
|
||||
_.invoke(this.aggs.getRequestAggs(), 'requesting');
|
||||
};
|
||||
|
||||
|
@ -149,6 +161,11 @@ export default function VisFactory(Notifier, Private) {
|
|||
Vis.prototype.getUiState = function () {
|
||||
return this.__uiState;
|
||||
};
|
||||
|
||||
/**
|
||||
* Currently this is only used to extract map-specific information
|
||||
* (e.g. mapZoom, mapCenter).
|
||||
*/
|
||||
Vis.prototype.uiStateVal = function (key, val) {
|
||||
if (this.hasUiState()) {
|
||||
if (_.isUndefined(val)) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue