إنشاء خطوات مخصّصة في Google Workspace Studio باستخدام Node.js على Cloud Run

1. مقدمة

تتيح لك Google Workspace Studio إنجاز العمليات التجارية آليًا في تطبيقات Google Workspace، مثل Gmail وGoogle Chat وGoogle Drive و"جداول بيانات Google". عندما تحتاج إلى بدء خطوات سير العمل من تطبيق ويب خارجي أو تنفيذ منطق مخصّص في بيئة السحابة الإلكترونية الخاصة بك، يمكنك توسيع نطاق Google Workspace Studio باستخدام إضافة Google Workspace HTTP في Node.js على Cloud Run:

  1. خطوة بدء مخصّصة (workflowTrigger): تبدأ سير عمل Google Workspace Studio عند وقوع حدث خارجي، مثل إرسال أحد العملاء طلبًا في بوابة ويب داخلية، وذلك من خلال طلب واجهة برمجة تطبيقات REST الخاصة بـ Google Workspace Studio triggers.fire.
  2. خطوة الإجراء المخصّص (workflowAction): تتلقّى هذه الخطوة متغيرات من الخطوات السابقة في المسار، وتنفّذ منطقًا مخصّصًا أو استنتاجًا مستندًا إلى الذكاء الاصطناعي في خدمة Node.js، وتعيد متغيرات الإخراج إلى Google Workspace Studio.

في هذا الدرس التطبيقي حول الترميز، ستنشئ وتنفّذ إضافة HTTP كاملة بلغة Node.js في Google Workspace Studio على Cloud Run، وتستخدم Cloud Firestore للاحتفاظ باشتراكات مشغّل التدفق النشط والتذاكر المُرسَلة في جميع مراجعات الحاويات، وتصنّف مدى إلحاح التذاكر في خطوة الإجراء المخصّص باستخدام Gemini 3.8 Flash (gemini-3.8-flash) على منصة وكيل Gemini Enterprise مع خيار احتياطي مرن لمطابقة الكلمات الرئيسية.

ما ستنشئه

  • تطبيق ويب Support Quick Portal وإضافة HTTP في Google Workspace تم إنشاؤهما باستخدام Node.js وExpress وFirestore على Cloud Run
  • خطوة بداية مخصّصة طلب دعم جديد (supportTicketTrigger) تحتفظ باشتراكات المسار النشطة في مجموعة studio_triggers Firestore، وتسجّل طلبات الدعم المُرسَلة في مجموعة support_tickets Firestore، ويتم تشغيلها كلما تم إرسال طلب دعم في بوابة الويب، مع تمرير ticketTitle وticketDescription إلى مسار Google Workspace Studio.
  • خطوة إجراء مخصّص التعرّف على مدى الإلحاح (urgencyDetectorStep) تصنّف مدى إلحاح الطلب باستخدام Gemini 3.8 Flash (gemini-3.8-flash) على منصة وكلاء Gemini Enterprise (aiplatform.googleapis.com)، مع الرجوع تلقائيًا إلى مطابقة الكلمات الرئيسية (urgent وblocker وcritical).

أهداف الدورة التعليمية

  • كيفية ضبط بيان نشر إضافة Google Workspace HTTP (deployment.json) باستخدام العنصرَين workflowTrigger وworkflowAction
  • كيفية الاحتفاظ باشتراكات مشغّلات Google Workspace Studio (triggerId وnotifyUri وuserOAuthToken) في studio_triggers مجموعة Firestore وتشغيل التدفقات باستخدام واجهة triggers.fire REST API
  • كيفية إنشاء workflowAction خطوة مخصّصة في Node.js باستخدام Gemini 3.8 Flash (gemini-3.8-flash) للناتج المنظَّم على Gemini Enterprise Agent Platform مع خيار احتياطي للكلمات الرئيسية

المتطلبات الأساسية

  • حساب Google Workspace لديه إذن الوصول إلى Google Workspace Studio
  • مشروع Google Cloud تم تفعيل الفوترة فيه، وتم إنشاؤه ضمن مؤسسة Google Workspace نفسها التي يتبع لها حسابك كمستخدم، وذلك حتى تتمكّن من تثبيت عمليات نشر الإضافات غير المنشورة بغرض الاختبار

2. إعداد مشروعك على Google Cloud وFirestore

في هذه الخطوة، يمكنك ضبط شاشة طلب الموافقة المتعلّقة ببروتوكول OAuth في مشروعك، وتفعيل واجهات برمجة تطبيقات Google Cloud وGoogle Workspace المطلوبة في Cloud Shell، وتوفير قاعدة بيانات (default) Firestore، وإعداد حساب خدمة تفويض الإضافة.

  1. افتح Google Cloud Console، واختَر مشروعك على السحابة الإلكترونية، ثم انقر على رمز تفعيل Cloud Shell في شريط التنقل العلوي.
  2. اضبط شاشة طلب الموافقة المتعلّقة ببروتوكول OAuth ليتمكّن Google Workspace من توفير عميل OAuth للإضافة:
    1. افتح صفحة "العلامة التجارية لمنصة Google للمصادقة".
    2. انقر على البدء إذا طُلب منك ذلك. (إذا سبق لك ضبط شاشة طلب الموافقة، انتقِل إلى الخطوة 3).
    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
    

3- إنشاء تطبيق 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.js لإدارة مجموعتَي Firestore والحفاظ على رموز OAuth المميزة للمستخدمين المخزّنة مؤقتًا:

  • ‫studio_triggers: تخزِّن هذه السمة اشتراكات سير العمل النشطة في Google Workspace Studio (triggerId وnotifyUri وuserOAuthToken) لكي تظل متاحة عند إعادة تشغيل حاوية Cloud Run وعند وقوع أحداث التوسيع إلى صفر.
  • ‫support_tickets: يخزِّن هذا القسم سجلاً لكل طلب دعم تم إرساله من خلال البوابة.
  • refreshTokenInFirestore(req): عندما يتصل Google Workspace Studio بأي من نقاط نهاية Webhook، يتضمّن 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)

أنشئ starter.js لتنفيذ نقطتَي نهاية لبرنامج ربط الويب المطلوبتَين في خطوة بدء مخصّصة في Google Workspace Studio:

  • ‫onConfigTrigger (POST /onConfigTrigger): يتم استدعاؤه عندما يختار المستخدم طلب دعم جديد كبداية لسير العمل في Google Workspace Studio. تعرض هذه السمة pushCard إجراء تنقّل يصف أداة بدء التشغيل.
  • ‫onManageTrigger (POST /onManageTrigger): يتم استدعاؤه عندما يفعِّل المستخدم مسارًا (On) (req.body.workflow.triggerCreation) أو يوقفه (Off) أو يحذفه (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

أنشِئ action.js لتنفيذ نقطتَي نهاية لبرنامج ربط التطبيقات (Webhook) تتطلّبهما خطوة إجراء مخصّصة في Google Workspace Studio:

  • 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 Platform مع ناتج منظَّم (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 Studio (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 معًا في خادم HTTP Express يستمع إلى 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

4. النشر على Cloud Run وتثبيت الإضافة

في هذه الخطوة، يمكنك نشر خدمة Node.js على Cloud Run، ومنح أذونات "إدارة الهوية وإمكانية الوصول" لمشغّل إضافات Google Workspace وFirestore وإمكانية الوصول إلى وقت تشغيل Agent Platform، وإنشاء ملف deployment.json، وتثبيت عملية نشر الإضافة.

  1. انشر خدمة support-extension على Cloud Run مع متغيرات بيئة مشروعك ومجموعة Firestore:
    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. امنح الإذن roles/run.invoker لخدمة Cloud Run لحساب خدمة "إضافات Google Workspace" كي يتمكّن Workspace Studio من استدعاء نقاط نهاية خطاف الويب:
    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. استرداد عنوان URL الذي تم نشره على 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" لحسابك وثبِّتها، ثم اطبع عنوان URL الخاص بـ "بوابة الدعم السريع":
    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

  1. افتح Google Workspace Studio (أعِد تحميل علامة تبويب المتصفّح إذا كانت مفتوحة من قبل) وانقر على مسار عمل جديد.
  2. انقر على اختيار أداة مساعدة > إضافة الدعم > طلب دعم جديد.
  3. انقر على إضافة خطوة (+) > إضافة دعم > رصد مدى الإلحاح. في حقل وصف المصدر، انقر على + المتغيّرات واختَر وصف التذكرة.
  4. انقر على إضافة خطوة (+) وأضِف خطوة Chat > إرسال إشعار إليّ في Chat تتضمّن المتغيّرات عنوان التذكرة ومستوى الأهمية ووصف التذكرة في الرسالة.
  5. فعِّل الخيار تشغيل في أعلى يسار الصفحة. يطلب Google Workspace Studio على الفور نقطة النهاية /onManageTrigger على Cloud Run، والتي تكتب اشتراك المشغّل النشط في المجموعة studio_triggers في Firestore.
  6. افتح عنوان URL لبوابة الدعم السريع (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.

6. تَنظيم

لتجنُّب تحمّل رسوم مستمرة على حسابك على 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

7. تهانينا!

لقد أنشأت ونشرت واختبرت إضافة HTTP بلغة Node.js في Google Workspace Studio باستخدام Firestore وCloud Run وGemini 3.8 Flash على Gemini Enterprise Agent Platform.

المواضيع التي تناولناها

  • إعداد بيان إضافة HTTP في Google Workspace Studio (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 Platform، مع خيار احتياطي مرن لمطابقة الكلمات الرئيسية

الخطوات التالية