Skip to content

Commit

Permalink
feat(plugin-cc): set agent status implementation (webex#3970)
Browse files Browse the repository at this point in the history
Co-authored-by: Priya <[email protected]>
Co-authored-by: parv_gour <parv_gour@PAGOUR-M-D8B2>
Co-authored-by: pagour98 <[email protected]>
Co-authored-by: arungane <[email protected]>
Co-authored-by: Bharath Balan <[email protected]>
Co-authored-by: Adhwaith Menon <[email protected]>
Co-authored-by: adhmenon <[email protected]>
Co-authored-by: Rajesh Kumar <[email protected]>
  • Loading branch information
9 people authored Nov 13, 2024
1 parent b70f7bd commit 19db600
Show file tree
Hide file tree
Showing 12 changed files with 239 additions and 20 deletions.
38 changes: 37 additions & 1 deletion docs/samples/contact-center/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ let webex;
let sdk;
let agentDeviceType;
let deviceId;
let agentStatusId;
let agentStatus;
let agentId;

const authTypeElm = document.querySelector('#auth-type');
const credentialsFormElm = document.querySelector('#credentials');
Expand All @@ -27,6 +30,8 @@ const agentLogin = document.querySelector('#AgentLogin');
const agentLoginButton = 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');

Expand Down Expand Up @@ -116,6 +121,7 @@ function register() {
console.log('Event subscription successful: ', agentProfile);
teamsDropdown.innerHTML = ''; // Clear previously selected option on teamsDropdown
const listTeams = agentProfile.teams;
agentId = agentProfile.agentId;
listTeams.forEach((team) => {
const option = document.createElement('option');
option.value = team.id;
Expand All @@ -133,8 +139,22 @@ function register() {
option.value = voiceOptions;
agentLogin.add(option);
});

const idleCodesList = agentProfile.idleCodes;

if(idleCodesList.length > 0) setAgentStatusButton.disabled = false;

idleCodesList.forEach((idleCodes) => {
if(idleCodes.isSystemCode === false) {
const option = document.createElement('option');
option.text = idleCodes.name;
option.value = idleCodes.id;
idleCodesDropdown.add(option);
}
});

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

Expand Down Expand Up @@ -173,6 +193,22 @@ function doAgentLogin() {
});
}

async function handleAgentStatus(event) {
const select = document.getElementById('idleCodesDropdown');
auxCodeId = event.target.value;
agentStatus = select.options[select.selectedIndex].text;
}

function setAgentStatus() {
let state = "Available";
if(agentStatus !== 'Available') state = 'Idle';
webex.cc.setAgentState({state, auxCodeId, lastStateChangeReason: agentStatus, agentId}).then((response) => {
console.log('Agent status set successfully', response);
}).catch(error => {
console.error('Agent status set failed', error);
});
}


function logoutAgent() {
webex.cc.stationLogout({logoutReason: 'logout'}).then((response) => {
Expand Down
8 changes: 5 additions & 3 deletions docs/samples/contact-center/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,11 @@ <h2 class="collapsible">
</fieldset>
<fieldset>
<legend>Agent status</legend>
<select id="statusDropdown" class="form-control w-auto my-3"></select>
<button id="setAgentStatus" class="btn btn-primary my-3 ml-2" onclick="setAgentStatus()">set
sate</button>
<select name= "idleCodesDropdown" id="idleCodesDropdown" class="form-control w-auto my-3" onchange="handleAgentStatus(event)">
<option value="" selected hidden>Select Idle Codes</option>
</select>
<button id="setAgentStatus" disabled class="btn btn-primary my-3 ml-2" onclick="setAgentStatus()">Set Agent
Status</button>
</fieldset>
</div>
<fieldset id="buddyAgentsBox">
Expand Down
24 changes: 23 additions & 1 deletion packages/@webex/plugin-cc/src/cc.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {WebexPlugin} from '@webex/webex-core';
import AgentConfig from './features/Agentconfig';
import {
SetStateResponse,
CCPluginConfig,
IContactCenter,
WebexSDK,
Expand All @@ -22,8 +23,8 @@ import {AGENT, WEB_RTC_PREFIX} from './services/constants';
import {WebSocketManager} from './services/core/WebSocket/WebSocketManager';
import Services from './services';
import LoggerProxy from './logger-proxy';
import {StateChange, Logout} from './services/agent/types';
import {ConnectionService} from './services/core/WebSocket/connection-service';
import {Logout} from './services/agent/types';
import {getErrorDetails} from './services/core/Utils';

export default class ContactCenter extends WebexPlugin implements IContactCenter {
Expand Down Expand Up @@ -212,6 +213,27 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
return WEB_RTC_PREFIX + this.agentConfig.agentId;
}

/**
* This is used for setting agent state.
* @param options
* @returns Promise<SetStateResponse>
* @throws Error
*/

public async setAgentState(data: StateChange): Promise<SetStateResponse> {
try {
const agentStatusResponse = await this.services.agent.stateChange({
data: {...data, agentId: data.agentId || this.agentConfig.agentId},
});

this.$webex.logger.log(`file: ${CC_FILE}: SET AGENT STATUS API SUCCESS`);

return agentStatusResponse;
} catch (error) {
throw getErrorDetails(error, 'setAgentState');
}
}

/**
* This method returns the connection configuration.
*/
Expand Down
21 changes: 19 additions & 2 deletions packages/@webex/plugin-cc/src/features/Agentconfig.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import {WORK_TYPE_CODE} from './types';
import AgentConfigService from '../services/config';
import {IAgentProfile, Team, AuxCode, WebexSDK} from '../types';
import {DEFAULT_ATTRIBUTES, DEFAULT_PAGE, DEFAULT_PAGE_SIZE} from './constants';
import {WebexSDK, IAgentProfile, Team, AuxCode} from '../types';
import {
AGENT_STATE_AVAILABLE,
AGENT_STATE_AVAILABLE_DESCRIPTION,
AGENT_STATE_AVAILABLE_ID,
DEFAULT_ATTRIBUTES,
DEFAULT_PAGE,
DEFAULT_PAGE_SIZE,
} from './constants';
import HttpRequest from '../services/core/HttpRequest';

export default class AgentConfig {
Expand Down Expand Up @@ -87,6 +94,16 @@ export default class AgentConfig {
: [];

this.agentProfile.teams.push(...teams);
auxCodesList.data.push({
id: AGENT_STATE_AVAILABLE_ID,
active: true,
defaultCode: true,
name: AGENT_STATE_AVAILABLE,
isSystemCode: false,
workTypeCode: WORK_TYPE_CODE.IDLE_CODE,
description: AGENT_STATE_AVAILABLE_DESCRIPTION,
});
this.agentProfile.idleCodes = auxCodesList.data;

this.agentProfile.wrapUpCodes = auxCodesList.data.filter(
(auxCode) => auxCode.workTypeCode === WORK_TYPE_CODE.WRAP_UP_CODE
Expand Down
5 changes: 4 additions & 1 deletion packages/@webex/plugin-cc/src/features/constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
// making query params configurable for List Teams and List Aux Codes API
export const DEFAULT_PAGE = 0;
export const DEFAULT_PAGE_SIZE = 10;
export const DEFAULT_ATTRIBUTES = ['id', 'name'];
export const AGENT_STATE_AVAILABLE_ID = '0';
export const AGENT_STATE_AVAILABLE = 'Available';
export const AGENT_STATE_AVAILABLE_DESCRIPTION = 'Agent is available to receive calls';
export const DEFAULT_ATTRIBUTES = [];
2 changes: 1 addition & 1 deletion packages/@webex/plugin-cc/src/services/agent/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export default function routingAgent(routing: AqmReqs) {
stateChange: routing.req((p: {data: Agent.StateChange}) => ({
url: '/v1/agents/session/state',
host: WCC_API_GATEWAY,
data: {...p.data, auxCodeId: p.data.auxCodeIdArray},
data: p.data,
err,
method: HTTP_METHODS.PUT,
notifSuccess: {
Expand Down
6 changes: 3 additions & 3 deletions packages/@webex/plugin-cc/src/services/agent/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,15 +91,15 @@ export type DNRegistered = Msg<{

export type Logout = {logoutReason?: 'User requested logout' | 'Inactivity Logout'};

export type AgentState = 'Available' | 'Idle' | 'RONA' | string;

export type StateChange = {
state: AgentState;
auxCodeIdArray: string;
auxCodeId: string;
lastStateChangeReason?: string;
agentId?: string;
};

export type AgentState = 'Available' | 'Idle' | 'RONA' | string;

export type UserStationLogin = {
dialNumber?: string | null;
dn?: string | null;
Expand Down
2 changes: 1 addition & 1 deletion packages/@webex/plugin-cc/src/services/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export default class AgentConfigService {
}

/**
* Method to get Desktop Profile by passing Org Id.
* Method to get Desktop Profile by passing desktopProfileId.
* @param {string} desktopProfileId ID of the Desktop Profile to be retrieved.
* @returns {Promise<DesktopProfileResponse>} A promise that eventually resolves to an API response.
*/
Expand Down
1 change: 1 addition & 0 deletions packages/@webex/plugin-cc/src/services/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export const AGENT = 'agent';
export const SUBSCRIBE_API = 'v1/notification/subscribe';
export const LOGIN_API = 'v1/agents/login';
export const WEB_RTC_PREFIX = 'webrtc-';
export const STATE_CHANGE_API = 'v1/agents/session/state';
1 change: 1 addition & 0 deletions packages/@webex/plugin-cc/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,4 +317,5 @@ export type BuddyAgents = {
export type StationLoginResponse = Agent.StationLoginSuccess | Error;
export type StationLogoutResponse = Agent.LogoutSuccess | Error;
export type StationReLoginResponse = Agent.ReloginSuccess | Error;
export type SetStateResponse = Agent.StateChangeSuccess | Error;
export type BuddyAgentsResponse = Agent.BuddyAgentsSuccess | Error;
106 changes: 99 additions & 7 deletions packages/@webex/plugin-cc/test/unit/spec/cc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,12 @@ import {
} from '../../../src/types';
import ContactCenter from '../../../src/cc';
import MockWebex from '@webex/test-helper-mock-webex';
import { StationLoginSuccess } from '../../../src/services/agent/types';
import { IAgentProfile } from '../../../src/types';
import { AGENT, WEB_RTC_PREFIX } from '../../../src/services/constants';
import {StationLoginSuccess} from '../../../src/services/agent/types';
import {IAgentProfile} from '../../../src/types';
import {AGENT, WEB_RTC_PREFIX} from '../../../src/services/constants';
import Services from '../../../src/services';
import config from '../../../src/config';
import LoggerProxy from '../../../src/logger-proxy';
import { getErrorDetails, createErrDetailsObject } from '../../../src/services/core/Utils';

// Mock the Worker API
import '../../../__mocks__/workerMock';
Expand Down Expand Up @@ -77,6 +76,7 @@ describe('webex.cc', () => {
stationLogin: jest.fn(),
logout: jest.fn(),
reload: jest.fn(),
stateChange: jest.fn(),
buddyAgents: jest.fn(),
},
};
Expand Down Expand Up @@ -305,7 +305,7 @@ describe('webex.cc', () => {

describe('stationLogout', () => {
it('should logout successfully', async () => {
const data = { logoutReason: 'Logout reason' };
const data = {logoutReason: 'Logout reason'};
const response = {};

const stationLogoutMock = jest
Expand All @@ -314,12 +314,12 @@ describe('webex.cc', () => {

const result = await webex.cc.stationLogout(data);

expect(stationLogoutMock).toHaveBeenCalledWith({ data: data });
expect(stationLogoutMock).toHaveBeenCalledWith({data: data});
expect(result).toEqual(response);
});

it('should handle error during stationLogout', async () => {
const data = { logoutReason: 'Logout reason' };
const data = {logoutReason: 'Logout reason'};
const error = {
details: {
trackingId: '1234',
Expand Down Expand Up @@ -372,6 +372,98 @@ describe('webex.cc', () => {
);
});
});

describe('setAgentStatus', () => {
it('should set agent status successfully when status is Available', async () => {
const expectedPayload = {
state: 'Available',
auxCodeId: '0',
agentId: '123',
lastStateChangeReason: 'Agent is available',
};

const setAgentStatusMock = jest
.spyOn(webex.cc.services.agent, 'stateChange')
.mockResolvedValue(expectedPayload);

const result = await webex.cc.setAgentState(expectedPayload);

expect(setAgentStatusMock).toHaveBeenCalledWith({data: expectedPayload});
expect(result).toEqual(expectedPayload);
expect(webex.logger.log).toHaveBeenCalledWith('file: cc: SET AGENT STATUS API SUCCESS');
});

it('should set agent status successfully when status is Meeting', async () => {
const expectedPayload = {
state: 'Meeting',
auxCodeId: '12345',
agentId: '123',
lastStateChangeReason: 'Agent is in meeting',
};

const setAgentStatusMock = jest
.spyOn(webex.cc.services.agent, 'stateChange')
.mockResolvedValue(expectedPayload);

const result = await webex.cc.setAgentState(expectedPayload);

expect(setAgentStatusMock).toHaveBeenCalledWith({data: expectedPayload});
expect(result).toEqual(expectedPayload);
expect(webex.logger.log).toHaveBeenCalledWith('file: cc: SET AGENT STATUS API SUCCESS');
});

it('should handle error during setAgentStatus when status is Meeting', async () => {
const expectedPayload = {
state: 'Meeting',
auxCodeId: '12345',
agentId: '123',
lastStateChangeReason: 'Agent is in meeting',
};

const error = {
details: {
trackingId: '1234',
data: {
reason: 'missing status',
},
},
};
jest.spyOn(webex.cc.services.agent, 'stateChange').mockRejectedValue(error);

await expect(webex.cc.setAgentState(expectedPayload)).rejects.toThrow(
error.details.data.reason
);
expect(LoggerProxy.logger.error).toHaveBeenCalledWith(
`setAgentState failed with trackingId: ${error.details.trackingId}`
);
});

it('should handle invalid status', async () => {
const invalidPayload = {
state: 'invalid',
auxCodeId: '12345',
agentId: '123',
lastStateChangeReason: 'invalid',
};
const error = {
details: {
trackingId: '1234',
data: {
reason: 'Invalid status',
},
},
};
jest.spyOn(webex.cc.services.agent, 'stateChange').mockRejectedValue(error);

await expect(webex.cc.setAgentState(invalidPayload)).rejects.toThrow(
error.details.data.reason
);
expect(LoggerProxy.logger.error).toHaveBeenCalledWith(
`setAgentState failed with trackingId: ${error.details.trackingId}`
);
});
});

describe('getBuddyAgents', () => {
it('should return buddy agents response when successful', async () => {
const data: BuddyAgents = {state: 'Available', mediaType: 'telephony'};
Expand Down
Loading

0 comments on commit 19db600

Please sign in to comment.