diff --git a/src/app/(main)/websites/[websiteId]/cohorts/CohortAddButton.tsx b/src/app/(main)/websites/[websiteId]/cohorts/CohortAddButton.tsx index 4ddc3c55..abf03d10 100644 --- a/src/app/(main)/websites/[websiteId]/cohorts/CohortAddButton.tsx +++ b/src/app/(main)/websites/[websiteId]/cohorts/CohortAddButton.tsx @@ -15,7 +15,7 @@ export function CohortAddButton({ websiteId }: { websiteId: string }) { {formatMessage(labels.cohort)} - + {({ close }) => { return ; }} diff --git a/src/app/(main)/websites/[websiteId]/cohorts/CohortEditButton.tsx b/src/app/(main)/websites/[websiteId]/cohorts/CohortEditButton.tsx index 51ac8505..bd5f3abf 100644 --- a/src/app/(main)/websites/[websiteId]/cohorts/CohortEditButton.tsx +++ b/src/app/(main)/websites/[websiteId]/cohorts/CohortEditButton.tsx @@ -17,7 +17,7 @@ export function CohortEditButton({ return ( }> - + {({ close }) => { return ( {formatMessage(labels.segment)} - + {({ close }) => { return ; }} diff --git a/src/app/(main)/websites/[websiteId]/segments/SegmentEditButton.tsx b/src/app/(main)/websites/[websiteId]/segments/SegmentEditButton.tsx index 817477e3..7821815a 100644 --- a/src/app/(main)/websites/[websiteId]/segments/SegmentEditButton.tsx +++ b/src/app/(main)/websites/[websiteId]/segments/SegmentEditButton.tsx @@ -17,7 +17,7 @@ export function SegmentEditButton({ return ( }> - + {({ close }) => { return ( , options: QueryOptions = {}) { const query = filtersObjectToArray(filters, options).reduce((arr, { name, column, operator }) => { + const isCohort = options?.isCohort; + + if (isCohort) { + column = FILTER_COLUMNS[name.slice('cohort_'.length)]; + } + if (column) { if (name === 'eventType') { arr.push(`and ${mapFilter(column, operator, name, 'UInt32')}`); @@ -107,18 +113,18 @@ function getFilterQuery(filters: Record, options: QueryOptions = {} return query.join('\n'); } -function getCohortQuery(filters: Record, options: QueryOptions = {}) { - if (!filters) { +function getCohortQuery(filters: Record) { + if (!filters || Object.keys(filters).length === 0) { return ''; } - const filterQuery = getFilterQuery(filters, options); + const filterQuery = getFilterQuery(filters, { isCohort: true }); return `join ( select distinct session_id from website_event where website_id = {websiteId:UUID} - and created_at between {startDate:DateTime64} and {endDate:DateTime64} + and created_at between {cohort_startDate:DateTime64} and {cohort_endDate:DateTime64} ${filterQuery} ) as cohort on cohort.session_id = website_event.session_id @@ -159,11 +165,15 @@ function getQueryParams(filters: Record) { } function parseFilters(filters: Record, options?: QueryOptions) { + const cohortFilters = Object.fromEntries( + Object.entries(filters).filter(([key]) => key.startsWith('cohort_')), + ); + return { filterQuery: getFilterQuery(filters, options), dateQuery: getDateQuery(filters), queryParams: getQueryParams(filters), - cohortQuery: getCohortQuery(filters?.cohortFilters, options), + cohortQuery: getCohortQuery(cohortFilters), }; } diff --git a/src/lib/prisma.ts b/src/lib/prisma.ts index 1bacff1b..8f239e5f 100644 --- a/src/lib/prisma.ts +++ b/src/lib/prisma.ts @@ -2,7 +2,7 @@ import debug from 'debug'; import { PrismaClient } from '@/generated/prisma/client'; import { PrismaPg } from '@prisma/adapter-pg'; import { readReplicas } from '@prisma/extension-read-replicas'; -import { SESSION_COLUMNS, OPERATORS, DEFAULT_PAGE_SIZE } from './constants'; +import { SESSION_COLUMNS, OPERATORS, DEFAULT_PAGE_SIZE, FILTER_COLUMNS } from './constants'; import { QueryOptions, QueryFilters } from './types'; import { filtersObjectToArray } from './params'; @@ -79,24 +79,15 @@ 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 `${column} ilike '${value}'`; - case OPERATORS.doesNotContain: - return `${column} not ilike '${value}'`; - default: - return ''; - } -} - function getFilterQuery(filters: Record, options: QueryOptions = {}): string { const query = filtersObjectToArray(filters, options).reduce( (arr, { name, column, operator, prefix = '' }) => { + const isCohort = options?.isCohort; + + if (isCohort) { + column = FILTER_COLUMNS[name.slice('cohort_'.length)]; + } + if (column) { arr.push(`and ${mapFilter(`${prefix}${column}`, operator, name)}`); @@ -115,41 +106,23 @@ function getFilterQuery(filters: Record, options: QueryOptions = {} return query.join('\n'); } -function getCohortQuery(websiteId: string, filters: QueryFilters = {}, options: QueryOptions = {}) { - const query = filtersObjectToArray(filters, options).reduce( - (arr, { name, column, operator, value }) => { - if (column) { - arr.push( - `${arr.length === 0 ? 'where' : 'and'} ${mapCohortFilter(column, operator, value)}`, - ); +function getCohortQuery(filters: QueryFilters = {}) { + if (!filters || Object.keys(filters).length === 0) { + return ''; + } - if (name === 'referrer') { - arr.push(`and referrer_domain != hostname`); - } - } + const filterQuery = getFilterQuery(filters, { isCohort: true }); - 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 + 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 + where website_event.website_id = {{websiteId}} + and website_event.created_at between {{cohort_startDate}} and {{cohort_endDate}} + ${filterQuery} + ) cohort on cohort.session_id = website_event.session_id `; - } - - return ''; } function getDateQuery(filters: Record) { @@ -184,6 +157,10 @@ function parseFilters(filters: Record, options?: QueryOptions) { ['referrer', ...SESSION_COLUMNS].includes(key), ); + const cohortFilters = Object.fromEntries( + Object.entries(filters).filter(([key]) => key.startsWith('cohort_')), + ); + return { joinSessionQuery: options?.joinSession || joinSession @@ -192,7 +169,7 @@ function parseFilters(filters: Record, options?: QueryOptions) { dateQuery: getDateQuery(filters), filterQuery: getFilterQuery(filters, options), queryParams: getQueryParams(filters), - cohortQuery: getCohortQuery(filters?.cohort), + cohortQuery: getCohortQuery(cohortFilters), }; } diff --git a/src/lib/request.ts b/src/lib/request.ts index c5e5b9bc..faa76520 100644 --- a/src/lib/request.ts +++ b/src/lib/request.ts @@ -1,11 +1,11 @@ -import { z } from 'zod/v4'; -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'; +import { DEFAULT_PAGE_SIZE, FILTER_COLUMNS } from '@/lib/constants'; +import { getAllowedUnits, getMinimumUnit, maxDate, parseDateRange } from '@/lib/date'; import { fetchWebsite } from '@/lib/load'; +import { badRequest, unauthorized } from '@/lib/response'; import { QueryFilters } from '@/lib/types'; import { getWebsiteSegment } from '@/queries'; +import { z } from 'zod/v4'; export async function parseRequest( request: Request, @@ -104,7 +104,27 @@ export async function getQueryFilters( } if (params.cohort) { - filters.cohortFilters = (await getWebsiteSegment(websiteId, params.cohort))?.parameters; + const cohortFilters = (await getWebsiteSegment(websiteId, params.cohort)) + ?.parameters as Record; + + // convert dateRange to startDate and endDate + if (cohortFilters.dateRange) { + const { startDate, endDate } = parseDateRange(cohortFilters.dateRange); + cohortFilters.startDate = startDate; + cohortFilters.endDate = endDate; + delete cohortFilters.dateRange; + } + + Object.assign( + filters, + Object.fromEntries( + Object.entries(cohortFilters || {}).map(([key, value]) => + key === 'startDate' || key === 'endDate' + ? [`cohort_${key}`, new Date(value)] + : [`cohort_${key}`, value], + ), + ), + ); } } diff --git a/src/lib/types.ts b/src/lib/types.ts index 53bdaea5..6fdbfde7 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -49,6 +49,7 @@ export interface QueryOptions { columns?: Record; limit?: number; prefix?: string; + isCohort?: boolean; } export interface QueryFilters