diff --git a/apps/api/src/controllers/profile.controller.ts b/apps/api/src/controllers/profile.controller.ts
index 5bb787c8..1971b2a4 100644
--- a/apps/api/src/controllers/profile.controller.ts
+++ b/apps/api/src/controllers/profile.controller.ts
@@ -24,6 +24,7 @@ export async function updateProfile(
await upsertProfile({
id: profileId,
+ isExternal: true,
projectId,
properties: {
...(properties ?? {}),
@@ -45,7 +46,7 @@ export async function incrementProfileProperty(
const { profileId, property, value } = request.body;
const projectId = request.projectId;
- const profile = await getProfileById(profileId);
+ const profile = await getProfileById(profileId, projectId);
if (!profile) {
return reply.status(404).send('Not found');
}
@@ -69,6 +70,7 @@ export async function incrementProfileProperty(
id: profile.id,
projectId,
properties: profile.properties,
+ isExternal: true,
});
reply.status(202).send(profile.id);
@@ -83,7 +85,7 @@ export async function decrementProfileProperty(
const { profileId, property, value } = request.body;
const projectId = request.projectId;
- const profile = await getProfileById(profileId);
+ const profile = await getProfileById(profileId, projectId);
if (!profile) {
return reply.status(404).send('Not found');
}
@@ -107,6 +109,7 @@ export async function decrementProfileProperty(
id: profile.id,
projectId,
properties: profile.properties,
+ isExternal: true,
});
reply.status(202).send(profile.id);
diff --git a/apps/dashboard/src/app/(app)/[organizationSlug]/[projectId]/events/event-list-item.tsx b/apps/dashboard/src/app/(app)/[organizationSlug]/[projectId]/events/event-list-item.tsx
index 6dab0d1d..ce1b7dd7 100644
--- a/apps/dashboard/src/app/(app)/[organizationSlug]/[projectId]/events/event-list-item.tsx
+++ b/apps/dashboard/src/app/(app)/[organizationSlug]/[projectId]/events/event-list-item.tsx
@@ -6,6 +6,7 @@ import { Tooltiper } from '@/components/ui/tooltip';
import { useAppParams } from '@/hooks/useAppParams';
import { useNumber } from '@/hooks/useNumerFormatter';
import { cn } from '@/utils/cn';
+import { getProfileName } from '@/utils/getters';
import Link from 'next/link';
import type {
@@ -96,10 +97,7 @@ export function EventListItem(props: EventListItemProps) {
-
+
{
@@ -108,7 +106,7 @@ export function EventListItem(props: EventListItemProps) {
href={`/${organizationSlug}/${projectId}/profiles/${profile?.id}`}
className="max-w-[80px] overflow-hidden text-ellipsis whitespace-nowrap text-sm text-muted-foreground hover:underline"
>
- {profile?.firstName} {profile?.lastName}
+ {getProfileName(profile)}
diff --git a/apps/dashboard/src/app/(app)/[organizationSlug]/[projectId]/profiles/[profileId]/page.tsx b/apps/dashboard/src/app/(app)/[organizationSlug]/[projectId]/profiles/[profileId]/page.tsx
index d11218dc..ff4db3dc 100644
--- a/apps/dashboard/src/app/(app)/[organizationSlug]/[projectId]/profiles/[profileId]/page.tsx
+++ b/apps/dashboard/src/app/(app)/[organizationSlug]/[projectId]/profiles/[profileId]/page.tsx
@@ -58,7 +58,7 @@ export default async function Page({
const startDate = parseAsString.parseServerSide(searchParams.startDate);
const endDate = parseAsString.parseServerSide(searchParams.endDate);
const [profile, events, count, conversions] = await Promise.all([
- getProfileById(profileId),
+ getProfileById(profileId, projectId),
getEventList(eventListOptions),
getEventsCount(eventListOptions),
getConversionEventNames(projectId),
diff --git a/apps/dashboard/src/utils/getters.ts b/apps/dashboard/src/utils/getters.ts
index 1b5eae29..f8e2afe4 100644
--- a/apps/dashboard/src/utils/getters.ts
+++ b/apps/dashboard/src/utils/getters.ts
@@ -1,6 +1,15 @@
import type { IServiceProfile } from '@openpanel/db';
export function getProfileName(profile: IServiceProfile | undefined | null) {
- if (!profile) return 'No name';
- return [profile.firstName, profile.lastName].filter(Boolean).join(' ');
+ if (!profile) return 'Unknown';
+
+ if (!profile.isExternal) {
+ return profile.id;
+ }
+
+ return (
+ [profile.firstName, profile.lastName].filter(Boolean).join(' ') ||
+ profile.email ||
+ profile.id
+ );
}
diff --git a/packages/db/clickhouse_tables.sql b/packages/db/clickhouse_tables.sql
index 7300bb8e..daa4443c 100644
--- a/packages/db/clickhouse_tables.sql
+++ b/packages/db/clickhouse_tables.sql
@@ -43,7 +43,6 @@ ORDER BY
CREATE TABLE openpanel.profiles (
`id` String,
- `external_id` String,
`first_name` String,
`last_name` String,
`email` String,
@@ -90,4 +89,23 @@ FROM
events
GROUP BY
date,
- project_id;
\ No newline at end of file
+ project_id;
+
+-- DROP external_id and add is_external column
+ALTER TABLE
+ profiles DROP COLUMN external_id;
+
+ALTER TABLE
+ profiles
+ADD
+ COLUMN is_external Boolean
+AFTER
+ id;
+
+ALTER TABLE
+ profiles
+UPDATE
+ is_external = length(id) != 32
+WHERE
+ true
+ and length(id) != 32;
\ No newline at end of file
diff --git a/packages/db/src/services/event.service.ts b/packages/db/src/services/event.service.ts
index 7aff8e42..825b0ab5 100644
--- a/packages/db/src/services/event.service.ts
+++ b/packages/db/src/services/event.service.ts
@@ -3,7 +3,7 @@ import { escape } from 'sqlstring';
import superjson from 'superjson';
import { v4 as uuid } from 'uuid';
-import { randomSplitName, toDots } from '@openpanel/common';
+import { toDots } from '@openpanel/common';
import { redis, redisPub } from '@openpanel/redis';
import type { IChartEventFilter } from '@openpanel/validation';
@@ -215,14 +215,14 @@ export async function createEvent(
`create event ${payload.name} for deviceId: ${payload.deviceId} profileId ${payload.profileId}`
);
- const exists = await getProfileById(payload.profileId);
+ const exists = await getProfileById(payload.profileId, payload.projectId);
if (!exists && payload.profileId !== '') {
- const { firstName, lastName } = randomSplitName();
await upsertProfile({
id: payload.profileId,
+ isExternal: false,
projectId: payload.projectId,
- firstName,
- lastName,
+ firstName: '',
+ lastName: '',
properties: {
path: payload.path,
country: payload.country,
diff --git a/packages/db/src/services/profile.service.ts b/packages/db/src/services/profile.service.ts
index 050eb9d6..799efdee 100644
--- a/packages/db/src/services/profile.service.ts
+++ b/packages/db/src/services/profile.service.ts
@@ -7,13 +7,13 @@ import { ch, chQuery } from '../clickhouse-client';
import { createSqlBuilder } from '../sql-builder';
import { getEventFiltersWhereClause } from './chart.service';
-export async function getProfileById(id: string) {
- if (id === '') {
+export async function getProfileById(id: string, projectId: string) {
+ if (id === '' || projectId === '') {
return null;
}
const [profile] = await chQuery(
- `SELECT *, created_at as max_created_at FROM profiles WHERE id = ${escape(id)} ORDER BY created_at DESC LIMIT 1`
+ `SELECT *, created_at as max_created_at FROM profiles WHERE id = ${escape(id)} AND project_id = ${escape(projectId)} ORDER BY created_at DESC LIMIT 1`
);
if (!profile) {
@@ -39,6 +39,7 @@ function getProfileSelectFields() {
'argMax(avatar, created_at) as avatar',
'argMax(properties, created_at) as properties',
'argMax(project_id, created_at) as project_id',
+ 'argMax(is_external, created_at) as is_external',
'max(created_at) as max_created_at',
].join(', ');
}
@@ -109,32 +110,14 @@ export async function getProfileListCount({
return data?.count ?? 0;
}
-export async function getProfilesByExternalId(
- externalId: string | null,
- projectId: string
-) {
- if (externalId === null) {
- return [];
- }
-
- const data = await chQuery(
- `SELECT
- ${getProfileSelectFields()}
- FROM profiles
- GROUP BY id
- HAVING project_id = ${escape(projectId)} AND external_id = ${escape(externalId)}`
- );
-
- return data.map(transformProfile);
-}
-
export type IServiceProfile = Omit<
IClickhouseProfile,
- 'max_created_at' | 'properties' | 'first_name' | 'last_name'
+ 'max_created_at' | 'properties' | 'first_name' | 'last_name' | 'is_external'
> & {
firstName: string;
lastName: string;
createdAt: Date;
+ isExternal: boolean;
properties: Record & {
country?: string;
city?: string;
@@ -155,6 +138,7 @@ export interface IClickhouseProfile {
avatar: string;
properties: Record;
project_id: string;
+ is_external: boolean;
max_created_at: string;
}
@@ -166,6 +150,7 @@ export interface IServiceUpsertProfile {
email?: string;
avatar?: string;
properties?: Record;
+ isExternal: boolean;
}
export function transformProfile({
@@ -178,6 +163,7 @@ export function transformProfile({
...profile,
firstName: first_name,
lastName: last_name,
+ isExternal: profile.is_external,
properties: toObject(profile.properties),
createdAt: new Date(max_created_at),
};
diff --git a/packages/trpc/src/routers/profile.ts b/packages/trpc/src/routers/profile.ts
index 902e4d41..7776c132 100644
--- a/packages/trpc/src/routers/profile.ts
+++ b/packages/trpc/src/routers/profile.ts
@@ -20,7 +20,7 @@ export const profileRouter = createTRPCRouter({
.map((item) => item.replace(/\.([0-9]+)/g, '[*]'))
.map((item) => `properties.${item}`);
- properties.push('external_id', 'first_name', 'last_name', 'email');
+ properties.push('id', 'first_name', 'last_name', 'email');
return pipe(
sort((a, b) => a.length - b.length),