Before you Begin
This 30-minute tutorial shows you how to use the embedded JavaScript editor in Oracle Digital Assistant to create and deploy an entity event handler component for a composite bag entity.
Background
You can use Apache FreeMarker expressions to support the composite bag's prompting, error, and validation functions, or you can use entity event handlers. When any of these functions require advanced Apache FreeMarker expressions, you might want to opt instead for entity event handlers because they are easier to write, maintain, and debug. For this tutorial, you're going to add event handlers to the composite bag entity that belongs to an expense reporting skill. These event handlers enable the skill to prompt for values, validate these values, and output confirmation messages.
While you can create entity event handlers using external SDKs (the subject of this advanced tutorial), you can instead create them within Oracle Digital Assistant using the embedded JavaScript editor.
What Do You Need?
- Familiarity with JavaScript and Node.js
- Access to Oracle Digital Assistant
- The
EEH_Materials.zip
file, which includes the starter skill and a finished version of the skill. Download this file and then unzip it to your local system.
Explore the Starter Skill
The first thing you need to do is import the starter skill into your Oracle Digital Assistant instance so that you can see what you're working with.
The entity event handler that you are creating for the composite bag entity provides the following functionality:
- Shows an acknowledgement message whenever a user updates an expense type, amount, or date.
- Validates amounts to prevent users from expensing amounts below $5.
- Validates a date to prevent user from submitting an expense that has a future date.
- Sends expense data to a backend server and publishes the expense submission status.
- Publishes expense details provided by the user in a confirmation message.
Note:
This tutorial focuses on entity event handlers, not on theSystem.CommonResponse
or the System.ResolveEntities
components that are used with composite bag entities. If you are new to composite bag entities, then now is a good time to pause and complete the Enable Real-World Entity Extraction with
Composite Bag Entities and Create Different Response Types with the System.CommonResponse Component tutorials before returning here. Import the Skill
- Log into Oracle Digital Assistant.
- Click in the top left corner to open the side menu.
- Expand Development and then click Skills.
- Hide the menu by clicking again.
- Click Import Skill (located at the upper right).
- Browse to, and then select,
ExpenseBotEHStarter(1.0).zip
from the extracted file. Then click Open. - If you're running on Version 21.04 of Oracle Digital Assistant:
- Click Upgrade.
- Enter a new version number in the the Create New Version of the Skill dialog.
- Select the latest platform version. Then click Create.
- Click Train.
- Select Trainer Tm and then click Submit. It may take a few minutes for training to complete, but when it does, you'll be able to explore the basic capabilities of the skill in the next step.
Test the Skill
In this step, we're going to chat with skill using the Conversation Tester (accessed by clicking Preview at the top right). In the context of guiding you through the expense submission conversation, this part of the tutorial will call out the behavior that you will be changing by creating an entity event handler.
- Enter I want to create an expense report.
- When prompted for the expense type, select Meal.
- When prompted for expense date, enter tomorrow. Note that the skill doesn't prevent you from entering a future date.
- When prompted for an amount, enter $2. Again, the skill doesn't perform any validation and doesn't prompt you to enter a higher amount.
- Click Yes. The skill replies with a simple confirmation message. Note in the Skill Tester's Conversation tab that the dialog flow transitions to the
SubmitExpense
state. - Click Reset to clear the conversation, then click Close to dismiss the Conversation Tester.
Create the Entity Event Handler Service
In this step, you're going to use the embedded editor to create an entity event handler for the skill's composite bag entity, cbe.Expense.
- Click Entities in the left navbar.
- Select cbe.Expense.
- Click + Event Handler.
- In the Create Event Handler dialog:
- Enter ExpenseService in the Service Name field.
- Enter resolveExpense in the Handler Name field.
- Click Create.
entity
anditems
objects, but by default, the starter code is populated with theentity
object only. The editor generates theitems
object when you add a template for an item-level event (the next step in this tutorial). An entity event handler exports two objects:metadata
that provides the name of the component (resolveExpense
).- handlers that contain the entity-level, item-level, or custom handler functions.
The starter code also includes a completed entity-level event, event
: A JSON object of the the event-specific properties.context
: An object providing a set of convenience methods that inspect and change the composite bag items and their resolution status, manage the entity queue when multiple bag entities need to be processed, and sends messages to the user.- Click Close (located at the upper right of the editor). The service and handler names appear in the Event Handler menu list in the Entities page. The page also displays the deployment status (Ready).
- To take a look at the deployment, click Components in the left navbar. Then click resolveExpense to see the list of published events. There is one (so far, that is -- you'll be adding others later on). It's the
publishMessage
event that's created by default.
publishMessage
. This event, like all event handlers, is an asynchronous JavaScript function that has two arguments:
Add Item-Level Validator Events
In this step, you'll add validate
functions for the expense amount and the expense date.
- Click Entities in the left navbar.
- Select cbe.Expense.
- Be sure that resolveExpense is selected in the Event Handler menu. Then click Edit .
- In the editor, click + AddEvent (located a the upper left).
- Select Item-Level Events.
- Select ExpenseAmount.
- Select validate.
- If needed, clear Include template comments.
- Click Insert Event. The template now has an
items:
node and avalidate
function forExpenseAmount
:items: { ExpenseAmount: { validate: async (event, context) => { } } }
- Repeat these steps to add a
validate
event to ExpenseDate. When you're done, the template should have the following:items: { ExpenseAmount: { validate: async (event, context) => { } }, ExpenseDate: { validate: async (event, context) => { } } }
- In
ExpenseAmount
, add the following code to thevalidate
event:let amount = event.newValue.amount; let currency = event.newValue.currency; let minExpenseAmount = 5; if (amount < minExpenseAmount) { context.addValidationError('ExpenseAmount', context.translate('ExpenseAmount.text', minExpenseAmount, currency)); return false; }
When you're done, the code should look like this:ExpenseAmount: { validate: async (event, context) => { let amount = event.newValue.amount; let currency = event.newValue.currency; let minExpenseAmount = 5; if (amount < minExpenseAmount) { context.addValidationError('ExpenseAmount', context.translate('ExpenseAmount.text', minExpenseAmount, currency)); return false; } }
- For
ExpenseDate
, add the following code to thevalidate
event:if (new Date(event.newValue.date) > new Date()) { context.addValidationError("ExpenseDate",context.translate('ExpenseDate.text')); return false; }
When you're done, the code should look like this:ExpenseDate: { validate: async (event, context) => { if (new Date(event.newValue.date) > new Date()) { context.addValidationError("ExpenseDate",context.translate('ExpenseDate.text')); return false; } }
Here are a few things to note about the code:
- The
context
object, which is passed to the event handler function, provides theaddValidationError
function for the entity and outputs the validation message that describes both the incorrect input and the expected input ("Expense date cannot be in the future. Please enter 'today' or a date in the past." or "Amounts below 5 dollars cannot be expensed. Please enter a higher amount."). - The
translate
function accesses resource bundle from thecontext
object. To review or update these strings, click Resource Bundles in the left navbar. For example, click the ExpenseDate.text key to see the string for the error message that's displayed when you entered tomorrow in the Skill Tester.
Add Confirmation Events
In this step, you're going to add a confirmation message that allows users to proceed, update a response, or cancel the expense report. You're going to create this message using a combination of the validate
event and publishPromptMessage
event, which enables you to create a customized prompt.
- Add the following code just below
const fetch = require("node-fetch")
;const confirmValues = { YES: 1, NO: 2, CANCEL: 3 };
- Click + AddEvent.
- Select Item-Level Events.
- Select Confirmation then validate.
- Clear Include template comments, then click Insert Event to add the following starter code:
Confirmation: { validate: async (event, context) => { } }
- Click + Add Event
- Click Item-Level Events.
- Select Confirmation (if it's not already selected), then select publishPromptMessage.
- Clear Include template comments (if needed), then click Insert Event. When you're done, the starter code should look like this:
Confirmation: { validate: async (event, context) => { }, publishPromptMessage: async (event, context) => { } }
- Add the following code to the
validate
event:if (event.newValue.value ==='No') { context.setCustomProperty('valSelected',confirmValues.NO); return false; } if (event.newValue.value === 'Cancel') { context.setCustomProperty('valSelected',confirmValues.CANCEL); return false; } if (event.newValue.value === 'Yes') { context.setCustomProperty('valSelected',confirmValues.YES); return true; } return false;
This code sets a custom property variable,'valSelected'
, to NO, CANCEL, or YES based on the user's choice. The custom property is saved in thecontext
, not as a dialog flow variable, which means that it is only available for when the composite bag entity is resolved. - For
publishPromptMessage
, add the following code:updatedItemsMessage(context); if ( (context.getCustomProperty("valSelected") === confirmValues.CANCEL)) { context.cancel(); } else { if ((context.getCustomProperty("valSelected") === confirmValues.NO) && context.getItemsUpdated().length==0) { context.addMessage(context.translate('Confirmation.publishPromptMessage.text1')); } else if (event.promptCount>0 && context.getItemsUpdated().length==0) { context.addMessage(context.translate('Confirmation.publishPromptMessage.text2')); } else { let message = context.translate('Confirmation.publishPromptMessage.text3')+ '\n'; message+= context.getDisplayValues().map(v => v.name+": "+v.value).join("\n"); message+='\n\n'+ context.translate('Confirmation.publishPromptMessage.text4'); context.addMessage(message); } } context.setCustomProperty("valSelected",null);
This code:- Checks if user hasn't confirmed the expense report, but chooses to update any item.
- Triggers the
cancel
action if the user enters Cancel so that the dialog transitions to thecancelExpense
state.
The completed Confirmation
code should look like this:
Confirmation: { validate:async (event, context) => { if (event.newValue.value ==='No') { context.setCustomProperty('valSelected',confirmValues.NO); return false; } if (event.newValue.value === 'Cancel') { context.setCustomProperty('valSelected',confirmValues.CANCEL); return false; } if (event.newValue.value === 'Yes') { context.setCustomProperty('valSelected',confirmValues.YES); return true; } return false; }, publishPromptMessage:async (event, context) => { updatedItemsMessage(context); if ( (context.getCustomProperty("valSelected") === confirmValues.CANCEL)) { context.cancel(); } else { if ((context.getCustomProperty("valSelected") === confirmValues.NO) && context.getItemsUpdated().length==0) { context.addMessage(context.translate('Confirmation.publishPromptMessage.text1')); } else if (event.promptCount>0 && context.getItemsUpdated().length==0) { context.addMessage(context.translate('Confirmation.publishPromptMessage.text2')); } else { let message = context.translate('Confirmation.publishPromptMessage.text3')+ '\n'; message+= context.getDisplayValues().map(v => v.name+": "+v.value).join("\n"); message+='\n\n'+ context.translate('Confirmation.publishPromptMessage.text4'); context.addMessage(message); } } context.setCustomProperty("valSelected",null); } } } }
Add Entity-Level Event
Now that you have completed the item-level validations, you're ready to add the code that resolves the entities, submits the expense report request to a backend service and returns the expense report status message. A real-world skill would submit the expense details to a backend service through a REST call, but for the purposes of this tutorial, we're just going to simulate this REST call.
- Click + AddEvent.
- Select Entity-Level Events.
- Choose resolved.
- Clear Include template comments (if needed), and then click Insert Event. The following starter code is added to the
entity
object:resolved: async (event, context) => { }
- Add following code to the
resolved
event:/* try { const response = await fetch('https://my-json-server.typicode.com/rohitdhamija/fakeserver/expense'); if (response.status === 200) { const data = await response.json(); context.addMessage(data[0].expense_status); } } catch (error) { context.logger().info(error); } */ context.addMessage(context.translate('resolved.expenseStatus.text'));
Thenode-fetch
API makes the REST call. This API is pre-installed with thebots-node-sdk
, so you don't need to add any additional libraries. Thecontext.addMessage
function creates the message that reports the status of the expense report submission.When you're done, the code should look like this:
resolved: async (event, context) => { /* try { const response = await fetch('https://my-json-server.typicode.com/rohitdhamija/fakeserver/expense'); if (response.status === 200) { const data = await response.json(); context.addMessage(data[0].expense_status); } } catch (error) { context.logger().info(error); } */ context.addMessage(context.translate('resolved.expenseStatus.text')); }
- Now that you've completed the entity event handler, click Save and then click Close.
- Check out the event handlers that you've added by first clicking Components in the left navbar. Then click resolveEntities.
Update the Dialog Flow
Since the entity-level event handler manages the expense report submission message, the dialog flow definition no longer requires the submitExpense
state. In this step, we're going to remove this state and update the createExpense
state with a new transition that ends the conversation and resets all of the variable values.
- Open the dialog flow editor by clicking Flows in the left navbar.
- Delete the
submitExpense
state. - In the
createExpense
state, replace thenext: "submitExpense"
action with the following:return: "done"
Note:
This action must be six spaces from the left margin so that it aligns withactions
.main: true name: RD_ExpenseBotEEH #context: Define the variables which will used throughout the dialog flow here. context: variables: iResult: "nlpresult" expense: "cbe.Expense" rb: "resourcebundle" states: intent: component: "System.Intent" properties: variable: "iResult" transitions: actions: expense.reg.CreateExpense: "createExpense" unresolvedIntent: "unresolvedIntent" createExpense: component: "System.ResolveEntities" properties: variable: "expense" useFullEntityMatches: true nlpResultVariable: "iResult" cancelPolicy: "immediate" transitions: actions: cancel: "cancelExpense" return: "done" cancelExpense: component: "System.Output" properties: text: "${rb('cancelExpense.text')}" transitions: return: "done" unresolvedIntent: component: "System.Output" properties: text: "${rb('unresolvedIntent.text')}" transitions: return: "done"
Test the Event Handlers
Now that you've completed the event handlers, you're ready to see how they help the skill to respond.
- Click to open the Conversation Tester.
- Enter create meal expense.
- When prompted for an expense date, enter tomorrow. The skill should respond "Expense date cannot be in the future. Please enter 'today' or a date in the past."
- Enter yesterday.
- When prompted, enter $50.
- Enter can you please update amount to $60. The skill responds with a confirmation.
- Enter yes to proceed. The skill should respond with the submission status (which is handled by composite bag entity defined for the CreateExpense state), ending the conversation.
Learn More
Create an Entity Event Handler with Oracle Digital Assistant
F41353-02
April 2021
Copyright © 2021, Oracle and/or its affiliates.
Shows you how to create an entity event handler for a composite bag entity using the editor in Oracle Digital Assistant.
This software and related documentation are provided under a license agreement containing restrictions on use and disclosure and are protected by intellectual property laws. Except as expressly permitted in your license agreement or allowed by law, you may not use, copy, reproduce, translate, broadcast, modify, license, transmit, distribute, exhibit, perform, publish, or display any part, in any form, or by any means. Reverse engineering, disassembly, or decompilation of this software, unless required by law for interoperability, is prohibited.
If this is software or related documentation that is delivered to the U.S. Government or anyone licensing it on behalf of the U.S. Government, then the following notice is applicable:
U.S. GOVERNMENT END USERS: Oracle programs (including any operating system, integrated software, any programs embedded, installed or activated on delivered hardware, and modifications of such programs) and Oracle computer documentation or other Oracle data delivered to or accessed by U.S. Government end users are "commercial computer software" or "commercial computer software documentation" pursuant to the applicable Federal Acquisition Regulation and agency-specific supplemental regulations. As such, the use, reproduction, duplication, release, display, disclosure, modification, preparation of derivative works, and/or adaptation of i) Oracle programs (including any operating system, integrated software, any programs embedded, installed or activated on delivered hardware, and modifications of such programs), ii) Oracle computer documentation and/or iii) other Oracle data, is subject to the rights and limitations specified in the license contained in the applicable contract. The terms governing the U.S. Government's use of Oracle cloud services are defined by the applicable contract for such services. No other rights are granted to the U.S. Government.
This software or hardware is developed for general use in a variety of information management applications. It is not developed or intended for use in any inherently dangerous applications, including applications that may create a risk of personal injury. If you use this software or hardware in dangerous applications, then you shall be responsible to take all appropriate fail-safe, backup, redundancy, and other measures to ensure its safe use. Oracle Corporation and its affiliates disclaim any liability for any damages caused by use of this software or hardware in dangerous applications.
Oracle and Java are registered trademarks of Oracle and/or its affiliates. Other names may be trademarks of their respective owners.
Intel and Intel Inside are trademarks or registered trademarks of Intel Corporation. All SPARC trademarks are used under license and are trademarks or registered trademarks of SPARC International, Inc. AMD, Epyc, and the AMD logo are trademarks or registered trademarks of Advanced Micro Devices. UNIX is a registered trademark of The Open Group.
This software or hardware and documentation may provide access to or information about content, products, and services from third parties. Oracle Corporation and its affiliates are not responsible for and expressly disclaim all warranties of any kind with respect to third-party content, products, and services unless otherwise set forth in an applicable agreement between you and Oracle. Oracle Corporation and its affiliates will not be responsible for any loss, costs, or damages incurred due to your access to or use of third-party content, products, or services, except as set forth in an applicable agreement between you and Oracle.