Eduard Lebedyuk · Mar 7, 2016 go to post

Is there any way to set environmental variables before calling $zf?

For example I want to add something to PATH, but if I do it in an actual $zf call it would be a lot longer and hard to read. So is there any way to set environmental variables for current process from Cache?

Eduard Lebedyuk · Mar 7, 2016 go to post

2016.2 Studio does not seem to have package delete button.

Just delete all contents in a package and it would disappear.  Or execute this query:

DELETE FROM %Dictionary.ClassDefinition WHERE Name %STARTSWITH 'package'

To upload images click on this button (on the WYSIWYG editor):

Eduard Lebedyuk · Mar 5, 2016 go to post

InterSystems University Outreach Program (Russia). Each developer has his's own machine with his own Caché/Ensemble instance. We use Git source control system and there are several Studio integration plugins we offer, but mainly Cache-tort-git. Changes are commited to a central GitHub repository preferably via pull-requests (see GitHub workflow). Changes from GitHub repository are automatically pulled to a production server(s) with Cache GitHub Continuous Integration tool.

Eduard Lebedyuk · Mar 3, 2016 go to post

Also ##Expression macro preprocessor command can be masked and distributed along several macros (which in turn can be distributed across several inc files). For example consider the following class:

Class Utils.Macro Extends %RegisteredObject
{

/// do ##class(Utils.Macro).Test()
ClassMethod Test()
{
    #define Tab #
    #define Indent $$$Tab$$$Tab
    #define Start Expre
    #define End ssion($$$Call)
    #define Call ##class(Utils.Macro).GenerateNewClassObject(%classname)
    #define All $$$Indent $$$Start$$$End
    set b=$$$All
}

ClassMethod GenerateNewClassObject(cls As %String)
{
    zw
    // custom logic depending on class may be implemented
    q "##class("_cls_").%New()"
}

}

Note, that in this example full-text search for ##Expression would yield nothing.

In here GenerateNewClassObject method would be executed on Utils.Macro class compilation. because $$$All macro would be evaluated to:

##Expression(##class(Utils.Macro).GenerateNewClassObject(%classname)

Which in turn would be evaluated on the last line:  set b=$$$All where GenerateNewClassObject would be called by ##Expression command.

 

So I guess don't compile Caché ObjectScript from untrusted sources at all or check carefully what you compile.

Eduard Lebedyuk · Mar 3, 2016 go to post

And for any macro definitions which contain ##Expression, ##Function, #Execute and ##SafeExpression.  Also #If, #IfDef, #IfNDef conditions (any Caché ObjectScript expression) are evaluated at compile time.

Eduard Lebedyuk · Mar 2, 2016 go to post

ClassMethod way to set a language for a session is:

Do ##class(%MessageDictionary).SetSessionLanguage(lang)

The interesting case would be $$$Text("englishtext") macros getting compiled on a new instance under a session in other language. Under these conditions the ^CacheMsg global would be written with "englishtext" values under session language. Which would be set as a default language for this domain (Messages in $$$Text are presumed to be written in domain default language, unless specified otherwise).

To prevent that set domain default language or change  session language before the compilation.

As far as I remember importing message dictionary does not set default language for the domain itself.

Eduard Lebedyuk · Feb 26, 2016 go to post

I stand somewhat corrected

do $System.Process.Terminate()

In %ZSTART(or ZAUTHENTICATE for that matter) does solve my problem. Seems like overkill, though. Really wanted just to return status.

Eduard Lebedyuk · Feb 26, 2016 go to post

Exactly, what I want is two-factor authorisation but with my own authentication infrastructure for a second factor.

Eduard Lebedyuk · Feb 26, 2016 go to post

Additional check is calling external device to provide rfid/biometric/whatever authentication on the user logged in to provide additional security level. Yes, application approach seems to be better.

Eduard Lebedyuk · Feb 24, 2016 go to post

I need to perform additional check during Cache user authorization. Is there any way to do that?

Eduard Lebedyuk · Feb 24, 2016 go to post

How much does a standart 1-2cpu/1GB RAM/32Gb SSD virtual server with linux distro costs per month? I was unable to find it on their website.

Eduard Lebedyuk · Feb 23, 2016 go to post

Great!

RSS feed by tag works in Thunderbird. Main RSS feed still shows as invalid.

Some thoughts about email integration:

  1. Replace the icon with a simple envelope icon - which  is more recognizable. I can easily recognize the first three icons, but the last one I incorrectly accociate with msn logo
  2. After clicking on a "share via email bullton" I'm being redirected to a new page where I can send a message from communityportal@intersystems.com. I think you  can raise convertion by using a simple mailto link (so default mail client pops up with a predefined topic and message instead of the new page) - so the person sharing the post can share from his own email account.
Eduard Lebedyuk · Feb 21, 2016 go to post

1. They must be granted to either application or user

2. Only resources of Service, Application and User type could be used there. %DB are database resources

3. SMP -> Menu -> View Roles -> Choose the role "webapp-admin" -> General Tab -> Priveleges -> Add -> choose the resoure -> OK -> Save

Repeat for webapp-user

4. Like this:

Parameter RESOURCE = "ResourceName1:Permission,ResourceName2,ResourceName3:Permission";

Where Permission is one of: READ, WRITE, USE

If Permission is skipped (see ResourceName2) then USE permission is checked.

Eduard Lebedyuk · Feb 19, 2016 go to post

Here's an aggregation rss feed which combines the Community Portal RSS and InterSystems StackOverFlow RSS tags. It's available here and works with Thunderbird.

Eduard Lebedyuk · Feb 19, 2016 go to post

Have you thought about uploading this projects on GitHub?

UPD. Nevermind, seen your message about GitHub in another topic.

Eduard Lebedyuk · Feb 19, 2016 go to post

If you paste table from somewhere else, when creating a post, sometimes borders may be undisplayed. To fix that switch into HTML view (via "Disable rich-text" button) and find the beginning of your table definition. It would look somewhat like that:

<table class="confluenceTable">

And replace it with:

<table border="1" cellpadding="1" cellspacing="1">
Eduard Lebedyuk · Feb 18, 2016 go to post

I had a similar problem. The task was to write custom logging system, which would automatically store current method argument values. Here's how I done it.

First the the persistent log class (relevant parts):

Class App.Log Extends %Persistent
{

/// Replacement for missing values
Parameter Null = "Null";

/// Type of event
Property EventType As %String(MAXLEN = 10, VALUELIST = ",NONE,FATAL,ERROR,WARN,INFO,STAT,DEBUG,RAW") [ InitialExpression = "INFO" ];

/// Name of class, where event happened
Property ClassName As %String(MAXLEN = 256);

/// Name of method, where event happened
Property MethodName As %String(MAXLEN = 128);

/// Line of int code
Property Source As %String(MAXLEN = 2000);

/// Cache user
Property UserName As %String(MAXLEN = 128) [ InitialExpression = {$Username} ];

/// Arguments' values passed to method
Property Arguments As %String(MAXLEN = 32000, TRUNCATE = 1);

/// Date and time
Property TimeStamp As %TimeStamp [ InitialExpression = {$zdt($h, 3, 1)} ];

/// User message
Property Message As %String(MAXLEN = 32000, TRUNCATE = 1);

/// User IP address
Property ClientIPAddress As %String(MAXLEN = 32) [ InitialExpression = {..GetClientAddress()} ];

/// Add new log event
/// Use via $$$LogEventTYPE().
ClassMethod AddRecord(ClassName As %String = "", MethodName As %String = "", Source As %String = "", EventType As %String = "", Arguments As %String = "", Message As %String = "")
{
    Set record = ..%New()
    Set record.Arguments = Arguments
    Set record.ClassName = ClassName
    Set record.EventType = EventType
    Set record.Message = Message
    Set record.MethodName = MethodName
    Set record.Source = Source
    Do record.%Save()
}
}

And here's macros for client code:

#define StackPlace         $st($st(-1),"PLACE")
#define CurrentClass     ##Expression($$$quote(%classname))
#define CurrentMethod     ##Expression($$$quote(%methodname))

#define MethodArguments ##Expression(##class(App.Log).GetMethodArguments(%classname,%methodname))

#define LogEvent(%type, %message) Do ##class(App.Log).AddRecord($$$CurrentClass,$$$CurrentMethod,$$$StackPlace,%type,$$$MethodArguments,%message)
#define LogNone(%message)         $$$LogEvent("NONE", %message)
#define LogError(%message)         $$$LogEvent("ERROR", %message)
#define LogFatal(%message)         $$$LogEvent("FATAL", %message)
#define LogWarn(%message)         $$$LogEvent("WARN", %message)
#define LogInfo(%message)         $$$LogEvent("INFO", %message)
#define LogStat(%message)         $$$LogEvent("STAT", %message)
#define LogDebug(%message)         $$$LogEvent("DEBUG", %message)
#define LogRaw(%message)         $$$LogEvent("RAW", %message)

Now, how that works in client code?  Let's say there is a class:

Include App.LogMacro
Class App.Use [ CompileAfter = App.Log ]
{

/// Do ##class(App.Use).Test()
ClassMethod Test(a As %Integer = 1, ByRef b = 2)
{
    $$$LogWarn("Message")
}
}

In the int code, the $$$LogWarn macro would be transformed into:

Do ##class(App.Log).AddRecord("App.Use","Test",$st($st(-1),"PLACE"),"WARN","a="_$g(a,"Null")_"; b="_$g(b,"Null")_";", "Message")

And after execution a new record would be added to App.Log table (note, that the method was called with default params - if it was called with other values they would be saved, as this logging system gets arguments values at runtime):

There is also some additional functionality, such as objects serializationinto json and context restoration at a later date, but that does not pertrain to the current discussion.

Anyway, the main idea is that at compile time we have a macro that:

  • Gets method arguments list from %Dictionary.CompiledMethod

  • For each argument decides on a strategy on how to get it's value at runtime

  • Writes source code that would implement value get at runtime

  • Builds code to get all method arguments values

  • Inserts this  code into method

Relevant methods (in App.Log):

/// Entry point to get method arguments string
ClassMethod GetMethodArguments(ClassName As %String, MethodName As %String) As %String
{
    Set list = ..GetMethodArgumentsList(ClassName,MethodName)
    Set string = ..ArgumentsListToString(list)
    Return string
}

/// Get a list of method arguments
ClassMethod GetMethodArgumentsList(ClassName As %String, MethodName As %String) As %List
{
    Set result = ""
    Set def = ##class(%Dictionary.CompiledMethod).%OpenId(ClassName _ "||" _ MethodName)
    If ($IsObject(def)) {
        Set result = def.FormalSpecParsed
    }
    Return result
}

/// Convert list of method arguments to string
ClassMethod ArgumentsListToString(List As %List) As %String
{
    Set result = ""
    For i=1:1:$ll(List) {
        Set result = result _ $$$quote($s(i>1=0:"",1:"; ") _ $lg($lg(List,i))_"=")
        _ ..GetArgumentValue($lg($lg(List,i)),$lg($lg(List,i),2))
        _$S(i=$ll(List)=0:"",1:$$$quote(";"))
    }
    Return result
}

ClassMethod GetArgumentValue(Name As %String, ClassName As %Dictionary.CacheClassname) As %String
{
    If $ClassMethod(ClassName, "%Extends", "%RegisteredObject") {
        // it's an object
        Return "_##class(App.Log).SerializeObject("_Name _ ")_"
    } Else {
        // it's a datatype
        Return "_$g(" _ Name _ ","_$$$quote(..#Null)_")_"
    }
}

The project is open-sourced and availible on GitHub (to use import all classes from App package into any namespace).

Eduard Lebedyuk · Feb 17, 2016 go to post

I modified your code like this:

Class test.DummyClass Extends %RegisteredObject
{

Property notanumber As %String;

Property aboolean As %Boolean;

/// do ##class(test.DummyClass).Test()
ClassMethod Test()
{
    
    set dummy = ..%New()
    set dummy.notanumber = "28001"
    set dummy.aboolean = 1
    do ##class(%ZEN.Auxiliary.jsonProvider).%ObjectToJSON(dummy,,,"aelotw")
}
}

Terminal output:

>do ##class(test.DummyClass).Test()
{
        "notanumber":"28001",
        "aboolean":true
}
Eduard Lebedyuk · Feb 17, 2016 go to post

It can be done in 2016.1 FT, see this post. To do it in older versions, inherit from system classes.

 

Alternatively you can define a persistent class with properties of required types and transform it into json.

 

P.S. As a hack: JSON consumer can sometimes accept 1/0 in place of true/false, so you can try to use this values.

Eduard Lebedyuk · Feb 16, 2016 go to post

Modify your query like this:

SELECT COUNT(DISTINCT $EXTRACT(Criteria, 1, 490)) AS Relevance 
FROM HS_IHE_ATNA_Repository.Aggregation 
WHERE EventType = 'CROSS GATEWAY QUERY'

Only symbols 1 to 490 would be used for comparison in DISTINCT condition, also see $EXTRACT docs.

Eduard Lebedyuk · Feb 16, 2016 go to post

Good way to calculate such property. One question I have is why you use indirection and $name function in the generated method?  It can be safely removed, for example like this:

ClassMethod IsLastKnownRecordCheck(CitizenRef As %Integer, RelocationDate As %Date) As %Boolean [ CodeMode = objectgenerator ]
{
    set storagename="Default"
    set storageInd=%class.Storages.FindObjectId(%classname_"||"_storagename)
    set storage=%class.Storages.GetAt(storageInd)
    set indexLocation=storage.IndexLocation
    do %code.WriteLine($c(9)_"quit $order("_indexLocation_"(""CitizenRelocation"", CitizenRef, RelocationDate))=""""")
    quit $$$OK
}
Eduard Lebedyuk · Feb 16, 2016 go to post

Hello.  You can do it like this:

  1.  In your source fact class define a new property - IsLastKnownRecord As %Boolean
  2. Fill this property with correct values during batch imort, or fill it with a task or a trigger or a callback (many ways to calculate it, choose any approach you want)
  3. In your cube delete IsLastKnownRecord dimension
  4. In studio open your cube class and add buildRestriction to the cube definition:  <cube buildRestriction="IsLastKnownRecord=1"
  5. Save and compile your cube
  6. During cube build only facts with IsLastKnownRecord value equal to true would be used to build a cube