カスタムの Workspace Studio 開始条件とアクション ステップを構築する

1. はじめに

この Codelab では、Google Workspace Studio と直接連携するインタラクティブなサポート クイック ポータルを構築します。サードパーティのインフラストラクチャに依存せずにすぐにテストできるカスタム スターター ステップとカスタム アクション ステップの両方を構築する方法を学びます。

Google Apps Script を使用して、次のものを作成します。

  • ユーザーがサポート チケットを手動で送信するための洗練されたフォームを備えたウェブアプリ。
  • ウェブアプリの送信をキャプチャして Workspace Studio フローをトリガーするカスタム スターター。
  • 受信したチケットの説明を分析して緊急度(「高」または「通常」)を特定するカスタムステップ。

学習内容

  • appsscript.json マニフェストでスターターとステップを構成する方法。
  • スターター登録ライフサイクル中に notifyUri を構築する方法。
  • UrlFetchApp を使用して外部コードから Workspace Studio フローをプログラムでトリガーする方法。
  • カスタム ステップで出力変数を構築して、データをダウンストリームに渡す方法。

前提条件

  • Google Workspace Studio が有効になっている Google Workspace アカウント。
  • ドメインの管理コンソールで [未公開(テスト)のカスタムステップを許可する] 設定が有効になっている([アプリ] > [Google Workspace] > [Workspace Studio] > [カスタムステップの設定] にあります)。
  • Google Apps Script の知識。

2. Apps Script プロジェクトをセットアップする

まず、コードを格納する新しい Apps Script プロジェクトを作成します。

  1. script.google.com に移動し、[新しいプロジェクト] をクリックします。
  2. プロジェクトに「Support Quick Portal」という名前を付けます。
  3. 左側のサイドバーで プロジェクトの設定 [プロジェクトの設定] をクリックします。
  4. [「appsscript.json」マニフェスト ファイルをエディタで表示する] チェックボックスをオンにします。
  5. 編集者 [エディタ] ビューに戻ります。

デフォルトでは、Apps Script プロジェクトは非表示のデフォルトの Google Cloud プロジェクトに関連付けられています。Google Workspace Studio API を呼び出して PERMISSION_DENIED エラーを回避するには、スクリプトを標準のクラウド プロジェクトに切り替え、API を有効にして、Google Auth Platform を適切に構成する必要があります。

  1. プロジェクトを選択または作成する: Google Cloud コンソールに移動し、ページ上部のプロジェクト プルダウン メニューを使用して、既存のプロジェクトを選択するか、新しいプロジェクトを作成します。
  2. API を有効にする: 選択したプロジェクトが上部のバーでアクティブになったら、API 有効化フローを開き、[次へ]、[有効にする] の順にクリックして Workspace Studio API を有効にします。
  3. OAuth ブランディングを構成する: Google Auth Platform のブランディング ページを開きます。
    1. プロンプトが表示されたら、[使ってみる] をクリックします。(すでに構成されている場合は、ステップ 4 に進みます)。
    2. [アプリ情報] で、アプリ名とユーザー サポートのメールアドレスを入力します。[次へ] をクリックします。
    3. [対象] で [内部]([内部] がない場合は [外部])を選択し、[次へ] をクリックします。
    4. 連絡先情報を入力し、データに関するポリシーに同意して、[作成] をクリックします。
  4. データアクセスを構成する: [データアクセス] ページを開き、[スコープを追加または削除] をクリックします。
    1. [スコープを手動で追加] で、https://www.googleapis.com/auth/workspace.studio.trigger を貼り付けます。
    2. [テーブルに追加]、[更新] の順にクリックし、最後にページ下部の [保存] をクリックします。
  5. [IAM と管理] > [設定](または Cloud コンソールのダッシュボード)に移動し、[プロジェクト番号] をコピーします。
  6. Apps Script プロジェクトに戻り、左側のサイドバーで プロジェクトの設定 [プロジェクトの設定] をクリックします。
  7. [Google Cloud Platform(GCP)プロジェクト] セクションで、[プロジェクトを変更] をクリックします。
  8. コピーした GCP プロジェクト番号を入力し、[プロジェクトを設定] をクリックします。

マニフェストを構成する

新しく表示された appsscript.json ファイルを開きます。その内容を次のコードで置き換えます。このマニフェストでは、必要な OAuth スコープを明示的にリストし、studio.flows.workflowElements ブロックで開始条件を workflowTrigger、アクションを workflowAction として定義しています。

{
  "timeZone": "America/Los_Angeles",
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8",
  "webapp": {
     "executeAs": "USER_ACCESSING",
     "access": "MYSELF"
  },
  "oauthScopes": [
    "https://www.googleapis.com/auth/script.external_request",
    "https://www.googleapis.com/auth/workspace.studio.trigger",
    "https://www.googleapis.com/auth/script.storage",
    "https://www.googleapis.com/auth/script.locale"
  ],
  "urlFetchWhitelist": [
    "https://workspacestudio.googleapis.com/"
  ],
  "addOns": {
    "common": {
      "name": "Support Extension",
      "logoUrl": "https://www.gstatic.com/images/branding/productlogos/buganizer/v1/192px.svg",
      "useLocaleFromApp": true
    },
    "studio": {
      "flows": {
        "workflowElements": [
          {
            "id": "supportTicketTrigger",
            "state": "ACTIVE",
            "name": "New Support Ticket",
            "description": "Fires when someone submits a ticket via the Web App.",
            "workflowTrigger": {
              "inputs": [],
              "outputs": [
                {
                  "id": "ticketTitle",
                  "description": "The title of the ticket",
                  "cardinality": "SINGLE",
                  "dataType": { "basicType": "STRING" }
                },
                {
                  "id": "ticketDescription",
                  "description": "The full description of the issue",
                  "cardinality": "SINGLE",
                  "dataType": { "basicType": "STRING" }
                }
              ],
              "onConfigFunction": "onConfigTrigger",
              "onManageFunction": "onManageTrigger"
            }
          },
          {
            "id": "urgencyDetectorStep",
            "state": "ACTIVE",
            "name": "Detect Urgency",
            "description": "Scans the ticket description to detect High or Normal urgency.",
            "workflowAction": {
              "inputs": [
                {
                  "id": "ticketDescription",
                  "description": "The ticket description",
                  "cardinality": "SINGLE",
                  "dataType": { "basicType": "STRING" }
                }
              ],
              "outputs": [
                {
                  "id": "urgencyLevel",
                  "description": "The urgency High/Normal",
                  "cardinality": "SINGLE",
                  "dataType": { "basicType": "STRING" }
                }
              ],
              "onConfigFunction": "onConfigUrgency",
              "onExecuteFunction": "onExecuteUrgency"
            }
          }
        ]
      }
    }
  }
}

3. ウェブアプリの UI を作成する

ターミナル ツールやサードパーティの Webhook を使用せずに開始条件をトリガーする視覚的でテスト可能な方法を提供するために、Apps Script ウェブアプリを構築します。

Apps Script エディタで、[ファイル] の横にある ファイルを追加 [ファイルを追加] をクリックし、[HTML] を選択します。名前を index.html にします(.html 拡張子は自動的に追加されます)。

次の簡略化された UI コードを貼り付けます。

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <style>
      body { font-family: Arial, sans-serif; padding: 20px; max-width: 500px; margin: auto; }
      label { font-weight: bold; display: block; margin-top: 15px; }
      input, textarea { width: 100%; margin-top: 5px; padding: 10px; box-sizing: border-box; }
      button { background-color: #1a73e8; color: white; border: none; padding: 10px 15px; margin-top: 15px; font-weight: bold; cursor: pointer; border-radius: 4px; }
      button:hover { background-color: #1557b0; }
      #status { margin-top: 15px; color: green; font-weight: bold; }
    </style>
  </head>
  <body>
    <h2>Support Quick Portal</h2>
    <p>Submit your issue below to trigger the Workspace Studio flow.</p>

    <label for="title">Ticket Title</label>
    <input type="text" id="title" placeholder="e.g. Broken monitor">

    <label for="description">Issue Description</label>
    <textarea id="description" rows="4" placeholder="Explain your issue (use words like 'urgent' to test the step!)..."></textarea>

    <button onclick="submitTicket()">Submit Ticket</button>
    <div id="status"></div>

    <script>
      function submitTicket() {
        const title = document.getElementById('title').value;
        const description = document.getElementById('description').value;
        const statusEl = document.getElementById('status');

        if (!title || !description) {
           statusEl.style.color = "red";
           statusEl.innerText = "Please fill out both fields.";
           return;
        }

        statusEl.style.color = "green";
        statusEl.innerText = "Submitting ticket...";

        google.script.run
          .withSuccessHandler(function(response) {
            statusEl.innerText = response;
          })
          .withFailureHandler(function(error) {
            statusEl.style.color = 'red';
            statusEl.innerText = 'Error: ' + error.message;
          })
          .submitTicketToStudio(title, description);
      }
    </script>
  </body>
</html>

4. バックエンド スクリプトをビルドする

では、すべてを配線してみましょう。コードの可読性を維持するため、ウェブアプリ、開始条件ロジック、アクション ステップ ロジックを 3 つの異なるスクリプト ファイルに分割します。

WebApp.gs

エディタでデフォルトの Code.gs ファイルの名前を WebApp.gs に変更し、ボイラープレート myFunction を削除して、以下のブロックをコピーして貼り付けます。このファイルは、ウェブアプリのホスティングを処理します。

/** --- WEB APP HOSTING --- **/

/**
 * Serves the HTML UI when users visit the web app URL.
 */
function doGet() {
  return HtmlService.createHtmlOutputFromFile('index')
      .setTitle('Support Quick Portal')
      .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}

/**
 * Called by the Web App form. It calls the Workspace Studio API.
 */
function submitTicketToStudio(title, description) {
  const props = PropertiesService.getUserProperties();
  const notifyUri = props.getProperty('notifyUri');
  const triggerId = props.getProperty('triggerId');

  if (!notifyUri || !triggerId) {
    throw new Error('Trigger URL missing! Make sure the flow is enabled in Workspace Studio.');
  }

  // Use ScriptApp.getOAuthToken() (requires the workspace.studio.trigger scope in manifest)
  const token = ScriptApp.getOAuthToken();
  const requestId = Utilities.getUuid();

  // Package our outputs mapped in appsscript.json
  const payload = {
    "name": "triggers/" + triggerId,
    "outputs": {
      "ticketTitle": { "stringValues": [title] },
      "ticketDescription": { "stringValues": [description] }
    },
    "requestId": requestId
  };

  const options = {
    "method": "POST",
    "contentType": "application/json",
    "headers": { "Authorization": "Bearer " + token },
    "payload": JSON.stringify(payload),
    "muteHttpExceptions": true
  };

  // Fire!
  const response = UrlFetchApp.fetch(notifyUri, options);
  if (response.getResponseCode() !== 200) {
    console.error(response.getContentText());
    throw new Error('Workspace Studio API Error: ' + response.getResponseCode());
  }
  return 'Ticket successfully routed to Workspace Studio!';
}

Starter.gs

次に、[ファイル] にカーソルを合わせ、ファイルを追加 [ファイルを追加] をクリックして [スクリプト] を選択し、Starter.gs という名前を付けます。以下のブロックをコピーして貼り付けます。このファイルは、開始条件の構成と Workspace Studio のライフサイクル管理を処理します。

/** --- STARTER LOGIC --- **/

/**
 * Workspace Studio calls this to display the config UI when the user adds the starter.
 * We just return a static message since there are no properties to configure.
 */
function onConfigTrigger() {
  const section = CardService.newCardSection()
      .setHeader("Configure Web App Starter")
      .addWidget(CardService.newTextParagraph().setText("Ready to go! Once you enable this flow, deploy your Apps Script project as a Web App to submit tickets."));

  return CardService.newCardBuilder().addSection(section).build();
}

/**
 * Workspace Studio posts data here to manage the lifecycle (enable/disable the trigger).
 * Critically, we must construct or save the `notifyUri` and `triggerId` to call it later.
 */
function onManageTrigger(event) {
  const triggerCreation = event.workflow.triggerCreation;
  const triggerDeletion = event.workflow.triggerDeletion;

  const props = PropertiesService.getUserProperties();

  if (triggerCreation) {
    // Save the unique URL and ID when the flow is enabled
    // Note: notifyUri might be omitted, so fallback to constructing it dynamically
    const notifyUri = triggerCreation.notifyUri || `https://workspacestudio.googleapis.com/v1/triggers/${triggerCreation.triggerId}:fire`;
    props.setProperty('notifyUri', notifyUri);
    props.setProperty('triggerId', triggerCreation.triggerId);
  } else if (triggerDeletion) {
    // Cleanup if the user deletes the flow
    props.deleteProperty('notifyUri');
    props.deleteProperty('triggerId');
  }
}

Action.gs

次に、[ファイル] にカーソルを合わせ、ファイルを追加 [ファイルを追加] をクリックして [スクリプト] を選択し、Action.gs という名前を付けます。以下の Urgency Step ロジックをコピーして貼り付けます。

/** --- ACTION STEP LOGIC --- **/

/**
 * Workspace Studio configuration card for the Step.
 * We prompt the user to bind a variable to our "ticketDescription" input.
 */
function onConfigUrgency() {
  const input = CardService.newTextInput()
      .setFieldName("ticketDescription")
      .setTitle("Source Description")
      .setHint('Select the description variable outputted by the starter')
      .setHostAppDataSource(
        CardService.newHostAppDataSource().setWorkflowDataSource(CardService.newWorkflowDataSource().setIncludeVariables(true))
      );

  const section = CardService.newCardSection()
      .setHeader("Urgency Detector Step")
      .addWidget(input);

  return CardService.newCardBuilder().addSection(section).build();
}

/**
 * The synchronous logic executed during the Step.
 */
function onExecuteUrgency(event) {
  // Read mapped inputs
  let description = event.workflow.actionInvocation.inputs["ticketDescription"].stringValues[0];
  if (!description) description = "";

  const urgentKeywords = ['urgent', 'crash', 'broken', 'down', 'fire'];
  let urgencyLevel = 'Normal';

  if (urgentKeywords.some(keyword => description.toLowerCase().includes(keyword))) {
    urgencyLevel = 'High';
  }

  // Package outputs correctly
  const variableDataMap = {
    "urgencyLevel": AddOnsResponseService.newVariableData().addStringValue(urgencyLevel)
  };

  return outputVariables(variableDataMap);
}

/**
 * Helper strictly enforcing the expected Step object return type.
 */
function outputVariables(variableDataMap) {
  const workflowAction = AddOnsResponseService.newReturnOutputVariablesAction()
      .setVariableDataMap(variableDataMap);
  const hostAppAction = AddOnsResponseService.newHostAppAction()
      .setWorkflowAction(workflowAction);
  return AddOnsResponseService.newRenderActionBuilder()
      .setHostAppAction(hostAppAction)
      .build();
}

5. デプロイとテスト

コードを記述したので、プロジェクトをデプロイして構成しましょう。

ステップ 1: スクリプトを再承認する

新しい標準の Google Cloud プロジェクトをリンクしたため、新しいプロジェクトに対してスクリプトのスコープ(UrlFetchApp で使用される script.external_request など)を明示的に承認する必要があります。

  1. Apps Script エディタで、[ファイル] の [WebApp.gs] を選択し、上部のツールバーのプルダウンから doGet 関数を選択します。
  2. [実行] をクリックします。
  3. [Authorization required](承認が必要です)というプロンプトが表示されます。[権限を確認] をクリックします。
  4. アカウントを選択して、スクリプトを承認します。「未確認のアプリ」という警告が表示された場合は、[詳細] -> [サポート クイック ポータルに移動] をクリックしてバイパスします。
  5. [実行ログ] に [実行完了] と表示されるまで待ちます。これで、スクリプトが完全に承認されたことが確認できます。

ステップ 2: 拡張機能とウェブアプリをテストする

appsscript.json はアドオンとウェブアプリの両方を定義するため、両方を同時に構成できます。

  1. Apps Script の右上にある [デプロイ] > [デプロイをテスト] をクリックします。
  2. [種類の選択] の横にある デプロイタイプを有効にする [デプロイタイプを有効にする] をクリックし、[Google Workspace アドオン] がオンになっていることを確認して、[インストール] をクリックします。
  3. デプロイタイプを有効にする [デプロイタイプを有効にする] をもう一度クリックし、[ウェブアプリ] がオンになっていることを確認して、URL をコピーします(末尾は /dev になります)。これは、作成した Support Quick Portal ウェブアプリの URL です。
  4. [完了] をクリックします。Workspace Studio がアドオンを登録する前に、ブラウザの更新が必要になることがあります。

ステップ 3: Studio Flow を構成する

  1. studio.workspace.google.com で Google Workspace Studio を開きます。
  2. 左側の ツールを追加 [ツールを追加] をクリックし、[フロー] を選択します。
  3. 開始条件を構成するには、[開始条件の選択] をクリックし、インストールした Support Extension > [New Support Ticket] を選択します。「権限が必要です」というメッセージが表示されたら、[権限を付与] をクリックして、アドオンがデータにアクセスできるようにします。
  4. 次に、[アクション] で [ステップを選択] をクリックし、[サポート拡張機能] までスクロールして、[緊急度を検出] ステップを選択します。
  5. [ソースの説明] で、追加 [変数] をクリックし、[ステップ 1: 新しいサポート チケット] > [問題の完全な説明](ticketDescription)を選択します。
  6. [ステップを選択] をもう一度クリックし、[Gmail > メールを送信する] や [Chat > Chat で通知する] などの組み込みステップを選択します。
    • Gmail を選択した場合:
      • 宛先: 自分のメールアドレスを入力します。
      • 件名: チケット ticketTitle の優先度は urgencyLevel です
      • メッセージ: ticketDescription
    • チャットを選択した場合:
      • メッセージ: チケット ticketTitle の優先度は urgencyLevel です。ticketDescription
  7. ワークフローをオンにします。[フローの名前を変更] というメッセージが表示されたら、Support Ticket Router などの名前を入力して保存します。これにより、onManageTrigger ライフサイクルがトリガーされ、notifyUri が保存されます。

ステップ 4: シミュレーションを実行する

  1. このページのステップ 2 でコピーしたウェブアプリの URL を新しいブラウザタブで開きます(末尾は /dev です)。
  2. フォームに必要事項を記入します。説明にトリガー キーワード(「緊急」や「クラッシュ」など)を含めるようにしてください。
  3. [送信] をクリックします。「チケットは正常に転送されました」というメッセージが表示されるまで待ちます。
  4. Workspace Studio のアクティビティ ログ、Gmail の受信トレイ、Google Chat(選択した操作によって異なります)を確認します。カスタムの Apps Script 要素を使用してネイティブに統合された、緊急度が「高」に設定されたメッセージまたはメールが表示されます。

6. クリーンアップ

ワークスペースと Google Cloud アカウントが煩雑にならないように、この Codelab で作成したリソースをクリーンアップできます。

  1. Workspace Studio フローを削除します。
    • studio.workspace.google.com を開きます。
    • Support Ticket Router フローを見つけます。
    • その横にあるその他メニューをクリックし、[削除] を選択します。
  2. Apps Script プロジェクトを削除します。
    • script.google.com にアクセスします。
    • Support Quick Portal プロジェクトを見つけます。
    • その他メニューをクリックし、[削除] を選択します。
  3. Google Cloud プロジェクトをシャットダウンします。
    • Google Cloud コンソールに移動します。
    • 上部のプルダウンで新しいプロジェクトが選択されていることを確認します。
    • [IAM と管理] > [設定] に移動し、[シャットダウン] をクリックします。

7. まとめ

外部のサードパーティ サブスクリプションに依存することなく、Google Workspace Studio カスタム拡張機能の完全なエコシステムを構築できました。

本日扱った内容:

  • workflowTrigger と onManageFunction を併用して、Google のライフサイクル イベントを正しくキャプチャして処理します。
  • workspacestudio.googleapis.com API エンドポイントに対して ScriptApp.getOAuthToken() を使用してバックエンド リクエストを認証します。
  • workflowAction を使用して入力変数を同期的に処理し、ダウンストリーム出力を生成します。

これらの原則をスケールアウトして、Web アプリ(または着信 Webhook)のトリガー元を変更することで、Jira、Salesforce、Zendesk などの実際の SaaS プラットフォームにフックできるようになりました。