Skip to content

Commit

Permalink
Merge pull request #19 from spenceralger/notification_service
Browse files Browse the repository at this point in the history
Initial phase of notification service
  • Loading branch information
spenceralger committed Mar 12, 2014
2 parents 43605fa + 2eb3d84 commit d06d490
Show file tree
Hide file tree
Showing 17 changed files with 820 additions and 40 deletions.
9 changes: 6 additions & 3 deletions src/index.html
Expand Up @@ -22,9 +22,12 @@
</li>
</ul>
<ul class="nav navbar-nav pull-right">
<li>
<a ng-click="configure()"><i class="fa fa-gear"></i></a>
<li ng-repeat="level in levels">
<a ng-click="notifTest(level.name)" tooltip="{{level.name}}">
<i class="fa" ng-class="'fa-' + level.icon"></i>
</a>
</li>
<li><a ng-click="configure()" tooltip="Configure Kibana"><i class="fa fa-gear"></i></a></li>
</ul>
</div>
</nav>
Expand All @@ -34,7 +37,7 @@
config-submit="saveOpts">
</config>
<div class="application" kbn-view></div>

</div>
<div kbn-notifications list="notifList"></div>
</body>
</html>
52 changes: 50 additions & 2 deletions src/kibana/controllers/kibana.js
Expand Up @@ -6,10 +6,19 @@ define(function (require) {
require('services/config');
require('services/courier');
require('directives/view');
require('angular-bootstrap');

require('modules')
.get('kibana/controllers')
.controller('kibana', function ($scope, courier, config, configFile) {
.get('kibana/controllers', ['ui.bootstrap'])
.config(function ($tooltipProvider) {
$tooltipProvider.options({
placement: 'bottom',
animation: true,
popupDelay: 150,
appendToBody: false
});
})
.controller('kibana', function ($scope, courier, config, configFile, notify, $timeout) {
$scope.apps = configFile.apps;

$scope.$on('$locationChangeSuccess', function (event, uri) {
Expand Down Expand Up @@ -38,6 +47,38 @@ define(function (require) {
$scope.configureTemplateUrl = require('text!../partials/global_config.html');
};

// expose the notification services list of notifs on the $scope so that the
// notification directive can show them on the screen
$scope.notifList = notify._notifs;
// provide alternate methods for setting timeouts, which will properly trigger digest cycles
notify._setTimerFns($timeout, $timeout.cancel);

(function TODO_REMOVE() {
// stuff for testing notifications
$scope.levels = [
{ name: 'info', icon: 'info' },
{ name: 'warning', icon: 'info-circle' },
{ name: 'error', icon: 'warning' },
{ name: 'fatal', icon: 'fire' },
];
$scope.notifTest = function (type) {
var arg = 'Something happened, just thought you should know.';
var cb;
if (type === 'fatal' || type === 'error') {
arg = new Error('Ah fuck');
}
if (type === 'error') {
cb = function (resp) {
if (resp !== 'report') return;
$timeout(function () {
notify.info('Report sent, thank you for your help.');
}, 750);
};
}
notify[type](arg, cb);
};
}());

/**
* Persist current settings
* @return {[type]} [description]
Expand Down Expand Up @@ -71,6 +112,13 @@ define(function (require) {
config.$watch('refreshInterval', $scope.setFetchInterval);
$scope.$watch('opts.activeFetchInterval', $scope.setFetchInterval);


// setup the courier
courier.on('error', function (err) {
$scope[$scope.$$phase ? '$eval' : '$apply'](function () {
notify.error(err);
});
});
$scope.$on('application.load', function () {
courier.start();
});
Expand Down
11 changes: 9 additions & 2 deletions src/kibana/index.js
Expand Up @@ -10,6 +10,7 @@ define(function (require) {
var setup = require('./setup');
var configFile = require('../config');
var modules = require('modules');
var notify = require('notify/notify');

require('elasticsearch');
require('angular-route');
Expand Down Expand Up @@ -46,8 +47,14 @@ define(function (require) {
return 'apps/' + app.id + '/index';
})), function bootstrap() {
$(function () {
angular.bootstrap(document, ['kibana']);
$(document.body).children().show();
notify.lifecycle('bootstrap');
angular
.bootstrap(document, ['kibana'])
.invoke(function (notify) {
notify.lifecycle('bootstrap', true);
$(document.body).children().show();
});

});
});

Expand Down
135 changes: 135 additions & 0 deletions src/kibana/notify/directives.js
@@ -0,0 +1,135 @@
define(function (require) {
var notify = require('modules').get('notify');
var _ = require('lodash');
var $ = require('jquery');
var MutableWatcher = require('utils/mutable_watcher');
var nextTick = require('utils/next_tick');

var defaultToastOpts = {
title: 'Notice',
lifetime: 7000
};

var transformKey = (function () {
var el = document.createElement('div');
return _.find(['transform', 'webkitTransform', 'OTransform', 'MozTransform', 'msTransform'], function (key) {
return el.style[key] !== void 0;
});
}());

notify.directive('kbnNotifications', function () {
return {
restrict: 'A',
scope: {
list: '=list'
},
template: require('text!./partials/toaster.html'),
link: function ($scope, $el) {

$el.addClass('toaster-container');

// handles recalculating positions and offsets, schedules
// recalcs and waits for 100 seconds before running again.
var layoutList = (function () {
// lazy load the $nav element
var navSelector = '.content > nav.navbar:first()';
var $nav;

// pixels between the top of list and it's attachment(nav/window)
var spacing = 10;
// was the element set to postition: fixed last calc?

var visible = false;

var recalc = function () {
// set $nav lazily
if (!$nav || !$nav.length) $nav = $(navSelector);

// if we can't find the nav, don't display the list
if (!$nav.length) return;

// the top point at which the list should be secured
var fixedTop = $nav.height();

// height of the section at the top of the page that is hidden
var hiddenBottom = document.body.scrollTop;

var top, left, css = {
visibility: 'visible'
};

if (hiddenBottom > fixedTop) {
// if we are already fixed, no reason to set the styles again
css.position = 'fixed';
top = spacing;
} else {
css.position = 'absolute';
top = fixedTop + spacing;
}

// calculate the expected left value (keep it centered)
left = Math.floor((document.body.scrollWidth - $el.width()) / 2);
css[transformKey] = 'translateX(' + Math.round(left) + 'px) translateY(' + Math.round(top) + 'px)';
if (transformKey !== 'msTransform') {
// The Z transform will keep this in the GPU (faster, and prevents artifacts),
// but IE9 doesn't support 3d transforms and will choke.
css[transformKey] += ' translateZ(0)';
}

$el.css(css);
};

// track the already scheduled recalcs
var timeoutId;
var clearSchedule = function () {
timeoutId = null;
};

var schedule = function () {
if (timeoutId) return;
else recalc();
timeoutId = setTimeout(clearSchedule, 25);
};

// call to remove the $el from the view
schedule.hide = function () {
$el.css('visibility', 'hidden');
visible = false;
};

return schedule;
}());

function listen(off) {
$(window)[off ? 'off' : 'on']('resize scroll', layoutList);
}

var wat = new MutableWatcher({
$scope: $scope,
expression: 'list',
type: 'collection'
}, showList);

function showList(list) {
if (list && list.length) {
listen();
wat.set(hideList);

// delay so that angular has time to update the DOM
nextTick(layoutList);
}
}

function hideList(list) {
if (!list || !list.length) {
listen(true);
wat.set(showList);
layoutList.hide();
}
}

$scope.$on('$destoy', _.partial(listen, true));
}
};
});
});
72 changes: 72 additions & 0 deletions src/kibana/notify/errors.js
@@ -0,0 +1,72 @@
define(function (require) {
var errors = {};
var _ = require('lodash');
var inherits = require('utils/inherits');

var canStack = (function () {
var err = new Error();
return !!err.stack;
}());

// abstract error class
function KibanaError(msg, constructor) {
this.message = msg;

Error.call(this, this.message);
if (!this.stack) {
if (Error.captureStackTrace) {
Error.captureStackTrace(this, constructor || KibanaError);
} else if (canStack) {
this.stack = (new Error()).stack;
} else {
this.stack = '';
}
}
}
errors.KibanaError = KibanaError;
inherits(KibanaError, Error);

/**
* Map of error text for different error types
* @type {Object}
*/
var requireTypeText = {
timeout: 'a network timeout',
nodefine: 'an invalid module definition',
scripterror: 'a generic script error'
};

/**
* ScriptLoadFailure error class for handling requirejs load failures
* @param {String} [msg] -
*/
errors.ScriptLoadFailure = function ScriptLoadFailure(err) {
var explain = requireTypeText[err.requireType] || err.requireType || 'an unknown error';

this.stack = err.stack;
var modules = err.requireModules;
if (_.isArray(modules) && modules.length > 0) {
modules = modules.map(JSON.stringify);

if (modules.length > 1) {
modules = modules.slice(0, -1).join(', ') + ' and ' + modules.slice(-1)[0];
} else {
modules = modules[0];
}

modules += ' modules';
}

if (!modules || !modules.length) {
modules = 'unknown modules';
}


KibanaError.call(this,
'Unable to load ' + modules + ' because of ' + explain + '.',
errors.ScriptLoadFailure);
};
inherits(errors.ScriptLoadFailure, KibanaError);

return errors;
});

0 comments on commit d06d490

Please sign in to comment.