go to post Michael Davidovich · 19 hr ago @Timothy Leavitt to the rescue! Thank you, this has been the most successful approach by far. I got this to work by defining a <script language='cache' runat='server'> block where within it I wrote a <script language='javascript'> block and then set my global variable there which I later user in my $(function() { . . . method. The only challenge is that since I was using write commands to write out a script within a script, it wouldn't let me simply w "</script>" at the end, so I had to kind of hack it and w "</"_script>" That said, I obviously designed this poorly and the payload coming back didn't scale up and it's making the webpage slow overall. This will get us up and working again, but I need to redesign this so it's more efficient. For what it's worth, I thought for this particular tool I was designing that I would just return a whole payload to the client that represented the configuration for this particular setting not knowing that it scales up very quickly. The idea was that instead of making calls to the server over and over, we would let them modify the whole configuration on the client and once it's done, send it back over. I guess fodder for another post!
go to post Michael Davidovich · Apr 18 Hi @Stephen Canzano and thank you for the reply! I essentially tried a similar approach doing this set stream = ##class(%Stream.TmpCharacter).%New() do myDynamicObject.%ToJSON(stream) Where now the whole dynamic object is set to stream. The problem comes when I send that stream back to the client. I need the data in that stream for a JavaScript function so I read from the stream into a variable on the server. However because it's so big, reading the stream into a variable to use in the JavaScript is causing the MAXSTRING error as well. I tried wrapping it in a REST API but under the hood in the %Rest.Impl class the %WriteResponse is just calling a %ToJSON on the dynamic object which is causing the error. It doesn't seem to be handling streams or if it does, it still wants to get my dynamic object into a JSON string but it's just too big. The data does change over time, so I don't think the JSON adaptor is the right approach. Thanks for any additional advice!
go to post Michael Davidovich · Apr 11 @Ali Nasser digging into this more this morning I am trying to determine the best use cases be between SQLProcs and Calculated data. There are some obvious differences like do you want something to be calculated when saved or updated. But in general since a lot of my data exists, it's going to be calculating on the fly (I suppose I could write a script to save/update all the existing records for force a calculated field). I start to wonder though, are calculated fields and SQLProcs savinging me query performance or am I just offloading my post processing into the query itself? Does that make sense? The reality is that our swizzled references will knock us down a few pegs no matter how I configure it but I do hope wrapping it all in the query can be more efficient. I have a feeling we'll see some gains from the filtering and sorting end since I'll be able to simply select this, that, this and that and then the filters and sorts are working with that table data already calculated.
go to post Michael Davidovich · Apr 10 @Ali Nasser thank you so much for this thoughtful response! - I did see the new LIMIT and OFFSET SQL functionality, but we aren't yet on 2025 but we are soon to be there. - Thanks for the reference to swizzling - this is indeed I think what tends to slow things down. - I did not know you could compute properties like that! I will have to experiment with this. I can see it working for many properties. It does become a bit cumbersome in the class definition as they get larger and larger. I think we could use a mix of the triggers and the query runtime calcs, because some things static but others might changed based on other data. - Ok I admit I thought twice when I said you can't call methods from the SQL queries and I do recall seeing SQLProc keyword documentation and I think I need to dig into this more along with the calculated data. - My ancedetoal though on base query plus ObjectScript was that the base query would run REALLY quick to just return a base set of ObjectIds that is the key for all the ObjectScript processing. I just did try this approach and you're right, it does take a lot more processing. - Since we are replicating the DataTables client side behavior, yes, we are trying to search the whole row for "Sally" in that example. Right now I'm hacking this with a concatenated string in my ObjectScript processing and a search text, but it sounds like full-text search is a specialty and more delicate kind of processing that I shouldn't try to hack like I am. We use a LOT of embedded SQL in our application and I have had to move to dynamic to make this work, however since dynamic queries aren't cached, I guess I thought they were less performant. But compared to a base embedded query with additional ObjectScript processing, I think you have given me some ideas to go back and make this work with Dynamic SQL without any additional COS processing. Thank you for your help here! How wonderful!
go to post Michael Davidovich · Dec 17, 2024 I'm not sure this is the answer you're looking for, but I would loop through the result set and store the data in a local array and then loop through the array and output the data with the calculated column set as you wish. The array would allow you to loop through each ID to find the latest to mark as new. This is an ObjectScript approach along with SQL. I wonder if you could just re-save the data back to the table once calculated so you can still project it as needed to other systems.
go to post Michael Davidovich · Dec 13, 2024 I think the answer is you don't implement AccessCheck() in an implementation class. Looking at some documentation examples, classes that extend %REST.impl don't extend %CSP.REST and the latter is where AccessCheck() exists and is called. I think to get closer to what I want you'd forward your calls to the dispatch class to a lower level dispatch class which may call %CSP.REST.Page() (I haven't tested this yet). But my goal was implementation level access checks because we have row level security in a multi tenant database. Since often you'd find a object ID in a URL (/csp/testApp/coffeePots/:coffeePotId) and, in my little example here, coffeePotId is most easily accessible in the method implementation itself, the only way to check the access to that ID at a higher level would be to do some tricky URL parsing that the %CSP.REST methods already do so well behind the scenes. My solution was to create a XData block of all the mappings from objectClass -> HTTP Method -> associatedPermission (e.g. CoffeePots -> POST -> Can create coffee pot) and then use that in a generic method that can be called in each implementation to check permissions. On another note, you don't want me writing a best REST API practices article for the next ISC article contest, lol.
go to post Michael Davidovich · Dec 11, 2024 The closest I've gotten here is to use OnPreDispatch() to hack the flow and call the ImplementationClass.Page() method but this opens a whole can of worms.
go to post Michael Davidovich · Dec 11, 2024 @Eduard Lebedyuk thank you! I'm not sure that will work: even if I extend my implementation class with my dispatch class, the implementation class doesn't seem to fire off the Page() method which would call AccessCheck(). So Adding the ##SUPER() call into my subclassed AccessCheck() won't work because the subclass Page() and thus AccessCheck() methods are never called. I think I'm trying to do something this wasn't designed to do? My dispatch class extends %CSP.REST which implements AccessCheck() and then forwards to an implementation class which also extends %CSP.REST. I don't see why the forward wouldn't call the %CSP.REST.Page() method but it's hard to track in the %CSP package. Am I understanding your suggestion on how to user ##SUPER() here? Is there a way to call common code in a %RegisteredObject? Like an OnBefore method (I don't see anything like that but maybe it happens a different way). I think my issue is that Page() dispatches the request by calling the implementation method directly so any %CSP.REST or %CSP.Page functions aren't triggered. I could add a middle layer to the dispatch and the implementation but then I still have the same problem: I want lower level access checks in the implementation method to trigger every time without having to call the access check in each method.
go to post Michael Davidovich · Dec 11, 2024 It appears once a request is dispatched, that's really it. The rest of the URL seems to parsed out with the arguments and the correct endpoint with args is called. It seems the convention is that AccessCheck is only called ONCE from the dispatch class. I can't seem to find clever way to get around this. It would be more helpful to override AccessCheck at the forwarded routes because then there's less parsing of URL to determine where I'm at and what's going on and what permissions I need to check and so on an so forth.
go to post Michael Davidovich · Aug 2, 2024 There are lots of articles in the DC and in the documentation about error handling and I have a few of them bookmarked, but for the most part I always throw $$$GeneralError (5001). Using this tool and asking about when I should or shouldn't use this helped me learn that there are more buckets to throw custom errors under. Plus the link to the source article gives even more info on registering custom error codes (I've never seen this article, so glad this tool pointed me to it). https://community.intersystems.com/ask-dc-ai?question_id=132835
go to post Michael Davidovich · Jul 23, 2024 I appreciated this video as yet another resource for learning. Please do more! The use case for the namespaces presented here seemed fringe to me. If you need to simulate an FTP server, why not spin up a small container that does so. Or are you talking about one HealthShare system to another HealthShare system? The common use for namespaces I've seen is separating dev, test, qa and production environments. I would personally like to understand better using namespaces to separate code versus data. Maybe this gets into the video's early point that one must understand the definitions. I guess I was taught to think of a namespace as a database, but when you see the following setting comes up in installation manifests it starts to get confusing because you have a code database and a data database in the same namespace and in this example I use the namespace as as the database name. Maybe I'm just not understanding it at all! <Namespace Name="${NAMESPACE}" Code="${NAMESPACE}" Data="${NAMESPACE}" Create="yes" Ensemble="1"> Thanks for sharing this video!
go to post Michael Davidovich · Apr 24, 2024 @Robert Cemper Thanks so much! Glad I'm not alone in the struggle! So obviously there's good reason behind LineTerminator being a property than can be changed and adapted and we have to continue to think of how to handle them as we consume files. You start to understand why so many other data formats beyond text files!
go to post Michael Davidovich · Apr 10, 2024 From the WRC: I spoke to the developer and he said it's a bug . . . there is an existing project to clean up a bunch of code in this area. I'll ask them when it will be finished and in what version the code will appear.
go to post Michael Davidovich · Apr 10, 2024 Also here's an interesting session from GS 2022 that you might find helpful to target a method for your use case: https://youtu.be/CQqXkuWkHiY?si=Fiug-X54uFRku_7U
go to post Michael Davidovich · Apr 10, 2024 I know this isn't helpful but it knocks one solution OFF the list. I thought you could use Outputs in your method signature but this isn't supported with Python methods: While passing arguments by reference is a feature of ObjectScript methods, there is no equivalent way to pass arguments by reference to a method written in Python. The ByRef and Output keywords in the signature of an ObjectScript method are conventions used to indicate to the user that the method expects that an argument is to be passed by reference. In fact, ByRef and Output have no actual function and are ignored by the compiler. Adding ByRef or Output to the signature of a method written in Python results in a compiler error. https://docs.intersystems.com/iris20241/csp/docbook/DocBook.UI.Page.cls?... From your example, I think you'd want to fire up the Python shell from the COS command line and import the iris package and call your function with the normal multiple return value syntax. If you import the iris package you can then save the output to a global or pass it to another COS function that you need it for.
go to post Michael Davidovich · Mar 26, 2024 Keep this page bookmarked at all times and reference often, even if you think you know a command: https://docs.intersystems.com/iris20241/csp/docbook/DocBook.UI.Page.cls?... If you can get to know all these commands intimately, you'll have a deep understand of library classes and methods and also INT and MAC code.
go to post Michael Davidovich · Mar 22, 2024 You can also treat each integer as a string. This would work if your list was small but would be annoying if you had a larger list. It's probably better suited for text strings and not integers treated as strings, but it is an option! USER>for y="1","4","6","8","9","12" { w y,! } 1 4 6 8 9 12
go to post Michael Davidovich · Mar 22, 2024 Further inspection reveals ..ATIndexOpen is an 'Index Open' generated method. So it's trying to open some object with the ATIndex of that encrypted token. Not sure how the that all connects . . .
go to post Michael Davidovich · Mar 22, 2024 Thanks, @Ron Sweeney Yes, I did put the token in the debugger and confirm only once scope is in the token and made sure that I copied and pasted that in the ValidateJWT() method so I didn't fat finger. So the scope is indeed there. My workaround is to not try to validate the scope within the library methods and just pull out the scope from the body and validate in a for loop.