Crea risorse di terze parti dal menu @

Questa pagina spiega come creare un componente aggiuntivo Google Workspace che consenta agli utenti di Documenti Google di creare risorse, ad esempio una richiesta di assistenza o un'attività di progetto, in un servizio di terze parti da Documenti Google.

Con un componente aggiuntivo di Google Workspace, puoi aggiungere il tuo servizio al menu @ in Documenti. Il componente aggiuntivo aggiunge voci di menu che consentono agli utenti di creare risorse nel tuo servizio tramite una finestra di dialogo del modulo in Documenti.

Come gli utenti creano le risorse

Per creare una risorsa nel tuo servizio da un documento Google Docs, gli utenti digitano @ in un documento e selezionano il tuo servizio dal menu @:

L'utente visualizza l'anteprima di una scheda

Quando gli utenti digitano @ in un documento e selezionano il tuo servizio, viene visualizzata una scheda che include i campi del modulo necessari per creare una risorsa. Dopo che l'utente invia il modulo di creazione della risorsa, il componente aggiuntivo deve creare la risorsa nel servizio e generare un URL che punta a essa.

Il componente aggiuntivo inserisce un chip nel documento per la risorsa creata. Quando gli utenti passano il puntatore sopra questo chip, viene richiamato il trigger di anteprima del link associato al componente aggiuntivo. Assicurati che il componente aggiuntivo inserisca chip con pattern di link supportati dai trigger di anteprima dei link.

Prerequisiti

Apps Script

  • Un componente aggiuntivo di Google Workspace che supporta le anteprime dei link per i pattern dei link delle risorse create dagli utenti. Per creare un componente aggiuntivo con anteprime dei link, consulta Visualizzare l'anteprima del link con smart chip.

Node.js

  • Un componente aggiuntivo di Google Workspace che supporta le anteprime dei link per i pattern dei link delle risorse create dagli utenti. Per creare un componente aggiuntivo con anteprime dei link, consulta Visualizzare l'anteprima del link con smart chip.

Python

  • Un componente aggiuntivo di Google Workspace che supporta le anteprime dei link per i pattern dei link delle risorse create dagli utenti. Per creare un componente aggiuntivo con anteprime dei link, consulta Visualizzare l'anteprima del link con smart chip.

Java

  • Un componente aggiuntivo di Google Workspace che supporta le anteprime dei link per i pattern dei link delle risorse create dagli utenti. Per creare un componente aggiuntivo con anteprime dei link, consulta Visualizzare l'anteprima del link con smart chip.

Configurare la creazione di risorse per il componente aggiuntivo

Questa sezione spiega come configurare la creazione di risorse per il tuo componente aggiuntivo, che include i seguenti passaggi:

  1. Configura la creazione di risorse nel manifest del componente aggiuntivo.
  2. Crea le schede del modulo che gli utenti devono utilizzare per creare risorse all'interno del tuo servizio.
  3. Gestisci gli invii di moduli in modo che la funzione che crea la risorsa venga eseguita quando gli utenti inviano il modulo.

Configura la creazione delle risorse

Per configurare la creazione di risorse, specifica le seguenti sezioni e i seguenti campi nel manifest del componente aggiuntivo:

  1. Nella sezione addOns del campo docs, implementa l'attivatore createActionTriggers che include un runFunction. Definisci questa funzione nella sezione seguente, Crea le schede del modulo.

    Per scoprire quali campi puoi specificare nel trigger createActionTriggers, consulta la documentazione di riferimento per i manifest di Apps Script o le risorse di deployment per altri runtime.

  2. Nel campo oauthScopes, aggiungi l'ambito https://www.googleapis.com/auth/workspace.linkcreate in modo che gli utenti possano autorizzare il componente aggiuntivo a creare risorse. Nello specifico, questo ambito consente al componente aggiuntivo di leggere le informazioni che gli utenti inviano al modulo di creazione delle risorse e di inserire un chip intelligente nel documento in base a queste informazioni.

Ad esempio, consulta la sezione addons di un manifest che configura la creazione di risorse per il seguente servizio di richiesta di assistenza:

{
  "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"
        }
      ]
    }
  }
}

Nell'esempio, il componente aggiuntivo Google Workspace consente agli utenti di creare richieste di assistenza. Ogni trigger createActionTriggers deve avere i seguenti campi:

  • Un ID univoco
  • Un'etichetta di testo che viene visualizzata nel menu @ di Documenti
  • Un URL del logo che rimanda a un'icona visualizzata accanto al testo dell'etichetta nel menu @
  • Una funzione di callback che fa riferimento a una funzione Apps Script o a un endpoint HTTP che restituisce una scheda

Creare le schede del modulo

Per creare risorse nel tuo servizio dal menu @ di Documenti, devi implementare tutte le funzioni che hai specificato nell'oggetto createActionTriggers.

Quando un utente interagisce con una delle voci di menu, viene attivato il trigger createActionTriggers corrispondente e la relativa funzione di callback presenta una scheda con i campi del modulo per la creazione della risorsa.

Elementi e azioni supportati

Per creare l'interfaccia della scheda, utilizzi i widget per visualizzare le informazioni e gli input di cui gli utenti hanno bisogno per creare la risorsa. La maggior parte dei widget e delle azioni dei componenti aggiuntivi di Google Workspace sono supportati, con le seguenti eccezioni:

  • I piè di pagina delle schede non sono supportati.
  • Le notifiche non sono supportate.
  • Per la navigazione, è supportata solo la navigazione updateCard.

Esempio di scheda con input del modulo

L'esempio seguente mostra una funzione di callback Apps Script che visualizza una scheda quando un utente seleziona Crea richiesta di assistenza dal menu @:

Apps Script

apps-script/3p-resources/3p-resources.gs
/**
 * Produces a support case creation form card.
 * 
 * @param {!Object} event The event object.
 * @param {!Object=} errors An optional map of per-field error messages.
 * @param {boolean} isUpdate Whether to return the form as an update card navigation.
 * @return {!Card|!ActionResponse} The resulting card or action response.
 */
function createCaseInputCard(event, errors, isUpdate) {

  const cardHeader = CardService.newCardHeader()
    .setTitle('Create a support case')

  const cardSectionTextInput1 = CardService.newTextInput()
    .setFieldName('name')
    .setTitle('Name')
    .setMultiline(false);

  const cardSectionTextInput2 = CardService.newTextInput()
    .setFieldName('description')
    .setTitle('Description')
    .setMultiline(true);

  const cardSectionSelectionInput1 = CardService.newSelectionInput()
    .setFieldName('priority')
    .setTitle('Priority')
    .setType(CardService.SelectionInputType.DROPDOWN)
    .addItem('P0', 'P0', false)
    .addItem('P1', 'P1', false)
    .addItem('P2', 'P2', false)
    .addItem('P3', 'P3', false);

  const cardSectionSelectionInput2 = CardService.newSelectionInput()
    .setFieldName('impact')
    .setTitle('Impact')
    .setType(CardService.SelectionInputType.CHECK_BOX)
    .addItem('Blocks a critical customer operation', 'Blocks a critical customer operation', false);

  const cardSectionButtonListButtonAction = CardService.newAction()
    .setPersistValues(true)
    .setFunctionName('submitCaseCreationForm')
    .setParameters({});

  const cardSectionButtonListButton = CardService.newTextButton()
    .setText('Create')
    .setTextButtonStyle(CardService.TextButtonStyle.TEXT)
    .setOnClickAction(cardSectionButtonListButtonAction);

  const cardSectionButtonList = CardService.newButtonSet()
    .addButton(cardSectionButtonListButton);

  // Builds the form inputs with error texts for invalid values.
  const cardSection = CardService.newCardSection();
  if (errors?.name) {
    cardSection.addWidget(createErrorTextParagraph(errors.name));
  }
  cardSection.addWidget(cardSectionTextInput1);
  if (errors?.description) {
    cardSection.addWidget(createErrorTextParagraph(errors.description));
  }
  cardSection.addWidget(cardSectionTextInput2);
  if (errors?.priority) {
    cardSection.addWidget(createErrorTextParagraph(errors.priority));
  }
  cardSection.addWidget(cardSectionSelectionInput1);
  if (errors?.impact) {
    cardSection.addWidget(createErrorTextParagraph(errors.impact));
  }

  cardSection.addWidget(cardSectionSelectionInput2);
  cardSection.addWidget(cardSectionButtonList);

  const card = CardService.newCardBuilder()
    .setHeader(cardHeader)
    .addSection(cardSection)
    .build();

  if (isUpdate) {
    return CardService.newActionResponseBuilder()
      .setNavigation(CardService.newNavigation().updateCard(card))
      .build();
  } else {
    return card;
  }
}

Node.js

node/3p-resources/index.js
/**
 * Produces a support case creation form card.
 * 
 * @param {!Object} event The event object.
 * @param {!Object=} errors An optional map of per-field error messages.
 * @param {boolean} isUpdate Whether to return the form as an update card navigation.
 * @return {!Card|!ActionResponse} The resulting card or action response.
 */
function createCaseInputCard(event, errors, isUpdate) {

  const cardHeader1 = {
    title: "Create a support case"
  };

  const cardSection1TextInput1 = {
    textInput: {
      name: "name",
      label: "Name"
    }
  };

  const cardSection1TextInput2 = {
    textInput: {
      name: "description",
      label: "Description",
      type: "MULTIPLE_LINE"
    }
  };

  const cardSection1SelectionInput1 = {
    selectionInput: {
      name: "priority",
      label: "Priority",
      type: "DROPDOWN",
      items: [{
        text: "P0",
        value: "P0"
      }, {
        text: "P1",
        value: "P1"
      }, {
        text: "P2",
        value: "P2"
      }, {
        text: "P3",
        value: "P3"
      }]
    }
  };

  const cardSection1SelectionInput2 = {
    selectionInput: {
      name: "impact",
      label: "Impact",
      items: [{
        text: "Blocks a critical customer operation",
        value: "Blocks a critical customer operation"
      }]
    }
  };

  const cardSection1ButtonList1Button1Action1 = {
    function: process.env.URL,
    parameters: [
      {
        key: "submitCaseCreationForm",
        value: true
      }
    ],
    persistValues: true
  };

  const cardSection1ButtonList1Button1 = {
    text: "Create",
    onClick: {
      action: cardSection1ButtonList1Button1Action1
    }
  };

  const cardSection1ButtonList1 = {
    buttonList: {
      buttons: [cardSection1ButtonList1Button1]
    }
  };

  // Builds the creation form and adds error text for invalid inputs.
  const cardSection1 = [];
  if (errors?.name) {
    cardSection1.push(createErrorTextParagraph(errors.name));
  }
  cardSection1.push(cardSection1TextInput1);
  if (errors?.description) {
    cardSection1.push(createErrorTextParagraph(errors.description));
  }
  cardSection1.push(cardSection1TextInput2);
  if (errors?.priority) {
    cardSection1.push(createErrorTextParagraph(errors.priority));
  }
  cardSection1.push(cardSection1SelectionInput1);
  if (errors?.impact) {
    cardSection1.push(createErrorTextParagraph(errors.impact));
  }

  cardSection1.push(cardSection1SelectionInput2);
  cardSection1.push(cardSection1ButtonList1);

  const card = {
    header: cardHeader1,
    sections: [{
      widgets: cardSection1
    }]
  };

  if (isUpdate) {
    return {
      renderActions: {
        action: {
          navigations: [{
            updateCard: card
          }]
        }
      }
    };
  } else {
    return {
      action: {
        navigations: [{
          pushCard: card
        }]
      }
    };
  }
}

Python

python/3p-resources/create_3p_resources/main.py
def create_case_input_card(event, errors = {}, isUpdate = False):
    """Produces a support case creation form card.
    Args:
      event: The event object.
      errors: An optional dict of per-field error messages.
      isUpdate: Whether to return the form as an update card navigation.
    Returns:
      The resulting card or action response.
    """
    card_header1 = {
        "title": "Create a support case"
    }

    card_section1_text_input1 = {
        "textInput": {
            "name": "name",
            "label": "Name"
        }
    }

    card_section1_text_input2 = {
        "textInput": {
            "name": "description",
            "label": "Description",
            "type": "MULTIPLE_LINE"
        }
    }

    card_section1_selection_input1 = {
        "selectionInput": {
            "name": "priority",
            "label": "Priority",
            "type": "DROPDOWN",
            "items": [{
                "text": "P0",
                "value": "P0"
            }, {
                "text": "P1",
                "value": "P1"
            }, {
                "text": "P2",
                "value": "P2"
            }, {
                "text": "P3",
                "value": "P3"
            }]
        }
    }

    card_section1_selection_input2 = {
        "selectionInput": {
            "name": "impact",
            "label": "Impact",
            "items": [{
                "text": "Blocks a critical customer operation",
                "value": "Blocks a critical customer operation"
            }]
        }
    }

    card_section1_button_list1_button1_action1 = {
        "function": os.environ["URL"],
        "parameters": [
        {
            "key": "submitCaseCreationForm",
            "value": True
        }
        ],
        "persistValues": True
    }

    card_section1_button_list1_button1 = {
        "text": "Create",
        "onClick": {
            "action": card_section1_button_list1_button1_action1
        }
    }

    card_section1_button_list1 = {
        "buttonList": {
            "buttons": [card_section1_button_list1_button1]
        }
    }

    # Builds the creation form and adds error text for invalid inputs.
    card_section1 = []
    if "name" in errors:
        card_section1.append(create_error_text_paragraph(errors["name"]))
    card_section1.append(card_section1_text_input1)
    if "description" in errors:
        card_section1.append(create_error_text_paragraph(errors["description"]))
    card_section1.append(card_section1_text_input2)
    if "priority" in errors:
        card_section1.append(create_error_text_paragraph(errors["priority"]))
    card_section1.append(card_section1_selection_input1)
    if "impact" in errors:
        card_section1.append(create_error_text_paragraph(errors["impact"]))

    card_section1.append(card_section1_selection_input2)
    card_section1.append(card_section1_button_list1)

    card = {
        "header": card_header1,
        "sections": [{
            "widgets": card_section1
        }]
    }

    if isUpdate:
        return {
            "renderActions": {
                "action": {
                        "navigations": [{
                        "updateCard": card
                    }]
                }
            }
        }
    else:
        return {
            "action": {
                "navigations": [{
                    "pushCard": card
                }]
            }
        }

Java

java/3p-resources/src/main/java/Create3pResources.java
/**
 * Produces a support case creation form.
 * 
 * @param event The event object.
 * @param errors A map of per-field error messages.
 * @param isUpdate Whether to return the form as an update card navigation.
 * @return The resulting card or action response.
 */
JsonObject createCaseInputCard(JsonObject event, Map<String, String> errors, boolean isUpdate) {
  JsonObject cardHeader = new JsonObject();
  cardHeader.add("title", new JsonPrimitive("Create a support case"));

  JsonObject cardSectionTextInput1 = new JsonObject();
  cardSectionTextInput1.add("name", new JsonPrimitive("name"));
  cardSectionTextInput1.add("label", new JsonPrimitive("Name"));

  JsonObject cardSectionTextInput1Widget = new JsonObject();
  cardSectionTextInput1Widget.add("textInput", cardSectionTextInput1);

  JsonObject cardSectionTextInput2 = new JsonObject();
  cardSectionTextInput2.add("name", new JsonPrimitive("description"));
  cardSectionTextInput2.add("label", new JsonPrimitive("Description"));
  cardSectionTextInput2.add("type", new JsonPrimitive("MULTIPLE_LINE"));

  JsonObject cardSectionTextInput2Widget = new JsonObject();
  cardSectionTextInput2Widget.add("textInput", cardSectionTextInput2);

  JsonObject cardSectionSelectionInput1ItemsItem1 = new JsonObject();
  cardSectionSelectionInput1ItemsItem1.add("text", new JsonPrimitive("P0"));
  cardSectionSelectionInput1ItemsItem1.add("value", new JsonPrimitive("P0"));

  JsonObject cardSectionSelectionInput1ItemsItem2 = new JsonObject();
  cardSectionSelectionInput1ItemsItem2.add("text", new JsonPrimitive("P1"));
  cardSectionSelectionInput1ItemsItem2.add("value", new JsonPrimitive("P1"));

  JsonObject cardSectionSelectionInput1ItemsItem3 = new JsonObject();
  cardSectionSelectionInput1ItemsItem3.add("text", new JsonPrimitive("P2"));
  cardSectionSelectionInput1ItemsItem3.add("value", new JsonPrimitive("P2"));

  JsonObject cardSectionSelectionInput1ItemsItem4 = new JsonObject();
  cardSectionSelectionInput1ItemsItem4.add("text", new JsonPrimitive("P3"));
  cardSectionSelectionInput1ItemsItem4.add("value", new JsonPrimitive("P3"));

  JsonArray cardSectionSelectionInput1Items = new JsonArray();
  cardSectionSelectionInput1Items.add(cardSectionSelectionInput1ItemsItem1);
  cardSectionSelectionInput1Items.add(cardSectionSelectionInput1ItemsItem2);
  cardSectionSelectionInput1Items.add(cardSectionSelectionInput1ItemsItem3);
  cardSectionSelectionInput1Items.add(cardSectionSelectionInput1ItemsItem4);

  JsonObject cardSectionSelectionInput1 = new JsonObject();
  cardSectionSelectionInput1.add("name", new JsonPrimitive("priority"));
  cardSectionSelectionInput1.add("label", new JsonPrimitive("Priority"));
  cardSectionSelectionInput1.add("type", new JsonPrimitive("DROPDOWN"));
  cardSectionSelectionInput1.add("items", cardSectionSelectionInput1Items);

  JsonObject cardSectionSelectionInput1Widget = new JsonObject();
  cardSectionSelectionInput1Widget.add("selectionInput", cardSectionSelectionInput1);

  JsonObject cardSectionSelectionInput2ItemsItem = new JsonObject();
  cardSectionSelectionInput2ItemsItem.add("text", new JsonPrimitive("Blocks a critical customer operation"));
  cardSectionSelectionInput2ItemsItem.add("value", new JsonPrimitive("Blocks a critical customer operation"));

  JsonArray cardSectionSelectionInput2Items = new JsonArray();
  cardSectionSelectionInput2Items.add(cardSectionSelectionInput2ItemsItem);

  JsonObject cardSectionSelectionInput2 = new JsonObject();
  cardSectionSelectionInput2.add("name", new JsonPrimitive("impact"));
  cardSectionSelectionInput2.add("label", new JsonPrimitive("Impact"));
  cardSectionSelectionInput2.add("items", cardSectionSelectionInput2Items);

  JsonObject cardSectionSelectionInput2Widget = new JsonObject();
  cardSectionSelectionInput2Widget.add("selectionInput", cardSectionSelectionInput2);

  JsonObject cardSectionButtonListButtonActionParametersParameter = new JsonObject();
  cardSectionButtonListButtonActionParametersParameter.add("key", new JsonPrimitive("submitCaseCreationForm"));
  cardSectionButtonListButtonActionParametersParameter.add("value", new JsonPrimitive(true));

  JsonArray cardSectionButtonListButtonActionParameters = new JsonArray();
  cardSectionButtonListButtonActionParameters.add(cardSectionButtonListButtonActionParametersParameter);

  JsonObject cardSectionButtonListButtonAction = new JsonObject();
  cardSectionButtonListButtonAction.add("function", new JsonPrimitive(System.getenv().get("URL")));
  cardSectionButtonListButtonAction.add("parameters", cardSectionButtonListButtonActionParameters);
  cardSectionButtonListButtonAction.add("persistValues", new JsonPrimitive(true));

  JsonObject cardSectionButtonListButtonOnCLick = new JsonObject();
  cardSectionButtonListButtonOnCLick.add("action", cardSectionButtonListButtonAction);

  JsonObject cardSectionButtonListButton = new JsonObject();
  cardSectionButtonListButton.add("text", new JsonPrimitive("Create"));
  cardSectionButtonListButton.add("onClick", cardSectionButtonListButtonOnCLick);

  JsonArray cardSectionButtonListButtons = new JsonArray();
  cardSectionButtonListButtons.add(cardSectionButtonListButton);

  JsonObject cardSectionButtonList = new JsonObject();
  cardSectionButtonList.add("buttons", cardSectionButtonListButtons);

  JsonObject cardSectionButtonListWidget = new JsonObject();
  cardSectionButtonListWidget.add("buttonList", cardSectionButtonList);

  // Builds the form inputs with error texts for invalid values.
  JsonArray cardSection = new JsonArray();
  if (errors.containsKey("name")) {
    cardSection.add(createErrorTextParagraph(errors.get("name").toString()));
  }
  cardSection.add(cardSectionTextInput1Widget);
  if (errors.containsKey("description")) {
    cardSection.add(createErrorTextParagraph(errors.get("description").toString()));
  }
  cardSection.add(cardSectionTextInput2Widget);
  if (errors.containsKey("priority")) {
    cardSection.add(createErrorTextParagraph(errors.get("priority").toString()));
  }
  cardSection.add(cardSectionSelectionInput1Widget);
  if (errors.containsKey("impact")) {
    cardSection.add(createErrorTextParagraph(errors.get("impact").toString()));
  }

  cardSection.add(cardSectionSelectionInput2Widget);
  cardSection.add(cardSectionButtonListWidget);

  JsonObject cardSectionWidgets = new JsonObject();
  cardSectionWidgets.add("widgets", cardSection);

  JsonArray sections = new JsonArray();
  sections.add(cardSectionWidgets);

  JsonObject card = new JsonObject();
  card.add("header", cardHeader);
  card.add("sections", sections);

  JsonObject navigation = new JsonObject();
  if (isUpdate) {
    navigation.add("updateCard", card);
  } else {
    navigation.add("pushCard", card);
  }

  JsonArray navigations = new JsonArray();
  navigations.add(navigation);

  JsonObject action = new JsonObject();
  action.add("navigations", navigations);

  JsonObject renderActions = new JsonObject();
  renderActions.add("action", action);

  if (!isUpdate) {
    return renderActions;
  }

  JsonObject update = new JsonObject();
  update.add("renderActions", renderActions);

  return update;
}

La funzione createCaseInputCard esegue il rendering della seguente scheda:

Scheda con input del modulo

La scheda include input di testo, un menu a discesa e una casella di controllo. Ha anche un pulsante di testo con un'azione onClick che esegue un'altra funzione per gestire l'invio del modulo di creazione.

Dopo che l'utente ha compilato il modulo e fatto clic su Crea, il componente aggiuntivo invia gli input del modulo alla funzione onClick azione, chiamata submitCaseCreationForm nel nostro esempio, a questo punto il componente aggiuntivo può convalidare gli input e utilizzarli per creare la risorsa nel servizio di terze parti.

Gestire gli invii di moduli

Dopo che un utente invia il modulo di creazione, viene eseguita la funzione associata all'azione onClick. Per un'esperienza utente ideale, il componente aggiuntivo deve gestire sia gli invii di moduli riusciti che quelli errati.

Gestire la creazione riuscita della risorsa

La funzione onClick del componente aggiuntivo deve creare la risorsa nel servizio di terze parti e generare un URL che punta a questa risorsa.

Per comunicare l'URL della risorsa a Documenti per la creazione di chip, la funzione onClick deve restituire un SubmitFormResponse con un array di un elemento in renderActions.action.links che punta a un link. Il titolo del link deve rappresentare il titolo della risorsa creata e l'URL deve indirizzare a quella risorsa.

L'esempio seguente mostra un SubmitFormResponse per una risorsa creata:

Apps Script

apps-script/3p-resources/3p-resources.gs
/**
 * Returns a submit form response that inserts a link into the document.
 * 
 * @param {string} title The title of the link to insert.
 * @param {string} url The URL of the link to insert.
 * @return {!SubmitFormResponse} The resulting submit form response.
 */
function createLinkRenderAction(title, url) {
  return {
    renderActions: {
      action: {
        links: [{
          title: title,
          url: url
        }]
      }
    }
  };
}

Node.js

node/3p-resources/index.js
/**
 * Returns a submit form response that inserts a link into the document.
 * 
 * @param {string} title The title of the link to insert.
 * @param {string} url The URL of the link to insert.
 * @return {!SubmitFormResponse} The resulting submit form response.
 */
function createLinkRenderAction(title, url) {
  return {
    renderActions: {
      action: {
        links: [{
          title: title,
          url: url
        }]
      }
    }
  };
}

Python

python/3p-resources/create_3p_resources/main.py
def create_link_render_action(title, url):
    """Returns a submit form response that inserts a link into the document.
    Args:
      title: The title of the link to insert.
      url: The URL of the link to insert.
    Returns:
      The resulting submit form response.
    """
    return {
        "renderActions": {
            "action": {
                "links": [{
                    "title": title,
                    "url": url
                }]
            }
        }
    }

Java

java/3p-resources/src/main/java/Create3pResources.java
/**
 * Returns a submit form response that inserts a link into the document.
 * 
 * @param title The title of the link to insert.
 * @param url The URL of the link to insert.
 * @return The resulting submit form response.
 */
JsonObject createLinkRenderAction(String title, String url) {
  JsonObject link = new JsonObject();
  link.add("title", new JsonPrimitive(title));
  link.add("url", new JsonPrimitive(url));

  JsonArray links = new JsonArray();
  links.add(link);

  JsonObject action = new JsonObject();
  action.add("links", links);

  JsonObject renderActions = new JsonObject();
  renderActions.add("action", action);

  JsonObject linkRenderAction = new JsonObject();
  linkRenderAction.add("renderActions", renderActions);

  return linkRenderAction;
}

Una volta restituito SubmitFormResponse, la finestra di dialogo modale si chiude e il componente aggiuntivo inserisce un chip nel documento. Quando gli utenti passano il puntatore sopra questo chip, viene richiamato il trigger di anteprima del link associato. Assicurati che il componente aggiuntivo non inserisca chip con pattern di link non supportati dai trigger di anteprima dei link.

Gestisci gli errori

Se un utente tenta di inviare un modulo con campi non validi, anziché restituire un SubmitFormResponse con un link, il componente aggiuntivo deve restituire un'azione di rendering che visualizza un errore utilizzando una navigazione updateCard. In questo modo, l'utente può vedere cosa ha fatto di sbagliato e riprovare. Consulta updateCard(card) per Apps Script e updateCard per altri runtime. Le notifiche e la navigazione pushCard non sono supportate.

Esempio di gestione degli errori

L'esempio seguente mostra il codice richiamato quando un utente invia il modulo. Se gli input non sono validi, la scheda viene aggiornata e mostra i messaggi di errore. Se gli input sono validi, il componente aggiuntivo restituisce un SubmitFormResponse con un link alla risorsa creata.

Apps Script

apps-script/3p-resources/3p-resources.gs
/**
 * Submits the creation form. If valid, returns a render action
 * that inserts a new link into the document. If invalid, returns an
 * update card navigation that re-renders the creation form with error messages.
 * 
 * @param {!Object} event The event object with form input values.
 * @return {!ActionResponse|!SubmitFormResponse} The resulting response.
 */
function submitCaseCreationForm(event) {
  const caseDetails = {
    name: event.formInput.name,
    description: event.formInput.description,
    priority: event.formInput.priority,
    impact: !!event.formInput.impact,
  };

  const errors = validateFormInputs(caseDetails);
  if (Object.keys(errors).length > 0) {
    return createCaseInputCard(event, errors, /* isUpdate= */ true);
  } else {
    const title = `Case ${caseDetails.name}`;
    // Adds the case details as parameters to the generated link URL.
    const url = 'https://example.com/support/cases/?' + generateQuery(caseDetails);
    return createLinkRenderAction(title, url);
  }
}

/**
* Build a query path with URL parameters.
*
* @param {!Map} parameters A map with the URL parameters.
* @return {!string} The resulting query path.
*/
function generateQuery(parameters) {
  return Object.entries(parameters).flatMap(([>k, v]) =
    Array.isArray(v) ? v>.map(e = `${k}=${encodeURIComponent(e)}`) : `${k}=${encodeURIComponent(v)}`
  &).join("");
}

Node.js

node/3p-resources/index.js
/**
 * Submits the creation form. If valid, returns a render action
 * that inserts a new link into the document. If invalid, returns an
 * update card navigation that re-renders the creation form with error messages.
 * 
 * @param {!Object} event The event object with form input values.
 * @return {!ActionResponse|!SubmitFormResponse} The resulting response.
 */
function submitCaseCreationForm(event) {
  const caseDetails = {
    name: event.commonEventObject.formInputs?.name?.stringInputs?.value[0],
    description: event.commonEventObject.formInputs?.description?.stringInputs?.value[0],
    priority: event.commonEventObject.formInputs?.priority?.stringInputs?.value[0],
    impact: !!event.commonEventObject.formInputs?.impact?.stringInputs?.value[0],
  };

  const errors = validateFormInputs(caseDetails);
  if (Object.keys(errors).length > 0) {
    return createCaseInputCard(event, errors, /* isUpdate= */ true);
  } else {
    const title = `Case ${caseDetails.name}`;
    // Adds the case details as parameters to the generated link URL.
    const url = new URL('https://example.com/support/cases/');
    for (const [key, value] of Object.entries(caseDetails)) {
      url.searchParams.append(key, value);
    }
    return createLinkRenderAction(title, url.href);
  }
}

Python

python/3p-resources/create_3p_resources/main.py
def submit_case_creation_form(event):
    """Submits the creation form.

    If valid, returns a render action that inserts a new link
    into the document. If invalid, returns an update card navigation that
    re-renders the creation form with error messages.
    Args:
      event: The event object with form input values.
    Returns:
      The resulting response.
    """
    formInputs = event["commonEventObject"]["formInputs"] if "formInputs" in event["commonEventObject"] else None
    case_details = {
        "name":  None,
        "description": None,
        "priority": None,
        "impact": None,
    }
    if formInputs is not None:
        case_details["name"] = formInputs["name"]["stringInputs"]["value"][0] if "name" in formInputs else None
        case_details["description"] = formInputs["description"]["stringInputs"]["value"][0] if "description" in formInputs else None
        case_details["priority"] = formInputs["priority"]["stringInputs">;]["value"][0] if "priority" in formInputs else None
        case_details["impact"] = formInputs["impact"]["stringInputs"]["value"][0] if "impact" in formInputs else False

    errors = validate_form_inputs(case_details)
    if len(errors)  0:
        return create_case_input_card(event, errors, True) # Update mode
    else:
        title = f'Case {case_details["name"]}'
        # Adds the case details as parameters to the generated link URL.
        url = "https://example.com/support/cases/?" + urlencode(case_details)
        return create_link_render_action(title, url)

Java

java/3p-resources/src/main/java/Create3pResources.java
/**
 * Submits the creation form. If valid, returns a render action
 * that inserts a new link into the document. If invalid, returns an
 * update card navigation that re-renders the creation form with error messages.
 * 
 * @param event The event object with form input values.
 * @return The resulting response.
 */
JsonObject submitCaseCreationForm(JsonObject event) throws Exception {
  JsonObject formInputs = event.getAsJsonObject("commonEventObject").getAsJsonObject("form<Inputs");>
  MapString, String caseD<etails = new H>ashMapString, String();
  if (formInputs != null) {
    if (formInputs.has("name")) {
      caseDetails.put("name", formInputs.getAsJsonObject("name").getAsJsonObject("stringInputs").getAsJsonArray("value").get(0).getAsString());
    }
    if (formInputs.has("description")) {
      caseDetails.put("description", formInputs.getAsJsonObject("description").getAsJsonObject("stringInputs").getAsJsonArray("value").get(0).getAsString());
    }
    if (formInputs.has("priority")) {
      caseDetails.put("priority", formInputs.getAsJsonObject("priority").getAsJsonObject("stringInputs").getAsJsonArray("value").get(0).getAsString());
    }
    if (formInputs.has("i<mpact")) >{
      caseDetails.put("impact", formInputs.getAsJso>nObject("impact").getAsJsonObject("stringInputs").getAsJsonArray("value").get(0).getAsString());
    }
  }

  MapString, String errors = validateFormInputs(caseDetails);
  if (errors.size()  0) {
    return createCaseInputCard(event, errors, /* isUpdate= */ true);
  } else {
    String title = String.format("Case %s", caseDetails.get("name"));
    // Adds the case details as parameters to the generated link URL.
    URIBuilder uriBuilder = new URIBuilder("https://example.com/support/cases/");
    for (String caseDetailKey : caseDetails.keySet()) {
      uriBuilder.addParameter(caseDetailKey, caseDetails.get(caseDetailKey));
    }
    return createLinkRenderAction(title, uriBuilder.build().toURL().toString());
  }
}

Il seguente esempio di codice convalida gli input del modulo e crea messaggi di errore per gli input non validi:

Apps Script

apps-script/3p-resources/3p-resources.gs
/**
 * Validates case creation form input values.
 * 
 * @param {!Object} caseDetails The values of each form input submitted by the user.
 * @return {!Object} A map from field name to error message. An empty object
 *     represents a valid form submission.
 */
function validateFormInputs(caseDetails) {
  const errors = {};
  if (!caseDetails.name) {
    errors.name = 'You must provide a name';
  }
  if (!caseDetails.description) {
    errors.description = 'You must provide a description';
  }
  if (!caseDetails.priority) {
    errors.priority = 'You must provide a priority';
  }
 && if (caseDetails.impact  caseDe&&tails.priority !== 'P0'  caseDetails.priority !== 'P1') {
    errors.impact = 'If an issue blocks a critical customer operation, priority must be P0 or P1';
  }

  return errors;
}

/**
 * Returns a text paragraph with red text indicating a form field validation error.
 * 
 * @param {string} errorMessage A description of input value error.
 * @return {!TextParagraph} The resulting text paragraph.
 */
function createErrorTextParagraph(errorMessage) {
  retu<rn CardService.newText><P>aragra<ph>()
    .setText('<font >color=\"#BA0300\"bError:/b ' + errorMessage + '/font');
}

Node.js

node/3p-resources/index.js
/**
 * Validates case creation form input values.
 * 
 * @param {!Object} caseDetails The values of each form input submitted by the user.
 * @return {!Object} A map from field name to error message. An empty object
 *     represents a valid form submission.
 */
function validateFormInputs(caseDetails) {
  const errors = {};
  if (caseDetails.name === undefined) {
    errors.name = 'You must provide a name';
  }
  if (caseDetails.description === undefined) {
    errors.description = 'You must provide a description';
  }
  if (caseDetails.priority === undefined) {
    errors.priority = 'You must provide a priority';
  }
 && if (caseDetails.impact  !(['P0', 'P1']).includes(caseDetails.priority)) {
    errors.impact = 'If an issue blocks a critical customer operation, priority must be P0 or P1';
  }

  return errors;
}

/**
 * Returns a text paragraph with red text indicating a form field validation error.
 * 
 * @param {string} errorMessage A description of input value error.
 * @return {!TextParagraph} The resulting text paragraph.
 */
function createErrorTextParagraph(errorMessage)< {
  return {
    text><P>aragra<ph>: {
      text: '<font >color=\"#BA0300\"bError:/b ' + errorMessage + '/font'
    }
  }
}

Python

python/3p-resources/create_3p_resources/main.py
def validate_form_inputs(case_details):
    """Validates case creation form input values.
    Args:
      case_details: The values of each form input submitted by the user.
    Returns:
      A dict from field name to error message. An empty object represents a valid form submission.
    """
    errors = {}
    if case_details["name"] is None:
        errors["name"] = "You must provide a name"
    if case_details["description"] is None:
        errors["description"] = "You must provide a description"
    if case_details["priority"] is None:
        errors["priority"] = "You must provide a priority"
    if case_details["impact"] is not None and case_details["priority"] not in ['P0', 'P1']:
        errors["impact"] = "If an issue blocks a critical customer operation, priority must be P0 or P1"
    return errors


def create_error_text_paragraph(error_message):
    """Returns a text paragraph with red text indicating a form field validation <error.
    Args:
     >< >error_<es>sage: A description of< inpu>t value error.
    Returns:
      The resulting text paragraph.
    """
    return {
        "textParagraph": {
            "text": 'font color=\"#BA0300\"bError:/b ' + error_message + '/font'
        }
    }

Java

java/3p-resources/src/main/java/Create3pResources.java
/**
 * Validates case creation form input values.
 * 
 * @param caseDetails The values of each form input submitted by the user.
 * @return A map from field name to error message. An empty object
 *     represents a valid form submission.
 */
Map<String, String> validateFormInputs(Map<String, String> caseDetails) {
  Map<String, String> errors = new HashMap<String, String>();
  if (!caseDetails.containsKey("name")) {
    errors.put("name", "You must provide a name");
  }
  if (!caseDetails.containsKey("description")) {
    errors.put("description", "You must provide a description");
  }
  if (!caseDetails.containsKey("priority")) {
    errors.put("priority&quo&&t;, "You must provide a priority");
  }
  if (caseDetails.containsKey("impact")  !Arrays.asList(new String[]{"P0", "P1"}).contains(caseDetails.get("priority"))) {
    errors.put("impact", "If an issue blocks a critical customer operation, priority must be P0 or P1");
  }

  return errors;
}

/**
 * Returns a text paragraph with red text indicating a form field validation error.
 * 
 * @param errorMessage A description of input value error.
 * @return The resulting text paragraph.
 */
Jso<nObject createErrorTex><t>Paragr<ap>h(String errorMessage<) {
 > JsonObject textParagraph = new JsonObject();
  textParagraph.add("text", new JsonPrimitive("font color=\"#BA0300\"bError:/b " + errorMessage + "/font"));

  JsonObject textParagraphWidget = new JsonObject();
  textParagraphWidget.add("textParagraph", textParagraph);

  return textParagraphWidget;
}

Esempio completo: componente aggiuntivo per le richieste di assistenza

Il seguente esempio mostra un componente aggiuntivo di Google Workspace che visualizza l'anteprima dei link alle richieste di assistenza di un'azienda e consente agli utenti di creare richieste di assistenza da Google Docs.

L'esempio esegue queste operazioni:

  • Genera una scheda con i campi del modulo per creare una richiesta di assistenza dal menu @ di Documenti.
  • Convalida gli input del modulo e restituisce messaggi di errore per gli input non validi.
  • Inserisce il nome e il link della richiesta di assistenza creata nel documento Documenti come smart chip.
  • Visualizza l'anteprima del link alla richiesta di assistenza, ad esempio https://www.example.com/support/cases/1234. La smart chip mostra un'icona e la scheda di anteprima include il nome, la priorità e la descrizione della richiesta.

Manifest

Apps Script

apps-script/3p-resources/appsscript.json
{
  "timeZone": "America/New_York",
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8",
  "oauthScopes": [
    "https://www.googleapis.com/auth/workspace.linkpreview",
    "https://www.googleapis.com/auth/workspace.linkcreate"
  ],
  "addOns": {
    "common": {
      "name": "Manage support cases",
      "logoUrl": "https://developers.google.com/workspace/add-ons/images/support-icon.png",
      "layoutProperties": {
        "primaryColor": "#dd4b39"
      }
    },
    "docs": {
      "linkPreviewTriggers": [
        {
          "runFunction": "caseLinkPreview",
          "patterns": [
            {
              "hostPattern": "example.com",
              "pathPrefix": "support/cases"
            },
            {
              "hostPattern": "*.example.com",
              "pathPrefix": "cases"
            },
            {
              "hostPattern": "cases.example.com"
            }
          ],
          "labelText": "Support case",
          "localizedLabelText": {
            "es": "Caso de soporte"
          },
          "logoUrl": "https://developers.google.com/workspace/add-ons/images/support-icon.png"
        }
      ],
      "createActionTriggers": [
        {
          "id": "createCase",
          "labelText": "Create support case",
          "localizedLabelText": {
            "es": "Crear caso de soporte"
          },
          "runFunction": "createCaseInputCard",
          "logoUrl": "https://developers.google.com/workspace/add-ons/images/support-icon.png"
        }
      ]
    }
  }
}

Node.js

node/3p-resources/deployment.json
{
  "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"
        }
      ]
    }
  }
}

Codice

Apps Script

apps-script/3p-resources/3p-resources.gs
/**
 * Copyright 2024 Google LLC
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     https://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
* Entry point for a support case link preview.
*
* @param {!Object} event The event object.
* @return {!Card} The resulting preview link card.
*/
function caseLinkPreview(event) {

  // If the event object URL matches a specified pattern for support case links.
  if (event.docs.matchedUrl.url) {

    // Uses the event object to parse the URL and identify the case details.
    const caseDetails = parseQuery(event.docs.matchedUrl.url);

    // Builds a preview card with the case name, and description
    const caseHeader = CardService.newCardHeader()
      .setTitle(`Case ${caseDetails["name"][0]}`);
    const caseDescription = CardService.newTextParagraph()
      .setText(caseDetails["description"][0]);

    // Returns the card.
    // Uses the text from the card's header for the title of the smart chip.
    return CardService.newCardBuilder()
      .setHeader(caseHeader)
      .addSection(CardService.newCardSection().addWidget(caseDescription))
      .build();
  }
}

/**
* Extracts the URL parameters from the given URL.
*
* @param {!string} url The URL to parse.
* @return {!Map} A map with the extracted URL parameters.
*/
function parseQuery(url) {
  const query = url.split(&quo&t;?")[1];
  if (query) {
    return query.split("")
    .reduce(function(o, e) {
      var temp = e.split("=");
      var key = temp[0].trim();
      var value = temp[1].trim();
      value = isNaN(value) ? value : Number(value);
      if (o[key]) {
        o[key].push(value);
      } else {
        o[key] = [value];
      }
      return o;
    }, {});
  }
  return null;
}



/**
 * Produces a support case creation form card.
 * 
 * @param {!Object} event The event object.
 * @param {!Object=} errors An optional map of per-field error messages.
 * @param {boolean} isUpdate Whether to return the form as an update card navigation.
 * @return {!Card|!ActionResponse} The resulting card or action response.
 */
function createCaseInputCard(event, errors, isUpdate) {

  const cardHeader = CardService.newCardHeader()
    .setTitle('Create a support case')

  const cardSectionTextInput1 = CardService.newTextInput()
    .setFieldName('name')
    .setTitle('Name')
    .setMultiline(false);

  const cardSectionTextInput2 = CardService.newTextInput()
    .setFieldName('description')
    .setTitle('Description')
    .setMultiline(true);

  const cardSectionSelectionInput1 = CardService.newSelectionInput()
    .setFieldName('priority')
    .setTitle('Priority')
    .setType(CardService.SelectionInputType.DROPDOWN)
    .addItem('P0', 'P0', false)
    .addItem('P1', 'P1', false)
    .addItem('P2', 'P2', false)
    .addItem('P3', 'P3', false);

  const cardSectionSelectionInput2 = CardService.newSelectionInput()
    .setFieldName('impact')
    .setTitle('Impact')
    .setType(CardService.SelectionInputType.CHECK_BOX)
    .addItem('Blocks a critical customer operation', 'Blocks a critical customer operation', false);

  const cardSectionButtonListButtonAction = CardService.newAction()
    .setPersistValues(true)
    .setFunctionName('submitCaseCreationForm')
    .setParameters({});

  const cardSectionButtonListButton = CardService.newTextButton()
    .setText('Create')
    .setTextButtonStyle(CardService.TextButtonStyle.TEXT)
    .setOnClickAction(cardSectionButtonListButtonAction);

  const cardSectionButtonList = CardService.newButtonSet()
    .addButton(cardSectionButtonListButton);

  // Builds the form inputs with error texts for invalid values.
  const cardSection = CardService.newCardSection();
  if (errors?.name) {
    cardSection.addWidget(createErrorTextParagraph(errors.name));
  }
  cardSection.addWidget(cardSectionTextInput1);
  if (errors?.description) {
    cardSection.addWidget(createErrorTextParagraph(errors.description));
  }
  cardSection.addWidget(cardSectionTextInput2);
  if (errors?.priority) {
    cardSection.addWidget(createErrorTextParagraph(errors.priority));
  }
  cardSection.addWidget(cardSectionSelectionInput1);
  if (errors?.impact) {
    cardSection.addWidget(createErrorTextParagraph(errors.impact));
  }

  cardSection.addWidget(cardSectionSelectionInput2);
  cardSection.addWidget(cardSectionButtonList);

  const card = CardService.newCardBuilder()
    .setHeader(cardHeader)
    .addSection(cardSection)
    .build();

  if (isUpdate) {
    return CardService.newActionResponseBuilder()
      .setNavigation(CardService.newNavigation().updateCard(card))
      .build();
  } else {
    return card;
  }
}


/**
 * Submits the creation form. If valid, returns a render action
 * that inserts a new link into the document. If invalid, returns an
 * update card navigation that re-renders the creation form with error messages.
 * 
 * @param {!Object} event The event object with form input values.
 * @return {!ActionResponse|!SubmitFormResponse} The resulting response.
 */
function submitCaseCreationForm(event) {
  const caseDetails = {
   > name: event.formInput.name,
    description: event.formInput.description,
    priority: event.formInput.priority,
    impact: !!event.formInput.impact,
  };

  const errors = validateFormInputs(caseDetails);
  if (Object.keys(errors).length  0) {
    return createCaseInputCard(event, errors, /* isUpdate= */ true);
  } else {
    const title = `Case ${caseDetails.name}`;
    // Adds the case details as parameters to the generated link URL.
    const url = 'https://example.com/support/cases/?' + generateQuery(caseDetails);
    return createLinkRenderAction(title, url);
  }>
}

/**
* Build a query path with> URL parameters.
*
* @param {!Map} parameters A map with the URL parameters.
*& @return {!string} The resulting query path.
*/
function generateQuery(parameters) {
  return Object.entries(parameters).flatMap(([k, v]) =
    Array.isArray(v) ? v.map(e = `${k}=${encodeURIComponent(e)}`) : `${k}=${encodeURIComponent(v)}`
  ).join("");
}


/**
 * Validates case creation form input values.
 * 
 * @param {!Object} caseDetails The values of each form input submitted by the user.
 * @return {!Object} A map from field name to error message. An empty object
 *     represents a valid form submission.
 */
function validateFormInputs(caseDetails) {
  const errors = {};
  if (!caseDetails.name) {&&
    errors.name = 'You mus&&t provide a name';
  }
  if (!caseDetails.description) {
    errors.description = 'You must provide a description';
  }
  if (!caseDetails.priority) {
    errors.priority = 'You must provide a priority';
  }
  if (caseDetails.impact  caseDetails.priority !== 'P0'  caseDetails.priority !== 'P1') {
    errors.impact = 'If an issue blocks a critical customer operation, priority must be P0 or P1';
  }

  return errors;
}

/**
 * Returns a t<ext paragraph with red>< >text i<nd>icating a form field <valid>ation error.
 * 
 * @param {string} errorMessage A description of input value error.
 * @return {!TextParagraph} The resulting text paragraph.
 */
function createErrorTextParagraph(errorMessage) {
  return CardService.newTextParagraph()
    .setText('font color=\"#BA0300\"bError:/b ' + errorMessage + '/font');
}


/**
 * Returns a submit form response that inserts a link into the document.
 * 
 * @param {string} title The title of the link to insert.
 * @param {string} url The URL of the link to insert.
 * @return {!SubmitFormResponse} The resulting submit form response.
 */
function createLinkRenderAction(title, url) {
  return {
    renderActions: {
      action: {
        links: [{
          title: title,
          url: url
        }]
      }
    }
  };
}

Node.js

node/3p-resources/index.js
/**
 * Copyright 2024 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Responds to any HTTP request related to link previews.
 *
 * @param {Object} req An HTTP request context.
 * @param {Object} res An HTTP response context.
 */
exports.createLinkPr>eview = (req, res) = {
  const event = req.body;
  if (event.docs.matchedUrl.url) {
    const url = event.docs.matchedUrl.url;
    const parsedUrl = new URL(url);
    // If the event object URL matches a specified pattern for preview links.
    if (parsedUrl.hostname === 'example.com') {
      if (parsedUrl.pathname.startsWith('/support/cases/')) {
        return res.json(caseLinkPreview(parsedUrl));
      }
    }
  }
};


/**
 * 
 * A support case link preview.
 *
 * @param {!URL} url The event object.
 * @return {!Card} The resulting preview link card.
 */
function caseLinkPreview(url) {
  // Builds a preview card with the case name, and description
  // Uses the text from the card's header for the title of the smart chip.
  // Parses the URL and identify the case details.
  const name = `Case ${url.searchParams.get("name")}`;
  return {
    action: {
      linkPreview: {
        title: name,
        previewCard: {
          header: {
            title: name
          },
          sections: [{
            widgets: [{
              textParagraph: {
                text: url.searchParams.get("description")
              }
            }]
          }]
        }
      }
    }
  };
}



/**
 * Responds to any HTTP request related to 3P resource creations.
 *
 * @param {Object} req An HTTP request context.
 * @param {Object} res An HTTP re>sponse context.
 */
exports.create3pResources = (req, res) = {
  const event = req.body;
  if (event.commonEventObject.parameters?.submitCaseCreationForm) {
    res.json(submitCaseCreationForm(event));
  } else {
    res.json(createCaseInputCard(event));
  }
};


/**
 * Produces a support case creation form card.
 * 
 * @param {!Object} event The event object.
 * @param {!Object=} errors An optional map of per-field error messages.
 * @param {boolean} isUpdate Whether to return the form as an update card navigation.
 * @return {!Card|!ActionResponse} The resulting card or action response.
 */
function createCaseInputCard(event, errors, isUpdate) {

  const cardHeader1 = {
    title: "Create a support case"
  };

  const cardSection1TextInput1 = {
    textInput: {
      name: "name",
      label: "Name"
    }
  };

  const cardSection1TextInput2 = {
    textInput: {
      name: "description",
      label: "Description",
      type: "MULTIPLE_LINE"
    }
  };

  const cardSection1SelectionInput1 = {
    selectionInput: {
      name: "priority",
      label: "Priority",
      type: "DROPDOWN",
      items: [{
        text: "P0",
        value: "P0"
      }, {
        text: "P1",
        value: "P1"
      }, {
        text: "P2",
        value: "P2"
      }, {
        text: "P3",
        value: "P3"
      }]
    }
  };

  const cardSection1SelectionInput2 = {
    selectionInput: {
      name: "impact",
      label: "Impact",
      items: [{
        text: "Blocks a critical customer operation",
        value: "Blocks a critical customer operation"
      }]
    }
  };

  const cardSection1ButtonList1Button1Action1 = {
    function: process.env.URL,
    parameters: [
      {
        key: "submitCaseCreationForm",
        value: true
      }
    ],
    persistValues: true
  };

  const cardSection1ButtonList1Button1 = {
    text: "Create",
    onClick: {
      action: cardSection1ButtonList1Button1Action1
    }
  };

  const cardSection1ButtonList1 = {
    buttonList: {
      buttons: [cardSection1ButtonList1Button1]
    }
  };

  // Builds the creation form and adds error text for invalid inputs.
  const cardSection1 = [];
  if (errors?.name) {
    cardSection1.push(createErrorTextParagraph(errors.name));
  }
  cardSection1.push(cardSection1TextInput1);
  if (errors?.description) {
    cardSection1.push(createErrorTextParagraph(errors.description));
  }
  cardSection1.push(cardSection1TextInput2);
  if (errors?.priority) {
    cardSection1.push(createErrorTextParagraph(errors.priority));
  }
  cardSection1.push(cardSection1SelectionInput1);
  if (errors?.impact) {
    cardSection1.push(createErrorTextParagraph(errors.impact));
  }

  cardSection1.push(cardSection1SelectionInput2);
  cardSection1.push(cardSection1ButtonList1);

  const card = {
    header: cardHeader1,
    sections: [{
      widgets: cardSection1
    }]
  };

  if (isUpdate) {
    return {
      renderActions: {
        action: {
          navigations: [{
            updateCard: card
          }]
        }
      }
    };
  } else {
    return {
      action: {
        navigations: [{
          pushCard: card
        }]
      }
    };
  }
}


/**
 * Submits the creation form. If valid, returns a render action
 * that inserts a new link into the document. If invalid, returns an
 * update card navigation that re-renders the creation form with error messages.
 * 
 * @param {!Object} event The event object with form input values.
 * @return {!ActionResponse|!SubmitFormResponse} The resulting response.
 */
function submitCaseCreationForm(event) {
  const caseDetails = {
    name: event.commonEventObject.formInputs?.name?.stringInputs?.value[0],
    description: event.commonEventObject.formInputs?.de>scription?.stringInputs?.value[0],
    priority: event.commonEventObject.formInputs?.priority?.stringInputs?.value[0],
    impact: !!event.commonEventObject.formInputs?.impact?.stringInputs?.value[0],
  };

  const errors = validateFormInputs(caseDetails);
  if (Object.keys(errors).length  0) {
    return createCaseInputCard(event, errors, /* isUpdate= */ true);
  } else {
    const title = `Case ${caseDetails.name}`;
    // Adds the case details as parameters to the generated link URL.
    const url = new URL('https://example.com/support/cases/');
    for (const [key, value] of Object.entries(caseDetails)) {
      url.searchParams.append(key, value);
    }
    return createLinkRenderAction(title, url.href);
  }
}


/**
 * Validates case creation form input values.
 * 
 * @param {!Object} caseDetails The values of each form input submitted by the user.
 * @return {!Object} A map from field name to error message. An empty object
 *     represents a valid form submission.
 */
function validateFormInputs(caseDetails) {
  const errors = {};
  if (caseDetails.name === &&undefined) {
    errors.name = 'You must provide a name';
  }
  if (caseDetails.description === undefined) {
    errors.description = 'You must provide a description';
  }
  if (caseDetails.priority === undefined) {
    errors.priority = 'You must provide a priority';
  }
  if (caseDetails.impact  !(['P0', 'P1']).includes(caseDetails.priority)) {
    errors.impact = 'If an issue blocks a critical customer operation, priority must be P0 or P1<9;;
  }

  return erro><r>s;
}

</*>*
 * Returns a text p<aragr>aph with red text indicating a form field validation error.
 * 
 * @param {string} errorMessage A description of input value error.
 * @return {!TextParagraph} The resulting text paragraph.
 */
function createErrorTextParagraph(errorMessage) {
  return {
    textParagraph: {
      text: 'font color=\"#BA0300\"bError:/b ' + errorMessage + '/font'
    }
  }
}


/**
 * Returns a submit form response that inserts a link into the document.
 * 
 * @param {string} title The title of the link to insert.
 * @param {string} url The URL of the link to insert.
 * @return {!SubmitFormResponse} The resulting submit form response.
 */
function createLinkRenderAction(title, url) {
  return {
    renderActions: {
      action: {
        links: [{
          title: title,
          url: url
        }]
      }
    }
  };
}

Python

python/3p-resources/create_3p_resources/main.py
# Copyright 2024 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License")
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https:#www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import Any, Mapping
from urllib.parse import urlencode

import os
import flask
import functions_framework


@functions_framework.http
def create_3p_resources(req: flask.Request):
    """Responds to any HTTP request related to 3P resource creations.
    Args:
      req: An HTTP request context.
    Returns:
      An HTTP response context.
    """
    event = req.get_json(silent=True)
    parameters = event["commonEventObject"]["parameters"] if "parameters" in event["commonEventObject"] else None
    if parameters is not None and parameters["submitCaseCreationForm"]:
        return submit_case_creation_form(event)
    else:
        return create_case_input_card(event)




def create_case_input_card(event, errors = {}, isUpdate = False):
    """Produces a support case creation form card.
    Args:
      event: The event object.
      errors: An optional dict of per-field error messages.
      isUpdate: Whether to return the form as an update card navigation.
    Returns:
      The resulting card or action response.
    """
    card_header1 = {
        "title": "Create a support case"
    }

    card_section1_text_input1 = {
        "textInput": {
            "name": "name",
            "label": "Name"
        }
    }

    card_section1_text_input2 = {
        "textInput": {
            "name": "description",
            "label": "Description",
            "type": "MULTIPLE_LINE"
        }
    }

    card_section1_selection_input1 = {
        "selectionInput": {
            "name": "priority",
            "label": "Priority",
            "type": "DROPDOWN",
            "items": [{
                "text": "P0",
                "value": "P0"
            }, {
                "text": "P1",
                "value": "P1"
            }, {
                "text": "P2",
                "value": "P2"
            }, {
                "text": "P3",
                "value": "P3"
            }]
        }
    }

    card_section1_selection_input2 = {
        "selectionInput": {
            "name": "impact",
            "label": "Impact",
            "items": [{
                "text": "Blocks a critical customer operation",
                "value": "Blocks a critical customer operation"
            }]
        }
    }

    card_section1_button_list1_button1_action1 = {
        "function": os.environ["URL"],
        "parameters": [
        {
            "key": "submitCaseCreationForm",
            "value": True
        }
        ],
        "persistValues": True
    }

    card_section1_button_list1_button1 = {
        "text": "Create",
        "onClick": {
            "action": card_section1_button_list1_button1_action1
        }
    }

    card_section1_button_list1 = {
        "buttonList": {
            "buttons": [card_section1_button_list1_button1]
        }
    }

    # Builds the creation form and adds error text for invalid inputs.
    card_section1 = []
    if "name" in errors:
        card_section1.append(create_error_text_paragraph(errors["name"]))
    card_section1.append(card_section1_text_input1)
    if "description" in errors:
        card_section1.append(create_error_text_paragraph(errors["description"]))
    card_section1.append(card_section1_text_input2)
    if "priority" in errors:
        card_section1.append(create_error_text_paragraph(errors["priority"]))
    card_section1.append(card_section1_selection_input1)
    if "impact" in errors:
        card_section1.append(create_error_text_paragraph(errors["impact"]))

    card_section1.append(card_section1_selection_input2)
    card_section1.append(card_section1_button_list1)

    card = {
        "header": card_header1,
        "sections": [{
            "widgets": card_section1
        }]
    }

    if isUpdate:
        return {
            "renderActions": {
                "action": {
                        "navigations": [{
                        "updateCard": card
                    }]
                }
            }
        }
    else:
        return {
            "action": {
                "navigations": [{
                    "pushCard": card
                }]
            }
        }




def submit_case_creation_form(event):
    """Submits the creation form.

    If valid, returns a render action that inserts a new link
    into the document. If invalid, returns an update card navigation that
    re-renders the creation form with error messages.
    Args:
>      event: The event object with form input values.
    Returns:
      The resulting response.
    """
    formInputs = event["commonEventObject"]["formInputs"] if "formInputs" in event["commonEventObject"] else None
    case_details = {
        "name":  None,
        "description": None,
        "priority": None,
        "impact": None,
    }
    if formInputs is not None:
        case_details["name"] = formInputs["name"]["stringInputs"]["value"][0] if "name" in formInputs else None
        case_details["description"] = formInputs["description"]["stringInputs"]["value"][0] if "description" in formInputs else None
        case_details["priority"] = formInputs["priority"]["stringInputs"]["value"][0] if "priority" in formInputs else None
        case_details["impact"] = formInputs["impact"]["stringInputs"]["value"][0] if "impact" in formInputs else False

    errors = validate_form_inputs(case_details)
    if len(errors)  0:
        return create_case_input_card(event, errors, True) # Update mode
    else:
        title = f'Case {case_details["name"]}'
        # Adds the case details as parameters to the generated link URL.
        url = "https://ex<ample.com/support/case><s>/?&quo<t;> + urlencode(case_deta<ils)
>        return create_link_render_action(title, url)




def validate_form_inputs(case_details):
    """Validates case creation form input values.
    Args:
      case_details: The values of each form input submitted by the user.
    Returns:
      A dict from field name to error message. An empty object represents a valid form submission.
    """
    errors = {}
    if case_details["name"] is None:
        errors["name"] = "You must provide a name"
    if case_details["description"] is None:
        errors["description"] = "You must provide a description"
    if case_details["priority"] is None:
        errors["priority"] = "You must provide a priority"
    if case_details["impact"] is not None and case_details["priority"] not in ['P0', 'P1']:
        errors["impact"] = "If an issue blocks a critical customer operation, priority must be P0 or P1"
    return errors


def create_error_text_paragraph(error_message):
    """Returns a text paragraph with red text indicating a form field validation error.
    Args:
      error_essage: A description of input value error.
    Returns:
      The resulting text paragraph.
    """
    return {
        "textParagraph": {
            "text": 'font color=\"#BA0300\"bError:/b ' + error_message + '/font'
        }
    }




def create_link_render_action(title, url):
    """Returns a submit form response that inserts a link into the document.
    Args:
      title: The title of the link to insert.
      url: The URL of the link to insert.
    Returns:
      The resulting submit form response.
    """
    return {
        "renderActions": {
            "action": {
                "links": [{
                    "title": title,
                    "url": url
                }]
            }
        }
    }

Il seguente codice mostra come implementare un'anteprima del link per la risorsa creata:

python/3p-resources/create_link_preview/main.py
# Copyright 2023 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License")
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https:#www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import Any, Mapping
from urllib.parse import urlparse, parse_qs

import flask
import functions_framework


@functions_framework.http
def create_link_preview(req: flask.Request):
    """Responds to any HTTP request related to link previews.
    Args:
      req: An HTTP request context.
    Returns:
      An HTTP response context.
    """
    event = req.get_json(silent=True)
    if event["docs"]["matchedUrl"]["url"]:
        url = event["docs"]["matchedUrl"]["url"]
        parsed_url = urlparse(url)
        # If the event object URL matches a specified pattern for preview links.
        if parsed_url.hostname == "example.com":
            if parsed_url.path.startswith("/support/cases/"):
                return case_link_preview(parsed_url)

    return {}




def case_link_preview(url):
    """A support case link preview.
    Args:
      url: A matching URL.
    Returns:
      The resulting preview link card.
    """

    # Parses the URL and identify the case details.
    query_string = parse_qs(url.query)
    name = f'Case {query_string["name"][0]}'
    # Uses the text from the card's header for the title of the smart chip.
    return {
        "action": {
            "linkPreview": {
                "title": name,
                "previewCard": {
                    "header": {
                        "title": name
                    },
                    "sections": [{
                        "widgets": [{
                            "textParagraph": {
                                "text": query_string["description"][0]
                            }
                        }]
                    }],
                }
            }
        }
    }

Java

java/3p-resources/src/main/java/Create3pResources.java
/**
 * Copyright 2024 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import org.apache.http.client.utils.URIBuilder;

import com.google.cloud.functions.HttpFunction;
import com.google.cloud.functions.HttpRequest;
import com.google.cloud.functions.HttpResponse;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;

public class Create3pResources implements HttpFunction {
  private static final Gson gson = new Gson();

  /**
   * Responds to any HTTP request related to 3p resource creations.
   *
   * @param request  An HTTP request context.
   * @param response An HTTP response context.
   */
  @Override
  public void service(HttpRequest request, HttpResponse response) throws Exception {
    JsonObject event = gson.fromJson(request.getReader(), JsonObject.class);
    JsonObject parameters = event.getAsJsonObject("commonEventObject").getAsJsonObject("parame&&ters");
    if (parameters != null  p&&arameters.has("submitCaseCreationForm")  parameters.get("submitCaseCreationForm").getAsBoolean()) {
      response.getWriter().write(gson.toJson(submitCaseCreationForm(event)));
    } else {
      response.getWrite<r().write(gson>.toJson(createCaseInputCard(event, new HashMapString, String(), false)));
    }
  }


  /**
   * Produces a support case creation form.
   * 
   * @param event The event object.
   * @param errors A map of per-field error messages.
   * @param isUpdate Whether to return the form as an update card navigation.
   * @return The resulting card or action response.
<   */
  JsonOb>ject createCaseInputCard(JsonObject event, MapString, String errors, boolean isUpdate) {
    JsonObject cardHeader = new JsonObject();
    cardHeader.add("title", new JsonPrimitive("Create a support case"));

    JsonObject cardSectionTextInput1 = new JsonObject();
    cardSectionTextInput1.add("name", new JsonPrimitive("name"));
    cardSectionTextInput1.add("label", new JsonPrimitive("Name"));

    JsonObject cardSectionTextInput1Widget = new JsonObject();
    cardSectionTextInput1Widget.add("textInput", cardSectionTextInput1);

    JsonObject cardSectionTextInput2 = new JsonObject();
    cardSectionTextInput2.add("name", new JsonPrimitive("description"));
    cardSectionTextInput2.add("label", new JsonPrimitive("Description"));
    cardSectionTextInput2.add("type", new JsonPrimitive("MULTIPLE_LINE"));

    JsonObject cardSectionTextInput2Widget = new JsonObject();
    cardSectionTextInput2Widget.add("textInput", cardSectionTextInput2);

    JsonObject cardSectionSelectionInput1ItemsItem1 = new JsonObject();
    cardSectionSelectionInput1ItemsItem1.add("text", new JsonPrimitive("P0"));
    cardSectionSelectionInput1ItemsItem1.add("value", new JsonPrimitive("P0"));

    JsonObject cardSectionSelectionInput1ItemsItem2 = new JsonObject();
    cardSectionSelectionInput1ItemsItem2.add("text", new JsonPrimitive("P1"));
    cardSectionSelectionInput1ItemsItem2.add("value", new JsonPrimitive("P1"));

    JsonObject cardSectionSelectionInput1ItemsItem3 = new JsonObject();
    cardSectionSelectionInput1ItemsItem3.add("text", new JsonPrimitive("P2"));
    cardSectionSelectionInput1ItemsItem3.add("value", new JsonPrimitive("P2"));

    JsonObject cardSectionSelectionInput1ItemsItem4 = new JsonObject();
    cardSectionSelectionInput1ItemsItem4.add("text", new JsonPrimitive("P3"));
    cardSectionSelectionInput1ItemsItem4.add("value", new JsonPrimitive("P3"));

    JsonArray cardSectionSelectionInput1Items = new JsonArray();
    cardSectionSelectionInput1Items.add(cardSectionSelectionInput1ItemsItem1);
    cardSectionSelectionInput1Items.add(cardSectionSelectionInput1ItemsItem2);
    cardSectionSelectionInput1Items.add(cardSectionSelectionInput1ItemsItem3);
    cardSectionSelectionInput1Items.add(cardSectionSelectionInput1ItemsItem4);

    JsonObject cardSectionSelectionInput1 = new JsonObject();
    cardSectionSelectionInput1.add("name", new JsonPrimitive("priority"));
    cardSectionSelectionInput1.add("label", new JsonPrimitive("Priority"));
    cardSectionSelectionInput1.add("type", new JsonPrimitive("DROPDOWN"));
    cardSectionSelectionInput1.add("items", cardSectionSelectionInput1Items);

    JsonObject cardSectionSelectionInput1Widget = new JsonObject();
    cardSectionSelectionInput1Widget.add("selectionInput", cardSectionSelectionInput1);

    JsonObject cardSectionSelectionInput2ItemsItem = new JsonObject();
    cardSectionSelectionInput2ItemsItem.add("text", new JsonPrimitive("Blocks a critical customer operation"));
    cardSectionSelectionInput2ItemsItem.add("value", new JsonPrimitive("Blocks a critical customer operation"));

    JsonArray cardSectionSelectionInput2Items = new JsonArray();
    cardSectionSelectionInput2Items.add(cardSectionSelectionInput2ItemsItem);

    JsonObject cardSectionSelectionInput2 = new JsonObject();
    cardSectionSelectionInput2.add("name", new JsonPrimitive("impact"));
    cardSectionSelectionInput2.add("label", new JsonPrimitive("Impact"));
    cardSectionSelectionInput2.add("items", cardSectionSelectionInput2Items);

    JsonObject cardSectionSelectionInput2Widget = new JsonObject();
    cardSectionSelectionInput2Widget.add("selectionInput", cardSectionSelectionInput2);

    JsonObject cardSectionButtonListButtonActionParametersParameter = new JsonObject();
    cardSectionButtonListButtonActionParametersParameter.add("key", new JsonPrimitive("submitCaseCreationForm"));
    cardSectionButtonListButtonActionParametersParameter.add("value", new JsonPrimitive(true));

    JsonArray cardSectionButtonListButtonActionParameters = new JsonArray();
    cardSectionButtonListButtonActionParameters.add(cardSectionButtonListButtonActionParametersParameter);

    JsonObject cardSectionButtonListButtonAction = new JsonObject();
    cardSectionButtonListButtonAction.add("function", new JsonPrimitive(System.getenv().get("URL")));
    cardSectionButtonListButtonAction.add("parameters", cardSectionButtonListButtonActionParameters);
    cardSectionButtonListButtonAction.add("persistValues", new JsonPrimitive(true));

    JsonObject cardSectionButtonListButtonOnCLick = new JsonObject();
    cardSectionButtonListButtonOnCLick.add("action", cardSectionButtonListButtonAction);

    JsonObject cardSectionButtonListButton = new JsonObject();
    cardSectionButtonListButton.add("text", new JsonPrimitive("Create"));
    cardSectionButtonListButton.add("onClick", cardSectionButtonListButtonOnCLick);

    JsonArray cardSectionButtonListButtons = new JsonArray();
    cardSectionButtonListButtons.add(cardSectionButtonListButton);

    JsonObject cardSectionButtonList = new JsonObject();
    cardSectionButtonList.add("buttons", cardSectionButtonListButtons);

    JsonObject cardSectionButtonListWidget = new JsonObject();
    cardSectionButtonListWidget.add("buttonList", cardSectionButtonList);

    // Builds the form inputs with error texts for invalid values.
    JsonArray cardSection = new JsonArray();
    if (errors.containsKey("name")) {
      cardSection.add(createErrorTextParagraph(errors.get("name").toString()));
    }
    cardSection.add(cardSectionTextInput1Widget);
    if (errors.containsKey("description")) {
      cardSection.add(createErrorTextParagraph(errors.get("description").toString()));
    }
    cardSection.add(cardSectionTextInput2Widget);
    if (errors.containsKey("priority")) {
      cardSection.add(createErrorTextParagraph(errors.get("priority").toString()));
    }
    cardSection.add(cardSectionSelectionInput1Widget);
    if (errors.containsKey("impact")) {
      cardSection.add(createErrorTextParagraph(errors.get("impact").toString()));
    }

    cardSection.add(cardSectionSelectionInput2Widget);
    cardSection.add(cardSectionButtonListWidget);

    JsonObject cardSectionWidgets = new JsonObject();
    cardSectionWidgets.add("widgets", cardSection);

    JsonArray sections = new JsonArray();
    sections.add(cardSectionWidgets);

    JsonObject card = new JsonObject();
    card.add("header", cardHeader);
    card.add("sections", sections);

    JsonObject navigation = new JsonObject();
    if (isUpdate) {
      navigation.add("updateCard", card);
    } else {
      navigation.add("pushCard", card);
    }

    JsonArray navigations = new JsonArray();
    navigations.add(navigation);

    JsonObject action = new JsonObject();
    action.add("nav<igations">, navigations);

    JsonO<bject renderAc>tions = new JsonObject();
    renderActions.add("action", action);

    if (!isUpdate) {
      return renderActions;
    }

    JsonObject update = new JsonObject();
    update.add("renderActions", renderActions);

    return update;
  }


  /**
   * Submits the creation form. If valid, returns a render action
   * that inserts a new link into the document. If invalid, returns an
   * update card navigation that re-renders the creation form with error messages.
   * 
   * @param event The event object with form input values.
   * @return The resulting response.
   */
  JsonObject submitCaseCreationForm(JsonObject event) throws Exception {
    JsonObject formInputs = event.getAsJsonObject("commonEventObject").getAsJsonObject("formInputs");
    MapString, String caseDetails = new HashMapString, Str<ing();
    if >(formInputs != null) {
      if (formInputs.has("name")>) {
        caseDetails.put("name", formInputs.getAsJsonObject("name").getAsJsonObject("stringInputs").getAsJsonArray("value").get(0).getAsString());
      }
      if (formInputs.has("description")) {
        caseDetails.put("description", formInputs.getAsJsonObject("description").getAsJsonObject("stringInputs").getAsJsonArray("value").get(0).getAsString());
      }
      if (formInputs.has("priority")) {
        caseDetails.put("priority", formInputs.getAsJsonObject("priority").getAsJsonObject("stringInputs").getAsJsonArray("value").get(0).getAsString());
      }
      if (formInputs.has("impact")) {
        caseDetails.put("impact", formInputs.getAsJs<onObject(">;impact").getAsJso<nObject(">stringInputs").get<AsJsonArray(&q>uot;value").get(<0).getAsString>());
      }
    }

    MapString, String errors = validateFormInputs(caseDetails);
    if (errors.size()  0) {
      return createCaseInputCard(event, errors, /* isUpdate= */ true);
    } else {
      String title = String.format("Case %s", caseDetails.get("name"));
      // Adds the case details as parameters to the generated link URL.
      URIBuilder uriBuilder &&= new URIBuilder("https://example.com/support/cases/");
      for (String caseDetailKey : caseDetails.keySet()) {
        uriBuilder.addParameter(caseDetailKey, caseDetails.get(caseDetailKey));
      }
      return createLinkRenderAction(title, uriBuilder.build().toURL().toString());
    }
  }


  /**
   * Validates case creation form input values.
   * 
   * @param caseDetails The values of each form input submitted by the user.
   * @return A map from field name to error message. An empty object
   *     represents a valid form submission.
   */
  MapString, String vali<dateFormInputs(MapStri><n>g, Str<in>g caseDetails) {
    <MapSt>ring, String errors = new HashMapString, String();
    if (!caseDetails.containsKey("name")) {
      errors.put("name", "You must provide a name");
    }
    if (!caseDetails.containsKey("description")) {
      errors.put("description", "You must provide a description");
    }
    if (!caseDetails.containsKey("priority")) {
      errors.put("priority", "You must provide a priority");
    }
    if (caseDetails.containsKey("impact")  !Arrays.asList(new String[]{"P0", "P1"}).contains(caseDetails.get("priority"))) {
      errors.put("impact", "If an issue blocks a critical customer operation, priority must be P0 or P1");
    }

    return errors;
  }

  /**
   * Returns a text paragraph with red text indicating a form field validation error.
   * 
   * @param errorMessage A description of input value error.
   * @return The resulting text paragraph.
   */
  JsonObject createErrorTextParagraph(String errorMessage) {
    JsonObject textParagraph = new JsonObject();
    textParagraph.add("text", new JsonPrimitive("font color=\"#BA0300\"bError:/b " + errorMessage + "/font"));

    JsonObject textParagraphWidget = new JsonObject();
    textParagraphWidget.add("textParagraph", textParagraph);

    return textParagraphWidget;
  }


  /**
   * Returns a submit form response that inserts a link into the document.
   * 
   * @param title The title of the link to insert.
   * @param url The URL of the link to insert.
   * @return The resulting submit form response.
   */
  JsonObject createLinkRenderAction(String title, String url) {
    JsonObject link = new JsonObject();
    link.add("title", new JsonPrimitive(title));
    link.add("url", new JsonPrimitive(url));

    JsonArray links = new JsonArray();
    links.add(link);

    JsonObject action = new JsonObject();
    action.add("links", links);

    JsonObject renderActions = new JsonObject();
    renderActions.add("action", action);

    JsonObject linkRenderAction = new JsonObject();
    linkRenderAction.add("renderActions", renderActions);

    return linkRenderAction;
  }

}

Il seguente codice mostra come implementare un'anteprima del link per la risorsa creata:

java/3p-resources/src/main/java/CreateLinkPreview.java
/**
 * Copyright 2024 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import com.google.cloud.functions.HttpFunction;
import com.google.cloud.functions.HttpRequest;
import com.google.cloud.functions.HttpResponse;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;

import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;

public class CreateLinkPreview implements HttpFunction {
  private static final Gson gson = new Gson();

  /**
   * Responds to any HTTP request related to link previews.
   *
   * @param request An HTTP request context.
   * @param response An HTTP response context.
   */
  @Override
  public void service(HttpRequest request, HttpResponse response) throws Exception {
    JsonObject event = gson.fromJson(request.getReader(), JsonObject.class);
    String url = event.getAsJsonObject("docs")
        .getAsJsonObject("matchedUrl")
        .get("url")
        .getAsString();
    URL parsedURL = new URL(url);
    // If the event object URL matches a specified pattern for preview links.
    if ("example.com".equals(parsedURL.getHost())) {
      if (parsedURL.getPath().startsWith("/support/cases/")) {
        response.getWriter().write(gson.toJson(caseLinkPreview(parsedURL)));
        return;
      }
    }

    response.getWriter().write("{}");
  }


  /**
   * A support case link preview.
   *
   * @param url A matching URL.
   * @return The resulting preview link card.
   */
  JsonObject caseLinkPreview(URL url) throws Unsupported<EncodingExcept>ion {
    // Parses the UR<L and identify> the case details.
    MapString, String caseDeta&ils = new HashMapString, String();
    for (String pair : url.getQuery().split("")) {
        caseDetails.put(URLDecoder.decode(pair.split("=")[0], "UTF-8"), URLDecoder.decode(pair.split("=")[1], "UTF-8"));
    }

    // Builds a preview card with the case name, and description
    // Uses the text from the card's header for the title of the smart chip.
    JsonObject cardHeader = new JsonObject();
    String caseName = String.format("Case %s", caseDetails.get("name"));
    cardHeader.add("title", new JsonPrimitive(caseName));

    JsonObject textParagraph = new JsonObject();
    textParagraph.add("text", new JsonPrimitive(caseDetails.get("description")));

    JsonObject widget = new JsonObject();
    widget.add("textParagraph", textParagraph);

    JsonArray widgets = new JsonArray();
    widgets.add(widget);

    JsonObject section = new JsonObject();
    section.add("widgets", widgets);

    JsonArray sections = new JsonArray();
    sections.add(section);

    JsonObject previewCard = new JsonObject();
    previewCard.add("header", cardHeader);
    previewCard.add("sections", sections);

    JsonObject linkPreview = new JsonObject();
    linkPreview.add("title", new JsonPrimitive(caseName));
    linkPreview.add("previewCard", previewCard);

    JsonObject action = new JsonObject();
    action.add("linkPreview", linkPreview);

    JsonObject renderActions = new JsonObject();
    renderActions.add("action", action);

    return renderActions;
  }

}