mirror of
https://github.com/umami-software/umami.git
synced 2026-02-10 07:37:11 +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
|
|
@ -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 = {}) {
|
||||
const query = filtersToArray(filters, options).reduce((arr, { name, column, operator }) => {
|
||||
if (column) {
|
||||
|
|
@ -103,6 +118,42 @@ function getFilterQuery(filters: Record<string, any>, options: QueryOptions = {}
|
|||
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>) {
|
||||
const { startDate, endDate, timezone } = filters;
|
||||
|
||||
|
|
@ -141,6 +192,7 @@ function parseFilters(filters: Record<string, any>, options?: QueryOptions) {
|
|||
filterQuery: getFilterQuery(filters, options),
|
||||
dateQuery: getDateQuery(filters),
|
||||
queryParams: getQueryParams(filters),
|
||||
cohortQuery: getCohortQuery(filters?.cohort),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -49,6 +49,11 @@ export const SESSION_COLUMNS = [
|
|||
'hostname',
|
||||
];
|
||||
|
||||
export const FILTER_GROUPS = {
|
||||
segment: 'segment',
|
||||
cohort: 'cohort',
|
||||
};
|
||||
|
||||
export const FILTER_COLUMNS = {
|
||||
path: '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 {
|
||||
const query = filtersToArray(filters, options).reduce(
|
||||
(arr, { name, column, operator, prefix = '' }) => {
|
||||
|
|
@ -125,6 +143,43 @@ function getFilterQuery(filters: Record<string, any>, options: QueryOptions = {}
|
|||
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>) {
|
||||
const { startDate, endDate } = filters;
|
||||
|
||||
|
|
@ -165,6 +220,7 @@ function parseFilters(filters: Record<string, any>, options?: QueryOptions) {
|
|||
dateQuery: getDateQuery(filters),
|
||||
filterQuery: getFilterQuery(filters, options),
|
||||
queryParams: getQueryParams(filters),
|
||||
cohortQuery: getCohortQuery(filters?.cohort),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
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 { getAllowedUnits, getMinimumUnit, maxDate } from '@/lib/date';
|
||||
import { checkAuth } from '@/lib/auth';
|
||||
import { fetchWebsite } from '@/lib/load';
|
||||
import { QueryFilters } from '@/lib/types';
|
||||
import { getWebsiteSegment } from '@/queries';
|
||||
|
||||
export async function parseRequest(
|
||||
request: Request,
|
||||
|
|
@ -65,16 +66,30 @@ export function getRequestDateRange(query: Record<string, string>) {
|
|||
};
|
||||
}
|
||||
|
||||
export function getRequestFilters(query: Record<string, any>) {
|
||||
return Object.keys(FILTER_COLUMNS).reduce((obj, key) => {
|
||||
export async function getRequestFilters(query: Record<string, any>, websiteId?: string) {
|
||||
const result: Record<string, any> = {};
|
||||
|
||||
for (const key of Object.keys(FILTER_COLUMNS)) {
|
||||
const value = query[key];
|
||||
|
||||
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>) {
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@ export const filterParams = {
|
|||
hostname: z.string().optional(),
|
||||
language: z.string().optional(),
|
||||
event: z.string().optional(),
|
||||
segment: z.string().optional(),
|
||||
cohort: z.string().optional(),
|
||||
};
|
||||
|
||||
export const searchParams = {
|
||||
|
|
@ -272,3 +274,5 @@ export const reportResultSchema = z.intersection(
|
|||
}),
|
||||
reportTypeSchema,
|
||||
);
|
||||
|
||||
export const segmentTypeParam = z.enum(['segment', 'cohort']);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue