Article
· Jun 7 7m read

Tasks flow with InterSystems IRIS Workflow Engine - Connecting to external applications

Finally and with a little delay, we conclude this series of articles about our Workflow Engine by showing an example of the connection that we could make from a mobile application.

In the previous article we showed the example that we were going to discuss, an application that allows detailed control of a chronic pathology such as hypertension for both the patient and their associated doctor. In this example, the patient will have access from their mobile phone to a web application (basically, a web page designed to be responsive with the device) in which they will receive notifications based on the measurements that the portable blood pressure monitor sends to the IRIS instance.

Therefore we will have two different accesses to our IRIS instance:

  • User access from mobile application.
  • Device access to submit blood pressure readings.

In this article we will see the first of them that allows the patient to manage the tasks that their readings generate.

Connection mobile application - IRIS

For this connection, the simplest thing is to configure a web application within IRIS and to do this we will access it from the management portal, System Administration -> Security -> Applications -> Web Applications:

Next, in the list shown to us, we will click on Create new application, which will open a screen like the following::

On this screen let's configure the following fields:

  • Name: in which we will define the URL that we will publish to give access to our functionality deployed in IRIS.
  • Namespace: the namespace to which we want the web application to be associated, this will allow us to later take advantage of the functionalities of interoperability productions.
  • REST: We will select this option since what we are going to publish is a REST API to allow HTTP connections.
  • Dispatch Class: ObjectScript class that will receive the HTTP call and decide what to do with it.
  • Use JWT Authentication: checking this option will enable the /login and /logout endpoints on the URL that we have defined for our application, which will allow us to obtain a JSON Web Token to authenticate our calls over IRIS.
  • Security Settings -> Allowed Authentication Methods: we will set a password to secure our calls.

Let's take a look at our Workflow.WS.Service class:

Class Workflow.WS.Service Extends %CSP.REST
{

Parameter HandleCorsRequest = 0;
Parameter CHARSET = "utf-8";
XData UrlMap [ XMLNamespace = "https://www.intersystems.com/urlmap" ]
{
<Routes>
	<Route Url="/getTasks" Method="GET" Call="GetTasks" />
    <Route Url="/saveTask" Method="POST" Call="SaveTask" />
</Routes>
}

ClassMethod OnHandleCorsRequest(url As %String) As %Status
{
	set url = %request.GetCgiEnv("HTTP_REFERER")
    set origin = $p(url,"/",1,3) // origin = "http(s)://origin.com:port"
    // here you can check specific origins
    // otherway, it will allow all origins (useful while developing only)
	do %response.SetHeader("Access-Control-Allow-Credentials","true")
	do %response.SetHeader("Access-Control-Allow-Methods","GET,POST,PUT,DELETE,OPTIONS")
	do %response.SetHeader("Access-Control-Allow-Origin",origin)
	do %response.SetHeader("Access-Control-Allow-Headers","Access-Control-Allow-Origin, Origin, X-Requested-With, Content-Type, Accept, Authorization, Cache-Control")
	quit $$$OK
}

ClassMethod GetTasks() As %Status
{
    Try {
        Do ##class(%REST.Impl).%SetContentType("application/json")
        If '##class(%REST.Impl).%CheckAccepts("application/json") Do ##class(%REST.Impl).%ReportRESTError(..#HTTP406NOTACCEPTABLE,$$$ERROR($$$RESTBadAccepts)) Quit
        Do ##class(%REST.Impl).%SetStatusCode("200")
        set sql = "SELECT %Actions, %Message, %Priority, %Subject, TaskStatus_TimeCreated, ID FROM EnsLib_Workflow.TaskResponse WHERE TaskStatus_AssignedTo = ? AND TaskStatus_IsComplete = 0"
        set statement = ##class(%SQL.Statement).%New(), statement.%ObjectSelectMode = 1
        set status = statement.%Prepare(sql)
        if ($$$ISOK(status)) {
            set resultSet = statement.%Execute($USERNAME)
            if (resultSet.%SQLCODE = 0) {
                set tasks = []
                while (resultSet.%Next() '= 0) {
                    set task = {"actions": "", "message": "", "priority": "", "subject": "", "creation": "", "id": ""}
                    set task.actions = resultSet.%GetData(1)
                    set task.message = resultSet.%GetData(2)
                    set task.priority = resultSet.%GetData(3)
                    set task.subject = resultSet.%GetData(4)
                    set task.creation = resultSet.%GetData(5)
                    set task.id = resultSet.%GetData(6)
                    do tasks.%Push(task)
                }                
            }
        }
        set result = {"username": ""}
        set result.username = $USERNAME
        Do ##class(%REST.Impl).%WriteResponse(tasks)
        
    } Catch (ex) {
        Do ##class(%REST.Impl).%SetStatusCode("400")
        return ex.DisplayString()
    }

    Quit $$$OK
}

ClassMethod SaveTask() As %Status
{
    Try {
        Do ##class(%REST.Impl).%SetContentType("application/json")
        If '##class(%REST.Impl).%CheckAccepts("application/json") Do ##class(%REST.Impl).%ReportRESTError(..#HTTP406NOTACCEPTABLE,$$$ERROR($$$RESTBadAccepts)) Quit
        // Reading the body of the http call with the person data
        set dynamicBody = {}.%FromJSON(%request.Content)
        
        set task = ##class(EnsLib.Workflow.TaskResponse).%OpenId(dynamicBody.%Get("id"))
        set sc = task.CompleteTask(dynamicBody.action)

        if $$$ISOK(sc) {	        
            Do ##class(%REST.Impl).%SetStatusCode("200")
            Do ##class(%REST.Impl).%WriteResponse({"result": "success"})         
		}	
        
    } Catch (ex) {
        Do ##class(%REST.Impl).%SetStatusCode("400")
        Do ##class(%REST.Impl).%WriteResponse({"result": "error"})
    }

    Quit $$$OK
}

}

As you can see, from our WS we solve all the logic we need, but I do not recommend doing this, since we lose the traceability that we could achieve by sending the requests received to the production configured in the Namespace.

Taking a look at the URLMap section we will see that we have 2 endpoints configured in our WS:

  • getTasks: to recover all the user's pending tasks (if you see the SQL used we are passing the username directly from the variable generated when logging in).
  • saveTask: in which we will receive the user's response to the task and proceed to finish it by executing the CompleteTaks method of the EnsLib.Workflow.TaskResponse class.

On IRIS's part, everything would already be configured for the external application to connect.

Testing the external application with the Workflow Engine

First we will simulate sending a message from HL7 to our production by copying the file /shared/hl7/message_1_1.hl7 to the path /shared/in, let's see the message we are sending:

MSH|^~\&|HIS|HULP|EMPI||20240402111716||ADT^A08|346831|P|2.5.1
EVN|A08|20240402111716
PID|||07751332X^^^MI^NI~900263^^^HULP^PI||LÓPEZ CABEZUELA^ÁLVARO^^^||19560121|F|||PASEO MARIO FERNÁNDEZ 258 1 DERECHA^^MADRID^MADRID^28627^SPAIN||555819817^PRN^^ALVARO.LOPEZ@VODAFONE.COM|||||||||||||||||N|
PV1||N
OBX|1|NM|162986007^Pulso^SNM||72|^bpm|||||F|||20240402111716
OBX|2|NM|105723007^Temperatura^SNM||37|^Celsius|||||F|||20240402111716
OBX|3|NM|163030003^Presión sanguínea sistólica^SNM||142|^mmHg|||||F|||20240402111716
OBX|4|NM|163031004^Presión sanguínea diastólica^SNM||83|^mmHg|||||F|||20240402111716

For those of you who do not remember the previous articles, we had defined in our BPL that an alert would be generated when the systolic pressure exceeded 140 or the diastolic pressure exceeded 90, therefore, this message should generate a warning task for our patient with DNI (National Document of Identification) 07751332X and another automatic one that will remain open until IRIS receives the new notification.

Let's check our mobile application:

There we have 2 tasks generated, the first one that we have defined as manual and that depends on the user for its completion and the second that we have declared as automatic and that for it to disappear the user must take a new reading with their blood pressure monitor. If we take a look at the HTTP call we can see how we have included the JWT in it:

If the user clicks on the Accept button of the pending task, the flow will remain waiting to receive the blood pressure meter reading and will not advance to the next steps. Once the manual task is accepted and a new reading from the blood pressure monitor is received in which the marked limits are again exceeded, the system will generate two new warning tasks, one for the patient and another for the associated doctor in which a possible crisis is warned. :

Perfect! We have already set up our patient notification system in a simple and fast way.

Conclusion

As you have seen in this series of articles, the functionality of the Workflow Engine together with the interoperability capabilities of InterSystems IRIS provide incredible potential for the implementation of business processes that few other business solutions can provide. It is true that technical knowledge may be necessary to get the most out of it, but it is really worth the effort required.

Discussion (4)1
Log in or sign up to continue

Great!

I see you are using this query:

SELECT %Actions, %Message, %Priority, %Subject, TaskStatus_TimeCreated, ID 
FROM EnsLib_Workflow.TaskResponse 
WHERE TaskStatus_AssignedTo = ? 
  AND TaskStatus_IsComplete = 0

So it returns only uncompleted tasks assigned to a current user (by the way UserName is a valid sql variable so you don't need to pass it as an argument from ObjectScript). It does not return unassigned tasks - do you autoassign tasks in production? If so, how?