From 007916789e47816715e047c2e2cfe80627f01778 Mon Sep 17 00:00:00 2001 From: Robert Lail Date: Mon, 25 Apr 2011 10:57:37 -0500 Subject: [PATCH] implement bulk actions for errs: resolve, unresolve, delete --- app/controllers/apps_controller.rb | 5 +++-- app/controllers/errs_controller.rb | 122 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------------- app/models/err.rb | 5 ++++- app/views/errs/_table.html.haml | 67 ++++++++++++++++++++++++++++++++++++++----------------------------- config/application.rb | 23 +++++++++++------------ config/routes.rb | 27 +++++++++++++++------------ public/javascripts/application.js | 117 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------------------------------ public/javascripts/underscore-1.1.6.js | 26 ++++++++++++++++++++++++++ public/stylesheets/application.css | 14 ++++++++++++-- spec/controllers/errs_controller_spec.rb | 34 ++++++++++++++++++++++++++++++++++ 10 files changed, 296 insertions(+), 144 deletions(-) create mode 100644 public/javascripts/underscore-1.1.6.js diff --git a/app/controllers/apps_controller.rb b/app/controllers/apps_controller.rb index 89490aa..1cf9e48 100644 --- a/app/controllers/apps_controller.rb +++ b/app/controllers/apps_controller.rb @@ -8,11 +8,12 @@ class AppsController < InheritedResources::Base respond_to do |format| format.html do @all_errs = !!params[:all_errs] - + @errs = resource.errs @errs = @errs.unresolved unless @all_errs @errs = @errs.in_env(params[:environment]).ordered.paginate(:page => params[:page], :per_page => current_user.per_page) - + + @selected_errs = params[:errs] || [] @deploys = @app.deploys.order_by(:created_at.desc).limit(5) end format.atom do diff --git a/app/controllers/errs_controller.rb b/app/controllers/errs_controller.rb index 5c37755..d13abaf 100644 --- a/app/controllers/errs_controller.rb +++ b/app/controllers/errs_controller.rb @@ -1,11 +1,15 @@ class ErrsController < ApplicationController - - before_filter :find_app, :except => [:index, :all] - before_filter :find_err, :except => [:index, :all] - + include ActionView::Helpers::TextHelper + + before_filter :find_app, :except => [:index, :all, :destroy_several, :resolve_several, :unresolve_several] + before_filter :find_err, :except => [:index, :all, :destroy_several, :resolve_several, :unresolve_several] + before_filter :find_selected_errs, :only => [:destroy_several, :resolve_several, :unresolve_several] + + def index app_scope = current_user.admin? ? App.all : current_user.apps @errs = Err.for_apps(app_scope).in_env(params[:environment]).unresolved.ordered + @selected_errs = params[:errs] || [] respond_to do |format| format.html do @errs = @errs.paginate(:page => params[:page], :per_page => current_user.per_page) @@ -13,12 +17,15 @@ class ErrsController < ApplicationController format.atom end end - + + def all app_scope = current_user.admin? ? App.all : current_user.apps + @selected_errs = params[:errs] || [] @errs = Err.for_apps(app_scope).ordered.paginate(:page => params[:page], :per_page => current_user.per_page) end - + + def show page = (params[:notice] || @err.notices_count) page = 1 if page.to_i.zero? @@ -26,10 +33,11 @@ class ErrsController < ApplicationController @notice = @notices.first @comment = Comment.new end - + + def create_issue set_tracker_params - + if @app.issue_tracker @app.issue_tracker.create_issue @err else @@ -41,26 +49,28 @@ class ErrsController < ApplicationController flash[:error] = "There was an error during issue creation. Check your tracker settings or try again later." redirect_to app_err_path(@app, @err) end - + + def unlink_issue @err.update_attribute :issue_link, nil redirect_to app_err_path(@app, @err) end - + + def resolve # Deal with bug in mongoid where find is returning an Enumberable obj @err = @err.first if @err.respond_to?(:first) - + @err.resolve! - + flash[:success] = 'Great news everyone! The err has been resolved.' - + redirect_to :back rescue ActionController::RedirectBackError redirect_to app_path(@app) end - - + + def create_comment @comment = Comment.new(params[:comment].merge(:user_id => current_user.id)) if @comment.valid? @@ -72,7 +82,8 @@ class ErrsController < ApplicationController end redirect_to app_err_path(@app, @err) end - + + def destroy_comment @comment = Comment.find(params[:comment_id]) if @comment.destroy @@ -82,27 +93,62 @@ class ErrsController < ApplicationController end redirect_to app_err_path(@app, @err) end - - - protected - - def find_app - @app = App.find(params[:app_id]) - - # Mongoid Bug: could not chain: current_user.apps.find_by_id! - # apparently finding by 'watchers.email' and 'id' is broken - raise(Mongoid::Errors::DocumentNotFound.new(App,@app.id)) unless current_user.admin? || current_user.watching?(@app) - end - - def find_err - @err = @app.errs.find(params[:id]) - end - - def set_tracker_params - IssueTracker.default_url_options[:host] = request.host - IssueTracker.default_url_options[:port] = request.port - IssueTracker.default_url_options[:protocol] = request.scheme + + + def resolve_several + @selected_errs.each(&:resolve!) + flash[:success] = "Great news everyone! #{pluralize(@selected_errs.count, 'err has', 'errs have')} been resolved." + redirect_to :back + end + + + def unresolve_several + @selected_errs.each(&:unresolve!) + flash[:success] = "#{pluralize(@selected_errs.count, 'err has', 'errs have')} been unresolved." + redirect_to :back + end + + + def destroy_several + @selected_errs.each(&:destroy) + flash[:notice] = "#{pluralize(@selected_errs.count, 'err has', 'errs have')} been deleted." + redirect_to :back + end + + +protected + + + def find_app + @app = App.find(params[:app_id]) + + # Mongoid Bug: could not chain: current_user.apps.find_by_id! + # apparently finding by 'watchers.email' and 'id' is broken + raise(Mongoid::Errors::DocumentNotFound.new(App,@app.id)) unless current_user.admin? || current_user.watching?(@app) + end + + + def find_err + @err = @app.errs.find(params[:id]) + end + + + def set_tracker_params + IssueTracker.default_url_options[:host] = request.host + IssueTracker.default_url_options[:port] = request.port + IssueTracker.default_url_options[:protocol] = request.scheme + end + + + def find_selected_errs + err_ids = (params[:errs] || []).compact + if err_ids.empty? + flash[:notice] = "You have not selected any errors" + redirect_to :back + else + @selected_errs = Array(Err.find(err_ids)) end - + end + + end - diff --git a/app/models/err.rb b/app/models/err.rb index f99e52e..dfdada6 100644 --- a/app/models/err.rb +++ b/app/models/err.rb @@ -41,6 +41,10 @@ class Err self.update_attributes!(:resolved => true) end + def unresolve! + self.update_attributes!(:resolved => false) + end + def unresolved? !resolved? end @@ -56,4 +60,3 @@ class Err end end - diff --git a/app/views/errs/_table.html.haml b/app/views/errs/_table.html.haml index b3e3ff3..40ffba3 100644 --- a/app/views/errs/_table.html.haml +++ b/app/views/errs/_table.html.haml @@ -1,30 +1,39 @@ -%table.errs - %thead - %tr - %th App - %th What & Where - %th Latest - %th Deploy - %th Count - %th Resolve - %tbody - - errs.each do |err| - %tr{:class => err.resolved? ? 'resolved' : 'unresolved'} - %td.app - = link_to err.app.name, app_path(err.app) - - if current_page?(:controller => 'errs') - %span.environment= link_to err.environment, errs_path(:environment => err.environment) - - else - %span.environment= link_to err.environment, app_path(err.app, :environment => err.environment) - %td.message - = link_to err.message, app_err_path(err.app, err) - %em= err.where - %td.latest #{time_ago_in_words(last_notice_at err)} ago - %td.deploy= err.app.last_deploy_at ? err.app.last_deploy_at.to_s(:micro) : 'n/a' - %td.count= link_to err.notices_count, app_err_path(err.app, err) - %td.resolve= link_to image_tag("thumbs-up.png"), resolve_app_err_path(err.app, err), :title => "Resolve", :method => :put, :confirm => err_confirm, :class => 'resolve' if err.unresolved? - - if errs.none? +=form_tag do + %table.errs.selectable + %thead %tr - %td{:colspan => 6} - %em No errs here -= will_paginate @errs, :previous_label => '« Previous', :next_label => 'Next »' + %th + %th App + %th What & Where + %th Latest + %th Deploy + %th Count + %th Resolve + %tbody + - errs.each do |err| + %tr{:class => err.resolved? ? 'resolved' : 'unresolved'} + %td.select + = check_box_tag "errs[]", err.id, @selected_errs.member?(err.id.to_s) + %td.app + = link_to err.app.name, app_path(err.app) + - if current_page?(:controller => 'errs') + %span.environment= link_to err.environment, errs_path(environment: err.environment) + - else + %span.environment= link_to err.environment, app_path(err.app, environment: err.environment) + %td.message + = link_to err.message, app_err_path(err.app, err) + %em= err.where + %td.latest #{time_ago_in_words(last_notice_at err)} ago + %td.deploy= err.app.last_deploy_at ? err.app.last_deploy_at.to_s(:micro) : 'n/a' + %td.count= link_to err.notices.count, app_err_path(err.app, err) + %td.resolve= link_to image_tag("thumbs-up.png"), resolve_app_err_path(err.app, err), :title => "Resolve", :method => :put, :confirm => err_confirm, :class => 'resolve' if err.unresolved? + - if errs.none? + %tr + %td{:colspan => (@app ? 5 : 6)} + %em No errs here + = will_paginate @errs, :previous_label => '« Previous', :next_label => 'Next »' + .tab-bar + %ul + %li= submit_tag 'Resolve', :id => 'resolve_errs', :class => 'button', 'data-action' => resolve_several_errs_path + %li= submit_tag 'Unresolve', :id => 'unresolve_errs', :class => 'button', 'data-action' => unresolve_several_errs_path + %li= submit_tag 'Delete', :id => 'delete_errs', :class => 'button', 'data-action' => destroy_several_errs_path diff --git a/config/application.rb b/config/application.rb index bdf0f12..6fd75fe 100644 --- a/config/application.rb +++ b/config/application.rb @@ -19,43 +19,42 @@ module Errbit # Settings in config/environments/* take precedence over those specified here. # Application configuration should go into files in config/initializers # -- all .rb files in that directory are automatically loaded. - + # Custom directories with classes and modules you want to be autoloadable. config.autoload_paths += [Rails.root.join("app/models/issue_trackers")] - + # Only load the plugins named here, in the order given (default is alphabetical). # :all can be used as a placeholder for all plugins not explicitly named. # config.plugins = [ :exception_notification, :ssl_requirement, :all ] - + # Activate observers that should always be running. # config.active_record.observers = :cacher, :garbage_collector, :forum_observer - + # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. # config.time_zone = 'Central Time (US & Canada)' - + # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] # config.i18n.default_locale = :de - + # JavaScript files you want as :defaults (application.js is always included). - config.action_view.javascript_expansions[:defaults] = %w(jquery rails form) - + config.action_view.javascript_expansions[:defaults] = %w(jquery underscore-1.1.6 rails form) + # > rails generate - config config.generators do |g| g.orm :mongoid g.template_engine :haml g.test_framework :rspec, :fixture => false end - + # IssueTracker subclasses use inheritance, so preloading models provides querying consistency in dev mode. config.mongoid.preload_models = true - + # Configure the default encoding used in templates for Ruby 1.9. config.encoding = "utf-8" - + # Configure sensitive parameters which will be filtered from the log file. config.filter_parameters += [:password] end end - diff --git a/config/routes.rb b/config/routes.rb index c2ae40e..e74afb6 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,38 +1,41 @@ Errbit::Application.routes.draw do - + devise_for :users - + # Hoptoad Notifier Routes match '/notifier_api/v2/notices' => 'notices#create' match '/deploys.txt' => 'deploys#create' - - resources :notices, :only => [:show] - resources :deploys, :only => [:show] + + resources :notices, :only => [:show] + resources :deploys, :only => [:show] resources :users - resources :errs, :only => [:index] do + resources :errs, :only => [:index] do collection do + post :destroy_several + post :resolve_several + post :unresolve_several get :all end end - + resources :apps do resources :errs do resources :notices member do put :resolve + put :unresolve post :create_issue delete :unlink_issue post :create_comment delete :destroy_comment end end - + resources :deploys, :only => [:index] end - + devise_for :users - + root :to => 'apps#index' - + end - diff --git a/public/javascripts/application.js b/public/javascripts/application.js index 0b813eb..1879954 100644 --- a/public/javascripts/application.js +++ b/public/javascripts/application.js @@ -1,51 +1,72 @@ // App JS -$(function(){ - activateTabbedPanels(); - - $('#watcher_name').live("click", function() { - $(this).closest('form').find('.show').removeClass('show'); - $('#app_watchers_attributes_0_user_id').addClass('show'); - }); - - $('#watcher_email').live("click", function() { - $(this).closest('form').find('.show').removeClass('show'); - $('#app_watchers_attributes_0_email').addClass('show'); - }); - - $('a.copy_config').live("click", function() { - $('select.choose_other_app').show().focus(); - }); - $('select.choose_other_app').live("change", function() { - var loc = window.location; - window.location.href = loc.protocol + "//" + loc.host + loc.pathname + - "?copy_attributes_from=" + $(this).val(); - }); -}); - -function activateTabbedPanels() { - $('.tab-bar a').each(function(){ - var tab = $(this); +$(function() { + + function init() { + + activateTabbedPanels(); + + activateSelectableRows(); + + $('#watcher_name').live("click", function() { + $(this).closest('form').find('.show').removeClass('show'); + $('#app_watchers_attributes_0_user_id').addClass('show'); + }); + + $('#watcher_email').live("click", function() { + $(this).closest('form').find('.show').removeClass('show'); + $('#app_watchers_attributes_0_email').addClass('show'); + }); + + $('a.copy_config').live("click", function() { + $('select.choose_other_app').show().focus(); + }); + + $('select.choose_other_app').live("change", function() { + var loc = window.location; + window.location.href = loc.protocol + "//" + loc.host + loc.pathname + + "?copy_attributes_from=" + $(this).val(); + }); + + $('input[type=submit][data-action]').click(function() { + $(this).closest('form').attr('action', $(this).attr('data-action')); + }); + } + + function activateTabbedPanels() { + $('.tab-bar a').each(function(){ + var tab = $(this); + var panel = $('#'+tab.attr('rel')); + panel.addClass('panel'); + panel.find('h3').hide(); + }) + + $('.tab-bar a').click(function(){ + activateTab($(this)); + return(false); + }); + activateTab($('.tab-bar a').first()); + } + + function activateTab(tab) { + tab = $(tab); var panel = $('#'+tab.attr('rel')); - panel.addClass('panel'); - panel.find('h3').hide(); - }) - - $('.tab-bar a').click(function(){ - activateTab($(this)); - return(false); - }); - activateTab($('.tab-bar a').first()); -} - -function activateTab(tab) { - tab = $(tab); - var panel = $('#'+tab.attr('rel')); - - tab.closest('.tab-bar').find('a.active').removeClass('active'); - tab.addClass('active'); - - $('.panel').hide(); - panel.show(); -} - + + tab.closest('.tab-bar').find('a.active').removeClass('active'); + tab.addClass('active'); + + $('.panel').hide(); + panel.show(); + } + + function activateSelectableRows() { + $('.selectable tr').click(function(event) { + if(!_.include(['A', 'INPUT', 'BUTTON', 'TEXTAREA'], event.target.nodeName)) { + var checkbox = $(this).find('input[name="errs[]"]'); + checkbox.attr('checked', !checkbox.is(':checked')); + } + }) + } + + init(); +}); diff --git a/public/javascripts/underscore-1.1.6.js b/public/javascripts/underscore-1.1.6.js new file mode 100644 index 0000000..a13585b --- /dev/null +++ b/public/javascripts/underscore-1.1.6.js @@ -0,0 +1,26 @@ +// Underscore.js 1.1.6 +// (c) 2011 Jeremy Ashkenas, DocumentCloud Inc. +// Underscore is freely distributable under the MIT license. +// Portions of Underscore are inspired or borrowed from Prototype, +// Oliver Steele's Functional, and John Resig's Micro-Templating. +// For all details and documentation: +// http://documentcloud.github.com/underscore +(function(){var p=this,C=p._,m={},i=Array.prototype,n=Object.prototype,f=i.slice,D=i.unshift,E=n.toString,l=n.hasOwnProperty,s=i.forEach,t=i.map,u=i.reduce,v=i.reduceRight,w=i.filter,x=i.every,y=i.some,o=i.indexOf,z=i.lastIndexOf;n=Array.isArray;var F=Object.keys,q=Function.prototype.bind,b=function(a){return new j(a)};typeof module!=="undefined"&&module.exports?(module.exports=b,b._=b):p._=b;b.VERSION="1.1.6";var h=b.each=b.forEach=function(a,c,d){if(a!=null)if(s&&a.forEach===s)a.forEach(c,d);else if(b.isNumber(a.length))for(var e= +0,k=a.length;e=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a, +c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);var e={computed:Infinity};h(a,function(a,b,f){b=c?c.call(d,a,b,f):a;bd?1:0}),"value")};b.sortedIndex=function(a,c,d){d||(d=b.identity);for(var e=0,f=a.length;e>1;d(a[g])=0})})};b.zip=function(){for(var a=f.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c), +e=0;e=0;d--)b=[a[d].apply(this,b)];return b[0]}};b.after=function(a,b){return function(){if(--a<1)return b.apply(this,arguments)}};b.keys=F||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var b=[],d;for(d in a)l.call(a,d)&&(b[b.length]=d);return b};b.values=function(a){return b.map(a, +b.identity)};b.functions=b.methods=function(a){return b.filter(b.keys(a),function(c){return b.isFunction(a[c])}).sort()};b.extend=function(a){h(f.call(arguments,1),function(b){for(var d in b)b[d]!==void 0&&(a[d]=b[d])});return a};b.defaults=function(a){h(f.call(arguments,1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,c){if(a===c)return!0;var d=typeof a;if(d!= +typeof c)return!1;if(a==c)return!0;if(!a&&c||a&&!c)return!1;if(a._chain)a=a._wrapped;if(c._chain)c=c._wrapped;if(a.isEqual)return a.isEqual(c);if(b.isDate(a)&&b.isDate(c))return a.getTime()===c.getTime();if(b.isNaN(a)&&b.isNaN(c))return!1;if(b.isRegExp(a)&&b.isRegExp(c))return a.source===c.source&&a.global===c.global&&a.ignoreCase===c.ignoreCase&&a.multiline===c.multiline;if(d!=="object")return!1;if(a.length&&a.length!==c.length)return!1;d=b.keys(a);var e=b.keys(c);if(d.length!=e.length)return!1; +for(var f in a)if(!(f in c)||!b.isEqual(a[f],c[f]))return!1;return!0};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(l.call(a,c))return!1;return!0};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=n||function(a){return E.call(a)==="[object Array]"};b.isArguments=function(a){return!(!a||!l.call(a,"callee"))};b.isFunction=function(a){return!(!a||!a.constructor||!a.call||!a.apply)};b.isString=function(a){return!!(a===""||a&&a.charCodeAt&&a.substr)}; +b.isNumber=function(a){return!!(a===0||a&&a.toExponential&&a.toFixed)};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===!0||a===!1};b.isDate=function(a){return!(!a||!a.getTimezoneOffset||!a.setUTCFullYear)};b.isRegExp=function(a){return!(!a||!a.test||!a.exec||!(a.ignoreCase||a.ignoreCase===!1))};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.noConflict=function(){p._=C;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e= +0;e/g,interpolate:/<%=([\s\S]+?)%>/g};b.template=function(a,c){var d=b.templateSettings;d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.interpolate,function(a,b){return"',"+b.replace(/\\'/g,"'")+",'"}).replace(d.evaluate|| +null,function(a,b){return"');"+b.replace(/\\'/g,"'").replace(/[\r\n\t]/g," ")+"__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');";d=new Function("obj",d);return c?d(c):d};var j=function(a){this._wrapped=a};b.prototype=j.prototype;var r=function(a,c){return c?b(a).chain():a},H=function(a,c){j.prototype[a]=function(){var a=f.call(arguments);D.call(a,this._wrapped);return r(c.apply(b,a),this._chain)}};b.mixin(b);h(["pop","push","reverse","shift","sort", +"splice","unshift"],function(a){var b=i[a];j.prototype[a]=function(){b.apply(this._wrapped,arguments);return r(this._wrapped,this._chain)}});h(["concat","join","slice"],function(a){var b=i[a];j.prototype[a]=function(){return r(b.apply(this._wrapped,arguments),this._chain)}});j.prototype.chain=function(){this._chain=!0;return this};j.prototype.value=function(){return this._wrapped}})(); \ No newline at end of file diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css index 3123fea..b79ea70 100644 --- a/public/stylesheets/application.css +++ b/public/stylesheets/application.css @@ -244,7 +244,10 @@ a.action { float: right; font-size: 0.9em;} } /* Forms */ -form { +form#new_user, +form.edit_user, +form#new_app, +form.edit_app { width: 620px; } form > div, form fieldset > div { margin: 1em 0;} @@ -289,7 +292,11 @@ 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 input[type=submit].button { + font-size: 1em; + text-transform: none; +} +form div.buttons { color: #666; background: #FFF url(images/button-bg.png) 0 bottom repeat-x; border-radius: 50px; @@ -477,6 +484,7 @@ pre { } /* Buttons */ +input[type="submit"].button, a.button { display: inline-block; padding: 0 0.8em; @@ -492,6 +500,7 @@ a.button { -webkit-box-shadow: inset 0px 0px 4px #FFF; line-height: 30px; } +input[type="submit"]:hover.button, a:hover.button { box-shadow: 0px 0px 4px #ccc; -moz-box-shadow: 0px 0px 4px #ccc; @@ -508,6 +517,7 @@ a.button.active { -webkit-box-shadow: inset 0 0 5px #999; } + /* Tab Bar */ .tab-bar { margin-bottom: 24px; diff --git a/spec/controllers/errs_controller_spec.rb b/spec/controllers/errs_controller_spec.rb index 39b4a6b..b437570 100644 --- a/spec/controllers/errs_controller_spec.rb +++ b/spec/controllers/errs_controller_spec.rb @@ -410,5 +410,39 @@ describe ErrsController do end end + describe "Bulk Actions" do + before(:each) do + sign_in Factory(:admin) + @err1 = Factory(:err, :resolved => true) + @err2 = Factory(:err, :resolved => false) + end + + it "should apply to multiple errs" do + post :resolve_several, :errs => [@err1.id.to_s, @err2.id.to_s] + assigns(:selected_errs).should == [@err1, @err2] + end + + context "POST /errs/resolve_several" do + it "should resolve the issue" do + post :resolve_several, :errs => [@err2.id.to_s] + @err2.reload.resolved?.should == true + end + end + + context "POST /errs/unresolve_several" do + it "should unresolve the issue" do + post :unresolve_several, :errs => [@err1.id.to_s] + @err1.reload.resolved?.should == false + end + end + + context "POST /errs/destroy_several" do + it "should delete the errs" do + lambda { + post :destroy_several, :errs => [@err1.id.to_s] + }.should change(Err, :count).by(-1) + end + end + end end -- libgit2 0.21.2