Gertjan Klein · Dec 14, 2025 go to post

I'd just use def1arg:

#def1arg FMT(%parms) ##class(Very.Very.Long.Class.Name).Format(%parms)

(From memory, I don't have an instance to test available at the moment.) The macro 'intellisense' doesn't help with the parameters anymore, but in your case they are very easy to remember: format, args. I use something like this myself.

Gertjan Klein · Apr 17, 2025 go to post

As far as I know, %File does not allow setting the file encoding. The %Stream.FileCharacter class Dmitry mentions does, but it's not called encoding. To use UTF-8, you need to set property TranslateTable to "UTF8" (no dash).

Gertjan Klein · Sep 21, 2023 go to post

When an error has occurred, the return value is not 0. Instead, it is a %Status, and likely already contains the error information you need.

As to your actual question, finding (the id of) the response message header is not trivial. Ensemble Business Services have a %RequestHeader property from which you can take the CorrespondingMessageId, but unfortunately it is only populated if the call did not return an error status. It is still possible, though. You can use the id of the request body to find out what you need. It does need some SQL, though:

ClassMethod GetResponseHeaderId(RequestId As%String, Output sc As%Status) As%String
{
  &sql(SELECT CorrespondingMessageId INTO :ResponseHeaderId
         FROM Ens.MessageHeader
        WHERE MessageBodyId = :RequestId)
  If SQLCODE Set sc = $$$ERROR($$$SQLError, SQLCODE, $Get(%msg))
  
  Set sc = $$$OKReturn ResponseHeaderId
}

With this helper method, you can open the response message header object. Assuming the request body is called CallReq:

Set Id = ..GetResponseHeaderId(CallReq.%Id(), .sc)
  If 'sc Quit sc
  
  Set RspHdr = ##class(Ens.MessageHeader).%OpenId(Id, , .sc)
  If 'sc Quit sc
  
  $$$LOGINFO("Response is an error: "_RspHdr.IsError)

HTH,
Gertjan.

Gertjan Klein · Sep 12, 2023 go to post

Yes, something like that could work. I personally won't spend time on this, as I refuse to use the browser editors for this reason. I still use Studio for everything. It has it's annoyances, but at least it doesn't destroy my work for no good reason.

(This whole conversation astonishes me. We are talking about ideas and votes, like this is some minor inconvenience to some, instead of a prio 1 bug that InterSystems should fix yesterday.)

Gertjan Klein · Sep 11, 2023 go to post

I agree with the problem (and the emotional effect it has!), less with the solution. The moment I e.g. take a call, the BPL/DTL may not be in a state fit to save it. Making the server handle this seems overly complicated. My personal preference would be to just have a simple JavaScript method keep the web session alive as long as the browser is open. That would also alleviate the need for these constant "Your session is about to expire" popups in the Ensemble production portal.

Gertjan Klein · Jun 26, 2023 go to post

And for completeness: setting a property with a name not known until runtime can be done with the counterpart of getattr, setattr:

USER>do ##class(%SYS.Python).Shell()

Python 3.10.6 (main, Mar 10 2023, 10:55:28) [GCC 11.3.0] on linux
Type quit() or Ctrl-D to exit this shell.
>>> e=iris.cls("%Exception.PythonException")._New("MyOops",123,"def+123^XYZ","SomeData")
>>> e.Name
'MyOops'
>>> setattr(e,'Name','TheirOops')
>>> e.Name
'TheirOops'

Gertjan Klein · Apr 25, 2023 go to post

A simple space before the modulus operator will fix this:

#($select($data(^ImportantFlag) #2:"Important!",1:"Normal"))#

(Note that I needed to add a closing brace for the $Select as well.) If you wish, you can add a few more spaces for readability. 😉

Gertjan Klein · Mar 11, 2023 go to post

In our case, it adds 2.3s to a build that takes way longer than that. (Our build creates Foundation namespaces, and loading the FHIR resources takes insanely long.) I expect this to be Python startup time, plus some time proportional to the amount of data to copy (roughly 300MB in my test).

Gertjan Klein · Mar 9, 2023 go to post

Thanks, this is very useful. I've just tested this on an image with various build steps, and this saves us quite a bit of image space. The copy step now adds a little under 300MB to the base image, instead of almost 5GB (!). Squashing an image has the same effect, but prevents layer caching, so each push to a docker repository would upload the entire image. Your way, after the first time, presumably just the 300MB. Nice!

Interestingly, we've had already contacted our sales engineers about the massive amount of image disk space used after our build steps. I couldn't find what it's used for; the actual Linux filesystem is way smaller. The build steps also don't visibly use significant disk space, that I could find. I'm hoping InterSystems manages to do something about this in the future. In the meantime, we've got a nice workaround. Thanks again!

Gertjan Klein · Feb 2, 2023 go to post

I would file this with WRC. The settings documentation suggest that you can specify the in- and output encoding with the Charset (Tekenset) setting. That implies that you should set that to utf-8, but that doesn't actually work. From looking at the source code, it appears that the business service (EnsLib.REST.Service) hardcodes a %GlobalBinaryStream response stream, which will output the bytes as they are.

As a workaround you could convert (encode) the stream to UTF-8 yourself before sending it.

Gertjan Klein · Dec 22, 2022 go to post

I like to second Dmitry's concerns. I can somewhat see the reasoning behind removing as much as possible from the docker images. Especially if convenient docker or docker-compose recipes are made available, this could perhaps be of limited inconvenience. But for the Windows installers, I really don't see the point. Especially on a developer system, if/when the internal server is not accessible from the outside world, security is not an issue at all. If it is, an option not to install the built-in apache could be added. Giving us choices, instead of taking them away. I consider removing the built-in SMP server a major inconvenience.

Gertjan Klein · Dec 16, 2022 go to post

Just to be clear: in future IRIS installations, the System Management Portal will be unavailable, unless you have a web server installed and configured?

Separate question: the EAP program page mentioned above requires a login, and then offers to download an evaluation version of IRIS. I just want to look at that FAQ, which I couldn't find. Could that be made available more easily?

Gertjan Klein · Jul 28, 2022 go to post

Wow, thanks very much. I did try, and it seems to work flawlessly. It makes editing the markdown already more pleasant, even without the preview being colored. Thanks again!

Gertjan Klein · Jul 5, 2022 go to post

I'm not sure about the meaning of the Distributed column, but this does indeed look suspicious. If you have that option, I'd stop and start IRIS (which should clear existing licenses), and try the web terminal again. If it now works, you have at least found the cause.

As to a more permanent solution, I don't know. I don't use web terminal all that often, but I would hope that, when you log in, it adds a connection to a license that login may already have. Perhaps the web socket connection plays a part in this. Like I said, I haven't really investigated this; I just mentioned it so you'd have something to check.

Gertjan Klein · Jul 5, 2022 go to post

You can check whether you still have licenses available in the management portal: System Operation -> License Usage. If Current License Units Used equals License Units Authorized, web terminal can't allocate a new license, which it does seem to need. If not, yours is a different problem.

Gertjan Klein · Jul 4, 2022 go to post

I've seen this happen on a CE docker instance; in this case the cause was that licenses ran out. I did not investigate further, but it appeared each start of a web terminal caused a new license to be consumed. Perhaps this could be your problem?

Gertjan Klein · Jun 30, 2022 go to post

Method %JSONImport returns a %Status, which tells you what the problem is (using $System.Status.DisplayError()):
ERROR #9406: Unexpected format for value of field, appointmentid, using AthenaAppointment mapping
That value is a number in JSON, but you defined it as a %String in the class. The JSON import code disapproves. There are more fields like that, and additionally field reasonid is not defined in the mapping. If you fix these problems, the data will import.

(I'd also remove the %DynamicAbstractObject superclass, it is unneeded and gives errors on object destruction.)

Gertjan Klein · Feb 16, 2022 go to post

The ToXML function is broken. This is a known bug (I've reported it mid-Januari).

It is possible to fix it, but it requires patching system code, class HS.FHIR.DTL.Util.XML.Adapter to be precise.

In that class, at (on my system, 2021.2) line 359 (in method ToXMLHelper), there is this statement:

set isprimitive = ($extract(propType)="%")

This basically assumes all properties are objects, except those that start with a %-sign. This is obviously wrong. The code will work if you replace that statement with this:

set isprimitive = ($extract(propType)="%") || (propType="") || ($$$classIsDataType(propType))

Here we additionally check for properties without a type (DomainResource has one: property id), and typed properties that are datatypes. With this change, the ToXML() method works for me.

HTH,
Gertjan.

Gertjan Klein · May 24, 2021 go to post

Just a quick response to your "topic for a different discussion", as it is relevant in this one. A Scope inside the loop allows one to examine the exact error, and if it is not fatal, decide to handle/ignore it and continue with the next iteration. A Scope outside the loop makes this a lot harder.

What BPL structure shows the problem solution most clearly depends on the problem and sometimes, as you note, on personal preference. It is therefore important to specify exactly which conditions are not supported, rather than "certain conditions". This allows the programmer to choose the easiest and/or clearest solution path, and not have to worry about things breaking if iterations exceed a certain (unspecified) number.

To purposefully withhold that information, as you suggest the documentation does, is saddening.

Gertjan Klein · May 24, 2021 go to post

Actually, on closer examination, it appears that in your example the problem is that the Continue is inside the Scope. If you move it outside the Scope, the problem doesn't occur (as you noted). The cause is that the fault handler that was pushed on the stack for the Scope is not removed, because the Scope is not exited "normally". Therefore, each iteration adds a fault handler to the stack that is never removed.

Having one or more scopes in a loop is a perfectly normal thing to do, if you want fine-grained error handling and/or recovery. I have just done some tests, and they work perfectly. If "best practice" dictates that this should not be done, "best practice" is wrong. A Continue inside a Scope, however, is never needed (even if perhaps convenient sometimes).

Unfortunately, I don't remember exactly what I did when I ran into my handler stack issue (it's been a while). Perhaps I also did a Continue inside a Scope. In that case, it was a bug on my part.

Perhaps this can be better documented? The description of the limitation you quoted above is rather vague. Even better would be if the compiler recognized this construct, and removed as many fault handlers from the stack as were pushed on it, when it encounters a Continue inside a Scope in a loop.

Gertjan Klein · May 23, 2021 go to post

Thanks, this is useful information. I have encountered this bug in the past, but did not have the time to figure out exactly what triggered it, and restructured my code.

It would be nice if this bug was fixed, though.

Gertjan Klein · Apr 24, 2021 go to post

Thanks, but a terminal command (requiring you to install the preview release) is not what I call documentation. But I did install it. To give an example of what the "Help()" gives me:

Help(method)
     Write out a list of the methods of this object to the console.
addToPath(path)
     <p>
createServer(serverDef)
     Create a new server.  This function requires the "%Admin_Manage" resource.

Interestingly, on the local installed preview, the class documentation does show the %SYSTEM.external class (with ever so slightly more information, btw.). I really hope that this lack of documentation is fixed before release, because as it is now, it is unusable.

Regard,
Gertjan.

Gertjan Klein · Nov 27, 2020 go to post

As you're using Ensemble, you can use class EnsLib.EDI.XML.Document. I don't have version 2015, but the following works on IRIS. I hope this is present in Ensemble 2015 as well. (Note that, annoyingly, method domGetValueAt is marked internal, and therefore doesn't show up in the documentation. I don't know how to achieve the same result without using this method.)

Set XML = "<a><b><c>some content</c></b></a>"Set Path = "/a/b"Set Doc = ##class(EnsLib.EDI.XML.Document).ImportFromString(XML, .sc)If 'sc Quit $System.Status.DisplayError(sc)Set sc = Doc.domGetValueAt(.Value, Path, "fo", 1)If 'sc Quit $System.Status.DisplayError(sc)
Write Value,!

This outputs "<b><c>some content</c></b>" as you want.

Hope this helps,
Gertjan.

Gertjan Klein · Sep 29, 2020 go to post

I don't know if try/catch is slow, and I don't care. I don't use it because it is too wordy to handle errors exactly where they occur. It encourages code like in your example, where the code in methods is wrapped entirely in a try/catch block. You say the error object has all the information, but I disagree. It has some low-level information, but often lacks the context I need to determine what the problem is.

My preferred way of handling %Status errors is to add a %Status in front of it with more details of what happened when the error occurred, and return this to the caller. Somewhere up the call chain something will then handle the problem, e.g. add something to the Ensemble event log. This is such a standard way of working for me that I created a macro specifically for prefixing the new status information.

For errors that "raise" I also prefer $ZTrap+$ZError; I don't see the added value of try/catch here either.

Gertjan Klein · Sep 28, 2020 go to post

I use %Status exclusively; I really, really don't like try/catch. The most important reason for me is that I want to add information about what went wrong to the status. In e.g. obj.%Save(), the returned status tells me that saving an object went wrong, and hopefully why. I want to add to this which object could not be saved, and possibly some other state that may be relevant for debugging the problem. I find this creates code that is easy to read and debug.

By the way, "If 'sc" is, to me, a lot easier on the eyes than "If $$$ISERR(sc)"...

Gertjan Klein · Jun 25, 2020 go to post

$System.OBJ doesn't handle CSP pages, but $System.CSP does. You can use LoadPage:

Set sc = $System.CSP.LoadPage("/dev/test.csp", "duck")

(You need to use the URL here, not the class name.) This compiles the CSP page into a class, and then compiles that class.

Gertjan Klein · Jun 18, 2020 go to post

Ah, I see -- yes, I can reproduce this, and I would think this is a bug. (It does work as expected in an "if", but not in a "case".) You may want to take this up with WRC, to see what they think.

As a workaround, are you aware that you can use local variables? You could assign the value of source.Items.(i) to a local variable, and use that in the case statement. Something like this:

In a switch statement like yours, this would probably save some typing as well. It does mean the lines in the DTL no longer display properly, though. (Although they might not anyway here.)

Regards,
Gertjan.

Gertjan Klein · Jun 17, 2020 go to post

Can you cut this down to a minimal example (actual code)? What you describe seems to work here. What "doesn't work"?

If I iterate over a (list of) property:

this ultimately compiles to:

As you can see, the .(i) is replaced with .GetAt(i). (Note that I replace the default key "k1" with "i" here to match your example.)

Regards,
Gertjan.

Gertjan Klein · Nov 15, 2019 go to post

Studio behaves really annoyingly if it loses its TCP/IP connection, as you described. I don't know how to prevent that. You probably don't have to restart HealthShare, though. When the crash happens, don't close the dialog immediately. Instead, first remove all locks for the process Studio was connected to; it shouldn't be too hard to find. (Something like System Operation -> Locks -> Manage Locks, I don't have access to a HealthShare instance right now.) Then allow Studio to reconnect, which should now work without issues.