Alex Woodhead · Jun 30, 2023 go to post

Thinking it is useful for an IPM repo to provide different "visibility" of "available" deployed modules, depending on the account used to access the IPM server.

This avoids having a separate repo per customer. So one common repo service, where account-permissions to access different modules (and versions) is configured.

Can this work for a subscription business model ie: Once installed the software runs forever. Maybe this is limited to some extent with byte-code being published per IRIS version. So a "subscription" is on the IPM repo by security configuration continuing to enable access to module updates.

In that case the listing / search of modules, might give hints on whether subscription is enabled.

Speculating then a "10 seat / process" license would translate to a specific module byte-code build? To upgrade the "license capacity" is installing a different module version? This has potential to cause service disruption, if only interested in a change in capacity behavior.

Maybe a product would be split into two parts, an IPM "license module" that changes, and an IPM "main software module" that remains unaffected.

Alex Woodhead · Jun 29, 2023 go to post
ClassMethod help()
{
    set src = "{""name"" : ""greg"", ""weight"" : 220.00 }"
    set obj = ##class(%Library.DynamicObject).%FromJSON(src)
    write obj.%Get("name",,"string"),!
    write obj.%Get("weight",,"string"),!
    return $$$OK
}

Output:

USER>Do ##class(Test.DynaNumString).help()
greg
220.00
Alex Woodhead · Jun 27, 2023 go to post

Hi Mark,

Some thoughts on this:

Anticipate the online backup (CBK) may tie-in with operating system and Cache version to run the restore

It may be better to have "at rest" CACHE.DAT files:

They can be:

  • Renamed to IRIS.DAT
  • Mounted and "upgraded"
  • Endian converted if needed

Having md5 checksum of CBK / DAT files can help identify transfer issues to / from offline media.

An integrity check can also be a useful confirmation tool to trace back if the original backup had issue.

Having undeployed SQL table definitions maybe useful future proofing.

For older versions of Cache install media contact the wrc.intersystems.com

Anticipate IRIS will be here after another 45 years, so will easily meet the 10 year requirement.

Alex Woodhead · Jun 27, 2023 go to post

Hi Robbie,

I had created a OpenExchange PythonHelper : https://openexchange.intersystems.com/package/PyHelper

// Create an IRIS List turn into a Python List
set pyList=##class(alwo.PyHelper).toPyListOrString($LB(1,2,3,4,5,6))
// Write to terminal to check content
zw pyList
pyList=3@%SYS.Python  ; [1, 2, 3, 4, 5, 6]  ; <OREF>
// Convert the Python List to a IRIS List
set myIRISList=##class(alwo.PyHelper).ListFrompyList(.pyList)
// Output to terminal to confirm
zw myIRISList
myIRISList=$lb(1,2,3,4,5,6)

Any feedback welcome.

Alex Woodhead · Jun 25, 2023 go to post

Hi Evgeny,

Not saying this is best way but this indirection can be achieved with python eval. For example:

$Classmethod equivalent

classname="%SYSTEM.SYS"
methodname="ProcessID"
eval(f"iris.cls(\"{classname}\").{methodname}()")

$Property equivalent

Instantiating a Python exception and then iterate over properties printing out:

myerror=iris.cls("%Exception.PythonException")._New("MyOops",123,"def+123^XYZ","SomeData")

for propertyname in ["Name","Code","Data","Location"]:
    propvalue=eval(f"myerror.{propertyname}")

    print(f"Property Name {propertyname} has value {propvalue}\n")

output was:

Property Name Name has value MyOops
 
Property Name Code has value 123
 
Property Name Data has value SomeData
 
Property Name Location has value def+123^XYZ
Alex Woodhead · Jun 25, 2023 go to post

Hi Evgeny,

Not saying this is best way but this indirection can be achieved with python eval. For example:

$Classmethod equivalent

classname="%SYSTEM.SYS"
methodname="ProcessID"
eval(f"iris.cls(\"{classname}\").{methodname}()")

$Property equivalent

Instantiating a Python exception and then iterate over properties printing out name and value:

myerror=iris.cls("%Exception.PythonException")._New("MyOops",123,"def+123^XYZ","SomeData")

for propertyname in ["Name","Code","Data","Location"]:
    propvalue=eval(f"myerror.{propertyname}")

    print(f"Property Name {propertyname} has value {propvalue}\n")

output was:

Property Name Name has value MyOops
 
Property Name Code has value 123
 
Property Name Data has value SomeData
 
Property Name Location has value def+123^XYZ
Alex Woodhead · Jun 23, 2023 go to post

$Extract ($E) and $Translate ($TR) can provide a string manipulation

> set inDate="1997-08-09 10:38:39.700000000"
> set outDate=$TR($E(inDate,1,19)," ","T")_"Z"
> write !,outDate
1997-08-09T10:38:39Z

So steps are:

  • Grab the first 19 characters
  • Convert space " " to "T"
  • Append a "Z" to the end

Ignore if already the case, but would also recommend familiarity with $Piece, $Select and "[" (Contains operator) . $Find can also be efficient in string searches.

Alex Woodhead · Jun 23, 2023 go to post

Hi Evgeny,

The function $Parameter in conjunction with $This, to access these.

For example:

value='$Parameter($This, "[Parameter Name]")'

For example adding to Stream example yesterday.

Vanilla Source class.

Class Test.Str2Stream.ExampleIn Extends (%Persistent, %XML.Adaptor)
{
Property Filename As %String;
Property Content As %String;
}

DTL class. Where the magic happens:

Class Test.Str2Stream.Trans Extends Ens.DataTransformDTL [ DependsOn = (Test.Str2Stream.ExampleIn, Ens.StreamContainer) ]
{
Parameter Salutation = "YeaBuddy";
Parameter Reaction = "LightWeight";
Parameter IGNOREMISSINGSOURCE = 1;
Parameter REPORTERRORS = 1;
Parameter TREATEMPTYREPEATINGFIELDASNULL = 0;
XData DTL [ XMLNamespace = "http://www.intersystems.com/dtl" ]
{
<transform sourceClass='Test.Str2Stream.ExampleIn' targetClass='Ens.StreamContainer' create='new' language='objectscript' >
<assign value='##class(Test.Str2Stream).StringToStream(source.Content,source.Filename)' property='target.Stream' action='set' />
<assign value='target.Stream.WriteLine($Parameter($This,"Salutation"))' property='x' action='set' />
<assign value='target.Stream.WriteLine("These plates are "_$Parameter($This,"Reaction"))' property='x' action='set' />
</transform>
}
}

Testing In:

<ExampleIn>
  <Filename>ABC</Filename>
  <Content>The session begins...
</Content>
</ExampleIn>

Testing output:

Alex Woodhead · Jun 22, 2023 go to post

After a bit of digging I came up with the following equivalents.

$Horolog

>>> iris.cls("%SYSTEM.SYS").Horolog()
'66647,85547'

Equivalent access:

>>> var=iris.cls("%Library.UTC").NowLocal()
>>> var
'2023-06-22 23:50:04.386'
>>> iris.cls("%Library.UTC").ConvertTimeStampToHorolog(var)
'66647,85804.386'

$NAMESPACE ($ZNSPACE)

>>> iris.cls("%SYSTEM.SYS").NameSpace()
'USER'

ZN [Namespace] - aka change namespace

Keep your object fingers in the car at all times!!

Any created object script references need to be cleared BEFORE changing back. Is it necessary.

>>> iris.cls("%SYSTEM.Process").SetNamespace("%SYS")
'%SYS'

$JOB

>>> iris.cls("%SYSTEM.SYS").ProcessID()
'1548'

$SYSTEM - Instance name

>>> iris.cls("%SYS.System").GetInstanceName()
'IRIS123'

But you might have same name on different operating system / container so:

>>> iris.cls("%SYS.System").GetUniqueInstanceName()
'THEMACHINE.YOURDOMAIN.COM:IRIS123'

$ZTIMESTAMP

>>> iris.cls("%SYSTEM.SYS").TimeStamp()
'66647,81615.3832864'

$ZTIMEZONE – Contains the time zone offset from the Greenwich meridian

>>> iris.cls("%SYSTEM.SYS").TimeZone()
0

$ZVERSION – Contains a string describing the current version of InterSystems IRIS

>>> iris.cls("%SYSTEM.Version").Format(0)
'IRIS for Windows (x86-64) 202x.x.0 (Build xxU) Thu xx 2023 06:22:16 EDT'
Alex Woodhead · Jun 22, 2023 go to post

Thanks @Evgeny Shvarov , this is the optimal approach. As on reading the documentation it suggests you can target different IRIS builds (with the help of docker code compiling hosts) and the IPM repository can host the same module version for different $ZVersion IRIS targets transparently.

Is there intention to support code signing, and with pre-install validation, to ensure code cannot be modified at rest, between being uploaded to a repo and then downloaded to a third-party instance?

Wondering practically about an IRIS upgrade process. Should the IPM client have ability to:

  • List installed modules that have a previous IPM bytecode install
  • List installed modules that would be affected by an IRIS upgrade. Is the newer source code even available in registered repos? (Postpone the IRIS upgrade)
  • Provide a batch upgrade option, to upgrade installed modules to the SAME module version but with the newer version of IRIS $ZVersion Byte code
Alex Woodhead · Jun 22, 2023 go to post

Restating advice from above.

A mapping rule is required per database with data.

For example.

------------ Mapping one -----------------

Global Database Location = 201606_HIPAA

Global Name: HISTORY

Global Subscripts to be Mapped: (201606)

------------ Mapping Two -----------------

Global Database Location = 201607_HIPAA

Global Name: HISTORY

Global Subscripts to be Mapped: (201607)

-------------------------------------

Can see Global "History" is stated in question.

Suggest review is needed to double-check where indexes and rowID counters are stored.

Alex Woodhead · Jun 21, 2023 go to post

Adding some code collateral to help explore challenge:

Class definition top

Include (%occInclude, %syConfig)

Some code to listing maps in a database

ZN "%SYS"
set pNamespace="HSCUSTOM"
set cns="Map."_pNamespace
set map=""
set found=0
for {
  set map=$O(^CONFIG(cns,map),+1,db)
  quit:map=""
  set len=$L($P(map,"("),"_")
  set globalMap=$P(map,"_",len,999)
  write !,"global match:""",globalMap,""" to Database:",db}
}

Example output:

...
global match:"IRIS.MsgNames("EnsSearchTable")" to Database:ENSLIB
global match:"IRIS.MsgNames("EnsWf")" to Database:ENSLIB
global match:"IRIS.MsgNames("EnsXPATH")" to Database:ENSLIB
global match:"IRIS.MsgNames("EnsebXML")" to Database:ENSLIB
global match:"IRIS.MsgNames("Ensemble")" to Database:ENSLIB
global match:"IRIS.MsgNames("ITK")" to Database:ENSLIB
global match:"IRIS.MsgNames("RuleEditor")" to Database:ENSLIB
....

Delete a map

set tSC=##Class(Config.MapGlobals).Delete(pNamespace,globalMatch,,$$$CPFSave)

Create a map

set global="IRIS.MsgNames("EnsSearchTable")"  // example like variable "gm" above.

kill params
sset params("Database")="CUSTOMLIB"  // The database you want to use
set params("Collation")=""
set tSC = ##Class(Config.MapGlobals).Create(pNamespace,global,.params,,$$$CPFSave)

// Always apply any pending changes
// Always confirm in testing that the configuration "sticks" after a system restart

do ##class(Config.CPF).Activate()

Alternatives to programmatic approach

Extended global syntax can be useful to copy start / end data between previously mapped and currently mapped database.

Alex Woodhead · Jun 21, 2023 go to post

Hi Evgeny,

The following is a tool System Management Specialists may use.

You can achieve Jobbing and also running arbitrary ObjectScript code by employing RunLegacyTask.

For example run arbitrary code:

USER>zw ^ABC

USER>D $SYSTEM.Python.Shell()
 
Python 3.9.5 (default, Mar 14 2023, 06:58:44) [MSC v.1927 64 bit (AMD64)] on win32
Type quit() or Ctrl-D to exit this shell.
>>> myjob=iris.cls("%SYS.Task.RunLegacyTask")._New()

>>> myjob.ExecuteCode="Set ^ABC=""123"""

>>> res=myjob.OnTask()

>>> res
1
>>> quit()

USER>zw ^ABC
^ABC=123

For example Jobbing a Routine.

Routine (Test.mac) source code:

 Test
  Set ^ABC=345
  Quit

Use legacy Task to Job it off:

USER>zw ^ABC
^ABC=123
 
USER>D $SYSTEM.Python.Shell()
 
Python 3.9.5 (default, Mar 14 2023, 06:58:44) [MSC v.1927 64 bit (AMD64)] on win32
Type quit() or Ctrl-D to exit this shell.

>>> myjob=iris.cls("%SYS.Task.RunLegacyTask")._New()
>>> myjob.ExecuteCode="Job Test^Test"
>>> res=myjob.OnTask()
>>> res
1
>>> quit()
 
USER>zw ^ABC
^ABC=345

The status result can be useful to understand problems.

For example the Routine line label "Test" was miss-typed as "TEST"

>>> myjob=iris.cls("%SYS.Task.RunLegacyTask")._New()
>>> myjob.ExecuteCode="Job TEST^Test"
>>> res=myjob.OnTask()
>>> iris.cls("%SYSTEM.Status").DisplayError(res)
 
ERROR #5001: <NOLINE>zexecuteCode+3^%SYS.Task.RunLegacyTask.11

Enjoy IRIS. No limits :)

Alex Woodhead · Jun 21, 2023 go to post

CSP pages are at the beginning of a long compilation pipeline

CSP + CSR -> Cls + inc -> Mac -> Int -> Obj

So anticipate CSP can be similar case to handling classes but with the attention on the target package name configured for class generation.

For a webserver extension handler (for csp, cls) it can be configured to skip check for the existence of physical CSP file. In CSP application configuration the "check for recompilation" option can be disabled.

Alternatively CSP pages can be implemented directly as classes that extend %CSP.Page with web requests going to " [classname] .cls " instead.

Check out utility method :

Do $SYSTEM.OBJ.MakeClassDeployed(.classes, qspec, fulldeploy)

Another avenue for consideration are legacy utilities: %RIMF and %ROMF can load and export respectively, compiled obj byte code (pcode)

Alex Woodhead · Jun 21, 2023 go to post

Hi Evgeny,

They should show up after being compiled.

May need to close the DTL completely and reopen to get the list to repopulate?

The benefits should be for easy reusability in Transforms and Business Rules

Alex Woodhead · Jun 21, 2023 go to post

Hi Evgeny,

An option is to use a FunctionSet, for example:

 Class Test.Str2Stream Extends Ens.Rule.FunctionSet
{ ClassMethod StringToStream(initValue As %String = "undefined", filename As %String = "") As %GlobalCharacterStream
{
set ret=##class(%GlobalCharacterStream).%New()
set ret.Attributes("Filename")=filename
do ret.Write(initValue)
quit ret
}
}

This will let you see the nice single line "From Source property" and "To Target Property"

Where assignment value was:

##class(Test.Str2Stream).StringToStream(source.Content,source.Filename)

The Gotcha

If you have a test form input message:

<ExampleIn>
  <Filename>ABC</Filename>
  <Content>ho ho ho</Content>
</ExampleIn>

And this is not populating output like:

<StreamContainer>
  <OriginalFilename>ABC</OriginalFilename>
  <Stream><![CDATA[ho ho ho]</Stream>
  <Type>GC</Type>
</StreamContainer>

Then go back and check the input message type extends %XML.Adaptor.

 Class Test.Str2Stream.ExampleIn Extends (%Persistent, %XML.Adaptor)

ie: It is a limitation of the Test form that it expects to be able to correlate the content from XML to Test Object.

Hope this helps.

Alex Woodhead · Jun 19, 2023 go to post

That does sound as if painted into a corner, especially if this is superuser account.

The preferences are cached in a Portal Settings global.

Can raise an idea on Ideas Portal to facilitate convenient clear / reset of user portal cached settings.

Alex Woodhead · Jun 19, 2023 go to post

One way to approach this is with WebServerURLPrefix.

The Web Gateway has special handling when it detects the first part of application URL matches the instance name of a connection.

Where:

https://srv2/[Instance name 1]/csp/sys/UtilHome.csp

https://srv2/[Instance name 2]/csp/sys/UtilHome.csp  

See: https://docs.intersystems.com/irisforhealth20231/csp/docbook/DocBook.UI…

To let Apache know to expect this traffic could use:

<Location /instance1>
   CSP On 
</Location>
<Location /instance2>
   CSP On
</Location>

See: https://docs.intersystems.com/irisforhealth20231/csp/docbook/DocBook.UI…

Alex Woodhead · Jun 19, 2023 go to post

Starting the production, allows the "Test" action to be available.

Testing is enabled on production settings

Alex Woodhead · Jun 19, 2023 go to post

Hi Evgeny,

Historic message type flows can be seen with:

SELECT %NOLOCK DISTINCT SourceConfigName,TargetConfigName,MessageBodyClassName
FROM Ens.MessageHeader
ORDER BY SourceConfigName,TargetConfigName,MessageBodyClassName

For a running production with Testing enabled, the Test Action launches a dialog with drop-down list of test message types handled by an operation.

See: Class EnsPortal.Dialog.TestingService with classmethod GetValidRequestTypeList

A pattern I have noticed implemented for processing generic XML Streams is for a property to be exposed in the business operation that configures a list of expected ClassNames ( supporting %XML.Adapter ) to correlate.

An interesting question for 3rd-party providers of interoperability components.

Maybe this is suggesting a "New Transform" Wizard could be aware of the production in focus.

In some implementations due to the visibility of notes in the management portal, these may be directed more at a "support role" than a "developer role" for which the organizations may be different. So some empathy with removing the dev teeth from descriptions where they might be confusing, outside an operational support context. Possibly would be beneficial to have different types of production visible notes.

Alex Woodhead · Jun 15, 2023 go to post

Hi Brian,

Possibly the new size is not calculated if message is not already saved.

However, Custom RuleSet functions can be implemented in classes for example:

Then the Rule Condition could be:

(DocSizeX12(Document))>500000

Alex Woodhead · Jun 14, 2023 go to post

Correct. There is no use of IRIS though the example could be extended to profile metadata from classes. With generative AI the line between Class Dictionary and Documentation could be less distinct. The example doesn't make use of vector metadata yet. The documentation has description of settings, and a deployment has a cpf / api to allude setting values, so maybe it is possible for AI to describe the nature of a deployment or support further information regarding messages / application / event log.

( Deliberately suggesting too many options for a single competition candidate to attempt to take on )

Alex Woodhead · Jun 12, 2023 go to post

If the question is regarding automated setup configuration of services:

A recent advancement if you are using Linux / Unix platform is to use cpf merge.
https://docs.intersystems.com/iris20231/csp/docbook/DocBook.UI.Page.cls…
This can manage instance configuration file and CSP applications.

CSP Merge can be applied on the first startup of service in Docker provisioning.

Before that, and for Windows currently, have successfully implemented %INSTALLER classes to reconfigure vanilla servers for application migration and usage.
https://docs.intersystems.com/iris20231/csp/docbook/Doc.View.cls?KEY=GC…

Alex Woodhead · Jun 12, 2023 go to post

For info to also share about Class: Ens.Production has ClassMethod ApplySettings.

set pSettings(<itemName>,<target>,<settingName>)=<settingValue>
do ##class(Ens.Production).ApplySettings(<productionName>, .pSettings)

Alex Woodhead · Jun 12, 2023 go to post

From terminal / csession do you get "1" returned when running:

write ##class(%SYS.TaskSuper).SuspendSet(1)

Failing that, if the server is not being used currently would stop and then start Ensemble Service in Emergency Mode and disable TaskScheduler / remove problem schedule. Then shut down Ensemble service from Emergency mode and start up Ensemble as normal.

Alex Woodhead · Jun 9, 2023 go to post

Thanks @Evgeny Shvarov for the challenge.

I was digging through the ZPM code this afternoon. What I discovered is the need to implement AbstractInstaller and include in your module resources.

Here is a class that implements loading System Default Settings via ZPM command (when configuration phase is included by a ZPM activity)

 Class alwo.ChangeProductionSetting Extends %ZPM.AbstractInstaller
{
 Parameter Version = 1.0;

Parameter MaintenanceVersion = 5;

 ClassMethod OnConfigureComponent(pNamespace As %String, pVerbose As %Boolean = 0, ByRef pVars) As %Status
{
set tSC=$$$OK
set tSettingName=""
set (tProductionName,tItemName,tHostClassName,tTarget)="*"
set tValue=""
set varName=""
for {
  set varName=$O(pVars("zpm",varName),1,varValue)
  quit:varName=""
  if $ZCVT(varName,"U")="PRODUCTIONNAME" {
    set tProductionName=varValue
  } elseif $ZCVT(varName,"U")="TARGET" {
    set tTarget=varValue
  } elseif $ZCVT(varName,"U")="SETTINGNAME" {
    set tSettingName=varValue
  } elseif $ZCVT(varName,"U")="ITEMNAME" {
    set tItemName=varValue
  } elseif $ZCVT(varName,"U")="VALUE" {
    set tValue=varValue
  } elseif $ZCVT(varName,"U")="HOSTCLASSNAME" {
    set tHostClassName=varValue
  }
}

set existingId=tProductionName_"||"_tItemName_"||"_tHostClassName_"||"_tSettingName 

if tSettingName'="" {
  if ##class(Ens.Config.DefaultSettings).%ExistsId(existingId) {
     set oDefaultSetting=##class(Ens.Config.DefaultSettings).%OpenId(existingId,1)
  } else {
    set oDefaultSetting=##class(Ens.Config.DefaultSettings).%New()
    set oDefaultSetting.ProductionName=tProductionName
    set oDefaultSetting.ItemName=tItemName
    set oDefaultSetting.HostClassName=tHostClassName
    set oDefaultSetting.SettingName=tSettingName
  }
  // Assumes use of whitespace in settingValue is as intended
  set oDefaultSetting.SettingValue=tValue
  set tSC=oDefaultSetting.%Save()
}
Quit tSC
}    
}

Next in my ZPM module definition I added the tag at same level as Resource tags:

<Resource Name="alwo.TestProduction.CLS"/>
<Resource .... />
<InstallerClass>alwo.ChangeProductionSetting</InstallerClass>

In the zpm install command I could use:

load -v \some\localfolder\AddSetting -DProductionName=alwo.TestProduction -DHostClassName="EnsLib.MsgRouter.RoutingEngineST" -DItemName=TestProcess -DTarget=Item -DSettingName=RetryInterval -DValue=25

If you change your mind about the setting or want to add a second setting you can use for example:

module-action alwo-changeproductionsetting configure -only -DProductionName=alwo.TestProduction -DHostClassName="EnsLib.MsgRouter.RoutingEngineST" -DItemName=TestProcess -DTarget=Item -DSettingName=RetryInterval -DValue=25

For the arguments the only mandatory ones are:

  • -DSettingName
  • -DValue

When the others are not specified they simply go in as wildcard "*" against the System Default Settings.

BTW: Need to delete the "settable" values from the Production XData configuration in distribution, so that the default is applied.

ie: If looking at Production Configuration in Management Portal and a production Item setting is displaying with the Black font label, it means this value is masking the System Default Setting.

If you don't want to pass so many D parameters then some of the values could be hardcoded for project as Parameters in the InstallerClass.

 Parameter DProductionName="A Production Name";

Parameter DTarget="Item";

Parameter DItemName="Telegram.BusinessOperation";

Parameter DSettingName="Token";

Then would would be closer to original requirement:

zpm "install telegram-gpt -D Token=sometoken"

Hope this helps.

Kind regards,

Alex