Skip to content

Commit 0cdfff1

Browse files
authored
SAK-50685 Rubrics concurrent grading duplicate new evaluations (#14307)
1 parent 69a94ff commit 0cdfff1

File tree

2 files changed

+61
-19
lines changed

2 files changed

+61
-19
lines changed

rubrics/impl/src/main/java/org/sakaiproject/rubrics/impl/RubricsServiceImpl.java

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -872,9 +872,34 @@ public EvaluationTransferBean saveEvaluation(EvaluationTransferBean evaluationBe
872872

873873
Evaluation evaluation;
874874
List<Long> newOutcomesCriterionIds = new ArrayList<>();
875+
875876
if (evaluationBean.getId() != null) {
877+
// UI knows the evaluation ID, fetch it directly
876878
evaluation = evaluationRepository.getById(evaluationBean.getId());
879+
} else {
880+
// UI thinks this is new, but check if an evaluation exists for this association and item
881+
evaluation = evaluationRepository.findByAssociationIdAndEvaluatedItemIdAndOwner(
882+
evaluationBean.getAssociationId(),
883+
evaluationBean.getEvaluatedItemId(),
884+
evaluationBean.getEvaluatedItemOwnerId())
885+
.orElseGet(() -> {
886+
// Create a new evaluation with outcomes from the bean
887+
Evaluation e = new Evaluation();
888+
e.getCriterionOutcomes().addAll(evaluationBean.getCriterionOutcomes().stream().map(o -> {
889+
CriterionOutcome outcome = new CriterionOutcome();
890+
outcome.setCriterionId(o.getCriterionId());
891+
outcome.setPoints(o.getPoints());
892+
outcome.setComments(o.getComments());
893+
outcome.setPointsAdjusted(o.getPointsAdjusted());
894+
outcome.setSelectedRatingId(o.getSelectedRatingId());
895+
return outcome;
896+
}).toList());
897+
return e;
898+
});
899+
}
877900

901+
// If evaluation already exists (has an ID), merge the criterion outcomes
902+
if (evaluation.getId() != null) {
878903
List<CriterionOutcome> outcomes = evaluation.getCriterionOutcomes();
879904
List<Long> outcomeIds = outcomes.stream().map(CriterionOutcome::getCriterionId).collect(Collectors.toList());
880905

@@ -914,17 +939,6 @@ public EvaluationTransferBean saveEvaluation(EvaluationTransferBean evaluationBe
914939
}
915940
// outcomeIds should be empty, if not the db contained outcomes not reported in the ui so remove them
916941
outcomes.removeIf(o -> outcomeIds.contains(o.getCriterionId()));
917-
} else {
918-
evaluation = new Evaluation();
919-
evaluation.getCriterionOutcomes().addAll(evaluationBean.getCriterionOutcomes().stream().map(o -> {
920-
CriterionOutcome outcome = new CriterionOutcome();
921-
outcome.setCriterionId(o.getCriterionId());
922-
outcome.setPoints(o.getPoints());
923-
outcome.setComments(o.getComments());
924-
outcome.setPointsAdjusted(o.getPointsAdjusted());
925-
outcome.setSelectedRatingId(o.getSelectedRatingId());
926-
return outcome;
927-
}).collect(Collectors.toList()));
928942
}
929943

930944
// only set these once
@@ -950,11 +964,11 @@ public EvaluationTransferBean saveEvaluation(EvaluationTransferBean evaluationBe
950964
ReturnedEvaluation returnedEvaluation = returnedEvaluationRepository.findByOriginalEvaluationId(evaluation.getId())
951965
.map(re -> {
952966
re.setOverallComment(savedEvaluation.getOverallComment());
953-
Map<Long, CriterionOutcome> outcomes = savedEvaluation.getCriterionOutcomes().stream()
967+
Map<Long, CriterionOutcome> outcomesById = savedEvaluation.getCriterionOutcomes().stream()
954968
.collect(Collectors.toMap(CriterionOutcome::getCriterionId, co -> co));
955-
re.getCriterionOutcomes().removeIf(o -> outcomes.get(o.getCriterionId()) == null);
969+
re.getCriterionOutcomes().removeIf(o -> outcomesById.get(o.getCriterionId()) == null);
956970
re.getCriterionOutcomes().forEach(rco -> {
957-
CriterionOutcome o = outcomes.get(rco.getCriterionId());
971+
CriterionOutcome o = outcomesById.get(rco.getCriterionId());
958972
rco.setSelectedRatingId(o.getSelectedRatingId());
959973
rco.setPointsAdjusted(o.getPointsAdjusted());
960974
rco.setPoints(o.getPoints());

rubrics/impl/src/test/org/sakaiproject/rubrics/impl/test/RubricsServiceTests.java

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,14 +81,9 @@
8181
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
8282
import org.springframework.test.util.AopTestUtils;
8383

84-
import javax.xml.parsers.DocumentBuilder;
85-
import javax.xml.parsers.DocumentBuilderFactory;
86-
import javax.xml.parsers.ParserConfigurationException;
87-
8884
import org.w3c.dom.CDATASection;
8985
import org.w3c.dom.Document;
9086
import org.w3c.dom.Element;
91-
import org.w3c.dom.Node;
9287
import org.w3c.dom.NodeList;
9388

9489
import lombok.extern.slf4j.Slf4j;
@@ -1035,6 +1030,39 @@ public void merge() {
10351030
assertTrue(newTitles.contains(extraTitle));
10361031
}
10371032

1033+
@Test
1034+
public void saveDuplicateNewEvaluations() throws Exception {
1035+
switchToInstructor();
1036+
RubricTransferBean rubric = rubricsService.createDefaultRubric(siteId);
1037+
String toolId = "sakai.assignment";
1038+
String toolItemId = "item-concurrent";
1039+
Map<String, String> rbcsParams = new HashMap<>();
1040+
rbcsParams.put(RubricsConstants.RBCS_ASSOCIATE, "1");
1041+
rbcsParams.put(RubricsConstants.RBCS_LIST, rubric.getId().toString());
1042+
ToolItemRubricAssociation association = rubricsService.saveRubricAssociation(toolId, toolItemId, rbcsParams)
1043+
.orElseThrow(() -> new IllegalStateException("Association not created"));
1044+
1045+
EvaluationTransferBean evaluation = buildEvaluation(association.getId(), rubric, toolItemId);
1046+
EvaluationTransferBean eval1 = rubricsService.saveEvaluation(evaluation, siteId);
1047+
EvaluationTransferBean eval2 = rubricsService.saveEvaluation(evaluation, siteId); // duplicate save from the same instructor
1048+
evaluation.setEvaluatorId(user3);
1049+
EvaluationTransferBean eval3 = rubricsService.saveEvaluation(evaluation, siteId); // duplicate save from different instructor
1050+
1051+
assertNotNull(eval1);
1052+
assertNotNull(eval2);
1053+
assertNotNull(eval3);
1054+
assertEquals(eval1.getId(), eval2.getId());
1055+
assertEquals(eval1.getId(), eval3.getId());
1056+
1057+
List<Evaluation> evaluations = evaluationRepository.findByAssociationId(association.getId());
1058+
assertEquals(1, evaluations.size());
1059+
assertEquals(eval1.getId(), evaluations.get(0).getId());
1060+
assertEquals(user3, evaluations.get(0).getEvaluatorId());
1061+
1062+
assertTrue(evaluationRepository.findByAssociationIdAndEvaluatedItemId(association.getId(), toolItemId).isPresent());
1063+
assertTrue(evaluationRepository.findByAssociationIdAndEvaluatedItemIdAndOwner(association.getId(), toolItemId, user2).isPresent());
1064+
}
1065+
10381066
private EvaluationTransferBean buildEvaluation(Long associationId, RubricTransferBean rubricBean, String toolItemId) {
10391067

10401068
EvaluationTransferBean etb = new EvaluationTransferBean();

0 commit comments

Comments
 (0)