With what we've done the syntax ends up looking like:

Class DC.Demo.Hierarchy Extends %Persistent [ MemberSuper = AppS.Index.Methods ]
{

Property message As %String;

Property login As %String;

Property parentId As DC.Demo.Hierarchy [ SqlFieldName = parent_id ];

Index parentId On parentId [ Type = bitmap ];

ClassMethod RunDemo()
{
    Do ..%KillExtent()
    &sql(insert into DC_Demo.Hierarchy (message, login, parent_id)
        values ('Bacon ipsum dolor amet pork shoulder ribs', 'User 1', null))
    &sql(insert into DC_Demo.Hierarchy (message, login, parent_id)
        values ('BGouda croque monsieur emmental.', 'User 2', 1))
    &sql(insert into DC_Demo.Hierarchy (message, login, parent_id)
        values ('Manchego fromage frais airedale', 'User 3', 2))
        
    Do ##class(%SQL.Statement).%ExecDirect(,
        "select id, message, parent_id from DC_Demo.Hierarchy "_
        "where id %FIND DC_Demo.Hierarchy_parentIdFind(2,'all descendants')").%Display()
        
    Do ##class(%SQL.Statement).%ExecDirect(,
        "select id, message, parent_id from DC_Demo.Hierarchy "_
        "where id %FIND DC_Demo.Hierarchy_parentIdFind(3,'all related')").%Display()
}

}

Because there's a self-referencing property with a bitmap index, the hierarchy support is automatic via the MemberSuper class. Output is:

d ##class(DC.Demo.Hierarchy).RunDemo()
ID    message    parent_id
2    BGouda croque monsieur emmental.    1
3    Manchego fromage frais airedale    2

2 Rows(s) Affected

ID    message    parent_id
1    Bacon ipsum dolor amet pork shoulder ribs    
2    BGouda croque monsieur emmental.    1
3    Manchego fromage frais airedale    2

3 Rows(s) Affected

There's nothing built-in for this, but you can simulate it via custom class queries or %SQL.AbstractFind. I have an implementation of %SQL.AbstractFind/%Library.FunctionalIndex that does some things with hierarchies but falls short of the capabilities you linked in the Oracle doc. Specifically, it can find all ancestors/descendants/both (the whole tree) in a hierarchy efficiently, but it doesn't follow the same rules around ordering and won't let you do paths and such. (I'd want to clean it up a good deal before sharing, but that's probably worthwhile at some point.)

@Daniel Bertozzi , following up - I downloaded ImageMagick and the following works just fine for me (though I'm a little surprised at how slow it is):

Class DC.Demo.ImageMagick
{

ClassMethod Convert(inFile As %String = "C:\Temp\ImageMagick\inFile.jpg", outFile As %String = "C:\Temp\ImageMagick\outFile.jpg")
{
    Do $zf(-100,"","magick",inFile,"-resize","640x480",outFile)
}

}

I think the likely issue is that ImageMagick isn't on your PATH. You'll need to restart your instance for it to pick up PATH changes, so this might be the root cause if you just installed ImageMagick. Could also be interesting to run with the /SHELL flag and see if that works.

Hopefully this helps!

In communication outside of this thread, turns out the issue stemmed from a failed %Save() - something like:

Class DC.Demo.OREFOIDDemo Extends %Persistent
{

Property Foo As %String(VALUELIST = ",Bar");

ClassMethod RunDemo()
{
    Do ..%KillExtent()
    
    Set thingOne = ..%New()
    Set thingOne.Foo = "Bar"
    Do thingOne.%Save()
    Set thingOne.Foo = "Baz"
    Do thingOne.%Save()
    
    // Later, and elsewhere, because thingOne happens to be in memory,
    // it appears that the value "Baz" has been persisted:
    
    Set thingTwo = ..%OpenId(thingOne.%Id())
    
    Write !,"thingOne = ",thingOne
    Write !,"thingTwo = ",thingTwo
    Write !,"thingOne.Foo = ",thingOne.Foo
    Write !,"thingTwo.Foo = ",thingTwo.Foo
    
    Write !,"thingOne.FooGetStored(thingOne.%Id()) = ",thingOne.FooGetStored(thingOne.%Id())
    
    Do thingTwo.%Reload()
    Write !,"After thingTwo.%Reload(), thingOne.Foo = ",thingOne.Foo
    Write !,"After thingTwo.%Reload(), thingTwo.Foo = ",thingOne.Foo
}

}

Which produces output:

Do ##class(DC.Demo.OREFOIDDemo).RunDemo()
thingOne = 15@DC.Demo.OREFOIDDemo
thingTwo = 15@DC.Demo.OREFOIDDemo
thingOne.Foo = Baz
thingTwo.Foo = Baz
thingOne.FooGetStored(thingOne.%Id()) = Bar
After thingTwo.%Reload(), thingOne.Foo = Bar
After thingTwo.%Reload(), thingTwo.Foo = Bar

Given that SQLCODE is 100 (in other comment thread), might you have an index on %ConfigName that has not been built?

What do you see from the following queries?

select * from X_X.X where %ConfigName IN ('X_X_X','Y_Y_Y')

select * from %IGNOREINDEX * X_X.X where %ConfigName IN ('X_X_X','Y_Y_Y')

Also, is X_X.X the table corresponding to the class where the property is defined?

Short answer: yes, ZPM can manage whatever you want other than ObjectScript, it just takes a little extra work.

Longer answer:

This "extra work" involves writing a class that extends %ZPM.PackageManager.Developer.Processor.Abstract and overrides OnBeforePhase or OnAfterPhase, then implementing whatever behavior you want. You can see a bunch of classes in the %ZPM.PackageManager.Developer.Processor package that do this.

I'd imagine being able to put something in the Resources element of module.xml like:

<Resource Name="/external-dependencies/some-package" ProcessorClass="MyPackage.AptGetInstall" />

Where MyPackage.AptGetInstall overrides OnAfterPhase and for the "Activate" phase runs apt-get install <name of resource following "/external-dependencies/"

The catch:

If you want to support all operating systems, think about what the Windows equivalent would be and/or add defensive coding to avoid trying to run the command on Windows.

Here's how I'd typically do something like that, going back to my example from one of your earlier questions and expanding a bit. The persistent class as a new %Boolean property named "Toggleable" (not a magical name - you can call it whatever you like), and the tablePane has <column> elements added. The query supplying data for the table has the ID and Toggleable columns added as well. The "Toggleable" column has OnDrawCell defined to provide custom HTML for the cells in the table; this references an ObjectScript method (which doesn't need to be, and in fact shouldn't be, a ZenMethod). That ObjectScript method renders HTML which calls a ZenMethod to actually do the update, using query results from the %query variable (which is a special thing that's available for use in OnDrawCell). No table refresh is needed, but if you refresh the page you'll see that the checkbox values have indeed been persisted.

Here's the Zen XData change:

<tablePane id="myTable" OnCreateResultSet="CreateResultSet" valueColumn="ID">
<parameter value="" />
<parameter value="" />
<parameter value="" />
<column colName="ID" hidden="true" />
<column colName="Name" />
<column colName="SomeDate" />
<column colName="Toggleable" OnDrawCell="DrawToggleCell" />
</tablePane>

And corresponding ObjectScript methods:

/// Note: this doesn't need to be a ZenMethod.
ClassMethod DrawToggleCell(pTable As %ZEN.Component.tablePane, pColName As %String, pSeed As %String) As %Status
{
    &html<<input type="checkbox" #($Case(%query("Toggleable"),1:"checked",:""))# onchange="zenPage.Toggle(#(..QuoteJS(%query("ID")))#,this.checked)" />>
    Quit $$$OK
}

ClassMethod Toggle(pID As %String, pValue As %Boolean) [ ZenMethod ]
{
    Set obj = ##class(DC.Demo.SampleData).%OpenId(pID,,.sc)
    $$$ThrowOnError(sc)
    Set obj.Toggleable = pValue
    $$$ThrowOnError(obj.%Save())
}

Here's the full sample:

Class DC.Demo.SampleData Extends (%Persistent, %Populate)
{

Property Name As %String;

Property SomeDate As %Date;

Property Toggleable As %Boolean;

}

Class DC.Demo.ZenPage Extends %ZEN.Component.page
{

XData Contents [ XMLNamespace = "http://www.intersystems.com/zen" ]
{
<page xmlns="http://www.intersystems.com/zen">
<fieldSet legend="Filter" layout="horizontal">
<text label="Name Starts With:" onchange="zen('myTable').setProperty('parameters',1,zenThis.getValue())" />
<dateText label="Start Date:" onchange="zen('myTable').setProperty('parameters',2,zenThis.getValue())" />
<dateText label="End Date:" onchange="zen('myTable').setProperty('parameters',3,zenThis.getValue())" />
</fieldSet>
<tablePane id="myTable" OnCreateResultSet="CreateResultSet" valueColumn="ID">
<parameter value="" />
<parameter value="" />
<parameter value="" />
<column colName="ID" hidden="true" />
<column colName="Name" />
<column colName="SomeDate" />
<column colName="Toggleable" OnDrawCell="DrawToggleCell" />
</tablePane>
<button onclick="zenPage.Populate()" caption="Repopulate Data" />
</page>
}

ClassMethod CreateResultSet(Output pSC As %Status, pInfo As %ZEN.Auxiliary.QueryInfo) As %SQL.Statement
{
    Set nameFilter = pInfo.parms(1)
    Set startDateFilter = pInfo.parms(2) // Will be in ODBC format
    Set endDateFilter = pInfo.parms(3) // Will be in ODBC format
    Set query = ##class(%SQL.Statement).%New()
    Set query.%SelectMode = 1
    Set sql = "select ID, Name, SomeDate, Toggleable from DC_Demo.SampleData"
    Set conditions = ""
    If (nameFilter '= "") {
        Set conditions = conditions_$ListBuild("Name %STARTSWITH ?")
        Set parameters($i(parameters)) = nameFilter
    }
    If (startDateFilter '= "") && (endDateFilter '= "") {
        // Yes, this could just be independent AND'ed conditions on start/end date,
        // which would reduce code complexity, but you wanted to see BETWEEN, so... :)
        Set conditions = conditions_$ListBuild("SomeDate BETWEEN ? and ?")
        Set parameters($i(parameters)) = startDateFilter
        Set parameters($i(parameters)) = endDateFilter
    } ElseIf (startDateFilter '= "") {
        Set conditions = conditions_$ListBuild("SomeDate >= ?")
        Set parameters($i(parameters)) = startDateFilter
    } ElseIf (endDateFilter '= "") {
        Set conditions = conditions_$ListBuild("SomeDate <= ?")
        Set parameters($i(parameters)) = endDateFilter
    }
    If (conditions '= "") {
        Set sql = sql _ " where "_$ListToString(conditions," and ")
    }
    Set pSC = query.%Prepare(sql)
    If $$$ISERR(pSC) {
        Quit $$$NULLOREF
    }
    
    //Important: Reduce to only the parameters specified/used.
    Kill pInfo.parms
    Merge pInfo.parms = parameters
    Quit query
}

ClassMethod Populate() [ ZenMethod ]
{
    Do ##class(DC.Demo.SampleData).%KillExtent()
    Do ##class(DC.Demo.SampleData).Populate(20,,,,0)
    &js<zen('myTable').executeQuery();>
}

/// Note: this doesn't need to be a ZenMethod.
ClassMethod DrawToggleCell(pTable As %ZEN.Component.tablePane, pColName As %String, pSeed As %String) As %Status
{
    &html<<input type="checkbox" #($Case(%query("Toggleable"),1:"checked",:""))# onchange="zenPage.Toggle(#(..QuoteJS(%query("ID")))#,this.checked)" />>
    Quit $$$OK
}

ClassMethod Toggle(pID As %String, pValue As %Boolean) [ ZenMethod ]
{
    Set obj = ##class(DC.Demo.SampleData).%OpenId(pID,,.sc)
    $$$ThrowOnError(sc)
    Set obj.Toggleable = pValue
    $$$ThrowOnError(obj.%Save())
}

}

If the CSPGateway is configured properly you should certainly be able to make a REST request to the webserver rather than needing to use the private webserver port. Reading this again I think the issue isn't the header you saw but actually is mixed content - see e.g. https://www.howtogeek.com/443032/what-is-mixed-content-and-why-is-chrome...

In the browser developer tools this will show up as:

So you really need to make the request via https - which is what you said in the first place, of course! And the best way to do that will be via your IIS webserver, which might already be configured correctly, and (if it's not) can be configured using the instructions I linked to above.

Here's a quick sample:

Class DC.Demo.XDataDemo
{

ClassMethod Driver()
{
    Set array = ..GetXDataContents("DC.Demo")
    zw array
}

ClassMethod GetXDataContents(package As %String) As %Library.ArrayOfObjects
{
    $$$ThrowOnError($System.OBJ.GetPackageList(.classes,package))
    Set array = ##class(%Library.ArrayOfObjects).%New()
    Set class = ""
    For {
        Set class = $Order(classes(class))
        Quit:class=""
        Set xDataName = ""
        For {
            Set xDataName = $$$defMemberNext(class,$$$cCLASSxdata,xDataName)
            Quit:xDataName=""
            Set xdata = ##class(%Dictionary.XDataDefinition).IDKEYOpen(class,xDataName,,.sc)
            $$$ThrowOnError(sc)
            Do array.SetAt(xdata.Data,class_":"_xDataName)
        }
    }
    Quit array
}

XData Foo
{
}

XData Bar
{
}

}

Note that this gets XData blocks defined in a class; if you want to get "inherited" XData blocks listed for each subclass along with the inherited content it's only slightly more complex:

ClassMethod GetXDataContents(package As %String) As %Library.ArrayOfObjects
{
    $$$ThrowOnError($System.OBJ.GetPackageList(.classes,package))
    Set array = ##class(%Library.ArrayOfObjects).%New()
    Set class = ""
    For {
        Set class = $Order(classes(class))
        Quit:class=""
        Set xDataName = ""
        For {
            Set xDataName = $$$comMemberNext(class,$$$cCLASSxdata,xDataName)
            Quit:xDataName=""
            Set origin = $$$comMemberKeyGet(class,$$$cCLASSxdata,xDataName,$$$cXDATAorigin)
            Set xdata = ##class(%Dictionary.XDataDefinition).IDKEYOpen(origin,xDataName,,.sc)
            $$$ThrowOnError(sc)
            Do array.SetAt(xdata.Data,class_":"_xDataName)
        }
    }
    Quit array
}

First off, you should strongly consider using a production webserver, not the built-in one. On configuring the CSPGateway for this, see https://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY...

You might also need to investigate CORS settings - https://docs.intersystems.com/latest/csp/docbook/Doc.View.cls?KEY=GREST_... is helpful reading on this.

A ZenMethod is written in ObjectScript, not JavaScript, so that's why getValue() doesn't work. You can only access page components in a ClassMethod if you pass them in as arguments; otherwise, you need to make the method an instance method (that is, just Method readValues). Then you can use:

Set clinic = ..%GetComponentById('clinic').value

If you do need to do things in JavaScript in a ZenMethod, you can do them in an &js block - e.g.:

&js<alert(#(..QuoteJS(clinic))#);>

Which, in combination with the line above, would safely quote the string "clinic" for use in JavaScript (e.g., escaping quotes within the string), and this JavaScript will run on the clientafter the method returns. (That is, it isn't immediate; if you had a "hang 5" command after the &js block you wouldn't see an alert on the client until after the method ends.)

Instance methods in Zen are expensive because the whole page needs to be serialized and sent to the server (so that you can access and potentially modify all the components on the page). However, your method signature suggests that it's expecting a Zen proxyObject. You could build a Zen proxyObject with all of the form field values you care about in a JavaScript method on the client, and send that to the server by passing it to your ClassMethod, and that would be more efficient (if all you want to do is retrieve data from the form).

I generally don't use OnCreateResultSet, but here's a sample with it:

Class DC.Demo.ZenPage Extends %ZEN.Component.page
{

XData Contents [ XMLNamespace = "http://www.intersystems.com/zen" ]
{
<page xmlns="http://www.intersystems.com/zen">
<fieldSet legend="Filter" layout="horizontal">
<text label="Name Starts With:" onchange="zen('myTable').setProperty('parameters',1,zenThis.getValue())" />
<dateText label="Date:" onchange="zen('myTable').setProperty('parameters',2,zenThis.getValue())" />
<button onclick="zen('myTable').executeQuery()" caption="Filter" />
</fieldSet>
<tablePane id="myTable" OnCreateResultSet="CreateResultSet">
<parameter value="" />
<parameter value="" />
</tablePane>
<button onclick="zenPage.Populate()" caption="Repopulate Data" />
</page>
}

ClassMethod CreateResultSet(Output pSC As %Status, pInfo As %ZEN.Auxiliary.QueryInfo) As %ResultSet
{
    Set nameFilter = pInfo.parms(1)
    Set dateFilter = pInfo.parms(2) // Will be in ODBC format
    Set query = ##class(%ResultSet).%New()
    Set query.RuntimeMode = 1 // ODBC
    Set sql = "select Name, SomeDate from DC_Demo.SampleData"
    Set conditions = ""
    If (nameFilter '= "") {
        Set conditions = conditions_$ListBuild("Name %STARTSWITH ?")
        Set parameters($i(parameters)) = nameFilter
    }
    If (dateFilter '= "") {
        Set conditions = conditions_$ListBuild("SomeDate = ?")
        Set parameters($i(parameters)) = dateFilter
    }
    If (conditions '= "") {
        Set sql = sql _ " where "_$ListToString(conditions," and ")
    }
    Set pSC = query.Prepare(sql)
    If $$$ISERR(pSC) {
        Quit $$$NULLOREF
    }
    Set pSC = query.Execute(parameters...)
    If $$$ISERR(pSC) {
        Quit $$$NULLOREF
    }
    Quit query
}

ClassMethod Populate() [ ZenMethod ]
{
    Do ##class(DC.Demo.SampleData).%KillExtent()
    Do ##class(DC.Demo.SampleData).Populate(20,,,,0)
    &js<zen('myTable').executeQuery();>
}

}

And the data behind it (minus storage definition):

Class DC.Demo.SampleData Extends (%Persistent, %Populate)
{

Property Name As %String;

Property SomeDate As %Date;

}