Créer des étapes Google Workspace Studio personnalisées dans Node.js sur Cloud Run

1. Introduction

Google Workspace Studio vous permet d'automatiser les processus métier dans les applications Google Workspace telles que Gmail, Google Chat, Google Drive et Google Sheets. Lorsque vos workflows doivent démarrer à partir d'une application Web externe ou exécuter une logique personnalisée dans votre propre environnement cloud, vous pouvez étendre Google Workspace Studio avec un module complémentaire Google Workspace HTTP dans Node.js sur Cloud Run :

  1. Étape de démarrage personnalisée (workflowTrigger) : démarre un flux Google Workspace Studio lorsqu'un événement externe se produit (par exemple, lorsqu'un client envoie une demande dans un portail Web interne) en appelant l'API REST triggers.fire de Google Workspace Studio.
  2. Étape d'action personnalisée (workflowAction) : reçoit les variables des étapes précédentes du flux, exécute une logique personnalisée ou une inférence d'IA dans votre service Node.js, puis renvoie les variables de sortie à Google Workspace Studio.

Dans cet atelier de programmation, vous allez créer et déployer une extension HTTP Node.js complète pour Google Workspace Studio sur Cloud Run, utiliser Cloud Firestore pour conserver les abonnements aux déclencheurs de flux actifs et les demandes envoyées dans les révisions de conteneur, et classer l'urgence des demandes dans votre étape d'action personnalisée à l'aide de Gemini 3.8 Flash (gemini-3.8-flash) sur la Gemini Enterprise Agent Platform avec une solution de secours résiliente basée sur la correspondance de mots clés.

Ce que vous allez faire

  • Application Web Support Quick Portal et module complémentaire Google Workspace HTTP créés avec Node.js, Express et Firestore sur Cloud Run.
  • Étape de déclencheur personnalisé Nouvelle demande d'assistance (supportTicketTrigger) qui conserve les abonnements actifs aux flux dans une collection Firestore studio_triggers, enregistre les demandes envoyées dans une collection Firestore support_tickets et se déclenche chaque fois qu'une demande est envoyée dans le portail Web, en transmettant ticketTitle et ticketDescription dans un flux Google Workspace Studio.
  • Étape d'action personnalisée Détecter l'urgence (urgencyDetectorStep) qui classe l'urgence des demandes à l'aide de Gemini 3.8 Flash (gemini-3.8-flash) sur Gemini Enterprise Agent Platform (aiplatform.googleapis.com), avec un retour automatique à la correspondance de mots clés (urgent, blocker, critical).

Points abordés

  • Comment configurer un fichier manifeste de déploiement de module complémentaire Google Workspace HTTP (deployment.json) avec les éléments workflowTrigger et workflowAction.
  • Découvrez comment conserver les abonnements aux déclencheurs Google Workspace Studio (triggerId, notifyUri et userOAuthToken) dans une collection Firestore studio_triggers et déclencher des flux à l'aide de l'API REST triggers.fire.
  • Découvrez comment créer une étape personnalisée workflowAction en Node.js à l'aide de la sortie structurée Gemini 3.8 Flash (gemini-3.8-flash) sur Gemini Enterprise Agent Platform avec un mot clé de secours.

Prérequis

  • Compte Google Workspace ayant accès à Google Workspace Studio.
  • Un projet Google Cloud avec la facturation activée, créé dans la même organisation Google Workspace que votre compte utilisateur, afin que vous puissiez installer des déploiements de modules complémentaires non publiés pour les tester.

2. Configurer votre projet Google Cloud et Firestore

Dans cette étape, vous allez configurer l'écran de consentement OAuth de votre projet, activer les API Google Cloud et Google Workspace requises dans Cloud Shell, provisionner votre base de données (default) Firestore et initialiser votre compte de service d'autorisation de module complémentaire.

  1. Ouvrez la console Google Cloud, sélectionnez votre projet Google Cloud, puis cliquez sur l'icône Activer Cloud Shell dans la barre de navigation supérieure.
  2. Configurez l'écran de consentement OAuth pour que Google Workspace puisse provisionner un client OAuth pour votre module complémentaire :
    1. Ouvrez la page de branding de Google Auth Platform.
    2. Si vous y êtes invité, cliquez sur Commencer. (Si votre écran de consentement est déjà configuré, passez à l'étape 3.)
    3. Sous Informations sur l'application, saisissez Support Extension pour le Nom de l'application, sélectionnez votre Adresse e-mail d'assistance utilisateur, puis cliquez sur Suivant.
    4. Sous Audience, sélectionnez Interne, puis cliquez sur Suivant.
    5. Sous Coordonnées, saisissez votre adresse e-mail, puis cliquez sur Suivant.
    6. Sous Terminer, acceptez le Règlement sur les données utilisateur dans les services d'API Google, cliquez sur Continuer, puis sur Créer.
  3. Dans Cloud Shell, définissez votre projet actif (remplacez YOUR_PROJECT_ID par l'ID de votre projet Google Cloud) et activez les API requises :
    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. Provisionnez votre base de données (default) Firestore dans us-central1 (si elle n'existe pas déjà) et initialisez votre compte de service Google Workspace Add-ons :
    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. Créer l'application Node.js avec Firestore et Gemini 3.8 Flash

Dans cette étape, vous allez créer l'application Node.js dans ~/studio-support-addon. Pour que le code reste modulaire et lisible, vous organisez le projet en cinq fichiers ciblés :

  • firestore.js : initialise le client Cloud Firestore (collections studio_triggers et support_tickets) et actualise les jetons OAuth mis en cache.
  • starter.js : implémente les Webhooks d'étape de démarrage personnalisés Nouvelle demande d'assistance (/onConfigTrigger et /onManageTrigger).
  • action.js : implémente les Webhooks de l'étape d'action personnalisée Détecter l'urgence (/onConfigUrgency et /onExecuteUrgency) à l'aide de Gemini 3.8 Flash (gemini-3.8-flash) avec un remplacement par correspondance de mots clés.
  • portal.js : diffuse l'UI Web du portail d'assistance rapide (GET /) et déclenche les déclencheurs Google Workspace Studio actifs (POST /api/submit-ticket).
  • index.js : relie toutes les routes Express et démarre le serveur HTTP.

Initialiser le projet et package.json

  1. Créez un répertoire de projet et accédez-y :
    mkdir -p ~/studio-support-addon && cd ~/studio-support-addon
    
  2. Créez package.json avec express, @google-cloud/firestore et @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 : état Firestore partagé et actualisation du jeton OAuth

Créez firestore.js pour gérer vos deux collections Firestore et actualiser les jetons OAuth utilisateur mis en cache :

  • studio_triggers : stocke les abonnements actifs aux flux Google Workspace Studio (triggerId, notifyUri et userOAuthToken) afin qu'ils survivent aux redémarrages des conteneurs Cloud Run et aux événements de scaling à zéro instance.
  • support_tickets : stocke un enregistrement de chaque demande d'assistance envoyée via le portail.
  • refreshTokenInFirestore(req) : chaque fois que Google Workspace Studio appelle l'un de vos points de terminaison de webhook, il inclut un authorizationEventObject.userOAuthToken à durée de vie limitée. Cet utilitaire met à jour les documents de déclencheur actifs avec le jeton le plus récent afin que les appels triggers.fire en arrière-plan restent autorisés.
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 — Étape de déclencheur personnalisé (workflowTrigger)

Créez starter.js pour implémenter les deux points de terminaison de webhook requis par une étape de démarrage personnalisée Google Workspace Studio :

  • onConfigTrigger (POST /onConfigTrigger) : appelé lorsqu'un utilisateur sélectionne New Support Ticket (Nouvelle demande d'assistance) comme point de départ de son flux dans Google Workspace Studio. Renvoie une action de navigation pushCard décrivant le starter.
  • onManageTrigger (POST /onManageTrigger) : appelé lorsqu'un utilisateur active (req.body.workflow.triggerCreation) ou désactive / supprime (req.body.workflow.triggerDeletion) un flux. Enregistre ou supprime le document d'abonnement dans la collection Firestore studio_triggers.
cat << 'EOF' > starter.js
import { triggersCollection, refreshTokenInFirestore } from './firestore.js';

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

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

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

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

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

action.js — Étape d'action personnalisée (workflowAction) avec Gemini 3.8 Flash

Créez action.js pour implémenter les deux points de terminaison de webhook requis par une étape d'action personnalisée Google Workspace Studio :

  • onConfigUrgency (POST /onConfigUrgency) : renvoie une fiche de configuration avec un widget textInput qui définit hostAppDataSource.workflowDataSource.includeVariables: true, ce qui permet aux utilisateurs de sélectionner la variable ticketDescription générée par l'étape de démarrage.
  • onExecuteUrgency (POST /onExecuteUrgency) : lit l'entrée ticketDescription de req.body.workflow.actionInvocation.inputs, classe son impact commercial et son urgence à l'aide de Gemini 3.8 Flash (gemini-3.8-flash) sur la Gemini Enterprise Agent Platform avec sortie structurée (énumération responseSchema ["High", "Normal"]), revient à detectKeywordUrgency() (urgent, blocker, critical) si l'appel de modèle échoue et renvoie la variable de sortie urgencyLevel à l'aide de 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 : UI du portail d'assistance rapide et répartiteur triggers.fire

Créez portal.js pour diffuser le portail Web et envoyer les demandes d'assistance à Google Workspace Studio :

  • renderPortal (GET /) : interroge la collection Firestore studio_triggers pour afficher le nombre de déclencheurs Google Workspace Studio actifs enregistrés et fournit le formulaire HTML d'envoi de la demande.
  • submitTicket (POST /api/submit-ticket) : parcourt tous les documents de déclencheur actifs dans studio_triggers, appelle le point de terminaison triggers.fire Google Workspace Studio (https://workspacestudio.googleapis.com/v1/triggers/{triggerId}:fire) avec les variables de sortie ticketTitle et ticketDescription et un requestId unique, puis enregistre la demande dans la collection Firestore support_tickets.
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 : point d'entrée du serveur Express

Créez index.js pour relier les routes de votre portail et les points de terminaison de webhook Google Workspace Studio à un serveur HTTP Express écoutant sur PORT (8080 par défaut sur 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. Déployer sur Cloud Run et installer le module complémentaire

Dans cette étape, vous allez déployer le service Node.js sur Cloud Run, accorder des autorisations IAM pour l'invocateur de modules complémentaires Google Workspace et l'accès à l'exécution Firestore et de la plate-forme d'agent, générer le fichier manifeste deployment.json et installer le déploiement du module complémentaire.

  1. Déployez le service support-extension sur Cloud Run avec les variables d'environnement de votre projet et de votre collection 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. Accordez roles/run.invoker sur le service Cloud Run au compte de service des modules complémentaires Google Workspace afin que Google Workspace Studio puisse appeler vos points de terminaison de webhook :
    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. Accordez roles/datastore.user (pour lire et écrire les collections Firestore studio_triggers et support_tickets) et roles/aiplatform.user (pour appeler Gemini sur Gemini Enterprise Agent Platform) au compte de service d'exécution 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. Récupérez l'URL Cloud Run déployée et générez le fichier manifeste deployment.json définissant les éléments supportTicketTrigger (workflowTrigger) et 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. Créez (ou remplacez) et installez le déploiement du module complémentaire Google Workspace pour votre compte, puis imprimez l'URL du portail d'assistance rapide :
    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. Tester vos étapes personnalisées dans Google Workspace Studio

  1. Ouvrez Google Workspace Studio (actualisez l'onglet du navigateur s'il était déjà ouvert), puis cliquez sur Nouveau flux.
  2. Cliquez sur Choisir un déclencheur > Extension d'assistance > Nouvelle demande d'assistance.
  3. Cliquez sur Ajouter une étape (+) > Extension d'assistance > Détecter l'urgence. Dans le champ Description de la source, cliquez sur + Variables et sélectionnez Description de la demande.
  4. Cliquez sur Ajouter une étape (+), puis ajoutez une étape Chat > M'envoyer une notification dans Chat qui inclut les variables Titre de la demande, Niveau d'urgence et Description de la demande dans le message.
  5. En haut à droite, activez le flux. Google Workspace Studio appelle immédiatement votre point de terminaison /onManageTrigger sur Cloud Run, qui écrit l'abonnement au déclencheur actif dans la collection studio_triggers de Firestore.
  6. Ouvrez l'URL du portail d'assistance rapide (https://support-extension-...run.app) dans un nouvel onglet, vérifiez que le portail affiche le déclencheur actif 1 dans Firestore, puis envoyez deux demandes pour tester la classification Gemini 3.8 Flash (gemini-3.8-flash) :
    • Demande à fort impact (Titre de la demande : London office lockout, Description du problème : All 500 employees in the London office are locked out of their workstations 20 minutes before the board presentation.) : même sans mots clés codés en dur comme urgent ou blocker, gemini-3.8-flash classe cette demande comme High.
    • Ticket de routine (Titre du ticket : Conference room adapters, Description du problème : Could we order two extra HDMI adapters for the third-floor conference room next month?) : gemini-3.8-flash classe ce ticket comme étant d'urgence Normal.
  7. Vérifiez dans Google Chat et dans l'onglet Activité de Google Workspace Studio que votre flux s'est exécuté pour les deux tickets avec Urgency Level: High et Urgency Level: Normal.

6. Effectuer un nettoyage

Pour éviter que les ressources déployées soient facturées en permanence sur votre compte Google Cloud, supprimez-les dans 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. Félicitations !

Vous avez créé, déployé et testé une extension HTTP Node.js pour Google Workspace Studio à l'aide de Firestore, Cloud Run et Gemini 3.8 Flash sur la Gemini Enterprise Agent Platform.

Points abordés

  • Configuration d'un fichier manifeste de module complémentaire HTTP Google Workspace Studio (deployment.json) avec les éléments workflowTrigger et workflowAction.
  • Persistance des abonnements Google Workspace Studio workflowTrigger dans la collection Firestore studio_triggers et journalisation des envois de portail dans support_tickets afin que les déclencheurs survivent à la mise à l'échelle et aux redéploiements de Cloud Run.
  • Création d'une étape workflowAction qui classe les demandes d'assistance avec une sortie structurée Gemini 3.8 Flash (gemini-3.8-flash) sur Gemini Enterprise Agent Platform, avec une solution de secours résiliente basée sur la correspondance de mots clés.

Étapes suivantes