|
| 1 | +/// This class serves as an intermediary for managing Source Control for Productions. |
| 2 | +/// Included is handling for exporting Productions as individual pieces of settings |
| 3 | +/// and importing individual item settings. |
| 4 | +Class SourceControl.Git.Production Extends %RegisteredObject |
| 5 | +{ |
| 6 | + |
| 7 | +/// Exports settings for a given Production and each Config Item from |
| 8 | +/// the ProductionDefinition as separate XMLs. These are exported to |
| 9 | +/// the /ptd subdirectory under the client's ^Sources directory. |
| 10 | +ClassMethod ExportProductionDefinitionShards(productionClass As %String, nameMethod As %String) As %Status |
| 11 | +{ |
| 12 | + // First, export Production definition omitting Config Items |
| 13 | + Set sc = ..ExportProductionSettings(productionClass, nameMethod) |
| 14 | + If $$$ISERR(sc) { |
| 15 | + Return sc |
| 16 | + } |
| 17 | + |
| 18 | + // next, export each item to a separate file |
| 19 | + Set rs = ##class(%SQL.Statement).%ExecDirect(, |
| 20 | + "select Name, ClassName from Ens_Config.Item where Production = ?" |
| 21 | + , productionClass |
| 22 | + ) |
| 23 | + Throw:rs.%SQLCODE<0 ##class(%Exception.SQL).CreateFromSQLCODE(rs.%SQLCODE,rs.%Message) |
| 24 | + While rs.%Next() { |
| 25 | + Set ptdName = "" |
| 26 | + Set item = ##class(Ens.Config.Production).OpenItemByConfigName(productionClass _ "||" _ rs.Name _ "|" _ rs.ClassName) |
| 27 | + If $isobject(item) { |
| 28 | + Set sc = ..ExportConfigItemSettings(productionClass, item, nameMethod) |
| 29 | + If $$$ISERR(sc) { |
| 30 | + Return sc |
| 31 | + } |
| 32 | + } |
| 33 | + } |
| 34 | + Return $$$OK |
| 35 | +} |
| 36 | + |
| 37 | +/// Exports a Studio project including both the provided PTD and export notes for the PTD |
| 38 | +ClassMethod ExportProjectForPTD(productionClass, ptdName, exportPath) As %Status |
| 39 | +{ |
| 40 | + set st = $$$OK |
| 41 | + try { |
| 42 | + set project = ##class(%Studio.Project).%New() |
| 43 | + set project.Name = $replace($replace(ptdName,".","_"),":","-") |
| 44 | + kill projContentsList |
| 45 | + set projContentsList(ptdName _ ".PTD") = "" |
| 46 | + $$$ThrowOnError(##class(Ens.Deployment.Utils).CreateExportNotesPTD(project.Name,productionClass,,.projContentsList,0,.exportNotesPTDName)) |
| 47 | + // strip items from export notes that break our diff |
| 48 | + set st = ##class(Ens.Util.ProjectTextDocument).GetStream(.notesStream, exportNotesPTDName) |
| 49 | + quit:$$$ISERR(st) |
| 50 | + set newNotesStream = ##class(%Stream.GlobalCharacter).%New() |
| 51 | + while 'notesStream.AtEnd { |
| 52 | + set line = notesStream.ReadLine() |
| 53 | + if $match(line, "^<(Machine|Instance|Namespace|Username)>.*") { |
| 54 | + // remove these |
| 55 | + } elseif $match(line, "^<UTC>.*") { |
| 56 | + // dummy timestamp for source control hooks to work properly |
| 57 | + set st = newNotesStream.WriteLine("<UTC>1841-01-01 00:00:00.000</UTC>") |
| 58 | + quit:$$$ISERR(st) |
| 59 | + } else { |
| 60 | + set st = newNotesStream.WriteLine(line) |
| 61 | + quit:$$$ISERR(st) |
| 62 | + } |
| 63 | + } |
| 64 | + do:##class(%RoutineMgr).Exists(exportNotesPTDName_".PTD") ##class(%RoutineMgr).Delete(exportNotesPTDName_".PTD") |
| 65 | + set st = ##class(Ens.Util.ProjectTextDocument).Create(newNotesStream, exportNotesPTDName, "Export Notes for export "_project.Name) |
| 66 | + quit:$$$ISERR(st) |
| 67 | + // Internal/External naming logic relies on Export Notes being added to project first. If this is changed check for dependencies |
| 68 | + do project.AddItem(exportNotesPTDName_".PTD") |
| 69 | + do project.AddItem(ptdName_".PTD") |
| 70 | + $$$ThrowOnError(project.%Save()) |
| 71 | + set projContentsList(exportNotesPTDName_".PTD") = "" |
| 72 | + set projContentsList(project.Name_".PRJ") = "" |
| 73 | + $$$ThrowOnError($System.OBJ.Export(.projContentsList, exportPath, "/diffexport=1")) |
| 74 | + } catch err { |
| 75 | + set st = err.AsStatus() |
| 76 | + } |
| 77 | + if $IsObject(project) { |
| 78 | + set st = $$$ADDSC(st,##class(%Studio.Project).%DeleteId(project.Name)) |
| 79 | + } |
| 80 | + return st |
| 81 | +} |
| 82 | + |
| 83 | +/// Creates and exports a PTD item for a given internal name, either a single config item |
| 84 | +/// or the production settings. |
| 85 | +ClassMethod ExportPTD(internalName As %String, nameMethod) As %Status |
| 86 | +{ |
| 87 | + Set name = $Piece(internalName,".",1,$Length(internalName,".")-1) |
| 88 | + Set $ListBuild(productionName, itemName) = $ListFromString(name, "||") |
| 89 | + Set $ListBuild(itemName, itemClassName) = $ListFromString(itemName, "|") |
| 90 | + If $Piece($Piece(name,"||",2),"|",2) = "" { |
| 91 | + Set sc = ..ExportProductionSettings(productionName, nameMethod) |
| 92 | + } Else { |
| 93 | + Set configItemName = productionName_"||"_$Piece(itemName, "Settings-", 2)_"|"_itemClassName |
| 94 | + Set item = ##class(Ens.Config.Production).OpenItemByConfigName(configItemName) |
| 95 | + Set sc = ..ExportConfigItemSettings(productionName, item, nameMethod) |
| 96 | + } |
| 97 | + Return sc |
| 98 | +} |
| 99 | + |
| 100 | +/// Export a single Production Config Item. For a given Ens.Config.Item, the |
| 101 | +/// exports the PTD for this item to the file system under the directory specified |
| 102 | +ClassMethod ExportConfigItemSettings(productionClass As %String, item As %RegisteredObject, nameMethod As %String) As %Status |
| 103 | +{ |
| 104 | + Set internalName = productionClass_"||Settings-"_item.Name_"|"_item.ClassName_".PTD" |
| 105 | + Set externalName = $ClassMethod($$SrcCtrlCls^%buildccr, nameMethod, internalName) |
| 106 | + Set filename = ##class(%File).NormalizeFilename(externalName) |
| 107 | + set st = ##class(Ens.Deployment.Utils).CreatePTDFromItem(.item, .ptdName) |
| 108 | + $$$QuitOnError(st) |
| 109 | + set st = ..ExportProjectForPTD(productionClass, ptdName, filename) |
| 110 | + $$$QuitOnError(st) |
| 111 | + Return st |
| 112 | +} |
| 113 | + |
| 114 | +/// Exports the Production settings from ProductionDefinition given the Production |
| 115 | +/// class name |
| 116 | +ClassMethod ExportProductionSettings(productionClass As %String, nameMethod As %String) As %Status |
| 117 | +{ |
| 118 | + Set internalName = productionClass_"||ProductionSettings-"_productionClass_".PTD" |
| 119 | + Set class = ##class(%Dictionary.CompiledClass).%OpenId(productionClass) |
| 120 | + Set sc = ##class(Ens.Deployment.Utils).CreatePTDFromProduction(class, .ptdName) |
| 121 | + If $$$ISERR(sc) { |
| 122 | + Return sc |
| 123 | + } |
| 124 | + Set externalName = $ClassMethod($$SrcCtrlCls^%buildccr, nameMethod, internalName) |
| 125 | + Set filename = ##class(%File).NormalizeFilename(externalName) |
| 126 | + set sc = ..ExportProjectForPTD(productionClass, ptdName, filename) |
| 127 | + Return sc |
| 128 | +} |
| 129 | + |
| 130 | +ClassMethod GetModifiedItemsBeforeSave(internalName, Location, Output modifiedItems) |
| 131 | +{ |
| 132 | + kill modifiedItems |
| 133 | + set productionName = $piece(internalName,".",1,*-1) |
| 134 | + if ..IsEnsPortal() { |
| 135 | + // If editing from SMP, get the modified items by looking at %IsModified on the items in the production in memory. |
| 136 | + // No way to know if an item has been added or deleted, so ignore it. |
| 137 | + set productionConfig = ##class(Ens.Config.Production).%OpenId(productionName) |
| 138 | + if $isobject(productionConfig) { |
| 139 | + set modifiedItem = $$$NULLOREF |
| 140 | + for i=1:1:productionConfig.Items.Count() { |
| 141 | + set item = productionConfig.Items.GetAt(i) |
| 142 | + if item.%IsModified() { |
| 143 | + set modifiedItem = item |
| 144 | + quit |
| 145 | + } |
| 146 | + for j=1:1:item.Settings.Count() { |
| 147 | + set setting = item.Settings.GetAt(i) |
| 148 | + if $isobject(setting) && setting.%IsModified() { |
| 149 | + set modifiedItem = item |
| 150 | + quit |
| 151 | + } |
| 152 | + } |
| 153 | + } |
| 154 | + set modifiedInternalName = "" |
| 155 | + if $isobject(modifiedItem) { |
| 156 | + set modifiedInternalName = productionName _ "||Settings-" _ modifiedItem.Name _ "|" _ modifiedItem.ClassName _ ".PTD" |
| 157 | + } else { |
| 158 | + // cannot check %IsModified on production config settings because they are not actually modified at this point. |
| 159 | + // workaround: just assume any change not to a specific item is to the production settings |
| 160 | + set modifiedInternalName = productionName _ "||ProductionSettings-" _ productionName _ ".PTD" |
| 161 | + } |
| 162 | + } |
| 163 | + if (modifiedInternalName '= "") { |
| 164 | + set modifiedItems(modifiedInternalName) = "M" |
| 165 | + } |
| 166 | + } else { |
| 167 | + // If editing/adding/deleting from Studio, get the modified items by comparing the XDATA in Location with the XDATA in the compiled class. |
| 168 | + // FUTURE: implement this to support Studio |
| 169 | + } |
| 170 | + // populate data for use in OnAfterSave |
| 171 | + kill ^mtempsscProd($job,"modifiedItems") |
| 172 | + merge ^mtempsscProd($job,"modifiedItems") = modifiedItems |
| 173 | + // FUTURE: use a percent variable or PPG instead |
| 174 | + kill ^mtempsscProd($job,"items") |
| 175 | + set rs = ##class(%SQL.Statement).%ExecDirect( |
| 176 | + ,"select Name, ClassName from Ens_Config.Item where Production = ?" |
| 177 | + , productionName) |
| 178 | + $$$ThrowSQLIfError(rs.%SQLCODE, rs.%Message) |
| 179 | + while rs.%Next() { |
| 180 | + set ^mtempsscProd($job,"items",$listbuild(rs.Name, rs.ClassName)) = 1 |
| 181 | + } |
| 182 | +} |
| 183 | + |
| 184 | +ClassMethod GetModifiedItemsAfterSave(internalName, Output modifiedItems) |
| 185 | +{ |
| 186 | + kill modifiedItems |
| 187 | + set productionName = $piece(internalName,".",1,*-1) |
| 188 | + if ..IsEnsPortal() { |
| 189 | + // If adding/deleting from SMP, get the modified items by comparing items in temp global with items now |
| 190 | + set rs = ##class(%SQL.Statement).%ExecDirect( |
| 191 | + ,"select Name, ClassName from Ens_Config.Item where Production = ?" |
| 192 | + , productionName) |
| 193 | + $$$ThrowSQLIfError(rs.%SQLCODE, rs.%Message) |
| 194 | + while rs.%Next() { |
| 195 | + if '$get(^mtempsscProd($job,"items", $listbuild(rs.Name, rs.ClassName))) { |
| 196 | + set itemInternalName = productionName _ "||Settings-" _ rs.Name _ "|" _ rs.ClassName _ ".PTD" |
| 197 | + set modifiedItems(itemInternalName) = "A" |
| 198 | + } |
| 199 | + kill ^mtempsscProd($job,"items", $listbuild(rs.Name, rs.ClassName)) |
| 200 | + } |
| 201 | + set key = $order(^mtempsscProd($job,"items","")) |
| 202 | + while (key '= "") { |
| 203 | + set itemInternalName = productionName _ "||Settings-" _ $listget(key,1) _ "|" _ $listget(key,2) _ ".PTD" |
| 204 | + set modifiedItems(itemInternalName) = "D" |
| 205 | + set key = $order(^mtempsscProd($job,"items",key)) |
| 206 | + } |
| 207 | + // If editing from SMP, get the modified items from a cache stored in OnBeforeSave. |
| 208 | + // Only do this if there are no added/deleted items, because otherwise production settings will be incorrectly included. |
| 209 | + if '$data(modifiedItems) { |
| 210 | + merge modifiedItems = ^mtempsscProd($job,"modifiedItems") |
| 211 | + } |
| 212 | + } else { |
| 213 | + // If editing/adding/deleting from Studio, get the modified items from a percent variable set in OnBeforeSave. |
| 214 | + // FUTURE: implement this to support Studio. |
| 215 | + } |
| 216 | +} |
| 217 | + |
| 218 | +/// Check if current CSP session is EnsPortal page |
| 219 | +ClassMethod IsEnsPortal() As %Boolean |
| 220 | +{ |
| 221 | + If $IsObject($Get(%session)) && ($Get(%request.Data("pageclass","1")) [ "EnsPortal") { |
| 222 | + Return 1 |
| 223 | + } |
| 224 | + Return 0 |
| 225 | +} |
| 226 | + |
| 227 | +/// Perform check if Production Decomposition logic should be used for given item |
| 228 | +ClassMethod IsProductionClass(className As %String, nameMethod As %String) As %Boolean |
| 229 | +{ |
| 230 | + if $$$comClassDefined(className) { |
| 231 | + return $classmethod(className, "%Extends", "Ens.Production") |
| 232 | + } else { |
| 233 | + set filename = $classmethod($$SrcCtrlCls^%buildccr, nameMethod, className_".CLS") |
| 234 | + if ##class(%File).Exists(filename) { |
| 235 | + $$$ThrowOnError($System.OBJ.Load(filename)) |
| 236 | + } |
| 237 | + set classDef = ##class(%Dictionary.ClassDefinition).%OpenId(className) |
| 238 | + if $isobject(classDef) { |
| 239 | + for key=1:1:classDef.XDatas.Count() { |
| 240 | + if classDef.XDatas.GetAt(key).Name = "ProductionDefinition" { |
| 241 | + return 1 |
| 242 | + } |
| 243 | + } |
| 244 | + } |
| 245 | + } |
| 246 | + return 0 |
| 247 | +} |
| 248 | + |
| 249 | +/// Given a file name for a PTD item, returns a suggested internal name. |
| 250 | +ClassMethod ParseExternalName(externalName, Output internalName) |
| 251 | +{ |
| 252 | + set file = $piece(externalName, "/", *) |
| 253 | + set deployDoc = ##class(EnsLib.EDI.XML.Document).%New(externalName) |
| 254 | + set exportNotesPTDText = $ZCVT(deployDoc.GetValueAt("/Export/Document[1]/1"),"I","XML") |
| 255 | + set exportNotesPTD = ##class(EnsLib.EDI.XML.Document).%New(exportNotesPTDText) |
| 256 | + set productionName = exportNotesPTD.GetValueAt("/Deployment/Creation/SourceProduction") |
| 257 | + if $extract(file,1,9) = "ProdStgs-" { |
| 258 | + set internalName = productionName_"||ProductionSettings-"_productionName_".PTD" |
| 259 | + } else { |
| 260 | + // Special case for Config Item Settings PTD, requires checking PTD CDATA for Item and Class name |
| 261 | + set settingsPTDText = $zconvert(deployDoc.GetValueAt("/Export/Document[2]/1"),"I","XML") |
| 262 | + set settingsPTD = ##class(EnsLib.EDI.XML.Document).%New(settingsPTDText) |
| 263 | + set itemClass = settingsPTD.GetValueAt("/Item/@ClassName") |
| 264 | + set itemName = settingsPTD.GetValueAt("/Item/@Name") |
| 265 | + set internalName = productionName_"||Settings-"_itemName_"|"_itemClass_".PTD" |
| 266 | + } |
| 267 | +} |
| 268 | + |
| 269 | +/// Given an internal name for a PTD item, returns a suggested filename for export. |
| 270 | +ClassMethod ParseInternalName(internalName, noFolders As %Boolean = 0, Output fileName) |
| 271 | +{ |
| 272 | + set name = $piece(internalName,".",1,*-1) |
| 273 | + if 'noFolders { |
| 274 | + set name = $replace(name,"||","/") |
| 275 | + set $ListBuild(productionName, name) = $ListFromString(name, "/") |
| 276 | + } |
| 277 | + // Abbreviate "ProductionSettings" to "ProdStgs", "Settings" to "Stgs". |
| 278 | + Set prefix = $Case($Extract(name), "P":"ProdStgs-", "S":"Stgs-", :"") |
| 279 | + Set name = prefix_$Piece(name,"-",2,*) |
| 280 | + set $ListBuild(itemName, itemClassName) = $ListFromString(name, "|") |
| 281 | + set name = $select( |
| 282 | + $get(itemClassName) '= "": itemName_$zhex($zcrc(itemClassName,6)), |
| 283 | + 1: name |
| 284 | + ) |
| 285 | + if 'noFolders { |
| 286 | + set name = productionName _ "/" _ name |
| 287 | + } |
| 288 | + set fileName = $translate($replace(name, ".", "_") _ ".xml", "\", "/") |
| 289 | +} |
| 290 | + |
| 291 | +} |
0 commit comments