Redmineでリマインダー
社内で使ってるRedmineのチケットを定期的に状況報告をメールでするようにしたい、という話。0.8からそういう機能がつくんだけど、いまいち自分の使い方と合ってないみたいなので、こちらを参考にして自分で書いてみた。
やりたいことは次のようなこと
- 決まった時間(朝と夕方)にプロジェクトごとにチケット情報をまとめる
- 期限が過ぎているチケット
- 今日が期限のチケット
- 今日作成されたチケット
- 今日更新されたチケット
- プロジェクトメンバーへメール送信
- 1メール/1プロジェクト
実際書いてみると、まだまだRedmine/RailsはおろかRubyのことも全然わかんないなー。class << selfとか何のためにやるんだろう?とか、DBからデータ取る方法が雑過ぎるような…、とかいろいろ勉強不足なのかよくわかりました。
更新: 2009/4/21
- SMTPサーバの設定をmail.ymlから読むようにしました
- サブミッションポートに対応(587番ポート使うやつ)
動作環境
- 実行にはgem install tlsmailが必要です
- 動作確認はRuby1.8.7で行っています
- %REDMINE_HOME%\lib\hogeに適当に名前をつけて配置してください
- %REDMINE_HOME%\libでも構いませんが、その場合は5行目の"../../config/environment"を"../config/environment"に変えてください
#!C:\Program Files\ruby-1.8\bin\ruby ENV['RAILS_ENV'] ||= 'production' require "rubygems" require "tlsmail" require File.join(File.dirname(__FILE__), "../../config/environment") require 'time' require 'kconv' require 'tmail' require 'net/smtp' class Scheduler class << self def reminder statuses = IssueStatus.find(:all) $host = "hogehost:3000" Project.find(:all, :conditions=>["status = ?", 1], :order => "name DESC").each do |project| members = project.assignable_users send_bodys = Array.new send_bodys << "Redmine Reminder #{Date.today} #{project.name}" send_bodys << <<-EOF <html> <head> <style> table.deadline{background: #cc3333} table.today {background: #ffff33} table.created {background: #99ffcc} table.updated {background: #99ffff} </style> </head> <body> EOF send_bodys << "<h1><a href=\"http://#{$host}/projects/#{project.identifier}/issues\">#{project.name}</a> - #{Date.today}</h1>" send_bodys << "<p>[#{members.join(',')}]</p>" send_bodys << "" send_bodys << "" issues_by_expire = project.issues.select{|i| !i.closed?} issue_bodys = Array.new ### ### 期限に関するチケット ### deadline_issues = issues_by_expire.select {|i| i.due_date.nil? || i.overdue?} unless deadline_issues.empty? issue_bodys << "<h3>■期限切れのチケット</h3>" issue_bodys << %!<table class="deadline">! issue_bodys << "<tr><th>id</th><th>期限</th><th>担当</th><th>件名</th></tr>" deadline_issues.each do |i| assigned = members.select {|member| member.id == i.assigned_to_id}.first url = "http://#{$host}/issues/show/#{i.id}" issue_bodys << "<tr><td><a href=\"#{url}\">#{i.id}</a></td><td>#{i.due_date}</td><td>#{assigned.name if assigned}</td><td>#{i.subject}</td></tr>" end issue_bodys << "</table>" end today_issues = issues_by_expire.select {|i| i.due_date == Date.today} unless today_issues.empty? issue_bodys << "<h3>■期限が今日のチケット</h3>" issue_bodys << %!<table class="today">! issue_bodys << "<tr><th>id</th><th>担当</th><th>件名</th></tr>" today_issues.each do |i| assigned = members.select {|member| member.id == i.assigned_to_id}.first url = "http://#{$host}/issues/show/#{i.id}" issue_bodys << "<tr><td><a href=\"#{url}\">#{i.id}</a></td><td>#{assigned.name if assigned}</td><td>#{i.subject}</td></tr>" end issue_bodys << "</table>" end ### ### 今日更新のあったチケット ### issues_by_update = Issue.find(:all, :conditions=>["project_id = ?", project.id]) created_issues = issues_by_update.select {|i| i.created_on.year == Time.now.year && i.created_on.month == Time.now.month && i.created_on.day == Time.now.day} unless created_issues.empty? issue_bodys << "<h3>□今日作成されたチケット</h3>" issue_bodys << %!<table class="created">! issue_bodys << "<tr><th>id</th><th>予定時間</th><th>起票</th><th>担当</th><th>件名</th></tr>" created_issues.each do |i| assigned = members.select {|member| member.id == i.assigned_to_id}.first author = members.select {|member| member.id == i.author_id}.first url = "http://#{$host}/issues/show/#{i.id}" issue_bodys << "<tr><td><a href=\"#{url}\">#{i.id}</a></td><td>#{i.estimated_hours}h</td><td>#{author.name}</td><td>#{assigned.name if assigned}</td><td>#{i.subject}</td></tr>" end issue_bodys << "</table>" end updated_issues = issues_by_update.select {|i| i.updated_on.year == Time.now.year && i.updated_on.month == Time.now.month && i.updated_on.day == Time.now.day} unless updated_issues.empty? issue_bodys << "<h3>□今日更新されたチケット</h3>" issue_bodys << %!<table class="updated">! issue_bodys << "<tr><th>id</th><th>状態</th><th>進捗</th><th>担当</th><th>件名</th></tr>" updated_issues.each do |i| assigned = members.select {|member| member.id == i.assigned_to_id}.first status = statuses.select {|s| s.id == i.status_id}.first url = "http://#{$host}/issues/show/#{i.id}" issue_bodys << "<tr><td><a href=\"#{url}\">#{i.id}</a></td><td>#{status.name}</td><td>#{i.done_ratio}%</td><td>#{assigned.name if assigned}</td><td>#{i.subject}</td></tr>" end issue_bodys << "</table>" end next if issue_bodys.empty? issue_bodys << "</body></html>" #send_schedule_mail(send_bodys.concat(issue_bodys)) send_schedule_mail(send_bodys.concat(issue_bodys), members.map(&:mail)) end end private def send_schedule_mail(body, send_addr=["デフォルトのメールアドレス"]) finddate = body.shift mail = TMail::Mail.new mail.to = *(send_addr) mail.from = Setting.mail_from conv_subject = Kconv.toutf8(finddate).split(//, 1).pack('m').chomp encoded_subject = "=?UTF-8?B?" + conv_subject.gsub('\n', '') + "?=" mail.subject = encoded_subject mail.date = Time.now mail.mime_version = '1.0' message = TMail::Mail.new message.set_content_type('text', 'html', {'charset'=>'iso-2022-jp'}) message.transfer_encoding = '7bit' message.body = Kconv.tojis(body.join("\r\n")) mail.parts.push(message) mail.write_back filename = File.join(File.dirname(__FILE__), '../../config/email.yml') mailconfig = YAML::load_file(filename)['production']['smtp_settings'] smtp_server = mailconfig['address'] port = mailconfig['port'] domain = mailconfig['domain'] authentication = mailconfig['authentication'] user_name = mailconfig['user_name'] password = mailconfig['password'] begin # tls使わない場合はこっち # Net::SMTP.start(smtp_server) do |smtp| # smtp.sendmail(mail.encoded, mail.from, *(send_addr)) # end Net::SMTP.enable_tls(OpenSSL::SSL::VERIFY_NONE) Net::SMTP.start(smtp_server, port, domain, user_name, password, authentication) do |smtp| smtp.send_mail(mail.encoded, mail.from, *(send_addr)) end rescue => ex puts ex.message if ex.class == Net::SMTPServerBusy sleep 3 retry end end true end end end if __FILE__ == $0 Scheduler.reminder end