From 15d1ebdb4607ba7abf816e3f24c040a21865af2a Mon Sep 17 00:00:00 2001
From: Don Walizer <12420708+dwalizer@users.noreply.github.com>
Date: Fri, 29 Sep 2023 07:57:21 -0400
Subject: [PATCH 1/5] #2425 Add display of when skill points expired
---
.../skill/progress/SkillProgress2.vue | 11 ++++++
.../client-display-skill-expiration_spec.js | 38 +++++++++++++++++++
e2e-tests/cypress/support/commands.js | 1 -
.../UserAchievementExpirationService.groovy | 13 +++++--
.../attributes/ExpirationAttrs.groovy | 1 +
.../skills/skillLoading/SkillsLoader.groovy | 6 +++
.../skillLoading/model/SkillSummary.groovy | 1 +
7 files changed, 66 insertions(+), 5 deletions(-)
diff --git a/client-display/src/userSkills/skill/progress/SkillProgress2.vue b/client-display/src/userSkills/skill/progress/SkillProgress2.vue
index fbbdebb1ad..f42fbe37cb 100644
--- a/client-display/src/userSkills/skill/progress/SkillProgress2.vue
+++ b/client-display/src/userSkills/skill/progress/SkillProgress2.vue
@@ -107,6 +107,11 @@ limitations under the License.
Expires {{ expirationDate(true) | relativeTime() }}, perform this skill to keep your points!
+
+
+ Points expired {{ skill.lastExpirationDate | relativeTime() }}
+
+
Pending Approval
@@ -315,6 +320,12 @@ limitations under the License.
showBadgesAndTagsRow() {
return ((this.skill.badges && this.skill.badges.length > 0 && !this.badgeId) || (this.skill.tags && this.skill.tags.length > 0));
},
+ showHasExpiredMessage() {
+ if (this.skillInternal && this.skillInternal.lastExpirationDate && this.skillInternal.points === 0) {
+ return true;
+ }
+ return false;
+ },
showMotivationalExpirationMessage() {
if (this.skillInternal && this.skillInternal.achievedOn && this.expirationDate() && this.skillInternal.isMotivationalSkill) {
if (this.motivationalSkillWarningGracePeriod) {
diff --git a/e2e-tests/cypress/e2e/client-display/client-display-skill-expiration_spec.js b/e2e-tests/cypress/e2e/client-display/client-display-skill-expiration_spec.js
index 0d2a93cb54..c9a98247cd 100644
--- a/e2e-tests/cypress/e2e/client-display/client-display-skill-expiration_spec.js
+++ b/e2e-tests/cypress/e2e/client-display/client-display-skill-expiration_spec.js
@@ -268,4 +268,42 @@ describe('Client Display Expiration Tests', () => {
cy.get(`[data-cy="expirationDate"]`).contains(`Expires ${thirdRuntime.fromNow()}`)
});
+
+ it('expired achievement shows how long ago in UI', () => {
+ cy.configureExpiration(1, 0, 1, 'DAILY');
+ cy.configureExpiration(2, 0, 1, 'DAILY');
+ cy.configureExpiration(3, 0, 3, 'DAILY');
+ const yesterday = moment.utc().subtract(1, 'day')
+ const twoDaysAgo = moment.utc().subtract(2, 'day')
+ cy.doReportSkill({ project: 1, skill: 1, subjNum: 1, userId: Cypress.env('proxyUser'), date: yesterday.format('YYYY-MM-DD HH:mm') })
+ cy.doReportSkill({ project: 1, skill: 1, subjNum: 1, userId: Cypress.env('proxyUser'), date: twoDaysAgo.format('YYYY-MM-DD HH:mm') })
+ cy.doReportSkill({ project: 1, skill: 2, subjNum: 1, userId: Cypress.env('proxyUser'), date: yesterday.format('YYYY-MM-DD HH:mm') })
+ cy.doReportSkill({ project: 1, skill: 2, subjNum: 1, userId: Cypress.env('proxyUser'), date: twoDaysAgo.format('YYYY-MM-DD HH:mm') })
+ cy.doReportSkill({ project: 1, skill: 3, subjNum: 1, userId: Cypress.env('proxyUser'), date: yesterday.format('YYYY-MM-DD HH:mm') })
+
+ cy.expireSkills();
+ cy.cdVisit('/');
+ cy.cdClickSubj(0);
+
+ cy.get(`[data-cy="skillProgress_index-0"] [data-cy="hasExpired"]`)
+ .should('exist');
+ cy.get(`[data-cy="skillProgress_index-0"] [data-cy="hasExpired"]`).contains(`Points expired a few seconds ago`)
+ cy.get(`[data-cy="skillProgress_index-1"] [data-cy="hasExpired"]`)
+ .should('exist');
+ cy.get(`[data-cy="skillProgress_index-1"] [data-cy="hasExpired"]`).contains('Points expired a few seconds ago')
+ cy.get(`[data-cy="skillProgress_index-2"] [data-cy="hasExpired"]`)
+ .should('not.exist');
+
+ cy.cdClickSkill(0);
+ cy.get(`[data-cy="hasExpired"]`).should('exist');
+ cy.get(`[data-cy="hasExpired"]`).contains(`Points expired a few seconds ago`)
+
+ cy.get('[data-cy="nextSkill"]').click();
+ cy.get(`[data-cy="hasExpired"]`).should('exist');
+ cy.get(`[data-cy="hasExpired"]`).contains('Points expired a few seconds ago')
+
+ cy.get('[data-cy="nextSkill"]').click();
+ cy.get(`[data-cy="hasExpired"]`).should('not.exist');
+
+ });
});
diff --git a/e2e-tests/cypress/support/commands.js b/e2e-tests/cypress/support/commands.js
index c872c14c70..5a84e4a929 100644
--- a/e2e-tests/cypress/support/commands.js
+++ b/e2e-tests/cypress/support/commands.js
@@ -1402,7 +1402,6 @@ Cypress.Commands.add('configureExpiration', (skillNum = '1', numDays = 30, every
nextExpirationDate: m ? m.format('x') : null
});
});
-
Cypress.Commands.add('expireSkills', () => {
cy.logout();
cy.resetEmail();
diff --git a/service/src/main/java/skills/services/admin/UserAchievementExpirationService.groovy b/service/src/main/java/skills/services/admin/UserAchievementExpirationService.groovy
index 0955703308..fcd1d74671 100644
--- a/service/src/main/java/skills/services/admin/UserAchievementExpirationService.groovy
+++ b/service/src/main/java/skills/services/admin/UserAchievementExpirationService.groovy
@@ -68,6 +68,7 @@ class UserAchievementExpirationService {
if (nextExpirationDate.isBefore(now)) {
expireAchievementsForSkill(skillAttributesDef.skillRefId)
+ expirationAttrs.lastExpirationDate = nextExpirationDate
// update nextExpirationDate
expirationAttrs.nextExpirationDate = getNextExpirationDate(expirationAttrs)?.toDate()
skillAttributesDef.attributes = skillAttributeService.mapper.writeValueAsString(expirationAttrs)
@@ -75,7 +76,7 @@ class UserAchievementExpirationService {
}
} else if (expirationAttrs.expirationType == ExpirationAttrs.DAILY) {
LocalDateTime achievedOnOlderThan = now.minusDays(expirationAttrs.every)
- expireAchievementsForSkillAchievedBefore(skillAttributesDef.skillRefId, achievedOnOlderThan?.toDate())
+ expireAchievementsForSkillAchievedBefore(skillAttributesDef, achievedOnOlderThan?.toDate(), expirationAttrs)
} else if (expirationAttrs.expirationType != ExpirationAttrs.NEVER) {
log.error("Unexpected expirationType [${expirationAttrs?.expirationType}] - ${expirationAttrs}")
}
@@ -89,15 +90,19 @@ class UserAchievementExpirationService {
skillEventAdminService.deleteAllSkillEventsForSkill(skillRefId)
}
- private void expireAchievementsForSkillAchievedBefore(Integer skillRefId, Date expirationDate) {
+ private void expireAchievementsForSkillAchievedBefore(SkillAttributesDef skillAttributesDef, Date expirationDate, ExpirationAttrs expirationAttrs) {
// find any UserAchievement's for this skill where the most recent associated UserPerformedSkill.performedOn is older than the expiration date
- List expiredUserAchievements = expiredUserAchievementRepo.findUserAchievementsBySkillRefIdWithMostRecentUserPerformedSkillBefore(skillRefId, expirationDate)
+ List expiredUserAchievements = expiredUserAchievementRepo.findUserAchievementsBySkillRefIdWithMostRecentUserPerformedSkillBefore(skillAttributesDef.skillRefId, expirationDate)
expiredUserAchievements.each { ua ->
// move this user_achievement record to the expired_user_achievements table
expiredUserAchievementRepo.expireAchievementById(ua.id)
// remove all skill events for this skill and user
- skillEventAdminService.deleteAllSkillEventsForSkillAndUser(skillRefId, ua.userId)
+ skillEventAdminService.deleteAllSkillEventsForSkillAndUser(skillAttributesDef.skillRefId, ua.userId)
+
+ expirationAttrs.lastExpirationDate = new Date()
+ skillAttributesDef.attributes = skillAttributeService.mapper.writeValueAsString(expirationAttrs)
+ skillAttributesDefRepo.save(skillAttributesDef)
}
}
diff --git a/service/src/main/java/skills/services/attributes/ExpirationAttrs.groovy b/service/src/main/java/skills/services/attributes/ExpirationAttrs.groovy
index a22b6ef827..0ae8a018c7 100644
--- a/service/src/main/java/skills/services/attributes/ExpirationAttrs.groovy
+++ b/service/src/main/java/skills/services/attributes/ExpirationAttrs.groovy
@@ -33,4 +33,5 @@ class ExpirationAttrs {
Integer every
String monthlyDay
Date nextExpirationDate
+ Date lastExpirationDate
}
diff --git a/service/src/main/java/skills/skillLoading/SkillsLoader.groovy b/service/src/main/java/skills/skillLoading/SkillsLoader.groovy
index c7b7ab7e69..7054e542f0 100644
--- a/service/src/main/java/skills/skillLoading/SkillsLoader.groovy
+++ b/service/src/main/java/skills/skillLoading/SkillsLoader.groovy
@@ -528,10 +528,12 @@ class SkillsLoader {
ExpirationAttrs expirationAttrs = skillAttributeService.getExpirationAttrs(projectId, skillId)
Date expirationDate
Date mostRecentlyPerformedOn
+ Date lastExpirationDate
int daysOfInactivityBeforeExp = 0
Boolean isMotivationalSkill = false
if (expirationAttrs) {
expirationDate = expirationAttrs.nextExpirationDate
+ lastExpirationDate = expirationAttrs.lastExpirationDate
isMotivationalSkill = expirationAttrs?.expirationType == ExpirationAttrs.DAILY
if (isMotivationalSkill) {
UserPerformedSkill mostRecentUPS = userPerformedSkillRepo.findTopBySkillRefIdAndUserIdOrderByPerformedOnDesc(skillDef.id, userId)
@@ -612,6 +614,7 @@ class SkillsLoader {
isMotivationalSkill: isMotivationalSkill,
daysOfInactivityBeforeExp: daysOfInactivityBeforeExp,
mostRecentlyPerformedOn: mostRecentlyPerformedOn,
+ lastExpirationDate: lastExpirationDate
)
}
@@ -1226,11 +1229,13 @@ class SkillsLoader {
Date achievedOn = achievedLevelRepository.getAchievedDateByUserIdAndProjectIdAndSkillId(userId, skillDef.projectId, skillDef.skillId)
Date expirationDate
Date mostRecentlyPerformedOn
+ Date lastExpirationDate
int daysOfInactivityBeforeExp = 0
Boolean isMotivationalSkill = false
if (skillDefAndUserPoints.attributes && skillDefAndUserPoints.attributes.type == SkillAttributesDef.SkillAttributesType.AchievementExpiration) {
ExpirationAttrs expirationAttrs = skillAttributeService.convertAttrs(skillDefAndUserPoints.attributes, ExpirationAttrs)
expirationDate = expirationAttrs.nextExpirationDate
+ lastExpirationDate = expirationAttrs.lastExpirationDate
isMotivationalSkill = expirationAttrs?.expirationType == ExpirationAttrs.DAILY
if (isMotivationalSkill) {
UserPerformedSkill mostRecentUPS = userPerformedSkillRepo.findTopBySkillRefIdAndUserIdOrderByPerformedOnDesc(skillDef.id, userId)
@@ -1273,6 +1278,7 @@ class SkillsLoader {
isMotivationalSkill: isMotivationalSkill,
daysOfInactivityBeforeExp: daysOfInactivityBeforeExp,
mostRecentlyPerformedOn: mostRecentlyPerformedOn,
+ lastExpirationDate: lastExpirationDate
)
}
}
diff --git a/service/src/main/java/skills/skillLoading/model/SkillSummary.groovy b/service/src/main/java/skills/skillLoading/model/SkillSummary.groovy
index 5e73f4cae7..0b60eb9667 100644
--- a/service/src/main/java/skills/skillLoading/model/SkillSummary.groovy
+++ b/service/src/main/java/skills/skillLoading/model/SkillSummary.groovy
@@ -51,4 +51,5 @@ class SkillSummary extends SkillSummaryParent {
int daysOfInactivityBeforeExp
Date mostRecentlyPerformedOn
Date expirationDate
+ Date lastExpirationDate
}
From 571bdf385428ff95bc3d262a07cb6d73683b8d77 Mon Sep 17 00:00:00 2001
From: Don Walizer <12420708+dwalizer@users.noreply.github.com>
Date: Fri, 29 Sep 2023 09:51:34 -0400
Subject: [PATCH 2/5] #2425 Fix date conversion issue
---
.../services/admin/UserAchievementExpirationService.groovy | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/service/src/main/java/skills/services/admin/UserAchievementExpirationService.groovy b/service/src/main/java/skills/services/admin/UserAchievementExpirationService.groovy
index fcd1d74671..4baa6d4ec6 100644
--- a/service/src/main/java/skills/services/admin/UserAchievementExpirationService.groovy
+++ b/service/src/main/java/skills/services/admin/UserAchievementExpirationService.groovy
@@ -68,7 +68,7 @@ class UserAchievementExpirationService {
if (nextExpirationDate.isBefore(now)) {
expireAchievementsForSkill(skillAttributesDef.skillRefId)
- expirationAttrs.lastExpirationDate = nextExpirationDate
+ expirationAttrs.lastExpirationDate = expirationAttrs.nextExpirationDate
// update nextExpirationDate
expirationAttrs.nextExpirationDate = getNextExpirationDate(expirationAttrs)?.toDate()
skillAttributesDef.attributes = skillAttributeService.mapper.writeValueAsString(expirationAttrs)
From aa32915196467f97fecf149e80affd4a9a4a1008 Mon Sep 17 00:00:00 2001
From: Don Walizer <12420708+dwalizer@users.noreply.github.com>
Date: Wed, 18 Oct 2023 10:03:19 -0400
Subject: [PATCH 3/5] #2425 Change method of getting expiration date, add tests
---
.../UserAchievementExpirationService.groovy | 8 +-
.../attributes/ExpirationAttrs.groovy | 1 -
.../skills/skillLoading/SkillsLoader.groovy | 18 ++++-
.../repos/ExpiredUserAchievementRepo.groovy | 9 +++
...PostAchievementSkillExpirationSpecs.groovy | 80 +++++++++++++++++++
5 files changed, 107 insertions(+), 9 deletions(-)
diff --git a/service/src/main/java/skills/services/admin/UserAchievementExpirationService.groovy b/service/src/main/java/skills/services/admin/UserAchievementExpirationService.groovy
index 4baa6d4ec6..9e27502dcb 100644
--- a/service/src/main/java/skills/services/admin/UserAchievementExpirationService.groovy
+++ b/service/src/main/java/skills/services/admin/UserAchievementExpirationService.groovy
@@ -68,7 +68,6 @@ class UserAchievementExpirationService {
if (nextExpirationDate.isBefore(now)) {
expireAchievementsForSkill(skillAttributesDef.skillRefId)
- expirationAttrs.lastExpirationDate = expirationAttrs.nextExpirationDate
// update nextExpirationDate
expirationAttrs.nextExpirationDate = getNextExpirationDate(expirationAttrs)?.toDate()
skillAttributesDef.attributes = skillAttributeService.mapper.writeValueAsString(expirationAttrs)
@@ -76,7 +75,7 @@ class UserAchievementExpirationService {
}
} else if (expirationAttrs.expirationType == ExpirationAttrs.DAILY) {
LocalDateTime achievedOnOlderThan = now.minusDays(expirationAttrs.every)
- expireAchievementsForSkillAchievedBefore(skillAttributesDef, achievedOnOlderThan?.toDate(), expirationAttrs)
+ expireAchievementsForSkillAchievedBefore(skillAttributesDef, achievedOnOlderThan?.toDate())
} else if (expirationAttrs.expirationType != ExpirationAttrs.NEVER) {
log.error("Unexpected expirationType [${expirationAttrs?.expirationType}] - ${expirationAttrs}")
}
@@ -90,7 +89,7 @@ class UserAchievementExpirationService {
skillEventAdminService.deleteAllSkillEventsForSkill(skillRefId)
}
- private void expireAchievementsForSkillAchievedBefore(SkillAttributesDef skillAttributesDef, Date expirationDate, ExpirationAttrs expirationAttrs) {
+ private void expireAchievementsForSkillAchievedBefore(SkillAttributesDef skillAttributesDef, Date expirationDate) {
// find any UserAchievement's for this skill where the most recent associated UserPerformedSkill.performedOn is older than the expiration date
List expiredUserAchievements = expiredUserAchievementRepo.findUserAchievementsBySkillRefIdWithMostRecentUserPerformedSkillBefore(skillAttributesDef.skillRefId, expirationDate)
@@ -99,9 +98,6 @@ class UserAchievementExpirationService {
expiredUserAchievementRepo.expireAchievementById(ua.id)
// remove all skill events for this skill and user
skillEventAdminService.deleteAllSkillEventsForSkillAndUser(skillAttributesDef.skillRefId, ua.userId)
-
- expirationAttrs.lastExpirationDate = new Date()
- skillAttributesDef.attributes = skillAttributeService.mapper.writeValueAsString(expirationAttrs)
skillAttributesDefRepo.save(skillAttributesDef)
}
}
diff --git a/service/src/main/java/skills/services/attributes/ExpirationAttrs.groovy b/service/src/main/java/skills/services/attributes/ExpirationAttrs.groovy
index 0ae8a018c7..a22b6ef827 100644
--- a/service/src/main/java/skills/services/attributes/ExpirationAttrs.groovy
+++ b/service/src/main/java/skills/services/attributes/ExpirationAttrs.groovy
@@ -33,5 +33,4 @@ class ExpirationAttrs {
Integer every
String monthlyDay
Date nextExpirationDate
- Date lastExpirationDate
}
diff --git a/service/src/main/java/skills/skillLoading/SkillsLoader.groovy b/service/src/main/java/skills/skillLoading/SkillsLoader.groovy
index 7054e542f0..76d521537f 100644
--- a/service/src/main/java/skills/skillLoading/SkillsLoader.groovy
+++ b/service/src/main/java/skills/skillLoading/SkillsLoader.groovy
@@ -41,6 +41,7 @@ import skills.services.GlobalBadgesService
import skills.services.LevelDefinitionStorageService
import skills.services.admin.SkillTagService
import skills.services.admin.SkillsGroupAdminService
+import skills.services.admin.UserAchievementExpirationService
import skills.services.admin.UserCommunityService
import skills.services.admin.skillReuse.SkillReuseIdUtil
import skills.services.attributes.BonusAwardAttrs
@@ -162,6 +163,9 @@ class SkillsLoader {
@Autowired
SkillAttributeService skillAttributeService
+ @Autowired
+ ExpiredUserAchievementRepo expiredUserAchievementRepo
+
@Autowired
TaskConfig taskConfig
@@ -533,7 +537,12 @@ class SkillsLoader {
Boolean isMotivationalSkill = false
if (expirationAttrs) {
expirationDate = expirationAttrs.nextExpirationDate
- lastExpirationDate = expirationAttrs.lastExpirationDate
+ if(!achievedOn) {
+ def expiredSkill = expiredUserAchievementRepo.findMostRecentExpirationForSkill(projectId, userId, skillId)
+ if (expiredSkill) {
+ lastExpirationDate = expiredSkill.expiredOn
+ }
+ }
isMotivationalSkill = expirationAttrs?.expirationType == ExpirationAttrs.DAILY
if (isMotivationalSkill) {
UserPerformedSkill mostRecentUPS = userPerformedSkillRepo.findTopBySkillRefIdAndUserIdOrderByPerformedOnDesc(skillDef.id, userId)
@@ -1235,7 +1244,12 @@ class SkillsLoader {
if (skillDefAndUserPoints.attributes && skillDefAndUserPoints.attributes.type == SkillAttributesDef.SkillAttributesType.AchievementExpiration) {
ExpirationAttrs expirationAttrs = skillAttributeService.convertAttrs(skillDefAndUserPoints.attributes, ExpirationAttrs)
expirationDate = expirationAttrs.nextExpirationDate
- lastExpirationDate = expirationAttrs.lastExpirationDate
+ if(!achievedOn) {
+ def expiredSkill = expiredUserAchievementRepo.findMostRecentExpirationForSkill(skillDef.projectId, userId, skillDef.skillId)
+ if (expiredSkill) {
+ lastExpirationDate = expiredSkill.expiredOn
+ }
+ }
isMotivationalSkill = expirationAttrs?.expirationType == ExpirationAttrs.DAILY
if (isMotivationalSkill) {
UserPerformedSkill mostRecentUPS = userPerformedSkillRepo.findTopBySkillRefIdAndUserIdOrderByPerformedOnDesc(skillDef.id, userId)
diff --git a/service/src/main/java/skills/storage/repos/ExpiredUserAchievementRepo.groovy b/service/src/main/java/skills/storage/repos/ExpiredUserAchievementRepo.groovy
index 76f94592e4..8b5758dbe2 100644
--- a/service/src/main/java/skills/storage/repos/ExpiredUserAchievementRepo.groovy
+++ b/service/src/main/java/skills/storage/repos/ExpiredUserAchievementRepo.groovy
@@ -21,6 +21,7 @@ import org.springframework.data.domain.PageRequest
import org.springframework.data.jpa.repository.Modifying
import org.springframework.data.jpa.repository.Query
import org.springframework.data.repository.CrudRepository
+import org.springframework.lang.Nullable
import org.springframework.data.repository.query.Param
import skills.controller.result.model.ExpiredSkillRes
import skills.storage.model.ExpiredUserAchievement
@@ -78,4 +79,12 @@ interface ExpiredUserAchievementRepo extends CrudRepository
+ skillsService.addSkill([projectId: proj.projectId, skillId: it.skillId], userId, timestamp8Days)
+ skillsService.addSkill([projectId: proj.projectId, skillId: it.skillId], userId, timestamp9Days)
+ skillsService.addSkill([projectId: proj.projectId, skillId: it.skillId], secondUserId, new Date(secondTimestamp))
+ }
+
+ when:
+ expireSkills(proj.projectId, skills)
+ def skill4Information = skillsService.getSingleSkillSummary(userId, proj.projectId, skills[3].skillId)
+ def skill7Information = skillsService.getSingleSkillSummary(userId, proj.projectId, skills[6].skillId)
+ def skill4InformationUser2 = skillsService.getSingleSkillSummary(secondUserId, proj.projectId, skills[3].skillId)
+ def skill7InformationUser2 = skillsService.getSingleSkillSummary(secondUserId, proj.projectId, skills[6].skillId)
+
+ then:
+ skill4Information.lastExpirationDate != null
+ skill7Information.lastExpirationDate != null
+ skill4InformationUser2.lastExpirationDate == null
+ skill7InformationUser2.lastExpirationDate == null
+ }
+
+ def "achieve skill after expiration"() {
+ def proj = SkillsFactory.createProject()
+ def subj = SkillsFactory.createSubject()
+ def skills = SkillsFactory.createSkills(10, )
+
+ skillsService.createProject(proj)
+ skillsService.createSubject(subj)
+ skillsService.createSkills(skills)
+
+ Map badge = [projectId: proj.projectId, badgeId: 'badge1', name: 'Test Badge 1']
+ skillsService.addBadge(badge)
+ skillsService.assignSkillToBadge(projectId: proj.projectId, badgeId: badge.badgeId, skillId: skills[0].skillId)
+ badge.enabled = 'true'
+ skillsService.updateBadge(badge, badge.badgeId)
+
+ String userId = "user1"
+ Long timestamp = (new Date()-8).time
+ Date yesterday = (new Date()-1)
+ Date twoDaysAgo = (new Date()-2)
+ Date timestamp8Days = new Date()-8
+ Date timestamp9Days = new Date()-8
+
+ setup:
+ skills.forEach { it ->
+ skillsService.addSkill([projectId: proj.projectId, skillId: it.skillId], userId, timestamp8Days)
+ skillsService.addSkill([projectId: proj.projectId, skillId: it.skillId], userId, timestamp9Days)
+ }
+
+ when:
+ expireSkills(proj.projectId, skills)
+ skillsService.addSkill([projectId: proj.projectId, skillId: skills[3].skillId], userId, yesterday)
+ skillsService.addSkill([projectId: proj.projectId, skillId: skills[3].skillId], userId, twoDaysAgo)
+ def skill4Information = skillsService.getSingleSkillSummary(userId, proj.projectId, skills[3].skillId)
+
+ then:
+ skill4Information.lastExpirationDate == null
+ }
}
From d6401b976ff8625ef7a6baf9193727d2b13ce483 Mon Sep 17 00:00:00 2001
From: Don Walizer <12420708+dwalizer@users.noreply.github.com>
Date: Fri, 20 Oct 2023 09:04:27 -0400
Subject: [PATCH 4/5] #2425 Switch to getting skills for subject in a batch
---
.../UserAchievementExpirationService.groovy | 9 ++---
.../skills/skillLoading/SkillsLoader.groovy | 5 +--
.../skillLoading/SubjectDataLoader.groovy | 20 ++++++++++
.../repos/ExpiredUserAchievementRepo.groovy | 8 ++++
...PostAchievementSkillExpirationSpecs.groovy | 37 ++++++++++++++++++-
5 files changed, 69 insertions(+), 10 deletions(-)
diff --git a/service/src/main/java/skills/services/admin/UserAchievementExpirationService.groovy b/service/src/main/java/skills/services/admin/UserAchievementExpirationService.groovy
index 9e27502dcb..0955703308 100644
--- a/service/src/main/java/skills/services/admin/UserAchievementExpirationService.groovy
+++ b/service/src/main/java/skills/services/admin/UserAchievementExpirationService.groovy
@@ -75,7 +75,7 @@ class UserAchievementExpirationService {
}
} else if (expirationAttrs.expirationType == ExpirationAttrs.DAILY) {
LocalDateTime achievedOnOlderThan = now.minusDays(expirationAttrs.every)
- expireAchievementsForSkillAchievedBefore(skillAttributesDef, achievedOnOlderThan?.toDate())
+ expireAchievementsForSkillAchievedBefore(skillAttributesDef.skillRefId, achievedOnOlderThan?.toDate())
} else if (expirationAttrs.expirationType != ExpirationAttrs.NEVER) {
log.error("Unexpected expirationType [${expirationAttrs?.expirationType}] - ${expirationAttrs}")
}
@@ -89,16 +89,15 @@ class UserAchievementExpirationService {
skillEventAdminService.deleteAllSkillEventsForSkill(skillRefId)
}
- private void expireAchievementsForSkillAchievedBefore(SkillAttributesDef skillAttributesDef, Date expirationDate) {
+ private void expireAchievementsForSkillAchievedBefore(Integer skillRefId, Date expirationDate) {
// find any UserAchievement's for this skill where the most recent associated UserPerformedSkill.performedOn is older than the expiration date
- List expiredUserAchievements = expiredUserAchievementRepo.findUserAchievementsBySkillRefIdWithMostRecentUserPerformedSkillBefore(skillAttributesDef.skillRefId, expirationDate)
+ List expiredUserAchievements = expiredUserAchievementRepo.findUserAchievementsBySkillRefIdWithMostRecentUserPerformedSkillBefore(skillRefId, expirationDate)
expiredUserAchievements.each { ua ->
// move this user_achievement record to the expired_user_achievements table
expiredUserAchievementRepo.expireAchievementById(ua.id)
// remove all skill events for this skill and user
- skillEventAdminService.deleteAllSkillEventsForSkillAndUser(skillAttributesDef.skillRefId, ua.userId)
- skillAttributesDefRepo.save(skillAttributesDef)
+ skillEventAdminService.deleteAllSkillEventsForSkillAndUser(skillRefId, ua.userId)
}
}
diff --git a/service/src/main/java/skills/skillLoading/SkillsLoader.groovy b/service/src/main/java/skills/skillLoading/SkillsLoader.groovy
index 76d521537f..22e8d52ed1 100644
--- a/service/src/main/java/skills/skillLoading/SkillsLoader.groovy
+++ b/service/src/main/java/skills/skillLoading/SkillsLoader.groovy
@@ -1245,10 +1245,7 @@ class SkillsLoader {
ExpirationAttrs expirationAttrs = skillAttributeService.convertAttrs(skillDefAndUserPoints.attributes, ExpirationAttrs)
expirationDate = expirationAttrs.nextExpirationDate
if(!achievedOn) {
- def expiredSkill = expiredUserAchievementRepo.findMostRecentExpirationForSkill(skillDef.projectId, userId, skillDef.skillId)
- if (expiredSkill) {
- lastExpirationDate = expiredSkill.expiredOn
- }
+ lastExpirationDate = skillDefAndUserPoints.expiredOn
}
isMotivationalSkill = expirationAttrs?.expirationType == ExpirationAttrs.DAILY
if (isMotivationalSkill) {
diff --git a/service/src/main/java/skills/skillLoading/SubjectDataLoader.groovy b/service/src/main/java/skills/skillLoading/SubjectDataLoader.groovy
index 1481c472b7..832777be23 100644
--- a/service/src/main/java/skills/skillLoading/SubjectDataLoader.groovy
+++ b/service/src/main/java/skills/skillLoading/SubjectDataLoader.groovy
@@ -27,6 +27,7 @@ import skills.services.settings.SettingsService
import skills.skillLoading.model.SkillDependencySummary
import skills.skillLoading.model.SkillTag
import skills.storage.model.*
+import skills.storage.repos.ExpiredUserAchievementRepo
import skills.storage.repos.QuizToSkillDefRepo
import skills.storage.repos.SkillApprovalRepo
import skills.storage.repos.SkillDefRepo
@@ -66,6 +67,9 @@ class SubjectDataLoader {
@Autowired
SkillApprovalRepo skillApprovalRepo
+ @Autowired
+ ExpiredUserAchievementRepo expiredUserAchievementRepo
+
static class SkillsAndPoints {
SkillDef skillDef
int points
@@ -85,6 +89,7 @@ class SubjectDataLoader {
List badges = []
List tags = []
SkillAttributesDef attributes
+ Date expiredOn
}
static class SkillsData {
@@ -140,6 +145,7 @@ class SubjectDataLoader {
skillsAndPoints = handleBadges(projectId, skillsAndPoints)
skillsAndPoints = handleSkillTags(projectId, skillsAndPoints)
skillsAndPoints = handleSkillQuizInfo(projectId, skillsAndPoints)
+ skillsAndPoints = handleSkillExpirations(projectId, userId, skillsAndPoints)
new SkillsData(childrenWithPoints: skillsAndPoints)
}
@@ -172,6 +178,20 @@ class SubjectDataLoader {
return skillsAndPoints;
}
+ private List handleSkillExpirations(String projectId, String userId, List skillsAndPoints) {
+ if(projectId) {
+ List skillIds = collectSkillIds(skillsAndPoints)
+ def expiredSkills = expiredUserAchievementRepo.findMostRecentExpirationForAllSkills(projectId, userId, skillIds)
+ if (expiredSkills) {
+ skillsAndPoints.each { it ->
+ def expirationInfo = expiredSkills.find{ skill -> skill.skillId == it.skillDef.skillId }
+ it.expiredOn = expirationInfo?.expiredOn
+ }
+ }
+ }
+ return skillsAndPoints
+ }
+
@Profile
private List handleSkillTags(String projectId, List skillsAndPoints) {
if(projectId) {
diff --git a/service/src/main/java/skills/storage/repos/ExpiredUserAchievementRepo.groovy b/service/src/main/java/skills/storage/repos/ExpiredUserAchievementRepo.groovy
index 8b5758dbe2..140d79e4e8 100644
--- a/service/src/main/java/skills/storage/repos/ExpiredUserAchievementRepo.groovy
+++ b/service/src/main/java/skills/storage/repos/ExpiredUserAchievementRepo.groovy
@@ -87,4 +87,12 @@ interface ExpiredUserAchievementRepo extends CrudRepository findMostRecentExpirationForAllSkills(@Param("projectId") String projectId, @Param("userId") String userId, @Param("skills") List skills)
}
diff --git a/service/src/test/java/skills/intTests/skillExpiration/PostAchievementSkillExpirationSpecs.groovy b/service/src/test/java/skills/intTests/skillExpiration/PostAchievementSkillExpirationSpecs.groovy
index 6542b1f9c3..e7f4c27908 100644
--- a/service/src/test/java/skills/intTests/skillExpiration/PostAchievementSkillExpirationSpecs.groovy
+++ b/service/src/test/java/skills/intTests/skillExpiration/PostAchievementSkillExpirationSpecs.groovy
@@ -1917,7 +1917,6 @@ class PostAchievementSkillExpirationSpecs extends DefaultIntSpec {
skillsService.updateBadge(badge, badge.badgeId)
String userId = "user1"
- Long timestamp = (new Date()-8).time
Date yesterday = (new Date()-1)
Date twoDaysAgo = (new Date()-2)
Date timestamp8Days = new Date()-8
@@ -1938,4 +1937,40 @@ class PostAchievementSkillExpirationSpecs extends DefaultIntSpec {
then:
skill4Information.lastExpirationDate == null
}
+
+ def "get expired skill summaries for subject"() {
+ def proj = SkillsFactory.createProject()
+ def subj = SkillsFactory.createSubject()
+ def skills = SkillsFactory.createSkills(10, )
+
+ skillsService.createProject(proj)
+ skillsService.createSubject(subj)
+ skillsService.createSkills(skills)
+
+ Map badge = [projectId: proj.projectId, badgeId: 'badge1', name: 'Test Badge 1']
+ skillsService.addBadge(badge)
+ skillsService.assignSkillToBadge(projectId: proj.projectId, badgeId: badge.badgeId, skillId: skills[0].skillId)
+ badge.enabled = 'true'
+ skillsService.updateBadge(badge, badge.badgeId)
+
+ String userId = "user1"
+ String secondUserId = "user2"
+ Date timestamp8Days = new Date()-8
+ Date timestamp9Days = new Date()-8
+ Long secondTimestamp = new Date().time
+
+ setup:
+ skills.forEach { it ->
+ skillsService.addSkill([projectId: proj.projectId, skillId: it.skillId], userId, timestamp8Days)
+ skillsService.addSkill([projectId: proj.projectId, skillId: it.skillId], userId, timestamp9Days)
+ skillsService.addSkill([projectId: proj.projectId, skillId: it.skillId], secondUserId, new Date(secondTimestamp))
+ }
+
+ when:
+ expireSkills(proj.projectId, skills)
+ def subjectInfo = skillsService.getSkillSummary(userId, proj.projectId, subj.subjectId)
+
+ then:
+ subjectInfo.skills.lastExpiredDate != null
+ }
}
From c47dfcc171da782a1327e825c7dbcc4667bfb502 Mon Sep 17 00:00:00 2001
From: Don Walizer <12420708+dwalizer@users.noreply.github.com>
Date: Tue, 24 Oct 2023 10:32:44 -0400
Subject: [PATCH 5/5] #2425 Select most recent expiration date if more than
one, add tests
---
.../skillLoading/SubjectDataLoader.groovy | 7 +-
...PostAchievementSkillExpirationSpecs.groovy | 79 ++++++++++++++++++-
2 files changed, 83 insertions(+), 3 deletions(-)
diff --git a/service/src/main/java/skills/skillLoading/SubjectDataLoader.groovy b/service/src/main/java/skills/skillLoading/SubjectDataLoader.groovy
index 832777be23..988804ea3b 100644
--- a/service/src/main/java/skills/skillLoading/SubjectDataLoader.groovy
+++ b/service/src/main/java/skills/skillLoading/SubjectDataLoader.groovy
@@ -184,8 +184,11 @@ class SubjectDataLoader {
def expiredSkills = expiredUserAchievementRepo.findMostRecentExpirationForAllSkills(projectId, userId, skillIds)
if (expiredSkills) {
skillsAndPoints.each { it ->
- def expirationInfo = expiredSkills.find{ skill -> skill.skillId == it.skillDef.skillId }
- it.expiredOn = expirationInfo?.expiredOn
+ def expirations = expiredSkills.findAll{skill -> skill.skillId == it.skillDef.skillId}
+ if(expirations) {
+ expirations?.sort { skill -> skill.expiredOn }
+ it.expiredOn = expirations.first()?.expiredOn
+ }
}
}
}
diff --git a/service/src/test/java/skills/intTests/skillExpiration/PostAchievementSkillExpirationSpecs.groovy b/service/src/test/java/skills/intTests/skillExpiration/PostAchievementSkillExpirationSpecs.groovy
index e7f4c27908..50d7bb091c 100644
--- a/service/src/test/java/skills/intTests/skillExpiration/PostAchievementSkillExpirationSpecs.groovy
+++ b/service/src/test/java/skills/intTests/skillExpiration/PostAchievementSkillExpirationSpecs.groovy
@@ -1971,6 +1971,83 @@ class PostAchievementSkillExpirationSpecs extends DefaultIntSpec {
def subjectInfo = skillsService.getSkillSummary(userId, proj.projectId, subj.subjectId)
then:
- subjectInfo.skills.lastExpiredDate != null
+ subjectInfo.skills.lastExpirationDate != null
+ }
+
+ def "achieve skill after expiration and get subject summary"() {
+ def proj = SkillsFactory.createProject()
+ def subj = SkillsFactory.createSubject()
+ def skills = SkillsFactory.createSkills(10, )
+
+ skillsService.createProject(proj)
+ skillsService.createSubject(subj)
+ skillsService.createSkills(skills)
+
+ Map badge = [projectId: proj.projectId, badgeId: 'badge1', name: 'Test Badge 1']
+ skillsService.addBadge(badge)
+ skillsService.assignSkillToBadge(projectId: proj.projectId, badgeId: badge.badgeId, skillId: skills[0].skillId)
+ badge.enabled = 'true'
+ skillsService.updateBadge(badge, badge.badgeId)
+
+ String userId = "user1"
+
+ setup:
+ skills.forEach { it ->
+ skillsService.addSkill([projectId: proj.projectId, skillId: it.skillId], userId, new Date() - 21)
+ skillsService.addSkill([projectId: proj.projectId, skillId: it.skillId], userId, new Date() - 20)
+ }
+
+ when:
+ expireSkills(proj.projectId, skills)
+ def subjectInfo = skillsService.getSkillSummary(userId, proj.projectId, subj.subjectId)
+ skillsService.addSkill([projectId: proj.projectId, skillId: skills[0].skillId], userId, new Date() - 8)
+ skillsService.addSkill([projectId: proj.projectId, skillId: skills[0].skillId], userId, new Date() - 9)
+ def laterSubjectInfo = skillsService.getSkillSummary(userId, proj.projectId, subj.subjectId)
+
+ then:
+ subjectInfo.skills.lastExpirationDate != null
+ laterSubjectInfo.skills[0].lastExpirationDate == null
+ }
+
+ def "with multiple expirations, latest expiration is returned in subject summary"() {
+ def proj = SkillsFactory.createProject()
+ def subj = SkillsFactory.createSubject()
+ def skills = SkillsFactory.createSkills(10, )
+
+ skillsService.createProject(proj)
+ skillsService.createSubject(subj)
+ skillsService.createSkills(skills)
+
+ Map badge = [projectId: proj.projectId, badgeId: 'badge1', name: 'Test Badge 1']
+ skillsService.addBadge(badge)
+ skillsService.assignSkillToBadge(projectId: proj.projectId, badgeId: badge.badgeId, skillId: skills[0].skillId)
+ badge.enabled = 'true'
+ skillsService.updateBadge(badge, badge.badgeId)
+
+ String userId = "user1"
+
+ setup:
+ skills.forEach { it ->
+ skillsService.addSkill([projectId: proj.projectId, skillId: it.skillId], userId, new Date() - 31)
+ skillsService.addSkill([projectId: proj.projectId, skillId: it.skillId], userId, new Date() - 30)
+ }
+
+ when:
+ expireSkills(proj.projectId, skills)
+ def subjectInfo = skillsService.getSkillSummary(userId, proj.projectId, subj.subjectId)
+ skillsService.addSkill([projectId: proj.projectId, skillId: skills[0].skillId], userId, new Date() - 20)
+ skillsService.addSkill([projectId: proj.projectId, skillId: skills[0].skillId], userId, new Date() - 19)
+ expireSkills(proj.projectId, skills)
+ def secondSubjectInfo = skillsService.getSkillSummary(userId, proj.projectId, subj.subjectId)
+ skillsService.addSkill([projectId: proj.projectId, skillId: skills[0].skillId], userId, new Date() - 9)
+ skillsService.addSkill([projectId: proj.projectId, skillId: skills[0].skillId], userId, new Date() - 8)
+ expireSkills(proj.projectId, skills)
+ def laterSubjectInfo = skillsService.getSkillSummary(userId, proj.projectId, subj.subjectId)
+
+ then:
+ laterSubjectInfo.skills[0].lastExpirationDate != null
+ subjectInfo.skills[0].lastExpirationDate != laterSubjectInfo.skills[0].lastExpirationDate != null
+ secondSubjectInfo.skills[0].lastExpirationDate != laterSubjectInfo.skills[0].lastExpirationDate != null
+ subjectInfo.skills[0].lastExpirationDate != secondSubjectInfo.skills[0].lastExpirationDate != null
}
}