##// 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 23 attr_reader :email, :user
24 24
25 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 34 super email
28 35 end
29 36
@@ -66,11 +73,13 class MailHandler < ActionMailer::Base
66 73 # Creates a new issue
67 74 def receive_issue
68 75 project = target_project
69 # TODO: make the tracker configurable
70 tracker = project.trackers.find(:first)
76 tracker = (get_keyword(:tracker) && project.trackers.find_by_name(get_keyword(: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 80 # check permission
72 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 83 issue.subject = email.subject.chomp
75 84 issue.description = email.plain_text_body.chomp
76 85 issue.save!
@@ -84,13 +93,7 class MailHandler < ActionMailer::Base
84 93 # TODO: other ways to specify project:
85 94 # * parse the email To field
86 95 # * specific project (eg. Setting.mail_handler_target_project)
87 identifier = if !@@handler_options[:project].blank?
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)
96 target = Project.find_by_identifier(get_keyword(:project))
94 97 raise MissingInformation.new('Unable to determine target project') if target.nil?
95 98 target
96 99 end
@@ -120,6 +123,14 class MailHandler < ActionMailer::Base
120 123 end
121 124 end
122 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 134 end
124 135
125 136 class TMail::Mail
@@ -12,16 +12,22 require 'getoptlong'
12 12 class RedmineMailHandler
13 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 17 def initialize
18 self.issue_attributes = {}
19
18 20 opts = GetoptLong.new(
19 21 [ '--help', '-h', GetoptLong::NO_ARGUMENT ],
20 22 [ '--version', '-V', GetoptLong::NO_ARGUMENT ],
21 23 [ '--verbose', '-v', GetoptLong::NO_ARGUMENT ],
22 24 [ '--url', '-u', GetoptLong::REQUIRED_ARGUMENT ],
23 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 33 opts.each do |opt, arg|
@@ -36,8 +42,10 class RedmineMailHandler
36 42 self.verbose = true
37 43 when '--version'
38 44 puts VERSION; exit
39 when '--project'
40 self.project = arg.dup
45 when '--project', '--tracker', '--category', '--priority'
46 self.issue_attributes[opt.gsub(%r{^\-\-}, '')] = arg.dup
47 when '--allow-override'
48 self.allow_override = arg.dup
41 49 end
42 50 end
43 51
@@ -46,8 +54,11 class RedmineMailHandler
46 54
47 55 def submit(email)
48 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 61 debug "Posting to #{uri}..."
50 data = { 'key' => key, 'project' => project, 'email' => email }
51 62 response = Net::HTTP.post_form(URI.parse(uri), data)
52 63 debug "Response received: #{response.code}"
53 64 response.code == 201 ? 0 : 1
@@ -56,17 +67,39 class RedmineMailHandler
56 67 private
57 68
58 69 def usage
59 puts "Usage: rdm-mailhandler [options] --url=<Redmine URL> --key=<API key>"
60 puts "Reads an email from standard input and forward it to a Redmine server"
61 puts
62 puts "Options:"
63 puts " --help show this help"
64 puts " --verbose show extra information"
65 puts " --project identifier of the target project"
66 puts
67 puts "Examples:"
68 puts " rdm-mailhandler --url http://redmine.domain.foo --key secret"
69 puts " rdm-mailhandler --url https://redmine.domain.foo --key secret --project foo"
70 puts <<-USAGE
71 Usage: rdm-mailhandler [options] --url=<Redmine URL> --key=<API key>
72 Reads an email from standard input and forward it to a Redmine server
73
74 Required:
75 -u, --url URL of the Redmine server
76 -k, --key Redmine API key
77
78 General options:
79 -h, --help show this help
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 103 exit
71 104 end
72 105
@@ -21,16 +21,31 namespace :redmine do
21 21 desc <<-END_DESC
22 22 Read an email from standard input.
23 23
24 Available options:
25 * project => identifier of the project the issue should be added to
24 Issue attributes control options:
25 project=PROJECT identifier of the target project
26 tracker=TRACKER name of the target tracker
27 category=CATEGORY name of the target category
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
26 32
27 Example:
28 rake redmine:email:receive project=foo RAILS_ENV="production"
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 43 END_DESC
30 44
31 task :receive => :environment do
32 options = {}
33 options[:project] = ENV['project'] if ENV['project']
45 task :read => :environment do
46 options = { :issue => {} }
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 50 MailHandler.receive(STDIN.read, options)
36 51 end
@@ -39,17 +54,37 END_DESC
39 54 Read emails from an IMAP server.
40 55
41 56 Available IMAP options:
42 * host => IMAP server host (default: 127.0.0.1)
43 * port => IMAP server port (default: 143)
44 * ssl => Use SSL? (default: false)
45 * username => IMAP account
46 * password => IMAP password
47 * folder => IMAP folder to read (default: INBOX)
48 Other options:
49 * project => identifier of the project the issue should be added to
50
51 Example:
52 rake redmine:email:receive_iamp host=imap.foo.bar username=redmine@somenet.foo password=xxx project=foo RAILS_ENV="production"
57 host=HOST IMAP server host (default: 127.0.0.1)
58 port=PORT IMAP server port (default: 143)
59 ssl=SSL Use SSL? (default: false)
60 username=USERNAME IMAP account
61 password=PASSWORD IMAP password
62 folder=FOLDER IMAP folder to read (default: INBOX)
63
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:
82
83 rake redmine:email:receive_iamp 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 88 END_DESC
54 89
55 90 task :receive_imap => :environment do
@@ -60,8 +95,9 END_DESC
60 95 :password => ENV['password'],
61 96 :folder => ENV['folder']}
62 97
63 options = {}
64 options[:project] = ENV['project'] if ENV['project']
98 options = { :issue => {} }
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 102 Redmine::IMAP.check(imap_options, options)
67 103 end
@@ -9,3 +9,9 issue_categories_002:
9 9 project_id: 1
10 10 assigned_to_id:
11 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 18 require File.dirname(__FILE__) + '/../test_helper'
19 19
20 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 31 FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures/mail_handler'
24 32
@@ -38,8 +46,36 class MailHandlerTest < Test::Unit::TestCase
38 46 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
39 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 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 79 assert issue.is_a?(Issue)
44 80 assert !issue.new_record?
45 81 issue.reload
General Comments 0
You need to be logged in to leave comments. Login now