From 0fff24074a4079b134284a0e82dd315cc8479828 Mon Sep 17 00:00:00 2001 From: georgweiss Date: Tue, 27 May 2025 15:07:10 +0200 Subject: [PATCH 1/5] Support for auto filtering in save&restore. Initial commit --- .../client/SaveAndRestoreClient.java | 19 +- .../client/SaveAndRestoreClientImpl.java | 18 +- .../ui/SaveAndRestoreController.java | 174 ++++++++++++------ .../ui/SaveAndRestoreService.java | 10 + .../saveandrestore/messages.properties | 4 +- .../saveandrestore/ui/SaveAndRestoreUI.fxml | 27 ++- .../main/resources/save-and-restore-style.css | 9 + .../model/websocket/MessageType.java | 4 +- .../websocket/WebMessageDeserializer.java | 6 +- .../BeamModeFilterSelector.java | 46 +++++ .../FilterSelectionHandler.java | 96 ++++++++++ .../filterselection/FilterSelector.java | 92 +++++++++ .../web/controllers/FilterController.java | 41 ++++- 13 files changed, 461 insertions(+), 85 deletions(-) create mode 100644 services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/filterselection/BeamModeFilterSelector.java create mode 100644 services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/filterselection/FilterSelectionHandler.java create mode 100644 services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/filterselection/FilterSelector.java diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClient.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClient.java index cc81f3c8d8..06a9c2a91c 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClient.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClient.java @@ -24,7 +24,9 @@ import org.phoebus.applications.saveandrestore.model.search.SearchResult; import javax.ws.rs.core.MultivaluedMap; +import java.util.Collections; import java.util.List; +import java.util.Set; /** * Implementations of this interface shall handle the communication to the save-and-restore service. @@ -77,13 +79,6 @@ public interface SaveAndRestoreClient { */ Node createNewNode(String parentsUniqueId, Node node); - /** - * Updates a node, e.g. if user wishes to add or remove tags from a snapshot {@link Node} - * - * @param nodeToUpdate The {@link Node} subject to update. - * @return The updated {@link Node}. - */ - Node updateNode(Node nodeToUpdate); /** * Updates a node, e.g. if user wishes to add or remove tags from a snapshot {@link Node} @@ -208,7 +203,7 @@ public interface SaveAndRestoreClient { /** * Deletes a {@link Filter} based on its name. - * @param name + * @param name Unique name of a {@link Filter} */ void deleteFilter(String name); @@ -257,4 +252,12 @@ public interface SaveAndRestoreClient { * @return A {@link List} of {@link SnapshotItem}s carrying snapshot values read by the service. */ List takeSnapshot(String configurationNodeId); + + default String getAutoSelectedFilter(){ + return null; + } + + default Set getAutoFilterSelectorNames(){ + return Collections.emptySet(); + } } diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClientImpl.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClientImpl.java index 786270532e..e00dcf129e 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClientImpl.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClientImpl.java @@ -42,6 +42,7 @@ import java.time.Duration; import java.util.Base64; import java.util.List; +import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; @@ -155,11 +156,6 @@ public Node createNewNode(String parentsUniqueId, Node node) { } } - @Override - public Node updateNode(Node nodeToUpdate) { - return updateNode(nodeToUpdate, false); - } - @Override public Node updateNode(Node nodeToUpdate, boolean customTimeForMigration) { try { @@ -698,4 +694,16 @@ private HttpResponse getCall(String relativeUrl) { throw new RuntimeException(e); } } + + @Override + public String getAutoSelectedFilter(){ + HttpResponse response = getCall("/filter/selected"); + return response.body(); + } + + @Override + public Set getAutoFilterSelectorNames(){ + return getCall("/filter/selectors", new TypeReference<>() { + }); + } } diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java index 4636ae53ab..5a0bfe06ac 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java @@ -23,6 +23,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import javafx.application.Platform; import javafx.beans.binding.Bindings; +import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.collections.FXCollections; @@ -34,7 +35,6 @@ import javafx.scene.control.Alert.AlertType; import javafx.scene.control.Button; import javafx.scene.control.ButtonType; -import javafx.scene.control.CheckBox; import javafx.scene.control.ComboBox; import javafx.scene.control.ContextMenu; import javafx.scene.control.ListCell; @@ -42,12 +42,14 @@ import javafx.scene.control.Menu; import javafx.scene.control.MenuItem; import javafx.scene.control.MultipleSelectionModel; +import javafx.scene.control.RadioButton; import javafx.scene.control.SelectionMode; import javafx.scene.control.SeparatorMenuItem; import javafx.scene.control.SplitPane; import javafx.scene.control.Tab; import javafx.scene.control.TabPane; import javafx.scene.control.TextInputDialog; +import javafx.scene.control.ToggleGroup; import javafx.scene.control.Tooltip; import javafx.scene.control.TreeItem; import javafx.scene.control.TreeView; @@ -58,6 +60,7 @@ import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; import javafx.scene.input.TransferMode; +import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; import javafx.stage.FileChooser; import javafx.util.Callback; @@ -160,7 +163,16 @@ public class SaveAndRestoreController extends SaveAndRestoreBaseController @SuppressWarnings("unused") @FXML - private CheckBox enableFilterCheckBox; + private RadioButton autoFilter; + + @SuppressWarnings("unused") + @FXML + private HBox filterSelectionButtons; + + @SuppressWarnings("unused") + @FXML + private RadioButton manualFilter; + @SuppressWarnings("unused") @FXML @@ -173,8 +185,11 @@ public class SaveAndRestoreController extends SaveAndRestoreBaseController protected static final Logger LOG = Logger.getLogger(SaveAndRestoreController.class.getName()); protected Comparator> treeNodeComparator; protected SimpleBooleanProperty disabledUi = new SimpleBooleanProperty(false); - private final SimpleBooleanProperty filterEnabledProperty = new SimpleBooleanProperty(false); + //private final SimpleBooleanProperty filterEnabledProperty = new SimpleBooleanProperty(false); private static final Logger logger = Logger.getLogger(SaveAndRestoreController.class.getName()); + private final ObjectProperty currentFilterProperty = new SimpleObjectProperty<>(null); + private final ObjectProperty filterSelectionModeProperty = + new SimpleObjectProperty<>(FilterOperationMode.MANUAL); @SuppressWarnings("unused") @FXML @@ -186,6 +201,7 @@ public class SaveAndRestoreController extends SaveAndRestoreBaseController private final ObservableList searchResultNodes = FXCollections.observableArrayList(); private final ObservableList filtersList = FXCollections.observableArrayList(); + private final ObservableList autoFilterSelectorNames = FXCollections.observableArrayList(); private final CountDownLatch treeInitializationCountDownLatch = new CountDownLatch(1); private final ObservableList selectedItemsProperty = FXCollections.observableArrayList(); @@ -198,7 +214,6 @@ public class SaveAndRestoreController extends SaveAndRestoreBaseController private final MenuItem deleteNodeMenuItem = new MenuItem(Messages.contextMenuDelete, ImageCache.getImageView(ImageCache.class, "/icons/delete.png")); private final MenuItem pasteMenuItem = new MenuItem(Messages.paste, ImageCache.getImageView(ImageCache.class, "/icons/paste.png")); - List menuItems = Arrays.asList( new LoginMenuItem(this, selectedItemsProperty, () -> ApplicationService.createInstance("credentials_management")), @@ -241,7 +256,7 @@ public void initialize(URL url, ResourceBundle resourceBundle) { treeNodeComparator = Comparator.comparing(TreeItem::getValue); treeView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); - treeView.getStylesheets().add(getClass().getResource("/save-and-restore-style.css").toExternalForm()); + treeViewPane.getStylesheets().add(getClass().getResource("/save-and-restore-style.css").toExternalForm()); browserSelectionModel = treeView.getSelectionModel(); @@ -250,9 +265,33 @@ public void initialize(URL url, ResourceBundle resourceBundle) { searchButtonImageView.setFitHeight(20); searchButton.setGraphic(searchButtonImageView); - enableFilterCheckBox.selectedProperty().bindBidirectional(filterEnabledProperty); - filtersComboBox.disableProperty().bind(filterEnabledProperty.not()); - filterEnabledProperty.addListener((observable, oldValue, newValue) -> filterEnabledChanged(newValue)); + manualFilter.setUserData(FilterOperationMode.MANUAL); + autoFilter.setUserData(FilterOperationMode.AUTO); + + ToggleGroup toggleGroup = new ToggleGroup(); + toggleGroup.getToggles().addAll(autoFilter, manualFilter); + toggleGroup.selectedToggleProperty().addListener((obs, o, n) -> + filterSelectionModeProperty.set((FilterOperationMode) n.getUserData())); + filterSelectionModeProperty.addListener((obs, o, n) -> { + if (n != null && n.equals(FilterOperationMode.AUTO)) { + // Check if a filter selection is active on service side + JobManager.schedule("Get and set auto filter", monitor -> { + String autoSelectedFilter = saveAndRestoreService.getAutoSelectedFilter(); + if (autoSelectedFilter != null) { + Optional filterOptional = + saveAndRestoreService.getAllFilters().stream().filter(f -> autoSelectedFilter.equals(f.getName())).findFirst(); + filterOptional.ifPresent(this::selectFilter); + } + }); + } + }); + + filterSelectionButtons.visibleProperty().bind(Bindings.createBooleanBinding(() -> + !filtersList.isEmpty() && !autoFilterSelectorNames.isEmpty(), filtersList, autoFilterSelectorNames)); + + filtersComboBox.disableProperty().bind(Bindings.createBooleanBinding(() -> + filterSelectionModeProperty.get().equals(FilterOperationMode.AUTO), filterSelectionModeProperty)); + filtersComboBox.valueProperty().bindBidirectional(currentFilterProperty); treeView.setEditable(true); @@ -279,8 +318,8 @@ public ListCell cal public void updateItem(org.phoebus.applications.saveandrestore.model.search.Filter item, boolean empty) { super.updateItem(item, empty); - if (!empty && item != null) { - setText(item.getName()); + if (!empty) { + setText(item == null ? Messages.noFilter : item.getName()); } } }; @@ -292,7 +331,7 @@ public void updateItem(org.phoebus.applications.saveandrestore.model.search.Filt @Override public String toString(Filter filter) { if (filter == null) { - return ""; + return Messages.noFilter; } else { return filter.getName(); } @@ -310,12 +349,14 @@ public Filter fromString(String s) { if (!newValue.equals(oldValue)) { applyFilter(newValue); } + } else { + clearFilter(); } }); filtersComboBox.itemsProperty().bind(new SimpleObjectProperty<>(filtersList)); - enableFilterCheckBox.disableProperty().bind(Bindings.createBooleanBinding(filtersList::isEmpty, filtersList)); + //enableFilterCheckBox.disableProperty().bind(Bindings.createBooleanBinding(filtersList::isEmpty, filtersList)); // Clear clipboard to make sure that only custom data format is // considered in paste actions. @@ -350,16 +391,17 @@ public Filter fromString(String s) { } /** - * Loads the data for the tree root as provided (persisted) by the current + * Pulls initial data from the service in order to configure the UI and render the {@link TreeView}. * {@link org.phoebus.applications.saveandrestore.client.SaveAndRestoreClient}. */ - public void loadTreeData() { + public void loadInitialData() { JobManager.schedule("Load save-and-restore tree data", monitor -> { Node rootNode = saveAndRestoreService.getRootNode(); treeInitializationCountDownLatch.countDown(); TreeItem rootItem = createTreeItem(rootNode); List savedTreeViewStructure = getSavedTreeStructure(); + // Check if there is a save tree structure. Also check that the first node id (=tree root) // has the same unique id as the actual root node retrieved from the remote service. This check // is needed to handle the case when the client connects to a different save-and-restore service. @@ -379,15 +421,27 @@ public void loadTreeData() { List> childItems = childNodes.stream().map(this::createTreeItem).sorted(treeNodeComparator).toList(); rootItem.getChildren().addAll(childItems); } + + // Get all filters from service + List filters = saveAndRestoreService.getAllFilters(); + // Get all auto select filter names + autoFilterSelectorNames.setAll(saveAndRestoreService.getAutoFilterSelectorNames()); + Platform.runLater(() -> { treeView.setRoot(rootItem); expandNodes(treeView.getRoot()); // Event handler for expanding nodes treeView.getRoot().addEventHandler(TreeItem.branchExpandedEvent(), e -> expandTreeNode(e.getTreeItem())); treeInitializationCountDownLatch.countDown(); - }); - loadFilters(); + filtersList.setAll(filters); + filtersList.add(0, null); + String savedFilterName = getSavedFilterName(); + if (savedFilterName != null) { + Optional f = filtersComboBox.getItems().stream().filter(filter -> filter.getName().equals(savedFilterName)).findFirst(); + f.ifPresent(filter -> filtersComboBox.getSelectionModel().select(filter)); + } + }); }); } @@ -898,7 +952,7 @@ public void saveLocalState() { findExpandedNodes(expandedNodes, treeView.getRoot()); try { PhoebusPreferenceService.userNodeForClass(SaveAndRestoreApplication.class).put(TREE_STATE, objectMapper.writeValueAsString(expandedNodes)); - if (filterEnabledProperty.get() && filtersComboBox.getSelectionModel().getSelectedItem() != null) { + if (/*filterEnabledProperty.get() &&*/ filtersComboBox.getSelectionModel().getSelectedItem() != null) { PhoebusPreferenceService.userNodeForClass(SaveAndRestoreApplication.class).put(FILTER_NAME, objectMapper.writeValueAsString(filtersComboBox.getSelectionModel().getSelectedItem().getName())); } @@ -1073,9 +1127,10 @@ public void openResource(URI uri) { * disabled, then all items match as we have a "no filter". */ public boolean matchesFilter(Node node) { - if (!filterEnabledProperty.get()) { + if (currentFilterProperty.isNull().get()) { return true; } + TreeItem selectedItem = treeView.getSelectionModel().getSelectedItem(); if (selectedItem == null) { return searchResultNodes.contains(node); @@ -1085,25 +1140,6 @@ public boolean matchesFilter(Node node) { } } - /** - * Retrieves all {@link Filter}s from service and populates the filter combo box. - */ - private void loadFilters() { - try { - List filters = saveAndRestoreService.getAllFilters(); - Platform.runLater(() -> { - filtersList.setAll(filters); - String savedFilterName = getSavedFilterName(); - if (savedFilterName != null) { - Optional f = filtersComboBox.getItems().stream().filter(filter -> filter.getName().equals(savedFilterName)).findFirst(); - f.ifPresent(filter -> filtersComboBox.getSelectionModel().select(filter)); - } - }); - } catch (Exception e) { - LOG.log(Level.SEVERE, "Failed to load filters", e); - } - } - /** * Applies a {@link Filter} selected by user. The service will be queries for {@link Node}s matching * the {@link Filter}, then the {@link TreeView} is updated based on the search result. @@ -1130,16 +1166,9 @@ private void applyFilter(Filter filter) { }); } - private void filterEnabledChanged(boolean enabled) { - if (!enabled) { - searchResultNodes.clear(); - treeView.refresh(); - } else { - Filter filter = filtersComboBox.getSelectionModel().getSelectedItem(); - if (filter != null) { - applyFilter(filter); - } - } + private void clearFilter() { + searchResultNodes.clear(); + treeView.refresh(); } /** @@ -1168,17 +1197,24 @@ private void filterAddedOrUpdated(Filter filter) { } } + /** + * Handles removal of a {@link Filter}. + *

+ * If the name matches the {@link Filter} currently being loaded, the filter selection is cleared, i.e. + * it switches to "no filter". + *

+ * + * @param name The name of a {@link Filter} + */ private void filterRemoved(String name) { - Optional filterOptional = filtersList.stream().filter(f -> f.getName().equals(name)).findFirst(); + Optional filterOptional = filtersList.stream().filter(f -> f != null && f.getName().equals(name)).findFirst(); if (filterOptional.isPresent()) { Filter filterToRemove = new Filter(); filterToRemove.setName(name); - filtersList.remove(filterToRemove); - // If this is the active filter, unselect it - filterEnabledProperty.set(false); - filtersComboBox.getSelectionModel().select(null); - // And refresh tree view - Platform.runLater(() -> treeView.refresh()); + Platform.runLater(() -> { + filtersList.remove(filterToRemove); + currentFilterProperty.set(null); + }); } } @@ -1425,16 +1461,44 @@ public void handleWebSocketMessage(SaveAndRestoreWebSocketMessage saveAndRestore case NODE_UPDATED -> nodeChanged((Node) saveAndRestoreWebSocketMessage.payload()); case FILTER_ADDED_OR_UPDATED -> filterAddedOrUpdated((Filter) saveAndRestoreWebSocketMessage.payload()); case FILTER_REMOVED -> filterRemoved((String) saveAndRestoreWebSocketMessage.payload()); + case FILTER_SELECTED -> selectFilter((Filter) saveAndRestoreWebSocketMessage.payload()); + case FILTER_UNSELECTED -> unselectFilter((Filter) saveAndRestoreWebSocketMessage.payload()); + } + } + + /** + * Selects a {@link Filter} by + * + * @param filter The {@link Filter} to select. + */ + private void selectFilter(Filter filter) { + if (filterSelectionModeProperty.get().equals(FilterOperationMode.AUTO)) { + Platform.runLater(() -> filtersComboBox.getSelectionModel().select(filter)); + } + //filterEnabledProperty.setValue(true); + } + + private void unselectFilter(Filter filter) { + if (filterSelectionModeProperty.get().equals(FilterOperationMode.AUTO)) { + Filter currentlySelectedFilter = filtersComboBox.getSelectionModel().getSelectedItem(); + if (currentlySelectedFilter != null && currentlySelectedFilter.equals(filter)) { + Platform.runLater(() -> filtersComboBox.getSelectionModel().select(null)); + } } } private void handleWebSocketConnected() { serviceConnected.setValue(true); - loadTreeData(); + loadInitialData(); } private void handleWebSocketDisconnected() { serviceConnected.setValue(false); saveLocalState(); } + + private enum FilterOperationMode { + MANUAL, + AUTO + } } diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreService.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreService.java index 76b9422110..002777910e 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreService.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreService.java @@ -50,6 +50,7 @@ import java.time.Instant; import java.util.ArrayList; import java.util.List; +import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingQueue; @@ -427,4 +428,13 @@ private VType readFromArchiver(String pvName, Instant time) { return VDisconnectedData.INSTANCE; } } + + public Set getAutoFilterSelectorNames(){ + return saveAndRestoreClient.getAutoFilterSelectorNames(); + } + + public String getAutoSelectedFilter(){ + String selectedFilter = saveAndRestoreClient.getAutoSelectedFilter(); + return selectedFilter.isEmpty() ? null : selectedFilter; + } } diff --git a/app/save-and-restore/app/src/main/resources/org/phoebus/applications/saveandrestore/messages.properties b/app/save-and-restore/app/src/main/resources/org/phoebus/applications/saveandrestore/messages.properties index 18ef48875a..90dc4ea20d 100644 --- a/app/save-and-restore/app/src/main/resources/org/phoebus/applications/saveandrestore/messages.properties +++ b/app/save-and-restore/app/src/main/resources/org/phoebus/applications/saveandrestore/messages.properties @@ -90,6 +90,7 @@ failedGetSpecificFilter=Failed to retrieve filter "{0}" failedSaveFilter=Failed to save filter failedToPasteObjects=Failed to paste object(s) filter=Filter +filterAuto=Auto filterEditorDescriptionOrComment=Description/Comment filterEditorNodeName=Node Name filterEditorNodeTypeFolder=Folder @@ -100,7 +101,8 @@ filterEditorTags=Tags filterEditorStartTime=Start Time filterEditorEndTime=End Time filterEditorUser=User -filterName=Filter Name +filterManual=Manual +filterName=Filter Name? filterNotFound=Filter "{0}" not found filterQueryColumn=Query filterLastUpdatedColumn=Last Updated diff --git a/app/save-and-restore/app/src/main/resources/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreUI.fxml b/app/save-and-restore/app/src/main/resources/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreUI.fxml index 3608b13d1f..009285f51d 100644 --- a/app/save-and-restore/app/src/main/resources/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreUI.fxml +++ b/app/save-and-restore/app/src/main/resources/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreUI.fxml @@ -14,15 +14,11 @@ - - + + @@ -31,13 +27,28 @@ + + + + + + + + + + + + + + + diff --git a/app/save-and-restore/app/src/main/resources/save-and-restore-style.css b/app/save-and-restore/app/src/main/resources/save-and-restore-style.css index f01406d2cf..32cf2e129b 100644 --- a/app/save-and-restore/app/src/main/resources/save-and-restore-style.css +++ b/app/save-and-restore/app/src/main/resources/save-and-restore-style.css @@ -138,4 +138,13 @@ .column-header.leftAlignedTableColumnHeader > .label { -fx-alignment: center-left; -fx-padding: 0 0 0 2; +} + +.combo-box-base:disabled{ + -fx-opacity: 1.0; + -fx-font-weight: bold; +} + +.combo-box > .list-cell:disabled{ + -fx-opacity: 1.0; } \ No newline at end of file diff --git a/app/save-and-restore/model/src/main/java/org/phoebus/applications/saveandrestore/model/websocket/MessageType.java b/app/save-and-restore/model/src/main/java/org/phoebus/applications/saveandrestore/model/websocket/MessageType.java index 9cc99875bd..3f4aa1e286 100644 --- a/app/save-and-restore/model/src/main/java/org/phoebus/applications/saveandrestore/model/websocket/MessageType.java +++ b/app/save-and-restore/model/src/main/java/org/phoebus/applications/saveandrestore/model/websocket/MessageType.java @@ -12,5 +12,7 @@ public enum MessageType { NODE_UPDATED, NODE_REMOVED, FILTER_ADDED_OR_UPDATED, - FILTER_REMOVED + FILTER_REMOVED, + FILTER_SELECTED, + FILTER_UNSELECTED } diff --git a/app/save-and-restore/model/src/main/java/org/phoebus/applications/saveandrestore/model/websocket/WebMessageDeserializer.java b/app/save-and-restore/model/src/main/java/org/phoebus/applications/saveandrestore/model/websocket/WebMessageDeserializer.java index 3e636cbf5a..a26fb3ad8f 100644 --- a/app/save-and-restore/model/src/main/java/org/phoebus/applications/saveandrestore/model/websocket/WebMessageDeserializer.java +++ b/app/save-and-restore/model/src/main/java/org/phoebus/applications/saveandrestore/model/websocket/WebMessageDeserializer.java @@ -33,8 +33,8 @@ public WebMessageDeserializer(Class clazz) { * @return A {@link SaveAndRestoreWebSocketMessage} object, or null if deserialization fails. */ @Override - public SaveAndRestoreWebSocketMessage deserialize(JsonParser jsonParser, - DeserializationContext context) { + public SaveAndRestoreWebSocketMessage deserialize(JsonParser jsonParser, + DeserializationContext context) { try { JsonNode rootNode = jsonParser.getCodec().readTree(jsonParser); String messageType = rootNode.get("messageType").asText(); @@ -49,7 +49,7 @@ public SaveAndRestoreWebSocketMessage deserialize(JsonParser j }); return saveAndRestoreWebSocketMessage; } - case FILTER_ADDED_OR_UPDATED -> { + case FILTER_ADDED_OR_UPDATED, FILTER_SELECTED, FILTER_UNSELECTED -> { SaveAndRestoreWebSocketMessage saveAndRestoreWebSocketMessage = objectMapper.readValue(rootNode.toString(), new TypeReference<>() { }); return saveAndRestoreWebSocketMessage; diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/filterselection/BeamModeFilterSelector.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/filterselection/BeamModeFilterSelector.java new file mode 100644 index 0000000000..026c8482f0 --- /dev/null +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/filterselection/BeamModeFilterSelector.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2025 European Spallation Source ERIC. + */ + +package org.phoebus.service.saveandrestore.filterselection; + + +import org.phoebus.core.vtypes.VTypeHelper; +import org.phoebus.pv.PV; +import org.phoebus.pv.PVPool; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +public class BeamModeFilterSelector extends FilterSelector{ + + public BeamModeFilterSelector(){ + try { + PV pv = PVPool.getPV("ao1"); + pv.onValueEvent().subscribe(value -> { + if (!VTypeHelper.isDisconnected(value)) { + if(VTypeHelper.toDouble(value) > 1.0){ + filterSelected("golden only"); + } + else{ + filterUnselected("golden only"); + } + } + else{ + filterUnselected("golden only"); + } + }); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public List getSupportedFilterNames() { + return List.of("golden only"); + } + + + +} diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/filterselection/FilterSelectionHandler.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/filterselection/FilterSelectionHandler.java new file mode 100644 index 0000000000..bd8bc0e07d --- /dev/null +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/filterselection/FilterSelectionHandler.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2025 European Spallation Source ERIC. + */ + +package org.phoebus.service.saveandrestore.filterselection; + +import org.phoebus.applications.saveandrestore.model.search.Filter; +import org.phoebus.applications.saveandrestore.model.websocket.MessageType; +import org.phoebus.applications.saveandrestore.model.websocket.SaveAndRestoreWebSocketMessage; +import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO; +import org.phoebus.service.saveandrestore.websocket.WebSocketHandler; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + + +import javax.annotation.PostConstruct; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + + +@Component +public class FilterSelectionHandler { + + @Autowired + private WebSocketHandler webSocketHandler; + + @Autowired + private NodeDAO nodeDAO; + + private List filterSelectors; + + private Set autoSelectFilterNames; + + public FilterSelectionHandler(List filterSelectors){ + this.filterSelectors = filterSelectors; + + } + + @SuppressWarnings("unused") + @PostConstruct + public void initialize(){ + autoSelectFilterNames = new HashSet<>(); + this.filterSelectors.forEach(s -> { + s.setFilterSelectedCallback(this::filterSelected); + s.setFilterUnselectedCallback(this::filterUnselected); + autoSelectFilterNames.addAll(s.getSupportedFilterNames()); + }); + } + + /** + * + * @return A {@link Set} of unique {@link Filter} names supported + * by all {@link FilterSelector} implementations known to the service. + * Note that a {@link Filter} may be supported by multiple {@link FilterSelector}s, + * but the list of names should not contain duplicates. + */ + public Set getSelectorFilterNames(){ + return autoSelectFilterNames; + } + + /** + * @return The name of a {@link Filter} currently selected by a {@link FilterSelector} implementations, + * or null if no {@link Filter} has been selected. + * Note that if multiple {@link FilterSelector}s select a {@link Filter}, then this method will return + * the first one encountered. + */ + public String getSelectedFilter(){ + for(FilterSelector filterSelector : filterSelectors){ + if(filterSelector.getSelectedFilter() != null){ + return filterSelector.getSelectedFilter(); + } + } + return null; + } + + private void filterSelected(String filterName){ + List allFilters = nodeDAO.getAllFilters(); + Optional filterOptional = allFilters.stream().filter(f -> f.getName().equals(filterName)).findFirst(); + if(filterOptional.isPresent()){ + webSocketHandler.sendMessage(new SaveAndRestoreWebSocketMessage(MessageType.FILTER_SELECTED, filterOptional.get())); + } + } + + private void filterUnselected(String filterName){ + List allFilters = nodeDAO.getAllFilters(); + Optional filterOptional = allFilters.stream().filter(f -> f.getName().equals(filterName)).findFirst(); + if(filterOptional.isPresent()) { + webSocketHandler.sendMessage(new SaveAndRestoreWebSocketMessage(MessageType.FILTER_UNSELECTED, filterOptional.get())); + } + } + +} diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/filterselection/FilterSelector.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/filterselection/FilterSelector.java new file mode 100644 index 0000000000..d102c83e2e --- /dev/null +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/filterselection/FilterSelector.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2025 European Spallation Source ERIC. + */ + +package org.phoebus.service.saveandrestore.filterselection; + +import java.util.Collections; +import java.util.List; +import java.util.function.Consumer; + +/** + * A {@link FilterSelector} is a class that selects or unselects a {@link org.phoebus.applications.saveandrestore.model.search.Filter}. + * When the business logic evaluates to selection of a {@link org.phoebus.applications.saveandrestore.model.search.Filter}, + * a registered callback is invoked. Conversely, when the business logic no longer evaluates to selection of a + * {@link org.phoebus.applications.saveandrestore.model.search.Filter}, a registered callback is invoked. + * + *

+ * NOTE: Implementations must make sure to not select {@link org.phoebus.applications.saveandrestore.model.search.Filter}s + * at the same time. Only one implementation at a given point in time should select a {@link org.phoebus.applications.saveandrestore.model.search.Filter}. + *

+ * + *

Example: based on one or several PV values an implementation can set up conditions to perform a selection + * on different {@link org.phoebus.applications.saveandrestore.model.search.Filter}s.

+ * + *

The {@link org.phoebus.service.saveandrestore.web.controllers.FilterController} API will ensure that if a + * {@link org.phoebus.applications.saveandrestore.model.search.Filter} is supported by any of the {@link FilterSelector} + * implementations, deletion of that {@link org.phoebus.applications.saveandrestore.model.search.Filter} is blocked.

+ */ +public abstract class FilterSelector { + + private Consumer filterSelectedCallback; + private Consumer filterUnselectedCallback; + protected String selectedFilter; + + /** + * + * @return A list of {@link org.phoebus.applications.saveandrestore.model.search.Filter} names supported by + * all {@link FilterSelector} implementations. + */ + public List getSupportedFilterNames() { + return Collections.emptyList(); + } + + /** + * Registers the callback invoked when a {@link org.phoebus.applications.saveandrestore.model.search.Filter} is + * selected. + * @param callback The callback method. + */ + public void setFilterSelectedCallback(Consumer callback) { + this.filterSelectedCallback = callback; + } + + /** + * Registers the callback invoked when a {@link org.phoebus.applications.saveandrestore.model.search.Filter} is + * unselected. + * @param callback The callback method. + */ + public void setFilterUnselectedCallback(Consumer callback) { + this.filterUnselectedCallback = callback; + } + + /** + * Invokes callback when a {@link org.phoebus.applications.saveandrestore.model.search.Filter} is selected. + * @param filterName Unique name of a {@link org.phoebus.applications.saveandrestore.model.search.Filter} + */ + public void filterSelected(String filterName) { + selectedFilter = filterName; + if (filterSelectedCallback != null) { + filterSelectedCallback.accept(filterName); + } + } + + /** + * Invokes callback when a {@link org.phoebus.applications.saveandrestore.model.search.Filter} is unselected. + * @param filterName Unique name of a {@link org.phoebus.applications.saveandrestore.model.search.Filter} + */ + public void filterUnselected(String filterName) { + selectedFilter = null; + if (filterUnselectedCallback != null) { + filterUnselectedCallback.accept(filterName); + } + } + + /** + * + * @return Name of a {@link org.phoebus.applications.saveandrestore.model.search.Filter} if selected by any + * implementation, otherwise null. + */ + public String getSelectedFilter(){ + return selectedFilter; + } +} diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/FilterController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/FilterController.java index 57e34face3..8f2cdb7c09 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/FilterController.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/FilterController.java @@ -22,15 +22,22 @@ import org.phoebus.applications.saveandrestore.model.search.Filter; import org.phoebus.applications.saveandrestore.model.websocket.MessageType; import org.phoebus.applications.saveandrestore.model.websocket.SaveAndRestoreWebSocketMessage; +import org.phoebus.service.saveandrestore.filterselection.FilterSelectionHandler; +import org.phoebus.service.saveandrestore.filterselection.FilterSelector; import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO; import org.phoebus.service.saveandrestore.websocket.WebSocketHandler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; import java.security.Principal; import java.util.List; -import java.util.Optional; +import java.util.Set; /** * Controller class for {@link Filter} endpoints. @@ -45,6 +52,9 @@ public class FilterController extends BaseController { @Autowired private WebSocketHandler webSocketHandler; + @Autowired + private FilterSelectionHandler filterSelectionHandler; + /** * Saves a new or updated {@link Filter}. * @@ -64,7 +74,6 @@ public Filter saveFilter(@RequestBody final Filter filter, } /** - * * @return A {@link List} of all persisted {@link Filter} objects. Empty if none are found. */ @SuppressWarnings("unused") @@ -75,7 +84,8 @@ public List getAllFilters() { /** * Deletes a {@link Filter} - * @param name Unique name of the {@link Filter} + * + * @param name Unique name of the {@link Filter} * @param principal User {@link Principal} as injected by Spring. */ @SuppressWarnings("unused") @@ -85,4 +95,27 @@ public void deleteFilter(@PathVariable final String name, Principal principal) { nodeDAO.deleteFilter(name); webSocketHandler.sendMessage(new SaveAndRestoreWebSocketMessage(MessageType.FILTER_REMOVED, name)); } + + /** + * + * @return A {@link List} of {@link Filter} names currently being selected by all {@link FilterSelector} implementations. + * Note however that if multiple {@link FilterSelector}s select a {@link Filter}, then the client behavior + * is affected as it will select them sequentially. + */ + @SuppressWarnings("unused") + @GetMapping(value = "/filter/selected") + public String getSelectedFilter() { + return filterSelectionHandler.getSelectedFilter(); + } + + /** + * + * @return A {@link Set} of unique {@link Filter} names supported by all {@link FilterSelector} + * implementations know to the service. + */ + @SuppressWarnings("unused") + @GetMapping(value = "/filter/selectors", produces = JSON) + public Set getFilterSelectorNames(){ + return filterSelectionHandler.getSelectorFilterNames(); + } } From df70587b90918e8832bd45cfb86087eb0d0a5b42 Mon Sep 17 00:00:00 2001 From: georgweiss Date: Tue, 27 May 2025 15:15:02 +0200 Subject: [PATCH 2/5] Update javadoc --- .../FilterSelectionHandler.java | 35 ++++++++----------- .../filterselection/FilterSelector.java | 5 +++ 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/filterselection/FilterSelectionHandler.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/filterselection/FilterSelectionHandler.java index bd8bc0e07d..8d4a04cfb2 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/filterselection/FilterSelectionHandler.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/filterselection/FilterSelectionHandler.java @@ -12,16 +12,16 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; - import javax.annotation.PostConstruct; -import java.util.ArrayList; -import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; - +/** + * {@link Component} responsible for managing {@link FilterSelector}s and expose methods + * interacting with these from (for instance) {@link org.springframework.stereotype.Controller}s. + */ @Component public class FilterSelectionHandler { @@ -31,18 +31,18 @@ public class FilterSelectionHandler { @Autowired private NodeDAO nodeDAO; - private List filterSelectors; + private final List filterSelectors; private Set autoSelectFilterNames; - public FilterSelectionHandler(List filterSelectors){ + public FilterSelectionHandler(List filterSelectors) { this.filterSelectors = filterSelectors; } @SuppressWarnings("unused") @PostConstruct - public void initialize(){ + public void initialize() { autoSelectFilterNames = new HashSet<>(); this.filterSelectors.forEach(s -> { s.setFilterSelectedCallback(this::filterSelected); @@ -52,13 +52,12 @@ public void initialize(){ } /** - * * @return A {@link Set} of unique {@link Filter} names supported * by all {@link FilterSelector} implementations known to the service. * Note that a {@link Filter} may be supported by multiple {@link FilterSelector}s, * but the list of names should not contain duplicates. */ - public Set getSelectorFilterNames(){ + public Set getSelectorFilterNames() { return autoSelectFilterNames; } @@ -68,29 +67,25 @@ public Set getSelectorFilterNames(){ * Note that if multiple {@link FilterSelector}s select a {@link Filter}, then this method will return * the first one encountered. */ - public String getSelectedFilter(){ - for(FilterSelector filterSelector : filterSelectors){ - if(filterSelector.getSelectedFilter() != null){ + public String getSelectedFilter() { + for (FilterSelector filterSelector : filterSelectors) { + if (filterSelector.getSelectedFilter() != null) { return filterSelector.getSelectedFilter(); } } return null; } - private void filterSelected(String filterName){ + private void filterSelected(String filterName) { List allFilters = nodeDAO.getAllFilters(); Optional filterOptional = allFilters.stream().filter(f -> f.getName().equals(filterName)).findFirst(); - if(filterOptional.isPresent()){ - webSocketHandler.sendMessage(new SaveAndRestoreWebSocketMessage(MessageType.FILTER_SELECTED, filterOptional.get())); - } + filterOptional.ifPresent(filter -> webSocketHandler.sendMessage(new SaveAndRestoreWebSocketMessage(MessageType.FILTER_SELECTED, filter))); } - private void filterUnselected(String filterName){ + private void filterUnselected(String filterName) { List allFilters = nodeDAO.getAllFilters(); Optional filterOptional = allFilters.stream().filter(f -> f.getName().equals(filterName)).findFirst(); - if(filterOptional.isPresent()) { - webSocketHandler.sendMessage(new SaveAndRestoreWebSocketMessage(MessageType.FILTER_UNSELECTED, filterOptional.get())); - } + filterOptional.ifPresent(filter -> webSocketHandler.sendMessage(new SaveAndRestoreWebSocketMessage(MessageType.FILTER_UNSELECTED, filter))); } } diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/filterselection/FilterSelector.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/filterselection/FilterSelector.java index d102c83e2e..9a330580c1 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/filterselection/FilterSelector.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/filterselection/FilterSelector.java @@ -15,6 +15,11 @@ * {@link org.phoebus.applications.saveandrestore.model.search.Filter}, a registered callback is invoked. * *

+ * Implementations must be Spring {@link org.springframework.stereotype.Component}s such that the {@link FilterSelectionHandler} + * can discover them. The Java SPI approach is not used. + *

+ * + *

* NOTE: Implementations must make sure to not select {@link org.phoebus.applications.saveandrestore.model.search.Filter}s * at the same time. Only one implementation at a given point in time should select a {@link org.phoebus.applications.saveandrestore.model.search.Filter}. *

From 02d98944eafd338d4ad3ccee18501b26d6eecdc7 Mon Sep 17 00:00:00 2001 From: georgweiss Date: Wed, 28 May 2025 11:02:07 +0200 Subject: [PATCH 3/5] Adding unit tests --- .../filterselection/FilterSelectorTest.java | 41 ++++++++++ .../FilterSelectorTestConfig.java | 78 +++++++++++++++++++ 2 files changed, 119 insertions(+) create mode 100644 services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/filterselection/FilterSelectorTest.java create mode 100644 services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/filterselection/FilterSelectorTestConfig.java diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/filterselection/FilterSelectorTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/filterselection/FilterSelectorTest.java new file mode 100644 index 0000000000..63a933dbc5 --- /dev/null +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/filterselection/FilterSelectorTest.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2025 European Spallation Source ERIC. + */ + +package org.phoebus.service.saveandrestore.filterselection; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static org.junit.jupiter.api.Assertions.*; + +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = FilterSelectorTestConfig.class) +public class FilterSelectorTest { + + @Autowired + private FilterSelector testFilterSelector; + + @Autowired + private FilterSelectionHandler filterSelectionHandler; + + @Test + public void testDiscoverFilterSelectors() { + assertNotNull(testFilterSelector); + } + + @Test + public void setTestFilterSelectorNames() { + assertEquals("a", testFilterSelector.getSupportedFilterNames().get(0)); + assertEquals("b", testFilterSelector.getSupportedFilterNames().get(1)); + } + + @Test + public void testFilterSelectorHandler(){ + assertEquals(2, filterSelectionHandler.getSelectorFilterNames().size()); + } +} diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/filterselection/FilterSelectorTestConfig.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/filterselection/FilterSelectorTestConfig.java new file mode 100644 index 0000000000..7be87fef7e --- /dev/null +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/filterselection/FilterSelectorTestConfig.java @@ -0,0 +1,78 @@ +/** + * Copyright (C) 2018 European Spallation Source ERIC. + *

+ * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + *

+ * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + *

+ * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +package org.phoebus.service.saveandrestore.filterselection; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.mockito.Mockito; +import org.phoebus.saveandrestore.util.SnapshotUtil; +import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO; +import org.phoebus.service.saveandrestore.persistence.dao.impl.elasticsearch.ConfigurationDataRepository; +import org.phoebus.service.saveandrestore.persistence.dao.impl.elasticsearch.ElasticsearchTreeRepository; +import org.phoebus.service.saveandrestore.persistence.dao.impl.elasticsearch.FilterRepository; +import org.phoebus.service.saveandrestore.persistence.dao.impl.elasticsearch.SnapshotDataRepository; +import org.phoebus.service.saveandrestore.search.SearchUtil; +import org.phoebus.service.saveandrestore.web.config.AcceptHeaderResolver; +import org.phoebus.service.saveandrestore.web.config.WebSecurityConfig; +import org.phoebus.service.saveandrestore.websocket.WebSocketHandler; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.Profile; +import org.springframework.util.Base64Utils; +import org.springframework.web.socket.WebSocketSession; + +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +@SpringBootConfiguration +@ComponentScan(basePackages = "org.phoebus.service.saveandrestore.filterselection") +@SuppressWarnings("unused") +@Profile("!IT") +public class FilterSelectorTestConfig { + + @Bean + public FilterSelector testFilterSelector(){ + FilterSelector filterSelector = new FilterSelector() { + @Override + public List getSupportedFilterNames() { + return List.of("a", "b"); + } + }; + return filterSelector; + } + + @Bean + public WebSocketHandler webSocketHandler(){ + return Mockito.mock(WebSocketHandler.class); + } + + @Bean + public ObjectMapper objectMapper(){ + return new ObjectMapper(); + } + + @Bean + public NodeDAO nodeDAO(){ + return Mockito.mock(NodeDAO.class); + } +} From 495ecc420ca7488728435010486a685ab1dcd926 Mon Sep 17 00:00:00 2001 From: georgweiss Date: Mon, 30 Jun 2025 14:31:18 +0200 Subject: [PATCH 4/5] Support for auto filter activation --- .../client/SaveAndRestoreClient.java | 48 ++++--- .../client/SaveAndRestoreClientImpl.java | 12 -- .../ui/SaveAndRestoreController.java | 128 ++++++++---------- .../ui/SaveAndRestoreService.java | 9 -- .../ui/search/SearchAndFilterTab.java | 13 +- .../search/SearchAndFilterViewController.java | 8 ++ .../saveandrestore/messages.properties | 1 + .../saveandrestore/ui/SaveAndRestoreUI.fxml | 31 +++-- .../model/search/FilterActivator.java | 93 +++++++++++++ .../model/websocket/MessageType.java | 4 +- .../websocket/WebMessageDeserializer.java | 2 +- .../BeamModeFilterSelector.java | 46 ------- .../FilterSelectionHandler.java | 91 ------------- .../filterselection/FilterSelector.java | 97 ------------- .../web/controllers/FilterController.java | 29 ---- .../filterselection/FilterSelectorTest.java | 41 ------ .../FilterSelectorTestConfig.java | 78 ----------- 17 files changed, 221 insertions(+), 510 deletions(-) create mode 100644 app/save-and-restore/model/src/main/java/org/phoebus/applications/saveandrestore/model/search/FilterActivator.java delete mode 100644 services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/filterselection/BeamModeFilterSelector.java delete mode 100644 services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/filterselection/FilterSelectionHandler.java delete mode 100644 services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/filterselection/FilterSelector.java delete mode 100644 services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/filterselection/FilterSelectorTest.java delete mode 100644 services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/filterselection/FilterSelectorTestConfig.java diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClient.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClient.java index 06a9c2a91c..688a6b9b09 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClient.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClient.java @@ -19,14 +19,23 @@ package org.phoebus.applications.saveandrestore.client; import org.phoebus.applications.saveandrestore.SaveAndRestoreClientException; -import org.phoebus.applications.saveandrestore.model.*; +import org.phoebus.applications.saveandrestore.model.CompositeSnapshot; +import org.phoebus.applications.saveandrestore.model.Configuration; +import org.phoebus.applications.saveandrestore.model.ConfigurationData; +import org.phoebus.applications.saveandrestore.model.Node; +import org.phoebus.applications.saveandrestore.model.NodeType; +import org.phoebus.applications.saveandrestore.model.RestoreResult; +import org.phoebus.applications.saveandrestore.model.Snapshot; +import org.phoebus.applications.saveandrestore.model.SnapshotData; +import org.phoebus.applications.saveandrestore.model.SnapshotItem; +import org.phoebus.applications.saveandrestore.model.Tag; +import org.phoebus.applications.saveandrestore.model.TagData; +import org.phoebus.applications.saveandrestore.model.UserData; import org.phoebus.applications.saveandrestore.model.search.Filter; import org.phoebus.applications.saveandrestore.model.search.SearchResult; import javax.ws.rs.core.MultivaluedMap; -import java.util.Collections; import java.util.List; -import java.util.Set; /** * Implementations of this interface shall handle the communication to the save-and-restore service. @@ -123,6 +132,7 @@ public interface SaveAndRestoreClient { /** * Constructs a path like string to facilitate location of a {@link Node} in the tree structure. + * * @param uniqueNodeId Unique id * @return Path like /Root folder/foo/bar/my/favourite/node */ @@ -133,8 +143,9 @@ public interface SaveAndRestoreClient { /** * Creates a new {@link Node} of type {@link NodeType#CONFIGURATION} in the remote * service. - * @param parentNodeId Non-null and non-empty unique id of an existing parent {@link Node}, - * which must be of type {@link NodeType#FOLDER}. + * + * @param parentNodeId Non-null and non-empty unique id of an existing parent {@link Node}, + * which must be of type {@link NodeType#FOLDER}. * @param configuration {@link ConfigurationData} object * @return A representation of the persisted {@link Configuration}. */ @@ -147,8 +158,9 @@ public interface SaveAndRestoreClient { /** * Creates a {@link Snapshot} + * * @param parentNodeId The unique id of the configuration {@link Node} associated with the {@link Snapshot} - * @param snapshot The {@link Snapshot} data object. + * @param snapshot The {@link Snapshot} data object. * @return The new {@link Snapshot} as persisted by the service */ Snapshot createSnapshot(String parentNodeId, Snapshot snapshot); @@ -157,7 +169,8 @@ public interface SaveAndRestoreClient { /** * Creates a new {@link CompositeSnapshot}. - * @param parentNodeId The parent {@link Node} for the new {@link CompositeSnapshot} + * + * @param parentNodeId The parent {@link Node} for the new {@link CompositeSnapshot} * @param compositeSnapshot The data object * @return A {@link CompositeSnapshot} as persisted by the service. */ @@ -167,8 +180,9 @@ public interface SaveAndRestoreClient { * Utility for the purpose of checking whether a set of snapshots contain duplicate PV names. * The input snapshot ids may refer to {@link Node}s of types {@link org.phoebus.applications.saveandrestore.model.NodeType#SNAPSHOT} * and {@link org.phoebus.applications.saveandrestore.model.NodeType#COMPOSITE_SNAPSHOT} + * * @param snapshotNodeIds List of {@link Node} ids corresponding to {@link Node}s of types {@link org.phoebus.applications.saveandrestore.model.NodeType#SNAPSHOT} - * and {@link org.phoebus.applications.saveandrestore.model.NodeType#COMPOSITE_SNAPSHOT} + * and {@link org.phoebus.applications.saveandrestore.model.NodeType#COMPOSITE_SNAPSHOT} * @return A list of PV names that occur more than once across the list of {@link Node}s corresponding * to the input. Empty if no duplicates are found. */ @@ -177,6 +191,7 @@ public interface SaveAndRestoreClient { /** * Updates a composite snapshot. Note that the list of referenced snapshots must be the full list of wanted * snapshots, i.e. there is no way to only add new references, or only remove unwanted references. + * * @param compositeSnapshot A {@link CompositeSnapshot} object hold data. * @return The updates {@link CompositeSnapshot} object. */ @@ -184,6 +199,7 @@ public interface SaveAndRestoreClient { /** * Search for {@link Node}s based on the specified search parameters. + * * @param searchParams {@link MultivaluedMap} holding search parameters. * @return A {@link SearchResult} with potentially empty list of matching {@link Node}s */ @@ -191,6 +207,7 @@ public interface SaveAndRestoreClient { /** * Save a new or updated {@link Filter} + * * @param filter The {@link Filter} to save * @return The saved {@link Filter} */ @@ -203,12 +220,14 @@ public interface SaveAndRestoreClient { /** * Deletes a {@link Filter} based on its name. + * * @param name Unique name of a {@link Filter} */ void deleteFilter(String name); /** * Adds a tag to a list of unique node ids, see {@link TagData} + * * @param tagData see {@link TagData} * @return A list of updated {@link Node}s. This may contain fewer elements than the list of unique node ids * passed in the tagData parameter. @@ -217,6 +236,7 @@ public interface SaveAndRestoreClient { /** * Deletes a tag from a list of unique node ids, see {@link TagData} + * * @param tagData see {@link TagData} * @return A list of updated {@link Node}s. This may contain fewer elements than the list of unique node ids * passed in the tagData parameter. @@ -225,6 +245,7 @@ public interface SaveAndRestoreClient { /** * For the purpose of login and authentication in the service. + * * @param userName User's account name * @param password User's password * @return A {@link UserData} object if login is successful, otherwise implementation should throw @@ -234,6 +255,7 @@ public interface SaveAndRestoreClient { /** * Requests service to restore the specified {@link SnapshotItem}s + * * @param snapshotItems A {@link List} of {@link SnapshotItem}s * @return A @{@link List} of {@link RestoreResult}s with information on potentially failed {@link SnapshotItem}s. */ @@ -241,6 +263,7 @@ public interface SaveAndRestoreClient { /** * Requests service to restore the specified snapshot. + * * @param snapshotNodeId Unique id of a snapshot * @return A @{@link List} of {@link RestoreResult}s with information on potentially failed {@link SnapshotItem}s. */ @@ -248,16 +271,9 @@ public interface SaveAndRestoreClient { /** * Requests service to take a snapshot. + * * @param configurationNodeId The unique id of the {@link Configuration} for which to take the snapshot * @return A {@link List} of {@link SnapshotItem}s carrying snapshot values read by the service. */ List takeSnapshot(String configurationNodeId); - - default String getAutoSelectedFilter(){ - return null; - } - - default Set getAutoFilterSelectorNames(){ - return Collections.emptySet(); - } } diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClientImpl.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClientImpl.java index e00dcf129e..05c5755d8e 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClientImpl.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClientImpl.java @@ -694,16 +694,4 @@ private HttpResponse getCall(String relativeUrl) { throw new RuntimeException(e); } } - - @Override - public String getAutoSelectedFilter(){ - HttpResponse response = getCall("/filter/selected"); - return response.body(); - } - - @Override - public Set getAutoFilterSelectorNames(){ - return getCall("/filter/selectors", new TypeReference<>() { - }); - } } diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java index 5a0bfe06ac..401b85028a 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java @@ -23,6 +23,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import javafx.application.Platform; import javafx.beans.binding.Bindings; +import javafx.beans.property.BooleanProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleObjectProperty; @@ -35,6 +36,7 @@ import javafx.scene.control.Alert.AlertType; import javafx.scene.control.Button; import javafx.scene.control.ButtonType; +import javafx.scene.control.CheckBox; import javafx.scene.control.ComboBox; import javafx.scene.control.ContextMenu; import javafx.scene.control.ListCell; @@ -42,14 +44,12 @@ import javafx.scene.control.Menu; import javafx.scene.control.MenuItem; import javafx.scene.control.MultipleSelectionModel; -import javafx.scene.control.RadioButton; import javafx.scene.control.SelectionMode; import javafx.scene.control.SeparatorMenuItem; import javafx.scene.control.SplitPane; import javafx.scene.control.Tab; import javafx.scene.control.TabPane; import javafx.scene.control.TextInputDialog; -import javafx.scene.control.ToggleGroup; import javafx.scene.control.Tooltip; import javafx.scene.control.TreeItem; import javafx.scene.control.TreeView; @@ -60,7 +60,6 @@ import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; import javafx.scene.input.TransferMode; -import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; import javafx.stage.FileChooser; import javafx.util.Callback; @@ -76,6 +75,7 @@ import org.phoebus.applications.saveandrestore.model.NodeType; import org.phoebus.applications.saveandrestore.model.Tag; import org.phoebus.applications.saveandrestore.model.search.Filter; +import org.phoebus.applications.saveandrestore.model.search.FilterActivator; import org.phoebus.applications.saveandrestore.model.search.SearchQueryUtil; import org.phoebus.applications.saveandrestore.model.search.SearchQueryUtil.Keys; import org.phoebus.applications.saveandrestore.model.search.SearchResult; @@ -127,6 +127,7 @@ import java.util.Map; import java.util.Optional; import java.util.ResourceBundle; +import java.util.ServiceLoader; import java.util.Stack; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -163,16 +164,7 @@ public class SaveAndRestoreController extends SaveAndRestoreBaseController @SuppressWarnings("unused") @FXML - private RadioButton autoFilter; - - @SuppressWarnings("unused") - @FXML - private HBox filterSelectionButtons; - - @SuppressWarnings("unused") - @FXML - private RadioButton manualFilter; - + private CheckBox autoFilterCheckbox; @SuppressWarnings("unused") @FXML @@ -185,11 +177,9 @@ public class SaveAndRestoreController extends SaveAndRestoreBaseController protected static final Logger LOG = Logger.getLogger(SaveAndRestoreController.class.getName()); protected Comparator> treeNodeComparator; protected SimpleBooleanProperty disabledUi = new SimpleBooleanProperty(false); - //private final SimpleBooleanProperty filterEnabledProperty = new SimpleBooleanProperty(false); private static final Logger logger = Logger.getLogger(SaveAndRestoreController.class.getName()); private final ObjectProperty currentFilterProperty = new SimpleObjectProperty<>(null); - private final ObjectProperty filterSelectionModeProperty = - new SimpleObjectProperty<>(FilterOperationMode.MANUAL); + @SuppressWarnings("unused") @FXML @@ -201,7 +191,6 @@ public class SaveAndRestoreController extends SaveAndRestoreBaseController private final ObservableList searchResultNodes = FXCollections.observableArrayList(); private final ObservableList filtersList = FXCollections.observableArrayList(); - private final ObservableList autoFilterSelectorNames = FXCollections.observableArrayList(); private final CountDownLatch treeInitializationCountDownLatch = new CountDownLatch(1); private final ObservableList selectedItemsProperty = FXCollections.observableArrayList(); @@ -214,6 +203,14 @@ public class SaveAndRestoreController extends SaveAndRestoreBaseController private final MenuItem deleteNodeMenuItem = new MenuItem(Messages.contextMenuDelete, ImageCache.getImageView(ImageCache.class, "/icons/delete.png")); private final MenuItem pasteMenuItem = new MenuItem(Messages.paste, ImageCache.getImageView(ImageCache.class, "/icons/paste.png")); + private final BooleanProperty autoFilterActive = new SimpleBooleanProperty(); + + /** + * Potentially empty list of {@link FilterActivator}s implementing auto selection of {@link Filter}s. + */ + private final List filterActivators = new ArrayList<>(); + + List menuItems = Arrays.asList( new LoginMenuItem(this, selectedItemsProperty, () -> ApplicationService.createInstance("credentials_management")), @@ -260,37 +257,21 @@ public void initialize(URL url, ResourceBundle resourceBundle) { browserSelectionModel = treeView.getSelectionModel(); - ImageView searchButtonImageView = ImageCache.getImageView(SaveAndRestoreApplication.class, "/icons/sar-search.png"); - searchButtonImageView.setFitWidth(20); - searchButtonImageView.setFitHeight(20); - searchButton.setGraphic(searchButtonImageView); - - manualFilter.setUserData(FilterOperationMode.MANUAL); - autoFilter.setUserData(FilterOperationMode.AUTO); - - ToggleGroup toggleGroup = new ToggleGroup(); - toggleGroup.getToggles().addAll(autoFilter, manualFilter); - toggleGroup.selectedToggleProperty().addListener((obs, o, n) -> - filterSelectionModeProperty.set((FilterOperationMode) n.getUserData())); - filterSelectionModeProperty.addListener((obs, o, n) -> { - if (n != null && n.equals(FilterOperationMode.AUTO)) { - // Check if a filter selection is active on service side - JobManager.schedule("Get and set auto filter", monitor -> { - String autoSelectedFilter = saveAndRestoreService.getAutoSelectedFilter(); - if (autoSelectedFilter != null) { - Optional filterOptional = - saveAndRestoreService.getAllFilters().stream().filter(f -> autoSelectedFilter.equals(f.getName())).findFirst(); - filterOptional.ifPresent(this::selectFilter); - } - }); + autoFilterCheckbox.selectedProperty().bindBidirectional(autoFilterActive); + + autoFilterActive.addListener((obs, o, n) -> { + if (n) { + // Check if a filter selection is active in any implementation. Match on first found. + Optional filterActivatorOptional = + filterActivators.stream().filter(a -> a.getActivatedFilter() != null).findFirst(); + filterActivatorOptional.ifPresent(filterActivator -> activateFilter(filterActivator.getActivatedFilter())); } }); - filterSelectionButtons.visibleProperty().bind(Bindings.createBooleanBinding(() -> - !filtersList.isEmpty() && !autoFilterSelectorNames.isEmpty(), filtersList, autoFilterSelectorNames)); + autoFilterCheckbox.visibleProperty().bind(Bindings.createBooleanBinding(() -> + !filtersList.isEmpty(), filtersList)); - filtersComboBox.disableProperty().bind(Bindings.createBooleanBinding(() -> - filterSelectionModeProperty.get().equals(FilterOperationMode.AUTO), filterSelectionModeProperty)); + filtersComboBox.disableProperty().bind(Bindings.createBooleanBinding(autoFilterActive::get, autoFilterActive)); filtersComboBox.valueProperty().bindBidirectional(currentFilterProperty); treeView.setEditable(true); @@ -356,7 +337,6 @@ public Filter fromString(String s) { filtersComboBox.itemsProperty().bind(new SimpleObjectProperty<>(filtersList)); - //enableFilterCheckBox.disableProperty().bind(Bindings.createBooleanBinding(filtersList::isEmpty, filtersList)); // Clear clipboard to make sure that only custom data format is // considered in paste actions. @@ -388,6 +368,7 @@ public Filter fromString(String s) { webSocketClientService.setConnectCallback(this::handleWebSocketConnected); webSocketClientService.setDisconnectCallback(this::handleWebSocketDisconnected); webSocketClientService.connect(); + } /** @@ -423,9 +404,7 @@ public void loadInitialData() { } // Get all filters from service - List filters = saveAndRestoreService.getAllFilters(); - // Get all auto select filter names - autoFilterSelectorNames.setAll(saveAndRestoreService.getAutoFilterSelectorNames()); + filtersList.addAll(saveAndRestoreService.getAllFilters()); Platform.runLater(() -> { treeView.setRoot(rootItem); @@ -433,14 +412,13 @@ public void loadInitialData() { // Event handler for expanding nodes treeView.getRoot().addEventHandler(TreeItem.branchExpandedEvent(), e -> expandTreeNode(e.getTreeItem())); treeInitializationCountDownLatch.countDown(); - - filtersList.setAll(filters); filtersList.add(0, null); String savedFilterName = getSavedFilterName(); if (savedFilterName != null) { Optional f = filtersComboBox.getItems().stream().filter(filter -> filter.getName().equals(savedFilterName)).findFirst(); f.ifPresent(filter -> filtersComboBox.getSelectionModel().select(filter)); } + setupFilterActivators(); }); }); } @@ -952,7 +930,7 @@ public void saveLocalState() { findExpandedNodes(expandedNodes, treeView.getRoot()); try { PhoebusPreferenceService.userNodeForClass(SaveAndRestoreApplication.class).put(TREE_STATE, objectMapper.writeValueAsString(expandedNodes)); - if (/*filterEnabledProperty.get() &&*/ filtersComboBox.getSelectionModel().getSelectedItem() != null) { + if (filtersComboBox.getSelectionModel().getSelectedItem() != null) { PhoebusPreferenceService.userNodeForClass(SaveAndRestoreApplication.class).put(FILTER_NAME, objectMapper.writeValueAsString(filtersComboBox.getSelectionModel().getSelectedItem().getName())); } @@ -965,6 +943,7 @@ public void saveLocalState() { public boolean handleTabClosed() { saveLocalState(); webSocketClientService.closeWebSocket(); + filterActivators.forEach(FilterActivator::stop); return true; } @@ -1087,7 +1066,7 @@ protected void moveNodes(List sourceNodes, Node targetNode, TransferMode t * * * @param uri An {@link URI} on the form file:/unique-id?action=[open_sar_node|open_sar_filter]&app=saveandrestore, where unique-id is the - * unique id of a {@link Node} or the unique id (name) of a {@link Filter}. If action is not sepcified, + * unique id of a {@link Node} or the unique id (name) of a {@link Filter}. If action is not specified, * it defaults to open-node. */ public void openResource(URI uri) { @@ -1461,27 +1440,45 @@ public void handleWebSocketMessage(SaveAndRestoreWebSocketMessage saveAndRestore case NODE_UPDATED -> nodeChanged((Node) saveAndRestoreWebSocketMessage.payload()); case FILTER_ADDED_OR_UPDATED -> filterAddedOrUpdated((Filter) saveAndRestoreWebSocketMessage.payload()); case FILTER_REMOVED -> filterRemoved((String) saveAndRestoreWebSocketMessage.payload()); - case FILTER_SELECTED -> selectFilter((Filter) saveAndRestoreWebSocketMessage.payload()); - case FILTER_UNSELECTED -> unselectFilter((Filter) saveAndRestoreWebSocketMessage.payload()); } } + private void setupFilterActivators() { + filterActivators.addAll( + ServiceLoader.load(FilterActivator.class).stream().map(ServiceLoader.Provider::get).toList()); + filterActivators.forEach(a -> a.setCallbacks(this::activateFilter, this::deactivateFilter)); + } + + private Optional findFilter(String filterName) { + return filtersList.stream().filter(f -> f != null && f.getName().equals(filterName)).findFirst(); + } + /** - * Selects a {@link Filter} by + * Activates a {@link Filter}. If filterName does not match any of the available {@link Filter}s, + * nothing happens except logging of the inconsistency. * - * @param filter The {@link Filter} to select. + * @param filterName The name of the {@link Filter} to select. */ - private void selectFilter(Filter filter) { - if (filterSelectionModeProperty.get().equals(FilterOperationMode.AUTO)) { - Platform.runLater(() -> filtersComboBox.getSelectionModel().select(filter)); + public void activateFilter(String filterName) { + if (autoFilterActive.get()) { + Optional filterOptional = findFilter(filterName); + if (filterOptional.isPresent()) { + Platform.runLater(() -> filtersComboBox.getSelectionModel().select(filterOptional.get())); + } else { + logger.log(Level.WARNING, "Cannot activate filter as filter named \"" + filterName + "\" was not found."); + } } - //filterEnabledProperty.setValue(true); } - private void unselectFilter(Filter filter) { - if (filterSelectionModeProperty.get().equals(FilterOperationMode.AUTO)) { + /** + * If auto {@link Filter} activation is enabled and the active filter matches filterName, then + * this method will switch to no filter but maintain auto activation. + * @param filterName Name/id of the de-activated filter. + */ + public void deactivateFilter(String filterName) { + if (autoFilterActive.get()) { Filter currentlySelectedFilter = filtersComboBox.getSelectionModel().getSelectedItem(); - if (currentlySelectedFilter != null && currentlySelectedFilter.equals(filter)) { + if (currentlySelectedFilter != null && currentlySelectedFilter.getName().equals(filterName)) { Platform.runLater(() -> filtersComboBox.getSelectionModel().select(null)); } } @@ -1496,9 +1493,4 @@ private void handleWebSocketDisconnected() { serviceConnected.setValue(false); saveLocalState(); } - - private enum FilterOperationMode { - MANUAL, - AUTO - } } diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreService.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreService.java index 002777910e..e6d87602cb 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreService.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreService.java @@ -428,13 +428,4 @@ private VType readFromArchiver(String pvName, Instant time) { return VDisconnectedData.INSTANCE; } } - - public Set getAutoFilterSelectorNames(){ - return saveAndRestoreClient.getAutoFilterSelectorNames(); - } - - public String getAutoSelectedFilter(){ - String selectedFilter = saveAndRestoreClient.getAutoSelectedFilter(); - return selectedFilter.isEmpty() ? null : selectedFilter; - } } diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/search/SearchAndFilterTab.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/search/SearchAndFilterTab.java index 7a048e309b..7001537dde 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/search/SearchAndFilterTab.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/search/SearchAndFilterTab.java @@ -38,8 +38,6 @@ public class SearchAndFilterTab extends SaveAndRestoreTab { public static final String SEARCH_AND_FILTER_TAB_ID = "SearchAndFilterTab"; - private SearchAndFilterViewController searchAndFilterViewController; - public SearchAndFilterTab(SaveAndRestoreController saveAndRestoreController) { setId(SEARCH_AND_FILTER_TAB_ID); @@ -67,8 +65,7 @@ public SearchAndFilterTab(SaveAndRestoreController saveAndRestoreController) { Node node = loader.load(); controller = loader.getController(); setContent(node); - searchAndFilterViewController = loader.getController(); - setOnCloseRequest(event -> searchAndFilterViewController.handleSaveAndFilterTabClosed()); + setOnCloseRequest(event -> ((SearchAndFilterViewController)controller).handleSaveAndFilterTabClosed()); } catch (IOException e) { Logger.getLogger(SearchAndFilterTab.class.getName()) .log(Level.SEVERE, "Unable to load search tab content fxml", e); @@ -78,4 +75,12 @@ public SearchAndFilterTab(SaveAndRestoreController saveAndRestoreController) { setText(Messages.search); setGraphic(new ImageView(ImageCache.getImage(ImageCache.class, "/icons/sar-search_18x18.png"))); } + + public void filterActivated(String filterName){ + ((SearchAndFilterViewController)controller).filterActivated(filterName); + } + + public void filterDeactivated(String filterName){ + ((SearchAndFilterViewController)controller).filterDeactivated(filterName); + } } diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/search/SearchAndFilterViewController.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/search/SearchAndFilterViewController.java index 7c30f1f6d9..25f7d53bbe 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/search/SearchAndFilterViewController.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/search/SearchAndFilterViewController.java @@ -647,4 +647,12 @@ public void handleWebSocketMessage(SaveAndRestoreWebSocketMessage saveAndRest case FILTER_REMOVED, FILTER_ADDED_OR_UPDATED -> loadFilters(); } } + + public void filterActivated(String filterName){ + + } + + public void filterDeactivated(String filterName){ + + } } diff --git a/app/save-and-restore/app/src/main/resources/org/phoebus/applications/saveandrestore/messages.properties b/app/save-and-restore/app/src/main/resources/org/phoebus/applications/saveandrestore/messages.properties index 90dc4ea20d..9e86307194 100644 --- a/app/save-and-restore/app/src/main/resources/org/phoebus/applications/saveandrestore/messages.properties +++ b/app/save-and-restore/app/src/main/resources/org/phoebus/applications/saveandrestore/messages.properties @@ -91,6 +91,7 @@ failedSaveFilter=Failed to save filter failedToPasteObjects=Failed to paste object(s) filter=Filter filterAuto=Auto +filterAutoTooltip=Check to enable automatic filter activation filterEditorDescriptionOrComment=Description/Comment filterEditorNodeName=Node Name filterEditorNodeTypeFolder=Folder diff --git a/app/save-and-restore/app/src/main/resources/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreUI.fxml b/app/save-and-restore/app/src/main/resources/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreUI.fxml index 009285f51d..531e5fee12 100644 --- a/app/save-and-restore/app/src/main/resources/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreUI.fxml +++ b/app/save-and-restore/app/src/main/resources/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreUI.fxml @@ -2,6 +2,7 @@ + @@ -27,23 +28,23 @@ - - - - - - - - - - - - - - - + + + + + + + +