@@ -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, : |
|
|
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 | |
|
26 | ||
|
27 | Example: | |
|
28 | rake redmine:email:receive project=foo RAILS_ENV="production" | |
|
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 | |
|
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 | 43 | END_DESC |
|
30 | 44 | |
|
31 |
task :re |
|
|
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 |
|
|
|
43 |
|
|
|
44 |
|
|
|
45 |
|
|
|
46 |
|
|
|
47 |
|
|
|
48 | Other options: | |
|
49 | * project => identifier of the project the issue should be added to | |
|
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: | |
|
50 | 82 | |
|
51 | Example: | |
|
52 |
|
|
|
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