2024-06-13 12:39:18 +00:00
package statistics
2024-06-13 15:37:58 +00:00
import (
"context"
"database/sql"
"fmt"
)
2024-06-13 12:39:18 +00:00
type DepsClick struct {
Conn * sql . DB
}
type StatisticClick struct {
conn * sql . DB
}
2024-06-13 15:37:58 +00:00
func NewClickStatistic ( ctx context . Context , deps DepsClick ) ( * StatisticClick , error ) {
s := & StatisticClick {
2024-06-13 12:39:18 +00:00
conn : deps . Conn ,
}
2024-06-13 15:37:58 +00:00
err := s . checkMW ( ctx )
if err != nil {
fmt . Println ( "error check material view existing" )
return nil , err
}
return s , nil
}
2024-06-13 16:01:44 +00:00
// todo toanaliz for keydevice,keydevicetype,keybrowser,
2024-06-13 15:37:58 +00:00
func ( s * StatisticClick ) checkMW ( ctx context . Context ) error {
2024-06-14 18:06:01 +00:00
// в мат вью получаем последний ссесионный координат, то есть тот момент на котором сессия в квизе завершилась
// не зависит какой это тип вопроса, страт, обычный, резулт, получаем всегда последний у сессии
// он определят конечное положение пользователя в опросе
2024-06-13 15:37:58 +00:00
query := `
CREATE MATERIALIZED VIEW IF NOT EXISTS mv_last_answers_events
ENGINE = MergeTree ( )
ORDER BY ( ctxsession , event_time ) AS
SELECT
2024-06-14 10:58:29 +00:00
event_time , ctxsession , ctxquizid , ctxquestionid , ctxidint , message , keyos ,
ctxuserip , ctxuserport , keydomain , keypath , ctxquiz , ctxreferrer
FROM ( SELECT
event_time , ctxsession , ctxquizid , ctxquestionid , ctxidint , message , keyos ,
ctxuserip , ctxuserport , keydomain , keypath , ctxquiz , ctxreferrer ,
2024-06-14 11:22:18 +00:00
row_number ( ) OVER ( PARTITION BY ctxsession ORDER BY event_time DESC ) as row_num
2024-06-14 10:58:29 +00:00
FROM statistics
WHERE message IN ( ' InfoQuizOpen ' , ' InfoAnswer ' , ' InfoResult ' ) AND event_level = ' info ' ) AS sorted
WHERE row_num = 1 ;
2024-06-13 15:37:58 +00:00
`
_ , err := s . conn . ExecContext ( ctx , query )
if err != nil {
return err
}
return nil
}
2024-06-13 19:03:41 +00:00
type Statistic struct {
2024-06-13 15:37:58 +00:00
Count int64
QuestionID int64
}
2024-06-13 19:03:41 +00:00
type PipeLineStatsResp [ ] [ ] Statistic
2024-06-14 13:51:47 +00:00
type FunnelData struct {
2024-06-14 18:06:01 +00:00
Session string
QuestionIDs [ ] int64
2024-06-14 13:51:47 +00:00
}
func ( s * StatisticClick ) funnelData ( ctx context . Context , quizID int64 , from uint64 , to uint64 ) ( [ ] FunnelData , error ) {
2024-06-14 18:06:01 +00:00
// получили все уникальные сессии из мат вью
sessionQuery := `
SELECT DISTINCT ctxsession
FROM mv_last_answers_events
WHERE ctxquizid = ? AND event_time BETWEEN ? AND ?
`
sessionRows , err := s . conn . QueryContext ( ctx , sessionQuery , quizID , from , to )
2024-06-13 15:37:58 +00:00
if err != nil {
return nil , err
}
2024-06-14 18:06:01 +00:00
defer sessionRows . Close ( )
2024-06-14 13:51:47 +00:00
2024-06-14 18:06:01 +00:00
var funnelData [ ] FunnelData
for sessionRows . Next ( ) {
var ctxsession string
err := sessionRows . Scan ( & ctxsession )
2024-06-13 15:37:58 +00:00
if err != nil {
return nil , err
}
2024-06-14 18:06:01 +00:00
// получаем вопросы уникальные, последние в рамках сессии
questionQuery := `
SELECT DISTINCT ctxquestionid
FROM statistics WHERE ctxsession = ? ORDER BY event_time DESC ;
`
questionRows , err := s . conn . QueryContext ( ctx , questionQuery , ctxsession )
if err != nil {
return nil , err
}
defer questionRows . Close ( )
var questions [ ] int64
for questionRows . Next ( ) {
var ctxquestionid int64
err := questionRows . Scan ( & ctxquestionid )
if err != nil {
return nil , err
}
questions = append ( questions , ctxquestionid )
}
fmt . Println ( "questions" , questions )
if err := questionRows . Err ( ) ; err != nil {
return nil , err
}
2024-06-13 15:37:58 +00:00
2024-06-14 18:06:01 +00:00
funnelData = append ( funnelData , FunnelData {
Session : ctxsession ,
QuestionIDs : questions ,
2024-06-13 19:03:41 +00:00
} )
2024-06-14 13:51:47 +00:00
}
2024-06-13 15:37:58 +00:00
2024-06-14 18:06:01 +00:00
if err := sessionRows . Err ( ) ; err != nil {
2024-06-14 13:51:47 +00:00
return nil , err
2024-06-13 15:37:58 +00:00
}
2024-06-14 18:06:01 +00:00
return funnelData , nil
2024-06-14 13:51:47 +00:00
}
2024-06-14 18:06:01 +00:00
// todo еще раз понять подумать осознать как подписывать воронку сейчас если массив соддержит 1 элемент это старт ответ
2024-06-14 13:51:47 +00:00
func ( s * StatisticClick ) getOneFunnelQuestions ( ctx context . Context , quizID int64 , from uint64 , to uint64 ) ( [ ] int64 , error ) {
var result [ ] int64
2024-06-14 18:06:01 +00:00
funnelData , err := s . funnelData ( ctx , quizID , from , to )
2024-06-14 13:51:47 +00:00
if err != nil {
return nil , err
2024-06-13 15:37:58 +00:00
}
2024-06-14 18:06:01 +00:00
for _ , fd := range funnelData {
if len ( fd . QuestionIDs ) == 1 {
result = append ( result , fd . QuestionIDs ... )
2024-06-14 13:51:47 +00:00
}
}
return result , nil
}
func ( s * StatisticClick ) GetPipelinesStatistics ( ctx context . Context , quizID int64 , from uint64 , to uint64 ) ( PipeLineStatsResp , error ) {
2024-06-14 18:06:01 +00:00
// тут нужно подписать все вопросы, которые принадлежат окончанию сессии, то есть массив ответов до окончания сессии
// это делается для каждой из сессии которая имеется в мат вью, это отношение к результату которого достиг респондент
// для того чтобы получить индентификатор воронки, то есть тот который последний этап прохождения был, то на чем все закончилось
// нужно из всех подписанных которые подписываются массивом и нужно выбрать из всех подписанных ответов на вопросы, выбрать те
// которые имеет всего один элемент в списке, то есть достижение никакого другого вопроса или результата не проходило через
// этот ответа кроме попытки достигнуть этотго оттвета или результата, это и являются конечные точки прохождения вопросов и от них считаются воронки и являются индентификаторами воронки
// именно те которые с массивом с 1 элементом
// нужно вернуть в каждом списке пару, id вопроса и количество ответов на этот вопрос, count - уникальные сессии которые прошли через этот вопрос
2024-06-14 13:51:47 +00:00
var pipelines PipeLineStatsResp
idS , err := s . getOneFunnelQuestions ( ctx , quizID , from , to )
if err != nil {
2024-06-13 15:37:58 +00:00
return nil , err
}
2024-06-14 18:06:01 +00:00
for _ , questionID := range idS {
query := `
SELECT ctxquestionid , COUNT ( DISTINCT ctxsession ) AS session_count
FROM mv_last_answers_events
WHERE ctxquizid = ? AND ctxquestionid = ? AND event_time BETWEEN ? AND ?
GROUP BY ctxquestionid
`
2024-06-14 13:51:47 +00:00
2024-06-14 18:06:01 +00:00
rows , err := s . conn . QueryContext ( ctx , query , quizID , questionID , from , to )
2024-06-14 13:51:47 +00:00
if err != nil {
return nil , err
}
defer rows . Close ( )
var currentPipeline [ ] Statistic
for rows . Next ( ) {
2024-06-14 18:06:01 +00:00
var id int64
2024-06-14 13:51:47 +00:00
var count int64
2024-06-14 18:06:01 +00:00
err := rows . Scan ( & id , & count )
2024-06-14 13:51:47 +00:00
if err != nil {
return nil , err
}
currentPipeline = append ( currentPipeline , Statistic {
2024-06-14 18:06:01 +00:00
QuestionID : id ,
2024-06-14 13:51:47 +00:00
Count : count ,
} )
2024-06-14 18:06:01 +00:00
}
2024-06-14 13:51:47 +00:00
2024-06-14 18:06:01 +00:00
if err := rows . Err ( ) ; err != nil {
return nil , err
2024-06-14 13:51:47 +00:00
}
if len ( currentPipeline ) > 0 {
pipelines = append ( pipelines , currentPipeline )
}
}
2024-06-13 15:37:58 +00:00
return pipelines , nil
2024-06-13 12:39:18 +00:00
}