tianji/src/server/trpc/routers/telemetry.ts

434 lines
9.4 KiB
TypeScript
Raw Normal View History

2024-02-17 16:47:22 +00:00
import { z } from 'zod';
import {
OpenApiMetaInfo,
router,
workspaceOwnerProcedure,
workspaceProcedure,
} from '../trpc';
2024-02-27 12:36:56 +00:00
import {
EVENT_COLUMNS,
FILTER_COLUMNS,
OPENAPI_TAG,
SESSION_COLUMNS,
} from '../../utils/const';
2024-02-17 16:47:22 +00:00
import { prisma } from '../../model/_client';
import { TelemetryModelSchema } from '../../prisma/zod';
import { OpenApiMeta } from 'trpc-openapi';
2024-02-27 16:34:02 +00:00
import {
baseFilterSchema,
baseStatsSchema,
statsItemType,
} from '../../model/_schema/filter';
2024-02-27 12:36:56 +00:00
import {
getTelemetryPageview,
getTelemetryPageviewMetrics,
getTelemetrySession,
getTelemetrySessionMetrics,
2024-02-27 16:34:02 +00:00
getTelemetryStats,
2024-03-03 16:48:51 +00:00
getTelemetryUrlMetrics,
2024-02-27 12:36:56 +00:00
} from '../../model/telemetry';
import { BaseQueryFilters } from '../../utils/prisma';
2024-02-27 16:34:02 +00:00
import dayjs from 'dayjs';
2024-02-17 16:47:22 +00:00
export const telemetryRouter = router({
all: workspaceProcedure
.meta(
buildTelemetryOpenapi({
method: 'GET',
path: '/all',
})
)
.output(z.array(TelemetryModelSchema))
.query(async ({ input }) => {
const { workspaceId } = input;
const res = await prisma.telemetry.findMany({
where: {
workspaceId,
},
orderBy: {
updatedAt: 'desc',
},
});
2024-02-27 16:34:02 +00:00
return res;
}),
info: workspaceProcedure
.meta(
buildTelemetryOpenapi({
method: 'GET',
path: '/info',
})
)
.input(
z.object({
telemetryId: z.string(),
})
)
.output(TelemetryModelSchema.nullable())
.query(async ({ input }) => {
const { workspaceId, telemetryId } = input;
const res = await prisma.telemetry.findUnique({
where: {
workspaceId,
id: telemetryId,
},
});
2024-02-17 16:47:22 +00:00
return res;
}),
2024-04-14 07:47:55 +00:00
allEventCount: workspaceProcedure
.meta(
buildTelemetryOpenapi({
method: 'GET',
path: '/allEventCount',
})
)
.output(z.record(z.string(), z.number()))
.query(async ({ input }) => {
const { workspaceId } = input;
const res = await prisma.telemetryEvent.groupBy({
by: ['telemetryId'],
where: {
workspaceId,
},
_count: true,
});
return res.reduce<Record<string, number>>((prev, item) => {
if (item.telemetryId) {
prev[item.telemetryId] = item._count;
}
return prev;
}, {});
}),
2024-02-22 16:07:30 +00:00
eventCount: workspaceProcedure
.meta(
buildTelemetryOpenapi({
method: 'GET',
path: '/eventCount',
})
)
.input(
z.object({
telemetryId: z.string(),
})
)
.output(z.number())
.query(async ({ input }) => {
const { workspaceId, telemetryId } = input;
const count = await prisma.telemetryEvent.count({
where: {
workspaceId,
telemetryId,
},
});
return count;
}),
2024-02-17 16:47:22 +00:00
upsert: workspaceOwnerProcedure
.meta(
buildTelemetryOpenapi({
method: 'POST',
path: '/upsert',
})
)
.input(
z.object({
telemetryId: z.string().optional(),
name: z.string(),
})
)
.output(TelemetryModelSchema)
.mutation(async ({ input }) => {
const { workspaceId, telemetryId, name } = input;
if (telemetryId) {
return prisma.telemetry.update({
where: {
id: telemetryId,
workspaceId,
},
data: {
name,
},
});
} else {
return prisma.telemetry.create({
data: {
workspaceId,
name,
},
});
}
}),
2024-03-29 11:59:42 +00:00
delete: workspaceOwnerProcedure
.meta(
buildTelemetryOpenapi({
method: 'POST',
path: '/delete',
})
)
.input(
z.object({
telemetryId: z.string(),
})
)
.output(TelemetryModelSchema)
.mutation(async ({ input }) => {
const { workspaceId, telemetryId } = input;
return prisma.telemetry.delete({
where: {
id: telemetryId,
workspaceId,
},
});
}),
2024-02-27 12:36:56 +00:00
pageviews: workspaceProcedure
.meta(
buildTelemetryOpenapi({
method: 'GET',
path: '/pageviews',
})
)
.input(
z
.object({
telemetryId: z.string(),
startAt: z.number(),
endAt: z.number(),
unit: z.string().optional(),
})
.merge(baseFilterSchema.partial())
)
.output(z.object({ pageviews: z.any(), sessions: z.any() }))
.query(async ({ input }) => {
const {
telemetryId,
startAt,
endAt,
timezone,
url,
country,
region,
city,
} = input;
2024-02-27 12:36:56 +00:00
const startDate = new Date(startAt);
const endDate = new Date(endAt);
// const { startDate, endDate, unit } = await parseDateRange({
// websiteId,
// startAt: Number(startAt),
// endAt: Number(endAt),
// unit: String(input.unit),
// });
const filters = {
startDate,
endDate,
unit: input.unit,
timezone,
2024-02-27 12:36:56 +00:00
url,
country,
region,
city,
};
const [pageviews, sessions] = await Promise.all([
getTelemetryPageview(telemetryId, filters as BaseQueryFilters),
getTelemetrySession(telemetryId, filters as BaseQueryFilters),
]);
return {
pageviews,
sessions,
};
}),
metrics: workspaceProcedure
.meta(
buildTelemetryOpenapi({
method: 'GET',
path: '/metrics',
})
)
.input(
z
.object({
2024-03-03 16:48:51 +00:00
telemetryId: z.string(),
2024-03-13 17:26:02 +00:00
type: z.enum(['source', 'url', 'event', 'referrer', 'country']),
2024-02-27 12:36:56 +00:00
startAt: z.number(),
endAt: z.number(),
})
.merge(baseFilterSchema.partial())
)
.output(
z.array(
z.object({
x: z.string().nullable(),
y: z.number(),
})
)
)
.query(async ({ input }) => {
const {
telemetryId,
type,
startAt,
endAt,
timezone,
url,
country,
region,
city,
} = input;
2024-02-27 12:36:56 +00:00
const startDate = new Date(startAt);
const endDate = new Date(endAt);
// const { startDate, endDate } = await parseDateRange({
// websiteId,
// startAt,
// endAt,
// });
const filters = {
startDate,
endDate,
timezone,
2024-02-27 12:36:56 +00:00
url,
country,
region,
city,
};
2024-03-03 16:48:51 +00:00
if (type === 'source') {
const data = await getTelemetryUrlMetrics(telemetryId, filters);
return data.map((d) => ({ x: d.x, y: Number(d.y) }));
}
2024-02-27 12:36:56 +00:00
const column = FILTER_COLUMNS[type] || type;
if (SESSION_COLUMNS.includes(type)) {
const data = await getTelemetrySessionMetrics(
2024-03-03 16:48:51 +00:00
telemetryId,
2024-02-27 12:36:56 +00:00
column,
filters
);
return data.map((d) => ({ x: d.x, y: Number(d.y) }));
}
if (EVENT_COLUMNS.includes(type)) {
const data = await getTelemetryPageviewMetrics(
2024-03-03 16:48:51 +00:00
telemetryId,
2024-02-27 12:36:56 +00:00
column,
filters
);
return data.map((d) => ({ x: d.x, y: Number(d.y) }));
}
return [];
}),
2024-02-27 16:34:02 +00:00
stats: workspaceProcedure
.meta(
buildTelemetryOpenapi({
method: 'GET',
path: '/stats',
})
)
.input(
z
.object({
telemetryId: z.string(),
startAt: z.number(),
endAt: z.number(),
unit: z.string().optional(),
})
.merge(baseFilterSchema.partial())
)
.output(baseStatsSchema)
.query(async ({ input }) => {
const {
telemetryId,
timezone,
url,
country,
region,
city,
startAt,
endAt,
} = input;
const startDate = new Date(startAt);
const endDate = new Date(endAt);
// const { startDate, endDate, unit } = await parseDateRange({
// telemetryId,
// startAt: Number(startAt),
// endAt: Number(endAt),
// unit: input.unit,
// });
const diff = dayjs(endDate).diff(startDate, 'minutes');
const prevStartDate = dayjs(startDate).subtract(diff, 'minutes').toDate();
const prevEndDate = dayjs(endDate).subtract(diff, 'minutes').toDate();
const filters = {
startDate,
endDate,
timezone,
unit: input.unit,
url,
country,
region,
city,
} as BaseQueryFilters;
const [metrics, prevPeriod] = await Promise.all([
getTelemetryStats(telemetryId, {
...filters,
startDate,
endDate,
}),
getTelemetryStats(telemetryId, {
...filters,
startDate: prevStartDate,
endDate: prevEndDate,
}),
]);
2024-03-29 11:59:42 +00:00
const stats = Object.keys(metrics[0]).reduce(
(obj, key) => {
const current = Number(metrics[0][key]) || 0;
const prev = Number(prevPeriod[0][key]) || 0;
obj[key] = {
value: current,
prev,
};
return obj;
},
{} as Record<string, { value: number; prev: number }>
);
2024-02-27 16:34:02 +00:00
return baseStatsSchema.parse(stats);
}),
2024-02-17 16:47:22 +00:00
});
function buildTelemetryOpenapi(meta: OpenApiMetaInfo): OpenApiMeta {
return {
openapi: {
tags: [OPENAPI_TAG.TELEMETRY],
protect: true,
...meta,
path: `/workspace/{workspaceId}${meta.path}`,
},
};
}