rename mixan to OPENPANEL!
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import type { IChartEventFilter, IGetChartDataInput } from '@mixan/validation';
|
||||
import type { IChartEventFilter, IGetChartDataInput } from '@openpanel/validation';
|
||||
|
||||
import { formatClickhouseDate } from '../clickhouse-client';
|
||||
import type { SqlBuilderObject } from '../sql-builder';
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import { omit, uniq } from 'ramda';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { omit, uniq } from "ramda";
|
||||
import { v4 as uuid } from "uuid";
|
||||
|
||||
import { randomSplitName, toDots } from '@mixan/common';
|
||||
import { redis, redisPub } from '@mixan/redis';
|
||||
import type { IChartEventFilter } from '@mixan/validation';
|
||||
import { randomSplitName, toDots } from "@openpanel/common";
|
||||
import { redis, redisPub } from "@openpanel/redis";
|
||||
import type { IChartEventFilter } from "@openpanel/validation";
|
||||
|
||||
import {
|
||||
ch,
|
||||
chQuery,
|
||||
convertClickhouseDateToJs,
|
||||
formatClickhouseDate,
|
||||
} from '../clickhouse-client';
|
||||
import type { EventMeta, Prisma } from '../prisma-client';
|
||||
import { db } from '../prisma-client';
|
||||
import { createSqlBuilder } from '../sql-builder';
|
||||
import { getEventFiltersWhereClause } from './chart.service';
|
||||
import { getProfileById, getProfiles, upsertProfile } from './profile.service';
|
||||
import type { IServiceProfile } from './profile.service';
|
||||
} from "../clickhouse-client";
|
||||
import type { EventMeta, Prisma } from "../prisma-client";
|
||||
import { db } from "../prisma-client";
|
||||
import { createSqlBuilder } from "../sql-builder";
|
||||
import { getEventFiltersWhereClause } from "./chart.service";
|
||||
import { getProfileById, getProfiles, upsertProfile } from "./profile.service";
|
||||
import type { IServiceProfile } from "./profile.service";
|
||||
|
||||
export interface IClickhouseEvent {
|
||||
id: string;
|
||||
@@ -49,7 +49,7 @@ export interface IClickhouseEvent {
|
||||
}
|
||||
|
||||
export function transformEvent(
|
||||
event: IClickhouseEvent
|
||||
event: IClickhouseEvent,
|
||||
): IServiceCreateEventPayload {
|
||||
return {
|
||||
id: event.id,
|
||||
@@ -124,7 +124,7 @@ export async function getLiveVisitors(projectId: string) {
|
||||
|
||||
export async function getEvents(
|
||||
sql: string,
|
||||
options: GetEventsOptions = {}
|
||||
options: GetEventsOptions = {},
|
||||
): Promise<IServiceCreateEventPayload[]> {
|
||||
const events = await chQuery<IClickhouseEvent>(sql);
|
||||
if (options.profile) {
|
||||
@@ -155,17 +155,17 @@ export async function getEvents(
|
||||
}
|
||||
|
||||
export async function createEvent(
|
||||
payload: Omit<IServiceCreateEventPayload, 'id'>
|
||||
payload: Omit<IServiceCreateEventPayload, "id">,
|
||||
) {
|
||||
if (!payload.profileId) {
|
||||
payload.profileId = payload.deviceId;
|
||||
}
|
||||
console.log(
|
||||
`create event ${payload.name} for deviceId: ${payload.deviceId} profileId ${payload.profileId}`
|
||||
`create event ${payload.name} for deviceId: ${payload.deviceId} profileId ${payload.profileId}`,
|
||||
);
|
||||
|
||||
const exists = await getProfileById(payload.profileId);
|
||||
if (!exists && payload.profileId !== '') {
|
||||
if (!exists && payload.profileId !== "") {
|
||||
const { firstName, lastName } = randomSplitName();
|
||||
await upsertProfile({
|
||||
id: payload.profileId,
|
||||
@@ -198,40 +198,40 @@ export async function createEvent(
|
||||
profile_id: payload.profileId,
|
||||
project_id: payload.projectId,
|
||||
session_id: payload.sessionId,
|
||||
properties: toDots(omit(['_path'], payload.properties)),
|
||||
path: payload.path ?? '',
|
||||
properties: toDots(omit(["_path"], payload.properties)),
|
||||
path: payload.path ?? "",
|
||||
created_at: formatClickhouseDate(payload.createdAt),
|
||||
country: payload.country ?? '',
|
||||
city: payload.city ?? '',
|
||||
region: payload.region ?? '',
|
||||
os: payload.os ?? '',
|
||||
os_version: payload.osVersion ?? '',
|
||||
browser: payload.browser ?? '',
|
||||
browser_version: payload.browserVersion ?? '',
|
||||
device: payload.device ?? '',
|
||||
brand: payload.brand ?? '',
|
||||
model: payload.model ?? '',
|
||||
country: payload.country ?? "",
|
||||
city: payload.city ?? "",
|
||||
region: payload.region ?? "",
|
||||
os: payload.os ?? "",
|
||||
os_version: payload.osVersion ?? "",
|
||||
browser: payload.browser ?? "",
|
||||
browser_version: payload.browserVersion ?? "",
|
||||
device: payload.device ?? "",
|
||||
brand: payload.brand ?? "",
|
||||
model: payload.model ?? "",
|
||||
duration: payload.duration,
|
||||
referrer: payload.referrer ?? '',
|
||||
referrer_name: payload.referrerName ?? '',
|
||||
referrer_type: payload.referrerType ?? '',
|
||||
referrer: payload.referrer ?? "",
|
||||
referrer_name: payload.referrerName ?? "",
|
||||
referrer_type: payload.referrerType ?? "",
|
||||
};
|
||||
|
||||
const res = await ch.insert({
|
||||
table: 'events',
|
||||
table: "events",
|
||||
values: [event],
|
||||
format: 'JSONEachRow',
|
||||
format: "JSONEachRow",
|
||||
clickhouse_settings: {
|
||||
date_time_input_format: 'best_effort',
|
||||
date_time_input_format: "best_effort",
|
||||
},
|
||||
});
|
||||
|
||||
redisPub.publish('event', JSON.stringify(transformEvent(event)));
|
||||
redisPub.publish("event", JSON.stringify(transformEvent(event)));
|
||||
redis.set(
|
||||
`live:event:${event.project_id}:${event.profile_id}`,
|
||||
'',
|
||||
'EX',
|
||||
60 * 5
|
||||
"",
|
||||
"EX",
|
||||
60 * 5,
|
||||
);
|
||||
|
||||
return {
|
||||
@@ -270,7 +270,7 @@ export async function getEventList({
|
||||
if (events && events.length > 0) {
|
||||
sb.where.events = `name IN (${join(
|
||||
events.map((n) => `'${n}'`),
|
||||
','
|
||||
",",
|
||||
)})`;
|
||||
}
|
||||
|
||||
@@ -285,7 +285,7 @@ export async function getEventList({
|
||||
// sb.where.cursor = `created_at <= '${formatClickhouseDate(cursor)}'`;
|
||||
// }
|
||||
|
||||
sb.orderBy.created_at = 'created_at DESC';
|
||||
sb.orderBy.created_at = "created_at DESC";
|
||||
|
||||
return getEvents(getSql(), { profile: true, meta: true });
|
||||
}
|
||||
@@ -295,7 +295,7 @@ export async function getEventsCount({
|
||||
profileId,
|
||||
events,
|
||||
filters,
|
||||
}: Omit<GetEventListOptions, 'cursor' | 'take'>) {
|
||||
}: Omit<GetEventListOptions, "cursor" | "take">) {
|
||||
const { sb, getSql, join } = createSqlBuilder();
|
||||
sb.where.projectId = `project_id = '${projectId}'`;
|
||||
if (profileId) {
|
||||
@@ -305,7 +305,7 @@ export async function getEventsCount({
|
||||
if (events && events.length > 0) {
|
||||
sb.where.events = `name IN (${join(
|
||||
events.map((n) => `'${n}'`),
|
||||
','
|
||||
",",
|
||||
)})`;
|
||||
}
|
||||
|
||||
@@ -317,7 +317,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;
|
||||
@@ -339,8 +339,8 @@ export function createBotEvent({
|
||||
path,
|
||||
}: CreateBotEventPayload) {
|
||||
return ch.insert({
|
||||
table: 'events_bots',
|
||||
format: 'JSONEachRow',
|
||||
table: "events_bots",
|
||||
format: "JSONEachRow",
|
||||
values: [
|
||||
{
|
||||
name,
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { toDots, toObject } from '@mixan/common';
|
||||
import type { IChartEventFilter } from '@mixan/validation';
|
||||
import { toDots, toObject } from "@openpanel/common";
|
||||
import type { IChartEventFilter } from "@openpanel/validation";
|
||||
|
||||
import { ch, chQuery } from '../clickhouse-client';
|
||||
import { createSqlBuilder } from '../sql-builder';
|
||||
import { getEventFiltersWhereClause } from './chart.service';
|
||||
import { ch, chQuery } from "../clickhouse-client";
|
||||
import { createSqlBuilder } from "../sql-builder";
|
||||
import { getEventFiltersWhereClause } from "./chart.service";
|
||||
|
||||
export async function getProfileById(id: string) {
|
||||
if (id === '') {
|
||||
if (id === "") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [profile] = await chQuery<IClickhouseProfile>(
|
||||
`SELECT * FROM profiles WHERE id = '${id}' ORDER BY created_at DESC LIMIT 1`
|
||||
`SELECT * FROM profiles WHERE id = '${id}' ORDER BY created_at DESC LIMIT 1`,
|
||||
);
|
||||
|
||||
if (!profile) {
|
||||
@@ -30,15 +30,15 @@ interface GetProfileListOptions {
|
||||
|
||||
function getProfileSelectFields() {
|
||||
return [
|
||||
'id',
|
||||
'argMax(first_name, created_at) as first_name',
|
||||
'argMax(last_name, created_at) as last_name',
|
||||
'argMax(email, created_at) as email',
|
||||
'argMax(avatar, created_at) as avatar',
|
||||
'argMax(properties, created_at) as properties',
|
||||
'argMax(project_id, created_at) as project_id',
|
||||
'max(created_at) as max_created_at',
|
||||
].join(', ');
|
||||
"id",
|
||||
"argMax(first_name, created_at) as first_name",
|
||||
"argMax(last_name, created_at) as last_name",
|
||||
"argMax(email, created_at) as email",
|
||||
"argMax(avatar, created_at) as avatar",
|
||||
"argMax(properties, created_at) as properties",
|
||||
"argMax(project_id, created_at) as project_id",
|
||||
"max(created_at) as max_created_at",
|
||||
].join(", ");
|
||||
}
|
||||
|
||||
interface GetProfilesOptions {
|
||||
@@ -53,9 +53,9 @@ export async function getProfiles({ ids }: GetProfilesOptions) {
|
||||
`SELECT
|
||||
${getProfileSelectFields()}
|
||||
FROM profiles
|
||||
WHERE id IN (${ids.map((id) => `'${id}'`).join(',')})
|
||||
WHERE id IN (${ids.map((id) => `'${id}'`).join(",")})
|
||||
GROUP BY id
|
||||
`
|
||||
`,
|
||||
);
|
||||
|
||||
return data.map(transformProfile);
|
||||
@@ -85,7 +85,7 @@ export async function getProfileList({
|
||||
}
|
||||
sb.limit = take;
|
||||
sb.offset = (cursor ?? 0) * take;
|
||||
sb.orderBy.created_at = 'max_created_at DESC';
|
||||
sb.orderBy.created_at = "max_created_at DESC";
|
||||
const data = await chQuery<IClickhouseProfile>(getSql());
|
||||
return data.map(transformProfile);
|
||||
}
|
||||
@@ -93,9 +93,9 @@ export async function getProfileList({
|
||||
export async function getProfileListCount({
|
||||
projectId,
|
||||
filters,
|
||||
}: Omit<GetProfileListOptions, 'cursor' | 'take'>) {
|
||||
}: Omit<GetProfileListOptions, "cursor" | "take">) {
|
||||
const { sb, getSql } = createSqlBuilder();
|
||||
sb.select.count = 'count(id) as count';
|
||||
sb.select.count = "count(id) as count";
|
||||
sb.from = getProfileInnerSelect(projectId);
|
||||
if (filters) {
|
||||
sb.where = {
|
||||
@@ -109,7 +109,7 @@ export async function getProfileListCount({
|
||||
|
||||
export async function getProfilesByExternalId(
|
||||
externalId: string | null,
|
||||
projectId: string
|
||||
projectId: string,
|
||||
) {
|
||||
if (externalId === null) {
|
||||
return [];
|
||||
@@ -121,7 +121,7 @@ export async function getProfilesByExternalId(
|
||||
FROM profiles
|
||||
GROUP BY id
|
||||
HAVING project_id = '${projectId}' AND external_id = '${externalId}'
|
||||
`
|
||||
`,
|
||||
);
|
||||
|
||||
return data.map(transformProfile);
|
||||
@@ -129,7 +129,7 @@ export async function getProfilesByExternalId(
|
||||
|
||||
export type IServiceProfile = Omit<
|
||||
IClickhouseProfile,
|
||||
'max_created_at' | 'properties'
|
||||
"max_created_at" | "properties"
|
||||
> & {
|
||||
createdAt: Date;
|
||||
properties: Record<string, unknown>;
|
||||
@@ -177,27 +177,27 @@ export async function upsertProfile({
|
||||
projectId,
|
||||
}: IServiceUpsertProfile) {
|
||||
const [profile] = await chQuery<IClickhouseProfile>(
|
||||
`SELECT * FROM profiles WHERE id = '${id}' AND project_id = '${projectId}' ORDER BY created_at DESC LIMIT 1`
|
||||
`SELECT * FROM profiles WHERE id = '${id}' AND project_id = '${projectId}' ORDER BY created_at DESC LIMIT 1`,
|
||||
);
|
||||
|
||||
await ch.insert({
|
||||
table: 'profiles',
|
||||
format: 'JSONEachRow',
|
||||
table: "profiles",
|
||||
format: "JSONEachRow",
|
||||
clickhouse_settings: {
|
||||
date_time_input_format: 'best_effort',
|
||||
date_time_input_format: "best_effort",
|
||||
},
|
||||
values: [
|
||||
{
|
||||
id,
|
||||
first_name: firstName ?? profile?.first_name ?? '',
|
||||
last_name: lastName ?? profile?.last_name ?? '',
|
||||
email: email ?? profile?.email ?? '',
|
||||
avatar: avatar ?? profile?.avatar ?? '',
|
||||
first_name: firstName ?? profile?.first_name ?? "",
|
||||
last_name: lastName ?? profile?.last_name ?? "",
|
||||
email: email ?? profile?.email ?? "",
|
||||
avatar: avatar ?? profile?.avatar ?? "",
|
||||
properties: toDots({
|
||||
...(profile?.properties ?? {}),
|
||||
...(properties ?? {}),
|
||||
}),
|
||||
project_id: projectId ?? profile?.project_id ?? '',
|
||||
project_id: projectId ?? profile?.project_id ?? "",
|
||||
created_at: new Date(),
|
||||
},
|
||||
],
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { alphabetIds, lineTypes, timeRanges } from '@mixan/constants';
|
||||
import { alphabetIds, lineTypes, timeRanges } from "@openpanel/constants";
|
||||
import type {
|
||||
IChartBreakdown,
|
||||
IChartEvent,
|
||||
@@ -6,42 +6,42 @@ import type {
|
||||
IChartInput,
|
||||
IChartLineType,
|
||||
IChartRange,
|
||||
} from '@mixan/validation';
|
||||
} from "@openpanel/validation";
|
||||
|
||||
import { db } from '../prisma-client';
|
||||
import type { Report as DbReport } from '../prisma-client';
|
||||
import { db } from "../prisma-client";
|
||||
import type { Report as DbReport } from "../prisma-client";
|
||||
|
||||
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',
|
||||
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',
|
||||
segment: event.segment ?? "event",
|
||||
filters: (event.filters ?? []).map(transformFilter),
|
||||
id: event.id ?? alphabetIds[index]!,
|
||||
name: event.name || 'unknown_event',
|
||||
name: event.name || "unknown_event",
|
||||
displayName: event.displayName,
|
||||
property: event.property,
|
||||
};
|
||||
}
|
||||
|
||||
export function transformReport(
|
||||
report: DbReport
|
||||
report: DbReport,
|
||||
): IChartInput & { id: string } {
|
||||
return {
|
||||
id: report.id,
|
||||
@@ -51,11 +51,11 @@ export function transformReport(
|
||||
chartType: report.chart_type,
|
||||
lineType: (report.line_type as IChartLineType) ?? lineTypes.monotone,
|
||||
interval: report.interval,
|
||||
name: report.name || 'Untitled',
|
||||
range: (report.range as IChartRange) ?? timeRanges['1m'],
|
||||
name: report.name || "Untitled",
|
||||
range: (report.range as IChartRange) ?? timeRanges["1m"],
|
||||
previous: report.previous ?? false,
|
||||
formula: report.formula ?? undefined,
|
||||
metric: report.metric ?? 'sum',
|
||||
metric: report.metric ?? "sum",
|
||||
unit: report.unit ?? undefined,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user