From 6cb518b035f805a435fda5e2a524f28115b1e1a7 Mon Sep 17 00:00:00 2001 From: Spencer Alger Date: Wed, 26 Feb 2014 10:09:26 -0700 Subject: [PATCH] Discovery Panel Skeleton - renamed calculateIndices files to be calculate_indices - hooked up dataSource.getFields() to the mapper, uses source._wrapcb() to properly trigger digest cycles - fixed an error handling bug in the search source - courier error DocFetchFailure is now just FetchFailure and will be sent from SearchSources as well - courier/index_pattern module is now gone - fixed a bug in the mapper that improperly triggered errors when a pattern matched multiple indices that had similar fields - prototype for discover app is included - directives/kbn_view module is now directives/view, the kbn prefix is implied - added basic table directive --- src/courier/courier.js | 31 +++--- src/courier/data_source/data_source.js | 66 +++++++------ src/courier/data_source/doc.js | 7 +- src/courier/data_source/search.js | 3 +- src/courier/errors.js | 8 +- src/courier/index_pattern.js | 23 ----- src/courier/mapper.js | 12 +-- src/kibana/apps/dashboard/styles/main.html | 0 src/kibana/apps/discover/index.html | 44 ++++++++- src/kibana/apps/discover/index.js | 97 +++++++++++++++++++ src/kibana/apps/discover/styles/main.html | 0 src/kibana/apps/visualize/styles/main.html | 0 src/kibana/directives/table.js | 51 ++++++++++ .../directives/{kbn_view.js => view.js} | 0 src/kibana/index.js | 2 +- src/kibana/partials/table.html | 12 +++ src/kibana/require.config.js | 1 + src/kibana/styles/_bootstrap.less | 55 +++++++++++ src/kibana/styles/main.css | 4 +- src/kibana/styles/main.less | 5 +- tasks/config/less.js | 2 +- test/unit/index.html | 2 +- 22 files changed, 338 insertions(+), 87 deletions(-) delete mode 100644 src/courier/index_pattern.js create mode 100644 src/kibana/apps/dashboard/styles/main.html create mode 100644 src/kibana/apps/discover/styles/main.html create mode 100644 src/kibana/apps/visualize/styles/main.html create mode 100644 src/kibana/directives/table.js rename src/kibana/directives/{kbn_view.js => view.js} (100%) create mode 100644 src/kibana/partials/table.html create mode 100644 src/kibana/styles/_bootstrap.less diff --git a/src/courier/courier.js b/src/courier/courier.js index 54b4349a6042..be3e0fcc607c 100644 --- a/src/courier/courier.js +++ b/src/courier/courier.js @@ -24,7 +24,11 @@ define(function (require) { // execute a search right now search: function (courier) { if (courier._activeSearchRequest) { - return courier._error(new HastyRefresh()); + // ensure that this happens async, otherwise listeners + // might miss error events + return nextTick(function () { + courier._error(new HastyRefresh()); + }); } courier._activeSearchRequest = SearchSource.fetch( @@ -174,19 +178,24 @@ define(function (require) { }, this); }; + // be default, the courier will throw an error if a fetch + // occurs before a previous fetch finishes. To prevent this, you + // should call abort before calling .fetch() + Courier.prototype.abort = function () { + if (this._activeSearchRequest) { + this._activeSearchRequest.abort(); + this._activeSearchRequest = null; + } + }; + // force a fetch of all datasources right now, optionally filter by type Courier.prototype.fetch = function (onlyType) { var courier = this; - // ensure that onFetch functions always run after the tick - // so that users can will be able to listen after or before the call to - // fetch and always get the same behavior (even if the onFetch runs synchronously) - nextTick(function () { - _.forOwn(onFetch, function (fn, type) { - if (onlyType && onlyType !== type) return; - if (courier._refs[type].length) fn(courier); - courier._refs[type].forEach(function (ref) { - ref.fetchCount ++; - }); + _.forOwn(onFetch, function (fn, type) { + if (onlyType && onlyType !== type) return; + if (courier._refs[type].length) fn(courier); + courier._refs[type].forEach(function (ref) { + ref.fetchCount ++; }); }); }; diff --git a/src/courier/data_source/data_source.js b/src/courier/data_source/data_source.js index 2d1bac072acf..2241157d86b1 100644 --- a/src/courier/data_source/data_source.js +++ b/src/courier/data_source/data_source.js @@ -3,7 +3,6 @@ define(function (require) { var _ = require('lodash'); var EventEmitter = require('utils/event_emitter'); var Mapper = require('courier/mapper'); - var IndexPattern = require('courier/index_pattern'); function DataSource(courier, initialState) { var state; @@ -54,9 +53,6 @@ define(function (require) { this._methods.forEach(function (name) { this[name] = function (val) { state[name] = val; - if (name === 'index' && arguments[1]) { - state.index = new IndexPattern(val, arguments[1]); - } return this; }; }, this); @@ -81,8 +77,8 @@ define(function (require) { * @callback {Error, Array} - calls cb with a possible error or an array of field names * @todo */ - DataSource.prototype.getFieldNames = function (cb) { - throw new Error('not implemented'); + DataSource.prototype.getFields = function (cb) { + this._courier._mapper.getFields(this, this._wrapcb(cb)); }; /** @@ -114,32 +110,33 @@ define(function (require) { * @return {this} - chainable */ DataSource.prototype.$scope = function ($scope) { - var emitter = this; + var courier = this; - if (emitter._emitter$scope) { - emitter._emitter$scope = $scope; + if (courier._$scope) { + // simply change the scope that callbacks will point to + courier._$scope = $scope; return this; } - emitter._emitter$scope = $scope; - var origOn = emitter.on; + courier._$scope = $scope; - emitter.on = function (event, listener) { - var wrapped = function () { - var args = arguments; - // always use the stored ref so that it can be updated if needed - var $scope = emitter._emitter$scope; - $scope[$scope.$$phase ? '$eval' : '$apply'](function () { - listener.apply(emitter, args); - }); - }; + // wrap the 'on' method so that all listeners + // can be wrapped in calls to $scope.$apply + var origOn = courier.on; + courier.on = function (event, listener) { + var wrapped = courier._wrapcb(listener); + // set .listener so that it can be removed by + // .removeListener() using the original function wrapped.listener = listener; - return origOn.call(emitter, event, wrapped); + return origOn.call(courier, event, wrapped); }; - emitter.on.restore = function () { - delete emitter._emitter$scope; - emitter.on = origOn; + // make sure the alias is still set + courier.addListener = courier.on; + + courier.on.restore = function () { + delete courier._$scope; + courier.on = courier.addListener = origOn; }; return this; @@ -213,11 +210,22 @@ define(function (require) { return flatState; }; - DataSource.prototype._resolveIndexPattern = function (start, end) { - if (this._state.indexInterval) { - throw new Error('Not implemented'); - } - return this._state.index; + DataSource.prototype._wrapcb = function (cb) { + var courier = this; + var wrapped = function () { + var args = arguments; + // always use the stored ref so that it can be updated if needed + var $scope = courier._$scope; + + // don't fall apart if we don't have a scope + if (!$scope) return cb.apply(courier, args); + + // use angular's $apply or $eval functions for the given scope + $scope[$scope.$$phase ? '$eval' : '$apply'](function () { + cb.apply(courier, args); + }); + }; + return wrapped; }; return DataSource; diff --git a/src/courier/data_source/doc.js b/src/courier/data_source/doc.js index b742d4d5b5d2..789677e2920c 100644 --- a/src/courier/data_source/doc.js +++ b/src/courier/data_source/doc.js @@ -2,7 +2,8 @@ define(function (require) { var DataSource = require('courier/data_source/data_source'); var inherits = require('utils/inherits'); var nextTick = require('utils/next_tick'); - var errors = require('courier/errors'); + var VersionConflict = require('courier/errors').VersionConflict; + var FetchFailure = require('courier/errors').FetchFailure; var listenerCount = require('utils/event_emitter').listenerCount; var _ = require('lodash'); @@ -42,7 +43,7 @@ define(function (require) { var ref = allRefs[i]; var source = ref.source; - if (resp.error) return source._error(new errors.DocFetchFailure(resp)); + if (resp.error) return source._error(new FetchFailure(resp)); if (resp.found) { if (ref.version === resp._version) return; // no change ref.version = resp._version; @@ -147,7 +148,7 @@ define(function (require) { if (err) return cb(err); if (resp && resp.status === 409) { - err = new errors.VersionConflict(resp); + err = new VersionConflict(resp); if (listenerCount(source, 'conflict')) { return source.emit('conflict', err); } else { diff --git a/src/courier/data_source/search.js b/src/courier/data_source/search.js index 8741d7adf78c..66ceaba587ce 100644 --- a/src/courier/data_source/search.js +++ b/src/courier/data_source/search.js @@ -2,6 +2,7 @@ define(function (require) { var DataSource = require('courier/data_source/data_source'); var inherits = require('utils/inherits'); var errors = require('courier/errors'); + var FetchFailure = require('courier/errors').FetchFailure; var _ = require('lodash'); function SearchSource(courier, initialState) { @@ -44,7 +45,7 @@ define(function (require) { _.each(resp.responses, function (resp, i) { var source = allRefs[i]; - if (resp.error) return errors.emit(source, courier, resp); + if (resp.error) return source._error(new FetchFailure(resp)); source.emit('results', resp); }); diff --git a/src/courier/errors.js b/src/courier/errors.js index fac65a0a91f1..9a8196adb53c 100644 --- a/src/courier/errors.js +++ b/src/courier/errors.js @@ -38,17 +38,17 @@ define(function (require) { /** - * DocFetchFailure Error - where there is an error getting a doc + * FetchFailure Error - where there is an error getting a doc * @param {String} [msg] - An error message that will probably end up in a log. */ - errors.DocFetchFailure = function DocFetchFailure(resp) { + errors.FetchFailure = function FetchFailure(resp) { CourierError.call(this, 'Failed to get the doc: ' + JSON.stringify(resp), - errors.DocFetchFailure); + errors.FetchFailure); this.resp = resp; }; - inherits(errors.DocFetchFailure, CourierError); + inherits(errors.FetchFailure, CourierError); /** diff --git a/src/courier/index_pattern.js b/src/courier/index_pattern.js deleted file mode 100644 index f6a14c1fbf20..000000000000 --- a/src/courier/index_pattern.js +++ /dev/null @@ -1,23 +0,0 @@ -define(function (require) { - function IndexPattern(index, interval) { - this.text = index; - this.interval = interval; - } - - IndexPattern.prototype = { - toJSON: function () { - return this.text; - }, - toString: function () { - return this.text; - }, - forTimeRange: function () { - throw new Error('not implemented'); - }, - wildcard: function () { - throw new Error('not implemented'); - } - }; - - return IndexPattern; -}); \ No newline at end of file diff --git a/src/courier/mapper.js b/src/courier/mapper.js index f5b6c3be070c..fc14762d8b2c 100644 --- a/src/courier/mapper.js +++ b/src/courier/mapper.js @@ -133,17 +133,15 @@ define(function (require) { // TODO: Add week/month check client.indices.getFieldMapping(params, function (err, response, status) { - - // TODO: Add error message - var fields = {}; - _.each(response, function (index) { + _.each(response, function (index, indexName) { + if (indexName === config.cacheIndex) return; _.each(index.mappings, function (type) { _.each(type, function (field, name) { - if (_.isUndefined(field.mapping) || name[0] === '_') return; - if (!_.isUndefined(fields[name]) && fields[name] !== field.mapping[_.keys(field.mapping)[0]]) - return courier._error(new Error.MappingConflict()); + if (_.size(field.mapping) === 0 || name[0] === '_') return; + if (!_.isUndefined(fields[name]) && fields[name].type !== field.mapping[_.keys(field.mapping)[0]].type) + return courier._error(new Error.MappingConflict(name)); fields[name] = field.mapping[_.keys(field.mapping)[0]]; }); }); diff --git a/src/kibana/apps/dashboard/styles/main.html b/src/kibana/apps/dashboard/styles/main.html new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/kibana/apps/discover/index.html b/src/kibana/apps/discover/index.html index 96554298c6d7..5bf9d8987181 100644 --- a/src/kibana/apps/discover/index.html +++ b/src/kibana/apps/discover/index.html @@ -1 +1,43 @@ -

Discover

\ No newline at end of file +
+

Discover

+
+
+ +
+ +
+
+ +
+ +
+
+ + + + +
+
+
+
+
+ + +
+ +
\ No newline at end of file diff --git a/src/kibana/apps/discover/index.js b/src/kibana/apps/discover/index.js index ad925a4e0851..3e96fea96dd0 100644 --- a/src/kibana/apps/discover/index.js +++ b/src/kibana/apps/discover/index.js @@ -1,6 +1,103 @@ define(function (require) { var angular = require('angular'); + var _ = require('lodash'); + + require('directives/table'); require('css!./styles/main.css'); var app = angular.module('app/discover', []); + + var sizeOptions = [ + { display: '30', val: 30 }, + { display: '50', val: 50 }, + { display: '80', val: 80 }, + { display: '125', val: 125 }, + { display: '250', val: 250 }, + { display: 'Unlimited', val: null }, + ]; + + var intervalOptions = [ + { display: '', val: null }, + { display: 'Hourly', val: 'hourly' }, + { display: 'Daily', val: 'daily' }, + { display: 'Weekly', val: 'weekly' }, + { display: 'Monthly', val: 'monthly' }, + { display: 'Yearly', val: 'yearly' } + ]; + + app.controller('discover', function ($scope, courier, config) { + var source = courier.rootSearchSource.extend() + .size(30) + .$scope($scope) + .on('results', function (res) { + if (!$scope.fields) getFields(); + $scope.rows = res.hits.hits; + }); + + // stores the complete list of fields + $scope.fields = []; + + // stores the fields we want to fetch + $scope.columns = []; + + // At what interval are your index patterns + $scope.intervalOptions = intervalOptions; + $scope.interval = $scope.intervalOptions[0]; + + // options to control the size of the queries + $scope.sizeOptions = sizeOptions; + $scope.size = $scope.sizeOptions[0]; + + // watch the discover.defaultIndex config value for changes + config.$watch('discover.defaultIndex', function (val) { + if (!val) { + config.set('discover.defaultIndex', '_all'); + return; + } + // only set if datasource doesn't have an index + if (!source.get('index')) $scope.index = val; + }); + + $scope.$watch('index', function (val) { + // set the index on the data source + source.index(val); + // clear the columns and fields, then refetch when we so a search + $scope.columns = $scope.fields = null; + }); + + $scope.$watch('query', function (query) { + if (query) { + source.query({ + query_string: { + query: query + } + }); + } else { + // clear the query + source.query(null); + } + }); + + $scope.$watch('size', function (selectedSize) { + source.size(selectedSize.val); + }); + + $scope.reset = function () { + // the check happens only when the results come in; prevents a race condition + // if (!$scope.fields) getFields(); + courier.abort(); + courier.fetch(); + }; + + function getFields() { + source.getFields(function (err, fields) { + $scope.fields = fields; + $scope.columns = _.keys(fields); + source.source({ + include: $scope.columns + }); + }); + } + + }); }); \ No newline at end of file diff --git a/src/kibana/apps/discover/styles/main.html b/src/kibana/apps/discover/styles/main.html new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/kibana/apps/visualize/styles/main.html b/src/kibana/apps/visualize/styles/main.html new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/kibana/directives/table.js b/src/kibana/directives/table.js new file mode 100644 index 000000000000..91590d572715 --- /dev/null +++ b/src/kibana/directives/table.js @@ -0,0 +1,51 @@ +define(function (require) { + var html = require('text!partials/table.html'); + var angular = require('angular'); + var _ = require('lodash'); + + var module = angular.module('kibana/directives'); + + /** + * kbnTable directive + * + * displays results in a simple table view. Pass the result object + * via the results attribute on the kbnTable element: + * ``` + * + * ``` + */ + + var defaults = { + columns: [], + rows: [] + }; + + module.directive('kbnTable', function () { + return { + restrict: 'E', + template: html, + scope: { + columns: '=', + rows: '=' + }, + controller: function ($scope) { + _.defaults($scope, defaults); + + $scope.makeRowHtml = function (row) { + var html = ''; + _.each($scope.columns, function (col) { + html += ''; + if (row[col] !== void 0) { + html += row[col]; + } else { + html += row._source[col]; + } + html += ''; + }); + html += ''; + return html; + }; + } + }; + }); +}); \ No newline at end of file diff --git a/src/kibana/directives/kbn_view.js b/src/kibana/directives/view.js similarity index 100% rename from src/kibana/directives/kbn_view.js rename to src/kibana/directives/view.js diff --git a/src/kibana/index.js b/src/kibana/index.js index 61cf83050623..0e3a7172a82c 100644 --- a/src/kibana/index.js +++ b/src/kibana/index.js @@ -77,7 +77,7 @@ define(function (require) { // require global modules require([ 'controllers/kibana', - 'directives/kbn_view', + 'directives/view', 'constants/base' ], loaded()); diff --git a/src/kibana/partials/table.html b/src/kibana/partials/table.html new file mode 100644 index 000000000000..44cb3ef3692c --- /dev/null +++ b/src/kibana/partials/table.html @@ -0,0 +1,12 @@ + + + + + + + + + +
{{col}}
+ {{row._source[col] || row[col]}} +
\ No newline at end of file diff --git a/src/kibana/require.config.js b/src/kibana/require.config.js index 26404bafc1fb..10ddf3abb48e 100644 --- a/src/kibana/require.config.js +++ b/src/kibana/require.config.js @@ -7,6 +7,7 @@ require.config({ 'angular-route': '../bower_components/angular-route/angular-route', async: '../bower_components/async/lib/async', css: '../bower_components/require-css/css', + text: '../bower_components/requirejs-text/text', d3: '../bower_components/d3/d3', elasticsearch: '../bower_components/elasticsearch/elasticsearch.angular', jquery: '../bower_components/jquery/jquery', diff --git a/src/kibana/styles/_bootstrap.less b/src/kibana/styles/_bootstrap.less new file mode 100644 index 000000000000..64120c7aac7b --- /dev/null +++ b/src/kibana/styles/_bootstrap.less @@ -0,0 +1,55 @@ +// Core variables and mixins +@import "../../bower_components/bootstrap/less/variables.less"; + +@icon-font-path: "../../bower_components/bootstrap/fonts/"; + +@import "../../bower_components/bootstrap/less/mixins.less"; + +// Reset +@import "../../bower_components/bootstrap/less/normalize.less"; +@import "../../bower_components/bootstrap/less/print.less"; + +// Core CSS +@import "../../bower_components/bootstrap/less/scaffolding.less"; +@import "../../bower_components/bootstrap/less/type.less"; +@import "../../bower_components/bootstrap/less/code.less"; +@import "../../bower_components/bootstrap/less/grid.less"; +@import "../../bower_components/bootstrap/less/tables.less"; +@import "../../bower_components/bootstrap/less/forms.less"; +@import "../../bower_components/bootstrap/less/buttons.less"; + +// Components +@import "../../bower_components/bootstrap/less/component-animations.less"; +@import "../../bower_components/bootstrap/less/glyphicons.less"; +@import "../../bower_components/bootstrap/less/dropdowns.less"; +@import "../../bower_components/bootstrap/less/button-groups.less"; +@import "../../bower_components/bootstrap/less/input-groups.less"; +@import "../../bower_components/bootstrap/less/navs.less"; +@import "../../bower_components/bootstrap/less/navbar.less"; +@import "../../bower_components/bootstrap/less/breadcrumbs.less"; +@import "../../bower_components/bootstrap/less/pagination.less"; +@import "../../bower_components/bootstrap/less/pager.less"; +@import "../../bower_components/bootstrap/less/labels.less"; +@import "../../bower_components/bootstrap/less/badges.less"; +@import "../../bower_components/bootstrap/less/jumbotron.less"; +@import "../../bower_components/bootstrap/less/thumbnails.less"; +@import "../../bower_components/bootstrap/less/alerts.less"; +@import "../../bower_components/bootstrap/less/progress-bars.less"; +@import "../../bower_components/bootstrap/less/media.less"; +@import "../../bower_components/bootstrap/less/list-group.less"; +@import "../../bower_components/bootstrap/less/panels.less"; +@import "../../bower_components/bootstrap/less/wells.less"; +@import "../../bower_components/bootstrap/less/close.less"; + +// Components w/ JavaScript +@import "../../bower_components/bootstrap/less/modals.less"; +@import "../../bower_components/bootstrap/less/tooltip.less"; +@import "../../bower_components/bootstrap/less/popovers.less"; +@import "../../bower_components/bootstrap/less/carousel.less"; + +// Utility classes +@import "../../bower_components/bootstrap/less/utilities.less"; +@import "../../bower_components/bootstrap/less/responsive-utilities.less"; + +// automatically include the theme file +@import "../../bower_components/bootstrap/less/theme.less"; \ No newline at end of file diff --git a/src/kibana/styles/main.css b/src/kibana/styles/main.css index 2fd2e943a256..9ef6f1b8a41a 100644 --- a/src/kibana/styles/main.css +++ b/src/kibana/styles/main.css @@ -2367,8 +2367,8 @@ input[type="button"].btn-block { } @font-face { font-family: 'Glyphicons Halflings'; - src: url('../fonts/glyphicons-halflings-regular.eot'); - src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg'); + src: url('../../bower_components/bootstrap/fonts/glyphicons-halflings-regular.eot'); + src: url('../../bower_components/bootstrap/fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../../bower_components/bootstrap/fonts/glyphicons-halflings-regular.woff') format('woff'), url('../../bower_components/bootstrap/fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../../bower_components/bootstrap/fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg'); } .glyphicon { position: relative; diff --git a/src/kibana/styles/main.less b/src/kibana/styles/main.less index 833b3769fa35..bc8dbe60662c 100644 --- a/src/kibana/styles/main.less +++ b/src/kibana/styles/main.less @@ -1,6 +1,5 @@ -@import "../../bower_components/bootstrap/less/bootstrap.less"; -@import "../../bower_components/bootstrap/less/theme.less"; +@import "./_bootstrap.less"; body { margin: 60px 10px; -} +} \ No newline at end of file diff --git a/tasks/config/less.js b/tasks/config/less.js index bb7e345f0d93..aa72cedd553d 100644 --- a/tasks/config/less.js +++ b/tasks/config/less.js @@ -3,7 +3,7 @@ module.exports = { src: [ '<%= app %>/styles/**/*.less', '<%= app %>/apps/**/*.less', - '!_*.less' + '!<%= src %>/**/_*.less' ], expand: true, ext: '.css', diff --git a/test/unit/index.html b/test/unit/index.html index edc63104e2fc..21af67da4d96 100644 --- a/test/unit/index.html +++ b/test/unit/index.html @@ -14,7 +14,7 @@ } } }); -require(["/specs/calculateIndices.js","/specs/courier.js","/specs/data_source.js","/specs/mapper.js"], function () { +require(["/specs/calculate_indices.js","/specs/courier.js","/specs/data_source.js","/specs/mapper.js"], function () { window.mochaRunner = mocha.run().on('end', function () { window.mochaResults = this.stats; });