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

その

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

Google Workspace アドオンを使用すると、サービスのリンクを検出し、ユーザーにプレビューを求めるプロンプトを表示できます。サポートケース、セールスリード、従業員プロフィールへのリンクなど、複数の URL パターンをプレビューするようにアドオンを構成できます。

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

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

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

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

スプレッドシートとスライドのリンク プレビューのデベロッパー プレビューでは、サードパーティのスマートチップはサポートされていません。ユーザーが 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 フィールドを追加して Google ドキュメントを拡張し、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 を引数として渡します。このイベント オブジェクトのペイロードを使用して、リンク プレビュー用のスマートチップとカードを作成します。

たとえば、ドキュメントの URL パターン example.com/cases を指定するアドオンの場合、ユーザーがリンク 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/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

カードサービス フィールド 種類
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 と説明が表示されます。
  • サポートケース エージェント(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;
  }

}