Skip to content

Commit 9360301

Browse files
authored
Merge pull request #2465 from NationalSecurityAgency/t#2464/learning_path
#2464: fixed learning path validation to consider whether the badge i…
2 parents a877de0 + 67a23eb commit 9360301

File tree

2 files changed

+54
-10
lines changed

2 files changed

+54
-10
lines changed

service/src/main/java/skills/services/admin/CircularLearningPathChecker.groovy

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class CircularLearningPathChecker {
3636
// private
3737
private BadgeAndSkillsLookup badgeAndSkillsLookup = new BadgeAndSkillsLookup()
3838
private PrerequisiteNodeLookup prerequisiteNodeLookup = new PrerequisiteNodeLookup()
39+
private Set<String> allItemIdsOnFinalLearningPath
3940
private SkillInfo start
4041

4142
// contains all of the badges by following start node in the opposite direction of prerequisite path
@@ -182,6 +183,7 @@ class CircularLearningPathChecker {
182183
SkillInfo skillInfo = new SkillInfo(projectId: skillDef.projectId, skillId: skillDef.skillId, name: skillDef.name, type: skillDef.type)
183184
SkillInfo prereqSkillInfo = new SkillInfo(projectId: prereqSkillDef.projectId, skillId: prereqSkillDef.skillId, name: prereqSkillDef.name, type: prereqSkillDef.type)
184185
List<SkillInfo> path = [skillInfo]
186+
allItemIdsOnFinalLearningPath = constructAllItemIdsOnFinalLearningPath(edgePairs, skillInfo, prereqSkillInfo)
185187

186188
startNodeBadgesOnParentPath = []
187189
collectParentBadges(skillInfo, startNodeBadgesOnParentPath, 0)
@@ -274,7 +276,9 @@ class CircularLearningPathChecker {
274276
badgesOnPath = badgesOnPath.findAll { it.type == SkillDef.ContainerType.Badge }
275277
for (SkillInfo badgeOnPathSkillInfo : badgesOnPath) {
276278
BadgeAndSkills badge = badgeAndSkillsLookup.getBadgeByBadgeId(badgeOnPathSkillInfo.skillId)
277-
if (badge.badgeHasSkillId(current.skillId) && badge.badgeGraphNode.skillId != current.circularCheckBadgeLoadedDueToPreviousSkillFollowingRouteOfBadgeId) {
279+
if (isBadgeOnCurrentLearningPath(badge) &&
280+
badge.badgeHasSkillId(current.skillId) &&
281+
badge.badgeGraphNode.skillId != current.circularCheckBadgeLoadedDueToPreviousSkillFollowingRouteOfBadgeId) {
278282
return new DependencyCheckResult(possible: false,
279283
failureType: DependencyCheckResult.FailureType.BadgeSkillIsAlreadyOnPath,
280284
violatingSkillId: current.skillId,
@@ -327,21 +331,36 @@ class CircularLearningPathChecker {
327331
private DependencyCheckResult checkBadgesForSkillOverlap(BadgeAndSkills badge, List<SkillInfo> checkAgainst) {
328332
for (SkillInfo badgeOnPathSkillInfo : checkAgainst) {
329333
BadgeAndSkills badgeOnPath = badgeAndSkillsLookup.getBadgeByBadgeId(badgeOnPathSkillInfo.skillId)
330-
SkillInfo found = badgeOnPath.skills.find { SkillInfo searchFor -> badge.skills.find { searchFor.skillId == it.skillId } }
331-
if (found) {
332-
return new DependencyCheckResult(possible: false,
333-
failureType: DependencyCheckResult.FailureType.BadgeOverlappingSkills,
334-
violatingSkillInBadgeId: badge.badgeGraphNode.skillId,
335-
violatingSkillInBadgeName: badge.badgeGraphNode.name,
336-
violatingSkillId: found.skillId,
337-
violatingSkillName: found.name,
338-
reason: "Multiple badges on the same Learning path cannot have overlapping skills. Both badge [${badge.badgeGraphNode.name}] and [${badgeOnPath.badgeGraphNode.name}] badge have [${found.name}] skill.")
334+
if (isBadgeOnCurrentLearningPath(badgeOnPath)) {
335+
SkillInfo found = badgeOnPath.skills.find { SkillInfo searchFor -> badge.skills.find { searchFor.skillId == it.skillId } }
336+
if (found) {
337+
return new DependencyCheckResult(possible: false,
338+
failureType: DependencyCheckResult.FailureType.BadgeOverlappingSkills,
339+
violatingSkillInBadgeId: badge.badgeGraphNode.skillId,
340+
violatingSkillInBadgeName: badge.badgeGraphNode.name,
341+
violatingSkillId: found.skillId,
342+
violatingSkillName: found.name,
343+
reason: "Multiple badges on the same Learning path cannot have overlapping skills. Both badge [${badge.badgeGraphNode.name}] and [${badgeOnPath.badgeGraphNode.name}] badge have [${found.name}] skill.")
344+
}
339345
}
340346
}
341347

342348
return new DependencyCheckResult()
343349
}
344350

351+
private Set<String> constructAllItemIdsOnFinalLearningPath(List<SkillDefGraphResPair> edgePairs, SkillInfo skillInfo, SkillInfo prereqSkillInfo) {
352+
Set res = edgePairs.collect {
353+
["${it.node.projectId}-${it.node.skillId}".toString(), "${it.prerequisite.projectId}-${it.prerequisite.skillId}".toString()]
354+
}.flatten().toSet()
355+
res.add("${skillInfo.projectId}-${skillInfo.skillId}".toString())
356+
res.add("${prereqSkillInfo.projectId}-${prereqSkillInfo.skillId}".toString())
357+
return res
358+
}
359+
private boolean isBadgeOnCurrentLearningPath(BadgeAndSkills badge) {
360+
boolean isBadgeOnPath = allItemIdsOnFinalLearningPath.contains("${badge.badgeGraphNode.projectId}-${badge.badgeGraphNode.skillId}".toString())
361+
return isBadgeOnPath
362+
}
363+
345364
private void collectParentBadges(SkillInfo start, List<SkillInfo> badges, int currentIteration) {
346365
if (currentIteration > 1000) {
347366
throw new IllegalStateException("Number of [1000] iterations exceeded when checking for circular dependency for [${start.skillId}]")

service/src/test/java/skills/intTests/dependentSkills/LearningPathValidationEndpointSpecs.groovy

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -742,4 +742,29 @@ class LearningPathValidationEndpointSpecs extends DefaultIntSpec {
742742
!result.violatingSkillName
743743
result.reason == "Discovered circular prerequisite [Skill:skill1 -> Skill:skill3 -> Badge:badge1(Skill:skill1)]"
744744
}
745+
746+
def "skills can be belong to multiple badges"() {
747+
def p1 = createProject(1)
748+
def p1subj1 = createSubject(1, 1)
749+
def p1Skills = createSkills(4, 1, 1, 100)
750+
skillsService.createProjectAndSubjectAndSkills(p1, p1subj1, p1Skills)
751+
752+
def badge1 = SkillsFactory.createBadge(1, 1)
753+
skillsService.createBadge(badge1)
754+
skillsService.assignSkillToBadge([projectId: p1.projectId, badgeId: badge1.badgeId, skillId: p1Skills[2].skillId])
755+
skillsService.assignSkillToBadge([projectId: p1.projectId, badgeId: badge1.badgeId, skillId: p1Skills[3].skillId])
756+
757+
def badge2 = SkillsFactory.createBadge(1, 2)
758+
skillsService.createBadge(badge2)
759+
skillsService.assignSkillToBadge([projectId: p1.projectId, badgeId: badge2.badgeId, skillId: p1Skills[0].skillId])
760+
skillsService.assignSkillToBadge([projectId: p1.projectId, badgeId: badge2.badgeId, skillId: p1Skills[3].skillId])
761+
762+
skillsService.addLearningPathPrerequisite(p1.projectId, p1Skills[3].skillId, p1Skills[2].skillId)
763+
764+
when:
765+
def result = skillsService.vadlidateLearningPathPrerequisite(p1.projectId, p1Skills[1].skillId, p1.projectId, p1Skills[0].skillId)
766+
767+
then:
768+
result.possible == true
769+
}
745770
}

0 commit comments

Comments
 (0)