Skip to content

Commit c7c8564

Browse files
authored
fix(insights): convert string filter values to numbers for integer columns (calcom#24730)
## What does this PR do? Fixes PostgreSQL error "operator does not exist: integer = text" when filtering by `bookingStatusOrder` and `bookingUserId` fields. Frontend sends filter values as strings, but these database columns are integers requiring type conversion. Changes: - Convert `bookingStatusOrder` filter values from strings to numbers - Convert `bookingUserId` filter values from strings to numbers - Update tests to use numeric values for `bookingStatusOrder` filters - Add test for string-to-number conversion in `bookingUserId` filter - Fix `Prisma.Sql` composition in 3 integration tests ## Mandatory Tasks (DO NOT REMOVE) - [x] I have self-reviewed the code (A decent size PR without self-review might be rejected). - [x] N/A - I have updated the developer docs in /docs if this PR makes changes that would require a [documentation change](https://cal.com/docs). If N/A, write N/A here and check the checkbox. - [x] I confirm automated tests are in place that prove my fix is effective or that my feature works. <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Converts string filter values to numbers for integer columns in Insights routing to prevent PostgreSQL type errors. Fixes filtering by bookingStatusOrder and bookingUserId when the frontend sends strings. - **Bug Fixes** - Cast bookingStatusOrder and bookingUserId filter values to numbers and use = ANY([...]). - Update tests to expect numeric arrays; add test for string-to-number conversion on bookingUserId. - Correct Prisma.sql composition in three integration tests before calling $queryRaw. <!-- End of auto-generated description by cubic. -->
1 parent b620581 commit c7c8564

2 files changed

Lines changed: 68 additions & 16 deletions

File tree

packages/features/insights/services/InsightsRoutingBaseService.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -710,7 +710,12 @@ export class InsightsRoutingBaseService {
710710
// Extract booking status order filter
711711
const bookingStatusOrder = filtersMap["bookingStatusOrder"];
712712
if (bookingStatusOrder && isMultiSelectFilterValue(bookingStatusOrder.value)) {
713-
const statusCondition = makeSqlCondition(bookingStatusOrder.value);
713+
// Convert string values to numbers for integer column
714+
const integerFilterValue = {
715+
...bookingStatusOrder.value,
716+
data: bookingStatusOrder.value.data.map((order) => Number(order)),
717+
};
718+
const statusCondition = makeSqlCondition(integerFilterValue);
714719
if (statusCondition) {
715720
conditions.push(Prisma.sql`rfrd."bookingStatusOrder" ${statusCondition}`);
716721
}
@@ -764,7 +769,15 @@ export class InsightsRoutingBaseService {
764769
// Extract member user IDs filter (multi-select)
765770
const memberUserIds = filtersMap["bookingUserId"];
766771
if (memberUserIds && isMultiSelectFilterValue(memberUserIds.value)) {
767-
conditions.push(Prisma.sql`rfrd."bookingUserId" = ANY(${memberUserIds.value.data})`);
772+
// Convert string values to numbers for integer column
773+
const integerFilterValue = {
774+
...memberUserIds.value,
775+
data: memberUserIds.value.data.map((id) => Number(id)),
776+
};
777+
const userIdCondition = makeSqlCondition(integerFilterValue);
778+
if (userIdCondition) {
779+
conditions.push(Prisma.sql`rfrd."bookingUserId" ${userIdCondition}`);
780+
}
768781
}
769782

770783
// Extract form ID filter (single-select)

packages/features/insights/services/InsightsRoutingService.integration-test.ts

Lines changed: 53 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -759,9 +759,10 @@ describe("InsightsRoutingService Integration Tests", () => {
759759
});
760760

761761
const baseConditions = await service.getBaseConditions();
762-
const results = await prisma.$queryRaw<Array<{ id: number }>>`
762+
const query = Prisma.sql`
763763
SELECT id FROM "RoutingFormResponseDenormalized" rfrd WHERE ${baseConditions}
764764
`;
765+
const results = await prisma.$queryRaw<Array<{ id: number }>>(query);
765766

766767
// Should only return the authorized user's form response
767768
expect(results).toHaveLength(2);
@@ -863,9 +864,10 @@ describe("InsightsRoutingService Integration Tests", () => {
863864
});
864865

865866
const baseConditions = await service.getBaseConditions();
866-
const results = await prisma.$queryRaw<Array<{ id: number }>>`
867+
const query = Prisma.sql`
867868
SELECT id FROM "RoutingFormResponseDenormalized" rfrd WHERE ${baseConditions}
868869
`;
870+
const results = await prisma.$queryRaw<Array<{ id: number }>>(query);
869871

870872
// Should only return the authorized user's form response
871873
expect(results).toHaveLength(1);
@@ -953,9 +955,10 @@ describe("InsightsRoutingService Integration Tests", () => {
953955
});
954956

955957
const baseConditions = await service.getBaseConditions();
956-
const results = await prisma.$queryRaw<Array<{ id: number }>>`
958+
const query = Prisma.sql`
957959
SELECT id FROM "RoutingFormResponseDenormalized" rfrd WHERE ${baseConditions}
958960
`;
961+
const results = await prisma.$queryRaw<Array<{ id: number }>>(query);
959962

960963
// Should return both form responses (original user's and team member's)
961964
expect(results).toHaveLength(2);
@@ -1070,7 +1073,7 @@ describe("InsightsRoutingService Integration Tests", () => {
10701073
id: "bookingStatusOrder",
10711074
value: {
10721075
type: ColumnFilterType.MULTI_SELECT,
1073-
data: ["pending", "accepted"],
1076+
data: ["2", "1"], // String values that will be converted to numbers
10741077
},
10751078
},
10761079
],
@@ -1081,7 +1084,7 @@ describe("InsightsRoutingService Integration Tests", () => {
10811084
expect(filterConditions).toEqual(
10821085
Prisma.sql`(rfrd."createdAt" >= ${defaultFilters.startDate}::timestamp AND rfrd."createdAt" <= ${
10831086
defaultFilters.endDate
1084-
}::timestamp) AND (rfrd."bookingStatusOrder" = ANY(${["pending", "accepted"]}))`
1087+
}::timestamp) AND (rfrd."bookingStatusOrder" = ANY(${[2, 1]}))`
10851088
);
10861089

10871090
await testData.cleanup();
@@ -1204,6 +1207,45 @@ describe("InsightsRoutingService Integration Tests", () => {
12041207
await testData.cleanup();
12051208
});
12061209

1210+
it("should filter by member user IDs with string values (multi-select)", async () => {
1211+
const testData = await createTestData({
1212+
teamRole: MembershipRole.OWNER,
1213+
orgRole: MembershipRole.OWNER,
1214+
});
1215+
1216+
const defaultFilters = createDefaultFilters();
1217+
const service = new InsightsRoutingService({
1218+
prisma,
1219+
options: {
1220+
scope: "user",
1221+
userId: testData.user.id,
1222+
orgId: testData.org.id,
1223+
teamId: undefined,
1224+
},
1225+
filters: {
1226+
...defaultFilters,
1227+
columnFilters: [
1228+
{
1229+
id: "bookingUserId",
1230+
value: {
1231+
type: ColumnFilterType.MULTI_SELECT,
1232+
data: [String(testData.user.id), "999"], // String values that will be converted to numbers
1233+
},
1234+
},
1235+
],
1236+
},
1237+
});
1238+
1239+
const filterConditions = await service.getFilterConditions();
1240+
expect(filterConditions).toEqual(
1241+
Prisma.sql`(rfrd."createdAt" >= ${defaultFilters.startDate}::timestamp AND rfrd."createdAt" <= ${
1242+
defaultFilters.endDate
1243+
}::timestamp) AND (rfrd."bookingUserId" = ANY(${[testData.user.id, 999]}))`
1244+
);
1245+
1246+
await testData.cleanup();
1247+
});
1248+
12071249
it("should filter by attendee name (text)", async () => {
12081250
const testData = await createTestData({
12091251
teamRole: MembershipRole.OWNER,
@@ -1450,7 +1492,7 @@ describe("InsightsRoutingService Integration Tests", () => {
14501492
id: "bookingStatusOrder",
14511493
value: {
14521494
type: ColumnFilterType.MULTI_SELECT,
1453-
data: ["pending"],
1495+
data: ["2"], // String value that will be converted to number (2 = PENDING)
14541496
},
14551497
},
14561498
{
@@ -1476,7 +1518,7 @@ describe("InsightsRoutingService Integration Tests", () => {
14761518
Prisma.sql`(((rfrd."createdAt" >= ${defaultFilters.startDate}::timestamp AND rfrd."createdAt" <= ${
14771519
defaultFilters.endDate
14781520
}::timestamp) AND (rfrd."bookingStatusOrder" = ANY(${[
1479-
"pending",
1521+
2,
14801522
]}))) AND (rfrd."bookingAssignmentReason" ILIKE ${`%manual%`})) AND (EXISTS (
14811523
SELECT 1 FROM "RoutingFormResponseField" rrf
14821524
WHERE rrf."responseId" = rfrd."id"
@@ -1556,7 +1598,7 @@ describe("InsightsRoutingService Integration Tests", () => {
15561598
id: "bookingStatusOrder",
15571599
value: {
15581600
type: ColumnFilterType.MULTI_SELECT,
1559-
data: ["pending", "accepted"],
1601+
data: ["2", "1"], // String values that will be converted to numbers (2=PENDING, 1=ACCEPTED)
15601602
},
15611603
},
15621604
],
@@ -1567,10 +1609,7 @@ describe("InsightsRoutingService Integration Tests", () => {
15671609
expect(filterConditions).toEqual(
15681610
Prisma.sql`((rfrd."createdAt" >= ${defaultFilters.startDate}::timestamp AND rfrd."createdAt" <= ${
15691611
defaultFilters.endDate
1570-
}::timestamp) AND (rfrd."bookingStatusOrder" = ANY(${[
1571-
"pending",
1572-
"accepted",
1573-
]}))) AND (rfrd."formId" = ${"form-456"})`
1612+
}::timestamp) AND (rfrd."bookingStatusOrder" = ANY(${[2, 1]}))) AND (rfrd."formId" = ${"form-456"})`
15741613
);
15751614

15761615
await testData.cleanup();
@@ -1600,7 +1639,7 @@ describe("InsightsRoutingService Integration Tests", () => {
16001639
id: "bookingStatusOrder", // System filter
16011640
value: {
16021641
type: ColumnFilterType.MULTI_SELECT,
1603-
data: ["pending"],
1642+
data: ["2"], // String value that will be converted to number (2 = PENDING)
16041643
},
16051644
},
16061645
{
@@ -1618,7 +1657,7 @@ describe("InsightsRoutingService Integration Tests", () => {
16181657
expect(filterConditions).toEqual(
16191658
Prisma.sql`((rfrd."createdAt" >= ${defaultFilters.startDate}::timestamp AND rfrd."createdAt" <= ${
16201659
defaultFilters.endDate
1621-
}::timestamp) AND (rfrd."bookingStatusOrder" = ANY(${["pending"]}))) AND (EXISTS (
1660+
}::timestamp) AND (rfrd."bookingStatusOrder" = ANY(${[2]}))) AND (EXISTS (
16221661
SELECT 1 FROM "RoutingFormResponseField" rrf
16231662
WHERE rrf."responseId" = rfrd."id"
16241663
AND rrf."fieldId" = ${customFieldId}

0 commit comments

Comments
 (0)