Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Add Hoptoad Widget, list app names with the error count

  • Loading branch information...
commit ce749c401ad6d64dec6c5b6891ab3f671a3ef2c1 1 parent 054c6e3
Marc Lagrange authored committed
View
2  Gemfile
@@ -14,6 +14,8 @@ group :production do
gem "thin", "1.2.7"
gem "sinatra", "1.1.2"
gem "haml", "3.0.25"
+ gem "i18n"
+ gem "roxml"
end
group :test do
View
6 Gemfile.lock
@@ -17,6 +17,7 @@ GEM
gemcutter (0.6.1)
git (1.2.5)
haml (3.0.25)
+ i18n (0.5.0)
jeweler (1.4.0)
gemcutter (>= 0.1.0)
git (>= 1.2.5)
@@ -29,6 +30,9 @@ GEM
rack (1.2.1)
rake (0.8.7)
roauth (0.0.3)
+ roxml (3.1.6)
+ activesupport (>= 2.3.0)
+ nokogiri (>= 1.3.3)
rspec (1.3.0)
rubyforge (2.0.4)
json_pure (>= 1.1.7)
@@ -57,10 +61,12 @@ DEPENDENCIES
eventmachine (= 0.12.10)
gemcutter
haml (= 3.0.25)
+ i18n
jeweler
launchy (= 0.3.7)
nokogiri (= 1.4.4)
rake (= 0.8.7)
+ roxml
rspec
sinatra (= 1.1.2)
thin (= 1.2.7)
View
6 example/config.yml.example
@@ -65,3 +65,9 @@ widgets:
url: "http://www.engadget.com/rss.xml" # URL to RSS feed
xpath: "//item//title" # XPath to content to show
nitems: 5 # Number of RSS items to show
+
+ Hoptoad:
+ title: Apps errors
+ name: hoptoad
+ account: foo
+ auth_key: foobarbaz
View
29 widgets/hoptoad/hoptoad.css
@@ -0,0 +1,29 @@
+/* Hoptoad */
+
+div.hoptoad { width: 330px; }
+div.hoptoad .content img {
+ width: 32px;
+ height: 32px;
+}
+
+div.hoptoad span.app_name {
+ font-size: 14pt;
+ padding-left: 1em;
+}
+
+div.hoptoad span.no_app_errors {
+ font-weight: bold;
+ color: green;
+ padding-left: 10em;
+ font-size: 14pt;
+}
+div.hoptoad span.app_errors {
+ color: red;
+ font-weight: bold;
+ padding-left: 10em;
+ font-size: 14pt;
+}
+
+div.hoptoad .content p {
+ line-height: 24px;
+}
View
54 widgets/hoptoad/hoptoad.js
@@ -0,0 +1,54 @@
+var Hoptoad = Class.create(Widget, {
+ initialize: function($super, widget_id, config) {
+ this.messages = [];
+ return($super(widget_id, config));
+ },
+
+ handlePayload: function(payload) {
+ this.messages = payload;
+ },
+
+ build: function() {
+ this.contentContainer = this.buildContent();
+ this.headerContainer = this.buildHeader();
+ this.iconContainer = this.buildIcon();
+
+ this.container.insert(this.headerContainer);
+ this.container.insert(this.iconContainer);
+ this.container.insert(this.contentContainer);
+
+ this.makeDraggable();
+ },
+
+ update: function() {
+ this.contentContainer.childElements().invoke('remove');
+
+ this.messages.reverse(false).each(function(message) {
+ var cont = new Element('p');
+ if (message.errors == "0") {
+ var class_errors = "no_app_errors";
+ } else {
+ var class_errors = "app_errors";
+ }
+ var app_errors = new Element('div', { className: 'app_errors' }).update(
+ '<span class="app_name">' + message.name + '</span>: <span class="' + class_errors + '">' + message.errors + '</span>'
+ );
+ cont.insert(app_errors);
+ cont.insert(new Element('hr' ));
+ this.contentContainer.insert(cont);
+ }.bind(this));
+ },
+
+ buildContent: function() {
+ return(new Element("div", { 'class': 'content' }));
+ },
+
+ buildHeader: function() {
+ return(new Element("h2", { 'class': 'handle' }).update(this.title));
+ },
+
+ buildIcon: function() {
+ return(new Element("img", { src: "images/hoptoad/hoptoad-promo.png", 'class': 'hoptoad icon' }));
+ }
+});
+
View
116 widgets/hoptoad/hoptoad.rb
@@ -0,0 +1,116 @@
+require 'roxml'
+module Sonia
+ module Widgets
+ # ROXML classes doesn't include all stuff that Hoptoad returns
+ # since we dont need it : we already have the api key and we
+ # only need to count the total of errors for each projects
+ # ROXML Projects class
+ class Project
+ include ROXML
+ xml_accessor :id
+ xml_accessor :name
+ end
+ class Projects
+ include ROXML
+ xml_accessor :projects, :as => [Project]
+ end
+
+ # ROXML Errors class
+ class Group
+ include ROXML
+ xml_accessor :project_id, :from => "@project-id"
+ xml_accessor :resolved
+ end
+
+ class Groups
+ include ROXML
+ xml_accessor :groups, :as => [Group]
+ end
+
+ class Hoptoad < Sonia::Widget
+ PROJECTS_URL = "http://%s.hoptoadapp.com/projects.xml?auth_token=%s" # SSL redirecto to non-ssl url
+ PROJECT_ERRORS_URL = "http://%s.hoptoadapp.com/errors.xml?auth_token=%s&project_id=%s" # same
+
+ def initialize(config)
+ super(config)
+
+ @projects = []
+ EventMachine::add_periodic_timer(60 * 5) { fetch_data }
+ end
+
+ def initial_push
+ fetch_data
+ end
+
+ private
+ def fetch_data
+ fetch_projects do
+ fetch_errors
+ end
+ end
+
+ def fetch_projects(&blk)
+ log_info "Polling `#{projects_url}'"
+ http = EventMachine::HttpRequest.new(projects_url).get
+ http.errback { log_fatal_error(http) }
+ http.callback {
+ handle_projects_response(http)
+ blk.call
+ }
+ end
+
+ def handle_projects_response(http)
+ if http.response_header.status == 200
+ @projects = Projects.from_xml(http.response).projects.map { |p| { :id => p.id, :name => p.name } }
+ else
+ log_unsuccessful_response_body(http.response)
+ end
+ rescue => e
+ log_backtrace(e)
+ end
+
+ def fetch_errors
+ multi = EventMachine::MultiRequest.new
+ @projects.each do |project|
+ url = project_errors_url(project[:id])
+ log_info "Polling `#{url}'"
+ multi.add(EventMachine::HttpRequest.new(url).get)
+ end
+ multi.errback { log_fatal_error(multi) }
+ multi.callback {
+ handle_errors_response(multi)
+ }
+ end
+
+ def handle_errors_response(multi)
+ errors = multi.responses[:succeeded].map do |response|
+ begin
+ error_p_id = response.instance_variable_get(:@uri).query.split("project_id=").last
+ project = project(error_p_id)
+ project[:errors] = Groups.from_xml(response.response).groups.size
+ project
+ rescue => e
+ log_backtrace(e)
+ end
+ end
+
+ push errors
+ rescue => e
+ log_backtrace(e)
+ end
+
+
+ def projects_url
+ PROJECTS_URL % [config.account, config.auth_key]
+ end
+ def project_errors_url(project_id)
+ PROJECT_ERRORS_URL % [config.account, config.auth_key, project_id]
+ end
+
+ def project(project_id)
+ @projects.detect { |r| r[:id] == project_id }
+ end
+
+ end
+ end
+end
View
BIN  widgets/hoptoad/images/hoptoad-promo.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Please sign in to comment.
Something went wrong with that request. Please try again.