-
Notifications
You must be signed in to change notification settings - Fork 12k
fix: Create RoutingFormResponseService to get field value from identifier
#22396
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
7de251e
Make identifier required
joeauyeung ec1b49d
Fallback to null if identifier isn't present
joeauyeung 1f1ce53
Type fix
joeauyeung 727108e
Type fixes
joeauyeung ae6d85d
Type fix
joeauyeung 1c7e7ce
Merge branch 'main' into add-form-identifier-to-response
joeauyeung ba25c08
Create `RoutingFormResponseRepository`
joeauyeung 80a995d
Create `RoutingFormResponseService`
joeauyeung 44914b2
Use repsotiories to find form value
joeauyeung 009236e
Merge branch 'main' into add-form-identifier-to-response
joeauyeung cbfafb6
Delete console.logs
joeauyeung b953a49
Undo change in `ZResponseInputSchema` schema
joeauyeung b9571aa
Type fix
joeauyeung ba9323c
Undo changes
joeauyeung 255fadb
fix: correct import path in RoutingFormResponseService to resolve run…
devin-ai-integration[bot] 6aa198c
Undo changes
joeauyeung 6a7266d
Update type
joeauyeung 1ce827b
Update typing
joeauyeung 153e569
Address feedback
joeauyeung 1c8a756
Address feedback
joeauyeung f5f048d
chore: Provide a suggestion for pr 22396, new structure (#22491)
emrysal 26b2bdf
Merge branch 'main' into add-form-identifier-to-response
emrysal e8a2d13
Factory included wrong calls
emrysal 4fff3d2
Fix test for findFieldValueByIdentifier
emrysal dfeffba
Add tests
joeauyeung d37869a
Fix test
joeauyeung a125b9f
Fix test
joeauyeung File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
38 changes: 38 additions & 0 deletions
38
packages/lib/server/repository/PrismaRoutingFormResponseRepository.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| import type { PrismaClient } from "@calcom/prisma"; | ||
| import prisma from "@calcom/prisma"; | ||
|
|
||
| import type { RoutingFormResponseRepositoryInterface } from "./RoutingFormResponseRepository.interface"; | ||
|
|
||
| export class PrismaRoutingFormResponseRepository implements RoutingFormResponseRepositoryInterface { | ||
| constructor(private readonly prismaClient: PrismaClient = prisma) {} | ||
|
|
||
| findByIdIncludeForm(id: number) { | ||
| return this.prismaClient.app_RoutingForms_FormResponse.findUnique({ | ||
| where: { | ||
| id, | ||
| }, | ||
| include: { | ||
| form: { | ||
| select: { | ||
| fields: true, | ||
| }, | ||
| }, | ||
| }, | ||
| }); | ||
| } | ||
|
|
||
| findByBookingUidIncludeForm(bookingUid: string) { | ||
| return this.prismaClient.app_RoutingForms_FormResponse.findUnique({ | ||
| where: { | ||
| routedToBookingUid: bookingUid, | ||
| }, | ||
| include: { | ||
| form: { | ||
| select: { | ||
| fields: true, | ||
| }, | ||
| }, | ||
| }, | ||
| }); | ||
| } | ||
| } |
11 changes: 11 additions & 0 deletions
11
packages/lib/server/repository/RoutingFormResponseRepository.interface.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| import type { App_RoutingForms_Form, App_RoutingForms_FormResponse } from "@prisma/client"; | ||
|
|
||
| export interface RoutingFormResponseRepositoryInterface { | ||
| findByIdIncludeForm( | ||
| id: number | ||
| ): Promise<(App_RoutingForms_FormResponse & { form: { fields: App_RoutingForms_Form["fields"] } }) | null>; | ||
|
|
||
| findByBookingUidIncludeForm( | ||
| bookingUid: string | ||
| ): Promise<(App_RoutingForms_FormResponse & { form: { fields: App_RoutingForms_Form["fields"] } }) | null>; | ||
| } |
94 changes: 94 additions & 0 deletions
94
packages/lib/server/service/routingForm/RoutingFormResponseDataFactory.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| import { describe, it, expect, beforeEach, vi } from "vitest"; | ||
|
|
||
| import type { RoutingFormResponseRepositoryInterface } from "../../repository/RoutingFormResponseRepository.interface"; | ||
| import { RoutingFormResponseDataFactory } from "./RoutingFormResponseDataFactory"; | ||
| import { parseRoutingFormResponse } from "./responseData/parseRoutingFormResponse"; | ||
|
|
||
| vi.mock("./responseData/parseRoutingFormResponse", () => ({ | ||
| parseRoutingFormResponse: vi.fn(), | ||
| })); | ||
|
|
||
| const mockLogger = { | ||
| getSubLogger: () => ({ | ||
| error: vi.fn(), | ||
| }), | ||
| }; | ||
|
|
||
| const mockRoutingFormResponseRepo: RoutingFormResponseRepositoryInterface = { | ||
| findByBookingUidIncludeForm: vi.fn(), | ||
| findByIdIncludeForm: vi.fn(), | ||
| }; | ||
|
|
||
| describe("RoutingFormResponseDataFactory", () => { | ||
| let factory: RoutingFormResponseDataFactory; | ||
|
|
||
| beforeEach(() => { | ||
| vi.clearAllMocks(); | ||
| factory = new RoutingFormResponseDataFactory({ | ||
| logger: mockLogger as any, | ||
| routingFormResponseRepo: mockRoutingFormResponseRepo, | ||
| }); | ||
| }); | ||
|
|
||
| describe("createWithBookingUid", () => { | ||
| it("should call parseRoutingFormResponse with correct data when form response is found", async () => { | ||
| const mockFormResponse = { | ||
| id: 1, | ||
| response: { name: "test" }, | ||
| form: { fields: [{ label: "name", type: "text" }] }, | ||
| }; | ||
| const bookingUid = "test-uid"; | ||
| vi.mocked(mockRoutingFormResponseRepo.findByBookingUidIncludeForm).mockResolvedValue( | ||
| mockFormResponse as any | ||
| ); | ||
|
|
||
| const result = await factory.createWithBookingUid(bookingUid); | ||
|
|
||
| expect(mockRoutingFormResponseRepo.findByBookingUidIncludeForm).toHaveBeenCalledWith(bookingUid); | ||
| expect(parseRoutingFormResponse).toHaveBeenCalledWith( | ||
| mockFormResponse.response, | ||
| mockFormResponse.form.fields | ||
| ); | ||
| }); | ||
|
|
||
| it("should throw an error if form response is not found", async () => { | ||
| const bookingUid = "test-uid"; | ||
| vi.mocked(mockRoutingFormResponseRepo.findByBookingUidIncludeForm).mockResolvedValue(null); | ||
|
|
||
| await expect(factory.createWithBookingUid(bookingUid)).rejects.toThrow("Form response not found"); | ||
|
|
||
| expect(mockRoutingFormResponseRepo.findByBookingUidIncludeForm).toHaveBeenCalledWith(bookingUid); | ||
| expect(parseRoutingFormResponse).not.toHaveBeenCalled(); | ||
| }); | ||
| }); | ||
|
|
||
| describe("createWithResponseId", () => { | ||
| it("should call parseRoutingFormResponse with correct data when form response is found", async () => { | ||
| const mockFormResponse = { | ||
| id: 1, | ||
| response: { email: "test@example.com" }, | ||
| form: { fields: [{ label: "email", type: "email" }] }, | ||
| }; | ||
| const responseId = 1; | ||
| vi.mocked(mockRoutingFormResponseRepo.findByIdIncludeForm).mockResolvedValue(mockFormResponse as any); | ||
|
|
||
| const result = await factory.createWithResponseId(responseId); | ||
|
|
||
| expect(mockRoutingFormResponseRepo.findByIdIncludeForm).toHaveBeenCalledWith(responseId); | ||
| expect(parseRoutingFormResponse).toHaveBeenCalledWith( | ||
| mockFormResponse.response, | ||
| mockFormResponse.form.fields | ||
| ); | ||
| }); | ||
|
|
||
| it("should throw an error if form response is not found", async () => { | ||
| const responseId = 1; | ||
| vi.mocked(mockRoutingFormResponseRepo.findByIdIncludeForm).mockResolvedValue(null); | ||
|
|
||
| await expect(factory.createWithResponseId(responseId)).rejects.toThrow("Form response not found"); | ||
|
|
||
| expect(mockRoutingFormResponseRepo.findByIdIncludeForm).toHaveBeenCalledWith(responseId); | ||
| expect(parseRoutingFormResponse).not.toHaveBeenCalled(); | ||
| }); | ||
| }); | ||
| }); | ||
43 changes: 43 additions & 0 deletions
43
packages/lib/server/service/routingForm/RoutingFormResponseDataFactory.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| import type logger from "@calcom/lib/logger"; | ||
|
|
||
| import type { RoutingFormResponseRepositoryInterface } from "../../repository/RoutingFormResponseRepository.interface"; | ||
| import { parseRoutingFormResponse } from "./responseData/parseRoutingFormResponse"; | ||
|
|
||
| interface Dependencies { | ||
| logger: typeof logger; | ||
| routingFormResponseRepo: RoutingFormResponseRepositoryInterface; | ||
| } | ||
|
|
||
| export class RoutingFormResponseDataFactory { | ||
| constructor(private readonly deps: Dependencies) {} | ||
|
|
||
| async createWithBookingUid(bookingUid: string) { | ||
| const log = this.deps.logger.getSubLogger({ | ||
| prefix: ["[routingFormFieldService]", { bookingUid }], | ||
| }); | ||
|
|
||
| const formResponse = await this.deps.routingFormResponseRepo.findByBookingUidIncludeForm(bookingUid); | ||
|
|
||
| if (!formResponse) { | ||
| log.error("Form response not found"); | ||
| throw new Error("Form response not found"); | ||
| } | ||
|
|
||
| return parseRoutingFormResponse(formResponse.response, formResponse.form.fields); | ||
| } | ||
|
|
||
| async createWithResponseId(responseId: number) { | ||
| const log = this.deps.logger.getSubLogger({ | ||
| prefix: ["[routingFormFieldService]", { responseId }], | ||
| }); | ||
|
|
||
| const formResponse = await this.deps.routingFormResponseRepo.findByIdIncludeForm(responseId); | ||
|
|
||
| if (!formResponse) { | ||
| log.error("Form response not found"); | ||
| throw new Error("Form response not found"); | ||
| } | ||
|
|
||
| return parseRoutingFormResponse(formResponse.response, formResponse.form.fields); | ||
| } | ||
| } |
33 changes: 33 additions & 0 deletions
33
packages/lib/server/service/routingForm/responseData/findFieldValueByIdentifier.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| import { describe, it, expect } from "vitest"; | ||
|
|
||
| import { findFieldValueByIdentifier } from "./findFieldValueByIdentifier"; | ||
| import type { RoutingFormResponseData } from "./types"; | ||
|
|
||
| describe("findFieldValueByIdentifier", () => { | ||
| const responseData: RoutingFormResponseData = { | ||
| response: { | ||
| "field-123": { value: "test@example.com" }, | ||
| "field-456": { value: "John Doe" }, | ||
| }, | ||
| fields: [ | ||
| { id: "field-123", label: "E-mail", identifier: "email", type: "text" }, | ||
| { id: "field-456", label: "Name", identifier: "name", type: "text" }, | ||
| ], | ||
| }; | ||
|
|
||
| it("returns the correct value for an existing field identifier", async () => { | ||
| const result = findFieldValueByIdentifier(responseData, "email"); | ||
| expect(result.success).toBe(true); | ||
| // @ts-expect-error we know data is defined here | ||
| expect(result.data).toBe("test@example.com"); | ||
| }); | ||
|
|
||
| it("throws an error and logs when identifier is not found", () => { | ||
| const invalidIdentifier = "unknown"; | ||
|
|
||
| const result = findFieldValueByIdentifier(responseData, invalidIdentifier); | ||
| expect(result.success).toBe(false); | ||
| // @ts-expect-error we know error is defined here | ||
| expect(result.error).toBe(`Field with identifier ${invalidIdentifier} not found`); | ||
| }); | ||
| }); |
21 changes: 21 additions & 0 deletions
21
packages/lib/server/service/routingForm/responseData/findFieldValueByIdentifier.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| import getFieldIdentifier from "@calcom/app-store/routing-forms/lib/getFieldIdentifier"; | ||
|
|
||
| import type { RoutingFormResponseData } from "./types"; | ||
|
|
||
| type FindFieldValueByIdentifierResult = | ||
| | { success: true; data: string | string[] | number | null } | ||
| | { success: false; error: string }; | ||
|
|
||
| export function findFieldValueByIdentifier( | ||
| data: RoutingFormResponseData, | ||
| identifier: string | ||
| ): FindFieldValueByIdentifierResult { | ||
| const field = data.fields.find((field) => getFieldIdentifier(field) === identifier); | ||
| if (!field) { | ||
| return { success: false, error: `Field with identifier ${identifier} not found` }; | ||
| } | ||
|
|
||
| const fieldValue = data.response[field.id]?.value; | ||
|
|
||
| return { success: true, data: fieldValue ?? null }; | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Configure the mock to return a value for consistent test behavior.
The
parseRoutingFormResponsemock should return a value to make the test assertions work correctly. The tests expectresultto be"parsedData"but the mock doesn't return anything.vi.mock("./responseData/parseRoutingFormResponse", () => ({ - parseRoutingFormResponse: vi.fn(), + parseRoutingFormResponse: vi.fn().mockReturnValue("parsedData"), }));📝 Committable suggestion
🤖 Prompt for AI Agents