diff --git a/src/main/java/org/olat/collaboration/CollaborationTools.java b/src/main/java/org/olat/collaboration/CollaborationTools.java index 7335af2921113daa7c33204cf256a8305b15ffe0..6e1da8f06550055b1c2096d49f5f60fbc2b3788d 100644 --- a/src/main/java/org/olat/collaboration/CollaborationTools.java +++ b/src/main/java/org/olat/collaboration/CollaborationTools.java @@ -594,8 +594,6 @@ public class CollaborationTools implements Serializable { portfolioService.updateBinderUserInformations(binder, ureq.getIdentity()); BinderSecurityCallback secCallback = BinderSecurityCallbackFactory.getCallbackForBusinessGroup(); BinderController binderCtrl = new BinderController(ureq, wControl, stackPanel, secCallback, binder, BinderConfiguration.createBusinessGroupConfig()); - List<ContextEntry> entries = BusinessControlFactory.getInstance().createCEListFromResourceType("Toc"); - binderCtrl.activate(ureq, entries, null); ctrl = binderCtrl; } else { PortfolioStructureMap map = (PortfolioStructureMap) CoreSpringFactory.getImpl(EPFrontendManager.class) diff --git a/src/main/java/org/olat/course/nodes/iq/QTI21AssessmentRunController.java b/src/main/java/org/olat/course/nodes/iq/QTI21AssessmentRunController.java index 2d713ea0357bed1007115e82b0b4eb68e78151ad..d424995e395d50eed3b6f1ea43441a3698ce41bc 100644 --- a/src/main/java/org/olat/course/nodes/iq/QTI21AssessmentRunController.java +++ b/src/main/java/org/olat/course/nodes/iq/QTI21AssessmentRunController.java @@ -42,7 +42,6 @@ import org.olat.core.gui.control.generic.iframe.IFrameDisplayController; import org.olat.core.gui.media.MediaResource; import org.olat.core.gui.media.NotFoundMediaResource; import org.olat.core.gui.translator.Translator; -import org.olat.core.id.Identity; import org.olat.core.id.OLATResourceable; import org.olat.core.logging.activity.ThreadLocalUserActivityLogger; import org.olat.core.util.Formatter; @@ -227,6 +226,7 @@ public class QTI21AssessmentRunController extends BasicController implements Gen mainVC.contextPut("hasPassedValue", (scoreEval.getPassed() == null ? Boolean.FALSE : Boolean.TRUE)); mainVC.contextPut("passed", scoreEval.getPassed()); mainVC.contextPut("attempts", attempts); //at least one attempt + mainVC.contextPut("showChangeLog", Boolean.TRUE); } } else if(courseNode instanceof IQTESTCourseNode) { IQTESTCourseNode testCourseNode = (IQTESTCourseNode)courseNode; @@ -238,6 +238,7 @@ public class QTI21AssessmentRunController extends BasicController implements Gen mainVC.contextPut("passed", Boolean.FALSE); mainVC.contextPut("comment", null); mainVC.contextPut("attempts", 0); + mainVC.contextPut("showChangeLog", Boolean.FALSE); } else { Boolean passed = assessmentEntry.getPassed(); //block if test passed (and config set to check it) @@ -248,7 +249,6 @@ public class QTI21AssessmentRunController extends BasicController implements Gen } mainVC.contextPut("blockAfterSuccess", blocked); - Identity identity = userCourseEnv.getIdentityEnvironment().getIdentity(); boolean resultsVisible = assessmentEntry.getUserVisibility() == null || assessmentEntry.getUserVisibility().booleanValue(); mainVC.contextPut("resultsVisible", resultsVisible); mainVC.contextPut("score", AssessmentHelper.getRoundedScore(assessmentEntry.getScore())); @@ -262,11 +262,8 @@ public class QTI21AssessmentRunController extends BasicController implements Gen } Integer attempts = assessmentEntry.getAttempts(); mainVC.contextPut("attempts", attempts == null ? new Integer(0) : attempts); - - if(!anonym) { - UserNodeAuditManager am = userCourseEnv.getCourseEnvironment().getAuditManager(); - mainVC.contextPut("log", am.getUserNodeLog(courseNode, identity)); - } + boolean showChangelog = (!anonym && resultsVisible && isResultVisible(config)); + mainVC.contextPut("showChangeLog", showChangelog); if(deliveryOptions.isDigitalSignature()) { AssessmentTestSession session = qtiService.getAssessmentTestSession(assessmentEntry.getAssessmentId()); @@ -305,7 +302,7 @@ public class QTI21AssessmentRunController extends BasicController implements Gen } /** - * WARNING! The variables showResultsOnHomePage, showResultsVisible and showChangelog are not used + * WARNING! The variables showResultsOnHomePage and showResultsVisible are not used * in the velocity template and the CONFIG_KEY_RESULT_ON_HOME_PAGE is not editable * in the configuration of the course element for QTI 2.1!!!! * @@ -345,9 +342,10 @@ public class QTI21AssessmentRunController extends BasicController implements Gen } } - UserNodeAuditManager am = userCourseEnv.getCourseEnvironment().getAuditManager(); - mainVC.contextPut("log", am.getUserNodeLog(courseNode, getIdentity())); - mainVC.contextPut("showChangelog", showResultsOnHomePage); + if(!anonym) { + UserNodeAuditManager am = userCourseEnv.getCourseEnvironment().getAuditManager(); + mainVC.contextPut("log", am.getUserNodeLog(courseNode, getIdentity())); + } } private boolean isResultVisible(ModuleConfiguration modConfig) { diff --git a/src/main/java/org/olat/course/nodes/iq/_content/assessment_run.html b/src/main/java/org/olat/course/nodes/iq/_content/assessment_run.html index 8411566bb23db500a64b027abb000fc0e2c58563..6305ce0af905135f64a2487a1f004b66136f2a62 100644 --- a/src/main/java/org/olat/course/nodes/iq/_content/assessment_run.html +++ b/src/main/java/org/olat/course/nodes/iq/_content/assessment_run.html @@ -169,8 +169,7 @@ <div class="o_button_group">$r.render("start")</div> #end - -#if($r.isNotNull($log)) ##&& $showChangelog (in QTI 1.2) +#if($r.isNotNull($log) && $r.isNotNull($showChangeLog) && $showChangeLog) <div class="o_box"> #o_togglebox_start("o_course_run_log" $r.translate("log.title")) <pre class="small">$log</pre> diff --git a/src/main/java/org/olat/group/manager/BusinessGroupServiceImpl.java b/src/main/java/org/olat/group/manager/BusinessGroupServiceImpl.java index de911c432189951d27548959a9c87841194a9733..23bbd6bfc4cda6ac856cec8f8fc99e25a822cead 100644 --- a/src/main/java/org/olat/group/manager/BusinessGroupServiceImpl.java +++ b/src/main/java/org/olat/group/manager/BusinessGroupServiceImpl.java @@ -1475,7 +1475,7 @@ public class BusinessGroupServiceImpl implements BusinessGroupService, UserDataD mailing = new MailPackage(true); } - BusinessGroupMailing.sendEmail(ureqIdentity, firstWaitingListIdentity, group, MailType.graduateFromWaitingListToParticpant, mailing); + BusinessGroupMailing.sendEmail(null, firstWaitingListIdentity, group, MailType.graduateFromWaitingListToParticpant, mailing); counter++; } } diff --git a/src/main/java/org/olat/group/ui/BGMailHelper.java b/src/main/java/org/olat/group/ui/BGMailHelper.java index e29acffcc06146506e5b41d76c0afbc8ae32bdad..c7d68019f25aa182e8d0306ee527c969cd184ad9 100644 --- a/src/main/java/org/olat/group/ui/BGMailHelper.java +++ b/src/main/java/org/olat/group/ui/BGMailHelper.java @@ -192,13 +192,18 @@ public class BGMailHelper { */ private static MailTemplate createMailTemplate(BusinessGroupShort group, Identity actor, String subjectKey, String bodyKey) { // get some data about the actor and fetch the translated subject / body via i18n module - String[] bodyArgs = new String[] { - actor.getUser().getProperty(UserConstants.FIRSTNAME, null), - actor.getUser().getProperty(UserConstants.LASTNAME, null), - actor.getUser().getProperty(UserConstants.EMAIL, null), - actor.getUser().getProperty(UserConstants.EMAIL, null)// 2x for compatibility with old i18m properties - }; - Locale locale = I18nManager.getInstance().getLocaleOrDefault(actor.getUser().getPreferences().getLanguage()); + String[] bodyArgs = null; + String lang = null; + if (actor != null) { + bodyArgs = new String[] { + actor.getUser().getProperty(UserConstants.FIRSTNAME, null), + actor.getUser().getProperty(UserConstants.LASTNAME, null), + actor.getUser().getProperty(UserConstants.EMAIL, null), + actor.getUser().getProperty(UserConstants.EMAIL, null)// 2x for compatibility with old i18m properties + }; + lang = actor.getUser().getPreferences().getLanguage(); + } + Locale locale = I18nManager.getInstance().getLocaleOrDefault(lang); Translator trans = Util.createPackageTranslator(BGMailHelper.class, locale, Util.createPackageTranslator(BusinessGroupListController.class, locale)); String subject = trans.translate(subjectKey); diff --git a/src/main/java/org/olat/group/ui/run/BusinessGroupMainRunController.java b/src/main/java/org/olat/group/ui/run/BusinessGroupMainRunController.java index f52a68026d325b72089cc4b8ad965eed7536976e..b7fc5c8b69dbbd2ca3cf9fb6fc892ee91f7e32a5 100644 --- a/src/main/java/org/olat/group/ui/run/BusinessGroupMainRunController.java +++ b/src/main/java/org/olat/group/ui/run/BusinessGroupMainRunController.java @@ -790,6 +790,9 @@ public class BusinessGroupMainRunController extends MainLayoutBasicController im listenTo(collabToolCtr); toolbarPanel.popUpToRootController(ureq); toolbarPanel.pushController("Portfolio", collabToolCtr); + + List<ContextEntry> entries = BusinessControlFactory.getInstance().createCEListFromResourceType("Toc"); + ((Activateable2)collabToolCtr).activate(ureq, entries, null); return (Activateable2)collabToolCtr; } diff --git a/src/main/java/org/olat/ims/qti21/QTI21StatisticsManager.java b/src/main/java/org/olat/ims/qti21/QTI21StatisticsManager.java index 371f237ce4067a8aea8a067b7496980b55fe371e..e60bd2e7755abb49bd41d7d4f64b1fd34680bc0e 100644 --- a/src/main/java/org/olat/ims/qti21/QTI21StatisticsManager.java +++ b/src/main/java/org/olat/ims/qti21/QTI21StatisticsManager.java @@ -26,6 +26,7 @@ import org.olat.ims.qti.statistics.model.StatisticAssessment; import org.olat.ims.qti.statistics.model.StatisticsItem; import org.olat.ims.qti21.model.QTI21StatisticSearchParams; import org.olat.ims.qti21.model.statistics.AbstractTextEntryInteractionStatistics; +import org.olat.ims.qti21.model.statistics.AssessmentItemStatistic; import org.olat.ims.qti21.model.statistics.ChoiceStatistics; import org.olat.ims.qti21.model.statistics.HotspotChoiceStatistics; import org.olat.ims.qti21.model.statistics.KPrimStatistics; @@ -37,6 +38,7 @@ import uk.ac.ed.ph.jqtiplus.node.item.interaction.HotspotInteraction; import uk.ac.ed.ph.jqtiplus.node.item.interaction.HottextInteraction; import uk.ac.ed.ph.jqtiplus.node.item.interaction.MatchInteraction; import uk.ac.ed.ph.jqtiplus.node.item.interaction.TextEntryInteraction; +import uk.ac.ed.ph.jqtiplus.resolution.ResolvedAssessmentTest; /** * @@ -86,5 +88,15 @@ public interface QTI21StatisticsManager { public List<AbstractTextEntryInteractionStatistics> getTextEntryInteractionsStatistic(String itemRefIdent, AssessmentItem item, List<TextEntryInteraction> interactions, QTI21StatisticSearchParams searchParams); + + /** + * + * @param items + * @param searchParams + * @param numOfParticipants + * @return + */ + public List<AssessmentItemStatistic> getStatisticPerItem(ResolvedAssessmentTest resolvedAssessmentTest, QTI21StatisticSearchParams searchParams, + double numOfParticipants); } diff --git a/src/main/java/org/olat/ims/qti21/manager/QTI21StatisticsManagerImpl.java b/src/main/java/org/olat/ims/qti21/manager/QTI21StatisticsManagerImpl.java index ce14890587a932ab8677c5a9b4d2686b4aef49e2..7d5d6457e6b7f544fe4dbee6fad649575e121b3f 100644 --- a/src/main/java/org/olat/ims/qti21/manager/QTI21StatisticsManagerImpl.java +++ b/src/main/java/org/olat/ims/qti21/manager/QTI21StatisticsManagerImpl.java @@ -40,6 +40,7 @@ import org.olat.ims.qti.statistics.model.StatisticsItem; import org.olat.ims.qti21.QTI21StatisticsManager; import org.olat.ims.qti21.model.QTI21StatisticSearchParams; import org.olat.ims.qti21.model.statistics.AbstractTextEntryInteractionStatistics; +import org.olat.ims.qti21.model.statistics.AssessmentItemStatistic; import org.olat.ims.qti21.model.statistics.ChoiceStatistics; import org.olat.ims.qti21.model.statistics.HotspotChoiceStatistics; import org.olat.ims.qti21.model.statistics.KPrimStatistics; @@ -67,6 +68,9 @@ import uk.ac.ed.ph.jqtiplus.node.item.interaction.content.Hottext; import uk.ac.ed.ph.jqtiplus.node.item.interaction.graphic.HotspotChoice; import uk.ac.ed.ph.jqtiplus.node.item.response.declaration.MapEntry; import uk.ac.ed.ph.jqtiplus.node.item.response.declaration.ResponseDeclaration; +import uk.ac.ed.ph.jqtiplus.node.test.AssessmentItemRef; +import uk.ac.ed.ph.jqtiplus.resolution.ResolvedAssessmentItem; +import uk.ac.ed.ph.jqtiplus.resolution.ResolvedAssessmentTest; import uk.ac.ed.ph.jqtiplus.types.Identifier; import uk.ac.ed.ph.jqtiplus.utils.QueryUtils; import uk.ac.ed.ph.jqtiplus.value.BaseType; @@ -637,6 +641,120 @@ public class QTI21StatisticsManagerImpl implements QTI21StatisticsManager { return datas; } + @Override + public List<AssessmentItemStatistic> getStatisticPerItem(ResolvedAssessmentTest resolvedAssessmentTest, QTI21StatisticSearchParams searchParams, double numOfParticipants) { + StringBuilder sb = new StringBuilder(); + sb.append("select isession.assessmentItemIdentifier, isession.score, isession.manualScore, count(*) from qtiassessmentitemsession isession") + .append(" inner join isession.assessmentTestSession asession"); + decorateRSet(sb, searchParams, true); + sb.append(" and isession.duration > 0") + .append(" group by isession.assessmentItemIdentifier, isession.score, isession.manualScore"); + + TypedQuery<Object[]> query = dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), Object[].class); + decorateRSetQuery(query, searchParams); + List<Object[]> results = query.getResultList(); + if(results.isEmpty()) { + return new ArrayList<>(); + } + + Map<String,AssessmentItemRef> itemMap = new HashMap<>(); + for(AssessmentItemRef itemRef:resolvedAssessmentTest.getAssessmentItemRefs()) { + itemMap.put(itemRef.getIdentifier().toString(), itemRef); + } + + Map<String, AssessmentItemHelper> identifierToHelpers = new HashMap<>(); + for(Object[] result:results) { + int pos = 0; + String identifier = PersistenceHelper.extractString(result, pos++); + BigDecimal score = (BigDecimal)result[pos++]; + BigDecimal manualScore = (BigDecimal)result[pos++]; + Long count = PersistenceHelper.extractLong(result, pos++); + if(score == null || identifier == null || count == null) { + continue; + } + + AssessmentItemHelper helper = identifierToHelpers.get(identifier); + if(helper == null) { + AssessmentItemRef itemRef = itemMap.get(identifier); + if(itemRef == null) { + continue; + } + ResolvedAssessmentItem item = resolvedAssessmentTest.getResolvedAssessmentItem(itemRef); + if(item == null) { + continue; + } + helper = new AssessmentItemHelper(item.getRootNodeLookup().extractIfSuccessful()); + identifierToHelpers.put(identifier, helper); + } + + helper.addCount(count); + if(manualScore != null) { + helper.addTotalScore(count, manualScore); + } else { + helper.addTotalScore(count, score); + } + + if(helper.getMaxScore() != null) { + double maxValue = helper.getMaxScore().doubleValue(); + if(Math.abs(score.doubleValue() - maxValue) < 0.0001) { + helper.addCorrectAnswers(count); + } + } + } + + List<AssessmentItemStatistic> statistics = new ArrayList<>(identifierToHelpers.size()); + for(AssessmentItemHelper helper:identifierToHelpers.values()) { + long numOfAnswersItem = helper.count; + long numOfCorrectAnswers = helper.countCorrectAnswers; + double average = (helper.totalScore / helper.count); + double averageParticipants = (helper.totalScore / numOfParticipants); + statistics.add(new AssessmentItemStatistic(helper.getAssessmentItem(), average, averageParticipants, numOfAnswersItem, numOfCorrectAnswers)); + } + return statistics; + } + + public static class AssessmentItemHelper { + private long count = 0l; + private double totalScore = 0.0d; + private Double maxScore; + private long countCorrectAnswers = 0; + private final AssessmentItem assessmentItem; + + public AssessmentItemHelper(AssessmentItem assessmentItem) { + this.assessmentItem = assessmentItem; + if(assessmentItem != null) { + maxScore = QtiNodesExtractor.extractMaxScore(assessmentItem); + } + } + + public AssessmentItem getAssessmentItem() { + return assessmentItem; + } + + public Double getMaxScore() { + return maxScore; + } + + public void addTotalScore(Long numOfAnswers, BigDecimal score) { + if(numOfAnswers != null && score != null) { + totalScore += (numOfAnswers.doubleValue() * score.doubleValue()); + } + } + + public void addCount(Long toAdd) { + if(toAdd != null) { + count += toAdd.longValue(); + } + } + + public void addCorrectAnswers(Long toAdd) { + if(toAdd != null) { + countCorrectAnswers += toAdd.longValue(); + } + } + } + public static class RawData { private final Long itemSessionKey; diff --git a/src/main/java/org/olat/ims/qti21/model/statistics/AssessmentItemStatistic.java b/src/main/java/org/olat/ims/qti21/model/statistics/AssessmentItemStatistic.java new file mode 100644 index 0000000000000000000000000000000000000000..4f30d05518793e1fca9ef84df9a4bb9a2e13b555 --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/model/statistics/AssessmentItemStatistic.java @@ -0,0 +1,76 @@ +/** + * <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.ims.qti21.model.statistics; + +import uk.ac.ed.ph.jqtiplus.node.item.AssessmentItem; + +/** + * + * Initial date: 9 mai 2017<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class AssessmentItemStatistic { + + private final AssessmentItem assessmentItem; + private final double averageScore; + private final double averageScoreAllParticipants; + private final long numOfAnswers; + private final long numOfCorrectAnswers; + + public AssessmentItemStatistic(AssessmentItem assessmentItem, double averageScore, double averageScoreAllParticipants, + long numOfAnswers, long numOfCorrectAnswers) { + this.assessmentItem = assessmentItem; + this.averageScore = averageScore; + this.averageScoreAllParticipants = averageScoreAllParticipants; + this.numOfAnswers = numOfAnswers; + this.numOfCorrectAnswers = numOfCorrectAnswers; + } + + public AssessmentItem getAssessmentItem() { + return assessmentItem; + } + + /** + * Average build with the score and the number of answered questions. + * It doesn't take not answered questions in the count. + * @return + */ + public double getAverageScore() { + return averageScore; + } + + /** + * Average build with the score and the number of participants to the test. + * The average is built with the users which didn't answer the question. + * @return + */ + public double getAverageScoreAllParticipants() { + return averageScoreAllParticipants; + } + + public long getNumOfAnswers() { + return numOfAnswers; + } + + public long getNumOfCorrectAnswers() { + return numOfCorrectAnswers; + } +} diff --git a/src/main/java/org/olat/ims/qti21/model/statistics/NumericalInputInteractionStatistics.java b/src/main/java/org/olat/ims/qti21/model/statistics/NumericalInputInteractionStatistics.java index 41573aa0c5e7e59f0ce61921cc856aca6f896579..900817488bf4b3827b9fe6927ac6dc5b73316fcc 100644 --- a/src/main/java/org/olat/ims/qti21/model/statistics/NumericalInputInteractionStatistics.java +++ b/src/main/java/org/olat/ims/qti21/model/statistics/NumericalInputInteractionStatistics.java @@ -67,14 +67,29 @@ public class NumericalInputInteractionStatistics extends AbstractTextEntryIntera if(correctFloatResponse == null) return false; try { - double secondNumber = Double.parseDouble(value); - double firstNumber = correctFloatResponse.doubleValue(); - double tolerance1 = lowerTolerance == null ? 0.0d : lowerTolerance.doubleValue(); - double tolerance2 = upperTolerance == null ? 0.0d : upperTolerance.doubleValue(); - return toleranceMode.isEqual(firstNumber, secondNumber, tolerance1, tolerance2, true, true); - } catch (NumberFormatException | NullPointerException e) { + double answer = Double.parseDouble(value); + return match(answer); + } catch (NumberFormatException e) { + if(value.indexOf(',') >= 0) {//allow , instead of . + try { + double answer = Double.parseDouble(value.replace(',', '.')); + return match(answer); + } catch (final NumberFormatException e1) { + //format can happen + } catch (Exception e2) { + log.error("", e2); + } + } + return false; + } catch (Exception e) { log.error("", e); return false; } } + + private boolean match(double answer) { + double lTolerance = lowerTolerance == null ? 0.0d : lowerTolerance.doubleValue(); + double uTolerance = upperTolerance == null ? 0.0d : upperTolerance.doubleValue(); + return toleranceMode.isEqual(answer, correctFloatResponse.doubleValue(), lTolerance, uTolerance, true, true); + } } diff --git a/src/main/java/org/olat/ims/qti21/ui/statistics/QTI21AssessmentTestStatisticsController.java b/src/main/java/org/olat/ims/qti21/ui/statistics/QTI21AssessmentTestStatisticsController.java index 3f61d84f964d86793ce8c6024329abcecbae9704..f1899648fe76aadce348ac45cebacc8246fcafea 100644 --- a/src/main/java/org/olat/ims/qti21/ui/statistics/QTI21AssessmentTestStatisticsController.java +++ b/src/main/java/org/olat/ims/qti21/ui/statistics/QTI21AssessmentTestStatisticsController.java @@ -23,6 +23,8 @@ import static org.olat.ims.qti.statistics.ui.StatisticFormatter.duration; import static org.olat.ims.qti.statistics.ui.StatisticFormatter.format; import static org.olat.ims.qti.statistics.ui.StatisticFormatter.getModeString; +import java.util.ArrayList; +import java.util.Collections; import java.util.Date; import java.util.List; @@ -30,6 +32,7 @@ import org.olat.core.commons.fullWebApp.popup.BaseFullWebappPopupLayoutFactory; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; import org.olat.core.gui.components.chart.BarSeries; +import org.olat.core.gui.components.chart.BarSeries.Stringuified; import org.olat.core.gui.components.chart.StatisticsComponent; import org.olat.core.gui.components.link.Link; import org.olat.core.gui.components.link.LinkFactory; @@ -54,8 +57,14 @@ import org.olat.course.nodes.QTICourseNode; import org.olat.course.nodes.iq.IQEditController; import org.olat.ims.qti.statistics.QTIType; import org.olat.ims.qti.statistics.model.StatisticAssessment; +import org.olat.ims.qti.statistics.ui.QTI12AssessmentStatisticsController.ItemInfos; +import org.olat.ims.qti21.QTI21StatisticsManager; +import org.olat.ims.qti21.model.statistics.AssessmentItemStatistic; import org.olat.modules.assessment.ui.UserFilterController; import org.olat.modules.assessment.ui.event.UserFilterEvent; +import org.springframework.beans.factory.annotation.Autowired; + +import uk.ac.ed.ph.jqtiplus.node.item.AssessmentItem; /** * @@ -73,6 +82,9 @@ public class QTI21AssessmentTestStatisticsController extends BasicController imp private QTIType type; private QTICourseNode courseNode; private final QTI21StatisticResourceResult resourceResult; + + @Autowired + private QTI21StatisticsManager qtiStatisticsManager; public QTI21AssessmentTestStatisticsController(UserRequest ureq, WindowControl wControl, TooledStackedPanel stackPanel, QTI21StatisticResourceResult resourceResult, boolean withFilter, boolean printMode) { @@ -137,6 +149,7 @@ public class QTI21AssessmentTestStatisticsController extends BasicController imp private void updateData() { StatisticAssessment stats = resourceResult.getQTIStatisticAssessment(); initScoreHistogram(stats); + initScoreStatisticPerItem(stats.getNumOfParticipants()); initDurationHistogram(stats); initCourseNodeInformation(stats); } @@ -212,6 +225,39 @@ public class QTI21AssessmentTestStatisticsController extends BasicController imp mainVC.put("durationHistogram", durationHistogramVC); } + private void initScoreStatisticPerItem(double numOfParticipants) { + BarSeries d1 = new BarSeries(); + BarSeries d2 = new BarSeries(); + + List<AssessmentItemStatistic> statisticItems = qtiStatisticsManager + .getStatisticPerItem(resourceResult.getResolvedAssessmentTest(), resourceResult.getSearchParams(), numOfParticipants); + + int i = 0; + List<ItemInfos> itemInfos = new ArrayList<>(statisticItems.size()); + for (AssessmentItemStatistic statisticItem: statisticItems) { + AssessmentItem item = statisticItem.getAssessmentItem(); + + String label = Integer.toString(++i); + String text = item.getTitle(); + d1.add(statisticItem.getAverageScore(), label); + d2.add(statisticItem.getNumOfCorrectAnswers(), label); + itemInfos.add(new ItemInfos(label, text)); + } + + mainVC.contextPut("itemInfoList", itemInfos); + + VelocityContainer averageScorePeritemVC = createVelocityContainer("hbar_average_score_per_item"); + Stringuified data1 = BarSeries.getDatasAndColors(Collections.singletonList(d1), "bar_default"); + averageScorePeritemVC.contextPut("datas", data1); + mainVC.put("averageScorePerItemChart", averageScorePeritemVC); + + VelocityContainer percentRightAnswersPerItemVC = createVelocityContainer("hbar_right_answer_per_item"); + Stringuified data2 = BarSeries.getDatasAndColors(Collections.singletonList(d2), "bar_green"); + percentRightAnswersPerItemVC.contextPut("datas", data2); + percentRightAnswersPerItemVC.contextPut("numOfParticipants", Long.toString(Math.round(numOfParticipants))); + mainVC.put("percentRightAnswersPerItemChart", percentRightAnswersPerItemVC); + } + @Override public void activate(UserRequest ureq, List<ContextEntry> entries, StateEntry state) { // diff --git a/src/main/java/org/olat/ims/qti21/ui/statistics/QTI21StatisticResourceResult.java b/src/main/java/org/olat/ims/qti21/ui/statistics/QTI21StatisticResourceResult.java index b5eff08a55b25cd0aca9f8a3d879dfd7f276921b..a66d7a8bdea1faecc1c2f1af9c8ace60dd9154ac 100644 --- a/src/main/java/org/olat/ims/qti21/ui/statistics/QTI21StatisticResourceResult.java +++ b/src/main/java/org/olat/ims/qti21/ui/statistics/QTI21StatisticResourceResult.java @@ -21,6 +21,7 @@ package org.olat.ims.qti21.ui.statistics; import java.io.File; import java.net.URI; +import java.util.ArrayList; import java.util.List; import org.olat.core.CoreSpringFactory; @@ -115,6 +116,20 @@ public class QTI21StatisticResourceResult implements StatisticResourceResult { return testEntry; } + public ResolvedAssessmentTest getResolvedAssessmentTest() { + return resolvedAssessmentTest; + } + + public List<AssessmentItem> getAssessmentItems() { + List<AssessmentItemRef> itemRefs = resolvedAssessmentTest.getAssessmentItemRefs(); + List<AssessmentItem> items = new ArrayList<>(itemRefs.size()); + for(AssessmentItemRef itemRef:itemRefs) { + AssessmentItem item = resolvedAssessmentTest.getResolvedAssessmentItem(itemRef).getRootNodeLookup().extractIfSuccessful(); + items.add(item); + } + return items; + } + public QTI21StatisticSearchParams getSearchParams() { return searchParams; } diff --git a/src/main/java/org/olat/ims/qti21/ui/statistics/_content/hbar_average_score_per_item.html b/src/main/java/org/olat/ims/qti21/ui/statistics/_content/hbar_average_score_per_item.html new file mode 100644 index 0000000000000000000000000000000000000000..c8309dcfaaac7c3d8a98dfa98183ce3cdef06d49 --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/ui/statistics/_content/hbar_average_score_per_item.html @@ -0,0 +1,13 @@ +<div id="$r.getId('d3div')"><div id="$r.getId('d3holder')" class='d3chart' style='width:90%;'></div> +<script type='text/javascript'> +/* <![CDATA[ */ +jQuery(function () { + jQuery('#$r.getId("d3holder")').qtiStatistics('averageScorePerItem', { + values: [$datas.data], + barHeight: 20, + xBottomLegend: ' $r.translate("chart.answer.averageScoreQuestions.y")', + yLeftLegend: '$r.translate("chart.item")' + }); +}); +/* ]]> */</script> +</div> \ No newline at end of file diff --git a/src/main/java/org/olat/ims/qti21/ui/statistics/_content/hbar_right_answer_per_item.html b/src/main/java/org/olat/ims/qti21/ui/statistics/_content/hbar_right_answer_per_item.html new file mode 100644 index 0000000000000000000000000000000000000000..349f6f4b29a1185ed3c1d03520d0b1908bbf7151 --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/ui/statistics/_content/hbar_right_answer_per_item.html @@ -0,0 +1,15 @@ +<div id="$r.getId('d3div')"><div id="$r.getId('d3holder')" class='d3chart' style='width:90%;'></div> +<script type='text/javascript'> +/* <![CDATA[ */ +jQuery(function () { + jQuery('#$r.getId("d3holder")').qtiStatistics('rightAnswerPerItem', { + values: [$datas.data], + barHeight: 20, + participants: $numOfParticipants, + xBottomLegend: '$r.translate("chart.percent.participants.num")', + xTopLegend: '$r.translate("chart.percent.participants")', + yLeftLegend: '$r.translate("chart.item")' + }); +}); +/* ]]> */</script> +</div> \ No newline at end of file diff --git a/src/main/java/org/olat/ims/qti21/ui/statistics/_content/statistics_assessment_test.html b/src/main/java/org/olat/ims/qti21/ui/statistics/_content/statistics_assessment_test.html index 8fb5fd7bc5da67edb31c36e3f505408f16b5e4c8..1bef835532f73e54ef595207c2e542065307c66f 100644 --- a/src/main/java/org/olat/ims/qti21/ui/statistics/_content/statistics_assessment_test.html +++ b/src/main/java/org/olat/ims/qti21/ui/statistics/_content/statistics_assessment_test.html @@ -4,6 +4,7 @@ </div> #end +<div class="o_qti_statistics"> <div class="panel panel-default"> <div class="panel-heading"><h4>$r.translate("fig.title")</h4></div> <table class="table"><tbody> @@ -44,4 +45,29 @@ #end #if($r.available("durationHistogram") && $r.visible("durationHistogram")) $r.render("durationHistogram") -#end \ No newline at end of file +#end + +#if($r.available("averageScorePerItemChart")) +<div class="o_print_break_avoid"> + <h4>$r.translate("chart.averagescore.peritem")</h4> + $r.render("averageScorePerItemChart") + <ul> + #foreach($itemInfo in $itemInfoList) + <li><strong>${itemInfo.label}.</strong> $itemInfo.text</li> + #end + </ul> +</div> +#end + +#if($r.available("percentRightAnswersPerItemChart")) +<div class="o_print_break_avoid"> + <h4>$r.translate("chart.rightanswers.peritem")</h4> + $r.render("percentRightAnswersPerItemChart") + <ul> + #foreach($itemInfo in $itemInfoList) + <li><strong>${itemInfo.label}.</strong> $itemInfo.text</li> + #end + </ul> +</div> +#end +</div> diff --git a/src/main/java/org/olat/ims/qti21/ui/statistics/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/ims/qti21/ui/statistics/_i18n/LocalStrings_de.properties index 12a26d71cf7ebcfd4253bef23d6a97d1dddfe690..2a26a53ac6fb6ddd74d5c7b844917445d2c7ca49 100644 --- a/src/main/java/org/olat/ims/qti21/ui/statistics/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/ims/qti21/ui/statistics/_i18n/LocalStrings_de.properties @@ -13,6 +13,9 @@ chart.percent.participants.num=$org.olat.ims.qti.statistics.ui\:chart.percent.pa chart.points=$org.olat.ims.qti.statistics.ui\:chart.points chart.responses=$org.olat.ims.qti.statistics.ui\:chart.responses chart.score.histogramm=$org.olat.ims.qti.statistics.ui\:chart.score.histogramm +chart.averagescore.peritem=$org.olat.ims.qti.statistics.ui\:chart.averagescore.peritem +chart.rightanswers.peritem=$org.olat.ims.qti.statistics.ui\:chart.rightanswers.peritem +chart.answer.averageScoreQuestions.y=$org.olat.ims.qti.statistics.ui\:chart.answer.averageScoreQuestions.y download.raw.data=$org.olat.ims.qti.statistics.ui\:download.raw.data fib.wrong.answer=$org.olat.ims.qti.statistics.ui\:fib.wrong.answer fig.averagedur=$org.olat.ims.qti.statistics.ui\:fig.averagedur diff --git a/src/main/java/org/olat/modules/portfolio/BinderSecurityCallbackFactory.java b/src/main/java/org/olat/modules/portfolio/BinderSecurityCallbackFactory.java index 39bd4273f9d2f478147e7526d4bf337a88612933..f5acbe70cfa1e4dd9572db66598954620c70fcb1 100644 --- a/src/main/java/org/olat/modules/portfolio/BinderSecurityCallbackFactory.java +++ b/src/main/java/org/olat/modules/portfolio/BinderSecurityCallbackFactory.java @@ -93,7 +93,7 @@ public class BinderSecurityCallbackFactory { * @return */ public static final BinderSecurityCallback getCallbackForBusinessGroup() { - return new BinderSecurityCallbackImpl(true, false, null); + return new BinderSecurityCallbackGroup(true, false, null); } private static class BinderSecurityCallbackForDeletedPages extends DefaultBinderSecurityCallback { @@ -252,6 +252,18 @@ public class BinderSecurityCallbackFactory { } } + private static class BinderSecurityCallbackGroup extends BinderSecurityCallbackImpl { + + public BinderSecurityCallbackGroup(boolean owner, boolean task, BinderDeliveryOptions deliveryOptions) { + super(owner, task, deliveryOptions); + } + + @Override + public boolean canDeleteBinder(Binder binder) { + return false; + } + } + private static class BinderSecurityCallbackImpl implements BinderSecurityCallback { /**