Commit 16c162bfea3dd9e61bf041de35e06266d07c7697
Exists in
master
and in
1 other branch
Merge branch 'add-api' of https://github.com/concordia-publishing-house/errbit i…
…nto concordia-publishing-house-add-api Conflicts: Gemfile Gemfile.lock
Showing
11 changed files
with
237 additions
and
5 deletions
Show diff stats
Gemfile
@@ -42,13 +42,15 @@ platform :ruby do | @@ -42,13 +42,15 @@ platform :ruby do | ||
42 | end | 42 | end |
43 | 43 | ||
44 | gem 'ri_cal' | 44 | gem 'ri_cal' |
45 | -gem 'yajl-ruby' | 45 | +gem 'yajl-ruby', :require => "yajl" |
46 | 46 | ||
47 | group :development, :test do | 47 | group :development, :test do |
48 | gem 'rspec-rails', '~> 2.6' | 48 | gem 'rspec-rails', '~> 2.6' |
49 | gem 'webmock', :require => false | 49 | gem 'webmock', :require => false |
50 | unless ENV["CI"] | 50 | unless ENV["CI"] |
51 | gem 'ruby-debug', :platform => :mri_18 | 51 | gem 'ruby-debug', :platform => :mri_18 |
52 | + gem 'debugger', :platform => :mri_19 | ||
53 | + gem 'pry' | ||
52 | end | 54 | end |
53 | # gem 'rpm_contrib' | 55 | # gem 'rpm_contrib' |
54 | # gem 'newrelic_rpm' | 56 | # gem 'newrelic_rpm' |
@@ -61,6 +63,7 @@ group :test do | @@ -61,6 +63,7 @@ group :test do | ||
61 | gem 'rspec', '~> 2.6' | 63 | gem 'rspec', '~> 2.6' |
62 | gem 'database_cleaner', '~> 0.6.0' | 64 | gem 'database_cleaner', '~> 0.6.0' |
63 | gem 'email_spec' | 65 | gem 'email_spec' |
66 | + gem 'timecop' | ||
64 | end | 67 | end |
65 | 68 | ||
66 | group :heroku do | 69 | group :heroku do |
Gemfile.lock
@@ -57,6 +57,7 @@ GEM | @@ -57,6 +57,7 @@ GEM | ||
57 | xpath (~> 0.1.4) | 57 | xpath (~> 0.1.4) |
58 | childprocess (0.3.5) | 58 | childprocess (0.3.5) |
59 | ffi (~> 1.0, >= 1.0.6) | 59 | ffi (~> 1.0, >= 1.0.6) |
60 | + coderay (1.0.6) | ||
60 | columnize (0.3.6) | 61 | columnize (0.3.6) |
61 | crack (0.3.1) | 62 | crack (0.3.1) |
62 | css_parser (1.2.6) | 63 | css_parser (1.2.6) |
@@ -64,6 +65,13 @@ GEM | @@ -64,6 +65,13 @@ GEM | ||
64 | rdoc | 65 | rdoc |
65 | daemons (1.1.8) | 66 | daemons (1.1.8) |
66 | database_cleaner (0.6.7) | 67 | database_cleaner (0.6.7) |
68 | + debugger (1.2.0) | ||
69 | + columnize (>= 0.3.1) | ||
70 | + debugger-linecache (~> 1.1.1) | ||
71 | + debugger-ruby_core_source (~> 1.1.3) | ||
72 | + debugger-linecache (1.1.2) | ||
73 | + debugger-ruby_core_source (>= 1.1.1) | ||
74 | + debugger-ruby_core_source (1.1.3) | ||
67 | devise (1.5.3) | 75 | devise (1.5.3) |
68 | bcrypt-ruby (~> 3.0) | 76 | bcrypt-ruby (~> 3.0) |
69 | orm_adapter (~> 0.0.3) | 77 | orm_adapter (~> 0.0.3) |
@@ -122,6 +130,7 @@ GEM | @@ -122,6 +130,7 @@ GEM | ||
122 | i18n (>= 0.4.0) | 130 | i18n (>= 0.4.0) |
123 | mime-types (~> 1.16) | 131 | mime-types (~> 1.16) |
124 | treetop (~> 1.4.8) | 132 | treetop (~> 1.4.8) |
133 | + method_source (0.7.1) | ||
125 | mime-types (1.19) | 134 | mime-types (1.19) |
126 | mongo (1.6.2) | 135 | mongo (1.6.2) |
127 | bson (~> 1.6.2) | 136 | bson (~> 1.6.2) |
@@ -182,6 +191,10 @@ GEM | @@ -182,6 +191,10 @@ GEM | ||
182 | premailer (1.7.3) | 191 | premailer (1.7.3) |
183 | css_parser (>= 1.1.9) | 192 | css_parser (>= 1.1.9) |
184 | htmlentities (>= 4.0.0) | 193 | htmlentities (>= 4.0.0) |
194 | + pry (0.9.9.6) | ||
195 | + coderay (~> 1.0.5) | ||
196 | + method_source (~> 0.7.1) | ||
197 | + slop (>= 2.4.4, < 3) | ||
185 | rack (1.4.1) | 198 | rack (1.4.1) |
186 | rack-cache (1.2) | 199 | rack-cache (1.2) |
187 | rack (>= 0.4) | 200 | rack (>= 0.4) |
@@ -243,6 +256,7 @@ GEM | @@ -243,6 +256,7 @@ GEM | ||
243 | libwebsocket (~> 0.1.3) | 256 | libwebsocket (~> 0.1.3) |
244 | multi_json (~> 1.0) | 257 | multi_json (~> 1.0) |
245 | rubyzip | 258 | rubyzip |
259 | + slop (2.4.4) | ||
246 | sprockets (2.1.3) | 260 | sprockets (2.1.3) |
247 | hike (~> 1.2) | 261 | hike (~> 1.2) |
248 | rack (~> 1.0) | 262 | rack (~> 1.0) |
@@ -255,6 +269,7 @@ GEM | @@ -255,6 +269,7 @@ GEM | ||
255 | rack (>= 1.0.0) | 269 | rack (>= 1.0.0) |
256 | thor (0.16.0) | 270 | thor (0.16.0) |
257 | tilt (1.3.3) | 271 | tilt (1.3.3) |
272 | + timecop (0.3.5) | ||
258 | treetop (1.4.10) | 273 | treetop (1.4.10) |
259 | polyglot | 274 | polyglot |
260 | polyglot (>= 0.3.1) | 275 | polyglot (>= 0.3.1) |
@@ -288,6 +303,7 @@ DEPENDENCIES | @@ -288,6 +303,7 @@ DEPENDENCIES | ||
288 | capistrano | 303 | capistrano |
289 | capybara | 304 | capybara |
290 | database_cleaner (~> 0.6.0) | 305 | database_cleaner (~> 0.6.0) |
306 | + debugger | ||
291 | devise (~> 1.5.3) | 307 | devise (~> 1.5.3) |
292 | email_spec | 308 | email_spec |
293 | execjs | 309 | execjs |
@@ -308,6 +324,7 @@ DEPENDENCIES | @@ -308,6 +324,7 @@ DEPENDENCIES | ||
308 | omniauth-github | 324 | omniauth-github |
309 | oruen_redmine_client | 325 | oruen_redmine_client |
310 | pivotal-tracker | 326 | pivotal-tracker |
327 | + pry | ||
311 | rack-ssl | 328 | rack-ssl |
312 | rack-ssl-enforcer | 329 | rack-ssl-enforcer |
313 | rails (= 3.2.8) | 330 | rails (= 3.2.8) |
@@ -319,6 +336,7 @@ DEPENDENCIES | @@ -319,6 +336,7 @@ DEPENDENCIES | ||
319 | ruby-fogbugz | 336 | ruby-fogbugz |
320 | therubyracer | 337 | therubyracer |
321 | thin | 338 | thin |
339 | + timecop | ||
322 | uglifier (>= 1.0.3) | 340 | uglifier (>= 1.0.3) |
323 | unicorn | 341 | unicorn |
324 | useragent (~> 0.3.1) | 342 | useragent (~> 0.3.1) |
@@ -0,0 +1,23 @@ | @@ -0,0 +1,23 @@ | ||
1 | +class Api::V1::NoticesController < ApplicationController | ||
2 | + respond_to :json, :xml | ||
3 | + | ||
4 | + def index | ||
5 | + query = {} | ||
6 | + fields = %w{created_at message error_class} | ||
7 | + | ||
8 | + if params.key?(:start_date) && params.key?(:end_date) | ||
9 | + start_date = Time.parse(params[:start_date]).utc | ||
10 | + end_date = Time.parse(params[:end_date]).utc | ||
11 | + query = {:created_at => {"$lte" => end_date, "$gte" => start_date}} | ||
12 | + end | ||
13 | + | ||
14 | + results = benchmark("[api/v1/notices_controller] query time") { Mongoid.master["notices"].find(query, :fields => fields).to_a } | ||
15 | + | ||
16 | + respond_to do |format| | ||
17 | + format.html { render :json => Yajl.dump(results) } # render JSON if no extension specified on path | ||
18 | + format.json { render :json => Yajl.dump(results) } | ||
19 | + format.xml { render :xml => results } | ||
20 | + end | ||
21 | + end | ||
22 | + | ||
23 | +end |
@@ -0,0 +1,23 @@ | @@ -0,0 +1,23 @@ | ||
1 | +class Api::V1::ProblemsController < ApplicationController | ||
2 | + respond_to :json, :xml | ||
3 | + | ||
4 | + def index | ||
5 | + query = {} | ||
6 | + fields = %w{app_id app_name environment message where first_notice_at last_notice_at resolved resolved_at notices_count} | ||
7 | + | ||
8 | + if params.key?(:start_date) && params.key?(:end_date) | ||
9 | + start_date = Time.parse(params[:start_date]).utc | ||
10 | + end_date = Time.parse(params[:end_date]).utc | ||
11 | + query = {:first_notice_at=>{"$lte"=>end_date}, "$or"=>[{:resolved_at=>nil}, {:resolved_at=>{"$gte"=>start_date}}]} | ||
12 | + end | ||
13 | + | ||
14 | + results = benchmark("[api/v1/problems_controller] query time") { Mongoid.master["problems"].find(query, :fields => fields).to_a } | ||
15 | + | ||
16 | + respond_to do |format| | ||
17 | + format.html { render :json => Yajl.dump(results) } # render JSON if no extension specified on path | ||
18 | + format.json { render :json => Yajl.dump(results) } | ||
19 | + format.xml { render :xml => results } | ||
20 | + end | ||
21 | + end | ||
22 | + | ||
23 | +end |
app/controllers/notices_controller.rb
app/models/problem.rb
@@ -7,8 +7,10 @@ class Problem | @@ -7,8 +7,10 @@ class Problem | ||
7 | include Mongoid::Timestamps | 7 | include Mongoid::Timestamps |
8 | 8 | ||
9 | field :last_notice_at, :type => DateTime | 9 | field :last_notice_at, :type => DateTime |
10 | + field :first_notice_at, :type => DateTime | ||
10 | field :last_deploy_at, :type => Time | 11 | field :last_deploy_at, :type => Time |
11 | field :resolved, :type => Boolean, :default => false | 12 | field :resolved, :type => Boolean, :default => false |
13 | + field :resolved_at, :type => Time | ||
12 | field :issue_link, :type => String | 14 | field :issue_link, :type => String |
13 | field :issue_type, :type => String | 15 | field :issue_type, :type => String |
14 | 16 | ||
@@ -28,7 +30,9 @@ class Problem | @@ -28,7 +30,9 @@ class Problem | ||
28 | index :app_name | 30 | index :app_name |
29 | index :message | 31 | index :message |
30 | index :last_notice_at | 32 | index :last_notice_at |
33 | + index :first_notice_at | ||
31 | index :last_deploy_at | 34 | index :last_deploy_at |
35 | + index :resolved_at | ||
32 | index :notices_count | 36 | index :notices_count |
33 | 37 | ||
34 | belongs_to :app | 38 | belongs_to :app |
@@ -52,7 +56,7 @@ class Problem | @@ -52,7 +56,7 @@ class Problem | ||
52 | end | 56 | end |
53 | 57 | ||
54 | def resolve! | 58 | def resolve! |
55 | - self.update_attributes!(:resolved => true, :notices_count => 0) | 59 | + self.update_attributes!(:resolved => true, :resolved_at => Time.now, :notices_count => 0) |
56 | end | 60 | end |
57 | 61 | ||
58 | def unresolve! | 62 | def unresolve! |
@@ -103,6 +107,10 @@ class Problem | @@ -103,6 +107,10 @@ class Problem | ||
103 | else raise("\"#{sort}\" is not a recognized sort") | 107 | else raise("\"#{sort}\" is not a recognized sort") |
104 | end | 108 | end |
105 | end | 109 | end |
110 | + | ||
111 | + def self.in_date_range(date_range) | ||
112 | + where(:first_notice_at.lte => date_range.end).where("$or" => [{:resolved_at => nil}, {:resolved_at.gte => date_range.begin}]) | ||
113 | + end | ||
106 | 114 | ||
107 | 115 | ||
108 | def reset_cached_attributes | 116 | def reset_cached_attributes |
@@ -125,7 +133,7 @@ class Problem | @@ -125,7 +133,7 @@ class Problem | ||
125 | 133 | ||
126 | def cache_notice_attributes(notice=nil) | 134 | def cache_notice_attributes(notice=nil) |
127 | notice ||= notices.first | 135 | notice ||= notices.first |
128 | - attrs = {:last_notice_at => notices.order_by([:created_at, :asc]).last.try(:created_at)} | 136 | + attrs = {:last_notice_at => notices.order_by([:created_at, :asc]).last.try(:created_at), :first_notice_at => notices.order_by([:created_at, :asc]).first.try(:created_at)} |
129 | attrs.merge!( | 137 | attrs.merge!( |
130 | :message => notice.message, | 138 | :message => notice.message, |
131 | :environment => notice.environment_name, | 139 | :environment => notice.environment_name, |
config/routes.rb
@@ -37,10 +37,17 @@ Errbit::Application.routes.draw do | @@ -37,10 +37,17 @@ Errbit::Application.routes.draw do | ||
37 | delete :unlink_issue | 37 | delete :unlink_issue |
38 | end | 38 | end |
39 | end | 39 | end |
40 | - | 40 | + |
41 | resources :deploys, :only => [:index] | 41 | resources :deploys, :only => [:index] |
42 | end | 42 | end |
43 | 43 | ||
44 | + namespace :api do | ||
45 | + namespace :v1 do | ||
46 | + resources :problems, :only => [:index], :defaults => { :format => 'json' } | ||
47 | + resources :notices, :only => [:index], :defaults => { :format => 'json' } | ||
48 | + end | ||
49 | + end | ||
50 | + | ||
44 | root :to => 'apps#index' | 51 | root :to => 'apps#index' |
45 | 52 | ||
46 | end | 53 | end |
db/migrate/20120822195841_set_first_notice_at_on_problems.rb
0 → 100644
@@ -0,0 +1,10 @@ | @@ -0,0 +1,10 @@ | ||
1 | +class SetFirstNoticeAtOnProblems < Mongoid::Migration | ||
2 | + def self.up | ||
3 | + Problem.all.each do |problem| | ||
4 | + problem.update_attribute :first_notice_at, problem.notices.order_by([:created_at, :asc]).first.try(:created_at) | ||
5 | + end | ||
6 | + end | ||
7 | + | ||
8 | + def self.down | ||
9 | + end | ||
10 | +end | ||
0 | \ No newline at end of file | 11 | \ No newline at end of file |
@@ -0,0 +1,58 @@ | @@ -0,0 +1,58 @@ | ||
1 | +require 'spec_helper' | ||
2 | + | ||
3 | +describe Api::V1::NoticesController do | ||
4 | + | ||
5 | + context "when logged in" do | ||
6 | + before do | ||
7 | + @user = Fabricate(:user) | ||
8 | + end | ||
9 | + | ||
10 | + describe "GET /api/v1/notices" do | ||
11 | + before do | ||
12 | + Fabricate(:notice, created_at: Time.new(2012, 8, 01)) | ||
13 | + Fabricate(:notice, created_at: Time.new(2012, 8, 01)) | ||
14 | + Fabricate(:notice, created_at: Time.new(2012, 8, 21)) | ||
15 | + Fabricate(:notice, created_at: Time.new(2012, 8, 30)) | ||
16 | + end | ||
17 | + | ||
18 | + | ||
19 | + | ||
20 | + it "should return JSON if JSON is requested" do | ||
21 | + get :index, auth_token: @user.authentication_token, format: "json" | ||
22 | + lambda { JSON.load(response.body) }.should_not raise_error(JSON::ParserError) | ||
23 | + end | ||
24 | + | ||
25 | + it "should return XML if XML is requested" do | ||
26 | + get :index, auth_token: @user.authentication_token, format: "xml" | ||
27 | + lambda { XML::Parser.string(response.body).parse }.should_not raise_error | ||
28 | + end | ||
29 | + | ||
30 | + it "should return JSON by default" do | ||
31 | + get :index, auth_token: @user.authentication_token | ||
32 | + lambda { JSON.load(response.body) }.should_not raise_error(JSON::ParserError) | ||
33 | + end | ||
34 | + | ||
35 | + | ||
36 | + | ||
37 | + describe "given a date range" do | ||
38 | + | ||
39 | + it "should return only the notices created during the date range" do | ||
40 | + get :index, {auth_token: @user.authentication_token, start_date: "2012-08-01", end_date: "2012-08-27"} | ||
41 | + response.should be_success | ||
42 | + notices = JSON.load response.body | ||
43 | + notices.length.should == 3 | ||
44 | + end | ||
45 | + | ||
46 | + end | ||
47 | + | ||
48 | + it "should return all notices" do | ||
49 | + get :index, {auth_token: @user.authentication_token} | ||
50 | + response.should be_success | ||
51 | + notices = JSON.load response.body | ||
52 | + notices.length.should == 4 | ||
53 | + end | ||
54 | + | ||
55 | + end | ||
56 | + end | ||
57 | + | ||
58 | +end |
@@ -0,0 +1,58 @@ | @@ -0,0 +1,58 @@ | ||
1 | +require 'spec_helper' | ||
2 | + | ||
3 | +describe Api::V1::ProblemsController do | ||
4 | + | ||
5 | + context "when logged in" do | ||
6 | + before do | ||
7 | + @user = Fabricate(:user) | ||
8 | + end | ||
9 | + | ||
10 | + describe "GET /api/v1/problems" do | ||
11 | + before do | ||
12 | + Fabricate(:problem, :first_notice_at => Date.new(2012, 8, 01), :resolved_at => Date.new(2012, 8, 02)) | ||
13 | + Fabricate(:problem, :first_notice_at => Date.new(2012, 8, 01), :resolved_at => Date.new(2012, 8, 21)) | ||
14 | + Fabricate(:problem, :first_notice_at => Date.new(2012, 8, 21)) | ||
15 | + Fabricate(:problem, :first_notice_at => Date.new(2012, 8, 30)) | ||
16 | + end | ||
17 | + | ||
18 | + | ||
19 | + | ||
20 | + it "should return JSON if JSON is requested" do | ||
21 | + get :index, :auth_token => @user.authentication_token, :format => "json" | ||
22 | + lambda { JSON.load(response.body) }.should_not raise_error(JSON::ParserError) | ||
23 | + end | ||
24 | + | ||
25 | + it "should return XML if XML is requested" do | ||
26 | + get :index, :auth_token => @user.authentication_token, :format => "xml" | ||
27 | + lambda { XML::Parser.string(response.body).parse }.should_not raise_error | ||
28 | + end | ||
29 | + | ||
30 | + it "should return JSON by default" do | ||
31 | + get :index, :auth_token => @user.authentication_token | ||
32 | + lambda { JSON.load(response.body) }.should_not raise_error(JSON::ParserError) | ||
33 | + end | ||
34 | + | ||
35 | + | ||
36 | + | ||
37 | + describe "given a date range" do | ||
38 | + | ||
39 | + it "should return only the problems open during the date range" do | ||
40 | + get :index, {:auth_token => @user.authentication_token, :start_date => "2012-08-20", :end_date => "2012-08-27"} | ||
41 | + response.should be_success | ||
42 | + problems = JSON.load response.body | ||
43 | + problems.length.should == 2 | ||
44 | + end | ||
45 | + | ||
46 | + end | ||
47 | + | ||
48 | + it "should return all problems" do | ||
49 | + get :index, {:auth_token => @user.authentication_token} | ||
50 | + response.should be_success | ||
51 | + problems = JSON.load response.body | ||
52 | + problems.length.should == 4 | ||
53 | + end | ||
54 | + | ||
55 | + end | ||
56 | + end | ||
57 | + | ||
58 | +end |
spec/models/problem_spec.rb
@@ -40,6 +40,22 @@ describe Problem do | @@ -40,6 +40,22 @@ describe Problem do | ||
40 | end | 40 | end |
41 | end | 41 | end |
42 | 42 | ||
43 | + context '#first_notice_at' do | ||
44 | + it "returns the created_at timestamp of the first notice" do | ||
45 | + err = Fabricate(:err) | ||
46 | + problem = err.problem | ||
47 | + problem.should_not be_nil | ||
48 | + | ||
49 | + problem.first_notice_at.should be_nil | ||
50 | + | ||
51 | + notice1 = Fabricate(:notice, :err => err) | ||
52 | + problem.first_notice_at.should == notice1.created_at | ||
53 | + | ||
54 | + notice2 = Fabricate(:notice, :err => err) | ||
55 | + problem.first_notice_at.should == notice1.created_at | ||
56 | + end | ||
57 | + end | ||
58 | + | ||
43 | 59 | ||
44 | context '#message' do | 60 | context '#message' do |
45 | it "adding a notice caches its message" do | 61 | it "adding a notice caches its message" do |
@@ -87,6 +103,15 @@ describe Problem do | @@ -87,6 +103,15 @@ describe Problem do | ||
87 | problem.should be_resolved | 103 | problem.should be_resolved |
88 | end | 104 | end |
89 | 105 | ||
106 | + it "should record the time when it was resolved" do | ||
107 | + problem = Fabricate(:problem) | ||
108 | + expected_resolved_at = Time.now | ||
109 | + Timecop.freeze(expected_resolved_at) do | ||
110 | + problem.resolve! | ||
111 | + end | ||
112 | + problem.resolved_at.to_s.should == expected_resolved_at.to_s | ||
113 | + end | ||
114 | + | ||
90 | it "should throw an err if it's not successful" do | 115 | it "should throw an err if it's not successful" do |
91 | problem = Fabricate(:problem) | 116 | problem = Fabricate(:problem) |
92 | problem.should_not be_resolved | 117 | problem.should_not be_resolved |