תצוגה מקדימה של קישורים עם צ'יפים חכמים

בדף הזה מוסבר איך ליצור תוסף ל-Google Workspace שמאפשר למשתמשי Google Docs , Sheets ו-Slides לראות תצוגה מקדימה של קישורים משירות של צד שלישי.

תוסף של Google Workspace יכול לזהות את הקישורים לשירות שלכם ולבקש מהמשתמשים לראות אותם בתצוגה מקדימה. תוכלו להגדיר תוסף כדי להציג תצוגה מקדימה של מספר תבניות של כתובות URL, כמו קישורים לבקשות תמיכה, לידים למכירות ופרופילים של עובדים.

איך משתמשים רואים תצוגה מקדימה של קישורים

כדי לראות תצוגה מקדימה של קישורים, המשתמשים צריכים להשתמש בצ'יפים חכמים ובכרטיסים.

משתמש מציג כרטיס בתצוגה מקדימה

כשמשתמשים מקלידים או מדביקים כתובת URL במסמך, מוצגת ב-Google Docs בקשה להחליף את הקישור בצ'יפ חכם. בצ'יפ החכם מוצגים סמל ושם קצר או תיאור קצר של התוכן של הקישור. כשהמשתמש מעביר את העכבר מעל לצ'יפ, הוא רואה ממשק של כרטיס עם תצוגה מקדימה של מידע נוסף על הקובץ או הקישור.

בסרטון הבא אפשר לראות איך המשתמש ממיר קישור לצ'יפ חכם ומציג כרטיס בתצוגה מקדימה:

איך משתמשים רואים תצוגה מקדימה של קישורים ב-Sheets וב-Slides

אין תמיכה בצ'יפים חכמים של צד שלישי בתצוגות מקדימות של קישורים ב-Sheets וב-Slides. כשהמשתמשים מקלידים או מדביקים כתובת URL בגיליון אלקטרוני או במצגת, ב-Sheets או במצגת ב-Slides מוצגת בקשה להחליף את הקישור בשם של הקישור כטקסט מקושר במקום כצ'יפ. כשהמשתמש מעביר את העכבר מעל כותרת הקישור, הוא רואה ממשק של כרטיס שמציג מידע על הקישור.

התמונה הבאה מראה איך תצוגה מקדימה של קישור מעובדת ב-Sheets וב-Slides:

דוגמה לתצוגה מקדימה של קישור ב-Sheets וב-Slides

דרישות מוקדמות

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.

אופציונלי: הגדרת אימות לשירות של צד שלישי

אם התוסף מתחבר לשירות שדורש הרשאה, המשתמשים יצטרכו לבצע אימות לשירות כדי לראות תצוגה מקדימה של הקישורים. כלומר, כשמשתמשים מדביקים בפעם הראשונה קישור מהשירות שלכם בקובץ Docs, Sheets או Slides, התוסף צריך להפעיל את תהליך ההרשאה.

כדי להגדיר שירות OAuth או בקשה להרשאה בהתאמה אישית, תוכלו להיעזר באחד מהמדריכים הבאים:

בקטע הזה מוסבר איך להגדיר תצוגה מקדימה לקישורים בתוסף, כולל השלבים הבאים:

  1. הגדירו תצוגות מקדימות של קישורים במשאב הפריסה או בקובץ המניפסט של התוסף.
  2. איך מפתחים את הממשק של הצ'יפ החכם והכרטיס לקישורים.

הגדרת תצוגות מקדימות של קישורים

כדי להגדיר תצוגה מקדימה לקישורים, צריך לציין את הקטעים והשדות הבאים במשאב הפריסה או בקובץ המניפסט של התוסף:

  1. בקטע addOns מוסיפים את השדה docs כדי להרחיב את Docs, את השדה sheets כדי להרחיב את Sheets ואת השדה slides כדי להרחיב את 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 יוצרת ומציגה כרטיס וצ'יפ חכם, או ב-Sheets וב-Slides, מחליפה את כתובת ה-URL בכותרת הקישור.

פיתוח הצ'יפ החכם והכרטיס

כדי להחזיר צ'יפ חכם וכרטיס לקישור, צריך להטמיע את כל הפונקציות שציינתם באובייקט linkPreviewTriggers.

כשמשתמש יוצר אינטראקציה עם קישור שתואם לתבנית URL מסוימת, הטריגר linkPreviewTriggers מופעל ופונקציית הקריאה החוזרת מעבירה את אובייקט האירוע EDITOR_NAME.matchedUrl.url כארגומנט. המטען הייעודי (payload) של אובייקט האירוע הזה משמש לפיתוח הצ'יפ החכם והכרטיס לתצוגה המקדימה של הקישור.

לדוגמה, אם משתמש מציג בתצוגה מקדימה את הקישור https://www.example.com/cases/123456 ב-Docs, מוחזר המטען הייעודי (payload) הבא של האירוע:

JSON

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

כדי ליצור את ממשק הכרטיס, משתמשים בווידג'טים כדי להציג מידע על הקישור. אפשר גם ליצור פעולות שמאפשרות למשתמשים לפתוח את הקישור או לשנות את התוכן שלו. לרשימת הווידג'טים והפעולות הזמינים, ראו רכיבים נתמכים לכרטיסי תצוגה מקדימה.

כדי ליצור את הצ'יפ החכם והכרטיס לתצוגה מקדימה של קישור:

  1. מטמיעים את הפונקציה שציינתם בקטע linkPreviewTriggers במשאב הפריסה או בקובץ המניפסט של התוסף:
    1. הפונקציה צריכה לקבל אובייקט אירוע שמכיל את הערך EDITOR_NAME.matchedUrl.url כארגומנט, ולהחזיר אובייקט Card יחיד.
    2. אם השירות דורש הרשאה, הפונקציה צריכה גם להפעיל את תהליך ההרשאה.
  2. בכל כרטיס תצוגה מקדימה, כדאי להטמיע פונקציות של התקשרות חזרה שמציעות אינטראקטיביות של ווידג'טים לממשק. לדוגמה, אם הוספתם לחצן 'הצגת קישור', תוכלו ליצור פעולה שמציינת פונקציית קריאה חוזרת לפתיחת הקישור בחלון חדש. למידע נוסף על אינטראקציות עם ווידג'טים, ראו פעולות של תוספים.

הקוד הבא יוצר את פונקציית הקריאה החוזרת caseLinkPreview ל-Docs:

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 Workspace, שמספק תצוגה מקדימה של קישורים לבקשות התמיכה של החברה ב-Google Docs.

הדוגמה כוללת:

  • תצוגה מקדימה של קישורים לבקשות תמיכה, כמו https://www.example.com/support/cases/1234. בצ'יפ החכם מוצג סמל תמיכה, וכרטיס התצוגה המקדימה כולל את מספר הפנייה ותיאור.
  • אם הלוקאל של המשתמש מוגדר לספרדית, הצ'יפ החכם מבצע לוקליזציה של labelText לספרדית.

משאב פריסה

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

}