Using Analytics

Relay has some helpful tools for capturing data from your workflows for your own benefit. In this section, we will talk about how you can capture data, and then review the data that you have captured during your workflows. This data may be for logs, or analytics, or some other purpose you choose.

πŸ“˜

URN Type for Generating Analytics

When calling logUserEvent or logMessage, the target parameter can be either a device URN or an interaction URN. You also have the option to leave this parameter undefined, then it will not include the device id in the logged data.

Functions for Capturing Data

If you created a workflow and would like to be able to capture and later retrieve data on that workflow, Relay provides some helpful functions for that. This includes the date and time it was triggered, the category and type of workflow, and who triggered it. Using the logUserEvent() or logMessage() functions within a workflow allows you to record that data from within a workflow.

The logUserEvent() function takes in three parameters: a message that you would like to be captured, the URN of the device that triggered that event, and the category. All of these are strings. The return type is void, since this function copies information on when and how this function was triggered into the database on the server. Unlike the logMessage() function, logUserEvent() includes a parameter for the URN of who triggered the workflow. The logMessage() function performs similarly tologUserEvent(), except it does not include the source URN parameter and therefore that parameter is not recorded in the data. Note that the maximum number of characters that you can pass into the content, or message, parameter is 1000, and the maximum number of characters that you can pass into the category parameter is 50.

Capturing the Data From Your Workflow

The following is an example of a workflow that uses the logUserEvent() function to log data during a workflow. This is the same Hello World workflow that you have seen in the Create your first workflow section, the only difference is that we added the analytics function.

import { relay, Event, createWorkflow, Uri } from '@relaypro/sdk'

const app = relay()

const helloWorkflow = createWorkflow(workflow => {
  const interactionName = 'hello interaction'
  
  workflow.on(Event.START, async (event) => {
    const { trigger: { args: { source_uri } } } = event
    await workflow.startInteraction(source_uri, interactionName)
  })

  workflow.on(Event.INTERACTION_STARTED, async ({ source_uri: interaction_uri }) => {
    const deviceName = Uri.parseDeviceName(interaction_uri)
    console.log(`interaction start ${interaction_uri}`)
    await workflow.sayAndWait(interaction_uri, `What is your name ?`)
    const { text: userProvidedName } = await workflow.listen(interaction_uri)
    const greeting = await workflow.getVar(`greeting`)
    await workflow.sayAndWait(interaction_uri, `${greeting} ${userProvidedName}! You are currently using ${deviceName}`)
    
    // We logged that this workflow has ran with the following information
    await workflow.logUserMessage('Hello world workflow was triggered.', interaction_uri, 'Communication')
    
    await workflow.endInteraction(interaction_uri)
  })

  workflow.on(Event.INTERACTION_ENDED, async() => {
    await workflow.terminate()
  })
})

app.workflow('hellopath', helloWorkflow)
import relay.workflow
import os
import logging

port = os.getenv('PORT', 8080)
wf_server = relay.workflow.Server('0.0.0.0', port, log_level=logging.INFO)
hello_workflow = relay.workflow.Workflow('hello workflow')
wf_server.register(hello_workflow, '/hellopath')

interaction_name = 'hello interaction'


@hello_workflow.on_start
async def start_handler(workflow, trigger):
    target = workflow.make_target_uris(trigger)
    await workflow.start_interaction(target, interaction_name)


@hello_workflow.on_interaction_lifecycle
async def lifecycle_handler(workflow, itype, interaction_uri, reason):
    if itype == relay.workflow.TYPE_STARTED:
        await workflow.say_and_wait(interaction_uri, 'hello world')
        await workflow.end_interaction(interaction_uri)
   			# We log that this workflow has ran with the following information
        await workflow.log_user_message("Hello world workflow was triggered", interaction_uri, "Communication")
    if itype == relay.workflow.TYPE_ENDED:
        await workflow.terminate()


wf_server.start()
using RelayDotNet;

namespace SamplesLibrary
{
  public class HelloWorldWorkflow : AbstractRelayWorkflow
  {
    public HelloWorldWorkflow(Relay relay) : base(relay)
    {
    }

    public override void OnStart(IDictionary<string, object> dictionary)
    {
      var trigger = (Dictionary<string, object>) dictionary["trigger"];
      var triggerArgs = (Dictionary<string, object>) trigger["args"];
      var deviceUri = (string) triggerArgs["source_uri"];

      Relay.StartInteraction(this, deviceUri, "hello world", new Dictionary<string, object>());
    }

    public override async void OnInteractionLifecycle(IDictionary<string, object> dictionary)
    {
      var type = (string) dictionary["type"];

      if (type == InteractionLifecycleType.Started)
      {
        var sourceUri = (string) dictionary["source_uri"];
        var deviceName = await Relay.GetDeviceName(this, sourceUri);
        await Relay.SayAndWait(this, sourceUri, "What is your name?");
        var listenResponse = await Relay.Listen(this, sourceUri);
        var greeting = await Relay.GetVar(this, "greeting", "hello");
        await Relay.SayAndWait(this, sourceUri, $"{greeting} {listenResponse["text"]}! You are currently using {deviceName}");

        // We logged that this workflow has ran with the following information
        await Relay.LogUserMessage(this, "Hello world workflow was triggered", sourceUri, "Communication");

        Relay.EndInteraction(this, sourceUri);
      }
      else if (type == InteractionLifecycleType.Ended)
      {
        Relay.Terminate(this);
      }
    }
  }
}
package com.relaypro.app.examples.standalone;

import com.relaypro.app.examples.util.JettyWebsocketServer;
import com.relaypro.sdk.Relay;
import com.relaypro.sdk.Workflow;
import com.relaypro.sdk.types.InteractionLifecycleEvent;
import com.relaypro.sdk.types.StartEvent;

import java.util.Map;

public class HelloWorld {

    public static void main(String... args) {
        int port = 8080;
        Map<String, String> env = System.getenv();
        try {
            if (env.containsKey("PORT") && (Integer.parseInt(env.get("PORT")) > 0)) {
                port = Integer.parseInt(env.get("PORT"));
            }
        } catch (NumberFormatException e) {
            System.err.println("Unable to parse PORT env value as an integer, ignoring: " + env.get("PORT"));
        }
        Relay.addWorkflow("hellopath", new MyWorkflow());

        // Note that this uses the Jetty websocket server implementation in the app's util package.
        JettyWebsocketServer.startServer(port);
    }

    private static class MyWorkflow extends Workflow {
        private final Logger logger = LoggerFactory.getLogger(MyWorkflow.class);
        private final String INTERACTION_NAME = "hello interaction";

        @Override
        public void onStart(Relay relay, StartEvent startEvent) {
            super.onStart(relay, startEvent);

            String sourceUri = Relay.getSourceUri(startEvent);
            relay.startInteraction( sourceUri, INTERACTION_NAME, null);
        }

        @Override
        public void onInteractionLifecycle(Relay relay, InteractionLifecycleEvent lifecycleEvent) {
            super.onInteractionLifecycle(relay, lifecycleEvent);

            String interactionUri = (String)lifecycleEvent.sourceUri;
            if (lifecycleEvent.isTypeStarted()) {
              String deviceName = relay.getDeviceName(interactionUri, false);
              relay.say(interactionUri, "What is your name?");
              String returned = relay.listen(interactionUri, "request_1");
              relay.say(interactionUri, "Hello " + returned + ". You are currently using " + deviceName);
              
              // We logged that this workflow has ran with the following information
       				relay.logUserMessage("Hello world workflow was triggered", interactionUri, "Communication");
              relay.endInteraction(interactionUri);
            }
            if (lifecycleEvent.isTypeEnded()) {
                relay.terminate();
            }
        }
    }
}
package main

import (
    "relay-go/pkg/sdk"
    log "github.com/sirupsen/logrus"
)

var port = ":8080"

func main() {
  log.SetLevel(log.InfoLevel)

  sdk.AddWorkflow("hellopath", func(api sdk.RelayApi) {
    var sourceUri string

    api.OnStart(func(startEvent sdk.StartEvent) {
      sourceUri := api.GetSourceUri(startEvent)
      log.Debug("Started hello wf from sourceUri: ", sourceUri, " trigger: ", startEvent.Trigger)
      api.StartInteraction(sourceUri, "hello interaction")
    })

    api.OnInteractionLifecycle(func(interactionLifecycleEvent sdk.InteractionLifecycleEvent) {
      log.Debug("User workflow got interaction lifecycle: ", interactionLifecycleEvent)

      if interactionLifecycleEvent.LifecycleType == "started" {
        sourceUri = interactionLifecycleEvent.SourceUri
        var deviceName = api.GetDeviceName(sourceUri, false)
        api.SayAndWait(sourceUri, "What is your name?", sdk.ENGLISH)
        var name = api.Listen(sourceUri, []string {}, false, sdk.ENGLISH, 30)
        api.Say(sourceUri, "Hello " + name + " you are currently using " + deviceName, sdk.ENGLISH)
        
        // We logged that this workflow has ran with the following information
        api.LogUserMessage("Hello world workflow was triggered", interactionUri, "Communication")  
        api.EndInteraction(sourceUri)
      }

      if interactionLifecycleEvent.LifecycleType == "ended" {
        log.Debug("i'm a callback for interaction lifecycle: ", interactionLifecycleEvent)
        api.Terminate()
      }
    })
  })

  sdk.InitializeRelaySdk(port)
}

Viewing Your Analytics

After you run a workflow that calls one of the analytical functions, you can view that data through the CLI's relay workflow analytics command. To get the usage of this command, you can enter relay workflow analytics --help into the command line.

$ relay workflow analytics --help
Display and filter workflow analytics

USAGE
  $ relay workflow:analytics -s <value> [--json] [-w <value>] [-i <value>] [-u <value>] [-c <value>] [-t system|user] [-p] [--columns <value> | -x] [--sort <value>] [--filter <value>] [--output csv|json|yaml | 
    | [--csv | --no-truncate]] [--no-header | ]

FLAGS
  -s, --subscriber-id=<value>         (required) [default: 5e70d451-7836-4196-b347-8f6278b671f0] subscriber id
  -c, --category=<value>              analytic category
  -i, --workflow-instance-id=<value>  workflow instance id
  -p, --parse                         whether to parse/process the analytic content based on the 'content_type'
  -t, --type=(system|user)            analytic type
  -u, --user-id=<value>               user id
  -w, --workflow-id=<value>           workflow id
  -x, --extended                      show extra columns
  --columns=<value>                   only show provided columns (comma-separated)
  --csv                               output is csv format [alias: --output=csv]
  --filter=<value>                    filter property by partial string matching, ex: name=foo
  --no-header                         hide table header from output
  --no-truncate                       do not truncate output to fit screen
  --output=<option>                   output in a more machine friendly format
                                      <options: csv|json|yaml>
  --sort=<value>                      property to sort by (prepend '-' for descending)

GLOBAL FLAGS
  --json  Format output as json.

DESCRIPTION
  Display and filter workflow analytics

With this command, you can view all analytical events that have occurred on your account. The output should include which workflows were triggered, when they were triggered and terminated, the ID of the device on which the event occurred, whether it was logged by the user or the system, any errors that have occurred on the system, and more. Since the output can get pretty lengthy, lets look at some ways you can specify which data you want to retrieve using some of the CLI flags.

Viewing Analytics on a Specific Workflow

You can easily view the analytics on a specific workflow by retrieving the workflow's ID, and then running the analytics command with the --workflow-id flag followed by that ID. Remember, to get the workflow ID you can use the relay workflow list command.

$ relay workflow list
=== Installed Workflows

 ID                                     Name       Type          
 ────────────────────────────────────── ────────── ───────────── 
 wf_helloworld_dRWhWtV4Ppz3XRxy6qfGKD   helloworld phrases:hello  
 wf_broadcast_GEwwc9Bkd9C1Ci9AcDFVVyKYD broadcast  phrases:cleanup

Now, you can copy the workflow's ID and use that as your argument when listing out analytics for that workflow. After you run that command, you should see something like the following. Be sure to scroll to the right in the CLI window below to see the good stuff, like the User ID and Content categories.

$ relay workflow analytics --workflow-id=wf_helloworld_dRWhWtV4Ppz3XRxy6qfGKD
=> Showing up to 100 events
 Workflow ID                          Type   Category      Instance ID            Timestamp                   User ID         Content                                                       
 ──────────────────────────────────── ────── ───────────── ────────────────────── ─────────────────────────── ─────────────── ───────────────────────────────────────────────────────────── 
 wf_helloworld_dRWhWtV4Ppz3XRxy6qfGKD system workflow      sCD7gNuWUKyLOWbLSVeKpB 2022-10-28T13:13:57.550649Z 990007560151234 {                                                             
                                                                                                                                "command": "terminate",                                     
                                                                                                                                "end_time": "2022-10-28T13:13:57Z",                         
                                                                                                                                "last_api_request": {                                       
                                                                                                                                  "event": "wf_api_terminate_request",                      
                                                                                                                                  "state_name": "running",                                  
                                                                                                                                  "type": "cast"                                            
                                                                                                                                },                                                          
                                                                                                                                "start_time": "2022-10-28T13:13:55Z",                       
                                                                                                                                "terminate_reason": "normal",                               
                                                                                                                                "trigger_event": {                                          
                                                                                                                                  "phrase": "test",                                         
                                                                                                                                  "spillover": "undefined",                                 
                                                                                                                                  "trigger_type": "phrase"                                  
                                                                                                                                },                                                          
                                                                                                                                "workflow_uri": "wss://myserver.myhost.com.com/hellopath" 
                                                                                                                              }                                                             
 wf_helloworld_dRWhWtV4Ppz3XRxy6qfGKD user   Communication sCD7gNuWUKyLOWbLSVeKpB 2022-10-28T13:13:57.299237Z 990007560151234 Hello world workflow was triggered

As is the case with most other CLI commands, the output is organized into categories for easy navigation. Under the Content category is where you'll find most of the analytical information regarding your workflows. This includes the start and end time of the workflow, the reason for it's termination, the trigger that was used, etc. Notice you can also see the data that you logged in your workflow. In the example above, I logged an event under a category called "Communication" that logs the "Hello world workflow was triggered".

Filtering and Sorting

Since these commands can show up to 100 events, you might want a way to efficiently view the specific data that you need. This is where filtering, sorting and other flags for organizing your data come in handy. You can use these flags in all other CLI commands that produce output in the form of a table, such as relay workflow list (see more on how to sort the output of this command in the Using the CLI section). If you want to filter your data retrieved from the relay workflow analytics command to only include a certain type (system or user), category, or so on, you can use the --filter= flag. For example, if I wanted to filter my data to only include analytical events from the "user" type, I would enter the following.

$ relay workflow analytics --type=user
=> Showing up to 100 events
 Workflow ID                            Type Category      Instance ID            Timestamp                   User ID         Content                            
 ────────────────────────────────────── ──── ───────────── ────────────────────── ─────────────────────────── ─────────────── ────────────────────────────────── 
 wf_broadcast_GEwwc9Bkd9C1Ci9AcDFVVyKYD user Cleaning      s8tCfp5Stzqx1NhMWqU8ZA 2022-10-28T16:15:03.733766Z                 A cleanup broadcast was sent out  
 wf_helloworld_dRWhWtV4Ppz3XRxy6qfGKD   user Communication sCD7gNuWUKyLOWbLSVeKpB 2022-10-28T13:13:57.299237Z 990007560151234 Hello world workflow was triggered

Now, only the analytics that I created in my workflows with the API call is shown, hence the "user" type.

You can also use the --category flag to only show data from a certain category. Remember, you can customize your categories when you make the API calls in your workflows. Here, I want to only view analytics from the "Communication" category. Using this flag, I can view the analytics on workflows under this category.

$ relay workflow analytics --category=Communication
=> Showing up to 100 events
 Workflow ID                          Type Category      Instance ID            Timestamp                   User ID         Content                            
 ──────────────────────────────────── ──── ───────────── ────────────────────── ─────────────────────────── ─────────────── ────────────────────────────────── 
 wf_helloworld_dRWhWtV4Ppz3XRxy6qfGKD user Communication sCD7gNuWUKyLOWbLSVeKpB 2022-10-28T13:13:57.299237Z 990007560151234 Hello world workflow was triggered

That's it! Now you know how to log analytics on your workflows, and retrieve that data using the CLI.