Eduard Lebedyuk · Aug 11, 2017 go to post
  1. Copy them into new array with keys = target property value. Iteration over this new array would be ordered as desired. Or when you set your array initially you can set keys = target property value.
  2. Another option is to order by local. Set some local with subscript = target property value, and local value as an oref. Then iterate over the local - you'll get ordered orefs.
Eduard Lebedyuk · Aug 10, 2017 go to post

1. How do you want to use this partial index?

2. NULL values are indexed, same as any other value (with one difference that any number of NULL values are allowed in  a Unique index). Consider the following example:

Class Sample.Person Extends %Persistent
{

Property Name As %String;

Index NameIndex On Name;

/// do ##class(Sample.Person).Test()
ClassMethod Test()
{
    kill ^Sample.PersonD, ^Sample.PersonI
    do ..AddPerson("ed")
    do ..AddPerson("ed")
    do ..AddPerson("bob")
    do ..AddPerson()
    do ..AddPerson()
    
    zw ^Sample.PersonD, ^Sample.PersonI
}

ClassMethod AddPerson(Name)
{
    set p = ..%New()
    set:$d(Name) p.Name = Name
    do p.%Save()
}

Whn I run the text method:

do ##class(Sample.Person).Test()

I get the following data global:

^Sample.PersonD=5
^Sample.PersonD(1)=$lb("","ed")
^Sample.PersonD(2)=$lb("","ed")
^Sample.PersonD(3)=$lb("","bob")
^Sample.PersonD(4)=$lb("","")
^Sample.PersonD(5)=$lb("","")

And the following index global:

^Sample.PersonI("NameIndex"," ",4)=""
^Sample.PersonI("NameIndex"," ",5)=""
^Sample.PersonI("NameIndex"," BOB",3)=""
^Sample.PersonI("NameIndex"," ED",1)=""
^Sample.PersonI("NameIndex"," ED",2)=""

As you see NULL values are indexed same as any other value.

Index global has the following structure:

^ClassIndexGlobal(IndexName, IndexValue, Id) = DataStoredInIndex

Where:

  • ClassIndexGlobal - is a global  name used for storing class globals. Defined in Storage.
  • IndexName-  is a name of the index
  • IndexValue - is a collated stored value (so "bob" becomes " BOB", and NULL  becomes " "). Some additional info on collation. Documentation.
  • Id - object id
  • DataStoredInIndex - any additional data stored in index. Documenatation.

Also for this SQL:

SELECT ID
FROM Sample.Person
WHERE Name IS NULL

The following plan that uses our index gets generated:

  • Read index map Sample.Person.NameIndex, using the given %SQLUPPER(Name), and looping on ID.
  • For each row:
    •  Output the row.
Eduard Lebedyuk · Aug 10, 2017 go to post

If you have a method that only returns dynamic object or array you can use [codemode = expression]:

ClassMethod TestGETMixedDynamicObject(class As Frontier.UnitTest.Fixtures.Class) As %DynamicObject(PUBLIC=1) [codemode=expression]
{
{
    "class": (class)
}
}
Eduard Lebedyuk · Aug 9, 2017 go to post

Use old json provider:

set st = ##class(%ZEN.Auxiliary.jsonProvider).%ConvertJSONToObject(json, class,.obj, $$$YES)

Instead of class argument, the json can contain _class property.

Eduard Lebedyuk · Aug 9, 2017 go to post

Settings are not really meant to be used like that.

If you want to report usage volume the best option would be creting a DeepSee cube over Ens.MessageHeader class. For a limited subset of analytic operations SQL may be enough.

Eduard Lebedyuk · Aug 8, 2017 go to post

Here's what I came up with.

Business service (works in SYNC or ASYNC mode depending on OneWay setting):

Class Passthrough.PassthroughService Extends EnsLib.SOAP.GenericService
{

Property DefaultResponce As %String(MAXLEN = "") [ InitialExpression = "<soap:Envelope><soap:Body></soap:Body></soap:Envelope>" ];

Parameter SETTINGS = "OneWay:Basic";

/// Pass through to OnProcessInput()
Method ProcessBody(pAction As %String, pRequestBody As %CharacterStream, pResponseBody As %CharacterStream) As %Boolean
{
    Set tSC=..ProcessInput(pRequestBody, .pResponseBody, pAction)
    Set:pResponseBody="" pResponseBody = ..DefaultResponce
    Quit $$$OK
}

}

And a BP for ASYNC logging (you need to set it as a target for BS only if you want ASYNC mode and logging, otherwise just call BO directly):

Class Passthrough.PassthroughProcess Extends Ens.BusinessProcess [ ClassType = persistent ]
{

/// Configuration item to which to send messages
Property TargetConfigName As Ens.DataType.ConfigName;

Parameter SETTINGS = "TargetConfigName:Basic:selector?multiSelect=0&context={Ens.ContextSearch/ProductionItems?targets=1&productionName=@productionId}";

Method OnRequest(pRequest As EnsLib.SOAP.GenericMessage, Output pResponse As EnsLib.SOAP.GenericMessage) As %Status
{
    Quit ..SendRequestSync(..TargetConfigName, pRequest, .pResponse)
}

}

Default EnsLib.SOAP.GenericOperation can be used for BO.

Eduard Lebedyuk · Aug 8, 2017 go to post

Have you thought about writing an article on External Service Registry  best practices? It seems like a very interesting topic.

Eduard Lebedyuk · Aug 8, 2017 go to post
  • Do you use an issue tracking / collaboration system? If so which one. Any you would recommend or immediately dismiss based on personal experience?

I use Github and GitLab. Issues are tracked there. They are fairly similar, use GitLab if you want on-premise solution.

  • How do you keep track of large code bases? Thousdands of folders named backup1, backups2, ..., SVN, git?

Git.

  • Do you have a development server to which you commit and test features there, or do you rather run a local copy of caché and implement features locally first, then push to the server?

Everything is implemented and tested locally. Then I push to a version control. Continuous integration does the rest.

Eduard Lebedyuk · Aug 8, 2017 go to post

I have modified Directory to point to a physical path, and restarted Caché. Still getting 404.

 

UPD. Adding trailing slash to directory name solved my problem.

Thank you.

Eduard Lebedyuk · Aug 7, 2017 go to post

Here's an example of calling class method with two parameters:

<call method="Method">
  <parameter expression="%request.Get("URLPAPAM")"/>
  <parameter value="123"/>
</call>

Check %ZEN.Report.call and %ZEN.Report.parameter for details.

Eduard Lebedyuk · Aug 5, 2017 go to post

Why do you prefer $System.Status.OK()to $$$OK and $System.Status.IsError(sc) to $$$ISERR(sc)and $$$ISOK(sc)?

Eduard Lebedyuk · Aug 3, 2017 go to post

You can define a parameter as an ObjectScript expression that it is evaluated at runtime. To do so, specify its type as COSEXPRESSION and specify an ObjectScript expression as the value:

Parameter PARAMNAME As COSEXPRESSION = "ObjectScriptExpression";

where PARAMNAME is the parameter being defined and ObjectScriptExpression is the ObjectScript content that is evaluated at runtime.
An example class parameter definition would be:

Parameter DateParam As COSEXPRESSION = "$H";

Documentation.

That said, I'd recommend gradual refactoring of these parameters into methods.

Eduard Lebedyuk · Aug 2, 2017 go to post

Ensemble event log?

It is stored in Ens.Util.Log class, so you can easily export it to csv/html/xml/pdf/txt from SQL. Here's a sample export to CSV:

set rs = ##class(%SQL.Statement).%ExecDirect(,"SELECT * FROM Ens_Util.Log")
set file = "C:\InterSystems\Ensemble\mgr\Temp\Ens.Log"
do rs.%DisplayFormatted(100, file) // 100 for CSV format

Docs for %DisplayFormatted.

Eduard Lebedyuk · Aug 1, 2017 go to post

There are several ways to do that.

  1. Add from/to arguments to your existing REST servise, so your client asks for a data within a specified time slice.
    • Specifying no arguments yields all data
    • Specifying only from yields data starting at from and till now
    • Specifying both from and to yields only data acquired between from and to
  2. Use websockets.
Eduard Lebedyuk · Jul 31, 2017 go to post

If it's a part of Ensemble Production, you need to create Business Operation. Here's a sample BO that does POST request:

/// This operation does a POST request to a REST API and receives Auth token
Class Production.Operation.NLPAuthOperation Extends Ens.BusinessOperation
{

Parameter ADAPTER = "EnsLib.HTTP.OutboundAdapter";

Property Adapter As EnsLib.HTTP.OutboundAdapter;

Parameter INVOCATION = "Queue";

/// Get Auth token
Method GetAuth(request As Ens.Request, Output response As Ens.StringResponse) As %Status
{
    #dim sc As %Statis = $$$OK
    
    // Form request body (using Credentials)
    set input = {"user": ( ..Adapter.%CredentialsObj.Username), "pass": (..Adapter.%CredentialsObj.Password)}
    
    // Send post request
    set sc = ..Adapter.Post(.httpResponse,,input.%ToJSON())
    quit:$$$ISERR(sc) sc
    
    // Get token from response
    set token = {}.%FromJSON(httpResponse.Data).token

    //
    set response = ##class(Ens.StringResponse).%New(token)
    quit sc
}

XData MessageMap
{
<MapItems>
    <MapItem MessageType="Ens.Request">
        <Method>GetAuth</Method>
    </MapItem>
</MapItems>
}

}

If you're outside of Ensemble, you need to use %Net.HttpRequest class. Here's an example.

Eduard Lebedyuk · Jul 27, 2017 go to post

Let's say you have called this url:

http://localhost:57772/rest/users/1?fields=list

And you have this route:

<Route Url="/users/:id" Method="GET" Call="Test"/>

Then in your Test method call:

write %request.Get("fields")

it would output list.

Eduard Lebedyuk · Jul 26, 2017 go to post

Turns out, I forgot to setup DispatchClass for /passthrough  application. After setting it to  EnsLib.SOAP.GenericService, the following URL works:

http://localhost:57773/passthrough/PassthroughService/CurrencyConvertor.asmx
Eduard Lebedyuk · Jul 25, 2017 go to post

That's $$$defClassDefined(class). It shows that class definition exists (or doesn't), but it doesn't show if a class is compiled.

Eduard Lebedyuk · Jul 24, 2017 go to post

%ExistsId does not open an object for %Dictionary package. Just checks the globals (see %Dictionary.CompiledClass for example).

The fastest way to check if a class exists would be:

write $$$comClassDefined(class)