diff --git a/backend/pkg/messages/filters.go b/backend/pkg/messages/filters.go index 094586abf..9b84a4c92 100644 --- a/backend/pkg/messages/filters.go +++ b/backend/pkg/messages/filters.go @@ -11,4 +11,4 @@ func IsMobileType(id int) bool { func IsDOMType(id int) bool { return 0 == id || 4 == id || 5 == id || 6 == id || 7 == id || 8 == id || 9 == id || 10 == id || 11 == id || 12 == id || 13 == id || 14 == id || 15 == id || 16 == id || 18 == id || 19 == id || 20 == id || 34 == id || 35 == id || 37 == id || 38 == id || 49 == id || 50 == id || 51 == id || 43 == id || 52 == id || 54 == id || 55 == id || 57 == id || 58 == id || 59 == id || 60 == id || 61 == id || 67 == id || 68 == id || 69 == id || 70 == id || 71 == id || 72 == id || 73 == id || 74 == id || 75 == id || 76 == id || 77 == id || 113 == id || 114 == id || 117 == id || 118 == id || 119 == id || 122 == id || 93 == id || 96 == id || 100 == id || 101 == id || 102 == id || 103 == id || 104 == id || 105 == id || 106 == id || 111 == id -} +} \ No newline at end of file diff --git a/backend/pkg/messages/messages.go b/backend/pkg/messages/messages.go index 5759ea627..36b2576f4 100644 --- a/backend/pkg/messages/messages.go +++ b/backend/pkg/messages/messages.go @@ -4,6 +4,7 @@ package messages const ( MsgTimestamp = 0 MsgSessionStart = 1 + MsgSessionEndDeprecated = 3 MsgSetPageLocationDeprecated = 4 MsgSetViewportSize = 5 MsgSetViewportScroll = 6 @@ -25,6 +26,7 @@ const ( MsgConsoleLog = 22 MsgPageLoadTiming = 23 MsgPageRenderTiming = 24 + MsgJSExceptionDeprecated = 25 MsgIntegrationEvent = 26 MsgCustomEvent = 27 MsgUserID = 28 @@ -35,6 +37,9 @@ const ( MsgPageEvent = 33 MsgStringDictGlobal = 34 MsgSetNodeAttributeDictGlobal = 35 + MsgCSSInsertRule = 37 + MsgCSSDeleteRule = 38 + MsgFetch = 39 MsgProfiler = 40 MsgOTable = 41 MsgStateAction = 42 @@ -54,11 +59,14 @@ const ( MsgPerformanceTrackAggr = 56 MsgLoadFontFace = 57 MsgSetNodeFocus = 58 + MsgLongTask = 59 MsgSetNodeAttributeURLBased = 60 MsgSetCSSDataURLBased = 61 + MsgIssueEventDeprecated = 62 MsgTechnicalInfo = 63 MsgCustomIssue = 64 MsgAssetCache = 66 + MsgCSSInsertRuleURLBased = 67 MsgMouseClick = 68 MsgMouseClickDeprecated = 69 MsgCreateIFrameDocument = 70 @@ -71,6 +79,7 @@ const ( MsgAdoptedSSRemoveOwner = 77 MsgJSException = 78 MsgZustand = 79 + MsgBatchMeta = 80 MsgBatchMetadata = 81 MsgPartitionedMessage = 82 MsgNetworkRequest = 83 @@ -734,7 +743,7 @@ func (msg *PageRenderTiming) TypeID() int { type JSExceptionDeprecated struct { message - Name string + Name string Message string Payload string } @@ -1061,8 +1070,8 @@ func (msg *SetNodeAttributeDictGlobal) TypeID() int { type CSSInsertRule struct { message - ID uint64 - Rule string + ID uint64 + Rule string Index uint64 } @@ -1086,7 +1095,7 @@ func (msg *CSSInsertRule) TypeID() int { type CSSDeleteRule struct { message - ID uint64 + ID uint64 Index uint64 } @@ -1109,13 +1118,13 @@ func (msg *CSSDeleteRule) TypeID() int { type Fetch struct { message - Method string - URL string - Request string - Response string - Status uint64 + Method string + URL string + Request string + Response string + Status uint64 Timestamp uint64 - Duration uint64 + Duration uint64 } func (msg *Fetch) Encode() []byte { @@ -1635,12 +1644,12 @@ func (msg *SetNodeFocus) TypeID() int { type LongTask struct { message - Timestamp uint64 - Duration uint64 - Context uint64 + Timestamp uint64 + Duration uint64 + Context uint64 ContainerType uint64 - ContainerSrc string - ContainerId string + ContainerSrc string + ContainerId string ContainerName string } @@ -1720,12 +1729,12 @@ func (msg *SetCSSDataURLBased) TypeID() int { type IssueEventDeprecated struct { message - MessageID uint64 - Timestamp uint64 - Type string + MessageID uint64 + Timestamp uint64 + Type string ContextString string - Context string - Payload string + Context string + Payload string } func (msg *IssueEventDeprecated) Encode() []byte { @@ -1818,9 +1827,9 @@ func (msg *AssetCache) TypeID() int { type CSSInsertRuleURLBased struct { message - ID uint64 - Rule string - Index uint64 + ID uint64 + Rule string + Index uint64 BaseURL string } @@ -2145,9 +2154,9 @@ func (msg *Zustand) TypeID() int { type BatchMeta struct { message - PageNo uint64 + PageNo uint64 FirstIndex uint64 - Timestamp int64 + Timestamp int64 } func (msg *BatchMeta) Encode() []byte { @@ -2291,17 +2300,17 @@ func (msg *WSChannel) TypeID() int { type Incident struct { message Label string - StartTime string - EndTime string + StartTime int64 + EndTime int64 } func (msg *Incident) Encode() []byte { - buf := make([]byte, 31+len(msg.Label)+len(msg.StartTime)+len(msg.EndTime)) + buf := make([]byte, 31+len(msg.Label)) buf[0] = 85 p := 1 p = WriteString(msg.Label, buf, p) - p = WriteString(msg.StartTime, buf, p) - p = WriteString(msg.EndTime, buf, p) + p = WriteInt(msg.StartTime, buf, p) + p = WriteInt(msg.EndTime, buf, p) return buf[:p] } diff --git a/backend/pkg/messages/read-message.go b/backend/pkg/messages/read-message.go index d6a12ad38..6cedb2542 100644 --- a/backend/pkg/messages/read-message.go +++ b/backend/pkg/messages/read-message.go @@ -69,12 +69,12 @@ func DecodeSessionStart(reader BytesReader) (Message, error) { } func DecodeSessionEndDeprecated(reader BytesReader) (Message, error) { - var err error = nil - msg := &SessionEndDeprecated{} - if msg.Timestamp, err = reader.ReadUint(); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &SessionEndDeprecated{} + if msg.Timestamp, err = reader.ReadUint(); err != nil { + return nil, err + } + return msg, err } func DecodeSetPageLocationDeprecated(reader BytesReader) (Message, error) { @@ -391,18 +391,18 @@ func DecodePageRenderTiming(reader BytesReader) (Message, error) { } func DecodeJSExceptionDeprecated(reader BytesReader) (Message, error) { - var err error = nil - msg := &JSExceptionDeprecated{} - if msg.Name, err = reader.ReadString(); err != nil { - return nil, err - } + var err error = nil + msg := &JSExceptionDeprecated{} + if msg.Name, err = reader.ReadString(); err != nil { + return nil, err + } if msg.Message, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Payload, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeIntegrationEvent(reader BytesReader) (Message, error) { @@ -634,57 +634,57 @@ func DecodeSetNodeAttributeDictGlobal(reader BytesReader) (Message, error) { } func DecodeCSSInsertRule(reader BytesReader) (Message, error) { - var err error = nil - msg := &CSSInsertRule{} - if msg.ID, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &CSSInsertRule{} + if msg.ID, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Rule, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Index, err = reader.ReadUint(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeCSSDeleteRule(reader BytesReader) (Message, error) { - var err error = nil - msg := &CSSDeleteRule{} - if msg.ID, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &CSSDeleteRule{} + if msg.ID, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Index, err = reader.ReadUint(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeFetch(reader BytesReader) (Message, error) { - var err error = nil - msg := &Fetch{} - if msg.Method, err = reader.ReadString(); err != nil { - return nil, err - } + var err error = nil + msg := &Fetch{} + if msg.Method, err = reader.ReadString(); err != nil { + return nil, err + } if msg.URL, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Request, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Response, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Status, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.Timestamp, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.Duration, err = reader.ReadUint(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeProfiler(reader BytesReader) (Message, error) { @@ -1000,30 +1000,30 @@ func DecodeSetNodeFocus(reader BytesReader) (Message, error) { } func DecodeLongTask(reader BytesReader) (Message, error) { - var err error = nil - msg := &LongTask{} - if msg.Timestamp, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &LongTask{} + if msg.Timestamp, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Duration, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.Context, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.ContainerType, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.ContainerSrc, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.ContainerId, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.ContainerName, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeSetNodeAttributeURLBased(reader BytesReader) (Message, error) { @@ -1060,27 +1060,27 @@ func DecodeSetCSSDataURLBased(reader BytesReader) (Message, error) { } func DecodeIssueEventDeprecated(reader BytesReader) (Message, error) { - var err error = nil - msg := &IssueEventDeprecated{} - if msg.MessageID, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &IssueEventDeprecated{} + if msg.MessageID, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Timestamp, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.Type, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.ContextString, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Context, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Payload, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeTechnicalInfo(reader BytesReader) (Message, error) { @@ -1117,21 +1117,21 @@ func DecodeAssetCache(reader BytesReader) (Message, error) { } func DecodeCSSInsertRuleURLBased(reader BytesReader) (Message, error) { - var err error = nil - msg := &CSSInsertRuleURLBased{} - if msg.ID, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &CSSInsertRuleURLBased{} + if msg.ID, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.Rule, err = reader.ReadString(); err != nil { - return nil, err - } + return nil, err + } if msg.Index, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.BaseURL, err = reader.ReadString(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeMouseClick(reader BytesReader) (Message, error) { @@ -1315,18 +1315,18 @@ func DecodeZustand(reader BytesReader) (Message, error) { } func DecodeBatchMeta(reader BytesReader) (Message, error) { - var err error = nil - msg := &BatchMeta{} - if msg.PageNo, err = reader.ReadUint(); err != nil { - return nil, err - } + var err error = nil + msg := &BatchMeta{} + if msg.PageNo, err = reader.ReadUint(); err != nil { + return nil, err + } if msg.FirstIndex, err = reader.ReadUint(); err != nil { - return nil, err - } + return nil, err + } if msg.Timestamp, err = reader.ReadInt(); err != nil { - return nil, err - } - return msg, err + return nil, err + } + return msg, err } func DecodeBatchMetadata(reader BytesReader) (Message, error) { @@ -1425,10 +1425,10 @@ func DecodeIncident(reader BytesReader) (Message, error) { if msg.Label, err = reader.ReadString(); err != nil { return nil, err } - if msg.StartTime, err = reader.ReadString(); err != nil { + if msg.StartTime, err = reader.ReadInt(); err != nil { return nil, err } - if msg.EndTime, err = reader.ReadString(); err != nil { + if msg.EndTime, err = reader.ReadInt(); err != nil { return nil, err } return msg, err diff --git a/ee/connectors/msgcodec/messages.py b/ee/connectors/msgcodec/messages.py index e4de175ef..9022e72b3 100644 --- a/ee/connectors/msgcodec/messages.py +++ b/ee/connectors/msgcodec/messages.py @@ -339,6 +339,23 @@ class PageEvent(Message): self.web_vitals = web_vitals +class StringDictGlobal(Message): + __id__ = 34 + + def __init__(self, key, value): + self.key = key + self.value = value + + +class SetNodeAttributeDictGlobal(Message): + __id__ = 35 + + def __init__(self, id, name, value): + self.id = id + self.name = name + self.value = value + + class CSSInsertRule(Message): __id__ = 37 diff --git a/ee/connectors/msgcodec/messages.pyx b/ee/connectors/msgcodec/messages.pyx index d801cb3b1..dcc0ce8f2 100644 --- a/ee/connectors/msgcodec/messages.pyx +++ b/ee/connectors/msgcodec/messages.pyx @@ -511,6 +511,30 @@ cdef class PageEvent(PyMessage): self.web_vitals = web_vitals +cdef class StringDictGlobal(PyMessage): + cdef public int __id__ + cdef public unsigned long key + cdef public str value + + def __init__(self, unsigned long key, str value): + self.__id__ = 34 + self.key = key + self.value = value + + +cdef class SetNodeAttributeDictGlobal(PyMessage): + cdef public int __id__ + cdef public unsigned long id + cdef public unsigned long name + cdef public unsigned long value + + def __init__(self, unsigned long id, unsigned long name, unsigned long value): + self.__id__ = 35 + self.id = id + self.name = name + self.value = value + + cdef class CSSInsertRule(PyMessage): cdef public int __id__ cdef public unsigned long id @@ -1179,10 +1203,10 @@ cdef class WSChannel(PyMessage): cdef class Incident(PyMessage): cdef public int __id__ cdef public str label - cdef public str start_time - cdef public str end_time + cdef public long start_time + cdef public long end_time - def __init__(self, str label, str start_time, str end_time): + def __init__(self, str label, long start_time, long end_time): self.__id__ = 85 self.label = label self.start_time = start_time diff --git a/ee/connectors/msgcodec/msgcodec.py b/ee/connectors/msgcodec/msgcodec.py index c069eb44a..09f0e4c48 100644 --- a/ee/connectors/msgcodec/msgcodec.py +++ b/ee/connectors/msgcodec/msgcodec.py @@ -360,6 +360,19 @@ class MessageCodec(Codec): web_vitals=self.read_string(reader) ) + if message_id == 34: + return StringDictGlobal( + key=self.read_uint(reader), + value=self.read_string(reader) + ) + + if message_id == 35: + return SetNodeAttributeDictGlobal( + id=self.read_uint(reader), + name=self.read_uint(reader), + value=self.read_uint(reader) + ) + if message_id == 37: return CSSInsertRule( id=self.read_uint(reader), @@ -719,8 +732,8 @@ class MessageCodec(Codec): if message_id == 85: return Incident( label=self.read_string(reader), - start_time=self.read_string(reader), - end_time=self.read_string(reader) + start_time=self.read_int(reader), + end_time=self.read_int(reader) ) if message_id == 112: diff --git a/ee/connectors/msgcodec/msgcodec.pyx b/ee/connectors/msgcodec/msgcodec.pyx index 5320a66d3..436acbef0 100644 --- a/ee/connectors/msgcodec/msgcodec.pyx +++ b/ee/connectors/msgcodec/msgcodec.pyx @@ -458,6 +458,19 @@ cdef class MessageCodec: web_vitals=self.read_string(reader) ) + if message_id == 34: + return StringDictGlobal( + key=self.read_uint(reader), + value=self.read_string(reader) + ) + + if message_id == 35: + return SetNodeAttributeDictGlobal( + id=self.read_uint(reader), + name=self.read_uint(reader), + value=self.read_uint(reader) + ) + if message_id == 37: return CSSInsertRule( id=self.read_uint(reader), @@ -817,8 +830,8 @@ cdef class MessageCodec: if message_id == 85: return Incident( label=self.read_string(reader), - start_time=self.read_string(reader), - end_time=self.read_string(reader) + start_time=self.read_int(reader), + end_time=self.read_int(reader) ) if message_id == 112: diff --git a/frontend/app/components/Session_/EventsBlock/EventGroupWrapper.tsx b/frontend/app/components/Session_/EventsBlock/EventGroupWrapper.tsx index 92f89bcea..edaaad07e 100644 --- a/frontend/app/components/Session_/EventsBlock/EventGroupWrapper.tsx +++ b/frontend/app/components/Session_/EventsBlock/EventGroupWrapper.tsx @@ -1,5 +1,5 @@ import { TYPES } from 'Types/session/event'; -import React from 'react'; +import React, { useMemo } from 'react'; import { observer } from 'mobx-react-lite'; import { useStore } from 'App/mstore'; import UxtEvent from 'Components/Session_/EventsBlock/UxtEvent'; @@ -32,6 +32,7 @@ function EventGroupWrapper(props) { presentInSearch, isNote, isTabChange, + isIncident, filterOutNote, } = props; const { t } = useTranslation(); @@ -57,6 +58,16 @@ function EventGroupWrapper(props) { /> ); } + if (isIncident) { + return ( + + ) + } if (isLocation) { return ( { + if (isSearched) { + return '#F0A930'; + } + if (props.isPrev) { + return '#A7BFFF'; + } + if (props.isCurrent) { + return '#394EFF'; + } + return 'transparent'; + }, [isSearched, props.isPrev, props.isCurrent]); + return ( <>
@@ -172,4 +191,22 @@ function TabChange({ from, to, activeUrl, onClick }) { ); }; +function Incident({ label, onClick }) { + const { t } = useTranslation(); + return ( +
+
+ +
+ {t('Incident')} + {label} +
+
+
+ ); +}; + export default observer(EventGroupWrapper); diff --git a/frontend/app/components/Session_/EventsBlock/EventsBlock.tsx b/frontend/app/components/Session_/EventsBlock/EventsBlock.tsx index 33cdaa123..546692b5c 100644 --- a/frontend/app/components/Session_/EventsBlock/EventsBlock.tsx +++ b/frontend/app/components/Session_/EventsBlock/EventsBlock.tsx @@ -35,7 +35,7 @@ function EventsBlock(props: IProps) { useStore(); const session = sessionStore.current; const { notesWithEvents } = session; - const { uxtVideo } = session; + const { uxtVideo, incidents } = session; const { filteredEvents } = sessionStore; const query = sessionStore.eventsQuery; const { eventsIndex } = sessionStore; @@ -49,8 +49,6 @@ function EventsBlock(props: IProps) { const { store, player } = React.useContext(PlayerContext); const [currentTimeEventIndex, setCurrentTimeEventIndex] = React.useState(0); - console.log('FILTER', uiPlayerStore.showOnlySearchEvents) - const { time, endTime, @@ -88,7 +86,7 @@ function EventsBlock(props: IProps) { } }); } - const eventsWithMobxNotes = [...notesWithEvents, ...notes].sort(sortEvents); + const eventsWithMobxNotes = [...incidents, ...notesWithEvents, ...notes, ].sort(sortEvents); const filteredTabEvents = query.length ? tabChangeEvents.filter((e) => (e.activeUrl as string).includes(query)) : tabChangeEvents; @@ -200,6 +198,7 @@ function EventsBlock(props: IProps) { const event = usedEvents[index]; const isNote = 'noteId' in event; const isTabChange = 'type' in event && event.type === 'TABCHANGE'; + const isIncident = 'type' in event && event.type === 'INCIDENT'; const isCurrent = index === currentTimeEventIndex; const isPrev = index < currentTimeEventIndex; const isSearched = event.isHighlighted; @@ -218,6 +217,7 @@ function EventsBlock(props: IProps) { showSelection={!playing} isNote={isNote} isTabChange={isTabChange} + isIncident={isIncident} isPrev={isPrev} filterOutNote={filterOutNote} setActiveTab={setActiveTab} diff --git a/frontend/app/components/Session_/Player/Controls/EventsList.tsx b/frontend/app/components/Session_/Player/Controls/EventsList.tsx index c469c71a9..732613f48 100644 --- a/frontend/app/components/Session_/Player/Controls/EventsList.tsx +++ b/frontend/app/components/Session_/Player/Controls/EventsList.tsx @@ -6,13 +6,15 @@ import { import { observer } from 'mobx-react-lite'; import { getTimelinePosition } from './getTimelinePosition'; import { useStore } from '@/mstore'; +import { getTimelineEventWidth } from './getTimelineEventWidth'; +import { Tooltip } from 'antd'; function EventsList() { const { store } = useContext(PlayerContext); - const { uiPlayerStore } = useStore(); + const { uiPlayerStore, sessionStore } = useStore(); + const { eventCount, endTime, tabStates, sessionStart } = store.get(); + const { incidents } = sessionStore.current; - const { eventCount, endTime } = store.get(); - const { tabStates } = store.get(); const scale = 100 / endTime; const events = React.useMemo( () => Object.values(tabStates)[0]?.eventList.filter((e) => { @@ -39,10 +41,25 @@ function EventsList() {
))} + {incidents.map((i) => { + const width = getTimelineEventWidth(endTime, (i as any).time, (i as any).endTime - sessionStart); + return ( + +
+ + ) + })} ); } diff --git a/frontend/app/components/Session_/Player/Controls/getTimelineEventWidth.ts b/frontend/app/components/Session_/Player/Controls/getTimelineEventWidth.ts new file mode 100644 index 000000000..954eaaf33 --- /dev/null +++ b/frontend/app/components/Session_/Player/Controls/getTimelineEventWidth.ts @@ -0,0 +1,21 @@ +import { getTimelinePosition } from '@/utils'; + +export function getTimelineEventWidth( + sessionDuration: number, + eventStart: number, + eventEnd: number, +): number | string { + if (eventStart < 0) { + eventStart = 0; + } + if (eventEnd > sessionDuration) { + eventEnd = sessionDuration; + } + if (eventStart === eventEnd) { + return '2px'; + } + + const width = ((eventEnd - eventStart) / sessionDuration) * 100; + + return width < 1 ? '4px' : width; +} diff --git a/frontend/app/components/shared/SessionsTabOverview/components/SessionTags/SessionTags.tsx b/frontend/app/components/shared/SessionsTabOverview/components/SessionTags/SessionTags.tsx index 018e0b389..6983948ac 100644 --- a/frontend/app/components/shared/SessionsTabOverview/components/SessionTags/SessionTags.tsx +++ b/frontend/app/components/shared/SessionsTabOverview/components/SessionTags/SessionTags.tsx @@ -1,6 +1,6 @@ import { issues_types, types } from 'Types/session/issue'; import { Grid, Segmented } from 'antd'; -import { Angry, CircleAlert, Skull, WifiOff, ChevronDown } from 'lucide-react'; +import { Angry, CircleAlert, Skull, WifiOff, ChevronDown, MessageCircleWarning } from 'lucide-react'; import { observer } from 'mobx-react-lite'; import React, { useState, useEffect, useRef } from 'react'; import { useStore } from 'App/mstore'; @@ -15,6 +15,7 @@ const tagIcons = { [types.CLICK_RAGE]: , [types.CRASH]: , [types.TAP_RAGE]: , + [types.INCIDENTS]: , } as Record; function SessionTags() { diff --git a/frontend/app/mstore/sessionStore.ts b/frontend/app/mstore/sessionStore.ts index 466a7219d..42b4161ce 100644 --- a/frontend/app/mstore/sessionStore.ts +++ b/frontend/app/mstore/sessionStore.ts @@ -19,19 +19,6 @@ import { searchStore, searchStoreLive } from './index'; import { checkEventWithFilters } from '@/components/Session_/Player/Controls/checkEventWithFilters'; const range = getDateRangeFromValue(LAST_7_DAYS); -const mockIncidents = [ - { - label: 'Inciden 1', - startTime: 1746629916704, - endTime: 1746629916704, - }, - { - label: 'Incident 2', - startTime: 1746629916704, - endTime: 1746629916704, - }, -] - const defaultDateFilters = { url: '', rangeValue: LAST_7_DAYS, @@ -357,9 +344,8 @@ export default class SessionStore { events: evData.events.map((e) => ({ ...e, isHighlighted: checkEventWithFilters(e, searchStore.instance.filters) - })).concat(mockIncidents) + })), }); - console.log('!!!!', eventsData) } catch (e) { console.error('Failed to fetch events', e); } @@ -373,6 +359,7 @@ export default class SessionStore { stackEvents = [], userEvents = [], userTesting = [], + incidents = [], } = eventsData; const filterEvents = filter.events as Record[]; @@ -413,6 +400,7 @@ export default class SessionStore { userEvents, stackEvents, userTesting, + incidents, ); this.current = session; this.eventsIndex = matching; diff --git a/frontend/app/player/web/Lists.ts b/frontend/app/player/web/Lists.ts index 381473c8f..2168038e7 100644 --- a/frontend/app/player/web/Lists.ts +++ b/frontend/app/player/web/Lists.ts @@ -59,6 +59,7 @@ const SIMPLE_LIST_NAMES = [ 'exceptions', 'profiles', 'frustrations', + 'incidents', ] as const; const MARKED_LIST_NAMES = [ 'log', diff --git a/frontend/app/player/web/messages/RawMessageReader.gen.ts b/frontend/app/player/web/messages/RawMessageReader.gen.ts index 270029843..c95301414 100644 --- a/frontend/app/player/web/messages/RawMessageReader.gen.ts +++ b/frontend/app/player/web/messages/RawMessageReader.gen.ts @@ -735,8 +735,8 @@ export default class RawMessageReader extends PrimitiveReader { case 85: { const label = this.readString(); if (label === null) { return resetPointer() } - const startTime = this.readString(); if (startTime === null) { return resetPointer() } - const endTime = this.readString(); if (endTime === null) { return resetPointer() } + const startTime = this.readInt(); if (startTime === null) { return resetPointer() } + const endTime = this.readInt(); if (endTime === null) { return resetPointer() } return { tp: MType.Incident, label, diff --git a/frontend/app/player/web/messages/raw.gen.ts b/frontend/app/player/web/messages/raw.gen.ts index 56dd6faf6..ef04d7f6d 100644 --- a/frontend/app/player/web/messages/raw.gen.ts +++ b/frontend/app/player/web/messages/raw.gen.ts @@ -502,8 +502,8 @@ export interface RawWsChannel { export interface RawIncident { tp: MType.Incident, label: string, - startTime: string, - endTime: string, + startTime: number, + endTime: number, } export interface RawSelectionChange { diff --git a/frontend/app/player/web/messages/tracker.gen.ts b/frontend/app/player/web/messages/tracker.gen.ts index 957efbccb..16b036a4e 100644 --- a/frontend/app/player/web/messages/tracker.gen.ts +++ b/frontend/app/player/web/messages/tracker.gen.ts @@ -492,8 +492,8 @@ type TrWSChannel = [ type TrIncident = [ type: 85, label: string, - startTime: string, - endTime: string, + startTime: number, + endTime: number, ] type TrInputChange = [ diff --git a/frontend/app/svg/icons/funnel/message-circle-warning.svg b/frontend/app/svg/icons/funnel/message-circle-warning.svg new file mode 100644 index 000000000..723cfce0b --- /dev/null +++ b/frontend/app/svg/icons/funnel/message-circle-warning.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/app/types/session/event.ts b/frontend/app/types/session/event.ts index 7811d882e..b3435de85 100644 --- a/frontend/app/types/session/event.ts +++ b/frontend/app/types/session/event.ts @@ -295,8 +295,7 @@ export class Incident extends Event { Object.assign(this, { ...evt, label: evt.label || 'User signaled an incident', - startTime: evt.startTime, - endTime: evt.startTime || evt.endTime, + type: 'INCIDENT', }); } } @@ -312,7 +311,6 @@ export type InjectedEvent = | Incident; export default function (event: EventData) { - console.log('DEETECT EVENT', event); if ('allow_typing' in event) { return new UxtEvent(event); } diff --git a/frontend/app/types/session/issue.ts b/frontend/app/types/session/issue.ts index 6aaf1245b..b8c3e364d 100644 --- a/frontend/app/types/session/issue.ts +++ b/frontend/app/types/session/issue.ts @@ -1,4 +1,3 @@ -import i18next, { TFunction } from 'i18next'; import Record from 'Types/Record'; export const types = { @@ -10,6 +9,7 @@ export const types = { MOUSE_THRASHING: 'mouse_thrashing', TAP_RAGE: 'tap_rage', DEAD_CLICK: 'dead_click', + INCIDENTS: 'incidents', } as const; type TypeKeys = keyof typeof types; @@ -75,6 +75,13 @@ export const issues_types = [ name: 'Mouse Thrashing', icon: 'cursor-trash', }, + { + type: types.INCIDENTS, + visible: true, + order: 7, + name: 'Incidents', + icon: 'funnel/message-circle-warning', + } // { 'type': 'memory', 'visible': true, 'order': 4, 'name': 'High Memory', 'icon': 'funnel/sd-card' }, // { 'type': 'vault', 'visible': true, 'order': 5, 'name': 'Vault', 'icon': 'safe' }, // { 'type': 'bookmark', 'visible': true, 'order': 5, 'name': 'Bookmarks', 'icon': 'safe' }, diff --git a/frontend/app/types/session/session.ts b/frontend/app/types/session/session.ts index 55170ced8..20f8793d1 100644 --- a/frontend/app/types/session/session.ts +++ b/frontend/app/types/session/session.ts @@ -1,7 +1,7 @@ import { Duration } from 'luxon'; import { Note } from 'App/services/NotesService'; import { toJS } from 'mobx'; -import SessionEvent, { TYPES, EventData, InjectedEvent } from './event'; +import SessionEvent, { TYPES, EventData, InjectedEvent, Incident } from './event'; import StackEvent from './stackEvent'; import SessionError, { IError } from './error'; import Issue, { IIssue, types as issueTypes } from './issue'; @@ -281,6 +281,8 @@ export default class Session { frustrations: Array; + incidents: Array + timezone?: ISession['timezone']; platform: ISession['platform']; @@ -443,6 +445,7 @@ export default class Session { userEvents: any[] = [], stackEvents: any[] = [], userTestingEvents: any[] = [], + incidents: any[] = [], ) { const exceptions = (errors as IError[])?.map((e) => new SessionError(e)) || []; @@ -503,6 +506,11 @@ export default class Session { const frustrationList = [...frustrationEvents, ...frustrationIssues].sort(sortEvents) || []; + const incidentsList = incidents.sort((a, b) => a.startTime - b.startTime).map((i) => ({ + ...i, + time: i.startTime - this.startedAt, + })).map((i) => new Incident(i)); + const mixedEventsWithIssues = mergeEventLists( events, frustrationIssues.filter((i) => i.type !== issueTypes.DEAD_CLICK), @@ -521,6 +529,7 @@ export default class Session { this.frustrations = frustrationList; this.crashes = crashes || []; this.addedEvents = true; + this.incidents = incidentsList; return this; } diff --git a/mobs/messages.rb b/mobs/messages.rb index ab2c67353..d84230ed1 100644 --- a/mobs/messages.rb +++ b/mobs/messages.rb @@ -522,8 +522,8 @@ end message 85, 'Incident', :replayer => :devtools do string 'Label' - string 'StartTime' - string 'EndTime' + int 'StartTime' + int 'EndTime' end # 90-111 reserved iOS diff --git a/tracker/tracker/src/common/messages.gen.ts b/tracker/tracker/src/common/messages.gen.ts index 278d770f7..1b81ccb44 100644 --- a/tracker/tracker/src/common/messages.gen.ts +++ b/tracker/tracker/src/common/messages.gen.ts @@ -574,8 +574,8 @@ export type WSChannel = [ export type Incident = [ /*type:*/ Type.Incident, /*label:*/ string, - /*startTime:*/ string, - /*endTime:*/ string, + /*startTime:*/ number, + /*endTime:*/ number, ] export type InputChange = [ diff --git a/tracker/tracker/src/main/app/messages.gen.ts b/tracker/tracker/src/main/app/messages.gen.ts index 4abdef128..f659563a1 100644 --- a/tracker/tracker/src/main/app/messages.gen.ts +++ b/tracker/tracker/src/main/app/messages.gen.ts @@ -907,8 +907,8 @@ export function WSChannel( export function Incident( label: string, - startTime: string, - endTime: string, + startTime: number, + endTime: number, ): Messages.Incident { return [ Messages.Type.Incident, diff --git a/tracker/tracker/src/webworker/MessageEncoder.gen.ts b/tracker/tracker/src/webworker/MessageEncoder.gen.ts index adb0a651e..b16f825da 100644 --- a/tracker/tracker/src/webworker/MessageEncoder.gen.ts +++ b/tracker/tracker/src/webworker/MessageEncoder.gen.ts @@ -283,7 +283,7 @@ export default class MessageEncoder extends PrimitiveEncoder { break case Messages.Type.Incident: - return this.string(msg[1]) && this.string(msg[2]) && this.string(msg[3]) + return this.string(msg[1]) && this.int(msg[2]) && this.int(msg[3]) break case Messages.Type.InputChange: