Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 1 addition & 13 deletions apps/dashboard/src/lib/components/EventsList.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { useQuery } from "$lib/zero/client.svelte";
import { recentEvents } from "$lib/zero/queries";
import type { Event } from "$lib/zero/schema";
import { formatTimestamp, formatProperties } from "$lib/utils/formatters";

let { projectId }: { projectId: string } = $props();

Expand Down Expand Up @@ -31,19 +32,6 @@
expandedId = expandedId === id ? null : id;
}

function formatTimestamp(ts: number): string {
return new Date(ts).toLocaleString();
}

function formatProperties(props: Record<string, unknown> | null | undefined): string {
if (!props) return "{}";
try {
return JSON.stringify(props, null, 2);
} catch {
return String(props);
}
}

function loadMore() {
displayLimit += 100;
}
Expand Down
5 changes: 1 addition & 4 deletions apps/dashboard/src/lib/components/FlagsList.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { useQuery, getZero } from "$lib/zero/client.svelte";
import { flagsForProject } from "$lib/zero/queries";
import type { Flag } from "$lib/zero/schema";
import { formatTimestamp } from "$lib/utils/formatters";

let { projectId }: { projectId: string } = $props();

Expand Down Expand Up @@ -40,10 +41,6 @@
if (!confirm("Delete this flag?")) return;
await zero.mutate.flags.delete({ id });
}

function formatTimestamp(ts: number): string {
return new Date(ts).toLocaleString();
}
</script>

<!-- Create Flag -->
Expand Down
14 changes: 1 addition & 13 deletions apps/dashboard/src/lib/components/SessionDetail.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { useQuery, useQueryOne } from "$lib/zero/client.svelte";
import { sessionById, eventsForSession } from "$lib/zero/queries";
import type { Session, Event } from "$lib/zero/schema";
import { formatTimestamp, formatProperties } from "$lib/utils/formatters";

let { sessionId }: { sessionId: string } = $props();

Expand All @@ -12,19 +13,6 @@
const loading = $derived(sessionQuery.loading || eventsQuery.loading);
const session = $derived(sessionQuery.data);

function formatTimestamp(ts: number): string {
return new Date(ts).toLocaleString();
}

function formatProperties(props: Record<string, unknown> | null | undefined): string {
if (!props) return "{}";
try {
return JSON.stringify(props, null, 2);
} catch {
return String(props);
}
}

// Expanded event ID for properties view
let expandedId = $state<string | null>(null);

Expand Down
16 changes: 1 addition & 15 deletions apps/dashboard/src/lib/components/SessionsList.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,12 @@
import { useQuery } from "$lib/zero/client.svelte";
import { recentSessions } from "$lib/zero/queries";
import type { Session } from "$lib/zero/schema";
import { formatTimestamp, formatRelativeTime } from "$lib/utils/formatters";

let { projectId }: { projectId: string } = $props();

// Use getter function so query re-runs if projectId changes
const sessionsQuery = useQuery<Session>(() => recentSessions(projectId, 100));

function formatTimestamp(ts: number): string {
return new Date(ts).toLocaleString();
}

function formatRelativeTime(ts: number): string {
const diff = Date.now() - ts;
const minutes = Math.floor(diff / 60000);
if (minutes < 1) return "Just now";
if (minutes < 60) return `${minutes}m ago`;
const hours = Math.floor(minutes / 60);
if (hours < 24) return `${hours}h ago`;
const days = Math.floor(hours / 24);
return `${days}d ago`;
}
</script>

<div class="bg-rp-surface rounded-lg border border-rp-overlay">
Expand Down
90 changes: 6 additions & 84 deletions apps/dashboard/src/lib/db.ts
Original file line number Diff line number Diff line change
@@ -1,88 +1,10 @@
/**
* PostgreSQL client for server-side queries.
*
* Note: Most data fetching is handled by Zero synced queries on the client.
* This module is primarily used for auth-related queries.
*/
import postgres from "postgres";
import { DATABASE_URL } from "$env/static/private";

export const sql = postgres(DATABASE_URL);

// Projects
export async function getProjects() {
return sql`SELECT id, name, api_key, created_at FROM projects ORDER BY created_at DESC`;
}

export async function getProject(id: string) {
const [project] = await sql`SELECT * FROM projects WHERE id = ${id}`;
return project;
}

// Events
export async function getRecentEvents(projectId: string, limit = 100) {
return sql`
SELECT id, event_name, properties, timestamp, session_id, user_id
FROM events
WHERE project_id = ${projectId}
ORDER BY timestamp DESC
LIMIT ${limit}
`;
}

export async function getEventCounts(projectId: string, days = 7) {
return sql`
SELECT
date_trunc('day', timestamp)::date as day,
event_name,
count(*)::int as count
FROM events
WHERE project_id = ${projectId}
AND timestamp > NOW() - INTERVAL '1 day' * ${days}
GROUP BY 1, 2
ORDER BY 1 DESC, 3 DESC
`;
}

// Sessions
export async function getSessions(projectId: string, limit = 50) {
return sql`
SELECT id, user_id, started_at, last_event_at, event_count, entry_url
FROM sessions
WHERE project_id = ${projectId}
ORDER BY started_at DESC
LIMIT ${limit}
`;
}

export async function getSessionEvents(sessionId: string) {
return sql`
SELECT event_name, properties, timestamp
FROM events
WHERE session_id = ${sessionId}
ORDER BY timestamp ASC
`;
}

// Flags
export async function getFlags(projectId: string) {
return sql`
SELECT id, key, name, enabled, updated_at
FROM flags
WHERE project_id = ${projectId}
ORDER BY key
`;
}

export async function toggleFlag(flagId: string, enabled: boolean) {
const [flag] = await sql`
UPDATE flags
SET enabled = ${enabled}, updated_at = NOW()
WHERE id = ${flagId}
RETURNING *
`;
return flag;
}

export async function createFlag(projectId: string, key: string, name: string) {
const [flag] = await sql`
INSERT INTO flags (project_id, key, name, enabled)
VALUES (${projectId}, ${key}, ${name}, false)
RETURNING *
`;
return flag;
}
38 changes: 38 additions & 0 deletions apps/dashboard/src/lib/utils/formatters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* Shared formatting utilities for the dashboard.
*/

/**
* Format a Unix timestamp to a locale-specific date/time string.
*/
export function formatTimestamp(ts: number): string {
return new Date(ts).toLocaleString();
}

/**
* Format a properties object as pretty-printed JSON.
*/
export function formatProperties(
props: Record<string, unknown> | null | undefined,
): string {
if (!props) return "{}";
try {
return JSON.stringify(props, null, 2);
} catch {
return String(props);
}
}

/**
* Format a timestamp as a relative time string (e.g., "5m ago", "2h ago").
*/
export function formatRelativeTime(ts: number): string {
const diff = Date.now() - ts;
const minutes = Math.floor(diff / 60000);
if (minutes < 1) return "Just now";
if (minutes < 60) return `${minutes}m ago`;
const hours = Math.floor(minutes / 60);
if (hours < 24) return `${hours}h ago`;
const days = Math.floor(hours / 24);
return `${days}d ago`;
}