SlackからGitHubにissueを登録するアプリをGoogle App Scriptを使って作ってみた

SlackからGitHubにissueを登録するアプリをGoogle App Scriptを使って作ってみた

昨今、タスク管理のサービスは様々ありますが、僕はGitHubにプライペートなリポジトリを作ってissueをタスク管理の仕組みとして使っています。

そんな中でいくつかのissueを連続して登録するときに「毎回ボタンをポチポチ押すのが面倒だな〜」と思うことがあったので、slackからバシバシissue登録できる仕組みを作ってみました。

GitHubのissueでタスク管理する

いままでTodoistやTrelloを試してきましたが、なかなかしっくりこなくて、あっちのサービス・こっちのサービスと渡り歩いてきました。

ある時、「なるべくGitHubのissueに登録して管理している。細かな作業はそれ用のリポジトリを作っている。」という知り合いがいて、それをヒントにGitHubにタスク管理用のプライペートリポジトリ作って、issueでタスク管理するようにし始めました。

issueの一覧はTodoリストとして確認できるし、Projectを作ればカンバン方式で案件の進捗具合を確認できます。

さらには、issueを作ったりcloseしたりするとcontributionsのグラフに色がついて活動してる感じがでます。
大事ですよね。活動してる”風”。

ただ、issueを登録するときの煩わしさと仲良くする必要がありまして…

GitHubのissue登録

issueを1件登録するために「New issue」ボタン押す→タイトル入力→「Submit new issue」ボタン押す…

というややめんどくさい手順が必要になります。
issue1件だけならいいのですが、タイミング的に複数件一気に登録したいときは面倒くささ100倍です…

そんな面倒くささを解消するために、普段開きっぱなしにしているslackからサクッとissue登録するための仕組みを作ってみました。

slackからGitHubにissueを登録する仕組み

前置きが長くなりましたが、今回作ったslackからGitHubにissueを登録する仕組みを紹介します。

構成

構成というか、使うサービス・仕組みはこちら▼

  • slack
  • Google App Script
  • GitHub GraphQL API v4

slackからスラッシュコマンドでGoogle App Scriptで作ったWEBアプリを実行します。

Google App Scriptを使っているのは、自分だけが使うオレオレツールなのでWEBアプリのURLがわけわからんURLでも問題ないのと、GASならサーバーとかドメインとか気にしなくていいのでお手軽だからです。

GitHubへのissue登録はREST APIではなくGraphQL APIを使ってみます。
ココらへんは単に「せっかくだから使ってみよう」と思っただけです。自分だけしか使わないツールなので動かなくなってもいいのでなるべく挑戦していきます。

GitHubへissueを登録するGoogle App Script

GitHub GraphQL API v4を使ったissueの取得・登録は以前に試した記事をご参考にしてください。

作ったGASのコードはこちら▼

/**
 * Config
 */
var ACCESS_TOKEN = '作成したアクセストークン';
var ENDPOINT = 'https://api.github.com/graphql';
var REPO_ID = 'issue登録するリポジトリのID';

/**
 * doGet
 */
function doGet(e) {
  return createTextOutput('Hello World!');
}

/**
 * doPost
 */
function doPost(e) {
  //受け取ったパラメーターの分解
  var param = parseParameter(e);
  if(param.title=='') {
    return createTextOutput('titleの指定は必須です。');
  }
  createGitHubIssue(param.title,param.body,param.project,param.label);
  return createTextOutput('');
}

/**
 * パラメーターの分解
 */
function parseParameter(e) {
  var param = e.parameter.text.split(' ')
  //title
  var title = param[0];
  param.shift();
  //body
  var body = '';
  if(param.length>=1) {
    //残りを全部本文にする
    body = param.join(' ');
  }
  //projectとlabelは後で分解方法考える
  var param_data = {
    'title': title,
    'body':  body,
    'project' : '',
    'label' : '',
  };

  return param_data;
}

/**
 * Issue登録
 */
function createGitHubIssue(title,body,project,label) {
  //mutation
  var mutation = createMutation(title,body,project,label);

  var options = {
    'method' : 'post',
    'contentType' : 'application/json',
    'headers' : {
      'Authorization' : 'Bearer ' +  ACCESS_TOKEN,
      'Accept' : 'application/vnd.github.starfire-preview+json',
     },
    'payload' : JSON.stringify({query:mutation})
  };
  return post(options);
}

/**
 * Issue作成mutation作成
 */
function createMutation(title,body,project,label) {
  return 'mutation {\
    createIssue('+createIssueInput(title,body,project,label)+') {\
      issue {\
        title,\
        url\
      }\
    }\
  }';
}

/**
 * Issue作成の為のinputを作成
 */
function createIssueInput(title,body,project,label) {
  var repositoryId = 'repositoryId:"'+REPO_ID+'"';
  title = ',title:"'+title+'"';
  if(body!='') {
    body = ',body:"'+body+'"';
  }
  if(project!='') {
    project = ', projectIds:['+getProjectIds(project)+']';
  }
  if(label!='') {
    label = ',labelIds:['+getLabelIds(label)+']';
  }
  return 'input:{'+repositoryId+title+body+project+label+'}';
}

/**
 * プロジェクトIDの取得
 */
function getProjectIds(project) {
  return '';
}

/**
 * ラベルIDの取得
 */
function getLabelIds(label) {
  return '';
}

/**
 * queryの実行
 */
function getGitHubData(query) {
  var options = {
    'method' : 'post',
    'contentType' : 'application/json',
    'headers' : {
      'Authorization' : 'Bearer ' +  ACCESS_TOKEN
     },
    'payload' : JSON.stringify({query:query})
  };
  return post(options);
}

/**
 * UrlFetchApp.fetch
 */
function post(options) {
  //Issue登録のときの'Accept' : 'application/vnd.github.starfire-preview+json' がなくなればもう少しさっぱりまとめられる
  var response = UrlFetchApp.fetch(ENDPOINT, options);
  var json = JSON.parse(response.getContentText());

  Logger.log(json);
  return json;
}

/**
 * 結果返却用JSON作成
 */
function createJSONOutput(data) {
  return ContentService.createTextOutput(JSON.stringify(data)).setMimeType(ContentService.MimeType.JSON);
}
/**
 * 結果返却用テキスト作成
 */
function createTextOutput(text) {
  return ContentService.createTextOutput(text);
}

Google App Scriptを作成したらウェブアプリケーションとして登録し、URLを取得しておきます。

GASのウェブアプリケーション登録

ウェブアプリケーションとして登録する手順は前々回の記事を参考にしてください▼

slackのスラッシュコマンドを登録

slackから実行するコマンドを以下のリンクから登録します。

Create an App をクリックしてアプリの登録を開始

「Create an App」をクリックします。

アプリ名と追加するワークスペースを選択

アプリの名前と有効化するワークスペースを選択します。

スラッシュコマンドの設定をする

続いて、「Slash Commands」メニューを選択し、「Create New Command」をクリックします。

コマンドのパラメーター設定

スラッシュコマンドの設定で以下の内容を入力します。

  • Command : slackで自分が入力することになるコマンド名
  • Request URL : GASをウェブアプリケーションとして登録したURL
  • Short Description : アプリの簡単な説明
  • User Hint : コマンドを入力したときに表示される入力のヒント
作成したAppをinstallする

設定が完了したら、「Install App」メニューを選択し、「Install App to Workspace」をクリックします。

slackからコマンドを実行してみる

実際にSlackでスラッシュコマンドを入力してみて、サジェストされればOKです!

Slackからissueを登録

slackからissueを登録する

issueの登録はスラッシュコマンドの後にタイトルと本文をスペース区切りで入力します。

1つ目はタイトルとして登録され、残りは全て本文になります。本文は一応複数行の登録に対応したつもりです。

GitHub Appを有効化しておくと、登録結果を通知できる

GitHub Appをワークスペースにインストールしてリポジトリをsubscribeしておけば、issue登録時に通知を受け取れます。

GASのreturnで組み込むこともできますが、GitHub Appに任せたほうがアイコンとかもいい感じにでるのでオススメです。

まとめ

これでサクサクとslackからissueを登録できるようになりました!

GASを使ってるので、やろうと思えば定期的にissueやマイルストーンなども作れそうなのでいろいろ自動化ができるかなーと思います。

ではまた。