スマートチップを使用してリンクをプレビューする

このページでは、Google ドキュメントのユーザーがサードパーティ サービスからリンクをプレビューできるようにする Google Workspace アドオンを作成する方法について説明します。

Google Workspace アドオンは、サービスのリンクを検出し、Google ドキュメントのユーザーにプレビューするよう求めることができます。サポートケース、見込み顧客、従業員プロフィールへのリンクなど、複数の URL パターンをプレビューするようにアドオンを構成できます。

Google ドキュメント ドキュメントでリンクをプレビューするには、スマートチップやカードを操作します。

ユーザーがカードをプレビューする

ユーザーがドキュメントに URL を入力または貼り付けると、リンクをスマートチップに置き換えるように求めるプロンプトが表示されます。スマートチップには、アイコンと、リンク コンテンツの短いタイトルまたは説明が表示されます。ユーザーがチップにカーソルを合わせると、ファイルやリンクに関する詳細情報をプレビューするカード インターフェースが表示されます。

次の動画では、ユーザーがリンクをスマートチップに変換してカードをプレビューする方法を示します。

Prerequisites

Apps Script

Node.js

Python

Java

省略可: サードパーティ サービスへの認証を設定する

承認を必要とするサービスにアドオンが接続される場合、リンクをプレビューするには、ユーザーがサービスに対して認証を行う必要があります。つまり、ユーザーがサービスから Google ドキュメントのドキュメントに初めてリンクを貼り付けるときに、アドオンで認証フローを呼び出す必要があります。

OAuth サービスまたはカスタム承認プロンプトを設定するには、次のいずれかのガイドをご覧ください。

このセクションでは、アドオンのリンク プレビューを設定する方法について説明します。これには、次の手順が含まれます。

  1. アドオンのデプロイ リソースまたはマニフェスト ファイルでリンク プレビューを構成する。
  2. リンクのスマートチップとカード インターフェースを作成します。

リンク プレビューを設定する

リンク プレビューを構成するには、アドオンのデプロイ リソースまたはマニフェスト ファイルで次のセクションとフィールドを指定します。

  1. addOns セクションに docs フィールドを追加して、Google ドキュメントを拡張します。
  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 アドオンによって会社のサポートケース サービスのリンクがプレビューされます。このアドオンは、リンクをプレビューするために 3 つの URL パターンを指定します。リンクが Google ドキュメント ドキュメント内の 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. プレビュー カードごとに、インターフェースのウィジェット インタラクティビティを提供するコールバック関数を実装します。たとえば、「リンクを表示」というボタンを含める場合、新しいウィンドウでリンクを開くコールバック関数を指定するアクションを作成できます。ウィジェットの操作の詳細については、アドオン アクションをご覧ください。

次のコードは、コールバック関数 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

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 をスペイン語にローカライズします。

Deployment リソース

Apps Script

apps-script/preview-links/appsscript.json
{
  "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

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

}