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:

Note:

This tutorial focuses on entity event handlers, not on the System.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

  1. Log into Oracle Digital Assistant.
  2. Click menu icon in the top left corner to open the side menu.
  3. Expand Development and then click Skills.
  4. Hide the menu by clicking menu icon again.
  5. Click Import Skill (located at the upper right).
    Description of  follows
    Description of the illustration import_skill.png
  6. Browse to, and then select, ExpenseBotEHStarter(1.0).zip from the extracted file. Then click Open.
  7. If you're running on Version 21.04 of Oracle Digital Assistant:
  8. Click Train.
    Description of  follows
    Description of the illustration train.png
  9. 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.
    Description of  follows
    Description of the illustration choose_training_model_dialog.png

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.

Description of this image follows
Description of the illustration skill_tester_top_margin.png

  1. Enter I want to create an expense report.
  2. When prompted for the expense type, select Meal.
  3. When prompted for expense date, enter tomorrow. Note that the skill doesn't prevent you from entering a future date.
  4. 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.
  5. 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.
    Description of  follows
    Description of the illustration initial_conversation.png
  6. Click Reset to clear the conversation, then click Close The Close icon to dismiss the Conversation Tester.
    Components menu icon

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.

  1. Click Entities Entities Menu Icon in the left navbar.
  2. Select cbe.Expense.
  3. Click + Event Handler.
    Description of create_eeh.png follows
    Description of the illustration default_editor.png
  4. In the Create Event Handler dialog: The handler code can include both entity and items objects, but by default, the starter code is populated with the entity object only. The editor generates the items 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.
  5. The starter code also includes a completed entity-level event, publishMessage. This event, like all event handlers, is an asynchronous JavaScript function that has two arguments:
    • 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.
      Description of sample-image-1.png follows
      Description of the illustration default_editor.png
  6. 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).
    Description of eeh_status.png follows
    Description of the illustration eeh_status.png
  7. To take a look at the deployment, click Components Components menu icon 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.
    Description of publishmessage.png follows
    Description of the illustration publishmessage.png

Add Item-Level Validator Events

In this step, you'll add validate functions for the expense amount and the expense date.

  1. Click Entities Entities Menu Icon in the left navbar.
  2. Select cbe.Expense.
  3. Be sure that resolveExpense is selected in the Event Handler menu. Then click Edit The Edit icon.
    Description of  follows
    Description of the illustration edit_event_handler.png
  4. In the editor, click + AddEvent (located a the upper left).
    Components menu icon
  5. Select Item-Level Events.
    Description of  follows
    Description of the illustration select_event_category.png
  6. Select ExpenseAmount.
    Description of  follows
    Description of the illustration expenseAmount.png
  7. Select validate.
  8. If needed, clear Include template comments.
  9. Click Insert Event.
    Description of  follows
    Description of the illustration add_validate_event.png
    The template now has an items: node and a validate function for ExpenseAmount:
        items: {
    
          ExpenseAmount: {
    
            validate: async (event, context) => {
              
            }
    
          }
    
        }
  10. 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) => {
              
            }
    
          }
    
    
        }
    
  11. In ExpenseAmount, add the following code to the validate 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;
                }         
            }
  12. For ExpenseDate, add the following code to the validate 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 the addValidationError 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 the context object. To review or update these strings, click Resource Bundles Resource Bundles icon 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.
    Description of resource_bundle_keys.png follows
    Description of the illustration resource_bundle_keys.png

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.

  1. Add the following code just below const fetch = require("node-fetch");
    const confirmValues = {
      YES: 1,
      NO: 2,
      CANCEL: 3
    };
    
    Description of this image follows
  2. Click + AddEvent.
  3. Select Item-Level Events.
  4. Select Confirmation then validate.
  5. Clear Include template comments, then click Insert Event to add the following starter code:
          Confirmation: {
    
            validate: async (event, context) => {
              
            }
    
          }
  6. Click + Add Event
  7. Click Item-Level Events.
  8. Select Confirmation (if it's not already selected), then select publishPromptMessage.
  9. 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) => {
              
            }
    
    
    
          }
       
  10. 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 the context, not as a dialog flow variable, which means that it is only available for when the composite bag entity is resolved.
  11. 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 the cancelExpense 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.

  1. Click + AddEvent.
  2. Select Entity-Level Events.
    Description of  follows
    Description of the illustration add_entity_level_events.png
  3. Choose resolved.
  4. Clear Include template comments (if needed), and then click Insert Event.
    Description of  follows
    Description of the illustration add_entity_resolved.png
    The following starter code is added to the entity object:
          resolved: async (event, context) => {
              
          } 
  5. Description of this image follows
    Description of the illustration resolved_event.png
  6. 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'));
    
    The node-fetch API makes the REST call. This API is pre-installed with the bots-node-sdk, so you don't need to add any additional libraries. The context.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'));
             } 
  7. Now that you've completed the entity event handler, click Save and then click Close.
  8. Check out the event handlers that you've added by first clicking Components Components menu icon in the left navbar. Then click resolveEntities.
    Description of this image follows
    Description of the illustration completed_events.png

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.

  1. Open the dialog flow editor by clicking Flows Flows icon in the left navbar.
  2. Delete the submitExpense state.
  3. In the createExpense state, replace the next: "submitExpense" action with the following:
          return: "done" 

    Note:

    This action must be six spaces from the left margin so that it aligns with actions.
    When you're done, the dialog flow definition should look like this:
    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.

  1. Click The Skill Tester Icon to open the Conversation Tester.
  2. Enter create meal expense.
  3. 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."
  4. Enter yesterday.
  5. When prompted, enter $50.
  6. Enter can you please update amount to $60. The skill responds with a confirmation.
  7. 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.
  8. Description of  follows
    Description of the illustration final_conversation.png

Learn More