Retrieve the id of a process and navigate to a new form/process on the action of a button

1
0
-1

This question may be a detailed one as I need to understand the anatomy of an application through it.

So, I have an instantiation form that shows the list of customers onboarded to our system so far; there is a "Add new customer" button which should open another form containing the onboarding formalities. I need to provide the URL of the latter form in the action of the button. I have been following the documentation to understand how to form a URL but I am not able to get the missing pieces right - namely, the processInstanceId and the taskId of the next process/form to navigate to.

Moreover, I want to know the difference between processDefinitionId and processInstanceId.
For instance, for the above case, I provide a URL like this - /bonita/portal/form/processInstance/{{processId}}/task/Basic%20information%20ingestion

I am not able to pass the processId correctly. I get an error if I run this from my studio.

Another aspect of my application is that after filling the form, I need to run a sequence of system tasks which will do some operations (business logic, DB,etc.). I already have python scripts written for these tasks, is there a way I can run those in my system tasks.

I am attaching the diagram for a visual understanding of my use case -
OnboardingDiagram.png
I need a bit of guidance to move forward as currently I am stuck on how to proceed.

Thanks in advance!

EDIT:
I am somehow unable to show the image so I'll just chart out the process as follows:

CustomerList (instantiationForm) ---> Basic information Ingestion (nextForm/nextStep) ---> (sequence of system tasks)

Basically from instantiationForm I need to give a button to navigate to "nextForm" in order to add a new customer.

2 answers

1
0
-1
This one is the BEST answer!

There are multiple things here so I will do the best I can to help you.

Your diagram image does not display...

1) The anatomy of the application, in my opinion it's the developed process that needs looking at as this is where the opportunity lies.

I have an instantiation form that shows the list of customers onboarded to our system so far; there is a "Add new customer" button which should open another form containing the onboarding formalities. I need to provide the URL of the latter form in the action of the button. I have been following the documentation to understand how to form a URL but I am not able to get the missing pieces right - namely, the processInstanceId and the taskId of the next process/form to navigate to.

I have an instantiation form that shows the list of customers onboarded to our system so far; there is a "Add new customer" button which should open another form containing the onboarding formalities.

Just out of interest how do you show your existing customers on the Instantiation form? We're having trouble doing that...

So your process is supposed to work as follows:

  • Open Instantiation form
  • User clicks ADD Customer button
  • Open a Onboarding form

What is supposed to happen AFTER the Onboarding form is completed and the user presumably hits save?

Is the process supposed to go back to the Instantiation form with the list of customers already onboarded, or something else? You don't say.

In my mind you have two choices:

  1. Show a Modal Form ( MF Link ) and then when clicking save add the Onboarded Customer to the collection for update and close the modal window, or
  2. Hide all other fields on the form and show the new form ( Hidden Form Link ) from a hidden container and then when clicking save add the Onboarded Customer to the collection for update, hide the Onboarding container and unhide the other fields.

This way there is no need for specific URL's or breaking the chain of the process. You could also use both these methods to update BDM and start a background task doing all the business operations (business logic, DB,etc.) which are not online tasks.

Personally I like the Hidden Form, it works very well for what we want it to do.

Regarding Python, you can run these from a Script Connector as "System Script" however I've not tried it. You can also run python in Tomcat with various caveats. See the following links:
https://www.quora.com/Can-I-use-Tomcat-server-to-deploy-Python-rest-web-...

http://lekshmideepu.blogspot.se/2013/03/configure-tomcat-7-to-run-python...

regards
Seán

PS: As this reply offers an answer your question, and if you like it, please Mark UP and/or as Resolved.

Comments

Submitted by megha on Sat, 04/01/2017 - 12:52

Thank you for the useful links and precise answer to my question.

First to attempt to answer your question - Just out of interest how do you show your existing customers on the Instantiation form? We're having trouble doing that...

I am basically populating a table widget based on a JSON response from a a REST API call. Then I have just specified the properties (content, column keys) of the widget to show the content of the response. The way the entire process is defined (similar to what you mentioned in the reply) is as follows: The customers list shows existing customers (CustomerList Form) and on clicking "Add New" the new form (Onboarding Form) should open up, which in turn has a submit button to add the customer to DB and then the same API call will render the new customer in the list. I will try and use the "Hidden Form" link and apply the concept. But I just wanted to use the URL linking and see if that helped. I have been reading the documentation but unable to wrap my head around it.

Actually, my challenge is to find a way to start processes or background tasks (as you mentioned) from other forms.

One way to solve the running of Python functions/scripts is to host these as services (REST APIs) and then start them in the background but how should I perform these actions.

If I can get some help regarding these things, I would gladly appreciate it!

Thank you.

Submitted by megha on Sat, 04/01/2017 - 13:05

Is the process supposed to go back to the Instantiation form with the list of customers already onboarded, or something else?
Form the onboarding form, the user will have two options either to go back to the instantiation form or to another form that requires him to perform another task but I think if I figure it out for one form navigation, I'll be able to perform the others as well.

P.S. Sorry for the mulitple comments!!

Submitted by megha on Mon, 04/03/2017 - 08:32

Hey Sean,

Thanks for the link on Hidden Forms. Using the custom widget, I was able to make a step form. Ic an configure the submit button on each of these forms to store the records in DB and also update the BDM through the contract.
I have a small query, though - now I have all my pages in one form and I have removed the case instantiation form. So when I run this process through Studio, I am navigated to Bonita Portal with an auto-logged in user and I can see the task list depending on my process diagram. If I take this task, I am able to see the form and I think this looks fine. But now, after filling the last form (which is essentially a multi-document upload), I want to run the MongoDB connection script task. So should I configure this connection script in the task itself or link it to a separate script task. Currently, I am doing the latter and unable to understand how to run the script from the last form.
The way I presume it should be done is:
1. Upload docs through the Step Form and store it in a fileContainer contract.
2. Retrieve the value of the fileContainer contract (using contexts??) in the next task which is essentially a script task.

Please tell me if this understanding is correct.

Thanks in advance!!

Submitted by Sean McP on Mon, 04/03/2017 - 10:45

It took us a while to appreciate the power of the "Page Hidden Forms", they are very good and underrated.

We actually make the "Page Hidden Forms" our instantiation form so when we run it it automatically runs. It's almost tlike a one page application (excluding management approval which is the following step.

Anyway:

But now, after filling the last form (which is essentially a multi-document upload), I want to run the MongoDB connection script task. So should I configure this connection script in the task itself or link it to a separate script task.

The way we do this is:

Hidden form 1 -> data input only -> Next
Hidden form 2 -> data input only -> Next
Hidden form 3 -> document upload -> Submit

The document Upload is MULTIPLE documents and defined in the Contract as follows:

Execution Tab -> Contract -> Add -> Name uploadedFiles -> Type File -> Multiple ticked

Then go to the POOL -> Data Tab -> Documents -> Add Document -> Multiple selected -> Initial content -> From Contract -> Select the Contract Name uploadedFiles.

Then Add a SCRIPT TASK to the Pool immediately after the task for the upload form. Here you can do the Database Upload.

It is much easier to do it this way and you are separating the two functions without them breaking each other. Makes for easier debugging as well.

Tasks in the diagram don't use contexts, they have direct access to the variables where the information is stored.

Currently, I am doing the latter and unable to understand how to run the script from the last form.

Doing the above will ensure the tasks run automatically and you don't have to run the script, it's already run for you.

Hope that helps

regards
Seán

Submitted by megha on Mon, 04/03/2017 - 11:46

Thank you for the prompt reply. I have done exactly the way you suggested (except the configuration of the Documents in the Data tab). I shall execute this as two separate tasks (decoupling user input from logic - seems intuitive).

I still have a doubt, though - earlier I had made this form my case Instantiation form but then I realised that the case instantiation as an initFunc() for every contract but no "Operations" tab. So, if I were to update the BDM through the contract, then how do I achieve it in case instantiation?? And can I start my script task (DB connection and upload) immediately after the case instantiation to facilitate the upload operation.

Thanks a lot for sharing your expertise. It has helped me a great deal to move forward with the process flow implementation.

Regards
Megha

Submitted by Sean McP on Mon, 04/03/2017 - 12:09

The way it seems to work is as follows, and it confused me for a while as well...

  1. User Starts a Process - This is NOT a Case yet.
  2. System shows the Instantiation Form
  3. User fills in Form to complete the contract, then clicks Submit
  4. Now the Case is created based on Process, then
  5. initFunc() functions execute to insert data from the form into the BDM
  6. Then the Start1 icon is fired
  7. Then the Step1 in the pool is started
    7.1 Connectors in
    7.2 Task form
    7.3 Task operations
    7.4 Connectors Out
  8. Then the Step2 in the pool is started etc...as part of process processing
  9. etc...etc...etc.

The initFunc() "is" the Operations Tab associated with Instantiation Form (notice is in quotes:)

... if I were to update the BDM through the contract, then how do I achieve it in case instantiation?...

As you can see the BDM is populated automatically at Case Instantiation by the initFunc();

And yes you can start your MongoDB upload immediately after.

Hopefully the following is what you'll have (a short example):

  1. A pool, which has an Instantiation Form (along with BDM etc.)
  2. Then a Start1 icon (the start of the actual tasks for the case)
  3. Then a Script or Service Task to upload the documents to the DB
  4. Then an End1 icon (the end of the actual tasks for the case)

That's how it works for me anyway, yes takes time to understand but once you get it, it makes sense (sort of)

regards
Seán

Submitted by megha on Mon, 04/03/2017 - 12:22

All right, so in a way the process diagram, defining the sequential process flow and is triggered with the case instantiation form and all the processes after it will execute (assuming there are no logical errors) right till the end.

Thank you so much for this valuable post. It has clarified a lot of confusion regarding the Bonita processes.

This one-page approach is definitely simpler to work with than the page navigation among tasks. However, just of curiosity, the hidden form approach will not work if each hidden form has a separate (say, script/service) task associated with it.

Thank you again!!!
Regards
Megha

Submitted by Sean McP on Mon, 04/03/2017 - 12:34

1) Yes...

2) Absolutely, it took me a while

3) The Hidden Form approach do not/cannot have script/service tasks associated with them it is after all - just one form. So no. If you really need this then you need multiple tasks and separate forms.

And Finally :) I've just remembered something about how to do Files: Checked and yes I will have to tell you how to do it right tomorrow. It's late here and I need to go and remember exactly how we did it.

If I remember rightly:

Step1 -> Output Operation to documentList
Step2 -> MultiInstantiation on documentList calling subroutine
Subroutine calls Upload to DB Connector

Till then,

regards
Seán

Submitted by megha on Mon, 04/03/2017 - 12:47

All right, that's really helpful. You have already helped loads. I will try my current approach to capture the documents in the contract and starting the next task as a Script task.
If that doesn't work, then I can test the multi-instantiation approach you have suggested.

The Hidden Form approach do not/cannot have script/service tasks associated with them it is after all - just one form. So no. If you really need this then you need multiple tasks and separate forms.

Yes, I understand. For the current use case, the a single-page application will work as I just need to call our product API stack to store the results in DB. Perhaps, I will need to dig deeper to know how to work with multiple tasks and navigating among them. :)

Thanks a lot!

Regards
Megha

Submitted by Sean McP on Mon, 04/03/2017 - 23:49

OK, how to do the document upload.

Create the subroutine to do the uploads

  1. Create a NEW DIAGRAM call it pushDocumentToDatabase
  2. In Execution->Instantiation Form select No Form
  3. Add a Script Task
  4. Add an End icon
  5. Add a pool variable LONG docID
  6. Save

Now to send the data to the subroutine:

  1. In your process Pool add a POOL Variable outputDocuments of type Object->List
  2. After the Start icon add a Script/Service Task
  3. In the Execution Tab select Operations, Add an Operation
  4. On the Left select the outputDocuments variable -> TAKES VALUE OF -> EDIT PENCIL to open the script editor.
  5. Add the following code:
  1. import org.bonitasoft.engine.api.ProcessRuntimeAPI;
  2. import java.util.logging.Logger;
  3.  
  4. int dI = 0;
  5. boolean debug = true;
  6.  
  7. ProcessRuntimeAPI processRuntimeAPI = apiAccessor.getProcessAPI();
  8. String processName = processRuntimeAPI.getProcessInstance(processInstanceId).getName();
  9.  
  10. //set the name of the routine
  11. String thisTrace = " "+processName+ " getUpdatedOutputs: "
  12.  
  13. Logger logger= Logger.getLogger("org.bonitasoft");
  14. if(debug){dI++; logger.severe(dI+thisTrace+"Trace Start");}
  15. //TODO - Code goes in here
  16.  
  17. List<Long> xList = new ArrayList<Long>();
  18. if(debug){
  19. dI++;if(0<=outputFiles){logger.severe(dI+thisTrace+"outputFiles: "+outputFiles.toString())}else{logger.severe(dI+thisTrace+"outputFiles: "+outputFiles.toString())}
  20. dI++;if(1<=outputFiles){logger.severe(dI+thisTrace+"outputDoc01: "+outputDoc01.getVersion()+" "+outputDoc01.getContentFileName())}
  21. dI++;if(2<=outputFiles){logger.severe(dI+thisTrace+"outputDoc02: "+outputDoc02.getVersion()+" "+outputDoc02.getContentFileName())}
  22. dI++;if(3<=outputFiles){logger.severe(dI+thisTrace+"outputDoc03: "+outputDoc03.getVersion()+" "+outputDoc03.getContentFileName())}
  23. dI++;if(4<=outputFiles){logger.severe(dI+thisTrace+"outputDoc04: "+outputDoc04.getVersion()+" "+outputDoc04.getContentFileName())}
  24. dI++;if(5<=outputFiles){logger.severe(dI+thisTrace+"outputDoc05: "+outputDoc05.getVersion()+" "+outputDoc05.getContentFileName())}
  25. dI++;if(6<=outputFiles){logger.severe(dI+thisTrace+"outputDoc06: "+outputDoc06.getVersion()+" "+outputDoc06.getContentFileName())}
  26. dI++;if(7<=outputFiles){logger.severe(dI+thisTrace+"outputDoc07: "+outputDoc07.getVersion()+" "+outputDoc07.getContentFileName())}
  27. dI++;if(8<=outputFiles){logger.severe(dI+thisTrace+"outputDoc08: "+outputDoc08.getVersion()+" "+outputDoc08.getContentFileName())}
  28. dI++;if(9<=outputFiles){logger.severe(dI+thisTrace+"outputDoc09: "+outputDoc09.getVersion()+" "+outputDoc09.getContentFileName())}
  29. dI++;if(10<=outputFiles){logger.severe(dI+thisTrace+"outputDoc10: "+outputDoc10.getVersion()+" "+outputDoc10.getContentFileName())}
  30. }
  31.  
  32. def x;
  33.  
  34. if(1<=outputFiles){if (outputDoc01 == null){ /* do nothing */ } else{x = apiAccessor.getProcessAPI().getLastDocument(processInstanceId, "outputDoc01").getId(); xList.add(x);}}
  35. if(2<=outputFiles){if (outputDoc02 == null){ /* do nothing */ } else{x = apiAccessor.getProcessAPI().getLastDocument(processInstanceId, "outputDoc02").getId(); xList.add(x);}}
  36. if(3<=outputFiles){if (outputDoc03 == null){ /* do nothing */ } else{x = apiAccessor.getProcessAPI().getLastDocument(processInstanceId, "outputDoc03").getId(); xList.add(x);}}
  37. if(4<=outputFiles){if (outputDoc04 == null){ /* do nothing */ } else{x = apiAccessor.getProcessAPI().getLastDocument(processInstanceId, "outputDoc04").getId(); xList.add(x);}}
  38. if(5<=outputFiles){if (outputDoc05 == null){ /* do nothing */ } else{x = apiAccessor.getProcessAPI().getLastDocument(processInstanceId, "outputDoc05").getId(); xList.add(x);}}
  39. if(6<=outputFiles){if (outputDoc06 == null){ /* do nothing */ } else{x = apiAccessor.getProcessAPI().getLastDocument(processInstanceId, "outputDoc06").getId(); xList.add(x);}}
  40. if(7<=outputFiles){if (outputDoc07 == null){ /* do nothing */ } else{x = apiAccessor.getProcessAPI().getLastDocument(processInstanceId, "outputDoc07").getId(); xList.add(x);}}
  41. if(8<=outputFiles){if (outputDoc08 == null){ /* do nothing */ } else{x = apiAccessor.getProcessAPI().getLastDocument(processInstanceId, "outputDoc08").getId(); xList.add(x);}}
  42. if(9<=outputFiles){if (outputDoc09 == null){ /* do nothing */ } else{x = apiAccessor.getProcessAPI().getLastDocument(processInstanceId, "outputDoc09").getId(); xList.add(x);}}
  43. if(10<=outputFiles){if(outputDoc10 == null){ /* do nothing */ } else{x = apiAccessor.getProcessAPI().getLastDocument(processInstanceId, "outputDoc10").getId(); xList.add(x);}}
  44.  
  45. if(debug){dI++; logger.severe(dI+thisTrace+"xList"+xList.toString());}
  46.  
  47. if(debug){dI++; logger.severe(dI+thisTrace+"Trace End");}
  48. return xList;

NOTE(1): Debug code is included and can be read on the log
NOTE(2): the code is for hardcoded documents we have - you need to change the two major code sections to loop over the Document List variable that you've defined. The first section prints all the information on the log, the second created the usable outputDocuments variable previously defined
6. After the Script/Service Task add an ABSTRACT Task->General->Iteration
7. Select Parallel or Sequential multi-Instantiation as required
8. Select Create instances from a list
9. Input is variable outputDocuments
10. Iterator is docID
11. goto General->Process to call and give the name of pushDocumentToDatabase subroutine
12. Execution->data to send and add docID Assigned to Data docID

That gives you everything except the last bit to turn the data into a blob that can be uploaded:

  1. Document d1 = apiAccessor.getProcessAPI().getDocument(docID);
  2. String documentname = d1.getContentFileName();
  3. String mimeType = d1.getContentMimeType();
  4. // use fileName if title is empty
  5. title = (title==null||title.isEmpty()) ? d1.getContentFileName():title;
  6. final byte[] contentByte = ((DocumentAPI) processRuntimeAPI).getDocumentContent(d1.getContentStorageId());
  7. final ByteArrayInputStream gubedocument = new ByteArrayInputStream(contentByte);
  8. int doclength = contentByte.length;

and the final prepared statement that uploads the data:

  1. PreparedStatement ps = con.prepareStatement("INSERT INTO " + tble
  2. + " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
  3. //1 documentid
  4. //2 tdocumentname
  5. //3 documentname
  6. //4 versionmajor
  7. //5 versionminor
  8. //6 versionmini
  9. //7 uploader
  10. //8 approver
  11. //9 uploaddate
  12. //10 approvaldate
  13. //11 status
  14. //12 document - the blob
  15.  
  16. ps.setLong(1, 0);
  17. ps.setString(2, removeExtension(documentname.replace("_", " "))); // remove
  18. ps.setString(3, gubedocumentname);
  19. ps.setLong(4, 0);
  20. ps.setLong(5, 0);
  21. ps.setLong(6, 0);
  22. ps.setString(7, "Uploader");
  23. ps.setString(8, "Approver");
  24. ps.setTimestamp(9, date);
  25. ps.setTimestamp(10, date);
  26. ps.setString(11, "Pending");
  27. ps.setBinaryStream(12, document, doclength);
  28. ps.executeUpdate();
  29.  
  30. ps.close();
  31. con.close();

It's all up to you now,

regards
Seán

Submitted by megha on Tue, 04/04/2017 - 07:59

All right, this is something interesting to work with!

I had a similar code for looping over my documents and calling a function in the script connector itself that will upload my documents to the DB Server (sequentially). After looking at the code you have sent, there are clearly several merits of this approach:
1. Decoupling logic for input (document retrieval from forms) and the insertion logic.
2. Parallel or sequential multi-instantiation

But since, I have a deliverable due for this week, I just wanted to know if the following code will work as per my requirements:

`import com.mongodb.gridfs.GridFS;
import com.mongodb.gridfs.GridFSInputFile;
import com.mongodb.DBCollection;
import com.mongodb.DB;
import com.mongodb.Mongo;
import com.mongodb.MongoClient;
import com.mongodb.MongoException;
import com.mongodb.MongoClientURI;

import java.net.UnknownHostException;
import java.io.IOException;

import org.bonitasoft.engine.api.DocumentAPI
import org.bonitasoft.engine.api.ProcessAPI
import org.bonitasoft.engine.api.ProcessRuntimeAPI
import org.bonitasoft.engine.bpm.document.Document

List<Document> docList = new ArrayList<>();
final String MONGO_USER = "XXXX";
final String MONGO_PWD = "XXXXXX";
final String MONGO_HOSTNAME = "localhost";
final int MONGO_PORT = 27017;

// creating a connection with Mongo instance
void saveFileToMongo(InputStream streamFile, String fName, String mimeType){
    try{

        MongoClient newMongoConn = new MongoClient(new MongoClientURI("mongodb://"+":"+MONGO_PWD+"@"+MONGO_HOSTNAME+":"+MONGO_PORT));
        DB dbInstance = newMongoConn.getDB("test");
        DBCollection coll = dbInstance.getCollection("fs.files");

        //create a namespace
        GridFS gfs = new GridFS(dbInstance, "retailerDocs");
        GridFSInputFile inputFile = gfs.createFile(streamFile);
        inputFile.setFilename(fName);
        inputFile.setContentType(mimeType);
        inputFile.save();
    }
    catch(UnknownHostException ue){
        ue.printStackTrace();
    }
    catch(MongoException me){
        me.printStackTrace();
    }
    catch(IOException ie){
        ie.printStackTrace();
    }
}
// getting the document from the UI elements
//Long processId = apiAccessor.getProcprocessDefinitionId();

List<String> allDocs = ["laf", "site_verify",  "resident_poi","resident_poa", "negative_checklist",
    "entity_poi", "entity_poa"];

ProcessAPI processRuntimeAPI = apiAccessor.getProcessAPI();
for (String doc : allDocs){
    Long docId = apiAccessor.getProcessAPI().getLastDocument(processInstanceId, doc);

    Document d1 = apiAccessor.getProcessAPI().getDocument(docId);
    String docName = d1.getContentFileName();
    String mimeType = d1.getContentMimeType();
    final byte[] contentByte = ((DocumentAPI) processRuntimeAPI).getDocumentContent(d1.getContentStorageId());
    final ByteArrayInputStream byteStreamDocument = new ByteArrayInputStream(contentByte);

    saveFileToMongo(byteStreamDocument, docName, mimeType);
}

`
If this is executed, probably I can try the multi-instantiated subroutine method as well.

Also, on a different note, in my one-page application with multiple (hidden) forms, if I click Submit button the information will get stored in the contract associated with the BDM and it will get updated (correct?); if in addition to that I also want to send the data to an external API call which takes some headers as well in the request. Now there is a property where I can mention the URL to send to, my query is this URL supposed to point to my BDM or I can use it to point to an external API (but still the issue of specifying custom headers (Authorization, etc.)). Since, I had this confusion, I decided that it will be best to create a custom button that has a special onClick function to handle GET/POST requests along with headers. I need to know if this is a correct thing to do.

Thank you!

Regards
Megha

Submitted by megha on Wed, 04/05/2017 - 13:25

In the context of hidden forms (multiple steps), does each form need a submit button to save the contract necessary to update the BDM or can I have a button at the last form to update all contracts at once?

I actually ran a small test with the Submit Button on one of my hidden forms. I get the following error on clicking the Submit button:
"exception":"class org.bonitasoft.engine.bpm.flownode.UserTaskNotFoundException","message":"USERNAME=helen.kelly | Activity instance with id 8737521854282115197 not found"

Can you please tell me why this error is occuring?

Regards

Submitted by Sean McP on Wed, 04/05/2017 - 13:45

Nope, but only ONE submit button...at the very end, the next and previous buttons flick between the "pages" of the form that's it.

As to why this user task - no idea...sorry.

Submitted by megha on Wed, 04/05/2017 - 16:43

Thank you for the quick and useful reply.

Your answer helped me solve the UserTaskNotFoundException as well. I was trying to use the SUBMIT button on each of the hidden forms which do not have a task/process associated with them. Since no context was found on clicking submit, it threw the exception.

Moreover, I added the SUBMIT button at the end of all the steps and I am able to see an output (currently its a contract validation error, but once I move past that, I may be able to test the rest of the process).
In relation to this, I will only be able to move ahead to the next step (in my case, the Doc Upload Script Task) only if I successfully fulfil the instantiation form contract, is that correct??

Thanks a lot, once again for the help!

1
0
-1

Hi,

I feel that is a duplicate of this question, no?: http://community.bonitasoft.com/questions-and-answers/retrieve-id-proces...

Cheers

Notifications