Let's get started on our agent UI. Assuming you've followed the conventions so far in this tutorial, the UI we create will be accessible using your web browser at:
http://localhost:8080/agents?WorkerSid=WK01234012340123401234
(substitute your Alice's WorkerSid)
We pass the WorkerSid in the URL to avoid implementing complex user management in our demo. In reality, you are likely to store a user's WorkerSid in your database alongside other User attributes.
Let's add on our TwilioTaskRouterServlet
file to add an endpoint to generate a page based on a template.
1import java.io.IOException;2import java.util.HashMap;3import java.util.List;4import java.util.Map;56import javax.servlet.RequestDispatcher;7import javax.servlet.ServletException;8import javax.servlet.http.HttpServlet;9import javax.servlet.http.HttpServletRequest;10import javax.servlet.http.HttpServletResponse;1112import com.twilio.Twilio;13import com.twilio.http.HttpMethod;14import com.twilio.jwt.taskrouter.*;15import com.twilio.rest.taskrouter.v1.workspace.Task;16import com.twilio.rest.taskrouter.v1.workspace.task.Reservation;17import com.twilio.twiml.*;18import org.json.simple.JSONObject;1920public class TwilioTaskRouterServlet extends HttpServlet {2122private String accountSid;23private String authToken;24private String workspaceSid;25private String workflowSid;2627@Override28public void init() {29accountSid = this.getServletConfig().getInitParameter("AccountSid");30authToken = this.getServletConfig().getInitParameter("AuthToken");31workspaceSid = this.getServletConfig().getInitParameter("WorkspaceSid");32workflowSid = this.getServletConfig().getInitParameter("WorkflowSid");3334Twilio.init(accountSid, authToken);35}3637// service() responds to both GET and POST requests.38// You can also use doGet() or doPost()39@Override40public void service(final HttpServletRequest request, final HttpServletResponse response)41throws IOException, ServletException {42if (request.getPathInfo() == null || request.getPathInfo().isEmpty()) {43return;44}4546if (request.getPathInfo().equals("/assignment_callback")) {47response.setContentType("application/json");4849final Map<String, String> dequeueInstruction = new HashMap<String, String>();50dequeueInstruction.put("instruction", "dequeue");51dequeueInstruction.put("from", "+15556667777");52dequeueInstruction.put("post_work_activity_sid", "WA0123401234...");5354response.getWriter().print(JSONObject.toJSONString(dequeueInstruction));55} else if (request.getPathInfo().equals("/create_task")) {56response.setContentType("application/json");57final String taskAttributes = createTask();58response.getWriter().print(createTask());59} else if (request.getPathInfo().equals("/accept_reservation")) {60response.setContentType("application/json");61final String taskSid = request.getParameter("TaskSid");62final String reservationSid = request.getParameter("ReservationSid");63response.getWriter().print(acceptReservation(taskSid, reservationSid));64} else if (request.getPathInfo().equals("/incoming_call")) {65response.setContentType("application/xml");66response.getWriter().print(handleIncomingCall());67} else if (request.getPathInfo().equals("/enqueue_call")) {68response.setContentType("application/xml");69response.getWriter().print(enqueueTask());70} else if (request.getPathInfo().equals("/agents")) {71generateAgentView(request, response);72}73}7475public String createTask() {76String attributes = "{\"selected_language\":\"es\"}";7778Task task = Task.creator(workspaceSid, attributes, workflowSid).create();7980return "{\"task_sid\":\"" + task.getSid() + "\"}";81}8283public String acceptReservation(final String taskSid, final String reservationSid) {84Reservation reservation = Reservation.updater(workspaceSid, taskSid, reservationSid)85.setReservationStatus(Reservation.Status.ACCEPTED).update();8687return "{\"worker_name\":\"" + reservation.getWorkerName() + "\"}";88}8990public String handleIncomingCall() {91VoiceResponse twiml =92new VoiceResponse.Builder()93.gather(new Gather.Builder()94.say(new Say.Builder("Para Español oprime el uno.").language(Say.Language.ES)95.build())96.say(new Say.Builder("For English, please hold or press two.")97.language(Say.Language.EN).build())98.numDigits(1).timeout(5).build())99.build();100101try {102return twiml.toXml();103} catch (TwiMLException e) {104return "Error creating TwiML: " + e.getMessage();105}106}107108public String enqueueTask() {109com.twilio.twiml.Task task =110new com.twilio.twiml.Task.Builder().data("{\"selected_language\":\"es\"}").build();111112EnqueueTask enqueue = new EnqueueTask.Builder(task).workflowSid(workflowSid).build();113114VoiceResponse twiml = new VoiceResponse.Builder().enqueue(enqueue).build();115116try {117return twiml.toXml();118} catch (TwiMLException e) {119return "Error creating TwiML: " + e.getMessage();120}121}122123public void generateAgentView(final HttpServletRequest request, final HttpServletResponse response)124throws ServletException, IOException {125final String workerSid = request.getParameter("WorkerSid");126127List<Policy> policies = PolicyUtils.defaultWorkerPolicies(workspaceSid, workerSid);128129Map<String, FilterRequirement> activityUpdateFilter = new HashMap<>();130activityUpdateFilter.put("ActivitySid", FilterRequirement.REQUIRED);131132Policy allowActivityUpdates = new Policy.Builder()133.url(UrlUtils.worker(workspaceSid, workerSid))134.method(HttpMethod.POST)135.postFilter(activityUpdateFilter).build();136137Policy allowTasksUpdate = new Policy.Builder()138.url(UrlUtils.allTasks(workerSid))139.method(HttpMethod.POST)140.build();141142Policy allowReservationUpdate = new Policy.Builder()143.url(UrlUtils.allReservations(workspaceSid, workerSid))144.method(HttpMethod.POST)145.build();146147policies.add(allowActivityUpdates);148policies.add(allowTasksUpdate);149policies.add(allowReservationUpdate);150151152TaskRouterCapability.Builder capabilityBuilder =153new TaskRouterCapability.Builder(accountSid, authToken, workspaceSid, workerSid)154.policies(policies);155156String token = capabilityBuilder.build().toJwt();157158System.out.println(token);159160// By default, tokens are good for one hour.161// Override this default timeout by specifiying a new value (in seconds).162// For example, to generate a token good for 8 hours:163token = capabilityBuilder.build().toJwt();164165// Forward the token information to a JSP view166response.setContentType("text/html");167request.setAttribute("worker_token", token);168final RequestDispatcher view = request.getServletContext().getRequestDispatcher("/agent.jsp");169view.forward(request, response);170}171}
1<?xml version="1.0" encoding="ISO-8859-1"?>2<web-app xmlns="http://java.sun.com/xml/ns/j2ee"3xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"4xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"5version="2.4">67<display-name>Twilio TaskRouter App</display-name>89<servlet>10<servlet-name>TwilioTaskRouterServlet</servlet-name>11<servlet-class>com.twilio.TwilioTaskRouterServlet</servlet-class>12</servlet>1314<servlet-mapping>15<servlet-name>TwilioTaskRouterServlet</servlet-name>16<url-pattern>/taskrouter/*</url-pattern>17</servlet-mapping>1819</web-app>
Now create a JSP file that will be rendered when the URL is requested:
1<!DOCTYPE html>2<html>3<head>4<title>Customer Care - Voice Agent Screen</title>5<link rel="stylesheet" href="//media.twiliocdn.com/taskrouter/quickstart/agent.css"/>6<script src="https://sdk.twilio.com/js/taskrouter/v1.21/taskrouter.min.js" integrity="sha384-5fq+0qjayReAreRyHy38VpD3Gr9R2OYIzonwIkoGI4M9dhfKW6RWeRnZjfwSrpN8" crossorigin="anonymous"></script>7<script type="text/javascript">8/* Subscribe to a subset of the available TaskRouter.js events for a worker */9function registerTaskRouterCallbacks() {10worker.on('ready', function(worker) {11agentActivityChanged(worker.activityName);12logger("Successfully registered as: " + worker.friendlyName)13logger("Current activity is: " + worker.activityName);14});1516worker.on('activity.update', function(worker) {17agentActivityChanged(worker.activityName);18logger("Worker activity changed to: " + worker.activityName);19});2021worker.on("reservation.created", function(reservation) {22logger("-----");23logger("You have been reserved to handle a call!");24logger("Call from: " + reservation.task.attributes.from);25logger("Selected language: " + reservation.task.attributes.selected_language);26logger("-----");27});2829worker.on("reservation.accepted", function(reservation) {30logger("Reservation " + reservation.sid + " accepted!");31});3233worker.on("reservation.rejected", function(reservation) {34logger("Reservation " + reservation.sid + " rejected!");35});3637worker.on("reservation.timeout", function(reservation) {38logger("Reservation " + reservation.sid + " timed out!");39});4041worker.on("reservation.canceled", function(reservation) {42logger("Reservation " + reservation.sid + " canceled!");43});44}4546/* Hook up the agent Activity buttons to TaskRouter.js */4748function bindAgentActivityButtons() {49// Fetch the full list of available Activities from TaskRouter. Store each50// ActivitySid against the matching Friendly Name51var activitySids = {};52worker.activities.fetch(function(error, activityList) {53var activities = activityList.data;54var i = activities.length;55while (i--) {56activitySids[activities[i].friendlyName] = activities[i].sid;57}58});5960/* For each button of class 'change-activity' in our Agent UI, look up the61ActivitySid corresponding to the Friendly Name in the button's next-activity62data attribute. Use Worker.js to transition the agent to that ActivitySid63when the button is clicked.*/64var elements = document.getElementsByClassName('change-activity');65var i = elements.length;66while (i--) {67elements[i].onclick = function() {68var nextActivity = this.dataset.nextActivity;69var nextActivitySid = activitySids[nextActivity];70worker.update({"ActivitySid":nextActivitySid});71}72}73}7475/* Update the UI to reflect a change in Activity */7677function agentActivityChanged(activity) {78hideAgentActivities();79showAgentActivity(activity);80}8182function hideAgentActivities() {83var elements = document.getElementsByClassName('agent-activity');84var i = elements.length;85while (i--) {86elements[i].style.display = 'none';87}88}8990function showAgentActivity(activity) {91activity = activity.toLowerCase();92var elements = document.getElementsByClassName(('agent-activity ' + activity));93elements.item(0).style.display = 'block';94}9596/* Other stuff */9798function logger(message) {99var log = document.getElementById('log');100log.value += "\n> " + message;101log.scrollTop = log.scrollHeight;102}103104window.onload = function() {105// Initialize TaskRouter.js on page load using window.workerToken -106// a Twilio Capability token that was set from rendering the template with agents endpoint107logger("Initializing...");108window.worker = new Twilio.TaskRouter.Worker("${worker_token}");109110registerTaskRouterCallbacks();111bindAgentActivityButtons();112};113</script>114</head>115<body>116<div class="content">117<section class="agent-activity offline">118<p class="activity">Offline</p>119<button class="change-activity" data-next-activity="Idle">Go Available</button>120</section>121<section class="agent-activity idle">122<p class="activity"><span>Available</span></p>123<button class="change-activity" data-next-activity="Offline">Go Offline</button>124</section>125<section class="agent-activity reserved">126<p class="activity">Reserved</p>127</section>128<section class="agent-activity busy">129<p class="activity">Busy</p>130</section>131<section class="agent-activity wrapup">132<p class="activity">Wrap-Up</p>133<button class="change-activity" data-next-activity="Idle">Go Available</button>134<button class="change-activity" data-next-activity="Offline">Go Offline</button>135</section>136<section class="log">137<textarea id="log" readonly="true"></textarea>138</section>139</div>140</body>141</html>
You'll notice that we included two external files:
And that's it! Compile your Java class and start your server.
Open http://yourserver.com/<path-to-servlet>/agents?WorkerSid=WK012340123401234
in your browser and you should see the screen below. If you make the same phone call as we made in Part 3, you should see Alice's Activity transition on screen as she is reserved and assigned to handle the Task.
If you see "Initializing..." and no progress, make sure that you have included the correct WorkerSid in the "WorkerSid" request parameter of the URL.
For more details, refer to the TaskRouter JavaScript SDK documentation.