ZhgChg.Li

Google Apps Script Web AppでGithub Action CI/CDを効率化|フォーム連携で開発スピード向上

Github Action WorkflowをGoogle Apps Script Web Appフォームと連携し、JiraやAsana、Slackなどのツールと統合。開発効率を最大35%向上させる具体的手法を解説します。

Google Apps Script Web AppでGithub Action CI/CDを効率化|フォーム連携で開発スピード向上
本記事は AI による翻訳です。お気づきの点があればお知らせください。

Google Apps Script Web App フォームを使った Github Action CI/CD ワークフローの連携

Github Action ワークフローフォームの最適化と他のワークフローツール(Jira、Asana、Slack など)との統合による開発効率の向上。

左:元の Github Action Workflow フォーム / 右: 最終成果 (GAS Web App フォーム)

左:元の Github Action Workflow フォーム / 右: 最終成果 (GAS Web App フォーム)

2025/07 アップデート:

この機能は実際のパッケージングツールに統合されています。最新の記事事例をご参照ください:「CI/CD 実践ガイド(4):Google Apps Script Web App を使って GitHub Actions と連携し、無料で使いやすいパッケージングツールプラットフォームを構築する

背景

以前のチームは Github Action と Self-hosted Github Runner、さらに Slack を組み合わせて一連の CI/CD サービスを構築していました。全体的に効果は良好で、アプリ開発者にとっては構築とメンテナンスが比較的簡単でした。公式ドキュメントにある YAML パラメータに従って設定すれば自動でトリガーされ、マシン面でも自分のマシンを Runner として簡単に使えました。サービス自体は Github が管理しており、バージョンアップなどを気にする必要がなく、Runner は逆方向に Github からタスクを受け取るため、外部ネットワークのポート開放も不要でした。

BitriseのようなGUIでのYAMLビルド方式と、Jenkinsのように自前のマシンを使う柔軟性や低コストのビルドを同時に享受できるが、Jenkinsのようにサービス自体のメンテナンスに時間をかける必要はない。

時間があれば、完全な App CI/CD と GitHub Actions の構築プロセスについての記事を書きます。

問題:Github Action CI/CD GUI フォーム

Github Action GUI Form

Github Action GUIフォーム

アプリ開発では、CDでテスト版や正式版のビルド、審査申請をトリガーする際に、外部パラメータの提供や環境・ブランチの選択が必要であり、それに応じて処理が開始されます。

Jenkinsは自前でサービスを構築し、完全なWeb GUIを持っていますが、GitHub Actionsにはそれがありません。唯一のWeb GUIフォームは、Actions内の「Run workflow」をクリックすると表示される簡易フォームで、ユーザーが外部パラメータを入力してCI/CDワークフローをトリガーできます。

通常このCDパッケージを使用するユーザーは、必ずしもアプリ開発者本人やプロジェクトの権限を持っているわけではありません。例えば、QAが特定バージョンをパッケージ化したり、PMやバックエンドが開発中のバージョンをテスト用にパッケージ化したりします。GitHub Actionsのフォームはそのプロジェクトの権限がないと使用できませんが、ユーザーはプロジェクトの権限を持っているとは限らず、エンジニアリングのバックグラウンドも必ずしも必要ありません。

そしてここでは動的なフォームやデータ検証を行うことができません。

そのため、他のユーザーが操作できるように別のGUIサービスを用意する必要があります。

自作 Slack App で解決

以前、チームには自動化を熱愛するメンバーがいて、Kotlin+Ktor を使って完全な Slack App のウェブサービスを構築しました。Slack のメッセージ、フォーム、コマンドなどの機能と連携し、CD パッケージのリクエストを受信・転送し、GitHub Actions をトリガーして操作を実行し、その結果を Slack に返す仕組みです。

現在は開発リソースがなく、以前のように Kotlin+Ktor でサービスを構築していません

自分で作る Web/iOS/macOS アプリツール

現在のチームはもともと Jenkins を使用しており、基本的な Web インターフェースで他のユーザーがログインして使用できるようにしています。さらに、非エンジニアのユーザーが使いやすいように、パラメータをラップする App を自作して Jenkins と連携させています。

しかし、Github Action に移行した後、この一連の仕組みは廃止されました。

❌ プライベートGithubページ

直接 Github Pages を CI/CD Web GUI として構築することも可能ですが、現在は Github Enterprise のみが Github Pages のアクセス権限を設定できます。他のプランでは、Private Repo であっても公開されてしまい、安全性が確保できません。

❌ Slack App、しかし Google Apps Script で構築

最初は以前のチームの経験に基づいて Slack App を CI/CD GUI フォームサービスとして使おうと考えましたが、現在は以前のように Kotlin+Ktor でサービスを構築するリソースがありません。そのため、まずは Function as a Service で手早く構築を試みようと思いました。

Function as a Service には多くの種類がありますが、Cloud Functions は自由度が高いです。しかし、組織のIT制限により、自由にパブリッククラウド関数を追加できず、料金の問題もあるため、やはり私たちの古くからの友人である Google Apps Script に戻ります。

以前にGoogle Apps Scriptを使った自動化に関する記事をいくつか書きました。興味のある方はご参照ください:

1. Google Apps Scriptを使った日次データレポートのRPA自動化

2. 「 簡単3ステップ — 無料で作るGA4自動データ通知ロボット

3. 「 Crashlytics + Google Analyticsで自動的にアプリのクラッシュフリーユーザー率を照会

4. 「 Crashlytics + Big Query を使ったよりリアルタイムで便利なクラッシュ追跡ツールの構築

総じて、Google Apps Script は Google のもう一つの Function as a Service サービスで、主な特徴は無料で Google サービスと素早く連携できることです。しかし制限も多く、例えば専用の言語しか使えない、実行時間が6分を超えられない、実行回数の上限がある、マルチスレッドに対応していないなどがあります。詳細は私の以前の記事をご参照ください。

結論は不可能である理由は以下の通りです:

  • Function as a Service コールドスタート問題
    サービスが一定時間呼び出されないとスリープ状態になり、再度呼び出すと起動に時間がかかる(3〜≥5秒)。Slack App は API の応答時間に非常に厳しく、サービスは3秒以内に応答しないと失敗とみなされます。そのため、Slack 側でエラーが発生し、イベントリスナーも失われたと判断され、再送が繰り返されます。

  • Google Apps ScriptのdoGet、doPostメソッドではHeadersを取得できません。
    これにより、公式のセキュリティ認証が利用できず、Slackのリトライ機能を無効にすることもできません。

  • Google Apps Script の単一スレッド問題。
    他のサービスと連携する場合、応答時間が3秒を超えるとSlackに失敗と判断されます。

Slackのメッセージ、Block Kit、フォームを無理やり使って一連のプロセスをつなげましたが、上述の問題が頻発したため、最終的に断念しました。

このシステムを構築する場合は、やはり自分でサーバーやサービスを立てるべきで、Function as a Serviceは使わないでください!

❌ Slack ワークフローフォーム

Slack Workflow Form (❌ カスタマイズ不可)

Slack Workflow Form (❌ カスタマイズ不可)

また、Slack内蔵の自動化機能であるWorkflow Formも試しましたが、動的なフォーム内容(例:ブランチを取得してユーザーに選ばせる)はできず、カスタマイズできるのはデータ送信後のステップのみでした。

✅ Google Apps Script Web App GUI フォーム

山が動かなくても道は変わる、考えを変えれば必ずしも Slack 統合にこだわる必要はないと思えた。Slack 統合は既存のチーム協業ツールに直接組み込めるため最良の方法だが、リソースの制約から他の安定して使いやすい方法を探さざるを得なかった。

振り返ると、Google Apps Script 自体が Web アプリとしてデプロイ可能で、Web の doGet 時に GUI フォームを返し、フォーム送信後に GitHub 連携の処理をトリガーできる。

最終成果 🎉

Demo Web App Form

Demo Web App Form

ワークフロー

私たちは Google Apps Script Web App を使って CI/CD フォームを構築し、Google Workspace アカウントに直接紐付けて、組織内のユーザーのみがアクセスできるように設定しています。現在ログインしているユーザーのメールアドレスを自動取得し、GitHub リポジトリの共有アカウント(または権限を持つアカウントの Personal Access Token)を使って GitHub API からブランチ一覧を取得し、送信後に同じく API を呼び出して GitHub Actions をトリガーし、CI/CD のジョブを開始します。

また、ユーザーのメールアドレスを使って Slack App 経由で Slack API を呼び出し、そのユーザーの Slack ID を取得し、Slack App を通じてメッセージを送信し、CI/CD タスクの実行状況を通知することができます。

他のツールや開発プロセスと連携することも可能です。例えば、まず Asana や Jira からチケットを取得し、選択後に Github API を使ってブランチを検索し、Github Action をトリガーします。最後に Slack でユーザーに通知します。

Step 1. Google Apps Script Web App フォームの作成

Google Apps Script にアクセスし、新しいプロジェクトを作成します。

Step 2. フォーム内容と GAS スクリプトの作成

HTMLやCSSを書くのが久しぶりで、自分でスタイルをデザインするのも面倒なので、ChatGPTに少しデザインされたHTMLフォームのテンプレートを作ってもらいました。

GAS の左側ファイル一覧で「+」をクリックし、新しいファイル名に「Form.html」を入力して、GPT が生成した HTML フォームのテンプレート内容を貼り付けます。

Form.html:

<!--HTML & Style Gen by ChatGPT 4o-->
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title><?=title?></title>
  <style>
    body {
      font-family: Arial, sans-serif;
      margin: 0;
      padding: 20px;
      background-color: #f7f7f7;
    }
    .form-container {
      max-width: 600px;
      margin: auto;
      padding: 20px;
      background-color: #ffffff;
      border-radius: 8px;
      box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
    }
    .form-container h2 {
      margin-bottom: 20px;
      color: #333333;
    }
    .form-group {
      margin-bottom: 15px;
    }
    .form-group label {
      display: block;
      margin-bottom: 5px;
      font-weight: bold;
      color: #555555;
    }
    .form-group input,
    .form-group select,
    .form-group textarea {
      width: 95%;
      padding: 10px;
      border: 1px solid #cccccc;
      border-radius: 4px;
      font-size: 16px;
    }
    .form-group input[type="radio"] {
      width: auto;
      margin-right: 10px;
    }
    .form-group .radio-label {
      display: inline-block;
      margin-right: 20px;
    }
    .form-group button {
      background-color: #4CAF50;
      color: white;
      padding: 10px 20px;
      border: none;
      border-radius: 4px;
      cursor: pointer;
      font-size: 16px;
    }
    .form-group button:hover {
      background-color: #45a049;
    }
    .message {
      margin-top: 20px;
      padding: 15px;
      border-radius: 5px;
      font-size: 1em;
      text-align: center;
    }
    .message.success {
      background-color: #d4edda;
      color: #155724;
      border: 1px solid #c3e6cb;
    }
    .message.error {
      background-color: #f8d7da;
      color: #721c24;
      border: 1px solid #f5c6cb;
    }
    .hidden {
      display: none;
    }
  </style>
</head>
<body>
  <div class="form-container">
    <h2><?=title?></h2>
    <form id="myForm">
      <div id="message-block" class="hidden"></div>
      <div class="form-group">
        <label for="email">メールアドレス:</label>
        <input type="email" value="<?=email?>" readonly/>
      </div>
      <div class="form-group">
        <label for="buildNumber">バージョン番号:</label>
        <input type="number" value="<?=buildNumber?>"/>
      </div>
      <div class="form-group">
        <label for="branch">レビュー中のPR:</label>
        <select id="branch" name="branch">
          <option>選択してください</option>
          <? pullRequests.forEach(pullRequest => { ?>
            <option value="<?=pullRequest.head.ref?>">[<?=pullRequest.state?>] <?=pullRequest.title?></option>
          <? }); ?>
        </select>
      </div>
      <div class="form-group">
        <label for="message">更新内容:</label>
        <textarea id="message" name="message" rows="4" placeholder="メッセージを入力してください"></textarea>
      </div>
      <div class="form-group">
        <button type="submit">送信</button>
      </div>
    </form>
  </div>
  <script>
    function displayMessage(ok, message) {
      const messageBlock = document.getElementById('message-block');
      messageBlock.className = ok ? 'message success' : 'message error';
      messageBlock.innerHTML = message;
      messageBlock.classList.remove('hidden');
    }
    
    document.getElementById("myForm").addEventListener("submit", function(e) {
      e.preventDefault();
      const formData = new FormData(this);
      const formObject = Object.fromEntries(formData);
      google.script.run.withSuccessHandler((response) => {
        displayMessage(response.ok, response.message);
      }).processForm(formObject);
    });
  </script>
</body>
</html>

フォームの内容は必要に応じて自由に調整できます。

コード.gs:

function doGet(e) {
  // 左側のファイル Form.html に対応
  const htmlTemplate = HtmlService.createTemplateFromFile('Form');
  
  const email = Session.getActiveUser().getEmail();
  // ユーザーのメールアドレスを取得、実行権限:ウェブアプリのアクセスユーザーに限る
  
  const title = "App CD パッケージリクエストフォーム";
  
  const buildNumber = genBuildNumber();

  htmlTemplate.email = email;
  htmlTemplate.title = title;
  htmlTemplate.pullRequests = []; // 次は Github と連携...
  htmlTemplate.buildNumber = buildNumber;

  const html = htmlTemplate.evaluate();
  html.setTitle(title);
  //html.setWidth(600) // ページ幅の設定

  return html
}

function processForm(object) {
  return {"ok": true, "message": "リクエストが正常に送信されました!"};
}

function genBuildNumber() {
  const now = new Date();
  const formattedDate = Utilities.formatDate(now, "Asia/Taipei", "yyyyMMddHHmmss");
  const milliseconds = now.getMilliseconds().toString().padStart(3, '0'); // ミリ秒を3桁に固定
  return `${formattedDate}${milliseconds}`; 
}

このステップでは、まずフォームのGUIを完成させ、次にGitHub APIを接続してPRのブランチリストを取得します。

Step 2. Google Apps Script Web App フォームのデプロイ

まずは先ほどの内容を一度デプロイして、結果を確認しましょう。

GAS の右上で「デプロイ」->「新しいデプロイ」->「ウェブアプリ」を選択:

実行権限とアクセス権限はそれぞれ以下のように設定できます:

実行者:

  • 統一はあなたのアカウントでスクリプトを実行します。

  • ウェブアプリの利用者は、現在ログインしている Google アカウントのユーザー権限でスクリプトを実行します。

誰がアクセスできますか:

  • 私だけです

  • XXX 同じ組織内の全ユーザー 同じ組織かつログイン済みのGoogleアカウントユーザーのみアクセス可能。

  • ログインしているすべての Google アカウントのユーザー ログインしている Google アカウントのユーザーは誰でもアクセス可能です。

  • すべての人が Googleアカウントのログイン不要、誰でも公開アクセス可能。

「アクセス可能なユーザー:XXX 同じ組織内のすべてのユーザー」+「実行者:ウェブアプリにアクセスするユーザー」を選択することで、組織アカウントの人だけが使用できるように自動で制限され、かつ自分の権限で実行されます!

とても便利な権限管理機能です!

右下の「デプロイ」をクリックしてください。

ウェブアプリケーションのURLは、Web AppのアクセスURLです。

https://script.google.com/macros/s/AKfycbw8SuK7lLLMdY86y3jxMJyzXqa5tdxJryRnteOnNi-lK--j6CmKYXj7UuU58DiS0NSVvA/exec

URLが長くて見た目が悪いですが、仕方なく自分で短縮URLツールを使って短くしました。

リンクをクリックしてページの効果を確認:

ここでGASの制限を2つ追加で説明します:

  • GAS Web App の上部警告メッセージは、デフォルトで非表示にできません

  • GAS Web App は IFrame を使ってページを埋め込んでいるため、100% のレスポンシブデザイン(RWD)を実現するのは難しいです。
    .setWidth() でウィンドウの幅を調整するしかありません。

Google Apps Script 承認警告

初回使用時、「デバッグ」または「実行」をクリックすると、以下の権限警告が表示されることがあります:

実行したいアカウントを選択し、「このアプリは Google による確認を受けていません」と表示された場合は、「詳細」->「XXX(安全ではありません)に移動」をクリックし、「許可」を選択してください:

GAS プログラムの権限に変更がある場合(例:Google Sheet へのアクセス権追加など)、再度認証が必要になります。それ以外は、一度認証すれば再表示されません。

もし「XXX」へのアクセス権がブロックされ、Google認証手続きが完了していない場合は、私の最新記事のGCP設定を参照してください。

Step 3. Github API を連携してPRブランチ一覧を取得する

Github API に関するロジックを格納するために、新しく Github.gs ファイルを作成します。

Github.gs:

// SECRET
const githubPersonalAccessToken = ""
// あなたのGithubアカウントまたは組織共有のGithubアカウントでPATを作成してください
// https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens

// 方法 1: Restful API アクセス
function githubAPI(method, path, payload = null) {
  try {
    const url = "https://api.github.com"+path;  
    var options = {
      method: method,
      headers: {
        "Accept": "application/vnd.github+json",
        "Authorization": `Bearer ${githubPersonalAccessToken}`,
        "X-GitHub-Api-Version": "2022-11-28"
      }
    };

    if (method.toLowerCase().trim() == "post") {
      options.payload = JSON.stringify(payload);
    }

    const response = UrlFetchApp.fetch(url, options);
    const data = JSON.parse(response.getContentText());
    return data;
  } catch (error) {
    throw error;
  }
}

// 方法 2: GraphQL アクセス
// Github API の一部の詳細なクエリ機能はGraphQL APIでのみ提供されています
// https://docs.github.com/en/graphql
function githubGraphQL(query, variables) {
  const url = "https://api.github.com/graphql";
  const payload = {
    query: query,
    variables: variables
  };

  const options = {
    method: "post",
    contentType: "application/json",
    headers: {
      "Accept": "application/vnd.github+json",
      "Authorization": `Bearer ${githubPersonalAccessToken}`,
      "X-GitHub-Api-Version": "2022-11-28"
    },
    payload: JSON.stringify(payload)
  };

  try {
    const response = UrlFetchApp.fetch(url, options);
    const data = JSON.parse(response.getContentText());
    return data;
  } catch (error) {
    throw error;
  }
}

// GraphQL 例:
// const query = `
//   query($owner: String!, $repo: String!) {
//     repository(owner: $owner, name: $repo) {
//       pullRequests(states: OPEN, first: 100, orderBy: { field: CREATED_AT, direction: DESC }) {
//         nodes {
//           title
//           url
//           number
//           createdAt
//           author {
//             login
//           }
//           headRefName
//           baseRefName
//           body
//         }
//         pageInfo {
//           hasNextPage
//           endCursor
//         }
//       }
//     }
//   }
// `;
// const variables = {
//   owner: "swiftlang",
//   repo: "swift"
// };
// const response = githubGraphQL(query, variables);

Github API には2つのアクセス方法があります。ひとつは従来の Restful、もうひとつはより柔軟な GraphQLです。本記事では Restful を例に説明します。

程式碼.gs:

function doGet(e) {
  // 左側のファイル Form.html に対応
  const htmlTemplate = HtmlService.createTemplateFromFile('Form');
  
  const email = Session.getActiveUser().getEmail();
  // ユーザーのメールアドレスを取得。実行者の権限:ウェブアプリ利用者のみ有効

  const title = "App CD パッケージリクエストフォーム";
  
  const pullRequests = githubAPI("get", "/repos/swiftlang/swift/pulls");
  // 例:https://github.com/swiftlang/swift/pulls
  
  const buildNumber = genBuildNumber();

  htmlTemplate.email = email;
  htmlTemplate.title = title;
  htmlTemplate.pullRequests = pullRequests;
  htmlTemplate.buildNumber = buildNumber;

  const html = htmlTemplate.evaluate();
  html.setTitle(title);
  //html.setWidth(600) // ページの幅を設定

  return html
}

function processForm(object) {
  if (object.buildNumber == "") {
    return {"ok": false, "message": "バージョン番号を入力してください!"};
  }
  if (object.branch == "") {
    return {"ok": false, "message": "ブランチを選択してください!"};
  }

  // GitHub Action に渡すパラメータ
  const payload = {
    ref: object.branch,
    inputs: {
      buildNumber: object.buildNumber
    }
  };
  
  //  
  try {
    const response = githubAPI("post", "/repos/zhgchgli0718/ios-project-for-github-action-ci-cd-demo/actions/workflows/CD-Job.yml/dispatches", payload);
    // 例:https://github.com/zhgchgli0718/ios-project-for-github-action-ci-cd-demo/blob/main/.github/workflows/CD-Job.yml

    return {"ok": true, "message": `パッケジリクエストを送信しました!<br/>対象ブランチ:<strong>${object.branch}</strong><br/>ジョン番号:<strong>${object.buildNumber}</strong>`};
  } catch (error) {
    return {"ok": false, "message": "エラーが発生しました:"+error.message};
  }
}

processForm メソッドではフォームからの返却内容を処理しますが、さらに多くの処理を追加することも可能です。

GAS x Github API x Github Action

こちらは対応する Github Action の補足です。

CD-Job.yml:

# CD パッケージングジョブ

name: CD-Job

on:
  workflow_dispatch:
    inputs:
      buildNumber: # GAS payload.inputs.xxx に対応
        description: 'バージョン番号'
        required: false
        type: string
      # ...その他
      # 入力タイプは公式ドキュメントを参照してください:https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#onworkflow_dispatchinputs
      
jobs:
  some-job:
    runs-on: ubuntu-latest
    steps:
      - name: 入力値の表示
        run: \\|
          echo "Release Build Number: ${{ github.event.inputs.buildNumber }}"    

Step 4. Google Apps Script Web App フォームの再デプロイ

⚠️ご注意ください、GASコードの変更は再デプロイが必要であり、それによって変更が反映されます。⚠️

⚠️ご注意ください、GASコードの変更は再デプロイが必要であり、その後に有効になります。⚠️

⚠️ご注意ください、GASコードの変更は再デプロイが必要で、そうしないと反映されません。⚠️

GAS 右上の「デプロイ」から「編集」を選択し、バージョンで「新しいバージョンを作成」を選びます。

「デプロイ」→ 完了をクリック。

再度ウェブページをリロードすると、変更後の結果が表示されます:

⚠️ご注意ください、GASのコードを変更した場合は、再デプロイが必要です。⚠️

⚠️ご注意ください、GAS のコードを変更した場合は、再デプロイしないと反映されません。⚠️

⚠️ご注意ください、GAS のコードを変更した場合は、再度デプロイしないと変更が反映されません。⚠️

完了!🎉🎉🎉

Demo Web App フォーム

Demo Web App Form

現在、このリンクを組織内で他のメンバーと共有できます。彼らはこのウェブGUIを使って直接CI/CD作業を実行できます。

拡張 (1)— ユーザーのメールアドレスで Slack User ID を検索し、進捗通知の送信・更新を行う

前文で述べたように、CI/CDの実行状況をリアルタイムで通知したいため、ユーザーが提供したメールアドレスを使ってSlackのユーザーIDを取得できます。

Slack.gs:

const slackBotToken = ""
// https://medium.com/zrealm-robotic-process-automation/slack-chatgpt-integration-bd94cc88f9c9

function slackRequest(path, content) {
  const options = {
    method: "post",
    contentType: "application/json",
    headers: {
      Authorization: `Bearer ${slackBotToken}`, // 認証にボットトークンを使用
      'X-Slack-No-Retry': 1
    },
    payload: JSON.stringify(content)
  };

  try {
    const response = UrlFetchApp.fetch("https://slack.com/api/"+path, options);
    const responseData = JSON.parse(response.getContentText());
    if (responseData.ok) {
      return responseData
    } else {
      throw new Error(`Slack: ${responseData.error}`);
    }
  } catch (error) {
    throw error;
  }
}

// メールアドレスでSlack UIDを検索
function getSlackUserId(email) {
  return slackRequest(`users.lookupByEmail?email=${encodeURIComponent(email)}`)?.user?.id;
}

// 目標のSlack UID(channelID)にメッセージを送信
function sendSlackMessage(channelId, ts = null, value)  {
  var content = {
    channel: channelId
  };

  if (ts != null) {
    content.thread_ts = ts;
  }
  
  if (typeof value === "string") {
    content.text = value;
  } else {
    content.blocks = value;
  }
  return slackRequest("chat.postMessage", content);
}

// 送信済みメッセージの内容を更新
function updateSlackMessage(channelId, ts = null, value)  {
  var content = {
    channel: channelId
  };

  if (ts != null) {
    content.ts = ts;
  }
  
  if (typeof value === "string") {
    content.text = value;
  } else {
    content.blocks = value;
  }
  return slackRequest("chat.update", content);
}

Slack API の使用については 公式ドキュメント を参照してください。

Githun Action YAML はこの Action を使ってメッセージを継続的に更新し、Slack にメッセージを送信できます:

# ...
on:
  workflow_dispatch:
    inputs:
      buildNumber: # GAS payload.inputs.xxx に対応
        description: 'バージョン番号'
        required: false
        type: string
      # ...その他
      SLACK_USER_ID:
        description: 'アクション通知を受け取るSlackユーザーID'
        type: string
      SLACK_CHANNEL_ID:
        description: 'アクション通知を受け取るSlackチャンネルID'
        type: string
      SLACK_THREAD_TS:
        description: 'Slackメッセージのts'
        type: string
      
jobs:
  # ジョブの一部...

  if-deploy-failed-message:
    runs-on: ubuntu-latest
    if: failure()
      - name: slackメッセージの更新
        uses: slackapi/[email protected]
        with:
            method: chat.update
            token: ${{ secrets.SLACK_BOT_TOKEN }}
            payload: \\|
              channel: ${{ github.event.inputs.SLACK_CHANNEL_ID }}
              ts: ${{ github.event.inputs.SLACK_THREAD_TS }}
              text: " パッケージングタスクが失敗しました。実行状況を確認するか、後ほど再試行してください。\n<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\\|実行状況を確認する> cc'ed <@${{ github.event.inputs.SLACK_USER_ID }}>"

効果:

Slack App の連携詳細は、以前の記事をご参照ください:Slack & ChatGPT Integration

拡張 (2) — Jiraチケットの検索

Jira.gs:

const jiraPersonalAccessToken = ""
// https://confluence.atlassian.com/enterprise/using-personal-access-tokens-1026032365.html

function getJiraTickets() {
  const url = `https://xxx.atlassian.net/rest/api/3/search`;

  // JQLクエリ
  const jql = `project = XXX`;
  const queryParams = {
    jql: jql,
    maxResults: 50, // 必要に応じて調整
  };

  const options = {
    method: "get",
    headers: {
      Authorization: "Basic " + jiraPersonalAccessToken,
      "Content-Type": "application/json",
    },
    muteHttpExceptions: true,
  };

  const queryString = Object.keys(queryParams).map(key => `${encodeURIComponent(key)}=${encodeURIComponent(queryParams[key])}`).join("&");
  const response = UrlFetchApp.fetch(url + "?" + queryString + "&fields=", options);
  // 一部のフィールドのみ返すよう指定可能

  if (response.getResponseCode() === 200) {
    const issues = JSON.parse(response.getContentText()).issues;
    return issues;
  } else {
    Logger.log(`Error: ${response.getResponseCode()} - ${response.getContentText()}`);
    throw new Error("Jiraの課題取得に失敗しました。");
  }
} 

その他の Jira API の使用については、公式ドキュメント を参照してください。

拡張 (3) — Asana チケットの検索

Asana.gs:

const asanaPersonalAccessToken = ""
// https://developers.asana.com/docs/personal-access-token

function asanaAPI(endpoint, method = "GET", data = null) {
    var options = {
      "method" : method,
      "contentType" : "application/json",
      "headers": {
          "Authorization":  "Bearer "+asanaPersonalAccessToken
      }
    };

    if (data != null) {
      options["payload"] = JSON.stringify({"data":data});
    }

    const url = "https://app.asana.com/api/1.0"+endpoint;
    const res = UrlFetchApp.fetch(url, options);
    const data = JSON.parse(res.getContentText());
    return data;
}

// プロジェクト内のタスクを検索
// asanaAPI("/projects/PROJECT_ID/tasks");

その他の Asana API の使用方法については、公式ドキュメント を参照してください。

まとめ

自動化や業務・開発プロセスの最適化に欠けているのは技術ではなくアイデアです。アイデアさえあれば、それを実現する適切な技術は必ず見つかります。共に頑張りましょう!

2025/07 更新:

この機能は実際のパッケージングツールに統合されています。最新の記事事例をご参照ください:「CI/CD 実践ガイド(4):Google Apps Script Web App を使って GitHub Actions と連携し、無料で使いやすいパッケージングツールプラットフォームを構築する

Post Mediumから変換されたもの by ZMediumToMarkdown.

GitHub で編集
この記事を改善
本記事は Medium で初公開
オリジナルを読む
この記事をシェア
リンクをコピー · SNS でシェア
ZhgChgLi
著者

ZhgChgLi

An iOS, web, and automation developer from Taiwan 🇹🇼 who also loves sharing, traveling, and writing.

コメント