スマートチップを使用したリンクのプレビュー

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

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

ユーザーがリンクをプレビューする方法

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

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

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

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

スプレッドシートとスライドでリンクをプレビューする方法

スプレッドシートとスライドのリンク プレビューでは、サードパーティのスマートチップはサポートされていません。ユーザーがスプレッドシートやプレゼンテーションに URL を入力または貼り付けると、Google スプレッドシートまたはスライドで、リンクをチップではなくリンクテキストとしてタイトルに置き換えるよう求めるメッセージが表示されます。ユーザーがリンクのタイトルにカーソルを合わせると、リンクに関する情報をプレビューするカード インターフェースが表示されます。

次の図は、スプレッドシートとスライドでリンク プレビューがどのように表示されるかを示しています。

スプレッドシートとスライドのリンク プレビューの例

前提条件

Apps Script

  • Google Workspace アカウント。
  • Google Workspace アドオン。アドオンを構築するには、このquickstartに従ってください。

Node.js

  • Google Workspace アカウント。
  • Google Workspace アドオン。アドオンを構築するには、このquickstartに従ってください。

Python

  • Google Workspace アカウント。
  • Google Workspace アドオン。アドオンを構築するには、このquickstartに従ってください。

Java

  • Google Workspace アカウント。
  • Google Workspace アドオン。アドオンを構築するには、このquickstartに従ってください。

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

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

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

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

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

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

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

  1. addOns セクションに、ドキュメントを拡張する docs フィールド、スプレッドシートを拡張する sheets フィールド、スライドを拡張する slides フィールドを追加します。
  2. 各フィールドで、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"
          }
        }
      ]
    },
    "sheets": {
      "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"
          }
        }
      ]
    },
    "slides": {
      "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 パターンを指定します。リンクが URL パターンのいずれかと一致するたびに、コールバック関数 caseLinkPreview がカードとスマートチップをビルドして表示します。また、スプレッドシートとスライドでは、URL をリンクのタイトルに置き換えます。

スマートチップとスマートカードをビルドする

リンクのスマートチップとカードを返すには、linkPreviewTriggers オブジェクトで指定した関数を実装する必要があります。

指定された URL パターンに一致するリンクをユーザーが操作すると、linkPreviewTriggers トリガーが起動し、そのコールバック関数がイベント オブジェクト EDITOR_NAME.matchedUrl.url を引数として渡します。このイベント オブジェクトのペイロードを使用して、リンク プレビュー用のスマートチップとカードをビルドします。

たとえば、ユーザーが Google ドキュメントでリンク https://www.example.com/cases/123456 をプレビューすると、次のイベント ペイロードが返されます。

JSON

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

カード インターフェースを作成するには、ウィジェットを使用してリンクに関する情報を表示します。ユーザーがリンクを開いたり、リンクのコンテンツを変更したりするアクションを作成することもできます。使用可能なウィジェットとアクションのリストについては、プレビュー カードでサポートされているコンポーネントをご覧ください。

リンク プレビュー用のスマートチップとカードを作成するには:

  1. アドオンのデプロイ リソースまたはマニフェスト ファイルの linkPreviewTriggers セクションで指定した関数を実装します。
    1. この関数は、EDITOR_NAME.matchedUrl.url を引数として含むイベント オブジェクトを受け取り、単一の Card オブジェクトを返す必要があります。
    2. サービスで認可が必要な場合は、関数で認可フローを呼び出す必要もあります。
  2. プレビュー カードごとに、インターフェースにウィジェットのインタラクティビティを提供するコールバック関数を実装します。たとえば、[リンクを表示] というボタンを含めると、リンクを新しいウィンドウで開くコールバック関数を指定するアクションを作成できます。ウィジェットの操作の詳細については、アドオン アクションをご覧ください。

次のコードは、ドキュメント用のコールバック関数 caseLinkPreview を作成します。

Apps Script

apps-script/3p-resources/3p-resources.gs
/**
* 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;
}

Node.js

node/3p-resources/index.js
/**
 * 
 * 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")
              }
            }]
          }]
        }
      }
    }
  };
}

Python

python/3p-resources/create_link_preview/main.py

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/CreateLinkPreview.java
/**
 * 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;
}

プレビュー カードでサポートされているコンポーネント

Google Workspace アドオンは、リンク プレビュー カードで次のウィジェットとアクションをサポートしています。

Apps Script

カードサービス フィールド タイプ
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 ドキュメント内の企業のサポートケースへのリンクをプレビューする Google Workspace アドオンを使用しています。

この例では、次のことを行います。

  • サポートケース(https://www.example.com/support/cases/1234 など)へのリンクをプレビューします。スマートチップにはサポート アイコンが表示され、プレビュー カードにはケース ID と説明が表示されます。
  • ユーザーのロケールがスペイン語に設定されている場合、スマートチップはその labelText をスペイン語にローカライズします。

Deployment リソース

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

JSON

{
  "oauthScopes": [
    "https://www.googleapis.com/auth/workspace.linkpreview"
  ],
  "addOns": {
    "common": {
      "name": "Preview support cases",
      "logoUrl": "https://developers.google.com/workspace/add-ons/images/support-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"
        }
      ]
    }
  }
}

コード

Apps Script

apps-script/3p-resources/3p-resources.gs
/**
* 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;
}

Node.js

node/3p-resources/index.js
/**
 * 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")
              }
            }]
          }]
        }
      }
    }
  };
}

Python

python/3p-resources/create_link_preview/main.py
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/CreateLinkPreview.java
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;
  }

}