JIRAで外部のDBにあるデータを課題のフィールドに使用する #augj

Atlassian Advent Calendar 2012 21日目のエントリーです。

最近試したJIRA Database Values Pluginについて書いてみたいと思います。

Database Values Pluginは、その名のとおりDBに入っているデータから課題のフィールドを作成することができるプラグインです。例えば、顧客の要望を課題として登録する際に、別の顧客管理DBからデータをとってきて、リストから選択するようなことができます。

試したJIRAのバージョンは5.0.2だったので、最新版とは少し異なるかもしれません。接続先DBはMySQLで試しました。

インストール手順

まず、JIRA Database Values Pluginから、プラグインのファイル(jarファイル)をダウンロードしてきて、JIRAのインストール先のatlassian-jiraWEB-INF/lib にコピーし、JIRAを再起動します。

最新のプラグインは、JIRAの管理画面から直接インストールできるのですが、このプラグインは少し古いインストール方法にしか対応していないようです。

JIRAを再起動すると、管理画面のカスタムフィールドの追加画面に“Database Values Selection Field”が表示されるので、カスタムフィールドを追加します。

f:id:w650:20121220233132p:plain

次に、追加したカスタムフィールドの編集画面を開き、URLに含まれるID(id=10155 のようなパラメータがあります)をメモって、atlassian-jira\WEB-INF/classes/jira-database-values-plugin-ID.properties という名称のファイルを作成します(IDの部分に10155のような数字が入ります)。そのファイルに、以下のようにDB接続パラメータやプラグインの設定を記述します。

# The database connection parameters
database.driver=com.mysql.jdbc.Driver
database.user=ユーザー名
database.password=パスワード
database.connection.url=jdbc:mysql://サーバー名/データベース名

# Cache Timeout (= 15 minutes by default). The actual db is queried only once and then the results are kept in the cache for the given timeout. Uncomment the line below to change it.
#cache.timeout=900000

# The SQL Query that will be executed on the database
sql.query=select id, name, from users
# The column number (starting from 0) that contains the primary key of the returned data.
primarykey.column.number=0
# The pattern that should be used to render the data in view mode. Use {column_number} as placeholders. You can use HTML, but make sure you close your tags properly!
rendering.viewpattern={1}
# The pattern that should be used to render the data in edit mode. Use {column_number} as placeholders.
rendering.editpattern={1}
# The pattern that should be used to render the data in searcher. Use {column_number} as placeholders.
rendering.searchpattern={1}
# This is used when sorting in the issue navigator. When not defined, the 'rendering.viewpattern' is used.
rendering.sortpattern={1}
# Use 0 to have a combobox for editing, 1 to have AJAX-style input field, 2 for cascading select
edit.type=1
# Use 0 to have a list for searching, 1 to have AJAX-style input field
search.type=1
# The pattern that is used for the history and the activity view. If not specified, the 'rendering.viewpattern' is used. Note that you cannot use HTML.
rendering.changelog.viewpattern={1}
# The pattern that is used for the pie chart, 2d filter statistics and single level group by. If not specified, the 'rendering.viewpattern' is used. Note that you cannot use HTML.
rendering.statistics.viewpattern={1}

設定の詳細はこちらにあります。接続先情報と、データを取得するためのsqlを指定し、取得したデータを使用して表示用のパターン等を指定しています。表示にはHTMLも使えるので、別ページにリンクさせることもできます。

JIRAを再起動し、追加したカスタムフィールドを課題編集画面に追加すると使用可能になります。

プルダウンの他に、こんな感じでAjaxのサジェストボックスを使うことも可能です。またフィールドの検索にも対応しています。
f:id:w650:20121220233305p:plain

まとめ

設定ファイルを直接編集する必要があったりJIRAを再起動する必要があったりするのが面倒ですが、機能的にはほぼやりたいことができました。あとは複数選択ができるとよいのですが。。

ただ、いくつか問題点もありました。画面サイズによって発生するようなのですが、候補が表示される位置がテキストボックスからずれてしまう場合があり、そのため候補に気付かない可能性があります。

あとは、DBのからのデータの取得が完了してから課題の編集画面が表示されるので、DBへの接続に時間がかかる構成では操作性が低下します。キャッシュするので毎回では無いはずです。

おまけ

以前、このプラグインを試したときに、同じようなプラグインで、Database Custom Fieldというのもあって、何かの理由があってDatabase Values Pluginを使った気がしたのですが、このエントリーを書くにあたって再度見てみたところ、Database Custom Fieldは複数選択できるようになってました。また画面から設定ができるので、こちらの方がオススメかもしれません。。

JIRAのリリースノートのカスタマイズ

運用にあわせてJIRAで作成できるリリースノートを以下のようにカスタマイズしました。

  • 課題タイプが"タスク"の課題は表示しない
  • 解決状況が"修正済み"以外の課題は表示しない
  • コンポーネント名を表示する

Creating a Custom Release Notes Template Containing Release Comments - JIRA 4.1 - Atlassian Documentation - Confluence
リリースノートのカスタマイズ方法はこのページのとおりです。このページのサンプルではリリースノート用コメントのためのフィールドを追加しています。

テンプレートの作成

上記ページのサンプルか atlassian-jira/WEB-INF/classes/templates/jira/project/releasenotes/ 配下にあるデフォルトのvmファイルもとにVelocityのテンプレートを作成します。HTML用とテキスト用をそれぞれ作成します。私は、デフォルトのvmを使いました。私の修正したvmファイルは以下のとおりです。ファイルの文字コードUTF-8にしておきます。

<title>$textUtils.htmlEncode($action.getText('release.notes.text.title', $project, $version))</title>
<body>
<table>
<tr>
<td>
<a href="$!appProps.getString('jira.baseurl')/secure/ConfigureReleaseNote.jspa?projectId=${versionObj.projectObject.id}&version=${versionObj.id}">$action.getText('releasenotes.configure')</a>

#foreach ($issueType in $issueTypes)
#if($issueType.issues.size() > 0 && !$issueType.name.equals("タスク"))
<h2>$textUtils.htmlEncode($issueType.name)</h2>
<ul>
#foreach ($issue in $issueType.issues)
#set ($resolution = $issue.getResolutionObject())
#set ($status = $issue.getStatusObject())
## check for resolved or closed and fixed
#if (($status.getId() == "5" || $status.getId() == "6") && $resolution.getId() == "1")
 <li>[<a href='$!appProps.getString("jira.baseurl")/browse/$issue.key'>$issue.key</a>] - [#foreach($component in $issue.components)#if($velocityCount>1), #end$component.name#end] $textUtils.htmlEncode($issue.summary)</li>
#end
#end
</ul>
#end
#end
</td>
</tr>

<tr>
<td>

<hr width="100%">

<a name="editarea"><h2>$action.getText('release.notes.edit.copy')</h2></a>
<p>$action.getText('release.notes.description')<br></p>

<textarea rows="40" cols="120">

$action.getText('release.notes.heading', $project, $version)

#foreach ($issueType in $issueTypes)
#if($issueType.issues.size() > 0 && !$issueType.name.equals("タスク"))
** $textUtils.htmlEncode($issueType.name)
#foreach ($issue in $issueType.issues)
#set ($resolution = $issue.getResolutionObject())
#set ($status = $issue.getStatusObject())
#if (($status.getId() == "5" || $status.getId() == "6") && $resolution.getId() == "1")
    * [$issue.key] - [#foreach($component in $issue.components)#if($velocityCount>1), #end$component.name#end] $textUtils.htmlEncode($issue.summary)
#end
#end
#end
#end
</textarea>
</td>
</tr>
</table>
</body>

velocity.propertiesの修正

vmファイルのエンコーディングは、ISO-8859-1になっているので、vmファイルに直接日本語を書いても正常に処理されません。atlassian-jira/WEB-INF/classes/elocity.propertiesで"UTF-8"に変更しました。

#----------------------------------------------------------------------------
# T E M P L A T E  E N C O D I N G
#----------------------------------------------------------------------------

input.encoding=UTF-8
output.encoding=UTF-8

今回はタスクを表示しないために、vmファイル内で"タスク"と直書きしていますが、うまくやれば直書きしなくてもよいはずなので、そうすればこの変更は不要になるので要調査。

vmファイルの編集はトライ&エラーになるので、vmファイルのキャッシュは無効にしておいた方がよいかもしれません。

class.resource.loader.cache=false

jira-application.propertiesの修正

atlassian-jira/WEB-INF/classes/jira-application.properties の以下の部分で作成したテンプレートを指定します。

# These list the templates that can be used for generating changlogs.
# For each template, there needs to a corresponding entry in *both* properties
jira.releasenotes.templatenames = Html, Text
jira.releasenotes.templates = releasenotes-html.vm, releasenotes-text.vm
# The default relaese note format
jira.releasenotes.default = Text

jira.releasenotes.templatesで指定した一つめがHTMLで二つめがテキスト用として認識されるようです(jira.releasenotes.templatenamesの記述順)。

JIRA再起動

設定を有効にするためにJIRAを再起動します。

IPv6が有効になっているとJIRAマクロが動作しない??

Confluenceで、JIRAの課題一覽を貼り付けるJIRA Issues Macroを使う設定のメモです。基本的にはサーバ(Ubuntu)に依存する問題だと思います。

JIRA Issuesマクロを使用するために、以下の情報を参考に信頼接続の設定をしました。これはJIRAの方であらかじめで接続を許可するConfluenceを登録する作業です。
JIRA と Confluence 間の信頼接続のセットアップ - Confluence (コンフルエンス) 関連資料

その上で、Confluenceのページなどの入力フォームで

{jiraissues:url=条件を付加したJIRAのURL}

こんな感じでマクロを指定します。ただ、設定ミスがあった場合のエラー内容(JIRAから返ってきたステータスコード)だけでは原因を特定することは難しいので、JIRA側のログを確認する必要があります。

まずは、こんなログが出力されていました。

[500ErrorPage.jsp] Exception caught in 500 page "0:0:0:0:0:0:0:1" does not represent a valid IP address.
com.atlassian.security.auth.trustedapps.IPAddressFormatException: "0:0:0:0:0:0:0:1" does not represent a valid IP address.

IPv6のアドレスを処理しようとしているようなので、IPv6を無効にするために/etc/sysctl.conf に以下を追加しました。

net.ipv6.conf.all.disable_ipv6=1

次は、こんなログが出力されていました。

anonymous 660x127x1 - 127.0.0.1 /sr/jira.issueviews:searchrequest-xml/temp/SearchRequest.xml [auth.trustedapps.filter.TrustedApplicationFilterAuthenticator] Failed to login trusted application: confluence:9560065 due to: com.atlassian.security.auth.trustedapps.InvalidCertificateException: BAD_XFORWARD_IP;	Request not allowed from IP address: {0};	["127.0.0.1"]

なぜか127.0.0.1が拒否されていました??

JIRAの信頼接続の設定画面は次のようになっていて、最初にアプリケーション名(ConfluenceのURL)を入力すると他のフィールドは自動で指定してくれるのですが、

許可するIPアドレスが127.0.1.1になってました。

これは、Ubuntuでは/etc/hostsの記述でlocalhostだと127.0.0.1、ホスト名だと127.0.1.1を返す設定になっていることが原因のようです。

127.0.0.1       localhost
127.0.1.1       jira

ただし、信頼接続の設定で許可するIPアドレス127.0.0.1に変更しても、同じエラーで拒否されてしまうので(今度はなぜか呼出元が127.0.1.1になる)、127.0.0.1と127.0.1.1を両方登録しておくか、/etc/hostsを変更する必要があるようです。

TracからJIRAへのデータ移行(続き)

TracからJIRAへのデータ移行にて紹介した以下のスクリプトですが、私の環境にあわせ、このスクリプトを変更してTracのデータを取り込みました。
yet another Trac 2 JIRA import - JIRA Community Space - Atlassian Documentation - Confluence
具体的には以下のような変更を行いました。

  1. 添付ファイル名のURIエンコードを解除する
  2. 添付ファイルのリンク切れをチェックする
  3. マイルストーンは影響バージョンではなく、対応バージョンに設定する
  4. バージョンが取り込まれていなかったので、影響バージョンに設定する
  5. ユーザ名が"hogehoge "のような形式のメールアドレスに対応する

現時点でのファイルを↓に置いていますのでご自由にどうぞ。
kenichiro22's yatrac2jira-v3 at master - GitHub
Windows上のPython2.5 + Trac 0.10.4 で使用しました。

プラグインの初期化に失敗??

JIRA 4.1がリリースされていたので、バージョンアップしてプラグインを入れたりして再起動しているうちに、↓のようなエラーで起動しなくなってしまいました..。

[plugin.osgi.factory.OsgiPlugin] Unable to install plugin 'com.atlassian.jira.gadgets'
com.atlassian.plugin.osgi.container.OsgiContainerException: Unable to install bundle
	at com.atlassian.plugin.osgi.container.felix.FelixOsgiContainerManager.installBundle(FelixOsgiContainerManager.java:424)
	at com.atlassian.plugin.osgi.factory.OsgiPluginUninstalledHelper.install(OsgiPluginUninstalledHelper.java:67)
	at com.atlassian.plugin.osgi.factory.OsgiPlugin.installInternal(OsgiPlugin.java:327)
	at com.atlassian.plugin.impl.AbstractPlugin.install(AbstractPlugin.java:325)
	at com.atlassian.plugin.impl.AbstractDelegatingPlugin.install(AbstractDelegatingPlugin.java:203)
	at com.atlassian.plugin.manager.DefaultPluginManager.addPlugins(DefaultPluginManager.java:531)
	at com.atlassian.plugin.manager.DefaultPluginManager.init(DefaultPluginManager.java:153)
	at com.atlassian.jira.plugin.JiraPluginManager.start(JiraPluginManager.java:49)
	at com.atlassian.jira.ComponentManager$PluginSystem.start(ComponentManager.java:1286)
	at com.atlassian.jira.ComponentManager.quickStart(ComponentManager.java:225)
	at com.atlassian.jira.ComponentManager.start(ComponentManager.java:211)
	at com.atlassian.jira.upgrade.ConsistencyLauncher.launchConsistencyChecker(ConsistencyLauncher.java:63)
	at com.atlassian.jira.upgrade.ConsistencyLauncher.contextInitialized(ConsistencyLauncher.java:42)
	at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:3934)
	at org.apache.catalina.core.StandardContext.start(StandardContext.java:4429)
	at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1045)
	at org.apache.catalina.core.StandardHost.start(StandardHost.java:722)
	at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1045)
	at org.apache.catalina.core.StandardEngine.start(StandardEngine.java:443)
	at org.apache.catalina.core.StandardService.start(StandardService.java:516)
	at org.apache.catalina.core.StandardServer.start(StandardServer.java:710)
	at org.apache.catalina.startup.Catalina.start(Catalina.java:583)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:616)
	at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:288)
	at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:413)
Caused by: org.osgi.framework.BundleException: Invalid bundle format
	at com.atlassian.plugin.osgi.container.felix.FelixOsgiContainerManager$BundleRegistration.install(FelixOsgiContainerManager.java:561)
	at com.atlassian.plugin.osgi.container.felix.FelixOsgiContainerManager.installBundle(FelixOsgiContainerManager.java:420)
	... 27 more
Caused by: java.util.zip.ZipException: error in opening zip file
	at java.util.zip.ZipFile.open(Native Method)
	at java.util.zip.ZipFile.<init>(ZipFile.java:131)
	at java.util.jar.JarFile.<init>(JarFile.java:150)
	at java.util.jar.JarFile.<init>(JarFile.java:114)
	at com.atlassian.plugin.osgi.container.felix.FelixOsgiContainerManager$BundleRegistration.install(FelixOsgiContainerManager.java:539)
	... 28 more

OSGiバンドルとして提供されているプラグインの読込みに失敗しているみたいですね。jira.homeで指定したディレクトリにあるplugins/.osgi-pluginsというディレクトリを削除したところ起動するようになりました。

JIRAではApache Felix使っているんですね。OSGi勉強中なので、このあたり中身を見てみたいところです。

TracからJIRAへのデータ移行

BTSの移行の一番やっかいな部分は既存データの移行だと思います。他のBTSからJIRAへのデータ移行については、次の資料にまとまっています。
他の課題管理ツールから移行する - JIRA (ジラ) 関連資料 - Go2Group Wiki
Tracからの移行の場合にはビルトインのインポータは存在しないので、ここで挙げられているスクリプトを使うことにしました。JIRAとしてデータインポート方法は次のような方法があり、

  • CSVファイルからのインポート
  • Jelly(XMLベースのスクリプトエンジン)スクリプトからのインポート
  • RPC(SOAP or XML RPC)を使う
  • 直接DBにデータを登録する

紹介されているサードパーティスクリプトはCSVもしくはJellyを使っています。今回は、Jellyを使う次のスクリプトを使いました。
yet another Trac 2 JIRA import - JIRA Community Space - Atlassian Documentation - Confluence
このスクリプトでは、

  • プロジェクトは作成してくれる
  • Tracと同一のユーザをJIRAで作成しておけばユーザも移行してくれる
  • チケットのコメント・添付ファイルも移行してくれる
  • TracのマイルストーンはJIRAのバージョンに移行される

私の環境では、Tracのバージョンが0.10.4、Pythonのバージョンが2.5で、yatrac2jira-v2.py というスクリプトを使いました。使用方法は、Tracのサーバにて次のように実行します。

python yatrac2jira-v2.py <Tracの対象プロジェクトのパス> <JIRAでのタグ> <JIRAでのプロジェクトオーナーのユーザID> > output.jelly

日本語ファイル名の添付ファイルがあると、192行目付近の添付ファイルの処理でurllib.quoteでKeyErrorが発生しました。

print '<jira:AttachFile key="${key}" filepath="' + env.path + "/attachments/ticket/" + str(ticket.id) + "/" + urllib.quote(ch[4]) + '" option="override"/>'

とりあえず、次のようにencode()を追加したところOKでした。

print '<jira:AttachFile key="${key}" filepath="' + env.path + "/attachments/ticket/" + str(ticket.id) + "/" + urllib.quote(ch[4].encode) + '" option="override"/>'

また、Tracでのチケットタイプと優先度を変更している場合には、スクリプト内でマッピングを追加しておく必要があります。

def mapIssueType(type):
    type = type.capitalize();
    if type == "Enhancement":
        return "Improvement"
    elif type == "Defect":
        return "Bug"
    elif type == "Task":
        return "Task"
    elif type == "Highlevel":
        return "Improvement"
    sys.stderr.write("Fallback to Bug for "+type+"\n")
    return "Bug"

def mapPriority(p):
    #if p == "major" or p == "minor" or p == "normal" or p == "trivial" or p == "critical":
    #return p.capitalize();
    p = p.capitalize();

    if p == "Highest":
        return "Blocker"
    if p == "Blocker":
        return "Blocker"
    if p == "High":
        return "Critical"
    if p == "Critical":
        return "Critical"
    if p == "Medium":
        return "Major"
    if p == "Normal":
        return "Major"
    if p == "Major":
        return "Major"
    if p == "Minor":
        return "Minor"
    if p == "Low":
        return "Minor"
    if p == "Trivial":
        return "Trivial"
    if p == "Lowest":
        return "Trivial"
    sys.stderr.write("Fallback to major priority for "+p+"\n")
    return "Major"

次に、出力されたJellyスクリプトでの添付ファイルのパスが、Tracのサーバ内でのローカルのパス("C:\TracLight\projects\trac\プロジェクト名"の部分)になっていますので、JIRAが別サーバの場合には、添付ファイルをJIRAサーバにコピーしたうえで、この部分を修正します。

<jira:AttachFile key="${key}" filepath="C:\TracLight\projects\trac\プロジェクト名/attachments/ticket/1/attachment.zip" option="override"/>

ただし、日本語ファイル名はJIRAではエンコードされたままなのでなんとかしたいところです。

JIRAでJellyスクリプトを読込みます。JIRA管理ページにて、[オプションと設定]-[Jelly Runner]を開きます。Jellyスクリプトをサーバにコピーしておいてそのパスを指定するか、ファイルの内容を貼りつけます。

これでインポートできるはずなのですが、、私の環境では、約1000件のチケットのうち400件目くらいで次のエラーが発生していました。

スクリプトを実行できません。 
Extra Information: [hide]
Error: <JiraJelly xmlns:jira="jelly:com.atlassian.jira.jelly.enterprise.JiraTagLib">
Exception: org.apache.commons.jelly.JellyTagException: file:/home/jira/Share/output.jelly:12718:0: Unable to make temporary copy of file.
java.io.PrintWriter@13b92a1

この問題についてはまだ調査中ですが、すべてインポートできれば、ひとまず移行できる状態にはなりそうです。あとは、次の点をどうするかが課題です。

  • チケット本文・コメント内でのTracリンク(#111, r1111, ...)
  • チケットのwiki記法(改行, 箇条書きなど)
  • Subversionのコミットログに記述したTracリンク

ところで、JIRAは標準ではWYSYWIGエディタは搭載していないんですね。
[#JRA-8943] WYSIWYG / Rich Text Editor - Atlassian JIRA

追加

Jellyスクリプトのインポートに失敗する問題は、Tracの添付ファイルフォルダで欠けているファイルがあったためでした。jiraのlogs/catalina.outファイルにログが出てました。

JIRA/Confluenceをお試し中

BTSとしてかれこれ4年ほどTracを使ってきました。はじめは素のTracを、途中からTrac月(TracLightning)を使っています。プラグインを入れればだいたいの事はできるので、不満はないのですが、最近、JIRAを含めAtlrassianの製品に10ユーザーまで1,000円のスターターライセンスができたので、移行を検討すべく試用版を入れてみました。JIRA/Confluenceは最近のオープンソースのプロジェクトでかなりよく見かけます。特にConfluenceでドキュメントを提供しているプロジェクトは多いので、一度は見たことがある人が多いのではないでしょうか。

さて、TracLightning相当にするためには、BTSのJIRAとWikiのConfluenceを組み合わせる必要があります。

あとソースリポジトリ(ここではSubversion)との連携も必要です。これは同じくatlassianのFishEyeを使うか、JIRAのSubversionプラグインを利用することになります。ただし、Subversionプラグインは、定期的にSubversionのコミットコメントを収集しJIRAの課題への参照があれば(Tracの#111みたいな)、課題ページから参照できるようにするものです。ですので、リポジトリビューアーは別途必要なようです。

まず、JIRA/Confluenceそれぞれスタンドアローン版とWAR版があり、どういう構成にするのか悩みます。スタンドアローン版というのはJIRAにTomcatを(TomcatにJIRAを?)組み込んだものです。Atlassian社としてのおすすめは、JIRA/Confluenceともにスタンドアローン版をポートを変えてインストールする、ということのようです。Tomcatが2つ動くのは効率的ではないように感じますが、、独立させる方が影響を及ぼさないとのことです。

インストール手順ははAtlassianのドキュメントが充実しているので、この辺を見ればインストールは簡単です。

今回は、次のような構成でインストールしました。

  • OS:Ubuntu
  • DBMS:MySQL
  • JIRA/Confluenceのスタンドアローン版
  • JIRAとConfluenceのユーザDBを統合
  • Subversionプラグインを使う

DBは組み込みのHSQLでも動作しますが、あくまで評価用とのことなので、はじめからMySQLにしました。

私の試した手順は改めて書きたいと思いますが、(簡単とは書きましたが、、)意外と面倒でした。JIRA/Confluenceの連携のためにserver.xmlでポートを変えたりデータソースの設定を記述したりで、Trac(TracLightning)ほどのお手軽さはないです。

とはいえ、普段の自分の開発環境(Java)と近いので、中身をのぞいてみると、参考になることが多そうです。これがJIRA/Confluenceを試してみることにした理由のひとつでもあるのですが。