Xây dựng các bước bắt đầu và hành động trong Workspace Studio tuỳ chỉnh

1. Giới thiệu

Trong lớp học lập trình này, bạn sẽ tạo một Cổng thông tin hỗ trợ nhanh tương tác trực tiếp với Google Workspace Studio. Bạn sẽ tìm hiểu cách tạo cả bước bắt đầu tuỳ chỉnh và bước hành động tuỳ chỉnh có thể được kiểm thử ngay mà không cần dựa vào bất kỳ cơ sở hạ tầng nào của bên thứ ba.

Chúng ta sẽ sử dụng Google Apps Script để tạo:

  • Một Ứng dụng web có biểu mẫu đơn giản để người dùng gửi phiếu yêu cầu hỗ trợ theo cách thủ công.
  • Một Custom Starter (Trình kích hoạt tuỳ chỉnh) ghi lại những nội dung được gửi qua Ứng dụng web đó và kích hoạt một quy trình trong Workspace Studio.
  • Một Bước tuỳ chỉnh phân tích nội dung mô tả của phiếu yêu cầu đang đến để xác định mức độ khẩn cấp ("Cao" hoặc "Bình thường").

Kiến thức bạn sẽ học được

  • Cách định cấu hình các thành phần khởi động và bước trong tệp kê khai appsscript.json.
  • Cách tạo notifyUri trong vòng đời đăng ký của người dùng mới.
  • Cách kích hoạt quy trình trong Workspace Studio theo cách lập trình từ mã bên ngoài bằng UrlFetchApp.
  • Cách tạo các biến đầu ra trong các bước tuỳ chỉnh để truyền dữ liệu xuống luồng.

Điều kiện tiên quyết

  • Tài khoản Google Workspace có bật Google Workspace Studio.
  • Chế độ cài đặt Cho phép các bước tuỳ chỉnh chưa xuất bản (thử nghiệm) được bật trong Bảng điều khiển dành cho quản trị viên của miền (trong phần Ứng dụng > Google Workspace > Workspace Studio > chế độ cài đặt các bước tuỳ chỉnh).
  • Quen thuộc với Google Apps Script.

2. Thiết lập dự án Apps Script

Trước tiên, hãy tạo một dự án Apps Script mới để lưu trữ mã của chúng ta:

  1. Truy cập vào script.google.com rồi nhấp vào Dự án mới.
  2. Đặt tên cho dự án là Support Quick Portal (Cổng thông tin hỗ trợ nhanh).
  3. Nhấp vào Cài đặt dự án Cài đặt dự án trên thanh bên trái.
  4. Đánh dấu vào hộp Hiện tệp kê khai appsscript.json trong trình chỉnh sửa.
  5. Quay lại chế độ xem Người chỉnh sửa Trình chỉnh sửa.

Theo mặc định, các dự án Apps Script được liên kết với một dự án ẩn trên Google Cloud. Để gọi API Google Workspace Studio và tránh lỗi PERMISSION_DENIED, bạn phải chuyển tập lệnh sang một dự án Cloud tiêu chuẩn, bật API và định cấu hình đúng Nền tảng xác thực của Google.

  1. Chọn hoặc tạo một dự án: Truy cập vào Google Cloud Console rồi sử dụng trình đơn thả xuống dự án ở đầu trang để chọn một dự án hiện có hoặc tạo một dự án mới.
  2. Bật API: Sau khi dự án bạn chọn hoạt động trên thanh trên cùng, hãy mở Quy trình bật API rồi nhấp vào Tiếp theo và Bật cho Workspace Studio API.
  3. Định cấu hình thương hiệu OAuth: Mở trang Thương hiệu của Nền tảng xác thực của Google.
    1. Nếu được nhắc, hãy nhấp vào Bắt đầu. (Nếu đã định cấu hình, hãy chuyển sang bước 4).
    2. Trong phần Thông tin ứng dụng, hãy nhập Tên ứng dụng và Email hỗ trợ người dùng. Nhấp vào Tiếp theo.
    3. Trong phần Đối tượng, hãy chọn Nội bộ (hoặc Bên ngoài nếu không có lựa chọn Nội bộ) rồi nhấp vào Tiếp theo.
    4. Cung cấp thông tin liên hệ, đồng ý với Chính sách dữ liệu rồi nhấp vào Tạo.
  4. Định cấu hình quyền truy cập dữ liệu: Mở trang Quyền truy cập dữ liệu rồi nhấp vào Thêm hoặc xoá phạm vi.
    1. Trong mục Thêm phạm vi theo cách thủ công, hãy dán https://www.googleapis.com/auth/workspace.studio.trigger.
    2. Nhấp vào Thêm vào bảng, sau đó nhấp vào Cập nhật và cuối cùng nhấp vào Lưu ở cuối trang.
  5. Chuyển đến phần IAM và Quản trị > Cài đặt (hoặc trang tổng quan Cloud Console) rồi sao chép Số dự án.
  6. Quay lại dự án Apps Script rồi nhấp vào Cài đặt dự án Cài đặt dự án trên thanh bên trái.
  7. Trong mục Dự án trên Google Cloud Platform (GCP), hãy nhấp vào Thay đổi dự án.
  8. Nhập Số dự án trên GCP mà bạn đã sao chép rồi nhấp vào Đặt dự án.

Định cấu hình tệp kê khai

Mở tệp appsscript.json vừa xuất hiện. Thay thế nội dung của tệp bằng mã dưới đây. Tệp kê khai này liệt kê rõ ràng các phạm vi OAuth bắt buộc và xác định điều kiện khởi động là workflowTrigger và hành động là workflowAction trong khối 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. Xây dựng giao diện người dùng của ứng dụng web

Để cung cấp một cách trực quan và có thể kiểm thử để kích hoạt chương trình khởi động mà không cần dùng các công cụ đầu cuối hoặc webhook của bên thứ ba, chúng ta sẽ tạo một Ứng dụng web Apps Script.

Trong trình chỉnh sửa tập lệnh Apps Script, bên cạnh Tệp, hãy nhấp vào biểu tượng Thêm một tệp Thêm tệp rồi chọn HTML. Đặt tên là index.html (tiện ích .html sẽ được thêm tự động).

Dán mã giao diện người dùng được đơn giản hoá sau đây:

<!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. Tạo tập lệnh phụ trợ

Bây giờ, hãy kết nối tất cả lại với nhau. Để giữ cho mã dễ đọc, chúng ta sẽ tách Ứng dụng web, logic Khởi động và logic Bước hành động thành 3 tệp kịch bản riêng biệt.

WebApp.gs

Đổi tên tệp Code.gs mặc định trong trình chỉnh sửa thành WebApp.gs, xoá myFunction và sao chép rồi dán khối bên dưới vào tệp đó. Tệp này xử lý hoạt động lưu trữ Ứng dụng web:

/** --- 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

Tiếp theo, di chuột lên Tệp, nhấp vào Thêm một tệp Thêm tệp, chọn Tập lệnh rồi đặt tên là Starter.gs. Sao chép và dán khối bên dưới vào đó. Tệp này xử lý cấu hình Starter và hoạt động quản lý vòng đời của 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

Tiếp theo, di chuột lên Tệp, nhấp vào Thêm một tệp Thêm tệp, chọn Tập lệnh rồi đặt tên là Action.gs. Sao chép và dán logic Bước khẩn cấp bên dưới vào đó:

/** --- 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. Triển khai và kiểm thử

Sau khi viết mã, hãy triển khai dự án và định cấu hình dự án.

Bước 1: Uỷ quyền lại cho Tập lệnh

Vì bạn đã liên kết một dự án trên đám mây mới (dự án tiêu chuẩn) trên Google Cloud, nên bạn phải uỷ quyền rõ ràng các phạm vi của tập lệnh (chẳng hạn như script.external_request do UrlFetchApp sử dụng) đối với dự án mới.

  1. Trong trình chỉnh sửa tập lệnh Apps Script, hãy chọn WebApp.gs trong mục Tệp, sau đó chọn hàm doGet trong trình đơn thả xuống trên thanh công cụ trên cùng.
  2. Nhấp vào Chạy.
  3. Lời nhắc "Cần có uỷ quyền" sẽ xuất hiện. Nhấp vào Xem các quyền.
  4. Chọn tài khoản của bạn và uỷ quyền cho tập lệnh. Nếu bạn thấy cảnh báo "Ứng dụng chưa được xác minh", hãy nhấp vào Nâng cao -> Chuyển đến Cổng thông tin hỗ trợ nhanh để bỏ qua.
  5. Chờ đến khi Nhật ký thực thi cho thấy Đã hoàn tất quá trình thực thi, điều này xác nhận rằng tập lệnh hiện đã được uỷ quyền hoàn toàn.

Bước 2: Kiểm thử Tiện ích và Ứng dụng web

Vì appsscript.json của bạn xác định cả Tiện ích bổ sung và Ứng dụng web, nên bạn có thể định cấu hình cả hai cùng một lúc.

  1. Nhấp vào Triển khai > Kiểm thử việc triển khai ở góc trên cùng bên phải của Apps Script.
  2. Bên cạnh Chọn loại, hãy nhấp vào Bật các loại triển khai Bật các loại triển khai và đảm bảo bạn đã chọn Tiện ích bổ sung của Google Workspace, sau đó nhấp vào Cài đặt.
  3. Nhấp lại vào Bật các loại triển khai Bật các loại triển khai, đảm bảo bạn đã đánh dấu vào Ứng dụng web và sao chép URL (URL này sẽ kết thúc bằng /dev). Đây là URL cho ứng dụng web Support Quick Portal mà bạn đã tạo.
  4. Nhấp vào Xong. Bạn có thể cần làm mới trình duyệt trước khi Workspace Studio đăng ký tiện ích bổ sung.

Bước 3: Định cấu hình Studio Flow

  1. Mở Google Workspace Studio tại studio.workspace.google.com.
  2. Nhấp vào biểu tượng Thêm công cụ Thêm công cụ ở bên trái rồi chọn Luồng.
  3. Để định cấu hình Trình khởi chạy, hãy nhấp vào Chọn trình khởi chạy, sau đó chọn Tiện ích hỗ trợ đã cài đặt > Phiếu yêu cầu hỗ trợ mới. Nếu bạn thấy thông báo "Cần có quyền", hãy nhấp vào Cấp quyền để cho phép tiện ích bổ sung truy cập vào dữ liệu của bạn.
  4. Tiếp theo, trong phần Actions (Hành động), hãy nhấp vào Choose a step (Chọn một bước), di chuyển đến "Support Extension" (Tiện ích hỗ trợ) rồi chọn bước Detect Urgency (Phát hiện mức độ khẩn cấp).
  5. Trong phần Nội dung mô tả nguồn, hãy nhấp vào Thêm Biến rồi chọn Bước 1: Phiếu yêu cầu hỗ trợ mới > Nội dung mô tả đầy đủ về vấn đề (ticketDescription).
  6. Nhấp lại vào Chọn một bước rồi chọn một bước tích hợp sẵn như Gmail > Gửi email hoặc Chat > Thông báo cho tôi trong Chat.
    • Nếu bạn chọn Gmail:
      • Đến: Nhập địa chỉ email của bạn.
      • Tiêu đề: Mức độ ưu tiên của yêu cầu hỗ trợ ticketTitle là urgencyLevel
      • Nội dung: ticketDescription
    • Nếu bạn chọn Chat:
      • Thông báo: Mức độ ưu tiên của yêu cầu ticketTitle là urgencyLevel. ticketDescription
  7. Bật quy trình công việc. Nếu bạn thấy lời nhắc Đổi tên quy trình, hãy nhập tên như Support Ticket Router rồi lưu. Thao tác này sẽ kích hoạt vòng đời onManageTrigger, lưu notifyUri.

Bước 4: Chạy mô phỏng

  1. Mở URL của Ứng dụng web mà bạn đã sao chép trước đó trong Bước 2 của trang này trong một thẻ trình duyệt mới (URL này kết thúc bằng /dev).
  2. Điền vào biểu mẫu. Đảm bảo bạn thêm một từ khoá kích hoạt (chẳng hạn như "khẩn cấp" hoặc "sự cố") vào nội dung mô tả.
  3. Nhấp vào Gửi. Chờ thông báo "Đã chuyển phiếu yêu cầu hỗ trợ thành công".
  4. Kiểm tra Nhật ký hoạt động của Workspace Studio, Hộp thư đến của Gmail hoặc Google Chat (tuỳ thuộc vào hành động bạn chọn). Bạn sẽ thấy một thông báo hoặc email được gắn thẻ là "Cao" khẩn cấp được tích hợp nguyên bản bằng cách sử dụng các phần tử Apps Script tuỳ chỉnh.

6. Dọn dẹp

Để tránh làm lộn xộn không gian làm việc và tài khoản Google Cloud, bạn có thể dọn dẹp các tài nguyên mà bạn đã tạo trong lớp học lập trình này.

  1. Xoá quy trình trong Workspace Studio:
    • Mở studio.workspace.google.com.
    • Tìm Support Ticket Router của bạn.
    • Nhấp vào trình đơn có biểu tượng ba dấu chấm bên cạnh rồi chọn Xoá.
  2. Xoá dự án Apps Script:
    • Truy cập vào script.google.com.
    • Tìm dự án Support Quick Portal.
    • Nhấp vào trình đơn có biểu tượng ba dấu chấm rồi chọn Xoá.
  3. Tắt Dự án trên Google Cloud:
    • Chuyển đến Google Cloud Console.
    • Đảm bảo bạn đã chọn dự án mới trong trình đơn thả xuống ở trên cùng.
    • Chuyển đến IAM và Quản trị > Cài đặt rồi nhấp vào Tắt.

7. Kết luận

Bạn đã xây dựng thành công một hệ sinh thái hoàn chỉnh cho các tiện ích tuỳ chỉnh của Google Workspace Studio mà không cần dựa vào bất kỳ gói thuê bao bên ngoài nào của bên thứ ba.

Nội dung đã đề cập:

  • Sử dụng workflowTrigger cùng với onManageFunction để ghi lại và xử lý chính xác các sự kiện trong vòng đời của Google.
  • Xác thực các yêu cầu phụ trợ bằng cách sử dụng ScriptApp.getOAuthToken() đối với các điểm cuối API workspacestudio.googleapis.com.
  • Sử dụng workflowAction để xử lý đồng bộ các biến đầu vào và tạo đầu ra ở hạ nguồn.

Giờ đây, bạn có thể mở rộng các nguyên tắc này để kết nối với các nền tảng SaaS thực như Jira, Salesforce hoặc Zendesk bằng cách sửa đổi vị trí kích hoạt của Ứng dụng web (hoặc webhook đến).