aProject,
overviewList.add(new LambdaAjaxFormComponentUpdatingBehavior("change", this::onChange));
add(overviewList);
- LambdaAjaxLink lambdaAjaxLink = new LambdaAjaxLink(MID_CREATE_BUTTON, this::actionCreate);
+ var lambdaAjaxLink = new LambdaAjaxLink(MID_CREATE_BUTTON, this::actionCreate);
lambdaAjaxLink.setVisible(showCreateButton);
add(lambdaAjaxLink);
- RecommenderGeneralSettings settings = preferencesService.loadDefaultTraitsForProject(
+ var settings = preferencesService.loadDefaultTraitsForProject(
KEY_RECOMMENDER_GENERAL_SETTINGS, projectModel.getObject());
var form = new Form<>("form", CompoundPropertyModel.of(settings));
form.setOutputMarkupId(true);
- form.add(new CheckBox("waitForRecommendersOnOpenDocument").setOutputMarkupId(true));
+ form.add(new CheckBox("waitForRecommendersOnOpenDocument") //
+ .setOutputMarkupId(true));
+ form.add(new CheckBox("showRecommendationsWhenViewingOtherUser") //
+ .setOutputMarkupId(true));
+ form.add(new CheckBox("showRecommendationsWhenViewingCurationUser") //
+ .setOutputMarkupId(true) //
+ .setVisible(recommendationService.isCurationSidebarEnabled()));
form.add(new LambdaAjaxButton<>("save", this::actionSaveSettings));
add(form);
}
diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/project/RecommenderListPanel.properties b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/project/RecommenderListPanel.properties
index a44aaad2b1b..65d6c2160a5 100644
--- a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/project/RecommenderListPanel.properties
+++ b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/project/RecommenderListPanel.properties
@@ -15,4 +15,7 @@
# limitations under the License.
recommenders=Recommenders
-waitForRecommendersOnOpenDocument=Wait for suggestions from non-trainable recommenders when opening document
\ No newline at end of file
+settings=Settings
+waitForRecommendersOnOpenDocument=Wait for suggestions from non-trainable recommenders when opening document
+showRecommendationsWhenViewingOtherUser=Show suggestions when viewing annotations from another user
+showRecommendationsWhenViewingCurationUser=Show suggestions when viewing curation annotations
diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/render/RecommendationRenderer.java b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/render/RecommendationRenderer.java
index 662cd589a2c..9e19a9785b2 100644
--- a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/render/RecommendationRenderer.java
+++ b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/render/RecommendationRenderer.java
@@ -18,27 +18,29 @@
package de.tudarmstadt.ukp.inception.recommendation.render;
import static de.tudarmstadt.ukp.clarin.webanno.model.Mode.ANNOTATION;
+import static de.tudarmstadt.ukp.inception.recommendation.api.RecommendationService.KEY_RECOMMENDER_GENERAL_SETTINGS;
import static de.tudarmstadt.ukp.inception.recommendation.api.model.SuggestionDocumentGroup.groupByType;
import static java.util.function.Function.identity;
import static java.util.stream.Collectors.groupingBy;
import java.util.HashMap;
+import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import org.springframework.core.annotation.Order;
+import de.tudarmstadt.ukp.clarin.webanno.security.UserDao;
+import de.tudarmstadt.ukp.inception.preferences.PreferencesService;
import de.tudarmstadt.ukp.inception.recommendation.api.RecommendationService;
import de.tudarmstadt.ukp.inception.recommendation.api.SuggestionSupport;
import de.tudarmstadt.ukp.inception.recommendation.api.SuggestionSupportRegistry;
import de.tudarmstadt.ukp.inception.recommendation.api.model.AnnotationSuggestion;
import de.tudarmstadt.ukp.inception.recommendation.api.model.Recommender;
import de.tudarmstadt.ukp.inception.recommendation.config.RecommenderServiceAutoConfiguration;
-import de.tudarmstadt.ukp.inception.rendering.editorstate.AnnotatorState;
import de.tudarmstadt.ukp.inception.rendering.pipeline.RenderStep;
import de.tudarmstadt.ukp.inception.rendering.request.RenderRequest;
import de.tudarmstadt.ukp.inception.rendering.vmodel.VDocument;
-import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService;
/**
*
@@ -52,17 +54,19 @@ public class RecommendationRenderer
{
public static final String ID = "RecommendationRenderer";
- private final AnnotationSchemaService annotationService;
private final RecommendationService recommendationService;
private final SuggestionSupportRegistry suggestionSupportRegistry;
+ private final PreferencesService preferencesService;
+ private final UserDao userService;
- public RecommendationRenderer(AnnotationSchemaService aAnnotationService,
- RecommendationService aRecommendationService,
- SuggestionSupportRegistry aSuggestionSupportRegistry)
+ public RecommendationRenderer(RecommendationService aRecommendationService,
+ SuggestionSupportRegistry aSuggestionSupportRegistry,
+ PreferencesService aPreferencesService, UserDao aUserService)
{
- annotationService = aAnnotationService;
recommendationService = aRecommendationService;
suggestionSupportRegistry = aSuggestionSupportRegistry;
+ preferencesService = aPreferencesService;
+ userService = aUserService;
}
@Override
@@ -74,7 +78,7 @@ public String getId()
@Override
public boolean accepts(RenderRequest aRequest)
{
- AnnotatorState state = aRequest.getState();
+ var state = aRequest.getState();
if (aRequest.getCas() == null) {
return false;
@@ -85,6 +89,21 @@ public boolean accepts(RenderRequest aRequest)
return false;
}
+ var prefs = preferencesService.loadDefaultTraitsForProject(KEY_RECOMMENDER_GENERAL_SETTINGS,
+ aRequest.getProject());
+
+ // Do not show predictions when viewing annotations of another user
+ if (!prefs.isShowRecommendationsWhenViewingOtherUser()
+ && !Objects.equals(aRequest.getAnnotationUser(), aRequest.getSessionOwner())) {
+ return false;
+ }
+
+ // Do not show predictions when viewing annotations of curation user
+ if (!prefs.isShowRecommendationsWhenViewingCurationUser()
+ && Objects.equals(aRequest.getAnnotationUser(), userService.getCurationUser())) {
+ return false;
+ }
+
return true;
}
diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/service/RecommendationServiceImpl.java b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/service/RecommendationServiceImpl.java
index 2328f84083e..5f5efd803ab 100644
--- a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/service/RecommendationServiceImpl.java
+++ b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/service/RecommendationServiceImpl.java
@@ -60,6 +60,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.event.EventListener;
import org.springframework.core.Ordered;
@@ -112,6 +113,8 @@
import de.tudarmstadt.ukp.inception.recommendation.config.RecommenderServiceAutoConfiguration;
import de.tudarmstadt.ukp.inception.recommendation.event.RecommenderDeletedEvent;
import de.tudarmstadt.ukp.inception.recommendation.event.RecommenderUpdatedEvent;
+import de.tudarmstadt.ukp.inception.recommendation.event.RecommendersResumedEvent;
+import de.tudarmstadt.ukp.inception.recommendation.event.RecommendersSuspendedEvent;
import de.tudarmstadt.ukp.inception.recommendation.model.DirtySpot;
import de.tudarmstadt.ukp.inception.recommendation.tasks.NonTrainableRecommenderActivationTask;
import de.tudarmstadt.ukp.inception.recommendation.tasks.PredictionTask;
@@ -175,6 +178,9 @@ public class RecommendationServiceImpl
{
};
+ @Value("${curation.sidebar.enabled:false}")
+ private boolean curationSidebarEnabled;
+
@Autowired
public RecommendationServiceImpl(PreferencesService aPreferencesService,
SessionRegistry aSessionRegistry, UserDao aUserRepository,
@@ -211,62 +217,71 @@ public RecommendationServiceImpl(PreferencesService aPreferencesService,
aLayerRecommendtionSupportRegistry);
}
+ @Deprecated
@Override
- public Predictions getPredictions(User aUser, Project aProject)
+ public boolean isCurationSidebarEnabled()
{
- var state = getState(aUser.getUsername(), aProject);
+ return curationSidebarEnabled;
+ }
+
+ @Override
+ public Predictions getPredictions(User aSessionOwner, Project aProject)
+ {
+ var state = getState(aSessionOwner.getUsername(), aProject);
return state.getActivePredictions();
}
@Override
- public Predictions getIncomingPredictions(User aUser, Project aProject)
+ public Predictions getIncomingPredictions(User aSessionOwner, Project aProject)
{
- var state = getState(aUser.getUsername(), aProject);
+ var state = getState(aSessionOwner.getUsername(), aProject);
return state.getIncomingPredictions();
}
@Override
- public void putIncomingPredictions(User aUser, Project aProject, Predictions aPredictions)
+ public void putIncomingPredictions(User aSessionOwner, Project aProject,
+ Predictions aPredictions)
{
- var state = getState(aUser.getUsername(), aProject);
+ var state = getState(aSessionOwner.getUsername(), aProject);
synchronized (state) {
state.setIncomingPredictions(aPredictions);
}
}
@Override
- public boolean hasActiveRecommenders(String aUser, Project aProject)
+ public boolean hasActiveRecommenders(String aSessionOwner, Project aProject)
{
- var state = getState(aUser, aProject);
+ var state = getState(aSessionOwner, aProject);
synchronized (state) {
return !state.getActiveRecommenders().isEmpty();
}
}
@Override
- public void setEvaluatedRecommenders(User aUser, AnnotationLayer aLayer,
+ public void setEvaluatedRecommenders(User aSessionOwner, AnnotationLayer aLayer,
List aRecommenders)
{
- var state = getState(aUser.getUsername(), aLayer.getProject());
+ var state = getState(aSessionOwner.getUsername(), aLayer.getProject());
synchronized (state) {
state.setEvaluatedRecommenders(aLayer, aRecommenders);
}
}
@Override
- public List getEvaluatedRecommenders(User aUser, AnnotationLayer aLayer)
+ public List getEvaluatedRecommenders(User aSessionOwner,
+ AnnotationLayer aLayer)
{
- var state = getState(aUser.getUsername(), aLayer.getProject());
+ var state = getState(aSessionOwner.getUsername(), aLayer.getProject());
synchronized (state) {
return new ArrayList<>(state.getEvaluatedRecommenders().get(aLayer));
}
}
@Override
- public Optional getEvaluatedRecommender(User aUser,
+ public Optional getEvaluatedRecommender(User aSessionOwner,
Recommender aRecommender)
{
- var state = getState(aUser.getUsername(), aRecommender.getProject());
+ var state = getState(aSessionOwner.getUsername(), aRecommender.getProject());
synchronized (state) {
return state.getEvaluatedRecommenders().get(aRecommender.getLayer()).stream()
.filter(r -> r.getRecommender().equals(aRecommender)).findAny();
@@ -274,18 +289,19 @@ public Optional getEvaluatedRecommender(User aUser,
}
@Override
- public List getActiveRecommenders(User aUser, AnnotationLayer aLayer)
+ public List getActiveRecommenders(User aSessionOwner,
+ AnnotationLayer aLayer)
{
- var state = getState(aUser.getUsername(), aLayer.getProject());
+ var state = getState(aSessionOwner.getUsername(), aLayer.getProject());
synchronized (state) {
return new ArrayList<>(state.getActiveRecommenders().get(aLayer));
}
}
@Override
- public List getActiveRecommenders(User aUser, Project aProject)
+ public List getActiveRecommenders(User aSessionOwner, Project aProject)
{
- var state = getState(aUser.getUsername(), aProject);
+ var state = getState(aSessionOwner.getUsername(), aProject);
synchronized (state) {
return new ArrayList<>(state.getActiveRecommenders().values());
}
@@ -373,14 +389,14 @@ public Recommender getRecommender(AnnotationSuggestion aSuggestion)
@Override
@Transactional
- public boolean existsRecommender(Project aProject, String aName)
+ public boolean existsRecommender(Project aProject, String aRecommender)
{
var cb = entityManager.getCriteriaBuilder();
var query = cb.createQuery(Long.class);
var root = query.from(Recommender.class);
query.select(cb.count(root)).where(cb.and( //
- cb.equal(root.get(Recommender_.name), aName), //
+ cb.equal(root.get(Recommender_.name), aRecommender), //
cb.equal(root.get(Recommender_.project), aProject)));
long count = entityManager.createQuery(query).getSingleResult();
@@ -390,7 +406,7 @@ public boolean existsRecommender(Project aProject, String aName)
@Override
@Transactional
- public Optional getRecommender(Project aProject, String aName)
+ public Optional getRecommender(Project aProject, String aRecommender)
{
String query = String.join("\n", //
"FROM Recommender ", //
@@ -398,7 +414,7 @@ public Optional getRecommender(Project aProject, String aName)
"AND project = :project");
return entityManager.createQuery(query, Recommender.class) //
- .setParameter("name", aName) //
+ .setParameter("name", aRecommender) //
.setParameter("project", aProject) //
.getResultStream() //
.findFirst();
@@ -490,7 +506,7 @@ public void onDocumentOpened(DocumentOpenedEvent aEvent)
else {
// If the session owner has switched the data they are looking at, we need to
// clear and rebuild the predictions.
- clearState(sessionOwnerName);
+ resetState(sessionOwnerName);
}
}
@@ -543,6 +559,10 @@ public void onDocumentOpened(DocumentOpenedEvent aEvent)
private boolean nonTrainableRecommenderRunSync(SourceDocument doc, Predictions predictions,
User aSessionOwner, String trigger, String aDataOwner)
{
+ if (isSuspended(aSessionOwner.getUsername(), doc.getProject())) {
+ return false;
+ }
+
if (predictions != null && predictions.hasRunPredictionOnDocument(doc)) {
LOG.trace("Not running sync prediction for non-trainable recommenders as we already "
+ "have predictions");
@@ -750,7 +770,7 @@ public void onAfterCasWritten(AfterCasWrittenEvent aEvent)
@EventListener
public void onRecommenderUpdated(RecommenderUpdatedEvent aEvent)
{
- clearState(aEvent.getRecommender().getProject());
+ resetState(aEvent.getRecommender().getProject());
}
@EventListener
@@ -765,13 +785,13 @@ public void onRecommenderDelete(RecommenderDeletedEvent aEvent)
@EventListener
public void onDocumentCreated(AfterDocumentCreatedEvent aEvent)
{
- clearState(aEvent.getDocument().getProject());
+ resetState(aEvent.getDocument().getProject());
}
@EventListener
public void onDocumentRemoval(BeforeDocumentRemovedEvent aEvent)
{
- clearState(aEvent.getDocument().getProject());
+ resetState(aEvent.getDocument().getProject());
}
@EventListener
@@ -789,21 +809,25 @@ public void onAfterProjectRemoved(AfterProjectRemovedEvent aEvent)
@EventListener
public void onLayerConfigurationChangedEvent(LayerConfigurationChangedEvent aEvent)
{
- clearState(aEvent.getProject());
+ resetState(aEvent.getProject());
}
@Override
- public void triggerPrediction(String aUsername, String aEventName, SourceDocument aDocument,
+ public void triggerPrediction(String aSessionOwner, String aEventName, SourceDocument aDocument,
String aDataOwner)
{
- var user = userRepository.get(aUsername);
+ if (isSuspended(aSessionOwner, aDocument.getProject())) {
+ return;
+ }
+
+ var sessionOwner = userRepository.get(aSessionOwner);
- if (user == null) {
+ if (sessionOwner == null) {
return;
}
schedulingService.enqueue(PredictionTask.builder() //
- .withSessionOwner(user) //
+ .withSessionOwner(sessionOwner) //
.withTrigger(aEventName) //
.withCurrentDocument(aDocument) //
.withDataOwner(aDataOwner) //
@@ -814,6 +838,10 @@ public void triggerPrediction(String aUsername, String aEventName, SourceDocumen
public void triggerTrainingAndPrediction(String aSessionOwner, Project aProject,
String aEventName, SourceDocument aCurrentDocument, String aDataOwner)
{
+ if (isSuspended(aSessionOwner, aProject)) {
+ return;
+ }
+
triggerTraining(aSessionOwner, aProject, aEventName, aCurrentDocument, aDataOwner, false,
null);
}
@@ -822,6 +850,10 @@ public void triggerTrainingAndPrediction(String aSessionOwner, Project aProject,
public void triggerSelectionTrainingAndPrediction(String aSessionOwner, Project aProject,
String aEventName, SourceDocument aCurrentDocument, String aDataOwner)
{
+ if (isSuspended(aSessionOwner, aProject)) {
+ return;
+ }
+
triggerTraining(aSessionOwner, aProject, aEventName, aCurrentDocument, aDataOwner, true,
null);
}
@@ -830,6 +862,10 @@ private void triggerTraining(String aSessionOwner, Project aProject, String aEve
SourceDocument aCurrentDocument, String aDataOwner, boolean aForceSelection,
Set aDirties)
{
+ if (isSuspended(aSessionOwner, aProject)) {
+ return;
+ }
+
var user = userRepository.get(aSessionOwner);
// do not trigger training during when viewing others' work
if (user == null || !user.equals(userRepository.getCurrentUser())) {
@@ -885,10 +921,10 @@ private void triggerTraining(String aSessionOwner, Project aProject, String aEve
}
@Override
- public List getLog(String aUser, Project aProject)
+ public List getLog(String aSessionOwner, Project aProject)
{
- var activePredictions = getState(aUser, aProject).getActivePredictions();
- var incomingPredictions = getState(aUser, aProject).getIncomingPredictions();
+ var activePredictions = getState(aSessionOwner, aProject).getActivePredictions();
+ var incomingPredictions = getState(aSessionOwner, aProject).getIncomingPredictions();
var messageSets = new ArrayList();
@@ -914,10 +950,37 @@ public boolean isPredictForAllDocuments(String aUser, Project aProject)
}
@Override
- public void setPredictForAllDocuments(String aUser, Project aProject,
+ public void setPredictForAllDocuments(String aSessionOwner, Project aProject,
boolean aPredictForAllDocuments)
{
- getState(aUser, aProject).setPredictForAllDocuments(aPredictForAllDocuments);
+ getState(aSessionOwner, aProject).setPredictForAllDocuments(aPredictForAllDocuments);
+ }
+
+ @Override
+ public boolean isSuspended(String aSessionOwner, Project aProject)
+ {
+ return getState(aSessionOwner, aProject).isSuspended();
+ }
+
+ @Override
+ public void setSuspended(String aSessionOwner, Project aProject, boolean aState)
+ {
+ var suspended = isSuspended(aSessionOwner, aProject);
+ if (suspended == aState) {
+ return;
+ }
+
+ getState(aSessionOwner, aProject).setSuspended(aState);
+ if (aState) {
+ applicationEventPublisher
+ .publishEvent(new RecommendersSuspendedEvent(this, aProject, aSessionOwner));
+ ;
+ }
+ else {
+ applicationEventPublisher
+ .publishEvent(new RecommendersResumedEvent(this, aProject, aSessionOwner));
+ ;
+ }
}
@EventListener
@@ -950,14 +1013,14 @@ public void afterDocumentReset(AfterDocumentResetEvent aEvent)
{
var currentDocument = aEvent.getDocument().getDocument();
var currentUser = aEvent.getDocument().getUser();
- clearState(currentUser);
+ resetState(currentUser);
deleteLearningRecords(currentDocument, currentUser);
}
@Override
- public Preferences getPreferences(User aUser, Project aProject)
+ public Preferences getPreferences(User aSessionOwner, Project aProject)
{
- var state = getState(aUser.getUsername(), aProject);
+ var state = getState(aSessionOwner.getUsername(), aProject);
return state.getPreferences();
}
@@ -974,22 +1037,47 @@ public Optional> getRecommenderFactory(Recommende
return Optional.ofNullable(recommenderFactoryRegistry.getFactory(aRecommender.getTool()));
}
- private RecommendationState getState(String aUsername, Project aProject)
+ private RecommendationState getState(String aSessionOwner, Project aProject)
{
synchronized (states) {
- return states.computeIfAbsent(new RecommendationStateKey(aUsername, aProject),
+ return states.computeIfAbsent(new RecommendationStateKey(aSessionOwner, aProject),
(v) -> new RecommendationState());
}
}
@Override
- public void clearState(String aUsername)
+ public void resetState(String aSessionOwner)
+ {
+ Validate.notNull(aSessionOwner, "Username must be specified");
+
+ synchronized (states) {
+ states.entrySet().stream() //
+ .filter(e -> aSessionOwner.equals(e.getKey().getUser()))
+ .forEach(e -> e.getValue().reset());
+ trainingTaskCounter.keySet().removeIf(key -> aSessionOwner.equals(key.getUser()));
+ }
+ }
+
+ private void clearState(String aSessionOwner)
+ {
+ Validate.notNull(aSessionOwner, "Username must be specified");
+
+ synchronized (states) {
+ states.keySet().removeIf(key -> aSessionOwner.equals(key.getUser()));
+ trainingTaskCounter.keySet().removeIf(key -> aSessionOwner.equals(key.getUser()));
+ }
+ }
+
+ private void resetState(Project aProject)
{
- Validate.notNull(aUsername, "Username must be specified");
+ Validate.notNull(aProject, "Project must be specified");
synchronized (states) {
- states.keySet().removeIf(key -> aUsername.equals(key.getUser()));
- trainingTaskCounter.keySet().removeIf(key -> aUsername.equals(key.getUser()));
+ states.entrySet().stream() //
+ .filter(e -> Objects.equals(aProject.getId(), e.getKey().getProjectId()))
+ .forEach(e -> e.getValue().reset());
+ trainingTaskCounter.keySet()
+ .removeIf(key -> Objects.equals(aProject.getId(), key.getProjectId()));
}
}
@@ -1035,9 +1123,10 @@ public Optional getContext(String aSessionOwner, Recommender
}
@Override
- public void putContext(User aUser, Recommender aRecommender, RecommenderContext aContext)
+ public void putContext(User aSessionOwner, Recommender aRecommender,
+ RecommenderContext aContext)
{
- var state = getState(aUser.getUsername(), aRecommender.getProject());
+ var state = getState(aSessionOwner.getUsername(), aRecommender.getProject());
synchronized (state) {
state.putContext(aRecommender, aContext);
}
@@ -1202,12 +1291,14 @@ public int hashCode()
*/
private class RecommendationState
{
+ private boolean suspended;
private Preferences preferences;
+ private boolean predictForAllDocuments;
+
private MultiValuedMap evaluatedRecommenders;
private Map contexts;
private Predictions activePredictions;
private Predictions incomingPredictions;
- private boolean predictForAllDocuments;
private Map> learningRecords;
private int predictionsSinceLastEvaluation;
private int predictionsUntilNextEvaluation;
@@ -1220,6 +1311,17 @@ public RecommendationState()
learningRecords = new ConcurrentHashMap<>();
}
+ public void reset()
+ {
+ evaluatedRecommenders = new HashSetValuedHashMap<>();
+ contexts = new ConcurrentHashMap<>();
+ activePredictions = null;
+ incomingPredictions = null;
+ learningRecords = new ConcurrentHashMap<>();
+ predictionsSinceLastEvaluation = 0;
+ predictionsUntilNextEvaluation = 0;
+ }
+
public Preferences getPreferences()
{
return preferences;
@@ -1390,6 +1492,16 @@ public void setPredictForAllDocuments(boolean aPredictForAllDocuments)
predictForAllDocuments = aPredictForAllDocuments;
}
+ public boolean isSuspended()
+ {
+ return suspended;
+ }
+
+ public void setSuspended(boolean aSuspended)
+ {
+ suspended = aSuspended;
+ }
+
public void logRecord(LearningRecord aRecord)
{
var records = learningRecords.computeIfAbsent(aRecord.getLayer(),
diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/processor/BulkProcessingPage.html b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/sidebar/EvaluationProgressPanel.html
similarity index 72%
rename from inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/processor/BulkProcessingPage.html
rename to inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/sidebar/EvaluationProgressPanel.html
index b4350a9314f..e1f6c9c1552 100644
--- a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/processor/BulkProcessingPage.html
+++ b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/sidebar/EvaluationProgressPanel.html
@@ -17,19 +17,7 @@
limitations under the License.
-->
-
-
-
-
-
-
-
-
-
-
+
+
+
diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/sidebar/EvaluationProgressPanel.java b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/sidebar/EvaluationProgressPanel.java
new file mode 100644
index 00000000000..103996bc33c
--- /dev/null
+++ b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/sidebar/EvaluationProgressPanel.java
@@ -0,0 +1,64 @@
+/*
+ * 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.sidebar;
+
+import static org.apache.commons.lang3.StringUtils.repeat;
+
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.panel.GenericPanel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.LoadableDetachableModel;
+import org.apache.wicket.spring.injection.annot.SpringBean;
+import org.wicketstuff.event.annotation.OnEvent;
+
+import de.tudarmstadt.ukp.clarin.webanno.model.Project;
+import de.tudarmstadt.ukp.clarin.webanno.security.UserDao;
+import de.tudarmstadt.ukp.inception.recommendation.api.RecommendationService;
+import de.tudarmstadt.ukp.inception.recommendation.api.event.PredictionsSwitchedEvent;
+import de.tudarmstadt.ukp.inception.recommendation.api.model.Progress;
+
+public class EvaluationProgressPanel
+ extends GenericPanel
+{
+ private static final long serialVersionUID = -8498000053224985486L;
+
+ private @SpringBean UserDao userService;
+ private @SpringBean RecommendationService recommendationService;
+
+ public EvaluationProgressPanel(String aId, IModel aModel)
+ {
+ super(aId, aModel);
+
+ setOutputMarkupId(true);
+
+ var sessionOwner = userService.getCurrentUser();
+
+ add(new Label("progress", LoadableDetachableModel.of(() -> {
+ Progress p = recommendationService.getProgressTowardsNextEvaluation(sessionOwner,
+ getModelObject());
+ return repeat(" ", p.getDone())
+ + repeat(" ", p.getTodo());
+ })).setEscapeModelStrings(false)); // SAFE - RENDERING ONLY SPECIFIC ICONS
+ }
+
+ @OnEvent
+ public void onPredictionsSwitched(PredictionsSwitchedEvent aEvent)
+ {
+ aEvent.getRequestTarget().ifPresent(target -> target.add(this));
+ }
+}
diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/sidebar/RecommendationSidebar.html b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/sidebar/RecommendationSidebar.html
index 417406d31cc..383fede09aa 100644
--- a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/sidebar/RecommendationSidebar.html
+++ b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/sidebar/RecommendationSidebar.html
@@ -42,11 +42,9 @@
-
+
+
+
+
+
-
+
+
+
+
+ Update suggestions
+
+
+
+
+