Article
· Mar 19, 2023 9m read

Tutorial from Real Practice in China Hosipital Infomatics Construction: How to autobackup your code/ auto excute code when you are not allowed to use Git?

1.Background

        1.1 I met a few project that their interface servers were crashed. Cutoms wanted resume server as fast as we can. their servers are running at lan,and they can't use git,there are some namesapce in the server running different service,and usualy there is only one server.

        1.2 In the message,it has property in type of characterstream,as you know,the message search page doesn't support filtering with  property of characterstream,so it's so hard to find the messge you want.

        1.3 Other workmate may update the code on the server,and mybe their is something wrong.

2.Challenge

         2.1 how to resume bussiness quickly?

        2.2 how to support filtering with  property of characterstream?

       2.3 how to auto make a copy when the import class are edited?

3.Solution

auto make a copy when the import class are edited

first ,we define a class named "SYS.base", which there is only a parameter named " CLSBAKPATH",and set a file path value to is,such as

Class SYS.Base Extends %RegisteredObject
{ Parameter CLSBAKPATH = "D:\IRIS\CLSBAK"; }

then, define a class named " SYS.Projection", it must Extends base and %Projection.AbstractProjection,add " Projection Reference As SYS.Projection",overwrite the classmethod " CreateProjection" ;

the class look like :

Class SYS.Projection Extends (%Projection.AbstractProjection, Base)
{

Projection Reference As SYS.Projection;
ClassMethod CreateProjection(classname As %String, ByRef parameters As %String, modified As %String, qstruct) As %Status
{
  w !
  s changetime=^oddDEF(classname,63)
  s changetime=$zdt(changetime,3)
  w "class:"_classname_" is modified at "_changetime_" !",!
  s CLSBAKPATH=..#CLSBAKPATH
  i CLSBAKPATH["/" d
  .s PL="/"
  e  d
  .s PL="\"
  s:$e(CLSBAKPATH,*)'=PL CLSBAKPATH=CLSBAKPATH_PL
  s sc=$SYSTEM.OBJ.Export(classname_".cls",CLSBAKPATH_classname_$tr(changetime," :-","")_".xml")
  q $$$OK
} }


the classmethod shows class will be exported to the aimed path “ CLSBAKPATH” direct to,and it name will look like 'classname20230217130218.xml' 

last,how to use it?

choose a class which will be autobackuped,make it Extends  "SYS.Projection" 。so when the class is compiled,it will be exported 。if you recompile it without modify,it will cover the old backup file。

by the way,i think the key point is %Projection.AbstractProjection,with “CreateProjection”, we can do more,if you want to update some code in wok-environment,and then excute some aimed method by someone who don't know code,this is a good way。what he need to do is importing xml file to studio, and compile it ,if he don't check the "Compile Imported Items " box.

so far,it only  bakups a single class file。what if we want backup more?

here is a way!simply to say ,make a task!

it usually used in backuping interface code and data.

in this tutorial,we backup file in local drive.

first,we define a class named "Common.SYS"

then add a classmethod named " Export",it's the main method,which will call others functional method. a. Export class,b. Export Credentials,c. Export GLB.

the class look likes:

Class Common.SYS Extends %RegisteredObject,
{ /// Exportclass
ClassMethod ExportAllClassesIndividual(NS As %String = "")
{
s Drive=..#Drive
s currentns=$ZUTIL(67,6,$j)
i NS'="" d
.w:'$d(^["%sys"]CONFIG("Namespaces",NS)) "namespace  "_NS_" is not existed,please check it!",!
.q:'$d(^["%sys"]CONFIG("Namespaces",NS))
.zn NS
.s FilePath=Drive_":/BakFile/"_NS_"/"
.i '##class(%File).DirectoryExists(FilePath) d
..d ##class(%File).CreateDirectoryChain(FilePath)
.s sc=$system.OBJ.ExportAllClassesIndividual(FilePath,"b",.el,"","CDYZone,Sample,User")
.i sc'=1 d
..w " namespace "_NS_" get an error when export class! "_$System.Status.GetErrorText(sc),!
e  d
.s alns=""
.f  s alns=$o(^["%sys"]CONFIG("Namespaces",alns)) q:alns=""  d
..q:$e(alns,1)="%"
..q:(alns="ENSDEMO")||(alns="ENSEMBLE")||(alns["-")
..q:'$d(^[alns]CacheMsg("Confirm"))
..zn alns
..s FilePath=Drive_":/BakFile/"_alns_"/"
..i '##class(%File).DirectoryExists(FilePath) d
...d ##class(%File).CreateDirectoryChain(FilePath)
..s sc=$system.OBJ.ExportAllClassesIndividual(FilePath,"b",.el,"","Common,SYS")
..i sc'=1 d
...w " namespace "_alns_" get an error when export class! "_$System.Status.GetErrorText(sc),!
zn currentns
q $$$OK
} /// Ens.Config.Credentials
ClassMethod ExportAllCredentials(NS As %String = "")
{
s Drive=..#Drive
s currentns=$ZUTIL(67,6,$j)
i NS'="" d
.w:'$d(^["%sys"]CONFIG("Namespaces",NS)) "namespace "_NS_" is not existed,please check it!",!
.q:'$d(^["%sys"]CONFIG("Namespaces",NS))
.q:'$d(^[NS]Ens.Conf.CredentialsD)
.zn NS
.s Data=[]
.s FilePath=Drive_":/BakFile/"_NS_"/Ens_Config_Credentials"
.i '##class(%File).DirectoryExists(FilePath) d
..d ##class(%File).CreateDirectoryChain(FilePath)
.s Data=[]
.s CID=""
.f  s CID=$o(^Ens.Conf.CredentialsD(CID)) q:CID=""  d
..s obj=##class(Ens.Config.Credentials).%OpenId(CID)
..q:'$IsObject(obj)
..s data={}
..s data.SystemName=obj.SystemName
..s data.Username=obj.Username
..s data.Password=obj.Password
..d Data.%Push(data)
.d:Data.%Size()>0 ..SaveFile(FilePath,Data.%ToJSON())
e  d
.;w currentns,!
.s alns=""
.f  s alns=$o(^["%sys"]CONFIG("Namespaces",alns)) q:alns=""  d
..q:$e(alns,1)="%"
..q:(alns="ENSDEMO")||(alns="ENSEMBLE")||(alns["-")
..q:'$d(^[alns]CacheMsg("Confirm"))
..q:'$d(^[alns]Ens.Conf.CredentialsD)
..zn alns
..s FilePath=Drive_":/BakFile/"_alns_"/Ens_Config_Credentials"
..i '##class(%File).DirectoryExists(FilePath) d
...d ##class(%File).CreateDirectoryChain(FilePath)
..s Data=[]
..s CID=""
..f  s CID=$o(^Ens.Conf.CredentialsD(CID)) q:CID=""  d
...s obj=##class(Ens.Config.Credentials).%OpenId(CID)
...q:'$IsObject(obj)
...s data={}
...s data.SystemName=obj.SystemName
...s data.Username=obj.Username
...s data.Password=obj.Password
...d Data.%Push(data)
..d:Data.%Size()>0 ..SaveFile(FilePath,Data.%ToJSON())
zn currentns
q $$$OK
}

ClassMethod SaveFile(FilePath, Data)
{
s file=##class(%FileCharacterStream).%New()
s file.Filename=FilePath_"/Credentials.JSON"
d file.Write(Data)
s sc=file.%Save()
i sc'=1 d
.w FilePath_" save Credential failed! "_$System.Status.GetErrorText(sc),!
e  d
.w FilePath_" save Credential success! ",!
q $$$OK
}

ClassMethod SaveGLB(NS)
{
s Drive=..#Drive
w:'$d(^["%sys"]CONFIG("Namespaces",NS)) "namespace "_NS_" is not existed,please check it!",!
q:'$d(^["%sys"]CONFIG("Namespaces",NS)) 1
s rset = ##class(%ResultSet).%New("%SYS.GlobalQuery:NameSpaceList")
q:'rset.QueryIsValid() "invalid Query:"
d rset.Execute(NS,"CDY*")
s Flag=0
while (rset.Next()){
s HasData=rset.GetDataByName("HasData")
i HasData=1 d
.s Name=rset.GetDataByName("Name")
.q:Name["PushConfigListD" ;some needed globals
.s Data(Name)=1
.s Flag=1
}
w:Flag=0 NS_" namespace doesn't have global to be exported",!
q:Flag=0 NS_" namespace doesn't have global to be exported"
s FilePath=Drive_":/BakFile/"_NS_"/GLB/"
i '##class(%File).DirectoryExists(FilePath) d
.d ##class(%File).CreateDirectoryChain(FilePath)
q ##class(%Library.Global).Export(NS,.Data,FilePath_"SYS.gof")
}

/// ExportAll CDY* Globals
ClassMethod ExportAllCDYGLB(NS As %String = "")
{ s currentns=$ZUTIL(67,6,$j)
i NS'="" d
.w:'$d(^["%sys"]CONFIG("Namespaces",NS)) "namespace "_NS_" is not existed,please check it!",!
.q:'$d(^["%sys"]CONFIG("Namespaces",NS))
.s sc=..SaveGLB(NS)
.i sc'=1 d
..w " namespace "_NS_" get an error when export global! "_$System.Status.GetErrorText(sc),!
e  d
.s alns=""
.f  s alns=$o(^["%sys"]CONFIG("Namespaces",alns)) q:alns=""  d
..q:$e(alns,1)="%"
..q:(alns="ENSDEMO")||(alns="ENSEMBLE")||(alns["-")
..q:'$d(^[alns]CacheMsg("Confirm"))
..s sc=..SaveGLB(alns)
..i sc'=1 d
...w " namespace"_alns_" get an error when export global! "_$System.Status.GetErrorText(sc),!
zn currentns
q $$$OK
}

/// location to store backuped file
Parameter Drive = "d";
ClassMethod Export()
{
s NS="" ///namespace
d ..ExportAllClassesIndividual(NS) ///class file(include package)
d ..ExportAllCredentials(NS) ///Credential information
d ..ExportAllCDYGLB(NS) ///Global file, or  data
q $$$OK
}
}

add a custom task to iris,you will have the last code and data.

optiminzing message search

i see  Dmitry Maslennikov thinks Projection is a bad way。i should tell something. when i get the solution of making messge search page support filtering with property in type of characterstream(to solve the problem,i ask intersystems's engineer,she told me,use " EnsPortal.MsgFilter.Assistant" ,overwrite the classmethod " GetSQLCondition" . so i make a class extends EnsPortal.MsgFilter.Assistant.it seems have solve the problem.After complied the class and then   s ^EnsPortal.Settings("MessageViewer","AssistantClass")=class

). i write code as below:


Class ENSLIB.MsgFilterAssistant Extends EnsPortal.MsgFilter.Assistant
{

ClassMethod GetSQLCondition(pOperator As %String, pProp As %String, pValue As %String, pDisplay As %Boolean = 0) As %String
{
  if (pValue = "") || ((pOperator '= "Like") && (pOperator '= "NotLike")) quit ##super(pOperator, pProp, pValue, pDisplay)
  
  if ("%%" = $extract(pValue, *-2, *-1))
  {
    set pValue = "'" _ $extract(pValue, 1, *-3) _ "' ESCAPE '" _ $extract(pValue, *) _ "'"
  }
  else
  {
    set pValue = "'" _ $replace(pValue, "'", "''") _ "'"
  }
  quit "substring(" _ pProp _ ", 1, 3000000) " _ $case(pOperator, "Like": "LIKE", "NotLike": "NOT LIKE") _ " " _ pValue
}

/// w ##class(ENSLIB.MsgFilterAssistant).SetMV()
ClassMethod SetMV()
{
	s ^EnsPortal.Settings("MessageViewer","AssistantClass")="ENSLIB.MsgFilterAssistant"
	q $$$OK
}

}

after import the code, someone must excute '##class(ENSLIB.MsgFilterAssistant).SetMV()' ,then  it will work! so i think if there is a way that after importing or compiling,it will auto run SetMV ,i look for the solution ,from forum to open exchange,finally i  got it,it's Projection.so ,code change to this:


Class ENSLIB.MsgFilterAssistant Extends (EnsPortal.MsgFilter.Assistant, %Projection.AbstractProjection)
{

Projection Reference As MsgFilterAssistant;
ClassMethod GetSQLCondition(pOperator As %String, pProp As %String, pValue As %String, pDisplay As %Boolean = 0) As %String
{
  if (pValue = "") || ((pOperator '= "Like") && (pOperator '= "NotLike")) quit ##super(pOperator, pProp, pValue, pDisplay)
  
  if ("%%" = $extract(pValue, *-2, *-1))
  {
    set pValue = "'" _ $extract(pValue, 1, *-3) _ "' ESCAPE '" _ $extract(pValue, *) _ "'"
  }
  else
  {
    set pValue = "'" _ $replace(pValue, "'", "''") _ "'"
  }
  quit "substring(" _ pProp _ ", 1, 3000000) " _ $case(pOperator, "Like": "LIKE", "NotLike": "NOT LIKE") _ " " _ pValue
}

/// w ##class(ENSLIB.MsgFilterAssistant).SetMV()
ClassMethod SetMV()
{
	s ^EnsPortal.Settings("MessageViewer","AssistantClass")=$this
	q $$$OK
}

ClassMethod CreateProjection(cls As %String, ByRef params) As %Status
{
	w !
	w "开始执行",!
	d ..SetMV()
	w "执行完成",!
	q $$$OK
}

}

  after compiling the class, it will auto excute SetMV,this is wanted! and  this is why i use Projection! is there another way that behavior as same as this effect?  i think all we should make work more simpler.

if you have better way,please share it!

4.More

i will continue to find a better way to make a copy of source class and config data.

Discussion (11)4
Log in or sign up to continue

Sorry, but it is the most horrible way to do it.

too old-school, the code has been outdated for many years. Dots syntax in 2023, seriously?

Projections are definitely not a way to solve it and did not get why they were even considered here

The best way to go is using %Studio.SourceControl, there are a lot of examples, and even some are out of the box already.

And most modern way now is to switch from Studio to VSCode, and to local-side development. So, all your classes will always be as files and can be synced to the git repository.

thank you,because of most cases use old version cache,it don't support %Studio.SourceControl. do you know why Projections is a bad way? what  disaster  does it will cause? one side is backup local code,i backup it to local,and then upload to gitee, i tried to used gitee’s callback function to autobackup,but failed。and the second side is to make backuping and updating code being simplly.Implementation Engineers want a simple way.