Предварительные ссылки со смарт-чипами

На этой странице объясняется, как создать надстройку Google Workspace, которая позволяет пользователям Документов, Таблиц и Презентации Google просматривать ссылки из стороннего сервиса.

Надстройка Google Workspace может обнаруживать ссылки вашего сервиса и предлагать пользователям просмотреть их. Вы можете настроить надстройку для предварительного просмотра нескольких шаблонов URL-адресов, таких как ссылки на обращения в службу поддержки, потенциальных клиентов и профили сотрудников.

Как пользователи просматривают ссылки

Для предварительного просмотра ссылок пользователи взаимодействуют со смарт-чипами и картами .

Пользователь просматривает карточку

Когда пользователи вводят или вставляют URL-адрес в документ или таблицу, Документы Google или Таблицы Google предлагают им заменить ссылку смарт-чипом. На смарт-чипе отображается значок и краткое название или описание содержимого ссылки. Когда пользователь наводит курсор на чип, он видит интерфейс карты, который позволяет просмотреть дополнительную информацию о файле или ссылке.

В следующем видео показано, как пользователь преобразует ссылку в смарт-чип и предварительно просматривает карту:

Как пользователи просматривают ссылки в Презентациях

Сторонние смарт-чипы не поддерживаются для предварительного просмотра ссылок в Презентациях. Когда пользователи вводят или вставляют URL-адрес в презентацию, Slides предлагает им заменить ссылку ее заголовком в виде связанного текста, а не чипа. Когда пользователь наводит курсор на заголовок ссылки, он видит интерфейс карточки, в котором можно просмотреть информацию о ссылке.

На следующем изображении показано, как предварительный просмотр ссылки отображается в слайдах:

Пример предварительного просмотра ссылки для слайдов

Предварительные условия

Необязательно: настройте аутентификацию для стороннего сервиса.

Если ваше дополнение подключается к сервису, требующему авторизации, пользователи должны пройти аутентификацию в сервисе для предварительного просмотра ссылок. Это означает, что когда пользователи впервые вставляют ссылку из вашего сервиса в файл Документов, Таблиц или Презентаций, ваша надстройка должна вызвать поток авторизации.

Чтобы настроить службу OAuth или настраиваемый запрос на авторизацию, см. раздел Подключение надстройки к сторонней службе .

В этом разделе объясняется, как настроить предварительный просмотр ссылок для вашего дополнения, что включает в себя следующие шаги:

  1. Настройте предварительный просмотр ссылок в манифесте вашего дополнения.
  2. Создайте интерфейс смарт-чипа и карты для ваших ссылок.

Настройка предварительного просмотра ссылок

Чтобы настроить предварительный просмотр ссылок, укажите следующие разделы и поля в манифесте вашего дополнения:

  1. В разделе addOns добавьте поле docs , чтобы расширить «Документы», поле sheets , чтобы расширить «Листы», и поле slides , чтобы расширить «Слайды».
  2. В каждом поле реализуйте триггер linkPreviewTriggers , включающий функцию runFunction (эту функцию вы определите в следующем разделе « Создание смарт-чипа и карты »).

    Чтобы узнать, какие поля можно указать в триггере linkPreviewTriggers , см. справочную документацию по манифестам Apps Script или ресурсам развертывания для других сред выполнения .

  3. В поле oauthScopes добавьте область https://www.googleapis.com/auth/workspace.linkpreview , чтобы пользователи могли разрешить надстройке просматривать ссылки от их имени.

В качестве примера см. раздел oauthScopes и addons следующего манифеста, который настраивает предварительный просмотр ссылок для службы поддержки.

{
  "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 просматривает ссылки на службу поддержки компании. Надстройка определяет три шаблона URL-адресов для предварительного просмотра ссылок. Всякий раз, когда ссылка соответствует одному из шаблонов URL-адресов, функция обратного вызова caseLinkPreview создает и отображает карточку и смарт-чип в документах, таблицах или слайдах и заменяет URL-адрес заголовком ссылки.

Создайте смарт-чип и карту

Чтобы вернуть смарт-чип и карту по ссылке, необходимо реализовать любые функции, которые вы указали в объекте linkPreviewTriggers .

Когда пользователь взаимодействует со ссылкой, соответствующей указанному шаблону URL-адреса, срабатывает триггер linkPreviewTriggers , и его функция обратного вызова передает объект события EDITOR_NAME .matchedUrl.url в качестве аргумента. Полезные данные этого объекта события используются для создания смарт-чипа и карты для предварительного просмотра ссылки.

Например, если пользователь просматривает ссылку 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 для Документов:

приложения-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;
}
узел/3p-ресурсы/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/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/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 поддерживают следующие виджеты и действия для карточек предварительного просмотра ссылок:

Поле «Обслуживание карты» Тип
TextParagraph Виджет
DecoratedText Виджет
Image Виджет
IconImage Виджет
ButtonSet Виджет
TextButton Виджет
ImageButton Виджет
Grid Виджет
Divider Виджет
OpenLink Действие
Navigation Действие
Поддерживается только метод updateCard .
Поле карты ( google.apps.card.v1 ) Тип
TextParagraph Виджет
DecoratedText Виджет
Image Виджет
Icon Виджет
ButtonList Виджет
Button Виджет
Grid Виджет
Divider Виджет
OpenLink Действие
Navigation Действие
Поддерживается только метод updateCard .

Полный пример: надстройка запроса на поддержку

В следующем примере представлена ​​надстройка Google Workspace, которая просматривает ссылки на обращения в службу поддержки компании в Документах Google.

В примере делается следующее:

  • Предварительный просмотр ссылок на обращения в службу поддержки, например https://www.example.com/support/cases/1234 . На смарт-чипе отображается значок поддержки, а на карточке предварительного просмотра указан идентификатор обращения и описание.
  • Если языковой стандарт пользователя установлен на испанский, смарт-чип локализует свой labelText на испанский язык.

Манифест

приложения-скрипт/3p-ресурсы/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"
        }
      ]
    }
  }
}
{
  "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"
        }
      ]
    }
  }
}

Код

приложения-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;
}
узел/3p-ресурсы/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/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/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;
  }

}