Redmine REST APIでハマったこと


背景

今年 Redmine REST API を使って下記のようなことをする機会がありました。

  • 進捗管理をExcelからRedmineにmigration
  • CIのリグレッションテストでFailしたテストケースをRedmineに自動起票

目的

今まで Redmine REST API は使ったことがなく、実装しながら学んでいきました。その中でハマったことを中心に皆さんに共有して、僕のはハマった罠を回避して頂きたいと思います。

前提

  • javaをそこそこ理解している
  • Redmineの機能をそこそこ理解している

環境

Redmine Version
Environment:
  Redmine version                3.4.6.stable
java Version
java version "1.8.0_221"
Java(TM) SE Runtime Environment (build 1.8.0_221-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.221-b11, mixed mode)
pom.xml
        <dependency>
            <groupId>com.taskadapter</groupId>
            <artifactId>redmine-java-api</artifactId>
            <version>4.0.0.preview.1</version>
        </dependency>

ハマったこと

1. ユーザーに付与された権限にAPIの機能も制限される

Redmine REST APIを使うために最初に RedmineManagerクラスを生成します。このときに API KEYというものを使用します。

RedmineManager manager = RedmineManagerFactory.createWithApiKey(REDMINE_URL, API_KEY);

この API KEYはRedmineのユーザー一人一人に付与されます。当時、自分のユーザー権限は管理者権限ではありませんでした。APIのメソッドによってはRedmineのシステム管理者権限が必要なものがあります。

全ユーザーを取得
    /**
     * Load list of users from the server.
     * <p><strong>This operation requires "Redmine Administrator" permission.</strong>
     * <p>
     * This method calls Redmine with "include = memberships,groups" parameter.
     *
     * @return list of User objects
     * @throws RedmineAuthenticationException invalid or no API access key is used with the server, which
     *                                 requires authorization. Check the constructor arguments.
     * @throws NotFoundException
     * @throws RedmineException
     */
    public List<User> getUsers() throws RedmineException {
        return transport.getObjectsList(User.class, new BasicNameValuePair(
                "include", "memberships,groups"));
    }

柔軟に利用したい場合、システム管理者権限になることをお勧めします。

2. Transport が必要

redmine-java-apiのversion4以降から取り込まれた概念のようです。RedmineManagerクラスから取得できます。

RedmineManager redmineManager = RedmineManagerFactory.createWithApiKey(REDMINE_URL, API_KEY);
Transport transport = redmineManager.getTransport();

Transportがないとチケット作成・更新時に「Transportが未設定だぞ!」という例外がスローされます。なので作成・更新前に Issueのインスタンスに Transportを設定してあげてください。

新規チケット作成
// Transportをセット
Issue issue = new Issue(manager.getTransport());
// (中略)
issue = issue.create();
既存チケット更新
Issue issue = manager.getIssueManager().getIssueById(fetchedIssue.getTicketId());
// Transportをセット
issue.setTransport(manager.getTransport());
// (中略)
issue.update();

3. Paramsの使い方

既存チケットに更新をかけるとき、「この条件にあったチケット」を取得したいときがあります。そのときは Params を使うことで「この条件」を表現できます。

チケットの題名が"actual.getFileName"に一致するという条件
Params().add("set_filter", "1")
        .add("f[]", "subject")
        .add("op[subject]", "=")
        .add("v[subject][]", actual.getFileName());

ハマったことは下記3点です。

題名以外(例:担当者)ときのキー値は?

ドキュメントを見つけられなかったので、力技で特定してました。チケット一覧のフィルタで希望の条件を設定するとURLのパラメータとしてフィルタ条件が表示されます。パラメータをデコードするとキーの値を特定できます。

一致以外(例:一致しない、含む)のときは?

上の方法で解析。

チケット全部取得したいから条件なしで取得したら500件しか取得できない

現状不明。方法をご存じの方がいたらご教示ください。

4. 既存チケットのカスタムフィールドを更新するときは一度clearする

一度既存チケットのカスタムフィールドを取得して、値を更新する。そのあとに既存チケットのカスタムフィールドを clearCustomFields してから追加しないとうまく更新されませんでした。

// この処理で既存チケットからカスタムフィールドを取得して更新して、返却している
Collection<CustomField> customFieldCollection = setEachCustomField(issue, fetchedIssue);
issue.clearCustomFields();
issue.addCustomFields(customFieldCollection);

5. 日付型のカスタムフィールドに値を設定するときのフォーマット

yyyy-MM-dd一択です。これ以外はparseエラーになります。

以上(思いついたら追加していきます。)

参考