Cloud Run'da Node.js ile özel Google Workspace Studio adımları oluşturma

1. Giriş

Google Workspace Studio, Gmail, Google Chat, Google Drive ve Google E-Tablolar gibi Google Workspace uygulamalarında iş süreçlerini otomatikleştirmenize olanak tanır. İş akışlarınızın harici bir web uygulamasından başlatılması veya kendi bulut ortamınızda özel mantık yürütülmesi gerektiğinde, Cloud Run'da Node.js'de HTTP Google Workspace eklentisi ile Google Workspace Studio'yu genişletebilirsiniz:

  1. Özel başlangıç adımı (workflowTrigger): Google Workspace Studio triggers.fire REST API'yi çağırarak, harici bir etkinlik gerçekleştiğinde (ör. bir müşterinin dahili bir web portalında destek kaydı göndermesi) Google Workspace Studio akışını başlatır.
  2. Özel işlem adımı (workflowAction): Akışın önceki adımlarından değişkenler alır, Node.js hizmetinizde özel mantık veya yapay zeka çıkarımı çalıştırır ve çıkış değişkenlerini Google Workspace Studio'ya geri döndürür.

Bu codelab'de, Cloud Run'da Google Workspace Studio için eksiksiz bir Node.js HTTP uzantısı oluşturup dağıtacak, etkin akış tetikleyici aboneliklerini ve gönderilen biletleri kapsayıcı revizyonları arasında kalıcı hale getirmek için Cloud Firestore'u kullanacak ve Gemini Enterprise Agent Platform'da Gemini 3.8 Flash (gemini-3.8-flash) ile özel işlem adımınızda bilet aciliyetini sınıflandıracaksınız.

Ne oluşturacaksınız?

  • Cloud Run üzerinde Node.js, Express ve Firestore ile oluşturulmuş bir Support Quick Portal web uygulaması ve HTTP Google Workspace eklentisi.
  • studio_triggers Firestore koleksiyonunda etkin akış aboneliklerini sürdüren, support_tickets Firestore koleksiyonunda gönderilen destek kayıtlarını günlüğe kaydeden ve web portalında bir destek kaydı gönderildiğinde tetiklenen, ticketTitle ve ticketDescription değerlerini Google Workspace Studio akışına ileten bir Yeni Destek Kaydı özel başlangıç adımı (supportTicketTrigger).
  • Gemini Enterprise Agent Platform'da (aiplatform.googleapis.com) Gemini 3.8 Flash (gemini-3.8-flash) kullanarak bilet aciliyetini sınıflandıran bir Aciliyet Algıla özel işlem adımı (urgencyDetectorStep) ve anahtar kelime eşlemeye (urgent, blocker, critical) otomatik geri dönüş.

Neler öğreneceksiniz?

  • workflowTrigger ve workflowAction öğeleriyle bir HTTP Google Workspace eklentisi dağıtım manifesti (deployment.json) nasıl yapılandırılır?
  • Google Workspace Studio tetikleyici aboneliklerinin (triggerId, notifyUri ve userOAuthToken) studio_triggers Firestore koleksiyonunda nasıl kalıcı hale getirileceği ve triggers.fire REST API kullanılarak akışların nasıl tetikleneceği.
  • Gemini Enterprise Agent Platform'da Gemini 3.8 Flash (gemini-3.8-flash) yapılandırılmış çıkışını kullanarak Node.js'de anahtar kelime geri dönüşüyle workflowAction özel adım oluşturma

Ön koşullar

  • Google Workspace Studio'ya erişimi olan bir Google Workspace hesabı.
  • Faturalandırmanın etkinleştirildiği bir Google Cloud projesi. Test için yayınlanmamış eklenti dağıtımlarını yükleyebilmeniz amacıyla, kullanıcı hesabınızla aynı Google Workspace kuruluşunda oluşturulur.

2. Google Cloud projenizi ve Firestore'u ayarlama

Bu adımda, projenizin OAuth kullanıcı rızası ekranını yapılandırır, Cloud Shell'de gerekli Google Cloud ve Google Workspace API'lerini etkinleştirir, (default) Firestore veritabanınızı sağlar ve eklenti yetkilendirme hizmeti hesabınızı ilk kullanıma hazırlarsınız.

  1. Google Cloud Console'u açın, Google Cloud projenizi seçin ve üst gezinme çubuğundaki Cloud Shell'i etkinleştir simgesini tıklayın.
  2. Google Workspace'in eklentiniz için bir OAuth istemcisi sağlayabilmesi amacıyla OAuth kullanıcı rızası ekranını yapılandırın:
    1. Google Auth Platform Branding sayfasını açın.
    2. İstenirse Başlayın'ı tıklayın. (Kullanıcı rızası ekranınız zaten yapılandırılmışsa 3. adıma geçin.)
    3. Uygulama bilgileri bölümünde Uygulama adı için Support Extension girin, Kullanıcı destek e-postanızı seçin ve Sonraki'yi tıklayın.
    4. Kitle bölümünde Şirket içi'ni seçin ve Sonraki'yi tıklayın.
    5. İletişim bilgileri bölümünde e-posta adresinizi girip Sonraki'yi tıklayın.
    6. Bitir bölümünde Google API Hizmetleri: Kullanıcı Verileri Politikası'nı kabul edin, Devam'ı ve Oluştur'u tıklayın.
  3. Cloud Shell'de etkin projenizi ayarlayın (YOUR_PROJECT_ID yerine Google Cloud proje kimliğinizi girin) ve gerekli API'leri etkinleştirin:
    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. (default) Firestore veritabanınızı us-central1 içinde sağlayın (henüz yoksa) ve Google Workspace eklentileri hizmet hesabınızı ilk kullanıma hazırlayın:
    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. Firestore ve Gemini 3.8 Flash ile Node.js uygulaması oluşturma

Bu adımda, Node.js uygulamasını ~/studio-support-addon içinde oluşturursunuz. Kodu modüler ve okunabilir tutmak için projeyi beş odaklanmış dosyaya ayırın:

  • firestore.js: Cloud Firestore istemcisini (studio_triggers ve support_tickets koleksiyonları) başlatır ve önbelleğe alınmış OAuth jetonlarını yeniler.
  • starter.js: Yeni Destek Kaydı özel başlangıç adımı webhook'larını (/onConfigTrigger ve /onManageTrigger) uygular.
  • action.js: Anahtar kelime eşleştirme geri dönüşüyle Gemini 3.8 Flash (gemini-3.8-flash) kullanarak Aciliyet Algıla özel işlem adımı webhook'larını (/onConfigUrgency ve /onExecuteUrgency) uygular.
  • portal.js: Destek Hızlı Portalı web kullanıcı arayüzüne (GET /) hizmet verir ve etkin Google Workspace Studio tetikleyicilerini (POST /api/submit-ticket) etkinleştirir.
  • index.js: Tüm Express rotalarını birbirine bağlar ve HTTP sunucusunu başlatır.

Projeyi ve package.json başlatın.

  1. Bir proje dizini oluşturun ve bu dizine gidin:
    mkdir -p ~/studio-support-addon && cd ~/studio-support-addon
    
  2. express, @google-cloud/firestore ve @google/genai ile package.json oluşturma:
    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 — Paylaşılan Firestore durumu ve OAuth jetonu yenileme

İki Firestore koleksiyonunuzu yönetmek ve önbelleğe alınmış kullanıcı OAuth jetonlarını güncel tutmak için firestore.js oluşturun:

  • studio_triggers: Cloud Run kapsayıcı yeniden başlatmalarında ve sıfıra ölçeklendirme etkinliklerinde etkin Google Workspace Studio akışı aboneliklerinin (triggerId, notifyUri ve userOAuthToken) korunmasını sağlar.
  • support_tickets: Portal üzerinden gönderilen her destek kaydının kaydını saklar.
  • refreshTokenInFirestore(req): Google Workspace Studio, webhook uç noktalarınızdan herhangi birini her çağırdığında kısa ömürlü bir authorizationEventObject.userOAuthToken içerir. Bu yardımcı, arka plan triggers.fire çağrılarının yetkili kalması için etkin tetikleyici belgeleri en son jetonla günceller.
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 — Özel Başlangıç Adımı (workflowTrigger)

Google Workspace Studio özel başlangıç adımının gerektirdiği iki webhook uç noktasını uygulamak için starter.js oluşturun:

  • onConfigTrigger (POST /onConfigTrigger): Kullanıcı, Google Workspace Studio'da akışının başlangıcı olarak Yeni Destek Kaydı'nı seçtiğinde çağrılır. Başlatıcıyı açıklayan bir pushCard gezinme işlemi döndürür.
  • onManageTrigger (POST /onManageTrigger): Kullanıcı bir akışı etkinleştirdiğinde (req.body.workflow.triggerCreation) veya devre dışı bıraktığında / sildiğinde (req.body.workflow.triggerDeletion) çağrılır. Abonelik dokümanını studio_triggers Firestore koleksiyonuna kaydeder veya siler.
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 — Gemini 3.8 Flash ile Özel İşlem Adımı (workflowAction)

Google Workspace Studio özel işlem adımının gerektirdiği iki webhook uç noktasını uygulamak için action.js oluşturun:

  • onConfigUrgency (POST /onConfigUrgency): hostAppDataSource.workflowDataSource.includeVariables: true değerini ayarlayan bir textInput widget'ı içeren bir yapılandırma kartı döndürür. Bu kart, kullanıcıların başlangıç adımında oluşturulan ticketDescription değişkenini seçmesine olanak tanır.
  • onExecuteUrgency (POST /onExecuteUrgency): req.body.workflow.actionInvocation.inputs'den gelen ticketDescription girişini okur, yapılandırılmış çıkış (responseSchema enum ["High", "Normal"]) ile Gemini Enterprise Agent Platform'da Gemini 3.8 Flash (gemini-3.8-flash) kullanarak iş etkisini ve aciliyetini sınıflandırır, model çağrısı başarısız olursa detectKeywordUrgency()'e (urgent, blocker, critical) geri döner ve hostAppAction.workflowAction.returnOutputVariablesAction kullanarak urgencyLevel çıkış değişkenini döndürür.
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 — Support Quick Portal UI ve triggers.fire dispatcher

Web portalına hizmet vermek ve bilet gönderimlerini Google Workspace Studio'ya yönlendirmek için portal.js oluşturun:

  • renderPortal (GET /): Kayıtlı etkin Google Workspace Studio tetikleyicilerinin sayısını göstermek için studio_triggers Firestore koleksiyonunu sorgular ve HTML destek kaydı gönderme formunu sunar.
  • submitTicket (POST /api/submit-ticket): studio_triggers içindeki tüm etkin tetikleyici dokümanları yineler, ticketTitle ve ticketDescription çıkış değişkenleri ve benzersiz bir requestId ile Google Workspace Studio triggers.fire uç noktasını (https://workspacestudio.googleapis.com/v1/triggers/{triggerId}:fire) çağırır ve bileti support_tickets Firestore koleksiyonuna kaydeder.
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: Express sunucusu giriş noktası

Portal rotalarınızı ve Google Workspace Studio webhook uç noktalarınızı index.js ile PORT üzerinde dinleyen bir Express HTTP sunucusunda (Cloud Run'da varsayılan 8080) birbirine bağlayın:

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. Cloud Run'a dağıtma ve eklentiyi yükleme

Bu adımda Node.js hizmetini Cloud Run'a dağıtacak, Google Workspace eklentisi çağırma, Firestore ve Agent Platform çalışma zamanı erişimi için IAM izinleri verecek, deployment.json manifestini oluşturacak ve eklenti dağıtımını yükleyeceksiniz.

  1. support-extension hizmetini, proje ve Firestore koleksiyonu ortam değişkenlerinizle Cloud Run'a dağıtın:
    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. Google Workspace Studio'nun webhook uç noktalarınızı çağırabilmesi için Cloud Run hizmetinde Google Workspace Eklentileri hizmet hesabına roles/run.invoker izni verin:
    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. Cloud Run çalışma zamanı hizmet hesabına roles/datastore.user (studio_triggers ve support_tickets Firestore koleksiyonlarını okuma ve yazma için) ve roles/aiplatform.user (Gemini Enterprise Agent Platform'da Gemini'ı çağırma için) izinlerini verin:
    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. Dağıtılan Cloud Run URL'nizi alın ve deployment.json manifestini oluşturun. Bu manifest, supportTicketTrigger (workflowTrigger) ve urgencyDetectorStep (workflowAction) öğelerini tanımlar:
    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. Hesabınız için Google Workspace eklenti dağıtımını oluşturun (veya değiştirin), yükleyin ve Destek Hızlı Portalı URL'nizi yazdırın:
    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. Google Workspace Studio'da özel adımlarınızı test etme

  1. Google Workspace Studio'yu açın (zaten açıksa tarayıcı sekmesini yenileyin) ve Yeni Akış'ı tıklayın.
  2. Başlangıç seçin > Destek Uzantısı > Yeni Destek Kaydı'nı tıklayın.
  3. Adım ekle (+) > Destek Uzantısı > Aciliyet Algıla'yı tıklayın. Kaynak Açıklaması alanında + Değişkenler'i tıklayın ve Destek Kaydı Açıklaması'nı seçin.
  4. Adım ekle'yi (+) tıklayın ve mesajda Destek Kaydı Başlığı, Aciliyet Düzeyi ve Destek Kaydı Açıklaması değişkenlerini içeren bir Chat > Chat'te beni bilgilendir adımı ekleyin.
  5. Sağ üst köşedeki akışı Açık konuma getirin. Google Workspace Studio, Cloud Run'daki /onManageTrigger uç noktanızı hemen çağırır. Bu uç nokta, etkin tetikleyici aboneliğini Firestore'daki studio_triggers koleksiyonuna yazar.
  6. Destek Hızlı Portal URL'nizi (https://support-extension-...run.app) yeni bir sekmede açın, portalda Firestore'da 1 etkin tetikleyicinin göründüğünü onaylayın ve Gemini 3.8 Flash (gemini-3.8-flash) sınıflandırmasını test etmek için iki bilet gönderin:
    • Yüksek etkili bilet (Bilet Başlığı: London office lockout, Sorun Açıklaması: All 500 employees in the London office are locked out of their workstations 20 minutes before the board presentation.): urgent veya blocker gibi sabit kodlu anahtar kelimeler olmasa bile gemini-3.8-flash, bu bileti High aciliyetinde sınıflandırır.
    • Normal öncelikli destek kaydı (Destek kaydı başlığı: Conference room adapters, Sorun açıklaması: Could we order two extra HDMI adapters for the third-floor conference room next month?): gemini-3.8-flash, bu destek kaydını Normal aciliyetinde sınıflandırır.
  7. Google Chat'te ve Google Workspace Studio Etkinlik sekmesinde, akışınızın hem Urgency Level: High hem de Urgency Level: Normal ile her iki bilet için de çalıştığını doğrulayın.

6. Temizleme

Google Cloud hesabınızdan sürekli olarak ücret alınmasını önlemek için Cloud Shell'de dağıtılan kaynakları kaldırın:

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. Tebrikler!

Gemini Enterprise Agent Platform'da Firestore, Cloud Run ve Gemini 3.8 Flash kullanarak Google Workspace Studio için bir Node.js HTTP uzantısı oluşturup dağıttınız ve test ettiniz.

İşlediğimiz konular

  • workflowTrigger ve workflowAction öğeleriyle Google Workspace Studio HTTP eklentisi manifesti (deployment.json) yapılandırma.
  • Google Workspace Studio workflowTrigger aboneliklerini studio_triggers Firestore koleksiyonunda kalıcı hale getirme ve support_tickets içinde günlük portalı gönderimleri oluşturma. Böylece tetikleyiciler, Cloud Run ölçeklendirme ve yeniden dağıtma işlemlerinden etkilenmez.
  • Gemini Enterprise Agent Platform'da Gemini 3.8 Flash (gemini-3.8-flash) ile destek taleplerini sınıflandıran bir workflowAction adımı oluşturma. Bu adımda, anahtar kelime eşleştirme ile ilgili yedekleme özelliği de bulunur.

Sonraki adımlar