diff --git a/README.md b/README.md index a01d87a..ecacf74 100644 --- a/README.md +++ b/README.md @@ -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") \ No newline at end of file diff --git a/blueprint/images/embedded-app-in-action.png b/blueprint/images/embedded-app-in-action.png new file mode 100644 index 0000000..3947c49 Binary files /dev/null and b/blueprint/images/embedded-app-in-action.png differ diff --git a/blueprint/images/embedded-app-title.png b/blueprint/images/embedded-app-title.png new file mode 100644 index 0000000..fd25960 Binary files /dev/null and b/blueprint/images/embedded-app-title.png differ diff --git a/blueprint/images/flowchart.png b/blueprint/images/flowchart.png index 8c312e0..24e2cd8 100644 Binary files a/blueprint/images/flowchart.png and b/blueprint/images/flowchart.png differ diff --git a/blueprint/index-draft.md b/blueprint/index-draft.md index 119f82e..a7480f7 100644 --- a/blueprint/index-draft.md +++ b/blueprint/index-draft.md @@ -5,8 +5,30 @@ indextype: blueprint icon: blueprint image: images/flowchart.png category: 6 -summary: 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. +summary: This Genesys Cloud Developer Blueprint demonstrates an example of how Partial Transcript notifications can be used in the event of a Genesys Cloud Integration. --- +:::{"alert":"primary","title":"About Genesys Cloud Blueprints","autoCollapse":false} +Genesys Cloud blueprints were built to help you jump-start building an application or integrating with a third-party partner. +Blueprints are meant to outline how to build and deploy your solutions, not a production-ready turn-key solution. + +For more details on Genesys Cloud blueprint support and practices +please see our Genesys Cloud blueprint [FAQ](https://developer.genesys.cloud/blueprints/faq)sheet. +::: + +![Partial Transcript App flowchart](images/flowchart.png "Partial Transcript App flowchart") + +## Solution +` +The sample app included in this blueprint solution is an admin dashboard that allows administrators to view active conversations in their organization's queues. The administrator can look at info about each conversation, including the ongoing transcript, and assign a "standing" to each call. The "standing" is a simple good-or-bad state based on whether the agent said one of the blacklisted phrases. These phrases are defined within `src/components/queue-list/QueueList.tsx` and can be replaced with the phrases of your choosing. + +```javascript +const blacklistedPhrases: string[] = ['blacklisted phrase']; // Replace this with the phrases you want to blacklist +``` + +:::warning: **Warning**: If your organization contains a large number of queues, you may encounter rate limits when subscribing to the conversations for each queue. In this case, one fix is to design your solution as a standalone app rather than an embedded client app. This allows you to initialize and run the app persistently in the background, which prevents re-initializing the embedded client app each time it is opened. Alternatively, you may implement a user-defined filter on the queue list with a maximum list size that prevents the rate limits. This will lengthen the initialization time for the application and could cause unexpected behavior. +::: + +The blueprint describes the required steps to develop and integrate this sample app into Genesys Cloud. ## Contents @@ -17,62 +39,51 @@ summary: This Genesys Cloud Developer Blueprint demonstrates an example of how p * [Configuring the React Project to use Genesys Cloud SDK](https://developer.genesys.cloud/blueprints/react-app-with-genesys-cloud-sdk/#create-a-react-project "How to integrate the Genesys Cloud SDK") * [Additional resources](#additional-resources "Goes to the Additional resources section") - -![Partial Transcript App Flowchart](images/flowchart.png) - ## Solution components - **Genesys Cloud** - A suite of Genesys cloud services for enterprise-grade communications, collaboration, and contact center management. You deploy the Chat Translator solution in Genesys Cloud. -- **Genesys AppFoundry** - The Genesys app marketplace for solutions that run on the Genesys Cloud platform. You download the integration used in this solution from the Genesys AppFoundry. -- **Client Application integration** - The Genesys Cloud integration that embeds third-party webapps via iframe in the Genesys Cloud UI. For more information, see: [Set up a Client Application integration](https://help.mypurecloud.com/?p=131851 "Goes to Set up a Client Application integration page") in the Genesys Cloud Resource Center. +- **Genesys AppFoundry** - The Genesys app marketplace for solutions that run on the Genesys Cloud platform. You download the integration used in this solution from the Genesys AppFoundry. +- **About Client Application integration** - The Genesys Cloud integration that embeds third-party webapps via iframe in the Genesys Cloud UI. For more information, see: [About Client Application integrations](https://help.mypurecloud.com/?p=131847 "Goes to About Client Application integrations article") in the Genesys Cloud Resource Center. ## Prerequisites ### Specialized knowledge -* Experience with Typescript or JavaScript * Administrator-level knowledge of Genesys Cloud - +* Experience with Typescript or JavaScript +* Experience using the Genesys Cloud Platform API ### Software development kit (SDK) -- **Platform API JavaScript Client** - The sample app employs React+TypeScript; thus, the javaScript SDK is used. However, the same functionality could be achieved using other languages. For more information, see: [Platform API Javascript Client](https://developer.genesys.cloud/api/rest/client-libraries/javascript/ "Goes to Platform API Javascript Client page") in the Genesys Cloud Developer Center. -- **Genesys Cloud Client App SDK** - A JavaScript library used to integrate third-party web-based applications with Genesys Cloud. Handles app and UI-level integrations such as navigation, alerting, attention, and lifecycle management. - -## Requirements - -### Specialized knowledge - -This solution requires implementation experience in several areas or a willingness to learn: - -- Administrator-level knowledge of Genesys Cloud -- Genesys Cloud Platform API knowledge -- React knowledge -- TypeScript knowledge +- **Platform API JavaScript Client** - The sample app employs React+TypeScript; thus, the javaScript SDK is used. However, the same functionality could be achieved using other languages. For more information see, [Platform API Javascript Client](https://developer.genesys.cloud/api/rest/client-libraries/javascript/ "Goes to Platform API Javascript Client page") in the Genesys Cloud Developer Center. +- **Genesys AppFoundry** - The Genesys app marketplace for solutions that run on the Genesys Cloud platform. You download the integration used in this solution from the Genesys AppFoundry. ### Genesys Cloud account requirements -This solution requires a Genesys Cloud license. For more information, see: [Genesys Cloud pricing](https://www.genesys.com/pricing "Goes to Pick the Perfect Plan for your Business page"). +This solution requires a Genesys Cloud license. For more information see, [Genesys Cloud pricing](https://www.genesys.com/pricing "Goes to Pick the Perfect Plan for your Business page"). -A recommended Genesys Cloud role for the solutions engineer is the Master Admin. For more information, see: [Roles and permissions overview](https://help.mypurecloud.com/?p=24360 "Goes to Roles and permissions overview article") in the Genesys Cloud Developer Center. +A recommended Genesys Cloud role for the solutions engineer is the Master Admin. For more information see, [Roles and permissions overview](https://help.mypurecloud.com/?p=24360 "Goes to Roles and permissions overview article") in the Genesys Cloud Developer Center. ## Running locally ### Download the repository that contains the project files -For more information, see: [Partial Transcription Blueprint](https://github.com/GenesysCloudBlueprints/partial-transcription-blueprint "Goes to Partial Transcription Blueprint page") in the GitHub repository. + +For more information see, [Partial Transcription Blueprint](https://github.com/GenesysCloudBlueprints/partial-transcription-blueprint "Goes to Partial Transcription Blueprint page") in the GitHub repository. ```bash git clone https://github.com/GenesysCloudBlueprints/partial-transcription-blueprint.git ``` +For more information, see: [Partial Transcription Blueprint](https://github.com/GenesysCloudBlueprints/partial-transcription-blueprint "Goes to Partial Transcription Blueprint page") in the GitHub repository. + ### Create an Implicit Grant OAuth 1. Log in to your Genesys Cloud organization and create a new OAuth Credential (Implicit Grant). [Create an OAuth client](https://help.mypurecloud.com/?p=188023 "Goes to create an OAuth client page") in the Genesys Cloud Resource Center. 2. Add **http://localhost:3000** to the **Authorized redirect URIs**. -**Note**: If the **redirectUri** value has changed in the config file, you must add the new URI. +**Note**: If the **redirectUri** value has changed in the config file, you must add the new URI. -3. Add the following in the Scopes section: +3. Add the following in the Scopes section * analytics * authorization * conversations @@ -83,7 +94,7 @@ git clone https://github.com/GenesysCloudBlueprints/partial-transcription-bluepr ### Update configuration file -Modify the values in the configuration file before running the app. Use the values from the OAuth Client you created in the last step as follows: +Modify the values in the configuration file before running the app. Use the values from the OAuth Client you created in the last step: clientConfig.js: @@ -93,10 +104,9 @@ export const clientConfig = { REDIRECT_URI: '', }; ``` - ### Run the app -Open a terminal and set the working directory to the root directory of the project, then run the following: +Open a terminal and set the working directory to the root directory of the project, then run the following ```bash npm install @@ -105,8 +115,8 @@ npm run start ### Install and activate the Client Application in Genesys Cloud -1. Log in to your Genesys Cloud organization and add an integration. For more information, see [Add an integration](https://help.mypurecloud.com/articles/add-an-integration/ "Goes to Add an integration page") in the Genesys Cloud Resource Center. -2. Install the **Client Application** integration. For more information, see [Set up a Client Application integration](https://help.mypurecloud.com/articles/set-custom-client-application-integration/ "Goes to Set up a Client Application integration page") in the Genesys Cloud Resource Center. +1. Log in to your Genesys Cloud organization and add an integration. For more information see, [Add an integration](https://help.mypurecloud.com/articles/add-an-integration/ "Goes to Add an integration page") in the Genesys Cloud Resource Center. +2. Install the **Client Application** integration. For more information see, [Set up a Client Application integration](https://help.mypurecloud.com/articles/set-custom-client-application-integration/ "Goes to Set up a Client Application integration page") in the Genesys Cloud Resource Center. 3. (Optional) Use the Name box to give the widget a meaningful name (e.g., **Active Conversation Dashboard**). ![Client Application Integration](images/integration.png) @@ -119,25 +129,26 @@ npm run start ![Client Application Integration Config](images/integration-config.png) -8. Activate the Client Application +8. Activate the Client Application integration ### Test the solution -1. Set up a test queue with only you as a member since this guarantees you are assigned inbound calls to the queue. For more information, see: [Create and configure queues](https://help.mypurecloud.com/?p=18650 "Goes to the Create and configure queues page") in the Genesys Cloud Resource Center. -** - Make sure that “Voice Transcription” is enabled in both queue settings, Speech, and Text Analytics: + +1. Set up a test queue with only you as a member since this guarantees you are assigned inbound calls to the queue. For more information see, [Create and configure queues](https://help.mypurecloud.com/?p=18650 "Goes to the Create and configure queues page") in the Genesys Cloud Resource Center. +** - Make sure that “Voice Transcription” is enabled in both queue settings, Speech, and Text Analytics ![Transcription Setting Queue](images/transcription-queue.png) ![Transcription Setting Analytics](images/transcription-speech-and-text.png) -2. Ensure there is an inbound call flow configured to transfer inbound calls to the selected queue. For more information, see: [Work with inbound flows](https://help.mypurecloud.com/articles/work-with-inbound-call-flows/ "Goes to the Work with inbound flows page") in the Genesys Cloud Resource Center. +2. Ensure there is an inbound call flow configured to transfer inbound calls to the selected queue. For more information see, [Work with inbound flows](https://help.mypurecloud.com/articles/work-with-inbound-call-flows/ "Goes to the Work with inbound flows page") in the Genesys Cloud Resource Center. ![Inbound Call Flow](images/inbound-call-flow.png) -3. Ensure there is a call route assigned to the inbound call flow from the previous step. For more information, see: [Add a call route](https://help.mypurecloud.com/articles/add-a-call-route/ "Goes to the Add a call route page") in the Genesys Cloud Resource Center. +3. Ensure there is a call route assigned to the inbound call flow from the previous step. For more information see, [Add a call route](https://help.mypurecloud.com/articles/add-a-call-route/ "Goes to the Add a call route page") in the Genesys Cloud Resource Center. ![Call route](images/call-route.png) -4. Ensure there is a DID number assigned to the call route from the previous step. For more information, see: [Manage DID and toll-free number assignments](https://help.mypurecloud.com/?p=45223 "Goes to the Manage DID and toll-free number assignments page") in the Genesys Cloud Resource Center. +4. Ensure there is a DID number assigned to the call route from the previous step. For more information see, [Manage DID and toll-free number assignments](https://help.mypurecloud.com/?p=45223 "Goes to the Manage DID and toll-free number assignments page") in the Genesys Cloud Resource Center. ![DID Assignment](images/did-assignment.png) @@ -149,7 +160,9 @@ npm run start 10. Open the client Application set up in the previous steps. 11. Find your queue in the **Active Conversation Dashboard** and expand the list to find your active conversations. -## Sample app overview +## Sample App overview + +![Active conversation dashboard](images/embedded-app-title.png "Active conversation dashboard") ### Genesys Cloud Utils @@ -161,17 +174,19 @@ This is the top level of the SPA (Single Page Application). The top-level consis ### Queue listing -Each queue listing consists of an "accordion." Before the expansion, it displays the title and the number of active conversations in the queue. After the expansion, it displays the conversation listings for the queue. +Each queue listing consists of a menu, and before the expansion, it shows the title and the number of active conversations in the queue. After the expansion, it displays the conversation listings for the queue. ### Conversation listing -Each conversation listing is an "accordion." In this case, expanding the listing shows the conversation start time, the "standing" of the conversation (as defined in the summary of this document), the agent assigned to the conversation, and a live transcript. +![Conversation listing](images/embedded-app-in-action.png "Conversation listing") + +Each conversation listing is a menu that expands and shows the conversation start time, the "standing" of the conversation (as defined in the summary of this document), the agent assigned to the conversation, and a live transcript. ## Configure the React project to use Genesys Cloud SDK -Listed are the required steps to integrate the Genesys Cloud SDK into your own React app. +To integrate the Genesys Cloud SDK into your own React app, complete the following steps. -### Creating a React project +### Create a React project If you are creating an app from scratch, run the following commands in a terminal in the directory of your choice: @@ -179,12 +194,11 @@ If you are creating an app from scratch, run the following commands in a termina npm install -g npx npx create-react-app name-of-your-app --template TypeScript ``` - If you configure an existing React app, you should use a version greater than v16.0 since the sample app uses React hooks introduced in React v16.0. See the tsconfig.json file in the root directory of this project for a TypeScript configuration example. ### Install NPM packages -1. Install the Genesys Cloud Platform Client: +1. Install the Genesys Cloud Platform Client ```bash npm install purecloud-platform-client-v2 @@ -192,14 +206,14 @@ If you configure an existing React app, you should use a version greater than v1 ### Import the platform-client-sdk to your project -Use the following process to import the platform-client-sdk: +Use the following process to import the platform-client-sdk ```javascript const platformClient = require('purecloud-platform-client-v2/dist/node/purecloud-platform-client-v2.js'); ``` Now, you can use the various API tools in the platformClient object. -Example: +Example ```javascript const platformClient = require('purecloud-platform-client-v2/dist/node/purecloud-platform-client-v2.js'); diff --git a/src/components/conversation/Conversation.tsx b/src/components/conversation/Conversation.tsx index c065f12..0dfc163 100644 --- a/src/components/conversation/Conversation.tsx +++ b/src/components/conversation/Conversation.tsx @@ -45,7 +45,7 @@ export function Conversation(props: IProps) {
{conversation.assignedAgent?.agentName}
- + agent
diff --git a/src/components/queue-list/QueueList.tsx b/src/components/queue-list/QueueList.tsx index c287b59..e64d316 100644 --- a/src/components/queue-list/QueueList.tsx +++ b/src/components/queue-list/QueueList.tsx @@ -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([]); @@ -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() @@ -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); @@ -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); + } + } } /** @@ -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 @@ -386,7 +430,7 @@ export function QueueList(props: any) { setQueues(newQueues); }; - addSubscription(transcriptionTopic, transcriptionCallback); + addSubscriptionWrapper(transcriptionTopic, transcriptionCallback); } /** diff --git a/src/utils/genesysCloudUtils.ts b/src/utils/genesysCloudUtils.ts index 7d283f7..65bfdde 100644 --- a/src/utils/genesysCloudUtils.ts +++ b/src/utils/genesysCloudUtils.ts @@ -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; @@ -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)}`, diff --git a/src/utils/notificationsController.ts b/src/utils/notificationsController.ts index 6973dff..ad3dd2c 100644 --- a/src/utils/notificationsController.ts +++ b/src/utils/notificationsController.ts @@ -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; }); } @@ -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}`); }); } \ No newline at end of file