Trang này giải thích cách tạo một tiện ích bổ sung cho Google Workspace để người dùng Google Tài liệu có thể tạo tài nguyên (chẳng hạn như yêu cầu hỗ trợ hoặc việc cần làm trong dự án) trong một dịch vụ 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 sẽ thêm các mục trong trình đơn để người dùng có thể 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 một tài nguyên trong dịch vụ của bạn ngay trong tài liệu Google Tài liệu, người dùng nhập @ trong tài liệu rồi chọn dịch vụ của bạn trong trình đơn @:
Khi người dùng nhập @ vào tài liệu và chọn dịch vụ của bạn, bạn sẽ cung cấp cho họ một thẻ có chứa các thông tin đầu vào của 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 của bạn 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 đã tạo. Khi người dùng di chuyển con trỏ lên khối này, khối này sẽ kích hoạt 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. Đả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 được các trình kích hoạt 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 Google Workspace hỗ trợ tính năng xem trước đường liên kết cho các mẫu đường liên kết của những tài nguyên mà người dùng tạo. Để tạo một tiện ích bổ sung có tính năng 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 Google Workspace hỗ trợ tính năng xem trước đường liên kết cho các mẫu đường liên kết của những tài nguyên mà người dùng tạo. Để tạo một tiện ích bổ sung có tính năng 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 Google Workspace hỗ trợ tính năng xem trước đường liên kết cho các mẫu đường liên kết của những tài nguyên mà người dùng tạo. Để tạo một tiện ích bổ sung có tính năng 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 Google Workspace hỗ trợ tính năng xem trước đường liên kết cho các mẫu đường liên kết của những tài nguyên mà người dùng tạo. Để tạo một tiện ích bổ sung có tính năng 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 quy trình 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 quy trình 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 việc tạo tài nguyên trong tệp kê khai của tiện ích bổ sung.
- Tạo 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 chạy khi người dùng gửi biểu mẫu.
Định cấu hình việc 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 trong tệp kê khai của tiện ích bổ sung:
Trong phần
addOnstrong trườngdocs, hãy triển khai điều kiện kích hoạtcreateActionTriggersbao gồmrunFunction. (Bạn sẽ xác định hàm này trong phần sau, Tạo thẻ biểu mẫu.)Để tìm hiểu về những trường mà bạn có thể chỉ định trong trình kích hoạt
createActionTriggers, hãy xem tài liệu tham khảo về 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 của một tệp kê khai định cấu hình việc tạo tài nguyên cho dịch vụ 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 Google Workspace cho phép người dùng tạo 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 @ của Tài liệu
- URL biểu trưng trỏ đến một biểu tượng xuất hiện bên cạnh văn bản nhãn trong trình đơn @
- Một 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ụ từ 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 gọi lại của trình kích hoạt này sẽ trình bày một thẻ có các đầu vào biểu mẫu để tạo tài nguyên.
Các phần tử và thao tác được hỗ trợ
Để tạo giao diện thẻ, bạn sử dụng các tiện ích để hiển thị 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 các tiện ích và thao tác bổ trợ của Google Workspace đều được hỗ trợ, ngoại trừ những trường hợp sau:
- Không hỗ trợ chân trang thẻ.
- Không hỗ trợ thông báo.
- Đối với các thành phần điều hướng, hệ thống chỉ hỗ trợ thành phần điều hướng
updateCard.
Ví dụ về thẻ có dữ liệu đầu vào của biểu mẫu
Ví dụ sau đây cho thấy một hàm gọi lại Apps Script hiển thị thẻ khi người dùng chọn Tạo yêu cầu hỗ trợ trong 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 hiển thị thẻ sau:

Thẻ này bao gồm các trường nhập văn bản, một trình đơn thả xuống và một hộp đánh dấu. Thành phần này cũng có một nút văn bản có 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 thông tin đầu vào của biểu mẫu đến hàm hành động onClick (được gọi là submitCaseCreationForm trong ví dụ của chúng tôi). Tại thời điểm đó, tiện ích bổ sung có thể xác thực thông tin đầu vào và sử dụng thông tin đó để tạo tài nguyên trong dịch vụ bên thứ ba.
Xử lý lượt gửi biểu mẫu
Sau khi người dùng gửi biểu mẫu tạo, hàm được liên kết với thao tác onClick sẽ chạy. Để mang lại trải nghiệm lý tưởng cho người dùng, tiện ích bổ sung của bạn phải xử lý cả trường hợp gửi biểu mẫu thành công và không thành công.
Xử lý việc tạo tài nguyên thành công
Hàm onClick của tiện ích bổ sung phải tạo tài nguyên trong dịch vụ bên thứ ba và tạo một URL trỏ đến tài nguyên đó.
Để truyền URL của tài nguyên trở lại Docs để tạo khối, hàm onClick phải trả về SubmitFormResponse có mảng gồm một phần tử trong renderActions.action.links trỏ đến một đường liên kết. Tiêu đề của đường 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 dẫn đến tài nguyên đó.
Ví dụ sau đây cho thấy 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 chip vào tài liệu.
Khi người dùng giữ con trỏ lên khối này, thao tác đó sẽ kích hoạt trình kích hoạt xem trước đường liên kết được liên kết. Đảm bảo rằng tiện ích bổ sung của bạn không chèn các khối có mẫu đường liên kết mà các điều kiện 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 có đường liên kết, tiện ích bổ sung sẽ trả về một thao tác hiển thị để hiển thị lỗi bằng cách sử dụng thành phần điều hướng updateCard.
Điều này giúp người dùng biết được lỗi họ mắc phải và thử lại. Hãy xem updateCard(card) đối với Apps Script và updateCard đối với các thời gian chạy khác. Không hỗ trợ thông báo và chế độ 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à cho thấy 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ề một SubmitFormResponse có đường liên kết đến tài nguyên đã 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("form<Inputs");> MapString, String caseD<etails = new H>ashMapString, 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("i<mpact")) >{ caseDetails.put("impact", formInputs.getAsJso>nObject("impact").getAsJsonObject("stringInputs").getAsJsonArray("value").get(0).getAsString()); } } MapString, 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 dữ liệu đầu vào của biểu mẫu và tạo thông báo lỗi cho dữ liệu đầ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 caseDe&&tails.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) { retu<rn CardService.newText><P>aragra<ph>() .setText('<font >color=\"#BA0300\"bError:/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 { text><P>aragra<ph>: { text: '<font >color=\"#BA0300\"bError:/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_<es>sage: A description of< inpu>t value error. Returns: The resulting text paragraph. """ return { "textParagraph": { "text": 'font color=\"#BA0300\"bError:/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&quo&&t;, "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. */ Jso<nObject createErrorTex><t>Paragr<ap>h(String errorMessage<) { > JsonObject textParagraph = new JsonObject(); textParagraph.add("text", new JsonPrimitive("font color=\"#BA0300\"bError:/b " + errorMessage + "/font")); JsonObject textParagraphWidget = new JsonObject(); textParagraphWidget.add("textParagraph", textParagraph); return textParagraphWidget; }
Ví dụ hoàn chỉnh: Tiện ích bổ sung yêu cầu hỗ trợ
Ví dụ sau đây minh hoạ một tiện ích bổ sung Google Workspace xem trước các đường liên kết đến các trường hợp hỗ trợ của một công ty và cho phép người dùng tạo các trường hợp hỗ trợ ngay trong Google Tài liệu.
Ví dụ này thực hiện những việc sau:
- Tạo một thẻ có các trường biểu mẫu để tạo yêu cầu hỗ trợ từ trình đơn @ của Tài liệu.
- Xác thực dữ liệu đầu vào của biểu mẫu và trả về thông báo lỗi cho dữ liệu đầu vào không hợp lệ.
- Chèn tên và đường liên kết của phiếu yêu cầu hỗ trợ đã tạo vào tài liệu trên 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ệp kê 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(&quo&t;?")[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 mus&&t 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 t<ext paragraph with red>< >text i<nd>icating a form field <valid>ation 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\"bError:/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.createLinkPr>eview = (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 re>sponse 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?.de>scription?.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<9;; } return erro><r>s; } </*>* * Returns a text p<aragr>aph 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\"bError:/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://ex<ample.com/support/case><s>/?&quo<t;> + urlencode(case_deta<ils) > 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\"bError:/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("parame&&ters"); if (parameters != null p&&arameters.has("submitCaseCreationForm") parameters.get("submitCaseCreationForm").getAsBoolean()) { response.getWriter().write(gson.toJson(submitCaseCreationForm(event))); } else { response.getWrite<r().write(gson>.toJson(createCaseInputCard(event, new HashMapString, 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. < */ JsonOb>ject createCaseInputCard(JsonObject event, MapString, 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("nav<igations">, navigations); JsonO<bject renderAc>tions = 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"); MapString, String caseDetails = new HashMapString, Str<ing(); 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.getAsJs<onObject(">;impact").getAsJso<nObject(">stringInputs").get<AsJsonArray(&q>uot;value").get(<0).getAsString>()); } } MapString, 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. */ MapString, String vali<dateFormInputs(MapStri><n>g, Str<in>g caseDetails) { <MapSt>ring, String errors = new HashMapString, 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\"bError:/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 Unsupported<EncodingExcept>ion { // Parses the UR<L and identify> the case details. MapString, String caseDeta&ils = new HashMapString, 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ệp kê khai Google Tài liệu
- Giao diện thẻ cho tính năng xem trước đường liên kết