fix(api): ensure we always have profile in cache (before inserted to clickhouse)
This commit is contained in:
@@ -86,6 +86,10 @@ export default function AddNotificationRule({ rule }: Props) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const onSubmit: SubmitHandler<IForm> = (data) => {
|
const onSubmit: SubmitHandler<IForm> = (data) => {
|
||||||
|
if (!data.config.events[0]?.name) {
|
||||||
|
toast.error('At least one event is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
mutation.mutate(data);
|
mutation.mutate(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -183,6 +187,10 @@ export default function AddNotificationRule({ rule }: Props) {
|
|||||||
<code>{'{{properties.your.property}}'}</code> - Get the value
|
<code>{'{{properties.your.property}}'}</code> - Get the value
|
||||||
of a custom property
|
of a custom property
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<code>{'{{profile.firstName}}'}</code> - Get the value of a
|
||||||
|
profile property
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<div className="flex gap-x-2 flex-wrap">
|
<div className="flex gap-x-2 flex-wrap">
|
||||||
And many more...
|
And many more...
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { escape } from 'sqlstring';
|
|||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
import { DateTime, toDots } from '@openpanel/common';
|
import { DateTime, toDots } from '@openpanel/common';
|
||||||
import { cacheable, getCache } from '@openpanel/redis';
|
import { cacheable } from '@openpanel/redis';
|
||||||
import type { IChartEventFilter } from '@openpanel/validation';
|
import type { IChartEventFilter } from '@openpanel/validation';
|
||||||
|
|
||||||
import { botBuffer, eventBuffer, sessionBuffer } from '../buffers';
|
import { botBuffer, eventBuffer, sessionBuffer } from '../buffers';
|
||||||
@@ -19,7 +19,7 @@ import type { EventMeta, Prisma } from '../prisma-client';
|
|||||||
import { db } from '../prisma-client';
|
import { db } from '../prisma-client';
|
||||||
import { createSqlBuilder } from '../sql-builder';
|
import { createSqlBuilder } from '../sql-builder';
|
||||||
import { getEventFiltersWhereClause } from './chart.service';
|
import { getEventFiltersWhereClause } from './chart.service';
|
||||||
import type { IServiceProfile } from './profile.service';
|
import type { IServiceProfile, IServiceUpsertProfile } from './profile.service';
|
||||||
import { getProfileById, getProfiles, upsertProfile } from './profile.service';
|
import { getProfileById, getProfiles, upsertProfile } from './profile.service';
|
||||||
|
|
||||||
export type IImportedEvent = Omit<
|
export type IImportedEvent = Omit<
|
||||||
@@ -325,7 +325,7 @@ export async function createEvent(payload: IServiceCreateEventPayload) {
|
|||||||
await Promise.all([sessionBuffer.add(event), eventBuffer.add(event)]);
|
await Promise.all([sessionBuffer.add(event), eventBuffer.add(event)]);
|
||||||
|
|
||||||
if (payload.profileId) {
|
if (payload.profileId) {
|
||||||
const profile = {
|
const profile: IServiceUpsertProfile = {
|
||||||
id: String(payload.profileId),
|
id: String(payload.profileId),
|
||||||
isExternal: payload.profileId !== payload.deviceId,
|
isExternal: payload.profileId !== payload.deviceId,
|
||||||
projectId: payload.projectId,
|
projectId: payload.projectId,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { omit, uniq } from 'ramda';
|
import { omit, uniq } from 'ramda';
|
||||||
import { escape } from 'sqlstring';
|
import { escape } from 'sqlstring';
|
||||||
|
|
||||||
import { toObject } from '@openpanel/common';
|
import { strip, toObject } from '@openpanel/common';
|
||||||
import { cacheable } from '@openpanel/redis';
|
import { cacheable } from '@openpanel/redis';
|
||||||
import type { IChartEventFilter } from '@openpanel/validation';
|
import type { IChartEventFilter } from '@openpanel/validation';
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ export async function getProfileById(id: string, projectId: string) {
|
|||||||
return transformProfile(profile);
|
return transformProfile(profile);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getProfileByIdCached = getProfileById; //cacheable(getProfileById, 60 * 30);
|
export const getProfileByIdCached = cacheable(getProfileById, 60 * 30);
|
||||||
|
|
||||||
interface GetProfileListOptions {
|
interface GetProfileListOptions {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
@@ -142,14 +142,15 @@ export async function getProfileListCount({
|
|||||||
return data[0]?.count ?? 0;
|
return data[0]?.count ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IServiceProfile = Omit<
|
export type IServiceProfile = {
|
||||||
IClickhouseProfile,
|
id: string;
|
||||||
'created_at' | 'properties' | 'first_name' | 'last_name' | 'is_external'
|
email: string;
|
||||||
> & {
|
avatar: string;
|
||||||
firstName: string;
|
firstName: string;
|
||||||
lastName: string;
|
lastName: string;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
isExternal: boolean;
|
isExternal: boolean;
|
||||||
|
projectId: string;
|
||||||
properties: Record<string, unknown> & {
|
properties: Record<string, unknown> & {
|
||||||
region?: string;
|
region?: string;
|
||||||
country?: string;
|
country?: string;
|
||||||
@@ -197,14 +198,15 @@ export function transformProfile({
|
|||||||
...profile
|
...profile
|
||||||
}: IClickhouseProfile): IServiceProfile {
|
}: IClickhouseProfile): IServiceProfile {
|
||||||
return {
|
return {
|
||||||
...profile,
|
|
||||||
firstName: first_name,
|
firstName: first_name,
|
||||||
lastName: last_name,
|
lastName: last_name,
|
||||||
isExternal: profile.is_external,
|
isExternal: profile.is_external,
|
||||||
properties: profile.properties
|
properties: toObject(profile.properties),
|
||||||
? omit(['browserVersion', 'osVersion'], toObject(profile.properties))
|
|
||||||
: {},
|
|
||||||
createdAt: new Date(created_at),
|
createdAt: new Date(created_at),
|
||||||
|
projectId: profile.project_id,
|
||||||
|
id: profile.id,
|
||||||
|
email: profile.email,
|
||||||
|
avatar: profile.avatar,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,18 +223,22 @@ export async function upsertProfile(
|
|||||||
}: IServiceUpsertProfile,
|
}: IServiceUpsertProfile,
|
||||||
isFromEvent = false,
|
isFromEvent = false,
|
||||||
) {
|
) {
|
||||||
return profileBuffer.add(
|
const profile: IClickhouseProfile = {
|
||||||
{
|
id,
|
||||||
id,
|
first_name: firstName || '',
|
||||||
first_name: firstName!,
|
last_name: lastName || '',
|
||||||
last_name: lastName!,
|
email: email || '',
|
||||||
email: email!,
|
avatar: avatar || '',
|
||||||
avatar: avatar!,
|
properties: strip((properties as Record<string, string | undefined>) || {}),
|
||||||
properties: properties as Record<string, string | undefined>,
|
project_id: projectId,
|
||||||
project_id: projectId,
|
created_at: formatClickhouseDate(new Date()),
|
||||||
created_at: formatClickhouseDate(new Date()),
|
is_external: isExternal,
|
||||||
is_external: isExternal,
|
};
|
||||||
},
|
|
||||||
isFromEvent,
|
if (!isFromEvent) {
|
||||||
);
|
// Save to cache directly since the profile might be used before its saved in clickhouse
|
||||||
|
getProfileByIdCached.set(id, projectId)(transformProfile(profile));
|
||||||
|
}
|
||||||
|
|
||||||
|
return profileBuffer.add(profile, isFromEvent);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,6 +87,12 @@ export function cacheable<T extends (...args: any) => any>(
|
|||||||
const key = getKey(...args);
|
const key = getKey(...args);
|
||||||
return getRedisCache().del(key);
|
return getRedisCache().del(key);
|
||||||
};
|
};
|
||||||
|
cachedFn.set =
|
||||||
|
(...args: Parameters<T>) =>
|
||||||
|
async (payload: Awaited<ReturnType<T>>) => {
|
||||||
|
const key = getKey(...args);
|
||||||
|
return getRedisCache().setex(key, expireInSec, JSON.stringify(payload));
|
||||||
|
};
|
||||||
|
|
||||||
return cachedFn;
|
return cachedFn;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,6 +121,7 @@ export const notificationRouter = createTRPCRouter({
|
|||||||
.map((id) => ({ id })),
|
.map((id) => ({ id })),
|
||||||
},
|
},
|
||||||
config: input.config,
|
config: input.config,
|
||||||
|
template: input.template || null,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|||||||
Reference in New Issue
Block a user