chore(root): migrate to biome
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { ch, TABLE_NAMES } from '../clickhouse-client';
|
||||
import { TABLE_NAMES, ch } from '../clickhouse-client';
|
||||
import type { IClickhouseBotEvent } from '../services/event.service';
|
||||
import type {
|
||||
Find,
|
||||
|
||||
@@ -17,20 +17,20 @@ export type OnCompleted<T> =
|
||||
export type ProcessQueue<T> = (data: QueueItem<T>[]) => Promise<number[]>;
|
||||
|
||||
export type Find<T, R = unknown> = (
|
||||
callback: (item: QueueItem<T>) => boolean
|
||||
callback: (item: QueueItem<T>) => boolean,
|
||||
) => Promise<R | null>;
|
||||
|
||||
export type FindMany<T, R = unknown> = (
|
||||
callback: (item: QueueItem<T>) => boolean
|
||||
callback: (item: QueueItem<T>) => boolean,
|
||||
) => Promise<R[]>;
|
||||
|
||||
const getError = (e: unknown) => {
|
||||
if (e instanceof Error) {
|
||||
return [
|
||||
'Name: ' + e.name,
|
||||
'Message: ' + e.message,
|
||||
'Stack: ' + e.stack,
|
||||
'Cause: ' + (e.cause ? String(e.cause) : ''),
|
||||
`Name: ${e.name}`,
|
||||
`Message: ${e.message}`,
|
||||
`Stack: ${e.stack}`,
|
||||
`Cause: ${e.cause ? String(e.cause) : ''}`,
|
||||
].join('\n');
|
||||
}
|
||||
return 'Unknown error';
|
||||
@@ -59,13 +59,13 @@ export abstract class RedisBuffer<T> {
|
||||
this.table = options.table;
|
||||
this.batchSize = options.batchSize;
|
||||
this.disableAutoFlush = options.disableAutoFlush;
|
||||
this.logger = createLogger({ name: `buffer` }).child({
|
||||
this.logger = createLogger({ name: 'buffer' }).child({
|
||||
table: this.table,
|
||||
});
|
||||
}
|
||||
|
||||
public getKey(name?: string) {
|
||||
const key = this.prefix + ':' + this.table;
|
||||
const key = `${this.prefix}:${this.table}`;
|
||||
if (name) {
|
||||
return `${key}:${name}`;
|
||||
}
|
||||
@@ -78,12 +78,12 @@ export abstract class RedisBuffer<T> {
|
||||
|
||||
const length = await getRedisCache().llen(this.getKey());
|
||||
this.logger.debug(
|
||||
`Inserted item into buffer ${this.table}. Current length: ${length}`
|
||||
`Inserted item into buffer ${this.table}. Current length: ${length}`,
|
||||
);
|
||||
|
||||
if (!this.disableAutoFlush && this.batchSize && length >= this.batchSize) {
|
||||
this.logger.info(
|
||||
`Buffer ${this.table} reached batch size (${this.batchSize}). Flushing...`
|
||||
`Buffer ${this.table} reached batch size (${this.batchSize}). Flushing...`,
|
||||
);
|
||||
this.flush();
|
||||
}
|
||||
@@ -99,7 +99,7 @@ export abstract class RedisBuffer<T> {
|
||||
}
|
||||
|
||||
this.logger.info(
|
||||
`Flushing ${queue.length} items from buffer ${this.table}`
|
||||
`Flushing ${queue.length} items from buffer ${this.table}`,
|
||||
);
|
||||
|
||||
try {
|
||||
@@ -112,19 +112,19 @@ export abstract class RedisBuffer<T> {
|
||||
if (this.onCompleted) {
|
||||
const res = await this.onCompleted(data);
|
||||
this.logger.info(
|
||||
`Completed processing ${res.length} items from buffer ${this.table}`
|
||||
`Completed processing ${res.length} items from buffer ${this.table}`,
|
||||
);
|
||||
return { count: res.length, data: res };
|
||||
}
|
||||
|
||||
this.logger.info(
|
||||
`Processed ${indexes.length} items from buffer ${this.table}`
|
||||
`Processed ${indexes.length} items from buffer ${this.table}`,
|
||||
);
|
||||
return { count: indexes.length, data: indexes };
|
||||
} catch (e) {
|
||||
this.logger.error(
|
||||
`Failed to process queue while flushing buffer ${this.table}:`,
|
||||
e
|
||||
e,
|
||||
);
|
||||
const timestamp = new Date().getTime();
|
||||
await getRedisCache().hset(this.getKey(`failed:${timestamp}`), {
|
||||
@@ -133,13 +133,13 @@ export abstract class RedisBuffer<T> {
|
||||
retries: 0,
|
||||
});
|
||||
this.logger.warn(
|
||||
`Stored ${queue.length} failed items in ${this.getKey(`failed:${timestamp}`)}`
|
||||
`Stored ${queue.length} failed items in ${this.getKey(`failed:${timestamp}`)}`,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
this.logger.error(
|
||||
`Failed to get queue while flushing buffer ${this.table}:`,
|
||||
e
|
||||
e,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -152,7 +152,7 @@ export abstract class RedisBuffer<T> {
|
||||
multi.lrem(this.getKey(), 0, DELETE);
|
||||
await multi.exec();
|
||||
this.logger.debug(
|
||||
`Deleted ${indexes.length} items from buffer ${this.table}`
|
||||
`Deleted ${indexes.length} items from buffer ${this.table}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -165,7 +165,7 @@ export abstract class RedisBuffer<T> {
|
||||
}))
|
||||
.filter((item): item is QueueItem<T> => item.event !== null);
|
||||
this.logger.debug(
|
||||
`Retrieved ${result.length} items from buffer ${this.table}`
|
||||
`Retrieved ${result.length} items from buffer ${this.table}`,
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import SuperJSON from 'superjson';
|
||||
import { deepMergeObjects } from '@openpanel/common';
|
||||
import { getRedisCache, getRedisPub } from '@openpanel/redis';
|
||||
|
||||
import { ch, TABLE_NAMES } from '../clickhouse-client';
|
||||
import { TABLE_NAMES, ch } from '../clickhouse-client';
|
||||
import { transformEvent } from '../services/event.service';
|
||||
import type {
|
||||
IClickhouseEvent,
|
||||
@@ -22,7 +22,7 @@ import { RedisBuffer } from './buffer';
|
||||
|
||||
const sortOldestFirst = (
|
||||
a: QueueItem<IClickhouseEvent>,
|
||||
b: QueueItem<IClickhouseEvent>
|
||||
b: QueueItem<IClickhouseEvent>,
|
||||
) =>
|
||||
new Date(a.event.created_at).getTime() -
|
||||
new Date(b.event.created_at).getTime();
|
||||
@@ -37,25 +37,25 @@ export class EventBuffer extends RedisBuffer<IClickhouseEvent> {
|
||||
public onInsert?: OnInsert<IClickhouseEvent> | undefined = (event) => {
|
||||
getRedisPub().publish(
|
||||
'event:received',
|
||||
SuperJSON.stringify(transformEvent(event))
|
||||
SuperJSON.stringify(transformEvent(event)),
|
||||
);
|
||||
if (event.profile_id) {
|
||||
getRedisCache().set(
|
||||
`live:event:${event.project_id}:${event.profile_id}`,
|
||||
'',
|
||||
'EX',
|
||||
60 * 5
|
||||
60 * 5,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
public onCompleted?: OnCompleted<IClickhouseEvent> | undefined = (
|
||||
savedEvents
|
||||
savedEvents,
|
||||
) => {
|
||||
for (const event of savedEvents) {
|
||||
getRedisPub().publish(
|
||||
'event:saved',
|
||||
SuperJSON.stringify(transformEvent(event))
|
||||
SuperJSON.stringify(transformEvent(event)),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ export class EventBuffer extends RedisBuffer<IClickhouseEvent> {
|
||||
queue
|
||||
.filter(
|
||||
(item) =>
|
||||
item.event.name !== 'screen_view' || item.event.device === 'server'
|
||||
item.event.name !== 'screen_view' || item.event.device === 'server',
|
||||
)
|
||||
.forEach((item) => {
|
||||
// Find the last event with data and merge it with the current event
|
||||
@@ -93,7 +93,7 @@ export class EventBuffer extends RedisBuffer<IClickhouseEvent> {
|
||||
|
||||
const event = deepMergeObjects<IClickhouseEvent>(
|
||||
omit(['properties', 'duration'], lastEventWithData?.event || {}),
|
||||
item.event
|
||||
item.event,
|
||||
);
|
||||
|
||||
if (lastEventWithData) {
|
||||
@@ -111,8 +111,8 @@ export class EventBuffer extends RedisBuffer<IClickhouseEvent> {
|
||||
(item) => item.event.session_id,
|
||||
queue.filter(
|
||||
(item) =>
|
||||
item.event.name === 'screen_view' && item.event.device !== 'server'
|
||||
)
|
||||
item.event.name === 'screen_view' && item.event.device !== 'server',
|
||||
),
|
||||
);
|
||||
|
||||
// Iterate over each group
|
||||
@@ -125,7 +125,7 @@ export class EventBuffer extends RedisBuffer<IClickhouseEvent> {
|
||||
const hasSessionEnd = queue.find(
|
||||
(item) =>
|
||||
item.event.name === 'session_end' &&
|
||||
item.event.session_id === sessionId
|
||||
item.event.session_id === sessionId,
|
||||
);
|
||||
|
||||
screenViews
|
||||
@@ -187,7 +187,7 @@ export class EventBuffer extends RedisBuffer<IClickhouseEvent> {
|
||||
};
|
||||
|
||||
public findMany: FindMany<IClickhouseEvent, IServiceEvent> = async (
|
||||
callback
|
||||
callback,
|
||||
) => {
|
||||
return this.getQueue(-1)
|
||||
.then((queue) => {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { mergeDeepRight } from 'ramda';
|
||||
import { toDots } from '@openpanel/common';
|
||||
import { getRedisCache } from '@openpanel/redis';
|
||||
|
||||
import { ch, chQuery, TABLE_NAMES } from '../clickhouse-client';
|
||||
import { TABLE_NAMES, ch, chQuery } from '../clickhouse-client';
|
||||
import type {
|
||||
IClickhouseProfile,
|
||||
IServiceProfile,
|
||||
@@ -35,13 +35,13 @@ export class ProfileBuffer extends RedisBuffer<IClickhouseProfile> {
|
||||
const cleanedQueue = this.combineQueueItems(queue);
|
||||
const redisProfiles = await this.getCachedProfiles(cleanedQueue);
|
||||
const dbProfiles = await this.fetchDbProfiles(
|
||||
cleanedQueue.filter((_, index) => !redisProfiles[index])
|
||||
cleanedQueue.filter((_, index) => !redisProfiles[index]),
|
||||
);
|
||||
|
||||
const values = this.createProfileValues(
|
||||
cleanedQueue,
|
||||
redisProfiles,
|
||||
dbProfiles
|
||||
dbProfiles,
|
||||
);
|
||||
|
||||
if (values.length > 0) {
|
||||
@@ -55,7 +55,7 @@ export class ProfileBuffer extends RedisBuffer<IClickhouseProfile> {
|
||||
private matchPartialObject(
|
||||
full: any,
|
||||
partial: any,
|
||||
options: { ignore: string[] }
|
||||
options: { ignore: string[] },
|
||||
): boolean {
|
||||
if (typeof partial !== 'object' || partial === null) {
|
||||
return partial === full;
|
||||
@@ -78,7 +78,7 @@ export class ProfileBuffer extends RedisBuffer<IClickhouseProfile> {
|
||||
}
|
||||
|
||||
private combineQueueItems(
|
||||
queue: QueueItem<IClickhouseProfile>[]
|
||||
queue: QueueItem<IClickhouseProfile>[],
|
||||
): QueueItem<IClickhouseProfile>[] {
|
||||
const itemsToClickhouse = new Map<string, QueueItem<IClickhouseProfile>>();
|
||||
|
||||
@@ -92,11 +92,11 @@ export class ProfileBuffer extends RedisBuffer<IClickhouseProfile> {
|
||||
}
|
||||
|
||||
private async getCachedProfiles(
|
||||
cleanedQueue: QueueItem<IClickhouseProfile>[]
|
||||
cleanedQueue: QueueItem<IClickhouseProfile>[],
|
||||
): Promise<(IClickhouseProfile | null)[]> {
|
||||
const redisCache = getRedisCache();
|
||||
const keys = cleanedQueue.map(
|
||||
(item) => `profile:${item.event.project_id}:${item.event.id}`
|
||||
(item) => `profile:${item.event.project_id}:${item.event.id}`,
|
||||
);
|
||||
const cachedProfiles = await redisCache.mget(...keys);
|
||||
return cachedProfiles.map((profile) => {
|
||||
@@ -109,7 +109,7 @@ export class ProfileBuffer extends RedisBuffer<IClickhouseProfile> {
|
||||
}
|
||||
|
||||
private async fetchDbProfiles(
|
||||
cleanedQueue: QueueItem<IClickhouseProfile>[]
|
||||
cleanedQueue: QueueItem<IClickhouseProfile>[],
|
||||
): Promise<IClickhouseProfile[]> {
|
||||
if (cleanedQueue.length === 0) {
|
||||
return [];
|
||||
@@ -122,21 +122,21 @@ export class ProfileBuffer extends RedisBuffer<IClickhouseProfile> {
|
||||
WHERE
|
||||
(id, project_id) IN (${cleanedQueue.map((item) => `('${item.event.id}', '${item.event.project_id}')`).join(',')})
|
||||
ORDER BY
|
||||
created_at DESC`
|
||||
created_at DESC`,
|
||||
);
|
||||
}
|
||||
|
||||
private createProfileValues(
|
||||
cleanedQueue: QueueItem<IClickhouseProfile>[],
|
||||
redisProfiles: (IClickhouseProfile | null)[],
|
||||
dbProfiles: IClickhouseProfile[]
|
||||
dbProfiles: IClickhouseProfile[],
|
||||
): IClickhouseProfile[] {
|
||||
return cleanedQueue
|
||||
.map((item, index) => {
|
||||
const cachedProfile = redisProfiles[index];
|
||||
const dbProfile = dbProfiles.find(
|
||||
(p) =>
|
||||
p.id === item.event.id && p.project_id === item.event.project_id
|
||||
p.id === item.event.id && p.project_id === item.event.project_id,
|
||||
);
|
||||
const profile = cachedProfile || dbProfile;
|
||||
|
||||
@@ -150,7 +150,7 @@ export class ProfileBuffer extends RedisBuffer<IClickhouseProfile> {
|
||||
},
|
||||
{
|
||||
ignore: ['created_at'],
|
||||
}
|
||||
},
|
||||
)
|
||||
) {
|
||||
console.log('Ignoring profile', item.event.id);
|
||||
@@ -182,14 +182,14 @@ export class ProfileBuffer extends RedisBuffer<IClickhouseProfile> {
|
||||
multi.setex(
|
||||
`profile:${value.project_id}:${value.id}`,
|
||||
60 * 30, // 30 minutes
|
||||
JSON.stringify(value)
|
||||
JSON.stringify(value),
|
||||
);
|
||||
});
|
||||
await multi.exec();
|
||||
}
|
||||
|
||||
private async insertIntoClickhouse(
|
||||
values: IClickhouseProfile[]
|
||||
values: IClickhouseProfile[],
|
||||
): Promise<void> {
|
||||
await ch.insert({
|
||||
table: TABLE_NAMES.profiles,
|
||||
@@ -199,7 +199,7 @@ export class ProfileBuffer extends RedisBuffer<IClickhouseProfile> {
|
||||
}
|
||||
|
||||
public findMany: FindMany<IClickhouseProfile, IServiceProfile> = async (
|
||||
callback
|
||||
callback,
|
||||
) => {
|
||||
return this.getQueue(-1)
|
||||
.then((queue) => {
|
||||
|
||||
@@ -53,19 +53,19 @@ export const ch = new Proxy(originalCh, {
|
||||
error.message.includes('socket hang up') ||
|
||||
error.message.includes('Timeout error'))
|
||||
) {
|
||||
childLogger.error(`Captured error`, {
|
||||
childLogger.error('Captured error', {
|
||||
error,
|
||||
});
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
try {
|
||||
// Retry once
|
||||
childLogger.info(`Retrying query`);
|
||||
childLogger.info('Retrying query');
|
||||
if (property in target) {
|
||||
// @ts-expect-error
|
||||
return await target[property](...args);
|
||||
}
|
||||
} catch (retryError) {
|
||||
logger.error(`Retry failed`, retryError);
|
||||
logger.error('Retry failed', retryError);
|
||||
throw retryError; // Rethrow or handle as needed
|
||||
}
|
||||
} else {
|
||||
@@ -85,7 +85,7 @@ export const ch = new Proxy(originalCh, {
|
||||
});
|
||||
|
||||
export async function chQueryWithMeta<T extends Record<string, any>>(
|
||||
query: string
|
||||
query: string,
|
||||
): Promise<ResponseJSON<T>> {
|
||||
const start = Date.now();
|
||||
const res = await ch.query({
|
||||
@@ -102,7 +102,7 @@ export async function chQueryWithMeta<T extends Record<string, any>>(
|
||||
...acc,
|
||||
[key]:
|
||||
item[key] && meta?.type.includes('Int')
|
||||
? parseFloat(item[key] as string)
|
||||
? Number.parseFloat(item[key] as string)
|
||||
: item[key],
|
||||
};
|
||||
}, {} as T);
|
||||
@@ -120,14 +120,14 @@ export async function chQueryWithMeta<T extends Record<string, any>>(
|
||||
}
|
||||
|
||||
export async function chQuery<T extends Record<string, any>>(
|
||||
query: string
|
||||
query: string,
|
||||
): Promise<T[]> {
|
||||
return (await chQueryWithMeta<T>(query)).data;
|
||||
}
|
||||
|
||||
export function formatClickhouseDate(
|
||||
_date: Date | string,
|
||||
skipTime = false
|
||||
skipTime = false,
|
||||
): string {
|
||||
const date = typeof _date === 'string' ? new Date(_date) : _date;
|
||||
if (skipTime) {
|
||||
@@ -153,5 +153,5 @@ export function toDate(str: string, interval?: IInterval) {
|
||||
}
|
||||
|
||||
export function convertClickhouseDateToJs(date: string) {
|
||||
return new Date(date.replace(' ', 'T') + 'Z');
|
||||
return new Date(`${date.replace(' ', 'T')}Z`);
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ import type {
|
||||
} from '@openpanel/validation';
|
||||
|
||||
import {
|
||||
formatClickhouseDate,
|
||||
TABLE_NAMES,
|
||||
formatClickhouseDate,
|
||||
toDate,
|
||||
} from '../clickhouse-client';
|
||||
import { createSqlBuilder } from '../sql-builder';
|
||||
@@ -32,7 +32,7 @@ export function getSelectPropertyKey(property: string) {
|
||||
if (property.startsWith('properties.')) {
|
||||
if (property.includes('*')) {
|
||||
return `arrayMap(x -> trim(x), mapValues(mapExtractKeyLike(properties, ${escape(
|
||||
transformPropertyKey(property)
|
||||
transformPropertyKey(property),
|
||||
)})))`;
|
||||
}
|
||||
return `properties['${property.replace(/^properties\./, '')}']`;
|
||||
@@ -64,7 +64,7 @@ export function getChartSql({
|
||||
sb.select.label_0 = `'*' as label_0`;
|
||||
}
|
||||
|
||||
sb.select.count = `count(*) as count`;
|
||||
sb.select.count = 'count(*) as count';
|
||||
switch (interval) {
|
||||
case 'minute': {
|
||||
sb.select.date = `toStartOfMinute(toTimeZone(created_at, '${getTimezoneFromDateString(startDate)}')) as date`;
|
||||
@@ -111,15 +111,16 @@ export function getChartSql({
|
||||
});
|
||||
|
||||
if (event.segment === 'user') {
|
||||
sb.select.count = `countDistinct(profile_id) as count`;
|
||||
sb.select.count = 'countDistinct(profile_id) as count';
|
||||
}
|
||||
|
||||
if (event.segment === 'session') {
|
||||
sb.select.count = `countDistinct(session_id) as count`;
|
||||
sb.select.count = 'countDistinct(session_id) as count';
|
||||
}
|
||||
|
||||
if (event.segment === 'user_average') {
|
||||
sb.select.count = `COUNT(*)::float / COUNT(DISTINCT profile_id)::float as count`;
|
||||
sb.select.count =
|
||||
'COUNT(*)::float / COUNT(DISTINCT profile_id)::float as count';
|
||||
}
|
||||
|
||||
if (event.segment === 'property_sum' && event.property) {
|
||||
@@ -136,7 +137,7 @@ export function getChartSql({
|
||||
sb.from = `(
|
||||
SELECT DISTINCT ON (profile_id) * from ${TABLE_NAMES.events} WHERE ${join(
|
||||
sb.where,
|
||||
' AND '
|
||||
' AND ',
|
||||
)}
|
||||
ORDER BY profile_id, created_at DESC
|
||||
) as subQuery`;
|
||||
@@ -157,9 +158,9 @@ export function getEventFiltersWhereClause(filters: IChartEventFilter[]) {
|
||||
|
||||
if (name === 'has_profile') {
|
||||
if (value.includes('true')) {
|
||||
where[id] = `profile_id != device_id`;
|
||||
where[id] = 'profile_id != device_id';
|
||||
} else {
|
||||
where[id] = `profile_id = device_id`;
|
||||
where[id] = 'profile_id = device_id';
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -211,7 +212,7 @@ export function getEventFiltersWhereClause(filters: IChartEventFilter[]) {
|
||||
where[id] = value
|
||||
.map(
|
||||
(val) =>
|
||||
`${whereFrom} LIKE ${escape(`%${String(val).trim()}%`)}`
|
||||
`${whereFrom} LIKE ${escape(`%${String(val).trim()}%`)}`,
|
||||
)
|
||||
.join(' OR ');
|
||||
}
|
||||
@@ -226,7 +227,7 @@ export function getEventFiltersWhereClause(filters: IChartEventFilter[]) {
|
||||
where[id] = value
|
||||
.map(
|
||||
(val) =>
|
||||
`${whereFrom} NOT LIKE ${escape(`%${String(val).trim()}%`)}`
|
||||
`${whereFrom} NOT LIKE ${escape(`%${String(val).trim()}%`)}`,
|
||||
)
|
||||
.join(' OR ');
|
||||
}
|
||||
@@ -240,7 +241,8 @@ export function getEventFiltersWhereClause(filters: IChartEventFilter[]) {
|
||||
} else {
|
||||
where[id] = value
|
||||
.map(
|
||||
(val) => `${whereFrom} LIKE ${escape(`${String(val).trim()}%`)}`
|
||||
(val) =>
|
||||
`${whereFrom} LIKE ${escape(`${String(val).trim()}%`)}`,
|
||||
)
|
||||
.join(' OR ');
|
||||
}
|
||||
@@ -254,7 +256,8 @@ export function getEventFiltersWhereClause(filters: IChartEventFilter[]) {
|
||||
} else {
|
||||
where[id] = value
|
||||
.map(
|
||||
(val) => `${whereFrom} LIKE ${escape(`%${String(val).trim()}`)}`
|
||||
(val) =>
|
||||
`${whereFrom} LIKE ${escape(`%${String(val).trim()}`)}`,
|
||||
)
|
||||
.join(' OR ');
|
||||
}
|
||||
@@ -268,7 +271,7 @@ export function getEventFiltersWhereClause(filters: IChartEventFilter[]) {
|
||||
} else {
|
||||
where[id] = value
|
||||
.map(
|
||||
(val) => `match(${whereFrom}, ${escape(String(val).trim())})`
|
||||
(val) => `match(${whereFrom}, ${escape(String(val).trim())})`,
|
||||
)
|
||||
.join(' OR ');
|
||||
}
|
||||
@@ -306,7 +309,7 @@ export function getEventFiltersWhereClause(filters: IChartEventFilter[]) {
|
||||
case 'doesNotContain': {
|
||||
where[id] = value
|
||||
.map(
|
||||
(val) => `${name} NOT LIKE ${escape(`%${String(val).trim()}%`)}`
|
||||
(val) => `${name} NOT LIKE ${escape(`%${String(val).trim()}%`)}`,
|
||||
)
|
||||
.join(' OR ');
|
||||
break;
|
||||
|
||||
@@ -8,11 +8,11 @@ import type { IChartEventFilter } from '@openpanel/validation';
|
||||
|
||||
import { botBuffer, eventBuffer } from '../buffers';
|
||||
import {
|
||||
TABLE_NAMES,
|
||||
ch,
|
||||
chQuery,
|
||||
convertClickhouseDateToJs,
|
||||
formatClickhouseDate,
|
||||
TABLE_NAMES,
|
||||
} from '../clickhouse-client';
|
||||
import type { EventMeta, Prisma } from '../prisma-client';
|
||||
import { db } from '../prisma-client';
|
||||
@@ -211,7 +211,7 @@ function maskString(str: string, mask = '*') {
|
||||
}
|
||||
|
||||
export function transformMinimalEvent(
|
||||
event: IServiceEvent
|
||||
event: IServiceEvent,
|
||||
): IServiceEventMinimal {
|
||||
return {
|
||||
id: event.id,
|
||||
@@ -242,7 +242,7 @@ export async function getLiveVisitors(projectId: string) {
|
||||
|
||||
export async function getEvents(
|
||||
sql: string,
|
||||
options: GetEventsOptions = {}
|
||||
options: GetEventsOptions = {},
|
||||
): Promise<IServiceEvent[]> {
|
||||
const events = await chQuery<IClickhouseEvent>(sql);
|
||||
const projectId = events[0]?.project_id;
|
||||
@@ -278,7 +278,7 @@ export async function createEvent(payload: IServiceCreateEventPayload) {
|
||||
payload.profileId = payload.deviceId;
|
||||
}
|
||||
console.log(
|
||||
`create event ${payload.name} for [deviceId]: ${payload.deviceId} [profileId]: ${payload.profileId} [projectId]: ${payload.projectId} [path]: ${payload.path}`
|
||||
`create event ${payload.name} for [deviceId]: ${payload.deviceId} [profileId]: ${payload.profileId} [projectId]: ${payload.projectId} [path]: ${payload.path}`,
|
||||
);
|
||||
|
||||
if (payload.profileId !== '') {
|
||||
@@ -389,7 +389,7 @@ export async function getEventList({
|
||||
os: true,
|
||||
browser: true,
|
||||
},
|
||||
incomingSelect ?? {}
|
||||
incomingSelect ?? {},
|
||||
);
|
||||
|
||||
if (select.id) {
|
||||
@@ -491,7 +491,7 @@ export async function getEventList({
|
||||
if (events && events.length > 0) {
|
||||
sb.where.events = `name IN (${join(
|
||||
events.map((event) => escape(event)),
|
||||
','
|
||||
',',
|
||||
)})`;
|
||||
}
|
||||
|
||||
@@ -537,7 +537,7 @@ export async function getEventsCount({
|
||||
if (events && events.length > 0) {
|
||||
sb.where.events = `name IN (${join(
|
||||
events.map((event) => escape(event)),
|
||||
','
|
||||
',',
|
||||
)})`;
|
||||
}
|
||||
|
||||
@@ -549,7 +549,7 @@ export async function getEventsCount({
|
||||
}
|
||||
|
||||
const res = await chQuery<{ count: number }>(
|
||||
getSql().replace('*', 'count(*) as count')
|
||||
getSql().replace('*', 'count(*) as count'),
|
||||
);
|
||||
|
||||
return res[0]?.count ?? 0;
|
||||
@@ -593,7 +593,7 @@ export async function getLastScreenViewFromProfileId({
|
||||
}
|
||||
|
||||
const eventInBuffer = await eventBuffer.find(
|
||||
(item) => item.event.profile_id === profileId
|
||||
(item) => item.event.profile_id === profileId,
|
||||
);
|
||||
|
||||
if (eventInBuffer) {
|
||||
@@ -602,7 +602,7 @@ export async function getLastScreenViewFromProfileId({
|
||||
|
||||
const [eventInDb] = profileId
|
||||
? await getEvents(
|
||||
`SELECT * FROM ${TABLE_NAMES.events} WHERE name = 'screen_view' AND profile_id = ${escape(profileId)} AND project_id = ${escape(projectId)} AND created_at >= now() - INTERVAL 30 MINUTE ORDER BY created_at DESC LIMIT 1`
|
||||
`SELECT * FROM ${TABLE_NAMES.events} WHERE name = 'screen_view' AND profile_id = ${escape(profileId)} AND project_id = ${escape(projectId)} AND created_at >= now() - INTERVAL 30 MINUTE ORDER BY created_at DESC LIMIT 1`,
|
||||
)
|
||||
: [];
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { db } from '../prisma-client';
|
||||
|
||||
export async function getId(
|
||||
tableName: 'project' | 'dashboard' | 'organization',
|
||||
name: string
|
||||
name: string,
|
||||
) {
|
||||
const newId = slug(name);
|
||||
if (!db[tableName]) {
|
||||
|
||||
@@ -7,10 +7,10 @@ import type { IChartEventFilter } from '@openpanel/validation';
|
||||
|
||||
import { profileBuffer } from '../buffers';
|
||||
import {
|
||||
TABLE_NAMES,
|
||||
ch,
|
||||
chQuery,
|
||||
formatClickhouseDate,
|
||||
TABLE_NAMES,
|
||||
} from '../clickhouse-client';
|
||||
import { createSqlBuilder } from '../sql-builder';
|
||||
|
||||
@@ -49,7 +49,7 @@ export async function getProfileById(id: string, projectId: string) {
|
||||
}
|
||||
|
||||
const [profile] = await chQuery<IClickhouseProfile>(
|
||||
`SELECT * FROM ${TABLE_NAMES.profiles} WHERE id = ${escape(String(id))} AND project_id = ${escape(projectId)} ORDER BY created_at DESC LIMIT 1`
|
||||
`SELECT * FROM ${TABLE_NAMES.profiles} WHERE id = ${escape(String(id))} AND project_id = ${escape(projectId)} ORDER BY created_at DESC LIMIT 1`,
|
||||
);
|
||||
|
||||
if (!profile) {
|
||||
@@ -82,7 +82,7 @@ export async function getProfiles(ids: string[], projectId: string) {
|
||||
WHERE
|
||||
project_id = ${escape(projectId)} AND
|
||||
id IN (${filteredIds.map((id) => escape(id)).join(',')})
|
||||
`
|
||||
`,
|
||||
);
|
||||
|
||||
return data.map(transformProfile);
|
||||
@@ -251,7 +251,7 @@ export async function getProfileId({
|
||||
profile_id: string;
|
||||
project_id: string;
|
||||
}>(
|
||||
`SELECT * FROM ${TABLE_NAMES.alias} WHERE project_id = '${projectId}' AND (alias = '${profileId}' OR profile_id = '${profileId}')`
|
||||
`SELECT * FROM ${TABLE_NAMES.alias} WHERE project_id = '${projectId}' AND (alias = '${profileId}' OR profile_id = '${profileId}')`,
|
||||
);
|
||||
|
||||
if (res[0]) {
|
||||
|
||||
@@ -87,7 +87,7 @@ export async function getCurrentProjects(organizationSlug: string) {
|
||||
|
||||
if (access.length > 0) {
|
||||
return projects.filter((project) =>
|
||||
access.some((a) => a.projectId === project.id)
|
||||
access.some((a) => a.projectId === project.id),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,20 +19,20 @@ export type IServiceReport = Awaited<ReturnType<typeof getReportById>>;
|
||||
|
||||
export function transformFilter(
|
||||
filter: Partial<IChartEventFilter>,
|
||||
index: number
|
||||
index: number,
|
||||
): IChartEventFilter {
|
||||
return {
|
||||
id: filter.id ?? alphabetIds[index] ?? 'A',
|
||||
name: filter.name ?? 'Unknown Filter',
|
||||
operator: filter.operator ?? 'is',
|
||||
value:
|
||||
typeof filter.value === 'string' ? [filter.value] : filter.value ?? [],
|
||||
typeof filter.value === 'string' ? [filter.value] : (filter.value ?? []),
|
||||
};
|
||||
}
|
||||
|
||||
export function transformReportEvent(
|
||||
event: Partial<IChartEvent>,
|
||||
index: number
|
||||
index: number,
|
||||
): IChartEvent {
|
||||
return {
|
||||
segment: event.segment ?? 'event',
|
||||
@@ -45,7 +45,7 @@ export function transformReportEvent(
|
||||
}
|
||||
|
||||
export function transformReport(
|
||||
report: DbReport
|
||||
report: DbReport,
|
||||
): IChartProps & { id: string } {
|
||||
return {
|
||||
id: report.id,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { escape } from 'sqlstring';
|
||||
|
||||
import { chQuery, TABLE_NAMES } from '../clickhouse-client';
|
||||
import { TABLE_NAMES, chQuery } from '../clickhouse-client';
|
||||
|
||||
type IGetWeekRetentionInput = {
|
||||
projectId: string;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { generateSalt } from '@openpanel/common';
|
||||
import { generateSalt } from '@openpanel/common/server';
|
||||
|
||||
import { db } from '../prisma-client';
|
||||
|
||||
@@ -61,17 +61,14 @@ export async function createInitialSalts() {
|
||||
} else {
|
||||
console.log('Error getting salts', error);
|
||||
if (retryCount < MAX_RETRIES) {
|
||||
const delay = BASE_DELAY * Math.pow(2, retryCount);
|
||||
const delay = BASE_DELAY * 2 ** retryCount;
|
||||
console.log(
|
||||
`Retrying in ${delay}ms... (Attempt ${retryCount + 1}/${MAX_RETRIES})`
|
||||
`Retrying in ${delay}ms... (Attempt ${retryCount + 1}/${MAX_RETRIES})`,
|
||||
);
|
||||
await new Promise((resolve) => setTimeout(resolve, delay));
|
||||
return createSaltsWithRetry(retryCount + 1);
|
||||
} else {
|
||||
throw new Error(
|
||||
`Failed to create salts after ${MAX_RETRIES} attempts`
|
||||
);
|
||||
}
|
||||
throw new Error(`Failed to create salts after ${MAX_RETRIES} attempts`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -27,16 +27,16 @@ export function createSqlBuilder() {
|
||||
};
|
||||
|
||||
const getWhere = () =>
|
||||
Object.keys(sb.where).length ? 'WHERE ' + join(sb.where, ' AND ') : '';
|
||||
Object.keys(sb.where).length ? `WHERE ${join(sb.where, ' AND ')}` : '';
|
||||
const getHaving = () =>
|
||||
Object.keys(sb.having).length ? 'HAVING ' + join(sb.having, ' AND ') : '';
|
||||
Object.keys(sb.having).length ? `HAVING ${join(sb.having, ' AND ')}` : '';
|
||||
const getFrom = () => `FROM ${sb.from}`;
|
||||
const getSelect = () =>
|
||||
'SELECT ' + (Object.keys(sb.select).length ? join(sb.select, ', ') : '*');
|
||||
`SELECT ${Object.keys(sb.select).length ? join(sb.select, ', ') : '*'}`;
|
||||
const getGroupBy = () =>
|
||||
Object.keys(sb.groupBy).length ? 'GROUP BY ' + join(sb.groupBy, ', ') : '';
|
||||
Object.keys(sb.groupBy).length ? `GROUP BY ${join(sb.groupBy, ', ')}` : '';
|
||||
const getOrderBy = () =>
|
||||
Object.keys(sb.orderBy).length ? 'ORDER BY ' + join(sb.orderBy, ', ') : '';
|
||||
Object.keys(sb.orderBy).length ? `ORDER BY ${join(sb.orderBy, ', ')}` : '';
|
||||
const getLimit = () => (sb.limit ? `LIMIT ${sb.limit}` : '');
|
||||
const getOffset = () => (sb.offset ? `OFFSET ${sb.offset}` : '');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user