Skip to content

Commit ccf93cb

Browse files
committed
feat: refactor insights SQL builders and update related tests
1 parent af46e73 commit ccf93cb

File tree

9 files changed

+443
-281
lines changed

9 files changed

+443
-281
lines changed

example/web/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"version": "0.1.0",
44
"private": true,
55
"scripts": {
6-
"dev": "next dev --turbopack",
6+
"dev": "PORT=7788 next dev --turbopack",
77
"build": "next build",
88
"start": "next start",
99
"lint": "next lint"

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"start": "cd src/server && cross-env NODE_ENV=production node ./dist/src/server/main.js",
1111
"start:docker": "pnpm start:docker:db && pnpm start",
1212
"start:docker:db": "cd src/server && pnpm db:migrate:apply && pnpm db:migrate:script",
13+
"example": "cd example/web && pnpm dev",
1314
"test": "vitest",
1415
"build": "pnpm build:tracker && pnpm build:app && pnpm build:geo",
1516
"build:static": "pnpm build:tracker && pnpm build:client && pnpm build:geo",
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
22

3-
exports[`buildInsightsSurveySql > groups > sql 1`] = `
3+
exports[`SurveyInsightsSqlBuilder > groups > sql 1`] = `
44
"select
5-
to_char(date_trunc('day', "SurveyResult"."createdAt" at time zone 'UTC'), 'YYYY-MM-DD') date,
6-
"payload" ->> 'rating' as "%rating" , count(1) as "$all_event"
7-
from "SurveyResult"
8-
9-
where "SurveyResult"."surveyId" = 'cm658i2tqw96upkejldn8rpbs' AND "SurveyResult"."createdAt" between '2025-02-10T16:00:00.000Z'::timestamptz and '2025-03-13T15:59:59.999Z'::timestamptz
10-
group by 1 , 2"
5+
to_char(date_trunc('day', "SurveyResult"."createdAt" at time zone 'UTC'), 'YYYY-MM-DD') date,
6+
"SurveyResult"."payload" ->> 'rating' as "%rating" , count(1) as "$all_event"
7+
from "SurveyResult"
8+
9+
where "SurveyResult"."surveyId" = 'cm658i2tqw96upkejldn8rpbs' AND "SurveyResult"."createdAt" between '2025-02-10T16:00:00.000Z'::timestamptz and '2025-03-13T15:59:59.999Z'::timestamptz
10+
group by 1 , 2"
1111
`;
1212

13-
exports[`buildInsightsSurveySql > groups with custom bucket > sql 1`] = `
13+
exports[`SurveyInsightsSqlBuilder > groups with custom bucket > sql 1`] = `
1414
"select
15-
to_char(date_trunc('day', "SurveyResult"."createdAt" at time zone 'UTC'), 'YYYY-MM-DD') date,
16-
("SurveyResult"."payload"->> 'rating') IN ('1' , '2' , '3') as "%rating|in list|1,2,3" , count(1) as "$all_event"
17-
from "SurveyResult"
18-
19-
where "SurveyResult"."surveyId" = 'cm658i2tqw96upkejldn8rpbs' AND "SurveyResult"."createdAt" between '2025-02-10T16:00:00.000Z'::timestamptz and '2025-03-13T15:59:59.999Z'::timestamptz
20-
group by 1 , 2"
15+
to_char(date_trunc('day', "SurveyResult"."createdAt" at time zone 'UTC'), 'YYYY-MM-DD') date,
16+
("SurveyResult"."payload"->> 'rating') IN ('1' , '2' , '3') as "%rating|in list|1,2,3" , count(1) as "$all_event"
17+
from "SurveyResult"
18+
19+
where "SurveyResult"."surveyId" = 'cm658i2tqw96upkejldn8rpbs' AND "SurveyResult"."createdAt" between '2025-02-10T16:00:00.000Z'::timestamptz and '2025-03-13T15:59:59.999Z'::timestamptz
20+
group by 1 , 2"
2121
`;

src/server/model/insights/__snapshots__/website.spec.ts.snap

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
22

3-
exports[`buildInsightsWebsiteSql > groups > sql 1`] = `
3+
exports[`WebsiteInsightsSqlBuilder > groups > sql 1`] = `
44
"select
55
to_char(date_trunc('day', "WebsiteEvent"."createdAt" at time zone 'UTC'), 'YYYY-MM-DD') date,
66
"WebsiteEventData"."numberValue" as "%number" , count(1) as "$all_event"
@@ -9,7 +9,7 @@ exports[`buildInsightsWebsiteSql > groups > sql 1`] = `
99
group by 1 , 2"
1010
`;
1111

12-
exports[`buildInsightsWebsiteSql > groups with custom bucket > sql 1`] = `
12+
exports[`WebsiteInsightsSqlBuilder > groups with custom bucket > sql 1`] = `
1313
"select
1414
to_char(date_trunc('day', "WebsiteEvent"."createdAt" at time zone 'UTC'), 'YYYY-MM-DD') date,
1515
"WebsiteEventData"."numberValue" != '1' as "%number|not equals|1" , count(1) as "$all_event"

src/server/model/insights/shared.ts

+104-15
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,71 @@ import {
99
} from '@tianji/shared';
1010
import { get } from 'lodash-es';
1111
import { castToDate, castToNumber, castToString } from '../../utils/cast.js';
12+
import dayjs from 'dayjs';
1213

13-
export function buildCommonFilterQueryOperator(
14-
type: FilterInfoType,
15-
_operator: string,
16-
value: FilterInfoValue | null,
17-
valueField: Prisma.Sql
18-
) {
19-
if (type === 'number') {
20-
const operator = _operator as FilterNumberOperator;
14+
export class InsightsSqlBuilder {
15+
protected getDateQuery(
16+
field: string,
17+
unit: string,
18+
timezone: string
19+
): Prisma.Sql {
20+
return Prisma.sql`to_char(date_trunc(${unit}, ${Prisma.raw(field)} at time zone ${timezone}), 'YYYY-MM-DD')`;
21+
}
22+
23+
protected buildGroupByText(length: number): string {
24+
return Array.from({ length })
25+
.map((_, i) => `${i + 1}`)
26+
.join(' , ');
27+
}
28+
29+
protected buildDateRangeQuery(
30+
field: string,
31+
startAt: number,
32+
endAt: number
33+
): Prisma.Sql {
34+
return Prisma.sql`${Prisma.raw(field)} between ${dayjs(startAt).toISOString()}::timestamptz and ${dayjs(endAt).toISOString()}::timestamptz`;
35+
}
36+
37+
public buildCommonFilterQueryOperator(
38+
type: FilterInfoType,
39+
_operator: string,
40+
value: FilterInfoValue | null,
41+
valueField: Prisma.Sql
42+
): Prisma.Sql {
43+
if (type === 'number') {
44+
return this.buildNumberFilterOperator(
45+
_operator as FilterNumberOperator,
46+
value,
47+
valueField
48+
);
49+
} else if (type === 'string') {
50+
return this.buildStringFilterOperator(
51+
_operator as FilterStringOperator,
52+
value,
53+
valueField
54+
);
55+
} else if (type === 'boolean') {
56+
return this.buildBooleanFilterOperator(
57+
_operator as FilterBooleanOperator,
58+
value,
59+
valueField
60+
);
61+
} else if (type === 'date') {
62+
return this.buildDateFilterOperator(
63+
_operator as FilterDateOperator,
64+
value,
65+
valueField
66+
);
67+
}
68+
69+
return Prisma.sql`1 = 1`;
70+
}
2171

72+
private buildNumberFilterOperator(
73+
operator: FilterNumberOperator,
74+
value: FilterInfoValue | null,
75+
valueField: Prisma.Sql
76+
): Prisma.Sql {
2277
if (operator === 'equals') {
2378
return Prisma.sql`${valueField} = ${castToNumber(value)}`;
2479
}
@@ -46,9 +101,15 @@ export function buildCommonFilterQueryOperator(
46101
if (operator === 'between') {
47102
return Prisma.sql`${valueField} BETWEEN ${castToNumber(get(value, '0'))} AND ${castToNumber(get(value, '1'))}`;
48103
}
49-
} else if (type === 'string') {
50-
const operator = _operator as FilterStringOperator;
51104

105+
return Prisma.sql`1 = 1`;
106+
}
107+
108+
private buildStringFilterOperator(
109+
operator: FilterStringOperator,
110+
value: FilterInfoValue | null,
111+
valueField: Prisma.Sql
112+
): Prisma.Sql {
52113
if (operator === 'equals') {
53114
return Prisma.sql`${valueField} = ${castToString(value)}`;
54115
}
@@ -67,25 +128,53 @@ export function buildCommonFilterQueryOperator(
67128
if (operator === 'not in list' && Array.isArray(value)) {
68129
return Prisma.sql`${valueField} NOT IN (${value.join(',')})`;
69130
}
70-
} else if (type === 'boolean') {
71-
const operator = _operator as FilterBooleanOperator;
72131

132+
return Prisma.sql`1 = 1`;
133+
}
134+
135+
private buildBooleanFilterOperator(
136+
operator: FilterBooleanOperator,
137+
value: FilterInfoValue | null,
138+
valueField: Prisma.Sql
139+
): Prisma.Sql {
73140
if (operator === 'equals') {
74141
return Prisma.sql`${valueField} = ${castToNumber(value)}`;
75142
}
76143
if (operator === 'not equals') {
77144
return Prisma.sql`${valueField} != ${castToNumber(value)}`;
78145
}
79-
} else if (type === 'date') {
80-
const operator = _operator as FilterDateOperator;
81146

147+
return Prisma.sql`1 = 1`;
148+
}
149+
150+
private buildDateFilterOperator(
151+
operator: FilterDateOperator,
152+
value: FilterInfoValue | null,
153+
valueField: Prisma.Sql
154+
): Prisma.Sql {
82155
if (operator === 'between') {
83156
return Prisma.sql`${valueField} BETWEEN ${castToDate(get(value, '0'))} AND ${castToDate(get(value, '1'))}`;
84157
}
85158
if (operator === 'in day') {
86159
return Prisma.sql`${valueField} = DATE(${castToDate(value)})`;
87160
}
161+
162+
return Prisma.sql`1 = 1`;
88163
}
164+
}
89165

90-
return Prisma.sql`1 = 1`;
166+
// 为了向后兼容,保留这个函数,但内部使用 InsightsSqlBuilder 的实现
167+
export function buildCommonFilterQueryOperator(
168+
type: FilterInfoType,
169+
_operator: string,
170+
value: FilterInfoValue | null,
171+
valueField: Prisma.Sql
172+
): Prisma.Sql {
173+
const builder = new InsightsSqlBuilder();
174+
return builder.buildCommonFilterQueryOperator(
175+
type,
176+
_operator,
177+
value,
178+
valueField
179+
);
91180
}

src/server/model/insights/survey.spec.ts

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { describe, expect, test } from 'vitest';
2-
import { buildInsightsSurveySql } from './survey.js';
2+
import { SurveyInsightsSqlBuilder } from './survey.js';
33
import { unwrapSQL } from '../../utils/prisma.js';
44

5-
describe('buildInsightsSurveySql', () => {
5+
describe('SurveyInsightsSqlBuilder', () => {
66
const insightId = 'cm658i2tqw96upkejldn8rpbs';
77
const insightType = 'survey';
88

99
test('groups', () => {
10-
const sql = buildInsightsSurveySql(
10+
const builder = new SurveyInsightsSqlBuilder(
1111
{
1212
insightId,
1313
insightType,
@@ -35,11 +35,12 @@ describe('buildInsightsSurveySql', () => {
3535
}
3636
);
3737

38+
const sql = builder.build();
3839
expect(unwrapSQL(sql)).toMatchSnapshot('sql');
3940
});
4041

4142
test('groups with custom bucket', () => {
42-
const sql = buildInsightsSurveySql(
43+
const builder = new SurveyInsightsSqlBuilder(
4344
{
4445
insightId,
4546
insightType,
@@ -73,6 +74,7 @@ describe('buildInsightsSurveySql', () => {
7374
}
7475
);
7576

77+
const sql = builder.build();
7678
expect(unwrapSQL(sql)).toMatchSnapshot('sql');
7779
});
7880
});

0 commit comments

Comments
 (0)