diff --git a/Gemfile b/Gemfile index 0d5b4fb5..98a80b32 100644 --- a/Gemfile +++ b/Gemfile @@ -17,7 +17,7 @@ gem "aws-sdk" gem 'delayed_job_active_record' gem "daemons" gem 'jquery-rails', '>= 1.0.12' - +gem 'pivotal_git_scripts' group :postgres do gem "pg" diff --git a/Gemfile.lock b/Gemfile.lock index 83b3b99a..c50d6495 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -32,13 +32,13 @@ GEM addressable (2.2.6) archive-tar-minitar (0.5.2) arel (2.0.10) - aws-sdk (1.3.2) + aws-sdk (1.3.4) httparty (~> 0.7) json (~> 1.4) nokogiri (>= 1.4.4) uuidtools (~> 2.1) builder (2.1.2) - bunny (0.7.8) + bunny (0.7.9) capistrano (2.9.0) highline net-scp (>= 1.0.0) @@ -59,7 +59,7 @@ GEM moneta ohai (>= 0.5.0) columnize (0.3.6) - daemons (1.1.6) + daemons (1.1.8) delayed_job (3.0.1) activesupport (~> 3.0) delayed_job_active_record (0.3.2) @@ -86,7 +86,7 @@ GEM formatador (0.2.1) gem_plugin (0.2.3) headless (0.1.0) - heroku (2.19.1) + heroku (2.20.0) launchy (>= 0.3.2) rest-client (~> 1.6.1) rubyzip @@ -144,7 +144,8 @@ GEM mixlib-log systemu (~> 2.2.0) yajl-ruby - pg (0.12.2) + pg (0.13.1) + pivotal_git_scripts (1.1.0) polyglot (0.3.3) rack (1.2.5) rack-mount (0.6.14) @@ -193,7 +194,7 @@ GEM ruby-openid (>= 2.1.7) ruby_core_source (0.1.5) archive-tar-minitar (>= 0.5.2) - rubyzip (0.9.5) + rubyzip (0.9.6.1) rvm (1.9.2) selenium-client (1.2.18) selenium-rc (2.4.0) @@ -241,6 +242,7 @@ DEPENDENCIES mysql2 (< 0.3) nokogiri pg + pivotal_git_scripts rails (= 3.0.3) rake rspec-rails (= 2.2.0) diff --git a/app/views/dashboards/_project.html.erb b/app/views/dashboards/_project.html.erb index be021e29..c6881360 100644 --- a/app/views/dashboards/_project.html.erb +++ b/app/views/dashboards/_project.html.erb @@ -2,18 +2,27 @@ if project.red? background = 'redbox' background = 'redbox-aggregate' if project.is_a? AggregateProject - status_image = project.building? ? 'build-loader-red.gif' : 'exclamation.png' + if project.building? + status_class = 'project_status red building' + else + status_class = 'project_status red' + end elsif project.green? background = 'greenbox' background = 'greenbox-aggregate' if project.is_a? AggregateProject - status_image = project.building? ? 'build-loader-green.gif' : 'checkmark.png' + if project.building? + status_class = 'project_status green building' + else + status_class = 'project_status green' + end else background = 'bluebox' background = 'bluebox-aggregate' if project.is_a? AggregateProject + status_class = 'project_status offline' end %> -
+

<%= link_to(h(project.name), project.url) %>

@@ -28,11 +37,6 @@ <%= render :partial=>'dashboards/history', :locals=>{:project=>project} %>
-
- <% if project.online? %> - <%= link_to(image_tag(status_image, {:class => 'status_image'}), project.status.url) %> - <% else %> - <%= image_tag("questionmark.png", {:class => 'status_image'}) %> - <% end %> +
diff --git a/app/views/dashboards/_twitter_search.html.erb b/app/views/dashboards/_twitter_search.html.erb index 588f11ba..c10d6345 100644 --- a/app/views/dashboards/_twitter_search.html.erb +++ b/app/views/dashboards/_twitter_search.html.erb @@ -1,36 +1,36 @@ -
> - +
+
diff --git a/app/views/dashboards/show.html.erb b/app/views/dashboards/show.html.erb index 6a91c928..169331d8 100644 --- a/app/views/dashboards/show.html.erb +++ b/app/views/dashboards/show.html.erb @@ -5,7 +5,11 @@ }); <% end %> - +
+ <% ["/images/bluebox-aggregate.png", "/images/questionmark.png", "/images/bluebox.png"].each do |image| %> + <%= image_tag image %> + <% end %> +
<% for project in @projects %>
diff --git a/app/views/messages/index.html.erb b/app/views/messages/index.html.erb index d610e737..a58e8c07 100644 --- a/app/views/messages/index.html.erb +++ b/app/views/messages/index.html.erb @@ -14,7 +14,9 @@ <% for message in @messages %> "> <%=h message.text %> - <%=h time_ago_in_words(message.expires_at) %> + <% if message.expires_at %> + <%=h time_ago_in_words(message.expires_at) %> + <% end %> <%= message.tag_list %> <%= link_to 'Edit', edit_message_path(message), :class => "button" %> <%= link_to 'Delete', message_path(message), :method => :delete, :class => "button" %> diff --git a/public/javascripts/refresh.js b/public/javascripts/refresh.js index 6011fef5..94432bdd 100644 --- a/public/javascripts/refresh.js +++ b/public/javascripts/refresh.js @@ -16,23 +16,44 @@ function refresh() { var currentDiv = ProjectDivs[i]; var id = jQuery(currentDiv).attr("project_id"); if (id) { - jQuery.get('/projects/'+id+'/load_project_with_status', function reloadDiv(divContents) { + jQuery.ajax({ + url: '/projects/'+id+'/load_project_with_status', + method: 'GET', + success: function reloadDiv(divContents) { var new_project_id = this.url.split('/')[2]; if (divContents.length > 0) { jQuery('.projects div.project:not(.aggregate) div.box[project_id='+new_project_id+']').replaceWith(divContents); } - }); + }, + error: (function(currentDiv) { + return function errorState(xhr) { + var new_id = jQuery(currentDiv).attr("project_id"); + jQuery(".projects div.project:not(.aggregate) div.box[project_id="+ new_id +"] div.project_status").addClass('offline'); + jQuery(".projects div.project:not(.aggregate) div.box[project_id="+ new_id +"]").addClass('bluebox'); + jQuery(".projects div.project:not(.aggregate) div.box[project_id="+ new_id +"] div.project_status").removeClass('building'); + } + })(currentDiv) + }); } else { id = jQuery(currentDiv).attr("message_id"); if (id) { - jQuery.get("/messages/"+id+"/load_message", function reloadDiv(divContents) { + jQuery.ajax({ + url:"/messages/"+id+"/load_message", + method: 'GET', + success: function reloadDiv(divContents) { var new_message_id = this.url.split('/')[2]; if (divContents.length > 0) { jQuery('.projects div.box[message_id='+new_message_id+']').replaceWith(divContents); } else { jQuery('.projects div.box[message_id='+new_message_id+']').remove(); } - + }, + error: (function(currentDiv) { + return function errorState(xhr) { + var new_id = jQuery(currentDiv).attr("message_id"); + jQuery(".projects div.message div.box[message_id="+ new_id +"]").addClass('offline'); + } + })(currentDiv) }); } } @@ -42,23 +63,47 @@ function refresh() { var currentDiv = aggregateDivs[i]; var id = jQuery(currentDiv).attr("project_id"); if (id) { - jQuery.get('/aggregate_projects/'+id+'/load_aggregate_project_with_status', function reloadDiv(divContents) { + jQuery.ajax({ + url: '/aggregate_projects/'+id+'/load_aggregate_project_with_status', + method: 'GET', + success: function reloadDiv(divContents) { var new_project_id = this.url.split('/')[2]; if (divContents.length > 0) { jQuery('.projects div.project.aggregate div.box[project_id='+new_project_id+']').replaceWith(divContents); } + }, + error: (function(currentDiv) { + return function errorState(xhr) { + var new_id = jQuery(currentDiv).attr("project_id"); + jQuery(".projects div.project.aggregate div.box[project_id="+ new_id +"] div.project_status").addClass('offline'); + jQuery(".projects div.project.aggregate div.box[project_id="+ new_id +"]").addClass('bluebox-aggregate'); + jQuery(".projects div.project.aggregate div.box[project_id="+ new_id +"] div.project_status").removeClass('building'); + } + })(currentDiv) }); } } var twitterFeeds = jQuery(".projects div.project.message div.tweets"); for (var j = 0; j < twitterFeeds.length; j++) { var currentDiv = twitterFeeds[j]; + var id = jQuery(currentDiv).attr("tweet_id"); + if (id) { - jQuery.get('/twitter_searches/'+id+'/load_tweet', function reloadDiv(divContents) { - var tweet_id = jQuery(divContents).attr('project_id'); - jQuery('.projects div.project.message div.tweets[tweet_id='+tweet_id+']').replaceWith(divContents); + jQuery.ajax({ + url: '/twitter_searches/'+id+'/load_tweet', + method: 'GET', + success: function reloadDiv(divContents) { + var tweet_id = jQuery(divContents).attr('tweet_id'); + var foundExistingTwitterBox = jQuery('.projects div.project.message div.tweets[tweet_id='+tweet_id+']') + foundExistingTwitterBox.replaceWith(jQuery(divContents)); + }, + error: (function(currentDiv) { + return function errorState(xhr) { + var new_id = jQuery(currentDiv).attr("tweet_id"); + jQuery(".projects div.project.message div.tweets[tweet_id="+ new_id +"]").addClass('offline'); } - ); + })(currentDiv) + }); } } scheduleRefresh(); diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css index 5b9afea9..936e4082 100644 --- a/public/stylesheets/style.css +++ b/public/stylesheets/style.css @@ -1,20 +1,20 @@ body { font-family: Helvetica, sans-serif; - font-size: 13px; + font-size: 13px; background: white; margin: 0; } -body, p, li { - color: black; +body, p, li { + color: black; } p { font-size: 1em; line-height: 1.6em; font-weight: normal; - margin: 0 0 1.6em 0; - padding: 0; + margin: 0 0 1.6em 0; + padding: 0; } /* @@ -22,9 +22,9 @@ p { */ h1 { - font-family: Helvetica, sans-serif; - padding: 0; - line-height: 1em; + font-family: Helvetica, sans-serif; + padding: 0; + line-height: 1em; margin: 15px 0; font-weight: bold; } @@ -49,9 +49,9 @@ h1 { =Links */ -a, +a, a:link { - color: black; + color: black; } a:hover { @@ -87,7 +87,7 @@ a:hover { #title { padding-top: 10px; margin-left: 300px; - text-align: left; + text-align: left; } .project { @@ -97,11 +97,72 @@ a:hover { margin-bottom:15px; width:230px; height:210px; + +} + +.project_status.green { + background: url("/images/checkmark.png") no-repeat; + width: 50px; + height: 50px; + margin: 0 0 25px 80px; +} +.project_status.red { + background: url("/images/exclamation.png") no-repeat; + width: 50px; + height: 50px; + margin: 0 0 25px 97px; +} +.project_status.building.green { + background-image: url("/images/build-loader-green.gif"); + width: 200px; + height: 50px; + margin: 0 0 25px 20px; +} +.project_status.building.red { + background-image: url("/images/build-loader-red.gif"); + width: 200px; + height: 50px; + margin: 0 0 25px 20px; +} +.box.redbox { + background: url('/images/redbox.png') no-repeat top left; +} + +.box.redbox-aggregate { + background: url('/images/redbox-aggregate.png') no-repeat top left; +} +.box.greenbox { + background: url('/images/greenbox.png') no-repeat top left; +} +.box.greenbox-aggregate { + background: url("/images/greenbox-aggregate.png") no-repeat top left; +} +.box.bluebox { + background: url('/images/bluebox.png') no-repeat top left; +} +.box.bluebox-aggregate { + background: url('/images/bluebox-aggregate.png') no-repeat top left; +} +.tweet.offline { + background: url('/images/bluebox-aggregate.png') no-repeat top left; +} + +.cached_images { + display: none; +} + + + +.project_status.offline { + background: url("/images/questionmark.png") no-repeat; + width: 50px; + height: 50px; + margin: 0 0 27px 95px; } .project_visit { float: right; - font-size: smaller; + font-size: smaller; } .project_name { @@ -126,7 +187,7 @@ a:hover { .project_published_at { text-align: center; - font-size: 1em; + font-size: 1em; } .project_red_since { @@ -138,7 +199,7 @@ a:hover { .project_invalid { text-align: center; - font-size: 1em; + font-size: 1em; } #top_right { diff --git a/spec/javascripts/refreshSpec.js b/spec/javascripts/refreshSpec.js index bc032179..d75c6889 100644 --- a/spec/javascripts/refreshSpec.js +++ b/spec/javascripts/refreshSpec.js @@ -78,49 +78,97 @@ describe('onChangeRefreshTimeoutDropdown', function() { it("should set the timeout to new value", function() { window.currentTimeout = "fakeHandle" document.getElementById("refreshInterval").value = "300"; - spyOn(window, "setTimeout"); - spyOn(window, "clearTimeout"); - onChangeRefreshTimeoutDropdown(); - expect(window.setTimeout).wasCalledWith("refresh();", 300000); - expect(window.clearTimeout).wasCalledWith("fakeHandle"); + spyOn(window, "setTimeout"); + spyOn(window, "clearTimeout"); + onChangeRefreshTimeoutDropdown(); + expect(window.setTimeout).wasCalledWith("refresh();", 300000); + expect(window.clearTimeout).wasCalledWith("fakeHandle"); }) }); describe('refresh', function() { var spyOnGet; beforeEach(function() { - var $ = jQuery.noConflict(); - var fixtures = '' + - '
' + - '
' + - '
' + - '
' + - '
' + - '
' + - '
' + - '
'+ - '
'+ - '
'+ - '
' + - '
'; - spyOnGet = spyOn(jQuery, "get"); - spyOn(window, "scheduleRefresh"); - setFixtures(fixtures); + var $ = jQuery.noConflict(); + var fixtures = '' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
'+ + '
'+ + '
'+ + '
' + + '
'; + spyOnGet = spyOn(jQuery, "ajax"); + spyOn(window, "scheduleRefresh"); + setFixtures(fixtures); }); afterEach(function(){ - jQuery.get.reset(); + jQuery.ajax.reset(); }); it("should call $.get for the project", function() { - refresh(); + refresh(); expect(spyOnGet).toHaveBeenCalled(); - expect(spyOnGet.argsForCall[0][0]).toBe("projects/1/load_project_with_status"); - expect(spyOnGet.argsForCall[1][0]).toBe("messages/1/load_message"); - expect(spyOnGet.argsForCall[2][0]).toBe("aggregate_projects/1/load_aggregate_project_with_status"); - expect(spyOnGet.argsForCall[3][0]).toBe("twitter_searches/1/load_tweet"); + expect(spyOnGet.argsForCall[0][0].url).toBe("/projects/1/load_project_with_status"); + expect(spyOnGet.argsForCall[1][0].url).toBe("/messages/1/load_message"); + expect(spyOnGet.argsForCall[2][0].url).toBe("/aggregate_projects/1/load_aggregate_project_with_status"); + expect(spyOnGet.argsForCall[3][0].url).toBe("/twitter_searches/1/load_tweet"); expect(window.scheduleRefresh).toHaveBeenCalled(); }); +}); +describe("when the server is down", function() { + beforeEach(function() { + var fixtures = '' + + '
' + + '
' + + '
' + + '
' + + '
'+ + '
' + + '
' + + '
' + + '
' + + '
'+ + '
' + + '
'+ + '
'+ + '
'+ + '
' + + '
'; + spyOn(window, "scheduleRefresh"); + setFixtures(fixtures); + spyOn(jQuery, "ajax").andCallFake(function(params) { + params.error({status: 404, statusText:''}, 'Not found'); + }); + }); + afterEach(function(){ + jQuery.ajax.reset(); + }); + + it("should make the background blue", function() { + refresh(); + expect(jQuery('div.project:not(.aggregate) div.box')).toHaveClass("bluebox"); + expect(jQuery('div.project.aggregate div.box')).toHaveClass("bluebox-aggregate"); + expect(jQuery('div.project .project_status')).toHaveClass("offline"); + expect(jQuery('div.project .project_status')).not.toHaveClass("building"); + }); + describe("after the server is back online", function() { + beforeEach(function() { + jQuery.ajax.andReturn("200"); + }); + it("should not have class bluebox", function() { + refresh(); + expect(jQuery('div.project:not(.aggregate) div.box')).not.toHaveClass("bluebox"); + expect(jQuery('div.project.aggregate div.box')).not.toHaveClass("bluebox-aggregate"); + expect(jQuery('div.project .project_status')).not.toHaveClass("offline"); + }); + }); });