fix: highlighting for question with cyclic logic (#2669)

Co-authored-by: Johannes <72809645+jobenjada@users.noreply.github.com>
This commit is contained in:
Dhruwang Jariwala
2024-05-24 12:55:46 +05:30
committed by GitHub
parent c8f2f94361
commit 1848e062f1
2 changed files with 26 additions and 14 deletions
@@ -19,7 +19,12 @@ import { checkForEmptyFallBackValue, extractRecallInfo } from "@formbricks/lib/u
import { TProduct } from "@formbricks/types/product";
import { TSurvey, TSurveyQuestion } from "@formbricks/types/surveys";
import { isCardValid, validateQuestion, validateSurveyQuestionsInBatch } from "../lib/validation";
import {
findQuestionsWithCyclicLogic,
isCardValid,
validateQuestion,
validateSurveyQuestionsInBatch,
} from "../lib/validation";
import { AddQuestionButton } from "./AddQuestionButton";
import { EditThankYouCard } from "./EditThankYouCard";
import { EditWelcomeCard } from "./EditWelcomeCard";
@@ -89,8 +94,12 @@ export const QuestionsView = ({
const isFirstQuestion = question.id === localSurvey.questions[0].id;
let temp = structuredClone(invalidQuestions);
if (validateQuestion(question, surveyLanguages, isFirstQuestion)) {
temp = invalidQuestions.filter((id) => id !== question.id);
setInvalidQuestions(temp);
// If question is valid, we now check for cyclic logic
const questionsWithCyclicLogic = findQuestionsWithCyclicLogic(localSurvey.questions);
if (!questionsWithCyclicLogic.includes(question.id)) {
temp = invalidQuestions.filter((id) => id !== question.id);
setInvalidQuestions(temp);
}
} else if (!invalidQuestions.includes(question.id)) {
temp.push(question.id);
setInvalidQuestions(temp);
@@ -246,12 +246,13 @@ export const validateId = (
return true;
};
// Checks if there is a cycle present in the survey data logic.
export const isSurveyLogicCyclic = (questions: TSurveyQuestions) => {
// Checks if there is a cycle present in the survey data logic and returns all questions responsible for the cycle.
export const findQuestionsWithCyclicLogic = (questions: TSurveyQuestions): string[] => {
const visited: Record<string, boolean> = {};
const recStack: Record<string, boolean> = {};
const cyclicQuestions: Set<string> = new Set();
const checkForCycle = (questionId: string) => {
const checkForCyclicLogic = (questionId: string): boolean => {
if (!visited[questionId]) {
visited[questionId] = true;
recStack[questionId] = true;
@@ -261,12 +262,14 @@ export const isSurveyLogicCyclic = (questions: TSurveyQuestions) => {
for (const logic of question.logic) {
const destination = logic.destination;
if (!destination) {
return false;
continue;
}
if (!visited[destination] && checkForCycle(destination)) {
if (!visited[destination] && checkForCyclicLogic(destination)) {
cyclicQuestions.add(questionId);
return true;
} else if (recStack[destination]) {
cyclicQuestions.add(questionId);
return true;
}
}
@@ -274,7 +277,7 @@ export const isSurveyLogicCyclic = (questions: TSurveyQuestions) => {
// Handle default behavior
const nextQuestionIndex = questions.findIndex((question) => question.id === questionId) + 1;
const nextQuestion = questions[nextQuestionIndex];
if (nextQuestion && !visited[nextQuestion.id] && checkForCycle(nextQuestion.id)) {
if (nextQuestion && !visited[nextQuestion.id] && checkForCyclicLogic(nextQuestion.id)) {
return true;
}
}
@@ -286,12 +289,10 @@ export const isSurveyLogicCyclic = (questions: TSurveyQuestions) => {
for (const question of questions) {
const questionId = question.id;
if (checkForCycle(questionId)) {
return true;
}
checkForCyclicLogic(questionId);
}
return false;
return Array.from(cyclicQuestions);
};
export const isSurveyValid = (
@@ -455,7 +456,9 @@ export const isSurveyValid = (
}
// Detecting any cyclic dependencies in survey logic.
if (isSurveyLogicCyclic(survey.questions)) {
const questionsWithCyclicLogic = findQuestionsWithCyclicLogic(survey.questions);
if (questionsWithCyclicLogic.length > 0) {
setInvalidQuestions(questionsWithCyclicLogic);
toast.error("Cyclic logic detected. Please fix it before saving.");
return false;
}