Help us understand the problem. What is going on with this article?

Redmineに関連チケット作成ボタンを追加する

More than 1 year has passed since last update.

やったこと

関連チケット作成ボタンを作成しました。
Redmine Advent Calendar 2018 -- 12/3 に登録しました)

関連チケット作成ボタンを作成しました.png

今回もONOZATYさんのViewCustomizeの実施例を参考にしました。
(ほとんど真似です!いつもどうもありがとうございます!!)
・REST APIを利用して複数の子チケットをまとめて作成する(Redmine View Customize Plugin)
http://blog.enjoyxstudy.com

背景

チケットの関連付けは、通常では後からチケット同士を関連するチケットとして設定をしますが、例えば、ある不具合を別の機種に横展開するケースなどで、チケットを関連付けする前提で新規チケットを作成したいという事です。

現状ですと、

不具合チケットの番号を覚えておいて、
新しいチケット作成 => 新しいチケット内容を記載 => 送信ボタン押下 で、
新しいチケット登録後に表示された画面で、
先程覚えておいたチケット番号を関連するチケットとして設定する。

といった手順になります。

事前にチケット番号をメモしておかないと、
新しいチケットに内容を記載しているうちに番号忘れてしまうし、
でもいちいちメモするのめんどいし、
で、新しいチケットを別タブに開くようにしてやったりしています。

それで。。。
あーー。関連チケット作成ボタンがほしい。。。
みたいな感じに思っていたのですが、
上記のONOZATYさんのブログでの小チケット自動作成ボタンを見て、同じ様にすれば出来るじゃん。ってなったのです。

画面イメージ

関連チケットを作成しますか? ⇛ OK

関連チケットを作成しますか.png

作成された関連チケット

作成された関連チケット.png

元のチケットにも関連済み

当然ですが。。
元のチケットにも関連済み.png

ソース実装例

(ViewCustomizePlugin 1.2.0以上でお願いします)

パスのパターン: /issues/[0-9]+
挿入位置: チケット詳細の下
種別: JavaScript

手順としては、
・説明欄をコピーする為に元のチケットを取得(おまけ)
・新しいチケットの作成
・チケットの関連付け
・関連付けされた新しいチケットを表示
になります。

コード:

$(function() {
  // 関連チケット作成ボタン
  const link = $('<a title="関連チケットの作成" class="icon icon-add" href="#">関連チケットの作成</a>');
  $('#relations').after($('<p>').append(link));

  // ボタンを押した
  link.on('click', function() {
    if (!confirm('関連チケットを作成しますか?')) {
      return;
    }
    $(window).on('beforeunload', function() {
      return '関連チケット作成の処理が完了していません。このまま移動しますか?';
    });

    let myIssueId =  ViewCustomize.context.issue.id;
    let projectId = $('#issue_project_id').val();
    let trackerId = $('#issue_tracker_id').val();
    let subject = $('#issue_subject').val();
    let priorityId = $('#issue_priority_id').val();
    let categoryId = $('#issue_category_id').val();
    let versionId = $('#issue_fixed_version_id').val();

    // 説明欄をコピーする為に取得(この処理はおまけ)
    getIssue(myIssueId)
      .then(function(get_Data) {
        let getData = JSON.parse(get_Data);

        // 関連チケットとして作成する情報
        let relationIssue = 
        {
          "issue": {
            "project_id": projectId,
            "tracker_id": trackerId,
            "subject": subject + " - の関連チケット",
            "priority_id": priorityId,
            "category_id": categoryId,
            "fixed_version_id": versionId,
            "description": "チケット #" + myIssueId + " より\r\n<pre>\r\n" + getData["issue"].description + "\r\n</pre>"
            }
        };

        // 新規チケットの作成
        createIssue(relationIssue)
          .then(function(post_Data) {
            let postData = JSON.parse(post_Data);
            // チケットの関連付け
            createRelation(myIssueId, postData["issue"].id);
          });
      });
  });//on_click

  // 説明欄の取得
  function getIssue(my_issue_id) {
    let dfd = $.Deferred();

    $.ajax({
      type: 'GET',
      url: 'http://192.168.33.10/redmine/issues/' + my_issue_id + '.json',
      headers: {
        'X-Redmine-API-Key': ViewCustomize.context.user.apiKey
      },
      dataType: 'text',
      contentType: 'application/json'
    })
    .done(function(Data) {
        dfd.resolve(Data);
    })
    .fail(function() {
        alert('関連チケットの作成に失敗した可能性があります');
        dfd.reject();
    });

    return dfd.promise();
  }

  // 新規チケットの作成
  function createIssue(new_issue) {
    let dfd = $.Deferred();

    $.ajax({
      type: 'POST',
      url: 'http://192.168.33.10/redmine/issues.json',
      headers: {
        'X-Redmine-API-Key': ViewCustomize.context.user.apiKey
      },
      // 作成時はレスポンスのコンテンツが無く、jsonだとエラーとなるのでtextにしておく
      dataType: 'text',
      contentType: 'application/json',
      data: JSON.stringify(new_issue)
    })
    .done(function(newData) {
        dfd.resolve(newData);
    })
    .fail(function() {
        alert('関連チケットの作成に失敗した可能性があります');
        dfd.reject();
    });

    return dfd.promise();
  }

  // チケットの関連付け
  function createRelation(issue_id, issueTo_Id) {
    let dfd = $.Deferred();

    let json_data = JSON.stringify(
      {
        relation: {
          "issue_id" : issue_id,
          "issue_to_id" : issueTo_Id,
          "relation_type" : "relates"
        }
      }
    );

    $.ajax({
      type: 'POST',
      url: 'http://192.168.33.10/redmine/issues/' + issue_id + '/relations.json',
      headers: {
        'X-Redmine-API-Key': ViewCustomize.context.user.apiKey
      },
      // 作成時はレスポンスのコンテンツが無く、jsonだとエラーとなるのでtextにしておく
      dataType: 'text',
      contentType: 'application/json',
      data: json_data
    })
    .done(function() {
      $(window).off('beforeunload');

      // 成功したら作成した関連チケットページを表示する
      location.href = 'http://192.168.33.10/redmine/issues/' + issueTo_Id;
      dfd.resolve();
    })
    .fail(function() {
      alert('チケットの関連付けに失敗した可能性があります');
      dfd.reject();
    });

    return dfd.promise();
  }
})

あとがき

途中の説明欄をコピーしてくる処理はいらないなら消してしまっていいと思います。
(4行くらい)

あと、ajax途中にページ遷移する場合のポップアップ警告を表示入れましたが、
完全じゃない様な気がします。・・・すみません。

また、今回、初めてDeferred() 使ってみました。(恥かしいですが…)
(・・・いらなかったかもしれません)
使い方に間違いがあるかもしれませんので、どなたかご指摘お願いしたいです。。。(^ _^*)

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away