Skip to content

Commit

Permalink
Merge pull request #5243 from inception-project/feature/5206-Reduce-v…
Browse files Browse the repository at this point in the history
…isual-clutter-caused-by-flash-messages

#5206 - Reduce visual clutter caused by flash messages
reckart authored Jan 22, 2025

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
2 parents 38c720f + 28c4729 commit b80b970
Showing 10 changed files with 156 additions and 61 deletions.
Original file line number Diff line number Diff line change
@@ -17,9 +17,13 @@
*/
package de.tudarmstadt.ukp.inception.recommendation.config;

import de.tudarmstadt.ukp.inception.recommendation.config.RecommenderPropertiesImpl.Messages;

public interface RecommenderProperties
{
boolean isActionButtonsEnabled();

boolean isEnabled();

Messages getMessages();
}
Original file line number Diff line number Diff line change
@@ -30,6 +30,7 @@ public class RecommenderPropertiesImpl
{
private boolean enabled;
private boolean actionButtonsEnabled;
private Messages messages = new Messages();

@Override
public boolean isEnabled()
@@ -52,4 +53,70 @@ public void setActionButtonsEnabled(boolean aActionButtonsEnabled)
{
actionButtonsEnabled = aActionButtonsEnabled;
}

@Override
public Messages getMessages()
{
return messages;
}

public class Messages
{
private boolean noNewPredictionsAvailable = false;
private boolean newPredictionsAvailable = false;
private boolean evaluationSuccessful = false;
private boolean evaluationFailed = true;
private boolean nonTrainableRecommenderActivation = false;

public boolean isNoNewPredictionsAvailable()
{
return noNewPredictionsAvailable;
}

public void setNoNewPredictionsAvailable(boolean aNoNewPredictionsAvailable)
{
noNewPredictionsAvailable = aNoNewPredictionsAvailable;
}

public boolean isNewPredictionsAvailable()
{
return newPredictionsAvailable;
}

public void setNewPredictionsAvailable(boolean aNewPredictionsAvailable)
{
newPredictionsAvailable = aNewPredictionsAvailable;
}

public boolean isEvaluationSuccessful()
{
return evaluationSuccessful;
}

public void setEvaluationSuccessful(boolean aEvaluationSuccessful)
{
evaluationSuccessful = aEvaluationSuccessful;
}

public boolean isEvaluationFailed()
{
return evaluationFailed;
}

public void setEvaluationFailed(boolean aEvaluationFailed)
{
evaluationFailed = aEvaluationFailed;
}

public boolean isNonTrainableRecommenderActivation()
{
return nonTrainableRecommenderActivation;
}

public void setNonTrainableRecommenderActivation(boolean aNonTrainableRecommenderActivation)
{
nonTrainableRecommenderActivation = aNonTrainableRecommenderActivation;
}

}
}
Original file line number Diff line number Diff line change
@@ -26,11 +26,14 @@
import org.apache.commons.lang3.builder.ToStringStyle;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;

import de.tudarmstadt.ukp.inception.support.logging.LogLevel;
import de.tudarmstadt.ukp.inception.support.logging.LogMessage;

@JsonInclude(value = Include.NON_NULL)
public record RRecommenderLogMessage(@JsonProperty(LEVEL) LogLevel level,
@JsonProperty(MESSAGE) String message,
@JsonProperty(value = ADD_MARKER_CLASSES) Collection<String> markerClassesToAdd,
Original file line number Diff line number Diff line change
@@ -17,6 +17,7 @@
*/
package de.tudarmstadt.ukp.inception.recommendation.footer;

import static de.tudarmstadt.ukp.clarin.webanno.security.WicketSecurityUtils.getCsrfTokenFromSession;
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;
@@ -76,6 +77,7 @@ protected void onConfigure()
if (maybeProject.isPresent()) {
setVisible(maybeProject.isPresent());
setDefaultModel(Model.ofMap(Map.of( //
"csrfToken", getCsrfTokenFromSession(), //
"wsEndpointUrl", constructEndpointUrl(), //
"topicChannel",
TOPIC_ELEMENT_PROJECT + getProject().get().getId() + TOPIC_ELEMENT_USER
@@ -107,9 +109,8 @@ private Optional<Project> getProject()

private String constructEndpointUrl()
{
Url endPointUrl = Url.parse(format("%s%s", servletContext.getContextPath(), WS_ENDPOINT));
var endPointUrl = Url.parse(format("%s%s", servletContext.getContextPath(), WS_ENDPOINT));
endPointUrl.setProtocol("ws");
String fullUrl = RequestCycle.get().getUrlRenderer().renderFullUrl(endPointUrl);
return fullUrl;
return RequestCycle.get().getUrlRenderer().renderFullUrl(endPointUrl);
}
}
Original file line number Diff line number Diff line change
@@ -79,20 +79,22 @@ public void onRecommenderTaskEvent(RecommenderTaskNotificationEvent aEvent)

private RRecommenderLogMessage makeEventMessage(RecommenderTaskNotificationEvent aEvent)
{
var message = aEvent.getMessage();
var messageBody = message != null ? message.message : null;
var messageLevel = message != null ? message.level : null;

if (aEvent.getSource() instanceof PredictionTask) {
var sessionOwner = userService.get(aEvent.getUser());
var predictions = recommendationService.getIncomingPredictions(sessionOwner,
aEvent.getProject());

if (predictions != null && predictions.hasNewSuggestions()) {
return new RRecommenderLogMessage(aEvent.getMessage().getLevel(),
aEvent.getMessage().getMessage(),
return new RRecommenderLogMessage(messageLevel, messageBody,
asList(RecommenderActionBarPanel.STATE_PREDICTIONS_AVAILABLE), emptyList());
}
}

return new RRecommenderLogMessage(aEvent.getMessage().getLevel(),
aEvent.getMessage().getMessage());
return new RRecommenderLogMessage(messageLevel, messageBody);
}

static String getChannel(Project project, String aUsername)
Original file line number Diff line number Diff line change
@@ -37,6 +37,7 @@
import de.tudarmstadt.ukp.inception.recommendation.api.recommender.RecommendationEngine;
import de.tudarmstadt.ukp.inception.recommendation.api.recommender.RecommendationException;
import de.tudarmstadt.ukp.inception.recommendation.api.recommender.RecommenderContext;
import de.tudarmstadt.ukp.inception.recommendation.config.RecommenderProperties;
import de.tudarmstadt.ukp.inception.recommendation.event.RecommenderEvaluationResultEvent;
import de.tudarmstadt.ukp.inception.recommendation.event.RecommenderTaskNotificationEvent;
import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService;
@@ -56,6 +57,7 @@ public class NonTrainableRecommenderActivationTask
private @Autowired AnnotationSchemaService annoService;
private @Autowired RecommendationService recommendationService;
private @Autowired ApplicationEventPublisher appEventPublisher;
private @Autowired RecommenderProperties properties;

public NonTrainableRecommenderActivationTask(Builder<? extends Builder<?>> aBuilder)
{
@@ -120,13 +122,14 @@ public void execute()

recommendationService.setEvaluatedRecommenders(user, layer, evaluatedRecommenders);

appEventPublisher
.publishEvent(
RecommenderTaskNotificationEvent
.builder(this, getProject(), user.getUsername()) //
.withMessage(LogMessage.info(this,
"Activation of non-trainable recommenders complete"))
.build());
if (properties.getMessages().isNonTrainableRecommenderActivation()) {
appEventPublisher
.publishEvent(RecommenderTaskNotificationEvent
.builder(this, getProject(), user.getUsername()) //
.withMessage(LogMessage.info(this,
"Activation of non-trainable recommenders complete"))
.build());
}
}
}

Original file line number Diff line number Diff line change
@@ -64,6 +64,7 @@
import de.tudarmstadt.ukp.inception.recommendation.api.recommender.RecommendationEngine;
import de.tudarmstadt.ukp.inception.recommendation.api.recommender.RecommendationException;
import de.tudarmstadt.ukp.inception.recommendation.api.recommender.RecommenderContext;
import de.tudarmstadt.ukp.inception.recommendation.config.RecommenderProperties;
import de.tudarmstadt.ukp.inception.recommendation.event.RecommenderTaskNotificationEvent;
import de.tudarmstadt.ukp.inception.rendering.model.Range;
import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService;
@@ -87,6 +88,7 @@ public class PredictionTask
private @Autowired DocumentService documentService;
private @Autowired ApplicationEventPublisher appEventPublisher;
private @Autowired SuggestionSupportRegistry suggestionSupportRegistry;
private @Autowired RecommenderProperties properties;

private final SourceDocument currentDocument;
private final int predictionBegin;
@@ -146,25 +148,42 @@ public void execute()
recommendationService.putIncomingPredictions(sessionOwner, project, predictions);

if (predictions.hasNewSuggestions()) {
appEventPublisher.publishEvent(RecommenderTaskNotificationEvent
.builder(this, project, sessionOwner.getUsername()) //
.withMessage(LogMessage.info(this,
predictions.getNewSuggestionCount()
+ " new predictions available" //
+ " (some may be hidden/merged)")) //
.build());
logNewPredictionsAvailable(project, sessionOwner);
}
else {
appEventPublisher.publishEvent(RecommenderTaskNotificationEvent
.builder(this, project, sessionOwner.getUsername()) //
.withMessage(LogMessage.info(this,
"Prediction run produced no new suggestions")) //
.build());
logNoNewPredictionsAvailable(project, sessionOwner);
}
}
}
}

private void logNoNewPredictionsAvailable(Project project, User sessionOwner)
{
if (properties.getMessages().isNoNewPredictionsAvailable()) {
appEventPublisher.publishEvent(RecommenderTaskNotificationEvent
.builder(this, project, sessionOwner.getUsername()) //
.withMessage(
LogMessage.info(this, "Prediction run produced no new suggestions")) //
.build());
}
}

private void logNewPredictionsAvailable(Project project, User sessionOwner)
{
var event = RecommenderTaskNotificationEvent.builder(this, project,
sessionOwner.getUsername());

if (properties.getMessages().isNewPredictionsAvailable()) {
event.withMessage(LogMessage.info(this,
predictions.getNewSuggestionCount() + " new predictions available" //
+ " (some may be hidden/merged)"));

}

// Send the event anyway (even without message) to trigger the refresh button to wriggle
appEventPublisher.publishEvent(event.build());
}

public Predictions getPredictions()
{
return predictions;
Original file line number Diff line number Diff line change
@@ -49,6 +49,7 @@
import de.tudarmstadt.ukp.inception.recommendation.api.model.EvaluatedRecommender;
import de.tudarmstadt.ukp.inception.recommendation.api.model.Recommender;
import de.tudarmstadt.ukp.inception.recommendation.api.recommender.RecommendationException;
import de.tudarmstadt.ukp.inception.recommendation.config.RecommenderProperties;
import de.tudarmstadt.ukp.inception.recommendation.event.RecommenderEvaluationResultEvent;
import de.tudarmstadt.ukp.inception.recommendation.event.RecommenderTaskNotificationEvent;
import de.tudarmstadt.ukp.inception.scheduling.SchedulingService;
@@ -77,6 +78,7 @@ public class SelectionTask
private @Autowired RecommendationService recommendationService;
private @Autowired ApplicationEventPublisher appEventPublisher;
private @Autowired SchedulingService schedulingService;
private @Autowired RecommenderProperties properties;

private final SourceDocument currentDocument;
private final String dataOwner;
@@ -241,20 +243,24 @@ private void logNoRecommendersSeen(String sessionOwnerName)
private void logEvaluationSuccessful(User sessionOwner)
{
LOG.info("[{}]: Evaluation complete", sessionOwner.getUsername());
appEventPublisher.publishEvent(RecommenderTaskNotificationEvent
.builder(this, getProject(), sessionOwner.getUsername()) //
.withMessage(LogMessage.info(this, "Evaluation complete")) //
.build());
if (properties.getMessages().isEvaluationSuccessful()) {
appEventPublisher.publishEvent(RecommenderTaskNotificationEvent
.builder(this, getProject(), sessionOwner.getUsername()) //
.withMessage(LogMessage.info(this, "Evaluation complete")) //
.build());
}
}

private void logEvaluationFailed(Project project, User sessionOwner, String recommenderName,
Throwable e)
{
LOG.error("[{}][{}]: Evaluation failed", sessionOwner.getUsername(), recommenderName, e);
appEventPublisher.publishEvent(
RecommenderTaskNotificationEvent.builder(this, project, sessionOwner.getUsername()) //
.withMessage(LogMessage.error(this, e.getMessage())) //
.build());
if (properties.getMessages().isEvaluationFailed()) {
appEventPublisher.publishEvent(RecommenderTaskNotificationEvent
.builder(this, project, sessionOwner.getUsername()) //
.withMessage(LogMessage.error(this, e.getMessage())) //
.build());
}
}

private void logNoRecommenders(String sessionOwnerName, AnnotationLayer layer)
Original file line number Diff line number Diff line change
@@ -31,6 +31,7 @@
export let wsEndpointUrl: string; // should this be full ws://... url
export let topicChannel: string;
export let feedbackPanelId = null;
export let csrfToken: string;
let socket: WebSocket = null;
let stompClient: Client = null;
@@ -48,6 +49,9 @@
wsEndpoint.protocol = protocol;
stompClient = Stomp.over(() => (socket = new WebSocket(wsEndpoint.toString())));
stompClient.connectHeaders = {
'X-CSRF-TOKEN': csrfToken
}
stompClient.onConnect = () => onConnect();
stompClient.onStompError = handleBrokerError;
stompClient.activate();
@@ -74,7 +78,7 @@
}
var msgBody = JSON.parse(msg.body) as RRecommenderLogMessage;
console.log(msgBody)
// console.log(msgBody)
msgBody.removeClasses?.forEach(c => document.body.classList.remove(c))
msgBody.addClasses?.forEach(c => document.body.classList.add(c))
switch (msgBody.level) {
Original file line number Diff line number Diff line change
@@ -27,7 +27,6 @@
import static de.tudarmstadt.ukp.inception.support.uima.WebAnnoCasUtil.getSentenceNumber;
import static de.tudarmstadt.ukp.inception.support.uima.WebAnnoCasUtil.isSame;
import static java.util.Arrays.asList;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.uima.fit.util.CasUtil.selectAt;
import static wicket.contrib.input.events.EventType.click;
import static wicket.contrib.input.events.key.KeyType.Delete;
@@ -804,8 +803,6 @@ private void deleteAnnotation(CAS aCas, AnnotatorState state, VID aVid, Annotati

relationAdapter.delete(state.getDocument(), state.getUser().getUsername(), aCas,
VID.of(rel.getRelation()));

info(generateMessage(relationAdapter.getLayer(), null, true));
}
}

@@ -826,8 +823,6 @@ private void deleteAnnotation(CAS aCas, AnnotatorState state, VID aVid, Annotati

// Actually delete annotation
adapter.delete(state.getDocument(), state.getUser().getUsername(), aCas, aVid);

info(generateMessage(adapter.getLayer(), null, true));
}

private void cleanUpLinkFeatures(CAS aCas, FeatureStructure fs, SpanAdapter adapter,
@@ -1075,8 +1070,11 @@ protected void onConfigure()
setVisible(getModelObject() != null && getModelObject().getDocument() != null);

// Set read only if annotation is finished or the user is viewing other's work
var selectedLayerIsReadOnly = getModel().map(AnnotatorState::getSelectedAnnotationLayer)
.map(AnnotationLayer::isReadonly).orElse(true).getObject();
var selectedLayerIsReadOnly = getModel() //
.map(AnnotatorState::getSelectedAnnotationLayer) //
.map(AnnotationLayer::isReadonly) //
.orElse(true) //
.getObject();
setEnabled(editorPage.isEditable() && !selectedLayerIsReadOnly);
}

@@ -1121,8 +1119,8 @@ public void onBulkAnnotationEvent(BulkAnnotationEvent aEvent)

try {
var selection = getModelObject().getSelection();
int id = selection.getAnnotation().getId();
boolean annotationStillExists = getEditorCas().select(Annotation.class) //
var id = selection.getAnnotation().getId();
var annotationStillExists = getEditorCas().select(Annotation.class) //
.at(selection.getBegin(), selection.getEnd()) //
.anyMatch(ann -> ann._id() == id);

@@ -1215,21 +1213,21 @@ private void populateTagsetBasedOnRules(CAS aCas, FeatureState aModel)
private static List<ReorderableTag> compareSortAndAdd(List<PossibleValue> aPossibleValues,
List<ReorderableTag> aTags, RulesIndicator aRulesIndicator)
{
List<ReorderableTag> returnList = new ArrayList<>();
var returnList = new ArrayList<ReorderableTag>();

// if no possible values, means didn't satisfy conditions
if (aPossibleValues.isEmpty()) {
aRulesIndicator.didntMatchAnyRule();
return aTags;
}

Map<String, ReorderableTag> tagIndex = new LinkedHashMap<>();
var tagIndex = new LinkedHashMap<String, ReorderableTag>();
for (ReorderableTag tag : aTags) {
tagIndex.put(tag.getName(), tag);
}

for (PossibleValue value : aPossibleValues) {
ReorderableTag tag = tagIndex.get(value.getValue());
for (var value : aPossibleValues) {
var tag = tagIndex.get(value.getValue());
if (tag == null) {
continue;
}
@@ -1323,18 +1321,6 @@ protected static void handleException(Component aComponent, AjaxRequestTarget aT
}
}

private static String generateMessage(AnnotationLayer aLayer, String aLabel, boolean aDeleted)
{
var action = aDeleted ? "deleted" : "created/updated";

var msg = "The [" + aLayer.getUiName() + "] annotation has been " + action + ".";
if (isNotBlank(aLabel)) {
msg += " Label: [" + aLabel + "]";
}

return msg;
}

private LambdaAjaxLink createClearButton()
{
var link = new LambdaAjaxLink("clear", this::actionClear);
@@ -1346,7 +1332,7 @@ private LambdaAjaxLink createClearButton()

private Component createReverseButton()
{
LambdaAjaxLink link = new LambdaAjaxLink("reverse", this::actionReverse);
var link = new LambdaAjaxLink("reverse", this::actionReverse);
link.setOutputMarkupPlaceholderTag(true);
link.add(LambdaBehavior.onConfigure(_this -> {
AnnotatorState state = getModelObject();

0 comments on commit b80b970

Please sign in to comment.