added incidents

This commit is contained in:
Андрей Бабушкин 2025-05-13 16:25:20 +02:00
parent c6d64fc986
commit 5090891ee9
25 changed files with 352 additions and 196 deletions

View file

@ -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
}
}

View file

@ -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]
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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:

View file

@ -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:

View file

@ -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 (
<Incident
from={event.time}
to={event.endTime - event.startTime + event.time}
label={event.label}
onClick={onEventClick}
/>
)
}
if (isLocation) {
return (
<Event
@ -100,11 +111,19 @@ function EventGroupWrapper(props) {
);
};
const shadowColor = isSearched ? '#F0A930' : props.isPrev
? '#A7BFFF'
: props.isCurrent
? '#394EFF'
: 'transparent';
const shadowColor = useMemo(() => {
if (isSearched) {
return '#F0A930';
}
if (props.isPrev) {
return '#A7BFFF';
}
if (props.isCurrent) {
return '#394EFF';
}
return 'transparent';
}, [isSearched, props.isPrev, props.isCurrent]);
return (
<>
<div>
@ -172,4 +191,22 @@ function TabChange({ from, to, activeUrl, onClick }) {
);
};
function Incident({ label, onClick }) {
const { t } = useTranslation();
return (
<div
onClick={onClick}
className="pr-6 pl-4 py-2 relative user-select-none transition-all duration-200 cursor-pointer rounded-[3px] hover:bg-[var(--color-active-blue)] bg-[var(--color-white)]"
>
<div className='flex items-center py-2 gap-[10.5px]'>
<Icon name="console/warning" size={18} color="gray-dark" />
<div className="flex flex-col">
<span style={{ fontWeight: 500 }}>{t('Incident')}</span>
<span className="text-ellipsis overflow-hidden whitespace-nowrap max-w-full text-sm text-[var(--color-gray-medium)]">{label}</span>
</div>
</div>
</div>
);
};
export default observer(EventGroupWrapper);

View file

@ -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}

View file

@ -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() {
<div
/* @ts-ignore TODO */
key={`${e.key}_${e.time}`}
className={`absolute w-[2px] h-[10px] z-[3] pointer-events-none ${e.isHighlighted ? 'bg-[#f0a930]' : 'bg-[#394eff]'}`}
className={`absolute w-[2px] h-[10px] z-[4] pointer-events-none ${e.isHighlighted ? 'bg-[#f0a930]' : 'bg-[#394eff]'}`}
style={{ left: `${getTimelinePosition(e.time, scale)}%` }}
/>
))}
{incidents.map((i) => {
const width = getTimelineEventWidth(endTime, (i as any).time, (i as any).endTime - sessionStart);
return (
<Tooltip title={i.label} key={(i as any).startTime}>
<div
/* @ts-ignore TODO */
className={`absolute h-[10px] z-[3] bg-[#ff5454]`}
style={{
left: `${getTimelinePosition((i as any).time, scale)}%`,
width: typeof width === 'string' ? width : `${width}%`,
}}
/>
</Tooltip>
)
})}
</>
);
}

View file

@ -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;
}

View file

@ -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]: <Angry size={14} />,
[types.CRASH]: <Skull size={14} />,
[types.TAP_RAGE]: <Angry size={14} />,
[types.INCIDENTS]: <MessageCircleWarning size={14} />,
} as Record<string, any>;
function SessionTags() {

View file

@ -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<string, any>[];
@ -413,6 +400,7 @@ export default class SessionStore {
userEvents,
stackEvents,
userTesting,
incidents,
);
this.current = session;
this.eventsIndex = matching;

View file

@ -59,6 +59,7 @@ const SIMPLE_LIST_NAMES = [
'exceptions',
'profiles',
'frustrations',
'incidents',
] as const;
const MARKED_LIST_NAMES = [
'log',

View file

@ -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,

View file

@ -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 {

View file

@ -492,8 +492,8 @@ type TrWSChannel = [
type TrIncident = [
type: 85,
label: string,
startTime: string,
endTime: string,
startTime: number,
endTime: number,
]
type TrInputChange = [

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-message-circle-warning-icon lucide-message-circle-warning"><path d="M7.9 20A9 9 0 1 0 4 16.1L2 22Z"/><path d="M12 8v4"/><path d="M12 16h.01"/></svg>

After

Width:  |  Height:  |  Size: 350 B

View file

@ -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);
}

View file

@ -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' },

View file

@ -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<IIssue | InjectedEvent>;
incidents: Array<Incident>
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;
}

View file

@ -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

View file

@ -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 = [

View file

@ -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,

View file

@ -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: