@@ -73,7 +73,7 @@ ClassMethod DeleteProductionDefinitionShards(productionClass As %String, deleteM
7373}
7474
7575/// Exports a Studio project including both the provided PTD and export notes for the PTD
76- ClassMethod ExportProjectForPTD (productionClass , ptdName , exportPath ) As %Status
76+ ClassMethod ExportProjectForPTD (productionClass As %String , ptdName As %String , exportPath As %String ) As %Status
7777{
7878 set st = $$$OK
7979 try {
@@ -136,9 +136,9 @@ ClassMethod ExportProjectForPTD(productionClass, ptdName, exportPath) As %Status
136136
137137/// Creates and exports a PTD item for a given internal name, either a single config item
138138/// or the production settings.
139- ClassMethod ExportPTD (internalName As %String , nameMethod ) As %Status
139+ ClassMethod ExportPTD (internalName As %String , nameMethod As %String ) As %Status
140140{
141- Set name = $Piece (internalName ," ." ,1 ,$Length ( internalName , " . " ) -1 )
141+ Set name = $Piece (internalName ," ." ,1 ,* -1 )
142142 Set $ListBuild (productionName , itemName ) = $ListFromString (name , " ||" )
143143 Set $ListBuild (itemName , itemClassName ) = $ListFromString (itemName , " |" )
144144 Set sc = $$$OK
@@ -228,7 +228,7 @@ ClassMethod ExportProductionSettings(productionClass As %String, nameMethod As %
228228 Return sc
229229}
230230
231- ClassMethod GetModifiedItemsBeforeSave (internalName , Location , Output modifiedItems )
231+ ClassMethod GetModifiedItemsBeforeSave (internalName As %String , Location As %String , Output modifiedItems )
232232{
233233 kill modifiedItems
234234 set productionName = $piece (internalName ," ." ,1 ,*-1 )
@@ -255,17 +255,27 @@ ClassMethod GetModifiedItemsBeforeSave(internalName, Location, Output modifiedIt
255255 set modifiedInternalName = " "
256256 if $isobject (modifiedItem ) {
257257 set modifiedInternalName = ..CreateInternalName (productionName , modifiedItem .Name , modifiedItem .ClassName , 0 )
258- } else {
258+ } elseif productionConfig . %IsModified () {
259259 // cannot check %IsModified on production config settings because they are not actually modified at this point.
260260 // workaround: just assume any change not to a specific item is to the production settings
261261 set modifiedInternalName = ..CreateInternalName (productionName ,,,1 )
262+ } else {
263+ // if nothing is modified, assume this is deleting a config item
264+ // only allow if no items are checked out by other users
265+ // TODO: determine specific item being deleted in OnBeforeSave to allow for accurate editability checks
266+ if $isobject (productionConfig ) {
267+ set modifiedItems (..CreateInternalName (productionName ,,,1 )) = " M"
268+ for i =1 :1 :productionConfig .Items .Count () {
269+ set item = productionConfig .Items .GetAt (i )
270+ set modifiedItems (..CreateInternalName (productionName , item .Name , item .ClassName , 0 )) = " M"
271+ }
272+ }
262273 }
263274 }
264275 if ($get (modifiedInternalName ) '= " " ) {
265276 set modifiedItems (modifiedInternalName ) = " M"
266277 }
267278 } else {
268- // FUTURE: get the actually modified items by comparing the XDATA in Location with the XDATA in the compiled class
269279 // If making changes from Studio, list every item in the production.
270280 if $isobject (productionConfig ) {
271281 set modifiedItems (..CreateInternalName (productionName ,,,1 )) = " M"
@@ -289,63 +299,80 @@ ClassMethod GetModifiedItemsBeforeSave(internalName, Location, Output modifiedIt
289299 }
290300}
291301
292- ClassMethod GetModifiedItemsAfterSave (internalName , Output modifiedItems )
302+ ClassMethod GetModifiedItemsAfterSave (internalName As %String , Output modifiedItems )
293303{
294304 kill modifiedItems
295305 set productionName = $piece (internalName ," ." ,1 ,*-1 )
296- if ..IsEnsPortal () {
297- // If adding/deleting from SMP, get the modified items by comparing items in temp global with items now
298- set rs = ..ExecDirectNoPriv (
299- " select Name, ClassName from Ens_Config.Item where Production = ?"
300- , productionName )
301- throw :rs .%SQLCODE <0 ##class (%Exception.SQL ).CreateFromSQLCODE (rs .%SQLCODE ,rs .%Message )
302- while rs .%Next () {
303- if '$get (^IRIS .Temp (" sscProd" ,$job ," items" , $listbuild (rs .Name , rs .ClassName ))) {
304- set itemInternalName = ..CreateInternalName (productionName , rs .Name , rs .ClassName , 0 )
305- set modifiedItems (itemInternalName ) = " A"
306- }
307- kill ^IRIS .Temp (" sscProd" ,$job ," items" , $listbuild (rs .Name , rs .ClassName ))
308- }
309- set key = $order (^IRIS .Temp (" sscProd" ,$job ," items" ," " ))
310- while (key '= " " ) {
311- set itemInternalName = ..CreateInternalName (productionName , $listget (key ,1 ), $listget (key ,2 ), 0 )
312- set modifiedItems (itemInternalName ) = " D"
313- set key = $order (^IRIS .Temp (" sscProd" ,$job ," items" ,key ))
314- }
306+ if ..IsEnsPortal (.source ) {
307+ // If adding/deleting from SMP, get the modified items by comparing items in temp global with items now
308+ do ..GetAddOrDeletedItems (productionName , .modifiedItems )
315309 // If editing from SMP, get the modified items from a cache stored in OnBeforeSave.
316310 // Only do this if there are no added/deleted items, because otherwise production settings will be incorrectly included.
317311 if '$data (modifiedItems ) {
318312 merge modifiedItems = ^IRIS .Temp (" sscProd" ,$job ," modifiedItems" )
319313 }
320314 } else {
321- // If editing in the IDE, list every item in the production.
322- // FUTURE: get the actually modified items using the temp global set in OnBeforeSave
315+ // If editing/adding/deleting from Studio, VS Code, or Interop Editor UI, mark all items for edit then find adds/deletes
316+ if source = " IDE" {
317+ // only compile f editing from IDE
318+ $$$ThrowOnError($System .OBJ .Compile (productionName , " ck-d/multicompile=0" ))
319+ }
323320 set productionConfig = ##class (Ens.Config.Production ).%OpenId (productionName )
324321 if $isobject (productionConfig ) {
325- set modifiedItems (..CreateInternalName (productionName ,,,1 )) = " M"
326- for i =1 :1 :productionConfig .Items .Count () {
327- set item = productionConfig .Items .GetAt (i )
328- set modifiedItems (..CreateInternalName (productionName , item .Name , item .ClassName , 0 )) = " M"
329- }
322+ merge modifiedItems = ^IRIS .Temp (" sscProd" ,$job ," modifiedItems" )
323+ do ..GetAddOrDeletedItems (productionName , .modifiedItems )
330324 }
331325 }
332326}
333327
328+ /// Get added or deleted Config Items by checking Ens_Config.Item table against cache from OnBeforeSave
329+ ClassMethod GetAddOrDeletedItems (productionName As %String , ByRef modifiedItems )
330+ {
331+ set rs = ..ExecDirectNoPriv (
332+ " select Name, ClassName from Ens_Config.Item where Production = ?"
333+ , productionName )
334+ throw :rs .%SQLCODE <0 ##class (%Exception.SQL ).CreateFromSQLCODE (rs .%SQLCODE ,rs .%Message )
335+ while rs .%Next () {
336+ if '$get (^IRIS .Temp (" sscProd" ,$job ," items" , $listbuild (rs .Name , rs .ClassName ))) {
337+ set itemInternalName = ..CreateInternalName (productionName , rs .Name , rs .ClassName , 0 )
338+ set modifiedItems (itemInternalName ) = " A"
339+ }
340+ kill ^IRIS .Temp (" sscProd" ,$job ," items" , $listbuild (rs .Name , rs .ClassName ))
341+ }
342+ set key = $order (^IRIS .Temp (" sscProd" ,$job ," items" ," " ))
343+ while (key '= " " ) {
344+ set itemInternalName = ..CreateInternalName (productionName , $listget (key ,1 ), $listget (key ,2 ), 0 )
345+ set modifiedItems (itemInternalName ) = " D"
346+ set key = $order (^IRIS .Temp (" sscProd" ,$job ," items" ,key ))
347+ }
348+ }
349+
334350/// Check if current CSP session is EnsPortal page
335- ClassMethod IsEnsPortal () As %Boolean
351+ ClassMethod IsEnsPortal (Output source As %String = " " ) As %Boolean
336352{
337- Return $Data (%request ) && '($IsObject (%request ) &&
338- (
339- (%request .UserAgent [ " Code" ) // VS Code
340- || (%request .UserAgent [ " node-fetch" ) // VS Code
341- || (%request .Application [ " /api/interop-editors" ))) // New interoperability editor
353+ if $Data (%request ) && $isobject (%request ) {
354+ if (%request .Application [ " /api/atelier" ) {
355+ set source = " IDE"
356+ } elseif (%request .Application [ " /api/interop-editors" ) {
357+ set source = " Interop Editor"
358+ } else {
359+ return 1
360+ }
361+ } else {
362+ Set source = " IDE"
363+ }
364+ return 0
342365}
343366
344367/// Perform check if Production Decomposition logic should be used for given item
345368ClassMethod IsProductionClass (className As %String , nameMethod As %String ) As %Boolean
346369{
347370 if (className '= " " ) && $$$comClassDefined(className ) {
348- return $classmethod (className , " %Extends" , " Ens.Production" )
371+ try {
372+ return $classmethod (className , " %Extends" , " Ens.Production" )
373+ } catch err {
374+ if '(err .AsStatus () [ " CLASS DOES NOT EXIST" ) throw err
375+ }
349376 } else {
350377 // check if there exists a Production settings PTD export for ths Production
351378 set settingsPTD = ..CreateInternalName (className ,,,1 )
@@ -371,7 +398,7 @@ ClassMethod IsProductionClass(className As %String, nameMethod As %String) As %B
371398}
372399
373400/// Given a file name for a PTD item, returns a suggested internal name. This method assumes that the file exists on disk.
374- ClassMethod ParseExternalName (externalName , Output internalName = " " , Output productionName = " " ) As %Status
401+ ClassMethod ParseExternalName (externalName As %String , Output internalName = " " , Output productionName = " " ) As %Status
375402{
376403 set sc = $$$OK
377404 try {
@@ -413,7 +440,7 @@ ClassMethod ParseExternalName(externalName, Output internalName = "", Output pro
413440/// - itemName: name of the configuration item
414441/// - productionName: name of the associated production
415442/// - isProdSettings: if true, this item is a production settings; if false, this item is a configuration item settings
416- ClassMethod ParseInternalName (internalName , noFolders As %Boolean = 0 , Output fileName , Output itemName , Output itemClassName , Output productionName , Output isProdSettings As %Boolean )
443+ ClassMethod ParseInternalName (internalName As %String , noFolders As %Boolean = 0 , Output fileName As %String , Output itemName As %String , Output itemClassName As %String , Output productionName As %String , Output isProdSettings As %Boolean )
417444{
418445 set name = $piece (internalName ," ." ,1 ,*-1 )
419446 if 'noFolders {
@@ -436,7 +463,7 @@ ClassMethod ParseInternalName(internalName, noFolders As %Boolean = 0, Output fi
436463}
437464
438465/// Calculates the internal name for a decomposed production item
439- ClassMethod CreateInternalName (productionName = " " , itemName = " " , itemClassName = " " , isProductionSettings As %Boolean = 0 ) As %String
466+ ClassMethod CreateInternalName (productionName As %String = " " , itemName As %String = " " , itemClassName As %String = " " , isProductionSettings As %Boolean = 0 ) As %String
440467{
441468 return $select (
442469 isProductionSettings : productionName _" ||ProductionSettings-" _productionName _" .PTD" ,
@@ -445,7 +472,7 @@ ClassMethod CreateInternalName(productionName = "", itemName = "", itemClassName
445472}
446473
447474/// Given an external name for a PTD item, removes that item from the production.
448- ClassMethod RemoveItemByExternalName (externalName , nameMethod ) As %Status
475+ ClassMethod RemoveItemByExternalName (externalName As %String , nameMethod As %String ) As %Status
449476{
450477 set sc = $$$OK
451478 set productionName = $replace ($piece ($replace (externalName ," \" ," /" )," /" ,*-1 )," _" ," ." )
@@ -466,7 +493,7 @@ ClassMethod RemoveItemByExternalName(externalName, nameMethod) As %Status
466493}
467494
468495/// Given an internal name for a PTD item, removes that item from the production.
469- ClassMethod RemoveItem (internalName , noFolders As %Boolean = 0 ) As %Status
496+ ClassMethod RemoveItem (internalName As %String , noFolders As %Boolean = 0 ) As %Status
470497{
471498 set sc = $$$OK
472499 try {
@@ -477,11 +504,21 @@ ClassMethod RemoveItem(internalName, noFolders As %Boolean = 0) As %Status
477504 if 'isProdSettings {
478505 set production = ##class (Ens.Config.Production ).%OpenId (productionName ,,.sc )
479506 quit :$$$ISERR(sc )
480- set configItem = ##class (Ens.Config.Production ).OpenItemByConfigName (productionName _" ||" _itemName _" |" _itemClassName ,.sc )
481- quit :$$$ISERR(sc )
482- do production .RemoveItem (configItem )
507+ set configItem = ##class (Ens.Config.Production ).OpenItemByConfigName (productionName _" ||" _itemName _" |" _itemClassName ,.sc )
508+
509+ // only remove config item if it still exists and if item was opened ok
510+ if $$$ISERR(sc ) {
511+ if '(sc [ " ErrConfigItemNotFound" ) {
512+ return sc
513+ }
514+ } else {
515+ do production .RemoveItem (configItem )
516+ }
517+
483518 set sc = production .%Save ()
484519 quit :$$$ISERR(sc )
520+ set sc = production .SaveToClass ()
521+ quit :$$$ISERR(sc )
485522 }
486523 } catch err {
487524 set sc = err .AsStatus ()
@@ -491,7 +528,7 @@ ClassMethod RemoveItem(internalName, noFolders As %Boolean = 0) As %Status
491528
492529/// Given internal name for a Production Settings PTD, creates the corresponding Production
493530/// Class if it does not already exist in this namespace
494- ClassMethod CreateProduction (productionName As %String , superClasses = " " ) As %Status
531+ ClassMethod CreateProduction (productionName As %String , superClasses As %String = " " ) As %Status
495532{
496533 set classDef = ##class (%Dictionary.ClassDefinition ).%New (productionName )
497534 if superClasses '= " " {
@@ -525,7 +562,7 @@ ClassMethod GetUserProductionChanges(productionName As %String, ByRef items)
525562}
526563
527564/// Executes a SQL query without privilege checking if possible on this IRIS version
528- ClassMethod ExecDirectNoPriv (sql , args ...) As %SQL .StatementResult
565+ ClassMethod ExecDirectNoPriv (sql As %String , args ...) As %SQL .StatementResult
529566{
530567 // once minimum version is IRIS 2021.1.3, remove and just use %ExecDirectNoPriv
531568 try {
0 commit comments