@@ -260,6 +260,60 @@ class AnalyticsService {
260260 }
261261 }
262262
263+ /**
264+ * Export data for external analytics tools
265+ */
266+ async exportData ( startDate , endDate , format = 'json' ) {
267+ try {
268+ const start = new Date ( startDate ) ;
269+ const end = new Date ( endDate ) ;
270+ const allEvents = [ ] ;
271+
272+ // Collect events from the date range
273+ const diffTime = Math . abs ( end - start ) ;
274+ const diffDays = Math . ceil ( diffTime / ( 1000 * 60 * 60 * 24 ) ) ;
275+
276+ for ( let i = 0 ; i <= diffDays ; i ++ ) {
277+ const date = new Date ( start ) ;
278+ date . setDate ( date . getDate ( ) + i ) ;
279+ const dateStr = date . toISOString ( ) . split ( 'T' ) [ 0 ] ;
280+ const filename = path . join ( this . dataDir , `analytics-${ dateStr } .jsonl` ) ;
281+
282+ if ( fs . existsSync ( filename ) ) {
283+ const events = await this . readLogFile ( filename ) ;
284+ allEvents . push ( ...events . filter ( event => {
285+ const eventDate = new Date ( event . timestamp ) ;
286+ return eventDate >= start && eventDate <= end ;
287+ } ) ) ;
288+ }
289+ }
290+
291+ if ( format === 'csv' ) {
292+ // Convert to CSV format
293+ if ( allEvents . length === 0 ) return 'timestamp,type,sessionId,page,query,rating\n' ;
294+
295+ const csvHeader = Object . keys ( allEvents [ 0 ] ) . join ( ',' ) + '\n' ;
296+ const csvRows = allEvents . map ( event =>
297+ Object . values ( event ) . map ( value =>
298+ typeof value === 'string' && value . includes ( ',' ) ? `"${ value } "` : value
299+ ) . join ( ',' )
300+ ) . join ( '\n' ) ;
301+
302+ return csvHeader + csvRows ;
303+ }
304+
305+ return {
306+ exportDate : new Date ( ) . toISOString ( ) ,
307+ dateRange : { start : startDate , end : endDate } ,
308+ totalEvents : allEvents . length ,
309+ events : allEvents
310+ } ;
311+ } catch ( error ) {
312+ console . error ( 'Failed to export data:' , error ) ;
313+ return null ;
314+ }
315+ }
316+
263317 /**
264318 * Private helper methods
265319 */
@@ -355,73 +409,89 @@ class AnalyticsService {
355409 }
356410
357411 async getUserFlow ( days ) {
358- // Implementation for user flow analysis
359- return {
360- entryPages : [ ] ,
361- exitPages : [ ] ,
362- commonPaths : [ ]
363- } ;
364- }
412+ const files = await this . getRecentLogFiles ( days ) ;
413+ const entryPages = new Map ( ) ;
414+ const exitPages = new Map ( ) ;
415+ const pathSequences = new Map ( ) ;
365416
366- async getPerformanceMetrics ( days ) {
367- // Implementation for performance metrics
368- return {
369- avgLoadTime : 0 ,
370- bounceRate : 0 ,
371- avgSessionDuration : 0
372- } ;
373- }
417+ for ( const file of files ) {
418+ const events = await this . readLogFile ( file ) ;
419+ const sessions = new Map ( ) ;
374420
375- /**
376- * Export data for external analytics tools
377- */
378- async exportData ( startDate , endDate , format = 'json' ) {
379- // Implementation for data export
380- return null ;
381- }
421+ // Group events by session
422+ for ( const event of events ) {
423+ if ( event . type === 'page_view' && event . sessionId ) {
424+ if ( ! sessions . has ( event . sessionId ) ) {
425+ sessions . set ( event . sessionId , [ ] ) ;
426+ }
427+ sessions . get ( event . sessionId ) . push ( event ) ;
428+ }
429+ }
382430
383- /**
384- * Generate analytics report
385- */
386- async generateReport ( days = 30 ) {
387- const data = await this . getDashboardData ( days ) ;
388-
389- const report = {
390- generatedAt : new Date ( ) . toISOString ( ) ,
391- period : `${ days } days` ,
392- summary : data . summary ,
393- insights : {
394- mostPopularPage : data . topPages [ 0 ] ?. page || 'N/A' ,
395- topSearchTerm : data . topSearches [ 0 ] ?. query || 'N/A' ,
396- userEngagement : data . summary . avgSessionTime > 180 ? 'High' : 'Moderate'
397- } ,
398- recommendations : this . generateRecommendations ( data )
399- } ;
431+ // Analyze each session
432+ for ( const [ sessionId , sessionEvents ] of sessions ) {
433+ if ( sessionEvents . length === 0 ) continue ;
400434
401- return report ;
402- }
435+ // Sort by timestamp
436+ sessionEvents . sort ( ( a , b ) => new Date ( a . timestamp ) - new Date ( b . timestamp ) ) ;
403437
404- generateRecommendations ( data ) {
405- const recommendations = [ ] ;
406-
407- if ( data . topSearches . length > 0 ) {
408- recommendations . push ( {
409- type : 'content' ,
410- message : `Consider creating more content about "${ data . topSearches [ 0 ] . query } " - it's a popular search term`
411- } ) ;
438+ // Track entry and exit pages
439+ const firstPage = sessionEvents [ 0 ] . page ;
440+ const lastPage = sessionEvents [ sessionEvents . length - 1 ] . page ;
441+
442+ entryPages . set ( firstPage , ( entryPages . get ( firstPage ) || 0 ) + 1 ) ;
443+ exitPages . set ( lastPage , ( exitPages . get ( lastPage ) || 0 ) + 1 ) ;
444+
445+ // Track common paths for sessions with multiple pages
446+ if ( sessionEvents . length > 1 ) {
447+ const pathKey = sessionEvents . map ( event => event . page ) . join ( ' > ' ) ;
448+ pathSequences . set ( pathKey , ( pathSequences . get ( pathKey ) || 0 ) + 1 ) ;
449+ }
450+ }
412451 }
413-
414- if ( data . recentFeedback . length > 0 ) {
415- const avgRating = data . recentFeedback . reduce ( ( sum , f ) => sum + f . rating , 0 ) / data . recentFeedback . length ;
416- if ( avgRating < 4 ) {
417- recommendations . push ( {
418- type : 'improvement' ,
419- message : 'Recent feedback suggests areas for improvement - review feedback comments'
420- } ) ;
452+
453+ const topEntryPages = Array . from ( entryPages . entries ( ) )
454+ . sort ( ( a , b ) => b [ 1 ] - a [ 1 ] )
455+ . map ( ( [ page , count ] ) => ( { page, count } ) )
456+ . slice ( 0 , 10 ) ;
457+
458+ const topExitPages = Array . from ( exitPages . entries ( ) )
459+ . sort ( ( a , b ) => b [ 1 ] - a [ 1 ] )
460+ . map ( ( [ page , count ] ) => ( { page, count } ) )
461+ . slice ( 0 , 10 ) ;
462+
463+ const topPaths = Array . from ( pathSequences . entries ( ) )
464+ . sort ( ( a , b ) => b [ 1 ] - a [ 1 ] )
465+ . map ( ( [ path , count ] ) => ( { path, count } ) )
466+ . slice ( 0 , 10 ) ;
467+
468+ return { topEntryPages, topExitPages, topPaths } ;
469+ }
470+
471+ async getPerformanceMetrics ( days ) {
472+ const files = await this . getRecentLogFiles ( days ) ;
473+ let totalLoadTime = 0 ;
474+ let totalCount = 0 ;
475+
476+ for ( const file of files ) {
477+ const events = await this . readLogFile ( file ) ;
478+
479+ for ( const event of events ) {
480+ if ( event . type === 'page_view' && event . loadTime ) {
481+ totalLoadTime += event . loadTime ;
482+ totalCount ++ ;
483+ }
421484 }
422485 }
423486
424- return recommendations ;
487+ const avgLoadTime = totalCount > 0 ? totalLoadTime / totalCount : 0 ;
488+
489+ return {
490+ totalLoadTime,
491+ avgLoadTime,
492+ totalCount,
493+ period : `${ days } days`
494+ } ;
425495 }
426496}
427497
0 commit comments