Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Add support for Pivotal Tracker issue creation

  • Loading branch information...
commit a02dacae093789471b601c378f6015690a527fc5 1 parent 6858ff3
@benlangfeld benlangfeld authored
View
1  Gemfile
@@ -8,6 +8,7 @@ gem 'will_paginate'
gem 'devise', '~> 1.1.8'
gem 'lighthouse-api'
gem 'redmine_client', :git => "git://github.com/oruen/redmine_client.git"
+gem 'pivotal-tracker'
platform :ruby do
gem 'bson_ext', '~> 1.2'
View
11 Gemfile.lock
@@ -54,7 +54,10 @@ GEM
factory_girl (~> 1.3)
railties (>= 3.0.0)
haml (3.0.25)
+ happymapper (0.3.2)
+ libxml-ruby (~> 1.1.3)
i18n (0.5.0)
+ libxml-ruby (1.1.4)
lighthouse-api (2.0)
activeresource (>= 3.0.0)
activesupport (>= 3.0.0)
@@ -72,6 +75,11 @@ GEM
tzinfo (~> 0.3.22)
will_paginate (~> 3.0.pre)
nokogiri (1.4.4)
+ pivotal-tracker (0.2.0)
+ builder
+ happymapper (>= 0.2.4)
+ nokogiri (~> 1.4.1)
+ rest-client (~> 1.5.1)
polyglot (0.3.1)
rack (1.2.2)
rack-mount (0.6.13)
@@ -92,6 +100,8 @@ GEM
rake (>= 0.8.7)
thor (~> 0.14.4)
rake (0.8.7)
+ rest-client (1.5.1)
+ mime-types (>= 1.16)
rspec (2.5.0)
rspec-core (~> 2.5.0)
rspec-expectations (~> 2.5.0)
@@ -128,6 +138,7 @@ DEPENDENCIES
lighthouse-api
mongoid (~> 2.0.0.rc.7)
nokogiri
+ pivotal-tracker
rails (= 3.0.5)
redmine_client!
rspec (~> 2.5)
View
10 README.md
@@ -92,17 +92,23 @@ for you. Checkout [Hoptoad](http://hoptoadapp.com) from the guys over at
Lighthouseapp integration
-------------------------
-* Account is the name of your subdomain, i.e. **litcafe** for project at http://litcafe.lighthouseapp.com/projects/73466-face/overview
+* Account is the name of your subdomain, i.e. **litcafe** for project at http://litcafe.lighthouseapp.com/projects/73466-face/overview
* 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.
* Project id is number identifier of your project, i.e. **73466** for project at http://litcafe.lighthouseapp.com/projects/73466-face/overview
Redmine integration
-------------------------
-* Account is the host of your redmine installation, i.e. **http://redmine.org**
+* Account is the host of your redmine installation, i.e. **http://redmine.org**
* 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.
* Project id is an identifier of your project, i.e. **chilliproject** for project at http://www.redmine.org/projects/chilliproject
+Pivotal Tracker integration
+-------------------------
+
+* 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.
+* Project id is an identifier of your project, i.e. **24324** for project at http://www.pivotaltracker.com/projects/24324
+
TODO
----
View
8 app/helpers/application_helper.rb
@@ -2,4 +2,12 @@ module ApplicationHelper
def lighthouse_tracker? object
object.issue_tracker_type == "lighthouseapp"
end
+
+ def redmine_tracker? object
+ object.issue_tracker_type == "redmine"
+ end
+
+ def pivotal_tracker? object
+ object.issue_tracker_type == "pivotal"
+ end
end
View
20 app/models/app.rb
@@ -1,7 +1,7 @@
class App
include Mongoid::Document
include Mongoid::Timestamps
-
+
field :name, :type => String
field :api_key
field :resolve_errs_on_deploy, :type => Boolean, :default => false
@@ -21,29 +21,29 @@ class App
embeds_many :deploys
embeds_one :issue_tracker
references_many :errs, :dependent => :destroy
-
+
before_validation :generate_api_key, :on => :create
-
+
validates_presence_of :name, :api_key
validates_uniqueness_of :name, :allow_blank => true
validates_uniqueness_of :api_key, :allow_blank => true
validates_associated :watchers
validate :check_issue_tracker
-
+
accepts_nested_attributes_for :watchers, :allow_destroy => true,
:reject_if => proc { |attrs| attrs[:user_id].blank? && attrs[:email].blank? }
accepts_nested_attributes_for :issue_tracker, :allow_destroy => true,
- :reject_if => proc { |attrs| !%w( lighthouseapp redmine ).include?(attrs[:issue_tracker_type]) }
-
+ :reject_if => proc { |attrs| !%w(lighthouseapp redmine pivotal).include?(attrs[:issue_tracker_type]) }
+
# Mongoid Bug: find(id) on association proxies returns an Enumerator
def self.find_by_id!(app_id)
where(:_id => app_id).first || raise(Mongoid::Errors::DocumentNotFound.new(self,app_id))
end
-
+
def self.find_by_api_key!(key)
where(:api_key => key).first || raise(Mongoid::Errors::DocumentNotFound.new(self,key))
end
-
+
def last_deploy_at
deploys.last && deploys.last.created_at
end
@@ -58,9 +58,9 @@ def notify_on_deploys
!(self[:notify_on_deploys] == false)
end
alias :notify_on_deploys? :notify_on_deploys
-
+
protected
-
+
def generate_api_key
self.api_key ||= ActiveSupport::SecureRandom.hex
end
View
37 app/models/issue_tracker.rb
@@ -6,7 +6,7 @@ class IssueTracker
default_url_options[:host] = Errbit::Application.config.action_mailer.default_url_options[:host]
validate :check_params
-
+
embedded_in :app, :inverse_of => :issue_tracker
field :account, :type => String
@@ -15,8 +15,14 @@ class IssueTracker
field :issue_tracker_type, :type => String, :default => 'lighthouseapp'
def create_issue err
- return create_lighthouseapp_issue err if issue_tracker_type == 'lighthouseapp'
- create_redmine_issue err if issue_tracker_type == 'redmine'
+ case issue_tracker_type
+ when 'lighthouseapp'
+ create_lighthouseapp_issue err
+ when 'redmine'
+ create_redmine_issue err
+ when 'pivotal'
+ create_pivotal_issue err
+ end
end
protected
@@ -34,6 +40,14 @@ def create_redmine_issue err
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}")
end
+ def create_pivotal_issue err
+ PivotalTracker::Client.token = api_token
+ PivotalTracker::Client.use_ssl = true
+ project = PivotalTracker::Project.find project_id.to_i
+ story = project.stories.create :name => issue_title(err), :story_type => 'bug', :description => self.class.pivotal_body_template.result(binding)
+ err.update_attribute :issue_link, "https://www.pivotaltracker.com/story/show/#{story.id}"
+ end
+
def create_lighthouseapp_issue err
Lighthouse.account = account
Lighthouse.token = api_token
@@ -56,12 +70,17 @@ def issue_title err
end
def check_params
- blank_flags = %w( api_token project_id account ).map {|m| self[m].blank? }
+ blank_flag_fields = %w(api_token project_id)
+ blank_flag_fields << 'account' if %w(lighthouseapp redmine).include? issue_tracker_type
+ blank_flags = blank_flag_fields.map {|m| self[m].blank? }
if blank_flags.any? && !blank_flags.all?
- message = if issue_tracker_type == 'lighthouseapp'
+ message = case issue_tracker_type
+ when 'lighthouseapp'
"You must specify your Lighthouseapp account, api token and project id"
- else
+ when 'redmine'
"You must specify your Redmine url, api token and project id"
+ when 'pivotal'
+ "You must specify your Pivotal Tracker api token and project id"
end
errors.add(:base, message)
end
@@ -71,9 +90,13 @@ class << self
def lighthouseapp_body_template
@@lighthouseapp_body_template ||= ERB.new(File.read(Rails.root + "app/views/errs/lighthouseapp_body.txt.erb").gsub(/^\s*/, ''))
end
-
+
def redmine_body_template
@@redmine_body_template ||= ERB.new(File.read(Rails.root + "app/views/errs/redmine_body.txt.erb"))
end
+
+ def pivotal_body_template
+ @@pivotal_body_template ||= ERB.new(File.read(Rails.root + "app/views/errs/pivotal_body.txt.erb"))
+ end
end
end
View
15 app/views/apps/_fields.html.haml
@@ -3,7 +3,7 @@
%div.required
= f.label :name
= f.text_field :name
-
+
%div.checkbox
= f.check_box :notify_on_errs
= f.label :notify_on_errs, 'Notify on errors'
@@ -39,19 +39,24 @@
= label_tag :issue_tracker_type_lighthouseapp, 'Lighthouse', :for => label_for_attr(w, 'issue_tracker_type_lighthouseapp')
= w.radio_button :issue_tracker_type, :redmine
= label_tag :issue_tracker_type_redmine, 'Redmine', :for => label_for_attr(w, 'issue_tracker_type_redmine')
- %div.tracker_params{:class => lighthouse_tracker?(w.object) ? 'choosen' : nil}
+ = w.radio_button :issue_tracker_type, :pivotal
+ = label_tag :issue_tracker_type_pivotal, 'Pivotal Tracker', :for => label_for_attr(w, 'issue_tracker_type_pivotal')
+ %div.tracker_params.lighthouseapp{:class => lighthouse_tracker?(w.object) ? 'chosen' : nil}
= w.label :account, "Account"
= w.text_field :account, :placeholder => "abc from abc.lighthouseapp.com"
= w.label :api_token, "API token"
= w.text_field :api_token, :placeholder => "API Token for your account"
= w.label :project_id, "Project ID"
= w.text_field :project_id, :placeholder => "123 from abc from abc.lighthouseapp.com/projects/123"
- %div.tracker_params{:class => lighthouse_tracker?(w.object) ? nil : 'choosen'}
+ %div.tracker_params.redmine{:class => redmine_tracker?(w.object) ? 'chosen' : nil}
= w.label :account, "Redmine URL"
= w.text_field :account, :placeholder => "like http://www.redmine.org/"
= w.label :api_token, "API token"
= w.text_field :api_token, :placeholder => "API Token for your account"
= w.label :project_id, "Project ID"
= w.text_field :project_id
-
-
+ %div.tracker_params.pivotal{:class => pivotal_tracker?(w.object) ? 'chosen' : nil}
+ = w.label :project_id, "Project ID"
+ = w.text_field :project_id
+ = w.label :api_token, "API token"
+ = w.text_field :api_token, :placeholder => "API Token for your account"
View
21 app/views/errs/pivotal_body.txt.erb
@@ -0,0 +1,21 @@
+See this exception on Errbit: <%= app_err_url err.app, err %>
+<% if notice = err.notices.first %>
+ <% if notice.request['url'].present? %>URL: <%= notice.request['url'] %><% end %>
+ Where: <%= notice.err.where %>
+ Occurred: <%= notice.created_at.to_s :micro %>
+ Similar: <%= (notice.err.notices.count - 1).to_s %>
+
+ Params:
+ <%= pretty_hash notice.params %>
+
+ Session:
+ <%= pretty_hash notice.session %>
+
+ Backtrace:
+ <%= notice.backtrace.map { |line| "#{line['number']}: #{line['file'].sub(/^\[PROJECT_ROOT\]/, '')} -> *#{line['method']}*" }.join "\n" %>
+
+ Environment:
+ <% notice.env_vars.each do |key, val| %>
+ <%= "#{key}: #{val}" %>
+ <% end %>
+<% end %>
View
21 public/javascripts/form.js
@@ -1,6 +1,6 @@
$(function(){
activateNestedForms();
-
+
if($('div.watcher.nested').length)
activateWatcherTypeSelector();
@@ -11,9 +11,9 @@ $(function(){
function activateNestedForms() {
$('.nested-wrapper').each(function(){
var wrapper = $(this);
-
+
makeNestedItemsDestroyable(wrapper);
-
+
var addLink = $('<a/>').text('add another').addClass('add-nested');
addLink.click(appendNestedItem);
wrapper.append(addLink);
@@ -35,7 +35,7 @@ function appendNestedItem() {
var nestedItem = addLink.parent().find('.nested').first().clone().show();
var timestamp = new Date();
timestamp = timestamp.valueOf();
-
+
nestedItem.find('input, select').each(function(){
var input = $(this);
input.attr('id', input.attr('id').replace(/([_\[])\d+([\]_])/,'$1'+timestamp+'$2'));
@@ -73,17 +73,10 @@ function activateWatcherTypeSelector() {
}
function activateIssueTrackerTypeSelector() {
- var not_choosen = $("div.tracker_params").filter(function () {
- return !$(this).hasClass("choosen");
- });
- window.hiddenTracker = not_choosen.html();
- not_choosen.remove();
$('div.issue_tracker input[name*=issue_tracker_type]').live('click', function(){
- var choosen = $(this).val();
+ var chosen = $(this).val();
var wrapper = $(this).closest('.nested');
- var tmp;
- tmp = wrapper.find('div.choosen').html();
- wrapper.find('div.choosen').html(window.hiddenTracker);
- window.hiddenTracker = tmp;
+ wrapper.find('div.chosen').removeClass('chosen');
+ wrapper.find('div.'+chosen).addClass('chosen');
});
}
View
102 public/stylesheets/application.css
@@ -1,9 +1,9 @@
-html {
+html {
margin: 0; padding: 0;
color: #585858; background-color: #E2E2E2;
font-size: 62.8%; font-family: Helvetica, "Lucida Grande","Lucida Sans",Arial,sans-serif;
}
-body {
+body {
margin: 0; padding: 0;
font-size: 1.3em; line-height: 1.4em;
}
@@ -34,7 +34,7 @@ a:visited { color: #0069cc;}
a:hover { color: #0069cc; text-decoration: underline; }
a.action { float: right; font-size: 0.9em;}
-#header > div, #nav-bar, #content-wrapper, #footer {
+#header > div, #nav-bar, #content-wrapper, #footer {
width: 930px;
margin: 0 auto;
position: relative;
@@ -98,19 +98,19 @@ a.action { float: right; font-size: 0.9em;}
margin-bottom: 24px;
height: 41px;
}
-#nav-bar li {
- float: left;
+#nav-bar li {
+ float: left;
margin-right: 18px;
color: #666;
background: #FFF url(images/button-bg.png) 0 bottom repeat-x;
border-radius: 50px;
-moz-border-radius: 50px;
-webkit-border-radius: 50px;
- border: 1px solid #bbb;
+ border: 1px solid #bbb;
}
#nav-bar li a {
color: #666;
- display: block;
+ display: block;
padding: 0 20px 0 40px;
font-size: 14px; font-weight: bold; line-height: 39px; text-decoration: none;
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;}
#nav-bar li.apps a { background-image: url(images/icons/briefcase.png); }
#nav-bar li.errs a { background-image: url(images/icons/error.png); }
#nav-bar li.users a { background-image: url(images/icons/user.png); }
-#nav-bar li:hover {
+#nav-bar li:hover {
box-shadow: 0 0 3px #69c;
-moz-box-shadow: 0 0 3px #69c;
-webkit-box-shadow: 0 0 3px #69c;
}
-#nav-bar li.active {
- border-color: #fff;
+#nav-bar li.active {
+ border-color: #fff;
background-color: #CCC;
background-image: none;
- box-shadow: inset 0 0 5px #999;
- -moz-box-shadow: inset 0 0 5px #999;
+ box-shadow: inset 0 0 5px #999;
+ -moz-box-shadow: inset 0 0 5px #999;
-webkit-box-shadow: inset 0 0 5px #999;
}
@@ -141,13 +141,13 @@ a.action { float: right; font-size: 0.9em;}
/* Content Title */
#content-title {
- padding: 30px 20px;
+ padding: 30px 20px;
border-top: 1px solid #FFF;
border-bottom: 1px solid #FFF;
background-color: #e2e2e2;
}
#content-title h1 {
- padding: 0; margin: 0;
+ padding: 0; margin: 0;
width: 85%;
border: none;
color: #666;
@@ -161,7 +161,7 @@ a.action { float: right; font-size: 0.9em;}
position: absolute;
top: 25px; right: 20px;
}
-#action-bar span {
+#action-bar span {
display: inline-block;
margin-left: 18px;
text-decoration: none;
@@ -174,14 +174,14 @@ a.action { float: right; font-size: 0.9em;}
}
#action-bar span a {
color: #666;
- display: block;
+ display: block;
padding: 0 20px 0 40px;
font-size: 14px; font-weight: bold; line-height: 39px; text-decoration: none;
text-shadow: 1px 1px 0px #FFF; -webkit-text-shadow: 1px 1px 0px #FFF;
background: transparent 10px 8px no-repeat;
}
#action-bar a:hover { text-decoration: none;}
-#action-bar span:hover {
+#action-bar span:hover {
box-shadow: 0 0 3px #69c;
-moz-box-shadow: 0 0 3px #69c;
-webkit-box-shadow: 0 0 3px #69c;
@@ -194,14 +194,14 @@ a.action { float: right; font-size: 0.9em;}
#content {
padding: 20px; border-top: 1px solid #C6C6C6;
background-color: #FFF;
-}
+}
-#content a.button {
+#content a.button {
float: right;
display: block;
margin-bottom: 10px;
}
-
+
/* Footer */
#footer {
padding: 20px 0;
@@ -211,22 +211,22 @@ a.action { float: right; font-size: 0.9em;}
/* Flash Messages */
#flash-messages li {
- padding: 13px 45px;
- margin-bottom:25px;
+ padding: 13px 45px;
+ margin-bottom:25px;
border: 1px solid #C6C6C6;
background-color: #F9F9F9;
line-height: 1em;
}
#flash-messages li.notice {
- padding-left: 20px;
+ padding-left: 20px;
background-color: #b5eeff;
border: 1px solid #6cf;
}
-#flash-messages li.success {
+#flash-messages li.success {
background: #cfc url(images/icons/success.png) 16px 50% no-repeat;
border: 1px solid #6c3;
}
-#flash-messages li.error {
+#flash-messages li.error {
background: #fcc url(images/icons/error.png) 16px 50% no-repeat;
border: 1px solid #f99;
}
@@ -244,13 +244,13 @@ form fieldset {
padding: 0.8em; margin-bottom: 1em;
background-color: #F0F0F0; border: 1px solid #C6C6C6; border-left: none; border-right: none;
}
-form fieldset legend {
- font-size: 1.2em; font-weight: bold; text-transform: uppercase;
+form fieldset legend {
+ font-size: 1.2em; font-weight: bold; text-transform: uppercase;
color: #555;
}
form label {
font-weight: bold; text-transform: uppercase; line-height: 1.6em;
- display: inline-block;
+ display: inline-block;
}
form label.inline { display: inline; }
form .checkbox label { display: inline; }
@@ -281,17 +281,17 @@ form input[type=submit] {
font-size: 1.2em; line-height: 1em; text-transform: uppercase;
border: none; color: #FFF; background-color: #387fc1;
}
-form div.buttons {
+form div.buttons {
color: #666;
background: #FFF url(images/button-bg.png) 0 bottom repeat-x;
border-radius: 50px;
-moz-border-radius: 50px;
-webkit-border-radius: 50px;
- border: 1px solid #bbb;
+ border: 1px solid #bbb;
display: inline-block;
}
-form div.buttons:hover {
- color: #666;
+form div.buttons:hover {
+ color: #666;
box-shadow: 0 0 3px #69c;
-moz-box-shadow: 0 0 3px #69c;
-webkit-box-shadow: 0 0 3px #69c;
@@ -351,10 +351,10 @@ form .error-messages ul {
}
/* Tables */
-table {
- width: 100%;
+table {
+ width: 100%;
border: 1px solid #C6C6C6;
- margin-bottom: 1.5em;
+ margin-bottom: 1.5em;
border-collapse: separate;
}
table thead th {
@@ -364,10 +364,10 @@ table thead th {
table tbody tr:first-child td {
border-top: 1px solid #C6C6C6;
}
-table th, table td {
- border-top: 1px solid #C6C6C6;
- padding: 10px 8px;
- text-align: left;
+table th, table td {
+ border-top: 1px solid #C6C6C6;
+ padding: 10px 8px;
+ text-align: left;
}
table th { background-color: #E2E2E2; font-weight: bold; text-transform: uppercase; white-space: nowrap; }
table tbody tr:nth-child(odd) td { background-color: #F9F9F9; }
@@ -442,8 +442,8 @@ pre {
background-color: #CCC;
background-image: none;
border-color: #FFF;
- box-shadow: inset 0 0 5px #999;
- -moz-box-shadow: inset 0 0 5px #999;
+ box-shadow: inset 0 0 5px #999;
+ -moz-box-shadow: inset 0 0 5px #999;
-webkit-box-shadow: inset 0 0 5px #999;
font-style: normal;
}
@@ -477,11 +477,11 @@ a:hover.button {
background-color: #eee;
}
a.button.active {
- border-color: #fff;
+ border-color: #fff;
background-color: #CCC;
background-image: none;
- box-shadow: inset 0 0 5px #999;
- -moz-box-shadow: inset 0 0 5px #999;
+ box-shadow: inset 0 0 5px #999;
+ -moz-box-shadow: inset 0 0 5px #999;
-webkit-box-shadow: inset 0 0 5px #999;
}
@@ -502,10 +502,10 @@ a.button.active {
}
/* Watchers and Issue Tracker Forms */
-div.nested.watcher .user, div.nested.watcher .email, div.issue_tracker.nested .lighthouseapp, div.issue_tracker.nested .redmine {
+div.nested.watcher .user, div.nested.watcher .email, div.issue_tracker.nested .lighthouseapp, div.issue_tracker.nested .redmine, div.issue_tracker.nested .pivotal {
display: none;
}
-div.nested.watcher .choosen, div.nested.issue_tracker .choosen {
+div.nested.watcher .choosen, div.nested.issue_tracker .chosen {
display: block;
}
@@ -559,7 +559,7 @@ table.errs td.app .environment {
font-size: 0.8em;
color: #999;
}
-table.errs td.message a {
+table.errs td.message a {
width: 420px;
display: block;
word-wrap: break-word;
@@ -587,6 +587,10 @@ table.errs tr.resolved td > * {
background: transparent url(/images/redmine_create.png) 6px 5px no-repeat;
}
+#action-bar a.pivotal_create {
+ background: transparent url(/images/pivotal_create.png) 6px 5px no-repeat;
+}
+
#action-bar a.lighthouseapp_goto {
background: transparent url(/images/lighthouseapp_goto.png) 6px 5px no-repeat;
}
@@ -595,6 +599,10 @@ table.errs tr.resolved td > * {
background: transparent url(/images/redmine_goto.png) 6px 5px no-repeat;
}
+#action-bar a.pivotal_goto {
+ background: transparent url(/images/pivotal_goto.png) 6px 5px no-repeat;
+}
+
/* Notices Pagination */
.notice-pagination {
float: left;
View
76 spec/controllers/apps_controller_spec.rb
@@ -5,7 +5,7 @@
it_requires_authentication
it_requires_admin_privileges :for => {:new => :get, :edit => :get, :create => :post, :update => :put, :destroy => :delete}
-
+
describe "GET /apps" do
context 'when logged in as an admin' do
it 'finds all apps' do
@@ -16,7 +16,7 @@
assigns(:apps).should == apps
end
end
-
+
context 'when logged in as a regular user' do
it 'finds apps the user is watching' do
sign_in(user = Factory(:user))
@@ -31,7 +31,7 @@
end
end
end
-
+
describe "GET /apps/:id" do
context 'logged in as an admin' do
before(:each) do
@@ -75,27 +75,27 @@
end
end
end
-
+
context 'logged in as a user' do
it 'finds the app if the user is watching it' do
pending
end
-
+
it 'does not find the app if the user is not watching it' do
sign_in Factory(:user)
app = Factory(:app)
- lambda {
+ lambda {
get :show, :id => app.id
}.should raise_error(Mongoid::Errors::DocumentNotFound)
end
end
end
-
+
context 'logged in as an admin' do
before do
sign_in Factory(:admin)
end
-
+
describe "GET /apps/new" do
it 'instantiates a new app with a prebuilt watcher' do
get :new
@@ -104,7 +104,7 @@
assigns(:app).watchers.should_not be_empty
end
end
-
+
describe "GET /apps/:id/edit" do
it 'finds the correct app' do
app = Factory(:app)
@@ -112,29 +112,29 @@
assigns(:app).should == app
end
end
-
+
describe "POST /apps" do
before do
@app = Factory(:app)
App.stub(:new).and_return(@app)
end
-
+
context "when the create is successful" do
before do
@app.should_receive(:save).and_return(true)
end
-
+
it "should redirect to the app page" do
post :create, :app => {}
response.should redirect_to(app_path(@app))
end
-
+
it "should display a message" do
post :create, :app => {}
request.flash[:success].should match(/success/)
end
end
-
+
context "when the create is unsuccessful" do
it "should render the new page" do
@app.should_receive(:save).and_return(false)
@@ -143,18 +143,18 @@
end
end
end
-
+
describe "PUT /apps/:id" do
before do
@app = Factory(:app)
end
-
+
context "when the update is successful" do
it "should redirect to the app page" do
put :update, :id => @app.id, :app => {}
response.should redirect_to(app_path(@app))
end
-
+
it "should display a message" do
put :update, :id => @app.id, :app => {}
request.flash[:success].should match(/success/)
@@ -168,7 +168,7 @@
response.should redirect_to(app_path(id))
end
end
-
+
context "when the update is unsuccessful" do
it "should render the edit page" do
put :update, :id => @app.id, :app => { :name => '' }
@@ -179,7 +179,7 @@
context "setting up issue tracker", :cur => true do
context "unknown tracker type" do
before(:each) do
- put :update, :id => @app.id, :app => { :issue_tracker_attributes => {
+ put :update, :id => @app.id, :app => { :issue_tracker_attributes => {
:issue_tracker_type => 'unknown', :project_id => '1234', :api_token => '123123', :account => 'myapp'
} }
@app.reload
@@ -211,7 +211,7 @@
@app.reload
@app.issue_tracker.should be_nil
- response.body.should match(/You must specify your Lighthouseapp account, api token and project id/)
+ response.body.should match(/You must specify your Lighthouseapp account, api token and project id/)
end
end
@@ -236,38 +236,60 @@
@app.reload
@app.issue_tracker.should be_nil
- response.body.should match(/You must specify your Redmine url, api token and project id/)
+ response.body.should match(/You must specify your Redmine url, api token and project id/)
+ end
+ end
+
+ context "pivotal" do
+ it "should save tracker params" do
+ put :update, :id => @app.id, :app => { :issue_tracker_attributes => {
+ :issue_tracker_type => 'pivotal', :project_id => '1234', :api_token => '123123' } }
+ @app.reload
+
+ tracker = @app.issue_tracker
+ tracker.issue_tracker_type.should == 'pivotal'
+ tracker.project_id.should == '1234'
+ tracker.api_token.should == '123123'
+ end
+
+ it "should show validation notice when sufficient params are not present" do
+ put :update, :id => @app.id, :app => { :issue_tracker_attributes => {
+ :issue_tracker_type => 'pivotal', :project_id => '1234', :api_token => '123123' } }
+ @app.reload
+
+ @app.issue_tracker.should be_nil
+ response.body.should match(/You must specify your Pivotal Tracker api token and project id/)
end
end
end
end
-
+
describe "DELETE /apps/:id" do
before do
@app = Factory(:app)
App.stub(:find).with(@app.id).and_return(@app)
end
-
+
it "should find the app" do
delete :destroy, :id => @app.id
assigns(:app).should == @app
end
-
+
it "should destroy the app" do
@app.should_receive(:destroy)
delete :destroy, :id => @app.id
end
-
+
it "should display a message" do
delete :destroy, :id => @app.id
request.flash[:success].should match(/success/)
end
-
+
it "should redirect to the apps page" do
delete :destroy, :id => @app.id
response.should redirect_to(apps_path)
end
end
end
-
+
end
View
83 spec/controllers/errs_controller_spec.rb
@@ -1,15 +1,15 @@
require 'spec_helper'
describe ErrsController do
-
+
it_requires_authentication :for => {
:index => :get, :all => :get, :show => :get, :resolve => :put
},
:params => {:app_id => 'dummyid', :id => 'dummyid'}
-
+
let(:app) { Factory(:app) }
let(:err) { Factory(:err, :app => app) }
-
+
describe "GET /errs" do
render_views
context 'when logged in as an admin' do
@@ -31,7 +31,7 @@
response.should be_success
response.body.should match(@err.message)
end
-
+
it "should handle lots of errors" do
pending "Turning off long running spec"
1000.times { Factory :notice }
@@ -55,7 +55,7 @@
end
end
end
-
+
context 'when logged in as a user' do
it 'gets a paginated list of unresolved errs for the users apps' do
sign_in(user = Factory(:user))
@@ -68,7 +68,7 @@
end
end
end
-
+
describe "GET /errs/all" do
context 'when logged in as an admin' do
it "gets a paginated list of all errs" do
@@ -83,7 +83,7 @@
assigns(:errs).should == errs
end
end
-
+
context 'when logged in as a user' do
it 'gets a paginated list of all errs for the users apps' do
sign_in(user = Factory(:user))
@@ -96,29 +96,29 @@
end
end
end
-
+
describe "GET /apps/:app_id/errs/:id" do
render_views
-
+
before do
3.times { Factory(:notice, :err => err)}
end
-
+
context 'when logged in as an admin' do
before do
sign_in Factory(:admin)
end
-
+
it "finds the app" do
get :show, :app_id => app.id, :id => err.id
assigns(:app).should == app
end
-
+
it "finds the err" do
get :show, :app_id => app.id, :id => err.id
assigns(:err).should == err
end
-
+
it "successfully render page" do
get :show, :app_id => app.id, :id => err.id
response.should be_success
@@ -131,9 +131,9 @@
err = Factory :err
get :show, :app_id => err.app.id, :id => err.id
- response.body.should_not button_matcher
+ response.body.should_not button_matcher
end
-
+
it "should exist for err's app with issue tracker" do
tracker = Factory(:lighthouseapp_tracker)
err = Factory(:err, :app => tracker.app)
@@ -141,7 +141,7 @@
response.body.should button_matcher
end
-
+
it "should not exist for err with issue_link" do
tracker = Factory(:lighthouseapp_tracker)
err = Factory(:err, :app => tracker.app, :issue_link => "http://some.host")
@@ -151,7 +151,7 @@
end
end
end
-
+
context 'when logged in as a user' do
before do
sign_in(@user = Factory(:user))
@@ -160,12 +160,12 @@
@watcher = Factory(:user_watcher, :user => @user, :app => @watched_app)
@watched_err = Factory(:err, :app => @watched_app)
end
-
+
it 'finds the err if the user is watching the app' do
get :show, :app_id => @watched_app.to_param, :id => @watched_err.id
assigns(:err).should == @watched_err
end
-
+
it 'raises a DocumentNotFound error if the user is not watching the app' do
lambda {
get :show, :app_id => @unwatched_err.app_id, :id => @unwatched_err.id
@@ -173,17 +173,17 @@
end
end
end
-
+
describe "PUT /apps/:app_id/errs/:id/resolve" do
before do
sign_in Factory(:admin)
-
+
@err = Factory(:err)
App.stub(:find).with(@err.app.id).and_return(@err.app)
@err.app.errs.stub(:find).and_return(@err)
@err.stub(:resolve!)
end
-
+
it 'finds the app and the err' do
App.should_receive(:find).with(@err.app.id).and_return(@err.app)
@err.app.errs.should_receive(:find).and_return(@err)
@@ -191,17 +191,17 @@
assigns(:app).should == @err.app
assigns(:err).should == @err
end
-
+
it "should resolve the issue" do
@err.should_receive(:resolve!).and_return(true)
put :resolve, :app_id => @err.app.id, :id => @err.id
end
-
+
it "should display a message" do
put :resolve, :app_id => @err.app.id, :id => @err.id
request.flash[:success].should match(/Great news/)
end
-
+
it "should redirect to the app page" do
put :resolve, :app_id => @err.app.id, :id => @err.id
response.should redirect_to(app_path(@err.app))
@@ -285,6 +285,39 @@
err.issue_link.should == @issue_link.sub(/\.xml/, '')
end
end
+
+ context "redmine tracker" do
+ let(:notice) { Factory :notice }
+ let(:tracker) { Factory :pivotal_tracker, :app => notice.err.app }
+ let(:err) { notice.err }
+
+ before(:each) do
+ pending
+ number = 5
+ @issue_link = "#{tracker.account}/issues/#{number}.xml?project_id=#{tracker.project_id}"
+ body = "<issue><subject>my subject</subject><id>#{number}</id></issue>"
+ stub_request(:post, "#{tracker.account}/issues.xml").to_return(:status => 201, :headers => {'Location' => @issue_link}, :body => body )
+
+ post :create_issue, :app_id => err.app.id, :id => err.id
+ err.reload
+ end
+
+ it "should make request to Pivotal Tracker with err params" do
+ requested = have_requested(:post, "#{tracker.account}/issues.xml")
+ WebMock.should requested.with(:headers => {'X-Redmine-API-Key' => tracker.api_token})
+ WebMock.should requested.with(:body => /<project-id>#{tracker.project_id}<\/project-id>/)
+ WebMock.should requested.with(:body => /<subject>\[#{ err.environment }\]\[#{err.where}\] #{err.message.to_s.truncate(100)}<\/subject>/)
+ WebMock.should requested.with(:body => /<description>.+<\/description>/m)
+ end
+
+ it "should redirect to err page" do
+ response.should redirect_to( app_err_path(err.app, err) )
+ end
+
+ it "should create issue link for err" do
+ err.issue_link.should == @issue_link.sub(/\.xml/, '')
+ end
+ end
end
context "absent issue tracker" do
View
17 spec/factories/issue_tracker_factories.rb
@@ -1,12 +1,19 @@
-Factory.define :lighthouseapp_tracker, :class => IssueTracker do |e|
- e.issue_tracker_type 'lighthouseapp'
- e.account { Factory.next :word }
+Factory.define :generic_tracker, :class => IssueTracker do |e|
e.api_token { Factory.next :word }
e.project_id { Factory.next :word }
e.association :app, :factory => :app
end
-Factory.define :redmine_tracker, :parent => :lighthouseapp_tracker do |e|
+Factory.define :lighthouseapp_tracker, :parent => :generic_tracker do |e|
+ e.issue_tracker_type 'lighthouseapp'
+ e.account { Factory.next :word }
+end
+
+Factory.define :redmine_tracker, :parent => :generic_tracker do |e|
e.issue_tracker_type 'redmine'
e.account { "http://#{Factory.next(:word)}.com" }
-end
+end
+
+Factory.define :pivotal_tracker, :parent => :generic_tracker do |e|
+ e.issue_tracker_type 'pivotal'
+end
Please sign in to comment.
Something went wrong with that request. Please try again.