]> source.dussan.org Git - redmine.git/commitdiff
Mail handler: more control over issue attributes (#1110).
authorJean-Philippe Lang <jp_lang@yahoo.fr>
Sun, 6 Jul 2008 16:26:25 +0000 (16:26 +0000)
committerJean-Philippe Lang <jp_lang@yahoo.fr>
Sun, 6 Jul 2008 16:26:25 +0000 (16:26 +0000)
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
extra/mail_handler/rdm-mailhandler.rb
lib/tasks/email.rake
test/fixtures/issue_categories.yml
test/fixtures/mail_handler/ticket_with_attributes.eml [new file with mode: 0644]
test/unit/mail_handler_test.rb

index 124f7db74bd62806953fd200d020c35e2957cbd3..109db2989086828441a22ab10c0148611326dac8 100644 (file)
@@ -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
index 585afefeff3939c4f52e0694fbb3b5c31b3a646f..87a6798fa3e3816ef0d773c30f8d3d6496196121 100644 (file)
@@ -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=<Redmine URL> --key=<API 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=<Redmine URL> --key=<API 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
   
index 01407c36d8e093766b0fd8de513f5afccac54259..daf0aa9bbce37e3ae5e9e652f1af55d317b1d3a7 100644 (file)
@@ -21,16 +21,31 @@ namespace :redmine do
     desc <<-END_DESC\r
 Read an email from standard input.\r
 \r
-Available options:\r
-  * project => identifier of the project the issue should be added to\r
-  \r
-Example:\r
-  rake redmine:email:receive project=foo RAILS_ENV="production"\r
+Issue attributes control options:\r
+  project=PROJECT          identifier of the target project\r
+  tracker=TRACKER          name of the target tracker\r
+  category=CATEGORY        name of the target category\r
+  priority=PRIORITY        name of the target priority\r
+  allow_override=ATTRS     allow email content to override attributes\r
+                           specified by previous options\r
+                           ATTRS is a comma separated list of attributes\r
+\r
+Examples:\r
+  # No project specified. Emails MUST contain the 'Project' keyword:\r
+  rake redmine:email:read RAILS_ENV="production" < raw_email\r
+\r
+  # Fixed project and default tracker specified, but emails can override\r
+  # both tracker and priority attributes:\r
+  rake redmine:email:read RAILS_ENV="production" \\\r
+                  project=foo \\\r
+                  tracker=bug \\\r
+                  allow_override=tracker,priority < raw_email\r
 END_DESC\r
 \r
-    task :receive => :environment do\r
-      options = {}\r
-      options[:project] = ENV['project'] if ENV['project']\r
+    task :read => :environment do\r
+      options = { :issue => {} }\r
+      %w(project tracker category priority).each { |a| options[:issue][a.to_sym] = ENV[a] if ENV[a] }\r
+      options[:allow_override] = ENV['allow_override'] if ENV['allow_override']\r
       \r
       MailHandler.receive(STDIN.read, options)\r
     end\r
@@ -39,17 +54,37 @@ END_DESC
 Read emails from an IMAP server.\r
 \r
 Available IMAP options:\r
-  * host      => IMAP server host (default: 127.0.0.1)\r
-  * port      => IMAP server port (default: 143)\r
-  * ssl       => Use SSL? (default: false)\r
-  * username  => IMAP account\r
-  * password  => IMAP password\r
-  * folder    => IMAP folder to read (default: INBOX)\r
-Other options:\r
-  * project   => identifier of the project the issue should be added to\r
+  host=HOST                IMAP server host (default: 127.0.0.1)\r
+  port=PORT                IMAP server port (default: 143)\r
+  ssl=SSL                  Use SSL? (default: false)\r
+  username=USERNAME        IMAP account\r
+  password=PASSWORD        IMAP password\r
+  folder=FOLDER            IMAP folder to read (default: INBOX)\r
+\r
+Issue attributes control options:\r
+  project=PROJECT          identifier of the target project\r
+  tracker=TRACKER          name of the target tracker\r
+  category=CATEGORY        name of the target category\r
+  priority=PRIORITY        name of the target priority\r
+  allow_override=ATTRS     allow email content to override attributes\r
+                           specified by previous options\r
+                           ATTRS is a comma separated list of attributes\r
+  \r
+Examples:\r
+  # No project specified. Emails MUST contain the 'Project' keyword:\r
+  \r
+  rake redmine:email:receive_iamp RAILS_ENV="production" \\\r
+    host=imap.foo.bar username=redmine@somenet.foo password=xxx\r
+\r
+\r
+  # Fixed project and default tracker specified, but emails can override\r
+  # both tracker and priority attributes:\r
   \r
-Example:\r
-  rake redmine:email:receive_iamp host=imap.foo.bar username=redmine@somenet.foo password=xxx project=foo RAILS_ENV="production"\r
+  rake redmine:email:receive_iamp RAILS_ENV="production" \\\r
+    host=imap.foo.bar username=redmine@somenet.foo password=xxx ssl=1 \\\r
+    project=foo \\\r
+    tracker=bug \\\r
+    allow_override=tracker,priority\r
 END_DESC\r
 \r
     task :receive_imap => :environment do\r
@@ -60,8 +95,9 @@ END_DESC
                       :password => ENV['password'],\r
                       :folder => ENV['folder']}\r
                       \r
-      options = {}\r
-      options[:project] = ENV['project'] if ENV['project']\r
+      options = { :issue => {} }\r
+      %w(project tracker category priority).each { |a| options[:issue][a.to_sym] = ENV[a] if ENV[a] }\r
+      options[:allow_override] = ENV['allow_override'] if ENV['allow_override']\r
 \r
       Redmine::IMAP.check(imap_options, options)\r
     end\r
index 6c2a07b582f378c03f3dfc957e2a492bf86adbe1..2b74b59775729dc63fb9c2642a379457cc01283b 100644 (file)
@@ -9,3 +9,9 @@ issue_categories_002:
   project_id: 1\r
   assigned_to_id: \r
   id: 2\r
+issue_categories_003: \r
+  name: Stock management\r
+  project_id: 2\r
+  assigned_to_id: \r
+  id: 3\r
+  
\ 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 (file)
index 0000000..1185234
--- /dev/null
@@ -0,0 +1,43 @@
+Return-Path: <jsmith@somenet.foo>
+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" <jsmith@somenet.foo>
+To: <redmine@somenet.foo>
+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
index 6bb638f211ba316192b6f7a18770b4e6f272e1e9..6e8a47c353f5b2c121e040cec047c36117725bb5 100644 (file)
 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