diff --git a/src/app/(main)/websites/[websiteId]/(reports)/page.tsx b/src/app/(main)/websites/[websiteId]/(reports)/page.tsx
deleted file mode 100644
index c9916b15..00000000
--- a/src/app/(main)/websites/[websiteId]/(reports)/page.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-import GoalsPage from './goals/page';
-
-export default GoalsPage;
diff --git a/src/app/(main)/websites/[websiteId]/WebsiteFilterButton.tsx b/src/app/(main)/websites/[websiteId]/WebsiteFilterButton.tsx
index cf1cae24..94022e87 100644
--- a/src/app/(main)/websites/[websiteId]/WebsiteFilterButton.tsx
+++ b/src/app/(main)/websites/[websiteId]/WebsiteFilterButton.tsx
@@ -32,7 +32,7 @@ export function WebsiteFilterButton({
{},
);
- const url = replaceParams({ ...params, segment: segment?.id });
+ const url = replaceParams({ ...params, segment });
router.push(url);
};
diff --git a/src/components/common/FilterEditForm.tsx b/src/components/common/FilterEditForm.tsx
index 80574c5e..45c402d2 100644
--- a/src/components/common/FilterEditForm.tsx
+++ b/src/components/common/FilterEditForm.tsx
@@ -21,10 +21,11 @@ export function FilterEditForm({
}: FilterEditFormProps) {
const { formatMessage, labels } = useMessages();
const [currentFilters, setCurrentFilters] = useState(filters);
- const [currentSegment, setCurrentSegment] = useState(null);
+ const [currentSegment, setCurrentSegment] = useState(segmentId);
const handleReset = () => {
setCurrentFilters([]);
+ setCurrentSegment(null);
};
const handleSave = () => {
@@ -32,8 +33,8 @@ export function FilterEditForm({
onClose?.();
};
- const handleSegmentChange = (segment?: { id: string }) => {
- setCurrentSegment(segment);
+ const handleSegmentChange = (id: string) => {
+ setCurrentSegment(id);
};
return (
@@ -49,7 +50,7 @@ export function FilterEditForm({
diff --git a/src/components/hooks/queries/useWebsiteMetricsQuery.ts b/src/components/hooks/queries/useWebsiteMetricsQuery.ts
index 17db2d92..f521798e 100644
--- a/src/components/hooks/queries/useWebsiteMetricsQuery.ts
+++ b/src/components/hooks/queries/useWebsiteMetricsQuery.ts
@@ -2,7 +2,6 @@ import { keepPreviousData } from '@tanstack/react-query';
import { useApi } from '../useApi';
import { useFilterParameters } from '../useFilterParameters';
import { useDateParameters } from '../useDateParameters';
-import { useSearchParams } from 'next/navigation';
import { ReactQueryOptions } from '@/lib/types';
export type WebsiteMetricsData = {
@@ -18,7 +17,6 @@ export function useWebsiteMetricsQuery(
const { get, useQuery } = useApi();
const date = useDateParameters(websiteId);
const filters = useFilterParameters();
- const searchParams = useSearchParams();
return useQuery({
queryKey: [
@@ -34,7 +32,6 @@ export function useWebsiteMetricsQuery(
get(`/websites/${websiteId}/metrics`, {
...date,
...filters,
- [searchParams.get('view')]: undefined,
...params,
}),
enabled: !!websiteId,
diff --git a/src/components/hooks/useFilterParameters.ts b/src/components/hooks/useFilterParameters.ts
index 44c8896e..54032120 100644
--- a/src/components/hooks/useFilterParameters.ts
+++ b/src/components/hooks/useFilterParameters.ts
@@ -21,6 +21,8 @@ export function useFilterParameters() {
page,
pageSize,
search,
+ segment,
+ cohort,
},
} = useNavigation();
@@ -41,6 +43,8 @@ export function useFilterParameters() {
tag,
hostname,
search,
+ segment,
+ cohort,
};
}, [
path,
@@ -60,5 +64,7 @@ export function useFilterParameters() {
page,
pageSize,
search,
+ segment,
+ cohort,
]);
}
diff --git a/src/components/input/SegmentFilters.tsx b/src/components/input/SegmentFilters.tsx
index 51690a01..6d1936ee 100644
--- a/src/components/input/SegmentFilters.tsx
+++ b/src/components/input/SegmentFilters.tsx
@@ -1,4 +1,3 @@
-import { useState } from 'react';
import { List, Column, ListItem } from '@umami/react-zen';
import { useWebsiteSegmentsQuery } from '@/components/hooks';
import { LoadingPanel } from '@/components/common/LoadingPanel';
@@ -11,17 +10,15 @@ export interface SegmentFiltersProps {
export function SegmentFilters({ websiteId, segmentId, onSave }: SegmentFiltersProps) {
const { data, isLoading } = useWebsiteSegmentsQuery(websiteId, { type: 'segment' });
- const [currentSegment, setCurrentSegment] = useState(segmentId);
- const handleSave = (id: string) => {
- setCurrentSegment(id);
- onSave?.(data.find(item => item.id === id));
+ const handleChange = (id: string) => {
+ onSave?.(id);
};
return (
- handleSave(id[0])}>
+ handleChange(id[0])}>
{data?.map(item => {
return (
diff --git a/src/lib/clickhouse.ts b/src/lib/clickhouse.ts
index 1670546e..bb184d6b 100644
--- a/src/lib/clickhouse.ts
+++ b/src/lib/clickhouse.ts
@@ -87,21 +87,6 @@ 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, options: QueryOptions = {}) {
const query = filtersToArray(filters, options).reduce((arr, { name, column, operator }) => {
if (column) {
@@ -118,40 +103,22 @@ function getFilterQuery(filters: Record, 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
- `;
+function getCohortQuery(filters: Record, options: QueryOptions = {}) {
+ if (!filters) {
+ return '';
}
- return '';
+ const filterQuery = getFilterQuery(filters, options);
+
+ return `join (
+ select distinct session_id
+ from website_event
+ where website_id = {websiteId:UUID}
+ and created_at between {startDate:DateTime64} and {endDate:DateTime64}
+ ${filterQuery}
+ ) as cohort
+ on cohort.session_id = website_event.session_id
+ `;
}
function getDateQuery(filters: Record) {
@@ -192,7 +159,7 @@ function parseFilters(filters: Record, options?: QueryOptions) {
filterQuery: getFilterQuery(filters, options),
dateQuery: getDateQuery(filters),
queryParams: getQueryParams(filters),
- cohortQuery: getCohortQuery(filters?.cohort),
+ cohortQuery: getCohortQuery(filters?.cohortFilters, options),
};
}
diff --git a/src/lib/params.ts b/src/lib/params.ts
index e769ed61..9b3abf58 100644
--- a/src/lib/params.ts
+++ b/src/lib/params.ts
@@ -5,7 +5,7 @@ export function parseParameterValue(param: any) {
if (typeof param === 'string') {
const [, operator, value] = param.match(/^([a-z]+)\.(.*)/) || [];
- return { operator, value };
+ return { operator: operator || OPERATORS.equals, value: value || param };
}
return { operator: OPERATORS.equals, value: param };
}
diff --git a/src/lib/prisma.ts b/src/lib/prisma.ts
index b8ad4984..ab97b7d6 100644
--- a/src/lib/prisma.ts
+++ b/src/lib/prisma.ts
@@ -241,7 +241,7 @@ async function pagedQuery(model: string, criteria: T, filters?: QueryFilters)
return { data, count, page: +page, pageSize: size, orderBy, search };
}
-async function pagedRawQuery(
+async function rawPagedQuery(
query: string,
filters: QueryFilters,
queryParams: Record,
@@ -360,7 +360,7 @@ export default {
getTimestampDiffSQL,
getSearchSQL,
pagedQuery,
- pagedRawQuery,
+ pagedRawQuery: rawPagedQuery,
parseFilters,
rawQuery,
};
diff --git a/src/lib/request.ts b/src/lib/request.ts
index 3b16f355..c5e5b9bc 100644
--- a/src/lib/request.ts
+++ b/src/lib/request.ts
@@ -1,5 +1,5 @@
import { z } from 'zod/v4';
-import { FILTER_COLUMNS, DEFAULT_PAGE_SIZE, FILTER_GROUPS } from '@/lib/constants';
+import { FILTER_COLUMNS, DEFAULT_PAGE_SIZE } from '@/lib/constants';
import { badRequest, unauthorized } from '@/lib/response';
import { getAllowedUnits, getMinimumUnit, maxDate } from '@/lib/date';
import { checkAuth } from '@/lib/auth';
@@ -79,16 +79,6 @@ export function getRequestFilters(query: Record) {
return result;
}
-export async function getRequestSegments(websiteId: string, query: Record) {
- for (const key of Object.keys(FILTER_GROUPS)) {
- const value = query[key];
-
- if (value !== undefined) {
- return getWebsiteSegment(websiteId, key, value);
- }
- }
-}
-
export async function setWebsiteDate(websiteId: string, data: Record) {
const website = await fetchWebsite(websiteId);
@@ -109,7 +99,13 @@ export async function getQueryFilters(
if (websiteId) {
await setWebsiteDate(websiteId, dateRange);
- Object.assign(filters, await getRequestSegments(websiteId, params));
+ if (params.segment) {
+ Object.assign(filters, (await getWebsiteSegment(websiteId, params.segment))?.parameters);
+ }
+
+ if (params.cohort) {
+ filters.cohortFilters = (await getWebsiteSegment(websiteId, params.cohort))?.parameters;
+ }
}
return {
diff --git a/src/lib/schema.ts b/src/lib/schema.ts
index 41931b7b..7a655498 100644
--- a/src/lib/schema.ts
+++ b/src/lib/schema.ts
@@ -35,8 +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(),
+ segment: z.string().uuid().optional(),
+ cohort: z.string().uuid().optional(),
eventType: z.coerce.number().int().positive().optional(),
};
diff --git a/src/lib/types.ts b/src/lib/types.ts
index 29d566a5..81627458 100644
--- a/src/lib/types.ts
+++ b/src/lib/types.ts
@@ -44,7 +44,14 @@ export interface QueryOptions {
prefix?: string;
}
-export interface QueryFilters extends DateParams, FilterParams, SortParams, PageParams {}
+export interface QueryFilters
+ extends DateParams,
+ FilterParams,
+ SortParams,
+ PageParams,
+ SegmentParams {
+ cohortFilters?: QueryFilters;
+}
export interface DateParams {
startDate?: Date;
@@ -86,6 +93,11 @@ export interface PageParams {
pageSize?: number;
}
+export interface SegmentParams {
+ segment?: string;
+ cohort?: string;
+}
+
export interface PageResult {
data: T;
count: number;
diff --git a/src/queries/prisma/segment.ts b/src/queries/prisma/segment.ts
index 1f962e8f..01b82e9b 100644
--- a/src/queries/prisma/segment.ts
+++ b/src/queries/prisma/segment.ts
@@ -13,13 +13,9 @@ export async function getSegment(segmentId: string): Promise {
});
}
-export async function getWebsiteSegment(
- websiteId: string,
- type: string,
- name: string,
-): Promise {
- return prisma.client.segment.findFirst({
- where: { websiteId, type, name },
+export async function getWebsiteSegment(websiteId: string, segmentId: string): Promise {
+ return prisma.client.Segment.findFirst({
+ where: { id: segmentId, websiteId },
});
}