From a02dacae093789471b601c378f6015690a527fc5 Mon Sep 17 00:00:00 2001 From: Ben Langfeld Date: Mon, 25 Apr 2011 21:25:02 +0100 Subject: [PATCH] Add support for Pivotal Tracker issue creation --- Gemfile | 1 + Gemfile.lock | 11 +++++++++++ README.md | 10 ++++++++-- app/helpers/application_helper.rb | 8 ++++++++ app/models/app.rb | 20 ++++++++++---------- app/models/issue_tracker.rb | 37 ++++++++++++++++++++++++++++++------- app/views/apps/_fields.html.haml | 15 ++++++++++----- app/views/errs/pivotal_body.txt.erb | 21 +++++++++++++++++++++ public/javascripts/form.js | 21 +++++++-------------- public/stylesheets/application.css | 102 +++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------------------------- spec/controllers/apps_controller_spec.rb | 76 +++++++++++++++++++++++++++++++++++++++++++++++++--------------------------- spec/controllers/errs_controller_spec.rb | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------- spec/factories/issue_tracker_factories.rb | 17 ++++++++++++----- 13 files changed, 280 insertions(+), 142 deletions(-) create mode 100644 app/views/errs/pivotal_body.txt.erb diff --git a/Gemfile b/Gemfile index f0f9cc6..859199a 100644 --- a/Gemfile +++ b/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' diff --git a/Gemfile.lock b/Gemfile.lock index 69b33db..f5ea79d 100644 --- a/Gemfile.lock +++ b/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) diff --git a/README.md b/README.md index e20a020..23714f4 100644 --- a/README.md +++ b/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 ---- diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index a5ba883..9edce4b 100644 --- a/app/helpers/application_helper.rb +++ b/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 diff --git a/app/models/app.rb b/app/models/app.rb index 3709ae1..c3098a1 100644 --- a/app/models/app.rb +++ b/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 @@ class App !(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 diff --git a/app/models/issue_tracker.rb b/app/models/issue_tracker.rb index 717cd37..7a6dc23 100644 --- a/app/models/issue_tracker.rb +++ b/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 @@ class IssueTracker 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 @@ class IssueTracker 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 IssueTracker 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 diff --git a/app/views/apps/_fields.html.haml b/app/views/apps/_fields.html.haml index d9a2793..43a2267 100644 --- a/app/views/apps/_fields.html.haml +++ b/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" diff --git a/app/views/errs/pivotal_body.txt.erb b/app/views/errs/pivotal_body.txt.erb new file mode 100644 index 0000000..d820ea4 --- /dev/null +++ b/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 %> diff --git a/public/javascripts/form.js b/public/javascripts/form.js index b2e1eef..b0f6c64 100644 --- a/public/javascripts/form.js +++ b/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 = $('').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'); }); } \ No newline at end of file diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css index 41ac340..9d51f99 100644 --- a/public/stylesheets/application.css +++ b/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; diff --git a/spec/controllers/apps_controller_spec.rb b/spec/controllers/apps_controller_spec.rb index 38eb9c7..dee4236 100644 --- a/spec/controllers/apps_controller_spec.rb +++ b/spec/controllers/apps_controller_spec.rb @@ -5,7 +5,7 @@ describe AppsController do 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 @@ describe AppsController do 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 @@ describe AppsController do end end end - + describe "GET /apps/:id" do context 'logged in as an admin' do before(:each) do @@ -75,27 +75,27 @@ describe AppsController do 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 @@ describe AppsController do 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 @@ describe AppsController do 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 @@ describe AppsController do 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 @@ describe AppsController do 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 @@ describe AppsController do 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 @@ describe AppsController do @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 @@ describe AppsController do @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 diff --git a/spec/controllers/errs_controller_spec.rb b/spec/controllers/errs_controller_spec.rb index 86f8fb4..283a067 100644 --- a/spec/controllers/errs_controller_spec.rb +++ b/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 @@ describe ErrsController do 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 @@ describe ErrsController do 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 @@ describe ErrsController do 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 @@ describe ErrsController do 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 @@ describe ErrsController do 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 @@ describe ErrsController do 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 @@ describe ErrsController do 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 @@ describe ErrsController do end end end - + context 'when logged in as a user' do before do sign_in(@user = Factory(:user)) @@ -160,12 +160,12 @@ describe ErrsController do @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 @@ describe ErrsController do 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 @@ describe ErrsController do 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 @@ describe ErrsController do 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 = "my subject#{number}" + 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 => /#{tracker.project_id}<\/project-id>/) + WebMock.should requested.with(:body => /\[#{ err.environment }\]\[#{err.where}\] #{err.message.to_s.truncate(100)}<\/subject>/) + WebMock.should requested.with(:body => /.+<\/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 diff --git a/spec/factories/issue_tracker_factories.rb b/spec/factories/issue_tracker_factories.rb index a1bb160..b2c20f1 100644 --- a/spec/factories/issue_tracker_factories.rb +++ b/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 \ No newline at end of file +end + +Factory.define :pivotal_tracker, :parent => :generic_tracker do |e| + e.issue_tracker_type 'pivotal' +end -- libgit2 0.21.2