Jeffrey Drumm · Aug 12, 2019 go to post

Hi Robert,

You can actually export the entire production in a single go through the Export button found in the Actions tab of the Production Settings. You would then use the "Manage / Deployment Changes" page on the target system to deploy the entire production to the destination namespace.

Other options include: Select "Files of type: DTL Document (*.dtl)" in Studio's Export option under the Tools menu (click "Add"), and select the entire list with shift-click (and similarly import through Studio), or use the ObjectScript class method  $System.OBJ.Export("*.dtl","/path/to/exportfile.xml") and corresponding import method $system.OBJ.Load("/path/to/importfile.xml") in the target environment.

The class methods work locally on your HealthConnect servers; if they're physically distinct hosts you'll need to copy the export file to the target server. Also you would run the export/load utilities in the namespace in which your production is located, of course ...

Jeffrey Drumm · Aug 9, 2019 go to post

Ah, now it makes sense. The Document type you'd specified for the inbound message did not match the structure of the message itself, so the RemoveSegmentAt() method didn't know how to "find" the EVN segment ... it didn't exist in that schema.

The recommended approach would be to use a schema/document type (custom if necessary) that matches the structure of your inbound message, making the segment to be removed optional in the definition. With that in place, the RemoveSegmentAt() method would have worked as expected.

Jeffrey Drumm · Aug 8, 2019 go to post

I would expect to see that error on the original message (before the EVN segment was deleted) but not on the cloned/modified version. Are you sure you're looking at the right message?

Jeffrey Drumm · Aug 2, 2019 go to post

You were close with you original code ...

Set newreq = request.%ConstructClone()

Set sc = newreq.RemoveSegmentAt("EVN")

Set request = newreq

the variable sc is the status of the remove segment operation; the EVN segment is actually removed from newreq.

Jeffrey Drumm · Aug 1, 2019 go to post

You can use the Source property to identify the component that sent the message into the router:

Jeffrey Drumm · Aug 1, 2019 go to post

I've cobbled together a little TamperMonkey/GreaseMonkey script that diddles with the style sheet without having to make unsupported changes to your installation's configuration. Adust the devattr, qaattr and prdattr variables to suit your color tastes, and the match arguments for the hostnames to identify your servers.

// ==UserScript==

// @name         Management Console Banner

// @namespace    http://tampermonkey.net/

// @version      0.2

// @description  Seems to work for both Chrome and Firefox

// @author       Jeff Drumm, HICG LLC

// @include      *://*/csp/sys*

// @include      *://*/csp/healthshare*

// @grant        none

// ==/UserScript==

function addGlobalStyle(css) {

    var head, style;

  head = document.getElementsByTagName('head')[0];

    if (!head) { return; }

  style = document.createElement('style');

  style.type = 'text/css';

  style.innerHTML = css;

  head.appendChild(style);

}

var devattr = 'color-stop(0.0,rgb(95, 246, 18)), color-stop(0.5,rgb(20, 204, 51)), color-stop(1.0,rgb(232, 227, 226))'

var qaattr = 'color-stop(0.0,rgb(248, 252, 10)), color-stop(0.5,rgb(204, 199, 20)), color-stop(1.0,rgb(232, 227, 226))'

var prdattr = 'color-stop(0.0,rgb(255, 2, 49)), color-stop(0.5,rgb(204, 18, 18)), color-stop(1.0,rgb(232, 227, 226))'

var curattr = ''

if (window.location.hostname.match('^ensprod.*')) {

    curattr = prdattr;

} else if (window.location.hostname.match('^ensqa.*')) {

    curattr = qaattr;

} else {

    curattr = devattr;

}

addGlobalStyle('.portalTitle { background: -webkit-gradient(linear, left top, left bottom, ' + curattr + ') !important; } ');

Jeffrey Drumm · Jul 29, 2019 go to post

Mmmyyyeaaaaaah, You're going to want to do that in a DTL:

Create a rule that appends the text to the target.AlertText property:

You can add conditionals to provide different alert explanation values based on the SourceConfigName or by parsing the source.AlertText text to find something interesting.

Finally, stick the DTL in the Send rule for whatever operation you're sending alerts to.

Jeffrey Drumm · Jul 29, 2019 go to post

Apparently there's a patch for the task so that it doesn't pollute the audit database when the <PROTECT> error occurs on a R/O database, but I don't think it's available for 2017.2.1.

I've been told dev key MAK5003 in Caché/Ensemble 2018.1.2+ is the solution, but the next upgrade will be to IRIS, which should also have that fix.

Jeffrey Drumm · Jul 28, 2019 go to post

 Second followup: R/O mode did something bad to the "Update SQL query statistics" task that comes standard with Caché. Caused it to dump millions of <PROTECT> records into the Audit database, which ballooned in size and ran the application directory out of disk space. Terminating  and restarting the job had the same effect. Not sure what's going on, have a ticket open with the WRC now.

Jeffrey Drumm · Jul 26, 2019 go to post

Just a quick follow-up ... no issues at all with making the database R/O. Works just as well as I'd hoped!

Jeffrey Drumm · Jul 26, 2019 go to post

That's a great suggestion, Eduard, and far simpler than my code-based solution. My only concern is that I will be extracting the messages from the retired Ensemble message database so that it can be deleted, and I'm not sure whether the task I've written to do this will require any temporary, behind-the-scenes global storage on the database itself. Easy enough to test, though, as I have two other environments with the same configuration (Dev and QA).

Jeffrey Drumm · Jul 25, 2019 go to post

You could do it with an SQL query:

DELETE FROM Ens_Util.LookupTable WHERE TableName = '<name of table>'

You can create the query either via the Management Console (System Explorer | SQL | Execute Query tab) or from the SQL Shell

JEFF>d $system.SQL.Shell()
SQL Command Line Shell
----------------------------------------------------
 
The command prefix is currently set to: <<nothing>>.
Enter q to quit, ? for help.
JEFF>>delete from Ens_Util.LookupTable where TableName = 'facLookup'
1.      delete from Ens_Util.LookupTable where TableName = 'facLookup'
 
3 Rows Affected
statement prepare time(s)/globals/lines/disk: 0.1893s/11322/125529/16ms
          execute time(s)/globals/lines/disk: 0.0021s/29/408/0ms
                          cached query class: %sqlcq.JEFF.cls27
---------------------------------------------------------------------------

Jeffrey Drumm · Jul 24, 2019 go to post

You can check to see if the internal web server is enabled with:

%SYS>d ##class(Config.Startup).Get(.Prop)
 
%SYS>w Prop("WebServer"),!

1
Jeffrey Drumm · Jul 23, 2019 go to post

You could write a simple classmethod that starts and stops the offending component when an inactivity alert is received. It would do little more than executing:

Do  ##class(Ens.Director).EnableConfigItem("service name",0,1)

Do  ##class(Ens.Director).EnableConfigItem("service name",1,1)

That would almost certainly reset the inactivity timer. As long as the class it's in extends Ens.Rule.FunctionSet, you'll have it available in the function selector in the Rule Editor drop-down list.

Jeffrey Drumm · Jul 23, 2019 go to post

As @Brian Schoen alluded to, $CHAR -- abbreviated to $C if you want to be one of the cool kids -- is what you need. $C(13,10) is the line-end sequence in Windows. $C(10) is Unix. $C(13), though, is old-school Macintosh* laugh

* Also works for HL7 Segment delimiters wink

Jeffrey Drumm · Jul 20, 2019 go to post

Just add this to %ZLANGC00.mac:

/// Display Management Portal Port
ZMPORT
ZMP
  W ^%SYS("WebServer","Port")
  QUIT
JEFF>zmp
57772
JEFF>

Or This:

/// Display Management Portal URL
ZMURL
ZMU
  Set sc=##class(%RoutineMgr).GetWebServerPort(.Port,.Server,.URLPrefix,.URL)
  W URL_"csp/sys/UtilHome.csp"
  QUIT
JEFF>zmu
http://WIN7X64-VM02:57772/csp/sys/UtilHome.csp
JEFF>

Why waste valuable Prompt characters? laugh

Thanks to @Herman Slagman and @Robert Cemper for letting me blatantly steal their ideas wink

Jeffrey Drumm · Jul 19, 2019 go to post

Jimmy,

If you're interested in just examining the headers and body generated by your request, you can install Postman and use it as a proxy. It will capture everything for you and present it in a very readable format.

Jeffrey Drumm · Jul 16, 2019 go to post

The Inactivity Timeout is a fixed value, and you can't easily reset it for different times of the day. You can, however, fairly easily control which times of the day alerts are actually sent, based on a variety of criteria:

The TimeIsBetween() and DayIsAWeekDay() functions in the screenshot above are relatively simple custom methods in a class that extends Ens.Rule.FunctionSet, which makes them selectable in the rule editor's function editor dropdown. I wrote them simply for the improved readability they provide for the routing rule.

In the rule above, alerts for the HIE_ADT_out interface are sent only between 7am and 7pm to the Integration team; on weekdays only the help desk is included. Any that fall outside of that timeframe are discarded.

Jeffrey Drumm · Jul 16, 2019 go to post

Well, I've verified that the DTL I provided works against the  HL7 2.4:ORU_R01 document type, so my assumption is that your messages don't conform to that specification.  If they don't have the structure below, they won't be parseable:

And if that's the case, you would need to create a custom message schema matching their layout to work with them in the DTL editor.

Jeffrey Drumm · Jul 16, 2019 go to post

Just an observation ... generally speaking, the reasoning behind the repetition delimiters in OBX:5 is to provide a form of line-orientation to the resulted report; sort of like <br/> tags in HTML.  The receiving system can use these as guidelines for displaying text that should display as separate lines. The repetitions are rarely more than 80 characters long, the "standard" reading width for a fixed-pitch terminal. "Empty" delimiters ("~~") often serve to indicate paragraph endings, aka "blank lines."

Separating the repetitions into individual segments is also a common method of keeping report formatting intact. However, I've yet to see a vendor expecting each segment to represent a "paragraph."

I'm concerned that the vendor you're working with will be presenting the result report in a format other than as intended by the source.

Jeffrey Drumm · Jul 16, 2019 go to post

I updated my comment and turned it into an answer ... with DTL rather than ObjectScript since I didn't realize that was the specific type of solution you were looking for. Let me know if that doesn't get you where you need to be ...

Jeffrey Drumm · Jul 16, 2019 go to post

Is that screen shot from the Ensemble Message Viewer? If yes, than what you're seeing is Ensemble's visualization of an empty field/component/subcomponent/repetition. The tilde (~) character is the repetition delimiter at the field level; InterSystems uses the "·" character as an indicator that the repetition was left empty. There's no actual character there to split on, but you can certainly iterate through repetitions within OBX:5 and build a new message segment for each.

The DTL would be something like this:

Jeffrey Drumm · Jul 9, 2019 go to post

OK, it's quick and dirty and I'm probably doing something that will make the old-timers here laugh hysterically, but it works. The caveats are:

  • It only cares about word wrapping on the space character. Punctuation adjacent to non-space characters will stay with the adjacent characters.
  • It totally ignores things like \.br\ tags. they're just text as far as it's concerned.
  • It returns a $LIST, where each list element is a line of text from the source string, no longer than the width specified. You can iterate through it with $LISTNEXT or $LISTGET and populate your OBX segments, but you'll probably have to do that in a CODE rule.

So anyway ...

ClassMethod WordWrap(pTxt As %String, pWidth As %Integer) As %List
{
    If $LENGTH(pTxt) > pWidth
    {
        set tLine = ""
        Set tCnt = 0
        Set tWordList = $LFS(pTxt," ")
        Set tListLen  = $LISTLENGTH(tWordList)
        Set tWordPtr = 0
        Set tWordCur = ""
        While $LISTNEXT(tWordList,tWordPtr,tWordCur)
        {
            If $LENGTH(tLine_tWordCur) > pWidth
            {
                Set $LIST(tList,*+1) = $ZSTRIP(tLine,">W")
                Set tLine = tWordCur_" "
                Set tLastCnt = tCnt
            }
            Else
            {
                Set tLine = tLine_tWordCur_" "
            }
            Set tCnt = tCnt + 1
        }
    }
    Else
    {
 	Set tList = $LB(pTxt)
	Set (tLastCnt,tListLen) = 0
    }
    If tLastCnt < tListLen
    {
         Set $LIST(tList,*+1) = $LTS($LIST(tWordList,tLastCnt + 1,tListLen)," ")
    }
    Return tList
}

Have fun!

Jeffrey Drumm · Jul 8, 2019 go to post

Scott, is there any requirement that this field be split along word boundaries such as whitespace and punctuation characters? That makes it a bit more challenging :)

Jeffrey Drumm · Jul 3, 2019 go to post

No impact that I've seen. It simply adds a button to the Message Viewer that displays the SQL used to select the message list.

Jeffrey Drumm · Jul 3, 2019 go to post

So ... before you go changing those globals (if you haven't done so already), try

TIE> do ##class(Ens.Queue).AbortQueue("queuename") 

Once you've turned down all of the inbound services/processes. If that doesn't work, use my previous suggestion.

I do think you should shut down the services and processes feeding the routers. And it wouldn't hurt to shut down the routers as well.

If the count is still at 1 after you do that, set the value to 0. And I actually think that the job IDs are from the source services and processes, not the routers themselves.