I'd also love to see the web application's properties programmatically, if possible (such as the physical files path).

Use Security.Application class directly, or preferably via %Installer manifest.

but I honestly don't know why it's choosing that web application over the default of /csp/default-namespace

Uncompile and compile ZEN classes after setting default web application. Or you can try to set HOMEPAGE parameter in the application class.

My question is, if we go to using Group By ID (which is currently null) in the web application, will this fix the sharing of %session Data? (This is why I have to make sure I'm editing the correct web application!)

If the user opens one web application in two tabs, they would be sharing a session (and namespace). Group By ID would only help if you have N different applications and would specify N different values for Group By ID property. In that case, they should use N different sessions.

Finally, we have a web application for each namespace; will we need the Group By ID for each application defined for each namespace, and should it be the same ID?

Depends, if you want to share %session or not.

Simpliest way: Cache executes an operating system command via $zf(-1) function.

Alternatively, you can load a dll with $zf(-4) (also 3, 5) functions.

There is also CNA project - it provides an interface for using native C-compatible shared libraries without anything but Caché ObjectScript code. CNA is a wrapper for libffi. CNA consists of native library (libcna) and Caché class (CNA.CNA). It is a wrapper around $zf functions.

That said, the most effective would be some kind of a library load with library written in either C or generated from some computational language. For example Mathlab can generate C code, which can be then compiled into a library.

Something like this? I had a similar problem, but the check was required only for part of the properties. If you want all of them, remove the line with continue.

Method IsEmpty() As %Boolean [ CodeMode = objectgenerator ]
{
    For i = 1:1:%class.Properties.Count() {
        Set Prop = %class.Properties.GetAt(i)
        CONTINUE:(Prop.Internal || Prop.Calculated || Prop.ReadOnly || Prop.Private || Prop.Identity)
        
        Do %code.WriteLine(" Quit:.." _ Prop.Name _ "'="""" $$$NO")
    }
    Do %code.WriteLine(" Quit $$$YES")
}

  1. Inherit from EnsPortal.MessageViewer
  2. Copy searchPane from EnsPortal.Template.filteredViewer
  3. In searchPane, TimeFormat node replace attribute  value="12" with value="999"
  4. Compile. Your new class would have Complete as TimeFormat default value

Alternatively, instead of 2-4 you can set TimeFormat value in your class by overriding some init callback ( %OnAfterCreatePage maybe, don't forget to call ##super() though) and setting it there. It would be a better solution.

2020 UPDATE.

I think I found a better solution.

1. Extend EnsPortal.MsgFilter.Assistant class.

2. In this class redefine EnumerateExecute method to provide the desired format. Replace this line:

Do ..addTerm(.aSelect,"{fn RIGHT(%EXTERNAL(head.TimeCreated),"_dateLen_"
)} As TimeCreated")

to provide the format you need.

Alternatively redefine OnFinalizeSQL to replace

{fn RIGHT(%EXTERNAL(head.TimeCreated),999)}

with something else.

3. Set the global

^EnsPortal.Settings("MessageViewer","AssistantClass")

to the value of your class.

This looks like an official way to modify MessageViewer behavior.

Sorry, I forgot that SYS.Database is deployed. You'll need to match $zu(49) to properties manually. But the macros usually have the same name. For example field 14 is sfnpiece macro ("piece" part of the name is irrelevant, so just sfn it is) and it corresponds to SFN property in SYS.Database.

 

To test, you can write the following method:

ClassMethod Test( Directory )

{
    Set db=##Class(SYS.Database).%OpenId(Directory)
    Write db.SFN = $p($zu(49),",",$$$sfnpiece) //14th piece
    // More checks
}

I imported your example, executed:

Do ##class(Wendy.LTCodes).SetData()

Then executed this sql:

SELECT
ID, Code, Description, Invalid
FROM Wendy.LTCodes

and received the following results:

IDCodeDescription  Invalid
N001N001ANYOLD DESC1
N002N002C5 REPEAT 1 
N111N111SPECIMEN COMMENT 
N200N200MSUD SCREEN 
N500N500Sickle Cell Screen 

seems to be working.

 

But then I didn't really understand the use of Parameter InvalidGLVN = "^Utils.GlobalPropP";

it's for use with indirection.  Example:

set ^Utils.GlobalPropP = 123
set glvn = "^Utils.GlobalPropP"
write glvn
> ^Utils.GlobalPropP
write @glvn
> 123

If our SVN repository already is storing discrete .cls files, does Atelier do any conversion when we load from SVN into our server instance?

No, except maybe for repository structure. That depends is Atelier + EGit support repository structure you use. For how to use EGit with Atelier check this article.

At what point would I see .udl files? I am thinking I would only see that if SVN was storing .xml and each of those would be converted to .udl.

There are no .udl files. There are just cls/mac/inc etc files in udl format, meaning they are represented on disk as is and not in the xml format. The extension would be .cls and so on. Here's the sample repository created with Atelier.

%Label is the way to go.

Its fifth argument is a CSS applied to a cell. So the following MDX:

SELECT NON EMPTY %LABEL([Measures].[%COUNT],,,,"width:100%") ON 1 FROM [HOLEFOODS]

Would be displayed like this in analyzer:

And its sixth argument is a CSS applied to the cell header:

SELECT NON EMPTY %LABEL([Measures].[%COUNT],,,,,"width:80%") ON 1 FROM [HOLEFOODS]

Alternatively you can use DeepSeeWeb to display dashboards with pivots. In there cells width can be easily modified.

Here it is:

/// Returns MDX string used to create pivot.<br>
/// <b>pPivotName</b> - fullname of pivot. Eg: "KPIs & Plugins/HoleFoods.pivot". Case insensitive.<br>
/// <b>pStatus</b> - Status of query execution.<br>
/// <b>MDX</b> - MDX query with filters<br> /// <b>BaseMDX</b> - MDX query without filters<br>
ClassMethod GetMdx(pPivotName As %String, Output MDX, Output BaseMDX) As %Status
{
    #dim tPivot As %DeepSee.Dashboard.Pivot
    #dim tPivotTable As %DeepSee.Component.pivotTable
    set MDX = ""
    set BaseMDX = ""

    set tPivot = ##class(%DeepSee.UserLibrary.Utils).%OpenFolderItem(pPivotName,.pStatus)
    return:'$IsObject(tPivot) $$$OK
    return:$$$ISERR(pStatus) pStatus

    set tPivotTable = ##class(%DeepSee.Component.pivotTable).%New()
    set pStatus = tPivot.%CopyToComponent(tPivotTable)
    return:$$$ISERR(pStatus) pStatus

     // returns tQueryText - mdx without filters
    set rs = tPivotTable.%CreateResultSet(.pStatus, .tParms, .tFilterInfo, .tAdvancedFilters, .BaseMDX)
    
    return:$$$ISERR(pStatus) pStatus

    set pStatus = tPivotTable.%GetFilterInfo(.tFilterInfo, .tAdvancedFilters)
    //return:$$$ISERR(pStatus) pStatus

    if (($d(tFilterInfo)=0) &&($d(tAdvancedFilters)=0)) {
        set MDX = BaseMDX // no filters, so we're good
    } else {
        // returns tQueryText - mdx with filters
        set rs = tPivotTable.%CreateResultSet(.pStatus, .tParms, .tFilterInfo, .tAdvancedFilters, .MDX)
        return:$$$ISERR(pStatus) pStatus
    }

    // Remove \n
    set MDX = $TR(MDX, $C(10), "")
    set BaseMDX = $TR(BaseMDX, $C(10), "")

    return pStatus
}

It is a part of MDX2JSON REST API.

Save timestamp with user timezone (get timezone from request), for example (returns server timezone):

write $ZDATETIME($HOROLOG, 1, 5)

Return timestamp to a client, there are a lot of js libraries which automatically convert incoming timestamp with a different timezone into user's local time.

The advantage of this approach is that you only need to convert timezone once, the rest would be done on client.

Check out ISO 8601  for timestamp formats with timezone support and which js understands.

This approach works best, when you don't need to access your application data from the systems without support for this type of timestamp.