diff --git a/src/core_plugins/kibana/public/visualize/editor/editor.js b/src/core_plugins/kibana/public/visualize/editor/editor.js index 548aebe4684b3..422550b3cca0f 100644 --- a/src/core_plugins/kibana/public/visualize/editor/editor.js +++ b/src/core_plugins/kibana/public/visualize/editor/editor.js @@ -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 diff --git a/src/core_plugins/kibana/public/visualize/saved_visualizations/_saved_vis.js b/src/core_plugins/kibana/public/visualize/saved_visualizations/_saved_vis.js index 20ab2138469b7..0bf171cd90333 100644 --- a/src/core_plugins/kibana/public/visualize/saved_visualizations/_saved_vis.js +++ b/src/core_plugins/kibana/public/visualize/saved_visualizations/_saved_vis.js @@ -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) { diff --git a/src/ui/public/courier/data_source/_doc_send_to_es.js b/src/ui/public/courier/data_source/_doc_send_to_es.js index 5cc07f9e0425e..f7191b03100a9 100644 --- a/src/ui/public/courier/data_source/_doc_send_to_es.js +++ b/src/ui/public/courier/data_source/_doc_send_to_es.js @@ -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'; diff --git a/src/ui/public/courier/data_source/doc_source.js b/src/ui/public/courier/data_source/doc_source.js index 67d6de7e9e6b5..80be1cf7b6095 100644 --- a/src/ui/public/courier/data_source/doc_source.js +++ b/src/ui/public/courier/data_source/doc_source.js @@ -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'; diff --git a/src/ui/public/courier/data_source/search_source.js b/src/ui/public/courier/data_source/search_source.js index fa15f248c92a0..109cca094ca55 100644 --- a/src/ui/public/courier/data_source/search_source.js +++ b/src/ui/public/courier/data_source/search_source.js @@ -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'; diff --git a/src/ui/public/courier/saved_object/saved_object.js b/src/ui/public/courier/saved_object/saved_object.js index 5c32ccac9064c..8cfe0fe1bb39f 100644 --- a/src/ui/public/courier/saved_object/saved_object.js +++ b/src/ui/public/courier/saved_object/saved_object.js @@ -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'; diff --git a/src/ui/public/es.js b/src/ui/public/es.js index dfbceaeec2b45..def095dc08b41 100644 --- a/src/ui/public/es.js +++ b/src/ui/public/es.js @@ -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'; diff --git a/src/ui/public/events.js b/src/ui/public/events.js index 5f7a29ccf398b..1a6b5e39934dc 100644 --- a/src/ui/public/events.js +++ b/src/ui/public/events.js @@ -1,3 +1,9 @@ +/** + * @name Events + * + * @extends SimpleEmitter + */ + import _ from 'lodash'; import Notifier from 'ui/notify/notifier'; import SimpleEmitter from 'ui/utils/simple_emitter'; diff --git a/src/ui/public/persisted_state/persisted_state.js b/src/ui/public/persisted_state/persisted_state.js index 3975c20c24ea6..d883732fb3a08 100644 --- a/src/ui/public/persisted_state/persisted_state.js +++ b/src/ui/public/persisted_state/persisted_state.js @@ -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; -}; \ No newline at end of file +}; diff --git a/src/ui/public/state_management/app_state.js b/src/ui/public/state_management/app_state.js index c2312c5cdcffa..903071a723677 100644 --- a/src/ui/public/state_management/app_state.js +++ b/src/ui/public/state_management/app_state.js @@ -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); diff --git a/src/ui/public/state_management/state.js b/src/ui/public/state_management/state.js index 84f5f6128ca1b..5dc113ecc6faa 100644 --- a/src/ui/public/state_management/state.js +++ b/src/ui/public/state_management/state.js @@ -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'; diff --git a/src/ui/public/vis/agg_config.js b/src/ui/public/vis/agg_config.js index c1250db719b98..6ac5e9349fb05 100644 --- a/src/ui/public/vis/agg_config.js +++ b/src/ui/public/vis/agg_config.js @@ -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 * diff --git a/src/ui/public/vis/agg_configs.js b/src/ui/public/vis/agg_configs.js index ccacd7cafaefa..293cd2504a7fa 100644 --- a/src/ui/public/vis/agg_configs.js +++ b/src/ui/public/vis/agg_configs.js @@ -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'; diff --git a/src/ui/public/vis/vis.js b/src/ui/public/vis/vis.js index f350c4bdbfd50..365cc8163c4a3 100644 --- a/src/ui/public/vis/vis.js +++ b/src/ui/public/vis/vis.js @@ -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)) {