Commit a02dacae093789471b601c378f6015690a527fc5
1 parent
6858ff32
Exists in
master
and in
1 other branch
Add support for Pivotal Tracker issue creation
Showing
13 changed files
with
280 additions
and
142 deletions
Show diff stats
Gemfile
@@ -8,6 +8,7 @@ gem 'will_paginate' | @@ -8,6 +8,7 @@ gem 'will_paginate' | ||
8 | gem 'devise', '~> 1.1.8' | 8 | gem 'devise', '~> 1.1.8' |
9 | gem 'lighthouse-api' | 9 | 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 'pivotal-tracker' | ||
11 | 12 | ||
12 | platform :ruby do | 13 | platform :ruby do |
13 | gem 'bson_ext', '~> 1.2' | 14 | 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) |
@@ -72,6 +75,11 @@ GEM | @@ -72,6 +75,11 @@ GEM | ||
72 | tzinfo (~> 0.3.22) | 75 | tzinfo (~> 0.3.22) |
73 | will_paginate (~> 3.0.pre) | 76 | will_paginate (~> 3.0.pre) |
74 | nokogiri (1.4.4) | 77 | nokogiri (1.4.4) |
78 | + pivotal-tracker (0.2.0) | ||
79 | + builder | ||
80 | + happymapper (>= 0.2.4) | ||
81 | + nokogiri (~> 1.4.1) | ||
82 | + rest-client (~> 1.5.1) | ||
75 | polyglot (0.3.1) | 83 | polyglot (0.3.1) |
76 | rack (1.2.2) | 84 | rack (1.2.2) |
77 | rack-mount (0.6.13) | 85 | rack-mount (0.6.13) |
@@ -92,6 +100,8 @@ GEM | @@ -92,6 +100,8 @@ GEM | ||
92 | rake (>= 0.8.7) | 100 | rake (>= 0.8.7) |
93 | thor (~> 0.14.4) | 101 | thor (~> 0.14.4) |
94 | rake (0.8.7) | 102 | rake (0.8.7) |
103 | + rest-client (1.5.1) | ||
104 | + mime-types (>= 1.16) | ||
95 | rspec (2.5.0) | 105 | rspec (2.5.0) |
96 | rspec-core (~> 2.5.0) | 106 | rspec-core (~> 2.5.0) |
97 | rspec-expectations (~> 2.5.0) | 107 | rspec-expectations (~> 2.5.0) |
@@ -128,6 +138,7 @@ DEPENDENCIES | @@ -128,6 +138,7 @@ DEPENDENCIES | ||
128 | lighthouse-api | 138 | lighthouse-api |
129 | mongoid (~> 2.0.0.rc.7) | 139 | mongoid (~> 2.0.0.rc.7) |
130 | nokogiri | 140 | nokogiri |
141 | + pivotal-tracker | ||
131 | rails (= 3.0.5) | 142 | rails (= 3.0.5) |
132 | redmine_client! | 143 | redmine_client! |
133 | rspec (~> 2.5) | 144 | rspec (~> 2.5) |
README.md
@@ -92,17 +92,23 @@ for you. Checkout [Hoptoad](http://hoptoadapp.com) from the guys over at | @@ -92,17 +92,23 @@ for you. Checkout [Hoptoad](http://hoptoadapp.com) from the guys over at | ||
92 | Lighthouseapp integration | 92 | Lighthouseapp integration |
93 | ------------------------- | 93 | ------------------------- |
94 | 94 | ||
95 | -* Account is the name of your subdomain, i.e. **litcafe** for project at http://litcafe.lighthouseapp.com/projects/73466-face/overview | 95 | +* Account is the name of your subdomain, i.e. **litcafe** for project at http://litcafe.lighthouseapp.com/projects/73466-face/overview |
96 | * Errbit uses token-based authentication. Get your API Token or visit [http://help.lighthouseapp.com/kb/api/how-do-i-get-an-api-token](http://help.lighthouseapp.com/kb/api/how-do-i-get-an-api-token) to learn how to get it. | 96 | * Errbit uses token-based authentication. Get your API Token or visit [http://help.lighthouseapp.com/kb/api/how-do-i-get-an-api-token](http://help.lighthouseapp.com/kb/api/how-do-i-get-an-api-token) to learn how to get it. |
97 | * Project id is number identifier of your project, i.e. **73466** for project at http://litcafe.lighthouseapp.com/projects/73466-face/overview | 97 | * Project id is number identifier of your project, i.e. **73466** for project at http://litcafe.lighthouseapp.com/projects/73466-face/overview |
98 | 98 | ||
99 | Redmine integration | 99 | Redmine integration |
100 | ------------------------- | 100 | ------------------------- |
101 | 101 | ||
102 | -* Account is the host of your redmine installation, i.e. **http://redmine.org** | 102 | +* Account is the host of your redmine installation, i.e. **http://redmine.org** |
103 | * 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. | 103 | * 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. |
104 | * Project id is an identifier of your project, i.e. **chilliproject** for project at http://www.redmine.org/projects/chilliproject | 104 | * Project id is an identifier of your project, i.e. **chilliproject** for project at http://www.redmine.org/projects/chilliproject |
105 | 105 | ||
106 | +Pivotal Tracker integration | ||
107 | +------------------------- | ||
108 | + | ||
109 | +* 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. | ||
110 | +* Project id is an identifier of your project, i.e. **24324** for project at http://www.pivotaltracker.com/projects/24324 | ||
111 | + | ||
106 | TODO | 112 | TODO |
107 | ---- | 113 | ---- |
108 | 114 |
app/helpers/application_helper.rb
@@ -2,4 +2,12 @@ module ApplicationHelper | @@ -2,4 +2,12 @@ module ApplicationHelper | ||
2 | def lighthouse_tracker? object | 2 | def lighthouse_tracker? object |
3 | object.issue_tracker_type == "lighthouseapp" | 3 | object.issue_tracker_type == "lighthouseapp" |
4 | end | 4 | end |
5 | + | ||
6 | + def redmine_tracker? object | ||
7 | + object.issue_tracker_type == "redmine" | ||
8 | + end | ||
9 | + | ||
10 | + def pivotal_tracker? object | ||
11 | + object.issue_tracker_type == "pivotal" | ||
12 | + end | ||
5 | end | 13 | end |
app/models/app.rb
1 | class App | 1 | class App |
2 | include Mongoid::Document | 2 | include Mongoid::Document |
3 | include Mongoid::Timestamps | 3 | include Mongoid::Timestamps |
4 | - | 4 | + |
5 | field :name, :type => String | 5 | field :name, :type => String |
6 | field :api_key | 6 | field :api_key |
7 | field :resolve_errs_on_deploy, :type => Boolean, :default => false | 7 | field :resolve_errs_on_deploy, :type => Boolean, :default => false |
@@ -21,29 +21,29 @@ class App | @@ -21,29 +21,29 @@ class App | ||
21 | embeds_many :deploys | 21 | embeds_many :deploys |
22 | embeds_one :issue_tracker | 22 | embeds_one :issue_tracker |
23 | references_many :errs, :dependent => :destroy | 23 | references_many :errs, :dependent => :destroy |
24 | - | 24 | + |
25 | before_validation :generate_api_key, :on => :create | 25 | before_validation :generate_api_key, :on => :create |
26 | - | 26 | + |
27 | validates_presence_of :name, :api_key | 27 | validates_presence_of :name, :api_key |
28 | validates_uniqueness_of :name, :allow_blank => true | 28 | validates_uniqueness_of :name, :allow_blank => true |
29 | validates_uniqueness_of :api_key, :allow_blank => true | 29 | validates_uniqueness_of :api_key, :allow_blank => true |
30 | validates_associated :watchers | 30 | validates_associated :watchers |
31 | validate :check_issue_tracker | 31 | validate :check_issue_tracker |
32 | - | 32 | + |
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]) } | ||
37 | - | 36 | + :reject_if => proc { |attrs| !%w(lighthouseapp redmine pivotal).include?(attrs[:issue_tracker_type]) } |
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) |
40 | where(:_id => app_id).first || raise(Mongoid::Errors::DocumentNotFound.new(self,app_id)) | 40 | where(:_id => app_id).first || raise(Mongoid::Errors::DocumentNotFound.new(self,app_id)) |
41 | end | 41 | end |
42 | - | 42 | + |
43 | def self.find_by_api_key!(key) | 43 | def self.find_by_api_key!(key) |
44 | where(:api_key => key).first || raise(Mongoid::Errors::DocumentNotFound.new(self,key)) | 44 | where(:api_key => key).first || raise(Mongoid::Errors::DocumentNotFound.new(self,key)) |
45 | end | 45 | end |
46 | - | 46 | + |
47 | def last_deploy_at | 47 | def last_deploy_at |
48 | deploys.last && deploys.last.created_at | 48 | deploys.last && deploys.last.created_at |
49 | end | 49 | end |
@@ -58,9 +58,9 @@ class App | @@ -58,9 +58,9 @@ class App | ||
58 | !(self[:notify_on_deploys] == false) | 58 | !(self[:notify_on_deploys] == false) |
59 | end | 59 | end |
60 | alias :notify_on_deploys? :notify_on_deploys | 60 | alias :notify_on_deploys? :notify_on_deploys |
61 | - | 61 | + |
62 | protected | 62 | protected |
63 | - | 63 | + |
64 | def generate_api_key | 64 | def generate_api_key |
65 | self.api_key ||= ActiveSupport::SecureRandom.hex | 65 | self.api_key ||= ActiveSupport::SecureRandom.hex |
66 | end | 66 | end |
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; |
@@ -587,6 +587,10 @@ table.errs tr.resolved td > * { | @@ -587,6 +587,10 @@ table.errs tr.resolved td > * { | ||
587 | background: transparent url(/images/redmine_create.png) 6px 5px no-repeat; | 587 | background: transparent url(/images/redmine_create.png) 6px 5px no-repeat; |
588 | } | 588 | } |
589 | 589 | ||
590 | +#action-bar a.pivotal_create { | ||
591 | + background: transparent url(/images/pivotal_create.png) 6px 5px no-repeat; | ||
592 | +} | ||
593 | + | ||
590 | #action-bar a.lighthouseapp_goto { | 594 | #action-bar a.lighthouseapp_goto { |
591 | background: transparent url(/images/lighthouseapp_goto.png) 6px 5px no-repeat; | 595 | background: transparent url(/images/lighthouseapp_goto.png) 6px 5px no-repeat; |
592 | } | 596 | } |
@@ -595,6 +599,10 @@ table.errs tr.resolved td > * { | @@ -595,6 +599,10 @@ table.errs tr.resolved td > * { | ||
595 | background: transparent url(/images/redmine_goto.png) 6px 5px no-repeat; | 599 | background: transparent url(/images/redmine_goto.png) 6px 5px no-repeat; |
596 | } | 600 | } |
597 | 601 | ||
602 | +#action-bar a.pivotal_goto { | ||
603 | + background: transparent url(/images/pivotal_goto.png) 6px 5px no-repeat; | ||
604 | +} | ||
605 | + | ||
598 | /* Notices Pagination */ | 606 | /* Notices Pagination */ |
599 | .notice-pagination { | 607 | .notice-pagination { |
600 | float: left; | 608 | 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', :api_token => '123123' } } | ||
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
1 | require 'spec_helper' | 1 | require 'spec_helper' |
2 | 2 | ||
3 | describe ErrsController do | 3 | describe ErrsController do |
4 | - | 4 | + |
5 | it_requires_authentication :for => { | 5 | it_requires_authentication :for => { |
6 | :index => :get, :all => :get, :show => :get, :resolve => :put | 6 | :index => :get, :all => :get, :show => :get, :resolve => :put |
7 | }, | 7 | }, |
8 | :params => {:app_id => 'dummyid', :id => 'dummyid'} | 8 | :params => {:app_id => 'dummyid', :id => 'dummyid'} |
9 | - | 9 | + |
10 | let(:app) { Factory(:app) } | 10 | let(:app) { Factory(:app) } |
11 | let(:err) { Factory(:err, :app => app) } | 11 | let(:err) { Factory(:err, :app => app) } |
12 | - | 12 | + |
13 | describe "GET /errs" do | 13 | describe "GET /errs" do |
14 | render_views | 14 | render_views |
15 | context 'when logged in as an admin' do | 15 | context 'when logged in as an admin' do |
@@ -31,7 +31,7 @@ describe ErrsController do | @@ -31,7 +31,7 @@ describe ErrsController do | ||
31 | response.should be_success | 31 | response.should be_success |
32 | response.body.should match(@err.message) | 32 | response.body.should match(@err.message) |
33 | end | 33 | end |
34 | - | 34 | + |
35 | it "should handle lots of errors" do | 35 | it "should handle lots of errors" do |
36 | pending "Turning off long running spec" | 36 | pending "Turning off long running spec" |
37 | 1000.times { Factory :notice } | 37 | 1000.times { Factory :notice } |
@@ -55,7 +55,7 @@ describe ErrsController do | @@ -55,7 +55,7 @@ describe ErrsController do | ||
55 | end | 55 | end |
56 | end | 56 | end |
57 | end | 57 | end |
58 | - | 58 | + |
59 | context 'when logged in as a user' do | 59 | context 'when logged in as a user' do |
60 | it 'gets a paginated list of unresolved errs for the users apps' do | 60 | it 'gets a paginated list of unresolved errs for the users apps' do |
61 | sign_in(user = Factory(:user)) | 61 | sign_in(user = Factory(:user)) |
@@ -68,7 +68,7 @@ describe ErrsController do | @@ -68,7 +68,7 @@ describe ErrsController do | ||
68 | end | 68 | end |
69 | end | 69 | end |
70 | end | 70 | end |
71 | - | 71 | + |
72 | describe "GET /errs/all" do | 72 | describe "GET /errs/all" do |
73 | context 'when logged in as an admin' do | 73 | context 'when logged in as an admin' do |
74 | it "gets a paginated list of all errs" do | 74 | it "gets a paginated list of all errs" do |
@@ -83,7 +83,7 @@ describe ErrsController do | @@ -83,7 +83,7 @@ describe ErrsController do | ||
83 | assigns(:errs).should == errs | 83 | assigns(:errs).should == errs |
84 | end | 84 | end |
85 | end | 85 | end |
86 | - | 86 | + |
87 | context 'when logged in as a user' do | 87 | context 'when logged in as a user' do |
88 | it 'gets a paginated list of all errs for the users apps' do | 88 | it 'gets a paginated list of all errs for the users apps' do |
89 | sign_in(user = Factory(:user)) | 89 | sign_in(user = Factory(:user)) |
@@ -96,29 +96,29 @@ describe ErrsController do | @@ -96,29 +96,29 @@ describe ErrsController do | ||
96 | end | 96 | end |
97 | end | 97 | end |
98 | end | 98 | end |
99 | - | 99 | + |
100 | describe "GET /apps/:app_id/errs/:id" do | 100 | describe "GET /apps/:app_id/errs/:id" do |
101 | render_views | 101 | render_views |
102 | - | 102 | + |
103 | before do | 103 | before do |
104 | 3.times { Factory(:notice, :err => err)} | 104 | 3.times { Factory(:notice, :err => err)} |
105 | end | 105 | end |
106 | - | 106 | + |
107 | context 'when logged in as an admin' do | 107 | context 'when logged in as an admin' do |
108 | before do | 108 | before do |
109 | sign_in Factory(:admin) | 109 | sign_in Factory(:admin) |
110 | end | 110 | end |
111 | - | 111 | + |
112 | it "finds the app" do | 112 | it "finds the app" do |
113 | get :show, :app_id => app.id, :id => err.id | 113 | get :show, :app_id => app.id, :id => err.id |
114 | assigns(:app).should == app | 114 | assigns(:app).should == app |
115 | end | 115 | end |
116 | - | 116 | + |
117 | it "finds the err" do | 117 | it "finds the err" do |
118 | get :show, :app_id => app.id, :id => err.id | 118 | get :show, :app_id => app.id, :id => err.id |
119 | assigns(:err).should == err | 119 | assigns(:err).should == err |
120 | end | 120 | end |
121 | - | 121 | + |
122 | it "successfully render page" do | 122 | it "successfully render page" do |
123 | get :show, :app_id => app.id, :id => err.id | 123 | get :show, :app_id => app.id, :id => err.id |
124 | response.should be_success | 124 | response.should be_success |
@@ -131,9 +131,9 @@ describe ErrsController do | @@ -131,9 +131,9 @@ describe ErrsController do | ||
131 | err = Factory :err | 131 | err = Factory :err |
132 | get :show, :app_id => err.app.id, :id => err.id | 132 | get :show, :app_id => err.app.id, :id => err.id |
133 | 133 | ||
134 | - response.body.should_not button_matcher | 134 | + response.body.should_not button_matcher |
135 | end | 135 | end |
136 | - | 136 | + |
137 | it "should exist for err's app with issue tracker" do | 137 | it "should exist for err's app with issue tracker" do |
138 | tracker = Factory(:lighthouseapp_tracker) | 138 | tracker = Factory(:lighthouseapp_tracker) |
139 | err = Factory(:err, :app => tracker.app) | 139 | err = Factory(:err, :app => tracker.app) |
@@ -141,7 +141,7 @@ describe ErrsController do | @@ -141,7 +141,7 @@ describe ErrsController do | ||
141 | 141 | ||
142 | response.body.should button_matcher | 142 | response.body.should button_matcher |
143 | end | 143 | end |
144 | - | 144 | + |
145 | it "should not exist for err with issue_link" do | 145 | it "should not exist for err with issue_link" do |
146 | tracker = Factory(:lighthouseapp_tracker) | 146 | tracker = Factory(:lighthouseapp_tracker) |
147 | err = Factory(:err, :app => tracker.app, :issue_link => "http://some.host") | 147 | err = Factory(:err, :app => tracker.app, :issue_link => "http://some.host") |
@@ -151,7 +151,7 @@ describe ErrsController do | @@ -151,7 +151,7 @@ describe ErrsController do | ||
151 | end | 151 | end |
152 | end | 152 | end |
153 | end | 153 | end |
154 | - | 154 | + |
155 | context 'when logged in as a user' do | 155 | context 'when logged in as a user' do |
156 | before do | 156 | before do |
157 | sign_in(@user = Factory(:user)) | 157 | sign_in(@user = Factory(:user)) |
@@ -160,12 +160,12 @@ describe ErrsController do | @@ -160,12 +160,12 @@ describe ErrsController do | ||
160 | @watcher = Factory(:user_watcher, :user => @user, :app => @watched_app) | 160 | @watcher = Factory(:user_watcher, :user => @user, :app => @watched_app) |
161 | @watched_err = Factory(:err, :app => @watched_app) | 161 | @watched_err = Factory(:err, :app => @watched_app) |
162 | end | 162 | end |
163 | - | 163 | + |
164 | it 'finds the err if the user is watching the app' do | 164 | it 'finds the err if the user is watching the app' do |
165 | get :show, :app_id => @watched_app.to_param, :id => @watched_err.id | 165 | get :show, :app_id => @watched_app.to_param, :id => @watched_err.id |
166 | assigns(:err).should == @watched_err | 166 | assigns(:err).should == @watched_err |
167 | end | 167 | end |
168 | - | 168 | + |
169 | it 'raises a DocumentNotFound error if the user is not watching the app' do | 169 | it 'raises a DocumentNotFound error if the user is not watching the app' do |
170 | lambda { | 170 | lambda { |
171 | get :show, :app_id => @unwatched_err.app_id, :id => @unwatched_err.id | 171 | get :show, :app_id => @unwatched_err.app_id, :id => @unwatched_err.id |
@@ -173,17 +173,17 @@ describe ErrsController do | @@ -173,17 +173,17 @@ describe ErrsController do | ||
173 | end | 173 | end |
174 | end | 174 | end |
175 | end | 175 | end |
176 | - | 176 | + |
177 | describe "PUT /apps/:app_id/errs/:id/resolve" do | 177 | describe "PUT /apps/:app_id/errs/:id/resolve" do |
178 | before do | 178 | before do |
179 | sign_in Factory(:admin) | 179 | sign_in Factory(:admin) |
180 | - | 180 | + |
181 | @err = Factory(:err) | 181 | @err = Factory(:err) |
182 | App.stub(:find).with(@err.app.id).and_return(@err.app) | 182 | App.stub(:find).with(@err.app.id).and_return(@err.app) |
183 | @err.app.errs.stub(:find).and_return(@err) | 183 | @err.app.errs.stub(:find).and_return(@err) |
184 | @err.stub(:resolve!) | 184 | @err.stub(:resolve!) |
185 | end | 185 | end |
186 | - | 186 | + |
187 | it 'finds the app and the err' do | 187 | it 'finds the app and the err' do |
188 | App.should_receive(:find).with(@err.app.id).and_return(@err.app) | 188 | App.should_receive(:find).with(@err.app.id).and_return(@err.app) |
189 | @err.app.errs.should_receive(:find).and_return(@err) | 189 | @err.app.errs.should_receive(:find).and_return(@err) |
@@ -191,17 +191,17 @@ describe ErrsController do | @@ -191,17 +191,17 @@ describe ErrsController do | ||
191 | assigns(:app).should == @err.app | 191 | assigns(:app).should == @err.app |
192 | assigns(:err).should == @err | 192 | assigns(:err).should == @err |
193 | end | 193 | end |
194 | - | 194 | + |
195 | it "should resolve the issue" do | 195 | it "should resolve the issue" do |
196 | @err.should_receive(:resolve!).and_return(true) | 196 | @err.should_receive(:resolve!).and_return(true) |
197 | put :resolve, :app_id => @err.app.id, :id => @err.id | 197 | put :resolve, :app_id => @err.app.id, :id => @err.id |
198 | end | 198 | end |
199 | - | 199 | + |
200 | it "should display a message" do | 200 | it "should display a message" do |
201 | put :resolve, :app_id => @err.app.id, :id => @err.id | 201 | put :resolve, :app_id => @err.app.id, :id => @err.id |
202 | request.flash[:success].should match(/Great news/) | 202 | request.flash[:success].should match(/Great news/) |
203 | end | 203 | end |
204 | - | 204 | + |
205 | it "should redirect to the app page" do | 205 | it "should redirect to the app page" do |
206 | put :resolve, :app_id => @err.app.id, :id => @err.id | 206 | put :resolve, :app_id => @err.app.id, :id => @err.id |
207 | response.should redirect_to(app_path(@err.app)) | 207 | response.should redirect_to(app_path(@err.app)) |
@@ -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 |