Skip to content

Commit

Permalink
Merge pull request #3061 from ControlSystemStudio/CSSTUDIO-2191-alarm…
Browse files Browse the repository at this point in the history
…-area-to-tree

Drill down to alarm sub tree displays from alarm area panel
  • Loading branch information
thelarsjohansson authored Jul 3, 2024
2 parents a135fd4 + 73a1c1c commit 3c74eb1
Show file tree
Hide file tree
Showing 6 changed files with 243 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
package org.phoebus.applications.alarm.ui;

import java.net.URI;
import java.util.HashMap;
import java.util.Map;

/** Alarm URI helpers
*
Expand All @@ -23,18 +25,32 @@ public class AlarmURI
/** URI schema used to refer to an alarm config */
public static final String SCHEMA = "alarm";

/** Nme to use for item as query parameter */
public static final String QUERY_PARAMETER_ITEM_NAME = "itemName";

/** @param server Kafka server host:port
* @param config_name Alarm configuration root
* @return URI used to access that alarm configuration, "alarm://host:port/config_name"
*/
public static URI createURI(final String server, final String config_name)
{
return URI.create(SCHEMA + "://" + server + "/" + config_name);
public static URI createURI(final String server, final String config_name) {
return createURI(server, config_name, null);
}

/** @param server Kafka server host:port
* @param config_name Alarm configuration root
* @param rawQuery raw query for URI
* @return URI used to access that alarm configuration, "alarm://host:port/config_name?rawQuery"
*/
public static URI createURI(final String server, final String config_name, String rawQuery) {
if (rawQuery != null && rawQuery.length() > 0)
return URI.create(SCHEMA + "://" + server + "/" + config_name + "?" + rawQuery);
else
return URI.create(SCHEMA + "://" + server + "/" + config_name);
}

/** Parse alarm configuration parameters from URI
* @param resource "alarm://localhost:9092/Accelerator"
* @return [ "localhost:9092", "Accelerator" ]
* @param resource "alarm://localhost:9092/Accelerator" or "alarm://localhost:9092/Accelerator?param=value"
* @return ["localhost:9092", "Accelerator", null] or ["localhost:9092", "Accelerator", "param=value"]
* @throws Exception on error
*/
public static String[] parseAlarmURI(final URI resource) throws Exception
Expand All @@ -55,10 +71,35 @@ public static String[] parseAlarmURI(final URI resource) throws Exception
if (port < 0)
port = 9092;
String config_name = resource.getPath();
String rawQuery = resource.getRawQuery();
if (config_name.startsWith("/"))
config_name = config_name.substring(1);
if (config_name.isEmpty())
throw new Exception("Missing alarm config name in " + resource + ", expecting " + SCHEMA + "://{host}:{port}/{config_name}");
return new String[] { resource.getHost() + ":" + port, config_name };
return new String[] { resource.getHost() + ":" + port, config_name, rawQuery };
}

/**
* Return a map with parameter names and values as key-value pairs for raw query of given resource.
* @param resource "alarm://localhost:9092/Accelerator" or "alarm://localhost:9092/Accelerator?param=value"
* @return map with parameter names as keys and parameter values as values for raw query
*/
public static Map<String, String> getRawQueryParametersValues(URI resource) {
Map<String, String> map = new HashMap<>();
String[] queryParametersValues = resource.getRawQuery() != null ? resource.getRawQuery().split("&") : null;
if (queryParametersValues != null) {
for (String queryParameterValue : queryParametersValues) {
String[] parameterValue = queryParameterValue.split("=");
if (parameterValue != null) {
if (parameterValue.length == 2) {
map.put(parameterValue[0], parameterValue[1]);
} else if (parameterValue.length == 1) {
map.put(parameterValue[0], null);
}
}
}
}
return map;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

import static org.phoebus.applications.alarm.AlarmSystem.logger;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
Expand All @@ -31,16 +33,15 @@
import org.phoebus.applications.alarm.model.AlarmTreeItem;
import org.phoebus.applications.alarm.model.SeverityLevel;
import org.phoebus.applications.alarm.ui.AlarmUI;
import org.phoebus.applications.alarm.ui.AlarmURI;
import org.phoebus.ui.javafx.JFXUtil;
import org.phoebus.ui.javafx.UpdateThrottle;

import javafx.application.Platform;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Label;
import javafx.scene.control.MenuItem;
import javafx.scene.paint.Color;
import javafx.scene.shape.StrokeLineCap;
import javafx.scene.shape.StrokeLineJoin;
Expand Down Expand Up @@ -104,8 +105,6 @@ public AlarmAreaView(final AlarmClient model)

areaFilter = new AreaFilter(AlarmSystem.alarm_area_level);
model.addListener(this);

createContextMenu();
}

// AlarmClientModelListener
Expand Down Expand Up @@ -205,6 +204,23 @@ private void recreateItems(final List<String> items)
for (String item_name : items)
{
final Label view_item = newAreaLabel(item_name);

// context menu content for alarm model item instead of alarm area
// link to item in tree view
view_item.setOnContextMenuRequested(event -> {
// need to clear and repopulate context menu since alarm area is recreated multiple times
// (but number of times unknown)
for (int i=menu.getItems().size()-1; i>0; i--) {
if (menu.getItems().get(i).getClass().equals(OpenTreeViewAction.class)) {
menu.getItems().remove(i);
}
}

OpenTreeViewAction otva = new OpenTreeViewAction(alarmConfigName, AlarmURI.QUERY_PARAMETER_ITEM_NAME + "=" + URLEncoder.encode(item_name, StandardCharsets.UTF_8));
menu.getItems().add(otva);
menu.show(this.getScene().getWindow(), event.getScreenX(), event.getScreenY());
});

itemViewMap.put(item_name, view_item);
updateItem(item_name);
final int column = index % AlarmSystem.alarm_area_column_count;
Expand Down Expand Up @@ -241,19 +257,10 @@ private void updateItem(final String item_name)
view_item.setStyle("-fx-alignment: center; -fx-border-color: black; -fx-border-width: 2; -fx-border-radius: 10; -fx-background-insets: 1; -fx-background-radius: 10; -fx-text-fill: " + JFXUtil.webRGB(AlarmUI.getAlarmAreaPanelColor(severityLevel)) + "; -fx-background-color: " + JFXUtil.webRGB(AlarmUI.getAlarmAreaPanelBackgroundColor(severityLevel)));
}

private void createContextMenu()
{
final ObservableList<MenuItem> menu_items = menu.getItems();

menu_items.add(new OpenTreeViewAction(alarmConfigName));
this.setOnContextMenuRequested(event ->
menu.show(this.getScene().getWindow(), event.getScreenX(), event.getScreenY())
);
}

/** @return Context menu */
public ContextMenu getMenu()
{
return menu;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,24 @@
* @author Evan Smith
*/
@SuppressWarnings("nls")
public class OpenTreeViewAction extends MenuItem
{
public class OpenTreeViewAction extends MenuItem {

/**
* Constructor
* @param alarmConfigName The alarm configuration name
*/
public OpenTreeViewAction(String alarmConfigName)
{
public OpenTreeViewAction(String alarmConfigName) {
this(alarmConfigName, null);
}

/**
* Constructor
* @param alarmConfigName The alarm configuration name
* @param alarmRawQuery raw query for alarm (null if no such information is available)
*/
public OpenTreeViewAction(String alarmConfigName, String alarmRawQuery) {
final AlarmTreeMenuEntry entry = new AlarmTreeMenuEntry();
entry.setResource(AlarmURI.createURI(AlarmSystem.server, alarmConfigName));
entry.setResource(AlarmURI.createURI(AlarmSystem.server, alarmConfigName, alarmRawQuery));
setText(entry.getName());
setGraphic(new ImageView(entry.getIcon()));
setOnAction(event ->
Expand All @@ -45,4 +52,5 @@ public OpenTreeViewAction(String alarmConfigName)
}
});
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import static org.phoebus.applications.alarm.AlarmSystem.logger;

import java.net.URI;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Level;

Expand Down Expand Up @@ -46,7 +48,12 @@ public AlarmTreeInstance(final AlarmTreeApplication app, final URI input) throws
{
this.app = app;

tab = new DockItemWithInput(this, create(input), input, null, null);
// split input with/without (raw) query
final URI resource = new URI(input.getScheme(), input.getUserInfo(), input.getHost(), input.getPort(), input.getPath(), null, null);
String itemName = AlarmURI.getRawQueryParametersValues(input).get(AlarmURI.QUERY_PARAMETER_ITEM_NAME);
itemName = itemName != null ? URLDecoder.decode(itemName, StandardCharsets.UTF_8) : null;

tab = new DockItemWithInput(this, create(resource, itemName), resource, null, null);
Platform.runLater(() -> tab.setLabel(config_name + " " + app.getDisplayName()));
tab.addCloseCheck(() ->
{
Expand All @@ -65,10 +72,11 @@ public AppDescriptor getAppDescriptor()
/** Create UI for input, starts alarm client
*
* @param input Alarm URI, will be parsed into `server` and `config_name`
* @param itemName item name that may be expanded or given focus
* @return Alarm UI
* @throws Exception
*/
private Node create(final URI input) throws Exception
private Node create(final URI input, String itemName) throws Exception
{
final String[] parsed = AlarmURI.parseAlarmURI(input);
server = parsed[0];
Expand All @@ -77,7 +85,7 @@ private Node create(final URI input) throws Exception
try
{
client = new AlarmClient(server, config_name, AlarmSystem.kafka_properties);
final AlarmTreeView tree_view = new AlarmTreeView(client);
final AlarmTreeView tree_view = new AlarmTreeView(client, itemName);
client.start();

if (AlarmSystem.config_names.length > 0)
Expand All @@ -104,7 +112,8 @@ private void changeConfig(final String new_config_name)
{
// Use same server name, but new config_name
final URI new_input = AlarmURI.createURI(server, new_config_name);
tab.setContent(create(new_input));
// no need for initial item name
tab.setContent(create(new_input, null));
tab.setInput(new_input);
Platform.runLater(() -> tab.setLabel(config_name + " " + app.getDisplayName()));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ public class AlarmTreeView extends BorderPane implements AlarmClientListener

/** Model with current alarm tree, sends updates */
private final AlarmClient model;
private final String itemName;

/** Latch for initially pausing model listeners
*
Expand Down Expand Up @@ -150,7 +151,15 @@ public class AlarmTreeView extends BorderPane implements AlarmClientListener
// constant performance?

/** @param model Model to represent. Must <u>not</u> be running, yet */
public AlarmTreeView(final AlarmClient model)
public AlarmTreeView(final AlarmClient model) {
this(model, null);
}

/**
* @param model Model to represent. Must <u>not</u> be running, yet
* @param itemName item name that may be expanded or given focus
*/
public AlarmTreeView(final AlarmClient model, String itemName)
{
if (model.isRunning())
throw new IllegalStateException();
Expand All @@ -159,6 +168,7 @@ public AlarmTreeView(final AlarmClient model)
changing.setBackground(new Background(new BackgroundFill(Color.BLUE, CornerRadii.EMPTY, Insets.EMPTY)));

this.model = model;
this.itemName = itemName;

tree_view.setShowRoot(false);
tree_view.setCellFactory(view -> new AlarmTreeViewCell());
Expand Down Expand Up @@ -203,6 +213,16 @@ private void startup()
// Represent model that should by now be fairly complete
tree_view.setRoot(createViewItem(model.getRoot()));

// expand tree item if is matches item name
if (tree_view.getRoot() != null && itemName != null) {
for (TreeItem treeItem : tree_view.getRoot().getChildren()) {
if (((AlarmTreeItem) treeItem.getValue()).getName().equals(itemName)) {
expandAlarms(treeItem);
break;
}
}
}

// Set change indicator so that it clears when there are no more changes
indicateChange();
showServerState(model.isServerAlive());
Expand Down
Loading

0 comments on commit 3c74eb1

Please sign in to comment.