mirror of
https://github.com/formbricks/formbricks.git
synced 2025-12-30 10:19:51 -06:00
feat: Hidden fields and metadata for integrations (#2752)
Co-authored-by: Johannes <72809645+jobenjada@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
afe01a61ae
commit
543d85eb28
@@ -2,6 +2,7 @@
|
||||
|
||||
import { getServerSession } from "next-auth";
|
||||
import { authOptions } from "@formbricks/lib/authOptions";
|
||||
import { hasUserEnvironmentAccess } from "@formbricks/lib/environment/auth";
|
||||
import { canUserAccessIntegration } from "@formbricks/lib/integration/auth";
|
||||
import { createOrUpdateIntegration, deleteIntegration } from "@formbricks/lib/integration/service";
|
||||
import { AuthorizationError } from "@formbricks/types/errors";
|
||||
@@ -11,6 +12,12 @@ export const createOrUpdateIntegrationAction = async (
|
||||
environmentId: string,
|
||||
integrationData: TIntegrationInput
|
||||
) => {
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session) throw new AuthorizationError("Not authenticated");
|
||||
|
||||
const isAuthorized = await hasUserEnvironmentAccess(session.user.id, environmentId);
|
||||
if (!isAuthorized) throw new AuthorizationError("Not authorized");
|
||||
|
||||
return await createOrUpdateIntegration(environmentId, integrationData);
|
||||
};
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
TIntegrationAirtableTables,
|
||||
} from "@formbricks/types/integration/airtable";
|
||||
import { TSurvey } from "@formbricks/types/surveys";
|
||||
import { AdditionalIntegrationSettings } from "@formbricks/ui/AdditionalIntegrationSettings";
|
||||
import { Alert, AlertDescription, AlertTitle } from "@formbricks/ui/Alert";
|
||||
import { Button } from "@formbricks/ui/Button";
|
||||
import { Checkbox } from "@formbricks/ui/Checkbox";
|
||||
@@ -46,6 +47,8 @@ export type IntegrationModalInputs = {
|
||||
table: string;
|
||||
survey: string;
|
||||
questions: string[];
|
||||
includeHiddenFields: boolean;
|
||||
includeMetadata: boolean;
|
||||
};
|
||||
|
||||
const NoBaseFoundError = () => {
|
||||
@@ -72,12 +75,24 @@ export const AddIntegrationModal = ({
|
||||
const [tables, setTables] = useState<TIntegrationAirtableTables["tables"]>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { handleSubmit, control, watch, setValue, reset } = useForm<IntegrationModalInputs>();
|
||||
const [includeHiddenFields, setIncludeHiddenFields] = useState(false);
|
||||
const [includeMetadata, setIncludeMetadata] = useState(false);
|
||||
const airtableIntegrationData: TIntegrationAirtableInput = {
|
||||
type: "airtable",
|
||||
config: {
|
||||
key: airtableIntegration?.config?.key,
|
||||
data: airtableIntegration.config.data ?? [],
|
||||
email: airtableIntegration?.config?.email,
|
||||
},
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isEditMode) {
|
||||
const { index: _index, ...rest } = defaultData;
|
||||
reset(rest);
|
||||
fetchTable(defaultData.base);
|
||||
setIncludeHiddenFields(defaultData.includeHiddenFields);
|
||||
setIncludeMetadata(defaultData.includeMetadata);
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
@@ -104,15 +119,6 @@ export const AddIntegrationModal = ({
|
||||
throw new Error("Please select at least one question");
|
||||
}
|
||||
|
||||
const airtableIntegrationData: TIntegrationAirtableInput = {
|
||||
type: "airtable",
|
||||
config: {
|
||||
key: airtableIntegration?.config?.key,
|
||||
data: airtableIntegration.config.data ?? [],
|
||||
email: airtableIntegration?.config?.email,
|
||||
},
|
||||
};
|
||||
|
||||
const currentTable = tables.find((item) => item.id === data.table);
|
||||
const integrationData: TIntegrationAirtableConfigData = {
|
||||
surveyId: selectedSurvey.id,
|
||||
@@ -124,6 +130,8 @@ export const AddIntegrationModal = ({
|
||||
baseId: data.base,
|
||||
tableId: data.table,
|
||||
tableName: currentTable?.name ?? "",
|
||||
includeHiddenFields,
|
||||
includeMetadata,
|
||||
};
|
||||
|
||||
if (isEditMode) {
|
||||
@@ -165,10 +173,10 @@ export const AddIntegrationModal = ({
|
||||
|
||||
const handleDelete = async (index: number) => {
|
||||
try {
|
||||
const integrationCopy = { ...airtableIntegration };
|
||||
integrationCopy.config.data.splice(index, 1);
|
||||
const integrationData = structuredClone(airtableIntegrationData);
|
||||
integrationData.config.data.splice(index, 1);
|
||||
|
||||
await createOrUpdateIntegrationAction(environmentId, integrationCopy);
|
||||
await createOrUpdateIntegrationAction(environmentId, integrationData);
|
||||
handleClose();
|
||||
router.refresh();
|
||||
|
||||
@@ -280,42 +288,52 @@ export const AddIntegrationModal = ({
|
||||
) : null}
|
||||
|
||||
{survey && selectedSurvey && (
|
||||
<div>
|
||||
<Label htmlFor="Surveys">Questions</Label>
|
||||
<div className="mt-1 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", attributeClasses)?.questions.map(
|
||||
(question) => (
|
||||
<Controller
|
||||
key={question.id}
|
||||
control={control}
|
||||
name={"questions"}
|
||||
render={({ field }) => (
|
||||
<div className="my-1 flex items-center space-x-2">
|
||||
<label htmlFor={question.id} className="flex cursor-pointer items-center">
|
||||
<Checkbox
|
||||
type="button"
|
||||
id={question.id}
|
||||
value={question.id}
|
||||
className="bg-white"
|
||||
checked={field.value?.includes(question.id)}
|
||||
onCheckedChange={(checked) => {
|
||||
return checked
|
||||
? field.onChange([...field.value, question.id])
|
||||
: field.onChange(field.value?.filter((value) => value !== question.id));
|
||||
}}
|
||||
/>
|
||||
<span className="ml-2">
|
||||
{getLocalizedValue(question.headline, "default")}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<Label htmlFor="Surveys">Questions</Label>
|
||||
<div className="mt-1 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", attributeClasses)?.questions.map(
|
||||
(question) => (
|
||||
<Controller
|
||||
key={question.id}
|
||||
control={control}
|
||||
name={"questions"}
|
||||
render={({ field }) => (
|
||||
<div className="my-1 flex items-center space-x-2">
|
||||
<label htmlFor={question.id} className="flex cursor-pointer items-center">
|
||||
<Checkbox
|
||||
type="button"
|
||||
id={question.id}
|
||||
value={question.id}
|
||||
className="bg-white"
|
||||
checked={field.value?.includes(question.id)}
|
||||
onCheckedChange={(checked) => {
|
||||
return checked
|
||||
? field.onChange([...field.value, question.id])
|
||||
: field.onChange(
|
||||
field.value?.filter((value) => value !== question.id)
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<span className="ml-2">
|
||||
{getLocalizedValue(question.headline, "default")}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<AdditionalIntegrationSettings
|
||||
includeHiddenFields={includeHiddenFields}
|
||||
includeMetadata={includeMetadata}
|
||||
setIncludeHiddenFields={setIncludeHiddenFields}
|
||||
setIncludeMetadata={setIncludeMetadata}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -112,6 +112,8 @@ export const ManageIntegration = (props: ManageIntegrationProps) => {
|
||||
questions: data.questionIds,
|
||||
survey: data.surveyId,
|
||||
table: data.tableId,
|
||||
includeHiddenFields: !!data.includeHiddenFields,
|
||||
includeMetadata: !!data.includeMetadata,
|
||||
index,
|
||||
});
|
||||
setIsModalOpen(true);
|
||||
|
||||
@@ -17,6 +17,9 @@ export async function getSpreadsheetNameByIdAction(
|
||||
|
||||
const isAuthorized = await hasUserEnvironmentAccess(session.user.id, environmentId);
|
||||
if (!isAuthorized) throw new AuthorizationError("Not authorized");
|
||||
|
||||
return await getSpreadsheetNameById(googleSheetIntegration, spreadsheetId);
|
||||
const integrationData = structuredClone(googleSheetIntegration);
|
||||
integrationData.config.data.forEach((data) => {
|
||||
data.createdAt = new Date(data.createdAt);
|
||||
});
|
||||
return await getSpreadsheetNameById(integrationData, spreadsheetId);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
TIntegrationGoogleSheetsInput,
|
||||
} from "@formbricks/types/integration/googleSheet";
|
||||
import { TSurvey } from "@formbricks/types/surveys";
|
||||
import { AdditionalIntegrationSettings } from "@formbricks/ui/AdditionalIntegrationSettings";
|
||||
import { Button } from "@formbricks/ui/Button";
|
||||
import { Checkbox } from "@formbricks/ui/Checkbox";
|
||||
import { DropdownSelector } from "@formbricks/ui/DropdownSelector";
|
||||
@@ -45,7 +46,7 @@ export const AddIntegrationModal = ({
|
||||
selectedIntegration,
|
||||
attributeClasses,
|
||||
}: AddIntegrationModalProps) => {
|
||||
const integrationData = {
|
||||
const integrationData: TIntegrationGoogleSheetsConfigData = {
|
||||
spreadsheetId: "",
|
||||
spreadsheetName: "",
|
||||
surveyId: "",
|
||||
@@ -61,6 +62,8 @@ export const AddIntegrationModal = ({
|
||||
const [spreadsheetUrl, setSpreadsheetUrl] = useState("");
|
||||
const [isDeleting, setIsDeleting] = useState<boolean>(false);
|
||||
const existingIntegrationData = googleSheetIntegration?.config?.data;
|
||||
const [includeHiddenFields, setIncludeHiddenFields] = useState(false);
|
||||
const [includeMetadata, setIncludeMetadata] = useState(false);
|
||||
const googleSheetIntegrationData: TIntegrationGoogleSheetsInput = {
|
||||
type: "googleSheets",
|
||||
config: {
|
||||
@@ -71,7 +74,7 @@ export const AddIntegrationModal = ({
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedSurvey) {
|
||||
if (selectedSurvey && !selectedIntegration) {
|
||||
const questionIds = selectedSurvey.questions.map((question) => question.id);
|
||||
setSelectedQuestions(questionIds);
|
||||
}
|
||||
@@ -86,6 +89,8 @@ export const AddIntegrationModal = ({
|
||||
})!
|
||||
);
|
||||
setSelectedQuestions(selectedIntegration.questionIds);
|
||||
setIncludeHiddenFields(!!selectedIntegration.includeHiddenFields);
|
||||
setIncludeMetadata(!!selectedIntegration.includeMetadata);
|
||||
return;
|
||||
} else {
|
||||
setSpreadsheetUrl("");
|
||||
@@ -95,7 +100,7 @@ export const AddIntegrationModal = ({
|
||||
|
||||
const linkSheet = async () => {
|
||||
try {
|
||||
if (isValidGoogleSheetsUrl(spreadsheetUrl)) {
|
||||
if (!isValidGoogleSheetsUrl(spreadsheetUrl)) {
|
||||
throw new Error("Please enter a valid spreadsheet url");
|
||||
}
|
||||
if (!selectedSurvey) {
|
||||
@@ -110,6 +115,7 @@ export const AddIntegrationModal = ({
|
||||
environmentId,
|
||||
spreadsheetId
|
||||
);
|
||||
|
||||
setIsLinkingSheet(true);
|
||||
integrationData.spreadsheetId = spreadsheetId;
|
||||
integrationData.spreadsheetName = spreadsheetName;
|
||||
@@ -121,6 +127,8 @@ export const AddIntegrationModal = ({
|
||||
? "All questions"
|
||||
: "Selected questions";
|
||||
integrationData.createdAt = new Date();
|
||||
integrationData.includeHiddenFields = includeHiddenFields;
|
||||
integrationData.includeMetadata = includeMetadata;
|
||||
if (selectedIntegration) {
|
||||
// update action
|
||||
googleSheetIntegrationData.config!.data[selectedIntegration.index] = integrationData;
|
||||
@@ -148,12 +156,16 @@ export const AddIntegrationModal = ({
|
||||
};
|
||||
|
||||
const setOpenWithStates = (isOpen: boolean) => {
|
||||
resetForm();
|
||||
setOpen(isOpen);
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
setSpreadsheetUrl("");
|
||||
setIsLinkingSheet(false);
|
||||
setSelectedSurvey(null);
|
||||
setIncludeHiddenFields(false);
|
||||
setIncludeMetadata(false);
|
||||
};
|
||||
|
||||
const deleteLink = async () => {
|
||||
@@ -214,33 +226,41 @@ export const AddIntegrationModal = ({
|
||||
</div>
|
||||
</div>
|
||||
{selectedSurvey && (
|
||||
<div>
|
||||
<Label htmlFor="Surveys">Questions</Label>
|
||||
<div className="mt-1 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", attributeClasses)?.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
|
||||
type="button"
|
||||
id={question.id}
|
||||
value={question.id}
|
||||
className="bg-white"
|
||||
checked={selectedQuestions.includes(question.id)}
|
||||
onCheckedChange={() => {
|
||||
handleCheckboxChange(question.id);
|
||||
}}
|
||||
/>
|
||||
<span className="ml-2 w-[30rem] truncate">
|
||||
{getLocalizedValue(question.headline, "default")}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<Label htmlFor="Surveys">Questions</Label>
|
||||
<div className="mt-1 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", attributeClasses)?.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
|
||||
type="button"
|
||||
id={question.id}
|
||||
value={question.id}
|
||||
className="bg-white"
|
||||
checked={selectedQuestions.includes(question.id)}
|
||||
onCheckedChange={() => {
|
||||
handleCheckboxChange(question.id);
|
||||
}}
|
||||
/>
|
||||
<span className="ml-2 w-[30rem] truncate">
|
||||
{getLocalizedValue(question.headline, "default")}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<AdditionalIntegrationSettings
|
||||
includeHiddenFields={includeHiddenFields}
|
||||
includeMetadata={includeMetadata}
|
||||
setIncludeHiddenFields={setIncludeHiddenFields}
|
||||
setIncludeMetadata={setIncludeMetadata}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -14,7 +14,5 @@ export const constructGoogleSheetsUrl = (spreadsheetId: string): string => {
|
||||
};
|
||||
|
||||
export const isValidGoogleSheetsUrl = (url: string): boolean => {
|
||||
// Regular expression to match Google Sheets URL format
|
||||
const googleSheetsUrlRegex = /^https:\/\/docs\.google\.com\/spreadsheets\/d\/[a-zA-Z0-9-_]+\/?$/;
|
||||
return googleSheetsUrlRegex.test(url);
|
||||
return url.startsWith("https://docs.google.com/spreadsheets/d/");
|
||||
};
|
||||
|
||||
@@ -121,11 +121,19 @@ export const AddIntegrationModal = ({
|
||||
const hiddenFields = selectedSurvey?.hiddenFields.enabled
|
||||
? selectedSurvey?.hiddenFields.fieldIds?.map((fId) => ({
|
||||
id: fId,
|
||||
name: fId,
|
||||
name: `Hidden field : ${fId}`,
|
||||
type: TSurveyQuestionTypeEnum.OpenText,
|
||||
})) || []
|
||||
: [];
|
||||
return [...questions, ...hiddenFields];
|
||||
const Metadata = [
|
||||
{
|
||||
id: "metadata",
|
||||
name: `Metadata`,
|
||||
type: TSurveyQuestionTypeEnum.OpenText,
|
||||
},
|
||||
];
|
||||
|
||||
return [...questions, ...hiddenFields, ...Metadata];
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selectedSurvey?.id]);
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
TIntegrationSlackInput,
|
||||
} from "@formbricks/types/integration/slack";
|
||||
import { TSurvey } from "@formbricks/types/surveys";
|
||||
import { AdditionalIntegrationSettings } from "@formbricks/ui/AdditionalIntegrationSettings";
|
||||
import { Button } from "@formbricks/ui/Button";
|
||||
import { Checkbox } from "@formbricks/ui/Checkbox";
|
||||
import { DropdownSelector } from "@formbricks/ui/DropdownSelector";
|
||||
@@ -48,6 +49,8 @@ export const AddChannelMappingModal = ({
|
||||
const [selectedSurvey, setSelectedSurvey] = useState<TSurvey | null>(null);
|
||||
const [selectedChannel, setSelectedChannel] = useState<TIntegrationItem | null>(null);
|
||||
const [isDeleting, setIsDeleting] = useState<boolean>(false);
|
||||
const [includeHiddenFields, setIncludeHiddenFields] = useState(false);
|
||||
const [includeMetadata, setIncludeMetadata] = useState(false);
|
||||
const existingIntegrationData = slackIntegration?.config?.data;
|
||||
const slackIntegrationData: TIntegrationSlackInput = {
|
||||
type: "slack",
|
||||
@@ -78,6 +81,8 @@ export const AddChannelMappingModal = ({
|
||||
})!
|
||||
);
|
||||
setSelectedQuestions(selectedIntegration.questionIds);
|
||||
setIncludeHiddenFields(!!selectedIntegration.includeHiddenFields);
|
||||
setIncludeMetadata(!!selectedIntegration.includeMetadata);
|
||||
return;
|
||||
}
|
||||
resetForm();
|
||||
@@ -107,6 +112,8 @@ export const AddChannelMappingModal = ({
|
||||
? "All questions"
|
||||
: "Selected questions",
|
||||
createdAt: new Date(),
|
||||
includeHiddenFields,
|
||||
includeMetadata,
|
||||
};
|
||||
if (selectedIntegration) {
|
||||
// update action
|
||||
@@ -222,30 +229,40 @@ export const AddChannelMappingModal = ({
|
||||
</div>
|
||||
{selectedSurvey && (
|
||||
<div>
|
||||
<Label htmlFor="Surveys">Questions</Label>
|
||||
<div className="mt-1 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", attributeClasses)?.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
|
||||
type="button"
|
||||
id={question.id}
|
||||
value={question.id}
|
||||
className="bg-white"
|
||||
checked={selectedQuestions.includes(question.id)}
|
||||
onCheckedChange={() => {
|
||||
handleCheckboxChange(question.id);
|
||||
}}
|
||||
/>
|
||||
<span className="ml-2">{getLocalizedValue(question.headline, "default")}</span>
|
||||
</label>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
<div>
|
||||
<Label htmlFor="Surveys">Questions</Label>
|
||||
<div className="mt-1 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", attributeClasses)?.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
|
||||
type="button"
|
||||
id={question.id}
|
||||
value={question.id}
|
||||
className="bg-white"
|
||||
checked={selectedQuestions.includes(question.id)}
|
||||
onCheckedChange={() => {
|
||||
handleCheckboxChange(question.id);
|
||||
}}
|
||||
/>
|
||||
<span className="ml-2">
|
||||
{getLocalizedValue(question.headline, "default")}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<AdditionalIntegrationSettings
|
||||
includeHiddenFields={includeHiddenFields}
|
||||
includeMetadata={includeMetadata}
|
||||
setIncludeHiddenFields={setIncludeHiddenFields}
|
||||
setIncludeMetadata={setIncludeMetadata}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -10,8 +10,42 @@ import { TIntegrationGoogleSheets } from "@formbricks/types/integration/googleSh
|
||||
import { TIntegrationNotion, TIntegrationNotionConfigData } from "@formbricks/types/integration/notion";
|
||||
import { TIntegrationSlack } from "@formbricks/types/integration/slack";
|
||||
import { TPipelineInput } from "@formbricks/types/pipelines";
|
||||
import { TResponseMeta } from "@formbricks/types/responses";
|
||||
import { TSurvey, TSurveyQuestionTypeEnum } from "@formbricks/types/surveys";
|
||||
|
||||
const convertMetaObjectToString = (metadata: TResponseMeta): string => {
|
||||
let result: string[] = [];
|
||||
if (metadata.source) result.push(`Source: ${metadata.source}`);
|
||||
if (metadata.url) result.push(`URL: ${metadata.url}`);
|
||||
if (metadata.userAgent?.browser) result.push(`Browser: ${metadata.userAgent.browser}`);
|
||||
if (metadata.userAgent?.os) result.push(`OS: ${metadata.userAgent.os}`);
|
||||
if (metadata.userAgent?.device) result.push(`Device: ${metadata.userAgent.device}`);
|
||||
if (metadata.country) result.push(`Country: ${metadata.country}`);
|
||||
if (metadata.action) result.push(`Action: ${metadata.action}`);
|
||||
|
||||
// Join all the elements in the result array with a newline for formatting
|
||||
return result.join("\n");
|
||||
};
|
||||
|
||||
const processDataForIntegration = async (
|
||||
data: TPipelineInput,
|
||||
survey: TSurvey,
|
||||
includeMetadata: boolean,
|
||||
includeHiddenFields: boolean,
|
||||
questionIds: string[]
|
||||
): Promise<string[][]> => {
|
||||
const ids =
|
||||
includeHiddenFields && survey.hiddenFields.fieldIds
|
||||
? [...questionIds, ...survey.hiddenFields.fieldIds]
|
||||
: questionIds;
|
||||
const values = await extractResponses(data, ids, survey);
|
||||
if (includeMetadata) {
|
||||
values[0].push(convertMetaObjectToString(data.response.meta));
|
||||
values[1].push("Metadata");
|
||||
}
|
||||
return values;
|
||||
};
|
||||
|
||||
export const handleIntegrations = async (
|
||||
integrations: TIntegration[],
|
||||
data: TPipelineInput,
|
||||
@@ -43,8 +77,13 @@ const handleAirtableIntegration = async (
|
||||
if (integration.config.data.length > 0) {
|
||||
for (const element of integration.config.data) {
|
||||
if (element.surveyId === data.surveyId) {
|
||||
const values = await extractResponses(data, element.questionIds as string[], survey);
|
||||
|
||||
const values = await processDataForIntegration(
|
||||
data,
|
||||
survey,
|
||||
!!element.includeMetadata,
|
||||
!!element.includeHiddenFields,
|
||||
element.questionIds
|
||||
);
|
||||
await airtableWriteData(integration.config.key, element, values);
|
||||
}
|
||||
}
|
||||
@@ -59,11 +98,18 @@ const handleGoogleSheetsIntegration = async (
|
||||
if (integration.config.data.length > 0) {
|
||||
for (const element of integration.config.data) {
|
||||
if (element.surveyId === data.surveyId) {
|
||||
const values = await extractResponses(data, element.questionIds, survey);
|
||||
const values = await processDataForIntegration(
|
||||
data,
|
||||
survey,
|
||||
!!element.includeMetadata,
|
||||
!!element.includeHiddenFields,
|
||||
element.questionIds
|
||||
);
|
||||
const integrationData = structuredClone(integration);
|
||||
integrationData.config.data.forEach((data) => {
|
||||
data.createdAt = new Date(data.createdAt);
|
||||
});
|
||||
|
||||
await writeData(integrationData, element.spreadsheetId, values);
|
||||
}
|
||||
}
|
||||
@@ -78,7 +124,13 @@ const handleSlackIntegration = async (
|
||||
if (integration.config.data.length > 0) {
|
||||
for (const element of integration.config.data) {
|
||||
if (element.surveyId === data.surveyId) {
|
||||
const values = await extractResponses(data, element.questionIds as string[], survey);
|
||||
const values = await processDataForIntegration(
|
||||
data,
|
||||
survey,
|
||||
!!element.includeMetadata,
|
||||
!!element.includeHiddenFields,
|
||||
element.questionIds
|
||||
);
|
||||
await writeDataToSlack(integration.config.key, element.channelId, values, survey?.name);
|
||||
}
|
||||
}
|
||||
@@ -86,7 +138,7 @@ const handleSlackIntegration = async (
|
||||
};
|
||||
|
||||
const extractResponses = async (
|
||||
data: TPipelineInput,
|
||||
pipelineData: TPipelineInput,
|
||||
questionIds: string[],
|
||||
survey: TSurvey
|
||||
): Promise<string[][]> => {
|
||||
@@ -94,12 +146,18 @@ const extractResponses = async (
|
||||
const questions: string[] = [];
|
||||
|
||||
for (const questionId of questionIds) {
|
||||
//check for hidden field Ids
|
||||
if (survey.hiddenFields.fieldIds?.includes(questionId)) {
|
||||
responses.push(processResponseData(pipelineData.response.data[questionId]));
|
||||
questions.push(questionId);
|
||||
continue;
|
||||
}
|
||||
const question = survey?.questions.find((q) => q.id === questionId);
|
||||
if (!question) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const responseValue = data.response.data[questionId];
|
||||
const responseValue = pipelineData.response.data[questionId];
|
||||
|
||||
if (responseValue !== undefined) {
|
||||
let answer: typeof responseValue;
|
||||
@@ -162,11 +220,16 @@ const buildNotionPayloadProperties = (
|
||||
});
|
||||
|
||||
mapping.forEach((map) => {
|
||||
const value = responses[map.question.id];
|
||||
|
||||
properties[map.column.name] = {
|
||||
[map.column.type]: getValue(map.column.type, processResponseData(value)),
|
||||
};
|
||||
if (map.question.id === "metadata") {
|
||||
properties[map.column.name] = {
|
||||
[map.column.type]: getValue(map.column.type, convertMetaObjectToString(data.response.meta)),
|
||||
};
|
||||
} else {
|
||||
const value = responses[map.question.id];
|
||||
properties[map.column.name] = {
|
||||
[map.column.type]: getValue(map.column.type, processResponseData(value)),
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return properties;
|
||||
|
||||
@@ -10,6 +10,8 @@ export const ZIntegrationBase = z.object({
|
||||
export const ZIntegrationBaseSurveyData = z.object({
|
||||
createdAt: z.date(),
|
||||
questionIds: z.array(z.string()),
|
||||
includeHiddenFields: z.boolean().optional(),
|
||||
includeMetadata: z.boolean().optional(),
|
||||
questions: z.string(),
|
||||
surveyId: z.string(),
|
||||
surveyName: z.string(),
|
||||
|
||||
54
packages/ui/AdditionalIntegrationSettings/index.tsx
Normal file
54
packages/ui/AdditionalIntegrationSettings/index.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import { Checkbox } from "../Checkbox";
|
||||
import { Label } from "../Label";
|
||||
|
||||
interface AdditionalIntegrationSettingsProps {
|
||||
includeHiddenFields: boolean;
|
||||
includeMetadata: boolean;
|
||||
setIncludeHiddenFields: (includeHiddenFields: boolean) => void;
|
||||
setIncludeMetadata: (includeHiddenFields: boolean) => void;
|
||||
}
|
||||
|
||||
export const AdditionalIntegrationSettings = ({
|
||||
includeHiddenFields,
|
||||
includeMetadata,
|
||||
setIncludeHiddenFields,
|
||||
setIncludeMetadata,
|
||||
}: AdditionalIntegrationSettingsProps) => {
|
||||
return (
|
||||
<div className="mt-4">
|
||||
<Label htmlFor="Surveys">Additional Setings</Label>
|
||||
<div className="text-sm">
|
||||
<div className="my-1 flex items-center space-x-2">
|
||||
<label htmlFor={"includeHiddenFields"} className="flex cursor-pointer items-center">
|
||||
<Checkbox
|
||||
type="button"
|
||||
id={"includeHiddenFields"}
|
||||
value={"includeHiddenFields"}
|
||||
className="bg-white"
|
||||
checked={includeHiddenFields}
|
||||
onCheckedChange={() => {
|
||||
setIncludeHiddenFields(!includeHiddenFields);
|
||||
}}
|
||||
/>
|
||||
<span className="ml-2 w-[30rem] truncate">Include Hidden Fields</span>
|
||||
</label>
|
||||
</div>
|
||||
<div className="my-1 flex items-center space-x-2">
|
||||
<label htmlFor={"includeMetadata"} className="flex cursor-pointer items-center">
|
||||
<Checkbox
|
||||
type="button"
|
||||
id={"includeMetadata"}
|
||||
value={"includeMetadata"}
|
||||
className="bg-white"
|
||||
checked={includeMetadata}
|
||||
onCheckedChange={() => {
|
||||
setIncludeMetadata(!includeMetadata);
|
||||
}}
|
||||
/>
|
||||
<span className="ml-2 w-[30rem] truncate">Include Metadata (Browser, Country, etc.)</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -53,7 +53,7 @@ export const DropdownSelector = ({
|
||||
className="z-50 max-h-64 min-w-[220px] max-w-[90%] overflow-auto rounded-md bg-white text-sm text-slate-800 shadow-md"
|
||||
align="start">
|
||||
{items
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
.sort((a, b) => a.name?.localeCompare(b.name))
|
||||
.map((item) => (
|
||||
<DropdownMenuItem
|
||||
key={item.id}
|
||||
|
||||
Reference in New Issue
Block a user