Build a Twilio Flex Plugin to Quickly Spot an Agent in Need

March 02, 2022
Written by
Reviewed by

Build a Twilio Flex Plugin to Quickly Spot an Agent in Need

In this blog post we will write a Twilio Flex custom plugin to let agents virtually raise their hand to ask a supervisor for help. Flex is Twilio's programmable contact centre that is enabling businesses to transform their communications with customers and streamline their operations by offering agents a single pane of glass for them to manage customer interactions.

Help! I need somebody

I know what you are thinking, The Beatles and Twilio? You would be surprised…

In times gone by we may have had a room full of agents who could flag a supervisor physically with a twist and shout or by raising their hand. Today, as more and more contact centres have embraced the remote or hybrid working models, the ways in which agents interact with their supervisors has evolved. Maybe even right now you have remote agents sending messages in Slack or Teams to get the attention of their supervisor.

The plugin that we will create here will use Twilio Sync under the hood to further enhance your agents' experience and give them the functionality to flag directly to their supervisor that they may need help with a customer interaction while ensuring that they don’t need to leave Flex to do so.

All you need is a Flex project

If you don’t have a Flex project, you can get one for free here, alternatively if you already have a Twilio account you can create a free flex project via the Twilio Console.

For the purposes of following along with this blog post you will require the Twilio CLI you can find instructions on how to install it by heading over to the Twilio CLI Quick Start guide.

So with our prerequisites done let’s create our template plugin using the Twilio CLI. Use this command to create the plugin:

twilio flex:plugins:create plugin-raise-hand --install --typescript

If you do not have the @twilo-labs/plugin-flex CLI plugin installed, you will be prompted to install this while running the command:

> twilio flex:plugins-create plugin-raise-hand --install --typescript

 » Plugin @twilio-labs/plugin-flex not installed for command "flex:plugins-create"

? Would you like to install the plugin? (y/N)

Enter y for the Flex plugin to install automatically, alternatively you can visit our Flex Plugins install guide.

This command creates a Flex plugin project for us, with a boilerplate component, the --install command will install all dependencies and the --typescript flag configures our project to use typescript.

So with that let’s create our custom component directory where we will write the code for this. Firstly lets move into our newly created plugin directory:

cd plugin-raise-hand

I like to leave the default CustomTaskList component that the template comes with in the folder structure until we have completed development of our component. This often comes in handy to refer back to.

And then create our component directory:

mkdir ./src/components/worker-hand  && cd $_

Create the files that are required:

touch worker-hand.tsx worker-hand.styles.ts worker-hand.container.ts

Copy the contents of this boilerplate worker-hand.container.ts file into your worker-hand.container.ts file.

Then copy the contents of this boilerplate worker-hand.styles.ts file into your worker-hand.styles.ts file.

Finally, copy the contents of this boilerplate worker-hand.tsx file into your worker-hand.ts file.

I wanna raise your hand

Now let’s get to writing some code for our agent’s virtual hand. We need an indicator when the agent’s hand is raised. To do this, we need to first make our component stateful. This can be achieved by defining an interface for our component’s state object, then defining a boolean property called isRaised inside our interface. When isRaised is true, that means an agent has their hand raised. Inside our worker-hand.tsx file:

interface WorkerHandState {
  isRaised: boolean;
}

Modifying our component class definition

export default class WorkerHand extends React.Component<Props, WorkerHandState> 

And creating a property in our WorkerHand class that implements our new interface within the workerHand class

readonly state: WorkerHandState = {
    isRaised: false,
 };

After we have done this, we can create our handlers that will update the isRaised state property of our WorkerHand component. Create these two methods above the render() method in the WorkerHand class:

 handleRaiseHand = async (e: MouseEvent<HTMLButtonElement>) => {
    e.stopPropagation();

    await this.setState({
      ...this.state,
      isRaised: true,
    });
  };

  handleLowerHand = async (e: MouseEvent<HTMLButtonElement>) => {
    e.stopPropagation();

    await this.setState({
      ...this.state,
      isRaised: false,
    });
  };

Then we will add to our render function, 2 icon buttons and depending on our state render either an outlined or a filled version of the icon. Replace the existing render() function with this code:

render() {
    return (
      <WorkerHandStyles>
        {this.state.isRaised ? (
          <StyledIconButton
            onClick={this.handleLowerHand}
            aria-label="lowereddHand"
          >
            <PanTool />
          </StyledIconButton>
        ) : (
          <StyledIconButton
            onClick={this.handleRaiseHand}
            aria-label="raisedHand"
          >
            <PanToolOutlined />
          </StyledIconButton>
        )}
      </WorkerHandStyles>
    );
  }

For this to work, we need to add some styles to our to our worker-hand.styles.ts, we can change that file as below:

import { default as styled } from "react-emotion";
import { IconButton } from "@material-ui/core";

export const WorkerHandStyles = styled("div")({});

export const StyledIconButton = styled(IconButton)({
  backgroundColor: "transparent !important"
});

Then to our worker-hand.tsx component file add a couple of imports below the imports that are already there.

import PanTool from "@material-ui/icons/PanTool";
import PanToolOutlined from "@material-ui/icons/PanToolOutlined";
import { StyledIconButton } from "./worker-hand.styles";
import { MouseEvent } from "react";

At this point, let’s import our new component to the RaiseHandPlugin.tsx, this file is created by the Twilio CLI in the src/ directory from the project root and contains the code that will setup and initialise our plugin.Firstly add the following import below the existing imports at the top of the file:

import WorkerHand from './components/worker-hand/worker-hand.container';

And then at the end of the init() function we can add our component to the flex UI

flex.MainHeader.Content.add(<WorkerHand key="worker-hand" />, {
      sortOrder: -1,
      align: 'end'
    });

At this point we can test out our component by using the following Twilio CLI command at the root of our project

twilio flex:plugins:start

And we can see our hand showing up on our flex interface. Yay!

Animated example of using the flex-raise-hand plugin, agent view

Here comes the Sync

Great! We have our hand available for the agent to raise, but we have two problems remaining: If the agent was to refresh their browser with their hand raised, it will be lowered again as the state isn’t persisted. Also there is no way that our supervisor could view the state of their team's hands.

We can fix this by using another one of Twilio’s awesome products, Twilio Sync. Sync enables us to persist our state in json document format in the cloud, and via Flex’s built in SDK connects via websockets to deliver state updates straight to the UI in real time. Flex already uses Sync so we do not have to set up anything new in order to use it.

To start, let's bring Flex’s built-in sync client into our worker-hand.tsx component, we can also add the code that will allow us to define an ID for a worker within Flex (this will come in handy later). We can do this by adding a Worker interface and modifying the components existing OwnProps interface as below:

interface Worker {
    sid : string;
}

interface OwnProps {
  worker: Worker;
  syncClient: SyncClient;
}

Not forgetting our imports at the top of the file, below the existing imports:

import { SyncClient } from "twilio-sync";

(See code in context here)

Then we can pass the attributes into the component where we add the component to the UI, that’s in our src\RaiseHandPlugin.tsx file. In this file replace the line you previously added to the end of the init() function with the code below:

flex.MainHeader.Content.add(<WorkerHand key="worker-hand" worker={manager.workerClient} syncClient={manager.insightsClient} />, {
      sortOrder: -1,
      align: 'end'
    });

(See code in context here)

From there now we can get to modifying our React component. Let’s use the passed in worker SID to create a unique sync document for this user and create a property for that inside our workerHand class

readonly workerHandStateDocName = `${this.props.worker.sid}-HandState`;

(See code in context here)

While we are there we can to modify the handleRaiseHand() and handleLowerHand() functions that we created earlier to include updating the twilio sync doc, we’ll also introduce a new function handleDocUpdate() this is just for code reuse:

handleDocUpdate = async (state: WorkerHandState) => {
    await this.setState({
      ...this.state,
      isRaised: state.isRaised,
    });
  };

  handleRaiseHand = async (e: MouseEvent<HTMLButtonElement>) => {
    e.stopPropagation();

    const syncDoc = await this.props.syncClient.document(
      this.workerHandStateDocName
    );
    const docState = syncDoc.value as WorkerHandState;
    docState.isRaised = true;

    syncDoc.update(docState);
  };

  handleLowerHand = async (e: MouseEvent<HTMLButtonElement>) => {
    e.stopPropagation();

    const syncDoc = await this.props.syncClient.document(
      this.workerHandStateDocName
    );
    const docState = syncDoc.value as WorkerHandState;

    docState.isRaised = false;

    syncDoc.update(docState);
  };

(See code in context here)

Importantly, we should add some code that initialises our sync document and updates the state of the component when it is loaded, for this React exposes to us the ComponentDidMount() hook we can add some code in there to get everything initialised. Add the following method to the workerHand class:

async componentDidMount() {
    const syncDoc = await this.props.syncClient.document(
      this.workerHandStateDocName
    );
    const docState = syncDoc.value as WorkerHandState;

    await this.handleDocUpdate(docState);

    syncDoc.on("updated", async (event) => {
      await this.handleDocUpdate(event.value as WorkerHandState);
    });
  }

(See code in context here)

This code retrieves a Sync document with the given name, or will create it if it doesn’t exist. Then it updates the component's internal state with that information and finally subscribes to the Sync document's “updated” event where it will trigger an update of the internal state each time something new is pushed to the Sync document.

Let’s once again, give it a try and ensure that everything still works by running the start command at the root of our project.

twilio flex:plugins:start

It’s worth noting at this point, nothing visually should change from what you had previously, however we have added functionality to our plugin that means the state of our workers hand is persisted and can be shared, Awesome!

Get by with a little help from my supervisor

I hear you, updating our component to persist the state in the cloud is great, but if our component looks and works exactly the same then what else do we need to do?

This is where the super power of React components and the Twilio Flex plugin architecture come in. We can re-use our component that we created on the supervisors overview screen. Try adding this line into the RaiseHandPlugin.tsx files init() function:

flex.WorkersDataTable.Content.add(
      <ColumnDefinition
        key="agent-hand-custom"
        header={""}
        content={(item) => (
          <WorkerHand
            key={`worker-${item.worker.sid}-hand`}
            worker={item.worker}
            syncClient={manager.insightsClient}
          />
        )}
      />,
      { sortOrder: 0 }
    );

Requiring an import for ColumnDefinition,  which is exposed by the flex-ui package, this import can be added to the top of the file just after the existing imports:

import { ColumnDefinition } from "@twilio/flex-ui";

(See code in context here)

Give it a test again by using twilio CLI at the root of the project

twilio plugins:flex:start

Head over to the Teams tab too, you should be able to see each of the workers in your flex instance and the status of their hands, from here supervisors can also lower the agent's hand for them if they want to just let it be.

We can tidy up our plugin and remove the default CustomTaskList boilerplate that comes with a new flex plugin

Come together

When all of the elements we worked on above come together, agents will not have to leave flex to flag a supervisor for help and supervisors will be working in a single place to monitor agent activity and react to any agent calls for help.

Animated example of using the flex-raise-hand plugin, supervisor view

Can you think of any more features to add to this? Maybe use Flex Actions Framework to lower peoples hand when they log in/out, or Flex Notifications Framework to grab the attention of a supervisor when they are away from the Teams view.

We can’t wait to see what you build!

Jordan Hanley Bio

Jordan is a Senior Solutions Engineer helping Twilio customers in the UK and Ireland to unlock the power of Twilio API’s and Software. Jordan loves talking about software, his family, his dogs and Formula 1. Oh, and maybe I am more of a fan of The Beatles than I thought… Get in touch with me by email jhanley@twilio.com

[Header image photo by Camylla Battani on Unsplash]