From 7aa0c83e66733877d84408dcdd6f7c699736ce1d Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sun, 11 May 2025 23:54:22 -0700 Subject: [PATCH 01/18] Updated migration script. --- scripts/data-migrations/convert-utm-clid-columns.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/data-migrations/convert-utm-clid-columns.sql b/scripts/data-migrations/convert-utm-clid-columns.sql index cf85a156..7211c202 100644 --- a/scripts/data-migrations/convert-utm-clid-columns.sql +++ b/scripts/data-migrations/convert-utm-clid-columns.sql @@ -45,4 +45,4 @@ SET fbclid = LEFT(SUBSTRING_INDEX(SUBSTRING_INDEX(REGEXP_SUBSTR(url_query, '(?:[ utm_medium = LEFT(SUBSTRING_INDEX(SUBSTRING_INDEX(REGEXP_SUBSTR(url_query, '(?:[&?]|^)utm_medium=[^&]+'), '=', -1), '&', 1), 255), utm_source = LEFT(SUBSTRING_INDEX(SUBSTRING_INDEX(REGEXP_SUBSTR(url_query, '(?:[&?]|^)utm_source=[^&]+'), '=', -1), '&', 1), 255), utm_term = LEFT(SUBSTRING_INDEX(SUBSTRING_INDEX(REGEXP_SUBSTR(url_query, '(?:[&?]|^)utm_term=[^&]+'), '=', -1), '&', 1), 255) -WHERE 1 = 1; +WHERE url_query IS NOT NULL; From 907a6850dbac98dbe7f9996301421fa8e4d57c7a Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Mon, 12 May 2025 00:01:24 -0700 Subject: [PATCH 02/18] Update migration script. --- scripts/data-migrations/convert-utm-clid-columns.sql | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/data-migrations/convert-utm-clid-columns.sql b/scripts/data-migrations/convert-utm-clid-columns.sql index cf85a156..fffe1ddb 100644 --- a/scripts/data-migrations/convert-utm-clid-columns.sql +++ b/scripts/data-migrations/convert-utm-clid-columns.sql @@ -25,7 +25,8 @@ FROM (SELECT event_id, website_id, session_id, (regexp_matches(url_query, '(?:[&?]|^)utm_medium=([^&]+)', 'i'))[1] AS utm_medium, (regexp_matches(url_query, '(?:[&?]|^)utm_source=([^&]+)', 'i'))[1] AS utm_source, (regexp_matches(url_query, '(?:[&?]|^)utm_term=([^&]+)', 'i'))[1] AS utm_term - FROM "website_event") url + FROM "website_event" + WHERE url_query IS NOT NULL) url WHERE we.event_id = url.event_id and we.session_id = url.session_id and we.website_id = url.website_id; @@ -45,4 +46,4 @@ SET fbclid = LEFT(SUBSTRING_INDEX(SUBSTRING_INDEX(REGEXP_SUBSTR(url_query, '(?:[ utm_medium = LEFT(SUBSTRING_INDEX(SUBSTRING_INDEX(REGEXP_SUBSTR(url_query, '(?:[&?]|^)utm_medium=[^&]+'), '=', -1), '&', 1), 255), utm_source = LEFT(SUBSTRING_INDEX(SUBSTRING_INDEX(REGEXP_SUBSTR(url_query, '(?:[&?]|^)utm_source=[^&]+'), '=', -1), '&', 1), 255), utm_term = LEFT(SUBSTRING_INDEX(SUBSTRING_INDEX(REGEXP_SUBSTR(url_query, '(?:[&?]|^)utm_term=[^&]+'), '=', -1), '&', 1), 255) -WHERE 1 = 1; +WHERE url_query IS NOT NULL; From 68fab48ab729e7d6ac4d925ab8c35dbedd9885ab Mon Sep 17 00:00:00 2001 From: 360 Date: Thu, 15 May 2025 10:13:07 +0100 Subject: [PATCH 03/18] fix: correct example description for Umami version in bug report template --- .github/ISSUE_TEMPLATE/1.bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/1.bug_report.yml b/.github/ISSUE_TEMPLATE/1.bug_report.yml index 711468f2..2404918b 100644 --- a/.github/ISSUE_TEMPLATE/1.bug_report.yml +++ b/.github/ISSUE_TEMPLATE/1.bug_report.yml @@ -25,7 +25,7 @@ body: - type: input attributes: label: Which Umami version are you using? (if relevant) - description: 'For example: Chrome, Edge, Firefox, etc' + description: 'For example: 2.18.0, 2.15.1, 1.39.0, etc' - type: input attributes: label: Which browser are you using? (if relevant) From 8d483d92830cda0efa15d5c54d42a26168004077 Mon Sep 17 00:00:00 2001 From: Eritque arcus Date: Thu, 22 May 2025 00:19:50 -0500 Subject: [PATCH 04/18] fix: hash is not included in record --- src/app/api/send/route.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/api/send/route.ts b/src/app/api/send/route.ts index 60d6f7af..9d41a68f 100644 --- a/src/app/api/send/route.ts +++ b/src/app/api/send/route.ts @@ -145,7 +145,7 @@ export async function POST(request: Request) { const base = hostname ? `https://${hostname}` : 'https://localhost'; const currentUrl = new URL(url, base); - let urlPath = currentUrl.pathname === '/undefined' ? '' : currentUrl.pathname; + let urlPath = currentUrl.pathname === '/undefined' ? '' : currentUrl.pathname + currentUrl.hash; const urlQuery = currentUrl.search.substring(1); const urlDomain = currentUrl.hostname.replace(/^www./, ''); From 33110a44ec1c04896e4b6f06ba7773156901b6d6 Mon Sep 17 00:00:00 2001 From: Eritque arcus Date: Thu, 22 May 2025 00:24:48 -0500 Subject: [PATCH 05/18] fix: fix remove trailing slash regex --- src/app/api/send/route.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/api/send/route.ts b/src/app/api/send/route.ts index 9d41a68f..65c88a28 100644 --- a/src/app/api/send/route.ts +++ b/src/app/api/send/route.ts @@ -169,7 +169,7 @@ export async function POST(request: Request) { const twclid = currentUrl.searchParams.get('twclid'); if (process.env.REMOVE_TRAILING_SLASH) { - urlPath = urlPath.replace(/(.+)\/$/, '$1'); + urlPath = urlPath.replace(/\/(?=(#.*)?$)/, ''); } if (referrer) { From e8f166cc690c595f21a1ebf6143bbaf61b343848 Mon Sep 17 00:00:00 2001 From: Ru Chern Chong <10343662+ruchernchong@users.noreply.github.com> Date: Thu, 22 May 2025 15:19:34 +0800 Subject: [PATCH 06/18] Update ci.yml Only run the CI if it belongs to the original repository --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 314c6944..835407b4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,6 +11,8 @@ env: jobs: build: + # Only run the CI if it belongs to the original repository + if: github.repository == 'umami-software/umami' runs-on: ubuntu-latest strategy: From 76519e0d14c996913c1ddd75db6a95f4d36f68b6 Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Wed, 4 Jun 2025 09:27:28 -0700 Subject: [PATCH 07/18] add segment and report param migrations --- .../migrations/11_add_segment/migration.sql | 17 +++++++++ .../12_update_report_parameter/migration.sql | 3 ++ db/postgresql/schema.prisma | 35 +++++++++++++------ 3 files changed, 45 insertions(+), 10 deletions(-) create mode 100644 db/postgresql/migrations/11_add_segment/migration.sql create mode 100644 db/postgresql/migrations/12_update_report_parameter/migration.sql diff --git a/db/postgresql/migrations/11_add_segment/migration.sql b/db/postgresql/migrations/11_add_segment/migration.sql new file mode 100644 index 00000000..7fbb0867 --- /dev/null +++ b/db/postgresql/migrations/11_add_segment/migration.sql @@ -0,0 +1,17 @@ +-- CreateTable +CREATE TABLE "segment" ( + "segment_id" UUID NOT NULL, + "website_id" UUID NOT NULL, + "name" VARCHAR(200) NOT NULL, + "filters" JSONB NOT NULL, + "created_at" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMPTZ(6), + + CONSTRAINT "segment_pkey" PRIMARY KEY ("segment_id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "segment_segment_id_key" ON "segment"("segment_id"); + +-- CreateIndex +CREATE INDEX "segment_website_id_idx" ON "segment"("website_id"); diff --git a/db/postgresql/migrations/12_update_report_parameter/migration.sql b/db/postgresql/migrations/12_update_report_parameter/migration.sql new file mode 100644 index 00000000..f063973a --- /dev/null +++ b/db/postgresql/migrations/12_update_report_parameter/migration.sql @@ -0,0 +1,3 @@ +-- AlterTable +ALTER TABLE "report" +ALTER COLUMN "parameters" SET DATA TYPE JSONB USING parameters::JSONB diff --git a/db/postgresql/schema.prisma b/db/postgresql/schema.prisma index 69efa265..a6c35908 100644 --- a/db/postgresql/schema.prisma +++ b/db/postgresql/schema.prisma @@ -77,6 +77,7 @@ model Website { eventData EventData[] report Report[] sessionData SessionData[] + segment Segment[] @@index([userId]) @@index([teamId]) @@ -103,12 +104,12 @@ model WebsiteEvent { referrerQuery String? @map("referrer_query") @db.VarChar(500) referrerDomain String? @map("referrer_domain") @db.VarChar(500) pageTitle String? @map("page_title") @db.VarChar(500) - gclid String? @map("gclid") @db.VarChar(255) - fbclid String? @map("fbclid") @db.VarChar(255) - msclkid String? @map("msclkid") @db.VarChar(255) - ttclid String? @map("ttclid") @db.VarChar(255) + gclid String? @db.VarChar(255) + fbclid String? @db.VarChar(255) + msclkid String? @db.VarChar(255) + ttclid String? @db.VarChar(255) lifatid String? @map("li_fat_id") @db.VarChar(255) - twclid String? @map("twclid") @db.VarChar(255) + twclid String? @db.VarChar(255) eventType Int @default(1) @map("event_type") @db.Integer eventName String? @map("event_name") @db.VarChar(50) tag String? @db.VarChar(50) @@ -199,7 +200,7 @@ model TeamUser { id String @id() @unique() @map("team_user_id") @db.Uuid teamId String @map("team_id") @db.Uuid userId String @map("user_id") @db.Uuid - role String @map("role") @db.VarChar(50) + role String @db.VarChar(50) createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6) updatedAt DateTime? @updatedAt @map("updated_at") @db.Timestamptz(6) @@ -215,10 +216,10 @@ model Report { id String @id() @unique() @map("report_id") @db.Uuid userId String @map("user_id") @db.Uuid websiteId String @map("website_id") @db.Uuid - type String @map("type") @db.VarChar(200) - name String @map("name") @db.VarChar(200) - description String @map("description") @db.VarChar(500) - parameters String @map("parameters") @db.VarChar(6000) + type String @db.VarChar(200) + name String @db.VarChar(200) + description String @db.VarChar(500) + parameters Json createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6) updatedAt DateTime? @updatedAt @map("updated_at") @db.Timestamptz(6) @@ -231,3 +232,17 @@ model Report { @@index([name]) @@map("report") } + +model Segment { + id String @id() @unique() @map("segment_id") @db.Uuid + websiteId String @map("website_id") @db.Uuid + name String @db.VarChar(200) + filters Json + createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6) + updatedAt DateTime? @updatedAt @map("updated_at") @db.Timestamptz(6) + + website Website @relation(fields: [websiteId], references: [id]) + + @@index([websiteId]) + @@map("segment") +} \ No newline at end of file From a9c79388873d8da94254941ff1e5dad0c0a5041e Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Wed, 4 Jun 2025 09:53:31 -0700 Subject: [PATCH 08/18] add data conversion to report param migration --- .../migrations/12_update_report_parameter/migration.sql | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/db/postgresql/migrations/12_update_report_parameter/migration.sql b/db/postgresql/migrations/12_update_report_parameter/migration.sql index f063973a..aca2f39d 100644 --- a/db/postgresql/migrations/12_update_report_parameter/migration.sql +++ b/db/postgresql/migrations/12_update_report_parameter/migration.sql @@ -1,3 +1,7 @@ +-- ConvertData +UPDATE "report" +SET "parameters" = CONCAT('"', REPLACE(parameters, '"', '\"'), '"'); + -- AlterTable ALTER TABLE "report" -ALTER COLUMN "parameters" SET DATA TYPE JSONB USING parameters::JSONB +ALTER COLUMN "parameters" SET DATA TYPE JSONB USING parameters::JSONB; From 57acaf9855063f6143d79543fa96acd43231f366 Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Wed, 4 Jun 2025 16:06:11 -0700 Subject: [PATCH 09/18] remove data conversion --- .../migrations/12_update_report_parameter/migration.sql | 4 ---- 1 file changed, 4 deletions(-) diff --git a/db/postgresql/migrations/12_update_report_parameter/migration.sql b/db/postgresql/migrations/12_update_report_parameter/migration.sql index aca2f39d..19b663f4 100644 --- a/db/postgresql/migrations/12_update_report_parameter/migration.sql +++ b/db/postgresql/migrations/12_update_report_parameter/migration.sql @@ -1,7 +1,3 @@ --- ConvertData -UPDATE "report" -SET "parameters" = CONCAT('"', REPLACE(parameters, '"', '\"'), '"'); - -- AlterTable ALTER TABLE "report" ALTER COLUMN "parameters" SET DATA TYPE JSONB USING parameters::JSONB; From 826f29bbc07438597999160cac26e30fbe3e1e4d Mon Sep 17 00:00:00 2001 From: undefined Date: Thu, 5 Jun 2025 17:34:09 +0100 Subject: [PATCH 10/18] chore: allow custom geolite database path --- src/lib/detect.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/detect.ts b/src/lib/detect.ts index a023d27d..99c101ab 100644 --- a/src/lib/detect.ts +++ b/src/lib/detect.ts @@ -124,7 +124,7 @@ export async function getLocation(ip: string = '', headers: Headers, hasPayloadI if (!global[MAXMIND]) { const dir = path.join(process.cwd(), 'geo'); - global[MAXMIND] = await maxmind.open(path.resolve(dir, 'GeoLite2-City.mmdb')); + global[MAXMIND] = await maxmind.open(process.env.GEOLITE_DB_PATH || path.resolve(dir, 'GeoLite2-City.mmdb')); } const result = global[MAXMIND].get(ip); From a16846f4ce2cbed81eeb3be7a1c02222fddc90be Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Fri, 6 Jun 2025 08:47:52 -0700 Subject: [PATCH 11/18] add website_revenue table and view. update revenue report to use view --- db/clickhouse/schema.sql | 35 ++++++++++++++ src/queries/sql/reports/getRevenue.ts | 68 ++++++++------------------- 2 files changed, 55 insertions(+), 48 deletions(-) diff --git a/db/clickhouse/schema.sql b/db/clickhouse/schema.sql index fef600e0..ff85112b 100644 --- a/db/clickhouse/schema.sql +++ b/db/clickhouse/schema.sql @@ -246,3 +246,38 @@ SELECT * ORDER BY toStartOfDay(created_at), website_id, referrer_domain, created ); ALTER TABLE umami.website_event MATERIALIZE PROJECTION website_event_referrer_domain_projection; + +-- revenue +CREATE TABLE umami.website_revenue +( + website_id UUID, + session_id UUID, + event_id UUID, + event_name String, + currency String, + revenue DECIMAL(18,4), + created_at DateTime('UTC') +) +ENGINE = MergeTree + PARTITION BY toYYYYMM(created_at) + ORDER BY (website_id, session_id, created_at) + SETTINGS index_granularity = 8192; + + +CREATE MATERIALIZED VIEW umami.website_revenue_mv +TO umami.website_revenue +AS +SELECT DISTINCT + ed.website_id, + ed.session_id, + ed.event_id, + ed.event_name, + c.currency, + coalesce(toDecimal64(ed.number_value, 2), toDecimal64(ed.string_value, 2)) revenue, + ed.created_at +FROM umami.event_data ed +JOIN (SELECT event_id, string_value as currency + FROM event_data + WHERE positionCaseInsensitive(data_key, 'currency') > 0) c + ON c.event_id = ed.event_id +WHERE positionCaseInsensitive(data_key, 'revenue') > 0; diff --git a/src/queries/sql/reports/getRevenue.ts b/src/queries/sql/reports/getRevenue.ts index f1fb1d73..f7996fcc 100644 --- a/src/queries/sql/reports/getRevenue.ts +++ b/src/queries/sql/reports/getRevenue.ts @@ -180,18 +180,11 @@ async function clickhouseQuery( select event_name x, ${getDateSQL('created_at', unit, timezone)} t, - sum(coalesce(toDecimal64(number_value, 2), toDecimal64(string_value, 2))) y - from event_data - join (select event_id - from event_data - where website_id = {websiteId:UUID} - and created_at between {startDate:DateTime64} and {endDate:DateTime64} - and positionCaseInsensitive(data_key, 'currency') > 0 - and string_value = {currency:String}) currency - on currency.event_id = event_data.event_id + sum(revenue) y + from website_revenue where website_id = {websiteId:UUID} and created_at between {startDate:DateTime64} and {endDate:DateTime64} - and positionCaseInsensitive(data_key, 'revenue') > 0 + and currency = {currency:String} group by x, t order by t `, @@ -207,23 +200,16 @@ async function clickhouseQuery( ` select s.country as name, - sum(coalesce(toDecimal64(number_value, 2), toDecimal64(string_value, 2))) as value - from event_data ed - join (select event_id - from event_data - where website_id = {websiteId:UUID} - and created_at between {startDate:DateTime64} and {endDate:DateTime64} - and positionCaseInsensitive(data_key, 'currency') > 0 - and string_value = {currency:String}) c - on c.event_id = ed.event_id + sum(w.revenue) as value + from website_revenue w join (select distinct website_id, session_id, country from website_event_stats_hourly where website_id = {websiteId:UUID}) s - on ed.website_id = s.website_id - and ed.session_id = s.session_id - where ed.website_id = {websiteId:UUID} - and ed.created_at between {startDate:DateTime64} and {endDate:DateTime64} - and positionCaseInsensitive(ed.data_key, 'revenue') > 0 + on w.website_id = s.website_id + and w.session_id = s.session_id + where w.website_id = {websiteId:UUID} + and w.created_at between {startDate:DateTime64} and {endDate:DateTime64} + and w.currency = {currency:String} group by s.country `, { websiteId, startDate, endDate, currency }, @@ -237,20 +223,13 @@ async function clickhouseQuery( }>( ` select - sum(coalesce(toDecimal64(number_value, 2), toDecimal64(string_value, 2))) as sum, + sum(revenue) as sum, uniqExact(event_id) as count, uniqExact(session_id) as unique_count - from event_data - join (select event_id - from event_data - where website_id = {websiteId:UUID} - and created_at between {startDate:DateTime64} and {endDate:DateTime64} - and positionCaseInsensitive(data_key, 'currency') > 0 - and string_value = {currency:String}) currency - on currency.event_id = event_data.event_id + from website_revenue where website_id = {websiteId:UUID} and created_at between {startDate:DateTime64} and {endDate:DateTime64} - and positionCaseInsensitive(data_key, 'revenue') > 0 + and currency = {currency:String} `, { websiteId, startDate, endDate, currency }, ).then(result => result?.[0]); @@ -266,22 +245,15 @@ async function clickhouseQuery( >( ` select - c.currency, - sum(coalesce(toDecimal64(ed.number_value, 2), toDecimal64(ed.string_value, 2))) as sum, - uniqExact(ed.event_id) as count, - uniqExact(ed.session_id) as unique_count - from event_data ed - join (select event_id, string_value as currency - from event_data - where website_id = {websiteId:UUID} - and created_at between {startDate:DateTime64} and {endDate:DateTime64} - and positionCaseInsensitive(data_key, 'currency') > 0) c - on c.event_id = ed.event_id + currency, + sum(revenue) as sum, + uniqExact(event_id) as count, + uniqExact(session_id) as unique_count + from website_revenue where website_id = {websiteId:UUID} and created_at between {startDate:DateTime64} and {endDate:DateTime64} - and positionCaseInsensitive(data_key, 'revenue') > 0 - group by c.currency - order by sum desc; + group by currency + order by sum desc `, { websiteId, startDate, endDate, unit, timezone, currency }, ); From 9a437dcfa2110ca150c598b1b2042d8444fbdea2 Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Sat, 7 Jun 2025 07:43:36 -0700 Subject: [PATCH 12/18] convert attribution report --- db/clickhouse/schema.sql | 2 +- src/queries/sql/reports/getAttribution.ts | 19 ++++++------------- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/db/clickhouse/schema.sql b/db/clickhouse/schema.sql index ff85112b..396d300a 100644 --- a/db/clickhouse/schema.sql +++ b/db/clickhouse/schema.sql @@ -277,7 +277,7 @@ SELECT DISTINCT ed.created_at FROM umami.event_data ed JOIN (SELECT event_id, string_value as currency - FROM event_data + FROM umami.event_data WHERE positionCaseInsensitive(data_key, 'currency') > 0) c ON c.event_id = ed.event_id WHERE positionCaseInsensitive(data_key, 'revenue') > 0; diff --git a/src/queries/sql/reports/getAttribution.ts b/src/queries/sql/reports/getAttribution.ts index 0c3d447a..f224eb5c 100644 --- a/src/queries/sql/reports/getAttribution.ts +++ b/src/queries/sql/reports/getAttribution.ts @@ -311,21 +311,14 @@ async function clickhouseQuery( const revenueEventQuery = `WITH events AS ( select - ed.session_id, - max(ed.created_at) max_dt, - sum(coalesce(toDecimal64(number_value, 2), toDecimal64(string_value, 2))) as value - from event_data ed - join (select event_id - from event_data - where website_id = {websiteId:UUID} - and created_at between {startDate:DateTime64} and {endDate:DateTime64} - and positionCaseInsensitive(data_key, 'currency') > 0 - and string_value = {currency:String}) c - on c.event_id = ed.event_id + session_id, + max(created_at) max_dt, + sum(revenue) as value + from website_revenue where website_id = {websiteId:UUID} and created_at between {startDate:DateTime64} and {endDate:DateTime64} - and ${column} = {conversionStep:String} - and positionCaseInsensitive(ed.data_key, 'revenue') > 0 + and ${column} = {conversionStep:String} + and currency = {currency:String} group by 1),`; function getModelQuery(model: string) { From 8c42d9ef0210881ec191744e4c41a8596c915c55 Mon Sep 17 00:00:00 2001 From: Sancho Godinho <73981314+sancho1952007@users.noreply.github.com> Date: Tue, 10 Jun 2025 00:07:05 +0530 Subject: [PATCH 13/18] Delete mac-os.png Will update with new icon --- public/images/os/mac-os.png | Bin 1465 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 public/images/os/mac-os.png diff --git a/public/images/os/mac-os.png b/public/images/os/mac-os.png deleted file mode 100644 index e57d01cf217f9f97a853a013db7e6fca851a4374..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1465 zcmZ`(YdF&j9R6?Sp5zv$q;iSSY$MDg8)HjzYc!W5x6S2*v7NakGq)xk5^_omk;@+B zehW*SsXU@`DHJ7g&AnWXvp${==fnGZpWpjF@AH27JwLXCosE<1fO#(fFwF%3 zMQUEFqnW?}``Fr80S77RuQ~;rI2Gqg6Euzd0tj>FL8L$wrQ-=$(OEH&A`10^tt#+@ zl%t6xx|4T=8Z|tW5_pZQMvtPB)gptb0DxonxZpECzz(&qD@1{XGnhY)jb(i2IbUpN4wVlsHBeDsZ~84b zY{s)lxJJ7%GsQ0U^GVf9`7lzOL$MiBedNh2L#>l7?|ktObWQn(%#{_n6|{bl<5NUl zb%o9q?ya?}0hVCN_7t4kEK-jxCFdX5nBW|_x zg|Yo2me;?i^yc_y`r*s+-%*<3EPKr;h}p-w&BVmie27{CYc$*9{EGI?m?R(a+P8`Y zsYqjRagp^LOXgUw`wnzfbmigBn-sdw*=Z*Vl6N8(w>t_@%|Tbrm9%bV<;6d~EXw$_ z=x8%^vd)DKC!OX8ke(rm#3!mEcIzEU+6oqF2}G#tQuJK4CD&<#5Kqo(PcuvwRd5*A zS-Q=3yBIfQvC~hs&db;JdEGolDI)`e#&c$Za2w*Fs;y#0;6n!wz~5olz5G?SAl!G@U$Y z=;A#b>Xc=lez$3m!I0LBvD%V^i=oF9Qd?#@Sy?%h84vk7$UV{x9p{w#s0yx|;nj8D zF%J1XAMLl8WU=Ee5(ECmW1>U$kqJAS7hv7;nnd=sN86e`O5XMCZWx(I3qF*u(! z+)|WF;y0o^_8D#y9WKUo_O-IzTfX|_iGr@c7|2S`&g?U}8wc5MBYu?UFg^xlzde0- z=tAAlxwLI;iqX&TbN~RQ;H}U^+}|bHg|?QC@+s*7GJ_OYP%hfwuobC>pQ*-*V^*bN zdoH z;?(|@`^|F5Is;qnc@Z^o8ziT7(B}(`c&Tg9*=sDsCdvaL+40TsY;V{l zdy~SWV$ymn3TM1>Rl}ZBZ}mRS>H9LfKF1-}c?y5EEZQfwT2EE{d2475H+ecX#HI0Y zPFH~K;DYr}BjIIZN<0YS&8qdTS596~nawhb>)_LZk5?T)S1v4O=(YCDtX2tLnFbw8 zqWgK%$)>*HWB~zi7#yJoGuDF{5a9?@LlaX3@+=Hy3WJ$Q+CKeHAUM=7kP`L(0i4S^ PNDu( Date: Tue, 10 Jun 2025 00:08:24 +0530 Subject: [PATCH 14/18] Updated MacOS icon The current MacOS icon highly resembles the iOS icon. MacOS is easily identifiable by its unique finder icon. Hence, uploaded a new icon to display the MacOS traffic. --- public/images/os/mac-os.png | Bin 0 -> 2332 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 public/images/os/mac-os.png diff --git a/public/images/os/mac-os.png b/public/images/os/mac-os.png new file mode 100644 index 0000000000000000000000000000000000000000..a7cd52eb071d56986197ad62e7e6400724f60b1f GIT binary patch literal 2332 zcmV+%3FG#OP)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw000QLNklH!&m?YFY`EQd?%)>FJy^=eGA=d;5KB?=#a5>YCZJ?(6^l@4u`y9#kIBecbO^ zHD_Orcjtk(-^=q|@VprgML8Srat|3B#zPKxHGXd-30~dU!PaJoI7jY#+?8v!mmA}A z8FIE`)|Y|oWR_)roSvS3g?(@xmwPrC3!nPSnW@_3)}MP7|DdUJMW#hsNlDsK7LB#o zKE>~F)yMqxocDZGgpmvR7XoQK!}{92Ns>O+>2{yExVZRb3SSn8K$>qpdTv8LvGGMS z?aybIZI%yVbr6**Wv@ZtCNEMq0X{a>MmjL)rGQUi5@(E&)&&?L(kK~Gi?u(|Y<|{* z|A7-HPV8mB9cV$;yzZC&WH_H)vIBOj{6Y%XR9T9)4hi`!P|$21Px6%iVI`{ zS5!GrMOWaY(@L*DXg2FRZk(I@1!wLHHXUu>X=;^EW=ocO5cnR$3P z9AZPoLt<%jam7i^Yp3M8HqBU)Q1(TRSL?3yZL=})>GO+=`vNbl9FVPGSFxAdS*$a>Dd2Kgh zcZnxVW-;DN`V%stQ^%}pwNt|g!WflCRSJGeHQl6d{9OT3%m@Hd8MEA^6WL}^=|sg6 z7kH?9IZjdNH^Ugy75ZVO`Qm7a7#D_{)0i>KU1Z4pEzT^#dbEx<&KOww6|K)29_e&n z*(Oo2egsdN($;}&z<}RfOAylStwMGEPYFJZ$QQRs`$08^8U{uaPiO$ru8VajpP! z4JdMC$qs%6JTm>NNZVPq14d0K$(w%-2#x zzKcXBimTTOwYAn_dS(WX{@_QDAE>V)jTw4u@R!F6zW}b;UqH&DmY*i*MN>F=%R%Hx zjDTiQwA>I8SV^lpaBZ7&I@!I|n#4W(KZSS{Q~M$!95Z7OJb!GKs{mXrUTwlm=^&$& zh#8VT`%FMHyc;z~;++QLU2eV1!MQ9=5yx~~m!XsqI9h7WrcN%+<`^JC+B+XJFxH4M zuf;A0s1{#b*VHZHQfE1~ih%R3lr<%4i#wzc>A;D5LtsT@WTgp8ACSyqgX(KbmeYXr zgOFKH@h=Lcm4F#F{!ekdlzEO@oddY2;NCzX2RfAtzY_fVvvj-z$0Sq>JXGn0^ka7i zNXvw|!7=n-aX6GBv%I9VNamv=9Fz|*ASpmBtd=p7;zaclI+NV^>C+a2?s>%8w8UZFNak$<&CwHOo#hG7A~F~EtU6pl0AqD?(+eGjeh=-FxDG@ne) zM>voV@XD-3g_&2TDIgc8NH`xT1u$yi`kZo(O5qLw1v>_cJWw29nFCT1RyCxWnNM#k z9)l5u?qD>86Lr#`qr+^ujUqj7WmThs7HLR-2}6uVBbObE>v*h^<^?$8xiW+zKo1K# znaxAgue^;L{`_sM3}g6ALS90eIOow{`V7U+Fhy@@vBY{97l}q@qAKoCC7(v4iL+C7(M%?!NA^dChFfMNX2%-j^jqg~#Vw|lS#D5_Tm~#16XoTqXhB)`uTR3{; zNxb~Rv-shUe~Rrl-HhRINXrzL7ms)9I}jc(`pw^I|IM#X?#|+#xKuzAL=hrsSi|=C z947z$JB$`z#Iiq+Zeu656S)a7Oa^%Dwzo0rE#S4+PGZpOV&5Hi4scRy%sP!hhgjW9y!rmERxImg# z=pPvdHo~np)$qQZv)HuxM$FD`p$Y1|2S%hlrT^*%Vj*dc-lCmOv?hY(EA1meK8pWI zKfB9?qo{T3idrU_w7gJw8NH4P*y^pQ*7vxVvCJ?{xPNVX_|okid~{EU?n+9b3Hm(- zt&#+zM~=2y*4oVGJ`-4a+T?@wpK^LO?`__tTzb`~M~da$sV7;J;W!odQ5p7-4EtE& zW24jOgCic{=#=YO*E4|Zkgc|V+>L+0d- zE7yTgKj)LoGVb+KGW#7iz@{l7nO zz7+)K>~du8-C@n#mI`0sfeFLF1RM{7$V6diDwT?<)@mfkn59dL&pmzY_@M{C{>76t zM;RXkfk)g^z4orpzjPmge8L2kc}6Aw5%xlP6c@iEmZ$+*bi{wq5g*>$#{KvB%#8Za zn7g=zu`b?|%%+_rPF`QUxNzj){yTrqv7r;rJn$covldkF1!b220000 Date: Mon, 9 Jun 2025 11:59:39 -0700 Subject: [PATCH 15/18] add revenue table and save --- .../migrations/13_add_revenue/migration.sql | 31 ++++++++++++++++ db/postgresql/schema.prisma | 23 ++++++++++++ src/queries/sql/events/saveEvent.ts | 15 ++++++++ src/queries/sql/events/saveRevenue.ts | 36 +++++++++++++++++++ 4 files changed, 105 insertions(+) create mode 100644 db/postgresql/migrations/13_add_revenue/migration.sql create mode 100644 src/queries/sql/events/saveRevenue.ts diff --git a/db/postgresql/migrations/13_add_revenue/migration.sql b/db/postgresql/migrations/13_add_revenue/migration.sql new file mode 100644 index 00000000..96e11661 --- /dev/null +++ b/db/postgresql/migrations/13_add_revenue/migration.sql @@ -0,0 +1,31 @@ +-- AlterTable +ALTER TABLE "segment" ADD COLUMN "type" VARCHAR(200) NOT NULL; + +-- CreateTable +CREATE TABLE "revenue" ( + "revenue_id" UUID NOT NULL, + "website_id" UUID NOT NULL, + "session_id" UUID NOT NULL, + "event_id" UUID NOT NULL, + "event_name" VARCHAR(50) NOT NULL, + "currency" VARCHAR(100) NOT NULL, + "revenue" DECIMAL(19,4), + "created_at" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "revenue_pkey" PRIMARY KEY ("revenue_id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "revenue_revenue_id_key" ON "revenue"("revenue_id"); + +-- CreateIndex +CREATE INDEX "revenue_website_id_idx" ON "revenue"("website_id"); + +-- CreateIndex +CREATE INDEX "revenue_session_id_idx" ON "revenue"("session_id"); + +-- CreateIndex +CREATE INDEX "revenue_website_id_created_at_idx" ON "revenue"("website_id", "created_at"); + +-- CreateIndex +CREATE INDEX "revenue_website_id_session_id_created_at_idx" ON "revenue"("website_id", "session_id", "created_at"); diff --git a/db/postgresql/schema.prisma b/db/postgresql/schema.prisma index a6c35908..cf824f79 100644 --- a/db/postgresql/schema.prisma +++ b/db/postgresql/schema.prisma @@ -43,6 +43,7 @@ model Session { websiteEvent WebsiteEvent[] sessionData SessionData[] + revenue Revenue[] @@index([createdAt]) @@index([websiteId]) @@ -76,6 +77,7 @@ model Website { team Team? @relation(fields: [teamId], references: [id]) eventData EventData[] report Report[] + revenue Revenue[] sessionData SessionData[] segment Segment[] @@ -236,6 +238,7 @@ model Report { model Segment { id String @id() @unique() @map("segment_id") @db.Uuid websiteId String @map("website_id") @db.Uuid + type String @db.VarChar(200) name String @db.VarChar(200) filters Json createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6) @@ -245,4 +248,24 @@ model Segment { @@index([websiteId]) @@map("segment") +} + +model Revenue { + id String @id() @unique() @map("revenue_id") @db.Uuid + websiteId String @map("website_id") @db.Uuid + sessionId String @map("session_id") @db.Uuid + eventId String @map("event_id") @db.Uuid + eventName String @map("event_name") @db.VarChar(50) + currency String @db.VarChar(100) + revenue Decimal? @db.Decimal(19, 4) + createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6) + + website Website @relation(fields: [websiteId], references: [id]) + session Session @relation(fields: [sessionId], references: [id]) + + @@index([websiteId]) + @@index([sessionId]) + @@index([websiteId, createdAt]) + @@index([websiteId, sessionId, createdAt]) + @@map("revenue") } \ No newline at end of file diff --git a/src/queries/sql/events/saveEvent.ts b/src/queries/sql/events/saveEvent.ts index 737b9b9f..92056206 100644 --- a/src/queries/sql/events/saveEvent.ts +++ b/src/queries/sql/events/saveEvent.ts @@ -5,6 +5,7 @@ import kafka from '@/lib/kafka'; import prisma from '@/lib/prisma'; import { uuid } from '@/lib/crypto'; import { saveEventData } from './saveEventData'; +import { saveRevenue } from './saveRevenue'; export interface SaveEventArgs { websiteId: string; @@ -130,6 +131,20 @@ async function relationalQuery({ eventData, createdAt, }); + + const { revenue, currency } = eventData; + + if (revenue > 0 && currency) { + await saveRevenue({ + websiteId, + sessionId, + eventId: websiteEventId, + eventName: eventName?.substring(0, EVENT_NAME_LENGTH), + currency, + revenue, + createdAt, + }); + } } } diff --git a/src/queries/sql/events/saveRevenue.ts b/src/queries/sql/events/saveRevenue.ts new file mode 100644 index 00000000..a38df83a --- /dev/null +++ b/src/queries/sql/events/saveRevenue.ts @@ -0,0 +1,36 @@ +import { uuid } from '@/lib/crypto'; +import { PRISMA, runQuery } from '@/lib/db'; +import prisma from '@/lib/prisma'; + +export interface SaveRevenueArgs { + websiteId: string; + sessionId: string; + eventId: string; + eventName: string; + currency: string; + revenue: number; + createdAt: Date; +} + +export async function saveRevenue(data: SaveRevenueArgs) { + return runQuery({ + [PRISMA]: () => relationalQuery(data), + }); +} + +async function relationalQuery(data: SaveRevenueArgs) { + const { websiteId, sessionId, eventId, eventName, currency, revenue, createdAt } = data; + + await prisma.client.revenue.create({ + data: { + id: uuid(), + websiteId, + sessionId, + eventId, + eventName, + currency, + revenue, + createdAt, + }, + }); +} From 796952698cf7e0266990a5d7b64286a15503b839 Mon Sep 17 00:00:00 2001 From: Sufyan Farea Date: Mon, 9 Jun 2025 22:34:58 +0300 Subject: [PATCH 16/18] docs: improve and update Arabic translations --- public/intl/messages/ar-SA.json | 134 ++++++++++++++++++-------------- src/lang/ar-SA.json | 104 +++++++++++++------------ 2 files changed, 129 insertions(+), 109 deletions(-) diff --git a/public/intl/messages/ar-SA.json b/public/intl/messages/ar-SA.json index b466054a..da0109c0 100644 --- a/public/intl/messages/ar-SA.json +++ b/public/intl/messages/ar-SA.json @@ -38,7 +38,7 @@ "label.add-step": [ { "type": 0, - "value": "Add step" + "value": "إضافة خطوة" } ], "label.add-website": [ @@ -77,6 +77,18 @@ "value": "تحليلات" } ], + "label.attribution": [ + { + "type": 0, + "value": "الإسناد" + } + ], + "label.attribution-description": [ + { + "type": 0, + "value": "شاهد كيف يتفاعل المستخدمون مع حملاتك التسويقية وما الذي يحفز التحويلات." + } + ], "label.average": [ { "type": 0, @@ -122,7 +134,7 @@ "label.cancel": [ { "type": 0, - "value": "ألغِ" + "value": "إلغاء" } ], "label.change-password": [ @@ -152,7 +164,7 @@ "label.compare": [ { "type": 0, - "value": "Compare" + "value": "المقارنة" } ], "label.confirm": [ @@ -170,7 +182,7 @@ "label.contains": [ { "type": 0, - "value": "يحتوي" + "value": "يحتوي على" } ], "label.continue": [ @@ -182,7 +194,7 @@ "label.count": [ { "type": 0, - "value": "Count" + "value": "العدد" } ], "label.countries": [ @@ -236,7 +248,7 @@ "label.current": [ { "type": 0, - "value": "Current" + "value": "الحالي" } ], "label.current-password": [ @@ -254,7 +266,7 @@ "label.dashboard": [ { "type": 0, - "value": "الشاشة الرئيسية" + "value": "لوحة التحكم" } ], "label.data": [ @@ -356,7 +368,7 @@ "label.does-not-contain": [ { "type": 0, - "value": "لا يحتوي" + "value": "لا يحتوي على" } ], "label.domain": [ @@ -374,7 +386,7 @@ "label.edit": [ { "type": 0, - "value": "عدّل" + "value": "تعديل" } ], "label.edit-dashboard": [ @@ -398,13 +410,13 @@ "label.end-step": [ { "type": 0, - "value": "End Step" + "value": "الخطوة الأخيرة" } ], "label.entry": [ { "type": 0, - "value": "Entry URL" + "value": "رابط الدخول" } ], "label.event": [ @@ -428,7 +440,7 @@ "label.exit": [ { "type": 0, - "value": "Exit URL" + "value": "رابط المغادرة" } ], "label.false": [ @@ -476,7 +488,7 @@ "label.first-seen": [ { "type": 0, - "value": "First seen" + "value": "أول ظهور" } ], "label.funnel": [ @@ -494,19 +506,19 @@ "label.goal": [ { "type": 0, - "value": "Goal" + "value": "الهدف" } ], "label.goals": [ { "type": 0, - "value": "Goals" + "value": "الأهداف" } ], "label.goals-description": [ { "type": 0, - "value": "Track your goals for pageviews and events." + "value": "تابع تحقق أهدافك المرتبطة بمشاهدات الصفحات والأحداث." } ], "label.greater-than": [ @@ -548,13 +560,13 @@ "label.is": [ { "type": 0, - "value": "هو" + "value": "يساوي" } ], "label.is-not": [ { "type": 0, - "value": "لم" + "value": "لا يساوي" } ], "label.is-not-set": [ @@ -584,13 +596,13 @@ "label.journey": [ { "type": 0, - "value": "Journey" + "value": "رحلة المستخدم" } ], "label.journey-description": [ { "type": 0, - "value": "Understand how users navigate through your website." + "value": "تعرّف على كيفية تنقّل المستخدمين داخل موقعك." } ], "label.language": [ @@ -642,7 +654,7 @@ "label.last-months": [ { "type": 0, - "value": "Last " + "value": "آخر " }, { "type": 1, @@ -650,13 +662,13 @@ }, { "type": 0, - "value": " months" + "value": " شهر/أشهر" } ], "label.last-seen": [ { "type": 0, - "value": "Last seen" + "value": "آخر ظهور" } ], "label.leave": [ @@ -704,7 +716,7 @@ "label.manager": [ { "type": 0, - "value": "Manager" + "value": "مدير" } ], "label.max": [ @@ -876,13 +888,13 @@ "label.path": [ { "type": 0, - "value": "Path" + "value": "المسار" } ], "label.paths": [ { "type": 0, - "value": "Paths" + "value": "المسارات" } ], "label.powered-by": [ @@ -898,19 +910,19 @@ "label.previous": [ { "type": 0, - "value": "Previous" + "value": "السابق" } ], "label.previous-period": [ { "type": 0, - "value": "Previous period" + "value": "الفترة السابقة" } ], "label.previous-year": [ { "type": 0, - "value": "Previous year" + "value": "العام السابق" } ], "label.profile": [ @@ -922,13 +934,13 @@ "label.properties": [ { "type": 0, - "value": "Properties" + "value": "الخصائص" } ], "label.property": [ { "type": 0, - "value": "Property" + "value": "الخاصية" } ], "label.queries": [ @@ -1042,19 +1054,19 @@ "label.revenue": [ { "type": 0, - "value": "Revenue" + "value": "الإيرادات" } ], "label.revenue-description": [ { "type": 0, - "value": "Look into your revenue across time." + "value": "قم بإلقاء نظرة على بيانات إيراداتك وكيفية إنفاق المستخدمين." } ], "label.revenue-property": [ { "type": 0, - "value": "Revenue Property" + "value": "خاصية الإيرادات" } ], "label.role": [ @@ -1114,7 +1126,7 @@ "label.session": [ { "type": 0, - "value": "Session" + "value": "الزيارة" } ], "label.sessions": [ @@ -1144,13 +1156,13 @@ "label.start-step": [ { "type": 0, - "value": "Start Step" + "value": "الخطوة الأولى" } ], "label.steps": [ { "type": 0, - "value": "Steps" + "value": "الخطوات" } ], "label.sum": [ @@ -1165,6 +1177,18 @@ "value": "تابلت" } ], + "label.tag": [ + { + "type": 0, + "value": "الوسم" + } + ], + "label.tags": [ + { + "type": 0, + "value": "الوسوم" + } + ], "label.team": [ { "type": 0, @@ -1180,7 +1204,7 @@ "label.team-manager": [ { "type": 0, - "value": "Team manager" + "value": "مدير الفريق" } ], "label.team-member": [ @@ -1204,7 +1228,7 @@ "label.team-view-only": [ { "type": 0, - "value": "Team view only" + "value": "عرض الفريق فقط" } ], "label.team-websites": [ @@ -1288,13 +1312,13 @@ "label.transactions": [ { "type": 0, - "value": "Transactions" + "value": "المعاملات" } ], "label.transfer": [ { "type": 0, - "value": "Transfer" + "value": "نقل" } ], "label.transfer-website": [ @@ -1330,7 +1354,7 @@ "label.uniqueCustomers": [ { "type": 0, - "value": "Unique Customers" + "value": "العملاء الفريدون" } ], "label.unknown": [ @@ -1348,19 +1372,19 @@ "label.update": [ { "type": 0, - "value": "Update" + "value": "تحديث" } ], "label.url": [ { "type": 0, - "value": "URL" + "value": "الرابط" } ], "label.urls": [ { "type": 0, - "value": "URLs" + "value": "الروابط" } ], "label.user": [ @@ -1372,7 +1396,7 @@ "label.user-property": [ { "type": 0, - "value": "User Property" + "value": "سمات المستخدم" } ], "label.username": [ @@ -1396,7 +1420,7 @@ "label.utm-description": [ { "type": 0, - "value": "Track your campaigns through UTM parameters." + "value": "تابع حملاتك التسويقية باستخدام معلمات UTM." } ], "label.value": [ @@ -1432,7 +1456,7 @@ "label.views-per-visit": [ { "type": 0, - "value": "Views per visit" + "value": "مشاهدات لكل زيارة" } ], "label.visit-duration": [ @@ -1450,7 +1474,7 @@ "label.visits": [ { "type": 0, - "value": "Visits" + "value": "الزيارات" } ], "label.website": [ @@ -1534,7 +1558,7 @@ "message.collected-data": [ { "type": 0, - "value": "Collected data" + "value": "البيانات المجمعة" } ], "message.confirm-delete": [ @@ -1754,15 +1778,7 @@ "message.share-url": [ { "type": 0, - "value": "هذا الرابط الذي تم مشاركته بشكل عام لـ " - }, - { - "type": 1, - "value": "target" - }, - { - "type": 0, - "value": "." + "value": "إحصائيات موقعك متاحة للجميع على الرابط التالي:" } ], "message.team-already-member": [ diff --git a/src/lang/ar-SA.json b/src/lang/ar-SA.json index 981fea84..9ef0527b 100644 --- a/src/lang/ar-SA.json +++ b/src/lang/ar-SA.json @@ -5,13 +5,15 @@ "label.add": "أضِف", "label.add-description": "أضِف وصف", "label.add-member": "أضِف عضو", - "label.add-step": "Add step", + "label.add-step": "إضافة خطوة", "label.add-website": "إضافة موقع", "label.admin": "مدير", "label.after": "يعد", "label.all": "الكل", "label.all-time": "كل الوقت", "label.analytics": "تحليلات", + "label.attribution": "الإسناد", + "label.attribution-description": "شاهد كيف يتفاعل المستخدمون مع حملاتك التسويقية وما الذي يحفز التحويلات.", "label.average": "المتوسط", "label.back": "للخلف", "label.before": "قبل", @@ -19,17 +21,17 @@ "label.breakdown": "التصنيف", "label.browser": "المتصفح", "label.browsers": "المتصفحات", - "label.cancel": "ألغِ", + "label.cancel": "إلغاء", "label.change-password": "تغيير كلمة المرور", "label.cities": "المدن", "label.city": "المدينة", "label.clear-all": "مسح الكل", - "label.compare": "Compare", + "label.compare": "المقارنة", "label.confirm": "تأكيد", "label.confirm-password": "تأكيد كلمة المرور", - "label.contains": "يحتوي", + "label.contains": "يحتوي على", "label.continue": "تابع", - "label.count": "Count", + "label.count": "العدد", "label.countries": "الدول", "label.country": "الدولة", "label.create": "أنشِئ", @@ -38,10 +40,10 @@ "label.create-user": "أنشِئ مستخدم", "label.created": "أُنشئت", "label.created-by": "أُنشئ من قبل", - "label.current": "Current", + "label.current": "الحالي", "label.current-password": "كلمة المرور الحالية", "label.custom-range": "فترة مخصّصة", - "label.dashboard": "الشاشة الرئيسية", + "label.dashboard": "لوحة التحكم", "label.data": "البيانات", "label.date": "التاريخ", "label.date-range": "فترة مخصّصة", @@ -58,19 +60,19 @@ "label.device": "الجهاز", "label.devices": "الأجهزة", "label.dismiss": "تجاهل", - "label.does-not-contain": "لا يحتوي", + "label.does-not-contain": "لا يحتوي على", "label.domain": "النطاق", "label.dropoff": "إنزال", - "label.edit": "عدّل", + "label.edit": "تعديل", "label.edit-dashboard": "عدّل لوحة التحكم", "label.edit-member": "عدّل العضو", "label.enable-share-url": "فعّل مشاركة الرابط", - "label.end-step": "End Step", - "label.entry": "Entry URL", + "label.end-step": "الخطوة الأخيرة", + "label.entry": "رابط الدخول", "label.event": "الحدث", "label.event-data": "تاريخ الحدث", "label.events": "الأحداث", - "label.exit": "Exit URL", + "label.exit": "رابط المغادرة", "label.false": "خطأ", "label.field": "الحقل", "label.fields": "الحقول", @@ -78,33 +80,33 @@ "label.filter-combined": "مُجمّعة", "label.filter-raw": "خام", "label.filters": "التصفيات", - "label.first-seen": "First seen", + "label.first-seen": "أول ظهور", "label.funnel": "قمع", "label.funnel-description": "فهم معدل التحويل والانقطاع عن المستخدمين.", - "label.goal": "Goal", - "label.goals": "Goals", - "label.goals-description": "Track your goals for pageviews and events.", + "label.goal": "الهدف", + "label.goals": "الأهداف", + "label.goals-description": "تابع تحقق أهدافك المرتبطة بمشاهدات الصفحات والأحداث.", "label.greater-than": "أكبَر مِن", "label.greater-than-equals": "أكبَر مِن أو يساوي", "label.host": "Host", "label.hosts": "Hosts", "label.insights": "نتائج التحليلات", "label.insights-description": "تعمق في بياناتك باستخدام الشرائح والتصفيات.", - "label.is": "هو", - "label.is-not": "لم", + "label.is": "يساوي", + "label.is-not": "لا يساوي", "label.is-not-set": "لم ضُبط", "label.is-set": "ضُبط", "label.join": "انضم", "label.join-team": "انضم للفريق", - "label.journey": "Journey", - "label.journey-description": "Understand how users navigate through your website.", + "label.journey": "رحلة المستخدم", + "label.journey-description": "تعرّف على كيفية تنقّل المستخدمين داخل موقعك.", "label.language": "اللغة", "label.languages": "اللغات", "label.laptop": "لابتوب", "label.last-days": "آخر {x} يوم/ايام", "label.last-hours": "آخر {x} ساعة", - "label.last-months": "Last {x} months", - "label.last-seen": "Last seen", + "label.last-months": "آخر {x} شهر/أشهر", + "label.last-seen": "آخر ظهور", "label.leave": "غادر", "label.leave-team": "مغادرة المجموعة", "label.less-than": "أقل مِن", @@ -112,7 +114,7 @@ "label.login": "تسجيل الدخول", "label.logout": "تسجيل الخروج", "label.manage": "التحكم", - "label.manager": "Manager", + "label.manager": "مدير", "label.max": "الحد الأقصى", "label.member": "عضو", "label.members": "الأعضاء", @@ -134,15 +136,15 @@ "label.pageTitle": "عنوان الصفحة", "label.pages": "الصفحات", "label.password": "كلمة المرور", - "label.path": "Path", - "label.paths": "Paths", + "label.path": "المسار", + "label.paths": "المسارات", "label.powered-by": "مشغل بواسطة {name}", - "label.previous": "Previous", - "label.previous-period": "Previous period", - "label.previous-year": "Previous year", + "label.previous": "السابق", + "label.previous-period": "الفترة السابقة", + "label.previous-year": "العام السابق", "label.profile": "الملف الشخصي", - "label.properties": "Properties", - "label.property": "Property", + "label.properties": "الخصائص", + "label.property": "الخاصية", "label.queries": "استعلامات", "label.query": "استعلام", "label.query-parameters": "متغيرات الرابط", @@ -161,9 +163,9 @@ "label.reset-website": "اعادة تعيين الإحصائيات", "label.retention": "الاحتفاظ", "label.retention-description": "قس مدى ثبات موقعك على الويب من خلال تتبع عدد مرات عودة المستخدمين.", - "label.revenue": "Revenue", - "label.revenue-description": "Look into your revenue across time.", - "label.revenue-property": "Revenue Property", + "label.revenue": "الإيرادات", + "label.revenue-description": "قم بإلقاء نظرة على بيانات إيراداتك وكيفية إنفاق المستخدمين.", + "label.revenue-property": "خاصية الإيرادات", "label.role": "الصلاحية", "label.run-query": "شغّل الاستعلام", "label.save": "حفظ", @@ -173,22 +175,24 @@ "label.select-date": "حدد التاريخ", "label.select-role": "حدد الدور", "label.select-website": "حدد موقع", - "label.session": "Session", + "label.session": "الزيارة", "label.sessions": "الزيارات", "label.settings": "الإعدادات", "label.share-url": "مشاركة الرابط", "label.single-day": "يوم واحد", - "label.start-step": "Start Step", - "label.steps": "Steps", + "label.start-step": "الخطوة الأولى", + "label.steps": "الخطوات", "label.sum": "المجموع", "label.tablet": "تابلت", + "label.tag": "الوسم", + "label.tags": "الوسوم", "label.team": "الفريق", "label.team-id": "معرّف الفريق", - "label.team-manager": "Team manager", + "label.team-manager": "مدير الفريق", "label.team-member": "عضو الفريق", "label.team-name": "اسم الفريق", "label.team-owner": "مدير الفريق", - "label.team-view-only": "Team view only", + "label.team-view-only": "عرض الفريق فقط", "label.team-websites": "مواقع الفريق", "label.teams": "الفرق", "label.theme": "السمة", @@ -202,34 +206,34 @@ "label.total": "الإجمالي", "label.total-records": "إجمالي السجلات", "label.tracking-code": "كود التتبع", - "label.transactions": "Transactions", - "label.transfer": "Transfer", + "label.transactions": "المعاملات", + "label.transfer": "نقل", "label.transfer-website": "انقل الموقع", "label.true": "حقيقي", "label.type": "النوع", "label.unique": "فريد", "label.unique-visitors": "زائرون فريدون", - "label.uniqueCustomers": "Unique Customers", + "label.uniqueCustomers": "العملاء الفريدون", "label.unknown": "غير معروف", "label.untitled": "بدون عنوان", - "label.update": "Update", - "label.url": "URL", - "label.urls": "URLs", + "label.update": "تحديث", + "label.url": "الرابط", + "label.urls": "الروابط", "label.user": "المستخدم", - "label.user-property": "User Property", + "label.user-property": "سمات المستخدم", "label.username": "اسم المستخدم", "label.users": "المستخدمين", "label.utm": "UTM", - "label.utm-description": "Track your campaigns through UTM parameters.", + "label.utm-description": "تابع حملاتك التسويقية باستخدام معلمات UTM.", "label.value": "القيمة", "label.view": "عرض", "label.view-details": "عرض التفاصيل", "label.view-only": "عرض فقط", "label.views": "المشاهدات", - "label.views-per-visit": "Views per visit", + "label.views-per-visit": "مشاهدات لكل زيارة", "label.visit-duration": "متوسط وقت الزيارة", "label.visitors": "الزوار", - "label.visits": "Visits", + "label.visits": "الزيارات", "label.website": "الموقع", "label.website-id": "معرّف الموقع", "label.websites": "المواقع", @@ -237,7 +241,7 @@ "label.yesterday": "الأمس", "message.action-confirmation": "اكتب {confirmation} في المربع أدناه للتأكيد.", "message.active-users": "{x} حاليا {x, plural, one {زائر واحد} other {زوار}}", - "message.collected-data": "Collected data", + "message.collected-data": "البيانات المجمعة", "message.confirm-delete": "هل أنت متأكد من حذف {target}?", "message.confirm-leave": "هل أنت متأكد من مغادرة {target}?", "message.confirm-remove": "هل انت متأكد من حذف {target}?", @@ -263,7 +267,7 @@ "message.reset-website": "لإعادة ضبط موقع الويب هذا، اكتب {confirmation} في المربع أدناه للتأكيد.", "message.reset-website-warning": "سيتم اعادة تعيين كافة الإحصائيات لهذا الموقع، لكن لن يتم تعيير كود التتبع", "message.saved": "تم الحفظ بنجاح.", - "message.share-url": "هذا الرابط الذي تم مشاركته بشكل عام لـ {target}.", + "message.share-url": "إحصائيات موقعك متاحة للجميع على الرابط التالي:", "message.team-already-member": "أنت عضو في الفريق", "message.team-not-found": "لم يتم العثور على الفريق", "message.team-websites-info": "يمكن مشاهدة الموقع من اي عضو في الفريق.", From e20db80fa1c45ac366dfd74a5dba9499dafa4f71 Mon Sep 17 00:00:00 2001 From: Sufyan Farea <33679576+sufyanfa@users.noreply.github.com> Date: Mon, 9 Jun 2025 22:54:35 +0300 Subject: [PATCH 17/18] Update src/lang/ar-SA.json Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- src/lang/ar-SA.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lang/ar-SA.json b/src/lang/ar-SA.json index 9ef0527b..cfc3d502 100644 --- a/src/lang/ar-SA.json +++ b/src/lang/ar-SA.json @@ -265,7 +265,7 @@ "message.no-websites-configured": "لم تقم بإعداد اي موقع.", "message.page-not-found": "الصفحة غير موجودة.", "message.reset-website": "لإعادة ضبط موقع الويب هذا، اكتب {confirmation} في المربع أدناه للتأكيد.", - "message.reset-website-warning": "سيتم اعادة تعيين كافة الإحصائيات لهذا الموقع، لكن لن يتم تعيير كود التتبع", + "message.reset-website-warning": "سيتم اعادة تعيين كافة الإحصائيات لهذا الموقع، لكن لن يتم تغيير كود التتبع", "message.saved": "تم الحفظ بنجاح.", "message.share-url": "إحصائيات موقعك متاحة للجميع على الرابط التالي:", "message.team-already-member": "أنت عضو في الفريق", From a4465aaa43193ad3a49b298536ae1640015a6aeb Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Tue, 10 Jun 2025 09:58:39 -0700 Subject: [PATCH 18/18] update psql revenue and attribution report to use new revenue table --- src/queries/sql/reports/getAttribution.ts | 30 +++----- src/queries/sql/reports/getRevenue.ts | 90 +++++++---------------- 2 files changed, 37 insertions(+), 83 deletions(-) diff --git a/src/queries/sql/reports/getAttribution.ts b/src/queries/sql/reports/getAttribution.ts index f224eb5c..b3a8a704 100644 --- a/src/queries/sql/reports/getAttribution.ts +++ b/src/queries/sql/reports/getAttribution.ts @@ -78,26 +78,16 @@ async function relationalQuery( group by 1),`; const revenueEventQuery = `WITH events AS ( - select - we.session_id, - max(ed.created_at) max_dt, - sum(coalesce(cast(number_value as decimal(10,2)), cast(string_value as decimal(10,2)))) value - from event_data ed - join website_event we - on we.event_id = ed.website_event_id - and we.website_id = ed.website_id - join (select website_event_id - from event_data - where website_id = {{websiteId::uuid}} - and created_at between {{startDate}} and {{endDate}} - and data_key ${like} '%currency%' - and string_value = {{currency}}) currency - on currency.website_event_id = ed.website_event_id - where ed.website_id = {{websiteId::uuid}} - and ed.created_at between {{startDate}} and {{endDate}} - and ${column} = {{conversionStep}} - and ed.data_key ${like} '%revenue%' - group by 1),`; + select + session_id, + max(created_at) max_dt, + sum(revenue) value + from revenue + where website_id = {{websiteId::uuid}} + and created_at between {{startDate}} and {{endDate}} + and ${column} = {{conversionStep}} + and currency ${like} {{currency}} + group by 1),`; function getModelQuery(model: string) { return model === 'firstClick' diff --git a/src/queries/sql/reports/getRevenue.ts b/src/queries/sql/reports/getRevenue.ts index f7996fcc..8f4855cc 100644 --- a/src/queries/sql/reports/getRevenue.ts +++ b/src/queries/sql/reports/getRevenue.ts @@ -48,22 +48,13 @@ async function relationalQuery( const chartRes = await rawQuery( ` select - we.event_name x, - ${getDateSQL('ed.created_at', unit, timezone)} t, - sum(coalesce(cast(number_value as decimal(10,2)), cast(string_value as decimal(10,2)))) y - from event_data ed - join website_event we - on we.event_id = ed.website_event_id - join (select website_event_id - from event_data - where website_id = {{websiteId::uuid}} - and created_at between {{startDate}} and {{endDate}} - and data_key ${like} '%currency%' - and string_value = {{currency}}) currency - on currency.website_event_id = ed.website_event_id - where ed.website_id = {{websiteId::uuid}} - and ed.created_at between {{startDate}} and {{endDate}} - and ed.data_key ${like} '%revenue%' + event_name x, + ${getDateSQL('created_at', unit, timezone)} t, + sum(revenue) y + from revenue + where website_id = {{websiteId::uuid}} + and created_at between {{startDate}} and {{endDate}} + and currency ${like} {{currency}} group by x, t order by t `, @@ -74,22 +65,13 @@ async function relationalQuery( ` select s.country as name, - sum(coalesce(cast(number_value as decimal(10,2)), cast(string_value as decimal(10,2)))) value - from event_data ed - join website_event we - on we.event_id = ed.website_event_id + sum(r.revenue) value + from revenue r join session s - on s.session_id = we.session_id - join (select website_event_id - from event_data - where website_id = {{websiteId::uuid}} - and created_at between {{startDate}} and {{endDate}} - and data_key ${like} '%currency%' - and string_value = {{currency}}) currency - on currency.website_event_id = ed.website_event_id - where ed.website_id = {{websiteId::uuid}} - and ed.created_at between {{startDate}} and {{endDate}} - and ed.data_key ${like} '%revenue%' + on s.session_id = r.session_id + where r.website_id = {{websiteId::uuid}} + and r.created_at between {{startDate}} and {{endDate}} + and r.currency ${like} {{currency}} group by s.country `, { websiteId, startDate, endDate, currency }, @@ -98,22 +80,13 @@ async function relationalQuery( const totalRes = await rawQuery( ` select - sum(coalesce(cast(number_value as decimal(10,2)), cast(string_value as decimal(10,2)))) as sum, + sum(revenue) as sum, count(distinct event_id) as count, count(distinct session_id) as unique_count - from event_data ed - join website_event we - on we.event_id = ed.website_event_id - join (select website_event_id - from event_data - where website_id = {{websiteId::uuid}} - and created_at between {{startDate}} and {{endDate}} - and data_key ${like} '%currency%' - and string_value = {{currency}}) currency - on currency.website_event_id = ed.website_event_id - where ed.website_id = {{websiteId::uuid}} - and ed.created_at between {{startDate}} and {{endDate}} - and ed.data_key ${like} '%revenue%' + from revenue r + where website_id = {{websiteId::uuid}} + and created_at between {{startDate}} and {{endDate}} + and currency ${like} {{currency}} `, { websiteId, startDate, endDate, currency }, ).then(result => result?.[0]); @@ -121,24 +94,15 @@ async function relationalQuery( const tableRes = await rawQuery( ` select - c.currency, - sum(coalesce(cast(number_value as decimal(10,2)), cast(string_value as decimal(10,2)))) as sum, - count(distinct ed.website_event_id) as count, - count(distinct we.session_id) as unique_count - from event_data ed - join website_event we - on we.event_id = ed.website_event_id - join (select website_event_id, string_value as currency - from event_data - where website_id = {{websiteId::uuid}} - and created_at between {{startDate}} and {{endDate}} - and data_key ${like} '%currency%') c - on c.website_event_id = ed.website_event_id - where ed.website_id = {{websiteId::uuid}} - and ed.created_at between {{startDate}} and {{endDate}} - and ed.data_key ${like} '%revenue%' - group by c.currency - order by sum desc; + currency, + sum(revenue) as sum, + count(distinct event_id) as count, + count(distinct session_id) as unique_count + from revenue r + where website_id = {{websiteId::uuid}} + and created_at between {{startDate}} and {{endDate}} + group by currency + order by sum desc `, { websiteId, startDate, endDate, unit, timezone, currency }, );