0
votes

I'm facing a "runtime application timed out" error whenever I try to filter TaskQueues. Here is the complete scenario.

On the twilio flex contact pad, I have to show cumulative count of pending tasks from all the task queues, that the agent belongs to. For this, I have written a twilio function which takes the worker skills as input and filters TaskQueues based on skills supplied, and count the pending tasks. I followed the twilio documentation provided here. I tried different ways to write the expression, but all are eventually resulting in time out error.

The worker attributes are defined as below

{
  "routing": {
    "skills": [
      "German",
      "English",
      "French"
    ],
    "domains": [
      "ABC"
    ],
    "categories": [
      "XYZ"
    ],
    "levels": {
      "XYZ": 34
    },
    "platforms": [
      "Platform1"
    ],
    "provider": "Provider_1"
  },
  "full_name": "XXXXXXX",
  "image_url": "https:\/\/www.avatar.co\/avatar\/91f0e496?d=mp",
  "roles": [
    "admin",
    "wfo.full_access"
  ],
  "contact_uri": "client:XXX.YYYY",
  "disabled_skills": {
    "skills": [],
    "levels": {}
  },
  "email": "[email protected]"
}

Below the code snippet to filter the Queues.

const TokenValidator = require('twilio-flex-token-validator').functionValidator;

const WORKSPACE_ID = 'WSXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
const workerSkills = ['English','French'];
console.log('SKILLS IN FUNC : ' + `routing.skills IN [${workerSkills}]`);
const getQueues = (client) => {
    return new Promise ((resolve, reject) => {
        client.taskrouter.v1.workspaces(WORKSPACE_ID)
        .taskQueues 
        .list({evaluateWorkerAttributes:'{"routing.skills" : "${workerSkills}" }',
        //.list({evaluateWorkerAttributes:"routing.skills HAS 'French'",
            limit: 1000
        })
        .then(taskQueues => resolve(taskQueues))
    });
}

const getQueueRealTimeStatistics = (client, queueID) => {
    return new Promise ((resolve, reject) => {
        client.taskrouter.v1.workspaces(WORKSPACE_ID)
        .taskQueues(queueID)
        .realTimeStatistics()
        .fetch()
        .then(realTimeStatistics => resolve(realTimeStatistics))
    });
}

exports.handler = function(context, event, callback) {
    const client = context.getTwilioClient();
    let pendingTasks = 0;
    const promises = [];
    const response = new Twilio.Response();
    response.appendHeader('Access-Control-Allow-Origin', '*');
    response.appendHeader('Access-Control-Allow-Methods', 'OPTIONS POST GET');
    response.appendHeader('Access-Control-Allow-Headers', 'Content-Type');
    response.appendHeader('Content-Type', 'application/json');
    
    getQueues(client).then(res => res.forEach(x => {
        promises.push(getQueueRealTimeStatistics(client, x.sid));
        console.log('queueID : ' + x.sid);
    })).then(x => {
        Promise.all(promises).then(res => {
            
        res.forEach(y => {
            pendingTasks = pendingTasks+ parseInt(y.tasksByStatus.pending,10);
        })
           //response.setBody(pendingTasks);
           response.setBody(pendingTasks + parseInt(res, 10));
        })
        .then(x => callback(null, response))
        .catch(err => {
            console.log(err.message);
            response.appendHeader('Content-Type', 'plain/text');
            response.setBody(err.message);
            response.setStatusCode(500);
            // If there's an error, send an error response
            // Keep using the response object for CORS purposes
            callback(null, response);
        });  
    })
};

Can someone help to resolve this issue. Thanks in advance.

1
How do you return data from your Twilio Function? Can you share the entire Function code? - philnash
Edited the original post with complete function - Ashok Pedapati
Where? Please update the question with more code. Thanks! - philnash
Just added the code to the Question - Ashok Pedapati

1 Answers

0
votes

Twilio developer evangelist here.

It looks like your function is written mostly correctly and should return. There's two things I can think of that might be going wrong.

It really is timing out

I'm not sure how many queues you have, but getting all the queues and then getting all the statistics for each queue could just be pushing your Function over the 10 second limit for Function execution. If that is the case, then may have to break up the Function so that it can run within the time. Are you able to run this locally (using the Twilio Serverless Toolkit) and it succeed?

There's an error that is not being caught and the promises don't resolve

Your code looks right to me, but the way you wrap the results of API calls in a new promise means that if there is an error the wrapper promise never resolves or rejects. This would mean that the Function hangs waiting for a result until the timeout occurs.

Thing is, you don't need to wrap the API calls in a new Promise, they already return Promises.

I would try rewriting it like this, avoiding new Promise wrappers and then seeing if there is an error that you aren't catching. You can also avoid pushing promises into an array and jump straight into the Promise.all too:

const TokenValidator = require("twilio-flex-token-validator").functionValidator;

const WORKSPACE_ID = "WSXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
const workerSkills = ["English", "French"];
console.log("SKILLS IN FUNC : " + `routing.skills IN [${workerSkills}]`);
const getQueues = (client) => {
  return client.taskrouter.v1.workspaces(WORKSPACE_ID).taskQueues.list({
    evaluateWorkerAttributes: '{"routing.skills" : "${workerSkills}" }',
    //.list({evaluateWorkerAttributes:"routing.skills HAS 'French'",
    limit: 1000,
  });
};

const getQueueRealTimeStatistics = (client, queueID) => {
  return client.taskrouter.v1
    .workspaces(WORKSPACE_ID)
    .taskQueues(queueID)
    .realTimeStatistics()
    .fetch();
};

exports.handler = function (context, event, callback) {
  const client = context.getTwilioClient();
  let pendingTasks = 0;
  const response = new Twilio.Response();
  response.appendHeader("Access-Control-Allow-Origin", "*");
  response.appendHeader("Access-Control-Allow-Methods", "OPTIONS POST GET");
  response.appendHeader("Access-Control-Allow-Headers", "Content-Type");
  response.appendHeader("Content-Type", "application/json");

  getQueues(client)
    .then((queues) => {
      return Promise.all(
        queues.map((queue) => {
          console.log("queueID : " + queue.sid);
          return getQueueRealTimeStatistics(client, queue.sid);
        })
      );
    })
    .then((queueStats) => {
      queueStats.forEach((queueStat) => {
        pendingTasks = pendingTasks + parseInt(queueStat.tasksByStatus.pending, 10);
      });
      response.setBody({ pendingTasks });
    })
    .then(() => callback(null, response))
    .catch((err) => {
      console.log(err.message);
      response.appendHeader("Content-Type", "plain/text");
      response.setBody(err.message);
      response.setStatusCode(500);
      // If there's an error, send an error response
      // Keep using the response object for CORS purposes
      callback(null, response);
    });
};

Doing it this way cuts down some of the nesting too. Let me know if it helps.