2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【GAS】SlackワークフローからRedmineチケットを自動作成できるツールを作ってみた

Last updated at Posted at 2024-06-10

はじめに

今回、業務で社内改善ツールとしてSlackワークフローからRedmineチケットを自動作成するツールを作成する機会があったので、開発内容を備忘録として記録したいと思います。

完成イメージ

① ユーザアクション:Slackワークフローからチケット情報を入力

image.png

② ツール実行:Redmineチケットが自動起票される

image.png

③ ツール実行:Redmine URLを含むメッセージをSlackに通知

image.png

構成

  • Slackワークフロー
  • Slackカスタムアプリ
    • Incoming WebHooksアプリ
    • Userリスト取得アプリ
  • Googleスプレッドシート
  • Google Apps Script(以下GAS)
  • RedmineAPI

カスタムインテグレーションが非推奨となったため、カスタムインテグレーションとしてのOutgoing Webhooks、 Incoming WebHooksを使わないことを前提に構成しました。

今回はSlackカスタムアプリとしてIncoming WebHooksを作成しております。

SlackワークフローのステップからはRedmineに直接アクセスできないため、ワークフローからの「Googleスプレッドシートへのデータ追加」をトリガーに、GAS経由でRedmineAPIを実行しています。

処理の流れ

image.png

  • Slackワークフローより
    • チケット情報を記入
    • ワークフローステップからデータをGoogleスプレッドシートに記入
  • Googleスプレッドシートより
    • 変更を感知してGASを発火
  • GASより
    • Googleスプレッドシートからデータを取得して整形
    • RedmineAPIを実行してチケット作成
    • 作成されたチケットURLを記載したメッセージをSlack通知

実装方法

1) データ記入用スプレッドシート作成

新規スプレッドシートを作成します。
「拡張機能」 > 「Apps Script」からGASを開始します。

image.png

2) curlでRedmine APIを実行してデータ構造を確認する

以下の記事を参考にRedmine APIを有効にし、APIアクセスキーを取得します。

以下の記事を参考にチケットの情報を確認します。
確認した内容は以降の手順でデータの整形時などに参考にします。

3)スプレッドシートに項目列を追加

手順2で確認したRedmineのチケット情報を参考に、先ほど作成したスプレッドシートの1行目にRedmineへ登録する項目の行を追加します。

image.png

4)Slackワークフロー作成

情報を取得するワークフローを作成します。

image.png

ステップ2では先ほど作成したスプレッドシートを選択し、「スプレッドシートの対象列」と「フォームで収集する項目」が対応するよう指定します。

image.png

5)GASに変更感知のトリガーを設定

GASのトリガーを開きます。

image.png

以下のようにトリガーを設定します。

image.png

6)Slackカスタムアプリ作成

① 以下の記事を参考にIncoming Webhookアプリを作成し、メッセージを投稿したいチャネルのWebhook URLを取得します。

② 以下の記事を参考にUserリスト取得アプリを作成し、User Tokenを取得します。

7)スクリプトプロパティの設定

手順6で取得したWebhook URLやUser Tokenをスクリプトプロパティに設定します。
「プロジェクトの設定」を開きます。

image.png

スクリプトプロパティに以下3つを設定します。

  • 手順6で取得したWebhook URL
  • 手順6で取得したUser Token
  • 手順2で取得したRedmineAPIキー

image.png

8)GASの実装

以下5ファイルを作成します。

  • main.gs
  • forms.gs
  • redmine.gs
  • slack.gs
  • tools.gs
main.gs
var prop = PropertiesService.getScriptProperties().getProperties();

function main() {
  let mySheet = SpreadsheetApp.getActiveSheet();
  let sheetName = mySheet.getSheetName();

  let data = {};
  let slackMessage = {};
  let response = {}

  // フォーム追加時はここに分岐を追加
  response = formMain(sheetName)

  data = response.data
  slackMessage = response.slackMessage

  let ticketUrl = createTicket(data)
  post_slack(ticketUrl, slackMessage);
}

form.gs
function formMain(sheetName) {
  let data = srformatData(sheetName)
  let slackMessage = srSlackMessage(data)
  return {"data": data, "slackMessage": slackMessage}
}

// Redmineチケット作成用フォーマット
function srformatData(sheetName) {
  let response = {};
  var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = spreadsheet.getSheetByName(sheetName);
  var lastRow = sheet.getLastRow();

  // 以下、手順2で確認したRedmineのチケット項目に合わせて任意に変更してください。
  // トラッカー
  response['tracker'] = 31;

  // 期日
  response['due_date'] = formatDate(sheet.getRange(lastRow, 1).getValue());

  // 対象顧客
  response['target_clients'] = sheet.getRange(lastRow, 2).getValue();

  // 題名
  response['subject'] = sheet.getRange(lastRow, 10).getValue();

  // 説明
  response['description'] = "♦︎依頼詳細\n" + sheet.getRange(lastRow, 3).getValue()

  // 調査依頼内容
  response['request_detail'] = sheet.getRange(lastRow, 3).getValue();

  // 案件番号
  response['case_number'] = sheet.getRange(lastRow, 4).getValue();

  // 通知メンバー1
  response['notice_1'] = removeAtSymbol(sheet.getRange(lastRow, 5).getValue());

  // 通知メンバー2
  response['notice_2'] = removeAtSymbol(sheet.getRange(lastRow, 6).getValue());

  // 通知メンバー3
  response['notice_3'] = removeAtSymbol(sheet.getRange(lastRow, 7).getValue());

  // 作成者
  response['created_user'] = removeAtSymbol(sheet.getRange(lastRow, 8).getValue());

  return response;
}

// Slackメッセージ送信用フォーマット
function srSlackMessage(data) {
  let users = getUsers()

  let notice_1 = fetchUserIdByDisplayName(users, data['notice_1'])
  let notice_2 = fetchUserIdByDisplayName(users, data['notice_2'])
  let notice_3 = fetchUserIdByDisplayName(users, data['notice_3'])
  let created_user = fetchUserIdByDisplayName(users, data['created_user'])

  let notice_3_text = ''
  if (notice_3 !== undefined) {
     notice_3_text = ' *通知メンバー③* \n' + '<@' + notice_3 + '>'
  }
  
  // 以下、Slack通知フォーマットを任意に変更してください。
  const json =
  {
    'text': '<@' + created_user + '>さんからの *Discoveriez調査依頼フォーム* の送信\n\n'
     + ' *期日* \n' + data['due_date'] + '\n\n'
     + ' *概要* \n' + data['subject'] + '\n\n'
     + ' *対象顧客* \n' + data['target_clients'] + '\n\n'
     + ' *調査依頼内容* \n' + data['request_detail'] + '\n\n'
     + ' *通知メンバー①* \n<@' + notice_1 + '>\n\n'
     + ' *通知メンバー②* \n<@' + notice_2 + '>\n\n'
     + notice_3_text
  };
  
  return json;
}
redmine.gs
function createTicket(data) {
  // 以下、手順2で確認したチケット項目に合わせて変更してください。
  const payload = {
    "issue": {
         "project_id": 237
       	,"tracker_id": data['tracker']
        ,"status_id": 1
        ,"priority_id": 2
        ,"description": data['description']
        ,"subject": data['subject']
        ,"due_date": data['due_date']
        ,"custom_field_values": {
           "66": data['case_number']
        }
    }
  };
  
  let redmine_url = 'https://{RedmineURL}/issues.json';
  let api_key = prop.REDMINE_API_KEY; // RedmineのAPI key を入力
  let headers = {
      'X-Redmine-API-Key': api_key,
      'Content-Type': 'application/json',
  };
  let options = {
      'method': 'POST',
      'contentType': 'application/json',
      'headers': headers,
      'payload': JSON.stringify(payload),
  };
  let response = UrlFetchApp.fetch(redmine_url, options);
  let id = JSON.parse(response).issue.id;
  return 'https://{RedmineURL}/issues/' + id;
}

slack.gs
function post_slack(ticketUrl, json) {
  // SlackのWebhookURL
  const slack_webhook_url = prop.WEBHOOK_TEST_DM; // webhook url入力
  
  json.text += '\n\n*チケットURL* \n' + ticketUrl

  // SlackのWebhook URLに送信するデータをJSONに変換する
  const payload = JSON.stringify(json);

  // UrlFetchAppで使用するメソッドやコンテントタイプを指定
  const options =
  {
    'method': 'post',
    'contentType': 'application/json',
    'payload': payload
  };

  // Slackに送信
  UrlFetchApp.fetch(slack_webhook_url, options);
}

function getUsers() {
  var slackToken = prop.BOT_USER_OAUTH_TOKEN;

  // ユーザーリストを取得するためのSlack APIエンドポイント
  var url = "https://slack.com/api/users.list";

  // APIリクエストのオプション
  var headers = {
    'Authorization': 'Bearer '+ slackToken
  };
  var options = {
     "method": "get"
    ,"headers": headers
  };
  
  // Slack APIからユーザーリストを取得
  var response = UrlFetchApp.fetch(url, options);
  var users = JSON.parse(response.getContentText()).members;

  return users;
}

function fetchUserIdByDisplayName(users, displayName) {
  // 表示名からユーザーIDを検索
  var userId = null;
  for (var i = 0; i < users.length; i++) {
    if (users[i].profile.display_name === displayName || users[i].name === displayName) {
      userId = users[i].id;
      break;
    }
  }
  
  // ユーザーIDが見つからない場合の処理
  if (userId === null) {
    Logger.log("指定された表示名のユーザーが見つかりません: " + displayName);
    return;
  }
  return userId;
}

tools.gs
function formatDate(date) {
  var date = new Date(date);
  var format = "yyyy-MM-dd";
  var timeZone = Session.getScriptTimeZone();
  var formattedDate = Utilities.formatDate(date, timeZone, format);
  return formattedDate;
}

function removeAtSymbol(str) {
  if (str.charAt(0) === '@') {
    return str.substring(1);
  }
  return str;
}

新規フォームの追加方法

複数のフォームを扱えるように、共通部分は切り出しています。
フォームを追加するときは以下の手順で追加可能です。

① スプレッドシートに新規シートを追加
② Slackワークフローを新規作成し、データの追加先に上記のシートを指定
③ form.gsをもとに該当フォーム用のgsファイルを作成し、項目調整
④ 以下のようにmain.gsに分岐追加

main.gs 分岐追加例
  //シート名が「①で追加したシート」であれば「②で追加したgsファイルのmain関数」を実行する
  if (sheetName == '見積もり依頼フォーム') {
    response = qrMain(sheetName)
  
  } else if(sheetName == '新規機能開発依頼フォーム' ) {
    response = drMain(sheetName)
  } 

※ 関数は全ファイルでユニークになるよう設定してください。
GASの関数はすべてグローバル関数となるので、複数ファイルで同一の名前の関数名をつけてはならないため(泣)

動作確認

Slackワークフローを実行し、Redmineチケット作成とSlack通知が完了すればOK!

ちなみに、GASの実行ログは以下「実行数」で確認できます。
スクリプト内でLogger.log()を記載すればここにログが出力されるのでデバッグも可能です。

image.png

おわりに

GASは初めて触りましたが、簡単にツールを作成することができて楽しかったです。
Notion APIなどでも遊んでみたいと思います!誰かの参考になれば幸いです。

参考記事

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?