Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ux analytics plugin #3044

Draft
wants to merge 20 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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 @@ -13,6 +13,9 @@
import java.util.Optional;
import java.util.concurrent.FutureTask;

import java.util.Optional;
import java.util.concurrent.FutureTask;

/** Listener to a widget representation
*
* <p>Provides notification of events (action invoked, ..)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,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 @@ -525,6 +526,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
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import org.csstudio.display.builder.model.DisplayModel;
import org.csstudio.display.builder.model.Widget;
import org.csstudio.display.builder.representation.ToolkitListener;
import org.csstudio.display.builder.representation.ToolkitRepresentation;
import org.csstudio.display.builder.representation.javafx.JFXRepresentation;
import org.phoebus.framework.workbench.ApplicationService;
Expand Down
1 change: 1 addition & 0 deletions app/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,6 @@
<module>imageviewer</module>
<module>credentials-management</module>
<module>eslog</module>
<module>ux-analytics</module>
</modules>
</project>
118 changes: 118 additions & 0 deletions app/ux-analytics/monitor/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
<?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>4.7.4-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>4.7.4-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.neo4j.driver</groupId>
<artifactId>neo4j-java-driver</artifactId>
<version>5.19.0</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-sync</artifactId>
<version>5.1.0</version>
</dependency>
<dependency>
<groupId>org.phoebus</groupId>
<artifactId>core-ui</artifactId>
<version>4.7.4-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.phoebus</groupId>
<artifactId>app-display-model</artifactId>
<version>4.7.4-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.phoebus</groupId>
<artifactId>app-display-runtime</artifactId>
<version>4.7.4-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>software.amazon.awssdk</groupId>
<artifactId>bom</artifactId>
<version>2.25.66</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
<version>2.25.66</version>
</dependency>
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<version>3.4.0</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.phoebus</groupId>
<artifactId>app-logbook-elog</artifactId>
<version>4.7.4-SNAPSHOT</version>
<scope>compile</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,55 @@
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.backend.database.MongoDBConnection;
import org.phoebus.applications.uxanalytics.monitor.backend.database.Neo4JConnection;
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){
if(connection instanceof MongoDBConnection){
jfxConnection = connection;
} else if(connection instanceof Neo4JConnection){
phoebusConnection = connection;
}
}

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

public void setJfxConnection(BackendConnection jfxConnection) {
this.jfxConnection = jfxConnection;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
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 ActiveTab tab;
private final UXAMonitor monitor = UXAMonitor.getInstance();

public UXAMouseMonitor(ActiveTab tab){
tab.getParentTab().getContent().addEventFilter(MouseEvent.MOUSE_CLICKED, this);
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,105 @@
package org.phoebus.applications.uxanalytics.monitor;

import org.csstudio.display.builder.model.Widget;
import org.csstudio.display.builder.model.properties.ActionInfo;
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.ActionUtil.openDisplay", ResourceOpenSources.ACTION_BUTTON,
"org.phoebus.ui.application.PhoebusApplication.fileOpen", ResourceOpenSources.FILE_BROWSER,
"org.csstudio.display.builder.runtime.app.NavigationAction.navigate", ResourceOpenSources.NAVIGATION_BUTTON,
"org.phoebus.ui.internal.MementoHelper.restoreDockItem", ResourceOpenSources.RESTORED,
"org.phoebus.ui.application.PhoebusApplication.createTopResourcesMenu", ResourceOpenSources.TOP_RESOURCES,
"org.csstudio.display.builder.runtime.app.DisplayRuntimeInstance.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;
}

@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[]) {
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
Loading