Compare commits

...

1 Commits

Author SHA1 Message Date
Cursor Agent 8e3f0c53a4 fix: handle segment deletion race condition in survey update
- Check if segment exists before attempting to update/delete it
- Recreate private segment if deleted for app-type surveys
- Skip deletion if segment already deleted
- Update test mocks to include findUnique method

Fixes FORMBRICKS-F7
2026-03-12 09:36:35 +00:00
3 changed files with 169 additions and 74 deletions
+79 -37
View File
@@ -386,54 +386,96 @@ export const updateSurveyInternal = async (
} }
try { try {
// update the segment: // Check if segment exists to handle race conditions where it may have been deleted
let updatedInput: Prisma.SegmentUpdateInput = { const existingSegment = await prisma.segment.findUnique({
...segment,
surveys: undefined,
};
if (segment.surveys) {
updatedInput = {
...segment,
surveys: {
connect: segment.surveys.map((surveyId) => ({ id: surveyId })),
},
};
}
await prisma.segment.update({
where: { id: segment.id }, where: { id: segment.id },
data: updatedInput,
select: {
surveys: { select: { id: true } },
environmentId: true,
id: true,
},
}); });
if (!existingSegment) {
// Segment was deleted, recreate it as a private segment
await prisma.survey.update({
where: { id: surveyId },
data: {
segment: {
connectOrCreate: {
where: {
environmentId_title: {
environmentId,
title: surveyId,
},
},
create: {
title: surveyId,
isPrivate: true,
filters: segment.filters || [],
description: segment.description,
environment: {
connect: {
id: environmentId,
},
},
},
},
},
},
});
} else {
// Segment exists, update it normally
let updatedInput: Prisma.SegmentUpdateInput = {
...segment,
surveys: undefined,
};
if (segment.surveys) {
updatedInput = {
...segment,
surveys: {
connect: segment.surveys.map((surveyId) => ({ id: surveyId })),
},
};
}
await prisma.segment.update({
where: { id: segment.id },
data: updatedInput,
select: {
surveys: { select: { id: true } },
environmentId: true,
id: true,
},
});
}
} catch (error) { } catch (error) {
logger.error(error, "Error updating survey"); logger.error(error, "Error updating survey");
throw new Error("Error updating survey"); throw new Error("Error updating survey");
} }
} else { } else {
if (segment.isPrivate) { if (segment.isPrivate) {
// disconnect the private segment first and then delete: // Check if segment exists before attempting to disconnect and delete
await prisma.segment.update({ const existingSegment = await prisma.segment.findUnique({
where: { id: segment.id }, where: { id: segment.id },
data: {
surveys: {
disconnect: {
id: surveyId,
},
},
},
}); });
// delete the private segment: if (existingSegment) {
await prisma.segment.delete({ // disconnect the private segment first and then delete:
where: { await prisma.segment.update({
id: segment.id, where: { id: segment.id },
}, data: {
}); surveys: {
disconnect: {
id: surveyId,
},
},
},
});
// delete the private segment:
await prisma.segment.delete({
where: {
id: segment.id,
},
});
}
} else { } else {
await prisma.survey.update({ await prisma.survey.update({
where: { where: {
@@ -17,6 +17,7 @@ vi.mock("@formbricks/database", () => ({
update: vi.fn(), update: vi.fn(),
}, },
segment: { segment: {
findUnique: vi.fn(),
update: vi.fn(), update: vi.fn(),
delete: vi.fn(), delete: vi.fn(),
}, },
@@ -154,6 +155,16 @@ describe("Survey Editor Library Tests", () => {
beforeEach(() => { beforeEach(() => {
vi.mocked(prisma.survey.update).mockResolvedValue(mockSurvey as any); vi.mocked(prisma.survey.update).mockResolvedValue(mockSurvey as any);
vi.mocked(prisma.segment.findUnique).mockResolvedValue({
id: "segment1",
environmentId: "env123",
title: "Test Segment",
isPrivate: false,
filters: [],
description: null,
createdAt: new Date(),
updatedAt: new Date(),
} as any);
vi.mocked(prisma.segment.update).mockResolvedValue({ vi.mocked(prisma.segment.update).mockResolvedValue({
id: "segment1", id: "segment1",
environmentId: "env123", environmentId: "env123",
+79 -37
View File
@@ -98,54 +98,96 @@ export const updateSurvey = async (updatedSurvey: TSurvey): Promise<TSurvey> =>
} }
try { try {
// update the segment: // Check if segment exists to handle race conditions where it may have been deleted
let updatedInput: Prisma.SegmentUpdateInput = { const existingSegment = await prisma.segment.findUnique({
...segment,
surveys: undefined,
};
if (segment.surveys) {
updatedInput = {
...segment,
surveys: {
connect: segment.surveys.map((surveyId) => ({ id: surveyId })),
},
};
}
await prisma.segment.update({
where: { id: segment.id }, where: { id: segment.id },
data: updatedInput,
select: {
surveys: { select: { id: true } },
environmentId: true,
id: true,
},
}); });
if (!existingSegment) {
// Segment was deleted, recreate it as a private segment
await prisma.survey.update({
where: { id: surveyId },
data: {
segment: {
connectOrCreate: {
where: {
environmentId_title: {
environmentId,
title: surveyId,
},
},
create: {
title: surveyId,
isPrivate: true,
filters: segment.filters || [],
description: segment.description,
environment: {
connect: {
id: environmentId,
},
},
},
},
},
},
});
} else {
// Segment exists, update it normally
let updatedInput: Prisma.SegmentUpdateInput = {
...segment,
surveys: undefined,
};
if (segment.surveys) {
updatedInput = {
...segment,
surveys: {
connect: segment.surveys.map((surveyId) => ({ id: surveyId })),
},
};
}
await prisma.segment.update({
where: { id: segment.id },
data: updatedInput,
select: {
surveys: { select: { id: true } },
environmentId: true,
id: true,
},
});
}
} catch (error) { } catch (error) {
logger.error(error, "Error updating survey"); logger.error(error, "Error updating survey");
throw new Error("Error updating survey"); throw new Error("Error updating survey");
} }
} else { } else {
if (segment.isPrivate) { if (segment.isPrivate) {
// disconnect the private segment first and then delete: // Check if segment exists before attempting to disconnect and delete
await prisma.segment.update({ const existingSegment = await prisma.segment.findUnique({
where: { id: segment.id }, where: { id: segment.id },
data: {
surveys: {
disconnect: {
id: surveyId,
},
},
},
}); });
// delete the private segment: if (existingSegment) {
await prisma.segment.delete({ // disconnect the private segment first and then delete:
where: { await prisma.segment.update({
id: segment.id, where: { id: segment.id },
}, data: {
}); surveys: {
disconnect: {
id: surveyId,
},
},
},
});
// delete the private segment:
await prisma.segment.delete({
where: {
id: segment.id,
},
});
}
} else { } else {
await prisma.survey.update({ await prisma.survey.update({
where: { where: {