Na tej stronie znajdziesz instrukcje tworzenia dodatku Google Workspace, który umożliwia użytkownikom Dokumentów Google tworzenie zasobów, takich jak zgłoszenie do zespołu pomocy czy zadanie projektowe, w usłudze innej firmy w Dokumentach Google.
Za pomocą dodatku do Google Workspace możesz dodać swoją usługę do menu @ w Dokumentach. Dodatek dodaje elementy menu, które umożliwiają użytkownikom tworzenie zasobów w Twojej usłudze za pomocą okna dialogowego formularza w Dokumentach.
Jak użytkownicy tworzą zasoby
Aby utworzyć zasób w usłudze z poziomu dokumentu w Dokumentach Google, użytkownicy mogą wpisać @ w dokumencie i wybrać usługę z menu @:
Gdy użytkownicy wpisują @ w dokumencie i wybierają Twoją usługę, wyświetla się karta z polami formularza, których użytkownicy potrzebują do utworzenia zasobu. Gdy użytkownik prześle formularz tworzenia zasobu, Twój dodatek powinien utworzyć zasób w usłudze i wygenerować adres URL, który do niego prowadzi.
Wtyczka wstawia do dokumentu element dotyczący utworzonego zasobu. Gdy użytkownik najedzie kursorem na ten element, zostanie wywołany powiązany z dodatkiem element uruchamiający podgląd linku. Upewnij się, że dodatek wstawia elementy inteligentne z wzorami linków obsługiwanymi przez czynniki uruchamiające podgląd linków.
Aby skonfigurować tworzenie zasobów, w pliku manifestu dodatku określ te sekcje i pola:
W sekcji addOns w polu docs zastosuj regułę createActionTriggers, która zawiera runFunction. (definiujesz tę funkcję w następnej sekcji Tworzenie kart formularza).
W polu oauthScopes dodaj zakres https://www.googleapis.com/auth/workspace.linkcreate, aby użytkownicy mogli autoryzować dodatki do tworzenia zasobów.
W szczególności zakres ten pozwala dodatkowi odczytywać informacje przesłane przez użytkowników w formularzu tworzenia zasobu i wstawiać w dokumencie element inteligentny na podstawie tych informacji.
Przykładem jest sekcja addons w pliku manifestu, która konfiguruje tworzenie zasobu dla tego zgłoszenia do zespołu pomocy:
{"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"}]}}}
W tym przykładzie dodatek Google Workspace umożliwia użytkownikom tworzenie zgłoszeń do zespołu pomocy.
Każdy createActionTriggers musi zawierać te pola:
Unikalny identyfikator
Etykieta tekstowa wyświetlana w menu @ w Dokumentach
Adres URL logo wskazujący ikonę, która pojawia się obok tekstu etykiety w menu @.
Funkcja wywołania zwrotnego, która odwołuje się do funkcji Apps Script lub punktu końcowego HTTP zwracającego kartę
Tworzenie kart formularza
Aby utworzyć zasoby w usłudze z poziomu menu Docs @, musisz zaimplementować wszystkie funkcje określone w obiekcie createActionTriggers.
Gdy użytkownik wejdzie w interakcję z jednym z Twoich elementów menu, odpowiedni createActionTriggers wyzwalacz zostanie uruchomiony, a jego funkcja wywołania wyświetli kartę z polami formularza do tworzenia zasobu.
Obsługiwane elementy i działania
Aby utworzyć interfejs karty, użyj widżetów do wyświetlania informacji i danych wejściowych, których użytkownicy potrzebują do utworzenia zasobu. Większość dodatków Google Workspace, widżetów i działań jest obsługiwanych z następującymi wyjątkami:
Stopki kart nie są obsługiwane.
Powiadomienia nie są obsługiwane.
W przypadku nawigacji obsługiwana jest tylko nawigacja updateCard.
Przykład karty z polami formularza
Ten przykład pokazuje funkcję wywołania zwrotnego w Google Apps Script, która wyświetla kartę, gdy użytkownik w menu @ kliknie Utwórz zgłoszenie:
/** * 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;}
Funkcja createCaseInputCard generuje tę kartę:
Karta zawiera pola tekstowe, menu i pole wyboru. Zawiera też przycisk tekstowy z działaniem onClick, które uruchamia inną funkcję do obsługi przesyłania formularza tworzenia.
Gdy użytkownik wypełni formularz i kliknie Utwórz, dodatek prześle dane wejściowe formularza do funkcji działania onClick (w naszym przykładzie nazywa się ona submitCaseCreationForm). W tym momencie dodatek może zweryfikować dane wejściowe i użyć ich do utworzenia zasobu w usłudze innej firmy.
Obsługa przesłanych formularzy
Gdy użytkownik prześle formularz tworzenia, zostanie uruchomiona funkcja powiązana z działaniem onClick. Aby zapewnić użytkownikom optymalną wygodę, dodatek powinien obsługiwać zarówno prawidłowe, jak i nieprawidłowe przesyłanie formularzy.
Obsługa utworzenia zasobu
Funkcja onClick Twojego dodatku powinna utworzyć zasób w usłudze zewnętrznej i wygenerować adres URL, który do niego prowadzi.
Aby przekazać Docs adres URL zasobu do użycia w elemencie, funkcja onClick powinna zwracać SubmitFormResponse z jednoelementowym tablicą w funkcji renderActions.action.links, która wskazuje na link. Tytuł linku powinien odpowiadać tytułowi utworzonego zasobu, a adres URL powinien wskazywać na ten zasób.
Poniższy przykład pokazuje SubmitFormResponse utworzonego zasobu:
/** * 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;}
Gdy zwrócona zostanie wartość SubmitFormResponse, okno modalne się zamknie, a dodatek wstawi do dokumentu element.
Gdy użytkownik najedzie na ten element, wywołuje powiązany z nim element reguły podglądu. Upewnij się, że dodatek nie wstawia elementów z wzorami linków, których nie obsługują czynniki uruchamiające podgląd linków.
Obsługa błędów
Jeśli użytkownik spróbuje przesłać formularz z nieprawidłowymi polami, zamiast zwracania SubmitFormResponse z linkiem dodatek powinien zwrócić działanie renderowania, które wyświetla błąd za pomocą nawigacji updateCard.
Dzięki temu użytkownik może sprawdzić, co zrobił nie tak, i spróbować ponownie. W przypadku Apps Script użyj wartości updateCard(card), a w przypadku innych środowisk wykonawalnych – updateCard. Powiadomienia i nawigacja pushCard nie są obsługiwane.
Przykład obsługi błędów
Przykład poniżej pokazuje kod wywoływany, gdy użytkownik przesyła formularz. Jeśli dane wejściowe są nieprawidłowe, karta się aktualizuje i wyświetla komunikaty o błędach. Jeśli dane wejściowe są prawidłowe, dodatek zwraca SubmitFormResponse z linkiem do utworzonego zasobu.
/** * 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());}}
Ten przykład kodu sprawdza dane wejściowe formularza i tworzy komunikaty o błędach w przypadku nieprawidłowych danych:
/** * 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;}
Pełny przykład: dodatek do zgłoszenia do zespołu pomocy
Ten przykład pokazuje dodatek Google Workspace, który wyświetla podgląd linków do zgłoszeń do zespołu pomocy firmy i umożliwia użytkownikom tworzenie zgłoszeń w Dokumentach Google.
Przykład wykonuje te czynności:
Generuje kartę z polami formularza, aby utworzyć zgłoszenie do zespołu pomocy z menu Dokumenty Google.
Sprawdza dane wejściowe formularza i zwraca komunikaty o błędach w przypadku nieprawidłowych danych.
Wstawia nazwę i link do utworzonego zgłoszenia do zespołu pomocy w dokumencie Dokumentów jako element inteligentny.
Wyświetla podgląd linku do zgłoszenia do zespołu pomocy, np. https://www.example.com/support/cases/1234. Element inteligentny wyświetla ikonę, a karta podglądu zawiera nazwę, priorytet i opis zgłoszenia.
{"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}]}}}
Poniższy kod pokazuje, jak zaimplementować podgląd linku dla utworzonego zasobu:
# 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;}}
Poniższy kod pokazuje, jak zaimplementować podgląd linku dla utworzonego zasobu:
/** * 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;}}
[null,null,["Ostatnia aktualizacja: 2024-12-22 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."]]],[]]