diff --git a/docs/samples/contact-center/app.js b/docs/samples/contact-center/app.js index 4144f3ac6f2..cb81ddb920a 100644 --- a/docs/samples/contact-center/app.js +++ b/docs/samples/contact-center/app.js @@ -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'); @@ -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'); @@ -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; @@ -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); }) } @@ -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) => { diff --git a/docs/samples/contact-center/index.html b/docs/samples/contact-center/index.html index 97be88b448d..356dda80793 100644 --- a/docs/samples/contact-center/index.html +++ b/docs/samples/contact-center/index.html @@ -116,9 +116,11 @@

Agent status - - + +
diff --git a/packages/@webex/plugin-cc/src/cc.ts b/packages/@webex/plugin-cc/src/cc.ts index a32300b18b9..c0ca3179f95 100644 --- a/packages/@webex/plugin-cc/src/cc.ts +++ b/packages/@webex/plugin-cc/src/cc.ts @@ -1,6 +1,7 @@ import {WebexPlugin} from '@webex/webex-core'; import AgentConfig from './features/Agentconfig'; import { + SetStateResponse, CCPluginConfig, IContactCenter, WebexSDK, @@ -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 { @@ -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 + * @throws Error + */ + + public async setAgentState(data: StateChange): Promise { + 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. */ diff --git a/packages/@webex/plugin-cc/src/features/Agentconfig.ts b/packages/@webex/plugin-cc/src/features/Agentconfig.ts index 7f02b70c50d..89641951c63 100644 --- a/packages/@webex/plugin-cc/src/features/Agentconfig.ts +++ b/packages/@webex/plugin-cc/src/features/Agentconfig.ts @@ -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 { @@ -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 diff --git a/packages/@webex/plugin-cc/src/features/constants.ts b/packages/@webex/plugin-cc/src/features/constants.ts index 662036643e4..5f256f52bd9 100644 --- a/packages/@webex/plugin-cc/src/features/constants.ts +++ b/packages/@webex/plugin-cc/src/features/constants.ts @@ -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 = []; diff --git a/packages/@webex/plugin-cc/src/services/agent/index.ts b/packages/@webex/plugin-cc/src/services/agent/index.ts index de5ee8ec1b4..daddaca930e 100644 --- a/packages/@webex/plugin-cc/src/services/agent/index.ts +++ b/packages/@webex/plugin-cc/src/services/agent/index.ts @@ -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: { diff --git a/packages/@webex/plugin-cc/src/services/agent/types.ts b/packages/@webex/plugin-cc/src/services/agent/types.ts index 49973b6e09e..fc83a784ba4 100644 --- a/packages/@webex/plugin-cc/src/services/agent/types.ts +++ b/packages/@webex/plugin-cc/src/services/agent/types.ts @@ -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; diff --git a/packages/@webex/plugin-cc/src/services/config/index.ts b/packages/@webex/plugin-cc/src/services/config/index.ts index 7be743098e0..192f29f87c4 100644 --- a/packages/@webex/plugin-cc/src/services/config/index.ts +++ b/packages/@webex/plugin-cc/src/services/config/index.ts @@ -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} A promise that eventually resolves to an API response. */ diff --git a/packages/@webex/plugin-cc/src/services/constants.ts b/packages/@webex/plugin-cc/src/services/constants.ts index 24198ef0e33..6c7769f5b41 100644 --- a/packages/@webex/plugin-cc/src/services/constants.ts +++ b/packages/@webex/plugin-cc/src/services/constants.ts @@ -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'; diff --git a/packages/@webex/plugin-cc/src/types.ts b/packages/@webex/plugin-cc/src/types.ts index 990bdb306ad..e75992edd51 100644 --- a/packages/@webex/plugin-cc/src/types.ts +++ b/packages/@webex/plugin-cc/src/types.ts @@ -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; diff --git a/packages/@webex/plugin-cc/test/unit/spec/cc.ts b/packages/@webex/plugin-cc/test/unit/spec/cc.ts index 6ba0affb9df..597a857f9e6 100644 --- a/packages/@webex/plugin-cc/test/unit/spec/cc.ts +++ b/packages/@webex/plugin-cc/test/unit/spec/cc.ts @@ -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'; @@ -77,6 +76,7 @@ describe('webex.cc', () => { stationLogin: jest.fn(), logout: jest.fn(), reload: jest.fn(), + stateChange: jest.fn(), buddyAgents: jest.fn(), }, }; @@ -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 @@ -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', @@ -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'}; diff --git a/packages/@webex/plugin-cc/test/unit/spec/features/AgentConfig.ts b/packages/@webex/plugin-cc/test/unit/spec/features/AgentConfig.ts index 598f7f9eda2..6882cfdcb18 100644 --- a/packages/@webex/plugin-cc/test/unit/spec/features/AgentConfig.ts +++ b/packages/@webex/plugin-cc/test/unit/spec/features/AgentConfig.ts @@ -113,6 +113,15 @@ describe('AgentConfig', () => { name: 'testName', workTypeCode: WORK_TYPE_CODE.IDLE_CODE, }, + { + active: true, + defaultCode: true, + description: 'Agent is available to receive calls', + id: '0', + isSystemCode: false, + name: 'Available', + workTypeCode: 'IDLE_CODE', + }, ], }; @@ -221,6 +230,15 @@ describe('AgentConfig', () => { name: 'testName', workTypeCode: WORK_TYPE_CODE.IDLE_CODE, }, + { + active: true, + defaultCode: true, + description: 'Agent is available to receive calls', + id: '0', + isSystemCode: false, + name: 'Available', + workTypeCode: 'IDLE_CODE', + }, ], }; @@ -297,6 +315,15 @@ describe('AgentConfig', () => { name: 'testName', workTypeCode: WORK_TYPE_CODE.IDLE_CODE, }, + { + active: true, + defaultCode: true, + description: 'Agent is available to receive calls', + id: '0', + isSystemCode: false, + name: 'Available', + workTypeCode: 'IDLE_CODE', + }, ], }; @@ -373,6 +400,15 @@ describe('AgentConfig', () => { name: 'testName', workTypeCode: WORK_TYPE_CODE.IDLE_CODE, }, + { + active: true, + defaultCode: true, + description: 'Agent is available to receive calls', + id: '0', + isSystemCode: false, + name: 'Available', + workTypeCode: 'IDLE_CODE', + }, ], }; @@ -449,6 +485,15 @@ describe('AgentConfig', () => { name: 'testName', workTypeCode: WORK_TYPE_CODE.IDLE_CODE, }, + { + active: true, + defaultCode: true, + description: 'Agent is available to receive calls', + id: '0', + isSystemCode: false, + name: 'Available', + workTypeCode: 'IDLE_CODE', + }, ], };