ContractS開発者ブログ

契約マネジメントシステム「ContractS CLM」の開発者ブログです。株式会社HolmesはContractS株式会社に社名変更しました。

GitLabのIssueがリリースされると関連するSalesforceの要望リストをGASでSlackに通知させてみた

こんにちは。Holmesでプロダクトオーナーをしているid:w-miuchiです。

長々としたタイトルですが、3つのアプリケーションを連動させた例として紹介させてください。

背景

開発部以外の他部門から、リリースされる機能で解決する問題の把握が難しいという意見を頂きました。過去に投稿した要望が改善されたのかどうかがわからないとのことでした。

弊社では、プロダクトバックログ(以降PBI)をGitLabのIssueで管理しています。
また、内外問わずプロダクトに対する要望はSalesforceで管理しています。

リリース時に対象となるPBIを通知しているのですが、PBIを読み取るのが難しいという問題がありました。

改善案

PBIを作成する元となった要望を通知すれば把握が進むのではないかと考えました。
その際、PBIを一つ一つ開いて確認するのではなく、ある程度の一覧性は担保したいと思います。

Salesforceの要望にはIssueのURLが登録されています。
リリース時にはPBIとなるGitLabのIssueをCloseします。このタイミングで関連付いた要望を取得し通知することにしました。

構成

構成は下記通りです。

f:id:w-miuchi:20210313120400j:plain

当初Salesforceの要望リストはAPIで取得しようかと考えていましたが、Googleスプレッドシートに同期する方が早いことがわかりました。
こちらは後ほど記載します。

設定

1. GitLab -> Goole Apps Script

今回Goole Apps Script(以降GAS)に関しては、

github.com

を利用しました。 こちらに関しては本記事では省略させて頂きます。
自身のIDEを利用でき、コマンドラインでDeployまでできるのは非常に嬉しいです。

上記で発行したURLをGitLabのWebhookに設定するだけです。
今回はTriggerをIssues eventsのみ設定しました。

f:id:w-miuchi:20210313120801p:plain

2. SalesforceGoogle スプレッドシート

こちらはGoogleスプレッドシートのアドオンで可能です。 以下に沿って設定しました。

support.google.com

3. GAS -> Google スプレッドシート

Salesforceの要望を取得している関数が以下になります。
同期しているスプレッドシートのIDを指定して取得しています。
取得したスプレッドシートjsonに変換しています。

const getCustomerRequestList = (): object[] => {
  const sheet = SpreadsheetApp.openById('xxxxxxxxxxx');
  const rows = sheet.getDataRange().getValues() as object[];
  const keys = rows.splice(0, 1)[0] as any[];
  return rows.map(function(row) {
    const obj = {};
    row.map(function(item, index) {
      obj[String(keys[index])] = String(item);
    });
    return obj;
  });
}

4. GAS -> Slack

Slack APIを利用します。

tanuhack.com

こちら参考させていただきました。

ここでハマってしまったのがbotとしているためSlackチャンネルに登録したアプリケーションのインストールが必要なことでした。

1と3を含む全体の処理は以下のとおりです。
(中途半端なTypeScriptであることはご容赦くださいmm)

const doPost = (e: any) => {
  const postData = e?.postData ? JSON.parse(e.postData.getDataAsString()) : {} as object

  // issueの変更後ステータスを取得
  const currentLabels = postData.changes?.labels?.current || [] as object[]
  /// PB以外除外
  const pbLabel = getLabel(currentLabels, ['PB'])
  if (!pbLabel) return
  /// 開発開始、Sandboxリリース、本番リリースを対象
  const isClosed = postData.object_attributes?.state === 'closed'
  const statusLabel = getLabel(currentLabels, ['Doing', 'Sandbox'])
  if (!statusLabel && !isClosed) return

  // issueのIdを取得
  const issueId = postData.object_attributes?.iid as string
  /// 関連issueを取得
  const linkedIssueList = getLinkedIssueList(issueId)
  /// 対象issueIdを設定
  const issueIdList = [issueId].concat(linkedIssueList.map(linkedIssue => {
    return linkedIssue.iid
  }));

  // 要望一覧を取得
  const customerRequestList = getCustomerRequestList();
  // Issueに該当する要望を特定
  const issueCustomerRequestList = customerRequestList.filter(customerRequest => {
    return issueIdList.some(issueId => {
      return customerRequest.GitURL__c.match(new RegExp('/' + issueId + '$'))
    })
  })

  // Slackに要望のステータス変更を出力
  /// 出力テキストを作成
  const statusLabelText = isClosed ? 'Closed' : statusLabel?.title
  const noticeContent = createNoticeContent(postData.object_attributes?.title, statusLabelText, issueId, issueCustomerRequestList)
  /// Slack書き込み
  postSlack(noticeContent)
}

おまけ

コマンドやブラウザから実行した場合はログを出力してくれるのですが、 非同期で実行させた場合、ログを出力されません。 (方法があったかもしれませんが...) そのため以下ような処理でGoogle Drive上にログを出力しました。

const writeLogs = (content: string): void => {
  const folder = DriveApp.getFolderById('xxxxxxx')
  const contentType = 'text/plain' as string
  const charset = 'utf-8' as string
  const fileName = Utilities.formatDate(new Date(), 'JST', 'yyyyMMddHHmmss') as string

  const blob = Utilities.newBlob('', contentType, fileName + '.txt')
      .setDataFromString(content, charset)

  folder.createFile(blob)
}

f:id:w-miuchi:20210308085713p:plain

課題

実は大きな問題点があります。
Salesforceの要望にはIssueのURLが登録されている
と記載しましたが、そもそもこの運用が出来てないということです。。。
今回は技術検証を先に行いましたが、まだ本運用に至っておりません。。。

最後に

いかがだったでしょうか。
課題は残っておりますが、技術的に可能なことがわかりました。
複数のサービスをWebhook、Addon、APIを利用し連動させるのは楽しいですね。
部分的にでも参考になればなによりです。