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

Added disclaimer. #4

Open
wants to merge 21 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Develop an integration app that uses Partial Transcript notifications

This Genesys Cloud Developer Blueprint demonstrates an example of how partial transcript notifications can be used in the context of a Genesys Cloud Integration. The sample app is about an admin dashboard that allows administrators to view active conversations in the admin's organization queues. The administrator can look at info about each conversation, including the ongoing transcript, and assign the call and "standing" of the call. The "standing" of the call is a binary good-or-bad state depending whether the agent uttered one of the red-listed words. The blueprint describes the required steps to develop and integrate this app into the Genesys Cloud app.
This Genesys Cloud Developer Blueprint provides an example of a complete Terraform configuration that creates a Genesys Cloud EventBridge integration and writes events from the integration into S3.

![Partial Transcript App flowchart](blueprint/images/flowchart.png "Partial Transcript App flowchart")
Binary file added blueprint/images/embedded-app-in-action.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added blueprint/images/embedded-app-title.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified blueprint/images/flowchart.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
106 changes: 60 additions & 46 deletions blueprint/index-draft.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/components/conversation/Conversation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export function Conversation(props: IProps) {
<div className="status-name">{conversation.assignedAgent?.agentName}</div>
</div>
<div className="agent-image">
<img src={conversation.assignedAgent?.imageUri || ''} />
<img alt="agent" src={conversation.assignedAgent?.imageUri || ''} />
</div>
</div>
</div>
Expand Down
58 changes: 51 additions & 7 deletions src/components/queue-list/QueueList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,8 @@ export enum Standing {
export function QueueList(props: any) {

let closedConversationIds: string[] = [];
const badWords = ['bad word'];
let retryAfter: number = 0;
const blacklistedPhrases: string[] = ['blacklisted phrase']; // Replace this with the phrases you want to blacklist

const [queues, setQueues] = useState<Queue[]>([]);

Expand All @@ -163,7 +164,6 @@ export function QueueList(props: any) {
* Initializes the data and subscriptions for the Active Conversations Dashboard
*/
async function setupQueues() {
let userId: string;
let tempQueues: Queue[];
// authenticate logged-in user
await authenticate()
Expand Down Expand Up @@ -244,14 +244,32 @@ export function QueueList(props: any) {
* @param queueId id of the matching queue
*/
async function subscribeToQueueConversations(baseQueues: Queue[], matchingQueue: Queue, queueId: string) {
const queueConversationTopic = `v2.routing.queues.${queueId}.conversations.calls`
const queueConversationTopic: string = `v2.routing.queues.${queueId}.conversations.calls`;

// the callback fired when subscription notifications are received
const queueConversationCallback = async (data: QueueConversationEvent) => {
console.log('QUEUE CONVERSATION DATA', data);
const { eventBody } = data;

if (!matchingQueue || closedConversationIds.find((cid: string) => cid === data.eventBody.id)) return;
if (!matchingQueue || closedConversationIds.find((cid: string) => cid === eventBody.id)) return;

const terminatedParticipantsLength = eventBody.participants?.filter((p: Participant) => p.state?.toLowerCase() === 'terminated' || p.state?.toLowerCase() === 'disconnected')?.length || 0;
const participantsLength = eventBody.participants?.length || 0;

// If the call is disconnected, remove the subscription
if (participantsLength && terminatedParticipantsLength === participantsLength) {
cancelSubscription(queueConversationTopic);
closedConversationIds.push(eventBody.id);
const newQueues: Queue[] = baseQueues.map((queue: Queue) => {
return {
...queue,
conversationIds: queue.conversationIds?.filter((cId: string) => cId !== eventBody.id),
conversations: queue.conversations?.filter((c: IConversation) => c.conversationId !== eventBody.id)
}
});
setQueues(newQueues);
return;
}

const conversationAlreadyPresent = matchingQueue.conversationIds?.length > 0
&& matchingQueue.conversationIds.some((cId: string) => cId === data.eventBody.id);
Expand Down Expand Up @@ -296,7 +314,33 @@ export function QueueList(props: any) {
// subscribe to transcripts of initial conversations
matchingQueue.conversationIds?.forEach((cId: string) => subscribeToTranscript(baseQueues, cId));
// subscribe to the queue's conversations
addSubscription(queueConversationTopic, queueConversationCallback);
addSubscriptionWrapper(queueConversationTopic, queueConversationCallback);
}

/**
* Adds subscriptions and handles exponential backoff if a 429 error code is received
*
* @param topic the topic for subscription
* @param cb callback for when notifications are received
*/
async function addSubscriptionWrapper(topic: string, cb: any) {
if (retryAfter > 0) {
const timeout = retryAfter * 1000;
setTimeout(() => {
retryAfter = 0;
addSubscriptionWrapper(topic, cb);
}, timeout);
} else {
const err = await addSubscription(topic, cb);
if (err && err.status === 429) {
retryAfter = err.headers['retry-after'];
const timeout = retryAfter * 1000;
setTimeout(() => {
retryAfter = 0;
addSubscriptionWrapper(topic, cb);
}, timeout);
}
}
}

/**
Expand Down Expand Up @@ -343,7 +387,7 @@ export function QueueList(props: any) {
// determine whether the agent spoke a word that puts the conversation in bad standing
const agentSpokeBadWord: boolean = eventBody.transcripts?.some((transcript: Transcript) => {
return transcript.channel.toLowerCase() === 'internal'
&& badWords.some((badWord: string) => transcript.alternatives?.[0]?.transcript?.toLowerCase()?.includes(badWord));
&& blacklistedPhrases.some((badWord: string) => transcript.alternatives?.[0]?.transcript?.toLowerCase()?.includes(badWord));
});

// add the new interactions
Expand Down Expand Up @@ -386,7 +430,7 @@ export function QueueList(props: any) {

setQueues(newQueues);
};
addSubscription(transcriptionTopic, transcriptionCallback);
addSubscriptionWrapper(transcriptionTopic, transcriptionCallback);
}

/**
Expand Down
8 changes: 1 addition & 7 deletions src/utils/genesysCloudUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,6 @@ const usersApi = new platformClient.UsersApi();
const analyticsApi = new platformClient.AnalyticsApi();
const routingApi = new platformClient.RoutingApi();

/*
* This presence ID is hardcoded because System presence IDs are hardcoded into Genesys Cloud, can never change, and are not unique to orgs or regions
* In constrast, Org presences are not hardcoded.
*/
const offlinePresenceId = 'ccf3c10a-aa2c-4845-8e8d-f59fa48c58e5';

const client = platformClient.ApiClient.instance;
const { clientId, redirectUri } = clientConfig;

Expand Down Expand Up @@ -83,7 +77,7 @@ export async function getQueues(skipCache: boolean = false) {
*/
export function getActiveConversationsForQueue(queueId: string) {
const startInterval = moment().add(-1, 'day').startOf('day');
const endInterval = moment().add(1, 'day'). startOf('day');
const endInterval = moment().add(1, 'day').startOf('day');

const body: any = {
interval: `${startInterval.toISOString(true)}/${endInterval.toISOString(true)}`,
Expand Down
8 changes: 6 additions & 2 deletions src/utils/notificationsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,11 @@ export function addSubscription(topic: string, callback: any) {
return notificationsApi.postNotificationsChannelSubscriptions(channel.id, body)
.then((data: ISubscriptionResponse) => {
subscriptionMap[topic] = callback;
console.log(`Added subscription to ${topic}`);
console.log(`Added subscription to ${topic}`, data);
})
.catch((err: any) => {
console.error('Error adding subscription', err);
return err;
});
}

Expand All @@ -80,6 +84,6 @@ export async function removeSubscription(topic: string, callback: any) {
return notificationsApi.postNotificationsChannelSubscriptions(channel.id, body)
.then((data: ISubscriptionResponse) => {
subscriptionMap[topic] = callback;
console.log(`Added subscription to ${topic}`);
console.log(`Removed subscription to ${topic}`);
});
}