mirror of
https://github.com/formbricks/formbricks.git
synced 2026-03-05 00:48:03 -06:00
Compare commits
3 Commits
fix/mls-tw
...
refactor/p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
044e14b05c | ||
|
|
95f3950123 | ||
|
|
32d40e3fe9 |
@@ -682,10 +682,18 @@ export const createContactsFromCSV = async (
|
||||
environmentId,
|
||||
};
|
||||
|
||||
const contactPromises = csvData.map((record) => processCsvRecord(record, processingContext));
|
||||
const CHUNK_SIZE = 50;
|
||||
const allResults: (TContact | null)[] = [];
|
||||
|
||||
const results = await Promise.all(contactPromises);
|
||||
return { contacts: results.filter((contact): contact is TContact => contact !== null) };
|
||||
for (let i = 0; i < csvData.length; i += CHUNK_SIZE) {
|
||||
const chunk = csvData.slice(i, i + CHUNK_SIZE);
|
||||
const chunkResults = await Promise.all(
|
||||
chunk.map((record) => processCsvRecord(record, processingContext))
|
||||
);
|
||||
allResults.push(...chunkResults);
|
||||
}
|
||||
|
||||
return { contacts: allResults.filter((contact): contact is TContact => contact !== null) };
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
throw new DatabaseError(error.message);
|
||||
|
||||
@@ -106,24 +106,23 @@ export const OrganizationActions = ({
|
||||
toast.error(errorMessage);
|
||||
}
|
||||
} else {
|
||||
const invitePromises = await Promise.all(
|
||||
data.map(async ({ name, email, role, teamIds }) => {
|
||||
const inviteUserActionResult = await inviteUserAction({
|
||||
organizationId: organization.id,
|
||||
email: email.toLowerCase(),
|
||||
name,
|
||||
role,
|
||||
teamIds,
|
||||
});
|
||||
return {
|
||||
email,
|
||||
success: Boolean(inviteUserActionResult?.data),
|
||||
};
|
||||
})
|
||||
);
|
||||
let failedInvites: string[] = [];
|
||||
let successInvites: string[] = [];
|
||||
invitePromises.forEach((invite) => {
|
||||
const inviteResults: { email: string; success: boolean }[] = [];
|
||||
for (const { name, email, role, teamIds } of data) {
|
||||
const inviteUserActionResult = await inviteUserAction({
|
||||
organizationId: organization.id,
|
||||
email: email.toLowerCase(),
|
||||
name,
|
||||
role,
|
||||
teamIds,
|
||||
});
|
||||
inviteResults.push({
|
||||
email,
|
||||
success: Boolean(inviteUserActionResult?.data),
|
||||
});
|
||||
}
|
||||
const failedInvites: string[] = [];
|
||||
const successInvites: string[] = [];
|
||||
inviteResults.forEach((invite) => {
|
||||
if (!invite.success) {
|
||||
failedInvites.push(invite.email);
|
||||
} else {
|
||||
|
||||
@@ -152,13 +152,13 @@ describe("tag lib", () => {
|
||||
.mockResolvedValueOnce(baseTag as any)
|
||||
.mockResolvedValueOnce(newTag as any);
|
||||
vi.mocked(prisma.response.findMany).mockResolvedValueOnce([{ id: "resp1" }] as any);
|
||||
vi.mocked(prisma.$transaction).mockResolvedValueOnce(undefined).mockResolvedValueOnce(undefined);
|
||||
vi.mocked(prisma.$transaction).mockResolvedValueOnce(undefined);
|
||||
const result = await mergeTags(baseTag.id, newTag.id);
|
||||
expect(result).toEqual(ok(newTag));
|
||||
expect(prisma.tag.findUnique).toHaveBeenCalledWith({ where: { id: baseTag.id } });
|
||||
expect(prisma.tag.findUnique).toHaveBeenCalledWith({ where: { id: newTag.id } });
|
||||
expect(prisma.response.findMany).toHaveBeenCalled();
|
||||
expect(prisma.$transaction).toHaveBeenCalledTimes(2);
|
||||
expect(prisma.$transaction).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
test("merges tags with no responses with both tags", async () => {
|
||||
vi.mocked(prisma.tag.findUnique)
|
||||
@@ -195,6 +195,20 @@ describe("tag lib", () => {
|
||||
});
|
||||
}
|
||||
});
|
||||
test("returns error when merging a tag into itself", async () => {
|
||||
const result = await mergeTags(baseTag.id, baseTag.id);
|
||||
expect(result.ok).toBe(false);
|
||||
|
||||
if (!result.ok) {
|
||||
expect(result.error).toStrictEqual({
|
||||
code: "merge_same_tag",
|
||||
message: "Cannot merge a tag into itself",
|
||||
});
|
||||
}
|
||||
|
||||
expect(prisma.tag.findUnique).not.toHaveBeenCalled();
|
||||
expect(prisma.$transaction).not.toHaveBeenCalled();
|
||||
});
|
||||
test("throws on prisma error", async () => {
|
||||
vi.mocked(prisma.tag.findUnique).mockRejectedValueOnce(new Error("fail"));
|
||||
const result = await mergeTags(baseTag.id, newTag.id);
|
||||
|
||||
@@ -72,6 +72,13 @@ export const mergeTags = async (
|
||||
): Promise<Result<TTag, { code: TagError; message: string; meta?: Record<string, string> }>> => {
|
||||
validateInputs([originalTagId, ZId], [newTagId, ZId]);
|
||||
|
||||
if (originalTagId === newTagId) {
|
||||
return err({
|
||||
code: TagError.MERGE_SAME_TAG,
|
||||
message: "Cannot merge a tag into itself",
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
let originalTag: TTag | null;
|
||||
|
||||
@@ -103,90 +110,35 @@ export const mergeTags = async (
|
||||
});
|
||||
}
|
||||
|
||||
// finds all the responses that have both the tags
|
||||
let responsesWithBothTags = await prisma.response.findMany({
|
||||
// Find responses that have both tags to avoid unique constraint violations during merge
|
||||
const responsesWithBothTags = await prisma.response.findMany({
|
||||
where: {
|
||||
AND: [
|
||||
{
|
||||
tags: {
|
||||
some: {
|
||||
tagId: {
|
||||
in: [originalTagId],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
tags: {
|
||||
some: {
|
||||
tagId: {
|
||||
in: [newTagId],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
AND: [{ tags: { some: { tagId: originalTagId } } }, { tags: { some: { tagId: newTagId } } }],
|
||||
},
|
||||
select: { id: true },
|
||||
});
|
||||
|
||||
if (!!responsesWithBothTags?.length) {
|
||||
await Promise.all(
|
||||
responsesWithBothTags.map(async (response) => {
|
||||
await prisma.$transaction([
|
||||
prisma.tagsOnResponses.deleteMany({
|
||||
where: {
|
||||
responseId: response.id,
|
||||
tagId: {
|
||||
in: [originalTagId, newTagId],
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
||||
prisma.tagsOnResponses.create({
|
||||
data: {
|
||||
responseId: response.id,
|
||||
tagId: newTagId,
|
||||
},
|
||||
}),
|
||||
]);
|
||||
})
|
||||
);
|
||||
|
||||
await prisma.$transaction([
|
||||
prisma.tagsOnResponses.updateMany({
|
||||
where: {
|
||||
tagId: originalTagId,
|
||||
},
|
||||
data: {
|
||||
tagId: newTagId,
|
||||
},
|
||||
}),
|
||||
|
||||
prisma.tag.delete({
|
||||
where: {
|
||||
id: originalTagId,
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
return ok(newTag);
|
||||
}
|
||||
const conflictResponseIds = responsesWithBothTags.map((r) => r.id);
|
||||
|
||||
await prisma.$transaction([
|
||||
// Remove originalTag from responses that already have newTag (prevents unique constraint violation)
|
||||
...(conflictResponseIds.length > 0
|
||||
? [
|
||||
prisma.tagsOnResponses.deleteMany({
|
||||
where: {
|
||||
responseId: { in: conflictResponseIds },
|
||||
tagId: originalTagId,
|
||||
},
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
// Move all remaining originalTag associations to newTag
|
||||
prisma.tagsOnResponses.updateMany({
|
||||
where: {
|
||||
tagId: originalTagId,
|
||||
},
|
||||
data: {
|
||||
tagId: newTagId,
|
||||
},
|
||||
}),
|
||||
|
||||
prisma.tag.delete({
|
||||
where: {
|
||||
id: originalTagId,
|
||||
},
|
||||
where: { tagId: originalTagId },
|
||||
data: { tagId: newTagId },
|
||||
}),
|
||||
// Delete the original tag
|
||||
prisma.tag.delete({ where: { id: originalTagId } }),
|
||||
]);
|
||||
|
||||
return ok(newTag);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export enum TagError {
|
||||
TAG_NOT_FOUND = "tag_not_found",
|
||||
TAG_NAME_ALREADY_EXISTS = "tag_name_already_exists",
|
||||
MERGE_SAME_TAG = "merge_same_tag",
|
||||
UNEXPECTED_ERROR = "unexpected_error",
|
||||
}
|
||||
|
||||
@@ -128,10 +128,6 @@ export const CopySurveyForm = ({ defaultProjects, survey, onCancel, setOpen }: C
|
||||
: project?.environments[1];
|
||||
|
||||
return {
|
||||
operation: copySurveyToOtherEnvironmentAction({
|
||||
surveyId: survey.id,
|
||||
targetEnvironmentId: environmentId,
|
||||
}),
|
||||
projectName: project?.name ?? "Unknown Project",
|
||||
environmentType: environment?.type ?? "unknown",
|
||||
environmentId,
|
||||
@@ -139,7 +135,14 @@ export const CopySurveyForm = ({ defaultProjects, survey, onCancel, setOpen }: C
|
||||
});
|
||||
});
|
||||
|
||||
const results = await Promise.all(copyOperationsWithMetadata.map((item) => item.operation));
|
||||
const results: Awaited<ReturnType<typeof copySurveyToOtherEnvironmentAction>>[] = [];
|
||||
for (const item of copyOperationsWithMetadata) {
|
||||
const result = await copySurveyToOtherEnvironmentAction({
|
||||
surveyId: survey.id,
|
||||
targetEnvironmentId: item.environmentId,
|
||||
});
|
||||
results.push(result);
|
||||
}
|
||||
|
||||
let successCount = 0;
|
||||
let errorCount = 0;
|
||||
|
||||
@@ -66,10 +66,14 @@ export const SelectedRowSettings = <T,>({
|
||||
setIsDeleting(true);
|
||||
const rowsToBeDeleted = table.getFilteredSelectedRowModel().rows.map((row) => row.id);
|
||||
|
||||
if (type === "response") {
|
||||
await Promise.all(rowsToBeDeleted.map((rowId) => deleteAction(rowId, { decrementQuotas })));
|
||||
} else {
|
||||
await Promise.all(rowsToBeDeleted.map((rowId) => deleteAction(rowId)));
|
||||
const CHUNK_SIZE = 5;
|
||||
for (let i = 0; i < rowsToBeDeleted.length; i += CHUNK_SIZE) {
|
||||
const chunk = rowsToBeDeleted.slice(i, i + CHUNK_SIZE);
|
||||
if (type === "response") {
|
||||
await Promise.all(chunk.map((rowId) => deleteAction(rowId, { decrementQuotas })));
|
||||
} else {
|
||||
await Promise.all(chunk.map((rowId) => deleteAction(rowId)));
|
||||
}
|
||||
}
|
||||
|
||||
// Update the row list UI
|
||||
|
||||
Reference in New Issue
Block a user