Merge branch 'dev' of https://github.com/umami-software/umami into dev
This commit is contained in:
commit
fe19a4fbb4
11 files changed
+903
-8
No files matched your search
@@ -0,0 +1,184 @@
|
||||
{
|
||||
"ab": "Abxaz dili",
|
||||
"aa": "Afar dili",
|
||||
"af": "Afrikaans dili",
|
||||
"ak": "Akan dili",
|
||||
"sq": "Alban dili",
|
||||
"am": "Amhar dili",
|
||||
"ar": "Ərəb dili",
|
||||
"an": "Araqon dili",
|
||||
"hy": "Erməni dili",
|
||||
"as": "Assam dili",
|
||||
"av": "Avar dili",
|
||||
"ae": "Avesta dili",
|
||||
"ay": "Aymara dili",
|
||||
"az": "Azərbaycan dili",
|
||||
"bm": "Bambara dili",
|
||||
"ba": "Başqırd dili",
|
||||
"eu": "Bask dili",
|
||||
"be": "Belarus dili",
|
||||
"bn": "Benqal dili",
|
||||
"bh": "Bihari dili",
|
||||
"bi": "Bislama dili",
|
||||
"bs": "Bosniya dili",
|
||||
"br": "Breton dili",
|
||||
"bg": "Bolqar dili",
|
||||
"my": "Birma dili",
|
||||
"ca": "Katalan dili",
|
||||
"ch": "Çamorro dili",
|
||||
"ce": "Çeçen dili",
|
||||
"ny": "Çiçeva dili",
|
||||
"zh": "Çin dili",
|
||||
"cv": "Çuvaş dili",
|
||||
"kw": "Korn dili",
|
||||
"co": "Korsika dili",
|
||||
"cr": "Kri dili",
|
||||
"hr": "Xorvat dili",
|
||||
"cs": "Çex dili",
|
||||
"da": "Danimarka dili",
|
||||
"dv": "Divehi dili",
|
||||
"nl": "Holland dili",
|
||||
"en": "İngilis dili",
|
||||
"eo": "Esperanto dili",
|
||||
"et": "Eston dili",
|
||||
"ee": "Eve dili",
|
||||
"fo": "Farer dili",
|
||||
"fj": "Fici dili",
|
||||
"fi": "Fin dili",
|
||||
"fr": "Fransız dili",
|
||||
"ff": "Fula dili",
|
||||
"gl": "Qalisiya dili",
|
||||
"ka": "Gürcü dili",
|
||||
"de": "Alman dili",
|
||||
"el": "Yunan dili (müasir)",
|
||||
"gn": "Qvarani dili",
|
||||
"gu": "Qucarat dili",
|
||||
"ht": "Haiti dili",
|
||||
"ha": "Hausa dili",
|
||||
"he": "İvrit dili (müasir)",
|
||||
"hz": "Herero dili",
|
||||
"hi": "Hind dili",
|
||||
"ho": "Hiri Motu dili",
|
||||
"hu": "Macar dili",
|
||||
"ia": "İnterlingua dili",
|
||||
"id": "İndoneziya dili",
|
||||
"ie": "İnterlingue dili",
|
||||
"ga": "İrland dili",
|
||||
"ig": "İqbo dili",
|
||||
"ik": "İnupiak dili",
|
||||
"io": "İdo dili",
|
||||
"is": "İsland dili",
|
||||
"it": "İtalyan dili",
|
||||
"iu": "İnuktitut dili",
|
||||
"ja": "Yapon dili",
|
||||
"jv": "Yava dili",
|
||||
"kl": "Qrenland dili",
|
||||
"kn": "Kannada dili",
|
||||
"kr": "Kanuri dili",
|
||||
"ks": "Kəşmir dili",
|
||||
"kk": "Qazax dili",
|
||||
"km": "Kxmer dili",
|
||||
"ki": "Kikuyu dili",
|
||||
"rw": "Kinyaruanda dili",
|
||||
"ky": "Qırğız dili",
|
||||
"kv": "Komi dili",
|
||||
"kg": "Konqo dili",
|
||||
"ko": "Koreya dili",
|
||||
"ku": "Kürd dili",
|
||||
"kj": "Kvanyama dili",
|
||||
"la": "Latın dili",
|
||||
"lb": "Lüksemburq dili",
|
||||
"lg": "Luqanda dili",
|
||||
"li": "Limburq dili",
|
||||
"ln": "Lingala dili",
|
||||
"lo": "Lao dili",
|
||||
"lt": "Litva dili",
|
||||
"lu": "Luba-Katanqa dili",
|
||||
"lv": "Latış dili",
|
||||
"gv": "Men dili",
|
||||
"mk": "Makedon dili",
|
||||
"mg": "Malaqasiya dili",
|
||||
"ms": "Malay dili",
|
||||
"ml": "Malayalam dili",
|
||||
"mt": "Malta dili",
|
||||
"mi": "Maori dili",
|
||||
"mr": "Marathi dili",
|
||||
"mh": "Marşal dili",
|
||||
"mn": "Monqol dili",
|
||||
"na": "Nauru dili",
|
||||
"nv": "Navaho dili",
|
||||
"nb": "Norveç bokmal dili",
|
||||
"nd": "Şimal ndebele dili",
|
||||
"ne": "Nepal dili",
|
||||
"ng": "Ndonqa dili",
|
||||
"nn": "Norveç nynorsk dili",
|
||||
"no": "Norveç dili",
|
||||
"ii": "Nuosu dili",
|
||||
"nr": "Cənub ndebele dili",
|
||||
"oc": "Oksitan dili",
|
||||
"oj": "Ocibve dili",
|
||||
"cu": "Qədim kilsə slavyan dili",
|
||||
"om": "Oromo dili",
|
||||
"or": "Oriya dili",
|
||||
"os": "Osetin dili",
|
||||
"pa": "Pəncab dili",
|
||||
"pi": "Pali dili",
|
||||
"fa": "Fars dili",
|
||||
"pl": "Polyak dili",
|
||||
"ps": "Puştu dili",
|
||||
"pt": "Portuqal dili",
|
||||
"qu": "Keçua dili",
|
||||
"rm": "Romanş dili",
|
||||
"rn": "Kirundi dili",
|
||||
"ro": "Rumın dili",
|
||||
"ru": "Rus dili",
|
||||
"sa": "Sanskrit dili",
|
||||
"sc": "Sardiniya dili",
|
||||
"sd": "Sindhi dili",
|
||||
"se": "Şimal sami dili",
|
||||
"sm": "Samoa dili",
|
||||
"sg": "Sanqo dili",
|
||||
"sr": "Serb dili",
|
||||
"gd": "Şotland qael dili",
|
||||
"sn": "Şona dili",
|
||||
"si": "Sinhal dili",
|
||||
"sk": "Slovak dili",
|
||||
"sl": "Sloven dili",
|
||||
"so": "Somali dili",
|
||||
"st": "Cənub soto dili",
|
||||
"es": "İspan dili",
|
||||
"su": "Sunda dili",
|
||||
"sw": "Suahili dili",
|
||||
"ss": "Svati dili",
|
||||
"sv": "İsveç dili",
|
||||
"ta": "Tamil dili",
|
||||
"te": "Teluqu dili",
|
||||
"tg": "Tacik dili",
|
||||
"th": "Tay dili",
|
||||
"ti": "Tiqrinya dili",
|
||||
"bo": "Tibet dili",
|
||||
"tk": "Türkmən dili",
|
||||
"tl": "Taqaloq dili",
|
||||
"tn": "Tsvana dili",
|
||||
"to": "Tonqa dili",
|
||||
"tr": "Türk dili",
|
||||
"ts": "Tsonqa dili",
|
||||
"tt": "Tatar dili",
|
||||
"tw": "Tvi dili",
|
||||
"ty": "Tahiti dili",
|
||||
"ug": "Uyğur dili",
|
||||
"uk": "Ukrayna dili",
|
||||
"ur": "Urdu dili",
|
||||
"uz": "Özbək dili",
|
||||
"ve": "Venda dili",
|
||||
"vi": "Vyetnam dili",
|
||||
"vo": "Volapük dili",
|
||||
"wa": "Valon dili",
|
||||
"cy": "Uels dili",
|
||||
"wo": "Volof dili",
|
||||
"fy": "Qərbi friz dili",
|
||||
"xh": "Xosa dili",
|
||||
"yi": "İdiş dili",
|
||||
"yo": "Yoruba dili",
|
||||
"za": "Çjuan dili"
|
||||
}
|
||||
@@ -0,0 +1,535 @@
|
||||
{
|
||||
"label": {
|
||||
"access-code": "Giriş kodu",
|
||||
"account": "Hesab",
|
||||
"action": "Əməliyyat",
|
||||
"actions": "Əməliyyatlar",
|
||||
"activity": "Fəaliyyət",
|
||||
"add": "Əlavə et",
|
||||
"add-board": "Lövhə əlavə et",
|
||||
"add-description": "Təsvir əlavə et",
|
||||
"add-link": "Keçid əlavə et",
|
||||
"add-member": "Üzv əlavə et",
|
||||
"add-pixel": "Piksel əlavə et",
|
||||
"add-step": "Addım əlavə et",
|
||||
"add-website": "Veb sayt əlavə et",
|
||||
"admin": "Admin",
|
||||
"affiliate": "Tərəfdaş",
|
||||
"after": "Sonra",
|
||||
"all": "Hamısı",
|
||||
"all-time": "Bütün vaxt",
|
||||
"analysis": "Təhlil",
|
||||
"analytics": "Analitika",
|
||||
"application": "Tətbiq",
|
||||
"apply": "Tətbiq et",
|
||||
"attribution": "Atribusiya",
|
||||
"attribution-description": "İstifadəçilərin marketinqinizlə necə əlaqə qurduğunu və nəyin çevrilmələrə səbəb olduğunu görün.",
|
||||
"audience": "Auditoriya",
|
||||
"average": "Orta",
|
||||
"back": "Geri",
|
||||
"before": "Əvvəl",
|
||||
"behavior": "Davranış",
|
||||
"block-selector": "Blok seçici",
|
||||
"boards": "Lövhələr",
|
||||
"board-type": "Lövhə növü",
|
||||
"bounce-rate": "Səkmə dərəcəsi",
|
||||
"breakdown": "Bölgü",
|
||||
"browser": "Brauzer",
|
||||
"browsers": "Brauzerlər",
|
||||
"campaign": "Kampaniya",
|
||||
"campaigns": "Kampaniyalar",
|
||||
"cancel": "Ləğv et",
|
||||
"close": "Bağla",
|
||||
"change-password": "Parolu dəyiş",
|
||||
"channel": "Kanal",
|
||||
"channels": "Kanallar",
|
||||
"chart": "Qrafik",
|
||||
"cities": "Şəhərlər",
|
||||
"city": "Şəhər",
|
||||
"clear-all": "Hamısını təmizlə",
|
||||
"cls": "CLS",
|
||||
"cohort": "Koqorta",
|
||||
"cohorts": "Koqortalar",
|
||||
"compare": "Müqayisə et",
|
||||
"compare-dates": "Tarixləri müqayisə et",
|
||||
"confirm": "Təsdiqlə",
|
||||
"confirm-password": "Parolu təsdiqlə",
|
||||
"contains": "Ehtiva edir",
|
||||
"regex-match": "Regex-ə uyğun gəlir",
|
||||
"regex-not-match": "Regex-ə uyğun gəlmir",
|
||||
"content": "Məzmun",
|
||||
"continue": "Davam et",
|
||||
"conversion": "Çevrilmə",
|
||||
"conversion-rate": "Çevrilmə dərəcəsi",
|
||||
"conversion-step": "Çevrilmə addımı",
|
||||
"count": "Say",
|
||||
"countries": "Ölkələr",
|
||||
"country": "Ölkə",
|
||||
"create": "Yarat",
|
||||
"create-report": "Hesabat yarat",
|
||||
"create-team": "Komanda yarat",
|
||||
"create-user": "İstifadəçi yarat",
|
||||
"created": "Yaradılıb",
|
||||
"created-by": "Yaradan",
|
||||
"criteria": "Meyar",
|
||||
"cumulative": "Kumulyativ",
|
||||
"currency": "Valyuta",
|
||||
"current": "Cari",
|
||||
"current-password": "Cari parol",
|
||||
"custom-range": "Fərdi aralıq",
|
||||
"dashboard": "İdarə paneli",
|
||||
"data": "Məlumat",
|
||||
"date": "Tarix",
|
||||
"date-range": "Tarix aralığı",
|
||||
"day": "Gün",
|
||||
"default-date-range": "Standart tarix aralığı",
|
||||
"delete": "Sil",
|
||||
"delete-report": "Hesabatı sil",
|
||||
"delete-team": "Komandanı sil",
|
||||
"delete-user": "İstifadəçini sil",
|
||||
"delete-website": "Veb saytı sil",
|
||||
"description": "Təsvir",
|
||||
"desktop": "Masaüstü",
|
||||
"destination-url": "Təyinat URL-i",
|
||||
"details": "Detallar",
|
||||
"device": "Cihaz",
|
||||
"devices": "Cihazlar",
|
||||
"direct": "Birbaşa",
|
||||
"dismiss": "İmtina et",
|
||||
"distinct-id": "Unikal ID",
|
||||
"documentation": "Sənədləşdirmə",
|
||||
"does-not-contain": "Ehtiva etmir",
|
||||
"does-not-include": "Daxil deyil",
|
||||
"doest-not-exist": "Mövcud deyil",
|
||||
"domain": "Domen",
|
||||
"download": "Endir",
|
||||
"dropoff": "Tərk etmə",
|
||||
"duration": "Müddət",
|
||||
"min-duration-seconds": "Minimum müddət (saniyə)",
|
||||
"edit": "Redaktə et",
|
||||
"design": "Dizayn",
|
||||
"edit-member": "Üzvü redaktə et",
|
||||
"email": "E-poçt",
|
||||
"enable-share-url": "Paylaşım URL-ini aktivləşdir",
|
||||
"end-step": "Son addım",
|
||||
"entry": "Giriş səhifəsi",
|
||||
"environment": "Mühit",
|
||||
"event": "Hadisə",
|
||||
"event-data": "Hadisə məlumatları",
|
||||
"event-properties": "Hadisə xüsusiyyətləri",
|
||||
"event-name": "Hadisə adı",
|
||||
"events": "Hadisələr",
|
||||
"exclude-bounce": "Səkməni çıxart",
|
||||
"exists": "Mövcuddur",
|
||||
"exit": "Çıxış səhifəsi",
|
||||
"false": "Yalan",
|
||||
"fcp": "FCP",
|
||||
"field": "Sahə",
|
||||
"fields": "Sahələr",
|
||||
"filter": "Süzgəc",
|
||||
"filter-combined": "Birləşdirilmiş",
|
||||
"filter-raw": "Xam",
|
||||
"filters": "Süzgəclər",
|
||||
"filters-enabled": "Süzgəclər aktivdir",
|
||||
"property-filter": "Xüsusiyyət süzgəci",
|
||||
"activity-by-property": "Xüsusiyyətə görə fəaliyyət",
|
||||
"add-note": "Qeyd əlavə et",
|
||||
"first-click": "İlk klik",
|
||||
"first-seen": "İlk görülmə",
|
||||
"funnel": "Huni",
|
||||
"funnel-description": "İstifadəçilərin çevrilmə və tərk etmə dərəcəsini anlayın.",
|
||||
"funnels": "Hunilər",
|
||||
"goal": "Hədəf",
|
||||
"goals": "Hədəflər",
|
||||
"good": "Yaxşı",
|
||||
"goals-description": "Səhifə baxışları və hadisələr üçün hədəflərinizi izləyin.",
|
||||
"greater-than": "Böyükdür",
|
||||
"greater-than-equals": "Böyük və ya bərabərdir",
|
||||
"grouped": "Qruplaşdırılıb",
|
||||
"growth": "Artım",
|
||||
"heatmap": "İstilik xəritəsi",
|
||||
"heatmap-description": "Ziyarətçilərin səhifələrinizdə harada kliklədiyini və nə qədər aşağı sürüşdürdüyünü vizuallaşdırın.",
|
||||
"heatmaps": "İstilik xəritələri",
|
||||
"hostname": "Host adı",
|
||||
"hour": "Saat",
|
||||
"includes": "Daxildir",
|
||||
"insight": "Dərketmə",
|
||||
"insights": "Dərketmələr",
|
||||
"insights-description": "Seqmentlərdən və süzgəclərdən istifadə edərək məlumatınıza daha dərindən nəzər salın.",
|
||||
"inp": "INP",
|
||||
"invalid-url": "Yanlış URL",
|
||||
"is": "Bərabərdir",
|
||||
"is-false": "Yalandır",
|
||||
"is-not": "Bərabər deyil",
|
||||
"is-not-set": "Təyin edilməyib",
|
||||
"is-set": "Təyin edilib",
|
||||
"is-true": "Doğrudur",
|
||||
"join": "Qoşul",
|
||||
"join-team": "Komandaya qoşul",
|
||||
"journey": "Səyahət",
|
||||
"journey-description": "İstifadəçilərin veb saytınızda necə hərəkət etdiyini anlayın.",
|
||||
"journeys": "Səyahətlər",
|
||||
"language": "Dil",
|
||||
"languages": "Dillər",
|
||||
"laptop": "Noutbuk",
|
||||
"last-click": "Son klik",
|
||||
"last-days": "Son {x} gün",
|
||||
"last-hours": "Son {x} saat",
|
||||
"last-months": "Son {x} ay",
|
||||
"last-seen": "Son görülmə",
|
||||
"lcp": "LCP",
|
||||
"leave": "Tərk et",
|
||||
"leave-team": "Komandanı tərk et",
|
||||
"less-than": "Kiçikdir",
|
||||
"less-than-equals": "Kiçik və ya bərabərdir",
|
||||
"link": "Keçid",
|
||||
"links": "Keçidlər",
|
||||
"location": "Məkan",
|
||||
"login": "Giriş",
|
||||
"llm": "LLM",
|
||||
"logout": "Çıxış",
|
||||
"manage": "İdarə et",
|
||||
"manager": "Menecer",
|
||||
"mask-level": "Maskalama səviyyəsi",
|
||||
"max": "Maks",
|
||||
"max-duration": "Maksimum müddət",
|
||||
"match": "Uyğunluq",
|
||||
"match-all": "Hamısı",
|
||||
"match-any": "İstənilən",
|
||||
"maximize": "Böyüt",
|
||||
"medium": "Vasitə",
|
||||
"member": "Üzv",
|
||||
"members": "Üzvlər",
|
||||
"min": "Min",
|
||||
"minute": "Dəqiqə",
|
||||
"mobile": "Mobil",
|
||||
"model": "Model",
|
||||
"month": "Ay",
|
||||
"more": "Daha çox",
|
||||
"my-account": "Mənim hesabım",
|
||||
"my-websites": "Mənim veb saytlarım",
|
||||
"name": "Ad",
|
||||
"needs-improvement": "Təkmilləşdirmə tələb edir",
|
||||
"new-password": "Yeni parol",
|
||||
"none": "Yoxdur",
|
||||
"number-of-records": "{x} {x, plural, one {qeyd} other {qeyd}}",
|
||||
"ok": "OK",
|
||||
"online": "Onlayn",
|
||||
"open": "Qarışıq",
|
||||
"organic-search": "Üzvi axtarış",
|
||||
"organic-shopping": "Üzvi alış-veriş",
|
||||
"organic-social": "Üzvi sosial",
|
||||
"organic-video": "Üzvi video",
|
||||
"os": "OS",
|
||||
"other": "Digər",
|
||||
"overview": "İcmal",
|
||||
"owner": "Sahib",
|
||||
"page": "Səhifə",
|
||||
"page-of": "Səhifə {current} / {total}",
|
||||
"page-views": "Səhifə baxışları",
|
||||
"pageTitle": "Səhifə başlığı",
|
||||
"period": "Dövr",
|
||||
"pages": "Səhifələr",
|
||||
"paid-ads": "Ödənişli reklamlar",
|
||||
"paid-search": "Ödənişli axtarış",
|
||||
"paid-shopping": "Ödənişli alış-veriş",
|
||||
"paid-social": "Ödənişli sosial",
|
||||
"paid-video": "Ödənişli video",
|
||||
"password": "Parol",
|
||||
"path": "Yol",
|
||||
"paths": "Yollar",
|
||||
"performance": "Performans",
|
||||
"pixel": "Piksel",
|
||||
"pixels": "Piksellər",
|
||||
"poor": "Zəif",
|
||||
"powered-by": "{name} tərəfindən işə salınıb",
|
||||
"preferences": "Tərcihlər",
|
||||
"previous": "Əvvəlki",
|
||||
"previous-period": "Əvvəlki dövr",
|
||||
"previous-year": "Əvvəlki il",
|
||||
"profile": "Profil",
|
||||
"profiles": "Profillər",
|
||||
"properties": "Xüsusiyyətlər",
|
||||
"property": "Xüsusiyyət",
|
||||
"queries": "Sorğular",
|
||||
"query": "Sorğu",
|
||||
"query-parameters": "Sorğu parametrləri",
|
||||
"realtime": "Real vaxt",
|
||||
"recorded": "Qeydə alınıb",
|
||||
"referral": "İstinad",
|
||||
"referrer": "Yönləndirən sayt",
|
||||
"referrers": "Yönləndirən saytlar",
|
||||
"refresh": "Təzələ",
|
||||
"regenerate": "Yenidən yarat",
|
||||
"region": "Bölgə",
|
||||
"regions": "Bölgələr",
|
||||
"remaining": "Qalıb",
|
||||
"remove": "Çıxar",
|
||||
"remove-member": "Üzvü çıxar",
|
||||
"play": "Oynat",
|
||||
"replay": "Təkrar baxış",
|
||||
"replay-id": "Təkrar baxış ID-si",
|
||||
"recorder-code": "Qeydiyyat kodu",
|
||||
"replay-enabled": "Təkrar baxış aktivdir",
|
||||
"replays": "Təkrar baxışlar",
|
||||
"reports": "Hesabatlar",
|
||||
"required": "Məcburi",
|
||||
"reset": "Sıfırla",
|
||||
"reset-website": "Veb saytı sıfırla",
|
||||
"retention": "Saxlanma",
|
||||
"retention-days": "Saxlanma günləri",
|
||||
"retention-description": "Veb saytınızın istifadəçi sadiqliyini onların nə qədər tez-tez qayıtdığını izləyərək ölçün.",
|
||||
"revenue": "Gəlir",
|
||||
"revenue-description": "Gəlir məlumatınıza və istifadəçilərin xərcləmə tərzinə nəzər salın.",
|
||||
"role": "Rol",
|
||||
"run-query": "Sorğunu işə sal",
|
||||
"save": "Yadda saxla",
|
||||
"save-cohort": "Koqortanı yadda saxla",
|
||||
"save-replay": "Təkrar baxışı yadda saxla",
|
||||
"save-segment": "Seqmenti yadda saxla",
|
||||
"sample-rate": "Nümunə tezliyi",
|
||||
"sample-size": "Nümunə ölçüsü",
|
||||
"screen": "Ekran",
|
||||
"screens": "Ekranlar",
|
||||
"search": "Axtarış",
|
||||
"segment": "Seqment",
|
||||
"segments": "Seqmentlər",
|
||||
"select": "Seç",
|
||||
"select-link": "Keçid seç",
|
||||
"select-pixel": "Piksel seç",
|
||||
"select-date": "Tarix seç",
|
||||
"select-filter": "Süzgəc seç",
|
||||
"select-role": "Rol seç",
|
||||
"select-component": "Komponent seç",
|
||||
"select-event": "Hadisə seç",
|
||||
"select-website": "Veb sayt seç",
|
||||
"session": "Sessiya",
|
||||
"session-data": "Sessiya xüsusiyyətləri",
|
||||
"sessions": "Sessiyalar",
|
||||
"settings": "Parametrlər",
|
||||
"share": "Paylaş",
|
||||
"share-url": "Paylaşım URL-i",
|
||||
"single-day": "Tək gün",
|
||||
"slug": "Slug",
|
||||
"sms": "SMS",
|
||||
"source": "Mənbə",
|
||||
"sources": "Mənbələr",
|
||||
"start-step": "Başlanğıc addım",
|
||||
"steps": "Addımlar",
|
||||
"sum": "Cəm",
|
||||
"support": "Dəstək",
|
||||
"switch-account": "Hesabı dəyiş",
|
||||
"switch-to-card-view": "Kart görünüşünə keç",
|
||||
"switch-to-table-view": "Cədvəl görünüşünə keç",
|
||||
"table": "Cədvəl",
|
||||
"tablet": "Planşet",
|
||||
"tag": "Etiket",
|
||||
"tags": "Etiketlər",
|
||||
"team": "Komanda",
|
||||
"team-id": "Komanda ID-si",
|
||||
"team-manager": "Komanda meneceri",
|
||||
"team-member": "Komanda üzvü",
|
||||
"team-name": "Komanda adı",
|
||||
"team-owner": "Komanda sahibi",
|
||||
"team-settings": "Komanda parametrləri",
|
||||
"team-view-only": "Komanda yalnız baxış",
|
||||
"team-websites": "Komanda veb saytları",
|
||||
"teams": "Komandalar",
|
||||
"term": "Termin",
|
||||
"terms": "Terminlər",
|
||||
"theme": "Tema",
|
||||
"this-month": "Bu ay",
|
||||
"this-week": "Bu həftə",
|
||||
"this-year": "Bu il",
|
||||
"timezone": "Saat qurşağı",
|
||||
"title": "Başlıq",
|
||||
"today": "Bu gün",
|
||||
"toggle-charts": "Qrafikləri aç/bağla",
|
||||
"total": "Cəmi",
|
||||
"total-records": "Ümumi qeydlər",
|
||||
"tracking-code": "İzləmə kodu",
|
||||
"traffic": "Trafik",
|
||||
"transactions": "Tranzaksiyalar",
|
||||
"transfer": "Köçür",
|
||||
"transfer-website": "Veb saytı köçür",
|
||||
"true": "Doğru",
|
||||
"ttfb": "TTFB",
|
||||
"type": "Növ",
|
||||
"unique": "Unikal",
|
||||
"unique-events": "Unikal hadisələr",
|
||||
"unique-visitors": "Unikal ziyarətçilər",
|
||||
"aov": "AOV",
|
||||
"arpu": "ARPU",
|
||||
"uniqueCustomers": "Unikal müştərilər",
|
||||
"customers": "Müştərilər",
|
||||
"note": "Qeyd",
|
||||
"notes": "Qeydlər",
|
||||
"orders": "Sifarişlər",
|
||||
"saved": "Yadda saxlanıldı",
|
||||
"unknown": "Naməlum",
|
||||
"untitled": "Adsız",
|
||||
"upgrade": "Təkmilləşdir",
|
||||
"update": "Yenilə",
|
||||
"url": "URL",
|
||||
"user": "İstifadəçi",
|
||||
"username": "İstifadəçi adı",
|
||||
"users": "İstifadəçilər",
|
||||
"utm": "UTM",
|
||||
"utm-campaign": "UTM kampaniyası",
|
||||
"utm-content": "UTM məzmunu",
|
||||
"utm-description": "Kampaniyalarınızı UTM parametrləri vasitəsilə izləyin.",
|
||||
"utm-medium": "UTM vasitəsi",
|
||||
"utm-source": "UTM mənbəyi",
|
||||
"utm-term": "UTM termini",
|
||||
"value": "Dəyər",
|
||||
"version": "Versiya",
|
||||
"view": "Bax",
|
||||
"view-details": "Ətraflı bax",
|
||||
"view-only": "Yalnız baxış",
|
||||
"views": "Baxışlar",
|
||||
"views-per-visit": "Ziyarət başına baxış",
|
||||
"visit-duration": "Ziyarət müddəti",
|
||||
"visitors": "Ziyarətçilər",
|
||||
"visits": "Ziyarətlər",
|
||||
"website": "Veb sayt",
|
||||
"website-id": "Veb sayt ID-si",
|
||||
"websites": "Veb saytlar",
|
||||
"window": "Pəncərə",
|
||||
"yesterday": "Dünən",
|
||||
"security": "Təhlükəsizlik",
|
||||
"two-factor-auth": "İkifaktorlu doğrulama",
|
||||
"two-factor-setup-title": "İkifaktorlu doğrulamanı qur",
|
||||
"two-factor-step1": "1-ci addım",
|
||||
"two-factor-step2": "2-ci addım",
|
||||
"two-factor-scan-qr": "QR kodu skan et",
|
||||
"two-factor-cant-scan": "QR kodu skan edə bilmirsiniz?",
|
||||
"two-factor-manual-entry": "Bunun əvəzinə bu məxfi açarı daxil edin:",
|
||||
"two-factor-copy-code": "Kodu kopyala",
|
||||
"two-factor-code-copied": "Kopyalandı!",
|
||||
"two-factor-get-code": "Doğrulama kodu al",
|
||||
"two-factor-enter-code": "Doğrulama kodunu daxil et",
|
||||
"two-factor-use-backup": "Bunun əvəzinə ehtiyat kodundan istifadə et",
|
||||
"two-factor-save-backup-codes": "Ehtiyat kodlarınızı yadda saxlayın",
|
||||
"two-factor-download-codes": "Kodları endir",
|
||||
"two-factor-saved-codes": "Ehtiyat kodlarımı yadda saxladım",
|
||||
"two-factor-backup-file-title": "Umami - İkifaktorlu doğrulama ehtiyat kodları",
|
||||
"two-factor-success-title": "İkifaktorlu doğrulama aktivləşdirildi",
|
||||
"two-factor-enable": "2FA-nı aktivləşdir",
|
||||
"two-factor-disable": "2FA-nı söndür",
|
||||
"two-factor-disable-title": "İkifaktorlu doğrulamanı söndür",
|
||||
"two-factor-status-not-configured": "Konfiqurasiya edilməyib",
|
||||
"two-factor-active": "Aktiv",
|
||||
"two-factor-require-global": "Bütün istifadəçilər üçün 2FA tələb et",
|
||||
"two-factor-require-user": "Bu istifadəçi üçün 2FA tələb et",
|
||||
"two-factor-require-team": "Bu komandanın bütün üzvləri üçün 2FA tələb et",
|
||||
"two-factor-reset": "2FA-nı sıfırla",
|
||||
"two-factor-global-active-tooltip": "Qlobal 2FA aktivdir",
|
||||
"two-factor-verify": "Təsdiqlə",
|
||||
"two-factor-login": "İkifaktorlu doğrulama",
|
||||
"api-keys": "API açarları",
|
||||
"api-key": "API açarı",
|
||||
"create-api-key": "API açarı yarat",
|
||||
"last-used": "Son istifadə",
|
||||
"never": "Heç vaxt",
|
||||
"copy": "Kopyala",
|
||||
"authorize": "İcazə ver",
|
||||
"deny": "Rədd et"
|
||||
},
|
||||
"message": {
|
||||
"action-confirmation": "Təsdiqləmək üçün aşağıdakı xanaya {confirmation} yazın.",
|
||||
"active-users": "{x} cari {x, plural, one {ziyarətçi} other {ziyarətçi}}",
|
||||
"bad-request": "Yanlış sorğu",
|
||||
"collected-data": "Toplanmış məlumat",
|
||||
"confirm-delete": "{target} silmək istədiyinizə əminsiniz?",
|
||||
"confirm-leave": "<b>{target}</b> tərk etmək istədiyinizə əminsiniz?",
|
||||
"confirm-remove": "<b>{target}</b> çıxarmaq istədiyinizə əminsiniz?",
|
||||
"confirm-reset": "{target} sıfırlamaq istədiyinizə əminsiniz?",
|
||||
"delete-team-warning": "Komandanı silmək onun bütün veb saytlarını da siləcək.",
|
||||
"delete-website-warning": "Veb saytın bütün məlumatları silinəcək.",
|
||||
"error": "Nəsə səhv getdi.",
|
||||
"empty-dashboard": "İdarə paneliniz boşdur. İlk komponentinizi əlavə etmək üçün Dizayn üzərinə klikləyin.",
|
||||
"event-log": "<a>{url}</a> ünvanında <b>{event}</b>",
|
||||
"forbidden": "Qadağandır",
|
||||
"go-to-settings": "Parametrlərə keç",
|
||||
"incorrect-username-password": "İstifadəçi adı və/və ya parol yanlışdır.",
|
||||
"invalid-domain": "Yanlış domen. http/https daxil etməyin.",
|
||||
"invalid-value": "Yanlış dəyər",
|
||||
"max-length": "Maksimum {n} simvol uzunluğunda",
|
||||
"min-password-length": "Minimum {n} simvol uzunluğunda",
|
||||
"new-version-available": "Umami-nin yeni versiyası {version} mövcuddur!",
|
||||
"no-data-available": "Məlumat yoxdur.",
|
||||
"no-event-data": "Hadisə məlumatı yoxdur.",
|
||||
"no-match-password": "Parollar uyğun gəlmir.",
|
||||
"no-results-found": "Nəticə tapılmadı.",
|
||||
"no-team-websites": "Bu komandanın heç bir veb saytı yoxdur.",
|
||||
"no-teams": "Siz heç bir komanda yaratmamısınız.",
|
||||
"no-users": "İstifadəçi yoxdur.",
|
||||
"no-websites-configured": "Sizin heç bir konfiqurasiya edilmiş veb saytınız yoxdur.",
|
||||
"not-found": "Tapılmadı",
|
||||
"nothing-selected": "Heç nə seçilməyib.",
|
||||
"page-not-found": "Səhifə tapılmadı",
|
||||
"reset-website": "Bu veb saytı sıfırlamaq üçün təsdiqləmək məqsədilə aşağıdakı xanaya {confirmation} yazın.",
|
||||
"reset-website-warning": "Bu veb saytın bütün statistikası silinəcək, lakin parametrləriniz qalacaq.",
|
||||
"saved": "Yadda saxlanıldı.",
|
||||
"select-board-entity-first": "Əvvəlcə veb sayt, piksel və ya keçid seçin.",
|
||||
"select-component-preview": "Önbaxış üçün komponent seçin",
|
||||
"select-website-first": "Əvvəlcə veb sayt seçin",
|
||||
"sever-error": "Server xətası",
|
||||
"share-url": "Veb saytınızın statistikası aşağıdakı URL-də hər kəsə açıqdır:",
|
||||
"team-already-member": "Siz artıq bu komandanın üzvüsünüz.",
|
||||
"team-not-found": "Komanda tapılmadı.",
|
||||
"team-websites-info": "Veb saytları komandadakı hər kəs görə bilər.",
|
||||
"tracking-code": "Bu veb saytın statistikasını izləmək üçün aşağıdakı kodu HTML-inizin '<head>...</head>' bölməsinə yerləşdirin.",
|
||||
"transfer-team-website-to-user": "Bu veb sayt hesabınıza köçürülsün?",
|
||||
"transfer-user-website-to-team": "Bu veb saytın köçürüləcəyi komandanı seçin.",
|
||||
"transfer-website": "Veb saytın mülkiyyətini hesabınıza və ya başqa komandaya köçürün.",
|
||||
"triggered-event": "Baş vermiş hadisə",
|
||||
"unauthorized": "İcazəsiz",
|
||||
"user-deleted": "İstifadəçi silindi.",
|
||||
"replay-min-duration-description": "Yalnız bu müddətə bərabər və ya ondan uzun təkrar baxışları göstər.",
|
||||
"viewed-page": "Baxılan səhifə",
|
||||
"upgrade-required": "Bu funksiya {plan} plan abunəliyi tələb edir.",
|
||||
"visitor-log": "<b>{country}</b>-dan <b>{os}</b> <b>{device}</b> üzərində <b>{browser}</b> istifadə edən ziyarətçi",
|
||||
"two-factor-setup-description": "Hər dəfə daxil olanda, parolunuza əlavə olaraq, bir dəfəlik kod yaratmaq və istifadə etmək üçün doğrulayıcı tətbiqdən istifadə edəcəksiniz.",
|
||||
"two-factor-setup-required-description": "Umami-yə daxil olmaq üçün ikifaktorlu doğrulama (2FA) tələb olunur.",
|
||||
"two-factor-step1-description": "Aşağıdakı QR kodu skan edin və ya məxfi açarı əl ilə doğrulayıcı tətbiqinizə daxil edin.",
|
||||
"two-factor-step2-description": "Doğrulayıcı tətbiqinizdə gördüyünüz 6 rəqəmli kodu daxil edin.",
|
||||
"two-factor-enabled-description": "Hesabınız artıq ikifaktorlu doğrulama ilə qorunur. Bundan sonra hər daxil olanda doğrulayıcı tətbiqinizdən bir kod daxil etməli olacaqsınız.",
|
||||
"two-factor-backup-description": "Doğrulayıcı tətbiqinizə girişi itirsəniz, daxil olmaq üçün bu bir dəfəlik ehtiyat kodlarından birini istifadə edə bilərsiniz. Onları təhlükəsiz bir yerdə saxlayın, bir daha göstərilməyəcək.",
|
||||
"two-factor-backup-codes-once": "Bu kodları təhlükəsiz saxlayın. Hər kod yalnız bir dəfə istifadə edilə bilər.",
|
||||
"two-factor-login-description": "Doğrulayıcı tətbiqinizdən 6 rəqəmli kodu daxil edin.",
|
||||
"two-factor-disable-description": "Təsdiqləmək üçün parolunuzu və doğrulayıcı tətbiqinizdən doğrulama kodunu daxil edin.",
|
||||
"two-factor-required-message": "Hesabınız üçün ikifaktorlu doğrulama məcburidir və söndürülə bilməz. Kömək üçün administratorunuzla əlaqə saxlayın.",
|
||||
"two-factor-override-note": "Qlobal 2FA aktiv olduqda istifadəçi və komanda üzrə 2FA parametrləri əhəmiyyətini itirir.",
|
||||
"two-factor-admin-global-description": "Bütün istifadəçilərdən növbəti daxilolmada ikifaktorlu doğrulamanı qurmalarını tələb edin. 2FA-nı hələ konfiqurasiya etməmiş istifadəçilərdən Umami-yə giriş əldə etməzdən əvvəl bunu etmək istəniləcək.",
|
||||
"two-factor-require-user-description": "Aktivləşdirildikdə, bu istifadəçi hələ etməyibsə növbəti daxilolmada 2FA qurmağa dəvət ediləcək.",
|
||||
"two-factor-require-team-description": "Aktivləşdirildikdə, bu komandanın bütün üzvləri hələ etməyiblərsə növbəti daxilolmada 2FA qurmağa dəvət ediləcək.",
|
||||
"two-factor-user-description": "Hesabınıza əlavə təhlükəsizlik qatı əlavə edin. Hər daxil olanda parolunuz və doğrulayıcı tətbiqinizdən bir kod lazım olacaq.",
|
||||
"two-factor-reset-description": "Bu istifadəçinin cari ikifaktorlu konfiqurasiyasını silin. Siyasətə görə 2FA hələ tələb olunursa, növbəti daxilolmada yeni QR kodu skan etmələri istəniləcək.",
|
||||
"two-factor-error-invalid-code": "Yanlış doğrulama kodu. Zəhmət olmasa yenidən cəhd edin.",
|
||||
"two-factor-error-missing-token": "Doğrulama tokeni yoxdur.",
|
||||
"two-factor-error-invalid-partial-token": "Yanlış və ya vaxtı bitmiş doğrulama tokeni.",
|
||||
"two-factor-error-invalid-backup-code": "Yanlış ehtiyat kodu.",
|
||||
"two-factor-error-code-used": "Bu kod artıq istifadə olunub. Növbəti kodu gözləyin.",
|
||||
"two-factor-error-too-many-attempts": "Çox sayda uğursuz cəhd. Hesabınız müvəqqəti bloklanıb.",
|
||||
"two-factor-error-disable-not-allowed": "İkifaktorlu doğrulama məcburidir və söndürülə bilməz.",
|
||||
"two-factor-error-incorrect-password": "Yanlış parol.",
|
||||
"two-factor-error-already-enabled": "İkifaktorlu doğrulama artıq aktivdir.",
|
||||
"two-factor-error-not-enabled": "İkifaktorlu doğrulama aktiv deyil.",
|
||||
"two-factor-error-no-pending-setup": "Gözləyən ikifaktorlu doğrulama qurulumu tapılmadı.",
|
||||
"two-factor-error-not-configured": "Serverdə etibarlı TWO_FACTOR_ENCRYPTION_KEY olmadığı üçün ikifaktorlu doğrulama əlçatan deyil. Kömək üçün administratorunuzla əlaqə saxlayın.",
|
||||
"two-factor-locked-message": "Çox sayda uğursuz cəhd. {time} vaxtında yenidən cəhd edin.",
|
||||
"two-factor-active-description": "Hesabınız ikifaktorlu doğrulama ilə qorunur. Hər daxilolma doğrulayıcı tətbiqinizdən bir kod tələb edir.",
|
||||
"api-key-created": "API açarınızı indi kopyalayın. Onu bir daha görə bilməyəcəksiniz.",
|
||||
"no-api-keys": "Siz heç bir API açarı yaratmamısınız.",
|
||||
"oauth-login-prompt": "Davam etmək üçün daxil olun.",
|
||||
"oauth-consent": "{name} Umami hesabınıza giriş əldə etmək istəyir.",
|
||||
"oauth-permissions": "Bu, tətbiqə aşağıdakılara icazə verəcək:",
|
||||
"oauth-scope-websites-read": "Girişiniz olan veb saytları görmək",
|
||||
"oauth-scope-analytics-read": "Bu veb saytların analitika məlumatlarını oxumaq",
|
||||
"oauth-redirect-notice": "Təsdiqlədikdən sonra {url} ünvanına yönləndiriləcəksiniz.",
|
||||
"oauth-signed-in-as": "{username} kimi daxil olunub",
|
||||
"oauth-invalid-request": "Bu icazə sorğusu etibarsızdır.",
|
||||
"oauth-redirecting": "Yönləndirilir…"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import type { ReactNode } from 'react';
|
||||
import { expect, test, vi } from 'vitest';
|
||||
import { FilterRecord } from './FilterRecord';
|
||||
|
||||
vi.mock('@umami/react-zen', () => ({
|
||||
Button: ({ children }: { children: ReactNode }) => <button>{children}</button>,
|
||||
Column: ({ children }: { children: ReactNode }) => <div>{children}</div>,
|
||||
Grid: ({ children }: { children: ReactNode }) => <div>{children}</div>,
|
||||
Icon: ({ children }: { children: ReactNode }) => <span>{children}</span>,
|
||||
Label: ({ children }: { children: ReactNode }) => <label>{children}</label>,
|
||||
ListItem: ({ children }: { children: ReactNode }) => <div>{children}</div>,
|
||||
Loading: () => <div>Loading</div>,
|
||||
Select: ({ children }: { children: ReactNode }) => <div>{children}</div>,
|
||||
TextField: ({ value }: { value: string }) => (
|
||||
<input aria-label="Filter value" value={value} readOnly />
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock('@/components/common/Empty', () => ({ Empty: () => <div>Empty</div> }));
|
||||
vi.mock('@/components/common/MultiSelect', () => ({
|
||||
MultiSelect: ({ children }: { children: ReactNode }) => <div>{children}</div>,
|
||||
MultiSelectItem: ({ children }: { children: ReactNode }) => <div>{children}</div>,
|
||||
}));
|
||||
vi.mock('@/components/hooks', () => ({
|
||||
useFilters: () => ({ fields: [], operators: [] }),
|
||||
useFormat: () => ({ formatValue: (value: string) => value }),
|
||||
useWebsiteValuesQuery: () => ({ data: [], isLoading: false }),
|
||||
}));
|
||||
vi.mock('@/components/icons', () => ({ X: () => null }));
|
||||
|
||||
test('keeps commas in regex filter values', () => {
|
||||
const value = '^[a-zA-Z0-9]{21,22}$';
|
||||
|
||||
render(
|
||||
<FilterRecord
|
||||
type="path"
|
||||
startDate={new Date('2026-08-01')}
|
||||
endDate={new Date('2026-08-02')}
|
||||
name="path"
|
||||
operator="re"
|
||||
value={value}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByLabelText('Filter value')).toHaveValue(value);
|
||||
});
|
||||
|
||||
test('keeps commas when changing to a search operator', () => {
|
||||
const value = '^[a-zA-Z0-9]{21,22}$';
|
||||
const props = {
|
||||
type: 'path',
|
||||
startDate: new Date('2026-08-01'),
|
||||
endDate: new Date('2026-08-02'),
|
||||
name: 'path',
|
||||
value,
|
||||
};
|
||||
const { rerender } = render(<FilterRecord {...props} operator="eq" />);
|
||||
|
||||
rerender(<FilterRecord {...props} operator="re" />);
|
||||
|
||||
expect(screen.getByLabelText('Filter value')).toHaveValue(value);
|
||||
});
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
Select,
|
||||
TextField,
|
||||
} from '@umami/react-zen';
|
||||
import { useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Empty } from '@/components/common/Empty';
|
||||
import { MultiSelect, MultiSelectItem } from '@/components/common/MultiSelect';
|
||||
import { useFilters, useFormat, useWebsiteValuesQuery } from '@/components/hooks';
|
||||
@@ -29,6 +29,12 @@ export interface FilterRecordProps {
|
||||
onChange?: (name: string, value: string) => void;
|
||||
}
|
||||
|
||||
function getSelectedValues(value: string | string[], operator: string) {
|
||||
if (Array.isArray(value)) return value;
|
||||
if (!value) return [];
|
||||
return isSearchOperator(operator) ? [value] : value.split(',');
|
||||
}
|
||||
|
||||
export function FilterRecord({
|
||||
websiteId,
|
||||
type,
|
||||
@@ -42,8 +48,8 @@ export function FilterRecord({
|
||||
onChange,
|
||||
}: FilterRecordProps) {
|
||||
const { fields, operators } = useFilters();
|
||||
const initValues = Array.isArray(value) ? value : value ? value.split(',') : [];
|
||||
const [selected, setSelected] = useState<string[]>(initValues);
|
||||
const isSearch = isSearchOperator(operator);
|
||||
const [selected, setSelected] = useState<string[]>(() => getSelectedValues(value, operator));
|
||||
const [search, setSearch] = useState('');
|
||||
const { formatValue } = useFormat();
|
||||
const { data, isLoading } = useWebsiteValuesQuery({
|
||||
@@ -53,9 +59,12 @@ export function FilterRecord({
|
||||
startDate,
|
||||
endDate,
|
||||
});
|
||||
const isSearch = isSearchOperator(operator);
|
||||
const items = data?.filter(({ value }) => value) || [];
|
||||
|
||||
useEffect(() => {
|
||||
setSelected(getSelectedValues(value, operator));
|
||||
}, [operator, value]);
|
||||
|
||||
const handleSearch = (value: string) => {
|
||||
setSearch(value);
|
||||
};
|
||||
|
||||
@@ -697,6 +697,7 @@ export const CURRENCIES = [
|
||||
{ id: 'PLN', name: 'Polish Złoty' },
|
||||
{ id: 'NOK', name: 'Norwegian Krone' },
|
||||
{ id: 'DKK', name: 'Danish Krone' },
|
||||
{ id: 'ISK', name: 'Icelandic Króna' },
|
||||
{ id: 'NZD', name: 'New Zealand Dollar' },
|
||||
{ id: 'ZAR', name: 'South African Rand' },
|
||||
{ id: 'MXN', name: 'Mexican Peso' },
|
||||
@@ -705,6 +706,7 @@ export const CURRENCIES = [
|
||||
{ id: 'MYR', name: 'Malaysian Ringgit' },
|
||||
{ id: 'INR', name: 'Indian Rupee' },
|
||||
{ id: 'KRW', name: 'South Korean Won' },
|
||||
{ id: 'TWD', name: 'New Taiwan Dollar' },
|
||||
{ id: 'BRL', name: 'Brazilian Real' },
|
||||
{ id: 'TRY', name: 'Turkish Lira' },
|
||||
{ id: 'CZK', name: 'Czech Koruna' },
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
arSA,
|
||||
az,
|
||||
be,
|
||||
bg,
|
||||
bn,
|
||||
@@ -49,6 +50,7 @@ import {
|
||||
|
||||
export const languages = {
|
||||
'ar-SA': { label: 'العربية', dateLocale: arSA, dir: 'rtl' },
|
||||
'az-AZ': { label: 'Azərbaycan', dateLocale: az },
|
||||
'be-BY': { label: 'Беларуская', dateLocale: be },
|
||||
'bg-BG': { label: 'български език', dateLocale: bg },
|
||||
'bn-BD': { label: 'বাংলা', dateLocale: bn },
|
||||
|
||||
+57
-1
@@ -24,9 +24,10 @@ vi.mock('@/generated/prisma/client', () => ({
|
||||
}));
|
||||
|
||||
let getRawQueryClient!: typeof import('./prisma').getRawQueryClient;
|
||||
let prisma!: typeof import('./prisma').default;
|
||||
|
||||
beforeAll(async () => {
|
||||
({ getRawQueryClient } = await import('./prisma'));
|
||||
({ getRawQueryClient, default: prisma } = await import('./prisma'));
|
||||
});
|
||||
|
||||
interface RawQueryClient {
|
||||
@@ -80,3 +81,58 @@ describe('getRawQueryClient', () => {
|
||||
expect(getRawQueryClient(client, { write: true })).toBe(client);
|
||||
});
|
||||
});
|
||||
|
||||
describe('pagedRawQuery default ordering', () => {
|
||||
function mockQueries(count = '4') {
|
||||
const queryRaw = vi.mocked((prisma.client as any).$queryRawUnsafe);
|
||||
queryRaw.mockClear();
|
||||
queryRaw.mockImplementation(async (sql: string) =>
|
||||
sql.includes('count(*) as num') ? [{ num: count }] : [{ id: 1 }],
|
||||
);
|
||||
return queryRaw;
|
||||
}
|
||||
|
||||
test.each([undefined, 5])(
|
||||
'applies trusted default order only to the page query with cap %s',
|
||||
async maxResults => {
|
||||
const queryRaw = mockQueries('5');
|
||||
const result = await prisma.pagedRawQuery(
|
||||
'select * from thing',
|
||||
{},
|
||||
{ page: 2, pageSize: 2, maxResults },
|
||||
'test',
|
||||
'max(created_at) desc, session_id',
|
||||
);
|
||||
|
||||
expect(queryRaw).toHaveBeenCalledTimes(2);
|
||||
expect(queryRaw.mock.calls[0][0]).not.toContain('order by max(created_at) desc');
|
||||
expect(queryRaw.mock.calls[1][0]).toContain('order by max(created_at) desc, session_id');
|
||||
expect(queryRaw.mock.calls[1][0]).toContain('limit 2 offset 2');
|
||||
expect(result).toEqual({
|
||||
data: [{ id: 1 }],
|
||||
count: 5,
|
||||
page: 2,
|
||||
pageSize: 2,
|
||||
isCapped: !!maxResults,
|
||||
orderBy: undefined,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test('keeps explicit ordering and legacy no-order behavior', async () => {
|
||||
const queryRaw = mockQueries();
|
||||
const result = await prisma.pagedRawQuery(
|
||||
'select * from thing',
|
||||
{},
|
||||
{ page: 1, pageSize: 2, orderBy: 'name', sortDescending: true },
|
||||
undefined,
|
||||
'max(created_at) desc, session_id',
|
||||
);
|
||||
expect(queryRaw.mock.calls[1][0]).toContain('order by name desc');
|
||||
expect(queryRaw.mock.calls[1][0]).not.toContain('order by max(created_at) desc, session_id');
|
||||
expect(result.orderBy).toBe('name');
|
||||
queryRaw.mockClear();
|
||||
await prisma.pagedRawQuery('select * from thing', {}, { page: 1, pageSize: 2 });
|
||||
expect(queryRaw.mock.calls[1][0]).not.toMatch(/order by/);
|
||||
});
|
||||
});
|
||||
+2
-1
@@ -786,6 +786,7 @@ async function pagedRawQuery(
|
||||
queryParams: Record<string, any>,
|
||||
filters: QueryFilters,
|
||||
name?: string,
|
||||
defaultOrderBy?: string,
|
||||
) {
|
||||
const { page = 1, pageSize, orderBy, sortDescending = false } = filters;
|
||||
const size = +pageSize || DEFAULT_PAGE_SIZE;
|
||||
@@ -793,7 +794,7 @@ async function pagedRawQuery(
|
||||
const direction = sortDescending ? 'desc' : 'asc';
|
||||
|
||||
const statements = [
|
||||
orderBy && `order by ${orderBy} ${direction}`,
|
||||
orderBy ? `order by ${orderBy} ${direction}` : defaultOrderBy && `order by ${defaultOrderBy}`,
|
||||
+size > 0 && `limit ${+size} offset ${offset}`,
|
||||
]
|
||||
.filter(n => n)
|
||||
|
||||
@@ -75,11 +75,11 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||
session.country,
|
||||
session.region,
|
||||
session.city
|
||||
order by max(website_event.created_at) desc, session.session_id
|
||||
`,
|
||||
queryParams,
|
||||
filters,
|
||||
FUNCTION_NAME,
|
||||
'max(website_event.created_at) desc, session.session_id',
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { afterEach, expect, test, vi } from 'vitest';
|
||||
|
||||
afterEach(() => {
|
||||
delete (window as Window & { umami?: unknown }).umami;
|
||||
delete (document as Document & { currentScript?: HTMLScriptElement }).currentScript;
|
||||
delete (document as Document & { readyState?: DocumentReadyState }).readyState;
|
||||
vi.unstubAllGlobals();
|
||||
vi.resetModules();
|
||||
});
|
||||
|
||||
test('identifies data-distinct-id before the initial page view', async () => {
|
||||
const script = document.createElement('script');
|
||||
script.src = 'https://analytics.example.com/script.js';
|
||||
script.dataset.websiteId = 'website-id';
|
||||
script.dataset.distinctId = 'visitor-id';
|
||||
|
||||
Object.defineProperties(document, {
|
||||
currentScript: { configurable: true, value: script },
|
||||
readyState: { configurable: true, value: 'complete' },
|
||||
});
|
||||
|
||||
const fetchMock = vi.fn().mockResolvedValue({ json: vi.fn().mockResolvedValue({}) });
|
||||
vi.stubGlobal('fetch', fetchMock);
|
||||
|
||||
await import('./index');
|
||||
|
||||
await vi.waitFor(() => expect(fetchMock).toHaveBeenCalledTimes(2));
|
||||
const requests = fetchMock.mock.calls.map(([, init]) => JSON.parse(init.body));
|
||||
|
||||
expect(requests[0]).toMatchObject({
|
||||
type: 'identify',
|
||||
payload: { id: 'visitor-id', website: 'website-id' },
|
||||
});
|
||||
expect(requests[1]).toMatchObject({
|
||||
type: 'event',
|
||||
payload: { id: 'visitor-id', website: 'website-id' },
|
||||
});
|
||||
});
|
||||
@@ -241,6 +241,7 @@ type MetricEntry = PerformanceEntry & {
|
||||
const website = config('website-id');
|
||||
const hostUrl = config('host-url');
|
||||
const beforeSend = config('before-send');
|
||||
const distinctId = config('distinct-id') || undefined;
|
||||
const tag = config('tag') || undefined;
|
||||
const autoTrack = config('auto-track') !== _false;
|
||||
const dnt = config('do-not-track') === _true;
|
||||
@@ -641,9 +642,13 @@ type MetricEntry = PerformanceEntry & {
|
||||
let initialized = false;
|
||||
let disabled = false;
|
||||
let cache: string | undefined;
|
||||
let identity: string | undefined;
|
||||
let identity = distinctId;
|
||||
let flushPerformance: (() => void) | undefined;
|
||||
|
||||
if (distinctId) {
|
||||
void identify(distinctId);
|
||||
}
|
||||
|
||||
if (autoTrack && !trackingDisabled()) {
|
||||
if (document.readyState === 'complete') {
|
||||
init();
|
||||
|
||||
Reference in new issue
Block a user