improved

CX Innovation with Workspace Designer

Hello Builders,

To achieve a first-class Customer Experience (CX), a more customer-tailored approach is fundamental. To guarantee a successful interaction, agents must always have access to relevant information. This means a constant need to context-switch between Talkdesk and multiple other applications in order to get the relevant information to better support the customer.

Imagine you are selling your products via an online shop and your agents give support to customers. You have a back office system that has all the product information as well as the users shopping carts. Your agent has to check the back office on each interaction, searching for relevant data to better support the customer. This means more handling time, more waiting time for the customer, more questions, and a worse overall experience. It’s time to change that!

With Talkdesk Workspace Designer™, it’s possible to create a tailored solution for each agent, giving the tools and information needed for a perfect customer experience. In this blog series, we’ll implement an integration with an online shop via Workspace Designer. Let’s start?

Prerequisite Knowledge

To better understand and follow this blog series, we recommend reading:

Basic knowledge of the following topics would also be helpful:

  • HTML (HyperText Markup Language).
  • Javascript.
  • APIs and how they work.

Getting Started

In this blog series, we’ll use MohammadReza Keikavousi’s Fake Store API to mock an online shop.

To access and interact with an API, we first need to create a Connection. This can be done using Talkdesk Connections™. We’ll not cover how to create the Connection and the needed set of Actions but you can find the required schema below.

The ultimate goal of this blog series is to have the shopping data embedded in Talkdesk. This will be achieved by having an agent who can not only see a customer cart, but is also able to search for product details and add products to the customer’s shopping cart.

Implementation

This first goal of this post is to be able to build an app where an agent can search for a user and be able to get his ID (required for retrieving the shopping cart). Since the customer does not know its own internal ID, we need to search for the user ID by accessing this endpoint.

Go ahead and create the necessary Connection and Action for our API. If you’re having trouble creating the schema, use the one below:

{
 "$schema": "http://json-schema.org/draft-07/schema",
 "type": "array",
 "items": {
   "type": "object",
   "required": [
     "address",
     "id",
     "email",
     "username",
     "password",
     "name",
     "phone",
     "__v"
   ],
   "properties": {
     "address": {
       "type": "object",
       "required": [
         "geolocation",
         "city",
         "street",
         "number",
         "zipcode"
       ],
       "properties": {
         "geolocation": {
           "type": "object",
           "required": [
             "lat",
             "long"
           ],
           "properties": {
             "lat": {
               "type": "string"
             },
             "long": {
               "type": "string"
             }
           }
         },
         "city": {
           "type": "string"
         },
         "street": {
           "type": "string"
         },
         "number": {
           "type": "integer"
         },
         "zipcode": {
           "type": "string"
         }
       }
     },
     "id": {
       "type": "integer"
     },
     "email": {
       "type": "string"
     },
     "username": {
       "type": "string"
     },
     "password": {
       "type": "string"
     },
     "name": {
       "type": "object",
       "required": [
         "firstname",
         "lastname"
       ],
       "properties": {
         "firstname": {
           "type": "string"
         },
         "lastname": {
           "type": "string"
         }
       }
     },
     "phone": {
       "type": "string"
     },
     "__v": {
       "type": "integer"
     }
   }
 }
}

The next step, after creating the Connection and Action for that endpoint, is to create a card.

After generating the card, we’re redirected to the Automation Designer, the tool that Workspace Designer leverages to build the card flow. The first component is always the Card Trigger. This trigger is executed when a card is presented to the user. It represents the start of the card execution.
Now we need to add the Execute Action component and choose the appropriate Connection and Action. This component automatically reads the schema and builds an interactive form so you can associate variables with both inputs and outputs.
In our case, we’ll create a variable named users, in the current automation source, that will store the endpoint output.

1704

Figure 1 - Adding a variable

Building an Interface

Now we must print that information for the agent. Let’s use a Render View node to build the interface. The Render View node allows us to write HTML, CSS and Javascript.

As you can see in the documentation, you can print a variable using the method Context.getVariable(“var_name”).

To preview how an interface will be rendered, you can use the Card Preview functionality by clicking the button below the code editor. If your interface uses variables, you can mock values by using the Preview functionality in the side panel.

1704

Figure 2 - Mocking variables values for the Card Preview

You can test the card, using a preview value, by using this snippet:

<Text>{JSON.stringify(Context.getVariable("users"))}</Text>

❗️

All JavaScript code must be wrapped in curly braces.

The result should be something like this:

1704

Figure 3 - Testing a card using the Card Preview functionality

Ok! That’s something, but it isn’t really user-friendly!
So now, take advantage of your knowledge and of the available components to build a more user-friendly interface. Here’s the code we’ve used:

<Text>
   <table style={{borderCollapse: "collapse", width: "100%"}}>
       <tr>
           <td style={{padding: "8px"}}><strong>Name</strong></td>
           <td style={{padding: "8px"}}><strong>Email</strong></td>
           <td style={{padding: "8px"}}><strong>Zipcode</strong></td>
           <td style={{padding: "8px"}}><strong></strong></td>
       </tr>
       {Context.getVariable("users").map((el, i) => {
           var firstname = el.name.firstname.charAt(0).toUpperCase() + el.name.firstname.slice(1);
           var lastname = el.name.lastname.charAt(0).toUpperCase() + el.name.lastname.slice(1);
           var odd = i % 2 == 0;
           return (
           <tr style={{backgroundColor: odd ? "#f2f4f5" : ""}}>
               <td style={{padding: "8px"}}>{firstname} {lastname}</td>
               <td style={{padding: "8px"}}>{el.email}</td>
               <td style={{padding: "8px"}}>{el.address.zipcode}</td>
               <td style={{padding: "8px", textAlign: "right"}}><Button shape="compact" size="small" variation="outline">&#128722;</Button></td>
           </tr>
       )})}
   </table>
</Text>

Using the Preview, we get:

1704

Figure 4 - Previewing how the interface will look using mock values

Adding the Search Functionality

Ok that is cool, it lists all the users. But what if they’re a million? I think we need to have search functionality, right? Let’s do it!

You could build it within the same interface (Render View node). But this time we’ll take advantage of the Render Form to create an interactive form that asks for a name and saves it to a new variable called user_name.

1704

Figure 5 - Creating an interactive form using the Render Form

Since the list users endpoint does not allow for searching, we can use a Function component to manipulate the data outputted by the Execute Action component.

We won’t cover the technical details of the function, but in the following snippet we loop through the users and check if the searched term is contained in the first or last name.
We then save those who match into a new variable called filtered_users using the method Context.setVariable(“var_name”, value).

var users = Context.getVariable("users");
var user_name = Context.getVariable("user_name").toLowerCase();
var filtered_users = [];
users.map((el, i) => {
   if (el.name.firstname.includes(user_name) || el.name.lastname.includes(user_name)) {
       filtered_users.push(el);
   }
})


Context.setVariable("filtered_users", filtered_users)

Do not forget to update your interface to map via the filtered_users instead of users. It’s also mandatory to add exits to all component nodes. For testing purposes, feel free to add a simple Render View that shows an error/timeout message for the Execute Action and Function components.

Now the card will show a form asking for a name and then show a filtered list of users who match that name.

382

Figure 6 - Card showing a filtered list of users

As you can see, we added a shopping cart button on the table (Figure 6 - top right corner). In the next blog post, we’ll make this button interact with another card and show that specific user's shopping cart.
But that will be covered in the next blog post!

Final Thoughts and Looking Ahead

In this blog post we’ve covered how to build interactive forms, execute Connection Actions, manipulate data via a JavaScript function and build an interface. This allowed us to build a simple app for agents to search for users on an external system directly on Talkdesk.

You can download the card using this snippet:

{
  "id": "8442bb71-a6b2-4444-9a76-6d5447084ffb",
  "name": "Get user cart",
  "type": "WORKSPACE_DESIGNER_CARDS",
  "publish_state": "DEPLOYED",
  "last_updated": "2023-03-14T14:00:55.050700Z",
  "last_published": "2023-03-14T14:00:57.170028Z",
  "content": [
    {
      "id": "e7b8d85b-ef76-4c81-8a91-1c87458ad687",
      "name": "",
      "creation_index": 1,
      "nodes": [
        {
          "id": "881b6028-b5ff-4c64-b9d7-7bc21d6679ea",
          "type": "card-trigger",
          "position": {
            "x": -535,
            "y": 50
          },
          "data": {}
        },
        {
          "id": "d7cb8720-b4e7-45b1-ba7f-096f5af33ab4",
          "type": "execute-action",
          "namespace": "common",
          "position": {
            "x": -75,
            "y": 30
          },
          "data": {
            "configuration": {
              "connection_id": "6401c55a7b9b7d2de21a3904",
              "action_id": "64107da607a6b3619ae9e58c",
              "outputs": [
                {
                  "id": "64107da607a6b3619ae9e58c-34bf4910-987e-4268-a569-ef25e5a67484",
                  "name": "root",
                  "title": "Root",
                  "type": "array",
                  "required": false,
                  "variable": "0a405f1b-e114-413d-879e-7f438354316f",
                  "input_type": "variable"
                }
              ]
            }
          },
          "name": "Get users"
        },
        {
          "id": "0a4c2cc6-dcb1-45f3-9006-50134ec2cb49",
          "type": "render-view",
          "namespace": "workspace_designer_cards",
          "position": {
            "x": 345,
            "y": 15
          },
          "data": {
            "configuration": {
              "code": "<Text>\n    <table style={{borderCollapse: \"collapse\", width: \"100%\"}}>\n        <tr>\n            <td style={{padding: \"8px\"}}><strong>Name</strong></td>\n            <td style={{padding: \"8px\"}}><strong>Email</strong></td>\n            <td style={{padding: \"8px\"}}><strong>Zipcode</strong></td>\n            <td style={{padding: \"8px\"}}><strong></strong></td>\n        </tr>\n        {Context.getVariable(\"filtered_users\").map((el, i) => {\n            var firstname = el.name.firstname.charAt(0).toUpperCase() + el.name.firstname.slice(1);\n            var lastname = el.name.lastname.charAt(0).toUpperCase() + el.name.lastname.slice(1);\n            var odd = i % 2 == 0;\n            return (\n            <tr style={{backgroundColor: odd ? \"#f2f4f5\" : \"\"}}>\n                <td style={{padding: \"8px\"}}>{firstname} {lastname}</td>\n                <td style={{padding: \"8px\"}}>{el.email}</td>\n                <td style={{padding: \"8px\"}}>{el.address.zipcode}</td>\n                <td style={{padding: \"8px\", textAlign: \"right\"}}><Button shape=\"compact\" size=\"small\" variation=\"outline\">&#128722;</Button></td>\n            </tr>\n        )})}\n    </table>\n</Text>",
              "compiled_code": "PQKgxA+hAKCqBKBRKJgCh4FMCGBjALgHS4BOO+miANpgLaYB2+AFFnkQGInYDm9TAGgAEDAK5Uqw0JBgJkEVGwLEy2CtTqMWAFUwAPfMLESp4KHCQpgSoqXKUa-FgCJ82AEY1nwgN5ohQgDO+ACeNABcQn4BAe4A9iQAJpgkAMJxEtgADoGYkc64GVTZud7+MQDuAJaJ+AAW+QCMAAzNAKTO5QC+aF2mMhbyijjKdmoOmkzMriTeIuKSQtLmclY2KvYaTtP4iXPRQaERUeUBWdiJiVUMPPkAHFl6nQE9fUtmspYK1iO2quqOLTTYIkOI3ObGRbOABy2HozgAlAj+isvsN2Btxlsga49r5ysEwnkTjEhOdLtdbkJnA8nt1eijPkMfhixgDJi4QWCeBCFsJnIhaNgqlREcj3gNVt91myJttcfsCUdiQczhcrjd7o9nkJXozBmtfpj2fKueCjHzqQAtKpZQrJMX6qXo0b-OU43aKgKE46qsnqylaukvBkS1HMmVu7FTZxmnkWiRI8XpJj6Ig8TD4ABq2BIVQ8NGmADMRRQyIkIKJciRAojCEKssxmJhFlUEUIALwAPhJQgAbrmhCWa-gGHDMJ2hC3CGP6IRh8FZ5hiHVcwBBFjNBGEfBxWBZLIpVLYXLMdsAainVBn4-nVRHS8IgSoVVwmGYjQRAG5ygOSEJikXcdJ2nR9ANHW9cFXEgN2YLcdz3A8jxPd8LyvG853Ax9n1fd9Px-AI-yEOJLknKohDaIQACZOw7IRmgIoQyHwUQSAYMMmUNVko0BGN8FmfFSR9FVThidw8AAax4UFRAYRJ0ioBJIhIxIhAAfmpMAiyoosABYiwAVmcIR8h1EMXidNEWVdTZeJcT1BJiYTIj9NUKU1alaTM3VumEBcIPoflqWELDx3FZZOOlI1ZWjey8V7b1lRc0S3I1KkaW1USegs9C6GFKhwo+A0ou42yOR2eK-WchKYnJNKg287LdWEad1TIQJAkIAAvW17UwQrJSsyMyvlByauq1z-Xc9KvIEFKhAoAw1xfHgGHyPMeDqfBGt8jjipdP4RqBAAhUR8F3BhHO9VdD3yQpaHOAgyiEqouuJWMhQkZ6Yj-fN8CqMF8jiM6XwYTAdTeZwAB1RAAETuABmWGYdhxBYaosVv16JNvyAA",
              "exits": []
            }
          },
          "name": "view"
        },
        {
          "id": "289334b6-854f-437c-b7c2-1b098cee5c1c",
          "type": "render-form",
          "namespace": "workspace_designer_cards",
          "position": {
            "x": -330,
            "y": 30
          },
          "data": {
            "configuration": {
              "title": "Search users",
              "fields": [
                {
                  "id": "cf30527c-19ca-4f9b-8af5-07da29aff11b",
                  "label": "Name",
                  "value": "",
                  "type": "text",
                  "required": true,
                  "default_value": {
                    "id": "",
                    "input_type": "variable",
                    "variable": "",
                    "value": ""
                  },
                  "output": "40e24be7-e15d-4f81-b2b2-7df2b07fc0e9"
                }
              ]
            }
          },
          "name": "Ask for user name"
        },
        {
          "id": "caef4627-9792-4174-9e7c-32b199f10ded",
          "type": "function",
          "namespace": "common",
          "position": {
            "x": 135,
            "y": -15
          },
          "data": {
            "configuration": {
              "code": "var users = Context.getVariable(\"users\");\nvar user_name = Context.getVariable(\"user_name\").toLowerCase();\nvar filtered_users = [];\nusers.map((el, i) => {\n    if (el.name.firstname.includes(user_name) || el.name.lastname.includes(user_name)) {\n        filtered_users.push(el);\n    }\n})\n\nContext.setVariable(\"filtered_users\", filtered_users)"
            }
          },
          "name": "Filter users"
        }
      ],
      "edges": [
        {
          "id": "reactflow__edge-881b6028-b5ff-4c64-b9d7-7bc21d6679eatrigger-exit-289334b6-854f-437c-b7c2-1b098cee5c1crender-form-target",
          "source": "881b6028-b5ff-4c64-b9d7-7bc21d6679ea",
          "target": "289334b6-854f-437c-b7c2-1b098cee5c1c",
          "source_handle": "trigger-exit",
          "target_handle": "render-form-target",
          "type": "edge"
        },
        {
          "id": "reactflow__edge-289334b6-854f-437c-b7c2-1b098cee5c1crender-form-source-d7cb8720-b4e7-45b1-ba7f-096f5af33ab4target-1",
          "source": "289334b6-854f-437c-b7c2-1b098cee5c1c",
          "target": "d7cb8720-b4e7-45b1-ba7f-096f5af33ab4",
          "source_handle": "render-form-source",
          "target_handle": "target-1",
          "type": "edge"
        },
        {
          "id": "reactflow__edge-d7cb8720-b4e7-45b1-ba7f-096f5af33ab4success-caef4627-9792-4174-9e7c-32b199f10dedtarget-1",
          "source": "d7cb8720-b4e7-45b1-ba7f-096f5af33ab4",
          "target": "caef4627-9792-4174-9e7c-32b199f10ded",
          "source_handle": "success",
          "target_handle": "target-1",
          "type": "edge"
        },
        {
          "id": "reactflow__edge-d7cb8720-b4e7-45b1-ba7f-096f5af33ab4error-caef4627-9792-4174-9e7c-32b199f10dedtarget-1",
          "source": "d7cb8720-b4e7-45b1-ba7f-096f5af33ab4",
          "target": "caef4627-9792-4174-9e7c-32b199f10ded",
          "source_handle": "error",
          "target_handle": "target-1",
          "type": "edge"
        },
        {
          "id": "reactflow__edge-d7cb8720-b4e7-45b1-ba7f-096f5af33ab4timeout-caef4627-9792-4174-9e7c-32b199f10dedtarget-1",
          "source": "d7cb8720-b4e7-45b1-ba7f-096f5af33ab4",
          "target": "caef4627-9792-4174-9e7c-32b199f10ded",
          "source_handle": "timeout",
          "target_handle": "target-1",
          "type": "edge"
        },
        {
          "id": "reactflow__edge-caef4627-9792-4174-9e7c-32b199f10dedsuccess-0a4c2cc6-dcb1-45f3-9006-50134ec2cb49render-view-target",
          "source": "caef4627-9792-4174-9e7c-32b199f10ded",
          "target": "0a4c2cc6-dcb1-45f3-9006-50134ec2cb49",
          "source_handle": "success",
          "target_handle": "render-view-target",
          "type": "edge"
        },
        {
          "id": "reactflow__edge-caef4627-9792-4174-9e7c-32b199f10dederror-0a4c2cc6-dcb1-45f3-9006-50134ec2cb49render-view-target",
          "source": "caef4627-9792-4174-9e7c-32b199f10ded",
          "target": "0a4c2cc6-dcb1-45f3-9006-50134ec2cb49",
          "source_handle": "error",
          "target_handle": "render-view-target",
          "type": "edge"
        }
      ]
    }
  ],
  "metadata": {
    "type": "WORKSPACE_DESIGNER_CARDS",
    "variables": [
      {
        "id": "0a405f1b-e114-413d-879e-7f438354316f",
        "name": "users",
        "configuration": {
          "source": "currentAutomation",
          "path": ""
        },
        "type": "CUSTOM"
      },
      {
        "id": "40e24be7-e15d-4f81-b2b2-7df2b07fc0e9",
        "name": "user_name",
        "configuration": {
          "source": "currentAutomation",
          "path": ""
        },
        "type": "CUSTOM"
      },
      {
        "id": "b3eabdbb-49be-4c7c-88eb-5a56856b4ce4",
        "name": "filtered_users",
        "configuration": {
          "source": "currentAutomation",
          "path": ""
        },
        "type": "CUSTOM"
      },
      {
        "id": "80dfab71-0264-4a67-a460-a5150d8f744c",
        "name": "active_user_id",
        "descriptioni18n_key": "workspaceDesignerCards:system.variables.active_user_id",
        "type": "SYSTEM"
      },
      {
        "id": "c09d303b-4fb6-4038-806e-317e0938669f",
        "name": "active_user_email",
        "descriptioni18n_key": "workspaceDesignerCards:system.variables.active_user_email",
        "type": "SYSTEM"
      }
    ]
  },
  "schema_version": "1.0.0"
}

❗️

It isn’t possible to export Connections. This means that, after importing the card, you must edit it and associate your Connection/Action you’ve created using the Fake Store API.

What do you think could be improved? Maybe a button back to the form, or add the ability to also search by email?

Use our Talkdesk Community group to interact with other users and share use cases and ideas!

In the next blog post we’ll deep dive into variables, how cards can communicate between them and how to follow different paths depending on how the agents interact with the card.

Feel free to email me or send a message via the community group. See you next time!

João Moleiro
[email protected]