Skip to content

Loading…

Refactor timeline to look nice, be awesome #80

Closed
wants to merge 3 commits into from

3 participants

@fsargent

Refactor timeline to look like a london underground tube map, possibly datavis using D3.

@fsargent

This can be reviewed now. Lots of little fixes for other things, not specifically the timeline.

@kans

Why did this line get moved from create_middleware into all the middlewares?

  • if (api_config === undefined ) {

  •  payload = null ;
    
  •  next();
    
  •  return;
    
  • }

@fsargent

The previous version looks like this:

return function(req, res, next){
    if (!req.devops.related_apis || !req.devops.related_apis[name]) {
      req.devops[name] = null;

Now all the middlewares check themselves to see if they have credentials, rather than it being built into the create_middleware.
I believe this is better because now we're not tieing middlewares directly to related_api credentials. We use middlewares for much more than that now.

@ggreer

Looks like a lot of tests are broken on this branch.

@ggreer

I cherry-picked the styling changes. The middlewares have been refactored quite a bit, so the rest of this can't be merged.

@ggreer ggreer closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Apr 19, 2012
  1. @fsargent
  2. @fsargent

    Refactored how we deal with middleware which doesnt have data in devo…

    fsargent committed
    …ps.json. see readme.md in middleware folder. Timeline will use any date format that javascripts Date() function accepts.
  3. @fsargent

    Improved timeline css.

    fsargent committed
View
22 lib/utils.js
@@ -222,22 +222,16 @@ exports.create_basic_auth = function(username, password){
* @return {fn} middleware function
*/
exports.create_middleware = function(name, middleware){
- return function(req, res, next){
- // Do they have the api defined in the devops ball?
- if (!req.devops.related_apis || !req.devops.related_apis[name]) {
- req.devops[name] = null;
+ return function(req, res, next) {
+ var payload = {error: null, data: null};
+ req.devops[name] = payload;
+ try{
+ middleware(req, res, next, payload, req.devops.related_apis[name]);
+ } catch (e){
+ payload.error = e;
+ // TODO: this may not be callable if the error was with express
next();
- return;
}
- var payload = {error: null, data: null};
- req.devops[name] = payload;
- try{
- middleware(req, res, next, payload, req.devops.related_apis[name]);
- }catch (e){
- payload.error = e;
- // TODO: this may not be callable if the error was with express
- next();
- }
};
};
View
14 lib/web/middleware/README.md
@@ -20,4 +20,16 @@ There are two kinds of middlewares:
populate data.
These middlewares are typically created via Utils.create_middleware which passes in two extra params: payload and api_config.
api_config maps to req.devops[middleware_name] while payload contains the data and error objects. Middlewares
- update the payload instead of setting the data and error fields on the devops object directly.
+ update the payload instead of setting the data and error fields on the devops object directly.
+
+Middlewares should set their payload to null when they discover they have no relevant credentials in the req.devops or api_config.
+
+```
+ // blank credentials test.
+ if (api_config === undefined ) {
+ payload = null ;
+ next();
+ return;
+ }
+```
+
View
7 lib/web/middleware/dreadnot.js
@@ -29,6 +29,13 @@ request.prototype = {
module.exports = utils.create_middleware('dreadnot', function dreadnot(req, res, next, payload, api_config) {
+ // blank credentials test.
+ if (api_config === undefined ) {
+ payload = null ;
+ next();
+ return;
+ }
+
var requests = [];
_.each(api_config.stacks, function(a_stack){
_.each(_.keys(a_stack), function(stack_name){
View
1 lib/web/middleware/index.js
@@ -21,6 +21,7 @@ module.exports = {
load_devops: require('./load_devops'),
navbar: require('./navbar'),
// independent devops middleware:
+ timeline: require('./timeline'),
pager_duty: require('./pager_duty'),
version_one: require('./version_one'),
github: require('./github'),
View
9 lib/web/middleware/pager_duty.js
@@ -8,8 +8,13 @@ var _ = require('underscore');
* on_end_cb a callback that gets called with the XHR response data
*/
-module.exports = utils.create_middleware('pager_duty', function(req, res, next, payload, api_config) {
-
+module.exports = utils.create_middleware('pager_duty', function pager_duty(req, res, next, payload, api_config) {
+ // blank credentials test.
+ if (api_config === undefined ) {
+ payload = null ;
+ next();
+ return;
+ }
// PagerDuty requires the date range for all requests.
var now, until, options;
View
61 lib/web/middleware/timeline.js
@@ -0,0 +1,61 @@
+var utils = require('../../utils');
+var _ = require('underscore');
+
+module.exports = utils.create_middleware('timeline', function timeline (req, res, next, payload) {
+
+ // blank credentials test.
+ if (req.devops.events === undefined ) {
+ payload = null;
+ next();
+ return;
+ }
+ var events = req.devops.events;
+ var max = 0;
+
+ var now = (new Date()).getTime();
+ var future_events = [];
+ var amt;
+
+ // define the position of the events
+ try {
+
+ for (var i=0; i<events.length; i++){
+ var event = events[i];
+ if (_.isNull(event.timestamp)){
+ continue;
+ }
+
+ // accept both ISO and unix timestamp formats
+ if (typeof (event.timestamp) === 'number') {
+ event.timestamp = new Date(event.timestamp * 1000);
+ } else {
+ event.timestamp = new Date(event.timestamp);
+ }
+
+ // get max
+ if (event.timestamp > max){
+ max = event.timestamp;
+ }
+ // make valid events list
+ if (event.timestamp > now){
+ future_events.push(event);
+ }
+ }
+ if (future_events.length > 1) {
+ future_events.sort(function(x,y) {
+ return y.timestamp < x.timestamp;
+ });
+ }
+ _.each(future_events, function(event){
+ event.days_remaining = Math.floor((event.timestamp - now) / (1000*60*60*24));
+ // figure out the amount and offset from the right 100px and from the left 25px
+ amt = ((event.timestamp - now) / (max - now + 1)) * 0.8;
+ event.position = amt * 100;
+ });
+ } catch(e) {
+ payload.error = e;
+ return next();
+ }
+ payload.data = {"events": future_events};
+ next();
+});
View
14 lib/web/middleware/version_one.js
@@ -35,7 +35,15 @@ var selection = ["Custom_Severity.Name",
"Scope",
"Priority"];
-module.exports = utils.create_middleware('version_one', function(req, res, next, payload, api_config) {
+module.exports = utils.create_middleware('version_one', function version_one (req, res, next, payload, api_config) {
+
+ // blank credentials test.
+ if (api_config === undefined ) {
+ payload = null ;
+ next();
+ return;
+ }
+
var options = {
port: api_config.port,
host: api_config.host,
@@ -52,8 +60,8 @@ module.exports = utils.create_middleware('version_one', function(req, res, next,
'Authorization': 'Basic ' + new Buffer(api_config.auth).toString('base64')
}
};
-
- utils.request_maker(options,
+
+utils.request_maker(options,
function(error, data){
var byAge = {};
var now = new Date();
View
36 lib/web/routes.js
@@ -45,6 +45,8 @@ module.exports.install = function(app, secure_app, api_cache, devops_directory){
}, function(cb) {
middleware.new_relic(req, res, cb);
}, function(cb){
+ middleware.timeline(req, res, cb);
+ }, function(cb){
middleware.dreadnot(req, res, cb);
}
],
@@ -52,40 +54,6 @@ module.exports.install = function(app, secure_app, api_cache, devops_directory){
if (err) {
req.devops.errors.push(err);
}
-
- var events = req.devops.events;
- var max = 0;
-
- var now = (new Date()).getTime();
- var future_events = [];
- for (var i=0; i<events.length; i++){
- var event = events[i];
- if (_.isNull(event.timestamp)){
- continue;
- }
- // convert to seconds
- event.timestamp *= 1000;
- // get max
- if (event.timestamp > max){
- max = event.timestamp;
- }
- // make valid events list
- if (event.timestamp > now){
- future_events.push(event);
- }
- }
- if (future_events.length > 1) {
- future_events.sort(function(x,y) {
- return y.timestamp < x.timestamp;
- });
- }
- _.each(future_events, function(event){
- event.days_remaining = Math.floor((event.timestamp - now) / (1000*60*60*24));
- // figure out the amount and offset from the right 100px and from the left 25px
- var amt = ((event.timestamp - now) / (max - now + 1)) * 0.8;
- event.position = amt * 100;
- });
- req.devops.events = future_events;
res.render('index.jade', req.devops);
}
);
View
24 lib/web/views/includes/pager_duty.jade
@@ -1,24 +0,0 @@
-.pager_duty
- h1 PagerDuty
- if pager_duty.error
- p #{pager_duty.error}
- else if pager_duty.data.error
- p API returned the following error:
- p.alert #{pager_duty.data.error.code}: #{pager_duty.data.error.message}
- else
- h4 Currently On Call:
- p
- ul
- li
- strong #{pager_duty.data.entries[0].user.name} <br>
- | from: #{pager_duty.data.entries[0].start}<br>
- | to: #{pager_duty.data.entries[0].end}
- p
- if pager_duty.data.entries[1]
- h4 Next On Call:
- p
- ul
- li
- strong #{pager_duty.data.entries[1].user.name} <br>
- | from: #{pager_duty.data.entries[1].start}<br>
- | to: #{pager_duty.data.entries[1].end}
View
20 lib/web/views/includes/timeline.jade
@@ -0,0 +1,20 @@
+block append scripts
+
+
+if timeline.error
+ p.alert!=trace(timeline.error)
+else
+ for event in timeline.data.events
+ div.timeline-tooltip(style="left: #{event.position}%")
+ #{event.days_remaining}
+ div.alert.hide
+ b=event.name
+ br
+ #{event.description}
+ ul.related_links
+ if event.related_links
+ for link in event.related_links
+ li
+ a(href="#{link}") #{link}
+ span.date-info
+ #{event.days_remaining} days left - !{(new Date(event.timestamp)).toDateString()}
View
16 lib/web/views/includes/timeline_event.jade
@@ -1,16 +0,0 @@
-block append scripts
-
-for event in events
- div.timeline-tooltip(style="left: #{event.position}%")
- #{event.days_remaining}
- div.alert.hide
- b=event.name
- br
- #{event.description}
- ul.related_links
- if event.related_links
- for link in event.related_links
- li
- a(href="#{link}") #{link}
- span.date-info
- #{event.days_remaining} days left - !{(new Date(event.timestamp)).toDateString()}
View
11 lib/web/views/index.jade
@@ -7,8 +7,9 @@ block append scripts
script(src='/static/js/contacts.js')
block content
- #timeline
- include includes/timeline_event
+ if timeline.error || timeline.data
+ #timeline
+ include includes/timeline
if errors.length
#errors.alert.alert-error
@@ -45,15 +46,15 @@ block content
#environments-block.ablock
include includes/environments
- if version_one
+ if version_one.error || version_one.data
#versionone-block.ablock.middleware-block
include includes/version_one
- if new_relic
+ if new_relic.error || new_relic.data
#newrelic-block.ablock.middleware-block
include includes/new_relic
- if dreadnot
+ if dreadnot.error || dreadnot.data
#dreadnot-block.ablock.middleware-block
include includes/dreadnot
View
26 static/css/index.less
@@ -16,8 +16,30 @@
.on-call { color: red; }
-#timeline {margin: 0 50px;}
+#timeline {margin: 0px 0px 30px 0px;}
#timeline-tooltip .name { font-weight: bold; }
#timeline-tooltip .date-info { font-size: .8em; display: block; text-align: right; }
-#timeline > div { position: absolute; cursor: pointer;}
+
+
+.timeline-tooltip {
+ position: absolute;
+ cursor: pointer;
+ border-radius: 999px;
+ behavior: url(PIE.htc);
+
+ width: 12px;
+ height: 12px;
+ padding: 10px;
+
+ background: #fff;
+ border: 2px solid #666;
+ color: #666;
+ text-align: center;
+
+ font: 12px Arial, sans-serif
+}
+
.contact-team li { padding: 2px 0px; }
+.alert {
+ width: 250px;
+}
View
81 static/js/event-timeline.js
@@ -1,81 +0,0 @@
-/**
- * Add timeline to top of dashboard if event data is present.
- * The numbers indicate remaining days.
- * Red numbers indicate close deadlines.
- * Clicking on the numbers displays all event information.
- */
-$(document).ready(function() {
- // list of {name:'', description:'', timestamp: converted to milliseconds!}
- // includes event representing "now".
- var events = [];
- var now = (new Date()).getTime();
- var max = 0;
- events = [{
- timestamp: now,
- name: "now",
- description: ""
- }];
- $("#events li").each(function(idx, el) {
- var event = $(this);
- var timestamp = $(this).children(".timestamp").text();
- var name = $(this).children(".name");
- var description = $(this).children(".description");
- var related_links = $(this).children(".related_links");
- if (timestamp) {
- timestamp = parseInt(timestamp) * 1000;
- if (timestamp > now) {
- if (max < timestamp) { max = timestamp; }
- events.push({
- timestamp: timestamp,
- name: name,
- description: description,
- related_links: related_links
- });
- }
- }
- });
- if (events.length > 1) {
- events.sort(function(x,y) {
- return y.timestamp < x.timestamp;
- });
- var timeline = $("#timeline");
- var elwidth = 100;
- var width = timeline.width();
- for (var i = 0; i < events.length; i++) {
- var event = events[i];
- if (event.timestamp >= now) {
- var days_remaining = Math.floor((event.timestamp - now) / (1000*60*60*24));
- // figure out the amount and offset from the right 100px and from the left 25px
- var amt = ((event.timestamp - now) / (max - now)) * (width - 100) + 25;
- var pct = amt / width * 100;
- var el = $("<div></div>");
- el.css("left", pct+"%");
- el.css("position", "absolute");
- el.css("cursor", "pointer");
- if (event.timestamp !== now && days_remaining < 22) {
- el.css("color", "red");
- }
- el.text(days_remaining);
-
- var tooltip = $("<div></div>").addClass("alert hide timeline-tooltip");
- tooltip.append(event.name);
- tooltip.append(event.description);
- tooltip.append(event.related_links);
- tooltip.append($("<span></span>").addClass("date-info").append([
- days_remaining,
- " days left - ",
- (new Date(event.timestamp)).toDateString()].join("")));
- el.append(tooltip);
- el.click(function() {
- $(this).children().toggle();
- });
- timeline.append(el);
- }
- }
- timeline.css("margin-top", "-15px");
- timeline.css("height", "20px");
- var events = $("#events");
- events.prev().detach();
- events.detach();
- }
-});
Something went wrong with that request. Please try again.