diff options
23 files changed, 670 insertions, 82 deletions
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index f7ca1ec..23d7fef 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -16,5 +16,10 @@ //= require users //= require_tree . //= require bootstrap +//= require jquery.pjax + +$(function() { + $('a:not([data-remote]):not([data-behavior]):not([data-skip-pjax])').pjax('[data-pjax-container]'); +}); //= require rails.validations //= require rails.validations.simple_form diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml index 9e68674..dd7e5aa 100644 --- a/app/views/home/index.html.haml +++ b/app/views/home/index.html.haml @@ -1,11 +1,14 @@ +%h1 spacer for firefox +%h1 spacer for firefox Try to fetch a = link_to "cert", cert_path %p -Try to create a +Create a = link_to "ticket", new_ticket_path -%p -See all -= link_to "tickets", tickets_path +- if logged_in? + %p + See all + = link_to "tickets", tickets_path diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index e6d22f0..bd03477 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -19,5 +19,6 @@ .row .span12 = render 'layouts/messages' + %div{"data-pjax-container" => ""} = yield %footer diff --git a/core/lib/leap_web_core/dependencies.rb b/core/lib/leap_web_core/dependencies.rb index 7f6ca87..00ef515 100644 --- a/core/lib/leap_web_core/dependencies.rb +++ b/core/lib/leap_web_core/dependencies.rb @@ -11,6 +11,7 @@ module LeapWebCore "haml" => "~> 3.1.7", "bootstrap-sass" => "~> 2.0.4", "jquery-rails" => nil, + "pjax_rails" => nil, "simple_form" => nil } diff --git a/core/lib/leap_web_core/ui_dependencies.rb b/core/lib/leap_web_core/ui_dependencies.rb index e0a0b86..8ca9b91 100644 --- a/core/lib/leap_web_core/ui_dependencies.rb +++ b/core/lib/leap_web_core/ui_dependencies.rb @@ -2,6 +2,7 @@ require "haml" require "bootstrap-sass" require "jquery-rails" require "simple_form" +require "pjax_rails" if Rails.env == "development" require "haml-rails" diff --git a/help/app/controllers/tickets_controller.rb b/help/app/controllers/tickets_controller.rb index b5f3a63..3ff19b8 100644 --- a/help/app/controllers/tickets_controller.rb +++ b/help/app/controllers/tickets_controller.rb @@ -3,6 +3,10 @@ class TicketsController < ApplicationController respond_to :html #, :json #has_scope :open, :type => boolean + before_filter :set_strings + + before_filter :authorize, :only => [:index] + def new @ticket = Ticket.new @ticket.comments.build @@ -10,15 +14,17 @@ class TicketsController < ApplicationController def create @ticket = Ticket.new(params[:ticket]) - if current_user + if logged_in? @ticket.created_by = current_user.id @ticket.email = current_user.email if current_user.email @ticket.comments.last.posted_by = current_user.id else @ticket.comments.last.posted_by = nil #hacky, but protecting this attribute doesn't work right, so this should make sure it isn't set. end - flash[:notice] = 'Ticket was successfully created.' if @ticket.save + if !logged_in? + flash[:notice] = flash[:notice] + ' You can later access this ticket at the url ' + request.protocol + request.host_with_port + ticket_path(@ticket.id) + '. You might want to bookmark this page to find it again. Anybody with this URL will be able to access this ticket, so if you are on a shared computer you might want to remove it from the browser history' #todo + end respond_with(@ticket) end @@ -33,34 +39,78 @@ class TicketsController < ApplicationController def show @ticket = Ticket.find(params[:id]) + if !@ticket + redirect_to tickets_path, :alert => "No such ticket" + return + end + ticket_access_denied? #authorize_ticket_access # @ticket.comments.build # build ticket comments? end def update @ticket = Ticket.find(params[:id]) - @ticket.attributes = params[:ticket] - @ticket.comments.last.posted_by = (current_user ? current_user.id : nil) #protecting posted_by isn't working, so this should protect it. + if !ticket_access_denied? + if status = params[:change_status] #close or open button was pressed + @ticket.close if params[:change_status] == 'close' + @ticket.reopen if params[:change_status] == 'open' + else + params[:ticket][:comments_attributes] = nil if params[:ticket][:comments_attributes].values.first[:body].blank? #unset comments hash if no new comment was typed + @ticket.attributes = params[:ticket] #this will call comments_attributes= + # @ticket.is_open = false if params[:commit] == @reply_close_str #this overrides is_open selection + @ticket.close if params[:commit] == @reply_close_str #this overrides is_open selection - if @ticket.save - flash[:notice] = 'Ticket was successfully updated.' - respond_with @ticket - else - #redirect_to [:show, @ticket] # - flash[:alert] = 'Ticket has not been changed' - redirect_to @ticket - #respond_with(@ticket) # why does this go to edit?? redirect??? + # what if there is an update and no new comment? Confirm that there is a new comment to update posted_by: + @ticket.comments.last.posted_by = (current_user ? current_user.id : nil) if @ticket.comments_changed? #protecting posted_by isn't working, so this should protect it. + end + if @ticket.changed? and @ticket.save + flash[:notice] = 'Ticket was successfully updated.' + if @ticket.is_open + respond_with @ticket + else #for closed tickets, redirect to index. + redirect_to tickets_path + end + else + #redirect_to [:show, @ticket] # + flash[:alert] = 'Ticket has not been changed' + redirect_to @ticket + #respond_with(@ticket) # why does this go to edit?? redirect??? + end end end def index - # @tickets = Ticket.by_title #not actually what we will want - respond_with(@tickets = Ticket.all) #we'll want only tickets that this user can access + #TODO: we will need pagination + @all_tickets = Ticket.for_user(current_user, params, admin?) #for tests, useful to have as separate variable + + #below works if @tickets is a CouchRest::Model::Designs::View, but not if it is an Array + @tickets = @all_tickets.page(params[:page]).per(10) #TEST + #respond_with(@tickets) + end + + def destroy + @ticket = Ticket.find(params[:id]) + @ticket.destroy if admin? + redirect_to tickets_path end private + def ticket_access? + @ticket and (admin? or !@ticket.created_by or (current_user and current_user.id == @ticket.created_by)) + end + + def ticket_access_denied? + access_denied unless ticket_access? + end + + + def set_strings + @post_reply_str = 'Post reply' #t :post_reply + @reply_close_str = 'Reply and close' #t :reply_and_close + end + # not using now, as we are using comment_attributes= from the Ticket model =begin def add_comment diff --git a/help/app/helpers/tickets_helper.rb b/help/app/helpers/tickets_helper.rb new file mode 100644 index 0000000..bd2c069 --- /dev/null +++ b/help/app/helpers/tickets_helper.rb @@ -0,0 +1,43 @@ +module TicketsHelper + + def status + params[:open_status] || 'open' + end + + def admin + # do we not want this set for non-admins? the param will be viewable in the url + params[:admin_status] || 'all' + end + + def order + params[:sort_order] || 'updated_at_desc' + end + + def link_to_status(new_status) + label = new_status + ' issues' + link_to label, :open_status => new_status, :admin_status => admin, :sort_order => order + end + + def link_to_order(order_field) + if order.start_with?(order_field) + # link for currently-filtered field. Link to other direction of this field. + if order.end_with? 'asc' + direction = 'desc' + icon_direction = 'up' + else + direction = 'asc' + icon_direction = 'down' + end + arrow = content_tag(:i, '', class: 'icon-arrow-'+ icon_direction) + else + # for not-currently-filtered field, don't display an arrow, and link to descending direction + arrow = '' + direction = 'desc' + end + + link_to :sort_order => order_field + '_at_' + direction, :open_status => status, :admin_status => admin do + arrow + order_field + ' at' + end + end + +end diff --git a/help/app/models/ticket.rb b/help/app/models/ticket.rb index f38fed2..cd4fcfb 100644 --- a/help/app/models/ticket.rb +++ b/help/app/models/ticket.rb @@ -20,23 +20,109 @@ class Ticket < CouchRest::Model::Base #also, both created_by and regarding_user could be nil---say user forgets username, or has general question property :title, String property :email, String #verify - + #property :user_verified, TrueClass, :default => false #will be true exactly when user is set #admins - property :code, String, :protected => true # only should be set if created_by is nil + #property :code, String, :protected => true # only should be set if created_by is nil #instead we will just use couchdb ID property :is_open, TrueClass, :default => true property :comments, [TicketComment] timestamps! - + #before_validation :set_created_by, :set_code, :set_email, :on => :create - before_validation :set_code, :set_email, :on => :create + before_validation :set_email, :on => :create #named_scope :open, :conditions => {:is_open => true} #?? design do - view :by_title + #TODO--clean this all up + view :by_is_open + view :by_created_by + + view :by_updated_at + view :by_created_at + + view :by_is_open_and_created_by + view :by_is_open_and_created_at + view :by_is_open_and_updated_at + + view :includes_post_by, + :map => + "function(doc) { + var arr = {} + if (doc['type'] == 'Ticket' && doc.comments) { + doc.comments.forEach(function(comment){ + if (comment.posted_by && !arr[comment.posted_by]) { + //don't add duplicates + arr[comment.posted_by] = true; + emit(comment.posted_by, 1); + } + }); + } + }", :reduce => "function(k,v,r) { return sum(v); }" + + view :includes_post_by_and_open_status_and_updated_at, + :map => + "function(doc) { + var arr = {} + if (doc['type'] == 'Ticket' && doc.comments) { + doc.comments.forEach(function(comment){ + if (comment.posted_by && !arr[comment.posted_by]) { + //don't add duplicates + arr[comment.posted_by] = true; + emit([comment.posted_by, doc.is_open, doc.updated_at], 1); + } + }); + } + }", :reduce => "function(k,v,r) { return sum(v); }" + + view :includes_post_by_and_open_status_and_created_at, + :map => + "function(doc) { + var arr = {} + if (doc['type'] == 'Ticket' && doc.comments) { + doc.comments.forEach(function(comment){ + if (comment.posted_by && !arr[comment.posted_by]) { + //don't add duplicates + arr[comment.posted_by] = true; + emit([comment.posted_by, doc.is_open, doc.created_at], 1); + } + }); + } + }", :reduce => "function(k,v,r) { return sum(v); }" + + view :includes_post_by_and_updated_at, + :map => + "function(doc) { + var arr = {} + if (doc['type'] == 'Ticket' && doc.comments) { + doc.comments.forEach(function(comment){ + if (comment.posted_by && !arr[comment.posted_by]) { + //don't add duplicates + arr[comment.posted_by] = true; + emit([comment.posted_by, doc.updated_at], 1); + } + }); + } + }", :reduce => "function(k,v,r) { return sum(v); }" + + + view :includes_post_by_and_created_at, + :map => + "function(doc) { + var arr = {} + if (doc['type'] == 'Ticket' && doc.comments) { + doc.comments.forEach(function(comment){ + if (comment.posted_by && !arr[comment.posted_by]) { + //don't add duplicates + arr[comment.posted_by] = true; + emit([comment.posted_by, doc.created_at], 1); + } + }); + } + }", :reduce => "function(k,v,r) { return sum(v); }" + end validates :title, :presence => true @@ -45,20 +131,83 @@ class Ticket < CouchRest::Model::Base # html5 has built-in validation which isn't ideal, as it says 'please enter an email address' for invalid email addresses, which implies an email address is required, and it is not. validates :email, :format => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/, :if => :email #email address is optional - + #TODO: #def set_created_by # self.created_by = User.current if User.current #end - + + def self.for_user(user, options = {}, is_admin = false) + + # TODO: This is obviously super tedious. we will refactor later. + # TODO: thought i should reverse keys for descending, but that didn't work. look into whether that should be tweaked, and whether it works okay with pagination (seems to now...) + # TODO: Time.now + 2.days is to catch tickets created in future. shouldn't happen but does on my computer now, so this at least catches for now. + # TODO handle default values correctly: + options[:open_status] = 'open' if !options[:open_status] #hacky. redo this when handling defaults correctly + + if (is_admin && (options[:admin_status] != 'mine')) + # show all (selected) tickets to admin + if options[:open_status] == 'all' + if options[:sort_order] == 'created_at_desc' + Ticket.by_created_at.startkey(0).endkey(Time.now + 2.days).descending + elsif options[:sort_order] == 'updated_at_asc' + Ticket.by_updated_at.startkey(0).endkey(Time.now + 2.days) + elsif options[:sort_order] == 'created_at_asc' + Ticket.by_created_at.startkey(0).endkey(Time.now + 2.days) + else + Ticket.by_updated_at.startkey(0).endkey(Time.now + 2.days).descending + end + else + if options[:sort_order] == 'created_at_desc' + Ticket.by_is_open_and_created_at.startkey([(options[:open_status] == 'open'), 0]).endkey([(options[:open_status] == 'open'), Time.now + 2.days]).descending + elsif options[:sort_order] == 'updated_at_asc' + Ticket.by_is_open_and_updated_at.startkey([(options[:open_status] == 'open'), 0]).endkey([(options[:open_status] == 'open'), Time.now + 2.days]) + elsif options[:sort_order] == 'created_at_asc' + Ticket.by_is_open_and_created_at.startkey([(options[:open_status] == 'open'), 0]).endkey([(options[:open_status] == 'open'), Time.now + 2.days]) + else + Ticket.by_is_open_and_updated_at.startkey([(options[:open_status] == 'open'), 0]).endkey([(options[:open_status] == 'open'), Time.now + 2.days]).descending + end + end + else + # only show tickets this user has commented on, as user is non-admin or admin viewing only their tickets + if options[:open_status] == 'all' + if options[:sort_order] == 'created_at_desc' + Ticket.includes_post_by_and_created_at.startkey([user.id, 0]).endkey([user.id, Time.now + 2.days]).descending + elsif options[:sort_order] == 'updated_at_asc' + Ticket.includes_post_by_and_updated_at.startkey([user.id, 0]).endkey([user.id, Time.now + 2.days]) + elsif options[:sort_order] == 'created_at_asc' + Ticket.includes_post_by_and_created_at.startkey([user.id, 0]).endkey([user.id, Time.now + 2.days]) + else + Ticket.includes_post_by_and_updated_at.startkey([user.id, 0]).endkey([user.id, Time.now + 2.days]).descending + end + else + if options[:sort_order] == 'created_at_desc' + Ticket.includes_post_by_and_open_status_and_created_at.startkey([user.id, (options[:open_status] == 'open'), 0]).endkey([user.id, (options[:open_status] == 'open'), Time.now + 2.days]).descending + elsif options[:sort_order] == 'updated_at_asc' + Ticket.includes_post_by_and_open_status_and_updated_at.startkey([user.id, (options[:open_status] == 'open'), 0]).endkey([user.id, (options[:open_status] == 'open'), Time.now + 2.days]) + elsif options[:sort_order] == 'created_at_asc' + Ticket.includes_post_by_and_open_status_and_created_at.startkey([user.id, (options[:open_status] == 'open'), 0]).endkey([user.id, (options[:open_status] == 'open'), Time.now + 2.days]) + else + Ticket.includes_post_by_and_open_status_and_updated_at.startkey([user.id, (options[:open_status] == 'open'), 0]).endkey([user.id, (options[:open_status] == 'open'), Time.now + 2.days]).descending + end + end + end + end + + def self.tickets_by_commenter(user_id)#, options = {}) + Ticket.includes_post_by_and_updated_at.startkey([user_id, 0]).endkey([user_id, Time.now]) + end + def is_creator_validated? !!created_by end - def set_code +=begin + def set_code #let's not use this---can use same show url # ruby 1.9 provides url-safe option---this is not necessarily url-safe self.code = SecureRandom.hex(8) if !is_creator_validated? end +=end def set_email @@ -66,23 +215,41 @@ class Ticket < CouchRest::Model::Base # in controller set to be current users email if that exists end + #not saving with close and reopen, as we will save in update when they are called. def close self.is_open = false - save + #save end def reopen self.is_open = true - save + #save end - def comments_attributes=(attributes) + def commenters + commenters = [] + self.comments.each do |comment| + if comment.posted_by + if user = User.find(comment.posted_by) + commenters << user.login if user and !commenters.include?(user.login) + else + commenters << 'unknown user' if !commenters.include?('unknown user') #todo don't hardcode string 'unknown user' + end + else + commenters << 'unauthenticated user' if !commenters.include?('unauthenticated user') #todo don't hardcode string 'unauthenticated user' + end + end + commenters.join(', ') + end - comment = TicketComment.new(attributes.values.first) #TicketComment.new(attributes) - #comment.posted_by = User.current.id if User.current #we want to avoid User.current, and current_user won't work here. instead will set in tickets_controller - comment.posted_at = Time.now - comments << comment - + def comments_attributes=(attributes) + if attributes # could be empty as we will empty if nothing was typed in + comment = TicketComment.new(attributes.values.first) #TicketComment.new(attributes) + #comment.posted_by = User.current.id if User.current #we want to avoid User.current, and current_user won't work here. instead will set in tickets_controller + # what about: comment.posted_by = self.updated_by (will need to add ticket.updated_by) + comment.posted_at = Time.now + comments << comment + end end =begin @@ -91,5 +258,5 @@ class Ticket < CouchRest::Model::Base errors.add 'email', 'contains an invalid address' end end -=end +=end end diff --git a/help/app/views/tickets/_admin-nav.html.haml b/help/app/views/tickets/_admin-nav.html.haml new file mode 100644 index 0000000..0e45c40 --- /dev/null +++ b/help/app/views/tickets/_admin-nav.html.haml @@ -0,0 +1,5 @@ +%ul.nav.nav-pills.nav-stacked + %li{:class => ("active" if admin == 'mine')} + = link_to 'tickets i admin', {:admin_status => 'mine', :open_status => status, :sort_order => order} + %li{:class => ("active" if admin == 'all')} + = link_to 'all tickets', {:admin_status => 'all', :open_status => status, :sort_order => order} diff --git a/help/app/views/tickets/_order-nav.html.haml b/help/app/views/tickets/_order-nav.html.haml new file mode 100644 index 0000000..9e8bcee --- /dev/null +++ b/help/app/views/tickets/_order-nav.html.haml @@ -0,0 +1,5 @@ +%ul.nav.nav-pills.pull-right + %li{:class=> ("active" if order.start_with? 'created_at' )} + = link_to_order('created') + %li{:class=> ("active" if order.start_with? 'updated_at' )} + = link_to_order('updated') diff --git a/help/app/views/tickets/_status-nav.html.haml b/help/app/views/tickets/_status-nav.html.haml new file mode 100644 index 0000000..69f4248 --- /dev/null +++ b/help/app/views/tickets/_status-nav.html.haml @@ -0,0 +1,7 @@ +%ul.nav.nav-tabs + %li{:class => ("active" if status == 'open')} + = link_to_status 'open' + %li{:class => ("active" if status == 'closed')} + = link_to_status 'closed' + %li{:class => ("active" if status == 'all')} + = link_to_status 'all' diff --git a/help/app/views/tickets/_table-nav.html.haml b/help/app/views/tickets/_table-nav.html.haml new file mode 100644 index 0000000..635b59b --- /dev/null +++ b/help/app/views/tickets/_table-nav.html.haml @@ -0,0 +1,5 @@ +.row + .span6 + = render 'tickets/status-nav' + .span4 + = render 'tickets/order-nav' diff --git a/help/app/views/tickets/_ticket.html.haml b/help/app/views/tickets/_ticket.html.haml new file mode 100644 index 0000000..3edfa8b --- /dev/null +++ b/help/app/views/tickets/_ticket.html.haml @@ -0,0 +1,13 @@ +%tr + %td + %b + = link_to ticket.title, ticket + %br + %small + created: + = ticket.created_at.to_s(:short) + updated: + = ticket.updated_at.to_s(:short) + %small.pull-right + comments by: + = ticket.commenters diff --git a/help/app/views/tickets/index.html.haml b/help/app/views/tickets/index.html.haml index 6db2140..fdbeec5 100644 --- a/help/app/views/tickets/index.html.haml +++ b/help/app/views/tickets/index.html.haml @@ -1,10 +1,22 @@ -%h2 tickets index (just as space) +%h1 tickets index + Create a = link_to "new ticket", new_ticket_path -= # below shouldn't be unless logged in -%h2 Tickets -= # want to have selection option to see tickets, that are open, closed or all -- @tickets.each do |ticket| - %p - = link_to ticket.title, ticket -= #render(:partial => "ticket", :collection => @tickets) + += #%div{"data-pjax-container" => ""} # not sure how to get this working right +.row + .span2 + - if admin? + = render 'tickets/admin-nav' + .span10 + = render 'tickets/table-nav' + %table.table-striped.table-bordered.table-hover{:style => "width:100%;"} + %tbody + = render @tickets.all + = paginate @tickets + +%div{"data-pjax-container" => ""} + / PJAX updates will go here + hmmm + + diff --git a/help/app/views/tickets/show.html.haml b/help/app/views/tickets/show.html.haml index a9b994e..d9f594b 100644 --- a/help/app/views/tickets/show.html.haml +++ b/help/app/views/tickets/show.html.haml @@ -1,26 +1,37 @@ -- if flash[:notice] - =flash[:notice] -- if flash[:alert] - =flash[:alert] %h2= @ticket.title -is open? -= @ticket.is_open -- if @ticket.code - code: - = @ticket.code - if @ticket.email email: = @ticket.email -- if User.find(@ticket.created_by) - Created by - = User.find(@ticket.created_by).login -- else - Unauthenticated ticket creator +%li + - if User.find(@ticket.created_by) + Created by + = User.find(@ticket.created_by).login + - else + Unauthenticated ticket creator +%li + = "status:" + - if @ticket.is_open + = 'open' + = #link_to 'close', ticket_path, :method => :put + = #button_to 'close', ticket_path, :method => :put + = button_to 'close', {:change_status => :close}, :method => :put + - else + = 'closed' + = button_to 'open', {:change_status => :open}, :method => :put = render(:partial => "comment", :collection => @ticket.comments) += #render @ticket.comments should work if view is in /app/views/comments/_comment -= simple_form_for (@ticket, :html => {:novalidate => true}) do |f| #turn off html5 validations to test += simple_form_for(@ticket, :html => {:novalidate => true}) do |f| #turn off html5 validations to test = f.simple_fields_for :comments, TicketComment.new do |c| = c.input :body, :label => 'Comment', :as => :text = #render :partial => 'new_comment' - = f.button :submit - = link_to t(:cancel), tickets_path, :class => :btn
\ No newline at end of file + = #f.label :is_open + = #f.select :is_open, [true, false] #remove + = f.button :submit, @post_reply_str + - if @ticket.is_open + = f.button :submit, @reply_close_str += #link_to t(:destroy), ticket_path, :confirm => 'are you sure?', :method => :delete, :class => :btn if admin? # for link_to to work with delete, need to figure out jquery interaction correctly. see http://stackoverflow.com/questions/3774925/delete-link-sends-get-instead-of-delete-in-rails-3-view etc.. += button_to 'destroy', ticket_path, :confirm => 'are you sure?', :method => :delete if admin? += # TODO want to have button to close += # TODO if admin, have button to delete += link_to t(:cancel), tickets_path, :class => :btn diff --git a/help/config/routes.rb b/help/config/routes.rb index 5e57e02..86a9201 100644 --- a/help/config/routes.rb +++ b/help/config/routes.rb @@ -1,5 +1,5 @@ Rails.application.routes.draw do - resources :tickets, :only => [:new, :create, :index, :show, :update] + resources :tickets, :only => [:new, :create, :index, :show, :update, :destroy] #resources :ticket, :only => [:show] end diff --git a/help/test/functional/tickets_controller_test.rb b/help/test/functional/tickets_controller_test.rb index b9e03ac..592bd8c 100644 --- a/help/test/functional/tickets_controller_test.rb +++ b/help/test/functional/tickets_controller_test.rb @@ -2,38 +2,83 @@ require 'test_helper' class TicketsControllerTest < ActionController::TestCase - test "should get index" do + setup do + User.create(User.valid_attributes_hash.merge({:login => 'first_test'})) + User.create(User.valid_attributes_hash.merge({:login => 'different'})) + Ticket.create( {:title => "stub test ticket", :id => 'stubtestticketid', :comments_attributes => {"0" => {"body" =>"body of stubbed test ticket"}}}) + Ticket.create( {:title => "stub test ticket two", :id => 'stubtestticketid2', :comments_attributes => {"0" => {"body" =>"body of second stubbed test ticket"}}}) + end + + teardown do + User.find_by_login('first_test').destroy + User.find_by_login('different').destroy + Ticket.find('stubtestticketid').destroy + Ticket.find('stubtestticketid2').destroy + end + + test "should get index if logged in" do + login :is_admin? => false get :index assert_response :success assert_not_nil assigns(:tickets) end + test "no index if not logged in" do + get :index + assert_response :redirect + assert_nil assigns(:tickets) + end + test "should get new" do get :new assert_equal Ticket, assigns(:ticket).class assert_response :success end + test "ticket show access" do + ticket = Ticket.first + ticket.created_by = nil # TODO: hacky, but this makes sure this ticket is an unauthenticated one + ticket.save + get :show, :id => ticket.id + assert_response :success + + ticket.created_by = User.last.id + ticket.save + get :show, :id => ticket.id + assert_response :redirect + assert_redirected_to login_url + + login(User.last) + get :show, :id => ticket.id + assert_response :success + + login(User.first) #assumes User.first != User.last: + assert_not_equal User.first, User.last + get :show, :id => ticket.id + assert_response :redirect + assert_redirected_to root_url + + end test "should create unauthenticated ticket" do - params = {:title => "ticket test title", :comments_attributes => {"0" => {"body" =>"body of test ticket"}}} + params = {:title => "unauth ticket test title", :comments_attributes => {"0" => {"body" =>"body of test ticket"}}} assert_difference('Ticket.count') do post :create, :ticket => params end assert_response :redirect - #assert_equal assigns(:ticket).email, User.current.email - #assert_equal User.find(assigns(:ticket).created_by).login, User.current.login assert_nil assigns(:ticket).created_by assert_equal 1, assigns(:ticket).comments.count - end + assert_nil assigns(:ticket).comments.first.posted_by + assigns(:ticket).destroy # destroys without checking permission. is that okay? + end test "should create authenticated ticket" do - params = {:title => "ticket test title", :comments_attributes => {"0" => {"body" =>"body of test ticket"}}} + params = {:title => "auth ticket test title", :comments_attributes => {"0" => {"body" =>"body of test ticket"}}} login :email => "test@email.net" @@ -42,23 +87,188 @@ class TicketsControllerTest < ActionController::TestCase end assert_response :redirect - ticket = assigns(:ticket) - assert ticket - assert_equal @current_user.id, ticket.created_by - assert_equal @current_user.email, ticket.email + + assert_not_nil assigns(:ticket).created_by + assert_equal assigns(:ticket).created_by, @current_user.id + assert_equal assigns(:ticket).email, @current_user.email assert_equal 1, assigns(:ticket).comments.count + assert_not_nil assigns(:ticket).comments.first.posted_by + assert_equal assigns(:ticket).comments.first.posted_by, @current_user.id + assigns(:ticket).destroy end - test "add comment to ticket" do + test "add comment to unauthenticated ticket" do + ticket = Ticket.find('stubtestticketid') + ticket.created_by = nil # TODO: hacky, but this makes sure this ticket is an unauthenticated one + ticket.save - ticket = Ticket.last - assert_difference('Ticket.last.comments.count') do + assert_difference('Ticket.find("stubtestticketid").comments.count') do put :update, :id => ticket.id, :ticket => {:comments_attributes => {"0" => {"body" =>"NEWER comment"}} } end - assert_equal ticket, assigns(:ticket) + + assert_equal ticket, assigns(:ticket) # still same ticket, with different comments + assert_not_equal ticket.comments, assigns(:ticket).comments # ticket == assigns(:ticket), but they have different comments (which we want) + + end + + + test "add comment to own authenticated ticket" do + + login User.last + ticket = Ticket.find('stubtestticketid') + ticket.created_by = User.last.id # TODO: hacky, but confirms it is their ticket + ticket.save + + #they should be able to comment if it is their ticket: + assert_difference('Ticket.find("stubtestticketid").comments.count') do + put :update, :id => ticket.id, + :ticket => {:comments_attributes => {"0" => {"body" =>"NEWER comment"}} } + end + assert_not_equal ticket.comments, assigns(:ticket).comments + assert_not_nil assigns(:ticket).comments.last.posted_by + assert_equal assigns(:ticket).comments.last.posted_by, @current_user.id + + end + + + test "cannot comment if it is not your ticket" do + + login :is_admin? => false, :email => nil + ticket = Ticket.first + + assert_not_nil User.first.id + ticket.created_by = User.first.id + ticket.save + # they should *not* be able to comment if it is not their ticket + put :update, :id => ticket.id, :ticket => {:comments_attributes => {"0" => {"body" =>"not allowed comment"}} } + assert_response :redirect + assert_access_denied + + assert_equal ticket.comments, assigns(:ticket).comments + + end + + + test "admin add comment to authenticated ticket" do + + login :is_admin? => true + + ticket = Ticket.find('stubtestticketid') + assert_not_nil User.last.id + ticket.created_by = User.last.id # TODO: hacky, but confirms it somebody elses ticket: + assert_not_equal User.last.id, @current_user.id + ticket.save + + #admin should be able to comment: + assert_difference('Ticket.find("stubtestticketid").comments.count') do + put :update, :id => ticket.id, + :ticket => {:comments_attributes => {"0" => {"body" =>"NEWER comment"}} } + end + assert_not_equal ticket.comments, assigns(:ticket).comments + assert_not_nil assigns(:ticket).comments.last.posted_by + assert_equal assigns(:ticket).comments.last.posted_by, @current_user.id + + end + + test "tickets by admin" do + + login :is_admin? => true, :email => nil + + get :index, {:admin_status => "mine", :open_status => "open"} + assert assigns(:all_tickets).count > 1 # at least 2 tickets + + # if we close one ticket, the admin should have 1 less open ticket they admin + assert_difference('assigns[:all_tickets].count', -1) do + assigns(:tickets).first.close + assigns(:tickets).first.save + get :index, {:admin_status => "mine", :open_status => "open"} + end + + testticket = Ticket.create :title => 'temp testytest' + + # test admin_status 'mine' vs 'all' + get :index, {:admin_status => "all", :open_status => "open"} + assert assigns(:all_tickets).include?(testticket) + get :index, {:admin_status => "mine", :open_status => "open"} + assert !assigns(:all_tickets).include?(testticket) + + # admin should have one more ticket if a new tick gets an admin comment + assert_difference('assigns[:all_tickets].count') do + put :update, :id => testticket.id, :ticket => {:comments_attributes => {"0" => {"body" =>"NEWER comment"}}} + get :index, {:admin_status => "mine", :open_status => "open"} + end + + assert assigns(:all_tickets).include?(assigns(:ticket)) + assert_not_nil assigns(:ticket).comments.last.posted_by + assert_equal assigns(:ticket).comments.last.posted_by, @current_user.id + + assigns(:ticket).destroy + + # test ordering + + get :index, {:admin_status => "mine", :open_status => "open", :sort_order => 'created_at_desc'} + + # this will consider all tickets, not just those on first page + first_tick = assigns(:all_tickets).all.first + last_tick = assigns(:all_tickets).all.last + assert first_tick.created_at > last_tick.created_at + + # and now reverse order: + get :index, {:admin_status => "mine", :open_status => "open", :sort_order => 'created_at_asc'} + + assert_equal first_tick, assigns(:all_tickets).last + assert_equal last_tick, assigns(:all_tickets).first + + assert_not_equal first_tick, assigns(:all_tickets).first + assert_not_equal last_tick, assigns(:all_tickets).last + + end + + test "tickets for regular user" do + login :is_admin? => false, :email => nil + + put :update, :id => 'stubtestticketid',:ticket => {:comments_attributes => {"0" => {"body" =>"NEWER comment"}} } + assert_not_nil assigns(:ticket).comments.last.posted_by + assert_equal assigns(:ticket).comments.last.posted_by, @current_user.id + + get :index, {:open_status => "open"} + assert assigns(:all_tickets).count > 0 + assert assigns(:all_tickets).include?(Ticket.find('stubtestticketid')) + + assert !assigns(:all_tickets).include?(Ticket.find('stubtestticketid2')) + + # user should have one more ticket if a new tick gets a comment by this user + assert_difference('assigns[:all_tickets].count') do + put :update, :id => 'stubtestticketid2' , :ticket => {:comments_attributes => {"0" => {"body" =>"NEWER comment"}}} + get :index, {:open_status => "open"} + end + assert assigns(:all_tickets).include?(Ticket.find('stubtestticketid2')) + + # if we close one ticket, the user should have 1 less open ticket + assert_difference('assigns[:all_tickets].count', -1) do + t = Ticket.find('stubtestticketid2') + t.close + t.save + get :index, {:open_status => "open"} + end + + number_open_tickets = assigns(:all_tickets).count + + # look at closed tickets: + get :index, {:open_status => "closed"} + assert assigns(:all_tickets).include?(Ticket.find('stubtestticketid2')) + assert !assigns(:all_tickets).include?(Ticket.find('stubtestticketid')) + number_closed_tickets = assigns(:all_tickets).count + + # all tickets should equal closed + open + get :index, {:open_status => "all"} + assert assigns(:all_tickets).include?(Ticket.find('stubtestticketid2')) + assert assigns(:all_tickets).include?(Ticket.find('stubtestticketid')) + assert_equal assigns(:all_tickets).count, number_closed_tickets + number_open_tickets end end + diff --git a/help/test/unit/ticket_test.rb b/help/test/unit/ticket_test.rb index 6b63a23..e93a121 100644 --- a/help/test/unit/ticket_test.rb +++ b/help/test/unit/ticket_test.rb @@ -23,7 +23,7 @@ class TicketTest < ActiveSupport::TestCase #user = LeapWebUsers::User.create #t.user = user - + #t.email = '' #invalid #assert !t.valid? #t.email = 'blah@blah.com, bb@jjj.org' @@ -40,7 +40,7 @@ class TicketTest < ActiveSupport::TestCase @sample.created_by = 22 #current_user assert @sample.is_creator_validated? end - + =begin # TODO: do once have current_user stuff in order test "code if & only if not creator-validated" do @@ -56,6 +56,38 @@ class TicketTest < ActiveSupport::TestCase end =end -end + test "finds open tickets sorted by created_at" do + tickets = Ticket.by_is_open_and_created_at. + startkey([true, 0]). + endkey([true, Time.now + 10.hours]) # some tickets were created in the future + assert_equal Ticket.by_is_open.key(true).count, tickets.count + end + + test "find tickets user commented on" do + + # clear old tickets just in case + # this will cause RestClient::ResourceNotFound errors if there are multiple copies of the same ticket returned + Ticket.includes_post_by.key('123').each {|t| t.destroy} + testticket = Ticket.create :title => "test retrieving commented tickets" + comment = TicketComment.new :body => "my email broke", :posted_by => "123" + assert_equal 0, testticket.comments.count + assert_equal [], Ticket.includes_post_by.key('123').all + testticket.comments << comment + testticket.save + assert_equal 1, testticket.reload.comments.count + assert_equal [testticket], Ticket.includes_post_by.key('123').all + + comment = TicketComment.new :body => "another comment", :posted_by => "123" + testticket.comments << comment + testticket.save + + # this will ensure that the ticket is only included once, even though the user has commented on the ticket twice: + assert_equal [testticket], Ticket.includes_post_by.key('123').all + + testticket.destroy + assert_equal [], Ticket.includes_post_by.key('123').all; + end + +end diff --git a/ui_dependencies.rb b/ui_dependencies.rb index 454e9a8..eed79a3 100644 --- a/ui_dependencies.rb +++ b/ui_dependencies.rb @@ -2,8 +2,10 @@ gem "haml", "~> 3.1.7" gem "bootstrap-sass", "~> 2.1.0" gem "jquery-rails" gem "simple_form" +gem "pjax_rails" gem 'client_side_validations' gem 'client_side_validations-simple_form' +gem 'kaminari', "0.13.0" # for pagination. trying 0.13.0 as there seem to be issues with 0.14.0 when using couchrest group :assets do gem "haml-rails", "~> 0.3.4" diff --git a/users/app/controllers/controller_extension/authentication.rb b/users/app/controllers/controller_extension/authentication.rb index 6ac7a5b..f2184d9 100644 --- a/users/app/controllers/controller_extension/authentication.rb +++ b/users/app/controllers/controller_extension/authentication.rb @@ -24,7 +24,9 @@ module ControllerExtension::Authentication end def access_denied - redirect_to login_url, :alert => "Not authorized" + # TODO: should we redirect to the root_url in either case, and have the root_url include the login screen (and also ability to create unauthenticated tickets) when no user is logged in? + redirect_to login_url, :alert => "Not authorized" if !logged_in? + redirect_to root_url, :alert => "Not authorized" if logged_in? end def admin? diff --git a/users/test/functional/application_controller_test.rb b/users/test/functional/application_controller_test.rb index 857bae5..94b77bd 100644 --- a/users/test/functional/application_controller_test.rb +++ b/users/test/functional/application_controller_test.rb @@ -9,7 +9,7 @@ class ApplicationControllerTest < ActionController::TestCase def test_authorize_redirect @controller.send(:authorize) - assert_access_denied + assert_access_denied(true, false) end def test_authorized diff --git a/users/test/support/auth_test_helper.rb b/users/test/support/auth_test_helper.rb index f3506ae..6a82f24 100644 --- a/users/test/support/auth_test_helper.rb +++ b/users/test/support/auth_test_helper.rb @@ -19,10 +19,12 @@ module AuthTestHelper return @current_user end - def assert_access_denied(denied = true) + def assert_access_denied(denied = true, logged_in = true) if denied assert_equal({:alert => "Not authorized"}, flash.to_hash) - assert_redirected_to login_path + # todo: eventually probably eliminate separate conditions + assert_redirected_to login_path if !logged_in + assert_redirected_to root_path if logged_in else assert flash[:alert].blank? end diff --git a/users/test/unit/user_test.rb b/users/test/unit/user_test.rb index cce11c2..2269d4e 100644 --- a/users/test/unit/user_test.rb +++ b/users/test/unit/user_test.rb @@ -49,4 +49,15 @@ class UserTest < ActiveSupport::TestCase assert_equal client_rnd, srp_session.aa end + test 'is user an admin' do + admin_login = APP_CONFIG['admins'].first + attribs = User.valid_attributes_hash + attribs[:login] = admin_login + admin_user = User.new(attribs) + assert admin_user.is_admin? + assert !@user.is_admin? + + end + + end |