go to post Timothy Leavitt · Apr 13, 2016 For batch/shell scripts, ccontrol runw may be better. You can see all the options with: ccontrol help With ccontrol runw, spaces are accepted; for example, this should work (after replacing <instancename> with the name of your Caché instance): ccontrol runw <instancename> ^ANDYTST(\"c:\folder with spaces\\\") USER I'm not sure if there are options other than OS authentication (which, if enabled, has such commands run as the Caché user matching the OS-level username). This post may also be relevant: https://community.intersystems.com/post/how-return-status-code-cache-pro...
go to post Timothy Leavitt · Apr 5, 2016 Here's a simple example that'll run in the Samples namespace. It demonstrates saving all the data at the same time and saving it one row at a time after each cell is edited. Class App.Sample.DataGridPage Extends %ZEN.Component.page { /// This Style block contains page-specific CSS style definitions. XData Style { <style type="text/css"> #dataGrid { width: 100%; height: 500px; } </style> } /// This XML block defines the contents of this page. XData Contents [ XMLNamespace = "http://www.intersystems.com/zen" ] { <page xmlns="http://www.intersystems.com/zen" title="dataGrid save sample"> <jsonSQLProvider id="json" OnSubmitContent="SubmitContent" targetClass="%ZEN.proxyObject" sql="select ID,Name,DOB,SSN from sample.person order by name" /> <dataGrid pageSize="20" id="dataGrid" pagingMode="client" controllerId="json" sortMode="client" selectMode="cells" onchangecell="return zenPage.fireChangeCell(value);" onchange="zenPage.gridChanged();"> <columnDescriptor caption="ID" type="string" readOnly="false"/> <columnDescriptor caption="Name" type="string" readOnly="false"/> <columnDescriptor caption="DOB" type="string" readOnly="false"/> <columnDescriptor caption="SSN" type="string" readOnly="false"/> </dataGrid> <hgroup labelPosition="left" cellAlign="even"> <radioSet id="modeRadio" valueList="edit,manual" displayList="After Each Edit,Manually" label="Save Data: " value="edit" /> <button onclick="zen('json').submitContent()" caption="Save Everything" /> </hgroup> </page> } ClientMethod fireChangeCell(value) [ Language = javascript ] { // Capture the number of the last row that was changed. zenPage._lastChangedRow = zen('dataGrid').getProperty('currRow'); return value; } ClientMethod gridChanged() [ Language = javascript ] { if (zen('modeRadio').getValue() == 'edit') { zen('json').submitContent('saveRow:'+zenPage._lastChangedRow); } } Method SubmitContent(pCommand As %String, pProvider As %ZEN.Auxiliary.jsonProvider, pSubmitObject As %ZEN.proxyObject, ByRef pResponseObject As %RegisteredObject) As %Status { Set tSC = $$$OK Try { TSTART If (pCommand = "") { //Save everything. For { Set tProxy = pSubmitObject.children.GetNext(.tKey) Quit:tKey="" $$$ThrowOnError(..SavePersonProxy(tProxy)) } } Else { Set tCommandInfo = $ListFromString(pCommand,":") If ($lg(tCommandInfo,1) = "saveRow") { //Save only the specified row (faster) Set tData = pSubmitObject.children.GetAt($lg(tCommandInfo,2)) If $IsObject(tData) { Set tObj = ##class(Sample.Person).%OpenId(tData.ID,,.tSC) $$$ThrowOnError(tSC) Set tObj.Name = tData.Name Set tObj.SSN = tData.SSN Set tObj.DOB = $zdh(tData.DOB) $$$ThrowOnError(tObj.%Save()) } Else { $$$ThrowStatus($$$ERROR($$$GeneralError,"An error occurred saving row "_$lg(tCommandInfo,2))) } } } TCOMMIT } Catch anyException { TROLLBACK Set tSC = anyException.AsStatus() } Quit tSC } Method SavePersonProxy(pProxy As %ZEN.proxyObject) As %Status { Set tObj = ##class(Sample.Person).%OpenId(pProxy.ID,,.tSC) Quit:$$$ISERR(tSC) tSC Set tObj.Name = pProxy.Name Set tObj.SSN = pProxy.SSN Set tObj.DOB = $zdh(pProxy.DOB) Quit tObj.%Save() } }
go to post Timothy Leavitt · Apr 1, 2016 Presumably, if you're showing the results in a report sorted based on the currently-selected column (in the currColumn property of the tablePane), you could also look at the sort order for the tablePane (sortOrder property, "asc" or "desc") and then $order over the index global in reverse if it's "desc". Here's a class query/example that could help - you can modify the ROWSPEC to fit your purposes. /// Queries snapshot data for a Zen tablePane, optionally sorted. /// QuerySnapshotExecute returns an error if the snapshot or a required index (for sorting) is missing. /// /// sessionId : CSP session ID of the user whose tablePane will be shown /// snapshotId : snapshotId property of the tablePane /// tablePaneIndex : index property of the tablePane /// sortColumn : (optional) currColumn property of the tablePane: the column name of the column to sort by /// sortOrder : (optional; default is ascending) sortOrder property of the tablePane (asc/desc) Query QuerySnapshot(sessionId As %String, snapshotId As %Integer, tablePaneIndex As %Integer, sortColumn As %String = "", sortOrder As %String = "") As %Query(ROWSPEC = "col1:%String,col2:%String,col3:%String,col4:%String,col5:%String,col6:%String,col7:%String,col8:%String,col9:%String,col10:%String,col11:%String") [ SqlProc ] { } ClassMethod QuerySnapshotExecute(ByRef qHandle As %Binary, sessionId As %String, snapshotId As %Integer, tablePaneIndex As %Integer, sortColumn As %String = "", sortOrder As %String = "") As %Status { Set tDataGlobal = "^CacheTemp.zenData("""_sessionId_""","_snapshotId_","_tablePaneIndex_",""data"")" Quit:'$Data(@tDataGlobal) $$$ERROR($$$GeneralError,"Invalid reference to tablePane snapshot.") Set tIndexGlobal = $Case(sortColumn,"":"",:"^CacheTemp.zenData("""_sessionId_""","_snapshotId_","_tablePaneIndex_",""index"","""_sortColumn_""")") Quit:'$Data(@tIndexGlobal) $$$ERROR($$$GeneralError,$$$FormatText("tablePane snapshot index not populated for property %1",sortColumn)) Set qHandle = $ListBuild(tDataGlobal,tIndexGlobal,$Case(sortOrder,"desc":-1,:1),"","") Quit $$$OK } ClassMethod QuerySnapshotFetch(ByRef qHandle As %Binary, ByRef Row As %List, ByRef AtEnd As %Integer = 0) As %Status [ PlaceAfter = QuerySnapshotExecute ] { Set $ListBuild(tDataGlobal,tIndexGlobal,tSortOrder,tSub1,tSub2) = qHandle If (tIndexGlobal = "") { // Not sorting by any column. Set tSub2 = $Order(@tDataGlobal@(tSub2),tSortOrder) If (tSub2 = "") { Set AtEnd = 1 } } Else { // First $order over values of the indexed column Set:tSub1="" tSub1 = $Order(@tIndexGlobal@(tSub1),tSortOrder) If (tSub1 '= "") { // There may be multiple matches for a single key in the index. Get the next one for this key. Set tSub2 = $Order(@tIndexGlobal@(tSub1,tSub2),tSortOrder) // If we previously were on the last value for the index key, move on to the next index key. If (tSub2 = "") { Set tSub1 = $Order(@tIndexGlobal@(tSub1),tSortOrder) Set:tSub1'="" tSub2 = $Order(@tIndexGlobal@(tSub1,tSub2),tSortOrder) } } If (tSub1 = "") && (tSub2 = "") { Set AtEnd = 1 } } If 'AtEnd { Set Row = @tDataGlobal@(tSub2) Set $List(qHandle,4) = tSub1 Set $List(qHandle,5) = tSub2 } Quit $$$OK } ClassMethod QuerySnapshotClose(ByRef qHandle As %Binary) As %Status [ PlaceAfter = QuerySnapshotExecute ] { Quit $$$OK } Sample use, against /csp/samples/ZENTest.TableTest.cls (and, in my case, with the class query defined in App.TablePaneUtils): call App.TablePaneUtils_QuerySnapshot(<your session ID>,<your snapshot number>,23) call App.TablePaneUtils_QuerySnapshot(<your session ID>,<your snapshot number>,23,,'desc') call App.TablePaneUtils_QuerySnapshot(<your session ID>,<your snapshot number>,23,'Title','asc')
go to post Timothy Leavitt · Mar 28, 2016 An easier way to capture the stack and values of variables at all stack levels is: Do LOG^%ETN Then it's possible to view full information on stack and variables from terminal with: Do ^%ER Or in the management portal at System Operation > System Logs > Application Error Log. If you're logging to globals to track more limited parts of the process state for debugging, it's helpful to use a ^CacheTemp* or ^mtemp* global so that the debugging information (a) isn't rolled back by TROLLBACK and (b) won't accumulate in an important database if the debugging code is accidentally left in.
go to post Timothy Leavitt · Mar 21, 2016 A few side notes... The correct/best way to create a %Object from a %RegisteredObject (or vice versa) is $compose, not $fromObject (which has been marked as Internal in more recent builds). This is first available in 2016.2. SAMPLES>set person = ##class(Sample.Person).%OpenId(1) SAMPLES>set obj = {}.$compose(person) SAMPLES>w obj.$toJSON() {"Age":88,"DOB":31520,"FavoriteColors":["Blue"],"Home":{"City":"Youngstown","State":"CO","Street":"1360 Oak Avenue","Zip":74578},"Name":"Tillem,Terry Y.","Office":{"City":"Gansevoort","State":"KY","Street":"4525 Main Court","Zip":93076},"SSN":"132-94-8739"} Also, you can get %RegisteredObjects as JSON more directly: SAMPLES>set person = ##class(Sample.Person).%OpenId(1) SAMPLES>w person.$toJSON() {"Age":88,"DOB":31520,"FavoriteColors":["Blue"],"Home":{"City":"Youngstown","State":"CO","Street":"1360 Oak Avenue","Zip":74578},"Name":"Tillem,Terry Y.","Office":{"City":"Gansevoort","State":"KY","Street":"4525 Main Court","Zip":93076},"SSN":"132-94-8739"}
go to post Timothy Leavitt · Mar 18, 2016 Try: Do $System.Process.Terminate(,exitCode) See documentation for reference. On older versions I believe the equivalent is: Do $zu(4,$job,exitCode) But this shouldn't be used if the nicer method is available.
go to post Timothy Leavitt · Mar 17, 2016 Actually - if you're going to do this after issuing the Get, you should use request.Get(,,0), then do this, then call Reset() on the request manually. Otherwise the parameters will get cleared out and the query string will always be blank.
go to post Timothy Leavitt · Mar 17, 2016 I'd suggest something like this, after calling Get: Set tParams = request.ReturnParams() Set tQuery = $Case(tParams,"":"",:"?"_tParams) Set tURL = $Select(request.Https:"https://",1:"http://")_request.Server_":"_request.Port_"/" _request.Location_tQuery
go to post Timothy Leavitt · Mar 16, 2016 1/2. I use the "My Content" page to quickly get back to my own posts. I don't use "My Collaborations" or understand how the content that appears there is determined.3. I'd expect to see my own posts (and perhaps answers) on "My Content" and for "My Collaborations" to show links to questions that I have answered and to posts/answers that I have commented on. Answers could fit in either category (or both); I'm not sure if those are best understood as content or as a particular type of comment.4. Show the content described in (3) on these pages. Also, for answers, rather than showing "answer406256" as the title (for example), show "Answer: <title of question>" and link to the answer within the question page rather than the answer on its own. I think the same would also apply to comments shown on "My Collaborations" (if that approach is taken). If "My Collaborations" shows comments it might make sense to group them by post, in case there's a very active back-and-forth.
go to post Timothy Leavitt · Mar 15, 2016 Here are two perspectives, from different development workflows:My team (working on a large Caché-based application) does development on a newer Ensemble version than the version on which our software is released. We perform functional and performance testing against the older version. Most of the time, moving code from newer versions to older works just fine; when it does fail, it tends to be very obvious. The last class dictionary version change was in 2011.1, so that isn’t a concern for upgrades involving recent versions. These used to be much more frequent. Working this way provides a good sort of pressure for us to upgrade when we find new features that the old version doesn’t have or performance improvements on the newer version. It also eases concerns about future upgrades to the version on which the application has been developed.For Caché-based internal applications at InterSystems, we have separate designated dev/test/live environments. Application code changes typically spend a relatively short time in dev, and a much shorter time in test, before going live. Upgrades are typically done in a short time frame and move through the environments in that order. It would be incredibly risky to upgrade the live environment first! Rather, our process and validation for upgrades go through the same process as functional changes to these applications. The dev environment is upgraded first; this might take time if there are application issues found in testing after the upgrade. The test environment is upgraded next, typically a very short time before the live environment is upgraded. It's OK if code changes are made in dev before the test environment is upgraded, because changes will still be compiled and tested in the older Caché/Ensemble version prior to going live. Of course, if testing fails, the upgrade may become a prerequisite for the given change to the application. Additionally, we periodically clone the test environment for upgrades to and validation against field test versions. Using virtual machines makes this very easy.
go to post Timothy Leavitt · Mar 14, 2016 This should accomplish what you want: var tablePane = zen('yourTablePaneId'); // Don't actually execute the query the next time the table is rendered. tablePane.setProperty('initialExecute',false); // Clear the snapshot (cached results of the query, used for quick pagination) before re-rendering. // This is irrelevant if the tablePane isn't using snapshots. tablePane.setProperty('clearSnapshot',true); // Regenerate HTML for the tablePane - it'll be empty. tablePane.refreshContents(); Or, a bit more simply: var tablePane = zen('yourTablePaneId'); // Don't actually execute the query the next time the table is rendered. tablePane.setProperty('initialExecute',false); // Re-execute the query (this also clears the snapshot if snapshots are in use) tablePane.executeQuery(); If refreshContents() / executeQuery() is called again, query results will be shown, because initialExecute is set to true each time the tablePane is drawn.
go to post Timothy Leavitt · Mar 11, 2016 One solution would be to look at the audit database. It's not pretty, but there might not be any other way. ClassMethod GetLoginService() As %String { New $Namespace Zn "%SYS" Set tService = "" // Ensure that login events are being audited. Set tLoginEvent = ##class(Security.Events).Get("%System","%Login","Login",.tProps) If '$Get(tProps("Enabled")) { // Querying the audit DB probably won't do any good. Quit tService } // Warning: on systems with a lot of activity, this query might take a long time. // It might be worth filtering by recent UTCTimeStamp, assuming processes won't be that long-running. Set tRes = ##class(%SQL.Statement).%ExecDirect(, "select top 1 EventData from %SYS.Audit "_ "where EventSource = '%System' and EventType = '%Login' and Event = 'Login' and PID = ? "_ "order by UTCTimeStamp DESC, SystemID DESC, AuditIndex DESC",$Job) Set tHasResult = tRes.%Next() If (tHasResult) { Set tData = tRes.%Get("EventData") //NOTE: This makes assumptions about the format of EventData. //Particularly, that it looks something like: /* Service name: %Service_Bindings Login roles: %All $I: |TCP|1972|15396 $P: |TCP|1972|15396 */ // Set tFirstLine = $Piece(tData,$c(13,10)) //Presumably "Service name:" might be localized, but %Service_<something> would not be. Set:tFirstLine["%Service" tService = "%Service"_$Piece(tFirstLine,"%Service",2) } Quit tService } Note: if your application is using Caché security correctly, you'd probably need to define a privileged routine application to allow access to Security.Events and the audit database.
go to post Timothy Leavitt · Feb 18, 2016 Specifics on %CSP.Page / cspbind error handling:There are alternatives to modifying system-level error message translations. Even if the localization is changed, the end user would probably still see "SQLCODE -139," which isn't helpful. What you probably want to show the user is "Someone else has modified this record. Please reload the page and try again." or something similar.Using %CSP.Page and a form with cspbind, there's no way to customize the error handling in the generated <form name>_save function. It seems to always show any error message in an alert. An alternative would be to use a similar approach to form.csp and formsubmit.csp in the SAMPLES namespace, submitting the form and calling the <form name>Submit method of the page rather than using a hyperevent (in form_save()). Note that you don't need a separate page to handle the form submit; one page can do it all.Of course, if the form is saved with a POST rather than a hyperevent, and optimistic concurrency control is in use, it might be worth a separate call to the server immediately before submitting the form for an optimistic concurrency check. If someone else edited the record, the page could tell the user rather than submitting the form. This wouldn't prevent the error in question, but would at least make it much less likely.(Side note: %CSP.Page/cspbind is very old technology. There are better tools available now, but if you're stuck with it in a large existing project, that's understandable.) General thoughts on showing users friendly error messages:The more general problem is: "Often, the error codes and messages the system produces are not helpful to an end user. How do you show the user the information they need when an error occurs?"This gets more complicated because error messages can come from very different sources - for example, SQL, an object %Save, a variable being undefined, or the application detecting a user error.The standard for the large Caché-based application I work on is:Within UI classes (class-based CSP or Zen, in our case), server-side code is wrapped in try-catch blocks.Different types of exceptions (subclasses of %Exception.AbstractException) are thrown for the different types of errors that occur. These may be:SQLCODEs and associated messages from embedded or dynamic SQLError %Status codes from object %Saves and various other thingsGeneral internal errors in application business logicUser errors detected in business logic. (These are treated differently for logging purposes; user errors may be more common than system errors and might not be logged.)System errors (<UNDEFINED>, <SUBSCRIPT>, etc.)Any of these types of exceptions. except system errors, may include a user-friendly message explaining what went wrong.If an exception is caught, a method is called to (1) log that an error occurred and (2) either provide a description of the error that the user can understand, or else just say that an error happened (with the confusing details omitted, although they are logged).If the exception already has a friendly message for the user, it's returned.Otherwise, for error SQLCODEs and error %Status codes, we provide general messages for concurrency-related errors and some other common error codes. We also have a system for interpreting foreign and unique key violations (from an SQLCODE or %Status) and providing more descriptive messages based on those.Worst case, the message "An internal error occurred (log ID _____)" is returned.The message is shown to the user in red text, an alert dialog (ideally not a vanilla JavaScript alert), or however else makes sense in context.There are macros for the different types of exceptions and for getting the user-friendly message, for ease of maintainability.
go to post Timothy Leavitt · Feb 16, 2016 This has been bothering me a little bit; %Dictionary.* should really be a last-resort option, in my opinion, and it isn't easy to use it to get the full picture from an SQL perspective.Here are some alternative/possibly-better solutions, using %SQL.StatementMetadata and INFORMATION_SCHEMA. It looks like INFORMATION_SCHEMA is more exactly what you were looking for, if you're running on a recent enough Caché version (2015.1+). I haven't been able to find documentation on it other than the class reference, though. /// NOTE: It could be good to validate pTableName to avoid SQL injection. (Outside the scope of this demo.)/// This works pre-2015.1 (since %SQL.Statement was introduced - maybe 2012.2+?)ClassMethod GetTableColumns(pTableName As %String) As %List{ #dim tResult As %SQL.StatementResult #dim tMetadata As %SQL.StatementMetadata Set tStmt = ##class(%SQL.Statement).%New() $$$ThrowOnError(tStmt.%Prepare("select top 0 * from "_pTableName)) Set tResult = tStmt.%Execute(), tMetadata = tResult.%GetMetadata() Set tCols = "" For i=1:1:tMetadata.columnCount { Set tCols = tCols_$ListBuild(tMetadata.columns.GetAt(i).colName) } Quit tCols}/// This will only work on 2015.1+; INFORMATION_SCHEMA is a new feature. For more information, see the class reference for it in the documentation.ClassMethod GetTableColumnsNew(pTableName As %String) As %List{ #dim tResult As %SQL.StatementResult Set tStmt = ##class(%SQL.Statement).%New() Set tSchema = $Piece(pTableName,".") Set tTableName = $Piece(pTableName,".",2) $$$ThrowOnError(tStmt.%Prepare("select COLUMN_NAME from INFORMATION_SCHEMA.COLUMNS where TABLE_SCHEMA = ? and TABLE_NAME = ?")) Set tResult = tStmt.%Execute(tSchema,tTableName) Set tCols = "" While tResult.%Next(.tSC) { Set tCols = tCols_$ListBuild(tResult.%Get("COLUMN_NAME")) } $$$ThrowOnError(tSC) Quit tCols}Using this:SAMPLES>set cols = ##class(Demo.TableColumns).GetTableColumns("Sample.Person")SAMPLES>w $lts(cols)ID,Age,DOB,FavoriteColors,Name,SSN,Spouse,Home_City,Home_State,Home_Street,Home_Zip,Office_City,Office_State,Office_Street,Office_ZipSAMPLES>set cols = ##class(Demo.TableColumns).GetTableColumnsNew("Sample.Person")SAMPLES>w $lts(cols) ID,Age,DOB,FavoriteColors,Name,SSN,Spouse,Home_City,Home_State,Home_Street,Home_Zip,Office_City,Office_State,Office_Street,Office_Zip
go to post Timothy Leavitt · Feb 9, 2016 In various posts, you've talked about having an implementation tool that is run from the browser, and you've also talked about running it from terminal. Here's a design that would be well-suited to both:* Settings that apply to the whole thing are properties of an object (class extends %RegisteredObject)* There's a class method (static method in other languages) to get the settings' values interactively* There's an instance method to run the setup on an instance of the class Include %syPromptClass Demo.SetupTool Extends %RegisteredObject{Property Type As %String(VALUELIST = ",Gateway,IHE");Property Setting1Value As %String [ InitialExpression = "ABC" ];Property Setting2Value As %Boolean [ InitialExpression = 1 ];ClassMethod RunInteractive(){ Set tSettingsObject = ..%New() Write "Deployment Utility" Set options(1) = "Gateway" Set options(2) = "IHE" Do ##class(%Prompt).GetArray("Deployment Type?",.tType,.options,,,,$$$InitialDisplayMask+$$$MatchArrayMask) Set tSettingsObject.Type = tType Set tString = tSettingsObject.Setting1Value Do ##class(%Prompt).GetString("Enter a string: ",.tString) Set tSettingsObject.Setting1Value = tString Set tYesOrNo = tSettingsObject.Setting2Value Do ##class(%Prompt).GetYesNo("Is this a helpful demo? ",.tYesOrNo) Set tSettingsObject.Setting2Value = tYesOrNo Do tSettingsObject.DoSetup()}Method DoSetup(){ Write !,"Starting ",..Type," deployment...",! // Here, do things based on properties of this object. If '..Setting2Value { //Do something specific if Setting2Value is false. Write "Why not?",! }}} Then, for example:USER>d ##class(Demo.SetupTool).RunInteractive()Deployment Utility 1) Gateway2) IHE Deployment Type? 1 GatewayEnter a string: ABC =>Is this a helpful demo? Yes => NoStarting Gateway deployment...Why not? Or, if you want to do the same kind of setup from a simple ZenMethod in a Zen page or something, you could set up a settings object and then call DoSetup() on it.USER>set tSettings = ##class(Demo.SetupTool).%New() USER>set tSettings.Type = "IHE" USER>d tSettings.DoSetup() Starting IHE deployment...
go to post Timothy Leavitt · Feb 8, 2016 Here's some relevant documentation: http://docs.intersystems.com/cache20152/csp/docbook/DocBook.UI.Page.cls?KEY=GORIENT_ch_cos#GORIENT_cos_scope One way to make this work as-is would be to put tstVar in the "public" list: start set tstVar = "Green" do TestIt() TestIt() [tstVar] { write tstVar } Another would be to give the variable a name starting with a %: start set %tstVar = "Green" do TestIt() TestIt() { write %tstVar }
go to post Timothy Leavitt · Feb 8, 2016 Look at $zconvert (abbreviated $zcvt): http://docs.intersystems.com/cache20152/csp/docbook/DocBook.UI.Page.cls?KEY=RCOS_fzconvert For example: USER>w $zcvt("thisIsATest","U") THISISATEST USER>w $zcvt("thisIsATest","L") thisisatest
go to post Timothy Leavitt · Feb 5, 2016 "GOTO label" jumps to a specified label in the same context frame."DO label" adds a new context frame. Code at the specified label will execute until a QUIT/RETURN or the end of the routine, then execution will resume at the next line after the DO command.So in this case it's running through the whole routine (including everything under the dataentry label, with dtype already set to something valid), including printing the message at the end. Then it proceeds to the next thing after the DO command, which is printing that line again.For what it's worth, if you're building command-line tools, %Library.Prompt may save you a lot of time. See the class reference for more informatioan.For example: #include %syPrompt New options,dtype Write "Deployment Utility" Set options(1) = "Gateway" Set options(2) = "IHE" Do ##class(%Prompt).GetArray("Deployment Type?",.dtype,.options,,,,$$$InitialDisplayMask+$$$MatchArrayMask) Write !, "Starting ", dtype," deployment..." Quit
go to post Timothy Leavitt · Feb 4, 2016 At a Caché level (for namespaces, databases, code, and security), %Installer may be useful; see: http://docs.intersystems.com/cache20152/csp/docbook/DocBook.UI.Page.cls?KEY=GCI_manifest For Ensemble, there are some additional deployment-related features that might do more for you in terms of settings and lookup tables. See: http://docs.intersystems.com/ens20152/csp/docbook/DocBook.UI.Page.cls?KEY=EGDV_deploying#EGDV_deployment_overview
go to post Timothy Leavitt · Jan 21, 2016 Another option, and a caveat about ^%SYS("SystemMode"): It looks like you're using CCR.* "Environment" is a meaningful term in that context. In namespaces configured for CCR, you can retrieve the environment with $$Env^%buildccr, i.e.: CCR>w $$Env^%buildccr BASE It is possible to have multiple namespaces on the same instance configured as different environments. ^%SYS("SystemMode") is set to the furthest-along environment (of BASE-TEST-LIVE) of any namespace on the instance. That is, if you have a namespace configured as BASE, and configure another one as TEST, ^%SYS("SystemMode") will be changed to TEST - and this is system-wide. Just something to keep in mind. *CCR (Change Control Record) is an InterSystems in-house application used in sites where InterSystems does implementation work, so this isn't as generally relevant as ^%SYS("SystemMode").