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 } }