We specifically chose not to support this capability in Atelier, other than for the special case of launching web pages. As an implementation of Studio Source Control, running an executable on the client only makes sense in cases where the "server" and the "client" are always the same, and for that model the source control plugins publicly and freely available in the Eclipse ecosystem are far superior in terms of support and usability.

I would be interested to hear more details of your use case. What version control system are you using? Git?

(Note to other readers: "UserAction = 3" and other details of the Studio Extension framework are documented here.)

There are two good approaches to this, both relying on an onchange event handler on the first dataCombo.

The first, simpler solution would be to just have an onchange event handler set the value directly:

<dataCombo id="Grupo_Producto" name="Grupo_Producto" dataBinding="bdGrupo" size="40" 
 label="(*) Grupo" dropdownWidth="400" dropdownHeight="100" loadingMessage="Cargando..."
 columnHeaders="Codigo, Nombre"
 queryClass="Innova.CL.Core.BD.Configuracion.Operacional.Grupo" queryName="obtieneGrupoProd"
 sqlLookup="SELECT Nombre FROM Innova_CL_Core_BD_Configuracion_Operacional.Grupo Where ID = ?"
 onchange="zen('Linea_Producto').setProperty('parameters',1,zenThis.getValue());"
 > 
</dataCombo>

However, if you have multiple components on the page that depend on the current value of Grupo_Producto, it might be simpler to use a property of the page that has the value of the component copied to it when that value changes. This is handy because you can reference the value in a Zen expression, e.g., #(%page.GrupoProducto)#, rather than needing to copy the same value to lots of places. Here is an example with two dataCombos in the SAMPLES namespace:

Class DC.Demo.ComboBox Extends %ZEN.Component.page
{

Property homeState As %ZEN.Datatype.string;

XData Contents [ XMLNamespace = "http://www.intersystems.com/zen" ]
{
<page xmlns="http://www.intersystems.com/zen">
<dataCombo id="homeState" label="State"
sql="select distinct Home_State from Sample.Person order by Home_State"
onchange="zenPage.dataComboChanged(zenThis)"
/>
<dataCombo id="homeCity" label="City"
sql="select distinct %exact Home_City from Sample.Person where Home_State = ? order by Home_City">
<parameter value="#(%page.homeState)#" />
</dataCombo>
</page>
}

ClientMethod dataComboChanged(component) [ Language = javascript ]
{
    // Update the page property's value.
    // Note: page property and component ID don't necessarily need to match, but it's convenient if they do.
    // (Imagine a cascade of dataCombos with dependent values.)
    zenPage.setProperty(component.id,component.getValue());
    
    // Possibly also call refreshContents() on components referencing properties that may have changed,
    // and/or clear values that may have been invalidated by the change.
    // dataCombo does not need this, but other components (e.g., tablePane) would.
}

}

I can't speak to a fix, but a few thoughts:

  • There is a critical difference between Studio and Atelier projects: Atelier projects are not even present on the server / in the database. This may prevent them from being used interchangeably with Studio projects for purposes of your source control extension and change control processes.
    • Because of this difference, it would probably not be a good idea to populate ^||%Studio.Project with the name of the project in Atelier.
  • One option might be updating your source control extension (perhaps adding new menu items and UserAction/AfterUserAction handling for them) to support creating a %Studio.Project and associating/dissociating files with one, even from Atelier. This would support mixed use of Studio and Atelier and make migration simpler.
  • If you want to be able to detect in your source control extension code whether it is being called from Studio or Atelier, you can look at the StudioVersion property (defined in %Studio.Extension.Base). Particularly, this will contain the string "Atelier" if invoked from Atelier.

A nice enhancement for Atelier might be supporting a project definition that is shared between the client and server.

The current Atelier project is not exposed. What is your intended use case for it?

Regarding $Username - I suspect that the /api/atelier web application on your instance has only "Unauthenticated" selected under "Allowed Authentication Methods." If you change this to "Password" only and restart Atelier you should see $Username populated correctly.

If you're really interested in efficiency, one option might be put the list into a local array with $ListNext and then iterate over the array in reverse (either with a decrementing counter in a for loop, or using $order(array(sub),-1)). For example, rather than:

For i=$ListLength(tInputList):-1:1 {
    Set tValue = $ListGet(tInputList,i)
    // Do something with tValue
}

You could use something like the following (which accounts for null values in the $ListBuild list by skipping that index in the integer-subscripted array):

Kill tArray
Set tPointer = 0
While $ListNext(tInputList,tPointer,tValue) {
    If $Data(tValue) {
        Set tArray($i(tArray)) = tValue
    } Else {
        Set tArray = tArray + 1
    }
}

Set tKey = ""
For i=$Get(tArray):-1:1 {
    If $Data(tArray(i),tValue) {
        // Do something with tValue
    }
}

For "large enough" lists (the threshold for this depends on the length of the list elements), the second approach will be faster. (In testing, this performs better than putting the reversal of the original $ListBuild list into another $ListBuild list.)

More generally speaking: if you have a list that you only append to the end of (rather than needing to insert/shift elements randomly in the middle of it), and will need to iterate over it in reverse, a local array with integer subscripts is a better choice of data structure than a $ListBuild list.

That's not actually the case. If you look at the .INT code for the generated %CreatePage method of a Zen page with &#65292; in columnHeaders (I tested this out with ZENDemo.FormDemo in the SAMPLES namespace), you'll see something like:

Set dtCmb7.columnHeaders="House number,apartment"

The numeric character reference is converted to the Unicode character when the Zen XML is imported; the reason &#44; doesn't work is that it's converted to a plain old comma, which is no different from just putting a comma in the XML in terms of what Zen does with it.

To expand on Sean's answer from the comment section, perhaps try the numeric character reference for the fullwidth comma:

columnHeaders="House number&#65292;apartment"

Another option would be to create a custom Zen component that extends %ZEN.Component.dataCombo and handles delimiters differently for the columnHeaders property. Unfortunately, this approach would require overriding %DrawDropDownContents, which is an enormous method and flagged as [Internal] - best avoided, in my opinion!

I don't know of any magic $zf/$zu function or API to do this. Looking at StackOverflow, a common approach seems to be parsing the output of "tasklist" filtered by executable name and/or window title.

Here's a classmethod to get the PIDs of all processes with a given executable name (Windows-only, of course):

Class DC.Demo.WindowsProcessList
{

ClassMethod GetPIDsByExecutable(pExecutable As %String = "", Output pPIDList As %List) As %Status
{
    Set tSC = $$$OK
    Set tFileCreated = 0
    Set pPIDList = ""
    Try {
        Set tSC = ##class(%Net.Remote.Utility).RunCommandViaZF("tasklist /V /FO CSV /NH",.tTempFileName,,,0)
        $$$ThrowOnError(tSC)
        Set tFileCreated = (tTempFileName '= "")
        Set tRowSpec = "ROW(ImageName VARCHAR(255),PID INT)"
        Set tResult = ##class(%SQL.Statement).%ExecDirect(,
            "call %SQL_Util.CSV(,?,?)",
            tRowSpec,tTempFileName)
        
        // For debugging (if tRowSpec changes):
        // Do tResult.%Display()
        
        If (tResult.%SQLCODE < 0) {
            Throw ##class(%Exception.SQL).CreateFromSQLCODE(tResult.%SQLCODE, tResult.%Message)
        }
        
        // Note: could instead call tResult.%NextResult() to get a result set with column names as properties.
        While tResult.%Next(.tSC) {
            $$$ThrowOnError(tSC)
            If ($ZConvert(tResult.%GetData(1),"L") = $ZConvert(pExecutable,"L")) {
                Set pPIDList = pPIDList_$ListBuild(tResult.%GetData(2))
            }
        }
        $$$ThrowOnError(tSC)
    } Catch e {
        Set tSC = e.AsStatus()
    }
    
    // Cleanup
    If tFileCreated {
        Do ##class(%Library.File).Delete(tTempFileName)
    }
    Quit tSC
}

}

A simpler workaround seems to be:

if $get(seen(""_$THIS)) quit

And here's another way to demonstrate that something weird is going on under the hood:

SAMPLES>k
 
SAMPLES>d $system.OBJ.ShowObjects()
No registered objects
 
SAMPLES>s obj = ##class(Test.Store).%OpenId(1)
 
SAMPLES>d $system.OBJ.ShowObjects()
Oref      Class Name                    Ref Count
----      ----------                    ---------
1         Test.Store                    1
 
SAMPLES>d obj.SomeMethod()
 
SAMPLES>d $system.OBJ.ShowObjects()
Oref      Class Name                    Ref Count
----      ----------                    ---------
1         Test.Store                    2
 
SAMPLES>k obj
 
SAMPLES>d $system.OBJ.ShowObjects()
Oref      Class Name                    Ref Count
----      ----------                    ---------
1         Test.Store                    1

Interestingly, $data doesn't seem to have the same issue.

After actually trying out my own suggestion, I think this would actually be better:

ClassMethod OnLogin() As %Status
{
    #dim %session As %CSP.Session
    If $Get(^ZPMGSYSTEM("%DOWNFLAG")) = 1 {
        Set %session.EndSession = 1
    }
    Quit $$$OK
}

My original suggestion doesn't actually end the session, it just results in an error response for one request. Trying to load the desired page again seems to actually work in that case.

To provide server-side enforcement as well (from a security perspective, in addition to what you have client-side to provide a nicer experience for well-behaved users), you should be able to use %CSP.SessionEvents with OnLogin overridden to check a global (or something) and return an error set %session.EndSession = 1 if logins are disabled. This class would need to be set up for the web application(s) through which your application is accessed - see "Event Class" here.

When working with files, it's much better to use %Stream classes rather than an instance of %Library.File. If you're looking to get the attributes of a file/directory, the Attributes method in %Library.File is probably your best option. According to the class reference (which has all the details), it returns a combination of bits, the meaning of which varies depending on the operating system. This is kind of ugly, but here's an example on Windows:

USER>set attributes = ##class(%Library.File).Attributes("C:\Users\tleavitt\AppData\")
 
USER>w attributes
8210

USER>for i=0:1:16 { w:$zboolean(attributes,2**i,1) 2**i,! }
2
16
8192

According to the class reference, this is a directory (16) that is hidden (2) and not indexed (8192).

%Library.File also has some OS-independent classmethods that may be easier to use for simpler things like seeing if a file is read-only.