유니버설 액션

범용 작업은 사용자가 선택 시 새 웹페이지를 열거나 새 UI 카드를 표시하거나 특정 Apps Script 함수를 실행할 수 있는 메뉴 항목 요소입니다. 작동 방식은 카드 작업과 매우 유사하지만, 유니버설 작업은 현재 부가기능 컨텍스트와 관계없이 항상 부가기능의 모든 카드에 배치된다는 점이 다릅니다.

범용 작업을 사용하면 사용자가 부가기능의 어느 부분과 상호작용하는지에 관계없이 항상 특정 기능에 액세스할 수 있도록 할 수 있습니다. 다음은 범용 작업의 몇 가지 사용 사례입니다.

  • 설정 웹페이지를 열거나 설정 카드를 표시합니다.
  • 사용자에게 도움말 정보를 표시합니다.
  • '새 고객 추가'와 같은 새 워크플로를 시작합니다.
  • 사용자가 부가기능에 관한 의견을 보낼 수 있는 카드를 표시합니다.

현재 컨텍스트에 종속되지 않는 작업이 있을 때마다 이를 범용 작업으로 만드는 것이 좋습니다.

범용 작업 사용

범용 작업은 부가기능의 프로젝트 매니페스트에 구성됩니다. 범용 작업을 구성하면 부가기능 사용자는 항상 이 작업을 사용할 수 있습니다. 사용자가 카드를 보고 있는 경우 정의한 범용 작업 세트는 항상 카드 메뉴에 해당 카드에 정의한 카드 작업 뒤에 표시됩니다. 범용 작업은 부가기능의 매니페스트에 정의된 순서와 동일한 순서로 카드 메뉴에 표시됩니다.

범용 작업 구성

부가기능의 매니페스트에서 범용 작업을 구성합니다. 자세한 내용은 매니페스트를 참고하세요.

각 작업에 대해 해당 작업의 메뉴에 표시할 텍스트를 지정합니다. 그런 다음 작업이 새 탭에서 웹페이지를 직접 열어야 함을 나타내는 openLink 필드를 지정할 수 있습니다. 또는 범용 작업이 선택될 때 실행할 Apps Script 콜백 함수를 지정하는 runFunction 필드를 지정할 수 있습니다.

runFunction가 사용되면 지정된 콜백 함수는 일반적으로 다음 중 하나를 실행합니다.

  • 빌드된 UniversalActionResponse 객체를 반환하여 즉시 표시할 UI 카드를 빌드합니다.
  • 빌드된 UniversalActionResponse 객체를 반환하여 다른 작업을 실행한 후 URL을 엽니다.
  • 새 카드로 전환하거나 URL을 열지 않는 백그라운드 작업을 실행합니다. 이 경우 콜백 함수는 아무것도 반환하지 않습니다.

호출 시 콜백 함수에는 열린 카드 및 부가기능 컨텍스트에 관한 정보가 포함된 이벤트 객체가 전달됩니다.

다음 코드 스니펫은 Gmail을 확장하는 동안 범용 작업을 사용하는 Google Workspace 부가기능의 매니페스트 발췌 부분 예시를 보여줍니다. 이 코드는 부가기능에서 열린 메시지를 보낸 사람을 확인할 수 있도록 메타데이터 범위를 명시적으로 설정합니다.

  "oauthScopes": [
    "https://www.googleapis.com/auth/gmail.addons.current.message.metadata"
  ],
  "addOns": {
    "common": {
      "name": "Universal Actions Only Addon",
      "logoUrl": "https://www.example.com/hosted/images/2x/my-icon.png",
      "openLinkUrlPrefixes": [
        "https://www.google.com",
        "https://www.example.com/urlbase"
      ],
      "universalActions": [{
          "label": "Open google.com",
          "openLink": "https://www.google.com"
        }, {
          "label": "Open contact URL",
          "runFunction": "openContactURL"
        }, {
          "label": "Open settings",
          "runFunction": "createSettingsResponse"
        }, {
          "label": "Run background sync",
          "runFunction": "runBackgroundSync"
      }],
      ...
    },
    "gmail": {
      "contextualTriggers": [
        {
          "unconditional": {},
          "onTriggerFunction": "getContextualAddOn"
        }
      ]
    },
    ...
  },
  ...

위 예에서 정의된 세 가지 범용 작업은 다음을 실행합니다.

  • google.com 열기를 선택하면 새 탭에서 https://www.google.com이 열립니다.
  • 연락처 URL 열기는 열려야 하는 URL을 결정하는 함수를 실행한 다음 OpenLink 객체를 사용하여 새 탭에서 URL을 엽니다. 코드는 발신자의 이메일 주소를 사용하여 URL을 빌드합니다.
  • 설정 열기는 부가기능 스크립트 프로젝트에 정의된 createSettingsCards() 함수를 실행합니다. 이 함수는 부가기능 설정 및 기타 정보가 포함된 카드 세트를 포함하는 유효한 UniversalActionResponse 객체를 반환합니다. 함수가 이 객체의 빌드를 완료하면 UI에 카드 목록이 표시됩니다 (여러 카드 반환 참고).
  • 백그라운드 동기화 실행은 부가기능 스크립트 프로젝트에 정의된 runBackgroundSync() 함수를 실행합니다. 이 함수는 카드를 빌드하지 않습니다. 대신 UI를 변경하지 않는 다른 백그라운드 작업을 실행합니다. 함수가 UniversalActionResponse를 반환하지 않으므로 함수가 완료되면 UI에 새 카드가 표시되지 않습니다. 대신 함수가 실행되는 동안 UI에 로드 표시기 스피너가 표시됩니다.

다음은 openContactURL(), createSettingsResponse(), runBackgroundSync() 함수를 구성하는 방법의 예입니다.

/**
 * Open a contact URL.
 * @param {Object} e an event object
 * @return {UniversalActionResponse}
 */
function openContactURL(e) {
  // Activate temporary Gmail scopes, in this case so that the
  // open message metadata can be read.
  var accessToken = e.gmail.accessToken;
  GmailApp.setCurrentMessageAccessToken(accessToken);

  // Build URL to open based on a base URL and the sender's email.
  // This URL must be included in the openLinkUrlPrefixes whitelist.
  var messageId = e.gmail.messageId;
  var message = GmailApp.getMessageById(messageId);
  var sender = message.getFrom();
  var url = "https://www.example.com/urlbase/" + sender;
  return CardService.newUniversalActionResponseBuilder()
      .setOpenLink(CardService.newOpenLink()
          .setUrl(url))
      .build();
}

/**
 * Create a collection of cards to control the add-on settings and
 * present other information. These cards are displayed in a list when
 * the user selects the associated "Open settings" universal action.
 *
 * @param {Object} e an event object
 * @return {UniversalActionResponse}
 */
function createSettingsResponse(e) {
  return CardService.newUniversalActionResponseBuilder()
      .displayAddOnCards(
          [createSettingCard(), createAboutCard()])
      .build();
}

/**
 * Create and return a built settings card.
 * @return {Card}
 */
function createSettingCard() {
  return CardService.newCardBuilder()
      .setHeader(CardService.newCardHeader().setTitle('Settings'))
      .addSection(CardService.newCardSection()
          .addWidget(CardService.newSelectionInput()
              .setType(CardService.SelectionInputType.CHECK_BOX)
              .addItem("Ask before deleting contact", "contact", false)
              .addItem("Ask before deleting cache", "cache", false)
              .addItem("Preserve contact ID after deletion", "contactId", false))
          // ... continue adding widgets or other sections here ...
      ).build();   // Don't forget to build the card!
}

/**
 * Create and return a built 'About' informational card.
 * @return {Card}
 */
function createAboutCard() {
  return CardService.newCardBuilder()
      .setHeader(CardService.newCardHeader().setTitle('About'))
      .addSection(CardService.newCardSection()
          .addWidget(CardService.newTextParagraph()
              .setText('This add-on manages contact information. For more '
                  + 'details see the <a href="https://www.example.com/help">'
                  + 'help page</a>.'))
      // ... add other information widgets or sections here ...
      ).build();  // Don't forget to build the card!
}

/**
 * Run background tasks, none of which should alter the UI.
 * Also records the time of sync in the script properties.
 *
 * @param {Object} e an event object
 */
function runBackgroundSync(e) {
  var props = PropertiesService.getUserProperties();
  props.setProperty("syncTime", new Date().toString());

  syncWithContacts();  // Not shown.
  updateCache();       // Not shown.
  validate();          // Not shown.

  // no return value tells the UI to keep showing the current card.
}