Skip to content
Browse files

Use settings from stots to power the daemons

  • Loading branch information...
1 parent f050177 commit d885bda7280277b300606543b4e80198dadbc023 @iain committed
View
1 Gemfile
@@ -17,6 +17,7 @@ gem 'faraday'
gem 'faraday_middleware'
gem 'typhoeus'
gem 'nokogiri'
+gem 'foreman'
group :assets do
gem 'compass', '~> 0.12.alpha.4'
View
5 Gemfile.lock
@@ -66,6 +66,9 @@ GEM
rack (>= 1.1.0, < 2)
faraday_middleware (0.7.0)
faraday (~> 0.7.3)
+ foreman (0.36.1)
+ term-ansicolor (~> 1.0.7)
+ thor (>= 0.13.6)
friendly_id (4.0.0)
fssm (0.2.8.1)
haml (3.1.4)
@@ -142,6 +145,7 @@ GEM
rack (~> 1.0)
tilt (~> 1.1, != 1.3.0)
sqlite3 (1.3.5)
+ term-ansicolor (1.0.7)
thor (0.14.6)
tilt (1.3.3)
treetop (1.4.10)
@@ -165,6 +169,7 @@ DEPENDENCIES
factory_girl_rails
faraday
faraday_middleware
+ foreman
friendly_id
haml
has_scope
View
29 README.md
@@ -1 +1,30 @@
# Stots
+
+Stots is a dashboard that combines activity on different services into one nice dashboard.
+
+## Features
+
+### Show overviews of activity on different services per project
+
+We use a lot of different services for developing software. Like Github for our source code, Pivotal
+Tracker for our project management, Airbrake for error tracking and so on. These services are
+perfect at what they do and we love them to bits.
+
+But it can be a bit confusing some times. We have so many websites to keep track of, it's no wonder
+that we sometimes lose sight of some messages. Stots helps you centralize everything in one place.
+You'll still need to visit those services to do actual work, but you'll know you have to, by looking
+at Stots.
+
+### Visualize activity in insightful ways
+
+Do you have the feeling that you're rushing at the end of iterations to get things done? Did you
+increase the amount of errors since you switched some library? These patterns are hard to see when
+all you see is long lists.
+
+Stots will try to visualize multiple aspects of building software and combine them in instersting
+ways. Now you can see how one thing influences the other.
+
+### Plugin architecture to add new services
+
+New services are coming and going regularly. Stots uses the power of Rails engines to add new
+features and new services in a snap.
View
7 app/controllers/settings_controller.rb
@@ -1,5 +1,12 @@
class SettingsController < ApplicationController
+ respond_to :xml, :json, :only => [ :index ]
+
+ def index
+ @settings = Setting.group(params[:group]).to_hash
+ respond_with @settings, :root => "settings", :dasherize => false
+ end
+
def edit
@settings = Settings.new
end
View
20 app/models/airbrake/deploy.rb
@@ -4,17 +4,15 @@ class Airbrake::Deploy < ActiveRecord::Base
validates_presence_of :airbrake_project_id
- def self.import(project, deploys)
- deploys.each do |data|
- deploy = where(:airbrake_id => data[:id]).first || new(:airbrake_id => data[:id])
- deploy.rails_env = data[:rails_env]
- deploy.revision = data[:revision]
- deploy.local_username = data[:local_username]
- deploy.deployed_at = data[:created_at]
- deploy.ends_at = data[:ends_at]
- deploy.airbrake_project = project
- deploy.save!
- end
+ def self.import(project, data)
+ deploy = where(:airbrake_id => data[:id]).first || new(:airbrake_id => data[:id])
+ deploy.rails_env = data[:rails_env]
+ deploy.revision = data[:revision]
+ deploy.local_username = data[:local_username]
+ deploy.deployed_at = data[:created_at]
+ deploy.ends_at = data[:ends_at]
+ deploy.airbrake_project = project
+ deploy.save!
end
end
View
18 app/models/airbrake/error.rb
@@ -3,14 +3,16 @@ class Airbrake::Error < ActiveRecord::Base
validates_presence_of :airbrake_project_id, :group_id, :notice_id
def self.import(data)
- data[:notices].each do |notice|
- error = where(:notice_id => notice[:id]).first || new(:notice_id => notice[:id])
- error.resolved = data[:resolved]
- error.airbrake_project_id = data[:project_id]
- error.error_message = data[:error_message]
- error.occurred_at = Time.parse(notice[:created_at])
- error.group_id = data[:id]
- error.save!
+ transaction do
+ data[:notices].each do |notice|
+ error = where(:notice_id => notice[:id]).first || new(:notice_id => notice[:id])
+ error.resolved = data[:resolved]
+ error.airbrake_project_id = data[:project_id]
+ error.error_message = data[:error_message]
+ error.occurred_at = Time.parse(notice[:created_at])
+ error.group_id = data[:id]
+ error.save!
+ end
end
end
View
14 app/models/airbrake/project.rb
@@ -10,11 +10,15 @@ def to_label
end
def self.import(data)
- project = where(:airbrake_id => data[:id]).first || new(:airbrake_id => data[:id])
- project.name = data[:name]
- project.api_key = data[:api_key]
- project.save!
- Airbrake::Deploy.import(project, Array(data[:deploys]))
+ transaction do
+ project = where(:airbrake_id => data[:id]).first || new(:airbrake_id => data[:id])
+ project.name = data[:name]
+ project.api_key = data[:api_key]
+ project.save!
+ Array(data[:deploys]).each do |deploy|
+ Airbrake::Deploy.import(project, deploy)
+ end
+ end
end
end
View
2 app/models/pivotal_tracker/activity.rb
@@ -2,7 +2,7 @@ class PivotalTracker::Activity < ActiveRecord::Base
belongs_to :project, :class_name => "PivotalTracker::Project", :foreign_key => "pivotal_id"
- validates_presence_of :pivotal_id, :project_id
+ validates_presence_of :pivotal_id
def self.import(project, data)
activity = where(:pivotal_id => data[:id]).first || new(:pivotal_id => data[:id])
View
2 app/models/pivotal_tracker/iteration.rb
@@ -2,7 +2,7 @@ class PivotalTracker::Iteration < ActiveRecord::Base
belongs_to :project, :class_name => "PivotalTracker::Project", :foreign_key => "pivotal_id"
- validates_presence_of :project_id, :pivotal_id
+ validates_presence_of :pivotal_id
def self.import(project, data)
iteration = where(:pivotal_id => data[:id]).first || new(:pivotal_id => data[:id])
View
28 app/models/pivotal_tracker/project.rb
@@ -8,19 +8,21 @@ class PivotalTracker::Project < ActiveRecord::Base
validates_presence_of :pivotal_id, :name
def self.import(data)
- project = where(:pivotal_id => data[:id]).first || new(:pivotal_id => data[:id])
- project.name = data[:name]
- project.iteration_length = data[:iteration_length]
- project.week_start_day = data[:week_start_day]
- project.point_scale = data[:point_scale]
- project.current_velocity = data[:current_velocity]
- project.initial_velocity = data[:initial_velocity]
- project.save!
- data[:iterations].each do |iteration|
- PivotalTracker::Iteration.import(project, iteration)
- end
- data[:activities].each do |activity|
- PivotalTracker::Activity.import(project, activity)
+ transaction do
+ project = where(:pivotal_id => data[:id]).first || new(:pivotal_id => data[:id])
+ project.name = data[:name]
+ project.iteration_length = data[:iteration_length]
+ project.week_start_day = data[:week_start_day]
+ project.point_scale = data[:point_scale]
+ project.current_velocity = data[:current_velocity]
+ project.initial_velocity = data[:initial_velocity]
+ project.save!
+ data[:iterations].each do |iteration|
+ PivotalTracker::Iteration.import(project, iteration)
+ end
+ data[:activities].each do |activity|
+ PivotalTracker::Activity.import(project, activity)
+ end
end
end
View
8 app/models/setting.rb
@@ -21,6 +21,14 @@ def self.value_of(group, key)
group(group).key(key).value
end
+ def self.to_hash
+ Hash[scoped.map(&:to_pairs)]
+ end
+
+ def to_pairs
+ [ key, value ]
+ end
+
def reader_name
"#{group}_#{key}"
end
View
1 config/routes.rb
@@ -4,6 +4,7 @@
get "/settings" => "settings#edit"
put "/settings" => "settings#update"
+ get "/settings/:group" => "settings#index"
resources :projects do
resources :iframes, :except => [ :index ]
View
24 script/airbrake_errors.rb
@@ -1,5 +1,3 @@
-URL = "https://finalist.airbrake.io"
-KEY = ENV["AIRBRAKE_KEY"]
STOTS = "http://localhost:3000"
require 'faraday'
@@ -14,14 +12,26 @@ def puts(message)
STDOUT.puts("[#{Time.now}] #{message}")
end
-$connection = Faraday.new(:url => URL) do |builder|
-# builder.use Faraday::Response::RaiseError
+$local_connection = Faraday.new(:url => STOTS) do |builder|
+ builder.use Faraday::Request::UrlEncoded
+ builder.use Faraday::Response::RaiseError
builder.adapter :typhoeus
end
-$local_connection = Faraday.new(:url => STOTS) do |builder|
- builder.use Faraday::Request::UrlEncoded
-# builder.use Faraday::Response::RaiseError
+puts "Getting settings from Stots"
+
+response = $local_connection.get do |req|
+ req.url "/settings/airbrake.xml"
+end
+
+response.on_complete do
+ settings = Nokogiri::XML(response.body)
+ KEY = settings.search("//settings/api_key").first.content
+ URL = settings.search("//settings/account_url").first.content
+end
+
+$connection = Faraday.new(:url => URL) do |builder|
+ builder.use Faraday::Response::RaiseError
builder.adapter :typhoeus
end
View
20 script/airbrake_projects.rb
@@ -1,5 +1,3 @@
-URL = "https://finalist.airbrake.io"
-KEY = ENV["AIRBRAKE_KEY"]
STOTS = "http://localhost:3000"
require 'faraday'
@@ -14,13 +12,25 @@ def puts(message)
STDOUT.puts("[#{Time.now}] #{message}")
end
-$connection = Faraday.new(:url => URL) do |builder|
+$local_connection = Faraday.new(:url => STOTS) do |builder|
+ builder.use Faraday::Request::UrlEncoded
builder.use Faraday::Response::RaiseError
builder.adapter :typhoeus
end
-$local_connection = Faraday.new(:url => STOTS) do |builder|
- builder.use Faraday::Request::UrlEncoded
+puts "Getting settings from Stots"
+
+response = $local_connection.get do |req|
+ req.url "/settings/airbrake.xml"
+end
+
+response.on_complete do
+ settings = Nokogiri::XML(response.body)
+ KEY = settings.search("//settings/api_key").first.content
+ URL = settings.search("//settings/account_url").first.content
+end
+
+$connection = Faraday.new(:url => URL) do |builder|
builder.use Faraday::Response::RaiseError
builder.adapter :typhoeus
end
View
43 script/daemon
@@ -1,35 +1,22 @@
#!/usr/bin/env ruby
-threads = []
+scripts = %w(pivotal_tracker airbrake_errors airbrake_projects)
-threads << Thread.new do
- loop do
- puts "Running airbrake error fetcher"
- `touch log/airbrake.log`
- success = `ruby script/airbrake_errors.rb >> log/airbrake.log`
- if success
- puts "Airbrake error fetcher went OK"
- sleep 1
- else
- puts "Airbrake error fetcher failed"
- sleep 100
+threads = scripts.map do |script|
+ Thread.new do
+ loop do
+ puts "Running script #{script}"
+ `touch log/#{script}.log`
+ success = `ruby script/#{script}.rb >> log/#{script}.log`
+ if success
+ puts "Script #{script} went OK."
+ sleep 1
+ else
+ puts "Script #{script} FAILED! Will retry in 100 seconds."
+ sleep 100
+ end
end
end
end
-threads << Thread.new do
- loop do
- puts "Running airbrake project fetcher"
- `touch log/airbrake.log`
- success = `ruby script/airbrake_projects.rb >> log/airbrake.log`
- if success
- puts "Airbrake projects fetcher went OK"
- sleep 10
- else
- puts "Airbrake projects fetcher failed"
- sleep 100
- end
- end
-end
-
-threads.map(&:join)
+threads.each(&:join)
View
135 script/pivotal_tracker.rb
@@ -0,0 +1,135 @@
+URL = "https://www.pivotaltracker.com"
+STOTS = "http://localhost:3000"
+
+require 'faraday'
+require 'faraday_middleware'
+require 'typhoeus'
+require 'nokogiri'
+require 'time'
+
+starting_time = Time.now
+
+def puts(message)
+ STDOUT.puts("[#{Time.now}] #{message}")
+end
+
+$connection = Faraday.new(:url => URL) do |builder|
+ builder.use Faraday::Response::RaiseError
+ builder.adapter :typhoeus
+# builder.use Faraday::Response::Logger
+end
+
+module SimpleXML
+ def x(tag)
+ search(tag).first.content
+ end
+end
+Nokogiri::XML::Node.send(:include, SimpleXML)
+
+$local_connection = Faraday.new(:url => STOTS) do |builder|
+ builder.use Faraday::Request::UrlEncoded
+ builder.use Faraday::Response::RaiseError
+ builder.adapter :typhoeus
+end
+
+$connection.in_parallel(Typhoeus::Hydra.hydra) do
+ $local_connection.in_parallel(Typhoeus::Hydra.hydra) do
+
+ puts "Getting settings from Stots"
+
+ settings = $local_connection.get do |req|
+ req.url "/settings/pivotal_tracker.xml"
+ end
+
+ settings.on_complete do
+
+ KEY = Nokogiri::XML(settings.body).search("//settings/api_key").first.content
+
+ puts "Getting activity listing from Pivotal Tracker"
+
+ response = $connection.get do |req|
+ req.url "/services/v3/projects"
+ req.headers['X-TrackerToken'] = KEY
+ end
+
+ response.on_complete do
+ puts "Got projects"
+
+ Nokogiri::XML(response.body).search("//projects/project").each do |project|
+ id = project.x("id")
+ puts "Reading project #{id} - #{project.x("name")}"
+
+ res = $connection.get do |req|
+ req.url "/services/v3/projects/#{id}/activities"
+ req.headers['X-TrackerToken'] = KEY
+ req.params[:limit] = 1000
+ end
+
+ res2 = $connection.get do |req|
+ req.headers['X-TrackerToken'] = KEY
+ req.url "/services/v3/projects/#{id}/iterations"
+ end
+
+ res.on_complete do
+
+ puts "Retrieved activities for project #{id}"
+
+ activities = Nokogiri::XML(res.body).search("//activities/activity").map do |activity|
+
+ { :id => activity.x("id"),
+ :event_type => activity.x("event_type"),
+ :occurred_at => activity.x("occurred_at"),
+ :author => activity.x("author"),
+ :description => activity.x("description")
+ }
+
+ end
+
+ res2.on_complete do
+
+ puts "Retrieved iterations for project #{id}"
+
+ iterations = Nokogiri::XML(res2.body).search("//iterations/iteration").map do |iteration|
+ { :id => iteration.x("id"),
+ :number => iteration.x("number"),
+ :start => iteration.x("start"),
+ :finish => iteration.x("finish"),
+ :team_strength => iteration.x("team_strength"),
+ :stories => iteration.search("stories/story").size
+ }
+ end
+
+ data = {
+ :id => id,
+ :name => project.x("name"),
+ :iteration_length => project.x("iteration_length"),
+ :week_start_day => project.x("week_start_day"),
+ :point_scale => project.x("point_scale"),
+ :current_velocity => project.x("current_velocity"),
+ :initial_velocity => project.x("initial_velocity"),
+ :activities => activities,
+ :iterations => iterations
+ }
+
+ puts "Sending data"
+
+ local_response = $local_connection.post do |request|
+ request.url "/pivotal_tracker/projects"
+ request.body = { :project => data }
+ end
+
+ local_response.on_complete do
+ puts "Saved project #{data[:name]}"
+ end
+
+ end
+
+ end
+
+ end
+ end
+ end
+ end
+
+end
+puts "Done. It took me %.03f seconds" % (Time.now - starting_time)
View
126 script/tracker.rb
@@ -1,126 +0,0 @@
-URL = "https://www.pivotaltracker.com"
-KEY = ENV["TRACKER_TOKEN"]
-STOTS = "http://localhost:3000"
-
-require 'faraday'
-require 'faraday_middleware'
-require 'typhoeus'
-require 'nokogiri'
-require 'time'
-
-starting_time = Time.now
-
-def puts(message)
- STDOUT.puts("[#{Time.now}] #{message}")
-end
-
-$connection = Faraday.new(:url => URL) do |builder|
- builder.use Faraday::Response::RaiseError
- builder.adapter :typhoeus
-# builder.use Faraday::Response::Logger
-end
-
-module SimpleXML
- def x(tag)
- search(tag).first.content
- end
-end
-Nokogiri::XML::Node.send(:include, SimpleXML)
-
-$local_connection = Faraday.new(:url => STOTS) do |builder|
- builder.use Faraday::Request::UrlEncoded
- builder.use Faraday::Response::RaiseError
- builder.adapter :typhoeus
-end
-
-puts "Getting activity listing from Pivotal Tracker"
-
-$connection.in_parallel(Typhoeus::Hydra.hydra) do
- $local_connection.in_parallel(Typhoeus::Hydra.hydra) do
-
-
- response = $connection.get do |req|
- req.url "/services/v3/projects"
- req.headers['X-TrackerToken'] = KEY
- end
-
- response.on_complete do
- puts "Got projects"
-
- Nokogiri::XML(response.body).search("//projects/project").each do |project|
- id = project.x("id")
- puts "Reading project #{id} - #{project.x("name")}"
-
- res = $connection.get do |req|
- req.url "/services/v3/projects/#{id}/activities"
- req.headers['X-TrackerToken'] = KEY
- req.params[:limit] = 1000
- end
-
- res2 = $connection.get do |req|
- req.headers['X-TrackerToken'] = KEY
- req.url "/services/v3/projects/#{id}/iterations"
- end
-
- res.on_complete do
-
- puts "Retrieved activities for project #{id}"
-
- activities = Nokogiri::XML(res.body).search("//activities/activity").map do |activity|
-
- { :id => activity.x("id"),
- :event_type => activity.x("event_type"),
- :occurred_at => activity.x("occurred_at"),
- :author => activity.x("author"),
- :description => activity.x("description")
- }
-
- end
-
- res2.on_complete do
-
- puts "Retrieved iterations for project #{id}"
-
- iterations = Nokogiri::XML(res2.body).search("//iterations/iteration").map do |iteration|
- { :id => iteration.x("id"),
- :number => iteration.x("number"),
- :start => iteration.x("start"),
- :finish => iteration.x("finish"),
- :team_strength => iteration.x("team_strength"),
- :stories => iteration.search("stories/story").size
- }
- end
-
- data = {
- :id => id,
- :name => project.x("name"),
- :iteration_length => project.x("iteration_length"),
- :week_start_day => project.x("week_start_day"),
- :point_scale => project.x("point_scale"),
- :current_velocity => project.x("current_velocity"),
- :initial_velocity => project.x("initial_velocity"),
- :activities => activities,
- :iterations => iterations
- }
-
- puts "Sending data"
-
- local_response = $local_connection.post do |request|
- request.url "/pivotal_tracker/projects"
- request.body = { :project => data }
- end
-
- local_response.on_complete do
- puts "Saved project #{data[:name]}"
- end
-
- end
-
- end
-
- end
- end
- end
-end
-
-puts "Done. It took me %.03f seconds" % (Time.now - starting_time)
View
1 spec/models/pivotal_tracker/activity_spec.rb
@@ -5,6 +5,5 @@
it { should belong_to :project }
it { should validate_presence_of :pivotal_id }
- it { should validate_presence_of :project_id }
end
View
1 spec/models/pivotal_tracker/iteration_spec.rb
@@ -4,7 +4,6 @@
it { should belong_to :project }
- it { should validate_presence_of :project_id }
it { should validate_presence_of :pivotal_id }
end

0 comments on commit d885bda

Please sign in to comment.
Something went wrong with that request. Please try again.