diff --git a/src/main/java/org/olat/course/assessment/manager/AssessmentToolManagerImpl.java b/src/main/java/org/olat/course/assessment/manager/AssessmentToolManagerImpl.java index e18e67bb97585ae2f574f3c77791c492f9319c2f..31df0207c6059dbe1cd77507655c92c19cf57763 100644 --- a/src/main/java/org/olat/course/assessment/manager/AssessmentToolManagerImpl.java +++ b/src/main/java/org/olat/course/assessment/manager/AssessmentToolManagerImpl.java @@ -50,8 +50,8 @@ public class AssessmentToolManagerImpl implements AssessmentToolManager { sb.append("select ident") .append(" from ").append(IdentityImpl.class.getName()).append(" as ident ") .append(" where exists (") - .append(" select infos from usercourseinfos infos where infos.identity=ident") - .append(" and infos.resource.key=:resourceKey") + .append(" select infos from assessmententry infos where infos.identity=ident") + .append(" and infos.repositoryEntry.key=:courseEntryKey") .append(" ) or exists (select rel from repoentrytogroup as rel, bgroup as baseGroup, bgroupmember as membership") .append(" where rel.entry.key=:courseEntryKey and rel.group=baseGroup and membership.group=baseGroup and membership.identity=ident") .append(" and membership.role='").append(GroupRoles.participant.name()).append("'") @@ -60,7 +60,6 @@ public class AssessmentToolManagerImpl implements AssessmentToolManager { List<Identity> list = dbInstance.getCurrentEntityManager() .createQuery(sb.toString(), Identity.class) .setParameter("courseEntryKey", params.getCourseEntry().getKey()) - .setParameter("resourceKey", params.getCourseEntry().getOlatResource().getKey()) .getResultList(); return list; } diff --git a/src/main/java/org/olat/course/assessment/manager/CourseAssessmentManagerImpl.java b/src/main/java/org/olat/course/assessment/manager/CourseAssessmentManagerImpl.java index e8d6d50cd47f43dee52b92431f5fa2934d855c63..e1ef5135e0229533b62993bbdd3276cf9b028274 100644 --- a/src/main/java/org/olat/course/assessment/manager/CourseAssessmentManagerImpl.java +++ b/src/main/java/org/olat/course/assessment/manager/CourseAssessmentManagerImpl.java @@ -206,23 +206,24 @@ public class CourseAssessmentManagerImpl implements AssessmentManager { Boolean passed = scoreEvaluation.getPassed(); Long assessmentId = scoreEvaluation.getAssessmentID(); - AssessmentEntry nodeAssessment = getOrCreate(assessedIdentity, courseNode); + AssessmentEntry assessmentEntry = getOrCreate(assessedIdentity, courseNode); if(score == null) { - nodeAssessment.setScore(null); + assessmentEntry.setScore(null); } else { - nodeAssessment.setScore(new BigDecimal(Float.toString(score))); + assessmentEntry.setScore(new BigDecimal(Float.toString(score))); } - nodeAssessment.setPassed(passed); - nodeAssessment.setFullyAssessed(scoreEvaluation.getFullyAssessed()); - nodeAssessment.setAssessmentId(assessmentId); + assessmentEntry.setPassed(passed); + assessmentEntry.setFullyAssessed(scoreEvaluation.getFullyAssessed()); + assessmentEntry.setAssessmentId(assessmentId); if(incrementUserAttempts) { - int attempts = nodeAssessment.getAttempts() == null ? 1 :nodeAssessment.getAttempts().intValue() + 1; - nodeAssessment.setAttempts(attempts); + int attempts = assessmentEntry.getAttempts() == null ? 1 :assessmentEntry.getAttempts().intValue() + 1; + assessmentEntry.setAttempts(attempts); } - nodeAssessment = assessmentService.updateAssessmentEntry(nodeAssessment); + assessmentEntry = assessmentService.updateAssessmentEntry(assessmentEntry); if(courseNode instanceof AssessableCourseNode) { + userCourseEnv.getScoreAccounting().scoreInfoChanged((AssessableCourseNode)courseNode, scoreEvaluation); // Update users efficiency statement efficiencyStatementManager.updateUserEfficiencyStatement(userCourseEnv); } diff --git a/src/main/java/org/olat/ims/qti/statistics/QTIStatisticResourceResult.java b/src/main/java/org/olat/ims/qti/statistics/QTIStatisticResourceResult.java index efa1cc32aff0ea9e682a18513fc9ce1e7f5443d7..ee83cde87a8924afa21346a3584d2e4cc1686f06 100644 --- a/src/main/java/org/olat/ims/qti/statistics/QTIStatisticResourceResult.java +++ b/src/main/java/org/olat/ims/qti/statistics/QTIStatisticResourceResult.java @@ -54,7 +54,7 @@ import org.olat.ims.qti.statistics.model.StatisticAssessment; import org.olat.ims.qti.statistics.ui.QTI12AssessmentStatisticsController; import org.olat.ims.qti.statistics.ui.QTI12ItemStatisticsController; import org.olat.ims.qti.statistics.ui.QTI21OnyxAssessmentStatisticsController; -import org.olat.ims.qti21.statistics.ui.QTI21AssessmentTestStatisticsController; +import org.olat.ims.qti21.ui.statistics.QTI21AssessmentTestStatisticsController; import org.olat.repository.RepositoryEntry; import de.bps.onyx.plugin.OnyxModule; diff --git a/src/main/java/org/olat/ims/qti21/QTI21Service.java b/src/main/java/org/olat/ims/qti21/QTI21Service.java index 276a673f928797fa9cd32e26c0cd15d6c251a416..5970e910f212acaa4110ed5d6525da17f6d3999b 100644 --- a/src/main/java/org/olat/ims/qti21/QTI21Service.java +++ b/src/main/java/org/olat/ims/qti21/QTI21Service.java @@ -74,7 +74,7 @@ public interface QTI21Service { */ public List<UserTestSession> getUserTestSessions(RepositoryEntryRef courseEntry, String subIdent, IdentityRef identity); - public void recordTestAssessmentResult(UserTestSession candidateSession, AssessmentResult assessmentResult); + public UserTestSession recordTestAssessmentResult(UserTestSession candidateSession, AssessmentResult assessmentResult); public UserTestSession finishTestSession(UserTestSession candidateSession, AssessmentResult assessmentResul, Date timestamp); diff --git a/src/main/java/org/olat/ims/qti21/QTI21StatisticsManager.java b/src/main/java/org/olat/ims/qti21/QTI21StatisticsManager.java new file mode 100644 index 0000000000000000000000000000000000000000..2cdcb128ce4c3121852cb350a09c01228fb19682 --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/QTI21StatisticsManager.java @@ -0,0 +1,56 @@ +/** + * OLAT - Online Learning and Training<br> + * http://www.olat.org + * <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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <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> + * Copyright (c) frentix GmbH<br> + * http://www.frentix.com<br> + * <p> + */ +package org.olat.ims.qti21; + +import org.olat.ims.qti.statistics.model.StatisticAssessment; +import org.olat.ims.qti21.model.QTI21StatisticSearchParams; + +/** + * + * Initial date: 24.07.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public interface QTI21StatisticsManager { + + /* + * minimal number of results we need for a test, or for a question.. + * + * (if number of participants in test is smaller, don't show test) (if + * results for a specific question is smaller, don't display question) + */ + public static final int MIN_RESULTS_TEST = 2; + public static final int MIN_RESULTS_QUESTION = 1; + + + public static final int AVG_NUM_OF_SCORE_BUCKETS = 10; + + /** + * Return the statistics of a test in a course + * @param courseResID + * @param resSubPath + * @return + */ + public StatisticAssessment getAssessmentStatistics(QTI21StatisticSearchParams searchParams); + + + +} diff --git a/src/main/java/org/olat/ims/qti21/UserTestSession.java b/src/main/java/org/olat/ims/qti21/UserTestSession.java index 68d44b072c0dc0e3507b57a9e5c36843351177f7..39cac51deec2060db987b54c472db168dcd44d6c 100644 --- a/src/main/java/org/olat/ims/qti21/UserTestSession.java +++ b/src/main/java/org/olat/ims/qti21/UserTestSession.java @@ -19,6 +19,7 @@ */ package org.olat.ims.qti21; +import java.math.BigDecimal; import java.util.Date; import org.olat.core.id.CreateInfo; @@ -42,6 +43,14 @@ public interface UserTestSession extends CreateInfo, ModifiedInfo { public void setTerminationTime(Date timestamp); + public Boolean getPassed(); + + public void setPassed(Boolean passed); + + public BigDecimal getScore(); + + public void setScore(BigDecimal score); + public boolean isExploded(); public String getStorage(); diff --git a/src/main/java/org/olat/ims/qti21/manager/QTI21ServiceImpl.java b/src/main/java/org/olat/ims/qti21/manager/QTI21ServiceImpl.java index 52145ee18e7b494b43c99b52ebafa526f5d05aca..b1a4741ab921affbd648d4eb38d34e0dcec08436 100644 --- a/src/main/java/org/olat/ims/qti21/manager/QTI21ServiceImpl.java +++ b/src/main/java/org/olat/ims/qti21/manager/QTI21ServiceImpl.java @@ -23,6 +23,7 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; +import java.math.BigDecimal; import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; @@ -45,6 +46,7 @@ import org.olat.core.logging.Tracing; import org.olat.core.util.FileUtils; import org.olat.fileresource.types.ImsQTI21Resource; import org.olat.fileresource.types.ImsQTI21Resource.PathResourceLocator; +import org.olat.ims.qti21.QTI21Constants; import org.olat.ims.qti21.QTI21ContentPackage; import org.olat.ims.qti21.QTI21Module; import org.olat.ims.qti21.QTI21Service; @@ -80,6 +82,9 @@ import uk.ac.ed.ph.jqtiplus.state.TestPlanNodeKey; import uk.ac.ed.ph.jqtiplus.state.TestSessionState; import uk.ac.ed.ph.jqtiplus.state.marshalling.ItemSessionStateXmlMarshaller; import uk.ac.ed.ph.jqtiplus.state.marshalling.TestSessionStateXmlMarshaller; +import uk.ac.ed.ph.jqtiplus.types.Identifier; +import uk.ac.ed.ph.jqtiplus.value.BooleanValue; +import uk.ac.ed.ph.jqtiplus.value.NumberValue; import uk.ac.ed.ph.jqtiplus.value.RecordValue; import uk.ac.ed.ph.jqtiplus.value.SingleValue; import uk.ac.ed.ph.jqtiplus.value.Value; @@ -222,12 +227,13 @@ public class QTI21ServiceImpl implements QTI21Service { } @Override - public void recordTestAssessmentResult(UserTestSession candidateSession, AssessmentResult assessmentResult) { + public UserTestSession recordTestAssessmentResult(UserTestSession candidateSession, AssessmentResult assessmentResult) { // First record full result XML to filesystem storeAssessmentResultFile(candidateSession, assessmentResult); // Then record test outcome variables to DB recordOutcomeVariables(candidateSession, assessmentResult.getTestResult()); + return testSessionDao.update(candidateSession); } @Override @@ -247,9 +253,25 @@ public class QTI21ServiceImpl implements QTI21Service { private void recordOutcomeVariables(UserTestSession candidateSession, AbstractResult resultNode) { for (final ItemVariable itemVariable : resultNode.getItemVariables()) { - if (itemVariable instanceof OutcomeVariable - || QtiConstants.VARIABLE_DURATION_IDENTIFIER.equals(itemVariable.getIdentifier())) { - log.audit(candidateSession.getKey() + " :: " + itemVariable.getIdentifier() + " - " + stringifyQtiValue(itemVariable.getComputedValue())); + if (itemVariable instanceof OutcomeVariable) { + + OutcomeVariable outcomeVariable = (OutcomeVariable)itemVariable; + Identifier identifier = outcomeVariable.getIdentifier(); + if(QtiConstants.VARIABLE_DURATION_IDENTIFIER.equals(identifier)) { + log.audit(candidateSession.getKey() + " :: " + itemVariable.getIdentifier() + " - " + stringifyQtiValue(itemVariable.getComputedValue())); + } else if(QTI21Constants.SCORE_IDENTIFIER.equals(identifier)) { + Value value = itemVariable.getComputedValue(); + if(value instanceof NumberValue) { + double score = ((NumberValue)value).doubleValue(); + candidateSession.setScore(new BigDecimal(Double.toString(score))); + } + } else if(QTI21Constants.PASS_IDENTIFIER.equals(identifier)) { + Value value = itemVariable.getComputedValue(); + if(value instanceof BooleanValue) { + boolean pass = ((BooleanValue)value).booleanValue(); + candidateSession.setPassed(pass); + } + } } } } diff --git a/src/main/java/org/olat/ims/qti21/manager/QTI21StatisticsManagerImpl.java b/src/main/java/org/olat/ims/qti21/manager/QTI21StatisticsManagerImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..de8f3c2d4a0e01d4e6c0ee5bc452f0a487422b22 --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/manager/QTI21StatisticsManagerImpl.java @@ -0,0 +1,141 @@ +/** + * OLAT - Online Learning and Training<br> + * http://www.olat.org + * <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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <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> + * Copyright (c) frentix GmbH<br> + * http://www.frentix.com<br> + * <p> + */ +package org.olat.ims.qti21.manager; + +import java.math.BigDecimal; +import java.util.List; + +import javax.persistence.TypedQuery; + +import org.olat.core.commons.persistence.DB; +import org.olat.ims.qti.statistics.manager.Statistics; +import org.olat.ims.qti.statistics.model.StatisticAssessment; +import org.olat.ims.qti21.QTI21StatisticsManager; +import org.olat.ims.qti21.model.QTI21StatisticSearchParams; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * + * Initial date: 24.07.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +@Service +public class QTI21StatisticsManagerImpl implements QTI21StatisticsManager { + + @Autowired + private DB dbInstance; + + private StringBuilder decorateRSet(StringBuilder sb, QTI21StatisticSearchParams searchParams) { + sb.append(" where asession.repositoryEntry.key=:repositoryEntryKey") + .append(" and asession.lastModified = (select max(a2session.lastModified) from qtiassessmentsession a2session") + .append(" where a2session.identity=asession.identity and a2session.repositoryEntry=asession.repositoryEntry") + .append(" )"); + + if(searchParams.getLimitToGroups() != null && searchParams.getLimitToGroups().size() > 0) { + sb.append(" and asession.identity.key in ( select membership.identity.key from bgroupmember membership ") + .append(" where membership.group in (:baseGroups)") + .append(" )"); + } + + if(searchParams.isMayViewAllUsersAssessments()) { + sb.append(" and asession.identity.key in (select data.identity.key from assessmententry data ") + .append(" where data.repositoryEntry=asession.repositoryEntry") + .append(" )"); + } + return sb; + } + + private void decorateRSetQuery(TypedQuery<?> query, QTI21StatisticSearchParams searchParams) { + query.setParameter("repositoryEntryKey", searchParams.getEntry().getKey()); + if(searchParams.getLimitToGroups() != null && searchParams.getLimitToGroups().size() > 0) { + query.setParameter("baseGroups", searchParams.getLimitToGroups()); + } + } + + @Override + public StatisticAssessment getAssessmentStatistics(QTI21StatisticSearchParams searchParams) { + StringBuilder sb = new StringBuilder(); + sb.append("select asession.score, asession.passed from qtiassessmentsession asession "); + decorateRSet(sb, searchParams); + sb.append(" order by asession.key asc"); + + TypedQuery<Object[]> rawDataQuery = dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), Object[].class); + decorateRSetQuery(rawDataQuery, searchParams); + List<Object[]> rawDatas = rawDataQuery.getResultList(); + + int numOfPassed = 0; + int numOfFailed = 0; + double totalDuration = 0.0; + double maxScore = 0.0; + double minScore = Double.MAX_VALUE; + double[] scores = new double[rawDatas.size()]; + + + int dataPos = 0; + for(Object[] rawData:rawDatas) { + Boolean passed = (Boolean)rawData[1]; + if(passed != null) { + if(passed.booleanValue()) { + numOfPassed++; + } else { + numOfFailed++; + } + } + + BigDecimal score = (BigDecimal)rawData[0]; + if(score != null) { + double scored = score.doubleValue(); + scores[dataPos] = scored; + maxScore = Math.max(maxScore, scored); + minScore = Math.min(minScore, scored); + } + + dataPos++; + } + if (rawDatas.size() == 0) { + minScore = 0; + } + + Statistics statisticsHelper = new Statistics(scores); + + int numOfParticipants = rawDatas.size(); + StatisticAssessment stats = new StatisticAssessment(); + stats.setNumOfParticipants(numOfParticipants); + stats.setNumOfPassed(numOfPassed); + stats.setNumOfFailed(numOfFailed); + long averageDuration = Math.round(totalDuration / numOfParticipants); + stats.setAverageDuration(averageDuration); + stats.setAverage(statisticsHelper.getMean()); + double range = maxScore - minScore; + stats.setRange(range); + stats.setMaxScore(maxScore); + stats.setMinScore(minScore); + stats.setStandardDeviation(statisticsHelper.getStdDev()); + stats.setMedian(statisticsHelper.median()); + stats.setMode(statisticsHelper.mode()); + stats.setScores(scores); + return stats; + } + +} \ No newline at end of file diff --git a/src/main/java/org/olat/ims/qti21/model/QTI21StatisticSearchParams.java b/src/main/java/org/olat/ims/qti21/model/QTI21StatisticSearchParams.java new file mode 100644 index 0000000000000000000000000000000000000000..683ff1e3afcaf8e319e833f1e3f618891d2f9c4f --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/model/QTI21StatisticSearchParams.java @@ -0,0 +1,63 @@ +/** + * <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; + +import java.util.List; + +import org.olat.basesecurity.Group; +import org.olat.repository.RepositoryEntry; + +/** + * + * Initial date: 24.07.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class QTI21StatisticSearchParams { + + private final RepositoryEntry entry; + + private List<Group> limitToGroups; + private boolean mayViewAllUsersAssessments; + + public QTI21StatisticSearchParams(RepositoryEntry entry) { + this.entry = entry; + } + + public RepositoryEntry getEntry() { + return entry; + } + + public List<Group> getLimitToGroups() { + return limitToGroups; + } + + public void setLimitToGroups(List<Group> limitToGroups) { + this.limitToGroups = limitToGroups; + } + + public boolean isMayViewAllUsersAssessments() { + return mayViewAllUsersAssessments; + } + + public void setMayViewAllUsersAssessments(boolean mayViewAllUsersAssessments) { + this.mayViewAllUsersAssessments = mayViewAllUsersAssessments; + } +} diff --git a/src/main/java/org/olat/ims/qti21/model/jpa/UserTestSessionImpl.java b/src/main/java/org/olat/ims/qti21/model/jpa/UserTestSessionImpl.java index 163d4011120c2fbdc367ec6a51c6cfb2b4e83b9b..92284440b412e191098a7907b770efa1d5f99e74 100644 --- a/src/main/java/org/olat/ims/qti21/model/jpa/UserTestSessionImpl.java +++ b/src/main/java/org/olat/ims/qti21/model/jpa/UserTestSessionImpl.java @@ -19,6 +19,7 @@ */ package org.olat.ims.qti21.model.jpa; +import java.math.BigDecimal; import java.util.Date; import javax.persistence.Column; @@ -132,6 +133,12 @@ public class UserTestSessionImpl implements UserTestSession, Persistable { @Temporal(TemporalType.TIMESTAMP) @Column(name="q_termination_time", nullable=true, insertable=true, updatable=true) private Date terminationTime; + + @Column(name="q_passed", nullable=true, insertable=true, updatable=true) + private Boolean passed; + + @Column(name="q_score", nullable=true, insertable=true, updatable=true) + private BigDecimal score; /** * Flag to indicate if this session blew up while running, either because @@ -248,6 +255,22 @@ public class UserTestSessionImpl implements UserTestSession, Persistable { this.terminationTime = terminationTime; } + public Boolean getPassed() { + return passed; + } + + public void setPassed(Boolean passed) { + this.passed = passed; + } + + public BigDecimal getScore() { + return score; + } + + public void setScore(BigDecimal score) { + this.score = score; + } + @Override public int hashCode() { return key == null ? -86534687 : key.hashCode(); diff --git a/src/main/java/org/olat/ims/qti21/repository/handlers/QTI21AssessmentTestHandler.java b/src/main/java/org/olat/ims/qti21/repository/handlers/QTI21AssessmentTestHandler.java index ddab048826701753e9b8a6cfa3574702a1ba8999..6ef22732deb8ba0ea91fea87da981ef9dbcabbe0 100644 --- a/src/main/java/org/olat/ims/qti21/repository/handlers/QTI21AssessmentTestHandler.java +++ b/src/main/java/org/olat/ims/qti21/repository/handlers/QTI21AssessmentTestHandler.java @@ -68,6 +68,7 @@ import org.olat.ims.qti21.model.xml.ManifestPackage; import org.olat.ims.qti21.model.xml.OnyxToQtiWorksHandler; import org.olat.ims.qti21.ui.AssessmentTestDisplayController; import org.olat.ims.qti21.ui.InMemoryOutcomesListener; +import org.olat.ims.qti21.ui.QTI21RuntimeController; import org.olat.ims.qti21.ui.editor.AssessmentTestComposerController; import org.olat.imscp.xml.manifest.ManifestType; import org.olat.repository.RepositoryEntry; @@ -75,7 +76,6 @@ import org.olat.repository.RepositoryService; import org.olat.repository.handlers.EditionSupport; import org.olat.repository.handlers.FileHandler; import org.olat.repository.model.RepositoryEntrySecurity; -import org.olat.repository.ui.RepositoryEntryRuntimeController; import org.olat.repository.ui.RepositoryEntryRuntimeController.RuntimeControllerCreator; import org.olat.resource.OLATResource; import org.olat.resource.OLATResourceManager; @@ -295,7 +295,7 @@ public class QTI21AssessmentTestHandler extends FileHandler { @Override public MainLayoutController createLaunchController(RepositoryEntry re, RepositoryEntrySecurity reSecurity, UserRequest ureq, WindowControl wControl) { - return new RepositoryEntryRuntimeController(ureq, wControl, re, reSecurity, + return new QTI21RuntimeController(ureq, wControl, re, reSecurity, new RuntimeControllerCreator() { @Override public Controller create(UserRequest uureq, WindowControl wwControl, TooledStackedPanel toolbarPanel, diff --git a/src/main/java/org/olat/ims/qti21/ui/QTI21RuntimeController.java b/src/main/java/org/olat/ims/qti21/ui/QTI21RuntimeController.java new file mode 100644 index 0000000000000000000000000000000000000000..d9b13f633216e5bb2c266341717b9227b21ab3e4 --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/ui/QTI21RuntimeController.java @@ -0,0 +1,136 @@ +/** + * <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.ui; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.dropdown.Dropdown; +import org.olat.core.gui.components.link.Link; +import org.olat.core.gui.components.link.LinkFactory; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.control.generic.dtabs.Activateable2; +import org.olat.core.id.OLATResourceable; +import org.olat.core.logging.activity.ThreadLocalUserActivityLogger; +import org.olat.core.util.resource.OresHelper; +import org.olat.ims.qti21.ui.statistics.QTI21AssessmentTestStatisticsController; +import org.olat.modules.assessment.ui.AssessmentOverviewController; +import org.olat.repository.RepositoryEntry; +import org.olat.repository.RepositoryEntryManagedFlag; +import org.olat.repository.model.RepositoryEntrySecurity; +import org.olat.repository.ui.RepositoryEntryRuntimeController; +import org.olat.util.logging.activity.LoggingResourceable; + +/** + * + * Initial date: 23.07.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class QTI21RuntimeController extends RepositoryEntryRuntimeController { + + private Link assessmentLink, testStatisticLink; + + private AssessmentOverviewController assessmentToolCtrl; + private QTI21AssessmentTestStatisticsController statsToolCtr; + + + public QTI21RuntimeController(UserRequest ureq, WindowControl wControl, + RepositoryEntry re, RepositoryEntrySecurity reSecurity, RuntimeControllerCreator runtimeControllerCreator) { + super(ureq, wControl, re, reSecurity, runtimeControllerCreator); + } + + @Override + protected void initRuntimeTools(Dropdown toolsDropdown) { + + if (reSecurity.isEntryAdmin()) { + boolean managed = RepositoryEntryManagedFlag.isManaged(getRepositoryEntry(), RepositoryEntryManagedFlag.editcontent); + editLink = LinkFactory.createToolLink("edit.cmd", translate("details.openeditor"), this, "o_sel_repository_editor"); + editLink.setIconLeftCSS("o_icon o_icon-lg o_icon_edit"); + editLink.setEnabled(!managed); + toolsDropdown.addComponent(editLink); + + membersLink = LinkFactory.createToolLink("members", translate("details.members"), this, "o_sel_repo_members"); + membersLink.setIconLeftCSS("o_icon o_icon-fw o_icon_membersmanagement"); + toolsDropdown.addComponent(membersLink); + } + + if (reSecurity.isEntryAdmin() || reSecurity.isCourseCoach() || reSecurity.isGroupCoach()) { + assessmentLink = LinkFactory.createToolLink("assessment", translate("command.openassessment"), this, "o_icon_assessment_tool"); + assessmentLink.setElementCssClass("o_sel_course_assessment_tool"); + toolsDropdown.addComponent(assessmentLink); + + testStatisticLink = LinkFactory.createToolLink("qtistatistic", translate("command.openteststatistic"), this, "o_icon_statistics_tool"); + toolsDropdown.addComponent(testStatisticLink); + } + + if (reSecurity.isEntryAdmin()) { + RepositoryEntry re = getRepositoryEntry(); + ordersLink = LinkFactory.createToolLink("bookings", translate("details.orders"), this, "o_sel_repo_booking"); + ordersLink.setIconLeftCSS("o_icon o_icon-fw o_icon_booking"); + boolean booking = acService.isResourceAccessControled(re.getOlatResource(), null); + ordersLink.setEnabled(booking); + toolsDropdown.addComponent(ordersLink); + } + } + + @Override + protected void event(UserRequest ureq, Component source, Event event) { + if(testStatisticLink == source) { + doAssessmentTestStatistics(ureq); + } else if(assessmentLink == source) { + doAssessmentTool(ureq); + } + super.event(ureq, source, event); + } + + private Activateable2 doAssessmentTestStatistics(UserRequest ureq) { + OLATResourceable ores = OresHelper.createOLATResourceableType("TestStatistics"); + ThreadLocalUserActivityLogger.addLoggingResourceInfo(LoggingResourceable.wrapBusinessPath(ores)); + WindowControl swControl = addToHistory(ureq, ores, null); + + if (reSecurity.isEntryAdmin() || reSecurity.isCourseCoach() || reSecurity.isGroupCoach()) { + QTI21AssessmentTestStatisticsController ctrl = new QTI21AssessmentTestStatisticsController(ureq, swControl, getRepositoryEntry(), false); + listenTo(ctrl); + statsToolCtr = pushController(ureq, "Statistics", ctrl); + currentToolCtr = statsToolCtr; + setActiveTool(testStatisticLink); + return statsToolCtr; + } + return null; + } + + private Activateable2 doAssessmentTool(UserRequest ureq) { + OLATResourceable ores = OresHelper.createOLATResourceableType("TestStatistics"); + ThreadLocalUserActivityLogger.addLoggingResourceInfo(LoggingResourceable.wrapBusinessPath(ores)); + WindowControl swControl = addToHistory(ureq, ores, null); + + if (reSecurity.isEntryAdmin() || reSecurity.isCourseCoach() || reSecurity.isGroupCoach()) { + AssessmentOverviewController ctrl = new AssessmentOverviewController(ureq, swControl, toolbarPanel, + getRepositoryEntry(), null); + listenTo(ctrl); + assessmentToolCtrl = pushController(ureq, "Statistics", ctrl); + currentToolCtr = assessmentToolCtrl; + setActiveTool(assessmentLink); + return assessmentToolCtrl; + } + return null; + } +} diff --git a/src/main/java/org/olat/ims/qti21/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/ims/qti21/ui/_i18n/LocalStrings_de.properties index c56716958f85247f5fa2fd7f8e9684f02af6588b..73e18748b9f7921bd3fc566999693a8691c848be 100644 --- a/src/main/java/org/olat/ims/qti21/ui/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/ims/qti21/ui/_i18n/LocalStrings_de.properties @@ -3,6 +3,8 @@ form.metadata.title=Title assessment.test.config=<assessmentTest> Konfiguration assessment.testpart.config=<testPart> Konfiguration assessment.section.config=<assessmentSection> Konfiguration +command.openassessment=Bewertungswerkzeug +command.openteststatistic=Test statistics item.session.control.allow.comment=Kommentar erlauben serialize=Serialize serialize.error=Unerwarte Fehler während Speicherung von Datei \ No newline at end of file diff --git a/src/main/java/org/olat/ims/qti21/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/ims/qti21/ui/_i18n/LocalStrings_en.properties index 7aa187ce5f70426a547dcd06389300ba5248e59e..8c3354023743f0f895c1671561617a32bdf04ee5 100644 --- a/src/main/java/org/olat/ims/qti21/ui/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/ims/qti21/ui/_i18n/LocalStrings_en.properties @@ -1,2 +1,4 @@ #Sat Jan 22 17:01:28 CET 2011 -serialize=Serialize \ No newline at end of file +serialize=Serialize +command.openassessment=Assessment tool +command.openteststatistic=Test statistics \ No newline at end of file diff --git a/src/main/java/org/olat/ims/qti21/statistics/ui/QTI21AssessmentTestStatisticsController.java b/src/main/java/org/olat/ims/qti21/ui/statistics/QTI21AssessmentTestStatisticsController.java similarity index 73% rename from src/main/java/org/olat/ims/qti21/statistics/ui/QTI21AssessmentTestStatisticsController.java rename to src/main/java/org/olat/ims/qti21/ui/statistics/QTI21AssessmentTestStatisticsController.java index ea39aa70f509bfd0ab00665520e1902d94cfac78..bf10065b8d910807aea57e831df87d3e2caea0e5 100644 --- a/src/main/java/org/olat/ims/qti21/statistics/ui/QTI21AssessmentTestStatisticsController.java +++ b/src/main/java/org/olat/ims/qti21/ui/statistics/QTI21AssessmentTestStatisticsController.java @@ -17,12 +17,14 @@ * frentix GmbH, http://www.frentix.com * <p> */ -package org.olat.ims.qti21.statistics.ui; +package org.olat.ims.qti21.ui.statistics; 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.List; + import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; import org.olat.core.gui.components.chart.BarSeries; @@ -31,25 +33,32 @@ import org.olat.core.gui.components.velocity.VelocityContainer; import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.controller.BasicController; +import org.olat.core.gui.control.generic.dtabs.Activateable2; +import org.olat.core.id.context.ContextEntry; +import org.olat.core.id.context.StateEntry; import org.olat.ims.qti.statistics.QTIStatisticResourceResult; +import org.olat.ims.qti.statistics.QTIType; import org.olat.ims.qti.statistics.model.StatisticAssessment; +import org.olat.ims.qti21.QTI21StatisticsManager; +import org.olat.ims.qti21.model.QTI21StatisticSearchParams; +import org.olat.repository.RepositoryEntry; +import org.springframework.beans.factory.annotation.Autowired; /** * * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ -public class QTI21AssessmentTestStatisticsController extends BasicController { +public class QTI21AssessmentTestStatisticsController extends BasicController implements Activateable2 { private final VelocityContainer mainVC; - private final QTIStatisticResourceResult resourceResult; + @Autowired + private QTI21StatisticsManager statisticsManager; public QTI21AssessmentTestStatisticsController(UserRequest ureq, WindowControl wControl, QTIStatisticResourceResult resourceResult, boolean printMode) { super(ureq, wControl); - - this.resourceResult = resourceResult; mainVC = createVelocityContainer("statistics_assessment_test"); mainVC.put("loadd3js", new StatisticsComponent("d3loader")); @@ -62,6 +71,22 @@ public class QTI21AssessmentTestStatisticsController extends BasicController { initCourseNodeInformation(stats); } + public QTI21AssessmentTestStatisticsController(UserRequest ureq, WindowControl wControl, + RepositoryEntry entry, boolean printMode) { + super(ureq, wControl); + + mainVC = createVelocityContainer("statistics_assessment_test"); + mainVC.put("loadd3js", new StatisticsComponent("d3loader")); + mainVC.contextPut("printMode", new Boolean(printMode)); + putInitialPanel(mainVC); + + QTI21StatisticSearchParams searchParams = new QTI21StatisticSearchParams(entry); + StatisticAssessment stats = statisticsManager.getAssessmentStatistics(searchParams); + initScoreHistogram(stats); + //initDurationHistogram(stats); + initCourseNodeInformation(stats); + } + @Override protected void doDispose() { // @@ -70,7 +95,7 @@ public class QTI21AssessmentTestStatisticsController extends BasicController { private void initCourseNodeInformation(StatisticAssessment stats) { mainVC.contextPut("numOfParticipants", stats.getNumOfParticipants()); - mainVC.contextPut("type", resourceResult.getType()); + mainVC.contextPut("type", QTIType.qtiworks); mainVC.contextPut("numOfPassed", stats.getNumOfPassed()); mainVC.contextPut("numOfFailed", stats.getNumOfFailed()); @@ -98,6 +123,11 @@ public class QTI21AssessmentTestStatisticsController extends BasicController { mainVC.put("durationHistogram", durationHistogramVC); } + @Override + public void activate(UserRequest ureq, List<ContextEntry> entries, StateEntry state) { + // + } + @Override protected void event(UserRequest ureq, Component source, Event event) { // diff --git a/src/main/java/org/olat/ims/qti21/statistics/ui/_content/histogram_duration.html b/src/main/java/org/olat/ims/qti21/ui/statistics/_content/histogram_duration.html similarity index 100% rename from src/main/java/org/olat/ims/qti21/statistics/ui/_content/histogram_duration.html rename to src/main/java/org/olat/ims/qti21/ui/statistics/_content/histogram_duration.html diff --git a/src/main/java/org/olat/ims/qti21/statistics/ui/_content/histogram_score.html b/src/main/java/org/olat/ims/qti21/ui/statistics/_content/histogram_score.html similarity index 100% rename from src/main/java/org/olat/ims/qti21/statistics/ui/_content/histogram_score.html rename to src/main/java/org/olat/ims/qti21/ui/statistics/_content/histogram_score.html diff --git a/src/main/java/org/olat/ims/qti21/statistics/ui/_content/statistics_assessment_test.html b/src/main/java/org/olat/ims/qti21/ui/statistics/_content/statistics_assessment_test.html similarity index 100% rename from src/main/java/org/olat/ims/qti21/statistics/ui/_content/statistics_assessment_test.html rename to src/main/java/org/olat/ims/qti21/ui/statistics/_content/statistics_assessment_test.html diff --git a/src/main/java/org/olat/ims/qti21/statistics/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/ims/qti21/ui/statistics/_i18n/LocalStrings_de.properties similarity index 100% rename from src/main/java/org/olat/ims/qti21/statistics/ui/_i18n/LocalStrings_de.properties rename to src/main/java/org/olat/ims/qti21/ui/statistics/_i18n/LocalStrings_de.properties diff --git a/src/main/java/org/olat/ims/qti21/statistics/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/ims/qti21/ui/statistics/_i18n/LocalStrings_en.properties similarity index 100% rename from src/main/java/org/olat/ims/qti21/statistics/ui/_i18n/LocalStrings_en.properties rename to src/main/java/org/olat/ims/qti21/ui/statistics/_i18n/LocalStrings_en.properties diff --git a/src/main/java/org/olat/modules/assessment/manager/AssessmentEntryDAO.java b/src/main/java/org/olat/modules/assessment/manager/AssessmentEntryDAO.java index ea26650e4e97fab98c219ff235931281c7bf93a9..b39b3ab6a20c0b968d97b48c62be241a1199be21 100644 --- a/src/main/java/org/olat/modules/assessment/manager/AssessmentEntryDAO.java +++ b/src/main/java/org/olat/modules/assessment/manager/AssessmentEntryDAO.java @@ -103,7 +103,7 @@ public class AssessmentEntryDAO { if(referenceSoftKey != null) { sb.append(" and referenceEntry.softkey=:softkey"); } else { - sb.append(" and referenceEntry.softkey is null"); + sb.append(" and data.referenceEntry is null"); } TypedQuery<AssessmentEntry> query = dbInstance.getCurrentEntityManager() diff --git a/src/main/java/org/olat/modules/assessment/ui/AssessedIdentityListController.java b/src/main/java/org/olat/modules/assessment/ui/AssessedIdentityListController.java new file mode 100644 index 0000000000000000000000000000000000000000..2fcb010329971c30b53d595062a2a43fa0cb84f1 --- /dev/null +++ b/src/main/java/org/olat/modules/assessment/ui/AssessedIdentityListController.java @@ -0,0 +1,279 @@ +/** + * <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.modules.assessment.ui; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import org.olat.basesecurity.BaseSecurity; +import org.olat.basesecurity.BaseSecurityModule; +import org.olat.core.commons.persistence.SortKey; +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.impl.FormBasicController; +import org.olat.core.gui.components.form.flexible.impl.FormEvent; +import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiColumnModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiTableDataModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiColumnDef; +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.SelectionEvent; +import org.olat.core.gui.components.form.flexible.impl.elements.table.SortableFlexiTableDataModel; +import org.olat.core.gui.components.stack.TooledStackedPanel; +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.id.Identity; +import org.olat.core.util.Util; +import org.olat.core.util.coordinate.CoordinatorManager; +import org.olat.core.util.event.GenericEventListener; +import org.olat.course.CourseFactory; +import org.olat.course.ICourse; +import org.olat.course.assessment.AssessedIdentitiesTableDataModel; +import org.olat.course.assessment.AssessmentMainController; +import org.olat.course.assessment.AssessmentToolManager; +import org.olat.course.assessment.IAssessmentCallback; +import org.olat.course.assessment.IdentityAssessmentEditController; +import org.olat.course.assessment.model.SearchAssessedIdentityParams; +import org.olat.course.certificate.CertificateEvent; +import org.olat.course.certificate.CertificateLight; +import org.olat.course.certificate.CertificatesManager; +import org.olat.course.certificate.ui.DownloadCertificateCellRenderer; +import org.olat.repository.RepositoryEntry; +import org.olat.user.UserManager; +import org.olat.user.propertyhandlers.UserPropertyHandler; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 21.07.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class AssessedIdentityListController extends FormBasicController implements GenericEventListener { + + public static final int USER_PROPS_OFFSET = 500; + public static final String usageIdentifyer = AssessedIdentitiesTableDataModel.usageIdentifyer; + + private FlexiTableElement tableEl; + private AssessedUserTableModel usersTableModel; + private final List<UserPropertyHandler> userPropertyHandlers; + private final TooledStackedPanel stackPanel; + + private RepositoryEntry entry; + private final boolean isAdministrativeUser; + private final IAssessmentCallback assessmentCallback; + + @Autowired + private UserManager userManager; + @Autowired + private BaseSecurity securityManager; + @Autowired + private BaseSecurityModule securityModule; + @Autowired + private CertificatesManager certificatesManager; + @Autowired + private AssessmentToolManager assessmentToolManager; + + + public AssessedIdentityListController(UserRequest ureq, WindowControl wControl, + TooledStackedPanel stackPanel, RepositoryEntry entry, IAssessmentCallback assessmentCallback) { + super(ureq, wControl, "identities"); + setTranslator(Util.createPackageTranslator(AssessmentMainController.class, getLocale(), getTranslator())); + setTranslator(userManager.getPropertyHandlerTranslator(getTranslator())); + + this.stackPanel = stackPanel; + this.entry = entry; + this.assessmentCallback = assessmentCallback; + isAdministrativeUser = securityModule.isUserAllowedAdminProps(ureq.getUserSession().getRoles()); + userPropertyHandlers = UserManager.getInstance().getUserPropertyHandlersFor(usageIdentifyer, isAdministrativeUser); + + initForm(ureq); + updateModel(); + + // Register for assessment changed events + CoordinatorManager.getInstance().getCoordinator().getEventBus() + .registerFor(this, getIdentity(), CertificatesManager.ORES_CERTIFICATE_EVENT); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + //add the table + FlexiTableColumnModel columnsModel = FlexiTableDataModelFactory.createFlexiTableColumnModel(); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(UserCols.username, "select")); + + int colIndex = USER_PROPS_OFFSET; + for (int i = 0; i < userPropertyHandlers.size(); i++) { + UserPropertyHandler userPropertyHandler = userPropertyHandlers.get(i); + boolean visible = UserManager.getInstance().isMandatoryUserProperty(usageIdentifyer , userPropertyHandler); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(visible, userPropertyHandler.i18nColumnDescriptorLabelKey(), colIndex++, "select", false, null)); + } + + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(UserCols.certificate, new DownloadCertificateCellRenderer())); + + usersTableModel = new AssessedUserTableModel(columnsModel); + tableEl = uifactory.addTableElement(getWindowControl(), "identities", usersTableModel, getTranslator(), formLayout); + tableEl.setExportEnabled(true); + } + + private void updateModel() { + SearchAssessedIdentityParams params = new SearchAssessedIdentityParams(entry); + params.setWithCertificates(true); + List<Identity> assessedIdentities = assessmentToolManager.getAssessedIdentities(params); + List<AssessedIdentityRow> rows = new ArrayList<>(assessedIdentities.size()); + for(Identity assessedIdentity:assessedIdentities) { + rows.add(new AssessedIdentityRow(assessedIdentity, userPropertyHandlers, getLocale())); + } + usersTableModel.setObjects(rows); + + ConcurrentMap<Long, CertificateLight> certificates = new ConcurrentHashMap<>(); + List<CertificateLight> certificateList = certificatesManager.getLastCertificates(entry.getOlatResource()); + for(CertificateLight certificate:certificateList) { + CertificateLight currentCertificate = certificates.get(certificate.getIdentityKey()); + if(currentCertificate == null || currentCertificate.getCreationDate().before(certificate.getCreationDate())) { + certificates.put(certificate.getIdentityKey(), certificate); + } + } + usersTableModel.setCertificates(certificates); + } + + private void updateCertificate(Long certificateKey) { + CertificateLight certificate = certificatesManager.getCertificateLightById(certificateKey); + usersTableModel.putCertificate(certificate); + tableEl.getComponent().setDirty(true); + } + + @Override + protected void doDispose() { + // + } + + @Override + public void event(Event event) { + if(event instanceof CertificateEvent) { + CertificateEvent ce = (CertificateEvent)event; + if(entry.getOlatResource().getKey().equals(ce.getResourceKey())) { + updateCertificate(ce.getCertificateKey()); + } + } + } + + @Override + protected void formOK(UserRequest ureq) { + // + } + + @Override + protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { + if(tableEl == source) { + if(event instanceof SelectionEvent) { + SelectionEvent se = (SelectionEvent)event; + String cmd = se.getCommand(); + AssessedIdentityRow selectedRow = usersTableModel.getObject(se.getIndex()); + if("select".equals(cmd)) { + doSelectUser(ureq, selectedRow); + } + } + } else if(source instanceof FormLink) { + FormLink link = (FormLink)source; + if("download-cert".equals(link.getCmd())) { + + } + } + super.formInnerEvent(ureq, source, event); + } + + private void doSelectUser(UserRequest ureq, AssessedIdentityRow row) { + ICourse course = CourseFactory.loadCourse(entry.getOlatResource()); + Identity assessedIdentity = securityManager.loadIdentityByKey(row.getIdentityKey()); + IdentityAssessmentEditController userController = new IdentityAssessmentEditController(getWindowControl(), ureq, + stackPanel, assessedIdentity, course, true, false, true); + listenTo(userController); + String fullname = userManager.getUserDisplayName(assessedIdentity); + stackPanel.pushController(fullname, userController); + } + + public static class AssessedUserTableModel extends DefaultFlexiTableDataModel<AssessedIdentityRow> implements SortableFlexiTableDataModel<AssessedIdentityRow> { + + private ConcurrentMap<Long, CertificateLight> certificates; + + public AssessedUserTableModel(FlexiTableColumnModel columnModel) { + super(columnModel); + } + + public void setCertificates(ConcurrentMap<Long, CertificateLight> certificates) { + this.certificates = certificates; + } + + public void putCertificate(CertificateLight certificate) { + if(certificates != null) { + certificates.put(certificate.getIdentityKey(), certificate); + } + } + + @Override + public void sort(SortKey sortKey) { + // + } + + @Override + public Object getValueAt(int row, int col) { + AssessedIdentityRow identityRow = getObject(row); + return getValueAt(identityRow, col); + } + + @Override + public Object getValueAt(AssessedIdentityRow row, int col) { + if(col >= 0 && col < UserCols.values().length) { + switch(UserCols.values()[col]) { + case username: return row.getIdentityName(); + case certificate: return certificates.get(row.getIdentityKey()); + } + } + int propPos = col - USER_PROPS_OFFSET; + return row.getIdentityProp(propPos); + } + + @Override + public DefaultFlexiTableDataModel<AssessedIdentityRow> createCopyWithEmptyList() { + return new AssessedUserTableModel(getTableColumnModel()); + } + } + + public enum UserCols implements FlexiColumnDef { + username("table.header.name"), + certificate("table.header.certificate"); + + private final String i18nKey; + + private UserCols(String i18nKey) { + this.i18nKey = i18nKey; + } + + public String i18nHeaderKey() { + return i18nKey; + } + } +} diff --git a/src/main/java/org/olat/modules/assessment/ui/AssessedIdentityRow.java b/src/main/java/org/olat/modules/assessment/ui/AssessedIdentityRow.java new file mode 100644 index 0000000000000000000000000000000000000000..58661ada047c8823891bea2c1efd5c65d063bb7c --- /dev/null +++ b/src/main/java/org/olat/modules/assessment/ui/AssessedIdentityRow.java @@ -0,0 +1,44 @@ +/** + * <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.modules.assessment.ui; + +import java.util.List; +import java.util.Locale; + +import org.olat.core.id.Identity; +import org.olat.user.UserPropertiesRow; +import org.olat.user.propertyhandlers.UserPropertyHandler; + +/** + * + * Initial date: 21.07.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class AssessedIdentityRow extends UserPropertiesRow { + + + public AssessedIdentityRow(Identity identity, List<UserPropertyHandler> userPropertyHandlers, Locale locale) { + super(identity, userPropertyHandlers, locale); + } + + + +} diff --git a/src/main/java/org/olat/modules/assessment/ui/AssessmentOverviewController.java b/src/main/java/org/olat/modules/assessment/ui/AssessmentOverviewController.java new file mode 100644 index 0000000000000000000000000000000000000000..17ebf1ef3270fc0fccc48b614220229a2a0293bf --- /dev/null +++ b/src/main/java/org/olat/modules/assessment/ui/AssessmentOverviewController.java @@ -0,0 +1,143 @@ +/** + * <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.modules.assessment.ui; + +import java.util.List; + +import org.olat.core.commons.fullWebApp.LayoutMain3ColsController; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.panel.Panel; +import org.olat.core.gui.components.stack.TooledStackedPanel; +import org.olat.core.gui.components.tree.GenericTreeModel; +import org.olat.core.gui.components.tree.GenericTreeNode; +import org.olat.core.gui.components.tree.MenuTree; +import org.olat.core.gui.components.tree.TreeModel; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.control.controller.MainLayoutBasicController; +import org.olat.core.gui.control.generic.dtabs.Activateable2; +import org.olat.core.id.context.ContextEntry; +import org.olat.core.id.context.StateEntry; +import org.olat.core.util.Util; +import org.olat.course.assessment.AssessmentMainController; +import org.olat.course.assessment.IAssessmentCallback; +import org.olat.repository.RepositoryEntry; + +/** + * + * Initial date: 21.07.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class AssessmentOverviewController extends MainLayoutBasicController implements Activateable2 { + + private MenuTree menuTree; + private final Panel mainPanel; + private TooledStackedPanel stackPanel; + + private RepositoryEntry entry; + private IAssessmentCallback assessmentCallback; + + public AssessmentOverviewController(UserRequest ureq, WindowControl wControl, TooledStackedPanel stackPanel, + RepositoryEntry entry, IAssessmentCallback assessmentCallback) { + super(ureq, wControl); + setTranslator(Util.createPackageTranslator(AssessmentMainController.class, getLocale(), getTranslator())); + this.entry = entry; + this.stackPanel = stackPanel; + this.assessmentCallback = assessmentCallback; + + mainPanel = new Panel("assessmentToolv2"); + + // Navigation menu + menuTree = new MenuTree("menuTree"); + TreeModel tm = buildTreeModel(); + menuTree.setTreeModel(tm); + menuTree.setSelectedNodeId(tm.getRootNode().getIdent()); + menuTree.addListener(this); + + LayoutMain3ColsController columLayoutCtr = new LayoutMain3ColsController(ureq, getWindowControl(), menuTree, mainPanel, "course" + entry.getResourceableId()); + listenTo(columLayoutCtr); // cleanup on dispose + putInitialPanel(columLayoutCtr.getInitialComponent()); + } + + private TreeModel buildTreeModel() { + GenericTreeNode root, gtn; + + GenericTreeModel gtm = new GenericTreeModel(); + root = new GenericTreeNode(); + root.setTitle(translate("menu.index")); + root.setUserObject("index"); + root.setAltText(translate("menu.index.alt")); + gtm.setRootNode(root); + + gtn = new GenericTreeNode(); + gtn.setTitle(translate("menu.userfocus")); + gtn.setUserObject("users"); + gtn.setAltText(translate("menu.userfocus.alt")); + gtn.setCssClass("o_sel_assessment_tool_users"); + root.addChild(gtn); + + return gtm; + } + + @Override + protected void doDispose() { + // + } + + @Override + public void activate(UserRequest ureq, List<ContextEntry> entries, StateEntry state) { + // + } + + @Override + protected void event(UserRequest ureq, Component source, Event event) { + if (source == menuTree) { + if (event.getCommand().equals(MenuTree.COMMAND_TREENODE_CLICKED)) { + Object uo = menuTree.getSelectedNode().getUserObject(); + if("groups".equals(uo)) { + doSelectGroupView(); + } else if("courseNodes".equals(uo)) { + doSelectCourseNodesView(); + } else if("users".equals(uo)) { + doSelectUsersView(ureq); + } + } + } + } + + private void doSelectGroupView() { + + } + + private void doSelectCourseNodesView() { + + } + + private void doSelectUsersView(UserRequest ureq) { + AssessedIdentityListController listController = new AssessedIdentityListController(ureq, getWindowControl(), stackPanel, + entry, assessmentCallback); + listenTo(listController); + mainPanel.setContent(listController.getInitialComponent()); + } + + +} diff --git a/src/main/java/org/olat/modules/assessment/ui/_content/identities.html b/src/main/java/org/olat/modules/assessment/ui/_content/identities.html new file mode 100644 index 0000000000000000000000000000000000000000..42a472edfd4b53b23f921ce1e1734d813c08e393 --- /dev/null +++ b/src/main/java/org/olat/modules/assessment/ui/_content/identities.html @@ -0,0 +1 @@ +$r.render("identities") \ No newline at end of file diff --git a/src/main/java/org/olat/repository/ui/RepositoryEntryRuntimeController.java b/src/main/java/org/olat/repository/ui/RepositoryEntryRuntimeController.java index dbf051d677ecf40307ecf058841aea7f54b82d6b..2d8de3e06376d2121a5233cc7af806a4f55565eb 100644 --- a/src/main/java/org/olat/repository/ui/RepositoryEntryRuntimeController.java +++ b/src/main/java/org/olat/repository/ui/RepositoryEntryRuntimeController.java @@ -288,6 +288,25 @@ public class RepositoryEntryRuntimeController extends MainLayoutBasicController } protected void initToolbar(Dropdown toolsDropdown, Dropdown settingsDropdown) { + initRuntimeTools(toolsDropdown); + initSettingsTools(settingsDropdown); + initEditionTools(settingsDropdown); + + detailsLink = LinkFactory.createToolLink("details", translate("details.header"), this, "o_sel_repo_details"); + detailsLink.setIconLeftCSS("o_icon o_icon-fw o_icon_details"); + detailsLink.setElementCssClass("o_sel_author_details"); + detailsLink.setVisible(showInfos); + toolbarPanel.addTool(detailsLink); + + boolean marked = markManager.isMarked(re, getIdentity(), null); + String css = marked ? Mark.MARK_CSS_ICON : Mark.MARK_ADD_CSS_ICON; + bookmarkLink = LinkFactory.createToolLink("bookmark", translate("details.bookmark.label"), this, css); + bookmarkLink.setTitle(translate(marked ? "details.bookmark.remove" : "details.bookmark")); + bookmarkLink.setVisible(allowBookmark); + toolbarPanel.addTool(bookmarkLink, Align.right); + } + + protected void initRuntimeTools(Dropdown toolsDropdown) { if (reSecurity.isEntryAdmin()) { //tools if(handler.supportsEdit(re.getOlatResource()) == EditionSupport.yes) { @@ -308,22 +327,6 @@ public class RepositoryEntryRuntimeController extends MainLayoutBasicController ordersLink.setEnabled(booking); toolsDropdown.addComponent(ordersLink); } - - initSettingsTools(settingsDropdown); - initEditionTools(settingsDropdown); - - detailsLink = LinkFactory.createToolLink("details", translate("details.header"), this, "o_sel_repo_details"); - detailsLink.setIconLeftCSS("o_icon o_icon-fw o_icon_details"); - detailsLink.setElementCssClass("o_sel_author_details"); - detailsLink.setVisible(showInfos); - toolbarPanel.addTool(detailsLink); - - boolean marked = markManager.isMarked(re, getIdentity(), null); - String css = marked ? Mark.MARK_CSS_ICON : Mark.MARK_ADD_CSS_ICON; - bookmarkLink = LinkFactory.createToolLink("bookmark", translate("details.bookmark.label"), this, css); - bookmarkLink.setTitle(translate(marked ? "details.bookmark.remove" : "details.bookmark")); - bookmarkLink.setVisible(allowBookmark); - toolbarPanel.addTool(bookmarkLink, Align.right); } protected void initSettingsTools(Dropdown settingsDropdown) { diff --git a/src/main/resources/database/mysql/alter_10_x_0_to_11_0_0.sql b/src/main/resources/database/mysql/alter_10_x_0_to_11_0_0.sql index e53734ee89a5d429f35b9f00227e406ba9110133..b242685640fd1ddbd38c98841f65ab8b53e00cdc 100644 --- a/src/main/resources/database/mysql/alter_10_x_0_to_11_0_0.sql +++ b/src/main/resources/database/mysql/alter_10_x_0_to_11_0_0.sql @@ -12,7 +12,7 @@ create table o_as_entry ( a_coach_comment text, fk_entry bigint not null, a_subident varchar(64), - fk_reference_entry bigint not null, + fk_reference_entry bigint, fk_identity bigint not null, primary key (id), unique (fk_identity, fk_entry, a_subident) @@ -34,6 +34,8 @@ create table o_qti_assessment_session ( q_author_mode bit not null default 0, q_finish_time datetime, q_termination_time datetime, + q_score float(65,30) default null, + q_passed bit default null, q_storage varchar(32), fk_reference_entry bigint not null, fk_entry bigint, diff --git a/src/main/resources/database/mysql/setupDatabase.sql b/src/main/resources/database/mysql/setupDatabase.sql index 497e85e3bcfd4bdcbdb4004536da40f62c1a5056..b9b28f6a4a03ed221fd469b19b41f56e70acc2da 100644 --- a/src/main/resources/database/mysql/setupDatabase.sql +++ b/src/main/resources/database/mysql/setupDatabase.sql @@ -1078,7 +1078,7 @@ create table o_as_entry ( a_coach_comment text, fk_entry bigint not null, a_subident varchar(64), - fk_reference_entry bigint not null, + fk_reference_entry bigint, fk_identity bigint not null, primary key (id), unique (fk_identity, fk_entry, a_subident) @@ -1220,6 +1220,8 @@ create table o_qti_assessment_session ( q_author_mode bit not null default 0, q_finish_time datetime, q_termination_time datetime, + q_score float(65,30) default null, + q_passed bit default null, q_storage varchar(32), fk_reference_entry bigint not null, fk_entry bigint, diff --git a/src/main/resources/database/postgresql/alter_10_x_0_to_11_0_0.sql b/src/main/resources/database/postgresql/alter_10_x_0_to_11_0_0.sql index 6e76bea71363662e1dfb571877ab3f079bde77f2..282cb2c2a1cfc14db8b7ea561efaeaaf6ee959e4 100644 --- a/src/main/resources/database/postgresql/alter_10_x_0_to_11_0_0.sql +++ b/src/main/resources/database/postgresql/alter_10_x_0_to_11_0_0.sql @@ -12,7 +12,7 @@ create table o_as_entry ( a_coach_comment text, fk_entry int8 not null, a_subident varchar(64), - fk_reference_entry int8 not null, + fk_reference_entry int8, fk_identity int8 not null, primary key (id), unique(fk_identity, fk_entry, a_subident) @@ -36,6 +36,8 @@ create table o_qti_assessment_session ( q_author_mode bool default false, q_finish_time timestamp, q_termination_time timestamp, + q_score decimal default null, + q_passed bool default null, q_storage varchar(32), fk_reference_entry int8 not null, fk_entry int8, diff --git a/src/main/resources/database/postgresql/setupDatabase.sql b/src/main/resources/database/postgresql/setupDatabase.sql index edb770723b1162ab77806966b7885e1944ad303c..c02e6d65d047566b7875d56ef4b18ecee45e0185 100644 --- a/src/main/resources/database/postgresql/setupDatabase.sql +++ b/src/main/resources/database/postgresql/setupDatabase.sql @@ -1079,7 +1079,7 @@ create table o_as_entry ( a_coach_comment text, fk_entry int8 not null, a_subident varchar(64), - fk_reference_entry int8 not null, + fk_reference_entry int8, fk_identity int8 not null, primary key (id), unique(fk_identity, fk_entry, a_subident) @@ -1221,6 +1221,8 @@ create table o_qti_assessment_session ( q_author_mode bool default false, q_finish_time timestamp, q_termination_time timestamp, + q_score decimal default null, + q_passed bool default null, q_storage varchar(32), fk_reference_entry int8 not null, fk_entry int8,