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-8f06a7374b81tags/0.8.0-RC1
@@ -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 |
@@ -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 | |||
@@ -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 |
@@ -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 | |||
@@ -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 |
@@ -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 |