ساختن مراحل سفارشی Google Workspace Studio در Node.js در Cloud Run

۱. مقدمه

Google Workspace Studio به شما امکان می‌دهد فرایندهای کسب‌وکار را در برنامه‌های Google Workspace مانند Gmail،‏ Google Chat،‏ Google Drive، و «کاربرگ‌نگار Google» خودکارسازی کنید. وقتی گردش‌های کار شما باید از یک برنامه وب خارجی شروع شود یا منطق سفارشی در محیط ابری خودتان اجرا شود، می‌توانید بااستفاده از برافزای Google Workspace در Node.js در Cloud Run، «استودیوِ Google Workspace» را با HTTP گسترش دهید:

  1. مرحله آغازگر سفارشی (workflowTrigger): با فراخوانی «میانای برنامه‌سازی کاربردی» triggers.fire REST در Google Workspace Studio، وقتی رویداد خارجی رخ می‌دهد (مثلاً مشتری در درگاه وب داخلی درخواست پشتیبانی ارسال می‌کند) جریان Google Workspace Studio را شروع می‌کند.
  2. مرحله کنش سفارشی (workflowAction): متغیرها را از مراحل قبلی در جریان دریافت می‌کند، منطق سفارشی یا استنباط هوش مصنوعی را در سرویس Node.js شما اجرا می‌کند، و متغیرهای برونداد را به Google Workspace Studio برمی‌گرداند.

در این codelab، افزونه کامل Node.js HTTP برای Google Workspace Studio را در Cloud Run می‌سازید و مستقر می‌کنید، از Cloud Firestore برای ماندگار کردن اشتراک‌های راه‌انداز جاری و درخواست‌های ارسال‌شده در سراسر تجدیدنظرهای محتوی استفاده می‌کنید، و بااستفاده از Gemini 3.8 Flash (gemini-3.8-flash) در پلاتفرم عامل Gemini Enterprise با برگشتی مقاوم دربرابر تطبیق کلیدواژه، فوریت درخواست را در مرحله کنش سفارشی خود طبقه‌بندی می‌کنید.

آنچه خواهید ساخت

  • برنامه وب درگاه سریع پشتیبانی و «برافزای Google Workspace» با HTTP که با Node.js،‏ Express، و Firestore در Cloud Run ساخته شده است.
  • یک مرحله آغازگر سفارشی درخواست پشتیبانی جدید (supportTicketTrigger) که اشتراک‌های جاری گردش کار را در studio_triggers مجموعه Firestore حفظ می‌کند، درخواست‌های ارسال‌شده را در support_tickets مجموعه Firestore ثبت می‌کند، و هرگاه درگاه وب درخواستی ارسال شود اجرا می‌شود—ticketTitle و ticketDescription را به گردش کار «استودیوِ Google Workspace» منتقل می‌کند.
  • یک مرحله کنش سفارشی تشخیص فوریت (urgencyDetectorStep) که فوریت درخواست پشتیبانی را بااستفاده از Gemini 3.8 Flash (gemini-3.8-flash) در پلاتفرم Gemini Enterprise Agent (aiplatform.googleapis.com) طبقه‌بندی می‌کند، با بازگشت خودکار به تطبیق کلیدواژه (urgent،‏ blocker،‏ critical).

آنچه خواهید آموخت

  • نحوه پیکربندی مانیفست استقرار «برافزای Google Workspace» با پروتکل HTTP (deployment.json) با عناصر workflowTrigger و workflowAction.
  • نحوه ماندگار کردن اشتراک‌های راه‌انداز «استودیوِ Google Workspace» (triggerId، notifyUri، و userOAuthToken) در studio_triggers مجموعه Firestore و راه‌اندازی جریان‌ها بااستفاده از triggers.fire REST API.
  • نحوه ساختن گام سفارشی workflowAction در Node.js بااستفاده از برونداد ساختاری Gemini 3.8 Flash (gemini-3.8-flash) در پلاتفرم Gemini Enterprise Agent با کلیدواژه برگشتی.

پیش‌نیازها

  • حساب Google Workspace با دسترسی به استودیو Google Workspace.
  • پروژه Google Cloud با صورت‌حساب فعال که در همان سازمان Google Workspace حساب کاربری شما ایجاد شده است تا بتوانید استقرار افزونه‌های منتشرنشده را برای آزمایش نصب کنید.

۲. راه‌اندازی پروژه Google Cloud و Firestore

در این مرحله، صفحه رضایت OAuth پروژه خود را پیکربندی می‌کنید، «میاناهای برنامه‌سازی کاربردی» Google Cloud و Google Workspace موردنیاز را در Cloud Shell فعال می‌کنید، پایگاه داده (default) Firestore خود را آماده می‌کنید، و حساب سرویس مجوز برافزای خود را مقداردهی اولیه می‌کنید.

  1. کنسول Google Cloud را باز کنید، پروژه Google Cloud خود را انتخاب کنید، و روی نماد فعال‌سازی Cloud Shell در نوار پیمایش بالا کلیک کنید.
  2. صفحه کسب رضایت OAuth را پیکربندی کنید تا Google Workspace بتواند کارخواه OAuth را برای برافزای شما آماده کند:
    1. صفحه «نمانام پلاتفرم اصالت‌سنجی Google» را باز کنید.
    2. اگر درخواست شد، روی شروع به کار کلیک کنید. (اگر صفحه موافقت شما ازقبل پیکربندی شده است، به مرحله ۳ بروید.)
    3. در بخش اطلاعات برنامه، Support Extension را برای نام برنامه وارد کنید، ایمیل پشتیبانی کاربر را انتخاب کنید، و روی بعدی کلیک کنید.
    4. در بخش مخاطب، داخلی را انتخاب کنید و روی بعدی کلیک کنید.
    5. در بخش اطلاعات تماس، نشانی ایمیلتان را وارد کنید و روی بعدی کلیک کنید.
    6. در بخش تکمیل، با خط‌مشی داده‌های کاربر «سرویس‌های Google API» موافقت کنید، روی ادامه کلیک کنید، و سپس روی ایجاد کلیک کنید.
  3. در Cloud Shell، پروژه فعال خود را تنظیم کنید (YOUR_PROJECT_ID را با شناسه پروژه Google Cloud خود جایگزین کنید) و میاناهای برنامه‌سازی کاربردی موردنیاز را فعال کنید:
    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 خود را در us-central1 تدارک ببینید (اگر ازقبل وجود ندارد) و حساب سرویس «برافزاهای Google Workspace» خود را مقداردهی اولیه کنید:
    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
    

۳. ساختن برنامه Node.js با Firestore و Gemini 3.8 Flash

در این مرحله، برنامه Node.js را در ~/studio-support-addon می‌سازید. برای اینکه کد را واحدی و خوانا نگه دارید، پروژه را در پنج فایل متمرکز سازمان‌دهی می‌کنید:

  • firestore.js: کارخواه Cloud Firestore (مجموعه‌های studio_triggers و support_tickets) را مقداردهی اولیه می‌کند و ژتون‌های OAuth ذخیره‌شده در حافظه نهان را بازآوری می‌کند.
  • ‫starter.js: وب‌قلاب‌های مرحله آغازگر سفارشی بلیت پشتیبانی جدید را پیاده‌سازی می‌کند (/onConfigTrigger و /onManageTrigger).
  • action.js: وب‌قلاب‌های مرحله کنش سفارشی تشخیص فوریت (/onConfigUrgency و /onExecuteUrgency) را بااستفاده از Gemini 3.8 Flash (gemini-3.8-flash) با برگشتی تطبیق کلیدواژه پیاده‌سازی می‌کند.
  • portal.js: رابط کاربری وب درگاه سریع پشتیبانی (GET /) را ارائه می‌دهد و محرک‌های فعال Google Workspace Studio (POST /api/submit-ticket) را راه‌اندازی می‌کند.
  • ‫index.js: همه مسیرهای Express را به‌هم متصل می‌کند و سرور HTTP را شروع می‌کند.

مقداردهی اولیه پروژه و package.json

  1. فهرست راهنمای پروژه بسازید و به آن پیمایش کنید:
    mkdir -p ~/studio-support-addon && cd ~/studio-support-addon
    
  2. ساختن package.json با express،‏ @google-cloud/firestore، و @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 — وضعیت Firestore هم‌رسانی‌شده و بازآوری کد OAuth

برای مدیریت دو مجموعه Firestore و تازه نگه داشتن ژتون‌های OAuth کاربر ذخیره‌شده در حافظه نهان، firestore.js را ایجاد کنید:

  • studio_triggers: اشتراک‌های جاری فعال Google Workspace Studio (triggerId،‏ notifyUri، و userOAuthToken) را ذخیره می‌کند تا از رویدادهای مقیاس‌بندی به صفر و راه‌اندازی مجدد محتوی Cloud Run جان سالم به‌در ببرند.
  • support_tickets: سابقه هر بلیت پشتیبانی ارائه‌شده ازطریق درگاه را ذخیره می‌کند.
  • refreshTokenInFirestore(req): هروقت «استودیوِ Google Workspace» با هریک از نقاط پایانی وب‌قلاب شما تماس می‌گیرد، authorizationEventObject.userOAuthToken کوتاه‌مدتی را اضافه می‌کند. این دستیار اسناد محرک فعال را با جدیدترین کد به‌روزرسانی می‌کند تا تماس‌های پس‌زمینه triggers.fire همچنان مجاز باقی بمانند.
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 — مرحله شروع سفارشی (workflowTrigger)

برای پیاده‌سازی دو نقطه پایانی وب‌قلاب موردنیاز مرحله آغازگر سفارشی Google Workspace Studio، starter.js را ایجاد کنید:

  • onConfigTrigger (POST /onConfigTrigger): زمانی فراخوانده می‌شود که کاربر درخواست پشتیبانی جدید را به‌عنوان شروع‌کننده جاری‌سازی خود در «استودیو Google Workspace» انتخاب کند. کنش پیمایش pushCard را برمی‌گرداند که شروع‌کننده را توصیف می‌کند.
  • onManageTrigger (POST /onManageTrigger): وقتی کاربر جریانی را روشن (req.body.workflow.triggerCreation) یا خاموش می‌کند / آن را حذف می‌کند (req.body.workflow.triggerDeletion)، فراخوانده می‌شود. سند اشتراک را در مجموعه studio_triggers Firestore ذخیره یا حذف می‌کند.
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 — مرحله کنش سفارشی (workflowAction) با Gemini 3.8 Flash

برای پیاده‌سازی دو نقطه پایانی وب‌قلاب موردنیاز مرحله کنش سفارشی Google Workspace Studio، action.js را ایجاد کنید:

  • onConfigUrgency (POST /onConfigUrgency): کارت پیکربندی با ابزاره textInput برمی‌گرداند که hostAppDataSource.workflowDataSource.includeVariables: true را تنظیم می‌کند و به کاربران امکان می‌دهد متغیر ticketDescription را که توسط مرحله شروع تولید شده است انتخاب کنند.
  • onExecuteUrgency (POST /onExecuteUrgency): ورودی ticketDescription را از req.body.workflow.actionInvocation.inputs می‌خواند، تأثیر کسب‌وکار و فوریت آن را بااستفاده از Gemini 3.8 Flash (gemini-3.8-flash) در پلاتفرم Gemini Enterprise Agent با برونداد ساختاریافته (responseSchema enum ["High", "Normal"]) طبقه‌بندی می‌کند، اگر فراخوانی مدل ناموفق باشد به detectKeywordUrgency() (urgent،‏ blocker،‏ critical) برمی‌گردد، و متغیر برونداد urgencyLevel را بااستفاده از 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 — پشتیبانی از رابط کاربری «درگاه سریع» و توزیع‌کننده triggers.fire

‫portal.js را برای ارائه درگاه وب و ارسال درخواست‌های بلیت به Google Workspace Studio ایجاد کنید:

  • renderPortal (GET /): مجموعه studio_triggers Firestore را پُرسمان می‌کند تا نشان دهد چند راه‌انداز فعال Google Workspace Studio ثبت شده است و فرم ارسال درخواست پشتیبانی HTML را ارائه می‌دهد.
  • submitTicket (POST /api/submit-ticket): در همه سندهای محرک فعال در studio_triggers تکرار می‌کند، نقطه پایانی triggers.fire «استودیو Google Workspace» (https://workspacestudio.googleapis.com/v1/triggers/{triggerId}:fire) را با متغیرهای برونداد ticketTitle و ticketDescription و requestId منحصربه‌فرد فرا می‌خواند، و درخواست پشتیبانی را در مجموعه 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 — نقطه ورودی سرور Express

index.js را ایجاد کنید تا مسیرهای درگاه و نقاط پایانی وب‌قلاب Google Workspace Studio را در سرور Express HTTP که در PORT (پیش‌فرض 8080 در 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

۴. مستقر کردن در Cloud Run و نصب برافزا

در این مرحله، سرویس Node.js را در Cloud Run مستقر می‌کنید، اجازه‌های IAM را برای فراخواننده «برافزاهای Google Workspace» و دسترسی زمان اجرای Firestore و «پلاتفرم عامل» اعطا می‌کنید، deployment.json مانیفست را تولید می‌کنید، و استقرار برافزا را نصب می‌کنید.

  1. سرویس support-extension را با متغیرهای محیطی پروژه و مجموعه Firestore در Cloud Run مستقر کنید:
    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» در سرویس Cloud Run roles/run.invoker اعطا کنید تا «استودیو Google Workspace» بتواند نقطه‌های پایانی وب‌قلاب شما را فراخوانی کند:
    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. ‫roles/datastore.user (برای خواندن و نوشتن مجموعه‌های studio_triggers و support_tickets Firestore) و roles/aiplatform.user (برای فراخوانی Gemini در «پلاتفرم نماینده Gemini Enterprise») را به حساب سرویس زمان اجرای 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. نشانی وب Cloud Run مستقرشده خود را بازیابی کنید و مانیفست deployment.json را که عناصر supportTicketTrigger (workflowTrigger) و 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. استقرار «برافزای Google Workspace» را برای حسابتان ایجاد (یا جایگزین) و نصب کنید، و نشانی وب «درگاه سریع پشتیبانی» را چاپ کنید:
    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}"
    

۵. آزمایش کردن مراحل سفارشی در Google Workspace Studio

  1. Google Workspace Studio را باز کنید (اگر قبلاً باز بوده است، برگه مرورگر را بازآوری کنید) و روی جریان جدید کلیک کنید.
  2. روی انتخاب یک شروع‌کننده > افزونه پشتیبانی > درخواست پشتیبانی جدید کلیک کنید.
  3. روی افزودن مرحله (+) > افزونه پشتیبانی > تشخیص فوریت کلیک کنید. در فیلد شرح منبع، روی + متغیرها کلیک کنید و شرح درخواست پشتیبانی را انتخاب کنید.
  4. روی افزودن مرحله (+) کلیک کنید و مرحله گپ > در Chat به من اطلاع بده را اضافه کنید که شامل متغیرهای عنوان درخواست پشتیبانی، سطح فوریت، و شرح درخواست پشتیبانی در پیام باشد.
  5. جریان را در گوشه بالا سمت راست روشن کنید. ‫Google Workspace Studio بلافاصله با نقطه پایانی /onManageTrigger شما در Cloud Run تماس می‌گیرد، که اشتراک محرک فعال را در مجموعه studio_triggers در Firestore می‌نویسد.
  6. نشانی وب «درگاه سریع پشتیبانی» (https://support-extension-...run.app) را در برگه جدیدی باز کنید، تأیید کنید که درگاه 1 محرک فعال را در Firestore نمایش می‌دهد، و دو درخواست پشتیبانی برای آزمایش طبقه‌بندی Gemini 3.8 Flash (gemini-3.8-flash) ارسال کنید:
    • درخواست پشتیبانی با تأثیر بالا (عنوان درخواست پشتیبانی: London office lockout، شرح مشکل: All 500 employees in the London office are locked out of their workstations 20 minutes before the board presentation.): حتی بدون کلیدواژه‌های کدبندی‌شده سخت مثل urgent یا blocker، gemini-3.8-flash این درخواست پشتیبانی را به‌عنوان فوریت High طبقه‌بندی می‌کند.
    • درخواست روال (عنوان درخواست: Conference room adapters، شرح مشکل: Could we order two extra HDMI adapters for the third-floor conference room next month?): gemini-3.8-flash این درخواست را به‌عنوان فوریت Normal طبقه‌بندی می‌کند.
  7. در Google Chat و در برگه فعالیت Google Workspace Studio تأیید کنید که جریان شما برای هر دو درخواست با Urgency Level: High و Urgency Level: Normal اجرا شده است.

‫۶. مرتب‌سازی

برای جلوگیری از کسر هزینه‌های جاری از حساب Google Cloud خود، منابع مستقرشده را در 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

‫۷. تبریک!

بااستفاده از Firestore،‏ Cloud Run، و Gemini 3.8 Flash در پلاتفرم کارگزار Gemini Enterprise، افزونه Node.js HTTP برای Google Workspace Studio ساختید، مستقر کردید، و آزمایش کردید!

آنچه پوشش داده‌ایم

  • درحال پیکربندی مانیفست برافزای HTTP «استودیوِ Google Workspace» (deployment.json) با workflowTrigger و workflowAction عنصر.
  • اشتراک‌های Google Workspace Studio workflowTrigger در مجموعه studio_triggers Firestore ماندگار می‌شوند و ارسال‌های درگاه در support_tickets ثبت می‌شوند تا محرک‌ها از مقیاس‌بندی و استقرار مجدد Cloud Run جان سالم به‌در ببرند.
  • درحال ساختن مرحله workflowAction که پیام‌های پشتیبانی را با Gemini 3.8 Flash (gemini-3.8-flash) برونداد ساختاریافته در پلاتفرم Gemini Enterprise Agent طبقه‌بندی می‌کند، با یک برگشت به تطبیق کلیدواژه انعطاف‌پذیر.

مراحل بعدی