summaryrefslogtreecommitdiff
path: root/engines/support
diff options
context:
space:
mode:
authorelijah <elijah@riseup.net>2015-04-30 00:32:33 -0700
committerelijah <elijah@riseup.net>2015-04-30 00:32:33 -0700
commit63871baf6061668b162972193c55b5a8f7490797 (patch)
treeca8cd5fbab18cbe59b728a123f450140ed98f519 /engines/support
parentc3b133cb6f02003ab934e5008e108f489ace4158 (diff)
added support for email notifications of ticket changes
Diffstat (limited to 'engines/support')
-rw-r--r--engines/support/Gemfile6
-rw-r--r--engines/support/Gemfile.lock14
-rw-r--r--engines/support/Rakefile12
-rw-r--r--engines/support/app/controllers/tickets_controller.rb8
-rw-r--r--engines/support/app/helpers/ticket_i18n_helper.rb20
-rw-r--r--engines/support/app/mailers/.gitkeep0
-rw-r--r--engines/support/app/mailers/ticket_mailer.rb82
-rw-r--r--engines/support/app/models/ticket.rb43
-rw-r--r--engines/support/app/models/ticket_comment.rb40
-rw-r--r--engines/support/app/views/ticket_mailer/notice.text.erb5
-rw-r--r--engines/support/config/locales/en.yml8
-rw-r--r--engines/support/config/locales/es.yml3
-rw-r--r--engines/support/leap_web_help.gemspec6
-rw-r--r--engines/support/test/factories.rb2
-rw-r--r--engines/support/test/functional/ticket_mailer_test.rb7
-rw-r--r--engines/support/test/unit/ticket_test.rb2
16 files changed, 193 insertions, 65 deletions
diff --git a/engines/support/Gemfile b/engines/support/Gemfile
index ad7d29b..0296338 100644
--- a/engines/support/Gemfile
+++ b/engines/support/Gemfile
@@ -1,11 +1,5 @@
source "https://rubygems.org"
-eval(File.read(File.dirname(__FILE__) + '/../common_dependencies.rb'))
-eval(File.read(File.dirname(__FILE__) + '/..//ui_dependencies.rb'))
-
-# We require leap_web_core from here so we can use the path option.
-gem "leap_web_core", :path => '../core'
-
# Declare your gem's dependencies in leap_web_users.gemspec.
# Bundler will treat runtime dependencies like base dependencies, and
# development dependencies will be added by default to the :development group.
diff --git a/engines/support/Gemfile.lock b/engines/support/Gemfile.lock
new file mode 100644
index 0000000..fe4ce6c
--- /dev/null
+++ b/engines/support/Gemfile.lock
@@ -0,0 +1,14 @@
+PATH
+ remote: .
+ specs:
+ leap_web_help (0.6.0)
+
+GEM
+ remote: https://rubygems.org/
+ specs:
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ leap_web_help!
diff --git a/engines/support/Rakefile b/engines/support/Rakefile
index 0e73163..4787e78 100644
--- a/engines/support/Rakefile
+++ b/engines/support/Rakefile
@@ -1,8 +1,4 @@
#!/usr/bin/env rake
-
-require 'rake/packagetask'
-require 'rubygems/package_task'
-
begin
require 'bundler/setup'
rescue LoadError
@@ -18,16 +14,14 @@ end
RDoc::Task.new(:rdoc) do |rdoc|
rdoc.rdoc_dir = 'rdoc'
- rdoc.title = 'LeapWebHelp'
+ rdoc.title = 'Blah'
rdoc.options << '--line-numbers'
rdoc.rdoc_files.include('README.rdoc')
rdoc.rdoc_files.include('lib/**/*.rb')
end
-spec = eval(File.read('leap_web_help.gemspec'))
-Gem::PackageTask.new(spec) do |p|
- p.gem_spec = spec
-end
+
+
Bundler::GemHelper.install_tasks
diff --git a/engines/support/app/controllers/tickets_controller.rb b/engines/support/app/controllers/tickets_controller.rb
index 602bbd9..c1abfa2 100644
--- a/engines/support/app/controllers/tickets_controller.rb
+++ b/engines/support/app/controllers/tickets_controller.rb
@@ -62,6 +62,7 @@ class TicketsController < ApplicationController
if @ticket.comments_changed?
@ticket.comments.last.posted_by = current_user.id
@ticket.comments.last.private = false unless admin?
+ send_email_update(@ticket, @ticket.comments.last)
end
flash_for @ticket, with_errors: true
@@ -153,4 +154,11 @@ class TicketsController < ApplicationController
)
end
+ def send_email_update(ticket, comment)
+ TicketMailer.send_notice(ticket, comment, ticket_url(ticket))
+ rescue StandardError => exc
+ flash_for(exc)
+ raise exc if Rails.env == 'development'
+ end
+
end
diff --git a/engines/support/app/helpers/ticket_i18n_helper.rb b/engines/support/app/helpers/ticket_i18n_helper.rb
new file mode 100644
index 0000000..61b4cf2
--- /dev/null
+++ b/engines/support/app/helpers/ticket_i18n_helper.rb
@@ -0,0 +1,20 @@
+module TicketI18nHelper
+
+ #
+ # outputs translations for all the possible translated strings.
+ # used in emails, sense we don't know the locale of the recipient.
+ #
+ def t_all_available(key)
+ default = I18n.t(key, locale: I18n.default_locale)
+ result = []
+ result << "[#{I18n.default_locale}] #{default}"
+ I18n.available_locales.each do |locale|
+ text = I18n.t(key, locale: locale, default: default)
+ if text != default
+ result << "[#{locale}] #{text}"
+ end
+ end
+ result.join("\n")
+ end
+
+end
diff --git a/engines/support/app/mailers/.gitkeep b/engines/support/app/mailers/.gitkeep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/engines/support/app/mailers/.gitkeep
diff --git a/engines/support/app/mailers/ticket_mailer.rb b/engines/support/app/mailers/ticket_mailer.rb
new file mode 100644
index 0000000..9a83345
--- /dev/null
+++ b/engines/support/app/mailers/ticket_mailer.rb
@@ -0,0 +1,82 @@
+class TicketMailer < ActionMailer::Base
+
+ helper :ticket_i18n
+
+ def self.send_notice(ticket, comment, url)
+ reply_recipients(ticket, comment.user).each do |email, key|
+ TicketMailer.notice(ticket, comment, url, email, key).deliver
+ end
+ end
+
+ #
+ # ticket - the ticket in question
+ # author - user who created last comment
+ # url - url of the ticket
+ # email - email to send reply notice to
+ # key - public key for email
+ #
+ # TODO: OpenPGP encrypt email to the public key.
+ #
+ def notice(ticket, comment, url, email, key)
+ @url = url
+ @email = email
+ @key = key
+ mail({
+ :reply_to => reply_to_address,
+ :subject => "Re: [#{ticket.id}] #{ticket.subject}",
+ :to => email,
+ :from => from_address(ticket, comment)
+ })
+ end
+
+ private
+
+ #
+ # I am not sure what makes the most sense here. For now, since we do not
+ # include any reply text in the notification email, it makes sense to make
+ # the from address be the robot, not the user.
+ #
+ def from_address(ticket, comment)
+ if true
+ reply_to_address
+ else
+ from_name = if comment.user.present?
+ comment.user.login
+ elsif ticket.created_by.present?
+ ticket.created_by_user.login
+ else
+ I18n.t(:anonymous)
+ end
+ "%s <%s>" % [from_name, reply_to_address]
+ end
+ end
+
+ # TODO: change me to support virtual domains
+ def reply_to_address
+ [APP_CONFIG[:mailer][:from_address], APP_CONFIG[:domain]].join('@')
+ end
+
+ #
+ # returns a hash of {'email' => 'public key'}, where key might be nil.
+ #
+ def self.reply_recipients(ticket, author)
+ recipients = {}
+ ticket.comments.each do |comment|
+ user = comment.posted_by_user
+ if user && (author.nil? || user.id != author.id)
+ if user.email
+ recipients[user.identity.address] = (user.identity.keys[:pgp] if user.identity.keys[:pgp].present?)
+ end
+ if user.contact_email.present?
+ recipients[user.contact_email] = (user.contact_email_key if user.contact_email_key.present?)
+ end
+ end
+ end
+ if author && author.is_admin? && ticket.email.present?
+ recipients[ticket.email] = nil
+ end
+ logger.info { "emailing reply regarding ticket #{ticket.id} to #{recipients.keys.join(', ')}" }
+ recipients
+ end
+
+end
diff --git a/engines/support/app/models/ticket.rb b/engines/support/app/models/ticket.rb
index 554fbd6..b1bdf8d 100644
--- a/engines/support/app/models/ticket.rb
+++ b/engines/support/app/models/ticket.rb
@@ -3,10 +3,10 @@
# look into whether that should be tweaked, and whether it works okay with
# pagination (seems to now...)
#
-# TODO: better validation of email
-#
# TODO: don't hardcode strings 'unknown user' and 'unauthenticated user'
#
+# TODO: this should use associations instead of non-standard created_by.
+#
class Ticket < CouchRest::Model::Base
use_database "tickets"
@@ -19,6 +19,8 @@ class Ticket < CouchRest::Model::Base
timestamps!
+ unique_id :generate_code
+
design do
view :by_updated_at
view :by_created_at
@@ -33,13 +35,11 @@ class Ticket < CouchRest::Model::Base
validates :subject, :presence => true
- # email can have three states:
- # * nil - prefilled with created_by's email
- # * "" - cleared
- # * valid email address
- validates :email, :allow_blank => true, :format => /\A(([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,}))?\Z/
-
- # validates :comments, presence: true
+ # email can be nil, "", or valid address.
+ # validation provided by 'valid_email' gem.
+ validates :email, :allow_blank => true,
+ :email => true,
+ :mx_with_fallback => true
def self.search(options = {})
@selection = TicketSelection.new(options)
@@ -74,17 +74,21 @@ class Ticket < CouchRest::Model::Base
self.is_open = true
end
- def commenters
+ def commenters(locale = I18n.locale)
commenters = []
+ unknown = false
+ anonymous = false
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')
+ elsif !unknown
+ unknown = true
+ commenters << I18n.t(:unknown, :locale => locale)
end
- else
- commenters << 'unauthenticated user' if !commenters.include?('unauthenticated user')
+ elsif !anonymous
+ anonymous = true
+ commenters << I18n.t(:anonymous, :locale => locale)
end
end
commenters.join(', ')
@@ -113,4 +117,15 @@ class Ticket < CouchRest::Model::Base
User.find_by_login(self.regarding_user)
end
+ # Generate a unique code for identifying this ticket.
+ # This will become the ID of the document. It is also used for
+ # tracking email replies. The code must be URL friendly.
+ def generate_code
+ while code = SecureRandom.urlsafe_base64.downcase.gsub(/[li1o0_-]/,'')[0..7]
+ if code.length == 8 && self.class.find(code).nil?
+ return code
+ end
+ end
+ end
+
end
diff --git a/engines/support/app/models/ticket_comment.rb b/engines/support/app/models/ticket_comment.rb
index 2c5df41..1c04a1f 100644
--- a/engines/support/app/models/ticket_comment.rb
+++ b/engines/support/app/models/ticket_comment.rb
@@ -1,22 +1,17 @@
+#
+# elijah: the property 'posted_by' should have been a belongs_to() association
+# or at least 'posted_by_id', but I am leaving it as is because I am
+# not sure how to change it.
+#
class TicketComment
include CouchRest::Model::Embeddable
- #belongs_to :ticket #is this best way to do it? will want to access all of a tickets comments, so maybe this isn't the way?
- property :posted_by, String#, :protected => true #Integer#this should be current_user if that is set, meaning the user is logged in #cannot have it be protected and set via comments_attributes=. also, if it is protected and we set in the tickets_controller, it gets unset. TODO---is this okay to have it not protected and manually check it? We do not users to be able to set this.
- # if the current user is not set, then we could just say the comment comes from an 'unauthenticated user', which would be somebody with the secret URL
- property :posted_at, Time#, :protected => true
- #property :posted_verified, TrueClass, :protected => true #should be true if current_user is set when the comment is created
+ property :posted_by, String
+ property :posted_at, Time
property :body, String
- property :private, TrueClass # private comments are only viewable by admins #this is checked when set, to make sure it was set by an admin
+ property :private, TrueClass
- # ? timestamps!
validates :body, :presence => true
- #before_validation :set_time#, :set_posted_by
-
- #design do
- # view :by_posted_at
- # view :by_body
- #end
# translations are in the same scope as those of a "proper" couchrest model
def self.i18n_scope
@@ -28,21 +23,10 @@ class TicketComment
end
def posted_by_user
- User.find(posted_by) if posted_by
- end
-
-=begin
- #TODO.
- #this is resetting all comments associated with the ticket:
- def set_time
- self.posted_at = Time.now
- end
-=end
-
-=begin
- def set_posted_by
- self.posted_by = User.current if User.current
+ if posted_by
+ @_posted_by_user ||= User.find(posted_by)
+ end
end
-=end
+ alias user posted_by_user
end
diff --git a/engines/support/app/views/ticket_mailer/notice.text.erb b/engines/support/app/views/ticket_mailer/notice.text.erb
new file mode 100644
index 0000000..c0e6f44
--- /dev/null
+++ b/engines/support/app/views/ticket_mailer/notice.text.erb
@@ -0,0 +1,5 @@
+<%= t_all_available(:email_notice_text) %>
+
+ <%= @url %>
+
+<%= t_all_available(:email_no_reply_text) %> \ No newline at end of file
diff --git a/engines/support/config/locales/en.yml b/engines/support/config/locales/en.yml
index 8d2af67..071db97 100644
--- a/engines/support/config/locales/en.yml
+++ b/engines/support/config/locales/en.yml
@@ -1,9 +1,11 @@
en:
support_tickets: "Support"
+ email_notice_text: "A new comment has been added to this help ticket."
+ email_no_reply_text: "Do not reply to this email."
# translations used in the layout views or @title
layouts:
# fallback for all translations of "tickets" nested below:
- tickets: "Tickets"
+ tickets: "Tickets"
title:
tickets: "Tickets"
header:
@@ -90,8 +92,8 @@ en:
"false": "Closed"
# mouse over hints for the given fields
hints:
- ticket:
- email: "Please provide an email address so we can get back to you."
+ ticket:
+ email: "Provide an email address in order to notified when this ticket is updated."
# these will fallback to translations in "simple_form.hints.defaults"
# placeholders inside the fields before anything was typed
#placeholders:
diff --git a/engines/support/config/locales/es.yml b/engines/support/config/locales/es.yml
new file mode 100644
index 0000000..7773197
--- /dev/null
+++ b/engines/support/config/locales/es.yml
@@ -0,0 +1,3 @@
+es:
+ email_notice_text: "Se ha aƱadido un nuevo comentario."
+ email_no_reply_text: "No responda a este mensaje." \ No newline at end of file
diff --git a/engines/support/leap_web_help.gemspec b/engines/support/leap_web_help.gemspec
index 7b668d5..818604f 100644
--- a/engines/support/leap_web_help.gemspec
+++ b/engines/support/leap_web_help.gemspec
@@ -8,9 +8,9 @@ Gem::Specification.new do |s|
s.version = LeapWeb::VERSION
s.authors = ["Jessib"]
s.email = ["jessib@leap.se"]
- s.homepage = "http://www.leap.se"
- s.summary = "Help Desk for LeapWeb"
- s.description = "Managing Tickets for a Leap provider"
+ s.homepage = "https://www.leap.se"
+ s.summary = "Help Desk for LEAP webapp"
+ s.description = "Managing help tickets for a LEAP provider"
s.files = Dir["{app,config,lib}/**/*"] + ["Rakefile", "README.md"]
s.test_files = Dir["test/**/*"]
diff --git a/engines/support/test/factories.rb b/engines/support/test/factories.rb
index bcf41e8..3fc28b3 100644
--- a/engines/support/test/factories.rb
+++ b/engines/support/test/factories.rb
@@ -2,7 +2,7 @@ FactoryGirl.define do
factory :ticket do
subject { Faker::Lorem.sentence }
- email { Faker::Internet.email }
+ email { "fake@example.org" }
factory :ticket_with_comment do
comments_attributes do
diff --git a/engines/support/test/functional/ticket_mailer_test.rb b/engines/support/test/functional/ticket_mailer_test.rb
new file mode 100644
index 0000000..6bbaaaf
--- /dev/null
+++ b/engines/support/test/functional/ticket_mailer_test.rb
@@ -0,0 +1,7 @@
+require 'test_helper'
+
+class TicketMailerTest < ActionMailer::TestCase
+ # test "the truth" do
+ # assert true
+ # end
+end
diff --git a/engines/support/test/unit/ticket_test.rb b/engines/support/test/unit/ticket_test.rb
index 678d8dc..c64e8f4 100644
--- a/engines/support/test/unit/ticket_test.rb
+++ b/engines/support/test/unit/ticket_test.rb
@@ -13,7 +13,7 @@ class TicketTest < ActiveSupport::TestCase
end
test "ticket validates email format" do
- t = FactoryGirl.build :ticket, email: "aswerssfd"
+ t = FactoryGirl.build :ticket, email: "invalid email"
assert !t.valid?
end