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 | 42 | end |
43 | 43 | |
44 | 44 | gem 'ri_cal' |
45 | -gem 'yajl-ruby' | |
45 | +gem 'yajl-ruby', :require => "yajl" | |
46 | 46 | |
47 | 47 | group :development, :test do |
48 | 48 | gem 'rspec-rails', '~> 2.6' |
49 | 49 | gem 'webmock', :require => false |
50 | 50 | unless ENV["CI"] |
51 | 51 | gem 'ruby-debug', :platform => :mri_18 |
52 | + gem 'debugger', :platform => :mri_19 | |
53 | + gem 'pry' | |
52 | 54 | end |
53 | 55 | # gem 'rpm_contrib' |
54 | 56 | # gem 'newrelic_rpm' |
... | ... | @@ -61,6 +63,7 @@ group :test do |
61 | 63 | gem 'rspec', '~> 2.6' |
62 | 64 | gem 'database_cleaner', '~> 0.6.0' |
63 | 65 | gem 'email_spec' |
66 | + gem 'timecop' | |
64 | 67 | end |
65 | 68 | |
66 | 69 | group :heroku do | ... | ... |
Gemfile.lock
... | ... | @@ -57,6 +57,7 @@ GEM |
57 | 57 | xpath (~> 0.1.4) |
58 | 58 | childprocess (0.3.5) |
59 | 59 | ffi (~> 1.0, >= 1.0.6) |
60 | + coderay (1.0.6) | |
60 | 61 | columnize (0.3.6) |
61 | 62 | crack (0.3.1) |
62 | 63 | css_parser (1.2.6) |
... | ... | @@ -64,6 +65,13 @@ GEM |
64 | 65 | rdoc |
65 | 66 | daemons (1.1.8) |
66 | 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 | 75 | devise (1.5.3) |
68 | 76 | bcrypt-ruby (~> 3.0) |
69 | 77 | orm_adapter (~> 0.0.3) |
... | ... | @@ -122,6 +130,7 @@ GEM |
122 | 130 | i18n (>= 0.4.0) |
123 | 131 | mime-types (~> 1.16) |
124 | 132 | treetop (~> 1.4.8) |
133 | + method_source (0.7.1) | |
125 | 134 | mime-types (1.19) |
126 | 135 | mongo (1.6.2) |
127 | 136 | bson (~> 1.6.2) |
... | ... | @@ -182,6 +191,10 @@ GEM |
182 | 191 | premailer (1.7.3) |
183 | 192 | css_parser (>= 1.1.9) |
184 | 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 | 198 | rack (1.4.1) |
186 | 199 | rack-cache (1.2) |
187 | 200 | rack (>= 0.4) |
... | ... | @@ -243,6 +256,7 @@ GEM |
243 | 256 | libwebsocket (~> 0.1.3) |
244 | 257 | multi_json (~> 1.0) |
245 | 258 | rubyzip |
259 | + slop (2.4.4) | |
246 | 260 | sprockets (2.1.3) |
247 | 261 | hike (~> 1.2) |
248 | 262 | rack (~> 1.0) |
... | ... | @@ -255,6 +269,7 @@ GEM |
255 | 269 | rack (>= 1.0.0) |
256 | 270 | thor (0.16.0) |
257 | 271 | tilt (1.3.3) |
272 | + timecop (0.3.5) | |
258 | 273 | treetop (1.4.10) |
259 | 274 | polyglot |
260 | 275 | polyglot (>= 0.3.1) |
... | ... | @@ -288,6 +303,7 @@ DEPENDENCIES |
288 | 303 | capistrano |
289 | 304 | capybara |
290 | 305 | database_cleaner (~> 0.6.0) |
306 | + debugger | |
291 | 307 | devise (~> 1.5.3) |
292 | 308 | email_spec |
293 | 309 | execjs |
... | ... | @@ -308,6 +324,7 @@ DEPENDENCIES |
308 | 324 | omniauth-github |
309 | 325 | oruen_redmine_client |
310 | 326 | pivotal-tracker |
327 | + pry | |
311 | 328 | rack-ssl |
312 | 329 | rack-ssl-enforcer |
313 | 330 | rails (= 3.2.8) |
... | ... | @@ -319,6 +336,7 @@ DEPENDENCIES |
319 | 336 | ruby-fogbugz |
320 | 337 | therubyracer |
321 | 338 | thin |
339 | + timecop | |
322 | 340 | uglifier (>= 1.0.3) |
323 | 341 | unicorn |
324 | 342 | useragent (~> 0.3.1) | ... | ... |
... | ... | @@ -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 @@ |
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 | 7 | include Mongoid::Timestamps |
8 | 8 | |
9 | 9 | field :last_notice_at, :type => DateTime |
10 | + field :first_notice_at, :type => DateTime | |
10 | 11 | field :last_deploy_at, :type => Time |
11 | 12 | field :resolved, :type => Boolean, :default => false |
13 | + field :resolved_at, :type => Time | |
12 | 14 | field :issue_link, :type => String |
13 | 15 | field :issue_type, :type => String |
14 | 16 | |
... | ... | @@ -28,7 +30,9 @@ class Problem |
28 | 30 | index :app_name |
29 | 31 | index :message |
30 | 32 | index :last_notice_at |
33 | + index :first_notice_at | |
31 | 34 | index :last_deploy_at |
35 | + index :resolved_at | |
32 | 36 | index :notices_count |
33 | 37 | |
34 | 38 | belongs_to :app |
... | ... | @@ -52,7 +56,7 @@ class Problem |
52 | 56 | end |
53 | 57 | |
54 | 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 | 60 | end |
57 | 61 | |
58 | 62 | def unresolve! |
... | ... | @@ -103,6 +107,10 @@ class Problem |
103 | 107 | else raise("\"#{sort}\" is not a recognized sort") |
104 | 108 | end |
105 | 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 | 116 | def reset_cached_attributes |
... | ... | @@ -125,7 +133,7 @@ class Problem |
125 | 133 | |
126 | 134 | def cache_notice_attributes(notice=nil) |
127 | 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 | 137 | attrs.merge!( |
130 | 138 | :message => notice.message, |
131 | 139 | :environment => notice.environment_name, | ... | ... |
config/routes.rb
... | ... | @@ -37,10 +37,17 @@ Errbit::Application.routes.draw do |
37 | 37 | delete :unlink_issue |
38 | 38 | end |
39 | 39 | end |
40 | - | |
40 | + | |
41 | 41 | resources :deploys, :only => [:index] |
42 | 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 | 51 | root :to => 'apps#index' |
45 | 52 | |
46 | 53 | end | ... | ... |
db/migrate/20120822195841_set_first_notice_at_on_problems.rb
0 → 100644
... | ... | @@ -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 | 11 | \ No newline at end of file | ... | ... |
... | ... | @@ -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 @@ |
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 | 40 | end |
41 | 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 | 60 | context '#message' do |
45 | 61 | it "adding a notice caches its message" do |
... | ... | @@ -87,6 +103,15 @@ describe Problem do |
87 | 103 | problem.should be_resolved |
88 | 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 | 115 | it "should throw an err if it's not successful" do |
91 | 116 | problem = Fabricate(:problem) |
92 | 117 | problem.should_not be_resolved | ... | ... |