Drittanbieter-Ressourcen über das @-Menü erstellen
Auf dieser Seite wird beschrieben, wie Sie ein Google Workspace-Add-on erstellen, mit dem Google Docs-Nutzer Ressourcen wie einen Supportfall oder eine Projektaufgabe in einem Drittanbieterdienst direkt in Google Docs erstellen können.
Mit einem Google Workspace-Add-on können Sie Ihren Dienst dem @-Menü in Google Docs hinzufügen. Das Add-on fügt Menüpunkte hinzu, über die Nutzer über ein Formulardialogfeld in Google Docs Ressourcen in Ihrem Dienst erstellen können.
So erstellen Nutzer Ressourcen
Wenn Nutzer eine Ressource in Ihrem Dienst in einem Google Docs-Dokument erstellen möchten, geben sie @ in ein Dokument ein und wählen Ihren Dienst aus dem @-Menü aus:
Wenn Nutzer @ in ein Dokument eingeben und Ihren Dienst auswählen, wird ihnen eine Karte mit den Formulareingaben angezeigt, die sie zum Erstellen einer Ressource benötigen. Nachdem der Nutzer das Formular zur Ressourcenerstellung gesendet hat, sollte Ihr Add-on die Ressource in Ihrem Dienst erstellen und eine URL generieren, die darauf verweist.
Das Add-on fügt dem Dokument einen Chip für die erstellte Ressource ein. Wenn Nutzer den Mauszeiger auf diesen Chip bewegen, wird der zugehörige Linkvorschau-Trigger des Add-ons aufgerufen. Achten Sie darauf, dass Ihr Add-on Chips mit Linkmustern einfügt, die von Ihren Triggern für Linkvorschauen unterstützt werden.
Vorbereitung
Apps Script
Ein Google Workspace-Add-on, das Linkvorschauen für die Linkmuster der von Nutzern erstellten Ressourcen unterstützt. Informationen zum Erstellen eines Add-ons mit Linkvorschauen finden Sie unter Vorschaulinks mit Smartchips erstellen.
Node.js
Ein Google Workspace-Add-on, das Linkvorschauen für die Linkmuster der von Nutzern erstellten Ressourcen unterstützt. Informationen zum Erstellen eines Add-ons mit Linkvorschauen finden Sie unter Vorschaulinks mit Smartchips erstellen.
Python
Ein Google Workspace-Add-on, das Linkvorschauen für die Linkmuster der von Nutzern erstellten Ressourcen unterstützt. Informationen zum Erstellen eines Add-ons mit Linkvorschauen finden Sie unter Vorschaulinks mit Smartchips erstellen.
Java
Ein Google Workspace-Add-on, das Linkvorschauen für die Linkmuster der von Nutzern erstellten Ressourcen unterstützt. Informationen zum Erstellen eines Add-ons mit Linkvorschauen finden Sie unter Vorschaulinks mit Smartchips erstellen.
Ressourcenerstellung für Ihr Add-on einrichten
In diesem Abschnitt wird beschrieben, wie Sie die Erstellung von Ressourcen für Ihr Add-on einrichten. Dazu sind die folgenden Schritte erforderlich:
Verarbeite Formulareinreichungen, damit die Funktion, die die Ressource erstellt, ausgeführt wird, wenn Nutzer das Formular einreichen.
Ressourcenerstellung konfigurieren
Um die Erstellung von Ressourcen zu konfigurieren, geben Sie die folgenden Abschnitte und Felder im Manifest Ihres Add-ons an:
Implementieren Sie im Abschnitt addOns im Feld docs den Trigger createActionTriggers, der ein runFunction enthält. Diese Funktion definieren Sie im folgenden Abschnitt Formularkarten erstellen.
Fügen Sie im Feld oauthScopes den Bereich https://www.googleapis.com/auth/workspace.linkcreate hinzu, damit Nutzer das Add-on zum Erstellen von Ressourcen autorisieren können.
Insbesondere kann das Add-on mit diesem Umfang die Informationen lesen, die Nutzer im Formular zum Erstellen von Ressourcen einreichen, und anhand dieser Informationen einen Smartchip in das Dokument einfügen.
Im Abschnitt addons eines Manifests wird beispielsweise die Ressourcenerstellung für den folgenden Supportanfragedienst konfiguriert:
{"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"}]}}}
Im Beispiel können Nutzer mit dem Google Workspace-Add-on Supportanfragen erstellen.
Jeder createActionTriggers-Trigger muss die folgenden Felder enthalten:
Eine eindeutige ID
Ein Textlabel, das im Docs-Menü „@“ angezeigt wird
Eine Logo-URL, die auf ein Symbol verweist, das neben dem Labeltext im Dreistrich-Menü angezeigt wird
Eine Callback-Funktion, die entweder auf eine Apps Script-Funktion oder einen HTTP-Endpunkt verweist, der eine Karte zurückgibt
Formularkarten erstellen
Wenn Sie Ressourcen in Ihrem Dienst über das @-Menü in Google Docs erstellen möchten, müssen Sie alle Funktionen implementieren, die Sie im createActionTriggers-Objekt angegeben haben.
Wenn ein Nutzer mit einem Ihrer Menüpunkte interagiert, wird der entsprechende createActionTriggers-Trigger ausgelöst und die Callback-Funktion zeigt eine Karte mit Formulareingaben zum Erstellen der Ressource an.
Unterstützte Elemente und Aktionen
Zum Erstellen der Kartenoberfläche verwenden Sie Widgets, um Informationen und Eingaben anzuzeigen, die Nutzer zum Erstellen der Ressource benötigen. Die meisten Google Workspace-Add-on-Widgets und -Aktionen werden unterstützt, mit folgenden Ausnahmen:
Kartenfußzeilen werden nicht unterstützt.
Benachrichtigungen werden nicht unterstützt.
Für Navigationen wird nur die updateCard-Navigation unterstützt.
Beispiel für eine Karte mit Formulareingaben
Im folgenden Beispiel wird eine Apps Script-Callback-Funktion gezeigt, die eine Karte anzeigt, wenn ein Nutzer im Dreistrich-Menü die Option Supportanfrage erstellen auswählt:
/** * 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;}
Die Funktion createCaseInputCard rendert die folgende Karte:
Die Karte enthält Textfelder, ein Drop-down-Menü und ein Kästchen. Außerdem gibt es eine Textschaltfläche mit einer onClick-Aktion, die eine andere Funktion ausführt, um die Einreichung des Erstellungsformulars zu verarbeiten.
Nachdem der Nutzer das Formular ausgefüllt und auf Erstellen geklickt hat, sendet das Add-on die Formulareingaben an die Aktionsfunktion onClick, in unserem Beispiel submitCaseCreationForm. Das Add-on kann die Eingaben dann überprüfen und zum Erstellen der Ressource im Drittanbieterdienst verwenden.
Formulareingaben abwickeln
Nachdem ein Nutzer das Formular zum Erstellen gesendet hat, wird die mit der Aktion onClick verknüpfte Funktion ausgeführt. Für eine optimale Nutzererfahrung sollte Ihr Add-on sowohl erfolgreiche als auch fehlerhafte Formulareinreichungen verarbeiten.
Erfolgreiche Ressourcenerstellung verarbeiten
Die onClick-Funktion Ihres Add-ons sollte die Ressource im Drittanbieterdienst erstellen und eine URL generieren, die darauf verweist.
Damit die URL der Ressource zur Erstellung des Chips an Google Docs zurückgegeben werden kann, sollte die onClick-Funktion ein SubmitFormResponse mit einem Array mit einem Element in renderActions.action.links zurückgeben, das auf einen Link verweist. Der Linktitel sollte dem Titel der erstellten Ressource entsprechen und die URL sollte auf diese Ressource verweisen.
Das folgende Beispiel zeigt eine SubmitFormResponse für eine erstellte Ressource:
/** * 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;}
Nachdem die SubmitFormResponse zurückgegeben wurde, wird das modale Dialogfeld geschlossen und das Add-on fügt dem Dokument einen Chip ein.
Wenn Nutzer den Mauszeiger auf diesen Chip bewegen, wird der zugehörige Trigger für die Linkvorschau aufgerufen. Achten Sie darauf, dass Ihr Add-on keine Chips mit Linkmustern einfügt, die von Ihren Triggern für Linkvorschauen nicht unterstützt werden.
Fehler verarbeiten
Wenn ein Nutzer versucht, ein Formular mit ungültigen Feldern einzureichen, sollte das Add-on anstelle einer SubmitFormResponse mit einem Link eine Renderaktion zurückgeben, die einen Fehler mit einer updateCard-Navigation anzeigt.
So kann der Nutzer sehen, was er falsch gemacht hat, und es noch einmal versuchen. Für Apps Script ist das updateCard(card) und für andere Laufzeiten updateCard. Benachrichtigungen und pushCard-Navigationen werden nicht unterstützt.
Beispiel für die Fehlerbehandlung
Im folgenden Beispiel wird der Code angezeigt, der aufgerufen wird, wenn ein Nutzer das Formular einreicht. Wenn die Eingaben ungültig sind, wird die Karte aktualisiert und es werden Fehlermeldungen angezeigt. Wenn die Eingaben gültig sind, gibt das Add-on eine SubmitFormResponse mit einem Link zur erstellten Ressource zurück.
/** * 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());}}
Im folgenden Codebeispiel werden die Formulareingaben validiert und Fehlermeldungen für ungültige Eingaben erstellt:
/** * 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;}
Vollständiges Beispiel: Add-on für Supportanfragen
Das folgende Beispiel zeigt ein Google Workspace-Add-on, das eine Vorschau von Links zu den Supportanfragen eines Unternehmens enthält und Nutzern ermöglicht, Supportanfragen direkt in Google Docs zu erstellen.
Das Beispiel führt Folgendes aus:
Er generiert eine Karte mit Formularfeldern, um über das Docs @-Menü eine Supportanfrage zu erstellen.
Prüft Formulareingaben und gibt Fehlermeldungen für ungültige Eingaben zurück.
Der Name und der Link der erstellten Supportanfrage werden als Smartchip in das Docs-Dokument eingefügt.
Vorschau des Links zur Supportanfrage, z. B. https://www.example.com/support/cases/1234. Der Smartchip enthält ein Symbol und die Vorschaukarte den Fallnamen, die Priorität und die Beschreibung.
{"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}]}}}
Der folgende Code zeigt, wie eine Linkvorschau für die erstellte Ressource implementiert wird:
# 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;}}
Der folgende Code zeigt, wie eine Linkvorschau für die erstellte Ressource implementiert wird:
/** * 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,["Zuletzt aktualisiert: 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."]]],[]]