go to post Timothy Leavitt · Jan 29, 2021 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.
go to post Timothy Leavitt · Jan 22, 2021 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()) } }
go to post Timothy Leavitt · Jan 13, 2021 For IIS, see https://docs.intersystems.com/latest/csp/docbook/Doc.View.cls?KEY=GCGI_w... - you need to set up a wildcard entry for the REST application's path.
go to post Timothy Leavitt · Jan 13, 2021 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.
go to post Timothy Leavitt · Jan 13, 2021 @Evgeny Shvarov I think in this context it's a request from the browser, not from the server (e.g., using %Net.HttpRequest)
go to post Timothy Leavitt · Jan 12, 2021 What are the file permissions? Does the "Effective user for Caché superserver and its jobs" have read permission?
go to post Timothy Leavitt · Jan 12, 2021 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 }
go to post Timothy Leavitt · Jan 12, 2021 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.
go to post Timothy Leavitt · Jan 11, 2021 HS.JSON.Path is intended for use with dynamic objects. With a dynamic object it'd look like (for example): SAMPLE>set obj = {"destination":{"endpoint":"foo"}} SAMPLE>w ##class(HS.JSON.Path).%Evaluate(obj,"$.destination.endpoint").%Get(0) foo Also, this should probably be a question rather than an announcement.
go to post Timothy Leavitt · Jan 8, 2021 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 client, after 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).
go to post Timothy Leavitt · Jan 7, 2021 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; }
go to post Timothy Leavitt · Jan 7, 2021 Note - it's generally better practice to usezenPage.getComponentById('comboboxEdit').getValue()or equivalently (and shorter), zen('comboboxEdit').getValue()
go to post Timothy Leavitt · Oct 19, 2020 Neat - I'm looking at EnsLogViewer and it seems like the "MultiType" class query is a good example of this. https://github.com/intersystems-ru/EnsLogViewer/blob/master/EnsPortal/Ev...
go to post Timothy Leavitt · Oct 16, 2020 I'm currently looking in to this and hope to have it fixed in short order. Thank you for letting us know. We apologize for the inconvenience and will let you know once the issue has been addressed.
go to post Timothy Leavitt · Oct 16, 2020 This issue has been resolved. Thank you again for notifying us.
go to post Timothy Leavitt · Sep 10, 2020 @Olga Zavrazhnova , it looks like we have a bad link at https://community.intersystems.com/post/global-masters-advocate-hub-star... (leading to the certificate error shown). @Kevin Johnson see https://intersystems.influitive.com/users/sign_in instead of the link in that article
go to post Timothy Leavitt · Aug 25, 2020 I'm intrigued to hear about expression indices - sounds really cool. Without those, another option is just to have a separate class/table. Suppose the key to the AR array is the address type (Home, Office, etc.); then you could have: Class Sample.Person1 Extends (%Persistent, %Populate) { Property Name As %String; Relationship Addresses As Sample.PersonAddress [ Cardinality = children, Inverse = Person ]; } Class Sample.PersonAddress Extends (%Persistent, %Populate) { Relationship Person As Sample.Person1 [ Cardinality = parent, Inverse = Addresses ]; Property Type As %String; Property Address As Sample.Address; } Sample.PersonAddress then can have whatever plain old normal indices you want (except bitmap indices - if you want those, make it one-to-many instead of parent/child). Generally: any time you add an array property - especially an array of objects - it's worth stepping back and thinking about whether it should just be its own full-blown class/table.
go to post Timothy Leavitt · Aug 25, 2020 I'll add, a query on this might look like: select distinct Person->ID, Person->Name from Sample.PersonAddress where Address_State = 'RI' and Type = 'Home' Note the "arrow syntax" for implicit joins.
go to post Timothy Leavitt · Aug 11, 2020 On IRIS there's $zu(209,code) - e.g.: USER>w $zu(209,3) <3> The system cannot find the path specified. In IRIS 2020.1+ you don't need the $zu: USER>w $System.Util.GetOSErrorText(3) <3> The system cannot find the path specified. AFAIK there's no Caché/Ensemble equivalent. (Or maybe this is in newer Caché/Ensemble versions.)
go to post Timothy Leavitt · Aug 6, 2020 I've had a few times where I've needed to do a targeted restore based on a journal (e.g., restoring a week of work an intern accidentally reverted; this would work for class definition changes if you could find the right window). Just to add to what Dmitriy and Erik have said, assuming your case is eligible, here's a code sample using the %SYS.Journal classes (modified from one of the times I had to do this): Class DC.Demo.JrnFix { /// Intended to be run from terminal. Find the right values to put in the variables at the top first. /// Also, use at your own risk. ClassMethod Run() { // Path to journal file (find this based on timestamps) Set file = "/path/to/journal/file" // Path to database containing data that was killed // (assuming killed during transaction so individual nodes are journalled as ZKILL) Set dbJrn = "/path/to/database/directory/" // First problem offset/address (find a real value for this via management portal or further // %SYS.Journal scripting - e.g., output from below with full range of addresses used) Set addStart = 0 // Last problem offset/address (find a real value for this via management portal or further // %SYS.Journal scripting - e.g., output from below with full range of addresses used) Set addEnd = 1000000000 // Global that you're looking to restore - as much of the global reference as is possible Set global = "MyApp.DataD" Set jrn = ##class(%SYS.Journal.File).%OpenId(file) #dim rec As %SYS.Journal.SetKillRecord TSTART Set rec = jrn.GetRecordAt(addEnd) Do { If ((rec.%IsA("%SYS.Journal.SetKillRecord"))&&(rec.DatabaseName=dbJrn)) { If (rec.GlobalNode [ global) { w rec.Address,! Set @rec.GlobalNode = rec.OldValue } Else { // Keep track of other globals we see (optional) Set skippedList($p(rec.GlobalNode,"(")) = "" } } Set rec = rec.Prev } While (rec.Address > addStart) ZWrite skippedList Break //At this point, examine things, TCOMMIT, and quit if things look good. TROLLBACK } }