Skip to content

Commit

Permalink
Merge pull request #3 from bhabalan/cc/getBuddyAgents-2
Browse files Browse the repository at this point in the history
feat(cc): getBuddyAgents implementation
  • Loading branch information
bhabalan authored Nov 12, 2024
2 parents 2926276 + 20d5f42 commit e61eac1
Show file tree
Hide file tree
Showing 10 changed files with 229 additions and 9 deletions.
32 changes: 32 additions & 0 deletions docs/samples/contact-center/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const agentLoginButton = document.querySelector('#loginAgent');
const dialNumber = document.querySelector('#dialNumber');
const registerStatus = document.querySelector('#ws-connection-status');
const logoutAgentElm = document.querySelector('#logoutAgent');
const buddyAgentsDropdownElm = document.getElementById('buddyAgentsDropdown');

// Store and Grab `access-token` from sessionStorage
if (sessionStorage.getItem('date') > new Date().getTime()) {
Expand Down Expand Up @@ -183,6 +184,37 @@ function logoutAgent() {
});
}

async function fetchBuddyAgents() {
try {
buddyAgentsDropdownElm.innerHTML = ''; // Clear previous options
const buddyAgentsResponse = await webex.cc.getBuddyAgents({mediaType: 'telephony'});

if (!buddyAgentsResponse || !buddyAgentsResponse.data) {
console.error('Failed to fetch buddy agents');
buddyAgentsDropdownElm.innerHTML = `<option disabled="true">Failed to fetch buddy agents<option>`;
return;
}

if (buddyAgentsResponse.data.agentList.length === 0) {
console.log('The fetched buddy agents list was empty');
buddyAgentsDropdownElm.innerHTML = `<option disabled="true">No buddy agents available<option>`;
return;
}

buddyAgentsResponse.data.agentList.forEach((agent) => {
const option = document.createElement('option');
option.text = `${agent.agentName} - ${agent.state}`;
option.value = agent.agentId;
buddyAgentsDropdownElm.add(option);
});

} catch (error) {
console.error('Failed to fetch buddy agents', error);
buddyAgentsDropdownElm.innerHTML = ''; // Clear previous options
buddyAgentsDropdownElm.innerHTML = `<option disabled="true">Failed to fetch buddy agents, ${error}<option>`;
}
}

const allCollapsibleElements = document.querySelectorAll('.collapsible');
allCollapsibleElements.forEach((el) => {
el.addEventListener('click', (event) => {
Expand Down
11 changes: 8 additions & 3 deletions docs/samples/contact-center/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ <h2 class="collapsible">
</div>
</fieldset>
</form>
</div>
</div>
</div>
</section>
</div>
Expand All @@ -91,7 +91,7 @@ <h2 class="collapsible">
Agent Station Login/Logout
<i class="arrow fa fa-angle-down" aria-hidden="true"></i>
</h2>

<div class="section-content">
<fieldset>
<legend>Agent</legend>
Expand Down Expand Up @@ -121,6 +121,11 @@ <h2 class="collapsible">
sate</button>
</fieldset>
</div>
<fieldset id="buddyAgentsBox">
<legend>Buddy Agents</legend>
<button id="fetchBuddyAgents" class="btn btn-primary my-3" onclick="fetchBuddyAgents()">Get Buddy Agents</button>
<select id="buddyAgentsDropdown" class="form-control w-auto my-3"></select>
</fieldset>
</div>
</fieldset>
</div>
Expand All @@ -133,4 +138,4 @@ <h2 class="collapsible">
<script src="app.js"></script>
</body>

</html>
</html>
20 changes: 20 additions & 0 deletions packages/@webex/plugin-cc/src/cc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
StationLoginResponse,
StationLogoutResponse,
StationReLoginResponse,
BuddyAgentsResponse,
BuddyAgents,
SubscribeRequest,
} from './types';
import {READY, CC_FILE, EMPTY_STRING} from './constants';
Expand Down Expand Up @@ -80,6 +82,24 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
}
}

/**
* Returns the list of buddy agents in the given state and media according to agent profile settings
*
* @param {BuddyAgents} data - The data required to fetch buddy agents, including additional agent profile information.
* @returns {Promise<BuddyAgentsResponse>} A promise that resolves to the response containing buddy agents information.
* @throws Error
* @example getBuddyAgents({state: 'Available', mediaType: 'telephony'})
*/
public async getBuddyAgents(data: BuddyAgents): Promise<BuddyAgentsResponse> {
try {
return await this.services.agent.buddyAgents({
data: {agentProfileId: this.agentConfig.agentProfileId, ...data},
});
} catch (error) {
throw getErrorDetails(error, 'getBuddyAgents');
}
}

/**
* This is used for connecting the websocket and fetching the agent profile.
* @returns Promise<IAgentProfile>
Expand Down
21 changes: 21 additions & 0 deletions packages/@webex/plugin-cc/src/services/agent/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,5 +100,26 @@ export default function routingAgent(routing: AqmReqs) {
errId: 'Service.aqm.agent.stateChange',
},
})),
buddyAgents: routing.req((p: {data: Agent.BuddyAgents}) => ({
url: `/v1/agents/buddyList`,
host: WCC_API_GATEWAY,
data: {...p.data},
err,
method: HTTP_METHODS.POST,
notifSuccess: {
bind: {
type: CC_EVENTS.AGENT_BUDDY_AGENTS,
data: {type: CC_EVENTS.AGENT_BUDDY_AGENTS_SUCCESS},
},
msg: {} as Agent.BuddyAgentsSuccess,
},
notifFail: {
bind: {
type: CC_EVENTS.AGENT_BUDDY_AGENTS,
data: {type: CC_EVENTS.AGENT_BUDDY_AGENTS_RETRIEVE_FAILED},
},
errId: 'Service.aqm.agent.BuddyAgentsRetrieveFailed',
},
})),
};
}
26 changes: 26 additions & 0 deletions packages/@webex/plugin-cc/src/services/agent/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,29 @@ export type UserStationLogin = {
export type LoginOption = 'AGENT_DN' | 'EXTENSION' | 'BROWSER';

export type DeviceType = LoginOption | string;

export type BuddyAgents = {
agentProfileId: string;
mediaType: string;
/** Filter for agent state eg : Available | Idle */
state?: string;
};

export type BuddyDetails = {
agentId: string;
state: string;
teamId: string;
dn: string;
agentName: string;
siteId: string;
};

export type BuddyAgentsSuccess = Msg<{
eventType: string;
agentId: string;
trackingId: string;
agentSessionId: string;
orgId: string;
type: string;
agentList: Array<BuddyDetails>;
}>;
6 changes: 5 additions & 1 deletion packages/@webex/plugin-cc/src/services/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ export const CC_EVENTS = {
AGENT_STATE_CHANGE: 'AgentStateChange',
AGENT_STATE_CHANGE_SUCCESS: 'AgentStateChangeSuccess',
AGENT_STATE_CHANGE_FAILED: 'AgentStateChangeFailed',
AGENT_BUDDY_AGENTS: 'BuddyAgents',
AGENT_BUDDY_AGENTS_SUCCESS: 'BuddyAgents',
AGENT_BUDDY_AGENTS_RETRIEVE_FAILED: 'BuddyAgentsRetrieveFailed',
} as const;

// Derive the type using the utility type
Expand All @@ -29,7 +32,8 @@ export type WebSocketEvent = {
| Agent.StationLoginSuccess
| Agent.LogoutSuccess
| Agent.ReloginSuccess
| Agent.StateChangeSuccess;
| Agent.StateChangeSuccess
| Agent.BuddyAgentsSuccess;
};

/**
Expand Down
3 changes: 2 additions & 1 deletion packages/@webex/plugin-cc/src/services/core/Err.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ export type AgentErrorIds =
| {'Service.aqm.agent.stateChange': Failure}
| {'Service.aqm.agent.reload': Failure}
| {'Service.aqm.agent.logout': Failure}
| {'Service.reqs.generic.failure': {trackingId: string}};
| {'Service.reqs.generic.failure': {trackingId: string}}
| {'Service.aqm.agent.BuddyAgentsRetrieveFailed': Failure};

export type ReqError =
| 'Service.aqm.reqs.GenericRequestError'
Expand Down
21 changes: 20 additions & 1 deletion packages/@webex/plugin-cc/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,8 +294,27 @@ export type RequestBody =
| SubscribeRequest
| Agent.Logout
| Agent.UserStationLogin
| Agent.StateChange;
| Agent.StateChange
| Agent.BuddyAgents;

/**
* Represents the options to fetch buddy agents for the logged in agent.
* @public
*/
export type BuddyAgents = {
/**
* The media type for the request. The supported values are telephony, chat, social and email.
*/
mediaType: 'telephony' | 'chat' | 'social' | 'email';
/**
* It represents the current state of the returned agents which can be either Available or Idle.
* If state is omitted, the API will return a list of both available and idle agents.
* This is useful for consult scenarios, since consulting an idle agent is also supported.
*/
state?: 'Available' | 'Idle';
};

export type StationLoginResponse = Agent.StationLoginSuccess | Error;
export type StationLogoutResponse = Agent.LogoutSuccess | Error;
export type StationReLoginResponse = Agent.ReloginSuccess | Error;
export type BuddyAgentsResponse = Agent.BuddyAgentsSuccess | Error;
90 changes: 88 additions & 2 deletions packages/@webex/plugin-cc/test/unit/spec/cc.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import 'jsdom-global/register';
import {LoginOption, StationLogoutResponse, WebexSDK} from '../../../src/types';
import {
BuddyAgents,
BuddyAgentsResponse,
LoginOption,
StationLogoutResponse,
WebexSDK,
} from '../../../src/types';
import ContactCenter from '../../../src/cc';
import MockWebex from '@webex/test-helper-mock-webex';
import {StationLoginSuccess} from '../../../src/services/agent/types';
Expand Down Expand Up @@ -44,7 +50,7 @@ describe('webex.cc', () => {
let mockWebSocketManager;

beforeEach(() => {
webex = new MockWebex({
webex = MockWebex({
children: {
cc: ContactCenter,
},
Expand All @@ -70,6 +76,7 @@ describe('webex.cc', () => {
stationLogin: jest.fn(),
logout: jest.fn(),
reload: jest.fn(),
buddyAgents: jest.fn(),
},
};
(Services.getInstance as jest.Mock).mockReturnValue(mockServicesInstance);
Expand Down Expand Up @@ -318,4 +325,83 @@ describe('webex.cc', () => {
);
});
});

describe('getBuddyAgents', () => {
it('should return buddy agents response when successful', async () => {
const data: BuddyAgents = {state: 'Available', mediaType: 'telephony'};
webex.cc.agentConfig = {
agentId: 'agentId',
agentProfileId: 'test-agent-profile-id',
};

const buddyAgentsResponse: BuddyAgentsResponse = {
type: 'BuddyAgentsSuccess',
orgId: '',
trackingId: '1234',
data: {
eventType: 'BuddyAgents',
agentId: 'agentId',
trackingId: '1234',
orgId: '',
type: '',
agentSessionId: 'session123',
agentList: [
{
agentId: 'agentId',
state: 'Available',
teamId: 'teamId',
dn: '1234567890',
agentName: 'John',
siteId: 'siteId',
},
],
},
};

const buddyAgentsSpy = jest
.spyOn(webex.cc.services.agent, 'buddyAgents')
.mockResolvedValue(buddyAgentsResponse);

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

expect(buddyAgentsSpy).toHaveBeenCalledWith({
data: {agentProfileId: 'test-agent-profile-id', ...data},
});

expect(result).toEqual(buddyAgentsResponse);
});

it('should handle error', async () => {
const data: BuddyAgents = {state: 'Available', mediaType: 'telephony'};
webex.cc.agentConfig = {
agentId: 'f520d6b5-28ad-4f2f-b83e-781bb64af617',
agentProfileId: 'test-agent-profile-id',
};

const error = {
details: {
data: {
agentId: 'f520d6b5-28ad-4f2f-b83e-781bb64af617',
eventTime: 1731402794534,
eventType: 'AgentDesktopMessage',
orgId: 'e7924666-777d-40d4-a504-01aa1e62dd2f',
reason: 'AGENT_NOT_FOUND',
reasonCode: 1038,
trackingId: '5d2ddfaf-9b8a-491f-9c3f-3bb8ba60d595',
type: 'BuddyAgentsRetrieveFailed',
},
orgId: 'e7924666-777d-40d4-a504-01aa1e62dd2f',
trackingId: 'notifs_a7727d9e-7651-4c60-90a7-ff3de47b784d',
type: 'BuddyAgents',
},
};

jest.spyOn(webex.cc.services.agent, 'buddyAgents').mockRejectedValue(error);

await expect(webex.cc.getBuddyAgents(data)).rejects.toThrow(error.details.data.reason);
expect(LoggerProxy.logger.error).toHaveBeenCalledWith(
`getBuddyAgents failed with trackingId: ${error.details.trackingId}`
);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,10 @@ describe('AQM routing agent', () => {
expect(req).toBeDefined();
expect(reqSpy).toHaveBeenCalled();
});
});
it('buddyAgents', async () => {
const reqSpy = jest.spyOn(fakeAqm, 'req');
const req = await agent.buddyAgents({data: {} as any});
expect(req).toBeDefined();
expect(reqSpy).toHaveBeenCalled();
});
});

0 comments on commit e61eac1

Please sign in to comment.