diff --git a/.github/ISSUE_TEMPLATE/1.bug_report.yml b/.github/ISSUE_TEMPLATE/1.bug_report.yml index d48567e6..2404918b 100644 --- a/.github/ISSUE_TEMPLATE/1.bug_report.yml +++ b/.github/ISSUE_TEMPLATE/1.bug_report.yml @@ -24,13 +24,13 @@ body: render: shell - type: input attributes: - label: Which Umami version are you using? + label: Which Umami version are you using? (if relevant) description: 'For example: 2.18.0, 2.15.1, 1.39.0, etc' - type: input attributes: - label: How are you deploying your application? - description: 'For example: Vercel, Railway, Docker, etc' + label: Which browser are you using? (if relevant) + description: 'For example: Chrome, Edge, Firefox, etc' - type: input attributes: - label: Which browser are you using? - description: 'For example: Chrome, Edge, Firefox, etc' + label: How are you deploying your application? (if relevant) + description: 'For example: Vercel, Railway, Docker, etc' diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 24761faf..a9509bce 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -3,18 +3,13 @@ name: Create docker images on: push: tags: - - "v*.*.*" + - 'v*.*.*' workflow_dispatch: inputs: version: - description: "Optional image version (e.g. 3.0.0, v3.0.0, or 3.0.0-beta.1)" + description: 'Optional image version (e.g. 3.0.0, v3.0.0, or 3.0.0-beta.1)' required: false - default: "" - include_latest: - description: "Include latest tag" - required: false - type: boolean - default: true + default: '' jobs: build: @@ -27,9 +22,6 @@ jobs: steps: - uses: actions/checkout@v5 - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -54,7 +46,6 @@ jobs: INPUT="${{ github.event.inputs.version }}" REF_TYPE="${{ github.ref_type }}" REF_NAME="${{ github.ref_name }}" - INCLUDE_LATEST="${{ github.event.inputs.include_latest }}" # Determine version source if [[ -n "$INPUT" ]]; then @@ -65,8 +56,7 @@ jobs: VERSION="" fi - GHCR_TAGS="" - DOCKER_TAGS="" + TAGS="" if [[ -n "$VERSION" ]]; then MAJOR=$(echo "$VERSION" | cut -d. -f1) @@ -74,54 +64,37 @@ jobs: if [[ "$VERSION" == *-* ]]; then # prerelease: only version tag - GHCR_TAGS="ghcr.io/${{ github.repository }}:$VERSION" - DOCKER_TAGS="umamisoftware/umami:$VERSION" + TAGS="$VERSION" else - # stable release: version + hierarchy - GHCR_TAGS="ghcr.io/${{ github.repository }}:$VERSION" - GHCR_TAGS="$GHCR_TAGS,ghcr.io/${{ github.repository }}:${MAJOR}.${MINOR}" - GHCR_TAGS="$GHCR_TAGS,ghcr.io/${{ github.repository }}:${MAJOR}" - GHCR_TAGS="$GHCR_TAGS,ghcr.io/${{ github.repository }}:postgresql-latest" - - DOCKER_TAGS="umamisoftware/umami:$VERSION" - DOCKER_TAGS="$DOCKER_TAGS,umamisoftware/umami:${MAJOR}.${MINOR}" - DOCKER_TAGS="$DOCKER_TAGS,umamisoftware/umami:${MAJOR}" - DOCKER_TAGS="$DOCKER_TAGS,umamisoftware/umami:postgresql-latest" - - # Add latest tag based on trigger and input - if [[ "$REF_TYPE" == "tag" ]] || [[ "$INCLUDE_LATEST" == "true" ]]; then - GHCR_TAGS="$GHCR_TAGS,ghcr.io/${{ github.repository }}:latest" - DOCKER_TAGS="$DOCKER_TAGS,umamisoftware/umami:latest" - fi + # stable release: version + hierarchy + latest + TAGS="$VERSION,${MAJOR}.${MINOR},${MAJOR},postgresql-latest,latest" fi else # Non-tag build (e.g. from main branch) - GHCR_TAGS="ghcr.io/${{ github.repository }}:${REF_NAME}" - DOCKER_TAGS="umamisoftware/umami:${REF_NAME}" + TAGS="${REF_NAME}" fi - echo "ghcr_tags=$GHCR_TAGS" >> $GITHUB_OUTPUT - echo "docker_tags=$DOCKER_TAGS" >> $GITHUB_OUTPUT - echo "Computed GHCR tags: $GHCR_TAGS" - echo "Computed Docker Hub tags: $DOCKER_TAGS" + echo "tags=$TAGS" >> $GITHUB_OUTPUT + echo "Computed tags: $TAGS" - - name: Build and push to GHCR - uses: docker/build-push-action@v5 - with: - context: . - platforms: linux/amd64,linux/arm64 - push: true - tags: ${{ steps.compute.outputs.ghcr_tags }} - cache-from: type=gha - cache-to: type=gha,mode=max + - name: Build and push Docker image + run: | + TAGS="${{ steps.compute.outputs.tags }}" - - name: Build and push to Docker Hub - if: github.repository == 'umami-software/umami' - uses: docker/build-push-action@v5 - with: - context: . - platforms: linux/amd64,linux/arm64 - push: true - tags: ${{ steps.compute.outputs.docker_tags }} - cache-from: type=gha - cache-to: type=gha,mode=max + # Set image targets conditionally + if [[ "${{ github.repository }}" == "umami-software/umami" ]]; then + IMAGES=("umamisoftware/umami" "ghcr.io/${{ github.repository }}") + else + IMAGES=("ghcr.io/${{ github.repository }}") + fi + + for IMAGE in "${IMAGES[@]}"; do + echo "Building and pushing $IMAGE with tags: $TAGS" + docker buildx build \ + --platform linux/amd64,linux/arm64 \ + --push \ + $(echo "$TAGS" | tr ',' '\n' | sed "s|^|--tag ${IMAGE}:|") \ + --cache-from type=gha \ + --cache-to type=gha,mode=max \ + . + done diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e33a9680..eee0a02d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,7 +3,7 @@ name: Node.js CI on: [push] env: - DATABASE_URL: "postgresql://user:pass@localhost:5432/dummy" + DATABASE_TYPE: postgresql SKIP_DB_CHECK: 1 jobs: @@ -11,18 +11,27 @@ jobs: if: github.repository == 'umami-software/umami' runs-on: ubuntu-latest + strategy: + matrix: + include: + - node-version: 18.18 + pnpm-version: 10 + db-type: postgresql + steps: - - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 - with: - version: 10 - run_install: false - - name: Use Node.js 18.18 - uses: actions/setup-node@v4 - with: - node-version: 18.18 - cache: "pnpm" - - run: npm install --global pnpm - - run: pnpm install - - run: pnpm test - - run: pnpm build + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 # required so that setup-node will work + with: + version: ${{ matrix.pnpm-version }} + run_install: false + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: 'pnpm' + env: + DATABASE_TYPE: ${{ matrix.db-type }} + - run: npm install --global pnpm + - run: pnpm install + - run: pnpm test + - run: pnpm build diff --git a/.gitignore b/.gitignore index de893d0f..753389d1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,46 +1,45 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -node_modules -.pnp -.pnp.js -.pnpm-store -package-lock.json - -# testing -/coverage - -# next.js -/.next -/out - -# production -/build -/public/script.js -/geo -/dist -/generated -/src/generated -pm2.yml - -# misc -.DS_Store -.idea -.yarn -*.iml -*.log -.vscode -.tool-versions - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# local env files -.env -.env.* -*.env.* - -*.dev.yml - +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +node_modules +.pnp +.pnp.js +.pnpm-store +package-lock.json + +# testing +/coverage + +# next.js +/.next +/out + +# production +/build +/public/script.js +/geo +/dist +/generated +/src/generated + +# misc +.DS_Store +.idea +.yarn +*.iml +*.log +.vscode +.tool-versions + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env +.env.* +*.env.* + +*.dev.yml + diff --git a/README.md b/README.md index cd365a68..95bb4330 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ Docker image: docker pull docker.umami.is/umami-software/umami:latest ``` -Docker compose (Runs Umami with a PostgreSQL database): +Docker compose to run Umami with a Postgres database, run: ```bash docker compose up -d diff --git a/next.config.ts b/next.config.ts index 1a4e2e0e..99dcca0d 100644 --- a/next.config.ts +++ b/next.config.ts @@ -8,7 +8,6 @@ const cloudMode = process.env.CLOUD_MODE || ''; const cloudUrl = process.env.CLOUD_URL || ''; const collectApiEndpoint = process.env.COLLECT_API_ENDPOINT || ''; const corsMaxAge = process.env.CORS_MAX_AGE || ''; -const defaultCurrency = process.env.DEFAULT_CURRENCY || ''; const defaultLocale = process.env.DEFAULT_LOCALE || ''; const forceSSL = process.env.FORCE_SSL || ''; const frameAncestors = process.env.ALLOWED_FRAME_URLS || ''; @@ -171,7 +170,6 @@ export default { cloudMode, cloudUrl, currentVersion: pkg.version, - defaultCurrency, defaultLocale, }, basePath, diff --git a/package.json b/package.json index c4c832c0..f425d9cf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "umami", - "version": "3.0.3", + "version": "3.0.2", "description": "A modern, privacy-focused alternative to Google Analytics.", "author": "Umami Software, Inc. ", "license": "MIT", @@ -102,15 +102,15 @@ "kafkajs": "^2.1.0", "lucide-react": "^0.543.0", "maxmind": "^5.0.0", - "next": "^15.5.9", + "next": "^15.5.7", "node-fetch": "^3.2.8", "npm-run-all": "^4.1.5", "papaparse": "^5.5.3", "pg": "^8.16.3", "prisma": "^7.1.0", "pure-rand": "^7.0.1", - "react": "^19.2.3", - "react-dom": "^19.2.3", + "react": "^19.2.1", + "react-dom": "^19.2.1", "react-error-boundary": "^4.0.4", "react-intl": "^7.1.14", "react-simple-maps": "^2.3.0", diff --git a/public/images/country/t1.png b/public/images/country/t1.png deleted file mode 100644 index c45ed0fe..00000000 Binary files a/public/images/country/t1.png and /dev/null differ diff --git a/public/intl/messages/zh-CN.json b/public/intl/messages/zh-CN.json index a4ad51fa..b3d2f3c0 100644 --- a/public/intl/messages/zh-CN.json +++ b/public/intl/messages/zh-CN.json @@ -5,18 +5,6 @@ "value": "访问代码" } ], - "label.account": [ - { - "type": 0, - "value": "账户" - } - ], - "label.action": [ - { - "type": 0, - "value": "行为" - } - ], "label.actions": [ { "type": 0, @@ -47,24 +35,12 @@ "value": "添加描述" } ], - "label.add-link": [ - { - "type": 0, - "value": "添加链接" - } - ], "label.add-member": [ { "type": 0, "value": "添加成员" } ], - "label.add-pixel": [ - { - "type": 0, - "value": "添加像素" - } - ], "label.add-step": [ { "type": 0, @@ -107,24 +83,12 @@ "value": "所有时间段" } ], - "label.analysis": [ - { - "type": 0, - "value": "分析" - } - ], "label.analytics": [ { "type": 0, "value": "分析" } ], - "label.application": [ - { - "type": 0, - "value": "应用" - } - ], "label.apply": [ { "type": 0, @@ -143,12 +107,6 @@ "value": "查看用户如何与您的营销互动,以及是什么促成了转化。" } ], - "label.audience": [ - { - "type": 0, - "value": "受众" - } - ], "label.average": [ { "type": 0, @@ -167,12 +125,6 @@ "value": "之前" } ], - "label.behavior": [ - { - "type": 0, - "value": "行为" - } - ], "label.boards": [ { "type": 0, @@ -221,24 +173,12 @@ "value": "修改密码" } ], - "label.channel": [ - { - "type": 0, - "value": "渠道" - } - ], "label.channels": [ { "type": 0, "value": "渠道" } ], - "label.chart": [ - { - "type": 0, - "value": "图表" - } - ], "label.cities": [ { "type": 0, @@ -263,12 +203,6 @@ "value": "队列" } ], - "label.cohorts": [ - { - "type": 0, - "value": "队列" - } - ], "label.compare": [ { "type": 0, @@ -383,12 +317,6 @@ "value": "创建者" } ], - "label.criteria": [ - { - "type": 0, - "value": "条件" - } - ], "label.currency": [ { "type": 0, @@ -491,12 +419,6 @@ "value": "台式机" } ], - "label.destination-url": [ - { - "type": 0, - "value": "目标URL" - } - ], "label.details": [ { "type": 0, @@ -533,12 +455,6 @@ "value": "唯一ID" } ], - "label.documentation": [ - { - "type": 0, - "value": "文档" - } - ], "label.does-not-contain": [ { "type": 0, @@ -563,12 +479,6 @@ "value": "域名" } ], - "label.download": [ - { - "type": 0, - "value": "下载" - } - ], "label.dropoff": [ { "type": 0, @@ -596,7 +506,7 @@ "label.email": [ { "type": 0, - "value": "邮箱" + "value": "Email" } ], "label.enable-share-url": [ @@ -617,12 +527,6 @@ "value": "入口 URL" } ], - "label.environment": [ - { - "type": 0, - "value": "环境" - } - ], "label.event": [ { "type": 0, @@ -767,12 +671,6 @@ "value": "分组" } ], - "label.growth": [ - { - "type": 0, - "value": "增长" - } - ], "label.hostname": [ { "type": 0, @@ -803,12 +701,6 @@ "value": "通过使用筛选器和划分时间段来更深入地研究数据。" } ], - "label.invalid-url": [ - { - "type": 0, - "value": "无效URL" - } - ], "label.is": [ { "type": 0, @@ -971,24 +863,12 @@ "value": "少于等于" } ], - "label.link": [ - { - "type": 0, - "value": "链接" - } - ], "label.links": [ { "type": 0, "value": "链接" } ], - "label.location": [ - { - "type": 0, - "value": "位置" - } - ], "label.login": [ { "type": 0, @@ -1140,7 +1020,7 @@ "label.online": [ { "type": 0, - "value": "在线" + "value": "Online" } ], "label.organic-search": [ @@ -1285,12 +1165,6 @@ "value": "路径" } ], - "label.pixel": [ - { - "type": 0, - "value": "像素" - } - ], "label.pixels": [ { "type": 0, @@ -1311,12 +1185,6 @@ "value": " 提供支持" } ], - "label.preferences": [ - { - "type": 0, - "value": "偏好" - } - ], "label.previous": [ { "type": 0, @@ -1341,12 +1209,6 @@ "value": "个人资料" } ], - "label.profiles": [ - { - "type": 0, - "value": "个人资料" - } - ], "label.properties": [ { "type": 0, @@ -1386,7 +1248,7 @@ "label.referral": [ { "type": 0, - "value": "来源" + "value": "Referral" } ], "label.referrer": [ @@ -1509,24 +1371,6 @@ "value": "保存" } ], - "label.save-cohort": [ - { - "type": 0, - "value": "保存为群组" - } - ], - "label.save-segment": [ - { - "type": 0, - "value": "保存为细分" - } - ], - "label.screen": [ - { - "type": 0, - "value": "屏幕" - } - ], "label.screens": [ { "type": 0, @@ -1539,18 +1383,6 @@ "value": "搜索" } ], - "label.segment": [ - { - "type": 0, - "value": "细分" - } - ], - "label.segments": [ - { - "type": 0, - "value": "细分" - } - ], "label.select": [ { "type": 0, @@ -1653,24 +1485,6 @@ "value": "总和" } ], - "label.support": [ - { - "type": 0, - "value": "支持" - } - ], - "label.switch-account": [ - { - "type": 0, - "value": "切换账户" - } - ], - "label.table": [ - { - "type": 0, - "value": "表格" - } - ], "label.tablet": [ { "type": 0, @@ -1821,12 +1635,6 @@ "value": "跟踪代码" } ], - "label.traffic": [ - { - "type": 0, - "value": "流量" - } - ], "label.transactions": [ { "type": 0, @@ -2038,7 +1846,7 @@ "message.bad-request": [ { "type": 0, - "value": "请求错误" + "value": "Bad request" } ], "message.collected-data": [ @@ -2138,7 +1946,7 @@ "message.forbidden": [ { "type": 0, - "value": "禁止访问" + "value": "Forbidden" } ], "message.go-to-settings": [ @@ -2238,13 +2046,13 @@ "message.not-found": [ { "type": 0, - "value": "未找到" + "value": "Not found" } ], "message.nothing-selected": [ { "type": 0, - "value": "未选择" + "value": "Nothing selected." } ], "message.page-not-found": [ @@ -2282,7 +2090,7 @@ "message.sever-error": [ { "type": 0, - "value": "服务器错误" + "value": "Server error" } ], "message.share-url": [ @@ -2350,7 +2158,7 @@ "message.unauthorized": [ { "type": 0, - "value": "未授权" + "value": "Unauthorized" } ], "message.user-deleted": [ diff --git a/public/iso-3166-2.json b/public/iso-3166-2.json index 2b3b5a80..347313d7 100644 --- a/public/iso-3166-2.json +++ b/public/iso-3166-2.json @@ -6,13 +6,13 @@ "AD-06": "Sant Julia de Loria", "AD-07": "Andorra la Vella", "AD-08": "Escaldes-Engordany", - "AE-AJ": "Ajman", - "AE-AZ": "Abu Dhabi", - "AE-DU": "Dubai", - "AE-FU": "Al Fujairah", - "AE-RK": "Ras al Khaimah", - "AE-SH": "Sharjah", - "AE-UQ": "Umm al Quwain", + "AE-AJ": "'Ajman", + "AE-AZ": "Abu Zaby", + "AE-DU": "Dubayy", + "AE-FU": "Al Fujayrah", + "AE-RK": "Ra's al Khaymah", + "AE-SH": "Ash Shariqah", + "AE-UQ": "Umm al Qaywayn", "AF-BAL": "Balkh", "AF-BAM": "Bamyan", "AF-BDG": "Badghis", diff --git a/src/app/(main)/links/LinkEditForm.tsx b/src/app/(main)/links/LinkEditForm.tsx index e9ad18f4..6c10c7f0 100644 --- a/src/app/(main)/links/LinkEditForm.tsx +++ b/src/app/(main)/links/LinkEditForm.tsx @@ -4,14 +4,13 @@ import { Form, FormField, FormSubmitButton, - Grid, Icon, Label, Loading, Row, TextField, } from '@umami/react-zen'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { useConfig, useLinkQuery, useMessages } from '@/components/hooks'; import { useUpdateQuery } from '@/components/hooks/queries/useUpdateQuery'; import { RefreshCw } from '@/components/icons'; @@ -43,7 +42,7 @@ export function LinkEditForm({ const { linksUrl } = useConfig(); const hostUrl = linksUrl || LINKS_URL; const { data, isLoading } = useLinkQuery(linkId); - const [defaultSlug] = useState(generateId()); + const [slug, setSlug] = useState(generateId()); const handleSubmit = async (data: any) => { await mutateAsync(data, { @@ -56,6 +55,14 @@ export function LinkEditForm({ }); }; + const handleSlug = () => { + const slug = generateId(); + + setSlug(slug); + + return slug; + }; + const checkUrl = (url: string) => { if (!isValidUrl(url)) { return formatMessage(labels.invalidUrl); @@ -63,19 +70,19 @@ export function LinkEditForm({ return true; }; + useEffect(() => { + if (data) { + setSlug(data.slug); + } + }, [data]); + if (linkId && isLoading) { return ; } return ( -
- {({ setValue, watch }) => { - const slug = watch('slug'); - + + {({ setValue }) => { return ( <> - - - - - - + + + @@ -124,6 +121,14 @@ export function LinkEditForm({ allowCopy style={{ width: '100%' }} /> + diff --git a/src/app/(main)/links/[linkId]/page.tsx b/src/app/(main)/links/[linkId]/page.tsx index 3a5f5d7e..4317ada2 100644 --- a/src/app/(main)/links/[linkId]/page.tsx +++ b/src/app/(main)/links/[linkId]/page.tsx @@ -1,14 +1,8 @@ import type { Metadata } from 'next'; -import { getLink } from '@/queries/prisma'; import { LinkPage } from './LinkPage'; export default async function ({ params }: { params: Promise<{ linkId: string }> }) { const { linkId } = await params; - const link = await getLink(linkId); - - if (!link || link?.deletedAt) { - return null; - } return ; } diff --git a/src/app/(main)/pixels/[pixelId]/page.tsx b/src/app/(main)/pixels/[pixelId]/page.tsx index e174c195..d1db92f3 100644 --- a/src/app/(main)/pixels/[pixelId]/page.tsx +++ b/src/app/(main)/pixels/[pixelId]/page.tsx @@ -1,14 +1,8 @@ import type { Metadata } from 'next'; -import { getPixel } from '@/queries/prisma'; import { PixelPage } from './PixelPage'; export default async function ({ params }: { params: { pixelId: string } }) { const { pixelId } = await params; - const pixel = await getPixel(pixelId); - - if (!pixel || pixel?.deletedAt) { - return null; - } return ; } diff --git a/src/app/(main)/settings/preferences/PreferenceSettings.tsx b/src/app/(main)/settings/preferences/PreferenceSettings.tsx index cc2d1b62..a2890ce9 100644 --- a/src/app/(main)/settings/preferences/PreferenceSettings.tsx +++ b/src/app/(main)/settings/preferences/PreferenceSettings.tsx @@ -4,7 +4,6 @@ import { DateRangeSetting } from './DateRangeSetting'; import { LanguageSetting } from './LanguageSetting'; import { ThemeSetting } from './ThemeSetting'; import { TimezoneSetting } from './TimezoneSetting'; -import { VersionSetting } from './VersionSetting'; export function PreferenceSettings() { const { user } = useLoginQuery(); @@ -32,10 +31,6 @@ export function PreferenceSettings() { - - - - ); } diff --git a/src/app/(main)/settings/preferences/VersionSetting.tsx b/src/app/(main)/settings/preferences/VersionSetting.tsx deleted file mode 100644 index afca1de6..00000000 --- a/src/app/(main)/settings/preferences/VersionSetting.tsx +++ /dev/null @@ -1,8 +0,0 @@ -'use client'; - -import { Text } from '@umami/react-zen'; -import { CURRENT_VERSION } from '@/lib/constants'; - -export function VersionSetting() { - return {CURRENT_VERSION}; -} diff --git a/src/app/(main)/websites/[websiteId]/(reports)/revenue/Revenue.tsx b/src/app/(main)/websites/[websiteId]/(reports)/revenue/Revenue.tsx index faee8b9a..0e782a16 100644 --- a/src/app/(main)/websites/[websiteId]/(reports)/revenue/Revenue.tsx +++ b/src/app/(main)/websites/[websiteId]/(reports)/revenue/Revenue.tsx @@ -12,10 +12,9 @@ import { ListTable } from '@/components/metrics/ListTable'; import { MetricCard } from '@/components/metrics/MetricCard'; import { MetricsBar } from '@/components/metrics/MetricsBar'; import { renderDateLabels } from '@/lib/charts'; -import { CHART_COLORS, CURRENCY_CONFIG, DEFAULT_CURRENCY } from '@/lib/constants'; +import { CHART_COLORS } from '@/lib/constants'; import { generateTimeSeries } from '@/lib/date'; import { formatLongCurrency, formatLongNumber } from '@/lib/format'; -import { getItem, setItem } from '@/lib/storage'; export interface RevenueProps { websiteId: string; @@ -25,15 +24,7 @@ export interface RevenueProps { } export function Revenue({ websiteId, startDate, endDate, unit }: RevenueProps) { - const [currency, setCurrency] = useState( - getItem(CURRENCY_CONFIG) || process.env.defaultCurrency || DEFAULT_CURRENCY, - ); - - const handleCurrencyChange = (value: string) => { - setCurrency(value); - setItem(CURRENCY_CONFIG, value); - }; - + const [currency, setCurrency] = useState('USD'); const { formatMessage, labels } = useMessages(); const { locale, dateLocale } = useLocale(); const { countryNames } = useCountryNames(locale); @@ -116,7 +107,7 @@ export function Revenue({ websiteId, startDate, endDate, unit }: RevenueProps) { return ( - + {data && ( diff --git a/src/app/(main)/websites/[websiteId]/layout.tsx b/src/app/(main)/websites/[websiteId]/layout.tsx index b12ff950..67595e9d 100644 --- a/src/app/(main)/websites/[websiteId]/layout.tsx +++ b/src/app/(main)/websites/[websiteId]/layout.tsx @@ -1,6 +1,5 @@ import type { Metadata } from 'next'; import { WebsiteLayout } from '@/app/(main)/websites/[websiteId]/WebsiteLayout'; -import { getWebsite } from '@/queries/prisma'; export default async function ({ children, @@ -10,11 +9,6 @@ export default async function ({ params: Promise<{ websiteId: string }>; }) { const { websiteId } = await params; - const website = await getWebsite(websiteId); - - if (!website || website?.deletedAt) { - return null; - } return {children}; } diff --git a/src/app/api/config/route.ts b/src/app/api/config/route.ts index 101a1224..4e40caa4 100644 --- a/src/app/api/config/route.ts +++ b/src/app/api/config/route.ts @@ -17,6 +17,5 @@ export async function GET(request: Request) { telemetryDisabled: !!process.env.DISABLE_TELEMETRY, trackerScriptName: process.env.TRACKER_SCRIPT_NAME, updatesDisabled: !!process.env.DISABLE_UPDATES, - currentVersion: !!process.env.currentVersion, }); } diff --git a/src/app/api/reports/attribution/route.ts b/src/app/api/reports/attribution/route.ts index ab9b1ddb..bd7d86dc 100644 --- a/src/app/api/reports/attribution/route.ts +++ b/src/app/api/reports/attribution/route.ts @@ -17,8 +17,8 @@ export async function POST(request: Request) { return unauthorized(); } - const parameters = await setWebsiteDate(websiteId, auth.user.id, body.parameters); - const filters = await getQueryFilters(body.filters, websiteId, auth.user?.id); + const parameters = await setWebsiteDate(websiteId, body.parameters); + const filters = await getQueryFilters(body.filters, websiteId); const data = await getAttribution(websiteId, parameters as AttributionParameters, filters); diff --git a/src/app/api/reports/breakdown/route.ts b/src/app/api/reports/breakdown/route.ts index a06636c7..3c593145 100644 --- a/src/app/api/reports/breakdown/route.ts +++ b/src/app/api/reports/breakdown/route.ts @@ -17,8 +17,8 @@ export async function POST(request: Request) { return unauthorized(); } - const parameters = await setWebsiteDate(websiteId, auth.user.id, body.parameters); - const filters = await getQueryFilters(body.filters, websiteId, auth.user?.id); + const parameters = await setWebsiteDate(websiteId, body.parameters); + const filters = await getQueryFilters(body.filters, websiteId); const data = await getBreakdown(websiteId, parameters as BreakdownParameters, filters); diff --git a/src/app/api/reports/funnel/route.ts b/src/app/api/reports/funnel/route.ts index f6e21029..c13f6f1c 100644 --- a/src/app/api/reports/funnel/route.ts +++ b/src/app/api/reports/funnel/route.ts @@ -17,8 +17,8 @@ export async function POST(request: Request) { return unauthorized(); } - const parameters = await setWebsiteDate(websiteId, auth.user.id, body.parameters); - const filters = await getQueryFilters(body.filters, websiteId, auth.user?.id); + const parameters = await setWebsiteDate(websiteId, body.parameters); + const filters = await getQueryFilters(body.filters, websiteId); const data = await getFunnel(websiteId, parameters as FunnelParameters, filters); diff --git a/src/app/api/reports/goal/route.ts b/src/app/api/reports/goal/route.ts index db2aabce..3bd0415d 100644 --- a/src/app/api/reports/goal/route.ts +++ b/src/app/api/reports/goal/route.ts @@ -17,8 +17,8 @@ export async function POST(request: Request) { return unauthorized(); } - const parameters = await setWebsiteDate(websiteId, auth.user.id, body.parameters); - const filters = await getQueryFilters(body.filters, websiteId, auth.user?.id); + const parameters = await setWebsiteDate(websiteId, body.parameters); + const filters = await getQueryFilters(body.filters, websiteId); const data = await getGoal(websiteId, parameters as GoalParameters, filters); diff --git a/src/app/api/reports/retention/route.ts b/src/app/api/reports/retention/route.ts index 5adf7bb8..d1a7d698 100644 --- a/src/app/api/reports/retention/route.ts +++ b/src/app/api/reports/retention/route.ts @@ -17,8 +17,8 @@ export async function POST(request: Request) { return unauthorized(); } - const filters = await getQueryFilters(body.filters, websiteId, auth.user?.id); - const parameters = await setWebsiteDate(websiteId, auth.user.id, body.parameters); + const filters = await getQueryFilters(body.filters, websiteId); + const parameters = await setWebsiteDate(websiteId, body.parameters); const data = await getRetention(websiteId, parameters as RetentionParameters, filters); diff --git a/src/app/api/reports/revenue/route.ts b/src/app/api/reports/revenue/route.ts index f4146b96..6a556612 100644 --- a/src/app/api/reports/revenue/route.ts +++ b/src/app/api/reports/revenue/route.ts @@ -17,8 +17,8 @@ export async function POST(request: Request) { return unauthorized(); } - const parameters = await setWebsiteDate(websiteId, auth.user.id, body.parameters); - const filters = await getQueryFilters(body.filters, websiteId, auth.user?.id); + const parameters = await setWebsiteDate(websiteId, body.parameters); + const filters = await getQueryFilters(body.filters, websiteId); const data = await getRevenue(websiteId, parameters as RevenuParameters, filters); diff --git a/src/app/api/reports/utm/route.ts b/src/app/api/reports/utm/route.ts index d4af4da0..577fdab7 100644 --- a/src/app/api/reports/utm/route.ts +++ b/src/app/api/reports/utm/route.ts @@ -18,8 +18,8 @@ export async function POST(request: Request) { return unauthorized(); } - const filters = await getQueryFilters(body.filters, websiteId, auth.user?.id); - const parameters = await setWebsiteDate(websiteId, auth.user.id, body.parameters); + const filters = await getQueryFilters(body.filters, websiteId); + const parameters = await setWebsiteDate(websiteId, body.parameters); const data = { utm_source: [], diff --git a/src/app/api/websites/[websiteId]/event-data/events/route.ts b/src/app/api/websites/[websiteId]/event-data/events/route.ts index 444afa27..eb6ee6ed 100644 --- a/src/app/api/websites/[websiteId]/event-data/events/route.ts +++ b/src/app/api/websites/[websiteId]/event-data/events/route.ts @@ -27,7 +27,7 @@ export async function GET( return unauthorized(); } - const filters = await getQueryFilters(query, websiteId, auth.user?.id); + const filters = await getQueryFilters(query, websiteId); const data = await getEventDataEvents(websiteId, { ...filters, diff --git a/src/app/api/websites/[websiteId]/event-data/fields/route.ts b/src/app/api/websites/[websiteId]/event-data/fields/route.ts index e034d937..bce6a977 100644 --- a/src/app/api/websites/[websiteId]/event-data/fields/route.ts +++ b/src/app/api/websites/[websiteId]/event-data/fields/route.ts @@ -27,7 +27,7 @@ export async function GET( return unauthorized(); } - const filters = await getQueryFilters(query, websiteId, auth.user?.id); + const filters = await getQueryFilters(query, websiteId); const data = await getEventDataFields(websiteId, filters); diff --git a/src/app/api/websites/[websiteId]/event-data/properties/route.ts b/src/app/api/websites/[websiteId]/event-data/properties/route.ts index 29719fb2..52d15cfb 100644 --- a/src/app/api/websites/[websiteId]/event-data/properties/route.ts +++ b/src/app/api/websites/[websiteId]/event-data/properties/route.ts @@ -27,7 +27,7 @@ export async function GET( return unauthorized(); } - const filters = await getQueryFilters(query, websiteId, auth.user?.id); + const filters = await getQueryFilters(query, websiteId); const data = await getEventDataProperties(websiteId, filters); diff --git a/src/app/api/websites/[websiteId]/event-data/stats/route.ts b/src/app/api/websites/[websiteId]/event-data/stats/route.ts index 1d1e76df..042e989a 100644 --- a/src/app/api/websites/[websiteId]/event-data/stats/route.ts +++ b/src/app/api/websites/[websiteId]/event-data/stats/route.ts @@ -27,7 +27,7 @@ export async function GET( return unauthorized(); } - const filters = await getQueryFilters(query, websiteId, auth.user?.id); + const filters = await getQueryFilters(query, websiteId); const data = await getEventDataStats(websiteId, filters); diff --git a/src/app/api/websites/[websiteId]/event-data/values/route.ts b/src/app/api/websites/[websiteId]/event-data/values/route.ts index ed01fb2b..12e8f2dc 100644 --- a/src/app/api/websites/[websiteId]/event-data/values/route.ts +++ b/src/app/api/websites/[websiteId]/event-data/values/route.ts @@ -30,7 +30,7 @@ export async function GET( } const { propertyName } = query; - const filters = await getQueryFilters(query, websiteId, auth.user?.id); + const filters = await getQueryFilters(query, websiteId); const data = await getEventDataValues(websiteId, { ...filters, diff --git a/src/app/api/websites/[websiteId]/events/route.ts b/src/app/api/websites/[websiteId]/events/route.ts index dfabb87f..74ec3ece 100644 --- a/src/app/api/websites/[websiteId]/events/route.ts +++ b/src/app/api/websites/[websiteId]/events/route.ts @@ -29,7 +29,7 @@ export async function GET( return unauthorized(); } - const filters = await getQueryFilters(query, websiteId, auth.user?.id); + const filters = await getQueryFilters(query, websiteId); const data = await getWebsiteEvents(websiteId, filters); diff --git a/src/app/api/websites/[websiteId]/events/series/route.ts b/src/app/api/websites/[websiteId]/events/series/route.ts index d5b92559..977e9c81 100644 --- a/src/app/api/websites/[websiteId]/events/series/route.ts +++ b/src/app/api/websites/[websiteId]/events/series/route.ts @@ -29,7 +29,7 @@ export async function GET( return unauthorized(); } - const filters = await getQueryFilters(query, websiteId, auth.user?.id); + const filters = await getQueryFilters(query, websiteId); const data = await getEventStats(websiteId, filters); diff --git a/src/app/api/websites/[websiteId]/export/route.ts b/src/app/api/websites/[websiteId]/export/route.ts index f9749f34..eec81c6d 100644 --- a/src/app/api/websites/[websiteId]/export/route.ts +++ b/src/app/api/websites/[websiteId]/export/route.ts @@ -28,7 +28,7 @@ export async function GET( return unauthorized(); } - const filters = await getQueryFilters(query, websiteId, auth.user?.id); + const filters = await getQueryFilters(query, websiteId); const [events, pages, referrers, browsers, os, devices, countries] = await Promise.all([ getEventMetrics(websiteId, { type: 'event' }, filters), diff --git a/src/app/api/websites/[websiteId]/metrics/expanded/route.ts b/src/app/api/websites/[websiteId]/metrics/expanded/route.ts index 7e6fbbfd..d52c1773 100644 --- a/src/app/api/websites/[websiteId]/metrics/expanded/route.ts +++ b/src/app/api/websites/[websiteId]/metrics/expanded/route.ts @@ -37,7 +37,7 @@ export async function GET( } const { type, limit, offset, search } = query; - const filters = await getQueryFilters(query, websiteId, auth.user?.id); + const filters = await getQueryFilters(query, websiteId); if (search) { filters[type] = `c.${search}`; diff --git a/src/app/api/websites/[websiteId]/metrics/route.ts b/src/app/api/websites/[websiteId]/metrics/route.ts index 2d0e6a60..12784adb 100644 --- a/src/app/api/websites/[websiteId]/metrics/route.ts +++ b/src/app/api/websites/[websiteId]/metrics/route.ts @@ -37,7 +37,7 @@ export async function GET( } const { type, limit, offset, search } = query; - const filters = await getQueryFilters(query, websiteId, auth.user?.id); + const filters = await getQueryFilters(query, websiteId); if (search) { filters[type] = `c.${search}`; diff --git a/src/app/api/websites/[websiteId]/pageviews/route.ts b/src/app/api/websites/[websiteId]/pageviews/route.ts index dc921bc0..af59bce4 100644 --- a/src/app/api/websites/[websiteId]/pageviews/route.ts +++ b/src/app/api/websites/[websiteId]/pageviews/route.ts @@ -27,7 +27,7 @@ export async function GET( return unauthorized(); } - const filters = await getQueryFilters(query, websiteId, auth.user?.id); + const filters = await getQueryFilters(query, websiteId); const [pageviews, sessions] = await Promise.all([ getPageviewStats(websiteId, filters), diff --git a/src/app/api/websites/[websiteId]/session-data/properties/route.ts b/src/app/api/websites/[websiteId]/session-data/properties/route.ts index a0aed73c..2d8db153 100644 --- a/src/app/api/websites/[websiteId]/session-data/properties/route.ts +++ b/src/app/api/websites/[websiteId]/session-data/properties/route.ts @@ -27,7 +27,7 @@ export async function GET( return unauthorized(); } - const filters = await getQueryFilters(query, websiteId, auth.user?.id); + const filters = await getQueryFilters(query, websiteId); const data = await getSessionDataProperties(websiteId, filters); diff --git a/src/app/api/websites/[websiteId]/session-data/values/route.ts b/src/app/api/websites/[websiteId]/session-data/values/route.ts index db710654..7d06870a 100644 --- a/src/app/api/websites/[websiteId]/session-data/values/route.ts +++ b/src/app/api/websites/[websiteId]/session-data/values/route.ts @@ -29,7 +29,7 @@ export async function GET( } const { propertyName } = query; - const filters = await getQueryFilters(query, websiteId, auth.user?.id); + const filters = await getQueryFilters(query, websiteId); const data = await getSessionDataValues(websiteId, { ...filters, diff --git a/src/app/api/websites/[websiteId]/sessions/[sessionId]/activity/route.ts b/src/app/api/websites/[websiteId]/sessions/[sessionId]/activity/route.ts index 3e70bea0..41b766d0 100644 --- a/src/app/api/websites/[websiteId]/sessions/[sessionId]/activity/route.ts +++ b/src/app/api/websites/[websiteId]/sessions/[sessionId]/activity/route.ts @@ -25,7 +25,7 @@ export async function GET( return unauthorized(); } - const filters = await getQueryFilters(query, websiteId, auth.user?.id); + const filters = await getQueryFilters(query, websiteId); const data = await getSessionActivity(websiteId, sessionId, filters); diff --git a/src/app/api/websites/[websiteId]/sessions/route.ts b/src/app/api/websites/[websiteId]/sessions/route.ts index f344476c..ed4757a1 100644 --- a/src/app/api/websites/[websiteId]/sessions/route.ts +++ b/src/app/api/websites/[websiteId]/sessions/route.ts @@ -28,7 +28,7 @@ export async function GET( return unauthorized(); } - const filters = await getQueryFilters(query, websiteId, auth.user?.id); + const filters = await getQueryFilters(query, websiteId); const data = await getWebsiteSessions(websiteId, filters); diff --git a/src/app/api/websites/[websiteId]/sessions/stats/route.ts b/src/app/api/websites/[websiteId]/sessions/stats/route.ts index 74b4e5e8..459830ed 100644 --- a/src/app/api/websites/[websiteId]/sessions/stats/route.ts +++ b/src/app/api/websites/[websiteId]/sessions/stats/route.ts @@ -27,7 +27,7 @@ export async function GET( return unauthorized(); } - const filters = await getQueryFilters(query, websiteId, auth.user?.id); + const filters = await getQueryFilters(query, websiteId); const metrics = await getWebsiteSessionStats(websiteId, filters); diff --git a/src/app/api/websites/[websiteId]/sessions/weekly/route.ts b/src/app/api/websites/[websiteId]/sessions/weekly/route.ts index f6dfd206..b9ccf3ef 100644 --- a/src/app/api/websites/[websiteId]/sessions/weekly/route.ts +++ b/src/app/api/websites/[websiteId]/sessions/weekly/route.ts @@ -28,7 +28,7 @@ export async function GET( return unauthorized(); } - const filters = await getQueryFilters(query, websiteId, auth.user?.id); + const filters = await getQueryFilters(query, websiteId); const data = await getWeeklyTraffic(websiteId, filters); diff --git a/src/app/api/websites/[websiteId]/stats/route.ts b/src/app/api/websites/[websiteId]/stats/route.ts index 2bf862cd..07c8b969 100644 --- a/src/app/api/websites/[websiteId]/stats/route.ts +++ b/src/app/api/websites/[websiteId]/stats/route.ts @@ -27,7 +27,7 @@ export async function GET( return unauthorized(); } - const filters = await getQueryFilters(query, websiteId, auth.user?.id); + const filters = await getQueryFilters(query, websiteId); const data = await getWebsiteStats(websiteId, filters); diff --git a/src/app/api/websites/[websiteId]/values/route.ts b/src/app/api/websites/[websiteId]/values/route.ts index c2c95b37..172325e3 100644 --- a/src/app/api/websites/[websiteId]/values/route.ts +++ b/src/app/api/websites/[websiteId]/values/route.ts @@ -42,7 +42,7 @@ export async function GET( value: segment.name, })); } else { - const filters = await getQueryFilters(query, websiteId, auth.user?.id); + const filters = await getQueryFilters(query, websiteId); values = await getValues(websiteId, FILTER_COLUMNS[type], filters); } diff --git a/src/app/api/websites/route.ts b/src/app/api/websites/route.ts index dd8e0ffd..e2b26c10 100644 --- a/src/app/api/websites/route.ts +++ b/src/app/api/websites/route.ts @@ -1,6 +1,6 @@ import { z } from 'zod'; import { uuid } from '@/lib/crypto'; -import { fetchAccount } from '@/lib/load'; +import redis from '@/lib/redis'; import { getQueryFilters, parseRequest } from '@/lib/request'; import { json, unauthorized } from '@/lib/response'; import { pagingParams, searchParams } from '@/lib/schema'; @@ -52,7 +52,7 @@ export async function POST(request: Request) { const { id, name, domain, shareId, teamId } = body; if (process.env.CLOUD_MODE && !teamId) { - const account = await fetchAccount(auth.user.id); + const account = await redis.client.get(`account:${auth.user.id}`); if (!account?.hasSubscription) { const count = await getWebsiteCount(auth.user.id); diff --git a/src/components/hooks/queries/useEventDataValuesQuery.ts b/src/components/hooks/queries/useEventDataValuesQuery.ts index db2a2d7e..6529e142 100644 --- a/src/components/hooks/queries/useEventDataValuesQuery.ts +++ b/src/components/hooks/queries/useEventDataValuesQuery.ts @@ -16,7 +16,7 @@ export function useEventDataValuesQuery( return useQuery({ queryKey: [ 'websites:event-data:values', - { websiteId, startAt, endAt, unit, timezone, ...filters, event, propertyName }, + { websiteId, event, propertyName, startAt, endAt, unit, timezone, ...filters }, ], queryFn: () => get(`/websites/${websiteId}/event-data/values`, { diff --git a/src/components/input/WebsiteSelect.tsx b/src/components/input/WebsiteSelect.tsx index 04c773a7..8d81eb9a 100644 --- a/src/components/input/WebsiteSelect.tsx +++ b/src/components/input/WebsiteSelect.tsx @@ -65,7 +65,7 @@ export function WebsiteSelect({ renderValue={renderValue} listProps={{ renderEmptyState: () => , - style: { maxHeight: 'calc(42vh - 65px)' }, + style: { maxHeight: '400px' }, }} > {({ id, name }: any) => {name}} diff --git a/src/components/messages.ts b/src/components/messages.ts index 712495d8..0438c06e 100644 --- a/src/components/messages.ts +++ b/src/components/messages.ts @@ -351,7 +351,6 @@ export const labels = defineMessages({ growth: { id: 'label.growth', defaultMessage: 'Growth' }, account: { id: 'label.account', defaultMessage: 'Account' }, application: { id: 'label.application', defaultMessage: 'Application' }, - version: { id: 'label.version', defaultMessage: 'Version' }, saveSegment: { id: 'label.save-segment', defaultMessage: 'Save as segment' }, saveCohort: { id: 'label.save-cohort', defaultMessage: 'Save as cohort' }, analysis: { id: 'label.analysis', defaultMessage: 'Analysis' }, diff --git a/src/lang/bg-BG.json b/src/lang/bg-BG.json index 50099032..4b0effc8 100644 --- a/src/lang/bg-BG.json +++ b/src/lang/bg-BG.json @@ -93,7 +93,7 @@ "label.event-name": "Име на събитие", "label.events": "Събития", "label.exists": "Съществува", - "label.exit": "URL за изход", + "label.exit": "Exit URL", "label.false": "Грешно", "label.field": "Поле", "label.fields": "Полета", @@ -135,7 +135,7 @@ "label.last-days": "Последните {x} дни", "label.last-hours": "Последните {x} часа", "label.last-months": "Последните {x} месеца", - "label.last-seen": "Последно видяно", + "label.last-seen": "Last seen", "label.leave": "Напусни", "label.leave-team": "Напусни екип", "label.less-than": "По-малко от", @@ -161,7 +161,7 @@ "label.none": "Няма", "label.number-of-records": "{x} {x, plural, one {един} other {други}}", "label.ok": "Добре", - "label.online": "Онлайн", + "label.online": "Online", "label.organic-search": "Органично търсене", "label.organic-shopping": "Органично пазаруване", "label.organic-social": "Органични социални мрежи", @@ -185,9 +185,9 @@ "label.paths": "Пътища", "label.pixels": "Пиксели", "label.powered-by": "Поддържано от {name}", - "label.previous": "Предишен", - "label.previous-period": "Предишен период", - "label.previous-year": "Предишна година", + "label.previous": "Previous", + "label.previous-period": "Previous period", + "label.previous-year": "Previous year", "label.profile": "Профил", "label.properties": "Свойства", "label.property": "Свойство", @@ -211,8 +211,8 @@ "label.reset-website": "Нулирай уебсайт", "label.retention": "Привързване", "label.retention-description": "Измерете привързаността към вашия уебсайт, като проследявате колко често потребителите се връщат.", - "label.revenue": "Приходи", - "label.revenue-description": "Прегледайте приходите си във времето.", + "label.revenue": "Revenue", + "label.revenue-description": "Look into your revenue across time.", "label.role": "Роля", "label.run-query": "Изпълни запитване", "label.save": "Запази", @@ -260,14 +260,14 @@ "label.total": "Общо", "label.total-records": "Общо записи", "label.tracking-code": "Код за проследяване", - "label.transactions": "Транзакции", + "label.transactions": "Transactions", "label.transfer": "Прехвърли", "label.transfer-website": "Прехвърляне на уебсайт", "label.true": "Вярно", "label.type": "Вид", "label.unique": "Уникален", "label.unique-visitors": "Уникални посетители", - "label.uniqueCustomers": "Уникални клиенти", + "label.uniqueCustomers": "Unique Customers", "label.unknown": "Неизвестен", "label.untitled": "Без заглавие", "label.update": "Актуализирай", @@ -282,7 +282,7 @@ "label.view-only": "Само за преглед", "label.views": "Прегледи", "label.views-per-visit": "Прегледи на посещение", - "label.visit-duration": "Продължителност на посещение", + "label.visit-duration": "Visit duration", "label.visitors": "Посетители", "label.visits": "Посещения", "label.website": "Уебсайт", @@ -292,8 +292,8 @@ "label.yesterday": "Вчера", "message.action-confirmation": "Въведете {confirmation} в полето по-долу, за да потвърдите.", "message.active-users": "{x} {x, plural, one {активен един} other {активни други}}", - "message.bad-request": "Невалидна заявка", - "message.collected-data": "Събрани данни", + "message.bad-request": "Bad request", + "message.collected-data": "Collected data", "message.confirm-delete": "Сигурни ли сте, че искате да изтриете {target}?", "message.confirm-leave": "Сигурни ли сте, че искате да напуснете {target}?", "message.confirm-remove": "Сигурни ли сте, че искате да премахнете {target}?", @@ -302,7 +302,7 @@ "message.delete-website-warning": "Всички данни за уебсайта ще бъдат изтрити.", "message.error": "Възникна грешка.", "message.event-log": "{event} на {url}", - "message.forbidden": "Забранено", + "message.forbidden": "Forbidden", "message.go-to-settings": "Отидете в настройките", "message.incorrect-username-password": "Неправилно потребителско име и/или парола.", "message.invalid-domain": "Невалиден домейн. Не включвайте http/https.", @@ -316,13 +316,13 @@ "message.no-teams": "Няма създадени екипи.", "message.no-users": "Няма потребители.", "message.no-websites-configured": "Нямате конфигурирани уебсайтове.", - "message.not-found": "Не е намерено", - "message.nothing-selected": "Няма избрано.", + "message.not-found": "Not found", + "message.nothing-selected": "Nothing selected.", "message.page-not-found": "Страницата не е намерена", "message.reset-website": "За да нулирате този уебсайт, въведете {confirmation} в полето по-долу, за да потвърдите.", "message.reset-website-warning": "Всички статистически данни за този уебсайт ще бъдат изтрити, но вашите настройки ще останат непроменени.", "message.saved": "Запазено.", - "message.sever-error": "Сървърна грешка", + "message.sever-error": "Server error", "message.share-url": "Статистиката за вашия уебсайт е публично достъпна на следния URL адрес:", "message.team-already-member": "Вече сте член на екипа.", "message.team-not-found": "Екипът не е намерен.", @@ -332,7 +332,7 @@ "message.transfer-user-website-to-team": "Изберете екипът на който да бъде прехвърлен уебсайта.", "message.transfer-website": "Прехвърли собствеността на уебсайта към вашия акаунт или към друг екип.", "message.triggered-event": "Активирано събитие", - "message.unauthorized": "Неоторизиран достъп", + "message.unauthorized": "Unauthorized", "message.user-deleted": "Потребителят е изтрит.", "message.viewed-page": "Страницата е видяна", "message.visitor-log": "Посетител от {country}, използващ {browser} на {os} {device}" diff --git a/src/lang/ja-JP.json b/src/lang/ja-JP.json index 20b48f40..7d2bf403 100644 --- a/src/lang/ja-JP.json +++ b/src/lang/ja-JP.json @@ -23,7 +23,7 @@ "label.behavior": "行動", "label.boards": "ボード", "label.bounce-rate": "直帰率", - "label.breakdown": "内訳", + "label.breakdown": "故障", "label.browser": "ブラウザ", "label.browsers": "ブラウザ", "label.campaigns": "キャンペーン", diff --git a/src/lang/zh-CN.json b/src/lang/zh-CN.json index 5490df42..c6f01dd5 100644 --- a/src/lang/zh-CN.json +++ b/src/lang/zh-CN.json @@ -1,15 +1,11 @@ { "label.access-code": "访问代码", - "label.account": "账户", - "label.action": "行为", "label.actions": "用户行为", "label.activity": "活动日志", "label.add": "添加", "label.add-board": "添加看板", "label.add-description": "添加描述", - "label.add-link": "添加链接", "label.add-member": "添加成员", - "label.add-pixel": "添加像素", "label.add-step": "添加步骤", "label.add-website": "添加网站", "label.admin": "管理员", @@ -17,13 +13,10 @@ "label.after": "之后", "label.all": "所有", "label.all-time": "所有时间段", - "label.analysis": "分析", "label.analytics": "分析", - "label.application": "应用", "label.apply": "应用", "label.attribution": "归因", "label.attribution-description": "查看用户如何与您的营销互动,以及是什么促成了转化。", - "label.audience": "受众", "label.average": "平均", "label.back": "返回", "label.before": "之前", @@ -36,14 +29,11 @@ "label.campaigns": "活动", "label.cancel": "取消", "label.change-password": "修改密码", - "label.channel": "渠道", "label.channels": "渠道", - "label.chart": "图表", "label.cities": "市/县", "label.city": "市/县", "label.clear-all": "清除全部", "label.cohort": "队列", - "label.cohorts": "队列", "label.compare": "比较", "label.compare-dates": "比较日期", "label.confirm": "确认", @@ -63,7 +53,6 @@ "label.create-user": "创建用户", "label.created": "已创建", "label.created-by": "创建者", - "label.criteria": "条件", "label.currency": "货币", "label.current": "当前", "label.current-password": "当前密码", @@ -81,28 +70,24 @@ "label.delete-website": "删除网站", "label.description": "描述", "label.desktop": "台式机", - "label.destination-url": "目标URL", "label.details": "详细信息", "label.device": "设备", "label.devices": "设备", "label.direct": "直接", "label.dismiss": "关闭", "label.distinct-id": "唯一ID", - "label.documentation": "文档", "label.does-not-contain": "不包含", "label.does-not-include": "不包括", "label.doest-not-exist": "不存在", "label.domain": "域名", - "label.download": "下载", "label.dropoff": "丢弃", "label.edit": "编辑", "label.edit-dashboard": "编辑仪表盘", "label.edit-member": "编辑成员", - "label.email": "邮箱", + "label.email": "Email", "label.enable-share-url": "启用共享链接", "label.end-step": "结束步骤", "label.entry": "入口 URL", - "label.environment": "环境", "label.event": "事件", "label.event-data": "事件数据", "label.event-name": "事件名称", @@ -127,13 +112,11 @@ "label.greater-than": "大于", "label.greater-than-equals": "大于或等于", "label.grouped": "分组", - "label.growth": "增长", "label.hostname": "主机名", "label.includes": "包括", "label.insight": "洞察", "label.insights": "见解", "label.insights-description": "通过使用筛选器和划分时间段来更深入地研究数据。", - "label.invalid-url": "无效URL", "label.is": "等于", "label.is-false": "否", "label.is-not": "不等于", @@ -157,9 +140,7 @@ "label.leave-team": "离开团队", "label.less-than": "少于", "label.less-than-equals": "少于等于", - "label.link": "链接", "label.links": "链接", - "label.location": "位置", "label.login": "登录", "label.logout": "退出", "label.manage": "管理", @@ -180,7 +161,7 @@ "label.none": "无", "label.number-of-records": "{x} {x, plural, one {record} other {records}}", "label.ok": "好的", - "label.online": "在线", + "label.online": "Online", "label.organic-search": "自然搜索", "label.organic-shopping": "自然购物", "label.organic-social": "自然社交", @@ -202,22 +183,19 @@ "label.password": "密码", "label.path": "路径", "label.paths": "路径", - "label.pixel": "像素", "label.pixels": "像素", "label.powered-by": "由 {name} 提供支持", - "label.preferences": "偏好", "label.previous": "先前", "label.previous-period": "上一时期", "label.previous-year": "上一年", "label.profile": "个人资料", - "label.profiles": "个人资料", "label.properties": "属性", "label.property": "属性", "label.queries": "查询", "label.query": "查询", "label.query-parameters": "查询参数", "label.realtime": "实时", - "label.referral": "来源", + "label.referral": "Referral", "label.referrer": "来源", "label.referrers": "来源域名", "label.refresh": "刷新", @@ -238,13 +216,8 @@ "label.role": "角色", "label.run-query": "查询", "label.save": "保存", - "label.save-cohort": "保存为群组", - "label.save-segment": "保存为细分", - "label.screen": "屏幕", "label.screens": "屏幕尺寸", "label.search": "搜索", - "label.segment": "细分", - "label.segments": "细分", "label.select": "选择", "label.select-date": "选择日期", "label.select-filter": "选择筛选器", @@ -262,9 +235,6 @@ "label.start-step": "开始步骤", "label.steps": "步骤", "label.sum": "总和", - "label.support": "支持", - "label.switch-account": "切换账户", - "label.table": "表格", "label.tablet": "平板", "label.tag": "标签", "label.tags": "标签", @@ -290,7 +260,6 @@ "label.total": "总数", "label.total-records": "总记录数", "label.tracking-code": "跟踪代码", - "label.traffic": "流量", "label.transactions": "交易", "label.transfer": "转移", "label.transfer-website": "转移网站", @@ -323,7 +292,7 @@ "label.yesterday": "昨天", "message.action-confirmation": "请在下方输入框中输入 {confirmation} 以确认操作。", "message.active-users": "当前在线 {x} 位访客", - "message.bad-request": "请求错误", + "message.bad-request": "Bad request", "message.collected-data": "已收集的数据", "message.confirm-delete": "你确定要删除 {target} 吗?", "message.confirm-leave": "你确定要离开 {target} 吗?", @@ -333,7 +302,7 @@ "message.delete-website-warning": "所有相关数据将会被删除。", "message.error": "发生错误。", "message.event-log": "{url} 上的 {event}", - "message.forbidden": "禁止访问", + "message.forbidden": "Forbidden", "message.go-to-settings": "去设置", "message.incorrect-username-password": "用户名或密码不正确。", "message.invalid-domain": "无效域名", @@ -347,13 +316,13 @@ "message.no-teams": "您尚未创建任何团队。", "message.no-users": "暂无用户。", "message.no-websites-configured": "你还没有设置任何网站。", - "message.not-found": "未找到", - "message.nothing-selected": "未选择", + "message.not-found": "Not found", + "message.nothing-selected": "Nothing selected.", "message.page-not-found": "页面未找到。", "message.reset-website": "如确定要重置该网站,请在下面输入 {confirmation} 以确认。", "message.reset-website-warning": "此网站的所有统计数据将被删除,但您的跟踪代码将保持不变。", "message.saved": "保存成功。", - "message.sever-error": "服务器错误", + "message.sever-error": "Server error", "message.share-url": "这是 {target} 的共享链接。", "message.team-already-member": "你已是该团队的成员。", "message.team-not-found": "未找到团队。", @@ -363,7 +332,7 @@ "message.transfer-user-website-to-team": "选择要转移此网站的团队。", "message.transfer-website": "将网站所有权转移到您的账户或其他团队。", "message.triggered-event": "触发事件", - "message.unauthorized": "未授权", + "message.unauthorized": "Unauthorized", "message.user-deleted": "用户已删除。", "message.viewed-page": "已浏览页面", "message.visitor-log": "来自 {country} 的访客在搭载 {os} 的 {device} 上使用 {browser} 浏览器进行访问。" diff --git a/src/lib/clickhouse.ts b/src/lib/clickhouse.ts index 0a336f80..f2ebbb72 100644 --- a/src/lib/clickhouse.ts +++ b/src/lib/clickhouse.ts @@ -61,7 +61,7 @@ function getDateStringSQL(data: any, unit: string = 'utc', timezone?: string) { function getDateSQL(field: string, unit: string, timezone?: string) { if (timezone) { - return `toDateTime(date_trunc('${unit}', ${field}, '${timezone}'), '${timezone}')`; + return `toDateTime(date_trunc('${unit}', ${field}, '${timezone}'))`; } return `toDateTime(date_trunc('${unit}', ${field}))`; } diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 502a3df6..e5090c3c 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -4,7 +4,6 @@ export const LOCALE_CONFIG = 'umami.locale'; export const TIMEZONE_CONFIG = 'umami.timezone'; export const DATE_RANGE_CONFIG = 'umami.date-range'; export const THEME_CONFIG = 'umami.theme'; -export const CURRENCY_CONFIG = 'umami.currency'; export const DASHBOARD_CONFIG = 'umami.dashboard'; export const LAST_TEAM_CONFIG = 'umami.last-team'; export const VERSION_CHECK = 'umami.version-check'; @@ -26,7 +25,6 @@ export const DEFAULT_WEBSITE_LIMIT = 10; export const DEFAULT_RESET_DATE = '2000-01-01'; export const DEFAULT_PAGE_SIZE = 20; export const DEFAULT_DATE_COMPARE = 'prev'; -export const DEFAULT_CURRENCY = 'USD'; export const REALTIME_RANGE = 30; export const REALTIME_INTERVAL = 10000; diff --git a/src/lib/detect.ts b/src/lib/detect.ts index 910d122b..68cb6672 100644 --- a/src/lib/detect.ts +++ b/src/lib/detect.ts @@ -28,12 +28,6 @@ const PROVIDER_HEADERS = [ regionHeader: 'cloudfront-viewer-country-region', cityHeader: 'cloudfront-viewer-city', }, - // EdgeOne headers (requires custom request headers in Rule Priorities, see: https://edgeone.ai/document/46151) - { - countryHeader: 'eo-ipcountry', - regionHeader: 'eo-region-code', - cityHeader: 'eo-ipcity', - }, ]; export function getDevice(userAgent: string, screen: string = '') { diff --git a/src/lib/format.ts b/src/lib/format.ts index 035a1811..52fd3048 100644 --- a/src/lib/format.ts +++ b/src/lib/format.ts @@ -1,5 +1,3 @@ -import { DEFAULT_CURRENCY } from './constants'; - export function parseTime(val: number) { const days = ~~(val / 86400); const hours = ~~(val / 3600) - days * 24; @@ -96,7 +94,7 @@ export function formatCurrency(value: number, currency: string, locale = 'en-US' // Fallback to default currency format if an error occurs formattedValue = new Intl.NumberFormat(locale, { style: 'currency', - currency: DEFAULT_CURRENCY, + currency: 'USD', }); } diff --git a/src/lib/ip.ts b/src/lib/ip.ts index a0e3a825..5cd77574 100644 --- a/src/lib/ip.ts +++ b/src/lib/ip.ts @@ -1,5 +1,3 @@ -import ipaddr from 'ipaddr.js'; - export const IP_ADDRESS_HEADERS = [ 'true-client-ip', // CDN 'cf-connecting-ip', // Cloudflare @@ -15,87 +13,35 @@ export const IP_ADDRESS_HEADERS = [ 'x-forwarded', ]; -/** - * Normalize IP strings to a canonical form: - * - strips IPv4-mapped IPv6 (e.g. ::ffff:192.0.2.1 -> 192.0.2.1) - * - keeps valid IPv4/IPv6 as-is (canonically formatted by ipaddr.js) - */ -function normalizeIp(ip?: string | null) { - if (!ip) return ip; - - try { - const parsed = ipaddr.parse(ip); - - if (parsed.kind() === 'ipv6' && (parsed as ipaddr.IPv6).isIPv4MappedAddress()) { - return (parsed as ipaddr.IPv6).toIPv4Address().toString(); - } - - return parsed.toString(); - } catch { - // Fallback: return original if parsing fails - return ip; - } -} - -function resolveIp(ip?: string | null) { - if (!ip) return ip; - - // First, try as-is - const normalized = normalizeIp(ip); - try { - ipaddr.parse(normalized); - return normalized; - } catch { - // try stripping port (handles IPv4:port; leaves IPv6 intact) - const stripped = stripPort(ip); - if (stripped !== ip) { - const normalizedStripped = normalizeIp(stripped); - try { - ipaddr.parse(normalizedStripped); - return normalizedStripped; - } catch { - return normalizedStripped; - } - } - - return normalized; - } -} - export function getIpAddress(headers: Headers) { const customHeader = process.env.CLIENT_IP_HEADER; if (customHeader && headers.get(customHeader)) { - return resolveIp(headers.get(customHeader)); + return headers.get(customHeader); } - const header = IP_ADDRESS_HEADERS.find(name => headers.get(name)); - if (!header) { - return undefined; - } + const header = IP_ADDRESS_HEADERS.find(name => { + return headers.get(name); + }); const ip = headers.get(header); if (header === 'x-forwarded-for') { - return resolveIp(ip?.split(',')?.[0]?.trim()); + return ip?.split(',')?.[0]?.trim(); } if (header === 'forwarded') { const match = ip.match(/for=(\[?[0-9a-fA-F:.]+\]?)/); if (match) { - return resolveIp(match[1]); + return match[1]; } } - return resolveIp(ip); + return ip; } -export function stripPort(ip?: string | null) { - if (!ip) { - return ip; - } - +export function stripPort(ip: string) { if (ip.startsWith('[')) { const endBracket = ip.indexOf(']'); if (endBracket !== -1) { diff --git a/src/lib/load.ts b/src/lib/load.ts index bf527975..d4d6c3c7 100644 --- a/src/lib/load.ts +++ b/src/lib/load.ts @@ -38,9 +38,3 @@ export async function fetchSession(websiteId: string, sessionId: string): Promis return session; } - -export async function fetchAccount(userId: string) { - const account = await redis.client.get(`account:${userId}`); - - return account; -} diff --git a/src/lib/prisma.ts b/src/lib/prisma.ts index bfd007d1..64cb870f 100644 --- a/src/lib/prisma.ts +++ b/src/lib/prisma.ts @@ -74,21 +74,15 @@ function getSearchSQL(column: string, param: string = 'search'): string { function mapFilter(column: string, operator: string, name: string, type: string = '') { const value = `{{${name}${type ? `::${type}` : ''}}}`; - if (name.startsWith('cohort_')) { - name = name.slice('cohort_'.length); - } - - const table = SESSION_COLUMNS.includes(name) ? 'session' : 'website_event'; - switch (operator) { case OPERATORS.equals: - return `${table}.${column} = ${value}`; + return `${column} = ${value}`; case OPERATORS.notEquals: - return `${table}.${column} != ${value}`; + return `${column} != ${value}`; case OPERATORS.contains: - return `${table}.${column} ilike ${value}`; + return `${column} ilike ${value}`; case OPERATORS.doesNotContain: - return `${table}.${column} not ilike ${value}`; + return `${column} not ilike ${value}`; default: return ''; } diff --git a/src/lib/request.ts b/src/lib/request.ts index d6543b18..42c44904 100644 --- a/src/lib/request.ts +++ b/src/lib/request.ts @@ -1,9 +1,8 @@ -import { startOfMonth, subMonths } from 'date-fns'; import { z } from 'zod'; import { checkAuth } from '@/lib/auth'; import { DEFAULT_PAGE_SIZE, FILTER_COLUMNS } from '@/lib/constants'; import { getAllowedUnits, getMinimumUnit, maxDate, parseDateRange } from '@/lib/date'; -import { fetchAccount, fetchWebsite } from '@/lib/load'; +import { fetchWebsite } from '@/lib/load'; import { filtersArrayToObject } from '@/lib/params'; import { badRequest, unauthorized } from '@/lib/response'; import type { QueryFilters } from '@/lib/types'; @@ -17,7 +16,7 @@ export async function parseRequest( const url = new URL(request.url); let query = Object.fromEntries(url.searchParams); let body = await getJsonBody(request); - let error: () => undefined | undefined | Response; + let error: () => undefined | undefined; let auth = null; if (schema) { @@ -81,17 +80,8 @@ export function getRequestFilters(query: Record) { return result; } -export async function setWebsiteDate(websiteId: string, userId: string, data: Record) { +export async function setWebsiteDate(websiteId: string, data: Record) { const website = await fetchWebsite(websiteId); - const cloudMode = !!process.env.CLOUD_MODE; - - if (cloudMode && website && !website.teamId) { - const account = await fetchAccount(userId); - - if (!account?.hasSubscription) { - data.startDate = maxDate(data.startDate, startOfMonth(subMonths(new Date(), 6))); - } - } if (website?.resetAt) { data.startDate = maxDate(data.startDate, new Date(website?.resetAt)); @@ -103,13 +93,12 @@ export async function setWebsiteDate(websiteId: string, userId: string, data: Re export async function getQueryFilters( params: Record, websiteId?: string, - userId?: string, ): Promise { const dateRange = getRequestDateRange(params); const filters = getRequestFilters(params); if (websiteId) { - await setWebsiteDate(websiteId, userId, dateRange); + await setWebsiteDate(websiteId, dateRange); if (params.segment) { const segmentParams = (await getWebsiteSegment(websiteId, params.segment)) diff --git a/src/queries/prisma/website.ts b/src/queries/prisma/website.ts index 6c8625d0..79cb7247 100644 --- a/src/queries/prisma/website.ts +++ b/src/queries/prisma/website.ts @@ -132,46 +132,42 @@ export async function updateWebsite( } export async function resetWebsite(websiteId: string) { - const { transaction } = prisma; + const { client, transaction } = prisma; const cloudMode = !!process.env.CLOUD_MODE; return transaction( - async tx => { - await tx.revenue.deleteMany({ + [ + client.revenue.deleteMany({ where: { websiteId }, - }); - - await tx.eventData.deleteMany({ + }), + client.eventData.deleteMany({ where: { websiteId }, - }); - - await tx.sessionData.deleteMany({ + }), + client.sessionData.deleteMany({ where: { websiteId }, - }); - - await tx.websiteEvent.deleteMany({ + }), + client.websiteEvent.deleteMany({ where: { websiteId }, - }); - - await tx.session.deleteMany({ + }), + client.session.deleteMany({ where: { websiteId }, - }); - - const website = await tx.website.update({ + }), + client.website.update({ where: { id: websiteId }, data: { resetAt: new Date(), }, - }); - - return website; - }, + }), + ], { timeout: 30000, }, ).then(async data => { if (cloudMode) { - await redis.client.set(`website:${websiteId}`, data); + await redis.client.set( + `website:${websiteId}`, + data.find(website => website.id), + ); } return data; @@ -179,52 +175,43 @@ export async function resetWebsite(websiteId: string) { } export async function deleteWebsite(websiteId: string) { - const { transaction } = prisma; + const { client, transaction } = prisma; const cloudMode = !!process.env.CLOUD_MODE; return transaction( - async tx => { - await tx.revenue.deleteMany({ + [ + client.revenue.deleteMany({ where: { websiteId }, - }); - - await tx.eventData.deleteMany({ + }), + client.eventData.deleteMany({ where: { websiteId }, - }); - - await tx.sessionData.deleteMany({ + }), + client.sessionData.deleteMany({ where: { websiteId }, - }); - - await tx.websiteEvent.deleteMany({ + }), + client.websiteEvent.deleteMany({ where: { websiteId }, - }); - - await tx.session.deleteMany({ + }), + client.session.deleteMany({ where: { websiteId }, - }); - - await tx.report.deleteMany({ + }), + client.report.deleteMany({ where: { websiteId }, - }); - - await tx.segment.deleteMany({ + }), + client.segment.deleteMany({ where: { websiteId }, - }); - - const website = cloudMode - ? await tx.website.update({ + }), + cloudMode + ? client.website.update({ data: { deletedAt: new Date(), }, where: { id: websiteId }, }) - : await tx.website.delete({ + : client.website.delete({ where: { id: websiteId }, - }); - - return website; - }, + }), + ], { timeout: 30000, }, diff --git a/src/queries/sql/reports/getJourney.ts b/src/queries/sql/reports/getJourney.ts index 21a7f22d..283e0fad 100644 --- a/src/queries/sql/reports/getJourney.ts +++ b/src/queries/sql/reports/getJourney.ts @@ -119,7 +119,7 @@ async function relationalQuery( select distinct website_event.visit_id, website_event.referrer_path, - coalesce(nullIf(website_event.event_name, ''), website_event.url_path) "event", + coalesce(nullIf(website_event.event_name, ''), website_event.url_path) event, row_number() OVER (PARTITION BY visit_id ORDER BY website_event.created_at) AS event_number from website_event ${cohortQuery} diff --git a/src/queries/sql/reports/getRevenue.ts b/src/queries/sql/reports/getRevenue.ts index 2a4604fe..fa25078c 100644 --- a/src/queries/sql/reports/getRevenue.ts +++ b/src/queries/sql/reports/getRevenue.ts @@ -41,15 +41,14 @@ async function relationalQuery( currency, }); - const joinQuery = - filterQuery || cohortQuery - ? `join website_event + const joinQuery = filterQuery + ? `join website_event on website_event.website_id = revenue.website_id and website_event.session_id = revenue.session_id and website_event.event_id = revenue.event_id and website_event.website_id = {{websiteId::uuid}} and website_event.created_at between {{startDate}} and {{endDate}}` - : ''; + : ''; const chart = await rawQuery( ` @@ -63,7 +62,7 @@ async function relationalQuery( ${joinSessionQuery} where revenue.website_id = {{websiteId::uuid}} and revenue.created_at between {{startDate}} and {{endDate}} - and upper(revenue.currency) = {{currency}} + and revenue.currency = upper({{currency}}) ${filterQuery} group by x, t order by t @@ -84,7 +83,7 @@ async function relationalQuery( ${cohortQuery} where revenue.website_id = {{websiteId::uuid}} and revenue.created_at between {{startDate}} and {{endDate}} - and upper(revenue.currency) = {{currency}} + and revenue.currency = upper({{currency}}) ${filterQuery} group by session.country `, @@ -103,7 +102,7 @@ async function relationalQuery( ${joinSessionQuery} where revenue.website_id = {{websiteId::uuid}} and revenue.created_at between {{startDate}} and {{endDate}} - and upper(revenue.currency) = {{currency}} + and revenue.currency = upper({{currency}}) ${filterQuery} `, queryParams, @@ -155,7 +154,7 @@ async function clickhouseQuery( ${cohortQuery} where website_revenue.website_id = {websiteId:UUID} and website_revenue.created_at between {startDate:DateTime64} and {endDate:DateTime64} - and upper(website_revenue.currency) = {currency:String} + and website_revenue.currency = upper({currency:String}) ${filterQuery} group by x, t order by t @@ -183,7 +182,7 @@ async function clickhouseQuery( ${cohortQuery} where website_revenue.website_id = {websiteId:UUID} and website_revenue.created_at between {startDate:DateTime64} and {endDate:DateTime64} - and upper(website_revenue.currency) = {currency:String} + and website_revenue.currency = upper({currency:String}) ${filterQuery} group by website_event.country order by value desc @@ -206,7 +205,7 @@ async function clickhouseQuery( ${cohortQuery} where website_revenue.website_id = {websiteId:UUID} and website_revenue.created_at between {startDate:DateTime64} and {endDate:DateTime64} - and upper(website_revenue.currency) = {currency:String} + and website_revenue.currency = upper({currency:String}) ${filterQuery} `, queryParams, diff --git a/src/queries/sql/sessions/saveSessionData.ts b/src/queries/sql/sessions/saveSessionData.ts index cce6cd28..74093177 100644 --- a/src/queries/sql/sessions/saveSessionData.ts +++ b/src/queries/sql/sessions/saveSessionData.ts @@ -46,23 +46,31 @@ export async function relationalQuery({ createdAt, })); + const existing = await client.sessionData.findMany({ + where: { + sessionId, + }, + select: { + id: true, + sessionId: true, + dataKey: true, + }, + }); + for (const data of flattenedData) { const { sessionId, dataKey, ...props } = data; + const record = existing.find(e => e.sessionId === sessionId && e.dataKey === dataKey); - // Try to update existing record using compound where clause - // This is safer than using id from a previous query due to race conditions - const updateResult = await client.sessionData.updateMany({ - where: { - sessionId, - dataKey, - }, - data: { - ...props, - }, - }); - - // If no record was updated, create a new one - if (updateResult.count === 0) { + if (record) { + await client.sessionData.update({ + where: { + id: record.id, + }, + data: { + ...props, + }, + }); + } else { await client.sessionData.create({ data, }); diff --git a/src/tracker/index.js b/src/tracker/index.js index 85d27430..ad3648ac 100644 --- a/src/tracker/index.js +++ b/src/tracker/index.js @@ -12,13 +12,7 @@ if (!currentScript) return; const { hostname, href, origin } = location; - - let localStorage; - try { - localStorage = href.startsWith('data:') ? undefined : window.localStorage; - } catch { - /* (DOMException) SecurityError: Access is denied for this document. */ - } + const localStorage = href.startsWith('data:') ? undefined : window.localStorage; const _data = 'data-'; const _false = 'false';