Merge branch 'dev' into master

This commit is contained in:
Mike Cao 2024-11-18 13:25:55 -08:00 committed by GitHub
commit e32d2b988c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
121 changed files with 15568 additions and 1757 deletions

28
.github/workflows/cd-cloud.yml vendored Normal file
View File

@ -0,0 +1,28 @@
name: Create docker images
on:
push:
branches:
- analytics
jobs:
build:
name: Build, push, and deploy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Generate random hash
id: random_hash
run: echo "hash=$(openssl rand -hex 4)" >> $GITHUB_OUTPUT
- uses: mr-smithers-excellent/docker-build-push@v6
name: Build & push Docker image to docker.io
with:
image: umamisoftware/umami
tags: cloud-${{ steps.random_hash.outputs.hash }}, cloud-latest
buildArgs: DATABASE_TYPE=postgresql
registry: docker.io
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

View File

@ -20,11 +20,26 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Extract version parts from input
id: extract_version
run: |
echo "version=$(echo ${{ github.event.inputs.version }})" >> $GITHUB_ENV
echo "major=$(echo ${{ github.event.inputs.version }} | cut -d. -f1)" >> $GITHUB_ENV
echo "minor=$(echo ${{ github.event.inputs.version }} | cut -d. -f2)" >> $GITHUB_ENV
- name: Generate tags
id: generate_tags
run: |
echo "tag_major=$(echo ${{ matrix.db-type }}-${{ env.major }})" >> $GITHUB_ENV
echo "tag_minor=$(echo ${{ matrix.db-type }}-${{ env.major }}.${{ env.minor }})" >> $GITHUB_ENV
echo "tag_patch=$(echo ${{ matrix.db-type }}-${{ env.version }})" >> $GITHUB_ENV
echo "tag_latest=$(echo ${{ matrix.db-type }}-latest)" >> $GITHUB_ENV
- uses: mr-smithers-excellent/docker-build-push@v6
name: Build & push Docker image to ghcr.io for ${{ matrix.db-type }}
with:
image: umami
tags: ${{ matrix.db-type }}-${{ inputs.version }}, ${{ matrix.db-type }}-latest
tags: ${{ env.tag_major }}, ${{ env.tag_minor }}, ${{ env.tag_patch }}, ${{ env.tag_latest }}
buildArgs: DATABASE_TYPE=${{ matrix.db-type }}
registry: ghcr.io
multiPlatform: true
@ -36,7 +51,7 @@ jobs:
name: Build & push Docker image to docker.io for ${{ matrix.db-type }}
with:
image: umamisoftware/umami
tags: ${{ matrix.db-type }}-${{ inputs.version }}, ${{ matrix.db-type }}-latest
tags: ${{ env.tag_major }}, ${{ env.tag_minor }}, ${{ env.tag_patch }}, ${{ env.tag_latest }}
buildArgs: DATABASE_TYPE=${{ matrix.db-type }}
registry: docker.io
username: ${{ secrets.DOCKER_USERNAME }}

View File

@ -17,14 +17,21 @@ jobs:
- name: Set env
run: |
echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
echo "NOW=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_ENV
- name: Generate tags
id: generate_tags
run: |
echo "tag_patch=$(echo ${{ matrix.db-type }})-${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
echo "tag_minor=$(echo ${{ matrix.db-type }})-$(echo ${GITHUB_REF#refs/tags/} | cut -d. -f1,2)" >> $GITHUB_ENV
echo "tag_major=$(echo ${{ matrix.db-type }})-$(echo ${GITHUB_REF#refs/tags/} | cut -d. -f1)" >> $GITHUB_ENV
echo "tag_latest=$(echo ${{ matrix.db-type }})-latest" >> $GITHUB_ENV
- uses: mr-smithers-excellent/docker-build-push@v6
name: Build & push Docker image to ghcr.io for ${{ matrix.db-type }}
with:
image: umami
tags: ${{ matrix.db-type }}-${{ env.RELEASE_VERSION }}, ${{ matrix.db-type }}-latest
tags: ${{ env.tag_major }}, ${{ env.tag_minor }}, ${{ env.tag_patch }}, ${{ env.tag_latest }}
buildArgs: DATABASE_TYPE=${{ matrix.db-type }}
registry: ghcr.io
multiPlatform: true
@ -32,12 +39,11 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: mr-smithers-excellent/docker-build-push@v6
name: Build & push Docker image to docker.io for ${{ matrix.db-type }}
with:
image: umamisoftware/umami
tags: ${{ matrix.db-type }}-${{ env.RELEASE_VERSION }}, ${{ matrix.db-type }}-latest
tags: ${{ env.tag_major }}, ${{ env.tag_minor }}, ${{ env.tag_patch }}, ${{ env.tag_latest }}
buildArgs: DATABASE_TYPE=${{ matrix.db-type }}
registry: docker.io
username: ${{ secrets.DOCKER_USERNAME }}

View File

@ -0,0 +1,77 @@
-- add tag column
ALTER TABLE umami.website_event ADD COLUMN "tag" String AFTER "event_name";
ALTER TABLE umami.website_event_stats_hourly ADD COLUMN "tag" SimpleAggregateFunction(groupArrayArray, Array(String)) AFTER "max_time";
-- update materialized view
DROP TABLE umami.website_event_stats_hourly_mv;
CREATE MATERIALIZED VIEW umami.website_event_stats_hourly_mv
TO umami.website_event_stats_hourly
AS
SELECT
website_id,
session_id,
visit_id,
hostname,
browser,
os,
device,
screen,
language,
country,
subdivision1,
city,
entry_url,
exit_url,
url_paths as url_path,
url_query,
referrer_domain,
page_title,
event_type,
event_name,
views,
min_time,
max_time,
tag,
timestamp as created_at
FROM (SELECT
website_id,
session_id,
visit_id,
hostname,
browser,
os,
device,
screen,
language,
country,
subdivision1,
city,
argMinState(url_path, created_at) entry_url,
argMaxState(url_path, created_at) exit_url,
arrayFilter(x -> x != '', groupArray(url_path)) as url_paths,
arrayFilter(x -> x != '', groupArray(url_query)) url_query,
arrayFilter(x -> x != '', groupArray(referrer_domain)) referrer_domain,
arrayFilter(x -> x != '', groupArray(page_title)) page_title,
event_type,
if(event_type = 2, groupArray(event_name), []) event_name,
sumIf(1, event_type = 1) views,
min(created_at) min_time,
max(created_at) max_time,
arrayFilter(x -> x != '', groupArray(tag)) tag,
toStartOfHour(created_at) timestamp
FROM umami.website_event
GROUP BY website_id,
session_id,
visit_id,
hostname,
browser,
os,
device,
screen,
language,
country,
subdivision1,
city,
event_type,
timestamp);

View File

@ -26,6 +26,7 @@ CREATE TABLE umami.website_event
--events
event_type UInt32,
event_name String,
tag String,
created_at DateTime('UTC'),
job_id Nullable(UUID)
)
@ -96,6 +97,7 @@ CREATE TABLE umami.website_event_stats_hourly
views SimpleAggregateFunction(sum, UInt64),
min_time SimpleAggregateFunction(min, DateTime('UTC')),
max_time SimpleAggregateFunction(max, DateTime('UTC')),
tag SimpleAggregateFunction(groupArrayArray, Array(String)),
created_at Datetime('UTC')
)
ENGINE = AggregatingMergeTree
@ -136,6 +138,7 @@ SELECT
views,
min_time,
max_time,
tag,
timestamp as created_at
FROM (SELECT
website_id,
@ -161,6 +164,7 @@ FROM (SELECT
sumIf(1, event_type = 1) views,
min(created_at) min_time,
max(created_at) max_time,
arrayFilter(x -> x != '', groupArray(tag)) tag,
toStartOfHour(created_at) timestamp
FROM umami.website_event
GROUP BY website_id,

View File

@ -11,7 +11,7 @@ CREATE TABLE `user` (
UNIQUE INDEX `user_user_id_key`(`user_id`),
UNIQUE INDEX `user_username_key`(`username`),
PRIMARY KEY (`user_id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
) ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `session` (
@ -33,7 +33,7 @@ CREATE TABLE `session` (
INDEX `session_created_at_idx`(`created_at`),
INDEX `session_website_id_idx`(`website_id`),
PRIMARY KEY (`session_id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
) ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `website` (
@ -53,7 +53,7 @@ CREATE TABLE `website` (
INDEX `website_created_at_idx`(`created_at`),
INDEX `website_share_id_idx`(`share_id`),
PRIMARY KEY (`website_id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
) ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `website_event` (
@ -76,7 +76,7 @@ CREATE TABLE `website_event` (
INDEX `website_event_website_id_created_at_idx`(`website_id`, `created_at`),
INDEX `website_event_website_id_session_id_created_at_idx`(`website_id`, `session_id`, `created_at`),
PRIMARY KEY (`event_id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
) ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `event_data` (
@ -95,7 +95,7 @@ CREATE TABLE `event_data` (
INDEX `event_data_website_event_id_idx`(`website_event_id`),
INDEX `event_data_website_id_website_event_id_created_at_idx`(`website_id`, `website_event_id`, `created_at`),
PRIMARY KEY (`event_id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
) ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `team` (
@ -109,7 +109,7 @@ CREATE TABLE `team` (
UNIQUE INDEX `team_access_code_key`(`access_code`),
INDEX `team_access_code_idx`(`access_code`),
PRIMARY KEY (`team_id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
) ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `team_user` (
@ -124,7 +124,7 @@ CREATE TABLE `team_user` (
INDEX `team_user_team_id_idx`(`team_id`),
INDEX `team_user_user_id_idx`(`user_id`),
PRIMARY KEY (`team_user_id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
) ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `team_website` (
@ -137,7 +137,7 @@ CREATE TABLE `team_website` (
INDEX `team_website_team_id_idx`(`team_id`),
INDEX `team_website_website_id_idx`(`website_id`),
PRIMARY KEY (`team_website_id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
) ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- AddSystemUser
INSERT INTO user (user_id, username, role, password) VALUES ('41e2b680-648e-4b09-bcd7-3e2b10c06264' , 'admin', 'admin', '$2b$10$BUli0c.muyCW1ErNJc3jL.vFRFtFJWrT8/GcR4A.sUdCznaXiqFXa');

View File

@ -21,7 +21,7 @@ CREATE TABLE `session_data` (
INDEX `session_data_website_id_idx`(`website_id`),
INDEX `session_data_session_id_idx`(`session_id`),
PRIMARY KEY (`session_data_id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
) ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `report` (
@ -41,7 +41,7 @@ CREATE TABLE `report` (
INDEX `report_type_idx`(`type`),
INDEX `report_name_idx`(`name`),
PRIMARY KEY (`report_id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
) ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- EventData migration
UPDATE event_data

View File

@ -0,0 +1,5 @@
-- AlterTable
ALTER TABLE `website_event` ADD COLUMN `tag` VARCHAR(50) NULL;
-- CreateIndex
CREATE INDEX `website_event_website_id_created_at_tag_idx` ON `website_event`(`website_id`, `created_at`, `tag`);

View File

@ -102,6 +102,7 @@ model WebsiteEvent {
pageTitle String? @map("page_title") @db.VarChar(500)
eventType Int @default(1) @map("event_type") @db.UnsignedInt
eventName String? @map("event_name") @db.VarChar(50)
tag String? @db.VarChar(50)
eventData EventData[]
session Session @relation(fields: [sessionId], references: [id])
@ -116,6 +117,7 @@ model WebsiteEvent {
@@index([websiteId, createdAt, referrerDomain])
@@index([websiteId, createdAt, pageTitle])
@@index([websiteId, createdAt, eventName])
@@index([websiteId, createdAt, tag])
@@index([websiteId, sessionId, createdAt])
@@index([websiteId, visitId, createdAt])
@@map("website_event")

View File

@ -0,0 +1,5 @@
-- AlterTable
ALTER TABLE "website_event" ADD COLUMN "tag" VARCHAR(50);
-- CreateIndex
CREATE INDEX "website_event_website_id_created_at_tag_idx" ON "website_event"("website_id", "created_at", "tag");

View File

@ -102,6 +102,7 @@ model WebsiteEvent {
pageTitle String? @map("page_title") @db.VarChar(500)
eventType Int @default(1) @map("event_type") @db.Integer
eventName String? @map("event_name") @db.VarChar(50)
tag String? @db.VarChar(50)
eventData EventData[]
session Session @relation(fields: [sessionId], references: [id])
@ -111,11 +112,13 @@ model WebsiteEvent {
@@index([visitId])
@@index([websiteId])
@@index([websiteId, createdAt])
@@index([websiteId, createdAt, urlPath])
@@index([websiteId, createdAt, urlQuery])
@@index([websiteId, createdAt, referrerDomain])
@@index([websiteId, createdAt, pageTitle])
@@index([websiteId, createdAt, eventName])
@@index([websiteId, createdAt, tag])
@@index([websiteId, sessionId, createdAt])
@@index([websiteId, visitId, createdAt])
@@map("website_event")

2
next-env.d.ts vendored
View File

@ -3,4 +3,4 @@
/// <reference types="next/navigation-types/compat/navigation" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.

View File

@ -3,6 +3,8 @@ require('dotenv').config();
const path = require('path');
const pkg = require('./package.json');
const TRACKER_SCRIPT = '/script.js';
const basePath = process.env.BASE_PATH;
const collectApiEndpoint = process.env.COLLECT_API_ENDPOINT;
const cloudMode = process.env.CLOUD_MODE;
@ -14,6 +16,7 @@ const forceSSL = process.env.FORCE_SSL;
const frameAncestors = process.env.ALLOWED_FRAME_URLS;
const privateMode = process.env.PRIVATE_MODE;
const trackerScriptName = process.env.TRACKER_SCRIPT_NAME;
const trackerScriptURL = process.env.TRACKER_SCRIPT_URL;
const contentSecurityPolicy = [
`default-src 'self'`,
@ -24,7 +27,7 @@ const contentSecurityPolicy = [
`frame-ancestors 'self' ${frameAncestors}`,
];
const headers = [
const defaultHeaders = [
{
key: 'X-DNS-Prefetch-Control',
value: 'on',
@ -39,14 +42,39 @@ const headers = [
];
if (forceSSL) {
headers.push({
defaultHeaders.push({
key: 'Strict-Transport-Security',
value: 'max-age=63072000; includeSubDomains; preload',
});
}
const trackerHeaders = [
{
key: 'Access-Control-Allow-Origin',
value: '*',
},
];
const headers = [
{
source: '/:path*',
headers: defaultHeaders,
},
{
source: TRACKER_SCRIPT,
headers: trackerHeaders,
},
];
const rewrites = [];
if (trackerScriptURL) {
rewrites.push({
source: TRACKER_SCRIPT,
destination: trackerScriptURL,
});
}
if (collectApiEndpoint) {
rewrites.push({
source: collectApiEndpoint,
@ -54,19 +82,6 @@ if (collectApiEndpoint) {
});
}
if (trackerScriptName) {
const names = trackerScriptName?.split(',').map(name => name.trim());
if (names) {
names.forEach(name => {
rewrites.push({
source: `/${name.replace(/^\/+/, '')}`,
destination: '/script.js',
});
});
}
}
const redirects = [
{
source: '/settings',
@ -85,6 +100,27 @@ const redirects = [
},
];
// Adding rewrites + headers for all alternative tracker script names.
if (trackerScriptName) {
const names = trackerScriptName?.split(',').map(name => name.trim());
if (names) {
names.forEach(name => {
const normalizedSource = `/${name.replace(/^\/+/, '')}`;
rewrites.push({
source: normalizedSource,
destination: TRACKER_SCRIPT,
});
headers.push({
source: normalizedSource,
headers: trackerHeaders,
});
});
}
}
if (cloudMode && cloudUrl) {
redirects.push({
source: '/settings/:path*',

View File

@ -1,6 +1,6 @@
{
"name": "umami",
"version": "2.13.2",
"version": "2.14.0",
"description": "A simple, fast, privacy-focused alternative to Google Analytics.",
"author": "Umami Software, Inc. <hello@umami.is>",
"license": "MIT",
@ -123,7 +123,7 @@
},
"devDependencies": {
"@formatjs/cli": "^4.2.29",
"@netlify/plugin-nextjs": "^5.1.0",
"@netlify/plugin-nextjs": "^5.8.1",
"@rollup/plugin-alias": "^5.0.0",
"@rollup/plugin-commonjs": "^25.0.4",
"@rollup/plugin-json": "^6.0.0",

File diff suppressed because one or more lines are too long

View File

@ -2,7 +2,7 @@
"label.access-code": [
{
"type": 0,
"value": "Access code"
"value": "Přístupový kód"
}
],
"label.actions": [
@ -14,31 +14,31 @@
"label.activity": [
{
"type": 0,
"value": "Activity log"
"value": "Log aktivity"
}
],
"label.add": [
{
"type": 0,
"value": "Add"
"value": "Přidat"
}
],
"label.add-description": [
{
"type": 0,
"value": "Add description"
"value": "Přidat popis"
}
],
"label.add-member": [
{
"type": 0,
"value": "Add member"
"value": "Přidat člena"
}
],
"label.add-step": [
{
"type": 0,
"value": "Add step"
"value": "Přidat krok"
}
],
"label.add-website": [
@ -56,7 +56,7 @@
"label.after": [
{
"type": 0,
"value": "After"
"value": "Po"
}
],
"label.all": [
@ -68,7 +68,7 @@
"label.all-time": [
{
"type": 0,
"value": "All time"
"value": "Celá doba"
}
],
"label.analytics": [
@ -80,7 +80,7 @@
"label.average": [
{
"type": 0,
"value": "Average"
"value": "Průměr"
}
],
"label.back": [
@ -92,7 +92,7 @@
"label.before": [
{
"type": 0,
"value": "Before"
"value": "Před"
}
],
"label.bounce-rate": [
@ -110,13 +110,13 @@
"label.browser": [
{
"type": 0,
"value": "Browser"
"value": "Prohlížeč"
}
],
"label.browsers": [
{
"type": 0,
"value": "Prohlížeč"
"value": "Prohlížeče"
}
],
"label.cancel": [
@ -134,31 +134,31 @@
"label.cities": [
{
"type": 0,
"value": "Cities"
"value": "Města"
}
],
"label.city": [
{
"type": 0,
"value": "City"
"value": "Město"
}
],
"label.clear-all": [
{
"type": 0,
"value": "Clear all"
"value": "Vyčistit vše"
}
],
"label.compare": [
{
"type": 0,
"value": "Compare"
"value": "Porovnat"
}
],
"label.confirm": [
{
"type": 0,
"value": "Confirm"
"value": "Potvrdit"
}
],
"label.confirm-password": [
@ -170,61 +170,61 @@
"label.contains": [
{
"type": 0,
"value": "Contains"
"value": "Obsahuje"
}
],
"label.continue": [
{
"type": 0,
"value": "Continue"
"value": "Pokračovat"
}
],
"label.count": [
{
"type": 0,
"value": "Count"
"value": "Počet"
}
],
"label.countries": [
{
"type": 0,
"value": "Země"
"value": "Státy"
}
],
"label.country": [
{
"type": 0,
"value": "Country"
"value": "Stát"
}
],
"label.create": [
{
"type": 0,
"value": "Create"
"value": "Vytvořit"
}
],
"label.create-report": [
{
"type": 0,
"value": "Create report"
"value": "Vytvořit hlášení"
}
],
"label.create-team": [
{
"type": 0,
"value": "Create team"
"value": "Vytvořit tým"
}
],
"label.create-user": [
{
"type": 0,
"value": "Create user"
"value": "Vytvořit uživatele"
}
],
"label.created": [
{
"type": 0,
"value": "Created"
"value": "Vytvořeno"
}
],
"label.created-by": [
@ -236,7 +236,7 @@
"label.current": [
{
"type": 0,
"value": "Current"
"value": "Aktuální"
}
],
"label.current-password": [
@ -266,7 +266,7 @@
"label.date": [
{
"type": 0,
"value": "Date"
"value": "Datum"
}
],
"label.date-range": [
@ -278,7 +278,7 @@
"label.day": [
{
"type": 0,
"value": "Day"
"value": "Den"
}
],
"label.default-date-range": [
@ -296,19 +296,19 @@
"label.delete-report": [
{
"type": 0,
"value": "Delete report"
"value": "Smazat hlášení"
}
],
"label.delete-team": [
{
"type": 0,
"value": "Delete team"
"value": "Smazat tým"
}
],
"label.delete-user": [
{
"type": 0,
"value": "Delete user"
"value": "Smazat uživatele"
}
],
"label.delete-website": [
@ -320,7 +320,7 @@
"label.description": [
{
"type": 0,
"value": "Description"
"value": "Popis"
}
],
"label.desktop": [
@ -332,13 +332,13 @@
"label.details": [
{
"type": 0,
"value": "Details"
"value": "Detaily"
}
],
"label.device": [
{
"type": 0,
"value": "Device"
"value": "Zařízení"
}
],
"label.devices": [
@ -356,7 +356,7 @@
"label.does-not-contain": [
{
"type": 0,
"value": "Does not contain"
"value": "Neobsahuje"
}
],
"label.domain": [
@ -380,13 +380,13 @@
"label.edit-dashboard": [
{
"type": 0,
"value": "Edit dashboard"
"value": "Upravit dashboard"
}
],
"label.edit-member": [
{
"type": 0,
"value": "Edit member"
"value": "Upravit člena"
}
],
"label.enable-share-url": [
@ -404,13 +404,13 @@
"label.entry": [
{
"type": 0,
"value": "Entry URL"
"value": "Vstupní URL"
}
],
"label.event": [
{
"type": 0,
"value": "Event"
"value": "Událost"
}
],
"label.event-data": [
@ -440,7 +440,7 @@
"label.field": [
{
"type": 0,
"value": "Field"
"value": "Pole"
}
],
"label.fields": [
@ -452,7 +452,7 @@
"label.filter": [
{
"type": 0,
"value": "Filter"
"value": "Filtr"
}
],
"label.filter-combined": [
@ -470,7 +470,7 @@
"label.filters": [
{
"type": 0,
"value": "Filters"
"value": "Filtry"
}
],
"label.first-seen": [
@ -494,13 +494,13 @@
"label.goal": [
{
"type": 0,
"value": "Goal"
"value": "l"
}
],
"label.goals": [
{
"type": 0,
"value": "Goals"
"value": "Cíle"
}
],
"label.goals-description": [
@ -596,13 +596,13 @@
"label.language": [
{
"type": 0,
"value": "Language"
"value": "Jazyk"
}
],
"label.languages": [
{
"type": 0,
"value": "Languages"
"value": "Jazyky"
}
],
"label.laptop": [
@ -642,7 +642,7 @@
"label.last-months": [
{
"type": 0,
"value": "Last "
"value": "Posledních "
},
{
"type": 1,
@ -650,7 +650,7 @@
},
{
"type": 0,
"value": " months"
"value": " měsíců"
}
],
"label.last-seen": [
@ -662,13 +662,13 @@
"label.leave": [
{
"type": 0,
"value": "Leave"
"value": "Opustit"
}
],
"label.leave-team": [
{
"type": 0,
"value": "Leave team"
"value": "Opustit tým"
}
],
"label.less-than": [
@ -698,13 +698,13 @@
"label.manage": [
{
"type": 0,
"value": "Manage"
"value": "Spravovat"
}
],
"label.manager": [
{
"type": 0,
"value": "Manager"
"value": "Správce"
}
],
"label.max": [
@ -716,13 +716,13 @@
"label.member": [
{
"type": 0,
"value": "Member"
"value": "Člen"
}
],
"label.members": [
{
"type": 0,
"value": "Members"
"value": "Členové"
}
],
"label.min": [
@ -746,13 +746,13 @@
"label.my-account": [
{
"type": 0,
"value": "My account"
"value": "Můj účet"
}
],
"label.my-websites": [
{
"type": 0,
"value": "My websites"
"value": "Mé weby"
}
],
"label.name": [
@ -822,13 +822,13 @@
"label.overview": [
{
"type": 0,
"value": "Overview"
"value": "Přehled"
}
],
"label.owner": [
{
"type": 0,
"value": "Owner"
"value": "Vlastník"
}
],
"label.page-of": [
@ -858,7 +858,7 @@
"label.pageTitle": [
{
"type": 0,
"value": "Page title"
"value": "Název stránky"
}
],
"label.pages": [
@ -876,13 +876,13 @@
"label.path": [
{
"type": 0,
"value": "Path"
"value": "Cesta"
}
],
"label.paths": [
{
"type": 0,
"value": "Paths"
"value": "Cesty"
}
],
"label.powered-by": [
@ -1444,13 +1444,13 @@
"label.visitors": [
{
"type": 0,
"value": "Návštěvy"
"value": "Návštěvníci"
}
],
"label.visits": [
{
"type": 0,
"value": "Visits"
"value": "Návštěvy"
}
],
"label.website": [
@ -1474,13 +1474,13 @@
"label.window": [
{
"type": 0,
"value": "Window"
"value": "Okno"
}
],
"label.yesterday": [
{
"type": 0,
"value": "Yesterday"
"value": "Včera"
}
],
"message.action-confirmation": [

File diff suppressed because it is too large Load Diff

View File

@ -410,13 +410,13 @@
"label.event": [
{
"type": 0,
"value": "Ereigniss"
"value": "Ereignis"
}
],
"label.event-data": [
{
"type": 0,
"value": "Ereignissdaten"
"value": "Ereignisdaten"
}
],
"label.events": [
@ -1798,7 +1798,7 @@
"message.triggered-event": [
{
"type": 0,
"value": "Ausgelöstes Ereigniss"
"value": "Ereignis ausgelöst"
}
],
"message.user-deleted": [

View File

@ -404,7 +404,7 @@
"label.entry": [
{
"type": 0,
"value": "Entry URL"
"value": "URL d'entrée"
}
],
"label.event": [
@ -476,7 +476,7 @@
"label.first-seen": [
{
"type": 0,
"value": "First seen"
"value": "Vu pour la première fois"
}
],
"label.funnel": [
@ -506,7 +506,7 @@
"label.goals-description": [
{
"type": 0,
"value": "Track your goals for pageviews and events."
"value": "Suivez vos objectifs en matière de pages vues et d'événements."
}
],
"label.greater-than": [
@ -590,7 +590,7 @@
"label.journey-description": [
{
"type": 0,
"value": "Understand how users navigate through your website."
"value": "Comprendre comment les utilisateurs naviguent sur votre site web."
}
],
"label.language": [
@ -886,19 +886,19 @@
"label.previous": [
{
"type": 0,
"value": "Previous"
"value": "Précédent"
}
],
"label.previous-period": [
{
"type": 0,
"value": "Previous period"
"value": "Période précédente"
}
],
"label.previous-year": [
{
"type": 0,
"value": "Previous year"
"value": "Année précédente"
}
],
"label.profile": [
@ -910,13 +910,13 @@
"label.properties": [
{
"type": 0,
"value": "Properties"
"value": "Propriétés"
}
],
"label.property": [
{
"type": 0,
"value": "Property"
"value": "Propriété"
}
],
"label.queries": [
@ -1036,13 +1036,13 @@
"label.revenue-description": [
{
"type": 0,
"value": "Look into your revenue across time."
"value": "Examinez vos revenus au fil du temps."
}
],
"label.revenue-property": [
{
"type": 0,
"value": "Revenue Property"
"value": "Propriétés des revenues"
}
],
"label.role": [
@ -1078,7 +1078,7 @@
"label.select": [
{
"type": 0,
"value": "Select"
"value": "Selectionner"
}
],
"label.select-date": [
@ -1132,7 +1132,7 @@
"label.start-step": [
{
"type": 0,
"value": "Start Step"
"value": "Etape de démarrage"
}
],
"label.steps": [
@ -1168,7 +1168,7 @@
"label.team-manager": [
{
"type": 0,
"value": "Team manager"
"value": "Manager de l'équipe"
}
],
"label.team-member": [
@ -1192,7 +1192,7 @@
"label.team-view-only": [
{
"type": 0,
"value": "Team view only"
"value": "Vue d'équipe uniquement"
}
],
"label.team-websites": [
@ -1318,7 +1318,7 @@
"label.uniqueCustomers": [
{
"type": 0,
"value": "Unique Customers"
"value": "Clients uniques"
}
],
"label.unknown": [
@ -1360,7 +1360,7 @@
"label.user-property": [
{
"type": 0,
"value": "User Property"
"value": "Propriétés d'utilisateurs"
}
],
"label.username": [

View File

@ -1012,7 +1012,7 @@
"label.retention-description": [
{
"type": 0,
"value": "사용자가 얼마나 자주 돌아오는지를 추적하여 웹사이트의 리텐션을 측정하십시오."
"value": "사용자가 얼마나 자주 돌아오는지를 추적하여 웹사이트의 리텐션을 측정하세요."
}
],
"label.revenue": [
@ -1372,7 +1372,7 @@
"label.utm-description": [
{
"type": 0,
"value": "UTM 매개변수를 통해 캠페인을 추적합니다."
"value": "UTM 매개변수를 통해 캠페인을 추적하세요."
}
],
"label.value": [
@ -1414,7 +1414,7 @@
"label.visit-duration": [
{
"type": 0,
"value": "평균 방문 시간"
"value": "방문 시간"
}
],
"label.visitors": [
@ -1548,7 +1548,7 @@
"message.error": [
{
"type": 0,
"value": "오류가 발생했습니다."
"value": "문제가 발생했습니다."
}
],
"message.event-log": [

View File

@ -38,13 +38,13 @@
"label.add-step": [
{
"type": 0,
"value": "Add step"
"value": "Adaugă pas"
}
],
"label.add-website": [
{
"type": 0,
"value": "Adăugare site web"
"value": "Adaugă site web"
}
],
"label.admin": [
@ -152,7 +152,7 @@
"label.compare": [
{
"type": 0,
"value": "Compare"
"value": "Compară"
}
],
"label.confirm": [
@ -182,7 +182,7 @@
"label.count": [
{
"type": 0,
"value": "Count"
"value": "Număr"
}
],
"label.countries": [
@ -230,13 +230,13 @@
"label.created-by": [
{
"type": 0,
"value": "Created By"
"value": "Creat de"
}
],
"label.current": [
{
"type": 0,
"value": "Current"
"value": "Curent"
}
],
"label.current-password": [
@ -266,13 +266,13 @@
"label.date": [
{
"type": 0,
"value": "Data"
"value": "Dată"
}
],
"label.date-range": [
{
"type": 0,
"value": "Interval de date"
"value": "Interval"
}
],
"label.day": [
@ -284,7 +284,7 @@
"label.default-date-range": [
{
"type": 0,
"value": "Interval de date implicit"
"value": "Interval implicit"
}
],
"label.delete": [
@ -314,7 +314,7 @@
"label.delete-website": [
{
"type": 0,
"value": "Ștergere site web"
"value": "Șterge site web"
}
],
"label.description": [
@ -398,13 +398,13 @@
"label.end-step": [
{
"type": 0,
"value": "End Step"
"value": "Pas final"
}
],
"label.entry": [
{
"type": 0,
"value": "Entry URL"
"value": "URL de intrare"
}
],
"label.event": [
@ -428,7 +428,7 @@
"label.exit": [
{
"type": 0,
"value": "Exit URL"
"value": "URL de ieșire"
}
],
"label.false": [
@ -476,7 +476,7 @@
"label.first-seen": [
{
"type": 0,
"value": "First seen"
"value": "Văzut pentru prima dată"
}
],
"label.funnel": [
@ -494,19 +494,19 @@
"label.goal": [
{
"type": 0,
"value": "Goal"
"value": "Obiectiv"
}
],
"label.goals": [
{
"type": 0,
"value": "Goals"
"value": "Obiective"
}
],
"label.goals-description": [
{
"type": 0,
"value": "Track your goals for pageviews and events."
"value": "Urmărește obiectivele de vizualizări și evenimente."
}
],
"label.greater-than": [
@ -584,13 +584,13 @@
"label.journey": [
{
"type": 0,
"value": "Journey"
"value": "Traseu"
}
],
"label.journey-description": [
{
"type": 0,
"value": "Understand how users navigate through your website."
"value": "Înțelege cum navighează vizitatorii prin website."
}
],
"label.language": [
@ -642,7 +642,7 @@
"label.last-months": [
{
"type": 0,
"value": "Last "
"value": "Ultimele "
},
{
"type": 1,
@ -650,13 +650,13 @@
},
{
"type": 0,
"value": " months"
"value": " luni"
}
],
"label.last-seen": [
{
"type": 0,
"value": "Last seen"
"value": "Văzut ultima dată"
}
],
"label.leave": [
@ -876,13 +876,13 @@
"label.path": [
{
"type": 0,
"value": "Path"
"value": "Rută"
}
],
"label.paths": [
{
"type": 0,
"value": "Paths"
"value": "Rute"
}
],
"label.powered-by": [
@ -898,19 +898,19 @@
"label.previous": [
{
"type": 0,
"value": "Previous"
"value": "Anterior"
}
],
"label.previous-period": [
{
"type": 0,
"value": "Previous period"
"value": "Perioda anterioară"
}
],
"label.previous-year": [
{
"type": 0,
"value": "Previous year"
"value": "Anul anterior"
}
],
"label.profile": [
@ -922,13 +922,13 @@
"label.properties": [
{
"type": 0,
"value": "Properties"
"value": "Proprietăți"
}
],
"label.property": [
{
"type": 0,
"value": "Property"
"value": "Proprietate"
}
],
"label.queries": [
@ -1042,13 +1042,13 @@
"label.revenue": [
{
"type": 0,
"value": "Revenue"
"value": "Venit"
}
],
"label.revenue-description": [
{
"type": 0,
"value": "Look into your revenue across time."
"value": "Urmărește venitul în timp."
}
],
"label.revenue-property": [
@ -1114,7 +1114,7 @@
"label.session": [
{
"type": 0,
"value": "Session"
"value": "Sesiune"
}
],
"label.sessions": [
@ -1144,13 +1144,13 @@
"label.start-step": [
{
"type": 0,
"value": "Start Step"
"value": "Pas de început"
}
],
"label.steps": [
{
"type": 0,
"value": "Steps"
"value": "Pași"
}
],
"label.sum": [
@ -1174,13 +1174,13 @@
"label.team-id": [
{
"type": 0,
"value": "ID Echipa"
"value": "ID Echipă"
}
],
"label.team-manager": [
{
"type": 0,
"value": "Team manager"
"value": "Manager echipă"
}
],
"label.team-member": [
@ -1288,7 +1288,7 @@
"label.transactions": [
{
"type": 0,
"value": "Transactions"
"value": "Tranzacții"
}
],
"label.transfer": [
@ -1330,7 +1330,7 @@
"label.uniqueCustomers": [
{
"type": 0,
"value": "Unique Customers"
"value": "Clienți unici"
}
],
"label.unknown": [
@ -1372,7 +1372,7 @@
"label.user-property": [
{
"type": 0,
"value": "User Property"
"value": "Proprietatea utilizatorului"
}
],
"label.username": [
@ -1396,7 +1396,7 @@
"label.utm-description": [
{
"type": 0,
"value": "Track your campaigns through UTM parameters."
"value": "Urmărește campaniile tale cu parametri UTM."
}
],
"label.value": [
@ -1432,7 +1432,7 @@
"label.views-per-visit": [
{
"type": 0,
"value": "Views per visit"
"value": "Vizualizări per vizită"
}
],
"label.visit-duration": [
@ -1450,7 +1450,7 @@
"label.visits": [
{
"type": 0,
"value": "Visits"
"value": "Vizite"
}
],
"label.website": [
@ -1534,7 +1534,7 @@
"message.collected-data": [
{
"type": 0,
"value": "Collected data"
"value": "Date colectate"
}
],
"message.confirm-delete": [

File diff suppressed because it is too large Load Diff

View File

@ -15,9 +15,9 @@ async function sendTelemetry(type) {
node: process.version,
platform: os.platform(),
arch: os.arch(),
os: `${os.type()} (${os.version()})`,
isDocker: isDocker(),
isCi: isCI,
os: `${os.type()} ${os.version()}`,
is_docker: isDocker(),
is_ci: isCI,
},
};

View File

@ -6,7 +6,7 @@ const path = require('path');
const endPoint = process.env.COLLECT_API_ENDPOINT;
if (endPoint) {
const file = path.resolve(__dirname, '../public/script.js');
const file = path.resolve(__dirname, '../public/tracker.js');
const tracker = fs.readFileSync(file);

View File

@ -48,6 +48,46 @@ export function TestConsole({ websiteId }: { websiteId: string }) {
});
}
function handleRunRevenue() {
window['umami'].track(props => ({
...props,
url: '/checkout-cart',
referrer: 'https://www.google.com',
}));
window['umami'].track('checkout-cart', {
revenue: parseFloat((Math.random() * 1000).toFixed(2)),
currency: 'USD',
});
window['umami'].track('affiliate-link', {
revenue: parseFloat((Math.random() * 1000).toFixed(2)),
currency: 'USD',
});
window['umami'].track('promotion-link', {
revenue: parseFloat((Math.random() * 1000).toFixed(2)),
currency: 'USD',
});
window['umami'].track('checkout-cart', {
revenue: parseFloat((Math.random() * 1000).toFixed(2)),
currency: 'EUR',
});
window['umami'].track('promotion-link', {
revenue: parseFloat((Math.random() * 1000).toFixed(2)),
currency: 'EUR',
});
window['umami'].track('affiliate-link', {
item1: {
productIdentity: 'ABC424',
revenue: parseFloat((Math.random() * 10000).toFixed(2)),
currency: 'JPY',
},
item2: {
productIdentity: 'ZYW684',
revenue: parseFloat((Math.random() * 10000).toFixed(2)),
currency: 'JPY',
},
});
}
function handleRunIdentify() {
window['umami'].identify({
userId: 123,
@ -127,10 +167,19 @@ export function TestConsole({ websiteId }: { websiteId: string }) {
>
Send event with data
</Button>
<Button
id="generate-revenue-button"
data-umami-event="checkout-cart"
data-umami-event-revenue={(Math.random() * 10000).toFixed(2).toString()}
data-umami-event-currency="USD"
variant="primary"
>
Generate revenue data
</Button>
<Button
id="button-with-div-button"
data-umami-event="button-click"
data-umami-event-name="bob"
data-umami-event-name={'bob'}
data-umami-event-id="123"
variant="primary"
>
@ -155,6 +204,9 @@ export function TestConsole({ websiteId }: { websiteId: string }) {
<Button id="manual-button" variant="primary" onClick={handleRunIdentify}>
Run identify
</Button>
<Button id="manual-button" variant="primary" onClick={handleRunRevenue}>
Revenue script
</Button>
</div>
</div>
<WebsiteChart websiteId={website.id} />

View File

@ -1,4 +1,4 @@
import { useState, useMemo } from 'react';
import { useState, useMemo, useEffect } from 'react';
import { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd';
import classNames from 'classnames';
import { Button, Loading } from 'react-basics';
@ -14,12 +14,25 @@ export function DashboardEdit({ teamId }: { teamId: string }) {
const { websiteOrder } = settings;
const { formatMessage, labels } = useMessages();
const [order, setOrder] = useState(websiteOrder || []);
const [websites, setWebsites] = useState([]);
const {
result,
query: { isLoading },
setParams,
} = useWebsites({ teamId });
const websites = result?.data;
useEffect(() => {
if (result?.data) {
setWebsites(prevWebsites => {
const newWebsites = [...prevWebsites, ...result.data];
if (newWebsites.length < result.count) {
setParams(prevParams => ({ ...prevParams, page: prevParams.page + 1 }));
}
return newWebsites;
});
}
}, [result]);
const ordered = useMemo(() => {
if (websites) {

View File

@ -1,4 +1,5 @@
import Funnel from 'assets/funnel.svg';
import Money from 'assets/money.svg';
import Lightbulb from 'assets/lightbulb.svg';
import Magnet from 'assets/magnet.svg';
import Path from 'assets/path.svg';
@ -51,12 +52,12 @@ export function ReportTemplates({ showHeader = true }: { showHeader?: boolean })
url: renderTeamUrl('/reports/journey'),
icon: <Path />,
},
// {
// title: formatMessage(labels.revenue),
// description: formatMessage(labels.revenueDescription),
// url: renderTeamUrl('/reports/revenue'),
// icon: <Money />,
// },
{
title: formatMessage(labels.revenue),
description: formatMessage(labels.revenueDescription),
url: renderTeamUrl('/reports/revenue'),
icon: <Money />,
},
];
return (

View File

@ -1,98 +0,0 @@
import BarChart, { BarChartProps } from 'components/charts/BarChart';
import { useLocale, useMessages } from 'components/hooks';
import MetricCard from 'components/metrics/MetricCard';
import MetricsBar from 'components/metrics/MetricsBar';
import { renderDateLabels } from 'lib/charts';
import { formatLongNumber } from 'lib/format';
import { useContext, useMemo } from 'react';
import { ReportContext } from '../[reportId]/Report';
export interface PageviewsChartProps extends BarChartProps {
isLoading?: boolean;
}
export function RevenueChart({ isLoading, ...props }: PageviewsChartProps) {
const { formatMessage, labels } = useMessages();
const { locale } = useLocale();
const { report } = useContext(ReportContext);
const { data, parameters } = report || {};
const chartData = useMemo(() => {
if (!data) {
return {};
}
return {
datasets: [
{
label: formatMessage(labels.average),
data: data?.chart.map(a => ({ x: a.time, y: a.avg })),
borderWidth: 2,
backgroundColor: '#8601B0',
borderColor: '#8601B0',
order: 1,
},
{
label: formatMessage(labels.total),
data: data?.chart.map(a => ({ x: a.time, y: a.sum })),
borderWidth: 2,
backgroundColor: '#f15bb5',
borderColor: '#f15bb5',
order: 2,
},
],
};
}, [data, locale]);
const metricData = useMemo(() => {
if (!data) {
return [];
}
const { sum, avg, count, uniqueCount } = data.total;
return [
{
value: sum,
label: formatMessage(labels.total),
formatValue: formatLongNumber,
},
{
value: avg,
label: formatMessage(labels.average),
formatValue: formatLongNumber,
},
{
value: count,
label: formatMessage(labels.transactions),
formatValue: formatLongNumber,
},
{
value: uniqueCount,
label: formatMessage(labels.uniqueCustomers),
formatValue: formatLongNumber,
},
] as any;
}, [data, locale]);
return (
<>
<MetricsBar isFetched={data}>
{metricData?.map(({ label, value, formatValue }) => {
return <MetricCard key={label} value={value} label={label} formatValue={formatValue} />;
})}
</MetricsBar>
{data && (
<BarChart
{...props}
data={chartData}
unit={parameters?.dateRange.unit}
isLoading={isLoading}
renderXLabel={renderDateLabels(parameters?.dateRange.unit, locale)}
/>
)}
</>
);
}
export default RevenueChart;

View File

@ -1,46 +1,41 @@
import { useMessages } from 'components/hooks';
import useRevenueValues from 'components/hooks/queries/useRevenueValues';
import { useContext } from 'react';
import { Form, FormButtons, FormInput, FormRow, SubmitButton, TextField } from 'react-basics';
import { Dropdown, Form, FormButtons, FormInput, FormRow, Item, SubmitButton } from 'react-basics';
import BaseParameters from '../[reportId]/BaseParameters';
import { ReportContext } from '../[reportId]/Report';
export function RevenueParameters() {
const { report, runReport, isRunning } = useContext(ReportContext);
const { formatMessage, labels } = useMessages();
const { id, parameters } = report || {};
const { websiteId, dateRange } = parameters || {};
const queryDisabled = !websiteId || !dateRange;
const queryEnabled = websiteId && dateRange;
const { data: values = [] } = useRevenueValues(
websiteId,
dateRange?.startDate,
dateRange?.endDate,
);
const handleSubmit = (data: any, e: any) => {
e.stopPropagation();
e.preventDefault();
if (!queryDisabled) {
runReport(data);
}
runReport(data);
};
return (
<Form values={parameters} onSubmit={handleSubmit} preventSubmit={true}>
<BaseParameters showDateSelect={true} allowWebsiteSelect={!id} />
<FormRow label={formatMessage(labels.event)}>
<FormInput name="eventName" rules={{ required: formatMessage(labels.required) }}>
<TextField autoComplete="off" />
</FormInput>
</FormRow>
<FormRow label={formatMessage(labels.revenueProperty)}>
<FormInput name="revenueProperty" rules={{ required: formatMessage(labels.required) }}>
<TextField autoComplete="off" />
</FormInput>
</FormRow>
<FormRow label={formatMessage(labels.userProperty)}>
<FormInput name="userProperty">
<TextField autoComplete="off" />
<FormRow label={formatMessage(labels.currency)}>
<FormInput name="currency" rules={{ required: formatMessage(labels.required) }}>
<Dropdown items={values.map(item => item.currency)}>
{item => <Item key={item}>{item}</Item>}
</Dropdown>
</FormInput>
</FormRow>
<FormButtons>
<SubmitButton variant="primary" disabled={queryDisabled} isLoading={isRunning}>
<SubmitButton variant="primary" disabled={!queryEnabled} isLoading={isRunning}>
{formatMessage(labels.runQuery)}
</SubmitButton>
</FormButtons>

View File

@ -1,10 +0,0 @@
.filters {
display: flex;
flex-direction: column;
justify-content: space-between;
border: 1px solid var(--base400);
border-radius: var(--border-radius);
line-height: 32px;
padding: 10px;
overflow: hidden;
}

View File

@ -1,15 +1,15 @@
import RevenueChart from './RevenueChart';
import RevenueParameters from './RevenueParameters';
import Report from '../[reportId]/Report';
import ReportHeader from '../[reportId]/ReportHeader';
import ReportMenu from '../[reportId]/ReportMenu';
import ReportBody from '../[reportId]/ReportBody';
import Money from 'assets/money.svg';
import { REPORT_TYPES } from 'lib/constants';
import Report from '../[reportId]/Report';
import ReportBody from '../[reportId]/ReportBody';
import ReportHeader from '../[reportId]/ReportHeader';
import ReportMenu from '../[reportId]/ReportMenu';
import RevenueParameters from './RevenueParameters';
import RevenueView from './RevenueView';
const defaultParameters = {
type: REPORT_TYPES.revenue,
parameters: { Revenue: [] },
parameters: {},
};
export default function RevenueReport({ reportId }: { reportId?: string }) {
@ -20,7 +20,7 @@ export default function RevenueReport({ reportId }: { reportId?: string }) {
<RevenueParameters />
</ReportMenu>
<ReportBody>
<RevenueChart unit="day" />
<RevenueView />
</ReportBody>
</Report>
);

View File

@ -0,0 +1,38 @@
import EmptyPlaceholder from 'components/common/EmptyPlaceholder';
import { useMessages } from 'components/hooks';
import { useContext } from 'react';
import { GridColumn, GridTable } from 'react-basics';
import { ReportContext } from '../[reportId]/Report';
import { formatLongCurrency } from 'lib/format';
export function RevenueTable() {
const { report } = useContext(ReportContext);
const { formatMessage, labels } = useMessages();
const { data } = report || {};
if (!data) {
return <EmptyPlaceholder />;
}
return (
<GridTable data={data.table || []}>
<GridColumn name="currency" label={formatMessage(labels.currency)} alignment="end">
{row => row.currency}
</GridColumn>
<GridColumn name="currency" label={formatMessage(labels.total)} width="300px" alignment="end">
{row => formatLongCurrency(row.sum, row.currency)}
</GridColumn>
<GridColumn name="currency" label={formatMessage(labels.average)} alignment="end">
{row => formatLongCurrency(row.count ? row.sum / row.count : 0, row.currency)}
</GridColumn>
<GridColumn name="currency" label={formatMessage(labels.transactions)} alignment="end">
{row => row.count}
</GridColumn>
<GridColumn name="currency" label={formatMessage(labels.uniqueCustomers)} alignment="end">
{row => row.unique_count}
</GridColumn>
</GridTable>
);
}
export default RevenueTable;

View File

@ -0,0 +1,11 @@
.container {
display: grid;
gap: 20px;
margin-bottom: 40px;
}
.row {
display: flex;
align-items: center;
gap: 10px;
}

View File

@ -0,0 +1,156 @@
import classNames from 'classnames';
import { colord } from 'colord';
import BarChart from 'components/charts/BarChart';
import PieChart from 'components/charts/PieChart';
import TypeIcon from 'components/common/TypeIcon';
import { useCountryNames, useLocale, useMessages } from 'components/hooks';
import { GridRow } from 'components/layout/Grid';
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 } from 'lib/constants';
import { formatLongCurrency, formatLongNumber } from 'lib/format';
import { useCallback, useContext, useMemo } from 'react';
import { ReportContext } from '../[reportId]/Report';
import RevenueTable from './RevenueTable';
import styles from './RevenueView.module.css';
export interface RevenueViewProps {
isLoading?: boolean;
}
export function RevenueView({ isLoading }: RevenueViewProps) {
const { formatMessage, labels } = useMessages();
const { locale } = useLocale();
const { countryNames } = useCountryNames(locale);
const { report } = useContext(ReportContext);
const {
data,
parameters: { dateRange, currency },
} = report || {};
const showTable = data?.table.length > 1;
const renderCountryName = useCallback(
({ x: code }) => (
<span className={classNames(locale, styles.row)}>
<TypeIcon type="country" value={code?.toLowerCase()} />
{countryNames[code]}
</span>
),
[countryNames, locale],
);
const chartData = useMemo(() => {
if (!data) return [];
const map = (data.chart as any[]).reduce((obj, { x, t, y }) => {
if (!obj[x]) {
obj[x] = [];
}
obj[x].push({ x: t, y });
return obj;
}, {});
return {
datasets: Object.keys(map).map((key, index) => {
const color = colord(CHART_COLORS[index % CHART_COLORS.length]);
return {
label: key,
data: map[key],
lineTension: 0,
backgroundColor: color.alpha(0.6).toRgbString(),
borderColor: color.alpha(0.7).toRgbString(),
borderWidth: 1,
};
}),
};
}, [data]);
const countryData = useMemo(() => {
if (!data) return [];
const labels = data.country.map(({ name }) => name);
const datasets = [
{
data: data.country.map(({ value }) => value),
backgroundColor: CHART_COLORS,
borderWidth: 0,
},
];
return { labels, datasets };
}, [data]);
const metricData = useMemo(() => {
if (!data) return [];
const { sum, count, unique_count } = data.total;
return [
{
value: sum,
label: formatMessage(labels.total),
formatValue: n => formatLongCurrency(n, currency),
},
{
value: count ? sum / count : 0,
label: formatMessage(labels.average),
formatValue: n => formatLongCurrency(n, currency),
},
{
value: count,
label: formatMessage(labels.transactions),
formatValue: formatLongNumber,
},
{
value: unique_count,
label: formatMessage(labels.uniqueCustomers),
formatValue: formatLongNumber,
},
] as any;
}, [data, locale]);
return (
<>
<div className={styles.container}>
<MetricsBar isFetched={data}>
{metricData?.map(({ label, value, formatValue }) => {
return <MetricCard key={label} value={value} label={label} formatValue={formatValue} />;
})}
</MetricsBar>
{data && (
<>
<BarChart
minDate={dateRange?.startDate}
maxDate={dateRange?.endDate}
data={chartData}
unit={dateRange?.unit}
stacked={true}
currency={currency}
renderXLabel={renderDateLabels(dateRange?.unit, locale)}
isLoading={isLoading}
/>
<GridRow columns="two">
<ListTable
metric={formatMessage(labels.country)}
data={data?.country.map(({ name, value }) => ({
x: name,
y: Number(value),
z: (value / data?.total.sum) * 100,
}))}
renderLabel={renderCountryName}
/>
<PieChart type="doughnut" data={countryData} />
</GridRow>
</>
)}
{showTable && <RevenueTable />}
</div>
</>
);
}
export default RevenueView;

View File

@ -59,5 +59,6 @@
align-items: center;
justify-content: space-between;
padding-inline-end: 0;
z-index: 10;
}
}

View File

@ -1,20 +1,21 @@
import { Icons, Icon, Text, Dropdown, Item } from 'react-basics';
import LinkButton from 'components/common/LinkButton';
import { useLocale, useMessages, useNavigation } from 'components/hooks';
import SideNav from 'components/layout/SideNav';
import BrowsersTable from 'components/metrics/BrowsersTable';
import CountriesTable from 'components/metrics/CountriesTable';
import RegionsTable from 'components/metrics/RegionsTable';
import CitiesTable from 'components/metrics/CitiesTable';
import CountriesTable from 'components/metrics/CountriesTable';
import DevicesTable from 'components/metrics/DevicesTable';
import EventsTable from 'components/metrics/EventsTable';
import HostsTable from 'components/metrics/HostsTable';
import LanguagesTable from 'components/metrics/LanguagesTable';
import OSTable from 'components/metrics/OSTable';
import PagesTable from 'components/metrics/PagesTable';
import QueryParametersTable from 'components/metrics/QueryParametersTable';
import ReferrersTable from 'components/metrics/ReferrersTable';
import HostsTable from 'components/metrics/HostsTable';
import RegionsTable from 'components/metrics/RegionsTable';
import ScreenTable from 'components/metrics/ScreenTable';
import EventsTable from 'components/metrics/EventsTable';
import SideNav from 'components/layout/SideNav';
import { useNavigation, useMessages, useLocale } from 'components/hooks';
import LinkButton from 'components/common/LinkButton';
import TagsTable from 'components/metrics/TagsTable';
import { Dropdown, Icon, Icons, Item, Text } from 'react-basics';
import styles from './WebsiteExpandedView.module.css';
const views = {
@ -34,6 +35,7 @@ const views = {
language: LanguagesTable,
event: EventsTable,
query: QueryParametersTable,
tag: TagsTable,
};
export default function WebsiteExpandedView({
@ -117,6 +119,11 @@ export default function WebsiteExpandedView({
label: formatMessage(labels.hosts),
url: renderUrl({ view: 'host' }),
},
{
key: 'tag',
label: formatMessage(labels.tags),
url: renderUrl({ view: 'tag' }),
},
];
const DetailsComponent = views[view] || (() => null);

View File

@ -1,24 +1,25 @@
import { useState } from 'react';
import SideNav from 'components/layout/SideNav';
import { useDateRange, useMessages, useNavigation } from 'components/hooks';
import PagesTable from 'components/metrics/PagesTable';
import ReferrersTable from 'components/metrics/ReferrersTable';
import BrowsersTable from 'components/metrics/BrowsersTable';
import OSTable from 'components/metrics/OSTable';
import DevicesTable from 'components/metrics/DevicesTable';
import ScreenTable from 'components/metrics/ScreenTable';
import CountriesTable from 'components/metrics/CountriesTable';
import RegionsTable from 'components/metrics/RegionsTable';
import CitiesTable from 'components/metrics/CitiesTable';
import LanguagesTable from 'components/metrics/LanguagesTable';
import EventsTable from 'components/metrics/EventsTable';
import QueryParametersTable from 'components/metrics/QueryParametersTable';
import { Grid, GridRow } from 'components/layout/Grid';
import SideNav from 'components/layout/SideNav';
import BrowsersTable from 'components/metrics/BrowsersTable';
import ChangeLabel from 'components/metrics/ChangeLabel';
import CitiesTable from 'components/metrics/CitiesTable';
import CountriesTable from 'components/metrics/CountriesTable';
import DevicesTable from 'components/metrics/DevicesTable';
import EventsTable from 'components/metrics/EventsTable';
import LanguagesTable from 'components/metrics/LanguagesTable';
import MetricsTable from 'components/metrics/MetricsTable';
import useStore from 'store/websites';
import OSTable from 'components/metrics/OSTable';
import PagesTable from 'components/metrics/PagesTable';
import QueryParametersTable from 'components/metrics/QueryParametersTable';
import ReferrersTable from 'components/metrics/ReferrersTable';
import RegionsTable from 'components/metrics/RegionsTable';
import ScreenTable from 'components/metrics/ScreenTable';
import TagsTable from 'components/metrics/TagsTable';
import { getCompareDate } from 'lib/date';
import { formatNumber } from 'lib/format';
import ChangeLabel from 'components/metrics/ChangeLabel';
import { useState } from 'react';
import useStore from 'store/websites';
import styles from './WebsiteCompareTables.module.css';
const views = {
@ -35,6 +36,7 @@ const views = {
language: LanguagesTable,
event: EventsTable,
query: QueryParametersTable,
tag: TagsTable,
};
export function WebsiteCompareTables({ websiteId }: { websiteId: string }) {
@ -109,6 +111,16 @@ export function WebsiteCompareTables({ websiteId }: { websiteId: string }) {
label: formatMessage(labels.queryParameters),
url: renderUrl({ view: 'query' }),
},
{
key: 'host',
label: formatMessage(labels.hosts),
url: renderUrl({ view: 'host' }),
},
{
key: 'tag',
label: formatMessage(labels.tags),
url: renderUrl({ view: 'tag' }),
},
];
const renderChange = ({ x, y }) => {

View File

@ -54,7 +54,7 @@ export function EventProperties({ websiteId }: { websiteId: string }) {
{propertyName && (
<div className={styles.chart}>
<div className={styles.title}>{propertyName}</div>
<PieChart key={propertyName} type="doughnut" data={chartData} />
<PieChart key={propertyName + eventName} type="doughnut" data={chartData} />
</div>
)}
</div>

View File

@ -12,7 +12,7 @@ export function RealtimeCountries({ data }) {
const renderCountryName = useCallback(
({ x: code }) => (
<span className={classNames(locale, styles.row)}>
<span className={classNames(styles.row)}>
<TypeIcon type="country" value={code?.toLowerCase()} />
{countryNames[code]}
</span>

View File

@ -4,7 +4,6 @@ import '@fontsource/inter/400.css';
import '@fontsource/inter/700.css';
import '@fontsource/inter/800.css';
import 'react-basics/dist/styles.css';
import 'styles/locale.css';
import 'styles/index.css';
import 'styles/variables.css';

View File

@ -7,6 +7,7 @@ import { useMemo, useState } from 'react';
export interface BarChartProps extends ChartProps {
unit: string;
stacked?: boolean;
currency?: string;
renderXLabel?: (label: string, index: number, values: any[]) => string;
renderYLabel?: (label: string, index: number, values: any[]) => string;
XAxisType?: string;
@ -27,6 +28,7 @@ export function BarChart(props: BarChartProps) {
stacked = false,
minDate,
maxDate,
currency,
} = props;
const options: any = useMemo(() => {
@ -76,7 +78,9 @@ export function BarChart(props: BarChartProps) {
const handleTooltip = ({ tooltip }: { tooltip: any }) => {
const { opacity } = tooltip;
setTooltip(opacity ? <BarChartTooltip tooltip={tooltip} unit={unit} /> : null);
setTooltip(
opacity ? <BarChartTooltip tooltip={tooltip} unit={unit} currency={currency} /> : null,
);
};
return (

View File

@ -1,7 +1,7 @@
import { formatDate } from 'lib/date';
import { Flexbox, StatusLight } from 'react-basics';
import { formatLongNumber } from 'lib/format';
import { useLocale } from 'components/hooks';
import { formatDate } from 'lib/date';
import { formatLongCurrency, formatLongNumber } from 'lib/format';
import { Flexbox, StatusLight } from 'react-basics';
const formats = {
millisecond: 'T',
@ -15,7 +15,7 @@ const formats = {
year: 'yyyy',
};
export default function BarChartTooltip({ tooltip, unit }) {
export default function BarChartTooltip({ tooltip, unit, currency }) {
const { locale } = useLocale();
const { labelColors, dataPoints } = tooltip;
@ -26,7 +26,10 @@ export default function BarChartTooltip({ tooltip, unit }) {
</div>
<div>
<StatusLight color={labelColors?.[0]?.backgroundColor}>
{formatLongNumber(dataPoints[0].raw.y)} {dataPoints[0].dataset.label}
{currency
? formatLongCurrency(dataPoints[0].raw.y, currency)
: formatLongNumber(dataPoints[0].raw.y)}{' '}
{dataPoints[0].dataset.label}
</StatusLight>
</div>
</Flexbox>

View File

@ -1,9 +1,7 @@
export * from './queries/useApi';
export * from './queries/useConfig';
export * from './queries/useEventDataEvents';
export * from './queries/useEventDataProperties';
export * from './queries/useEventDataValues';
export * from './queries/usePagedQuery';
export * from './queries/useLogin';
export * from './queries/useRealtime';
export * from './queries/useReport';
@ -28,6 +26,7 @@ export * from './queries/useWebsiteEvents';
export * from './queries/useWebsiteEventsSeries';
export * from './queries/useWebsiteMetrics';
export * from './queries/useWebsiteValues';
export * from './useApi';
export * from './useCountryNames';
export * from './useDateRange';
export * from './useDocumentClick';
@ -41,6 +40,7 @@ export * from './useLocale';
export * from './useMessages';
export * from './useModified';
export * from './useNavigation';
export * from './usePagedQuery';
export * from './useRegionNames';
export * from './useSticky';
export * from './useTeamUrl';

View File

@ -1,6 +1,6 @@
import { useEffect } from 'react';
import useStore, { setConfig } from 'store/app';
import { useApi } from './useApi';
import { useApi } from '../useApi';
let loading = false;

View File

@ -1,4 +1,4 @@
import useApi from './useApi';
import { useApi } from '../useApi';
import { UseQueryOptions } from '@tanstack/react-query';
import { useFilterParams } from '../useFilterParams';

View File

@ -1,5 +1,5 @@
import useApi from './useApi';
import { UseQueryOptions } from '@tanstack/react-query';
import { useApi } from '../useApi';
import { useFilterParams } from '../useFilterParams';
export function useEventDataProperties(

View File

@ -1,5 +1,5 @@
import useApi from './useApi';
import { UseQueryOptions } from '@tanstack/react-query';
import { useApi } from '../useApi';
import { useFilterParams } from '../useFilterParams';
export function useEventDataValues(
@ -12,7 +12,7 @@ export function useEventDataValues(
const params = useFilterParams(websiteId);
return useQuery<any>({
queryKey: ['websites:event-data:values', { websiteId, propertyName, ...params }],
queryKey: ['websites:event-data:values', { websiteId, eventName, propertyName, ...params }],
queryFn: () =>
get(`/websites/${websiteId}/event-data/values`, { ...params, eventName, propertyName }),
enabled: !!(websiteId && propertyName),

View File

@ -1,6 +1,6 @@
import useStore, { setUser } from 'store/app';
import useApi from './useApi';
import { UseQueryResult } from '@tanstack/react-query';
import useStore, { setUser } from 'store/app';
import { useApi } from '../useApi';
const selector = (state: { user: any }) => state.user;

View File

@ -1,7 +1,7 @@
import { useTimezone } from 'components/hooks';
import { REALTIME_INTERVAL } from 'lib/constants';
import { RealtimeData } from 'lib/types';
import { useApi } from './useApi';
import { useApi } from '../useApi';
export function useRealtime(websiteId: string) {
const { get, useQuery } = useApi();

View File

@ -1,6 +1,6 @@
import { produce } from 'immer';
import { useCallback, useEffect, useState } from 'react';
import { useApi } from './useApi';
import { useApi } from '../useApi';
import { useTimezone } from '../useTimezone';
import { useMessages } from '../useMessages';

View File

@ -1,5 +1,5 @@
import useApi from './useApi';
import usePagedQuery from './usePagedQuery';
import useApi from '../useApi';
import usePagedQuery from '../usePagedQuery';
import useModified from '../useModified';
export function useReports({ websiteId, teamId }: { websiteId?: string; teamId?: string }) {

View File

@ -0,0 +1,18 @@
import { useApi } from '../useApi';
export function useRevenueValues(websiteId: string, startDate: Date, endDate: Date) {
const { get, useQuery } = useApi();
return useQuery({
queryKey: ['revenue:values', { websiteId, startDate, endDate }],
queryFn: () =>
get(`/reports/revenue`, {
websiteId,
startDate,
endDate,
}),
enabled: !!(websiteId && startDate && endDate),
});
}
export default useRevenueValues;

View File

@ -1,4 +1,4 @@
import { useApi } from './useApi';
import { useApi } from '../useApi';
export function useSessionActivity(
websiteId: string,

View File

@ -1,4 +1,4 @@
import { useApi } from './useApi';
import { useApi } from '../useApi';
export function useSessionData(websiteId: string, sessionId: string) {
const { get, useQuery } = useApi();

View File

@ -1,4 +1,4 @@
import useApi from './useApi';
import { useApi } from '../useApi';
import { UseQueryOptions } from '@tanstack/react-query';
import { useFilterParams } from '../useFilterParams';

View File

@ -1,4 +1,4 @@
import useApi from './useApi';
import { useApi } from '../useApi';
import { UseQueryOptions } from '@tanstack/react-query';
import { useFilterParams } from '../useFilterParams';

View File

@ -1,5 +1,5 @@
import useStore, { setShareToken } from 'store/app';
import useApi from './useApi';
import { useApi } from '../useApi';
const selector = (state: { shareToken: string }) => state.shareToken;

View File

@ -1,4 +1,4 @@
import useApi from './useApi';
import { useApi } from '../useApi';
export function useTeam(teamId: string) {
const { get, useQuery } = useApi();

View File

@ -1,5 +1,5 @@
import useApi from './useApi';
import usePagedQuery from './usePagedQuery';
import { useApi } from '../useApi';
import usePagedQuery from '../usePagedQuery';
import useModified from '../useModified';
export function useTeamMembers(teamId: string) {

View File

@ -1,5 +1,5 @@
import useApi from './useApi';
import usePagedQuery from './usePagedQuery';
import { useApi } from '../useApi';
import { usePagedQuery } from '../usePagedQuery';
import useModified from '../useModified';
export function useTeamWebsites(teamId: string) {

View File

@ -1,5 +1,5 @@
import useApi from './useApi';
import usePagedQuery from './usePagedQuery';
import { useApi } from '../useApi';
import { usePagedQuery } from '../usePagedQuery';
import useModified from '../useModified';
export function useTeams(userId: string) {

View File

@ -1,4 +1,4 @@
import useApi from './useApi';
import { useApi } from '../useApi';
export function useUser(userId: string, options?: { [key: string]: any }) {
const { get, useQuery } = useApi();

View File

@ -1,5 +1,5 @@
import useApi from './useApi';
import usePagedQuery from './usePagedQuery';
import { useApi } from '../useApi';
import { usePagedQuery } from '../usePagedQuery';
import useModified from '../useModified';
export function useUsers() {

View File

@ -1,4 +1,4 @@
import useApi from './useApi';
import { useApi } from '../useApi';
export function useWebsite(websiteId: string, options?: { [key: string]: any }) {
const { get, useQuery } = useApi();

View File

@ -1,7 +1,7 @@
import useApi from './useApi';
import { useApi } from '../useApi';
import { UseQueryOptions } from '@tanstack/react-query';
import { useFilterParams } from '../useFilterParams';
import { usePagedQuery } from './usePagedQuery';
import { usePagedQuery } from '../usePagedQuery';
export function useWebsiteEvents(
websiteId: string,

View File

@ -1,4 +1,4 @@
import useApi from './useApi';
import { useApi } from '../useApi';
import { UseQueryOptions } from '@tanstack/react-query';
import { useFilterParams } from '../useFilterParams';

View File

@ -1,6 +1,7 @@
import { UseQueryOptions } from '@tanstack/react-query';
import useApi from './useApi';
import { useApi } from '../useApi';
import { useFilterParams } from '../useFilterParams';
import { useSearchParams } from 'next/navigation';
export function useWebsiteMetrics(
websiteId: string,
@ -9,6 +10,7 @@ export function useWebsiteMetrics(
) {
const { get, useQuery } = useApi();
const params = useFilterParams(websiteId);
const searchParams = useSearchParams();
return useQuery({
queryKey: [
@ -20,12 +22,9 @@ export function useWebsiteMetrics(
},
],
queryFn: async () => {
const filters = { ...params };
filters[queryParams.type] = undefined;
const data = await get(`/websites/${websiteId}/metrics`, {
...filters,
...params,
[searchParams.get('view')]: undefined,
...queryParams,
});

View File

@ -1,5 +1,5 @@
import { UseQueryOptions } from '@tanstack/react-query';
import { useApi } from './useApi';
import { useApi } from '../useApi';
import { useFilterParams } from '..//useFilterParams';
export function useWebsitePageviews(

View File

@ -1,4 +1,4 @@
import { useApi } from './useApi';
import { useApi } from '../useApi';
export function useWebsiteSession(websiteId: string, sessionId: string) {
const { get, useQuery } = useApi();

View File

@ -1,4 +1,4 @@
import { useApi } from './useApi';
import { useApi } from '../useApi';
import { useFilterParams } from '../useFilterParams';
export function useWebsiteSessionStats(websiteId: string, options?: { [key: string]: string }) {

View File

@ -1,5 +1,5 @@
import { useApi } from './useApi';
import { usePagedQuery } from './usePagedQuery';
import { useApi } from '../useApi';
import { usePagedQuery } from '../usePagedQuery';
import useModified from '../useModified';
import { useFilterParams } from 'components/hooks/useFilterParams';

View File

@ -1,4 +1,4 @@
import { useApi } from './useApi';
import { useApi } from '../useApi';
import useModified from '../useModified';
import { useFilterParams } from 'components/hooks/useFilterParams';

View File

@ -1,4 +1,4 @@
import { useApi } from './useApi';
import { useApi } from '../useApi';
import { useFilterParams } from '../useFilterParams';
export function useWebsiteStats(

View File

@ -1,4 +1,4 @@
import { useApi } from './useApi';
import { useApi } from '../useApi';
export function useWebsiteValues({
websiteId,

View File

@ -1,5 +1,5 @@
import { useApi } from './useApi';
import { usePagedQuery } from './usePagedQuery';
import { useApi } from '../useApi';
import { usePagedQuery } from '../usePagedQuery';
import { useLogin } from './useLogin';
import useModified from '../useModified';

View File

@ -5,7 +5,7 @@ import websiteStore, { setWebsiteDateRange, setWebsiteDateCompare } from 'store/
import appStore, { setDateRange } from 'store/app';
import { DateRange } from 'lib/types';
import { useLocale } from './useLocale';
import { useApi } from './queries/useApi';
import { useApi } from './useApi';
export function useDateRange(websiteId?: string): {
dateRange: DateRange;

View File

@ -15,6 +15,7 @@ export function useFields() {
{ name: 'region', type: 'string', label: formatMessage(labels.region) },
{ name: 'city', type: 'string', label: formatMessage(labels.city) },
{ name: 'host', type: 'string', label: formatMessage(labels.host) },
{ name: 'tag', type: 'string', label: formatMessage(labels.tag) },
];
return { fields };

View File

@ -7,7 +7,21 @@ export function useFilterParams(websiteId: string) {
const { startDate, endDate, unit } = dateRange;
const { timezone, toUtc } = useTimezone();
const {
query: { url, referrer, title, query, host, os, browser, device, country, region, city, event },
query: {
url,
referrer,
title,
query,
host,
os,
browser,
device,
country,
region,
city,
event,
tag,
},
} = useNavigation();
return {
@ -27,5 +41,6 @@ export function useFilterParams(websiteId: string) {
region,
city,
event,
tag,
};
}

View File

@ -1,8 +1,8 @@
import { UseQueryOptions } from '@tanstack/react-query';
import { useState } from 'react';
import { useApi } from './useApi';
import { PageResult, PageParams, PagedQueryResult } from 'lib/types';
import { useNavigation } from '../useNavigation';
import { useApi } from './useApi';
import { useNavigation } from './useNavigation';
export function usePagedQuery<T = any>({
queryKey,

View File

@ -1,4 +1,4 @@
import { Icon, Button, PopupTrigger, Popup, Text } from 'react-basics';
import { Icon, Button, PopupTrigger, Popup } from 'react-basics';
import classNames from 'classnames';
import { languages } from 'lib/lang';
import { useLocale } from 'components/hooks';
@ -33,7 +33,7 @@ export function LanguageButton() {
className={classNames(styles.item, { [styles.selected]: value === locale })}
onClick={(e: any) => handleSelect(value, close, e)}
>
<Text>{label}</Text>
<span lang={value}>{label}</span>
{value === locale && (
<Icon className={styles.icon}>
<Icons.Check />

View File

@ -98,6 +98,7 @@ export const labels = defineMessages({
devices: { id: 'label.devices', defaultMessage: 'Devices' },
countries: { id: 'label.countries', defaultMessage: 'Countries' },
languages: { id: 'label.languages', defaultMessage: 'Languages' },
tags: { id: 'label.tags', defaultMessage: 'Tags' },
count: { id: 'label.count', defaultMessage: 'Count' },
average: { id: 'label.average', defaultMessage: 'Average' },
sum: { id: 'label.sum', defaultMessage: 'Sum' },
@ -114,8 +115,6 @@ export const labels = defineMessages({
none: { id: 'label.none', defaultMessage: 'None' },
clearAll: { id: 'label.clear-all', defaultMessage: 'Clear all' },
property: { id: 'label.property', defaultMessage: 'Property' },
revenueProperty: { id: 'label.revenue-property', defaultMessage: 'Revenue Property' },
userProperty: { id: 'label.user-property', defaultMessage: 'User Property' },
today: { id: 'label.today', defaultMessage: 'Today' },
lastHours: { id: 'label.last-hours', defaultMessage: 'Last {x} hours' },
yesterday: { id: 'label.yesterday', defaultMessage: 'Yesterday' },
@ -153,6 +152,7 @@ export const labels = defineMessages({
regions: { id: 'label.regions', defaultMessage: 'Regions' },
reports: { id: 'label.reports', defaultMessage: 'Reports' },
eventData: { id: 'label.event-data', defaultMessage: 'Event data' },
sessionData: { id: 'label.session-data', defaultMessage: 'Session data' },
funnel: { id: 'label.funnel', defaultMessage: 'Funnel' },
funnelDescription: {
id: 'label.funnel-description',
@ -161,8 +161,9 @@ export const labels = defineMessages({
revenue: { id: 'label.revenue', defaultMessage: 'Revenue' },
revenueDescription: {
id: 'label.revenue-description',
defaultMessage: 'Look into your revenue across time.',
defaultMessage: 'Look into your revenue data and how users are spending.',
},
currency: { id: 'label.currency', defaultMessage: 'Currency' },
url: { id: 'label.url', defaultMessage: 'URL' },
urls: { id: 'label.urls', defaultMessage: 'URLs' },
path: { id: 'label.path', defaultMessage: 'Path' },
@ -220,6 +221,7 @@ export const labels = defineMessages({
browser: { id: 'label.browser', defaultMessage: 'Browser' },
device: { id: 'label.device', defaultMessage: 'Device' },
pageTitle: { id: 'label.pageTitle', defaultMessage: 'Page title' },
tag: { id: 'label.tag', defaultMessage: 'Tag' },
day: { id: 'label.day', defaultMessage: 'Day' },
date: { id: 'label.date', defaultMessage: 'Date' },
pageOf: { id: 'label.page-of', defaultMessage: 'Page {current} of {total}' },

View File

@ -12,12 +12,7 @@ export function CountriesTable({ ...props }: MetricsTableProps) {
const renderLink = ({ x: code }) => {
return (
<FilterLink
id="country"
className={locale}
value={countryNames[code] && code}
label={formatCountry(code)}
>
<FilterLink id="country" value={countryNames[code] && code} label={formatCountry(code)}>
<TypeIcon type="country" value={code?.toLowerCase()} />
</FilterLink>
);

View File

@ -25,7 +25,7 @@ export function HostsTable(props: MetricsTableProps) {
{...props}
title={formatMessage(labels.hosts)}
type="host"
metric={formatMessage(labels.views)}
metric={formatMessage(labels.visitors)}
renderLabel={renderLink}
/>
</>

View File

@ -13,7 +13,7 @@ export function LanguagesTable({
const languageNames = useLanguageNames(locale);
const renderLabel = ({ x }) => {
return <div className={locale}>{languageNames[x?.split('-')[0]] ?? x}</div>;
return languageNames[x?.split('-')[0]] ?? x;
};
return (

View File

@ -3,7 +3,6 @@ import { safeDecodeURIComponent } from 'next-basics';
import { colord } from 'colord';
import classNames from 'classnames';
import { LegendItem } from 'chart.js/auto';
import { useLocale } from 'components/hooks';
import styles from './Legend.module.css';
export function Legend({
@ -13,8 +12,6 @@ export function Legend({
items: any[];
onClick: (index: LegendItem) => void;
}) {
const { locale } = useLocale();
if (!items.find(({ text }) => text)) {
return null;
}
@ -32,7 +29,7 @@ export function Legend({
onClick={() => onClick(item)}
>
<StatusLight color={color.alpha(color.alpha() + 0.2).toHex()}>
<span className={locale}>{safeDecodeURIComponent(text)}</span>
{safeDecodeURIComponent(text)}
</StatusLight>
</div>
);

View File

@ -11,7 +11,7 @@ export function RegionsTable(props: MetricsTableProps) {
const renderLink = ({ x: code, country }) => {
return (
<FilterLink id="region" className={locale} value={code} label={getRegionName(code, country)}>
<FilterLink id="region" value={code} label={getRegionName(code, country)}>
<TypeIcon type="country" value={country?.toLowerCase()} />
</FilterLink>
);

View File

@ -0,0 +1,30 @@
import MetricsTable, { MetricsTableProps } from './MetricsTable';
import FilterLink from 'components/common/FilterLink';
import { useMessages } from 'components/hooks';
import { Flexbox } from 'react-basics';
export function TagsTable(props: MetricsTableProps) {
const { formatMessage, labels } = useMessages();
const renderLink = ({ x: tag }) => {
return (
<Flexbox alignItems="center">
<FilterLink id="tag" value={tag} label={!tag && formatMessage(labels.none)} />
</Flexbox>
);
};
return (
<>
<MetricsTable
{...props}
title={formatMessage(labels.tags)}
type="tag"
metric={formatMessage(labels.views)}
renderLabel={renderLink}
/>
</>
);
}
export default TagsTable;

View File

@ -1,88 +1,88 @@
{
"label.access-code": "Access code",
"label.access-code": "Přístupový kód",
"label.actions": "Akce",
"label.activity": "Activity log",
"label.add": "Add",
"label.add-description": "Add description",
"label.add-member": "Add member",
"label.add-step": "Add step",
"label.activity": "Log aktivity",
"label.add": "Přidat",
"label.add-description": "Přidat popis",
"label.add-member": "Přidat člena",
"label.add-step": "Přidat krok",
"label.add-website": "Přidat web",
"label.admin": "Administrátor",
"label.after": "After",
"label.after": "Po",
"label.all": "Vše",
"label.all-time": "All time",
"label.all-time": "Celá doba",
"label.analytics": "Analytics",
"label.average": "Average",
"label.average": "Průměr",
"label.back": "Zpět",
"label.before": "Before",
"label.before": "Před",
"label.bounce-rate": "Okamžité opuštění",
"label.breakdown": "Breakdown",
"label.browser": "Browser",
"label.browsers": "Prohlížeč",
"label.browser": "Prohlížeč",
"label.browsers": "Prohlížeče",
"label.cancel": "Zrušit",
"label.change-password": "Změnit heslo",
"label.cities": "Cities",
"label.city": "City",
"label.clear-all": "Clear all",
"label.compare": "Compare",
"label.confirm": "Confirm",
"label.cities": "Města",
"label.city": "Město",
"label.clear-all": "Vyčistit vše",
"label.compare": "Porovnat",
"label.confirm": "Potvrdit",
"label.confirm-password": "Potvrdit heslo",
"label.contains": "Contains",
"label.continue": "Continue",
"label.count": "Count",
"label.countries": "Země",
"label.country": "Country",
"label.create": "Create",
"label.create-report": "Create report",
"label.create-team": "Create team",
"label.create-user": "Create user",
"label.created": "Created",
"label.contains": "Obsahuje",
"label.continue": "Pokračovat",
"label.count": "Počet",
"label.countries": "Státy",
"label.country": "Stát",
"label.create": "Vytvořit",
"label.create-report": "Vytvořit hlášení",
"label.create-team": "Vytvořit tým",
"label.create-user": "Vytvořit uživatele",
"label.created": "Vytvořeno",
"label.created-by": "Created By",
"label.current": "Current",
"label.current": "Aktuální",
"label.current-password": "Aktuální heslo",
"label.custom-range": "Vlastní rozsah",
"label.dashboard": "Přehled",
"label.data": "Data",
"label.date": "Date",
"label.date": "Datum",
"label.date-range": "Období",
"label.day": "Day",
"label.day": "Den",
"label.default-date-range": "Výchozí období",
"label.delete": "Smazat",
"label.delete-report": "Delete report",
"label.delete-team": "Delete team",
"label.delete-user": "Delete user",
"label.delete-report": "Smazat hlášení",
"label.delete-team": "Smazat tým",
"label.delete-user": "Smazat uživatele",
"label.delete-website": "Smazat web",
"label.description": "Description",
"label.description": "Popis",
"label.desktop": "Stolní počítač",
"label.details": "Details",
"label.device": "Device",
"label.details": "Detaily",
"label.device": "Zařízení",
"label.devices": "Zařízení",
"label.dismiss": "Odejít",
"label.does-not-contain": "Does not contain",
"label.does-not-contain": "Neobsahuje",
"label.domain": "Doména",
"label.dropoff": "Dropoff",
"label.edit": "Upravit",
"label.edit-dashboard": "Edit dashboard",
"label.edit-member": "Edit member",
"label.edit-dashboard": "Upravit dashboard",
"label.edit-member": "Upravit člena",
"label.enable-share-url": "Povolit sdílení URL",
"label.end-step": "End Step",
"label.entry": "Entry URL",
"label.event": "Event",
"label.entry": "Vstupní URL",
"label.event": "Událost",
"label.event-data": "Event data",
"label.events": "Události",
"label.exit": "Exit URL",
"label.false": "False",
"label.field": "Field",
"label.field": "Pole",
"label.fields": "Fields",
"label.filter": "Filter",
"label.filter": "Filtr",
"label.filter-combined": "Kombinace",
"label.filter-raw": "Nezpracované",
"label.filters": "Filters",
"label.filters": "Filtry",
"label.first-seen": "First seen",
"label.funnel": "Funnel",
"label.funnel-description": "Understand the conversion and drop-off rate of users.",
"label.goal": "Goal",
"label.goals": "Goals",
"label.goal": "l",
"label.goals": "Cíle",
"label.goals-description": "Track your goals for pageviews and events.",
"label.greater-than": "Greater than",
"label.greater-than-equals": "Greater than or equals",
@ -98,44 +98,44 @@
"label.join-team": "Join team",
"label.journey": "Journey",
"label.journey-description": "Understand how users navigate through your website.",
"label.language": "Language",
"label.languages": "Languages",
"label.language": "Jazyk",
"label.languages": "Jazyky",
"label.laptop": "Přenosný počítač",
"label.last-days": "Posledních {x} dnů",
"label.last-hours": "Posledních {x} hodin",
"label.last-months": "Last {x} months",
"label.last-months": "Posledních {x} měsíců",
"label.last-seen": "Last seen",
"label.leave": "Leave",
"label.leave-team": "Leave team",
"label.leave": "Opustit",
"label.leave-team": "Opustit tým",
"label.less-than": "Less than",
"label.less-than-equals": "Less than or equals",
"label.login": "Přihlásit",
"label.logout": "Odhlásit",
"label.manage": "Manage",
"label.manager": "Manager",
"label.manage": "Spravovat",
"label.manager": "Správce",
"label.max": "Max",
"label.member": "Member",
"label.members": "Members",
"label.member": "Člen",
"label.members": "Členové",
"label.min": "Min",
"label.mobile": "Mobilní telefon",
"label.more": "Více",
"label.my-account": "My account",
"label.my-websites": "My websites",
"label.my-account": "Můj účet",
"label.my-websites": "Mé weby",
"label.name": "Jméno",
"label.new-password": "Nové heslo",
"label.none": "None",
"label.number-of-records": "{x} {x, plural, one {record} other {records}}",
"label.ok": "OK",
"label.os": "OS",
"label.overview": "Overview",
"label.owner": "Owner",
"label.overview": "Přehled",
"label.owner": "Vlastník",
"label.page-of": "Page {current} of {total}",
"label.page-views": "Zobrazení stránek",
"label.pageTitle": "Page title",
"label.pageTitle": "Název stránky",
"label.pages": "Stránky",
"label.password": "Heslo",
"label.path": "Path",
"label.paths": "Paths",
"label.path": "Cesta",
"label.paths": "Cesty",
"label.powered-by": "Běží na {name}",
"label.previous": "Previous",
"label.previous-period": "Previous period",
@ -228,13 +228,13 @@
"label.views": "Zobrazení",
"label.views-per-visit": "Views per visit",
"label.visit-duration": "Průměrný čas návštěvy",
"label.visitors": "Návštěvy",
"label.visits": "Visits",
"label.visitors": "Návštěvníci",
"label.visits": "Návštěvy",
"label.website": "Website",
"label.website-id": "Website ID",
"label.websites": "Weby",
"label.window": "Window",
"label.yesterday": "Yesterday",
"label.window": "Okno",
"label.yesterday": "Včera",
"message.action-confirmation": "Type {confirmation} in the box below to confirm.",
"message.active-users": "{x} aktuálně {x, plural, one {návštěvník} other {návštěvníci}}",
"message.collected-data": "Collected data",

View File

@ -2,278 +2,278 @@
"label.access-code": "Zuegangscode",
"label.actions": "Aktione",
"label.activity": "Aktivitätsverlauf",
"label.add": "Add",
"label.add-description": "Add description",
"label.add-member": "Add member",
"label.add-step": "Add step",
"label.add": "hinzuefüege",
"label.add-description": "Beschriibig hinzuefüege",
"label.add-member": "Mitglied hinzuefüege",
"label.add-step": "Schritt hinzuefüege",
"label.add-website": "Websiite hinzuefüege",
"label.admin": "Administrator",
"label.after": "After",
"label.after": "Nach",
"label.all": "Alli",
"label.all-time": "Gesamte Zitruum",
"label.all-time": "Gsamte Zitruum",
"label.analytics": "Analytics",
"label.average": "Average",
"label.average": "Durchschnitt",
"label.back": "Zrugg",
"label.before": "Before",
"label.before": "Vor",
"label.bounce-rate": "Absprungsrate",
"label.breakdown": "Breakdown",
"label.breakdown": "Uufschlüsselig",
"label.browser": "Browser",
"label.browsers": "Browser",
"label.cancel": "Abbreche",
"label.change-password": "Passwort ändere",
"label.cities": "Städt",
"label.city": "City",
"label.city": "Stadt",
"label.clear-all": "Alles lösche",
"label.compare": "Compare",
"label.compare": "Vergliiche",
"label.confirm": "Bestätige",
"label.confirm-password": "Passwort widerhole",
"label.contains": "Contains",
"label.contains": "Enthaltet",
"label.continue": "Wiiter",
"label.count": "Count",
"label.count": "Azahl",
"label.countries": "Länder",
"label.country": "Country",
"label.create": "Create",
"label.create-report": "Create report",
"label.country": "Land",
"label.create": "Erstelle",
"label.create-report": "Bricht erstelle",
"label.create-team": "Team erstelle",
"label.create-user": "Benutzer erstelle",
"label.created": "Erstellt",
"label.created-by": "Created By",
"label.current": "Current",
"label.current-password": "Jetzigs Passwort",
"label.current": "Aktuell",
"label.current-password": "Aktuells Passwort",
"label.custom-range": "Benutzerdefinierte Bereich",
"label.dashboard": "Übersicht",
"label.data": "Datä",
"label.date": "Date",
"label.date": "Datum",
"label.date-range": "Datumsbereich",
"label.day": "Day",
"label.default-date-range": "Vorigstellte Datumsbereich",
"label.day": "Tag",
"label.default-date-range": "Voriigstellte Datumsbereich",
"label.delete": "Lösche",
"label.delete-report": "Delete report",
"label.delete-report": "Bricht lösche",
"label.delete-team": "Team lösche",
"label.delete-user": "Benutzer lösche",
"label.delete-website": "Websiite lösche",
"label.description": "Description",
"label.description": "Beschriibig",
"label.desktop": "Desktop",
"label.details": "Details",
"label.device": "Device",
"label.device": "Grät",
"label.devices": "Grät",
"label.dismiss": "Verwerfe",
"label.does-not-contain": "Does not contain",
"label.dismiss": "Verwärfe",
"label.does-not-contain": "Enthaltet nid",
"label.domain": "Domain",
"label.dropoff": "Dropoff",
"label.dropoff": "Absprung",
"label.edit": "Bearbeite",
"label.edit-dashboard": "Dashboard bearbeite",
"label.edit-member": "Edit member",
"label.edit-member": "Mitglied bearbeite",
"label.enable-share-url": "Freigab-URL aktiviere",
"label.end-step": "End Step",
"label.entry": "Entry URL",
"label.event": "Event",
"label.event-data": "Event data",
"label.end-step": "Schlussschritt",
"label.entry": "Iigangs URL",
"label.event": "Ereigniss",
"label.event-data": "Ereigniss Date",
"label.events": "Ereigniss",
"label.exit": "Exit URL",
"label.false": "False",
"label.field": "Field",
"label.fields": "Fields",
"label.exit": "Uusgangs URL",
"label.false": "Falsch",
"label.field": "Fäld",
"label.fields": "Fälder",
"label.filter": "Filter",
"label.filter-combined": "Kombiniert",
"label.filter-raw": "Rohdate",
"label.filters": "Filters",
"label.first-seen": "First seen",
"label.funnel": "Funnel",
"label.funnel-description": "Understand the conversion and drop-off rate of users.",
"label.goal": "Goal",
"label.goals": "Goals",
"label.goals-description": "Track your goals for pageviews and events.",
"label.greater-than": "Greater than",
"label.greater-than-equals": "Greater than or equals",
"label.first-seen": "Erstmal gse",
"label.funnel": "Tunnel",
"label.funnel-description": "Verstönd Sie d Konversions- und Abspruungsrate vo Nutzer.",
"label.goal": "Ziel",
"label.goals": "Ziele",
"label.goals-description": "verfolged Sie Ihri Ziel für Siitenufrüef und Ereigniss.",
"label.greater-than": "Grösser als",
"label.greater-than-equals": "Grösser oder gliich",
"label.host": "Host",
"label.hosts": "Hosts",
"label.insights": "Insights",
"label.insights-description": "Dive deeper into your data by using segments and filters.",
"label.is": "Is",
"label.is-not": "Is not",
"label.is-not-set": "Is not set",
"label.is-set": "Is set",
"label.insights": "Iiblick",
"label.insights-description": "Vertüfed Sie sich i Ihri Date, mit Hilf vo Segment und Filter.",
"label.is": "Isch",
"label.is-not": "Isch nid",
"label.is-not-set": "Isch ned gsetzt",
"label.is-set": "Isch gsetzt",
"label.join": "Biträte",
"label.join-team": "Team biträte",
"label.journey": "Journey",
"label.journey-description": "Understand how users navigate through your website.",
"label.journey": "Reis",
"label.journey-description": "Verstönd Sie, wie Nutzer dur Ihri Website navigiered.",
"label.language": "Sprach",
"label.languages": "Sprache",
"label.laptop": "Laptop",
"label.last-days": "Letzti {x} Täg",
"label.last-hours": "Letzti {x} Stunde",
"label.last-months": "Last {x} months",
"label.last-seen": "Last seen",
"label.last-months": "Letzti {x} Mönet",
"label.last-seen": "Zletzt gse",
"label.leave": "Verlah",
"label.leave-team": "Team verlah",
"label.less-than": "Less than",
"label.less-than-equals": "Less than or equals",
"label.login": "Aamelde",
"label.logout": "Abmelde",
"label.manage": "Manage",
"label.less-than": "Kliiner als",
"label.less-than-equals": "Kliiner oder gliich",
"label.login": "Aamälde",
"label.logout": "Abmälde",
"label.manage": "Verwalte",
"label.manager": "Manager",
"label.max": "Max",
"label.member": "Member",
"label.member": "Mitglied",
"label.members": "Mitglieder",
"label.min": "Min",
"label.mobile": "Handy",
"label.mobile": "Händy",
"label.more": "Meh",
"label.my-account": "My account",
"label.my-websites": "My websites",
"label.my-account": "Min Account",
"label.my-websites": "Mini Websiite",
"label.name": "Name",
"label.new-password": "Neus Passwort",
"label.none": "Keis",
"label.number-of-records": "{x} {x, plural, one {record} other {records}}",
"label.ok": "OK",
"label.os": "OS",
"label.overview": "Overview",
"label.overview": "Übersicht",
"label.owner": "Bsitzer",
"label.page-of": "Page {current} of {total}",
"label.page-of": "Siite {current} vo {total}",
"label.page-views": "Siitenufrüef",
"label.pageTitle": "Page title",
"label.pageTitle": "Siitetitel",
"label.pages": "Siite",
"label.password": "Passwort",
"label.path": "Path",
"label.paths": "Paths",
"label.powered-by": "Betribe dur {name}",
"label.previous": "Previous",
"label.previous-period": "Previous period",
"label.previous-year": "Previous year",
"label.path": "Pfad",
"label.paths": "Pfade",
"label.powered-by": "Betriibe dur {name}",
"label.previous": "Vorherig",
"label.previous-period": "Vorherigi Periode",
"label.previous-year": "Vorherigs Jahr",
"label.profile": "Profil",
"label.properties": "Properties",
"label.property": "Property",
"label.queries": "Abfrage",
"label.query": "Query",
"label.query": "Abfrag",
"label.query-parameters": "Abfragparameter",
"label.realtime": "Echtzit",
"label.referrer": "Referrer",
"label.referrers": "Referrer",
"label.referrer": "Verwiiser",
"label.referrers": "Verwiisendi",
"label.refresh": "Aktualisiere",
"label.regenerate": "Erneuere",
"label.region": "Region",
"label.regions": "Regionä",
"label.remove": "Entferne",
"label.remove-member": "Remove member",
"label.reports": "Reports",
"label.remove-member": "Mitglied entferne",
"label.reports": "Brichte",
"label.required": "Erforderlich",
"label.reset": "Zruggsetze",
"label.reset-website": "Statistik zruggsetze",
"label.retention": "Retention",
"label.retention-description": "Measure your website stickiness by tracking how often users return.",
"label.revenue": "Revenue",
"label.revenue-description": "Look into your revenue across time.",
"label.revenue-property": "Revenue Property",
"label.retention-description": "Mässed Sie d Verwiilduur vo Ihrere Website, indem Sie verfolged wie oft ihri Nutzer zruggkehred.",
"label.revenue": "Umsatz",
"label.revenue-description": "Lueged Sie sich Ihre Umsatz im Lauf vor Ziit a.",
"label.revenue-property": "Umsatzeigenschafte",
"label.role": "Rollä",
"label.run-query": "Run query",
"label.run-query": "Abfrag starte",
"label.save": "Speichere",
"label.screens": "Bildschirmuflösige",
"label.search": "Search",
"label.select": "Select",
"label.select-date": "Select date",
"label.select-role": "Select role",
"label.search": "Sueche",
"label.select": "Auswähle",
"label.select-date": "Datä uuswähle",
"label.select-role": "Rollä uuswähle",
"label.select-website": "Websiite uuswähle",
"label.session": "Session",
"label.sessions": "Sessions",
"label.session": "Sitzig",
"label.sessions": "Sitzige",
"label.settings": "Istellige",
"label.share-url": "Freigab-URL",
"label.single-day": "Ein Tag",
"label.start-step": "Start Step",
"label.steps": "Steps",
"label.sum": "Sum",
"label.start-step": "Startschritt",
"label.steps": "Schritt",
"label.sum": "Summe",
"label.tablet": "Tablet",
"label.team": "Team",
"label.team-id": "Team ID",
"label.team-manager": "Team manager",
"label.team-manager": "Team Manager",
"label.team-member": "Team Mitglied",
"label.team-name": "Team name",
"label.team-name": "Team Name",
"label.team-owner": "Team Bsitzer",
"label.team-view-only": "Team view only",
"label.team-websites": "Team websites",
"label.team-view-only": "Nur für Teammitglieder sichtbar",
"label.team-websites": "Team Websiite",
"label.teams": "Teams",
"label.theme": "Thema",
"label.this-month": "De Monet",
"label.this-week": "Die Wuche",
"label.this-year": "Das Jahr",
"label.this-month": "Dä Monet",
"label.this-week": "Diä Wuuche",
"label.this-year": "Das Johr",
"label.timezone": "Ziitzone",
"label.title": "Titel",
"label.today": "Hüt",
"label.toggle-charts": "Schaubilder umschalte",
"label.toggle-charts": "Charts umschalte",
"label.total": "Total",
"label.total-records": "Total records",
"label.total-records": "Gsamti Datesätz",
"label.tracking-code": "Tracking Code",
"label.transactions": "Transactions",
"label.transfer": "Transfer",
"label.transfer-website": "Transfer website",
"label.true": "True",
"label.type": "Type",
"label.unique": "Unique",
"label.unique-visitors": "Eitigi Bsuecher",
"label.uniqueCustomers": "Unique Customers",
"label.transactions": "Transaktione",
"label.transfer": "Transferiere",
"label.transfer-website": "Websiite transferiere",
"label.true": "Wahr",
"label.type": "Typ",
"label.unique": "Einzigartigi",
"label.unique-visitors": "Einzigartigi Bsuecher",
"label.uniqueCustomers": "Einzigartigi Kunde",
"label.unknown": "Unbekannt",
"label.untitled": "Untitled",
"label.untitled": "Unbennant",
"label.update": "Update",
"label.url": "URL",
"label.urls": "URLs",
"label.user": "Benutzer",
"label.user-property": "User Property",
"label.user-property": "Benutzereigeschafte",
"label.username": "Benutzername",
"label.users": "Benutzer",
"label.utm": "UTM",
"label.utm-description": "Track your campaigns through UTM parameters.",
"label.value": "Value",
"label.utm-description": "Tracked Sie Ihri Kampagnen mit UTM Parameters.",
"label.value": "Wärt",
"label.view": "Azeige",
"label.view-details": "Details azeige",
"label.view-only": "View only",
"label.view-only": "Nume aluege",
"label.views": "Ufrüef",
"label.views-per-visit": "Views per visit",
"label.views-per-visit": "Ufrüef pro Bsuech",
"label.visit-duration": "Durchschn. Bsuechsziit",
"label.visitors": "Bsuecher",
"label.visits": "Visits",
"label.visits": "Bsüech",
"label.website": "Website",
"label.website-id": "Websiite ID",
"label.websites": "Websiite",
"label.window": "Window",
"label.window": "Fenster",
"label.yesterday": "Gester",
"message.action-confirmation": "Type {confirmation} in the box below to confirm.",
"message.action-confirmation": "Typed Sie {confirmation} is Feld underhalb um z bestätige.",
"message.active-users": "{x} {x, plural, one {aktive Bsuecher} other {aktivi Bsuecher}}",
"message.collected-data": "Collected data",
"message.collected-data": "Gsammleti Date",
"message.confirm-delete": "Sind Sie sich sicher, {target} zlösche?",
"message.confirm-leave": "Sind Sie sich sicher, {target} zverlah?",
"message.confirm-remove": "Are you sure you want to remove {target}?",
"message.confirm-reset": "Sind Sie sicher, dass Sie dStatistike vo {target} zruggsetze wend?",
"message.delete-team-warning": "Deleting a team will also delete all team websites.",
"message.delete-website-warning": "Alli dezueghörige Date werdet ebefalls glöscht.",
"message.error": "Es isch en Fehler uftrete.",
"message.confirm-remove": "Sind Sie sich sicher, dass Sie {target} wänd entferne?",
"message.confirm-reset": "Sind Sie sicher, dass Sie d Statistike vo {target} zruggsetze wänd?",
"message.delete-team-warning": "Es Team lösche dued ebefalls alli team Websiite lösche.",
"message.delete-website-warning": "Alli dezueghörige Date werded ebefalls glöscht.",
"message.error": "Es isch en Fehler ufträte.",
"message.event-log": "{event} uf {url}",
"message.go-to-settings": "Zu de Istellige",
"message.incorrect-username-password": "Falschs Passwort oder Benutzername.",
"message.incorrect-username-password": "Falsches Passwort oder Benutzername.",
"message.invalid-domain": "Ungültigi Domain",
"message.min-password-length": "Miminamli längi vo {n} Zeiche",
"message.new-version-available": "A new version of Umami {version} is available!",
"message.new-version-available": "Es isch en neue Version vo Umami {version} verfügbar!",
"message.no-data-available": "Kei Date vorhande.",
"message.no-event-data": "No event data is available.",
"message.no-event-data": "Es sind kei Event Date verfügbar.",
"message.no-match-password": "Passwörter stimmed ned überi",
"message.no-results-found": "No results were found.",
"message.no-results-found": "Kei Ergäbnis gfunde.",
"message.no-team-websites": "Dem Team sind kei Websiite zuegordnet.",
"message.no-teams": "Bisher sind no kei Teams erstellt worde.",
"message.no-users": "Da gits kei Benutzer",
"message.no-websites-configured": "Es isch kei Websiite vorhande.",
"message.page-not-found": "Siite ned gfunde.",
"message.reset-website": "To reset this website, type {confirmation} in the box below to confirm.",
"message.reset-website": "Um die Websiite zruggzsetze, typed Sie {confirmation} is Feld unde dran.",
"message.reset-website-warning": "Alli Date für die Websiite werdet glöscht, nur de Tracking Code blibt bestah.",
"message.saved": "Erfolgrich gspeichert.",
"message.share-url": "Ihri Websiitestatistik isch under de folgende URL öffentlich zuegänglich:",
"message.team-already-member": "Sie sind bereits es Mitglied vo dem Team.",
"message.team-already-member": "Sie sind bereits es Mitglied vo däm Team.",
"message.team-not-found": "Team nöd gfunde.",
"message.team-websites-info": "Websiite chönd vo jedem im Team agluegt werde",
"message.team-websites-info": "Websiite chöi vo jedem im Team agluegt werde",
"message.tracking-code": "Tracking Code",
"message.transfer-team-website-to-user": "Transfer this website to your account?",
"message.transfer-user-website-to-team": "Select the team to transfer this website to.",
"message.transfer-website": "Transfer website ownership to your account or another team.",
"message.triggered-event": "Triggered event",
"message.user-deleted": "Benutzer glöscht.",
"message.viewed-page": "Viewed page",
"message.visitor-log": "Bsuecher us {country} benutzt {browser} uf {os} {device}",
"message.visitors-dropped-off": "Visitors dropped off"
}
"message.transfer-team-website-to-user": "Websiite uf zu Ihrem Account transferiere?",
"message.transfer-user-website-to-team": "Wähled Sie s Team zum däm Websiite transferiert werde söll.",
"message.transfer-website": "Übertraged Sie d Websiite Eigetümerrecht uf Ihre Account oder uf es anders Team",
"message.triggered-event": "Usglösts Ereigniss",
"message.user-deleted": "Bnutzer glöscht.",
"message.viewed-page": "Siite agluegt",
"message.visitor-log": "Bsuecher us {country} nutzt {browser} uf {os} {device}",
"message.visitors-dropped-off": "Bsuercher verlore"
}

View File

@ -67,8 +67,8 @@
"label.enable-share-url": "Freigabe-URL aktivieren",
"label.end-step": "Schlussschritt",
"label.entry": "Eingangs-URL",
"label.event": "Ereigniss",
"label.event-data": "Ereignissdaten",
"label.event": "Ereignis",
"label.event-data": "Ereignisdaten",
"label.events": "Ereignisse",
"label.exit": "Ausgangs-URL",
"label.false": "Falsch",
@ -271,7 +271,7 @@
"message.transfer-team-website-to-user": "Diese Website zu Ihrem Account transferieren?",
"message.transfer-user-website-to-team": "Wählen Sie ein Team aus, zu dem die Website transferiert werden soll.",
"message.transfer-website": "Übertragen Sie die Eigentümerrechte zu Ihrem Account oder einem anderen Team.",
"message.triggered-event": "Ausgelöstes Ereigniss",
"message.triggered-event": "Ereignis ausgelöst",
"message.user-deleted": "Benutzer gelöscht.",
"message.viewed-page": "Seite besucht",
"message.visitor-log": "Besucher aus {country} benutzt {browser} auf {os} {device}",

View File

@ -66,7 +66,7 @@
"label.edit-member": "Modifier le membre",
"label.enable-share-url": "Activer l'URL de partage",
"label.end-step": "End Step",
"label.entry": "Entry URL",
"label.entry": "URL d'entrée",
"label.event": "Évènement",
"label.event-data": "Données d'évènements",
"label.events": "Évènements",
@ -78,12 +78,12 @@
"label.filter-combined": "Combiné",
"label.filter-raw": "Brut",
"label.filters": "Filtres",
"label.first-seen": "First seen",
"label.first-seen": "Vu pour la première fois",
"label.funnel": "Entonnoir",
"label.funnel-description": "Suivi des conversions et des taux d'abandons.",
"label.goal": "Goal",
"label.goals": "Goals",
"label.goals-description": "Track your goals for pageviews and events.",
"label.goals-description": "Suivez vos objectifs en matière de pages vues et d'événements.",
"label.greater-than": "Supérieur à",
"label.greater-than-equals": "Supérieur ou égal à",
"label.host": "Host",
@ -97,7 +97,7 @@
"label.join": "Rejoindre",
"label.join-team": "Rejoindre une équipe",
"label.journey": "Journey",
"label.journey-description": "Understand how users navigate through your website.",
"label.journey-description": "Comprendre comment les utilisateurs naviguent sur votre site web.",
"label.language": "Langue",
"label.languages": "Langues",
"label.laptop": "Portable",
@ -137,12 +137,12 @@
"label.path": "Path",
"label.paths": "Paths",
"label.powered-by": "Propulsé par {name}",
"label.previous": "Previous",
"label.previous-period": "Previous period",
"label.previous-year": "Previous year",
"label.previous": "Précédent",
"label.previous-period": "Période précédente",
"label.previous-year": "Année précédente",
"label.profile": "Profil",
"label.properties": "Properties",
"label.property": "Property",
"label.properties": "Propriétés",
"label.property": "Propriété",
"label.queries": "Requêtes",
"label.query": "Requête",
"label.query-parameters": "Paramètres de requête",
@ -162,14 +162,14 @@
"label.retention": "Rétention",
"label.retention-description": "Mesure de l'attractivité du site en visualisant les taux de visiteurs qui reviennent.",
"label.revenue": "Revenue",
"label.revenue-description": "Look into your revenue across time.",
"label.revenue-property": "Revenue Property",
"label.revenue-description": "Examinez vos revenus au fil du temps.",
"label.revenue-property": "Propriétés des revenues",
"label.role": "Rôle",
"label.run-query": "Éxécuter la requête",
"label.save": "Enregistrer",
"label.screens": "Résolutions d'écran",
"label.search": "Rechercher",
"label.select": "Select",
"label.select": "Selectionner",
"label.select-date": "Choisir une période",
"label.select-role": "Choisir un rôle",
"label.select-website": "Choisir un site",
@ -178,17 +178,17 @@
"label.settings": "Paramètres",
"label.share-url": "URL de partage",
"label.single-day": "Journée",
"label.start-step": "Start Step",
"label.start-step": "Etape de démarrage",
"label.steps": "Étapes",
"label.sum": "Somme",
"label.tablet": "Tablette",
"label.team": "Équipe",
"label.team-id": "ID d'équipe",
"label.team-manager": "Team manager",
"label.team-manager": "Manager de l'équipe",
"label.team-member": "Membre de l'équipe",
"label.team-name": "Nom de l'équipe",
"label.team-owner": "Propriétaire de l'équipe",
"label.team-view-only": "Team view only",
"label.team-view-only": "Vue d'équipe uniquement",
"label.team-websites": "Sites d'équipes",
"label.teams": "Équipes",
"label.theme": "Thème",
@ -209,14 +209,14 @@
"label.type": "Type",
"label.unique": "Unique",
"label.unique-visitors": "Visiteurs uniques",
"label.uniqueCustomers": "Unique Customers",
"label.uniqueCustomers": "Clients uniques",
"label.unknown": "Inconnu",
"label.untitled": "Sans titre",
"label.update": "Modifier",
"label.url": "URL",
"label.urls": "URLs",
"label.user": "Utilisateur",
"label.user-property": "User Property",
"label.user-property": "Propriétés d'utilisateurs",
"label.username": "Nom d'utilisateur",
"label.users": "Utilisateurs",
"label.utm": "UTM",

View File

@ -160,7 +160,7 @@
"label.reset": "초기화",
"label.reset-website": "웹사이트 초기화",
"label.retention": "리텐션",
"label.retention-description": "사용자가 얼마나 자주 돌아오는지를 추적하여 웹사이트의 리텐션을 측정하십시오.",
"label.retention-description": "사용자가 얼마나 자주 돌아오는지를 추적하여 웹사이트의 리텐션을 측정하세요.",
"label.revenue": "수익",
"label.revenue-description": "시간대별 수익을 살펴보세요.",
"label.revenue-property": "수익 속성",
@ -220,14 +220,14 @@
"label.username": "사용자 이름",
"label.users": "사용자",
"label.utm": "UTM",
"label.utm-description": "UTM 매개변수를 통해 캠페인을 추적합니다.",
"label.utm-description": "UTM 매개변수를 통해 캠페인을 추적하세요.",
"label.value": "값",
"label.view": "보기",
"label.view-details": "자세히 보기",
"label.view-only": "보기 전용",
"label.views": "조회",
"label.views-per-visit": "방문당 조회",
"label.visit-duration": "평균 방문 시간",
"label.visit-duration": "방문 시간",
"label.visitors": "방문자",
"label.visits": "방문",
"label.website": "웹사이트",
@ -244,7 +244,7 @@
"message.confirm-reset": "{target}을(를) 초기화하시겠습니까?",
"message.delete-team-warning": "팀을 삭제하면 팀에 등록된 모든 웹사이트도 삭제됩니다.",
"message.delete-website-warning": "관련된 모든 데이터가 삭제됩니다.",
"message.error": "오류가 발생했습니다.",
"message.error": "문제가 발생했습니다.",
"message.event-log": "{event} - {url}",
"message.go-to-settings": "설정으로 이동",
"message.incorrect-username-password": "사용자 이름 또는 비밀번호를 잘못 입력했습니다.",

View File

@ -5,8 +5,8 @@
"label.add": "Adaugă",
"label.add-description": "Adaugă descriere",
"label.add-member": "Adaugă membru",
"label.add-step": "Add step",
"label.add-website": "Adăugare site web",
"label.add-step": "Adaugă pas",
"label.add-website": "Adaugă site web",
"label.admin": "Administrator",
"label.after": "După",
"label.all": "Toate",
@ -24,12 +24,12 @@
"label.cities": "Orașe",
"label.city": "Oraș",
"label.clear-all": "Șterge tot",
"label.compare": "Compare",
"label.compare": "Compară",
"label.confirm": "Confirm",
"label.confirm-password": "Confirmare parolă",
"label.contains": "Conține",
"label.continue": "Continuă",
"label.count": "Count",
"label.count": "Număr",
"label.countries": "Țări",
"label.country": "Țară",
"label.create": "Crează",
@ -37,21 +37,21 @@
"label.create-team": "Crează echipă",
"label.create-user": "Crează utilizator",
"label.created": "Creat",
"label.created-by": "Created By",
"label.current": "Current",
"label.created-by": "Creat de",
"label.current": "Curent",
"label.current-password": "Parola curentă",
"label.custom-range": "Interval personalizat",
"label.dashboard": "Tablou de bord",
"label.data": "Date",
"label.date": "Data",
"label.date-range": "Interval de date",
"label.date": "Dată",
"label.date-range": "Interval",
"label.day": "Zi",
"label.default-date-range": "Interval de date implicit",
"label.default-date-range": "Interval implicit",
"label.delete": "Șterge",
"label.delete-report": "Șterge raport",
"label.delete-team": "Șterge echipă",
"label.delete-user": "Șterge utilizator",
"label.delete-website": "Ștergere site web",
"label.delete-website": "Șterge site web",
"label.description": "Descriere",
"label.desktop": "Desktop",
"label.details": "Detalii",
@ -65,12 +65,12 @@
"label.edit-dashboard": "Editare tablou de bord",
"label.edit-member": "Editare membru",
"label.enable-share-url": "Activare adresă URL de distribuire",
"label.end-step": "End Step",
"label.entry": "Entry URL",
"label.end-step": "Pas final",
"label.entry": "URL de intrare",
"label.event": "Eveniment",
"label.event-data": "Date despre eveniment",
"label.events": "Evenimente",
"label.exit": "Exit URL",
"label.exit": "URL de ieșire",
"label.false": "Fals",
"label.field": "Câmp",
"label.fields": "Câmpuri",
@ -78,12 +78,12 @@
"label.filter-combined": "Combinat",
"label.filter-raw": "Brut",
"label.filters": "Filtre",
"label.first-seen": "First seen",
"label.first-seen": "Văzut pentru prima dată",
"label.funnel": "Parcursul utilizatorului",
"label.funnel-description": "Înțelege rata de conversie și rata de abandon a utilizatorilor.",
"label.goal": "Goal",
"label.goals": "Goals",
"label.goals-description": "Track your goals for pageviews and events.",
"label.goal": "Obiectiv",
"label.goals": "Obiective",
"label.goals-description": "Urmărește obiectivele de vizualizări și evenimente.",
"label.greater-than": "Mai mare decât",
"label.greater-than-equals": "Mai mare sau egal cu",
"label.host": "Host",
@ -96,15 +96,15 @@
"label.is-set": "Este setat",
"label.join": "Alătură-te",
"label.join-team": "Alătură-te echipei",
"label.journey": "Journey",
"label.journey-description": "Understand how users navigate through your website.",
"label.journey": "Traseu",
"label.journey-description": "Înțelege cum navighează vizitatorii prin website.",
"label.language": "Limbă",
"label.languages": "Limbi",
"label.laptop": "Laptop",
"label.last-days": "Ultimele {x} zile",
"label.last-hours": "Ultimele {x} ore",
"label.last-months": "Last {x} months",
"label.last-seen": "Last seen",
"label.last-months": "Ultimele {x} luni",
"label.last-seen": "Văzut ultima dată",
"label.leave": "Părăsește",
"label.leave-team": "Părăsește echipa",
"label.less-than": "Mai puțin decât",
@ -134,15 +134,15 @@
"label.pageTitle": "Titlul paginii",
"label.pages": "Pagini",
"label.password": "Parolă",
"label.path": "Path",
"label.paths": "Paths",
"label.path": "Rută",
"label.paths": "Rute",
"label.powered-by": "Cu sprijinul {name}",
"label.previous": "Previous",
"label.previous-period": "Previous period",
"label.previous-year": "Previous year",
"label.previous": "Anterior",
"label.previous-period": "Perioda anterioară",
"label.previous-year": "Anul anterior",
"label.profile": "Profil",
"label.properties": "Properties",
"label.property": "Property",
"label.properties": "Proprietăți",
"label.property": "Proprietate",
"label.queries": "Interogări",
"label.query": "Interogare",
"label.query-parameters": "Parametri de interogare",
@ -161,8 +161,8 @@
"label.reset-website": "Resetează statisticile pentru site",
"label.retention": "Retenție",
"label.retention-description": "Măsoară atractivitatea site-ului tău prin urmărirea frecvenței cu care utilizatorii se întorc.",
"label.revenue": "Revenue",
"label.revenue-description": "Look into your revenue across time.",
"label.revenue": "Venit",
"label.revenue-description": "Urmărește venitul în timp.",
"label.revenue-property": "Revenue Property",
"label.role": "Rol",
"label.run-query": "Execută interogarea",
@ -173,18 +173,18 @@
"label.select-date": "Selectează data",
"label.select-role": "Selectează rolul",
"label.select-website": "Selectează website",
"label.session": "Session",
"label.session": "Sesiune",
"label.sessions": "Sesiuni",
"label.settings": "Setări",
"label.share-url": "Partajare URL",
"label.single-day": "O singură zi",
"label.start-step": "Start Step",
"label.steps": "Steps",
"label.start-step": "Pas de început",
"label.steps": "Pași",
"label.sum": "Sumă",
"label.tablet": "Tabletă",
"label.team": "Echipă",
"label.team-id": "ID Echipa",
"label.team-manager": "Team manager",
"label.team-id": "ID Echipă",
"label.team-manager": "Manager echipă",
"label.team-member": "Membru echipă",
"label.team-name": "Nume echipă",
"label.team-owner": "Titular echipă",
@ -202,34 +202,34 @@
"label.total": "Total",
"label.total-records": "Total înregistrări",
"label.tracking-code": "Cod de urmărire",
"label.transactions": "Transactions",
"label.transactions": "Tranzacții",
"label.transfer": "Transfer",
"label.transfer-website": "Transfer website",
"label.true": "Adevărat",
"label.type": "Tip",
"label.unique": "Unici",
"label.unique-visitors": "Vizitatori unici",
"label.uniqueCustomers": "Unique Customers",
"label.uniqueCustomers": "Clienți unici",
"label.unknown": "Necunoscut",
"label.untitled": "Fără titlu",
"label.update": "Update",
"label.url": "URL",
"label.urls": "URLs",
"label.user": "Utilizator",
"label.user-property": "User Property",
"label.user-property": "Proprietatea utilizatorului",
"label.username": "Nume utilizator",
"label.users": "Utilizatori",
"label.utm": "UTM",
"label.utm-description": "Track your campaigns through UTM parameters.",
"label.utm-description": "Urmărește campaniile tale cu parametri UTM.",
"label.value": "Valoare",
"label.view": "Vizualizare",
"label.view-details": "Vizualizare detalii",
"label.view-only": "Doar vizualizare",
"label.views": "Vizualizări",
"label.views-per-visit": "Views per visit",
"label.views-per-visit": "Vizualizări per vizită",
"label.visit-duration": "Timp mediu de vizitare",
"label.visitors": "Vizitatori",
"label.visits": "Visits",
"label.visits": "Vizite",
"label.website": "Website",
"label.website-id": "ID Website",
"label.websites": "Site-uri web",
@ -237,7 +237,7 @@
"label.yesterday": "Ieri",
"message.action-confirmation": "Scrie {confirmation} în câmpul de mai jos pentru a confirma.",
"message.active-users": "{x} {x, plural, one {vizitator activ} other {vizitatori activi}}",
"message.collected-data": "Collected data",
"message.collected-data": "Date colectate",
"message.confirm-delete": "Ești sigur că vrei să ștergi {target}?",
"message.confirm-leave": "Ești sigur că vrei să părăsești {target}?",
"message.confirm-remove": "Ești sigur că vrei să ștergi {target}?",

View File

@ -2,194 +2,194 @@
"label.access-code": "Код доступа",
"label.actions": "Действия",
"label.activity": "Журнал активности",
"label.add": "Add",
"label.add-description": "Add description",
"label.add-member": "Add member",
"label.add-step": "Add step",
"label.add": "Добавить",
"label.add-description": "Добавить описание",
"label.add-member": "Добавить участника",
"label.add-step": "Добавить шаг",
"label.add-website": "Добавить сайт",
"label.admin": "Администратор",
"label.after": "After",
"label.after": "После",
"label.all": "Все",
"label.all-time": "Все время",
"label.analytics": "Аналитика",
"label.average": "Average",
"label.average": "Средний",
"label.back": "Назад",
"label.before": "Before",
"label.before": "До",
"label.bounce-rate": "Отказы",
"label.breakdown": "Breakdown",
"label.browser": "Browser",
"label.breakdown": "Авария",
"label.browser": "Браузер",
"label.browsers": "Браузеры",
"label.cancel": "Отменить",
"label.change-password": "Изменить пароль",
"label.cities": "Города",
"label.city": "City",
"label.city": "Город",
"label.clear-all": "Очистить все",
"label.compare": "Compare",
"label.compare": "Сравнить",
"label.confirm": "Подтвердить",
"label.confirm-password": "Подтвердить пароль",
"label.contains": "Contains",
"label.contains": "Содержит",
"label.continue": "Продолжить",
"label.count": "Count",
"label.count": "Считать",
"label.countries": "Страны",
"label.country": "Country",
"label.create": "Create",
"label.create-report": "Create report",
"label.country": "Страна",
"label.create": "Создать",
"label.create-report": "Создать отчет",
"label.create-team": "Создать команду",
"label.create-user": "Создать пользователя",
"label.created": "Создано",
"label.created-by": "Created By",
"label.current": "Current",
"label.created-by": "Создано",
"label.current": "Текущий",
"label.current-password": "Текущий пароль",
"label.custom-range": "Другой период",
"label.dashboard": "Информационная панель",
"label.data": "Данные",
"label.date": "Date",
"label.date": "Дата",
"label.date-range": "Диапазон дат",
"label.day": "Day",
"label.day": "День",
"label.default-date-range": "Диапазон дат по-умолчанию",
"label.delete": "Удалить",
"label.delete-report": "Delete report",
"label.delete-report": "Удалить отчет",
"label.delete-team": "Удалить команду",
"label.delete-user": "Удалить пользователя",
"label.delete-website": "Удалить сайт",
"label.description": "Description",
"label.description": "Описание",
"label.desktop": "Настольный компьютер",
"label.details": "Подробности",
"label.device": "Device",
"label.device": "Устройство",
"label.devices": "Устройства",
"label.dismiss": "Отклонить",
"label.does-not-contain": "Does not contain",
"label.does-not-contain": "Не содержит",
"label.domain": "Домен",
"label.dropoff": "Dropoff",
"label.dropoff": "Высадка",
"label.edit": "Изменить",
"label.edit-dashboard": "Редактировать дашборд",
"label.edit-member": "Edit member",
"label.edit-member": "Редактировать участника",
"label.enable-share-url": "Разрешить делиться ссылкой",
"label.end-step": "End Step",
"label.entry": "Entry URL",
"label.event": "Event",
"label.event-data": "Event data",
"label.end-step": "Конечный шаг",
"label.entry": "URL-адрес входа",
"label.event": "Событие",
"label.event-data": "Данные о событии",
"label.events": "События",
"label.exit": "Exit URL",
"label.false": "False",
"label.field": "Field",
"label.fields": "Fields",
"label.filter": "Filter",
"label.exit": "URL-адрес выхода",
"label.false": "Ложь",
"label.field": "Поле",
"label.fields": "Поля",
"label.filter": "Фильтр",
"label.filter-combined": "Объединенные",
"label.filter-raw": "Сырые данные",
"label.filters": "Filters",
"label.first-seen": "First seen",
"label.funnel": "Funnel",
"label.funnel-description": "Understand the conversion and drop-off rate of users.",
"label.goal": "Goal",
"label.goals": "Goals",
"label.goals-description": "Track your goals for pageviews and events.",
"label.greater-than": "Greater than",
"label.greater-than-equals": "Greater than or equals",
"label.filters": "Фильтры",
"label.first-seen": "Первый вход",
"label.funnel": "Воронка",
"label.funnel-description": "Изучите коэффициент конверсии и ухода пользователей.",
"label.goal": "Цель",
"label.goals": "Цели",
"label.goals-description": "Отслеживайте свои цели по просмотрам страниц и событиям.",
"label.greater-than": "Больше, чем",
"label.greater-than-equals": "Больше или равно",
"label.host": "Host",
"label.hosts": "Hosts",
"label.insights": "Insights",
"label.insights-description": "Dive deeper into your data by using segments and filters.",
"label.is": "Is",
"label.is-not": "Is not",
"label.is-not-set": "Is not set",
"label.is-set": "Is set",
"label.insights": "Информация",
"label.insights-description": "Погрузитесь глубже в свои данные с помощью сегментов и фильтров.",
"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-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": "Less than",
"label.less-than-equals": "Less than or equals",
"label.less-than": "Меньше, чем",
"label.less-than-equals": "Меньше или равно",
"label.login": "Войти",
"label.logout": "Выйти",
"label.manage": "Manage",
"label.manager": "Manager",
"label.max": "Max",
"label.member": "Member",
"label.manage": "Управление",
"label.manager": "Менеджер",
"label.max": "Максимум",
"label.member": "Участник",
"label.members": "Участники",
"label.min": "Min",
"label.min": "Минимум",
"label.mobile": "Смартфон",
"label.more": "Больше",
"label.my-account": "My account",
"label.my-websites": "My websites",
"label.my-account": "Мой профиль",
"label.my-websites": "Мои сайты",
"label.name": "Имя",
"label.new-password": "Новый пароль",
"label.none": "Не указано",
"label.number-of-records": "{x} {x, plural, one {record} other {records}}",
"label.number-of-records": "{x} {x, plural, one {запись} other {записи}}",
"label.ok": "OK",
"label.os": "OS",
"label.overview": "Overview",
"label.overview": "Обзор",
"label.owner": "Владелец",
"label.page-of": "Page {current} of {total}",
"label.page-of": "Страница {current} из {total}",
"label.page-views": "Просмотры страниц",
"label.pageTitle": "Page title",
"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": "Query",
"label.query": "Запрос",
"label.query-parameters": "Параметры запроса",
"label.realtime": "Реальное время",
"label.referrer": "Referrer",
"label.referrer": "Реферер",
"label.referrers": "Источники",
"label.refresh": "Обновить",
"label.regenerate": "Обновить",
"label.region": "Region",
"label.region": "Регион",
"label.regions": "Регионы",
"label.remove": "Удалить",
"label.remove-member": "Remove member",
"label.reports": "Reports",
"label.remove-member": "Удалить участника",
"label.reports": "Отчеты",
"label.required": "Обязательное",
"label.reset": "Сбросить",
"label.reset-website": "Сбросить статистику",
"label.retention": "Retention",
"label.retention-description": "Measure your website stickiness by tracking how often users return.",
"label.revenue": "Revenue",
"label.revenue-description": "Look into your revenue across time.",
"label.revenue-property": "Revenue Property",
"label.retention": "Удержание",
"label.retention-description": "Измерьте «прилипаемость» вашего сайта, отслеживая, как часто пользователи возвращаются на него.",
"label.revenue": "Выручка",
"label.revenue-description": "Изучите свои доходы за определенное время.",
"label.revenue-property": "Доходная недвижимость",
"label.role": "Роль",
"label.run-query": "Run query",
"label.run-query": "Выполнить запрос",
"label.save": "Сохранить",
"label.screens": "Экраны",
"label.search": "Search",
"label.select": "Select",
"label.select-date": "Select date",
"label.select-role": "Select role",
"label.search": "Поиск",
"label.select": "Выберите",
"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.sum": "Sum",
"label.start-step": "Начальный этап",
"label.steps": "Шаги",
"label.sum": "Сумма",
"label.tablet": "Планшет",
"label.team": "Команда",
"label.team-id": "ID команды",
"label.team-manager": "Team manager",
"label.team-manager": "Менеджер команды",
"label.team-member": "Член команды",
"label.team-name": "Team name",
"label.team-name": "Название команды",
"label.team-owner": "Владелец команды",
"label.team-view-only": "Team view only",
"label.team-websites": "Team websites",
"label.team-view-only": "Только командный просмотр",
"label.team-websites": "Веб-сайты команды",
"label.teams": "Команды",
"label.theme": "Тема",
"label.this-month": "Этот месяц",
@ -199,62 +199,62 @@
"label.title": "Заголовок",
"label.today": "Сегодня",
"label.toggle-charts": "Показать/скрыть графики",
"label.total": "Total",
"label.total-records": "Total records",
"label.total": "Всего",
"label.total-records": "Всего записей",
"label.tracking-code": "Код отслеживания",
"label.transactions": "Transactions",
"label.transfer": "Transfer",
"label.transfer-website": "Transfer website",
"label.true": "True",
"label.type": "Type",
"label.unique": "Unique",
"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": "Untitled",
"label.update": "Update",
"label.untitled": "Без названия",
"label.update": "Обновление",
"label.url": "URL",
"label.urls": "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.value": "Value",
"label.utm-description": "Отслеживайте свои кампании с помощью UTM-параметров.",
"label.value": "Значение",
"label.view": "Просмотреть",
"label.view-details": "Посмотреть детали",
"label.view-only": "View only",
"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.website": "Website",
"label.visits": "Посещения",
"label.website": "Сайт",
"label.website-id": "ID сайта",
"label.websites": "Сайты",
"label.window": "Window",
"label.window": "Окно",
"label.yesterday": "Вчера",
"message.action-confirmation": "Type {confirmation} in the box below to confirm.",
"message.action-confirmation": "Введите {confirmation} в поле ниже, чтобы подтвердить.",
"message.active-users": "{x} текущих посетителей",
"message.collected-data": "Collected data",
"message.collected-data": "Собранные данные",
"message.confirm-delete": "Вы уверены, что хотите удалить {target}?",
"message.confirm-leave": "Вы уверены, что хотите уйти {target}?",
"message.confirm-remove": "Are you sure you want to remove {target}?",
"message.confirm-remove": "Вы уверены, что хотите удалить {target}?",
"message.confirm-reset": "Вы уверены, что хотите сбросить статистику {target}?",
"message.delete-team-warning": "Deleting a team will also delete all team websites.",
"message.delete-team-warning": "При удалении команды будут удалены и все ее веб-сайты.",
"message.delete-website-warning": "Все связанные данные будут также удалены.",
"message.error": "Что-то пошло не так.",
"message.event-log": "{event} on {url}",
"message.event-log": "{event} на {url}",
"message.go-to-settings": "Перейти к настройкам",
"message.incorrect-username-password": "Неверное имя пользователя/пароль.",
"message.invalid-domain": "Некорректный домен",
"message.min-password-length": "Минимальная длина {n} символов",
"message.new-version-available": "A new version of Umami {version} is available!",
"message.new-version-available": "Вышла новая версия Umami {version}!",
"message.no-data-available": "Нет данных.",
"message.no-event-data": "No event data is available.",
"message.no-event-data": "Данные о событиях отсутствуют.",
"message.no-match-password": "Пароли не совпадают",
"message.no-results-found": "No results were found.",
"message.no-results-found": "Результаты не найдены.",
"message.no-team-websites": "У этой команды нет ни одного сайта.",
"message.no-teams": "Вы не создали ни одной команды.",
"message.no-users": "Нет пользователей.",
@ -268,12 +268,12 @@
"message.team-not-found": "Команда не найдена.",
"message.team-websites-info": "Сайты могут просматривать все члены команды.",
"message.tracking-code": "Код отслеживания",
"message.transfer-team-website-to-user": "Transfer this website to your account?",
"message.transfer-user-website-to-team": "Select the team to transfer this website to.",
"message.transfer-website": "Transfer website ownership to your account or another team.",
"message.triggered-event": "Triggered event",
"message.transfer-team-website-to-user": "Перенести этот сайт в свой прфоиль?",
"message.transfer-user-website-to-team": "Выберите команду, которой нужно передать этот сайт.",
"message.transfer-website": "Передайте право владения сайтом своей учетной записи или другой команде.",
"message.triggered-event": "Запущенное событие",
"message.user-deleted": "Пользователь удален.",
"message.viewed-page": "Viewed page",
"message.viewed-page": "Просмотренная страница",
"message.visitor-log": "Посетитель из {country} используя {browser} на {os} {device}",
"message.visitors-dropped-off": "Visitors dropped off"
"message.visitors-dropped-off": "Высадка посетителей"
}

View File

@ -2,9 +2,9 @@ import { ClickHouseClient, createClient } from '@clickhouse/client';
import { formatInTimeZone } from 'date-fns-tz';
import debug from 'debug';
import { CLICKHOUSE } from 'lib/db';
import { getWebsite } from 'queries/index';
import { DEFAULT_PAGE_SIZE, OPERATORS } from './constants';
import { maxDate } from './date';
import { fetchWebsite } from './load';
import { filtersToArray } from './params';
import { PageParams, QueryFilters, QueryOptions } from './types';
@ -132,7 +132,7 @@ function getFilterParams(filters: QueryFilters = {}) {
}
async function parseFilters(websiteId: string, filters: QueryFilters = {}, options?: QueryOptions) {
const website = await fetchWebsite(websiteId);
const website = await getWebsite(websiteId);
return {
filterQuery: getFilterQuery(filters, options),

View File

@ -33,7 +33,7 @@ export const FILTER_REFERRERS = 'filter-referrers';
export const FILTER_PAGES = 'filter-pages';
export const UNIT_TYPES = ['year', 'month', 'hour', 'day', 'minute'];
export const EVENT_COLUMNS = ['url', 'entry', 'exit', 'referrer', 'title', 'query', 'event'];
export const EVENT_COLUMNS = ['url', 'entry', 'exit', 'referrer', 'title', 'query', 'event', 'tag'];
export const SESSION_COLUMNS = [
'browser',
@ -63,6 +63,7 @@ export const FILTER_COLUMNS = {
city: 'city',
language: 'language',
event: 'event_name',
tag: 'tag',
};
export const COLLECTION_TYPE = {

View File

@ -47,6 +47,9 @@ export function formatNumber(n: string | number) {
export function formatLongNumber(value: number) {
const n = Number(value);
if (n >= 1000000000) {
return `${(n / 1000000).toFixed(1)}b`;
}
if (n >= 1000000) {
return `${(n / 1000000).toFixed(1)}m`;
}
@ -78,3 +81,38 @@ export function stringToColor(str: string) {
}
return color;
}
export function formatCurrency(value: number, currency: string, locale = 'en-US') {
let formattedValue;
try {
formattedValue = new Intl.NumberFormat(locale, {
style: 'currency',
currency: currency,
});
} catch (error) {
// Fallback to default currency format if an error occurs
formattedValue = new Intl.NumberFormat(locale, {
style: 'currency',
currency: 'USD',
});
}
return formattedValue.format(value);
}
export function formatLongCurrency(value: number, currency: string, locale = 'en-US') {
const n = Number(value);
if (n >= 1000000000) {
return `${formatCurrency(n / 1000000000, currency, locale)}b`;
}
if (n >= 1000000) {
return `${formatCurrency(n / 1000000, currency, locale)}m`;
}
if (n >= 1000) {
return `${formatCurrency(n / 1000, currency, locale)}k`;
}
return formatCurrency(n, currency, locale);
}

View File

@ -179,6 +179,7 @@ export interface QueryFilters {
language?: string;
event?: string;
search?: string;
tag?: string;
}
export interface QueryOptions {

View File

@ -5,49 +5,60 @@ import { TimezoneTest, UnitTypeTest } from 'lib/yup';
import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { getRevenue } from 'queries/analytics/reports/getRevenue';
import { getRevenueValues } from 'queries/analytics/reports/getRevenueValues';
import * as yup from 'yup';
export interface RetentionRequestBody {
export interface RevenueRequestBody {
websiteId: string;
dateRange: { startDate: string; endDate: string; unit?: string; timezone?: string };
eventName: string;
revenueProperty: string;
userProperty: string;
currency?: string;
timezone?: string;
dateRange: { startDate: string; endDate: string; unit?: string };
}
const schema = {
POST: yup.object().shape({
websiteId: yup.string().uuid().required(),
timezone: TimezoneTest,
dateRange: yup
.object()
.shape({
startDate: yup.date().required(),
endDate: yup.date().required(),
unit: UnitTypeTest,
timezone: TimezoneTest,
})
.required(),
eventName: yup.string().required(),
revenueProperty: yup.string().required(),
userProperty: yup.string(),
}),
};
export default async (
req: NextApiRequestQueryBody<any, RetentionRequestBody>,
req: NextApiRequestQueryBody<any, RevenueRequestBody>,
res: NextApiResponse,
) => {
await useCors(req, res);
await useAuth(req, res);
await useValidate(schema, req, res);
if (req.method === 'GET') {
const { websiteId, startDate, endDate } = req.query;
if (!(await canViewWebsite(req.auth, websiteId))) {
return unauthorized(res);
}
const data = await getRevenueValues(websiteId, {
startDate: new Date(startDate),
endDate: new Date(endDate),
});
return ok(res, data);
}
if (req.method === 'POST') {
const {
websiteId,
dateRange: { startDate, endDate, unit, timezone },
eventName,
revenueProperty,
userProperty,
currency,
timezone,
dateRange: { startDate, endDate, unit },
} = req.body;
if (!(await canViewWebsite(req.auth, websiteId))) {
@ -59,9 +70,7 @@ export default async (
endDate: new Date(endDate),
unit,
timezone,
eventName,
revenueProperty,
userProperty,
currency,
});
return ok(res, data);

Some files were not shown because too many files have changed in this diff Show More