Compare commits

...

1 Commits

Author SHA1 Message Date
Cursor Agent
5cfeea0073 Normalize CSV contact attribute keys for case-insensitivity
Co-authored-by: johannes <johannes@formbricks.com>
2025-10-15 15:41:08 +00:00
2 changed files with 62 additions and 1 deletions

View File

@@ -167,6 +167,17 @@ export const UploadContactsCSVButton = ({
const transformedCsvData = csvResponse.map((record) => {
const newRecord: Record<string, string> = {};
Object.entries(record).forEach(([key, value]) => {
// Normalize default attribute keys to their canonical form for case-insensitive matching
const defaultAttributeKeysMap: Record<string, string> = {
userid: "userId",
firstname: "firstName",
lastname: "lastName",
email: "email",
language: "language",
};
const keyLower = key.toLowerCase();
const normalizedKey = defaultAttributeKeysMap[keyLower] || key;
// if the key is in the attribute map, we wanna replace it
if (attributeMap[key]) {
const attrKeyId = attributeMap[key];
@@ -178,7 +189,7 @@ export const UploadContactsCSVButton = ({
newRecord[attrKeyId] = value;
}
} else {
newRecord[key] = value;
newRecord[normalizedKey] = value;
}
});
@@ -244,6 +255,8 @@ export const UploadContactsCSVButton = ({
}, [error]);
// Function to download an example CSV
// Note: The example uses canonical casing for default attributes (email, userId, firstName, lastName, language)
// The upload process is case-insensitive for these attributes (e.g., "Language" will be normalized to "language")
const handleDownloadExampleCSV = () => {
const exampleData = [
{ email: "user1@example.com", userId: "1001", firstName: "John", lastName: "Doe" },

View File

@@ -319,6 +319,54 @@ describe("createContactsFromCSV", () => {
createContactsFromCSV(csvData, environmentId, "skip", { email: "email", name: "name" })
).rejects.toThrow(genericError);
});
test("handles case-insensitive attribute keys (language, userId, firstName, lastName, email)", async () => {
vi.mocked(prisma.contact.findMany).mockResolvedValue([]);
vi.mocked(prisma.contactAttribute.findMany).mockResolvedValue([]);
vi.mocked(prisma.contactAttributeKey.findMany)
.mockResolvedValueOnce([])
.mockResolvedValueOnce([
{ key: "email", id: "id-email" },
{ key: "userId", id: "id-userId" },
{ key: "firstName", id: "id-firstName" },
{ key: "lastName", id: "id-lastName" },
{ key: "language", id: "id-language" },
] as any);
vi.mocked(prisma.contactAttributeKey.createMany).mockResolvedValue({ count: 5 });
vi.mocked(prisma.contact.create).mockResolvedValue({
id: "c1",
environmentId,
createdAt: new Date(),
updatedAt: new Date(),
attributes: [
{ attributeKey: { key: "email" }, value: "john@example.com" },
{ attributeKey: { key: "userId" }, value: "user123" },
{ attributeKey: { key: "firstName" }, value: "John" },
{ attributeKey: { key: "lastName" }, value: "Doe" },
{ attributeKey: { key: "language" }, value: "en" },
],
} as any);
// CSV data with normalized keys (already handled by client-side component)
const csvData = [
{
email: "john@example.com",
userId: "user123",
firstName: "John",
lastName: "Doe",
language: "en",
},
];
const result = await createContactsFromCSV(csvData, environmentId, "skip", {
email: "email",
userId: "userId",
firstName: "firstName",
lastName: "lastName",
language: "language",
});
expect(Array.isArray(result)).toBe(true);
expect(result[0].id).toBe("c1");
expect(prisma.contactAttributeKey.createMany).toHaveBeenCalled();
});
});
describe("buildContactWhereClause", () => {