สร้างทรัพยากรของบุคคลที่สามจากเมนู @

หน้านี้อธิบายวิธีสร้างส่วนเสริม Google Workspace ที่ช่วยให้ผู้ใช้ Google เอกสารสร้างทรัพยากรต่างๆ ในบริการของบุคคลที่สามจากใน Google เอกสารได้ เช่น เคสขอรับความช่วยเหลือหรืองานในโปรเจ็กต์

เมื่อใช้ส่วนเสริมของ Google Workspace คุณจะเพิ่มบริการลงในเมนู @ ในเอกสารได้ ส่วนเสริมจะเพิ่มรายการเมนูที่ช่วยให้ผู้ใช้สร้างทรัพยากรในบริการของคุณผ่านกล่องโต้ตอบของแบบฟอร์มในเอกสารได้

วิธีที่ผู้ใช้สร้างทรัพยากร

หากต้องการสร้างทรัพยากรในบริการจากภายในเอกสารใน Google เอกสาร ผู้ใช้พิมพ์ @ ในเอกสาร และเลือกบริการจากเมนู @ ดังนี้

ผู้ใช้ดูตัวอย่างการ์ด

เมื่อผู้ใช้พิมพ์ @ ในเอกสารและเลือกบริการของคุณ คุณจะแสดงแก่ผู้ใช้ผ่านการ์ดที่มีอินพุตแบบฟอร์มที่ผู้ใช้ต้องใช้ในการสร้างทรัพยากร หลังจากที่ผู้ใช้ส่งแบบฟอร์มการสร้างทรัพยากรแล้ว ส่วนเสริมควรสร้างทรัพยากรในบริการและสร้าง URL ที่ชี้ไปยังทรัพยากรดังกล่าว

ส่วนเสริมจะแทรกชิปลงในเอกสารสำหรับทรัพยากรที่สร้างขึ้น เมื่อผู้ใช้กดตัวชี้เหนือชิปนี้ ระบบจะเรียกใช้ทริกเกอร์การแสดงตัวอย่างลิงก์ที่เกี่ยวข้องของส่วนเสริม ตรวจสอบว่าส่วนเสริมแทรกชิปที่มีรูปแบบลิงก์ที่ทริกเกอร์ตัวอย่างลิงก์รองรับ

ข้อกำหนดเบื้องต้น

Apps Script

  • ส่วนเสริมของ Google Workspace ที่รองรับตัวอย่างลิงก์สำหรับรูปแบบลิงก์สำหรับทรัพยากรที่ผู้ใช้สร้าง หากต้องการสร้างส่วนเสริมที่มีการแสดงตัวอย่างลิงก์ โปรดดูแสดงตัวอย่างลิงก์ที่มีชิปอัจฉริยะ

Node.js

  • ส่วนเสริมของ Google Workspace ที่รองรับตัวอย่างลิงก์สำหรับรูปแบบลิงก์สำหรับทรัพยากรที่ผู้ใช้สร้าง หากต้องการสร้างส่วนเสริมที่มีการแสดงตัวอย่างลิงก์ โปรดดูแสดงตัวอย่างลิงก์ที่มีชิปอัจฉริยะ

Python

  • ส่วนเสริมของ Google Workspace ที่รองรับตัวอย่างลิงก์สำหรับรูปแบบลิงก์สำหรับทรัพยากรที่ผู้ใช้สร้าง หากต้องการสร้างส่วนเสริมที่มีการแสดงตัวอย่างลิงก์ โปรดดูแสดงตัวอย่างลิงก์ที่มีชิปอัจฉริยะ

Java

  • ส่วนเสริมของ Google Workspace ที่รองรับตัวอย่างลิงก์สำหรับรูปแบบลิงก์สำหรับทรัพยากรที่ผู้ใช้สร้าง หากต้องการสร้างส่วนเสริมที่มีการแสดงตัวอย่างลิงก์ โปรดดูแสดงตัวอย่างลิงก์ที่มีชิปอัจฉริยะ

ตั้งค่าการสร้างทรัพยากรสำหรับส่วนเสริม

หัวข้อนี้จะอธิบายวิธีการตั้งค่าการสร้างทรัพยากรสำหรับส่วนเสริม ซึ่งรวมถึงขั้นตอนต่อไปนี้

  1. กำหนดค่าการสร้างทรัพยากรในทรัพยากรการทำให้ใช้งานได้หรือไฟล์ Manifest ของส่วนเสริม
  2. สร้างการ์ดแบบฟอร์มที่ผู้ใช้ต้องใช้เพื่อสร้างทรัพยากรภายในบริการ
  3. จัดการการส่งแบบฟอร์มเพื่อให้ฟังก์ชันที่สร้างทรัพยากรทำงานเมื่อผู้ใช้ส่งแบบฟอร์ม

กำหนดค่าการสร้างทรัพยากร

ในการกำหนดค่าการสร้างทรัพยากร ให้ระบุส่วนและช่องต่อไปนี้ในทรัพยากรการทำให้ใช้งานได้หรือไฟล์ Manifest ของส่วนเสริม

  1. ในส่วน addOns ในช่อง docs ให้ใช้ทริกเกอร์ createActionTriggers ที่มี runFunction (คุณจะกำหนดฟังก์ชันนี้ในส่วนสร้างการ์ดแบบฟอร์มต่อไปนี้)

    หากต้องการดูข้อมูลเกี่ยวกับช่องที่คุณระบุในทริกเกอร์ createActionTriggers ได้ โปรดดูเอกสารอ้างอิงสำหรับไฟล์ Manifest ของ Apps Script หรือทรัพยากรการติดตั้งใช้งานสำหรับรันไทม์อื่นๆ

  2. ในช่อง oauthScopes ให้เพิ่มขอบเขต https://www.googleapis.com/auth/workspace.linkcreate เพื่อให้ผู้ใช้ให้สิทธิ์ส่วนเสริมในการสร้างทรัพยากรได้ กล่าวอย่างเจาะจงก็คือ ขอบเขตนี้ช่วยให้ส่วนเสริมอ่านข้อมูลที่ผู้ใช้ส่งไปยังแบบฟอร์มการสร้างทรัพยากรและแทรกชิปอัจฉริยะลงในเอกสารโดยอิงตามข้อมูลนั้นได้

โปรดดูตัวอย่างในส่วน addons ของทรัพยากรการทำให้ใช้งานได้ที่กำหนดค่าการสร้างทรัพยากรสำหรับบริการเคสขอรับความช่วยเหลือต่อไปนี้

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

ในตัวอย่างนี้ ส่วนเสริม Google Workspace ช่วยให้ผู้ใช้สร้างเคสขอรับความช่วยเหลือได้ ทริกเกอร์ createActionTriggers แต่ละรายการต้องมีช่องต่อไปนี้

  • รหัสที่ไม่ซ้ำกัน
  • ป้ายกำกับข้อความที่ปรากฏในเมนู @ ของเอกสาร
  • URL โลโก้ที่ชี้ไปยังไอคอน ซึ่งปรากฏถัดจากข้อความป้ายกำกับในเมนู @
  • ฟังก์ชันเรียกกลับที่อ้างอิงฟังก์ชัน Apps Script หรือปลายทาง HTTP ที่แสดงผลการ์ด

สร้างการ์ดแบบฟอร์ม

หากต้องการสร้างทรัพยากรในบริการจากเมนู @ เอกสาร คุณต้องใช้ฟังก์ชันใดๆ ที่ระบุไว้ในออบเจ็กต์ createActionTriggers

เมื่อผู้ใช้โต้ตอบกับรายการในเมนู ทริกเกอร์ createActionTriggers ที่เกี่ยวข้องจะเริ่มทำงานและฟังก์ชันเรียกกลับจะแสดงการ์ดที่มีอินพุตแบบฟอร์มสำหรับการสร้างทรัพยากร

องค์ประกอบและการดำเนินการที่รองรับ

ในการสร้างอินเทอร์เฟซของการ์ด คุณสามารถใช้วิดเจ็ตเพื่อแสดงข้อมูลและอินพุตที่ผู้ใช้ต้องการสำหรับสร้างทรัพยากร วิดเจ็ตและการดำเนินการของส่วนเสริม Google Workspace ส่วนใหญ่จะใช้งานได้โดยมีข้อยกเว้นต่อไปนี้

  • ไม่รองรับส่วนท้ายของการ์ด
  • ระบบไม่รองรับการแจ้งเตือน
  • สําหรับการนําทางจะรองรับเฉพาะการนําทางใน updateCard เท่านั้น

ตัวอย่างการ์ดที่มีการป้อนข้อมูลแบบฟอร์ม

ตัวอย่างต่อไปนี้แสดงฟังก์ชันการเรียกกลับของ Apps Script ที่แสดงการ์ดเมื่อผู้ใช้เลือกสร้างเคสขอรับความช่วยเหลือจากเมนู @

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;
}

ฟังก์ชัน createCaseInputCard จะแสดงผลการ์ดต่อไปนี้

การ์ดที่มีอินพุตแบบฟอร์ม

การ์ดนี้ประกอบด้วยการป้อนข้อความ เมนูแบบเลื่อนลง และช่องทำเครื่องหมาย นอกจากนี้ยังมีปุ่มข้อความที่มีการดำเนินการ onClick ซึ่งเรียกใช้ฟังก์ชันอื่นเพื่อจัดการการส่งแบบฟอร์มการสร้างด้วย

หลังจากที่ผู้ใช้กรอกแบบฟอร์มและคลิกสร้าง ส่วนเสริมจะส่งอินพุตแบบฟอร์มไปยังฟังก์ชันonClick ที่เรียกว่า submitCaseCreationForm ในตัวอย่าง ซึ่งส่วนเสริมจะตรวจสอบอินพุตและใช้ในการสร้างทรัพยากรในบริการของบุคคลที่สามได้

จัดการการส่งแบบฟอร์ม

หลังจากที่ผู้ใช้ส่งแบบฟอร์มการสร้างแล้ว ฟังก์ชันที่เชื่อมโยงกับการดำเนินการ onClick จะทำงาน เพื่อให้ผู้ใช้ได้รับประสบการณ์ที่ดีที่สุด ส่วนเสริมควรจัดการกับทั้งการส่งแบบฟอร์มที่ประสบความสำเร็จและผิดพลาด

จัดการการสร้างทรัพยากรที่สำเร็จ

ฟังก์ชัน onClick ของส่วนเสริมควรสร้างทรัพยากรในบริการของบุคคลที่สาม และสร้าง URL ที่ชี้ไปยังทรัพยากรดังกล่าว

หากต้องการสื่อสาร URL ของทรัพยากรกลับไปยังเอกสารเพื่อสร้างชิป ฟังก์ชัน onClick ควรแสดงผล SubmitFormResponse ที่มีอาร์เรย์แบบหนึ่งองค์ประกอบใน renderActions.action.links ที่ชี้ไปยังลิงก์ ชื่อลิงก์ควรแสดงถึงชื่อของทรัพยากรที่สร้างขึ้น และ URL ควรชี้ไปยังทรัพยากรดังกล่าว

ตัวอย่างต่อไปนี้แสดง SubmitFormResponse สำหรับทรัพยากรที่สร้างขึ้น

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;
}

หลังจากแสดงผล SubmitFormResponse แล้ว กล่องโต้ตอบโมดัลจะปิดลงและส่วนเสริมจะแทรกชิปลงในเอกสาร เมื่อผู้ใช้กดตัวชี้เหนือชิปนี้ ระบบจะเรียกใช้ทริกเกอร์ตัวอย่างลิงก์ที่เกี่ยวข้อง ตรวจสอบว่าส่วนเสริมไม่ได้แทรกชิปที่มีรูปแบบลิงก์ที่ทริกเกอร์การแสดงตัวอย่างลิงก์ไม่รองรับ

จัดการข้อผิดพลาด

หากผู้ใช้พยายามส่งแบบฟอร์มที่มีช่องไม่ถูกต้อง แทนที่จะแสดงผล SubmitFormResponse พร้อมลิงก์ ส่วนเสริมควรแสดงผลการดำเนินการที่แสดงข้อผิดพลาดโดยใช้การไปยังส่วนต่างๆ ของ updateCard ซึ่งจะช่วยให้ผู้ใช้เห็นสิ่งที่ผิด แล้วลองอีกครั้ง ดู updateCard(card) สำหรับ Apps Script และ updateCard สำหรับรันไทม์อื่นๆ ระบบไม่รองรับการแจ้งเตือนและการนำทาง pushCard

ตัวอย่างการจัดการข้อผิดพลาด

ตัวอย่างต่อไปนี้แสดงโค้ดที่เรียกใช้เมื่อผู้ใช้ส่งแบบฟอร์ม หากข้อมูลที่ป้อนไม่ถูกต้อง การ์ดจะอัปเดตและแสดงข้อความแสดงข้อผิดพลาด หากอินพุตถูกต้อง ส่วนเสริมจะแสดงผล SubmitFormResponse พร้อมลิงก์ไปยังทรัพยากรที่สร้างขึ้น

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("formInputs");
  Map<String, String> caseDetails = new HashMap<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) {
    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());
  }
}

ตัวอย่างโค้ดต่อไปนี้จะตรวจสอบความถูกต้องของอินพุตแบบฟอร์มและสร้างข้อความแสดงข้อผิดพลาดสำหรับอินพุตที่ไม่ถูกต้อง

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 && 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 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 CardService.newTextParagraph()
    .setText('<font color=\"#BA0300\"><b>Error:</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 {
    textParagraph: {
      text: '<font color=\"#BA0300\"><b>Error:</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_essage: A description of input value error.
    Returns:
      The resulting text paragraph.
    """
    return {
        "textParagraph": {
            "text": '<font color=\"#BA0300\"><b>Error:</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", "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\"><b>Error:</b> " + errorMessage + "</font>"));

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

  return textParagraphWidget;
}

ตัวอย่างทั้งหมด: ส่วนเสริมเคสขอรับความช่วยเหลือ

ตัวอย่างต่อไปนี้แสดงส่วนเสริม Google Workspace ที่แสดงตัวอย่างลิงก์ไปยังเคสขอรับความช่วยเหลือของบริษัท และอนุญาตให้ผู้ใช้สร้างเคสขอรับความช่วยเหลือจากภายใน Google เอกสาร

ตัวอย่างจะดำเนินการดังนี้

  • สร้างการ์ดที่มีช่องของแบบฟอร์มเพื่อสร้างเคสขอรับความช่วยเหลือจากเมนูเอกสาร @
  • ตรวจสอบความถูกต้องของอินพุตแบบฟอร์มและแสดงผลข้อความแสดงข้อผิดพลาดสําหรับอินพุตที่ไม่ถูกต้อง
  • แทรกชื่อเคสขอรับความช่วยเหลือที่สร้างขึ้นและลิงก์เป็นชิปอัจฉริยะในเอกสารของเอกสาร
  • แสดงตัวอย่างลิงก์ไปยังเคสขอรับความช่วยเหลือ เช่น https://www.example.com/support/cases/1234 ชิปอัจฉริยะแสดงไอคอน และการ์ดตัวอย่างจะมีชื่อเคส ลำดับความสำคัญ และคำอธิบาย

ทรัพยากรการทำให้ใช้งานได้

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

รหัส

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("?")[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 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 && 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 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 CardService.newTextParagraph()
    .setText('<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.
 */
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.createLinkPreview = (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 response 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?.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);
  }
}


/**
 * 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 {
    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.
 */
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://example.com/support/cases/?" + urlencode(case_details)
        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\"><b>Error:</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
                }]
            }
        }
    }

โค้ดต่อไปนี้แสดงวิธีใช้งานตัวอย่างลิงก์สำหรับทรัพยากรที่สร้างขึ้น

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("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, new HashMap<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.
   */
  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;
  }


  /**
   * 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");
    Map<String, String> caseDetails = new HashMap<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) {
      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.
   */
  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", "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\"><b>Error:</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;
  }

}

โค้ดต่อไปนี้แสดงวิธีใช้งานตัวอย่างลิงก์สำหรับทรัพยากรที่สร้างขึ้น

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 UnsupportedEncodingException {
    // Parses the URL and identify the case details.
    Map<String, String> caseDetails = new HashMap<String, 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;
  }

}