Set Mirth destination to send transform data back as a custom ACK - mirth

I have a Mirth channel that set up as a web service listener, it receives an ID, build an HL7 query message and send this query and eventually get back an HL7 response.
Channel Name: QueryChanel
Source Connector Type: Web Service Listener
Destination Connector Name: QueryToVista
Destination connector Type:LLP Sender.
This is the typical HL7 response I receive back from my query is as follow:
MSH|~|\&|VAFC RECV|FACILITY|VAFC TRIGGER||20121011141136-0800||ADR~A19|58269|D|2.4|||NE|NE|USA
MSA|AA|1234|
QRD|20121011051137|R|I|500000001|||1^ICN|***500000001***|ICN|NI|
EVN|A1|20121004064809-0800||A1|0^^^^^^^^USVHA\\0363^L^^^NI^TEST FACILITY ID\050\L|20121004064809-0800|050
PID|1|500000001V075322|500000001V075322^^^USVHA\\0363^NI^VA FACILITY ID\050\L~123123123^^^USSSA\\0363^SS^TEST FACILITY ID\050\L~9^^^USVHA\\0363^PI^VA FACILITY ID\050\L||JOHN^DOE^^^^^L|""|19800502|M||""|""^""^""^""^""^^P^""^""~^^""^""^^^N|""|""|""||S|""||123123123|||""|""||||||""||
PD1|||SOFTWARE SERVICE^D^050
ZPD|1||||||||||||||||""
I can get all the above to return if I set my Source's Response From parameter to QueryToVista
However, I want to return only the value 500000001 from the above message. I've tried to play around with the transformer in the QueryChanel destination without success.
Update:
I tried to add a javascriptwriter connector after the QueryToVista connector in the same channel as follow:
var destination = responseMap.get('QueryToVista');
var responseMessage = destination.getMessage();
//Fails with following error: TypeError: Cannot read property "QRD.4" from undefined
var customack = ResponseFactory.getSuccessResponse(responseMessage['QRD']['QRD.4'] ['QRD.4.1'].toString())**
//work but send the whole HL7 message
var customack = ResponseFactory.getSuccessResponse(responseMessage.toString())**
responseMap.put('Barcode', customack);
I can't seem to use the normal transformation to retrieve the element at all.
Thank you.

You're on the right track, but your update illustrates a couple of issues. However, your basic approach of using two destinations is valid, so long as "Synchronize channel" is checked on the Summary tab.
Issue 1
In your example, the HL7 response you are wanting to parse is in pipe delimited HL7 form. In order to access the elements using E4X notation (eg. responseMessage['QRD']['QRD.4']['QRD.4.1']) you must first convert it into an E4X XML object. This can be done in two steps.
Convert the pipe delimited HL7 string into an XML string.
Convert the XML string into an E4X XML object
In a Javascript transformer of the JavaScript Writer (not the Javascript Writer script itself)
var response = responseMap.get("QueryToVista");
var responseStatus = response.getStatus();
// Get's the pipe delimited HL7 string
var responseMessageString = response.getMessage();
if (responseStatus == "SUCCESS")
{
// converts the pipe delimited HL7 string into an XML string
// note: the SerializeFactory object is available for use in transformer
// scripts, but not in the Javascript destination script itself
var responseMessageXMLString = SerializerFactory.getHL7Serializer(false,false,true).toXML(responseMessageString);
// convert the XML string into an E4X XML object
var responseMessageXMLE4X = new XML(responseMessageXMLString);
// grab the value you want
var ack_msg = responseMessageXMLE4X['QRD']['QRD.4']['QRD.4.1'].toString();
channelMap.put('ack_msg', ack_msg)
}
else
{
// responseStatus probably == "FAILURE" but I'm not sure of the full range of possibilities
// take whatever failure action you feel is appropriate
}
Edit**
I don't believe there is an Issue 2. After reviewing your own approach, I played a bit further, and believe I have confirmed that your approach was indeed correct for generating the SOAP reponse. I'm editing this section to reflect simpler code that still works.
In the Javascript Writer script
var barcode = channelMap.get('ack_msg');
var mirthResponse = ResponseFactory.getSuccessResponse(barcode);
responseMap.put('Barcode', mirthResponse);

Thank you very much csj,
I played around and got mine to work and looking at your solution, you pointed out my bottle neck to the issue as well which is the XML part, I did not realize you have to cast it into XML as per the new XML when you already call toXML function :)
Here is my script, though basic I thought I post it up for anyone find it useful down the road.
var destination = responseMap.get('QueryToVista');
var responseMessage = destination.getMessage();
var Xmsg = new XML(SerializerFactory.getHL7Serializer().toXML(responseMessage));
var xml_msg = '<?xml version="1.0" encoding="utf-8" ?>'+
'<XML><Patient Name="'+Xmsg['PID']['PID.5']['PID.5.1']+
'" Barcode="'+Xmsg['QRD']['QRD.8']['QRD.8.1']+'" /></XML>';
var sResp = ResponseFactory.getSuccessResponse(xml_msg)
responseMap.put('Response', sResp);

Related

Handle uncompleted file upload by API POST endpoint in asp.net core MVC

To simplify the problem let's say I have a simple asp.net mvc endpoint which receives a file. In most of the cases it will be a .jpg one:
[HttpPost]
[Route("appraisal/{appraisalID}/files/{fileSubjectCode}")]
[ProducesResponseType(StatusCodes.Status201Created, Type = typeof(IEnumerable<AppraisalFileModel>))]
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(ModelStateDictionary))]
public async Task<IActionResult> UploadAppraisalFile(int appraisalID, string fileSubjectCode, [FromForm]IFormFile file)
{
file = file ?? Request.Form.Files?.FirstOrDefault();
// intermitent code change to investigate and validate Complete File Size and Actual File Size
var completeFileSizeHeader = Request.Headers["Complete-File-Size"];
int.TryParse(completeFileSizeHeader, out int completeFileSize);
if (file == null || file.Length != completeFileSize)
{
using (var stream = new MemoryStream())
{
await file.CopyToAsync(stream);
stream.Position = 0;
var inputAsString = Convert.ToBase64String(stream.ToArray());
Logger.LogDebug("Complete-File-Size header doesn't much received byteArray size", file.Length, completeFileSize, inputAsString);
}
return StatusCode(StatusCodes.Status411LengthRequired, "Complete-File-Size header doesn't much received byteArray size");
}
// some other logic..
}
I'm trying to prevent an edge case when somebody performs a POST request against my API UploadAppraisalFile endpoint and suddenly loses an internet connection which would result in sending a request with not the full file content.
My idea was to count the file size at the point where the file is uploaded, add the information about the file size as an extra HTTP-HEADER (I called it Complete-File-Size), and then when the request reaches the backend, count if the received file size is exactly the same as the Complete-File-Size.
To produce such an issue/edge case I tried:
uploading a big file(about 16MB) and then suddenly after submitting the HTML form immediately close the browser window.
uploading a file and then, in the Chrome browser, in the Network pane, change the uploading speed to a very minimum, then submit the form and then immediately close the browser.
When I run the debug mode, in each case I found that either: UploadAppraisalFile endpoint was never reached or if it was reached then always the full file was sent. For the 2nd successful case, to be 100% sure I converted the received file into base64 string and then I checked how the file looks like in https://codebeautify.org/base64-to-image-converter.
My question is: Is it even possible that the sent POST request is broken and contains not full file content due to a broken internet connection that happened suddenly during the sending process? If yes, then what's the best way to produce the issue. Cheers
You can pass HttpContext.RequestAborted as a CancellationToken to ALL async methods provided by .NET in "some other logic" part.
Let's use code you provided as an example :
await stream.CopyToAsync(memoryStream, HttpContext.RequestAborted)
I don't have an access to a full method but I assume you save it to some blob storage or file system. Most of these persistence API's accept CancellationToken as a parameter.
Receiving incomplete file
I was able to achieve a "partial" file using this code and Postman. It will basically read chunks from response stream until connection is interrupted.
As soon as I close Postman window TaskCancelledException is raised and stream is closed.
[HttpPost]
public async Task<IActionResult> UploadAppraisalFile([FromForm] IFormFile file)
{
var appraisalfile = file ?? Request.Form.Files.FirstOrDefault();
if (appraisalfile != null)
{
var buffer = ArrayPool<byte>.Shared.Rent(1024);
using var stream = appraisalfile.OpenReadStream();
while (await stream.ReadAsync(buffer, 0, buffer.Length, HttpContext.RequestAborted) > 0)
{
// Do something with buffer
_logger.LogInformation("Total length (bytes) {0}, partial length (bytes) {1}", stream.Length, stream.Position);
}
ArrayPool<byte>.Shared.Return(buffer);
}
return Ok();
}

Add flag to UnityWebRequest

I am trying to make a POST request to webpage that expects the --data field to be filled with some data to be processed. I'm pretty much trying to recreate this curl request, but with UnityWebRequest.
curl -X POST http://localhost:8000/clic/say?text=Make+the+gene+set --data '{"geneSetMembers":["UST"],"geneSetName":"selection0"}'
The UnityWebRequest documentation mentions that GET requests don't set any flags other than the url, but it's not clear if no other custom options exist for posts. Is there some way to format a WWWform or something that will hold the data such that the server will recognize it?
var form = new WWWForm();
// some way to plug in the jsonified data to the form
webRequest = UnityWebRequest.Post(url + route + to_say, form);
webRequest.downloadHandler = new DownloadHandlerBuffer();
webRequest.SetRequestHeader("Content-Type", "application/json");
webRequest.SendWebRequest();
// etc etc
I've tried just giving the form a field named "data" a la
form.AddField("data", "{ \"geneSetMembers\":[\"UST\"],\"geneSetName\":\"selection0\"}");
but the server does not like it, saying it "got error Invalid JSON literal name: data" So clearly that's the wrong syntax for it
EDIT: put lines in the same order they were in original code. Sorry, I have commented lines between them
Maybe your server doesn't like to receive the data as a field called data.
This ofcourse depends totally on the PHP code we don't see since you didn't share that part. b
But at least I can tell you that --data or also simply -d in curl refer to the entire data section and is not a field called data.
You could try to instead use a MultiPartFormDataSection passing just the data itself without a specific field name
var data = "{\"geneSetMembers\":[\"UST\"],\"geneSetName\":\"selection0\"}";
var form = new List<IMultiFormPart>{ new MultiPartFormDataSection(data) };
webRequest = UnityWebRequest.Post(url + route + to_say, form);
yield return webRequest.SendWebRequest();
which is now sent as content-type multipart/form-data though ...
Another alternative if your server really needs to receive a content-type application/json might be to "manually" compose the request e.g. like
var data = "{\"geneSetMembers\":[\"UST\"],\"geneSetName\":\"selection0\"}";
var request = new UnityWebRequest(url + route + to_say, "POST");
var bodyRaw = Encoding.UTF8.GetBytes(data);
request.uploadHandler = (UploadHandler) new UploadHandlerRaw(bodyRaw);
request.downloadHandler = (DownloadHandler) new DownloadHandlerBuffer();
request.SetRequestHeader("Content-Type", "application/json");
yield return request.SendWebRequest();
Though of you look close now this seems actually not to be the case since if you read the man curl
(HTTP) Sends the specified data in a POST request to the HTTP server, in the same way that a browser does when a user has filled in an HTML form and presses the submit button. This will cause curl to pass the data to the server using the content-type application/x-www-form-urlencoded
which is actually exactly the default content type used by the simple string version of UnityWebRequest.Post.
So thinking about it it should actually be as simple as using the pure string version of UnityWebRequest.Post:
var data = "{\"geneSetMembers\":[\"UST\"],\"geneSetName\":\"selection0\"}";
var request = UnityWebRequest.Post(url + route + to_say, data);
yield return request.SendWebRequest();

How to download CSV using REST API?

Here's a REST API that I am trying for downloading data as CSV file.
(function process(/*RESTAPIRequest*/ request, /*RESTAPIResponse*/ response) {
var data = '\n'; // workaround to separate <xml> start tag on first line
data += 'Firstname,Lastname,Username' + '\n';
data += 'Nikhil,vartak,niksofteng' + '\n';
data += 'Unknown,person,anonymous' + '\n';
response.setHeader("Content-Disposition", "attachment;filename=Xyz.csv");
response.setContentType("text/csv");
response.setBody({'data':data});
})(request, response);
According to docs setBody requires a JS object and thus if I just pass data variable I get error stating that data cannot be parsed into ScriptableObject.
So with the current code I get below response:
{
"result": {
"data": "\nFirstname,Lastname,Username\nNikhil,vartak,niksofteng\nUnknown,person,anonymous\n"
}
}
And the generated CSV looks like this:
Any idea how to get rid of that XML markup on 1st and 5th line?
The setBody method expects a Javascript object which it is then going to serialize to JSON or XML based on what the client tells it do via Accept header.
In your case you want to produce your own serialized format: CSV. So instead of using the setBody method, use the stream writer interface to directly write to the response stream.
response.setContentType("text/csv");
response.setStatus(200);
var writer = response.getStreamWriter();
writer.write('Firstname,Lastname,Username\n');
writer.write('Nikhil,vartak,niksofteng\n');
etc.
Note you will have to handle all the details of CSV format yourself, including properly encoding any special characters like if you want a field to contain a comma like "Nik,hil".
Cheers,
Silas

How to make a REST delete method with cfhttp

I have never done it before and now when the need arise, things are not working.
I have to send an ID to delete a DB record with RESTful service. Here is the code I am trying:
<cfhttp url="http://127.0.0.1:8500/rest/test/something" method="DELETE" port="8500" result="qryRes1">
<cfhttpparam type="body" value="36"/>
</cfhttp>
and in the REST function
remote any function someName() httpmethod="DELETE"{
var testID = ToString(getHTTPRequestData().content);
//make db call to delete
return testid;
}
The result comes as blank [empty string]. I am not able to retrieve the sent value in function. What I am missing?
Edit: one slightly different but related to CF rest, is it necessary to convert query to an array before sending it back to client? Directly serializing won't solve the purpose same way?
you may want to take a look at deleteUser() in http://www.anujgakhar.com/2012/02/20/using-rest-services-in-coldfusion-10/ as an example of how to support DELETE in REST API style.
remote any function deleteUser(numeric userid restargsource="Path") httpmethod="DELETE" restpath="{userid}"
{
var response = "";
var qry = new Query();
var userQry = "";
qry.setSQl("delete from tbluser where id = :userid");
qry.addParam(name="userid", value="#arguments.userid#", cfsqltype="cf_sql_numeric");
userQry = qry.execute().getPrefix();
if(userQry.recordcount)
{
response = "User Deleted";
} else {
throw(type="Restsample.UserNotFoundError", errorCode='404', detail='User not found');
}
return response;
}
As for the 2nd part of your question, it'd be best to first turn a query into a array of structs first unless you're using CF11 which does it for you. See: http://www.raymondcamden.com/index.cfm/2014/5/8/ColdFusion-11s-new-Struct-format-for-JSON-and-how-to-use-it-in-ColdFusion-10
The default JSON structure for query in CF 8 to 10 were designed for <cfgrid> in ColdFusion on top of Adobe's discontinued Spry framework.

setting up script to include google docs form data in email notification

I've setup a form using googledocs. I just want to have the actual data entered into the form emailed to me, as opposed to the generic response advising that the form has been completed.
I have no skill or experience with code etc, but was sure i could get this sorted. I've spent hours+hours and haven't had any luck.
My form is really basic.it has 5 fields. 4 of which are just text responses, and one multiple choice.
I found this tute online (http://www.labnol.org/internet/google-docs-email-form/20884/) which i think sums up what i'm trying to do, but have not been able to get it to work.
from this site i entered the following code:
function sendFormByEmail(e)
{
var email = "reports.mckeir#gmail.com";
var subject = "Google Docs Form Submitted";
var s = SpreadsheetApp.getActiveSheet();
var headers = s.getRange(1,1,1,s.getLastColumn()).getValues()[0];
var message = "";
for(var i in headers)
message += headers[i] + ' = '+ e.namedValues[headers[i]].toString() + "\n\n";
MailApp.sendEmail(email, subject, message);
}
To this, i get the following response: ->
Your script, Contact Us Form Mailer, has recently failed to finish successfully. A summary of the failure(s) is shown below. To configure the triggers for this script, or change your setting for receiving future failure notifications, click here.
The script is used by the document 100% Club.
Details:
Start Function Error Message Trigger End
12/3/12 11:06 PM sendFormByEmail TypeError: Cannot call method "toString" of undefined. (line 12) formSubmit 12/3/12 11:06 PM
Is anyone able to help shed some light on this for me? I'm guessing i'm not including some data neeeded, but i honestly have no clue.
Workaround http://www.labnol.org/internet/google-docs-email-form/20884/
You have to setup app script to forward the data as email.
I'll point to the comment above that solved it for me: https://stackoverflow.com/a/14576983/134335
I took that post a step further:
I removed the normal notification. The app script makes that generic text redundant and useless now
I modified the script to actually parse the results and build the response accordingly.
function sendFormByEmail(e)
{
var toEmail = "changeme";
var name = "";
var email = "";
// Optional but change the following variable
// to have a custom subject for Google Docs emails
var subject = "Google Docs Form Submitted";
var message = "";
// The variable e holds all the form values in an array.
// Loop through the array and append values to the body.
var s = SpreadsheetApp.getActiveSheet();
var headers = s.getRange(1,1,1,s.getLastColumn()).getValues()[0];
// Credit to Henrique Abreu for fixing the sort order
for(var i in headers) {
if (headers[i] = "Name") {
name = e.namedValues[headers[i]].toString();
}
if (headers[i] = "Email") {
email = e.namedValues[headers[i]].toString();
}
if (headers[i] = "Subject") {
subject = e.namedValues[headers[i]].toString();
}
if (headers[i] = "Message") {
message = e.namedValues[headers[i]].toString();
}
}
// See https://developers.google.com/apps-script/reference/mail/mail-app#sendEmail(String,String,String,Object)
var mailOptions = {
name: name,
replyTo: email,
};
// This is the MailApp service of Google Apps Script
// that sends the email. You can also use GmailApp here.
MailApp.sendEmail(toEmail, subject, message, mailOptions);
// Watch the following video for details
// http://youtu.be/z6klwUxRwQI
// By Amit Agarwal - www.labnol.org
}
The script utilized in the example is extremely generic but very resilient to change because the message is built as a key/value pair of the form fields submitted.
If you use my script you'll have to tweak the for loop if statements to match your fields verbatim. You'll also want to edit the toEmail variable.
Thanks again for the question and answers. I was about to ditch Google Forms as the generic response was never enough for what I was trying to do.
Lastly, in response to the actual problem above "toString of undefined" specifically means one of the form fields was submitted as blank. If I had to guess, I would say the author only used this for forms where all the fields were required or a quick undefined check would've been put in place.
Something like the following would work:
for(var i in headers) {
var formValue = e.namedValues[headers[i]];
var formValueText = "";
if (typeof(formValue) != "undefined") {
formValueText = formValue.toString();
}
message += headers[i] + ' = '+ formvalueText + "\n\n";
}
I haven't tested this precisely but it's a pretty standard way of making sure the object is defined before trying methods like toString() that clearly won't work.
This would also explain Jon Fila's answer. The script blindly assumes all of the header rows in the response are sent by the form. If any of the fields aren't required or the spreadsheet has fields that are no longer in the form, you'll get a lot of undefined objects.
The script could've been coded better but I won't fault the author as it was clearly meant to be a proof of concept only. The fact that they mention the replyTo correction but don't give any examples on implementing it made it perfectly clear.
If this is a Google Form, do you have any extra columns in your spreadsheet that are not on the form? If you delete those extra columns then it started working for me.
You don't need to use a script. Simply go to Tools >> Notification Rules on your Google Spreadsheet. There you can change the settings to receive an email with your desired information every time the document is changed.