From 4dd2a6633f6ed59d230ea782f48453e75e4aed85 Mon Sep 17 00:00:00 2001 From: Richard Eckart de Castilho Date: Sun, 24 Sep 2023 18:50:42 +0200 Subject: [PATCH] #4205 - Ability to refresh document to load pending suggestions - Add a action bar button to refresh the document in case there are recommenders - Wiggle the button a bit if there are pending suggestions --- .../RecommendationEditorExtension.java | 4 ++ .../RecommenderActionBarExtension.java | 65 +++++++++++++++++++ .../actionbar/RecommenderActionBarPanel.html | 54 +++++++++++++++ .../actionbar/RecommenderActionBarPanel.java | 57 ++++++++++++++++ .../RecommenderActionBarPanel.properties | 17 +++++ .../RecommenderServiceAutoConfiguration.java | 8 +++ .../footer/RRecommenderLogMessage.java | 48 +++++++++++++- ...mendationEventWebsocketControllerImpl.java | 18 ++++- .../src/RecommendationEventFooterPanel.svelte | 9 ++- 9 files changed, 273 insertions(+), 7 deletions(-) create mode 100644 inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/actionbar/RecommenderActionBarExtension.java create mode 100644 inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/actionbar/RecommenderActionBarPanel.html create mode 100644 inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/actionbar/RecommenderActionBarPanel.java create mode 100644 inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/actionbar/RecommenderActionBarPanel.properties diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/RecommendationEditorExtension.java b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/RecommendationEditorExtension.java index a6f4003e684..fa4823d5d94 100644 --- a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/RecommendationEditorExtension.java +++ b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/RecommendationEditorExtension.java @@ -58,6 +58,7 @@ import de.tudarmstadt.ukp.inception.editor.AnnotationEditorExtension; import de.tudarmstadt.ukp.inception.editor.AnnotationEditorExtensionImplBase; import de.tudarmstadt.ukp.inception.editor.action.AnnotationActionHandler; +import de.tudarmstadt.ukp.inception.recommendation.actionbar.RecommenderActionBarPanel; import de.tudarmstadt.ukp.inception.recommendation.api.RecommendationService; import de.tudarmstadt.ukp.inception.recommendation.api.model.AnnotationSuggestion; import de.tudarmstadt.ukp.inception.recommendation.api.model.Predictions; @@ -325,6 +326,9 @@ public void renderRequested(AjaxRequestTarget aTarget, AnnotatorState aState) // also update their state to remain in sync with the new predictions applicationEventPublisher .publishEvent(new PredictionsSwitchedEvent(this, sessionOwner, aState)); + + aTarget.appendJavaScript("document.body.classList.remove('" + + RecommenderActionBarPanel.STATE_PREDICTIONS_AVAILABLE + "')"); } @Override diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/actionbar/RecommenderActionBarExtension.java b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/actionbar/RecommenderActionBarExtension.java new file mode 100644 index 00000000000..2b7aaad75df --- /dev/null +++ b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/actionbar/RecommenderActionBarExtension.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Technische Universität Darmstadt under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The Technische Universität Darmstadt + * licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.tudarmstadt.ukp.inception.recommendation.actionbar; + +import org.apache.wicket.markup.html.panel.Panel; +import org.springframework.core.annotation.Order; + +import de.tudarmstadt.ukp.clarin.webanno.api.annotation.actionbar.ActionBarExtension; +import de.tudarmstadt.ukp.clarin.webanno.api.annotation.page.AnnotationPageBase; +import de.tudarmstadt.ukp.clarin.webanno.ui.annotation.AnnotationPage; +import de.tudarmstadt.ukp.inception.recommendation.api.RecommendationService; +import de.tudarmstadt.ukp.inception.recommendation.config.RecommenderServiceAutoConfiguration; +import de.tudarmstadt.ukp.inception.rendering.editorstate.AnnotatorState; + +/** + *

+ * This class is exposed as a Spring Component via + * {@link RecommenderServiceAutoConfiguration#recommenderActionBarExtension}. + *

+ */ +@Order(2000) +public class RecommenderActionBarExtension + implements ActionBarExtension +{ + private final RecommendationService recommendationService; + + public RecommenderActionBarExtension(RecommendationService aRecommendationService) + { + recommendationService = aRecommendationService; + } + + @Override + public boolean accepts(AnnotationPageBase aPage) + { + if (!(aPage instanceof AnnotationPage)) { + return false; + } + + return aPage.getModel() // + .map(AnnotatorState::getProject) // + .map(recommendationService::existsEnabledRecommender) // + .orElse(false).getObject(); + } + + @Override + public Panel createActionBarItem(String aId, AnnotationPageBase aPage) + { + return new RecommenderActionBarPanel(aId, aPage); + } +} diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/actionbar/RecommenderActionBarPanel.html b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/actionbar/RecommenderActionBarPanel.html new file mode 100644 index 00000000000..fde19abb1f8 --- /dev/null +++ b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/actionbar/RecommenderActionBarPanel.html @@ -0,0 +1,54 @@ + + + + + + + +
+
+ +
+
+
+ + \ No newline at end of file diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/actionbar/RecommenderActionBarPanel.java b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/actionbar/RecommenderActionBarPanel.java new file mode 100644 index 00000000000..afa39653032 --- /dev/null +++ b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/actionbar/RecommenderActionBarPanel.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Technische Universität Darmstadt under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The Technische Universität Darmstadt + * licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.tudarmstadt.ukp.inception.recommendation.actionbar; + +import java.lang.invoke.MethodHandles; + +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.markup.html.panel.Panel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import de.tudarmstadt.ukp.clarin.webanno.api.annotation.page.AnnotationPageBase; +import de.tudarmstadt.ukp.clarin.webanno.support.lambda.LambdaAjaxLink; + +public class RecommenderActionBarPanel + extends Panel +{ + private static final long serialVersionUID = -2999081548651637508L; + + private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + public static final String STATE_PREDICTIONS_AVAILABLE = "state-predictions-available"; + + private final LambdaAjaxLink refreshDocumentButton; + + private final AnnotationPageBase page; + + public RecommenderActionBarPanel(String aId, AnnotationPageBase aPage) + { + super(aId); + + page = aPage; + + refreshDocumentButton = new LambdaAjaxLink("refreshDocument", this::actionRefreshDocument); + add(refreshDocumentButton); + } + + private void actionRefreshDocument(AjaxRequestTarget aTarget) + { + page.actionRefreshDocument(aTarget); + } +} diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/actionbar/RecommenderActionBarPanel.properties b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/actionbar/RecommenderActionBarPanel.properties new file mode 100644 index 00000000000..5e900d4d042 --- /dev/null +++ b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/actionbar/RecommenderActionBarPanel.properties @@ -0,0 +1,17 @@ +# Licensed to the Technische Universität Darmstadt under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The Technische Universität Darmstadt +# licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +refreshDocument=Refresh document diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/config/RecommenderServiceAutoConfiguration.java b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/config/RecommenderServiceAutoConfiguration.java index e2c5c013e97..0452bbe0171 100644 --- a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/config/RecommenderServiceAutoConfiguration.java +++ b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/config/RecommenderServiceAutoConfiguration.java @@ -37,6 +37,7 @@ import de.tudarmstadt.ukp.clarin.webanno.security.UserDao; import de.tudarmstadt.ukp.inception.preferences.PreferencesService; import de.tudarmstadt.ukp.inception.recommendation.RecommendationEditorExtension; +import de.tudarmstadt.ukp.inception.recommendation.actionbar.RecommenderActionBarExtension; import de.tudarmstadt.ukp.inception.recommendation.api.LearningRecordService; import de.tudarmstadt.ukp.inception.recommendation.api.RecommendationService; import de.tudarmstadt.ukp.inception.recommendation.api.RecommenderFactoryRegistry; @@ -219,4 +220,11 @@ public RecommendationRelationRenderer recommendationRelationRenderer( return new RecommendationRelationRenderer(aRecommendationService, aAnnotationService, aFsRegistry); } + + @Bean + public RecommenderActionBarExtension recommenderActionBarExtension( + RecommendationService aRecommendationService) + { + return new RecommenderActionBarExtension(aRecommendationService); + } } diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/footer/RRecommenderLogMessage.java b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/footer/RRecommenderLogMessage.java index 9714e5a6e65..34e334fbf31 100644 --- a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/footer/RRecommenderLogMessage.java +++ b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/footer/RRecommenderLogMessage.java @@ -17,7 +17,12 @@ */ package de.tudarmstadt.ukp.inception.recommendation.footer; +import static java.util.Collections.emptySet; +import static java.util.stream.Collectors.toSet; + import java.io.Serializable; +import java.util.Collection; +import java.util.Set; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; @@ -35,22 +40,50 @@ public class RRecommenderLogMessage private static final String MESSAGE = "message"; private static final String LEVEL = "level"; + private static final String ADD_MARKER_CLASSES = "addClasses"; + private static final String REMOVE_MARKER_CLASSES = "removeClasses"; private final LogLevel level; private final String message; + private final Set markerClassesToRemove; + private final Set markerClassesToAdd; public RRecommenderLogMessage(LogMessage aMessage) { level = aMessage.getLevel(); message = aMessage.getMessage(); + markerClassesToAdd = emptySet(); + markerClassesToRemove = emptySet(); } - @JsonCreator public RRecommenderLogMessage(@JsonProperty(LEVEL) LogLevel aLevel, @JsonProperty(MESSAGE) String aMessage) + { + this(aLevel, aMessage, null, null); + } + + @JsonCreator + public RRecommenderLogMessage(@JsonProperty(LEVEL) LogLevel aLevel, + @JsonProperty(MESSAGE) String aMessage, + @JsonProperty(value = ADD_MARKER_CLASSES) Collection aMarkerClassesToAdd, + @JsonProperty(value = REMOVE_MARKER_CLASSES) Collection aMarkerClassesToRemove) { level = aLevel; message = aMessage; + + if (aMarkerClassesToAdd == null) { + markerClassesToAdd = emptySet(); + } + else { + markerClassesToAdd = aMarkerClassesToAdd.stream().collect(toSet()); + } + + if (aMarkerClassesToRemove == null) { + markerClassesToRemove = emptySet(); + } + else { + markerClassesToRemove = aMarkerClassesToRemove.stream().collect(toSet()); + } } @JsonProperty(LEVEL) @@ -65,11 +98,22 @@ public String getMessage() return message; } + @JsonProperty(ADD_MARKER_CLASSES) + public Set getMarkerClassesToAdd() + { + return markerClassesToAdd; + } + + @JsonProperty(REMOVE_MARKER_CLASSES) + public Set getMarkerClassesToRemove() + { + return markerClassesToRemove; + } + @Override public String toString() { return new ToStringBuilder(this, ToStringStyle.JSON_STYLE).append(LEVEL, level) .append(MESSAGE, message).toString(); } - } diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/footer/RecommendationEventWebsocketControllerImpl.java b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/footer/RecommendationEventWebsocketControllerImpl.java index 5b27c3b3975..df4d64f7ebf 100644 --- a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/footer/RecommendationEventWebsocketControllerImpl.java +++ b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/footer/RecommendationEventWebsocketControllerImpl.java @@ -20,6 +20,8 @@ import static de.tudarmstadt.ukp.inception.websocket.config.WebSocketConstants.TOPIC_ELEMENT_PROJECT; import static de.tudarmstadt.ukp.inception.websocket.config.WebSocketConstants.TOPIC_ELEMENT_USER; import static de.tudarmstadt.ukp.inception.websocket.config.WebSocketConstants.TOPIC_RECOMMENDER; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; import java.lang.invoke.MethodHandles; @@ -35,7 +37,9 @@ import org.springframework.stereotype.Controller; import de.tudarmstadt.ukp.clarin.webanno.model.Project; +import de.tudarmstadt.ukp.inception.recommendation.actionbar.RecommenderActionBarPanel; import de.tudarmstadt.ukp.inception.recommendation.event.RecommenderTaskNotificationEvent; +import de.tudarmstadt.ukp.inception.recommendation.tasks.PredictionTask; @ConditionalOnWebApplication @ConditionalOnExpression("${websocket.enabled:true} and ${websocket.recommender-events.enabled:true}") @@ -57,8 +61,18 @@ public RecommendationEventWebsocketControllerImpl(@Autowired SimpMessagingTempla public void onRecommenderTaskEvent(RecommenderTaskNotificationEvent aEvent) { Project project = aEvent.getProject(); - RRecommenderLogMessage eventMsg = new RRecommenderLogMessage(aEvent.getMessage().getLevel(), - aEvent.getMessage().getMessage()); + RRecommenderLogMessage eventMsg; + if (aEvent.getSource() instanceof PredictionTask) { + eventMsg = new RRecommenderLogMessage(aEvent.getMessage().getLevel(), + aEvent.getMessage().getMessage(), + asList(RecommenderActionBarPanel.STATE_PREDICTIONS_AVAILABLE), emptyList()); + + } + else { + eventMsg = new RRecommenderLogMessage(aEvent.getMessage().getLevel(), + aEvent.getMessage().getMessage()); + } + String channel = getChannel(project, aEvent.getUser()); LOG.debug("Sending event to [{}]: {}", channel, eventMsg); diff --git a/inception/inception-recommendation/src/main/ts/src/RecommendationEventFooterPanel.svelte b/inception/inception-recommendation/src/main/ts/src/RecommendationEventFooterPanel.svelte index b3dde9655ef..fb0de756202 100644 --- a/inception/inception-recommendation/src/main/ts/src/RecommendationEventFooterPanel.svelte +++ b/inception/inception-recommendation/src/main/ts/src/RecommendationEventFooterPanel.svelte @@ -24,6 +24,8 @@ interface RRecommenderLogMessage { level: "INFO" | "WARN" | "ERROR" message: string + addClasses: string[] + removeClasses: string[] } export let wsEndpointUrl: string; // should this be full ws://... url @@ -72,14 +74,15 @@ } var msgBody = JSON.parse(msg.body) as RRecommenderLogMessage; + console.log(msgBody) + msgBody.removeClasses?.forEach(c => document.body.classList.remove(c)) + msgBody.addClasses?.forEach(c => document.body.classList.add(c)) switch (msgBody.level) { case "ERROR": feedbackPanelExtension.addErrorToFeedbackPanel(msgBody.message); break; case "WARN": - feedbackPanelExtension.addEWarningToFeedbackPanel( - msgBody.message - ); + feedbackPanelExtension.addEWarningToFeedbackPanel(msgBody.message); break; case "INFO": feedbackPanelExtension.addInfoToFeedbackPanel(msgBody.message);