What this tool does update skills for agents on a Contact Center Cluster (Cisco UCCX). The tool reads a csv file called agents.csv from the current working directory. This file has a list of agents and their skills. The skills are updated using a REST API.
Note that UCCX allows a maximum of 400 agents, so that's why I use the sync API for reading a file and csv because I don't think it's going to be performance impacting.
This is the line where the agent skills are updated:
await axios.put(ccxURI, xml, config);
It is done sequentially. I originally wanted to do it concurrently using Promise.all but for some reason the UCCX server responds with a 500 error. So for now I'm doing it one at a time.
Code is below:
"use strict";
const ora = require("ora");
let throbber;
const inquirer = require("inquirer");
const prompts = [
{
name: "IP",
type: "input",
message: "Enter IP address of UCCX publisher: ",
},
{
name: "user",
type: "input",
message: "Enter username: ",
},
{
name: "pass",
type: "password",
message: "Enter password: ",
},
];
const axios = require("axios");
const https = require("https");
const fs = require("fs");
// csv sync api is used as there can be at most 400 agents on the cluster
// hence it should not be performance impacting
const parse = require("csv-parse/lib/sync");
const parseOptions = {
comment: "#",
skip_empty_lines: true,
trim: true
};
// csv file won"t exceed 1MB so readFileSync should be OK
const csv = fs.readFileSync("agents.csv", "utf-8");
const parser = require("xml2json");
(async () => {
console.log("Please verify agents.csv is in the current working directory\n");
const answers = await inquirer.prompt(prompts);
const IP = answers.IP;
const config = {
auth: {
username: answers.user,
password: answers.pass
},
httpsAgent: new https.Agent({
rejectUnauthorized: false
}),
headers: {"Content-Type": "application/xml"}
};
const XMLtemplate = `
<resource>
<self></self>
<userID></userID>
<firstName></firstName>
<lastName></lastName>
<extension></extension>
<alias></alias>
<resourceGroup></resourceGroup>
<skillMap>
<skillCompetency></skillCompetency>
</skillMap>
<autoAvailable>true</autoAvailable>
<type></type>
<team name="Default">
<refURL>${IP}/adminapi/team/1</refURL>
</team>
<primarySupervisorOf/>
<secondarySupervisorOf/>
</resource>`;
const resourceGroupMapping = {};
const resourceGroupURI = `https://${IP}/adminapi/resourceGroup`;
const resourceGroupList = await axios.get(resourceGroupURI, config);
throbber = ora("Updating").start();
for(const resourceGroup of resourceGroupList.data.resourceGroup) {
resourceGroupMapping[resourceGroup.name] = resourceGroup.id;
}
const skillMapping = {};
const skillURI = `https://${IP}/adminapi/skill`;
const skillList = await axios.get(skillURI, config);
for(const skill of skillList.data.skill) {
skillMapping[skill.skillName] = skill.skillId;
}
const teamMapping = {};
const teamURI = `https://${IP}/adminapi/team`;
const teamList = await axios.get(teamURI, config);
for(const team of teamList.data.team) {
teamMapping[team.teamname] = team.teamId;
}
// sample csv
// #id,firstName,lastName,extension,resourceGroup,team,skill1,competence1,skill2,competence2,skill3,competence3,skill4,competence4 ...
// Adrian_A,Adrian,Aldana,1008,1,Saturday-Lending,Lending,Pre_Approved_Loans,9,HELOC,6,Auto_and_Consumer_Loans,4,Loan_Status,7
const records = parse(csv, parseOptions);
const recordLength = records.length;
const XMLOptions = {
object: true,
reversible: true,
trim: true
};
for(let index = 0; index < recordLength; index++) {
let skillsAndCompetency;
let jsonObject;
let xml;
let ccxURI;
jsonObject = parser.toJson(XMLtemplate, XMLOptions);
jsonObject.resource.skillMap.skillCompetency = [];
jsonObject.resource.self = { "$t" : `https://${IP}/adminapi/resource/${records[index][0]}` };
jsonObject.resource.userID = { "$t" : `${records[index][0]}` };
jsonObject.resource.firstName = { "$t" : `${records[index][1]}` };
jsonObject.resource.lastName = { "$t" : `${records[index][2]}` };
jsonObject.resource.extension = { "$t" : `${records[index][3]}` };
// include resource type
jsonObject.resource.type = { "$t" : `${records[index][4]}` };
// update resource group
if(records[index][5]) {
jsonObject.resource.resourceGroup.name = `${records[index][5]}`;
jsonObject.resource.resourceGroup.refURL = { "$t" : `https://${IP}/adminapi/resourceGroup/${resourceGroupMapping[records[index][5]]}` };
}
// update team
if(records[index][6] !== "Default") {
jsonObject.resource.team.name = records[index][6];
jsonObject.resource.team.refURL = { "$t" : `https://${IP}/adminapi/team/${teamMapping[records[index][6]]}` };
}
// update all skills and competency
for(let j = 7; j < records[index].length; j += 2) {
skillsAndCompetency = {
competencelevel : "",
skillNameUriPair : {
name: "",
refURL : ""
}
};
skillsAndCompetency.competencelevel = { "$t" : `${records[index][j + 1]}` };
skillsAndCompetency.skillNameUriPair.name = `${records[index][j]}`;
skillsAndCompetency.skillNameUriPair.refURL = { "$t" : `https://${IP}/adminapi/skill/${skillMapping[records[index][j]]}` };
jsonObject.resource.skillMap.skillCompetency.push(skillsAndCompetency);
}
// xml payload
xml = `<?xml version="1.0" encoding="UTF-8"?>` + parser.toXml(JSON.stringify(jsonObject));
// update skills for the agents
ccxURI = `https://${IP}/adminapi/resource/${records[index][0]}`;
// if a row in the csv is incorrect, continue processing subsequent rows
try {
await axios.put(ccxURI, xml, config);
} catch (error) {
throbber.stop();
console.log(`\nCould not assign skills to ${records[index][0]}\n${error.response.data.apiError[0].errorMessage}\n`);
throbber.start();
}
}
})()
.catch(error => console.log(error.stack))
.then( () => throbber.stop());