Cette page explique comment créer un module complémentaire Google Workspace qui permet aux utilisateurs de Google Docs de créer des ressources, telles qu'une demande d'assistance ou une tâche de projet, dans un service tiers depuis Google Docs.
Avec un module complémentaire Google Workspace, vous pouvez ajouter votre service au menu @ dans Docs. Le module complémentaire ajoute des éléments de menu qui permettent aux utilisateurs de créer des ressources dans votre service via une boîte de dialogue de formulaire dans Docs.
Comment les utilisateurs créent-ils des ressources ?
Pour créer une ressource dans votre service à partir d'un document Google Docs, les utilisateurs doivent saisir @ dans un document et sélectionner votre service dans le menu @ :
Lorsque les utilisateurs saisissent @ dans un document et sélectionnent votre service, une fiche s'affiche, qui inclut les entrées de formulaire dont ils ont besoin pour créer une ressource. Une fois que l'utilisateur a envoyé le formulaire de création de ressource, votre module complémentaire doit créer la ressource dans votre service et générer une URL qui y fait référence.
Le module complémentaire insère un chip dans le document pour la ressource créée. Lorsque les utilisateurs maintiennent le pointeur sur ce chip, le déclencheur d'aperçu de lien associé au module complémentaire est appelé. Assurez-vous que votre module complémentaire insère des chips avec des formats de lien compatibles avec vos déclencheurs de prévisualisation de lien.
Prérequis
Apps Script
Module complémentaire Google Workspace compatible avec les aperçus de liens pour les modèles de liens des ressources créées par les utilisateurs. Pour créer un module complémentaire avec des aperçus de liens, consultez Prévisualiser les liens avec les chips intelligents.
Node.js
Module complémentaire Google Workspace compatible avec les aperçus de liens pour les modèles de liens des ressources créées par les utilisateurs. Pour créer un module complémentaire avec des aperçus de liens, consultez Prévisualiser les liens avec les chips intelligents.
Python
Module complémentaire Google Workspace compatible avec les aperçus de liens pour les modèles de liens des ressources créées par les utilisateurs. Pour créer un module complémentaire avec des aperçus de liens, consultez Prévisualiser les liens avec les chips intelligents.
Java
Module complémentaire Google Workspace compatible avec les aperçus de liens pour les modèles de liens des ressources créées par les utilisateurs. Pour créer un module complémentaire avec des aperçus de liens, consultez Prévisualiser les liens avec les chips intelligents.
Configurer la création de ressources pour votre module complémentaire
Cette section explique comment configurer la création de ressources pour votre module complémentaire, ce qui comprend les étapes suivantes:
Gérez les envois de formulaires afin que la fonction qui crée la ressource s'exécute lorsque les utilisateurs envoient le formulaire.
Configurer la création de ressources
Pour configurer la création de ressources, spécifiez les sections et les champs suivants dans le fichier manifeste de votre module complémentaire:
Dans la section addOns du champ docs, implémentez le déclencheur createActionTriggers qui inclut un runFunction. (Vous définirez cette fonction dans la section suivante, Créer les fiches de formulaire.)
Dans le champ oauthScopes, ajoutez le champ d'application https://www.googleapis.com/auth/workspace.linkcreate afin que les utilisateurs puissent autoriser le module complémentaire à créer des ressources.
Plus précisément, ce champ d'application permet au module complémentaire de lire les informations que les utilisateurs envoient au formulaire de création de ressources et d'insérer un chip intelligent dans le document en fonction de ces informations.
Par exemple, consultez la section addons d'un fichier manifeste qui configure la création de ressources pour le service de demande d'assistance suivant:
{"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"}]}}}
Dans cet exemple, le module complémentaire Google Workspace permet aux utilisateurs de créer des demandes d'assistance.
Chaque déclencheur createActionTriggers doit comporter les champs suivants:
Un identifiant unique
Libellé de texte qui s'affiche dans le menu Docs @
URL du logo pointant vers une icône qui s'affiche à côté du texte du libellé dans le menu @
Une fonction de rappel qui fait référence à une fonction Apps Script ou à un point de terminaison HTTP qui renvoie une fiche
Créer les fiches de formulaire
Pour créer des ressources dans votre service à partir du menu @ de Docs, vous devez implémenter toutes les fonctions que vous avez spécifiées dans l'objet createActionTriggers.
Lorsqu'un utilisateur interagit avec l'un de vos éléments de menu, le déclencheur createActionTriggers correspondant se déclenche et sa fonction de rappel présente une fiche avec des entrées de formulaire pour créer la ressource.
Éléments et actions compatibles
Pour créer l'interface de la fiche, vous utilisez des widgets pour afficher les informations et les entrées dont les utilisateurs ont besoin pour créer la ressource. La plupart des widgets et des actions des modules complémentaires Google Workspace sont compatibles, à l'exception des éléments suivants:
Les pieds de page de la carte ne sont pas acceptés.
Les notifications ne sont pas acceptées.
Pour les navigations, seule la navigation updateCard est prise en charge.
Exemple de fiche avec des entrées de formulaire
L'exemple suivant montre une fonction de rappel Apps Script qui affiche une fiche lorsqu'un utilisateur sélectionne Créer une demande d'assistance dans le menu @ :
/** * 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;}
La fonction createCaseInputCard affiche la fiche suivante:
La fiche comprend des zones de saisie de texte, un menu déroulant et une case à cocher. Il comporte également un bouton de texte avec une action onClick qui exécute une autre fonction pour gérer l'envoi du formulaire de création.
Une fois que l'utilisateur a rempli le formulaire et cliqué sur Créer, le module complémentaire envoie les entrées du formulaire à la fonction d'action onClick (appelée submitCaseCreationForm dans notre exemple). À ce stade, le module complémentaire peut valider les entrées et les utiliser pour créer la ressource dans le service tiers.
Gérer les envois de formulaires
Une fois qu'un utilisateur a envoyé le formulaire de création, la fonction associée à l'action onClick s'exécute. Pour une expérience utilisateur optimale, votre module complémentaire doit gérer à la fois les envois de formulaires réussis et erronés.
Gérer la création réussie de ressources
La fonction onClick de votre module complémentaire doit créer la ressource dans votre service tiers et générer une URL qui y fait référence.
Pour communiquer l'URL de la ressource à Docs pour la création d'un chip, la fonction onClick doit renvoyer un SubmitFormResponse avec un tableau à un élément dans renderActions.action.links qui pointe vers un lien. Le titre du lien doit correspondre au titre de la ressource créée, et l'URL doit rediriger vers cette ressource.
L'exemple suivant montre un SubmitFormResponse pour une ressource créée:
/** * 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;}
Une fois le SubmitFormResponse renvoyé, la boîte de dialogue modale se ferme et le module complémentaire insère une chip dans le document.
Lorsque les utilisateurs maintiennent le pointeur sur ce chip, le déclencheur d'aperçu du lien associé est appelé. Assurez-vous que votre module complémentaire n'insère pas de chips avec des formats de lien non compatibles avec vos déclencheurs d'aperçu de lien.
Gérer les erreurs
Si un utilisateur tente d'envoyer un formulaire avec des champs non valides, au lieu de renvoyer un SubmitFormResponse avec un lien, le module complémentaire doit renvoyer une action de rendu qui affiche une erreur à l'aide d'une navigation updateCard.
Cela permet à l'utilisateur de voir ce qu'il a mal fait et de réessayer. Consultez updateCard(card) pour Apps Script et updateCard pour les autres environnements d'exécution. Les notifications et les navigations pushCard ne sont pas prises en charge.
Exemple de gestion des erreurs
L'exemple suivant montre le code appelé lorsqu'un utilisateur envoie le formulaire. Si les entrées sont non valides, la fiche est mise à jour et affiche des messages d'erreur. Si les entrées sont valides, le module complémentaire renvoie un SubmitFormResponse avec un lien vers la ressource créée.
/** * 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());}}
L'exemple de code suivant valide les entrées du formulaire et crée des messages d'erreur pour les entrées non valides:
/** * 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;}
Exemple complet: Module complémentaire de demande d'assistance
L'exemple suivant montre un module complémentaire Google Workspace qui affiche un aperçu des liens vers les demandes d'assistance d'une entreprise et permet aux utilisateurs de créer des demandes d'assistance depuis Google Docs.
en effectuant les opérations suivantes :
Génère une fiche avec des champs de formulaire pour créer une demande d'assistance à partir du menu Docs @.
Valide les entrées de formulaire et renvoie des messages d'erreur pour les entrées non valides.
Insère le nom et le lien de la demande d'assistance créée dans le document Docs en tant que chip intelligent.
Affiche un aperçu du lien vers la demande d'assistance, par exemple https://www.example.com/support/cases/1234. Le chip intelligent affiche une icône, et la fiche d'aperçu inclut le nom, la priorité et la description de la demande.
{"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}]}}}
Le code suivant montre comment implémenter un aperçu de lien pour la ressource créée:
# 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;}}
Le code suivant montre comment implémenter un aperçu de lien pour la ressource créée:
/** * 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;}}
Sauf indication contraire, le contenu de cette page est régi par une licence Creative Commons Attribution 4.0, et les échantillons de code sont régis par une licence Apache 2.0. Pour en savoir plus, consultez les Règles du site Google Developers. Java est une marque déposée d'Oracle et/ou de ses sociétés affiliées.
Dernière mise à jour le 2024/12/22 (UTC).
[null,null,["Dernière mise à jour le 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."]]],[]]