go to post Theo Stolker · Sep 23 This primarily went wrong because I had the "Use JWT Authentication" checkbox checked...
go to post Theo Stolker · Aug 13 Hi @John Murray, I was able to fix this in a dev environment bij adding the %All role to the /terminalsocket web applicationand adding Public Read Permission on the DB permission for the database that has the webterminal package. No changes were necessary to the CSPSystem user or anything.I am not sure how this compares in terms of security, but it works for a dev environment or sandbox.
go to post Theo Stolker · Jun 4 I just made an important change to the sample code for the NameValues class. By extending from %Persistent before extending ChangeLog.DB.ChangeLogWriter, you can avoid that tables ChangeLog_DB.ChangeLogWriter has records with just an id for al classes that inherit from it: ```Class ChangeLog.DB.NameValues Extends (%Persistent, ChangeLog.DB.ChangeLogWriter) ```
go to post Theo Stolker · Jun 4 Include EnsConstants /// Get Settings from Production and Create DefaultSettings Class CONNECTORSPKG.Utility.DefaultSettings { /// List productions ClassMethod ListProductions(print As %Boolean = 1, namespace As %String = {$NAMESPACE}) As %String { new $NAMESPACE set $NAMESPACE = namespace set production = "" if print { write "Productions in namespace " _ namespace _ ":",! } set prdRS = ##class(%ResultSet).%New("Ens.Config.Production:ProductionStatus") do prdRS.Execute() while (prdRS.Next()) { set status = prdRS.Data("Status") if status = "Running" { set production = prdRS.Data("Production") } if print { write prdRS.Data("Production")," (",status,")",! } } return production } /// Export to settingsfile ClassMethod Export() As %Status { return ##class(Ens.Config.DefaultSettings).%Export("/home/irisowner/systemdefaults/Ens.Config.DefaultSettings.esd", "MONDRIAAN.FoundationProduction") } /// Export from settingsfile ClassMethod Import() As %Status { set sc = ##class(Ens.Config.DefaultSettings).%Import("/home/irisowner/systemdefaults/Ens.Config.DefaultSettings.esd", .count, .idsimported) write "Imported ",count," settings",! return sc } /// Connectors action ClassMethod SettingsFromProduction(removeFromProduction As %Boolean = 0, updateSettings As %Boolean = 1) As %Status { return ..GetSettingsFromProduction("CONNECTORSPKG.FoundationProduction", "*:HTTPServer,SSLConfig;CONNECTORSPKG.BO.GenericHTTP.Operation:HttpMethod,AcceptHeader,ContentType,URL,AuthorizationType,Credentials,CustomAuthorizationHeader,AlertOnError,ReplyCodeActions", removeFromProduction, updateSettings) } /// Get settings From Production ClassMethod GetSettingsFromProduction(production As %String = {..ListProductions(0, $NAMESPACE)}, filter As %String = "", removeFromProduction As %Boolean = 0, updateSettings As %Boolean = 1) As %Status { write "Settings in production '",production _ "':",! set xdataId = production _ "||ProductionDefinition" set xdata = ##class(%Dictionary.XDataDefinition).%OpenId(xdataId, , .sc) if $$$ISERR(sc) { write "Failed to open XData block '" _ xdataId,"': ",$SYSTEM.Status.GetErrorText(sc),! return sc } set tProduction = ##class(Ens.Config.Production).%OpenId(production,,.sc) if $$$ISERR(sc) { write "Failed to open production '",production,"': ",$SYSTEM.Status.GetErrorText(sc),"; settings will not be removed from the production",! set removeFromProduction = 0 } set sc = ##class(%XML.TextReader).ParseStream(xdata.Data, .textreader) #dim textreader as %XML.TextReader if $$$ISERR(sc) { write "Failed to parse XData block '" _xdataId,"': ",$SYSTEM.Status.GetErrorText(sc),! return sc } set currentItemName = "" set currentItemClass = "" #dim tRemovedItemCount as %Integer = 0 while textreader.Read() { if (textreader.NodeType '= "element") { continue } if (textreader.LocalName = "Item") { if (textreader.MoveToAttributeName("Name")) { set currentItemName = textreader.Value } if (textreader.MoveToAttributeName("ClassName")) { set currentItemClass = textreader.Value } #; write "Found Item with Name='" _ currentItemName _ "' of class '" _ currentItemClass _ "':",! continue } if (textreader.LocalName = "Setting") { do ..HandleXDataSetting(textreader, tProduction, currentItemName, currentItemClass, filter, removeFromProduction, updateSettings, .tRemovedItemCount) } } if tRemovedItemCount > 0 { write "Removed ",tRemovedItemCount," settings; now saving Production '",production,"':" return ..SaveProduction(tProduction) } return $$$OK } /// Hanlde setting found in the XData ClassMethod HandleXDataSetting(textreader As %XML.TextReader, tProduction As Ens.Config.Production, currentItemName As %String, currentItemClass As %String, filter As %String, removeFromProduction As %Boolean, updateSettings As %Boolean, ByRef tRemovedItemCount As %Integer) { if (textreader.MoveToAttributeName("Target")) { set target = textreader.Value } if (textreader.MoveToAttributeName("Name")) { set name = textreader.Value } do textreader.Read() // Read value set value = textreader.Value #; write "Found Setting Target=",target,", name=",name,", Value=",value,! if ..InFilter(filter, currentItemName, currentItemClass, target, name, value) { if updateSettings && $$$ISERR(..HandleSetting(tProduction.Name, currentItemName, currentItemClass, target, name, value)) { return } if removeFromProduction && ..RemoveSettingFromProduction(tProduction, currentItemName, target, name) { set tRemovedItemCount = tRemovedItemCount + 1 } } } /// SaveProduction ClassMethod SaveProduction(tProduction As Ens.Config.Production) As %Status { // Save the changes we made to the production set sc = tProduction.%Save(1) if $$$ISERR(sc) { write " failed: ",$SYSTEM.Status.GetErrorText(sc),! return sc } write ! // Regenerate the XData in the corresponding class write "Save Production XData: " Set sc = tProduction.SaveToClass() if $$$ISERR(sc) { write " failed: ",$SYSTEM.Status.GetErrorText(sc),! return sc } write ! // Grab the state of the production set sc = ##class(Ens.Director).GetProductionStatus(.tRunningProduction, .tState) if $$$ISERR(sc) { write "Failed to get Production status: ",$SYSTEM.Status.GetErrorText(sc),! return sc } // Finally, does the production need updating? if (tRunningProduction = tProduction.Name) && (tState = $$$eProductionStateRunning) { // Update the running production with the new settings write "Update running production: " set sc = ##class(Ens.Director).UpdateProduction(##class(Ens.Director).GetRunningProductionUpdateTimeout()) if $$$ISERR(sc) { write " failed: ",$SYSTEM.Status.GetErrorText(sc),! return sc } } return $$$OK } /// Determine if the setting is in the Filter ClassMethod InFilter(filter As %String, itemName As %String, class As %String, target As %String, varName As %String, value As %String) As %Boolean { if filter = "" { return 1 } for classIndex = 1:1:$LENGTH(filter, ";") { set classPart = $PIECE(filter, ";", classIndex) set configClass = $PIECE(classPart, ":", 1) if (configClass '= "*") && (configClass '= class) { continue } set vars = $PIECE(classPart, ":", 2) for varIndex = 1:1:$LENGTH(vars, ",") { if $PIECE(vars, ",", varIndex) = varName { return 1 } } } return 0 } /// Handle Setting ClassMethod HandleSetting(production As %String, item As %String, class As %String, target As %String, varName As %String, value As %String) As %Status { if ##class(Ens.Config.DefaultSettings).IdKeyExists(production, item, class, varName, .id) { set defaultSetting = ##class(Ens.Config.DefaultSettings).%OpenId(id, , .sc) if $$$ISERR(sc) { write "Failed to get default setting named '",item,":",varName,"': ",$SYSTEM.Status.GetErrorText(sc),! return sc } if defaultSetting.SettingValue = value { write "Skipping '",item,":",varName,"' as it already exists with the same value",! return $$$OK } write "Updating '",item,":",varName,"' from '",defaultSetting.SettingValue,"' to '",value,"'",! } else { set defaultSetting = ##class(Ens.Config.DefaultSettings).%New() set defaultSetting.ProductionName = production set defaultSetting.ItemName = item set defaultSetting.HostClassName = class set defaultSetting.SettingName = varName set defaultSetting.Deployable = 1 write "Creating '",item,":",varName,"' from '",defaultSetting.SettingValue,"' with value '",value,"'",! } set defaultSetting.SettingValue = value set sc = defaultSetting.%Save() if $$$ISERR(sc) { write "Failed setting default setting named '",item,":",varName,"' to '",value,"': ",$SYSTEM.Status.GetErrorText(sc),! } return sc } /// Rmove Setting from production ClassMethod RemoveSettingFromProduction(tProduction As Ens.Config.Production, item As %String, target As %String, varName As %String) As %Boolean { #dim tItemObj as Ens.Config.Item = tProduction.FindItemByConfigName(item, .sc, 1) if '$IsObject(tItemObj) { write "Failed to get item '",item,"': ",$SYSTEM.Status.GetErrorText(sc),! return 0 } for i = 1:1:tItemObj.Settings.Count() { #dim tSetting As Ens.Config.Setting = tItemObj.Settings.GetAt(i) if (tSetting.Name = varName) && (tSetting.Target = target) { do tItemObj.Settings.RemoveAt(i, .success) return success } } return 0 } }
go to post Theo Stolker · Feb 20 Hi @Evgeny Shvarov , Currently I am a busy with a couple of customer projects, but when I find the time I will publish it as an Open Exchange / IPM project Thanks, Theo
go to post Theo Stolker · Feb 1 Thanks @Tani Frankel! I suspect the Support guidance is for when it is used in combination with HealthShare ODS production instances. When you run a local IRIS FHIR Repository as a developer, I believe there is no harm in using this. So the trick I was looking for is: set ^HS.FHIRServer("dev") = 1 After refreshing the fhirconfig page, the new tile pops up:
go to post Theo Stolker · Jan 24 @Evgeny Shvarov , one way to expose your local FHIR Endpoint would be through https://ngrok.com/use-cases/ingress-for-dev-test-environments Let me know if you have questions, Theo.
go to post Theo Stolker · Nov 28, 2023 The class ChangeLog.DB.PersistentWithChangeLog has just been changed. We had an issue with objects being part of the new data json, in which case %ToJSON() would fail. The code is now more robust.
go to post Theo Stolker · Sep 28, 2023 I found the issue, it has to do with the fact that the default lineTerminator for a stream on windows is CRLF, even inside docker. After applying https://github.com/lscalese/openapi-common-lib/pull/1 the API works like a charm. See https://github.com/lscalese/openapi-suite/issues/8
go to post Theo Stolker · Sep 27, 2023 Hi @Lorenzo Scalese, I am applying openapi suite on the APIs published on https://nuts-node.readthedocs.io/en/stable/pages/integrating/api.html, and the result is disappointing, as no operations and/or messages have been generated. For the 7 openAPI specifications, the generated package is identical and quite empty. So to me this is not helpful. Although a minor point, even the servers-property from the openapi specification (url: http://localhost:1323) is being replaced with the location of the yaml file. Why? I would like to discuss what we can do to make this better!
go to post Theo Stolker · Aug 24, 2023 Hi André-Claud, Taking the learning from @Eduard Lebedyuk, and just replacing ^MyGlobalName with %MyGlobalName would have worked: do ##class(MMLOGGINGPKG.Util.Singleton).RunMe()+----------------- general information ---------------| oref value: 55| class name: %ZEN.proxyObject| reference count: 1+----------------- attribute values ------------------| %changed = 1| %data("MyProp") = 22| %index = ""+-----------------------------------------------------Stored : 55@%ZEN.proxyObject Got : 55@%ZEN.proxyObject+----------------- general information ---------------| oref value: 55| class name: %ZEN.proxyObject| reference count: 3+----------------- attribute values ------------------| %changed = 1| %data("MyProp") = 22| %index = ""+-----------------------------------------------------
go to post Theo Stolker · Aug 23, 2023 A couple of clarifications: Replace "MMLOGGINGPKG.API.v1.spec" with your API spec You will have to tweak the schema as passed to GetValidator() to match what you want to validate The trick with $$$SchemaValidator is a way to create and initialize a singleton instance of the validator. The downside of that obviously is when you change the spec you have to make sure to restart the process in which the singleton resides. AddError is a classmethod that looks like: /// AddError ClassMethod AddError(fouten As %DynamicArray, regelnummer As %Integer, path As %String, error As %String) { do fouten.%Push({ "regelnummer": (regelnummer), "path": (path), "error": (error) }) }
go to post Theo Stolker · Aug 23, 2023 This is the code that was the result: /// Perform JSON Schema validation based on the API specification Class MMLOGGINGPKG.Validations.JsonSchemaValidation { /// Validate against the schema ClassMethod JsonIsValid(logregel As %DynamicObject, regelnummer As %String = "", errors As %DynamicArray, classname As %String) As %Boolean { #define SchemaValidator %JSchemaValidator if '$Data($$$SchemaValidator) || '$IsObject($$$SchemaValidator) { set specification = {}.%FromJSON(##class(%Dictionary.XDataDefinition).%OpenId("MMLOGGINGPKG.API.v1.spec||OpenAPI").Data) // Prevent enum validations, these are too verbose do specification.definitions.Event.properties.type.%Remove("enum") do specification.definitions.Request.properties.method.%Remove("enum") set $$$SchemaValidator = ..GetValidator({ "$ref": "#/definitions/Logregel", "definitions": (specification.definitions) }.%ToJSON()) } return ..PythonJsonIsValid(logregel.%ToJSON(), $$$SchemaValidator, regelnummer, errors, classname) } /// Validate JSON using Python implementation of jsonschema ClassMethod GetValidator(schema As %String) As %ObjectHandle [ Language = python ] { import json from jsonschema import Draft4Validator jschema = json.loads(schema) return Draft4Validator(schema=jschema) } /// Validate JSON using Python implementation of jsonschema ClassMethod PythonJsonIsValid(object As %String, validator As %ObjectHandle, regelnummer As %String, errors As %DynamicArray, classname As %String) As %Boolean [ Language = python ] { import json import iris from jsonschema import Draft4Validator jobject = json.loads(object) valid = 1 for error in validator.iter_errors(instance=jobject): try: valid = 0 #; print(error.json_path) #; e.g. '$' or $.error.status path = error.json_path.replace("$.", "").replace("$", "") pattern = "Additional properties are not allowed ('" if path.count(".") == 0 and error.message.startswith(pattern): try: object = error.path.pop() + "." except: object = "" pass x = error.message.replace(pattern, "") x = x.split("' w")[0] for attribute in x.split("', '"): iris.cls(classname).AddError(errors, regelnummer, object + attribute, "Additional properties are not allowed") else: iris.cls(classname).AddError(errors, regelnummer, path, error.message) pass except Exception as xxend: print(xxend) pass return valid } }
go to post Theo Stolker · Aug 23, 2023 Hi Yuri, Great post! I had the challenge to validate incoming json for a REST API for which I had created a decent swagger 2.0 spec, which follows the Draft4 specification of jsonschema. I implemented a validator that: Fetches the Swagger specification from the spec class Uses https://github.com/python-jsonschema/jsonschema In embedded Python code Creates a specific validator for the Draft4 specification Builds a nice JSON array with the errors found One thing to specifically note is the need to add "additionalProperties": false if you want to check against unexpected properties. I'll paste the code in the next comment. Let me know if you have feedback or questions!
go to post Theo Stolker · Jun 12, 2023 It would be helpful to also translate the code, because the non-English example code is still hard(er) to read and understand.
go to post Theo Stolker · May 25, 2023 I watched the video as I was curious what was behind the bold title. So what is this "true interoperability"? What I heard is that if institutions agree on the use of the same FHIR profile, these institutions will achieve a higher degree of interoperability as compared to just the syntactic operability achieved normally between HL7v2 and FHIR. I do agree that is true! So institutions using the same FHIR profile(s) have reached a level of seamless / instant interoperability. I guess at that point I am wondering: How can I put it to work? I found this video: Working with FHIR Profiles in InterSystems IRIS for Health. Are there any other relevant resources?
go to post Theo Stolker · Dec 27, 2022 Thanks, Julius, that is really helpful!! I have implemented the part of the method that I needed as follows: /// Move FHIR resourceproperties in the followin order: /// - resourceType /// - id /// - meta /// - extension ClassMethod FHIROrderResourceProperties(resource As %DynamicObject) As %DynamicObject { #dim order as %DynamicArray = [ "resourceType", "id", "meta", "extension" ] #dim newObject as %DynamicObject = {} for index = 0:1:order.%Size() - 1 { set element = order.%Get(index) set done(element) = 1 if $EXTRACT(resource.%GetTypeOf(element), 1, 2) '= "un" { do newObject.%Set(element, resource.%Get(element)) } } set iterator = resource.%GetIterator() while iterator.%GetNext(.element, .value) { if '$DATA(done(element)) { do newObject.%Set(element, value) } } return newObject }
go to post Theo Stolker · Dec 22, 2022 Yes, although the Python code does deal with an actual collection object :)
go to post Theo Stolker · Dec 22, 2022 /// Python reorder FHIR properties ClassMethod PyFHIRResourceReOrder(resource As %String) As %String [ Language = python ] { import json from collections import OrderedDict result = json.loads(resource, object_pairs_hook=OrderedDict) result.move_to_end("extension", last = False) result.move_to_end("meta", last = False) result.move_to_end("id", last = False) result.move_to_end("resourceType", last = False) return json.dumps(result) } This Python code is what I ended up with. It re-orders the properties "resourceType", "id", "meta" and "extension" to be at the start