Commit 49147153c60fa231d7ac0eef1d5e558222ddd73b
Exists in
master
and in
1 other branch
Merge branch 'tracker' of https://github.com/benlangfeld/errbit into benlangfeld_errbit
Conflicts: Gemfile app/helpers/application_helper.rb app/models/app.rb
Showing
14 changed files
with
247 additions
and
114 deletions
Show diff stats
.rspec
Gemfile
@@ -10,6 +10,7 @@ gem 'lighthouse-api' | @@ -10,6 +10,7 @@ gem 'lighthouse-api' | ||
10 | gem 'redmine_client', :git => "git://github.com/oruen/redmine_client.git" | 10 | gem 'redmine_client', :git => "git://github.com/oruen/redmine_client.git" |
11 | gem 'mongoid_rails_migrations' | 11 | gem 'mongoid_rails_migrations' |
12 | gem 'useragent', '~> 0.3.1' | 12 | gem 'useragent', '~> 0.3.1' |
13 | +gem 'pivotal-tracker' | ||
13 | 14 | ||
14 | platform :ruby do | 15 | platform :ruby do |
15 | gem 'bson_ext', '~> 1.2' | 16 | gem 'bson_ext', '~> 1.2' |
Gemfile.lock
@@ -54,7 +54,10 @@ GEM | @@ -54,7 +54,10 @@ GEM | ||
54 | factory_girl (~> 1.3) | 54 | factory_girl (~> 1.3) |
55 | railties (>= 3.0.0) | 55 | railties (>= 3.0.0) |
56 | haml (3.0.25) | 56 | haml (3.0.25) |
57 | + happymapper (0.3.2) | ||
58 | + libxml-ruby (~> 1.1.3) | ||
57 | i18n (0.5.0) | 59 | i18n (0.5.0) |
60 | + libxml-ruby (1.1.4) | ||
58 | lighthouse-api (2.0) | 61 | lighthouse-api (2.0) |
59 | activeresource (>= 3.0.0) | 62 | activeresource (>= 3.0.0) |
60 | activesupport (>= 3.0.0) | 63 | activesupport (>= 3.0.0) |
@@ -77,6 +80,11 @@ GEM | @@ -77,6 +80,11 @@ GEM | ||
77 | rails (~> 3.0.0) | 80 | rails (~> 3.0.0) |
78 | railties (~> 3.0.0) | 81 | railties (~> 3.0.0) |
79 | nokogiri (1.4.4) | 82 | nokogiri (1.4.4) |
83 | + pivotal-tracker (0.2.0) | ||
84 | + builder | ||
85 | + happymapper (>= 0.2.4) | ||
86 | + nokogiri (~> 1.4.1) | ||
87 | + rest-client (~> 1.5.1) | ||
80 | polyglot (0.3.1) | 88 | polyglot (0.3.1) |
81 | rack (1.2.2) | 89 | rack (1.2.2) |
82 | rack-mount (0.6.14) | 90 | rack-mount (0.6.14) |
@@ -97,6 +105,8 @@ GEM | @@ -97,6 +105,8 @@ GEM | ||
97 | rake (>= 0.8.7) | 105 | rake (>= 0.8.7) |
98 | thor (~> 0.14.4) | 106 | thor (~> 0.14.4) |
99 | rake (0.8.7) | 107 | rake (0.8.7) |
108 | + rest-client (1.5.1) | ||
109 | + mime-types (>= 1.16) | ||
100 | rspec (2.5.0) | 110 | rspec (2.5.0) |
101 | rspec-core (~> 2.5.0) | 111 | rspec-core (~> 2.5.0) |
102 | rspec-expectations (~> 2.5.0) | 112 | rspec-expectations (~> 2.5.0) |
@@ -135,6 +145,7 @@ DEPENDENCIES | @@ -135,6 +145,7 @@ DEPENDENCIES | ||
135 | mongoid (= 2.0.0.rc.8) | 145 | mongoid (= 2.0.0.rc.8) |
136 | mongoid_rails_migrations | 146 | mongoid_rails_migrations |
137 | nokogiri | 147 | nokogiri |
148 | + pivotal-tracker | ||
138 | rails (= 3.0.5) | 149 | rails (= 3.0.5) |
139 | redmine_client! | 150 | redmine_client! |
140 | rspec (~> 2.5) | 151 | rspec (~> 2.5) |
README.md
@@ -110,6 +110,12 @@ Redmine integration | @@ -110,6 +110,12 @@ Redmine integration | ||
110 | * Errbit uses token-based authentication. Get your API Key or visit [http://www.redmine.org/projects/redmine/wiki/Rest_api#Authentication](http://www.redmine.org/projects/redmine/wiki/Rest_api#Authentication) to learn how to get it. | 110 | * Errbit uses token-based authentication. Get your API Key or visit [http://www.redmine.org/projects/redmine/wiki/Rest_api#Authentication](http://www.redmine.org/projects/redmine/wiki/Rest_api#Authentication) to learn how to get it. |
111 | * Project id is an identifier of your project, i.e. **chilliproject** for project at http://www.redmine.org/projects/chilliproject | 111 | * Project id is an identifier of your project, i.e. **chilliproject** for project at http://www.redmine.org/projects/chilliproject |
112 | 112 | ||
113 | +Pivotal Tracker integration | ||
114 | +------------------------- | ||
115 | + | ||
116 | +* Errbit uses token-based authentication. Get your API Key or visit [http://www.pivotaltracker.com/help/api](http://www.pivotaltracker.com/help/api) to learn how to get it. | ||
117 | +* Project id is an identifier of your project, i.e. **24324** for project at http://www.pivotaltracker.com/projects/24324 | ||
118 | + | ||
113 | TODO | 119 | TODO |
114 | ---- | 120 | ---- |
115 | 121 |
app/helpers/application_helper.rb
@@ -5,7 +5,6 @@ module ApplicationHelper | @@ -5,7 +5,6 @@ module ApplicationHelper | ||
5 | object.issue_tracker_type == "lighthouseapp" | 5 | object.issue_tracker_type == "lighthouseapp" |
6 | end | 6 | end |
7 | 7 | ||
8 | - | ||
9 | def user_agent_graph(error) | 8 | def user_agent_graph(error) |
10 | tallies = tally(error.notices) {|notice| pretty_user_agent(notice.user_agent)} | 9 | tallies = tally(error.notices) {|notice| pretty_user_agent(notice.user_agent)} |
11 | create_percentage_table(tallies, :total => error.notices.count) | 10 | create_percentage_table(tallies, :total => error.notices.count) |
@@ -15,7 +14,6 @@ module ApplicationHelper | @@ -15,7 +14,6 @@ module ApplicationHelper | ||
15 | (user_agent.nil? || user_agent.none?) ? "N/A" : "#{user_agent.browser} #{user_agent.version}" | 14 | (user_agent.nil? || user_agent.none?) ? "N/A" : "#{user_agent.browser} #{user_agent.version}" |
16 | end | 15 | end |
17 | 16 | ||
18 | - | ||
19 | def tally(collection, &block) | 17 | def tally(collection, &block) |
20 | collection.inject({}) do |tallies, item| | 18 | collection.inject({}) do |tallies, item| |
21 | value = yield item | 19 | value = yield item |
@@ -24,7 +22,6 @@ module ApplicationHelper | @@ -24,7 +22,6 @@ module ApplicationHelper | ||
24 | end | 22 | end |
25 | end | 23 | end |
26 | 24 | ||
27 | - | ||
28 | def create_percentage_table(tallies, options={}) | 25 | def create_percentage_table(tallies, options={}) |
29 | total = (options[:total] || total_from_tallies(tallies)) | 26 | total = (options[:total] || total_from_tallies(tallies)) |
30 | percent = 100.0 / total.to_f | 27 | percent = 100.0 / total.to_f |
@@ -33,13 +30,16 @@ module ApplicationHelper | @@ -33,13 +30,16 @@ module ApplicationHelper | ||
33 | render :partial => "errs/tally_table", :locals => {:rows => rows} | 30 | render :partial => "errs/tally_table", :locals => {:rows => rows} |
34 | end | 31 | end |
35 | 32 | ||
36 | - | ||
37 | -private | ||
38 | - | ||
39 | - | ||
40 | def total_from_tallies(tallies) | 33 | def total_from_tallies(tallies) |
41 | tallies.values.inject(0) {|sum, n| sum + n} | 34 | tallies.values.inject(0) {|sum, n| sum + n} |
42 | end | 35 | end |
36 | + private :total_from_tallies | ||
43 | 37 | ||
44 | - | 38 | + def redmine_tracker? object |
39 | + object.issue_tracker_type == "redmine" | ||
40 | + end | ||
41 | + | ||
42 | + def pivotal_tracker? object | ||
43 | + object.issue_tracker_type == "pivotal" | ||
44 | + end | ||
45 | end | 45 | end |
app/models/app.rb
@@ -33,7 +33,7 @@ class App | @@ -33,7 +33,7 @@ class App | ||
33 | accepts_nested_attributes_for :watchers, :allow_destroy => true, | 33 | accepts_nested_attributes_for :watchers, :allow_destroy => true, |
34 | :reject_if => proc { |attrs| attrs[:user_id].blank? && attrs[:email].blank? } | 34 | :reject_if => proc { |attrs| attrs[:user_id].blank? && attrs[:email].blank? } |
35 | accepts_nested_attributes_for :issue_tracker, :allow_destroy => true, | 35 | accepts_nested_attributes_for :issue_tracker, :allow_destroy => true, |
36 | - :reject_if => proc { |attrs| !%w( lighthouseapp redmine ).include?(attrs[:issue_tracker_type]) } | 36 | + :reject_if => proc { |attrs| !%w(lighthouseapp redmine pivotal).include?(attrs[:issue_tracker_type]) } |
37 | 37 | ||
38 | # Mongoid Bug: find(id) on association proxies returns an Enumerator | 38 | # Mongoid Bug: find(id) on association proxies returns an Enumerator |
39 | def self.find_by_id!(app_id) | 39 | def self.find_by_id!(app_id) |
app/models/issue_tracker.rb
@@ -6,7 +6,7 @@ class IssueTracker | @@ -6,7 +6,7 @@ class IssueTracker | ||
6 | default_url_options[:host] = Errbit::Application.config.action_mailer.default_url_options[:host] | 6 | default_url_options[:host] = Errbit::Application.config.action_mailer.default_url_options[:host] |
7 | 7 | ||
8 | validate :check_params | 8 | validate :check_params |
9 | - | 9 | + |
10 | embedded_in :app, :inverse_of => :issue_tracker | 10 | embedded_in :app, :inverse_of => :issue_tracker |
11 | 11 | ||
12 | field :account, :type => String | 12 | field :account, :type => String |
@@ -15,8 +15,14 @@ class IssueTracker | @@ -15,8 +15,14 @@ class IssueTracker | ||
15 | field :issue_tracker_type, :type => String, :default => 'lighthouseapp' | 15 | field :issue_tracker_type, :type => String, :default => 'lighthouseapp' |
16 | 16 | ||
17 | def create_issue err | 17 | def create_issue err |
18 | - return create_lighthouseapp_issue err if issue_tracker_type == 'lighthouseapp' | ||
19 | - create_redmine_issue err if issue_tracker_type == 'redmine' | 18 | + case issue_tracker_type |
19 | + when 'lighthouseapp' | ||
20 | + create_lighthouseapp_issue err | ||
21 | + when 'redmine' | ||
22 | + create_redmine_issue err | ||
23 | + when 'pivotal' | ||
24 | + create_pivotal_issue err | ||
25 | + end | ||
20 | end | 26 | end |
21 | 27 | ||
22 | protected | 28 | protected |
@@ -34,6 +40,14 @@ class IssueTracker | @@ -34,6 +40,14 @@ class IssueTracker | ||
34 | err.update_attribute :issue_link, "#{RedmineClient::Issue.site.to_s.sub(/#{RedmineClient::Issue.site.path}$/, '')}#{RedmineClient::Issue.element_path(issue.id, :project_id => project_id)}".sub(/\.xml\?project_id=#{project_id}$/, "\?project_id=#{project_id}") | 40 | err.update_attribute :issue_link, "#{RedmineClient::Issue.site.to_s.sub(/#{RedmineClient::Issue.site.path}$/, '')}#{RedmineClient::Issue.element_path(issue.id, :project_id => project_id)}".sub(/\.xml\?project_id=#{project_id}$/, "\?project_id=#{project_id}") |
35 | end | 41 | end |
36 | 42 | ||
43 | + def create_pivotal_issue err | ||
44 | + PivotalTracker::Client.token = api_token | ||
45 | + PivotalTracker::Client.use_ssl = true | ||
46 | + project = PivotalTracker::Project.find project_id.to_i | ||
47 | + story = project.stories.create :name => issue_title(err), :story_type => 'bug', :description => self.class.pivotal_body_template.result(binding) | ||
48 | + err.update_attribute :issue_link, "https://www.pivotaltracker.com/story/show/#{story.id}" | ||
49 | + end | ||
50 | + | ||
37 | def create_lighthouseapp_issue err | 51 | def create_lighthouseapp_issue err |
38 | Lighthouse.account = account | 52 | Lighthouse.account = account |
39 | Lighthouse.token = api_token | 53 | Lighthouse.token = api_token |
@@ -56,12 +70,17 @@ class IssueTracker | @@ -56,12 +70,17 @@ class IssueTracker | ||
56 | end | 70 | end |
57 | 71 | ||
58 | def check_params | 72 | def check_params |
59 | - blank_flags = %w( api_token project_id account ).map {|m| self[m].blank? } | 73 | + blank_flag_fields = %w(api_token project_id) |
74 | + blank_flag_fields << 'account' if %w(lighthouseapp redmine).include? issue_tracker_type | ||
75 | + blank_flags = blank_flag_fields.map {|m| self[m].blank? } | ||
60 | if blank_flags.any? && !blank_flags.all? | 76 | if blank_flags.any? && !blank_flags.all? |
61 | - message = if issue_tracker_type == 'lighthouseapp' | 77 | + message = case issue_tracker_type |
78 | + when 'lighthouseapp' | ||
62 | "You must specify your Lighthouseapp account, api token and project id" | 79 | "You must specify your Lighthouseapp account, api token and project id" |
63 | - else | 80 | + when 'redmine' |
64 | "You must specify your Redmine url, api token and project id" | 81 | "You must specify your Redmine url, api token and project id" |
82 | + when 'pivotal' | ||
83 | + "You must specify your Pivotal Tracker api token and project id" | ||
65 | end | 84 | end |
66 | errors.add(:base, message) | 85 | errors.add(:base, message) |
67 | end | 86 | end |
@@ -71,9 +90,13 @@ class IssueTracker | @@ -71,9 +90,13 @@ class IssueTracker | ||
71 | def lighthouseapp_body_template | 90 | def lighthouseapp_body_template |
72 | @@lighthouseapp_body_template ||= ERB.new(File.read(Rails.root + "app/views/errs/lighthouseapp_body.txt.erb").gsub(/^\s*/, '')) | 91 | @@lighthouseapp_body_template ||= ERB.new(File.read(Rails.root + "app/views/errs/lighthouseapp_body.txt.erb").gsub(/^\s*/, '')) |
73 | end | 92 | end |
74 | - | 93 | + |
75 | def redmine_body_template | 94 | def redmine_body_template |
76 | @@redmine_body_template ||= ERB.new(File.read(Rails.root + "app/views/errs/redmine_body.txt.erb")) | 95 | @@redmine_body_template ||= ERB.new(File.read(Rails.root + "app/views/errs/redmine_body.txt.erb")) |
77 | end | 96 | end |
97 | + | ||
98 | + def pivotal_body_template | ||
99 | + @@pivotal_body_template ||= ERB.new(File.read(Rails.root + "app/views/errs/pivotal_body.txt.erb")) | ||
100 | + end | ||
78 | end | 101 | end |
79 | end | 102 | end |
app/views/apps/_fields.html.haml
@@ -3,7 +3,7 @@ | @@ -3,7 +3,7 @@ | ||
3 | %div.required | 3 | %div.required |
4 | = f.label :name | 4 | = f.label :name |
5 | = f.text_field :name | 5 | = f.text_field :name |
6 | - | 6 | + |
7 | %div.checkbox | 7 | %div.checkbox |
8 | = f.check_box :notify_on_errs | 8 | = f.check_box :notify_on_errs |
9 | = f.label :notify_on_errs, 'Notify on errors' | 9 | = f.label :notify_on_errs, 'Notify on errors' |
@@ -39,19 +39,24 @@ | @@ -39,19 +39,24 @@ | ||
39 | = label_tag :issue_tracker_type_lighthouseapp, 'Lighthouse', :for => label_for_attr(w, 'issue_tracker_type_lighthouseapp') | 39 | = label_tag :issue_tracker_type_lighthouseapp, 'Lighthouse', :for => label_for_attr(w, 'issue_tracker_type_lighthouseapp') |
40 | = w.radio_button :issue_tracker_type, :redmine | 40 | = w.radio_button :issue_tracker_type, :redmine |
41 | = label_tag :issue_tracker_type_redmine, 'Redmine', :for => label_for_attr(w, 'issue_tracker_type_redmine') | 41 | = label_tag :issue_tracker_type_redmine, 'Redmine', :for => label_for_attr(w, 'issue_tracker_type_redmine') |
42 | - %div.tracker_params{:class => lighthouse_tracker?(w.object) ? 'choosen' : nil} | 42 | + = w.radio_button :issue_tracker_type, :pivotal |
43 | + = label_tag :issue_tracker_type_pivotal, 'Pivotal Tracker', :for => label_for_attr(w, 'issue_tracker_type_pivotal') | ||
44 | + %div.tracker_params.lighthouseapp{:class => lighthouse_tracker?(w.object) ? 'chosen' : nil} | ||
43 | = w.label :account, "Account" | 45 | = w.label :account, "Account" |
44 | = w.text_field :account, :placeholder => "abc from abc.lighthouseapp.com" | 46 | = w.text_field :account, :placeholder => "abc from abc.lighthouseapp.com" |
45 | = w.label :api_token, "API token" | 47 | = w.label :api_token, "API token" |
46 | = w.text_field :api_token, :placeholder => "API Token for your account" | 48 | = w.text_field :api_token, :placeholder => "API Token for your account" |
47 | = w.label :project_id, "Project ID" | 49 | = w.label :project_id, "Project ID" |
48 | = w.text_field :project_id, :placeholder => "123 from abc from abc.lighthouseapp.com/projects/123" | 50 | = w.text_field :project_id, :placeholder => "123 from abc from abc.lighthouseapp.com/projects/123" |
49 | - %div.tracker_params{:class => lighthouse_tracker?(w.object) ? nil : 'choosen'} | 51 | + %div.tracker_params.redmine{:class => redmine_tracker?(w.object) ? 'chosen' : nil} |
50 | = w.label :account, "Redmine URL" | 52 | = w.label :account, "Redmine URL" |
51 | = w.text_field :account, :placeholder => "like http://www.redmine.org/" | 53 | = w.text_field :account, :placeholder => "like http://www.redmine.org/" |
52 | = w.label :api_token, "API token" | 54 | = w.label :api_token, "API token" |
53 | = w.text_field :api_token, :placeholder => "API Token for your account" | 55 | = w.text_field :api_token, :placeholder => "API Token for your account" |
54 | = w.label :project_id, "Project ID" | 56 | = w.label :project_id, "Project ID" |
55 | = w.text_field :project_id | 57 | = w.text_field :project_id |
56 | - | ||
57 | - | 58 | + %div.tracker_params.pivotal{:class => pivotal_tracker?(w.object) ? 'chosen' : nil} |
59 | + = w.label :project_id, "Project ID" | ||
60 | + = w.text_field :project_id | ||
61 | + = w.label :api_token, "API token" | ||
62 | + = w.text_field :api_token, :placeholder => "API Token for your account" |
@@ -0,0 +1,21 @@ | @@ -0,0 +1,21 @@ | ||
1 | +See this exception on Errbit: <%= app_err_url err.app, err %> | ||
2 | +<% if notice = err.notices.first %> | ||
3 | + <% if notice.request['url'].present? %>URL: <%= notice.request['url'] %><% end %> | ||
4 | + Where: <%= notice.err.where %> | ||
5 | + Occurred: <%= notice.created_at.to_s :micro %> | ||
6 | + Similar: <%= (notice.err.notices.count - 1).to_s %> | ||
7 | + | ||
8 | + Params: | ||
9 | + <%= pretty_hash notice.params %> | ||
10 | + | ||
11 | + Session: | ||
12 | + <%= pretty_hash notice.session %> | ||
13 | + | ||
14 | + Backtrace: | ||
15 | + <%= notice.backtrace.map { |line| "#{line['number']}: #{line['file'].sub(/^\[PROJECT_ROOT\]/, '')} -> *#{line['method']}*" }.join "\n" %> | ||
16 | + | ||
17 | + Environment: | ||
18 | + <% notice.env_vars.each do |key, val| %> | ||
19 | + <%= "#{key}: #{val}" %> | ||
20 | + <% end %> | ||
21 | +<% end %> |
public/javascripts/form.js
1 | $(function(){ | 1 | $(function(){ |
2 | activateNestedForms(); | 2 | activateNestedForms(); |
3 | - | 3 | + |
4 | if($('div.watcher.nested').length) | 4 | if($('div.watcher.nested').length) |
5 | activateWatcherTypeSelector(); | 5 | activateWatcherTypeSelector(); |
6 | 6 | ||
@@ -11,9 +11,9 @@ $(function(){ | @@ -11,9 +11,9 @@ $(function(){ | ||
11 | function activateNestedForms() { | 11 | function activateNestedForms() { |
12 | $('.nested-wrapper').each(function(){ | 12 | $('.nested-wrapper').each(function(){ |
13 | var wrapper = $(this); | 13 | var wrapper = $(this); |
14 | - | 14 | + |
15 | makeNestedItemsDestroyable(wrapper); | 15 | makeNestedItemsDestroyable(wrapper); |
16 | - | 16 | + |
17 | var addLink = $('<a/>').text('add another').addClass('add-nested'); | 17 | var addLink = $('<a/>').text('add another').addClass('add-nested'); |
18 | addLink.click(appendNestedItem); | 18 | addLink.click(appendNestedItem); |
19 | wrapper.append(addLink); | 19 | wrapper.append(addLink); |
@@ -35,7 +35,7 @@ function appendNestedItem() { | @@ -35,7 +35,7 @@ function appendNestedItem() { | ||
35 | var nestedItem = addLink.parent().find('.nested').first().clone().show(); | 35 | var nestedItem = addLink.parent().find('.nested').first().clone().show(); |
36 | var timestamp = new Date(); | 36 | var timestamp = new Date(); |
37 | timestamp = timestamp.valueOf(); | 37 | timestamp = timestamp.valueOf(); |
38 | - | 38 | + |
39 | nestedItem.find('input, select').each(function(){ | 39 | nestedItem.find('input, select').each(function(){ |
40 | var input = $(this); | 40 | var input = $(this); |
41 | input.attr('id', input.attr('id').replace(/([_\[])\d+([\]_])/,'$1'+timestamp+'$2')); | 41 | input.attr('id', input.attr('id').replace(/([_\[])\d+([\]_])/,'$1'+timestamp+'$2')); |
@@ -73,17 +73,10 @@ function activateWatcherTypeSelector() { | @@ -73,17 +73,10 @@ function activateWatcherTypeSelector() { | ||
73 | } | 73 | } |
74 | 74 | ||
75 | function activateIssueTrackerTypeSelector() { | 75 | function activateIssueTrackerTypeSelector() { |
76 | - var not_choosen = $("div.tracker_params").filter(function () { | ||
77 | - return !$(this).hasClass("choosen"); | ||
78 | - }); | ||
79 | - window.hiddenTracker = not_choosen.html(); | ||
80 | - not_choosen.remove(); | ||
81 | $('div.issue_tracker input[name*=issue_tracker_type]').live('click', function(){ | 76 | $('div.issue_tracker input[name*=issue_tracker_type]').live('click', function(){ |
82 | - var choosen = $(this).val(); | 77 | + var chosen = $(this).val(); |
83 | var wrapper = $(this).closest('.nested'); | 78 | var wrapper = $(this).closest('.nested'); |
84 | - var tmp; | ||
85 | - tmp = wrapper.find('div.choosen').html(); | ||
86 | - wrapper.find('div.choosen').html(window.hiddenTracker); | ||
87 | - window.hiddenTracker = tmp; | 79 | + wrapper.find('div.chosen').removeClass('chosen'); |
80 | + wrapper.find('div.'+chosen).addClass('chosen'); | ||
88 | }); | 81 | }); |
89 | } | 82 | } |
90 | \ No newline at end of file | 83 | \ No newline at end of file |
public/stylesheets/application.css
1 | -html { | 1 | +html { |
2 | margin: 0; padding: 0; | 2 | margin: 0; padding: 0; |
3 | color: #585858; background-color: #E2E2E2; | 3 | color: #585858; background-color: #E2E2E2; |
4 | font-size: 62.8%; font-family: Helvetica, "Lucida Grande","Lucida Sans",Arial,sans-serif; | 4 | font-size: 62.8%; font-family: Helvetica, "Lucida Grande","Lucida Sans",Arial,sans-serif; |
5 | } | 5 | } |
6 | -body { | 6 | +body { |
7 | margin: 0; padding: 0; | 7 | margin: 0; padding: 0; |
8 | font-size: 1.3em; line-height: 1.4em; | 8 | font-size: 1.3em; line-height: 1.4em; |
9 | } | 9 | } |
@@ -34,7 +34,7 @@ a:visited { color: #0069cc;} | @@ -34,7 +34,7 @@ a:visited { color: #0069cc;} | ||
34 | a:hover { color: #0069cc; text-decoration: underline; } | 34 | a:hover { color: #0069cc; text-decoration: underline; } |
35 | a.action { float: right; font-size: 0.9em;} | 35 | a.action { float: right; font-size: 0.9em;} |
36 | 36 | ||
37 | -#header > div, #nav-bar, #content-wrapper, #footer { | 37 | +#header > div, #nav-bar, #content-wrapper, #footer { |
38 | width: 930px; | 38 | width: 930px; |
39 | margin: 0 auto; | 39 | margin: 0 auto; |
40 | position: relative; | 40 | position: relative; |
@@ -98,19 +98,19 @@ a.action { float: right; font-size: 0.9em;} | @@ -98,19 +98,19 @@ a.action { float: right; font-size: 0.9em;} | ||
98 | margin-bottom: 24px; | 98 | margin-bottom: 24px; |
99 | height: 41px; | 99 | height: 41px; |
100 | } | 100 | } |
101 | -#nav-bar li { | ||
102 | - float: left; | 101 | +#nav-bar li { |
102 | + float: left; | ||
103 | margin-right: 18px; | 103 | margin-right: 18px; |
104 | color: #666; | 104 | color: #666; |
105 | background: #FFF url(images/button-bg.png) 0 bottom repeat-x; | 105 | background: #FFF url(images/button-bg.png) 0 bottom repeat-x; |
106 | border-radius: 50px; | 106 | border-radius: 50px; |
107 | -moz-border-radius: 50px; | 107 | -moz-border-radius: 50px; |
108 | -webkit-border-radius: 50px; | 108 | -webkit-border-radius: 50px; |
109 | - border: 1px solid #bbb; | 109 | + border: 1px solid #bbb; |
110 | } | 110 | } |
111 | #nav-bar li a { | 111 | #nav-bar li a { |
112 | color: #666; | 112 | color: #666; |
113 | - display: block; | 113 | + display: block; |
114 | padding: 0 20px 0 40px; | 114 | padding: 0 20px 0 40px; |
115 | font-size: 14px; font-weight: bold; line-height: 39px; text-decoration: none; | 115 | font-size: 14px; font-weight: bold; line-height: 39px; text-decoration: none; |
116 | text-shadow: 1px 1px 0px #FFF; -webkit-text-shadow: 1px 1px 0px #FFF; | 116 | text-shadow: 1px 1px 0px #FFF; -webkit-text-shadow: 1px 1px 0px #FFF; |
@@ -120,17 +120,17 @@ a.action { float: right; font-size: 0.9em;} | @@ -120,17 +120,17 @@ a.action { float: right; font-size: 0.9em;} | ||
120 | #nav-bar li.apps a { background-image: url(images/icons/briefcase.png); } | 120 | #nav-bar li.apps a { background-image: url(images/icons/briefcase.png); } |
121 | #nav-bar li.errs a { background-image: url(images/icons/error.png); } | 121 | #nav-bar li.errs a { background-image: url(images/icons/error.png); } |
122 | #nav-bar li.users a { background-image: url(images/icons/user.png); } | 122 | #nav-bar li.users a { background-image: url(images/icons/user.png); } |
123 | -#nav-bar li:hover { | 123 | +#nav-bar li:hover { |
124 | box-shadow: 0 0 3px #69c; | 124 | box-shadow: 0 0 3px #69c; |
125 | -moz-box-shadow: 0 0 3px #69c; | 125 | -moz-box-shadow: 0 0 3px #69c; |
126 | -webkit-box-shadow: 0 0 3px #69c; | 126 | -webkit-box-shadow: 0 0 3px #69c; |
127 | } | 127 | } |
128 | -#nav-bar li.active { | ||
129 | - border-color: #fff; | 128 | +#nav-bar li.active { |
129 | + border-color: #fff; | ||
130 | background-color: #CCC; | 130 | background-color: #CCC; |
131 | background-image: none; | 131 | background-image: none; |
132 | - box-shadow: inset 0 0 5px #999; | ||
133 | - -moz-box-shadow: inset 0 0 5px #999; | 132 | + box-shadow: inset 0 0 5px #999; |
133 | + -moz-box-shadow: inset 0 0 5px #999; | ||
134 | -webkit-box-shadow: inset 0 0 5px #999; | 134 | -webkit-box-shadow: inset 0 0 5px #999; |
135 | } | 135 | } |
136 | 136 | ||
@@ -141,13 +141,13 @@ a.action { float: right; font-size: 0.9em;} | @@ -141,13 +141,13 @@ a.action { float: right; font-size: 0.9em;} | ||
141 | 141 | ||
142 | /* Content Title */ | 142 | /* Content Title */ |
143 | #content-title { | 143 | #content-title { |
144 | - padding: 30px 20px; | 144 | + padding: 30px 20px; |
145 | border-top: 1px solid #FFF; | 145 | border-top: 1px solid #FFF; |
146 | border-bottom: 1px solid #FFF; | 146 | border-bottom: 1px solid #FFF; |
147 | background-color: #e2e2e2; | 147 | background-color: #e2e2e2; |
148 | } | 148 | } |
149 | #content-title h1 { | 149 | #content-title h1 { |
150 | - padding: 0; margin: 0; | 150 | + padding: 0; margin: 0; |
151 | width: 85%; | 151 | width: 85%; |
152 | border: none; | 152 | border: none; |
153 | color: #666; | 153 | color: #666; |
@@ -161,7 +161,7 @@ a.action { float: right; font-size: 0.9em;} | @@ -161,7 +161,7 @@ a.action { float: right; font-size: 0.9em;} | ||
161 | position: absolute; | 161 | position: absolute; |
162 | top: 25px; right: 20px; | 162 | top: 25px; right: 20px; |
163 | } | 163 | } |
164 | -#action-bar span { | 164 | +#action-bar span { |
165 | display: inline-block; | 165 | display: inline-block; |
166 | margin-left: 18px; | 166 | margin-left: 18px; |
167 | text-decoration: none; | 167 | text-decoration: none; |
@@ -174,14 +174,14 @@ a.action { float: right; font-size: 0.9em;} | @@ -174,14 +174,14 @@ a.action { float: right; font-size: 0.9em;} | ||
174 | } | 174 | } |
175 | #action-bar span a { | 175 | #action-bar span a { |
176 | color: #666; | 176 | color: #666; |
177 | - display: block; | 177 | + display: block; |
178 | padding: 0 20px 0 40px; | 178 | padding: 0 20px 0 40px; |
179 | font-size: 14px; font-weight: bold; line-height: 39px; text-decoration: none; | 179 | font-size: 14px; font-weight: bold; line-height: 39px; text-decoration: none; |
180 | text-shadow: 1px 1px 0px #FFF; -webkit-text-shadow: 1px 1px 0px #FFF; | 180 | text-shadow: 1px 1px 0px #FFF; -webkit-text-shadow: 1px 1px 0px #FFF; |
181 | background: transparent 10px 8px no-repeat; | 181 | background: transparent 10px 8px no-repeat; |
182 | } | 182 | } |
183 | #action-bar a:hover { text-decoration: none;} | 183 | #action-bar a:hover { text-decoration: none;} |
184 | -#action-bar span:hover { | 184 | +#action-bar span:hover { |
185 | box-shadow: 0 0 3px #69c; | 185 | box-shadow: 0 0 3px #69c; |
186 | -moz-box-shadow: 0 0 3px #69c; | 186 | -moz-box-shadow: 0 0 3px #69c; |
187 | -webkit-box-shadow: 0 0 3px #69c; | 187 | -webkit-box-shadow: 0 0 3px #69c; |
@@ -194,14 +194,14 @@ a.action { float: right; font-size: 0.9em;} | @@ -194,14 +194,14 @@ a.action { float: right; font-size: 0.9em;} | ||
194 | #content { | 194 | #content { |
195 | padding: 20px; border-top: 1px solid #C6C6C6; | 195 | padding: 20px; border-top: 1px solid #C6C6C6; |
196 | background-color: #FFF; | 196 | background-color: #FFF; |
197 | -} | 197 | +} |
198 | 198 | ||
199 | -#content a.button { | 199 | +#content a.button { |
200 | float: right; | 200 | float: right; |
201 | display: block; | 201 | display: block; |
202 | margin-bottom: 10px; | 202 | margin-bottom: 10px; |
203 | } | 203 | } |
204 | - | 204 | + |
205 | /* Footer */ | 205 | /* Footer */ |
206 | #footer { | 206 | #footer { |
207 | padding: 20px 0; | 207 | padding: 20px 0; |
@@ -211,22 +211,22 @@ a.action { float: right; font-size: 0.9em;} | @@ -211,22 +211,22 @@ a.action { float: right; font-size: 0.9em;} | ||
211 | 211 | ||
212 | /* Flash Messages */ | 212 | /* Flash Messages */ |
213 | #flash-messages li { | 213 | #flash-messages li { |
214 | - padding: 13px 45px; | ||
215 | - margin-bottom:25px; | 214 | + padding: 13px 45px; |
215 | + margin-bottom:25px; | ||
216 | border: 1px solid #C6C6C6; | 216 | border: 1px solid #C6C6C6; |
217 | background-color: #F9F9F9; | 217 | background-color: #F9F9F9; |
218 | line-height: 1em; | 218 | line-height: 1em; |
219 | } | 219 | } |
220 | #flash-messages li.notice { | 220 | #flash-messages li.notice { |
221 | - padding-left: 20px; | 221 | + padding-left: 20px; |
222 | background-color: #b5eeff; | 222 | background-color: #b5eeff; |
223 | border: 1px solid #6cf; | 223 | border: 1px solid #6cf; |
224 | } | 224 | } |
225 | -#flash-messages li.success { | 225 | +#flash-messages li.success { |
226 | background: #cfc url(images/icons/success.png) 16px 50% no-repeat; | 226 | background: #cfc url(images/icons/success.png) 16px 50% no-repeat; |
227 | border: 1px solid #6c3; | 227 | border: 1px solid #6c3; |
228 | } | 228 | } |
229 | -#flash-messages li.error { | 229 | +#flash-messages li.error { |
230 | background: #fcc url(images/icons/error.png) 16px 50% no-repeat; | 230 | background: #fcc url(images/icons/error.png) 16px 50% no-repeat; |
231 | border: 1px solid #f99; | 231 | border: 1px solid #f99; |
232 | } | 232 | } |
@@ -244,13 +244,13 @@ form fieldset { | @@ -244,13 +244,13 @@ form fieldset { | ||
244 | padding: 0.8em; margin-bottom: 1em; | 244 | padding: 0.8em; margin-bottom: 1em; |
245 | background-color: #F0F0F0; border: 1px solid #C6C6C6; border-left: none; border-right: none; | 245 | background-color: #F0F0F0; border: 1px solid #C6C6C6; border-left: none; border-right: none; |
246 | } | 246 | } |
247 | -form fieldset legend { | ||
248 | - font-size: 1.2em; font-weight: bold; text-transform: uppercase; | 247 | +form fieldset legend { |
248 | + font-size: 1.2em; font-weight: bold; text-transform: uppercase; | ||
249 | color: #555; | 249 | color: #555; |
250 | } | 250 | } |
251 | form label { | 251 | form label { |
252 | font-weight: bold; text-transform: uppercase; line-height: 1.6em; | 252 | font-weight: bold; text-transform: uppercase; line-height: 1.6em; |
253 | - display: inline-block; | 253 | + display: inline-block; |
254 | } | 254 | } |
255 | form label.inline { display: inline; } | 255 | form label.inline { display: inline; } |
256 | form .checkbox label { display: inline; } | 256 | form .checkbox label { display: inline; } |
@@ -281,17 +281,17 @@ form input[type=submit] { | @@ -281,17 +281,17 @@ form input[type=submit] { | ||
281 | font-size: 1.2em; line-height: 1em; text-transform: uppercase; | 281 | font-size: 1.2em; line-height: 1em; text-transform: uppercase; |
282 | border: none; color: #FFF; background-color: #387fc1; | 282 | border: none; color: #FFF; background-color: #387fc1; |
283 | } | 283 | } |
284 | -form div.buttons { | 284 | +form div.buttons { |
285 | color: #666; | 285 | color: #666; |
286 | background: #FFF url(images/button-bg.png) 0 bottom repeat-x; | 286 | background: #FFF url(images/button-bg.png) 0 bottom repeat-x; |
287 | border-radius: 50px; | 287 | border-radius: 50px; |
288 | -moz-border-radius: 50px; | 288 | -moz-border-radius: 50px; |
289 | -webkit-border-radius: 50px; | 289 | -webkit-border-radius: 50px; |
290 | - border: 1px solid #bbb; | 290 | + border: 1px solid #bbb; |
291 | display: inline-block; | 291 | display: inline-block; |
292 | } | 292 | } |
293 | -form div.buttons:hover { | ||
294 | - color: #666; | 293 | +form div.buttons:hover { |
294 | + color: #666; | ||
295 | box-shadow: 0 0 3px #69c; | 295 | box-shadow: 0 0 3px #69c; |
296 | -moz-box-shadow: 0 0 3px #69c; | 296 | -moz-box-shadow: 0 0 3px #69c; |
297 | -webkit-box-shadow: 0 0 3px #69c; | 297 | -webkit-box-shadow: 0 0 3px #69c; |
@@ -351,10 +351,10 @@ form .error-messages ul { | @@ -351,10 +351,10 @@ form .error-messages ul { | ||
351 | } | 351 | } |
352 | 352 | ||
353 | /* Tables */ | 353 | /* Tables */ |
354 | -table { | ||
355 | - width: 100%; | 354 | +table { |
355 | + width: 100%; | ||
356 | border: 1px solid #C6C6C6; | 356 | border: 1px solid #C6C6C6; |
357 | - margin-bottom: 1.5em; | 357 | + margin-bottom: 1.5em; |
358 | border-collapse: separate; | 358 | border-collapse: separate; |
359 | } | 359 | } |
360 | table thead th { | 360 | table thead th { |
@@ -364,10 +364,10 @@ table thead th { | @@ -364,10 +364,10 @@ table thead th { | ||
364 | table tbody tr:first-child td { | 364 | table tbody tr:first-child td { |
365 | border-top: 1px solid #C6C6C6; | 365 | border-top: 1px solid #C6C6C6; |
366 | } | 366 | } |
367 | -table th, table td { | ||
368 | - border-top: 1px solid #C6C6C6; | ||
369 | - padding: 10px 8px; | ||
370 | - text-align: left; | 367 | +table th, table td { |
368 | + border-top: 1px solid #C6C6C6; | ||
369 | + padding: 10px 8px; | ||
370 | + text-align: left; | ||
371 | } | 371 | } |
372 | table th { background-color: #E2E2E2; font-weight: bold; text-transform: uppercase; white-space: nowrap; } | 372 | table th { background-color: #E2E2E2; font-weight: bold; text-transform: uppercase; white-space: nowrap; } |
373 | table tbody tr:nth-child(odd) td { background-color: #F9F9F9; } | 373 | table tbody tr:nth-child(odd) td { background-color: #F9F9F9; } |
@@ -442,8 +442,8 @@ pre { | @@ -442,8 +442,8 @@ pre { | ||
442 | background-color: #CCC; | 442 | background-color: #CCC; |
443 | background-image: none; | 443 | background-image: none; |
444 | border-color: #FFF; | 444 | border-color: #FFF; |
445 | - box-shadow: inset 0 0 5px #999; | ||
446 | - -moz-box-shadow: inset 0 0 5px #999; | 445 | + box-shadow: inset 0 0 5px #999; |
446 | + -moz-box-shadow: inset 0 0 5px #999; | ||
447 | -webkit-box-shadow: inset 0 0 5px #999; | 447 | -webkit-box-shadow: inset 0 0 5px #999; |
448 | font-style: normal; | 448 | font-style: normal; |
449 | } | 449 | } |
@@ -477,11 +477,11 @@ a:hover.button { | @@ -477,11 +477,11 @@ a:hover.button { | ||
477 | background-color: #eee; | 477 | background-color: #eee; |
478 | } | 478 | } |
479 | a.button.active { | 479 | a.button.active { |
480 | - border-color: #fff; | 480 | + border-color: #fff; |
481 | background-color: #CCC; | 481 | background-color: #CCC; |
482 | background-image: none; | 482 | background-image: none; |
483 | - box-shadow: inset 0 0 5px #999; | ||
484 | - -moz-box-shadow: inset 0 0 5px #999; | 483 | + box-shadow: inset 0 0 5px #999; |
484 | + -moz-box-shadow: inset 0 0 5px #999; | ||
485 | -webkit-box-shadow: inset 0 0 5px #999; | 485 | -webkit-box-shadow: inset 0 0 5px #999; |
486 | } | 486 | } |
487 | 487 | ||
@@ -502,10 +502,10 @@ a.button.active { | @@ -502,10 +502,10 @@ a.button.active { | ||
502 | } | 502 | } |
503 | 503 | ||
504 | /* Watchers and Issue Tracker Forms */ | 504 | /* Watchers and Issue Tracker Forms */ |
505 | -div.nested.watcher .user, div.nested.watcher .email, div.issue_tracker.nested .lighthouseapp, div.issue_tracker.nested .redmine { | 505 | +div.nested.watcher .user, div.nested.watcher .email, div.issue_tracker.nested .lighthouseapp, div.issue_tracker.nested .redmine, div.issue_tracker.nested .pivotal { |
506 | display: none; | 506 | display: none; |
507 | } | 507 | } |
508 | -div.nested.watcher .choosen, div.nested.issue_tracker .choosen { | 508 | +div.nested.watcher .choosen, div.nested.issue_tracker .chosen { |
509 | display: block; | 509 | display: block; |
510 | } | 510 | } |
511 | 511 | ||
@@ -559,7 +559,7 @@ table.errs td.app .environment { | @@ -559,7 +559,7 @@ table.errs td.app .environment { | ||
559 | font-size: 0.8em; | 559 | font-size: 0.8em; |
560 | color: #999; | 560 | color: #999; |
561 | } | 561 | } |
562 | -table.errs td.message a { | 562 | +table.errs td.message a { |
563 | width: 420px; | 563 | width: 420px; |
564 | display: block; | 564 | display: block; |
565 | word-wrap: break-word; | 565 | word-wrap: break-word; |
@@ -608,6 +608,10 @@ table.tally th.value { | @@ -608,6 +608,10 @@ table.tally th.value { | ||
608 | background: transparent url(/images/redmine_create.png) 6px 5px no-repeat; | 608 | background: transparent url(/images/redmine_create.png) 6px 5px no-repeat; |
609 | } | 609 | } |
610 | 610 | ||
611 | +#action-bar a.pivotal_create { | ||
612 | + background: transparent url(/images/pivotal_create.png) 6px 5px no-repeat; | ||
613 | +} | ||
614 | + | ||
611 | #action-bar a.lighthouseapp_goto { | 615 | #action-bar a.lighthouseapp_goto { |
612 | background: transparent url(/images/lighthouseapp_goto.png) 6px 5px no-repeat; | 616 | background: transparent url(/images/lighthouseapp_goto.png) 6px 5px no-repeat; |
613 | } | 617 | } |
@@ -616,6 +620,10 @@ table.tally th.value { | @@ -616,6 +620,10 @@ table.tally th.value { | ||
616 | background: transparent url(/images/redmine_goto.png) 6px 5px no-repeat; | 620 | background: transparent url(/images/redmine_goto.png) 6px 5px no-repeat; |
617 | } | 621 | } |
618 | 622 | ||
623 | +#action-bar a.pivotal_goto { | ||
624 | + background: transparent url(/images/pivotal_goto.png) 6px 5px no-repeat; | ||
625 | +} | ||
626 | + | ||
619 | /* Notices Pagination */ | 627 | /* Notices Pagination */ |
620 | .notice-pagination { | 628 | .notice-pagination { |
621 | float: left; | 629 | float: left; |
spec/controllers/apps_controller_spec.rb
@@ -5,7 +5,7 @@ describe AppsController do | @@ -5,7 +5,7 @@ describe AppsController do | ||
5 | 5 | ||
6 | it_requires_authentication | 6 | it_requires_authentication |
7 | it_requires_admin_privileges :for => {:new => :get, :edit => :get, :create => :post, :update => :put, :destroy => :delete} | 7 | it_requires_admin_privileges :for => {:new => :get, :edit => :get, :create => :post, :update => :put, :destroy => :delete} |
8 | - | 8 | + |
9 | describe "GET /apps" do | 9 | describe "GET /apps" do |
10 | context 'when logged in as an admin' do | 10 | context 'when logged in as an admin' do |
11 | it 'finds all apps' do | 11 | it 'finds all apps' do |
@@ -16,7 +16,7 @@ describe AppsController do | @@ -16,7 +16,7 @@ describe AppsController do | ||
16 | assigns(:apps).should == apps | 16 | assigns(:apps).should == apps |
17 | end | 17 | end |
18 | end | 18 | end |
19 | - | 19 | + |
20 | context 'when logged in as a regular user' do | 20 | context 'when logged in as a regular user' do |
21 | it 'finds apps the user is watching' do | 21 | it 'finds apps the user is watching' do |
22 | sign_in(user = Factory(:user)) | 22 | sign_in(user = Factory(:user)) |
@@ -31,7 +31,7 @@ describe AppsController do | @@ -31,7 +31,7 @@ describe AppsController do | ||
31 | end | 31 | end |
32 | end | 32 | end |
33 | end | 33 | end |
34 | - | 34 | + |
35 | describe "GET /apps/:id" do | 35 | describe "GET /apps/:id" do |
36 | context 'logged in as an admin' do | 36 | context 'logged in as an admin' do |
37 | before(:each) do | 37 | before(:each) do |
@@ -75,27 +75,27 @@ describe AppsController do | @@ -75,27 +75,27 @@ describe AppsController do | ||
75 | end | 75 | end |
76 | end | 76 | end |
77 | end | 77 | end |
78 | - | 78 | + |
79 | context 'logged in as a user' do | 79 | context 'logged in as a user' do |
80 | it 'finds the app if the user is watching it' do | 80 | it 'finds the app if the user is watching it' do |
81 | pending | 81 | pending |
82 | end | 82 | end |
83 | - | 83 | + |
84 | it 'does not find the app if the user is not watching it' do | 84 | it 'does not find the app if the user is not watching it' do |
85 | sign_in Factory(:user) | 85 | sign_in Factory(:user) |
86 | app = Factory(:app) | 86 | app = Factory(:app) |
87 | - lambda { | 87 | + lambda { |
88 | get :show, :id => app.id | 88 | get :show, :id => app.id |
89 | }.should raise_error(Mongoid::Errors::DocumentNotFound) | 89 | }.should raise_error(Mongoid::Errors::DocumentNotFound) |
90 | end | 90 | end |
91 | end | 91 | end |
92 | end | 92 | end |
93 | - | 93 | + |
94 | context 'logged in as an admin' do | 94 | context 'logged in as an admin' do |
95 | before do | 95 | before do |
96 | sign_in Factory(:admin) | 96 | sign_in Factory(:admin) |
97 | end | 97 | end |
98 | - | 98 | + |
99 | describe "GET /apps/new" do | 99 | describe "GET /apps/new" do |
100 | it 'instantiates a new app with a prebuilt watcher' do | 100 | it 'instantiates a new app with a prebuilt watcher' do |
101 | get :new | 101 | get :new |
@@ -104,7 +104,7 @@ describe AppsController do | @@ -104,7 +104,7 @@ describe AppsController do | ||
104 | assigns(:app).watchers.should_not be_empty | 104 | assigns(:app).watchers.should_not be_empty |
105 | end | 105 | end |
106 | end | 106 | end |
107 | - | 107 | + |
108 | describe "GET /apps/:id/edit" do | 108 | describe "GET /apps/:id/edit" do |
109 | it 'finds the correct app' do | 109 | it 'finds the correct app' do |
110 | app = Factory(:app) | 110 | app = Factory(:app) |
@@ -112,29 +112,29 @@ describe AppsController do | @@ -112,29 +112,29 @@ describe AppsController do | ||
112 | assigns(:app).should == app | 112 | assigns(:app).should == app |
113 | end | 113 | end |
114 | end | 114 | end |
115 | - | 115 | + |
116 | describe "POST /apps" do | 116 | describe "POST /apps" do |
117 | before do | 117 | before do |
118 | @app = Factory(:app) | 118 | @app = Factory(:app) |
119 | App.stub(:new).and_return(@app) | 119 | App.stub(:new).and_return(@app) |
120 | end | 120 | end |
121 | - | 121 | + |
122 | context "when the create is successful" do | 122 | context "when the create is successful" do |
123 | before do | 123 | before do |
124 | @app.should_receive(:save).and_return(true) | 124 | @app.should_receive(:save).and_return(true) |
125 | end | 125 | end |
126 | - | 126 | + |
127 | it "should redirect to the app page" do | 127 | it "should redirect to the app page" do |
128 | post :create, :app => {} | 128 | post :create, :app => {} |
129 | response.should redirect_to(app_path(@app)) | 129 | response.should redirect_to(app_path(@app)) |
130 | end | 130 | end |
131 | - | 131 | + |
132 | it "should display a message" do | 132 | it "should display a message" do |
133 | post :create, :app => {} | 133 | post :create, :app => {} |
134 | request.flash[:success].should match(/success/) | 134 | request.flash[:success].should match(/success/) |
135 | end | 135 | end |
136 | end | 136 | end |
137 | - | 137 | + |
138 | context "when the create is unsuccessful" do | 138 | context "when the create is unsuccessful" do |
139 | it "should render the new page" do | 139 | it "should render the new page" do |
140 | @app.should_receive(:save).and_return(false) | 140 | @app.should_receive(:save).and_return(false) |
@@ -143,18 +143,18 @@ describe AppsController do | @@ -143,18 +143,18 @@ describe AppsController do | ||
143 | end | 143 | end |
144 | end | 144 | end |
145 | end | 145 | end |
146 | - | 146 | + |
147 | describe "PUT /apps/:id" do | 147 | describe "PUT /apps/:id" do |
148 | before do | 148 | before do |
149 | @app = Factory(:app) | 149 | @app = Factory(:app) |
150 | end | 150 | end |
151 | - | 151 | + |
152 | context "when the update is successful" do | 152 | context "when the update is successful" do |
153 | it "should redirect to the app page" do | 153 | it "should redirect to the app page" do |
154 | put :update, :id => @app.id, :app => {} | 154 | put :update, :id => @app.id, :app => {} |
155 | response.should redirect_to(app_path(@app)) | 155 | response.should redirect_to(app_path(@app)) |
156 | end | 156 | end |
157 | - | 157 | + |
158 | it "should display a message" do | 158 | it "should display a message" do |
159 | put :update, :id => @app.id, :app => {} | 159 | put :update, :id => @app.id, :app => {} |
160 | request.flash[:success].should match(/success/) | 160 | request.flash[:success].should match(/success/) |
@@ -168,7 +168,7 @@ describe AppsController do | @@ -168,7 +168,7 @@ describe AppsController do | ||
168 | response.should redirect_to(app_path(id)) | 168 | response.should redirect_to(app_path(id)) |
169 | end | 169 | end |
170 | end | 170 | end |
171 | - | 171 | + |
172 | context "when the update is unsuccessful" do | 172 | context "when the update is unsuccessful" do |
173 | it "should render the edit page" do | 173 | it "should render the edit page" do |
174 | put :update, :id => @app.id, :app => { :name => '' } | 174 | put :update, :id => @app.id, :app => { :name => '' } |
@@ -179,7 +179,7 @@ describe AppsController do | @@ -179,7 +179,7 @@ describe AppsController do | ||
179 | context "setting up issue tracker", :cur => true do | 179 | context "setting up issue tracker", :cur => true do |
180 | context "unknown tracker type" do | 180 | context "unknown tracker type" do |
181 | before(:each) do | 181 | before(:each) do |
182 | - put :update, :id => @app.id, :app => { :issue_tracker_attributes => { | 182 | + put :update, :id => @app.id, :app => { :issue_tracker_attributes => { |
183 | :issue_tracker_type => 'unknown', :project_id => '1234', :api_token => '123123', :account => 'myapp' | 183 | :issue_tracker_type => 'unknown', :project_id => '1234', :api_token => '123123', :account => 'myapp' |
184 | } } | 184 | } } |
185 | @app.reload | 185 | @app.reload |
@@ -211,7 +211,7 @@ describe AppsController do | @@ -211,7 +211,7 @@ describe AppsController do | ||
211 | @app.reload | 211 | @app.reload |
212 | 212 | ||
213 | @app.issue_tracker.should be_nil | 213 | @app.issue_tracker.should be_nil |
214 | - response.body.should match(/You must specify your Lighthouseapp account, api token and project id/) | 214 | + response.body.should match(/You must specify your Lighthouseapp account, api token and project id/) |
215 | end | 215 | end |
216 | end | 216 | end |
217 | 217 | ||
@@ -236,38 +236,60 @@ describe AppsController do | @@ -236,38 +236,60 @@ describe AppsController do | ||
236 | @app.reload | 236 | @app.reload |
237 | 237 | ||
238 | @app.issue_tracker.should be_nil | 238 | @app.issue_tracker.should be_nil |
239 | - response.body.should match(/You must specify your Redmine url, api token and project id/) | 239 | + response.body.should match(/You must specify your Redmine url, api token and project id/) |
240 | + end | ||
241 | + end | ||
242 | + | ||
243 | + context "pivotal" do | ||
244 | + it "should save tracker params" do | ||
245 | + put :update, :id => @app.id, :app => { :issue_tracker_attributes => { | ||
246 | + :issue_tracker_type => 'pivotal', :project_id => '1234', :api_token => '123123' } } | ||
247 | + @app.reload | ||
248 | + | ||
249 | + tracker = @app.issue_tracker | ||
250 | + tracker.issue_tracker_type.should == 'pivotal' | ||
251 | + tracker.project_id.should == '1234' | ||
252 | + tracker.api_token.should == '123123' | ||
253 | + end | ||
254 | + | ||
255 | + it "should show validation notice when sufficient params are not present" do | ||
256 | + put :update, :id => @app.id, :app => { :issue_tracker_attributes => { | ||
257 | + :issue_tracker_type => 'pivotal', :project_id => '1234' } } | ||
258 | + @app.reload | ||
259 | + | ||
260 | + @app.issue_tracker.should be_nil | ||
261 | + response.body.should match(/You must specify your Pivotal Tracker api token and project id/) | ||
240 | end | 262 | end |
241 | end | 263 | end |
242 | end | 264 | end |
243 | end | 265 | end |
244 | - | 266 | + |
245 | describe "DELETE /apps/:id" do | 267 | describe "DELETE /apps/:id" do |
246 | before do | 268 | before do |
247 | @app = Factory(:app) | 269 | @app = Factory(:app) |
248 | App.stub(:find).with(@app.id).and_return(@app) | 270 | App.stub(:find).with(@app.id).and_return(@app) |
249 | end | 271 | end |
250 | - | 272 | + |
251 | it "should find the app" do | 273 | it "should find the app" do |
252 | delete :destroy, :id => @app.id | 274 | delete :destroy, :id => @app.id |
253 | assigns(:app).should == @app | 275 | assigns(:app).should == @app |
254 | end | 276 | end |
255 | - | 277 | + |
256 | it "should destroy the app" do | 278 | it "should destroy the app" do |
257 | @app.should_receive(:destroy) | 279 | @app.should_receive(:destroy) |
258 | delete :destroy, :id => @app.id | 280 | delete :destroy, :id => @app.id |
259 | end | 281 | end |
260 | - | 282 | + |
261 | it "should display a message" do | 283 | it "should display a message" do |
262 | delete :destroy, :id => @app.id | 284 | delete :destroy, :id => @app.id |
263 | request.flash[:success].should match(/success/) | 285 | request.flash[:success].should match(/success/) |
264 | end | 286 | end |
265 | - | 287 | + |
266 | it "should redirect to the apps page" do | 288 | it "should redirect to the apps page" do |
267 | delete :destroy, :id => @app.id | 289 | delete :destroy, :id => @app.id |
268 | response.should redirect_to(apps_path) | 290 | response.should redirect_to(apps_path) |
269 | end | 291 | end |
270 | end | 292 | end |
271 | end | 293 | end |
272 | - | 294 | + |
273 | end | 295 | end |
spec/controllers/errs_controller_spec.rb
@@ -285,6 +285,39 @@ describe ErrsController do | @@ -285,6 +285,39 @@ describe ErrsController do | ||
285 | err.issue_link.should == @issue_link.sub(/\.xml/, '') | 285 | err.issue_link.should == @issue_link.sub(/\.xml/, '') |
286 | end | 286 | end |
287 | end | 287 | end |
288 | + | ||
289 | + context "redmine tracker" do | ||
290 | + let(:notice) { Factory :notice } | ||
291 | + let(:tracker) { Factory :pivotal_tracker, :app => notice.err.app } | ||
292 | + let(:err) { notice.err } | ||
293 | + | ||
294 | + before(:each) do | ||
295 | + pending | ||
296 | + number = 5 | ||
297 | + @issue_link = "#{tracker.account}/issues/#{number}.xml?project_id=#{tracker.project_id}" | ||
298 | + body = "<issue><subject>my subject</subject><id>#{number}</id></issue>" | ||
299 | + stub_request(:post, "#{tracker.account}/issues.xml").to_return(:status => 201, :headers => {'Location' => @issue_link}, :body => body ) | ||
300 | + | ||
301 | + post :create_issue, :app_id => err.app.id, :id => err.id | ||
302 | + err.reload | ||
303 | + end | ||
304 | + | ||
305 | + it "should make request to Pivotal Tracker with err params" do | ||
306 | + requested = have_requested(:post, "#{tracker.account}/issues.xml") | ||
307 | + WebMock.should requested.with(:headers => {'X-Redmine-API-Key' => tracker.api_token}) | ||
308 | + WebMock.should requested.with(:body => /<project-id>#{tracker.project_id}<\/project-id>/) | ||
309 | + WebMock.should requested.with(:body => /<subject>\[#{ err.environment }\]\[#{err.where}\] #{err.message.to_s.truncate(100)}<\/subject>/) | ||
310 | + WebMock.should requested.with(:body => /<description>.+<\/description>/m) | ||
311 | + end | ||
312 | + | ||
313 | + it "should redirect to err page" do | ||
314 | + response.should redirect_to( app_err_path(err.app, err) ) | ||
315 | + end | ||
316 | + | ||
317 | + it "should create issue link for err" do | ||
318 | + err.issue_link.should == @issue_link.sub(/\.xml/, '') | ||
319 | + end | ||
320 | + end | ||
288 | end | 321 | end |
289 | 322 | ||
290 | context "absent issue tracker" do | 323 | context "absent issue tracker" do |
spec/factories/issue_tracker_factories.rb
1 | -Factory.define :lighthouseapp_tracker, :class => IssueTracker do |e| | ||
2 | - e.issue_tracker_type 'lighthouseapp' | ||
3 | - e.account { Factory.next :word } | 1 | +Factory.define :generic_tracker, :class => IssueTracker do |e| |
4 | e.api_token { Factory.next :word } | 2 | e.api_token { Factory.next :word } |
5 | e.project_id { Factory.next :word } | 3 | e.project_id { Factory.next :word } |
6 | e.association :app, :factory => :app | 4 | e.association :app, :factory => :app |
7 | end | 5 | end |
8 | 6 | ||
9 | -Factory.define :redmine_tracker, :parent => :lighthouseapp_tracker do |e| | 7 | +Factory.define :lighthouseapp_tracker, :parent => :generic_tracker do |e| |
8 | + e.issue_tracker_type 'lighthouseapp' | ||
9 | + e.account { Factory.next :word } | ||
10 | +end | ||
11 | + | ||
12 | +Factory.define :redmine_tracker, :parent => :generic_tracker do |e| | ||
10 | e.issue_tracker_type 'redmine' | 13 | e.issue_tracker_type 'redmine' |
11 | e.account { "http://#{Factory.next(:word)}.com" } | 14 | e.account { "http://#{Factory.next(:word)}.com" } |
12 | -end | ||
13 | \ No newline at end of file | 15 | \ No newline at end of file |
16 | +end | ||
17 | + | ||
18 | +Factory.define :pivotal_tracker, :parent => :generic_tracker do |e| | ||
19 | + e.issue_tracker_type 'pivotal' | ||
20 | +end |