Feature/move list to client (#50)
This commit is contained in:
committed by
GitHub
parent
c2abdaadf2
commit
668434d246
@@ -343,18 +343,18 @@ export async function getEventList({
|
||||
sb.offset = Math.max(0, (cursor ?? 0) * take);
|
||||
sb.where.projectId = `project_id = ${escape(projectId)}`;
|
||||
|
||||
// sb.select.id = 'id';
|
||||
// sb.select.name = 'name';
|
||||
// sb.select.deviceId = 'device_id';
|
||||
// sb.select.profileId = 'profile_id';
|
||||
// sb.select.projectId = 'project_id';
|
||||
// sb.select.createdAt = 'created_at';
|
||||
// sb.select.path = 'path';
|
||||
// sb.select.duration = 'duration';
|
||||
// sb.select.city = 'city';
|
||||
// sb.select.country = 'country';
|
||||
// sb.select.os = 'os';
|
||||
// sb.select.browser = 'browser';
|
||||
sb.select.id = 'id';
|
||||
sb.select.name = 'name';
|
||||
sb.select.deviceId = 'device_id';
|
||||
sb.select.profileId = 'profile_id';
|
||||
sb.select.projectId = 'project_id';
|
||||
sb.select.createdAt = 'created_at';
|
||||
sb.select.path = 'path';
|
||||
sb.select.duration = 'duration';
|
||||
sb.select.city = 'city';
|
||||
sb.select.country = 'country';
|
||||
sb.select.os = 'os';
|
||||
sb.select.browser = 'browser';
|
||||
|
||||
if (profileId) {
|
||||
sb.where.deviceId = `device_id IN (SELECT device_id as did FROM ${TABLE_NAMES.events} WHERE profile_id = ${escape(profileId)} group by did)`;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { uniq } from 'ramda';
|
||||
import { omit, uniq } from 'ramda';
|
||||
import { escape } from 'sqlstring';
|
||||
|
||||
import { toObject } from '@openpanel/common';
|
||||
@@ -59,6 +59,8 @@ export async function getProfileById(id: string, projectId: string) {
|
||||
return transformProfile(profile);
|
||||
}
|
||||
|
||||
export const getProfileByIdCached = cacheable(getProfileById, 60 * 30);
|
||||
|
||||
interface GetProfileListOptions {
|
||||
projectId: string;
|
||||
take: number;
|
||||
@@ -122,6 +124,7 @@ export type IServiceProfile = Omit<
|
||||
createdAt: Date;
|
||||
isExternal: boolean;
|
||||
properties: Record<string, unknown> & {
|
||||
region?: string;
|
||||
country?: string;
|
||||
city?: string;
|
||||
os?: string;
|
||||
@@ -130,6 +133,10 @@ export type IServiceProfile = Omit<
|
||||
browser_version?: string;
|
||||
referrer_name?: string;
|
||||
referrer_type?: string;
|
||||
device?: string;
|
||||
brand?: string;
|
||||
model?: string;
|
||||
referrer?: string;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -167,7 +174,10 @@ export function transformProfile({
|
||||
firstName: first_name,
|
||||
lastName: last_name,
|
||||
isExternal: profile.is_external,
|
||||
properties: toObject(profile.properties),
|
||||
properties: omit(
|
||||
['browserVersion', 'osVersion'],
|
||||
toObject(profile.properties)
|
||||
),
|
||||
createdAt: new Date(created_at),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
sum,
|
||||
} from '@openpanel/common';
|
||||
import type { ISerieDataItem } from '@openpanel/common';
|
||||
import { alphabetIds } from '@openpanel/constants';
|
||||
import {
|
||||
chQuery,
|
||||
createSqlBuilder,
|
||||
@@ -523,7 +524,11 @@ export async function getChart(input: IChartInput) {
|
||||
uniq(pluck('name', input.events)).length !==
|
||||
pluck('name', input.events).length && series.length > 1;
|
||||
const final: FinalChart = {
|
||||
series: series.map((serie) => {
|
||||
series: series.map((serie, index) => {
|
||||
const eventIndex = input.events.findIndex(
|
||||
(event) => event.id === serie.event.id
|
||||
);
|
||||
const alphaId = alphabetIds[eventIndex];
|
||||
const previousSerie = previousSeries?.find(
|
||||
(prevSerie) => getSerieId(prevSerie) === getSerieId(serie)
|
||||
);
|
||||
@@ -541,10 +546,7 @@ export async function getChart(input: IChartInput) {
|
||||
return {
|
||||
id: getSerieId(serie),
|
||||
names: includeEventName
|
||||
? [
|
||||
`(${event.id || event.name}) ${serie.name[0]}`,
|
||||
...serie.name.slice(1),
|
||||
]
|
||||
? [`(${alphaId}) ${serie.name[0]}`, ...serie.name.slice(1)]
|
||||
: serie.name,
|
||||
event,
|
||||
metrics: {
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
import { TRPCError } from '@trpc/server';
|
||||
import { escape } from 'sqlstring';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { chQuery, convertClickhouseDateToJs, db } from '@openpanel/db';
|
||||
import {
|
||||
chQuery,
|
||||
convertClickhouseDateToJs,
|
||||
db,
|
||||
getEventList,
|
||||
getEvents,
|
||||
TABLE_NAMES,
|
||||
} from '@openpanel/db';
|
||||
import { zChartEventFilter } from '@openpanel/validation';
|
||||
|
||||
import { getProjectAccessCached } from '../access';
|
||||
import { TRPCAccessError } from '../errors';
|
||||
@@ -31,6 +40,72 @@ export const eventRouter = createTRPCRouter({
|
||||
});
|
||||
}),
|
||||
|
||||
byId: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
projectId: z.string(),
|
||||
})
|
||||
)
|
||||
.query(async ({ input: { id, projectId } }) => {
|
||||
const res = await getEvents(
|
||||
`SELECT * FROM ${TABLE_NAMES.events} WHERE id = ${escape(id)} AND project_id = ${escape(projectId)};`
|
||||
);
|
||||
|
||||
if (!res?.[0]) {
|
||||
throw new TRPCError({
|
||||
code: 'NOT_FOUND',
|
||||
message: 'Event not found',
|
||||
});
|
||||
}
|
||||
|
||||
return res[0];
|
||||
}),
|
||||
|
||||
events: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
projectId: z.string(),
|
||||
cursor: z.number().optional(),
|
||||
limit: z.number().default(8),
|
||||
profileId: z.string().optional(),
|
||||
take: z.number().default(50),
|
||||
events: z.array(z.string()).optional(),
|
||||
filters: z.array(zChartEventFilter).default([]),
|
||||
startDate: z.date().optional(),
|
||||
endDate: z.date().optional(),
|
||||
meta: z.boolean().optional(),
|
||||
profile: z.boolean().optional(),
|
||||
})
|
||||
)
|
||||
.query(async ({ input }) => getEventList(input)),
|
||||
conversions: publicProcedure
|
||||
.input(
|
||||
z.object({
|
||||
projectId: z.string(),
|
||||
})
|
||||
)
|
||||
.query(async ({ input: { projectId } }) => {
|
||||
const conversions = await db.eventMeta.findMany({
|
||||
where: {
|
||||
projectId,
|
||||
conversion: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (conversions.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return getEvents(
|
||||
`SELECT * FROM ${TABLE_NAMES.events} WHERE project_id = ${escape(projectId)} AND name IN (${conversions.map((c) => escape(c.name)).join(', ')}) ORDER BY created_at DESC LIMIT 20;`,
|
||||
{
|
||||
profile: true,
|
||||
meta: true,
|
||||
}
|
||||
);
|
||||
}),
|
||||
|
||||
bots: publicProcedure
|
||||
.input(
|
||||
z.object({
|
||||
|
||||
@@ -2,7 +2,13 @@ import { flatten, map, pipe, prop, sort, uniq } from 'ramda';
|
||||
import { escape } from 'sqlstring';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { chQuery, createSqlBuilder } from '@openpanel/db';
|
||||
import {
|
||||
chQuery,
|
||||
createSqlBuilder,
|
||||
getProfileList,
|
||||
getProfiles,
|
||||
TABLE_NAMES,
|
||||
} from '@openpanel/db';
|
||||
|
||||
import { createTRPCRouter, protectedProcedure } from '../trpc';
|
||||
|
||||
@@ -28,6 +34,46 @@ export const profileRouter = createTRPCRouter({
|
||||
)(properties);
|
||||
}),
|
||||
|
||||
list: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
projectId: z.string(),
|
||||
cursor: z.number().optional(),
|
||||
take: z.number().default(50),
|
||||
// filters: z.array(zChartEventFilter).default([]),
|
||||
})
|
||||
)
|
||||
.query(async ({ input: { projectId, cursor, take } }) => {
|
||||
return getProfileList({ projectId, cursor, take });
|
||||
}),
|
||||
|
||||
powerUsers: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
projectId: z.string(),
|
||||
cursor: z.number().optional(),
|
||||
take: z.number().default(50),
|
||||
// filters: z.array(zChartEventFilter).default([]),
|
||||
})
|
||||
)
|
||||
.query(async ({ input: { projectId, cursor, take } }) => {
|
||||
const res = await chQuery<{ profile_id: string; count: number }>(
|
||||
`SELECT profile_id, count(*) as count from ${TABLE_NAMES.events} where profile_id != '' and project_id = ${escape(projectId)} group by profile_id order by count() DESC LIMIT ${take} ${cursor ? `OFFSET ${cursor * take}` : ''}`
|
||||
);
|
||||
const profiles = await getProfiles(res.map((r) => r.profile_id));
|
||||
return (
|
||||
res
|
||||
.map((item) => {
|
||||
return {
|
||||
count: item.count,
|
||||
...(profiles.find((p) => p.id === item.profile_id)! ?? {}),
|
||||
};
|
||||
})
|
||||
// Make sure we return actual profiles
|
||||
.filter((item) => item.id)
|
||||
);
|
||||
}),
|
||||
|
||||
values: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
|
||||
@@ -18,6 +18,13 @@ export function objectToZodEnums<K extends string>(
|
||||
|
||||
export const mapKeys = objectToZodEnums;
|
||||
|
||||
export const zChartEventFilter = z.object({
|
||||
id: z.string().optional(),
|
||||
name: z.string(),
|
||||
operator: z.enum(objectToZodEnums(operators)),
|
||||
value: z.array(z.string().or(z.number()).or(z.boolean()).or(z.null())),
|
||||
});
|
||||
|
||||
export const zChartEvent = z.object({
|
||||
id: z.string().optional(),
|
||||
name: z.string(),
|
||||
@@ -32,14 +39,7 @@ export const zChartEvent = z.object({
|
||||
'property_sum',
|
||||
'property_average',
|
||||
]),
|
||||
filters: z.array(
|
||||
z.object({
|
||||
id: z.string().optional(),
|
||||
name: z.string(),
|
||||
operator: z.enum(objectToZodEnums(operators)),
|
||||
value: z.array(z.string().or(z.number()).or(z.boolean()).or(z.null())),
|
||||
})
|
||||
),
|
||||
filters: z.array(zChartEventFilter).default([]),
|
||||
});
|
||||
export const zChartBreakdown = z.object({
|
||||
id: z.string().optional(),
|
||||
|
||||
Reference in New Issue
Block a user