Files
formbricks/apps/web/lib/connector/transform.ts
Tiago Farto e665227437 feat(connector): use @formbricks/hub SDK instead of custom hub-client
- Remove apps/web/lib/connector/hub-client.ts
- Add @formbricks/hub dependency to apps/web
- pipeline-handler: create Hub client from env (HUB_API_KEY, HUB_API_URL), call SDK create in batch with same results shape
- transform: use FormbricksHub.FeedbackRecordCreateParams from SDK directly

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-25 12:20:46 +00:00

136 lines
4.7 KiB
TypeScript

import "server-only";
import type FormbricksHub from "@formbricks/hub";
import { TConnectorFormbricksMapping, THubFieldType } from "@formbricks/types/connector";
import { TResponse } from "@formbricks/types/responses";
import { TSurvey } from "@formbricks/types/surveys/types";
import { getTextContent } from "@formbricks/types/surveys/validation";
import { getLocalizedValue } from "@/lib/i18n/utils";
import { getElementsFromBlocks } from "@/lib/survey/utils";
type TResponseValue = string | number | string[] | Record<string, string> | undefined;
type TSurveyElement = ReturnType<typeof getElementsFromBlocks>[number];
const getHeadlineFromElement = (element: TSurveyElement | undefined): string => {
if (!element?.headline) return "Untitled";
const raw = getLocalizedValue(element.headline, "default");
return getTextContent(raw) || "Untitled";
};
function extractResponseValue(responseData: TResponse["data"], elementId: string): TResponseValue {
if (!responseData || typeof responseData !== "object") return undefined;
return (responseData as Record<string, TResponseValue>)[elementId];
}
const convertValueToHubFields = (
value: TResponseValue,
hubFieldType: THubFieldType
): Partial<
Pick<
FormbricksHub.FeedbackRecordCreateParams,
"value_text" | "value_number" | "value_boolean" | "value_date"
>
> => {
if (value === undefined || value === null) {
return {};
}
switch (hubFieldType) {
case "text":
if (typeof value === "string") return { value_text: value };
if (Array.isArray(value)) return { value_text: value.join(", ") };
if (typeof value === "object") return { value_text: JSON.stringify(value) };
return { value_text: String(value) };
case "number":
case "rating":
case "nps":
case "csat":
case "ces":
if (typeof value === "number") return { value_number: value };
if (typeof value === "string") {
const parsed = Number.parseFloat(value);
if (!Number.isNaN(parsed)) return { value_number: parsed };
}
return {};
case "boolean":
if (typeof value === "boolean") return { value_boolean: value };
if (typeof value === "string") {
return { value_boolean: value.toLowerCase() === "true" || value === "1" };
}
return {};
case "date":
if (typeof value === "string") {
const date = new Date(value);
if (!Number.isNaN(date.getTime())) return { value_date: date.toISOString() };
}
if (value instanceof Date) return { value_date: value.toISOString() };
return {};
case "categorical":
if (typeof value === "string") return { value_text: value };
if (Array.isArray(value)) return { value_text: value.join(", ") };
return { value_text: String(value) };
default:
return { value_text: typeof value === "string" ? value : String(value) };
}
};
/**
* Transform a Formbricks survey response into Hub FeedbackRecord payloads.
* Called from the pipeline handler when a response is created/finished.
*/
export function transformResponseToFeedbackRecords(
response: TResponse,
survey: TSurvey,
mappings: TConnectorFormbricksMapping[],
tenantId?: string
): FormbricksHub.FeedbackRecordCreateParams[] {
const responseData = response.data;
if (!responseData) return [];
const surveyMappings = mappings.filter((m) => m.surveyId === survey.id);
const elements = getElementsFromBlocks(survey.blocks);
const elementMap = new Map(elements.map((el) => [el.id, el]));
const feedbackRecords: FormbricksHub.FeedbackRecordCreateParams[] = [];
for (const mapping of surveyMappings) {
const value = extractResponseValue(responseData, mapping.elementId);
if (value === undefined || value === null || value === "") continue;
const fieldLabel = mapping.customFieldLabel || getHeadlineFromElement(elementMap.get(mapping.elementId));
const valueFields = convertValueToHubFields(value, mapping.hubFieldType);
const feedbackRecord: FormbricksHub.FeedbackRecordCreateParams = {
collected_at:
response.createdAt instanceof Date ? response.createdAt.toISOString() : String(response.createdAt),
source_type: "formbricks",
field_id: mapping.elementId,
field_type: mapping.hubFieldType,
source_id: survey.id,
source_name: survey.name,
field_label: fieldLabel,
...valueFields,
};
if (response.language && response.language !== "default") {
feedbackRecord.language = response.language;
}
if (tenantId) {
feedbackRecord.tenant_id = tenantId;
}
if (response.contact?.userId) {
feedbackRecord.user_identifier = response.contact.userId;
}
feedbackRecords.push(feedbackRecord);
}
return feedbackRecords;
}