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(cc): getBuddyAgents() implementation #1

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
34 changes: 32 additions & 2 deletions docs/samples/contact-center/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,11 +148,41 @@ async function handleAgentLogin(e) {
}
}

async function fetchBuddyAgents() {
try {
const buddyAgentsResponse = await webex.cc.getBuddyAgents({channelName: 'telephony', state: 'Available'});
const buddyAgentsDropdown = document.getElementById('buddyAgentsDropdown');
buddyAgentsDropdown.innerHTML = ''; // Clear previous options

if (buddyAgentsResponse.agentList.length === 0) {
const option = document.createElement('option');
option.text = 'No buddy agents available';
option.disabled = true;
buddyAgentsDropdown.add(option);
return;
}

buddyAgentsResponse.agentList.forEach((agent) => {
const option = document.createElement('option');
option.text = `${agent.agentName} - ${agent.state}`;
option.value = agent.agentId;
buddyAgentsDropdown.add(option);
});
} catch (error) {
const buddyAgentsDropdown = document.getElementById('buddyAgentsDropdown');
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please add this to the top of file. Please remove from the try block as well.

buddyAgentsDropdown.innerHTML = ''; // Clear previous options
Copy link
Collaborator

Choose a reason for hiding this comment

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

This line can be moved to finally { } block. Can be removed from both try and catch

const option = document.createElement('option');
option.text = `Failed to fetch buddy agents, ${error}`;
option.disabled = true;
Copy link
Collaborator

Choose a reason for hiding this comment

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

All the three lines can be written in simple way

From

const option = document.createElement('option');
option.text = `Failed to fetch buddy agents, ${error}`;
option.disabled = true;

To

const option = `<option disabled="true">Failed to fetch buddy agents, ${error}<option>`

Please check all other places as well.

buddyAgentsDropdown.add(option);
console.log('Failed to fetch buddy agents', error);
}
}

function doAgentLogin() {
webex.cc.stationLogin({teamId: teamsDropdown.value, loginOption: agentDeviceType, dialNumber: dialNumber.value}).then((response) => {
console.log('Agent Logged in successfully', response);
}
).catch((error) => {
}).catch((error) => {
console.log('Agent Login failed', error);
});
}
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 Desktop Using Webex CC SDK
<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">
</fieldset>
</div>
</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>
</fieldset>
</div>
</section>
Expand All @@ -132,4 +137,4 @@ <h2 class="collapsible">
<script src="app.js"></script>
</body>

</html>
</html>
36 changes: 35 additions & 1 deletion packages/@webex/plugin-cc/src/cc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ import {
SubscribeRequest,
WelcomeEvent,
STATION_LOGIN_TYPE,
GetBuddyAgentsRequest,
CHANNEL_NAME,
} from './types';
import {BuddyAgentsSuccess, StationLoginSuccess} from './services/types';
import {READY, CC_FILE} from './constants';
import Agent from './features/Agent';
import HttpRequest from './services/HttpRequest';
import WebRTCCalling from './WebRTCCalling';
import {StationLoginSuccess} from './services/types';

export default class ContactCenter extends WebexPlugin implements IContactCenter {
namespace = 'cc';
Expand Down Expand Up @@ -136,4 +138,36 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
return Promise.reject(error);
}
}

/**
* @param options - GetBuddyAgentsRequest
* @returns Promise<BuddyAgentsSuccess>
* @throws Error
* @example
* const buddyAgents = await webex.cc.getBuddyAgents({
* channelType: CHANNEL_TYPE.TELEPHONY,
* state: BuddyAgentState.AVAILABLE,
* });
*/
public async getBuddyAgents(
options: GetBuddyAgentsRequest = {

Choose a reason for hiding this comment

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

We still have Get in the name here

channelName: CHANNEL_NAME.TELEPHONY,
}
): Promise<BuddyAgentsSuccess> {
try {
const {channelName, state} = options;
const buddyAgents = await this.agent.getBuddyAgents({
agentProfileId: this.agentConfig.agentProfileId,
channelName,
state,
});
this.$webex.logger.log('Get Buddy Agents API SUCCESS');
Copy link
Collaborator

Choose a reason for hiding this comment

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

Agent code is already logging the success message. This will add redundant log


return buddyAgents;
} catch (error) {
this.$webex.logger.error('Get Buddy Agents API FAILED');

return Promise.reject(new Error('Error while retrieving buddy agents', error.message));
}
}
}
13 changes: 12 additions & 1 deletion packages/@webex/plugin-cc/src/features/Agent.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {STATION_LOGIN_TYPE, WebexSDK} from '../types';
import HttpRequest from '../services/HttpRequest';
import AgentService from '../services/AgentService';
import {StationLoginSuccess} from '../services/types';
import {BuddyAgents, BuddyAgentsSuccess, StationLoginSuccess} from '../services/types';

export default class Agent {
private webex: WebexSDK;
Expand Down Expand Up @@ -34,4 +34,15 @@ export default class Agent {
return Promise.reject(new Error('Error while performing agent login', error));
}
}

public async getBuddyAgents(options: BuddyAgents): Promise<BuddyAgentsSuccess> {
try {
const buddyAgentsResponse = await this.agentService.getBuddyAgents(options);
this.webex.logger.log('Buddy Agents API SUCCESS');

return buddyAgentsResponse;
} catch (error) {
return Promise.reject(new Error('Error while retrieving buddy agents', error));
}
}
}
36 changes: 34 additions & 2 deletions packages/@webex/plugin-cc/src/services/AgentService.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import {STATION_LOGIN_TYPE, WebexSDK, HTTP_METHODS} from '../types';
import {AGENT, LOGIN_API, WCC_API_GATEWAY, WEB_RTC_PREFIX} from './constants';
import {
AGENT,
GET_BUDDY_AGENTS_API,
BuddyAgentsEvent,
BuddyAgentsRetrieveFailedEvent,
LOGIN_API,
WCC_API_GATEWAY,
WEB_RTC_PREFIX,
} from './constants';
import HttpRequest from './HttpRequest';
import {StationLoginSuccess} from './types';
import {BuddyAgentsSuccess, BuddyAgents, StationLoginSuccess} from './types';

export default class AgentService {
private webex: WebexSDK;
Expand Down Expand Up @@ -56,4 +64,28 @@ export default class AgentService {
return Promise.reject(error);
}
}

public async getBuddyAgents(options: BuddyAgents): Promise<BuddyAgentsSuccess> {

Choose a reason for hiding this comment

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

Return type for all the methods should be generic not just associated with success

try {
const data = await this.httpRequest.sendRequestWithEvent({
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggestion (Not to be fixed in this PR) - We should allow sendRequestWithEvent to accept a response type so that we don't have to typecast the response while returning.

cc - @Kesari3008, Ravi

service: WCC_API_GATEWAY,
resource: GET_BUDDY_AGENTS_API,
method: HTTP_METHODS.POST,
payload: {
agentProfileId: options.agentProfileId,
mediaType: options.channelName,
state: options.state || undefined,
},
eventType: BuddyAgentsEvent,
success: [BuddyAgentsEvent],
failure: [BuddyAgentsRetrieveFailedEvent],
});

return data as BuddyAgentsSuccess;
} catch (error) {
this.webex.logger.error(`Error during get buddy agents: ${error}`);

return Promise.reject(new Error('Error while retrieving buddy agents', error));
}
}
}
12 changes: 11 additions & 1 deletion packages/@webex/plugin-cc/src/services/HttpRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,24 @@ class HttpRequest {
try {
const {service, resource, method, payload, eventType, success, failure} = options;

// prune undefined values
Object.keys(payload).forEach((key) => payload[key] === undefined && delete payload[key]);

Choose a reason for hiding this comment

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

What is this code supposed to be doing ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

{
"someKey": "someValue",
"someOtherKey": undefined
}

will get transformed to

{
"someKey": "someValue"
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

In which case we are going to pass a key with undefined value?


// Send the service request
const response = await this.webex.request({
service,
resource,
method,
body: payload,
});
this.webex.logger.log(`Service request sent successfully: ${response}`);
// @ts-ignore
const trackingid = response.headers.trackingid;

this.webex.logger.log(
`Service request sent successfully to ${service}/${resource} with payload: ${JSON.stringify(
payload
)}, Request TrackingID: ${trackingid}`
);

// Listen for the event
return new Promise((resolve, reject) => {
Expand Down
6 changes: 6 additions & 0 deletions packages/@webex/plugin-cc/src/services/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,10 @@ export const AGENT = 'agent';
// CC GATEWAY API URL PATHS
export const SUBSCRIBE_API = 'v1/notification/subscribe';
export const LOGIN_API = 'v1/agents/login';
export const GET_BUDDY_AGENTS_API = 'v1/agents/buddyList';

Choose a reason for hiding this comment

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

Remove Get from here as well

export const WEB_RTC_PREFIX = 'webrtc-';

export const AgentDesktopMessage = 'AgentDesktopMessage';

export const BuddyAgentsEvent = 'BuddyAgents';
export const BuddyAgentsRetrieveFailedEvent = 'BuddyAgentsRetrieveFailed';
28 changes: 28 additions & 0 deletions packages/@webex/plugin-cc/src/services/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import {CHANNEL_NAME, BUDDY_AGENT_STATE} from '../types';
import {BuddyAgentsEvent, AgentDesktopMessage} from './constants';

type Enum<T extends Record<string, unknown>> = T[keyof T];

export type Msg<T = any> = {
Expand Down Expand Up @@ -228,6 +231,31 @@ export interface StationLoginSuccess {
type: 'AgentStationLoginSuccess';
}

export type BuddyAgents = {
agentProfileId: string;
channelName: CHANNEL_NAME;
state?: BUDDY_AGENT_STATE;
};

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

export type BuddyAgentsSuccess = {
eventType: typeof AgentDesktopMessage;
agentId: string;
orgId: string;
trackingId: string;
agentList: BuddyDetails[];
agentSessionId: string;
type: typeof BuddyAgentsEvent;
};

export type SubscribeResponse = {
statusCode: number;
body: {
Expand Down
21 changes: 21 additions & 0 deletions packages/@webex/plugin-cc/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,24 @@ export interface SubscribeRequest {
}

export type EventResult = IAgentProfile;

export const CHANNEL_NAME = {
TELEPHONY: 'telephony',
CHAT: 'chat',
SOCIAL: 'social',
EMAIL: 'email',
} as const;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why are we using as const ? What does it do?

For this we can create enum.

Choose a reason for hiding this comment

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

@rarajes2 This is the new way of creating enum. Please refer to this document: https://confluence-eng-gpk2.cisco.com/conf/display/WTWC/How+to+use+enums+and+alternatives


export type CHANNEL_NAME = Enum<typeof CHANNEL_NAME>;
Copy link
Collaborator

Choose a reason for hiding this comment

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

We don't have to create a separate type like this. The constant defined above can be converted to an enum, no need to create a const variable.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@rarajes2 , Can you please provide an example on how to do this?


export const BUDDY_AGENT_STATE = {
AVAILABLE: 'Available',
IDLE: 'Idle',
} as const;

export type BUDDY_AGENT_STATE = Enum<typeof BUDDY_AGENT_STATE>;

export type GetBuddyAgentsRequest = {
channelName: CHANNEL_NAME;
state?: BUDDY_AGENT_STATE;
};
51 changes: 48 additions & 3 deletions packages/@webex/plugin-cc/test/unit/spec/features/Agent.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { STATION_LOGIN_TYPE, WebexSDK } from '../../../../src/types';
import {STATION_LOGIN_TYPE, WebexSDK, CHANNEL_NAME, BUDDY_AGENT_STATE} from '../../../../src/types';
import HttpRequest from '../../../../src/services/HttpRequest';
import AgentService from '../../../../src/services/AgentService';
import Agent from '../../../../src/features/Agent';
import { StationLoginSuccess } from '../../../../src/services/types';
import {BuddyAgents, BuddyAgentsSuccess, StationLoginSuccess} from '../../../../src/services/types';

// Mock dependencies
jest.mock('../../../../src/services/AgentService');
Expand Down Expand Up @@ -85,4 +85,49 @@ describe('Agent', () => {
expect(webexMock.logger.log).toHaveBeenCalledWith('LOGIN API SUCCESS');
expect(response).toBe(loginResponse);
});
});

describe('getBuddyAgents', () => {
const options: BuddyAgents = {
channelName: CHANNEL_NAME.TELEPHONY,
state: BUDDY_AGENT_STATE.AVAILABLE,
agentProfileId: '123',
};

it('should return buddy agents on success', async () => {
const buddyAgentsSuccess: BuddyAgentsSuccess = {
agentId: 'agentId1',
eventType: 'AgentDesktopMessage',
agentList: [
{
agentId: '12345',
state: 'Available',
teamId: 'abcd',
dn: '1001',
agentName: 'Agent1 Name1',
siteId: 'site1',
},
],
orgId: 'org1',
trackingId: 'track1',
agentSessionId: 'session1',
type: 'BuddyAgents',
};
agentServiceMock.getBuddyAgents.mockResolvedValue(buddyAgentsSuccess);

const result = await agent.getBuddyAgents(options);

expect(result).toEqual(buddyAgentsSuccess);
expect(agentServiceMock.getBuddyAgents).toHaveBeenCalledWith(options);
});

it('should throw an error when getBuddyAgents fails', async () => {

Choose a reason for hiding this comment

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

We should have one testcase for error response as well

const error = new Error('API Error');
agentServiceMock.getBuddyAgents.mockRejectedValue(error);

await expect(agent.getBuddyAgents(options)).rejects.toThrow(
'Error while retrieving buddy agents'
);
expect(agentServiceMock.getBuddyAgents).toHaveBeenCalledWith(options);
});
});
});
Loading