save sdk name and version
This commit is contained in:
committed by
Carl-Gerhard Lindesvärd
parent
41e46570b7
commit
ccfddc215f
@@ -1 +1,2 @@
|
|||||||
*.mdx
|
*.mdx
|
||||||
|
*.sql
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|||||||
@@ -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
|
||||||
salt: salts.current,
|
? generateDeviceId({
|
||||||
origin: projectId,
|
salt: salts.current,
|
||||||
ip,
|
origin: projectId,
|
||||||
ua,
|
ip,
|
||||||
});
|
ua,
|
||||||
const previousDeviceId = generateDeviceId({
|
})
|
||||||
salt: salts.previous,
|
: '';
|
||||||
origin: projectId,
|
const previousDeviceId = ua
|
||||||
ip,
|
? generateDeviceId({
|
||||||
ua,
|
salt: salts.previous,
|
||||||
});
|
origin: projectId,
|
||||||
|
ip,
|
||||||
|
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,
|
alias: payload.alias,
|
||||||
values: [
|
profileId: payload.profileId,
|
||||||
{
|
projectId,
|
||||||
projectId,
|
|
||||||
profile_id: payload.profileId,
|
|
||||||
alias: payload.alias,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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, {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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!;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user