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

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

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

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

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

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

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

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

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

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

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

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

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

Скрипт приложений

Node.js

Питон

Ява

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

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

Чтобы настроить службу 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;
}

Node.js

узел/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 .

JSON

Поле карты ( 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"
        }
      ]
    }
  }
}

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

Код

Скрипт приложений

приложения-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

узел/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;
  }

}