Sử dụng bộ sưu tập để sắp xếp ngăn nắp các trang
Lưu và phân loại nội dung dựa trên lựa chọn ưu tiên của bạn.
Trang này giải thích cách tạo tiện ích bổ sung 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ư trường hợp hỗ trợ hoặc việc cần làm trong dự án, trong một dịch vụ của bên thứ ba từ bên 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 trong trình đơn cho phép 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ừ trong tài liệu Google Tài liệu, người dùng
hãy 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 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ẻ chứa các mục nhập 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ụ và tạo URL trỏ đến tài nguyên đó.
Tiện ích bổ sung 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 giữ con trỏ trên khối này, khối này sẽ gọi trình kích hoạt bản xem trước đường liên kết liên kết của tiện ích bổ sung. Đảm bảo rằng tiện ích bổ sung của bạn chèn các khối có mẫu đường liên kết đượ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 của Google Workspace hỗ trợ bản 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 một tiện ích bổ sung có bản xem trước đường liên kết, hãy tham khảo phần 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ợ bản 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 một tiện ích bổ sung có bản xem trước đường liên kết, hãy tham khảo phần 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ợ bản 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 một tiện ích bổ sung có bản xem trước đường liên kết, hãy tham khảo phần 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ợ bản 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 một tiện ích bổ sung có bản xem trước đường liên kết, hãy tham khảo phần 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 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 addOns của trường docs, hãy triển khai điều kiện kích hoạt createActionTriggers bao gồm runFunction. (Bạn sẽ xác định hàm này trong phần sau, Tạo thẻ biểu mẫu.)
Trong trường oauthScopes, hãy thêm phạm vi https://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 tệp kê khai định cấu hình việc tạo tài nguyên cho dịch vụ trường hợp hỗ trợ sau:
{"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 trường hợp hỗ trợ.
Mỗi trình 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 @
Hàm gọi lại 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 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, 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 đó sẽ hiển thị một thẻ có các mục nhập 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 của tiện ích bổ sung Google Workspace đều được hỗ trợ, ngoại trừ các trường hợp sau:
Không hỗ trợ chân thẻ.
Không hỗ trợ thông báo.
Đối với thành phần điều hướng, chỉ hỗ trợ thành phần điều hướng updateCard.
Ví dụ về thẻ có mục nhập biểu mẫu
Ví dụ sau đây cho thấy một hàm gọi lại trong Apps Script hiển thị một thẻ khi người dùng chọn Tạo trường hợp hỗ trợ trong trình đơn @:
/** * 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. */functioncreateCaseInputCard(event,errors,isUpdate){constcardHeader=CardService.newCardHeader().setTitle('Createasupportcase')constcardSectionTextInput1=CardService.newTextInput().setFieldName('name').setTitle('Name').setMultiline(false);constcardSectionTextInput2=CardService.newTextInput().setFieldName('description').setTitle('Description').setMultiline(true);constcardSectionSelectionInput1=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);constcardSectionSelectionInput2=CardService.newSelectionInput().setFieldName('impact').setTitle('Impact').setType(CardService.SelectionInputType.CHECK_BOX).addItem('Blocksacriticalcustomeroperation','Blocksacriticalcustomeroperation',false);constcardSectionButtonListButtonAction=CardService.newAction().setPersistValues(true).setFunctionName('submitCaseCreationForm').setParameters({});constcardSectionButtonListButton=CardService.newTextButton().setText('Create').setTextButtonStyle(CardService.TextButtonStyle.TEXT).setOnClickAction(cardSectionButtonListButtonAction);constcardSectionButtonList=CardService.newButtonSet().addButton(cardSectionButtonListButton);// Builds the form inputs with error texts for invalid values.constcardSection=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);constcard=CardService.newCardBuilder().setHeader(cardHeader).addSection(cardSection).build();if(isUpdate){returnCardService.newActionResponseBuilder().setNavigation(CardService.newNavigation().updateCard(card)).build();}else{returncard;}}
/** * 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. */functioncreateCaseInputCard(event,errors,isUpdate){constcardHeader1={title:"Create a support case"};constcardSection1TextInput1={textInput:{name:"name",label:"Name"}};constcardSection1TextInput2={textInput:{name:"description",label:"Description",type:"MULTIPLE_LINE"}};constcardSection1SelectionInput1={selectionInput:{name:"priority",label:"Priority",type:"DROPDOWN",items:[{text:"P0",value:"P0"},{text:"P1",value:"P1"},{text:"P2",value:"P2"},{text:"P3",value:"P3"}]}};constcardSection1SelectionInput2={selectionInput:{name:"impact",label:"Impact",items:[{text:"Blocks a critical customer operation",value:"Blocks a critical customer operation"}]}};constcardSection1ButtonList1Button1Action1={function:process.env.URL,parameters:[{key:"submitCaseCreationForm",value:true}],persistValues:true};constcardSection1ButtonList1Button1={text:"Create",onClick:{action:cardSection1ButtonList1Button1Action1}};constcardSection1ButtonList1={buttonList:{buttons:[cardSection1ButtonList1Button1]}};// Builds the creation form and adds error text for invalid inputs.constcardSection1=[];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);constcard={header:cardHeader1,sections:[{widgets:cardSection1}]};if(isUpdate){return{renderActions:{action:{navigations:[{updateCard:card}]}}};}else{return{action:{navigations:[{pushCard:card}]}};}}
defcreate_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"inerrors:card_section1.append(create_error_text_paragraph(errors["name"]))card_section1.append(card_section1_text_input1)if"description"inerrors:card_section1.append(create_error_text_paragraph(errors["description"]))card_section1.append(card_section1_text_input2)if"priority"inerrors:card_section1.append(create_error_text_paragraph(errors["priority"]))card_section1.append(card_section1_selection_input1)if"impact"inerrors: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}]}ifisUpdate:return{"renderActions":{"action":{"navigations":[{"updateCard":card}]}}}else:return{"action":{"navigations":[{"pushCard":card}]}}
/** * 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. */JsonObjectcreateCaseInputCard(JsonObjectevent,Map<String,String>errors,booleanisUpdate){JsonObjectcardHeader=newJsonObject();cardHeader.add("title",newJsonPrimitive("Create a support case"));JsonObjectcardSectionTextInput1=newJsonObject();cardSectionTextInput1.add("name",newJsonPrimitive("name"));cardSectionTextInput1.add("label",newJsonPrimitive("Name"));JsonObjectcardSectionTextInput1Widget=newJsonObject();cardSectionTextInput1Widget.add("textInput",cardSectionTextInput1);JsonObjectcardSectionTextInput2=newJsonObject();cardSectionTextInput2.add("name",newJsonPrimitive("description"));cardSectionTextInput2.add("label",newJsonPrimitive("Description"));cardSectionTextInput2.add("type",newJsonPrimitive("MULTIPLE_LINE"));JsonObjectcardSectionTextInput2Widget=newJsonObject();cardSectionTextInput2Widget.add("textInput",cardSectionTextInput2);JsonObjectcardSectionSelectionInput1ItemsItem1=newJsonObject();cardSectionSelectionInput1ItemsItem1.add("text",newJsonPrimitive("P0"));cardSectionSelectionInput1ItemsItem1.add("value",newJsonPrimitive("P0"));JsonObjectcardSectionSelectionInput1ItemsItem2=newJsonObject();cardSectionSelectionInput1ItemsItem2.add("text",newJsonPrimitive("P1"));cardSectionSelectionInput1ItemsItem2.add("value",newJsonPrimitive("P1"));JsonObjectcardSectionSelectionInput1ItemsItem3=newJsonObject();cardSectionSelectionInput1ItemsItem3.add("text",newJsonPrimitive("P2"));cardSectionSelectionInput1ItemsItem3.add("value",newJsonPrimitive("P2"));JsonObjectcardSectionSelectionInput1ItemsItem4=newJsonObject();cardSectionSelectionInput1ItemsItem4.add("text",newJsonPrimitive("P3"));cardSectionSelectionInput1ItemsItem4.add("value",newJsonPrimitive("P3"));JsonArraycardSectionSelectionInput1Items=newJsonArray();cardSectionSelectionInput1Items.add(cardSectionSelectionInput1ItemsItem1);cardSectionSelectionInput1Items.add(cardSectionSelectionInput1ItemsItem2);cardSectionSelectionInput1Items.add(cardSectionSelectionInput1ItemsItem3);cardSectionSelectionInput1Items.add(cardSectionSelectionInput1ItemsItem4);JsonObjectcardSectionSelectionInput1=newJsonObject();cardSectionSelectionInput1.add("name",newJsonPrimitive("priority"));cardSectionSelectionInput1.add("label",newJsonPrimitive("Priority"));cardSectionSelectionInput1.add("type",newJsonPrimitive("DROPDOWN"));cardSectionSelectionInput1.add("items",cardSectionSelectionInput1Items);JsonObjectcardSectionSelectionInput1Widget=newJsonObject();cardSectionSelectionInput1Widget.add("selectionInput",cardSectionSelectionInput1);JsonObjectcardSectionSelectionInput2ItemsItem=newJsonObject();cardSectionSelectionInput2ItemsItem.add("text",newJsonPrimitive("Blocks a critical customer operation"));cardSectionSelectionInput2ItemsItem.add("value",newJsonPrimitive("Blocks a critical customer operation"));JsonArraycardSectionSelectionInput2Items=newJsonArray();cardSectionSelectionInput2Items.add(cardSectionSelectionInput2ItemsItem);JsonObjectcardSectionSelectionInput2=newJsonObject();cardSectionSelectionInput2.add("name",newJsonPrimitive("impact"));cardSectionSelectionInput2.add("label",newJsonPrimitive("Impact"));cardSectionSelectionInput2.add("items",cardSectionSelectionInput2Items);JsonObjectcardSectionSelectionInput2Widget=newJsonObject();cardSectionSelectionInput2Widget.add("selectionInput",cardSectionSelectionInput2);JsonObjectcardSectionButtonListButtonActionParametersParameter=newJsonObject();cardSectionButtonListButtonActionParametersParameter.add("key",newJsonPrimitive("submitCaseCreationForm"));cardSectionButtonListButtonActionParametersParameter.add("value",newJsonPrimitive(true));JsonArraycardSectionButtonListButtonActionParameters=newJsonArray();cardSectionButtonListButtonActionParameters.add(cardSectionButtonListButtonActionParametersParameter);JsonObjectcardSectionButtonListButtonAction=newJsonObject();cardSectionButtonListButtonAction.add("function",newJsonPrimitive(System.getenv().get("URL")));cardSectionButtonListButtonAction.add("parameters",cardSectionButtonListButtonActionParameters);cardSectionButtonListButtonAction.add("persistValues",newJsonPrimitive(true));JsonObjectcardSectionButtonListButtonOnCLick=newJsonObject();cardSectionButtonListButtonOnCLick.add("action",cardSectionButtonListButtonAction);JsonObjectcardSectionButtonListButton=newJsonObject();cardSectionButtonListButton.add("text",newJsonPrimitive("Create"));cardSectionButtonListButton.add("onClick",cardSectionButtonListButtonOnCLick);JsonArraycardSectionButtonListButtons=newJsonArray();cardSectionButtonListButtons.add(cardSectionButtonListButton);JsonObjectcardSectionButtonList=newJsonObject();cardSectionButtonList.add("buttons",cardSectionButtonListButtons);JsonObjectcardSectionButtonListWidget=newJsonObject();cardSectionButtonListWidget.add("buttonList",cardSectionButtonList);// Builds the form inputs with error texts for invalid values.JsonArraycardSection=newJsonArray();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);JsonObjectcardSectionWidgets=newJsonObject();cardSectionWidgets.add("widgets",cardSection);JsonArraysections=newJsonArray();sections.add(cardSectionWidgets);JsonObjectcard=newJsonObject();card.add("header",cardHeader);card.add("sections",sections);JsonObjectnavigation=newJsonObject();if(isUpdate){navigation.add("updateCard",card);}else{navigation.add("pushCard",card);}JsonArraynavigations=newJsonArray();navigations.add(navigation);JsonObjectaction=newJsonObject();action.add("navigations",navigations);JsonObjectrenderActions=newJsonObject();renderActions.add("action",action);if(!isUpdate){returnrenderActions;}JsonObjectupdate=newJsonObject();update.add("renderActions",renderActions);returnupdate;}
Hàm createCaseInputCard hiển thị thẻ sau:
Thẻ này bao gồm các mục nhập văn bản, trình đơn thả xuống và hộp đánh dấu. Cửa sổ 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 (được gọi là submitCaseCreationForm trong ví dụ của chúng tôi). Tại thời điểm này, tiện ích bổ sung có thể xác thực dữ liệu đầu vào và sử dụng dữ liệu đó để tạo tài nguyên trong dịch vụ của 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 liên kết với hành động onClick sẽ chạy. Để mang lại 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ả 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 sẽ tạo tài nguyên trong dịch vụ của bên thứ ba và tạo URL trỏ đến tài nguyên đó.
Để thông báo URL của tài nguyên trở lại Docs để tạo khối, hàm onClick sẽ trả về một SubmitFormResponse với một mảng một phần tử trong renderActions.action.links trỏ đến một đường liên kết. Tiêu đề đường liên kết phải thể hiện tiêu đề của tài nguyên đã tạo và URL phải trỏ đến tài nguyên đó.
Ví dụ sau đây cho thấy SubmitFormResponse cho một tài nguyên đã tạo:
/** * 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. */functioncreateLinkRenderAction(title,url){return{renderActions:{action:{links:[{title:title,url:url}]}}};}
/** * 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. */functioncreateLinkRenderAction(title,url){return{renderActions:{action:{links:[{title:title,url:url}]}}};}
defcreate_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}]}}}
/** * 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. */JsonObjectcreateLinkRenderAction(Stringtitle,Stringurl){JsonObjectlink=newJsonObject();link.add("title",newJsonPrimitive(title));link.add("url",newJsonPrimitive(url));JsonArraylinks=newJsonArray();links.add(link);JsonObjectaction=newJsonObject();action.add("links",links);JsonObjectrenderActions=newJsonObject();renderActions.add("action",action);JsonObjectlinkRenderAction=newJsonObject();linkRenderAction.add("renderActions",renderActions);returnlinkRenderAction;}
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, khối này sẽ gọi trình kích hoạt xem trước đường liên kết liên quan. Đảm bảo tiện ích bổ sung của bạn không chèn chip có mẫu đường liên kết không được trình kích hoạt bản xem trước đường liên kết hỗ trợ.
Xử lý lỗi
Nếu người dùng cố gắng gửi một biểu mẫu có các trường không hợp lệ, thay vì trả về một SubmitFormResponse có đường liên kết, tiện ích bổ sung sẽ trả về một thao tác kết xuất hiển thị lỗi bằng cách sử dụng thao tác điều hướng updateCard.
Điều này cho phép người dùng xem lỗi họ đã mắc phải và thử lại. Hãy xem updateCard(card) cho Apps Script và updateCard cho các môi trường thời gian chạy khác. Không hỗ trợ thông báo và thao tác điều hướ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ị thông báo lỗi. Nếu dữ liệu đầu vào hợp lệ, thì tiện ích bổ sung sẽ trả về một SubmitFormResponse có đường liên kết đến tài nguyên đã tạo.
/** * 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. */functionsubmitCaseCreationForm(event){constcaseDetails={name:event.formInput.name,description:event.formInput.description,priority:event.formInput.priority,impact:!!event.formInput.impact,};consterrors=validateFormInputs(caseDetails);if(Object.keys(errors).length > 0){returncreateCaseInputCard(event,errors,/* isUpdate= */true);}else{consttitle=`Case${caseDetails.name}`;// Adds the case details as parameters to the generated link URL.consturl='https://example.com/support/cases/?' + generateQuery(caseDetails);returncreateLinkRenderAction(title,url);}}/*** Build a query path with URL parameters.** @param {!Map} parameters A map with the URL parameters.* @return {!string} The resulting query path.*/functiongenerateQuery(parameters){returnObject.entries(parameters).flatMap(([k,v])=>
Array.isArray(v)?v.map(e=>`${k}=${encodeURIComponent(e)}`):`${k}=${encodeURIComponent(v)}`).join("&");}
/** * 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. */functionsubmitCaseCreationForm(event){constcaseDetails={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],};consterrors=validateFormInputs(caseDetails);if(Object.keys(errors).length > 0){returncreateCaseInputCard(event,errors,/* isUpdate= */true);}else{consttitle=`Case ${caseDetails.name}`;// Adds the case details as parameters to the generated link URL.consturl=newURL('https://example.com/support/cases/');for(const[key,value]ofObject.entries(caseDetails)){url.searchParams.append(key,value);}returncreateLinkRenderAction(title,url.href);}}
defsubmit_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"inevent["commonEventObject"]elseNonecase_details={"name":None,"description":None,"priority":None,"impact":None,}ifformInputsisnotNone:case_details["name"]=formInputs["name"]["stringInputs"]["value"][0]if"name"informInputselseNonecase_details["description"]=formInputs["description"]["stringInputs"]["value"][0]if"description"informInputselseNonecase_details["priority"]=formInputs["priority"]["stringInputs"]["value"][0]if"priority"informInputselseNonecase_details["impact"]=formInputs["impact"]["stringInputs"]["value"][0]if"impact"informInputselseFalseerrors=validate_form_inputs(case_details)iflen(errors) > 0:returncreate_case_input_card(event,errors,True)# Update modeelse: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)returncreate_link_render_action(title,url)
/** * 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. */JsonObjectsubmitCaseCreationForm(JsonObjectevent)throwsException{JsonObjectformInputs=event.getAsJsonObject("commonEventObject").getAsJsonObject("formInputs");Map<String,String>caseDetails=newHashMap<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){returncreateCaseInputCard(event,errors,/* isUpdate= */true);}else{Stringtitle=String.format("Case %s",caseDetails.get("name"));// Adds the case details as parameters to the generated link URL.URIBuilderuriBuilder=newURIBuilder("https://example.com/support/cases/");for(StringcaseDetailKey:caseDetails.keySet()){uriBuilder.addParameter(caseDetailKey,caseDetails.get(caseDetailKey));}returncreateLinkRenderAction(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ệ:
/** * 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. */functionvalidateFormInputs(caseDetails){consterrors={};if(!caseDetails.name){errors.name='Youmustprovideaname';}if(!caseDetails.description){errors.description='Youmustprovideadescription';}if(!caseDetails.priority){errors.priority='Youmustprovideapriority';}if(caseDetails.impact && caseDetails.priority!=='P0' && caseDetails.priority!=='P1'){errors.impact='Ifanissueblocksacriticalcustomeroperation,prioritymustbeP0orP1';}returnerrors;}/** * 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. */functioncreateErrorTextParagraph(errorMessage){returnCardService.newTextParagraph().setText('<fontcolor=\"#BA0300\"><b>Error:</b>'+errorMessage+'</font>');}
/** * 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. */functionvalidateFormInputs(caseDetails){consterrors={};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';}returnerrors;}/** * 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. */functioncreateErrorTextParagraph(errorMessage){return{textParagraph:{text:'<font color=\"#BA0300\"><b>Error:</b> '+errorMessage+'</font>'}}}
defvalidate_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={}ifcase_details["name"]isNone:errors["name"]="You must provide a name"ifcase_details["description"]isNone:errors["description"]="You must provide a description"ifcase_details["priority"]isNone:errors["priority"]="You must provide a priority"ifcase_details["impact"]isnotNoneandcase_details["priority"]notin['P0','P1']:errors["impact"]="If an issue blocks a critical customer operation, priority must be P0 or P1"returnerrorsdefcreate_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>'}}
/** * 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=newHashMap<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(newString[]{"P0","P1"}).contains(caseDetails.get("priority"))){errors.put("impact","If an issue blocks a critical customer operation, priority must be P0 or P1");}returnerrors;}/** * 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. */JsonObjectcreateErrorTextParagraph(StringerrorMessage){JsonObjecttextParagraph=newJsonObject();textParagraph.add("text",newJsonPrimitive("<font color=\"#BA0300\"><b>Error:</b> "+errorMessage+"</font>"));JsonObjecttextParagraphWidget=newJsonObject();textParagraphWidget.add("textParagraph",textParagraph);returntextParagraphWidget;}
Ví dụ hoàn chỉnh: Tiện ích bổ sung về 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 các đường liên kết đến các trường hợp hỗ trợ của 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ợ trong trình đơn Docs @.
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 trường hợp 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ả.
{"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"}]}}}
{"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"}]}}}
/** * 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.*/functioncaseLinkPreview(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.constcaseDetails=parseQuery(event.docs.matchedUrl.url);// Builds a preview card with the case name, and descriptionconstcaseHeader=CardService.newCardHeader().setTitle(`Case${caseDetails["name"][0]}`);constcaseDescription=CardService.newTextParagraph().setText(caseDetails["description"][0]);// Returns the card.// Uses the text from the card's header for the title of the smart chip.returnCardService.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.*/functionparseQuery(url){constquery=url.split("?")[1];if(query){returnquery.split("&").reduce(function(o,e){vartemp=e.split("=");varkey=temp[0].trim();varvalue=temp[1].trim();value=isNaN(value)?value:Number(value);if(o[key]){o[key].push(value);}else{o[key]=[value];}returno;},{});}returnnull;}/** * 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. */functioncreateCaseInputCard(event,errors,isUpdate){constcardHeader=CardService.newCardHeader().setTitle('Createasupportcase')constcardSectionTextInput1=CardService.newTextInput().setFieldName('name').setTitle('Name').setMultiline(false);constcardSectionTextInput2=CardService.newTextInput().setFieldName('description').setTitle('Description').setMultiline(true);constcardSectionSelectionInput1=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);constcardSectionSelectionInput2=CardService.newSelectionInput().setFieldName('impact').setTitle('Impact').setType(CardService.SelectionInputType.CHECK_BOX).addItem('Blocksacriticalcustomeroperation','Blocksacriticalcustomeroperation',false);constcardSectionButtonListButtonAction=CardService.newAction().setPersistValues(true).setFunctionName('submitCaseCreationForm').setParameters({});constcardSectionButtonListButton=CardService.newTextButton().setText('Create').setTextButtonStyle(CardService.TextButtonStyle.TEXT).setOnClickAction(cardSectionButtonListButtonAction);constcardSectionButtonList=CardService.newButtonSet().addButton(cardSectionButtonListButton);// Builds the form inputs with error texts for invalid values.constcardSection=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);constcard=CardService.newCardBuilder().setHeader(cardHeader).addSection(cardSection).build();if(isUpdate){returnCardService.newActionResponseBuilder().setNavigation(CardService.newNavigation().updateCard(card)).build();}else{returncard;}}/** * 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. */functionsubmitCaseCreationForm(event){constcaseDetails={name:event.formInput.name,description:event.formInput.description,priority:event.formInput.priority,impact:!!event.formInput.impact,};consterrors=validateFormInputs(caseDetails);if(Object.keys(errors).length > 0){returncreateCaseInputCard(event,errors,/* isUpdate= */true);}else{consttitle=`Case${caseDetails.name}`;// Adds the case details as parameters to the generated link URL.consturl='https://example.com/support/cases/?' + generateQuery(caseDetails);returncreateLinkRenderAction(title,url);}}/*** Build a query path with URL parameters.** @param {!Map} parameters A map with the URL parameters.* @return {!string} The resulting query path.*/functiongenerateQuery(parameters){returnObject.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. */functionvalidateFormInputs(caseDetails){consterrors={};if(!caseDetails.name){errors.name='Youmustprovideaname';}if(!caseDetails.description){errors.description='Youmustprovideadescription';}if(!caseDetails.priority){errors.priority='Youmustprovideapriority';}if(caseDetails.impact && caseDetails.priority!=='P0' && caseDetails.priority!=='P1'){errors.impact='Ifanissueblocksacriticalcustomeroperation,prioritymustbeP0orP1';}returnerrors;}/** * 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. */functioncreateErrorTextParagraph(errorMessage){returnCardService.newTextParagraph().setText('<fontcolor=\"#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. */functioncreateLinkRenderAction(title,url){return{renderActions:{action:{links:[{title:title,url:url}]}}};}
/** * 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)=>{constevent=req.body;if(event.docs.matchedUrl.url){consturl=event.docs.matchedUrl.url;constparsedUrl=newURL(url);// If the event object URL matches a specified pattern for preview links.if(parsedUrl.hostname==='example.com'){if(parsedUrl.pathname.startsWith('/support/cases/')){returnres.json(caseLinkPreview(parsedUrl));}}}};/** * * A support case link preview. * * @param {!URL} url The event object. * @return {!Card} The resulting preview link card. */functioncaseLinkPreview(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.constname=`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)=>{constevent=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. */functioncreateCaseInputCard(event,errors,isUpdate){constcardHeader1={title:"Create a support case"};constcardSection1TextInput1={textInput:{name:"name",label:"Name"}};constcardSection1TextInput2={textInput:{name:"description",label:"Description",type:"MULTIPLE_LINE"}};constcardSection1SelectionInput1={selectionInput:{name:"priority",label:"Priority",type:"DROPDOWN",items:[{text:"P0",value:"P0"},{text:"P1",value:"P1"},{text:"P2",value:"P2"},{text:"P3",value:"P3"}]}};constcardSection1SelectionInput2={selectionInput:{name:"impact",label:"Impact",items:[{text:"Blocks a critical customer operation",value:"Blocks a critical customer operation"}]}};constcardSection1ButtonList1Button1Action1={function:process.env.URL,parameters:[{key:"submitCaseCreationForm",value:true}],persistValues:true};constcardSection1ButtonList1Button1={text:"Create",onClick:{action:cardSection1ButtonList1Button1Action1}};constcardSection1ButtonList1={buttonList:{buttons:[cardSection1ButtonList1Button1]}};// Builds the creation form and adds error text for invalid inputs.constcardSection1=[];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);constcard={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. */functionsubmitCaseCreationForm(event){constcaseDetails={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],};consterrors=validateFormInputs(caseDetails);if(Object.keys(errors).length > 0){returncreateCaseInputCard(event,errors,/* isUpdate= */true);}else{consttitle=`Case ${caseDetails.name}`;// Adds the case details as parameters to the generated link URL.consturl=newURL('https://example.com/support/cases/');for(const[key,value]ofObject.entries(caseDetails)){url.searchParams.append(key,value);}returncreateLinkRenderAction(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. */functionvalidateFormInputs(caseDetails){consterrors={};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';}returnerrors;}/** * 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. */functioncreateErrorTextParagraph(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. */functioncreateLinkRenderAction(title,url){return{renderActions:{action:{links:[{title:title,url:url}]}}};}
# 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.fromtypingimportAny,Mappingfromurllib.parseimporturlencodeimportosimportflaskimportfunctions_framework@functions_framework.httpdefcreate_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"inevent["commonEventObject"]elseNoneifparametersisnotNoneandparameters["submitCaseCreationForm"]:returnsubmit_case_creation_form(event)else:returncreate_case_input_card(event)defcreate_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"inerrors:card_section1.append(create_error_text_paragraph(errors["name"]))card_section1.append(card_section1_text_input1)if"description"inerrors:card_section1.append(create_error_text_paragraph(errors["description"]))card_section1.append(card_section1_text_input2)if"priority"inerrors:card_section1.append(create_error_text_paragraph(errors["priority"]))card_section1.append(card_section1_selection_input1)if"impact"inerrors: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}]}ifisUpdate:return{"renderActions":{"action":{"navigations":[{"updateCard":card}]}}}else:return{"action":{"navigations":[{"pushCard":card}]}}defsubmit_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"inevent["commonEventObject"]elseNonecase_details={"name":None,"description":None,"priority":None,"impact":None,}ifformInputsisnotNone:case_details["name"]=formInputs["name"]["stringInputs"]["value"][0]if"name"informInputselseNonecase_details["description"]=formInputs["description"]["stringInputs"]["value"][0]if"description"informInputselseNonecase_details["priority"]=formInputs["priority"]["stringInputs"]["value"][0]if"priority"informInputselseNonecase_details["impact"]=formInputs["impact"]["stringInputs"]["value"][0]if"impact"informInputselseFalseerrors=validate_form_inputs(case_details)iflen(errors) > 0:returncreate_case_input_card(event,errors,True)# Update modeelse: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)returncreate_link_render_action(title,url)defvalidate_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={}ifcase_details["name"]isNone:errors["name"]="You must provide a name"ifcase_details["description"]isNone:errors["description"]="You must provide a description"ifcase_details["priority"]isNone:errors["priority"]="You must provide a priority"ifcase_details["impact"]isnotNoneandcase_details["priority"]notin['P0','P1']:errors["impact"]="If an issue blocks a critical customer operation, priority must be P0 or P1"returnerrorsdefcreate_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>'}}defcreate_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}]}}}
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.fromtypingimportAny,Mappingfromurllib.parseimporturlparse,parse_qsimportflaskimportfunctions_framework@functions_framework.httpdefcreate_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)ifevent["docs"]["matchedUrl"]["url"]:url=event["docs"]["matchedUrl"]["url"]parsed_url=urlparse(url)# If the event object URL matches a specified pattern for preview links.ifparsed_url.hostname=="example.com":ifparsed_url.path.startswith("/support/cases/"):returncase_link_preview(parsed_url)return{}defcase_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]}}]}],}}}}
/** * 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. */importjava.util.Arrays;importjava.util.HashMap;importjava.util.Map;importorg.apache.http.client.utils.URIBuilder;importcom.google.cloud.functions.HttpFunction;importcom.google.cloud.functions.HttpRequest;importcom.google.cloud.functions.HttpResponse;importcom.google.gson.Gson;importcom.google.gson.JsonArray;importcom.google.gson.JsonObject;importcom.google.gson.JsonPrimitive;publicclassCreate3pResourcesimplementsHttpFunction{privatestaticfinalGsongson=newGson();/** * Responds to any HTTP request related to 3p resource creations. * * @param request An HTTP request context. * @param response An HTTP response context. */@Overridepublicvoidservice(HttpRequestrequest,HttpResponseresponse)throwsException{JsonObjectevent=gson.fromJson(request.getReader(),JsonObject.class);JsonObjectparameters=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,newHashMap<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. */JsonObjectcreateCaseInputCard(JsonObjectevent,Map<String,String>errors,booleanisUpdate){JsonObjectcardHeader=newJsonObject();cardHeader.add("title",newJsonPrimitive("Create a support case"));JsonObjectcardSectionTextInput1=newJsonObject();cardSectionTextInput1.add("name",newJsonPrimitive("name"));cardSectionTextInput1.add("label",newJsonPrimitive("Name"));JsonObjectcardSectionTextInput1Widget=newJsonObject();cardSectionTextInput1Widget.add("textInput",cardSectionTextInput1);JsonObjectcardSectionTextInput2=newJsonObject();cardSectionTextInput2.add("name",newJsonPrimitive("description"));cardSectionTextInput2.add("label",newJsonPrimitive("Description"));cardSectionTextInput2.add("type",newJsonPrimitive("MULTIPLE_LINE"));JsonObjectcardSectionTextInput2Widget=newJsonObject();cardSectionTextInput2Widget.add("textInput",cardSectionTextInput2);JsonObjectcardSectionSelectionInput1ItemsItem1=newJsonObject();cardSectionSelectionInput1ItemsItem1.add("text",newJsonPrimitive("P0"));cardSectionSelectionInput1ItemsItem1.add("value",newJsonPrimitive("P0"));JsonObjectcardSectionSelectionInput1ItemsItem2=newJsonObject();cardSectionSelectionInput1ItemsItem2.add("text",newJsonPrimitive("P1"));cardSectionSelectionInput1ItemsItem2.add("value",newJsonPrimitive("P1"));JsonObjectcardSectionSelectionInput1ItemsItem3=newJsonObject();cardSectionSelectionInput1ItemsItem3.add("text",newJsonPrimitive("P2"));cardSectionSelectionInput1ItemsItem3.add("value",newJsonPrimitive("P2"));JsonObjectcardSectionSelectionInput1ItemsItem4=newJsonObject();cardSectionSelectionInput1ItemsItem4.add("text",newJsonPrimitive("P3"));cardSectionSelectionInput1ItemsItem4.add("value",newJsonPrimitive("P3"));JsonArraycardSectionSelectionInput1Items=newJsonArray();cardSectionSelectionInput1Items.add(cardSectionSelectionInput1ItemsItem1);cardSectionSelectionInput1Items.add(cardSectionSelectionInput1ItemsItem2);cardSectionSelectionInput1Items.add(cardSectionSelectionInput1ItemsItem3);cardSectionSelectionInput1Items.add(cardSectionSelectionInput1ItemsItem4);JsonObjectcardSectionSelectionInput1=newJsonObject();cardSectionSelectionInput1.add("name",newJsonPrimitive("priority"));cardSectionSelectionInput1.add("label",newJsonPrimitive("Priority"));cardSectionSelectionInput1.add("type",newJsonPrimitive("DROPDOWN"));cardSectionSelectionInput1.add("items",cardSectionSelectionInput1Items);JsonObjectcardSectionSelectionInput1Widget=newJsonObject();cardSectionSelectionInput1Widget.add("selectionInput",cardSectionSelectionInput1);JsonObjectcardSectionSelectionInput2ItemsItem=newJsonObject();cardSectionSelectionInput2ItemsItem.add("text",newJsonPrimitive("Blocks a critical customer operation"));cardSectionSelectionInput2ItemsItem.add("value",newJsonPrimitive("Blocks a critical customer operation"));JsonArraycardSectionSelectionInput2Items=newJsonArray();cardSectionSelectionInput2Items.add(cardSectionSelectionInput2ItemsItem);JsonObjectcardSectionSelectionInput2=newJsonObject();cardSectionSelectionInput2.add("name",newJsonPrimitive("impact"));cardSectionSelectionInput2.add("label",newJsonPrimitive("Impact"));cardSectionSelectionInput2.add("items",cardSectionSelectionInput2Items);JsonObjectcardSectionSelectionInput2Widget=newJsonObject();cardSectionSelectionInput2Widget.add("selectionInput",cardSectionSelectionInput2);JsonObjectcardSectionButtonListButtonActionParametersParameter=newJsonObject();cardSectionButtonListButtonActionParametersParameter.add("key",newJsonPrimitive("submitCaseCreationForm"));cardSectionButtonListButtonActionParametersParameter.add("value",newJsonPrimitive(true));JsonArraycardSectionButtonListButtonActionParameters=newJsonArray();cardSectionButtonListButtonActionParameters.add(cardSectionButtonListButtonActionParametersParameter);JsonObjectcardSectionButtonListButtonAction=newJsonObject();cardSectionButtonListButtonAction.add("function",newJsonPrimitive(System.getenv().get("URL")));cardSectionButtonListButtonAction.add("parameters",cardSectionButtonListButtonActionParameters);cardSectionButtonListButtonAction.add("persistValues",newJsonPrimitive(true));JsonObjectcardSectionButtonListButtonOnCLick=newJsonObject();cardSectionButtonListButtonOnCLick.add("action",cardSectionButtonListButtonAction);JsonObjectcardSectionButtonListButton=newJsonObject();cardSectionButtonListButton.add("text",newJsonPrimitive("Create"));cardSectionButtonListButton.add("onClick",cardSectionButtonListButtonOnCLick);JsonArraycardSectionButtonListButtons=newJsonArray();cardSectionButtonListButtons.add(cardSectionButtonListButton);JsonObjectcardSectionButtonList=newJsonObject();cardSectionButtonList.add("buttons",cardSectionButtonListButtons);JsonObjectcardSectionButtonListWidget=newJsonObject();cardSectionButtonListWidget.add("buttonList",cardSectionButtonList);// Builds the form inputs with error texts for invalid values.JsonArraycardSection=newJsonArray();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);JsonObjectcardSectionWidgets=newJsonObject();cardSectionWidgets.add("widgets",cardSection);JsonArraysections=newJsonArray();sections.add(cardSectionWidgets);JsonObjectcard=newJsonObject();card.add("header",cardHeader);card.add("sections",sections);JsonObjectnavigation=newJsonObject();if(isUpdate){navigation.add("updateCard",card);}else{navigation.add("pushCard",card);}JsonArraynavigations=newJsonArray();navigations.add(navigation);JsonObjectaction=newJsonObject();action.add("navigations",navigations);JsonObjectrenderActions=newJsonObject();renderActions.add("action",action);if(!isUpdate){returnrenderActions;}JsonObjectupdate=newJsonObject();update.add("renderActions",renderActions);returnupdate;}/** * 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. */JsonObjectsubmitCaseCreationForm(JsonObjectevent)throwsException{JsonObjectformInputs=event.getAsJsonObject("commonEventObject").getAsJsonObject("formInputs");Map<String,String>caseDetails=newHashMap<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){returncreateCaseInputCard(event,errors,/* isUpdate= */true);}else{Stringtitle=String.format("Case %s",caseDetails.get("name"));// Adds the case details as parameters to the generated link URL.URIBuilderuriBuilder=newURIBuilder("https://example.com/support/cases/");for(StringcaseDetailKey:caseDetails.keySet()){uriBuilder.addParameter(caseDetailKey,caseDetails.get(caseDetailKey));}returncreateLinkRenderAction(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=newHashMap<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(newString[]{"P0","P1"}).contains(caseDetails.get("priority"))){errors.put("impact","If an issue blocks a critical customer operation, priority must be P0 or P1");}returnerrors;}/** * 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. */JsonObjectcreateErrorTextParagraph(StringerrorMessage){JsonObjecttextParagraph=newJsonObject();textParagraph.add("text",newJsonPrimitive("<font color=\"#BA0300\"><b>Error:</b> "+errorMessage+"</font>"));JsonObjecttextParagraphWidget=newJsonObject();textParagraphWidget.add("textParagraph",textParagraph);returntextParagraphWidget;}/** * 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. */JsonObjectcreateLinkRenderAction(Stringtitle,Stringurl){JsonObjectlink=newJsonObject();link.add("title",newJsonPrimitive(title));link.add("url",newJsonPrimitive(url));JsonArraylinks=newJsonArray();links.add(link);JsonObjectaction=newJsonObject();action.add("links",links);JsonObjectrenderActions=newJsonObject();renderActions.add("action",action);JsonObjectlinkRenderAction=newJsonObject();linkRenderAction.add("renderActions",renderActions);returnlinkRenderAction;}}
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. */importcom.google.cloud.functions.HttpFunction;importcom.google.cloud.functions.HttpRequest;importcom.google.cloud.functions.HttpResponse;importcom.google.gson.Gson;importcom.google.gson.JsonArray;importcom.google.gson.JsonObject;importcom.google.gson.JsonPrimitive;importjava.io.UnsupportedEncodingException;importjava.net.URL;importjava.net.URLDecoder;importjava.util.HashMap;importjava.util.Map;publicclassCreateLinkPreviewimplementsHttpFunction{privatestaticfinalGsongson=newGson();/** * Responds to any HTTP request related to link previews. * * @param request An HTTP request context. * @param response An HTTP response context. */@Overridepublicvoidservice(HttpRequestrequest,HttpResponseresponse)throwsException{JsonObjectevent=gson.fromJson(request.getReader(),JsonObject.class);Stringurl=event.getAsJsonObject("docs").getAsJsonObject("matchedUrl").get("url").getAsString();URLparsedURL=newURL(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. */JsonObjectcaseLinkPreview(URLurl)throwsUnsupportedEncodingException{// Parses the URL and identify the case details.Map<String,String>caseDetails=newHashMap<String,String>();for(Stringpair: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.JsonObjectcardHeader=newJsonObject();StringcaseName=String.format("Case %s",caseDetails.get("name"));cardHeader.add("title",newJsonPrimitive(caseName));JsonObjecttextParagraph=newJsonObject();textParagraph.add("text",newJsonPrimitive(caseDetails.get("description")));JsonObjectwidget=newJsonObject();widget.add("textParagraph",textParagraph);JsonArraywidgets=newJsonArray();widgets.add(widget);JsonObjectsection=newJsonObject();section.add("widgets",widgets);JsonArraysections=newJsonArray();sections.add(section);JsonObjectpreviewCard=newJsonObject();previewCard.add("header",cardHeader);previewCard.add("sections",sections);JsonObjectlinkPreview=newJsonObject();linkPreview.add("title",newJsonPrimitive(caseName));linkPreview.add("previewCard",previewCard);JsonObjectaction=newJsonObject();action.add("linkPreview",linkPreview);JsonObjectrenderActions=newJsonObject();renderActions.add("action",action);returnrenderActions;}}
[[["Dễ hiểu","easyToUnderstand","thumb-up"],["Giúp tôi giải quyết được vấn đề","solvedMyProblem","thumb-up"],["Khác","otherUp","thumb-up"]],[["Thiếu thông tin tôi cần","missingTheInformationINeed","thumb-down"],["Quá phức tạp/quá nhiều bước","tooComplicatedTooManySteps","thumb-down"],["Đã lỗi thời","outOfDate","thumb-down"],["Vấn đề về bản dịch","translationIssue","thumb-down"],["Vấn đề về mẫu/mã","samplesCodeIssue","thumb-down"],["Khác","otherDown","thumb-down"]],["Cập nhật lần gần đây nhất: 2024-12-18 UTC."],[[["This guide details building a Google Workspace add-on to create and manage external resources (like support cases) directly within Google Docs."],["Users can create resources via a form within Docs, which then inserts a smart chip linking to the resource in the external service."],["The add-on requires configuration in the manifest file and utilizes Apps Script, Node.js, Python, or Java for development."],["Comprehensive code samples are provided to guide developers through card creation, form submission, and error handling."],["Smart chips representing the created resources offer link previews, enhancing user experience and information access."]]],[]]