diff --git a/dashboard/src/main.js b/dashboard/src/main.js index ec87a449a..c39ed281d 100644 --- a/dashboard/src/main.js +++ b/dashboard/src/main.js @@ -61,6 +61,7 @@ import Listbox from 'primevue/listbox' import ProgressBar from 'primevue/progressbar' import Chip from 'primevue/chip' import FloatLabel from "primevue/floatlabel"; +import Timeline from 'primevue/timeline'; import ConfirmationService from 'primevue/confirmationservice' import BadgeDirective from 'primevue/badgedirective' @@ -141,6 +142,7 @@ app.component('Listbox', Listbox) app.component('ProgressBar', ProgressBar) app.component('Chip', Chip) app.component('FloatLabel', FloatLabel) +app.component('Timeline', Timeline) app.component('SkillsButton', SkillsButton) app.component('SkillsTextInput', SkillsTextInput) diff --git a/dashboard/src/skills-display/components/skill/ApprovalEventMessage.vue b/dashboard/src/skills-display/components/skill/ApprovalEventMessage.vue new file mode 100644 index 000000000..b46743265 --- /dev/null +++ b/dashboard/src/skills-display/components/skill/ApprovalEventMessage.vue @@ -0,0 +1,51 @@ +/* +Copyright 2024 SkillTree + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + + + + + \ No newline at end of file diff --git a/dashboard/src/skills-display/components/skill/ApprovalHistory.vue b/dashboard/src/skills-display/components/skill/ApprovalHistory.vue new file mode 100644 index 000000000..7998c6d6e --- /dev/null +++ b/dashboard/src/skills-display/components/skill/ApprovalHistory.vue @@ -0,0 +1,88 @@ +/* +Copyright 2024 SkillTree + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + + + + + \ No newline at end of file diff --git a/dashboard/src/skills-display/components/skill/SkillOverviewFooter.vue b/dashboard/src/skills-display/components/skill/SkillOverviewFooter.vue index 0c8e24a07..070e557d7 100644 --- a/dashboard/src/skills-display/components/skill/SkillOverviewFooter.vue +++ b/dashboard/src/skills-display/components/skill/SkillOverviewFooter.vue @@ -24,6 +24,8 @@ import JustificationInput from '@/skills-display/components/skill/JustificationI import { useSkillsDisplayAttributesState } from '@/skills-display/stores/UseSkillsDisplayAttributesState.js' import { useLog } from '@/components/utils/misc/useLog.js' import QuizFooter from "@/skills-display/components/skill/QuizFooter.vue"; +import ApprovalHistory from '@/skills-display/components/skill/ApprovalHistory.vue'; +import QuizType from '@/skills-display/components/quiz/QuizType.js'; const props = defineProps({ skill: Object @@ -48,10 +50,17 @@ const isCompleted = computed(() => skillInternal.value.points === skillInternal. const selfReportDisabled = computed(() => (isCompleted.value && !isMotivationalSkill.value) || isPendingApproval()) const isHonorSystem = computed(() => skillInternal.value.selfReporting && skillInternal.value.selfReporting.type === 'HonorSystem') const isApprovalRequired = computed(() => skillInternal.value.selfReporting && skillInternal.value.selfReporting.type === 'Approval') +const isQuizSkill = computed(() => skillInternal.value.selfReporting && QuizType.isQuiz(skillInternal.value.selfReporting.type)) const isVideo = computed(() => skillInternal.value.selfReporting.type === 'Video') const isJustificationRequired = computed(() => skillInternal.value.selfReporting && skillInternal.value.selfReporting.justificationRequired) const isRejected = computed(() => skillInternal.value.selfReporting && skillInternal.value.selfReporting.rejectedOn !== null && skillInternal.value.selfReporting.rejectedOn !== undefined) const isMotivationalSkill = computed(() => skillInternal.value && skillInternal.value.isMotivationalSkill) +const showTimeline = computed(() => { + if (skillInternal.value.approvalHistory && skillInternal.value.approvalHistory.length > 0) { + return isApprovalRequired.value || (isQuizSkill.value && skillInternal.value.approvalHistory > 1) + } + return false +}) const showApprovalJustification = ref(false) const requestApprovalLoading = ref(false) @@ -123,7 +132,16 @@ const reportSkill = (approvalRequestedMsg) => { selfReport.value.msgHidden = false selfReport.value.res = res if (!isAlreadyPerformed() && isApprovalRequired.value) { - skillInternal.value.selfReporting.requestedOn = new Date() + const requestedOn = new Date() + skillInternal.value.selfReporting.requestedOn = requestedOn + if (skillInternal.value.approvalHistory) { + skillInternal.value.approvalHistory.unshift({ + id: '-1', + eventTime : requestedOn.getTime(), + eventStatus: 'Approval Requested', + description: approvalRequestedMsg + }) + } } updateEarnedPoints(res) if (res.explanation.includes('Skill Achievement retained')) { @@ -312,10 +330,11 @@ defineExpose({ + This skill is pending approval. Submitted {{ timeUtils.relativeTime(skillInternal.selfReporting.requestedOn) }} @@ -391,6 +410,8 @@ defineExpose({ + +
{ .should('not.exist'); cy.get('[data-cy="overallPointsEarnedCard"] [data-cy="mediaInfoCardTitle"]') .contains('0'); - cy.get('[data-cy="pendingApprovalStatus"]').contains('pending approval') + cy.get('[data-cy="approvalHistoryTimeline"]') + .children('.p-timeline-event') + .eq(0) + .should('contain.text', 'Approval Requested') // approve and then visit page again cy.approveRequest(); @@ -308,8 +311,14 @@ describe('Client Display Self Report Skills Tests', () => { cy.get('[data-cy="requestApprovalBtn"]') .should('be.enabled'); - cy.get('[data-cy="pendingApprovalStatus"]') - .should('not.exist'); + cy.get('[data-cy="approvalHistoryTimeline"]') + .children('.p-timeline-event') + .eq(0) + .should('contain.text', 'Approved') + cy.get('[data-cy="approvalHistoryTimeline"]') + .children('.p-timeline-event') + .eq(1) + .should('contain.text', 'Approval Requested') cy.get('[data-cy="overallPointsEarnedCard"] [data-cy="mediaInfoCardTitle"]') .contains('50'); cy.get('[data-cy="pointsAchievedTodayCard"] [data-cy="mediaInfoCardTitle"]') @@ -355,12 +364,13 @@ describe('Client Display Self Report Skills Tests', () => { cy.get('[data-cy="overallPointsEarnedCard"] [data-cy="mediaInfoCardTitle"]') .contains('0'); - cy.get('[data-cy="pendingApprovalStatus"]').should('not.exist') + // cy.get('[data-cy="approvalHistoryTimeline"]').should('not.exist') cy.get('[data-cy="selfReportAlert"] [data-pc-section="closebutton"]').click() - cy.get('[data-cy="pendingApprovalStatus"]') - .contains('pending approval'); - cy.get('[data-cy="pendingApprovalStatus"]') - .contains('Submitted a few seconds ago'); + cy.get('[data-cy="approvalHistoryTimeline"]') + .children('.p-timeline-event') + .eq(0) + .should('contain.text', 'Approval Requested') + .should('contain.text', 'a few seconds ago'); cy.get('[data-cy="selfReportAlert"]').should('not.exist') // refresh the page and validate that submit button is disabled and approval status is still displayed @@ -371,10 +381,11 @@ describe('Client Display Self Report Skills Tests', () => { .should('not.exist'); cy.get('[data-cy="overallPointsEarnedCard"] [data-cy="mediaInfoCardTitle"]') .contains('0'); - cy.get('[data-cy="pendingApprovalStatus"]') - .contains('ending approval'); - cy.get('[data-cy="pendingApprovalStatus"]') - .contains('Submitted a few seconds ago'); + cy.get('[data-cy="approvalHistoryTimeline"]') + .children('.p-timeline-event') + .eq(0) + .should('contain.text', 'Approval Requested') + .should('contain.text', 'a few seconds ago'); // approve and then visit page again cy.approveRequest(); @@ -384,8 +395,15 @@ describe('Client Display Self Report Skills Tests', () => { cy.get('[data-cy="requestApprovalBtn"]') .should('be.enabled'); - cy.get('[data-cy="pendingApprovalStatus"]') - .should('not.exist'); + cy.get('[data-cy="approvalHistoryTimeline"]') + .children('.p-timeline-event') + .eq(0) + .should('contain.text', 'Approved') + cy.get('[data-cy="approvalHistoryTimeline"]') + .children('.p-timeline-event') + .eq(1) + .should('contain.text', 'Approval Requested') + cy.get('[data-cy="overallPointsEarnedCard"] [data-cy="mediaInfoCardTitle"]') .contains('50'); cy.get('[data-cy="pointsAchievedTodayCard"] [data-cy="mediaInfoCardTitle"]') @@ -408,10 +426,11 @@ describe('Client Display Self Report Skills Tests', () => { .should('not.exist'); cy.get('[data-cy="overallPointsEarnedCard"] [data-cy="mediaInfoCardTitle"]') .contains('0'); - cy.get('[data-cy="pendingApprovalStatus"]') - .contains('pending approval'); - cy.get('[data-cy="pendingApprovalStatus"]') - .contains('Submitted 5 days ago'); + cy.get('[data-cy="approvalHistoryTimeline"]') + .children('.p-timeline-event') + .eq(0) + .should('contain.text', 'Approval Requested') + .should('contain.text', '5 days ago'); }); it('self report - skill was submitted for approval - on subject page', () => { @@ -562,10 +581,19 @@ describe('Client Display Self Report Skills Tests', () => { .should('not.exist'); cy.get('[data-cy="overallPointsEarnedCard"] [data-cy="mediaInfoCardTitle"]') .contains('0'); - cy.get('[data-cy="pendingApprovalStatus"]') - .contains('pending approval'); - cy.get('[data-cy="pendingApprovalStatus"]') - .contains('Submitted a few seconds ago'); + cy.get('[data-cy="approvalHistoryTimeline"]') + .children('.p-timeline-event') + .eq(0) + .should('contain.text', 'Approval Requested') + .should('contain.text', 'a few seconds ago'); + cy.get('[data-cy="approvalHistoryTimeline"]') + .children('.p-timeline-event') + .eq(1) + .should('contain.text', 'Rejected') + cy.get('[data-cy="approvalHistoryTimeline"]') + .children('.p-timeline-event') + .eq(2) + .should('contain.text', 'Approval Requested') }); it('self report - resubmit rejected skill - on subject page', () => { diff --git a/e2e-tests/package.json b/e2e-tests/package.json index 612adf2b9..a52ea796a 100644 --- a/e2e-tests/package.json +++ b/e2e-tests/package.json @@ -30,7 +30,7 @@ "cy:run:oauth": "TZ=UTC cypress run --browser chrome --env oauthMode=true --config video=true", "cy:run:accessibility": "TZ=UTC cypress run --browser chrome --env enableLighthouse=true,enableAvgLighthouseScore=true --browser chrome --headless --spec \"cypress/e2e/accessibility/*.js\" --config video=true", "cy:run:dev": "TZ=UTC cypress run --browser chrome --config baseUrl=http://localhost:5173 --env visualRegressionType=base", - "cy:run:dev:specificTest": "TZ=UTC cypress run --browser chrome --config baseUrl=http://localhost:5173 --spec \"cypress/e2e/users_spec.js\" --env visualRegressionType=base", + "cy:run:dev:specificTest": "TZ=UTC cypress run --browser chrome --config baseUrl=http://localhost:5173 --spec \"cypress/e2e/client-display/client-display-self_report_skills_spec.js\" --env visualRegressionType=base", "cy:run:dev:updateSnapshotForSpecificTests": "TZ=UTC cypress run --browser chrome --env visualRegressionType=base --config baseUrl=http://localhost:5173 --spec \"cypress/e2e/metrics/skillMetrics_spec.js\"", "cy:run:dev:metrics": "TZ=UTC cypress run --browser chrome --config baseUrl=http://localhost:5173 --spec \"cypress/e2e/metrics/projectMetrics_projects_spec.js,cypress/e2e/metrics/projectMetrics_subjects_spec.js,cypress/e2e/metrics/skillMetrics_spec.js,cypress/e2e/metrics/subjectMetrics_spec.js\"", "cy:run:dev:updateAdminSnapshots": "TZ=UTC cypress run --browser chrome --config baseUrl=http://localhost:5173 --env visualRegressionType=base --spec \"cypress/e2e/markdown_spec.js\"", diff --git a/service/src/main/java/skills/skillLoading/ApprovalHistoryLoader.groovy b/service/src/main/java/skills/skillLoading/ApprovalHistoryLoader.groovy new file mode 100644 index 000000000..6d3ba5dcc --- /dev/null +++ b/service/src/main/java/skills/skillLoading/ApprovalHistoryLoader.groovy @@ -0,0 +1,143 @@ +/** + * Copyright 2020 SkillTree + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package skills.skillLoading + +import groovy.transform.CompileStatic +import groovy.util.logging.Slf4j +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.data.domain.PageRequest +import org.springframework.data.domain.Sort +import org.springframework.stereotype.Component +import skills.controller.exceptions.SkillException +import skills.skillLoading.model.* +import skills.storage.model.* +import skills.storage.repos.* + +import static skills.storage.model.SkillDef.SelfReportingType +import static skills.storage.repos.SkillApprovalRepo.* + +@Component +@CompileStatic +@Slf4j +class ApprovalHistoryLoader { + + static final String REQUESTED = "Approval Requested" +// static final String PENDING = "Pending Approval" + static final String APPROVED = "Approved" + static final String REJECTED = "Rejected" + static final String AWAITING_GRADING = "Awaiting Grading" + static final String PASSED = "Passed" + static final String FAILED = "Failed" + static final String COMPLETED = "Completed" + + @Autowired + SkillApprovalRepo skillApprovalRepo + + @Autowired + UserQuizAttemptRepo userQuizAttemptRepo + + List loadApprovalHistory(SkillDefWithExtra skillDef, String userId, QuizToSkillDefRepo.QuizNameAndId quizNameAndId) { + List approvalHistory = [] + if (skillDef.selfReportingType == SelfReportingType.Approval) { + String queryProjId = skillDef.copiedFrom ? skillDef.copiedFromProjectId : skillDef.projectId + Integer querySkillRefId = skillDef.copiedFrom ? skillDef.copiedFrom : skillDef.id + List skillApprovals = skillApprovalRepo.findApprovalHistoryForSkillsDisplay(userId, queryProjId, querySkillRefId) + skillApprovals.eachWithIndex { skillApproval, index -> + if (skillApproval.rejectedOn) { + approvalHistory.add(new ApprovalEvent( + id: skillApproval.approvalId, + eventStatus: REJECTED, + userId: skillApproval.userId, + userIdForDisplay: skillApproval.userIdForDisplay, + approverUserId: skillApproval.approverUserId, + approverUserIdForDisplay: skillApproval.approverUserIdForDisplay, + eventTime: skillApproval.rejectedOn?.time, + description: skillApproval.rejectionMsg, + )) + approvalHistory.add(new ApprovalEvent( + id: "${skillApproval.approvalId}-${index}", + eventStatus: REQUESTED, + userId: skillApproval.userId, + userIdForDisplay: skillApproval.userIdForDisplay, + eventTime: skillApproval.requestedOn?.time, + description: skillApproval.requestMsg, + )) + } else if (skillApproval.requestedOn && skillApproval.approverUserId) { + approvalHistory.add(new ApprovalEvent( + id: skillApproval.approvalId, + eventStatus: APPROVED, + userId: skillApproval.userId, + userIdForDisplay: skillApproval.userIdForDisplay, + approverUserId: skillApproval.approverUserId, + approverUserIdForDisplay: skillApproval.approverUserIdForDisplay, + eventTime: skillApproval.approverActionTakenOn?.time, +// description: skillApproval.approvalMsg, + )) + approvalHistory.add(new ApprovalEvent( + id: "${skillApproval.approvalId}-${index}", + eventStatus: REQUESTED, + userId: skillApproval.userId, + userIdForDisplay: skillApproval.userIdForDisplay, + eventTime: skillApproval.requestedOn?.time, + description: skillApproval.requestMsg, + )) + } else { + approvalHistory.add(new ApprovalEvent( + id: skillApproval.approvalId, + eventStatus: REQUESTED, + userId: skillApproval.userId, + userIdForDisplay: skillApproval.userIdForDisplay, + eventTime: skillApproval.requestedOn?.time, + description: skillApproval.requestMsg, + )) + } + } +// if (approvalHistory && approvalHistory[0].eventStatus == REQUESTED) { +// approvalHistory.add(0, new ApprovalEvent(eventStatus: PENDING)) +// } + } else if (skillDef.selfReportingType == SelfReportingType.Quiz && quizNameAndId) { + if (quizNameAndId) { + PageRequest allPlease = PageRequest.of(0, Integer.MAX_VALUE, Sort.by(Sort.Direction.DESC, "updated")) + List gradingAttempts = userQuizAttemptRepo.findByQuizRefIdAndUserIdAndStatus( + quizNameAndId.getQuizRefId(), + userId, + [UserQuizAttempt.QuizAttemptStatus.NEEDS_GRADING, UserQuizAttempt.QuizAttemptStatus.PASSED, UserQuizAttempt.QuizAttemptStatus.FAILED], allPlease) + gradingAttempts.each { UserQuizAttempt attempt -> + approvalHistory.add(new ApprovalEvent( + id: attempt.id, + eventStatus: getEventStatusForQuizAttempt(attempt, quizNameAndId), + userId: attempt.userId, + eventTime: attempt.completed?.time, + )) + } + } + } + return approvalHistory + } + + private String getEventStatusForQuizAttempt(UserQuizAttempt attempt, QuizToSkillDefRepo.QuizNameAndId quizNameAndId) { + if (attempt.status == UserQuizAttempt.QuizAttemptStatus.NEEDS_GRADING) { + return AWAITING_GRADING + } + if (attempt.status == UserQuizAttempt.QuizAttemptStatus.PASSED) { + return quizNameAndId.quizType == QuizDefParent.QuizType.Survey ? COMPLETED : PASSED + } + if (attempt.status == UserQuizAttempt.QuizAttemptStatus.FAILED) { + return FAILED + } + throw new SkillException("Unknown attempt status [${attempt.status}]") + } +} diff --git a/service/src/main/java/skills/skillLoading/SkillsLoader.groovy b/service/src/main/java/skills/skillLoading/SkillsLoader.groovy index ecaf88f28..e413239d0 100644 --- a/service/src/main/java/skills/skillLoading/SkillsLoader.groovy +++ b/service/src/main/java/skills/skillLoading/SkillsLoader.groovy @@ -62,7 +62,7 @@ import skills.utils.InputSanitizer import java.util.stream.Stream import static skills.services.LevelDefinitionStorageService.LevelInfo -import static skills.storage.model.SkillDef.ContainerType +import static skills.storage.model.SkillDef.* @Component @CompileStatic @@ -171,6 +171,9 @@ class SkillsLoader { @Autowired TaskConfig taskConfig + @Autowired + ApprovalHistoryLoader approvalHistoryLoader + private static String PROP_HELP_URL_ROOT = CommonSettings.HELP_URL_ROOT @Transactional(readOnly = true) @@ -646,6 +649,8 @@ class SkillsLoader { String unsanitizedName = InputSanitizer.unsanitizeName(skillDef.name) boolean isReusedSkill = SkillReuseIdUtil.isTagged(unsanitizedName) + + List approvalHistory = approvalHistoryLoader.loadApprovalHistory(skillDef, userId, quizNameAndId) return new SkillSummary( projectId: skillDef.projectId, projectName: InputSanitizer.unsanitizeName(projDef.name), @@ -681,7 +686,8 @@ class SkillsLoader { mostRecentlyPerformedOn: mostRecentlyPerformedOn, lastExpirationDate: lastExpirationDate, groupName: groupName, - groupSkillId: skillDef.groupId + groupSkillId: skillDef.groupId, + approvalHistory: approvalHistory, ) } diff --git a/service/src/main/java/skills/skillLoading/model/ApprovalEvent.groovy b/service/src/main/java/skills/skillLoading/model/ApprovalEvent.groovy new file mode 100644 index 000000000..a7732538a --- /dev/null +++ b/service/src/main/java/skills/skillLoading/model/ApprovalEvent.groovy @@ -0,0 +1,27 @@ +/** + * Copyright 2020 SkillTree + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package skills.skillLoading.model + +class ApprovalEvent { + String id = 0 + String userId + String userIdForDisplay + String approverUserId + String approverUserIdForDisplay + String eventStatus + Long eventTime + String description +} diff --git a/service/src/main/java/skills/skillLoading/model/SkillSummary.groovy b/service/src/main/java/skills/skillLoading/model/SkillSummary.groovy index 083865dd9..bd2e0c97b 100644 --- a/service/src/main/java/skills/skillLoading/model/SkillSummary.groovy +++ b/service/src/main/java/skills/skillLoading/model/SkillSummary.groovy @@ -16,8 +16,6 @@ package skills.skillLoading.model import skills.storage.model.SimpleBadgeRes -import skills.storage.model.SkillDef -import skills.storage.model.SkillDefWithExtra class SkillSummary extends SkillSummaryParent { @@ -56,4 +54,6 @@ class SkillSummary extends SkillSummaryParent { String groupName String groupSkillId + + List approvalHistory = [] } diff --git a/service/src/main/java/skills/storage/repos/SkillApprovalRepo.groovy b/service/src/main/java/skills/storage/repos/SkillApprovalRepo.groovy index e5990eb32..d38bd7aad 100644 --- a/service/src/main/java/skills/storage/repos/SkillApprovalRepo.groovy +++ b/service/src/main/java/skills/storage/repos/SkillApprovalRepo.groovy @@ -316,6 +316,29 @@ interface SkillApprovalRepo extends CrudRepository { )''') List findApprovalForSkillsDisplay(String userId, String projectId, Integer skillRefId, Pageable pageable) + @Nullable + @Query(''' + SELECT + s.id as approvalId, + s.userId as userId, + uAttrs.userIdForDisplay as userIdForDisplay, + approverUAttrs.userId as approverUserId, + approverUAttrs.userIdForDisplay as approverUserIdForDisplay, + s.requestedOn as requestedOn, + s.approverActionTakenOn as approverActionTakenOn, + s.rejectedOn as rejectedOn, + s.requestMsg as requestMsg, + s.rejectionMsg as rejectionMsg + FROM SkillApproval s + JOIN UserAttrs uAttrs on uAttrs.userId = s.userId + LEFT OUTER JOIN UserAttrs approverUAttrs on approverUAttrs.userId = s.approverUserId + WHERE s.userId = ?1 and + s.projectId = ?2 and + s.skillRefId = ?3 + ORDER BY s.requestedOn DESC + ''') + List findApprovalHistoryForSkillsDisplay(String userId, String projectId, Integer skillRefId) + interface SkillApprovalPlusSkillId { SkillApproval getSkillApproval() String getSkillId() diff --git a/service/src/test/java/skills/intTests/clientDisplay/SingleSkillSummarySpec.groovy b/service/src/test/java/skills/intTests/clientDisplay/SingleSkillSummarySpec.groovy index b698b3684..4ca629f34 100644 --- a/service/src/test/java/skills/intTests/clientDisplay/SingleSkillSummarySpec.groovy +++ b/service/src/test/java/skills/intTests/clientDisplay/SingleSkillSummarySpec.groovy @@ -24,6 +24,7 @@ import skills.intTests.utils.DefaultIntSpec import skills.intTests.utils.SkillsClientException import skills.intTests.utils.SkillsFactory import skills.intTests.utils.SkillsService +import skills.skillLoading.ApprovalHistoryLoader import skills.storage.model.SkillApproval import skills.storage.model.SkillDef import skills.storage.repos.SkillApprovalRepo @@ -350,10 +351,43 @@ class SingleSkillSummarySpec extends DefaultIntSpec { approvals.totalCount == 1 approvalsHistoryUser1.totalCount == 3 + // latest event is loaded for selfReporting summary1.selfReporting.enabled !summary1.selfReporting.rejectionMsg summary1.selfReporting.requestedOn == dates[0].time !summary1.selfReporting.rejectedOn + + // approvalHistory still loads the entire history + summary1.approvalHistory + summary1.approvalHistory.size() == 7 + summary1.approvalHistory[0].eventStatus == ApprovalHistoryLoader.REQUESTED + summary1.approvalHistory[0].userId == users[0] + summary1.approvalHistory[0].eventTime == dates[0].time + summary1.approvalHistory[0].description == 'approve 3' + + summary1.approvalHistory[1].eventStatus == ApprovalHistoryLoader.APPROVED + summary1.approvalHistory[1].userId == users[0] + + summary1.approvalHistory[2].eventStatus == ApprovalHistoryLoader.REQUESTED + summary1.approvalHistory[2].userId == users[0] + summary1.approvalHistory[2].eventTime == dates[1].time + summary1.approvalHistory[2].description == 'approve 2' + + summary1.approvalHistory[3].eventStatus == ApprovalHistoryLoader.REJECTED + summary1.approvalHistory[3].userId == users[0] + + summary1.approvalHistory[4].eventStatus == ApprovalHistoryLoader.REQUESTED + summary1.approvalHistory[4].userId == users[0] + summary1.approvalHistory[4].eventTime == dates[2].time + summary1.approvalHistory[4].description == 'reject 1' + + summary1.approvalHistory[5].eventStatus == ApprovalHistoryLoader.APPROVED + summary1.approvalHistory[5].userId == users[0] + + summary1.approvalHistory[6].eventStatus == ApprovalHistoryLoader.REQUESTED + summary1.approvalHistory[6].userId == users[0] + summary1.approvalHistory[6].eventTime == dates[3].time + summary1.approvalHistory[6].description == 'approve 1' } def "when a self-reporting skill has a history of approvals only load the latest approval info - latest approval request was approved"() { @@ -396,10 +430,46 @@ class SingleSkillSummarySpec extends DefaultIntSpec { then: approvalsHistoryUser1.totalCount == 4 + // latest event is loaded for selfReporting summary1.selfReporting.enabled !summary1.selfReporting.rejectionMsg !summary1.selfReporting.requestedOn !summary1.selfReporting.rejectedOn + + // approvalHistory still loads the entire history + summary1.approvalHistory + summary1.approvalHistory.size() == 8 + summary1.approvalHistory[0].eventStatus == ApprovalHistoryLoader.APPROVED + summary1.approvalHistory[0].userId == users[0] + + summary1.approvalHistory[1].eventStatus == ApprovalHistoryLoader.REQUESTED + summary1.approvalHistory[1].userId == users[0] + summary1.approvalHistory[1].eventTime == dates[0].time + summary1.approvalHistory[1].description == 'approve 3' + + summary1.approvalHistory[2].eventStatus == ApprovalHistoryLoader.APPROVED + summary1.approvalHistory[2].userId == users[0] + + summary1.approvalHistory[3].eventStatus == ApprovalHistoryLoader.REQUESTED + summary1.approvalHistory[3].userId == users[0] + summary1.approvalHistory[3].eventTime == dates[1].time + summary1.approvalHistory[3].description == 'approve 2' + + summary1.approvalHistory[4].eventStatus == ApprovalHistoryLoader.REJECTED + summary1.approvalHistory[4].userId == users[0] + + summary1.approvalHistory[5].eventStatus == ApprovalHistoryLoader.REQUESTED + summary1.approvalHistory[5].userId == users[0] + summary1.approvalHistory[5].eventTime == dates[2].time + summary1.approvalHistory[5].description == 'reject 1' + + summary1.approvalHistory[6].eventStatus == ApprovalHistoryLoader.APPROVED + summary1.approvalHistory[6].userId == users[0] + + summary1.approvalHistory[7].eventStatus == ApprovalHistoryLoader.REQUESTED + summary1.approvalHistory[7].userId == users[0] + summary1.approvalHistory[7].eventTime == dates[3].time + summary1.approvalHistory[7].description == 'approve 1' } def "user can remove approval rejection from their view"() {