יצירת שלבים מותאמים אישית של פעולות ושלבים מותאמים אישית של Workspace Studio Starter

1. מבוא

בשיעור ה-Codelab הזה תבנו פורטל תמיכה מהיר אינטראקטיבי שמתקשר ישירות עם Google Workspace Studio. תלמדו איך ליצור שלב התחלתי מותאם אישית ושלב פעולה מותאם אישית שאפשר לבדוק באופן מיידי בלי להסתמך על תשתית של צד שלישי.

נשתמש ב-Google Apps Script כדי ליצור:

  • אפליקציית אינטרנט עם טופס אלגנטי שמאפשר למשתמשים לשלוח כרטיסי תמיכה באופן ידני.
  • Custom Starter שתופס את הטפסים שנשלחים מאפליקציית האינטרנט ומפעיל רצף פעולות ב-Workspace Studio.
  • שלב בהתאמה אישית שמנתח את התיאור של הכרטיס הנכנס כדי לזהות את רמות הדחיפות ("גבוהה" או "רגילה").

מה תלמדו

  • איך מגדירים את קובץ המניפסט של appsscript.json.
  • איך יוצרים את notifyUri במהלך מחזור החיים של ההרשמה למהדורת Starter.
  • איך מפעילים באופן אוטומטי תהליך ב-Workspace Studio מקוד חיצוני באמצעות UrlFetchApp.
  • איך ליצור משתני פלט בשלבים מותאמים אישית כדי להעביר נתונים בהמשך.

דרישות מוקדמות

  • חשבון Google Workspace שבו מופעל Google Workspace Studio.
  • ההגדרה Allow unpublished (test) custom steps מופעלת במסוף Admin של הדומיין (בקטע אפליקציות > Google Workspace > Workspace Studio > הגדרות של שלבים בהתאמה אישית).
  • היכרות עם Google Apps Script.

2. הגדרת פרויקט Apps Script

קודם ניצור פרויקט חדש ב-Apps Script כדי לאחסן את הקוד:

  1. עוברים אל script.google.com ולוחצים על New project (פרויקט חדש).
  2. נותנים לפרויקט את השם Support Quick Portal.
  3. בסרגל הצד הימני, לוחצים על הגדרות הפרויקט הגדרות הפרויקט.
  4. מסמנים את התיבה הצגת קובץ המניפסט appsscript.json בעורך.
  5. חוזרים לתצוגה עריכה Editor.

כברירת מחדל, פרויקטים ב-Apps Script משויכים לפרויקט מוסתר ב-Google Cloud. כדי להפעיל את Google Workspace Studio API ולמנוע שגיאות PERMISSION_DENIED, צריך להעביר את הסקריפט לפרויקט בענן רגיל, להפעיל את ה-API ולהגדיר את פלטפורמת Google Auth בצורה נכונה.

  1. בוחרים פרויקט או יוצרים פרויקט: נכנסים אל מסוף Google Cloud ומשתמשים בתפריט הנפתח של הפרויקטים בחלק העליון של הדף כדי לבחור פרויקט קיים או ליצור פרויקט חדש.
  2. מפעילים את ה-API: אחרי שהפרויקט שבחרתם פעיל בסרגל העליון, פותחים את תהליך הפעלת ה-API ולוחצים על הבא ועל הפעלה עבור Workspace Studio API.
  3. הגדרת מיתוג OAuth: פותחים את דף המיתוג של פלטפורמת האימות של Google.
    1. אם מתבקשים, לוחצים על שנתחיל?. (אם כבר הגדרתם את האפשרות הזו, דלגו לשלב 4).
    2. בקטע App Information (פרטי האפליקציה), מזינים שם לאפליקציה וכתובת אימייל לתמיכה במשתמשים. לוחצים על Next.
    3. בקטע Audience, לוחצים על Internal (או על External אם Internal לא זמין) ואז על Next.
    4. ממלאים את הפרטים ליצירת קשר, מסכימים למדיניות בנושא נתונים ולוחצים על יצירה.
  4. הגדרת גישה לנתונים: פותחים את הדף 'גישה לנתונים' ולוחצים על הוספה או הסרה של היקפי הרשאות.
    1. בקטע הוספת היקפי חשיפה באופן ידני, מדביקים את https://www.googleapis.com/auth/workspace.studio.trigger.
    2. לוחצים על הוספה לטבלה, ואז על עדכון, ולבסוף על שמירה בתחתית הדף.
  5. עוברים אל IAM & Admin > Settings (או אל לוח הבקרה של Cloud Console) ומעתיקים את מספר הפרויקט.
  6. חוזרים לפרויקט Apps Script ולוחצים על הגדרות הפרויקט Project Settings (הגדרות הפרויקט) בסרגל הצד הימני.
  7. בקטע פרויקט ב-Google Cloud Platform ‏ (GCP), לוחצים על שינוי פרויקט.
  8. מזינים את מספר הפרויקט ב-GCP שהעתקתם ולוחצים על הגדרת הפרויקט.

הגדרת המניפסט

פותחים את הקובץ appsscript.json שמופיע. מחליפים את התוכן שלו בקוד שבהמשך. במניפסט הזה מפורטים היקפי ההרשאות הנדרשים של OAuth, ומוגדרים טריגר לפעולה כ-workflowTrigger והפעולה כ-workflowAction בקטע studio.flows.workflowElements.

{
  "timeZone": "America/Los_Angeles",
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8",
  "webapp": {
     "executeAs": "USER_ACCESSING",
     "access": "MYSELF"
  },
  "oauthScopes": [
    "https://www.googleapis.com/auth/script.external_request",
    "https://www.googleapis.com/auth/workspace.studio.trigger",
    "https://www.googleapis.com/auth/script.storage",
    "https://www.googleapis.com/auth/script.locale"
  ],
  "urlFetchWhitelist": [
    "https://workspacestudio.googleapis.com/"
  ],
  "addOns": {
    "common": {
      "name": "Support Extension",
      "logoUrl": "https://www.gstatic.com/images/branding/productlogos/buganizer/v1/192px.svg",
      "useLocaleFromApp": true
    },
    "studio": {
      "flows": {
        "workflowElements": [
          {
            "id": "supportTicketTrigger",
            "state": "ACTIVE",
            "name": "New Support Ticket",
            "description": "Fires when someone submits a ticket via the Web App.",
            "workflowTrigger": {
              "inputs": [],
              "outputs": [
                {
                  "id": "ticketTitle",
                  "description": "The title of the ticket",
                  "cardinality": "SINGLE",
                  "dataType": { "basicType": "STRING" }
                },
                {
                  "id": "ticketDescription",
                  "description": "The full description of the issue",
                  "cardinality": "SINGLE",
                  "dataType": { "basicType": "STRING" }
                }
              ],
              "onConfigFunction": "onConfigTrigger",
              "onManageFunction": "onManageTrigger"
            }
          },
          {
            "id": "urgencyDetectorStep",
            "state": "ACTIVE",
            "name": "Detect Urgency",
            "description": "Scans the ticket description to detect High or Normal urgency.",
            "workflowAction": {
              "inputs": [
                {
                  "id": "ticketDescription",
                  "description": "The ticket description",
                  "cardinality": "SINGLE",
                  "dataType": { "basicType": "STRING" }
                }
              ],
              "outputs": [
                {
                  "id": "urgencyLevel",
                  "description": "The urgency High/Normal",
                  "cardinality": "SINGLE",
                  "dataType": { "basicType": "STRING" }
                }
              ],
              "onConfigFunction": "onConfigUrgency",
              "onExecuteFunction": "onExecuteUrgency"
            }
          }
        ]
      }
    }
  }
}

3. יצירת ממשק משתמש לאפליקציית האינטרנט

כדי לספק דרך ויזואלית שניתן לבדוק להפעלת ה-starter שלנו בלי להשתמש בכלי טרמינל או ב-webhooks של צד שלישי, ניצור אפליקציית אינטרנט של Apps Script.

בכלי לעריכת סקריפטים של Apps Script, לצד קבצים, לוחצים על הוסף קובץ הוספת קובץ ובוחרים באפשרות HTML. נותנים לו את השם index.html (הסיומת .html נוספת באופן אוטומטי).

מדביקים את הקוד הבא של ממשק המשתמש הפשוט:

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <style>
      body { font-family: Arial, sans-serif; padding: 20px; max-width: 500px; margin: auto; }
      label { font-weight: bold; display: block; margin-top: 15px; }
      input, textarea { width: 100%; margin-top: 5px; padding: 10px; box-sizing: border-box; }
      button { background-color: #1a73e8; color: white; border: none; padding: 10px 15px; margin-top: 15px; font-weight: bold; cursor: pointer; border-radius: 4px; }
      button:hover { background-color: #1557b0; }
      #status { margin-top: 15px; color: green; font-weight: bold; }
    </style>
  </head>
  <body>
    <h2>Support Quick Portal</h2>
    <p>Submit your issue below to trigger the Workspace Studio flow.</p>

    <label for="title">Ticket Title</label>
    <input type="text" id="title" placeholder="e.g. Broken monitor">

    <label for="description">Issue Description</label>
    <textarea id="description" rows="4" placeholder="Explain your issue (use words like 'urgent' to test the step!)..."></textarea>

    <button onclick="submitTicket()">Submit Ticket</button>
    <div id="status"></div>

    <script>
      function submitTicket() {
        const title = document.getElementById('title').value;
        const description = document.getElementById('description').value;
        const statusEl = document.getElementById('status');

        if (!title || !description) {
           statusEl.style.color = "red";
           statusEl.innerText = "Please fill out both fields.";
           return;
        }

        statusEl.style.color = "green";
        statusEl.innerText = "Submitting ticket...";

        google.script.run
          .withSuccessHandler(function(response) {
            statusEl.innerText = response;
          })
          .withFailureHandler(function(error) {
            statusEl.style.color = 'red';
            statusEl.innerText = 'Error: ' + error.message;
          })
          .submitTicketToStudio(title, description);
      }
    </script>
  </body>
</html>

4. בניית סקריפטים של בק-אנד

עכשיו נחבר את הכול. כדי שהקוד יהיה קריא, נפריד את אפליקציית האינטרנט, את הלוגיקה של Starter ואת הלוגיקה של Action Step לשלושה קובצי סקריפט שונים.

WebApp.gs

משנים את השם של קובץ Code.gs שמוגדר כברירת מחדל בעורך ל-WebApp.gs, מוחקים את קוד ה-boilerplate‏ myFunction ומעתיקים את הבלוק שלמטה ומדביקים אותו. הקובץ הזה מטפל באירוח של אפליקציית האינטרנט:

/** --- WEB APP HOSTING --- **/

/**
 * Serves the HTML UI when users visit the web app URL.
 */
function doGet() {
  return HtmlService.createHtmlOutputFromFile('index')
      .setTitle('Support Quick Portal')
      .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}

/**
 * Called by the Web App form. It calls the Workspace Studio API.
 */
function submitTicketToStudio(title, description) {
  const props = PropertiesService.getUserProperties();
  const notifyUri = props.getProperty('notifyUri');
  const triggerId = props.getProperty('triggerId');

  if (!notifyUri || !triggerId) {
    throw new Error('Trigger URL missing! Make sure the flow is enabled in Workspace Studio.');
  }

  // Use ScriptApp.getOAuthToken() (requires the workspace.studio.trigger scope in manifest)
  const token = ScriptApp.getOAuthToken();
  const requestId = Utilities.getUuid();

  // Package our outputs mapped in appsscript.json
  const payload = {
    "name": "triggers/" + triggerId,
    "outputs": {
      "ticketTitle": { "stringValues": [title] },
      "ticketDescription": { "stringValues": [description] }
    },
    "requestId": requestId
  };

  const options = {
    "method": "POST",
    "contentType": "application/json",
    "headers": { "Authorization": "Bearer " + token },
    "payload": JSON.stringify(payload),
    "muteHttpExceptions": true
  };

  // Fire!
  const response = UrlFetchApp.fetch(notifyUri, options);
  if (response.getResponseCode() !== 200) {
    console.error(response.getContentText());
    throw new Error('Workspace Studio API Error: ' + response.getResponseCode());
  }
  return 'Ticket successfully routed to Workspace Studio!';
}

Starter.gs

לאחר מכן, מעבירים את העכבר מעל קבצים, לוחצים על הוסף קובץ הוספת קובץ, בוחרים באפשרות סקריפט ונותנים לו את השם Starter.gs. מעתיקים את הבלוק שלמטה ומדביקים אותו. הקובץ הזה מטפל בהגדרת Starter ובניהול מחזור החיים של Workspace Studio:

/** --- STARTER LOGIC --- **/

/**
 * Workspace Studio calls this to display the config UI when the user adds the starter.
 * We just return a static message since there are no properties to configure.
 */
function onConfigTrigger() {
  const section = CardService.newCardSection()
      .setHeader("Configure Web App Starter")
      .addWidget(CardService.newTextParagraph().setText("Ready to go! Once you enable this flow, deploy your Apps Script project as a Web App to submit tickets."));

  return CardService.newCardBuilder().addSection(section).build();
}

/**
 * Workspace Studio posts data here to manage the lifecycle (enable/disable the trigger).
 * Critically, we must construct or save the `notifyUri` and `triggerId` to call it later.
 */
function onManageTrigger(event) {
  const triggerCreation = event.workflow.triggerCreation;
  const triggerDeletion = event.workflow.triggerDeletion;

  const props = PropertiesService.getUserProperties();

  if (triggerCreation) {
    // Save the unique URL and ID when the flow is enabled
    // Note: notifyUri might be omitted, so fallback to constructing it dynamically
    const notifyUri = triggerCreation.notifyUri || `https://workspacestudio.googleapis.com/v1/triggers/${triggerCreation.triggerId}:fire`;
    props.setProperty('notifyUri', notifyUri);
    props.setProperty('triggerId', triggerCreation.triggerId);
  } else if (triggerDeletion) {
    // Cleanup if the user deletes the flow
    props.deleteProperty('notifyUri');
    props.deleteProperty('triggerId');
  }
}

Action.gs

לאחר מכן, מעבירים את העכבר מעל קבצים, לוחצים על הוסף קובץ הוספת קובץ, בוחרים באפשרות סקריפט ונותנים לו את השם Action.gs. מעתיקים את הלוגיקה של שלב הדחיפות שבהמשך ומדביקים אותה:

/** --- ACTION STEP LOGIC --- **/

/**
 * Workspace Studio configuration card for the Step.
 * We prompt the user to bind a variable to our "ticketDescription" input.
 */
function onConfigUrgency() {
  const input = CardService.newTextInput()
      .setFieldName("ticketDescription")
      .setTitle("Source Description")
      .setHint('Select the description variable outputted by the starter')
      .setHostAppDataSource(
        CardService.newHostAppDataSource().setWorkflowDataSource(CardService.newWorkflowDataSource().setIncludeVariables(true))
      );

  const section = CardService.newCardSection()
      .setHeader("Urgency Detector Step")
      .addWidget(input);

  return CardService.newCardBuilder().addSection(section).build();
}

/**
 * The synchronous logic executed during the Step.
 */
function onExecuteUrgency(event) {
  // Read mapped inputs
  let description = event.workflow.actionInvocation.inputs["ticketDescription"].stringValues[0];
  if (!description) description = "";

  const urgentKeywords = ['urgent', 'crash', 'broken', 'down', 'fire'];
  let urgencyLevel = 'Normal';

  if (urgentKeywords.some(keyword => description.toLowerCase().includes(keyword))) {
    urgencyLevel = 'High';
  }

  // Package outputs correctly
  const variableDataMap = {
    "urgencyLevel": AddOnsResponseService.newVariableData().addStringValue(urgencyLevel)
  };

  return outputVariables(variableDataMap);
}

/**
 * Helper strictly enforcing the expected Step object return type.
 */
function outputVariables(variableDataMap) {
  const workflowAction = AddOnsResponseService.newReturnOutputVariablesAction()
      .setVariableDataMap(variableDataMap);
  const hostAppAction = AddOnsResponseService.newHostAppAction()
      .setWorkflowAction(workflowAction);
  return AddOnsResponseService.newRenderActionBuilder()
      .setHostAppAction(hostAppAction)
      .build();
}

5. פריסה ובדיקה

אחרי שכתבנו את הקוד, נבצע פריסה של הפרויקט ונגדיר אותו.

שלב 1: מתן הרשאה מחדש לסקריפט

מכיוון שקישרתם פרויקט חדש רגיל ב-Google Cloud, אתם צריכים לאשר במפורש את היקפי ההרשאות של הסקריפט (כמו script.external_request שמשמש את UrlFetchApp) בפרויקט החדש.

  1. בכלי לעריכת Apps Script, בוחרים באפשרות WebApp.gs בקטע קבצים, ואז בוחרים בפונקציה doGet מהתפריט הנפתח בסרגל הכלים העליון.
  2. לוחצים על Run.
  3. תוצג הנחיה 'נדרשת הרשאה'. לוחצים על בדיקת ההרשאות.
  4. בוחרים את החשבון ומאשרים את הסקריפט. אם מוצגת אזהרה לגבי אפליקציה לא מאומתת, לוחצים על אפשרויות מתקדמות -> מעבר לפורטל המהיר של התמיכה כדי לעקוף את האזהרה.
  5. מחכים עד שיופיע Execution completed בExecution log. המשמעות היא שהתסריט קיבל הרשאה מלאה.

שלב 2: בדיקת התוסף ואפליקציית האינטרנט

מכיוון ש-appsscript.json מגדיר גם תוסף וגם אפליקציית אינטרנט, אפשר להגדיר את שניהם בו-זמנית.

  1. בפינה השמאלית העליונה של Apps Script, לוחצים על Deploy > Test deployments (פריסה > בדיקת פריסות).
  2. לצד בחירת סוג, לוחצים על הפעלת סוגי פריסה הפעלת סוגי פריסה, מוודאים שהתיבה תוספים ל-Google Workspace מסומנת ולוחצים על התקנה.
  3. לוחצים שוב על הפעלת סוגי פריסה Enable deployment types, מוודאים שתיבת הסימון Web app מסומנת ומעתיקים את URL (הוא יסתיים ב-/dev). זו כתובת ה-URL של אפליקציית האינטרנט Support Quick Portal שיצרתם.
  4. לוחצים על סיום. יכול להיות שתצטרכו לרענן את הדפדפן כדי ש-Workspace Studio ירשום את התוסף.

שלב 3: הגדרת התהליך ב-Studio

  1. פותחים את Google Workspace Studio בכתובת studio.workspace.google.com.
  2. בצד שמאל, לוחצים על הוספת כלי הוספת כלי ובוחרים באפשרות Flow.
  3. כדי להגדיר את חבילת Starter, לוחצים על Choose a starter (בחירת חבילת Starter), בוחרים בSupport Extension (תוסף תמיכה) שהתקנתם > New Support Ticket (כרטיס תמיכה חדש). אם מוצגת ההודעה 'נדרשת הרשאה', לוחצים על מתן הרשאה כדי לאפשר לתוסף לגשת לנתונים שלכם.
  4. בקטע Actions (פעולות), לוחצים על Choose a step (בחירת שלב), גוללים אל Support Extension (תוסף תמיכה) ובוחרים בשלב Detect Urgency (זיהוי דחיפות).
  5. בקטע תיאור המקור, לוחצים על הוספה משתנים ובוחרים באפשרות שלב 1: כרטיס תמיכה חדש > התיאור המלא של הבעיה (ticketDescription).
  6. לוחצים שוב על בחירת שלב ובוחרים שלב מובנה כמו Gmail > שליחת אימייל או Chat > שלח לי הודעה ב-Chat.
    • אם בחרתם ב-Gmail:
      • אל: מזינים את כתובת האימייל שלכם.
      • נושא: העדיפות של כרטיס ticketTitle היא urgencyLevel
      • הודעה: ticketDescription
    • אם בחרתם באפשרות צ'אט:
      • הודעה: העדיפות של כרטיס ticketTitle היא urgencyLevel. ticketDescription
  7. מפעילים את תהליך העבודה. אם מופיעה בקשה לשנות את השם של התהליך, מזינים שם כמו Support Ticket Router ושומרים. הפעולה הזו מפעילה את מחזור החיים onManageTrigger ושומרת את notifyUri.

שלב 4: הרצת הסימולציה

  1. פותחים את כתובת ה-URL של אפליקציית האינטרנט שהעתקתם קודם בשלב 2 בדף הזה בכרטיסייה חדשה בדפדפן (היא מסתיימת ב-‎ /dev).
  2. ממלאים את הטופס. חשוב לכלול מילת מפתח להפעלת התראה (כמו "דחוף" או "קריסה") בתיאור.
  3. לוחצים על שליחה. מחכים להודעה 'כרטיס התמיכה הועבר בהצלחה'.
  4. כדאי לבדוק את יומן הפעילות ב-Workspace Studio, את תיבת הדואר הנכנס ב-Gmail או את Google Chat (בהתאם לפעולה שבחרתם). אמורה להופיע הודעה או אימייל עם התיוג "דחוף" שמשולבים באופן מקורי באמצעות רכיבי Apps Script מותאמים אישית.

6. ניקוי

כדי למנוע עומס בסביבת העבודה ובחשבון Google Cloud, אפשר למחוק את המשאבים שיצרתם במהלך ה-codelab הזה.

  1. מחיקת רצף פעולות ב-Workspace Studio:
    • פותחים את studio.workspace.google.com.
    • מחפשים את התהליך Support Ticket Router.
    • לוחצים על סמל האפשרויות הנוספות (3 נקודות) שליד השם ובוחרים באפשרות מחיקה.
  2. מחיקת פרויקט Apps Script:
    • עוברים אל script.google.com.
    • מאתרים את הפרויקט Support Quick Portal.
    • לוחצים על תפריט שלוש הנקודות ובוחרים באפשרות הסרה.
  3. סוגרים את הפרויקט ב-Google Cloud:
    • עוברים אל מסוף Google Cloud.
    • מוודאים שהפרויקט החדש נבחר בתפריט הנפתח העליון.
    • עוברים אל IAM ואדמין > הגדרות ולוחצים על כיבוי.

7. סיכום

יצרתם בהצלחה מערכת אקולוגית מלאה של תוספים מותאמים אישית ל-Google Workspace Studio בלי להסתמך על מינויים חיצוניים של צד שלישי.

הנושאים שנדון בהם:

  • שימוש ב-workflowTrigger לצד onManageFunction כדי לתעד ולטפל באירועים במחזור החיים של Google בצורה נכונה.
  • אימות בקשות לשרת הקצה העורפי באמצעות ScriptApp.getOAuthToken() מול נקודות הקצה של workspacestudio.googleapis.com API.
  • שימוש ב-workflowAction כדי לעבד משתני קלט באופן סינכרוני וליצור פלט במורד הזרם.

עכשיו אפשר להרחיב את העקרונות האלה כדי להתחבר לפלטפורמות SaaS אמיתיות כמו Jira, ‏ Salesforce או Zendesk, על ידי שינוי המקום שממנו מופעלת אפליקציית האינטרנט (או ה-webhook הנכנס).