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 Workflow フォームの最適化と他のワークフローツール(Jira、Asana、Slackなど)との統合による開発効率の向上。

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

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

2025/07 アップデート:

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

背景

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

BitriseのようなGUIによるYAMLビルド方法と、Jenkinsのように自前のマシンを使う柔軟性や低コストのビルド環境の両方を享受でき、しかもJenkinsのようにサービス自体のメンテナンスに時間をかける必要がありません。

時間があれば、後で完全な App CI/CD と Github Action の構築過程についての記事を書きます。

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

Github Action GUI Form

Github Action GUI フォーム

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

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

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

しかも、ここでは動的なフォームやデータ検証ができません。

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

自作 Slack App で解決

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

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

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

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

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

❌ プライベート Github Pages

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 Action をトリガーし、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% のレスポンシブデザインを実現するのは難しいです。
    .setWidth() を使ってウィンドウの幅を調整するしかありません。

Google Apps Script の認証警告

初めて使用する場合、「デバッグ」または「実行」をクリックすると、以下の認証警告が表示されることがあります:

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

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

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

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

私たちは Github.gs というファイルを新たに作成し、Github API に関するロジックを格納します。

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つのアクセス方法があります。1つは従来の Restful、もう1つはより柔軟な 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.

コメント