try simplified event buffer with duration calculation on the fly instead

This commit is contained in:
Carl-Gerhard Lindesvärd
2026-03-14 11:39:19 +01:00
parent 9c3c1458bb
commit 7a76b968ba
9 changed files with 269 additions and 789 deletions

View File

@@ -1,25 +1,20 @@
import type { IServiceEvent, IServiceEventMinimal } from '@openpanel/db';
import { Link } from '@tanstack/react-router';
import { SerieIcon } from '../report-chart/common/serie-icon';
import { EventIcon } from './event-icon';
import { Tooltiper } from '@/components/ui/tooltip';
import { useAppParams } from '@/hooks/use-app-params';
import { useNumber } from '@/hooks/use-numer-formatter';
import { pushModal } from '@/modals';
import { cn } from '@/utils/cn';
import { getProfileName } from '@/utils/getters';
import { Link } from '@tanstack/react-router';
import type { IServiceEvent, IServiceEventMinimal } from '@openpanel/db';
import { SerieIcon } from '../report-chart/common/serie-icon';
import { EventIcon } from './event-icon';
type EventListItemProps = IServiceEventMinimal | IServiceEvent;
export function EventListItem(props: EventListItemProps) {
const { organizationId, projectId } = useAppParams();
const { createdAt, name, path, duration, meta } = props;
const { createdAt, name, path, meta } = props;
const profile = 'profile' in props ? props.profile : null;
const number = useNumber();
const renderName = () => {
if (name === 'screen_view') {
if (path.includes('/')) {
@@ -32,83 +27,65 @@ export function EventListItem(props: EventListItemProps) {
return name.replace(/_/g, ' ');
};
const renderDuration = () => {
if (name === 'screen_view') {
return (
<span className="text-muted-foreground">
{number.shortWithUnit(duration / 1000, 'min')}
</span>
);
}
return null;
};
const isMinimal = 'minimal' in props;
return (
<>
<button
type="button"
onClick={() => {
if (!isMinimal) {
pushModal('EventDetails', {
id: props.id,
projectId,
createdAt,
});
}
}}
className={cn(
'card hover:bg-light-background flex w-full items-center justify-between rounded-lg p-4 transition-colors',
meta?.conversion &&
`bg-${meta.color}-50 dark:bg-${meta.color}-900 hover:bg-${meta.color}-100 dark:hover:bg-${meta.color}-700`,
)}
>
<div>
<div className="flex items-center gap-4 text-left ">
<EventIcon size="sm" name={name} meta={meta} />
<span>
<span className="font-medium">{renderName()}</span>
{' '}
{renderDuration()}
</span>
</div>
<div className="pl-10">
<div className="flex origin-left scale-75 gap-1">
<SerieIcon name={props.country} />
<SerieIcon name={props.os} />
<SerieIcon name={props.browser} />
</div>
<button
className={cn(
'card flex w-full items-center justify-between rounded-lg p-4 transition-colors hover:bg-light-background',
meta?.conversion &&
`bg-${meta.color}-50 dark:bg-${meta.color}-900 hover:bg-${meta.color}-100 dark:hover:bg-${meta.color}-700`
)}
onClick={() => {
if (!isMinimal) {
pushModal('EventDetails', {
id: props.id,
projectId,
createdAt,
});
}
}}
type="button"
>
<div>
<div className="flex items-center gap-4 text-left">
<EventIcon meta={meta} name={name} size="sm" />
<span className="font-medium">{renderName()}</span>
</div>
<div className="pl-10">
<div className="flex origin-left scale-75 gap-1">
<SerieIcon name={props.country} />
<SerieIcon name={props.os} />
<SerieIcon name={props.browser} />
</div>
</div>
<div className="flex gap-4">
{profile && (
<Tooltiper asChild content={getProfileName(profile)}>
<Link
onClick={(e) => {
e.stopPropagation();
}}
to={'/$organizationId/$projectId/profiles/$profileId'}
params={{
organizationId,
projectId,
profileId: profile.id,
}}
className="max-w-[80px] overflow-hidden text-ellipsis whitespace-nowrap text-muted-foreground hover:underline"
>
{getProfileName(profile)}
</Link>
</Tooltiper>
)}
<Tooltiper asChild content={createdAt.toLocaleString()}>
<div className=" text-muted-foreground">
{createdAt.toLocaleTimeString()}
</div>
</div>
<div className="flex gap-4">
{profile && (
<Tooltiper asChild content={getProfileName(profile)}>
<Link
className="max-w-[80px] overflow-hidden text-ellipsis whitespace-nowrap text-muted-foreground hover:underline"
onClick={(e) => {
e.stopPropagation();
}}
params={{
organizationId,
projectId,
profileId: profile.id,
}}
to={'/$organizationId/$projectId/profiles/$profileId'}
>
{getProfileName(profile)}
</Link>
</Tooltiper>
</div>
</button>
</>
)}
<Tooltiper asChild content={createdAt.toLocaleString()}>
<div className="text-muted-foreground">
{createdAt.toLocaleTimeString()}
</div>
</Tooltiper>
</div>
</button>
);
}

View File

@@ -1,15 +1,14 @@
import type { IServiceEvent } from '@openpanel/db';
import type { ColumnDef } from '@tanstack/react-table';
import { ColumnCreatedAt } from '@/components/column-created-at';
import { EventIcon } from '@/components/events/event-icon';
import { ProjectLink } from '@/components/links';
import { ProfileAvatar } from '@/components/profiles/profile-avatar';
import { SerieIcon } from '@/components/report-chart/common/serie-icon';
import { KeyValueGrid } from '@/components/ui/key-value-grid';
import { useNumber } from '@/hooks/use-numer-formatter';
import { pushModal } from '@/modals';
import { getProfileName } from '@/utils/getters';
import type { ColumnDef } from '@tanstack/react-table';
import { ColumnCreatedAt } from '@/components/column-created-at';
import { ProfileAvatar } from '@/components/profiles/profile-avatar';
import { KeyValueGrid } from '@/components/ui/key-value-grid';
import type { IServiceEvent } from '@openpanel/db';
export function useColumns() {
const number = useNumber();
@@ -28,17 +27,24 @@ export function useColumns() {
accessorKey: 'name',
header: 'Name',
cell({ row }) {
const { name, path, duration, properties, revenue } = row.original;
const { name, path, revenue } = row.original;
const fullTitle =
name === 'screen_view'
? path
: name === 'revenue' && revenue
? `${name} (${number.currency(revenue / 100)})`
: name.replace(/_/g, ' ');
const renderName = () => {
if (name === 'screen_view') {
if (path.includes('/')) {
return <span className="max-w-md truncate">{path}</span>;
return path;
}
return (
<>
<span className="text-muted-foreground">Screen: </span>
<span className="max-w-md truncate">{path}</span>
{path}
</>
);
}
@@ -50,38 +56,27 @@ export function useColumns() {
return name.replace(/_/g, ' ');
};
const renderDuration = () => {
if (name === 'screen_view') {
return (
<span className="text-muted-foreground">
{number.shortWithUnit(duration / 1000, 'min')}
</span>
);
}
return null;
};
return (
<div className="flex items-center gap-2">
<div className="flex min-w-0 items-center gap-2">
<button
type="button"
className="transition-transform hover:scale-105"
className="shrink-0 transition-transform hover:scale-105"
onClick={() => {
pushModal('EditEvent', {
id: row.original.id,
});
}}
type="button"
>
<EventIcon
size="sm"
name={row.original.name}
meta={row.original.meta}
name={row.original.name}
size="sm"
/>
</button>
<span className="flex gap-2">
<span className="flex min-w-0 flex-1 gap-2">
<button
type="button"
className="min-w-0 max-w-full truncate text-left font-medium hover:underline"
title={fullTitle}
onClick={() => {
pushModal('EventDetails', {
id: row.original.id,
@@ -89,11 +84,10 @@ export function useColumns() {
projectId: row.original.projectId,
});
}}
className="font-medium hover:underline"
type="button"
>
{renderName()}
<span className="block truncate">{renderName()}</span>
</button>
{renderDuration()}
</span>
</div>
);
@@ -107,8 +101,8 @@ export function useColumns() {
if (profile) {
return (
<ProjectLink
className="group row items-center gap-2 whitespace-nowrap font-medium hover:underline"
href={`/profiles/${encodeURIComponent(profile.id)}`}
className="group whitespace-nowrap font-medium hover:underline row items-center gap-2"
>
<ProfileAvatar size="sm" {...profile} />
{getProfileName(profile)}
@@ -119,8 +113,8 @@ export function useColumns() {
if (profileId && profileId !== deviceId) {
return (
<ProjectLink
href={`/profiles/${encodeURIComponent(profileId)}`}
className="whitespace-nowrap font-medium hover:underline"
href={`/profiles/${encodeURIComponent(profileId)}`}
>
Unknown
</ProjectLink>
@@ -130,8 +124,8 @@ export function useColumns() {
if (deviceId) {
return (
<ProjectLink
href={`/profiles/${encodeURIComponent(deviceId)}`}
className="whitespace-nowrap font-medium hover:underline"
href={`/profiles/${encodeURIComponent(deviceId)}`}
>
Anonymous
</ProjectLink>
@@ -152,10 +146,10 @@ export function useColumns() {
const { sessionId } = row.original;
return (
<ProjectLink
href={`/sessions/${encodeURIComponent(sessionId)}`}
className="whitespace-nowrap font-medium hover:underline"
href={`/sessions/${encodeURIComponent(sessionId)}`}
>
{sessionId.slice(0,6)}
{sessionId.slice(0, 6)}
</ProjectLink>
);
},
@@ -175,7 +169,7 @@ export function useColumns() {
cell({ row }) {
const { country, city } = row.original;
return (
<div className="row items-center gap-2 min-w-0">
<div className="row min-w-0 items-center gap-2">
<SerieIcon name={country} />
<span className="truncate">{city}</span>
</div>
@@ -189,7 +183,7 @@ export function useColumns() {
cell({ row }) {
const { os } = row.original;
return (
<div className="row items-center gap-2 min-w-0">
<div className="row min-w-0 items-center gap-2">
<SerieIcon name={os} />
<span className="truncate">{os}</span>
</div>
@@ -203,7 +197,7 @@ export function useColumns() {
cell({ row }) {
const { browser } = row.original;
return (
<div className="row items-center gap-2 min-w-0">
<div className="row min-w-0 items-center gap-2">
<SerieIcon name={browser} />
<span className="truncate">{browser}</span>
</div>
@@ -221,14 +215,14 @@ export function useColumns() {
const { properties } = row.original;
const filteredProperties = Object.fromEntries(
Object.entries(properties || {}).filter(
([key]) => !key.startsWith('__'),
),
([key]) => !key.startsWith('__')
)
);
const items = Object.entries(filteredProperties);
const limit = 2;
const data = items.slice(0, limit).map(([key, value]) => ({
name: key,
value: value,
value,
}));
if (items.length > limit) {
data.push({