package web import ( "encoding/json" . "openreplay/backend/pkg/messages" ) const MaxTimeDiff = 300 const MinClicksInARow = 3 type ClickRageDetector struct { lastTimestamp uint64 lastLabel string lastSelector string firstInARawTimestamp uint64 firstInARawMessageId uint64 countsInARow int url string } func (crd *ClickRageDetector) reset() { crd.lastTimestamp = 0 crd.lastLabel = "" crd.lastSelector = "" crd.firstInARawTimestamp = 0 crd.firstInARawMessageId = 0 crd.countsInARow = 0 crd.url = "" } func (crd *ClickRageDetector) createPayload() string { p, err := json.Marshal(struct{ Count int }{crd.countsInARow}) if err != nil { return "" } return string(p) } func (crd *ClickRageDetector) Build() Message { defer crd.reset() if crd.countsInARow < MinClicksInARow { return nil } return &IssueEvent{ Type: "click_rage", ContextString: crd.lastLabel, Payload: crd.createPayload(), Timestamp: crd.firstInARawTimestamp, MessageID: crd.firstInARawMessageId, URL: crd.url, Context: crd.lastSelector, // hack to pass selector to db (tags filter) } } func (crd *ClickRageDetector) Handle(message Message, timestamp uint64) Message { switch msg := message.(type) { case *MouseClick: // Set click url if crd.url == "" && msg.Url != "" { crd.url = msg.Url } // Click on different object -> build if we can and reset the builder if msg.Label == "" { return crd.Build() } // Update builder with last information if crd.lastLabel == msg.Label && timestamp-crd.lastTimestamp < MaxTimeDiff { crd.lastTimestamp = timestamp crd.countsInARow += 1 return nil } // Try to build event event := crd.Build() // Use current message as init values for new event crd.lastTimestamp = timestamp crd.lastLabel = msg.Label crd.lastSelector = msg.Selector crd.firstInARawTimestamp = timestamp crd.firstInARawMessageId = message.MsgID() crd.countsInARow = 1 if crd.url == "" && msg.Url != "" { crd.url = msg.Url } return event } return nil }