@@ -151,44 +151,86 @@ export const ProfilesBuilders: Record<string, SimpleQueryConfig> = {
151151
152152 return {
153153 sql : `
154- WITH user_profile AS (
154+ SELECT
155+ anonymous_id as visitor_id,
156+ MIN(time) as first_visit,
157+ MAX(time) as last_visit,
158+ COUNT(DISTINCT session_id) as total_sessions,
159+ COUNT(*) as total_pageviews,
160+ SUM(CASE WHEN time_on_page > 0 THEN time_on_page ELSE 0 END) as total_duration,
161+ formatReadableTimeDelta(SUM(CASE WHEN time_on_page > 0 THEN time_on_page ELSE 0 END)) as total_duration_formatted,
162+ any(device_type) as device,
163+ any(browser_name) as browser,
164+ any(os_name) as os,
165+ any(country) as country,
166+ any(region) as region
167+ FROM analytics.events
168+ WHERE
169+ client_id = {websiteId:String}
170+ AND anonymous_id = {visitorId:String}
171+ AND time >= parseDateTimeBestEffort({startDate:String})
172+ AND time <= parseDateTimeBestEffort({endDate:String})
173+ GROUP BY anonymous_id
174+ ` ,
175+ params : {
176+ websiteId,
177+ visitorId,
178+ startDate,
179+ endDate : `${ endDate } 23:59:59` ,
180+ } ,
181+ } ;
182+ } ,
183+ } ,
184+
185+ profile_sessions : {
186+ customSql : (
187+ websiteId : string ,
188+ startDate : string ,
189+ endDate : string ,
190+ filters ?: Filter [ ] ,
191+ _granularity ?: TimeUnit ,
192+ limit = 100 ,
193+ offset = 0
194+ ) => {
195+ const visitorId = filters ?. find ( ( f ) => f . field === 'anonymous_id' ) ?. value ;
196+
197+ if ( ! visitorId || typeof visitorId !== 'string' ) {
198+ throw new Error (
199+ 'anonymous_id filter is required for profile_sessions query'
200+ ) ;
201+ }
202+
203+ return {
204+ sql : `
205+ WITH user_sessions AS (
155206 SELECT
156- anonymous_id as visitor_id,
207+ session_id,
208+ CONCAT('Session ', ROW_NUMBER() OVER (ORDER BY MIN(time))) as session_name,
157209 MIN(time) as first_visit,
158210 MAX(time) as last_visit,
159- COUNT(DISTINCT session_id) as total_sessions ,
160- COUNT(*) as total_pageviews ,
161- SUM(CASE WHEN time_on_page > 0 THEN time_on_page ELSE 0 END ) as total_duration ,
162- formatReadableTimeDelta(SUM(CASE WHEN time_on_page > 0 THEN time_on_page ELSE 0 END)) as total_duration_formatted ,
211+ LEAST(dateDiff('second', MIN(time), MAX(time)), 28800) as duration ,
212+ formatReadableTimeDelta(LEAST(dateDiff('second', MIN(time), MAX(time)), 28800)) as duration_formatted ,
213+ countIf(event_name = 'screen_view' ) as page_views ,
214+ COUNT(DISTINCT path) as unique_pages ,
163215 any(device_type) as device,
164216 any(browser_name) as browser,
165217 any(os_name) as os,
166218 any(country) as country,
167- any(region) as region
219+ any(region) as region,
220+ any(referrer) as referrer
168221 FROM analytics.events
169222 WHERE
170223 client_id = {websiteId:String}
171224 AND anonymous_id = {visitorId:String}
172225 AND time >= parseDateTimeBestEffort({startDate:String})
173226 AND time <= parseDateTimeBestEffort({endDate:String})
174- GROUP BY anonymous_id
227+ GROUP BY session_id
228+ ORDER BY first_visit DESC
229+ LIMIT {limit:Int32} OFFSET {offset:Int32}
175230 ),
176- user_sessions AS (
231+ session_events AS (
177232 SELECT
178233 e.session_id,
179- CONCAT('Session ', ROW_NUMBER() OVER (ORDER BY MIN(e.time))) as session_name,
180- MIN(e.time) as first_visit,
181- MAX(e.time) as last_visit,
182- LEAST(dateDiff('second', MIN(e.time), MAX(e.time)), 28800) as duration,
183- formatReadableTimeDelta(LEAST(dateDiff('second', MIN(e.time), MAX(e.time)), 28800)) as duration_formatted,
184- COUNT(DISTINCT e.path) as page_views,
185- COUNT(DISTINCT e.path) as unique_pages,
186- any(e.device_type) as device,
187- any(e.browser_name) as browser,
188- any(e.os_name) as os,
189- any(e.country) as country,
190- any(e.region) as region,
191- any(e.referrer) as referrer,
192234 groupArray(
193235 tuple(
194236 e.id,
@@ -201,197 +243,48 @@ export const ProfilesBuilders: Record<string, SimpleQueryConfig> = {
201243 AND e.properties != '{}'
202244 THEN CAST(e.properties AS String)
203245 ELSE NULL
204- END,
205- NULL,
206- NULL
246+ END
207247 )
208248 ) as events
209249 FROM analytics.events e
250+ INNER JOIN user_sessions us ON e.session_id = us.session_id
210251 WHERE
211252 e.client_id = {websiteId:String}
212253 AND e.anonymous_id = {visitorId:String}
213- AND e.time >= parseDateTimeBestEffort({startDate:String})
214- AND e.time <= parseDateTimeBestEffort({endDate:String})
215254 GROUP BY e.session_id
216- ORDER BY first_visit DESC
217255 )
218256 SELECT
219- up.visitor_id,
220- up.first_visit,
221- up.last_visit,
222- up.total_sessions,
223- up.total_pageviews,
224- up.total_duration,
225- up.total_duration_formatted,
226- up.device,
227- up.browser,
228- up.os,
229- up.country,
230- up.region,
231- groupArray(
232- tuple(
233- us.session_id,
234- us.session_name,
235- us.first_visit,
236- us.last_visit,
237- us.duration,
238- us.duration_formatted,
239- us.page_views,
240- us.unique_pages,
241- us.device,
242- us.browser,
243- us.os,
244- us.country,
245- us.region,
246- us.referrer,
247- us.events
248- )
249- ) as sessions
250- FROM user_profile up
251- LEFT JOIN user_sessions us ON 1=1
252- GROUP BY
253- up.visitor_id, up.first_visit, up.last_visit, up.total_sessions,
254- up.total_pageviews, up.total_duration, up.total_duration_formatted,
255- up.device, up.browser, up.os, up.country, up.region
257+ us.session_id,
258+ us.session_name,
259+ us.first_visit,
260+ us.last_visit,
261+ us.duration,
262+ us.duration_formatted,
263+ us.page_views,
264+ us.unique_pages,
265+ us.device,
266+ us.browser,
267+ us.os,
268+ us.country,
269+ us.region,
270+ us.referrer,
271+ COALESCE(se.events, []) as events
272+ FROM user_sessions us
273+ LEFT JOIN session_events se ON us.session_id = se.session_id
274+ ORDER BY us.first_visit DESC
256275 ` ,
257276 params : {
258277 websiteId,
259278 visitorId,
260279 startDate,
261280 endDate : `${ endDate } 23:59:59` ,
281+ limit,
282+ offset,
262283 } ,
263284 } ;
264285 } ,
265- } ,
266-
267- profile_metrics : {
268- table : Analytics . events ,
269- fields : [
270- 'COUNT(DISTINCT anonymous_id) as total_visitors' ,
271- 'COUNT(DISTINCT session_id) as total_sessions' ,
272- 'AVG(CASE WHEN time_on_page > 0 THEN time_on_page / 1000 ELSE NULL END) as avg_session_duration' ,
273- 'COUNT(*) as total_events' ,
274- ] ,
275- where : [ "event_name = 'screen_view'" ] ,
276- timeField : 'time' ,
277- customizable : true ,
278- } ,
279-
280- profile_duration_distribution : {
281- table : Analytics . events ,
282- fields : [
283- 'CASE ' +
284- "WHEN time_on_page < 30 THEN '0-30s' " +
285- "WHEN time_on_page < 60 THEN '30s-1m' " +
286- "WHEN time_on_page < 300 THEN '1m-5m' " +
287- "WHEN time_on_page < 900 THEN '5m-15m' " +
288- "WHEN time_on_page < 3600 THEN '15m-1h' " +
289- "ELSE '1h+' " +
290- 'END as duration_range' ,
291- 'COUNT(DISTINCT anonymous_id) as visitors' ,
292- 'COUNT(DISTINCT session_id) as sessions' ,
293- ] ,
294- where : [ "event_name = 'screen_view'" , 'time_on_page > 0' ] ,
295- groupBy : [ 'duration_range' ] ,
296- orderBy : 'visitors DESC' ,
297- timeField : 'time' ,
298- customizable : true ,
299- } ,
300-
301- profiles_by_device : {
302- table : Analytics . events ,
303- fields : [
304- 'device_type as name' ,
305- 'COUNT(DISTINCT anonymous_id) as visitors' ,
306- 'COUNT(DISTINCT session_id) as sessions' ,
307- 'ROUND(AVG(CASE WHEN time_on_page > 0 THEN time_on_page / 1000 ELSE NULL END), 2) as avg_session_duration' ,
308- ] ,
309- where : [ "event_name = 'screen_view'" , "device_type != ''" ] ,
310- groupBy : [ 'device_type' ] ,
311- orderBy : 'visitors DESC' ,
312- timeField : 'time' ,
313- customizable : true ,
314- } ,
315-
316- profiles_by_browser : {
317- table : Analytics . events ,
318- fields : [
319- 'browser_name as name' ,
320- 'COUNT(DISTINCT anonymous_id) as visitors' ,
321- 'COUNT(DISTINCT session_id) as sessions' ,
322- 'ROUND(AVG(CASE WHEN time_on_page > 0 THEN time_on_page / 1000 ELSE NULL END), 2) as avg_session_duration' ,
323- ] ,
324- where : [ "event_name = 'screen_view'" , "browser_name != ''" ] ,
325- groupBy : [ 'browser_name' ] ,
326- orderBy : 'visitors DESC' ,
327- limit : 100 ,
328- timeField : 'time' ,
329-
330- customizable : true ,
331- } ,
332-
333- profiles_time_series : {
334- table : Analytics . events ,
335- fields : [
336- 'toDate(time) as date' ,
337- 'COUNT(DISTINCT anonymous_id) as visitors' ,
338- 'COUNT(DISTINCT session_id) as sessions' ,
339- 'ROUND(AVG(CASE WHEN time_on_page > 0 THEN time_on_page / 1000 ELSE NULL END), 2) as avg_session_duration' ,
340- ] ,
341- where : [ "event_name = 'screen_view'" ] ,
342- groupBy : [ 'toDate(time)' ] ,
343- orderBy : 'date ASC' ,
344- timeField : 'time' ,
345-
346- customizable : true ,
347- } ,
348-
349- returning_visitors : {
350- customSql : (
351- websiteId : string ,
352- startDate : string ,
353- endDate : string ,
354- _filters ?: Filter [ ] ,
355- _granularity ?: TimeUnit ,
356- limit ?: number ,
357- offset ?: number ,
358- _timezone ?: string ,
359- filterConditions ?: string [ ] ,
360- filterParams ?: Record < string , Filter [ 'value' ] >
361- ) => {
362- const combinedWhereClause = filterConditions ?. length
363- ? `AND ${ filterConditions . join ( ' AND ' ) } `
364- : '' ;
365-
366- return {
367- sql : `
368- SELECT
369- anonymous_id as visitor_id,
370- COUNT(DISTINCT session_id) as session_count,
371- MIN(time) as first_visit,
372- MAX(time) as last_visit,
373- COUNT(DISTINCT path) as unique_pages
374- FROM analytics.events
375- WHERE
376- client_id = {websiteId:String}
377- AND time >= parseDateTimeBestEffort({startDate:String})
378- AND time <= parseDateTimeBestEffort({endDate:String})
379- AND event_name = 'screen_view'
380- ${ combinedWhereClause }
381- GROUP BY anonymous_id
382- HAVING session_count > 1
383- ORDER BY session_count DESC
384- LIMIT {limit:Int32} OFFSET {offset:Int32}
385- ` ,
386- params : {
387- websiteId,
388- startDate,
389- endDate : `${ endDate } 23:59:59` ,
390- limit : limit || 100 ,
391- offset : offset || 0 ,
392- ...filterParams ,
393- } ,
394- } ;
286+ plugins : {
287+ normalizeGeo : true ,
395288 } ,
396289 } ,
397290} ;
0 commit comments