diff --git a/backend/internal/db/datasaver/saver.go b/backend/internal/db/datasaver/saver.go index 92dbff958..1a017fa6f 100644 --- a/backend/internal/db/datasaver/saver.go +++ b/backend/internal/db/datasaver/saver.go @@ -77,6 +77,10 @@ func (s *saverImpl) handleMessage(msg Message) error { return s.pg.InsertWebJSException(m) case *IntegrationEvent: return s.pg.InsertWebIntegrationEvent(m) + case *InputChange: + return s.pg.InsertWebInputDuration(m) + case *MouseThrashing: + return s.pg.InsertMouseThrashing(m) case *IOSSessionStart: return s.pg.InsertIOSSessionStart(m) case *IOSSessionEnd: diff --git a/backend/pkg/db/cache/messages-web.go b/backend/pkg/db/cache/messages-web.go index 0a870e5a2..58c703318 100644 --- a/backend/pkg/db/cache/messages-web.go +++ b/backend/pkg/db/cache/messages-web.go @@ -180,3 +180,21 @@ func (c *PGCache) InsertWebInputEvent(e *InputEvent) error { } return c.Conn.InsertWebInputEvent(sessionID, session.ProjectID, e) } + +func (c *PGCache) InsertWebInputDuration(e *InputChange) error { + sessionID := e.SessionID() + session, err := c.Cache.GetSession(sessionID) + if err != nil { + return err + } + return c.Conn.InsertWebInputDuration(sessionID, session.ProjectID, e) +} + +func (c *PGCache) InsertMouseThrashing(e *MouseThrashing) error { + sessionID := e.SessionID() + session, err := c.Cache.GetSession(sessionID) + if err != nil { + return err + } + return c.Conn.InsertMouseThrashing(sessionID, session.ProjectID, e) +} diff --git a/backend/pkg/db/postgres/bulks.go b/backend/pkg/db/postgres/bulks.go index 27ab2cafd..0dcfca646 100644 --- a/backend/pkg/db/postgres/bulks.go +++ b/backend/pkg/db/postgres/bulks.go @@ -9,7 +9,7 @@ type bulksTask struct { } func NewBulksTask() *bulksTask { - return &bulksTask{bulks: make([]Bulk, 0, 14)} + return &bulksTask{bulks: make([]Bulk, 0, 15)} } type BulkSet struct { @@ -19,6 +19,7 @@ type BulkSet struct { customEvents Bulk webPageEvents Bulk webInputEvents Bulk + webInputDurations Bulk webGraphQL Bulk webErrors Bulk webErrorEvents Bulk @@ -57,6 +58,8 @@ func (conn *BulkSet) Get(name string) Bulk { return conn.webPageEvents case "webInputEvents": return conn.webInputEvents + case "webInputDurations": + return conn.webInputDurations case "webGraphQL": return conn.webGraphQL case "webErrors": @@ -126,6 +129,14 @@ func (conn *BulkSet) initBulks() { if err != nil { log.Fatalf("can't create webPageEvents bulk: %s", err) } + conn.webInputDurations, err = NewBulk(conn.c, + "events.inputs", + "(session_id, message_id, timestamp, value, label, hesitation, duration)", + "($%d, $%d, $%d, LEFT($%d, 2000), NULLIF(LEFT($%d, 2000),''), $%d, $%d)", + 7, 200) + if err != nil { + log.Fatalf("can't create webPageEvents bulk: %s", err) + } conn.webGraphQL, err = NewBulk(conn.c, "events.graphql", "(session_id, timestamp, message_id, name, request_body, response_body)", @@ -209,6 +220,7 @@ func (conn *BulkSet) Send() { newTask.bulks = append(newTask.bulks, conn.customEvents) newTask.bulks = append(newTask.bulks, conn.webPageEvents) newTask.bulks = append(newTask.bulks, conn.webInputEvents) + newTask.bulks = append(newTask.bulks, conn.webInputDurations) newTask.bulks = append(newTask.bulks, conn.webGraphQL) newTask.bulks = append(newTask.bulks, conn.webErrors) newTask.bulks = append(newTask.bulks, conn.webErrorEvents) diff --git a/backend/pkg/db/postgres/messages-web.go b/backend/pkg/db/postgres/messages-web.go index 9251a4924..2037612f8 100644 --- a/backend/pkg/db/postgres/messages-web.go +++ b/backend/pkg/db/postgres/messages-web.go @@ -1,7 +1,10 @@ package postgres import ( + "encoding/hex" + "hash/fnv" "log" + "strconv" "openreplay/backend/pkg/db/types" . "openreplay/backend/pkg/messages" @@ -89,6 +92,24 @@ func (conn *Conn) InsertWebInputEvent(sessionID uint64, projectID uint32, e *Inp return nil } +func (conn *Conn) InsertWebInputDuration(sessionID uint64, projectID uint32, e *InputChange) error { + // Debug log + log.Printf("new InputDuration event: %v", e) + if e.Label == "" { + return nil + } + value := &e.Value + if e.ValueMasked { + value = nil + } + if err := conn.bulks.Get("webInputDurations").Append(sessionID, truncSqIdx(e.ID), e.Timestamp, value, e.Label, e.HesitationTime, e.InputDuration); err != nil { + log.Printf("insert web input event err: %s", err) + } + conn.updateSessionEvents(sessionID, 1, 0) + conn.insertAutocompleteValue(sessionID, projectID, "INPUT", e.Label) + return nil +} + func (conn *Conn) InsertWebErrorEvent(sessionID uint64, projectID uint32, e *types.ErrorEvent) error { errorID := e.ID(projectID) if err := conn.bulks.Get("webErrors").Append(errorID, projectID, e.Source, e.Name, e.Message, e.Payload); err != nil { @@ -145,3 +166,26 @@ func (conn *Conn) InsertSessionReferrer(sessionID uint64, referrer string) error WHERE session_id = $3 AND referrer IS NULL`, referrer, url.DiscardURLQuery(referrer), sessionID) } + +func (conn *Conn) InsertMouseThrashing(sessionID uint64, projectID uint32, e *MouseThrashing) error { + // Debug log + log.Printf("new MouseThrashing event: %v", e) + // + issueID := mouseThrashingID(projectID, sessionID, e.Timestamp) + if err := conn.bulks.Get("webIssues").Append(projectID, issueID, "mouse_trashing", e.Url); err != nil { + log.Printf("insert web issue err: %s", err) + } + if err := conn.bulks.Get("webIssueEvents").Append(sessionID, issueID, e.Timestamp, truncSqIdx(e.MsgID()), nil); err != nil { + log.Printf("insert web issue event err: %s", err) + } + conn.updateSessionIssues(sessionID, 0, 50) + return nil +} + +func mouseThrashingID(projectID uint32, sessID, ts uint64) string { + hash := fnv.New128a() + hash.Write([]byte("mouse_trashing")) + hash.Write([]byte(strconv.FormatUint(sessID, 10))) + hash.Write([]byte(strconv.FormatUint(ts, 10))) + return strconv.FormatUint(uint64(projectID), 16) + hex.EncodeToString(hash.Sum(nil)) +}