Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(plugin-cc): added-call-control #4015

Merged
merged 46 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
1122f07
feat(plugin-cc): call accept implemented
Kesari3008 Nov 26, 2024
acba03a
feat(plugin-cc): latest merge
Kesari3008 Nov 26, 2024
39a55a4
feat(plugin-cc): added ut, added sample app changes
Kesari3008 Nov 26, 2024
616d973
feat(plugin-cc): fixes in code
Kesari3008 Nov 26, 2024
555289d
feat(plugin-cc): manual testing for call answer and decline finished
Kesari3008 Nov 27, 2024
3754f66
feat(plugin-cc): small fixes in sample app
Kesari3008 Nov 28, 2024
d3809b2
feat(plugin-cc): fixed ut
Kesari3008 Nov 28, 2024
9b40fde
feat(plugin-cc): fixed ut
Kesari3008 Nov 28, 2024
e756062
feat(plugin-cc): ut fix
Kesari3008 Dec 1, 2024
cfe40f6
feat(plugin-cc): review comments
Kesari3008 Dec 2, 2024
963fa03
feat(plugin-cc): ut fixes
Kesari3008 Dec 2, 2024
37ec111
feat(plugin-cc): ut fixes
Kesari3008 Dec 2, 2024
e8dcefd
feat(plugin-cc): merge conflict
Kesari3008 Dec 2, 2024
c9f2e2c
feat(plugin-cc): review comments thrid round
Kesari3008 Dec 3, 2024
2d3b7ee
feat(plugin-cc): sample app fixes
Kesari3008 Dec 3, 2024
cc99ac8
feat(plugin-cc): test file import fixes
Kesari3008 Dec 3, 2024
4ac05c2
feat(cc-sdk): added-call-control
adhmenon Dec 3, 2024
ac450ed
fix(plugin-cc): rename folders
sreenara Dec 3, 2024
8f6563f
feat(cc-sdk): added-log-changes
adhmenon Dec 4, 2024
064328f
feat(cc-sdk): fixed-sample-app-issue
adhmenon Dec 4, 2024
8917ab4
feat(cc-sdk): added-wrapup-dropdown
adhmenon Dec 4, 2024
5b290c5
Merge branch 'SPARK-562169-Task-API-Implementation' of https://github…
adhmenon Dec 4, 2024
ee6908a
feat(plugin-cc): review comments
Kesari3008 Dec 4, 2024
847f0cc
Merge branch 'SPARK-562169-Task-API-Implementation' of github.com:Kes…
Kesari3008 Dec 4, 2024
338202d
feat(cc-sdk): fixed-renaming-issue
adhmenon Dec 4, 2024
f381fab
fix(plugin-cc): ut-fixes
Kesari3008 Dec 4, 2024
890e2be
Merge branch 'SPARK-562169-Task-API-Implementation' of https://github…
adhmenon Dec 4, 2024
4c6a2bc
fix(plugin-cc): ut added for off listeners
Kesari3008 Dec 4, 2024
fa4c42e
feat(plugin-cc): added types and add ut for cc file
Kesari3008 Dec 4, 2024
d65c4fd
Merge branch 'SPARK-562169-Task-API-Implementation' of https://github…
adhmenon Dec 5, 2024
352315f
feat(cc-sdk): fixed-pipeline-fix
adhmenon Dec 5, 2024
93dc3d0
feat(plugin-cc): updated logs
Kesari3008 Dec 6, 2024
a0d93ca
feat(cc-sdk): added-recording-pause-and-resume
adhmenon Dec 6, 2024
098948e
feat(cc-sdk): added-task-response-unit-tests
adhmenon Dec 6, 2024
2f154e3
feat(cc-sdk): added-end-call-edge-case
adhmenon Dec 6, 2024
1f2cee9
Merge branch 'SPARK-562169-Task-API-Implementation' of https://github…
adhmenon Dec 6, 2024
daeb3e5
feat(cc-sdk): added-cleanup-on-call-wrapup
adhmenon Dec 6, 2024
7988048
feat(plugin-cc): fixed the issue with incoming call being received wi…
Kesari3008 Dec 6, 2024
441c992
Merge branch 'SPARK-562169-Task-API-Implementation' of https://github…
adhmenon Dec 6, 2024
05b9ace
feat(cc-sdk): added-tests-for-edge-cases
adhmenon Dec 6, 2024
1d7a016
fix(plugin-cc): review comments
Kesari3008 Dec 11, 2024
f058339
Merge branch 'SPARK-562169-Task-API-Implementation' of https://github…
adhmenon Dec 11, 2024
57f71e1
fix(plugin-cc): review comments
Kesari3008 Dec 11, 2024
65cd8fe
Merge branch 'SPARK-562169-Task-API-Implementation' of https://github…
adhmenon Dec 11, 2024
83c4345
Merge branch 'wxcc' of https://github.com/webex/webex-js-sdk into dev…
adhmenon Dec 12, 2024
1f56afa
feat(wxcc): fixed-merge-conflicts
adhmenon Dec 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
193 changes: 178 additions & 15 deletions docs/samples/contact-center/app.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,3 @@
/* eslint-disable no-underscore-dangle */
/* eslint-env browser */

/* global Webex */

/* eslint-disable require-jsdoc */
/* eslint-disable no-unused-vars */
/* eslint-disable no-console */
/* eslint-disable no-global-assign */
/* eslint-disable no-multi-assign */
/* eslint-disable max-len */

// Globals
let webex;
let sdk;
Expand All @@ -18,6 +6,10 @@ let deviceId;
let agentStatusId;
let agentStatus;
let agentId;
let taskControl;
let task;
let taskId;
let wrapupCodes = []; // Add this to store wrapup codes

const authTypeElm = document.querySelector('#auth-type');
const credentialsFormElm = document.querySelector('#credentials');
Expand All @@ -27,13 +19,24 @@ const authStatusElm = document.querySelector('#access-token-status');
const registerBtn = document.querySelector('#webexcc-register');
const teamsDropdown = document.querySelector('#teamsDropdown');
const agentLogin = document.querySelector('#AgentLogin');
const agentLoginButton = document.querySelector('#loginAgent');
const loginAgentElm = document.querySelector('#loginAgent');
const dialNumber = document.querySelector('#dialNumber');
const registerStatus = document.querySelector('#ws-connection-status');
const idleCodesDropdown = document.querySelector('#idleCodesDropdown')
const setAgentStatusButton = document.querySelector('#setAgentStatus');
const logoutAgentElm = document.querySelector('#logoutAgent');
const buddyAgentsDropdownElm = document.getElementById('buddyAgentsDropdown');
const incomingCallListener = document.querySelector('#incomingsection');
const incomingDetailsElm = document.querySelector('#incoming-call');
const answerElm = document.querySelector('#answer');
const declineElm = document.querySelector('#decline');
const callControlListener = document.querySelector('#callcontrolsection');
const holdResumeElm = document.querySelector('#hold-resume');
const pauseResumeRecordingElm = document.querySelector('#pause-resume-recording');
const endElm = document.querySelector('#end');
const wrapupElm = document.querySelector('#wrapup');
const wrapupCodesDropdownElm = document.querySelector('#wrapupCodesDropdown');
const autoResumeCheckboxElm = document.querySelector('#auto-resume-checkbox'); // Add this
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will now store the entire list of codes using this dropdown and populate the request accordingly.


// Store and Grab `access-token` from sessionStorage
if (sessionStorage.getItem('date') > new Date().getTime()) {
Expand Down Expand Up @@ -70,6 +73,40 @@ function toggleDisplay(elementId, status) {
}
}

const taskEvents = new CustomEvent('task:incoming', {
detail: {
task: task,
},
});

function updateButtonsPostEndCall() {
holdResumeElm.disabled = true;
endElm.disabled = true;
pauseResumeRecordingElm.disabled = true;
wrapupElm.disabled = false;
wrapupCodesDropdownElm.disabled = false;
}

function registerTaskListeners(task) {
task.on('task:assigned', (task) => {
console.info('Call has been accepted for task: ', task.data.interactionId);
holdResumeElm.disabled = false;
holdResumeElm.innerText = 'Hold';
pauseResumeRecordingElm.disabled = false;
pauseResumeRecordingElm.innerText = 'Pause Recording';
endElm.disabled = false;
});
task.on('task:media', (track) => {
document.getElementById('remote-audio').srcObject = new MediaStream([track]);
});
task.on('task:end', (task) => {
if (!endElm.disabled) {
console.info('Call ended successfully by the external user');
updateButtonsPostEndCall();
}
});
}

function generateWebexConfig({credentials}) {
return {
appName: 'sdk-samples',
Expand Down Expand Up @@ -122,6 +159,8 @@ function register() {
teamsDropdown.innerHTML = ''; // Clear previously selected option on teamsDropdown
const listTeams = agentProfile.teams;
agentId = agentProfile.agentId;
wrapupCodes = agentProfile.wrapupCodes;
populateWrapupCodesDropdown();
listTeams.forEach((team) => {
const option = document.createElement('option');
option.value = team.id;
Expand All @@ -132,7 +171,7 @@ function register() {
agentLogin.innerHTML = '<option value="" selected>Choose Agent Login ...</option>'; // Clear previously selected option on agentLogin.
dialNumber.value = agentProfile.defaultDn ? agentProfile.defaultDn : '';
dialNumber.disabled = agentProfile.defaultDn ? false : true;
if(loginVoiceOptions.length > 0) agentLoginButton.disabled = false;
if (loginVoiceOptions.length > 0) loginAgentElm.disabled = false;
loginVoiceOptions.forEach((voiceOptions)=> {
const option = document.createElement('option');
option.text = voiceOptions;
Expand All @@ -142,6 +181,7 @@ function register() {
});

if (agentProfile.isAgentLoggedIn) {
loginAgentElm.disabled = true;
logoutAgentElm.classList.remove('hidden');
}

Expand All @@ -157,10 +197,25 @@ function register() {
idleCodesDropdown.add(option);
}
});

}).catch((error) => {
console.error('Event subscription failed', error);
})

webex.cc.eventEmitter.on('task:incoming', (task) => {
taskEvents.detail.task = task;

incomingCallListener.dispatchEvent(taskEvents);
})
}

function populateWrapupCodesDropdown() {
wrapupCodesDropdownElm.innerHTML = ''; // Clear previous options
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need name and id for the payload for wrapupm hence we store only that as of now.

wrapupCodes.forEach((code) => {
const option = document.createElement('option');
option.text = code.name;
option.value = code.id;
wrapupCodesDropdownElm.add(option);
});
}

async function handleAgentLogin(e) {
Expand All @@ -178,6 +233,7 @@ async function handleAgentLogin(e) {
function doAgentLogin() {
webex.cc.stationLogin({teamId: teamsDropdown.value, loginOption: agentDeviceType, dialNumber: dialNumber.value}).then((response) => {
console.log('Agent Logged in successfully', response);
loginAgentElm.disabled = true;
logoutAgentElm.classList.remove('hidden');
}
).catch((error) => {
Expand Down Expand Up @@ -205,6 +261,7 @@ function setAgentStatus() {
function logoutAgent() {
webex.cc.stationLogout({logoutReason: 'logout'}).then((response) => {
console.log('Agent logged out successfully', response);
loginAgentElm.disabled = false;

setTimeout(() => {
logoutAgentElm.classList.add('hidden');
Expand Down Expand Up @@ -247,6 +304,36 @@ async function fetchBuddyAgents() {
}
}

incomingCallListener.addEventListener('task:incoming', (event) => {
taskId = event.detail.task.data.interactionId;
task = event.detail.task;
const callerDisplay = event.detail.task.data.interaction.callAssociatedDetails.ani;
registerTaskListeners(task);

if (task.webCallingService.loginOption === 'BROWSER') {
answerElm.disabled = false;
declineElm.disabled = false;

incomingDetailsElm.innerText = `Call from ${callerDisplay}`;
} else {
incomingDetailsElm.innerText = `Call from ${callerDisplay}...please answer on the endpoint where the agent's extension is registered`;
}
});

function answer() {
answerElm.disabled = true;
declineElm.disabled = true;
task.accept(taskId);
incomingDetailsElm.innerText = 'Call Accepted';
}

function decline() {
answerElm.disabled = true;
declineElm.disabled = true;
task.decline(taskId);
incomingDetailsElm.innerText = 'No incoming calls';
}

const allCollapsibleElements = document.querySelectorAll('.collapsible');
allCollapsibleElements.forEach((el) => {
el.addEventListener('click', (event) => {
Expand Down Expand Up @@ -304,3 +391,79 @@ function expandAll() {
});
}

function holdResumeCall() {
if (holdResumeElm.innerText === 'Hold') {
holdResumeElm.disabled = true;
task.hold().then(() => {
console.info('Call held successfully');
holdResumeElm.innerText = 'Resume';
holdResumeElm.disabled = false;
}).catch((error) => {
console.error('Failed to hold the call', error);
holdResumeElm.disabled = false;
});
} else {
holdResumeElm.disabled = true;
task.resume().then(() => {
console.info('Call resumed successfully');
holdResumeElm.innerText = 'Hold';
holdResumeElm.disabled = false;
}).catch((error) => {
console.error('Failed to resume the call', error);
holdResumeElm.disabled = false;
});
}
}

function togglePauseResumeRecording() {
const autoResumed = autoResumeCheckboxElm.checked;
if (pauseResumeRecordingElm.innerText === 'Pause Recording') {
pauseResumeRecordingElm.disabled = true;
task.pauseRecording().then(() => {
console.info('Recording paused successfully');
pauseResumeRecordingElm.innerText = 'Resume Recording';
pauseResumeRecordingElm.disabled = false;
autoResumeCheckboxElm.disabled = false;
}).catch((error) => {
console.error('Failed to pause recording', error);
pauseResumeRecordingElm.disabled = false;
});
} else {
pauseResumeRecordingElm.disabled = true;
task.resumeRecording({ autoResumed: autoResumed }).then(() => {
console.info('Recording resumed successfully');
pauseResumeRecordingElm.innerText = 'Pause Recording';
pauseResumeRecordingElm.disabled = false;
autoResumeCheckboxElm.disabled = true;
}).catch((error) => {
console.error('Failed to resume recording', error);
pauseResumeRecordingElm.disabled = false;
});
}
}

function endCall() {
endElm.disabled = true;
task.end().then(() => {
console.log('Call ended successfully by agent');
updateButtonsPostEndCall();
}).catch((error) => {
console.error('Failed to end the call', error);
endElm.disabled = false;
});
}

function wrapupCall() {
wrapupElm.disabled = true;
adhmenon marked this conversation as resolved.
Show resolved Hide resolved
const wrapupReason = wrapupCodesDropdownElm.options[wrapupCodesDropdownElm.selectedIndex].text;
const auxCodeId = wrapupCodesDropdownElm.options[wrapupCodesDropdownElm.selectedIndex].value;
task.wrapup({wrapUpReason: wrapupReason, auxCodeId: auxCodeId}).then(() => {
console.info('Call wrapped up successfully');
holdResumeElm.disabled = true;
endElm.disabled = true;
wrapupCodesDropdownElm.disabled = true;
}).catch((error) => {
console.error('Failed to wrap up the call', error);
wrapupElm.disabled = false;
});
}
46 changes: 43 additions & 3 deletions docs/samples/contact-center/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,8 @@ <h2 class="collapsible">
<option value="" selected hidden>Choose Agent Login ...</option>
</select>
<input id="dialNumber" name="dialNumber" placeholder="Dial Number" value="" type="text">
<button id="loginAgent" disabled class="btn btn-primary my-3" onclick="doAgentLogin()">Login With
Selected Team</button>
<button id="logoutAgent" class="btn btn-primary my-3 ml-2 hidden" onclick="logoutAgent()">Logout Agent</button>
<button id="loginAgent" disabled class="btn btn-primary my-3" onclick="doAgentLogin()">Login</button>
<button id="logoutAgent" class="btn btn-primary my-3 ml-2 hidden" onclick="logoutAgent()">Logout</button>
</fieldset>
<fieldset>
<legend>Agent status</legend>
Expand All @@ -131,6 +130,47 @@ <h2 class="collapsible">
</div>
</fieldset>
</div>
<!-- calling / incoming -->
<div id="incomingsection">
<fieldset>
<legend>Incoming Call</legend>
<div class="u-mv">
<pre id="incoming-call"> No Incoming Calls</pre>
<button onclick="answer()" disabled="" id="answer" class="btn--green">Answer</button>
<button onclick="decline()" disabled="" id="decline" class="btn--red">Decline</button>
</div>
</fieldset>
<fieldset>
<legend>Remote Audio</legend>
<audio id="remote-audio" autoplay></audio>
</fieldset>
</div>
<!-- Agent Call Controls -->
<div id="callcontrolsection">
<fieldset>
<legend>Call Controls</legend>
<div class="u-mv">
<button onclick="holdResumeCall()" id="hold-resume" class="btn--yellow" disabled>Hold</button>
<button onclick="endCall()" id="end" class="btn--red" disabled>End</button>
<fieldset>
<legend>Call Wrapup</legend>
<button onclick="wrapupCall()" id="wrapup" class="btn--green" disabled>Wrapup</button>
<select id="wrapupCodesDropdown" disabled>
<option value="" selected hidden>Select Wrapup Code</option>
</select>
</fieldset>
<fieldset>
<legend>Recording Controls</legend>
<div class="u-mv">
<button onclick="togglePauseResumeRecording()" id="pause-resume-recording" class="btn--blue" disabled>Pause Recording</button>
<label for="auto-resume-checkbox" style="margin-left: 10px;">
<input type="checkbox" id="auto-resume-checkbox" disabled> Auto Resume
</label>
</div>
</fieldset>
</div>
</fieldset>
</div>
</section>
</div>
</div>
Expand Down
Loading
Loading