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.
_171import java.io.IOException;_171import java.util.HashMap;_171import java.util.List;_171import java.util.Map;_171_171import javax.servlet.RequestDispatcher;_171import javax.servlet.ServletException;_171import javax.servlet.http.HttpServlet;_171import javax.servlet.http.HttpServletRequest;_171import javax.servlet.http.HttpServletResponse;_171_171import com.twilio.Twilio;_171import com.twilio.http.HttpMethod;_171import com.twilio.jwt.taskrouter.*;_171import com.twilio.rest.taskrouter.v1.workspace.Task;_171import com.twilio.rest.taskrouter.v1.workspace.task.Reservation;_171import com.twilio.twiml.*;_171import org.json.simple.JSONObject;_171_171public class TwilioTaskRouterServlet extends HttpServlet {_171_171 private String accountSid;_171 private String authToken;_171 private String workspaceSid;_171 private String workflowSid;_171_171 @Override_171 public void init() {_171 accountSid = this.getServletConfig().getInitParameter("AccountSid");_171 authToken = this.getServletConfig().getInitParameter("AuthToken");_171 workspaceSid = this.getServletConfig().getInitParameter("WorkspaceSid");_171 workflowSid = this.getServletConfig().getInitParameter("WorkflowSid");_171_171 Twilio.init(accountSid, authToken);_171 }_171_171 // service() responds to both GET and POST requests._171 // You can also use doGet() or doPost()_171 @Override_171 public void service(final HttpServletRequest request, final HttpServletResponse response)_171 throws IOException, ServletException {_171 if (request.getPathInfo() == null || request.getPathInfo().isEmpty()) {_171 return;_171 }_171_171 if (request.getPathInfo().equals("/assignment_callback")) {_171 response.setContentType("application/json");_171_171 final Map<String, String> dequeueInstruction = new HashMap<String, String>();_171 dequeueInstruction.put("instruction", "dequeue");_171 dequeueInstruction.put("from", "+15556667777");_171 dequeueInstruction.put("post_work_activity_sid", "WA0123401234...");_171_171 response.getWriter().print(JSONObject.toJSONString(dequeueInstruction));_171 } else if (request.getPathInfo().equals("/create_task")) {_171 response.setContentType("application/json");_171 final String taskAttributes = createTask();_171 response.getWriter().print(createTask());_171 } else if (request.getPathInfo().equals("/accept_reservation")) {_171 response.setContentType("application/json");_171 final String taskSid = request.getParameter("TaskSid");_171 final String reservationSid = request.getParameter("ReservationSid");_171 response.getWriter().print(acceptReservation(taskSid, reservationSid));_171 } else if (request.getPathInfo().equals("/incoming_call")) {_171 response.setContentType("application/xml");_171 response.getWriter().print(handleIncomingCall());_171 } else if (request.getPathInfo().equals("/enqueue_call")) {_171 response.setContentType("application/xml");_171 response.getWriter().print(enqueueTask());_171 } else if (request.getPathInfo().equals("/agents")) {_171 generateAgentView(request, response);_171 }_171 }_171_171 public String createTask() {_171 String attributes = "{\"selected_language\":\"es\"}";_171_171 Task task = Task.creator(workspaceSid, attributes, workflowSid).create();_171_171 return "{\"task_sid\":\"" + task.getSid() + "\"}";_171 }_171_171 public String acceptReservation(final String taskSid, final String reservationSid) {_171 Reservation reservation = Reservation.updater(workspaceSid, taskSid, reservationSid)_171 .setReservationStatus(Reservation.Status.ACCEPTED).update();_171_171 return "{\"worker_name\":\"" + reservation.getWorkerName() + "\"}";_171 }_171_171 public String handleIncomingCall() {_171 VoiceResponse twiml =_171 new VoiceResponse.Builder()_171 .gather(new Gather.Builder()_171 .say(new Say.Builder("Para Español oprime el uno.").language(Say.Language.ES)_171 .build())_171 .say(new Say.Builder("For English, please hold or press two.")_171 .language(Say.Language.EN).build())_171 .numDigits(1).timeout(5).build())_171 .build();_171_171 try {_171 return twiml.toXml();_171 } catch (TwiMLException e) {_171 return "Error creating TwiML: " + e.getMessage();_171 }_171 }_171_171 public String enqueueTask() {_171 com.twilio.twiml.Task task =_171 new com.twilio.twiml.Task.Builder().data("{\"selected_language\":\"es\"}").build();_171_171 EnqueueTask enqueue = new EnqueueTask.Builder(task).workflowSid(workflowSid).build();_171_171 VoiceResponse twiml = new VoiceResponse.Builder().enqueue(enqueue).build();_171_171 try {_171 return twiml.toXml();_171 } catch (TwiMLException e) {_171 return "Error creating TwiML: " + e.getMessage();_171 }_171 }_171_171 public void generateAgentView(final HttpServletRequest request, final HttpServletResponse response)_171 throws ServletException, IOException {_171 final String workerSid = request.getParameter("WorkerSid");_171_171 List<Policy> policies = PolicyUtils.defaultWorkerPolicies(workspaceSid, workerSid);_171_171 Map<String, FilterRequirement> activityUpdateFilter = new HashMap<>();_171 activityUpdateFilter.put("ActivitySid", FilterRequirement.REQUIRED);_171_171 Policy allowActivityUpdates = new Policy.Builder()_171 .url(UrlUtils.worker(workspaceSid, workerSid))_171 .method(HttpMethod.POST)_171 .postFilter(activityUpdateFilter).build();_171_171 Policy allowTasksUpdate = new Policy.Builder()_171 .url(UrlUtils.allTasks(workerSid))_171 .method(HttpMethod.POST)_171 .build();_171_171 Policy allowReservationUpdate = new Policy.Builder()_171 .url(UrlUtils.allReservations(workspaceSid, workerSid))_171 .method(HttpMethod.POST)_171 .build();_171_171 policies.add(allowActivityUpdates);_171 policies.add(allowTasksUpdate);_171 policies.add(allowReservationUpdate);_171_171_171 TaskRouterCapability.Builder capabilityBuilder =_171 new TaskRouterCapability.Builder(accountSid, authToken, workspaceSid, workerSid)_171 .policies(policies);_171_171 String token = capabilityBuilder.build().toJwt();_171_171 System.out.println(token);_171_171 // By default, tokens are good for one hour._171 // Override this default timeout by specifiying a new value (in seconds)._171 // For example, to generate a token good for 8 hours:_171 token = capabilityBuilder.build().toJwt();_171_171 // Forward the token information to a JSP view_171 response.setContentType("text/html");_171 request.setAttribute("worker_token", token);_171 final RequestDispatcher view = request.getServletContext().getRequestDispatcher("/agent.jsp");_171 view.forward(request, response);_171 }_171}
_19<?xml version="1.0" encoding="ISO-8859-1"?>_19<web-app xmlns="http://java.sun.com/xml/ns/j2ee"_19 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"_19 xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"_19 version="2.4">_19_19 <display-name>Twilio TaskRouter App</display-name>_19_19 <servlet>_19 <servlet-name>TwilioTaskRouterServlet</servlet-name>_19 <servlet-class>com.twilio.TwilioTaskRouterServlet</servlet-class>_19 </servlet>_19_19 <servlet-mapping>_19 <servlet-name>TwilioTaskRouterServlet</servlet-name>_19 <url-pattern>/taskrouter/*</url-pattern>_19 </servlet-mapping>_19_19</web-app>
Now create a JSP file that will be rendered when the URL is requested:
_141 <!DOCTYPE html>_141 <html>_141 <head>_141 <title>Customer Care - Voice Agent Screen</title>_141 <link rel="stylesheet" href="//media.twiliocdn.com/taskrouter/quickstart/agent.css"/>_141 <script src="https://sdk.twilio.com/js/taskrouter/v1.21/taskrouter.min.js" integrity="sha384-5fq+0qjayReAreRyHy38VpD3Gr9R2OYIzonwIkoGI4M9dhfKW6RWeRnZjfwSrpN8" crossorigin="anonymous"></script>_141 <script type="text/javascript">_141 /* Subscribe to a subset of the available TaskRouter.js events for a worker */_141 function registerTaskRouterCallbacks() {_141 worker.on('ready', function(worker) {_141 agentActivityChanged(worker.activityName);_141 logger("Successfully registered as: " + worker.friendlyName)_141 logger("Current activity is: " + worker.activityName);_141 });_141_141 worker.on('activity.update', function(worker) {_141 agentActivityChanged(worker.activityName);_141 logger("Worker activity changed to: " + worker.activityName);_141 });_141_141 worker.on("reservation.created", function(reservation) {_141 logger("-----");_141 logger("You have been reserved to handle a call!");_141 logger("Call from: " + reservation.task.attributes.from);_141 logger("Selected language: " + reservation.task.attributes.selected_language);_141 logger("-----");_141 });_141_141 worker.on("reservation.accepted", function(reservation) {_141 logger("Reservation " + reservation.sid + " accepted!");_141 });_141_141 worker.on("reservation.rejected", function(reservation) {_141 logger("Reservation " + reservation.sid + " rejected!");_141 });_141_141 worker.on("reservation.timeout", function(reservation) {_141 logger("Reservation " + reservation.sid + " timed out!");_141 });_141_141 worker.on("reservation.canceled", function(reservation) {_141 logger("Reservation " + reservation.sid + " canceled!");_141 });_141 }_141_141 /* Hook up the agent Activity buttons to TaskRouter.js */_141_141 function bindAgentActivityButtons() {_141 // Fetch the full list of available Activities from TaskRouter. Store each_141 // ActivitySid against the matching Friendly Name_141 var activitySids = {};_141 worker.activities.fetch(function(error, activityList) {_141 var activities = activityList.data;_141 var i = activities.length;_141 while (i--) {_141 activitySids[activities[i].friendlyName] = activities[i].sid;_141 }_141 });_141_141 /* For each button of class 'change-activity' in our Agent UI, look up the_141 ActivitySid corresponding to the Friendly Name in the button's next-activity_141 data attribute. Use Worker.js to transition the agent to that ActivitySid_141 when the button is clicked.*/_141 var elements = document.getElementsByClassName('change-activity');_141 var i = elements.length;_141 while (i--) {_141 elements[i].onclick = function() {_141 var nextActivity = this.dataset.nextActivity;_141 var nextActivitySid = activitySids[nextActivity];_141 worker.update({"ActivitySid":nextActivitySid});_141 }_141 }_141 }_141_141 /* Update the UI to reflect a change in Activity */_141_141 function agentActivityChanged(activity) {_141 hideAgentActivities();_141 showAgentActivity(activity);_141 }_141_141 function hideAgentActivities() {_141 var elements = document.getElementsByClassName('agent-activity');_141 var i = elements.length;_141 while (i--) {_141 elements[i].style.display = 'none';_141 }_141 }_141_141 function showAgentActivity(activity) {_141 activity = activity.toLowerCase();_141 var elements = document.getElementsByClassName(('agent-activity ' + activity));_141 elements.item(0).style.display = 'block';_141 }_141_141 /* Other stuff */_141_141 function logger(message) {_141 var log = document.getElementById('log');_141 log.value += "\n> " + message;_141 log.scrollTop = log.scrollHeight;_141 }_141_141 window.onload = function() {_141 // Initialize TaskRouter.js on page load using window.workerToken -_141 // a Twilio Capability token that was set from rendering the template with agents endpoint_141 logger("Initializing...");_141 window.worker = new Twilio.TaskRouter.Worker("${worker_token}");_141_141 registerTaskRouterCallbacks();_141 bindAgentActivityButtons();_141 };_141 </script>_141 </head>_141 <body>_141 <div class="content">_141 <section class="agent-activity offline">_141 <p class="activity">Offline</p>_141 <button class="change-activity" data-next-activity="Idle">Go Available</button>_141 </section>_141 <section class="agent-activity idle">_141 <p class="activity"><span>Available</span></p>_141 <button class="change-activity" data-next-activity="Offline">Go Offline</button>_141 </section>_141 <section class="agent-activity reserved">_141 <p class="activity">Reserved</p>_141 </section>_141 <section class="agent-activity busy">_141 <p class="activity">Busy</p>_141 </section>_141 <section class="agent-activity wrapup">_141 <p class="activity">Wrap-Up</p>_141 <button class="change-activity" data-next-activity="Idle">Go Available</button>_141 <button class="change-activity" data-next-activity="Offline">Go Offline</button>_141 </section>_141 <section class="log">_141 <textarea id="log" readonly="true"></textarea>_141 </section>_141 </div>_141 </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.