Google Chat, Vertex AI, Apps Script를 사용하여 이슈에 대응

이 튜토리얼에서는 실시간으로 인시던트에 응답하는 Google Chat 앱을 만드는 방법을 보여줍니다. 인시던트에 응답할 때 앱은 Chat 스페이스를 만들고 채우고, 메시지, 슬래시 명령어, 대화상자로 인시던트 해결을 지원하고, AI를 사용하여 Google Docs 문서에서 인시던트 응답을 요약합니다.

인시던트는 해결을 위해 즉각적인 팀의 관심이 필요한 이벤트입니다. 사고의 예는 다음과 같습니다.

  • 고객 관계 관리 (CRM) 플랫폼에서 시간 제약이 있는 케이스가 생성되어 서비스팀이 해결을 위해 협업해야 합니다.
  • 시스템이 오프라인 상태가 되면 사이트 안정성 엔지니어 (SRE) 그룹에 알림이 전송되어 함께 온라인 상태로 되돌릴 수 있습니다.
  • 규모가 큰 지진이 발생하고 긴급 구조대원이 대응을 조정해야 합니다.

이 튜토리얼에서는 사용자가 웹페이지에서 버튼을 클릭하여 인시던트를 신고할 때 인시던트 알림이 시작됩니다. 웹페이지에서는 사용자에게 기본 인시던트 정보(제목, 설명, 응답자의 이메일 주소)를 입력하도록 요청하여 인시던트를 시뮬레이션합니다.

인시던트 관리 채팅 앱의 작동 방식을 확인하세요.

  • 인시던트를 시작하는 웹사이트입니다.
    그림 1. 사고를 신고할 수 있는 웹사이트입니다.
  • 인시던트 채팅 스페이스가 생성되었다는 알림
    그림 2. 문제 채팅 스페이스가 생성되었다는 알림
  • 사고 대응 Chat 스페이스입니다.
    그림 3. 침해 사고 대응 Chat 스페이스입니다.
  • 슬래시 명령어로 인시던트 해결
    그림 4. 슬래시 명령어를 사용하여 인시던트를 해결합니다.
  • 인시던트 해결 대화상자
    그림 5. 문제 해결 대화상자
  • 스페이스에 공유된 문제 해결 Google Docs 문서
    그림 6. 스페이스에 공유된 인시던트 해결 Google Docs 문서
  • AI 요약 문제 해결 Google 문서
    그림 7. AI 요약 인시던트 해결 Google Docs 문서

기본 요건

조직에 이러한 기본 요건이 사용 설정되어 있어야 하는 경우 Google Workspace 관리자에게 사용 설정해 달라고 요청하세요.

  • Google Chat에 액세스할 수 있는 Business 또는 Enterprise Google Workspace 계정
  • Google Workspace에서 디렉터리(연락처 공유)가 사용 설정되어 있어야 합니다. 인시던트 앱은 디렉터리를 사용하여 이름, 이메일 주소와 같은 인시던트 응답자의 연락처 정보를 조회합니다. 인시던트 대응자는 Google Workspace 조직의 Google Chat 계정이 있는 사용자여야 합니다.

목표

  • 인시던트에 응답하는 Chat 앱을 빌드합니다.
  • 다음과 같이 사용자가 인시던트에 대응하도록 지원합니다.
    • 침해 사고 대응 스페이스 만들기
    • 침해 사고 및 대응을 요약하는 메시지를 게시합니다.
    • 대화형 채팅 앱 기능으로 공동작업 지원
  • Vertex AI로 대화와 해결 방법을 요약합니다.

아키텍처

다음 다이어그램은 사고 대응 Google Chat 앱에서 사용하는 Google Workspace 및 Google Cloud 리소스의 아키텍처를 보여줍니다.

침해 사고 대응 Google Chat 앱의 아키텍처

아키텍처는 이슈 대응 Google Chat 앱이 이슈와 해결 방법을 처리하는 방법을 보여줍니다.

  1. 사용자가 Apps Script에서 호스팅되는 외부 웹사이트에서 인시던트를 시작합니다.

  2. 웹사이트는 Apps Script에서도 호스팅되는 Google Chat 앱에 비동기 HTTP 요청을 보냅니다.

  3. 인시던트 대응 Google Chat 앱이 요청을 처리합니다.

    1. Apps Script Admin SDK 서비스는 사용자 ID 및 이메일 주소와 같은 팀 구성원 정보를 가져옵니다.

    2. Apps Script 고급 Chat 서비스를 사용하여 Chat API에 대한 HTTP 요청 집합을 통해 인시던트 대응 Google Chat 앱은 인시던트 Chat 스페이스를 만들고, 팀 구성원으로 채우고, 스페이스에 메시지를 보냅니다.

  4. 팀 구성원이 Chat 스페이스에서 인시던트를 논의합니다.

  5. 팀원이 슬래시 명령어를 호출하여 인시던트 해결을 알립니다.

    1. Apps Script 고급 Chat 서비스를 사용하여 Chat API를 HTTP로 호출하면 모든 Chat 스페이스의 메시지가 나열됩니다.

    2. Vertex AI는 나열된 메시지를 수신하고 요약을 생성합니다.

    3. Apps Script DocumentApp 서비스는 Docs 문서를 만들고 Vertex AI의 요약을 문서에 추가합니다.

    4. 사고 대응 Google Chat 앱은 Chat API를 호출하여 요약 Docs 문서 링크를 공유하는 메시지를 보냅니다.

환경 준비

이 섹션에서는 Chat 앱용 Google Cloud 프로젝트를 만들고 구성하는 방법을 보여줍니다.

Google Cloud 프로젝트 만들기

Google Cloud 콘솔

  1. Google Cloud 콘솔에서 메뉴 > IAM 및 관리자 > 프로젝트 만들기로 이동합니다.

    프로젝트 만들기로 이동

  2. 프로젝트 이름 필드에 프로젝트의 설명이 포함된 이름을 입력합니다.

    (선택사항) 프로젝트 ID를 수정하려면 수정을 클릭합니다. 프로젝트가 생성된 후에는 프로젝트 ID를 변경할 수 없으므로 프로젝트의 전체 기간 동안 필요에 맞는 ID를 선택하세요.

  3. 위치 필드에서 찾아보기를 클릭하여 프로젝트의 잠재적 위치를 표시합니다. 그런 다음 선택을 클릭합니다.
  4. 만들기를 클릭합니다. Google Cloud 콘솔이 대시보드 페이지로 이동하고 몇 분 이내에 프로젝트가 생성됩니다.

gcloud CLI

다음 개발 환경 중 하나에서 Google Cloud CLI (gcloud)에 액세스합니다.

  • Cloud Shell: gcloud CLI가 이미 설정된 온라인 터미널을 사용하려면 Cloud Shell을 활성화하세요.
    Cloud Shell 활성화
  • 로컬 셸: 로컬 개발 환경을 사용하려면 gcloud CLI를 설치하고 초기화합니다.
    Cloud 프로젝트를 만들려면 gcloud projects create 명령어를 사용합니다.
    gcloud projects create PROJECT_ID
    만들려는 프로젝트의 ID를 설정하여 PROJECT_ID를 바꿉니다.

Cloud 프로젝트에 결제 사용 설정

Google Cloud 콘솔

  1. Google Cloud 콘솔에서 결제로 이동합니다. 메뉴 > 결제 > 내 프로젝트를 클릭합니다.

    내 프로젝트의 결제로 이동

  2. 조직 선택에서 Google Cloud 프로젝트와 연결된 조직을 선택합니다.
  3. 프로젝트 행에서 작업 메뉴()를 열고 결제 변경을 클릭한 후 Cloud Billing 계정을 선택합니다.
  4. 계정 설정을 클릭합니다.

gcloud CLI

  1. 사용 가능한 결제 계정을 나열하려면 다음을 실행합니다.
    gcloud billing accounts list
  2. 결제 계정을 Google Cloud 프로젝트에 연결합니다.
    gcloud billing projects link PROJECT_ID --billing-account=BILLING_ACCOUNT_ID

    다음을 바꿉니다.

    • PROJECT_ID는 결제를 사용 설정하려는 Cloud 프로젝트의 프로젝트 ID입니다.
    • BILLING_ACCOUNT_ID는 Google Cloud 프로젝트와 연결할 결제 계정 ID입니다.

API 사용 설정

Google Cloud 콘솔

  1. Google Cloud 콘솔에서 Google Chat API, Google Docs API, Admin SDK API, Vertex AI API를 사용 설정합니다.

    API 사용 설정

  2. 올바른 Cloud 프로젝트에서 API를 사용 설정하고 있는지 확인한 후 다음을 클릭합니다.

  3. 올바른 API를 사용 설정했는지 확인한 후 사용 설정을 클릭합니다.

gcloud CLI

  1. 필요한 경우 현재 Cloud 프로젝트를 gcloud config set project 명령어로 만든 프로젝트로 설정합니다.

    gcloud config set project PROJECT_ID

    PROJECT_ID를 생성한 Cloud 프로젝트의 프로젝트 ID로 바꿉니다.

  2. gcloud services enable 명령어를 사용하여 Google Chat API, Google Docs API, Admin SDK API, Vertex AI API를 사용 설정합니다.

    gcloud services enable chat.googleapis.com docs.googleapis.com admin.googleapis.com aiplatform.googleapis.com

인증 및 승인 설정

인증 및 승인을 통해 Chat 앱은 Google Workspace 및 Google Cloud의 리소스에 액세스하여 인시던트 대응을 처리할 수 있습니다.

이 튜토리얼에서는 앱을 내부적으로 게시하므로 자리표시자 정보를 사용해도 됩니다. 앱을 외부에 게시하기 전에 자리표시자 정보를 동의 화면의 실제 정보로 바꿉니다.

  1. Google Cloud 콘솔에서 메뉴 > > 브랜딩으로 이동합니다.

    브랜딩으로 이동

  2. 를 이미 구성한 경우 브랜딩, 잠재고객, 데이터 액세스에서 다음 OAuth 동의 화면 설정을 구성할 수 있습니다. 아직 구성되지 않음이라는 메시지가 표시되면 시작하기를 클릭합니다.

    1. 앱 정보앱 이름Incident Management을 입력합니다.
    2. 사용자 지원 이메일에서 이메일 주소 또는 적절한 Google 그룹을 선택합니다.
    3. 다음을 클릭합니다.
    4. 시청자층에서 내부를 선택합니다. 내부를 선택할 수 없는 경우 외부를 선택합니다.
    5. 다음을 클릭합니다.
    6. 연락처 정보에서 프로젝트 변경사항에 대한 알림을 받을 수 있는 이메일 주소를 입력합니다.
    7. 다음을 클릭합니다.
    8. 완료에서 Google API 서비스 사용자 데이터 정책을 검토하고 동의하는 경우 Google API 서비스: 사용자 데이터 정책에 동의합니다를 선택합니다.
    9. 계속을 클릭합니다.
    10. 만들기를 클릭합니다.
    11. 사용자 유형으로 외부를 선택한 경우 테스트 사용자를 추가합니다.
      1. 잠재고객을 클릭합니다.
      2. 테스트 사용자에서 사용자 추가를 클릭합니다.
      3. 이메일 주소와 승인된 다른 테스트 사용자를 입력한 다음 저장을 클릭합니다.
  3. 데이터 액세스 > 범위 추가 또는 삭제를 클릭합니다. Google Cloud 프로젝트에서 사용 설정한 각 API의 범위 목록이 패널에 표시됩니다.

    1. 직접 범위 추가에서 다음 범위를 붙여넣습니다.

      • https://www.googleapis.com/auth/chat.spaces.create
      • https://www.googleapis.com/auth/chat.memberships
      • https://www.googleapis.com/auth/chat.memberships.app
      • https://www.googleapis.com/auth/chat.messages
      • 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. 앱에 필요한 범위를 선택한 후 데이터 액세스 페이지에서 저장을 클릭합니다.

Chat 앱 만들기 및 배포

다음 섹션에서는 Chat 앱에 필요한 모든 애플리케이션 코드가 포함된 전체 Apps Script 프로젝트를 복사하고 업데이트하므로 각 파일을 복사하여 붙여넣을 필요가 없습니다.

일부 함수에는 ChatApp.gsprocessSlashCommand_()와 같이 이름 끝에 밑줄이 포함됩니다. 밑줄은 브라우저에서 열 때 인시던트 초기화 웹페이지에서 함수를 숨깁니다. 자세한 내용은 비공개 함수를 참고하세요.

Apps Script는 두 가지 파일 형식(.gs 스크립트 및 .html 파일)을 지원합니다. 이 지원을 준수하기 위해 앱의 클라이언트 측 JavaScript는 <script /> 태그 내에 포함되고 CSS는 HTML 파일 내의 <style /> 태그 내에 포함됩니다.

원하는 경우 GitHub에서 전체 프로젝트를 확인할 수 있습니다.

GitHub에서 보기

다음은 각 파일의 개요입니다.

Consts.gs

Cloud 프로젝트 ID, Vertex AI 위치 ID, 인시던트 종료를 위한 슬래시 명령 ID 등 다른 코드 파일에서 참조하는 상수를 정의합니다.

Consts.gs 코드 보기

apps-script/incident-response/Consts.gs
const PROJECT_ID = 'replace-with-your-project-id';
const VERTEX_AI_LOCATION_ID = 'us-central1';
const CLOSE_INCIDENT_COMMAND_ID = 1;
ChatApp.gs

메시지, 카드 클릭, 슬래시 명령어, 대화상자 등 Chat 상호작용 이벤트를 처리합니다. 인시던트 해결 세부정보를 수집하는 대화상자를 열어 /closeIncident 슬래시 명령어에 응답합니다. Chat API에서 spaces.messages.list 메서드를 호출하여 스페이스의 메시지를 읽습니다. Apps Script에서 Admin SDK Directory 서비스를 사용하여 사용자 ID를 가져옵니다.

ChatApp.gs 코드 보기

apps-script/incident-response/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/ChatSpaceCreator.gs
/**
 * Creates a space in Google Chat with the provided title and members, and posts an
 * initial message to it.
 *
 * @param {Object} formData the data submitted by the user. It should contain the fields
 *                          title, description, and users.
 * @return {string} the resource name of the new space.
 */
function createChatSpace(formData) {
  const users = formData.users.trim().length > 0 ? formData.users.split(',') : [];
  const spaceName = setUpSpace_(formData.title, users);
  addAppToSpace_(spaceName);
  createMessage_(spaceName, formData.description);
  return spaceName;
}

/**
 * Creates a space in Google Chat with the provided display name and members.
 *
 * @return {string} the resource name of the new space.
 */
function setUpSpace_(displayName, users) {
  const memberships = users.map(email => ({
    member: {
      name: `users/${email}`,
      type: "HUMAN"
    }
  }));
  const request = {
    space: {
      displayName: displayName,
      spaceType: "SPACE",
      externalUserAllowed: true
    },
    memberships: memberships
  };
  // Call Chat API method spaces.setup
  const space = Chat.Spaces.setup(request);
  return space.name;
}

/**
 * Adds this Chat app to the space.
 *
 * @return {string} the resource name of the new membership.
 */
function addAppToSpace_(spaceName) {
  const request = {
    member: {
      name: "users/app",
      type: "BOT"
    }
  };
  // Call Chat API method spaces.members.create
  const membership = Chat.Spaces.Members.create(request, spaceName);
  return membership.name;
}

/**
 * Posts a text message to the space on behalf of the user.
 *
 * @return {string} the resource name of the new message.
 */
function createMessage_(spaceName, text) {
  const request = {
    text: text
  };
  // Call Chat API method spaces.messages.create
  const message = Chat.Spaces.Messages.create(request, spaceName);
  return message.name;
}
DocsApi.gs

Google Docs API를 호출하여 사용자의 Google Drive에 Google Docs 문서를 만들고 VertexAiApi.gs에서 생성된 인시던트 정보의 요약을 문서에 작성합니다.

DocsApi.gs 코드 보기

apps-script/incident-response/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

Vertex AI를 사용하여 채팅 스페이스의 대화를 요약합니다. 이 요약은 DocsAPI.gs의 특별히 생성된 문서에 게시됩니다.

VertexAiApi.gs 코드 보기

apps-script/incident-response/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 prompt =
    "Summarize the following conversation between Engineers resolving an incident"
      + " in a few sentences. Use only the information from the conversation.\n\n"
      + chatHistory;
  const request = {
    instances: [
      { prompt: prompt }
    ],
    parameters: {
      temperature: 0.2,
      maxOutputTokens: 256,
      topK: 40,
      topP: 0.95
    }
  }
  const fetchOptions = {
    method: 'POST',
    headers: { Authorization: 'Bearer ' + ScriptApp.getOAuthToken() },
    contentType: 'application/json',
    payload: JSON.stringify(request)
  }
  const response = UrlFetchApp.fetch(
    `https://${VERTEX_AI_LOCATION_ID}-aiplatform.googleapis.com/v1`
      + `/projects/${PROJECT_ID}/locations/${VERTEX_AI_LOCATION_ID}`
      + "/publishers/google/models/text-bison:predict",
    fetchOptions);
  const payload = JSON.parse(response.getContentText());
  return payload.predictions[0].content;
}
WebController.gs

인시던트 초기화 웹사이트를 제공합니다.

WebController.gs 코드 보기

apps-script/incident-response/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/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

인시던트 초기화 웹사이트의 제출, 오류, 지우기를 비롯한 양식 동작을 처리합니다. WebController.gs의 맞춤 include 함수에 의해 Index.html에 포함됩니다.

JavaScript.html 코드 보기

apps-script/incident-response/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)
      .createChatSpace(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입니다. WebController.gs의 맞춤 include 함수에 의해 Index.html에 포함됩니다.

Stylesheet.html 코드 보기

apps-script/incident-response/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 프로젝트 번호 및 ID 찾기

  1. Google Cloud 콘솔에서 Cloud 프로젝트로 이동합니다.

    Google Cloud 콘솔로 이동

  2. 설정 및 유틸리티 > 프로젝트 설정을 클릭합니다.

  3. 프로젝트 번호프로젝트 ID 필드의 값을 확인합니다. 다음 섹션에서 사용합니다.

Apps Script 프로젝트 만들기

Apps Script 프로젝트를 만들고 Cloud 프로젝트에 연결하려면 다음 단계를 따르세요.

  1. 다음 버튼을 클릭하여 Google Chat으로 인시던트에 응답하기 Apps Script 프로젝트를 엽니다.
    프로젝트 열기
  2. 개요를 클릭합니다.
  3. 개요 페이지에서 사본 만들기 아이콘 사본 만들기를 클릭합니다.
  4. Apps Script 프로젝트 사본의 이름을 지정합니다.

    1. Google Chat으로 인시던트에 응답하기 사본을 클릭합니다.

    2. 프로젝트 제목Incident Management Chat app을 입력합니다.

    3. 이름 바꾸기를 클릭합니다.

  5. Apps Script 프로젝트의 사본에서 Consts.gs 파일로 이동하여 YOUR_PROJECT_ID을 Cloud 프로젝트의 ID로 바꿉니다.

Apps Script 프로젝트의 Cloud 프로젝트 설정

  1. Apps Script 프로젝트에서 프로젝트 설정 아이콘 프로젝트 설정을 클릭합니다.
  2. Google Cloud Platform(GCP) 프로젝트에서 프로젝트 변경을 클릭합니다.
  3. GCP 프로젝트 번호에 Cloud 프로젝트의 프로젝트 번호를 붙여넣습니다.
  4. 프로젝트 설정을 클릭합니다. 이제 Cloud 프로젝트와 Apps Script 프로젝트가 연결되었습니다.

Apps Script 배포 만들기

이제 모든 코드가 준비되었으므로 Apps Script 프로젝트를 배포합니다. Google Cloud에서 Chat 앱을 구성할 때 배포 ID를 사용합니다.

  1. Apps Script에서 인시던트 대응 앱의 프로젝트를 엽니다.

    Apps Script로 이동

  2. 배포 > 새 배포를 클릭합니다.

  3. 부가기능웹 앱이 아직 선택되지 않은 경우 유형 선택 옆에 있는 배포 유형 프로젝트 설정 아이콘을 클릭하고 부가기능웹 앱을 선택합니다.

  4. 설명에 이 버전에 대한 설명을 입력합니다(예: Complete version of incident management app).

  5. 실행 계정에서 웹 앱에 액세스하는 사용자를 선택합니다.

  6. 액세스 권한이 있는 사용자에서 내 Workspace 조직의 모든 사용자를 선택합니다. 여기서 '내 Workspace 조직'은 Google Workspace 조직의 이름입니다.

  7. 배포를 클릭합니다. Apps Script는 배포가 성공했다고 보고하고 인시던트 초기화 웹페이지의 배포 ID와 URL을 제공합니다.

  8. 나중에 인시던트를 시작할 때 방문할 웹 앱 URL을 기록해 둡니다. 배포 ID를 복사합니다. Google Cloud 콘솔에서 Chat 앱을 구성할 때 이 ID를 사용합니다.

  9. 완료를 클릭합니다.

Google Cloud 콘솔에서 Chat 앱 구성

이 섹션에서는 Apps Script 프로젝트에서 방금 만든 배포의 ID를 비롯한 Chat 앱에 관한 정보를 사용하여 Google Cloud 콘솔에서 Google Chat API를 구성하는 방법을 보여줍니다.

  1. Google Cloud 콘솔에서 메뉴 > 제품 더보기 > Google Workspace > 제품 라이브러리 > Google Chat API > 관리 > 구성을 클릭합니다.

    Chat API 구성으로 이동

  2. 앱 이름Incident Management를 입력합니다.

  3. 아바타 URLhttps://developers.google.com/chat/images/quickstart-app-avatar.png를 입력합니다.

  4. 설명Responds to incidents.를 입력합니다.

  5. 대화형 기능 사용 설정 전환 버튼을 클릭하여 설정 위치로 전환합니다.

  6. 기능에서 스페이스 및 그룹 대화 참여를 선택합니다.

  7. 연결 설정에서 Apps Script를 선택합니다.

  8. 배포 ID에 Apps Script 프로젝트 배포에서 이전에 복사한 Apps Script 배포 ID를 붙여넣습니다.

  9. 완전히 구현된 Chat 앱이 사용하는 슬래시 명령어를 등록합니다.

    1. 슬래시 명령어에서 슬래시 명령어 추가를 클릭합니다.

    2. 이름Close incident를 입력합니다.

    3. 명령어 ID1를 입력합니다.

    4. 설명Closes the incident being discussed in the space.를 입력합니다.

    5. 명령어 유형에서 슬래시 명령어를 선택합니다.

    6. 슬래시 명령어 이름/closeIncident를 입력합니다.

    7. 대화상자 열기를 선택합니다.

    8. 완료를 클릭합니다. 슬래시 명령어가 등록되고 나열됩니다.

  10. 공개 상태에서 내 Workspace 도메인의 특정 사용자 및 그룹에서 이 채팅 앱을 사용할 수 있도록 설정을 선택하고 이메일 주소를 입력합니다.

  11. 로그에서 Logging에 오류 로깅을 선택합니다.

  12. 저장을 클릭합니다. 구성 저장 메시지가 표시됩니다. 이는 앱을 테스트할 준비가 되었음을 의미합니다.

채팅 앱 테스트

인시던트 관리 Chat 앱을 테스트하려면 웹페이지에서 인시던트를 시작하고 Chat 앱이 예상대로 작동하는지 확인하세요.

  1. Apps Script 배포 웹 앱 URL로 이동합니다.

  2. Apps Script에서 데이터에 액세스할 권한을 요청하면 권한 검토를 클릭하고 Google Workspace 도메인의 적절한 Google 계정으로 로그인한 다음 허용을 클릭합니다.

  3. 인시던트 초기화 웹페이지가 열립니다. 테스트 정보를 입력합니다.

    1. 인시던트 제목The First Incident을 입력합니다.
    2. 원하는 경우 침해사고 대응자에 동료 침해사고 대응자의 이메일 주소를 입력합니다. Google Workspace 조직의 Google Chat 계정이 있는 사용자여야 합니다. 그렇지 않으면 스페이스를 만들 수 없습니다. 이메일 주소는 자동으로 포함되므로 직접 입력하지 마세요.
    3. 초기 메시지Testing the incident management Chat app.을 입력합니다.
  4. 채팅 스페이스 만들기를 클릭합니다. creating space 메시지가 표시됩니다.

  5. 스페이스가 생성되면 Space created! 메시지가 표시됩니다. 스페이스 열기를 클릭하면 새 탭에서 Chat에 스페이스가 열립니다.

  6. 원하는 경우 나와 다른 인시던트 대응팀이 스페이스에서 메시지를 보낼 수 있습니다. 앱은 Vertex AI를 사용하여 이러한 메시지를 요약하고 회고 문서를 공유합니다.

  7. 사고 대응을 종료하고 해결 프로세스를 시작하려면 채팅 스페이스에서 /closeIncident를 입력합니다. 이슈 관리 대화상자가 열립니다.

  8. 이슈 종료에 이슈 해결에 대한 설명을 입력합니다(예: Test complete).

  9. 사고 종료를 클릭합니다.

인시던트 관리 앱은 스페이스의 메시지를 나열하고, Vertex AI로 요약하고, 요약을 Google Docs 문서에 붙여넣고, 스페이스에서 문서를 공유합니다.

삭제

이 튜토리얼에서 사용한 리소스 비용이 Google Cloud 계정에 청구되지 않도록 하려면 Cloud 프로젝트를 삭제하는 것이 좋습니다.

  1. Google Cloud 콘솔에서 리소스 관리 페이지로 이동합니다. 메뉴 > IAM 및 관리자 > 리소스 관리를 클릭합니다.

    Resource Manager로 이동

  2. 프로젝트 목록에서 삭제할 프로젝트를 선택하고 삭제 를 클릭합니다.
  3. 대화상자에서 프로젝트 ID를 입력한 다음 종료를 클릭하여 프로젝트를 삭제합니다.