go to post Eduard Lebedyuk · May 31, 2016 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.
go to post Eduard Lebedyuk · May 24, 2016 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.
go to post Eduard Lebedyuk · May 10, 2016 As I understand, you need Caché users + some way to:Store additional informationProvide methods for web-login-account managementProvide additional security checksI guess domains could be used, okay. Also, here's somewhat related thread.>And if so, when deploying updates to the applications, would each domain/user-pool context be replicated environments> that would each need to be updated?Why? You have production server with real users and a test one(s) with some test users.
go to post Eduard Lebedyuk · May 10, 2016 Documentation has some information, see "Making Your Own Studio Templates".
go to post Eduard Lebedyuk · Apr 28, 2016 Use $Piece function to return a substring identified by a delimiter. : SELECT $PIECE('96842-2-AEV.00001-63561-001958-100728760I-42', '-', 2) Yields 2.
go to post Eduard Lebedyuk · Feb 18, 2016 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.CompiledMethodFor each argument decides on a strategy on how to get it's value at runtimeWrites source code that would implement value get at runtimeBuilds code to get all method arguments valuesInserts 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).