From 99c58227e83af60316f8c2135077c60455a45052 Mon Sep 17 00:00:00 2001 From: uhensler <urs.hensler@frentix.com> Date: Fri, 20 Mar 2020 15:31:05 +0100 Subject: [PATCH] OO-4582: Calculate max points of structure nodes --- .../course/assessment/AssessmentHelper.java | 4 +- .../editor/overview/OverviewDataModel.java | 4 +- .../org/olat/course/nodes/STCourseNode.java | 2 +- .../st/assessment/MaxScoreCumulator.java | 111 +++++++++ .../st/assessment/STAssessmentConfig.java | 22 +- .../st/assessment/STAssessmentHandler.java | 6 +- .../st/assessment/MaxScoreCumulatorTest.java | 228 ++++++++++++++++++ .../java/org/olat/test/AllTestsJunit4.java | 2 + 8 files changed, 367 insertions(+), 12 deletions(-) create mode 100644 src/main/java/org/olat/course/nodes/st/assessment/MaxScoreCumulator.java create mode 100644 src/test/java/org/olat/course/nodes/st/assessment/MaxScoreCumulatorTest.java diff --git a/src/main/java/org/olat/course/assessment/AssessmentHelper.java b/src/main/java/org/olat/course/assessment/AssessmentHelper.java index 34f8e89fb46..3fb3701ed7f 100644 --- a/src/main/java/org/olat/course/assessment/AssessmentHelper.java +++ b/src/main/java/org/olat/course/assessment/AssessmentHelper.java @@ -451,8 +451,8 @@ public class AssessmentHelper { assessmentNodeData.setScore(score); hasDisplayableUserValues = true; } - if(Mode.setByNode == assessmentConfig.getScoreMode() || Mode.setByNode == assessmentConfig.getPassedMode()) { - assessmentNodeData.setMaxScore(assessmentConfig.getMaxScore()); + assessmentNodeData.setMaxScore(assessmentConfig.getMaxScore()); + if(Mode.setByNode == assessmentConfig.getScoreMode()) { assessmentNodeData.setMinScore(assessmentConfig.getMinScore()); } } diff --git a/src/main/java/org/olat/course/editor/overview/OverviewDataModel.java b/src/main/java/org/olat/course/editor/overview/OverviewDataModel.java index 95744d3b7ee..122a32dca78 100644 --- a/src/main/java/org/olat/course/editor/overview/OverviewDataModel.java +++ b/src/main/java/org/olat/course/editor/overview/OverviewDataModel.java @@ -76,10 +76,10 @@ public class OverviewDataModel extends DefaultFlexiTreeTableDataModel<OverviewRo case score: return row.getAssessmentConfig().isAssessable() ? Boolean.valueOf(Mode.none != row.getAssessmentConfig().getScoreMode()) : null; - case scoreMin: return row.getAssessmentConfig().isAssessable() && Mode.none != row.getAssessmentConfig().getScoreMode() + case scoreMin: return row.getAssessmentConfig().isAssessable() && Mode.setByNode == row.getAssessmentConfig().getScoreMode() ? row.getAssessmentConfig().getMinScore() : null; - case scoreMax: return row.getAssessmentConfig().isAssessable() && Mode.none != row.getAssessmentConfig().getScoreMode() + case scoreMax: return row.getAssessmentConfig().isAssessable() && Mode.setByNode == row.getAssessmentConfig().getScoreMode() ? row.getAssessmentConfig().getMaxScore() : null; case passed: return row.getAssessmentConfig().isAssessable() diff --git a/src/main/java/org/olat/course/nodes/STCourseNode.java b/src/main/java/org/olat/course/nodes/STCourseNode.java index 936f25ea54a..915b78faa9b 100644 --- a/src/main/java/org/olat/course/nodes/STCourseNode.java +++ b/src/main/java/org/olat/course/nodes/STCourseNode.java @@ -380,7 +380,7 @@ public class STCourseNode extends AbstractAccessableCourseNode { STCourseNode stParent = getFirstSTParent(parent); if (stParent != null) { - boolean scoreCalculatorSupported = stParent.getModuleConfiguration().getBooleanSafe(CONFIG_SCORE_CALCULATOR_SUPPORTED); + boolean scoreCalculatorSupported = stParent.getModuleConfiguration().getBooleanSafe(CONFIG_SCORE_CALCULATOR_SUPPORTED, true); config.setBooleanEntry(CONFIG_SCORE_CALCULATOR_SUPPORTED, scoreCalculatorSupported); if (scoreCalculatorSupported) { scoreCalculator = new ScoreCalculator(); diff --git a/src/main/java/org/olat/course/nodes/st/assessment/MaxScoreCumulator.java b/src/main/java/org/olat/course/nodes/st/assessment/MaxScoreCumulator.java new file mode 100644 index 00000000000..18d4a7724b3 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/st/assessment/MaxScoreCumulator.java @@ -0,0 +1,111 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.course.nodes.st.assessment; + +import org.olat.core.CoreSpringFactory; +import org.olat.core.util.nodes.INode; +import org.olat.core.util.tree.TreeVisitor; +import org.olat.core.util.tree.Visitor; +import org.olat.course.assessment.CourseAssessmentService; +import org.olat.course.assessment.handler.AssessmentConfig; +import org.olat.course.assessment.handler.AssessmentConfig.Mode; +import org.olat.course.nodes.CourseNode; + +/** + * + * Initial date: 9 Mar 2020<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +class MaxScoreCumulator { + + public interface MaxScore { + + public Float getSum(); + + public Float getMax(); + } + + private CourseAssessmentService courseAssessmentService; + + MaxScore getMaxScore(CourseNode courseNode) { + return getMaxScore(courseNode, courseAssessmentService()); + } + + MaxScore getMaxScore(CourseNode courseNode, CourseAssessmentService courseAssessmentService) { + ScoreVisitor visitor = new ScoreVisitor(courseNode, courseAssessmentService); + TreeVisitor treeVisitor = new TreeVisitor(visitor, courseNode, true); + treeVisitor.visitAll(); + return visitor; + } + + private CourseAssessmentService courseAssessmentService() { + if (courseAssessmentService== null) { + courseAssessmentService = CoreSpringFactory.getImpl(CourseAssessmentService.class); + } + return courseAssessmentService; + } + + private final static class ScoreVisitor implements MaxScore, Visitor { + + private final CourseNode root; + private int count; + private float sum; + private float max = 0; + + private final CourseAssessmentService courseAssessmentService; + + private ScoreVisitor(CourseNode root, CourseAssessmentService courseAssessmentService) { + this.root = root; + this.courseAssessmentService = courseAssessmentService; + } + + @Override + public Float getSum() { + return count > 0? Float.valueOf(sum): null; + } + + @Override + public Float getMax() { + return count > 0? Float.valueOf(max): null; + } + + @Override + public void visit(INode node) { + if (node.getIdent().equals(root.getIdent())) return; + + if (node instanceof CourseNode) { + CourseNode courseNode = (CourseNode)node; + AssessmentConfig assessmentConfig = courseAssessmentService.getAssessmentConfig(courseNode); + if (Mode.setByNode == assessmentConfig.getScoreMode() && !assessmentConfig.ignoreInCourseAssessment()) { + Float maxScore = assessmentConfig.getMaxScore(); + if (maxScore != null) { + count++; + sum += maxScore.floatValue(); + if (max < maxScore.floatValue()) { + max = maxScore.floatValue(); + } + } + } + } + } + } + +} diff --git a/src/main/java/org/olat/course/nodes/st/assessment/STAssessmentConfig.java b/src/main/java/org/olat/course/nodes/st/assessment/STAssessmentConfig.java index ba8f9a99a1b..32a6f1f85b3 100644 --- a/src/main/java/org/olat/course/nodes/st/assessment/STAssessmentConfig.java +++ b/src/main/java/org/olat/course/nodes/st/assessment/STAssessmentConfig.java @@ -21,7 +21,9 @@ package org.olat.course.nodes.st.assessment; import org.olat.core.util.StringHelper; import org.olat.course.assessment.handler.AssessmentConfig; +import org.olat.course.nodes.CourseNode; import org.olat.course.nodes.STCourseNode; +import org.olat.course.nodes.st.assessment.MaxScoreCumulator.MaxScore; import org.olat.course.run.scoring.ScoreCalculator; import org.olat.modules.ModuleConfiguration; @@ -33,14 +35,20 @@ import org.olat.modules.ModuleConfiguration; */ public class STAssessmentConfig implements AssessmentConfig { + private static final MaxScoreCumulator MAX_SCORE_CUMULATOR = new MaxScoreCumulator(); + + private final CourseNode courseNode; private final boolean isRoot; private final ModuleConfiguration rootConfig; private final ScoreCalculator scoreCalculator; - public STAssessmentConfig(boolean isRoot, ModuleConfiguration rootConfig, ScoreCalculator scoreCalculator) { + public STAssessmentConfig(STCourseNode courseNode, boolean isRoot, ModuleConfiguration rootConfig) { + this.courseNode = courseNode; this.isRoot = isRoot; this.rootConfig = rootConfig; - this.scoreCalculator = scoreCalculator; + this.scoreCalculator = rootConfig.getBooleanSafe(STCourseNode.CONFIG_SCORE_CALCULATOR_SUPPORTED, true) + ? courseNode.getScoreCalculator() + : null; } @Override @@ -70,6 +78,16 @@ public class STAssessmentConfig implements AssessmentConfig { @Override public Float getMaxScore() { + if (scoreCalculator == null && rootConfig.has(STCourseNode.CONFIG_SCORE_KEY)) { + MaxScore maxScore = MAX_SCORE_CUMULATOR.getMaxScore(courseNode); + String scoreKey = rootConfig.getStringValue(STCourseNode.CONFIG_SCORE_KEY); + if (STCourseNode.CONFIG_SCORE_VALUE_SUM.equals(scoreKey)) { + return maxScore.getSum(); + } else if (STCourseNode.CONFIG_SCORE_VALUE_AVG.equals(scoreKey)) { + // max (not average) because the user was maybe only in one node assessed + return maxScore.getMax(); + } + } return null; } diff --git a/src/main/java/org/olat/course/nodes/st/assessment/STAssessmentHandler.java b/src/main/java/org/olat/course/nodes/st/assessment/STAssessmentHandler.java index b2f1851f6e2..bd10c94a280 100644 --- a/src/main/java/org/olat/course/nodes/st/assessment/STAssessmentHandler.java +++ b/src/main/java/org/olat/course/nodes/st/assessment/STAssessmentHandler.java @@ -50,7 +50,6 @@ import org.olat.course.run.scoring.LastModificationsEvaluator; import org.olat.course.run.scoring.ObligationEvaluator; import org.olat.course.run.scoring.PassedEvaluator; import org.olat.course.run.scoring.RootPassedEvaluator; -import org.olat.course.run.scoring.ScoreCalculator; import org.olat.course.run.scoring.ScoreEvaluator; import org.olat.course.run.scoring.StatusEvaluator; import org.olat.course.run.userview.UserCourseEnvironment; @@ -96,10 +95,7 @@ public class STAssessmentHandler implements AssessmentHandler { STCourseNode stCourseNode = (STCourseNode) courseNode; STCourseNode root = getRoot(courseNode); boolean isRoot = courseNode.getIdent().equals(root.getIdent()); - ScoreCalculator scoreCalclualtor = root.getModuleConfiguration().getBooleanSafe(STCourseNode.CONFIG_SCORE_CALCULATOR_SUPPORTED) - ? stCourseNode.getScoreCalculator() - : null; - return new STAssessmentConfig(isRoot, root.getModuleConfiguration(), scoreCalclualtor); + return new STAssessmentConfig(stCourseNode, isRoot, root.getModuleConfiguration()); } return NonAssessmentConfig.create(); } diff --git a/src/test/java/org/olat/course/nodes/st/assessment/MaxScoreCumulatorTest.java b/src/test/java/org/olat/course/nodes/st/assessment/MaxScoreCumulatorTest.java new file mode 100644 index 00000000000..78371967c58 --- /dev/null +++ b/src/test/java/org/olat/course/nodes/st/assessment/MaxScoreCumulatorTest.java @@ -0,0 +1,228 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.course.nodes.st.assessment; + +import static org.mockito.Mockito.when; + +import org.assertj.core.api.SoftAssertions; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.olat.course.assessment.CourseAssessmentService; +import org.olat.course.assessment.handler.AssessmentConfig; +import org.olat.course.assessment.handler.AssessmentConfig.Mode; +import org.olat.course.nodes.Card2BrainCourseNode; +import org.olat.course.nodes.CourseNode; +import org.olat.course.nodes.STCourseNode; +import org.olat.course.nodes.st.assessment.MaxScoreCumulator.MaxScore; + +/** + * + * Initial date: 20.03.2020<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +public class MaxScoreCumulatorTest { + + @Mock + private CourseAssessmentService courseAssessmentService; + + private MaxScoreCumulator sut = new MaxScoreCumulator(); + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void shouldGetCumulatedMaxScore() { + CourseNode parent = new STCourseNode(); + + CourseNode child1 = new Card2BrainCourseNode(); + parent.addChild(child1); + AssessmentConfig child1Config = new TestAssessmentConfig(Mode.setByNode, Float.valueOf(10), false); + when(courseAssessmentService.getAssessmentConfig(child1)).thenReturn(child1Config); + + CourseNode child2 = new Card2BrainCourseNode(); + parent.addChild(child2); + AssessmentConfig child2Config = new TestAssessmentConfig(Mode.setByNode, Float.valueOf(20), false); + when(courseAssessmentService.getAssessmentConfig(child2)).thenReturn(child2Config); + + CourseNode child3 = new Card2BrainCourseNode(); + parent.addChild(child3); + AssessmentConfig child3Config = new TestAssessmentConfig(Mode.setByNode, Float.valueOf(5), true); + when(courseAssessmentService.getAssessmentConfig(child3)).thenReturn(child3Config); + + CourseNode child4 = new Card2BrainCourseNode(); + parent.addChild(child4); + AssessmentConfig child4Config = new TestAssessmentConfig(Mode.setByNode, null, false); + when(courseAssessmentService.getAssessmentConfig(child4)).thenReturn(child4Config); + + CourseNode child5 = new Card2BrainCourseNode(); + parent.addChild(child5); + AssessmentConfig child5Config = new TestAssessmentConfig(Mode.none, Float.valueOf(10), false); + when(courseAssessmentService.getAssessmentConfig(child5)).thenReturn(child5Config); + + CourseNode child6 = new Card2BrainCourseNode(); + parent.addChild(child6); + AssessmentConfig child6Config = new TestAssessmentConfig(Mode.evaluated, Float.valueOf(10), false); + when(courseAssessmentService.getAssessmentConfig(child6)).thenReturn(child6Config); + + CourseNode child11 = new Card2BrainCourseNode(); + child6.addChild(child11); + AssessmentConfig child11Config = new TestAssessmentConfig(Mode.setByNode, Float.valueOf(50), false); + when(courseAssessmentService.getAssessmentConfig(child11)).thenReturn(child11Config); + + MaxScore maxScore = sut.getMaxScore(parent, courseAssessmentService); + + SoftAssertions softly = new SoftAssertions(); + softly.assertThat(maxScore.getSum()).isEqualTo(80); + softly.assertThat(maxScore.getMax()).isEqualTo(50); + softly.assertAll(); + } + + @Test + public void shouldReturnNullIfNoChildrenWithScore() { + CourseNode parent = new STCourseNode(); + + CourseNode child1 = new Card2BrainCourseNode(); + parent.addChild(child1); + AssessmentConfig child1Config = new TestAssessmentConfig(Mode.evaluated, Float.valueOf(10), false); + when(courseAssessmentService.getAssessmentConfig(child1)).thenReturn(child1Config); + + MaxScore maxScore = sut.getMaxScore(parent, courseAssessmentService); + + SoftAssertions softly = new SoftAssertions(); + softly.assertThat(maxScore.getSum()).isNull(); + softly.assertThat(maxScore.getMax()).isNull(); + softly.assertAll(); + } + + private static final class TestAssessmentConfig implements AssessmentConfig { + + private final Mode scoreMode; + private final Float maxScore; + private boolean ignoreInCourseAssessment; + + public TestAssessmentConfig(Mode scoreMode, Float maxScore, boolean ignoreInCourseAssessment) { + this.scoreMode = scoreMode; + this.maxScore = maxScore; + this.ignoreInCourseAssessment = ignoreInCourseAssessment; + } + + @Override + public boolean isAssessable() { + return false; + } + + @Override + public boolean ignoreInCourseAssessment() { + return ignoreInCourseAssessment; + } + + @Override + public void setIgnoreInCourseAssessment(boolean ignoreInCourseAssessment) { + this.ignoreInCourseAssessment = ignoreInCourseAssessment; + } + + @Override + public Mode getScoreMode() { + return scoreMode; + } + + @Override + public Float getMaxScore() { + return maxScore; + } + + @Override + public Float getMinScore() { + return null; + } + + @Override + public Mode getPassedMode() { + return null; + } + + @Override + public Float getCutValue() { + return null; + } + + @Override + public Mode getCompletionMode() { + return null; + } + + @Override + public boolean hasAttempts() { + return false; + } + + @Override + public boolean hasComment() { + return false; + } + + @Override + public boolean hasIndividualAsssessmentDocuments() { + return false; + } + + @Override + public boolean hasStatus() { + return false; + } + + @Override + public boolean isAssessedBusinessGroups() { + return false; + } + + @Override + public boolean isEditable() { + return false; + } + + @Override + public boolean isBulkEditable() { + return false; + } + + @Override + public boolean hasEditableDetails() { + return false; + } + + @Override + public boolean isExternalGrading() { + return false; + } + + @Override + public boolean isObligationOverridable() { + return false; + } + + } + +} diff --git a/src/test/java/org/olat/test/AllTestsJunit4.java b/src/test/java/org/olat/test/AllTestsJunit4.java index 9227622f1f7..5ba58a5d244 100644 --- a/src/test/java/org/olat/test/AllTestsJunit4.java +++ b/src/test/java/org/olat/test/AllTestsJunit4.java @@ -487,8 +487,10 @@ import org.junit.runners.Suite; org.olat.course.learningpath.manager.LearningPathNodeAccessProviderTest.class, org.olat.course.nodes.st.assessment.PassCounterTest.class, org.olat.course.nodes.st.assessment.CumulatingDurationEvaluatorTest.class, + org.olat.course.nodes.st.assessment.CumulatingScoreEvaluatorTest.class, org.olat.course.nodes.st.assessment.ConventionalSTCompletionEvaluatorTest.class, org.olat.course.nodes.st.assessment.MandatoryObligationEvaluatorTest.class, + org.olat.course.nodes.st.assessment.MaxScoreCumulatorTest.class, org.olat.course.nodes.st.assessment.STFullyAssessedEvaluatorTest.class, org.olat.course.nodes.st.assessment.STLastModificationsEvaluatorTest.class, org.olat.course.nodes.st.assessment.STRootPassedEvaluatorTest.class, -- GitLab