Membangun langkah-langkah Google Workspace Studio kustom di Node.js di Cloud Run

1. Pengantar

Google Workspace Studio memungkinkan Anda mengotomatiskan proses bisnis di seluruh aplikasi Google Workspace seperti Gmail, Google Chat, Google Drive, dan Google Spreadsheet. Jika alur kerja Anda perlu dimulai dari aplikasi web eksternal atau menjalankan logika kustom di lingkungan cloud Anda sendiri, Anda dapat memperluas Google Workspace Studio dengan Add-on Google Workspace HTTP di Node.js di Cloud Run:

  1. Langkah awal kustom (workflowTrigger): Memulai alur Google Workspace Studio saat peristiwa eksternal terjadi—seperti pelanggan yang mengirimkan tiket di portal web internal—dengan memanggil triggers.fire REST API Google Workspace Studio.
  2. Langkah tindakan kustom (workflowAction): Menerima variabel dari langkah-langkah sebelumnya dalam alur, menjalankan logika kustom atau inferensi AI di layanan Node.js Anda, dan menampilkan variabel output kembali ke Google Workspace Studio.

Dalam codelab ini, Anda akan membangun dan men-deploy ekstensi HTTP Node.js lengkap untuk Google Workspace Studio di Cloud Run, menggunakan Cloud Firestore untuk mempertahankan langganan pemicu alur aktif dan tiket yang dikirimkan di seluruh revisi penampung, serta mengklasifikasikan urgensi tiket di langkah tindakan kustom menggunakan Gemini 3.8 Flash (gemini-3.8-flash) di Gemini Enterprise Agent Platform dengan penggantian pencocokan kata kunci yang tangguh.

Yang akan Anda build

  • Aplikasi web Support Quick Portal dan Add-on Google Workspace HTTP yang dibuat dengan Node.js, Express, dan Firestore di Cloud Run.
  • Langkah awal kustom Tiket Dukungan Baru (supportTicketTrigger) yang mempertahankan langganan alur aktif dalam koleksi Firestore studio_triggers, mencatat tiket yang dikirimkan dalam koleksi Firestore support_tickets, dan diaktifkan setiap kali tiket dikirimkan di portal web—meneruskan ticketTitle dan ticketDescription ke alur Google Workspace Studio.
  • Langkah tindakan kustom Deteksi Keterdesakan (urgencyDetectorStep) yang mengklasifikasikan urgensi tiket menggunakan Gemini 3.8 Flash (gemini-3.8-flash) di Gemini Enterprise Agent Platform (aiplatform.googleapis.com), dengan penggantian otomatis ke pencocokan kata kunci (urgent, blocker, critical).

Yang akan Anda pelajari

  • Cara mengonfigurasi manifes deployment Add-on Google Workspace HTTP (deployment.json) dengan elemen workflowTrigger dan workflowAction.
  • Cara mempertahankan langganan pemicu Google Workspace Studio (triggerId, notifyUri, dan userOAuthToken) dalam koleksi Firestore studio_triggers dan memicu alur menggunakan REST API triggers.fire.
  • Cara membuat langkah kustom workflowAction di Node.js menggunakan output terstruktur Gemini 3.8 Flash (gemini-3.8-flash) di Gemini Enterprise Agent Platform dengan penggantian kata kunci.

Prasyarat

  • Akun Google Workspace dengan akses ke Google Workspace Studio.
  • Project Google Cloud dengan penagihan diaktifkan, dibuat dalam organisasi Google Workspace yang sama dengan akun pengguna Anda sehingga Anda dapat menginstal deployment add-on yang tidak dipublikasikan untuk pengujian.

2. Menyiapkan project Google Cloud dan Firestore

Pada langkah ini, Anda akan mengonfigurasi layar izin OAuth project, mengaktifkan Google Cloud dan Google Workspace API yang diperlukan di Cloud Shell, menyediakan database (default) Firestore, dan menginisialisasi akun layanan otorisasi add-on.

  1. Buka Konsol Google Cloud, pilih project Google Cloud Anda, lalu klik ikon Activate Cloud Shell di panel navigasi atas.
  2. Konfigurasi layar izin OAuth agar Google Workspace dapat menyediakan klien OAuth untuk add-on Anda:
    1. Buka halaman Branding Google Auth Platform.
    2. Jika diminta, klik Mulai. (Jika layar izin Anda sudah dikonfigurasi, lanjutkan ke langkah 3.)
    3. Di bagian App Information, masukkan Support Extension untuk App name, pilih User support email Anda, lalu klik Next.
    4. Di bagian Audience, pilih Internal, lalu klik Next.
    5. Di bagian Contact Information, masukkan alamat email Anda, lalu klik Next.
    6. Di bagian Selesai, setujui Google API Services: Kebijakan Data Pengguna, klik Continue, lalu klik Create.
  3. Di Cloud Shell, tetapkan project aktif Anda (ganti YOUR_PROJECT_ID dengan project ID Google Cloud Anda) dan aktifkan API yang diperlukan:
    gcloud config set project YOUR_PROJECT_ID
    gcloud services enable \
      aiplatform.googleapis.com \
      firestore.googleapis.com \
      run.googleapis.com \
      cloudbuild.googleapis.com \
      artifactregistry.googleapis.com \
      appsmarket-component.googleapis.com \
      gsuiteaddons.googleapis.com \
      workspacestudio.googleapis.com
    
  4. Sediakan database Firestore (default) di us-central1 (jika belum ada) dan inisialisasi akun layanan Add-on Google Workspace Anda:
    gcloud firestore databases describe --database="(default)" >/dev/null 2>&1 || \
      gcloud firestore databases create \
        --database="(default)" \
        --location=us-central1 \
        --type=firestore-native \
        --quiet
    gcloud workspace-add-ons get-authorization
    

3. Bangun aplikasi Node.js dengan Firestore dan Gemini 3.8 Flash

Pada langkah ini, Anda akan membangun aplikasi Node.js di ~/studio-support-addon. Untuk menjaga kode tetap modular dan mudah dibaca, Anda mengatur project ke dalam lima file terfokus:

  • firestore.js: Menginisialisasi klien Cloud Firestore (koleksi studio_triggers dan support_tickets) serta memperbarui token OAuth yang di-cache.
  • starter.js: Menerapkan webhook langkah awal kustom Tiket Dukungan Baru (/onConfigTrigger dan /onManageTrigger).
  • action.js: Menerapkan webhook langkah tindakan kustom Deteksi Urgensi (/onConfigUrgency dan /onExecuteUrgency) menggunakan Gemini 3.8 Flash (gemini-3.8-flash) dengan penggantian pencocokan kata kunci.
  • portal.js: Menayangkan UI web Support Quick Portal (GET /) dan memicu pemicu Google Workspace Studio yang aktif (POST /api/submit-ticket).
  • index.js: Menghubungkan semua rute Express dan memulai server HTTP.

Lakukan inisialisasi project dan package.json

  1. Buat direktori project dan buka direktori tersebut:
    mkdir -p ~/studio-support-addon && cd ~/studio-support-addon
    
  2. Buat package.json dengan express, @google-cloud/firestore, dan @google/genai:
    cat << 'EOF' > package.json
    {
      "name": "studio-support-addon",
      "version": "1.0.0",
      "type": "module",
      "main": "index.js",
      "scripts": {
        "start": "node index.js"
      },
      "dependencies": {
        "@google-cloud/firestore": "^7.11.0",
        "@google/genai": "^1.0.0",
        "express": "^4.21.2"
      }
    }
    EOF
    

firestore.js — Status Firestore bersama dan refresh token OAuth

Buat firestore.js untuk mengelola dua koleksi Firestore dan menjaga agar token OAuth pengguna yang di-cache tetap baru:

  • studio_triggers: Menyimpan langganan alur Google Workspace Studio yang aktif (triggerId, notifyUri, dan userOAuthToken) sehingga langganan tersebut tetap ada saat container Cloud Run dimulai ulang dan peristiwa penskalaan hingga nol.
  • support_tickets: Menyimpan catatan setiap tiket dukungan yang dikirimkan melalui portal.
  • refreshTokenInFirestore(req): Setiap kali Google Workspace Studio memanggil endpoint webhook Anda, endpoint tersebut akan menyertakan authorizationEventObject.userOAuthToken yang berumur pendek. Helper ini memperbarui dokumen pemicu aktif dengan token terbaru sehingga panggilan triggers.fire di latar belakang tetap diizinkan.
cat << 'EOF' > firestore.js
import { Firestore } from '@google-cloud/firestore';

export const db = new Firestore({
  projectId: process.env.GOOGLE_CLOUD_PROJECT || undefined,
  databaseId: '(default)',
  ignoreUndefinedProperties: true,
});

export const triggersCollection = db.collection(
  process.env.FIRESTORE_TRIGGERS_COLLECTION || 'studio_triggers'
);

export const ticketsCollection = db.collection(
  process.env.FIRESTORE_TICKETS_COLLECTION || 'support_tickets'
);

// Keep cached userOAuthToken fresh across active trigger documents
export async function refreshTokenInFirestore(req) {
  const freshToken = req.body?.authorizationEventObject?.userOAuthToken;
  if (!freshToken) return;
  try {
    const snapshot = await triggersCollection.get();
    if (snapshot.empty) return;
    const batch = db.batch();
    snapshot.docs.forEach((doc) => {
      batch.set(
        doc.ref,
        { userOAuthToken: freshToken, updatedAt: new Date().toISOString() },
        { merge: true }
      );
    });
    await batch.commit();
  } catch (err) {
    console.error('Error refreshing OAuth token in Firestore:', err.message);
  }
}
EOF

starter.js — Langkah Awal Kustom (workflowTrigger)

Buat starter.js untuk menerapkan dua endpoint webhook yang diperlukan oleh langkah awal kustom Google Workspace Studio:

  • onConfigTrigger (POST /onConfigTrigger): Dipanggil saat pengguna memilih Tiket Dukungan Baru sebagai titik awal alur mereka di Google Workspace Studio. Menampilkan tindakan navigasi pushCard yang mendeskripsikan starter.
  • onManageTrigger (POST /onManageTrigger): Dipanggil saat pengguna mengaktifkan alur Aktif (req.body.workflow.triggerCreation) atau menonaktifkannya / menghapusnya (req.body.workflow.triggerDeletion). Menyimpan atau menghapus dokumen langganan di koleksi Firestore studio_triggers.
cat << 'EOF' > starter.js
import { triggersCollection, refreshTokenInFirestore } from './firestore.js';

// Custom Starter Configuration Card
export async function onConfigTrigger(req, res) {
  await refreshTokenInFirestore(req);
  res.status(200).json({
    action: {
      navigations: [
        {
          pushCard: {
            sections: [
              {
                header: 'Support Ticket Listener',
                widgets: [
                  {
                    textParagraph: {
                      text: 'This starter listens for new tickets submitted through the Support Quick Portal and stores subscriptions in Firestore.',
                    },
                  },
                ],
              },
            ],
          },
        },
      ],
    },
  });
}

// Custom Starter Subscription Lifecycle (Firestore studio_triggers collection)
export async function onManageTrigger(req, res) {
  try {
    const triggerCreation = req.body?.workflow?.triggerCreation;
    const triggerDeletion = req.body?.workflow?.triggerDeletion;
    const userOAuthToken =
      req.body?.authorizationEventObject?.userOAuthToken || null;

    if (triggerCreation?.triggerId) {
      const triggerId = String(triggerCreation.triggerId);
      const docId = encodeURIComponent(triggerId);
      await triggersCollection.doc(docId).set(
        {
          triggerId,
          notifyUri: triggerCreation.notifyUri || null,
          ...(userOAuthToken ? { userOAuthToken } : {}),
          updatedAt: new Date().toISOString(),
        },
        { merge: true }
      );
    }

    if (triggerDeletion?.triggerId) {
      const triggerId = String(triggerDeletion.triggerId);
      const docId = encodeURIComponent(triggerId);
      await triggersCollection.doc(docId).delete();
    }

    if (!triggerCreation && !triggerDeletion && userOAuthToken) {
      await refreshTokenInFirestore(req);
    }
  } catch (err) {
    console.error('Error in /onManageTrigger:', err.message);
  }
  res.status(200).json({});
}
EOF

action.js — Langkah Tindakan Kustom (workflowAction) dengan Gemini 3.8 Flash

Buat action.js untuk menerapkan dua endpoint webhook yang diperlukan oleh langkah tindakan kustom Google Workspace Studio:

  • onConfigUrgency (POST /onConfigUrgency): Menampilkan kartu konfigurasi dengan widget textInput yang menetapkan hostAppDataSource.workflowDataSource.includeVariables: true, sehingga pengguna dapat memilih variabel ticketDescription yang dihasilkan oleh langkah awal.
  • onExecuteUrgency (POST /onExecuteUrgency): Membaca input ticketDescription dari req.body.workflow.actionInvocation.inputs, mengklasifikasikan dampak bisnis dan urgensinya menggunakan Gemini 3.8 Flash (gemini-3.8-flash) di Gemini Enterprise Agent Platform dengan output terstruktur (enum responseSchema ["High", "Normal"]), melakukan penggantian ke detectKeywordUrgency() (urgent, blocker, critical) jika panggilan model gagal, dan menampilkan variabel output urgencyLevel menggunakan hostAppAction.workflowAction.returnOutputVariablesAction.
cat << 'EOF' > action.js
import { GoogleGenAI, Type } from '@google/genai';
import { refreshTokenInFirestore } from './firestore.js';

// Initialize Google GenAI SDK for the Gemini Enterprise Agent Platform
const ai = new GoogleGenAI({
  vertexai: true,
  project: process.env.GOOGLE_CLOUD_PROJECT,
  location: 'global',
});

// Fallback keyword classifier if the Gemini API call is unavailable
export function detectKeywordUrgency(description) {
  const lower = (description || '').toLowerCase();
  return lower.includes('urgent') ||
    lower.includes('blocker') ||
    lower.includes('critical')
    ? 'High'
    : 'Normal';
}

// Custom Action Step Configuration Card
export async function onConfigUrgency(req, res) {
  await refreshTokenInFirestore(req);
  res.status(200).json({
    action: {
      navigations: [
        {
          pushCard: {
            sections: [
              {
                header: 'Urgency Detector Step',
                widgets: [
                  {
                    textInput: {
                      name: 'ticketDescription',
                      label: 'Source Description',
                      hintText:
                        'Select the description variable outputted by the starter',
                      hostAppDataSource: {
                        workflowDataSource: {
                          includeVariables: true,
                        },
                      },
                    },
                  },
                ],
              },
            ],
          },
        },
      ],
    },
  });
}

// Custom Action Step Execution: Gemini 3.8 Flash with keyword fallback
export async function onExecuteUrgency(req, res) {
  await refreshTokenInFirestore(req);
  const inputs = req.body?.workflow?.actionInvocation?.inputs || {};
  const description = inputs.ticketDescription?.stringValues?.[0] || '';

  let urgencyLevel = detectKeywordUrgency(description);
  try {
    const response = await ai.models.generateContent({
      model: 'gemini-3.8-flash',
      contents: `Evaluate the business impact and time sensitivity of this support ticket description and classify its urgency:\n\n${description}`,
      config: {
        responseMimeType: 'text/x.enum',
        responseSchema: {
          type: Type.STRING,
          enum: ['High', 'Normal'],
        },
      },
    });
    const candidate = (response.text || '').trim();
    if (candidate === 'High' || candidate === 'Normal') {
      urgencyLevel = candidate;
    }
  } catch (err) {
    console.error('Gemini fallback to keyword matching:', err.message);
  }

  res.status(200).json({
    hostAppAction: {
      workflowAction: {
        returnOutputVariablesAction: {
          variables: {
            urgencyLevel: {
              stringValues: [urgencyLevel],
            },
          },
        },
      },
    },
  });
}
EOF

portal.js — Mendukung UI Quick Portal dan pengirim triggers.fire

Buat portal.js untuk menayangkan portal web dan mengirimkan pengajuan tiket ke Google Workspace Studio:

  • renderPortal (GET /): Mengirim kueri ke koleksi Firestore studio_triggers untuk menampilkan jumlah pemicu Google Workspace Studio aktif yang terdaftar, dan menyajikan formulir pengiriman tiket HTML.
  • submitTicket (POST /api/submit-ticket): Melakukan iterasi melalui semua dokumen pemicu aktif di studio_triggers, memanggil endpoint triggers.fire Google Workspace Studio (https://workspacestudio.googleapis.com/v1/triggers/{triggerId}:fire) dengan variabel output ticketTitle dan ticketDescription serta requestId unik, lalu menyimpan tiket ke koleksi support_tickets Firestore.
cat << 'EOF' > portal.js
import { triggersCollection, ticketsCollection } from './firestore.js';

// Support Quick Portal UI
export async function renderPortal(req, res) {
  let activeCount = 0;
  try {
    const snapshot = await triggersCollection.get();
    activeCount = snapshot.size;
  } catch (err) {
    console.error('Error reading studio_triggers collection:', err.message);
  }

  res.status(200).send(`<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Support Quick Portal</title>
    <style>
      body { font-family: Arial, sans-serif; padding: 24px; max-width: 540px; margin: auto; color: #202124; }
      .badge { background: #e8f0fe; color: #1967d2; padding: 8px 12px; border-radius: 6px; font-size: 14px; margin-bottom: 16px; }
      label { font-weight: bold; display: block; margin-top: 16px; }
      input, textarea { width: 100%; margin-top: 6px; padding: 10px; box-sizing: border-box; border: 1px solid #dadce0; border-radius: 4px; }
      button { background-color: #1a73e8; color: white; border: none; padding: 10px 18px; margin-top: 16px; font-weight: bold; cursor: pointer; border-radius: 4px; }
      button:hover { background-color: #1557b0; }
      #status { margin-top: 16px; font-weight: bold; }
    </style>
  </head>
  <body>
    <h2>Support Quick Portal</h2>
    <div class="badge">
      Active Google Workspace Studio triggers in Firestore (<code>studio_triggers</code>):
      <strong id="trigger-count">${activeCount}</strong>
    </div>
    <p>Submit a support ticket below to fire your Google Workspace Studio flow.</p>

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

    <label for="description">Issue Description</label>
    <textarea id="description" rows="4" placeholder="Describe the issue..."></textarea>

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

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

        if (!title || !description) {
          statusEl.style.color = '#d93025';
          statusEl.textContent = 'Please fill out both fields.';
          return;
        }

        statusEl.style.color = '#188038';
        statusEl.textContent = 'Submitting ticket...';

        try {
          const res = await fetch('/api/submit-ticket', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ title, description }),
          });
          const data = await res.json();
          if (data.ok) {
            statusEl.textContent =
              'Ticket saved to Firestore! Fired ' + data.triggersFired + ' Google Workspace Studio trigger(s).';
          } else {
            statusEl.style.color = '#d93025';
            statusEl.textContent = 'Error: ' + (data.error || 'Unknown error');
          }
        } catch (err) {
          statusEl.style.color = '#d93025';
          statusEl.textContent = 'Request failed: ' + err.message;
        }
      }
    </script>
  </body>
</html>`);
}

// Portal Form Submission -> Fire Active Google Workspace Studio Triggers
export async function submitTicket(req, res) {
  const title = req.body?.title || 'Untitled Ticket';
  const description = req.body?.description || '';
  const results = [];

  try {
    const snapshot = await triggersCollection.get();
    for (const doc of snapshot.docs) {
      const trigger = doc.data();
      if (!trigger.triggerId || !trigger.userOAuthToken) continue;

      const response = await fetch(
        `https://workspacestudio.googleapis.com/v1/triggers/${encodeURIComponent(trigger.triggerId)}:fire`,
        {
          method: 'POST',
          headers: {
            Authorization: `Bearer ${trigger.userOAuthToken}`,
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({
            name: 'triggers/' + trigger.triggerId,
            outputs: {
              ticketTitle: { stringValues: [title] },
              ticketDescription: { stringValues: [description] },
            },
            requestId:
              'req_' + Date.now() + '_' + Math.floor(Math.random() * 1000),
          }),
        }
      );
      results.push({ triggerId: trigger.triggerId, status: response.status });
    }

    await ticketsCollection.add({
      title,
      description,
      triggersFired: results.length,
      createdAt: new Date().toISOString(),
    });

    res.status(200).json({ ok: true, triggersFired: results.length, results });
  } catch (err) {
    console.error('Error in /api/submit-ticket:', err.message);
    res.status(500).json({ ok: false, error: err.message });
  }
}
EOF

index.js — Titik entri server Express

Buat index.js untuk menghubungkan rute portal dan endpoint webhook Google Workspace Studio menjadi server HTTP Express yang memproses PORT (default 8080 di Cloud Run):

cat << 'EOF' > index.js
import express from 'express';
import { onConfigTrigger, onManageTrigger } from './starter.js';
import { onConfigUrgency, onExecuteUrgency } from './action.js';
import { renderPortal, submitTicket } from './portal.js';

const app = express();
app.use(express.json());

// Support Quick Portal routes
app.get('/', renderPortal);
app.post('/api/submit-ticket', submitTicket);

// Google Workspace Studio Custom Starter Step webhooks
app.post('/onConfigTrigger', onConfigTrigger);
app.post('/onManageTrigger', onManageTrigger);

// Google Workspace Studio Custom Action Step webhooks
app.post('/onConfigUrgency', onConfigUrgency);
app.post('/onExecuteUrgency', onExecuteUrgency);

const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
  console.log(`Support Extension listening on port ${PORT}`);
});
EOF

4. Men-deploy ke Cloud Run dan menginstal add-on

Pada langkah ini, Anda akan men-deploy layanan Node.js ke Cloud Run, memberikan izin IAM untuk pemanggil Add-on Google Workspace dan akses runtime Firestore dan Agent Platform, membuat manifes deployment.json, serta menginstal deployment add-on.

  1. Deploy layanan support-extension ke Cloud Run dengan variabel lingkungan project dan koleksi Firestore Anda:
    gcloud run deploy support-extension \
      --source . \
      --region us-central1 \
      --allow-unauthenticated \
      --set-env-vars="GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project),FIRESTORE_TRIGGERS_COLLECTION=studio_triggers,FIRESTORE_TICKETS_COLLECTION=support_tickets" \
      --quiet
    
  2. Beri roles/run.invoker pada layanan Cloud Run ke akun layanan Add-on Google Workspace agar Google Workspace Studio dapat memanggil endpoint webhook Anda:
    ADDON_SA=$(gcloud workspace-add-ons get-authorization --format="value(serviceAccountEmail)")
    gcloud run services add-iam-policy-binding support-extension \
      --region=us-central1 \
      --member="serviceAccount:${ADDON_SA}" \
      --role="roles/run.invoker" \
      --quiet
    
  3. Berikan roles/datastore.user (untuk membaca dan menulis koleksi Firestore studio_triggers dan support_tickets) serta roles/aiplatform.user (untuk memanggil Gemini di Gemini Enterprise Agent Platform) ke akun layanan runtime Cloud Run:
    PROJECT_ID=$(gcloud config get-value project)
    PROJECT_NUMBER=$(gcloud projects describe "${PROJECT_ID}" --format="value(projectNumber)")
    RUNTIME_SA=$(gcloud run services describe support-extension --region=us-central1 --format="value(spec.template.spec.serviceAccountName)")
    RUNTIME_SA=${RUNTIME_SA:-"${PROJECT_NUMBER}-compute@developer.gserviceaccount.com"}
    
    gcloud projects add-iam-policy-binding "${PROJECT_ID}" \
      --member="serviceAccount:${RUNTIME_SA}" \
      --role="roles/datastore.user" \
      --condition=None \
      --quiet
    
    gcloud projects add-iam-policy-binding "${PROJECT_ID}" \
      --member="serviceAccount:${RUNTIME_SA}" \
      --role="roles/aiplatform.user" \
      --condition=None \
      --quiet
    
  4. Ambil URL Cloud Run yang di-deploy dan buat manifes deployment.json yang menentukan elemen supportTicketTrigger (workflowTrigger) dan urgencyDetectorStep (workflowAction):
    SERVICE_URL=$(gcloud run services describe support-extension --region=us-central1 --format="value(status.url)")
    
    cat << EOF > deployment.json
    {
      "oauthScopes": [
        "https://www.googleapis.com/auth/workspace.studio.trigger",
        "https://www.googleapis.com/auth/userinfo.email"
      ],
      "addOns": {
        "common": {
          "name": "Support Extension",
          "logoUrl": "https://www.gstatic.com/images/branding/productlogos/buganizer/v1/192px.svg"
        },
        "studio": {
          "flows": {
            "workflowElements": [
              {
                "id": "supportTicketTrigger",
                "state": "ACTIVE",
                "name": "New Support Ticket",
                "description": "Fires when someone submits a ticket in the Support Quick Portal.",
                "workflowTrigger": {
                  "inputs": [],
                  "outputs": [
                    {
                      "id": "ticketTitle",
                      "description": "Ticket Title",
                      "cardinality": "SINGLE",
                      "dataType": { "basicType": "STRING" }
                    },
                    {
                      "id": "ticketDescription",
                      "description": "Ticket Description",
                      "cardinality": "SINGLE",
                      "dataType": { "basicType": "STRING" }
                    }
                  ],
                  "onConfigFunction": "${SERVICE_URL}/onConfigTrigger",
                  "onManageFunction": "${SERVICE_URL}/onManageTrigger"
                }
              },
              {
                "id": "urgencyDetectorStep",
                "state": "ACTIVE",
                "name": "Detect Urgency",
                "description": "Evaluates the ticket description to detect High or Normal urgency.",
                "workflowAction": {
                  "inputs": [
                    {
                      "id": "ticketDescription",
                      "description": "Description to scan",
                      "cardinality": "SINGLE",
                      "dataType": { "basicType": "STRING" }
                    }
                  ],
                  "outputs": [
                    {
                      "id": "urgencyLevel",
                      "description": "Detected Urgency (High / Normal)",
                      "cardinality": "SINGLE",
                      "dataType": { "basicType": "STRING" }
                    }
                  ],
                  "onConfigFunction": "${SERVICE_URL}/onConfigUrgency",
                  "onExecuteFunction": "${SERVICE_URL}/onExecuteUrgency"
                }
              }
            ]
          }
        },
        "httpOptions": {
          "authorizationHeader": "SYSTEM_ID_TOKEN"
        }
      }
    }
    EOF
    
  5. Buat (atau ganti) dan instal deployment Add-on Google Workspace untuk akun Anda, lalu cetak URL Portal Cepat Dukungan Anda:
    gcloud workspace-add-ons deployments create support-extension --deployment-file=deployment.json || \
      gcloud workspace-add-ons deployments replace support-extension --deployment-file=deployment.json
    gcloud workspace-add-ons deployments install support-extension
    echo "Support Quick Portal URL: ${SERVICE_URL}"
    

5. Menguji langkah kustom Anda di Google Workspace Studio

  1. Buka Google Workspace Studio (muat ulang tab browser jika sudah terbuka) lalu klik Alur Baru.
  2. Klik Pilih starter > Support Extension > Tiket Dukungan Baru.
  3. Klik Tambahkan langkah (+) > Ekstensi Dukungan > Deteksi Urgensi. Di kolom Deskripsi Sumber, klik + Variabel, lalu pilih Deskripsi Tiket.
  4. Klik Tambahkan langkah (+), lalu tambahkan langkah Chat > Beri tahu saya di Chat yang menyertakan variabel Judul Tiket, Tingkat Mendesak, dan Deskripsi Tiket dalam pesan.
  5. Aktifkan alur On di pojok kanan atas. Google Workspace Studio akan segera memanggil endpoint /onManageTrigger Anda di Cloud Run, yang menulis langganan pemicu aktif ke koleksi studio_triggers di Firestore.
  6. Buka URL Support Quick Portal (https://support-extension-...run.app) di tab baru, pastikan portal menampilkan 1 pemicu aktif di Firestore, dan kirim dua tiket untuk menguji klasifikasi Gemini 3.8 Flash (gemini-3.8-flash):
    • Tiket berdampak tinggi (Judul Tiket: London office lockout, Deskripsi Masalah: All 500 employees in the London office are locked out of their workstations 20 minutes before the board presentation.): Meskipun tanpa kata kunci yang dikodekan secara permanen seperti urgent atau blocker, gemini-3.8-flash mengklasifikasikan tiket ini sebagai mendesak High.
    • Tiket rutin (Judul Tiket: Conference room adapters, Deskripsi Masalah: Could we order two extra HDMI adapters for the third-floor conference room next month?): gemini-3.8-flash mengklasifikasikan tiket ini sebagai urgensi Normal.
  7. Verifikasi di Google Chat dan di tab Aktivitas Google Workspace Studio bahwa alur Anda berjalan untuk kedua tiket dengan Urgency Level: High dan Urgency Level: Normal.

6. Pembersihan

Agar tidak menimbulkan biaya berkelanjutan pada akun Google Cloud Anda, hapus resource yang di-deploy di Cloud Shell:

gcloud workspace-add-ons deployments uninstall support-extension
gcloud workspace-add-ons deployments delete support-extension --quiet
gcloud run services delete support-extension --region=us-central1 --quiet
gcloud firestore databases delete --database="(default)" --quiet

7. Selamat!

Anda telah membuat, men-deploy, dan menguji ekstensi HTTP Node.js untuk Google Workspace Studio menggunakan Firestore, Cloud Run, dan Gemini 3.8 Flash di Gemini Enterprise Agent Platform.

Yang telah kita bahas

  • Mengonfigurasi manifes Add-on HTTP Google Workspace Studio (deployment.json) dengan elemen workflowTrigger dan workflowAction.
  • Mempertahankan langganan Google Workspace Studio workflowTrigger di koleksi studio_triggers Firestore dan mengirimkan log pengiriman portal di support_tickets sehingga pemicu tetap ada saat penskalaan dan deployment ulang Cloud Run.
  • Membangun langkah workflowAction yang mengklasifikasikan tiket dukungan dengan output terstruktur Gemini 3.8 Flash (gemini-3.8-flash) di Gemini Enterprise Agent Platform, dengan penggantian pencocokan kata kunci yang tangguh.

Langkah berikutnya