integrations code cleanup

This commit is contained in:
pandeymangg
2025-11-26 12:32:27 +05:30
parent 9f59d7a967
commit 007d996870
24 changed files with 288 additions and 269 deletions
@@ -69,10 +69,10 @@ const NoBaseFoundError = () => {
);
};
const renderQuestionSelection = ({
const renderElementSelection = ({
t,
selectedSurvey,
questions,
elements,
control,
includeVariables,
setIncludeVariables,
@@ -85,7 +85,7 @@ const renderQuestionSelection = ({
}: {
t: TFunction;
selectedSurvey: TSurvey;
questions: TSurveyElement[];
elements: TSurveyElement[];
control: Control<IntegrationModalInputs>;
includeVariables: boolean;
setIncludeVariables: (value: boolean) => void;
@@ -102,29 +102,29 @@ 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">
{questions.map((question) => (
{elements.map((element) => (
<Controller
key={question.id}
key={element.id}
control={control}
name={"questions"}
name={"elements"}
render={({ field }) => (
<div className="my-1 flex items-center space-x-2">
<label htmlFor={question.id} className="flex cursor-pointer items-center">
<label htmlFor={element.id} className="flex cursor-pointer items-center">
<Checkbox
type="button"
id={question.id}
value={question.id}
id={element.id}
value={element.id}
className="bg-white"
checked={field.value?.includes(question.id)}
checked={field.value?.includes(element.id)}
onCheckedChange={(checked) => {
return checked
? field.onChange([...field.value, question.id])
: field.onChange(field.value?.filter((value) => value !== question.id));
? field.onChange([...(field.value || []), element.id])
: field.onChange(field.value?.filter((value) => value !== element.id) || []);
}}
/>
<span className="ml-2">
{getTextContent(
recallToHeadline(question.headline, selectedSurvey, false, "default")["default"]
recallToHeadline(element.headline, selectedSurvey, false, "default")["default"]
)}
</span>
</label>
@@ -199,7 +199,7 @@ export const AddIntegrationModal = ({
};
const selectedSurvey = surveys.find((item) => item.id === survey);
const questions = useMemo(
const elements = useMemo(
() => (selectedSurvey ? getElementsFromBlocks(selectedSurvey.blocks) : []),
[selectedSurvey]
);
@@ -218,7 +218,7 @@ export const AddIntegrationModal = ({
throw new Error(t("environments.integrations.please_select_a_survey_error"));
}
if (data.questions.length === 0) {
if (data.elements.length === 0) {
throw new Error(t("environments.integrations.select_at_least_one_question_error"));
}
@@ -226,9 +226,9 @@ export const AddIntegrationModal = ({
const integrationData: TIntegrationAirtableConfigData = {
surveyId: selectedSurvey.id,
surveyName: selectedSurvey.name,
questionIds: data.questions,
questions:
data.questions.length === questions.length
elementIds: data.elements,
elements:
data.elements.length === elements.length
? t("common.all_questions")
: t("common.selected_questions"),
createdAt: new Date(),
@@ -376,7 +376,7 @@ export const AddIntegrationModal = ({
required
onValueChange={(val) => {
field.onChange(val);
setValue("questions", []);
setValue("elements", []);
}}
defaultValue={defaultData?.survey}>
<SelectTrigger>
@@ -402,10 +402,10 @@ export const AddIntegrationModal = ({
{survey &&
selectedSurvey &&
renderQuestionSelection({
renderElementSelection({
t,
selectedSurvey,
questions,
elements: elements,
control,
includeVariables,
setIncludeVariables,
@@ -110,7 +110,7 @@ export const ManageIntegration = (props: ManageIntegrationProps) => {
onClick={() => {
setDefaultValues({
base: data.baseId,
questions: data.questionIds,
elements: data.elementIds,
survey: data.surveyId,
table: data.tableId,
includeVariables: !!data.includeVariables,
@@ -123,7 +123,7 @@ export const ManageIntegration = (props: ManageIntegrationProps) => {
}}>
<div className="col-span-2 text-center">{data.surveyName}</div>
<div className="col-span-2 text-center">{data.tableName}</div>
<div className="col-span-2 text-center">{data.questions}</div>
<div className="col-span-2 text-center">{data.elements}</div>
<div className="col-span-2 text-center">
{timeSince(data.createdAt.toString(), props.locale)}
</div>
@@ -2,7 +2,7 @@ export type IntegrationModalInputs = {
base: string;
table: string;
survey: string;
questions: string[];
elements: string[];
includeVariables: boolean;
includeHiddenFields: boolean;
includeMetadata: boolean;
@@ -62,12 +62,12 @@ export const AddIntegrationModal = ({
spreadsheetName: "",
surveyId: "",
surveyName: "",
questionIds: [""],
questions: "",
elementIds: [""],
elements: "",
createdAt: new Date(),
};
const { handleSubmit } = useForm();
const [selectedQuestions, setSelectedQuestions] = useState<string[]>([]);
const [selectedElements, setSelectedElements] = useState<string[]>([]);
const [isLinkingSheet, setIsLinkingSheet] = useState(false);
const [selectedSurvey, setSelectedSurvey] = useState<TSurvey | null>(null);
const [spreadsheetUrl, setSpreadsheetUrl] = useState("");
@@ -86,17 +86,17 @@ export const AddIntegrationModal = ({
},
};
const questions = useMemo(
const surveyElements = useMemo(
() => (selectedSurvey ? getElementsFromBlocks(selectedSurvey.blocks) : []),
[selectedSurvey]
);
useEffect(() => {
if (selectedSurvey && !selectedIntegration) {
const questionIds = questions.map((question) => question.id);
setSelectedQuestions(questionIds);
const elementIds = surveyElements.map((element) => element.id);
setSelectedElements(elementIds);
}
}, [questions, selectedIntegration, selectedSurvey]);
}, [surveyElements, selectedIntegration, selectedSurvey]);
useEffect(() => {
if (selectedIntegration) {
@@ -106,7 +106,7 @@ export const AddIntegrationModal = ({
return survey.id === selectedIntegration.surveyId;
})!
);
setSelectedQuestions(selectedIntegration.questionIds);
setSelectedElements(selectedIntegration.elementIds);
setIncludeVariables(!!selectedIntegration.includeVariables);
setIncludeHiddenFields(!!selectedIntegration.includeHiddenFields);
setIncludeMetadata(!!selectedIntegration.includeMetadata);
@@ -126,7 +126,7 @@ export const AddIntegrationModal = ({
if (!selectedSurvey) {
throw new Error(t("environments.integrations.please_select_a_survey_error"));
}
if (selectedQuestions.length === 0) {
if (selectedElements.length === 0) {
throw new Error(t("environments.integrations.select_at_least_one_question_error"));
}
const spreadsheetId = extractSpreadsheetIdFromUrl(spreadsheetUrl);
@@ -148,9 +148,9 @@ export const AddIntegrationModal = ({
integrationData.spreadsheetName = spreadsheetName;
integrationData.surveyId = selectedSurvey.id;
integrationData.surveyName = selectedSurvey.name;
integrationData.questionIds = selectedQuestions;
integrationData.questions =
selectedQuestions.length === questions.length
integrationData.elementIds = selectedElements;
integrationData.elements =
selectedElements.length === surveyElements.length
? t("common.all_questions")
: t("common.selected_questions");
integrationData.createdAt = new Date();
@@ -181,7 +181,7 @@ export const AddIntegrationModal = ({
};
const handleCheckboxChange = (questionId: TSurveyQuestionId) => {
setSelectedQuestions((prevValues) =>
setSelectedElements((prevValues) =>
prevValues.includes(questionId)
? prevValues.filter((value) => value !== questionId)
: [...prevValues, questionId]
@@ -268,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">
{questions.map((question) => (
{surveyElements.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
@@ -276,7 +276,7 @@ export const AddIntegrationModal = ({
id={question.id}
value={question.id}
className="bg-white"
checked={selectedQuestions.includes(question.id)}
checked={selectedElements.includes(question.id)}
onCheckedChange={() => {
handleCheckboxChange(question.id);
}}
@@ -118,7 +118,7 @@ export const ManageIntegration = ({
}}>
<div className="col-span-2 text-center">{data.surveyName}</div>
<div className="col-span-2 text-center">{data.spreadsheetName}</div>
<div className="col-span-2 text-center">{data.questions}</div>
<div className="col-span-2 text-center">{data.elements}</div>
<div className="col-span-2 text-center">{timeSince(data.createdAt.toString(), locale)}</div>
</button>
);
@@ -12,7 +12,8 @@ import {
TIntegrationNotionConfigData,
TIntegrationNotionDatabase,
} from "@formbricks/types/integration/notion";
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 { createOrUpdateIntegrationAction } from "@/app/(app)/environments/[environmentId]/project/integrations/actions";
import {
@@ -64,7 +65,7 @@ export const AddIntegrationModal = ({
const [mapping, setMapping] = useState<
{
column: { id: string; name: string; type: string };
question: { id: string; name: string; type: string };
element: { id: string; name: string; type: string };
error?: {
type: string;
msg: React.ReactNode | string;
@@ -73,7 +74,7 @@ export const AddIntegrationModal = ({
>([
{
column: { id: "", name: "", type: "" },
question: { id: "", name: "", type: "" },
element: { id: "", name: "", type: "" },
},
]);
const [isDeleting, setIsDeleting] = useState<boolean>(false);
@@ -86,13 +87,13 @@ export const AddIntegrationModal = ({
mapping: [
{
column: { id: "", name: "", type: "" },
question: { id: "", name: "", type: "" },
element: { id: "", name: "", type: "" },
},
],
createdAt: new Date(),
};
const questions = useMemo(
const elements = useMemo(
() => (selectedSurvey ? getElementsFromBlocks(selectedSurvey.blocks) : []),
[selectedSurvey]
);
@@ -124,12 +125,12 @@ export const AddIntegrationModal = ({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedDatabase?.id]);
const questionItems = useMemo(() => {
const mappedQuestions = selectedSurvey
? questions.map((q) => ({
id: q.id,
name: getTextContent(recallToHeadline(q.headline, selectedSurvey, false, "default")["default"]),
type: q.type,
const elementItems = useMemo(() => {
const mappedElements = selectedSurvey
? elements.map((el) => ({
id: el.id,
name: getTextContent(recallToHeadline(el.headline, selectedSurvey, false, "default")["default"]),
type: el.type,
}))
: [];
@@ -137,31 +138,31 @@ export const AddIntegrationModal = ({
selectedSurvey?.variables.map((variable) => ({
id: variable.id,
name: variable.name,
type: TSurveyQuestionTypeEnum.OpenText,
type: TSurveyElementTypeEnum.OpenText,
})) || [];
const hiddenFields =
selectedSurvey?.hiddenFields.fieldIds?.map((fId) => ({
id: fId,
name: `${t("common.hidden_field")} : ${fId}`,
type: TSurveyQuestionTypeEnum.OpenText,
type: TSurveyElementTypeEnum.OpenText,
})) || [];
const Metadata = [
{
id: "metadata",
name: t("common.metadata"),
type: TSurveyQuestionTypeEnum.OpenText,
type: TSurveyElementTypeEnum.OpenText,
},
];
const createdAt = [
{
id: "createdAt",
name: t("common.created_at"),
type: TSurveyQuestionTypeEnum.Date,
type: TSurveyElementTypeEnum.Date,
},
];
return [...mappedQuestions, ...variables, ...hiddenFields, ...Metadata, ...createdAt];
return [...mappedElements, ...variables, ...hiddenFields, ...Metadata, ...createdAt];
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedSurvey?.id]);
@@ -195,7 +196,7 @@ export const AddIntegrationModal = ({
throw new Error(t("environments.integrations.please_select_a_survey_error"));
}
if (mapping.length === 1 && (!mapping[0].question.id || !mapping[0].column.id)) {
if (mapping.length === 1 && (!mapping[0].element.id || !mapping[0].column.id)) {
throw new Error(t("environments.integrations.notion.please_select_at_least_one_mapping"));
}
@@ -204,8 +205,8 @@ export const AddIntegrationModal = ({
}
if (
mapping.filter((m) => m.column.id && !m.question.id).length >= 1 ||
mapping.filter((m) => m.question.id && !m.column.id).length >= 1
mapping.filter((m) => m.column.id && !m.element.id).length >= 1 ||
mapping.filter((m) => m.element.id && !m.column.id).length >= 1
) {
throw new Error(
t("environments.integrations.notion.please_complete_mapping_fields_with_notion_property")
@@ -266,23 +267,23 @@ export const AddIntegrationModal = ({
setSelectedDatabase(null);
setSelectedSurvey(null);
};
const getFilteredQuestionItems = (selectedIdx) => {
const selectedQuestionIds = mapping.filter((_, idx) => idx !== selectedIdx).map((m) => m.question.id);
const getFilteredElementItems = (selectedIdx) => {
const selectedElementIds = mapping.filter((_, idx) => idx !== selectedIdx).map((m) => m.element.id);
return questionItems.filter((q) => !selectedQuestionIds.includes(q.id));
return elementItems.filter((el) => !selectedElementIds.includes(el.id));
};
const createCopy = (item) => structuredClone(item);
const MappingRow = ({ idx }: { idx: number }) => {
const filteredQuestionItems = getFilteredQuestionItems(idx);
const filteredElementItems = getFilteredElementItems(idx);
const addRow = () => {
setMapping((prev) => [
...prev,
{
column: { id: "", name: "", type: "" },
question: { id: "", name: "", type: "" },
element: { id: "", name: "", type: "" },
},
]);
};
@@ -293,7 +294,7 @@ export const AddIntegrationModal = ({
});
};
const ErrorMsg = ({ error, col, ques }) => {
const ErrorMsg = ({ error, col, elem }) => {
const showErrorMsg = useMemo(() => {
switch (error?.type) {
case ERRORS.UNSUPPORTED_TYPE:
@@ -307,16 +308,16 @@ export const AddIntegrationModal = ({
</>
);
case ERRORS.MAPPING:
const question = getElementTypes(t).find((qt) => qt.id === ques.type);
if (!question) return null;
const element = getElementTypes(t).find((et) => et.id === elem.type);
if (!element) return null;
return (
<>
{t("environments.integrations.notion.que_name_of_type_cant_be_mapped_to", {
que_name: ques.name,
question_label: question.label,
que_name: elem.name,
question_label: element.label,
col_name: col.name,
col_type: col.type,
mapped_type: TYPE_MAPPING[question.id].join(" ,"),
mapped_type: TYPE_MAPPING[element.id].join(" ,"),
})}
</>
);
@@ -347,15 +348,15 @@ export const AddIntegrationModal = ({
key={idx}
error={mapping[idx]?.error}
col={mapping[idx].column}
ques={mapping[idx].question}
elem={mapping[idx].element}
/>
<div className="flex w-full items-center space-x-2">
<div className="flex w-full items-center">
<div className="max-w-full flex-1">
<DropdownSelector
placeholder={t("environments.integrations.notion.select_a_survey_question")}
items={filteredQuestionItems}
selectedItem={mapping?.[idx]?.question}
items={filteredElementItems}
selectedItem={mapping?.[idx]?.element}
setSelectedItem={(item) => {
setMapping((prev) => {
const copy = createCopy(prev);
@@ -367,7 +368,7 @@ export const AddIntegrationModal = ({
error: {
type: ERRORS.UNSUPPORTED_TYPE,
},
question: item,
element: item,
};
return copy;
}
@@ -379,7 +380,7 @@ export const AddIntegrationModal = ({
error: {
type: ERRORS.MAPPING,
},
question: item,
element: item,
};
return copy;
}
@@ -387,13 +388,13 @@ export const AddIntegrationModal = ({
copy[idx] = {
...copy[idx],
question: item,
element: item,
error: null,
};
return copy;
});
}}
disabled={questionItems.length === 0}
disabled={elementItems.length === 0}
/>
</div>
<div className="h-px w-4 border-t border-t-slate-300" />
@@ -405,9 +406,9 @@ export const AddIntegrationModal = ({
setSelectedItem={(item) => {
setMapping((prev) => {
const copy = createCopy(prev);
const ques = copy[idx].question;
if (ques.id) {
const isValidQuesType = TYPE_MAPPING[ques.type].includes(item.type);
const elem = copy[idx].element;
if (elem.id) {
const isValidElemType = TYPE_MAPPING[elem.type].includes(item.type);
if (UNSUPPORTED_TYPES_BY_NOTION.includes(item.type)) {
copy[idx] = {
@@ -420,7 +421,7 @@ export const AddIntegrationModal = ({
return copy;
}
if (!isValidQuesType) {
if (!isValidElemType) {
copy[idx] = {
...copy[idx],
error: {
@@ -13,7 +13,7 @@ import {
TIntegrationSlackConfigData,
TIntegrationSlackInput,
} from "@formbricks/types/integration/slack";
import { TSurvey, TSurveyQuestionId } from "@formbricks/types/surveys/types";
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 SlackLogo from "@/images/slacklogo.png";
@@ -55,7 +55,7 @@ export const AddChannelMappingModal = ({
}: AddChannelMappingModalProps) => {
const { handleSubmit } = useForm();
const { t } = useTranslation();
const [selectedQuestions, setSelectedQuestions] = useState<string[]>([]);
const [selectedElements, setSelectedElements] = useState<string[]>([]);
const [isLinkingChannel, setIsLinkingChannel] = useState(false);
const [selectedSurvey, setSelectedSurvey] = useState<TSurvey | null>(null);
const [selectedChannel, setSelectedChannel] = useState<TIntegrationItem | null>(null);
@@ -73,19 +73,19 @@ export const AddChannelMappingModal = ({
},
};
const questions = useMemo(
const surveyElements = useMemo(
() => (selectedSurvey ? getElementsFromBlocks(selectedSurvey.blocks) : []),
[selectedSurvey]
);
useEffect(() => {
if (selectedSurvey) {
const questionIds = questions.map((question) => question.id);
const elementIds = surveyElements.map((element) => element.id);
if (!selectedIntegration) {
setSelectedQuestions(questionIds);
setSelectedElements(elementIds);
}
}
}, [questions, selectedIntegration, selectedSurvey]);
}, [surveyElements, selectedIntegration, selectedSurvey]);
useEffect(() => {
if (selectedIntegration) {
@@ -98,7 +98,7 @@ export const AddChannelMappingModal = ({
return survey.id === selectedIntegration.surveyId;
})!
);
setSelectedQuestions(selectedIntegration.questionIds);
setSelectedElements(selectedIntegration.elementIds);
setIncludeVariables(!!selectedIntegration.includeVariables);
setIncludeHiddenFields(!!selectedIntegration.includeHiddenFields);
setIncludeMetadata(!!selectedIntegration.includeMetadata);
@@ -117,7 +117,7 @@ export const AddChannelMappingModal = ({
throw new Error(t("environments.integrations.please_select_a_survey_error"));
}
if (selectedQuestions.length === 0) {
if (selectedElements.length === 0) {
throw new Error(t("environments.integrations.select_at_least_one_question_error"));
}
setIsLinkingChannel(true);
@@ -126,9 +126,9 @@ export const AddChannelMappingModal = ({
channelName: selectedChannel.name,
surveyId: selectedSurvey.id,
surveyName: selectedSurvey.name,
questionIds: selectedQuestions,
questions:
selectedQuestions.length === selectedSurvey?.questions.length
elementIds: selectedElements,
elements:
selectedElements.length === surveyElements.length
? t("common.all_questions")
: t("common.selected_questions"),
createdAt: new Date(),
@@ -159,11 +159,11 @@ export const AddChannelMappingModal = ({
}
};
const handleCheckboxChange = (questionId: TSurveyQuestionId) => {
setSelectedQuestions((prevValues) =>
prevValues.includes(questionId)
? prevValues.filter((value) => value !== questionId)
: [...prevValues, questionId]
const handleCheckboxChange = (elementId: string) => {
setSelectedElements((prevValues) =>
prevValues.includes(elementId)
? prevValues.filter((value) => value !== elementId)
: [...prevValues, elementId]
);
};
@@ -274,22 +274,22 @@ 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">
{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">
{surveyElements.map((element) => (
<div key={element.id} className="my-1 flex items-center space-x-2">
<label htmlFor={element.id} className="flex cursor-pointer items-center">
<Checkbox
type="button"
id={question.id}
value={question.id}
id={element.id}
value={element.id}
className="bg-white"
checked={selectedQuestions.includes(question.id)}
checked={selectedElements.includes(element.id)}
onCheckedChange={() => {
handleCheckboxChange(question.id);
handleCheckboxChange(element.id);
}}
/>
<span className="ml-2">
{getTextContent(
recallToHeadline(question.headline, selectedSurvey, false, "default")[
recallToHeadline(element.headline, selectedSurvey, false, "default")[
"default"
]
)}
@@ -134,7 +134,7 @@ export const ManageIntegration = ({
}}>
<div className="col-span-2 text-center">{data.surveyName}</div>
<div className="col-span-2 text-center">{data.channelName}</div>
<div className="col-span-2 text-center">{data.questions}</div>
<div className="col-span-2 text-center">{data.elements}</div>
<div className="col-span-2 text-center">{timeSince(data.createdAt.toString(), locale)}</div>
</button>
);
@@ -477,7 +477,7 @@ describe("getQuestionSummary", () => {
responses,
mockDropOff
);
const openTextSummary = summary.find((s: any) => s.question?.id === "q_open");
const openTextSummary = summary.find((s: any) => s.element?.id === "q_open");
expect(openTextSummary?.type).toBe(TSurveyElementTypeEnum.OpenText);
expect(openTextSummary?.responseCount).toBe(1);
// @ts-expect-error
@@ -491,7 +491,7 @@ describe("getQuestionSummary", () => {
responses,
mockDropOff
);
const multiSingleSummary = summary.find((s: any) => s.question?.id === "q_multi_single");
const multiSingleSummary = summary.find((s: any) => s.element?.id === "q_multi_single");
expect(multiSingleSummary?.type).toBe(TSurveyElementTypeEnum.MultipleChoiceSingle);
expect(multiSingleSummary?.responseCount).toBe(1);
// @ts-expect-error
@@ -164,12 +164,12 @@ export const CustomFilter = ({ survey }: CustomFilterProps) => {
const datePickerRef = useRef<HTMLDivElement>(null);
const extracMetadataKeys = useCallback((obj, parentKey = "") => {
const extractMetadataKeys = useCallback((obj, parentKey = "") => {
let keys: string[] = [];
for (let key in obj) {
if (typeof obj[key] === "object" && obj[key] !== null) {
keys = keys.concat(extracMetadataKeys(obj[key], parentKey + key + " - "));
keys = keys.concat(extractMetadataKeys(obj[key], parentKey + key + " - "));
} else {
keys.push(parentKey + key);
}
@@ -172,7 +172,7 @@ const mockAirtableIntegration: TIntegrationAirtable = {
data: [
{
surveyId: surveyId,
questionIds: [questionId1, questionId2],
elementIds: [questionId1, questionId2],
baseId: "base1",
tableId: "table1",
createdAt: new Date(),
@@ -196,8 +196,8 @@ const mockGoogleSheetsIntegration: TIntegrationGoogleSheets = {
surveyId: surveyId,
spreadsheetId: "sheet1",
spreadsheetName: "Sheet Name",
questionIds: [questionId1],
questions: "What is Q1?",
elementIds: [questionId1],
elements: "What is Q1?",
createdAt: new Date("2024-01-01T00:00:00.000Z"),
includeHiddenFields: false,
includeMetadata: false,
@@ -219,8 +219,8 @@ const mockSlackIntegration: TIntegrationSlack = {
surveyId: surveyId,
channelId: "channel1",
channelName: "Channel 1",
questionIds: [questionId1, questionId2, questionId3],
questions: "Q1, Q2, Q3",
elementIds: [questionId1, questionId2, questionId3],
elements: "Q1, Q2, Q3",
createdAt: new Date(),
includeHiddenFields: true,
includeMetadata: true,
@@ -249,19 +249,19 @@ const mockNotionIntegration: TIntegrationNotion = {
databaseName: "DB 1",
mapping: [
{
question: { id: questionId1, name: "Question 1", type: TSurveyQuestionTypeEnum.OpenText },
element: { id: questionId1, name: "Question 1", type: TSurveyQuestionTypeEnum.OpenText },
column: { id: "col1", name: "Column 1", type: "rich_text" },
},
{
question: { id: questionId3, name: "Question 3", type: TSurveyQuestionTypeEnum.PictureSelection },
element: { id: questionId3, name: "Question 3", type: TSurveyQuestionTypeEnum.PictureSelection },
column: { id: "col3", name: "Column 3", type: "url" },
},
{
question: { id: "metadata", name: "Metadata", type: "metadata" },
element: { id: "metadata", name: "Metadata", type: "metadata" },
column: { id: "col_meta", name: "Metadata Col", type: "rich_text" },
},
{
question: { id: "createdAt", name: "Created At", type: "createdAt" },
element: { id: "createdAt", name: "Created At", type: "createdAt" },
column: { id: "col_created", name: "Created Col", type: "date" },
},
],
@@ -351,16 +351,14 @@ describe("handleIntegrations", () => {
mockAirtableIntegration.config.key,
mockAirtableIntegration.config.data[0],
[
[
"Answer 1",
"Choice 1, Choice 2",
"Hidden Value",
expectedMetadataString,
"Variable Value",
"2024-01-01 12:00",
], // responses + hidden + meta + var + created
["Question 1 {{recall:q2}}", "Question 2", hiddenFieldId, "Metadata", "Variable 1", "Created At"], // questions (raw headline for Airtable) + hidden + meta + var + created
]
"Answer 1",
"Choice 1, Choice 2",
"Hidden Value",
expectedMetadataString,
"Variable Value",
"2024-01-01 12:00",
], // responses + hidden + meta + var + created
["Question 1 {{recall:q2}}", "Question 2", hiddenFieldId, "Metadata", "Variable 1", "Created At"] // elements (raw headline for Airtable) + hidden + meta + var + created
);
});
@@ -395,10 +393,8 @@ describe("handleIntegrations", () => {
expect(googleSheetWriteData).toHaveBeenCalledWith(
expectedIntegrationData,
mockGoogleSheetsIntegration.config.data[0].spreadsheetId,
[
["Answer 1"], // responses
["Question 1 {{recall:q2}}"], // questions (raw headline for Google Sheets)
]
["Answer 1"], // responses
["Question 1 {{recall:q2}}"] // elements (raw headline for Google Sheets)
);
});
@@ -44,33 +44,40 @@ const processDataForIntegration = async (
includeMetadata: boolean,
includeHiddenFields: boolean,
includeCreatedAt: boolean,
questionIds: string[]
): Promise<string[][]> => {
elementIds: string[]
): Promise<{
responses: string[];
elements: string[];
}> => {
const ids =
includeHiddenFields && survey.hiddenFields.fieldIds
? [...questionIds, ...survey.hiddenFields.fieldIds]
: questionIds;
const values = await extractResponses(integrationType, data, ids, survey);
? [...elementIds, ...survey.hiddenFields.fieldIds]
: elementIds;
const { responses, elements } = await extractResponses(integrationType, data, ids, survey);
if (includeMetadata) {
values[0].push(convertMetaObjectToString(data.response.meta));
values[1].push("Metadata");
responses.push(convertMetaObjectToString(data.response.meta));
elements.push("Metadata");
}
if (includeVariables) {
survey.variables.forEach((variable) => {
survey.variables?.forEach((variable) => {
const value = data.response.variables[variable.id];
if (value !== undefined) {
values[0].push(String(data.response.variables[variable.id]));
values[1].push(variable.name);
responses.push(String(data.response.variables[variable.id]));
elements.push(variable.name);
}
});
}
if (includeCreatedAt) {
const date = new Date(data.response.createdAt);
values[0].push(`${getFormattedDateTimeString(date)}`);
values[1].push("Created At");
responses.push(`${getFormattedDateTimeString(date)}`);
elements.push("Created At");
}
return values;
return {
responses,
elements,
};
};
export const handleIntegrations = async (
@@ -133,9 +140,9 @@ const handleAirtableIntegration = async (
!!element.includeMetadata,
!!element.includeHiddenFields,
!!element.includeCreatedAt,
element.questionIds
element.elementIds
);
await airtableWriteData(integration.config.key, element, values);
await airtableWriteData(integration.config.key, element, values.responses, values.elements);
}
}
}
@@ -169,14 +176,14 @@ const handleGoogleSheetsIntegration = async (
!!element.includeMetadata,
!!element.includeHiddenFields,
!!element.includeCreatedAt,
element.questionIds
element.elementIds
);
const integrationData = structuredClone(integration);
integrationData.config.data.forEach((data) => {
data.createdAt = new Date(data.createdAt);
});
await writeData(integrationData, element.spreadsheetId, values);
await writeData(integrationData, element.spreadsheetId, values.responses, values.elements);
}
}
}
@@ -210,9 +217,15 @@ const handleSlackIntegration = async (
!!element.includeMetadata,
!!element.includeHiddenFields,
!!element.includeCreatedAt,
element.questionIds
element.elementIds
);
await writeDataToSlack(
integration.config.key,
element.channelId,
values.responses,
values.elements,
survey?.name
);
await writeDataToSlack(integration.config.key, element.channelId, values, survey?.name);
}
}
}
@@ -232,34 +245,36 @@ const handleSlackIntegration = async (
const extractResponses = async (
integrationType: TIntegrationType,
pipelineData: TPipelineInput,
questionIds: string[],
elementIds: string[],
survey: TSurvey
): Promise<string[][]> => {
): Promise<{
responses: string[];
elements: string[];
}> => {
const responses: string[] = [];
const questions: string[] = [];
const elements: string[] = [];
// Derive questions from blocks
const surveyQuestions = getElementsFromBlocks(survey.blocks);
const surveyElements = getElementsFromBlocks(survey.blocks);
for (const questionId of questionIds) {
for (const elementId of elementIds) {
//check for hidden field Ids
if (survey.hiddenFields.fieldIds?.includes(questionId)) {
responses.push(processResponseData(pipelineData.response.data[questionId]));
questions.push(questionId);
if (survey.hiddenFields.fieldIds?.includes(elementId)) {
responses.push(processResponseData(pipelineData.response.data[elementId]));
elements.push(elementId);
continue;
}
const question = surveyQuestions.find((q) => q.id === questionId);
if (!question) {
const element = surveyElements.find((q) => q.id === elementId);
if (!element) {
continue;
}
const responseValue = pipelineData.response.data[questionId];
const responseValue = pipelineData.response.data[elementId];
if (responseValue !== undefined) {
let answer: typeof responseValue;
if (question.type === TSurveyElementTypeEnum.PictureSelection) {
if (element.type === TSurveyElementTypeEnum.PictureSelection) {
const selectedChoiceIds = responseValue as string[];
answer = question?.choices
answer = element?.choices
.filter((choice) => selectedChoiceIds.includes(choice.id))
.map((choice) => choice.imageUrl)
.join("\n");
@@ -279,16 +294,19 @@ const extractResponses = async (
},
{} as Record<string, string>
);
questions.push(
elements.push(
parseRecallInfo(
getTextContent(getLocalizedValue(question?.headline, "default")),
getTextContent(getLocalizedValue(element?.headline, "default")),
integrationType === "slack" ? pipelineData.response.data : emptyResponseObject,
integrationType === "slack" ? pipelineData.response.variables : {}
) || ""
);
}
return [responses, questions];
return {
responses,
elements,
};
};
const handleNotionIntegration = async (
@@ -326,35 +344,34 @@ const buildNotionPayloadProperties = (
const properties: any = {};
const responses = data.response.data;
// Derive questions from blocks
const surveyQuestions = getElementsFromBlocks(surveyData.blocks);
const surveyElements = getElementsFromBlocks(surveyData.blocks);
const mappingQIds = mapping
.filter((m) => m.question.type === TSurveyElementTypeEnum.PictureSelection)
.map((m) => m.question.id);
const mappingElementIds = mapping
.filter((m) => m.element.type === TSurveyElementTypeEnum.PictureSelection)
.map((m) => m.element.id);
Object.keys(responses).forEach((resp) => {
if (mappingQIds.find((qId) => qId === resp)) {
if (mappingElementIds.find((elementId) => elementId === resp)) {
const selectedChoiceIds = responses[resp] as string[];
const pictureQuestion = surveyQuestions.find((q) => q.id === resp);
const pictureElement = surveyElements.find((el) => el.id === resp);
responses[resp] = (pictureQuestion as any)?.choices
responses[resp] = (pictureElement as any)?.choices
.filter((choice) => selectedChoiceIds.includes(choice.id))
.map((choice) => choice.imageUrl);
}
});
mapping.forEach((map) => {
if (map.question.id === "metadata") {
if (map.element.id === "metadata") {
properties[map.column.name] = {
[map.column.type]: getValue(map.column.type, convertMetaObjectToString(data.response.meta)) || null,
};
} else if (map.question.id === "createdAt") {
} else if (map.element.id === "createdAt") {
properties[map.column.name] = {
[map.column.type]: getValue(map.column.type, data.response.createdAt) || null,
};
} else {
const value = responses[map.question.id];
const value = responses[map.element.id];
properties[map.column.name] = {
[map.column.type]: getValue(map.column.type, value) || null,
};
+53 -53
View File
@@ -480,11 +480,11 @@ describe("surveys", () => {
responseStatus: "all",
filter: [
{
questionType: { type: "Tags", label: "Tag 1", id: "tag1" },
elementType: { type: "Tags", label: "Tag 1", id: "tag1" },
filterType: { filterComboBoxValue: "Applied" },
},
{
questionType: { type: "Tags", label: "Tag 2", id: "tag2" },
elementType: { type: "Tags", label: "Tag 2", id: "tag2" },
filterType: { filterComboBoxValue: "Not applied" },
},
] as any,
@@ -501,11 +501,11 @@ describe("surveys", () => {
responseStatus: "all",
filter: [
{
questionType: {
type: "Questions",
elementType: {
type: "Elements",
label: "Open Text",
id: "openTextQ",
questionType: TSurveyElementTypeEnum.OpenText,
elementType: TSurveyElementTypeEnum.OpenText,
},
filterType: { filterComboBoxValue: "Filled out" },
},
@@ -522,11 +522,11 @@ describe("surveys", () => {
responseStatus: "all",
filter: [
{
questionType: {
type: "Questions",
elementType: {
type: "Elements",
label: "Address",
id: "addressQ",
questionType: TSurveyElementTypeEnum.Address,
elementType: TSurveyElementTypeEnum.Address,
},
filterType: { filterComboBoxValue: "Skipped" },
},
@@ -543,11 +543,11 @@ describe("surveys", () => {
responseStatus: "all",
filter: [
{
questionType: {
type: "Questions",
elementType: {
type: "Elements",
label: "Contact Info",
id: "contactQ",
questionType: TSurveyElementTypeEnum.ContactInfo,
elementType: TSurveyElementTypeEnum.ContactInfo,
},
filterType: { filterComboBoxValue: "Filled out" },
},
@@ -564,11 +564,11 @@ describe("surveys", () => {
responseStatus: "all",
filter: [
{
questionType: {
type: "Questions",
elementType: {
type: "Elements",
label: "Ranking",
id: "rankingQ",
questionType: TSurveyElementTypeEnum.Ranking,
elementType: TSurveyElementTypeEnum.Ranking,
},
filterType: { filterComboBoxValue: "Filled out" },
},
@@ -585,11 +585,11 @@ describe("surveys", () => {
responseStatus: "all",
filter: [
{
questionType: {
type: "Questions",
elementType: {
type: "Elements",
label: "MC Single",
id: "mcSingleQ",
questionType: TSurveyElementTypeEnum.MultipleChoiceSingle,
elementType: TSurveyElementTypeEnum.MultipleChoiceSingle,
},
filterType: { filterValue: "Includes either", filterComboBoxValue: ["Choice 1"] },
},
@@ -606,11 +606,11 @@ describe("surveys", () => {
responseStatus: "all",
filter: [
{
questionType: {
type: "Questions",
elementType: {
type: "Elements",
label: "MC Multi",
id: "mcMultiQ",
questionType: TSurveyElementTypeEnum.MultipleChoiceMulti,
elementType: TSurveyElementTypeEnum.MultipleChoiceMulti,
},
filterType: { filterValue: "Includes all", filterComboBoxValue: ["Choice 1", "Choice 2"] },
},
@@ -627,11 +627,11 @@ describe("surveys", () => {
responseStatus: "all",
filter: [
{
questionType: {
type: "Questions",
elementType: {
type: "Elements",
label: "NPS",
id: "npsQ",
questionType: TSurveyElementTypeEnum.NPS,
elementType: TSurveyElementTypeEnum.NPS,
},
filterType: { filterValue: "Is equal to", filterComboBoxValue: "7" },
},
@@ -648,11 +648,11 @@ describe("surveys", () => {
responseStatus: "all",
filter: [
{
questionType: {
type: "Questions",
elementType: {
type: "Elements",
label: "Rating",
id: "ratingQ",
questionType: TSurveyElementTypeEnum.Rating,
elementType: TSurveyElementTypeEnum.Rating,
},
filterType: { filterValue: "Is less than", filterComboBoxValue: "4" },
},
@@ -669,11 +669,11 @@ describe("surveys", () => {
responseStatus: "all",
filter: [
{
questionType: {
type: "Questions",
elementType: {
type: "Elements",
label: "CTA",
id: "ctaQ",
questionType: TSurveyElementTypeEnum.CTA,
elementType: TSurveyElementTypeEnum.CTA,
},
filterType: { filterComboBoxValue: "Clicked" },
},
@@ -690,11 +690,11 @@ describe("surveys", () => {
responseStatus: "all",
filter: [
{
questionType: {
type: "Questions",
elementType: {
type: "Elements",
label: "Consent",
id: "consentQ",
questionType: TSurveyElementTypeEnum.Consent,
elementType: TSurveyElementTypeEnum.Consent,
},
filterType: { filterComboBoxValue: "Accepted" },
},
@@ -711,11 +711,11 @@ describe("surveys", () => {
responseStatus: "all",
filter: [
{
questionType: {
type: "Questions",
elementType: {
type: "Elements",
label: "Picture",
id: "pictureQ",
questionType: TSurveyElementTypeEnum.PictureSelection,
elementType: TSurveyElementTypeEnum.PictureSelection,
},
filterType: { filterValue: "Includes either", filterComboBoxValue: ["Picture 1"] },
},
@@ -732,11 +732,11 @@ describe("surveys", () => {
responseStatus: "all",
filter: [
{
questionType: {
type: "Questions",
elementType: {
type: "Elements",
label: "Matrix",
id: "matrixQ",
questionType: TSurveyElementTypeEnum.Matrix,
elementType: TSurveyElementTypeEnum.Matrix,
},
filterType: { filterValue: "Row 1", filterComboBoxValue: "Column 1" },
},
@@ -753,7 +753,7 @@ describe("surveys", () => {
responseStatus: "all",
filter: [
{
questionType: { type: "Hidden Fields", label: "plan", id: "plan" },
elementType: { type: "Hidden Fields", label: "plan", id: "plan" },
filterType: { filterValue: "Equals", filterComboBoxValue: "pro" },
},
],
@@ -769,7 +769,7 @@ describe("surveys", () => {
responseStatus: "all",
filter: [
{
questionType: { type: "Attributes", label: "role", id: "role" },
elementType: { type: "Attributes", label: "role", id: "role" },
filterType: { filterValue: "Not equals", filterComboBoxValue: "admin" },
},
],
@@ -785,7 +785,7 @@ describe("surveys", () => {
responseStatus: "all",
filter: [
{
questionType: { type: "Other Filters", label: "Language", id: "language" },
elementType: { type: "Other Filters", label: "Language", id: "language" },
filterType: { filterValue: "Equals", filterComboBoxValue: "en" },
},
],
@@ -801,7 +801,7 @@ describe("surveys", () => {
responseStatus: "all",
filter: [
{
questionType: { type: "Meta", label: "source", id: "source" },
elementType: { type: "Meta", label: "source", id: "source" },
filterType: { filterValue: "Not equals", filterComboBoxValue: "web" },
},
],
@@ -817,16 +817,16 @@ describe("surveys", () => {
responseStatus: "complete",
filter: [
{
questionType: {
type: "Questions",
elementType: {
type: "Elements",
label: "NPS",
id: "npsQ",
questionType: TSurveyElementTypeEnum.NPS,
elementType: TSurveyElementTypeEnum.NPS,
},
filterType: { filterValue: "Is more than", filterComboBoxValue: "7" },
},
{
questionType: { type: "Tags", label: "Tag 1", id: "tag1" },
elementType: { type: "Tags", label: "Tag 1", id: "tag1" },
filterType: { filterComboBoxValue: "Applied" },
},
],
@@ -845,7 +845,7 @@ describe("surveys", () => {
responseStatus: "all",
filter: [
{
questionType: { type: "Meta", label: "url", id: "url" },
elementType: { type: "Meta", label: "url", id: "url" },
filterType: { filterValue: "Contains", filterComboBoxValue: "example.com" },
},
],
@@ -873,7 +873,7 @@ describe("surveys", () => {
responseStatus: "all",
filter: [
{
questionType: { type: "Meta", label: "url", id: "url" },
elementType: { type: "Meta", label: "url", id: "url" },
filterType: { filterValue, filterComboBoxValue: expected.value },
},
],
@@ -889,7 +889,7 @@ describe("surveys", () => {
responseStatus: "all",
filter: [
{
questionType: { type: "Meta", label: "url", id: "url" },
elementType: { type: "Meta", label: "url", id: "url" },
filterType: { filterValue: "Contains", filterComboBoxValue: "" },
},
],
@@ -905,7 +905,7 @@ describe("surveys", () => {
responseStatus: "all",
filter: [
{
questionType: { type: "Meta", label: "url", id: "url" },
elementType: { type: "Meta", label: "url", id: "url" },
filterType: { filterValue: "Contains", filterComboBoxValue: " " },
},
],
@@ -921,7 +921,7 @@ describe("surveys", () => {
responseStatus: "all",
filter: [
{
questionType: { type: "Meta", label: "source", id: "source" },
elementType: { type: "Meta", label: "source", id: "source" },
filterType: { filterValue: "Equals", filterComboBoxValue: ["google"] },
},
],
@@ -937,11 +937,11 @@ describe("surveys", () => {
responseStatus: "all",
filter: [
{
questionType: { type: "Meta", label: "url", id: "url" },
elementType: { type: "Meta", label: "url", id: "url" },
filterType: { filterValue: "Contains", filterComboBoxValue: "formbricks.com" },
},
{
questionType: { type: "Meta", label: "source", id: "source" },
elementType: { type: "Meta", label: "source", id: "source" },
filterType: { filterValue: "Equals", filterComboBoxValue: ["newsletter"] },
},
],
+5 -7
View File
@@ -206,15 +206,13 @@ const getExistingFields = async (key: TIntegrationAirtableCredential, baseId: st
export const writeData = async (
key: TIntegrationAirtableCredential,
configData: TIntegrationAirtableConfigData,
values: string[][]
responses: string[],
elements: string[]
) => {
const responses = values[0];
const questions = values[1];
// 1) Build the record payload
const data: Record<string, string> = {};
for (let i = 0; i < questions.length; i++) {
data[questions[i]] =
for (let i = 0; i < elements.length; i++) {
data[elements[i]] =
responses[i].length > AIRTABLE_MESSAGE_LIMIT
? truncateText(responses[i], AIRTABLE_MESSAGE_LIMIT)
: responses[i];
@@ -222,7 +220,7 @@ export const writeData = async (
// 2) Figure out which fields need creating
const existingFields = await getExistingFields(key, configData.baseId, configData.tableId);
const fieldsToCreate = questions.filter((q) => !existingFields.has(q));
const fieldsToCreate = elements.filter((q) => !existingFields.has(q));
// 3) Create any missing fields with throttling to respect Airtable's 5 req/sec per base limit
if (fieldsToCreate.length > 0) {
+13 -8
View File
@@ -22,31 +22,36 @@ const { google } = require("googleapis");
export const writeData = async (
integrationData: TIntegrationGoogleSheets,
spreadsheetId: string,
values: string[][]
responses: string[],
elements: string[]
) => {
validateInputs(
[integrationData, ZIntegrationGoogleSheets],
[spreadsheetId, ZString],
[values, z.array(z.array(ZString))]
[responses, z.array(ZString)],
[elements, z.array(ZString)]
);
try {
const authClient = await authorize(integrationData);
const sheets = google.sheets({ version: "v4", auth: authClient });
const responses = {
const responsesMapped = {
values: [
values[0].map((value) =>
value.length > GOOGLE_SHEET_MESSAGE_LIMIT ? truncateText(value, GOOGLE_SHEET_MESSAGE_LIMIT) : value
responses.map((response) =>
response.length > GOOGLE_SHEET_MESSAGE_LIMIT
? truncateText(response, GOOGLE_SHEET_MESSAGE_LIMIT)
: response
),
],
};
const question = { values: [values[1]] };
const element = { values: [elements] };
sheets.spreadsheets.values.update(
{
spreadsheetId: spreadsheetId,
range: "A1",
valueInputOption: "RAW",
resource: question,
resource: element,
},
(err: Error) => {
if (err) {
@@ -60,7 +65,7 @@ export const writeData = async (
spreadsheetId: spreadsheetId,
range: "A2",
valueInputOption: "RAW",
resource: responses,
resource: responsesMapped,
},
(err: Error) => {
if (err) {
+2 -2
View File
@@ -47,8 +47,8 @@ describe("Integration Service", () => {
spreadsheetName: "Test Spreadsheet",
surveyId: "survey123",
surveyName: "Test Survey",
questionIds: ["q1", "q2"],
questions: "Question 1, Question 2",
elementIds: ["q1", "q2"],
elements: "Question 1, Question 2",
createdAt: new Date(),
includeHiddenFields: false,
includeMetadata: true,
@@ -375,8 +375,8 @@ export const mockSurveySummaryOutput = {
dropOffCount: 0,
dropOffPercentage: 0,
headline: "Question Text",
questionType: "openText",
questionId: "ars2tjk8hsi8oqk1uac00mo8",
elementType: "openText",
elementId: "ars2tjk8hsi8oqk1uac00mo8",
ttc: 0,
impressions: 0,
},
@@ -396,7 +396,7 @@ export const mockSurveySummaryOutput = {
quotas: [],
summary: [
{
question: {
element: {
headline: { default: "Question Text", de: "Fragetext" },
id: "ars2tjk8hsi8oqk1uac00mo8",
inputType: "text",
+1 -1
View File
@@ -427,7 +427,7 @@ describe("Response Utils", () => {
test("should extract survey details correctly", () => {
const result = extractSurveyDetails(mockSurvey as TSurvey, mockResponses as TResponse[]);
expect(result.metaDataFields).toContain("userAgent - browser");
expect(result.questions).toHaveLength(2); // 1 regular question + 2 matrix rows
expect(result.elements).toHaveLength(2); // 1 regular question + 2 matrix rows
expect(result.hiddenFields).toContain("hidden1");
expect(result.userAttributes).toContain("email");
});
+2 -2
View File
@@ -298,12 +298,12 @@ describe("Response Processing", () => {
const mapping = getElementResponseMapping(mockSurvey, mockResponse);
expect(mapping).toHaveLength(2);
expect(mapping[0]).toEqual({
question: "Question 1",
element: "Question 1",
response: "Answer 1",
type: TSurveyElementTypeEnum.OpenText,
});
expect(mapping[1]).toEqual({
question: "Question 2",
element: "Question 2",
response: "Option 1; Option 2",
type: TSurveyElementTypeEnum.MultipleChoiceMulti,
});
+4 -4
View File
@@ -75,11 +75,11 @@ export const getSlackChannels = async (environmentId: string): Promise<TIntegrat
export const writeDataToSlack = async (
credentials: TIntegrationSlackCredential,
channelId: string,
values: string[][],
responses: string[],
elements: string[],
surveyName: string | undefined
) => {
try {
const [responses, questions] = values;
let blockResponse = [
{
type: "section",
@@ -92,12 +92,12 @@ export const writeDataToSlack = async (
type: "divider",
},
];
for (let i = 0; i < values[0].length; i++) {
for (let i = 0; i < responses.length; i++) {
let questionSection = {
type: "section",
text: {
type: "mrkdwn",
text: `*${questions[i]}*`,
text: `*${elements[i]}*`,
},
};
const responseText = responses[i];
@@ -10,7 +10,7 @@ const logicRules = getLogicRules(mockT as unknown as TFunction);
describe("getLogicRules", () => {
test("should return correct structure for question rules", () => {
expect(logicRules).toHaveProperty("question");
expect(logicRules).toHaveProperty("element");
expect(logicRules.element).toBeInstanceOf(Object);
});
+2 -2
View File
@@ -30,8 +30,8 @@ export type TIntegration = z.infer<typeof ZIntegration>;
export const ZIntegrationBaseSurveyData = z.object({
createdAt: z.date(),
questionIds: z.array(z.string()),
questions: z.string(),
elementIds: z.array(z.string()),
elements: z.string(),
surveyId: z.string(),
surveyName: z.string(),
});
+4 -4
View File
@@ -33,10 +33,10 @@ export type TIntegrationNotionCredential = z.infer<typeof ZIntegrationNotionCred
export const ZIntegrationNotionConfigData = z
.object({
// question -> notion database column mapping
// element -> notion database column mapping
mapping: z.array(
z.object({
question: z.object({
element: z.object({
id: z.string(),
name: z.string(),
type: z.string(),
@@ -53,8 +53,8 @@ export const ZIntegrationNotionConfigData = z
})
.merge(
ZIntegrationBaseSurveyData.omit({
questionIds: true,
questions: true,
elementIds: true,
elements: true,
})
);
+4 -2
View File
@@ -7,12 +7,14 @@ export const ZIntegrationBase = z.object({
export const ZIntegrationBaseSurveyData = z.object({
createdAt: z.date(),
questionIds: z.array(z.string()),
// questionIds: z.array(z.string()),
elementIds: z.array(z.string()),
includeVariables: z.boolean().optional(),
includeHiddenFields: z.boolean().optional(),
includeMetadata: z.boolean().optional(),
includeCreatedAt: z.boolean().optional(),
questions: z.string(),
// questions: z.string(),
elements: z.string(),
surveyId: z.string(),
surveyName: z.string(),
});