From 40efaae6d5a2f48734caf4cdabd9537f923c0f47 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Lang Date: Sun, 6 Jul 2008 16:26:25 +0000 Subject: [PATCH] Mail handler: more control over issue attributes (#1110). Tracker, category and priority attributes can be specified in command line arguments and/or individually specified as overridable by email body keywords. git-svn-id: http://redmine.rubyforge.org/svn/trunk@1643 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/models/mail_handler.rb | 33 +++++--- extra/mail_handler/rdm-mailhandler.rb | 65 ++++++++++++---- lib/tasks/email.rake | 76 ++++++++++++++----- test/fixtures/issue_categories.yml | 6 ++ .../mail_handler/ticket_with_attributes.eml | 43 +++++++++++ test/unit/mail_handler_test.rb | 40 +++++++++- 6 files changed, 214 insertions(+), 49 deletions(-) create mode 100644 test/fixtures/mail_handler/ticket_with_attributes.eml diff --git a/app/models/mail_handler.rb b/app/models/mail_handler.rb index 124f7db74..109db2989 100644 --- a/app/models/mail_handler.rb +++ b/app/models/mail_handler.rb @@ -23,7 +23,14 @@ class MailHandler < ActionMailer::Base attr_reader :email, :user def self.receive(email, options={}) - @@handler_options = options + @@handler_options = options.dup + + @@handler_options[:issue] ||= {} + + @@handler_options[:allow_override] = @@handler_options[:allow_override].split(',').collect(&:strip) if @@handler_options[:allow_override].is_a?(String) + @@handler_options[:allow_override] ||= [] + # Project needs to be overridable if not specified + @@handler_options[:allow_override] << 'project' unless @@handler_options[:issue].has_key?(:project) super email end @@ -66,11 +73,13 @@ class MailHandler < ActionMailer::Base # Creates a new issue def receive_issue project = target_project - # TODO: make the tracker configurable - tracker = project.trackers.find(:first) + tracker = (get_keyword(:tracker) && project.trackers.find_by_name(get_keyword(:tracker))) || project.trackers.find(:first) + category = (get_keyword(:category) && project.issue_categories.find_by_name(get_keyword(:category))) + priority = (get_keyword(:priority) && Enumeration.find_by_opt_and_name('IPRI', get_keyword(:priority))) + # check permission raise UnauthorizedAction unless user.allowed_to?(:add_issues, project) - issue = Issue.new(:author => user, :project => project, :tracker => tracker) + issue = Issue.new(:author => user, :project => project, :tracker => tracker, :category => category, :priority => priority) issue.subject = email.subject.chomp issue.description = email.plain_text_body.chomp issue.save! @@ -84,13 +93,7 @@ class MailHandler < ActionMailer::Base # TODO: other ways to specify project: # * parse the email To field # * specific project (eg. Setting.mail_handler_target_project) - identifier = if !@@handler_options[:project].blank? - @@handler_options[:project] - elsif email.plain_text_body =~ %r{^Project:[ \t]*(.+)$}i - $1 - end - - target = Project.find_by_identifier(identifier.to_s) + target = Project.find_by_identifier(get_keyword(:project)) raise MissingInformation.new('Unable to determine target project') if target.nil? target end @@ -120,6 +123,14 @@ class MailHandler < ActionMailer::Base end end end + + def get_keyword(attr) + if @@handler_options[:allow_override].include?(attr.to_s) && email.plain_text_body =~ /^#{attr}:[ \t]*(.+)$/i + $1.strip + elsif !@@handler_options[:issue][attr].blank? + @@handler_options[:issue][attr] + end + end end class TMail::Mail diff --git a/extra/mail_handler/rdm-mailhandler.rb b/extra/mail_handler/rdm-mailhandler.rb index 585afefef..87a6798fa 100644 --- a/extra/mail_handler/rdm-mailhandler.rb +++ b/extra/mail_handler/rdm-mailhandler.rb @@ -12,16 +12,22 @@ require 'getoptlong' class RedmineMailHandler VERSION = '0.1' - attr_accessor :verbose, :project, :url, :key + attr_accessor :verbose, :issue_attributes, :allow_override, :url, :key def initialize + self.issue_attributes = {} + opts = GetoptLong.new( [ '--help', '-h', GetoptLong::NO_ARGUMENT ], [ '--version', '-V', GetoptLong::NO_ARGUMENT ], [ '--verbose', '-v', GetoptLong::NO_ARGUMENT ], [ '--url', '-u', GetoptLong::REQUIRED_ARGUMENT ], [ '--key', '-k', GetoptLong::REQUIRED_ARGUMENT], - [ '--project', '-p', GetoptLong::REQUIRED_ARGUMENT ] + [ '--project', '-p', GetoptLong::REQUIRED_ARGUMENT ], + [ '--tracker', '-t', GetoptLong::REQUIRED_ARGUMENT], + [ '--category', GetoptLong::REQUIRED_ARGUMENT], + [ '--priority', GetoptLong::REQUIRED_ARGUMENT], + [ '--allow-override', '-o', GetoptLong::REQUIRED_ARGUMENT] ) opts.each do |opt, arg| @@ -36,8 +42,10 @@ class RedmineMailHandler self.verbose = true when '--version' puts VERSION; exit - when '--project' - self.project = arg.dup + when '--project', '--tracker', '--category', '--priority' + self.issue_attributes[opt.gsub(%r{^\-\-}, '')] = arg.dup + when '--allow-override' + self.allow_override = arg.dup end end @@ -46,8 +54,11 @@ class RedmineMailHandler def submit(email) uri = url.gsub(%r{/*$}, '') + '/mail_handler' + + data = { 'key' => key, 'email' => email, 'allow_override' => allow_override } + issue_attributes.each { |attr, value| data["issue[#{attr}]"] = value } + debug "Posting to #{uri}..." - data = { 'key' => key, 'project' => project, 'email' => email } response = Net::HTTP.post_form(URI.parse(uri), data) debug "Response received: #{response.code}" response.code == 201 ? 0 : 1 @@ -56,17 +67,39 @@ class RedmineMailHandler private def usage - puts "Usage: rdm-mailhandler [options] --url= --key=" - puts "Reads an email from standard input and forward it to a Redmine server" - puts - puts "Options:" - puts " --help show this help" - puts " --verbose show extra information" - puts " --project identifier of the target project" - puts - puts "Examples:" - puts " rdm-mailhandler --url http://redmine.domain.foo --key secret" - puts " rdm-mailhandler --url https://redmine.domain.foo --key secret --project foo" + puts <<-USAGE +Usage: rdm-mailhandler [options] --url= --key= +Reads an email from standard input and forward it to a Redmine server + +Required: + -u, --url URL of the Redmine server + -k, --key Redmine API key + +General options: + -h, --help show this help + -v, --verbose show extra information + -V, --version show version information and exit + +Issue attributes control options: + -p, --project=PROJECT identifier of the target project + -t, --tracker=TRACKER name of the target tracker + --category=CATEGORY name of the target category + --priority=PRIORITY name of the target priority + -o, --allow-override=ATTRS allow email content to override attributes + specified by previous options + ATTRS is a comma separated list of attributes + +Examples: + # No project specified. Emails MUST contain the 'Project' keyword: + rdm-mailhandler --url http://redmine.domain.foo --key secret + + # Fixed project and default tracker specified, but emails can override + # both tracker and priority attributes: + rdm-mailhandler --url https://domain.foo/redmine --key secret \\ + --project foo \\ + --tracker bug \\ + --allow-override tracker,priority +USAGE exit end diff --git a/lib/tasks/email.rake b/lib/tasks/email.rake index 01407c36d..daf0aa9bb 100644 --- a/lib/tasks/email.rake +++ b/lib/tasks/email.rake @@ -21,16 +21,31 @@ namespace :redmine do desc <<-END_DESC Read an email from standard input. -Available options: - * project => identifier of the project the issue should be added to - -Example: - rake redmine:email:receive project=foo RAILS_ENV="production" +Issue attributes control options: + project=PROJECT identifier of the target project + tracker=TRACKER name of the target tracker + category=CATEGORY name of the target category + priority=PRIORITY name of the target priority + allow_override=ATTRS allow email content to override attributes + specified by previous options + ATTRS is a comma separated list of attributes + +Examples: + # No project specified. Emails MUST contain the 'Project' keyword: + rake redmine:email:read RAILS_ENV="production" < raw_email + + # Fixed project and default tracker specified, but emails can override + # both tracker and priority attributes: + rake redmine:email:read RAILS_ENV="production" \\ + project=foo \\ + tracker=bug \\ + allow_override=tracker,priority < raw_email END_DESC - task :receive => :environment do - options = {} - options[:project] = ENV['project'] if ENV['project'] + task :read => :environment do + options = { :issue => {} } + %w(project tracker category priority).each { |a| options[:issue][a.to_sym] = ENV[a] if ENV[a] } + options[:allow_override] = ENV['allow_override'] if ENV['allow_override'] MailHandler.receive(STDIN.read, options) end @@ -39,17 +54,37 @@ END_DESC Read emails from an IMAP server. Available IMAP options: - * host => IMAP server host (default: 127.0.0.1) - * port => IMAP server port (default: 143) - * ssl => Use SSL? (default: false) - * username => IMAP account - * password => IMAP password - * folder => IMAP folder to read (default: INBOX) -Other options: - * project => identifier of the project the issue should be added to + host=HOST IMAP server host (default: 127.0.0.1) + port=PORT IMAP server port (default: 143) + ssl=SSL Use SSL? (default: false) + username=USERNAME IMAP account + password=PASSWORD IMAP password + folder=FOLDER IMAP folder to read (default: INBOX) + +Issue attributes control options: + project=PROJECT identifier of the target project + tracker=TRACKER name of the target tracker + category=CATEGORY name of the target category + priority=PRIORITY name of the target priority + allow_override=ATTRS allow email content to override attributes + specified by previous options + ATTRS is a comma separated list of attributes + +Examples: + # No project specified. Emails MUST contain the 'Project' keyword: + + rake redmine:email:receive_iamp RAILS_ENV="production" \\ + host=imap.foo.bar username=redmine@somenet.foo password=xxx + + + # Fixed project and default tracker specified, but emails can override + # both tracker and priority attributes: -Example: - rake redmine:email:receive_iamp host=imap.foo.bar username=redmine@somenet.foo password=xxx project=foo RAILS_ENV="production" + rake redmine:email:receive_iamp RAILS_ENV="production" \\ + host=imap.foo.bar username=redmine@somenet.foo password=xxx ssl=1 \\ + project=foo \\ + tracker=bug \\ + allow_override=tracker,priority END_DESC task :receive_imap => :environment do @@ -60,8 +95,9 @@ END_DESC :password => ENV['password'], :folder => ENV['folder']} - options = {} - options[:project] = ENV['project'] if ENV['project'] + options = { :issue => {} } + %w(project tracker category priority).each { |a| options[:issue][a.to_sym] = ENV[a] if ENV[a] } + options[:allow_override] = ENV['allow_override'] if ENV['allow_override'] Redmine::IMAP.check(imap_options, options) end diff --git a/test/fixtures/issue_categories.yml b/test/fixtures/issue_categories.yml index 6c2a07b58..2b74b5977 100644 --- a/test/fixtures/issue_categories.yml +++ b/test/fixtures/issue_categories.yml @@ -9,3 +9,9 @@ issue_categories_002: project_id: 1 assigned_to_id: id: 2 +issue_categories_003: + name: Stock management + project_id: 2 + assigned_to_id: + id: 3 + \ No newline at end of file diff --git a/test/fixtures/mail_handler/ticket_with_attributes.eml b/test/fixtures/mail_handler/ticket_with_attributes.eml new file mode 100644 index 000000000..118523496 --- /dev/null +++ b/test/fixtures/mail_handler/ticket_with_attributes.eml @@ -0,0 +1,43 @@ +Return-Path: +Received: from osiris ([127.0.0.1]) + by OSIRIS + with hMailServer ; Sun, 22 Jun 2008 12:28:07 +0200 +Message-ID: <000501c8d452$a95cd7e0$0a00a8c0@osiris> +From: "John Smith" +To: +Subject: New ticket on a given project +Date: Sun, 22 Jun 2008 12:28:07 +0200 +MIME-Version: 1.0 +Content-Type: text/plain; + format=flowed; + charset="iso-8859-1"; + reply-type=original +Content-Transfer-Encoding: 7bit +X-Priority: 3 +X-MSMail-Priority: Normal +X-Mailer: Microsoft Outlook Express 6.00.2900.2869 +X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.2869 + +Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas imperdiet +turpis et odio. Integer eget pede vel dolor euismod varius. Phasellus +blandit eleifend augue. Nulla facilisi. Duis id diam. Class aptent taciti +sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In +in urna sed tellus aliquet lobortis. Morbi scelerisque tortor in dolor. Cras +sagittis odio eu lacus. Aliquam sem tortor, consequat sit amet, vestibulum +id, iaculis at, lectus. Fusce tortor libero, congue ut, euismod nec, luctus +eget, eros. Pellentesque tortor enim, feugiat in, dignissim eget, tristique +sed, mauris. Pellentesque habitant morbi tristique senectus et netus et +malesuada fames ac turpis egestas. Quisque sit amet libero. In hac habitasse +platea dictumst. + +Nulla et nunc. Duis pede. Donec et ipsum. Nam ut dui tincidunt neque +sollicitudin iaculis. Duis vitae dolor. Vestibulum eget massa. Sed lorem. +Nullam volutpat cursus erat. Cras felis dolor, lacinia quis, rutrum et, +dictum et, ligula. Sed erat nibh, gravida in, accumsan non, placerat sed, +massa. Sed sodales, ante fermentum ultricies sollicitudin, massa leo +pulvinar dui, a gravida orci mi eget odio. Nunc a lacus. + +Project: onlinestore +Tracker: Feature request +category: Stock management +priority: Urgent diff --git a/test/unit/mail_handler_test.rb b/test/unit/mail_handler_test.rb index 6bb638f21..6e8a47c35 100644 --- a/test/unit/mail_handler_test.rb +++ b/test/unit/mail_handler_test.rb @@ -18,7 +18,15 @@ require File.dirname(__FILE__) + '/../test_helper' class MailHandlerTest < Test::Unit::TestCase - fixtures :users, :projects, :enabled_modules, :roles, :members, :issues, :trackers, :enumerations + fixtures :users, :projects, + :enabled_modules, + :roles, + :members, + :issues, + :trackers, + :projects_trackers, + :enumerations, + :issue_categories FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures/mail_handler' @@ -38,8 +46,36 @@ class MailHandlerTest < Test::Unit::TestCase assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.') end + def test_add_issue_with_attributes_override + issue = submit_email('ticket_with_attributes.eml', :allow_override => 'tracker,category,priority') + assert issue.is_a?(Issue) + assert !issue.new_record? + issue.reload + assert_equal 'New ticket on a given project', issue.subject + assert_equal User.find_by_login('jsmith'), issue.author + assert_equal Project.find(2), issue.project + assert_equal 'Feature request', issue.tracker.to_s + assert_equal 'Stock management', issue.category.to_s + assert_equal 'Urgent', issue.priority.to_s + assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.') + end + + def test_add_issue_with_partial_attributes_override + issue = submit_email('ticket_with_attributes.eml', :issue => {:priority => 'High'}, :allow_override => ['tracker']) + assert issue.is_a?(Issue) + assert !issue.new_record? + issue.reload + assert_equal 'New ticket on a given project', issue.subject + assert_equal User.find_by_login('jsmith'), issue.author + assert_equal Project.find(2), issue.project + assert_equal 'Feature request', issue.tracker.to_s + assert_nil issue.category + assert_equal 'High', issue.priority.to_s + assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.') + end + def test_add_issue_with_attachment_to_specific_project - issue = submit_email('ticket_with_attachment.eml', :project => 'onlinestore') + issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'}) assert issue.is_a?(Issue) assert !issue.new_record? issue.reload -- 2.39.5