1. 简介
在此 Codelab 中,您将构建一个直接与 Google Workspace Studio 交互的互动式支持快速门户。您将学习如何构建自定义起始步骤和自定义操作步骤,以便立即进行测试,而无需依赖任何第三方基础设施。
我们将使用 Google Apps 脚本来创建:
- 一个Web 应用,其中包含一个简洁的表单,供用户手动提交支持服务工单。
- 一种自定义启动器,用于捕获这些 Web 应用提交内容并触发 Workspace Studio 流程。
- 一个自定义步骤,用于分析传入工单的说明,以确定紧急程度(“高”或“正常”)。
学习内容
- 如何在
appsscript.json清单中配置初始状态和步骤。 - 如何在启动器注册生命周期内构建
notifyUri。 - 如何使用
UrlFetchApp从外部代码以编程方式触发 Workspace Studio 流程。 - 如何在自定义步骤中构建输出变量,以便将数据传递到下游。
前提条件
- 已启用 Google Workspace Studio 的 Google Workspace 账号。
- 在网域的管理控制台中启用允许使用未发布的(测试)自定义步骤设置(位于应用 > Google Workspace > Workspace Studio > 自定义步骤设置下)。
- 熟悉 Google Apps 脚本。
2. 设置 Apps 脚本项目
首先,我们来创建一个新的 Apps 脚本项目来存放代码:
- 前往 script.google.com,然后点击新项目。
- 将项目命名为 Support Quick Portal。
- 点击左侧边栏中的
项目设置。
- 选中在编辑器中显示 appsscript.json 清单文件复选框。
- 返回到
编辑器视图。
关联标准 Google Cloud 项目
默认情况下,Apps 脚本项目与一个隐藏的默认 Google Cloud 项目相关联。如需调用 Google Workspace Studio API 并避免 PERMISSION_DENIED 错误,您必须将脚本切换为标准云项目,启用该 API,并正确配置 Google Auth 平台。
- 选择或创建项目:前往 Google Cloud 控制台,然后使用页面顶部的项目下拉菜单选择现有项目或创建新项目。
- 启用 API:在顶部栏中激活所选项目后,打开 API 启用流程,然后依次点击下一步和启用 Workspace Studio API。
- 配置 OAuth 品牌设置:打开 Google Auth Platform 品牌设置页面。
- 如果系统提示,请点击开始。(如果已配置,请跳至第 4 步)。
- 在应用信息下,输入应用名称和用户支持电子邮件地址。点击下一步。
- 在受众群体下,选择内部(如果没有“内部”,则选择外部),然后点击下一步。
- 提供联系信息,同意数据政策,然后点击创建。
- 配置数据访问权限:打开“数据访问权限”页面,然后点击添加或移除范围。
- 在手动添加范围下,粘贴
https://www.googleapis.com/auth/workspace.studio.trigger。 - 依次点击添加到表格和更新,最后点击页面底部的保存。
- 在手动添加范围下,粘贴
- 前往 IAM 和管理 > 设置(或 Cloud 控制台信息中心),然后复制项目编号。
- 返回您的 Apps 脚本项目,然后点击左侧边栏中的
项目设置。
- 在 Google Cloud Platform (GCP) 项目部分下,点击更改项目。
- 输入您复制的 GCP 项目编号,然后点击设置项目。
配置清单
打开新显示的 appsscript.json 文件。将其内容替换为以下代码。此清单明确列出了我们所需的 OAuth 范围,并在 studio.flows.workflowElements 块下将 启动方式 定义为 workflowTrigger,将 操作 定义为 workflowAction。
{
"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. 构建 Web 应用界面
为了提供一种直观且可测试的方式来触发我们的启动器,而无需使用终端工具或第三方 Webhook,我们将构建一个 Apps 脚本 Web 应用。
在 Apps 脚本编辑器中,点击文件旁边的 添加文件,然后选择 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. 构建后端脚本
现在,让我们将所有内容串联起来。为了保持代码的可读性,我们将 Web 应用、起始逻辑和操作步骤逻辑分别放在三个不同的脚本文件中。
WebApp.gs
将编辑器中的默认 Code.gs 文件重命名为 WebApp.gs,删除样板 myFunction,然后将以下代码块复制并粘贴到其中。此文件用于处理 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
接下来,将光标悬停在文件上,依次点击 添加文件、脚本,然后将其命名为
Starter.gs。将以下代码块复制并粘贴到该文件中。此文件用于处理启动器配置和 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 云项目,因此必须针对新项目明确授权脚本的范围(例如 UrlFetchApp 使用的 script.external_request)。
- 在 Apps 脚本编辑器中,选择文件下的
WebApp.gs,然后从顶部工具栏下拉菜单中选择doGet函数。 - 点击运行。
- 系统会显示“需要授权”提示。点击查看权限。
- 选择您的账号并授权脚本。如果您看到“未经验证的应用”警告,请点击高级 -> 前往“支持快速门户”以绕过该警告。
- 等待执行日志显示执行完成,这表示脚本现已获得完全授权。
第 2 步:测试扩展程序和 Web 应用
由于您的 appsscript.json 同时定义了插件和 Web 应用,因此您可以同时配置这两者。
- 在 Apps 脚本右上角,依次点击部署 > 测试部署。
- 点击选择类型旁边的
启用部署类型,确保已勾选 Google Workspace 加载项,然后点击安装。
- 再次点击
Enable deployment types,确保选中 Web app,然后复制 URL(以
/dev结尾)。这是您构建的 Support Quick Portal Web 应用的网址。 - 点击完成。您可能需要刷新浏览器,然后 Workspace Studio 才能注册该插件。
第 3 步:配置工作室流程
- 在 studio.workspace.google.com 中打开 Google Workspace Studio。
- 点击左侧的
Add tool(添加工具),然后选择 Flow(流程)。
- 如需配置启动器,请点击选择启动器,然后依次选择已安装的支持扩展程序 > 新建支持服务工单。如果系统提示“需要权限”,请点击授予权限,以允许该插件访问您的数据。
- 接下来,在“操作”下点击选择一个步骤,滚动到“支持扩展程序”,然后选择检测紧急程度步骤。
- 在来源说明中,依次点击
变量和第 1 步:新建支持服务工单 > 问题的完整说明 (
ticketDescription)。 - 再次点击选择步骤,然后选择内置步骤,例如 Gmail > 发送电子邮件或 Chat > 在 Chat 中通知我。
- 如果您选择了 Gmail:
- 收件人:输入您自己的电子邮件地址。
- 主题:工单
ticketTitle的优先级为urgencyLevel - 消息:
ticketDescription
- 如果您选择的是“聊天”:
- 消息:工单
ticketTitle的优先级为urgencyLevel。ticketDescription
- 消息:工单
- 如果您选择了 Gmail:
- 开启工作流。如果系统提示您重命名流程,请输入类似
Support Ticket Router的名称,然后保存。这会触发onManageTrigger生命周期,从而保存notifyUri。
第 4 步:运行模拟
- 在新的浏览器标签页中打开您之前在此页面的第 2 步中复制的 Web 应用网址(以 /dev 结尾)。
- 填写表单。请务必在说明中添加触发关键字(例如“紧急”或“崩溃”)。
- 点击提交。等待“工单已成功转送”消息。
- 查看您的 Workspace Studio 活动日志、Gmail 收件箱或 Google Chat(具体取决于您选择的操作)。您应该会看到一条标记为“高”优先级的消息或电子邮件,该消息或电子邮件已使用您的自定义 Apps 脚本元素进行原生集成。
6. 清理
为避免工作区和 Google Cloud 账号杂乱无章,您可以清理在本 Codelab 中创建的资源。
- 删除 Workspace Studio Flow:
- 打开 studio.workspace.google.com。
- 找到你的
Support Ticket Router节奏。 - 点击该应用旁边的三点状菜单,然后选择删除。
- 删除 Apps 脚本项目:
- 前往 script.google.com。
- 找到
Support Quick Portal项目。 - 点击三点状菜单,然后选择移除。
- 关闭 Google Cloud 项目:
- 前往 Google Cloud 控制台。
- 确保在顶部下拉菜单中选择您的新项目。
- 依次前往 IAM 和管理 > 设置,然后点击关停。
7. 总结
您已成功为 Google Workspace Studio 自定义扩展程序构建了一个完整的生态系统,而无需依赖任何外部第三方订阅。
内容回顾:
- 将
workflowTrigger与onManageFunction搭配使用,以正确捕获和处理 Google 的生命周期事件。 - 使用
ScriptApp.getOAuthToken()对针对workspacestudio.googleapis.comAPI 端点的后端请求进行身份验证。 - 使用
workflowAction同步处理输入变量并生成下游输出。
现在,您可以横向扩容这些原则,通过修改 Web 应用(或传入的网络钩子)的触发位置,将这些原则与 Jira、Salesforce 或 Zendesk 等实际 SaaS 平台相关联。