##// END OF EJS Templates
Mail handler: more control over issue attributes (#1110)....
Jean-Philippe Lang -
r1629:40efaae6d5a2
parent child
Show More
@@ -0,0 +1,43
1 Return-Path: <jsmith@somenet.foo>
2 Received: from osiris ([127.0.0.1])
3 by OSIRIS
4 with hMailServer ; Sun, 22 Jun 2008 12:28:07 +0200
5 Message-ID: <000501c8d452$a95cd7e0$0a00a8c0@osiris>
6 From: "John Smith" <jsmith@somenet.foo>
7 To: <redmine@somenet.foo>
8 Subject: New ticket on a given project
9 Date: Sun, 22 Jun 2008 12:28:07 +0200
10 MIME-Version: 1.0
11 Content-Type: text/plain;
12 format=flowed;
13 charset="iso-8859-1";
14 reply-type=original
15 Content-Transfer-Encoding: 7bit
16 X-Priority: 3
17 X-MSMail-Priority: Normal
18 X-Mailer: Microsoft Outlook Express 6.00.2900.2869
19 X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.2869
20
21 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas imperdiet
22 turpis et odio. Integer eget pede vel dolor euismod varius. Phasellus
23 blandit eleifend augue. Nulla facilisi. Duis id diam. Class aptent taciti
24 sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In
25 in urna sed tellus aliquet lobortis. Morbi scelerisque tortor in dolor. Cras
26 sagittis odio eu lacus. Aliquam sem tortor, consequat sit amet, vestibulum
27 id, iaculis at, lectus. Fusce tortor libero, congue ut, euismod nec, luctus
28 eget, eros. Pellentesque tortor enim, feugiat in, dignissim eget, tristique
29 sed, mauris. Pellentesque habitant morbi tristique senectus et netus et
30 malesuada fames ac turpis egestas. Quisque sit amet libero. In hac habitasse
31 platea dictumst.
32
33 Nulla et nunc. Duis pede. Donec et ipsum. Nam ut dui tincidunt neque
34 sollicitudin iaculis. Duis vitae dolor. Vestibulum eget massa. Sed lorem.
35 Nullam volutpat cursus erat. Cras felis dolor, lacinia quis, rutrum et,
36 dictum et, ligula. Sed erat nibh, gravida in, accumsan non, placerat sed,
37 massa. Sed sodales, ante fermentum ultricies sollicitudin, massa leo
38 pulvinar dui, a gravida orci mi eget odio. Nunc a lacus.
39
40 Project: onlinestore
41 Tracker: Feature request
42 category: Stock management
43 priority: Urgent
@@ -23,7 +23,14 class MailHandler < ActionMailer::Base
23 attr_reader :email, :user
23 attr_reader :email, :user
24
24
25 def self.receive(email, options={})
25 def self.receive(email, options={})
26 @@handler_options = options
26 @@handler_options = options.dup
27
28 @@handler_options[:issue] ||= {}
29
30 @@handler_options[:allow_override] = @@handler_options[:allow_override].split(',').collect(&:strip) if @@handler_options[:allow_override].is_a?(String)
31 @@handler_options[:allow_override] ||= []
32 # Project needs to be overridable if not specified
33 @@handler_options[:allow_override] << 'project' unless @@handler_options[:issue].has_key?(:project)
27 super email
34 super email
28 end
35 end
29
36
@@ -66,11 +73,13 class MailHandler < ActionMailer::Base
66 # Creates a new issue
73 # Creates a new issue
67 def receive_issue
74 def receive_issue
68 project = target_project
75 project = target_project
69 # TODO: make the tracker configurable
76 tracker = (get_keyword(:tracker) && project.trackers.find_by_name(get_keyword(:tracker))) || project.trackers.find(:first)
70 tracker = project.trackers.find(:first)
77 category = (get_keyword(:category) && project.issue_categories.find_by_name(get_keyword(:category)))
78 priority = (get_keyword(:priority) && Enumeration.find_by_opt_and_name('IPRI', get_keyword(:priority)))
79
71 # check permission
80 # check permission
72 raise UnauthorizedAction unless user.allowed_to?(:add_issues, project)
81 raise UnauthorizedAction unless user.allowed_to?(:add_issues, project)
73 issue = Issue.new(:author => user, :project => project, :tracker => tracker)
82 issue = Issue.new(:author => user, :project => project, :tracker => tracker, :category => category, :priority => priority)
74 issue.subject = email.subject.chomp
83 issue.subject = email.subject.chomp
75 issue.description = email.plain_text_body.chomp
84 issue.description = email.plain_text_body.chomp
76 issue.save!
85 issue.save!
@@ -84,13 +93,7 class MailHandler < ActionMailer::Base
84 # TODO: other ways to specify project:
93 # TODO: other ways to specify project:
85 # * parse the email To field
94 # * parse the email To field
86 # * specific project (eg. Setting.mail_handler_target_project)
95 # * specific project (eg. Setting.mail_handler_target_project)
87 identifier = if !@@handler_options[:project].blank?
96 target = Project.find_by_identifier(get_keyword(:project))
88 @@handler_options[:project]
89 elsif email.plain_text_body =~ %r{^Project:[ \t]*(.+)$}i
90 $1
91 end
92
93 target = Project.find_by_identifier(identifier.to_s)
94 raise MissingInformation.new('Unable to determine target project') if target.nil?
97 raise MissingInformation.new('Unable to determine target project') if target.nil?
95 target
98 target
96 end
99 end
@@ -120,6 +123,14 class MailHandler < ActionMailer::Base
120 end
123 end
121 end
124 end
122 end
125 end
126
127 def get_keyword(attr)
128 if @@handler_options[:allow_override].include?(attr.to_s) && email.plain_text_body =~ /^#{attr}:[ \t]*(.+)$/i
129 $1.strip
130 elsif !@@handler_options[:issue][attr].blank?
131 @@handler_options[:issue][attr]
132 end
133 end
123 end
134 end
124
135
125 class TMail::Mail
136 class TMail::Mail
@@ -12,16 +12,22 require 'getoptlong'
12 class RedmineMailHandler
12 class RedmineMailHandler
13 VERSION = '0.1'
13 VERSION = '0.1'
14
14
15 attr_accessor :verbose, :project, :url, :key
15 attr_accessor :verbose, :issue_attributes, :allow_override, :url, :key
16
16
17 def initialize
17 def initialize
18 self.issue_attributes = {}
19
18 opts = GetoptLong.new(
20 opts = GetoptLong.new(
19 [ '--help', '-h', GetoptLong::NO_ARGUMENT ],
21 [ '--help', '-h', GetoptLong::NO_ARGUMENT ],
20 [ '--version', '-V', GetoptLong::NO_ARGUMENT ],
22 [ '--version', '-V', GetoptLong::NO_ARGUMENT ],
21 [ '--verbose', '-v', GetoptLong::NO_ARGUMENT ],
23 [ '--verbose', '-v', GetoptLong::NO_ARGUMENT ],
22 [ '--url', '-u', GetoptLong::REQUIRED_ARGUMENT ],
24 [ '--url', '-u', GetoptLong::REQUIRED_ARGUMENT ],
23 [ '--key', '-k', GetoptLong::REQUIRED_ARGUMENT],
25 [ '--key', '-k', GetoptLong::REQUIRED_ARGUMENT],
24 [ '--project', '-p', GetoptLong::REQUIRED_ARGUMENT ]
26 [ '--project', '-p', GetoptLong::REQUIRED_ARGUMENT ],
27 [ '--tracker', '-t', GetoptLong::REQUIRED_ARGUMENT],
28 [ '--category', GetoptLong::REQUIRED_ARGUMENT],
29 [ '--priority', GetoptLong::REQUIRED_ARGUMENT],
30 [ '--allow-override', '-o', GetoptLong::REQUIRED_ARGUMENT]
25 )
31 )
26
32
27 opts.each do |opt, arg|
33 opts.each do |opt, arg|
@@ -36,8 +42,10 class RedmineMailHandler
36 self.verbose = true
42 self.verbose = true
37 when '--version'
43 when '--version'
38 puts VERSION; exit
44 puts VERSION; exit
39 when '--project'
45 when '--project', '--tracker', '--category', '--priority'
40 self.project = arg.dup
46 self.issue_attributes[opt.gsub(%r{^\-\-}, '')] = arg.dup
47 when '--allow-override'
48 self.allow_override = arg.dup
41 end
49 end
42 end
50 end
43
51
@@ -46,8 +54,11 class RedmineMailHandler
46
54
47 def submit(email)
55 def submit(email)
48 uri = url.gsub(%r{/*$}, '') + '/mail_handler'
56 uri = url.gsub(%r{/*$}, '') + '/mail_handler'
57
58 data = { 'key' => key, 'email' => email, 'allow_override' => allow_override }
59 issue_attributes.each { |attr, value| data["issue[#{attr}]"] = value }
60
49 debug "Posting to #{uri}..."
61 debug "Posting to #{uri}..."
50 data = { 'key' => key, 'project' => project, 'email' => email }
51 response = Net::HTTP.post_form(URI.parse(uri), data)
62 response = Net::HTTP.post_form(URI.parse(uri), data)
52 debug "Response received: #{response.code}"
63 debug "Response received: #{response.code}"
53 response.code == 201 ? 0 : 1
64 response.code == 201 ? 0 : 1
@@ -56,17 +67,39 class RedmineMailHandler
56 private
67 private
57
68
58 def usage
69 def usage
59 puts "Usage: rdm-mailhandler [options] --url=<Redmine URL> --key=<API key>"
70 puts <<-USAGE
60 puts "Reads an email from standard input and forward it to a Redmine server"
71 Usage: rdm-mailhandler [options] --url=<Redmine URL> --key=<API key>
61 puts
72 Reads an email from standard input and forward it to a Redmine server
62 puts "Options:"
73
63 puts " --help show this help"
74 Required:
64 puts " --verbose show extra information"
75 -u, --url URL of the Redmine server
65 puts " --project identifier of the target project"
76 -k, --key Redmine API key
66 puts
77
67 puts "Examples:"
78 General options:
68 puts " rdm-mailhandler --url http://redmine.domain.foo --key secret"
79 -h, --help show this help
69 puts " rdm-mailhandler --url https://redmine.domain.foo --key secret --project foo"
80 -v, --verbose show extra information
81 -V, --version show version information and exit
82
83 Issue attributes control options:
84 -p, --project=PROJECT identifier of the target project
85 -t, --tracker=TRACKER name of the target tracker
86 --category=CATEGORY name of the target category
87 --priority=PRIORITY name of the target priority
88 -o, --allow-override=ATTRS allow email content to override attributes
89 specified by previous options
90 ATTRS is a comma separated list of attributes
91
92 Examples:
93 # No project specified. Emails MUST contain the 'Project' keyword:
94 rdm-mailhandler --url http://redmine.domain.foo --key secret
95
96 # Fixed project and default tracker specified, but emails can override
97 # both tracker and priority attributes:
98 rdm-mailhandler --url https://domain.foo/redmine --key secret \\
99 --project foo \\
100 --tracker bug \\
101 --allow-override tracker,priority
102 USAGE
70 exit
103 exit
71 end
104 end
72
105
@@ -21,16 +21,31 namespace :redmine do
21 desc <<-END_DESC
21 desc <<-END_DESC
22 Read an email from standard input.
22 Read an email from standard input.
23
23
24 Available options:
24 Issue attributes control options:
25 * project => identifier of the project the issue should be added to
25 project=PROJECT identifier of the target project
26
26 tracker=TRACKER name of the target tracker
27 Example:
27 category=CATEGORY name of the target category
28 rake redmine:email:receive project=foo RAILS_ENV="production"
28 priority=PRIORITY name of the target priority
29 allow_override=ATTRS allow email content to override attributes
30 specified by previous options
31 ATTRS is a comma separated list of attributes
32
33 Examples:
34 # No project specified. Emails MUST contain the 'Project' keyword:
35 rake redmine:email:read RAILS_ENV="production" < raw_email
36
37 # Fixed project and default tracker specified, but emails can override
38 # both tracker and priority attributes:
39 rake redmine:email:read RAILS_ENV="production" \\
40 project=foo \\
41 tracker=bug \\
42 allow_override=tracker,priority < raw_email
29 END_DESC
43 END_DESC
30
44
31 task :receive => :environment do
45 task :read => :environment do
32 options = {}
46 options = { :issue => {} }
33 options[:project] = ENV['project'] if ENV['project']
47 %w(project tracker category priority).each { |a| options[:issue][a.to_sym] = ENV[a] if ENV[a] }
48 options[:allow_override] = ENV['allow_override'] if ENV['allow_override']
34
49
35 MailHandler.receive(STDIN.read, options)
50 MailHandler.receive(STDIN.read, options)
36 end
51 end
@@ -39,17 +54,37 END_DESC
39 Read emails from an IMAP server.
54 Read emails from an IMAP server.
40
55
41 Available IMAP options:
56 Available IMAP options:
42 * host => IMAP server host (default: 127.0.0.1)
57 host=HOST IMAP server host (default: 127.0.0.1)
43 * port => IMAP server port (default: 143)
58 port=PORT IMAP server port (default: 143)
44 * ssl => Use SSL? (default: false)
59 ssl=SSL Use SSL? (default: false)
45 * username => IMAP account
60 username=USERNAME IMAP account
46 * password => IMAP password
61 password=PASSWORD IMAP password
47 * folder => IMAP folder to read (default: INBOX)
62 folder=FOLDER IMAP folder to read (default: INBOX)
48 Other options:
63
49 * project => identifier of the project the issue should be added to
64 Issue attributes control options:
65 project=PROJECT identifier of the target project
66 tracker=TRACKER name of the target tracker
67 category=CATEGORY name of the target category
68 priority=PRIORITY name of the target priority
69 allow_override=ATTRS allow email content to override attributes
70 specified by previous options
71 ATTRS is a comma separated list of attributes
72
73 Examples:
74 # No project specified. Emails MUST contain the 'Project' keyword:
75
76 rake redmine:email:receive_iamp RAILS_ENV="production" \\
77 host=imap.foo.bar username=redmine@somenet.foo password=xxx
78
79
80 # Fixed project and default tracker specified, but emails can override
81 # both tracker and priority attributes:
50
82
51 Example:
83 rake redmine:email:receive_iamp RAILS_ENV="production" \\
52 rake redmine:email:receive_iamp host=imap.foo.bar username=redmine@somenet.foo password=xxx project=foo RAILS_ENV="production"
84 host=imap.foo.bar username=redmine@somenet.foo password=xxx ssl=1 \\
85 project=foo \\
86 tracker=bug \\
87 allow_override=tracker,priority
53 END_DESC
88 END_DESC
54
89
55 task :receive_imap => :environment do
90 task :receive_imap => :environment do
@@ -60,8 +95,9 END_DESC
60 :password => ENV['password'],
95 :password => ENV['password'],
61 :folder => ENV['folder']}
96 :folder => ENV['folder']}
62
97
63 options = {}
98 options = { :issue => {} }
64 options[:project] = ENV['project'] if ENV['project']
99 %w(project tracker category priority).each { |a| options[:issue][a.to_sym] = ENV[a] if ENV[a] }
100 options[:allow_override] = ENV['allow_override'] if ENV['allow_override']
65
101
66 Redmine::IMAP.check(imap_options, options)
102 Redmine::IMAP.check(imap_options, options)
67 end
103 end
@@ -9,3 +9,9 issue_categories_002:
9 project_id: 1
9 project_id: 1
10 assigned_to_id:
10 assigned_to_id:
11 id: 2
11 id: 2
12 issue_categories_003:
13 name: Stock management
14 project_id: 2
15 assigned_to_id:
16 id: 3
17 No newline at end of file
@@ -18,7 +18,15
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19
19
20 class MailHandlerTest < Test::Unit::TestCase
20 class MailHandlerTest < Test::Unit::TestCase
21 fixtures :users, :projects, :enabled_modules, :roles, :members, :issues, :trackers, :enumerations
21 fixtures :users, :projects,
22 :enabled_modules,
23 :roles,
24 :members,
25 :issues,
26 :trackers,
27 :projects_trackers,
28 :enumerations,
29 :issue_categories
22
30
23 FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures/mail_handler'
31 FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures/mail_handler'
24
32
@@ -38,8 +46,36 class MailHandlerTest < Test::Unit::TestCase
38 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
46 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
39 end
47 end
40
48
49 def test_add_issue_with_attributes_override
50 issue = submit_email('ticket_with_attributes.eml', :allow_override => 'tracker,category,priority')
51 assert issue.is_a?(Issue)
52 assert !issue.new_record?
53 issue.reload
54 assert_equal 'New ticket on a given project', issue.subject
55 assert_equal User.find_by_login('jsmith'), issue.author
56 assert_equal Project.find(2), issue.project
57 assert_equal 'Feature request', issue.tracker.to_s
58 assert_equal 'Stock management', issue.category.to_s
59 assert_equal 'Urgent', issue.priority.to_s
60 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
61 end
62
63 def test_add_issue_with_partial_attributes_override
64 issue = submit_email('ticket_with_attributes.eml', :issue => {:priority => 'High'}, :allow_override => ['tracker'])
65 assert issue.is_a?(Issue)
66 assert !issue.new_record?
67 issue.reload
68 assert_equal 'New ticket on a given project', issue.subject
69 assert_equal User.find_by_login('jsmith'), issue.author
70 assert_equal Project.find(2), issue.project
71 assert_equal 'Feature request', issue.tracker.to_s
72 assert_nil issue.category
73 assert_equal 'High', issue.priority.to_s
74 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
75 end
76
41 def test_add_issue_with_attachment_to_specific_project
77 def test_add_issue_with_attachment_to_specific_project
42 issue = submit_email('ticket_with_attachment.eml', :project => 'onlinestore')
78 issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'})
43 assert issue.is_a?(Issue)
79 assert issue.is_a?(Issue)
44 assert !issue.new_record?
80 assert !issue.new_record?
45 issue.reload
81 issue.reload
General Comments 0
You need to be logged in to leave comments. Login now