mirror of
https://github.com/formbricks/formbricks.git
synced 2026-04-20 11:22:55 -05:00
moves the integrations code over to the blocks schema
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
import { TFunction } from "i18next";
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { Control, Controller, useForm } from "react-hook-form";
|
||||
import { toast } from "react-hot-toast";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -14,14 +14,15 @@ import {
|
||||
TIntegrationAirtableInput,
|
||||
TIntegrationAirtableTables,
|
||||
} from "@formbricks/types/integration/airtable";
|
||||
import { TSurveyElement } from "@formbricks/types/surveys/elements";
|
||||
import { TSurvey } from "@formbricks/types/surveys/types";
|
||||
import { getTextContent } from "@formbricks/types/surveys/validation";
|
||||
import { createOrUpdateIntegrationAction } from "@/app/(app)/environments/[environmentId]/project/integrations/actions";
|
||||
import { BaseSelectDropdown } from "@/app/(app)/environments/[environmentId]/project/integrations/airtable/components/BaseSelectDropdown";
|
||||
import { fetchTables } from "@/app/(app)/environments/[environmentId]/project/integrations/airtable/lib/airtable";
|
||||
import AirtableLogo from "@/images/airtableLogo.svg";
|
||||
import { getLocalizedValue } from "@/lib/i18n/utils";
|
||||
import { replaceHeadlineRecall } from "@/lib/utils/recall";
|
||||
import { recallToHeadline } from "@/lib/utils/recall";
|
||||
import { getQuestionsFromBlocks } from "@/modules/survey/lib/client-utils";
|
||||
import { AdditionalIntegrationSettings } from "@/modules/ui/components/additional-integration-settings";
|
||||
import { Alert, AlertDescription, AlertTitle } from "@/modules/ui/components/alert";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
@@ -71,6 +72,7 @@ const NoBaseFoundError = () => {
|
||||
const renderQuestionSelection = ({
|
||||
t,
|
||||
selectedSurvey,
|
||||
questions,
|
||||
control,
|
||||
includeVariables,
|
||||
setIncludeVariables,
|
||||
@@ -83,6 +85,7 @@ const renderQuestionSelection = ({
|
||||
}: {
|
||||
t: TFunction;
|
||||
selectedSurvey: TSurvey;
|
||||
questions: TSurveyElement[];
|
||||
control: Control<IntegrationModalInputs>;
|
||||
includeVariables: boolean;
|
||||
setIncludeVariables: (value: boolean) => void;
|
||||
@@ -99,7 +102,7 @@ const renderQuestionSelection = ({
|
||||
<Label htmlFor="Surveys">{t("common.questions")}</Label>
|
||||
<div className="mt-1 max-h-[15vh] overflow-y-auto rounded-lg border border-slate-200">
|
||||
<div className="grid content-center rounded-lg bg-slate-50 p-3 text-left text-sm text-slate-900">
|
||||
{replaceHeadlineRecall(selectedSurvey, "default")?.questions.map((question) => (
|
||||
{questions.map((question) => (
|
||||
<Controller
|
||||
key={question.id}
|
||||
control={control}
|
||||
@@ -120,7 +123,9 @@ const renderQuestionSelection = ({
|
||||
}}
|
||||
/>
|
||||
<span className="ml-2">
|
||||
{getTextContent(getLocalizedValue(question.headline, "default"))}
|
||||
{getTextContent(
|
||||
recallToHeadline(question.headline, selectedSurvey, false, "default")["default"]
|
||||
)}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
@@ -194,6 +199,11 @@ export const AddIntegrationModal = ({
|
||||
};
|
||||
|
||||
const selectedSurvey = surveys.find((item) => item.id === survey);
|
||||
const questions = useMemo(
|
||||
() => (selectedSurvey ? getQuestionsFromBlocks(selectedSurvey.blocks) : []),
|
||||
[selectedSurvey]
|
||||
);
|
||||
|
||||
const submitHandler = async (data: IntegrationModalInputs) => {
|
||||
try {
|
||||
if (!data.base || data.base === "") {
|
||||
@@ -218,7 +228,7 @@ export const AddIntegrationModal = ({
|
||||
surveyName: selectedSurvey.name,
|
||||
questionIds: data.questions,
|
||||
questions:
|
||||
data.questions.length === selectedSurvey.questions.length
|
||||
data.questions.length === questions.length
|
||||
? t("common.all_questions")
|
||||
: t("common.selected_questions"),
|
||||
createdAt: new Date(),
|
||||
@@ -395,6 +405,7 @@ export const AddIntegrationModal = ({
|
||||
renderQuestionSelection({
|
||||
t,
|
||||
selectedSurvey,
|
||||
questions,
|
||||
control,
|
||||
includeVariables,
|
||||
setIncludeVariables,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import toast from "react-hot-toast";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -20,9 +20,9 @@ import {
|
||||
isValidGoogleSheetsUrl,
|
||||
} from "@/app/(app)/environments/[environmentId]/project/integrations/google-sheets/lib/util";
|
||||
import GoogleSheetLogo from "@/images/googleSheetsLogo.png";
|
||||
import { getLocalizedValue } from "@/lib/i18n/utils";
|
||||
import { getFormattedErrorMessage } from "@/lib/utils/helper";
|
||||
import { replaceHeadlineRecall } from "@/lib/utils/recall";
|
||||
import { recallToHeadline } from "@/lib/utils/recall";
|
||||
import { getQuestionsFromBlocks } from "@/modules/survey/lib/client-utils";
|
||||
import { AdditionalIntegrationSettings } from "@/modules/ui/components/additional-integration-settings";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import { Checkbox } from "@/modules/ui/components/checkbox";
|
||||
@@ -86,12 +86,17 @@ export const AddIntegrationModal = ({
|
||||
},
|
||||
};
|
||||
|
||||
const questions = useMemo(
|
||||
() => (selectedSurvey ? getQuestionsFromBlocks(selectedSurvey.blocks) : []),
|
||||
[selectedSurvey]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedSurvey && !selectedIntegration) {
|
||||
const questionIds = selectedSurvey.questions.map((question) => question.id);
|
||||
const questionIds = questions.map((question) => question.id);
|
||||
setSelectedQuestions(questionIds);
|
||||
}
|
||||
}, [selectedIntegration, selectedSurvey]);
|
||||
}, [questions, selectedIntegration, selectedSurvey]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedIntegration) {
|
||||
@@ -145,7 +150,7 @@ export const AddIntegrationModal = ({
|
||||
integrationData.surveyName = selectedSurvey.name;
|
||||
integrationData.questionIds = selectedQuestions;
|
||||
integrationData.questions =
|
||||
selectedQuestions.length === selectedSurvey?.questions.length
|
||||
selectedQuestions.length === questions.length
|
||||
? t("common.all_questions")
|
||||
: t("common.selected_questions");
|
||||
integrationData.createdAt = new Date();
|
||||
@@ -263,7 +268,7 @@ export const AddIntegrationModal = ({
|
||||
<Label htmlFor="Surveys">{t("common.questions")}</Label>
|
||||
<div className="mt-1 max-h-[15vh] overflow-y-auto overflow-x-hidden rounded-lg border border-slate-200">
|
||||
<div className="grid content-center rounded-lg bg-slate-50 p-3 text-left text-sm text-slate-900">
|
||||
{replaceHeadlineRecall(selectedSurvey, "default")?.questions.map((question) => (
|
||||
{questions.map((question) => (
|
||||
<div key={question.id} className="my-1 flex items-center space-x-2">
|
||||
<label htmlFor={question.id} className="flex cursor-pointer items-center">
|
||||
<Checkbox
|
||||
@@ -277,7 +282,11 @@ export const AddIntegrationModal = ({
|
||||
}}
|
||||
/>
|
||||
<span className="ml-2 w-[30rem] truncate">
|
||||
{getTextContent(getLocalizedValue(question.headline, "default"))}
|
||||
{getTextContent(
|
||||
recallToHeadline(question.headline, selectedSurvey, false, "default")[
|
||||
"default"
|
||||
]
|
||||
)}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
TIntegrationNotionDatabase,
|
||||
} from "@formbricks/types/integration/notion";
|
||||
import { TSurvey, TSurveyQuestionTypeEnum } from "@formbricks/types/surveys/types";
|
||||
import { getTextContent } from "@formbricks/types/surveys/validation";
|
||||
import { createOrUpdateIntegrationAction } from "@/app/(app)/environments/[environmentId]/project/integrations/actions";
|
||||
import {
|
||||
ERRORS,
|
||||
@@ -20,9 +21,9 @@ import {
|
||||
UNSUPPORTED_TYPES_BY_NOTION,
|
||||
} from "@/app/(app)/environments/[environmentId]/project/integrations/notion/constants";
|
||||
import NotionLogo from "@/images/notion.png";
|
||||
import { getLocalizedValue } from "@/lib/i18n/utils";
|
||||
import { structuredClone } from "@/lib/pollyfills/structuredClone";
|
||||
import { replaceHeadlineRecall } from "@/lib/utils/recall";
|
||||
import { recallToHeadline } from "@/lib/utils/recall";
|
||||
import { getQuestionsFromBlocks } from "@/modules/survey/lib/client-utils";
|
||||
import { getQuestionTypes } from "@/modules/survey/lib/questions";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import {
|
||||
@@ -91,6 +92,11 @@ export const AddIntegrationModal = ({
|
||||
createdAt: new Date(),
|
||||
};
|
||||
|
||||
const questions = useMemo(
|
||||
() => (selectedSurvey ? getQuestionsFromBlocks(selectedSurvey.blocks) : []),
|
||||
[selectedSurvey]
|
||||
);
|
||||
|
||||
const notionIntegrationData: TIntegrationInput = {
|
||||
type: "notion",
|
||||
config: {
|
||||
@@ -119,10 +125,10 @@ export const AddIntegrationModal = ({
|
||||
}, [selectedDatabase?.id]);
|
||||
|
||||
const questionItems = useMemo(() => {
|
||||
const questions = selectedSurvey
|
||||
? replaceHeadlineRecall(selectedSurvey, "default")?.questions.map((q) => ({
|
||||
const mappedQuestions = selectedSurvey
|
||||
? questions.map((q) => ({
|
||||
id: q.id,
|
||||
name: getLocalizedValue(q.headline, "default"),
|
||||
name: getTextContent(recallToHeadline(q.headline, selectedSurvey, false, "default")["default"]),
|
||||
type: q.type,
|
||||
}))
|
||||
: [];
|
||||
@@ -155,7 +161,7 @@ export const AddIntegrationModal = ({
|
||||
},
|
||||
];
|
||||
|
||||
return [...questions, ...variables, ...hiddenFields, ...Metadata, ...createdAt];
|
||||
return [...mappedQuestions, ...variables, ...hiddenFields, ...Metadata, ...createdAt];
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selectedSurvey?.id]);
|
||||
|
||||
|
||||
@@ -17,8 +17,8 @@ import { TSurvey, TSurveyQuestionId } from "@formbricks/types/surveys/types";
|
||||
import { getTextContent } from "@formbricks/types/surveys/validation";
|
||||
import { createOrUpdateIntegrationAction } from "@/app/(app)/environments/[environmentId]/project/integrations/actions";
|
||||
import SlackLogo from "@/images/slacklogo.png";
|
||||
import { getLocalizedValue } from "@/lib/i18n/utils";
|
||||
import { replaceHeadlineRecall } from "@/lib/utils/recall";
|
||||
import { recallToHeadline } from "@/lib/utils/recall";
|
||||
import { getQuestionsFromBlocks } from "@/modules/survey/lib/client-utils";
|
||||
import { AdditionalIntegrationSettings } from "@/modules/ui/components/additional-integration-settings";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import { Checkbox } from "@/modules/ui/components/checkbox";
|
||||
@@ -73,14 +73,19 @@ export const AddChannelMappingModal = ({
|
||||
},
|
||||
};
|
||||
|
||||
const questions = useMemo(
|
||||
() => (selectedSurvey ? getQuestionsFromBlocks(selectedSurvey.blocks) : []),
|
||||
[selectedSurvey]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedSurvey) {
|
||||
const questionIds = selectedSurvey.questions.map((question) => question.id);
|
||||
const questionIds = questions.map((question) => question.id);
|
||||
if (!selectedIntegration) {
|
||||
setSelectedQuestions(questionIds);
|
||||
}
|
||||
}
|
||||
}, [selectedIntegration, selectedSurvey]);
|
||||
}, [questions, selectedIntegration, selectedSurvey]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedIntegration) {
|
||||
@@ -269,7 +274,7 @@ export const AddChannelMappingModal = ({
|
||||
<Label htmlFor="Surveys">{t("common.questions")}</Label>
|
||||
<div className="mt-1 max-h-[15vh] overflow-y-auto rounded-lg border border-slate-200">
|
||||
<div className="grid content-center rounded-lg bg-slate-50 p-3 text-left text-sm text-slate-900">
|
||||
{replaceHeadlineRecall(selectedSurvey, "default")?.questions?.map((question) => (
|
||||
{questions.map((question) => (
|
||||
<div key={question.id} className="my-1 flex items-center space-x-2">
|
||||
<label htmlFor={question.id} className="flex cursor-pointer items-center">
|
||||
<Checkbox
|
||||
@@ -283,7 +288,11 @@ export const AddChannelMappingModal = ({
|
||||
}}
|
||||
/>
|
||||
<span className="ml-2">
|
||||
{getTextContent(getLocalizedValue(question.headline, "default"))}
|
||||
{getTextContent(
|
||||
recallToHeadline(question.headline, selectedSurvey, false, "default")[
|
||||
"default"
|
||||
]
|
||||
)}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,8 @@ import { TIntegrationGoogleSheets } from "@formbricks/types/integration/google-s
|
||||
import { TIntegrationNotion, TIntegrationNotionConfigData } from "@formbricks/types/integration/notion";
|
||||
import { TIntegrationSlack } from "@formbricks/types/integration/slack";
|
||||
import { TResponseMeta } from "@formbricks/types/responses";
|
||||
import { TSurvey, TSurveyQuestionTypeEnum } from "@formbricks/types/surveys/types";
|
||||
import { TSurveyElementTypeEnum } from "@formbricks/types/surveys/elements";
|
||||
import { TSurvey } from "@formbricks/types/surveys/types";
|
||||
import { getTextContent } from "@formbricks/types/surveys/validation";
|
||||
import { TPipelineInput } from "@/app/api/(internal)/pipeline/types/pipelines";
|
||||
import { writeData as airtableWriteData } from "@/lib/airtable/service";
|
||||
@@ -16,6 +17,7 @@ import { getLocalizedValue } from "@/lib/i18n/utils";
|
||||
import { writeData as writeNotionData } from "@/lib/notion/service";
|
||||
import { processResponseData } from "@/lib/responses";
|
||||
import { writeDataToSlack } from "@/lib/slack/service";
|
||||
import { getQuestionsFromBlocks } from "@/lib/survey/utils";
|
||||
import { getFormattedDateTimeString } from "@/lib/utils/datetime";
|
||||
import { parseRecallInfo } from "@/lib/utils/recall";
|
||||
import { truncateText } from "@/lib/utils/strings";
|
||||
@@ -236,6 +238,9 @@ const extractResponses = async (
|
||||
const responses: string[] = [];
|
||||
const questions: string[] = [];
|
||||
|
||||
// Derive questions from blocks
|
||||
const surveyQuestions = getQuestionsFromBlocks(survey.blocks);
|
||||
|
||||
for (const questionId of questionIds) {
|
||||
//check for hidden field Ids
|
||||
if (survey.hiddenFields.fieldIds?.includes(questionId)) {
|
||||
@@ -243,7 +248,7 @@ const extractResponses = async (
|
||||
questions.push(questionId);
|
||||
continue;
|
||||
}
|
||||
const question = survey?.questions.find((q) => q.id === questionId);
|
||||
const question = surveyQuestions.find((q) => q.id === questionId);
|
||||
if (!question) {
|
||||
continue;
|
||||
}
|
||||
@@ -252,7 +257,7 @@ const extractResponses = async (
|
||||
|
||||
if (responseValue !== undefined) {
|
||||
let answer: typeof responseValue;
|
||||
if (question.type === TSurveyQuestionTypeEnum.PictureSelection) {
|
||||
if (question.type === TSurveyElementTypeEnum.PictureSelection) {
|
||||
const selectedChoiceIds = responseValue as string[];
|
||||
answer = question?.choices
|
||||
.filter((choice) => selectedChoiceIds.includes(choice.id))
|
||||
@@ -321,14 +326,17 @@ const buildNotionPayloadProperties = (
|
||||
const properties: any = {};
|
||||
const responses = data.response.data;
|
||||
|
||||
// Derive questions from blocks
|
||||
const surveyQuestions = getQuestionsFromBlocks(surveyData.blocks);
|
||||
|
||||
const mappingQIds = mapping
|
||||
.filter((m) => m.question.type === TSurveyQuestionTypeEnum.PictureSelection)
|
||||
.filter((m) => m.question.type === TSurveyElementTypeEnum.PictureSelection)
|
||||
.map((m) => m.question.id);
|
||||
|
||||
Object.keys(responses).forEach((resp) => {
|
||||
if (mappingQIds.find((qId) => qId === resp)) {
|
||||
const selectedChoiceIds = responses[resp] as string[];
|
||||
const pictureQuestion = surveyData.questions.find((q) => q.id === resp);
|
||||
const pictureQuestion = surveyQuestions.find((q) => q.id === resp);
|
||||
|
||||
responses[resp] = (pictureQuestion as any)?.choices
|
||||
.filter((choice) => selectedChoiceIds.includes(choice.id))
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
"use client";
|
||||
|
||||
import { TSurveyBlock } from "@formbricks/types/surveys/blocks";
|
||||
import { TSurveyElement } from "@formbricks/types/surveys/elements";
|
||||
|
||||
export const copySurveyLink = (surveyUrl: string, singleUseId?: string): string => {
|
||||
return singleUseId ? `${surveyUrl}?suId=${singleUseId}` : surveyUrl;
|
||||
};
|
||||
|
||||
/**
|
||||
* Derives a flat array of elements from the survey's blocks structure.
|
||||
* This is the client-side equivalent of the server-side getQuestionsFromBlocks.
|
||||
* @param blocks - Array of survey blocks
|
||||
* @returns An array of TSurveyElement (pure elements without block-level properties)
|
||||
*/
|
||||
export const getQuestionsFromBlocks = (blocks: TSurveyBlock[]): TSurveyElement[] =>
|
||||
blocks.flatMap((block) => block.elements);
|
||||
|
||||
Reference in New Issue
Block a user