diff --git a/apps/docs/app/developer-docs/api-sdk/page.mdx b/apps/docs/app/developer-docs/api-sdk/page.mdx index 5811a2c894..292423075e 100644 --- a/apps/docs/app/developer-docs/api-sdk/page.mdx +++ b/apps/docs/app/developer-docs/api-sdk/page.mdx @@ -79,27 +79,6 @@ Promise<{ id: string }, NetworkError | Error> -- Update Display - - - - -```javascript {{ title: 'Update Display Method Call'}} -await api.client.display.update( - displayId: "", - { - userId: "", // optional - responseId: "", // optional - }, -); -``` - -```javascript {{ title: 'Update Display Method Return Type' }} -Promise<{ }, NetworkError | Error]> -``` - - - ## Responses diff --git a/apps/web/app/api/v1/client/[environmentId]/displays/[displayId]/route.ts b/apps/web/app/api/v1/client/[environmentId]/displays/[displayId]/route.ts deleted file mode 100644 index e9fcdcfcf2..0000000000 --- a/apps/web/app/api/v1/client/[environmentId]/displays/[displayId]/route.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { responses } from "@/app/lib/api/response"; -import { transformErrorToDetails } from "@/app/lib/api/validator"; -import { updateDisplay } from "@formbricks/lib/display/service"; -import { ZDisplayUpdateInput } from "@formbricks/types/displays"; - -interface Context { - params: { - displayId: string; - environmentId: string; - }; -} - -export const OPTIONS = async (): Promise => { - return responses.successResponse({}, true); -}; - -export const PUT = async (request: Request, context: Context): Promise => { - const { displayId, environmentId } = context.params; - const jsonInput = await request.json(); - const inputValidation = ZDisplayUpdateInput.safeParse({ - ...jsonInput, - environmentId, - }); - - if (!inputValidation.success) { - return responses.badRequestResponse( - "Fields are missing or incorrectly formatted", - transformErrorToDetails(inputValidation.error), - true - ); - } - - try { - await updateDisplay(displayId, inputValidation.data); - return responses.successResponse({}, true); - } catch (error) { - console.error(error); - return responses.internalServerErrorResponse(error.message, true); - } -}; diff --git a/apps/web/app/s/[surveyId]/components/LinkSurvey.tsx b/apps/web/app/s/[surveyId]/components/LinkSurvey.tsx index e7c4017786..117b59fe62 100644 --- a/apps/web/app/s/[surveyId]/components/LinkSurvey.tsx +++ b/apps/web/app/s/[surveyId]/components/LinkSurvey.tsx @@ -278,6 +278,7 @@ export const LinkSurvey = ({ url: window.location.href, source: sourceParam || "", }, + displayId: surveyState.displayId, ...(Object.keys(hiddenFieldsRecord).length > 0 && { hiddenFields: hiddenFieldsRecord }), }); }} diff --git a/packages/api/README.md b/packages/api/README.md index c546b4e968..a283d79e25 100644 --- a/packages/api/README.md +++ b/packages/api/README.md @@ -37,18 +37,6 @@ The API client is now ready to be used across your project. It can be used to in }); ``` -- Update a Display - - ```ts - await api.client.display.update( - displayId: "", - { - userId: "", // optional - responseId: "", // optional - }, - ); - ``` - ### Response - Create a Response diff --git a/packages/api/src/api/client/display.ts b/packages/api/src/api/client/display.ts index 1b06a95524..a01da13d37 100644 --- a/packages/api/src/api/client/display.ts +++ b/packages/api/src/api/client/display.ts @@ -1,4 +1,4 @@ -import { type TDisplayCreateInput, type TDisplayUpdateInput } from "@formbricks/types/displays"; +import { type TDisplayCreateInput } from "@formbricks/types/displays"; import { type Result } from "@formbricks/types/error-handlers"; import { type NetworkError } from "@formbricks/types/errors"; import { makeRequest } from "../../utils/make-request"; @@ -17,16 +17,4 @@ export class DisplayAPI { ): Promise> { return makeRequest(this.apiHost, `/api/v1/client/${this.environmentId}/displays`, "POST", displayInput); } - - async update( - displayId: string, - displayInput: Omit - ): Promise> { - return makeRequest( - this.apiHost, - `/api/v1/client/${this.environmentId}/displays/${displayId}`, - "PUT", - displayInput - ); - } } diff --git a/packages/database/data-migrations/20240905120500_refactor_display_response_relationship/data-migration.ts b/packages/database/data-migrations/20240905120500_refactor_display_response_relationship/data-migration.ts new file mode 100644 index 0000000000..315025402f --- /dev/null +++ b/packages/database/data-migrations/20240905120500_refactor_display_response_relationship/data-migration.ts @@ -0,0 +1,96 @@ +/* eslint-disable no-console -- logging is allowed in migration scripts */ +import { PrismaClient } from "@prisma/client"; + +const prisma = new PrismaClient(); + +async function runMigration(): Promise { + await prisma.$transaction( + async (tx) => { + const startTime = Date.now(); + console.log("Starting data migration..."); + + // Fetch all displays + const displays = await tx.display.findMany({ + where: { + responseId: { + not: null, + }, + }, + select: { + id: true, + responseId: true, + }, + }); + + if (displays.length === 0) { + // Stop the migration if there are no Displays + console.log("No Displays found"); + return; + } + + console.log(`Total displays with responseId: ${displays.length.toString()}`); + + let totalResponseTransformed = 0; + let totalDisplaysDeleted = 0; + await Promise.all( + displays.map(async (display) => { + if (!display.responseId) { + return Promise.resolve(); + } + + const response = await tx.response.findUnique({ + where: { id: display.responseId }, + select: { id: true }, + }); + + if (response) { + totalResponseTransformed++; + return Promise.all([ + tx.response.update({ + where: { id: response.id }, + data: { display: { connect: { id: display.id } } }, + }), + tx.display.update({ + where: { id: display.id }, + data: { responseId: null }, + }), + ]); + } + + totalDisplaysDeleted++; + return tx.display.delete({ + where: { id: display.id }, + }); + }) + ); + + console.log(`${totalResponseTransformed.toString()} responses transformed`); + console.log(`${totalDisplaysDeleted.toString()} displays deleted`); + const endTime = Date.now(); + console.log(`Data migration completed. Total time: ${((endTime - startTime) / 1000).toString()}s`); + }, + { + timeout: 300000, // 5 minutes + } + ); +} + +function handleError(error: unknown): void { + console.error("An error occurred during migration:", error); + process.exit(1); +} + +function handleDisconnectError(): void { + console.error("Failed to disconnect Prisma client"); + process.exit(1); +} + +function main(): void { + runMigration() + .catch(handleError) + .finally(() => { + prisma.$disconnect().catch(handleDisconnectError); + }); +} + +main(); diff --git a/packages/database/migrations/20240917112456_add_display_id_to_response/migration.sql b/packages/database/migrations/20240917112456_add_display_id_to_response/migration.sql new file mode 100644 index 0000000000..d5d4decd5b --- /dev/null +++ b/packages/database/migrations/20240917112456_add_display_id_to_response/migration.sql @@ -0,0 +1,14 @@ +/* + Warnings: + + - A unique constraint covering the columns `[displayId]` on the table `Response` will be added. If there are existing duplicate values, this will fail. + +*/ +-- AlterTable +ALTER TABLE "Response" ADD COLUMN "displayId" TEXT; + +-- CreateIndex +CREATE UNIQUE INDEX "Response_displayId_key" ON "Response"("displayId"); + +-- AddForeignKey +ALTER TABLE "Response" ADD CONSTRAINT "Response_displayId_fkey" FOREIGN KEY ("displayId") REFERENCES "Display"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/packages/database/package.json b/packages/database/package.json index fac4e4898e..5e9917e268 100644 --- a/packages/database/package.json +++ b/packages/database/package.json @@ -47,7 +47,8 @@ "data-migration:fix-logic-end-destination": "ts-node ./data-migrations/20240806120500_fix-logic-end-destination/data-migration.ts", "data-migration:v2.4": "pnpm data-migration:segments-cleanup && pnpm data-migration:multiple-endings && pnpm data-migration:simplified-email-verification && pnpm data-migration:fix-logic-end-destination", "data-migration:remove-dismissed-value-inconsistency": "ts-node ./data-migrations/20240807120500_cta_consent_dismissed_inconsistency/data-migration.ts", - "data-migration:v2.5": "pnpm data-migration:remove-dismissed-value-inconsistency" + "data-migration:v2.5": "pnpm data-migration:remove-dismissed-value-inconsistency", + "data-migration:add-display-id-to-response": "ts-node ./data-migrations/20240905120500_refactor_display_response_relationship/data-migration.ts" }, "dependencies": { "@prisma/client": "^5.18.0", diff --git a/packages/database/schema.prisma b/packages/database/schema.prisma index fd3860c52e..268cb3371b 100644 --- a/packages/database/schema.prisma +++ b/packages/database/schema.prisma @@ -132,6 +132,8 @@ model Response { // singleUseId, used to prevent multiple responses singleUseId String? language String? + displayId String? @unique + display Display? @relation(fields: [displayId], references: [id]) @@unique([surveyId, singleUseId]) @@index([surveyId, createdAt]) // to determine monthly response count @@ -198,8 +200,9 @@ model Display { surveyId String person Person? @relation(fields: [personId], references: [id], onDelete: Cascade) personId String? - responseId String? @unique + responseId String? @unique //deprecated status DisplayStatus? + response Response? @@index([surveyId]) @@index([personId, createdAt]) diff --git a/packages/js-core/src/app/lib/widget.ts b/packages/js-core/src/app/lib/widget.ts index b7557ebcaf..79a0395959 100644 --- a/packages/js-core/src/app/lib/widget.ts +++ b/packages/js-core/src/app/lib/widget.ts @@ -193,6 +193,7 @@ const renderWidget = async ( action, }, hiddenFields, + displayId: surveyState.displayId, }); if (isNewResponse) { diff --git a/packages/js-core/src/website/lib/widget.ts b/packages/js-core/src/website/lib/widget.ts index e233004868..85b91be500 100644 --- a/packages/js-core/src/website/lib/widget.ts +++ b/packages/js-core/src/website/lib/widget.ts @@ -188,6 +188,7 @@ const renderWidget = async ( action, }, hiddenFields, + displayId: surveyState.displayId, }); if (isNewResponse) { diff --git a/packages/lib/display/service.ts b/packages/lib/display/service.ts index 3abbd8b265..66cccded77 100644 --- a/packages/lib/display/service.ts +++ b/packages/lib/display/service.ts @@ -8,12 +8,9 @@ import { TDisplay, TDisplayCreateInput, TDisplayFilters, - TDisplayUpdateInput, ZDisplayCreateInput, - ZDisplayUpdateInput, } from "@formbricks/types/displays"; import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/errors"; -import { TPerson } from "@formbricks/types/people"; import { cache } from "../cache"; import { ITEMS_PER_PAGE } from "../constants"; import { createPerson, getPersonByUserId } from "../person/service"; @@ -25,7 +22,6 @@ export const selectDisplay = { createdAt: true, updatedAt: true, surveyId: true, - responseId: true, personId: true, status: true, }; @@ -60,57 +56,6 @@ export const getDisplay = reactCache( )() ); -export const updateDisplay = async ( - displayId: string, - displayInput: TDisplayUpdateInput -): Promise => { - validateInputs([displayInput, ZDisplayUpdateInput.partial()]); - - let person: TPerson | null = null; - if (displayInput.userId) { - person = await getPersonByUserId(displayInput.environmentId, displayInput.userId); - if (!person) { - throw new ResourceNotFoundError("Person", displayInput.userId); - } - } - - try { - const data = { - ...(person?.id && { - person: { - connect: { - id: person.id, - }, - }, - }), - ...(displayInput.responseId && { - responseId: displayInput.responseId, - }), - }; - const display = await prisma.display.update({ - where: { - id: displayId, - }, - data, - select: selectDisplay, - }); - - displayCache.revalidate({ - id: display.id, - surveyId: display.surveyId, - }); - - return display; - } catch (error) { - console.error(error); - if (error instanceof Prisma.PrismaClientKnownRequestError) { - throw new DatabaseError(error.message); - } - - throw error; - } -}; - export const createDisplay = async (displayInput: TDisplayCreateInput): Promise => { validateInputs([displayInput, ZDisplayCreateInput]); @@ -234,34 +179,6 @@ export const getDisplaysByUserId = reactCache( )() ); -export const deleteDisplayByResponseId = async ( - responseId: string, - surveyId: string -): Promise => { - validateInputs([responseId, ZId], [surveyId, ZId]); - - try { - const display = await prisma.display.delete({ - where: { - responseId, - }, - select: selectDisplay, - }); - - displayCache.revalidate({ - id: display.id, - personId: display.personId, - surveyId, - }); - return display; - } catch (error) { - if (error instanceof Prisma.PrismaClientKnownRequestError) { - throw new DatabaseError(error.message); - } - throw error; - } -}; - export const getDisplayCountBySurveyId = reactCache( (surveyId: string, filters?: TDisplayFilters): Promise => cache( @@ -301,3 +218,29 @@ export const getDisplayCountBySurveyId = reactCache( } )() ); + +export const deleteDisplay = async (displayId: string): Promise => { + validateInputs([displayId, ZId]); + try { + const display = await prisma.display.delete({ + where: { + id: displayId, + }, + select: selectDisplay, + }); + + displayCache.revalidate({ + id: display.id, + personId: display.personId, + surveyId: display.surveyId, + }); + + return display; + } catch (error) { + if (error instanceof Prisma.PrismaClientKnownRequestError) { + throw new DatabaseError(error.message); + } + + throw error; + } +}; diff --git a/packages/lib/display/tests/display.test.ts b/packages/lib/display/tests/display.test.ts index 8a334117d5..0aed38ac48 100644 --- a/packages/lib/display/tests/display.test.ts +++ b/packages/lib/display/tests/display.test.ts @@ -4,24 +4,20 @@ import { mockDisplay, mockDisplayInput, mockDisplayInputWithUserId, - mockDisplayUpdate, mockDisplayWithPersonId, - mockDisplayWithResponseId, mockEnvironment, - mockResponseId, mockSurveyId, } from "./__mocks__/data.mock"; import { Prisma } from "@prisma/client"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { testInputValidation } from "vitestSetup"; -import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/errors"; +import { DatabaseError } from "@formbricks/types/errors"; import { createDisplay, - deleteDisplayByResponseId, + deleteDisplay, getDisplay, getDisplayCountBySurveyId, getDisplaysByPersonId, - updateDisplay, } from "../service"; beforeEach(() => { @@ -135,49 +131,13 @@ describe("Tests for createDisplay service", () => { }); }); -describe("Tests for updateDisplay Service", () => { +describe("Tests for delete display service", () => { describe("Happy Path", () => { - it("Updates a display (responded)", async () => { - prisma.display.update.mockResolvedValue(mockDisplayWithResponseId); - prisma.environment.findUnique.mockResolvedValue(mockEnvironment); + it("Deletes a display", async () => { + prisma.display.delete.mockResolvedValue(mockDisplay); - const display = await updateDisplay(mockDisplay.id, mockDisplayUpdate); - expect(display).toEqual(mockDisplayWithResponseId); - }); - }); - - describe("Sad Path", () => { - testInputValidation(updateDisplay, "123", "123"); - - it("Throws DatabaseError on PrismaClientKnownRequestError", async () => { - prisma.environment.findUnique.mockResolvedValue(mockEnvironment); - const mockErrorMessage = "Mock error message"; - const errToThrow = new Prisma.PrismaClientKnownRequestError(mockErrorMessage, { - code: "P2002", - clientVersion: "0.0.1", - }); - - prisma.display.update.mockRejectedValue(errToThrow); - - await expect(updateDisplay(mockDisplay.id, mockDisplayUpdate)).rejects.toThrow(DatabaseError); - }); - - it("Throws a generic Error for other unexpected issues", async () => { - const mockErrorMessage = "Mock error message"; - prisma.display.update.mockRejectedValue(new Error(mockErrorMessage)); - - await expect(updateDisplay(mockDisplay.id, mockDisplayUpdate)).rejects.toThrow(Error); - }); - }); -}); - -describe("Tests for deleteDisplayByResponseId service", () => { - describe("Happy Path", () => { - it("Deletes a display when a response associated to it is deleted", async () => { - prisma.display.delete.mockResolvedValue(mockDisplayWithResponseId); - - const display = await deleteDisplayByResponseId(mockResponseId, mockSurveyId); - expect(display).toEqual(mockDisplayWithResponseId); + const display = await deleteDisplay(mockDisplay.id); + expect(display).toEqual(mockDisplay); }); }); describe("Sad Path", () => { @@ -190,14 +150,14 @@ describe("Tests for deleteDisplayByResponseId service", () => { prisma.display.delete.mockRejectedValue(errToThrow); - await expect(deleteDisplayByResponseId(mockResponseId, mockSurveyId)).rejects.toThrow(DatabaseError); + await expect(deleteDisplay(mockDisplay.id)).rejects.toThrow(DatabaseError); }); it("Throws a generic Error for other exceptions", async () => { const mockErrorMessage = "Mock error message"; prisma.display.delete.mockRejectedValue(new Error(mockErrorMessage)); - await expect(deleteDisplayByResponseId(mockResponseId, mockSurveyId)).rejects.toThrow(Error); + await expect(deleteDisplay(mockDisplay.id)).rejects.toThrow(Error); }); }); }); diff --git a/packages/lib/response/service.ts b/packages/lib/response/service.ts index 7d7253e588..e2e800668a 100644 --- a/packages/lib/response/service.ts +++ b/packages/lib/response/service.ts @@ -22,7 +22,7 @@ import { getAttributes } from "../attribute/service"; import { cache } from "../cache"; import { IS_FORMBRICKS_CLOUD, ITEMS_PER_PAGE, WEBAPP_URL } from "../constants"; import { displayCache } from "../display/cache"; -import { deleteDisplayByResponseId, getDisplayCountBySurveyId } from "../display/service"; +import { deleteDisplay, getDisplayCountBySurveyId } from "../display/service"; import { getMonthlyOrganizationResponseCount, getOrganizationByEnvironmentId } from "../organization/service"; import { createPerson, getPersonByUserId } from "../person/service"; import { sendPlanLimitsReachedEventToPosthogWeekly } from "../posthogServer"; @@ -62,6 +62,7 @@ export const responseSelection = { personAttributes: true, singleUseId: true, language: true, + displayId: true, person: { select: { id: true, @@ -254,6 +255,7 @@ export const createResponse = async (responseInput: TResponseInput): Promise => tags: responsePrisma.tags.map((tagPrisma: { tag: TTag }) => tagPrisma.tag), }; - deleteDisplayByResponseId(responseId, response.surveyId); - + if (response.displayId) { + deleteDisplay(response.displayId); + } const survey = await getSurvey(response.surveyId); if (survey) { diff --git a/packages/lib/responseQueue.ts b/packages/lib/responseQueue.ts index 21c2df8465..bcb4f8f834 100644 --- a/packages/lib/responseQueue.ts +++ b/packages/lib/responseQueue.ts @@ -87,19 +87,11 @@ export class ResponseQueue { userId: this.surveyState.userId || null, singleUseId: this.surveyState.singleUseId || null, data: { ...responseUpdate.data, ...responseUpdate.hiddenFields }, + displayId: this.surveyState.displayId, }); if (!response.ok) { throw new Error("Could not create response"); } - if (this.surveyState.displayId) { - try { - await this.api.client.display.update(this.surveyState.displayId, { - responseId: response.data.id, - }); - } catch (error) { - console.error(`Failed to update display, proceeding with the response. ${error}`); - } - } this.surveyState.updateResponseId(response.data.id); if (this.config.setSurveyState) { this.config.setSurveyState(this.surveyState); diff --git a/packages/lib/survey/service.ts b/packages/lib/survey/service.ts index 4811142683..e6b9045fc2 100644 --- a/packages/lib/survey/service.ts +++ b/packages/lib/survey/service.ts @@ -36,6 +36,7 @@ import { capturePosthogEnvironmentEvent } from "../posthogServer"; import { productCache } from "../product/cache"; import { getProductByEnvironmentId } from "../product/service"; import { responseCache } from "../response/cache"; +import { getResponsesByPersonId } from "../response/service"; import { segmentCache } from "../segment/cache"; import { createSegment, deleteSegment, evaluateSegment, getSegment, updateSegment } from "../segment/service"; import { diffInDays } from "../utils/datetime"; @@ -1124,6 +1125,7 @@ export const getSyncSurveys = reactCache( } const displays = await getDisplaysByPersonId(person.id); + const responses = await getResponsesByPersonId(person.id); // filter surveys that meet the displayOption criteria surveys = surveys.filter((survey) => { @@ -1133,20 +1135,18 @@ export const getSyncSurveys = reactCache( case "displayOnce": return displays.filter((display) => display.surveyId === survey.id).length === 0; case "displayMultiple": - return ( - displays - .filter((display) => display.surveyId === survey.id) - .filter((display) => display.responseId).length === 0 - ); + if (!responses) return true; + else { + return responses.filter((response) => response.surveyId === survey.id).length === 0; + } case "displaySome": if (survey.displayLimit === null) { return true; } if ( - displays - .filter((display) => display.surveyId === survey.id) - .some((display) => display.responseId) + responses && + responses.filter((response) => response.surveyId === survey.id).length !== 0 ) { return false; } diff --git a/packages/lib/survey/tests/survey.test.ts b/packages/lib/survey/tests/survey.test.ts index 216af08b99..06da286338 100644 --- a/packages/lib/survey/tests/survey.test.ts +++ b/packages/lib/survey/tests/survey.test.ts @@ -1,4 +1,5 @@ import { prisma } from "../../__mocks__/database"; +import { mockResponseNote, mockResponseWithMockPerson } from "../../response/tests/__mocks__/data.mock"; import { Prisma } from "@prisma/client"; import { beforeEach, describe, expect, it } from "vitest"; import { testInputValidation } from "vitestSetup"; @@ -303,6 +304,9 @@ describe("Tests for getSyncSurveys", () => { it("Returns synced surveys", async () => { prisma.survey.findMany.mockResolvedValueOnce([mockSyncSurveyOutput]); prisma.person.findUnique.mockResolvedValueOnce(mockPrismaPerson); + prisma.response.findMany.mockResolvedValue([mockResponseWithMockPerson]); + prisma.responseNote.findMany.mockResolvedValue([mockResponseNote]); + const surveys = await getSyncSurveys(mockId, mockPrismaPerson.id, "desktop", { version: "1.7.0", }); diff --git a/packages/lib/surveyState.ts b/packages/lib/surveyState.ts index cb0e9fcce8..16443882bc 100644 --- a/packages/lib/surveyState.ts +++ b/packages/lib/surveyState.ts @@ -76,6 +76,7 @@ export class SurveyState { finished: responseUpdate.finished, ttc: responseUpdate.ttc, data: { ...this.responseAcc.data, ...responseUpdate.data }, + displayId: responseUpdate.displayId, }; } diff --git a/packages/react-native/src/survey-web-view.tsx b/packages/react-native/src/survey-web-view.tsx index bb77a53beb..3028080bc7 100644 --- a/packages/react-native/src/survey-web-view.tsx +++ b/packages/react-native/src/survey-web-view.tsx @@ -96,6 +96,7 @@ export function SurveyWebView({ survey }: SurveyWebViewProps): JSX.Element | und finished: responseUpdate.finished, language: responseUpdate.language === "default" ? getDefaultLanguageCode(survey) : responseUpdate.language, + displayId: surveyState.displayId, }); }; diff --git a/packages/types/displays.ts b/packages/types/displays.ts index 9943e99ff5..d9157b8e21 100644 --- a/packages/types/displays.ts +++ b/packages/types/displays.ts @@ -4,9 +4,8 @@ export const ZDisplay = z.object({ id: z.string().cuid2(), createdAt: z.date(), updatedAt: z.date(), - personId: z.string().cuid2().nullable(), - surveyId: z.string().cuid2(), - responseId: z.string().cuid2().nullable(), + personId: z.string().cuid().nullable(), + surveyId: z.string().cuid(), status: z.enum(["seen", "responded"]).nullable(), }); @@ -21,14 +20,6 @@ export const ZDisplayCreateInput = z.object({ export type TDisplayCreateInput = z.infer; -export const ZDisplayUpdateInput = z.object({ - environmentId: z.string().cuid2(), - userId: z.string().optional(), - responseId: z.string().cuid2().optional(), -}); - -export type TDisplayUpdateInput = z.infer; - export const ZDisplaysWithSurveyName = ZDisplay.extend({ surveyName: z.string(), }); diff --git a/packages/types/responses.ts b/packages/types/responses.ts index 5586053b1b..df81229770 100644 --- a/packages/types/responses.ts +++ b/packages/types/responses.ts @@ -226,6 +226,7 @@ export const ZResponse = z.object({ createdAt: z.date(), updatedAt: z.date(), surveyId: z.string().cuid2(), + displayId: z.string().nullish(), person: ZResponsePerson.nullable(), personAttributes: ZResponsePersonAttributes, finished: z.boolean(), @@ -246,6 +247,7 @@ export const ZResponseInput = z.object({ environmentId: z.string().cuid2(), surveyId: z.string().cuid2(), userId: z.string().nullish(), + displayId: z.string().nullish(), singleUseId: z.string().nullable().optional(), finished: z.boolean(), language: z.string().optional(), @@ -301,6 +303,7 @@ export const ZResponseUpdate = z.object({ }) .optional(), hiddenFields: ZResponseHiddenFieldValue.optional(), + displayId: z.string().nullish(), }); export type TResponseUpdate = z.infer;