From 2cff2676f958b86b8c4e743ea970a3d965cf4946 Mon Sep 17 00:00:00 2001 From: Michael Hespenheide Date: Mon, 11 Mar 2024 20:29:40 -0400 Subject: [PATCH 01/55] fix: Administrator label in users table (#2) --- src/app/(main)/profile/ProfileSettings.tsx | 2 +- src/app/(main)/settings/users/UserAddForm.tsx | 4 ++-- src/app/(main)/settings/users/[userId]/UserEditForm.tsx | 4 ++-- src/components/messages.ts | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/app/(main)/profile/ProfileSettings.tsx b/src/app/(main)/profile/ProfileSettings.tsx index 1021c37ed..9c7db39fb 100644 --- a/src/app/(main)/profile/ProfileSettings.tsx +++ b/src/app/(main)/profile/ProfileSettings.tsx @@ -23,7 +23,7 @@ export function ProfileSettings() { return formatMessage(labels.user); } if (value === ROLES.admin) { - return formatMessage(labels.administrator); + return formatMessage(labels.admin); } if (value === ROLES.viewOnly) { return formatMessage(labels.viewOnly); diff --git a/src/app/(main)/settings/users/UserAddForm.tsx b/src/app/(main)/settings/users/UserAddForm.tsx index 7ea720072..979f399fe 100644 --- a/src/app/(main)/settings/users/UserAddForm.tsx +++ b/src/app/(main)/settings/users/UserAddForm.tsx @@ -34,7 +34,7 @@ export function UserAddForm({ onSave, onClose }) { return formatMessage(labels.user); } if (value === ROLES.admin) { - return formatMessage(labels.administrator); + return formatMessage(labels.admin); } if (value === ROLES.viewOnly) { return formatMessage(labels.viewOnly); @@ -58,7 +58,7 @@ export function UserAddForm({ onSave, onClose }) { {formatMessage(labels.viewOnly)} {formatMessage(labels.user)} - {formatMessage(labels.administrator)} + {formatMessage(labels.admin)} diff --git a/src/app/(main)/settings/users/[userId]/UserEditForm.tsx b/src/app/(main)/settings/users/[userId]/UserEditForm.tsx index 369b4ff29..1acfc581a 100644 --- a/src/app/(main)/settings/users/[userId]/UserEditForm.tsx +++ b/src/app/(main)/settings/users/[userId]/UserEditForm.tsx @@ -46,7 +46,7 @@ export function UserEditForm({ userId, onSave }: { userId: string; onSave?: () = return formatMessage(labels.user); } if (value === ROLES.admin) { - return formatMessage(labels.administrator); + return formatMessage(labels.admin); } if (value === ROLES.viewOnly) { return formatMessage(labels.viewOnly); @@ -76,7 +76,7 @@ export function UserEditForm({ userId, onSave }: { userId: string; onSave?: () = {formatMessage(labels.viewOnly)} {formatMessage(labels.user)} - {formatMessage(labels.administrator)} + {formatMessage(labels.admin)} diff --git a/src/components/messages.ts b/src/components/messages.ts index 36bdf0574..9173ab3ff 100644 --- a/src/components/messages.ts +++ b/src/components/messages.ts @@ -18,7 +18,7 @@ export const labels = defineMessages({ user: { id: 'label.user', defaultMessage: 'User' }, viewOnly: { id: 'label.view-only', defaultMessage: 'View only' }, manage: { id: 'label.manage', defaultMessage: 'Manage' }, - administrator: { id: 'label.administrator', defaultMessage: 'Administrator' }, + admin: { id: 'label.administrator', defaultMessage: 'Administrator' }, confirm: { id: 'label.confirm', defaultMessage: 'Confirm' }, details: { id: 'label.details', defaultMessage: 'Details' }, website: { id: 'label.website', defaultMessage: 'Website' }, From c91ade01c1f240fde8f8deee0d30ed57a465389e Mon Sep 17 00:00:00 2001 From: Michael Hespenheide Date: Mon, 11 Mar 2024 20:55:24 -0400 Subject: [PATCH 02/55] fix: Update admininstrator -> admin for translations (#3) --- public/intl/messages/am-ET.json | 2 +- public/intl/messages/ar-SA.json | 2 +- public/intl/messages/be-BY.json | 2 +- public/intl/messages/bn-BD.json | 2 +- public/intl/messages/ca-ES.json | 2 +- public/intl/messages/cs-CZ.json | 2 +- public/intl/messages/da-DK.json | 2 +- public/intl/messages/de-CH.json | 2 +- public/intl/messages/de-DE.json | 2 +- public/intl/messages/el-GR.json | 2 +- public/intl/messages/en-GB.json | 2 +- public/intl/messages/en-US.json | 2 +- public/intl/messages/es-ES.json | 2 +- public/intl/messages/fa-IR.json | 2 +- public/intl/messages/fi-FI.json | 2 +- public/intl/messages/fo-FO.json | 2 +- public/intl/messages/fr-FR.json | 2 +- public/intl/messages/ga-ES.json | 2 +- public/intl/messages/he-IL.json | 2 +- public/intl/messages/hi-IN.json | 2 +- public/intl/messages/hr-HR.json | 2 +- public/intl/messages/hu-HU.json | 2 +- public/intl/messages/id-ID.json | 2 +- public/intl/messages/it-IT.json | 2 +- public/intl/messages/ja-JP.json | 2 +- public/intl/messages/km-KH.json | 2 +- public/intl/messages/ko-KR.json | 2 +- public/intl/messages/lt-LT.json | 2 +- public/intl/messages/mn-MN.json | 2 +- public/intl/messages/ms-MY.json | 2 +- public/intl/messages/my-MM.json | 2 +- public/intl/messages/nb-NO.json | 2 +- public/intl/messages/nl-NL.json | 2 +- public/intl/messages/pl-PL.json | 2 +- public/intl/messages/pt-BR.json | 2 +- public/intl/messages/pt-PT.json | 2 +- public/intl/messages/ro-RO.json | 2 +- public/intl/messages/ru-RU.json | 2 +- public/intl/messages/si-LK.json | 2 +- public/intl/messages/sk-SK.json | 2 +- public/intl/messages/sl-SI.json | 2 +- public/intl/messages/sv-SE.json | 2 +- public/intl/messages/ta-IN.json | 2 +- public/intl/messages/th-TH.json | 2 +- public/intl/messages/tr-TR.json | 2 +- public/intl/messages/uk-UA.json | 2 +- public/intl/messages/ur-PK.json | 2 +- public/intl/messages/vi-VN.json | 2 +- public/intl/messages/zh-CN.json | 2 +- public/intl/messages/zh-TW.json | 2 +- src/components/messages.ts | 2 +- src/lang/am-ET.json | 2 +- src/lang/ar-SA.json | 2 +- src/lang/be-BY.json | 2 +- src/lang/bn-BD.json | 2 +- src/lang/ca-ES.json | 2 +- src/lang/cs-CZ.json | 2 +- src/lang/da-DK.json | 2 +- src/lang/de-CH.json | 2 +- src/lang/de-DE.json | 2 +- src/lang/el-GR.json | 2 +- src/lang/en-GB.json | 2 +- src/lang/en-US.json | 2 +- src/lang/es-ES.json | 2 +- src/lang/fa-IR.json | 2 +- src/lang/fi-FI.json | 2 +- src/lang/fo-FO.json | 2 +- src/lang/fr-FR.json | 2 +- src/lang/ga-ES.json | 2 +- src/lang/he-IL.json | 2 +- src/lang/hi-IN.json | 2 +- src/lang/hr-HR.json | 2 +- src/lang/hu-HU.json | 2 +- src/lang/id-ID.json | 2 +- src/lang/it-IT.json | 2 +- src/lang/ja-JP.json | 2 +- src/lang/km-KH.json | 2 +- src/lang/ko-KR.json | 2 +- src/lang/lt-LT.json | 2 +- src/lang/mn-MN.json | 2 +- src/lang/ms-MY.json | 2 +- src/lang/my-MM.json | 2 +- src/lang/nb-NO.json | 2 +- src/lang/nl-NL.json | 2 +- src/lang/pl-PL.json | 2 +- src/lang/pt-BR.json | 2 +- src/lang/pt-PT.json | 2 +- src/lang/ro-RO.json | 2 +- src/lang/ru-RU.json | 2 +- src/lang/si-LK.json | 2 +- src/lang/sk-SK.json | 2 +- src/lang/sl-SI.json | 2 +- src/lang/sv-SE.json | 2 +- src/lang/ta-IN.json | 2 +- src/lang/th-TH.json | 2 +- src/lang/tr-TR.json | 2 +- src/lang/uk-UA.json | 2 +- src/lang/ur-PK.json | 2 +- src/lang/vi-VN.json | 2 +- src/lang/zh-CN.json | 2 +- src/lang/zh-TW.json | 2 +- 101 files changed, 101 insertions(+), 101 deletions(-) diff --git a/public/intl/messages/am-ET.json b/public/intl/messages/am-ET.json index 5ed374974..5f8f984d6 100644 --- a/public/intl/messages/am-ET.json +++ b/public/intl/messages/am-ET.json @@ -41,7 +41,7 @@ "value": "Add website" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "Administrator" diff --git a/public/intl/messages/ar-SA.json b/public/intl/messages/ar-SA.json index 08b7e51f2..42dea85be 100644 --- a/public/intl/messages/ar-SA.json +++ b/public/intl/messages/ar-SA.json @@ -41,7 +41,7 @@ "value": "إضافة موقع" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "مدير" diff --git a/public/intl/messages/be-BY.json b/public/intl/messages/be-BY.json index dd9d1000b..17c512b4a 100644 --- a/public/intl/messages/be-BY.json +++ b/public/intl/messages/be-BY.json @@ -41,7 +41,7 @@ "value": "Дадаць сайт" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "Адміністратар" diff --git a/public/intl/messages/bn-BD.json b/public/intl/messages/bn-BD.json index e75da8f39..569d56808 100644 --- a/public/intl/messages/bn-BD.json +++ b/public/intl/messages/bn-BD.json @@ -41,7 +41,7 @@ "value": "ওয়েবসাইট যুক্ত করুন" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "অ্যাডমিন" diff --git a/public/intl/messages/ca-ES.json b/public/intl/messages/ca-ES.json index 89daf8a14..d5261697b 100644 --- a/public/intl/messages/ca-ES.json +++ b/public/intl/messages/ca-ES.json @@ -41,7 +41,7 @@ "value": "Afegeix lloc web" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "Administrador" diff --git a/public/intl/messages/cs-CZ.json b/public/intl/messages/cs-CZ.json index 8495aea0f..679ac8879 100644 --- a/public/intl/messages/cs-CZ.json +++ b/public/intl/messages/cs-CZ.json @@ -41,7 +41,7 @@ "value": "Přidat web" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "Administrátor" diff --git a/public/intl/messages/da-DK.json b/public/intl/messages/da-DK.json index 63da5bea9..7da7e8567 100644 --- a/public/intl/messages/da-DK.json +++ b/public/intl/messages/da-DK.json @@ -41,7 +41,7 @@ "value": "Tilføj hjemmeside" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "Administrator" diff --git a/public/intl/messages/de-CH.json b/public/intl/messages/de-CH.json index 9120e8c30..1cd613b80 100644 --- a/public/intl/messages/de-CH.json +++ b/public/intl/messages/de-CH.json @@ -41,7 +41,7 @@ "value": "Websiite hinzuefüege" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "Administrator" diff --git a/public/intl/messages/de-DE.json b/public/intl/messages/de-DE.json index 8268f5d73..3a2856dcb 100644 --- a/public/intl/messages/de-DE.json +++ b/public/intl/messages/de-DE.json @@ -41,7 +41,7 @@ "value": "Website hinzufügen" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "Administrator" diff --git a/public/intl/messages/el-GR.json b/public/intl/messages/el-GR.json index 598f7b6f4..c76a1bbdf 100644 --- a/public/intl/messages/el-GR.json +++ b/public/intl/messages/el-GR.json @@ -41,7 +41,7 @@ "value": "Προσθήκη ιστότοπου" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "Διαχειριστής" diff --git a/public/intl/messages/en-GB.json b/public/intl/messages/en-GB.json index c34650927..c58975e61 100644 --- a/public/intl/messages/en-GB.json +++ b/public/intl/messages/en-GB.json @@ -41,7 +41,7 @@ "value": "Add website" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "Administrator" diff --git a/public/intl/messages/en-US.json b/public/intl/messages/en-US.json index c666b361c..6a856ebfc 100644 --- a/public/intl/messages/en-US.json +++ b/public/intl/messages/en-US.json @@ -41,7 +41,7 @@ "value": "Add website" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "Administrator" diff --git a/public/intl/messages/es-ES.json b/public/intl/messages/es-ES.json index 869652895..0cb6edf0a 100644 --- a/public/intl/messages/es-ES.json +++ b/public/intl/messages/es-ES.json @@ -41,7 +41,7 @@ "value": "Nuevo sitio web" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "Administrador" diff --git a/public/intl/messages/fa-IR.json b/public/intl/messages/fa-IR.json index 221879c57..b0bef8a13 100644 --- a/public/intl/messages/fa-IR.json +++ b/public/intl/messages/fa-IR.json @@ -41,7 +41,7 @@ "value": "افزودن وب‌سایت" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "مدیر" diff --git a/public/intl/messages/fi-FI.json b/public/intl/messages/fi-FI.json index d4c39d62a..775731050 100644 --- a/public/intl/messages/fi-FI.json +++ b/public/intl/messages/fi-FI.json @@ -41,7 +41,7 @@ "value": "Lisää verkkosivu" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "Järjestelmänvalvoja" diff --git a/public/intl/messages/fo-FO.json b/public/intl/messages/fo-FO.json index 32c36503f..07db5d231 100644 --- a/public/intl/messages/fo-FO.json +++ b/public/intl/messages/fo-FO.json @@ -41,7 +41,7 @@ "value": "Legg heimasíðu afturat" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "Fyrisitari" diff --git a/public/intl/messages/fr-FR.json b/public/intl/messages/fr-FR.json index 6ab90a036..3e3452796 100644 --- a/public/intl/messages/fr-FR.json +++ b/public/intl/messages/fr-FR.json @@ -41,7 +41,7 @@ "value": "Ajouter un site" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "Administrateur" diff --git a/public/intl/messages/ga-ES.json b/public/intl/messages/ga-ES.json index ea32f939a..84e7c411d 100644 --- a/public/intl/messages/ga-ES.json +++ b/public/intl/messages/ga-ES.json @@ -41,7 +41,7 @@ "value": "Engadir sitio web" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "Administradora" diff --git a/public/intl/messages/he-IL.json b/public/intl/messages/he-IL.json index c6fadb62b..c0f31bb98 100644 --- a/public/intl/messages/he-IL.json +++ b/public/intl/messages/he-IL.json @@ -41,7 +41,7 @@ "value": "הוספת אתר" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "מנהל" diff --git a/public/intl/messages/hi-IN.json b/public/intl/messages/hi-IN.json index 0411eb3b1..dd5f1c690 100644 --- a/public/intl/messages/hi-IN.json +++ b/public/intl/messages/hi-IN.json @@ -41,7 +41,7 @@ "value": "वेबसाइट" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "प्रशासक" diff --git a/public/intl/messages/hr-HR.json b/public/intl/messages/hr-HR.json index 468e823a0..ef4a351e7 100644 --- a/public/intl/messages/hr-HR.json +++ b/public/intl/messages/hr-HR.json @@ -41,7 +41,7 @@ "value": "Dodaj web stranicu" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "Administrator" diff --git a/public/intl/messages/hu-HU.json b/public/intl/messages/hu-HU.json index 48e86d582..cd6aca19e 100644 --- a/public/intl/messages/hu-HU.json +++ b/public/intl/messages/hu-HU.json @@ -41,7 +41,7 @@ "value": "Weboldal hozzáadása" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "Adminisztrátor" diff --git a/public/intl/messages/id-ID.json b/public/intl/messages/id-ID.json index 911dd9156..c072168c1 100644 --- a/public/intl/messages/id-ID.json +++ b/public/intl/messages/id-ID.json @@ -41,7 +41,7 @@ "value": "Tambah situs web" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "Pengelola" diff --git a/public/intl/messages/it-IT.json b/public/intl/messages/it-IT.json index 98f4819f0..a5ef85252 100644 --- a/public/intl/messages/it-IT.json +++ b/public/intl/messages/it-IT.json @@ -41,7 +41,7 @@ "value": "Aggiungi sito" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "Amministratore" diff --git a/public/intl/messages/ja-JP.json b/public/intl/messages/ja-JP.json index a0fb59ed9..0aba80b2c 100644 --- a/public/intl/messages/ja-JP.json +++ b/public/intl/messages/ja-JP.json @@ -41,7 +41,7 @@ "value": "Webサイトの追加" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "管理者" diff --git a/public/intl/messages/km-KH.json b/public/intl/messages/km-KH.json index c8911fca8..c49866370 100644 --- a/public/intl/messages/km-KH.json +++ b/public/intl/messages/km-KH.json @@ -41,7 +41,7 @@ "value": "បន្ថែមគេហទំព័រ" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "អ្នកគ្រប់គ្រង" diff --git a/public/intl/messages/ko-KR.json b/public/intl/messages/ko-KR.json index 920893675..909c8d905 100644 --- a/public/intl/messages/ko-KR.json +++ b/public/intl/messages/ko-KR.json @@ -41,7 +41,7 @@ "value": "웹사이트 추가" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "관리자" diff --git a/public/intl/messages/lt-LT.json b/public/intl/messages/lt-LT.json index dcf8fe5b5..127c6d678 100644 --- a/public/intl/messages/lt-LT.json +++ b/public/intl/messages/lt-LT.json @@ -41,7 +41,7 @@ "value": "Pridėti svetainę" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "Administratorius" diff --git a/public/intl/messages/mn-MN.json b/public/intl/messages/mn-MN.json index 5d97a1237..75e13a294 100644 --- a/public/intl/messages/mn-MN.json +++ b/public/intl/messages/mn-MN.json @@ -41,7 +41,7 @@ "value": "Веб нэмэх" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "Админ" diff --git a/public/intl/messages/ms-MY.json b/public/intl/messages/ms-MY.json index d85f08f74..1afbbc7b7 100644 --- a/public/intl/messages/ms-MY.json +++ b/public/intl/messages/ms-MY.json @@ -41,7 +41,7 @@ "value": "Tambah laman web" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "Pentadbir" diff --git a/public/intl/messages/my-MM.json b/public/intl/messages/my-MM.json index 9b5f8a127..07469c83e 100644 --- a/public/intl/messages/my-MM.json +++ b/public/intl/messages/my-MM.json @@ -41,7 +41,7 @@ "value": "ဝက်ဘ်ဆိုဒ်ထည့်မည်" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "အက်ဒမင်" diff --git a/public/intl/messages/nb-NO.json b/public/intl/messages/nb-NO.json index 39aa52fe9..c9d4354a9 100644 --- a/public/intl/messages/nb-NO.json +++ b/public/intl/messages/nb-NO.json @@ -41,7 +41,7 @@ "value": "Legg til nettsted" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "Administrator" diff --git a/public/intl/messages/nl-NL.json b/public/intl/messages/nl-NL.json index f92948f62..f474f237f 100644 --- a/public/intl/messages/nl-NL.json +++ b/public/intl/messages/nl-NL.json @@ -41,7 +41,7 @@ "value": "Website koppelen" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "Beheerder" diff --git a/public/intl/messages/pl-PL.json b/public/intl/messages/pl-PL.json index 7834b2182..d07e9fa5d 100644 --- a/public/intl/messages/pl-PL.json +++ b/public/intl/messages/pl-PL.json @@ -41,7 +41,7 @@ "value": "Dodaj witrynę" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "Administrator" diff --git a/public/intl/messages/pt-BR.json b/public/intl/messages/pt-BR.json index af420197c..b3ffe6d9a 100644 --- a/public/intl/messages/pt-BR.json +++ b/public/intl/messages/pt-BR.json @@ -41,7 +41,7 @@ "value": "Adicionar site" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "Administrador" diff --git a/public/intl/messages/pt-PT.json b/public/intl/messages/pt-PT.json index add7ccb7d..99df041a0 100644 --- a/public/intl/messages/pt-PT.json +++ b/public/intl/messages/pt-PT.json @@ -41,7 +41,7 @@ "value": "Adicionar website" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "Administrador" diff --git a/public/intl/messages/ro-RO.json b/public/intl/messages/ro-RO.json index 10625a082..9d1d4080b 100644 --- a/public/intl/messages/ro-RO.json +++ b/public/intl/messages/ro-RO.json @@ -41,7 +41,7 @@ "value": "Adăugare site web" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "Administrator" diff --git a/public/intl/messages/ru-RU.json b/public/intl/messages/ru-RU.json index 86949d67d..3400f48de 100644 --- a/public/intl/messages/ru-RU.json +++ b/public/intl/messages/ru-RU.json @@ -41,7 +41,7 @@ "value": "Добавить сайт" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "Администратор" diff --git a/public/intl/messages/si-LK.json b/public/intl/messages/si-LK.json index f4e6b31bc..6507bac4f 100644 --- a/public/intl/messages/si-LK.json +++ b/public/intl/messages/si-LK.json @@ -41,7 +41,7 @@ "value": "වෙබ් අඩවිය එක් කරන්න" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "Administrator" diff --git a/public/intl/messages/sk-SK.json b/public/intl/messages/sk-SK.json index dfbcd0649..01096f0f7 100644 --- a/public/intl/messages/sk-SK.json +++ b/public/intl/messages/sk-SK.json @@ -41,7 +41,7 @@ "value": "Pridať web" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "Administrátor" diff --git a/public/intl/messages/sl-SI.json b/public/intl/messages/sl-SI.json index 2c29c8275..3fdea0436 100644 --- a/public/intl/messages/sl-SI.json +++ b/public/intl/messages/sl-SI.json @@ -41,7 +41,7 @@ "value": "Dodaj spletno mesto" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "Administrator" diff --git a/public/intl/messages/sv-SE.json b/public/intl/messages/sv-SE.json index 93103d51d..1e1397490 100644 --- a/public/intl/messages/sv-SE.json +++ b/public/intl/messages/sv-SE.json @@ -41,7 +41,7 @@ "value": "Lägg till webbplats" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "Administratör" diff --git a/public/intl/messages/ta-IN.json b/public/intl/messages/ta-IN.json index f183b55ba..430802191 100644 --- a/public/intl/messages/ta-IN.json +++ b/public/intl/messages/ta-IN.json @@ -41,7 +41,7 @@ "value": "வலைத்தளத்தைச் சேர்க்க" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "நிர்வாகியைச் சேர்க்க" diff --git a/public/intl/messages/th-TH.json b/public/intl/messages/th-TH.json index 0e1a70c09..a867f5578 100644 --- a/public/intl/messages/th-TH.json +++ b/public/intl/messages/th-TH.json @@ -41,7 +41,7 @@ "value": "เพิ่มเว็บไซต์" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "ผู้ดูแลระบบ" diff --git a/public/intl/messages/tr-TR.json b/public/intl/messages/tr-TR.json index d2fdb0a00..86883cc96 100644 --- a/public/intl/messages/tr-TR.json +++ b/public/intl/messages/tr-TR.json @@ -41,7 +41,7 @@ "value": "Web sitesi ekle" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "Yönetici" diff --git a/public/intl/messages/uk-UA.json b/public/intl/messages/uk-UA.json index 89f95612c..b83b2ce4c 100644 --- a/public/intl/messages/uk-UA.json +++ b/public/intl/messages/uk-UA.json @@ -41,7 +41,7 @@ "value": "Додати сайт" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "Адміністратор" diff --git a/public/intl/messages/ur-PK.json b/public/intl/messages/ur-PK.json index 2de7bc4d8..4cc0c72c6 100644 --- a/public/intl/messages/ur-PK.json +++ b/public/intl/messages/ur-PK.json @@ -41,7 +41,7 @@ "value": "ویب سائٹ کا اضافہ کریں" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "منتظم" diff --git a/public/intl/messages/vi-VN.json b/public/intl/messages/vi-VN.json index 733278f36..25b64a850 100644 --- a/public/intl/messages/vi-VN.json +++ b/public/intl/messages/vi-VN.json @@ -41,7 +41,7 @@ "value": "Thêm website" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "Quản trị" diff --git a/public/intl/messages/zh-CN.json b/public/intl/messages/zh-CN.json index 85b788c5a..7b2189ca1 100644 --- a/public/intl/messages/zh-CN.json +++ b/public/intl/messages/zh-CN.json @@ -41,7 +41,7 @@ "value": "添加网站" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "管理员" diff --git a/public/intl/messages/zh-TW.json b/public/intl/messages/zh-TW.json index af2a37a1c..b4ad7cf5b 100644 --- a/public/intl/messages/zh-TW.json +++ b/public/intl/messages/zh-TW.json @@ -41,7 +41,7 @@ "value": "新增網站" } ], - "label.administrator": [ + "label.admin": [ { "type": 0, "value": "管理員" diff --git a/src/components/messages.ts b/src/components/messages.ts index 9173ab3ff..ed5203946 100644 --- a/src/components/messages.ts +++ b/src/components/messages.ts @@ -18,7 +18,7 @@ export const labels = defineMessages({ user: { id: 'label.user', defaultMessage: 'User' }, viewOnly: { id: 'label.view-only', defaultMessage: 'View only' }, manage: { id: 'label.manage', defaultMessage: 'Manage' }, - admin: { id: 'label.administrator', defaultMessage: 'Administrator' }, + admin: { id: 'label.admin', defaultMessage: 'Administrator' }, confirm: { id: 'label.confirm', defaultMessage: 'Confirm' }, details: { id: 'label.details', defaultMessage: 'Details' }, website: { id: 'label.website', defaultMessage: 'Website' }, diff --git a/src/lang/am-ET.json b/src/lang/am-ET.json index 5eeb1dfc7..817e9e010 100644 --- a/src/lang/am-ET.json +++ b/src/lang/am-ET.json @@ -6,7 +6,7 @@ "label.add-description": "Add description", "label.add-member": "Add member", "label.add-website": "Add website", - "label.administrator": "Administrator", + "label.admin": "Administrator", "label.after": "After", "label.all": "All", "label.all-time": "All time", diff --git a/src/lang/ar-SA.json b/src/lang/ar-SA.json index ae5ba9e47..24620b2c9 100644 --- a/src/lang/ar-SA.json +++ b/src/lang/ar-SA.json @@ -6,7 +6,7 @@ "label.add-description": "أضِف وصف", "label.add-member": "أضِف عضو", "label.add-website": "إضافة موقع", - "label.administrator": "مدير", + "label.admin": "مدير", "label.after": "يعد", "label.all": "الكل", "label.all-time": "كل الوقت", diff --git a/src/lang/be-BY.json b/src/lang/be-BY.json index 9f4e5b173..d1a097c98 100644 --- a/src/lang/be-BY.json +++ b/src/lang/be-BY.json @@ -6,7 +6,7 @@ "label.add-description": "Add description", "label.add-member": "Add member", "label.add-website": "Дадаць сайт", - "label.administrator": "Адміністратар", + "label.admin": "Адміністратар", "label.after": "After", "label.all": "Усё", "label.all-time": "Увесь час", diff --git a/src/lang/bn-BD.json b/src/lang/bn-BD.json index 27e0398fd..fc855446f 100644 --- a/src/lang/bn-BD.json +++ b/src/lang/bn-BD.json @@ -6,7 +6,7 @@ "label.add-description": "Add description", "label.add-member": "Add member", "label.add-website": "ওয়েবসাইট যুক্ত করুন", - "label.administrator": "অ্যাডমিন", + "label.admin": "অ্যাডমিন", "label.after": "After", "label.all": "সবগুলো", "label.all-time": "সব সময়", diff --git a/src/lang/ca-ES.json b/src/lang/ca-ES.json index 85dd48808..4b1c71bce 100644 --- a/src/lang/ca-ES.json +++ b/src/lang/ca-ES.json @@ -6,7 +6,7 @@ "label.add-description": "Add description", "label.add-member": "Add member", "label.add-website": "Afegeix lloc web", - "label.administrator": "Administrador", + "label.admin": "Administrador", "label.after": "After", "label.all": "Tots", "label.all-time": "Sempre", diff --git a/src/lang/cs-CZ.json b/src/lang/cs-CZ.json index 969ef92e6..86a8b6016 100644 --- a/src/lang/cs-CZ.json +++ b/src/lang/cs-CZ.json @@ -6,7 +6,7 @@ "label.add-description": "Add description", "label.add-member": "Add member", "label.add-website": "Přidat web", - "label.administrator": "Administrátor", + "label.admin": "Administrátor", "label.after": "After", "label.all": "Vše", "label.all-time": "All time", diff --git a/src/lang/da-DK.json b/src/lang/da-DK.json index 5b2ae12f8..1c3ccf172 100644 --- a/src/lang/da-DK.json +++ b/src/lang/da-DK.json @@ -6,7 +6,7 @@ "label.add-description": "Add description", "label.add-member": "Add member", "label.add-website": "Tilføj hjemmeside", - "label.administrator": "Administrator", + "label.admin": "Administrator", "label.after": "After", "label.all": "Alle", "label.all-time": "Altid", diff --git a/src/lang/de-CH.json b/src/lang/de-CH.json index c0a88b1cd..4702d6cc2 100644 --- a/src/lang/de-CH.json +++ b/src/lang/de-CH.json @@ -6,7 +6,7 @@ "label.add-description": "Add description", "label.add-member": "Add member", "label.add-website": "Websiite hinzuefüege", - "label.administrator": "Administrator", + "label.admin": "Administrator", "label.after": "After", "label.all": "Alli", "label.all-time": "Gesamte Zitruum", diff --git a/src/lang/de-DE.json b/src/lang/de-DE.json index f5ee3a69c..a466231ce 100644 --- a/src/lang/de-DE.json +++ b/src/lang/de-DE.json @@ -6,7 +6,7 @@ "label.add-description": "Beschreibung hinzufügen", "label.add-member": "Add member", "label.add-website": "Website hinzufügen", - "label.administrator": "Administrator", + "label.admin": "Administrator", "label.after": "Nach", "label.all": "Alle", "label.all-time": "Gesamter Zeitraum", diff --git a/src/lang/el-GR.json b/src/lang/el-GR.json index 62b1166bd..2764b17e0 100644 --- a/src/lang/el-GR.json +++ b/src/lang/el-GR.json @@ -6,7 +6,7 @@ "label.add-description": "Add description", "label.add-member": "Add member", "label.add-website": "Προσθήκη ιστότοπου", - "label.administrator": "Διαχειριστής", + "label.admin": "Διαχειριστής", "label.after": "After", "label.all": "All", "label.all-time": "All time", diff --git a/src/lang/en-GB.json b/src/lang/en-GB.json index 107067589..cdc101c89 100644 --- a/src/lang/en-GB.json +++ b/src/lang/en-GB.json @@ -6,7 +6,7 @@ "label.add-description": "Add description", "label.add-member": "Add member", "label.add-website": "Add website", - "label.administrator": "Administrator", + "label.admin": "Administrator", "label.after": "After", "label.all": "All", "label.all-time": "All time", diff --git a/src/lang/en-US.json b/src/lang/en-US.json index cfc0da14d..2a28d8448 100644 --- a/src/lang/en-US.json +++ b/src/lang/en-US.json @@ -6,7 +6,7 @@ "label.add-description": "Add description", "label.add-member": "Add member", "label.add-website": "Add website", - "label.administrator": "Administrator", + "label.admin": "Administrator", "label.after": "After", "label.all": "All", "label.all-time": "All time", diff --git a/src/lang/es-ES.json b/src/lang/es-ES.json index 7fbb841aa..c8c294756 100644 --- a/src/lang/es-ES.json +++ b/src/lang/es-ES.json @@ -6,7 +6,7 @@ "label.add-description": "Añadir descripción", "label.add-member": "Añadir miembro", "label.add-website": "Nuevo sitio web", - "label.administrator": "Administrador", + "label.admin": "Administrador", "label.after": "Después", "label.all": "Todos", "label.all-time": "Todos los tiempos", diff --git a/src/lang/fa-IR.json b/src/lang/fa-IR.json index 238874690..36be8adda 100644 --- a/src/lang/fa-IR.json +++ b/src/lang/fa-IR.json @@ -6,7 +6,7 @@ "label.add-description": "Add description", "label.add-member": "Add member", "label.add-website": "افزودن وب‌سایت", - "label.administrator": "مدیر", + "label.admin": "مدیر", "label.after": "After", "label.all": "همه", "label.all-time": "همه زمان", diff --git a/src/lang/fi-FI.json b/src/lang/fi-FI.json index 13d4c52d0..bc9048dc4 100644 --- a/src/lang/fi-FI.json +++ b/src/lang/fi-FI.json @@ -6,7 +6,7 @@ "label.add-description": "Add description", "label.add-member": "Add member", "label.add-website": "Lisää verkkosivu", - "label.administrator": "Järjestelmänvalvoja", + "label.admin": "Järjestelmänvalvoja", "label.after": "After", "label.all": "Kaikki", "label.all-time": "Alusta lähtien", diff --git a/src/lang/fo-FO.json b/src/lang/fo-FO.json index 157f280fa..8fad3c943 100644 --- a/src/lang/fo-FO.json +++ b/src/lang/fo-FO.json @@ -6,7 +6,7 @@ "label.add-description": "Add description", "label.add-member": "Add member", "label.add-website": "Legg heimasíðu afturat", - "label.administrator": "Fyrisitari", + "label.admin": "Fyrisitari", "label.after": "After", "label.all": "Alt", "label.all-time": "All time", diff --git a/src/lang/fr-FR.json b/src/lang/fr-FR.json index a93aa1bf2..bbff27fd2 100644 --- a/src/lang/fr-FR.json +++ b/src/lang/fr-FR.json @@ -6,7 +6,7 @@ "label.add-description": "Ajouter une description", "label.add-member": "Ajouter un membre", "label.add-website": "Ajouter un site", - "label.administrator": "Administrateur", + "label.admin": "Administrateur", "label.after": "Après", "label.all": "Tout", "label.all-time": "Toutes les données", diff --git a/src/lang/ga-ES.json b/src/lang/ga-ES.json index 5bb060498..45bac85eb 100644 --- a/src/lang/ga-ES.json +++ b/src/lang/ga-ES.json @@ -6,7 +6,7 @@ "label.add-description": "Add description", "label.add-member": "Add member", "label.add-website": "Engadir sitio web", - "label.administrator": "Administradora", + "label.admin": "Administradora", "label.after": "After", "label.all": "Todo", "label.all-time": "Sempre", diff --git a/src/lang/he-IL.json b/src/lang/he-IL.json index ad75ab233..c95a41191 100644 --- a/src/lang/he-IL.json +++ b/src/lang/he-IL.json @@ -6,7 +6,7 @@ "label.add-description": "Add description", "label.add-member": "Add member", "label.add-website": "הוספת אתר", - "label.administrator": "מנהל", + "label.admin": "מנהל", "label.after": "After", "label.all": "הכל", "label.all-time": "All time", diff --git a/src/lang/hi-IN.json b/src/lang/hi-IN.json index eea62ddff..e90e3bbf4 100644 --- a/src/lang/hi-IN.json +++ b/src/lang/hi-IN.json @@ -6,7 +6,7 @@ "label.add-description": "Add description", "label.add-member": "Add member", "label.add-website": "वेबसाइट", - "label.administrator": "प्रशासक", + "label.admin": "प्रशासक", "label.after": "After", "label.all": "सब", "label.all-time": "All time", diff --git a/src/lang/hr-HR.json b/src/lang/hr-HR.json index 8349bb175..563af35d7 100644 --- a/src/lang/hr-HR.json +++ b/src/lang/hr-HR.json @@ -6,7 +6,7 @@ "label.add-description": "Add description", "label.add-member": "Add member", "label.add-website": "Dodaj web stranicu", - "label.administrator": "Administrator", + "label.admin": "Administrator", "label.after": "After", "label.all": "Sve", "label.all-time": "Svo vrijeme", diff --git a/src/lang/hu-HU.json b/src/lang/hu-HU.json index 9cde27daf..6a1be2be1 100644 --- a/src/lang/hu-HU.json +++ b/src/lang/hu-HU.json @@ -6,7 +6,7 @@ "label.add-description": "Add description", "label.add-member": "Add member", "label.add-website": "Weboldal hozzáadása", - "label.administrator": "Adminisztrátor", + "label.admin": "Adminisztrátor", "label.after": "After", "label.all": "Összes", "label.all-time": "All time", diff --git a/src/lang/id-ID.json b/src/lang/id-ID.json index 6f842f9a7..00fc71a5d 100644 --- a/src/lang/id-ID.json +++ b/src/lang/id-ID.json @@ -6,7 +6,7 @@ "label.add-description": "Add description", "label.add-member": "Add member", "label.add-website": "Tambah situs web", - "label.administrator": "Pengelola", + "label.admin": "Pengelola", "label.after": "After", "label.all": "Semua", "label.all-time": "Semua waktu", diff --git a/src/lang/it-IT.json b/src/lang/it-IT.json index 1c4bd85b7..330e92f70 100644 --- a/src/lang/it-IT.json +++ b/src/lang/it-IT.json @@ -6,7 +6,7 @@ "label.add-description": "Add description", "label.add-member": "Add member", "label.add-website": "Aggiungi sito", - "label.administrator": "Amministratore", + "label.admin": "Amministratore", "label.after": "After", "label.all": "Tutto", "label.all-time": "Sempre", diff --git a/src/lang/ja-JP.json b/src/lang/ja-JP.json index ed7c96c1e..1b164df45 100644 --- a/src/lang/ja-JP.json +++ b/src/lang/ja-JP.json @@ -6,7 +6,7 @@ "label.add-description": "説明を追加", "label.add-member": "メンバーの追加", "label.add-website": "Webサイトの追加", - "label.administrator": "管理者", + "label.admin": "管理者", "label.after": "直後", "label.all": "すべて", "label.all-time": "すべての時間帯", diff --git a/src/lang/km-KH.json b/src/lang/km-KH.json index 18da6a047..9da1971ad 100644 --- a/src/lang/km-KH.json +++ b/src/lang/km-KH.json @@ -6,7 +6,7 @@ "label.add-description": "Add description", "label.add-member": "Add member", "label.add-website": "បន្ថែមគេហទំព័រ", - "label.administrator": "អ្នកគ្រប់គ្រង", + "label.admin": "អ្នកគ្រប់គ្រង", "label.after": "After", "label.all": "ទាំងអស់", "label.all-time": "គ្រប់ពេល", diff --git a/src/lang/ko-KR.json b/src/lang/ko-KR.json index cf42f7951..e21a3f766 100644 --- a/src/lang/ko-KR.json +++ b/src/lang/ko-KR.json @@ -6,7 +6,7 @@ "label.add-description": "Add description", "label.add-member": "Add member", "label.add-website": "웹사이트 추가", - "label.administrator": "관리자", + "label.admin": "관리자", "label.after": "After", "label.all": "전체", "label.all-time": "All time", diff --git a/src/lang/lt-LT.json b/src/lang/lt-LT.json index 61ca1f8c6..368a15a6e 100644 --- a/src/lang/lt-LT.json +++ b/src/lang/lt-LT.json @@ -6,7 +6,7 @@ "label.add-description": "Add description", "label.add-member": "Add member", "label.add-website": "Pridėti svetainę", - "label.administrator": "Administratorius", + "label.admin": "Administratorius", "label.after": "After", "label.all": "Visi", "label.all-time": "Visas laikotarpis", diff --git a/src/lang/mn-MN.json b/src/lang/mn-MN.json index 301572d6d..02dc40526 100644 --- a/src/lang/mn-MN.json +++ b/src/lang/mn-MN.json @@ -6,7 +6,7 @@ "label.add-description": "Тайлбар нэмэх", "label.add-member": "Add member", "label.add-website": "Веб нэмэх", - "label.administrator": "Админ", + "label.admin": "Админ", "label.after": "Хойно", "label.all": "Бүх", "label.all-time": "Бүх цаг үеийн", diff --git a/src/lang/ms-MY.json b/src/lang/ms-MY.json index 65d07cb9f..69b7e5545 100644 --- a/src/lang/ms-MY.json +++ b/src/lang/ms-MY.json @@ -6,7 +6,7 @@ "label.add-description": "Add description", "label.add-member": "Add member", "label.add-website": "Tambah laman web", - "label.administrator": "Pentadbir", + "label.admin": "Pentadbir", "label.after": "After", "label.all": "Semua", "label.all-time": "All time", diff --git a/src/lang/my-MM.json b/src/lang/my-MM.json index 84b8aa886..ce27b83fb 100644 --- a/src/lang/my-MM.json +++ b/src/lang/my-MM.json @@ -6,7 +6,7 @@ "label.add-description": "အကြောင်းအရာဖော်ပြချက် ထည့်မည်", "label.add-member": "Add member", "label.add-website": "ဝက်ဘ်ဆိုဒ်ထည့်မည်", - "label.administrator": "အက်ဒမင်", + "label.admin": "အက်ဒမင်", "label.after": "ပြီးနောက်", "label.all": "အားလုံး", "label.all-time": "အချိန်အစမှအခုထိ", diff --git a/src/lang/nb-NO.json b/src/lang/nb-NO.json index 7b1c197b9..e39cc6ad9 100644 --- a/src/lang/nb-NO.json +++ b/src/lang/nb-NO.json @@ -6,7 +6,7 @@ "label.add-description": "Add description", "label.add-member": "Add member", "label.add-website": "Legg til nettsted", - "label.administrator": "Administrator", + "label.admin": "Administrator", "label.after": "After", "label.all": "Alle", "label.all-time": "Noensinne", diff --git a/src/lang/nl-NL.json b/src/lang/nl-NL.json index c3a38584f..0e3da90dc 100644 --- a/src/lang/nl-NL.json +++ b/src/lang/nl-NL.json @@ -6,7 +6,7 @@ "label.add-description": "Add description", "label.add-member": "Add member", "label.add-website": "Website koppelen", - "label.administrator": "Beheerder", + "label.admin": "Beheerder", "label.after": "After", "label.all": "Alles", "label.all-time": "Onbeperkt", diff --git a/src/lang/pl-PL.json b/src/lang/pl-PL.json index da8c9a43f..99f668500 100644 --- a/src/lang/pl-PL.json +++ b/src/lang/pl-PL.json @@ -6,7 +6,7 @@ "label.add-description": "Dodaj opis", "label.add-member": "Add member", "label.add-website": "Dodaj witrynę", - "label.administrator": "Administrator", + "label.admin": "Administrator", "label.after": "Po", "label.all": "Wszystkie", "label.all-time": "Cały czas", diff --git a/src/lang/pt-BR.json b/src/lang/pt-BR.json index 94f4d907d..2c34ab332 100644 --- a/src/lang/pt-BR.json +++ b/src/lang/pt-BR.json @@ -6,7 +6,7 @@ "label.add-description": "Add description", "label.add-member": "Add member", "label.add-website": "Adicionar site", - "label.administrator": "Administrador", + "label.admin": "Administrador", "label.after": "Depois", "label.all": "Todos", "label.all-time": "Todo o período", diff --git a/src/lang/pt-PT.json b/src/lang/pt-PT.json index 2c0858bbb..3bbc5e707 100644 --- a/src/lang/pt-PT.json +++ b/src/lang/pt-PT.json @@ -6,7 +6,7 @@ "label.add-description": "Add description", "label.add-member": "Add member", "label.add-website": "Adicionar website", - "label.administrator": "Administrador", + "label.admin": "Administrador", "label.after": "After", "label.all": "Todos", "label.all-time": "Todo o tempo", diff --git a/src/lang/ro-RO.json b/src/lang/ro-RO.json index 28722d4d1..8b9ffa025 100644 --- a/src/lang/ro-RO.json +++ b/src/lang/ro-RO.json @@ -6,7 +6,7 @@ "label.add-description": "Adaugă descriere", "label.add-member": "Adaugă membru", "label.add-website": "Adăugare site web", - "label.administrator": "Administrator", + "label.admin": "Administrator", "label.after": "După", "label.all": "Toate", "label.all-time": "Pentru tot timpul", diff --git a/src/lang/ru-RU.json b/src/lang/ru-RU.json index 6cd2097b7..78425bf4d 100644 --- a/src/lang/ru-RU.json +++ b/src/lang/ru-RU.json @@ -6,7 +6,7 @@ "label.add-description": "Add description", "label.add-member": "Add member", "label.add-website": "Добавить сайт", - "label.administrator": "Администратор", + "label.admin": "Администратор", "label.after": "After", "label.all": "Все", "label.all-time": "Все время", diff --git a/src/lang/si-LK.json b/src/lang/si-LK.json index 389d8cb12..7e65c9ae7 100644 --- a/src/lang/si-LK.json +++ b/src/lang/si-LK.json @@ -6,7 +6,7 @@ "label.add-description": "Add description", "label.add-member": "Add member", "label.add-website": "වෙබ් අඩවිය එක් කරන්න", - "label.administrator": "Administrator", + "label.admin": "Administrator", "label.after": "After", "label.all": "සියල්ල", "label.all-time": "හැම වෙලාවෙම", diff --git a/src/lang/sk-SK.json b/src/lang/sk-SK.json index b09b6e138..f82150093 100644 --- a/src/lang/sk-SK.json +++ b/src/lang/sk-SK.json @@ -6,7 +6,7 @@ "label.add-description": "Add description", "label.add-member": "Add member", "label.add-website": "Pridať web", - "label.administrator": "Administrátor", + "label.admin": "Administrátor", "label.after": "After", "label.all": "Všetko", "label.all-time": "All time", diff --git a/src/lang/sl-SI.json b/src/lang/sl-SI.json index 2d9b64780..2368a4f38 100644 --- a/src/lang/sl-SI.json +++ b/src/lang/sl-SI.json @@ -6,7 +6,7 @@ "label.add-description": "Dodaj opis", "label.add-member": "Add member", "label.add-website": "Dodaj spletno mesto", - "label.administrator": "Administrator", + "label.admin": "Administrator", "label.after": "Po", "label.all": "Vsi", "label.all-time": "Ves čas", diff --git a/src/lang/sv-SE.json b/src/lang/sv-SE.json index 0a9f9390e..37738596f 100644 --- a/src/lang/sv-SE.json +++ b/src/lang/sv-SE.json @@ -6,7 +6,7 @@ "label.add-description": "Lägg till beskrivning", "label.add-member": "Add member", "label.add-website": "Lägg till webbplats", - "label.administrator": "Administratör", + "label.admin": "Administratör", "label.after": "Efter", "label.all": "Alla", "label.all-time": "Sedan början", diff --git a/src/lang/ta-IN.json b/src/lang/ta-IN.json index 3241fc78e..0eca3c454 100644 --- a/src/lang/ta-IN.json +++ b/src/lang/ta-IN.json @@ -6,7 +6,7 @@ "label.add-description": "Add description", "label.add-member": "Add member", "label.add-website": "வலைத்தளத்தைச் சேர்க்க", - "label.administrator": "நிர்வாகியைச் சேர்க்க", + "label.admin": "நிர்வாகியைச் சேர்க்க", "label.after": "After", "label.all": "எல்லாம்", "label.all-time": "All time", diff --git a/src/lang/th-TH.json b/src/lang/th-TH.json index 5b74f5da9..11b94c1ce 100644 --- a/src/lang/th-TH.json +++ b/src/lang/th-TH.json @@ -6,7 +6,7 @@ "label.add-description": "Add description", "label.add-member": "Add member", "label.add-website": "เพิ่มเว็บไซต์", - "label.administrator": "ผู้ดูแลระบบ", + "label.admin": "ผู้ดูแลระบบ", "label.after": "After", "label.all": "ทั้งหมด", "label.all-time": "ทุกช่วงเวลา", diff --git a/src/lang/tr-TR.json b/src/lang/tr-TR.json index c778e66ad..4249c836d 100644 --- a/src/lang/tr-TR.json +++ b/src/lang/tr-TR.json @@ -6,7 +6,7 @@ "label.add-description": "Add description", "label.add-member": "Add member", "label.add-website": "Web sitesi ekle", - "label.administrator": "Yönetici", + "label.admin": "Yönetici", "label.after": "After", "label.all": "Tümü", "label.all-time": "All time", diff --git a/src/lang/uk-UA.json b/src/lang/uk-UA.json index 6cd12006e..e0da73718 100644 --- a/src/lang/uk-UA.json +++ b/src/lang/uk-UA.json @@ -6,7 +6,7 @@ "label.add-description": "Add description", "label.add-member": "Add member", "label.add-website": "Додати сайт", - "label.administrator": "Адміністратор", + "label.admin": "Адміністратор", "label.after": "After", "label.all": "Всі", "label.all-time": "Весь час", diff --git a/src/lang/ur-PK.json b/src/lang/ur-PK.json index 04b809023..c4565af35 100644 --- a/src/lang/ur-PK.json +++ b/src/lang/ur-PK.json @@ -6,7 +6,7 @@ "label.add-description": "Add description", "label.add-member": "Add member", "label.add-website": "ویب سائٹ کا اضافہ کریں", - "label.administrator": "منتظم", + "label.admin": "منتظم", "label.after": "After", "label.all": "تمام", "label.all-time": "تمام وقت", diff --git a/src/lang/vi-VN.json b/src/lang/vi-VN.json index 042dfe1d2..13ee815da 100644 --- a/src/lang/vi-VN.json +++ b/src/lang/vi-VN.json @@ -6,7 +6,7 @@ "label.add-description": "Add description", "label.add-member": "Add member", "label.add-website": "Thêm website", - "label.administrator": "Quản trị", + "label.admin": "Quản trị", "label.after": "After", "label.all": "Tất cả", "label.all-time": "Toàn thời gian", diff --git a/src/lang/zh-CN.json b/src/lang/zh-CN.json index 93b29b7cb..be8f33f46 100644 --- a/src/lang/zh-CN.json +++ b/src/lang/zh-CN.json @@ -6,7 +6,7 @@ "label.add-description": "添加描述", "label.add-member": "Add member", "label.add-website": "添加网站", - "label.administrator": "管理员", + "label.admin": "管理员", "label.after": "之后", "label.all": "所有", "label.all-time": "所有时间段", diff --git a/src/lang/zh-TW.json b/src/lang/zh-TW.json index cf4bfbd91..d837e8c7f 100644 --- a/src/lang/zh-TW.json +++ b/src/lang/zh-TW.json @@ -6,7 +6,7 @@ "label.add-description": "新增描述", "label.add-member": "Add member", "label.add-website": "新增網站", - "label.administrator": "管理員", + "label.admin": "管理員", "label.after": "之後", "label.all": "全部", "label.all-time": "所有時間", From 226c6e313fe4acf9107dd770609e3d0e9249ef5f Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Mon, 11 Mar 2024 19:13:53 -0700 Subject: [PATCH 03/55] Fixed page titles in tracker. --- src/tracker/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tracker/index.js b/src/tracker/index.js index 606da9257..87e79ef68 100644 --- a/src/tracker/index.js +++ b/src/tracker/index.js @@ -44,7 +44,7 @@ hostname, screen, language, - title: encodeURIComponent(title), + title: title ? encodeURIComponent(title) : undefined, url: encodeURI(currentUrl), referrer: encodeURI(currentRef), }); @@ -79,7 +79,7 @@ const handleTitleChanges = () => { const observer = new MutationObserver(([entry]) => { - title = entry && entry.target ? entry.target.text : undefined; + title = entry && entry.target ? entry.target.data : undefined; }); const node = document.querySelector('head > title'); From cbeefe733f92fcc416c5d4f38cce66851888d000 Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Thu, 21 Mar 2024 09:30:42 -0700 Subject: [PATCH 04/55] add psql migration --- db/clickhouse/schema.sql | 1 + .../migrations/05_add_visit_id/migration.sql | 14 ++++++++++++++ db/postgresql/schema.prisma | 3 +++ 3 files changed, 18 insertions(+) create mode 100644 db/postgresql/migrations/05_add_visit_id/migration.sql diff --git a/db/clickhouse/schema.sql b/db/clickhouse/schema.sql index 741f06ad2..dad4f4af3 100644 --- a/db/clickhouse/schema.sql +++ b/db/clickhouse/schema.sql @@ -3,6 +3,7 @@ CREATE TABLE umami.website_event ( website_id UUID, session_id UUID, + visit_id UUID, event_id UUID, --sessions hostname LowCardinality(String), diff --git a/db/postgresql/migrations/05_add_visit_id/migration.sql b/db/postgresql/migrations/05_add_visit_id/migration.sql new file mode 100644 index 000000000..b70b6a047 --- /dev/null +++ b/db/postgresql/migrations/05_add_visit_id/migration.sql @@ -0,0 +1,14 @@ +-- AlterTable +ALTER TABLE "website_event" ADD COLUMN "session_id" UUID NULL; + +UPDATE "website_event" +SET session_id = uuid_in(overlay(overlay(md5(CONCAT(session_id::text, to_char(date_trunc('hour', created_at), 'YYYY-MM-DD HH24:00:00'))) placing '4' from 13) placing '8' from 17)::cstring) +WHERE session_id IS NULL; + +ALTER TABLE "website_event" ALTER COLUMN "session_id" SET NOT NULL; + +-- CreateIndex +CREATE INDEX "website_event_visit_id_idx" ON "website_event"("visit_id"); + +-- CreateIndex +CREATE INDEX "website_event_website_id_visit_id_created_at_idx" ON "website_event"("website_id", "visit_id", "created_at"); diff --git a/db/postgresql/schema.prisma b/db/postgresql/schema.prisma index 31cc7616d..0cb8ae8a2 100644 --- a/db/postgresql/schema.prisma +++ b/db/postgresql/schema.prisma @@ -92,6 +92,7 @@ model WebsiteEvent { id String @id() @map("event_id") @db.Uuid websiteId String @map("website_id") @db.Uuid sessionId String @map("session_id") @db.Uuid + visitId String @map("visit_id") @db.Uuid createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6) urlPath String @map("url_path") @db.VarChar(500) urlQuery String? @map("url_query") @db.VarChar(500) @@ -107,6 +108,7 @@ model WebsiteEvent { @@index([createdAt]) @@index([sessionId]) + @@index([visitId]) @@index([websiteId]) @@index([websiteId, createdAt]) @@index([websiteId, createdAt, urlPath]) @@ -115,6 +117,7 @@ model WebsiteEvent { @@index([websiteId, createdAt, pageTitle]) @@index([websiteId, createdAt, eventName]) @@index([websiteId, sessionId, createdAt]) + @@index([websiteId, visitId, createdAt]) @@map("website_event") } From b36d61609d2df7be2218832aac73365503291e9c Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Thu, 21 Mar 2024 09:36:27 -0700 Subject: [PATCH 05/55] fix migration bug --- db/postgresql/migrations/05_add_visit_id/migration.sql | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/db/postgresql/migrations/05_add_visit_id/migration.sql b/db/postgresql/migrations/05_add_visit_id/migration.sql index b70b6a047..8274d1fb4 100644 --- a/db/postgresql/migrations/05_add_visit_id/migration.sql +++ b/db/postgresql/migrations/05_add_visit_id/migration.sql @@ -1,14 +1,14 @@ -- AlterTable -ALTER TABLE "website_event" ADD COLUMN "session_id" UUID NULL; +ALTER TABLE "website_event" ADD COLUMN "visit_id" UUID NULL; UPDATE "website_event" -SET session_id = uuid_in(overlay(overlay(md5(CONCAT(session_id::text, to_char(date_trunc('hour', created_at), 'YYYY-MM-DD HH24:00:00'))) placing '4' from 13) placing '8' from 17)::cstring) -WHERE session_id IS NULL; +SET visit_id = uuid_in(overlay(overlay(md5(CONCAT(session_id::text, to_char(date_trunc('hour', created_at), 'YYYY-MM-DD HH24:00:00'))) placing '4' from 13) placing '8' from 17)::cstring) +WHERE visit_id IS NULL; -ALTER TABLE "website_event" ALTER COLUMN "session_id" SET NOT NULL; +ALTER TABLE "website_event" ALTER COLUMN "visit_id" SET NOT NULL; -- CreateIndex CREATE INDEX "website_event_visit_id_idx" ON "website_event"("visit_id"); -- CreateIndex -CREATE INDEX "website_event_website_id_visit_id_created_at_idx" ON "website_event"("website_id", "visit_id", "created_at"); +CREATE INDEX "website_event_website_id_visit_id_created_at_idx" ON "website_event"("website_id", "visit_id", "created_at"); \ No newline at end of file From af1a118374edd3c0502230c56ae9f77e4288643a Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Thu, 21 Mar 2024 11:10:13 -0700 Subject: [PATCH 06/55] update mysql migrations --- .../migrations/05_add_visit_id/migration.sql | 21 +++++++++++++++++++ db/mysql/schema.prisma | 3 +++ 2 files changed, 24 insertions(+) create mode 100644 db/mysql/migrations/05_add_visit_id/migration.sql diff --git a/db/mysql/migrations/05_add_visit_id/migration.sql b/db/mysql/migrations/05_add_visit_id/migration.sql new file mode 100644 index 000000000..f40340067 --- /dev/null +++ b/db/mysql/migrations/05_add_visit_id/migration.sql @@ -0,0 +1,21 @@ +-- AlterTable +ALTER TABLE "website_event" ADD COLUMN "visit_id" VARCHAR(36) NULL; + +UPDATE "website_event" we +JOIN (SELECT s.session_id, + s.visit_time, + BIN_TO_UUID(RANDOM_BYTES(16) & 0xffffffffffff0fff3fffffffffffffff | 0x00000000000040008000000000000000) uuid + FROM (SELECT DISTINCT session_id, + DATE_FORMAT(created_at, '%Y-%m-%d %H:00:00') visit_time + FROM umami.website_event) s) a + ON we.session_id = a.session_id and DATE_FORMAT(we.created_at, '%Y-%m-%d %H:00:00') = a.visit_time +SET we.visit_id = a.uuid +WHERE we.visit_id IS NULL; + +ALTER TABLE "website_event" MODIFY "visit_id" VARCHAR(36) NOT NULL; + +-- CreateIndex +CREATE INDEX `website_event_visit_id_idx` ON `website_event`(`visit_id`); + +-- CreateIndex +CREATE INDEX `website_event_website_id_visit_id_created_at_idx` ON `website_event`(`website_id`, `visit_id`, `created_at`); diff --git a/db/mysql/schema.prisma b/db/mysql/schema.prisma index 8e5cbbc33..152ca265b 100644 --- a/db/mysql/schema.prisma +++ b/db/mysql/schema.prisma @@ -92,6 +92,7 @@ model WebsiteEvent { id String @id() @map("event_id") @db.VarChar(36) websiteId String @map("website_id") @db.VarChar(36) sessionId String @map("session_id") @db.VarChar(36) + visitId String @map("visit_id") @db.VarChar(36) createdAt DateTime? @default(now()) @map("created_at") @db.Timestamp(0) urlPath String @map("url_path") @db.VarChar(500) urlQuery String? @map("url_query") @db.VarChar(500) @@ -107,6 +108,7 @@ model WebsiteEvent { @@index([createdAt]) @@index([sessionId]) + @@index([visitId]) @@index([websiteId]) @@index([websiteId, createdAt]) @@index([websiteId, createdAt, urlPath]) @@ -115,6 +117,7 @@ model WebsiteEvent { @@index([websiteId, createdAt, pageTitle]) @@index([websiteId, createdAt, eventName]) @@index([websiteId, sessionId, createdAt]) + @@index([websiteId, visitId, createdAt]) @@map("website_event") } From 91a4cb4487660466250b9987919a4d12f0443664 Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Thu, 21 Mar 2024 11:33:35 -0700 Subject: [PATCH 07/55] add mysql column population --- db/mysql/migrations/05_add_visit_id/migration.sql | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/db/mysql/migrations/05_add_visit_id/migration.sql b/db/mysql/migrations/05_add_visit_id/migration.sql index f40340067..7a833a885 100644 --- a/db/mysql/migrations/05_add_visit_id/migration.sql +++ b/db/mysql/migrations/05_add_visit_id/migration.sql @@ -1,18 +1,19 @@ -- AlterTable -ALTER TABLE "website_event" ADD COLUMN "visit_id" VARCHAR(36) NULL; +ALTER TABLE `website_event` ADD COLUMN `visit_id` VARCHAR(36) NULL; -UPDATE "website_event" we -JOIN (SELECT s.session_id, +UPDATE `website_event` we +JOIN (SELECT DISTINCT + s.session_id, s.visit_time, BIN_TO_UUID(RANDOM_BYTES(16) & 0xffffffffffff0fff3fffffffffffffff | 0x00000000000040008000000000000000) uuid FROM (SELECT DISTINCT session_id, DATE_FORMAT(created_at, '%Y-%m-%d %H:00:00') visit_time - FROM umami.website_event) s) a + FROM `website_event`) s) a ON we.session_id = a.session_id and DATE_FORMAT(we.created_at, '%Y-%m-%d %H:00:00') = a.visit_time SET we.visit_id = a.uuid WHERE we.visit_id IS NULL; -ALTER TABLE "website_event" MODIFY "visit_id" VARCHAR(36) NOT NULL; +ALTER TABLE `website_event` MODIFY `visit_id` VARCHAR(36) NOT NULL; -- CreateIndex CREATE INDEX `website_event_visit_id_idx` ON `website_event`(`visit_id`); From 5faa7ddc74f7fdae9d0c0d3f8d1fc554753dec40 Mon Sep 17 00:00:00 2001 From: Kipras Melnikovas Date: Fri, 22 Mar 2024 04:09:53 +0200 Subject: [PATCH 08/55] update LT lang, part 2 --- src/lang/lt-LT.json | 212 ++++++++++++++++++++++---------------------- 1 file changed, 106 insertions(+), 106 deletions(-) diff --git a/src/lang/lt-LT.json b/src/lang/lt-LT.json index 61ca1f8c6..eacbdb7e6 100644 --- a/src/lang/lt-LT.json +++ b/src/lang/lt-LT.json @@ -1,237 +1,237 @@ { - "label.access-code": "Access code", + "label.access-code": "Prieigos kodas", "label.actions": "Veiksmai", - "label.activity-log": "Activity log", - "label.add": "Add", - "label.add-description": "Add description", - "label.add-member": "Add member", + "label.activity-log": "Veiklos žurnalas", + "label.add": "Pridėti", + "label.add-description": "Pridėti aprašymą", + "label.add-member": "Pridėti narį", "label.add-website": "Pridėti svetainę", "label.administrator": "Administratorius", - "label.after": "After", + "label.after": "Po", "label.all": "Visi", "label.all-time": "Visas laikotarpis", "label.analytics": "Analytics", - "label.average": "Average", + "label.average": "Vidurkis", "label.average-visit-time": "Vidutinė vizito trukmė", "label.back": "Atgal", - "label.before": "Before", + "label.before": "Prieš", "label.bounce-rate": "Atmetimo rodiklis", "label.breakdown": "Breakdown", - "label.browser": "Browser", + "label.browser": "Naršyklė", "label.browsers": "Naršyklės", "label.cancel": "Atšaukti", "label.change-password": "Pakeisti slaptažodį", - "label.cities": "Cities", - "label.city": "City", - "label.clear-all": "Clear all", - "label.confirm": "Confirm", + "label.cities": "Miestai", + "label.city": "Miestas", + "label.clear-all": "Išvalyti visus", + "label.confirm": "Patvirtinti", "label.confirm-password": "Patvirtinti slaptažodį", "label.contains": "Contains", "label.continue": "Continue", "label.countries": "Šalys", - "label.country": "Country", - "label.create": "Create", - "label.create-report": "Create report", - "label.create-team": "Create team", - "label.create-user": "Create user", + "label.country": "Šalis", + "label.create": "Sukurti", + "label.create-report": "Kurti ataskaitą", + "label.create-team": "Sukurti komandą", + "label.create-user": "Sukurti vartotoją", "label.created": "Created", "label.created-by": "Created By", "label.current-password": "Dabartinis slaptažodis", "label.custom-range": "Pasirinktinis intervalas", "label.dashboard": "Švieslentė", - "label.data": "Data", - "label.date": "Date", + "label.data": "Duomenys", + "label.date": "Data", "label.date-range": "Laikotarpis", - "label.day": "Day", + "label.day": "Diena", "label.default-date-range": "Numatytasis laikotarpis", "label.delete": "Ištrinti", - "label.delete-report": "Delete report", - "label.delete-team": "Delete team", - "label.delete-user": "Delete user", + "label.delete-report": "Ištrinti ataskaitą", + "label.delete-team": "Ištrinti komandą", + "label.delete-user": "Ištrinti vartotoją", "label.delete-website": "Ištrinti svetainę", - "label.description": "Description", + "label.description": "Aprašymas", "label.desktop": "Desktop", - "label.details": "Details", - "label.device": "Device", + "label.details": "Detalės", + "label.device": "Įrenginys", "label.devices": "Įrenginiai", "label.dismiss": "Gerai", "label.does-not-contain": "Does not contain", "label.domain": "Domenas", "label.dropoff": "Dropoff", "label.edit": "Redaguoti", - "label.edit-dashboard": "Edit dashboard", - "label.edit-member": "Edit member", + "label.edit-dashboard": "Redaguoti švieslentę", + "label.edit-member": "Redaguoti narį", "label.enable-share-url": "Įjungti bendrinimą su nuoroda", - "label.event": "Event", - "label.event-data": "Event data", + "label.event": "Įvykis", + "label.event-data": "Įvykių duomenys", "label.events": "Įvykiai", "label.false": "False", - "label.field": "Field", - "label.fields": "Fields", - "label.filter": "Filter", + "label.field": "Laukelis", + "label.fields": "Laukeliai", + "label.filter": "Filtruoti", "label.filter-combined": "Kombinuoti", "label.filter-raw": "Neapdoroti", - "label.filters": "Filters", + "label.filters": "Filtrai", "label.funnel": "Funnel", "label.funnel-description": "Understand the conversion and drop-off rate of users.", "label.greater-than": "Greater than", "label.greater-than-equals": "Greater than or equals", - "label.insights": "Insights", - "label.insights-description": "Dive deeper into your data by using segments and filters.", + "label.insights": "Įžvalgos", + "label.insights-description": "Pasinerkite giliau į savo duomenis naudodami segmentus ir filtrus.", "label.is": "Is", "label.is-not": "Is not", "label.is-not-set": "Is not set", "label.is-set": "Is set", - "label.join": "Join", - "label.join-team": "Join team", - "label.language": "Language", + "label.join": "Prisijungti", + "label.join-team": "Prisijungti į komandą", + "label.language": "Kalba", "label.languages": "Kalbos", "label.laptop": "Laptop", "label.last-days": "{x, plural, =0 {Paskutinės # dienų} zero {Paskutinės # dienų} one {Paskutinė diena} other {Paskutinės # dienos}}", "label.last-hours": "{x, plural, =0 {Paskutinės # valandų} zero {Paskutinės # valandų} one {Paskutinė # valanda} other {Paskutinės # valandos}}", - "label.leave": "Leave", - "label.leave-team": "Leave team", + "label.leave": "Išeiti", + "label.leave-team": "Išeiti iš komandos", "label.less-than": "Less than", "label.less-than-equals": "Less than or equals", "label.login": "Prisijungti", "label.logout": "Atsijungti", - "label.manage": "Manage", + "label.manage": "Tvarkyti", "label.max": "Max", - "label.member": "Member", - "label.members": "Members", + "label.member": "Narys", + "label.members": "Nariai", "label.min": "Min", "label.mobile": "Mobilusis", "label.more": "Daugiau", - "label.my-account": "My account", - "label.my-websites": "My websites", + "label.my-account": "Mano paskyra", + "label.my-websites": "Mano svetainės", "label.name": "Pavadinimas", "label.new-password": "Naujas slaptažodis", "label.none": "None", "label.number-of-records": "{x} {x, plural, one {record} other {records}}", "label.ok": "OK", - "label.os": "OS", - "label.overview": "Overview", + "label.os": "Operacinės sistemos", + "label.overview": "Apžvalga", "label.owner": "Savininkas", - "label.page-of": "Page {current} of {total}", + "label.page-of": "Puslapis {current} iš {total}", "label.page-views": "Puslapių peržiūros", - "label.pageTitle": "Page title", + "label.pageTitle": "Puslapio pavadinimas", "label.pages": "Puslapiai", "label.password": "Slaptažodis", "label.powered-by": "Powered by {name}", "label.profile": "Profilis", - "label.queries": "Queries", - "label.query": "Query", - "label.query-parameters": "Query parameters", + "label.queries": "Užklausos", + "label.query": "Užklausa", + "label.query-parameters": "Užklausų parametrai", "label.realtime": "Realiuoju laiku", - "label.referrer": "Referrer", - "label.referrers": "Referrers", + "label.referrer": "Persiuntėjas", + "label.referrers": "Persiuntėjai", "label.refresh": "Atnaujinti", "label.regenerate": "Regenerate", - "label.region": "Region", - "label.regions": "Regions", - "label.remove": "Remove", - "label.remove-member": "Remove member", - "label.reports": "Reports", + "label.region": "Regionas", + "label.regions": "Regionai", + "label.remove": "Pašalinti", + "label.remove-member": "Pašalinti narį", + "label.reports": "Ataskaitos", "label.required": "Reikalinga", "label.reset": "Atstatyti", "label.reset-website": "Atstatyti statistikos duomenis", "label.retention": "Retention", "label.retention-description": "Measure your website stickiness by tracking how often users return.", - "label.role": "Role", + "label.role": "Rolė", "label.run-query": "Run query", "label.save": "Išsaugoti", - "label.screens": "Screens", - "label.search": "Search", + "label.screens": "Ekranai", + "label.search": "Ieškoti", "label.select": "Select", - "label.select-date": "Select date", - "label.select-role": "Select role", - "label.select-website": "Select website", - "label.sessions": "Sessions", + "label.select-date": "Pasirinkti laikotarpį", + "label.select-role": "Pasirinkti rolę", + "label.select-website": "Pasirinkti svetainę", + "label.sessions": "Sesijos", "label.settings": "Nustatymai", "label.share-url": "Pasidalinti nuoroda", "label.single-day": "Viena diena", - "label.sum": "Sum", + "label.sum": "Suma", "label.tablet": "Planšetė", - "label.team": "Team", - "label.team-id": "Team ID", - "label.team-member": "Team member", - "label.team-name": "Team name", - "label.team-owner": "Team owner", + "label.team": "Komanda", + "label.team-id": "Komandos ID", + "label.team-member": "Komandos narys", + "label.team-name": "Komandos pavadinimas", + "label.team-owner": "Komandos savininkas", "label.team-view-only": "Team view only", "label.team-websites": "Team websites", - "label.teams": "Teams", - "label.theme": "Theme", + "label.teams": "Komandos", + "label.theme": "Spalvų tema", "label.this-month": "Šis mėnuo", "label.this-week": "Ši savaitė", "label.this-year": "Šie metai", "label.timezone": "Laiko zona", - "label.title": "Title", + "label.title": "Pavadinimas", "label.today": "Šiandien", "label.toggle-charts": "Rodyti / slėpti grafikus", "label.total": "Total", "label.total-records": "Total records", "label.tracking-code": "Sekimo kodas", - "label.transfer": "Transfer", - "label.transfer-website": "Transfer website", + "label.transfer": "Perleisti", + "label.transfer-website": "Perleisti svetainę", "label.true": "True", "label.type": "Type", "label.unique": "Unique", "label.unique-visitors": "Unikalūs lankytojai", "label.unknown": "Nežinoma", - "label.untitled": "Untitled", + "label.untitled": "Be pavadinimo", "label.url": "URL", "label.urls": "URLs", - "label.user": "User", + "label.user": "Vartotojas", "label.username": "Vartotojo vardas", - "label.users": "Users", + "label.users": "Vartotojai", "label.value": "Value", - "label.view": "View", + "label.view": "Atidaryti", "label.view-details": "Peržiūrėti detaliau", - "label.view-only": "View only", + "label.view-only": "Tik peržiūrėti", "label.views": "Peržiūros", "label.visitors": "Lankytojai", - "label.website": "Website", - "label.website-id": "Website ID", + "label.website": "Svetainė", + "label.website-id": "Svetainės ID", "label.websites": "Svetainės", "label.window": "Window", - "label.yesterday": "Yesterday", - "message.action-confirmation": "Type {confirmation} in the box below to confirm.", + "label.yesterday": "Vakar", + "message.action-confirmation": "Įrašykite {confirmation} žemiau, kad patvirtintumėte.", "message.active-users": "{x, plural, =0 {# aktyvių vartotojų} zero {# aktyvių vartotojų} one {# aktyvus vartotojas} other {# aktyvūs vartotojai}}", "message.confirm-delete": "Ar esate tikri, jog norite ištrinti svetainę {target}?", - "message.confirm-leave": "Are you sure you want to leave {target}?", - "message.confirm-remove": "Are you sure you want to remove {target}?", + "message.confirm-leave": "Ar esate tikri, jog norite palikti {target}?", + "message.confirm-remove": "Ar esate tikri, jog norite ištrinti {target}?", "message.confirm-reset": "Are esate tikri, jog norite atstatyti svetainės {target} statistikos duomenis?", - "message.delete-team-warning": "Deleting a team will also delete all team websites.", + "message.delete-team-warning": "Ištrinant komandą bus ištrintos ir visos komandos svetainės.", "message.delete-website-warning": "Visi susiję duomenys taip pat bus ištrinti.", "message.error": "Kažkas įvyko ne taip.", "message.event-log": "{event} on {url}", "message.go-to-settings": "Eiti į nustatymus", "message.incorrect-username-password": "Neteisingas vartotojo vardas/slaptažodis.", "message.invalid-domain": "Klaidingas domenas", - "message.min-password-length": "Minimum length of {n} characters", - "message.new-version-available": "A new version of Umami {version} is available!", + "message.min-password-length": "Reikia bent {n} simbolių", + "message.new-version-available": "Išleista nauja 'Umami' {version} versija!", "message.no-data-available": "Nėra jokių duomenų.", - "message.no-event-data": "No event data is available.", + "message.no-event-data": "Jokių duomenų apie įvykius nėra.", "message.no-match-password": "Slaptažodžiai nesutampa", - "message.no-results-found": "No results were found.", - "message.no-team-websites": "This team does not have any websites.", - "message.no-teams": "You have not created any teams.", - "message.no-users": "There are no users.", + "message.no-results-found": "Jokių rezultatų nerasta.", + "message.no-team-websites": "Ši komanda neturi jokių svetainių.", + "message.no-teams": "Jūs nesate sukūrę jokių komandų.", + "message.no-users": "Nėra jokių vartotojų.", "message.no-websites-configured": "Jūs nesate susikonfiguravę jokių svetainių.", "message.page-not-found": "Puslapis nerastas.", - "message.reset-website": "To reset this website, type {confirmation} in the box below to confirm.", + "message.reset-website": "Kad atstatyti šią svetainę, įrašykite {confirmation} žemiau, kad patvirtintumėte.", "message.reset-website-warning": "Visi šios svetainės statistikos duomenys bus ištrinti, bet sekimo kodas išliks nepaliestas.", "message.saved": "Sėkmingai išsaugota.", "message.share-url": "Tai yra viešai prieinama {target} nuoroda (URL).", - "message.team-already-member": "You are already a member of the team.", - "message.team-not-found": "Team not found.", - "message.team-websites-info": "Websites can be viewed by anyone on the team.", + "message.team-already-member": "Jūs jau esate šios komandos narys.", + "message.team-not-found": "Komanda nerasta.", + "message.team-websites-info": "Svetaines gali peržiūrėti bet kas iš šios komandos.", "message.tracking-code": "Sekimo kodas", - "message.transfer-team-website-to-user": "Transfer this website to your account?", - "message.transfer-user-website-to-team": "Select the team to transfer this website to.", - "message.transfer-website": "Transfer website ownership to your account or another team.", + "message.transfer-team-website-to-user": "Perduoti šią svetainę į jūsų paskyrą?", + "message.transfer-user-website-to-team": "Pasirinkite komandą, kuriai norite perduoti šią svetainę.", + "message.transfer-website": "Perduoti svetainės nuosavybę į savo paskyrą arba kitą komandą.", "message.triggered-event": "Triggered event", - "message.user-deleted": "User deleted.", + "message.user-deleted": "Vartotojas ištrintas.", "message.viewed-page": "Viewed page", "message.visitor-log": "Lankytojas iš {country}, naudojantis {browser} sistemoje {os} {device}", "message.visitors-dropped-off": "Visitors dropped off" From c4d0c433c064a48300b6c637d8052db87630c0ed Mon Sep 17 00:00:00 2001 From: Brian Cao Date: Sun, 24 Mar 2024 11:36:09 -0700 Subject: [PATCH 09/55] Fix mobile nav. --- src/app/(main)/NavBar.tsx | 44 ++++++++++++++++++---------- src/components/input/TeamsButton.tsx | 4 ++- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/src/app/(main)/NavBar.tsx b/src/app/(main)/NavBar.tsx index 08007b1c4..5e0e3da26 100644 --- a/src/app/(main)/NavBar.tsx +++ b/src/app/(main)/NavBar.tsx @@ -14,7 +14,7 @@ import styles from './NavBar.module.css'; export function NavBar() { const { formatMessage, labels } = useMessages(); const { pathname, router } = useNavigation(); - const { renderTeamUrl } = useTeamUrl(); + const { teamId, renderTeamUrl } = useTeamUrl(); const cloudMode = !!process.env.cloudMode; @@ -34,25 +34,38 @@ export function NavBar() { label: formatMessage(labels.settings), url: renderTeamUrl('/settings'), children: [ + ...(teamId + ? [ + { + label: formatMessage(labels.team), + url: renderTeamUrl('/settings/team'), + }, + ] + : []), { label: formatMessage(labels.websites), - url: '/settings/websites', - }, - { - label: formatMessage(labels.teams), - url: '/settings/teams', - }, - { - label: formatMessage(labels.users), - url: '/settings/users', - }, - { - label: formatMessage(labels.profile), - url: '/profile', + url: renderTeamUrl('/settings/websites'), }, + ...(!teamId + ? [ + { + label: formatMessage(labels.teams), + url: renderTeamUrl('/settings/teams'), + }, + { + label: formatMessage(labels.users), + url: '/settings/users', + }, + ] + : [ + { + label: formatMessage(labels.members), + url: renderTeamUrl('/settings/members'), + }, + ]), ], }, - cloudMode && { + { label: formatMessage(labels.profile), url: '/profile', }, @@ -94,6 +107,7 @@ export function NavBar() {
+
diff --git a/src/components/input/TeamsButton.tsx b/src/components/input/TeamsButton.tsx index e3b5a3a8d..1f6270b42 100644 --- a/src/components/input/TeamsButton.tsx +++ b/src/components/input/TeamsButton.tsx @@ -7,9 +7,11 @@ import styles from './TeamsButton.module.css'; export function TeamsButton({ className, + showText = true, onChange, }: { className?: string; + showText?: boolean; onChange?: (value: string) => void; }) { const { user } = useLogin(); @@ -31,7 +33,7 @@ export function TeamsButton({ ); } + +const ResultsMenu = ({ values, type, isLoading, onSelect }) => { + const { formatValue } = useFormat(); + if (isLoading) { + return ; + } + + if (!values?.length) { + return null; + } + + return ( + + {values?.map(value => { + return {safeDecodeURIComponent(formatValue(value, type))}; + })} + + ); +}; diff --git a/src/app/(main)/reports/insights/InsightsFieldParameters.tsx b/src/app/(main)/reports/[reportId]/FieldParameters.tsx similarity index 66% rename from src/app/(main)/reports/insights/InsightsFieldParameters.tsx rename to src/app/(main)/reports/[reportId]/FieldParameters.tsx index 798a828cd..ded9dee7b 100644 --- a/src/app/(main)/reports/insights/InsightsFieldParameters.tsx +++ b/src/app/(main)/reports/[reportId]/FieldParameters.tsx @@ -1,4 +1,4 @@ -import { useMessages } from 'components/hooks'; +import { useFields, useMessages } from 'components/hooks'; import Icons from 'components/icons'; import { useContext } from 'react'; import { Button, FormRow, Icon, Popup, PopupTrigger } from 'react-basics'; @@ -7,24 +7,12 @@ import ParameterList from '../[reportId]/ParameterList'; import PopupForm from '../[reportId]/PopupForm'; import { ReportContext } from '../[reportId]/Report'; -export function InsightsFieldParameters() { +export function FieldParameters() { const { report, updateReport } = useContext(ReportContext); const { formatMessage, labels } = useMessages(); const { parameters } = report || {}; const { fields } = parameters || {}; - - const fieldOptions = [ - { name: 'url', type: 'string', label: formatMessage(labels.url) }, - { name: 'title', type: 'string', label: formatMessage(labels.pageTitle) }, - { name: 'referrer', type: 'string', label: formatMessage(labels.referrer) }, - { name: 'query', type: 'string', label: formatMessage(labels.query) }, - { name: 'browser', type: 'string', label: formatMessage(labels.browser) }, - { name: 'os', type: 'string', label: formatMessage(labels.os) }, - { name: 'device', type: 'string', label: formatMessage(labels.device) }, - { name: 'country', type: 'string', label: formatMessage(labels.country) }, - { name: 'region', type: 'string', label: formatMessage(labels.region) }, - { name: 'city', type: 'string', label: formatMessage(labels.city) }, - ]; + const { fields: fieldOptions } = useFields(); const handleAdd = (value: { name: any }) => { if (!fields.find(({ name }) => name === value.name)) { @@ -72,4 +60,4 @@ export function InsightsFieldParameters() { ); } -export default InsightsFieldParameters; +export default FieldParameters; diff --git a/src/app/(main)/reports/insights/InsightsFilterParameters.module.css b/src/app/(main)/reports/[reportId]/FilterParameters.module.css similarity index 95% rename from src/app/(main)/reports/insights/InsightsFilterParameters.module.css rename to src/app/(main)/reports/[reportId]/FilterParameters.module.css index 8b1795d21..022c0d7e6 100644 --- a/src/app/(main)/reports/insights/InsightsFilterParameters.module.css +++ b/src/app/(main)/reports/[reportId]/FilterParameters.module.css @@ -34,3 +34,7 @@ border-radius: 5px; white-space: nowrap; } + +.edit { + margin-top: 20px; +} diff --git a/src/app/(main)/reports/[reportId]/FilterParameters.tsx b/src/app/(main)/reports/[reportId]/FilterParameters.tsx new file mode 100644 index 000000000..cd7a555ef --- /dev/null +++ b/src/app/(main)/reports/[reportId]/FilterParameters.tsx @@ -0,0 +1,113 @@ +import { useContext } from 'react'; +import { safeDecodeURIComponent } from 'next-basics'; +import { useMessages, useFormat, useFilters, useFields } from 'components/hooks'; +import Icons from 'components/icons'; +import { Button, FormRow, Icon, Popup, PopupTrigger } from 'react-basics'; +import FilterSelectForm from '../[reportId]/FilterSelectForm'; +import ParameterList from '../[reportId]/ParameterList'; +import PopupForm from '../[reportId]/PopupForm'; +import { ReportContext } from './Report'; +import { OPERATORS } from 'lib/constants'; +import FieldFilterEditForm from '../[reportId]/FieldFilterEditForm'; +import styles from './FilterParameters.module.css'; + +export function FilterParameters() { + const { report, updateReport } = useContext(ReportContext); + const { formatMessage, labels } = useMessages(); + const { formatValue } = useFormat(); + const { filterLabels } = useFilters(); + const { parameters } = report || {}; + const { websiteId, filters } = parameters || {}; + const { fields } = useFields(); + + const handleAdd = (value: { name: any }) => { + if (!filters.find(({ name }) => name === value.name)) { + updateReport({ parameters: { filters: filters.concat(value) } }); + } + }; + + const handleRemove = (name: string) => { + updateReport({ parameters: { filters: filters.filter(f => f.name !== name) } }); + }; + + const handleChange = filter => { + updateReport({ + parameters: { + filters: filters.map(f => { + if (filter.name === f.name) { + return filter; + } + return f; + }), + }, + }); + }; + + const AddButton = () => { + return ( + + + + + !filters.find(f => f.name === name))} + onChange={handleAdd} + /> + + + + ); + }; + + return ( + }> + + {filters.map(({ name, filter, value }: { name: string; filter: string; value: string }) => { + const label = fields.find(f => f.name === name)?.label; + const isEquals = [OPERATORS.equals, OPERATORS.notEquals].includes(filter as any); + return ( + handleRemove(name)}> + + + ); + })} + + + ); +} + +const FilterParameter = ({ name, label, filter, value, type = 'string', onChange }) => { + return ( + +
+
{label}
+
{filter}
+
{safeDecodeURIComponent(value)}
+
+ + + + + +
+ ); +}; + +export default FilterParameters; diff --git a/src/app/(main)/reports/[reportId]/FilterSelectForm.tsx b/src/app/(main)/reports/[reportId]/FilterSelectForm.tsx index 3d209fc14..f43ac5f6e 100644 --- a/src/app/(main)/reports/[reportId]/FilterSelectForm.tsx +++ b/src/app/(main)/reports/[reportId]/FilterSelectForm.tsx @@ -1,59 +1,37 @@ import { useState } from 'react'; -import { Loading } from 'react-basics'; -import { subDays } from 'date-fns'; import FieldSelectForm from './FieldSelectForm'; -import FieldFilterForm from './FieldFilterForm'; -import { useApi } from 'components/hooks'; - -function useValues(websiteId: string, type: string) { - const now = Date.now(); - const { get, useQuery } = useApi(); - const { data, error, isLoading } = useQuery({ - queryKey: ['websites:values', websiteId, type], - queryFn: () => - get(`/websites/${websiteId}/values`, { - type, - startAt: +subDays(now, 90), - endAt: now, - }), - enabled: !!(websiteId && type), - }); - - return { data, error, isLoading }; -} +import FieldFilterEditForm from './FieldFilterEditForm'; export interface FilterSelectFormProps { - websiteId: string; + websiteId?: string; fields: any[]; - onSelect?: (key: any) => void; + onChange?: (filter: { name: string; type: string; filter: string; value: string }) => void; allowFilterSelect?: boolean; } export default function FilterSelectForm({ websiteId, fields, - onSelect, + onChange, allowFilterSelect, }: FilterSelectFormProps) { const [field, setField] = useState<{ name: string; label: string; type: string }>(); - const { data, isLoading } = useValues(websiteId, field?.name); if (!field) { return ; } - if (isLoading) { - return ; - } + const { name, label, type } = field; return ( - ); } diff --git a/src/app/(main)/reports/[reportId]/ParameterList.tsx b/src/app/(main)/reports/[reportId]/ParameterList.tsx index 7fe123818..046e85dce 100644 --- a/src/app/(main)/reports/[reportId]/ParameterList.tsx +++ b/src/app/(main)/reports/[reportId]/ParameterList.tsx @@ -20,9 +20,17 @@ export function ParameterList({ children }: ParameterListProps) { ); } -const Item = ({ children, onRemove }: { children?: ReactNode; onRemove?: () => void }) => { +const Item = ({ + children, + onClick, + onRemove, +}: { + children?: ReactNode; + onClick?: () => void; + onRemove?: () => void; +}) => { return ( -
+
{children} diff --git a/src/app/(main)/reports/event-data/EventDataParameters.tsx b/src/app/(main)/reports/event-data/EventDataParameters.tsx index efa9fb675..adc182748 100644 --- a/src/app/(main)/reports/event-data/EventDataParameters.tsx +++ b/src/app/(main)/reports/event-data/EventDataParameters.tsx @@ -60,10 +60,9 @@ export function EventDataParameters() { } }; - const handleRemove = (group: string, index: number) => { + const handleRemove = (group: string) => { const data = [...parameterData[group]]; - data.splice(index, 1); - updateReport({ parameters: { [group]: data } }); + updateReport({ parameters: { [group]: data.filter(({ name }) => name !== group) } }); }; const AddButton = ({ group, onAdd }) => { @@ -104,29 +103,28 @@ export function EventDataParameters() { label={label} action={} > - handleRemove(group, index)} - > - {({ name, value }) => { + + {parameterData[group].map(({ name, value }) => { return ( -
- {group === REPORT_PARAMETERS.fields && ( - <> -
{name}
-
{value}
- - )} - {group === REPORT_PARAMETERS.filters && ( - <> -
{name}
-
{value[0]}
-
{value[1]}
- - )} -
+ handleRemove(group)}> +
+ {group === REPORT_PARAMETERS.fields && ( + <> +
{name}
+
{value}
+ + )} + {group === REPORT_PARAMETERS.filters && ( + <> +
{name}
+
{value[0]}
+
{value[1]}
+ + )} +
+
); - }} + })}
); diff --git a/src/app/(main)/reports/funnel/FunnelParameters.tsx b/src/app/(main)/reports/funnel/FunnelParameters.tsx index 6eefbaae4..7c4aa8454 100644 --- a/src/app/(main)/reports/funnel/FunnelParameters.tsx +++ b/src/app/(main)/reports/funnel/FunnelParameters.tsx @@ -38,11 +38,9 @@ export function FunnelParameters() { updateReport({ parameters: { urls: parameters.urls.concat(url) } }); }; - const handleRemoveUrl = (index: number, e: any) => { - e.stopPropagation(); + const handleRemoveUrl = (url: string) => { const urls = [...parameters.urls]; - urls.splice(index, 1); - updateReport({ parameters: { urls } }); + updateReport({ parameters: { urls: urls.filter(n => n.url !== url) } }); }; const AddUrlButton = () => { @@ -72,10 +70,11 @@ export function FunnelParameters() { }> - handleRemoveUrl(index, e)} - /> + + {urls.map(url => { + return handleRemoveUrl(url)} />; + })} + diff --git a/src/app/(main)/reports/insights/InsightsFilterParameters.tsx b/src/app/(main)/reports/insights/InsightsFilterParameters.tsx deleted file mode 100644 index 47554469c..000000000 --- a/src/app/(main)/reports/insights/InsightsFilterParameters.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import { useMessages, useFormat, useFilters } from 'components/hooks'; -import Icons from 'components/icons'; -import { useContext } from 'react'; -import { Button, FormRow, Icon, Popup, PopupTrigger } from 'react-basics'; -import FilterSelectForm from '../[reportId]/FilterSelectForm'; -import ParameterList from '../[reportId]/ParameterList'; -import PopupForm from '../[reportId]/PopupForm'; -import { ReportContext } from '../[reportId]/Report'; -import styles from './InsightsFilterParameters.module.css'; -import { safeDecodeURIComponent } from 'next-basics'; -import { OPERATORS } from 'lib/constants'; - -export function InsightsFilterParameters() { - const { report, updateReport } = useContext(ReportContext); - const { formatMessage, labels } = useMessages(); - const { formatValue } = useFormat(); - const { filterLabels } = useFilters(); - const { parameters } = report || {}; - const { websiteId, filters } = parameters || {}; - - const fieldOptions = [ - { name: 'url', type: 'string', label: formatMessage(labels.url) }, - { name: 'title', type: 'string', label: formatMessage(labels.pageTitle) }, - { name: 'referrer', type: 'string', label: formatMessage(labels.referrer) }, - { name: 'query', type: 'string', label: formatMessage(labels.query) }, - { name: 'browser', type: 'string', label: formatMessage(labels.browser) }, - { name: 'os', type: 'string', label: formatMessage(labels.os) }, - { name: 'device', type: 'string', label: formatMessage(labels.device) }, - { name: 'country', type: 'string', label: formatMessage(labels.country) }, - { name: 'region', type: 'string', label: formatMessage(labels.region) }, - { name: 'city', type: 'string', label: formatMessage(labels.city) }, - ]; - - const handleAdd = (value: { name: any }) => { - if (!filters.find(({ name }) => name === value.name)) { - updateReport({ parameters: { filters: filters.concat(value) } }); - } - }; - - const handleRemove = (name: string) => { - updateReport({ parameters: { filters: filters.filter(f => f.name !== name) } }); - }; - - const AddButton = () => { - return ( - - - - - !filters.find(f => f.name === name))} - onSelect={handleAdd} - /> - - - - ); - }; - - return ( - }> - - {filters.map(({ name, filter, value }) => { - const label = fieldOptions.find(f => f.name === name)?.label; - const isEquals = [OPERATORS.equals, OPERATORS.notEquals].includes(filter); - return ( - handleRemove(name)}> -
-
{label}
-
{filterLabels[filter]}
-
- {safeDecodeURIComponent(isEquals ? formatValue(value, name) : value)} -
-
-
- ); - })} -
-
- ); -} - -export default InsightsFilterParameters; diff --git a/src/app/(main)/reports/insights/InsightsParameters.tsx b/src/app/(main)/reports/insights/InsightsParameters.tsx index 22c57ff0c..7f58de6a4 100644 --- a/src/app/(main)/reports/insights/InsightsParameters.tsx +++ b/src/app/(main)/reports/insights/InsightsParameters.tsx @@ -3,8 +3,8 @@ import { useContext } from 'react'; import { Form, FormButtons, SubmitButton } from 'react-basics'; import BaseParameters from '../[reportId]/BaseParameters'; import { ReportContext } from '../[reportId]/Report'; -import InsightsFieldParameters from './InsightsFieldParameters'; -import InsightsFilterParameters from './InsightsFilterParameters'; +import FieldParameters from '../[reportId]/FieldParameters'; +import FilterParameters from '../[reportId]/FilterParameters'; export function InsightsParameters() { const { report, runReport, isRunning } = useContext(ReportContext); @@ -22,8 +22,8 @@ export function InsightsParameters() { return (
- {parametersSelected && } - {parametersSelected && } + {parametersSelected && } + {parametersSelected && } {formatMessage(labels.runQuery)} diff --git a/src/app/(main)/websites/[websiteId]/WebsiteFilterButton.tsx b/src/app/(main)/websites/[websiteId]/WebsiteFilterButton.tsx index 998c73c46..c7e4cc534 100644 --- a/src/app/(main)/websites/[websiteId]/WebsiteFilterButton.tsx +++ b/src/app/(main)/websites/[websiteId]/WebsiteFilterButton.tsx @@ -1,7 +1,7 @@ import { Button, Icon, Icons, Popup, PopupTrigger, Text } from 'react-basics'; import PopupForm from 'app/(main)/reports/[reportId]/PopupForm'; import FilterSelectForm from 'app/(main)/reports/[reportId]/FilterSelectForm'; -import { useMessages, useNavigation } from 'components/hooks'; +import { useFields, useMessages, useNavigation } from 'components/hooks'; export function WebsiteFilterButton({ websiteId, @@ -12,17 +12,7 @@ export function WebsiteFilterButton({ }) { const { formatMessage, labels } = useMessages(); const { renderUrl, router } = useNavigation(); - - const fieldOptions = [ - { name: 'url', type: 'string', label: formatMessage(labels.url) }, - { name: 'referrer', type: 'string', label: formatMessage(labels.referrer) }, - { name: 'browser', type: 'string', label: formatMessage(labels.browser) }, - { name: 'os', type: 'string', label: formatMessage(labels.os) }, - { name: 'device', type: 'string', label: formatMessage(labels.device) }, - { name: 'country', type: 'string', label: formatMessage(labels.country) }, - { name: 'region', type: 'string', label: formatMessage(labels.region) }, - { name: 'city', type: 'string', label: formatMessage(labels.city) }, - ]; + const { fields } = useFields(); const handleAddFilter = ({ name, value }) => { router.push(renderUrl({ [name]: value })); @@ -42,8 +32,8 @@ export function WebsiteFilterButton({ { + fields={fields} + onChange={value => { handleAddFilter(value); close(); }} diff --git a/src/components/hooks/index.ts b/src/components/hooks/index.ts index a737ba204..df4fbd88f 100644 --- a/src/components/hooks/index.ts +++ b/src/components/hooks/index.ts @@ -16,10 +16,12 @@ export * from './queries/useWebsite'; export * from './queries/useWebsites'; export * from './queries/useWebsiteEvents'; export * from './queries/useWebsiteMetrics'; +export * from './queries/useWebsiteValues'; export * from './useCountryNames'; export * from './useDateRange'; export * from './useDocumentClick'; export * from './useEscapeKey'; +export * from './useFields'; export * from './useFilters'; export * from './useForceUpdate'; export * from './useFormat'; diff --git a/src/components/hooks/queries/useWebsiteValues.ts b/src/components/hooks/queries/useWebsiteValues.ts new file mode 100644 index 000000000..ab287c076 --- /dev/null +++ b/src/components/hooks/queries/useWebsiteValues.ts @@ -0,0 +1,20 @@ +import { useApi } from 'components/hooks'; +import { subDays } from 'date-fns'; + +export function useWebsiteValues(websiteId: string, type: string) { + const now = Date.now(); + const { get, useQuery } = useApi(); + + return useQuery({ + queryKey: ['websites:values', websiteId, type], + queryFn: () => + get(`/websites/${websiteId}/values`, { + type, + startAt: +subDays(now, 90), + endAt: now, + }), + enabled: !!(websiteId && type), + }); +} + +export default useWebsiteValues; diff --git a/src/components/hooks/useFields.ts b/src/components/hooks/useFields.ts new file mode 100644 index 000000000..05d2b4588 --- /dev/null +++ b/src/components/hooks/useFields.ts @@ -0,0 +1,22 @@ +import { useMessages } from './useMessages'; + +export function useFields() { + const { formatMessage, labels } = useMessages(); + + const fields = [ + { name: 'url', type: 'string', label: formatMessage(labels.url) }, + { name: 'title', type: 'string', label: formatMessage(labels.pageTitle) }, + { name: 'referrer', type: 'string', label: formatMessage(labels.referrer) }, + { name: 'query', type: 'string', label: formatMessage(labels.query) }, + { name: 'browser', type: 'string', label: formatMessage(labels.browser) }, + { name: 'os', type: 'string', label: formatMessage(labels.os) }, + { name: 'device', type: 'string', label: formatMessage(labels.device) }, + { name: 'country', type: 'string', label: formatMessage(labels.country) }, + { name: 'region', type: 'string', label: formatMessage(labels.region) }, + { name: 'city', type: 'string', label: formatMessage(labels.city) }, + ]; + + return { fields }; +} + +export default useFields; diff --git a/src/components/messages.ts b/src/components/messages.ts index 4c5f5b86f..996aef6cd 100644 --- a/src/components/messages.ts +++ b/src/components/messages.ts @@ -148,6 +148,7 @@ export const labels = defineMessages({ url: { id: 'label.url', defaultMessage: 'URL' }, urls: { id: 'label.urls', defaultMessage: 'URLs' }, add: { id: 'label.add', defaultMessage: 'Add' }, + update: { id: 'label.update', defaultMessage: 'Update' }, window: { id: 'label.window', defaultMessage: 'Window' }, runQuery: { id: 'label.run-query', defaultMessage: 'Run query' }, field: { id: 'label.field', defaultMessage: 'Field' }, From d3ca8565214ecdced818d7118908ff49cc4956d1 Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Mon, 25 Mar 2024 16:29:29 -0700 Subject: [PATCH 13/55] check in clickhouse migration script --- db/clickhouse/migrations/02_add_visit_id.sql | 71 ++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 db/clickhouse/migrations/02_add_visit_id.sql diff --git a/db/clickhouse/migrations/02_add_visit_id.sql b/db/clickhouse/migrations/02_add_visit_id.sql new file mode 100644 index 000000000..f0379330d --- /dev/null +++ b/db/clickhouse/migrations/02_add_visit_id.sql @@ -0,0 +1,71 @@ +-- create new table +CREATE TABLE test.website_event_new +( + website_id UUID, + session_id UUID, + visit_id UUID, + event_id UUID, + hostname LowCardinality(String), + browser LowCardinality(String), + os LowCardinality(String), + device LowCardinality(String), + screen LowCardinality(String), + language LowCardinality(String), + country LowCardinality(String), + subdivision1 LowCardinality(String), + subdivision2 LowCardinality(String), + city String, + url_path String, + url_query String, + referrer_path String, + referrer_query String, + referrer_domain String, + page_title String, + event_type UInt32, + event_name String, + created_at DateTime('UTC'), + job_id UUID +) + engine = MergeTree + ORDER BY (website_id, session_id, created_at) + SETTINGS index_granularity = 8192; + +INSERT INTO test.website_event_new +SELECT we.website_id, + we.session_id, + we2.visit_id, + we.event_id, + we.hostname, + we.browser, + we.os, + we.device, + we.screen, + we.language, + we.country, + we.subdivision1, + we.subdivision2, + we.city, + we.url_path, + we.url_query, + we.referrer_path, + we.referrer_query, + we.referrer_domain, + we.page_title, + we.event_type, + we.event_name, + we.created_at, + we.job_id +FROM test.website_event we +JOIN (SELECT DISTINCT + s.session_id, + generateUUIDv4() visit_id, + s.created_at +FROM (SELECT DISTINCT session_id, + date_trunc('hour', created_at) created_at + FROM test.website_event) s) we2 + ON we.session_id = we2.session_id + and date_trunc('hour', we.created_at) = we2.created_at +ORDER BY we.session_id, we.created_at + +RENAME TABLE test.website_event TO test.website_event_old; +RENAME TABLE test.website_event_new TO test.website_event; \ No newline at end of file From 0aaf2c0b3bb508a739e9bc91fa99da9345269bec Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Mon, 25 Mar 2024 17:47:53 -0700 Subject: [PATCH 14/55] update visitId hash and expiration logic --- .../migrations/05_add_visit_id/migration.sql | 14 +++++++++++--- src/lib/crypto.ts | 8 +++++++- src/lib/session.ts | 7 +++++-- src/pages/api/send.ts | 12 +++++++++++- src/queries/analytics/events/saveEvent.ts | 13 ++++++++++--- 5 files changed, 44 insertions(+), 10 deletions(-) diff --git a/db/postgresql/migrations/05_add_visit_id/migration.sql b/db/postgresql/migrations/05_add_visit_id/migration.sql index 8274d1fb4..fd2f1b905 100644 --- a/db/postgresql/migrations/05_add_visit_id/migration.sql +++ b/db/postgresql/migrations/05_add_visit_id/migration.sql @@ -1,9 +1,17 @@ -- AlterTable ALTER TABLE "website_event" ADD COLUMN "visit_id" UUID NULL; -UPDATE "website_event" -SET visit_id = uuid_in(overlay(overlay(md5(CONCAT(session_id::text, to_char(date_trunc('hour', created_at), 'YYYY-MM-DD HH24:00:00'))) placing '4' from 13) placing '8' from 17)::cstring) -WHERE visit_id IS NULL; +UPDATE "website_event" we +SET visit_id = a.uuid +FROM (SELECT DISTINCT + s.session_id, + s.visit_time, + gen_random_uuid() uuid + FROM (SELECT DISTINCT session_id, + date_trunc('hour', created_at) visit_time + FROM "website_event") s) a +WHERE we.session_id = a.session_id + and date_trunc('hour', we.created_at) = a.visit_time; ALTER TABLE "website_event" ALTER COLUMN "visit_id" SET NOT NULL; diff --git a/src/lib/crypto.ts b/src/lib/crypto.ts index a27633525..d47c9fd80 100644 --- a/src/lib/crypto.ts +++ b/src/lib/crypto.ts @@ -1,4 +1,4 @@ -import { startOfMonth } from 'date-fns'; +import { startOfHour, startOfMonth } from 'date-fns'; import { hash } from 'next-basics'; import { v4, v5, validate } from 'uuid'; @@ -12,6 +12,12 @@ export function salt() { return hash(secret(), ROTATING_SALT); } +export function sessionSalt() { + const ROTATING_SALT = hash(startOfHour(new Date()).toUTCString()); + + return hash(secret(), ROTATING_SALT); +} + export function uuid(...args: any) { if (!args.length) return v4(); diff --git a/src/lib/session.ts b/src/lib/session.ts index 0f388db9a..6e2cbcc31 100644 --- a/src/lib/session.ts +++ b/src/lib/session.ts @@ -1,4 +1,4 @@ -import { isUuid, secret, uuid } from 'lib/crypto'; +import { isUuid, secret, sessionSalt, uuid } from 'lib/crypto'; import { getClientInfo } from 'lib/detect'; import { parseToken } from 'next-basics'; import { NextApiRequestCollect } from 'pages/api/send'; @@ -10,6 +10,7 @@ import { loadSession, loadWebsite } from './load'; export async function findSession(req: NextApiRequestCollect): Promise<{ id: any; websiteId: string; + visitId: string; hostname: string; browser: string; os: any; @@ -67,12 +68,14 @@ export async function findSession(req: NextApiRequestCollect): Promise<{ await getClientInfo(req, payload); const sessionId = uuid(websiteId, hostname, ip, userAgent); + const visitId = uuid(sessionId, sessionSalt()); // Clickhouse does not require session lookup if (clickhouse.enabled) { return { id: sessionId, websiteId, + visitId, hostname, browser, os: os as any, @@ -114,7 +117,7 @@ export async function findSession(req: NextApiRequestCollect): Promise<{ } } - return { ...session, ownerId: website.userId }; + return { ...session, ownerId: website.userId, visitId: visitId }; } async function checkUserBlock(userId: string) { diff --git a/src/pages/api/send.ts b/src/pages/api/send.ts index 5aa367f04..726a6fcc2 100644 --- a/src/pages/api/send.ts +++ b/src/pages/api/send.ts @@ -1,7 +1,7 @@ import ipaddr from 'ipaddr.js'; import { isbot } from 'isbot'; import { COLLECTION_TYPE, HOSTNAME_REGEX, IP_REGEX } from 'lib/constants'; -import { secret } from 'lib/crypto'; +import { secret, sessionSalt, uuid } from 'lib/crypto'; import { getIpAddress } from 'lib/detect'; import { useCors, useSession, useValidate } from 'lib/middleware'; import { CollectionType, YupRequest } from 'lib/types'; @@ -31,6 +31,7 @@ export interface NextApiRequestCollect extends NextApiRequest { session: { id: string; websiteId: string; + visitId: string; ownerId: string; hostname: string; browser: string; @@ -93,6 +94,14 @@ export default async (req: NextApiRequestCollect, res: NextApiResponse) => { const session = req.session; + // expire visitId after 30 minutes + session.visitId = + !!session.iat && Math.floor(new Date().getTime() / 1000) - session.iat > 1800 + ? uuid(session.id, sessionSalt()) + : session.visitId; + + session.iat = Math.floor(new Date().getTime() / 1000); + if (type === COLLECTION_TYPE.event) { // eslint-disable-next-line prefer-const let [urlPath, urlQuery] = url?.split('?') || []; @@ -125,6 +134,7 @@ export default async (req: NextApiRequestCollect, res: NextApiResponse) => { eventData, ...session, sessionId: session.id, + visitId: session.visitId, }); } diff --git a/src/queries/analytics/events/saveEvent.ts b/src/queries/analytics/events/saveEvent.ts index 0596023b4..25bcf9e7a 100644 --- a/src/queries/analytics/events/saveEvent.ts +++ b/src/queries/analytics/events/saveEvent.ts @@ -6,8 +6,9 @@ import { uuid } from 'lib/crypto'; import { saveEventData } from 'queries/analytics/eventData/saveEventData'; export async function saveEvent(args: { - sessionId: string; websiteId: string; + sessionId: string; + visitId: string; urlPath: string; urlQuery?: string; referrerPath?: string; @@ -34,8 +35,9 @@ export async function saveEvent(args: { } async function relationalQuery(data: { - sessionId: string; websiteId: string; + sessionId: string; + visitId: string; urlPath: string; urlQuery?: string; referrerPath?: string; @@ -48,6 +50,7 @@ async function relationalQuery(data: { const { websiteId, sessionId, + visitId, urlPath, urlQuery, referrerPath, @@ -64,6 +67,7 @@ async function relationalQuery(data: { id: websiteEventId, websiteId, sessionId, + visitId, urlPath: urlPath?.substring(0, URL_LENGTH), urlQuery: urlQuery?.substring(0, URL_LENGTH), referrerPath: referrerPath?.substring(0, URL_LENGTH), @@ -90,8 +94,9 @@ async function relationalQuery(data: { } async function clickhouseQuery(data: { - sessionId: string; websiteId: string; + sessionId: string; + visitId: string; urlPath: string; urlQuery?: string; referrerPath?: string; @@ -114,6 +119,7 @@ async function clickhouseQuery(data: { const { websiteId, sessionId, + visitId, urlPath, urlQuery, referrerPath, @@ -136,6 +142,7 @@ async function clickhouseQuery(data: { ...args, website_id: websiteId, session_id: sessionId, + visit_id: visitId, event_id: uuid(), country: country, subdivision1: From db75e1e5d20d2b87c91925433ffb7fe8474279fd Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Mon, 25 Mar 2024 17:49:13 -0700 Subject: [PATCH 15/55] Add iat to session object --- src/pages/api/send.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/api/send.ts b/src/pages/api/send.ts index 726a6fcc2..df6115aa3 100644 --- a/src/pages/api/send.ts +++ b/src/pages/api/send.ts @@ -43,6 +43,7 @@ export interface NextApiRequestCollect extends NextApiRequest { subdivision1: string; subdivision2: string; city: string; + iat: number; }; headers: { [key: string]: any }; yup: YupRequest; From 7c7fd577c3c24a6e77e842bccc593eaebcff1dad Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Mon, 25 Mar 2024 18:33:30 -0700 Subject: [PATCH 16/55] remove test schema from CH migration script --- db/clickhouse/migrations/02_add_visit_id.sql | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/db/clickhouse/migrations/02_add_visit_id.sql b/db/clickhouse/migrations/02_add_visit_id.sql index f0379330d..8660e664d 100644 --- a/db/clickhouse/migrations/02_add_visit_id.sql +++ b/db/clickhouse/migrations/02_add_visit_id.sql @@ -1,5 +1,5 @@ -- create new table -CREATE TABLE test.website_event_new +CREATE TABLE umami.website_event_new ( website_id UUID, session_id UUID, @@ -30,7 +30,7 @@ CREATE TABLE test.website_event_new ORDER BY (website_id, session_id, created_at) SETTINGS index_granularity = 8192; -INSERT INTO test.website_event_new +INSERT INTO umami.website_event_new SELECT we.website_id, we.session_id, we2.visit_id, @@ -55,17 +55,17 @@ SELECT we.website_id, we.event_name, we.created_at, we.job_id -FROM test.website_event we +FROM umami.website_event we JOIN (SELECT DISTINCT s.session_id, generateUUIDv4() visit_id, s.created_at FROM (SELECT DISTINCT session_id, date_trunc('hour', created_at) created_at - FROM test.website_event) s) we2 + FROM umami.website_event) s) we2 ON we.session_id = we2.session_id and date_trunc('hour', we.created_at) = we2.created_at ORDER BY we.session_id, we.created_at -RENAME TABLE test.website_event TO test.website_event_old; -RENAME TABLE test.website_event_new TO test.website_event; \ No newline at end of file +RENAME TABLE umami.website_event TO umami.website_event_old; +RENAME TABLE umami.website_event_new TO umami.website_event; \ No newline at end of file From d36bf4396add3f3424ccc76695a2a36802765e5c Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Mon, 25 Mar 2024 18:47:02 -0700 Subject: [PATCH 17/55] remove order by in insert select --- db/clickhouse/migrations/02_add_visit_id.sql | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/db/clickhouse/migrations/02_add_visit_id.sql b/db/clickhouse/migrations/02_add_visit_id.sql index 8660e664d..8aa5e6a18 100644 --- a/db/clickhouse/migrations/02_add_visit_id.sql +++ b/db/clickhouse/migrations/02_add_visit_id.sql @@ -64,8 +64,7 @@ FROM (SELECT DISTINCT session_id, date_trunc('hour', created_at) created_at FROM umami.website_event) s) we2 ON we.session_id = we2.session_id - and date_trunc('hour', we.created_at) = we2.created_at -ORDER BY we.session_id, we.created_at + and date_trunc('hour', we.created_at) = we2.created_at; RENAME TABLE umami.website_event TO umami.website_event_old; RENAME TABLE umami.website_event_new TO umami.website_event; \ No newline at end of file From c033b0582df5282545c77d76a37f754b670011cb Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Mon, 25 Mar 2024 19:13:54 -0700 Subject: [PATCH 18/55] create join table and remove subquery from insert --- db/clickhouse/migrations/02_add_visit_id.sql | 43 +++++++++++++++----- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/db/clickhouse/migrations/02_add_visit_id.sql b/db/clickhouse/migrations/02_add_visit_id.sql index 8aa5e6a18..5dd7bcd11 100644 --- a/db/clickhouse/migrations/02_add_visit_id.sql +++ b/db/clickhouse/migrations/02_add_visit_id.sql @@ -1,3 +1,22 @@ +CREATE TABLE umami.website_event_join +( + session_id UUID, + visit_id UUID, + created_at DateTime('UTC') +) + engine = MergeTree + ORDER BY (session_id, created_at) + SETTINGS index_granularity = 8192; + +INSERT INTO umami.website_event_join +SELECT DISTINCT + s.session_id, + generateUUIDv4() visit_id, + s.created_at +FROM (SELECT DISTINCT session_id, + date_trunc('hour', created_at) created_at + FROM website_event) s; + -- create new table CREATE TABLE umami.website_event_new ( @@ -33,7 +52,7 @@ CREATE TABLE umami.website_event_new INSERT INTO umami.website_event_new SELECT we.website_id, we.session_id, - we2.visit_id, + j.visit_id, we.event_id, we.hostname, we.browser, @@ -56,15 +75,17 @@ SELECT we.website_id, we.created_at, we.job_id FROM umami.website_event we -JOIN (SELECT DISTINCT - s.session_id, - generateUUIDv4() visit_id, - s.created_at -FROM (SELECT DISTINCT session_id, - date_trunc('hour', created_at) created_at - FROM umami.website_event) s) we2 - ON we.session_id = we2.session_id - and date_trunc('hour', we.created_at) = we2.created_at; +JOIN umami.website_event_join j + ON we.session_id = j.session_id + and date_trunc('hour', we.created_at) = j.created_at +WHERE we.created_at > '2023-03-31'; RENAME TABLE umami.website_event TO umami.website_event_old; -RENAME TABLE umami.website_event_new TO umami.website_event; \ No newline at end of file +RENAME TABLE umami.website_event_new TO umami.website_event; + +/* + + DROP TABLE umami.website_event_old + DROP TABLE umami.website_event_join + + */ \ No newline at end of file From 7a6a598a19f617b4324d5424a220d86d90fbbae0 Mon Sep 17 00:00:00 2001 From: Brian Cao Date: Mon, 25 Mar 2024 21:41:31 -0700 Subject: [PATCH 19/55] Fix css date picker. --- src/components/metrics/DatePickerForm.module.css | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/components/metrics/DatePickerForm.module.css b/src/components/metrics/DatePickerForm.module.css index 92a415724..7168fa0d0 100644 --- a/src/components/metrics/DatePickerForm.module.css +++ b/src/components/metrics/DatePickerForm.module.css @@ -9,10 +9,6 @@ justify-content: center; } -.calendars > div { - width: 380px; -} - .calendars > div + div { margin-inline-start: 20px; padding-inline-start: 20px; From 2fa95448e015ec188a855fb69538e334c6e995ef Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Mon, 25 Mar 2024 22:50:03 -0700 Subject: [PATCH 20/55] Updated url and referrer logic in tracker. --- src/tracker/index.js | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/tracker/index.js b/src/tracker/index.js index 3728be0e9..d16b2a9ae 100644 --- a/src/tracker/index.js +++ b/src/tracker/index.js @@ -8,7 +8,7 @@ history, } = window; const { hostname, pathname, search } = location; - const { currentScript } = document; + const { currentScript, referrer } = document; if (!currentScript) return; @@ -19,7 +19,7 @@ const website = attr(_data + 'website-id'); const hostUrl = attr(_data + 'host-url'); const autoTrack = attr(_data + 'auto-track') !== _false; - const stripSearch = attr(_data + 'strip-search') === _true; + const excludeSearch = attr(_data + 'exclude-search') === _true; const domain = attr(_data + 'domains') || ''; const domains = domain.split(',').map(n => n.trim()); const host = @@ -32,12 +32,8 @@ /* Helper functions */ - const getPath = url => { - try { - return new URL(url).pathname; - } catch (e) { - return url; - } + const parseURL = url => { + return excludeSearch ? url.split('?')[0] : url; }; const getPayload = () => ({ @@ -56,7 +52,7 @@ if (!url) return; currentRef = currentUrl; - currentUrl = getPath(url.toString()); + currentUrl = parseURL(url.toString()); if (currentUrl !== currentRef) { setTimeout(track, delayDuration); @@ -222,8 +218,8 @@ }; } - let currentUrl = `${pathname}${stripSearch ? '' : search}`; - let currentRef = document.referrer; + let currentUrl = `${pathname}${search}`; + let currentRef = referrer !== hostname ? referrer : ''; let title = document.title; let cache; let initialized; From e6aebf5104238a4b81f277b0ed2c661f88e3a1f3 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Mon, 25 Mar 2024 22:50:27 -0700 Subject: [PATCH 21/55] Upgraded Next and Prisma. --- package.json | 6 +- yarn.lock | 182 +++++++++++++++++++++++++-------------------------- 2 files changed, 94 insertions(+), 94 deletions(-) diff --git a/package.json b/package.json index 03f0f79aa..23c03519f 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "dependencies": { "@clickhouse/client": "^0.2.2", "@fontsource/inter": "^4.5.15", - "@prisma/client": "5.10.2", + "@prisma/client": "5.11.0", "@prisma/extension-read-replicas": "^0.3.0", "@react-spring/web": "^9.7.3", "@tanstack/react-query": "^5.28.6", @@ -98,11 +98,11 @@ "maxmind": "^4.3.6", "md5": "^2.3.0", "moment-timezone": "^0.5.35", - "next": "14.1.3", + "next": "14.1.4", "next-basics": "^0.39.0", "node-fetch": "^3.2.8", "npm-run-all": "^4.1.5", - "prisma": "5.10.2", + "prisma": "5.11.0", "react": "^18.2.0", "react-basics": "^0.123.0", "react-beautiful-dnd": "^13.1.0", diff --git a/yarn.lock b/yarn.lock index a6ccbe27b..e438a9b6d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2208,10 +2208,10 @@ "@netlify/node-cookies" "^0.1.0" urlpattern-polyfill "8.0.2" -"@next/env@14.1.3": - version "14.1.3" - resolved "https://registry.yarnpkg.com/@next/env/-/env-14.1.3.tgz#73007b64d487bbb95ed83145195f734fc1182d10" - integrity sha512-VhgXTvrgeBRxNPjyfBsDIMvgsKDxjlpw4IAUsHCX8Gjl1vtHUYRT3+xfQ/wwvLPDd/6kqfLqk9Pt4+7gysuCKQ== +"@next/env@14.1.4": + version "14.1.4" + resolved "https://registry.yarnpkg.com/@next/env/-/env-14.1.4.tgz#432e80651733fbd67230bf262aee28be65252674" + integrity sha512-e7X7bbn3Z6DWnDi75UWn+REgAbLEqxI8Tq2pkFOFAMpWAWApz/YCUhtWMWn410h8Q2fYiYL7Yg5OlxMOCfFjJQ== "@next/eslint-plugin-next@14.1.3": version "14.1.3" @@ -2220,50 +2220,50 @@ dependencies: glob "10.3.10" -"@next/swc-darwin-arm64@14.1.3": - version "14.1.3" - resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.1.3.tgz#b4c218fdb49275972d91e9a9a0ccadba243b6739" - integrity sha512-LALu0yIBPRiG9ANrD5ncB3pjpO0Gli9ZLhxdOu6ZUNf3x1r3ea1rd9Q+4xxUkGrUXLqKVK9/lDkpYIJaCJ6AHQ== +"@next/swc-darwin-arm64@14.1.4": + version "14.1.4" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.1.4.tgz#a3bca0dc4393ac4cf3169bbf24df63441de66bb7" + integrity sha512-ubmUkbmW65nIAOmoxT1IROZdmmJMmdYvXIe8211send9ZYJu+SqxSnJM4TrPj9wmL6g9Atvj0S/2cFmMSS99jg== -"@next/swc-darwin-x64@14.1.3": - version "14.1.3" - resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.1.3.tgz#aa0d4357179d68daaa6f400708b76666708ffec9" - integrity sha512-E/9WQeXxkqw2dfcn5UcjApFgUq73jqNKaE5bysDm58hEUdUGedVrnRhblhJM7HbCZNhtVl0j+6TXsK0PuzXTCg== +"@next/swc-darwin-x64@14.1.4": + version "14.1.4" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.1.4.tgz#ba3683d4e2d30099f3f2864dd7349a4d9f440140" + integrity sha512-b0Xo1ELj3u7IkZWAKcJPJEhBop117U78l70nfoQGo4xUSvv0PJSTaV4U9xQBLvZlnjsYkc8RwQN1HoH/oQmLlQ== -"@next/swc-linux-arm64-gnu@14.1.3": - version "14.1.3" - resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.1.3.tgz#1ba8df39c04368ede185f268c3a817a8f4290e4c" - integrity sha512-USArX9B+3rZSXYLFvgy0NVWQgqh6LHWDmMt38O4lmiJNQcwazeI6xRvSsliDLKt+78KChVacNiwvOMbl6g6BBw== +"@next/swc-linux-arm64-gnu@14.1.4": + version "14.1.4" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.1.4.tgz#3519969293f16379954b7e196deb0c1eecbb2f8b" + integrity sha512-457G0hcLrdYA/u1O2XkRMsDKId5VKe3uKPvrKVOyuARa6nXrdhJOOYU9hkKKyQTMru1B8qEP78IAhf/1XnVqKA== -"@next/swc-linux-arm64-musl@14.1.3": - version "14.1.3" - resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.1.3.tgz#2fa8fe435862eb186aca6d6068c8aef2126ab11e" - integrity sha512-esk1RkRBLSIEp1qaQXv1+s6ZdYzuVCnDAZySpa62iFTMGTisCyNQmqyCTL9P+cLJ4N9FKCI3ojtSfsyPHJDQNw== +"@next/swc-linux-arm64-musl@14.1.4": + version "14.1.4" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.1.4.tgz#4bb3196bd402b3f84cf5373ff1021f547264d62f" + integrity sha512-l/kMG+z6MB+fKA9KdtyprkTQ1ihlJcBh66cf0HvqGP+rXBbOXX0dpJatjZbHeunvEHoBBS69GYQG5ry78JMy3g== -"@next/swc-linux-x64-gnu@14.1.3": - version "14.1.3" - resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.1.3.tgz#57a687b44337af219e07a79ecc8c63a3c1b2d020" - integrity sha512-8uOgRlYEYiKo0L8YGeS+3TudHVDWDjPVDUcST+z+dUzgBbTEwSSIaSgF/vkcC1T/iwl4QX9iuUyUdQEl0Kxalg== +"@next/swc-linux-x64-gnu@14.1.4": + version "14.1.4" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.1.4.tgz#1b3372c98c83dcdab946cdb4ee06e068b8139ba3" + integrity sha512-BapIFZ3ZRnvQ1uWbmqEGJuPT9cgLwvKtxhK/L2t4QYO7l+/DxXuIGjvp1x8rvfa/x1FFSsipERZK70pewbtJtw== -"@next/swc-linux-x64-musl@14.1.3": - version "14.1.3" - resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.1.3.tgz#8c057f8f7fb9679915df25eda6ab0ea1b7af85ff" - integrity sha512-DX2zqz05ziElLoxskgHasaJBREC5Y9TJcbR2LYqu4r7naff25B4iXkfXWfcp69uD75/0URmmoSgT8JclJtrBoQ== +"@next/swc-linux-x64-musl@14.1.4": + version "14.1.4" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.1.4.tgz#8459088bdc872648ff78f121db596f2533df5808" + integrity sha512-mqVxTwk4XuBl49qn2A5UmzFImoL1iLm0KQQwtdRJRKl21ylQwwGCxJtIYo2rbfkZHoSKlh/YgztY0qH3wG1xIg== -"@next/swc-win32-arm64-msvc@14.1.3": - version "14.1.3" - resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.1.3.tgz#5367333e701f722009592013502aa8e735bee782" - integrity sha512-HjssFsCdsD4GHstXSQxsi2l70F/5FsRTRQp8xNgmQs15SxUfUJRvSI9qKny/jLkY3gLgiCR3+6A7wzzK0DBlfA== +"@next/swc-win32-arm64-msvc@14.1.4": + version "14.1.4" + resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.1.4.tgz#84280a08c00cc3be24ddd3a12f4617b108e6dea6" + integrity sha512-xzxF4ErcumXjO2Pvg/wVGrtr9QQJLk3IyQX1ddAC/fi6/5jZCZ9xpuL9Tzc4KPWMFq8GGWFVDMshZOdHGdkvag== -"@next/swc-win32-ia32-msvc@14.1.3": - version "14.1.3" - resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.1.3.tgz#dc455021fee85e037f6fb4134e85895dce5a0495" - integrity sha512-DRuxD5axfDM1/Ue4VahwSxl1O5rn61hX8/sF0HY8y0iCbpqdxw3rB3QasdHn/LJ6Wb2y5DoWzXcz3L1Cr+Thrw== +"@next/swc-win32-ia32-msvc@14.1.4": + version "14.1.4" + resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.1.4.tgz#23ff7f4bd0a27177428669ef6fa5c3923c738031" + integrity sha512-WZiz8OdbkpRw6/IU/lredZWKKZopUMhcI2F+XiMAcPja0uZYdMTZQRoQ0WZcvinn9xZAidimE7tN9W5v9Yyfyw== -"@next/swc-win32-x64-msvc@14.1.3": - version "14.1.3" - resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.1.3.tgz#4a8d4384901f0c48ece9dbb60cb9aea107d39e7c" - integrity sha512-uC2DaDoWH7h1P/aJ4Fok3Xiw6P0Lo4ez7NbowW2VGNXw/Xv6tOuLUcxhBYZxsSUJtpeknCi8/fvnSpyCFp4Rcg== +"@next/swc-win32-x64-msvc@14.1.4": + version "14.1.4" + resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.1.4.tgz#bccf5beccfde66d6c66fa4e2509118c796385eda" + integrity sha512-4Rto21sPfw555sZ/XNLqfxDUNeLhNYGO2dlPqsnuCg8N8a2a9u1ltqBOPQ4vj1Gf7eJC0W2hHG2eYUHuiXgY2w== "@nodelib/fs.scandir@2.1.5": version "2.1.5" @@ -2383,51 +2383,51 @@ resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== -"@prisma/client@5.10.2": - version "5.10.2" - resolved "https://registry.yarnpkg.com/@prisma/client/-/client-5.10.2.tgz#e087b40a4de8e3171eb9cbf0a873465cd2068e17" - integrity sha512-ef49hzB2yJZCvM5gFHMxSFL9KYrIP9udpT5rYo0CsHD4P9IKj473MbhU1gjKKftiwWBTIyrt9jukprzZXazyag== +"@prisma/client@5.11.0": + version "5.11.0" + resolved "https://registry.yarnpkg.com/@prisma/client/-/client-5.11.0.tgz#d8e55fab85163415b2245fb408b9106f83c8106d" + integrity sha512-SWshvS5FDXvgJKM/a0y9nDC1rqd7KG0Q6ZVzd+U7ZXK5soe73DJxJJgbNBt2GNXOa+ysWB4suTpdK5zfFPhwiw== -"@prisma/debug@5.10.2": - version "5.10.2" - resolved "https://registry.yarnpkg.com/@prisma/debug/-/debug-5.10.2.tgz#74be81d8969978f4d53c1b4e76d61f04bfbc3951" - integrity sha512-bkBOmH9dpEBbMKFJj8V+Zp8IZHIBjy3fSyhLhxj4FmKGb/UBSt9doyfA6k1UeUREsMJft7xgPYBbHSOYBr8XCA== +"@prisma/debug@5.11.0": + version "5.11.0" + resolved "https://registry.yarnpkg.com/@prisma/debug/-/debug-5.11.0.tgz#80e3f9d5a8f678c67a8783f7fcdda3cbbb8dd091" + integrity sha512-N6yYr3AbQqaiUg+OgjkdPp3KPW1vMTAgtKX6+BiB/qB2i1TjLYCrweKcUjzOoRM5BriA4idrkTej9A9QqTfl3A== -"@prisma/engines-version@5.10.0-34.5a9203d0590c951969e85a7d07215503f4672eb9": - version "5.10.0-34.5a9203d0590c951969e85a7d07215503f4672eb9" - resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-5.10.0-34.5a9203d0590c951969e85a7d07215503f4672eb9.tgz#1502335d4d72d2014cb25b8ad8a740a3a13400ea" - integrity sha512-uCy/++3Jx/O3ufM+qv2H1L4tOemTNqcP/gyEVOlZqTpBvYJUe0tWtW0y3o2Ueq04mll4aM5X3f6ugQftOSLdFQ== +"@prisma/engines-version@5.11.0-15.efd2449663b3d73d637ea1fd226bafbcf45b3102": + version "5.11.0-15.efd2449663b3d73d637ea1fd226bafbcf45b3102" + resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-5.11.0-15.efd2449663b3d73d637ea1fd226bafbcf45b3102.tgz#a7aa218b1ebf1077798c931632461aae8ce6a8f7" + integrity sha512-WXCuyoymvrS4zLz4wQagSsc3/nE6CHy8znyiMv8RKazKymOMd5o9FP5RGwGHAtgoxd+aB/BWqxuP/Ckfu7/3MA== -"@prisma/engines@5.10.2": - version "5.10.2" - resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-5.10.2.tgz#a4851d90f76ad6d22e783d5fd2e2e8c0640f1e81" - integrity sha512-HkSJvix6PW8YqEEt3zHfCYYJY69CXsNdhU+wna+4Y7EZ+AwzeupMnUThmvaDA7uqswiHkgm5/SZ6/4CStjaGmw== +"@prisma/engines@5.11.0": + version "5.11.0" + resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-5.11.0.tgz#96e941c5c81ce68f3a8b4c481007d397564c5d4b" + integrity sha512-gbrpQoBTYWXDRqD+iTYMirDlF9MMlQdxskQXbhARhG6A/uFQjB7DZMYocMQLoiZXO/IskfDOZpPoZE8TBQKtEw== dependencies: - "@prisma/debug" "5.10.2" - "@prisma/engines-version" "5.10.0-34.5a9203d0590c951969e85a7d07215503f4672eb9" - "@prisma/fetch-engine" "5.10.2" - "@prisma/get-platform" "5.10.2" + "@prisma/debug" "5.11.0" + "@prisma/engines-version" "5.11.0-15.efd2449663b3d73d637ea1fd226bafbcf45b3102" + "@prisma/fetch-engine" "5.11.0" + "@prisma/get-platform" "5.11.0" "@prisma/extension-read-replicas@^0.3.0": version "0.3.0" resolved "https://registry.yarnpkg.com/@prisma/extension-read-replicas/-/extension-read-replicas-0.3.0.tgz#2842a7c928f957c1dd58a6256104797596d43426" integrity sha512-F9+rSmYday6GT2qjhJtkZcBOpLO5LtpvFcMGqrBDHf+78LEdSuxfFjOxYlNuqk4B+th62yxpbhfpmB9/Mca14Q== -"@prisma/fetch-engine@5.10.2": - version "5.10.2" - resolved "https://registry.yarnpkg.com/@prisma/fetch-engine/-/fetch-engine-5.10.2.tgz#a061f6727d395c7033b55f9c6e92f8741a70d5c5" - integrity sha512-dSmXcqSt6DpTmMaLQ9K8ZKzVAMH3qwGCmYEZr/uVnzVhxRJ1EbT/w2MMwIdBNq1zT69Rvh0h75WMIi0mrIw7Hg== +"@prisma/fetch-engine@5.11.0": + version "5.11.0" + resolved "https://registry.yarnpkg.com/@prisma/fetch-engine/-/fetch-engine-5.11.0.tgz#cd7a2fa5b5d89f1da0689e329c56fa69223fba7d" + integrity sha512-994viazmHTJ1ymzvWugXod7dZ42T2ROeFuH6zHPcUfp/69+6cl5r9u3NFb6bW8lLdNjwLYEVPeu3hWzxpZeC0w== dependencies: - "@prisma/debug" "5.10.2" - "@prisma/engines-version" "5.10.0-34.5a9203d0590c951969e85a7d07215503f4672eb9" - "@prisma/get-platform" "5.10.2" + "@prisma/debug" "5.11.0" + "@prisma/engines-version" "5.11.0-15.efd2449663b3d73d637ea1fd226bafbcf45b3102" + "@prisma/get-platform" "5.11.0" -"@prisma/get-platform@5.10.2": - version "5.10.2" - resolved "https://registry.yarnpkg.com/@prisma/get-platform/-/get-platform-5.10.2.tgz#7af97b1d82e5574a474e3fbf6eaf04f4156bc535" - integrity sha512-nqXP6vHiY2PIsebBAuDeWiUYg8h8mfjBckHh6Jezuwej0QJNnjDiOq30uesmg+JXxGk99nqyG3B7wpcOODzXvg== +"@prisma/get-platform@5.11.0": + version "5.11.0" + resolved "https://registry.yarnpkg.com/@prisma/get-platform/-/get-platform-5.11.0.tgz#19a768127b1712c27f5dec8a0a79a4c9675829eb" + integrity sha512-rxtHpMLxNTHxqWuGOLzR2QOyQi79rK1u1XYAVLZxDGTLz/A+uoDnjz9veBFlicrpWjwuieM4N6jcnjj/DDoidw== dependencies: - "@prisma/debug" "5.10.2" + "@prisma/debug" "5.11.0" "@react-spring/animated@~9.7.3": version "9.7.3" @@ -8181,12 +8181,12 @@ next-basics@^0.39.0: jsonwebtoken "^9.0.0" pure-rand "^6.0.2" -next@14.1.3: - version "14.1.3" - resolved "https://registry.yarnpkg.com/next/-/next-14.1.3.tgz#465bb21a1a6e703e776ca53ea71d05642867fdb5" - integrity sha512-oexgMV2MapI0UIWiXKkixF8J8ORxpy64OuJ/J9oVUmIthXOUCcuVEZX+dtpgq7wIfIqtBwQsKEDXejcjTsan9g== +next@14.1.4: + version "14.1.4" + resolved "https://registry.yarnpkg.com/next/-/next-14.1.4.tgz#203310f7310578563fd5c961f0db4729ce7a502d" + integrity sha512-1WTaXeSrUwlz/XcnhGTY7+8eiaFvdet5z9u3V2jb+Ek1vFo0VhHKSAIJvDWfQpttWjnyw14kBeq28TPq7bTeEQ== dependencies: - "@next/env" "14.1.3" + "@next/env" "14.1.4" "@swc/helpers" "0.5.2" busboy "1.6.0" caniuse-lite "^1.0.30001579" @@ -8194,15 +8194,15 @@ next@14.1.3: postcss "8.4.31" styled-jsx "5.1.1" optionalDependencies: - "@next/swc-darwin-arm64" "14.1.3" - "@next/swc-darwin-x64" "14.1.3" - "@next/swc-linux-arm64-gnu" "14.1.3" - "@next/swc-linux-arm64-musl" "14.1.3" - "@next/swc-linux-x64-gnu" "14.1.3" - "@next/swc-linux-x64-musl" "14.1.3" - "@next/swc-win32-arm64-msvc" "14.1.3" - "@next/swc-win32-ia32-msvc" "14.1.3" - "@next/swc-win32-x64-msvc" "14.1.3" + "@next/swc-darwin-arm64" "14.1.4" + "@next/swc-darwin-x64" "14.1.4" + "@next/swc-linux-arm64-gnu" "14.1.4" + "@next/swc-linux-arm64-musl" "14.1.4" + "@next/swc-linux-x64-gnu" "14.1.4" + "@next/swc-linux-x64-musl" "14.1.4" + "@next/swc-win32-arm64-msvc" "14.1.4" + "@next/swc-win32-ia32-msvc" "14.1.4" + "@next/swc-win32-x64-msvc" "14.1.4" nice-try@^1.0.4: version "1.0.5" @@ -9320,12 +9320,12 @@ pretty-format@^29.0.0, pretty-format@^29.7.0: ansi-styles "^5.0.0" react-is "^18.0.0" -prisma@5.10.2: - version "5.10.2" - resolved "https://registry.yarnpkg.com/prisma/-/prisma-5.10.2.tgz#aa63085c49dc74cdb5c3816e8dd1fb4d74a2aadd" - integrity sha512-hqb/JMz9/kymRE25pMWCxkdyhbnIWrq+h7S6WysJpdnCvhstbJSNP/S6mScEcqiB8Qv2F+0R3yG+osRaWqZacQ== +prisma@5.11.0: + version "5.11.0" + resolved "https://registry.yarnpkg.com/prisma/-/prisma-5.11.0.tgz#ef3891f79921a2deec6f540eba13a3cc8525f6d2" + integrity sha512-KCLiug2cs0Je7kGkQBN9jDWoZ90ogE/kvZTUTgz2h94FEo8pczCkPH7fPNXkD1sGU7Yh65risGGD1HQ5DF3r3g== dependencies: - "@prisma/engines" "5.10.2" + "@prisma/engines" "5.11.0" process@^0.11.10: version "0.11.10" From 91efb7f1d0962f4c4d60cda27dfd9b2db2881362 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Mon, 25 Mar 2024 22:51:10 -0700 Subject: [PATCH 22/55] Fixed search for postgresql. --- src/lib/prisma.ts | 27 +++++++++-------- src/pages/api/send.ts | 2 +- src/pages/api/websites/[websiteId]/metrics.ts | 29 +++++++------------ .../analytics/pageviews/getPageviewMetrics.ts | 12 ++------ 4 files changed, 30 insertions(+), 40 deletions(-) diff --git a/src/lib/prisma.ts b/src/lib/prisma.ts index 9352eaeaa..b79d4249d 100644 --- a/src/lib/prisma.ts +++ b/src/lib/prisma.ts @@ -92,31 +92,31 @@ function getTimestampDiffQuery(field1: string, field2: string): string { } } -function mapFilter(column: string, filter: string, name: string, type = 'varchar') { - switch (filter) { +function mapFilter(column: string, op: string, name: string, type = 'varchar') { + switch (op) { case OPERATORS.equals: return `${column} = {{${name}::${type}}}`; case OPERATORS.notEquals: return `${column} != {{${name}::${type}}}`; case OPERATORS.contains: - return `${column} like {{${name}::${type}}}`; + return `${column} ilike {{${name}::${type}}}`; case OPERATORS.doesNotContain: - return `${column} not like {{${name}::${type}}}`; + return `${column} not ilike {{${name}::${type}}}`; default: return ''; } } function getFilterQuery(filters: QueryFilters = {}, options: QueryOptions = {}): string { - const query = Object.keys(filters).reduce((arr, name) => { - const value = filters[name]; - const filter = value?.filter ?? OPERATORS.equals; - const column = value?.column ?? FILTER_COLUMNS[name] ?? options?.columns?.[name]; + const query = Object.keys(filters).reduce((arr, key) => { + const filter = filters[key]; + const op = filter?.op ?? OPERATORS.equals; + const column = filter?.column ?? FILTER_COLUMNS[key] ?? options?.columns?.[key]; - if (value !== undefined && column !== undefined) { - arr.push(`and ${mapFilter(column, filter, name)}`); + if (filter !== undefined && column !== undefined) { + arr.push(`and ${mapFilter(column, op, key)}`); - if (name === 'referrer') { + if (key === 'referrer') { arr.push( 'and (website_event.referrer_domain != {{websiteDomain}} or website_event.referrer_domain is null)', ); @@ -171,7 +171,10 @@ async function rawQuery(sql: string, data: object): Promise { const query = sql?.replaceAll(/\{\{\s*(\w+)(::\w+)?\s*}}/g, (...args) => { const [, name, type] = args; - params.push(data[name]); + + const value = data[name]; + + params.push(value); return db === MYSQL ? '?' : `$${params.length}${type ?? ''}`; }); diff --git a/src/pages/api/send.ts b/src/pages/api/send.ts index 5aa367f04..d0d1b6263 100644 --- a/src/pages/api/send.ts +++ b/src/pages/api/send.ts @@ -97,7 +97,7 @@ export default async (req: NextApiRequestCollect, res: NextApiResponse) => { // eslint-disable-next-line prefer-const let [urlPath, urlQuery] = url?.split('?') || []; let [referrerPath, referrerQuery] = referrer?.split('?') || []; - let referrerDomain; + let referrerDomain = ''; if (!urlPath) { urlPath = '/'; diff --git a/src/pages/api/websites/[websiteId]/metrics.ts b/src/pages/api/websites/[websiteId]/metrics.ts index eb6361707..beb4dd502 100644 --- a/src/pages/api/websites/[websiteId]/metrics.ts +++ b/src/pages/api/websites/[websiteId]/metrics.ts @@ -3,7 +3,7 @@ import { badRequest, methodNotAllowed, ok, unauthorized } from 'next-basics'; import { WebsiteMetric, NextApiRequestQueryBody } from 'lib/types'; import { canViewWebsite } from 'lib/auth'; import { useAuth, useCors, useValidate } from 'lib/middleware'; -import { SESSION_COLUMNS, EVENT_COLUMNS, FILTER_COLUMNS } from 'lib/constants'; +import { SESSION_COLUMNS, EVENT_COLUMNS, FILTER_COLUMNS, OPERATORS } from 'lib/constants'; import { getPageviewMetrics, getSessionMetrics } from 'queries'; import { parseDateRangeQuery } from 'lib/query'; import * as yup from 'yup'; @@ -88,7 +88,7 @@ export default async ( } const { startDate, endDate } = await parseDateRangeQuery(req); - + const column = FILTER_COLUMNS[type] || type; const filters = { startDate, endDate, @@ -104,19 +104,18 @@ export default async ( city, language, event, - search, }; - const column = FILTER_COLUMNS[type] || type; + if (search) { + filters[column] = { + column, + op: OPERATORS.contains, + value: '%' + search + '%', + }; + } if (SESSION_COLUMNS.includes(type)) { - const data = await getSessionMetrics( - websiteId, - column, - { ...filters, search }, - limit, - offset, - ); + const data = await getSessionMetrics(websiteId, column, filters, limit, offset); if (type === 'language') { const combined = {}; @@ -138,13 +137,7 @@ export default async ( } if (EVENT_COLUMNS.includes(type)) { - const data = await getPageviewMetrics( - websiteId, - column, - { ...filters, search }, - limit, - offset, - ); + const data = await getPageviewMetrics(websiteId, column, filters, limit, offset); return ok(res, data); } diff --git a/src/queries/analytics/pageviews/getPageviewMetrics.ts b/src/queries/analytics/pageviews/getPageviewMetrics.ts index 8673dbe60..9b6a494bf 100644 --- a/src/queries/analytics/pageviews/getPageviewMetrics.ts +++ b/src/queries/analytics/pageviews/getPageviewMetrics.ts @@ -1,7 +1,7 @@ import prisma from 'lib/prisma'; import clickhouse from 'lib/clickhouse'; import { runQuery, CLICKHOUSE, PRISMA } from 'lib/db'; -import { EVENT_TYPE, SESSION_COLUMNS, OPERATORS } from 'lib/constants'; +import { EVENT_TYPE, SESSION_COLUMNS } from 'lib/constants'; import { QueryFilters } from 'lib/types'; export async function getPageviewMetrics( @@ -27,6 +27,7 @@ async function relationalQuery( offset: number = 0, ) { const { rawQuery, parseFilters } = prisma; + const { filterQuery, joinSession, params } = await parseFilters( websiteId, { @@ -69,16 +70,9 @@ async function clickhouseQuery( offset: number = 0, ): Promise<{ x: string; y: number }[]> { const { rawQuery, parseFilters } = clickhouse; + const { filterQuery, params } = await parseFilters(websiteId, { ...filters, - ...(filters.search && { - [column]: { - value: filters.search, - filter: OPERATORS.contains, - column, - name: column, - }, - }), eventType: column === 'event_name' ? EVENT_TYPE.customEvent : EVENT_TYPE.pageView, }); From 51139c1918abf53517ec6d07139748bb33b40d0b Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Tue, 26 Mar 2024 10:43:01 -0700 Subject: [PATCH 23/55] include visitors metrics in websiteStats --- src/lib/crypto.ts | 2 +- src/lib/session.ts | 4 +-- src/pages/api/send.ts | 4 +-- src/queries/analytics/getWebsiteStats.ts | 41 +++++++++++++++--------- 4 files changed, 31 insertions(+), 20 deletions(-) diff --git a/src/lib/crypto.ts b/src/lib/crypto.ts index d47c9fd80..1fbcf031d 100644 --- a/src/lib/crypto.ts +++ b/src/lib/crypto.ts @@ -12,7 +12,7 @@ export function salt() { return hash(secret(), ROTATING_SALT); } -export function sessionSalt() { +export function visitSalt() { const ROTATING_SALT = hash(startOfHour(new Date()).toUTCString()); return hash(secret(), ROTATING_SALT); diff --git a/src/lib/session.ts b/src/lib/session.ts index 6e2cbcc31..4eab67ff8 100644 --- a/src/lib/session.ts +++ b/src/lib/session.ts @@ -1,4 +1,4 @@ -import { isUuid, secret, sessionSalt, uuid } from 'lib/crypto'; +import { isUuid, secret, uuid, visitSalt } from 'lib/crypto'; import { getClientInfo } from 'lib/detect'; import { parseToken } from 'next-basics'; import { NextApiRequestCollect } from 'pages/api/send'; @@ -68,7 +68,7 @@ export async function findSession(req: NextApiRequestCollect): Promise<{ await getClientInfo(req, payload); const sessionId = uuid(websiteId, hostname, ip, userAgent); - const visitId = uuid(sessionId, sessionSalt()); + const visitId = uuid(sessionId, visitSalt()); // Clickhouse does not require session lookup if (clickhouse.enabled) { diff --git a/src/pages/api/send.ts b/src/pages/api/send.ts index 46f534fdd..1ca23a41b 100644 --- a/src/pages/api/send.ts +++ b/src/pages/api/send.ts @@ -1,7 +1,7 @@ import ipaddr from 'ipaddr.js'; import { isbot } from 'isbot'; import { COLLECTION_TYPE, HOSTNAME_REGEX, IP_REGEX } from 'lib/constants'; -import { secret, sessionSalt, uuid } from 'lib/crypto'; +import { secret, visitSalt, uuid } from 'lib/crypto'; import { getIpAddress } from 'lib/detect'; import { useCors, useSession, useValidate } from 'lib/middleware'; import { CollectionType, YupRequest } from 'lib/types'; @@ -98,7 +98,7 @@ export default async (req: NextApiRequestCollect, res: NextApiResponse) => { // expire visitId after 30 minutes session.visitId = !!session.iat && Math.floor(new Date().getTime() / 1000) - session.iat > 1800 - ? uuid(session.id, sessionSalt()) + ? uuid(session.id, visitSalt()) : session.visitId; session.iat = Math.floor(new Date().getTime() / 1000); diff --git a/src/queries/analytics/getWebsiteStats.ts b/src/queries/analytics/getWebsiteStats.ts index 51716b303..cd70ab785 100644 --- a/src/queries/analytics/getWebsiteStats.ts +++ b/src/queries/analytics/getWebsiteStats.ts @@ -1,19 +1,27 @@ -import prisma from 'lib/prisma'; import clickhouse from 'lib/clickhouse'; -import { runQuery, CLICKHOUSE, PRISMA } from 'lib/db'; import { EVENT_TYPE } from 'lib/constants'; +import { CLICKHOUSE, PRISMA, runQuery } from 'lib/db'; +import prisma from 'lib/prisma'; import { QueryFilters } from 'lib/types'; -export async function getWebsiteStats(...args: [websiteId: string, filters: QueryFilters]) { +export async function getWebsiteStats( + ...args: [websiteId: string, filters: QueryFilters] +): Promise< + { pageviews: number; uniques: number; visitors: number; bounces: number; totaltime: number }[] +> { return runQuery({ [PRISMA]: () => relationalQuery(...args), [CLICKHOUSE]: () => clickhouseQuery(...args), }); } -async function relationalQuery(websiteId: string, filters: QueryFilters) { - const { getDateQuery, getAddIntervalQuery, getTimestampDiffQuery, parseFilters, rawQuery } = - prisma; +async function relationalQuery( + websiteId: string, + filters: QueryFilters, +): Promise< + { pageviews: number; uniques: number; visitors: number; bounces: number; totaltime: number }[] +> { + const { getTimestampDiffQuery, parseFilters, rawQuery } = prisma; const { filterQuery, joinSession, params } = await parseFilters(websiteId, { ...filters, eventType: EVENT_TYPE.pageView, @@ -24,14 +32,13 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) { select sum(t.c) as "pageviews", count(distinct t.session_id) as "uniques", + count(distinct t.visit_id) as "visitors", sum(case when t.c = 1 then 1 else 0 end) as "bounces", - sum(case when t.max_time < ${getAddIntervalQuery('t.min_time', '1 hour')} - then ${getTimestampDiffQuery('t.min_time', 't.max_time')} - else 0 end) as "totaltime" + sum(${getTimestampDiffQuery('t.min_time', 't.max_time')}) as "totaltime" from ( select website_event.session_id, - ${getDateQuery('website_event.created_at', 'hour')}, + website_event.visit_id, count(*) as "c", min(website_event.created_at) as "min_time", max(website_event.created_at) as "max_time" @@ -53,8 +60,10 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) { async function clickhouseQuery( websiteId: string, filters: QueryFilters, -): Promise<{ pageviews: number; uniques: number; bounces: number; totaltime: number }[]> { - const { rawQuery, getDateQuery, parseFilters } = clickhouse; +): Promise< + { pageviews: number; uniques: number; visitors: number; bounces: number; totaltime: number }[] +> { + const { rawQuery, parseFilters } = clickhouse; const { filterQuery, params } = await parseFilters(websiteId, { ...filters, eventType: EVENT_TYPE.pageView, @@ -65,12 +74,13 @@ async function clickhouseQuery( select sum(t.c) as "pageviews", count(distinct t.session_id) as "uniques", + count(distinct t.visit_id) as "visitors", sum(if(t.c = 1, 1, 0)) as "bounces", - sum(if(max_time < min_time + interval 1 hour, max_time-min_time, 0)) as "totaltime" + sum(max_time-min_time) as "totaltime" from ( select session_id, - ${getDateQuery('created_at', 'hour')} time_series, + visit_id, count(*) c, min(created_at) min_time, max(created_at) max_time @@ -79,7 +89,7 @@ async function clickhouseQuery( and created_at between {startDate:DateTime64} and {endDate:DateTime64} and event_type = {eventType:UInt32} ${filterQuery} - group by session_id, time_series + group by session_id, visit_id ) as t; `, params, @@ -88,6 +98,7 @@ async function clickhouseQuery( return { pageviews: Number(a.pageviews), uniques: Number(a.uniques), + visitors: Number(a.visitors), bounces: Number(a.bounces), totaltime: Number(a.totaltime), }; From dcf2457e450cb294c3a16f64a98718da21f3a80d Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Tue, 26 Mar 2024 11:15:39 -0700 Subject: [PATCH 24/55] update terminology, add metric cards to new metrics --- .../[websiteId]/WebsiteMetricsBar.tsx | 51 ++++++++++++------- src/components/messages.ts | 2 + src/queries/analytics/getWebsiteStats.ts | 22 ++++---- 3 files changed, 45 insertions(+), 30 deletions(-) diff --git a/src/app/(main)/websites/[websiteId]/WebsiteMetricsBar.tsx b/src/app/(main)/websites/[websiteId]/WebsiteMetricsBar.tsx index 2dcb2b813..c488464db 100644 --- a/src/app/(main)/websites/[websiteId]/WebsiteMetricsBar.tsx +++ b/src/app/(main)/websites/[websiteId]/WebsiteMetricsBar.tsx @@ -21,11 +21,12 @@ export function WebsiteMetricsBar({ const { ref, isSticky } = useSticky({ enabled: sticky }); const { data, isLoading, isFetched, error } = useWebsiteStats(websiteId); - const { pageviews, uniques, bounces, totaltime } = data || {}; - const num = Math.min(data && uniques.value, data && bounces.value); + const { views, visitors, visits, bounces, totaltime } = data || {}; + const num = Math.min(data && visitors.value, data && bounces.value); const diffs = data && { - pageviews: pageviews.value - pageviews.change, - uniques: uniques.value - uniques.change, + views: views.value - views.change, + visitors: visitors.value - visitors.change, + visits: visits.value - visits.change, bounces: bounces.value - bounces.change, totaltime: totaltime.value - totaltime.change, }; @@ -39,25 +40,39 @@ export function WebsiteMetricsBar({ })} > - {pageviews && uniques && ( + {views && visitors && ( <> + + Number(n).toFixed(0) + '%'} @@ -66,14 +81,12 @@ export function WebsiteMetricsBar({ { return runQuery({ [PRISMA]: () => relationalQuery(...args), @@ -19,7 +19,7 @@ async function relationalQuery( websiteId: string, filters: QueryFilters, ): Promise< - { pageviews: number; uniques: number; visitors: number; bounces: number; totaltime: number }[] + { views: number; visitors: number; visits: number; bounces: number; totaltime: number }[] > { const { getTimestampDiffQuery, parseFilters, rawQuery } = prisma; const { filterQuery, joinSession, params } = await parseFilters(websiteId, { @@ -30,9 +30,9 @@ async function relationalQuery( return rawQuery( ` select - sum(t.c) as "pageviews", - count(distinct t.session_id) as "uniques", - count(distinct t.visit_id) as "visitors", + sum(t.c) as "views", + count(distinct t.session_id) as "visitors", + count(distinct t.visit_id) as "visits", sum(case when t.c = 1 then 1 else 0 end) as "bounces", sum(${getTimestampDiffQuery('t.min_time', 't.max_time')}) as "totaltime" from ( @@ -61,7 +61,7 @@ async function clickhouseQuery( websiteId: string, filters: QueryFilters, ): Promise< - { pageviews: number; uniques: number; visitors: number; bounces: number; totaltime: number }[] + { views: number; visitors: number; visits: number; bounces: number; totaltime: number }[] > { const { rawQuery, parseFilters } = clickhouse; const { filterQuery, params } = await parseFilters(websiteId, { @@ -72,9 +72,9 @@ async function clickhouseQuery( return rawQuery( ` select - sum(t.c) as "pageviews", - count(distinct t.session_id) as "uniques", - count(distinct t.visit_id) as "visitors", + sum(t.c) as "views", + count(distinct t.session_id) as "visitors", + count(distinct t.visit_id) as "visits", sum(if(t.c = 1, 1, 0)) as "bounces", sum(max_time-min_time) as "totaltime" from ( @@ -96,9 +96,9 @@ async function clickhouseQuery( ).then(a => { return Object.values(a).map(a => { return { - pageviews: Number(a.pageviews), - uniques: Number(a.uniques), + views: Number(a.views), visitors: Number(a.visitors), + visits: Number(a.visits), bounces: Number(a.bounces), totaltime: Number(a.totaltime), }; From cff2d0053655a18f6e32e62d1f1fa083f5bb58fa Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Tue, 26 Mar 2024 17:31:16 -0700 Subject: [PATCH 25/55] Refactored filter parameters. --- .../reports/[reportId]/FieldAddForm.tsx | 14 +- .../[reportId]/FieldFilterEditForm.module.css | 30 ++-- .../[reportId]/FieldFilterEditForm.tsx | 145 +++++++++++++----- .../[reportId]/FilterParameters.module.css | 2 +- .../reports/[reportId]/FilterParameters.tsx | 83 ++++++---- .../reports/[reportId]/FilterSelectForm.tsx | 6 +- .../hooks/queries/useWebsiteValues.ts | 25 ++- src/lib/clickhouse.ts | 4 +- src/lib/prisma.ts | 27 ++-- src/pages/api/reports/insights.ts | 4 +- src/pages/api/websites/[websiteId]/metrics.ts | 4 +- src/pages/api/websites/[websiteId]/values.ts | 26 +++- src/queries/analytics/getValues.ts | 44 +++++- 13 files changed, 291 insertions(+), 123 deletions(-) diff --git a/src/app/(main)/reports/[reportId]/FieldAddForm.tsx b/src/app/(main)/reports/[reportId]/FieldAddForm.tsx index 32a0b0afc..9217ce4df 100644 --- a/src/app/(main)/reports/[reportId]/FieldAddForm.tsx +++ b/src/app/(main)/reports/[reportId]/FieldAddForm.tsx @@ -3,8 +3,6 @@ import { createPortal } from 'react-dom'; import { REPORT_PARAMETERS } from 'lib/constants'; import PopupForm from './PopupForm'; import FieldSelectForm from './FieldSelectForm'; -import FieldAggregateForm from './FieldAggregateForm'; -import FieldFilterEditForm from './FieldFilterEditForm'; export function FieldAddForm({ fields = [], @@ -17,7 +15,11 @@ export function FieldAddForm({ onAdd: (group: string, value: string) => void; onClose: () => void; }) { - const [selected, setSelected] = useState<{ name: string; type: string; value: string }>(); + const [selected, setSelected] = useState<{ + name: string; + type: string; + value: string; + }>(); const handleSelect = (value: any) => { const { type } = value; @@ -39,12 +41,6 @@ export function FieldAddForm({ return createPortal( {!selected && } - {selected && group === REPORT_PARAMETERS.fields && ( - - )} - {selected && group === REPORT_PARAMETERS.filters && ( - - )} , document.body, ); diff --git a/src/app/(main)/reports/[reportId]/FieldFilterEditForm.module.css b/src/app/(main)/reports/[reportId]/FieldFilterEditForm.module.css index ed78512dc..43a34438c 100644 --- a/src/app/(main)/reports/[reportId]/FieldFilterEditForm.module.css +++ b/src/app/(main)/reports/[reportId]/FieldFilterEditForm.module.css @@ -1,12 +1,7 @@ -.popup { - display: flex; +.menu { + position: absolute; max-width: 300px; max-height: 210px; - overflow: hidden; -} - -.popup > div { - overflow-y: auto; } .filter { @@ -16,9 +11,26 @@ } .dropdown { - min-width: 180px; + min-width: 200px; } .text { - min-width: 180px; + min-width: 200px; +} + +.selected { + display: flex; + align-items: center; + justify-content: space-between; + padding: 8px 16px; + white-space: nowrap; + min-width: 200px; + font-weight: 900; + background: var(--base100); + border-radius: var(--border-radius); + cursor: pointer; +} + +.search { + position: relative; } diff --git a/src/app/(main)/reports/[reportId]/FieldFilterEditForm.tsx b/src/app/(main)/reports/[reportId]/FieldFilterEditForm.tsx index f3b5b247f..96faeb9ca 100644 --- a/src/app/(main)/reports/[reportId]/FieldFilterEditForm.tsx +++ b/src/app/(main)/reports/[reportId]/FieldFilterEditForm.tsx @@ -6,14 +6,15 @@ import { Flexbox, Dropdown, Button, + SearchField, TextField, + Text, + Icon, + Icons, Menu, - Popup, - PopupTrigger, Loading, } from 'react-basics'; import { useMessages, useFilters, useFormat, useLocale, useWebsiteValues } from 'components/hooks'; -import { safeDecodeURIComponent } from 'next-basics'; import { OPERATORS } from 'lib/constants'; import styles from './FieldFilterEditForm.module.css'; @@ -22,8 +23,11 @@ export interface FieldFilterFormProps { name: string; label?: string; type: string; + startDate: Date; + endDate: Date; + operator?: string; defaultValue?: string; - onChange?: (filter: { name: string; type: string; filter: string; value: string }) => void; + onChange?: (filter: { name: string; type: string; operator: string; value: string }) => void; allowFilterSelect?: boolean; isNew?: boolean; } @@ -33,19 +37,37 @@ export default function FieldFilterEditForm({ name, label, type, - defaultValue, + startDate, + endDate, + operator: defaultOperator = 'eq', + defaultValue = '', onChange, allowFilterSelect = true, isNew, }: FieldFilterFormProps) { const { formatMessage, labels } = useMessages(); - const [filter, setFilter] = useState('eq'); - const [value, setValue] = useState(defaultValue ?? ''); + const [operator, setOperator] = useState(defaultOperator); + const [value, setValue] = useState(defaultValue); + const [showMenu, setShowMenu] = useState(false); + const isEquals = [OPERATORS.equals, OPERATORS.notEquals].includes(operator as any); + const [search, setSearch] = useState(''); + const [selected, setSelected] = useState(isEquals ? value : ''); const { getFilters } = useFilters(); const { formatValue } = useFormat(); const { locale } = useLocale(); const filters = getFilters(type); - const { data: values = [], isLoading } = useWebsiteValues(websiteId, name); + const isDisabled = !operator || (isEquals && !selected) || (!isEquals && !value); + const { + data: values = [], + isLoading, + refetch, + } = useWebsiteValues({ + websiteId, + type: name, + startDate, + endDate, + search, + }); const formattedValues = useMemo(() => { if (!values) { @@ -69,25 +91,49 @@ export default function FieldFilterEditForm({ const filteredValues = useMemo(() => { return value - ? values.filter(n => formattedValues[n].toLowerCase().includes(value.toLowerCase())) + ? values.filter((n: string | number) => + formattedValues[n].toLowerCase().includes(value.toLowerCase()), + ) : values; }, [value, formattedValues]); - const renderFilterValue = value => { - return filters.find(f => f.value === value)?.label; + const renderFilterValue = (value: any) => { + return filters.find((f: { value: any }) => f.value === value)?.label; }; const handleAdd = () => { - onChange({ name, type, filter, value }); + onChange({ name, type, operator, value: isEquals ? selected : value }); }; - const handleMenuSelect = value => { - setValue(value); + const handleMenuSelect = (close: () => void, value: string) => { + setSelected(value); + close(); }; - const showMenu = - [OPERATORS.equals, OPERATORS.notEquals].includes(filter as any) && - !(filteredValues?.length === 1 && filteredValues[0] === formattedValues[value]); + const handleSearch = (value: string) => { + setSearch(value); + }; + + const handleReset = () => { + setSelected(''); + setValue(''); + setSearch(''); + refetch(); + }; + + const handleOperatorChange = (value: any) => { + setOperator(value); + + if ([OPERATORS.equals, OPERATORS.notEquals].includes(value)) { + setValue(''); + } else { + setSelected(''); + } + }; + + const handleBlur = () => { + window.setTimeout(() => setShowMenu(false), 500); + }; return ( @@ -97,35 +143,54 @@ export default function FieldFilterEditForm({ setFilter(key)} + onChange={handleOperatorChange} > {({ value, label }) => { return {label}; }} )} - - setValue(e.target.value)} - /> - {showMenu && ( - + {selected && isEquals && ( +
+ {selected} + + + +
+ )} + {!selected && isEquals && ( +
+ setValue(e.target.value)} + onSearch={handleSearch} + delay={500} + onFocus={() => setShowMenu(true)} + onBlur={handleBlur} + /> + {showMenu && ( - - )} - + )} +
+ )} + {!selected && !isEquals && ( + setValue(e.target.value)} + /> + )} - @@ -136,17 +201,23 @@ export default function FieldFilterEditForm({ const ResultsMenu = ({ values, type, isLoading, onSelect }) => { const { formatValue } = useFormat(); if (isLoading) { - return ; + return ( + + + + + + ); } if (!values?.length) { - return null; + return

poop

; } return ( - + {values?.map(value => { - return {safeDecodeURIComponent(formatValue(value, type))}; + return {formatValue(value, type)}; })} ); diff --git a/src/app/(main)/reports/[reportId]/FilterParameters.module.css b/src/app/(main)/reports/[reportId]/FilterParameters.module.css index 022c0d7e6..939d0652d 100644 --- a/src/app/(main)/reports/[reportId]/FilterParameters.module.css +++ b/src/app/(main)/reports/[reportId]/FilterParameters.module.css @@ -15,7 +15,7 @@ white-space: nowrap; } -.filter { +.op { color: var(--blue900); background-color: var(--blue100); font-size: 12px; diff --git a/src/app/(main)/reports/[reportId]/FilterParameters.tsx b/src/app/(main)/reports/[reportId]/FilterParameters.tsx index cd7a555ef..c712e9a41 100644 --- a/src/app/(main)/reports/[reportId]/FilterParameters.tsx +++ b/src/app/(main)/reports/[reportId]/FilterParameters.tsx @@ -1,5 +1,4 @@ import { useContext } from 'react'; -import { safeDecodeURIComponent } from 'next-basics'; import { useMessages, useFormat, useFilters, useFields } from 'components/hooks'; import Icons from 'components/icons'; import { Button, FormRow, Icon, Popup, PopupTrigger } from 'react-basics'; @@ -15,9 +14,8 @@ export function FilterParameters() { const { report, updateReport } = useContext(ReportContext); const { formatMessage, labels } = useMessages(); const { formatValue } = useFormat(); - const { filterLabels } = useFilters(); const { parameters } = report || {}; - const { websiteId, filters } = parameters || {}; + const { websiteId, filters, dateRange } = parameters || {}; const { fields } = useFields(); const handleAdd = (value: { name: any }) => { @@ -30,7 +28,7 @@ export function FilterParameters() { updateReport({ parameters: { filters: filters.filter(f => f.name !== name) } }); }; - const handleChange = filter => { + const handleChange = (close: () => void, filter: { name: any }) => { updateReport({ parameters: { filters: filters.map(f => { @@ -41,6 +39,7 @@ export function FilterParameters() { }), }, }); + close(); }; const AddButton = () => { @@ -67,44 +66,66 @@ export function FilterParameters() { return ( }> - {filters.map(({ name, filter, value }: { name: string; filter: string; value: string }) => { - const label = fields.find(f => f.name === name)?.label; - const isEquals = [OPERATORS.equals, OPERATORS.notEquals].includes(filter as any); - return ( - handleRemove(name)}> - - - ); - })} + {filters.map( + ({ name, operator, value }: { name: string; operator: string; value: string }) => { + const label = fields.find(f => f.name === name)?.label; + const isEquals = [OPERATORS.equals, OPERATORS.notEquals].includes(operator as any); + return ( + handleRemove(name)}> + + + ); + }, + )} ); } -const FilterParameter = ({ name, label, filter, value, type = 'string', onChange }) => { +const FilterParameter = ({ + websiteId, + name, + label, + operator, + value, + type = 'string', + startDate, + endDate, + onChange, +}) => { + const { filterLabels } = useFilters(); + return (
{label}
-
{filter}
-
{safeDecodeURIComponent(value)}
+
{filterLabels[operator]}
+
{value}
- - - + {(close: any) => ( + + + + )}
); diff --git a/src/app/(main)/reports/[reportId]/FilterSelectForm.tsx b/src/app/(main)/reports/[reportId]/FilterSelectForm.tsx index f43ac5f6e..b81c85767 100644 --- a/src/app/(main)/reports/[reportId]/FilterSelectForm.tsx +++ b/src/app/(main)/reports/[reportId]/FilterSelectForm.tsx @@ -1,11 +1,12 @@ import { useState } from 'react'; import FieldSelectForm from './FieldSelectForm'; import FieldFilterEditForm from './FieldFilterEditForm'; +import { useDateRange } from 'components/hooks'; export interface FilterSelectFormProps { websiteId?: string; fields: any[]; - onChange?: (filter: { name: string; type: string; filter: string; value: string }) => void; + onChange?: (filter: { name: string; type: string; operator: string; value: string }) => void; allowFilterSelect?: boolean; } @@ -16,6 +17,7 @@ export default function FilterSelectForm({ allowFilterSelect, }: FilterSelectFormProps) { const [field, setField] = useState<{ name: string; label: string; type: string }>(); + const [{ startDate, endDate }] = useDateRange(websiteId); if (!field) { return ; @@ -29,6 +31,8 @@ export default function FilterSelectForm({ name={name} label={label} type={type} + startDate={startDate} + endDate={endDate} onChange={onChange} allowFilterSelect={allowFilterSelect} isNew={true} diff --git a/src/components/hooks/queries/useWebsiteValues.ts b/src/components/hooks/queries/useWebsiteValues.ts index ab287c076..02e26fc33 100644 --- a/src/components/hooks/queries/useWebsiteValues.ts +++ b/src/components/hooks/queries/useWebsiteValues.ts @@ -1,19 +1,30 @@ import { useApi } from 'components/hooks'; -import { subDays } from 'date-fns'; -export function useWebsiteValues(websiteId: string, type: string) { - const now = Date.now(); +export function useWebsiteValues({ + websiteId, + type, + startDate, + endDate, + search, +}: { + websiteId: string; + type: string; + startDate: Date; + endDate: Date; + search?: string; +}) { const { get, useQuery } = useApi(); return useQuery({ - queryKey: ['websites:values', websiteId, type], + queryKey: ['websites:values', { websiteId, type, startDate, endDate, search }], queryFn: () => get(`/websites/${websiteId}/values`, { type, - startAt: +subDays(now, 90), - endAt: now, + startAt: +startDate, + endAt: +endDate, + search, }), - enabled: !!(websiteId && type), + enabled: !!(websiteId && type && startDate && endDate), }); } diff --git a/src/lib/clickhouse.ts b/src/lib/clickhouse.ts index 4be15db20..e41d987b7 100644 --- a/src/lib/clickhouse.ts +++ b/src/lib/clickhouse.ts @@ -61,8 +61,8 @@ function getDateFormat(date: Date) { return `'${dateFormat(date, 'UTC:yyyy-mm-dd HH:MM:ss')}'`; } -function mapFilter(column: string, filter: string, name: string, type: string = 'String') { - switch (filter) { +function mapFilter(column: string, operator: string, name: string, type: string = 'String') { + switch (operator) { case OPERATORS.equals: return `${column} = {${name}:${type}}`; case OPERATORS.notEquals: diff --git a/src/lib/prisma.ts b/src/lib/prisma.ts index b79d4249d..c3dd071b8 100644 --- a/src/lib/prisma.ts +++ b/src/lib/prisma.ts @@ -92,16 +92,20 @@ function getTimestampDiffQuery(field1: string, field2: string): string { } } -function mapFilter(column: string, op: string, name: string, type = 'varchar') { - switch (op) { +function mapFilter(column: string, operator: string, name: string, type: string = '') { + const db = getDatabaseType(); + const like = db === POSTGRESQL ? 'ilike' : 'like'; + const value = `{{${name}${type ? `::${type}` : ''}}}`; + + switch (operator) { case OPERATORS.equals: - return `${column} = {{${name}::${type}}}`; + return `${column} = ${value}`; case OPERATORS.notEquals: - return `${column} != {{${name}::${type}}}`; + return `${column} != ${value}`; case OPERATORS.contains: - return `${column} ilike {{${name}::${type}}}`; + return `${column} ${like} ${value}`; case OPERATORS.doesNotContain: - return `${column} not ilike {{${name}::${type}}}`; + return `${column} not ${like} ${value}`; default: return ''; } @@ -110,11 +114,11 @@ function mapFilter(column: string, op: string, name: string, type = 'varchar') { function getFilterQuery(filters: QueryFilters = {}, options: QueryOptions = {}): string { const query = Object.keys(filters).reduce((arr, key) => { const filter = filters[key]; - const op = filter?.op ?? OPERATORS.equals; + const operator = filter?.operator ?? OPERATORS.equals; const column = filter?.column ?? FILTER_COLUMNS[key] ?? options?.columns?.[key]; if (filter !== undefined && column !== undefined) { - arr.push(`and ${mapFilter(column, op, key)}`); + arr.push(`and ${mapFilter(column, operator, key)}`); if (key === 'referrer') { arr.push( @@ -131,9 +135,12 @@ function getFilterQuery(filters: QueryFilters = {}, options: QueryOptions = {}): function normalizeFilters(filters = {}) { return Object.keys(filters).reduce((obj, key) => { - const value = filters[key]; + const filter = filters[key]; + const value = filter?.value ?? filter; - obj[key] = value?.value ?? value; + obj[key] = [OPERATORS.contains, OPERATORS.doesNotContain].includes(filter?.operator) + ? `%${value}%` + : value; return obj; }, {}); diff --git a/src/pages/api/reports/insights.ts b/src/pages/api/reports/insights.ts index c70d218eb..efd3a6b51 100644 --- a/src/pages/api/reports/insights.ts +++ b/src/pages/api/reports/insights.ts @@ -13,7 +13,7 @@ export interface InsightsRequestBody { endDate: string; }; fields: { name: string; type: string; label: string }[]; - filters: { name: string; type: string; filter: string; value: string }[]; + filters: { name: string; type: string; operator: string; value: string }[]; groups: { name: string; type: string }[]; } @@ -42,7 +42,7 @@ const schema = { yup.object().shape({ name: yup.string().required(), type: yup.string().required(), - filter: yup.string().required(), + operator: yup.string().required(), value: yup.string().required(), }), ), diff --git a/src/pages/api/websites/[websiteId]/metrics.ts b/src/pages/api/websites/[websiteId]/metrics.ts index beb4dd502..d78c67c84 100644 --- a/src/pages/api/websites/[websiteId]/metrics.ts +++ b/src/pages/api/websites/[websiteId]/metrics.ts @@ -109,8 +109,8 @@ export default async ( if (search) { filters[column] = { column, - op: OPERATORS.contains, - value: '%' + search + '%', + operator: OPERATORS.contains, + value: search, }; } diff --git a/src/pages/api/websites/[websiteId]/values.ts b/src/pages/api/websites/[websiteId]/values.ts index 9cf2dba8b..36ca7948e 100644 --- a/src/pages/api/websites/[websiteId]/values.ts +++ b/src/pages/api/websites/[websiteId]/values.ts @@ -2,23 +2,33 @@ import { NextApiRequestQueryBody } from 'lib/types'; import { canViewWebsite } from 'lib/auth'; import { useAuth, useCors, useValidate } from 'lib/middleware'; import { NextApiResponse } from 'next'; -import { badRequest, methodNotAllowed, ok, unauthorized } from 'next-basics'; +import { + badRequest, + methodNotAllowed, + ok, + safeDecodeURIComponent, + unauthorized, +} from 'next-basics'; import { EVENT_COLUMNS, FILTER_COLUMNS, SESSION_COLUMNS } from 'lib/constants'; import { getValues } from 'queries'; import { parseDateRangeQuery } from 'lib/query'; +import * as yup from 'yup'; export interface ValuesRequestQuery { websiteId: string; + type: string; startAt: number; endAt: number; + search?: string; } -import * as yup from 'yup'; const schema = { GET: yup.object().shape({ websiteId: yup.string().uuid().required(), + type: yup.string().required(), startAt: yup.number().required(), endAt: yup.number().required(), + search: yup.string(), }), }; @@ -27,7 +37,7 @@ export default async (req: NextApiRequestQueryBody, res: Nex await useAuth(req, res); await useValidate(schema, req, res); - const { websiteId, type } = req.query; + const { websiteId, type, search } = req.query; const { startDate, endDate } = await parseDateRangeQuery(req); if (req.method === 'GET') { @@ -39,12 +49,18 @@ export default async (req: NextApiRequestQueryBody, res: Nex return unauthorized(res); } - const values = await getValues(websiteId, FILTER_COLUMNS[type as string], startDate, endDate); + const values = await getValues( + websiteId, + FILTER_COLUMNS[type as string], + startDate, + endDate, + search, + ); return ok( res, values - .map(({ value }) => value) + .map(({ value }) => safeDecodeURIComponent(value)) .filter(n => n) .sort(), ); diff --git a/src/queries/analytics/getValues.ts b/src/queries/analytics/getValues.ts index 44955423c..572fbac25 100644 --- a/src/queries/analytics/getValues.ts +++ b/src/queries/analytics/getValues.ts @@ -3,7 +3,7 @@ import clickhouse from 'lib/clickhouse'; import { runQuery, CLICKHOUSE, PRISMA } from 'lib/db'; export async function getValues( - ...args: [websiteId: string, column: string, startDate: Date, endDate: Date] + ...args: [websiteId: string, column: string, startDate: Date, endDate: Date, search: string] ) { return runQuery({ [PRISMA]: () => relationalQuery(...args), @@ -11,42 +11,72 @@ export async function getValues( }); } -async function relationalQuery(websiteId: string, column: string, startDate: Date, endDate: Date) { +async function relationalQuery( + websiteId: string, + column: string, + startDate: Date, + endDate: Date, + search: string, +) { const { rawQuery } = prisma; + let searchQuery = ''; + + if (search) { + searchQuery = `and ${column} LIKE {{search}}`; + } return rawQuery( ` - select distinct ${column} as "value" + select ${column} as "value", count(*) from website_event inner join session on session.session_id = website_event.session_id where website_event.website_id = {{websiteId::uuid}} and website_event.created_at between {{startDate}} and {{endDate}} - limit 500 + ${searchQuery} + group by 1 + order by 2 desc + limit 10 `, { websiteId, startDate, endDate, + search: `%${search}%`, }, ); } -async function clickhouseQuery(websiteId: string, column: string, startDate: Date, endDate: Date) { +async function clickhouseQuery( + websiteId: string, + column: string, + startDate: Date, + endDate: Date, + search: string, +) { const { rawQuery } = clickhouse; + let searchQuery = ''; + + if (search) { + searchQuery = `and positionCaseInsensitive(${column}, {search:String}) > 0`; + } return rawQuery( ` - select distinct ${column} as value + select ${column} as value, count(*) from website_event where website_id = {websiteId:UUID} and created_at between {startDate:DateTime64} and {endDate:DateTime64} - limit 500 + ${searchQuery} + group by 1 + order by 2 desc + limit 10 `, { websiteId, startDate, endDate, + search, }, ); } From 91f49ba5068af8b13fd5537f1b3aafe6cd4c5514 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Tue, 26 Mar 2024 21:46:57 -0700 Subject: [PATCH 26/55] Updated encoding logic in tracker and send. --- src/pages/api/send.ts | 18 +++++++++++++----- src/tracker/index.js | 20 +++++++++++++++++--- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/pages/api/send.ts b/src/pages/api/send.ts index 1ca23a41b..bf6fc2137 100644 --- a/src/pages/api/send.ts +++ b/src/pages/api/send.ts @@ -6,7 +6,15 @@ import { getIpAddress } from 'lib/detect'; import { useCors, useSession, useValidate } from 'lib/middleware'; import { CollectionType, YupRequest } from 'lib/types'; import { NextApiRequest, NextApiResponse } from 'next'; -import { badRequest, createToken, forbidden, methodNotAllowed, ok, send } from 'next-basics'; +import { + badRequest, + createToken, + forbidden, + methodNotAllowed, + ok, + safeDecodeURI, + send, +} from 'next-basics'; import { saveEvent, saveSessionData } from 'queries'; import * as yup from 'yup'; @@ -88,8 +96,8 @@ export default async (req: NextApiRequestCollect, res: NextApiResponse) => { } const { type, payload } = req.body; - - const { url, referrer, name: eventName, data: eventData, title: pageTitle } = payload; + const { url, referrer, name: eventName, data: eventData, title } = payload; + const pageTitle = safeDecodeURI(title); await useSession(req, res); @@ -105,8 +113,8 @@ export default async (req: NextApiRequestCollect, res: NextApiResponse) => { if (type === COLLECTION_TYPE.event) { // eslint-disable-next-line prefer-const - let [urlPath, urlQuery] = url?.split('?') || []; - let [referrerPath, referrerQuery] = referrer?.split('?') || []; + let [urlPath, urlQuery] = safeDecodeURI(url)?.split('?') || []; + let [referrerPath, referrerQuery] = safeDecodeURI(referrer)?.split('?') || []; let referrerDomain = ''; if (!urlPath) { diff --git a/src/tracker/index.js b/src/tracker/index.js index d16b2a9ae..ace1d7c23 100644 --- a/src/tracker/index.js +++ b/src/tracker/index.js @@ -32,6 +32,20 @@ /* Helper functions */ + const encode = str => { + try { + const result = decodeURI(str); + + if (result !== str) { + return result; + } + } catch { + return str; + } + + return encodeURI(str); + }; + const parseURL = url => { return excludeSearch ? url.split('?')[0] : url; }; @@ -41,9 +55,9 @@ hostname, screen, language, - title: encodeURIComponent(title), - url: encodeURI(currentUrl), - referrer: encodeURI(currentRef), + title: encode(title), + url: encode(currentUrl), + referrer: encode(currentRef), }); /* Event handlers */ From 41593ee1df9b0d0253ddaee0234cf1ecc8825e04 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Wed, 27 Mar 2024 02:06:16 -0700 Subject: [PATCH 27/55] Prevent null error. --- src/components/input/DateFilter.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/input/DateFilter.tsx b/src/components/input/DateFilter.tsx index f025da97b..721c43505 100644 --- a/src/components/input/DateFilter.tsx +++ b/src/components/input/DateFilter.tsx @@ -109,7 +109,7 @@ export function DateFilter({ ); } - return options.find(e => e.value === value).label; + return options.find(e => e.value === value)?.label; }; return ( From ab58c76b6a5a8587f65327d271091463a7aa67f6 Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Wed, 27 Mar 2024 11:17:00 -0700 Subject: [PATCH 28/55] change views back to pageviews --- db/clickhouse/migrations/02_add_visit_id.sql | 1 - .../[websiteId]/WebsiteMetricsBar.tsx | 22 ++++++++++--------- src/queries/analytics/getWebsiteStats.ts | 8 +++---- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/db/clickhouse/migrations/02_add_visit_id.sql b/db/clickhouse/migrations/02_add_visit_id.sql index 5dd7bcd11..202c0fd30 100644 --- a/db/clickhouse/migrations/02_add_visit_id.sql +++ b/db/clickhouse/migrations/02_add_visit_id.sql @@ -78,7 +78,6 @@ FROM umami.website_event we JOIN umami.website_event_join j ON we.session_id = j.session_id and date_trunc('hour', we.created_at) = j.created_at -WHERE we.created_at > '2023-03-31'; RENAME TABLE umami.website_event TO umami.website_event_old; RENAME TABLE umami.website_event_new TO umami.website_event; diff --git a/src/app/(main)/websites/[websiteId]/WebsiteMetricsBar.tsx b/src/app/(main)/websites/[websiteId]/WebsiteMetricsBar.tsx index 10f288955..6e2930dc5 100644 --- a/src/app/(main)/websites/[websiteId]/WebsiteMetricsBar.tsx +++ b/src/app/(main)/websites/[websiteId]/WebsiteMetricsBar.tsx @@ -21,10 +21,10 @@ export function WebsiteMetricsBar({ const { ref, isSticky } = useSticky({ enabled: sticky }); const { data, isLoading, isFetched, error } = useWebsiteStats(websiteId); - const { views, visitors, visits, bounces, totaltime } = data || {}; + const { pageviews, visitors, visits, bounces, totaltime } = data || {}; const num = Math.min(data && visitors.value, data && bounces.value); const diffs = data && { - views: views.value - views.change, + pageviews: pageviews.value - pageviews.change, visitors: visitors.value - visitors.change, visits: visits.value - visits.change, bounces: bounces.value - bounces.change, @@ -40,12 +40,12 @@ export function WebsiteMetricsBar({ })} > - {views && visitors && ( + {pageviews && visitors && ( <> { return runQuery({ [PRISMA]: () => relationalQuery(...args), @@ -19,7 +19,7 @@ async function relationalQuery( websiteId: string, filters: QueryFilters, ): Promise< - { views: number; visitors: number; visits: number; bounces: number; totaltime: number }[] + { pageviews: number; visitors: number; visits: number; bounces: number; totaltime: number }[] > { const { getTimestampDiffQuery, parseFilters, rawQuery } = prisma; const { filterQuery, joinSession, params } = await parseFilters(websiteId, { @@ -61,7 +61,7 @@ async function clickhouseQuery( websiteId: string, filters: QueryFilters, ): Promise< - { views: number; visitors: number; visits: number; bounces: number; totaltime: number }[] + { pageviews: number; visitors: number; visits: number; bounces: number; totaltime: number }[] > { const { rawQuery, parseFilters } = clickhouse; const { filterQuery, params } = await parseFilters(websiteId, { @@ -96,7 +96,7 @@ async function clickhouseQuery( ).then(a => { return Object.values(a).map(a => { return { - views: Number(a.views), + pageviews: Number(a.views), visitors: Number(a.visitors), visits: Number(a.visits), bounces: Number(a.bounces), From 740a9408c0fb3371e7b308d9fb78e986d7d7ff58 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Wed, 27 Mar 2024 16:54:21 -0700 Subject: [PATCH 29/55] Fixed views label. --- src/app/(main)/websites/[websiteId]/WebsiteMetricsBar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/(main)/websites/[websiteId]/WebsiteMetricsBar.tsx b/src/app/(main)/websites/[websiteId]/WebsiteMetricsBar.tsx index 6e2930dc5..e4acea3bf 100644 --- a/src/app/(main)/websites/[websiteId]/WebsiteMetricsBar.tsx +++ b/src/app/(main)/websites/[websiteId]/WebsiteMetricsBar.tsx @@ -43,7 +43,7 @@ export function WebsiteMetricsBar({ {pageviews && visitors && ( <> From cfa568f15cf91a1e8c21a9f8ba9cfe592fa678b8 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Wed, 27 Mar 2024 17:07:03 -0700 Subject: [PATCH 30/55] Fixed column name for pageviews. --- src/queries/analytics/getWebsiteStats.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/queries/analytics/getWebsiteStats.ts b/src/queries/analytics/getWebsiteStats.ts index d05ea808d..c5d010dba 100644 --- a/src/queries/analytics/getWebsiteStats.ts +++ b/src/queries/analytics/getWebsiteStats.ts @@ -30,7 +30,7 @@ async function relationalQuery( return rawQuery( ` select - sum(t.c) as "views", + sum(t.c) as "pageviews", count(distinct t.session_id) as "visitors", count(distinct t.visit_id) as "visits", sum(case when t.c = 1 then 1 else 0 end) as "bounces", @@ -72,7 +72,7 @@ async function clickhouseQuery( return rawQuery( ` select - sum(t.c) as "views", + sum(t.c) as "pageviews", count(distinct t.session_id) as "visitors", count(distinct t.visit_id) as "visits", sum(if(t.c = 1, 1, 0)) as "bounces", @@ -93,14 +93,14 @@ async function clickhouseQuery( ) as t; `, params, - ).then(a => { - return Object.values(a).map(a => { + ).then(result => { + return Object.values(result).map(n => { return { - pageviews: Number(a.views), - visitors: Number(a.visitors), - visits: Number(a.visits), - bounces: Number(a.bounces), - totaltime: Number(a.totaltime), + pageviews: Number(n.pageviews), + visitors: Number(n.visitors), + visits: Number(n.visits), + bounces: Number(n.bounces), + totaltime: Number(n.totaltime), }; }); }); From 4e77d95809b1cade7c6d060e3fefdbf3a188908a Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Wed, 27 Mar 2024 19:56:45 -0700 Subject: [PATCH 31/55] Updated minimum date check. --- src/lib/date.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/date.ts b/src/lib/date.ts index 6348e04bb..1c0b95b29 100644 --- a/src/lib/date.ts +++ b/src/lib/date.ts @@ -261,7 +261,7 @@ export function getMinimumUnit(startDate: number | Date, endDate: number | Date) return 'minute'; } else if (differenceInHours(endDate, startDate) <= 48) { return 'hour'; - } else if (differenceInCalendarMonths(endDate, startDate) <= 12) { + } else if (differenceInCalendarMonths(endDate, startDate) <= 6) { return 'day'; } else if (differenceInCalendarMonths(endDate, startDate) <= 24) { return 'month'; From a5362d919785909588f790c2acbc2636473794af Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Wed, 27 Mar 2024 20:05:19 -0700 Subject: [PATCH 32/55] Prevent undefined strings in tracker. --- src/tracker/index.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/tracker/index.js b/src/tracker/index.js index ace1d7c23..1c2fcc205 100644 --- a/src/tracker/index.js +++ b/src/tracker/index.js @@ -33,6 +33,10 @@ /* Helper functions */ const encode = str => { + if (!str) { + return undefined; + } + try { const result = decodeURI(str); From a933f5b4a0e10c277f89bd32483ea4e9dd7659f2 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Wed, 27 Mar 2024 20:13:31 -0700 Subject: [PATCH 33/55] Lowercase before checking headers. Closes #2634. --- src/lib/detect.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lib/detect.ts b/src/lib/detect.ts index af5c491f1..561b91be7 100644 --- a/src/lib/detect.ts +++ b/src/lib/detect.ts @@ -17,9 +17,11 @@ import { NextApiRequestCollect } from 'pages/api/send'; let lookup; export function getIpAddress(req: NextApiRequestCollect) { + const customHeader = String(process.env.CLIENT_IP_HEADER).toLowerCase(); + // Custom header - if (req.headers[process.env.CLIENT_IP_HEADER]) { - return req.headers[process.env.CLIENT_IP_HEADER]; + if (customHeader !== 'undefined' && req.headers[customHeader]) { + return req.headers[customHeader]; } // Cloudflare else if (req.headers['cf-connecting-ip']) { From 538fb49219ef8af723c0ac5164d9a582d8000876 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Wed, 27 Mar 2024 20:49:48 -0700 Subject: [PATCH 34/55] Null check. --- src/components/metrics/OSTable.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/metrics/OSTable.tsx b/src/components/metrics/OSTable.tsx index d822e3afc..7744bf119 100644 --- a/src/components/metrics/OSTable.tsx +++ b/src/components/metrics/OSTable.tsx @@ -11,7 +11,7 @@ export function OSTable(props: MetricsTableProps) { {os} Date: Thu, 28 Mar 2024 10:04:01 -0700 Subject: [PATCH 35/55] Fixed filter menu issue. --- .../(main)/reports/[reportId]/FieldFilterEditForm.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/app/(main)/reports/[reportId]/FieldFilterEditForm.tsx b/src/app/(main)/reports/[reportId]/FieldFilterEditForm.tsx index c1181e72c..7bb3ff122 100644 --- a/src/app/(main)/reports/[reportId]/FieldFilterEditForm.tsx +++ b/src/app/(main)/reports/[reportId]/FieldFilterEditForm.tsx @@ -105,9 +105,9 @@ export default function FieldFilterEditForm({ onChange({ name, type, operator, value: isEquals ? selected : value }); }; - const handleMenuSelect = (close: () => void, value: string) => { + const handleMenuSelect = (value: string) => { setSelected(value); - close(); + setShowMenu(false); }; const handleSearch = (value: string) => { @@ -177,7 +177,7 @@ export default function FieldFilterEditForm({ values={filteredValues} type={name} isLoading={isLoading} - onSelect={handleMenuSelect.bind(null, close)} + onSelect={handleMenuSelect} /> )}
@@ -202,7 +202,7 @@ const ResultsMenu = ({ values, type, isLoading, onSelect }) => { const { formatValue } = useFormat(); if (isLoading) { return ( - + @@ -216,7 +216,7 @@ const ResultsMenu = ({ values, type, isLoading, onSelect }) => { return ( - {values?.map(value => { + {values?.map((value: any) => { return {formatValue(value, type)}; })} From e1ad3b99cd246dacc95e4c801583b7d43ab1ebe4 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Thu, 28 Mar 2024 16:26:27 -0700 Subject: [PATCH 36/55] Fixed wrong chart color. --- src/components/charts/Chart.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/charts/Chart.tsx b/src/components/charts/Chart.tsx index ba49796eb..a01192e47 100644 --- a/src/components/charts/Chart.tsx +++ b/src/components/charts/Chart.tsx @@ -99,7 +99,7 @@ export function Chart({ updateChart(data); } } - }, [data]); + }, [data, options]); const handleLegendClick = (item: LegendItem) => { if (type === 'bar') { From 40c1ce40e29f3d73035d6841bf03bd2e87e470fa Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Fri, 29 Mar 2024 08:39:05 -0700 Subject: [PATCH 37/55] Fixed chart rendering issue. --- src/components/charts/BarChart.tsx | 75 ++++++++++++++++-------------- src/components/charts/Chart.tsx | 46 +++++++++--------- 2 files changed, 63 insertions(+), 58 deletions(-) diff --git a/src/components/charts/BarChart.tsx b/src/components/charts/BarChart.tsx index 09ff51548..5655d7439 100644 --- a/src/components/charts/BarChart.tsx +++ b/src/components/charts/BarChart.tsx @@ -1,3 +1,4 @@ +import { useMemo } from 'react'; import { useTheme } from 'components/hooks'; import Chart, { ChartProps } from 'components/charts/Chart'; import { renderNumberLabels } from 'lib/charts'; @@ -25,45 +26,47 @@ export function BarChart(props: BarChartProps) { stacked = false, } = props; - const options = { - scales: { - x: { - type: XAxisType, - stacked: true, - time: { - unit, + const options = useMemo(() => { + return { + scales: { + x: { + type: XAxisType, + stacked: true, + time: { + unit, + }, + grid: { + display: false, + }, + border: { + color: colors.chart.line, + }, + ticks: { + color: colors.chart.text, + autoSkip: false, + maxRotation: 0, + callback: renderXLabel, + }, }, - grid: { - display: false, - }, - border: { - color: colors.chart.line, - }, - ticks: { - color: colors.chart.text, - autoSkip: false, - maxRotation: 0, - callback: renderXLabel, + y: { + type: YAxisType, + min: 0, + beginAtZero: true, + stacked, + grid: { + color: colors.chart.line, + }, + border: { + color: colors.chart.line, + }, + ticks: { + color: colors.chart.text, + callback: renderYLabel || renderNumberLabels, + }, }, }, - y: { - type: YAxisType, - min: 0, - beginAtZero: true, - stacked, - grid: { - color: colors.chart.line, - }, - border: { - color: colors.chart.line, - }, - ticks: { - color: colors.chart.text, - callback: renderYLabel || renderNumberLabels, - }, - }, - }, - }; + }; + }, [colors]); const handleTooltip = ({ tooltip }: { tooltip: any }) => { const { opacity } = tooltip; diff --git a/src/components/charts/Chart.tsx b/src/components/charts/Chart.tsx index a01192e47..2ec40d078 100644 --- a/src/components/charts/Chart.tsx +++ b/src/components/charts/Chart.tsx @@ -1,4 +1,4 @@ -import { useState, useRef, useEffect, ReactNode } from 'react'; +import { useState, useRef, useEffect, useMemo, ReactNode } from 'react'; import { Loading } from 'react-basics'; import classNames from 'classnames'; import ChartJS, { LegendItem } from 'chart.js/auto'; @@ -38,29 +38,31 @@ export function Chart({ const chart = useRef(null); const [legendItems, setLegendItems] = useState([]); - const options = { - responsive: true, - maintainAspectRatio: false, - animation: { - duration: animationDuration, - resize: { - duration: 0, + const options = useMemo(() => { + return { + responsive: true, + maintainAspectRatio: false, + animation: { + duration: animationDuration, + resize: { + duration: 0, + }, + active: { + duration: 0, + }, }, - active: { - duration: 0, + plugins: { + legend: { + display: false, + }, + tooltip: { + enabled: false, + external: onTooltip, + }, }, - }, - plugins: { - legend: { - display: false, - }, - tooltip: { - enabled: false, - external: onTooltip, - }, - }, - ...chartOptions, - }; + ...chartOptions, + }; + }, [chartOptions]); const createChart = (data: any) => { ChartJS.defaults.font.family = 'Inter'; From 314bbee7172bd3f430c38227598901576a3bc87e Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Fri, 29 Mar 2024 09:36:07 -0700 Subject: [PATCH 38/55] Fixed funnel report parameters not showing. --- src/app/(main)/reports/funnel/FunnelParameters.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/app/(main)/reports/funnel/FunnelParameters.tsx b/src/app/(main)/reports/funnel/FunnelParameters.tsx index 7c4aa8454..e3eab41c2 100644 --- a/src/app/(main)/reports/funnel/FunnelParameters.tsx +++ b/src/app/(main)/reports/funnel/FunnelParameters.tsx @@ -72,7 +72,11 @@ export function FunnelParameters() { }> {urls.map(url => { - return handleRemoveUrl(url)} />; + return ( + handleRemoveUrl(url)}> + {url} + + ); })} From 7ec4e5dfa297036b4eb29dae0007df367273cd6a Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Fri, 29 Mar 2024 11:27:25 -0700 Subject: [PATCH 39/55] Fix UTM report for postgres. udpate parseParameters to work with bigInt --- src/queries/analytics/reports/getUTM.ts | 32 ++++++------------------- 1 file changed, 7 insertions(+), 25 deletions(-) diff --git a/src/queries/analytics/reports/getUTM.ts b/src/queries/analytics/reports/getUTM.ts index f30c1c8a1..4e1af9f02 100644 --- a/src/queries/analytics/reports/getUTM.ts +++ b/src/queries/analytics/reports/getUTM.ts @@ -26,15 +26,7 @@ async function relationalQuery( endDate: Date; timezone?: string; }, -): Promise< - { - date: string; - day: number; - visitors: number; - returnVisitors: number; - percentage: number; - }[] -> { +) { const { startDate, endDate } = filters; const { rawQuery } = prisma; @@ -44,7 +36,7 @@ async function relationalQuery( from website_event where website_id = {{websiteId::uuid}} and created_at between {{startDate}} and {{endDate}} - and url_query is not null + and coalesce(url_query, '') != '' and event_type = 1 group by 1 `, @@ -53,9 +45,7 @@ async function relationalQuery( startDate, endDate, }, - ).then(results => { - return results; - }); + ).then(result => parseParameters(result as any[])); } async function clickhouseQuery( @@ -65,15 +55,7 @@ async function clickhouseQuery( endDate: Date; timezone?: string; }, -): Promise< - { - date: string; - day: number; - visitors: number; - returnVisitors: number; - percentage: number; - }[] -> { +) { const { startDate, endDate } = filters; const { rawQuery } = clickhouse; @@ -104,11 +86,11 @@ function parseParameters(data: any[]) { if (key.match(/^utm_(\w+)$/)) { const name = safeDecodeURIComponent(value); if (!obj[key]) { - obj[key] = { [name]: +num }; + obj[key] = { [name]: Number(num) }; } else if (!obj[key][name]) { - obj[key][name] = +num; + obj[key][name] = Number(num); } else { - obj[key][name] += +num; + obj[key][name] += Number(num); } } } From dd88b1d901f19736d723076c886ddeb7db9b6432 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Fri, 29 Mar 2024 11:48:12 -0700 Subject: [PATCH 40/55] Fixed styling on update notice. Added private mode. --- next.config.js | 2 ++ src/app/(main)/UpdateNotice.module.css | 7 +++++-- src/app/(main)/UpdateNotice.tsx | 5 +++-- src/components/common/Favicon.tsx | 4 ++++ src/components/input/ProfileButton.tsx | 2 +- src/pages/api/scripts/telemetry.ts | 2 +- 6 files changed, 16 insertions(+), 6 deletions(-) diff --git a/next.config.js b/next.config.js index dce49100e..f8850c60c 100644 --- a/next.config.js +++ b/next.config.js @@ -14,6 +14,7 @@ const frameAncestors = process.env.ALLOWED_FRAME_URLS || ''; const disableLogin = process.env.DISABLE_LOGIN || ''; const disableUI = process.env.DISABLE_UI || ''; const hostURL = process.env.HOST_URL || ''; +const privateMode = process.env.PRIVATE_MODE || ''; const contentSecurityPolicy = [ `default-src 'self'`, @@ -120,6 +121,7 @@ const config = { disableLogin, disableUI, hostURL, + privateMode, }, basePath, output: 'standalone', diff --git a/src/app/(main)/UpdateNotice.module.css b/src/app/(main)/UpdateNotice.module.css index 261a31698..fec0962cd 100644 --- a/src/app/(main)/UpdateNotice.module.css +++ b/src/app/(main)/UpdateNotice.module.css @@ -1,14 +1,17 @@ .notice { position: absolute; + display: flex; + justify-content: space-between; + width: 100%; max-width: 800px; gap: 20px; - margin: 80px auto; + margin: 60px auto; align-self: center; background: var(--base50); padding: 20px; border: 1px solid var(--base300); border-radius: var(--border-radius); - z-index: var(--z-index-popup); + z-index: 9999; box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.1); } diff --git a/src/app/(main)/UpdateNotice.tsx b/src/app/(main)/UpdateNotice.tsx index 54ad05c9a..553e1138b 100644 --- a/src/app/(main)/UpdateNotice.tsx +++ b/src/app/(main)/UpdateNotice.tsx @@ -4,9 +4,9 @@ import { Button } from 'react-basics'; import { setItem } from 'next-basics'; import useStore, { checkVersion } from 'store/version'; import { REPO_URL, VERSION_CHECK } from 'lib/constants'; -import styles from './UpdateNotice.module.css'; import { useMessages } from 'components/hooks'; import { usePathname } from 'next/navigation'; +import styles from './UpdateNotice.module.css'; export function UpdateNotice({ user, config }) { const { formatMessage, labels, messages } = useMessages(); @@ -16,8 +16,9 @@ export function UpdateNotice({ user, config }) { const allowUpdate = user?.isAdmin && !config?.updatesDisabled && - !config?.cloudMode && !pathname.includes('/share/') && + !process.env.cloudMode && + !process.env.privateMode && !dismissed; const updateCheck = useCallback(() => { diff --git a/src/components/common/Favicon.tsx b/src/components/common/Favicon.tsx index 2bf43c771..2793c0d0f 100644 --- a/src/components/common/Favicon.tsx +++ b/src/components/common/Favicon.tsx @@ -6,6 +6,10 @@ function getHostName(url: string) { } export function Favicon({ domain, ...props }) { + if (process.env.privateMode) { + return null; + } + const hostName = domain ? getHostName(domain) : null; return hostName ? ( diff --git a/src/components/input/ProfileButton.tsx b/src/components/input/ProfileButton.tsx index ff3ee63ec..b1875165a 100644 --- a/src/components/input/ProfileButton.tsx +++ b/src/components/input/ProfileButton.tsx @@ -11,7 +11,7 @@ export function ProfileButton() { const { user } = useLogin(); const router = useRouter(); const { dir } = useLocale(); - const cloudMode = Boolean(process.env.cloudMode); + const cloudMode = !!process.env.cloudMode; const handleSelect = (key: Key, close: () => void) => { if (key === 'profile') { diff --git a/src/pages/api/scripts/telemetry.ts b/src/pages/api/scripts/telemetry.ts index bc0fd5032..a8a8872e5 100644 --- a/src/pages/api/scripts/telemetry.ts +++ b/src/pages/api/scripts/telemetry.ts @@ -6,7 +6,7 @@ export default function handler(req: NextApiRequest, res: NextApiResponse) { if (process.env.NODE_ENV === 'production') { res.setHeader('content-type', 'text/javascript'); - if (process.env.DISABLE_TELEMETRY) { + if (process.env.DISABLE_TELEMETRY || process.env.PRIVATE_MODE) { return res.send('/* telemetry disabled */'); } From 291562f6c257c5240d977e30beec1ffd562a3b9f Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Fri, 29 Mar 2024 16:04:39 -0700 Subject: [PATCH 41/55] Updated timezone hook, fixed chart rendering, added icons. --- .../profile/DateRangeSetting.module.css | 3 + src/app/(main)/profile/DateRangeSetting.tsx | 4 +- .../(main)/profile/TimezoneSetting.module.css | 4 ++ src/app/(main)/profile/TimezoneSetting.tsx | 13 +++-- src/components/common/Favicon.tsx | 3 +- src/components/hooks/queries/useReport.ts | 2 +- .../hooks/queries/useWebsitePageviews.ts | 2 +- src/components/hooks/useTheme.ts | 58 ++++++++++--------- src/components/hooks/useTimezone.ts | 22 ++++--- src/components/metrics/EventsChart.tsx | 2 +- src/components/metrics/ReferrersTable.tsx | 17 ++++-- src/pages/api/send.ts | 20 ++++--- src/store/app.ts | 7 +++ src/tracker/index.js | 2 + 14 files changed, 94 insertions(+), 65 deletions(-) create mode 100644 src/app/(main)/profile/DateRangeSetting.module.css diff --git a/src/app/(main)/profile/DateRangeSetting.module.css b/src/app/(main)/profile/DateRangeSetting.module.css new file mode 100644 index 000000000..9de13efe9 --- /dev/null +++ b/src/app/(main)/profile/DateRangeSetting.module.css @@ -0,0 +1,3 @@ +.field { + width: 200px; +} diff --git a/src/app/(main)/profile/DateRangeSetting.tsx b/src/app/(main)/profile/DateRangeSetting.tsx index a1ae7bc79..c57a209a6 100644 --- a/src/app/(main)/profile/DateRangeSetting.tsx +++ b/src/app/(main)/profile/DateRangeSetting.tsx @@ -3,6 +3,7 @@ import { Button, Flexbox } from 'react-basics'; import { useDateRange, useMessages } from 'components/hooks'; import { DEFAULT_DATE_RANGE } from 'lib/constants'; import { DateRange } from 'lib/types'; +import styles from './DateRangeSetting.module.css'; export function DateRangeSetting() { const { formatMessage, labels } = useMessages(); @@ -13,8 +14,9 @@ export function DateRangeSetting() { const handleReset = () => setDateRange(DEFAULT_DATE_RANGE); return ( - + n.toLowerCase().includes(search.toLowerCase())) - : listTimeZones(); + ? timezones.filter(n => n.toLowerCase().includes(search.toLowerCase())) + : timezones; const handleReset = () => saveTimezone(getTimezone()); return ( saveTimezone(value)} menuProps={{ className: styles.menu }} allowSearch={true} onSearch={setSearch} diff --git a/src/components/common/Favicon.tsx b/src/components/common/Favicon.tsx index 2793c0d0f..cdaeaf4bf 100644 --- a/src/components/common/Favicon.tsx +++ b/src/components/common/Favicon.tsx @@ -16,7 +16,8 @@ export function Favicon({ domain, ...props }) { diff --git a/src/components/hooks/queries/useReport.ts b/src/components/hooks/queries/useReport.ts index ef571d008..4dade4db6 100644 --- a/src/components/hooks/queries/useReport.ts +++ b/src/components/hooks/queries/useReport.ts @@ -8,7 +8,7 @@ export function useReport(reportId: string, defaultParameters: { [key: string]: const [report, setReport] = useState(null); const [isRunning, setIsRunning] = useState(false); const { get, post } = useApi(); - const [timezone] = useTimezone(); + const { timezone } = useTimezone(); const { formatMessage, labels } = useMessages(); const baseParameters = { diff --git a/src/components/hooks/queries/useWebsitePageviews.ts b/src/components/hooks/queries/useWebsitePageviews.ts index 91db3717f..960c9584f 100644 --- a/src/components/hooks/queries/useWebsitePageviews.ts +++ b/src/components/hooks/queries/useWebsitePageviews.ts @@ -4,7 +4,7 @@ export function useWebsitePageviews(websiteId: string, options?: { [key: string] const { get, useQuery } = useApi(); const [dateRange] = useDateRange(websiteId); const { startDate, endDate, unit } = dateRange; - const [timezone] = useTimezone(); + const { timezone } = useTimezone(); const { query: { url, referrer, os, browser, device, country, region, city, title }, } = useNavigation(); diff --git a/src/components/hooks/useTheme.ts b/src/components/hooks/useTheme.ts index f2a2d448b..aa2b1d381 100644 --- a/src/components/hooks/useTheme.ts +++ b/src/components/hooks/useTheme.ts @@ -1,4 +1,4 @@ -import { useEffect } from 'react'; +import { useEffect, useMemo } from 'react'; import useStore, { setTheme } from 'store/app'; import { getItem, setItem } from 'next-basics'; import { DEFAULT_THEME, THEME_COLORS, THEME_CONFIG } from 'lib/constants'; @@ -10,38 +10,40 @@ export function useTheme() { const theme = useStore(selector) || getItem(THEME_CONFIG) || DEFAULT_THEME; const primaryColor = colord(THEME_COLORS[theme].primary); - const colors = { - theme: { - ...THEME_COLORS[theme], - }, - chart: { - text: THEME_COLORS[theme].gray700, - line: THEME_COLORS[theme].gray200, - views: { - hoverBackgroundColor: primaryColor.alpha(0.7).toRgbString(), - backgroundColor: primaryColor.alpha(0.4).toRgbString(), - borderColor: primaryColor.alpha(0.7).toRgbString(), - hoverBorderColor: primaryColor.toRgbString(), + const colors = useMemo(() => { + return { + theme: { + ...THEME_COLORS[theme], }, - visitors: { - hoverBackgroundColor: primaryColor.alpha(0.9).toRgbString(), - backgroundColor: primaryColor.alpha(0.6).toRgbString(), - borderColor: primaryColor.alpha(0.9).toRgbString(), - hoverBorderColor: primaryColor.toRgbString(), + chart: { + text: THEME_COLORS[theme].gray700, + line: THEME_COLORS[theme].gray200, + views: { + hoverBackgroundColor: primaryColor.alpha(0.7).toRgbString(), + backgroundColor: primaryColor.alpha(0.4).toRgbString(), + borderColor: primaryColor.alpha(0.7).toRgbString(), + hoverBorderColor: primaryColor.toRgbString(), + }, + visitors: { + hoverBackgroundColor: primaryColor.alpha(0.9).toRgbString(), + backgroundColor: primaryColor.alpha(0.6).toRgbString(), + borderColor: primaryColor.alpha(0.9).toRgbString(), + hoverBorderColor: primaryColor.toRgbString(), + }, }, - }, - map: { - baseColor: THEME_COLORS[theme].primary, - fillColor: THEME_COLORS[theme].gray100, - strokeColor: THEME_COLORS[theme].primary, - hoverColor: THEME_COLORS[theme].primary, - }, - }; + map: { + baseColor: THEME_COLORS[theme].primary, + fillColor: THEME_COLORS[theme].gray100, + strokeColor: THEME_COLORS[theme].primary, + hoverColor: THEME_COLORS[theme].primary, + }, + }; + }, [theme]); - function saveTheme(value) { + const saveTheme = (value: string) => { setItem(THEME_CONFIG, value); setTheme(value); - } + }; useEffect(() => { document.body.setAttribute('data-theme', theme); diff --git a/src/components/hooks/useTimezone.ts b/src/components/hooks/useTimezone.ts index 3dbb52b3b..8bd76504b 100644 --- a/src/components/hooks/useTimezone.ts +++ b/src/components/hooks/useTimezone.ts @@ -1,20 +1,18 @@ -import { useState, useCallback } from 'react'; -import { getTimezone } from 'lib/date'; -import { getItem, setItem } from 'next-basics'; +import { setItem } from 'next-basics'; import { TIMEZONE_CONFIG } from 'lib/constants'; +import useStore, { setTimezone } from 'store/app'; + +const selector = (state: { timezone: string }) => state.timezone; export function useTimezone() { - const [timezone, setTimezone] = useState(getItem(TIMEZONE_CONFIG) || getTimezone()); + const timezone = useStore(selector); - const saveTimezone = useCallback( - (value: string) => { - setItem(TIMEZONE_CONFIG, value); - setTimezone(value); - }, - [setTimezone], - ); + const saveTimezone = (value: string) => { + setItem(TIMEZONE_CONFIG, value); + setTimezone(value); + }; - return [timezone, saveTimezone]; + return { timezone, saveTimezone }; } export default useTimezone; diff --git a/src/components/metrics/EventsChart.tsx b/src/components/metrics/EventsChart.tsx index 87e652768..9e98b6ce1 100644 --- a/src/components/metrics/EventsChart.tsx +++ b/src/components/metrics/EventsChart.tsx @@ -22,7 +22,7 @@ export interface EventsChartProps { export function EventsChart({ websiteId, className, token }: EventsChartProps) { const [{ startDate, endDate, unit, offset }] = useDateRange(websiteId); const { locale } = useLocale(); - const [timezone] = useTimezone(); + const { timezone } = useTimezone(); const { query: { url, event }, } = useNavigation(); diff --git a/src/components/metrics/ReferrersTable.tsx b/src/components/metrics/ReferrersTable.tsx index 0f5f5f58d..7dceedd8a 100644 --- a/src/components/metrics/ReferrersTable.tsx +++ b/src/components/metrics/ReferrersTable.tsx @@ -1,18 +1,23 @@ import MetricsTable, { MetricsTableProps } from './MetricsTable'; import FilterLink from 'components/common/FilterLink'; +import Favicon from 'components/common/Favicon'; import { useMessages } from 'components/hooks'; +import { Flexbox } from 'react-basics'; export function ReferrersTable(props: MetricsTableProps) { const { formatMessage, labels } = useMessages(); const renderLink = ({ x: referrer }) => { return ( - + + + + ); }; diff --git a/src/pages/api/send.ts b/src/pages/api/send.ts index bf6fc2137..b078a9047 100644 --- a/src/pages/api/send.ts +++ b/src/pages/api/send.ts @@ -20,16 +20,17 @@ import * as yup from 'yup'; export interface CollectRequestBody { payload: { - data: { [key: string]: any }; - hostname: string; - ip: string; - language: string; - referrer: string; - screen: string; - title: string; - url: string; website: string; - name: string; + data?: { [key: string]: any }; + hostname?: string; + ip?: string; + language?: string; + name?: string; + referrer?: string; + screen?: string; + tag?: string; + title?: string; + url: string; }; type: CollectionType; } @@ -72,6 +73,7 @@ const schema = { url: yup.string(), website: yup.string().uuid().required(), name: yup.string().max(50), + tag: yup.string().max(50), }) .required(), type: yup diff --git a/src/store/app.ts b/src/store/app.ts index f71a245bf..4d547d4e8 100644 --- a/src/store/app.ts +++ b/src/store/app.ts @@ -6,8 +6,10 @@ import { DEFAULT_THEME, LOCALE_CONFIG, THEME_CONFIG, + TIMEZONE_CONFIG, } from 'lib/constants'; import { getItem } from 'next-basics'; +import { getTimezone } from 'lib/date'; function getDefaultTheme() { return typeof window !== 'undefined' @@ -20,6 +22,7 @@ function getDefaultTheme() { const initialState = { locale: getItem(LOCALE_CONFIG) || DEFAULT_LOCALE, theme: getItem(THEME_CONFIG) || getDefaultTheme() || DEFAULT_THEME, + timezone: getItem(TIMEZONE_CONFIG) || getTimezone(), dateRange: getItem(DATE_RANGE_CONFIG) || DEFAULT_DATE_RANGE, shareToken: null, user: null, @@ -32,6 +35,10 @@ export function setTheme(theme: string) { store.setState({ theme }); } +export function setTimezone(timezone: string) { + store.setState({ timezone }); +} + export function setLocale(locale: string) { store.setState({ locale }); } diff --git a/src/tracker/index.js b/src/tracker/index.js index 1c2fcc205..361105379 100644 --- a/src/tracker/index.js +++ b/src/tracker/index.js @@ -18,6 +18,7 @@ const attr = currentScript.getAttribute.bind(currentScript); const website = attr(_data + 'website-id'); const hostUrl = attr(_data + 'host-url'); + const tag = attr(_data + 'tag'); const autoTrack = attr(_data + 'auto-track') !== _false; const excludeSearch = attr(_data + 'exclude-search') === _true; const domain = attr(_data + 'domains') || ''; @@ -216,6 +217,7 @@ ...getPayload(), name: obj, data: typeof data === 'object' ? data : undefined, + tag, }); } else if (typeof obj === 'object') { return send(obj); From 490e4464812627e1100f16a2238ab0e6b95c7d62 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Fri, 29 Mar 2024 16:17:24 -0700 Subject: [PATCH 42/55] Removed timezone-support package. --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 23c03519f..bd27a4cfe 100644 --- a/package.json +++ b/package.json @@ -115,7 +115,6 @@ "request-ip": "^3.3.0", "semver": "^7.5.4", "thenby": "^1.3.4", - "timezone-support": "^2.0.2", "uuid": "^9.0.0", "yup": "^0.32.11", "zustand": "^4.3.8" From a4d8afe5160a1d89e6246704c9a0c22f4f591aa2 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Fri, 29 Mar 2024 16:56:19 -0700 Subject: [PATCH 43/55] Convert local time to timezone. --- src/components/charts/BarChart.tsx | 2 +- .../hooks/queries/useWebsiteEvents.ts | 19 ++++++++++++- .../hooks/queries/useWebsitePageviews.ts | 5 ++-- src/components/metrics/EventsChart.tsx | 28 +++---------------- .../analytics/pageviews/getPageviewStats.ts | 4 +-- yarn.lock | 25 ----------------- 6 files changed, 28 insertions(+), 55 deletions(-) diff --git a/src/components/charts/BarChart.tsx b/src/components/charts/BarChart.tsx index 5655d7439..635bb10de 100644 --- a/src/components/charts/BarChart.tsx +++ b/src/components/charts/BarChart.tsx @@ -66,7 +66,7 @@ export function BarChart(props: BarChartProps) { }, }, }; - }, [colors]); + }, [colors, unit, stacked, renderXLabel, renderYLabel]); const handleTooltip = ({ tooltip }: { tooltip: any }) => { const { opacity } = tooltip; diff --git a/src/components/hooks/queries/useWebsiteEvents.ts b/src/components/hooks/queries/useWebsiteEvents.ts index de18a1f90..992814702 100644 --- a/src/components/hooks/queries/useWebsiteEvents.ts +++ b/src/components/hooks/queries/useWebsiteEvents.ts @@ -1,12 +1,29 @@ import useApi from './useApi'; import { UseQueryOptions } from '@tanstack/react-query'; +import { useDateRange, useNavigation, useTimezone } from 'components/hooks'; +import { zonedTimeToUtc } from 'date-fns-tz'; export function useWebsiteEvents( websiteId: string, - params?: { [key: string]: any }, options?: Omit, ) { const { get, useQuery } = useApi(); + const [dateRange] = useDateRange(websiteId); + const { startDate, endDate, unit, offset } = dateRange; + const { timezone } = useTimezone(); + const { + query: { url, event }, + } = useNavigation(); + + const params = { + startAt: +zonedTimeToUtc(startDate, timezone), + endAt: +zonedTimeToUtc(endDate, timezone), + unit, + offset, + timezone, + url, + event, + }; return useQuery({ queryKey: ['events', { ...params }], diff --git a/src/components/hooks/queries/useWebsitePageviews.ts b/src/components/hooks/queries/useWebsitePageviews.ts index 960c9584f..2d6e3428b 100644 --- a/src/components/hooks/queries/useWebsitePageviews.ts +++ b/src/components/hooks/queries/useWebsitePageviews.ts @@ -1,3 +1,4 @@ +import { zonedTimeToUtc } from 'date-fns-tz'; import { useApi, useDateRange, useNavigation, useTimezone } from 'components/hooks'; export function useWebsitePageviews(websiteId: string, options?: { [key: string]: string }) { @@ -10,8 +11,8 @@ export function useWebsitePageviews(websiteId: string, options?: { [key: string] } = useNavigation(); const params = { - startAt: +startDate, - endAt: +endDate, + startAt: +zonedTimeToUtc(startDate, timezone), + endAt: +zonedTimeToUtc(endDate, timezone), unit, timezone, url, diff --git a/src/components/metrics/EventsChart.tsx b/src/components/metrics/EventsChart.tsx index 9e98b6ce1..eb2fb703c 100644 --- a/src/components/metrics/EventsChart.tsx +++ b/src/components/metrics/EventsChart.tsx @@ -3,39 +3,19 @@ import { Loading } from 'react-basics'; import { colord } from 'colord'; import BarChart from 'components/charts/BarChart'; import { getDateArray } from 'lib/date'; -import { - useLocale, - useDateRange, - useTimezone, - useNavigation, - useWebsiteEvents, -} from 'components/hooks'; +import { useLocale, useDateRange, useWebsiteEvents } from 'components/hooks'; import { CHART_COLORS } from 'lib/constants'; import { renderDateLabels } from 'lib/charts'; export interface EventsChartProps { websiteId: string; className?: string; - token?: string; } -export function EventsChart({ websiteId, className, token }: EventsChartProps) { - const [{ startDate, endDate, unit, offset }] = useDateRange(websiteId); +export function EventsChart({ websiteId, className }: EventsChartProps) { + const [{ startDate, endDate, unit }] = useDateRange(websiteId); const { locale } = useLocale(); - const { timezone } = useTimezone(); - const { - query: { url, event }, - } = useNavigation(); - const { data, isLoading } = useWebsiteEvents(websiteId, { - startAt: +startDate, - endAt: +endDate, - unit, - timezone, - url, - event, - token, - offset, - }); + const { data, isLoading } = useWebsiteEvents(websiteId); const chartData = useMemo(() => { if (!data) return []; diff --git a/src/queries/analytics/pageviews/getPageviewStats.ts b/src/queries/analytics/pageviews/getPageviewStats.ts index b221e0104..3f29c75fe 100644 --- a/src/queries/analytics/pageviews/getPageviewStats.ts +++ b/src/queries/analytics/pageviews/getPageviewStats.ts @@ -66,8 +66,8 @@ async function clickhouseQuery( order by t `, params, - ).then(a => { - return Object.values(a).map(a => { + ).then(result => { + return Object.values(result).map(a => { return { x: a.x, y: Number(a.y) }; }); }); diff --git a/yarn.lock b/yarn.lock index e438a9b6d..5b8ba33cc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4265,11 +4265,6 @@ commander@2, commander@^2.20.0, commander@^2.20.3: resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== -commander@2.20.0: - version "2.20.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422" - integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ== - commander@8: version "8.3.0" resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" @@ -8103,13 +8098,6 @@ moize@^6.1.0: fast-equals "^3.0.1" micro-memoize "^4.1.2" -moment-timezone@0.5.26: - version "0.5.26" - resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.26.tgz#c0267ca09ae84631aa3dc33f65bedbe6e8e0d772" - integrity sha512-sFP4cgEKTCymBBKgoxZjYzlSovC20Y6J7y3nanDc5RoBIXKlZhoYwBoZGe3flwU6A372AcRwScH8KiwV6zjy1g== - dependencies: - moment ">= 2.9.0" - moment-timezone@^0.5.35: version "0.5.45" resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.45.tgz#cb685acd56bac10e69d93c536366eb65aa6bcf5c" @@ -8117,11 +8105,6 @@ moment-timezone@^0.5.35: dependencies: moment "^2.29.4" -"moment@>= 2.9.0": - version "2.29.4" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108" - integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w== - moment@^2.29.4: version "2.30.1" resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae" @@ -10778,14 +10761,6 @@ through@^2.3.8: resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== -timezone-support@^2.0.2: - version "2.2.0" - resolved "https://registry.yarnpkg.com/timezone-support/-/timezone-support-2.2.0.tgz#b3146cb99bf188a92b5348591202e8e3aa013135" - integrity sha512-4TmVraC9vxQVLMGeV5OaC12QWbYMhzFWTyAcBO64UB53kbLRIuDdQlr/ZvmatdOv8z5pWw/uK0kZ1DBm4uoUhw== - dependencies: - commander "2.20.0" - moment-timezone "0.5.26" - tiny-glob@^0.2.9: version "0.2.9" resolved "https://registry.yarnpkg.com/tiny-glob/-/tiny-glob-0.2.9.tgz#2212d441ac17928033b110f8b3640683129d31e2" From ef1112467245e0a7a07d6dc03dfd8f1e6738bbbf Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Mon, 1 Apr 2024 10:10:56 -0700 Subject: [PATCH 44/55] Filter tag enhancements. --- .../[reportId]/FieldFilterEditForm.tsx | 12 +-- .../reports/[reportId]/FieldParameters.tsx | 2 +- .../reports/[reportId]/FilterParameters.tsx | 9 +- .../websites/[websiteId]/WebsiteDetails.tsx | 11 +-- .../WebsiteFilterButton.module.css | 3 + .../[websiteId]/WebsiteFilterButton.tsx | 16 +--- src/components/hooks/useFilters.ts | 12 ++- src/components/metrics/FilterTags.module.css | 33 +++++-- src/components/metrics/FilterTags.tsx | 94 +++++++++++++++---- src/lib/constants.ts | 7 ++ src/lib/params.ts | 15 +++ src/lib/query.ts | 10 +- 12 files changed, 160 insertions(+), 64 deletions(-) create mode 100644 src/app/(main)/websites/[websiteId]/WebsiteFilterButton.module.css create mode 100644 src/lib/params.ts diff --git a/src/app/(main)/reports/[reportId]/FieldFilterEditForm.tsx b/src/app/(main)/reports/[reportId]/FieldFilterEditForm.tsx index 7bb3ff122..f1ebd30df 100644 --- a/src/app/(main)/reports/[reportId]/FieldFilterEditForm.tsx +++ b/src/app/(main)/reports/[reportId]/FieldFilterEditForm.tsx @@ -16,6 +16,7 @@ import { } from 'react-basics'; import { useMessages, useFilters, useFormat, useLocale, useWebsiteValues } from 'components/hooks'; import { OPERATORS } from 'lib/constants'; +import { operatorEquals } from 'lib/params'; import styles from './FieldFilterEditForm.module.css'; export interface FieldFilterFormProps { @@ -49,13 +50,12 @@ export default function FieldFilterEditForm({ const [operator, setOperator] = useState(defaultOperator); const [value, setValue] = useState(defaultValue); const [showMenu, setShowMenu] = useState(false); - const isEquals = [OPERATORS.equals, OPERATORS.notEquals].includes(operator as any); + const isEquals = operatorEquals(operator); const [search, setSearch] = useState(''); const [selected, setSelected] = useState(isEquals ? value : ''); - const { getFilters } = useFilters(); + const { filters } = useFilters(); const { formatValue } = useFormat(); const { locale } = useLocale(); - const filters = getFilters(type); const isDisabled = !operator || (isEquals && !selected) || (!isEquals && !value); const { data: values = [], @@ -98,7 +98,7 @@ export default function FieldFilterEditForm({ }, [value, formattedValues]); const renderFilterValue = (value: any) => { - return filters.find((f: { value: any }) => f.value === value)?.label; + return filters.find((filter: { value: any }) => filter.value === value)?.label; }; const handleAdd = () => { @@ -142,7 +142,7 @@ export default function FieldFilterEditForm({ {allowFilterSelect && ( f.type === type)} value={operator} renderValue={renderFilterValue} onChange={handleOperatorChange} @@ -154,7 +154,7 @@ export default function FieldFilterEditForm({ )} {selected && isEquals && (
- {selected} + {formatValue(selected, name)} diff --git a/src/app/(main)/reports/[reportId]/FieldParameters.tsx b/src/app/(main)/reports/[reportId]/FieldParameters.tsx index ded9dee7b..36cfbda9b 100644 --- a/src/app/(main)/reports/[reportId]/FieldParameters.tsx +++ b/src/app/(main)/reports/[reportId]/FieldParameters.tsx @@ -5,7 +5,7 @@ import { Button, FormRow, Icon, Popup, PopupTrigger } from 'react-basics'; import FieldSelectForm from '../[reportId]/FieldSelectForm'; import ParameterList from '../[reportId]/ParameterList'; import PopupForm from '../[reportId]/PopupForm'; -import { ReportContext } from '../[reportId]/Report'; +import { ReportContext } from './Report'; export function FieldParameters() { const { report, updateReport } = useContext(ReportContext); diff --git a/src/app/(main)/reports/[reportId]/FilterParameters.tsx b/src/app/(main)/reports/[reportId]/FilterParameters.tsx index c712e9a41..d88f785da 100644 --- a/src/app/(main)/reports/[reportId]/FilterParameters.tsx +++ b/src/app/(main)/reports/[reportId]/FilterParameters.tsx @@ -6,8 +6,8 @@ import FilterSelectForm from '../[reportId]/FilterSelectForm'; import ParameterList from '../[reportId]/ParameterList'; import PopupForm from '../[reportId]/PopupForm'; import { ReportContext } from './Report'; -import { OPERATORS } from 'lib/constants'; import FieldFilterEditForm from '../[reportId]/FieldFilterEditForm'; +import { operatorEquals } from 'lib/params'; import styles from './FilterParameters.module.css'; export function FilterParameters() { @@ -69,7 +69,8 @@ export function FilterParameters() { {filters.map( ({ name, operator, value }: { name: string; operator: string; value: string }) => { const label = fields.find(f => f.name === name)?.label; - const isEquals = [OPERATORS.equals, OPERATORS.notEquals].includes(operator as any); + const isEquals = operatorEquals(operator); + return ( handleRemove(name)}> { - const { filterLabels } = useFilters(); + const { operatorLabels } = useFilters(); return (
{label}
-
{filterLabels[operator]}
+
{operatorLabels[operator]}
{value}
diff --git a/src/app/(main)/websites/[websiteId]/WebsiteDetails.tsx b/src/app/(main)/websites/[websiteId]/WebsiteDetails.tsx index d917c6d71..1a131da13 100644 --- a/src/app/(main)/websites/[websiteId]/WebsiteDetails.tsx +++ b/src/app/(main)/websites/[websiteId]/WebsiteDetails.tsx @@ -13,20 +13,19 @@ import WebsiteTableView from './WebsiteTableView'; export default function WebsiteDetails({ websiteId }: { websiteId: string }) { const { data: website, isLoading, error } = useWebsite(websiteId); const pathname = usePathname(); - const showLinks = !pathname.includes('/share/'); - - const { - query: { view, url, referrer, os, browser, device, country, region, city, title }, - } = useNavigation(); + const { query } = useNavigation(); if (isLoading || error) { return ; } + const showLinks = !pathname.includes('/share/'); + const { view, ...params } = query; + return ( <> - + {!website && } diff --git a/src/app/(main)/websites/[websiteId]/WebsiteFilterButton.module.css b/src/app/(main)/websites/[websiteId]/WebsiteFilterButton.module.css new file mode 100644 index 000000000..80f101e3a --- /dev/null +++ b/src/app/(main)/websites/[websiteId]/WebsiteFilterButton.module.css @@ -0,0 +1,3 @@ +.button { + font-weight: 700; +} diff --git a/src/app/(main)/websites/[websiteId]/WebsiteFilterButton.tsx b/src/app/(main)/websites/[websiteId]/WebsiteFilterButton.tsx index 6ee0cb6dc..dda7cb063 100644 --- a/src/app/(main)/websites/[websiteId]/WebsiteFilterButton.tsx +++ b/src/app/(main)/websites/[websiteId]/WebsiteFilterButton.tsx @@ -1,8 +1,10 @@ +import classNames from 'classnames'; import { Button, Icon, Icons, Popup, PopupTrigger, Text } from 'react-basics'; import PopupForm from 'app/(main)/reports/[reportId]/PopupForm'; import FilterSelectForm from 'app/(main)/reports/[reportId]/FilterSelectForm'; import { useFields, useMessages, useNavigation } from 'components/hooks'; -import { OPERATORS } from 'lib/constants'; +import { OPERATOR_PREFIXES } from 'lib/constants'; +import styles from './WebsiteFilterButton.module.css'; export function WebsiteFilterButton({ websiteId, @@ -16,22 +18,14 @@ export function WebsiteFilterButton({ const { fields } = useFields(); const handleAddFilter = ({ name, operator, value }) => { - let prefix = ''; - - if (operator === OPERATORS.notEquals) { - prefix = '!'; - } else if (operator === OPERATORS.contains) { - prefix = '~'; - } else if (operator === OPERATORS.doesNotContain) { - prefix = '!~'; - } + const prefix = OPERATOR_PREFIXES[operator]; router.push(renderUrl({ [name]: prefix + value })); }; return ( - + - + @@ -69,12 +75,17 @@ export function FunnelParameters() { - }> + }> - {urls.map(url => { + {steps.map((step: { type: string; value: string }, index: number) => { return ( - handleRemoveUrl(url)}> - {url} + handleRemoveStep(index)}> +
+
+ {step.type === 'url' ? : } +
+
{step.value}
+
); })} diff --git a/src/app/(main)/reports/funnel/FunnelReport.tsx b/src/app/(main)/reports/funnel/FunnelReport.tsx index 7b9a6677a..850bbd906 100644 --- a/src/app/(main)/reports/funnel/FunnelReport.tsx +++ b/src/app/(main)/reports/funnel/FunnelReport.tsx @@ -9,7 +9,7 @@ import { REPORT_TYPES } from 'lib/constants'; const defaultParameters = { type: REPORT_TYPES.funnel, - parameters: { window: 60, urls: [] }, + parameters: { window: 60, steps: [] }, }; export default function FunnelReport({ reportId }: { reportId?: string }) { diff --git a/src/app/(main)/reports/funnel/FunnelStepAddForm.module.css b/src/app/(main)/reports/funnel/FunnelStepAddForm.module.css new file mode 100644 index 000000000..a254ff088 --- /dev/null +++ b/src/app/(main)/reports/funnel/FunnelStepAddForm.module.css @@ -0,0 +1,7 @@ +.dropdown { + width: 140px; +} + +.input { + width: 200px; +} diff --git a/src/app/(main)/reports/funnel/FunnelStepAddForm.tsx b/src/app/(main)/reports/funnel/FunnelStepAddForm.tsx new file mode 100644 index 000000000..978747c9c --- /dev/null +++ b/src/app/(main)/reports/funnel/FunnelStepAddForm.tsx @@ -0,0 +1,71 @@ +import { useState } from 'react'; +import { useMessages } from 'components/hooks'; +import { Button, FormRow, TextField, Flexbox, Dropdown, Item } from 'react-basics'; +import styles from './FunnelStepAddForm.module.css'; + +export interface UrlAddFormProps { + defaultValue?: string; + onAdd?: (step: { type: string; value: string }) => void; +} + +export function FunnelStepAddForm({ defaultValue = '', onAdd }: UrlAddFormProps) { + const [type, setType] = useState('url'); + const [value, setValue] = useState(defaultValue); + const { formatMessage, labels } = useMessages(); + const items = [ + { label: formatMessage(labels.url), value: 'url' }, + { label: formatMessage(labels.event), value: 'event' }, + ]; + const isDisabled = !type || !value; + + const handleSave = () => { + onAdd({ type, value }); + setValue(''); + }; + + const handleChange = e => { + setValue(e.target.value); + }; + + const handleKeyDown = e => { + if (e.key === 'Enter') { + e.stopPropagation(); + handleSave(); + } + }; + + const renderTypeValue = (value: any) => { + return items.find(item => item.value === value)?.label; + }; + + return ( + + + setType(value)} + > + {({ value, label }) => { + return {label}; + }} + + + + + + ); +} + +export default FunnelStepAddForm; diff --git a/src/app/(main)/reports/funnel/UrlAddForm.module.css b/src/app/(main)/reports/funnel/UrlAddForm.module.css deleted file mode 100644 index 6a3e03b5e..000000000 --- a/src/app/(main)/reports/funnel/UrlAddForm.module.css +++ /dev/null @@ -1,14 +0,0 @@ -.form { - position: absolute; - background: var(--base50); - width: 300px; - padding: 30px; - margin-top: 10px; - border: 1px solid var(--base400); - border-radius: var(--border-radius); - box-shadow: 0 0 0 5px rgba(0, 0, 0, 0.1); -} - -.input { - width: 100%; -} diff --git a/src/app/(main)/reports/funnel/UrlAddForm.tsx b/src/app/(main)/reports/funnel/UrlAddForm.tsx deleted file mode 100644 index 88c27ae91..000000000 --- a/src/app/(main)/reports/funnel/UrlAddForm.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { useState } from 'react'; -import { useMessages } from 'components/hooks'; -import { Button, Form, FormRow, TextField, Flexbox } from 'react-basics'; -import styles from './UrlAddForm.module.css'; - -export interface UrlAddFormProps { - defaultValue?: string; - onAdd?: (url: string) => void; -} - -export function UrlAddForm({ defaultValue = '', onAdd }: UrlAddFormProps) { - const [url, setUrl] = useState(defaultValue); - const { formatMessage, labels } = useMessages(); - - const handleSave = () => { - onAdd(url); - setUrl(''); - }; - - const handleChange = e => { - setUrl(e.target.value); - }; - - const handleKeyDown = e => { - if (e.key === 'Enter') { - e.stopPropagation(); - handleSave(); - } - }; - - return ( - - - - - - - - - ); -} - -export default UrlAddForm; diff --git a/src/components/hooks/queries/useReport.ts b/src/components/hooks/queries/useReport.ts index 4dade4db6..3aacabb40 100644 --- a/src/components/hooks/queries/useReport.ts +++ b/src/components/hooks/queries/useReport.ts @@ -4,7 +4,10 @@ import { useApi } from './useApi'; import { useTimezone } from '../useTimezone'; import { useMessages } from '../useMessages'; -export function useReport(reportId: string, defaultParameters: { [key: string]: any } = {}) { +export function useReport( + reportId: string, + defaultParameters: { type: string; parameters: { [key: string]: any } }, +) { const [report, setReport] = useState(null); const [isRunning, setIsRunning] = useState(false); const { get, post } = useApi(); @@ -28,6 +31,8 @@ export function useReport(reportId: string, defaultParameters: { [key: string]: dateRange.endDate = new Date(endDate); } + data.parameters = { ...defaultParameters?.parameters, ...data.parameters }; + setReport(data); }; @@ -41,7 +46,7 @@ export function useReport(reportId: string, defaultParameters: { [key: string]: setReport( produce((state: any) => { - state.parameters = parameters; + state.parameters = { ...defaultParameters?.parameters, ...parameters }; state.data = data; return state; @@ -60,7 +65,11 @@ export function useReport(reportId: string, defaultParameters: { [key: string]: const { parameters, ...rest } = data; if (parameters) { - state.parameters = { ...state.parameters, ...parameters }; + state.parameters = { + ...defaultParameters?.parameters, + ...state.parameters, + ...parameters, + }; } for (const key in rest) { @@ -80,7 +89,7 @@ export function useReport(reportId: string, defaultParameters: { [key: string]: } else { loadReport(reportId); } - }, []); + }, [reportId]); return { report, runReport, updateReport, isRunning }; } diff --git a/src/components/messages.ts b/src/components/messages.ts index 50774dfd7..4057bbfd3 100644 --- a/src/components/messages.ts +++ b/src/components/messages.ts @@ -232,6 +232,8 @@ export const labels = defineMessages({ id: 'label.utm-description', defaultMessage: 'Track your campaigns through UTM parameters.', }, + steps: { id: 'label.steps', defaultMessage: 'Steps' }, + addStep: { id: 'label.add-step', defaultMessage: 'Add step' }, }); export const messages = defineMessages({ diff --git a/src/queries/analytics/reports/getFunnel.ts b/src/queries/analytics/reports/getFunnel.ts index a7dfd542a..f9ceb85cd 100644 --- a/src/queries/analytics/reports/getFunnel.ts +++ b/src/queries/analytics/reports/getFunnel.ts @@ -3,8 +3,7 @@ import { CLICKHOUSE, PRISMA, runQuery } from 'lib/db'; import prisma from 'lib/prisma'; const formatResults = (steps: { type: string; value: string }[]) => (results: unknown) => { - return steps.map((steps: { type: string; value: string }, i: number) => { - const url = steps.value; + return steps.map((step: { type: string; value: string }, i: number) => { const visitors = Number(results[i]?.count) || 0; const previous = Number(results[i - 1]?.count) || 0; const dropped = previous > 0 ? previous - visitors : 0; @@ -12,7 +11,7 @@ const formatResults = (steps: { type: string; value: string }[]) => (results: un const remaining = visitors / Number(results[0].count); return { - url, + ...step, visitors, previous, dropped, @@ -49,7 +48,7 @@ async function relationalQuery( }, ): Promise< { - url: string; + value: string; visitors: number; dropoff: number; }[] @@ -141,7 +140,7 @@ async function clickhouseQuery( }, ): Promise< { - url: string; + value: string; visitors: number; dropoff: number; }[] From 3aee54009ce64e7191cf4c2970914a456b8229db Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Tue, 2 Apr 2024 23:53:08 -0700 Subject: [PATCH 53/55] Funnel report styling. --- .../(main)/reports/funnel/FunnelChart.module.css | 15 +++++++++------ src/app/(main)/reports/funnel/FunnelChart.tsx | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/app/(main)/reports/funnel/FunnelChart.module.css b/src/app/(main)/reports/funnel/FunnelChart.module.css index be2a9cdc8..45d0ea616 100644 --- a/src/app/(main)/reports/funnel/FunnelChart.module.css +++ b/src/app/(main)/reports/funnel/FunnelChart.module.css @@ -37,12 +37,12 @@ .card { display: grid; gap: 20px; + margin-top: 8px; } .header { display: flex; - align-items: center; - font-weight: 700; + flex-direction: column; gap: 20px; } @@ -58,7 +58,9 @@ } .label { - color: var(--base700); + color: var(--base600); + font-weight: 700; + text-transform: uppercase; } .track { @@ -71,9 +73,9 @@ } .item { - padding: 6px 10px; - border-radius: 4px; - border: 1px solid var(--base300); + font-size: 24px; + color: var(--base900); + font-weight: 700; } .metric { @@ -88,6 +90,7 @@ .visitors { color: var(--base900); + font-size: 32px; font-weight: 900; margin-right: 10px; } diff --git a/src/app/(main)/reports/funnel/FunnelChart.tsx b/src/app/(main)/reports/funnel/FunnelChart.tsx index 70076354c..0da71d6f5 100644 --- a/src/app/(main)/reports/funnel/FunnelChart.tsx +++ b/src/app/(main)/reports/funnel/FunnelChart.tsx @@ -25,7 +25,7 @@ export function FunnelChart({ className }: FunnelChartProps) {
- {formatMessage(type === 'url' ? labels.viewedPage : labels.triggeredEvent)}: + {formatMessage(type === 'url' ? labels.viewedPage : labels.triggeredEvent)} {value}
From cf8d49f8675f99e3a471cdacbcf6def246e9f783 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Wed, 3 Apr 2024 14:44:49 -0700 Subject: [PATCH 54/55] Funnel report updates. Fixed insights report. --- .../[reportId]/FieldFilterEditForm.tsx | 2 +- .../reports/[reportId]/FilterParameters.tsx | 3 +- .../reports/[reportId]/ParameterList.tsx | 5 +- .../reports/funnel/FunnelChart.module.css | 8 +-- .../funnel/FunnelParameters.module.css | 7 ++ .../reports/funnel/FunnelParameters.tsx | 43 +++++++++--- .../reports/funnel/FunnelStepAddForm.tsx | 69 +++++++++++-------- src/pages/api/reports/insights.ts | 4 +- 8 files changed, 94 insertions(+), 47 deletions(-) diff --git a/src/app/(main)/reports/[reportId]/FieldFilterEditForm.tsx b/src/app/(main)/reports/[reportId]/FieldFilterEditForm.tsx index 5612c5540..dc10b724d 100644 --- a/src/app/(main)/reports/[reportId]/FieldFilterEditForm.tsx +++ b/src/app/(main)/reports/[reportId]/FieldFilterEditForm.tsx @@ -191,7 +191,7 @@ export default function FieldFilterEditForm({ )} diff --git a/src/app/(main)/reports/[reportId]/FilterParameters.tsx b/src/app/(main)/reports/[reportId]/FilterParameters.tsx index edc0ff4ec..3118a6f4b 100644 --- a/src/app/(main)/reports/[reportId]/FilterParameters.tsx +++ b/src/app/(main)/reports/[reportId]/FilterParameters.tsx @@ -74,7 +74,8 @@ export function FilterParameters() { return ( handleRemove(name)}> void; onRemove?: () => void; }) => { return ( -
+
{children} diff --git a/src/app/(main)/reports/funnel/FunnelChart.module.css b/src/app/(main)/reports/funnel/FunnelChart.module.css index 45d0ea616..81b22c787 100644 --- a/src/app/(main)/reports/funnel/FunnelChart.module.css +++ b/src/app/(main)/reports/funnel/FunnelChart.module.css @@ -37,7 +37,7 @@ .card { display: grid; gap: 20px; - margin-top: 8px; + margin-top: 14px; } .header { @@ -73,7 +73,7 @@ } .item { - font-size: 24px; + font-size: 20px; color: var(--base900); font-weight: 700; } @@ -83,19 +83,19 @@ display: flex; justify-content: space-between; gap: 10px; - font-size: 24px; margin: 10px 0; text-transform: lowercase; } .visitors { color: var(--base900); - font-size: 32px; + font-size: 24px; font-weight: 900; margin-right: 10px; } .percent { + font-size: 20px; font-weight: 700; align-self: flex-end; } diff --git a/src/app/(main)/reports/funnel/FunnelParameters.module.css b/src/app/(main)/reports/funnel/FunnelParameters.module.css index 81ef9216b..219b98076 100644 --- a/src/app/(main)/reports/funnel/FunnelParameters.module.css +++ b/src/app/(main)/reports/funnel/FunnelParameters.module.css @@ -2,8 +2,15 @@ display: flex; align-items: center; gap: 10px; + width: 100%; } .type { color: var(--base700); } + +.value { + display: flex; + align-self: center; + gap: 20px; +} diff --git a/src/app/(main)/reports/funnel/FunnelParameters.tsx b/src/app/(main)/reports/funnel/FunnelParameters.tsx index 248318f13..7b1fb0c81 100644 --- a/src/app/(main)/reports/funnel/FunnelParameters.tsx +++ b/src/app/(main)/reports/funnel/FunnelParameters.tsx @@ -41,6 +41,17 @@ export function FunnelParameters() { updateReport({ parameters: { steps: parameters.steps.concat(step) } }); }; + const handleUpdateStep = ( + close: () => void, + index: number, + step: { type: string; value: string }, + ) => { + const steps = [...parameters.steps]; + steps[index] = step; + updateReport({ parameters: { steps } }); + close(); + }; + const handleRemoveStep = (index: number) => { const steps = [...parameters.steps]; delete steps[index]; @@ -57,7 +68,7 @@ export function FunnelParameters() { - + @@ -79,14 +90,30 @@ export function FunnelParameters() { {steps.map((step: { type: string; value: string }, index: number) => { return ( - handleRemoveStep(index)}> -
-
- {step.type === 'url' ? : } + + handleRemoveStep(index)} + > +
+
+ {step.type === 'url' ? : } +
+
{step.value}
-
{step.value}
-
- + + + {(close: () => void) => ( + + + + )} + + ); })} diff --git a/src/app/(main)/reports/funnel/FunnelStepAddForm.tsx b/src/app/(main)/reports/funnel/FunnelStepAddForm.tsx index 978747c9c..7d77b0c72 100644 --- a/src/app/(main)/reports/funnel/FunnelStepAddForm.tsx +++ b/src/app/(main)/reports/funnel/FunnelStepAddForm.tsx @@ -3,13 +3,18 @@ import { useMessages } from 'components/hooks'; import { Button, FormRow, TextField, Flexbox, Dropdown, Item } from 'react-basics'; import styles from './FunnelStepAddForm.module.css'; -export interface UrlAddFormProps { - defaultValue?: string; - onAdd?: (step: { type: string; value: string }) => void; +export interface FunnelStepAddFormProps { + type?: string; + value?: string; + onChange?: (step: { type: string; value: string }) => void; } -export function FunnelStepAddForm({ defaultValue = '', onAdd }: UrlAddFormProps) { - const [type, setType] = useState('url'); +export function FunnelStepAddForm({ + type: defaultType = 'url', + value: defaultValue = '', + onChange, +}: FunnelStepAddFormProps) { + const [type, setType] = useState(defaultType); const [value, setValue] = useState(defaultValue); const { formatMessage, labels } = useMessages(); const items = [ @@ -19,7 +24,7 @@ export function FunnelStepAddForm({ defaultValue = '', onAdd }: UrlAddFormProps) const isDisabled = !type || !value; const handleSave = () => { - onAdd({ type, value }); + onChange({ type, value }); setValue(''); }; @@ -39,32 +44,36 @@ export function FunnelStepAddForm({ defaultValue = '', onAdd }: UrlAddFormProps) }; return ( - - - setType(value)} - > - {({ value, label }) => { - return {label}; - }} - - + + + + setType(value)} + > + {({ value, label }) => { + return {label}; + }} + + + + + - - + + ); } diff --git a/src/pages/api/reports/insights.ts b/src/pages/api/reports/insights.ts index efd3a6b51..ba4f643e5 100644 --- a/src/pages/api/reports/insights.ts +++ b/src/pages/api/reports/insights.ts @@ -56,8 +56,8 @@ const schema = { }; function convertFilters(filters: any[]) { - return filters.reduce((obj, { name, ...value }) => { - obj[name] = value; + return filters.reduce((obj, filter) => { + obj[filter.name] = filter; return obj; }, {}); From ada332f174e6b0d330550b816373f673faadb122 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Wed, 3 Apr 2024 22:11:57 -0700 Subject: [PATCH 55/55] Fixed filtering on insights report. --- src/lib/params.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/params.ts b/src/lib/params.ts index 801c60b20..ef4568bac 100644 --- a/src/lib/params.ts +++ b/src/lib/params.ts @@ -31,7 +31,7 @@ export function filtersToArray(filters: QueryFilters = {}, options: QueryOptions } if (filter?.name && filter?.value !== undefined) { - return arr.concat(filter); + return arr.concat({ ...filter, column: options?.columns?.[key] ?? FILTER_COLUMNS[key] }); } const { operator, value } = parseParameterValue(filter);