summaryrefslogtreecommitdiff
path: root/help/app
diff options
context:
space:
mode:
Diffstat (limited to 'help/app')
-rw-r--r--help/app/controllers/tickets_controller.rb78
-rw-r--r--help/app/helpers/tickets_helper.rb43
-rw-r--r--help/app/models/ticket.rb202
-rw-r--r--help/app/views/tickets/_admin-nav.html.haml5
-rw-r--r--help/app/views/tickets/_order-nav.html.haml5
-rw-r--r--help/app/views/tickets/_status-nav.html.haml7
-rw-r--r--help/app/views/tickets/_table-nav.html.haml5
-rw-r--r--help/app/views/tickets/_ticket.html.haml13
-rw-r--r--help/app/views/tickets/index.html.haml28
-rw-r--r--help/app/views/tickets/show.html.haml45
10 files changed, 375 insertions, 56 deletions
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..f9852a4 100644
--- a/help/app/models/ticket.rb
+++ b/help/app/models/ticket.rb
@@ -20,23 +20,110 @@ 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, doc);
+ }
+ });
+ }
+ }"
+
+ 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], doc);
+ }
+ });
+ }
+ }"
+
+
+ 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], doc);
+ }
+ });
+ }
+ }"
+
+ 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], doc);
+ }
+ });
+ }
+ }"
+
+
+ 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], doc);
+ }
+ });
+ }
+ }"
+
end
validates :title, :presence => true
@@ -45,20 +132,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 +216,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 +259,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