remove fake names from profile
This commit is contained in:
@@ -24,6 +24,7 @@ export async function updateProfile(
|
|||||||
|
|
||||||
await upsertProfile({
|
await upsertProfile({
|
||||||
id: profileId,
|
id: profileId,
|
||||||
|
isExternal: true,
|
||||||
projectId,
|
projectId,
|
||||||
properties: {
|
properties: {
|
||||||
...(properties ?? {}),
|
...(properties ?? {}),
|
||||||
@@ -45,7 +46,7 @@ export async function incrementProfileProperty(
|
|||||||
const { profileId, property, value } = request.body;
|
const { profileId, property, value } = request.body;
|
||||||
const projectId = request.projectId;
|
const projectId = request.projectId;
|
||||||
|
|
||||||
const profile = await getProfileById(profileId);
|
const profile = await getProfileById(profileId, projectId);
|
||||||
if (!profile) {
|
if (!profile) {
|
||||||
return reply.status(404).send('Not found');
|
return reply.status(404).send('Not found');
|
||||||
}
|
}
|
||||||
@@ -69,6 +70,7 @@ export async function incrementProfileProperty(
|
|||||||
id: profile.id,
|
id: profile.id,
|
||||||
projectId,
|
projectId,
|
||||||
properties: profile.properties,
|
properties: profile.properties,
|
||||||
|
isExternal: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
reply.status(202).send(profile.id);
|
reply.status(202).send(profile.id);
|
||||||
@@ -83,7 +85,7 @@ export async function decrementProfileProperty(
|
|||||||
const { profileId, property, value } = request.body;
|
const { profileId, property, value } = request.body;
|
||||||
const projectId = request.projectId;
|
const projectId = request.projectId;
|
||||||
|
|
||||||
const profile = await getProfileById(profileId);
|
const profile = await getProfileById(profileId, projectId);
|
||||||
if (!profile) {
|
if (!profile) {
|
||||||
return reply.status(404).send('Not found');
|
return reply.status(404).send('Not found');
|
||||||
}
|
}
|
||||||
@@ -107,6 +109,7 @@ export async function decrementProfileProperty(
|
|||||||
id: profile.id,
|
id: profile.id,
|
||||||
projectId,
|
projectId,
|
||||||
properties: profile.properties,
|
properties: profile.properties,
|
||||||
|
isExternal: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
reply.status(202).send(profile.id);
|
reply.status(202).send(profile.id);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { Tooltiper } from '@/components/ui/tooltip';
|
|||||||
import { useAppParams } from '@/hooks/useAppParams';
|
import { useAppParams } from '@/hooks/useAppParams';
|
||||||
import { useNumber } from '@/hooks/useNumerFormatter';
|
import { useNumber } from '@/hooks/useNumerFormatter';
|
||||||
import { cn } from '@/utils/cn';
|
import { cn } from '@/utils/cn';
|
||||||
|
import { getProfileName } from '@/utils/getters';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
@@ -96,10 +97,7 @@ export function EventListItem(props: EventListItemProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-4">
|
<div className="flex gap-4">
|
||||||
<Tooltiper
|
<Tooltiper asChild content={getProfileName(profile)}>
|
||||||
asChild
|
|
||||||
content={`${profile?.firstName} ${profile?.lastName}`}
|
|
||||||
>
|
|
||||||
<Link
|
<Link
|
||||||
prefetch={false}
|
prefetch={false}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
@@ -108,7 +106,7 @@ export function EventListItem(props: EventListItemProps) {
|
|||||||
href={`/${organizationSlug}/${projectId}/profiles/${profile?.id}`}
|
href={`/${organizationSlug}/${projectId}/profiles/${profile?.id}`}
|
||||||
className="max-w-[80px] overflow-hidden text-ellipsis whitespace-nowrap text-sm text-muted-foreground hover:underline"
|
className="max-w-[80px] overflow-hidden text-ellipsis whitespace-nowrap text-sm text-muted-foreground hover:underline"
|
||||||
>
|
>
|
||||||
{profile?.firstName} {profile?.lastName}
|
{getProfileName(profile)}
|
||||||
</Link>
|
</Link>
|
||||||
</Tooltiper>
|
</Tooltiper>
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export default async function Page({
|
|||||||
const startDate = parseAsString.parseServerSide(searchParams.startDate);
|
const startDate = parseAsString.parseServerSide(searchParams.startDate);
|
||||||
const endDate = parseAsString.parseServerSide(searchParams.endDate);
|
const endDate = parseAsString.parseServerSide(searchParams.endDate);
|
||||||
const [profile, events, count, conversions] = await Promise.all([
|
const [profile, events, count, conversions] = await Promise.all([
|
||||||
getProfileById(profileId),
|
getProfileById(profileId, projectId),
|
||||||
getEventList(eventListOptions),
|
getEventList(eventListOptions),
|
||||||
getEventsCount(eventListOptions),
|
getEventsCount(eventListOptions),
|
||||||
getConversionEventNames(projectId),
|
getConversionEventNames(projectId),
|
||||||
|
|||||||
@@ -1,6 +1,15 @@
|
|||||||
import type { IServiceProfile } from '@openpanel/db';
|
import type { IServiceProfile } from '@openpanel/db';
|
||||||
|
|
||||||
export function getProfileName(profile: IServiceProfile | undefined | null) {
|
export function getProfileName(profile: IServiceProfile | undefined | null) {
|
||||||
if (!profile) return 'No name';
|
if (!profile) return 'Unknown';
|
||||||
return [profile.firstName, profile.lastName].filter(Boolean).join(' ');
|
|
||||||
|
if (!profile.isExternal) {
|
||||||
|
return profile.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
[profile.firstName, profile.lastName].filter(Boolean).join(' ') ||
|
||||||
|
profile.email ||
|
||||||
|
profile.id
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ ORDER BY
|
|||||||
|
|
||||||
CREATE TABLE openpanel.profiles (
|
CREATE TABLE openpanel.profiles (
|
||||||
`id` String,
|
`id` String,
|
||||||
`external_id` String,
|
|
||||||
`first_name` String,
|
`first_name` String,
|
||||||
`last_name` String,
|
`last_name` String,
|
||||||
`email` String,
|
`email` String,
|
||||||
@@ -90,4 +89,23 @@ FROM
|
|||||||
events
|
events
|
||||||
GROUP BY
|
GROUP BY
|
||||||
date,
|
date,
|
||||||
project_id;
|
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;
|
||||||
@@ -3,7 +3,7 @@ import { escape } from 'sqlstring';
|
|||||||
import superjson from 'superjson';
|
import superjson from 'superjson';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
import { randomSplitName, toDots } from '@openpanel/common';
|
import { toDots } from '@openpanel/common';
|
||||||
import { redis, redisPub } from '@openpanel/redis';
|
import { redis, redisPub } from '@openpanel/redis';
|
||||||
import type { IChartEventFilter } from '@openpanel/validation';
|
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}`
|
`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 !== '') {
|
if (!exists && payload.profileId !== '') {
|
||||||
const { firstName, lastName } = randomSplitName();
|
|
||||||
await upsertProfile({
|
await upsertProfile({
|
||||||
id: payload.profileId,
|
id: payload.profileId,
|
||||||
|
isExternal: false,
|
||||||
projectId: payload.projectId,
|
projectId: payload.projectId,
|
||||||
firstName,
|
firstName: '',
|
||||||
lastName,
|
lastName: '',
|
||||||
properties: {
|
properties: {
|
||||||
path: payload.path,
|
path: payload.path,
|
||||||
country: payload.country,
|
country: payload.country,
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ import { ch, chQuery } from '../clickhouse-client';
|
|||||||
import { createSqlBuilder } from '../sql-builder';
|
import { createSqlBuilder } from '../sql-builder';
|
||||||
import { getEventFiltersWhereClause } from './chart.service';
|
import { getEventFiltersWhereClause } from './chart.service';
|
||||||
|
|
||||||
export async function getProfileById(id: string) {
|
export async function getProfileById(id: string, projectId: string) {
|
||||||
if (id === '') {
|
if (id === '' || projectId === '') {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [profile] = await chQuery<IClickhouseProfile>(
|
const [profile] = await chQuery<IClickhouseProfile>(
|
||||||
`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) {
|
if (!profile) {
|
||||||
@@ -39,6 +39,7 @@ function getProfileSelectFields() {
|
|||||||
'argMax(avatar, created_at) as avatar',
|
'argMax(avatar, created_at) as avatar',
|
||||||
'argMax(properties, created_at) as properties',
|
'argMax(properties, created_at) as properties',
|
||||||
'argMax(project_id, created_at) as project_id',
|
'argMax(project_id, created_at) as project_id',
|
||||||
|
'argMax(is_external, created_at) as is_external',
|
||||||
'max(created_at) as max_created_at',
|
'max(created_at) as max_created_at',
|
||||||
].join(', ');
|
].join(', ');
|
||||||
}
|
}
|
||||||
@@ -109,32 +110,14 @@ export async function getProfileListCount({
|
|||||||
return data?.count ?? 0;
|
return data?.count ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getProfilesByExternalId(
|
|
||||||
externalId: string | null,
|
|
||||||
projectId: string
|
|
||||||
) {
|
|
||||||
if (externalId === null) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await chQuery<IClickhouseProfile>(
|
|
||||||
`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<
|
export type IServiceProfile = Omit<
|
||||||
IClickhouseProfile,
|
IClickhouseProfile,
|
||||||
'max_created_at' | 'properties' | 'first_name' | 'last_name'
|
'max_created_at' | 'properties' | 'first_name' | 'last_name' | 'is_external'
|
||||||
> & {
|
> & {
|
||||||
firstName: string;
|
firstName: string;
|
||||||
lastName: string;
|
lastName: string;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
|
isExternal: boolean;
|
||||||
properties: Record<string, unknown> & {
|
properties: Record<string, unknown> & {
|
||||||
country?: string;
|
country?: string;
|
||||||
city?: string;
|
city?: string;
|
||||||
@@ -155,6 +138,7 @@ export interface IClickhouseProfile {
|
|||||||
avatar: string;
|
avatar: string;
|
||||||
properties: Record<string, string | undefined>;
|
properties: Record<string, string | undefined>;
|
||||||
project_id: string;
|
project_id: string;
|
||||||
|
is_external: boolean;
|
||||||
max_created_at: string;
|
max_created_at: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,6 +150,7 @@ export interface IServiceUpsertProfile {
|
|||||||
email?: string;
|
email?: string;
|
||||||
avatar?: string;
|
avatar?: string;
|
||||||
properties?: Record<string, unknown>;
|
properties?: Record<string, unknown>;
|
||||||
|
isExternal: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function transformProfile({
|
export function transformProfile({
|
||||||
@@ -178,6 +163,7 @@ export function transformProfile({
|
|||||||
...profile,
|
...profile,
|
||||||
firstName: first_name,
|
firstName: first_name,
|
||||||
lastName: last_name,
|
lastName: last_name,
|
||||||
|
isExternal: profile.is_external,
|
||||||
properties: toObject(profile.properties),
|
properties: toObject(profile.properties),
|
||||||
createdAt: new Date(max_created_at),
|
createdAt: new Date(max_created_at),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export const profileRouter = createTRPCRouter({
|
|||||||
.map((item) => item.replace(/\.([0-9]+)/g, '[*]'))
|
.map((item) => item.replace(/\.([0-9]+)/g, '[*]'))
|
||||||
.map((item) => `properties.${item}`);
|
.map((item) => `properties.${item}`);
|
||||||
|
|
||||||
properties.push('external_id', 'first_name', 'last_name', 'email');
|
properties.push('id', 'first_name', 'last_name', 'email');
|
||||||
|
|
||||||
return pipe(
|
return pipe(
|
||||||
sort<string>((a, b) => a.length - b.length),
|
sort<string>((a, b) => a.length - b.length),
|
||||||
|
|||||||
Reference in New Issue
Block a user