Matching and mapping on Graphileon triggers

Matching and mapping on Graphileon triggers. A very powerful mechanism to control your application and data flow!

In the basics, the execution of software applications is a chain reaction: one function triggers another, which triggers another, and so on. The functions and the triggers between them form a closely knit network, that can be modeled as a graph.

Visualization of such a graph illustrates that the application can flow along many paths. This blog post is about the mechanisms we have put in place to fully control the route your application takes and how you can make sure that the next function that is executed will have the right data at its disposal.

Graphileon graph-applications, the basics

Graphileon graph-applications consist of (:IA_Function) nodes that are connected by [:TRIGGER] relationships, forming a set of chained (:IA_Function)-[:TRIGGER]->(:IA_Function) patterns. The patterns are stored in a labeled property graph (LPG).

The (:IA_Function) nodes correspond to application components like

Function Type UI Description
NetworkView yes An interactive graph visualization
TableView yes An interactive table
TreeView yes A hierarchical view of data
Iterate no A list of items that are processed sequentially or in parallel
InputView yes A data entry form
Query no A query that can be executed against a store
Request no A request to a (third party) API endpoint

 

and the [:TRIGGER] relationships to specific function events, like

Function Type Specific event examples
NetworkView nodeClick, relationDoubleClick, canvasClick, link
TableView rowClick, rowDoubleClick
TreeView itemClick, itemExpand, itemCollapse
Iterate iteration, iterationEnd
InputView submit
Query success, error
Request success, error

 

Graphileon is a no-code/low-code platform that allows users to knit together their application, simply by connecting various functions using triggers that are executed in response to certain events, e.g.:

 

Pattern Description
(Query)-[success]->(:TableView) A query that ouputs the result to a table when the query is successful.
(NetworkView)-[nodeClick]->(:Query)-[success]->(TreeView) When a node is clicked, a query is executed and, if successful, its result is visualized in as a hierarchical view.
(Request)-[success]->(Iterate)-[iteration]->(Query)-[success]->(NetworkView) A request is made to an API endpoint. For each item in the result, a query is executed and the result is sent to the network visualiation.

What happens at run time?

In runtime, when a function is “triggered”, its definitions are read from the corresponding (:IA_Function) node, together with the definitions of the outgoing [:TRIGGER] relationships. These definitions are then used to create instances of the function and triggers. In order to execute functions, specific property values have to be set, for example, the ones in the table below.

Function Type Specific properties
NetworkView nodes, relations, layout
TableView data
TreeView items
Iterate data
Query cypher, store
Request url, method, data

 

Each of these function types also has its own set of events to which it responds. The table below gives some typical examples.

Function Type Specific events
NetworkView nodeClick, relationDoubleClick, canvasClick, link
TableView rowClick, rowDoubleClick
TreeView itemExpand, itemCollapse
Iterate iteration, iterationEnd
Query success, error
Request success, error

In addition to these specific events, all functions respond to generic events like:

  • functionExecuted
  • functionUpdated
  • functionClosed

A complete overview of function types, events and properties can be found at https://docs.graphileon.com/graphileon/For_Dashboard_Designers/Reference/Data_Structures.html.

Triggers are fired when function events occur, then they provide the data for the next function

When events occur in a function (e.g. a node is clicked in a NetworkView, a Query is successfully executed, or a successful Request receives a response), only triggers that match the event are executed.
The trigger that is being executed carries information about the event. This information can be used to set the properties for the function that the trigger points at (the function that will be “triggered”). This is what we call mapping.

In the following two paragraphs, we will discuss Graphileon’s capabilities with regard to matching and mapping.

Matching

Let’s look at the above a bit more closely:

” … , the triggers that match the event are executed … “, suggests that there is a process of matching. Indeed there is and it works as follows.

Remember that [:TRIGGER] relationships are stored in an LPG. This allows us to set properties on the relationships. In our trigger definition, we can refer to the event using the shorthand(%) and at runtime, its properties can be accessed directly. Each event has a type property that indicates its nature, and a data property that contains relevant data. During the process of “matching”, the property values of the event are compared to the match-property-value combinations of the trigger. Only if all event property values match the trigger match-property-value conditions the trigger is executed.

NOTE: any trigger property that does not start with $ or #, is a match property. Together with its value it constitues the match-property-value condition.

Imagine a simple Graphileon application, consisting of a query that outputs the result to a table.


As you can see in the image above, to make this work, we have two (:IA_Function) nodes and two [:TRIGGER] relationships connecting them. The JSON below shows the details of the configuration.

{
    "nodes": [
        {
            "id": 635535,
            "labels": [
                "IA_Function"
            ],
            "properties": {
                "cypher": "MATCH (n:Person) RETURN id(n) AS id, n.name AS name LIMIT 10",
                "store": "data",
                "type": "Query"
            }
        },
        {
            "id": 635242,
            "labels": [
                "IA_Function"
            ],
            "properties": {
                "type": "TableView"
            }
        }
    ],
    "relations": [
        {
            "id": 894663,
            "source": 635535,
            "target": 635242,
            "type": "TRIGGER",
            "properties": {
                "#data": "(%).data",
                "type": "success"
            }
        },
        {
            "id": 580428,
            "source": 635535,
            "target": 635242,
            "type": "TRIGGER",
            "properties": {
                "#data": "evaluate([\r\n   {\"message\": \"an error occurred\"}\r\n])",
                "type": "error"
            }
        }
    ]
}

If we look at the properties of the two triggers we see two properties, #data and type . The type property does not start with $ or #, and will therefore serve as a trigger match-property, i.e. it will be matched against the corresponding event property-value condition.

"type": "success" which translates to the condition (%).type == "success"

and

"type": "error" which translates to the condition (%).type == "error"

During runtime, successful execution will result in a success event, and (%).type will be equal to “success”. Consequently, the trigger with "id": 894663 will be executed. In case of an error, e.g. the database is not accessible, there is a mistake in the cypher statement or the user is not allowed to run the query, (%).type will be equal to “error” and the trigger with "id": 894663 would match and be executed.

We can add as many match-properties as we want, as long as they do not start with $ or #. For instance, if we would the trigger with "type": "success" (in Graphileon speak: the “success trigger”) to execute only when there are more than 3 results, we can add a second property-value condition:

"(%).data.length > 3": "true" , which will translate during runtime to the condition (%).data.length > 3.

In the JSON below we have added two more examples of property-value conditions. One of them refers to (@) instead of (%). (@) is shorthand for the global object, that contains information about, among others, the user.

        {
            "id": 894663,
            "source": 635535,
            "target": 635242,
            "type": "TRIGGER",
            "properties": {
                "#data": "(%).data",
                "(%).data.length > 3": true,   // only executes if more than 3 results
                "(%).data[2].name": "Allard",  // only executes if third result is "Allard"
                "(@).user.name":  "Tom",       // only executes if Tom is logged on
                "type": "success",             // only executes if the event type = success
            }
        }

So, while matching is a powerful mechanism to control the execution of triggers, mapping, which is discussed below, is our way of providing input to functions downstream in the application flow.

Mapping

Mapping properties give you full control over the inputs to the next function or functions in your application flow. The properties always start with $ or #, the difference being that when using $, the trigger will send the data as a single value. # will map the data to an array. In the case of #data, the array typically corresponds to a set of rows.

In the example above we have one such mapping property:

"#data": "(%).data"

This property-value combination maps the data that is carried by the event to an array that can be consumed by the function that the trigger is pointing to, in this case, a TableView.

Although the primary function of mapping is to provide data to functions, it can also be used for many other things. Let’s look at some examples.

Container properties

Graphileon UI functions, the ones that have a name that ends with ‘View’, like TableView, NetworkView and TreeView run inside containers. These containers are located in areas. By default, containers can be placed in three areas: sidebar-left, content and sidebar-right. By using mapping properties on a trigger that points to a UI function, you can set the title, the size, make it appear in a specific area, and set other properties.

        {
            "id": 894663,
            "source": 635535,
            "target": 635242,
            "type": "TRIGGER",
            "properties": {
                "#data": "(%).data",
                // ===========
                // properties to change the behaviour of the container
                "$container.title": "evaluate('User: ' + (@).user.name + ' Date: ' +isoDate() )",  
                "$container.closable": true,
                "$container.height" : 400,
                "$container.id": "my container",
                "$area": "sidebar-left",
                // ===========
                "(%).data.length > 3": true,   // only executes if more than 3 results
                "(%).data[2].name": "Allard",  // only executes if third result is "Allard"
                "(@).user.name":  "Tom",       // only executes if Tom is logged on
                "type": "success",             // only executes if the event type = success
            }
        }

Note the use of the evaluate() function to tweak the container title:

"$container.title": "evaluate('User: ' + (@).user.name + ' Date: ' +isoDate() )"

Graphileon allows the use of plain javascript and lodash functions in function and trigger definitions, creating endless possibilities to control the visualization of data and behavior of functions.

Set field values in a form

To illustrate this, we add another (:IA_Function)-[:TRIGGER]->(:IA_Function) pattern to our configuration. We add an InputView function and a rowClick trigger from the TableView to the InputView.

This part of the configuration looks as follows:

{
    "nodes": [
        {
            "id": 635622,
            "labels": [
                "IA_Function"
            ],
            "properties": {
                "type": "InputView",
                "container.id": "myInputView",
               // the schema is edited for readability
                "schema": "evaluate({
                              fields: {
                                id : {
                                  model: 'id',
                                  label: 'Id',
                                  type: 'input',
                                  inputType: 'number',
                                  disabled: true
                                },
                                name : {
                                  model: 'name',
                                  label: 'Name',
                                  type: 'input',
                                  inputType: 'text'
                                },
                                submit: {
                                  type: 'submit',
                                  buttonText: 'Send to TableView'
                                }
                              }
                             )"

            }
        },
        {
            "id": 635242,
            "labels": [
                "IA_Function"
            ],
            "properties": {
                "type": "TableView"
            }
        }
    ],
    "relations": [
        {
            "id": 894664,
            "source": 635242,
            "target": 635622,
            "type": "TRIGGER",
            "properties": {
                "$data": "(%).data",
                "type": "rowClick"
            }
        }
    ]
}

The property-value combination "$data": "(%).data" maps (%).data (the data object in the rowClick event) to the data property of the InputView.

Also, note the setting the container.id of the InputView to a fixed value:

"container.id": "myInputView"

By doing so, Graphileon will re-use the same container every time, instead of spinning up a new container every time the rowClick trigger is executed.

Update the data in a function

If we want to update the data in the TableView so it reflects the changes that we make in the form, we need to add a trigger from the InputView back to the TableView. This trigger has to execute on a submit event only.


        {
            "id": 580430,
            "source": 635622,
            "target": 635242,
            "type": "TRIGGER",
            "properties": {
                "type": "submit"
                "$_instance": "_previous",
               // the "#_update.change.data" is edited for readability
                "#_update.change.data": "evaluate([
                                           {id: (%).data.id, name: (%).data.name}
                                         ])"


            }
        }

In the trigger definition above you have the various properties:

"type": "submit" the match property that ensures that the trigger only executes when (%).type equals “submit”

"#_update.change.data"maps the data on the submit event from the InputView to an array (of length 1) that can be processed by the TableView.

"$_instance": "_previous" indicates that the application flow should go towards the previous instance of the TableView. If we would not do this, Graphileon would initiate a new TableView instance every time the submit trigger would execute.

Delete a function

Although you can always remove function instances from the UI by clicking the close icon, sometimes you want to do it programmatically. This is where the $kill property comes in handy. By setting it to true, the function instance self-destructs.

For instance, if we want the form to be removed from the UI when it is submitted, we can add a trigger that starts from and ends on the InputView.


        {
            "id": 894524,
            "source": 635622,
            "target": 635622,
            "type": "TRIGGER",
            "properties": {
                "$_instance": "_previous",
                "$kill": "true",
                "type": "submit"
            }
        }

The Graphileon App library that is directly accessible from inside the Graphileon contains many other examples of how to match and map using the mechanisms described in this blog post. Feel free to try them out after spinning up your free Graphileon instance in our cloud environment.

The JSON with the example configuration can be downloaded here.

 


Card image cap
Graphileon partners with thatDot to support Quine streaming graph for real-time analytics

Graphileon partners with thatDot to develop connectors for Quine (quine.io), an open-source streaming graph solution for event-driven applications.

Card image cap
Release of Graphileon 3.6.0

The release of Graphileon 3.6.0 brings numerous enhancements and features to this graph database management software. Here are the key highlights: New Components and Features: This version introduces new and improved components (Functions) and incorporates user-requested features, enhancing the functionality of the software. Enhanced Visualization: Users can now customize the visualization of nodes in the … Continued

Card image cap
The Graphileon App Library

Graphileon includes an App Library since version 3.1. The App Library contains demos to inspire you and to get you started building your own graphy applications. We included demo configurations ranging from a simple “Hello World” popup to examples that show you how to work with Google Maps, Google Charts, API calls, node templating or … Continued

Get started with Graphileon Personal Edition in the cloud or on your desktop.

The easiest way to get to know Graphileon is to spin up the Personal Edition in the Graphileon Cloud. It comes with two graph stores installed and access to the App Library with examples and apps. You can also download and install Graphileon Personal Edition to run it on your desktop. Either way, you will be able to build graphy applications an browse your graph stores in a way you never did before.