WindowsのRedmineプラグイン開発をVisualStudio Codeに移行してみた

 前にやろうとしていたRedmineのバージョンアップ対応ですが、バージョンが決まり(Redmine2.5.1&Ruby2.0&Rails3.2 → Redmine4.0.4&Ruby2.5&Rails5.2)、インフラ含めスケジュールされたので、ぼちぼち対応に入ろうとした所、Rubyのバージョンを2.0から2.5に変えた影響か、Windowsローカル環境のAptanaStudio3では、実行は出来てもデバッグしようとすると、


と出て動かなくなりました。

 これまで3年くらい、Redmineプラグインの開発用に使っていたAptanaStudio3ですが、原因調査を早々に諦めて、、VSCodeに開発環境を移行する事にしました。
 使っているWindows端末にはRuby系の環境は既にインストール済みなので、Rubyのみ最新にします。

C:¥temp>ruby -v
ruby 2.5.5p157 (2019-03-15 revision 67260) [x64-mingw32]

C:¥temp>rails -v
Rails 5.2.2

 VSCodeを普通にインストールして、既存のAptanaStudio3で対応していたフォルダをワークスペースに追加するだけで、準備が出来ましたが、デバッグ設定が一苦労。。ググるといろいろ情報があり、思いのほか試行錯誤しましたが、こちらが一番参考になりました。デバッグ用のGemをインストールし、設定を試していきます。

 最終的にlaunch.jsonは下記となり、ブレークポイントも止まるようになりました。

"version": "0.2.0",
"configurations": [
    {
        "name": "Rails server",
        "type": "Ruby",
        "request": "launch",
        "cwd": "${workspaceRoot}",
        "pathToRDebugIDE": "C:¥¥Ruby25-x64¥¥bin¥¥rdebug-ide.bat",
        "env": { "RAILS_ENV": "development", "GEM_PATH": "${workspaceRoot}/vendor/bundle/bin/ruby/2.5.0"},
        "program": "${workspaceRoot}/bin/rails",
        "args": [
            "server"
        ]
    }
]


 VisualStudio Code ネットの評価に違わずいい感じです。新しいツールを使うのは少し手間が掛かりますが、気分的にもリフレッシュできますね。

Redmine4のプラグイン開発環境を整備してみた

 独自プラグインが複数入った既存のRedmine2.5をそろそろアップデートしたいという事で、つい先月リリースされたredmine4へアップデートする方向になり、プラグインの改修を行う為にredmine4用の開発環境を使っているWin端末に整備してみました。
 元々Redmine2.5用にWindows7 + Aptana Studio 3(eclipse) の開発環境があり、まずはruby2.0→ruby2.5.3にします。rubyは普通にWindows用のインストーラーで入れましたがruby2.0が入っているので、原始的ですが、環境変数をC:¥Ruby25-x64¥binが先になるように変更してruby2.5を有効にしました。

 次に、AptanaStrudioにrails プロジェクトを作成し、ダウンロードしてきたRedmine4を突っ込み、AptanaStudioのコンソールから、Redmineのインストールを行います。

    $ gem install bundler
    Fetching: bundler-2.0.1.gem (100%)
    Successfully installed bundler-2.0.1
    Parsing documentation for bundler-2.0.1
    Installing ri documentation for bundler-2.0.1
    Done installing documentation for bundler after 9 seconds
    1 gem installed

    $ bundle install –path vendor/bundle
    Fetching gem metadata from https://rubygems.org/……….
    Fetching gem metadata from https://rubygems.org/.
    Resolving dependencies….
    Fetching rake 12.3.2
    中略
    MSYS2 could not be found. Please run ‘ridk install’
    or download and install MSYS2 manually from https://msys2.github.io/

で、起動してみます。

    $ rails -s
    Could not find gem ‘rails (= 5.2.2) x64-mingw32’ in any of the gem sources liste
    Run bundle install to install missing gems

動きません。。。railsがインストール出来ていないようです。

前に出ていた、MSYS2 could not be found. という所で、MSYS2なるものを入れてビルドする必要があるようです。

DOS窓からridk installと打ち、1を選択するとインストーラー画面が出てインストール終了です。

で、railsをインストールします。

    C:¥Users¥maki>gem install rails
    Temporarily enhancing PATH for MSYS/MINGW…
    Building native extensions. This could take a while…
    ERROR: Error installing rails:
    ERROR: Failed to build gem native extension. current directory: C:/Ruby25-x64/lib/ruby/gems/2.5.0/gems/nio4r-2.3.1/ext/nio4r
    C:/Ruby25-x64/bin/ruby.exe -r ./siteconf20190117-14000-1ru4f4e.rb extconf.rb

    current directory: C:/Ruby25-x64/lib/ruby/gems/2.5.0/gems/nio4r-2.3.1/ext/nio4r
    make “DESTDIR=” clean
    ‘make’ は、内部コマンドまたは外部コマンド、
    操作可能なプログラムまたはバッチ ファイルとして認識されていません。
    current directory: C:/Ruby25-x64/lib/ruby/gems/2.5.0/gems/nio4r-2.3.1/ext/nio4r
    make “DESTDIR=”
    ‘make’ は、内部コマンドまたは外部コマンド、
    操作可能なプログラムまたはバッチ ファイルとして認識されていません。

    make failed, exit code 1

    Gem files will remain installed in C:/Ruby25-x64/lib/ruby/gems/2.5.0/gems/nio4r-
    2.3.1 for inspection.
    Results logged to C:/Ruby25-x64/lib/ruby/gems/2.5.0/extensions/x64-mingw32/2.5.0
    /nio4r-2.3.1/gem_make.out


makeが無い??MSYS2に標準では入っていないようです。。

で、こちらを参考にさせて頂き、MSYS2環境に下記のコマンドを打っていきます。

    $ pacman -Su

    $ pacman -Su

    $ pacman -S mingw-w64-x86_64-gcc

ビルド環境は出来たようなので、AptanaStudioの該当プロジェクト上のターミナルよりrailsのみインストールしてみます。

    $ gem install rails -v 5.2.2
    Temporarily enhancing PATH for MSYS/MINGW…
    Building native extensions. This could take a while…
    Successfully installed nio4r-2.3.1
    Fetching: websocket-extensions-0.1.3.gem (100%)
    Successfully installed websocket-extensions-0.1.3
    Fetching: websocket-driver-0.7.0.gem (100%)
    Building native extensions. This could take a while…
    中略
    Installing ri documentation for railties-5.2.2
    Parsing documentation for sprockets-3.7.2
    Installing ri documentation for sprockets-3.7.2
    Parsing documentation for sprockets-rails-3.2.1
    Installing ri documentation for sprockets-rails-3.2.1
    Parsing documentation for rails-5.2.2
    Installing ri documentation for rails-5.2.2
    Done installing documentation for nio4r, websocket-extensions, websocket-driver,

入ったような気がするが。。。
rails s
まだ動かない。。。

    $ bundle update
    Fetching gem metadata from https://rubygems.org/……….
    Fetching gem metadata from https://rubygems.org/.
    Resolving dependencies…..
    Using rake 12.3.2
    Using concurrent-ruby 1.1.4
    中略
    Fetching rmagick 2.16.0
    Installing rmagick 2.16.0 with native extensions
    Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

    current directory:
    C:/work/Dev/workspace/redmine4/vendor/bundle/ruby/2.5.0/gems/rmagick-2.16.0/ext/
    C:/Ruby25-x64/bin/ruby.exe -r ./siteconf20190117-11036-fkmn5g.rb extconf.rb
    *** extconf.rb failed ***
    Could not create Makefile due to some reason, probably lack of necessary
    libraries and/or headers. Check the mkmf.log file for more details. You may
    need configuration options.

rmagickのインストールでエラーが出ているようなので、除外してインストールし直し

    $ bundle install –without rmagick –path vendor/bundle
    Fetching gem metadata from https://rubygems.org/……….
    Fetching gem metadata from https://rubygems.org/.
    Resolving dependencies…..
    Using rake 12.3.2
    中略
    Bundle complete! 27 Gemfile dependencies, 74 gems now installed.
    Gems in the groups rmagick and rmagik were not installed.
    Bundled gems are installed into `./vendor/bundle`
    Post-install message from yard:
    ——————————————————————————–
    As of YARD v0.9.2:

    RubyGems “–document=yri,yard” hooks are now supported. You can auto-configure
    YARD to automatically build the yri index for installed gems by typing:

    $ yard config –gem-install-yri

    See `yard config –help` for more information on RubyGems install hooks.

    You can also add the following to your .gemspec to have YARD document your gem
    on install:

    spec.metadata[“yard.run”] = “yri” # use “yard” to build full HTML docs.

    ——————————————————————————–

やっと動きました!
DBのマイグレーションはしなかったけど、とりあえず動いたので、これから少しずつプラグインを導入しながら、もぐら叩きが始まります。。

redmine plugin DMSFを入れてみた

文書管理をしたいという話があがり、redmineプラグインにDMSFってのがあるよ!
って事で、対象のredmineが3.3なので、DSMFは現時点の最新より一つ古い 1.5.9 を入れてみました。

redmine/pluginsにダウンロードしたソースを置き、redmineのルートディレクトリに移動。
最初
bundle install –without development test
とした後に
bundle execでインストールしようとしたが、redmineローカル内のGemとして入ってないと怒られた。
また、xapian-full-alaveteliというgemがどうも入らないので、こちらはGemfileの該当箇所をコメントアウト。

結果、
bundle install –without development test –path vendor/bundle
とした後に
bundle exec rake redmine:plugins:migrate NAME=redmine_dmsf RAILS_ENV=production
でとりあえず入った。

プラグイン設定画面はこんな感じ
一番下のテキスト検索の箇所が、入らなかったgemと関係している模様。文書検索できるならさらに素晴らしいが日本語は選択対象に無いので、とりあえずOFF。

利用対象とするプロジェクトの設定から、「文書管理」ってモジュールにチェックを入れて使用開始。

wikiの隣あたりから「文書管理」が開くようになり、ファイルをドラッグアンドドロップでアップロードできるようになりました。
同じファイル名はアップロードするごとに自動的にマイナーバージョン番号がアップされ、メジャーにするときはアップロード時に画面で指定します。

文書の更新履歴はこんな感じ。

他に承認ワークフロー機能があるようなので試したところ、承認ワークフローの設定で新規ステップと押しても反応しないのでログを見たら、DBエラーが出てました。
該当ソースのgithub履歴をみるとバグフィックスしたコミットがあったので、ステップが登録できるようになりました!承認者をORかANDで設定していく形です。

で、該当文書の右端チェックを押すとワークフローを適用できます。
今回試しに「ソフトウェア資料」というワークフローを作りレビュアー3名をOR設定、承認者1名をセットするとこんな感じで履歴が見れます。

とは言え、1.5.9は他にもバグがあるのでパッチをあてないとredmineの動きがちょっとおかしくなります。最新バージョンを入れてみれば良かったと後悔。。

DMSFによる文書管理をこれからどう運用していくかは、既存のファイルサーバ管理からの移行方針とか、プロジェクトの切り方といったredmine運用も考えた上でやらないといけませんね。

redmineでたまにInternalErrorが出るようになった。

 筆者が通っているユーザーさんでは、redmineに独自開発含めプラグインを10個くらい入れて使っているのですが、何時からかInternalErrorが出るようになり、その調査を依頼されました。
 状況的にはチケットの一覧や詳細を表示する時、たまにInternalErrorとなる事があり、ただ再表示すれば普通に表示されるという何とも解りにくい状況。。
 redmineのログには下記InternalError発生時の内容が出力されています。

Completed 500 Internal Server Error in 6169.9ms

ActionView::Template::Error (undefined method `deep_symbolize_keys' for #<String:0x007f34da039088>):
    50: <p id="start_date_area">
    51:   <%= f.text_field(:start_date, :size => 10, :disabled => !@issue.leaf?,
    52:                    :required => @issue.required_attribute?('start_date')) %>
    53:   <%= calendar_for('issue_start_date') if @issue.leaf? %>
    54: </p>
    55: <% end %>
    56: 
  lib/redmine/i18n.rb:131:in `store_translations'
  lib/redmine/i18n.rb:152:in `init_translations'
  lib/redmine/i18n.rb:166:in `lookup'
  lib/redmine/i18n.rb:30:in `l'
  app/helpers/application_helper.rb:1180:in `block in include_calendar_headers_tags'
  app/helpers/application_helper.rb:1167:in `include_calendar_headers_tags'
  app/helpers/application_helper.rb:1159:in `calendar_for'
以下省略

i18nが関係しているようですが、たまにしか起きないし、それ以外に問題箇所を特定できそうな出力は読み取れません。
いろいろ試行錯誤してみますが原因は不明。。で、国際化対応が絡んでいるという事で、ログインユーザーの言語設定を英語に変えてみた所、相変わらずInternalErrorがたまに出るものの、ログの出力内容が変わりました。

Completed 500 Internal Server Error in 2011.0ms

NoMethodError (undefined method `deep_symbolize_keys' for #<String:0x007f8f44c7d8c0>):
  lib/redmine/i18n.rb:131:in `store_translations'
  lib/redmine/i18n.rb:152:in `init_translations'
  lib/redmine/i18n.rb:166:in `lookup'
  lib/redmine/i18n.rb:27:in `l'
  app/models/issue_query.rb:172:in `initialize_available_filters'
以下省略

で、エラーが出始めた時期とかを考慮すると、独自プラグインを入れた後なので、そのソースを眺めていると、en.ymlに不備を発見。直して入れてみると問題が解消したようです。

backlog4jで課題を登録してみる

今度は、backlogへの課題登録を自動化したいという話があり、前回のredmineと似通った要件なので再利用箇所も多く、redmine java api部分をヌーラボ公認のbacklog4jに置き換えて実装してみました。
redmineとの違いはそれなりにありますが、backlog4jの方はKeyとIDを混同しやすい感じがします。例えば、課題のKeyはURLを見ればすぐ解りますが、IDの方は内部的なユニークな数値です。これに注意しながらbacklog用に前回のラッパークラスを置き換えてみました。

import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang3.StringUtils;

import com.nulabinc.backlog4j.BacklogClient;
import com.nulabinc.backlog4j.BacklogClientFactory;
import com.nulabinc.backlog4j.Category;
import com.nulabinc.backlog4j.Issue;
import com.nulabinc.backlog4j.IssueComment;
import com.nulabinc.backlog4j.IssueType;
import com.nulabinc.backlog4j.Project;
import com.nulabinc.backlog4j.Status;
import com.nulabinc.backlog4j.api.option.AddIssueCommentParams;
import com.nulabinc.backlog4j.api.option.CreateIssueParams;
import com.nulabinc.backlog4j.api.option.GetIssuesParams;
import com.nulabinc.backlog4j.api.option.UpdateIssueParams;
import com.nulabinc.backlog4j.conf.BacklogConfigure;
import com.nulabinc.backlog4j.conf.BacklogJpConfigure;
import com.nulabinc.backlog4j.conf.BacklogPackageConfigure;

public class BacklogRegist {
	
	private BacklogClient backlog;
		
	public BacklogRegist(String url, String apikey) throws MalformedURLException  {
		
		BacklogConfigure conf = null;
		//オンプレ環境
		if(url.startsWith("http")){
			conf = new BacklogPackageConfigure(url);
		}
		//クラウド
		else{
			conf = new BacklogJpConfigure(url);
		}
		conf.apiKey(apikey);
 		backlog = new BacklogClientFactory(conf).newClient();
				
		
	}
	/**
	 * 課題を取得する
	 * @param issueKey
	 * */
	public Issue getIssue(String issueKey){
		
		if(issueKey == null){
			return null;
		}
		return getIssue = backlog.getIssue(issueKey);
	}
	/**
	 * 課題を作成する
	 * @param projectKey
	 * @param  issueTypeName 種別名称
	 * @param categoryName カテゴリ名称
	 * @param summary 件名
	 * @param description 詳細
	 * @return ticketID 0は登録失敗
	 * */
	public long regist(String projectKey, String issueTypeName, String categoryName, 
						String summary, String description){
		
		CreateIssueParams param = new CreateIssueParams(
				getProjectID(projectKey), summary,
				getIssueTypeId(projectKey, issueTypeName), Issue.PriorityType.Normal);
		
		param.description(description);
		
		if(StringUtils.isNotEmpty(categoryName)){
			List<String> categoryIds = new ArrayList<String>();
			categoryIds.add(getCategoryId(projectKey, categoryName));		
			param.categoryIds(categoryIds);
		}
		
		Issue issue = backlog.createIssue(param);
		if(issue != null){
			return issue.getId();
		}
		return 0;
	}
	
	/**
	 * 課題を更新する(コメントの追加も可能)
	 * @param projectKey プロジェクトKey
	 * @param  issueIdorKey 課題IDまたはKey 
	 * @param status ステータス名称(更新しなければセットしない)
	 * @param addComment  更新なければセットしない
	 * @return commentID 0は登録失敗
	 * */
	public long update(String issueIdorKey, String status, String addComment){
		long ret = 0;
		Issue issue = null;
		
		if(StringUtils.isNotEmpty(status)){
			UpdateIssueParams param = new UpdateIssueParams(issueIdorKey);
			for( Status Entity: backlog.getStatuses()){
				if( Entity.getName().equals(status)){
					param.status(Entity.getStatusType());
				}
			}
			issue = backlog.updateIssue(param);
		}
		else{
			issue = backlog.getIssue(issueIdorKey);
		}
		if(StringUtils.isNotEmpty(addComment)){
			AddIssueCommentParams params = new AddIssueCommentParams(issue.getIssueKey(), addComment);

			IssueComment cmt = backlog.addIssueComment(params);
			ret = cmt.getId();
		}
		else{
			ret = issue.getId();
		}
		return ret;
	}

	/**
	 * 課題にコメントを追加する
	 * @param projectKey
	 * @param  issueKey
	 * @param addComment
	 * @return commentID 0は登録失敗
	 * */
	public long addComment(String issueKey, String addComment){
		Issue issue = backlog.getIssue(issueKey);
		
		if( issue != null){
			AddIssueCommentParams params = new AddIssueCommentParams(issue.getIssueKey(), addComment);

			IssueComment cmt = backlog.addIssueComment(params);
			return cmt.getId();
			
		}
		return 0;
	}

	/**
	 * 課題のタイプ(種別)を取得する
	 * @param prjKey プロジェクトキー
	 * @param name 種別名称
	 * */
	public String getIssueTypeId(String projectKey, String name){
		for( IssueType type: backlog.getIssueTypes(projectKey)){
			if( type.getName().equals(name)){
				return type.getIdAsString();
			}
		}
		return null;
	}
	
	/**
	 * カテゴリを取得する
	 * @param prjKey プロジェクトキー
	 * @param name 種別名称
	 * */
	public String getCategoryId(String projectKey, String name){
		for( Category type: backlog.getCategories(projectKey)){
			if( type.getName().equals(name)){
				return type.getIdAsString();
			}
		}
		return null;
	}
	/**
	 * プロジェクトIDを取得する
	 * @param prjKey プロジェクトキー
	 * @return Projectid
	 * */
	public String getProjectID(String projectKey){
		for( Project type: backlog.getProjects()){
			if( type.getProjectKey().equals(projectKey)){
				return type.getIdAsString();
			}
		}
		return null;
	}
}

上記を呼び出すのはこんな感じ

BacklogRegist backlog = new BacklogRegist("{URLまたはスペースID}","{apiKey}");

//チケット取得
Issue issue = backlog.getIssue("PROJECT-3");

//チケット登録
long issueId = backlog.regist("PROJECT", "要望", "設定変更",  
	"ユーザーの追加について", "○○さんを追加してください");

後日談
GetIssieで3ヶ月経って問題発生・・・たまに課題の取得に失敗するようになりました・・・
下記のようにしましたが、最初からそうしろ!って事ですね。でも、3ヶ月は何も問題なかったのですが・・
public Issue getIssue(String issueKey)

Issue getIssue = backlog.getIssue(issueKey);

redmine java api でチケットを取得してみる

 前回の続きで、今度はチケットを取得してみます。単純にチケットIDを指定して一つのチケットを取得するのでは無く、条件を指定し一括してチケットを取得して何かをするような要件への対応です。

 下記サンプルでは、クエリーを使用せずに、
ステータスID=2 かつ トラッカーID=10または11 かつ 題名に「テスト」を含む
という条件に一致するチケットを全て取得する内容です。
100件ずつチケットを取得して、100件以上のチケットがあればページ番号を変えて全チケットを取得します。

String apikey = "APIキー値";
String uri = "redmineサイトURI";

String TICKET_LIMIT = "100";

Map<String,String> params = new HashMap<String,String>();

// リミット
params.put("limit",TICKET_LIMIT);

params.put("status_id","2");

params.put("tracker_id","10|11");
//題名に「テスト」を含む
params.put("subject", "~テスト");

RedmineManager mgr = RedmineManagerFactory.createWithApiKey(uri, apikey);

boolean iscontinue = true;
int page = 1;
while(iscontinue){
	
	List<Issue> issues = mgr.getIssueManager().getIssues(params);
	
	for(Issue issue : issues){
		System.out.println(issue.getSubject());
	}
	if(issues.size() < Integer.valueOf(TICKET_LIMIT)){
		iscontinue = false;
	}
	else{
		page++;
		params.put("page",String.valueOf(page));
	}
}

redmine java api でチケットを登録してみる

 redmineへのチケット登録を自動化したいという話があり、内容的には常時トリガーを拾って登録という感じだったので、javaの常駐プロセスでトリガーを拾う事を前提として、こちらを使用して実装してみました。
https://github.com/taskadapter/redmine-java-api
なお、登録対象のredmineは2.5系でしたが、それほどバージョンを意識しなくても大丈夫そうです。

事前準備として、対象のredmineへapi登録用のユーザーを用意し、登録対象のプロジェクトへの権限を付与、個人設定画面からapiキーを取得します。

その他、プロジェクトidやトラッカーid等のredmine内部で持っているidの値はブラウザからapiで下記のようにidの値を確認してセットとなります。ただ、redmine java apiには、様々なマネージャーがあるので、id指定でなくても都度問い合わせしてidを取得できそうです。

ソース的には下記のようなラッパークラスを作って、登録や更新を呼び出し側で簡略化出来るようにしてます。

package jp.eosoro.redmine;

import java.util.ArrayList;
import java.util.List;

import com.taskadapter.redmineapi.Include;
import com.taskadapter.redmineapi.RedmineException;
import com.taskadapter.redmineapi.RedmineManager;
import com.taskadapter.redmineapi.RedmineManagerFactory;
import com.taskadapter.redmineapi.bean.CustomField;
import com.taskadapter.redmineapi.bean.Issue;
import com.taskadapter.redmineapi.bean.ProjectFactory;
import com.taskadapter.redmineapi.bean.TrackerFactory;
import com.taskadapter.redmineapi.bean.User;
import com.taskadapter.redmineapi.bean.UserFactory;

/**
 * Redmine登録クラス
 * */
public class RedmineRegist {

	RedmineManager mgr;
	
	private Issue issue;
	private List<CustomField> customeField;
	
	/**
	 * コンストラクタ
	 * @param String RedmineサイトURI
	 * @param String APIキー
	 */
	public RedmineRegist(String uri, String apiAccessKey) {
		mgr = RedmineManagerFactory.createWithApiKey(uri, apiAccessKey);

		issue = new Issue();
		customeField = new ArrayList<CustomField>();		
	}
	/**
	 * チケット登録
	 * @param subject タイトル
	 * @param description 詳細
	 * @param assigned_toId 担当者を指定する時はユーザーid 指定しない場合は0
	 * @param assigned_toGroupId グループを指定するときはグループid 指定しない場合は0
	 * @param parentid 親チケットを指定する時はチケットid 指定しない場合は0
	 * @param projectId
	 * @param trackerId 
	 * @param priorityId
	 * @param statusId
	 * @return int 登録チケットID 登録失敗時はゼロ
	 * */ 
	public int regist(String subject, String description, int assigned_toId, int assigned_toGroupId, int parentid, int projectId, int trackerId, int priorityId, int statusId){
		
		getIssue().setSubject(subject);
		
		getIssue().setDescription(description);

		//プロジェクト指定
		getIssue().setProject(ProjectFactory.create(projectId));

		//トラッカー指定
		getIssue().setTracker(TrackerFactory.create(trackerId));
		
		//ステータス/優先度指定
		getIssue().setPriorityId(priorityId);
		getIssue().setStatusId(statusId);
		
		// カスタムフィールド設定
		getIssue().addCustomFields(getCustomeField());
		
		try {
			//担当者指定
			if(assigned_toId > 0){
				getIssue().setAssignee(getMgr().getUserManager().getUserById(assigned_toId));
				
			}
			//グループ
			else if(assigned_toGroupId > 0){
				User u = UserFactory.create(assigned_toGroupId);
				getIssue().setAssignee(u);
			}
			//親チケット
			if(parentid > 0){
				getIssue().setParentId(parentid);
			}
			return getMgr().getIssueManager().createIssue(getIssue()).getId();

		} catch (RedmineException e) {
			return 0;
		}
	}
	/**
	 * 関連するチケットをセットする
	 * @param int チケットID
	 * @throws RedmineException 
	 * */
	public void setRelation(int id, int relateid) throws RedmineException{
		getMgr().getIssueManager().createRelation(id, relateid, "relates");
		
	}
	/**
	 * ジャーナル追加
	 * @param id チケットID
	 * @param Notes コメント
	 * @throws RedmineException 
	 * */
	public int addJournal(int id, String Notes) throws RedmineException{
		Issue issue =  getMgr().getIssueManager().getIssueById(id, Include.journals);
		
		issue.setNotes(Notes);

		getMgr().getIssueManager().update(issue);

		return issue.getId();
	}

	public RedmineManager getMgr() {
		return mgr;
	}
	private Issue getIssue() {
		return issue;
	}
	public List<CustomField> getCustomeField() {
		return customeField;
	}
	public void setCustomeField(List<CustomField> customeField) {
		this.customeField = customeField;
	}
}

下記の呼び出し側では、親チケットを作成してジャーナルを追加、子チケットを2つ作成し関連付けてます。

       RedmineRegist  mine = new RedmineRegist (url, apikey);
       
       int parentId = mine.regist("親チケット", "親チケット本文", 0, 0, 0, 4, 4, 2, 1);
       
       mine.addJournal(parentId, "ジャーナル");

       int childId = mine.regist("子チケット", "子チケット本文",  0, 0, parentId, 4, 4, 2, 1);
       
       int relationId = mine.regist("関連チケット", "関連チケット本文",  0, 0, parentId, 4, 4, 2, 1);
       
       
       mine.setRelation(childId, relationId);

いろいろやってみましたが、redmineに対して殆どの事は出来そうです。

gitbucketとredmineを連携させてみる

 そろそろちゃんとソース管理をしないとと思い、既に使っているredmineにgitbucketを連携してみました。
redmineを入れているcentOS6サーバにgitbucketを導入してみます。って、下記を見る限り
http://qiita.com/pppurple/items/2e614a836e2184f70997
とりあえずgitbucketを動かすならwarをダウンロードしてJavaコマンド実行するだけのようですが、
java -jar gitbucket.war
では、どうもうまく動作しません。

で、payara-microにデプロイする形で実行してみます。
java -jar payara-micro-4.1.1.163.jar –port=18080 –deploy gitbucket.war

フロントがapacheなので、httpd.confのProxyPass当たりを追加し、とりあえずはgitbucketの画面が出るようになりました。

次はredmine側にリポジトリを登録します。が、リポジトリのパスはどこなんだろうと調べると、gitbucketデフォルトでは起動ユーザーのhomeに隠れて作られているとの事。このパスをそのままredmineのリポジトリに登録してみましたが、NotFound状態・・

リポジトリのパスを変えないとredmine側から参照できないようなので、ホームディレクトリを指定する実行時引数を追加。

結果的にこうなりました。
java -Dgitbucket.home=/var/lib/gitbucket -jar payara-micro-4.1.1.163.jar –port 18080 –deploy gitbucket.war

gitbucket側でリポジトリを新規作成し、サーバ内に出来た拡張子.gitのファイルパスをredmine側のリポジトリのパスに指定します。

やっとredmine側から見れるようになりました。
redminerep

作ったリポジトリへeclipseから強制PUSHするとこんな感じでgitbucketのホームディレクトリを変える前にコミットしたのも含めてredmineから参照出来ました。分散管理っていいですね。
redminerep2

後日、init.shを作って別途サービス登録しておきます。