mirror of
https://github.com/umami-software/umami.git
synced 2025-12-08 05:12:36 +01:00
Merge branch 'dev' into jajaja
# Conflicts: # db/postgresql/schema.prisma # pnpm-lock.yaml # src/app/(main)/websites/[websiteId]/WebsiteDetailsPage.tsx # src/app/(main)/websites/[websiteId]/compare/WebsiteComparePage.tsx # src/app/api/reports/route.ts # src/app/api/websites/[websiteId]/events/series/route.ts # src/app/api/websites/[websiteId]/metrics/route.ts # src/app/api/websites/[websiteId]/pageviews/route.ts # src/app/api/websites/[websiteId]/sessions/stats/route.ts # src/app/api/websites/[websiteId]/stats/route.ts # src/app/api/websites/[websiteId]/values/route.ts # src/components/hooks/useFields.ts # src/components/hooks/useFilterParams.ts # src/lang/vi-VN.json # src/lib/clickhouse.ts # src/lib/detect.ts # src/lib/prisma.ts # src/lib/request.ts # src/lib/schema.ts # src/lib/types.ts # src/queries/sql/events/getEventDataFields.ts # src/queries/sql/events/getEventDataProperties.ts # src/queries/sql/events/getEventDataStats.ts # src/queries/sql/events/getEventDataValues.ts # src/queries/sql/events/getEventMetrics.ts # src/queries/sql/events/getWebsiteEvents.ts # src/queries/sql/getChannelMetrics.ts # src/queries/sql/getRealtimeActivity.ts # src/queries/sql/getWebsiteStats.ts # src/queries/sql/pageviews/getPageviewMetrics.ts # src/queries/sql/pageviews/getPageviewStats.ts # src/queries/sql/reports/getBreakdown.ts # src/queries/sql/sessions/getSessionDataProperties.ts # src/queries/sql/sessions/getSessionDataValues.ts # src/queries/sql/sessions/getSessionMetrics.ts # src/queries/sql/sessions/getSessionStats.ts # src/queries/sql/sessions/getWebsiteSessionStats.ts # src/queries/sql/sessions/getWebsiteSessions.ts
This commit is contained in:
commit
87449ece9e
49 changed files with 704 additions and 345 deletions
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { uuid } from '../../src/lib/crypto';
|
||||||
|
|
||||||
describe('Website API tests', () => {
|
describe('Website API tests', () => {
|
||||||
Cypress.session.clearAllSavedSessions();
|
Cypress.session.clearAllSavedSessions();
|
||||||
|
|
||||||
|
|
@ -65,6 +67,37 @@ describe('Website API tests', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Creates a website with a fixed ID.', () => {
|
||||||
|
cy.fixture('websites').then(data => {
|
||||||
|
const websiteCreate = data.websiteCreate;
|
||||||
|
const fixedId = uuid();
|
||||||
|
cy.request({
|
||||||
|
method: 'POST',
|
||||||
|
url: '/api/websites',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: Cypress.env('authorization'),
|
||||||
|
},
|
||||||
|
body: { ...websiteCreate, id: fixedId },
|
||||||
|
}).then(response => {
|
||||||
|
expect(response.status).to.eq(200);
|
||||||
|
expect(response.body).to.have.property('id', fixedId);
|
||||||
|
expect(response.body).to.have.property('name', 'Cypress Website');
|
||||||
|
expect(response.body).to.have.property('domain', 'cypress.com');
|
||||||
|
|
||||||
|
// cleanup
|
||||||
|
cy.request({
|
||||||
|
method: 'DELETE',
|
||||||
|
url: `/api/websites/${fixedId}`,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: Cypress.env('authorization'),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('Returns all tracked websites.', () => {
|
it('Returns all tracked websites.', () => {
|
||||||
cy.request({
|
cy.request({
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,9 @@
|
||||||
CREATE TABLE "segment" (
|
CREATE TABLE "segment" (
|
||||||
"segment_id" UUID NOT NULL,
|
"segment_id" UUID NOT NULL,
|
||||||
"website_id" UUID NOT NULL,
|
"website_id" UUID NOT NULL,
|
||||||
|
"type" VARCHAR(200) NOT NULL,
|
||||||
"name" VARCHAR(200) NOT NULL,
|
"name" VARCHAR(200) NOT NULL,
|
||||||
"filters" JSONB NOT NULL,
|
"parameters" JSONB NOT NULL,
|
||||||
"created_at" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,
|
"created_at" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,
|
||||||
"updated_at" TIMESTAMPTZ(6),
|
"updated_at" TIMESTAMPTZ(6),
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,3 @@
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE "segment" ADD COLUMN "type" VARCHAR(200) NOT NULL;
|
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE "revenue" (
|
CREATE TABLE "revenue" (
|
||||||
"revenue_id" UUID NOT NULL,
|
"revenue_id" UUID NOT NULL,
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ model Session {
|
||||||
|
|
||||||
websiteEvent WebsiteEvent[]
|
websiteEvent WebsiteEvent[]
|
||||||
sessionData SessionData[]
|
sessionData SessionData[]
|
||||||
revenue Revenue[]
|
revenue Revenue[]
|
||||||
|
|
||||||
@@index([createdAt])
|
@@index([createdAt])
|
||||||
@@index([websiteId])
|
@@index([websiteId])
|
||||||
|
|
@ -224,7 +224,7 @@ model Report {
|
||||||
type String @db.VarChar(200)
|
type String @db.VarChar(200)
|
||||||
name String @db.VarChar(200)
|
name String @db.VarChar(200)
|
||||||
description String @db.VarChar(500)
|
description String @db.VarChar(500)
|
||||||
parameters Json
|
parameters Json
|
||||||
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
|
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
|
||||||
updatedAt DateTime? @updatedAt @map("updated_at") @db.Timestamptz(6)
|
updatedAt DateTime? @updatedAt @map("updated_at") @db.Timestamptz(6)
|
||||||
|
|
||||||
|
|
@ -239,13 +239,13 @@ model Report {
|
||||||
}
|
}
|
||||||
|
|
||||||
model Segment {
|
model Segment {
|
||||||
id String @id() @unique() @map("segment_id") @db.Uuid
|
id String @id() @unique() @map("segment_id") @db.Uuid
|
||||||
websiteId String @map("website_id") @db.Uuid
|
websiteId String @map("website_id") @db.Uuid
|
||||||
type String @db.VarChar(200)
|
type String @db.VarChar(200)
|
||||||
name String @db.VarChar(200)
|
name String @db.VarChar(200)
|
||||||
filters Json
|
parameters Json
|
||||||
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
|
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
|
||||||
updatedAt DateTime? @updatedAt @map("updated_at") @db.Timestamptz(6)
|
updatedAt DateTime? @updatedAt @map("updated_at") @db.Timestamptz(6)
|
||||||
|
|
||||||
website Website @relation(fields: [websiteId], references: [id])
|
website Website @relation(fields: [websiteId], references: [id])
|
||||||
|
|
||||||
|
|
@ -254,14 +254,14 @@ model Segment {
|
||||||
}
|
}
|
||||||
|
|
||||||
model Revenue {
|
model Revenue {
|
||||||
id String @id() @unique() @map("revenue_id") @db.Uuid
|
id String @id() @unique() @map("revenue_id") @db.Uuid
|
||||||
websiteId String @map("website_id") @db.Uuid
|
websiteId String @map("website_id") @db.Uuid
|
||||||
sessionId String @map("session_id") @db.Uuid
|
sessionId String @map("session_id") @db.Uuid
|
||||||
eventId String @map("event_id") @db.Uuid
|
eventId String @map("event_id") @db.Uuid
|
||||||
eventName String @map("event_name") @db.VarChar(50)
|
eventName String @map("event_name") @db.VarChar(50)
|
||||||
currency String @db.VarChar(100)
|
currency String @db.VarChar(100)
|
||||||
revenue Decimal? @db.Decimal(19, 4)
|
revenue Decimal? @db.Decimal(19, 4)
|
||||||
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
|
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
|
||||||
|
|
||||||
website Website @relation(fields: [websiteId], references: [id])
|
website Website @relation(fields: [websiteId], references: [id])
|
||||||
session Session @relation(fields: [sessionId], references: [id])
|
session Session @relation(fields: [sessionId], references: [id])
|
||||||
|
|
|
||||||
|
|
@ -24,11 +24,6 @@ export function SettingsLayout({ children }: { children: ReactNode }) {
|
||||||
label: formatMessage(labels.websites),
|
label: formatMessage(labels.websites),
|
||||||
url: '/settings/websites',
|
url: '/settings/websites',
|
||||||
},
|
},
|
||||||
user.isAdmin && {
|
|
||||||
id: 'users',
|
|
||||||
label: formatMessage(labels.users),
|
|
||||||
url: '/settings/users',
|
|
||||||
},
|
|
||||||
].filter(n => n);
|
].filter(n => n);
|
||||||
|
|
||||||
const value = items.find(({ url }) => pathname.includes(url))?.id;
|
const value = items.find(({ url }) => pathname.includes(url))?.id;
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ export async function GET(
|
||||||
return unauthorized();
|
return unauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
const filters = await getQueryFilters({
|
const filters = getQueryFilters({
|
||||||
...query,
|
...query,
|
||||||
websiteId,
|
websiteId,
|
||||||
startAt: subMinutes(startOfMinute(new Date()), REALTIME_RANGE).getTime(),
|
startAt: subMinutes(startOfMinute(new Date()), REALTIME_RANGE).getTime(),
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ export async function GET(request: Request, { params }: { params: Promise<{ user
|
||||||
}
|
}
|
||||||
|
|
||||||
const { userId } = await params;
|
const { userId } = await params;
|
||||||
const filters = await getQueryFilters(query);
|
const filters = getQueryFilters(query);
|
||||||
|
|
||||||
const websites = await getAllUserWebsitesIncludingTeamOwner(userId);
|
const websites = await getAllUserWebsitesIncludingTeamOwner(userId);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ export async function GET(
|
||||||
}
|
}
|
||||||
|
|
||||||
const { event } = query;
|
const { event } = query;
|
||||||
const filters = await getQueryFilters(query);
|
const filters = getQueryFilters(query);
|
||||||
|
|
||||||
const data = await getEventDataEvents(websiteId, {
|
const data = await getEventDataEvents(websiteId, {
|
||||||
...filters,
|
...filters,
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ export async function GET(
|
||||||
return unauthorized();
|
return unauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
const filters = await getQueryFilters(query);
|
const filters = getQueryFilters(query);
|
||||||
|
|
||||||
const data = await getEventDataFields(websiteId, filters);
|
const data = await getEventDataFields(websiteId, filters);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ export async function GET(
|
||||||
}
|
}
|
||||||
|
|
||||||
const { propertyName } = query;
|
const { propertyName } = query;
|
||||||
const filters = await getQueryFilters(query);
|
const filters = getQueryFilters(query);
|
||||||
|
|
||||||
const data = await getEventDataProperties(websiteId, { ...filters, propertyName });
|
const data = await getEventDataProperties(websiteId, { ...filters, propertyName });
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ export async function GET(
|
||||||
return unauthorized();
|
return unauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
const filters = await getQueryFilters(query);
|
const filters = getQueryFilters(query);
|
||||||
|
|
||||||
const data = await getEventDataStats(websiteId, filters);
|
const data = await getEventDataStats(websiteId, filters);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ export async function GET(
|
||||||
}
|
}
|
||||||
|
|
||||||
const { eventName, propertyName } = query;
|
const { eventName, propertyName } = query;
|
||||||
const filters = await getQueryFilters(query);
|
const filters = getQueryFilters(query);
|
||||||
|
|
||||||
const data = await getEventDataValues(websiteId, {
|
const data = await getEventDataValues(websiteId, {
|
||||||
...filters,
|
...filters,
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ export async function GET(
|
||||||
return unauthorized();
|
return unauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
const filters = await getQueryFilters(query);
|
const filters = getQueryFilters(query);
|
||||||
|
|
||||||
const data = await getWebsiteEvents(websiteId, filters);
|
const data = await getWebsiteEvents(websiteId, filters);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
import { canDeleteWebsite, canUpdateWebsite, canViewWebsite } from '@/lib/auth';
|
||||||
|
import { parseRequest } from '@/lib/request';
|
||||||
|
import { json, notFound, ok, unauthorized } from '@/lib/response';
|
||||||
|
import { segmentTypeParam } from '@/lib/schema';
|
||||||
|
import { deleteSegment, getSegment, updateSegment } from '@/queries';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
export async function GET(
|
||||||
|
request: Request,
|
||||||
|
{ params }: { params: Promise<{ websiteId: string; segmentId: string }> },
|
||||||
|
) {
|
||||||
|
const { auth, error } = await parseRequest(request);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return error();
|
||||||
|
}
|
||||||
|
|
||||||
|
const { websiteId, segmentId } = await params;
|
||||||
|
|
||||||
|
const segment = await getSegment(segmentId);
|
||||||
|
|
||||||
|
if (websiteId && !(await canViewWebsite(auth, websiteId))) {
|
||||||
|
return unauthorized();
|
||||||
|
}
|
||||||
|
|
||||||
|
return json(segment);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function POST(
|
||||||
|
request: Request,
|
||||||
|
{ params }: { params: Promise<{ websiteId: string; segmentId: string }> },
|
||||||
|
) {
|
||||||
|
const schema = z.object({
|
||||||
|
type: segmentTypeParam,
|
||||||
|
name: z.string().max(200),
|
||||||
|
parameters: z.object({}).passthrough(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const { auth, body, error } = await parseRequest(request, schema);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return error();
|
||||||
|
}
|
||||||
|
|
||||||
|
const { websiteId, segmentId } = await params;
|
||||||
|
const { type, name, parameters } = body;
|
||||||
|
|
||||||
|
const segment = await getSegment(segmentId);
|
||||||
|
|
||||||
|
if (!segment) {
|
||||||
|
return notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await canUpdateWebsite(auth, websiteId))) {
|
||||||
|
return unauthorized();
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await updateSegment(segmentId, {
|
||||||
|
type,
|
||||||
|
name,
|
||||||
|
parameters,
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
return json(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function DELETE(
|
||||||
|
request: Request,
|
||||||
|
{ params }: { params: Promise<{ websiteId: string; segmentId: string }> },
|
||||||
|
) {
|
||||||
|
const { auth, error } = await parseRequest(request);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return error();
|
||||||
|
}
|
||||||
|
|
||||||
|
const { websiteId, segmentId } = await params;
|
||||||
|
|
||||||
|
const segment = await getSegment(segmentId);
|
||||||
|
|
||||||
|
if (!segment) {
|
||||||
|
return notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await canDeleteWebsite(auth, websiteId))) {
|
||||||
|
return unauthorized();
|
||||||
|
}
|
||||||
|
|
||||||
|
await deleteSegment(segmentId);
|
||||||
|
|
||||||
|
return ok();
|
||||||
|
}
|
||||||
67
src/app/api/websites/[websiteId]/segments/route.ts
Normal file
67
src/app/api/websites/[websiteId]/segments/route.ts
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
import { canUpdateWebsite, canViewWebsite } from '@/lib/auth';
|
||||||
|
import { uuid } from '@/lib/crypto';
|
||||||
|
import { parseRequest } from '@/lib/request';
|
||||||
|
import { json, unauthorized } from '@/lib/response';
|
||||||
|
import { segmentTypeParam } from '@/lib/schema';
|
||||||
|
import { createSegment, getWebsiteSegments } from '@/queries';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
export async function GET(
|
||||||
|
request: Request,
|
||||||
|
{ params }: { params: Promise<{ websiteId: string }> },
|
||||||
|
) {
|
||||||
|
const schema = z.object({
|
||||||
|
type: segmentTypeParam,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { auth, query, error } = await parseRequest(request, schema);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return error();
|
||||||
|
}
|
||||||
|
|
||||||
|
const { websiteId } = await params;
|
||||||
|
const { type } = query;
|
||||||
|
|
||||||
|
if (websiteId && !(await canViewWebsite(auth, websiteId))) {
|
||||||
|
return unauthorized();
|
||||||
|
}
|
||||||
|
|
||||||
|
const segments = await getWebsiteSegments(websiteId, type);
|
||||||
|
|
||||||
|
return json(segments);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function POST(
|
||||||
|
request: Request,
|
||||||
|
{ params }: { params: Promise<{ websiteId: string }> },
|
||||||
|
) {
|
||||||
|
const schema = z.object({
|
||||||
|
type: segmentTypeParam,
|
||||||
|
name: z.string().max(200),
|
||||||
|
parameters: z.object({}).passthrough(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const { auth, body, error } = await parseRequest(request, schema);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return error();
|
||||||
|
}
|
||||||
|
|
||||||
|
const { websiteId } = await params;
|
||||||
|
const { type, name, parameters } = body;
|
||||||
|
|
||||||
|
if (!(await canUpdateWebsite(auth, websiteId))) {
|
||||||
|
return unauthorized();
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await createSegment({
|
||||||
|
id: uuid(),
|
||||||
|
websiteId,
|
||||||
|
type,
|
||||||
|
name,
|
||||||
|
parameters,
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
return json(result);
|
||||||
|
}
|
||||||
|
|
@ -22,7 +22,7 @@ export async function GET(
|
||||||
|
|
||||||
const { websiteId } = await params;
|
const { websiteId } = await params;
|
||||||
const { propertyName } = query;
|
const { propertyName } = query;
|
||||||
const filters = await getQueryFilters(query);
|
const filters = getQueryFilters(query);
|
||||||
|
|
||||||
if (!(await canViewWebsite(auth, websiteId))) {
|
if (!(await canViewWebsite(auth, websiteId))) {
|
||||||
return unauthorized();
|
return unauthorized();
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ export async function GET(
|
||||||
return unauthorized();
|
return unauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
const filters = await getQueryFilters(query);
|
const filters = getQueryFilters(query);
|
||||||
|
|
||||||
const data = await getSessionActivity(websiteId, sessionId, filters);
|
const data = await getSessionActivity(websiteId, sessionId, filters);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ export async function GET(
|
||||||
return unauthorized();
|
return unauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
const filters = await getQueryFilters(query);
|
const filters = getQueryFilters(query);
|
||||||
|
|
||||||
const data = await getWebsiteSessionsWeekly(websiteId, filters);
|
const data = await getWebsiteSessionsWeekly(websiteId, filters);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
import { z } from 'zod';
|
|
||||||
import { canViewWebsite } from '@/lib/auth';
|
import { canViewWebsite } from '@/lib/auth';
|
||||||
import { EVENT_COLUMNS, FILTER_COLUMNS, SESSION_COLUMNS } from '@/lib/constants';
|
import { EVENT_COLUMNS, FILTER_COLUMNS, FILTER_GROUPS, SESSION_COLUMNS } from '@/lib/constants';
|
||||||
import { getValues } from '@/queries';
|
import { getQueryFilters, parseRequest } from '@/lib/request';
|
||||||
import { parseRequest, getQueryFilters } from '@/lib/request';
|
|
||||||
import { badRequest, json, unauthorized } from '@/lib/response';
|
import { badRequest, json, unauthorized } from '@/lib/response';
|
||||||
|
import { getWebsiteSegments, getValues } from '@/queries';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { dateRangeParams, searchParams } from '@/lib/schema';
|
||||||
|
|
||||||
export async function GET(
|
export async function GET(
|
||||||
request: Request,
|
request: Request,
|
||||||
|
|
@ -11,9 +12,8 @@ export async function GET(
|
||||||
) {
|
) {
|
||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
type: z.string(),
|
type: z.string(),
|
||||||
startAt: z.coerce.number().int(),
|
...dateRangeParams,
|
||||||
endAt: z.coerce.number().int(),
|
...searchParams,
|
||||||
search: z.string().optional(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const { auth, query, error } = await parseRequest(request, schema);
|
const { auth, query, error } = await parseRequest(request, schema);
|
||||||
|
|
@ -23,19 +23,25 @@ export async function GET(
|
||||||
}
|
}
|
||||||
|
|
||||||
const { websiteId } = await params;
|
const { websiteId } = await params;
|
||||||
const { type } = query;
|
|
||||||
|
|
||||||
if (!(await canViewWebsite(auth, websiteId))) {
|
if (!(await canViewWebsite(auth, websiteId))) {
|
||||||
return unauthorized();
|
return unauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!SESSION_COLUMNS.includes(type) && !EVENT_COLUMNS.includes(type)) {
|
const { type } = query;
|
||||||
|
|
||||||
|
if (!SESSION_COLUMNS.includes(type) && !EVENT_COLUMNS.includes(type) && !FILTER_GROUPS[type]) {
|
||||||
return badRequest('Invalid type.');
|
return badRequest('Invalid type.');
|
||||||
}
|
}
|
||||||
|
|
||||||
const filters = await getQueryFilters(query);
|
let values;
|
||||||
|
|
||||||
const values = await getValues(websiteId, FILTER_COLUMNS[type], { ...filters });
|
if (FILTER_GROUPS[type]) {
|
||||||
|
values = (await getWebsiteSegments(websiteId, type)).map(segment => ({ value: segment.name }));
|
||||||
|
} else {
|
||||||
|
const filters = getQueryFilters(query);
|
||||||
|
values = await getValues(websiteId, FILTER_COLUMNS[type], filters);
|
||||||
|
}
|
||||||
|
|
||||||
return json(values.filter(n => n).sort());
|
return json(values.filter(n => n).sort());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ export async function POST(request: Request) {
|
||||||
domain: z.string().max(500),
|
domain: z.string().max(500),
|
||||||
shareId: z.string().max(50).nullable().optional(),
|
shareId: z.string().max(50).nullable().optional(),
|
||||||
teamId: z.string().nullable().optional(),
|
teamId: z.string().nullable().optional(),
|
||||||
|
id: z.string().uuid().nullable().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { auth, body, error } = await parseRequest(request, schema);
|
const { auth, body, error } = await parseRequest(request, schema);
|
||||||
|
|
@ -21,14 +22,14 @@ export async function POST(request: Request) {
|
||||||
return error();
|
return error();
|
||||||
}
|
}
|
||||||
|
|
||||||
const { name, domain, shareId, teamId } = body;
|
const { id, name, domain, shareId, teamId } = body;
|
||||||
|
|
||||||
if ((teamId && !(await canCreateTeamWebsite(auth, teamId))) || !(await canCreateWebsite(auth))) {
|
if ((teamId && !(await canCreateTeamWebsite(auth, teamId))) || !(await canCreateWebsite(auth))) {
|
||||||
return unauthorized();
|
return unauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
const data: any = {
|
const data: any = {
|
||||||
id: uuid(),
|
id: id ?? uuid(),
|
||||||
createdBy: auth.user.id,
|
createdBy: auth.user.id,
|
||||||
name,
|
name,
|
||||||
domain,
|
domain,
|
||||||
|
|
|
||||||
|
|
@ -17,20 +17,13 @@ export function LinkButton({
|
||||||
scroll = true,
|
scroll = true,
|
||||||
target,
|
target,
|
||||||
children,
|
children,
|
||||||
isDisabled,
|
|
||||||
...props
|
...props
|
||||||
}: LinkButtonProps) {
|
}: LinkButtonProps) {
|
||||||
const { dir } = useLocale();
|
const { dir } = useLocale();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button {...props} variant={variant} isDisabled={isDisabled} asChild>
|
<Button {...props} variant={variant} asChild>
|
||||||
<Link
|
<Link href={href} dir={dir} scroll={scroll} target={target}>
|
||||||
href={href}
|
|
||||||
dir={dir}
|
|
||||||
scroll={scroll}
|
|
||||||
target={target}
|
|
||||||
style={{ pointerEvents: isDisabled ? 'none' : undefined }}
|
|
||||||
>
|
|
||||||
{children}
|
{children}
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,9 @@ export function useFields() {
|
||||||
|
|
||||||
const fields = [
|
const fields = [
|
||||||
{ name: 'path', type: 'string', label: formatMessage(labels.path) },
|
{ name: 'path', type: 'string', label: formatMessage(labels.path) },
|
||||||
|
// { name: 'cohort', type: 'string', label: formatMessage(labels.cohort) },
|
||||||
|
// { name: 'segment', type: 'string', label: formatMessage(labels.segment) },
|
||||||
|
{ name: 'url', type: 'string', label: formatMessage(labels.url) },
|
||||||
{ name: 'title', type: 'string', label: formatMessage(labels.pageTitle) },
|
{ name: 'title', type: 'string', label: formatMessage(labels.pageTitle) },
|
||||||
{ name: 'referrer', type: 'string', label: formatMessage(labels.referrer) },
|
{ name: 'referrer', type: 'string', label: formatMessage(labels.referrer) },
|
||||||
//{ name: 'query', type: 'string', label: formatMessage(labels.query) },
|
//{ name: 'query', type: 'string', label: formatMessage(labels.query) },
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,8 @@ export const labels = defineMessages({
|
||||||
countries: { id: 'label.countries', defaultMessage: 'Countries' },
|
countries: { id: 'label.countries', defaultMessage: 'Countries' },
|
||||||
languages: { id: 'label.languages', defaultMessage: 'Languages' },
|
languages: { id: 'label.languages', defaultMessage: 'Languages' },
|
||||||
tags: { id: 'label.tags', defaultMessage: 'Tags' },
|
tags: { id: 'label.tags', defaultMessage: 'Tags' },
|
||||||
|
segments: { id: 'label.segments', defaultMessage: 'Segments' },
|
||||||
|
cohorts: { id: 'label.cohorts', defaultMessage: 'Cohorts' },
|
||||||
count: { id: 'label.count', defaultMessage: 'Count' },
|
count: { id: 'label.count', defaultMessage: 'Count' },
|
||||||
average: { id: 'label.average', defaultMessage: 'Average' },
|
average: { id: 'label.average', defaultMessage: 'Average' },
|
||||||
sum: { id: 'label.sum', defaultMessage: 'Sum' },
|
sum: { id: 'label.sum', defaultMessage: 'Sum' },
|
||||||
|
|
@ -239,6 +241,8 @@ export const labels = defineMessages({
|
||||||
device: { id: 'label.device', defaultMessage: 'Device' },
|
device: { id: 'label.device', defaultMessage: 'Device' },
|
||||||
pageTitle: { id: 'label.pageTitle', defaultMessage: 'Page title' },
|
pageTitle: { id: 'label.pageTitle', defaultMessage: 'Page title' },
|
||||||
tag: { id: 'label.tag', defaultMessage: 'Tag' },
|
tag: { id: 'label.tag', defaultMessage: 'Tag' },
|
||||||
|
segment: { id: 'label.segment', defaultMessage: 'Segment' },
|
||||||
|
cohort: { id: 'label.cohort', defaultMessage: 'Cohort' },
|
||||||
day: { id: 'label.day', defaultMessage: 'Day' },
|
day: { id: 'label.day', defaultMessage: 'Day' },
|
||||||
date: { id: 'label.date', defaultMessage: 'Date' },
|
date: { id: 'label.date', defaultMessage: 'Date' },
|
||||||
pageOf: { id: 'label.page-of', defaultMessage: 'Page {current} of {total}' },
|
pageOf: { id: 'label.page-of', defaultMessage: 'Page {current} of {total}' },
|
||||||
|
|
@ -324,7 +328,6 @@ export const labels = defineMessages({
|
||||||
links: { id: 'label.links', defaultMessage: 'Links' },
|
links: { id: 'label.links', defaultMessage: 'Links' },
|
||||||
pixels: { id: 'label.pixels', defaultMessage: 'Pixels' },
|
pixels: { id: 'label.pixels', defaultMessage: 'Pixels' },
|
||||||
addBoard: { id: 'label.add-board', defaultMessage: 'Add board' },
|
addBoard: { id: 'label.add-board', defaultMessage: 'Add board' },
|
||||||
cohort: { id: 'label.cohort', defaultMessage: 'Cohort' },
|
|
||||||
maximize: { id: 'label.maximize', defaultMessage: 'Maximize' },
|
maximize: { id: 'label.maximize', defaultMessage: 'Maximize' },
|
||||||
remaining: { id: 'label.remaining', defaultMessage: 'Remaining' },
|
remaining: { id: 'label.remaining', defaultMessage: 'Remaining' },
|
||||||
conversion: { id: 'label.conversion', defaultMessage: 'Conversion' },
|
conversion: { id: 'label.conversion', defaultMessage: 'Conversion' },
|
||||||
|
|
|
||||||
|
|
@ -3,336 +3,277 @@
|
||||||
"label.actions": "Hành động",
|
"label.actions": "Hành động",
|
||||||
"label.activity": "Nhật ký hoạt động",
|
"label.activity": "Nhật ký hoạt động",
|
||||||
"label.add": "Thêm",
|
"label.add": "Thêm",
|
||||||
"label.add-board": "Thêm bảng",
|
|
||||||
"label.add-description": "Thêm mô tả",
|
"label.add-description": "Thêm mô tả",
|
||||||
"label.add-member": "Thêm thành viên",
|
"label.add-member": "Thêm thành viên",
|
||||||
"label.add-step": "Thêm bước",
|
"label.add-step": "Thêm bước",
|
||||||
"label.add-website": "Thêm website",
|
"label.add-website": "Thêm website",
|
||||||
"label.admin": "Quản trị",
|
"label.admin": "Quản trị",
|
||||||
"label.affiliate": "Liên kết",
|
"label.after": "Sau đó",
|
||||||
"label.after": "Sau",
|
|
||||||
"label.all": "Tất cả",
|
"label.all": "Tất cả",
|
||||||
"label.all-time": "Toàn thời gian",
|
"label.all-time": "Toàn thời gian",
|
||||||
"label.analytics": "Phân tích",
|
"label.analytics": "Phân tích",
|
||||||
"label.apply": "Áp dụng",
|
|
||||||
"label.attribution": "Phân bổ",
|
|
||||||
"label.attribution-description": "Xem cách người dùng tương tác với tiếp thị của bạn và điều gì thúc đẩy chuyển đổi.",
|
|
||||||
"label.average": "Trung bình",
|
"label.average": "Trung bình",
|
||||||
"label.back": "Quay về",
|
"label.back": "Quay lại",
|
||||||
"label.before": "Trước",
|
"label.before": "Trước đó",
|
||||||
"label.boards": "Bảng",
|
|
||||||
"label.bounce-rate": "Tỷ lệ thoát trang",
|
"label.bounce-rate": "Tỷ lệ thoát trang",
|
||||||
"label.breakdown": "Phân tích chi tiết",
|
"label.breakdown": "Phân tích chi tiết",
|
||||||
"label.browser": "Trình duyệt",
|
"label.browser": "Trình duyệt",
|
||||||
"label.browsers": "Trình duyệt",
|
"label.browsers": "Các trình duyệt",
|
||||||
"label.campaigns": "Chiến dịch",
|
"label.cancel": "Hủy bỏ",
|
||||||
"label.cancel": "Huỷ bỏ",
|
|
||||||
"label.change-password": "Đổi mật khẩu",
|
"label.change-password": "Đổi mật khẩu",
|
||||||
"label.channels": "Kênh",
|
"label.cities": "Các thành phố",
|
||||||
"label.cities": "Thành phố",
|
|
||||||
"label.city": "Thành phố",
|
"label.city": "Thành phố",
|
||||||
"label.clear-all": "Xóa tất cả",
|
"label.clear-all": "Xóa tất cả",
|
||||||
"label.cohort": "Nhóm người dùng",
|
|
||||||
"label.compare": "So sánh",
|
"label.compare": "So sánh",
|
||||||
"label.compare-dates": "So sánh ngày",
|
|
||||||
"label.confirm": "Xác nhận",
|
"label.confirm": "Xác nhận",
|
||||||
"label.confirm-password": "Xác nhận mật khẩu",
|
"label.confirm-password": "Xác nhận mật khẩu",
|
||||||
"label.contains": "Chứa",
|
"label.contains": "Chứa",
|
||||||
"label.content": "Nội dung",
|
|
||||||
"label.continue": "Tiếp tục",
|
"label.continue": "Tiếp tục",
|
||||||
"label.conversion": "Chuyển đổi",
|
|
||||||
"label.conversion-rate": "Tỷ lệ chuyển đổi",
|
|
||||||
"label.conversion-step": "Bước chuyển đổi",
|
|
||||||
"label.count": "Số lượng",
|
"label.count": "Số lượng",
|
||||||
"label.countries": "Quốc gia",
|
"label.countries": "Các quốc gia",
|
||||||
"label.country": "Quốc gia",
|
"label.country": "Quốc gia",
|
||||||
"label.create": "Tạo",
|
"label.create": "Tạo",
|
||||||
"label.create-report": "Tạo báo cáo",
|
"label.create-report": "Tạo báo cáo",
|
||||||
"label.create-team": "Tạo nhóm",
|
"label.create-team": "Tạo nhóm",
|
||||||
"label.create-user": "Tạo người dùng",
|
"label.create-user": "Tạo người dùng",
|
||||||
"label.created": "Đã tạo",
|
"label.created": "Đã tạo",
|
||||||
"label.created-by": "Tạo bởi",
|
"label.created-by": "Được tạo bởi",
|
||||||
"label.currency": "Tiền tệ",
|
|
||||||
"label.current": "Hiện tại",
|
"label.current": "Hiện tại",
|
||||||
"label.current-password": "Mật khẩu hiện tại",
|
"label.current-password": "Mật khẩu hiện tại",
|
||||||
"label.custom-range": "Phạm vi ngày tuỳ chọn",
|
"label.custom-range": "Phạm vi tùy chỉnh",
|
||||||
"label.dashboard": "Bảng điều khiển",
|
"label.dashboard": "Bảng điều khiển",
|
||||||
"label.data": "Dữ liệu",
|
"label.data": "Dữ liệu",
|
||||||
"label.date": "Ngày",
|
"label.date": "Ngày",
|
||||||
"label.date-range": "Phạm vi ngày",
|
"label.date-range": "Phạm vi ngày",
|
||||||
"label.day": "Ngày",
|
"label.day": "Ngày",
|
||||||
"label.default-date-range": "Khoảng thời gian mặc định",
|
"label.default-date-range": "Khoảng thời gian mặc định",
|
||||||
"label.delete": "Xoá",
|
"label.delete": "Xóa",
|
||||||
"label.delete-report": "Xóa báo cáo",
|
"label.delete-report": "Xóa báo cáo",
|
||||||
"label.delete-team": "Xóa nhóm",
|
"label.delete-team": "Xóa nhóm",
|
||||||
"label.delete-user": "Xóa người dùng",
|
"label.delete-user": "Xóa người dùng",
|
||||||
"label.delete-website": "Xóa website",
|
"label.delete-website": "Xóa website",
|
||||||
"label.description": "Mô tả",
|
"label.description": "Mô tả",
|
||||||
"label.desktop": "Máy bàn",
|
"label.desktop": "Máy tính để bàn",
|
||||||
"label.details": "Chi tiết",
|
"label.details": "Chi tiết",
|
||||||
"label.device": "Thiết bị",
|
"label.device": "Thiết bị",
|
||||||
"label.devices": "Thiết bị",
|
"label.devices": "Các thiết bị",
|
||||||
"label.direct": "Trực tiếp",
|
"label.dismiss": "Bỏ qua",
|
||||||
"label.dismiss": "Loại trừ",
|
|
||||||
"label.distinct-id": "ID riêng biệt",
|
|
||||||
"label.does-not-contain": "Không chứa",
|
"label.does-not-contain": "Không chứa",
|
||||||
"label.does-not-include": "Không bao gồm",
|
|
||||||
"label.doest-not-exist": "Không tồn tại",
|
|
||||||
"label.domain": "Tên miền",
|
"label.domain": "Tên miền",
|
||||||
"label.dropoff": "Rời bỏ",
|
"label.dropoff": "Tỷ lệ bỏ qua",
|
||||||
"label.edit": "Chỉnh sửa",
|
"label.edit": "Chỉnh sửa",
|
||||||
"label.edit-dashboard": "Chỉnh sửa bảng điều khiển",
|
"label.edit-dashboard": "Chỉnh sửa bảng điều khiển",
|
||||||
"label.edit-member": "Chỉnh sửa thành viên",
|
"label.edit-member": "Chỉnh sửa thành viên",
|
||||||
"label.email": "Email",
|
"label.enable-share-url": "Bật chia sẻ URL",
|
||||||
"label.enable-share-url": "Bật khả năng chia sẻ URL",
|
|
||||||
"label.end-step": "Bước kết thúc",
|
"label.end-step": "Bước kết thúc",
|
||||||
"label.entry": "URL vào",
|
"label.entry": "URL truy cập",
|
||||||
"label.event": "Sự kiện",
|
"label.event": "Sự kiện",
|
||||||
"label.event-data": "Dữ liệu sự kiện",
|
"label.event-data": "Dữ liệu sự kiện",
|
||||||
"label.event-name": "Tên sự kiện",
|
"label.events": "Các sự kiện",
|
||||||
"label.events": "Sự kiện",
|
|
||||||
"label.exists": "Tồn tại",
|
|
||||||
"label.exit": "URL thoát",
|
"label.exit": "URL thoát",
|
||||||
"label.false": "Sai",
|
"label.false": "Sai",
|
||||||
"label.field": "Trường",
|
"label.field": "Trường",
|
||||||
"label.fields": "Các trường",
|
"label.fields": "Các trường",
|
||||||
"label.filter": "Bộ lọc",
|
"label.filter": "Lọc",
|
||||||
"label.filter-combined": "Kết hợp",
|
"label.filter-combined": "Kết hợp lọc",
|
||||||
"label.filter-raw": "Gốc",
|
"label.filter-raw": "Lọc thô",
|
||||||
"label.filters": "Bộ lọc",
|
"label.filters": "Bộ lọc",
|
||||||
"label.first-click": "Nhấp đầu tiên",
|
"label.first-seen": "Lần đầu tiên nhìn thấy",
|
||||||
"label.first-seen": "Lần đầu thấy",
|
|
||||||
"label.funnel": "Phễu",
|
"label.funnel": "Phễu",
|
||||||
"label.funnel-description": "Hiểu tỷ lệ chuyển đổi và rời bỏ của người dùng.",
|
"label.funnel-description": "Tìm hiểu tỷ lệ chuyển đổi và bỏ qua của người dùng.",
|
||||||
"label.funnels": "Phễu",
|
|
||||||
"label.goal": "Mục tiêu",
|
"label.goal": "Mục tiêu",
|
||||||
"label.goals": "Mục tiêu",
|
"label.goals": "Các mục tiêu",
|
||||||
"label.goals-description": "Theo dõi mục tiêu lượt xem trang và sự kiện.",
|
"label.goals-description": "Theo dõi các mục tiêu của bạn cho lượt xem trang và sự kiện.",
|
||||||
"label.greater-than": "Lớn hơn",
|
"label.greater-than": "Lớn hơn",
|
||||||
"label.greater-than-equals": "Lớn hơn hoặc bằng",
|
"label.greater-than-equals": "Lớn hơn hoặc bằng",
|
||||||
"label.grouped": "Nhóm lại",
|
"label.host": "Máy chủ",
|
||||||
"label.hostname": "Tên máy chủ",
|
"label.hosts": "Các máy chủ",
|
||||||
"label.includes": "Bao gồm",
|
"label.insights": "Thông tin chi tiết",
|
||||||
"label.insight": "Thông tin chi tiết",
|
"label.insights-description": "Tìm hiểu sâu hơn về dữ liệu của bạn bằng cách sử dụng phân đoạn và bộ lọc.",
|
||||||
"label.insights": "Insights",
|
|
||||||
"label.insights-description": "Dive deeper into your data by using segments and filters.",
|
|
||||||
"label.is": "Là",
|
"label.is": "Là",
|
||||||
"label.is-false": "Sai",
|
"label.is-not": "Không phải là",
|
||||||
"label.is-not": "Không là",
|
"label.is-not-set": "Chưa được đặt",
|
||||||
"label.is-not-set": "Chưa đặt",
|
|
||||||
"label.is-set": "Đã đặt",
|
"label.is-set": "Đã đặt",
|
||||||
"label.is-true": "Đúng",
|
|
||||||
"label.join": "Tham gia",
|
"label.join": "Tham gia",
|
||||||
"label.join-team": "Tham gia nhóm",
|
"label.join-team": "Tham gia nhóm",
|
||||||
"label.journey": "Hành trình",
|
"label.journey": "Hành trình",
|
||||||
"label.journey-description": "Hiểu cách người dùng di chuyển qua website của bạn.",
|
"label.journey-description": "Hiểu cách người dùng điều hướng qua website của bạn.",
|
||||||
"label.journeys": "Hành trình",
|
|
||||||
"label.language": "Ngôn ngữ",
|
"label.language": "Ngôn ngữ",
|
||||||
"label.languages": "Ngôn ngữ",
|
"label.languages": "Các ngôn ngữ",
|
||||||
"label.laptop": "Laptop",
|
"label.laptop": "Máy tính xách tay",
|
||||||
"label.last-click": "Nhấp cuối cùng",
|
|
||||||
"label.last-days": "{x} ngày gần nhất",
|
"label.last-days": "{x} ngày gần nhất",
|
||||||
"label.last-hours": "{x} giờ gần nhất",
|
"label.last-hours": "{x} giờ gần nhất",
|
||||||
"label.last-months": "{x} tháng gần nhất",
|
"label.last-months": "{x} tháng gần nhất",
|
||||||
"label.last-seen": "Lần cuối thấy",
|
"label.last-seen": "Lần cuối cùng nhìn thấy",
|
||||||
"label.leave": "Rời đi",
|
"label.leave": "Rời khỏi",
|
||||||
"label.leave-team": "Rời nhóm",
|
"label.leave-team": "Rời nhóm",
|
||||||
"label.less-than": "Nhỏ hơn",
|
"label.less-than": "Nhỏ hơn",
|
||||||
"label.less-than-equals": "Nhỏ hơn hoặc bằng",
|
"label.less-than-equals": "Nhỏ hơn hoặc bằng",
|
||||||
"label.links": "Liên kết",
|
|
||||||
"label.login": "Đăng nhập",
|
"label.login": "Đăng nhập",
|
||||||
"label.logout": "Đăng xuất",
|
"label.logout": "Đăng xuất",
|
||||||
"label.manage": "Quản lý",
|
"label.manage": "Quản lý",
|
||||||
"label.manager": "Quản lý viên",
|
"label.manager": "Quản lý",
|
||||||
"label.max": "Tối đa",
|
"label.max": "Tối đa",
|
||||||
"label.maximize": "Mở rộng",
|
|
||||||
"label.medium": "Trung bình",
|
|
||||||
"label.member": "Thành viên",
|
"label.member": "Thành viên",
|
||||||
"label.members": "Các thành viên",
|
"label.members": "Các thành viên",
|
||||||
"label.min": "Tối thiểu",
|
"label.min": "Tối thiểu",
|
||||||
"label.mobile": "Di động",
|
"label.mobile": "Di động",
|
||||||
"label.model": "Mô hình",
|
|
||||||
"label.more": "Thêm",
|
"label.more": "Thêm",
|
||||||
"label.my-account": "Tài khoản của tôi",
|
"label.my-account": "Tài khoản của tôi",
|
||||||
"label.my-websites": "Website của tôi",
|
"label.my-websites": "Các website của tôi",
|
||||||
"label.name": "Tên",
|
"label.name": "Tên",
|
||||||
"label.new-password": "Mật khẩu mới",
|
"label.new-password": "Mật khẩu mới",
|
||||||
"label.none": "Không có",
|
"label.none": "Không",
|
||||||
"label.number-of-records": "{x} {x, plural, one {record} other {records}}",
|
"label.number-of-records": "{x} {x, plural, one {bản ghi} other {bản ghi}}",
|
||||||
"label.ok": "OK",
|
"label.ok": "OK",
|
||||||
"label.online": "Online",
|
"label.os": "Hệ điều hành",
|
||||||
"label.organic-search": "Tìm kiếm tự nhiên",
|
"label.overview": "Tổng quan",
|
||||||
"label.organic-shopping": "Mua sắm tự nhiên",
|
|
||||||
"label.organic-social": "Mạng xã hội tự nhiên",
|
|
||||||
"label.organic-video": "Video tự nhiên",
|
|
||||||
"label.os": "OS",
|
|
||||||
"label.other": "Khác",
|
|
||||||
"label.overview": "Overview",
|
|
||||||
"label.owner": "Chủ sở hữu",
|
"label.owner": "Chủ sở hữu",
|
||||||
"label.page": "Trang",
|
"label.page-of": "Trang {current} trên {total}",
|
||||||
"label.page-of": "Page {current} of {total}",
|
"label.page-views": "Lượt xem trang",
|
||||||
"label.page-views": "Lượt xem",
|
"label.pageTitle": "Tiêu đề trang",
|
||||||
"label.pageTitle": "Page title",
|
"label.pages": "Các trang",
|
||||||
"label.pages": "Trang",
|
|
||||||
"label.paid-ads": "Quảng cáo trả phí",
|
|
||||||
"label.paid-search": "Tìm kiếm trả phí",
|
|
||||||
"label.paid-shopping": "Mua sắm trả phí",
|
|
||||||
"label.paid-social": "Mạng xã hội trả phí",
|
|
||||||
"label.paid-video": "Video trả phí",
|
|
||||||
"label.password": "Mật khẩu",
|
"label.password": "Mật khẩu",
|
||||||
"label.path": "Đường dẫn",
|
"label.path": "Đường dẫn",
|
||||||
"label.paths": "Các đường dẫn",
|
"label.paths": "Các đường dẫn",
|
||||||
"label.pixels": "Pixel",
|
"label.powered-by": "Được cung cấp bởi {name}",
|
||||||
"label.powered-by": "Bản quyền thuộc về {name}",
|
"label.previous": "Trước",
|
||||||
"label.previous": "Trước đó",
|
"label.previous-period": "Kỳ trước",
|
||||||
"label.previous-period": "Giai đoạn trước",
|
|
||||||
"label.previous-year": "Năm trước",
|
"label.previous-year": "Năm trước",
|
||||||
"label.profile": "Hồ sơ",
|
"label.profile": "Hồ sơ",
|
||||||
"label.properties": "Thuộc tính",
|
"label.properties": "Thuộc tính",
|
||||||
"label.property": "Thuộc tính",
|
"label.property": "Thuộc tính",
|
||||||
"label.queries": "Queries",
|
"label.queries": "Truy vấn",
|
||||||
"label.query": "Query",
|
"label.query": "Truy vấn",
|
||||||
"label.query-parameters": "Query parameters",
|
"label.query-parameters": "Tham số truy vấn",
|
||||||
"label.realtime": "Thời gian thực",
|
"label.realtime": "Thời gian thực",
|
||||||
"label.referral": "Giới thiệu",
|
"label.referrer": "Nguồn giới thiệu",
|
||||||
"label.referrer": "Referrer",
|
"label.referrers": "Các nguồn giới thiệu",
|
||||||
"label.referrers": "Liên kết giới thiệu",
|
|
||||||
"label.refresh": "Làm mới",
|
"label.refresh": "Làm mới",
|
||||||
"label.regenerate": "Regenerate",
|
"label.regenerate": "Tạo lại",
|
||||||
"label.region": "Region",
|
"label.region": "Vùng",
|
||||||
"label.regions": "Regions",
|
"label.regions": "Các vùng",
|
||||||
"label.remaining": "Còn lại",
|
"label.remove": "Xóa",
|
||||||
"label.remove": "Remove",
|
"label.remove-member": "Xóa thành viên",
|
||||||
"label.remove-member": "Remove member",
|
"label.reports": "Báo cáo",
|
||||||
"label.reports": "Reports",
|
|
||||||
"label.required": "Yêu cầu",
|
"label.required": "Yêu cầu",
|
||||||
"label.reset": "Tái thiết lập",
|
"label.reset": "Đặt lại",
|
||||||
"label.reset-website": "Tái thiết lập thống kê",
|
"label.reset-website": "Đặt lại thống kê website",
|
||||||
"label.retention": "Retention",
|
"label.retention": "Tỷ lệ giữ chân",
|
||||||
"label.retention-description": "Measure your website stickiness by tracking how often users return.",
|
"label.retention-description": "Đo lường mức độ gắn bó của website bằng cách theo dõi tần suất người dùng quay lại.",
|
||||||
"label.revenue": "Doanh thu",
|
"label.revenue": "Doanh thu",
|
||||||
"label.revenue-description": "Xem doanh thu của bạn theo thời gian.",
|
"label.revenue-description": "Xem xét doanh thu của bạn theo thời gian.",
|
||||||
"label.role": "Role",
|
"label.revenue-property": "Thuộc tính doanh thu",
|
||||||
"label.run-query": "Run query",
|
"label.role": "Vai trò",
|
||||||
|
"label.run-query": "Chạy truy vấn",
|
||||||
"label.save": "Lưu",
|
"label.save": "Lưu",
|
||||||
"label.screens": "Screens",
|
"label.screens": "Màn hình",
|
||||||
"label.search": "Search",
|
"label.search": "Tìm kiếm",
|
||||||
"label.select": "Select",
|
"label.select": "Chọn",
|
||||||
"label.select-date": "Select date",
|
"label.select-date": "Chọn ngày",
|
||||||
"label.select-filter": "Chọn bộ lọc",
|
"label.select-role": "Chọn vai trò",
|
||||||
"label.select-role": "Select role",
|
"label.select-website": "Chọn website",
|
||||||
"label.select-website": "Select website",
|
|
||||||
"label.session": "Phiên",
|
"label.session": "Phiên",
|
||||||
"label.session-data": "Dữ liệu phiên",
|
"label.sessions": "Các phiên",
|
||||||
"label.sessions": "Sessions",
|
|
||||||
"label.settings": "Cài đặt",
|
"label.settings": "Cài đặt",
|
||||||
"label.share": "Chia sẻ",
|
|
||||||
"label.share-url": "Chia sẻ URL",
|
"label.share-url": "Chia sẻ URL",
|
||||||
"label.single-day": "Trong ngày",
|
"label.single-day": "Một ngày",
|
||||||
"label.sms": "SMS",
|
"label.start-step": "Bước bắt đầu",
|
||||||
"label.sources": "Nguồn",
|
"label.steps": "Các bước",
|
||||||
"label.start-step": "Start Step",
|
"label.sum": "Tổng",
|
||||||
"label.steps": "Steps",
|
|
||||||
"label.sum": "Sum",
|
|
||||||
"label.tablet": "Máy tính bảng",
|
"label.tablet": "Máy tính bảng",
|
||||||
"label.tag": "Thẻ",
|
|
||||||
"label.tags": "Các thẻ",
|
|
||||||
"label.team": "Nhóm",
|
"label.team": "Nhóm",
|
||||||
"label.team-id": "ID nhóm",
|
"label.team-id": "ID nhóm",
|
||||||
"label.team-manager": "Quản lý nhóm",
|
"label.team-manager": "Quản lý nhóm",
|
||||||
"label.team-member": "Thành viên nhóm",
|
"label.team-member": "Thành viên nhóm",
|
||||||
"label.team-name": "Tên nhóm",
|
"label.team-name": "Tên nhóm",
|
||||||
"label.team-owner": "Chủ nhóm",
|
"label.team-owner": "Chủ sở hữu nhóm",
|
||||||
"label.team-settings": "Cài đặt nhóm",
|
"label.team-view-only": "Chỉ xem nhóm",
|
||||||
"label.team-view-only": "Team view only",
|
"label.team-websites": "Các website của nhóm",
|
||||||
"label.team-websites": "Team websites",
|
"label.teams": "Các nhóm",
|
||||||
"label.teams": "Teams",
|
"label.theme": "Chủ đề",
|
||||||
"label.terms": "Điều khoản",
|
|
||||||
"label.theme": "Giao diện",
|
|
||||||
"label.this-month": "Tháng này",
|
"label.this-month": "Tháng này",
|
||||||
"label.this-week": "Tuần này",
|
"label.this-week": "Tuần này",
|
||||||
"label.this-year": "Năm nay",
|
"label.this-year": "Năm nay",
|
||||||
"label.timezone": "Múi giờ",
|
"label.timezone": "Múi giờ",
|
||||||
"label.title": "Title",
|
"label.title": "Tiêu đề",
|
||||||
"label.today": "Hôm nay",
|
"label.today": "Hôm nay",
|
||||||
"label.toggle-charts": "Bật/tắt biểu đồ",
|
"label.toggle-charts": "Bật/tắt biểu đồ",
|
||||||
"label.total": "Total",
|
"label.total": "Tổng",
|
||||||
"label.total-records": "Total records",
|
"label.total-records": "Tổng số bản ghi",
|
||||||
"label.tracking-code": "Mã theo dõi",
|
"label.tracking-code": "Mã theo dõi",
|
||||||
"label.transactions": "Transactions",
|
"label.transactions": "Giao dịch",
|
||||||
"label.transfer": "Transfer",
|
"label.transfer": "Chuyển giao",
|
||||||
"label.transfer-website": "Transfer website",
|
"label.transfer-website": "Chuyển giao website",
|
||||||
"label.true": "True",
|
"label.true": "Đúng",
|
||||||
"label.type": "Type",
|
"label.type": "Loại",
|
||||||
"label.unique": "Unique",
|
"label.unique": "Duy nhất",
|
||||||
"label.unique-visitors": "Khách truy cập một lần",
|
"label.unique-visitors": "Khách truy cập duy nhất",
|
||||||
"label.uniqueCustomers": "Unique Customers",
|
"label.uniqueCustomers": "Khách hàng duy nhất",
|
||||||
"label.unknown": "Không rõ",
|
"label.unknown": "Không rõ",
|
||||||
"label.untitled": "Untitled",
|
"label.untitled": "Không có tiêu đề",
|
||||||
"label.update": "Update",
|
"label.update": "Cập nhật",
|
||||||
"label.user": "User",
|
"label.url": "URL",
|
||||||
|
"label.urls": "Các URL",
|
||||||
|
"label.user": "Người dùng",
|
||||||
|
"label.user-property": "Thuộc tính người dùng",
|
||||||
"label.username": "Tên đăng nhập",
|
"label.username": "Tên đăng nhập",
|
||||||
"label.users": "Users",
|
"label.users": "Người dùng",
|
||||||
"label.utm": "UTM",
|
"label.utm": "UTM",
|
||||||
"label.utm-description": "Track your campaigns through UTM parameters.",
|
"label.utm-description": "Theo dõi các chiến dịch của bạn thông qua các tham số UTM.",
|
||||||
"label.value": "Value",
|
"label.value": "Giá trị",
|
||||||
"label.view": "View",
|
"label.view": "Xem",
|
||||||
"label.view-details": "Xem chi tiết",
|
"label.view-details": "Xem chi tiết",
|
||||||
"label.view-only": "View only",
|
"label.view-only": "Chỉ xem",
|
||||||
"label.views": "Xem",
|
"label.views": "Lượt xem",
|
||||||
"label.views-per-visit": "Views per visit",
|
"label.views-per-visit": "Lượt xem trên mỗi lượt truy cập",
|
||||||
"label.visit-duration": "Thời gian truy cập trung bình",
|
"label.visit-duration": "Thời lượng truy cập",
|
||||||
"label.visitors": "Khách",
|
"label.visitors": "Khách truy cập",
|
||||||
"label.visits": "Visits",
|
"label.visits": "Lượt truy cập",
|
||||||
"label.website": "Website",
|
"label.website": "Website",
|
||||||
"label.website-id": "Website ID",
|
"label.website-id": "ID website",
|
||||||
"label.websites": "Websites",
|
"label.websites": "Các website",
|
||||||
"label.window": "Window",
|
"label.window": "Cửa sổ",
|
||||||
"label.yesterday": "Yesterday",
|
"label.yesterday": "Hôm qua",
|
||||||
"message.action-confirmation": "Type {confirmation} in the box below to confirm.",
|
"message.action-confirmation": "Nhập {confirmation} vào ô bên dưới để xác nhận.",
|
||||||
"message.active-users": "{x} hiện tại {x, plural, one {một} other {trên}}",
|
"message.active-users": "{x} {x, plural, one {người dùng} other {người dùng}} đang hoạt động",
|
||||||
"message.bad-request": "Bad request",
|
"message.collected-data": "Dữ liệu đã thu thập",
|
||||||
"message.collected-data": "Collected data",
|
"message.confirm-delete": "Bạn có chắc chắn muốn xóa {target}?",
|
||||||
"message.confirm-delete": "Bạn có chắc chắn muốn xoá {target}?",
|
"message.confirm-leave": "Bạn có chắc chắn muốn rời {target}?",
|
||||||
"message.confirm-leave": "Are you sure you want to leave {target}?",
|
"message.confirm-remove": "Bạn có chắc chắn muốn xóa {target}?",
|
||||||
"message.confirm-remove": "Are you sure you want to remove {target}?",
|
"message.confirm-reset": "Bạn có chắc chắn muốn đặt lại thống kê {target}?",
|
||||||
"message.confirm-reset": "Bạn có chắc chắn muốn tái thiết lập thống kê {target}?",
|
"message.delete-team-warning": "Việc xóa một nhóm cũng sẽ xóa tất cả các website của nhóm.",
|
||||||
"message.delete-team-warning": "Deleting a team will also delete all team websites.",
|
"message.delete-website-warning": "Tất cả dữ liệu liên quan cũng sẽ bị xóa.",
|
||||||
"message.delete-website-warning": "Tất cả các dữ liệu liên quan cũng sẽ bị xoá.",
|
|
||||||
"message.error": "Đã xảy ra lỗi.",
|
"message.error": "Đã xảy ra lỗi.",
|
||||||
"message.event-log": "{event} on {url}",
|
"message.event-log": "{event} trên {url}",
|
||||||
"message.forbidden": "Forbidden",
|
"message.go-to-settings": "Chuyển đến cài đặt",
|
||||||
"message.go-to-settings": "Chuyển tới cài đặt",
|
|
||||||
"message.incorrect-username-password": "Sai tên đăng nhập/mật khẩu.",
|
"message.incorrect-username-password": "Sai tên đăng nhập/mật khẩu.",
|
||||||
"message.invalid-domain": "Tên miền không hợp lệ",
|
"message.invalid-domain": "Tên miền không hợp lệ",
|
||||||
"message.min-password-length": "Minimum length of {n} characters",
|
"message.min-password-length": "Độ dài tối thiểu {n} ký tự",
|
||||||
"message.new-version-available": "A new version of Umami {version} is available!",
|
"message.new-version-available": "Có phiên bản mới của Umami {version}!",
|
||||||
"message.no-data-available": "Không có dữ liệu.",
|
"message.no-data-available": "Không có dữ liệu.",
|
||||||
"message.no-event-data": "No event data is available.",
|
"message.no-event-data": "Không có dữ liệu sự kiện.",
|
||||||
"message.no-match-password": "Mật khẩu không đồng nhất",
|
"message.no-match-password": "Mật khẩu không khớp",
|
||||||
"message.no-results-found": "No results were found.",
|
"message.no-results-found": "Không tìm thấy kết quả nào.",
|
||||||
"message.no-team-websites": "This team does not have any websites.",
|
"message.no-team-websites": "Nhóm này không có bất kỳ website nào.",
|
||||||
"message.no-teams": "You have not created any teams.",
|
"message.no-teams": "Bạn chưa tạo nhóm nào.",
|
||||||
"message.no-users": "There are no users.",
|
"message.no-users": "Không có người dùng nào.",
|
||||||
"message.no-websites-configured": "Bạn chưa có bất cứ website nào.",
|
"message.no-websites-configured": "Bạn chưa cấu hình bất kỳ website nào.",
|
||||||
"message.not-found": "Not found",
|
"message.page-not-found": "Không tìm thấy trang.",
|
||||||
"message.nothing-selected": "Nothing selected.",
|
"message.reset-website": "Để đặt lại website này, nhập {confirmation} vào ô bên dưới để xác nhận.",
|
||||||
"message.page-not-found": "Trang không tìm thấy.",
|
"message.reset-website-warning": "Tất cả số liệu thống kê của website này sẽ bị xóa, nhưng mã theo dõi sẽ vẫn giữ nguyên.",
|
||||||
"message.reset-website": "To reset this website, type {confirmation} in the box below to confirm.",
|
|
||||||
"message.reset-website-warning": "Tất cả số liệu thống kê của website này sẽ bị xoá, nhưng mã theo dõi sẽ vẫn giữ nguyên.",
|
|
||||||
"message.saved": "Đã lưu thành công.",
|
"message.saved": "Đã lưu thành công.",
|
||||||
"message.sever-error": "Server error",
|
|
||||||
"message.share-url": "Đây là đường dẫn URL cho {target}.",
|
"message.share-url": "Đây là đường dẫn URL cho {target}.",
|
||||||
"message.team-already-member": "You are already a member of the team.",
|
"message.team-already-member": "Bạn đã là thành viên của nhóm.",
|
||||||
"message.team-not-found": "Team not found.",
|
"message.team-not-found": "Không tìm thấy nhóm.",
|
||||||
"message.team-websites-info": "Websites can be viewed by anyone on the team.",
|
"message.team-websites-info": "Bất kỳ ai trong nhóm đều có thể xem các website.",
|
||||||
"message.tracking-code": "Mã theo dõi",
|
"message.tracking-code": "Mã theo dõi",
|
||||||
"message.transfer-team-website-to-user": "Transfer this website to your account?",
|
"message.transfer-team-website-to-user": "Chuyển website này sang tài khoản của bạn?",
|
||||||
"message.transfer-user-website-to-team": "Select the team to transfer this website to.",
|
"message.transfer-user-website-to-team": "Chọn nhóm để chuyển website này đến.",
|
||||||
"message.transfer-website": "Transfer website ownership to your account or another team.",
|
"message.transfer-website": "Chuyển quyền sở hữu website sang tài khoản của bạn hoặc một nhóm khác.",
|
||||||
"message.triggered-event": "Triggered event",
|
"message.triggered-event": "Sự kiện được kích hoạt",
|
||||||
"message.unauthorized": "Unauthorized",
|
"message.user-deleted": "Người dùng đã bị xóa.",
|
||||||
"message.user-deleted": "User deleted.",
|
"message.viewed-page": "Đã xem trang",
|
||||||
"message.viewed-page": "Viewed page",
|
"message.visitor-log": "Khách từ {country} đang sử dụng {browser} trên {os} {device}",
|
||||||
"message.visitor-log": "Khách từ {country} đang dùng {browser} trên {os} {device}"
|
"message.visitors-dropped-off": "Khách truy cập đã rời đi"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,21 @@ function mapFilter(column: string, operator: string, name: string, type: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mapCohortFilter(column: string, operator: string, value: string) {
|
||||||
|
switch (operator) {
|
||||||
|
case OPERATORS.equals:
|
||||||
|
return `${column} = '${value}'`;
|
||||||
|
case OPERATORS.notEquals:
|
||||||
|
return `${column} != '${value}'`;
|
||||||
|
case OPERATORS.contains:
|
||||||
|
return `positionCaseInsensitive(${column}, '${value}') > 0`;
|
||||||
|
case OPERATORS.doesNotContain:
|
||||||
|
return `positionCaseInsensitive(${column}, '${value}') = 0`;
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function getFilterQuery(filters: Record<string, any>, options: QueryOptions = {}) {
|
function getFilterQuery(filters: Record<string, any>, options: QueryOptions = {}) {
|
||||||
const query = filtersToArray(filters, options).reduce((arr, { name, column, operator }) => {
|
const query = filtersToArray(filters, options).reduce((arr, { name, column, operator }) => {
|
||||||
if (column) {
|
if (column) {
|
||||||
|
|
@ -103,6 +118,42 @@ function getFilterQuery(filters: Record<string, any>, options: QueryOptions = {}
|
||||||
return query.join('\n');
|
return query.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getCohortQuery(websiteId: string, filters: QueryFilters = {}, options: QueryOptions = {}) {
|
||||||
|
const query = filtersToArray(filters, options).reduce(
|
||||||
|
(arr, { name, column, operator, value }) => {
|
||||||
|
if (column) {
|
||||||
|
arr.push(
|
||||||
|
`${arr.length === 0 ? 'where' : 'and'} ${mapCohortFilter(column, operator, value)}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (name === 'referrer') {
|
||||||
|
arr.push(`and referrer_domain != hostname`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return arr;
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (query.length > 0) {
|
||||||
|
// add website and date range filters
|
||||||
|
query.push(`and website_id = '${websiteId}'`);
|
||||||
|
query.push(
|
||||||
|
`and created_at between parseDateTimeBestEffort('${filters.startDate}') and parseDateTimeBestEffort('${filters.endDate}')`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return `join
|
||||||
|
(select distinct session_id
|
||||||
|
from website_event
|
||||||
|
${query.join('\n')}) cohort
|
||||||
|
on cohort.session_id = website_event.session_id
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
function getDateQuery(filters: Record<string, any>) {
|
function getDateQuery(filters: Record<string, any>) {
|
||||||
const { startDate, endDate, timezone } = filters;
|
const { startDate, endDate, timezone } = filters;
|
||||||
|
|
||||||
|
|
@ -141,6 +192,7 @@ function parseFilters(filters: Record<string, any>, options?: QueryOptions) {
|
||||||
filterQuery: getFilterQuery(filters, options),
|
filterQuery: getFilterQuery(filters, options),
|
||||||
dateQuery: getDateQuery(filters),
|
dateQuery: getDateQuery(filters),
|
||||||
queryParams: getQueryParams(filters),
|
queryParams: getQueryParams(filters),
|
||||||
|
cohortQuery: getCohortQuery(filters?.cohort),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,11 @@ export const SESSION_COLUMNS = [
|
||||||
'hostname',
|
'hostname',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const FILTER_GROUPS = {
|
||||||
|
segment: 'segment',
|
||||||
|
cohort: 'cohort',
|
||||||
|
};
|
||||||
|
|
||||||
export const FILTER_COLUMNS = {
|
export const FILTER_COLUMNS = {
|
||||||
path: 'url_path',
|
path: 'url_path',
|
||||||
entry: 'url_path',
|
entry: 'url_path',
|
||||||
|
|
|
||||||
|
|
@ -104,6 +104,24 @@ function mapFilter(column: string, operator: string, name: string, type: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mapCohortFilter(column: string, operator: string, value: string) {
|
||||||
|
const db = getDatabaseType();
|
||||||
|
const like = db === POSTGRESQL ? 'ilike' : 'like';
|
||||||
|
|
||||||
|
switch (operator) {
|
||||||
|
case OPERATORS.equals:
|
||||||
|
return `${column} = '${value}'`;
|
||||||
|
case OPERATORS.notEquals:
|
||||||
|
return `${column} != '${value}'`;
|
||||||
|
case OPERATORS.contains:
|
||||||
|
return `${column} ${like} '${value}'`;
|
||||||
|
case OPERATORS.doesNotContain:
|
||||||
|
return `${column} not ${like} '${value}'`;
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function getFilterQuery(filters: Record<string, any>, options: QueryOptions = {}): string {
|
function getFilterQuery(filters: Record<string, any>, options: QueryOptions = {}): string {
|
||||||
const query = filtersToArray(filters, options).reduce(
|
const query = filtersToArray(filters, options).reduce(
|
||||||
(arr, { name, column, operator, prefix = '' }) => {
|
(arr, { name, column, operator, prefix = '' }) => {
|
||||||
|
|
@ -125,6 +143,43 @@ function getFilterQuery(filters: Record<string, any>, options: QueryOptions = {}
|
||||||
return query.join('\n');
|
return query.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getCohortQuery(websiteId: string, filters: QueryFilters = {}, options: QueryOptions = {}) {
|
||||||
|
const query = filtersToArray(filters, options).reduce(
|
||||||
|
(arr, { name, column, operator, value }) => {
|
||||||
|
if (column) {
|
||||||
|
arr.push(
|
||||||
|
`${arr.length === 0 ? 'where' : 'and'} ${mapCohortFilter(column, operator, value)}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (name === 'referrer') {
|
||||||
|
arr.push(`and referrer_domain != hostname`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return arr;
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (query.length > 0) {
|
||||||
|
// add website and date range filters
|
||||||
|
query.push(`and website_event.website_id = '${websiteId}'`);
|
||||||
|
query.push(
|
||||||
|
`and website_event.created_at between '${filters.startDate}'::timestamptz and '${filters.endDate}'::timestamptz`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return `join
|
||||||
|
(select distinct website_event.session_id
|
||||||
|
from website_event
|
||||||
|
join session on session.session_id = website_event.session_id
|
||||||
|
${query.join('\n')}) cohort
|
||||||
|
on cohort.session_id = website_event.session_id
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
function getDateQuery(filters: Record<string, any>) {
|
function getDateQuery(filters: Record<string, any>) {
|
||||||
const { startDate, endDate } = filters;
|
const { startDate, endDate } = filters;
|
||||||
|
|
||||||
|
|
@ -165,6 +220,7 @@ function parseFilters(filters: Record<string, any>, options?: QueryOptions) {
|
||||||
dateQuery: getDateQuery(filters),
|
dateQuery: getDateQuery(filters),
|
||||||
filterQuery: getFilterQuery(filters, options),
|
filterQuery: getFilterQuery(filters, options),
|
||||||
queryParams: getQueryParams(filters),
|
queryParams: getQueryParams(filters),
|
||||||
|
cohortQuery: getCohortQuery(filters?.cohort),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
import { z } from 'zod/v4';
|
import { z } from 'zod/v4';
|
||||||
import { FILTER_COLUMNS, DEFAULT_PAGE_SIZE } from '@/lib/constants';
|
import { FILTER_COLUMNS, DEFAULT_PAGE_SIZE, FILTER_GROUPS } from '@/lib/constants';
|
||||||
import { badRequest, unauthorized } from '@/lib/response';
|
import { badRequest, unauthorized } from '@/lib/response';
|
||||||
import { getAllowedUnits, getMinimumUnit, maxDate } from '@/lib/date';
|
import { getAllowedUnits, getMinimumUnit, maxDate } from '@/lib/date';
|
||||||
import { checkAuth } from '@/lib/auth';
|
import { checkAuth } from '@/lib/auth';
|
||||||
import { fetchWebsite } from '@/lib/load';
|
import { fetchWebsite } from '@/lib/load';
|
||||||
import { QueryFilters } from '@/lib/types';
|
import { QueryFilters } from '@/lib/types';
|
||||||
|
import { getWebsiteSegment } from '@/queries';
|
||||||
|
|
||||||
export async function parseRequest(
|
export async function parseRequest(
|
||||||
request: Request,
|
request: Request,
|
||||||
|
|
@ -65,16 +66,30 @@ export function getRequestDateRange(query: Record<string, string>) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getRequestFilters(query: Record<string, any>) {
|
export async function getRequestFilters(query: Record<string, any>, websiteId?: string) {
|
||||||
return Object.keys(FILTER_COLUMNS).reduce((obj, key) => {
|
const result: Record<string, any> = {};
|
||||||
|
|
||||||
|
for (const key of Object.keys(FILTER_COLUMNS)) {
|
||||||
const value = query[key];
|
const value = query[key];
|
||||||
|
|
||||||
if (value !== undefined) {
|
if (value !== undefined) {
|
||||||
obj[key] = value;
|
result[key] = value;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return obj;
|
for (const key of Object.keys(FILTER_GROUPS)) {
|
||||||
}, {});
|
const value = query[key];
|
||||||
|
if (value !== undefined) {
|
||||||
|
const segment = await getWebsiteSegment(websiteId, key, value);
|
||||||
|
if (key === 'segment') {
|
||||||
|
// merge filters into result
|
||||||
|
Object.assign(result, segment.parameters);
|
||||||
|
} else {
|
||||||
|
result[key] = segment.parameters;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function setWebsiteDate(websiteId: string, data: Record<string, any>) {
|
export async function setWebsiteDate(websiteId: string, data: Record<string, any>) {
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,8 @@ export const filterParams = {
|
||||||
hostname: z.string().optional(),
|
hostname: z.string().optional(),
|
||||||
language: z.string().optional(),
|
language: z.string().optional(),
|
||||||
event: z.string().optional(),
|
event: z.string().optional(),
|
||||||
|
segment: z.string().optional(),
|
||||||
|
cohort: z.string().optional(),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const searchParams = {
|
export const searchParams = {
|
||||||
|
|
@ -272,3 +274,5 @@ export const reportResultSchema = z.intersection(
|
||||||
}),
|
}),
|
||||||
reportTypeSchema,
|
reportTypeSchema,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const segmentTypeParam = z.enum(['segment', 'cohort']);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
export * from '@/queries/prisma/report';
|
export * from '@/queries/prisma/report';
|
||||||
|
export * from '@/queries/prisma/segment';
|
||||||
export * from '@/queries/prisma/team';
|
export * from '@/queries/prisma/team';
|
||||||
export * from '@/queries/prisma/teamUser';
|
export * from '@/queries/prisma/teamUser';
|
||||||
export * from '@/queries/prisma/user';
|
export * from '@/queries/prisma/user';
|
||||||
|
|
|
||||||
45
src/queries/prisma/segment.ts
Normal file
45
src/queries/prisma/segment.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
import prisma from '@/lib/prisma';
|
||||||
|
import { Prisma, Segment } from '@prisma/client';
|
||||||
|
|
||||||
|
async function findSegment(criteria: Prisma.SegmentFindUniqueArgs): Promise<Segment> {
|
||||||
|
return prisma.client.Segment.findUnique(criteria);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getSegment(segmentId: string): Promise<Segment> {
|
||||||
|
return findSegment({
|
||||||
|
where: {
|
||||||
|
id: segmentId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getWebsiteSegment(
|
||||||
|
websiteId: string,
|
||||||
|
type: string,
|
||||||
|
name: string,
|
||||||
|
): Promise<Segment> {
|
||||||
|
return prisma.client.segment.findFirst({
|
||||||
|
where: { websiteId, type, name },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getWebsiteSegments(websiteId: string, type: string): Promise<Segment[]> {
|
||||||
|
return prisma.client.Segment.findMany({
|
||||||
|
where: { websiteId, type },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createSegment(data: Prisma.SegmentUncheckedCreateInput): Promise<Segment> {
|
||||||
|
return prisma.client.Segment.create({ data });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateSegment(
|
||||||
|
SegmentId: string,
|
||||||
|
data: Prisma.SegmentUpdateInput,
|
||||||
|
): Promise<Segment> {
|
||||||
|
return prisma.client.Segment.update({ where: { id: SegmentId }, data });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteSegment(SegmentId: string): Promise<Segment> {
|
||||||
|
return prisma.client.Segment.delete({ where: { id: SegmentId } });
|
||||||
|
}
|
||||||
|
|
@ -12,7 +12,7 @@ export async function getEventDataFields(...args: [websiteId: string, filters: Q
|
||||||
|
|
||||||
async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||||
const { rawQuery, parseFilters, getDateSQL } = prisma;
|
const { rawQuery, parseFilters, getDateSQL } = prisma;
|
||||||
const { filterQuery, queryParams } = parseFilters(filters);
|
const { filterQuery, cohortQuery, queryParams } = parseFilters(filters);
|
||||||
|
|
||||||
return rawQuery(
|
return rawQuery(
|
||||||
`
|
`
|
||||||
|
|
@ -27,6 +27,9 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||||
count(*) as "total"
|
count(*) as "total"
|
||||||
from event_data
|
from event_data
|
||||||
join website_event on website_event.event_id = event_data.website_event_id
|
join website_event on website_event.event_id = event_data.website_event_id
|
||||||
|
and website_event.website_id = {{websiteId::uuid}}
|
||||||
|
and website_event.created_at between {{startDate}} and {{endDate}}
|
||||||
|
${cohortQuery}
|
||||||
where event_data.website_id = {{websiteId::uuid}}
|
where event_data.website_id = {{websiteId::uuid}}
|
||||||
and event_data.created_at between {{startDate}} and {{endDate}}
|
and event_data.created_at between {{startDate}} and {{endDate}}
|
||||||
${filterQuery}
|
${filterQuery}
|
||||||
|
|
@ -43,7 +46,7 @@ async function clickhouseQuery(
|
||||||
filters: QueryFilters,
|
filters: QueryFilters,
|
||||||
): Promise<{ propertyName: string; dataType: number; propertyValue: string; total: number }[]> {
|
): Promise<{ propertyName: string; dataType: number; propertyValue: string; total: number }[]> {
|
||||||
const { rawQuery, parseFilters } = clickhouse;
|
const { rawQuery, parseFilters } = clickhouse;
|
||||||
const { filterQuery, queryParams } = parseFilters(filters);
|
const { filterQuery, cohortQuery, queryParams } = parseFilters(filters);
|
||||||
|
|
||||||
return rawQuery(
|
return rawQuery(
|
||||||
`
|
`
|
||||||
|
|
@ -54,7 +57,8 @@ async function clickhouseQuery(
|
||||||
data_type = 4, toString(date_trunc('hour', date_value)),
|
data_type = 4, toString(date_trunc('hour', date_value)),
|
||||||
string_value) as "value",
|
string_value) as "value",
|
||||||
count(*) as "total"
|
count(*) as "total"
|
||||||
from event_data
|
from event_data website_event
|
||||||
|
${cohortQuery}
|
||||||
where website_id = {websiteId:UUID}
|
where website_id = {websiteId:UUID}
|
||||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||||
${filterQuery}
|
${filterQuery}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ async function relationalQuery(
|
||||||
filters: QueryFilters & { propertyName?: string },
|
filters: QueryFilters & { propertyName?: string },
|
||||||
) {
|
) {
|
||||||
const { rawQuery, parseFilters } = prisma;
|
const { rawQuery, parseFilters } = prisma;
|
||||||
const { filterQuery, queryParams } = parseFilters(filters, {
|
const { filterQuery, cohortQuery, queryParams } = parseFilters(filters, {
|
||||||
columns: { propertyName: 'data_key' },
|
columns: { propertyName: 'data_key' },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -29,6 +29,9 @@ async function relationalQuery(
|
||||||
count(*) as "total"
|
count(*) as "total"
|
||||||
from event_data
|
from event_data
|
||||||
join website_event on website_event.event_id = event_data.website_event_id
|
join website_event on website_event.event_id = event_data.website_event_id
|
||||||
|
and website_event.website_id = {{websiteId::uuid}}
|
||||||
|
and website_event.created_at between {{startDate}} and {{endDate}}
|
||||||
|
${cohortQuery}
|
||||||
where event_data.website_id = {{websiteId::uuid}}
|
where event_data.website_id = {{websiteId::uuid}}
|
||||||
and event_data.created_at between {{startDate}} and {{endDate}}
|
and event_data.created_at between {{startDate}} and {{endDate}}
|
||||||
${filterQuery}
|
${filterQuery}
|
||||||
|
|
@ -45,7 +48,7 @@ async function clickhouseQuery(
|
||||||
filters: QueryFilters & { propertyName?: string },
|
filters: QueryFilters & { propertyName?: string },
|
||||||
): Promise<{ eventName: string; propertyName: string; total: number }[]> {
|
): Promise<{ eventName: string; propertyName: string; total: number }[]> {
|
||||||
const { rawQuery, parseFilters } = clickhouse;
|
const { rawQuery, parseFilters } = clickhouse;
|
||||||
const { filterQuery, queryParams } = parseFilters(filters, {
|
const { filterQuery, cohortQuery, queryParams } = parseFilters(filters, {
|
||||||
columns: { propertyName: 'data_key' },
|
columns: { propertyName: 'data_key' },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -55,7 +58,8 @@ async function clickhouseQuery(
|
||||||
event_name as eventName,
|
event_name as eventName,
|
||||||
data_key as propertyName,
|
data_key as propertyName,
|
||||||
count(*) as total
|
count(*) as total
|
||||||
from event_data
|
from event_data website_event
|
||||||
|
${cohortQuery}
|
||||||
where website_id = {websiteId:UUID}
|
where website_id = {websiteId:UUID}
|
||||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||||
${filterQuery}
|
${filterQuery}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ export async function getEventDataStats(
|
||||||
|
|
||||||
async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||||
const { rawQuery, parseFilters } = prisma;
|
const { rawQuery, parseFilters } = prisma;
|
||||||
const { filterQuery, queryParams } = parseFilters({ ...filters, websiteId });
|
const { filterQuery, cohortQuery, queryParams } = parseFilters({ ...filters, websiteId });
|
||||||
|
|
||||||
return rawQuery(
|
return rawQuery(
|
||||||
`
|
`
|
||||||
|
|
@ -32,8 +32,12 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||||
data_key,
|
data_key,
|
||||||
count(*) as "total"
|
count(*) as "total"
|
||||||
from event_data
|
from event_data
|
||||||
where website_id = {{websiteId::uuid}}
|
join website_event on website_event.event_id = event_data.website_event_id
|
||||||
and created_at between {{startDate}} and {{endDate}}
|
and website_event.website_id = {{websiteId::uuid}}
|
||||||
|
and website_event.created_at between {{startDate}} and {{endDate}}
|
||||||
|
${cohortQuery}
|
||||||
|
where event_data.website_id = {{websiteId::uuid}}
|
||||||
|
and event_data.created_at between {{startDate}} and {{endDate}}
|
||||||
${filterQuery}
|
${filterQuery}
|
||||||
group by website_event_id, data_key
|
group by website_event_id, data_key
|
||||||
) as t
|
) as t
|
||||||
|
|
@ -47,7 +51,7 @@ async function clickhouseQuery(
|
||||||
filters: QueryFilters,
|
filters: QueryFilters,
|
||||||
): Promise<{ events: number; properties: number; records: number }[]> {
|
): Promise<{ events: number; properties: number; records: number }[]> {
|
||||||
const { rawQuery, parseFilters } = clickhouse;
|
const { rawQuery, parseFilters } = clickhouse;
|
||||||
const { filterQuery, queryParams } = parseFilters({ ...filters, websiteId });
|
const { filterQuery, cohortQuery, queryParams } = parseFilters({ ...filters, websiteId });
|
||||||
|
|
||||||
return rawQuery(
|
return rawQuery(
|
||||||
`
|
`
|
||||||
|
|
@ -60,7 +64,8 @@ async function clickhouseQuery(
|
||||||
event_id,
|
event_id,
|
||||||
data_key,
|
data_key,
|
||||||
count(*) as "total"
|
count(*) as "total"
|
||||||
from event_data
|
from event_data website_event
|
||||||
|
${cohortQuery}
|
||||||
where website_id = {websiteId:UUID}
|
where website_id = {websiteId:UUID}
|
||||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||||
${filterQuery}
|
${filterQuery}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import clickhouse from '@/lib/clickhouse';
|
||||||
import { CLICKHOUSE, PRISMA, runQuery } from '@/lib/db';
|
import { CLICKHOUSE, PRISMA, runQuery } from '@/lib/db';
|
||||||
import { QueryFilters } from '@/lib/types';
|
import { QueryFilters } from '@/lib/types';
|
||||||
|
|
||||||
export interface WebsiteEventData {
|
interface WebsiteEventData {
|
||||||
value: string;
|
value: string;
|
||||||
total: number;
|
total: number;
|
||||||
}
|
}
|
||||||
|
|
@ -25,7 +25,7 @@ async function relationalQuery(
|
||||||
filters: QueryFilters & { eventName?: string; propertyName?: string },
|
filters: QueryFilters & { eventName?: string; propertyName?: string },
|
||||||
) {
|
) {
|
||||||
const { rawQuery, parseFilters, getDateSQL } = prisma;
|
const { rawQuery, parseFilters, getDateSQL } = prisma;
|
||||||
const { filterQuery, queryParams } = parseFilters({ ...filters, websiteId });
|
const { filterQuery, cohortQuery, queryParams } = parseFilters({ ...filters, websiteId });
|
||||||
|
|
||||||
return rawQuery(
|
return rawQuery(
|
||||||
`
|
`
|
||||||
|
|
@ -38,6 +38,9 @@ async function relationalQuery(
|
||||||
count(*) as "total"
|
count(*) as "total"
|
||||||
from event_data
|
from event_data
|
||||||
join website_event on website_event.event_id = event_data.website_event_id
|
join website_event on website_event.event_id = event_data.website_event_id
|
||||||
|
and website_event.website_id = {{websiteId::uuid}}
|
||||||
|
and website_event.created_at between {{startDate}} and {{endDate}}
|
||||||
|
${cohortQuery}
|
||||||
where event_data.website_id = {{websiteId::uuid}}
|
where event_data.website_id = {{websiteId::uuid}}
|
||||||
and event_data.created_at between {{startDate}} and {{endDate}}
|
and event_data.created_at between {{startDate}} and {{endDate}}
|
||||||
and event_data.data_key = {{propertyName}}
|
and event_data.data_key = {{propertyName}}
|
||||||
|
|
@ -56,7 +59,7 @@ async function clickhouseQuery(
|
||||||
filters: QueryFilters & { eventName?: string; propertyName?: string },
|
filters: QueryFilters & { eventName?: string; propertyName?: string },
|
||||||
): Promise<{ value: string; total: number }[]> {
|
): Promise<{ value: string; total: number }[]> {
|
||||||
const { rawQuery, parseFilters } = clickhouse;
|
const { rawQuery, parseFilters } = clickhouse;
|
||||||
const { filterQuery, queryParams } = parseFilters({ ...filters, websiteId });
|
const { filterQuery, cohortQuery, queryParams } = parseFilters({ ...filters, websiteId });
|
||||||
|
|
||||||
return rawQuery(
|
return rawQuery(
|
||||||
`
|
`
|
||||||
|
|
@ -65,7 +68,8 @@ async function clickhouseQuery(
|
||||||
data_type = 4, toString(date_trunc('hour', date_value)),
|
data_type = 4, toString(date_trunc('hour', date_value)),
|
||||||
string_value) as "value",
|
string_value) as "value",
|
||||||
count(*) as "total"
|
count(*) as "total"
|
||||||
from event_data
|
from event_data website_event
|
||||||
|
${cohortQuery}
|
||||||
where website_id = {websiteId:UUID}
|
where website_id = {websiteId:UUID}
|
||||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||||
and data_key = {propertyName:String}
|
and data_key = {propertyName:String}
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,8 @@ export async function getEventMetrics(
|
||||||
async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||||
const { timezone = 'utc', unit = 'day' } = filters;
|
const { timezone = 'utc', unit = 'day' } = filters;
|
||||||
const { rawQuery, getDateSQL, parseFilters } = prisma;
|
const { rawQuery, getDateSQL, parseFilters } = prisma;
|
||||||
const { filterQuery, joinSessionQuery, queryParams } = parseFilters({
|
const { filterQuery, joinSessionQuery, cohortQuery, queryParams } = parseFilters({
|
||||||
...filters,
|
...filters,
|
||||||
websiteId,
|
|
||||||
eventType: EVENT_TYPE.customEvent,
|
eventType: EVENT_TYPE.customEvent,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -36,6 +35,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||||
count(*) y
|
count(*) y
|
||||||
from website_event
|
from website_event
|
||||||
${joinSessionQuery}
|
${joinSessionQuery}
|
||||||
|
${cohortQuery}
|
||||||
where website_event.website_id = {{websiteId::uuid}}
|
where website_event.website_id = {{websiteId::uuid}}
|
||||||
and website_event.created_at between {{startDate}} and {{endDate}}
|
and website_event.created_at between {{startDate}} and {{endDate}}
|
||||||
and event_type = {{eventType}}
|
and event_type = {{eventType}}
|
||||||
|
|
@ -53,7 +53,7 @@ async function clickhouseQuery(
|
||||||
): Promise<WebsiteEventMetricData[]> {
|
): Promise<WebsiteEventMetricData[]> {
|
||||||
const { timezone = 'UTC', unit = 'day' } = filters;
|
const { timezone = 'UTC', unit = 'day' } = filters;
|
||||||
const { rawQuery, getDateSQL, parseFilters } = clickhouse;
|
const { rawQuery, getDateSQL, parseFilters } = clickhouse;
|
||||||
const { filterQuery, queryParams } = parseFilters({
|
const { filterQuery, cohortQuery, queryParams } = parseFilters({
|
||||||
...filters,
|
...filters,
|
||||||
websiteId,
|
websiteId,
|
||||||
eventType: EVENT_TYPE.customEvent,
|
eventType: EVENT_TYPE.customEvent,
|
||||||
|
|
@ -61,13 +61,14 @@ async function clickhouseQuery(
|
||||||
|
|
||||||
let sql = '';
|
let sql = '';
|
||||||
|
|
||||||
if (filterQuery) {
|
if (filterQuery || cohortQuery) {
|
||||||
sql = `
|
sql = `
|
||||||
select
|
select
|
||||||
event_name x,
|
event_name x,
|
||||||
${getDateSQL('created_at', unit, timezone)} t,
|
${getDateSQL('created_at', unit, timezone)} t,
|
||||||
count(*) y
|
count(*) y
|
||||||
from website_event
|
from website_event
|
||||||
|
${cohortQuery}
|
||||||
where website_id = {websiteId:UUID}
|
where website_id = {websiteId:UUID}
|
||||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||||
and event_type = {eventType:UInt32}
|
and event_type = {eventType:UInt32}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ export function getWebsiteEvents(...args: [websiteId: string, filters: QueryFilt
|
||||||
async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||||
const { pagedRawQuery, parseFilters } = prisma;
|
const { pagedRawQuery, parseFilters } = prisma;
|
||||||
const { search } = filters;
|
const { search } = filters;
|
||||||
const { filterQuery, dateQuery, queryParams } = parseFilters({
|
const { filterQuery, dateQuery, cohortQuery, queryParams } = parseFilters({
|
||||||
...filters,
|
...filters,
|
||||||
websiteId,
|
websiteId,
|
||||||
});
|
});
|
||||||
|
|
@ -39,6 +39,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||||
event_type as "eventType",
|
event_type as "eventType",
|
||||||
event_name as "eventName"
|
event_name as "eventName"
|
||||||
from website_event
|
from website_event
|
||||||
|
${cohortQuery}
|
||||||
where website_id = {{websiteId::uuid}}
|
where website_id = {{websiteId::uuid}}
|
||||||
${dateQuery}
|
${dateQuery}
|
||||||
${filterQuery}
|
${filterQuery}
|
||||||
|
|
@ -52,7 +53,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||||
|
|
||||||
async function clickhouseQuery(websiteId: string, filters: QueryFilters) {
|
async function clickhouseQuery(websiteId: string, filters: QueryFilters) {
|
||||||
const { pagedRawQuery, parseFilters } = clickhouse;
|
const { pagedRawQuery, parseFilters } = clickhouse;
|
||||||
const { queryParams, dateQuery, filterQuery } = parseFilters({
|
const { queryParams, dateQuery, cohortQuery, filterQuery } = parseFilters({
|
||||||
...filters,
|
...filters,
|
||||||
websiteId,
|
websiteId,
|
||||||
});
|
});
|
||||||
|
|
@ -82,6 +83,7 @@ async function clickhouseQuery(websiteId: string, filters: QueryFilters) {
|
||||||
event_type as eventType,
|
event_type as eventType,
|
||||||
event_name as eventName
|
event_name as eventName
|
||||||
from website_event
|
from website_event
|
||||||
|
${cohortQuery}
|
||||||
where website_id = {websiteId:UUID}
|
where website_id = {websiteId:UUID}
|
||||||
${dateQuery}
|
${dateQuery}
|
||||||
${filterQuery}
|
${filterQuery}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ export async function getChannelMetrics(...args: [websiteId: string, filters?: Q
|
||||||
|
|
||||||
async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||||
const { rawQuery, parseFilters } = prisma;
|
const { rawQuery, parseFilters } = prisma;
|
||||||
const { queryParams, filterQuery, dateQuery } = parseFilters(filters);
|
const { queryParams, filterQuery, cohortQuery, dateQuery } = parseFilters(filters);
|
||||||
|
|
||||||
return rawQuery(
|
return rawQuery(
|
||||||
`
|
`
|
||||||
|
|
@ -21,6 +21,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||||
url_query as query,
|
url_query as query,
|
||||||
count(distinct session_id) as visitors
|
count(distinct session_id) as visitors
|
||||||
from website_event
|
from website_event
|
||||||
|
${cohortQuery}
|
||||||
where website_id = {{websiteId::uuid}}
|
where website_id = {{websiteId::uuid}}
|
||||||
${filterQuery}
|
${filterQuery}
|
||||||
${dateQuery}
|
${dateQuery}
|
||||||
|
|
@ -36,7 +37,7 @@ async function clickhouseQuery(
|
||||||
filters: QueryFilters,
|
filters: QueryFilters,
|
||||||
): Promise<{ x: string; y: number }[]> {
|
): Promise<{ x: string; y: number }[]> {
|
||||||
const { rawQuery, parseFilters } = clickhouse;
|
const { rawQuery, parseFilters } = clickhouse;
|
||||||
const { queryParams, filterQuery, dateQuery } = parseFilters(filters);
|
const { queryParams, filterQuery, cohortQuery, dateQuery } = parseFilters(filters);
|
||||||
|
|
||||||
const sql = `
|
const sql = `
|
||||||
select
|
select
|
||||||
|
|
@ -44,6 +45,7 @@ async function clickhouseQuery(
|
||||||
url_query as query,
|
url_query as query,
|
||||||
uniq(session_id) as visitors
|
uniq(session_id) as visitors
|
||||||
from website_event
|
from website_event
|
||||||
|
${cohortQuery}
|
||||||
where website_id = {websiteId:UUID}
|
where website_id = {websiteId:UUID}
|
||||||
${filterQuery}
|
${filterQuery}
|
||||||
${dateQuery}
|
${dateQuery}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ export async function getRealtimeActivity(...args: [websiteId: string, filters:
|
||||||
|
|
||||||
async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||||
const { rawQuery, parseFilters } = prisma;
|
const { rawQuery, parseFilters } = prisma;
|
||||||
const { queryParams, filterQuery, dateQuery } = parseFilters(filters);
|
const { queryParams, filterQuery, cohortQuery, dateQuery } = parseFilters(filters);
|
||||||
|
|
||||||
return rawQuery(
|
return rawQuery(
|
||||||
`
|
`
|
||||||
|
|
@ -27,6 +27,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||||
website_event.url_path as "urlPath",
|
website_event.url_path as "urlPath",
|
||||||
website_event.referrer_domain as "referrerDomain"
|
website_event.referrer_domain as "referrerDomain"
|
||||||
from website_event
|
from website_event
|
||||||
|
${cohortQuery}
|
||||||
inner join session
|
inner join session
|
||||||
on session.session_id = website_event.session_id
|
on session.session_id = website_event.session_id
|
||||||
where website_event.website_id = {{websiteId::uuid}}
|
where website_event.website_id = {{websiteId::uuid}}
|
||||||
|
|
@ -41,7 +42,10 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||||
|
|
||||||
async function clickhouseQuery(websiteId: string, filters: QueryFilters): Promise<{ x: number }> {
|
async function clickhouseQuery(websiteId: string, filters: QueryFilters): Promise<{ x: number }> {
|
||||||
const { rawQuery, parseFilters } = clickhouse;
|
const { rawQuery, parseFilters } = clickhouse;
|
||||||
const { queryParams, filterQuery, dateQuery } = parseFilters(filters);
|
const { queryParams, filterQuery, cohortQuery, dateQuery } = parseFilters({
|
||||||
|
...filters,
|
||||||
|
websiteId,
|
||||||
|
});
|
||||||
|
|
||||||
return rawQuery(
|
return rawQuery(
|
||||||
`
|
`
|
||||||
|
|
@ -56,12 +60,13 @@ async function clickhouseQuery(websiteId: string, filters: QueryFilters): Promis
|
||||||
url_path as urlPath,
|
url_path as urlPath,
|
||||||
referrer_domain as referrerDomain
|
referrer_domain as referrerDomain
|
||||||
from website_event
|
from website_event
|
||||||
|
${cohortQuery}
|
||||||
where website_id = {websiteId:UUID}
|
where website_id = {websiteId:UUID}
|
||||||
${filterQuery}
|
${filterQuery}
|
||||||
${dateQuery}
|
${dateQuery}
|
||||||
order by createdAt desc
|
order by createdAt desc
|
||||||
limit 100
|
limit 100
|
||||||
`,
|
`,
|
||||||
{ ...filters, ...queryParams },
|
queryParams,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ async function relationalQuery(
|
||||||
filters: QueryFilters,
|
filters: QueryFilters,
|
||||||
): Promise<WebsiteStatsData[]> {
|
): Promise<WebsiteStatsData[]> {
|
||||||
const { getTimestampDiffSQL, parseFilters, rawQuery } = prisma;
|
const { getTimestampDiffSQL, parseFilters, rawQuery } = prisma;
|
||||||
const { filterQuery, joinSessionQuery, queryParams } = parseFilters({
|
const { filterQuery, joinSessionQuery, cohortQuery, queryParams } = parseFilters({
|
||||||
...filters,
|
...filters,
|
||||||
websiteId,
|
websiteId,
|
||||||
eventType: EVENT_TYPE.pageView,
|
eventType: EVENT_TYPE.pageView,
|
||||||
|
|
@ -50,6 +50,7 @@ async function relationalQuery(
|
||||||
max(website_event.created_at) as "max_time"
|
max(website_event.created_at) as "max_time"
|
||||||
from website_event
|
from website_event
|
||||||
${joinSessionQuery}
|
${joinSessionQuery}
|
||||||
|
${cohortQuery}
|
||||||
where website_event.website_id = {{websiteId::uuid}}
|
where website_event.website_id = {{websiteId::uuid}}
|
||||||
and website_event.created_at between {{startDate}} and {{endDate}}
|
and website_event.created_at between {{startDate}} and {{endDate}}
|
||||||
and event_type = {{eventType}}
|
and event_type = {{eventType}}
|
||||||
|
|
@ -66,7 +67,7 @@ async function clickhouseQuery(
|
||||||
filters: QueryFilters,
|
filters: QueryFilters,
|
||||||
): Promise<WebsiteStatsData[]> {
|
): Promise<WebsiteStatsData[]> {
|
||||||
const { rawQuery, parseFilters } = clickhouse;
|
const { rawQuery, parseFilters } = clickhouse;
|
||||||
const { filterQuery, queryParams } = parseFilters({
|
const { filterQuery, cohortQuery, queryParams } = parseFilters({
|
||||||
...filters,
|
...filters,
|
||||||
websiteId,
|
websiteId,
|
||||||
eventType: EVENT_TYPE.pageView,
|
eventType: EVENT_TYPE.pageView,
|
||||||
|
|
@ -90,6 +91,7 @@ async function clickhouseQuery(
|
||||||
min(created_at) min_time,
|
min(created_at) min_time,
|
||||||
max(created_at) max_time
|
max(created_at) max_time
|
||||||
from website_event
|
from website_event
|
||||||
|
${cohortQuery}
|
||||||
where website_id = {websiteId:UUID}
|
where website_id = {websiteId:UUID}
|
||||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||||
and event_type = {eventType:UInt32}
|
and event_type = {eventType:UInt32}
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ async function relationalQuery(
|
||||||
const { type, limit = 500, offset = 0 } = parameters;
|
const { type, limit = 500, offset = 0 } = parameters;
|
||||||
const column = FILTER_COLUMNS[type] || type;
|
const column = FILTER_COLUMNS[type] || type;
|
||||||
const { rawQuery, parseFilters } = prisma;
|
const { rawQuery, parseFilters } = prisma;
|
||||||
const { filterQuery, joinSessionQuery, queryParams } = parseFilters(
|
const { filterQuery, joinSessionQuery, cohortQuery, queryParams } = parseFilters(
|
||||||
{
|
{
|
||||||
...filters,
|
...filters,
|
||||||
websiteId,
|
websiteId,
|
||||||
|
|
@ -75,6 +75,7 @@ async function relationalQuery(
|
||||||
${column === 'referrer_domain' ? 'count(distinct website_event.session_id)' : 'count(*)'} as y
|
${column === 'referrer_domain' ? 'count(distinct website_event.session_id)' : 'count(*)'} as y
|
||||||
from website_event
|
from website_event
|
||||||
${joinSessionQuery}
|
${joinSessionQuery}
|
||||||
|
${cohortQuery}
|
||||||
${entryExitQuery}
|
${entryExitQuery}
|
||||||
where website_event.website_id = {{websiteId::uuid}}
|
where website_event.website_id = {{websiteId::uuid}}
|
||||||
and website_event.created_at between {{startDate}} and {{endDate}}
|
and website_event.created_at between {{startDate}} and {{endDate}}
|
||||||
|
|
@ -94,11 +95,11 @@ async function clickhouseQuery(
|
||||||
websiteId: string,
|
websiteId: string,
|
||||||
parameters: PageviewMetricsParameters,
|
parameters: PageviewMetricsParameters,
|
||||||
filters: QueryFilters,
|
filters: QueryFilters,
|
||||||
): Promise<PageviewMetricsData[]> {
|
): Promise<{ x: string; y: number }[]> {
|
||||||
const { type, limit = 500, offset = 0 } = parameters;
|
const { type, limit = 500, offset = 0 } = parameters;
|
||||||
const column = FILTER_COLUMNS[type] || type;
|
const column = FILTER_COLUMNS[type] || type;
|
||||||
const { rawQuery, parseFilters } = clickhouse;
|
const { rawQuery, parseFilters } = clickhouse;
|
||||||
const { filterQuery, queryParams } = parseFilters({
|
const { filterQuery, cohortQuery, queryParams } = parseFilters({
|
||||||
...filters,
|
...filters,
|
||||||
websiteId,
|
websiteId,
|
||||||
eventType: column === 'event_name' ? EVENT_TYPE.customEvent : EVENT_TYPE.pageView,
|
eventType: column === 'event_name' ? EVENT_TYPE.customEvent : EVENT_TYPE.pageView,
|
||||||
|
|
@ -171,6 +172,7 @@ async function clickhouseQuery(
|
||||||
from (
|
from (
|
||||||
select ${columnQuery} as t
|
select ${columnQuery} as t
|
||||||
from website_event_stats_hourly as website_event
|
from website_event_stats_hourly as website_event
|
||||||
|
${cohortQuery}
|
||||||
where website_id = {websiteId:UUID}
|
where website_id = {websiteId:UUID}
|
||||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||||
and event_type = {eventType:UInt32}
|
and event_type = {eventType:UInt32}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ export async function getPageviewStats(...args: [websiteId: string, filters: Que
|
||||||
async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||||
const { timezone = 'utc', unit = 'day' } = filters;
|
const { timezone = 'utc', unit = 'day' } = filters;
|
||||||
const { getDateSQL, parseFilters, rawQuery } = prisma;
|
const { getDateSQL, parseFilters, rawQuery } = prisma;
|
||||||
const { filterQuery, joinSessionQuery, queryParams } = parseFilters({
|
const { filterQuery, cohortQuery, joinSessionQuery, queryParams } = parseFilters({
|
||||||
...filters,
|
...filters,
|
||||||
websiteId,
|
websiteId,
|
||||||
eventType: EVENT_TYPE.pageView,
|
eventType: EVENT_TYPE.pageView,
|
||||||
|
|
@ -27,6 +27,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||||
count(*) y
|
count(*) y
|
||||||
from website_event
|
from website_event
|
||||||
${joinSessionQuery}
|
${joinSessionQuery}
|
||||||
|
${cohortQuery}
|
||||||
where website_event.website_id = {{websiteId::uuid}}
|
where website_event.website_id = {{websiteId::uuid}}
|
||||||
and website_event.created_at between {{startDate}} and {{endDate}}
|
and website_event.created_at between {{startDate}} and {{endDate}}
|
||||||
and event_type = {{eventType}}
|
and event_type = {{eventType}}
|
||||||
|
|
@ -44,7 +45,7 @@ async function clickhouseQuery(
|
||||||
): Promise<{ x: string; y: number }[]> {
|
): Promise<{ x: string; y: number }[]> {
|
||||||
const { timezone = 'utc', unit = 'day' } = filters;
|
const { timezone = 'utc', unit = 'day' } = filters;
|
||||||
const { parseFilters, rawQuery, getDateSQL } = clickhouse;
|
const { parseFilters, rawQuery, getDateSQL } = clickhouse;
|
||||||
const { filterQuery, queryParams } = parseFilters({
|
const { filterQuery, cohortQuery, queryParams } = parseFilters({
|
||||||
...filters,
|
...filters,
|
||||||
websiteId,
|
websiteId,
|
||||||
eventType: EVENT_TYPE.pageView,
|
eventType: EVENT_TYPE.pageView,
|
||||||
|
|
@ -62,6 +63,7 @@ async function clickhouseQuery(
|
||||||
${getDateSQL('website_event.created_at', unit, timezone)} as t,
|
${getDateSQL('website_event.created_at', unit, timezone)} as t,
|
||||||
count(*) as y
|
count(*) as y
|
||||||
from website_event
|
from website_event
|
||||||
|
${cohortQuery}
|
||||||
where website_id = {websiteId:UUID}
|
where website_id = {websiteId:UUID}
|
||||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||||
and event_type = {eventType:UInt32}
|
and event_type = {eventType:UInt32}
|
||||||
|
|
@ -80,6 +82,7 @@ async function clickhouseQuery(
|
||||||
${getDateSQL('website_event.created_at', unit, timezone)} as t,
|
${getDateSQL('website_event.created_at', unit, timezone)} as t,
|
||||||
sum(views) as y
|
sum(views) as y
|
||||||
from website_event_stats_hourly website_event
|
from website_event_stats_hourly website_event
|
||||||
|
${cohortQuery}
|
||||||
where website_id = {websiteId:UUID}
|
where website_id = {websiteId:UUID}
|
||||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||||
and event_type = {eventType:UInt32}
|
and event_type = {eventType:UInt32}
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ async function relationalQuery(
|
||||||
): Promise<BreakdownData[]> {
|
): Promise<BreakdownData[]> {
|
||||||
const { getTimestampDiffSQL, parseFilters, rawQuery } = prisma;
|
const { getTimestampDiffSQL, parseFilters, rawQuery } = prisma;
|
||||||
const { startDate, endDate, fields } = parameters;
|
const { startDate, endDate, fields } = parameters;
|
||||||
const { filterQuery, joinSessionQuery, queryParams } = parseFilters(
|
const { filterQuery, joinSessionQuery, cohortQuery, queryParams } = parseFilters(
|
||||||
{
|
{
|
||||||
...filters,
|
...filters,
|
||||||
websiteId,
|
websiteId,
|
||||||
|
|
@ -62,6 +62,7 @@ async function relationalQuery(
|
||||||
min(website_event.created_at) as "min_time",
|
min(website_event.created_at) as "min_time",
|
||||||
max(website_event.created_at) as "max_time"
|
max(website_event.created_at) as "max_time"
|
||||||
from website_event
|
from website_event
|
||||||
|
${cohortQuery}
|
||||||
${joinSessionQuery}
|
${joinSessionQuery}
|
||||||
where website_event.website_id = {{websiteId::uuid}}
|
where website_event.website_id = {{websiteId::uuid}}
|
||||||
and website_event.created_at between {{startDate}} and {{endDate}}
|
and website_event.created_at between {{startDate}} and {{endDate}}
|
||||||
|
|
@ -85,7 +86,7 @@ async function clickhouseQuery(
|
||||||
): Promise<BreakdownData[]> {
|
): Promise<BreakdownData[]> {
|
||||||
const { parseFilters, rawQuery } = clickhouse;
|
const { parseFilters, rawQuery } = clickhouse;
|
||||||
const { startDate, endDate, fields } = parameters;
|
const { startDate, endDate, fields } = parameters;
|
||||||
const { filterQuery, queryParams } = parseFilters({
|
const { filterQuery, cohortQuery, queryParams } = parseFilters({
|
||||||
...filters,
|
...filters,
|
||||||
websiteId,
|
websiteId,
|
||||||
startDate,
|
startDate,
|
||||||
|
|
@ -111,6 +112,7 @@ async function clickhouseQuery(
|
||||||
min(created_at) min_time,
|
min(created_at) min_time,
|
||||||
max(created_at) max_time
|
max(created_at) max_time
|
||||||
from website_event
|
from website_event
|
||||||
|
${cohortQuery}
|
||||||
where website_id = {websiteId:UUID}
|
where website_id = {websiteId:UUID}
|
||||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||||
and event_type = {eventType:UInt32}
|
and event_type = {eventType:UInt32}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ async function relationalQuery(
|
||||||
filters: QueryFilters & { propertyName?: string },
|
filters: QueryFilters & { propertyName?: string },
|
||||||
) {
|
) {
|
||||||
const { rawQuery, parseFilters } = prisma;
|
const { rawQuery, parseFilters } = prisma;
|
||||||
const { filterQuery, queryParams } = parseFilters(filters, {
|
const { filterQuery, cohortQuery, queryParams } = parseFilters(filters, {
|
||||||
columns: { propertyName: 'data_key' },
|
columns: { propertyName: 'data_key' },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -25,12 +25,13 @@ async function relationalQuery(
|
||||||
`
|
`
|
||||||
select
|
select
|
||||||
data_key as "propertyName",
|
data_key as "propertyName",
|
||||||
count(distinct d.session_id) as "total"
|
count(distinct session_data.session_id) as "total"
|
||||||
from website_event e
|
from website_event
|
||||||
join session_data d
|
${cohortQuery}
|
||||||
on d.session_id = e.session_id
|
join session_data
|
||||||
where e.website_id = {{websiteId::uuid}}
|
on session_data.session_id = website_event.session_id
|
||||||
and e.created_at between {{startDate}} and {{endDate}}
|
where website_event.website_id = {{websiteId::uuid}}
|
||||||
|
and website_event.created_at between {{startDate}} and {{endDate}}
|
||||||
${filterQuery}
|
${filterQuery}
|
||||||
group by 1
|
group by 1
|
||||||
order by 2 desc
|
order by 2 desc
|
||||||
|
|
@ -45,7 +46,7 @@ async function clickhouseQuery(
|
||||||
filters: QueryFilters & { propertyName?: string },
|
filters: QueryFilters & { propertyName?: string },
|
||||||
): Promise<{ propertyName: string; total: number }[]> {
|
): Promise<{ propertyName: string; total: number }[]> {
|
||||||
const { rawQuery, parseFilters } = clickhouse;
|
const { rawQuery, parseFilters } = clickhouse;
|
||||||
const { filterQuery, queryParams } = parseFilters(filters, {
|
const { filterQuery, cohortQuery, queryParams } = parseFilters(filters, {
|
||||||
columns: { propertyName: 'data_key' },
|
columns: { propertyName: 'data_key' },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -53,13 +54,14 @@ async function clickhouseQuery(
|
||||||
`
|
`
|
||||||
select
|
select
|
||||||
data_key as propertyName,
|
data_key as propertyName,
|
||||||
count(distinct d.session_id) as total
|
count(distinct session_data.session_id) as total
|
||||||
from website_event e
|
from website_event
|
||||||
join session_data d final
|
${cohortQuery}
|
||||||
on d.session_id = e.session_id
|
join session_data final
|
||||||
where e.website_id = {websiteId:UUID}
|
on session_data.session_id = website_event.session_id
|
||||||
and e.created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
where website_event.website_id = {websiteId:UUID}
|
||||||
and d.data_key != ''
|
and website_event.created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||||
|
and session_data.data_key != ''
|
||||||
${filterQuery}
|
${filterQuery}
|
||||||
group by 1
|
group by 1
|
||||||
order by 2 desc
|
order by 2 desc
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ async function relationalQuery(
|
||||||
filters: QueryFilters & { propertyName?: string },
|
filters: QueryFilters & { propertyName?: string },
|
||||||
) {
|
) {
|
||||||
const { rawQuery, parseFilters, getDateSQL } = prisma;
|
const { rawQuery, parseFilters, getDateSQL } = prisma;
|
||||||
const { filterQuery, queryParams } = parseFilters(filters);
|
const { filterQuery, cohortQuery, queryParams } = parseFilters(filters);
|
||||||
|
|
||||||
return rawQuery(
|
return rawQuery(
|
||||||
`
|
`
|
||||||
|
|
@ -27,13 +27,14 @@ async function relationalQuery(
|
||||||
when data_type = 4 then ${getDateSQL('date_value', 'hour')}
|
when data_type = 4 then ${getDateSQL('date_value', 'hour')}
|
||||||
else string_value
|
else string_value
|
||||||
end as "value",
|
end as "value",
|
||||||
count(distinct d.session_id) as "total"
|
count(distinct session_data.session_id) as "total"
|
||||||
from website_event e
|
from website_event e
|
||||||
|
${cohortQuery}
|
||||||
join session_data d
|
join session_data d
|
||||||
on d.session_id = e.session_id
|
on session_data.session_id = website_event.session_id
|
||||||
where e.website_id = {{websiteId::uuid}}
|
where website_event.website_id = {{websiteId::uuid}}
|
||||||
and e.created_at between {{startDate}} and {{endDate}}
|
and website_event.created_at between {{startDate}} and {{endDate}}
|
||||||
and d.data_key = {{propertyName}}
|
and session_data.data_key = {{propertyName}}
|
||||||
${filterQuery}
|
${filterQuery}
|
||||||
group by value
|
group by value
|
||||||
order by 2 desc
|
order by 2 desc
|
||||||
|
|
@ -48,7 +49,7 @@ async function clickhouseQuery(
|
||||||
filters: QueryFilters & { propertyName?: string },
|
filters: QueryFilters & { propertyName?: string },
|
||||||
): Promise<{ propertyName: string; dataType: number; propertyValue: string; total: number }[]> {
|
): Promise<{ propertyName: string; dataType: number; propertyValue: string; total: number }[]> {
|
||||||
const { rawQuery, parseFilters } = clickhouse;
|
const { rawQuery, parseFilters } = clickhouse;
|
||||||
const { filterQuery, queryParams } = parseFilters(filters);
|
const { filterQuery, cohortQuery, queryParams } = parseFilters(filters);
|
||||||
|
|
||||||
return rawQuery(
|
return rawQuery(
|
||||||
`
|
`
|
||||||
|
|
@ -56,13 +57,14 @@ async function clickhouseQuery(
|
||||||
multiIf(data_type = 2, replaceAll(string_value, '.0000', ''),
|
multiIf(data_type = 2, replaceAll(string_value, '.0000', ''),
|
||||||
data_type = 4, toString(date_trunc('hour', date_value)),
|
data_type = 4, toString(date_trunc('hour', date_value)),
|
||||||
string_value) as "value",
|
string_value) as "value",
|
||||||
uniq(d.session_id) as "total"
|
uniq(session_data.session_id) as "total"
|
||||||
from website_event e
|
from website_event e
|
||||||
|
${cohortQuery}
|
||||||
join session_data d final
|
join session_data d final
|
||||||
on d.session_id = e.session_id
|
on session_data.session_id = website_event.session_id
|
||||||
where e.website_id = {websiteId:UUID}
|
where website_event.website_id = {websiteId:UUID}
|
||||||
and e.created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
and website_event.created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||||
and d.data_key = {propertyName:String}
|
and session_data.data_key = {propertyName:String}
|
||||||
${filterQuery}
|
${filterQuery}
|
||||||
group by value
|
group by value
|
||||||
order by 2 desc
|
order by 2 desc
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ async function relationalQuery(
|
||||||
const { type, limit = 500, offset = 0 } = parameters;
|
const { type, limit = 500, offset = 0 } = parameters;
|
||||||
const column = FILTER_COLUMNS[type] || type;
|
const column = FILTER_COLUMNS[type] || type;
|
||||||
const { parseFilters, rawQuery } = prisma;
|
const { parseFilters, rawQuery } = prisma;
|
||||||
const { filterQuery, joinSessionQuery, queryParams } = parseFilters(
|
const { filterQuery, joinSessionQuery, cohortQuery, queryParams } = parseFilters(
|
||||||
{
|
{
|
||||||
...filters,
|
...filters,
|
||||||
websiteId,
|
websiteId,
|
||||||
|
|
@ -46,6 +46,7 @@ async function relationalQuery(
|
||||||
count(distinct website_event.session_id) y
|
count(distinct website_event.session_id) y
|
||||||
${includeCountry ? ', country' : ''}
|
${includeCountry ? ', country' : ''}
|
||||||
from website_event
|
from website_event
|
||||||
|
${cohortQuery}
|
||||||
${joinSessionQuery}
|
${joinSessionQuery}
|
||||||
where website_event.website_id = {{websiteId::uuid}}
|
where website_event.website_id = {{websiteId::uuid}}
|
||||||
and website_event.created_at between {{startDate}} and {{endDate}}
|
and website_event.created_at between {{startDate}} and {{endDate}}
|
||||||
|
|
@ -69,7 +70,7 @@ async function clickhouseQuery(
|
||||||
const { type, limit = 500, offset = 0 } = parameters;
|
const { type, limit = 500, offset = 0 } = parameters;
|
||||||
const column = FILTER_COLUMNS[type] || type;
|
const column = FILTER_COLUMNS[type] || type;
|
||||||
const { parseFilters, rawQuery } = clickhouse;
|
const { parseFilters, rawQuery } = clickhouse;
|
||||||
const { filterQuery, queryParams } = parseFilters({
|
const { filterQuery, cohortQuery, queryParams } = parseFilters({
|
||||||
...filters,
|
...filters,
|
||||||
websiteId,
|
websiteId,
|
||||||
eventType: EVENT_TYPE.pageView,
|
eventType: EVENT_TYPE.pageView,
|
||||||
|
|
@ -85,6 +86,7 @@ async function clickhouseQuery(
|
||||||
count(distinct session_id) y
|
count(distinct session_id) y
|
||||||
${includeCountry ? ', country' : ''}
|
${includeCountry ? ', country' : ''}
|
||||||
from website_event
|
from website_event
|
||||||
|
${cohortQuery}
|
||||||
where website_id = {websiteId:UUID}
|
where website_id = {websiteId:UUID}
|
||||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||||
and event_type = {eventType:UInt32}
|
and event_type = {eventType:UInt32}
|
||||||
|
|
@ -102,6 +104,7 @@ async function clickhouseQuery(
|
||||||
uniq(session_id) y
|
uniq(session_id) y
|
||||||
${includeCountry ? ', country' : ''}
|
${includeCountry ? ', country' : ''}
|
||||||
from website_event_stats_hourly website_event
|
from website_event_stats_hourly website_event
|
||||||
|
${cohortQuery}
|
||||||
where website_id = {websiteId:UUID}
|
where website_id = {websiteId:UUID}
|
||||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||||
and event_type = {eventType:UInt32}
|
and event_type = {eventType:UInt32}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ export async function getSessionStats(...args: [websiteId: string, filters: Quer
|
||||||
async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||||
const { timezone = 'utc', unit = 'day' } = filters;
|
const { timezone = 'utc', unit = 'day' } = filters;
|
||||||
const { getDateSQL, parseFilters, rawQuery } = prisma;
|
const { getDateSQL, parseFilters, rawQuery } = prisma;
|
||||||
const { filterQuery, joinSessionQuery, queryParams } = parseFilters({
|
const { filterQuery, joinSessionQuery, cohortQuery, queryParams } = parseFilters({
|
||||||
...filters,
|
...filters,
|
||||||
websiteId,
|
websiteId,
|
||||||
eventType: EVENT_TYPE.pageView,
|
eventType: EVENT_TYPE.pageView,
|
||||||
|
|
@ -27,6 +27,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||||
count(distinct website_event.session_id) y
|
count(distinct website_event.session_id) y
|
||||||
from website_event
|
from website_event
|
||||||
${joinSessionQuery}
|
${joinSessionQuery}
|
||||||
|
${cohortQuery}
|
||||||
where website_event.website_id = {{websiteId::uuid}}
|
where website_event.website_id = {{websiteId::uuid}}
|
||||||
and website_event.created_at between {{startDate}} and {{endDate}}
|
and website_event.created_at between {{startDate}} and {{endDate}}
|
||||||
and event_type = {{eventType}}
|
and event_type = {{eventType}}
|
||||||
|
|
@ -44,7 +45,7 @@ async function clickhouseQuery(
|
||||||
): Promise<{ x: string; y: number }[]> {
|
): Promise<{ x: string; y: number }[]> {
|
||||||
const { timezone = 'utc', unit = 'day' } = filters;
|
const { timezone = 'utc', unit = 'day' } = filters;
|
||||||
const { parseFilters, rawQuery, getDateSQL } = clickhouse;
|
const { parseFilters, rawQuery, getDateSQL } = clickhouse;
|
||||||
const { filterQuery, queryParams } = parseFilters({
|
const { filterQuery, cohortQuery, queryParams } = parseFilters({
|
||||||
...filters,
|
...filters,
|
||||||
websiteId,
|
websiteId,
|
||||||
eventType: EVENT_TYPE.pageView,
|
eventType: EVENT_TYPE.pageView,
|
||||||
|
|
@ -62,6 +63,7 @@ async function clickhouseQuery(
|
||||||
${getDateSQL('website_event.created_at', unit, timezone)} as t,
|
${getDateSQL('website_event.created_at', unit, timezone)} as t,
|
||||||
count(distinct session_id) as y
|
count(distinct session_id) as y
|
||||||
from website_event
|
from website_event
|
||||||
|
${cohortQuery}
|
||||||
where website_id = {websiteId:UUID}
|
where website_id = {websiteId:UUID}
|
||||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||||
and event_type = {eventType:UInt32}
|
and event_type = {eventType:UInt32}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ async function relationalQuery(
|
||||||
filters: QueryFilters,
|
filters: QueryFilters,
|
||||||
): Promise<WebsiteSessionStatsData[]> {
|
): Promise<WebsiteSessionStatsData[]> {
|
||||||
const { parseFilters, rawQuery } = prisma;
|
const { parseFilters, rawQuery } = prisma;
|
||||||
const { filterQuery, queryParams } = parseFilters({ ...filters, websiteId });
|
const { filterQuery, cohortQuery, queryParams } = parseFilters({ ...filters, websiteId });
|
||||||
|
|
||||||
return rawQuery(
|
return rawQuery(
|
||||||
`
|
`
|
||||||
|
|
@ -36,6 +36,7 @@ async function relationalQuery(
|
||||||
count(distinct session.country) as "countries",
|
count(distinct session.country) as "countries",
|
||||||
sum(case when website_event.event_type = 2 then 1 else 0 end) as "events"
|
sum(case when website_event.event_type = 2 then 1 else 0 end) as "events"
|
||||||
from website_event
|
from website_event
|
||||||
|
${cohortQuery}
|
||||||
join session on website_event.session_id = session.session_id
|
join session on website_event.session_id = session.session_id
|
||||||
where website_event.website_id = {{websiteId::uuid}}
|
where website_event.website_id = {{websiteId::uuid}}
|
||||||
and website_event.created_at between {{startDate}} and {{endDate}}
|
and website_event.created_at between {{startDate}} and {{endDate}}
|
||||||
|
|
@ -50,7 +51,7 @@ async function clickhouseQuery(
|
||||||
filters: QueryFilters,
|
filters: QueryFilters,
|
||||||
): Promise<WebsiteSessionStatsData[]> {
|
): Promise<WebsiteSessionStatsData[]> {
|
||||||
const { rawQuery, parseFilters } = clickhouse;
|
const { rawQuery, parseFilters } = clickhouse;
|
||||||
const { filterQuery, queryParams } = parseFilters({ ...filters, websiteId });
|
const { filterQuery, cohortQuery, queryParams } = parseFilters({ ...filters, websiteId });
|
||||||
|
|
||||||
return rawQuery(
|
return rawQuery(
|
||||||
`
|
`
|
||||||
|
|
@ -61,6 +62,7 @@ async function clickhouseQuery(
|
||||||
uniq(country) as "countries",
|
uniq(country) as "countries",
|
||||||
sum(length(event_name)) as "events"
|
sum(length(event_name)) as "events"
|
||||||
from umami.website_event_stats_hourly "website_event"
|
from umami.website_event_stats_hourly "website_event"
|
||||||
|
${cohortQuery}
|
||||||
where website_id = {websiteId:UUID}
|
where website_id = {websiteId:UUID}
|
||||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||||
${filterQuery}
|
${filterQuery}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ export async function getWebsiteSessions(...args: [websiteId: string, filters: Q
|
||||||
async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||||
const { pagedRawQuery, parseFilters } = prisma;
|
const { pagedRawQuery, parseFilters } = prisma;
|
||||||
const { search } = filters;
|
const { search } = filters;
|
||||||
const { filterQuery, dateQuery, queryParams } = parseFilters({
|
const { filterQuery, dateQuery, cohortQuery, queryParams } = parseFilters({
|
||||||
...filters,
|
...filters,
|
||||||
websiteId,
|
websiteId,
|
||||||
search: search ? `%${search}%` : undefined,
|
search: search ? `%${search}%` : undefined,
|
||||||
|
|
@ -41,6 +41,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||||
sum(case when website_event.event_type = 1 then 1 else 0 end) as "views",
|
sum(case when website_event.event_type = 1 then 1 else 0 end) as "views",
|
||||||
max(website_event.created_at) as "createdAt"
|
max(website_event.created_at) as "createdAt"
|
||||||
from website_event
|
from website_event
|
||||||
|
${cohortQuery}
|
||||||
join session on session.session_id = website_event.session_id
|
join session on session.session_id = website_event.session_id
|
||||||
where website_event.website_id = {{websiteId::uuid}}
|
where website_event.website_id = {{websiteId::uuid}}
|
||||||
${dateQuery}
|
${dateQuery}
|
||||||
|
|
@ -67,7 +68,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||||
async function clickhouseQuery(websiteId: string, filters: QueryFilters) {
|
async function clickhouseQuery(websiteId: string, filters: QueryFilters) {
|
||||||
const { pagedRawQuery, parseFilters, getDateStringSQL } = clickhouse;
|
const { pagedRawQuery, parseFilters, getDateStringSQL } = clickhouse;
|
||||||
const { search } = filters;
|
const { search } = filters;
|
||||||
const { filterQuery, dateQuery, queryParams } = parseFilters({
|
const { filterQuery, dateQuery, cohortQuery, queryParams } = parseFilters({
|
||||||
...filters,
|
...filters,
|
||||||
websiteId,
|
websiteId,
|
||||||
});
|
});
|
||||||
|
|
@ -93,7 +94,8 @@ async function clickhouseQuery(websiteId: string, filters: QueryFilters) {
|
||||||
uniq(visit_id) as visits,
|
uniq(visit_id) as visits,
|
||||||
sumIf(views, event_type = 1) as views,
|
sumIf(views, event_type = 1) as views,
|
||||||
lastAt as createdAt
|
lastAt as createdAt
|
||||||
from website_event_stats_hourly
|
from website_event_stats_hourly website_event
|
||||||
|
${cohortQuery}
|
||||||
where website_id = {websiteId:UUID}
|
where website_id = {websiteId:UUID}
|
||||||
${dateQuery}
|
${dateQuery}
|
||||||
${filterQuery}
|
${filterQuery}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue