/*
* Copyright 2012
* Ubiquitous Knowledge Processing (UKP) Lab and FG Language Technology
* Technische Universit?t Darmstadt
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* 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.clarin.webanno.ui.monitoring.page;
import static de.tudarmstadt.ukp.clarin.webanno.model.AnnotationDocumentStateTransition.ANNOTATION_FINISHED_TO_ANNOTATION_IN_PROGRESS;
import static de.tudarmstadt.ukp.clarin.webanno.model.AnnotationDocumentStateTransition.ANNOTATION_IN_PROGRESS_TO_ANNOTATION_FINISHED;
import static de.tudarmstadt.ukp.clarin.webanno.model.AnnotationDocumentStateTransition.IGNORE_TO_NEW;
import static de.tudarmstadt.ukp.clarin.webanno.model.AnnotationDocumentStateTransition.NEW_TO_ANNOTATION_IN_PROGRESS;
import static de.tudarmstadt.ukp.clarin.webanno.model.AnnotationDocumentStateTransition.NEW_TO_IGNORE;
import static de.tudarmstadt.ukp.clarin.webanno.model.SourceDocumentState.ANNOTATION_FINISHED;
import static de.tudarmstadt.ukp.clarin.webanno.model.SourceDocumentState.ANNOTATION_IN_PROGRESS;
import static de.tudarmstadt.ukp.clarin.webanno.model.SourceDocumentState.CURATION_FINISHED;
import static de.tudarmstadt.ukp.clarin.webanno.model.SourceDocumentState.CURATION_IN_PROGRESS;
import static de.tudarmstadt.ukp.clarin.webanno.model.SourceDocumentState.NEW;
import static de.tudarmstadt.ukp.clarin.webanno.model.SourceDocumentStateTransition.ANNOTATION_IN_PROGRESS_TO_CURATION_IN_PROGRESS;
import static de.tudarmstadt.ukp.clarin.webanno.model.SourceDocumentStateTransition.CURATION_FINISHED_TO_CURATION_IN_PROGRESS;
import static de.tudarmstadt.ukp.clarin.webanno.model.SourceDocumentStateTransition.CURATION_IN_PROGRESS_TO_CURATION_FINISHED;
import static java.util.Arrays.asList;
import java.awt.Color;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.uima.UIMAException;
import org.apache.uima.jcas.JCas;
import org.apache.wicket.AttributeModifier;
import org.apache.wicket.Component;
import org.apache.wicket.ajax.AjaxEventBehavior;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.form.OnChangeAjaxBehavior;
import org.apache.wicket.ajax.markup.html.form.AjaxButton;
import org.apache.wicket.behavior.AttributeAppender;
import org.apache.wicket.extensions.markup.html.repeater.data.grid.DataGridView;
import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator;
import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn;
import org.apache.wicket.extensions.markup.html.repeater.data.table.DefaultDataTable;
import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.CheckBox;
import org.apache.wicket.markup.html.form.ChoiceRenderer;
import org.apache.wicket.markup.html.form.DropDownChoice;
import org.apache.wicket.markup.html.form.EnumChoiceRenderer;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.ListChoice;
import org.apache.wicket.markup.html.image.Image;
import org.apache.wicket.markup.html.image.NonCachingImage;
import org.apache.wicket.markup.html.panel.EmptyPanel;
import org.apache.wicket.markup.html.panel.FeedbackPanel;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.markup.repeater.Item;
import org.apache.wicket.model.AbstractReadOnlyModel;
import org.apache.wicket.model.CompoundPropertyModel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.LoadableDetachableModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.request.cycle.RequestCycle;
import org.apache.wicket.request.resource.PackageResourceReference;
import org.apache.wicket.request.resource.ResourceReference;
import org.apache.wicket.spring.injection.annot.SpringBean;
import org.apache.wicket.util.resource.AbstractResourceStream;
import org.apache.wicket.util.resource.IResourceStream;
import org.apache.wicket.util.resource.ResourceStreamNotFoundException;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.NumberTickUnit;
import org.jfree.chart.axis.TickUnits;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.renderer.category.BarRenderer;
import org.jfree.chart.renderer.category.StandardBarPainter;
import org.jfree.data.category.DefaultCategoryDataset;
import org.jfree.ui.RectangleInsets;
import org.jfree.util.UnitType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.context.SecurityContextHolder;
import org.wicketstuff.annotation.mount.MountPath;
import de.tudarmstadt.ukp.clarin.webanno.api.AnnotationSchemaService;
import de.tudarmstadt.ukp.clarin.webanno.api.DocumentService;
import de.tudarmstadt.ukp.clarin.webanno.api.ProjectService;
import de.tudarmstadt.ukp.clarin.webanno.api.SecurityUtil;
import de.tudarmstadt.ukp.clarin.webanno.api.WebAnnoConst;
import de.tudarmstadt.ukp.clarin.webanno.automation.model.MiraTemplate;
import de.tudarmstadt.ukp.clarin.webanno.automation.service.AutomationService;
import de.tudarmstadt.ukp.clarin.webanno.curation.agreement.AgreementUtils;
import de.tudarmstadt.ukp.clarin.webanno.curation.agreement.AgreementUtils.AgreementReportExportFormat;
import de.tudarmstadt.ukp.clarin.webanno.curation.agreement.AgreementUtils.AgreementResult;
import de.tudarmstadt.ukp.clarin.webanno.curation.agreement.AgreementUtils.ConcreteAgreementMeasure;
import de.tudarmstadt.ukp.clarin.webanno.curation.agreement.PairwiseAnnotationResult;
import de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiff2;
import de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiff2.DiffAdapter;
import de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiff2.DiffResult;
import de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiff2.LinkCompareBehavior;
import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationDocument;
import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationDocumentState;
import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationDocumentStateTransition;
import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature;
import de.tudarmstadt.ukp.clarin.webanno.model.LinkMode;
import de.tudarmstadt.ukp.clarin.webanno.model.PermissionLevel;
import de.tudarmstadt.ukp.clarin.webanno.model.Project;
import de.tudarmstadt.ukp.clarin.webanno.model.SourceDocument;
import de.tudarmstadt.ukp.clarin.webanno.model.SourceDocumentState;
import de.tudarmstadt.ukp.clarin.webanno.model.SourceDocumentStateTransition;
import de.tudarmstadt.ukp.clarin.webanno.security.UserDao;
import de.tudarmstadt.ukp.clarin.webanno.security.model.User;
import de.tudarmstadt.ukp.clarin.webanno.support.AJAXDownload;
import de.tudarmstadt.ukp.clarin.webanno.support.EntityModel;
import de.tudarmstadt.ukp.clarin.webanno.ui.core.menu.MenuItem;
import de.tudarmstadt.ukp.clarin.webanno.ui.core.menu.MenuItemCondition;
import de.tudarmstadt.ukp.clarin.webanno.ui.core.page.ApplicationPageBase;
import de.tudarmstadt.ukp.clarin.webanno.ui.monitoring.support.ChartImageResource;
import de.tudarmstadt.ukp.clarin.webanno.ui.monitoring.support.EmbeddableImage;
import de.tudarmstadt.ukp.clarin.webanno.ui.monitoring.support.TableDataProvider;
import de.tudarmstadt.ukp.dkpro.core.api.metadata.type.DocumentMetaData;
import de.tudarmstadt.ukp.dkpro.core.api.segmentation.type.Token;
/**
* A Page To display different monitoring and statistics measurements tabularly and graphically.
*/
@MenuItem(icon="images/statistics.png", label="Monitoring", prio=300)
@MountPath("/monitoring.html")
public class MonitoringPage
extends ApplicationPageBase
{
private static final Logger LOG = LoggerFactory.getLogger(DocumentStatusColumnMetaData.class);
private static final long serialVersionUID = -2102136855109258306L;
private static final int CHART_WIDTH = 300;
/**
* The user column in the user-document status table
*/
public static final String USER = "user:";
/**
* The document column in the user-document status table
*/
public static final String DOCUMENT = "document:";
public static final String CURATION = "curation";
public static final String LAST_ACCESS = "last access:";
public static final String LAST_ACCESS_ROW = "last access";
private @SpringBean AnnotationSchemaService annotationService;
private @SpringBean AutomationService automationService;
private @SpringBean DocumentService documentService;
private @SpringBean ProjectService projectService;
private @SpringBean UserDao userRepository;
private final ProjectSelectionForm projectSelectionForm;
private final MonitoringDetailForm monitoringDetailForm;
private final Image annotatorsProgressImage;
private final Image annotatorsProgressPercentageImage;
private final Image overallProjectProgressImage;
private TrainingResultForm trainingResultForm;
private Label overview;
private Panel annotationDocumentStatusTable;
// private DefaultDataTable<?,?> annotationDocumentStatusTable;
private final Label projectName;
private final AgreementForm agreementForm;
private String result;
private static final ResourceReference ICON_FINISHED = new PackageResourceReference(
MonitoringPage.class, "accept.png");
private static final ResourceReference ICON_IGNORE = new PackageResourceReference(
MonitoringPage.class, "lock.png");
private static final ResourceReference ICON_INPROGRESS = new PackageResourceReference(
MonitoringPage.class, "resultset_next.png");
private static final ResourceReference ICON_NEW = new PackageResourceReference(
MonitoringPage.class, "new.png");
private static final Map<Object, ResourceReference> ICONS;
static {
Map<Object, ResourceReference> icons = new HashMap<>();
icons.put(ANNOTATION_FINISHED, ICON_FINISHED);
icons.put(CURATION_FINISHED, ICON_FINISHED);
icons.put(CURATION_IN_PROGRESS, ICON_INPROGRESS);
// We only show these icons in the curation column and if the annotation is still in
// progress, then this counts as the curation not having stated yet (NEW)
icons.put(ANNOTATION_IN_PROGRESS, ICON_NEW);
icons.put(NEW, ICON_NEW);
icons.put(AnnotationDocumentState.FINISHED, ICON_FINISHED);
icons.put(AnnotationDocumentState.IGNORE, ICON_IGNORE);
icons.put(AnnotationDocumentState.IN_PROGRESS, ICON_INPROGRESS);
icons.put(AnnotationDocumentState.NEW, ICON_NEW);
ICONS = Collections.unmodifiableMap(icons);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public MonitoringPage()
throws UIMAException, IOException, ClassNotFoundException
{
// if(repository.listProjects().isEmpty()){
// error("No project exist in your instance of WebAnno. Please create/import project using Projects page.");
// return;
// }
projectSelectionForm = new ProjectSelectionForm("projectSelectionForm");
monitoringDetailForm = new MonitoringDetailForm("monitoringDetailForm");
add(agreementForm = new AgreementForm("agreementForm"));
trainingResultForm = new TrainingResultForm("trainingResultForm");
trainingResultForm.setVisible(false);
add(trainingResultForm);
annotatorsProgressImage = new NonCachingImage("annotator");
annotatorsProgressImage.setOutputMarkupPlaceholderTag(true);
annotatorsProgressImage.setVisible(false);
annotatorsProgressPercentageImage = new NonCachingImage("annotatorPercentage");
annotatorsProgressPercentageImage.setOutputMarkupPlaceholderTag(true);
annotatorsProgressPercentageImage.setVisible(false);
overallProjectProgressImage = new NonCachingImage("overallProjectProgressImage");
final Map<String, Integer> overallProjectProgress = getOverallProjectProgress();
overallProjectProgressImage.setImageResource(createProgressChart(overallProjectProgress,
100, true));
overallProjectProgressImage.setOutputMarkupPlaceholderTag(true);
overallProjectProgressImage.setVisible(true);
add(overallProjectProgressImage);
add(overview = new Label("overview", "overview of projects"));
add(projectSelectionForm);
projectName = new Label("projectName", "");
if (!projectService.listProjects().isEmpty()) {
Project project = projectService.listProjects().get(0);
List<List<String>> userAnnotationDocumentLists = new ArrayList<List<String>>();
List<SourceDocument> dc = documentService.listSourceDocuments(project);
List<SourceDocument> trainingDoc = new ArrayList<SourceDocument>();
for (SourceDocument sdc : dc) {
if (sdc.isTrainingDocument()) {
trainingDoc.add(sdc);
}
}
dc.removeAll(trainingDoc);
for (int j = 0; j < projectService.listProjectUsersWithPermissions(project).size(); j++) {
List<String> userAnnotationDocument = new ArrayList<String>();
userAnnotationDocument.add("");
for (int i = 0; i < dc.size(); i++) {
userAnnotationDocument.add("");
}
userAnnotationDocumentLists.add(userAnnotationDocument);
}
List<String> documentListAsColumnHeader = new ArrayList<String>();
documentListAsColumnHeader.add("Users");
for (SourceDocument d : dc) {
documentListAsColumnHeader.add(d.getName());
}
TableDataProvider prov = new TableDataProvider(documentListAsColumnHeader,
userAnnotationDocumentLists);
List<IColumn<?, ?>> cols = new ArrayList<IColumn<?, ?>>();
for (int i = 0; i < prov.getColumnCount(); i++) {
cols.add(new DocumentStatusColumnMetaData(prov, i, new Project()));
}
annotationDocumentStatusTable = new DefaultDataTable("rsTable", cols, prov, 2);
monitoringDetailForm.setVisible(false);
add(monitoringDetailForm.add(annotatorsProgressImage)
.add(annotatorsProgressPercentageImage).add(projectName)
.add(annotationDocumentStatusTable));
annotationDocumentStatusTable.setVisible(false);
}else{
annotationDocumentStatusTable = new EmptyPanel("rsTable");
monitoringDetailForm.setVisible(false);
add(monitoringDetailForm);
annotationDocumentStatusTable.setVisible(false);
annotatorsProgressImage.setVisible(false);
annotatorsProgressPercentageImage.setVisible(false);
info("No project exists in your instance of WebAnno. Please create/import project using Projects page.");
}
}
private class ProjectSelectionForm
extends Form<ProjectSelectionModel>
{
private static final long serialVersionUID = -1L;
public ProjectSelectionForm(String id)
{
super(id, new CompoundPropertyModel<ProjectSelectionModel>(new ProjectSelectionModel()));
add(new ListChoice<Project>("project")
{
private static final long serialVersionUID = 1L;
{
setChoices(new LoadableDetachableModel<List<Project>>()
{
private static final long serialVersionUID = 1L;
@Override
protected List<Project> load()
{
List<Project> allowedProject = new ArrayList<Project>();
String username = SecurityContextHolder.getContext()
.getAuthentication().getName();
User user = userRepository.get(username);
List<Project> allProjects = projectService.listProjects();
for (Project project : allProjects) {
if (SecurityUtil.isProjectAdmin(project, projectService, user)
|| SecurityUtil.isCurator(project, projectService, user)) {
allowedProject.add(project);
}
}
return allowedProject;
}
});
setChoiceRenderer(new ChoiceRenderer<Project>("name"));
setNullValid(false);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
protected void onSelectionChanged(Project aNewSelection)
{
List<SourceDocument> sourceDocuments = documentService
.listSourceDocuments(aNewSelection);
List<SourceDocument> trainingDoc = new ArrayList<SourceDocument>();
for (SourceDocument sdc : sourceDocuments) {
if (sdc.isTrainingDocument()) {
trainingDoc.add(sdc);
}
}
sourceDocuments.removeAll(trainingDoc);
if (aNewSelection == null) {
return;
}
monitoringDetailForm.setModelObject(aNewSelection);
monitoringDetailForm.setVisible(true);
updateTrainingResultForm(aNewSelection);
result = "";
agreementForm.setModelObject(new AgreementFormModel());
ProjectSelectionModel projectSelectionModel = ProjectSelectionForm.this.getModelObject();
projectSelectionModel.project = aNewSelection;
projectSelectionModel.annotatorsProgress = new TreeMap<String, Integer>();
projectSelectionModel.annotatorsProgressInPercent = new TreeMap<String, Integer>();
projectSelectionModel.totalDocuments = sourceDocuments.size();
ProjectSelectionForm.this.setVisible(true);
// Clear the cached CASes. When we switch to another project, we'll have to
// reload them.
updateAgreementTable(RequestCycle.get().find(AjaxRequestTarget.class), true);
// Annotator's Progress
if (projectSelectionModel.project != null) {
projectSelectionModel.annotatorsProgressInPercent
.putAll(getPercentageOfFinishedDocumentsPerUser(projectSelectionModel.project));
projectSelectionModel.annotatorsProgress.putAll(getFinishedDocumentsPerUser(projectSelectionModel.project));
}
projectName.setDefaultModelObject(projectSelectionModel.project.getName());
overallProjectProgressImage.setVisible(false);
overview.setVisible(false);
annotatorsProgressImage.setImageResource(createProgressChart(
projectSelectionModel.annotatorsProgress,
projectSelectionModel.totalDocuments, false));
annotatorsProgressImage.setVisible(true);
annotatorsProgressPercentageImage.setImageResource(createProgressChart(
projectSelectionModel.annotatorsProgressInPercent, 100, true));
annotatorsProgressPercentageImage.setVisible(true);
List<String> documentListAsColumnHeader = new ArrayList<String>();
documentListAsColumnHeader.add("Documents");
// A column for curation user annotation document status
documentListAsColumnHeader.add(CURATION);
// List of users with USER permission level
List<User> users = projectService.listProjectUsersWithPermissions(
projectSelectionModel.project, PermissionLevel.USER);
for (User user : users) {
documentListAsColumnHeader.add(user.getUsername());
}
List<List<String>> userAnnotationDocumentStatusList = new ArrayList<List<String>>();
// Add a timestamp row for every user.
List<String> projectTimeStamp = new ArrayList<String>();
projectTimeStamp.add(LAST_ACCESS + LAST_ACCESS_ROW); // first
// column
if (projectService.existsProjectTimeStamp(aNewSelection)) {
projectTimeStamp.add(LAST_ACCESS
+ new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").format(projectService
.getProjectTimeStamp(aNewSelection)));
}
else {
projectTimeStamp.add(LAST_ACCESS + "__");
}
for (User user : users) {
if (projectService.existsProjectTimeStamp(projectSelectionModel.project,
user.getUsername())) {
projectTimeStamp
.add(LAST_ACCESS + new SimpleDateFormat("dd/MM/yyyy HH:mm:ss")
.format(projectService.getProjectTimeStamp(
projectSelectionModel.project,
user.getUsername())));
}
else {
projectTimeStamp.add(LAST_ACCESS + "__");
}
}
userAnnotationDocumentStatusList.add(projectTimeStamp);
for (SourceDocument document : sourceDocuments) {
List<String> userAnnotationDocuments = new ArrayList<String>();
userAnnotationDocuments.add(DOCUMENT + document.getName());
// Curation Document status
userAnnotationDocuments.add(WebAnnoConst.CURATION_USER + "-" + DOCUMENT
+ document.getName());
for (User user : users) {
// annotation document status for this annotator
userAnnotationDocuments.add(user.getUsername() + "-" + DOCUMENT
+ document.getName());
}
userAnnotationDocumentStatusList.add(userAnnotationDocuments);
}
TableDataProvider provider = new TableDataProvider(documentListAsColumnHeader,
userAnnotationDocumentStatusList);
List<IColumn<?,?>> columns = new ArrayList<IColumn<?,?>>();
for (int i = 0; i < provider.getColumnCount(); i++) {
columns.add(new DocumentStatusColumnMetaData(provider, i, projectSelectionModel.project));
}
annotationDocumentStatusTable.remove();
annotationDocumentStatusTable = new DefaultDataTable("rsTable", columns,
provider, 20);
annotationDocumentStatusTable.setOutputMarkupId(true);
monitoringDetailForm.add(annotationDocumentStatusTable);
}
@Override
protected boolean wantOnSelectionChangedNotifications()
{
return true;
}
@Override
protected CharSequence getDefaultChoice(String aSelectedValue)
{
return "";
}
});
}
}
Model<Project> projectModel = new Model<Project>()
{
private static final long serialVersionUID = -6394439155356911110L;
@Override
public Project getObject()
{
return projectSelectionForm.getModelObject().project;
}
};
private Map<String, Integer> getFinishedDocumentsPerUser(Project aProject)
{
Map<String, Integer> annotatorsProgress = new HashMap<String, Integer>();
if (aProject != null) {
for (User user : projectService.listProjectUsersWithPermissions(aProject, PermissionLevel.USER)) {
for (SourceDocument document : documentService.listSourceDocuments(aProject)) {
if (documentService.isAnnotationFinished(document, user)) {
if (annotatorsProgress.get(user.getUsername()) == null) {
annotatorsProgress.put(user.getUsername(), 1);
}
else {
int previousValue = annotatorsProgress.get(user.getUsername());
annotatorsProgress.put(user.getUsername(), previousValue + 1);
}
}
}
if (annotatorsProgress.get(user.getUsername()) == null) {
annotatorsProgress.put(user.getUsername(), 0);
}
}
}
return annotatorsProgress;
}
private Map<String, Integer> getPercentageOfFinishedDocumentsPerUser(Project aProject)
{
Map<String, Integer> annotatorsProgress = new HashMap<String, Integer>();
if (aProject != null) {
for (User user : projectService.listProjectUsersWithPermissions(aProject, PermissionLevel.USER)) {
int finished = 0;
int ignored = 0;
int totalDocs = 0;
List<SourceDocument> documents = documentService.listSourceDocuments(aProject);
List<SourceDocument> trainingDoc = new ArrayList<SourceDocument>();
for (SourceDocument sdc : documents) {
if (sdc.isTrainingDocument()) {
trainingDoc.add(sdc);
}
}
documents.removeAll(trainingDoc);
for (SourceDocument document : documents) {
totalDocs++;
if (documentService.isAnnotationFinished(document, user)) {
finished++;
}
else if (documentService.existsAnnotationDocument(document, user)) {
AnnotationDocument annotationDocument = documentService.getAnnotationDocument(
document, user);
if (annotationDocument.getState().equals(AnnotationDocumentState.IGNORE)) {
ignored++;
}
}
}
annotatorsProgress.put(user.getUsername(),
(int) Math.round((double) (finished * 100) / (totalDocs - ignored)));
}
}
return annotatorsProgress;
}
private Map<String, Integer> getOverallProjectProgress()
{
Map<String, Integer> overallProjectProgress = new LinkedHashMap<String, Integer>();
String username = SecurityContextHolder.getContext().getAuthentication().getName();
User user = userRepository.get(username);
for (Project project : projectService.listProjects()) {
if (SecurityUtil.isCurator(project, projectService, user)
|| SecurityUtil.isProjectAdmin(project, projectService, user)) {
int annoFinished = documentService.listFinishedAnnotationDocuments(project).size();
int allAnno = documentService.numberOfExpectedAnnotationDocuments(project);
int progress = (int) Math.round((double) (annoFinished * 100) / (allAnno));
overallProjectProgress.put(project.getName(), progress);
}
}
return overallProjectProgress;
}
static public class ProjectSelectionModel
implements Serializable
{
protected int totalDocuments;
private static final long serialVersionUID = -1L;
public Project project;
public Map<String, Integer> annotatorsProgress = new TreeMap<String, Integer>();
public Map<String, Integer> annotatorsProgressInPercent = new TreeMap<String, Integer>();
}
private class MonitoringDetailForm
extends Form<Project>
{
private static final long serialVersionUID = -1L;
public MonitoringDetailForm(String id)
{
super(id, new CompoundPropertyModel<Project>(new EntityModel<Project>(new Project())));
}
}
private class AgreementForm
extends Form<AgreementFormModel>
{
private static final long serialVersionUID = -1L;
private ListChoice<AnnotationFeature> featureList;
private AgreementTable agreementTable2;
private DropDownChoice<ConcreteAgreementMeasure> measureDropDown;
private DropDownChoice<LinkCompareBehavior> linkCompareBehaviorDropDown;
private DropDownChoice<AgreementReportExportFormat> exportFormat;
private AjaxButton exportAll;
private CheckBox excludeIncomplete;
public AgreementForm(String id)
{
super(id, new CompoundPropertyModel<AgreementFormModel>(new AgreementFormModel()));
setOutputMarkupId(true);
setOutputMarkupPlaceholderTag(true);
add(measureDropDown = new DropDownChoice<ConcreteAgreementMeasure>(
"measure", asList(ConcreteAgreementMeasure.values()),
new EnumChoiceRenderer<ConcreteAgreementMeasure>(MonitoringPage.this)));
addUpdateAgreementTableBehavior(measureDropDown);
add(linkCompareBehaviorDropDown = new DropDownChoice<LinkCompareBehavior>(
"linkCompareBehavior", asList(LinkCompareBehavior.values()),
new EnumChoiceRenderer<LinkCompareBehavior>(MonitoringPage.this)) {
private static final long serialVersionUID = 1L;
@Override
protected void onConfigure()
{
AgreementFormModel model = AgreementForm.this.getModelObject();
if (model != null && model.feature != null) {
setVisible(!LinkMode.NONE.equals(model.feature.getLinkMode()));
}
else {
setVisible(false);
}
}
});
linkCompareBehaviorDropDown.setOutputMarkupId(true);
linkCompareBehaviorDropDown.setOutputMarkupPlaceholderTag(true);
addUpdateAgreementTableBehavior(linkCompareBehaviorDropDown);
add(exportFormat = new DropDownChoice<AgreementReportExportFormat>(
"exportFormat", asList(AgreementReportExportFormat.values()),
new EnumChoiceRenderer<AgreementReportExportFormat>(MonitoringPage.this)));
exportFormat.add(new OnChangeAjaxBehavior() {
private static final long serialVersionUID = -1L;
@Override
protected void onUpdate(AjaxRequestTarget aTarget)
{
// Actually nothing to do, we just want the Ajax behavior to update the model
// object.
}
});
add(excludeIncomplete = new CheckBox("excludeIncomplete") {
private static final long serialVersionUID = 1L;
@Override
protected void onConfigure()
{
super.onConfigure();
setEnabled(AgreementForm.this.getModelObject().measure.isNullValueSupported());
}
});
addUpdateAgreementTableBehavior(excludeIncomplete);
add(featureList = new ListChoice<AnnotationFeature>("feature")
{
private static final long serialVersionUID = 1L;
{
setOutputMarkupId(true);
setChoices(new LoadableDetachableModel<List<AnnotationFeature>>()
{
private static final long serialVersionUID = 1L;
@Override
protected List<AnnotationFeature> load()
{
List<AnnotationFeature> features = annotationService
.listAnnotationFeature((projectSelectionForm.getModelObject().project));
List<AnnotationFeature> unusedFeatures = new ArrayList<AnnotationFeature>();
for (AnnotationFeature feature : features) {
if (feature.getLayer().getName().equals(Token.class.getName())
|| feature.getLayer().getName()
.equals(WebAnnoConst.COREFERENCE_LAYER)) {
unusedFeatures.add(feature);
}
}
features.removeAll(unusedFeatures);
return features;
}
});
setChoiceRenderer(new ChoiceRenderer<AnnotationFeature>()
{
private static final long serialVersionUID = -3370671999669664776L;
@Override
public Object getDisplayValue(AnnotationFeature aObject)
{
return aObject.getLayer().getUiName() + " : " + aObject.getUiName();
}
});
setNullValid(false);
}
@Override
protected CharSequence getDefaultChoice(String aSelectedValue)
{
return "";
}
});
addUpdateAgreementTableBehavior(featureList);
add(agreementTable2 = new AgreementTable("agreementTable",
getModel(),
new LoadableDetachableModel<PairwiseAnnotationResult>()
{
private static final long serialVersionUID = 1L;
@Override
protected PairwiseAnnotationResult load()
{
AnnotationFeature feature = featureList.getModelObject();
// Do not do any agreement if no feature has been selected yet.
if (feature == null) {
return null;
}
Map<String, List<JCas>> casMap = getJCases();
Project project = projectSelectionForm.getModelObject().project;
List<DiffAdapter> adapters = CasDiff2.getAdapters(annotationService,
project);
AgreementFormModel pref = AgreementForm.this.getModelObject();
DiffResult diff = CasDiff2.doDiff(asList(feature.getLayer().getName()),
adapters, pref.linkCompareBehavior, casMap);
return AgreementUtils.getPairwiseAgreement(
AgreementForm.this.getModelObject().measure,
pref.excludeIncomplete, diff, feature.getLayer().getName(),
feature.getName(), casMap);
}
}));
exportAll = new AjaxButton("exportAll") {
private static final long serialVersionUID = 3908727116180563330L;
private AJAXDownload download;
{
download = new AJAXDownload() {
private static final long serialVersionUID = 1L;
@Override
protected IResourceStream getResourceStream()
{
return new AbstractResourceStream() {
private static final long serialVersionUID = 1L;
@Override
public InputStream getInputStream()
throws ResourceStreamNotFoundException
{
AnnotationFeature feature = featureList.getModelObject();
// Do not do any agreement if no feature has been selected yet.
if (feature == null) {
return null;
}
Map<String, List<JCas>> casMap = getJCases();
Project project = projectSelectionForm.getModelObject().project;
List<DiffAdapter> adapters = CasDiff2.getAdapters(annotationService,
project);
AgreementFormModel pref = AgreementForm.this.getModelObject();
DiffResult diff = CasDiff2.doDiff(asList(feature.getLayer().getName()),
adapters, pref.linkCompareBehavior, casMap);
AgreementResult agreementResult = AgreementUtils.makeStudy(diff,
feature.getLayer().getName(), feature.getName(),
pref.excludeIncomplete, casMap);
try {
return AgreementUtils.generateCsvReport(agreementResult);
}
catch (Exception e) {
// FIXME Is there some better error handling here?
LOG.error("Unable to generate report", e);
throw new ResourceStreamNotFoundException(e);
}
}
@Override
public void close()
throws IOException
{
// Nothing to do
}
};
}
};
add(download);
setOutputMarkupId(true);
setOutputMarkupPlaceholderTag(true);
}
@Override
protected void onConfigure()
{
super.onConfigure();
setVisible(featureList.getModelObject() != null);
}
@Override
protected void onSubmit(AjaxRequestTarget aTarget, Form<?> aForm)
{
download.initiate(aTarget, "agreement"
+ AgreementForm.this.getModelObject().exportFormat.getExtension());
}
};
add(exportAll);
}
@Override
protected void onConfigure()
{
ProjectSelectionModel model = projectSelectionForm.getModelObject();
setVisible(model != null && model.project != null);
}
private void addUpdateAgreementTableBehavior(Component aComponent)
{
aComponent.add(new OnChangeAjaxBehavior()
{
private static final long serialVersionUID = 1L;
@Override
protected void onUpdate(AjaxRequestTarget aTarget)
{
// We may get errors when loading the JCases but at that time we can no longer
// add the feedback panel to the cycle, so let's do it here.
aTarget.add(getFeedbackPanel());
updateAgreementTable(aTarget, false);
// // Adding this as well because when choosing a different measure, it may affect
// // the ability to exclude incomplete configurations.
// aTarget.add(excludeIncomplete);
// aTarget.add(linkCompareBehaviorDropDown);
// #1791 - for some reason the updateAgreementTableBehavior does not work
// anymore on the linkCompareBehaviorDropDown if we add it explicitly here/
// control its visibility in onConfigure()
// as a workaround, we currently just re-render the whole form
aTarget.add(agreementForm);
}
});
}
}
static public class AgreementFormModel
implements Serializable
{
private static final long serialVersionUID = -1L;
public AnnotationFeature feature;
public LinkCompareBehavior linkCompareBehavior = LinkCompareBehavior.LINK_TARGET_AS_LABEL;
public boolean excludeIncomplete = false;
public ConcreteAgreementMeasure measure =
ConcreteAgreementMeasure.KRIPPENDORFF_ALPHA_NOMINAL_AGREEMENT;
private boolean savedExcludeIncomplete = excludeIncomplete;
private boolean savedNullSupported = measure.isNullValueSupported();
public AgreementReportExportFormat exportFormat = AgreementReportExportFormat.CSV;
public void setMeasure(ConcreteAgreementMeasure aMeasure)
{
measure = aMeasure;
// Did the null-support status change?
if (savedNullSupported != measure.isNullValueSupported()) {
savedNullSupported = measure.isNullValueSupported();
// If it changed, is null support locked or not?
if (!measure.isNullValueSupported()) {
// Is locked, so save what we had before and lock it
savedExcludeIncomplete = excludeIncomplete;
excludeIncomplete = true;
}
else {
// Is not locked, so restore what we had before
excludeIncomplete = savedExcludeIncomplete;
}
}
}
// This method must be here so Wicket sets the "measure" value through the setter instead
// of using field injection
public ConcreteAgreementMeasure getMeasure()
{
return measure;
}
}
private void updateTrainingResultForm(Project aProject)
{
trainingResultForm.remove();
trainingResultForm = new TrainingResultForm("trainingResultForm");
add(trainingResultForm);
trainingResultForm.setVisible(WebAnnoConst.PROJECT_TYPE_AUTOMATION.equals(aProject.getMode()));
}
private class TrainingResultForm
extends Form<ResultModel>
{
private static final long serialVersionUID = 1037668483966897381L;
ListChoice<MiraTemplate> selectedTemplate;
public TrainingResultForm(String id)
{
super(id, new CompoundPropertyModel<ResultModel>(new ResultModel()));
add(new Label("resultLabel", new LoadableDetachableModel<String>()
{
private static final long serialVersionUID = 891566759811286173L;
@Override
protected String load()
{
return result;
}
}).setOutputMarkupId(true));
add(new Label("annoDocs", new LoadableDetachableModel<String>()
{
private static final long serialVersionUID = 891566759811286173L;
@Override
protected String load()
{
MiraTemplate template = selectedTemplate.getModelObject();
if (template != null && automationService.existsAutomationStatus(template)) {
return automationService.getAutomationStatus(template).getAnnoDocs() + "";
}
else {
return "";
}
}
}).setOutputMarkupId(true));
add(new Label("trainDocs", new LoadableDetachableModel<String>()
{
private static final long serialVersionUID = 891566759811286173L;
@Override
protected String load()
{
MiraTemplate template = selectedTemplate.getModelObject();
if (template != null && automationService.existsAutomationStatus(template)) {
return automationService.getAutomationStatus(template).getTrainDocs() + "";
}
else {
return "";
}
}
}).setOutputMarkupId(true));
add(new Label("totalDocs", new LoadableDetachableModel<String>()
{
private static final long serialVersionUID = 891566759811286173L;
@Override
protected String load()
{
MiraTemplate template = selectedTemplate.getModelObject();
if (template != null && automationService.existsAutomationStatus(template)) {
return automationService.getAutomationStatus(template).getTotalDocs() + "";
}
else {
return "";
}
}
}).setOutputMarkupId(true));
add(new Label("startTime", new LoadableDetachableModel<String>()
{
private static final long serialVersionUID = 891566759811286173L;
@Override
protected String load()
{
MiraTemplate template = selectedTemplate.getModelObject();
if (template != null && automationService.existsAutomationStatus(template)) {
return automationService.getAutomationStatus(template).getStartime().toString();
}
else {
return "";
}
}
}).setOutputMarkupId(true));
add(new Label("endTime", new LoadableDetachableModel<String>()
{
private static final long serialVersionUID = 891566759811286173L;
@Override
protected String load()
{
MiraTemplate template = selectedTemplate.getModelObject();
if (template != null && automationService.existsAutomationStatus(template)) {
if (automationService.getAutomationStatus(template).getEndTime()
.equals(automationService.getAutomationStatus(template).getStartime())) {
return "---";
}
return automationService.getAutomationStatus(template).getEndTime().toString();
}
else {
return "";
}
}
}).setOutputMarkupId(true));
add(new Label("status", new LoadableDetachableModel<String>()
{
private static final long serialVersionUID = 891566759811286173L;
@Override
protected String load()
{
MiraTemplate template = selectedTemplate.getModelObject();
if (template != null && automationService.existsAutomationStatus(template)) {
return automationService.getAutomationStatus(template).getStatus().getName();
}
else {
return "";
}
}
}).setOutputMarkupId(true));
add(selectedTemplate = new ListChoice<MiraTemplate>("layerResult")
{
private static final long serialVersionUID = 1L;
{
setChoices(new LoadableDetachableModel<List<MiraTemplate>>()
{
private static final long serialVersionUID = 1L;
@Override
protected List<MiraTemplate> load()
{
return automationService.listMiraTemplates(projectSelectionForm
.getModelObject().project);
}
});
setChoiceRenderer(new ChoiceRenderer<MiraTemplate>()
{
private static final long serialVersionUID = -2000622431037285685L;
@Override
public Object getDisplayValue(MiraTemplate aObject)
{
return "["
+ aObject.getTrainFeature().getLayer().getUiName()
+ "] "
+ (aObject.getTrainFeature().getTagset() == null ? aObject
.getTrainFeature().getUiName() : aObject
.getTrainFeature().getTagset().getName());
}
});
setNullValid(false);
}
@Override
protected CharSequence getDefaultChoice(String aSelectedValue)
{
return "";
}
});
selectedTemplate.add(new OnChangeAjaxBehavior()
{
private static final long serialVersionUID = 7492425689121761943L;
@Override
protected void onUpdate(AjaxRequestTarget aTarget)
{
result = getModelObject().layerResult.getResult();
aTarget.add(TrainingResultForm.this);
}
}).setOutputMarkupId(true);
}
}
public class ResultModel
implements Serializable
{
private static final long serialVersionUID = 3611186385198494181L;
public MiraTemplate layerResult;
public String annoDocs;
public String trainDocs;
public String totalDocs;
public String startTime;
public String endTime;
public String status;
}
// The CASes cannot be serialized, so we make them transient here. However, it does not matter
// as we do not access the field directly but via getJCases() which will re-load them if
// necessary, e.g. if the transient field is empty after a session is restored from a
// persisted state.
private transient Map<String, List<JCas>> cachedCASes;
/**
* Get the finished CASes used to compute agreement.
*/
private Map<String, List<JCas>> getJCases()
{
// Avoid reloading the CASes when switching features.
if (cachedCASes != null) {
return cachedCASes;
}
Project project = projectSelectionForm.getModelObject().project;
List<User> users = projectService
.listProjectUsersWithPermissions(project, PermissionLevel.USER);
List<SourceDocument> sourceDocuments = documentService.listSourceDocuments(project);
// Filter training documents out from the source documents. Training documents are not
// being annotated
// FIXME actually, listSourceDocuments() shouldn return training documents in the first
// place. Cf. https://github.com/webanno/webanno/issues/23
List<SourceDocument> trainingDoc = new ArrayList<SourceDocument>();
for (SourceDocument sdc : sourceDocuments) {
if (sdc.isTrainingDocument()) {
trainingDoc.add(sdc);
}
}
sourceDocuments.removeAll(trainingDoc);
cachedCASes = new LinkedHashMap<>();
for (User user : users) {
List<JCas> cases = new ArrayList<>();
for (SourceDocument document : sourceDocuments) {
JCas jCas = null;
// Load the CAS if there is a finished one.
if (documentService.existsAnnotationDocument(document, user)) {
AnnotationDocument annotationDocument = documentService.getAnnotationDocument(
document, user);
if (annotationDocument.getState().equals(AnnotationDocumentState.FINISHED)) {
try {
jCas = documentService.readAnnotationCas(annotationDocument);
documentService.upgradeCas(jCas.getCas(), annotationDocument);
// REC: I think there is no need to write the CASes here. We would not
// want to interfere with currently active annotator users
// Set the CAS name in the DocumentMetaData so that we can pick it
// up in the Diff position for the purpose of debugging / transparency.
DocumentMetaData documentMetadata = DocumentMetaData.get(jCas);
documentMetadata.setDocumentId(annotationDocument.getDocument().getName());
documentMetadata.setCollectionId(annotationDocument.getProject().getName());
}
catch (Exception e) {
LOG.error("Unable to load data", e);
error("Unable to load data: " + ExceptionUtils.getRootCauseMessage(e));
}
}
}
// The next line can enter null values into the list if a user didn't work on this
// source document yet.
cases.add(jCas);
}
cachedCASes.put(user.getUsername(), cases);
}
return cachedCASes;
}
private void updateAgreementTable(AjaxRequestTarget aTarget, boolean aClearCache)
{
try {
if (aClearCache) {
cachedCASes = null;
}
agreementForm.agreementTable2.getDefaultModel().detach();
if (aTarget != null) {
aTarget.add(agreementForm.agreementTable2);
}
}
catch (Throwable e) {
LOG.error("Error updating agreement table", e);
error("Error updating agreement table: " + ExceptionUtils.getRootCauseMessage(e));
if (aTarget != null) {
aTarget.addChildren(getPage(), FeedbackPanel.class);
}
}
}
private ChartImageResource createProgressChart(Map<String, Integer> chartValues, int aMaxValue,
boolean aIsPercentage)
{
// fill dataset
DefaultCategoryDataset dataset = new DefaultCategoryDataset();
for (String chartValue : chartValues.keySet()) {
dataset.setValue(chartValues.get(chartValue), "Completion", chartValue);
}
// create chart
JFreeChart chart = ChartFactory.createBarChart(null, null, null, dataset,
PlotOrientation.HORIZONTAL, false, false, false);
CategoryPlot plot = chart.getCategoryPlot();
plot.setInsets(new RectangleInsets(UnitType.ABSOLUTE, 0, 20, 0, 20));
plot.getRangeAxis().setRange(0.0, aMaxValue);
((NumberAxis) plot.getRangeAxis()).setNumberFormatOverride(new DecimalFormat("0"));
// For documents lessan 10, avoid repeating the number of documents such
// as 0 0 1 1 1
// NumberTickUnit automatically determin the range
if (!aIsPercentage && aMaxValue <= 10) {
TickUnits standardUnits = new TickUnits();
NumberAxis tick = new NumberAxis();
tick.setTickUnit(new NumberTickUnit(1));
standardUnits.add(tick.getTickUnit());
plot.getRangeAxis().setStandardTickUnits(standardUnits);
}
plot.setOutlineVisible(false);
plot.setBackgroundPaint(null);
BarRenderer renderer = new BarRenderer();
renderer.setBarPainter(new StandardBarPainter());
renderer.setShadowVisible(false);
// renderer.setGradientPaintTransformer(new
// StandardGradientPaintTransformer(
// GradientPaintTransformType.HORIZONTAL));
renderer.setSeriesPaint(0, Color.BLUE);
chart.getCategoryPlot().setRenderer(renderer);
return new ChartImageResource(chart, CHART_WIDTH, 30 + (chartValues.size() * 18));
}
/**
* Build dynamic columns for the user's annotation documents status {@link DataGridView}
*/
public class DocumentStatusColumnMetaData
extends AbstractColumn<List<String>, Object>
{
// private RepositoryService projectRepositoryService;
private static final long serialVersionUID = 1L;
private int columnNumber;
private Project project;
public DocumentStatusColumnMetaData(final TableDataProvider prov, final int colNumber,
Project aProject)
{
super(new AbstractReadOnlyModel<String>()
{
private static final long serialVersionUID = 1L;
@Override
public String getObject()
{
return prov.getColNames().get(colNumber);
}
});
columnNumber = colNumber;
project = aProject;
}
@Override
public void populateItem(final Item<ICellPopulator<List<String>>> aCellItem,
final String componentId, final IModel<List<String>> rowModel)
{
String username = SecurityContextHolder.getContext().getAuthentication()
.getName();
final User user = userRepository.get(username);
int rowNumber = aCellItem.getIndex();
aCellItem.setOutputMarkupId(true);
final String value = getCellValue(rowModel.getObject().get(columnNumber)).trim();
if (rowNumber == 0) {
aCellItem.add(new Label(componentId, value.substring(value.indexOf(":") + 1)));
}
else if (value.startsWith(MonitoringPage.LAST_ACCESS)) {
aCellItem.add(new Label(componentId, value.substring(value.indexOf(":") + 1)));
aCellItem.add(AttributeModifier.append("class", "centering"));
}
else if (value.substring(0, value.indexOf(":")).equals(WebAnnoConst.CURATION_USER)) {
SourceDocument document = documentService.getSourceDocument(project,
value.substring(value.indexOf(":") + 1));
SourceDocumentState state = document.getState();
// #770 - Disable per-document progress on account of slowing down monitoring page
// if (iconNameForState.equals(AnnotationDocumentState.IN_PROGRESS.toString())
// && document.getSentenceAccessed() != 0) {
// JCas jCas = null;
// try {
// jCas = projectRepositoryService.readJCas(document, document.getProject(), user);
// }
// catch (UIMAException e) {
// LOG.info(ExceptionUtils.getRootCauseMessage(e));
// }
// catch (ClassNotFoundException e) {
// LOG.info(e.getMessage());
// }
// catch (IOException e) {
// LOG.info(e.getMessage());
// }
// int totalSN = BratAjaxCasUtil.getNumberOfPages(jCas);
// aCellItem.add(new Label(componentId, document.getSentenceAccessed() + "/"+totalSN));
// }
// else {
EmbeddableImage icon = new EmbeddableImage(componentId, ICONS.get(state));
icon.add(new AttributeAppender("style", "cursor: pointer", ";"));
aCellItem.add(icon);
// }
aCellItem.add(AttributeModifier.append("class", "centering"));
aCellItem.add(new AjaxEventBehavior("click")
{
private static final long serialVersionUID = -4213621740511947285L;
@Override
protected void onEvent(AjaxRequestTarget aTarget)
{
if (!SecurityUtil.isCurator(project, projectService, user)) {
aTarget.appendJavaScript(
"alert('the state can only be changed explicitly by the curator')");
return;
}
try {
SourceDocument doc = documentService.getSourceDocument(project,
value.substring(value.indexOf(":") + 1));
if (doc.getState().equals(CURATION_FINISHED)) {
changeSourceDocumentState(doc, CURATION_FINISHED_TO_CURATION_IN_PROGRESS);
}
else if (doc.getState().equals(CURATION_IN_PROGRESS)) {
changeSourceDocumentState(doc, CURATION_IN_PROGRESS_TO_CURATION_FINISHED);
}
else if (doc.getState().equals(ANNOTATION_IN_PROGRESS)) {
changeSourceDocumentState(doc, ANNOTATION_IN_PROGRESS_TO_CURATION_IN_PROGRESS);
}
}
catch (IOException e) {
LOG.info(e.getMessage(), e);
}
updateAgreementTable(aTarget, true);
aTarget.add(aCellItem);
updateStats(aTarget, projectSelectionForm.getModelObject());
}
});
}
else {
SourceDocument document = documentService.getSourceDocument(project,
value.substring(value.indexOf(":") + 1));
User annotator = userRepository.get(value.substring(0, value.indexOf(":")));
AnnotationDocumentState state;
AnnotationDocument annoDoc = null;
if (documentService.existsAnnotationDocument(document, annotator)) {
annoDoc = documentService.getAnnotationDocument(document, annotator);
state = annoDoc.getState();
}
// user didn't even start working on it
else {
state = AnnotationDocumentState.NEW;
AnnotationDocument annotationDocument = new AnnotationDocument();
annotationDocument.setDocument(document);
annotationDocument.setName(document.getName());
annotationDocument.setProject(project);
annotationDocument.setUser(annotator.getUsername());
annotationDocument.setState(state);
documentService.createAnnotationDocument(annotationDocument);
}
// if state is in progress, add the last sentence number accessed
// #770 - Disable per-document progress on account of slowing down monitoring page
// if (annoDoc != null && (annoDoc.getSentenceAccessed() != 0)
// && annoDoc.getState().equals(AnnotationDocumentState.IN_PROGRESS)) {
// JCas jCas = null;
// try {
// jCas = projectRepositoryService.readJCas(document, document.getProject(), annotator);
// }
// catch (UIMAException e) {
// LOG.info(ExceptionUtils.getRootCauseMessage(e));
// }
// catch (ClassNotFoundException e) {
// LOG.info(e.getMessage());
// }
// catch (IOException e) {
// LOG.info(e.getMessage());
// }
// int totalSN = BratAjaxCasUtil.getNumberOfPages(jCas);
// aCellItem.add(new Label(componentId, annoDoc.getSentenceAccessed() + "/"+totalSN));
// }
// else {
EmbeddableImage icon = new EmbeddableImage(componentId, ICONS.get(state));
icon.add(new AttributeAppender("style", "cursor: pointer", ";"));
aCellItem.add(icon);
// }
aCellItem.add(AttributeModifier.append("class", "centering"));
aCellItem.add(new AjaxEventBehavior("click")
{
private static final long serialVersionUID = -5089819284917455111L;
@Override
protected void onEvent(AjaxRequestTarget aTarget)
{
SourceDocument document = documentService.getSourceDocument(project,
value.substring(value.indexOf(":") + 1));
User user = userRepository.get(value.substring(0,
value.indexOf(":")));
AnnotationDocumentState state;
if (documentService.existsAnnotationDocument(document, user)) {
AnnotationDocument annoDoc = documentService
.getAnnotationDocument(document, user);
state = annoDoc.getState();
if (state.toString()
.equals(AnnotationDocumentState.FINISHED.toString())) {
changeAnnotationDocumentState(document, user,
ANNOTATION_FINISHED_TO_ANNOTATION_IN_PROGRESS);
}
else if (state.toString().equals(
AnnotationDocumentState.IN_PROGRESS.toString())) {
changeAnnotationDocumentState(document, user,
ANNOTATION_IN_PROGRESS_TO_ANNOTATION_FINISHED);
}
if (state.toString().equals(AnnotationDocumentState.NEW.toString())) {
changeAnnotationDocumentState(document, user, NEW_TO_IGNORE);
}
if (state.toString().equals(AnnotationDocumentState.IGNORE.toString())) {
changeAnnotationDocumentState(document, user, IGNORE_TO_NEW);
}
}
// user didn't even start working on it
else {
AnnotationDocument annotationDocument = new AnnotationDocument();
annotationDocument.setDocument(document);
annotationDocument.setName(document.getName());
annotationDocument.setProject(project);
annotationDocument.setUser(user.getUsername());
annotationDocument.setState(AnnotationDocumentStateTransition
.transition(NEW_TO_ANNOTATION_IN_PROGRESS));
documentService.createAnnotationDocument(annotationDocument);
}
updateAgreementTable(aTarget, true);
aTarget.add(aCellItem);
updateStats(aTarget, projectSelectionForm.getModelObject());
}
});
}
}
private void updateStats(AjaxRequestTarget aTarget, ProjectSelectionModel aModel)
{
aModel.annotatorsProgress.clear();
aModel.annotatorsProgress.putAll(getFinishedDocumentsPerUser(project));
annotatorsProgressImage.setImageResource(createProgressChart(aModel.annotatorsProgress,
aModel.totalDocuments, false));
aTarget.add(annotatorsProgressImage.setOutputMarkupId(true));
aModel.annotatorsProgressInPercent.clear();
aModel.annotatorsProgressInPercent.putAll(getPercentageOfFinishedDocumentsPerUser(project));
annotatorsProgressPercentageImage.setImageResource(createProgressChart(
aModel.annotatorsProgressInPercent, 100, true));
aTarget.add(annotatorsProgressPercentageImage.setOutputMarkupId(true));
aTarget.add(monitoringDetailForm.setOutputMarkupId(true));
aTarget.add(agreementForm);
}
/**
* Helper method to get the cell value for the user-annotation document status as
* <b>username:documentName</b>
*/
private String getCellValue(String aValue)
{
// It is the user column, return user name
if (aValue.startsWith(MonitoringPage.DOCUMENT)) {
return aValue.substring(aValue.indexOf(MonitoringPage.DOCUMENT));
}
// return as it is
else if (aValue.startsWith(MonitoringPage.LAST_ACCESS)) {
return aValue;
}
// Initialization of the appliaction, no project selected
else if (project.getId() == 0) {
return "";
}
// It is document column, get the status from the database
else {
String username = aValue.substring(0, aValue.indexOf(MonitoringPage.DOCUMENT) - 1);
String documentName = aValue.substring(aValue.indexOf(MonitoringPage.DOCUMENT)
+ MonitoringPage.DOCUMENT.length());
return username + ":" + documentName;
}
}
/**
* change the state of an annotation document. used to re-open closed documents
*/
private void changeAnnotationDocumentState(SourceDocument aSourceDocument, User aUser,
AnnotationDocumentStateTransition aAnnotationDocumentStateTransition)
{
AnnotationDocument annotationDocument = documentService.getAnnotationDocument(
aSourceDocument, aUser);
annotationDocument.setState(AnnotationDocumentStateTransition
.transition(aAnnotationDocumentStateTransition));
documentService.createAnnotationDocument(annotationDocument);
}
/**
* change source document state when curation document state is changed.
*/
private void changeSourceDocumentState(SourceDocument aSourceDocument,
SourceDocumentStateTransition aSourceDocumentStateTransition)
throws IOException
{
aSourceDocument.setState(
SourceDocumentStateTransition.transition(aSourceDocumentStateTransition));
documentService.createSourceDocument(aSourceDocument);
}
}
/**
* Only admins and project managers can see this page
*/
@MenuItemCondition
public static boolean menuItemCondition(ProjectService aRepo, UserDao aUserRepo)
{
String username = SecurityContextHolder.getContext().getAuthentication().getName();
User user = aUserRepo.get(username);
return SecurityUtil.monitoringEnabeled(aRepo, user);
}
}