mirror of
https://github.com/formbricks/formbricks.git
synced 2026-05-14 19:38:53 -05:00
refactor: enhance CSV field mapping logic and improve localization
- Updated `sanitizeCsvFieldMappings` to utilize constants for protected target IDs. - Refactored connector actions to streamline field mapping processing. - Added a new test case for sanitization to ensure correct handling of all-protected mappings. - Improved localization for CSV row count and field types in the UI components.
This commit is contained in:
@@ -339,13 +339,14 @@ export const duplicateConnectorAction = authenticatedActionClient
|
||||
})),
|
||||
};
|
||||
} else if (source.fieldMappings.length > 0) {
|
||||
const projected = source.fieldMappings.map((m) => ({
|
||||
sourceFieldId: m.sourceFieldId,
|
||||
targetFieldId: m.targetFieldId,
|
||||
staticValue: m.staticValue ?? undefined,
|
||||
}));
|
||||
mappingsInput = {
|
||||
type: "field",
|
||||
mappings: source.fieldMappings.map((m) => ({
|
||||
sourceFieldId: m.sourceFieldId,
|
||||
targetFieldId: m.targetFieldId,
|
||||
staticValue: m.staticValue ?? undefined,
|
||||
})),
|
||||
mappings: source.type === "csv" ? (sanitizeCsvFieldMappings(projected) ?? []) : projected,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -20,4 +20,15 @@ describe("sanitizeCsvFieldMappings", () => {
|
||||
expect(sanitizeCsvFieldMappings(undefined)).toBeUndefined();
|
||||
expect(sanitizeCsvFieldMappings([])).toBeUndefined();
|
||||
});
|
||||
|
||||
test("returns only the static csv source_type mapping when input is all-protected", () => {
|
||||
const mappings: TConnectorFieldMappingCreateInput[] = [
|
||||
{ sourceFieldId: "tenant", targetFieldId: "tenant_id" },
|
||||
{ sourceFieldId: "type", targetFieldId: "source_type" },
|
||||
];
|
||||
|
||||
expect(sanitizeCsvFieldMappings(mappings)).toEqual([
|
||||
{ sourceFieldId: "", targetFieldId: "source_type", staticValue: "csv" },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import { TConnectorFieldMappingCreateInput } from "@formbricks/types/connector";
|
||||
import {
|
||||
CSV_HIDDEN_STATIC_MAPPINGS,
|
||||
CSV_PROTECTED_TARGET_IDS,
|
||||
} from "@/modules/ee/unify-feedback/sources/types";
|
||||
|
||||
export const sanitizeCsvFieldMappings = (
|
||||
fieldMappings: TConnectorFieldMappingCreateInput[] | undefined
|
||||
): TConnectorFieldMappingCreateInput[] | undefined => {
|
||||
if (!fieldMappings?.length) return undefined;
|
||||
|
||||
const userMappings = fieldMappings.filter(
|
||||
(mapping) => mapping.targetFieldId !== "tenant_id" && mapping.targetFieldId !== "source_type"
|
||||
const userMappings = fieldMappings.filter((mapping) =>
|
||||
CSV_PROTECTED_TARGET_IDS.every((id) => mapping.targetFieldId !== id)
|
||||
);
|
||||
|
||||
return [...userMappings, { sourceFieldId: "", targetFieldId: "source_type", staticValue: "csv" }];
|
||||
return [...userMappings, ...(CSV_HIDDEN_STATIC_MAPPINGS as TConnectorFieldMappingCreateInput[])];
|
||||
};
|
||||
|
||||
@@ -3691,6 +3691,7 @@
|
||||
"csv_pick_column_placeholder": "Pick a column or set a value…",
|
||||
"csv_required_fields_missing": "Please map required fields before saving: {fields}",
|
||||
"csv_response_preview": "Sample: \"{sample}\" → stored as {target}.",
|
||||
"csv_rows_count": "{count, plural, one {# row} other {# rows}}",
|
||||
"csv_sample_label": "Sample CSV",
|
||||
"csv_source_context": "Source Context",
|
||||
"csv_source_context_hint": "Identifies where this batch of feedback came from.",
|
||||
@@ -3731,6 +3732,12 @@
|
||||
"field_id": "Field ID",
|
||||
"field_label": "Field Label",
|
||||
"field_type": "Field Type",
|
||||
"fields": {
|
||||
"boolean": "Boolean",
|
||||
"date": "Date",
|
||||
"number": "Number",
|
||||
"text": "Text"
|
||||
},
|
||||
"formbricks_surveys": "Formbricks Surveys",
|
||||
"go_to_feedback_directories": "Go to directories settings",
|
||||
"historical_import_complete": "Import complete: {successes} succeeded, {failures} failed, {skipped} skipped (no data)",
|
||||
|
||||
@@ -44,6 +44,7 @@ import {
|
||||
import { Switch } from "@/modules/ui/components/switch";
|
||||
import {
|
||||
CSV_HIDDEN_STATIC_MAPPINGS,
|
||||
CSV_PROTECTED_TARGET_IDS,
|
||||
TCreateConnectorStep,
|
||||
TFieldMapping,
|
||||
TFormbricksConnectorForm,
|
||||
@@ -373,8 +374,9 @@ export const CreateConnectorModal = ({
|
||||
setIsCreating(true);
|
||||
|
||||
// Strip any user-supplied tenant_id and merge hidden static mappings (source_type=csv).
|
||||
const protectedIds = ["tenant_id", "source_type"];
|
||||
const userMappings = mappings.filter((m) => protectedIds.every((id) => m.targetFieldId !== id));
|
||||
const userMappings = mappings.filter((m) =>
|
||||
CSV_PROTECTED_TARGET_IDS.every((id) => m.targetFieldId !== id)
|
||||
);
|
||||
const fieldMappings = [...userMappings, ...CSV_HIDDEN_STATIC_MAPPINGS];
|
||||
|
||||
const connectorId = await onCreateConnector({
|
||||
|
||||
@@ -50,11 +50,7 @@ export function CsvConnectorUI({
|
||||
useEffect(() => {
|
||||
const sourceNameMapping = mappings.find((m) => m.targetFieldId === "source_name");
|
||||
const current = sourceNameMapping?.staticValue ?? sourceNameMapping?.sourceFieldId;
|
||||
if (
|
||||
lastAutoSourceNameRef.current !== undefined &&
|
||||
current !== undefined &&
|
||||
current !== lastAutoSourceNameRef.current
|
||||
) {
|
||||
if (lastAutoSourceNameRef.current !== undefined && current !== lastAutoSourceNameRef.current) {
|
||||
userEditedSourceNameRef.current = true;
|
||||
}
|
||||
}, [mappings]);
|
||||
@@ -212,7 +208,11 @@ export function CsvConnectorUI({
|
||||
<div className="flex items-center justify-between rounded-lg border border-slate-200 bg-slate-50 px-4 py-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium text-slate-800">{sourceLabel}</span>
|
||||
<Badge text={`${csvTotalRows} rows`} type="gray" size="tiny" />
|
||||
<Badge
|
||||
text={t("workspace.unify.csv_rows_count", { count: csvTotalRows })}
|
||||
type="gray"
|
||||
size="tiny"
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
variant="secondary"
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
} from "@/modules/ui/components/select";
|
||||
import {
|
||||
CSV_HIDDEN_STATIC_MAPPINGS,
|
||||
CSV_PROTECTED_TARGET_IDS,
|
||||
SAMPLE_CSV_COLUMNS,
|
||||
TFieldMapping,
|
||||
TFormbricksConnectorForm,
|
||||
@@ -199,8 +200,9 @@ export const EditConnectorModal = ({
|
||||
}
|
||||
|
||||
setIsUpdating(true);
|
||||
const protectedIds = ["tenant_id", "source_type"];
|
||||
const userMappings = mappings.filter((m) => protectedIds.every((id) => m.targetFieldId !== id));
|
||||
const userMappings = mappings.filter((m) =>
|
||||
CSV_PROTECTED_TARGET_IDS.every((id) => m.targetFieldId !== id)
|
||||
);
|
||||
const fieldMappings = [...userMappings, ...CSV_HIDDEN_STATIC_MAPPINGS];
|
||||
|
||||
await onUpdateConnector({
|
||||
|
||||
@@ -312,9 +312,9 @@ const computeResponseValuePreview = ({
|
||||
sampleRow?.[responseValueMapping.sourceFieldId] ??
|
||||
sourceFields.find((f) => f.id === responseValueMapping.sourceFieldId)?.sampleValue ??
|
||||
"";
|
||||
const targetLabel = target.replace("value_", "");
|
||||
const localizedTarget = t(`workspace.unify.fields.${target.replace("value_", "")}`);
|
||||
return t("workspace.unify.csv_response_preview", {
|
||||
sample,
|
||||
target: targetLabel.charAt(0).toUpperCase() + targetLabel.slice(1),
|
||||
target: localizedTarget,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -210,6 +210,8 @@ export const CSV_FIELD_GROUPS = {
|
||||
],
|
||||
} as const;
|
||||
|
||||
export const CSV_PROTECTED_TARGET_IDS = ["tenant_id", "source_type"] as const;
|
||||
|
||||
export const CSV_HIDDEN_STATIC_MAPPINGS: TFieldMapping[] = [
|
||||
{ sourceFieldId: "", targetFieldId: "source_type", staticValue: "csv" },
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user