Joget DX 8 Stable Released
The stable release for Joget DX 8 is now available, with a focus on UX and Governance.
Table of Contents |
---|
English |
---|
In this article, we will |
...
walkthrough the thought process |
...
of designing a solution for the following business use case:- |
Upon successful validation of data, the form data will be shared with an external system (i.e. CRM software) for further processing through the use of plugins (i.e. JSON Tool) or Bean Shell code. More on this later on.
This is an example on how of what the form would look like.
Figure 1
The only external factor that may be outside of the Joget platform's control would be the external integration with the CRM software. We will walkthrough a few scenarios on how best to design for this business use case with UI/UX kept in mind.
...
When end-user hits on the Submit button, the following will take place.
With what we have learned so far, this can be presented using the following diagram.
Figure 8
...
The easiest, no-code approach is to make use of JSON Tool plugin itself. The JSON Tool itself is a Process Tool & Post Form Submission Processing Plugin. This means that we can invoke it from within a process flow or from the submission of the form.
We can also write Bean Shell code. Here's a quick sample code to make http HTTP get call.
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import java.io.IOException; import org.joget.commons.util.LogUtil; CloseableHttpClient client = null; HttpRequestBase request = null; try{ String jsonUrl = "http://localhost:8080/jw/web/json/workflow/assignment/list/count?packageId=crm"; //sample url String name = "header1"; String value = "value1"; CloseableHttpClient client = null; CloseableHttpClient client = HttpClients.createDefault(); HttpRequestBase request = null; request = new HttpGet(jsonUrl); request.setHeader(name, value); HttpResponse response = client.execute(request); } catch (Exception ex) { LogUtil.error(getClass().getName(), ex, ""); } finally { try { if (request != null) { request.releaseConnection(); } if (client != null) { client.close(); } } catch (IOException ex) { LogUtil.error(getClass().getName(), ex, ""); } } |
We can execute this piece of code from various plugin types giving us the flexibility on where/when we want to invoke it. The only disadvantage compared to the former is that we need to maintain the custom coding ourselves instead of configuring through a plugin. These are the plugin types relevant to our solution to call the code from:-
...
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
import java.util.Map;
import java.util.HashMap;
import javax.servlet.http.HttpServletRequest;
import org.joget.apps.app.model.AppDefinition;
import org.joget.apps.app.service.AppPluginUtil;
import org.joget.apps.app.service.AppUtil;
import org.joget.plugin.base.ApplicationPlugin;
import org.joget.plugin.base.Plugin;
import org.joget.plugin.base.PluginManager;
import org.joget.plugin.property.model.PropertyEditable;
import org.joget.workflow.model.WorkflowAssignment;
public Object execute(AppDefinition appDef, HttpServletRequest request, WorkflowAssignment workflowAssignment) {
String jsonUrl = "http://localhost:8080/jw/web/json/workflow/assignment/list/count?packageId=crm"; //sample url
//Reuse Email Tool to send separated email to a list of users;
Plugin plugin = pluginManager.getPlugin("org.joget.apps.app.lib.JsonTool");
ApplicationPlugin jsonTool = (ApplicationPlugin) plugin;
Map propertiesMap = new HashMap();
propertiesMap.put("pluginManager", pluginManager);
propertiesMap.put("appDef", appDef);
propertiesMap.put("request", request);
propertiesMap.put("workflowAssignment", workflowAssignment);
//configure json tool plugin
propertiesMap.put("jsonUrl", jsonUrl);
propertiesMap.put("requestType", ""); //empty is for GET call
propertiesMap.put("multirowBaseObject", "");
propertiesMap.put("debugMode", "");
propertiesMap.put("formDefId", "request");
propertiesMap.put("headers", new Object[]{});
List fieldMappings = new ArrayList();
Map fieldMapping = new HashMap();
fieldMapping.put("jsonObjectName", "total");
fieldMapping.put("field", "day");
fieldMappings.add(fieldMapping);
//repeat this code to add more row
// fieldMapping = new HashMap();
// fieldMapping.put("jsonObjectName", "jsonAttrName");
// fieldMapping.put("field", "formFieldId");
// fieldMappings.add(fieldMapping);
propertiesMap.put("fieldMapping", fieldMappings.toArray());
//set properties and execute the tool
((PropertyEditable) jsonTool).setProperties(propertiesMap);
jsonTool.execute(propertiesMap);
return null;
}
//call execute method with injected variable
return execute(appDef, request, workflowAssignment); |
...
By using Post Form Submission Processing in Form, and "Method 1 JSON Call" earlier, this is the easiest and quickest method. This allows us to invoke any Process Tool & Post Form Submission Processing Plugin. JSON Tool is one such candidate.
Figure 9
Depending on the feature of the API call, we would assume that it would return a response to indicate successful execution. For example:-
Code Block | ||||
---|---|---|---|---|
| ||||
{ "success" : "true" } |
...
To avoid the waiting time for JSON Tool to finish executing, we can place it under Multi Tools instead.
Figure 10
Set the "Run Mode" such that it would execute the process tool (JSON Tool) in a new thread.
...
With what we have learned so far, there's still 1 con that we are trying to solve. Let's try to put the form within a process flow in a diagram as below.
Figure 11
By using a process flow, we can check the content of the returned JSON call to see if it matches the intended content. For example, we are expecting this reply and have it mapped to a workflow variable.
...
As the JSON Tool plugin is not a form validator nor form store binder type of plugin, we will need to use the workaround shared earlier, which is writing the bean shell code from scratch or calling the JSON Tool plugin programmatically.
Why Form Validator?
Form Validator Plugin expects a true/false to be returned. We may try to avoid the need of designing a process flow and achieve the same JSON result checking by making the JSON call through the validator plugin. The problem that would arise from this appraoch are:-
Why Form Store Binder?
To avoid calling the API repetitively, let's move down to the next layer, store binder.
By calling the JSON API within the Form Store Binder Plugin, we will need to explore on how to handle events such as when JSON API is not being responsive. In this type of plugin, it won't be expecting a true/false to be returned like the validator plugin though.
We can try to throw an exception instead in the Bean Shell code that we are writing.
Figure 12
This approach suffers from the following issues:-
...
How can we ensure that form submission captured, without the need for potential customer to wait for the JSON call to be completed and to continue to receive form submissions when the external API is not reachable at the point of form submisison?
Without reinventing the wheel, we extend the capabilities of the current JSON Tool to capture the response's status code and store it into workflow variable and/or form data. You can find the plugin here.
Taking cues from method 2 earlier, we will put the new plugin in Multi Tools and set the Run Mode to "Run tools sequentially in a new single thread". This is so that customer does not need to wait for JSON call to complete.
Figure 13
The following is a new section to configure to capture the JSON call's response status.
We created a new form to capture the JSON call log.
Figure 14
In this screenshot below, we are able to inspect each of the form submission made (left) and the result of the API call (right).
In the highlighted row, we can see that the API call failed with response status code of 524.
Figure 15
And in this screenshot below, there are 2 log records created. The JSON call is successful but the data response triggerred a casting exception.
Figure 16
With the log data on hand, we can then create a scheduled task that picked up unsuccessful API calls and attempt to trigger them again later on by using the following SQL (MySQL).
Code Block | ||
---|---|---|
| ||
SELECT req.*, log.c_status as `latest_status` FROM app_fd_demo_request req LEFT JOIN (SELECT MAX(dateCreated) as dateCreated, c_request_id FROM app_fd_demo_request_log log GROUP BY c_request_id) a ON req.id = a.c_request_id JOIN app_fd_demo_request_log log ON log.dateCreated = a.dateCreated WHERE log.c_status != '200' ORDER BY req.dateCreated DESC |
This is what the list would look like in the screenshot below. Each of the request rows submitted will show the latest log.
Figure 17
Since we are storing the exact form data in Joget's database, we can try to make the same JSON call again later on.
For example, we can make use of the Form Update Process Tool Datalist Action and map to the JSON Tool.
Figure 18
Once it is tested working, we can consider automating it and set up a scheduler job - iterate through the same list and execute JSON Tool using Iterator Process Tool.