Alex Woodhead · Jun 9, 2023 go to post

I would say Yes in the sense that HL7 messages can be constructed entirely from scratch via code.

The main supporting method for this on EnsLib.HL7.Message the method "SetValueAt" can use to add and replace content for an HL7 message.
There are various ImportFrom methods (File, Stream, String) where a vanilla base message can be correlated to an HL7 Message.
The DocType is assigned by method PokeDocType, after which virtual property paths used by "SetValueAt" method will assign content in the right place as expected.
The DTL (Data Transform Language) documents can visually assist converting some other form of structured data into new HL7 messages. They are dynamic in that they can respond not just to a source reference message but leverage Lookup tables to control the content created for output HL7 messages. Then new configuration can be added to lookup tables and this extends the behavior of an existing DTL.

For some code example I have a UnitTest framework class that contains code demonstrating correlation of flat content to an HL7 message, setting the DocType and comparing virtual document paths. https://openexchange.intersystems.com/package/UnitTest-DTL-HL7

Alex Woodhead · Jun 7, 2023 go to post

Some odd questions:

1) Is the license still valid

2) Is it possible to suspend the Task Schedule before changing the problem item.

Alex Woodhead · Jun 7, 2023 go to post

I think you hit the utility of the documented approach. To not accidentally delete the databases.

If you forgot to delete databases as part of namespace removal, they can be manually tidied up (System Administration->Configuration->System Configuration->Databases).

Questions I would also be asking when directed by Customer from a Vanilla "ENSEMBLE" name, to a purposeful named Namespace:

  • Are IRIS.DAT(s) located in the correct disk volume / Drive name. ie: Sufficient capacity for expected use. (Is it supposed to be on the same volume / location as where services are installed).
  • Is there a backup strategy. AND did I retest it yet?
  • Are the databases journal enabled.
  • Check Users don't still have "ENSEMBLE" as default namespace location
  • Check Scheduled Tasks are not expecting "ENSEMBLE" namespace
  • Update documented standard build script

One more thought. If you are using Unix / Linux for hosting IRIS, a more recent option is mergeCPF, to update both Namespace, Database and Application settings. Suitable for:

  • migration activity as a testable automated script
  • configuration and setup from vanilla docker images.

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

This allows applying updates with a simple text file AND it does both traditional CPF settings AND the [csp web] Application settings in one go.

Your question encouraged me to share a little web calculator for CreateApplication settings.

So less time needed to figure out Application setting names and values.

https://openexchange.intersystems.com/package/MergeCPF-Application-Sett…

Alex Woodhead · Jun 7, 2023 go to post

Am going to make some assumptions that you have been given an IRIS.DAT file and you want to mount that Database file on a new instance for some evaluation activity.

This is likely to not be exactly the situation but can serve as an example to help understand what you need to achieve.

A Namespace is a CODE and DATA context that database processes normally operate in.

To use the Database file it is "mounted" as a Database configuration.

The Database configuration is used by a Namespace, either as the DEFAULT for code and routines OR the data is MAPPED for use by a namespace.

As an example I create a copy of an empty database at: C:\InterSystems\somewhere\db\empty2
  This directory contains the IRIS.DAT.
In System Management Portal I can "Create a Database" (System Administration->Configuration->System Configuration->LocalDatabases)
  Use the path of new IRIS DAT file: "C:\InterSystems\somewhere\db\empty2"
  There will be a warning:  "Database file, IRIS.DAT, already exists in directory."

If you then navigate with System Explorer -> Globals

  And use "Lookin : Database" for the example is "EMPTY2"
  Here I added the Global Test

If the IRIS.DAT has both code (eg: SQL Table definitions) and Data, possibly you might want to create a new namespace and make the mounted database from earlier as the default database for routine and code for that namespace.
  So "Namespaces -> Create New Namespace

Alternatively, you may want to apply the new Data with code in another existing database for example SQL Table definitions. Here you could decide what data you want to map from the new database file to an existing namespace. (See: Explorer for Globals above)
  Navigate to Namespaces (System Administration->Configuration->System Configuration->Namespaces)
  Select "Global Mappings" of the namespace you want to "access data from the new Dat file".

Example to map the whole of global "Test" to existing namespace. Note this is will "hide" any data in "Test" that might exist in the default database of the pre-existing namespace.

Hint: Be sure to have database files unmounted or instance shut down when first learning to copy / swap database files around.

There are advanced ways of doing things. Occasionally you might get an IRIS database file from an operating system with a different Endian composition. The tool cvendian can help translate this to a target operating system endian mode. When moving big DAT files about it can be useful to run an SHA-1 or MD5 checksum hash on the database file before and after copying to ensure there is no corruption.

Hope this gave some ideas and helps progress the direction needed for your specific situation.

Alex Woodhead · Jun 6, 2023 go to post

I am utilizing properties on class methods to good effect.

I would not use a classmethod only approach for normal development.

There is nothing stopping a community parallel UnitTest.Manager re-implementation that follows a ClassMethod pattern.

Some have reimplemented UnitTest.Manager:

1) Without Deleteing the test classes at end of run (With Run instead of DebugRun)

2) Not needed ^UnitTestRoot to be defined.

Alex Woodhead · Jun 6, 2023 go to post

If for Interoperability productions it may also be appropriate to weigh up existing capabilities available for Production deployment. See: https://docs.intersystems.com/irisforhealth20232/csp/docbook/DocBook.UI…

It provides functionality for detecting implementation used by a production.

Have created idea for Production exports to generate IPM modules as well as the usual XML export deployment. https://ideas.intersystems.com/ideas/DPI-I-382

Alex Woodhead · Jun 6, 2023 go to post

Bring up the VSCode command palate.
Choose: ObjectScript: Compile current file with specified flags.

Use flags: ckb

Where:
b=Compile Dependent Classes
c=Compile
k=keep generated source code
u=Skip related upto date classes

Alex Woodhead · Jun 1, 2023 go to post

An example idea for Grand Prix entry:
Demonstration of a "Language Interface" (chat interface) to replace the functionality of a traditional online shop ( HTML UI )

  •   Use IRIS to hold a product catalogue (Images, Description, Cost, Stock)
  •   Use IRIS to present the "Language Interface". Can support via Embedded Python
  •   User Experience:
    •   Describe what you want instead of search
    •   Filter products based on preferences identified in conversation
    •   Automatically locate products via catalogue meta data from conversation content
  •   CSP or other web Session for current Shopping basket (Item, Quantity, Total)
  •   Persist and reuse preferences identified from previous shopping chats
Alex Woodhead · May 24, 2023 go to post

Cloning a particular task based on persistent class implementing %ConstructClone.

Can be good to have distinct names

ZN "%SYS"
set taskToCopyId=1000
set source=##class(%SYSTEM.Task).%OpenId(taskToCopyId)
for ns="FTP","WSDL","X12" {set copy=source.%ConstructClone()  set copy.JobGUID=""  set copy.Name=source.Name_" "_ns  set copy.NameSpace=ns  write !,ns," result ",copy.%Save()}

Can see other properties might want to reset via OBJ.Dump

set taskToCopyId=1000
set source=##class(%SYSTEM.Task).%OpenId(taskToCopyId)
do $SYSTEM.OBJ.Dump(source)
Alex Woodhead · May 24, 2023 go to post

Thanks for sharing answer.

Adding a snippet for %ZSTART in case fellow community person wished find / refer to later.

 #include %occStatus
  quit 0
SYSTEM() PUBLIC {
    Try {
    // Some test to determine whether to start or not
        if ##class(%File).Exists("c:\tmp\stop.txt") {
          Do INT^SHUTDOWN
        }  
    Catch { }
quit 0
}
Alex Woodhead · May 16, 2023 go to post

Great point Pietro. Experienced similar challenge for a global.

Between Cache 2010.2 and IRIS 2019.1 I needed to use ^%GOF and ^%GIF.

Also as global was mapped to different database, I needed to change from Namespace to Database Directory context for %GOF to export the data.

Alex Woodhead · May 16, 2023 go to post

A thought about moving from *.xml source controlled files to *.CLS (UDL) file content with VSCode.

  • The exported source content is different from existing in source control
  • The export name is different and may be seen as an unrelated resource

A while back I had shared community application ompare

I mention as this utility allows the comparison of what is installed in a namespace, regardless if the external format was XML or UDL. Some may find useful to see the real differences between their own historic product releases and a current release. At least until have a few releases / baseline in their new source repository.

Have notice other community apps like xml-to-udl that might be appropriate for need.

Alex Woodhead · May 15, 2023 go to post

Hi Stella,

Thinking could this use the "List Of" syntax to achieve a collection for XML projection.

Service:

Class TEST.SOAPCOL.MyService Extends EnsLib.SOAP.Service
{ 
/// Name of the WebService.
Parameter SERVICENAME = "MyService"; /// TODO: change this to actual SOAP namespace.
/// SOAP Namespace for the WebService
Parameter NAMESPACE = "http://tempuri.org"; Property Target As Ens.DataType.ConfigName; Parameter SETTINGS = "Target:Basic";
Method SendToTarget(pRqst As TEST.SOAPCOL.Request) As TEST.SOAPCOL.Response [ SoapAction = "http://tempuri.org/Sample.MyService.SendToTarget", WebMethod ]
{
    set tResponse=##class(TEST.SOAPCOL.Response).%New()
    Set tSC= ..SendRequestAsync(..Target, pRqst)
    return tResponse
}
}

Request (Note "List Of" )

Class TEST.SOAPCOL.Request Extends Ens.Request
{
  Property MyContainer As list Of TEST.SOAPCOL.ListItem;
} 

Inner Items

Class TEST.SOAPCOL.ListItem Extends (%Persistent, %XML.Adaptor)
{
Property PropertyA As %String;
Property PropertyB As %String; 
}

Response:

Class TEST.SOAPCOL.Response Extends Ens.Response
{
}  

Projection seen in Message Trace:

<?xml version="1.0" ?>
<!-- type: TEST.SOAPCOL.Request  id: 1 -->
<Request>
  <MyContainer>
    <ListItem>
      <PropertyA>AAA
      </PropertyA>
      <PropertyB>BBB
      </PropertyB>
    </ListItem>
  </MyContainer>
</Request>

Trying out client code in terminal:

set o=##class(MyService.MyServiceSoap).%New()
set o.Location="http://localhost:1980/TEST.SOAPCOL.MyService.CLS"
set pRqst=##class(MyService.Request).%New()
set item=##class(MyService.ListItem).%New()
set item.PropertyA="AAA"
set item.PropertyB="BBB"
do pRqst.MyCOntainer.Insert(item)
set response=o.SendToTarget(pRqst)
Alex Woodhead · May 15, 2023 go to post

In Routing Rule context, the newly projected function ExistsAny is applied.

The first argument is the LookupTable name from method signature.

An example expression:

1=(ExistsAny("AnemiaResults",Document.[OBX:5]))

Alex Woodhead · May 14, 2023 go to post

It is common to need to run unit tests for other modules that have overlapping / breaking functionality.

This is where the value of loading and running multiple TestSuites comes into its own.

Alex Woodhead · May 14, 2023 go to post

Hi Evgeny,

The global ^UnitTestRoot needs to be set to a real directory to satisfy the CORE UnitTest Runner.

As the first argument "Suite" is not specified, then no sub-directories are needed.

Set ^UnitTestRoot="\tmp\"

... may be sufficient for your purposes.

Alex Woodhead · May 13, 2023 go to post

A handy way to call UnitTest Case method in a terminal

Do ##class(%UnitTest.Manager).DebugRunTestCase("", "[ClassName]", "", "[MethodName]")

Run all Test methods for a TestCase:

Do ##class(%UnitTest.Manager).DebugRunTestCase("", "[ClassName]", "", "")

Placing a "break" line within a method can be useful when iterating creating the test. See the variables. Run other code and then type "g"+ [Enter] to continue.

The instance gives context to current test run, when raising assertions and other functionality.

Alex Woodhead · May 13, 2023 go to post

Hi Christine,

Please consider if the following snippet may be helpful.

Includes some common checks to prevent invalid lookup

Class Test.Fun Extends Ens.Rule.FunctionSet
{

ClassMethod ExistsAny(pTable As %String = "", pValue As %String = "", pDelimiter As %String = "<>") As %Boolean [ Final ]
{
  Quit:""=pTable 0
  Quit:""=pValue 0
  Quit:""=pDelimiter 0
  Set result=0
  // Use Length Function to count delimited fields. Always one or more
  Set len=$Length(pValue,pDelimiter)
  For i=1:1:len {
    Set key=$Piece(pValue,pDelimiter,i)
    // strip white space at front and end
    Set key=$ZSTRIP(key,"<>W")
    // Avoid empty string for key lookup
    Continue:""=key
    // make uppercase for case insensitive search
    Set key=$ZCVT(key,"U")
    If $Data(^Ens.LookupTable(pTable,key)) {
      Set result=1
      Quit
    }
  }
  Return result
}
} 
Alex Woodhead · May 12, 2023 go to post

I approached in similar way.

Difference was to use the built in "$Piece" function on original string, instead of using intermediary lists.

Also using the dynamic property names.

   ClassMethod AsJSON(value = "")
{
set dyn=[]
set len=$L(value,"|")
if len>1 {
for i=1:2:len {
set q=$P(value,"|",i)
set a=$P(value,"|",i+1)
set item={}
set item.number="number "_(i\2+1)
set item.squad=q
set item.answer=[]
set answer1={}
set answer1.initialLetter=a
do item.answer.%Push(answer1)
do dyn.%Push(item)
}
}
quit dyn.%ToJSON()
}
Alex Woodhead · May 12, 2023 go to post

Have used "%" variables to share context between a chain of sequentially executing classmethods on a CSP page.

This means enriching existing %request.Data variable, as it is already there and instrumented for Dump, or can use a different % variable name.

Alex Woodhead · Apr 25, 2023 go to post

If the original install media is not available, there are currently non-Docker software distributions for Cache 2018.1 available via wrc.intersystems.com. Your customer's business may have a license and support account with InterSystems to logon and download install the media.

Is there a "*.cpf" configuration file available in the backup or original server install folder? This will include configuration for namespaces and databases involved, and how they are mapped for data and "table" definitions. ie: A CBK is a single file with consistent backup of multiple databases each which would  be restored to a equivalent distinct database file location.

Anticipate the OS architecture may need to be the same. ie: Backed-up on windows, then would restore to different  install also on windows.

Alex Woodhead · Apr 19, 2023 go to post

Excuse the typo: Should be "$C(10)" not "%C(10)"

set:ln[$C(10) base64.LineTerminator=$C(10)
Alex Woodhead · Apr 19, 2023 go to post

Hi Scott,

Am wondering whether this is due to migrating from AIX to RedHat, and the stream is defaulting to expecting a different line terminator $C(13,10).

Could try add before the while loop:

set ln=base64.ReadLine() 
set:ln[$C(10) base64.LineTerminator=$C(10)
do base64.Rewind()

Kind regards,

Alex

Alex Woodhead · Apr 19, 2023 go to post

There is:

##class(%MessageDictionary).FormatText("Hello %1", "World")

Also "Include %occMessages" at top of your class will give access to macros:
* FormatText
* FormatTextHTML
* FormatTextJS

Both approaches work for the indeterminate number of args requirement.

Alex Woodhead · Apr 19, 2023 go to post

In the past have used Apache Batik https://xmlgraphics.apache.org/batik/

The scenario was that a message contained a diagnostic background image with layered SVG annotations but the receiving system needed to receive a flattened PNG image instead. Can also generate TIFF or JPEG instead.

Batik is a Java based integration, so depends if IRIS integrating with Java is an option for your deployment.

Interesting if someone suggests a reliable Python alternative.

Other directions might be phantom NodeJS / image magic.

Alex Woodhead · Apr 18, 2023 go to post

Hi Pravin,

The following pattern could work for you. Override and add a callback with a single line edit. Then maintaining the custom logic in a supporting callback. Will need to track behavior changes for %JSONImport when upgrading.

Method OnJSONImport(%JSONObject As %Library.DynamicAbstractObject)
{
  set %JSONObject.Version=""_%JSONObject.Version
}

/// %JSONImport imports JSON or dynamic object input into this object.<br>
/// The input argument is either JSON as a string or stream, or a subclass of %DynamicAbstractObject.<br>
/// mappingName is the name of the mapping to use for the import. The base mapping is represented by "" and is the default.
Method %JSONImport(input, %mappingName As %String = "") As %Status [ ServerOnly = 1 ]
{
Try {
  Set sc=$$$OK
  New %JSONObject
  If $isobject(input),input.%IsA("%Library.DynamicAbstractObject") {
    // Already a dynamic object
    Set %JSONObject=input
Else {
    // A JSON stream or string
    Set %JSONObject=##class(%Library.DynamicAbstractObject).%FromJSON(input)
  }
   // Add callback
  Do ..OnJSONImport(%JSONObject)
  // Do the import now.
  Set sc=..%JSONImportInternal() Catch ex {
  Set sc=ex.AsStatus()
  } Quit sc
}
Alex Woodhead · Apr 17, 2023 go to post

Hi Thembelani,

This site requires access by HTTPS.

I created a TLS configuration in Management Portal with defaults called "Open".

Then the following works for download.

Set httpRequest = ##class(%Net.HttpRequest).%New() Set httpRequest.Server = "msedgedriver.azureedge.net"
set httpRequest.Port=443
Set httpRequest.ContentType = "application/octet-stream"
Set httpRequest.SSLConfiguration="Open"
set httpRequest.Https=1
Do httpRequest.Get("/113.0.1774.0/edgedriver_win64.zip")
Set fileName = "tryedgedriver_win64.zip"
Set fileStream = ##class(%Stream.FileBinary).%New()
Set fileStream.Filename = "C:\tmp\"_fileName
Do fileStream.CopyFrom(httpRequest.HttpResponse.Data)
Do fileStream.%Save()
Do fileStream.%Close()

Kind regards,

Alex