remove fake names from profile

This commit is contained in:
Carl-Gerhard Lindesvärd
2024-05-06 09:20:45 +02:00
parent 2e2ee1422f
commit fc0a6a3c73
8 changed files with 55 additions and 41 deletions

View File

@@ -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);

View File

@@ -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>

View File

@@ -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),

View File

@@ -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
);
} }

View File

@@ -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;

View File

@@ -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,

View File

@@ -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),
}; };

View File

@@ -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),