Skip to content

Commit 7019fb9

Browse files
authored
Merge pull request #3213 from NationalSecurityAgency/t#3199/cache_quiz_text_answers
T#3199/cache quiz text answers
2 parents 73b0a31 + 07378fb commit 7019fb9

File tree

8 files changed

+319
-5
lines changed

8 files changed

+319
-5
lines changed

dashboard/pom.xml

+1
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@
130130
<validHeader>${basedir}/../license-add/LICENSE-HEADER_2021.txt</validHeader>
131131
<validHeader>${basedir}/../license-add/LICENSE-HEADER_2022.txt</validHeader>
132132
<validHeader>${basedir}/../license-add/LICENSE-HEADER_2023.txt</validHeader>
133+
<validHeader>${basedir}/../license-add/LICENSE-HEADER_2024.txt</validHeader>
133134
</validHeaders>
134135
<headerDefinitions>
135136
<headerDefinition>vue-header-style.xml</headerDefinition>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright 2025 SkillTree
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
export const useCheckIfAnswerChangedForValidation = () => {
17+
const cache = new Map()
18+
const hasValueChanged = (newValue, testContext) => {
19+
const quizAnswers = testContext?.parent.quizAnswers
20+
if (quizAnswers && quizAnswers.length === 1 && quizAnswers[0].id) {
21+
const answerId = quizAnswers[0].id
22+
const cachedValue = cache.get(answerId)
23+
if (cachedValue === newValue) {
24+
return false
25+
}
26+
cache.set(answerId, newValue)
27+
}
28+
return true
29+
}
30+
const reset = () => {
31+
cache.clear()
32+
}
33+
const removeAnswer = (testContext) => {
34+
const quizAnswers = testContext?.parent.quizAnswers
35+
if (quizAnswers && quizAnswers.length === 1 && quizAnswers[0].id) {
36+
const answerId = quizAnswers[0].id
37+
cache.delete(answerId)
38+
}
39+
}
40+
return {
41+
hasValueChanged,
42+
removeAnswer,
43+
reset
44+
}
45+
}

dashboard/src/skills-display/components/quiz/QuizRun.vue

+4-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { computed, nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
1818
import { object, string, number, array } from 'yup';
1919
import { useSkillsAnnouncer } from '@/common-components/utilities/UseSkillsAnnouncer.js'
2020
import { useTimeUtils } from '@/common-components/utilities/UseTimeUtils.js'
21+
import { useCheckIfAnswerChangedForValidation } from '@/common-components/utilities/UseCheckIfAnswerChangedForValidation.js'
2122
import dayjs from '@/common-components/DayJsCustomizer.js'
2223
import SkillsSpinner from '@/components/utils/SkillsSpinner.vue';
2324
@@ -59,6 +60,7 @@ const announcer = useSkillsAnnouncer()
5960
const timeUtils = useTimeUtils()
6061
const appConfig = useAppConfig()
6162
const numFormat = useNumberFormat()
63+
const checkIfAnswerChangedForValidation = useCheckIfAnswerChangedForValidation()
6264
6365
const isLoading = ref(true);
6466
const isCompleting = ref(false);
@@ -96,7 +98,7 @@ const schema = object({
9698
then: (sch) => sch
9799
.trim()
98100
.required((d) => `Answer to question #${getQuestionNumFromPath(d.path)} is required`)
99-
.customDescriptionValidator(null, false, null, (d) => `Answer to question #${getQuestionNumFromPath(d.path)}`),
101+
.customDescriptionValidator(null, false, null, (d) => `Answer to question #${getQuestionNumFromPath(d.path)}`, true, isSubmitting.value, errors.value),
100102
}),
101103
'answerRating': number()
102104
.when('questionType', {
@@ -266,6 +268,7 @@ const initializeFormData = (copy) => {
266268
answerRating: answerRating ? Number(answerRating.answerOption) : 0,
267269
}
268270
})
271+
checkIfAnswerChangedForValidation.reset()
269272
resetForm({ values: { questions: formQuestions }, errors: {} });
270273
}
271274
const updateSelectedAnswers = (questionSelectedAnswer) => {

dashboard/src/validators/UseCustomGlobalValidators.js

+10-3
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@ import { addMethod, string } from 'yup'
1818
import { useAppConfig } from '@/common-components/stores/UseAppConfig.js'
1919
import { useDebounceFn } from '@vueuse/core'
2020
import { useDescriptionValidatorService } from '@/common-components/validators/UseDescriptionValidatorService.js'
21+
import { useCheckIfAnswerChangedForValidation } from '@/common-components/utilities/UseCheckIfAnswerChangedForValidation.js'
2122
import { useLog } from '@/components/utils/misc/useLog.js'
2223

2324
export const useCustomGlobalValidators = () => {
2425

2526
const descriptionValidatorService = useDescriptionValidatorService()
27+
const checkIfAnswerChangedForValidation = useCheckIfAnswerChangedForValidation()
2628
const log = useLog()
2729

2830
function customNameValidator(fieldName = '') {
@@ -42,16 +44,21 @@ export const useCustomGlobalValidators = () => {
4244
return this.test("customNameValidator", null, (value, context) => validateName(value, context));
4345
}
4446

45-
function customDescriptionValidator(fieldName = '', enableProjectIdParam = true, useProtectedCommunityValidator = null, fieldNameFunction = null, enableQuizIdParam = true) {
47+
function customDescriptionValidator(fieldName = '', enableProjectIdParam = true, useProtectedCommunityValidator = null, fieldNameFunction = null, enableQuizIdParam = true, isSubmitting = false, errors = null) {
4648
const appConfig = useAppConfig()
47-
const validateName = useDebounceFn((value, context) => {
49+
const validateDescription = useDebounceFn((value, context) => {
4850
if (!value || value.trim().length === 0 || !appConfig.paragraphValidationRegex) {
4951
return true
5052
}
53+
const forceAnswerValidation = isSubmitting || errors?.hasOwnProperty(context.path)
54+
if (!forceAnswerValidation && !checkIfAnswerChangedForValidation.hasValueChanged(context.originalValue, context)) {
55+
return true
56+
}
5157
return descriptionValidatorService.validateDescription(value, enableProjectIdParam, useProtectedCommunityValidator, enableQuizIdParam).then((result) => {
5258
if (result.valid) {
5359
return true
5460
}
61+
checkIfAnswerChangedForValidation.removeAnswer(context)
5562
let fieldNameToUse = fieldName ? fieldName : '';
5663
if (!fieldNameToUse && fieldNameFunction) {
5764
fieldNameToUse = fieldNameFunction(context);
@@ -63,7 +70,7 @@ export const useCustomGlobalValidators = () => {
6370
})
6471
}, appConfig.formFieldDebounceInMs)
6572

66-
return this.test("customDescriptionValidator", null, (value, context) => validateName(value, context));
73+
return this.test("customDescriptionValidator", null, (value, context) => validateDescription(value, context));
6774
}
6875

6976
function nullValueNotAllowed() {

0 commit comments

Comments
 (0)