/**
* <a href="http://www.openolat.org">
* OpenOLAT - Online Learning and Training</a><br>
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); <br>
* you may not use this file except in compliance with the License.<br>
* You may obtain a copy of the License at the
* <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
* <p>
* Unless required by applicable law or agreed to in writing,<br>
* software distributed under the License is distributed on an "AS IS" BASIS, <br>
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
* See the License for the specific language governing permissions and <br>
* limitations under the License.
* <p>
* Initial code contributed and copyrighted by<br>
* frentix GmbH, http://www.frentix.com
* <p>
*/
package org.olat.course.nodes.gta.ui;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.olat.basesecurity.BaseSecurityModule;
import org.olat.basesecurity.GroupRoles;
import org.olat.basesecurity.IdentityRef;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.components.form.flexible.FormItem;
import org.olat.core.gui.components.form.flexible.FormItemContainer;
import org.olat.core.gui.components.form.flexible.elements.FlexiTableElement;
import org.olat.core.gui.components.form.flexible.elements.FormLink;
import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement;
import org.olat.core.gui.components.form.flexible.elements.SingleSelection;
import org.olat.core.gui.components.form.flexible.elements.TextElement;
import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
import org.olat.core.gui.components.form.flexible.impl.FormEvent;
import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiColumnModel;
import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiColumnModel;
import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel;
import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableDataModelFactory;
import org.olat.core.gui.components.form.flexible.impl.elements.table.StaticFlexiCellRenderer;
import org.olat.core.gui.components.form.flexible.impl.elements.table.TextFlexiCellRenderer;
import org.olat.core.gui.components.link.Link;
import org.olat.core.gui.control.Controller;
import org.olat.core.gui.control.Event;
import org.olat.core.gui.control.WindowControl;
import org.olat.core.gui.control.generic.closablewrapper.CloseableCalloutWindowController;
import org.olat.core.id.Identity;
import org.olat.core.id.OLATResourceable;
import org.olat.core.id.Roles;
import org.olat.core.id.UserConstants;
import org.olat.core.util.CodeHelper;
import org.olat.core.util.StringHelper;
import org.olat.course.CourseFactory;
import org.olat.course.ICourse;
import org.olat.course.assessment.AssessmentHelper;
import org.olat.course.nodes.GTACourseNode;
import org.olat.course.nodes.gta.GTAManager;
import org.olat.course.nodes.gta.ui.GroupAssessmentModel.Cols;
import org.olat.course.run.environment.CourseEnvironment;
import org.olat.course.run.scoring.ScoreEvaluation;
import org.olat.course.run.userview.UserCourseEnvironment;
import org.olat.group.BusinessGroup;
import org.olat.group.BusinessGroupService;
import org.olat.modules.assessment.AssessmentEntry;
import org.olat.modules.assessment.model.AssessmentEntryStatus;
import org.olat.user.UserManager;
import org.olat.user.propertyhandlers.UserPropertyHandler;
import org.springframework.beans.factory.annotation.Autowired;
/**
*
* Initial date: 14.02.2014<br>
* @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
*
*/
public class GroupAssessmentController extends FormBasicController {
private static final String[] userVisibilityKeys = new String[]{ "visible", "hidden" };
private static final String[] onKeys = new String[] { "on" };
private static final String[] onValues = new String[] { "" };
private FlexiTableElement table;
private GroupAssessmentModel model;
private FormLink saveAndDoneButton;
private TextElement groupScoreEl, groupCommentEl;
private SingleSelection userVisibilityEl;
private MultipleSelectionElement groupPassedEl, applyToAllEl;
private EditCommentController editCommentCtrl;
private CloseableCalloutWindowController commentCalloutCtrl;
private final boolean isAdministrativeUser;
private final List<UserPropertyHandler> userPropertyHandlers;
private Float cutValue;
private final boolean withScore, withPassed, withComment;
private final GTACourseNode gtaNode;
private final CourseEnvironment courseEnv;
private final BusinessGroup assessedGroup;
@Autowired
private GTAManager gtaManager;
@Autowired
private UserManager userManager;
@Autowired
private BaseSecurityModule securityModule;
@Autowired
private BusinessGroupService businessGroupService;
private final List<Long> duplicateMemberKeys;
public GroupAssessmentController(UserRequest ureq, WindowControl wControl,
CourseEnvironment courseEnv, GTACourseNode courseNode, BusinessGroup assessedGroup) {
super(ureq, wControl, "assessment_per_group");
this.gtaNode = courseNode;
this.courseEnv = courseEnv;
this.assessedGroup = assessedGroup;
withScore = courseNode.hasScoreConfigured();
withPassed = courseNode.hasPassedConfigured();
if(withPassed) {
cutValue = courseNode.getCutValueConfiguration();
}
withComment = courseNode.hasCommentConfigured();
Roles roles = ureq.getUserSession().getRoles();
isAdministrativeUser = securityModule.isUserAllowedAdminProps(roles);
userPropertyHandlers = userManager.getUserPropertyHandlersFor(GTACoachedGroupGradingController.USER_PROPS_ID, isAdministrativeUser);
setTranslator(userManager.getPropertyHandlerTranslator(getTranslator()));
List<IdentityRef> duplicates = gtaManager.getDuplicatedMemberships(courseNode);
duplicateMemberKeys = new ArrayList<>(duplicates.size());
for(IdentityRef duplicate:duplicates) {
duplicateMemberKeys.add(duplicate.getKey());
}
initForm(ureq);
ModelInfos modelInfos = loadModel();
updateGUI(modelInfos);
}
@Override
protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
FormLayoutContainer groupGradingCont = FormLayoutContainer.createDefaultFormLayout("groupGrading", getTranslator());
groupGradingCont.setRootForm(mainForm);
formLayout.add(groupGradingCont);
applyToAllEl = uifactory.addCheckboxesHorizontal("applytoall", "group.apply.toall", groupGradingCont, onKeys, onValues);
applyToAllEl.addActionListener(FormEvent.ONCHANGE);
applyToAllEl.setElementCssClass("o_sel_course_gta_apply_to_all");
if(withPassed && cutValue == null) {
groupPassedEl = uifactory.addCheckboxesHorizontal("checkgroup", "group.passed", groupGradingCont, onKeys, onValues);
groupPassedEl.setElementCssClass("o_sel_course_gta_group_passed");
}
if(withScore) {
String pointVal = "";
groupScoreEl = uifactory.addTextElement("pointgroup", "group.score", 5, pointVal, groupGradingCont);
groupScoreEl.setElementCssClass("o_sel_course_gta_group_score");
}
if(withComment) {
String comment = "";
groupCommentEl = uifactory.addTextAreaElement("usercomment", "group.comment", 2500, 5, 40, true, comment, groupGradingCont);
groupCommentEl.setElementCssClass("o_sel_course_gta_group_comment");
}
if(withPassed || withScore || withComment) {
String[] userVisibilityValues = new String[]{ translate("user.visibility.visible"), translate("user.visibility.hidden") };
userVisibilityEl = uifactory.addRadiosHorizontal("user.visibility", "user.visibility", groupGradingCont, userVisibilityKeys, userVisibilityValues);
}
FlexiTableColumnModel columnsModel = FlexiTableDataModelFactory.createFlexiTableColumnModel();
if(isAdministrativeUser) {
columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Cols.username.i18nKey(), Cols.username.ordinal()));
}
int i=0;
for (UserPropertyHandler userPropertyHandler : userPropertyHandlers) {
int colIndex = GTACoachedGroupGradingController.USER_PROPS_OFFSET + i++;
if (userPropertyHandler == null) continue;
String propName = userPropertyHandler.getName();
boolean visible = userManager.isMandatoryUserProperty(GTACoachedGroupGradingController.USER_PROPS_ID , userPropertyHandler);
if(visible) {
FlexiColumnModel col;
if(UserConstants.FIRSTNAME.equals(propName)
|| UserConstants.LASTNAME.equals(propName)) {
col = new DefaultFlexiColumnModel(userPropertyHandler.i18nColumnDescriptorLabelKey(),
colIndex, userPropertyHandler.getName(), true, propName,
new StaticFlexiCellRenderer(userPropertyHandler.getName(), new TextFlexiCellRenderer()));
} else {
col = new DefaultFlexiColumnModel(true, userPropertyHandler.i18nColumnDescriptorLabelKey(), colIndex, true, propName);
}
columnsModel.addFlexiColumnModel(col);
}
}
if(withPassed && cutValue == null) {
columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Cols.passedEl.i18nKey(), Cols.passedEl.ordinal()));
}
if(withScore) {
columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Cols.scoreEl.i18nKey(), Cols.scoreEl.ordinal()));
}
if(withComment) {
columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Cols.commentEl.i18nKey(), Cols.commentEl.ordinal()));
}
model = new GroupAssessmentModel(gtaNode, userPropertyHandlers, getLocale(), columnsModel);
table = uifactory.addTableElement(getWindowControl(), "group-list", model, getTranslator(), formLayout);
table.setCustomizeColumns(true);
table.setEditMode(true);
table.setAndLoadPersistedPreferences(ureq, "gtagroup-assessment");
FormLayoutContainer buttonsCont = FormLayoutContainer.createButtonLayout("buttons", getTranslator());
formLayout.add(buttonsCont);
uifactory.addFormSubmitButton("save", buttonsCont);
saveAndDoneButton = uifactory.addFormLink("save.done", buttonsCont, Link.BUTTON);
uifactory.addFormCancelButton("cancel", buttonsCont, ureq, getWindowControl());
}
private void updateGUI(ModelInfos modelInfos) {
if(modelInfos.isSame()) {
applyToAllEl.select(onKeys[0], true);
table.setVisible(false);
if(groupPassedEl != null) {
groupPassedEl.setVisible(true);
Boolean passed = modelInfos.getPassed();
groupPassedEl.select(onKeys[0], passed != null && passed.booleanValue());
}
if(groupScoreEl != null) {
groupScoreEl.setVisible(true);
Float score = modelInfos.getScore();
if(score != null) {
String scoreVal = AssessmentHelper.getRoundedScore(score);
groupScoreEl.setValue(scoreVal);
} else {
groupScoreEl.setValue("");
}
}
if(groupCommentEl != null) {
groupCommentEl.setVisible(true);
String comment = modelInfos.getComment();
if(comment != null) {
groupCommentEl.setValue(comment);
}
}
if(userVisibilityEl != null) {
userVisibilityEl.setVisible(true);
if(modelInfos.getUserVisible() == null || modelInfos.getUserVisible().booleanValue()) {
userVisibilityEl.select(userVisibilityKeys[0], true);
} else {
userVisibilityEl.select(userVisibilityKeys[1], true);
}
}
} else {
applyToAllEl.select(onKeys[0], false);
table.setVisible(true);
if(groupPassedEl != null) {
groupPassedEl.setVisible(false);
}
if(groupScoreEl != null) {
groupScoreEl.setVisible(false);
}
if(groupCommentEl != null) {
groupCommentEl.setVisible(false);
}
}
if(StringHelper.containsNonWhitespace(modelInfos.getDuplicates())) {
String warning = translate("error.duplicate.memberships", new String[]{ gtaNode.getShortTitle(), modelInfos.getDuplicates()});
flc.contextPut("duplicateWarning", warning);
} else {
flc.contextRemove("duplicateWarning");
}
}
/**
*
* @return True if all results are the same
*/
private ModelInfos loadModel() {
//load participants, load datas
ICourse course = CourseFactory.loadCourse(courseEnv.getCourseResourceableId());
List<Identity> identities = businessGroupService.getMembers(assessedGroup, GroupRoles.participant.name());
Map<Identity, AssessmentEntry> identityToEntryMap = new HashMap<>();
List<AssessmentEntry> entries = course.getCourseEnvironment()
.getAssessmentManager().getAssessmentEntries(assessedGroup, gtaNode);
for(AssessmentEntry entry:entries) {
identityToEntryMap.put(entry.getIdentity(), entry);
}
int count = 0;
boolean same = true;
StringBuilder duplicateWarning = new StringBuilder();
Float scoreRef = null;
Boolean passedRef = null;
String commentRef = null;
Boolean userVisibleRef = null;
List<AssessmentRow> rows = new ArrayList<>(identities.size());
for(Identity identity:identities) {
AssessmentEntry entry = identityToEntryMap.get(identity);
ScoreEvaluation scoreEval = null;
if(withScore || withPassed) {
scoreEval = gtaNode.getUserScoreEvaluation(entry);
if (scoreEval == null) {
scoreEval = ScoreEvaluation.EMPTY_EVALUATION;
}
}
String comment = null;
if(withComment && entry != null) {
comment = entry.getComment();
}
boolean duplicate = duplicateMemberKeys.contains(identity.getKey());
if(duplicate) {
if(duplicateWarning.length() > 0) duplicateWarning.append(", ");
duplicateWarning.append(StringHelper.escapeHtml(userManager.getUserDisplayName(identity)));
}
AssessmentRow row = new AssessmentRow(identity, duplicate);
rows.add(row);
if(withScore) {
Float score = scoreEval.getScore();
String pointVal = AssessmentHelper.getRoundedScore(score);
TextElement pointEl = uifactory.addTextElement("point" + count, null, 5, pointVal, flc);
pointEl.setDisplaySize(5);
row.setScoreEl(pointEl);
if(count == 0) {
scoreRef = score;
} else if(!same(scoreRef, score)) {
same = false;
}
}
if(withPassed && cutValue == null) {
Boolean passed = scoreEval.getPassed();
MultipleSelectionElement passedEl = uifactory.addCheckboxesHorizontal("check" + count, null, flc, onKeys, onValues);
if(passed != null && passed.booleanValue()) {
passedEl.select(onKeys[0], passed.booleanValue());
}
row.setPassedEl(passedEl);
if(count == 0) {
passedRef = passed;
} else if(!same(passedRef, passed)) {
same = false;
}
}
if(withComment) {
FormLink commentLink = uifactory.addFormLink("comment-" + CodeHelper.getRAMUniqueID(), "comment", "comment", null, flc, Link.LINK);
if(StringHelper.containsNonWhitespace(comment)) {
commentLink.setIconLeftCSS("o_icon o_icon_comments");
} else {
commentLink.setIconLeftCSS("o_icon o_icon_comments_none");
}
commentLink.setUserObject(row);
row.setComment(comment);
row.setCommentEditLink(commentLink);
if(count == 0) {
commentRef = comment;
} else if(!same(commentRef, comment)) {
same = false;
}
}
if(withScore || withPassed || withPassed) {
Boolean userVisible = scoreEval.getUserVisible();
if(userVisible == null) {
userVisible = Boolean.TRUE;
}
if(count == 0) {
userVisibleRef = userVisible;
} else if(!same(userVisibleRef, userVisible)) {
same = false;
}
}
count++;
}
model.setObjects(rows);
table.reset();
return new ModelInfos(same, scoreRef, passedRef, commentRef, userVisibleRef, duplicateWarning.toString());
}
private boolean same(Object reference, Object value) {
boolean same = true;
if((reference == null && value != null)
|| (reference != null && value == null)
|| (value != null && reference != null && !value.equals(reference))) {
same = false;
}
return same;
}
@Override
protected void doDispose() {
//
}
@Override
protected void event(UserRequest ureq, Controller source, Event event) {
if(commentCalloutCtrl == source) {
cleanUp();
} else if(editCommentCtrl == source) {
if(event == Event.DONE_EVENT) {
table.reset();
}
commentCalloutCtrl.deactivate();
cleanUp();
}
super.event(ureq, source, event);
}
private void cleanUp() {
removeAsListenerAndDispose(commentCalloutCtrl);
removeAsListenerAndDispose(editCommentCtrl);
commentCalloutCtrl = null;
editCommentCtrl = null;
}
@Override
protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
if(applyToAllEl == source) {
boolean allGroup = applyToAllEl.isAtLeastSelected(1);
table.setVisible(!allGroup);
if(groupPassedEl != null) {
groupPassedEl.setVisible(allGroup);
}
if(groupScoreEl != null) {
groupScoreEl.setVisible(allGroup);
}
if(groupCommentEl != null) {
groupCommentEl.setVisible(allGroup);
}
} else if(source == saveAndDoneButton) {
if(validateFormLogic(ureq)) {
applyChanges(true);
fireEvent(ureq, Event.CLOSE_EVENT);
}
} else if(source instanceof FormLink) {
FormLink link = (FormLink)source;
if("comment".equals(link.getCmd())) {
AssessmentRow row = (AssessmentRow)link.getUserObject();
doEditComment(ureq, row);
}
}
super.formInnerEvent(ureq, source, event);
}
@Override
protected boolean validateFormLogic(UserRequest ureq) {
boolean allOk = true;
if(withScore) {
if(applyToAllEl.isAtLeastSelected(1)) {
allOk &= validateScore(groupScoreEl);
} else {
List<AssessmentRow> rows = model.getObjects();
for(AssessmentRow row:rows) {
allOk &= validateScore(row.getScoreEl());
}
}
}
return allOk & super.validateFormLogic(ureq);
}
private boolean validateScore(TextElement scoreEl) {
boolean allOk = true;
scoreEl.clearError();
String value = scoreEl.getValue();
if(StringHelper.containsNonWhitespace(value)) {
try {
float score = Float.parseFloat(value);
if(score < 0.0f) {
scoreEl.setErrorKey("error.score.format", null);
allOk &= false;
}
} catch (NumberFormatException e) {
scoreEl.setErrorKey("error.score.format", null);
allOk &= false;
}
}
return allOk;
}
@Override
protected void formOK(UserRequest ureq) {
applyChanges(false);
fireEvent(ureq, Event.DONE_EVENT);
}
private void applyChanges(boolean setAsDone) {
List<AssessmentRow> rows = model.getObjects();
boolean userVisible = userVisibilityEl.isOneSelected() && userVisibilityEl.isSelected(0);
if(applyToAllEl.isAtLeastSelected(1)) {
applyChangesForTheWholeGroup(rows, setAsDone, userVisible);
} else {
applyChangesForEvenryMemberGroup(rows, setAsDone, userVisible);
}
}
private void applyChangesForEvenryMemberGroup(List<AssessmentRow> rows, boolean setAsDone, boolean userVisible) {
ICourse course = CourseFactory.loadCourse(courseEnv.getCourseResourceableId());
for(AssessmentRow row:rows) {
UserCourseEnvironment userCourseEnv = row.getUserCourseEnvironment(course);
Float score = null;
if(withScore) {
String value = row.getScoreEl().getValue();
if(StringHelper.containsNonWhitespace(value)) {
score = Float.parseFloat(value);
}
}
Boolean passed = null;
if(withPassed) {
if(cutValue == null) {
passed = row.getPassedEl().isSelected(0);
} else if(score != null) {
passed = (score.floatValue() >= cutValue.floatValue()) ? Boolean.TRUE : Boolean.FALSE;
}
}
ScoreEvaluation newScoreEval;
if(setAsDone) {
newScoreEval = new ScoreEvaluation(score, passed, AssessmentEntryStatus.done, userVisible, true, null);
} else {
newScoreEval = new ScoreEvaluation(score, passed, null, userVisible, null, null);
}
gtaNode.updateUserScoreEvaluation(newScoreEval, userCourseEnv, getIdentity(), false);
if(withComment) {
String comment = row.getComment();
if(StringHelper.containsNonWhitespace(comment)) {
gtaNode.updateUserUserComment(comment, userCourseEnv, getIdentity());
}
}
}
}
private void applyChangesForTheWholeGroup(List<AssessmentRow> rows, boolean setAsDone, boolean userVisible) {
ICourse course = CourseFactory.loadCourse(courseEnv.getCourseResourceableId());
Float score = null;
if(withScore) {
String scoreValue = groupScoreEl.getValue();
if(StringHelper.containsNonWhitespace(scoreValue)) {
score = Float.parseFloat(scoreValue);
}
}
Boolean passed = null;
if(withPassed) {
if(cutValue == null) {
passed = groupPassedEl.isSelected(0);
} else if(score != null) {
passed = (score.floatValue() >= cutValue.floatValue()) ? Boolean.TRUE : Boolean.FALSE;
}
}
for(AssessmentRow row:rows) {
UserCourseEnvironment userCourseEnv = row.getUserCourseEnvironment(course);
ScoreEvaluation newScoreEval;
if(setAsDone) {
newScoreEval = new ScoreEvaluation(score, passed, AssessmentEntryStatus.done, userVisible, true, null);
} else {
newScoreEval = new ScoreEvaluation(score, passed, null, userVisible, null, null);
}
gtaNode.updateUserScoreEvaluation(newScoreEval, userCourseEnv, getIdentity(), false);
}
if(withComment) {
String comment = groupCommentEl.getValue();
if(StringHelper.containsNonWhitespace(comment)) {
for(AssessmentRow row:rows) {
UserCourseEnvironment userCourseEnv = row.getUserCourseEnvironment(course);
gtaNode.updateUserUserComment(comment, userCourseEnv, getIdentity());
}
}
}
}
@Override
protected void formCancelled(UserRequest ureq) {
fireEvent(ureq, Event.CANCELLED_EVENT);
}
private void doEditComment(UserRequest ureq, AssessmentRow row) {
removeAsListenerAndDispose(commentCalloutCtrl);
OLATResourceable courseOres = courseEnv.getCourseGroupManager().getCourseResource();
editCommentCtrl = new EditCommentController(ureq, getWindowControl(), courseOres, gtaNode, row);
listenTo(editCommentCtrl);
commentCalloutCtrl = new CloseableCalloutWindowController(ureq, getWindowControl(),
editCommentCtrl.getInitialComponent(), row.getCommentEditLink().getFormDispatchId(),
"", true, "");
listenTo(commentCalloutCtrl);
commentCalloutCtrl.activate();
}
public static class ModelInfos {
private final String duplicates;
private final boolean same;
private final Float score;
private final Boolean passed;
private final String comment;
private final Boolean userVisible;
public ModelInfos(boolean same, Float score, Boolean passed, String comment, Boolean userVisible, String duplicates) {
this.same = same;
this.score = score;
this.passed = passed;
this.comment = comment;
this.userVisible = userVisible;
this.duplicates = duplicates;
}
public boolean isSame() {
return same;
}
public Float getScore() {
return score;
}
public Boolean getPassed() {
return passed;
}
public String getComment() {
return comment;
}
public Boolean getUserVisible() {
return userVisible;
}
public String getDuplicates() {
return duplicates;
}
}
}