Skip to content

Commit 6e16c7d

Browse files
committed
production utilities moved to class in git-source-control
1 parent 28c98d1 commit 6e16c7d

File tree

4 files changed

+303
-12
lines changed

4 files changed

+303
-12
lines changed

cls/SourceControl/Git/Extension.cls

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ Method OnAfterSave(InternalName As %String, Object As %RegisteredObject = {$$$NU
279279
if ##class(SourceControl.Git.Utils).IsNamespaceInGit() && ..IsInSourceControl(InternalName) {
280280
// If this is a production class and production decomposition is enabled, call recursively on all modified production items.
281281
if ##class(SourceControl.Git.Utils).ItemIsProductionToDecompose(InternalName) {
282-
do ##class(%Studio.SourceControl.Production).GetModifiedItemsAfterSave(InternalName, .productionItems)
282+
do ##class(SourceControl.Git.Production).GetModifiedItemsAfterSave(InternalName, .productionItems)
283283
set key = $order(productionItems(""))
284284
while (key '= "") {
285285
if productionItems(key) = "D" {
@@ -375,7 +375,7 @@ Method OnBeforeSave(InternalName As %String, Location As %String = "", Object As
375375
{
376376
set st = $$$OK
377377
if ##class(SourceControl.Git.Utils).ItemIsProductionToDecompose(InternalName) {
378-
do ##class(%Studio.SourceControl.Production).GetModifiedItemsBeforeSave(InternalName,,.productionItems)
378+
do ##class(SourceControl.Git.Production).GetModifiedItemsBeforeSave(InternalName,,.productionItems)
379379
set key = $order(productionItems(""))
380380
while (key '= "") {
381381
// if any modified items in this production class are checked out by a different user, fail the check.
@@ -450,3 +450,4 @@ Method AddToSourceControl(InternalName As %String, Description As %String = "")
450450
}
451451

452452
}
453+

cls/SourceControl/Git/File.cls

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ ClassMethod ExternalNameToInternalName(ExternalName As %String) As %String
4747
}
4848
}
4949
if itemIsPTD && ##class(%Library.EnsembleMgr).IsEnsembleNamespace() {
50-
do ##class(%Studio.SourceControl.Production).ParseExternalName(ExternalName,.internalName)
50+
do ##class(SourceControl.Git.Production).ParseExternalName(ExternalName,.internalName)
5151
} elseif (($data(outName)=1) || ($data(outName) = 11 && ($order(outName(""),-1) = $order(outName(""))))) && ($zconvert(##class(SourceControl.Git.Utils).Type(outName),"U") '= "CSP") {
5252
set internalName = outName
5353
}
@@ -84,3 +84,4 @@ Storage Default
8484
}
8585

8686
}
87+

cls/SourceControl/Git/Production.cls

Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
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+
}

cls/SourceControl/Git/Utils.cls

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1275,8 +1275,7 @@ ClassMethod ImportItem(InternalName As %String, force As %Boolean = 0, verbose A
12751275
set sc = ##class(Ens.Deployment.Deploy).DeployCode(filename,targetProduction,0,rollbackFile)
12761276
do ##class(%File).Delete(rollbackFile)
12771277
} elseif (type = "cls") && settings.decomposeProductions
1278-
&& $$$comClassDefined("%Studio.SourceControl.Production")
1279-
&& ##class(%Studio.SourceControl.Production).IsProductionClass(
1278+
&& ##class(SourceControl.Git.Production).IsProductionClass(
12801279
..NameWithoutExtension(InternalName), "FullExternalName") {
12811280
write !, "Production decomposition enabled, skipping import of production class"
12821281
set imported = 0
@@ -1339,8 +1338,8 @@ ClassMethod ListItemsRecursively(type, fileSpec, directory, ByRef itemList) [ Pr
13391338
do ..ListItemsRecursively(type, fileSpec, files.Name, .itemList)
13401339
} else {
13411340
set internalName = files.ItemName
1342-
if ($zconvert(type,"l") = "ptd") && $$$comClassDefined("%Studio.SourceControl.Production") {
1343-
do ##class(%Studio.SourceControl.Production).ParseExternalName($translate(files.Name,"\","/"), .internalName)
1341+
if ($zconvert(type,"l") = "ptd") {
1342+
do ##class(SourceControl.Git.Production).ParseExternalName($translate(files.Name,"\","/"), .internalName)
13441343
}
13451344
set itemList(internalName) = ""
13461345
}
@@ -1539,7 +1538,7 @@ ClassMethod ExportItem(InternalName As %String, expand As %Boolean = 1, force As
15391538
set filenames($I(filenames)) = filename
15401539
write !, "exporting new version of ", InternalName, " to ", filename
15411540
if (type = "ptd") {
1542-
$$$QuitOnError(##class(%Studio.SourceControl.Production).ExportPTD(InternalName,"FullExternalName"))
1541+
$$$QuitOnError(##class(SourceControl.Git.Production).ExportPTD(InternalName,"FullExternalName"))
15431542
} elseif (..ItemIsProductionToDecompose(InternalName, .productionName)) {
15441543
write !, "Production decomposition enabled, skipping export of production class"
15451544
} else {
@@ -1559,8 +1558,7 @@ ClassMethod ItemIsProductionToDecompose(InternalName, Output productionName)
15591558
set settings = ##class(SourceControl.Git.Settings).%New()
15601559
set name = $piece(InternalName,".",1,*-1)
15611560
set decomposeProduction = settings.decomposeProductions && (..Type(InternalName) = "cls")
1562-
&& $$$comClassDefined("%Studio.SourceControl.Production")
1563-
&& ##class(%Studio.SourceControl.Production).IsProductionClass(name, "FullExternalName")
1561+
&& ##class(SourceControl.Git.Production).IsProductionClass(name, "FullExternalName")
15641562
if decomposeProduction {
15651563
set productionName = name
15661564
}
@@ -2148,8 +2146,8 @@ ClassMethod Name(InternalName As %String, ByRef MappingExists As %Boolean) As %S
21482146
// If no specific mapping was specified (p=""), then return the whole csp filename; otherwise return the name without the mapped piece
21492147
set InternalName=$extract(InternalName,$length(p)+2,*)
21502148
quit $translate(found_$translate(InternalName,"%","_"),"\","/")
2151-
} elseif (..Type(InternalName) = "ptd") && $$$comClassDefined("%Studio.SourceControl.Production") {
2152-
do ##class(%Studio.SourceControl.Production).ParseInternalName(InternalName,'default,.filename)
2149+
} elseif (..Type(InternalName) = "ptd") {
2150+
do ##class(SourceControl.Git.Production).ParseInternalName(InternalName,'default,.filename)
21532151
return $translate(found_filename, "\","/")
21542152
} elseif ext="CLS"||(ext="PRJ")||usertype {
21552153
set nam=$replace(nam,"%", ..PercentClassReplace())

0 commit comments

Comments
 (0)