mirror of
https://github.com/umami-software/umami.git
synced 2026-02-24 22:45:36 +01:00
implement OR logic to filters, segments, cohorts
Some checks are pending
Node.js CI / build (push) Waiting to run
Some checks are pending
Node.js CI / build (push) Waiting to run
This commit is contained in:
parent
79c06787cd
commit
cc1e4438d0
64 changed files with 343 additions and 54 deletions
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "مدير",
|
||||
"max": "الحد الأقصى",
|
||||
"maximize": "توسيع",
|
||||
"match": "تطابق",
|
||||
"match-all": "الكل (AND)",
|
||||
"match-any": "أي (OR)",
|
||||
"medium": "وسيط",
|
||||
"member": "عضو",
|
||||
"members": "الأعضاء",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "Кіраўнік",
|
||||
"max": "Максімум",
|
||||
"maximize": "Разгарнуць",
|
||||
"match": "Адпаведнасць",
|
||||
"match-all": "Усе (AND)",
|
||||
"match-any": "Любы (OR)",
|
||||
"medium": "Сярэдні",
|
||||
"member": "Удзельнік",
|
||||
"members": "Удзельнікі",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "Мениджър",
|
||||
"max": "Максимум",
|
||||
"maximize": "Разшири",
|
||||
"match": "Съвпадение",
|
||||
"match-all": "Всички (AND)",
|
||||
"match-any": "Някои (OR)",
|
||||
"medium": "Среден",
|
||||
"member": "Член",
|
||||
"members": "Членове",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "পরিচালক",
|
||||
"max": "সর্বাধিক",
|
||||
"maximize": "বিস্তৃত করুন",
|
||||
"match": "মিলান",
|
||||
"match-all": "সব (AND)",
|
||||
"match-any": "যেকোনো (OR)",
|
||||
"medium": "মাঝারি",
|
||||
"member": "সদস্য",
|
||||
"members": "সদস্যগণ",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "Menadžer",
|
||||
"max": "Maks",
|
||||
"maximize": "Proširi",
|
||||
"match": "Podudaranje",
|
||||
"match-all": "Sve (AND)",
|
||||
"match-any": "Bilo koje (OR)",
|
||||
"medium": "Srednje",
|
||||
"member": "Član",
|
||||
"members": "Članovi",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "Responsable",
|
||||
"max": "Màx",
|
||||
"maximize": "Expandeix",
|
||||
"match": "Coincidència",
|
||||
"match-all": "Tots (AND)",
|
||||
"match-any": "Qualsevol (OR)",
|
||||
"medium": "Mitjà",
|
||||
"member": "Membre",
|
||||
"members": "Membres",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "Správce",
|
||||
"max": "Max",
|
||||
"maximize": "Rozbalit",
|
||||
"match": "Shoda",
|
||||
"match-all": "Vše (AND)",
|
||||
"match-any": "Jakýkoli (OR)",
|
||||
"medium": "Střední",
|
||||
"member": "Člen",
|
||||
"members": "Členové",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "Leder",
|
||||
"max": "Maks",
|
||||
"maximize": "Udvid",
|
||||
"match": "Match",
|
||||
"match-all": "Alle (AND)",
|
||||
"match-any": "Enhver (OR)",
|
||||
"medium": "Medie",
|
||||
"member": "Medlem",
|
||||
"members": "Medlemmer",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "Verwalter",
|
||||
"max": "Max",
|
||||
"maximize": "Uusklappe",
|
||||
"match": "Übereinstimmung",
|
||||
"match-all": "Alle (AND)",
|
||||
"match-any": "Beliebige (OR)",
|
||||
"medium": "Medium",
|
||||
"member": "Mitglied",
|
||||
"members": "Mitglieder",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "Verwaltung",
|
||||
"max": "Max",
|
||||
"maximize": "Erweitern",
|
||||
"match": "Übereinstimmung",
|
||||
"match-all": "Alle (AND)",
|
||||
"match-any": "Beliebige (OR)",
|
||||
"medium": "Medium",
|
||||
"member": "Mitglied",
|
||||
"members": "Mitglieder",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "Διαχειριστής",
|
||||
"max": "Μέγ",
|
||||
"maximize": "Expand",
|
||||
"match": "Αντιστοίχιση",
|
||||
"match-all": "Όλα (AND)",
|
||||
"match-any": "Οποιοδήποτε (OR)",
|
||||
"medium": "Μέσο",
|
||||
"member": "Μέλος",
|
||||
"members": "Μέλη",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "Manager",
|
||||
"max": "Max",
|
||||
"maximize": "Expand",
|
||||
"match": "Match",
|
||||
"match-all": "All (AND)",
|
||||
"match-any": "Any (OR)",
|
||||
"medium": "Medium",
|
||||
"member": "Member",
|
||||
"members": "Members",
|
||||
|
|
|
|||
|
|
@ -170,6 +170,9 @@
|
|||
"manage": "Manage",
|
||||
"manager": "Manager",
|
||||
"max": "Max",
|
||||
"match": "Match",
|
||||
"match-all": "All (AND)",
|
||||
"match-any": "Any (OR)",
|
||||
"maximize": "Maximize",
|
||||
"medium": "Medium",
|
||||
"member": "Member",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "Gerente",
|
||||
"max": "Máximo",
|
||||
"maximize": "Expandir",
|
||||
"match": "Coincidencia",
|
||||
"match-all": "Todo (AND)",
|
||||
"match-any": "Cualquiera (OR)",
|
||||
"medium": "Medio",
|
||||
"member": "Miembro",
|
||||
"members": "Miembros",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "مدیر",
|
||||
"max": "حداکثر",
|
||||
"maximize": "گسترش",
|
||||
"match": "تطابق",
|
||||
"match-all": "همه (AND)",
|
||||
"match-any": "هر (OR)",
|
||||
"medium": "متوسط",
|
||||
"member": "عضو",
|
||||
"members": "اعضا",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "Päällikkö",
|
||||
"max": "Maksimi",
|
||||
"maximize": "Laajenna",
|
||||
"match": "Vastaavuus",
|
||||
"match-all": "Kaikki (AND)",
|
||||
"match-any": "Mikä tahansa (OR)",
|
||||
"medium": "Keskitaso",
|
||||
"member": "Jäsen",
|
||||
"members": "Jäsenet",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "Stjóri",
|
||||
"max": "Mest",
|
||||
"maximize": "Víðka",
|
||||
"match": "Samsvørun",
|
||||
"match-all": "Allt (AND)",
|
||||
"match-any": "Nakað (OR)",
|
||||
"medium": "Miðal",
|
||||
"member": "Limur",
|
||||
"members": "Limir",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "Gestionnaire",
|
||||
"max": "Max",
|
||||
"maximize": "Développer",
|
||||
"match": "Correspondance",
|
||||
"match-all": "Tous (AND)",
|
||||
"match-any": "N'importe lequel (OR)",
|
||||
"medium": "Moyen",
|
||||
"member": "Membre",
|
||||
"members": "Membres",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "Xestor",
|
||||
"max": "Máx",
|
||||
"maximize": "Expandir",
|
||||
"match": "Coincidencia",
|
||||
"match-all": "Todo (AND)",
|
||||
"match-any": "Calquera (OR)",
|
||||
"medium": "Medio",
|
||||
"member": "Membro",
|
||||
"members": "Membros",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "מנהל",
|
||||
"max": "מקסימום",
|
||||
"maximize": "הרחב",
|
||||
"match": "התאמה",
|
||||
"match-all": "הכל (AND)",
|
||||
"match-any": "כלשהו (OR)",
|
||||
"medium": "בינוני",
|
||||
"member": "חבר",
|
||||
"members": "חברים",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "प्रबंधक",
|
||||
"max": "अधिकतम",
|
||||
"maximize": "विस्तार करें",
|
||||
"match": "मेल",
|
||||
"match-all": "सभी (AND)",
|
||||
"match-any": "कोई भी (OR)",
|
||||
"medium": "मध्यम",
|
||||
"member": "सदस्य",
|
||||
"members": "सदस्यगण",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "Upravitelj",
|
||||
"max": "Maksimum",
|
||||
"maximize": "Proširi",
|
||||
"match": "Podudaranje",
|
||||
"match-all": "Sve (AND)",
|
||||
"match-any": "Bilo koje (OR)",
|
||||
"medium": "Srednje",
|
||||
"member": "Član",
|
||||
"members": "Članovi",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "Menedzser",
|
||||
"max": "Maximum",
|
||||
"maximize": "Kibontás",
|
||||
"match": "Egyezés",
|
||||
"match-all": "Összes (AND)",
|
||||
"match-any": "Bármelyik (OR)",
|
||||
"medium": "Közepes",
|
||||
"member": "Tag",
|
||||
"members": "Tagok",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "Manajer",
|
||||
"max": "Maksimum",
|
||||
"maximize": "Perluas",
|
||||
"match": "Cocok",
|
||||
"match-all": "Semua (AND)",
|
||||
"match-any": "Salah satu (OR)",
|
||||
"medium": "Sedang",
|
||||
"member": "Anggota",
|
||||
"members": "Anggota",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "Gestore",
|
||||
"max": "Massimo",
|
||||
"maximize": "Espandi",
|
||||
"match": "Corrispondenza",
|
||||
"match-all": "Tutti (AND)",
|
||||
"match-any": "Qualsiasi (OR)",
|
||||
"medium": "Medio",
|
||||
"member": "Membro",
|
||||
"members": "Membri",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "管理者",
|
||||
"max": "最大",
|
||||
"maximize": "展開",
|
||||
"match": "一致",
|
||||
"match-all": "すべて (AND)",
|
||||
"match-any": "いずれか (OR)",
|
||||
"medium": "メディア",
|
||||
"member": "メンバー",
|
||||
"members": "メンバー",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "អ្នកគ្រប់គ្រង",
|
||||
"max": "អតិបរមា",
|
||||
"maximize": "ពង្រីក",
|
||||
"match": "ត្រូវគ្នា",
|
||||
"match-all": "ទាំងអស់ (AND)",
|
||||
"match-any": "ណាមួយ (OR)",
|
||||
"medium": "មធ្យម",
|
||||
"member": "សមាជិក",
|
||||
"members": "សមាជិក",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "관리자",
|
||||
"max": "최대",
|
||||
"maximize": "확장",
|
||||
"match": "일치",
|
||||
"match-all": "전체 (AND)",
|
||||
"match-any": "일부 (OR)",
|
||||
"medium": "미디엄",
|
||||
"member": "멤버",
|
||||
"members": "멤버",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "Vadovas",
|
||||
"max": "Maksimumas",
|
||||
"maximize": "Išplėsti",
|
||||
"match": "Atitikimas",
|
||||
"match-all": "Visi (AND)",
|
||||
"match-any": "Bet kuris (OR)",
|
||||
"medium": "Vidutinis",
|
||||
"member": "Narys",
|
||||
"members": "Nariai",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "Удирдагч",
|
||||
"max": "Дээд",
|
||||
"maximize": "Өргөтгөх",
|
||||
"match": "Тохирох",
|
||||
"match-all": "Бүгд (AND)",
|
||||
"match-any": "Ямар ч (OR)",
|
||||
"medium": "Дунд",
|
||||
"member": "Гишүүн",
|
||||
"members": "Гишүүд",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "Pengurus",
|
||||
"max": "Maks",
|
||||
"maximize": "Expand",
|
||||
"match": "Padanan",
|
||||
"match-all": "Semua (AND)",
|
||||
"match-any": "Mana-mana (OR)",
|
||||
"medium": "Medium",
|
||||
"member": "Ahli",
|
||||
"members": "Ahli",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "မန်နေဂျာ",
|
||||
"max": "အများဆုံး",
|
||||
"maximize": "Expand",
|
||||
"match": "ကိုက်ညီ",
|
||||
"match-all": "အားလုံး (AND)",
|
||||
"match-any": "တစ်ခုခု (OR)",
|
||||
"medium": "မီဒီယမ်",
|
||||
"member": "အဖွဲ့ဝင်",
|
||||
"members": "အဖွဲ့ဝင်များ",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "Administrator",
|
||||
"max": "Maks",
|
||||
"maximize": "Utvid",
|
||||
"match": "Samsvar",
|
||||
"match-all": "Alle (AND)",
|
||||
"match-any": "Enhver (OR)",
|
||||
"medium": "Medium",
|
||||
"member": "Bruker",
|
||||
"members": "Brukere",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "Beheerder",
|
||||
"max": "Max",
|
||||
"maximize": "Uitvouwen",
|
||||
"match": "Overeenkomst",
|
||||
"match-all": "Alle (AND)",
|
||||
"match-any": "Elk (OR)",
|
||||
"medium": "Medium",
|
||||
"member": "Gebruiker",
|
||||
"members": "Gebruikers",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "Menedżer",
|
||||
"max": "Maks",
|
||||
"maximize": "Rozwiń",
|
||||
"match": "Dopasowanie",
|
||||
"match-all": "Wszystkie (AND)",
|
||||
"match-any": "Dowolny (OR)",
|
||||
"medium": "Medium",
|
||||
"member": "Członek",
|
||||
"members": "Członkowie",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "Gerente",
|
||||
"max": "Máximo",
|
||||
"maximize": "Expandir",
|
||||
"match": "Correspondência",
|
||||
"match-all": "Todos (AND)",
|
||||
"match-any": "Qualquer (OR)",
|
||||
"medium": "Médio",
|
||||
"member": "Membro",
|
||||
"members": "Membros",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "Gestor",
|
||||
"max": "Máximo",
|
||||
"maximize": "Expandir",
|
||||
"match": "Correspondência",
|
||||
"match-all": "Todos (AND)",
|
||||
"match-any": "Qualquer (OR)",
|
||||
"medium": "Médio",
|
||||
"member": "Membro",
|
||||
"members": "Membros",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "Manager",
|
||||
"max": "Max",
|
||||
"maximize": "Extinde",
|
||||
"match": "Potrivire",
|
||||
"match-all": "Toate (AND)",
|
||||
"match-any": "Oricare (OR)",
|
||||
"medium": "Mediu",
|
||||
"member": "Membru",
|
||||
"members": "Membri",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "Менеджер",
|
||||
"max": "Максимум",
|
||||
"maximize": "Развернуть",
|
||||
"match": "Соответствие",
|
||||
"match-all": "Все (AND)",
|
||||
"match-any": "Любой (OR)",
|
||||
"medium": "Средний",
|
||||
"member": "Участник",
|
||||
"members": "Участники",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "කළමනාකරු",
|
||||
"max": "උපරිම",
|
||||
"maximize": "Expand",
|
||||
"match": "ගැළපීම",
|
||||
"match-all": "සියල්ල (AND)",
|
||||
"match-any": "ඕනෑම (OR)",
|
||||
"medium": "මාධ්යය",
|
||||
"member": "සාමාජිකයා",
|
||||
"members": "සාමාජිකයින්",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "Manažér",
|
||||
"max": "Maximum",
|
||||
"maximize": "Rozbaliť",
|
||||
"match": "Zhoda",
|
||||
"match-all": "Všetky (AND)",
|
||||
"match-any": "Akýkoľvek (OR)",
|
||||
"medium": "Stredný",
|
||||
"member": "Člen",
|
||||
"members": "Členovia",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "Upravitelj",
|
||||
"max": "Največ",
|
||||
"maximize": "Povečaj",
|
||||
"match": "Ujemanje",
|
||||
"match-all": "Vse (AND)",
|
||||
"match-any": "Katero koli (OR)",
|
||||
"medium": "Medij",
|
||||
"member": "Član",
|
||||
"members": "Člani",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "Ansvarig",
|
||||
"max": "Max",
|
||||
"maximize": "Expandera",
|
||||
"match": "Matchning",
|
||||
"match-all": "Alla (AND)",
|
||||
"match-any": "Vilken som helst (OR)",
|
||||
"medium": "Medium",
|
||||
"member": "Medlem",
|
||||
"members": "Medlemmar",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "மேலாளர்",
|
||||
"max": "அதிகபட்சம்",
|
||||
"maximize": "Expand",
|
||||
"match": "பொருத்தம்",
|
||||
"match-all": "அனைத்தும் (AND)",
|
||||
"match-any": "எதுவும் (OR)",
|
||||
"medium": "ஊடகம்",
|
||||
"member": "உறுப்பினர்",
|
||||
"members": "உறுப்பினர்கள்",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "ผู้จัดการ",
|
||||
"max": "สูงสุด",
|
||||
"maximize": "Expand",
|
||||
"match": "จับคู่",
|
||||
"match-all": "ทั้งหมด (AND)",
|
||||
"match-any": "ใดๆ (OR)",
|
||||
"medium": "สื่อ",
|
||||
"member": "สมาชิก",
|
||||
"members": "สมาชิก",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "Yönetici",
|
||||
"max": "Maks",
|
||||
"maximize": "Genişlet",
|
||||
"match": "Eşleşme",
|
||||
"match-all": "Tümü (AND)",
|
||||
"match-any": "Herhangi (OR)",
|
||||
"medium": "Orta",
|
||||
"member": "Üye",
|
||||
"members": "Üyeler",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "Менеджер",
|
||||
"max": "Макс.",
|
||||
"maximize": "Розгорнути",
|
||||
"match": "Відповідність",
|
||||
"match-all": "Усі (AND)",
|
||||
"match-any": "Будь-який (OR)",
|
||||
"medium": "Середній",
|
||||
"member": "Учасник",
|
||||
"members": "Учасники",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "منتظم",
|
||||
"max": "زیادہ سے زیادہ",
|
||||
"maximize": "Expand",
|
||||
"match": "مطابقت",
|
||||
"match-all": "سب (AND)",
|
||||
"match-any": "کوئی بھی (OR)",
|
||||
"medium": "میڈیم",
|
||||
"member": "رکن",
|
||||
"members": "اراکین",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "Menejer",
|
||||
"max": "Maksimal",
|
||||
"maximize": "Kattalashtirish",
|
||||
"match": "Moslik",
|
||||
"match-all": "Barchasi (AND)",
|
||||
"match-any": "Istalgan (OR)",
|
||||
"medium": "Vosita",
|
||||
"member": "A'zo",
|
||||
"members": "A'zolar",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "Quản lý",
|
||||
"max": "Tối đa",
|
||||
"maximize": "Phóng to",
|
||||
"match": "Khớp",
|
||||
"match-all": "Tất cả (AND)",
|
||||
"match-any": "Bất kỳ (OR)",
|
||||
"medium": "Phương tiện",
|
||||
"member": "Thành viên",
|
||||
"members": "Các thành viên",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "管理者",
|
||||
"max": "最大",
|
||||
"maximize": "展开",
|
||||
"match": "匹配",
|
||||
"match-all": "全部 (AND)",
|
||||
"match-any": "任意 (OR)",
|
||||
"medium": "中等",
|
||||
"member": "成员",
|
||||
"members": "成员",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@
|
|||
"manager": "管理者",
|
||||
"max": "最大值",
|
||||
"maximize": "Expand",
|
||||
"match": "符合",
|
||||
"match-all": "全部 (AND)",
|
||||
"match-any": "任意 (OR)",
|
||||
"medium": "媒介",
|
||||
"member": "成員",
|
||||
"members": "成員",
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {
|
|||
Loading,
|
||||
TextField,
|
||||
} from '@umami/react-zen';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useMessages, useUpdateQuery, useWebsiteCohortQuery } from '@/components/hooks';
|
||||
import { ActionSelect } from '@/components/input/ActionSelect';
|
||||
import { DateFilter } from '@/components/input/DateFilter';
|
||||
|
|
@ -32,6 +33,11 @@ export function CohortEditForm({
|
|||
}) {
|
||||
const { data } = useWebsiteCohortQuery(websiteId, cohortId);
|
||||
const { t, labels, messages, getErrorMessage } = useMessages();
|
||||
const [currentMatch, setCurrentMatch] = useState<string>('all');
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentMatch((data?.parameters as any)?.match || 'all');
|
||||
}, [data]);
|
||||
|
||||
const { mutateAsync, error, isPending, touch, toast } = useUpdateQuery(
|
||||
`/websites/${websiteId}/segments${cohortId ? `/${cohortId}` : ''}`,
|
||||
|
|
@ -41,14 +47,23 @@ export function CohortEditForm({
|
|||
);
|
||||
|
||||
const handleSubmit = async (formData: any) => {
|
||||
await mutateAsync(formData, {
|
||||
onSuccess: async () => {
|
||||
toast(t(messages.saved));
|
||||
touch('cohorts');
|
||||
onSave?.();
|
||||
onClose?.();
|
||||
await mutateAsync(
|
||||
{
|
||||
...formData,
|
||||
parameters: {
|
||||
...formData.parameters,
|
||||
match: currentMatch !== 'all' ? currentMatch : undefined,
|
||||
},
|
||||
},
|
||||
});
|
||||
{
|
||||
onSuccess: async () => {
|
||||
toast(t(messages.saved));
|
||||
touch('cohorts');
|
||||
onSave?.();
|
||||
onClose?.();
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
if (cohortId && !data) {
|
||||
|
|
@ -105,7 +120,12 @@ export function CohortEditForm({
|
|||
<Column>
|
||||
<Label>{t(labels.filters)}</Label>
|
||||
<FormField name="parameters.filters">
|
||||
<FieldFilters websiteId={websiteId} exclude={['path', 'event']} />
|
||||
<FieldFilters
|
||||
websiteId={websiteId}
|
||||
exclude={['path', 'event']}
|
||||
match={currentMatch}
|
||||
onMatchChange={setCurrentMatch}
|
||||
/>
|
||||
</FormField>
|
||||
</Column>
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import {
|
|||
Loading,
|
||||
TextField,
|
||||
} from '@umami/react-zen';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useMessages, useUpdateQuery, useWebsiteSegmentQuery } from '@/components/hooks';
|
||||
import { FieldFilters } from '@/components/input/FieldFilters';
|
||||
|
||||
|
|
@ -28,6 +29,11 @@ export function SegmentEditForm({
|
|||
}) {
|
||||
const { data } = useWebsiteSegmentQuery(websiteId, segmentId);
|
||||
const { t, labels, messages, getErrorMessage } = useMessages();
|
||||
const [currentMatch, setCurrentMatch] = useState<string>('all');
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentMatch((data?.parameters as any)?.match || 'all');
|
||||
}, [data]);
|
||||
|
||||
const { mutateAsync, error, isPending, touch, toast } = useUpdateQuery(
|
||||
`/websites/${websiteId}/segments${segmentId ? `/${segmentId}` : ''}`,
|
||||
|
|
@ -37,14 +43,23 @@ export function SegmentEditForm({
|
|||
);
|
||||
|
||||
const handleSubmit = async (formData: any) => {
|
||||
await mutateAsync(formData, {
|
||||
onSuccess: async () => {
|
||||
toast(t(messages.saved));
|
||||
touch('segments');
|
||||
onSave?.();
|
||||
onClose?.();
|
||||
await mutateAsync(
|
||||
{
|
||||
...formData,
|
||||
parameters: {
|
||||
...formData.parameters,
|
||||
match: currentMatch !== 'all' ? currentMatch : undefined,
|
||||
},
|
||||
},
|
||||
});
|
||||
{
|
||||
onSuccess: async () => {
|
||||
toast(t(messages.saved));
|
||||
touch('segments');
|
||||
onSave?.();
|
||||
onClose?.();
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
if (segmentId && !data) {
|
||||
|
|
@ -64,7 +79,11 @@ export function SegmentEditForm({
|
|||
<>
|
||||
<Label>{t(labels.filters)}</Label>
|
||||
<FormField name="parameters.filters" rules={{ required: t(labels.required) }}>
|
||||
<FieldFilters websiteId={websiteId} />
|
||||
<FieldFilters
|
||||
websiteId={websiteId}
|
||||
match={currentMatch}
|
||||
onMatchChange={setCurrentMatch}
|
||||
/>
|
||||
</FormField>
|
||||
</>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ export function useFilterParameters() {
|
|||
segment: query.segment,
|
||||
cohort: query.cohort,
|
||||
excludeBounce: query.excludeBounce,
|
||||
match: query.match,
|
||||
page: query.page,
|
||||
pageSize: query.pageSize,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import {
|
|||
Column,
|
||||
Grid,
|
||||
Icon,
|
||||
Label,
|
||||
List,
|
||||
ListItem,
|
||||
ListSection,
|
||||
|
|
@ -12,6 +13,7 @@ import {
|
|||
MenuTrigger,
|
||||
Popover,
|
||||
Row,
|
||||
Select,
|
||||
} from '@umami/react-zen';
|
||||
import { endOfDay, subMonths } from 'date-fns';
|
||||
import type { Key } from 'react';
|
||||
|
|
@ -24,11 +26,20 @@ export interface FieldFiltersProps {
|
|||
websiteId: string;
|
||||
value?: { name: string; operator: string; value: string }[];
|
||||
exclude?: string[];
|
||||
match?: string;
|
||||
onChange?: (data: any) => void;
|
||||
onMatchChange?: (match: string) => void;
|
||||
}
|
||||
|
||||
export function FieldFilters({ websiteId, value, exclude = [], onChange }: FieldFiltersProps) {
|
||||
const { t, messages } = useMessages();
|
||||
export function FieldFilters({
|
||||
websiteId,
|
||||
value,
|
||||
exclude = [],
|
||||
match = 'all',
|
||||
onChange,
|
||||
onMatchChange,
|
||||
}: FieldFiltersProps) {
|
||||
const { t, labels, messages } = useMessages();
|
||||
const { fields, groupLabels } = useFields();
|
||||
const startDate = subMonths(endOfDay(new Date()), 6);
|
||||
const endDate = endOfDay(new Date());
|
||||
|
|
@ -126,6 +137,17 @@ export function FieldFilters({ websiteId, value, exclude = [], onChange }: Field
|
|||
</List>
|
||||
</Column>
|
||||
<Column overflow="auto" gapY="4" style={{ contain: 'layout' }}>
|
||||
{onMatchChange && (
|
||||
<Row alignItems="center" gap>
|
||||
<Column gap="1">
|
||||
<Label>{t(labels.match)}</Label>
|
||||
<Select value={match} onChange={onMatchChange} style={{ width: 150 }}>
|
||||
<ListItem id="all">{t(labels.matchAll)}</ListItem>
|
||||
<ListItem id="any">{t(labels.matchAny)}</ListItem>
|
||||
</Select>
|
||||
</Column>
|
||||
</Row>
|
||||
)}
|
||||
{value.map((filter, index) => {
|
||||
return (
|
||||
<FilterRecord
|
||||
|
|
|
|||
|
|
@ -6,13 +6,18 @@ import { SegmentFilters } from '@/components/input/SegmentFilters';
|
|||
|
||||
export interface FilterEditFormProps {
|
||||
websiteId?: string;
|
||||
onChange?: (params: { filters: any[]; segment?: string; cohort?: string }) => void;
|
||||
onChange?: (params: {
|
||||
filters: any[];
|
||||
segment?: string;
|
||||
cohort?: string;
|
||||
match?: string;
|
||||
}) => void;
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
export function FilterEditForm({ websiteId, onChange, onClose }: FilterEditFormProps) {
|
||||
const {
|
||||
query: { segment, cohort },
|
||||
query: { segment, cohort, match },
|
||||
pathname,
|
||||
} = useNavigation();
|
||||
const { filters } = useFilters();
|
||||
|
|
@ -20,6 +25,7 @@ export function FilterEditForm({ websiteId, onChange, onClose }: FilterEditFormP
|
|||
const [currentFilters, setCurrentFilters] = useState(filters);
|
||||
const [currentSegment, setCurrentSegment] = useState(segment);
|
||||
const [currentCohort, setCurrentCohort] = useState(cohort);
|
||||
const [currentMatch, setCurrentMatch] = useState<string>(match || 'all');
|
||||
const { isMobile } = useMobile();
|
||||
const excludeFilters = pathname.includes('/pixels') || pathname.includes('/links');
|
||||
const excludeEvent = !pathname.endsWith('/events');
|
||||
|
|
@ -28,6 +34,7 @@ export function FilterEditForm({ websiteId, onChange, onClose }: FilterEditFormP
|
|||
setCurrentFilters([]);
|
||||
setCurrentSegment(undefined);
|
||||
setCurrentCohort(undefined);
|
||||
setCurrentMatch('all');
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
|
|
@ -35,6 +42,7 @@ export function FilterEditForm({ websiteId, onChange, onClose }: FilterEditFormP
|
|||
filters: currentFilters.filter(f => f.value),
|
||||
segment: currentSegment,
|
||||
cohort: currentCohort,
|
||||
match: currentMatch !== 'all' ? currentMatch : undefined,
|
||||
});
|
||||
onClose?.();
|
||||
};
|
||||
|
|
@ -61,7 +69,9 @@ export function FilterEditForm({ websiteId, onChange, onClose }: FilterEditFormP
|
|||
<FieldFilters
|
||||
websiteId={websiteId}
|
||||
value={currentFilters}
|
||||
match={currentMatch}
|
||||
onChange={setCurrentFilters}
|
||||
onMatchChange={setCurrentMatch}
|
||||
exclude={
|
||||
excludeFilters
|
||||
? ['path', 'title', 'hostname', 'distinctId', 'tag', 'event']
|
||||
|
|
@ -92,7 +102,13 @@ export function FilterEditForm({ websiteId, onChange, onClose }: FilterEditFormP
|
|||
<Button onPress={handleReset}>{t(labels.reset)}</Button>
|
||||
<Row alignItems="center" justifyContent="flex-end" gridColumn="span 2" gap>
|
||||
<Button onPress={onClose}>{t(labels.cancel)}</Button>
|
||||
<Button variant="primary" onPress={handleSave}>
|
||||
<Button
|
||||
variant="primary"
|
||||
onPress={handleSave}
|
||||
isDisabled={
|
||||
currentFilters.filter(f => f.value).length === 0 && !currentSegment && !currentCohort
|
||||
}
|
||||
>
|
||||
{t(labels.apply)}
|
||||
</Button>
|
||||
</Row>
|
||||
|
|
|
|||
|
|
@ -19,13 +19,14 @@ export function WebsiteFilterButton({
|
|||
const isOverview =
|
||||
/^\/teams\/[^/]+\/websites\/[^/]+$/.test(pathname) || /^\/share\/[^/]+$/.test(pathname);
|
||||
|
||||
const handleChange = ({ filters, segment, cohort }: any) => {
|
||||
const handleChange = ({ filters, segment, cohort, match }: any) => {
|
||||
const params = filtersArrayToObject(filters);
|
||||
|
||||
const url = updateParams({
|
||||
...params,
|
||||
segment,
|
||||
cohort,
|
||||
match,
|
||||
excludeBounce: excludeBounce ? 'true' : undefined,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -305,6 +305,9 @@ export const labels: Record<string, string> = {
|
|||
other: 'label.other',
|
||||
boards: 'label.boards',
|
||||
apply: 'label.apply',
|
||||
match: 'label.match',
|
||||
matchAll: 'label.match-all',
|
||||
matchAny: 'label.match-any',
|
||||
link: 'label.link',
|
||||
links: 'label.links',
|
||||
pixel: 'label.pixel',
|
||||
|
|
|
|||
|
|
@ -99,32 +99,44 @@ function mapFilter(
|
|||
}
|
||||
|
||||
function getFilterQuery(filters: Record<string, any>, options: QueryOptions = {}) {
|
||||
const query = filtersObjectToArray(filters, options).reduce(
|
||||
(arr, { name, column, operator, paramName }) => {
|
||||
const isCohort = options?.isCohort;
|
||||
const { isCohort, cohortMatch, cohortActionName } = options;
|
||||
const isOr = isCohort ? cohortMatch === 'any' : filters.match === 'any';
|
||||
const orClauses: string[] = [];
|
||||
const andClauses: string[] = [];
|
||||
|
||||
if (isCohort) {
|
||||
column = FILTER_COLUMNS[name.slice('cohort_'.length)];
|
||||
filtersObjectToArray(filters, options).forEach(({ name, column, operator, paramName }) => {
|
||||
if (isCohort) {
|
||||
column = FILTER_COLUMNS[name.slice('cohort_'.length)];
|
||||
}
|
||||
|
||||
if (column) {
|
||||
const isAlwaysAnd = name === 'eventType' || (isCohort && name === cohortActionName);
|
||||
|
||||
if (isAlwaysAnd) {
|
||||
andClauses.push(
|
||||
`and ${mapFilter(column, operator, name, name === 'eventType' ? 'UInt32' : 'String', paramName)}`,
|
||||
);
|
||||
} else if (isOr) {
|
||||
orClauses.push(mapFilter(column, operator, name, 'String', paramName));
|
||||
} else {
|
||||
andClauses.push(`and ${mapFilter(column, operator, name, 'String', paramName)}`);
|
||||
}
|
||||
|
||||
if (column) {
|
||||
if (name === 'eventType') {
|
||||
arr.push(`and ${mapFilter(column, operator, name, 'UInt32', paramName)}`);
|
||||
} else {
|
||||
arr.push(`and ${mapFilter(column, operator, name, 'String', paramName)}`);
|
||||
}
|
||||
|
||||
if (name === 'referrer') {
|
||||
arr.push(`and referrer_domain != hostname`);
|
||||
}
|
||||
if (name === 'referrer') {
|
||||
andClauses.push(`and referrer_domain != hostname`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return arr;
|
||||
},
|
||||
[],
|
||||
);
|
||||
const parts: string[] = [];
|
||||
|
||||
return query.join('\n');
|
||||
if (orClauses.length > 0) {
|
||||
parts.push(`and (\n ${orClauses.join('\n or ')}\n)`);
|
||||
}
|
||||
|
||||
parts.push(...andClauses);
|
||||
|
||||
return parts.join('\n');
|
||||
}
|
||||
|
||||
function getCohortQuery(filters: Record<string, any>) {
|
||||
|
|
@ -132,7 +144,10 @@ function getCohortQuery(filters: Record<string, any>) {
|
|||
return '';
|
||||
}
|
||||
|
||||
const filterQuery = getFilterQuery(filters, { isCohort: true });
|
||||
const cohortMatch = filters.cohort_match;
|
||||
const cohortActionName = filters.cohort_actionName;
|
||||
|
||||
const filterQuery = getFilterQuery(filters, { isCohort: true, cohortMatch, cohortActionName });
|
||||
|
||||
return `join (
|
||||
select distinct session_id
|
||||
|
|
@ -267,7 +282,7 @@ async function rawQuery<T = unknown>(
|
|||
output_format_json_quote_64bit_integers: 0,
|
||||
},
|
||||
});
|
||||
|
||||
console.log(query, params);
|
||||
return (await resultSet.json()) as T;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -106,30 +106,47 @@ function mapFilter(
|
|||
}
|
||||
|
||||
function getFilterQuery(filters: Record<string, any>, options: QueryOptions = {}): string {
|
||||
const query = filtersObjectToArray(filters, options).reduce(
|
||||
(arr, { name, column, operator, prefix = '', paramName }) => {
|
||||
const isCohort = options?.isCohort;
|
||||
const { isCohort, cohortMatch, cohortActionName } = options;
|
||||
const isOr = isCohort ? cohortMatch === 'any' : filters.match === 'any';
|
||||
const orClauses: string[] = [];
|
||||
const andClauses: string[] = [];
|
||||
|
||||
filtersObjectToArray(filters, options).forEach(
|
||||
({ name, column, operator, prefix = '', paramName }) => {
|
||||
if (isCohort) {
|
||||
column = FILTER_COLUMNS[name.slice('cohort_'.length)];
|
||||
}
|
||||
|
||||
if (column) {
|
||||
arr.push(`and ${mapFilter(`${prefix}${column}`, operator, name, '', paramName)}`);
|
||||
const clause = mapFilter(`${prefix}${column}`, operator, name, '', paramName);
|
||||
const isAlwaysAnd = name === 'eventType' || (isCohort && name === cohortActionName);
|
||||
|
||||
if (isAlwaysAnd) {
|
||||
andClauses.push(`and ${clause}`);
|
||||
} else if (isOr) {
|
||||
orClauses.push(clause);
|
||||
} else {
|
||||
andClauses.push(`and ${clause}`);
|
||||
}
|
||||
|
||||
if (name === 'referrer') {
|
||||
arr.push(
|
||||
andClauses.push(
|
||||
`and (website_event.referrer_domain != regexp_replace(website_event.hostname, '^www.', '') or website_event.referrer_domain is null)`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return arr;
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return query.join('\n');
|
||||
const parts: string[] = [];
|
||||
|
||||
if (orClauses.length > 0) {
|
||||
parts.push(`and (\n ${orClauses.join('\n or ')}\n)`);
|
||||
}
|
||||
|
||||
parts.push(...andClauses);
|
||||
|
||||
return parts.join('\n');
|
||||
}
|
||||
|
||||
function getCohortQuery(filters: QueryFilters = {}) {
|
||||
|
|
@ -137,7 +154,10 @@ function getCohortQuery(filters: QueryFilters = {}) {
|
|||
return '';
|
||||
}
|
||||
|
||||
const filterQuery = getFilterQuery(filters, { isCohort: true });
|
||||
const cohortMatch = (filters as any).cohort_match;
|
||||
const cohortActionName = (filters as any).cohort_actionName;
|
||||
|
||||
const filterQuery = getFilterQuery(filters, { isCohort: true, cohortMatch, cohortActionName });
|
||||
|
||||
return `join
|
||||
(select distinct website_event.session_id
|
||||
|
|
|
|||
|
|
@ -115,6 +115,8 @@ export async function getQueryFilters(
|
|||
const dateRange = getRequestDateRange(params);
|
||||
const filters = getRequestFilters(params);
|
||||
|
||||
let match = params?.match;
|
||||
|
||||
if (websiteId) {
|
||||
await setWebsiteDate(websiteId, dateRange);
|
||||
|
||||
|
|
@ -123,6 +125,10 @@ export async function getQueryFilters(
|
|||
?.parameters as Record<string, any>;
|
||||
|
||||
Object.assign(filters, filtersArrayToObject(segmentParams.filters));
|
||||
|
||||
if (segmentParams.match) {
|
||||
match = segmentParams.match;
|
||||
}
|
||||
}
|
||||
|
||||
if (params.cohort) {
|
||||
|
|
@ -146,6 +152,10 @@ export async function getQueryFilters(
|
|||
...filtersArrayToObject(cohortFilters),
|
||||
cohort_startDate: startDate,
|
||||
cohort_endDate: endDate,
|
||||
...(cohortParams.match && {
|
||||
cohort_match: cohortParams.match,
|
||||
cohort_actionName: `cohort_${cohortParams.action.type}`,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -157,6 +167,7 @@ export async function getQueryFilters(
|
|||
return {
|
||||
...dateRange,
|
||||
...filters,
|
||||
match,
|
||||
page: params?.page,
|
||||
pageSize: params?.pageSize ? params?.pageSize || DEFAULT_PAGE_SIZE : undefined,
|
||||
orderBy: params?.orderBy,
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ export const filterParams = {
|
|||
cohort: z.uuid().optional(),
|
||||
eventType: z.coerce.number().int().positive().optional(),
|
||||
excludeBounce: z.string().optional(),
|
||||
match: z.enum(['all', 'any']).optional(),
|
||||
};
|
||||
|
||||
export const searchParams = {
|
||||
|
|
@ -295,6 +296,7 @@ export const segmentParamSchema = z.object({
|
|||
}),
|
||||
)
|
||||
.optional(),
|
||||
match: z.enum(['all', 'any']).optional(),
|
||||
dateRange: z.string().optional(),
|
||||
action: z
|
||||
.object({
|
||||
|
|
|
|||
|
|
@ -53,6 +53,8 @@ export interface QueryOptions {
|
|||
limit?: number;
|
||||
prefix?: string;
|
||||
isCohort?: boolean;
|
||||
cohortMatch?: string;
|
||||
cohortActionName?: string;
|
||||
}
|
||||
|
||||
export interface QueryFilters
|
||||
|
|
@ -93,6 +95,7 @@ export interface FilterParams {
|
|||
cohort?: string;
|
||||
compare?: string;
|
||||
excludeBounce?: boolean;
|
||||
match?: 'all' | 'any';
|
||||
}
|
||||
|
||||
export interface SortParams {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue