Redmineのplugin開発
今回、Redmineのプラグインを開発してみてHookやPlugin用の設定ページの作成や独自rakeコマンド、Mail送信機能拡張など様々な機能を触ってみて思ったよりも便利に開発が出来るんだということがわかった。
できたものは超オレオレプログラムのごり押し機能だけど、勉強になったしとりあえず動くしまぁいいやと思った。
要望
チケットの開始時間単位のリマインダー機能。
Redmineには、リマインダー機能は既に存在するがチケット何時から開始とかいう場合に1時間前にリマインダーを送りたいとかができない。
ソース
GitHubに登録しています。
環境
- Redmine1.2
- Rails2.3.11
機能
まず、カスタムフィールドで開始時間を定義する。
ここを参考に「開始時間」のカスタムフィールドを定義しておく。
そして、それに関連するトラッカーを決める。
例えば「イベント」トラッカーにカスタムフィールド「開始時間」をひも付けておく。
そして、定義したトラッカーでチケットを作成した時に、OSのタスク機能を利用して開始時間の○時間前にメール送信用のタスクを登録する。
チケットが完了や却下など、完了を示すステータスで更新されたら登録しておいたタスクを削除する。
かなりごり押しのテクニックなのであまり参考になるようなところはないかもしれない。
実装
本体を傷つけないように、プラグインとして開発する。
まず、プラグインのスケルトンを作成する。
$ cd RAILS_ROOT $ ruby script/generate redmine_plugin ReminderTicket
これで雛形が作成されるので、個別に実装していく。
まずは、Modelを作成する。
既存のMailerモデルを継承して今回用にRemindMailerクラスとしてカスタマイズする。
self.reminder_ticket
このメソッドはrakeコマンドより呼び出される。
引数はチケットID(issue.id)を受け取るようにしておく。
reminder_time
このメソッドがメール送信時に呼び出されるメソッド。
呼び出し元はself.reminder_ticketでdeliver_をつけて呼び出している。
deliver_を頭に付けるとメールが送信されるみたいで、送信先(TOやCC)や件名、本文などを定義する。
render_multipartが呼び出されたところで、Viewに渡される。
app/models/remind_mailer.rb
class RemindMailer < Mailer def self.reminder_ticket(options={}) issue = Issue.find(options[:issue_id]) deliver_reminder_time(issue.assigned_to, issue) if issue.assigned_to && issue.assigned_to.active? end def reminder_time(user, issue) set_language_if_valid user.language recipients issue.recipients cc(issue.watcher_recipients - @recipients) subject l(:mail_subject_reminder_ticket, :start_time => issue.custom_field_values[Setting.plugin_redmine_reminder_ticket['target_custome_field_value_id'].to_i], :count => Setting.plugin_redmine_reminder_ticket['diff_time'].to_i / 3600.0) body :issue => issue, :start_time => issue.custom_field_values[Setting.plugin_redmine_reminder_ticket['target_custome_field_value_id'].to_i], :count => Setting.plugin_redmine_reminder_ticket['diff_time'].to_i / 3600.0, :issue_url => url_for(:controller => 'issues', :action => 'show', :id => issue.id) render_multipart('reminder', body) end end
次にメール送信時のViewと設定用のViewを作成する。
app/views/remind_mailerディレクトリに以下の2つのテンプレートを作成する。(このディレクトリ名はRailsの規約通りModel名と結びつけておく)
メール送信設定でテキスト形式とHTML形式が設定できると思うが、それら2つに対応させておく。
ファイル名はModelのrender_multipartの第一引数で定義した名前.text.html.rhtml | text.plain.rhtml としておく。
View内で使用できるインスタンス変数(@付きの変数)はModel内のbodyでシンボル定義した値となる。
app/views/remind_mailer/reminder.text.html.rhtml
<p><%= l(:mail_body_reminder_ticket, :start_time => @start_time, :count => @count) %></p> <ul> <li><%=h @issue.project %> - <%= "#{@issue.tracker} ##{@issue.id}"%>: <%=h @issue.subject %></li> </ul> <p><%= link_to l(:label_issue), @issue_url %></p>
app/veiws/remind_mailer/remainder.text.plain.rhtml
<%= l(:mail_body_reminder_ticket, :start_time => @start_time, :count => @count) %>: * <%= "#{@issue.project} - #{@issue.tracker} ##{@issue.id}: #{@issue.subject}" %> <%= @issue_url %>
2012.03.06追記
※Redmine1.3以降はテンプレート名が変更されているので注意!!
reminder.text.html.rhtml -> reminder.html.erb
reminder.text.plain.rhtml -> reminder.text.erb
設定用Viewを作成する。
プラグイン内で簡潔する設定内容を編集できる頁を作成する。
これは、Redmineの管理メニュー > プラグインでプラグインの一覧から個別に設定画面へ飛ぶことのできるものである。
app/views/settings/_redmine_reminder_ticket_settings.rhtml
<p> <table> <tr><td> Target Tracker ID </td><td> <%= text_field_tag('settings[target_tracker_id]', @settings['target_tracker_id'])%> </td></tr> <tr><td> Target Custome Field Value ID </td><td> <%= text_field_tag('settings[target_custome_field_value_id]', @settings['target_custome_field_value_id'])%> </td></tr> <tr><td> Diff Time </td><td> <%= text_field_tag('settings[diff_time]', @settings['diff_time'])%> </td></tr> <tr><td> SC </td><td> <%= text_field_tag('settings[sc]', @settings['sc'])%> </td></tr> <tr><td> RU </td><td> <%= text_field_tag('settings[ru]', @settings['ru'])%> </td></tr> <tr><td> RP </td><td> <%= text_field_tag('settings[rp]', @settings['rp'])%> </td></tr> </table> </p>
設定できる内容は、以下の通りとした。
target_tracker_id
- 対象となるトラッカーのID
target_custome_field_value_id
- 対象となる開始時間を定義したカスタムフィールドのID
diff_time
- 何時間前に送られるか
sc
- schtasksのオプション
ru
- schtasksのオプション
rp
- schtasksのオプション
一応多言語対応用にja.ymlを作成しておく。
日本語しか対応させていないけど。
ja: mail_subject_reminder_ticket: "本日%{start_time}から開始されるチケットが%{count}時間後に到来します" mail_body_reminder_ticket: "本日%{start_time}から開始されるチケットが%{count}時間後に到来します"
チケット作成時にタスクを登録して、そのタスクから呼び出されるバッチファイルを作成する。
どこに作成したらいいのかわからない&環境依存なのでもっといい方法があるはず。
rakeコマンド呼び出し用のバッチファイル(Windows専用)。
lib/bat/reminder_ticket.bat
cd C:\redmine rake redmine:send_reminder_ticket issue_id=%1 RAILS_ENV=production
次は要となる部分。
既存のチケット登録、更新処理の後に実行させたい機能をHookというのを使って実現する。
これは、既存コントローラで既に埋め込まれてるHookスクリプトを奪い取って(?)実行してしまう。という機能。
本体に影響を与えずに機能追加したいという場合に有効。
今回は、更新時(edit_after_save)と新規登録時(new_after_save)のHookを利用して
更新時に、完了ステータスなら(schtasksコマンドで)タスク削除
それ以外ならタスク削除&作成で更新処理
新規登録時に、タスク作成処理を実現した。
lib/reminder_ticket/hooks/controller_issues_edit_after_save_hook.rb
module ReminderTicket module Hooks class ControllerIssuesEditAfterSaveHook < Redmine::Hook::ViewListener def controller_issues_edit_after_save(context={}) begin issue = context[:issue] if issue.tracker_id == Setting.plugin_redmine_reminder_ticket['target_tracker_id'].to_i if issue.closing? or issue.closed? Schtasks.delete_from_issue(issue) else Schtasks.delete_from_issue(issue) Schtasks.create_from_issue(issue) end end rescue false end end end end end
lib/reminder_ticket/hooks/controller_issues_new_after_save_hook.rb
module ReminderTicket module Hooks class ControllerIssuesNewAfterSaveHooks < Redmine::Hook::ViewListener def controller_issues_new_after_save(context={}) begin issue = context[:issue] if issue.tracker_id == Setting.plugin_redmine_reminder_ticket['target_tracker_id'].to_i Schtasks.create_from_issue(issue) end rescue false end end end end end
rakeタスクファイルを作成する。
こいつがメール送信をするrakeコマンドを提供する。
バッチファイルで定義した以下のコマンドが打てるようになる。
引数はENV['引数名']で受け取れる。
Modelで定義したRemindMailerに投げてメールが送信される。
rake redmine:send_reminder_ticket issue_id=[issue_id] RAILS_ENV=production
lib/tasks/reminder_ticket.rake
namespace :redmine do task :send_reminder_ticket => :environment do options = {} options[:issue_id] = ENV['issue_id'].to_i if ENV['issue_id'] RemindMailer.reminder_ticket(options) end end
Windowsのタスク機能を提供する「schtasks」を呼び出すモジュールを定義する。
Hook時に呼び出されている。
lib/schtasks.rb
module Schtasks def self.create_from_issue(issue) options = {} options[:tn] = issue.id options[:tr] = "#{RAILS_ROOT}/vendor/plugins/redmine_reminder_ticket/lib/bat/reminder_ticket.bat #{issue.id}" options[:sc] = Setting.plugin_redmine_reminder_ticket['sc'] options[:sd] = issue.start_date.strftime("%Y/%m/%d") options[:st] = (Time.parse("#{options[:sd]} #{issue.custom_field_values[Setting.plugin_redmine_reminder_ticket['target_custome_field_value_id'].to_i]}") - Setting.plugin_redmine_reminder_ticket['diff_time'].to_i).strftime("%H:%M") options[:ru] = Setting.plugin_redmine_reminder_ticket['ru'] options[:rp] = Setting.plugin_redmine_reminder_ticket['rp'] create(options) end def self.create(options) return unless options system "schtasks /create /tn \"#{options[:tn]}\" /tr \"#{options[:tr]}\" /sc \"#{options[:sc]}\" /sd \"#{options[:sd]}\" /st \"#{options[:st]}\" /ru \"#{options[:ru]}\" /rp \"#{options[:rp]}\"" end def self.delete_from_issue(issue) options = {} options[:tn] = issue.id delete(options) end def self.delete(options) return unless options system "schtasks /delete /f /tn #{options[:tn]}" end end
大事なinit.rbファイル。
settingsで定義した:defaultが設定内容となる。
:partialは設定用Viewを指す。
settingsは@settingsとして設定用View内でインスタンス変数としてアクセスできる。
ただし、そのほかのスコープでは、Setting.plugin_redmine_reminder_ticket['設定項目名']でアクセスする必要がある。
命名規則は、Setting.plugin_[プラグイン名]となる。
設定用のModelとかは作成する必要がなく、init.rbでsettingsとして定義した内容が設定内容として保持されるというところに感動した。便利。
最初は個別に設定内容を定数として別ファイルに書こうかと思っていたがその必要がなくなった。
init.rb
require 'redmine' require 'time' require_dependency 'schtasks' require_dependency 'reminder_ticket/hooks/controller_issues_new_after_save_hook' require_dependency 'reminder_ticket/hooks/controller_issues_edit_after_save_hook' Redmine::Plugin.register :redmine_reminder_ticket do name 'Redmine Reminder Ticket plugin' author 'Souichi Saitou' description 'This is a plugin for Redmine' version '0.0.1' url '' author_url 'http://souichi.heroku.com' settings :default => {'target_tracker_id' => 4, 'target_custome_field_value_id' => 0, 'diff_time' => 3600, 'sc' => 'ONCE', 'ru' => nil, 'rp' => nil}, :partial => 'settings/redmine_reminder_ticket_settings' end
2012年はどんなサービスが流行るのかなぁ
2011年はソーシャルサービスブームがすごかったという印象を受けるが、
2012年はどのようなサービスが登場していくのだろうかというのを少し考えてみる。
2012年からはキュレーションサービスがもっと出てくるのではないかと思う。
ソーシャルサービスのお陰でネットをしている時間でもいつでも友人や家族に繋がることができるようになった。
そして、FaceBookやTwitterなど「タイムライン」で今、誰がどのような活動をしているのか知ることができるようになった。
また、スマートフォンなどの使い方にも慣れて自分の好きな情報も集められるようになってきた。
これまで「良いBlog」を探すことが多かったが、そこから一歩進んで「良い記事」をオンラインブックマークやお気に入り機能で集めるようになる。(もしくは、必要に応じてキュレーションサービスでまとめたり、カスタマイズしたり)
そうすると、「その人が集めている情報」から「その人がどんな趣味趣向を持っているか」がわかるようになる。
SNSとキュレーションサービスの普及でより自分に合わせた情報をリアルタイムで受け取ることができるようになる。
2012年はそうしたサービスがもっと盛り上がるのではないかぁ??というか盛り上がって欲しい。
もっと自分が欲しい情報をガンガン集められるようになりたい。
UMLの勉強
UMLモデリングレッスン 21の基本パターンでわかる要求モデルの作り方
- 作者: 平澤章
- 出版社/メーカー: 日経BP社
- 発売日: 2008/01/24
- メディア: 単行本(ソフトカバー)
- 購入: 18人 クリック: 284回
- この商品を含むブログ (25件) を見る
設計においてUML覚えておくと便利だなぁと最近思い出して、
色々調べてたけどやっぱり実践しながらじゃないと覚えないと思っていたところ
実際の事例を元に学べそうな良書にたどり着いた。
今日は休日なので一気に読み込んでみよう。
Mac Book Airよきかな
やりました。
MacBookAirゲット。
1年前くらいの発売時完全に欲しかったのを我慢したかいがありました。
というのもスペック後一歩。というところでした。
MacBookAirの場合は使用用途が外出したときとか
どの部屋でもできるとかそういったこれまで時間がとれなかったところを
埋める役割として期待していたので、メインという考えはまったくありませんでした。
あと、金銭的な問題で上記のような目的のマシンに10万円以上は到底払えないと考えていました。
なので、1年前のスペックでは最下位モデルが今一歩足りなかった。
(拡張すれば確かにクリアできたかもしれないけど、金銭的にアウト)
今回これらの今一歩感が完全に払拭されました。
ばっちり最下位モデルを購入して合間に使ってます。
いやぁ楽しい。
まず、Lionにもっとも適したマシンだなぁとつくづく思う。
OSとの一体感がユーザエクスペリエンス非常に高いです。
itunesやiPhotoは自宅のiMacと共有で見るとかしてストレージ圧迫しなければ得に入れるものがないから、逆に仮想OS入れたりして無駄遣いしています。
とりあえず、言えるのはいいってこと。
Windows環境でのRedmine構築手順
ここの記事を参考にさせて頂き、バージョンを指定しながら構築する手順
構築する環境
- rails2.3.11
- redmine1.2.0
DB
- sqlite3
- actionmailer (2.3.11)
- actionpack (2.3.11)
- activerecord (2.3.11)
- activeresource (2.3.11)
- activesupport (2.3.11)
- cgi_multipart_eof_fix (2.5.0)
- gem_plugin (0.2.3)
- i18n (0.4.2)
- mongrel (1.1.5 x86-mingw32)
- mongrel_service (0.4.0)
- rack (1.1.2)
- rails (2.3.11)
- rake (0.8.7)
- RedCloth (4.2.7 x86-mingw32)
- sqlite3 (1.3.3 x86-mingw32)
- sqlite3-ruby (1.3.3)
Rubyのインストール
ここから1.8.7系をダウンロードし、インストールする。
(途中で、環境変数にパスを通すか、拡張子を紐付けるかといったチェックボックスが出てくるのでどちらもチェックしておく)
コマンドプロンプトで正しくインストールされていることを確認。
$ruby -v ruby 1.8.7 (2011-02-18 patchlevel 334) [i386-mingw32] $gem -v 1.5.2
Railsのインストール
$gem install rails -v 2.3.11 Fetching: rake-0.9.2.gem (100%) Fetching: activesupport-2.3.11.gem (100%) Fetching: activerecord-2.3.11.gem (100%) Fetching: rack-1.1.2.gem (100%) Fetching: actionpack-2.3.11.gem (100%) Fetching: actionmailer-2.3.11.gem (100%) Fetching: activeresource-2.3.11.gem (100%) Fetching: rails-2.3.11.gem (100%) Successfully installed rake-0.9.2 Successfully installed activesupport-2.3.11 Successfully installed activerecord-2.3.11 Successfully installed rack-1.1.2 Successfully installed actionpack-2.3.11 Successfully installed actionmailer-2.3.11 Successfully installed activeresource-2.3.11 Successfully installed rails-2.3.11 8 gems installed Installing ri documentation for rake-0.9.2... Installing ri documentation for activesupport-2.3.11... Installing ri documentation for activerecord-2.3.11... Installing ri documentation for rack-1.1.2... Installing ri documentation for actionpack-2.3.11... Installing ri documentation for actionmailer-2.3.11... Installing ri documentation for activeresource-2.3.11... Installing ri documentation for rails-2.3.11... Installing RDoc documentation for rake-0.9.2... Installing RDoc documentation for activesupport-2.3.11... Installing RDoc documentation for activerecord-2.3.11... Installing RDoc documentation for rack-1.1.2... Installing RDoc documentation for actionpack-2.3.11... Installing RDoc documentation for actionmailer-2.3.11... Installing RDoc documentation for activeresource-2.3.11... Installing RDoc documentation for rails-2.3.11... $rails -v Rails 2.3.11
rakeのダウングレード
$gem uninstall rake Remove executables: rake in addition to the gem? [Yn] y Removing rake You have requested to uninstall the gem: rake-0.9.2 rails-2.3.11 depends on [rake (>= 0.8.3)] If you remove this gems, one or more dependencies will not be met. Continue with Uninstall? [Yn] y Successfully uninstalled rake-0.9.2 $gem install rake -v 0.8.7 Fetching: rake-0.8.7.gem (100%) Successfully installed rake-0.8.7 1 gem installed Installing ri documentation for rake-0.8.7... Installing RDoc documentation for rake-0.8.7...
DevKitのインストール
ここから
DevKit-tdm-32-4.5.2-20110712-1620-sfx.exe をRubyのディレクトリにdevkitとかのディレクトリ作ってそこにでも展開しておく。
環境変数に(例)C:\Ruby\devkit\binのPATHを通しておく。
RedClothのインストール
$gem install RedCloth Fetching: RedCloth-4.2.7-x86-mingw32.gem (100%) Successfully installed RedCloth-4.2.7-x86-mingw32 1 gem installed Installing ri documentation for RedCloth-4.2.7-x86-mingw32... Installing RDoc documentation for RedCloth-4.2.7-x86-mingw32...
sqlite3-rubyのインストール
$gem install sqlite3-ruby Fetching: sqlite3-1.3.3-x86-mingw32.gem (100%) ============================================================================= You've installed the binary version of sqlite3. It was built using SQLite3 version 3.7.3. It's recommended to use the exact same version to avoid potential issues. At the time of building this gem, the necessary DLL files where available in the following download: http://www.sqlite.org/sqlitedll-3_7_3.zip You can put the sqlite3.dll available in this package in your Ruby bin directory, for example C:\Ruby\bin ============================================================================= Fetching: sqlite3-ruby-1.3.3.gem (100%) ####################################################### Hello! The sqlite3-ruby gem has changed it's name to just sqlite3. Rather than installing `sqlite3-ruby`, you should install `sqlite3`. Please update your dependencies accordingly. Thanks from the Ruby sqlite3 team! <3 <3 <3 <3 ####################################################### Successfully installed sqlite3-1.3.3-x86-mingw32 Successfully installed sqlite3-ruby-1.3.3 2 gems installed Installing ri documentation for sqlite3-1.3.3-x86-mingw32... No definition for libversion Enclosing class/module 'mSqlite3' for class Statement not known Installing ri documentation for sqlite3-ruby-1.3.3... Installing RDoc documentation for sqlite3-1.3.3-x86-mingw32... No definition for libversion Enclosing class/module 'mSqlite3' for class Statement not known Installing RDoc documentation for sqlite3-ruby-1.3.3...
mongrel_serviceのインストール
$gem install mongrel_service Fetching: gem_plugin-0.2.3.gem (100%) Fetching: cgi_multipart_eof_fix-2.5.0.gem (100%) Fetching: mongrel-1.1.5-x86-mingw32.gem (100%) Fetching: mongrel_service-0.4.0.gem (100%) Successfully installed gem_plugin-0.2.3 Successfully installed cgi_multipart_eof_fix-2.5.0 Successfully installed mongrel-1.1.5-x86-mingw32 Successfully installed mongrel_service-0.4.0 4 gems installed Installing ri documentation for gem_plugin-0.2.3... Installing ri documentation for cgi_multipart_eof_fix-2.5.0... Installing ri documentation for mongrel-1.1.5-x86-mingw32... Installing ri documentation for mongrel_service-0.4.0... Installing RDoc documentation for gem_plugin-0.2.3... Installing RDoc documentation for cgi_multipart_eof_fix-2.5.0... Installing RDoc documentation for mongrel-1.1.5-x86-mingw32... Installing RDoc documentation for mongrel_service-0.4.0...
i18nのインストール
$gem install i18n -v 0.4.2 Fetching: i18n-0.4.2.gem (100%) Successfully installed i18n-0.4.2 1 gem installed Installing ri documentation for i18n-0.4.2... Installing RDoc documentation for i18n-0.4.2...
sqlite3のダウンロードと配置
Redmineのダウンロードと配置と設定
- ここから「redmine-1.2.0.zip」をダウンロード
- 解凍してできた「redmine-1.2.0」ディレクトリを「redmine」にリネーム
- 「C:\redmine\config\database.yml.example」を「database.yml」にリネーム
- database.ymlのproductionの以下の項目を修正する
production: adapter: sqlite3 database: db/redmine.db
秘密鍵の生成
$cd C:\redmine $rake config/initializers/session_store.rb (in C:/redmine)
DBの移行
$cd C:\redmine $rake db:migrate RAILS_ENV=production
サービスの登録
$mongrel_rails service::install -N "Redmine" -c C:\Redmine -p 80 -e production ** Copying native mongrel_service executable... Redmine service created.
- サービスの一覧から「Redmine」を選択しプロパティを編集する
- スタートアップの種類
- 手動→自動
- スタートアップの種類
- サービスを開始する
エラーが出る場合の回避方法
adminでログインできない場合
vendor/rails/actionpack/lib/action_controller/request.rb 449行目を以下に修正
session.destroy if session and session.respond_to?(:destroy)
You are being redirected.から進まない
ここを参考にC:\redmine\config\initializers\mongrel.rbを作成
完成
あとは、ここを参考に初期設定して完成。
watirでjavascriptの実行結果として返されるhtmlを取得する
最近、html解析作業にもっぱら心を奪われています。
特にwatirとhpricotは使いやすくていいなと思ってます。
指定したurlのhtmlを取得する。(javascriptでも可)
#! ruby require 'rubygems' require 'watir' class Foo attr_reader:html def initialize(url) browser = Watir::Browser.new browser.go(url) browser.wait # ブラウザの読込が終わるまで待つ @html = browser.html end end f = Foo.new(ここにurl) f.html
iphoneから投稿なので、間違ってるかも。