207 lines
6.9 KiB
Go
207 lines
6.9 KiB
Go
package statistics
|
||
|
||
import (
|
||
"context"
|
||
"database/sql"
|
||
"fmt"
|
||
)
|
||
|
||
type DepsClick struct {
|
||
Conn *sql.DB
|
||
}
|
||
|
||
type StatisticClick struct {
|
||
conn *sql.DB
|
||
}
|
||
|
||
func NewClickStatistic(ctx context.Context, deps DepsClick) (*StatisticClick, error) {
|
||
s := &StatisticClick{
|
||
conn: deps.Conn,
|
||
}
|
||
|
||
err := s.checkMW(ctx)
|
||
if err != nil {
|
||
fmt.Println("error check material view existing")
|
||
return nil, err
|
||
}
|
||
|
||
return s, nil
|
||
}
|
||
|
||
// todo toanaliz for keydevice,keydevicetype,keybrowser,
|
||
func (s *StatisticClick) checkMW(ctx context.Context) error {
|
||
// в мат вью получаем последний ссесионный координат, то есть тот момент на котором сессия в квизе завершилась
|
||
// не зависит какой это тип вопроса, страт, обычный, резулт, получаем всегда последний у сессии
|
||
// он определят конечное положение пользователя в опросе
|
||
query := `
|
||
CREATE MATERIALIZED VIEW IF NOT EXISTS mv_last_answers_events
|
||
ENGINE = MergeTree()
|
||
ORDER BY (ctxsession, event_time) AS
|
||
SELECT
|
||
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,
|
||
row_number() OVER (PARTITION BY ctxsession ORDER BY event_time DESC) as row_num
|
||
FROM statistics
|
||
WHERE message IN ('InfoQuizOpen', 'InfoAnswer', 'InfoResult') AND event_level = 'info') AS sorted
|
||
WHERE row_num = 1;
|
||
`
|
||
_, err := s.conn.ExecContext(ctx, query)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return nil
|
||
}
|
||
|
||
type Statistic struct {
|
||
Count int64
|
||
QuestionID int64
|
||
}
|
||
|
||
type PipeLineStatsResp [][]Statistic
|
||
|
||
type FunnelData struct {
|
||
Session string
|
||
QuestionIDs []int64
|
||
}
|
||
|
||
func (s *StatisticClick) funnelData(ctx context.Context, quizID int64, from uint64, to uint64) ([]FunnelData, error) {
|
||
// получили все уникальные сессии из мат вью
|
||
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)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer sessionRows.Close()
|
||
|
||
var funnelData []FunnelData
|
||
for sessionRows.Next() {
|
||
var ctxsession string
|
||
err := sessionRows.Scan(&ctxsession)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
// получаем вопросы уникальные, последние в рамках сессии
|
||
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
|
||
}
|
||
|
||
funnelData = append(funnelData, FunnelData{
|
||
Session: ctxsession,
|
||
QuestionIDs: questions,
|
||
})
|
||
}
|
||
|
||
if err := sessionRows.Err(); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return funnelData, nil
|
||
}
|
||
|
||
// todo еще раз понять подумать осознать как подписывать воронку сейчас если массив соддержит 1 элемент это старт ответ
|
||
func (s *StatisticClick) getOneFunnelQuestions(ctx context.Context, quizID int64, from uint64, to uint64) ([]int64, error) {
|
||
var result []int64
|
||
|
||
funnelData, err := s.funnelData(ctx, quizID, from, to)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
for _, fd := range funnelData {
|
||
if len(fd.QuestionIDs) == 1 {
|
||
result = append(result, fd.QuestionIDs...)
|
||
}
|
||
}
|
||
|
||
return result, nil
|
||
}
|
||
|
||
func (s *StatisticClick) GetPipelinesStatistics(ctx context.Context, quizID int64, from uint64, to uint64) (PipeLineStatsResp, error) {
|
||
// тут нужно подписать все вопросы, которые принадлежат окончанию сессии, то есть массив ответов до окончания сессии
|
||
// это делается для каждой из сессии которая имеется в мат вью, это отношение к результату которого достиг респондент
|
||
|
||
// для того чтобы получить индентификатор воронки, то есть тот который последний этап прохождения был, то на чем все закончилось
|
||
// нужно из всех подписанных которые подписываются массивом и нужно выбрать из всех подписанных ответов на вопросы, выбрать те
|
||
// которые имеет всего один элемент в списке, то есть достижение никакого другого вопроса или результата не проходило через
|
||
// этот ответа кроме попытки достигнуть этотго оттвета или результата, это и являются конечные точки прохождения вопросов и от них считаются воронки и являются индентификаторами воронки
|
||
// именно те которые с массивом с 1 элементом
|
||
|
||
// нужно вернуть в каждом списке пару, id вопроса и количество ответов на этот вопрос, count - уникальные сессии которые прошли через этот вопрос
|
||
|
||
var pipelines PipeLineStatsResp
|
||
idS, err := s.getOneFunnelQuestions(ctx, quizID, from, to)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
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
|
||
`
|
||
|
||
rows, err := s.conn.QueryContext(ctx, query, quizID, questionID, from, to)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer rows.Close()
|
||
|
||
var currentPipeline []Statistic
|
||
|
||
for rows.Next() {
|
||
var id int64
|
||
var count int64
|
||
err := rows.Scan(&id, &count)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
currentPipeline = append(currentPipeline, Statistic{
|
||
QuestionID: id,
|
||
Count: count,
|
||
})
|
||
}
|
||
|
||
if err := rows.Err(); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
if len(currentPipeline) > 0 {
|
||
pipelines = append(pipelines, currentPipeline)
|
||
}
|
||
}
|
||
|
||
return pipelines, nil
|
||
}
|