스마트 칩으로 링크 미리보기 (개발자 프리뷰)

을 사용 설정합니다.

이 페이지에서는 Google Docs 사용자가 서드 파티 서비스의 링크를 미리 볼 수 있도록 Google Workspace 부가기능을 빌드하는 방법을 설명합니다.

Google Workspace 부가기능은 서비스 링크를 감지하고 Google Docs 사용자에게 이를 미리 보라는 메시지를 표시할 수 있습니다. 지원 케이스, 영업 리드, 직원 프로필과 같은 여러 URL 패턴을 미리 보도록 부가기능을 구성할 수 있습니다.

Google Docs 문서의 링크를 미리 보기 위해 사용자는 스마트 칩카드와 상호작용합니다.

사용자가 카드 미리보기

사용자가 문서에 URL을 입력하거나 붙여넣으면 Google Docs에서 링크를 스마트 칩으로 대체하라는 메시지를 표시합니다. 스마트 칩에는 링크 콘텐츠의 아이콘과 짧은 제목 또는 설명이 표시됩니다. 사용자가 칩 위로 마우스를 가져가면 파일 또는 링크에 관한 자세한 정보를 미리 볼 수 있는 카드 인터페이스가 표시됩니다.

다음 동영상은 사용자가 링크를 스마트 칩으로 변환하고 카드를 미리 보는 방법을 보여줍니다.

기본 요건

Apps Script

Node.js

Python

자바

선택사항: 서드 파티 서비스에 대한 인증 설정

부가기능이 승인이 필요한 서비스에 연결되는 경우 사용자가 서비스에 액세스하려면 링크를 미리 확인해야 합니다. 즉, 사용자가 서비스에서 Google Docs 문서에 처음으로 링크를 붙여넣으면 부가기능에서 승인 흐름을 호출해야 합니다.

OAuth 서비스 또는 커스텀 승인 메시지를 설정하려면 다음 가이드 중 하나를 참조하세요.

이 섹션에서는 다음 단계에 따라 부가기능의 링크 미리보기를 설정하는 방법을 설명합니다.

  1. 부가기능의 배포 리소스 또는 매니페스트 파일에서 링크 미리보기를 구성합니다.
  2. 링크의 스마트 칩 및 카드 인터페이스를 빌드합니다.

링크 미리보기 구성

링크 미리보기를 구성하려면 부가기능의 배포 리소스 또는 매니페스트 파일에 다음 섹션과 필드를 지정합니다.

  1. addOns 섹션에서 docs 필드를 추가하여 Google Docs를 확장합니다.
  2. docs 필드에 runFunction가 포함된 linkPreviewTriggers 트리거를 구현합니다 (이 함수는 다음 섹션인 스마트 칩 및 카드 빌드에서 정의함).

    linkPreviewTriggers 트리거에서 지정할 수 있는 필드에 관한 자세한 내용은 Apps Script 매니페스트 파일 또는 다른 런타임용 배포 리소스 참조 문서를 확인하세요.

  3. 사용자가 대신 링크를 미리 볼 수 있도록 부가기능을 승인할 수 있도록 oauthScopes 필드에 https://www.googleapis.com/auth/workspace.linkpreview를 추가합니다.

예를 들어 지원 케이스 서비스의 링크 미리보기를 구성하는 배포 리소스의 oauthScopesaddons 섹션을 참고하세요.

{
  "oauthScopes": [
    "https://www.googleapis.com/auth/workspace.linkpreview"
  ],
  "addOns": {
    "common": {
      "name": "Preview support cases",
      "logoUrl": "https://www.example.com/images/company-logo.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",
          "logoUrl": "https://www.example.com/images/support-icon.png",
          "localizedLabelText": {
            "es": "Caso de soporte"
          }
        }
      ]
    }
  }
}

이 예에서 Google Workspace 부가기능 미리보기는 회사의 지원 케이스 서비스에 대한 링크입니다. 부가기능은 링크 미리보기를 위한 세 가지 URL 패턴을 지정합니다. 링크가 Google Docs 문서의 URL 패턴 중 하나와 일치할 때마다 콜백 함수인 caseLinkPreview가 스마트 칩과 카드를 빌드하여 표시합니다.

스마트 칩 및 카드 빌드

링크의 스마트 칩과 카드를 반환하려면 linkPreviewTriggers 객체에 지정한 모든 함수를 구현해야 합니다.

사용자가 지정된 URL 패턴과 일치하는 링크와 상호작용하면 linkPreviewTriggers 트리거가 실행되고 콜백 함수가 이벤트 객체 docs.matchedUrl.url를 인수로 전달합니다. 이 이벤트 객체의 페이로드를 사용하여 링크 미리보기를 위한 스마트 칩과 카드를 빌드합니다.

예를 들어 URL 패턴 example.com/cases를 지정하는 부가기능의 경우 사용자가 링크 https://www.example.com/cases/123456를 미리 보면 다음 이벤트 페이로드가 반환됩니다.

JSON

{
  "docs": {
    "matchedUrl": {
        "url": "https://www.example.com/support/cases/123456"
    }
  }
}

카드 인터페이스를 만들려면 위젯을 사용하여 링크에 관한 정보를 표시합니다. 사용자가 링크를 열거나 콘텐츠를 수정할 수 있는 작업을 빌드할 수도 있습니다. 사용 가능한 위젯 및 작업 목록은 미리보기 카드에 지원되는 구성요소를 참고하세요.

링크 미리보기를 위한 스마트 칩 및 카드를 빌드하는 방법은 다음과 같습니다.

  1. 부가기능 배포 리소스 또는 매니페스트 파일의 linkPreviewTriggers 섹션에 지정한 함수를 구현합니다.
    1. 함수는 docs.matchedUrl.url를 인수로 포함하는 이벤트 객체를 인수로 허용하고 단일 Card 객체를 반환해야 합니다.
    2. 서비스에 승인이 필요한 경우 함수는 승인 흐름도 호출해야 합니다.
  2. 각 미리보기 카드에 인터페이스의 위젯 상호작용을 제공하는 데 사용되는 콜백 함수를 구현합니다. 예를 들어 인터페이스에 버튼을 포함하는 경우 연결된 Action와 버튼을 클릭할 때 실행되는 구현 콜백 함수가 있어야 합니다.

다음 코드는 콜백 함수 caseLinkPreview를 만듭니다.

Apps Script

apps-script/preview-links/preview-link.gs
/**
* Entry point for a support case link preview
*
* @param {!Object} event
* @return {!Card}
*/
// Creates a function that passes an event object as a parameter.
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 ID.
    const segments = event.docs.matchedUrl.url.split('/');
    const caseId = segments[segments.length - 1];

    // Builds a preview card with the case ID, title, and description
    const caseHeader = CardService.newCardHeader()
      .setTitle(`Case ${caseId}: Title bar is broken.`);
    const caseDescription = CardService.newTextParagraph()
      .setText('Customer can\'t view title on mobile device.');

    // 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();
  }
}

Node.js

node/preview-links/index.js
/**
 * 
 * A support case link preview.
 *
 * @param {!string} url
 * @return {!Card}
 */
function caseLinkPreview(url) {

  // Parses the URL to identify the case ID.
  const segments = url.split('/');
  const caseId = segments[segments.length - 1];

  // Returns the card.
  // Uses the text from the card's header for the title of the smart chip.
  return {
    header: {
      title: `Case ${caseId}: Title bar is broken.`
    },
    sections: [{
      widgets: [{
        textParagraph: {
          text: `Customer can't view title on mobile device.`
        }
      }]
    }]
  };
}

Python

python/preview-links/main.py

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

    # Parses the URL to identify the case ID.
    segments = url.split("/")
    case_id = segments[-1]

    # Returns the card.
    # Uses the text from the card's header for the title of the smart chip.
    return {
        "header": {"title": f"Case {case_id}: Title bar is broken."},
        "sections": [
            {
                "widgets": [
                    {
                        "textParagraph": {
                            "text": "Customer can't view title on mobile device."
                        }
                    }
                ]
            }
        ],
    }

자바

java/preview-links/src/main/java/PreviewLink.java
/**
 * Creates a case link preview card.
 *
 * @param url A URL.
 * @return A case link preview card.
 */
Card caseLinkPreview(String url) {
  String[] segments = url.split("/");
  String caseId = segments[segments.length - 1];

  CardHeader cardHeader = new CardHeader();
  cardHeader.setTitle(String.format("Case %s: Title bar is broken.", caseId));

  TextParagraph textParagraph = new TextParagraph();
  textParagraph.setText("Customer can't view title on mobile device.");

  WidgetMarkup widget = new WidgetMarkup();
  widget.setTextParagraph(textParagraph);
  Section section = new Section();
  section.setWidgets(List.of(widget));

  Card card = new Card();
  card.setHeader(cardHeader);
  card.setSections(List.of(section));

  return card;
}

미리보기 카드에 지원되는 구성요소

Google Workspace 부가기능은 링크 미리보기 카드에 다음과 같은 위젯과 작업을 지원합니다.

Apps Script

Card Service 필드 유형
TextParagraph 위젯
DecoratedText 위젯
Image 위젯
IconImage 위젯
ButtonSet 위젯
TextButton 위젯
ImageButton 위젯
Grid 위젯
Divider 위젯
OpenLink 작업
Navigation 작업
updateCard 메서드만 지원됩니다.

JSON

카드 (google.apps.card.v1) 입력란 유형
TextParagraph 위젯
DecoratedText 위젯
Image 위젯
Icon 위젯
ButtonList 위젯
Button 위젯
Grid 위젯
Divider 위젯
OpenLink 작업
Navigation 작업
updateCard 메서드만 지원됩니다.

전체 예: 지원 케이스 부가기능

다음 예는 회사의 지원 케이스와 직원 프로필로 연결되는 링크를 미리 보는 Google Workspace 부가기능을 보여줍니다.

이 예시는 다음을 수행합니다.

  • 미리보기는 https://www.example.com/support/cases/1234와 같은 지원 케이스에 연결됩니다. 스마트 칩에 지원 아이콘이 표시되고 미리보기 카드에 케이스 ID와 설명이 포함됩니다.
  • https://www.example.com/people/rosario-cruz와 같은 지원 케이스 에이전트의 링크를 미리 봅니다. 스마트 칩에는 사람 아이콘이 표시되고 미리보기 카드에는 직원 이름, 이메일, 직책, 프로필 사진이 포함되어 있습니다.
  • 사용자의 언어가 스페인어로 설정된 경우 스마트 칩은 labelText를 스페인어로 현지화합니다.

배포 리소스

Apps Script

{
  "timeZone": "America/New_York",
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8",
  "oauthScopes": [
    "https://www.googleapis.com/auth/workspace.linkpreview"
  ],
  "addOns": {
    "common": {
      "name": "Preview support cases",
      "logoUrl": "https://developers.google.com/workspace/add-ons/images/link-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"
        },
        {
          "runFunction": "peopleLinkPreview",
          "patterns": [
            {
              "hostPattern": "example.com",
              "pathPrefix": "people"
            }
          ],
          "labelText": "People",
          "localizedLabelText": {
            "es": "Personas"
          },
          "logoUrl": "https://developers.google.com/workspace/add-ons/images/person-icon.png"
        }
      ]
    }
  }
}

JSON

{
  "oauthScopes": [
    "https://www.googleapis.com/auth/workspace.linkpreview"
  ],
  "addOns": {
    "common": {
      "name": "Preview support cases",
      "logoUrl": "https://developers.google.com/workspace/add-ons/images/link-icon.png",
      "layoutProperties": {
        "primaryColor": "#dd4b39"
      }
    },
    "docs": {
      "linkPreviewTriggers": [
        {
          "runFunction": "URL",
          "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"
        },
        {
          "runFunction": "URL",
          "patterns": [
            {
              "hostPattern": "example.com",
              "pathPrefix": "people"
            }
          ],
          "labelText": "People",
          "localizedLabelText": {
            "es": "Personas"
          },
          "logoUrl": "https://developers.google.com/workspace/add-ons/images/person-icon.png"
        }
      ]
    }
  }
}

코드

Apps Script

apps-script/preview-links/preview-link.gs
/**
* Entry point for a support case link preview
*
* @param {!Object} event
* @return {!Card}
*/
// Creates a function that passes an event object as a parameter.
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 ID.
    const segments = event.docs.matchedUrl.url.split('/');
    const caseId = segments[segments.length - 1];

    // Builds a preview card with the case ID, title, and description
    const caseHeader = CardService.newCardHeader()
      .setTitle(`Case ${caseId}: Title bar is broken.`);
    const caseDescription = CardService.newTextParagraph()
      .setText('Customer can\'t view title on mobile device.');

    // 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();
  }
}


/**
* Entry point for an employee profile link preview
*
* @param {!Object} event
* @return {!Card}
*/
function peopleLinkPreview(event) {

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

    // Builds a preview card with an employee's name, title, email, and profile photo.
    const userHeader = CardService.newCardHeader().setTitle("Rosario Cruz");
    const userImage = CardService.newImage()
      .setImageUrl("https://developers.google.com/workspace/add-ons/images/employee-profile.png");
    const userInfo = CardService.newDecoratedText()
      .setText("rosario@example.com")
      .setBottomLabel("Case Manager")
      .setIcon(CardService.Icon.EMAIL);
    const userSection = CardService.newCardSection()
      .addWidget(userImage)
      .addWidget(userInfo);

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

Node.js

node/preview-links/index.js
const UrlParser = require('url');

/**
 * Responds to any HTTP request.
 *
 * @param {Object} req HTTP request context.
 * @param {Object} res HTTP response context.
 */
exports.createLinkPreview = (req, res) => {
  const event = req.body;
  if (event.docs.matchedUrl.url) {
    res.json(createCard(event.docs.matchedUrl.url));
  }
};

/**
 * Creates a preview link card for either a case link or people link.
 * 
 * @param {!String} url
 * @return {!Card}
 */
function createCard(url) {
  const parsedUrl = UrlParser.parse(url);
  if (parsedUrl.hostname === 'www.example.com') {
    if (parsedUrl.path.startsWith('/support/cases/')) {
      return caseLinkPreview(url);
    }

    if (parsedUrl.path.startsWith('/people/')) {
      return peopleLinkPreview();
    }
  }
}


/**
 * 
 * A support case link preview.
 *
 * @param {!string} url
 * @return {!Card}
 */
function caseLinkPreview(url) {

  // Parses the URL to identify the case ID.
  const segments = url.split('/');
  const caseId = segments[segments.length - 1];

  // Returns the card.
  // Uses the text from the card's header for the title of the smart chip.
  return {
    header: {
      title: `Case ${caseId}: Title bar is broken.`
    },
    sections: [{
      widgets: [{
        textParagraph: {
          text: `Customer can't view title on mobile device.`
        }
      }]
    }]
  };
}


/**
 * An employee profile link preview.
 *
 * @return {!Card}
 */
function peopleLinkPreview() {

  // Builds a preview card with an employee's name, title, email, and profile photo.
  // Returns the card. Uses the text from the card's header for the title of the smart chip.
  return {
    header: {
      title: "Rosario Cruz"
    },
    sections: [{
      widgets: [
        {
          image: {
            imageUrl: 'https://developers.google.com/workspace/add-ons/images/employee-profile.png'
          }
        }, {
          keyValue: {
            icon: "EMAIL",
            content: "rosario@example.com",
            bottomLabel: "Case Manager"
          }
        }
      ]
    }]
  };
}

Python

python/preview-links/main.py
from typing import Any, Mapping
from urllib.parse import urlparse

import flask
import functions_framework


@functions_framework.http
def create_link_preview(req: flask.Request):
    """Responds to any HTTP request.
    Args:
      req: HTTP request context.
    Returns:
      The response object.
    """
    event = req.get_json(silent=True)
    if event["docs"]["matchedUrl"]["url"]:
        return create_card(event["docs"]["matchedUrl"]["url"])


def create_card(url):
    """Creates a preview link card for either a case link or people link.
    Args:
      url: The matched url.
    Returns:
      A case link preview card or a people link preview card.
    """
    parsed_url = urlparse(url)
    if parsed_url.hostname != "www.example.com":
        return {}

    if parsed_url.path.startswith("/support/cases/"):
        return case_link_preview(url)

    if parsed_url.path.startswith("/people/"):
        return people_link_preview()

    return {}




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

    # Parses the URL to identify the case ID.
    segments = url.split("/")
    case_id = segments[-1]

    # Returns the card.
    # Uses the text from the card's header for the title of the smart chip.
    return {
        "header": {"title": f"Case {case_id}: Title bar is broken."},
        "sections": [
            {
                "widgets": [
                    {
                        "textParagraph": {
                            "text": "Customer can't view title on mobile device."
                        }
                    }
                ]
            }
        ],
    }




def people_link_preview():
    """An employee profile link preview.
    Returns:
      A people link preview card.
    """

    # Builds a preview card with an employee's name, title, email, and profile photo.
    # Returns the card. Uses the text from the card's header for the title of the smart chip.
    return {
        "header": {"title": "Rosario Cruz"},
        "sections": [
            {
                "widgets": [
                    {
                        "image": {
                            "imageUrl": "https:#developers.google.com/workspace/add-ons/images/employee-profile.png"
                        }
                    },
                    {
                        "keyValue": {
                            "icon": "EMAIL",
                            "content": "rosario@example.com",
                            "bottomLabel": "Case Manager",
                        }
                    },
                ]
            }
        ],
    }

자바

java/preview-links/src/main/java/PreviewLink.java
import com.google.api.services.chat.v1.model.Card;
import com.google.api.services.chat.v1.model.CardHeader;
import com.google.api.services.chat.v1.model.Image;
import com.google.api.services.chat.v1.model.KeyValue;
import com.google.api.services.chat.v1.model.Section;
import com.google.api.services.chat.v1.model.TextParagraph;
import com.google.api.services.chat.v1.model.WidgetMarkup;
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.JsonObject;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;

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

  /**
   * Responds to any HTTP request.
   *
   * @param request  An HTTP request context.
   * @param response An HTTP response context.
   */
  @Override
  public void service(HttpRequest request, HttpResponse response) throws Exception {
    JsonObject body = gson.fromJson(request.getReader(), JsonObject.class);
    String url = body.getAsJsonObject("docs")
        .getAsJsonObject("matchedUrl")
        .get("url")
        .getAsString();

    response.getWriter().write(gson.toJson(createCard(url)));
  }

  /**
   * Creates a preview link card for either a case link or people link.
   *
   * @param url A URL.
   * @return A case link preview card or a people link preview card.
   */
  Card createCard(String url) throws MalformedURLException {
    URL parsedURL = new URL(url);

    if (!parsedURL.getHost().equals("www.example.com")) {
      return new Card();
    }

    if (parsedURL.getPath().startsWith("/support/cases/")) {
      return caseLinkPreview(url);
    }

    if (parsedURL.getPath().startsWith("/people/")) {
      return peopleLinkPreview();
    }

    return new Card();
  }


  /**
   * Creates a case link preview card.
   *
   * @param url A URL.
   * @return A case link preview card.
   */
  Card caseLinkPreview(String url) {
    String[] segments = url.split("/");
    String caseId = segments[segments.length - 1];

    CardHeader cardHeader = new CardHeader();
    cardHeader.setTitle(String.format("Case %s: Title bar is broken.", caseId));

    TextParagraph textParagraph = new TextParagraph();
    textParagraph.setText("Customer can't view title on mobile device.");

    WidgetMarkup widget = new WidgetMarkup();
    widget.setTextParagraph(textParagraph);
    Section section = new Section();
    section.setWidgets(List.of(widget));

    Card card = new Card();
    card.setHeader(cardHeader);
    card.setSections(List.of(section));

    return card;
  }


  /**
   * Creates a people link preview card.
   *
   * @return A people link preview card.
   */
  Card peopleLinkPreview() {
    CardHeader cardHeader = new CardHeader();
    cardHeader.setTitle("Rosario Cruz");

    Image image = new Image();
    image.setImageUrl("https://developers.google.com/workspace/add-ons/images/employee-profile.png");

    WidgetMarkup imageWidget = new WidgetMarkup();
    imageWidget.setImage(image);

    KeyValue keyValue = new KeyValue();
    keyValue.setIcon("EMAIL");
    keyValue.setContent("rosario@example.com");
    keyValue.setBottomLabel("Case Manager");

    WidgetMarkup keyValueWidget = new WidgetMarkup();
    keyValueWidget.setKeyValue(keyValue);

    Section section = new Section();
    section.setWidgets(List.of(imageWidget, keyValueWidget));

    Card card = new Card();
    card.setHeader(cardHeader);
    card.setSections(List.of(section));

    return card;
  }

}