diff --git a/next.config.ts b/next.config.ts
index 070eba635..ab7fbf484 100644
--- a/next.config.ts
+++ b/next.config.ts
@@ -6,6 +6,8 @@ const withNextIntl = createNextIntlPlugin('./src/i18n/request.ts');
const TRACKER_SCRIPT = '/script.js';
+const isProd = process.env.NODE_ENV === 'production';
+
const basePath = process.env.BASE_PATH || '';
const cloudMode = process.env.CLOUD_MODE || '';
const cloudUrl = process.env.CLOUD_URL || '';
@@ -17,6 +19,8 @@ const forceSSL = process.env.FORCE_SSL || '';
const frameAncestors = process.env.ALLOWED_FRAME_URLS || '';
const trackerScriptName = process.env.TRACKER_SCRIPT_NAME || '';
const trackerScriptURL = process.env.TRACKER_SCRIPT_URL || '';
+const selfTrack = process.env.UMAMI_SELF_TRACK || '';
+const selfRecord = process.env.UMAMI_SELF_RECORD || '';
const contentSecurityPolicy = `
default-src 'self';
@@ -88,11 +92,14 @@ const headers = [
source: '/:path*',
headers: defaultHeaders,
},
- {
+];
+
+if (isProd) {
+ headers.push({
source: TRACKER_SCRIPT,
headers: trackerHeaders,
- },
-];
+ });
+}
const rewrites = [];
@@ -169,7 +176,7 @@ if (trackerScriptName) {
}
}
-if (cloudMode) {
+if (isProd && cloudMode) {
rewrites.push({
source: '/script.js',
destination: 'https://cloud.umami.is/script.js',
@@ -186,6 +193,8 @@ export default withNextIntl({
currentVersion: pkg.version,
defaultCurrency,
defaultLocale,
+ selfTrack,
+ selfRecord,
},
basePath,
output: 'standalone',
diff --git a/prisma/migrations/20_add_session_replay_visit_id/migration.sql b/prisma/migrations/20_add_session_replay_visit_id/migration.sql
new file mode 100644
index 000000000..3f16db6f6
--- /dev/null
+++ b/prisma/migrations/20_add_session_replay_visit_id/migration.sql
@@ -0,0 +1,36 @@
+ALTER TABLE "session_replay"
+ADD COLUMN IF NOT EXISTS "visit_id" UUID;
+
+UPDATE "session_replay" AS sr
+SET "visit_id" = we."visit_id"
+FROM (
+ SELECT DISTINCT ON ("website_id", "session_id")
+ "website_id",
+ "session_id",
+ "visit_id"
+ FROM "website_event"
+ WHERE "visit_id" IS NOT NULL
+ ORDER BY "website_id", "session_id", "created_at" DESC
+) AS we
+WHERE sr."visit_id" IS NULL
+ AND sr."website_id" = we."website_id"
+ AND sr."session_id" = we."session_id";
+
+UPDATE "session_replay"
+SET "visit_id" = (
+ substr(md5("session_id"::text || ':' || "chunk_index"::text), 1, 8) ||
+ '-' ||
+ substr(md5("session_id"::text || ':' || "chunk_index"::text), 9, 4) ||
+ '-' ||
+ substr(md5("session_id"::text || ':' || "chunk_index"::text), 13, 4) ||
+ '-' ||
+ substr(md5("session_id"::text || ':' || "chunk_index"::text), 17, 4) ||
+ '-' ||
+ substr(md5("session_id"::text || ':' || "chunk_index"::text), 21, 12)
+)::uuid
+WHERE "visit_id" IS NULL;
+
+ALTER TABLE "session_replay"
+ALTER COLUMN "visit_id" SET NOT NULL;
+
+CREATE INDEX IF NOT EXISTS "session_replay_visit_id_idx" ON "session_replay"("visit_id");
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index bcb6a86b3..92cdc649b 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -385,6 +385,7 @@ model SessionReplay {
id String @id() @map("replay_id") @db.Uuid
websiteId String @map("website_id") @db.Uuid
sessionId String @map("session_id") @db.Uuid
+ visitId String @map("visit_id") @db.Uuid
chunkIndex Int @map("chunk_index") @db.Integer
events Bytes @map("events")
eventCount Int @map("event_count") @db.Integer
@@ -396,6 +397,7 @@ model SessionReplay {
@@index([websiteId])
@@index([sessionId])
+ @@index([visitId])
@@index([websiteId, sessionId])
@@index([websiteId, createdAt])
@@index([sessionId, chunkIndex])
diff --git a/src/app/(main)/App.tsx b/src/app/(main)/App.tsx
index 6ac744aac..162d34023 100644
--- a/src/app/(main)/App.tsx
+++ b/src/app/(main)/App.tsx
@@ -58,6 +58,23 @@ export function App({ children }) {
{process.env.NODE_ENV === 'production' && !pathname.includes('/share/') && (
)}
+ {process.env.selfTrack && (
+
+ )}
+ {process.env.selfRecord && (
+
+ )}
);
}
diff --git a/src/app/(main)/boards/[boardId]/BoardViewHeader.tsx b/src/app/(main)/boards/[boardId]/BoardViewHeader.tsx
index 6303123b4..ccb070228 100644
--- a/src/app/(main)/boards/[boardId]/BoardViewHeader.tsx
+++ b/src/app/(main)/boards/[boardId]/BoardViewHeader.tsx
@@ -1,9 +1,9 @@
-import { Text } from '@umami/react-zen';
+import { Icon, Row, Text } from '@umami/react-zen';
import { IconLabel } from '@/components/common/IconLabel';
import { LinkButton } from '@/components/common/LinkButton';
import { PageHeader } from '@/components/common/PageHeader';
import { useBoard, useMessages, useNavigation, useWebsiteQuery } from '@/components/hooks';
-import { Edit } from '@/components/icons';
+import { Edit, Globe } from '@/components/icons';
export function BoardViewHeader() {
const { board } = useBoard();
@@ -13,10 +13,19 @@ export function BoardViewHeader() {
return (
- {website?.name && {website.name}}
-
- }>{t(labels.edit)}
-
+
+ {website?.name && (
+
+
+
+
+ {website.name}
+
+ )}
+
+ }>{t(labels.edit)}
+
+
);
}
diff --git a/src/app/(main)/console/[websiteId]/TestConsolePage.tsx b/src/app/(main)/console/[websiteId]/TestConsolePage.tsx
index 979ffb862..82c8e2494 100644
--- a/src/app/(main)/console/[websiteId]/TestConsolePage.tsx
+++ b/src/app/(main)/console/[websiteId]/TestConsolePage.tsx
@@ -115,6 +115,7 @@ export function TestConsolePage({ websiteId }: { websiteId: string }) {
data-website-id={websiteId}
src={`${process.env.basePath || ''}/script.js`}
data-cache="true"
+ data-performance="true"
/>