diff --git a/apps/web/app/lib/api/survey-transformation.ts b/apps/web/app/lib/api/survey-transformation.ts index 92ce4c122d..0a13fb6bda 100644 --- a/apps/web/app/lib/api/survey-transformation.ts +++ b/apps/web/app/lib/api/survey-transformation.ts @@ -439,6 +439,33 @@ const transformBlockLogicToQuestionLogic = ( }); }; +const applyBlockAttributesToElement = ( + element: Record, + block: TSurveyBlock, + blockIdToQuestionId: Map, + endingIds: Set +): void => { + if (element.type === "cta" && element.ctaButtonLabel) { + element.buttonLabel = element.ctaButtonLabel; + } + + if (Array.isArray(block.logic) && block.logic.length > 0) { + element.logic = transformBlockLogicToQuestionLogic(block.logic, blockIdToQuestionId, endingIds); + } + + if (block.logicFallback) { + element.logicFallback = reverseLogicFallback(block.logicFallback, blockIdToQuestionId, endingIds); + } + + if (block.buttonLabel) { + element.buttonLabel = block.buttonLabel; + } + + if (block.backButtonLabel) { + element.backButtonLabel = block.backButtonLabel; + } +}; + export const transformBlocksToQuestions = ( blocks: TSurveyBlock[], endings: TSurveyEnding[] = [] @@ -459,27 +486,9 @@ export const transformBlocksToQuestions = ( for (const block of blocks) { if (block.elements.length === 0) continue; - const element = { ...block.elements[0] } as Record; + const element = { ...block.elements[0] }; - if (element.type === "cta" && element.ctaButtonLabel) { - element.buttonLabel = element.ctaButtonLabel; - } - - if (Array.isArray(block.logic) && block.logic.length > 0) { - element.logic = transformBlockLogicToQuestionLogic(block.logic, blockIdToQuestionId, endingIds); - } - - if (block.logicFallback) { - element.logicFallback = reverseLogicFallback(block.logicFallback, blockIdToQuestionId, endingIds); - } - - if (block.buttonLabel) { - element.buttonLabel = block.buttonLabel; - } - - if (block.backButtonLabel) { - element.backButtonLabel = block.backButtonLabel; - } + applyBlockAttributesToElement(element, block, blockIdToQuestionId, endingIds); questions.push(element); } diff --git a/apps/web/modules/survey/components/element-form-input/components/recall-item-select.tsx b/apps/web/modules/survey/components/element-form-input/components/recall-item-select.tsx index 27f20a7c5a..2db86e28e8 100644 --- a/apps/web/modules/survey/components/element-form-input/components/recall-item-select.tsx +++ b/apps/web/modules/survey/components/element-form-input/components/recall-item-select.tsx @@ -141,16 +141,19 @@ export const RecallItemSelect = ({ const getRecallItemIcon = (recallItem: TSurveyRecallItem) => { switch (recallItem.type) { - case "element": + case "element": { const element = elements.find((element) => element.id === recallItem.id); if (element) { return elementIconMapping[element?.type as keyof typeof elementIconMapping]; } + return null; + } case "hiddenField": return EyeOffIcon; - case "variable": + case "variable": { const variable = localSurvey.variables.find((variable) => variable.id === recallItem.id); return variable?.type === "number" ? FileDigitIcon : FileTextIcon; + } default: return null; } diff --git a/packages/surveys/src/components/general/element-conditional.tsx b/packages/surveys/src/components/general/element-conditional.tsx index 0473155efe..27df5388e6 100644 --- a/packages/surveys/src/components/general/element-conditional.tsx +++ b/packages/surveys/src/components/general/element-conditional.tsx @@ -123,8 +123,8 @@ export function ElementConditional({ return null; } - // NOSONAR - This is readable enough and can't be changed const renderElement = () => { + // NOSONAR - This is readable enough and can't be changed switch (element.type) { case TSurveyElementTypeEnum.OpenText: return ( diff --git a/packages/types/surveys/blocks.ts b/packages/types/surveys/blocks.ts index fe032c76a5..f108baf814 100644 --- a/packages/types/surveys/blocks.ts +++ b/packages/types/surveys/blocks.ts @@ -61,7 +61,20 @@ export type TSurveyBlockLogicActionObjective = "calculate" | "requireAnswer" | " export const ZActionRequireAnswer = z.object({ id: ZId, objective: z.literal("requireAnswer"), - target: ZSurveyElementId, + target: z + .string() + .min(1, "Conditional Logic: Target question id cannot be empty") + .superRefine((id, ctx) => { + const idParsed = ZSurveyElementId.safeParse(id); + if (!idParsed.success) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + // This is not possible from the UI hence we can use the term "element" instead of "question" + message: "Conditional Logic: Target element id is not a valid element id", + path: ["target"], + }); + } + }), }); export type TActionRequireAnswer = z.infer; @@ -71,7 +84,18 @@ export type TActionRequireAnswer = z.infer; export const ZActionJumpToBlock = z.object({ id: ZId, objective: z.literal("jumpToBlock"), - target: ZSurveyBlockId, // Must be a valid CUID + target: z + .string() + .min(1, "Conditional Logic: Target block id cannot be empty") + .superRefine((id, ctx) => { + const idParsed = ZSurveyBlockId.safeParse(id); + if (!idParsed.success) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Conditional Logic: Target block id is not a valid block id", + }); + } + }), }); export type TActionJumpToBlock = z.infer; diff --git a/packages/types/surveys/elements-validation.ts b/packages/types/surveys/elements-validation.ts index ae39a8efe2..11bf989878 100644 --- a/packages/types/surveys/elements-validation.ts +++ b/packages/types/surveys/elements-validation.ts @@ -61,7 +61,7 @@ export const validateElementLabels = ( ) { return { code: z.ZodIssueCode.custom, - message: `The ${field} in element ${String(elementIndex + 1)} of block ${String(blockIndex + 1)} is not present for the following languages: ${language.language.code}`, + message: `The ${field} in question ${String(elementIndex + 1)} of block ${String(blockIndex + 1)} is not present for the following languages: ${language.language.code}`, path: ["blocks", blockIndex, "elements", elementIndex, field], }; } @@ -75,8 +75,8 @@ export const validateElementLabels = ( const messageSuffix = isDefaultOnly ? " is missing" : " is missing for the following languages: "; const message = isDefaultOnly - ? `${messagePrefix}${messageField} in element ${String(elementIndex + 1)} of block ${String(blockIndex + 1)}${messageSuffix}` - : `${messagePrefix}${messageField} in element ${String(elementIndex + 1)} of block ${String(blockIndex + 1)}${messageSuffix} -fLang- ${invalidLanguageCodes.join()}`; + ? `${messagePrefix}${messageField} in question ${String(elementIndex + 1)} of block ${String(blockIndex + 1)}${messageSuffix}` + : `${messagePrefix}${messageField} in question ${String(elementIndex + 1)} of block ${String(blockIndex + 1)}${messageSuffix} -fLang- ${invalidLanguageCodes.join()}`; if (invalidLanguageCodes.length) { return { diff --git a/packages/types/surveys/types.ts b/packages/types/surveys/types.ts index e2f02b1049..b5454e011d 100644 --- a/packages/types/surveys/types.ts +++ b/packages/types/surveys/types.ts @@ -1606,7 +1606,7 @@ export const ZSurvey = z ctx.addIssue({ code: z.ZodIssueCode.custom, - message: `Element ${String(elementIndex + 1)} in block ${String(blockIndex + 1)} has duplicate row labels ${isDefaultOnly ? "" : "for the following languages:"}`, + message: `Question ${String(elementIndex + 1)} in block ${String(blockIndex + 1)} has duplicate row labels ${isDefaultOnly ? "" : "for the following languages:"}`, path: ["blocks", blockIndex, "elements", elementIndex, "rows"], params: isDefaultOnly ? undefined : { invalidLanguageCodes }, }); @@ -1624,7 +1624,7 @@ export const ZSurvey = z ctx.addIssue({ code: z.ZodIssueCode.custom, - message: `Element ${String(elementIndex + 1)} in block ${String(blockIndex + 1)} has duplicate column labels ${isDefaultOnly ? "" : "for the following languages:"}`, + message: `Question ${String(elementIndex + 1)} in block ${String(blockIndex + 1)} has duplicate column labels ${isDefaultOnly ? "" : "for the following languages:"}`, path: ["blocks", blockIndex, "elements", elementIndex, "columns"], params: isDefaultOnly ? undefined : { invalidLanguageCodes }, }); @@ -1635,7 +1635,7 @@ export const ZSurvey = z if (element.allowedFileExtensions && element.allowedFileExtensions.length === 0) { ctx.addIssue({ code: z.ZodIssueCode.custom, - message: `Element ${String(elementIndex + 1)} in block ${String(blockIndex + 1)} must have atleast one allowed file extension`, + message: `Question ${String(elementIndex + 1)} in block ${String(blockIndex + 1)} must have atleast one allowed file extension`, path: ["blocks", blockIndex, "elements", elementIndex, "allowedFileExtensions"], }); } @@ -1647,7 +1647,7 @@ export const ZSurvey = z if (!hostnameRegex.test(element.calHost)) { ctx.addIssue({ code: z.ZodIssueCode.custom, - message: `Element ${String(elementIndex + 1)} in block ${String(blockIndex + 1)} must have a valid host name`, + message: `Question ${String(elementIndex + 1)} in block ${String(blockIndex + 1)} must have a valid host name`, path: ["blocks", blockIndex, "elements", elementIndex, "calHost"], }); } @@ -1667,7 +1667,7 @@ export const ZSurvey = z if (fields.every((field) => !field.show)) { ctx.addIssue({ code: z.ZodIssueCode.custom, - message: `At least one field must be shown in the Contact Info element ${String(elementIndex + 1)} in block ${String(blockIndex + 1)}`, + message: `At least one field must be shown in the Contact Info question ${String(elementIndex + 1)} in block ${String(blockIndex + 1)}`, path: ["blocks", blockIndex, "elements", elementIndex], }); } @@ -1709,7 +1709,7 @@ export const ZSurvey = z if (fields.every((field) => !field.show)) { ctx.addIssue({ code: z.ZodIssueCode.custom, - message: `At least one field must be shown in the Address element ${String(elementIndex + 1)} in block ${String(blockIndex + 1)}`, + message: `At least one field must be shown in the Address question ${String(elementIndex + 1)} in block ${String(blockIndex + 1)}`, path: ["blocks", blockIndex, "elements", elementIndex], }); } @@ -3103,7 +3103,7 @@ const validateBlockConditions = ( if (!elementInfo) { issues.push({ code: z.ZodIssueCode.custom, - message: `Conditional Logic: Element ID ${elementId} does not exist in logic no: ${String(logicIndex + 1)} of block ${String(blockIndex + 1)}`, + message: `Conditional Logic: Element Id ${elementId} does not exist in logic no: ${String(logicIndex + 1)} of block ${String(blockIndex + 1)}`, path: ["blocks", blockIndex, "logic", logicIndex, "conditions"], }); return;