Eduard Lebedyuk · Sep 20, 2017 go to post

To convert string of any format into $horolog, use TO_DATE function:

w $SYSTEM.SQL.TODATE("20160105125915","YYYYMMDD")
>63922

To convert string of any format to timestamp use TO_TIMESTAMP function:

w $SYSTEM.SQL.TOTIMESTAMP("20160105125915","YYYYMMDDHHMISS")
>2016-01-05 12:59:15

These functions are available in Caché ObjectScript and SQL.

Eduard Lebedyuk · Sep 20, 2017 go to post

Provided you have this csv:

car,2000,100.51,27.10.2016,
phone,2003,65.8,15.01.2017,

You can import it into class Test.CSV:

1. Generate a persistent class Test.CSV

set rowtype = "name VARCHAR(50),year INTEGER,amount NUMERIC(9,2),date DATE"
set filename = "D:\data.csv"
do ##class(%SQL.Util.Procedures).CSVTOCLASS(2, .rowtype, filename,,,,"Test.CSV")

2. Import file or files

do ##class(Test.CSV).Import(2,filename)

 

Usually you can't import your CSV right away - the dates are in a different format, etc. You need to modify Import method and property definitions. For example I often:

  • Add FORMAT=4 property parameter for dates to import dates in dd/mm/yyyy format.
  • Find&replace Library.
  • Add this else line in Import method:
if $$$ISOK(tStatus) { set tCounter = tCounter + 1 } else { w $System.Status.GetErrorText(tStatus) return}

 

If you have a tab separated file, you need to change Import method signature from:

pDelimiter As %String = ","

to:

pDelimiter As %String = {$c(9)}
Eduard Lebedyuk · Sep 19, 2017 go to post

Check out Demo.ZenService.Zen.WeatherReportForm class in ENSDEMO namespace. GetWeatherReport method there creates a BS and sends a message to a BP outside of Ensemble context. You need to send an object and not a datatype, so in your example:

Set tSC = tService.ProcessInput(datePurge,.output)

Should be instead:

Set message = ##class(Ens.StringContainer).%New(datePurge)
Set tSC = tService.ProcessInput(message,.output)

If you want to send a message to a BP or BO you can do that too, just create an Ensemble message and use SendSync or SendAsync to send it from BS:

Set message = ##class(Ens.StringContainer).%New(datePurge)
Set targetHostName = "Name of BP or BO"
Set description = "My request description"
// Set timeout = 10 // how long to wait for a Sync call response, skip to wait forever
// Set tSC = tService.SendRequestSync(targetHostName, message, .response, timeout, description)
Set tSC = tService.SendRequestAsync(targetHostName, message, description)

That said, Ensemble hosts can run on schedule so you can use built-in Ensemble scheduler and not a system task manager.

Eduard Lebedyuk · Sep 19, 2017 go to post

How does Vue.JS stacks against other popular JS frameworks like Angular2, React, etc.?

Eduard Lebedyuk · Sep 19, 2017 go to post

I prefer %SQL.Util.Procedures because it is easier to use and it generates classes and methods that can be easily modified.

Eduard Lebedyuk · Sep 18, 2017 go to post

Ensure that user, which you are using to access the database has apropriate permissions to connect and access data.

Eduard Lebedyuk · Sep 16, 2017 go to post

> Would you have three branches for the code or 3 repo's.


Please describe differences between your DEV, TEST, PROD code. Still, I'd probably recommend one repo and 3 branches.

>Would you have three projects in Atelier to communicate with the environments and GitHub?


No, there would be one Atelier project for DEV. All other code propagation should be done automatically by Continuous Integration/Continuous Delivery solution.

Eduard Lebedyuk · Sep 12, 2017 go to post

That may mean one of the several things:

  • You're not specifying or specifyiong incorrectly one or several request header parameters. One of the most often encountered errors is not specifying ContentType as application/json.
  • Request content is formed incorrectly

Anyway, to solve this problem you need to make a correct request to the target system, record it and compare to the Ensemble request. Here's an article on that.

Eduard Lebedyuk · Sep 11, 2017 go to post

Same as with modifying a rule:

Business Rule Definition is stored as XML in XData block named RuleDefinition inside rule class and can be (de)serialized as an object of Ens.Rule.Model.ruleDefinition class. Some utility methods are available there.

Here's an example of modifying Demo.ComplexMap.Rule.SemesterBatchRouting rule in ENSDEMO class. It modifies "when" condition from 1 to 0.

zn "ENSDEMO"
set ruleClass = "Demo.ComplexMap.Rule.SemesterBatchRouting"
set sc = ##class(Ens.Rule.Model.ruleDefinition).LoadFromClass(ruleClass, .rule)
set rule.ruleSets.GetAt(1).actions.GetAt(1).whens.GetAt(1).condition=0
w rule.SaveAs(ruleClass)
set sc=$system.OBJ.Compile(ruleClass,"k-d")

But instead of:

set sc = ##class(Ens.Rule.Model.ruleDefinition).LoadFromClass(ruleClass, .rule)

You need to create and fill rule object manually.

Relevant discussion.

Eduard Lebedyuk · Sep 11, 2017 go to post

In any place (but preferably in the beginning) of your REST handler method add these lines:

set %response.ContentType = "text/html"
do ##class(%CSP.Utils).DisplayAllObjects()
quit $$$OK

and then open REST Url to see all current objects, including %request.

Eduard Lebedyuk · Sep 10, 2017 go to post

You can use %Dictionary package to modify classes.

For example you can open property which is an object of %Dictionary.PropertyDefinition, modify it and save. Recompile the class to see new class definition.

Here's an example of %Dictionary modification to create new method.

Eduard Lebedyuk · Sep 7, 2017 go to post
  1. Source control
  2. Code
    • Sure, I've done something similar. Here's the code.
    • Missed that, sorry.
  3. Ensemble
    • Sure, Inside your production you have access to all BO properties, including retry-relevant:  ..RetryCount, ..%LastReportedError and many others. So first you determine if the current message is a retry (..RetryCount>0) or not and then you can decide what to do with your request based on that.

 

Code for the custom adapter that specifies Content type:

Class Production.Adapter.HTTPOutboundAdapter Extends EnsLib.HTTP.OutboundAdapter
{

Method Post(Output pHttpResponse As %Net.HttpResponse, pFormVarNames As %String, pData...) As %Status
{
    quit ..SendFormDataArray(.pHttpResponse, "POST", ..GetRequest(), .pFormVarNames, .pData)
}

ClassMethod GetRequest() As %Net.HttpRequest
{
    set request = ##class(%Net.HttpRequest).%New()
    set request.ContentType = "application/json"
    quit request
}

}

Note that this way only one-line method Post gets redefined (you'll also need to redefine Get method, but still - these are small one-line methods). ContentType may be further refactored as a setting.

Eduard Lebedyuk · Sep 7, 2017 go to post

Some comments

  1. Source control
    • Each class should be in a separate file. There are many source control hooks available.
    • When redefining system methods like SendFormDataArray method in DLS.HTTP.OutboundAdapter class it's better to do it in two commits - in a first one you just copy method as is and in a second you modify it. It's easier to understand "what changed?" that way (via git blame).
  2. Code
    • Do not subclass/modify system methods if it's possible. What was modified SendFormDataArray?
    • Add comments to the parts you change when you must subclass/modify system methods.
    • $CLASSNAME($THIS) is equal to $CLASSNAME()
  3. Ensemble
Eduard Lebedyuk · Sep 7, 2017 go to post

Business Rule Definition is stored as XML in XData block named RuleDefinition inside rule class and can be (de)serialized as an object of Ens.Rule.Model.ruleDefinition class. Some utility methods are available there.

Here's an example of modifying Demo.ComplexMap.Rule.SemesterBatchRouting rule in ENSDEMO class. It modifies "when" condition from 1 to 0.

zn "ENSDEMO"
set ruleClass = "Demo.ComplexMap.Rule.SemesterBatchRouting"
set sc = ##class(Ens.Rule.Model.ruleDefinition).LoadFromClass(ruleClass, .rule)
set rule.ruleSets.GetAt(1).actions.GetAt(1).whens.GetAt(1).condition=0
w rule.SaveAs(ruleClass)
set sc=$system.OBJ.Compile(ruleClass,"k-d")

That said, I think the better approach would be to use (in order of
increasing implementation difficulty):

  • Context Class
  • Temporary Variables
  • Rule Assist Class

So the rule by itself does not change but values supplied by Context Class/Temporary Variables/etc do.

Eduard Lebedyuk · Sep 5, 2017 go to post

You can decode JSON escaped characters:

set string = $zcvt(string, "I", "JSON")

and remove special symbols after that.

Eduard Lebedyuk · Sep 2, 2017 go to post
  • Business Services receive or pull information from external systems
  • Business Processes do application logic
  • Business Operations send information into external systems
Eduard Lebedyuk · Sep 1, 2017 go to post

You can get output from %ToJSON() into a variable without any redirection:

set jsonString = dynamicObj.%ToJSON()
Eduard Lebedyuk · Sep 1, 2017 go to post

iKnow or iFind?

If iFind, then what index?

It depends on your corpus, and diversity of concepts encountered there.

I have 1:1 as corpus size:index size as a baseline, but it all depends on many factors.

Eduard Lebedyuk · Aug 31, 2017 go to post

This condition:

Super [ 'Persistent' 

Is insufficient. Consider this case:

Class Package.ClassA Extends %Library.Persistent
{
}

Class Package.ClassB Extends %XML.Adaptor
{
}

Class Package.ClassC Extends (ClassA, ClassB)
{
}

While Package.ClassC satisfies both conditions (it's a subclass of both %Library.Persistent and %XML.Adaptor), it would not be returned by the SQL query, as Super field does  not contain required superclasses directly.

 

But we can easily join 2 SubclassOf queries via SQL:

SELECT s1.name
FROM %Dictionary.ClassDefinitionQuery_SubclassOf('%Library.Persistent') s1
INNER JOIN %Dictionary.ClassDefinitionQuery_SubclassOf('%XML.Adaptor') s2 ON s2.name = s1.name
Eduard Lebedyuk · Aug 31, 2017 go to post

Does it work like this?

ClassMethod WriteCapture(vstrCommand As %String) As %String
{
    set tOldIORedirected = ##class(%Device).ReDirectIO()
    set tOldMnemonic = ##class(%Device).GetMnemonicRoutine()
    set tOldIO = $io
    try {
        set str=""

        //Redirect IO to the current routine - makes use of the labels defined below
        use $io::("^"_$ZNAME)

        //Enable redirection
        do ##class(%Device).ReDirectIO(1)

        XECUTE (vstrCommand)
        
    } catch ex {
        set str = ""
    }

    //Return to original redirection/mnemonic routine settings
    if (tOldMnemonic '= "") {
        use tOldIO::("^"_tOldMnemonic)
    } else {
        use tOldIO
    }
    do ##class(%Device).ReDirectIO(tOldIORedirected)

    quit str

    //Labels that allow for IO redirection
    //Read Character - we don't care about reading
rchr(c)      quit
    //Read a string - we don't care about reading
rstr(sz,to)  quit
    //Write a character - call the output label
wchr(s)      do output($char(s))  quit
    //Write a form feed - call the output label
wff()        do output($char(12))  quit
    //Write a newline - call the output label
wnl()        do output($char(13,10))  quit
    //Write a string - call the output label
wstr(s)      do output(s)  quit
    //Write a tab - call the output label
wtab(s)      do output($char(9))  quit
    //Output label - this is where you would handle what you actually want to do.
    //  in our case, we want to write to str
output(s)    set str=str_s   quit
}
Eduard Lebedyuk · Aug 31, 2017 go to post

You can use simply:

..RetryInterval

Instead of

%Ensemble("%Process").RetryInterval

Since current object is %Ensemble("%Process").