Skip to content

Commit 9500097

Browse files
committed
Merge branch 'main' into private-memory-store
2 parents 83a2c21 + 9cb506b commit 9500097

File tree

3 files changed

+90
-51
lines changed

3 files changed

+90
-51
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1717
- Changing system mode (environment name) in setting spersists after instance restart (#655)
1818
- Popping from stash is more responsive (#687)
1919
- Favorites links for Git pages now works on recent IRIS versions (#734)
20+
- IDE editing of decomposed productions now properly handles adds and deletes (#643)
2021

2122
### Fixed
2223
- Fixed error running Import All when Git settings file does not exist (#713)

cls/SourceControl/Git/Production.cls

+87-50
Original file line numberDiff line numberDiff line change
@@ -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
345368
ClassMethod 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 {

cls/SourceControl/Git/Utils.cls

+2-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ $Get(@..#Storage@("settings","decomposeProductions"), 0)
7979

8080
ClassMethod DecomposeProdAllowIDE() As %Boolean [ CodeMode = expression ]
8181
{
82-
$Get(@..#Storage@("settings","decomposeProdAllowIDE"), 0)
82+
$Get(@..#Storage@("settings","decomposeProdAllowIDE"), 1)
8383
}
8484

8585
ClassMethod GitRemoteType() As %String
@@ -3209,6 +3209,7 @@ ClassMethod GitUnstage(Output output As %Library.DynamicObject) As %Status
32093209
return $$$OK
32103210
}
32113211

3212+
}
32123213
ClassMethod WriteLineToFile(filePath As %String, line As %String)
32133214
{
32143215
Set file=##class(%File).%New(filePath)

0 commit comments

Comments
 (0)