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

View File

@@ -386,54 +386,96 @@ export const updateSurveyInternal = async (
}
try {
// update the segment:
let updatedInput: Prisma.SegmentUpdateInput = {
...segment,
surveys: undefined,
};
if (segment.surveys) {
updatedInput = {
...segment,
surveys: {
connect: segment.surveys.map((surveyId) => ({ id: surveyId })),
},
};
}
await prisma.segment.update({
// Check if segment exists to handle race conditions where it may have been deleted
const existingSegment = await prisma.segment.findUnique({
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) {
logger.error(error, "Error updating survey");
throw new Error("Error updating survey");
}
} else {
if (segment.isPrivate) {
// disconnect the private segment first and then delete:
await prisma.segment.update({
// Check if segment exists before attempting to disconnect and delete
const existingSegment = await prisma.segment.findUnique({
where: { id: segment.id },
data: {
surveys: {
disconnect: {
id: surveyId,
},
},
},
});
// delete the private segment:
await prisma.segment.delete({
where: {
id: segment.id,
},
});
if (existingSegment) {
// disconnect the private segment first and then delete:
await prisma.segment.update({
where: { id: segment.id },
data: {
surveys: {
disconnect: {
id: surveyId,
},
},
},
});
// delete the private segment:
await prisma.segment.delete({
where: {
id: segment.id,
},
});
}
} else {
await prisma.survey.update({
where: {

View File

@@ -17,6 +17,7 @@ vi.mock("@formbricks/database", () => ({
update: vi.fn(),
},
segment: {
findUnique: vi.fn(),
update: vi.fn(),
delete: vi.fn(),
},
@@ -154,6 +155,16 @@ describe("Survey Editor Library Tests", () => {
beforeEach(() => {
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({
id: "segment1",
environmentId: "env123",

View File

@@ -98,54 +98,96 @@ export const updateSurvey = async (updatedSurvey: TSurvey): Promise<TSurvey> =>
}
try {
// update the segment:
let updatedInput: Prisma.SegmentUpdateInput = {
...segment,
surveys: undefined,
};
if (segment.surveys) {
updatedInput = {
...segment,
surveys: {
connect: segment.surveys.map((surveyId) => ({ id: surveyId })),
},
};
}
await prisma.segment.update({
// Check if segment exists to handle race conditions where it may have been deleted
const existingSegment = await prisma.segment.findUnique({
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) {
logger.error(error, "Error updating survey");
throw new Error("Error updating survey");
}
} else {
if (segment.isPrivate) {
// disconnect the private segment first and then delete:
await prisma.segment.update({
// Check if segment exists before attempting to disconnect and delete
const existingSegment = await prisma.segment.findUnique({
where: { id: segment.id },
data: {
surveys: {
disconnect: {
id: surveyId,
},
},
},
});
// delete the private segment:
await prisma.segment.delete({
where: {
id: segment.id,
},
});
if (existingSegment) {
// disconnect the private segment first and then delete:
await prisma.segment.update({
where: { id: segment.id },
data: {
surveys: {
disconnect: {
id: surveyId,
},
},
},
});
// delete the private segment:
await prisma.segment.delete({
where: {
id: segment.id,
},
});
}
} else {
await prisma.survey.update({
where: {