Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
302654b
skeleton for UXA plugin
daykin Apr 22, 2024
643273c
Working ToolkitListener for tab containers
daykin May 23, 2024
d455a9a
sort-of fixed duplicate listener issue
daykin May 29, 2024
75b88a0
Listening hierarchy works, but doesn't do anything interesting yet
daykin May 29, 2024
bfe87ad
begin backend connection reference implementations
daykin Jun 10, 2024
959f997
update pom versions and remove a test MRE
daykin Jun 10, 2024
42c1f39
dumb sort-of working example after rebase
daykin Jun 11, 2024
3cfa620
connect listeners to backends
daykin Jun 12, 2024
f3ba92e
add ResourceOpenedListener so interested parties can find out what ca…
daykin Jun 18, 2024
c4701cf
Revert "add ResourceOpenedListener so interested parties can find out…
daykin Jun 18, 2024
a63cb52
static file helpers
daykin Jun 19, 2024
af98e59
Plugin now knows who opened a given display
daykin Jun 19, 2024
7939788
plugin knows about nav buttons, action buttons, reload, file browser,…
daykin Jun 20, 2024
4723be1
In case of action buttons, reliably know about source and destination…
daykin Jun 20, 2024
2a0ae1b
path normalization utils
daykin Jul 2, 2024
493bc06
mostly-working end-to-end neo4j connection
daykin Jul 2, 2024
d23e22e
Implement image clients, default to storing screencaps in base-64 mon…
daykin Jul 10, 2024
6489f46
reorganize
daykin Jul 10, 2024
9ca5035
begin using credentials manager
daykin Jul 10, 2024
cb9d8d1
try to auto-login on startup
daykin Jul 15, 2024
d06b843
catch correct method when display opened via action
daykin Dec 9, 2024
c51afd4
catch correct method when display opened via file browser
daykin Dec 10, 2024
3f753c2
use 'x.class.getName()' so IDEs catch package refactoring
daykin Dec 10, 2024
551fbd8
connect to a middleware application instead of directly to the server…
daykin Mar 26, 2025
63cb0c5
Keep file extensions
daykin Apr 10, 2025
b21790c
don't log repeated errors, to avoid spamming the log
daykin Apr 10, 2025
9755070
don't force runLater in serviceLayerConnection, caller can do so if n…
daykin Apr 14, 2025
778aa72
don't force runLater in serviceLayerConnection, caller can do so if n…
daykin Apr 14, 2025
707abea
tests for UXA service layer connection
daykin Apr 14, 2025
0c746b5
add accessors for 'mangled' analytics name and mouse listener
daykin Apr 25, 2025
e771bb8
remove duplicate addEventFilter from UXAMouseMonitor EventHandler
daykin Apr 25, 2025
ced6b39
more robust null rejection for things outside of a meaningful tree th…
daykin Apr 25, 2025
5919d75
clear and reinitialize if tracking (re)starts after application startup
daykin Apr 25, 2025
80fba46
Mouse handler needs to know about tab
daykin Apr 25, 2025
2c7f0ea
FileUtils improvements: LRU cache for SHA256 sums, correctly handle b…
daykin Apr 25, 2025
08f10f9
Unit tests for service layer connection
daykin Apr 25, 2025
092098d
Unit tests for FileUtils helper
daykin Apr 25, 2025
bf36d34
Unit tests for ActiveTab wrapper
daykin Apr 25, 2025
d5a4e6d
update dependencies
daykin Apr 25, 2025
cd64c91
properly handle removal of a tab - can't use ListChangeListener becau…
daykin Apr 28, 2025
1d724a5
UXAToolkitListener method-call handler only cares when monitor.getPho…
daykin Apr 28, 2025
75d20de
update existing tests to reflect ActiveTab revision
daykin Apr 28, 2025
100aa73
tests for ActiveWindowsService tab hierarchy tracker
daykin Apr 28, 2025
c2f1cd4
ensure all tests can be run together (avoid IllegalStateException fro…
daykin Apr 28, 2025
0199959
ignore tabs that aren't DockItemWithInput
daykin Apr 29, 2025
b6a397d
move analytics consent persistent file to `Locations.user()`
daykin Apr 29, 2025
95cf013
update versions to 5.0.3
daykin Feb 24, 2026
384ae15
restore AuthenticationScope to stock, no modification is necessary an…
daykin Feb 24, 2026
31e20ff
don't need mongodb
daykin Feb 24, 2026
e065d31
don't need neo4j wrapper, just sending HTTP to middleware
daykin Feb 24, 2026
0330c9c
remove duplicate import
daykin Feb 25, 2026
94cb513
remove unused import
daykin Feb 25, 2026
0009724
restore queue-server to app/pom.xml
daykin Feb 25, 2026
220c074
remove references to phoebus authentication storage
daykin Feb 25, 2026
1f7eef8
timestamp is done by server
daykin Feb 26, 2026
4740f43
Reentrant lock is not necessary for concurrentHashMap
daykin Feb 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ public static DisplayRuntimeInstance ofDisplayModel(final DisplayModel model)
layout.addEventFilter(KeyEvent.KEY_PRESSED, this::handleKeys);

dock_item.addClosedNotification(this::onClosed);
representation_init.run();
}

@Override
Expand Down Expand Up @@ -543,6 +544,11 @@ public void onClosed()
navigation.dispose();
}

DisplayModel getActiveModel()
{
return active_model;
}

public void addListener(ToolkitListener listener){
this.getRepresentation().removeListener(listener);
this.getRepresentation().addListener(listener);
Expand Down
1 change: 1 addition & 0 deletions app/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,6 @@
<module>credentials-management</module>
<module>eslog</module>
<module>queue-server</module>
<module>ux-analytics</module>
</modules>
</project>
170 changes: 170 additions & 0 deletions app/ux-analytics/monitor/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

<parent>
<groupId>org.phoebus</groupId>
<artifactId>app-ux-analytics</artifactId>
<version>5.0.3-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>app-analytics-monitor</artifactId>

<properties>
<maven.compiler.source>22</maven.compiler.source>
<maven.compiler.target>22</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-graphics</artifactId>
<version>19</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
<version>19</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.phoebus</groupId>
<artifactId>core-framework</artifactId>
<version>5.0.3-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.phoebus</groupId>
<artifactId>core-ui</artifactId>
<version>5.0.3-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.phoebus</groupId>
<artifactId>app-display-model</artifactId>
<version>5.0.3-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.phoebus</groupId>
<artifactId>app-display-runtime</artifactId>
<version>5.0.3-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.14</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<version>3.4.0</version>
<scope>runtime</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/com.sun.jersey.contribs/jersey-multipart -->
<dependency>
<groupId>com.sun.jersey.contribs</groupId>
<artifactId>jersey-multipart</artifactId>
<version>1.19</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.sun.activation</groupId>
<artifactId>javax.activation</artifactId>
<version>1.2.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-client</artifactId>
<version>1.19.4</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-common</artifactId>
<version>2.22.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-json</artifactId>
<version>1.19.4</version>
</dependency>
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
<version>2.1.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.phoebus</groupId>
<artifactId>app-filebrowser</artifactId>
<version>5.0.3-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testfx</groupId>
<artifactId>testfx-junit</artifactId>
<version>4.0.13-alpha</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.17.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.11.0</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.fxml</include>
</includes>
</resource>
</resources>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package org.phoebus.applications.uxanalytics.monitor;


import java.util.ArrayList;
import java.util.concurrent.ExecutorService;

import javafx.stage.Stage;
import org.csstudio.display.builder.runtime.RuntimeUtil;
import org.phoebus.applications.uxanalytics.monitor.backend.database.BackendConnection;
import org.phoebus.applications.uxanalytics.monitor.representation.ActiveWindowsService;

/**
* Singleton Class to capture UI events (clicks, PV Writes, Display open/close)
* and dispatch them to backend connections
*/
public class UXAMonitor{
private static final UXAMonitor instance = new UXAMonitor();
private ArrayList<Stage> activeStages;
private static ActiveWindowsService activeWindowsService = ActiveWindowsService.getInstance();
private static final ExecutorService executor = RuntimeUtil.getExecutor();

//This dispatcher has exactly one phoebus related connection and one JFX related connection
//If you want to broadcast to multiple back-ends, subclass BackendConnection to notify them.
private BackendConnection phoebusConnection;
private BackendConnection jfxConnection;

private UXAMonitor(){
}

public BackendConnection getJfxConnection() {return jfxConnection;}

public BackendConnection getPhoebusConnection() { return phoebusConnection;}

public static synchronized UXAMonitor getInstance() {
return instance;
}

public void notifyConnectionChange(BackendConnection connection){
jfxConnection = connection;
phoebusConnection = connection;
}

public void setPhoebusConnection(BackendConnection phoebusConnection) {
this.phoebusConnection = phoebusConnection;
}

public void disableTracking(){
activeWindowsService.stop();
}

public void enableTracking(){
activeWindowsService.start();
}

public void setJfxConnection(BackendConnection jfxConnection) {
this.jfxConnection = jfxConnection;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.phoebus.applications.uxanalytics.monitor;

import javafx.event.EventHandler;
import javafx.scene.input.MouseEvent;
import org.phoebus.applications.uxanalytics.monitor.representation.ActiveTab;

public class UXAMouseMonitor implements EventHandler<MouseEvent>{

private final UXAMonitor monitor = UXAMonitor.getInstance();
private final ActiveTab tab;

public UXAMouseMonitor(ActiveTab tab){
this.tab = tab;
}

@Override
public void handle(MouseEvent event) {
if(event.getEventType().equals(MouseEvent.MOUSE_CLICKED)){
monitor.getJfxConnection().handleClick(tab, (int) event.getX(), (int) event.getY());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package org.phoebus.applications.uxanalytics.monitor;

import org.csstudio.display.builder.model.Widget;
import org.csstudio.display.builder.model.spi.ActionInfo;
import org.csstudio.display.builder.model.properties.ActionInfoBase;
import org.csstudio.display.builder.representation.ToolkitListener;
import org.csstudio.display.builder.runtime.app.DisplayInfo;
import org.phoebus.applications.uxanalytics.monitor.representation.ActiveTab;
import org.phoebus.applications.uxanalytics.monitor.util.ResourceOpenSources;

import java.util.*;
import java.util.logging.Logger;


public class UXAToolkitListener implements ToolkitListener {

Logger logger = Logger.getLogger(UXAToolkitListener.class.getName());

public static final HashMap<String, ResourceOpenSources> openSources = new HashMap<>(
Map.of(
org.csstudio.display.builder.runtime.app.actionhandlers.OpenDisplayActionHandler.class.getName()+".handleAction", ResourceOpenSources.ACTION_BUTTON,
org.phoebus.applications.filebrowser.FileBrowserController.class.getName()+".openResource", ResourceOpenSources.FILE_BROWSER,
org.phoebus.ui.application.PhoebusApplication.class.getName()+".fileOpen", ResourceOpenSources.FILE_BROWSER,
org.csstudio.display.builder.runtime.app.NavigationAction.class.getName()+".navigate", ResourceOpenSources.NAVIGATION_BUTTON,
org.phoebus.ui.internal.MementoHelper.class.getName()+".restoreDockItem", ResourceOpenSources.RESTORED,
org.phoebus.ui.application.PhoebusApplication.class.getName()+".createTopResourcesMenu", ResourceOpenSources.TOP_RESOURCES,
org.csstudio.display.builder.runtime.app.DisplayRuntimeInstance.class.getName()+".reload", ResourceOpenSources.RELOAD
)
);

private ActiveTab tabWrapper;
private final UXAMonitor monitor = UXAMonitor.getInstance();
public void setTabWrapper(ActiveTab tabWrapper){
this.tabWrapper = tabWrapper;
}

@Override
public void handleAction(Widget widget, ActionInfo action) {
monitor.getPhoebusConnection().handleAction(tabWrapper, widget, action);
}

@Override
public void handleWrite(Widget widget, Object value) {
monitor.getPhoebusConnection().handlePVWrite(tabWrapper, widget, widget.getPropertyValue("pv_name"), value.toString());
}

@Override
public void handleClick(Widget widget, boolean with_control) {
//nothing for now
}

//Traverse down a given call stack to find out what caused the display to open
private static ResourceOpenSources getSourceOfOpen(StackTraceElement[] stackTrace){

for(StackTraceElement e: stackTrace){
String methodName = unmangleLambda(e.getMethodName());
String fullName = e.getClassName()+"."+methodName;
if(openSources.containsKey(fullName)){
return openSources.get(fullName);
}
}
return ResourceOpenSources.UNKNOWN;
}

private static String unmangleLambda(String expression){
//find index of first '$' after 'lambda$'
if(expression.contains("lambda$")) {
int start = expression.indexOf("lambda$") + 7;
int end = expression.indexOf("$", start);
return expression.substring(start, end);
}
return expression;
}

public UXAMonitor getMonitor() {
return monitor;
}

@Override
public void handleMethodCalled(Object... user_args) {
StackTraceElement[] stackTrace;
if(user_args[0] instanceof List &&
((List) user_args[0]).get(0) instanceof DisplayInfo &&
user_args[1] instanceof StackTraceElement[] &&
monitor.getPhoebusConnection()!=null) {
List<DisplayInfo> dst_src = (List<DisplayInfo>) user_args[0];
stackTrace = (StackTraceElement[]) user_args[1];
for(StackTraceElement e: stackTrace){
String methodName = e.getMethodName();
if (methodName.equals("loadDisplayFile")) {
ResourceOpenSources source = getSourceOfOpen(stackTrace);
switch(source){
case ACTION_BUTTON:
//nothing, handled by handleAction
break;

case NAVIGATION_BUTTON:
case RELOAD:
monitor.getPhoebusConnection().handleDisplayOpen(dst_src.get(0), dst_src.get(1), source);
break;

case FILE_BROWSER:
case RESTORED:
case TOP_RESOURCES:
case UNKNOWN:
monitor.getPhoebusConnection().handleDisplayOpen(dst_src.get(0), null, source);
}
}
}
}
}
}

Loading