מענה לתקריות שימוש ב-Google Chat, ב-Vertex AI וב-Apps Script

במדריך הזה נעשה שימוש בהיקפי הרשאה שמתחילים ב-https://www.googleapis.com/auth/chat.app.*, שזמינים כחלק מ-Developer Preview. כדי להשתמש בהם, צריך לקבל אישור אדמין חד-פעמי לאפליקציית Chat.

במדריך הזה מוסבר איך ליצור אפליקציה ל-Google Chat שתגיב לתקריות בזמן אמת. כשמגיבים לתקרית, האפליקציה יוצרת מרחב משותף ב-Chat ומאכלסת אותו, מסייעת בפתרון התקרית באמצעות הודעות, פקודות קו נטוי ודיאלוגים, ומשתמשת ב-AI כדי לסכם את התגובה לתקרית במסמך ב-Google Docs.

אירוע הוא אירוע שדורש טיפול מיידי של צוות אנשים כדי לפתור אותו. דוגמאות לאירועים:

  • בקשת תמיכה דחופה נוצרת בפלטפורמה לניהול קשרי לקוחות (CRM), וצוותי שירות צריכים לשתף פעולה כדי לפתור אותה.
  • מערכת עוברת למצב אופליין, ומופיעה התראה לקבוצה של מהנדסי Site Reliability‏ (SRE) כדי שיוכלו לעבוד יחד כדי להחזיר אותה למצב אונליין.
  • מתרחשת רעידת אדמה בעוצמה גבוהה, ועובדי החירום צריכים לתאם את התגובה שלהם.

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

איך משתמשים באפליקציית Chat לניהול אירועים:

  • האתר שמתחיל את התקרית.
    איור 1. האתר שבו אפשר לדווח על תקרית.
  • התראה על יצירת המרחב המשותף ב-Chat של התקרית.
    איור 2. הודעה על יצירת המרחב המשותף ב-Chat של התקרית.
  • המרחב המשותף ב-Chat לתגובה לתקרית.
    איור 3. מרחב הצ'אט לתגובה לתקרית.
  • פתרון התקרית באמצעות פקודה של שורת הפקודה.
    איור 4. פתרון התקרית באמצעות פקודה של שורת הפקודה.
  • תיבת הדו-שיח של פתרון התקרית.
    איור 5. תיבת הדו-שיח של פתרון אירוע.
  • מסמך Google Docs ששותף במרחב המשותף, שבו מפורטת פתרון התקרית.
    איור 6. מסמך Google Docs בנושא פתרון התקרית ששותף במרחב המשותף.
  • סיכום האירוע שנוצר על ידי ה-AI במסמך Google Docs.
    איור 7. מסמך Google Docs עם סיכום האירוע ותוצאת הטיפול באמצעות AI.

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

אם אתם צריכים להפעיל בארגון אחת מהדרישות המוקדמות האלה, תוכלו לבקש מהאדמין ב-Google Workspace להפעיל אותה:

  • חשבון Google Workspace בתוכנית Business או Enterprise עם גישה ל-Google Chat.
  • כדי להפעיל את הספרייה (שיתוף אנשי קשר) ב-Google Workspace. אפליקציית ניהול התקרית משתמשת בספרייה כדי לחפש את פרטי הקשר של צוות התגובה לתקרית, כמו שם וכתובת אימייל. צוות התגובה לתקריות חייב להיות מורכב ממשתמשים שיש להם חשבון Google Chat בארגון שלכם ב-Google Workspace.

מטרות

  • פיתוח אפליקציית Chat שמגיבה לתקריות.
  • כדי לעזור למשתמשים להגיב לתקריות, תוכלו:
    • יצירת מרחבים משותפים לתגובה לתקריות.
    • פרסום הודעות עם סיכום של אירועים ותגובות.
    • תמיכה בשיתוף פעולה באמצעות תכונות אינטראקטיביות באפליקציית Chat.
  • סיכום של שיחות ופתרונות בעזרת Vertex AI.

ארכיטקטורה

בתרשים הבא מוצגת הארכיטקטורה של משאבי Google Workspace ו-Google Cloud שבהם משתמשת אפליקציית Google Chat לתגובה לתקריות.

הארכיטקטורה של אפליקציית Google Chat לתגובה לתקריות

הארכיטקטורה מראה איך אפליקציית Google Chat לטיפול בתקריות מעבדת אירוע ותוצאה.

  1. משתמש מתחיל אירוע מאתר חיצוני שמתארח ב-Apps Script.

  2. האתר שולח בקשת HTTP אסינכררונית לאפליקציית Google Chat, שמתארחת גם היא ב-Apps Script.

  3. אפליקציית Google Chat לתגובה לתקריות מעבדת את הבקשה:

    1. שירות Admin SDK של Apps Script מקבל מידע על חברי הצוות, כמו מזהה משתמש וכתובת אימייל.

    2. באמצעות קבוצה של בקשות HTTP ל-Chat API באמצעות השירות Advanced Chat של Apps Script, אפליקציית Google Chat לטיפול בתקריות יוצרת מרחב Chat לתקרית, מאכלסת אותו עם חברי הצוות ושולחת הודעה למרחב.

  4. חברי הצוות מדברים על התקרית במרחב המשותף ב-Chat.

  5. חבר צוות מפעיל פקודה של שורת הפקודה כדי לסמן שהאירוע נפתר.

    1. קריאת HTTP ל-Chat API באמצעות השירות המתקדם של Apps Script ל-Chat מציגה את כל ההודעות במרחב המשותף ב-Chat.

    2. מערכת Vertex AI מקבלת את ההודעות שמפורטות ברשימה ויוצרת סיכום.

    3. השירות DocumentApp ב-Apps Script יוצר מסמך Docs ומוסיף למסמך את הסיכום של Vertex AI.

    4. אפליקציית Google Chat לתגובה לתקריות קוראת ל-Chat API כדי לשלוח הודעה עם קישור למסמך הסיכום ב-Docs.

הכנת הסביבה

בקטע הזה נסביר איך יוצרים ומגדירים פרויקט ב-Google Cloud לאפליקציית Chat.

יצירת פרויקט של Google Cloud

מסוף Google Cloud

  1. במסוף Google Cloud, נכנסים לתפריט > IAM & Admin > Create a Project.

    כניסה לדף Create a Project

  2. בשדה Project Name, מזינים שם תיאורי לפרויקט.

    אופציונלי: כדי לערוך את מזהה הפרויקט, לוחצים על Edit. אי אפשר לשנות את מזהה הפרויקט אחרי היצירה שלו, לכן חשוב לבחור מזהה שיעמוד בצרכים שלכם לכל משך חיי הפרויקט.

  3. בשדה Location, לוחצים על Browse כדי להציג מיקומים פוטנציאליים לפרויקט. לאחר מכן לוחצים על בחירה.
  4. לוחצים על יצירה. מסוף Google Cloud מנווט לדף Dashboard והפרויקט נוצר תוך כמה דקות.

CLI של gcloud

ניגשים ל-CLI של Google Cloud (gcloud) באחת מסביבות הפיתוח הבאות:

  • Cloud Shell: כדי להשתמש בטרמינל אונליין שבו כבר מוגדר ה-CLI של gcloud, צריך להפעיל את Cloud Shell.
    הפעלת Cloud Shell
  • מעטפת מקומית: כדי להשתמש בסביבת פיתוח מקומית צריך להתקין ולהפעיל את ה-CLI של gcloud.
    כדי ליצור פרויקט ב-Cloud, משתמשים בפקודה gcloud projects create:
    gcloud projects create PROJECT_ID
    מחליפים את PROJECT_ID במזהה של הפרויקט שרוצים ליצור.

הפעלת החיוב בפרויקט ב-Cloud

מסוף Google Cloud

  1. נכנסים לדף Billing במסוף Google Cloud. לוחצים על תפריט > חיוב > הפרויקטים שלי.

    כניסה לדף Billing for My Projects

  2. בקטע Select an organization, בוחרים את הארגון שמשויך לפרויקט ב-Google Cloud.
  3. בשורת הפרויקט, פותחים את התפריט Actions (), לוחצים על Change billing ובוחרים את חשבון החיוב ב-Cloud.
  4. לוחצים על Set account.

CLI של gcloud

  1. כדי להציג את החשבונות לחיוב הזמינים, מריצים את הפקודה:
    gcloud billing accounts list
  2. קישור חשבון לחיוב לפרויקט ב-Google Cloud:
    gcloud billing projects link PROJECT_ID --billing-account=BILLING_ACCOUNT_ID

    מחליפים את מה שכתוב בשדות הבאים:

    • PROJECT_ID הוא מזהה הפרויקט של הפרויקט ב-Cloud שרוצים להפעיל בו חיוב.
    • BILLING_ACCOUNT_ID הוא מזהה החשבון לחיוב שרוצים לקשר לפרויקט ב-Google Cloud.

הפעלת ממשקי ה-API

מסוף Google Cloud

  1. במסוף Google Cloud, מפעילים את Google Chat API, את Google Docs API, את Admin SDK API, את Google Workspace Marketplace SDK ואת Vertex AI API.

    הפעלת ממשקי ה-API

  2. מוודאים שאתם מפעילים את ממשקי ה-API בפרויקט Cloud הנכון ולוחצים על הבא.

  3. מוודאים שאתם מפעילים את ממשקי ה-API הנכונים ולוחצים על Enable.

CLI של gcloud

  1. אם צריך, מגדירים את הפרויקט הנוכחי ב-Cloud כפרויקט שיצרתם באמצעות הפקודה gcloud config set project:

    gcloud config set project PROJECT_ID

    מחליפים את PROJECT_ID במזהה הפרויקט של הפרויקט ב-Cloud שיצרתם.

  2. מפעילים את Google Chat API,‏ Google Docs API,‏ Admin SDK API,‏ Google Workspace Marketplace SDK ו-Vertex AI API באמצעות הפקודה gcloud services enable:

    gcloud services enable chat.googleapis.com docs.googleapis.com admin.googleapis.com aiplatform.googleapis.com appsmarket-component.googleapis.com

הגדרת אימות והרשאה

אפליקציית Chat ניגשת ל-Google Chat API באמצעות פרטי הכניסה של אפליקציית Chat. האפליקציה ניגשת ל-Admin SDK API ול-Google Docs API באמצעות פרטי הכניסה של המשתמש.

הגדרת אימות והרשאה של משתמשים

האימות וההרשאה מאפשרים לאפליקציית Chat לגשת למשאבים ב-Google Workspace וב-Google Cloud כדי לעבד תגובה לתקרית. באופן ספציפי, אימות המשתמשים משמש לקריאה ל-Google Docs API ול-Admin SDK API.

במדריך הזה תפרסמו את האפליקציה באופן פנימי בדומיין של Workspace, ולכן מותר להשתמש במידע של placeholder. לפני שמפרסמים את האפליקציה באופן חיצוני, צריך להחליף את פרטי הפלייסהולדר במידע אמיתי במסך ההסכמה.

  1. במסוף Google Cloud, עוברים לתפריט > > Branding.

    כניסה לדף Branding

  2. אם כבר הגדרתם את, תוכלו להגדיר את ההגדרות הבאות של מסך ההסכמה ל-OAuth בקטע מיתוג, קהל וגישה לנתונים. אם מופיעה ההודעה not configured yet, לוחצים על Get Started:

    1. בקטע פרטי האפליקציה, בשדה שם האפליקציה, מקלידים Incident Management.
    2. בקטע User support email, בוחרים את כתובת האימייל שלכם או קבוצת Google מתאימה.
    3. לוחצים על הבא.
    4. בקטע קהל, בוחרים באפשרות פנימי. אם אי אפשר לבחור באפשרות Internal, בוחרים באפשרות External.
    5. לוחצים על הבא.
    6. בקטע פרטים ליצירת קשר, מזינים כתובת אימייל שאליה יישלחו התראות על שינויים בפרויקט.
    7. לוחצים על הבא.
    8. בקטע סיום, קוראים את המדיניות בנושא נתוני משתמשים בשירותי Google API. אם מסכימים, בוחרים באפשרות אני מסכים/ה למדיניות בנושא נתוני משתמשים בשירותי Google API.
    9. לוחצים על המשך.
    10. לוחצים על יצירה.
    11. אם בחרתם באפשרות חיצוני לסוג המשתמש, מוסיפים משתמשי בדיקה:
      1. לוחצים על קהל.
      2. בקטע משתמשי בדיקה, לוחצים על הוספת משתמשים.
      3. מזינים את כתובת האימייל שלכם ואת כתובות האימייל של כל משתמשי הבדיקה המורשים האחרים, ולוחצים על שמירה.
  3. לוחצים על גישה לנתונים > הוספה או הסרה של היקפי גישה. תוצג חלונית עם רשימה של היקפי הרשאה לכל ממשק API שהפעלתם בפרויקט ב-Google Cloud.

    1. בקטע הוספת היקפים באופן ידני, מדביקים את ההיקפים הבאים:

      • https://www.googleapis.com/auth/documents
      • https://www.googleapis.com/auth/admin.directory.user.readonly
      • https://www.googleapis.com/auth/script.external_request
      • https://www.googleapis.com/auth/userinfo.email
      • https://www.googleapis.com/auth/cloud-platform
    2. לוחצים על הוספה לטבלה.

    3. לוחצים על עדכון.

    4. אחרי שבוחרים את ההיקפים הנדרשים לאפליקציה, לוחצים על Save בדף Data Access.

הגדרת אימות והרשאה לאפליקציה

אימות האפליקציה משמש לקריאה ל-Google Chat API.

יצירת חשבון שירות במסוף Google Cloud

כדי ליצור חשבון שירות:

מסוף Google Cloud

  1. במסוף Google Cloud, נכנסים לתפריט > IAM & Admin > Service Accounts.

    לדף Service accounts

  2. לוחצים על Create service account.
  3. ממלאים את פרטי חשבון השירות ולוחצים על יצירה והמשך.
  4. אופציונלי: מקצים תפקידים לחשבון השירות כדי לתת גישה למשאבים של הפרויקט ב-Google Cloud. פרטים נוספים זמינים במאמר הענקה, שינוי וביטול גישה למשאבים.
  5. לוחצים על המשך.
  6. אופציונלי: מזינים משתמשים או קבוצות שיכולים לנהל את חשבון השירות הזה ולבצע בו פעולות. פרטים נוספים זמינים במאמר ניהול התחזות לחשבון שירות.
  7. לוחצים על סיום. כותבים את כתובת האימייל של חשבון השירות.

CLI של gcloud

  1. יוצרים את חשבון השירות:
    gcloud iam service-accounts create SERVICE_ACCOUNT_NAME \
      --display-name="SERVICE_ACCOUNT_NAME"
  2. אופציונלי: מקצים תפקידים לחשבון השירות כדי לתת גישה למשאבים של הפרויקט ב-Google Cloud. פרטים נוספים זמינים במאמר הענקה, שינוי וביטול גישה למשאבים.

חשבון השירות מופיע בדף של חשבון השירות. בשלב הבא יוצרים מפתח פרטי לחשבון השירות.

יצירת מפתח פרטי

כדי ליצור ולהוריד מפתח פרטי לחשבון השירות:

  1. במסוף Google Cloud, נכנסים לתפריט > IAM & Admin > Service Accounts.

    לדף Service accounts

  2. בוחרים את חשבון השירות.
  3. לוחצים על Keys (מפתחות) > Add key (הוספת מפתח) > Create new key (יצירת מפתח חדש).
  4. בוחרים באפשרות JSON ולוחצים על Create.

    זוג המפתחות הציבורי/הפרטי החדש נוצר ומוריד למחשב כקובץ חדש. שומרים את קובץ ה-JSON שהורדתם בתור credentials.json בספריית העבודה. הקובץ הזה הוא העותק היחיד של המפתח. מידע נוסף על אחסון המפתח באופן מאובטח זמין במאמר ניהול מפתחות של חשבונות שירות.

  5. לוחצים על סגירה.

למידע נוסף על חשבונות שירות, תוכלו לקרוא את המאמר חשבונות שירות במסמכי העזרה של IAM ב-Google Cloud.

יצירת לקוח OAuth תואם ל-Google Workspace Marketplace

כדי ליצור לקוח OAuth תואם ל-Google Workspace Marketplace:

  1. במסוף Google Cloud, נכנסים לתפריט > IAM & Admin > Service Accounts.

    לדף Service accounts

  2. לוחצים על חשבון השירות שיצרתם לאפליקציית Chat.

  3. לוחצים על הגדרות מתקדמות.

  4. לוחצים על Create Google Workspace Marketplace-compatible OAuth client.

  5. לוחצים על המשך.

תוצג הודעת אישור על כך שנוצר לקוח OAuth תואם ל-Google Workspace Marketplace.

יצירה ופריסה של אפליקציית Chat

בקטע הבא תעתיקו ותעדכנו פרויקט Apps Script שלם שמכיל את כל קוד האפליקציה הנדרש לאפליקציית Chat, כך שלא תצטרכו להעתיק ולהדביק כל קובץ.

חלק מהפונקציות כוללות קו תחתון בסוף השם שלהן, כמו processSlashCommand_() מ-ChatApp.gs. הקו התחתון מסתיר את הפונקציה מדף האינטרנט של הפעלת התקרית כשהדף פתוח בדפדפן. מידע נוסף זמין במאמר פונקציות פרטיות.

ב-Apps Script יש תמיכה בשני סוגי קבצים: סקריפטים מסוג .gs וקבצים מסוג .html. כדי לעמוד בדרישות התמיכה הזו, ה-JavaScript של האפליקציה בצד הלקוח נכלל בתגי <script /> וה-CSS שלה נכלל בתגי <style /> בתוך קובץ HTML.

לחלופין, אפשר להציג את הפרויקט כולו ב-GitHub.

הצגת הקוד ב-GitHub

הנה סקירה כללית של כל קובץ:

Consts.gs

הגדרת קבועים שמפנים אליהם קובצי קוד אחרים, כולל מזהה הפרויקט ב-Cloud, מזהה המיקום ב-Vertex AI, פרטי הכניסה לאפליקציה לחשבון השירות ומזהה הפקודה עם קו נטוי לסגירת אירוע.

הצגת הקוד Consts.gs

apps-script/incident-response-app-auth/Consts.gs
const PROJECT_ID = 'replace-with-your-project-id';
const CLOSE_INCIDENT_COMMAND_ID = 1;
const APP_CREDENTIALS = 'replace-with-your-app-credentials';
const APP_CREDENTIALS_SCOPES = 'https://www.googleapis.com/auth/chat.bot https://www.googleapis.com/auth/chat.app.memberships https://www.googleapis.com/auth/chat.app.spaces.create';
const VERTEX_AI_LOCATION_ID = 'us-central1';
const MODEL_ID = 'gemini-1.5-flash-002';
ChatApp.gs

האירוע מטפל באירועי אינטראקציה ב-Chat, כולל הודעות, קליקים על כרטיסים, פקודות קו נטוי (slash) ודיאלוגים. תגובה לפקודת החיצים /closeIncident, שבה תיפתח תיבת דו-שיח כדי לאסוף פרטים לגבי פתרון התקרית. קריאת ההודעות במרחב באמצעות קריאה לשיטה spaces.messages.list ב-Chat API. הצגת מזהי משתמשים באמצעות שירות הספרייה של Admin SDK ב-Apps Script.

הצגת הקוד ChatApp.gs

apps-script/incident-response-app-auth/ChatApp.gs
/**
 * Responds to a MESSAGE event in Google Chat.
 *
 * This app only responds to a slash command with the ID 1 ("/closeIncident").
 * It will respond to any other message with a simple "Hello" text message.
 *
 * @param {Object} event the event object from Google Chat
 */
function onMessage(event) {
  if (event.message.slashCommand) {
    return processSlashCommand_(event);
  }
  return { "text": "Hello from Incident Response app!" };
}

/**
 * Responds to a CARD_CLICKED event in Google Chat.
 *
 * This app only responds to one kind of dialog (Close Incident).
 *
 * @param {Object} event the event object from Google Chat
 */
function onCardClick(event) {
  if (event.isDialogEvent) {
    if (event.dialogEventType == 'SUBMIT_DIALOG') {
      return processSubmitDialog_(event);
    }
    return {
      actionResponse: {
        type: "DIALOG",
        dialogAction: {
          actionStatus: "OK"
        }
      }
    };
  }
}

/**
 * Responds to a MESSAGE event with a Slash command in Google Chat.
 *
 * This app only responds to a slash command with the ID 1 ("/closeIncident")
 * by returning a Dialog.
 *
 * @param {Object} event the event object from Google Chat
 */
function processSlashCommand_(event) {
  if (event.message.slashCommand.commandId != CLOSE_INCIDENT_COMMAND_ID) {
    return {
      "text": "Command not recognized. Use the command `/closeIncident` to close the incident managed by this space."
    };
  }
  const sections = [
    {
      header: "Close Incident",
      widgets: [
        {
          textInput: {
            label: "Please describe the incident resolution",
            type: "MULTIPLE_LINE",
            name: "description"
          }
        },
        {
          buttonList: {
            buttons: [
              {
                text: "Close Incident",
                onClick: {
                  action: {
                    function: "closeIncident"
                  }
                }
              }
            ]
          }
        }
      ]
    }
  ];
  return {
    actionResponse: {
      type: "DIALOG",
      dialogAction: {
        dialog: {
          body: {
            sections,
          }
        }
      }
    }
  };
}

/**
 * Responds to a CARD_CLICKED event with a Dialog submission in Google Chat.
 *
 * This app only responds to one kind of dialog (Close Incident).
 * It creates a Doc with a summary of the incident information and posts a message
 * to the space with a link to the Doc.
 *
 * @param {Object} event the event object from Google Chat
 */
function processSubmitDialog_(event) {
  const resolution = event.common.formInputs.description[""].stringInputs.value[0];
  const chatHistory = concatenateAllSpaceMessages_(event.space.name);
  const chatSummary = summarizeChatHistory_(chatHistory);
  const docUrl = createDoc_(event.space.displayName, resolution, chatHistory, chatSummary);
  return {
    actionResponse: {
      type: "NEW_MESSAGE",
    },
    text: `Incident closed with the following resolution: ${resolution}\n\nHere is the automatically generated post-mortem:\n${docUrl}`
  };
}

/**
 * Lists all the messages in the Chat space, then concatenate all of them into
 * a single text containing the full Chat history.
 *
 * For simplicity for this demo, it only fetches the first 100 messages.
 *
 * Messages with slash commands are filtered out, so the returned history will
 * contain only the conversations between users and not app command invocations.
 *
 * @return {string} a text containing all the messages in the space in the format:
 *          Sender's name: Message
 */
function concatenateAllSpaceMessages_(spaceName) {
  // Call Chat API method spaces.messages.list
  const response = Chat.Spaces.Messages.list(spaceName, { 'pageSize': 100 });
  const messages = response.messages;
  // Fetch the display names of the message senders and returns a text
  // concatenating all the messages.
  let userMap = new Map();
  return messages
    .filter(message => message.slashCommand === undefined)
    .map(message => `${getUserDisplayName_(userMap, message.sender.name)}: ${message.text}`)
    .join('\n');
}

/**
 * Obtains the display name of a user by using the Admin Directory API.
 *
 * The fetched display name is cached in the provided map, so we only call the API
 * once per user.
 *
 * If the user does not have a display name, then the full name is used.
 *
 * @param {Map} userMap a map containing the display names previously fetched
 * @param {string} userName the resource name of the user
 * @return {string} the user's display name
 */
function getUserDisplayName_(userMap, userName) {
  if (userMap.has(userName)) {
    return userMap.get(userName);
  }
  let displayName = 'Unknown User';
  try {
    const user = AdminDirectory.Users.get(
      userName.replace("users/", ""),
      { projection: 'BASIC', viewType: 'domain_public' });
    displayName = user.name.displayName ? user.name.displayName : user.name.fullName;
  } catch (e) {
    // Ignore error if the API call fails (for example, because it's an
    // out-of-domain user or Chat app)) and just use 'Unknown User'.
  }
  userMap.set(userName, displayName);
  return displayName;
}
ChatSpaceCreator.gs

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

הצגת הקוד ChatSpaceCreator.gs

apps-script/incident-response-app-auth/ChatSpaceCreator.gs
/**
 * Handles an incident by creating a chat space, adding members, and posting a message.
 * All the actions are done using application credentials.
 *
 * @param {Object} formData - The data submitted by the user. It should contain the fields:
 *                           - title: The display name of the chat space.
 *                           - description: The description of the incident.
 *                           - users: A comma-separated string of user emails to be added to the space.
 * @return {string} The resource name of the new space.
 */
function handleIncident(formData) {
  const users = formData.users.trim().length > 0 ? formData.users.split(',') : [];
  const service = getService_();
  if (!service.hasAccess()) {
    console.error(service.getLastError());
    return;
   }
  const spaceName = createChatSpace_(formData.title, service);
  createHumanMembership_(spaceName, getUserEmail(), service);
  for (const user of users ){
    createHumanMembership_(spaceName, user, service);
  }
  createMessage_(spaceName, formData.description, service);
  return spaceName;
}
/**
 * Creates a chat space with application credentials.
 *
 * @param {string} displayName - The name of the chat space.
 * @param {object} service - The credentials of the service account.
 * @returns {string} The resource name of the new space.
*/
function createChatSpace_(displayName, service) {
  try {
    // For private apps, the alias can be used
    const my_customer_alias = "customers/my_customer";
    // Specify the space to create.
    const space = {
        displayName: displayName,
        spaceType: 'SPACE',                
        customer: my_customer_alias
    };
    // Call Chat API with a service account to create a message.
    const createdSpace = Chat.Spaces.create(
        space,
        {},
        // Authenticate with the service account token.
        {'Authorization': 'Bearer ' + service.getAccessToken()});
    return createdSpace.name;
  } catch (err) {
    // TODO (developer) - Handle exception.
    console.log('Failed to create space with error %s', err.message);
  }
}
/*
 * Creates a chat message with application credentials.
 *
 * @param {string} spaceName - The resource name of the space.
 * @param {string} message - The text to be posted.
 * @param {object} service - The credentials of the service account.
 * @return {string} the resource name of the new space.
 */
function createMessage_(spaceName, message, service) {
  try {
    // Call Chat API with a service account to create a message.
    const result = Chat.Spaces.Messages.create(
        {'text': message},
        spaceName,
        {},
        // Authenticate with the service account token.
        {'Authorization': 'Bearer ' + service.getAccessToken()});

  } catch (err) {
    // TODO (developer) - Handle exception.
    console.log('Failed to create message with error %s', err.message);
  }
}
/**
 * Creates a human membership in a chat space with application credentials.
 *
 * @param {string} spaceName - The resource name of the space.
 * @param {string} email - The email of the user to be added.
 * @param {object} service - The credentials of the service account.
 */
function createHumanMembership_(spaceName, email, service){
  try{
    const membership = {
      member: {
        name: 'users/'+email,
        // User type for the membership
        type: 'HUMAN'
      }
    };
    const result = Chat.Spaces.Members.create(
      membership,
      spaceName,
      {},
      {'Authorization': 'Bearer ' + service.getAccessToken()}
    );
  } catch (err){
    console.log('Failed to create membership with error %s', err.message)
  }

}

 /*
 * Creates a service for the service account.
 * @return {object}  - The credentials of the service account.
 */
function getService_() {
  return OAuth2.createService(APP_CREDENTIALS.client_email)
      .setTokenUrl('https://oauth2.googleapis.com/token')
      .setPrivateKey(APP_CREDENTIALS.private_key)
      .setIssuer(APP_CREDENTIALS.client_email)
      .setSubject(APP_CREDENTIALS.client_email)
      .setScope(APP_CREDENTIALS_SCOPES)
      .setPropertyStore(PropertiesService.getScriptProperties());
}
DocsApi.gs

קריאה ל-Google Docs API כדי ליצור מסמך Google Docs ב-Google Drive של המשתמש, וכתיבה של סיכום של פרטי התקרית שנוצרו ב-VertexAiApi.gs במסמך.

הצגת הקוד DocsApi.gs

apps-script/incident-response-app-auth/DocsApi.gs
/**
 * Creates a Doc in the user's Google Drive and writes a summary of the incident information to it.
 *
 * @param {string} title The title of the incident
 * @param {string} resolution Incident resolution described by the user
 * @param {string} chatHistory The whole Chat history be included in the document
 * @param {string} chatSummary A summary of the Chat conversation to be included in the document
 * @return {string} the URL of the created Doc
 */
function createDoc_(title, resolution, chatHistory, chatSummary) {
  let doc = DocumentApp.create(title);
  let body = doc.getBody();
  body.appendParagraph(`Post-Mortem: ${title}`).setHeading(DocumentApp.ParagraphHeading.TITLE);
  body.appendParagraph("Resolution").setHeading(DocumentApp.ParagraphHeading.HEADING1);
  body.appendParagraph(resolution);
  body.appendParagraph("Summary of the conversation").setHeading(DocumentApp.ParagraphHeading.HEADING1);
  body.appendParagraph(chatSummary);
  body.appendParagraph("Full Chat history").setHeading(DocumentApp.ParagraphHeading.HEADING1);
  body.appendParagraph(chatHistory);
  return doc.getUrl();
}
VertexAiApi.gs

סיכום השיחה במרחב המשותף ב-Chat באמצעות Vertex AI API. הסיכום הזה יפורסם במסמך שנוצר במיוחד ב-DocsAPI.gs.

הצגת הקוד VertexAiApi.gs

apps-script/incident-response-app-auth/VertexAiApi.gs
/**
 * Summarizes a Chat conversation using the Vertex AI text prediction API.
 *
 * @param {string} chatHistory The Chat history that will be summarized.
 * @return {string} The content from the text prediction response.
 */


function summarizeChatHistory_(chatHistory) {

  const API_ENDPOINT = `https://${VERTEX_AI_LOCATION_ID}-aiplatform.googleapis.com/v1/projects/${PROJECT_ID}/locations/${VERTEX_AI_LOCATION_ID}/publishers/google/models/${MODEL_ID}:generateContent`;
  const prompt = "Summarize the following conversation between Engineers resolving an incident"
      + " in a few sentences. Use only the information from the conversation.\n\n" + chatHistory;
  // Get the access token.
  const accessToken = ScriptApp.getOAuthToken();

  const headers = {
    'Authorization': 'Bearer ' + accessToken,
    'Content-Type': 'application/json',
  };
  const payload = {
    'contents': {
      'role': 'user',
      'parts' : [
        {
          'text': prompt
        }
      ]
    }
  }
  const options = {
    'method': 'post',
    'headers': headers,
    'payload': JSON.stringify(payload),
    'muteHttpExceptions': true,
  };
  try {
    const response = UrlFetchApp.fetch(API_ENDPOINT, options);
    const responseCode = response.getResponseCode();
    const responseText = response.getContentText();

    if (responseCode === 200) {
      const jsonResponse = JSON.parse(responseText);
      console.log(jsonResponse)
      if (jsonResponse.candidates && jsonResponse.candidates.length > 0) {
        return jsonResponse.candidates[0].content.parts[0].text; // Access the summarized text
      } else {
        return "No summary found in response.";
      }

    } else {
      console.error("Vertex AI API Error:", responseCode, responseText);
      return `Error: ${responseCode} - ${responseText}`;
    }
  } catch (e) {
    console.error("UrlFetchApp Error:", e);
    return "Error: " + e.toString();
  }
}
WebController.gs

האתר שמציג את האתר להתחלת אירוע.

הצגת הקוד WebController.gs

apps-script/incident-response-app-auth/WebController.gs
/**
 * Serves the web page from Index.html.
 */
function doGet() {
  return HtmlService
    .createTemplateFromFile('Index')
    .evaluate();
}

/**
 * Serves the web content from the specified filename.
 */
function include(filename) {
  return HtmlService
    .createHtmlOutputFromFile(filename)
    .getContent();
}

/**
 * Returns the email address of the user running the script.
 */
function getUserEmail() {
  return Session.getActiveUser().getEmail();
}
Index.html

ה-HTML שמרכיב את האתר לאתחול התקרית.

הצגת הקוד Index.html

apps-script/incident-response-app-auth/Index.html
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <link href='https://fonts.googleapis.com/css?family=Roboto' rel='stylesheet'>
    <?!= include('Stylesheet'); ?>
  </head>
  <body>
    <div class="container">
      <div class="content">
        <h1>Incident Manager</h1>
        <form id="incident-form" onsubmit="handleFormSubmit(this)">
          <div id="form">
            <p>
              <label for="title">Incident title</label><br/>
              <input type="text" name="title" id="title" />
            </p>
            <p>
              <label for="users">Incident responders</label><br/>
              <small>
                Please enter a comma-separated list of email addresses of the users
                that should be added to the space.
                Do not include <?= getUserEmail() ?> as it will be added automatically.
              </small><br/>
              <input type="text" name="users" id="users" />
            </p>
            <p>
              <label for="description">Initial message</label></br>
              <small>This message will be posted after the space is created.</small><br/>
              <textarea name="description" id="description"></textarea>
            </p>
            <p class="text-center">
              <input type="submit" value="CREATE CHAT SPACE" />
            </p>
          </div>
          <div id="output" class="hidden"></div>
          <div id="clear" class="hidden">
            <input type="reset" value="CREATE ANOTHER INCIDENT" onclick="onReset()" />
          </div>
        </form>
      </div>
    </div>
    <?!= include('JavaScript'); ?>
  </body>
</html>
JavaScript.html

הקוד מטפל בהתנהגות של הטפסים, כולל שליחה, שגיאות וניקוי, באתר לצורך הפעלת אירוע. הוא נכלל ב-Index.html באמצעות הפונקציה המותאמת אישית include ב-WebController.gs.

הצגת הקוד JavaScript.html

apps-script/incident-response-app-auth/JavaScript.html
<script>
  var formDiv = document.getElementById('form');
  var outputDiv = document.getElementById('output');
  var clearDiv = document.getElementById('clear');

  function handleFormSubmit(formObject) {
    event.preventDefault();
    outputDiv.innerHTML = 'Please wait while we create the space...';
    hide(formDiv);
    show(outputDiv);
    google.script.run
      .withSuccessHandler(updateOutput)
      .withFailureHandler(onFailure)
      .handleIncident(formObject);
  }

  function updateOutput(response) {
    var spaceId = response.replace('spaces/', '');
    outputDiv.innerHTML =
      '<p>Space created!</p><p><a href="https://mail.google.com/chat/#chat/space/'
        + spaceId
        + '" target="_blank">Open space</a></p>';
    show(outputDiv);
    show(clearDiv);
  }

  function onFailure(error) {
    outputDiv.innerHTML = 'ERROR: ' + error.message;
    outputDiv.classList.add('error');
    show(outputDiv);
    show(clearDiv);
  }

  function onReset() {
    outputDiv.innerHTML = '';
    outputDiv.classList.remove('error');
    show(formDiv);
    hide(outputDiv);
    hide(clearDiv);
  }

  function hide(element) {
    element.classList.add('hidden');
  }

  function show(element) {
    element.classList.remove('hidden');
  }
</script>
Stylesheet.html

קובץ ה-CSS של האתר להפעלת אירוע. הוא נכלל ב-Index.html באמצעות הפונקציה המותאמת אישית include ב-WebController.gs.

הצגת הקוד Stylesheet.html

apps-script/incident-response-app-auth/Stylesheet.html
<style>
  * {
    box-sizing: border-box;
  }
  body {
    font-family: Roboto, Arial, Helvetica, sans-serif;
  }
  div.container {
    display: flex;
    justify-content: center;
    align-items: center;
    position: absolute;
    top: 0; bottom: 0; left: 0; right: 0;
  }
  div.content {
    width: 80%;
    max-width: 1000px;
    padding: 1rem;
    border: 1px solid #999;
    border-radius: 0.25rem;
    box-shadow: 0 2px 2px 0 rgba(66, 66, 66, 0.08), 0 2px 4px 2px rgba(66, 66, 66, 0.16);
  }
  h1 {
    text-align: center;
    padding-bottom: 1rem;
    margin: 0 -1rem 1rem -1rem;
    border-bottom: 1px solid #999;
  }
 #output {
    text-align: center;
    min-height: 250px;
  }
  div#clear {
    text-align: center;
    padding-top: 1rem;
    margin: 1rem -1rem 0 -1rem;
    border-top: 1px solid #999;
  }
  input[type=text], textarea {
    width: 100%;
    padding: 1rem 0.5rem;
    margin: 0.5rem 0;
    border: 0;
    border-bottom: 1px solid #999;
    background-color: #f0f0f0;
  }
  textarea {
    height: 5rem;
  }
  small {
    color: #999;
  }
  input[type=submit], input[type=reset] {
    padding: 1rem;
    border: none;
    background-color: #6200ee;
    color: #fff;
    border-radius: 0.25rem;
    width: 25%;
  }
  .hidden {
    display: none;
  }
  .text-center {
    text-align: center;
  }
  .error {
    color: red;
  }
</style>

איך מוצאים את מספר הפרויקט והמזהה שלו ב-Cloud

  1. נכנסים למסוף Google Cloud לפרויקט ב-Cloud.

    כניסה למסוף Google Cloud

  2. לוחצים על סמל ההגדרות והכלים > הגדרות הפרויקט.

  3. שימו לב לערכים בשדות Project number ו-Project ID. תוכלו להשתמש בהם בקטעים הבאים.

יצירת פרויקט Apps Script

כדי ליצור פרויקט ב-Apps Script ולקשר אותו לפרויקט ב-Cloud:

  1. לוחצים על הלחצן הבא כדי לפתוח את פרויקט Apps Script‏ מענה על אירועים באמצעות Google Chat.
    פתיחת הפרויקט
  2. לוחצים על סקירה כללית.
  3. בדף הסקירה הכללית, לוחצים על הסמל ליצירת עותק יצירת עותק.
  4. נותנים שם להעתקה של פרויקט Apps Script:

    1. לוחצים על עותק של 'מענה לתקריות באמצעות Google Chat'.

    2. בשדה Project title, כותבים Incident Management Chat app.

    3. לוחצים על Rename.

  5. עוברים לקובץ Consts.gs בעותק של הפרויקט ב-Apps Script ומחליפים את YOUR_PROJECT_ID במזהה של הפרויקט ב-Cloud.

הגדרת הפרויקט ב-Cloud של פרויקט Apps Script

  1. בפרויקט Apps Script, לוחצים על הסמל של הגדרות הפרויקט Project Settings.
  2. בקטע פרויקט Google Cloud Platform (GCP), לוחצים על שינוי פרויקט.
  3. בקטע מספר פרויקט GCP, מדביקים את מספר הפרויקט ב-Cloud.
  4. לוחצים על Set project. הפרויקט ב-Cloud והפרויקט ב-Apps Script מקושרים עכשיו.

יצירת פריסה של Apps Script

עכשיו, אחרי שכל הקוד מוכן, פורסים את הפרויקט ב-Apps Script. משתמשים במזהה הפריסה כשמגדירים את אפליקציית Chat ב-Google Cloud.

  1. ב-Apps Script, פותחים את הפרויקט של אפליקציית התגובה לתקרית.

    כניסה לדף Apps Script

  2. לוחצים על פריסה > פריסה חדשה.

  3. אם האפשרויות Add-on ו-Web app עדיין לא מסומנות, לוחצים על סוגים של פריסה הסמל של הגדרות הפרויקט לצד Select type ובוחרים באפשרויות Add-on ו-Web app.

  4. בשדה Description, מזינים תיאור של הגרסה הזו, למשל Complete version of incident management app.

  5. בקטע Execute as, בוחרים באפשרות User accessing the web app.

  6. בקטע למי יש גישה, בוחרים באפשרות כל מי שנמצא בארגון שלך ב-Workspace, כאשר 'הארגון שלך ב-Workspace' הוא השם של הארגון שלכם ב-Google Workspace.

  7. לוחצים על פריסת. Apps Script מדווח על פריסה מוצלחת ומספק מזהה פריסה וכתובת URL לדף האינטרנט של הפעלת התקרית.

  8. שימו לב לכתובת ה-URL של אפליקציית האינטרנט כדי להיכנס אליה מאוחר יותר כשתפתחו אירוע. מעתיקים את מזהה הפריסה. משתמשים במזהה הזה כשמגדירים את אפליקציית Chat במסוף Google Cloud.

  9. לוחצים על סיום.

הגדרת אפליקציית Chat במסוף Google Cloud

בקטע הזה נסביר איך להגדיר את Google Chat API במסוף Google Cloud עם מידע על אפליקציית Chat, כולל מזהה הפריסה שיצרתם זה עתה מהפרויקט ב-Apps Script.

  1. במסוף Google Cloud, לוחצים על תפריט > מוצרים נוספים > Google Workspace > Product Library > Google Chat API > Manage > Configuration.

    כניסה להגדרת Chat API

  2. בשדה App name, מקלידים Incident Management.

  3. בשדה כתובת ה-URL של הדמות, מקלידים https://developers.google.com/chat/images/quickstart-app-avatar.png.

  4. בקטע תיאור, מקלידים Responds to incidents..

  5. מעבירים את המתג הפעלת תכונות אינטראקטיביות למצב מופעל.

  6. בקטע פונקציונליות, בוחרים באפשרויות קבלת הודעות אישיות והצטרפות למרחבים משותפים ולשיחות קבוצתיות.

  7. בקטע הגדרות החיבור, בוחרים באפשרות Apps Script.

  8. בשדה Deployment ID, מדביקים את מזהה הפריסה של Apps Script שהועתק קודם מהפריסה של פרויקט Apps Script.

  9. רושמים פקודת קו נטוי שבה משתמשת אפליקציית Chat המוטמעת במלואה:

    1. בקטע פקודות, לוחצים על הוספת פקודה.

    2. בשדה Command ID, מקלידים 1.

    3. בקטע תיאור, מקלידים את הערך Closes the incident being discussed in the space.

    4. בקטע Command type (סוג הפקודה), בוחרים באפשרות Slash command (פקודה דרך שורת הפקודה).

    5. בשדה Slash command name, מקלידים /closeIncident.

    6. בוחרים באפשרות פתיחת תיבת דו-שיח.

    7. לוחצים על סיום. פקודת ה-slash רשומה ומופיעה ברשימה.

  10. בקטע Visibility (חשיפה), בוחרים באפשרות Make this Chat app available to specific people and groups in Your Workspace Domain (האפליקציה תהיה זמינה לאנשים ולקבוצות ספציפיים בדומיין של Workspace) ומזינים את כתובת האימייל.

  11. בקטע יומנים, בוחרים באפשרות רישום שגיאות ביומן.

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

לקבל אישור מהאדמין

כדי לקבל אישור מהאדמין, צריך להגדיר את אפליקציית Chat ב-Google Workspace Marketplace SDK.

הגדרת אפליקציית Chat ב-Google Workspace Marketplace SDK

כדי להגדיר את אפליקציית Chat ב-Google Workspace Marketplace SDK:

  1. במסוף Google Cloud, נכנסים אל תפריט > APIs & Services > Enabled APIs & services > Google Workspace Marketplace SDK > App Configuration.

    כניסה לדף 'הגדרת אפליקציה'

  2. ממלאים את הדף 'הגדרת האפליקציה'. האופן שבו מגדירים את אפליקציית Chat תלוי בקהל היעד ובגורמים אחרים. במאמר הגדרת האפליקציה ב-Google Workspace Marketplace SDK מוסבר איך למלא את דף ההגדרות של האפליקציה. במסגרת המדריך הזה, מזינים את הפרטים הבאים:

    1. בקטע הרשאות גישה לאפליקציה, בוחרים באפשרות פרטי.
    2. בקטע Installation settings (הגדרות ההתקנה), בוחרים באפשרות Admin install (התקנה על ידי אדמין).
    3. בקטע App integrations (שילובי אפליקציות), בוחרים באפשרות Chat app (אפליקציית Chat).
    4. בקטע היקפי הרשאות OAuth, מזינים את ההיקפים הבאים:
      • https://www.googleapis.com/auth/chat.app.spaces
      • https://www.googleapis.com/auth/chat.app.memberships
    5. בקטע פרטים למפתחים, מזינים את שם המפתח, כתובת ה-URL של אתר המפתח וכתובת האימייל של המפתח.
    6. לוחצים על שמירת הטיוטה.

אחרי שמגדירים את האפליקציה, מעדכנים את דף האפליקציה בחנות:

  1. במסוף Google Cloud, עוברים אל תפריט > APIs & Services > Enabled APIs & services > Google Workspace Marketplace SDK > Store Listing.
  2. בקטע פרטי האפליקציה, בוחרים באפשרות 'פיתוח אינטרנט' כקטגוריה.
  3. בקטע נכסים גרפיים, מעלים סמלי אפליקציות בפורמטים הנדרשים.
  4. בקטע צילומי מסך, מעלים צילום מסך של האפליקציה.
  5. בקטע Support Links (קישורי תמיכה), ממלאים את כתובת ה-URL של התנאים וההגבלות, כתובת ה-URL של מדיניות הפרטיות וכתובת ה-URL של התמיכה.
  6. בקטע Distribution, בוחרים את האזורים שבהם האפליקציה תהיה זמינה.
  7. לוחצים על פרסום.

קבלת אישור מהאדמין

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

בדיקת אפליקציית Chat

כדי לבדוק את אפליקציית Chat לניהול אירועים, יוצרים אירוע מהדף באינטרנט ומוודאים שאפליקציית Chat פועלת כצפוי:

  1. עוברים לכתובת ה-URL של אפליקציית האינטרנט בפריסה של Apps Script.

  2. כשמערכת Apps Script מבקשת הרשאה לגשת לנתונים שלכם, לוחצים על בדיקת ההרשאות, נכנסים לחשבון Google המתאים בדומיין של Google Workspace ולוחצים על אישור.

  3. דף האינטרנט של הפעלת התקרית ייפתח. מזינים את פרטי הבדיקה:

    1. בשדה Incident title, מקלידים The First Incident.
    2. אם רוצים, אפשר להזין את כתובות האימייל של חברי הצוות שתומכים בתקרית בקטע Incident responders. הם צריכים להיות משתמשים עם חשבון Google Chat בארגון שלכם ב-Google Workspace, אחרת לא תוכלו ליצור את המרחב המשותף. אין להזין את כתובת האימייל שלכם כי היא נכללת באופן אוטומטי.
    3. בקטע הודעה ראשונית, מקלידים Testing the incident management Chat app.
  4. לוחצים על יצירת מרחב משותף. תופיע ההודעה creating space.

  5. אחרי שתיצרו את המרחב המשותף, תופיע ההודעה Space created!. לוחצים על פתיחת המרחב המשותף. המרחב המשותף ייפתח ב-Chat בכרטיסייה חדשה.

  6. אתם והאנשים האחרים שמטפלים באירוע יכולים לשלוח הודעות במרחב המשותף. האפליקציה מסכם את ההודעות האלה באמצעות Vertex AI ומשתפת מסמך רטרוספקטיבי.

  7. כדי לסיים את התגובה לתקרית ולהתחיל בתהליך הפתרון, מקלידים /closeIncident במרחב המשותף ב-Chat. תיפתח תיבת דו-שיח לניהול אירוע.

  8. בקטע Close incident, מזינים תיאור לפתרון התקרית, למשל Test complete.

  9. לוחצים על Close Incident.

באפליקציית Incident Management מופיעות ההודעות במרחב המשותף, הן מסוכמות באמצעות Vertex AI, המערכת מדביקה את הסיכום במסמך ב-Google Docs ומשתפת את המסמך במרחב המשותף.

הסרת המשאבים

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

  1. במסוף Google Cloud, עוברים לדף Manage resources. לוחצים על תפריט > IAM & Admin > Manage Resources.

    כניסה ל-Resource Manager

  2. ברשימת הפרויקטים, בוחרים את הפרויקט שרוצים למחוק ולוחצים על Delete .
  3. כדי למחוק את הפרויקט, כותבים את מזהה הפרויקט בתיבת הדו-שיח ולוחצים על Shut down.