Cloud Run-এ Node.js-এ কাস্টম Google Workspace Studio ধাপ তৈরি করা

১. ভূমিকা

Google Workspace Studio আপনাকে Gmail, Google Chat, Google Drive এবং Google Sheets-এর মতো Google Workspace অ্যাপ্লিকেশন জুড়ে ব্যবসায়িক প্রসেস অটোমেট করতে দেয়। আপনার ওয়ার্কফ্লো কোনও এক্সটার্নাল ওয়েব অ্যাপ্লিকেশন থেকে শুরু করতে হলে অথবা নিজের ক্লাউড এনভায়রনমেন্টে কাস্টম লজিক রান করাতে হলে, আপনি Cloud Run-এ Node.js-এ HTTP Google Workspace অ্যাড-অন দিয়ে Google Workspace Studio এক্সটেন্ড করতে পারবেন:

  1. কাস্টম স্টার্টার স্টেপ (workflowTrigger): কোনও এক্সটার্নাল ইভেন্ট ঘটলে Google Workspace Studio ফ্লো শুরু করে—যেমন, কোনও গ্রাহক ইন্টার্নাল ওয়েব পোর্টালে টিকিট জমা দিলে—Google Workspace Studio triggers.fire REST API কল করে।
  2. কাস্টম অ্যাকশন স্টেপ (workflowAction): ফ্লোয়ের আগের স্টেপ থেকে ভেরিয়েবল গ্রহণ করে, আপনার Node.js পরিষেবায় কাস্টম লজিক বা AI ইনফারেন্স রান করে এবং Google Workspace Studio-তে আউটপুট ভেরিয়েবল ফেরত দেয়।

এই কোডল্যাবে, আপনি Cloud Run-এ Google Workspace Studio-এর জন্য একটি সম্পূর্ণ Node.js HTTP এক্সটেনশন তৈরি ও ডিপ্লয় করবেন, অ্যাক্টিভ ফ্লো ট্রিগার সাবস্ক্রিপশন ও কন্টেনার রিভিশন জুড়ে জমা দেওয়া টিকিট সেভ করে রাখতে Cloud Firestore ব্যবহার করবেন এবং Gemini Enterprise Agent Platform-এ Gemini 3.8 Flash (gemini-3.8-flash) ব্যবহার করে আপনার কাস্টম অ্যাকশন ধাপে টিকিটের জরুরি অবস্থা শ্রেণীবদ্ধ করবেন, যার মধ্যে কীওয়ার্ড ম্যাচিং ফলব্যাকও থাকবে।

আপনি কী তৈরি করবেন

  • Cloud Run-এ Node.js, Express ও Firestore-এর সাহায্যে তৈরি করা সহায়তা কুইক পোর্টাল ওয়েব অ্যাপ ও HTTP Google Workspace অ্যাড-অন।
  • নতুন সহায়তা টিকিট কাস্টম স্টার্টার স্টেপ (supportTicketTrigger) যা studio_triggers Firestore কালেকশনে অ্যাক্টিভ ফ্লো সাবস্ক্রিপশন বজায় রাখে, support_tickets Firestore কালেকশনে জমা দেওয়া টিকিট লগ করে এবং ওয়েব পোর্টালে টিকিট জমা দেওয়া হলে ফায়ার করে—ticketTitle ও ticketDescription Google Workspace Studio ফ্লোতে পাস করে।
  • Gemini Enterprise Agent Platform (aiplatform.googleapis.com)-এ Gemini 3.8 Flash (gemini-3.8-flash) ব্যবহার করে টিকিটের জরুরি অবস্থা শ্রেণীবদ্ধ করে এমন একটি জরুরি অবস্থা শনাক্ত করুন কাস্টম অ্যাকশন স্টেপ (urgencyDetectorStep), যার মধ্যে কীওয়ার্ড ম্যাচিংয়ে (urgent, blocker, critical) অটোমেটিক ফলব্যাক আছে।

আপনি কী শিখবেন

  • workflowTrigger ও workflowAction এলিমেন্ট সহ HTTP Google Workspace অ্যাড-অন ডেপ্লয়মেন্ট ম্যানিফেস্ট (deployment.json) কীভাবে কনফিগার করতে হয়।
  • কীভাবে studio_triggers Firestore কালেকশনে Google Workspace Studio ট্রিগার সাবস্ক্রিপশন (triggerId, notifyUri ও userOAuthToken) সেভ করে রাখা যায় এবং triggers.fire REST API ব্যবহার করে ফ্লো ফায়ার করা যায়।
  • কীভাবে Gemini Enterprise Agent Platform-এ Gemini 3.8 Flash (gemini-3.8-flash) স্ট্রাকচার্ড আউটপুট ব্যবহার করে Node.js-এ workflowAction কাস্টম স্টেপ তৈরি করবেন, যেখানে কীওয়ার্ড ফলব্যাক থাকবে।

পূর্বশর্ত

  • Google Workspace Studio-তে অ্যাক্সেস সহ Google Workspace অ্যাকাউন্ট।
  • আপনার ব্যবহারকারীর অ্যাকাউন্টের মতো একই Google Workspace সংস্থার মধ্যে তৈরি করা বিলিং চালু থাকা Google Cloud প্রোজেক্ট যাতে আপনি পরীক্ষা করার জন্য প্রকাশ না করা অ্যাড-অন ডিপ্লয়মেন্ট ইনস্টল করতে পারেন।

২. আপনার Google Cloud প্রোজেক্ট ও Firestore সেট-আপ করা

এই ধাপে, আপনার প্রোজেক্টের OAuth সম্মতি স্ক্রিন কনফিগার করুন, Cloud Shell-এ প্রয়োজনীয় Google Cloud ও Google Workspace API চালু করুন, আপনার (default) Firestore ডেটাবেস প্রোভিশন করুন এবং আপনার অ্যাড-অন অনুমোদনের পরিষেবা অ্যাকাউন্ট চালু করুন।

  1. Google Cloud console খুলুন, আপনার Google Cloud প্রোজেক্ট বেছে নিন এবং উপরের নেভিগেশন বারে Cloud Shell অ্যাক্টিভেট করুন আইকনে ক্লিক করুন।
  2. OAuth-এর সম্মতি সংক্রান্ত স্ক্রিন কনফিগার করুন যাতে Google Workspace আপনার অ্যাড-অনের জন্য OAuth ক্লায়েন্ট প্রোভিশন করতে পারে:
    1. Google Auth প্ল্যাটফর্ম ব্র্যান্ডিং পৃষ্ঠা খুলুন।
    2. প্রম্পট করা হলে, শুরু করুন বিকল্পে ক্লিক করুন। (আপনার সম্মতি স্ক্রিন আগে থেকেই কনফিগার করা থাকলে, ধাপ ৩-এ যান।)
    3. অ্যাপ সংক্রান্ত তথ্য বিকল্পের অধীনে, অ্যাপের নাম-এর জন্য Support Extension লিখুন, আপনার ব্যবহারকারী সহায়তা সংক্রান্ত ইমেল বেছে নিন এবং পরবর্তী বিকল্পে ক্লিক করুন।
    4. দর্শক বিকল্পের মধ্যে, ইন্টার্নাল বিকল্প বেছে নিয়ে পরবর্তী বিকল্পে ক্লিক করুন।
    5. যোগাযোগের তথ্য বিকল্পের অধীনে, আপনার ইমেল আইডি লিখুন এবং পরবর্তী বিকল্পে ক্লিক করুন।
    6. সম্পূর্ণ করুন বিকল্পের অধীনে, Google API পরিষেবা: ব্যবহারকারীর ডেটা নীতিতে সম্মতি জানান, চালিয়ে যান বিকল্পে ক্লিক করুন এবং তৈরি করুন বিকল্পে ক্লিক করুন।
  3. Cloud Shell-এ, আপনার অ্যাক্টিভ প্রোজেক্ট সেট করুন (YOUR_PROJECT_ID-এর জায়গায় আপনার Google Cloud প্রোজেক্ট আইডি লিখুন) এবং প্রয়োজনীয় API চালু করুন:
    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. us-central1-এ আপনার (default) Firestore ডেটাবেস প্রোভিশন করুন (এটি আগে থেকে না থাকলে) এবং আপনার 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
    

৩. Firestore ও Gemini 3.8 Flash ব্যবহার করে Node.js অ্যাপ তৈরি করা

এই ধাপে, আপনি ~/studio-support-addon-এ Node.js অ্যাপ্লিকেশন তৈরি করেন। কোড মডিউলার ও পাঠযোগ্য রাখতে, আপনি প্রোজেক্টকে পাঁচটি ফোকাসড ফাইলে সাজান:

  • firestore.js: Cloud Firestore ক্লায়েন্ট (studio_triggers ও support_tickets কালেকশন) ইনিশিয়ালাইজ করে এবং ক্যাশে করা OAuth টোকেন রিফ্রেশ করে।
  • starter.js: নতুন সহায়তা টিকিট কাস্টম স্টার্টার স্টেপ ওয়েব হুক (/onConfigTrigger ও /onManageTrigger) প্রয়োগ করে।
  • action.js: Gemini 3.8 Flash (gemini-3.8-flash) ব্যবহার করে জরুরি অবস্থা শনাক্ত করুন কাস্টম অ্যাকশন স্টেপ ওয়েব হুক (/onConfigUrgency ও /onExecuteUrgency) প্রয়োগ করে, যার মধ্যে কীওয়ার্ড ম্যাচিং ফলব্যাক আছে।
  • portal.js: সহায়তা কুইক পোর্টাল ওয়েব UI (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. express, @google-cloud/firestore ও @google/genai-এর সাহায্যে package.json তৈরি করুন:
    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 Studio যখনই আপনার কোনও ওয়েব হুক এন্ডপয়েন্টকে কল করে, তখন এতে স্বল্পমেয়াদী 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 Studio-তে তার ফ্লোর স্টার্টার হিসেবে নতুন সহায়তা টিকিট বেছে নিলে কল করা হয়। স্টার্টার সম্পর্কে বর্ণনা করে এমন 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 — Gemini 3.8 Flash সহ কাস্টম অ্যাকশন ধাপ (workflowAction)

Google Workspace Studio কাস্টম অ্যাকশন ধাপে প্রয়োজনীয় দুটি ওয়েবহুক এন্ডপয়েন্ট প্রয়োগ করতে action.js তৈরি করুন:

  • onConfigUrgency (POST /onConfigUrgency): textInput উইজেট সহ একটি কনফিগারেশন কার্ড রিটার্ন করে যা hostAppDataSource.workflowDataSource.includeVariables: true সেট করে, এর ফলে ব্যবহারকারীরা স্টার্টার ধাপে আউটপুট করা ticketDescription ভেরিয়েবল বেছে নিতে পারেন।
  • onExecuteUrgency (POST /onExecuteUrgency): req.body.workflow.actionInvocation.inputs থেকে ticketDescription ইনপুট পড়ে, Gemini 3.8 Flash (gemini-3.8-flash) ব্যবহার করে Gemini Enterprise Agent Platform-এ এর ব্যবসায়িক প্রভাব ও জরুরি অবস্থা শ্রেণীবদ্ধ করে, স্ট্রাকচার্ড আউটপুট (responseSchema enum ["High", "Normal"]) দেয়, মডেল কল ব্যর্থ হলে detectKeywordUrgency() (urgent, blocker, critical)-এ ফলব্যাক করে এবং hostAppAction.workflowAction.returnOutputVariablesAction ব্যবহার করে urgencyLevel আউটপুট ভেরিয়েবল রিটার্ন করে।
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 — কুইক পোর্টাল UI ও triggers.fire ডিসপ্যাচার কাজ করে

ওয়েব পোর্টাল পরিবেশন করতে এবং Google Workspace Studio-তে টিকিট জমা দেওয়ার জন্য portal.js তৈরি করুন:

  • renderPortal (GET /): কতগুলি অ্যাক্টিভ Google Workspace Studio ট্রিগার রেজিস্টার করা আছে তা দেখাতে studio_triggers Firestore সংগ্রহ কোয়েরি করে এবং HTML টিকিট জমা দেওয়ার ফর্ম পরিবেশন করে।
  • submitTicket (POST /api/submit-ticket): studio_triggers-এ সব অ্যাক্টিভ ট্রিগার ডকুমেন্টের মাধ্যমে ইটারেট করে, ticketTitle এবং ticketDescription আউটপুট ভেরিয়েবল ও অনন্য requestId সহ Google Workspace Studio triggers.fire এন্ডপয়েন্ট (https://workspacestudio.googleapis.com/v1/triggers/{triggerId}:fire) ইনভোক করে এবং 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 সার্ভার এন্ট্রি পয়েন্ট

আপনার পোর্টাল রুট ও Google Workspace Studio ওয়েব হুক এন্ডপয়েন্টকে একসাথে PORT (Cloud Run-এ ডিফল্ট 8080) লিসেন করা Express HTTP সার্ভারে ওয়্যার করতে index.js তৈরি করুন:

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-এ ডিপ্লয় করেন, Google Workspace অ্যাড-অন ইনভোকার, Firestore এবং Agent Platform রানটাইম অ্যাক্সেসের জন্য IAM অনুমতি দেন, deployment.json ম্যানিফেস্ট তৈরি করেন এবং অ্যাড-অন ডিপ্লয়মেন্ট ইনস্টল করেন।

  1. আপনার প্রোজেক্ট ও Firestore কালেকশন এনভায়রনমেন্ট ভেরিয়েবল সহ Cloud Run-এ support-extension পরিষেবা ডিপ্লয় করুন:
    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 যাতে আপনার ওয়েবহুক এন্ডপয়েন্ট কল করতে পারে সেই জন্য Google Workspace অ্যাড-অন পরিষেবা অ্যাকাউন্টে Cloud Run পরিষেবার roles/run.invoker অ্যাক্সেস দিন:
    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 রানটাইম পরিষেবা অ্যাকাউন্টে roles/datastore.user (studio_triggers ও support_tickets Firestore সংগ্রহ রিড ও রাইট করার জন্য) এবং roles/aiplatform.user (Gemini Enterprise Agent Platform-এ Gemini-কে কল করার জন্য) গ্র্যান্ট করুন:
    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 URL রিট্রিভ করুন এবং 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}"
    

৫. Google Workspace Studio-তে আপনার কাস্টম ধাপ পরীক্ষা করা

  1. Google Workspace Studio খুলুন (আগে থেকেই খোলা থাকলে ব্রাউজার ট্যাব রিফ্রেশ করুন) এবং নতুন ফ্লো বিকল্পে ক্লিক করুন।
  2. স্টার্টার বেছে নিন > এক্সটেনশন সংক্রান্ত সহায়তা > সহায়তা সংক্রান্ত নতুন টিকিট বিকল্পে ক্লিক করুন।
  3. ধাপ যোগ করুন (+) > সহায়ক এক্সটেনশন > জরুরি অবস্থা শনাক্ত করুন বিকল্পে ক্লিক করুন। সোর্সের বিবরণ ফিল্ডে, + ভেরিয়েবল বিকল্পে ক্লিক করে টিকিটের বিবরণ বিকল্প বেছে নিন।
  4. ধাপ যোগ করুন (+) বিকল্পে ক্লিক করুন এবং চ্যাট > চ্যাটে আমাকে বিজ্ঞপ্তি দাও ধাপ যোগ করুন, যার মধ্যে মেসেজে টিকিটের নাম, জরুরি অবস্থার লেভেল এবং টিকিটের বিবরণ ভেরিয়েবল অন্তর্ভুক্ত থাকে।
  5. উপরের ডানদিকের কোণে ফ্লো চালু করুন বিকল্প টগল করুন। Google Workspace Studio সাথে সাথেই Cloud Run-এ আপনার /onManageTrigger এন্ডপয়েন্টে কল করে, যা Firestore-এর studio_triggers কালেকশনে অ্যাক্টিভ ট্রিগার সাবস্ক্রিপশন লেখে।
  6. নতুন ট্যাবে আপনার সহায়তা কুইক পোর্টাল URL (https://support-extension-...run.app) খুলুন, নিশ্চিত করুন যে পোর্টালটি Firestore-এ 1 অ্যাক্টিভ ট্রিগার দেখাচ্ছে এবং 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

৭. অভিনন্দন!

আপনি Gemini Enterprise Agent Platform-এ Firestore, Cloud Run ও Gemini 3.8 Flash ব্যবহার করে Google Workspace Studio-এর জন্য Node.js HTTP এক্সটেনশন তৈরি, ডিপ্লয় ও পরীক্ষা করেছেন!

আমরা কী কী কভার করেছি

  • workflowTrigger ও workflowAction এলিমেন্ট সহ Google Workspace Studio HTTP অ্যাড-অন ম্যানিফেস্ট (deployment.json) কনফিগার করা।
  • studio_triggers Firestore কালেকশনে Google Workspace Studio workflowTrigger সাবস্ক্রিপশন সেভ করা এবং support_tickets-এ পোর্টাল সাবমিশন লগ করা যাতে Cloud Run স্কেলিং ও রিডিপ্লয়মেন্টের পরেও ট্রিগার কাজ করে।
  • Gemini Enterprise Agent Platform-এ Gemini 3.8 Flash (gemini-3.8-flash) স্ট্রাকচার্ড আউটপুট সহ workflowAction স্টেপ তৈরি করা যা সহায়তা টিকিটকে শ্রেণীবদ্ধ করে, এর সাথে কীওয়ার্ড ম্যাচিং ফলব্যাকও আছে।

পরবর্তী ধাপ