go to post Timothy Leavitt · Sep 5, 2019 Other answers are good. I'll also add, if you're running from Terminal, you can go to File > Logging... and have all the output logged to a file. (This works for other things that produce output too, not just CompileAll.)
go to post Timothy Leavitt · Sep 5, 2019 Fun fact, you can also order by the column by number.In this case:select top 5 Description,Category,* from Cinema.Film order by 2
go to post Timothy Leavitt · Sep 4, 2019 With correct web server configuration to route everything through the CSPGateway, the above example should handle URLs for other resources like that without issue (as long as the content served by the dashboard server has relative links to those endpoints, not absolute) - that's the point of the <base> element.In my sample use case, it also handles several requests for images and other assets.
go to post Timothy Leavitt · Sep 4, 2019 I don't think it's totally stupid. We're looking at doing something similar with another technology (JReport).Here's a code sample to help you get started - just change the port / page / filters / permission checks appropriately. Class Demo.CSPProxy Extends %CSP.Page { Parameter HOST = "127.0.0.1"; Parameter PORT = 8888; Parameter PAGE = "jinfonet/runReport.jsp"; /// Event handler for <b>PreHTTP</b> event: this is invoked before /// the HTTP headers for a CSP page have been sent. All changes to the /// <class>%CSP.Response</class> class, such as adding cookies, HTTP headers, /// setting the content type etc. must be made from within the OnPreHTTP() method. /// Also changes to the state of the CSP application such as changing /// %session.EndSession or %session.AppTimeout must be made within the OnPreHTTP() method. /// It is prefered that changes to %session.Preserve are also made in the OnPreHTTP() method /// as this is more efficient, although it is supported in any section of the page. /// Return <b>0</b> to prevent <method>OnPage</method> from being called. ClassMethod OnPreHTTP() As %Boolean [ ServerOnly = 1 ] { Set request = ##class(%Net.HttpRequest).%New() Set request.Server = ..#HOST Set request.Port = ..#PORT // TODO: Add other stuff here, like authentication. Set page = $Piece(%request.CgiEnvs("REQUEST_URI"),"/"_$classname()_".cls/",2) If (page = "") { Set %base = $classname()_".cls/"_$Piece(..#PAGE,"/",1,*-1)_"/" // TODO: add query parameters from %request to the URL requested below. $$$ThrowOnError(request.Get(..#PAGE)) } Else { Set fullPage = "http://"_..#HOST_":"_..#PORT_"/"_page Do ##class(%Net.URLParser).Parse(fullPage,.parts) // TODO: Better way of checking the requested resource. If $Piece($Piece(parts("path"),"/",*),".",2) = "jsp" { Set %response.Status = ##class(%CSP.REST).#HTTP403FORBIDDEN Quit 0 } $$$ThrowOnError(request.Send(%request.Method,page)) } Set %data = request.HttpResponse.Data // TODO: Do any other headers matter? Set %response.Status = request.HttpResponse.StatusCode Set %response.ContentType = request.HttpResponse.ContentType Quit 1 } /// Event handler for <b>PAGE</b> event: this is invoked in order to /// generate the content of a csp page. ClassMethod OnPage() As %Status [ ServerOnly = 1 ] { If (%response.ContentType [ "html") && $Data(%base) { &html<<base href="#(..EscapeHTML(%base))#">> Do %data.OutputToDevice() } Else { Do %data.OutputToDevice() } Quit $$$OK } }
go to post Timothy Leavitt · Apr 1, 2019 A few weird things with custom login pages:CSPSystem needs read permission on the DB containing them. (You'll need to close CSPGateway connections after making this change so that it can reconnect with the right privileges.)It's specified with the ".cls" extension in the web application configuration, not just the classname.
go to post Timothy Leavitt · Mar 12, 2019 In my experience, code coverage measurement is useful even in development. It helps identify areas that are untested as you're writing tests.
go to post Timothy Leavitt · Mar 8, 2019 Hi Robert,Access rights are part of my concern. You can connect using an SSL/TLS configuration without having read permission on %DB_CACHESYS (or the IRIS equivalent).
go to post Timothy Leavitt · Jan 15, 2019 You should check the SQLCODE from running that statement. If SQLCODE is 100 (no record exists with the specified key), KeyID may not be defined.See: https://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=GSQL_esql#GSQL_esql_hvars
go to post Timothy Leavitt · Jan 14, 2019 This sounds like it could be an issue with caching (at the browser level rather than at the CSPGateway level, given that the page loads just fine in a different browser). Perhaps try clearing the cache in the Android browser then reloading the page?
go to post Timothy Leavitt · Jan 3, 2019 Generally speaking, I'd design this as a relationship to a set of "roles" that can be added or removed. (What if the person ceases to be a Doctor?) Methods of the person would dispatch to the roles.
go to post Timothy Leavitt · Jan 3, 2019 "Why?" (a good question) and "Don't do that!" aside, here's a technical answer: override %OnDetermineClass to allow instances of the parent class to be treated as the subclass in question. For example: Class DC.Demo.Extension.BaseRecord Extends %Persistent { Property Name As %String; ClassMethod %OnDetermineClass(oid As %ObjectIdentity, ByRef class As %String) As %Status [ ServerOnly = 1 ] { Set tSC = ##super(oid,.class) If (class = "DC.Demo.Extension.BaseRecord") { Set class = $classname() } Quit tSC } Storage Default { <Data name="BaseRecordDefaultData"> <Value name="1"> <Value>%%CLASSNAME</Value> </Value> <Value name="2"> <Value>Name</Value> </Value> </Data> <DataLocation>^DC.Demo.Extension.BaseRecordD</DataLocation> <DefaultData>BaseRecordDefaultData</DefaultData> <IdLocation>^DC.Demo.Extension.BaseRecordD</IdLocation> <IndexLocation>^DC.Demo.Extension.BaseRecordI</IndexLocation> <StreamLocation>^DC.Demo.Extension.BaseRecordS</StreamLocation> <Type>%Library.CacheStorage</Type> } } Class DC.Demo.Extension.SubRecord Extends BaseRecord { Property Foo As %String; Storage Default { <Data name="SubRecordDefaultData"> <Subscript>"SubRecord"</Subscript> <Value name="1"> <Value>Foo</Value> </Value> </Data> <DefaultData>SubRecordDefaultData</DefaultData> <Type>%Library.CacheStorage</Type> } } Class DC.Demo.Extension.Driver { ClassMethod Run() { Do ##class(DC.Demo.Extension.BaseRecord).%KillExtent() Set tBaseRecord = ##class(DC.Demo.Extension.BaseRecord).%New() Set tBaseRecord.Name = "Fred" Write !,"Save base record: ",tBaseRecord.%Save() Write !,"Contents of global ^DC.Demo.Extension.BaseRecordD:",! zw ^DC.Demo.Extension.BaseRecordD Set tSubRecord = ##class(DC.Demo.Extension.SubRecord).%OpenId(1) Set tSubRecord.Foo = "Bar" Write !,"Open as sub record, name = ",tSubRecord.Name Write !,"Save sub record (converts to sub record): ",tSubRecord.%Save() Write !,"Contents of global ^DC.Demo.Extension.BaseRecordD:",! zw ^DC.Demo.Extension.BaseRecordD } } Output is: USER>d ##class(DC.Demo.Extension.Driver).Run() Save base record: 1 Contents of global ^DC.Demo.Extension.BaseRecordD: ^DC.Demo.Extension.BaseRecordD=1 ^DC.Demo.Extension.BaseRecordD(1)=$lb("","Fred") Open as sub record, name = Fred Save sub record (converts to sub record): 1 Contents of global ^DC.Demo.Extension.BaseRecordD: ^DC.Demo.Extension.BaseRecordD=1 ^DC.Demo.Extension.BaseRecordD(1)=$lb("~DC.Demo.Extension.SubRecord~","Fred") ^DC.Demo.Extension.BaseRecordD(1,"SubRecord")=$lb("Bar")
go to post Timothy Leavitt · Jan 2, 2019 My top place by day was 242 on day 8 (that was the only day that I actually did the puzzle at midnight though - I'm not at all a night owl).My three wishes for ObjectScript would be:A greater variety of high-performance data structures with low-level support (rather than shoehorning everything into a local array/PPG/$ListBuild list to keep it wicked fast, or building more expensive objects that do exactly what I want)Libraries for working with those (and existing) data structures (or, better yet, an OO approach to everything, similar to %Library.DynamicArray/%Library.DynamicObject)Functional programming capabilities
go to post Timothy Leavitt · Dec 6, 2018 On IRIS, you can call irisdb.exe the same way you used to call cache.exe.Compare:https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls... (IRIS)https://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY... (Caché/Ensemble)
go to post Timothy Leavitt · Nov 16, 2018 The %System/%System/RoutineChange audit event is the only thing other than a source control hook that comes to mind. That's handy for detecting source code changes after the fact (when a class is compiled), but not intercepting attempts to change source code via an editor. What's your specific use case?
go to post Timothy Leavitt · Jul 30, 2018 This isn't the answer to your actual question, but it's worth pointing out ##class(%Library.File).ManagerDirectory() - instead of referencing the global, you could use: ##class(%Library.File).ManagerDirectory()_"LDAPKeyStore/" Also, ##class(%SYS.System).GetInstanceName() returns the instance name; you shouldn't need to set that in a global, and (furthermore) the instance name isn't necessarily part of the path to the mgr directory (the two can be configured independently). Other than that I think you'd be stuck with using XECUTE or $Xecute to do what you originally suggested.
go to post Timothy Leavitt · Jul 12, 2018 After further review, I'm really not sure why the first/second queries don't use the index. The problem with the last query is that %Key is the index in the list, not anything about Tag itself.Here's a solution that performs well in my testing: Class DC.Demo.Tag Extends (%Persistent, %Populate) { Index Tag On Tag [ Unique ]; Property Tag As %String; } Class DC.Demo.Tagged Extends (%Persistent, %Populate) { Relationship HasTags As DC.Demo.HasTag [ Cardinality = children, Inverse = Tagged ]; ClassMethod Run() { Do ..%KillExtent() Do ##class(DC.Demo.Tag).%KillExtent() Do ##class(DC.Demo.Tag).Populate(50) Do ..Populate(5000) Do ##class(DC.Demo.HasTag).Populate(10000) } } Class DC.Demo.HasTag Extends (%Persistent, %Populate) { Relationship Tagged As DC.Demo.Tagged [ Cardinality = parent, Inverse = HasTags ]; Property Tag As DC.Demo.Tag [ Required ]; Index UniqueTag On Tag [ IdKey ]; Index TaggedByTag On (Tag, Tagged); }
go to post Timothy Leavitt · Jul 12, 2018 Not a full answer, but re: the last index, did you call %BuildIndices after adding it?
go to post Timothy Leavitt · Jun 13, 2018 It's not the prettiest, but I think the simplest solution would be to avoid navigating to the parent object entirely:Add a Foobar property to EmbedObj.Via a row/object trigger in ContainerObj, propagate changes to Foobar to EmbedObj_Foobar.As an initial step for data population in existing records, run SQL: update ContainerObj set EmbedObj_Foobar = FoobarBase your SQLComputeCode on the copy of Foobar in the serial class.
go to post Timothy Leavitt · Jun 12, 2018 Re: extending method keywords, you can't do that at this time, but a useful approximation is structuring a comment - for example: /// @MyKeyword MyValue ClassMethod TestOne() { // Implementation } And then looking at the %Dictionary.MethodDefinition / %Dictionary.CompiledMethod documentation in a generator method. (But it looks like you might already be on to that with @AutoGenerated.) Re: making first compilation work, this works for me, by making the first compilation automatically trigger a second one when needed: ClassMethod GenerateMethods() As %Status [ CodeMode = objectgenerator ] { For i=1:1:%class.Methods.Count() { #dim method As %Dictionary.MethodDefinition = %class.Methods.GetAt(i) Continue:((method.Description["@AutoGenerated") || (method.CodeMode="objectgenerator")) Do %class.Methods.Insert(##class(util.TestGenerator).Generate(method.Name_"DoSomethingElse",%class.Name)) } If (%class.Methods.%IsModified()) { Do ##class(%Projection.AbstractProjection).QueueClass(%class.Name) } Quit %class.%Save() } Test code: Class util.Driver { ClassMethod Run() { do ##class(%Dictionary.MethodDefinition).IDKEYDelete("util.Test","TestOneDoSomethingElse") do ##class(%Dictionary.MethodDefinition).IDKEYDelete("util.Test","TestTwoDoSomethingElse") do $system.OBJ.UnCompile("util.*") do $system.OBJ.Compile("util.*","ck") } }
go to post Timothy Leavitt · Jun 8, 2018 If you want to count (or otherwise traverse) all the elements in a multidimensional array, you can use $Query - here's a sample with method that does that: ClassMethod Run() { Set a(1) = "blue" Set a(1,3,5,2) = "navy" Set a(2) = "red" Set a(2,2) = "crimson" Set a(3) = "yellow" Write ..Count(.a) } ClassMethod Count(ByRef pArray) As %Integer [ PublicList = pArray ] { Set count = 0 Set ref = $Query(pArray("")) While (ref '= "") { Set count = count + 1 Set ref = $Query(@ref) } Quit count }