diff --git a/compendium-frontend/src/survey/management/SurveyManagementComponent.tsx b/compendium-frontend/src/survey/management/SurveyManagementComponent.tsx index 8f1138f96e4558839b76d7deb00e498e82c2b34a..d1fedda1b4e940ebd6657d1469f678bf76a41961 100644 --- a/compendium-frontend/src/survey/management/SurveyManagementComponent.tsx +++ b/compendium-frontend/src/survey/management/SurveyManagementComponent.tsx @@ -13,6 +13,7 @@ import { Spinner } from "react-bootstrap"; import StatusButton from "../StatusButton"; import { Link } from "react-router-dom"; import { debounce } from "lodash" +import { validateSurvey } from "../utils"; function updateSurveyNotes(year: number, nren_id: number, notes: string) { fetch('/api/survey/' + year + '/' + nren_id + '/notes', { @@ -218,6 +219,9 @@ function SurveyManagementComponent() { title="Remove the lock from the survey so that another person can open the survey for editing. WARNING: The person that currently has the lock will not be able to save their changes anymore once someone else starts editing!"> remove lock </Button> + <Button onClick={() => validateSurvey(survey.year, response.nren.name).then(console.log)} style={{ pointerEvents: 'auto' }}> + Validate Survey + </Button> </td> </tr> ))} diff --git a/compendium-frontend/src/survey/utils.ts b/compendium-frontend/src/survey/utils.ts new file mode 100644 index 0000000000000000000000000000000000000000..a713a8a2750aa11bb7a33b5a46a39f18e90635f9 --- /dev/null +++ b/compendium-frontend/src/survey/utils.ts @@ -0,0 +1,60 @@ +import { validateQuestion, oldValidateWebsiteUrl } from './validation/validation'; + +export async function validateSurvey(year, nren) { + const { Model, FunctionFactory, Serializer } = await import('survey-core'); + + function setupProperties() { + const descriptionProperty = Serializer.getAllPropertiesByName("customDescription"); + const hideCheckboxLabelsProperty = Serializer.getAllPropertiesByName("hideCheckboxLabels"); + + if (!descriptionProperty.length) { + Serializer.addProperty("itemvalue", "customDescription:text"); + } + if (!hideCheckboxLabelsProperty.length) { + Serializer.addProperty("question", "hideCheckboxLabels:boolean"); + } + } + + if (!FunctionFactory.Instance.hasFunction("validateQuestion")) { + FunctionFactory.Instance.register("validateQuestion", validateQuestion); + } + + if (!FunctionFactory.Instance.hasFunction("validateWebsiteUrl")) { + FunctionFactory.Instance.register("validateWebsiteUrl", oldValidateWebsiteUrl); + } + + if (!year || !nren) { + return true; + } + const url = `/api/response/load/${year}/${nren}`; + + const response = await fetch(url); + const json = await response.json(); + + if (!response.ok) { + + if ('message' in json) { + throw new Error(json.message); + } else { + throw new Error(`Request failed with status ${response.status}`); + } + } + + // Setup custom properties that allow the survey to work + setupProperties(); + + const survey = new Model(json['model']); + survey.setVariable('surveyyear', year); + survey.setVariable('previousyear', parseInt(year!) - 1); + + survey.showNavigationButtons = false; + survey.requiredText = ''; + + survey.data = json['data']; + survey.clearIncorrectValues(true); + + const valid = survey.validate.bind(survey, true, true)(); + + console.log(survey) + return valid; +} \ No newline at end of file diff --git a/compendium-frontend/src/survey/validation/validation.ts b/compendium-frontend/src/survey/validation/validation.ts index e0138982678715e62b816bb868d6eb6432192531..f968223b7d77494d972b770e868f2f8fe19a29bb 100644 --- a/compendium-frontend/src/survey/validation/validation.ts +++ b/compendium-frontend/src/survey/validation/validation.ts @@ -1,3 +1,9 @@ +interface ValidationQuestion { + name?: string; + value?: string | number | null; + data?: ValidationQuestion; +} + function validateWebsiteUrl(value, nonEmpty = false) { if (!nonEmpty && (value == undefined || value == null || value == '')) { return true; @@ -17,6 +23,67 @@ function validateWebsiteUrl(value, nonEmpty = false) { return false; } } + +// Overrides for questions that need to be validated differently from the default expression in their group +const questionOverrides = { + data_protection_contact: (..._args) => true, // don't validate the contact field, anything goes.. +} + +export function validateQuestion(this: { question: ValidationQuestion, row?}, params) { + try { + const question = this.question; + const validator = params[0] || undefined; + + const matrix = question.data && 'name' in question.data; + + let questionName; + + if (matrix) { + questionName = question.data!.name; + } else { + questionName = question.name; + } + const value = question.value + + const hasOverride = questionOverrides[questionName]; + if (hasOverride) { + return hasOverride(value, ...params.slice(1)); + } + + const validationFunction = validationFunctions[validator]; + if (!validationFunction) { + throw new Error(`Validation function ${validator} not found for question ${questionName}`); + } + + return validationFunction(value, ...params.slice(1)); + } catch (e) { + console.error(e); + return false; + } +} + +export function oldValidateWebsiteUrl(params) { + let value = params[0]; + if ((value == undefined || value == null || value == '')) { + return true; + } + try { + value = value.trim(); + if (value.includes(" ")) { + return false; + } + // if there's not a protocol, add one for the test + if (!value.includes(":/")) { + value = "https://" + value; + } + + const url = new URL(value); + return !!url + } catch { + return false; + } +} + export const validationFunctions = { validateWebsiteUrl, } \ No newline at end of file