save sdk name and version

This commit is contained in:
Carl-Gerhard Lindesvärd
2024-08-07 22:21:22 +02:00
committed by Carl-Gerhard Lindesvärd
parent 41e46570b7
commit ccfddc215f
12 changed files with 96 additions and 57 deletions

View File

@@ -1 +1,2 @@
*.mdx *.mdx
*.sql

View File

@@ -7,6 +7,8 @@ import { eventsQueue } from '@openpanel/queue';
import { getRedisCache } from '@openpanel/redis'; import { getRedisCache } from '@openpanel/redis';
import type { PostEventPayload } from '@openpanel/sdk'; import type { PostEventPayload } from '@openpanel/sdk';
import { getStringHeaders } from './track.controller';
export async function postEvent( export async function postEvent(
request: FastifyRequest<{ request: FastifyRequest<{
Body: PostEventPayload; Body: PostEventPayload;
@@ -49,9 +51,7 @@ export async function postEvent(
type: 'incomingEvent', type: 'incomingEvent',
payload: { payload: {
projectId: request.projectId, projectId: request.projectId,
headers: { headers: getStringHeaders(request.headers),
ua,
},
event: { event: {
...request.body, ...request.body,
// Dont rely on the client for the timestamp // Dont rely on the client for the timestamp

View File

@@ -26,9 +26,6 @@ export async function importEvents(
table: TABLE_NAMES.events, table: TABLE_NAMES.events,
values, values,
format: 'JSONEachRow', format: 'JSONEachRow',
clickhouse_settings: {
date_time_input_format: 'best_effort',
},
}); });
console.log(res.summary?.written_rows, 'events imported'); console.log(res.summary?.written_rows, 'events imported');

View File

@@ -7,7 +7,8 @@ import { assocPath, pathOr } from 'ramda';
import { generateDeviceId } from '@openpanel/common'; import { generateDeviceId } from '@openpanel/common';
import { import {
ch, ch,
chQuery, createProfileAlias,
formatClickhouseDate,
getProfileById, getProfileById,
getProfileId, getProfileId,
getSalts, getSalts,
@@ -24,6 +25,16 @@ import type {
TrackHandlerPayload, TrackHandlerPayload,
} from '@openpanel/sdk'; } from '@openpanel/sdk';
export function getStringHeaders(headers: FastifyRequest['headers']) {
return Object.entries(headers).reduce(
(acc, [key, value]) => ({
...acc,
[key]: value ? String(value) : undefined,
}),
{}
);
}
export async function handler( export async function handler(
request: FastifyRequest<{ request: FastifyRequest<{
Body: TrackHandlerPayload; Body: TrackHandlerPayload;
@@ -45,12 +56,6 @@ export async function handler(
request.body.payload.profileId = profileId; request.body.payload.profileId = profileId;
} }
console.log(
'> Request',
request.body.type,
JSON.stringify(request.body.payload, null, 2)
);
if (!projectId) { if (!projectId) {
reply.status(400).send('missing origin'); reply.status(400).send('missing origin');
return; return;
@@ -59,25 +64,29 @@ export async function handler(
switch (request.body.type) { switch (request.body.type) {
case 'track': { case 'track': {
const [salts, geo] = await Promise.all([getSalts(), parseIp(ip)]); const [salts, geo] = await Promise.all([getSalts(), parseIp(ip)]);
const currentDeviceId = generateDeviceId({ const currentDeviceId = ua
? generateDeviceId({
salt: salts.current, salt: salts.current,
origin: projectId, origin: projectId,
ip, ip,
ua, ua,
}); })
const previousDeviceId = generateDeviceId({ : '';
const previousDeviceId = ua
? generateDeviceId({
salt: salts.previous, salt: salts.previous,
origin: projectId, origin: projectId,
ip, ip,
ua, ua,
}); })
: '';
await track({ await track({
payload: request.body.payload, payload: request.body.payload,
currentDeviceId, currentDeviceId,
previousDeviceId, previousDeviceId,
projectId, projectId,
geo, geo,
ua, headers: getStringHeaders(request.headers),
}); });
break; break;
} }
@@ -126,14 +135,14 @@ async function track({
previousDeviceId, previousDeviceId,
projectId, projectId,
geo, geo,
ua, headers,
}: { }: {
payload: TrackPayload; payload: TrackPayload;
currentDeviceId: string; currentDeviceId: string;
previousDeviceId: string; previousDeviceId: string;
projectId: string; projectId: string;
geo: GeoLocation; geo: GeoLocation;
ua: string; headers: Record<string, string | undefined>;
}) { }) {
// this will ensure that we don't have multiple events creating sessions // this will ensure that we don't have multiple events creating sessions
const locked = await getRedisCache().set( const locked = await getRedisCache().set(
@@ -148,9 +157,7 @@ async function track({
type: 'incomingEvent', type: 'incomingEvent',
payload: { payload: {
projectId, projectId,
headers: { headers,
ua,
},
event: { event: {
...payload, ...payload,
// Dont rely on the client for the timestamp // Dont rely on the client for the timestamp
@@ -173,7 +180,7 @@ async function identify({
payload: IdentifyPayload; payload: IdentifyPayload;
projectId: string; projectId: string;
geo: GeoLocation; geo: GeoLocation;
ua: string; ua?: string;
}) { }) {
const uaInfo = parseUserAgent(ua); const uaInfo = parseUserAgent(ua);
await upsertProfile({ await upsertProfile({
@@ -196,15 +203,10 @@ async function alias({
payload: AliasPayload; payload: AliasPayload;
projectId: string; projectId: string;
}) { }) {
await ch.insert({ await createProfileAlias({
table: TABLE_NAMES.alias,
values: [
{
projectId,
profile_id: payload.profileId,
alias: payload.alias, alias: payload.alias,
}, profileId: payload.profileId,
], projectId,
}); });
} }

View File

@@ -11,14 +11,14 @@ import {
toISOString, toISOString,
} from '@openpanel/common'; } from '@openpanel/common';
import type { IServiceCreateEventPayload, IServiceEvent } from '@openpanel/db'; import type { IServiceCreateEventPayload, IServiceEvent } from '@openpanel/db';
import { chQuery, createEvent, TABLE_NAMES } from '@openpanel/db'; import { createEvent } from '@openpanel/db';
import { getLastScreenViewFromProfileId } from '@openpanel/db/src/services/event.service'; import { getLastScreenViewFromProfileId } from '@openpanel/db/src/services/event.service';
import { eventsQueue, findJobByPrefix, sessionsQueue } from '@openpanel/queue'; import { eventsQueue, findJobByPrefix, sessionsQueue } from '@openpanel/queue';
import type { import type {
EventsQueuePayloadCreateSessionEnd, EventsQueuePayloadCreateSessionEnd,
EventsQueuePayloadIncomingEvent, EventsQueuePayloadIncomingEvent,
} from '@openpanel/queue'; } from '@openpanel/queue';
import { cacheable, getRedisQueue } from '@openpanel/redis'; import { getRedisQueue } from '@openpanel/redis';
const GLOBAL_PROPERTIES = ['__path', '__referrer']; const GLOBAL_PROPERTIES = ['__path', '__referrer'];
const SESSION_TIMEOUT = 1000 * 60 * 30; const SESSION_TIMEOUT = 1000 * 60 * 30;
@@ -55,7 +55,10 @@ export async function incomingEvent(job: Job<EventsQueuePayloadIncomingEvent>) {
? null ? null
: parseReferrer(getProperty('__referrer')); : parseReferrer(getProperty('__referrer'));
const utmReferrer = getReferrerWithQuery(query); const utmReferrer = getReferrerWithQuery(query);
const uaInfo = parseUserAgent(headers.ua); const userAgent = headers['user-agent'];
const sdkName = headers['openpanel-sdk-name'];
const sdkVersion = headers['openpanel-sdk-version'];
const uaInfo = parseUserAgent(userAgent);
if (uaInfo.isServer) { if (uaInfo.isServer) {
const event = await getLastScreenViewFromProfileId({ const event = await getLastScreenViewFromProfileId({
@@ -71,7 +74,7 @@ export async function incomingEvent(job: Job<EventsQueuePayloadIncomingEvent>) {
projectId, projectId,
properties: { properties: {
...omit(GLOBAL_PROPERTIES, properties), ...omit(GLOBAL_PROPERTIES, properties),
user_agent: headers.ua, user_agent: userAgent,
}, },
createdAt, createdAt,
country: event?.country || geo.country || '', country: event?.country || geo.country || '',
@@ -95,6 +98,8 @@ export async function incomingEvent(job: Job<EventsQueuePayloadIncomingEvent>) {
profile: undefined, profile: undefined,
meta: undefined, meta: undefined,
importedAt: null, importedAt: null,
sdkName,
sdkVersion,
}; };
return createEvent(payload); return createEvent(payload);
@@ -168,6 +173,8 @@ export async function incomingEvent(job: Job<EventsQueuePayloadIncomingEvent>) {
referrer: referrer?.url, referrer: referrer?.url,
referrerName: referrer?.name || utmReferrer?.name || '', referrerName: referrer?.name || utmReferrer?.name || '',
referrerType: referrer?.type || utmReferrer?.type || '', referrerType: referrer?.type || utmReferrer?.type || '',
sdkName,
sdkVersion,
}; };
if (!sessionEnd) { if (!sessionEnd) {

View File

@@ -1,8 +1,10 @@
CREATE DATABASE IF NOT EXISTS openpanel; CREATE DATABASE IF NOT EXISTS openpanel;
CREATE TABLE IF NOT EXISTS openpanel.events_v2 ( CREATE TABLE openpanel.events_v2 (
`id` UUID DEFAULT generateUUIDv4(), `id` UUID DEFAULT generateUUIDv4(),
`name` String, `name` String,
`sdk_name` String,
`sdk_version` String,
`device_id` String, `device_id` String,
`profile_id` String, `profile_id` String,
`project_id` String, `project_id` String,
@@ -29,9 +31,9 @@ CREATE TABLE IF NOT EXISTS openpanel.events_v2 (
`model` String, `model` String,
`imported_at` Nullable(DateTime), `imported_at` Nullable(DateTime),
INDEX idx_name name TYPE bloom_filter GRANULARITY 1, INDEX idx_name name TYPE bloom_filter GRANULARITY 1,
INDEX idx_properties_bounce properties ['__bounce'] TYPE INDEX idx_properties_bounce properties ['__bounce'] TYPE set (3) GRANULARITY 1,
set INDEX idx_origin origin TYPE bloom_filter(0.05) GRANULARITY 1,
(3) GRANULARITY 1 INDEX idx_path path TYPE bloom_filter(0.01) GRANULARITY 1
) ENGINE = MergeTree PARTITION BY toYYYYMM(created_at) ) ENGINE = MergeTree PARTITION BY toYYYYMM(created_at)
ORDER BY ORDER BY
(project_id, toDate(created_at), profile_id, name) SETTINGS index_granularity = 8192; (project_id, toDate(created_at), profile_id, name) SETTINGS index_granularity = 8192;

View File

@@ -78,9 +78,6 @@ export class ProfileBuffer extends RedisBuffer<IClickhouseProfile> {
is_external: item.event.is_external, is_external: item.event.is_external,
}; };
}), }),
clickhouse_settings: {
date_time_input_format: 'best_effort',
},
format: 'JSONEachRow', format: 'JSONEachRow',
}); });
return queue.map((item) => item.index); return queue.map((item) => item.index);

View File

@@ -21,6 +21,9 @@ export const originalCh = createClient({
compression: { compression: {
request: true, request: true,
}, },
clickhouse_settings: {
date_time_input_format: 'best_effort',
},
}); });
export const ch = new Proxy(originalCh, { export const ch = new Proxy(originalCh, {

View File

@@ -56,6 +56,8 @@ export interface IClickhouseEvent {
brand: string; brand: string;
model: string; model: string;
imported_at: string | null; imported_at: string | null;
sdk_name: string;
sdk_version: string;
// They do not exist here. Just make ts happy for now // They do not exist here. Just make ts happy for now
profile?: IServiceProfile; profile?: IServiceProfile;
@@ -93,6 +95,8 @@ export function transformEvent(event: IClickhouseEvent): IServiceEvent {
profile: event.profile, profile: event.profile,
meta: event.meta, meta: event.meta,
importedAt: event.imported_at ? new Date(event.imported_at) : null, importedAt: event.imported_at ? new Date(event.imported_at) : null,
sdkName: event.sdk_name,
sdkVersion: event.sdk_version,
}; };
} }
@@ -134,6 +138,8 @@ export interface IServiceEvent {
importedAt: Date | null; importedAt: Date | null;
profile: IServiceProfile | undefined; profile: IServiceProfile | undefined;
meta: EventMeta | undefined; meta: EventMeta | undefined;
sdkName: string | undefined;
sdkVersion: string | undefined;
} }
export interface IServiceEventMinimal { export interface IServiceEventMinimal {
@@ -295,6 +301,8 @@ export async function createEvent(payload: IServiceCreateEventPayload) {
referrer_name: payload.referrerName ?? '', referrer_name: payload.referrerName ?? '',
referrer_type: payload.referrerType ?? '', referrer_type: payload.referrerType ?? '',
imported_at: null, imported_at: null,
sdk_name: payload.sdkName ?? '',
sdk_version: payload.sdkVersion ?? '',
}; };
await eventBuffer.insert(event); await eventBuffer.insert(event);

View File

@@ -6,6 +6,7 @@ import type { IChartEventFilter } from '@openpanel/validation';
import { profileBuffer } from '../buffers'; import { profileBuffer } from '../buffers';
import { import {
ch,
chQuery, chQuery,
formatClickhouseDate, formatClickhouseDate,
TABLE_NAMES, TABLE_NAMES,
@@ -170,6 +171,29 @@ export function transformProfile({
}; };
} }
export async function createProfileAlias({
projectId,
alias,
profileId,
}: {
projectId: string;
alias: string;
profileId: string;
}) {
await ch.insert({
table: TABLE_NAMES.alias,
format: 'JSONEachRow',
values: [
{
projectId,
profile_id: profileId,
alias,
created_at: new Date(),
},
],
});
}
export async function upsertProfile({ export async function upsertProfile({
id, id,
firstName, firstName,

View File

@@ -18,9 +18,7 @@ export interface EventsQueuePayloadIncomingEvent {
longitude: number | undefined; longitude: number | undefined;
latitude: number | undefined; latitude: number | undefined;
}; };
headers: { headers: Record<string, string | undefined>;
ua: string | undefined;
};
currentDeviceId: string; currentDeviceId: string;
previousDeviceId: string; previousDeviceId: string;
priority: boolean; priority: boolean;

View File

@@ -85,7 +85,7 @@ export class OpenPanel {
defaultHeaders['openpanel-client-secret'] = options.clientSecret; defaultHeaders['openpanel-client-secret'] = options.clientSecret;
} }
defaultHeaders['openpanel-sdk'] = options.sdk || 'node'; defaultHeaders['openpanel-sdk-name'] = options.sdk || 'node';
defaultHeaders['openpanel-sdk-version'] = defaultHeaders['openpanel-sdk-version'] =
options.sdkVersion || process.env.SDK_VERSION!; options.sdkVersion || process.env.SDK_VERSION!;