go to post Michael Davidovich · Dec 17 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 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 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 @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 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 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 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 @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 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 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 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 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 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 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 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.
go to post Michael Davidovich · Mar 21 My other thought is that I have to define an auth server configuration but I didn't think that was necessary since we use Okta and just call their APIs as needed.
go to post Michael Davidovich · Feb 6 @Alex Baumberg are you running IRIS on a Docker container or is it installed locally on your machine? Check out this section: https://intersystems-community.github.io/vscode-objectscript/configurati... Either way the setup should be quite the same. Can someone confirm for client-side editing if the local source code folder needs a bind-mount to the Docker container and to to where it should be mapped? You might try using this to get started as another option: https://github.com/intersystems-community/objectscript-docker-template For what it's worth, I'm a few years into this and I'm still constantly confused :)
go to post Michael Davidovich · Oct 16, 2023 When you do get to 2021.2 check out this: https://docs.intersystems.com/iris20232/csp/docbook/DocBook.UI.Page.cls?...
go to post Michael Davidovich · Oct 9, 2023 @Gertjan Klein thanks for your help and sorry for my delayed response. I did not realize the message body is not set if there’s an error. This explains a lot. I was previously doing a try/catch and trying to set the %Status error text into the StringContainter response and frustrated that it wasn’t there. I think I’m trying to overdo what Ensemble already does for free. I also think I need to keep a response a response and keep errors as errors and not try to combine them. But thank you for your method here to get the header ID. Since as you say it's not trivial, it makes me think it's unconventional and again I'm trying to use the system in ways it wasn't really designed for. However, it's handy to understand how this works. Thanks for helping me think through this!