Trang này giải thích cách tạo một tiện ích bổ sung cho Google Workspace để cho phép người dùng Google Tài liệu tạo tài nguyên (chẳng hạn như yêu cầu hỗ trợ hoặc nhiệm vụ của dự án) trong một dịch vụ của bên thứ ba ngay trong Google Tài liệu.
Với tiện ích bổ sung của Google Workspace, bạn có thể thêm dịch vụ của mình vào trình đơn @ trong Tài liệu. Tiện ích bổ sung này thêm các mục trên trình đơn để người dùng tạo tài nguyên trong dịch vụ của bạn thông qua hộp thoại biểu mẫu trong Tài liệu.
Cách người dùng tạo tài nguyên
Để tạo tài nguyên trong dịch vụ của bạn từ bên trong tài liệu Google Tài liệu, người dùng
nhập @
vào tài liệu và chọn dịch vụ của bạn từ trình đơn @:
Khi người dùng nhập @
vào một tài liệu và chọn dịch vụ của bạn, bạn sẽ hiển thị cho họ một thẻ bao gồm thông tin đầu vào biểu mẫu mà người dùng cần để tạo tài nguyên. Sau khi người dùng gửi biểu mẫu tạo tài nguyên, tiện ích bổ sung sẽ tạo tài nguyên trong dịch vụ của bạn và tạo một URL trỏ đến tài nguyên đó.
Tiện ích bổ sung này sẽ chèn một khối vào tài liệu cho tài nguyên được tạo. Khi người dùng giữ con trỏ trên khối này, con trỏ sẽ gọi trình kích hoạt xem trước đường liên kết được liên kết của tiện ích bổ sung. Hãy đảm bảo tiện ích bổ sung của bạn chèn các khối có mẫu đường liên kết mà trình kích hoạt bản xem trước đường liên kết hỗ trợ.
Điều kiện tiên quyết
Apps Script
- Một tiện ích bổ sung của Google Workspace hỗ trợ xem trước đường liên kết cho các mẫu đường liên kết của tài nguyên mà người dùng tạo. Để tạo tiện ích bổ sung có bản xem trước đường liên kết, hãy tham khảo bài viết Xem trước đường liên kết bằng khối thông minh.
Node.js
- Một tiện ích bổ sung của Google Workspace hỗ trợ xem trước đường liên kết cho các mẫu đường liên kết của tài nguyên mà người dùng tạo. Để tạo tiện ích bổ sung có bản xem trước đường liên kết, hãy tham khảo bài viết Xem trước đường liên kết bằng khối thông minh.
Python
- Một tiện ích bổ sung của Google Workspace hỗ trợ xem trước đường liên kết cho các mẫu đường liên kết của tài nguyên mà người dùng tạo. Để tạo tiện ích bổ sung có bản xem trước đường liên kết, hãy tham khảo bài viết Xem trước đường liên kết bằng khối thông minh.
Java
- Một tiện ích bổ sung của Google Workspace hỗ trợ xem trước đường liên kết cho các mẫu đường liên kết của tài nguyên mà người dùng tạo. Để tạo tiện ích bổ sung có bản xem trước đường liên kết, hãy tham khảo bài viết Xem trước đường liên kết bằng khối thông minh.
Thiết lập tính năng tạo tài nguyên cho tiện ích bổ sung
Phần này giải thích cách thiết lập tính năng tạo tài nguyên cho tiện ích bổ sung, bao gồm các bước sau:
- Định cấu hình quá trình tạo tài nguyên trong tài nguyên triển khai hoặc tệp kê khai của tiện ích bổ sung.
- Tạo các thẻ biểu mẫu mà người dùng cần để tạo tài nguyên trong dịch vụ của bạn.
- Xử lý lượt gửi biểu mẫu để hàm tạo tài nguyên sẽ chạy khi người dùng gửi biểu mẫu.
Định cấu hình quá trình tạo tài nguyên
Để định cấu hình việc tạo tài nguyên, hãy chỉ định các phần và trường sau đây trong tệp kê khai hoặc tài nguyên triển khai của tiện ích bổ sung:
Trong phần
addOns
của trườngdocs
, hãy triển khai điều kiện kích hoạtcreateActionTriggers
có chứarunFunction
. (Bạn xác định hàm này trong phần Tạo thẻ biểu mẫu sau).Để tìm hiểu về những trường mà bạn có thể chỉ định trong điều kiện kích hoạt
createActionTriggers
, hãy xem tài liệu tham khảo cho tệp kê khai Apps Script hoặc tài nguyên triển khai cho các thời gian chạy khác.Trong trường
oauthScopes
, hãy thêm phạm vihttps://www.googleapis.com/auth/workspace.linkcreate
để người dùng có thể uỷ quyền cho tiện ích bổ sung tạo tài nguyên. Cụ thể, phạm vi này cho phép tiện ích bổ sung đọc thông tin mà người dùng gửi đến biểu mẫu tạo tài nguyên và chèn một khối thông minh vào tài liệu dựa trên thông tin đó.
Ví dụ: hãy xem phần addons
trong tài nguyên triển khai, giúp định cấu hình việc tạo tài nguyên cho dịch vụ yêu cầu hỗ trợ sau đây:
{
"oauthScopes": [
"https://www.googleapis.com/auth/workspace.linkpreview",
"https://www.googleapis.com/auth/workspace.linkcreate"
],
"addOns": {
"docs": {
"linkPreviewTriggers": [
...
],
"createActionTriggers": [
{
"id": "createCase",
"labelText": "Create support case",
"localizedLabelText": {
"es": "Crear caso de soporte"
},
"runFunction": "createCaseInputCard",
"logoUrl": "https://www.example.com/images/case.png"
}
]
}
}
}
Trong ví dụ này, Tiện ích bổ sung của Google Workspace cho phép người dùng tạo các yêu cầu hỗ trợ.
Mỗi điều kiện kích hoạt createActionTriggers
phải có các trường sau:
- Mã nhận dạng duy nhất
- Nhãn văn bản xuất hiện trong trình đơn Tài liệu @
- URL biểu trưng trỏ đến biểu tượng xuất hiện bên cạnh văn bản nhãn trong trình đơn @
- Hàm callback tham chiếu đến một hàm Apps Script hoặc một điểm cuối HTTP trả về một thẻ
Tạo thẻ biểu mẫu
Để tạo tài nguyên trong dịch vụ của bạn qua trình đơn Tài liệu @, bạn phải triển khai mọi hàm mà bạn đã chỉ định trong đối tượng createActionTriggers
.
Khi người dùng tương tác với một trong các mục trong trình đơn của bạn, trình kích hoạt createActionTriggers
tương ứng sẽ kích hoạt và hàm callback của nó sẽ hiển thị một thẻ có thông tin đầu vào của biểu mẫu để tạo tài nguyên.
Các thành phần và thao tác được hỗ trợ
Để tạo giao diện thẻ, bạn sử dụng các tiện ích để cho thấy thông tin và dữ liệu đầu vào mà người dùng cần để tạo tài nguyên. Hầu hết tiện ích và thao tác của tiện ích bổ sung của Google Workspace đều được hỗ trợ với những ngoại lệ sau:
- Không hỗ trợ chân trang thẻ.
- Tính năng thông báo không được hỗ trợ.
- Đối với các thao tác điều hướng, chỉ có thao tác điều hướng
updateCard
được hỗ trợ.
Ví dụ về thẻ có thông tin nhập vào biểu mẫu
Ví dụ sau đây minh hoạ hàm callback Apps Script, hàm này hiển thị một thẻ khi người dùng chọn Create support case (Tạo trường hợp hỗ trợ) trên trình đơn @:
Apps Script
/** * Produces a support case creation form card. * * @param {!Object} event The event object. * @param {!Object=} errors An optional map of per-field error messages. * @param {boolean} isUpdate Whether to return the form as an update card navigation. * @return {!Card|!ActionResponse} The resulting card or action response. */ function createCaseInputCard(event, errors, isUpdate) { const cardHeader = CardService.newCardHeader() .setTitle('Create a support case') const cardSectionTextInput1 = CardService.newTextInput() .setFieldName('name') .setTitle('Name') .setMultiline(false); const cardSectionTextInput2 = CardService.newTextInput() .setFieldName('description') .setTitle('Description') .setMultiline(true); const cardSectionSelectionInput1 = CardService.newSelectionInput() .setFieldName('priority') .setTitle('Priority') .setType(CardService.SelectionInputType.DROPDOWN) .addItem('P0', 'P0', false) .addItem('P1', 'P1', false) .addItem('P2', 'P2', false) .addItem('P3', 'P3', false); const cardSectionSelectionInput2 = CardService.newSelectionInput() .setFieldName('impact') .setTitle('Impact') .setType(CardService.SelectionInputType.CHECK_BOX) .addItem('Blocks a critical customer operation', 'Blocks a critical customer operation', false); const cardSectionButtonListButtonAction = CardService.newAction() .setPersistValues(true) .setFunctionName('submitCaseCreationForm') .setParameters({}); const cardSectionButtonListButton = CardService.newTextButton() .setText('Create') .setTextButtonStyle(CardService.TextButtonStyle.TEXT) .setOnClickAction(cardSectionButtonListButtonAction); const cardSectionButtonList = CardService.newButtonSet() .addButton(cardSectionButtonListButton); // Builds the form inputs with error texts for invalid values. const cardSection = CardService.newCardSection(); if (errors?.name) { cardSection.addWidget(createErrorTextParagraph(errors.name)); } cardSection.addWidget(cardSectionTextInput1); if (errors?.description) { cardSection.addWidget(createErrorTextParagraph(errors.description)); } cardSection.addWidget(cardSectionTextInput2); if (errors?.priority) { cardSection.addWidget(createErrorTextParagraph(errors.priority)); } cardSection.addWidget(cardSectionSelectionInput1); if (errors?.impact) { cardSection.addWidget(createErrorTextParagraph(errors.impact)); } cardSection.addWidget(cardSectionSelectionInput2); cardSection.addWidget(cardSectionButtonList); const card = CardService.newCardBuilder() .setHeader(cardHeader) .addSection(cardSection) .build(); if (isUpdate) { return CardService.newActionResponseBuilder() .setNavigation(CardService.newNavigation().updateCard(card)) .build(); } else { return card; } }
Node.js
/** * Produces a support case creation form card. * * @param {!Object} event The event object. * @param {!Object=} errors An optional map of per-field error messages. * @param {boolean} isUpdate Whether to return the form as an update card navigation. * @return {!Card|!ActionResponse} The resulting card or action response. */ function createCaseInputCard(event, errors, isUpdate) { const cardHeader1 = { title: "Create a support case" }; const cardSection1TextInput1 = { textInput: { name: "name", label: "Name" } }; const cardSection1TextInput2 = { textInput: { name: "description", label: "Description", type: "MULTIPLE_LINE" } }; const cardSection1SelectionInput1 = { selectionInput: { name: "priority", label: "Priority", type: "DROPDOWN", items: [{ text: "P0", value: "P0" }, { text: "P1", value: "P1" }, { text: "P2", value: "P2" }, { text: "P3", value: "P3" }] } }; const cardSection1SelectionInput2 = { selectionInput: { name: "impact", label: "Impact", items: [{ text: "Blocks a critical customer operation", value: "Blocks a critical customer operation" }] } }; const cardSection1ButtonList1Button1Action1 = { function: process.env.URL, parameters: [ { key: "submitCaseCreationForm", value: true } ], persistValues: true }; const cardSection1ButtonList1Button1 = { text: "Create", onClick: { action: cardSection1ButtonList1Button1Action1 } }; const cardSection1ButtonList1 = { buttonList: { buttons: [cardSection1ButtonList1Button1] } }; // Builds the creation form and adds error text for invalid inputs. const cardSection1 = []; if (errors?.name) { cardSection1.push(createErrorTextParagraph(errors.name)); } cardSection1.push(cardSection1TextInput1); if (errors?.description) { cardSection1.push(createErrorTextParagraph(errors.description)); } cardSection1.push(cardSection1TextInput2); if (errors?.priority) { cardSection1.push(createErrorTextParagraph(errors.priority)); } cardSection1.push(cardSection1SelectionInput1); if (errors?.impact) { cardSection1.push(createErrorTextParagraph(errors.impact)); } cardSection1.push(cardSection1SelectionInput2); cardSection1.push(cardSection1ButtonList1); const card = { header: cardHeader1, sections: [{ widgets: cardSection1 }] }; if (isUpdate) { return { renderActions: { action: { navigations: [{ updateCard: card }] } } }; } else { return { action: { navigations: [{ pushCard: card }] } }; } }
Python
def create_case_input_card(event, errors = {}, isUpdate = False): """Produces a support case creation form card. Args: event: The event object. errors: An optional dict of per-field error messages. isUpdate: Whether to return the form as an update card navigation. Returns: The resulting card or action response. """ card_header1 = { "title": "Create a support case" } card_section1_text_input1 = { "textInput": { "name": "name", "label": "Name" } } card_section1_text_input2 = { "textInput": { "name": "description", "label": "Description", "type": "MULTIPLE_LINE" } } card_section1_selection_input1 = { "selectionInput": { "name": "priority", "label": "Priority", "type": "DROPDOWN", "items": [{ "text": "P0", "value": "P0" }, { "text": "P1", "value": "P1" }, { "text": "P2", "value": "P2" }, { "text": "P3", "value": "P3" }] } } card_section1_selection_input2 = { "selectionInput": { "name": "impact", "label": "Impact", "items": [{ "text": "Blocks a critical customer operation", "value": "Blocks a critical customer operation" }] } } card_section1_button_list1_button1_action1 = { "function": os.environ["URL"], "parameters": [ { "key": "submitCaseCreationForm", "value": True } ], "persistValues": True } card_section1_button_list1_button1 = { "text": "Create", "onClick": { "action": card_section1_button_list1_button1_action1 } } card_section1_button_list1 = { "buttonList": { "buttons": [card_section1_button_list1_button1] } } # Builds the creation form and adds error text for invalid inputs. card_section1 = [] if "name" in errors: card_section1.append(create_error_text_paragraph(errors["name"])) card_section1.append(card_section1_text_input1) if "description" in errors: card_section1.append(create_error_text_paragraph(errors["description"])) card_section1.append(card_section1_text_input2) if "priority" in errors: card_section1.append(create_error_text_paragraph(errors["priority"])) card_section1.append(card_section1_selection_input1) if "impact" in errors: card_section1.append(create_error_text_paragraph(errors["impact"])) card_section1.append(card_section1_selection_input2) card_section1.append(card_section1_button_list1) card = { "header": card_header1, "sections": [{ "widgets": card_section1 }] } if isUpdate: return { "renderActions": { "action": { "navigations": [{ "updateCard": card }] } } } else: return { "action": { "navigations": [{ "pushCard": card }] } }
Java
/** * Produces a support case creation form. * * @param event The event object. * @param errors A map of per-field error messages. * @param isUpdate Whether to return the form as an update card navigation. * @return The resulting card or action response. */ JsonObject createCaseInputCard(JsonObject event, Map<String, String> errors, boolean isUpdate) { JsonObject cardHeader = new JsonObject(); cardHeader.add("title", new JsonPrimitive("Create a support case")); JsonObject cardSectionTextInput1 = new JsonObject(); cardSectionTextInput1.add("name", new JsonPrimitive("name")); cardSectionTextInput1.add("label", new JsonPrimitive("Name")); JsonObject cardSectionTextInput1Widget = new JsonObject(); cardSectionTextInput1Widget.add("textInput", cardSectionTextInput1); JsonObject cardSectionTextInput2 = new JsonObject(); cardSectionTextInput2.add("name", new JsonPrimitive("description")); cardSectionTextInput2.add("label", new JsonPrimitive("Description")); cardSectionTextInput2.add("type", new JsonPrimitive("MULTIPLE_LINE")); JsonObject cardSectionTextInput2Widget = new JsonObject(); cardSectionTextInput2Widget.add("textInput", cardSectionTextInput2); JsonObject cardSectionSelectionInput1ItemsItem1 = new JsonObject(); cardSectionSelectionInput1ItemsItem1.add("text", new JsonPrimitive("P0")); cardSectionSelectionInput1ItemsItem1.add("value", new JsonPrimitive("P0")); JsonObject cardSectionSelectionInput1ItemsItem2 = new JsonObject(); cardSectionSelectionInput1ItemsItem2.add("text", new JsonPrimitive("P1")); cardSectionSelectionInput1ItemsItem2.add("value", new JsonPrimitive("P1")); JsonObject cardSectionSelectionInput1ItemsItem3 = new JsonObject(); cardSectionSelectionInput1ItemsItem3.add("text", new JsonPrimitive("P2")); cardSectionSelectionInput1ItemsItem3.add("value", new JsonPrimitive("P2")); JsonObject cardSectionSelectionInput1ItemsItem4 = new JsonObject(); cardSectionSelectionInput1ItemsItem4.add("text", new JsonPrimitive("P3")); cardSectionSelectionInput1ItemsItem4.add("value", new JsonPrimitive("P3")); JsonArray cardSectionSelectionInput1Items = new JsonArray(); cardSectionSelectionInput1Items.add(cardSectionSelectionInput1ItemsItem1); cardSectionSelectionInput1Items.add(cardSectionSelectionInput1ItemsItem2); cardSectionSelectionInput1Items.add(cardSectionSelectionInput1ItemsItem3); cardSectionSelectionInput1Items.add(cardSectionSelectionInput1ItemsItem4); JsonObject cardSectionSelectionInput1 = new JsonObject(); cardSectionSelectionInput1.add("name", new JsonPrimitive("priority")); cardSectionSelectionInput1.add("label", new JsonPrimitive("Priority")); cardSectionSelectionInput1.add("type", new JsonPrimitive("DROPDOWN")); cardSectionSelectionInput1.add("items", cardSectionSelectionInput1Items); JsonObject cardSectionSelectionInput1Widget = new JsonObject(); cardSectionSelectionInput1Widget.add("selectionInput", cardSectionSelectionInput1); JsonObject cardSectionSelectionInput2ItemsItem = new JsonObject(); cardSectionSelectionInput2ItemsItem.add("text", new JsonPrimitive("Blocks a critical customer operation")); cardSectionSelectionInput2ItemsItem.add("value", new JsonPrimitive("Blocks a critical customer operation")); JsonArray cardSectionSelectionInput2Items = new JsonArray(); cardSectionSelectionInput2Items.add(cardSectionSelectionInput2ItemsItem); JsonObject cardSectionSelectionInput2 = new JsonObject(); cardSectionSelectionInput2.add("name", new JsonPrimitive("impact")); cardSectionSelectionInput2.add("label", new JsonPrimitive("Impact")); cardSectionSelectionInput2.add("items", cardSectionSelectionInput2Items); JsonObject cardSectionSelectionInput2Widget = new JsonObject(); cardSectionSelectionInput2Widget.add("selectionInput", cardSectionSelectionInput2); JsonObject cardSectionButtonListButtonActionParametersParameter = new JsonObject(); cardSectionButtonListButtonActionParametersParameter.add("key", new JsonPrimitive("submitCaseCreationForm")); cardSectionButtonListButtonActionParametersParameter.add("value", new JsonPrimitive(true)); JsonArray cardSectionButtonListButtonActionParameters = new JsonArray(); cardSectionButtonListButtonActionParameters.add(cardSectionButtonListButtonActionParametersParameter); JsonObject cardSectionButtonListButtonAction = new JsonObject(); cardSectionButtonListButtonAction.add("function", new JsonPrimitive(System.getenv().get("URL"))); cardSectionButtonListButtonAction.add("parameters", cardSectionButtonListButtonActionParameters); cardSectionButtonListButtonAction.add("persistValues", new JsonPrimitive(true)); JsonObject cardSectionButtonListButtonOnCLick = new JsonObject(); cardSectionButtonListButtonOnCLick.add("action", cardSectionButtonListButtonAction); JsonObject cardSectionButtonListButton = new JsonObject(); cardSectionButtonListButton.add("text", new JsonPrimitive("Create")); cardSectionButtonListButton.add("onClick", cardSectionButtonListButtonOnCLick); JsonArray cardSectionButtonListButtons = new JsonArray(); cardSectionButtonListButtons.add(cardSectionButtonListButton); JsonObject cardSectionButtonList = new JsonObject(); cardSectionButtonList.add("buttons", cardSectionButtonListButtons); JsonObject cardSectionButtonListWidget = new JsonObject(); cardSectionButtonListWidget.add("buttonList", cardSectionButtonList); // Builds the form inputs with error texts for invalid values. JsonArray cardSection = new JsonArray(); if (errors.containsKey("name")) { cardSection.add(createErrorTextParagraph(errors.get("name").toString())); } cardSection.add(cardSectionTextInput1Widget); if (errors.containsKey("description")) { cardSection.add(createErrorTextParagraph(errors.get("description").toString())); } cardSection.add(cardSectionTextInput2Widget); if (errors.containsKey("priority")) { cardSection.add(createErrorTextParagraph(errors.get("priority").toString())); } cardSection.add(cardSectionSelectionInput1Widget); if (errors.containsKey("impact")) { cardSection.add(createErrorTextParagraph(errors.get("impact").toString())); } cardSection.add(cardSectionSelectionInput2Widget); cardSection.add(cardSectionButtonListWidget); JsonObject cardSectionWidgets = new JsonObject(); cardSectionWidgets.add("widgets", cardSection); JsonArray sections = new JsonArray(); sections.add(cardSectionWidgets); JsonObject card = new JsonObject(); card.add("header", cardHeader); card.add("sections", sections); JsonObject navigation = new JsonObject(); if (isUpdate) { navigation.add("updateCard", card); } else { navigation.add("pushCard", card); } JsonArray navigations = new JsonArray(); navigations.add(navigation); JsonObject action = new JsonObject(); action.add("navigations", navigations); JsonObject renderActions = new JsonObject(); renderActions.add("action", action); if (!isUpdate) { return renderActions; } JsonObject update = new JsonObject(); update.add("renderActions", renderActions); return update; }
Hàm createCaseInputCard
kết xuất thẻ sau:
Thẻ này bao gồm mục nhập văn bản, trình đơn thả xuống và hộp đánh dấu. Mẫu này cũng có một nút văn bản với thao tác onClick
chạy một hàm khác để xử lý việc gửi biểu mẫu tạo.
Sau khi người dùng điền vào biểu mẫu và nhấp vào Tạo, tiện ích bổ sung sẽ gửi dữ liệu đầu vào của biểu mẫu đến hàm hành động onClick
(gọi là submitCaseCreationForm
trong ví dụ của chúng ta). Tại thời điểm đó, tiện ích bổ sung có thể xác thực dữ liệu đầu vào và sử dụng chúng để tạo tài nguyên trong dịch vụ của bên thứ ba.
Xử lý việc gửi biểu mẫu
Sau khi người dùng gửi biểu mẫu tạo, hàm liên kết với thao tác onClick
sẽ chạy. Để có trải nghiệm người dùng lý tưởng, tiện ích bổ sung của bạn phải xử lý cả việc gửi biểu mẫu thành công và không thành công.
Xử lý quá trình tạo tài nguyên thành công
Hàm onClick
của tiện ích bổ sung sẽ tạo tài nguyên trong dịch vụ của bên thứ ba và tạo một URL trỏ đến tài nguyên đó.
Để giao tiếp URL của tài nguyên trở lại Tài liệu để tạo khối, hàm onClick
phải trả về SubmitFormResponse
với mảng một phần tử trong renderActions.action.links
trỏ đến một đường liên kết. Tiêu đề liên kết phải đại diện cho tiêu đề của tài nguyên được tạo và URL phải trỏ đến tài nguyên đó.
Ví dụ sau đây minh hoạ SubmitFormResponse
cho một tài nguyên đã tạo:
Apps Script
/** * Returns a submit form response that inserts a link into the document. * * @param {string} title The title of the link to insert. * @param {string} url The URL of the link to insert. * @return {!SubmitFormResponse} The resulting submit form response. */ function createLinkRenderAction(title, url) { return { renderActions: { action: { links: [{ title: title, url: url }] } } }; }
Node.js
/** * Returns a submit form response that inserts a link into the document. * * @param {string} title The title of the link to insert. * @param {string} url The URL of the link to insert. * @return {!SubmitFormResponse} The resulting submit form response. */ function createLinkRenderAction(title, url) { return { renderActions: { action: { links: [{ title: title, url: url }] } } }; }
Python
def create_link_render_action(title, url): """Returns a submit form response that inserts a link into the document. Args: title: The title of the link to insert. url: The URL of the link to insert. Returns: The resulting submit form response. """ return { "renderActions": { "action": { "links": [{ "title": title, "url": url }] } } }
Java
/** * Returns a submit form response that inserts a link into the document. * * @param title The title of the link to insert. * @param url The URL of the link to insert. * @return The resulting submit form response. */ JsonObject createLinkRenderAction(String title, String url) { JsonObject link = new JsonObject(); link.add("title", new JsonPrimitive(title)); link.add("url", new JsonPrimitive(url)); JsonArray links = new JsonArray(); links.add(link); JsonObject action = new JsonObject(); action.add("links", links); JsonObject renderActions = new JsonObject(); renderActions.add("action", action); JsonObject linkRenderAction = new JsonObject(); linkRenderAction.add("renderActions", renderActions); return linkRenderAction; }
Sau khi SubmitFormResponse
được trả về, hộp thoại phương thức sẽ đóng và tiện ích bổ sung sẽ chèn một khối vào tài liệu.
Khi người dùng giữ con trỏ trên khối này, con trỏ sẽ gọi trình kích hoạt bản xem trước đường liên kết được liên kết. Hãy đảm bảo tiện ích bổ sung của bạn không chèn các khối có mẫu liên kết mà trình kích hoạt xem trước đường liên kết không hỗ trợ.
Xử lý lỗi
Nếu người dùng cố gắng gửi biểu mẫu có các trường không hợp lệ, thay vì trả về SubmitFormResponse
kèm theo một đường liên kết, thì tiện ích bổ sung sẽ trả về thao tác kết xuất có hiển thị lỗi khi sử dụng thành phần điều hướng updateCard
.
Điều này cho phép người dùng xem họ đã làm gì sai và thử lại. Hãy xem updateCard(card)
đối với Apps Script và updateCard
đối với các môi trường thời gian chạy khác. Không hỗ trợ thông báo và chỉ đường pushCard
.
Ví dụ về cách xử lý lỗi
Ví dụ sau đây cho thấy mã được gọi khi người dùng gửi biểu mẫu. Nếu dữ liệu đầu vào không hợp lệ, thẻ sẽ cập nhật và hiện thông báo lỗi. Nếu dữ liệu đầu vào hợp lệ, tiện ích bổ sung sẽ trả về SubmitFormResponse
có đường liên kết đến tài nguyên được tạo.
Apps Script
/** * Submits the creation form. If valid, returns a render action * that inserts a new link into the document. If invalid, returns an * update card navigation that re-renders the creation form with error messages. * * @param {!Object} event The event object with form input values. * @return {!ActionResponse|!SubmitFormResponse} The resulting response. */ function submitCaseCreationForm(event) { const caseDetails = { name: event.formInput.name, description: event.formInput.description, priority: event.formInput.priority, impact: !!event.formInput.impact, }; const errors = validateFormInputs(caseDetails); if (Object.keys(errors).length > 0) { return createCaseInputCard(event, errors, /* isUpdate= */ true); } else { const title = `Case ${caseDetails.name}`; // Adds the case details as parameters to the generated link URL. const url = 'https://example.com/support/cases/?' + generateQuery(caseDetails); return createLinkRenderAction(title, url); } } /** * Build a query path with URL parameters. * * @param {!Map} parameters A map with the URL parameters. * @return {!string} The resulting query path. */ function generateQuery(parameters) { return Object.entries(parameters).flatMap(([k, v]) => Array.isArray(v) ? v.map(e => `${k}=${encodeURIComponent(e)}`) : `${k}=${encodeURIComponent(v)}` ).join("&"); }
Node.js
/** * Submits the creation form. If valid, returns a render action * that inserts a new link into the document. If invalid, returns an * update card navigation that re-renders the creation form with error messages. * * @param {!Object} event The event object with form input values. * @return {!ActionResponse|!SubmitFormResponse} The resulting response. */ function submitCaseCreationForm(event) { const caseDetails = { name: event.commonEventObject.formInputs?.name?.stringInputs?.value[0], description: event.commonEventObject.formInputs?.description?.stringInputs?.value[0], priority: event.commonEventObject.formInputs?.priority?.stringInputs?.value[0], impact: !!event.commonEventObject.formInputs?.impact?.stringInputs?.value[0], }; const errors = validateFormInputs(caseDetails); if (Object.keys(errors).length > 0) { return createCaseInputCard(event, errors, /* isUpdate= */ true); } else { const title = `Case ${caseDetails.name}`; // Adds the case details as parameters to the generated link URL. const url = new URL('https://example.com/support/cases/'); for (const [key, value] of Object.entries(caseDetails)) { url.searchParams.append(key, value); } return createLinkRenderAction(title, url.href); } }
Python
def submit_case_creation_form(event): """Submits the creation form. If valid, returns a render action that inserts a new link into the document. If invalid, returns an update card navigation that re-renders the creation form with error messages. Args: event: The event object with form input values. Returns: The resulting response. """ formInputs = event["commonEventObject"]["formInputs"] if "formInputs" in event["commonEventObject"] else None case_details = { "name": None, "description": None, "priority": None, "impact": None, } if formInputs is not None: case_details["name"] = formInputs["name"]["stringInputs"]["value"][0] if "name" in formInputs else None case_details["description"] = formInputs["description"]["stringInputs"]["value"][0] if "description" in formInputs else None case_details["priority"] = formInputs["priority"]["stringInputs"]["value"][0] if "priority" in formInputs else None case_details["impact"] = formInputs["impact"]["stringInputs"]["value"][0] if "impact" in formInputs else False errors = validate_form_inputs(case_details) if len(errors) > 0: return create_case_input_card(event, errors, True) # Update mode else: title = f'Case {case_details["name"]}' # Adds the case details as parameters to the generated link URL. url = "https://example.com/support/cases/?" + urlencode(case_details) return create_link_render_action(title, url)
Java
/** * Submits the creation form. If valid, returns a render action * that inserts a new link into the document. If invalid, returns an * update card navigation that re-renders the creation form with error messages. * * @param event The event object with form input values. * @return The resulting response. */ JsonObject submitCaseCreationForm(JsonObject event) throws Exception { JsonObject formInputs = event.getAsJsonObject("commonEventObject").getAsJsonObject("formInputs"); Map<String, String> caseDetails = new HashMap<String, String>(); if (formInputs != null) { if (formInputs.has("name")) { caseDetails.put("name", formInputs.getAsJsonObject("name").getAsJsonObject("stringInputs").getAsJsonArray("value").get(0).getAsString()); } if (formInputs.has("description")) { caseDetails.put("description", formInputs.getAsJsonObject("description").getAsJsonObject("stringInputs").getAsJsonArray("value").get(0).getAsString()); } if (formInputs.has("priority")) { caseDetails.put("priority", formInputs.getAsJsonObject("priority").getAsJsonObject("stringInputs").getAsJsonArray("value").get(0).getAsString()); } if (formInputs.has("impact")) { caseDetails.put("impact", formInputs.getAsJsonObject("impact").getAsJsonObject("stringInputs").getAsJsonArray("value").get(0).getAsString()); } } Map<String, String> errors = validateFormInputs(caseDetails); if (errors.size() > 0) { return createCaseInputCard(event, errors, /* isUpdate= */ true); } else { String title = String.format("Case %s", caseDetails.get("name")); // Adds the case details as parameters to the generated link URL. URIBuilder uriBuilder = new URIBuilder("https://example.com/support/cases/"); for (String caseDetailKey : caseDetails.keySet()) { uriBuilder.addParameter(caseDetailKey, caseDetails.get(caseDetailKey)); } return createLinkRenderAction(title, uriBuilder.build().toURL().toString()); } }
Mã mẫu sau đây xác thực thông tin đầu vào trong biểu mẫu và tạo thông báo lỗi cho thông tin đầu vào không hợp lệ:
Apps Script
/** * Validates case creation form input values. * * @param {!Object} caseDetails The values of each form input submitted by the user. * @return {!Object} A map from field name to error message. An empty object * represents a valid form submission. */ function validateFormInputs(caseDetails) { const errors = {}; if (!caseDetails.name) { errors.name = 'You must provide a name'; } if (!caseDetails.description) { errors.description = 'You must provide a description'; } if (!caseDetails.priority) { errors.priority = 'You must provide a priority'; } if (caseDetails.impact && caseDetails.priority !== 'P0' && caseDetails.priority !== 'P1') { errors.impact = 'If an issue blocks a critical customer operation, priority must be P0 or P1'; } return errors; } /** * Returns a text paragraph with red text indicating a form field validation error. * * @param {string} errorMessage A description of input value error. * @return {!TextParagraph} The resulting text paragraph. */ function createErrorTextParagraph(errorMessage) { return CardService.newTextParagraph() .setText('<font color=\"#BA0300\"><b>Error:</b> ' + errorMessage + '</font>'); }
Node.js
/** * Validates case creation form input values. * * @param {!Object} caseDetails The values of each form input submitted by the user. * @return {!Object} A map from field name to error message. An empty object * represents a valid form submission. */ function validateFormInputs(caseDetails) { const errors = {}; if (caseDetails.name === undefined) { errors.name = 'You must provide a name'; } if (caseDetails.description === undefined) { errors.description = 'You must provide a description'; } if (caseDetails.priority === undefined) { errors.priority = 'You must provide a priority'; } if (caseDetails.impact && !(['P0', 'P1']).includes(caseDetails.priority)) { errors.impact = 'If an issue blocks a critical customer operation, priority must be P0 or P1'; } return errors; } /** * Returns a text paragraph with red text indicating a form field validation error. * * @param {string} errorMessage A description of input value error. * @return {!TextParagraph} The resulting text paragraph. */ function createErrorTextParagraph(errorMessage) { return { textParagraph: { text: '<font color=\"#BA0300\"><b>Error:</b> ' + errorMessage + '</font>' } } }
Python
def validate_form_inputs(case_details): """Validates case creation form input values. Args: case_details: The values of each form input submitted by the user. Returns: A dict from field name to error message. An empty object represents a valid form submission. """ errors = {} if case_details["name"] is None: errors["name"] = "You must provide a name" if case_details["description"] is None: errors["description"] = "You must provide a description" if case_details["priority"] is None: errors["priority"] = "You must provide a priority" if case_details["impact"] is not None and case_details["priority"] not in ['P0', 'P1']: errors["impact"] = "If an issue blocks a critical customer operation, priority must be P0 or P1" return errors def create_error_text_paragraph(error_message): """Returns a text paragraph with red text indicating a form field validation error. Args: error_essage: A description of input value error. Returns: The resulting text paragraph. """ return { "textParagraph": { "text": '<font color=\"#BA0300\"><b>Error:</b> ' + error_message + '</font>' } }
Java
/** * Validates case creation form input values. * * @param caseDetails The values of each form input submitted by the user. * @return A map from field name to error message. An empty object * represents a valid form submission. */ Map<String, String> validateFormInputs(Map<String, String> caseDetails) { Map<String, String> errors = new HashMap<String, String>(); if (!caseDetails.containsKey("name")) { errors.put("name", "You must provide a name"); } if (!caseDetails.containsKey("description")) { errors.put("description", "You must provide a description"); } if (!caseDetails.containsKey("priority")) { errors.put("priority", "You must provide a priority"); } if (caseDetails.containsKey("impact") && !Arrays.asList(new String[]{"P0", "P1"}).contains(caseDetails.get("priority"))) { errors.put("impact", "If an issue blocks a critical customer operation, priority must be P0 or P1"); } return errors; } /** * Returns a text paragraph with red text indicating a form field validation error. * * @param errorMessage A description of input value error. * @return The resulting text paragraph. */ JsonObject createErrorTextParagraph(String errorMessage) { JsonObject textParagraph = new JsonObject(); textParagraph.add("text", new JsonPrimitive("<font color=\"#BA0300\"><b>Error:</b> " + errorMessage + "</font>")); JsonObject textParagraphWidget = new JsonObject(); textParagraphWidget.add("textParagraph", textParagraph); return textParagraphWidget; }
Ví dụ đầy đủ: Tiện ích bổ sung cho yêu cầu hỗ trợ
Ví dụ sau đây cho thấy một tiện ích bổ sung của Google Workspace giúp xem trước đường liên kết đến các yêu cầu hỗ trợ của công ty và cho phép người dùng tạo các yêu cầu hỗ trợ trong Google Tài liệu.
Ví dụ sẽ thực hiện những việc sau:
- Tạo một thẻ có các trường trên biểu mẫu để tạo trường hợp hỗ trợ trong trình đơn Tài liệu @.
- Xác thực thông tin đầu vào của biểu mẫu và trả về thông báo lỗi đối với thông tin đầu vào không hợp lệ.
- Chèn tên và đường liên kết của yêu cầu hỗ trợ đã tạo vào tài liệu trong Tài liệu dưới dạng một khối thông minh.
- Xem trước đường liên kết đến yêu cầu hỗ trợ, chẳng hạn như
https://www.example.com/support/cases/1234
. Khối thông minh hiển thị một biểu tượng và thẻ xem trước bao gồm tên trường hợp, mức độ ưu tiên và nội dung mô tả.
Tài nguyên triển khai
Apps Script
{ "timeZone": "America/New_York", "exceptionLogging": "STACKDRIVER", "runtimeVersion": "V8", "oauthScopes": [ "https://www.googleapis.com/auth/workspace.linkpreview", "https://www.googleapis.com/auth/workspace.linkcreate" ], "addOns": { "common": { "name": "Manage support cases", "logoUrl": "https://developers.google.com/workspace/add-ons/images/support-icon.png", "layoutProperties": { "primaryColor": "#dd4b39" } }, "docs": { "linkPreviewTriggers": [ { "runFunction": "caseLinkPreview", "patterns": [ { "hostPattern": "example.com", "pathPrefix": "support/cases" }, { "hostPattern": "*.example.com", "pathPrefix": "cases" }, { "hostPattern": "cases.example.com" } ], "labelText": "Support case", "localizedLabelText": { "es": "Caso de soporte" }, "logoUrl": "https://developers.google.com/workspace/add-ons/images/support-icon.png" } ], "createActionTriggers": [ { "id": "createCase", "labelText": "Create support case", "localizedLabelText": { "es": "Crear caso de soporte" }, "runFunction": "createCaseInputCard", "logoUrl": "https://developers.google.com/workspace/add-ons/images/support-icon.png" } ] } } }
Node.js
{ "oauthScopes": [ "https://www.googleapis.com/auth/workspace.linkpreview", "https://www.googleapis.com/auth/workspace.linkcreate" ], "addOns": { "common": { "name": "Manage support cases", "logoUrl": "https://developers.google.com/workspace/add-ons/images/support-icon.png", "layoutProperties": { "primaryColor": "#dd4b39" } }, "docs": { "linkPreviewTriggers": [ { "runFunction": "$URL1", "patterns": [ { "hostPattern": "example.com", "pathPrefix": "support/cases" }, { "hostPattern": "*.example.com", "pathPrefix": "cases" }, { "hostPattern": "cases.example.com" } ], "labelText": "Support case", "localizedLabelText": { "es": "Caso de soporte" }, "logoUrl": "https://developers.google.com/workspace/add-ons/images/support-icon.png" } ], "createActionTriggers": [ { "id": "createCase", "labelText": "Create support case", "localizedLabelText": { "es": "Crear caso de soporte" }, "runFunction": "$URL2", "logoUrl": "https://developers.google.com/workspace/add-ons/images/support-icon.png" } ] } } }
Mã
Apps Script
/** * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Entry point for a support case link preview. * * @param {!Object} event The event object. * @return {!Card} The resulting preview link card. */ function caseLinkPreview(event) { // If the event object URL matches a specified pattern for support case links. if (event.docs.matchedUrl.url) { // Uses the event object to parse the URL and identify the case details. const caseDetails = parseQuery(event.docs.matchedUrl.url); // Builds a preview card with the case name, and description const caseHeader = CardService.newCardHeader() .setTitle(`Case ${caseDetails["name"][0]}`); const caseDescription = CardService.newTextParagraph() .setText(caseDetails["description"][0]); // Returns the card. // Uses the text from the card's header for the title of the smart chip. return CardService.newCardBuilder() .setHeader(caseHeader) .addSection(CardService.newCardSection().addWidget(caseDescription)) .build(); } } /** * Extracts the URL parameters from the given URL. * * @param {!string} url The URL to parse. * @return {!Map} A map with the extracted URL parameters. */ function parseQuery(url) { const query = url.split("?")[1]; if (query) { return query.split("&") .reduce(function(o, e) { var temp = e.split("="); var key = temp[0].trim(); var value = temp[1].trim(); value = isNaN(value) ? value : Number(value); if (o[key]) { o[key].push(value); } else { o[key] = [value]; } return o; }, {}); } return null; } /** * Produces a support case creation form card. * * @param {!Object} event The event object. * @param {!Object=} errors An optional map of per-field error messages. * @param {boolean} isUpdate Whether to return the form as an update card navigation. * @return {!Card|!ActionResponse} The resulting card or action response. */ function createCaseInputCard(event, errors, isUpdate) { const cardHeader = CardService.newCardHeader() .setTitle('Create a support case') const cardSectionTextInput1 = CardService.newTextInput() .setFieldName('name') .setTitle('Name') .setMultiline(false); const cardSectionTextInput2 = CardService.newTextInput() .setFieldName('description') .setTitle('Description') .setMultiline(true); const cardSectionSelectionInput1 = CardService.newSelectionInput() .setFieldName('priority') .setTitle('Priority') .setType(CardService.SelectionInputType.DROPDOWN) .addItem('P0', 'P0', false) .addItem('P1', 'P1', false) .addItem('P2', 'P2', false) .addItem('P3', 'P3', false); const cardSectionSelectionInput2 = CardService.newSelectionInput() .setFieldName('impact') .setTitle('Impact') .setType(CardService.SelectionInputType.CHECK_BOX) .addItem('Blocks a critical customer operation', 'Blocks a critical customer operation', false); const cardSectionButtonListButtonAction = CardService.newAction() .setPersistValues(true) .setFunctionName('submitCaseCreationForm') .setParameters({}); const cardSectionButtonListButton = CardService.newTextButton() .setText('Create') .setTextButtonStyle(CardService.TextButtonStyle.TEXT) .setOnClickAction(cardSectionButtonListButtonAction); const cardSectionButtonList = CardService.newButtonSet() .addButton(cardSectionButtonListButton); // Builds the form inputs with error texts for invalid values. const cardSection = CardService.newCardSection(); if (errors?.name) { cardSection.addWidget(createErrorTextParagraph(errors.name)); } cardSection.addWidget(cardSectionTextInput1); if (errors?.description) { cardSection.addWidget(createErrorTextParagraph(errors.description)); } cardSection.addWidget(cardSectionTextInput2); if (errors?.priority) { cardSection.addWidget(createErrorTextParagraph(errors.priority)); } cardSection.addWidget(cardSectionSelectionInput1); if (errors?.impact) { cardSection.addWidget(createErrorTextParagraph(errors.impact)); } cardSection.addWidget(cardSectionSelectionInput2); cardSection.addWidget(cardSectionButtonList); const card = CardService.newCardBuilder() .setHeader(cardHeader) .addSection(cardSection) .build(); if (isUpdate) { return CardService.newActionResponseBuilder() .setNavigation(CardService.newNavigation().updateCard(card)) .build(); } else { return card; } } /** * Submits the creation form. If valid, returns a render action * that inserts a new link into the document. If invalid, returns an * update card navigation that re-renders the creation form with error messages. * * @param {!Object} event The event object with form input values. * @return {!ActionResponse|!SubmitFormResponse} The resulting response. */ function submitCaseCreationForm(event) { const caseDetails = { name: event.formInput.name, description: event.formInput.description, priority: event.formInput.priority, impact: !!event.formInput.impact, }; const errors = validateFormInputs(caseDetails); if (Object.keys(errors).length > 0) { return createCaseInputCard(event, errors, /* isUpdate= */ true); } else { const title = `Case ${caseDetails.name}`; // Adds the case details as parameters to the generated link URL. const url = 'https://example.com/support/cases/?' + generateQuery(caseDetails); return createLinkRenderAction(title, url); } } /** * Build a query path with URL parameters. * * @param {!Map} parameters A map with the URL parameters. * @return {!string} The resulting query path. */ function generateQuery(parameters) { return Object.entries(parameters).flatMap(([k, v]) => Array.isArray(v) ? v.map(e => `${k}=${encodeURIComponent(e)}`) : `${k}=${encodeURIComponent(v)}` ).join("&"); } /** * Validates case creation form input values. * * @param {!Object} caseDetails The values of each form input submitted by the user. * @return {!Object} A map from field name to error message. An empty object * represents a valid form submission. */ function validateFormInputs(caseDetails) { const errors = {}; if (!caseDetails.name) { errors.name = 'You must provide a name'; } if (!caseDetails.description) { errors.description = 'You must provide a description'; } if (!caseDetails.priority) { errors.priority = 'You must provide a priority'; } if (caseDetails.impact && caseDetails.priority !== 'P0' && caseDetails.priority !== 'P1') { errors.impact = 'If an issue blocks a critical customer operation, priority must be P0 or P1'; } return errors; } /** * Returns a text paragraph with red text indicating a form field validation error. * * @param {string} errorMessage A description of input value error. * @return {!TextParagraph} The resulting text paragraph. */ function createErrorTextParagraph(errorMessage) { return CardService.newTextParagraph() .setText('<font color=\"#BA0300\"><b>Error:</b> ' + errorMessage + '</font>'); } /** * Returns a submit form response that inserts a link into the document. * * @param {string} title The title of the link to insert. * @param {string} url The URL of the link to insert. * @return {!SubmitFormResponse} The resulting submit form response. */ function createLinkRenderAction(title, url) { return { renderActions: { action: { links: [{ title: title, url: url }] } } }; }
Node.js
/** * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Responds to any HTTP request related to link previews. * * @param {Object} req An HTTP request context. * @param {Object} res An HTTP response context. */ exports.createLinkPreview = (req, res) => { const event = req.body; if (event.docs.matchedUrl.url) { const url = event.docs.matchedUrl.url; const parsedUrl = new URL(url); // If the event object URL matches a specified pattern for preview links. if (parsedUrl.hostname === 'example.com') { if (parsedUrl.pathname.startsWith('/support/cases/')) { return res.json(caseLinkPreview(parsedUrl)); } } } }; /** * * A support case link preview. * * @param {!URL} url The event object. * @return {!Card} The resulting preview link card. */ function caseLinkPreview(url) { // Builds a preview card with the case name, and description // Uses the text from the card's header for the title of the smart chip. // Parses the URL and identify the case details. const name = `Case ${url.searchParams.get("name")}`; return { action: { linkPreview: { title: name, previewCard: { header: { title: name }, sections: [{ widgets: [{ textParagraph: { text: url.searchParams.get("description") } }] }] } } } }; } /** * Responds to any HTTP request related to 3P resource creations. * * @param {Object} req An HTTP request context. * @param {Object} res An HTTP response context. */ exports.create3pResources = (req, res) => { const event = req.body; if (event.commonEventObject.parameters?.submitCaseCreationForm) { res.json(submitCaseCreationForm(event)); } else { res.json(createCaseInputCard(event)); } }; /** * Produces a support case creation form card. * * @param {!Object} event The event object. * @param {!Object=} errors An optional map of per-field error messages. * @param {boolean} isUpdate Whether to return the form as an update card navigation. * @return {!Card|!ActionResponse} The resulting card or action response. */ function createCaseInputCard(event, errors, isUpdate) { const cardHeader1 = { title: "Create a support case" }; const cardSection1TextInput1 = { textInput: { name: "name", label: "Name" } }; const cardSection1TextInput2 = { textInput: { name: "description", label: "Description", type: "MULTIPLE_LINE" } }; const cardSection1SelectionInput1 = { selectionInput: { name: "priority", label: "Priority", type: "DROPDOWN", items: [{ text: "P0", value: "P0" }, { text: "P1", value: "P1" }, { text: "P2", value: "P2" }, { text: "P3", value: "P3" }] } }; const cardSection1SelectionInput2 = { selectionInput: { name: "impact", label: "Impact", items: [{ text: "Blocks a critical customer operation", value: "Blocks a critical customer operation" }] } }; const cardSection1ButtonList1Button1Action1 = { function: process.env.URL, parameters: [ { key: "submitCaseCreationForm", value: true } ], persistValues: true }; const cardSection1ButtonList1Button1 = { text: "Create", onClick: { action: cardSection1ButtonList1Button1Action1 } }; const cardSection1ButtonList1 = { buttonList: { buttons: [cardSection1ButtonList1Button1] } }; // Builds the creation form and adds error text for invalid inputs. const cardSection1 = []; if (errors?.name) { cardSection1.push(createErrorTextParagraph(errors.name)); } cardSection1.push(cardSection1TextInput1); if (errors?.description) { cardSection1.push(createErrorTextParagraph(errors.description)); } cardSection1.push(cardSection1TextInput2); if (errors?.priority) { cardSection1.push(createErrorTextParagraph(errors.priority)); } cardSection1.push(cardSection1SelectionInput1); if (errors?.impact) { cardSection1.push(createErrorTextParagraph(errors.impact)); } cardSection1.push(cardSection1SelectionInput2); cardSection1.push(cardSection1ButtonList1); const card = { header: cardHeader1, sections: [{ widgets: cardSection1 }] }; if (isUpdate) { return { renderActions: { action: { navigations: [{ updateCard: card }] } } }; } else { return { action: { navigations: [{ pushCard: card }] } }; } } /** * Submits the creation form. If valid, returns a render action * that inserts a new link into the document. If invalid, returns an * update card navigation that re-renders the creation form with error messages. * * @param {!Object} event The event object with form input values. * @return {!ActionResponse|!SubmitFormResponse} The resulting response. */ function submitCaseCreationForm(event) { const caseDetails = { name: event.commonEventObject.formInputs?.name?.stringInputs?.value[0], description: event.commonEventObject.formInputs?.description?.stringInputs?.value[0], priority: event.commonEventObject.formInputs?.priority?.stringInputs?.value[0], impact: !!event.commonEventObject.formInputs?.impact?.stringInputs?.value[0], }; const errors = validateFormInputs(caseDetails); if (Object.keys(errors).length > 0) { return createCaseInputCard(event, errors, /* isUpdate= */ true); } else { const title = `Case ${caseDetails.name}`; // Adds the case details as parameters to the generated link URL. const url = new URL('https://example.com/support/cases/'); for (const [key, value] of Object.entries(caseDetails)) { url.searchParams.append(key, value); } return createLinkRenderAction(title, url.href); } } /** * Validates case creation form input values. * * @param {!Object} caseDetails The values of each form input submitted by the user. * @return {!Object} A map from field name to error message. An empty object * represents a valid form submission. */ function validateFormInputs(caseDetails) { const errors = {}; if (caseDetails.name === undefined) { errors.name = 'You must provide a name'; } if (caseDetails.description === undefined) { errors.description = 'You must provide a description'; } if (caseDetails.priority === undefined) { errors.priority = 'You must provide a priority'; } if (caseDetails.impact && !(['P0', 'P1']).includes(caseDetails.priority)) { errors.impact = 'If an issue blocks a critical customer operation, priority must be P0 or P1'; } return errors; } /** * Returns a text paragraph with red text indicating a form field validation error. * * @param {string} errorMessage A description of input value error. * @return {!TextParagraph} The resulting text paragraph. */ function createErrorTextParagraph(errorMessage) { return { textParagraph: { text: '<font color=\"#BA0300\"><b>Error:</b> ' + errorMessage + '</font>' } } } /** * Returns a submit form response that inserts a link into the document. * * @param {string} title The title of the link to insert. * @param {string} url The URL of the link to insert. * @return {!SubmitFormResponse} The resulting submit form response. */ function createLinkRenderAction(title, url) { return { renderActions: { action: { links: [{ title: title, url: url }] } } }; }
Python
# Copyright 2024 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License") # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https:#www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from typing import Any, Mapping from urllib.parse import urlencode import os import flask import functions_framework @functions_framework.http def create_3p_resources(req: flask.Request): """Responds to any HTTP request related to 3P resource creations. Args: req: An HTTP request context. Returns: An HTTP response context. """ event = req.get_json(silent=True) parameters = event["commonEventObject"]["parameters"] if "parameters" in event["commonEventObject"] else None if parameters is not None and parameters["submitCaseCreationForm"]: return submit_case_creation_form(event) else: return create_case_input_card(event) def create_case_input_card(event, errors = {}, isUpdate = False): """Produces a support case creation form card. Args: event: The event object. errors: An optional dict of per-field error messages. isUpdate: Whether to return the form as an update card navigation. Returns: The resulting card or action response. """ card_header1 = { "title": "Create a support case" } card_section1_text_input1 = { "textInput": { "name": "name", "label": "Name" } } card_section1_text_input2 = { "textInput": { "name": "description", "label": "Description", "type": "MULTIPLE_LINE" } } card_section1_selection_input1 = { "selectionInput": { "name": "priority", "label": "Priority", "type": "DROPDOWN", "items": [{ "text": "P0", "value": "P0" }, { "text": "P1", "value": "P1" }, { "text": "P2", "value": "P2" }, { "text": "P3", "value": "P3" }] } } card_section1_selection_input2 = { "selectionInput": { "name": "impact", "label": "Impact", "items": [{ "text": "Blocks a critical customer operation", "value": "Blocks a critical customer operation" }] } } card_section1_button_list1_button1_action1 = { "function": os.environ["URL"], "parameters": [ { "key": "submitCaseCreationForm", "value": True } ], "persistValues": True } card_section1_button_list1_button1 = { "text": "Create", "onClick": { "action": card_section1_button_list1_button1_action1 } } card_section1_button_list1 = { "buttonList": { "buttons": [card_section1_button_list1_button1] } } # Builds the creation form and adds error text for invalid inputs. card_section1 = [] if "name" in errors: card_section1.append(create_error_text_paragraph(errors["name"])) card_section1.append(card_section1_text_input1) if "description" in errors: card_section1.append(create_error_text_paragraph(errors["description"])) card_section1.append(card_section1_text_input2) if "priority" in errors: card_section1.append(create_error_text_paragraph(errors["priority"])) card_section1.append(card_section1_selection_input1) if "impact" in errors: card_section1.append(create_error_text_paragraph(errors["impact"])) card_section1.append(card_section1_selection_input2) card_section1.append(card_section1_button_list1) card = { "header": card_header1, "sections": [{ "widgets": card_section1 }] } if isUpdate: return { "renderActions": { "action": { "navigations": [{ "updateCard": card }] } } } else: return { "action": { "navigations": [{ "pushCard": card }] } } def submit_case_creation_form(event): """Submits the creation form. If valid, returns a render action that inserts a new link into the document. If invalid, returns an update card navigation that re-renders the creation form with error messages. Args: event: The event object with form input values. Returns: The resulting response. """ formInputs = event["commonEventObject"]["formInputs"] if "formInputs" in event["commonEventObject"] else None case_details = { "name": None, "description": None, "priority": None, "impact": None, } if formInputs is not None: case_details["name"] = formInputs["name"]["stringInputs"]["value"][0] if "name" in formInputs else None case_details["description"] = formInputs["description"]["stringInputs"]["value"][0] if "description" in formInputs else None case_details["priority"] = formInputs["priority"]["stringInputs"]["value"][0] if "priority" in formInputs else None case_details["impact"] = formInputs["impact"]["stringInputs"]["value"][0] if "impact" in formInputs else False errors = validate_form_inputs(case_details) if len(errors) > 0: return create_case_input_card(event, errors, True) # Update mode else: title = f'Case {case_details["name"]}' # Adds the case details as parameters to the generated link URL. url = "https://example.com/support/cases/?" + urlencode(case_details) return create_link_render_action(title, url) def validate_form_inputs(case_details): """Validates case creation form input values. Args: case_details: The values of each form input submitted by the user. Returns: A dict from field name to error message. An empty object represents a valid form submission. """ errors = {} if case_details["name"] is None: errors["name"] = "You must provide a name" if case_details["description"] is None: errors["description"] = "You must provide a description" if case_details["priority"] is None: errors["priority"] = "You must provide a priority" if case_details["impact"] is not None and case_details["priority"] not in ['P0', 'P1']: errors["impact"] = "If an issue blocks a critical customer operation, priority must be P0 or P1" return errors def create_error_text_paragraph(error_message): """Returns a text paragraph with red text indicating a form field validation error. Args: error_essage: A description of input value error. Returns: The resulting text paragraph. """ return { "textParagraph": { "text": '<font color=\"#BA0300\"><b>Error:</b> ' + error_message + '</font>' } } def create_link_render_action(title, url): """Returns a submit form response that inserts a link into the document. Args: title: The title of the link to insert. url: The URL of the link to insert. Returns: The resulting submit form response. """ return { "renderActions": { "action": { "links": [{ "title": title, "url": url }] } } }
Đoạn mã sau đây cho biết cách triển khai bản xem trước đường liên kết cho tài nguyên đã tạo:
# Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License") # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https:#www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from typing import Any, Mapping from urllib.parse import urlparse, parse_qs import flask import functions_framework @functions_framework.http def create_link_preview(req: flask.Request): """Responds to any HTTP request related to link previews. Args: req: An HTTP request context. Returns: An HTTP response context. """ event = req.get_json(silent=True) if event["docs"]["matchedUrl"]["url"]: url = event["docs"]["matchedUrl"]["url"] parsed_url = urlparse(url) # If the event object URL matches a specified pattern for preview links. if parsed_url.hostname == "example.com": if parsed_url.path.startswith("/support/cases/"): return case_link_preview(parsed_url) return {} def case_link_preview(url): """A support case link preview. Args: url: A matching URL. Returns: The resulting preview link card. """ # Parses the URL and identify the case details. query_string = parse_qs(url.query) name = f'Case {query_string["name"][0]}' # Uses the text from the card's header for the title of the smart chip. return { "action": { "linkPreview": { "title": name, "previewCard": { "header": { "title": name }, "sections": [{ "widgets": [{ "textParagraph": { "text": query_string["description"][0] } }] }], } } } }
Java
/** * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import java.util.Arrays; import java.util.HashMap; import java.util.Map; import org.apache.http.client.utils.URIBuilder; import com.google.cloud.functions.HttpFunction; import com.google.cloud.functions.HttpRequest; import com.google.cloud.functions.HttpResponse; import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; public class Create3pResources implements HttpFunction { private static final Gson gson = new Gson(); /** * Responds to any HTTP request related to 3p resource creations. * * @param request An HTTP request context. * @param response An HTTP response context. */ @Override public void service(HttpRequest request, HttpResponse response) throws Exception { JsonObject event = gson.fromJson(request.getReader(), JsonObject.class); JsonObject parameters = event.getAsJsonObject("commonEventObject").getAsJsonObject("parameters"); if (parameters != null && parameters.has("submitCaseCreationForm") && parameters.get("submitCaseCreationForm").getAsBoolean()) { response.getWriter().write(gson.toJson(submitCaseCreationForm(event))); } else { response.getWriter().write(gson.toJson(createCaseInputCard(event, new HashMap<String, String>(), false))); } } /** * Produces a support case creation form. * * @param event The event object. * @param errors A map of per-field error messages. * @param isUpdate Whether to return the form as an update card navigation. * @return The resulting card or action response. */ JsonObject createCaseInputCard(JsonObject event, Map<String, String> errors, boolean isUpdate) { JsonObject cardHeader = new JsonObject(); cardHeader.add("title", new JsonPrimitive("Create a support case")); JsonObject cardSectionTextInput1 = new JsonObject(); cardSectionTextInput1.add("name", new JsonPrimitive("name")); cardSectionTextInput1.add("label", new JsonPrimitive("Name")); JsonObject cardSectionTextInput1Widget = new JsonObject(); cardSectionTextInput1Widget.add("textInput", cardSectionTextInput1); JsonObject cardSectionTextInput2 = new JsonObject(); cardSectionTextInput2.add("name", new JsonPrimitive("description")); cardSectionTextInput2.add("label", new JsonPrimitive("Description")); cardSectionTextInput2.add("type", new JsonPrimitive("MULTIPLE_LINE")); JsonObject cardSectionTextInput2Widget = new JsonObject(); cardSectionTextInput2Widget.add("textInput", cardSectionTextInput2); JsonObject cardSectionSelectionInput1ItemsItem1 = new JsonObject(); cardSectionSelectionInput1ItemsItem1.add("text", new JsonPrimitive("P0")); cardSectionSelectionInput1ItemsItem1.add("value", new JsonPrimitive("P0")); JsonObject cardSectionSelectionInput1ItemsItem2 = new JsonObject(); cardSectionSelectionInput1ItemsItem2.add("text", new JsonPrimitive("P1")); cardSectionSelectionInput1ItemsItem2.add("value", new JsonPrimitive("P1")); JsonObject cardSectionSelectionInput1ItemsItem3 = new JsonObject(); cardSectionSelectionInput1ItemsItem3.add("text", new JsonPrimitive("P2")); cardSectionSelectionInput1ItemsItem3.add("value", new JsonPrimitive("P2")); JsonObject cardSectionSelectionInput1ItemsItem4 = new JsonObject(); cardSectionSelectionInput1ItemsItem4.add("text", new JsonPrimitive("P3")); cardSectionSelectionInput1ItemsItem4.add("value", new JsonPrimitive("P3")); JsonArray cardSectionSelectionInput1Items = new JsonArray(); cardSectionSelectionInput1Items.add(cardSectionSelectionInput1ItemsItem1); cardSectionSelectionInput1Items.add(cardSectionSelectionInput1ItemsItem2); cardSectionSelectionInput1Items.add(cardSectionSelectionInput1ItemsItem3); cardSectionSelectionInput1Items.add(cardSectionSelectionInput1ItemsItem4); JsonObject cardSectionSelectionInput1 = new JsonObject(); cardSectionSelectionInput1.add("name", new JsonPrimitive("priority")); cardSectionSelectionInput1.add("label", new JsonPrimitive("Priority")); cardSectionSelectionInput1.add("type", new JsonPrimitive("DROPDOWN")); cardSectionSelectionInput1.add("items", cardSectionSelectionInput1Items); JsonObject cardSectionSelectionInput1Widget = new JsonObject(); cardSectionSelectionInput1Widget.add("selectionInput", cardSectionSelectionInput1); JsonObject cardSectionSelectionInput2ItemsItem = new JsonObject(); cardSectionSelectionInput2ItemsItem.add("text", new JsonPrimitive("Blocks a critical customer operation")); cardSectionSelectionInput2ItemsItem.add("value", new JsonPrimitive("Blocks a critical customer operation")); JsonArray cardSectionSelectionInput2Items = new JsonArray(); cardSectionSelectionInput2Items.add(cardSectionSelectionInput2ItemsItem); JsonObject cardSectionSelectionInput2 = new JsonObject(); cardSectionSelectionInput2.add("name", new JsonPrimitive("impact")); cardSectionSelectionInput2.add("label", new JsonPrimitive("Impact")); cardSectionSelectionInput2.add("items", cardSectionSelectionInput2Items); JsonObject cardSectionSelectionInput2Widget = new JsonObject(); cardSectionSelectionInput2Widget.add("selectionInput", cardSectionSelectionInput2); JsonObject cardSectionButtonListButtonActionParametersParameter = new JsonObject(); cardSectionButtonListButtonActionParametersParameter.add("key", new JsonPrimitive("submitCaseCreationForm")); cardSectionButtonListButtonActionParametersParameter.add("value", new JsonPrimitive(true)); JsonArray cardSectionButtonListButtonActionParameters = new JsonArray(); cardSectionButtonListButtonActionParameters.add(cardSectionButtonListButtonActionParametersParameter); JsonObject cardSectionButtonListButtonAction = new JsonObject(); cardSectionButtonListButtonAction.add("function", new JsonPrimitive(System.getenv().get("URL"))); cardSectionButtonListButtonAction.add("parameters", cardSectionButtonListButtonActionParameters); cardSectionButtonListButtonAction.add("persistValues", new JsonPrimitive(true)); JsonObject cardSectionButtonListButtonOnCLick = new JsonObject(); cardSectionButtonListButtonOnCLick.add("action", cardSectionButtonListButtonAction); JsonObject cardSectionButtonListButton = new JsonObject(); cardSectionButtonListButton.add("text", new JsonPrimitive("Create")); cardSectionButtonListButton.add("onClick", cardSectionButtonListButtonOnCLick); JsonArray cardSectionButtonListButtons = new JsonArray(); cardSectionButtonListButtons.add(cardSectionButtonListButton); JsonObject cardSectionButtonList = new JsonObject(); cardSectionButtonList.add("buttons", cardSectionButtonListButtons); JsonObject cardSectionButtonListWidget = new JsonObject(); cardSectionButtonListWidget.add("buttonList", cardSectionButtonList); // Builds the form inputs with error texts for invalid values. JsonArray cardSection = new JsonArray(); if (errors.containsKey("name")) { cardSection.add(createErrorTextParagraph(errors.get("name").toString())); } cardSection.add(cardSectionTextInput1Widget); if (errors.containsKey("description")) { cardSection.add(createErrorTextParagraph(errors.get("description").toString())); } cardSection.add(cardSectionTextInput2Widget); if (errors.containsKey("priority")) { cardSection.add(createErrorTextParagraph(errors.get("priority").toString())); } cardSection.add(cardSectionSelectionInput1Widget); if (errors.containsKey("impact")) { cardSection.add(createErrorTextParagraph(errors.get("impact").toString())); } cardSection.add(cardSectionSelectionInput2Widget); cardSection.add(cardSectionButtonListWidget); JsonObject cardSectionWidgets = new JsonObject(); cardSectionWidgets.add("widgets", cardSection); JsonArray sections = new JsonArray(); sections.add(cardSectionWidgets); JsonObject card = new JsonObject(); card.add("header", cardHeader); card.add("sections", sections); JsonObject navigation = new JsonObject(); if (isUpdate) { navigation.add("updateCard", card); } else { navigation.add("pushCard", card); } JsonArray navigations = new JsonArray(); navigations.add(navigation); JsonObject action = new JsonObject(); action.add("navigations", navigations); JsonObject renderActions = new JsonObject(); renderActions.add("action", action); if (!isUpdate) { return renderActions; } JsonObject update = new JsonObject(); update.add("renderActions", renderActions); return update; } /** * Submits the creation form. If valid, returns a render action * that inserts a new link into the document. If invalid, returns an * update card navigation that re-renders the creation form with error messages. * * @param event The event object with form input values. * @return The resulting response. */ JsonObject submitCaseCreationForm(JsonObject event) throws Exception { JsonObject formInputs = event.getAsJsonObject("commonEventObject").getAsJsonObject("formInputs"); Map<String, String> caseDetails = new HashMap<String, String>(); if (formInputs != null) { if (formInputs.has("name")) { caseDetails.put("name", formInputs.getAsJsonObject("name").getAsJsonObject("stringInputs").getAsJsonArray("value").get(0).getAsString()); } if (formInputs.has("description")) { caseDetails.put("description", formInputs.getAsJsonObject("description").getAsJsonObject("stringInputs").getAsJsonArray("value").get(0).getAsString()); } if (formInputs.has("priority")) { caseDetails.put("priority", formInputs.getAsJsonObject("priority").getAsJsonObject("stringInputs").getAsJsonArray("value").get(0).getAsString()); } if (formInputs.has("impact")) { caseDetails.put("impact", formInputs.getAsJsonObject("impact").getAsJsonObject("stringInputs").getAsJsonArray("value").get(0).getAsString()); } } Map<String, String> errors = validateFormInputs(caseDetails); if (errors.size() > 0) { return createCaseInputCard(event, errors, /* isUpdate= */ true); } else { String title = String.format("Case %s", caseDetails.get("name")); // Adds the case details as parameters to the generated link URL. URIBuilder uriBuilder = new URIBuilder("https://example.com/support/cases/"); for (String caseDetailKey : caseDetails.keySet()) { uriBuilder.addParameter(caseDetailKey, caseDetails.get(caseDetailKey)); } return createLinkRenderAction(title, uriBuilder.build().toURL().toString()); } } /** * Validates case creation form input values. * * @param caseDetails The values of each form input submitted by the user. * @return A map from field name to error message. An empty object * represents a valid form submission. */ Map<String, String> validateFormInputs(Map<String, String> caseDetails) { Map<String, String> errors = new HashMap<String, String>(); if (!caseDetails.containsKey("name")) { errors.put("name", "You must provide a name"); } if (!caseDetails.containsKey("description")) { errors.put("description", "You must provide a description"); } if (!caseDetails.containsKey("priority")) { errors.put("priority", "You must provide a priority"); } if (caseDetails.containsKey("impact") && !Arrays.asList(new String[]{"P0", "P1"}).contains(caseDetails.get("priority"))) { errors.put("impact", "If an issue blocks a critical customer operation, priority must be P0 or P1"); } return errors; } /** * Returns a text paragraph with red text indicating a form field validation error. * * @param errorMessage A description of input value error. * @return The resulting text paragraph. */ JsonObject createErrorTextParagraph(String errorMessage) { JsonObject textParagraph = new JsonObject(); textParagraph.add("text", new JsonPrimitive("<font color=\"#BA0300\"><b>Error:</b> " + errorMessage + "</font>")); JsonObject textParagraphWidget = new JsonObject(); textParagraphWidget.add("textParagraph", textParagraph); return textParagraphWidget; } /** * Returns a submit form response that inserts a link into the document. * * @param title The title of the link to insert. * @param url The URL of the link to insert. * @return The resulting submit form response. */ JsonObject createLinkRenderAction(String title, String url) { JsonObject link = new JsonObject(); link.add("title", new JsonPrimitive(title)); link.add("url", new JsonPrimitive(url)); JsonArray links = new JsonArray(); links.add(link); JsonObject action = new JsonObject(); action.add("links", links); JsonObject renderActions = new JsonObject(); renderActions.add("action", action); JsonObject linkRenderAction = new JsonObject(); linkRenderAction.add("renderActions", renderActions); return linkRenderAction; } }
Đoạn mã sau đây cho biết cách triển khai bản xem trước đường liên kết cho tài nguyên đã tạo:
/** * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import com.google.cloud.functions.HttpFunction; import com.google.cloud.functions.HttpRequest; import com.google.cloud.functions.HttpResponse; import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import java.io.UnsupportedEncodingException; import java.net.URL; import java.net.URLDecoder; import java.util.HashMap; import java.util.Map; public class CreateLinkPreview implements HttpFunction { private static final Gson gson = new Gson(); /** * Responds to any HTTP request related to link previews. * * @param request An HTTP request context. * @param response An HTTP response context. */ @Override public void service(HttpRequest request, HttpResponse response) throws Exception { JsonObject event = gson.fromJson(request.getReader(), JsonObject.class); String url = event.getAsJsonObject("docs") .getAsJsonObject("matchedUrl") .get("url") .getAsString(); URL parsedURL = new URL(url); // If the event object URL matches a specified pattern for preview links. if ("example.com".equals(parsedURL.getHost())) { if (parsedURL.getPath().startsWith("/support/cases/")) { response.getWriter().write(gson.toJson(caseLinkPreview(parsedURL))); return; } } response.getWriter().write("{}"); } /** * A support case link preview. * * @param url A matching URL. * @return The resulting preview link card. */ JsonObject caseLinkPreview(URL url) throws UnsupportedEncodingException { // Parses the URL and identify the case details. Map<String, String> caseDetails = new HashMap<String, String>(); for (String pair : url.getQuery().split("&")) { caseDetails.put(URLDecoder.decode(pair.split("=")[0], "UTF-8"), URLDecoder.decode(pair.split("=")[1], "UTF-8")); } // Builds a preview card with the case name, and description // Uses the text from the card's header for the title of the smart chip. JsonObject cardHeader = new JsonObject(); String caseName = String.format("Case %s", caseDetails.get("name")); cardHeader.add("title", new JsonPrimitive(caseName)); JsonObject textParagraph = new JsonObject(); textParagraph.add("text", new JsonPrimitive(caseDetails.get("description"))); JsonObject widget = new JsonObject(); widget.add("textParagraph", textParagraph); JsonArray widgets = new JsonArray(); widgets.add(widget); JsonObject section = new JsonObject(); section.add("widgets", widgets); JsonArray sections = new JsonArray(); sections.add(section); JsonObject previewCard = new JsonObject(); previewCard.add("header", cardHeader); previewCard.add("sections", sections); JsonObject linkPreview = new JsonObject(); linkPreview.add("title", new JsonPrimitive(caseName)); linkPreview.add("previewCard", previewCard); JsonObject action = new JsonObject(); action.add("linkPreview", linkPreview); JsonObject renderActions = new JsonObject(); renderActions.add("action", action); return renderActions; } }
Thông tin có liên quan
- Xem trước đường liên kết bằng khối thông minh
- Kiểm thử tiện ích bổ sung
- Tài nguyên triển khai Google Tài liệu
- Giao diện thẻ cho bản xem trước đường liên kết