Craig Regester · Sep 13, 2023 go to post

Just a quick stab but I believe it's because you're extending %Persistent first and not Ens.Response and not specifying inheritance from the right so it's not appropriately applying the message viewer projection.

You normally wouldn't need to extend %persistent if these are going to be transient objects that you want to purge out via normal Ensemble purge (be sure to add in the appropriate logic to cascade delete child objects that are not %serial. Lots of posts on this "cascade delete"

Craig Regester · Sep 8, 2023 go to post

Hey Scott - While the order itself isn't important as long as your %JSONFIELDNAME values are proper, my guess is that the current error you are hitting is one of the sub-class definitions we can't see here is missing this as well:
Parameter %JSONIGNOREINVALIDFIELD As BOOLEAN = 1;
For instance, I bet LastName is a field coming back in the object represented by this property

Property AttendingPhysicians As list Of User.REST.Epic.dt.ArrayOfAttendingPhysician(%JSONFIELDNAME = "AttendingPhysicians");

So you would have to add that class param to that class as well (or wherever appropriate downstream) unless you intend to handle the LastName field appropriately.

Craig Regester · Aug 10, 2023 go to post

Hey Scott; I haven't read through all of this but use

Set tSC = pResponse.%JSONImport(tHTTPResponse.Data)
Quit:$$$ISERR(tSC) tSC

So you can capture perhaps why the import is failing.

Generally speaking as well, for items like:

Property AppointmentSchedules As User.REST.Epic.dt.ArrayOfScheduleProviderReturn(%JSONFIELDNAME = "AppointmentSchedules");
Which I believe is referencing a JSON Array of objects, you would need to do the following at bare minimum;

Property AppointmentSchedules As list Of User.REST.Epic.dt.ArrayOfScheduleProviderReturn(%JSONFIELDNAME = "AppointmentSchedules");
 

Believe you have several of these situations.

Craig Regester · Aug 26, 2022 go to post

Many thanks for sharing this Ron as it ultimately solved my issue w/ apache httpd.

I poured over the documentation several times convinced I was crazy/missing something obvious but never saw this particular nugget.

Any additional guidance from InterSystems on updating documentation or identifying why this is needed?

Craig Regester · Aug 3, 2022 go to post

As I mentioned to Bob in ISC Discord chat, I'm in the midst of writing a tech article on podman w/ SAM - there are a few tricks to know even beyond the container download issue you may have. Sorry for the delay on the article - switched companies in the last week or two and my ISC Developer Community account is in transition!

For that download issue, if you can grab the containers its after on another machine and upload to /tmp or wherever, podman lets you specify a local folder to source them from - podman import is the command to look at - so podman has a copy and when compose is ran, as long as the tags match it'll pull em right in!

Edit: doc reference for podman-import: https://docs.podman.io/en/latest/markdown/podman-import.1.html

Craig Regester · Jul 25, 2022 go to post

Agree with this approach, I was typing it up as well. Don't believe you're really seeing a request timeout and its masking the real issue.

Craig Regester · Jul 24, 2022 go to post

Believe you are looking for System Configuration->Security->Applications->Web Applications, then click on the link for the webapp that looks like: /csp/healthshare/devclin and adjust the Session Timeout setting.

Default is 900 seconds I believe but we often increase ours to 1800 (30 minutes).

However, not sure this will address it for you as I'm working with a rather Production as well and my result still comes back within a few seconds. You mentioned an HTTP 500 which is a generic error - I would have expected a 408 (Request Timeout) or 504 (Gateway Timeout) if it were truly a session timeout issue.

Wonder if you can see any error in the Application Error log.

Craig Regester · Jul 7, 2022 go to post

What version are you running where $EXTRACT is not available? Not sure I’ve heard of such a situation.  
 

edit: noted you referenced ..ReplaceStr which is an Interoperability function. There are semi-equivalents of $EXTRACT and $FIND in there as well - $EXTRACT is ..SubString. But note if you use the solution I or David presented, don’t use .. in front of $EXTRACT, $FIND or $PIECE as these aren't interoperability functions but pure ObjectScript functions. 
 

my suggestion, that I know works as we do something similar, is as follows:

“(“_$PIECE(input,”(“,2)

Have to re-add the opening paren since we’re using that as our splitter, but some find it easier than chaining multiple functions together. David’s solution is certainly valid too.

https://docs.intersystems.com/iris20221/csp/docbook/Doc.View.cls?KEY=RC…

Craig Regester · Jun 22, 2022 go to post

Not to dog pile on as the links provided above provide the information but I want to highlight this one in particular, as it was a big 'gotcha' for us and even a long time InterSystems consulting partner I was working with at the time.

You can do everything else right, but if you setup your URL prefix in IIS for dev to be, say, /dev, so URLs would be myhost.com/dev/csp/blahblahblah, but your instance name is IRIS or ANYTHING other than 'dev', you will fail and not understand why.

The link I shared is just a sub-section of the same link Alexander shared but it addresses this gotcha. You must use that command in terminal to set the prefix on the instance associated with the URL prefix you created for it to recognize it appropriately. Without it, the only 'prefix' that will work by default is the name of the instance itself.

Hint, you can specify multiple prefixes (if desired) in comma-delimited format. It mentions that as well but a reason I bring it up is it helps tremendously cut down on the number of separate IIS configs to maintain in mirroring situations because I can setup the main prefix for the VIPA (Virtual IP Address) but also add prefixes for the individual server identifiers so if necessary, we can directly go to the individual servers of the mirror, even if they're not the current primary, for maintenance tasks (upgrade prep, security/task syncs, etc).  In this way, it totally negates any need for the private web server to remain running on your instances (big security win) and keeps the amount of maintenance relatively low on the IIS (or other web servers) side as well.

Craig Regester · Jun 20, 2022 go to post

Nice! This post is definitely a good example of a plethora of ways to solve the same challenge. :-)

We have scripts similar to yours above that use SQL on Ens_Config.Item to iterate through for auditing component settings on a daily basis to ensure our engineers don't do anything too crazy (on purpose or not!) and it has worked well for us for several years now so confident in saying it should be a decent approach for you as well!

Best of luck!

Craig Regester · Jun 20, 2022 go to post

That’s fair - another consideration then is something we also use - globals as “global parameters”. 
 

I.e. create a global like ^MyPkg(“DowntimeActive”) = <future $H value here> and then use a simple boolean check to see if current $H is less than the globals $H. Thus it automatically expires for us.  
 

We’ve evolved a bit beyond this now but for the source control reasons, it may be a simple solution. 

Craig Regester · Jun 19, 2022 go to post

Hey @Jimmy Christian - Read through your responses to others to understand a bit more of what you're after. Short of creating a custom process class, there's no way to expose a setting on the Router in the Management Portal Production UX.

That said, if I understand what you are ultimately trying to achieve, might I suggest a simple Lookup Table named something like 'RouterDowntimeSettings' and then in that table, simply have some entries defined like:

Key Value
MyPkg.Rules.ClassName 1

Then inside your rules where you want to use this, you simply use the built-in Lookup function on that table and pass in the class name that you specified as the key. Might look something like this:
 

<rulename="Discard"disabled="false"><whencondition="Lookup('RouterDowntimeSettings','MyPkg.Rules.ClassName')=1"><return></return>

Since Lookup returns empty string if the key is not found, this would only return true if a key was explicitly specified in the table, thus making it easy to implement as needed without causing unexpected errors or functionality.
Just an alternate approach that may work for you and reduce the amount of effort (no custom class creation that would need to be maintained). 

Edit: Fixed the boolean, didn't see your original was using returns.

Craig Regester · May 16, 2022 go to post

While technically this could be written using a custom class extending Ens.BusinessService, what you describe has you playing the 'Operation' role more than the 'Service' role. We do this with many integrations and have a design pattern that works well for us.

In short, you need:

  • Custom Adapterless Trigger Service (extends Ens.BusinessService). Only purpose is to send a simple Ens.Request to a Busines Process (BPL of custom class that extends Ens.BusinessProcess) on a timed interval... either using a schedule or the call interval.
  • Custom Business Operation likely extending EnsLib.HTTP.GenericOperation or something similar.
  • Custom Business Process to handle the business logic flow...
    • at time of Ens.Request from the trigger service, it formats a request object and sends to the Business Operation that executes your GET call against the webservice to receive the JSON payload.
    • JSON Payload returned by Business Operation to the Business Process as a custom message object ideally (no longer JSON) and from there, any manner of normal Ensemble workflows can take place (data transforms, ID logic, call outs to other business operations and so forth.)

You appear to be on a very old version of Ensemble so not sure how much recent documentation will be relevant to your use case and you likely will face a lot more difficultly using Ensemble 2014.1 than you would with something 2019.x or newer, but here's a few reference links to get the thought processes going:
Using the HTTP Outbound Adapter | Using HTTP Adapters in Productions | InterSystems IRIS for Health 2021.2
Creating REST Operations in Productions | Using REST Services and Operations in Productions | InterSystems IRIS for Health 2021.2
 

Craig Regester · May 6, 2022 go to post

Sounds like the vendor is giving you bad information then. They need to be producing better error output on their side instead of just {'Message': 'An error has occurred.' }

Likely something in the formatting of what of the fields in the JSON package is wrong - incorrect field name or bad value - but without some better error messages from the vendor or more guidance from their end, you're kind of stuck.

Craig Regester · May 5, 2022 go to post

Apologies! Missed that line earlier! Your method of making the JSON is different than how I do it though - and execute it successfully - so could try this? 

set JsonArray = []
set SignaletiquePat = {}
        
set SignaletiquePat.internalID = "050522001"
set SignaletiquePat.lastName = "Tata"
set SignaletiquePat.firstName = "Silva"
set SignaletiquePat.dateOfBirth = "05/05/2022"
set SignaletiquePat.gender = "1"
set SignaletiquePat.clinicId = 22
set SignaletiquePat.mothersName = "Anne Dede"
set SignaletiquePat.address = "Rue des prés, 50"
set SignaletiquePat.postalCode = "4620"
set SignaletiquePat.place = "fléron"
set SignaletiquePat.telephone1 = "0499998855"

Do JsonArray.%Push(SignaletiquePat)
set JsonArrayOBJ = JsonArray.%ToJSON()
Craig Regester · May 5, 2022 go to post

Your code:

Do HTTPRequestPat.EntityBody.Write(JsonArrayOBJ) // like this ? didnt work
          set ..Adapter.HTTPServer = "185.36.164.222"
          set ..Adapter.URL = UrlPats_"/api/patient/signaletic"
        set st = ..Adapter.SendFormDataURL(..Adapter.URL,.callResponsePat,"POST",HTTPRequestPat,,JsonArrayOBJ)

Try this instead:

Set json=JSONArrayOBJ.%ToJSON()
Do HTTPRequestPat.EntityBody.Write(json)
set ..Adapter.HTTPServer = "185.36.164.222"
set ..Adapter.URL = UrlPats_"/api/patient/signaletic"
set st = ..Adapter.SendFormDataURL(..Adapter.URL,.callResponsePat,"POST",HTTPRequestPat)
Craig Regester · May 3, 2022 go to post

I was able to get it going...

Only real difference was the addition of 

command:
      - --check-caps false

For the iris service (probably should be added to the default release per Using InterSystems IRIS containers with Docker 20.10.14+ | InterSystem )

Then adjusting start.sh's final line to look like this:

# old line: docker-compose -p sam up -d
podman-compose -p sam up -d

stop.sh

podman-compose -p sam down

(Alternatively, could alias docker-compose podman-compose)

I can get into SAM now and add a cluster but for some reason it's not able to talk to it (no URL prefix or authentication involved but is going through a Web Gateway and SAM doesn't seem to allow specifying HTTPS instead of HTTP but maybe it does it in the background.

Are there plans to support URL prefix and authenticated endpoints? Having an unauthenticated endpoint is a bit of a no-no around here and to get around the URL Prefix thing with some of my instances, I'll have to start up the private web server just for SAM which is also not desired.

Craig Regester · May 2, 2022 go to post

Attempting to get this to run on RHEL8 w/ podman-compose instead of docker.
 

Updated start.sh and generally everything starts up ok but nginx can’t seem to reach the iris instance. Think it’s a difference in how the ports get exposed. Only spent a few minutes on it but if anyone is aware, just probing to see if someone else has already solved. 

Craig Regester · Apr 28, 2022 go to post

For maintenance releases, we use the document you linked to for a few key purposes:

  • Are there any items reflected that we're impacted by potentially, even if unknowingly? (i.e. something causing a data integrity issue or unclean data in Interoperability msg)
  • Are there any items that brought to light features/capabilities we may not have even know but ultimately decided to go after because of the release notes?
  • If yes to either of the above...
    • Are there any fixes that will require extensive retesting of our integrations in PROD/TEST(STAGE)?
      • If so, does that outweigh the benefits of bullets 1 and 2 and thus wait for a major EM release?

For example, I recently upgraded our IRIS for Health 2021.1 instances to 2021.1.2 to take advantage of the (enhanced) debugging in VS Code IDE after determining the other items wouldn't 'break' anything else we were doing and/or happy with... while also seeing a few other issues we've noted but were not show stoppers for us were also addressed!

So yes, very much like the format and in fact, would love to see MORE like this for the EM releases. (i.e., 2021.1.0) as some of us nerdy folks like to see the low-level changes/updates. :-)

Thanks for all you do!

Craig Regester · Apr 6, 2022 go to post

I may be misunderstanding your complete use-case but we are also doing something similar for 21st Century Cures and the easiest way to handle this, that I identified anyway, is through dynamic terminology mapping.

So DS can go in for the DocumentType.Code per usual but if you look at the FHIR Annotations/Mappings, you'll note DocumentType is a CodeTableDetail item - HS.SDA3.CodeTableDetail.DocumentType - so you can setup a simple CodeTable to map from DS to the appropriate LOINC without needing to mess with extensions and custom pairs. Just ensure you have an appropriate Translation Profile setup for your access gateway that serves out FHIR (ODS likely) so that it picks up the map to get from your HL7v2 DocumentType code table to LOINC.

EDIT to add - thus far, I have not once had to modify a built in transform to support US CDI Requirements. I suspect with the UCR release coming out in the next month or two, most of the US CDI v2/v3 mappings will also be handled better with standard CodeTable/Terminology Maps filling in any gaps.

Craig Regester · Mar 28, 2022 go to post

Unclear what you mean exactly but if you're wondering if IRIS will install on something like a Synology Disk Station - yes - it works just fine using either Docker w/ containers or full VM Server experience where you first install an OS like RedHat or Ubuntu into a VM and then install the appropriate kit.

Obviously would only be done for development purposes and not a production like scenario. Most NAS servers can't push the required IOPS to properly support a production IRIS server.

Craig Regester · Mar 19, 2022 go to post

Believe what you’re after us a query like this:

select *

from enslib_hl7.message as hl7

where hl7.id not in (select messagebodyid from ens.messageheader where messagebodyclassname = ‘EnsLib.HL7.Message’)

apologies for poor formatting, typing on a phone while a kiddo naps. But that should help identify the message bodies that are orphaned. 

Craig Regester · Mar 4, 2022 go to post

I have tackled a challenge like this - with additional complex wrinkles where the related records were all in separate files in one big zip-file pulled down via SFTP by my production Service and I needed to generate a combined file for the Complex Record Map to process with the appropriate prefixes in place.

I hope this will be useful to you - after getting it working, the vendor I had to write it for went belly up so I didn't finish cleaning up my traces or comments but this was fully working.  The general flow is an SFTP pickup of a zip file daily that contains 4 files... a PAT (patient) file, a data element 1, data element 2 and data element 3 file - all comma separated. the Patient Identifier in the first file could link to the other 3 files in one of the columns.

At the end, I up with a single file I pushed into CRM that looks like (where PAT ID is 123456):

PAT|123456,SMITH,JOHN,M,moredata,etc
DATA1|49492,123456,data1data,data1moredata,data1etc
DATA2|577545,123456,data1data,data1moredata,data1etc
DATA3|454543,123456,data1data,data1moredata,data1etc
I hope it's useful to you to at least get an idea of how to get started on your particular use case. Happy to try and clarify anything if needed.
 

/// Custom business service to handle ingesting multiple related delimited flat files (contained in a single ZIP!) and 
/// combining them into a single message per Patient that is then fed into a Complex RecordMap.
Class MyPkg.Services.CRM.BatchZip Extends EnsLib.RecordMap.Service.ComplexBatchStandard [ Final ]
{

Parameter ADAPTER = "EnsLib.FTP.InboundAdapter";

Parameter SETTINGS = "ZipUtility:Basic";

Parameter Slash = {$Case($System.Version.GetOS(),"Windows":"\",:"/")};

/// Operating System Utility, with parameters, that will be executed to extract zip file from vendor.
/// Use {filename} as placeholder for the dynamic ZIP file name within the parameters.
/// 
/// Note that any utility used must write the filenames of the contents to stdout for interrogation
/// by this service.
/// 
/// Default: unzip (GNU) linux utility for unix/linux based operating systems
Property ZipUtility As %String [ InitialExpression = "unzip -o {filename}" ];

Method OnProcessInput(
    pInput As %FileBinaryStream,
    pOutput As %RegisteredObject,
    ByRef pHint As %String) As %Status
{
    Set tSC = $$$OK
    Set instanceID = $System.Util.CreateDecimalGUID()
    $$$TRACE("Unique InstanceID: "_instanceID)
    Set ^MyPkg.Temp(instanceID) = "Creating CRM-compatible masterfile for this batch file input: "_pInput.Filename
    $$$TRACE("Starting to process "_##class(%File).GetFilename(pInput.Filename))
    $$$TRACE("Executing GetZipContents")
    Set tSC = ..GetZipContents(pInput.Filename, .files)
    If $$$ISERR(tSC) Quit tSC

    // Process each sub-file into a temporary global so we can add our CRM fixed leading data and join the records together
    $$$TRACE("Processing each file into a temporary global: ^MyPkg.Temp("_instanceID_")")
    Set ptr=0
    While $ListNext(files,ptr,file)
    {
        $$$TRACE("Processing file "_file)
        If ..startsWith($P(file,..#Slash,*,*),"patients_")
        {
            Set tSC = ..ProcessFile(file, ",", instanceID)
        }
        ElseIf ..startsWith($P(file,..#Slash,*,*),"dataElement1_")
        {
            Set tSC = ..ProcessFile(file, ",", instanceID)
        }
        ElseIf ..startsWith($P(file,..#Slash,*,*),"dataElement2_")
        {
            Set tSC = ..ProcessFile(file, ",", instanceID)
        }
        ElseIf ..startsWith($P(file,..#Slash,*,*),"dataElement3_")
        {
            Set tSC = ..ProcessFile(file, ",", instanceID)
        }
        Else
        {
            Do ##class(%File).Delete(file)
        }
    }
    
    $$$TRACE("Creating MasterInputFile that we'll feed into a Complex Record Map.")
    Set tSC = ..CreateMasterInputFile(pInput.Filename, instanceID, .masterInputFile)
    
    $$$TRACE("MasterInputFile: "_masterInputFile)
    $$$TRACE("Now processing MasterInputFile into Complex RecordMap.")
    Try {
        Set masterInputFileStream = ##class(%FileBinaryStream).%New()
        Set masterInputFileStream.Filename = masterInputFile
        Set tLookAhead = ""
        Set tIOStream = ##class(EnsLib.RecordMap.Service.FileServiceStream).%New(masterInputFileStream)
        Set tIOStream.Name = ..GetFileName(masterInputFileStream)
        
        While 'tIOStream.AtEnd {
            Set tPosition = tIOStream.Position
            Set tSC = ..GetBatch(tIOStream, .tBatch,,.tLookAhead)
            If $$$ISERR(tSC) || (tPosition=tIOStream.Position) Quit
            
            Set ..%SessionId = ""
            Set tStatus = ..ForceSessionId()
            If $$$ISERR(tStatus) Quit
            
            Set tSC = ..SendRequest(tBatch,'..SynchronousSend)
            If $$$ISERR(tSC) Quit
        }
        If $$$ISERR(tSC) Quit
        
        If 'tIOStream.AtEnd {
            $$$LOGWARNING($$$FormatText($$$Text("Failed to advance record stream. Stopped reading file '%1' at position %2, not at end.","Ensemble"),tIOStream.Name,tIOStream.Position))
        }
    }
    Catch ex {
        Set tSC = $$$EnsSystemError
    }
    If $get(tLookAhead) '= "" {
        $$$LOGINFO("Discarding trailing characters: '"_tLookAhead_"'")
    }
    
    $$$TRACE("Cleaning up the temporary global we created.")
    Set tSC = ..CleanUp(instanceID)
    $$$TRACE("Completed "_##class(%File).GetFilename(pInput.Filename))
    Quit tSC
}

Method ProcessFile(
    pFilename As %String,
    pDelimiter As %String = ",",
    pInstanceID As %String) As %Status [ Private ]
{
    Set tSC = $$$OK
    Set skipHeader = 1
    
    Set file=##class(%File).%New(pFilename)
    Set tSC = file.Open("RU")
    While 'file.AtEnd
    {
        Set line = file.ReadLine()
        If skipHeader
        {
            Set skipHeader = 0
            Continue    
        }
        
        If line '[ pDelimiter Continue
        
        If ..startsWith($P(pFilename,..#Slash,*,*),"patients_")
        {
            // How do we identify the 'key' value to link up the other pieces? Get a piece of the row and store it as a part of the global key!
            Set key = $Piece(line,pDelimiter,1,1)_","_$Piece(line,pDelimiter,2,2)
            // Let's give ourselves a prefix! PAT|
            Set ^MyPkg.Temp(pInstanceID,"PAT|",key) = "PAT|"_line
        }
        ElseIf ..startsWith($P(pFilename,..#Slash,*,*),"dataElement1_")
        {
            // Each dataElement has a key for itself but also a linking key to the PAT
            Set ^MyPkg.Temp(pInstanceID,"DATA1|",$Piece(line,pDelimiter,1,1),$Piece(line,pDelimiter,2,2)) = "DATA1|"_line
        }
        ElseIf ..startsWith($P(pFilename,..#Slash,*,*),"dataElement2_")
        {
            // Each dataElement has a key for itself but also a linking key to the PAT
            Set ^MyPkg.Temp(pInstanceID,"DATA2|",$Piece(line,pDelimiter,2,2),$Piece(line,pDelimiter,1,1)) = "DATA2|"_line
        }
        ElseIf ..startsWith($P(pFilename,..#Slash,*,*),"dataElement3_")
        {
            // Each dataElement has a key for itself but also a linking key to the PAT
            Set ^MyPkg.Temp(pInstanceID,"DATA3|",$Piece(line,pDelimiter,3,3),$Piece(line,pDelimiter,5,5)) = "DATA3|"_line
        }
    }
    
    Do file.Close()
    Do ##class(%File).Delete(pFilename)
    Quit tSC
}

/// Let's start putting everything together into one big file that CRM will process!
Method CreateMasterInputFile(
    pSourceFilename As %String,
    pInstanceID As %String,
    Output MasterInputFilename) As %Status [ Private ]
{
    Set tSC = $$$OK
    Set MasterInputFilename = $Replace(pSourceFilename,".zip",".txt")
    
    Set fileObj = ##class(%File).%New(MasterInputFilename)
    Set tSC = fileObj.Open("WSN")
    If ($SYSTEM.Status.IsError(tSC)) {
        Do $System.Status.DisplayError(tSC)
        Quit $$$NULLOREF
    }
    
    Set key=$Order(^MyPkg.Temp(pInstanceID,"PAT|",""))
    While key'=""
    {
        Set patID = $Piece(key,",",2,2)
        
        // Write out PAT| 
        Do fileObj.WriteLine(^MyPkg.Temp(pInstanceID,"PAT|",key))
        
        // Get dataElement1 for that PAT next... patID Key 1, dataElement1 Key 2
        Set data1Key = $Order(^MyPkg.Temp(pInstanceID,"DATA1|",""))
        While data1Key'=""
        {
            If data1Key = patID
            {
                Set data1Key2 = $Order(^MyPkg.Temp(pInstanceID,"DATA1|",data1Key,""))
                While data1Key2'=""
                {
                    Do fileObj.WriteLine(^MyPkg.Temp(pInstanceID,"DATA1|",data1Key,data1Key2))    
                    Set data1Key2 = $Order(^MyPkg.Temp(pInstanceID,"DATA1|",data1Key,data1Key2))
                }
            }
            Set data1Key = $Order(^MyPkg.Temp(pInstanceID,"DATA1|",data1Key))
        }
        
        // Get dataElement2 for that PAT next... patID Key 1, dataElement2 Key 2
        Set data2Key = $Order(^MyPkg.Temp(pInstanceID,"DATA2|",""))
        While data2Key'=""
        {
            If data2Key = patID
            {
                Set data2Key2 = $Order(^MyPkg.Temp(pInstanceID,"DATA2|",data2Key,""))
                While data2Key2'=""
                {
                    Do fileObj.WriteLine(^MyPkg.Temp(pInstanceID,"DATA2|",data2Key,data2Key2))    
                    Set data2Key2 = $Order(^MyPkg.Temp(pInstanceID,"DATA2|",data2Key,data2Key2))
                }
            }
            Set data2Key = $Order(^MyPkg.Temp(pInstanceID,"DATA2|",data2Key))
        }

        // Get dataElement3 for that PAT next... patID Key 1, dataElement3 Key 2
        Set data3Key = $Order(^MyPkg.Temp(pInstanceID,"DATA3|",""))
        While data3Key'=""
        {
            If data3Key = patID
            {
                Set data3Key2 = $Order(^MyPkg.Temp(pInstanceID,"DATA3|",data2Key,""))
                While data3Key2'=""
                {
                    Do fileObj.WriteLine(^MyPkg.Temp(pInstanceID,"DATA3|",data3Key,data3Key2))    
                    Set data3Key2 = $Order(^MyPkg.Temp(pInstanceID,"DATA3|",data3Key,data3Key2))
                }
            }
            Set data3Key = $Order(^MyPkg.Temp(pInstanceID,"DATA3|",data3Key))
        }
        
        Set key = $Order(^MyPkg.Temp(pInstanceID,"PAT|",key))
    }
    
    Do fileObj.Close()
    
    Quit tSC
}

/// Using full path, will extract Zip file using $ZF(-100) - OS-level execution - and read in the filenames
/// of what was extracted for further processing, returning as a list to OnProcessInput
Method GetZipContents(
    pFilename As %String,
    Output pContentFilenames As %List) As %Status [ Private ]
{
    Set tSC = $$$OK, tempFilenames = ""
    Set stdoutFilename = ##class(%File).TempFilename("myTempCRMBatch")
    
    Set unzipCmd = $Replace(..ZipUtility,"{filename}",pFilename)
    $$$TRACE("Executing OS command: "_unzipCmd)
    Set workingDirectory = $Piece(pFilename,..#Slash,1,*-1)
    Set unzipCmd = "cd "_workingDirectory_";"_unzipCmd
    Set sc = $ZF(-100,"/SHELL /NOQUOTE /STDOUT+="""_stdoutFilename_""" /STDERR+="""_stdoutFilename_"""",unzipCmd)
    
    Set stdout=##class(%File).%New(stdoutFilename)
    Set stdout.LineTerminator = $char(10)
    Set tSC = stdout.Open("RU")
    While 'stdout.AtEnd
    {
        Set stdoutLine = stdout.ReadLine()
        If stdoutLine [ ".csv"
        {
            Set temp = $LFS(stdoutLine," ")
            Set ptr = 0
            While $ListNext(temp,ptr,piece)
            {
                If $ZStrip(piece,"*W") [ ".csv"
                {
                    //$$$TRACE("Found file in zip: "_$ZStrip(piece,"*W"))
                    If tempFilenames '= "" Set tempFilenames = tempFilenames_","
                    Set tempFilenames = tempFilenames_workingDirectory_..#Slash_$ZStrip(piece,"*W")
                }
            }
            
        }
    }
    
    Do stdout.Close()
    Set tSC = ##class(%File).Delete(stdoutFilename)
    Set pContentFilenames = $LFS(tempFilenames,",")
    Quit tSC
}

Method CleanUp(pInstanceID As %String) As %Status [ Private ]
{
    Kill ^MyPkg.Temp(pInstanceID)
    Quit $$$OK
}

Method startsWith(
    value As %String,
    string As %String) As %Boolean [ CodeMode = expression, Internal, Private ]
{
($E($g(value),1,$L($g(string)))=$g(string))
}

}
Craig Regester · Feb 16, 2022 go to post

Hey Scott - Your questions require a bit of clarification to best answer but I can help a bit as I just went through this for both internally served and secured IRIS Management Portal and externally served and secured IRIS-hosted web services. 
 

There’s two layers to securing to consider and that’s where I would need clarification on which part your questions are after:

  • Mutual TLS 1.2 encryption to/from the web gateway module installed on Apache that acts as a reverse proxy of sorts between the web server and the IRIS server’s SuperServer port. (Actual users don’t use this port directly in a web browser) 
  • HTTPS/SSL Encryption on the Apache Web Server that encrypts the traffic between the client browser and web server itself.

For the a production quality/secure setup, you want to always achieve both of these in my opinion.

For the first bullet, if you control both sides of the equation (the IRIS server and the web server), you could easily do a self-signed cert using your redhat server’s CA as you can specify the CA Chain of Authority that validates the signed cert on both sides. 
 

For the second bullet, you really want to use a certificate authority that your user’s web browsers will natively trust. Eg if youre just serving up internally and all your users are joined to an internal domain, that domain’s CA could generate a web server cert you could install to be used by port 443 on apache httpd and your user’s browsers will likely be a-ok with it as domain CAs generally update their domain members Keystore on login (keyword being usually.) That CA could also be used to generate appropriate Server/Client profile certs to be used for the mutual tls of the first bullet  

But easiest approach for the second bullet is using a external trusted CA (think Thawte, VeriSign, and many others) as browsers will generally trust these “out of the box.” External CAs can also be used for the mutual TLS piece but generally overkill if the web gateway and iris server are all on the same internal network (again, in my opinion) - proper securing of private keys is important with use of internal CA for mutual TLS especially but really should be doing that anyway.

Reference for the mutual tls: https://docs.intersystems.com/irisforhealthlatest/csp/docbook/DocBook.U…

Craig Regester · Feb 13, 2022 go to post

Yea branches are still possible and yes users should log in as their own users (this extension allows users to enter their own git user.name and git user.email to track commits properly)  - there is stash support to a degree and the ability for branching but given the nature of IRIS being “trunk-based development” at its core, of course those branches still compile against the same IRIS namespace. 
But this extension will continue to improve and expand! I’ve been helping out where I can and providing suggestions from the standpoint of organizations that are traditional integration teams belonging to patient-care-centric healthcare orgs and Tim and his team are doing an amazing job. 
 

please be sure to join the discussions occurring at GitHub, try out the extension to get a feel for it and provide your voice to the continuing expansion of the extension! We’ve made some tremendous strides via this process and I’m excited to see the evolution!

Craig Regester · Feb 3, 2022 go to post

From the OS side in AIX, I can see it in parameters.isc (example from a QA env I'm playing with)

security_settings.iris_user: irisusr
security_settings.iris_group: irisusr
security_settings.manager_user: irisusr
security_settings.manager_group: irisusr

I do not recall how to see it in IRIS itself (or if it's even possible) but I remember wanting to figure out how to change the values after installation (due to someone goofing up an entry on a dev environment) and without a lot of effort, it is pretty difficult.

Craig Regester · Jan 5, 2022 go to post

Based on the msg alone , would need:

sudo apt install zlib1g

Instead of:

sudo apt install zlib1g-dev
Craig Regester · Dec 20, 2021 go to post

I posted this on a similar question a couple weeks back - this worked for me on my MBP M1:

If you prefer or need to use a kit (like myself), give multipass a look. Super easy to spin up a ubuntu VM in seconds.

Note that you need to pull down the arm64 version of ubuntu AND the arm64 iris kits, not traditional x86_64 architecture.

https://github.com/canonical/multipass
https://9to5linux.com/canonical-makes-it-easier-to-run-ubuntu-vms-on-ap…