diff --git a/backend/pkg/messages/messages.go b/backend/pkg/messages/messages.go index 8dab53ff0..bbe93230a 100644 --- a/backend/pkg/messages/messages.go +++ b/backend/pkg/messages/messages.go @@ -86,6 +86,7 @@ const ( MsgTabChange = 117 MsgTabData = 118 MsgCanvasNode = 119 + MsgTagTrigger = 120 MsgIssueEvent = 125 MsgSessionEnd = 126 MsgSessionSearch = 127 @@ -2301,6 +2302,27 @@ func (msg *CanvasNode) TypeID() int { return 119 } +type TagTrigger struct { + message + TagId int64 +} + +func (msg *TagTrigger) Encode() []byte { + buf := make([]byte, 11) + buf[0] = 120 + p := 1 + p = WriteInt(msg.TagId, buf, p) + return buf[:p] +} + +func (msg *TagTrigger) Decode() Message { + return msg +} + +func (msg *TagTrigger) TypeID() int { + return 120 +} + type IssueEvent struct { message MessageID uint64 diff --git a/backend/pkg/messages/read-message.go b/backend/pkg/messages/read-message.go index f71869977..05a937c1d 100644 --- a/backend/pkg/messages/read-message.go +++ b/backend/pkg/messages/read-message.go @@ -1401,6 +1401,15 @@ func DecodeCanvasNode(reader BytesReader) (Message, error) { return msg, err } +func DecodeTagTrigger(reader BytesReader) (Message, error) { + var err error = nil + msg := &TagTrigger{} + if msg.TagId, err = reader.ReadInt(); err != nil { + return nil, err + } + return msg, err +} + func DecodeIssueEvent(reader BytesReader) (Message, error) { var err error = nil msg := &IssueEvent{} @@ -2033,6 +2042,8 @@ func ReadMessage(t uint64, reader BytesReader) (Message, error) { return DecodeTabData(reader) case 119: return DecodeCanvasNode(reader) + case 120: + return DecodeTagTrigger(reader) case 125: return DecodeIssueEvent(reader) case 126: diff --git a/ee/connectors/msgcodec/messages.py b/ee/connectors/msgcodec/messages.py index 494af3793..c178fd8ef 100644 --- a/ee/connectors/msgcodec/messages.py +++ b/ee/connectors/msgcodec/messages.py @@ -808,6 +808,13 @@ class CanvasNode(Message): self.timestamp = timestamp +class TagTrigger(Message): + __id__ = 120 + + def __init__(self, tag_id): + self.tag_id = tag_id + + class IssueEvent(Message): __id__ = 125 diff --git a/ee/connectors/msgcodec/messages.pyx b/ee/connectors/msgcodec/messages.pyx index 7e8df67fc..fd7e64f84 100644 --- a/ee/connectors/msgcodec/messages.pyx +++ b/ee/connectors/msgcodec/messages.pyx @@ -1194,6 +1194,15 @@ cdef class CanvasNode(PyMessage): self.timestamp = timestamp +cdef class TagTrigger(PyMessage): + cdef public int __id__ + cdef public long tag_id + + def __init__(self, long tag_id): + self.__id__ = 120 + self.tag_id = tag_id + + cdef class IssueEvent(PyMessage): cdef public int __id__ cdef public unsigned long message_id diff --git a/ee/connectors/msgcodec/msgcodec.py b/ee/connectors/msgcodec/msgcodec.py index fe3a34324..4e39ee54a 100644 --- a/ee/connectors/msgcodec/msgcodec.py +++ b/ee/connectors/msgcodec/msgcodec.py @@ -727,6 +727,11 @@ class MessageCodec(Codec): timestamp=self.read_uint(reader) ) + if message_id == 120: + return TagTrigger( + tag_id=self.read_int(reader) + ) + if message_id == 125: return IssueEvent( message_id=self.read_uint(reader), diff --git a/ee/connectors/msgcodec/msgcodec.pyx b/ee/connectors/msgcodec/msgcodec.pyx index a9f47af3c..b17c9cc32 100644 --- a/ee/connectors/msgcodec/msgcodec.pyx +++ b/ee/connectors/msgcodec/msgcodec.pyx @@ -193,51 +193,51 @@ cdef class MessageCodec: if message_id == 0: return Timestamp( - timestamp=MessageCodec.read_uint(reader) + timestamp=self.read_uint(reader) ) if message_id == 1: return SessionStart( - timestamp=MessageCodec.read_uint(reader), - project_id=MessageCodec.read_uint(reader), - tracker_version=MessageCodec.read_string(reader), - rev_id=MessageCodec.read_string(reader), - user_uuid=MessageCodec.read_string(reader), - user_agent=MessageCodec.read_string(reader), - user_os=MessageCodec.read_string(reader), - user_os_version=MessageCodec.read_string(reader), - user_browser=MessageCodec.read_string(reader), - user_browser_version=MessageCodec.read_string(reader), - user_device=MessageCodec.read_string(reader), - user_device_type=MessageCodec.read_string(reader), - user_device_memory_size=MessageCodec.read_uint(reader), - user_device_heap_size=MessageCodec.read_uint(reader), - user_country=MessageCodec.read_string(reader), - user_id=MessageCodec.read_string(reader) + timestamp=self.read_uint(reader), + project_id=self.read_uint(reader), + tracker_version=self.read_string(reader), + rev_id=self.read_string(reader), + user_uuid=self.read_string(reader), + user_agent=self.read_string(reader), + user_os=self.read_string(reader), + user_os_version=self.read_string(reader), + user_browser=self.read_string(reader), + user_browser_version=self.read_string(reader), + user_device=self.read_string(reader), + user_device_type=self.read_string(reader), + user_device_memory_size=self.read_uint(reader), + user_device_heap_size=self.read_uint(reader), + user_country=self.read_string(reader), + user_id=self.read_string(reader) ) if message_id == 3: return SessionEndDeprecated( - timestamp=MessageCodec.read_uint(reader) + timestamp=self.read_uint(reader) ) if message_id == 4: return SetPageLocation( - url=MessageCodec.read_string(reader), - referrer=MessageCodec.read_string(reader), - navigation_start=MessageCodec.read_uint(reader) + url=self.read_string(reader), + referrer=self.read_string(reader), + navigation_start=self.read_uint(reader) ) if message_id == 5: return SetViewportSize( - width=MessageCodec.read_uint(reader), - height=MessageCodec.read_uint(reader) + width=self.read_uint(reader), + height=self.read_uint(reader) ) if message_id == 6: return SetViewportScroll( - x=MessageCodec.read_int(reader), - y=MessageCodec.read_int(reader) + x=self.read_int(reader), + y=self.read_int(reader) ) if message_id == 7: @@ -247,515 +247,515 @@ cdef class MessageCodec: if message_id == 8: return CreateElementNode( - id=MessageCodec.read_uint(reader), - parent_id=MessageCodec.read_uint(reader), - index=MessageCodec.read_uint(reader), - tag=MessageCodec.read_string(reader), - svg=MessageCodec.read_boolean(reader) + id=self.read_uint(reader), + parent_id=self.read_uint(reader), + index=self.read_uint(reader), + tag=self.read_string(reader), + svg=self.read_boolean(reader) ) if message_id == 9: return CreateTextNode( - id=MessageCodec.read_uint(reader), - parent_id=MessageCodec.read_uint(reader), - index=MessageCodec.read_uint(reader) + id=self.read_uint(reader), + parent_id=self.read_uint(reader), + index=self.read_uint(reader) ) if message_id == 10: return MoveNode( - id=MessageCodec.read_uint(reader), - parent_id=MessageCodec.read_uint(reader), - index=MessageCodec.read_uint(reader) + id=self.read_uint(reader), + parent_id=self.read_uint(reader), + index=self.read_uint(reader) ) if message_id == 11: return RemoveNode( - id=MessageCodec.read_uint(reader) + id=self.read_uint(reader) ) if message_id == 12: return SetNodeAttribute( - id=MessageCodec.read_uint(reader), - name=MessageCodec.read_string(reader), - value=MessageCodec.read_string(reader) + id=self.read_uint(reader), + name=self.read_string(reader), + value=self.read_string(reader) ) if message_id == 13: return RemoveNodeAttribute( - id=MessageCodec.read_uint(reader), - name=MessageCodec.read_string(reader) + id=self.read_uint(reader), + name=self.read_string(reader) ) if message_id == 14: return SetNodeData( - id=MessageCodec.read_uint(reader), - data=MessageCodec.read_string(reader) + id=self.read_uint(reader), + data=self.read_string(reader) ) if message_id == 15: return SetCSSData( - id=MessageCodec.read_uint(reader), - data=MessageCodec.read_string(reader) + id=self.read_uint(reader), + data=self.read_string(reader) ) if message_id == 16: return SetNodeScroll( - id=MessageCodec.read_uint(reader), - x=MessageCodec.read_int(reader), - y=MessageCodec.read_int(reader) + id=self.read_uint(reader), + x=self.read_int(reader), + y=self.read_int(reader) ) if message_id == 17: return SetInputTarget( - id=MessageCodec.read_uint(reader), - label=MessageCodec.read_string(reader) + id=self.read_uint(reader), + label=self.read_string(reader) ) if message_id == 18: return SetInputValue( - id=MessageCodec.read_uint(reader), - value=MessageCodec.read_string(reader), - mask=MessageCodec.read_int(reader) + id=self.read_uint(reader), + value=self.read_string(reader), + mask=self.read_int(reader) ) if message_id == 19: return SetInputChecked( - id=MessageCodec.read_uint(reader), - checked=MessageCodec.read_boolean(reader) + id=self.read_uint(reader), + checked=self.read_boolean(reader) ) if message_id == 20: return MouseMove( - x=MessageCodec.read_uint(reader), - y=MessageCodec.read_uint(reader) + x=self.read_uint(reader), + y=self.read_uint(reader) ) if message_id == 21: return NetworkRequestDeprecated( - type=MessageCodec.read_string(reader), - method=MessageCodec.read_string(reader), - url=MessageCodec.read_string(reader), - request=MessageCodec.read_string(reader), - response=MessageCodec.read_string(reader), - status=MessageCodec.read_uint(reader), - timestamp=MessageCodec.read_uint(reader), - duration=MessageCodec.read_uint(reader) + type=self.read_string(reader), + method=self.read_string(reader), + url=self.read_string(reader), + request=self.read_string(reader), + response=self.read_string(reader), + status=self.read_uint(reader), + timestamp=self.read_uint(reader), + duration=self.read_uint(reader) ) if message_id == 22: return ConsoleLog( - level=MessageCodec.read_string(reader), - value=MessageCodec.read_string(reader) + level=self.read_string(reader), + value=self.read_string(reader) ) if message_id == 23: return PageLoadTiming( - request_start=MessageCodec.read_uint(reader), - response_start=MessageCodec.read_uint(reader), - response_end=MessageCodec.read_uint(reader), - dom_content_loaded_event_start=MessageCodec.read_uint(reader), - dom_content_loaded_event_end=MessageCodec.read_uint(reader), - load_event_start=MessageCodec.read_uint(reader), - load_event_end=MessageCodec.read_uint(reader), - first_paint=MessageCodec.read_uint(reader), - first_contentful_paint=MessageCodec.read_uint(reader) + request_start=self.read_uint(reader), + response_start=self.read_uint(reader), + response_end=self.read_uint(reader), + dom_content_loaded_event_start=self.read_uint(reader), + dom_content_loaded_event_end=self.read_uint(reader), + load_event_start=self.read_uint(reader), + load_event_end=self.read_uint(reader), + first_paint=self.read_uint(reader), + first_contentful_paint=self.read_uint(reader) ) if message_id == 24: return PageRenderTiming( - speed_index=MessageCodec.read_uint(reader), - visually_complete=MessageCodec.read_uint(reader), - time_to_interactive=MessageCodec.read_uint(reader) + speed_index=self.read_uint(reader), + visually_complete=self.read_uint(reader), + time_to_interactive=self.read_uint(reader) ) if message_id == 25: return JSExceptionDeprecated( - name=MessageCodec.read_string(reader), - message=MessageCodec.read_string(reader), - payload=MessageCodec.read_string(reader) + name=self.read_string(reader), + message=self.read_string(reader), + payload=self.read_string(reader) ) if message_id == 26: return IntegrationEvent( - timestamp=MessageCodec.read_uint(reader), - source=MessageCodec.read_string(reader), - name=MessageCodec.read_string(reader), - message=MessageCodec.read_string(reader), - payload=MessageCodec.read_string(reader) + timestamp=self.read_uint(reader), + source=self.read_string(reader), + name=self.read_string(reader), + message=self.read_string(reader), + payload=self.read_string(reader) ) if message_id == 27: return CustomEvent( - name=MessageCodec.read_string(reader), - payload=MessageCodec.read_string(reader) + name=self.read_string(reader), + payload=self.read_string(reader) ) if message_id == 28: return UserID( - id=MessageCodec.read_string(reader) + id=self.read_string(reader) ) if message_id == 29: return UserAnonymousID( - id=MessageCodec.read_string(reader) + id=self.read_string(reader) ) if message_id == 30: return Metadata( - key=MessageCodec.read_string(reader), - value=MessageCodec.read_string(reader) + key=self.read_string(reader), + value=self.read_string(reader) ) if message_id == 31: return PageEvent( - message_id=MessageCodec.read_uint(reader), - timestamp=MessageCodec.read_uint(reader), - url=MessageCodec.read_string(reader), - referrer=MessageCodec.read_string(reader), - loaded=MessageCodec.read_boolean(reader), - request_start=MessageCodec.read_uint(reader), - response_start=MessageCodec.read_uint(reader), - response_end=MessageCodec.read_uint(reader), - dom_content_loaded_event_start=MessageCodec.read_uint(reader), - dom_content_loaded_event_end=MessageCodec.read_uint(reader), - load_event_start=MessageCodec.read_uint(reader), - load_event_end=MessageCodec.read_uint(reader), - first_paint=MessageCodec.read_uint(reader), - first_contentful_paint=MessageCodec.read_uint(reader), - speed_index=MessageCodec.read_uint(reader), - visually_complete=MessageCodec.read_uint(reader), - time_to_interactive=MessageCodec.read_uint(reader) + message_id=self.read_uint(reader), + timestamp=self.read_uint(reader), + url=self.read_string(reader), + referrer=self.read_string(reader), + loaded=self.read_boolean(reader), + request_start=self.read_uint(reader), + response_start=self.read_uint(reader), + response_end=self.read_uint(reader), + dom_content_loaded_event_start=self.read_uint(reader), + dom_content_loaded_event_end=self.read_uint(reader), + load_event_start=self.read_uint(reader), + load_event_end=self.read_uint(reader), + first_paint=self.read_uint(reader), + first_contentful_paint=self.read_uint(reader), + speed_index=self.read_uint(reader), + visually_complete=self.read_uint(reader), + time_to_interactive=self.read_uint(reader) ) if message_id == 32: return InputEvent( - message_id=MessageCodec.read_uint(reader), - timestamp=MessageCodec.read_uint(reader), - value=MessageCodec.read_string(reader), - value_masked=MessageCodec.read_boolean(reader), - label=MessageCodec.read_string(reader) + message_id=self.read_uint(reader), + timestamp=self.read_uint(reader), + value=self.read_string(reader), + value_masked=self.read_boolean(reader), + label=self.read_string(reader) ) if message_id == 37: return CSSInsertRule( - id=MessageCodec.read_uint(reader), - rule=MessageCodec.read_string(reader), - index=MessageCodec.read_uint(reader) + id=self.read_uint(reader), + rule=self.read_string(reader), + index=self.read_uint(reader) ) if message_id == 38: return CSSDeleteRule( - id=MessageCodec.read_uint(reader), - index=MessageCodec.read_uint(reader) + id=self.read_uint(reader), + index=self.read_uint(reader) ) if message_id == 39: return Fetch( - method=MessageCodec.read_string(reader), - url=MessageCodec.read_string(reader), - request=MessageCodec.read_string(reader), - response=MessageCodec.read_string(reader), - status=MessageCodec.read_uint(reader), - timestamp=MessageCodec.read_uint(reader), - duration=MessageCodec.read_uint(reader) + method=self.read_string(reader), + url=self.read_string(reader), + request=self.read_string(reader), + response=self.read_string(reader), + status=self.read_uint(reader), + timestamp=self.read_uint(reader), + duration=self.read_uint(reader) ) if message_id == 40: return Profiler( - name=MessageCodec.read_string(reader), - duration=MessageCodec.read_uint(reader), - args=MessageCodec.read_string(reader), - result=MessageCodec.read_string(reader) + name=self.read_string(reader), + duration=self.read_uint(reader), + args=self.read_string(reader), + result=self.read_string(reader) ) if message_id == 41: return OTable( - key=MessageCodec.read_string(reader), - value=MessageCodec.read_string(reader) + key=self.read_string(reader), + value=self.read_string(reader) ) if message_id == 42: return StateAction( - type=MessageCodec.read_string(reader) + type=self.read_string(reader) ) if message_id == 44: return Redux( - action=MessageCodec.read_string(reader), - state=MessageCodec.read_string(reader), - duration=MessageCodec.read_uint(reader) + action=self.read_string(reader), + state=self.read_string(reader), + duration=self.read_uint(reader) ) if message_id == 45: return Vuex( - mutation=MessageCodec.read_string(reader), - state=MessageCodec.read_string(reader) + mutation=self.read_string(reader), + state=self.read_string(reader) ) if message_id == 46: return MobX( - type=MessageCodec.read_string(reader), - payload=MessageCodec.read_string(reader) + type=self.read_string(reader), + payload=self.read_string(reader) ) if message_id == 47: return NgRx( - action=MessageCodec.read_string(reader), - state=MessageCodec.read_string(reader), - duration=MessageCodec.read_uint(reader) + action=self.read_string(reader), + state=self.read_string(reader), + duration=self.read_uint(reader) ) if message_id == 48: return GraphQL( - operation_kind=MessageCodec.read_string(reader), - operation_name=MessageCodec.read_string(reader), - variables=MessageCodec.read_string(reader), - response=MessageCodec.read_string(reader) + operation_kind=self.read_string(reader), + operation_name=self.read_string(reader), + variables=self.read_string(reader), + response=self.read_string(reader) ) if message_id == 49: return PerformanceTrack( - frames=MessageCodec.read_int(reader), - ticks=MessageCodec.read_int(reader), - total_js_heap_size=MessageCodec.read_uint(reader), - used_js_heap_size=MessageCodec.read_uint(reader) + frames=self.read_int(reader), + ticks=self.read_int(reader), + total_js_heap_size=self.read_uint(reader), + used_js_heap_size=self.read_uint(reader) ) if message_id == 50: return StringDict( - key=MessageCodec.read_uint(reader), - value=MessageCodec.read_string(reader) + key=self.read_uint(reader), + value=self.read_string(reader) ) if message_id == 51: return SetNodeAttributeDict( - id=MessageCodec.read_uint(reader), - name_key=MessageCodec.read_uint(reader), - value_key=MessageCodec.read_uint(reader) + id=self.read_uint(reader), + name_key=self.read_uint(reader), + value_key=self.read_uint(reader) ) if message_id == 53: return ResourceTimingDeprecated( - timestamp=MessageCodec.read_uint(reader), - duration=MessageCodec.read_uint(reader), - ttfb=MessageCodec.read_uint(reader), - header_size=MessageCodec.read_uint(reader), - encoded_body_size=MessageCodec.read_uint(reader), - decoded_body_size=MessageCodec.read_uint(reader), - url=MessageCodec.read_string(reader), - initiator=MessageCodec.read_string(reader) + timestamp=self.read_uint(reader), + duration=self.read_uint(reader), + ttfb=self.read_uint(reader), + header_size=self.read_uint(reader), + encoded_body_size=self.read_uint(reader), + decoded_body_size=self.read_uint(reader), + url=self.read_string(reader), + initiator=self.read_string(reader) ) if message_id == 54: return ConnectionInformation( - downlink=MessageCodec.read_uint(reader), - type=MessageCodec.read_string(reader) + downlink=self.read_uint(reader), + type=self.read_string(reader) ) if message_id == 55: return SetPageVisibility( - hidden=MessageCodec.read_boolean(reader) + hidden=self.read_boolean(reader) ) if message_id == 56: return PerformanceTrackAggr( - timestamp_start=MessageCodec.read_uint(reader), - timestamp_end=MessageCodec.read_uint(reader), - min_fps=MessageCodec.read_uint(reader), - avg_fps=MessageCodec.read_uint(reader), - max_fps=MessageCodec.read_uint(reader), - min_cpu=MessageCodec.read_uint(reader), - avg_cpu=MessageCodec.read_uint(reader), - max_cpu=MessageCodec.read_uint(reader), - min_total_js_heap_size=MessageCodec.read_uint(reader), - avg_total_js_heap_size=MessageCodec.read_uint(reader), - max_total_js_heap_size=MessageCodec.read_uint(reader), - min_used_js_heap_size=MessageCodec.read_uint(reader), - avg_used_js_heap_size=MessageCodec.read_uint(reader), - max_used_js_heap_size=MessageCodec.read_uint(reader) + timestamp_start=self.read_uint(reader), + timestamp_end=self.read_uint(reader), + min_fps=self.read_uint(reader), + avg_fps=self.read_uint(reader), + max_fps=self.read_uint(reader), + min_cpu=self.read_uint(reader), + avg_cpu=self.read_uint(reader), + max_cpu=self.read_uint(reader), + min_total_js_heap_size=self.read_uint(reader), + avg_total_js_heap_size=self.read_uint(reader), + max_total_js_heap_size=self.read_uint(reader), + min_used_js_heap_size=self.read_uint(reader), + avg_used_js_heap_size=self.read_uint(reader), + max_used_js_heap_size=self.read_uint(reader) ) if message_id == 57: return LoadFontFace( - parent_id=MessageCodec.read_uint(reader), - family=MessageCodec.read_string(reader), - source=MessageCodec.read_string(reader), - descriptors=MessageCodec.read_string(reader) + parent_id=self.read_uint(reader), + family=self.read_string(reader), + source=self.read_string(reader), + descriptors=self.read_string(reader) ) if message_id == 58: return SetNodeFocus( - id=MessageCodec.read_int(reader) + id=self.read_int(reader) ) if message_id == 59: return LongTask( - timestamp=MessageCodec.read_uint(reader), - duration=MessageCodec.read_uint(reader), - context=MessageCodec.read_uint(reader), - container_type=MessageCodec.read_uint(reader), - container_src=MessageCodec.read_string(reader), - container_id=MessageCodec.read_string(reader), - container_name=MessageCodec.read_string(reader) + timestamp=self.read_uint(reader), + duration=self.read_uint(reader), + context=self.read_uint(reader), + container_type=self.read_uint(reader), + container_src=self.read_string(reader), + container_id=self.read_string(reader), + container_name=self.read_string(reader) ) if message_id == 60: return SetNodeAttributeURLBased( - id=MessageCodec.read_uint(reader), - name=MessageCodec.read_string(reader), - value=MessageCodec.read_string(reader), - base_url=MessageCodec.read_string(reader) + id=self.read_uint(reader), + name=self.read_string(reader), + value=self.read_string(reader), + base_url=self.read_string(reader) ) if message_id == 61: return SetCSSDataURLBased( - id=MessageCodec.read_uint(reader), - data=MessageCodec.read_string(reader), - base_url=MessageCodec.read_string(reader) + id=self.read_uint(reader), + data=self.read_string(reader), + base_url=self.read_string(reader) ) if message_id == 62: return IssueEventDeprecated( - message_id=MessageCodec.read_uint(reader), - timestamp=MessageCodec.read_uint(reader), - type=MessageCodec.read_string(reader), - context_string=MessageCodec.read_string(reader), - context=MessageCodec.read_string(reader), - payload=MessageCodec.read_string(reader) + message_id=self.read_uint(reader), + timestamp=self.read_uint(reader), + type=self.read_string(reader), + context_string=self.read_string(reader), + context=self.read_string(reader), + payload=self.read_string(reader) ) if message_id == 63: return TechnicalInfo( - type=MessageCodec.read_string(reader), - value=MessageCodec.read_string(reader) + type=self.read_string(reader), + value=self.read_string(reader) ) if message_id == 64: return CustomIssue( - name=MessageCodec.read_string(reader), - payload=MessageCodec.read_string(reader) + name=self.read_string(reader), + payload=self.read_string(reader) ) if message_id == 66: return AssetCache( - url=MessageCodec.read_string(reader) + url=self.read_string(reader) ) if message_id == 67: return CSSInsertRuleURLBased( - id=MessageCodec.read_uint(reader), - rule=MessageCodec.read_string(reader), - index=MessageCodec.read_uint(reader), - base_url=MessageCodec.read_string(reader) + id=self.read_uint(reader), + rule=self.read_string(reader), + index=self.read_uint(reader), + base_url=self.read_string(reader) ) if message_id == 69: return MouseClick( - id=MessageCodec.read_uint(reader), - hesitation_time=MessageCodec.read_uint(reader), - label=MessageCodec.read_string(reader), - selector=MessageCodec.read_string(reader) + id=self.read_uint(reader), + hesitation_time=self.read_uint(reader), + label=self.read_string(reader), + selector=self.read_string(reader) ) if message_id == 70: return CreateIFrameDocument( - frame_id=MessageCodec.read_uint(reader), - id=MessageCodec.read_uint(reader) + frame_id=self.read_uint(reader), + id=self.read_uint(reader) ) if message_id == 71: return AdoptedSSReplaceURLBased( - sheet_id=MessageCodec.read_uint(reader), - text=MessageCodec.read_string(reader), - base_url=MessageCodec.read_string(reader) + sheet_id=self.read_uint(reader), + text=self.read_string(reader), + base_url=self.read_string(reader) ) if message_id == 72: return AdoptedSSReplace( - sheet_id=MessageCodec.read_uint(reader), - text=MessageCodec.read_string(reader) + sheet_id=self.read_uint(reader), + text=self.read_string(reader) ) if message_id == 73: return AdoptedSSInsertRuleURLBased( - sheet_id=MessageCodec.read_uint(reader), - rule=MessageCodec.read_string(reader), - index=MessageCodec.read_uint(reader), - base_url=MessageCodec.read_string(reader) + sheet_id=self.read_uint(reader), + rule=self.read_string(reader), + index=self.read_uint(reader), + base_url=self.read_string(reader) ) if message_id == 74: return AdoptedSSInsertRule( - sheet_id=MessageCodec.read_uint(reader), - rule=MessageCodec.read_string(reader), - index=MessageCodec.read_uint(reader) + sheet_id=self.read_uint(reader), + rule=self.read_string(reader), + index=self.read_uint(reader) ) if message_id == 75: return AdoptedSSDeleteRule( - sheet_id=MessageCodec.read_uint(reader), - index=MessageCodec.read_uint(reader) + sheet_id=self.read_uint(reader), + index=self.read_uint(reader) ) if message_id == 76: return AdoptedSSAddOwner( - sheet_id=MessageCodec.read_uint(reader), - id=MessageCodec.read_uint(reader) + sheet_id=self.read_uint(reader), + id=self.read_uint(reader) ) if message_id == 77: return AdoptedSSRemoveOwner( - sheet_id=MessageCodec.read_uint(reader), - id=MessageCodec.read_uint(reader) + sheet_id=self.read_uint(reader), + id=self.read_uint(reader) ) if message_id == 78: return JSException( - name=MessageCodec.read_string(reader), - message=MessageCodec.read_string(reader), - payload=MessageCodec.read_string(reader), - metadata=MessageCodec.read_string(reader) + name=self.read_string(reader), + message=self.read_string(reader), + payload=self.read_string(reader), + metadata=self.read_string(reader) ) if message_id == 79: return Zustand( - mutation=MessageCodec.read_string(reader), - state=MessageCodec.read_string(reader) + mutation=self.read_string(reader), + state=self.read_string(reader) ) if message_id == 80: return BatchMeta( - page_no=MessageCodec.read_uint(reader), - first_index=MessageCodec.read_uint(reader), - timestamp=MessageCodec.read_int(reader) + page_no=self.read_uint(reader), + first_index=self.read_uint(reader), + timestamp=self.read_int(reader) ) if message_id == 81: return BatchMetadata( - version=MessageCodec.read_uint(reader), - page_no=MessageCodec.read_uint(reader), - first_index=MessageCodec.read_uint(reader), - timestamp=MessageCodec.read_int(reader), - location=MessageCodec.read_string(reader) + version=self.read_uint(reader), + page_no=self.read_uint(reader), + first_index=self.read_uint(reader), + timestamp=self.read_int(reader), + location=self.read_string(reader) ) if message_id == 82: return PartitionedMessage( - part_no=MessageCodec.read_uint(reader), - part_total=MessageCodec.read_uint(reader) + part_no=self.read_uint(reader), + part_total=self.read_uint(reader) ) if message_id == 83: return NetworkRequest( - type=MessageCodec.read_string(reader), - method=MessageCodec.read_string(reader), - url=MessageCodec.read_string(reader), - request=MessageCodec.read_string(reader), - response=MessageCodec.read_string(reader), - status=MessageCodec.read_uint(reader), - timestamp=MessageCodec.read_uint(reader), - duration=MessageCodec.read_uint(reader), - transferred_body_size=MessageCodec.read_uint(reader) + type=self.read_string(reader), + method=self.read_string(reader), + url=self.read_string(reader), + request=self.read_string(reader), + response=self.read_string(reader), + status=self.read_uint(reader), + timestamp=self.read_uint(reader), + duration=self.read_uint(reader), + transferred_body_size=self.read_uint(reader) ) if message_id == 84: @@ -770,256 +770,261 @@ cdef class MessageCodec: if message_id == 112: return InputChange( - id=MessageCodec.read_uint(reader), - value=MessageCodec.read_string(reader), - value_masked=MessageCodec.read_boolean(reader), - label=MessageCodec.read_string(reader), - hesitation_time=MessageCodec.read_int(reader), - input_duration=MessageCodec.read_int(reader) + id=self.read_uint(reader), + value=self.read_string(reader), + value_masked=self.read_boolean(reader), + label=self.read_string(reader), + hesitation_time=self.read_int(reader), + input_duration=self.read_int(reader) ) if message_id == 113: return SelectionChange( - selection_start=MessageCodec.read_uint(reader), - selection_end=MessageCodec.read_uint(reader), - selection=MessageCodec.read_string(reader) + selection_start=self.read_uint(reader), + selection_end=self.read_uint(reader), + selection=self.read_string(reader) ) if message_id == 114: return MouseThrashing( - timestamp=MessageCodec.read_uint(reader) + timestamp=self.read_uint(reader) ) if message_id == 115: return UnbindNodes( - total_removed_percent=MessageCodec.read_uint(reader) + total_removed_percent=self.read_uint(reader) ) if message_id == 116: return ResourceTiming( - timestamp=MessageCodec.read_uint(reader), - duration=MessageCodec.read_uint(reader), - ttfb=MessageCodec.read_uint(reader), - header_size=MessageCodec.read_uint(reader), - encoded_body_size=MessageCodec.read_uint(reader), - decoded_body_size=MessageCodec.read_uint(reader), - url=MessageCodec.read_string(reader), - initiator=MessageCodec.read_string(reader), - transferred_size=MessageCodec.read_uint(reader), - cached=MessageCodec.read_boolean(reader) + timestamp=self.read_uint(reader), + duration=self.read_uint(reader), + ttfb=self.read_uint(reader), + header_size=self.read_uint(reader), + encoded_body_size=self.read_uint(reader), + decoded_body_size=self.read_uint(reader), + url=self.read_string(reader), + initiator=self.read_string(reader), + transferred_size=self.read_uint(reader), + cached=self.read_boolean(reader) ) if message_id == 117: return TabChange( - tab_id=MessageCodec.read_string(reader) + tab_id=self.read_string(reader) ) if message_id == 118: return TabData( - tab_id=MessageCodec.read_string(reader) + tab_id=self.read_string(reader) ) if message_id == 119: return CanvasNode( - node_id=MessageCodec.read_string(reader), - timestamp=MessageCodec.read_uint(reader) + node_id=self.read_string(reader), + timestamp=self.read_uint(reader) + ) + + if message_id == 120: + return TagTrigger( + tag_id=self.read_int(reader) ) if message_id == 125: return IssueEvent( - message_id=MessageCodec.read_uint(reader), - timestamp=MessageCodec.read_uint(reader), - type=MessageCodec.read_string(reader), - context_string=MessageCodec.read_string(reader), - context=MessageCodec.read_string(reader), - payload=MessageCodec.read_string(reader), - url=MessageCodec.read_string(reader) + message_id=self.read_uint(reader), + timestamp=self.read_uint(reader), + type=self.read_string(reader), + context_string=self.read_string(reader), + context=self.read_string(reader), + payload=self.read_string(reader), + url=self.read_string(reader) ) if message_id == 126: return SessionEnd( - timestamp=MessageCodec.read_uint(reader), - encryption_key=MessageCodec.read_string(reader) + timestamp=self.read_uint(reader), + encryption_key=self.read_string(reader) ) if message_id == 127: return SessionSearch( - timestamp=MessageCodec.read_uint(reader), - partition=MessageCodec.read_uint(reader) + timestamp=self.read_uint(reader), + partition=self.read_uint(reader) ) if message_id == 90: return IOSSessionStart( - timestamp=MessageCodec.read_uint(reader), - project_id=MessageCodec.read_uint(reader), - tracker_version=MessageCodec.read_string(reader), - rev_id=MessageCodec.read_string(reader), - user_uuid=MessageCodec.read_string(reader), - user_os=MessageCodec.read_string(reader), - user_os_version=MessageCodec.read_string(reader), - user_device=MessageCodec.read_string(reader), - user_device_type=MessageCodec.read_string(reader), - user_country=MessageCodec.read_string(reader) + timestamp=self.read_uint(reader), + project_id=self.read_uint(reader), + tracker_version=self.read_string(reader), + rev_id=self.read_string(reader), + user_uuid=self.read_string(reader), + user_os=self.read_string(reader), + user_os_version=self.read_string(reader), + user_device=self.read_string(reader), + user_device_type=self.read_string(reader), + user_country=self.read_string(reader) ) if message_id == 91: return IOSSessionEnd( - timestamp=MessageCodec.read_uint(reader) + timestamp=self.read_uint(reader) ) if message_id == 92: return IOSMetadata( - timestamp=MessageCodec.read_uint(reader), - length=MessageCodec.read_uint(reader), - key=MessageCodec.read_string(reader), - value=MessageCodec.read_string(reader) + timestamp=self.read_uint(reader), + length=self.read_uint(reader), + key=self.read_string(reader), + value=self.read_string(reader) ) if message_id == 93: return IOSEvent( - timestamp=MessageCodec.read_uint(reader), - length=MessageCodec.read_uint(reader), - name=MessageCodec.read_string(reader), - payload=MessageCodec.read_string(reader) + timestamp=self.read_uint(reader), + length=self.read_uint(reader), + name=self.read_string(reader), + payload=self.read_string(reader) ) if message_id == 94: return IOSUserID( - timestamp=MessageCodec.read_uint(reader), - length=MessageCodec.read_uint(reader), - id=MessageCodec.read_string(reader) + timestamp=self.read_uint(reader), + length=self.read_uint(reader), + id=self.read_string(reader) ) if message_id == 95: return IOSUserAnonymousID( - timestamp=MessageCodec.read_uint(reader), - length=MessageCodec.read_uint(reader), - id=MessageCodec.read_string(reader) + timestamp=self.read_uint(reader), + length=self.read_uint(reader), + id=self.read_string(reader) ) if message_id == 96: return IOSScreenChanges( - timestamp=MessageCodec.read_uint(reader), - length=MessageCodec.read_uint(reader), - x=MessageCodec.read_uint(reader), - y=MessageCodec.read_uint(reader), - width=MessageCodec.read_uint(reader), - height=MessageCodec.read_uint(reader) + timestamp=self.read_uint(reader), + length=self.read_uint(reader), + x=self.read_uint(reader), + y=self.read_uint(reader), + width=self.read_uint(reader), + height=self.read_uint(reader) ) if message_id == 97: return IOSCrash( - timestamp=MessageCodec.read_uint(reader), - length=MessageCodec.read_uint(reader), - name=MessageCodec.read_string(reader), - reason=MessageCodec.read_string(reader), - stacktrace=MessageCodec.read_string(reader) + timestamp=self.read_uint(reader), + length=self.read_uint(reader), + name=self.read_string(reader), + reason=self.read_string(reader), + stacktrace=self.read_string(reader) ) if message_id == 98: return IOSViewComponentEvent( - timestamp=MessageCodec.read_uint(reader), - length=MessageCodec.read_uint(reader), - screen_name=MessageCodec.read_string(reader), - view_name=MessageCodec.read_string(reader), - visible=MessageCodec.read_boolean(reader) + timestamp=self.read_uint(reader), + length=self.read_uint(reader), + screen_name=self.read_string(reader), + view_name=self.read_string(reader), + visible=self.read_boolean(reader) ) if message_id == 100: return IOSClickEvent( - timestamp=MessageCodec.read_uint(reader), - length=MessageCodec.read_uint(reader), - label=MessageCodec.read_string(reader), - x=MessageCodec.read_uint(reader), - y=MessageCodec.read_uint(reader) + timestamp=self.read_uint(reader), + length=self.read_uint(reader), + label=self.read_string(reader), + x=self.read_uint(reader), + y=self.read_uint(reader) ) if message_id == 101: return IOSInputEvent( - timestamp=MessageCodec.read_uint(reader), - length=MessageCodec.read_uint(reader), - value=MessageCodec.read_string(reader), - value_masked=MessageCodec.read_boolean(reader), - label=MessageCodec.read_string(reader) + timestamp=self.read_uint(reader), + length=self.read_uint(reader), + value=self.read_string(reader), + value_masked=self.read_boolean(reader), + label=self.read_string(reader) ) if message_id == 102: return IOSPerformanceEvent( - timestamp=MessageCodec.read_uint(reader), - length=MessageCodec.read_uint(reader), - name=MessageCodec.read_string(reader), - value=MessageCodec.read_uint(reader) + timestamp=self.read_uint(reader), + length=self.read_uint(reader), + name=self.read_string(reader), + value=self.read_uint(reader) ) if message_id == 103: return IOSLog( - timestamp=MessageCodec.read_uint(reader), - length=MessageCodec.read_uint(reader), - severity=MessageCodec.read_string(reader), - content=MessageCodec.read_string(reader) + timestamp=self.read_uint(reader), + length=self.read_uint(reader), + severity=self.read_string(reader), + content=self.read_string(reader) ) if message_id == 104: return IOSInternalError( - timestamp=MessageCodec.read_uint(reader), - length=MessageCodec.read_uint(reader), - content=MessageCodec.read_string(reader) + timestamp=self.read_uint(reader), + length=self.read_uint(reader), + content=self.read_string(reader) ) if message_id == 105: return IOSNetworkCall( - timestamp=MessageCodec.read_uint(reader), - length=MessageCodec.read_uint(reader), - type=MessageCodec.read_string(reader), - method=MessageCodec.read_string(reader), - url=MessageCodec.read_string(reader), - request=MessageCodec.read_string(reader), - response=MessageCodec.read_string(reader), - status=MessageCodec.read_uint(reader), - duration=MessageCodec.read_uint(reader) + timestamp=self.read_uint(reader), + length=self.read_uint(reader), + type=self.read_string(reader), + method=self.read_string(reader), + url=self.read_string(reader), + request=self.read_string(reader), + response=self.read_string(reader), + status=self.read_uint(reader), + duration=self.read_uint(reader) ) if message_id == 106: return IOSSwipeEvent( - timestamp=MessageCodec.read_uint(reader), - length=MessageCodec.read_uint(reader), - label=MessageCodec.read_string(reader), - x=MessageCodec.read_uint(reader), - y=MessageCodec.read_uint(reader), - direction=MessageCodec.read_string(reader) + timestamp=self.read_uint(reader), + length=self.read_uint(reader), + label=self.read_string(reader), + x=self.read_uint(reader), + y=self.read_uint(reader), + direction=self.read_string(reader) ) if message_id == 107: return IOSBatchMeta( - timestamp=MessageCodec.read_uint(reader), - length=MessageCodec.read_uint(reader), - first_index=MessageCodec.read_uint(reader) + timestamp=self.read_uint(reader), + length=self.read_uint(reader), + first_index=self.read_uint(reader) ) if message_id == 110: return IOSPerformanceAggregated( - timestamp_start=MessageCodec.read_uint(reader), - timestamp_end=MessageCodec.read_uint(reader), - min_fps=MessageCodec.read_uint(reader), - avg_fps=MessageCodec.read_uint(reader), - max_fps=MessageCodec.read_uint(reader), - min_cpu=MessageCodec.read_uint(reader), - avg_cpu=MessageCodec.read_uint(reader), - max_cpu=MessageCodec.read_uint(reader), - min_memory=MessageCodec.read_uint(reader), - avg_memory=MessageCodec.read_uint(reader), - max_memory=MessageCodec.read_uint(reader), - min_battery=MessageCodec.read_uint(reader), - avg_battery=MessageCodec.read_uint(reader), - max_battery=MessageCodec.read_uint(reader) + timestamp_start=self.read_uint(reader), + timestamp_end=self.read_uint(reader), + min_fps=self.read_uint(reader), + avg_fps=self.read_uint(reader), + max_fps=self.read_uint(reader), + min_cpu=self.read_uint(reader), + avg_cpu=self.read_uint(reader), + max_cpu=self.read_uint(reader), + min_memory=self.read_uint(reader), + avg_memory=self.read_uint(reader), + max_memory=self.read_uint(reader), + min_battery=self.read_uint(reader), + avg_battery=self.read_uint(reader), + max_battery=self.read_uint(reader) ) if message_id == 111: return IOSIssueEvent( - timestamp=MessageCodec.read_uint(reader), - type=MessageCodec.read_string(reader), - context_string=MessageCodec.read_string(reader), - context=MessageCodec.read_string(reader), - payload=MessageCodec.read_string(reader) + timestamp=self.read_uint(reader), + type=self.read_string(reader), + context_string=self.read_string(reader), + context=self.read_string(reader), + payload=self.read_string(reader) ) diff --git a/frontend/app/api_client.ts b/frontend/app/api_client.ts index 151f12826..42ddcd1d3 100644 --- a/frontend/app/api_client.ts +++ b/frontend/app/api_client.ts @@ -30,7 +30,8 @@ const siteIdRequiredPaths: string[] = [ '/notes', '/feature-flags', '/check-recording-status', - '/usability-tests' + '/usability-tests', + '/tags' ]; export const clean = (obj: any, forbiddenValues: any[] = [undefined, '']): any => { diff --git a/frontend/app/components/FFlags/FFlagsListHeader.tsx b/frontend/app/components/FFlags/FFlagsListHeader.tsx index 1ff06259e..673aa0456 100644 --- a/frontend/app/components/FFlags/FFlagsListHeader.tsx +++ b/frontend/app/components/FFlags/FFlagsListHeader.tsx @@ -3,7 +3,6 @@ import { Button, PageTitle } from 'UI' import FFlagsSearch from "Components/FFlags/FFlagsSearch"; import { useHistory } from "react-router"; import { newFFlag, withSiteId } from 'App/routes'; -import { observer } from 'mobx-react-lite'; function FFlagsListHeader({ siteId }: { siteId: string }) { const history = useHistory(); diff --git a/frontend/app/components/Session/Player/ClickMapRenderer/Renderer.tsx b/frontend/app/components/Session/Player/ClickMapRenderer/Renderer.tsx index 29da5800c..0161009c3 100644 --- a/frontend/app/components/Session/Player/ClickMapRenderer/Renderer.tsx +++ b/frontend/app/components/Session/Player/ClickMapRenderer/Renderer.tsx @@ -24,7 +24,7 @@ function Player() { >
-
+
); diff --git a/frontend/app/components/Session/Player/LivePlayer/LivePlayerInst.tsx b/frontend/app/components/Session/Player/LivePlayer/LivePlayerInst.tsx index 389b3a027..930a368f4 100644 --- a/frontend/app/components/Session/Player/LivePlayer/LivePlayerInst.tsx +++ b/frontend/app/components/Session/Player/LivePlayer/LivePlayerInst.tsx @@ -52,7 +52,7 @@ function Player(props: IProps) { >
-
+
{bottomBlock === CONSOLE ? (
diff --git a/frontend/app/components/Session/Player/ReplayPlayer/PlayerInst.tsx b/frontend/app/components/Session/Player/ReplayPlayer/PlayerInst.tsx index ceecda380..ee6caf956 100644 --- a/frontend/app/components/Session/Player/ReplayPlayer/PlayerInst.tsx +++ b/frontend/app/components/Session/Player/ReplayPlayer/PlayerInst.tsx @@ -33,6 +33,7 @@ import ConsolePanel from 'Shared/DevTools/ConsolePanel'; import ProfilerPanel from 'Shared/DevTools/ProfilerPanel'; import { PlayerContext } from 'App/components/Session/playerContext'; import { debounce } from 'App/utils'; +import { observer } from 'mobx-react-lite'; interface IProps { fullView: boolean; @@ -109,6 +110,7 @@ function Player(props: IProps) { document.addEventListener('mousemove', handleMouseMove); document.addEventListener('mouseup', handleMouseUp); }; + const isInspMode = playerContext.store.get().inspectorMode; return (
}
-
+
{!fullscreen && !!bottomBlock && (
} {bottomBlock === GRAPHQL && } {bottomBlock === EXCEPTIONS && } - {bottomBlock === INSPECTOR && }
)} {!fullView ? ( @@ -168,4 +169,4 @@ export default connect( fullscreenOff, updateLastPlayedSession, } -)(Player); +)(observer(Player)); diff --git a/frontend/app/components/Session/Player/TagWatch/SaveModal.tsx b/frontend/app/components/Session/Player/TagWatch/SaveModal.tsx new file mode 100644 index 000000000..3fa29e4ef --- /dev/null +++ b/frontend/app/components/Session/Player/TagWatch/SaveModal.tsx @@ -0,0 +1,69 @@ +import React from 'react'; +import { Button, Checkbox, Input } from 'antd'; +import { useHistory } from 'react-router-dom'; +import { withSiteId, sessions } from 'App/routes'; +import store from 'App/store'; + +interface Props { + onSave: (name: string, ignoreClRage: boolean, ignoreDeadCl: boolean) => Promise; + hideModal: () => void; +} + +function SaveModal({ onSave, hideModal }: Props) { + const history = useHistory(); + const [name, setName] = React.useState(''); + const [ignoreClRage, setIgnoreClRage] = React.useState(false); + const [ignoreDeadCl, setIgnoreDeadCl] = React.useState(false); + + const save = () => { + void onSave(name, ignoreClRage, ignoreDeadCl); + hideModal(); + }; + const saveAndOpen = () => { + onSave(name, ignoreClRage, ignoreDeadCl).then((tagId) => { + hideModal(); + const siteId = store.getState().getIn(['site', 'siteId']); + history.push(withSiteId(sessions({ tnw: `is|${tagId}`, range: 'LAST_24_HOURS' }), siteId)); + }); + }; + return ( +
+
Tag Element
+
+
+
Name
+ setName(e.target.value)} + /> +
+
+
Ignore following actions on this element
+
+ setIgnoreClRage(e.target.checked)}> + Click Rage + + setIgnoreDeadCl(e.target.checked)}> + Dead Click + +
+
+
+
+ + + +
+
+ ); +} + +export default SaveModal; diff --git a/frontend/app/components/Session/Player/TagWatch/TagWatch.tsx b/frontend/app/components/Session/Player/TagWatch/TagWatch.tsx new file mode 100644 index 000000000..375bf2dad --- /dev/null +++ b/frontend/app/components/Session/Player/TagWatch/TagWatch.tsx @@ -0,0 +1,93 @@ +import { useStore } from 'App/mstore'; +import SaveModal from 'Components/Session/Player/TagWatch/SaveModal'; +import React from 'react'; +import { PlayerContext } from 'Components/Session/playerContext'; +import { Button, Input } from 'antd'; +import { CopyButton } from 'UI'; +import { SearchOutlined, ZoomInOutlined } from '@ant-design/icons'; +import { observer } from 'mobx-react-lite'; +import { useModal } from 'App/components/Modal'; +import { toast } from 'react-toastify'; + +function TagWatch() { + const { tagWatchStore } = useStore(); + const [selector, setSelector] = React.useState(''); + const { store, player } = React.useContext(PlayerContext); + const { showModal, hideModal } = useModal(); + + const tagSelector = store.get().tagSelector; + + React.useEffect(() => { + player.pause(); + player.toggleInspectorMode(true); + player.scale(); + + return () => { + player.toggleInspectorMode(false); + player.scale(); + }; + }, []); + + React.useEffect(() => { + if (tagSelector !== '' && tagSelector !== selector) { + setSelector(tagSelector); + } + }, [tagSelector]); + + React.useEffect(() => { + if (selector !== tagSelector) { + player.markBySelector(selector); + } + }, [selector]); + + const onSave = async (name: string, ignoreClRage: boolean, ignoreDeadCl: boolean) => { + try { + const tag = await tagWatchStore.createTag({ + name, + selector, + ignoreClickRage: ignoreClRage, + ignoreDeadClick: ignoreDeadCl, + }); + // @ts-ignore + toast.success('Tag created'); + setSelector(''); + return tag + } catch { + // @ts-ignore + toast.error('Failed to create tag'); + } + }; + const openSaveModal = () => { + if (selector === '') { + return; + } + showModal(, { right: true, width: 400 }); + }; + return ( +
+
+
Element Selector
+ +
+ setSelector(e.target.value)} /> + +
+ Create and filter sessions by ‘watch elements’ to determine if they rendered or not. +
+
+ +
+ ); +} + +export default observer(TagWatch); diff --git a/frontend/app/components/Session/Player/TagWatch/index.ts b/frontend/app/components/Session/Player/TagWatch/index.ts new file mode 100644 index 000000000..9929e023f --- /dev/null +++ b/frontend/app/components/Session/Player/TagWatch/index.ts @@ -0,0 +1 @@ +export { default } from './TagWatch'; \ No newline at end of file diff --git a/frontend/app/components/Session/RightBlock.tsx b/frontend/app/components/Session/RightBlock.tsx index ed1ea1d63..dd082681d 100644 --- a/frontend/app/components/Session/RightBlock.tsx +++ b/frontend/app/components/Session/RightBlock.tsx @@ -1,30 +1,40 @@ -import React from 'react' +import React from 'react'; import EventsBlock from '../Session_/EventsBlock'; -import PageInsightsPanel from '../Session_/PageInsightsPanel/PageInsightsPanel' +import PageInsightsPanel from '../Session_/PageInsightsPanel/PageInsightsPanel'; +import TagWatch from "Components/Session/Player/TagWatch"; import cn from 'classnames'; import stl from './rightblock.module.css'; -function RightBlock(props: any) { - const { activeTab } = props; - - if (activeTab === 'EVENTS') { - return ( -
- -
- ) +function RightBlock({ + activeTab, + setActiveTab, +}: { + activeTab: string; + setActiveTab: (tab: string) => void; +}) { + switch (activeTab) { + case 'EVENTS': + return ( +
+ +
+ ); + case 'CLICKMAP': + return ( +
+ +
+ ); + case 'INSPECTOR': + return ( +
+ +
+ ); + default: + return null; } - if (activeTab === 'CLICKMAP') { - return ( -
- -
- ) - } - return null } -export default RightBlock +export default RightBlock; diff --git a/frontend/app/components/Session/Tabs/Tabs.tsx b/frontend/app/components/Session/Tabs/Tabs.tsx index 87bb8bb39..91006feae 100644 --- a/frontend/app/components/Session/Tabs/Tabs.tsx +++ b/frontend/app/components/Session/Tabs/Tabs.tsx @@ -1,31 +1,48 @@ import React from 'react'; import cn from 'classnames'; import stl from './tabs.module.css'; +import { Segmented } from 'antd'; +import { Icon } from 'UI' interface Props { - tabs: Array; - active: string; - onClick: (key: any) => void; - border?: boolean; - className?: string; + tabs: Array; + active: string; + onClick: (key: any) => void; + border?: boolean; + className?: string; } -const Tabs = ({ tabs, active, onClick, border = true, className }: Props) => ( -
- { tabs.map(({ key, text, hidden = false, disabled = false }) => ( - - ))} -
-); +const iconMap = { + "INSPECTOR": 'filters/tag-element', + "CLICKMAP": 'mouse-pointer-click', + 'EVENTS': 'user-switch' +} as const + +const Tabs = ({ tabs, active, onClick, border = true, className }: Props) => { + console.log(tabs) + return ( +
+
+ ); +}; Tabs.displayName = 'Tabs'; diff --git a/frontend/app/components/Session/Tabs/tabs.module.css b/frontend/app/components/Session/Tabs/tabs.module.css index 55f50749c..352a60a10 100644 --- a/frontend/app/components/Session/Tabs/tabs.module.css +++ b/frontend/app/components/Session/Tabs/tabs.module.css @@ -1,14 +1,17 @@ .tabs { display: flex; + height: 100%; + width: 100%; justify-content: space-around; align-items: center; + font-size: 16px; &.bordered { - border-bottom: solid thin $gray-light; + border-bottom: solid thin $gray-light; } - } +} - .tab { - padding: 14px 15px; +.tab { + padding: 14px 0; text-align: center; text-transform: uppercase; flex: 1; @@ -19,16 +22,16 @@ white-space: nowrap; &:hover { - color: $teal; + color: $teal; } &.active { - color: $teal; - border-bottom: solid thin $teal; + color: $teal; + border-bottom: solid thin $teal; } - } +} - .disabled { +.disabled { pointer-events: none; opacity: 0.5; - } +} diff --git a/frontend/app/components/Session/WebPlayer.tsx b/frontend/app/components/Session/WebPlayer.tsx index d11f9b501..a0dcaa3b6 100644 --- a/frontend/app/components/Session/WebPlayer.tsx +++ b/frontend/app/components/Session/WebPlayer.tsx @@ -17,8 +17,9 @@ import { useParams } from 'react-router-dom'; import { toast } from 'react-toastify'; const TABS = { - EVENTS: 'User Events', + EVENTS: 'Activity', CLICKMAP: 'Click Map', + INSPECTOR: 'Tag', }; const UXTTABS = { EVENTS: TABS.EVENTS diff --git a/frontend/app/components/Session_/Player/Controls/Controls.tsx b/frontend/app/components/Session_/Player/Controls/Controls.tsx index 94a08f197..e9bff24b7 100644 --- a/frontend/app/components/Session_/Player/Controls/Controls.tsx +++ b/frontend/app/components/Session_/Player/Controls/Controls.tsx @@ -2,7 +2,7 @@ import { useStore } from "App/mstore"; import React from 'react'; import cn from 'classnames'; import { connect } from 'react-redux'; -import {MarkedTarget, selectStorageType, STORAGE_TYPES, StorageType} from 'Player'; +import { selectStorageType, STORAGE_TYPES, StorageType } from 'Player'; import { PlayButton, PlayingState, FullScreenButton } from 'App/player-ui' import { Tooltip } from 'UI'; @@ -131,13 +131,8 @@ function Controls(props: any) { }; const toggleBottomTools = (blockName: number) => { - if (blockName === INSPECTOR) { - // player.toggleInspectorMode(false); - bottomBlock && toggleBottomBlock(); - } else { - // player.toggleInspectorMode(false); + player.toggleInspectorMode(false); toggleBottomBlock(blockName); - } }; const state = completed ? PlayingState.Completed : playing ? PlayingState.Playing : PlayingState.Paused diff --git a/frontend/app/components/Session_/Player/player.module.css b/frontend/app/components/Session_/Player/player.module.css index 8251eaee0..d03ab1bc1 100644 --- a/frontend/app/components/Session_/Player/player.module.css +++ b/frontend/app/components/Session_/Player/player.module.css @@ -15,8 +15,14 @@ /* border: solid thin $gray-light; */ /* border-radius: 3px; */ overflow: hidden; - background: repeating-conic-gradient($gray-lightest 0% 25%, transparent 0% 50%) - 50% / 10px 10px; +} + +.checkers { + background: repeating-conic-gradient($gray-lightest 0% 25%, transparent 0% 50%) + 50% / 10px 10px; +} +.solidBg { + background: $gray-lightest; } .mobileScreenWrapper { diff --git a/frontend/app/components/shared/MainSearchBar/MainSearchBar.tsx b/frontend/app/components/shared/MainSearchBar/MainSearchBar.tsx index e3b2090c1..9604b59e0 100644 --- a/frontend/app/components/shared/MainSearchBar/MainSearchBar.tsx +++ b/frontend/app/components/shared/MainSearchBar/MainSearchBar.tsx @@ -4,12 +4,14 @@ import SavedSearch from 'Shared/SavedSearch'; import { Button } from 'UI'; import { connect } from 'react-redux'; import { clearSearch } from 'Duck/search'; +import TagList from './components/TagList'; interface Props { clearSearch: () => void; appliedFilter: any; savedSearch: any; } + const MainSearchBar = (props: Props) => { const { appliedFilter } = props; const hasFilters = appliedFilter && appliedFilter.filters && appliedFilter.filters.size > 0; @@ -20,7 +22,8 @@ const MainSearchBar = (props: Props) => {
-
+
+ + ); +} + +const TagListModal = observer(({ onTagClick }: { onTagClick: (tagId: number) => void }) => { + const { tagWatchStore } = useStore(); + + const updateTagName = (id: number, name: string) => { + void tagWatchStore.updateTagName(id, name); + // very annoying + // @ts-ignore + toast.success('Tag name updated'); + }; + const onRemove = async (id: number) => { + if ( + await confirm({ + header: 'Remove Tag', + confirmButton: 'Remove', + confirmation: 'Are you sure you want to remove this tag?', + }) + ) { + void tagWatchStore.deleteTag(id); + } + }; + + return ( +
+
Tagged Elements
+ {tagWatchStore.tags.map((tag) => ( + + ))} +
+ ); +}); + +const TagRow = (props: { + tag: Tag; + onEdit: (id: number, name: string) => void; + onDelete: (id: number) => void; + onTagClick: (tagId: number) => void; +}) => { + const { tag, onEdit, onDelete, onTagClick } = props; + const [isEditing, setIsEditing] = React.useState(false); + const [name, setName] = React.useState(tag.name); + + return ( +
onTagClick(tag.tagId)} + key={tag.tagId} + > + + { + if (e !== tag.name) { + onEdit(tag.tagId, e); + setName(e); + } + setIsEditing(false); + }, + text: name, + editing: isEditing, + onCancel: () => { + setIsEditing(false); + setName(tag.name); + }, + triggerType: [], + maxLength: 90, + }} + > + {tag.name} + + +
{ + e.stopPropagation(); + setIsEditing(true); + }} + > + +
+
{ + e.stopPropagation(); + void onDelete(tag.tagId); + }} + > + +
+
+ ); +}; + +export default connect(() => ({}), { refreshFilterOptions, addFilterByKeyAndValue })( + observer(TagList) +); diff --git a/frontend/app/components/shared/SessionSearch/SessionSearch.tsx b/frontend/app/components/shared/SessionSearch/SessionSearch.tsx index 4238fad87..3b2e93385 100644 --- a/frontend/app/components/shared/SessionSearch/SessionSearch.tsx +++ b/frontend/app/components/shared/SessionSearch/SessionSearch.tsx @@ -3,33 +3,57 @@ import FilterList from 'Shared/Filters/FilterList'; import FilterSelection from 'Shared/Filters/FilterSelection'; import SaveFilterButton from 'Shared/SaveFilterButton'; import { connect } from 'react-redux'; +import { FilterKey } from 'Types/filter/filterType'; +import { addOptionsToFilter } from 'Types/filter/newFilter'; import { Button } from 'UI'; import { edit, addFilter, fetchSessions, updateFilter } from 'Duck/search'; - +import { observer } from 'mobx-react-lite'; +import { useStore } from 'App/mstore'; import { debounce } from 'App/utils'; import useSessionSearchQueryHandler from 'App/hooks/useSessionSearchQueryHandler'; +import { refreshFilterOptions } from 'Duck/search'; -let debounceFetch: any = () => {} +let debounceFetch: any = () => {}; interface Props { appliedFilter: any; edit: typeof edit; addFilter: typeof addFilter; saveRequestPayloads: boolean; - metaLoading?: boolean + metaLoading?: boolean; fetchSessions: typeof fetchSessions; updateFilter: typeof updateFilter; + refreshFilterOptions: typeof refreshFilterOptions; } + function SessionSearch(props: Props) { + const { tagWatchStore } = useStore(); const { appliedFilter, saveRequestPayloads = false, metaLoading = false } = props; const hasEvents = appliedFilter.filters.filter((i: any) => i.isEvent).size > 0; const hasFilters = appliedFilter.filters.filter((i: any) => !i.isEvent).size > 0; - useSessionSearchQueryHandler({ appliedFilter, applyFilter: props.updateFilter, loading: metaLoading }); + useSessionSearchQueryHandler({ + appliedFilter, + applyFilter: props.updateFilter, + loading: metaLoading, + onBeforeLoad: async () => { + const tags = await tagWatchStore.getTags(); + if (tags) { + addOptionsToFilter( + FilterKey.TAGGED_ELEMENT, + tags.map((tag) => ({ + label: tag.name, + value: tag.tagId.toString() + })) + ); + props.refreshFilterOptions(); + } + }, + }); useEffect(() => { debounceFetch = debounce(() => props.fetchSessions(), 500); - }, []) + }, []); const onAddFilter = (filter: any) => { props.addFilter(filter); @@ -49,7 +73,7 @@ function SessionSearch(props: Props) { filters: newFilters, }); - debounceFetch() + debounceFetch(); }; const onRemoveFilter = (filterIndex: any) => { @@ -61,7 +85,7 @@ function SessionSearch(props: Props) { filters: newFilters, }); - debounceFetch() + debounceFetch(); }; const onChangeEventsOrder = (e: any, { value }: any) => { @@ -69,7 +93,7 @@ function SessionSearch(props: Props) { eventsOrder: value, }); - debounceFetch() + debounceFetch(); }; return !metaLoading ? ( @@ -89,11 +113,7 @@ function SessionSearch(props: Props) {
- @@ -114,7 +134,7 @@ export default connect( (state: any) => ({ saveRequestPayloads: state.getIn(['site', 'instance', 'saveRequestPayloads']), appliedFilter: state.getIn(['search', 'instance']), - metaLoading: state.getIn(['customFields', 'fetchRequestActive', 'loading']) + metaLoading: state.getIn(['customFields', 'fetchRequestActive', 'loading']), }), - { edit, addFilter, fetchSessions, updateFilter } -)(SessionSearch); + { edit, addFilter, fetchSessions, updateFilter, refreshFilterOptions } +)(observer(SessionSearch)); diff --git a/frontend/app/components/ui/Icons/filters_tag_element.tsx b/frontend/app/components/ui/Icons/filters_tag_element.tsx new file mode 100644 index 000000000..d69534824 --- /dev/null +++ b/frontend/app/components/ui/Icons/filters_tag_element.tsx @@ -0,0 +1,19 @@ + +/* Auto-generated, do not edit */ +import React from 'react'; + +interface Props { + size?: number | string; + width?: number | string; + height?: number | string; + fill?: string; +} + +function Filters_tag_element(props: Props) { + const { size = 14, width = size, height = size, fill = '' } = props; + return ( + + ); +} + +export default Filters_tag_element; diff --git a/frontend/app/components/ui/Icons/index.ts b/frontend/app/components/ui/Icons/index.ts index a513b17bf..d8aa47651 100644 --- a/frontend/app/components/ui/Icons/index.ts +++ b/frontend/app/components/ui/Icons/index.ts @@ -225,6 +225,7 @@ export { default as Filters_referrer } from './filters_referrer'; export { default as Filters_resize } from './filters_resize'; export { default as Filters_rev_id } from './filters_rev_id'; export { default as Filters_state_action } from './filters_state_action'; +export { default as Filters_tag_element } from './filters_tag_element'; export { default as Filters_ttfb } from './filters_ttfb'; export { default as Filters_user_alt } from './filters_user_alt'; export { default as Filters_userid } from './filters_userid'; @@ -344,6 +345,7 @@ export { default as Mic } from './mic'; export { default as Minus } from './minus'; export { default as Mobile } from './mobile'; export { default as Mouse_alt } from './mouse_alt'; +export { default as Mouse_pointer_click } from './mouse_pointer_click'; export { default as Network } from './network'; export { default as Next1 } from './next1'; export { default as No_dashboard } from './no_dashboard'; @@ -454,6 +456,7 @@ export { default as Turtle } from './turtle'; export { default as User_alt } from './user_alt'; export { default as User_circle } from './user_circle'; export { default as User_friends } from './user_friends'; +export { default as User_switch } from './user_switch'; export { default as Users } from './users'; export { default as Vendors_graphql } from './vendors_graphql'; export { default as Vendors_mobx } from './vendors_mobx'; diff --git a/frontend/app/components/ui/Icons/mouse_pointer_click.tsx b/frontend/app/components/ui/Icons/mouse_pointer_click.tsx new file mode 100644 index 000000000..7e694c0a6 --- /dev/null +++ b/frontend/app/components/ui/Icons/mouse_pointer_click.tsx @@ -0,0 +1,19 @@ + +/* Auto-generated, do not edit */ +import React from 'react'; + +interface Props { + size?: number | string; + width?: number | string; + height?: number | string; + fill?: string; +} + +function Mouse_pointer_click(props: Props) { + const { size = 14, width = size, height = size, fill = '' } = props; + return ( + + ); +} + +export default Mouse_pointer_click; diff --git a/frontend/app/components/ui/Icons/user_switch.tsx b/frontend/app/components/ui/Icons/user_switch.tsx new file mode 100644 index 000000000..513c24123 --- /dev/null +++ b/frontend/app/components/ui/Icons/user_switch.tsx @@ -0,0 +1,19 @@ + +/* Auto-generated, do not edit */ +import React from 'react'; + +interface Props { + size?: number | string; + width?: number | string; + height?: number | string; + fill?: string; +} + +function User_switch(props: Props) { + const { size = 14, width = size, height = size, fill = '' } = props; + return ( + + ); +} + +export default User_switch; diff --git a/frontend/app/components/ui/SVG.tsx b/frontend/app/components/ui/SVG.tsx index 1acce18dc..2cb84072e 100644 --- a/frontend/app/components/ui/SVG.tsx +++ b/frontend/app/components/ui/SVG.tsx @@ -3,480 +3,483 @@ import React from 'react'; import { Activity, -Alarm_clock, -Alarm_plus, -All_sessions, -Analytics, -Anchor, -Arrow_alt_square_right, -Arrow_bar_left, -Arrow_clockwise, -Arrow_counterclockwise, -Arrow_down_short, -Arrow_down_up, -Arrow_down, -Arrow_repeat, -Arrow_right_short, -Arrow_square_left, -Arrow_square_right, -Arrow_up_short, -Arrow_up, -Arrows_angle_extend, -Avatar_icn_avatar1, -Avatar_icn_avatar10, -Avatar_icn_avatar11, -Avatar_icn_avatar12, -Avatar_icn_avatar13, -Avatar_icn_avatar14, -Avatar_icn_avatar15, -Avatar_icn_avatar16, -Avatar_icn_avatar17, -Avatar_icn_avatar18, -Avatar_icn_avatar19, -Avatar_icn_avatar2, -Avatar_icn_avatar20, -Avatar_icn_avatar21, -Avatar_icn_avatar22, -Avatar_icn_avatar23, -Avatar_icn_avatar3, -Avatar_icn_avatar4, -Avatar_icn_avatar5, -Avatar_icn_avatar6, -Avatar_icn_avatar7, -Avatar_icn_avatar8, -Avatar_icn_avatar9, -Ban, -Bar_chart_line, -Bar_pencil, -Battery_charging, -Battery, -Bell_fill, -Bell_plus, -Bell_slash, -Bell, -Binoculars, -Book_doc, -Book, -Bookmark, -Broadcast, -Browser_browser, -Browser_chrome, -Browser_edge, -Browser_electron, -Browser_facebook, -Browser_firefox, -Browser_ie, -Browser_opera, -Browser_safari, -Buildings, -Bullhorn, -Business_time, -Calendar_alt, -Calendar_check, -Calendar_day, -Calendar, -Call, -Camera_alt, -Camera_video_off, -Camera_video, -Camera, -Card_checklist, -Card_list, -Card_text, -Caret_down_fill, -Caret_left_fill, -Caret_right_fill, -Caret_up_fill, -Chat_dots, -Chat_left_text, -Chat_right_text, -Chat_square_quote, -Check_circle_fill, -Check_circle, -Check, -Chevron_double_left, -Chevron_double_right, -Chevron_down, -Chevron_left, -Chevron_right, -Chevron_up, -Circle_fill, -Circle, -Click_hesitation, -Click_rage, -Clipboard_check, -Clipboard_list_check, -Clock_history, -Clock, -Close, -Cloud_fog2_fill, -Code, -Cog, -Cogs, -Collection_play, -Collection, -Columns_gap_filled, -Columns_gap, -Console_error, -Console_exception, -Console_info, -Console_warning, -Console, -Controller, -Cookies, -Copy, -Credit_card_2_back, -Credit_card_front, -Cross, -Cubes, -Cursor_trash, -Dash, -Dashboard_icn, -Db_icons_icn_card_clickmap, -Db_icons_icn_card_errors, -Db_icons_icn_card_funnel, -Db_icons_icn_card_funnels, -Db_icons_icn_card_insights, -Db_icons_icn_card_library, -Db_icons_icn_card_mapchart, -Db_icons_icn_card_pathanalysis, -Db_icons_icn_card_performance, -Db_icons_icn_card_resources, -Db_icons_icn_card_table, -Db_icons_icn_card_timeseries, -Db_icons_icn_card_webvitals, -Desktop, -Device, -Diagram_3, -Dice_3, -Dizzy, -Door_closed, -Doublecheck, -Download, -Drag, -Edit, -Ellipsis_v, -Emoji_dizzy, -Enter, -Envelope_check, -Envelope_paper, -Envelope_x, -Envelope, -Errors_icon, -Event_click, -Event_click_hesitation, -Event_clickrage, -Event_code, -Event_i_cursor, -Event_input, -Event_input_hesitation, -Event_link, -Event_location, -Event_mouse_thrashing, -Event_resize, -Event_view, -Exclamation_circle_fill, -Exclamation_circle, -Exclamation_triangle, -Expand_wide, -Explosion, -External_link_alt, -Eye_slash_fill, -Eye_slash, -Eye, -Fetch, -Fflag_multi, -Fflag_single, -File_bar_graph, -File_code, -File_medical_alt, -File_pdf, -File, -Files, -Filetype_js, -Filetype_pdf, -Filter, -Filters_arrow_return_right, -Filters_browser, -Filters_click, -Filters_clickrage, -Filters_code, -Filters_console, -Filters_country, -Filters_cpu_load, -Filters_custom, -Filters_device, -Filters_dom_complete, -Filters_duration, -Filters_error, -Filters_fetch_failed, -Filters_fetch, -Filters_file_code, -Filters_graphql, -Filters_i_cursor, -Filters_input, -Filters_lcpt, -Filters_link, -Filters_location, -Filters_memory_load, -Filters_metadata, -Filters_os, -Filters_perfromance_network_request, -Filters_platform, -Filters_referrer, -Filters_resize, -Filters_rev_id, -Filters_state_action, -Filters_ttfb, -Filters_user_alt, -Filters_userid, -Filters_view, -Flag_na, -Folder_plus, -Folder2, -Fullscreen, -Funnel_cpu_fill, -Funnel_cpu, -Funnel_dizzy, -Funnel_emoji_angry_fill, -Funnel_emoji_angry, -Funnel_emoji_dizzy_fill, -Funnel_exclamation_circle_fill, -Funnel_exclamation_circle, -Funnel_file_earmark_break_fill, -Funnel_file_earmark_break, -Funnel_file_earmark_minus_fill, -Funnel_file_earmark_minus, -Funnel_file_medical_alt, -Funnel_file_x, -Funnel_hdd_fill, -Funnel_hourglass_top, -Funnel_image_fill, -Funnel_image, -Funnel_microchip, -Funnel_mouse, -Funnel_patch_exclamation_fill, -Funnel_sd_card, -Funnel_fill, -Funnel_new, -Funnel, -Gear_fill, -Gear, -Geo_alt_fill_custom, -Github, -Graph_up_arrow, -Graph_up, -Grid_1x2, -Grid_3x3, -Grid_check, -Grid_horizontal, -Grid, -Grip_horizontal, -Hash, -Hdd_stack, -Headset, -Heart_rate, -High_engagement, -History, -Hourglass_start, -Ic_errors, -Ic_network, -Ic_rage, -Ic_resources, -Id_card, -Image, -Info_circle_fill, -Info_circle, -Info_square, -Info, -Input_hesitation, -Inspect, -Integrations_assist, -Integrations_bugsnag_text, -Integrations_bugsnag, -Integrations_cloudwatch_text, -Integrations_cloudwatch, -Integrations_datadog, -Integrations_elasticsearch_text, -Integrations_elasticsearch, -Integrations_github, -Integrations_graphql, -Integrations_jira_text, -Integrations_jira, -Integrations_mobx, -Integrations_newrelic_text, -Integrations_newrelic, -Integrations_ngrx, -Integrations_openreplay_text, -Integrations_openreplay, -Integrations_redux, -Integrations_rollbar_text, -Integrations_rollbar, -Integrations_segment, -Integrations_sentry_text, -Integrations_sentry, -Integrations_slack_bw, -Integrations_slack, -Integrations_stackdriver, -Integrations_sumologic_text, -Integrations_sumologic, -Integrations_teams_white, -Integrations_teams, -Integrations_vuejs, -Integrations_zustand, -Journal_code, -Key, -Layer_group, -Layers_half, -Lightbulb_on, -Lightbulb, -Link_45deg, -List_alt, -List_arrow, -List_ul, -List, -Lock_alt, -Low_disc_space, -Magic, -Map_marker_alt, -Memory_ios, -Memory, -Mic_mute, -Mic, -Minus, -Mobile, -Mouse_alt, -Network, -Next1, -No_dashboard, -No_metrics_chart, -No_metrics, -No_recordings, -Os_android, -Os_chrome_os, -Os_fedora, -Os_ios, -Os_linux, -Os_mac_os_x, -Os_other, -Os_ubuntu, -Os_windows, -Os, -Pause_circle_fill, -Pause_fill, -Pause, -Pdf_download, -Pencil_stop, -Pencil, -People, -Percent, -Performance_icon, -Person_border, -Person_fill, -Person, -Pie_chart_fill, -Pin_fill, -Play_circle_bold, -Play_circle_light, -Play_circle, -Play_fill_new, -Play_fill, -Play_hover, -Play, -Plug, -Plus_circle, -Plus_lg, -Plus, -Pointer_sessions_search, -Prev1, -Pulse, -Puzzle_piece, -Puzzle, -Question_circle, -Question_lg, -Quote_left, -Quote_right, -Quotes, -Record_btn, -Record_circle_fill, -Record_circle, -Record2, -Redo_back, -Redo, -Redux, -Remote_control, -Replay_10, -Resources_icon, -Safe_fill, -Safe, -Sandglass, -Search, -Search_notification, -Server, -Share_alt, -Shield_lock, -Side_menu_closed, -Side_menu_open, -Signpost_split, -Signup, -Skip_forward_fill, -Skip_forward, -Slack, -Slash_circle, -Sleep, -Sliders, -Social_slack, -Social_trello, -Speedometer2, -Spinner, -Star_solid, -Star, -Step_forward, -Stickies, -Stop_record_circle, -Stopwatch, -Store, -Sync_alt, -Table_new, -Table, -Tablet_android, -Tachometer_slow, -Tachometer_slowest, -Tags, -Team_funnel, -Telephone_fill, -Telephone, -Terminal, -Text_paragraph, -Thermometer_sun, -Toggles, -Tools, -Trash, -Turtle, -User_alt, -User_circle, -User_friends, -Users, -Vendors_graphql, -Vendors_mobx, -Vendors_ngrx, -Vendors_redux, -Vendors_vuex, -Web_vitals, -Wifi, -Window_alt, -Window_restore, -Window_x, -Window, -Zoom_in + Alarm_clock, + Alarm_plus, + All_sessions, + Analytics, + Anchor, + Arrow_alt_square_right, + Arrow_bar_left, + Arrow_clockwise, + Arrow_counterclockwise, + Arrow_down_short, + Arrow_down_up, + Arrow_down, + Arrow_repeat, + Arrow_right_short, + Arrow_square_left, + Arrow_square_right, + Arrow_up_short, + Arrow_up, + Arrows_angle_extend, + Avatar_icn_avatar1, + Avatar_icn_avatar10, + Avatar_icn_avatar11, + Avatar_icn_avatar12, + Avatar_icn_avatar13, + Avatar_icn_avatar14, + Avatar_icn_avatar15, + Avatar_icn_avatar16, + Avatar_icn_avatar17, + Avatar_icn_avatar18, + Avatar_icn_avatar19, + Avatar_icn_avatar2, + Avatar_icn_avatar20, + Avatar_icn_avatar21, + Avatar_icn_avatar22, + Avatar_icn_avatar23, + Avatar_icn_avatar3, + Avatar_icn_avatar4, + Avatar_icn_avatar5, + Avatar_icn_avatar6, + Avatar_icn_avatar7, + Avatar_icn_avatar8, + Avatar_icn_avatar9, + Ban, + Bar_chart_line, + Bar_pencil, + Battery_charging, + Battery, + Bell_fill, + Bell_plus, + Bell_slash, + Bell, + Binoculars, + Book_doc, + Book, + Bookmark, + Broadcast, + Browser_browser, + Browser_chrome, + Browser_edge, + Browser_electron, + Browser_facebook, + Browser_firefox, + Browser_ie, + Browser_opera, + Browser_safari, + Buildings, + Bullhorn, + Business_time, + Calendar_alt, + Calendar_check, + Calendar_day, + Calendar, + Call, + Camera_alt, + Camera_video_off, + Camera_video, + Camera, + Card_checklist, + Card_list, + Card_text, + Caret_down_fill, + Caret_left_fill, + Caret_right_fill, + Caret_up_fill, + Chat_dots, + Chat_left_text, + Chat_right_text, + Chat_square_quote, + Check_circle_fill, + Check_circle, + Check, + Chevron_double_left, + Chevron_double_right, + Chevron_down, + Chevron_left, + Chevron_right, + Chevron_up, + Circle_fill, + Circle, + Click_hesitation, + Click_rage, + Clipboard_check, + Clipboard_list_check, + Clock_history, + Clock, + Close, + Cloud_fog2_fill, + Code, + Cog, + Cogs, + Collection_play, + Collection, + Columns_gap_filled, + Columns_gap, + Console_error, + Console_exception, + Console_info, + Console_warning, + Console, + Controller, + Cookies, + Copy, + Credit_card_2_back, + Credit_card_front, + Cross, + Cubes, + Cursor_trash, + Dash, + Dashboard_icn, + Db_icons_icn_card_clickmap, + Db_icons_icn_card_errors, + Db_icons_icn_card_funnel, + Db_icons_icn_card_funnels, + Db_icons_icn_card_insights, + Db_icons_icn_card_library, + Db_icons_icn_card_mapchart, + Db_icons_icn_card_pathanalysis, + Db_icons_icn_card_performance, + Db_icons_icn_card_resources, + Db_icons_icn_card_table, + Db_icons_icn_card_timeseries, + Db_icons_icn_card_webvitals, + Desktop, + Device, + Diagram_3, + Dice_3, + Dizzy, + Door_closed, + Doublecheck, + Download, + Drag, + Edit, + Ellipsis_v, + Emoji_dizzy, + Enter, + Envelope_check, + Envelope_paper, + Envelope_x, + Envelope, + Errors_icon, + Event_click, + Event_click_hesitation, + Event_clickrage, + Event_code, + Event_i_cursor, + Event_input, + Event_input_hesitation, + Event_link, + Event_location, + Event_mouse_thrashing, + Event_resize, + Event_view, + Exclamation_circle_fill, + Exclamation_circle, + Exclamation_triangle, + Expand_wide, + Explosion, + External_link_alt, + Eye_slash_fill, + Eye_slash, + Eye, + Fetch, + Fflag_multi, + Fflag_single, + File_bar_graph, + File_code, + File_medical_alt, + File_pdf, + File, + Files, + Filetype_js, + Filetype_pdf, + Filter, + Filters_arrow_return_right, + Filters_browser, + Filters_click, + Filters_clickrage, + Filters_code, + Filters_console, + Filters_country, + Filters_cpu_load, + Filters_custom, + Filters_device, + Filters_dom_complete, + Filters_duration, + Filters_error, + Filters_fetch_failed, + Filters_fetch, + Filters_file_code, + Filters_graphql, + Filters_i_cursor, + Filters_input, + Filters_lcpt, + Filters_link, + Filters_location, + Filters_memory_load, + Filters_metadata, + Filters_os, + Filters_perfromance_network_request, + Filters_platform, + Filters_referrer, + Filters_resize, + Filters_rev_id, + Filters_state_action, + Filters_tag_element, + Filters_ttfb, + Filters_user_alt, + Filters_userid, + Filters_view, + Flag_na, + Folder_plus, + Folder2, + Fullscreen, + Funnel_cpu_fill, + Funnel_cpu, + Funnel_dizzy, + Funnel_emoji_angry_fill, + Funnel_emoji_angry, + Funnel_emoji_dizzy_fill, + Funnel_exclamation_circle_fill, + Funnel_exclamation_circle, + Funnel_file_earmark_break_fill, + Funnel_file_earmark_break, + Funnel_file_earmark_minus_fill, + Funnel_file_earmark_minus, + Funnel_file_medical_alt, + Funnel_file_x, + Funnel_hdd_fill, + Funnel_hourglass_top, + Funnel_image_fill, + Funnel_image, + Funnel_microchip, + Funnel_mouse, + Funnel_patch_exclamation_fill, + Funnel_sd_card, + Funnel_fill, + Funnel_new, + Funnel, + Gear_fill, + Gear, + Geo_alt_fill_custom, + Github, + Graph_up_arrow, + Graph_up, + Grid_1x2, + Grid_3x3, + Grid_check, + Grid_horizontal, + Grid, + Grip_horizontal, + Hash, + Hdd_stack, + Headset, + Heart_rate, + High_engagement, + History, + Hourglass_start, + Ic_errors, + Ic_network, + Ic_rage, + Ic_resources, + Id_card, + Image, + Info_circle_fill, + Info_circle, + Info_square, + Info, + Input_hesitation, + Inspect, + Integrations_assist, + Integrations_bugsnag_text, + Integrations_bugsnag, + Integrations_cloudwatch_text, + Integrations_cloudwatch, + Integrations_datadog, + Integrations_elasticsearch_text, + Integrations_elasticsearch, + Integrations_github, + Integrations_graphql, + Integrations_jira_text, + Integrations_jira, + Integrations_mobx, + Integrations_newrelic_text, + Integrations_newrelic, + Integrations_ngrx, + Integrations_openreplay_text, + Integrations_openreplay, + Integrations_redux, + Integrations_rollbar_text, + Integrations_rollbar, + Integrations_segment, + Integrations_sentry_text, + Integrations_sentry, + Integrations_slack_bw, + Integrations_slack, + Integrations_stackdriver, + Integrations_sumologic_text, + Integrations_sumologic, + Integrations_teams_white, + Integrations_teams, + Integrations_vuejs, + Integrations_zustand, + Journal_code, + Key, + Layer_group, + Layers_half, + Lightbulb_on, + Lightbulb, + Link_45deg, + List_alt, + List_arrow, + List_ul, + List, + Lock_alt, + Low_disc_space, + Magic, + Map_marker_alt, + Memory_ios, + Memory, + Mic_mute, + Mic, + Minus, + Mobile, + Mouse_alt, + Mouse_pointer_click, + Network, + Next1, + No_dashboard, + No_metrics_chart, + No_metrics, + No_recordings, + Os_android, + Os_chrome_os, + Os_fedora, + Os_ios, + Os_linux, + Os_mac_os_x, + Os_other, + Os_ubuntu, + Os_windows, + Os, + Pause_circle_fill, + Pause_fill, + Pause, + Pdf_download, + Pencil_stop, + Pencil, + People, + Percent, + Performance_icon, + Person_border, + Person_fill, + Person, + Pie_chart_fill, + Pin_fill, + Play_circle_bold, + Play_circle_light, + Play_circle, + Play_fill_new, + Play_fill, + Play_hover, + Play, + Plug, + Plus_circle, + Plus_lg, + Plus, + Pointer_sessions_search, + Prev1, + Pulse, + Puzzle_piece, + Puzzle, + Question_circle, + Question_lg, + Quote_left, + Quote_right, + Quotes, + Record_btn, + Record_circle_fill, + Record_circle, + Record2, + Redo_back, + Redo, + Redux, + Remote_control, + Replay_10, + Resources_icon, + Safe_fill, + Safe, + Sandglass, + Search, + Search_notification, + Server, + Share_alt, + Shield_lock, + Side_menu_closed, + Side_menu_open, + Signpost_split, + Signup, + Skip_forward_fill, + Skip_forward, + Slack, + Slash_circle, + Sleep, + Sliders, + Social_slack, + Social_trello, + Speedometer2, + Spinner, + Star_solid, + Star, + Step_forward, + Stickies, + Stop_record_circle, + Stopwatch, + Store, + Sync_alt, + Table_new, + Table, + Tablet_android, + Tachometer_slow, + Tachometer_slowest, + Tags, + Team_funnel, + Telephone_fill, + Telephone, + Terminal, + Text_paragraph, + Thermometer_sun, + Toggles, + Tools, + Trash, + Turtle, + User_alt, + User_circle, + User_friends, + User_switch, + Users, + Vendors_graphql, + Vendors_mobx, + Vendors_ngrx, + Vendors_redux, + Vendors_vuex, + Web_vitals, + Wifi, + Window_alt, + Window_restore, + Window_x, + Window, + Zoom_in } from './Icons' -// export type IconNames = 'activity' | 'alarm_clock' | 'alarm_plus' | 'all_sessions' | 'analytics' | 'anchor' | 'arrow_alt_square_right' | 'arrow_bar_left' | 'arrow_clockwise' | 'arrow_counterclockwise' | 'arrow_down_short' | 'arrow_down_up' | 'arrow_down' | 'arrow_repeat' | 'arrow_right_short' | 'arrow_square_left' | 'arrow_square_right' | 'arrow_up_short' | 'arrow_up' | 'arrows_angle_extend' | 'avatar/icn_avatar1' | 'avatar/icn_avatar10' | 'avatar/icn_avatar11' | 'avatar/icn_avatar12' | 'avatar/icn_avatar13' | 'avatar/icn_avatar14' | 'avatar/icn_avatar15' | 'avatar/icn_avatar16' | 'avatar/icn_avatar17' | 'avatar/icn_avatar18' | 'avatar/icn_avatar19' | 'avatar/icn_avatar2' | 'avatar/icn_avatar20' | 'avatar/icn_avatar21' | 'avatar/icn_avatar22' | 'avatar/icn_avatar23' | 'avatar/icn_avatar3' | 'avatar/icn_avatar4' | 'avatar/icn_avatar5' | 'avatar/icn_avatar6' | 'avatar/icn_avatar7' | 'avatar/icn_avatar8' | 'avatar/icn_avatar9' | 'ban' | 'bar_chart_line' | 'bar_pencil' | 'battery_charging' | 'battery' | 'bell_fill' | 'bell_plus' | 'bell_slash' | 'bell' | 'binoculars' | 'book_doc' | 'book' | 'bookmark' | 'broadcast' | 'browser/browser' | 'browser/chrome' | 'browser/edge' | 'browser/electron' | 'browser/facebook' | 'browser/firefox' | 'browser/ie' | 'browser/opera' | 'browser/safari' | 'buildings' | 'bullhorn' | 'business_time' | 'calendar_alt' | 'calendar_check' | 'calendar_day' | 'calendar' | 'call' | 'camera_alt' | 'camera_video_off' | 'camera_video' | 'camera' | 'card_checklist' | 'card_list' | 'card_text' | 'caret_down_fill' | 'caret_left_fill' | 'caret_right_fill' | 'caret_up_fill' | 'chat_dots' | 'chat_left_text' | 'chat_right_text' | 'chat_square_quote' | 'check_circle_fill' | 'check_circle' | 'check' | 'chevron_double_left' | 'chevron_double_right' | 'chevron_down' | 'chevron_left' | 'chevron_right' | 'chevron_up' | 'circle_fill' | 'circle' | 'click_hesitation' | 'click_rage' | 'clipboard_check' | 'clipboard_list_check' | 'clock_history' | 'clock' | 'close' | 'cloud_fog2_fill' | 'code' | 'cog' | 'cogs' | 'collection_play' | 'collection' | 'columns_gap_filled' | 'columns_gap' | 'console/error' | 'console/exception' | 'console/info' | 'console/warning' | 'console' | 'controller' | 'cookies' | 'copy' | 'credit_card_2_back' | 'credit_card_front' | 'cross' | 'cubes' | 'cursor_trash' | 'dash' | 'dashboard_icn' | 'db_icons/icn_card_clickMap' | 'db_icons/icn_card_errors' | 'db_icons/icn_card_funnel' | 'db_icons/icn_card_funnels' | 'db_icons/icn_card_insights' | 'db_icons/icn_card_library' | 'db_icons/icn_card_mapchart' | 'db_icons/icn_card_pathAnalysis' | 'db_icons/icn_card_performance' | 'db_icons/icn_card_resources' | 'db_icons/icn_card_table' | 'db_icons/icn_card_timeseries' | 'db_icons/icn_card_webVitals' | 'desktop' | 'device' | 'diagram_3' | 'dice_3' | 'dizzy' | 'door_closed' | 'doublecheck' | 'download' | 'drag' | 'edit' | 'ellipsis_v' | 'emoji_dizzy' | 'enter' | 'envelope_check' | 'envelope_paper' | 'envelope_x' | 'envelope' | 'errors_icon' | 'event/click' | 'event/click_hesitation' | 'event/clickrage' | 'event/code' | 'event/i_cursor' | 'event/input' | 'event/input_hesitation' | 'event/link' | 'event/location' | 'event/mouse_thrashing' | 'event/resize' | 'event/view' | 'exclamation_circle_fill' | 'exclamation_circle' | 'exclamation_triangle' | 'expand_wide' | 'explosion' | 'external_link_alt' | 'eye_slash_fill' | 'eye_slash' | 'eye' | 'fetch' | 'fflag_multi' | 'fflag_single' | 'file_bar_graph' | 'file_code' | 'file_medical_alt' | 'file_pdf' | 'file' | 'files' | 'filetype_js' | 'filetype_pdf' | 'filter' | 'filters/arrow_return_right' | 'filters/browser' | 'filters/click' | 'filters/clickrage' | 'filters/code' | 'filters/console' | 'filters/country' | 'filters/cpu_load' | 'filters/custom' | 'filters/device' | 'filters/dom_complete' | 'filters/duration' | 'filters/error' | 'filters/fetch_failed' | 'filters/fetch' | 'filters/file_code' | 'filters/graphql' | 'filters/i_cursor' | 'filters/input' | 'filters/lcpt' | 'filters/link' | 'filters/location' | 'filters/memory_load' | 'filters/metadata' | 'filters/os' | 'filters/perfromance_network_request' | 'filters/platform' | 'filters/referrer' | 'filters/resize' | 'filters/rev_id' | 'filters/state_action' | 'filters/ttfb' | 'filters/user_alt' | 'filters/userid' | 'filters/view' | 'flag_na' | 'folder_plus' | 'folder2' | 'fullscreen' | 'funnel/cpu_fill' | 'funnel/cpu' | 'funnel/dizzy' | 'funnel/emoji_angry_fill' | 'funnel/emoji_angry' | 'funnel/emoji_dizzy_fill' | 'funnel/exclamation_circle_fill' | 'funnel/exclamation_circle' | 'funnel/file_earmark_break_fill' | 'funnel/file_earmark_break' | 'funnel/file_earmark_minus_fill' | 'funnel/file_earmark_minus' | 'funnel/file_medical_alt' | 'funnel/file_x' | 'funnel/hdd_fill' | 'funnel/hourglass_top' | 'funnel/image_fill' | 'funnel/image' | 'funnel/microchip' | 'funnel/mouse' | 'funnel/patch_exclamation_fill' | 'funnel/sd_card' | 'funnel_fill' | 'funnel_new' | 'funnel' | 'gear_fill' | 'gear' | 'geo_alt_fill_custom' | 'github' | 'graph_up_arrow' | 'graph_up' | 'grid_1x2' | 'grid_3x3' | 'grid_check' | 'grid_horizontal' | 'grid' | 'grip_horizontal' | 'hash' | 'hdd_stack' | 'headset' | 'heart_rate' | 'high_engagement' | 'history' | 'hourglass_start' | 'ic_errors' | 'ic_network' | 'ic_rage' | 'ic_resources' | 'id_card' | 'image' | 'info_circle_fill' | 'info_circle' | 'info_square' | 'info' | 'input_hesitation' | 'inspect' | 'integrations/assist' | 'integrations/bugsnag_text' | 'integrations/bugsnag' | 'integrations/cloudwatch_text' | 'integrations/cloudwatch' | 'integrations/datadog' | 'integrations/elasticsearch_text' | 'integrations/elasticsearch' | 'integrations/github' | 'integrations/graphql' | 'integrations/jira_text' | 'integrations/jira' | 'integrations/mobx' | 'integrations/newrelic_text' | 'integrations/newrelic' | 'integrations/ngrx' | 'integrations/openreplay_text' | 'integrations/openreplay' | 'integrations/redux' | 'integrations/rollbar_text' | 'integrations/rollbar' | 'integrations/segment' | 'integrations/sentry_text' | 'integrations/sentry' | 'integrations/slack_bw' | 'integrations/slack' | 'integrations/stackdriver' | 'integrations/sumologic_text' | 'integrations/sumologic' | 'integrations/teams_white' | 'integrations/teams' | 'integrations/vuejs' | 'integrations/zustand' | 'journal_code' | 'key' | 'layer_group' | 'layers_half' | 'lightbulb_on' | 'lightbulb' | 'link_45deg' | 'list_alt' | 'list_arrow' | 'list_ul' | 'list' | 'lock_alt' | 'low_disc_space' | 'magic' | 'map_marker_alt' | 'memory_ios' | 'memory' | 'mic_mute' | 'mic' | 'minus' | 'mobile' | 'mouse_alt' | 'network' | 'next1' | 'no_dashboard' | 'no_metrics_chart' | 'no_metrics' | 'no_recordings' | 'os/android' | 'os/chrome_os' | 'os/fedora' | 'os/ios' | 'os/linux' | 'os/mac_os_x' | 'os/other' | 'os/ubuntu' | 'os/windows' | 'os' | 'pause_circle_fill' | 'pause_fill' | 'pause' | 'pdf_download' | 'pencil_stop' | 'pencil' | 'people' | 'percent' | 'performance_icon' | 'person_border' | 'person_fill' | 'person' | 'pie_chart_fill' | 'pin_fill' | 'play_circle_bold' | 'play_circle_light' | 'play_circle' | 'play_fill_new' | 'play_fill' | 'play_hover' | 'play' | 'plug' | 'plus_circle' | 'plus_lg' | 'plus' | 'pointer_sessions_search' | 'prev1' | 'pulse' | 'puzzle_piece' | 'puzzle' | 'question_circle' | 'question_lg' | 'quote_left' | 'quote_right' | 'quotes' | 'record_btn' | 'record_circle_fill' | 'record_circle' | 'record2' | 'redo_back' | 'redo' | 'redux' | 'remote_control' | 'replay_10' | 'resources_icon' | 'safe_fill' | 'safe' | 'sandglass' | 'search' | 'search_notification' | 'server' | 'share_alt' | 'shield_lock' | 'side_menu_closed' | 'side_menu_open' | 'signpost_split' | 'signup' | 'skip_forward_fill' | 'skip_forward' | 'slack' | 'slash_circle' | 'sleep' | 'sliders' | 'social/slack' | 'social/trello' | 'speedometer2' | 'spinner' | 'star_solid' | 'star' | 'step_forward' | 'stickies' | 'stop_record_circle' | 'stopwatch' | 'store' | 'sync_alt' | 'table_new' | 'table' | 'tablet_android' | 'tachometer_slow' | 'tachometer_slowest' | 'tags' | 'team_funnel' | 'telephone_fill' | 'telephone' | 'terminal' | 'text_paragraph' | 'thermometer_sun' | 'toggles' | 'tools' | 'trash' | 'turtle' | 'user_alt' | 'user_circle' | 'user_friends' | 'users' | 'vendors/graphql' | 'vendors/mobx' | 'vendors/ngrx' | 'vendors/redux' | 'vendors/vuex' | 'web_vitals' | 'wifi' | 'window_alt' | 'window_restore' | 'window_x' | 'window' | 'zoom_in'; -export type OldIconNames = 'activity' | 'alarm-clock' | 'alarm-plus' | 'all-sessions' | 'analytics' | 'anchor' | 'arrow-alt-square-right' | 'arrow-bar-left' | 'arrow-clockwise' | 'arrow-counterclockwise' | 'arrow-down-short' | 'arrow-down-up' | 'arrow-down' | 'arrow-repeat' | 'arrow-right-short' | 'arrow-square-left' | 'arrow-square-right' | 'arrow-up-short' | 'arrow-up' | 'arrows-angle-extend' | 'avatar/icn_avatar1' | 'avatar/icn_avatar10' | 'avatar/icn_avatar11' | 'avatar/icn_avatar12' | 'avatar/icn_avatar13' | 'avatar/icn_avatar14' | 'avatar/icn_avatar15' | 'avatar/icn_avatar16' | 'avatar/icn_avatar17' | 'avatar/icn_avatar18' | 'avatar/icn_avatar19' | 'avatar/icn_avatar2' | 'avatar/icn_avatar20' | 'avatar/icn_avatar21' | 'avatar/icn_avatar22' | 'avatar/icn_avatar23' | 'avatar/icn_avatar3' | 'avatar/icn_avatar4' | 'avatar/icn_avatar5' | 'avatar/icn_avatar6' | 'avatar/icn_avatar7' | 'avatar/icn_avatar8' | 'avatar/icn_avatar9' | 'ban' | 'bar-chart-line' | 'bar-pencil' | 'battery-charging' | 'battery' | 'bell-fill' | 'bell-plus' | 'bell-slash' | 'bell' | 'binoculars' | 'book-doc' | 'book' | 'bookmark' | 'broadcast' | 'browser/browser' | 'browser/chrome' | 'browser/edge' | 'browser/electron' | 'browser/facebook' | 'browser/firefox' | 'browser/ie' | 'browser/opera' | 'browser/safari' | 'buildings' | 'bullhorn' | 'business-time' | 'calendar-alt' | 'calendar-check' | 'calendar-day' | 'calendar' | 'call' | 'camera-alt' | 'camera-video-off' | 'camera-video' | 'camera' | 'card-checklist' | 'card-list' | 'card-text' | 'caret-down-fill' | 'caret-left-fill' | 'caret-right-fill' | 'caret-up-fill' | 'chat-dots' | 'chat-left-text' | 'chat-right-text' | 'chat-square-quote' | 'check-circle-fill' | 'check-circle' | 'check' | 'chevron-double-left' | 'chevron-double-right' | 'chevron-down' | 'chevron-left' | 'chevron-right' | 'chevron-up' | 'circle-fill' | 'circle' | 'click-hesitation' | 'click-rage' | 'clipboard-check' | 'clipboard-list-check' | 'clock-history' | 'clock' | 'close' | 'cloud-fog2-fill' | 'code' | 'cog' | 'cogs' | 'collection-play' | 'collection' | 'columns-gap-filled' | 'columns-gap' | 'console/error' | 'console/exception' | 'console/info' | 'console/warning' | 'console' | 'controller' | 'cookies' | 'copy' | 'credit-card-2-back' | 'credit-card-front' | 'cross' | 'cubes' | 'cursor-trash' | 'dash' | 'dashboard-icn' | 'db-icons/icn-card-clickMap' | 'db-icons/icn-card-errors' | 'db-icons/icn-card-funnel' | 'db-icons/icn-card-funnels' | 'db-icons/icn-card-insights' | 'db-icons/icn-card-library' | 'db-icons/icn-card-mapchart' | 'db-icons/icn-card-pathAnalysis' | 'db-icons/icn-card-performance' | 'db-icons/icn-card-resources' | 'db-icons/icn-card-table' | 'db-icons/icn-card-timeseries' | 'db-icons/icn-card-webVitals' | 'desktop' | 'device' | 'diagram-3' | 'dice-3' | 'dizzy' | 'door-closed' | 'doublecheck' | 'download' | 'drag' | 'edit' | 'ellipsis-v' | 'emoji-dizzy' | 'enter' | 'envelope-check' | 'envelope-paper' | 'envelope-x' | 'envelope' | 'errors-icon' | 'event/click' | 'event/click_hesitation' | 'event/clickrage' | 'event/code' | 'event/i-cursor' | 'event/input' | 'event/input_hesitation' | 'event/link' | 'event/location' | 'event/mouse_thrashing' | 'event/resize' | 'event/view' | 'exclamation-circle-fill' | 'exclamation-circle' | 'exclamation-triangle' | 'expand-wide' | 'explosion' | 'external-link-alt' | 'eye-slash-fill' | 'eye-slash' | 'eye' | 'fetch' | 'fflag-multi' | 'fflag-single' | 'file-bar-graph' | 'file-code' | 'file-medical-alt' | 'file-pdf' | 'file' | 'files' | 'filetype-js' | 'filetype-pdf' | 'filter' | 'filters/arrow-return-right' | 'filters/browser' | 'filters/click' | 'filters/clickrage' | 'filters/code' | 'filters/console' | 'filters/country' | 'filters/cpu-load' | 'filters/custom' | 'filters/device' | 'filters/dom-complete' | 'filters/duration' | 'filters/error' | 'filters/fetch-failed' | 'filters/fetch' | 'filters/file-code' | 'filters/graphql' | 'filters/i-cursor' | 'filters/input' | 'filters/lcpt' | 'filters/link' | 'filters/location' | 'filters/memory-load' | 'filters/metadata' | 'filters/os' | 'filters/perfromance-network-request' | 'filters/platform' | 'filters/referrer' | 'filters/resize' | 'filters/rev-id' | 'filters/state-action' | 'filters/ttfb' | 'filters/user-alt' | 'filters/userid' | 'filters/view' | 'flag-na' | 'folder-plus' | 'folder2' | 'fullscreen' | 'funnel/cpu-fill' | 'funnel/cpu' | 'funnel/dizzy' | 'funnel/emoji-angry-fill' | 'funnel/emoji-angry' | 'funnel/emoji-dizzy-fill' | 'funnel/exclamation-circle-fill' | 'funnel/exclamation-circle' | 'funnel/file-earmark-break-fill' | 'funnel/file-earmark-break' | 'funnel/file-earmark-minus-fill' | 'funnel/file-earmark-minus' | 'funnel/file-medical-alt' | 'funnel/file-x' | 'funnel/hdd-fill' | 'funnel/hourglass-top' | 'funnel/image-fill' | 'funnel/image' | 'funnel/microchip' | 'funnel/mouse' | 'funnel/patch-exclamation-fill' | 'funnel/sd-card' | 'funnel-fill' | 'funnel-new' | 'funnel' | 'gear-fill' | 'gear' | 'geo-alt-fill-custom' | 'github' | 'graph-up-arrow' | 'graph-up' | 'grid-1x2' | 'grid-3x3' | 'grid-check' | 'grid-horizontal' | 'grid' | 'grip-horizontal' | 'hash' | 'hdd-stack' | 'headset' | 'heart-rate' | 'high-engagement' | 'history' | 'hourglass-start' | 'ic-errors' | 'ic-network' | 'ic-rage' | 'ic-resources' | 'id-card' | 'image' | 'info-circle-fill' | 'info-circle' | 'info-square' | 'info' | 'input-hesitation' | 'inspect' | 'integrations/assist' | 'integrations/bugsnag-text' | 'integrations/bugsnag' | 'integrations/cloudwatch-text' | 'integrations/cloudwatch' | 'integrations/datadog' | 'integrations/elasticsearch-text' | 'integrations/elasticsearch' | 'integrations/github' | 'integrations/graphql' | 'integrations/jira-text' | 'integrations/jira' | 'integrations/mobx' | 'integrations/newrelic-text' | 'integrations/newrelic' | 'integrations/ngrx' | 'integrations/openreplay-text' | 'integrations/openreplay' | 'integrations/redux' | 'integrations/rollbar-text' | 'integrations/rollbar' | 'integrations/segment' | 'integrations/sentry-text' | 'integrations/sentry' | 'integrations/slack-bw' | 'integrations/slack' | 'integrations/stackdriver' | 'integrations/sumologic-text' | 'integrations/sumologic' | 'integrations/teams-white' | 'integrations/teams' | 'integrations/vuejs' | 'integrations/zustand' | 'journal-code' | 'key' | 'layer-group' | 'layers-half' | 'lightbulb-on' | 'lightbulb' | 'link-45deg' | 'list-alt' | 'list-arrow' | 'list-ul' | 'list' | 'lock-alt' | 'low-disc-space' | 'magic' | 'map-marker-alt' | 'memory-ios' | 'memory' | 'mic-mute' | 'mic' | 'minus' | 'mobile' | 'mouse-alt' | 'network' | 'next1' | 'no-dashboard' | 'no-metrics-chart' | 'no-metrics' | 'no-recordings' | 'os/android' | 'os/chrome_os' | 'os/fedora' | 'os/ios' | 'os/linux' | 'os/mac_os_x' | 'os/other' | 'os/ubuntu' | 'os/windows' | 'os' | 'pause-circle-fill' | 'pause-fill' | 'pause' | 'pdf-download' | 'pencil-stop' | 'pencil' | 'people' | 'percent' | 'performance-icon' | 'person-border' | 'person-fill' | 'person' | 'pie-chart-fill' | 'pin-fill' | 'play-circle-bold' | 'play-circle-light' | 'play-circle' | 'play-fill-new' | 'play-fill' | 'play-hover' | 'play' | 'plug' | 'plus-circle' | 'plus-lg' | 'plus' | 'pointer-sessions-search' | 'prev1' | 'pulse' | 'puzzle-piece' | 'puzzle' | 'question-circle' | 'question-lg' | 'quote-left' | 'quote-right' | 'quotes' | 'record-btn' | 'record-circle-fill' | 'record-circle' | 'record2' | 'redo-back' | 'redo' | 'redux' | 'remote-control' | 'replay-10' | 'resources-icon' | 'safe-fill' | 'safe' | 'sandglass' | 'search' | 'search_notification' | 'server' | 'share-alt' | 'shield-lock' | 'side_menu_closed' | 'side_menu_open' | 'signpost-split' | 'signup' | 'skip-forward-fill' | 'skip-forward' | 'slack' | 'slash-circle' | 'sleep' | 'sliders' | 'social/slack' | 'social/trello' | 'speedometer2' | 'spinner' | 'star-solid' | 'star' | 'step-forward' | 'stickies' | 'stop-record-circle' | 'stopwatch' | 'store' | 'sync-alt' | 'table-new' | 'table' | 'tablet-android' | 'tachometer-slow' | 'tachometer-slowest' | 'tags' | 'team-funnel' | 'telephone-fill' | 'telephone' | 'terminal' | 'text-paragraph' | 'thermometer-sun' | 'toggles' | 'tools' | 'trash' | 'turtle' | 'user-alt' | 'user-circle' | 'user-friends' | 'users' | 'vendors/graphql' | 'vendors/mobx' | 'vendors/ngrx' | 'vendors/redux' | 'vendors/vuex' | 'web-vitals' | 'wifi' | 'window-alt' | 'window-restore' | 'window-x' | 'window' | 'zoom-in'; +// export type NewIconNames = 'activity' | 'alarm_clock' | 'alarm_plus' | 'all_sessions' | 'analytics' | 'anchor' | 'arrow_alt_square_right' | 'arrow_bar_left' | 'arrow_clockwise' | 'arrow_counterclockwise' | 'arrow_down_short' | 'arrow_down_up' | 'arrow_down' | 'arrow_repeat' | 'arrow_right_short' | 'arrow_square_left' | 'arrow_square_right' | 'arrow_up_short' | 'arrow_up' | 'arrows_angle_extend' | 'avatar/icn_avatar1' | 'avatar/icn_avatar10' | 'avatar/icn_avatar11' | 'avatar/icn_avatar12' | 'avatar/icn_avatar13' | 'avatar/icn_avatar14' | 'avatar/icn_avatar15' | 'avatar/icn_avatar16' | 'avatar/icn_avatar17' | 'avatar/icn_avatar18' | 'avatar/icn_avatar19' | 'avatar/icn_avatar2' | 'avatar/icn_avatar20' | 'avatar/icn_avatar21' | 'avatar/icn_avatar22' | 'avatar/icn_avatar23' | 'avatar/icn_avatar3' | 'avatar/icn_avatar4' | 'avatar/icn_avatar5' | 'avatar/icn_avatar6' | 'avatar/icn_avatar7' | 'avatar/icn_avatar8' | 'avatar/icn_avatar9' | 'ban' | 'bar_chart_line' | 'bar_pencil' | 'battery_charging' | 'battery' | 'bell_fill' | 'bell_plus' | 'bell_slash' | 'bell' | 'binoculars' | 'book_doc' | 'book' | 'bookmark' | 'broadcast' | 'browser/browser' | 'browser/chrome' | 'browser/edge' | 'browser/electron' | 'browser/facebook' | 'browser/firefox' | 'browser/ie' | 'browser/opera' | 'browser/safari' | 'buildings' | 'bullhorn' | 'business_time' | 'calendar_alt' | 'calendar_check' | 'calendar_day' | 'calendar' | 'call' | 'camera_alt' | 'camera_video_off' | 'camera_video' | 'camera' | 'card_checklist' | 'card_list' | 'card_text' | 'caret_down_fill' | 'caret_left_fill' | 'caret_right_fill' | 'caret_up_fill' | 'chat_dots' | 'chat_left_text' | 'chat_right_text' | 'chat_square_quote' | 'check_circle_fill' | 'check_circle' | 'check' | 'chevron_double_left' | 'chevron_double_right' | 'chevron_down' | 'chevron_left' | 'chevron_right' | 'chevron_up' | 'circle_fill' | 'circle' | 'click_hesitation' | 'click_rage' | 'clipboard_check' | 'clipboard_list_check' | 'clock_history' | 'clock' | 'close' | 'cloud_fog2_fill' | 'code' | 'cog' | 'cogs' | 'collection_play' | 'collection' | 'columns_gap_filled' | 'columns_gap' | 'console/error' | 'console/exception' | 'console/info' | 'console/warning' | 'console' | 'controller' | 'cookies' | 'copy' | 'credit_card_2_back' | 'credit_card_front' | 'cross' | 'cubes' | 'cursor_trash' | 'dash' | 'dashboard_icn' | 'db_icons/icn_card_clickMap' | 'db_icons/icn_card_errors' | 'db_icons/icn_card_funnel' | 'db_icons/icn_card_funnels' | 'db_icons/icn_card_insights' | 'db_icons/icn_card_library' | 'db_icons/icn_card_mapchart' | 'db_icons/icn_card_pathAnalysis' | 'db_icons/icn_card_performance' | 'db_icons/icn_card_resources' | 'db_icons/icn_card_table' | 'db_icons/icn_card_timeseries' | 'db_icons/icn_card_webVitals' | 'desktop' | 'device' | 'diagram_3' | 'dice_3' | 'dizzy' | 'door_closed' | 'doublecheck' | 'download' | 'drag' | 'edit' | 'ellipsis_v' | 'emoji_dizzy' | 'enter' | 'envelope_check' | 'envelope_paper' | 'envelope_x' | 'envelope' | 'errors_icon' | 'event/click' | 'event/click_hesitation' | 'event/clickrage' | 'event/code' | 'event/i_cursor' | 'event/input' | 'event/input_hesitation' | 'event/link' | 'event/location' | 'event/mouse_thrashing' | 'event/resize' | 'event/view' | 'exclamation_circle_fill' | 'exclamation_circle' | 'exclamation_triangle' | 'expand_wide' | 'explosion' | 'external_link_alt' | 'eye_slash_fill' | 'eye_slash' | 'eye' | 'fetch' | 'fflag_multi' | 'fflag_single' | 'file_bar_graph' | 'file_code' | 'file_medical_alt' | 'file_pdf' | 'file' | 'files' | 'filetype_js' | 'filetype_pdf' | 'filter' | 'filters/arrow_return_right' | 'filters/browser' | 'filters/click' | 'filters/clickrage' | 'filters/code' | 'filters/console' | 'filters/country' | 'filters/cpu_load' | 'filters/custom' | 'filters/device' | 'filters/dom_complete' | 'filters/duration' | 'filters/error' | 'filters/fetch_failed' | 'filters/fetch' | 'filters/file_code' | 'filters/graphql' | 'filters/i_cursor' | 'filters/input' | 'filters/lcpt' | 'filters/link' | 'filters/location' | 'filters/memory_load' | 'filters/metadata' | 'filters/os' | 'filters/perfromance_network_request' | 'filters/platform' | 'filters/referrer' | 'filters/resize' | 'filters/rev_id' | 'filters/state_action' | 'filters/tag_element' | 'filters/ttfb' | 'filters/user_alt' | 'filters/userid' | 'filters/view' | 'flag_na' | 'folder_plus' | 'folder2' | 'fullscreen' | 'funnel/cpu_fill' | 'funnel/cpu' | 'funnel/dizzy' | 'funnel/emoji_angry_fill' | 'funnel/emoji_angry' | 'funnel/emoji_dizzy_fill' | 'funnel/exclamation_circle_fill' | 'funnel/exclamation_circle' | 'funnel/file_earmark_break_fill' | 'funnel/file_earmark_break' | 'funnel/file_earmark_minus_fill' | 'funnel/file_earmark_minus' | 'funnel/file_medical_alt' | 'funnel/file_x' | 'funnel/hdd_fill' | 'funnel/hourglass_top' | 'funnel/image_fill' | 'funnel/image' | 'funnel/microchip' | 'funnel/mouse' | 'funnel/patch_exclamation_fill' | 'funnel/sd_card' | 'funnel_fill' | 'funnel_new' | 'funnel' | 'gear_fill' | 'gear' | 'geo_alt_fill_custom' | 'github' | 'graph_up_arrow' | 'graph_up' | 'grid_1x2' | 'grid_3x3' | 'grid_check' | 'grid_horizontal' | 'grid' | 'grip_horizontal' | 'hash' | 'hdd_stack' | 'headset' | 'heart_rate' | 'high_engagement' | 'history' | 'hourglass_start' | 'ic_errors' | 'ic_network' | 'ic_rage' | 'ic_resources' | 'id_card' | 'image' | 'info_circle_fill' | 'info_circle' | 'info_square' | 'info' | 'input_hesitation' | 'inspect' | 'integrations/assist' | 'integrations/bugsnag_text' | 'integrations/bugsnag' | 'integrations/cloudwatch_text' | 'integrations/cloudwatch' | 'integrations/datadog' | 'integrations/elasticsearch_text' | 'integrations/elasticsearch' | 'integrations/github' | 'integrations/graphql' | 'integrations/jira_text' | 'integrations/jira' | 'integrations/mobx' | 'integrations/newrelic_text' | 'integrations/newrelic' | 'integrations/ngrx' | 'integrations/openreplay_text' | 'integrations/openreplay' | 'integrations/redux' | 'integrations/rollbar_text' | 'integrations/rollbar' | 'integrations/segment' | 'integrations/sentry_text' | 'integrations/sentry' | 'integrations/slack_bw' | 'integrations/slack' | 'integrations/stackdriver' | 'integrations/sumologic_text' | 'integrations/sumologic' | 'integrations/teams_white' | 'integrations/teams' | 'integrations/vuejs' | 'integrations/zustand' | 'journal_code' | 'key' | 'layer_group' | 'layers_half' | 'lightbulb_on' | 'lightbulb' | 'link_45deg' | 'list_alt' | 'list_arrow' | 'list_ul' | 'list' | 'lock_alt' | 'low_disc_space' | 'magic' | 'map_marker_alt' | 'memory_ios' | 'memory' | 'mic_mute' | 'mic' | 'minus' | 'mobile' | 'mouse_alt' | 'mouse_pointer_click' | 'network' | 'next1' | 'no_dashboard' | 'no_metrics_chart' | 'no_metrics' | 'no_recordings' | 'os/android' | 'os/chrome_os' | 'os/fedora' | 'os/ios' | 'os/linux' | 'os/mac_os_x' | 'os/other' | 'os/ubuntu' | 'os/windows' | 'os' | 'pause_circle_fill' | 'pause_fill' | 'pause' | 'pdf_download' | 'pencil_stop' | 'pencil' | 'people' | 'percent' | 'performance_icon' | 'person_border' | 'person_fill' | 'person' | 'pie_chart_fill' | 'pin_fill' | 'play_circle_bold' | 'play_circle_light' | 'play_circle' | 'play_fill_new' | 'play_fill' | 'play_hover' | 'play' | 'plug' | 'plus_circle' | 'plus_lg' | 'plus' | 'pointer_sessions_search' | 'prev1' | 'pulse' | 'puzzle_piece' | 'puzzle' | 'question_circle' | 'question_lg' | 'quote_left' | 'quote_right' | 'quotes' | 'record_btn' | 'record_circle_fill' | 'record_circle' | 'record2' | 'redo_back' | 'redo' | 'redux' | 'remote_control' | 'replay_10' | 'resources_icon' | 'safe_fill' | 'safe' | 'sandglass' | 'search' | 'search_notification' | 'server' | 'share_alt' | 'shield_lock' | 'side_menu_closed' | 'side_menu_open' | 'signpost_split' | 'signup' | 'skip_forward_fill' | 'skip_forward' | 'slack' | 'slash_circle' | 'sleep' | 'sliders' | 'social/slack' | 'social/trello' | 'speedometer2' | 'spinner' | 'star_solid' | 'star' | 'step_forward' | 'stickies' | 'stop_record_circle' | 'stopwatch' | 'store' | 'sync_alt' | 'table_new' | 'table' | 'tablet_android' | 'tachometer_slow' | 'tachometer_slowest' | 'tags' | 'team_funnel' | 'telephone_fill' | 'telephone' | 'terminal' | 'text_paragraph' | 'thermometer_sun' | 'toggles' | 'tools' | 'trash' | 'turtle' | 'user_alt' | 'user_circle' | 'user_friends' | 'user_switch' | 'users' | 'vendors/graphql' | 'vendors/mobx' | 'vendors/ngrx' | 'vendors/redux' | 'vendors/vuex' | 'web_vitals' | 'wifi' | 'window_alt' | 'window_restore' | 'window_x' | 'window' | 'zoom_in'; +export type IconNames = 'activity' | 'alarm-clock' | 'alarm-plus' | 'all-sessions' | 'analytics' | 'anchor' | 'arrow-alt-square-right' | 'arrow-bar-left' | 'arrow-clockwise' | 'arrow-counterclockwise' | 'arrow-down-short' | 'arrow-down-up' | 'arrow-down' | 'arrow-repeat' | 'arrow-right-short' | 'arrow-square-left' | 'arrow-square-right' | 'arrow-up-short' | 'arrow-up' | 'arrows-angle-extend' | 'avatar/icn_avatar1' | 'avatar/icn_avatar10' | 'avatar/icn_avatar11' | 'avatar/icn_avatar12' | 'avatar/icn_avatar13' | 'avatar/icn_avatar14' | 'avatar/icn_avatar15' | 'avatar/icn_avatar16' | 'avatar/icn_avatar17' | 'avatar/icn_avatar18' | 'avatar/icn_avatar19' | 'avatar/icn_avatar2' | 'avatar/icn_avatar20' | 'avatar/icn_avatar21' | 'avatar/icn_avatar22' | 'avatar/icn_avatar23' | 'avatar/icn_avatar3' | 'avatar/icn_avatar4' | 'avatar/icn_avatar5' | 'avatar/icn_avatar6' | 'avatar/icn_avatar7' | 'avatar/icn_avatar8' | 'avatar/icn_avatar9' | 'ban' | 'bar-chart-line' | 'bar-pencil' | 'battery-charging' | 'battery' | 'bell-fill' | 'bell-plus' | 'bell-slash' | 'bell' | 'binoculars' | 'book-doc' | 'book' | 'bookmark' | 'broadcast' | 'browser/browser' | 'browser/chrome' | 'browser/edge' | 'browser/electron' | 'browser/facebook' | 'browser/firefox' | 'browser/ie' | 'browser/opera' | 'browser/safari' | 'buildings' | 'bullhorn' | 'business-time' | 'calendar-alt' | 'calendar-check' | 'calendar-day' | 'calendar' | 'call' | 'camera-alt' | 'camera-video-off' | 'camera-video' | 'camera' | 'card-checklist' | 'card-list' | 'card-text' | 'caret-down-fill' | 'caret-left-fill' | 'caret-right-fill' | 'caret-up-fill' | 'chat-dots' | 'chat-left-text' | 'chat-right-text' | 'chat-square-quote' | 'check-circle-fill' | 'check-circle' | 'check' | 'chevron-double-left' | 'chevron-double-right' | 'chevron-down' | 'chevron-left' | 'chevron-right' | 'chevron-up' | 'circle-fill' | 'circle' | 'click-hesitation' | 'click-rage' | 'clipboard-check' | 'clipboard-list-check' | 'clock-history' | 'clock' | 'close' | 'cloud-fog2-fill' | 'code' | 'cog' | 'cogs' | 'collection-play' | 'collection' | 'columns-gap-filled' | 'columns-gap' | 'console/error' | 'console/exception' | 'console/info' | 'console/warning' | 'console' | 'controller' | 'cookies' | 'copy' | 'credit-card-2-back' | 'credit-card-front' | 'cross' | 'cubes' | 'cursor-trash' | 'dash' | 'dashboard-icn' | 'db-icons/icn-card-clickMap' | 'db-icons/icn-card-errors' | 'db-icons/icn-card-funnel' | 'db-icons/icn-card-funnels' | 'db-icons/icn-card-insights' | 'db-icons/icn-card-library' | 'db-icons/icn-card-mapchart' | 'db-icons/icn-card-pathAnalysis' | 'db-icons/icn-card-performance' | 'db-icons/icn-card-resources' | 'db-icons/icn-card-table' | 'db-icons/icn-card-timeseries' | 'db-icons/icn-card-webVitals' | 'desktop' | 'device' | 'diagram-3' | 'dice-3' | 'dizzy' | 'door-closed' | 'doublecheck' | 'download' | 'drag' | 'edit' | 'ellipsis-v' | 'emoji-dizzy' | 'enter' | 'envelope-check' | 'envelope-paper' | 'envelope-x' | 'envelope' | 'errors-icon' | 'event/click' | 'event/click_hesitation' | 'event/clickrage' | 'event/code' | 'event/i-cursor' | 'event/input' | 'event/input_hesitation' | 'event/link' | 'event/location' | 'event/mouse_thrashing' | 'event/resize' | 'event/view' | 'exclamation-circle-fill' | 'exclamation-circle' | 'exclamation-triangle' | 'expand-wide' | 'explosion' | 'external-link-alt' | 'eye-slash-fill' | 'eye-slash' | 'eye' | 'fetch' | 'fflag-multi' | 'fflag-single' | 'file-bar-graph' | 'file-code' | 'file-medical-alt' | 'file-pdf' | 'file' | 'files' | 'filetype-js' | 'filetype-pdf' | 'filter' | 'filters/arrow-return-right' | 'filters/browser' | 'filters/click' | 'filters/clickrage' | 'filters/code' | 'filters/console' | 'filters/country' | 'filters/cpu-load' | 'filters/custom' | 'filters/device' | 'filters/dom-complete' | 'filters/duration' | 'filters/error' | 'filters/fetch-failed' | 'filters/fetch' | 'filters/file-code' | 'filters/graphql' | 'filters/i-cursor' | 'filters/input' | 'filters/lcpt' | 'filters/link' | 'filters/location' | 'filters/memory-load' | 'filters/metadata' | 'filters/os' | 'filters/perfromance-network-request' | 'filters/platform' | 'filters/referrer' | 'filters/resize' | 'filters/rev-id' | 'filters/state-action' | 'filters/tag-element' | 'filters/ttfb' | 'filters/user-alt' | 'filters/userid' | 'filters/view' | 'flag-na' | 'folder-plus' | 'folder2' | 'fullscreen' | 'funnel/cpu-fill' | 'funnel/cpu' | 'funnel/dizzy' | 'funnel/emoji-angry-fill' | 'funnel/emoji-angry' | 'funnel/emoji-dizzy-fill' | 'funnel/exclamation-circle-fill' | 'funnel/exclamation-circle' | 'funnel/file-earmark-break-fill' | 'funnel/file-earmark-break' | 'funnel/file-earmark-minus-fill' | 'funnel/file-earmark-minus' | 'funnel/file-medical-alt' | 'funnel/file-x' | 'funnel/hdd-fill' | 'funnel/hourglass-top' | 'funnel/image-fill' | 'funnel/image' | 'funnel/microchip' | 'funnel/mouse' | 'funnel/patch-exclamation-fill' | 'funnel/sd-card' | 'funnel-fill' | 'funnel-new' | 'funnel' | 'gear-fill' | 'gear' | 'geo-alt-fill-custom' | 'github' | 'graph-up-arrow' | 'graph-up' | 'grid-1x2' | 'grid-3x3' | 'grid-check' | 'grid-horizontal' | 'grid' | 'grip-horizontal' | 'hash' | 'hdd-stack' | 'headset' | 'heart-rate' | 'high-engagement' | 'history' | 'hourglass-start' | 'ic-errors' | 'ic-network' | 'ic-rage' | 'ic-resources' | 'id-card' | 'image' | 'info-circle-fill' | 'info-circle' | 'info-square' | 'info' | 'input-hesitation' | 'inspect' | 'integrations/assist' | 'integrations/bugsnag-text' | 'integrations/bugsnag' | 'integrations/cloudwatch-text' | 'integrations/cloudwatch' | 'integrations/datadog' | 'integrations/elasticsearch-text' | 'integrations/elasticsearch' | 'integrations/github' | 'integrations/graphql' | 'integrations/jira-text' | 'integrations/jira' | 'integrations/mobx' | 'integrations/newrelic-text' | 'integrations/newrelic' | 'integrations/ngrx' | 'integrations/openreplay-text' | 'integrations/openreplay' | 'integrations/redux' | 'integrations/rollbar-text' | 'integrations/rollbar' | 'integrations/segment' | 'integrations/sentry-text' | 'integrations/sentry' | 'integrations/slack-bw' | 'integrations/slack' | 'integrations/stackdriver' | 'integrations/sumologic-text' | 'integrations/sumologic' | 'integrations/teams-white' | 'integrations/teams' | 'integrations/vuejs' | 'integrations/zustand' | 'journal-code' | 'key' | 'layer-group' | 'layers-half' | 'lightbulb-on' | 'lightbulb' | 'link-45deg' | 'list-alt' | 'list-arrow' | 'list-ul' | 'list' | 'lock-alt' | 'low-disc-space' | 'magic' | 'map-marker-alt' | 'memory-ios' | 'memory' | 'mic-mute' | 'mic' | 'minus' | 'mobile' | 'mouse-alt' | 'mouse-pointer-click' | 'network' | 'next1' | 'no-dashboard' | 'no-metrics-chart' | 'no-metrics' | 'no-recordings' | 'os/android' | 'os/chrome_os' | 'os/fedora' | 'os/ios' | 'os/linux' | 'os/mac_os_x' | 'os/other' | 'os/ubuntu' | 'os/windows' | 'os' | 'pause-circle-fill' | 'pause-fill' | 'pause' | 'pdf-download' | 'pencil-stop' | 'pencil' | 'people' | 'percent' | 'performance-icon' | 'person-border' | 'person-fill' | 'person' | 'pie-chart-fill' | 'pin-fill' | 'play-circle-bold' | 'play-circle-light' | 'play-circle' | 'play-fill-new' | 'play-fill' | 'play-hover' | 'play' | 'plug' | 'plus-circle' | 'plus-lg' | 'plus' | 'pointer-sessions-search' | 'prev1' | 'pulse' | 'puzzle-piece' | 'puzzle' | 'question-circle' | 'question-lg' | 'quote-left' | 'quote-right' | 'quotes' | 'record-btn' | 'record-circle-fill' | 'record-circle' | 'record2' | 'redo-back' | 'redo' | 'redux' | 'remote-control' | 'replay-10' | 'resources-icon' | 'safe-fill' | 'safe' | 'sandglass' | 'search' | 'search_notification' | 'server' | 'share-alt' | 'shield-lock' | 'side_menu_closed' | 'side_menu_open' | 'signpost-split' | 'signup' | 'skip-forward-fill' | 'skip-forward' | 'slack' | 'slash-circle' | 'sleep' | 'sliders' | 'social/slack' | 'social/trello' | 'speedometer2' | 'spinner' | 'star-solid' | 'star' | 'step-forward' | 'stickies' | 'stop-record-circle' | 'stopwatch' | 'store' | 'sync-alt' | 'table-new' | 'table' | 'tablet-android' | 'tachometer-slow' | 'tachometer-slowest' | 'tags' | 'team-funnel' | 'telephone-fill' | 'telephone' | 'terminal' | 'text-paragraph' | 'thermometer-sun' | 'toggles' | 'tools' | 'trash' | 'turtle' | 'user-alt' | 'user-circle' | 'user-friends' | 'user-switch' | 'users' | 'vendors/graphql' | 'vendors/mobx' | 'vendors/ngrx' | 'vendors/redux' | 'vendors/vuex' | 'web-vitals' | 'wifi' | 'window-alt' | 'window-restore' | 'window-x' | 'window' | 'zoom-in'; interface Props { - name: OldIconNames; + name: IconNames; size?: number | string; width?: number | string; height?: number | string; @@ -1163,6 +1166,9 @@ const SVG = (props: Props) => { // case 'filters/state-action': case 'filters/state-action': return ; + // case 'filters/tag-element': + case 'filters/tag-element': return ; + // case 'filters/ttfb': case 'filters/ttfb': return ; @@ -1520,6 +1526,9 @@ const SVG = (props: Props) => { // case 'mouse-alt': case 'mouse-alt': return ; + // case 'mouse-pointer-click': + case 'mouse-pointer-click': return ; + case 'network': return ; @@ -1850,6 +1859,9 @@ const SVG = (props: Props) => { // case 'user-friends': case 'user-friends': return ; + // case 'user-switch': + case 'user-switch': return ; + case 'users': return ; diff --git a/frontend/app/constants/consoleLevels.js b/frontend/app/constants/consoleLevels.js index f449548d7..38c5ddb3a 100644 --- a/frontend/app/constants/consoleLevels.js +++ b/frontend/app/constants/consoleLevels.js @@ -1,5 +1,5 @@ export default { warning: 'Warnings', alert: 'Alerts', - all: 'Log Entires', + all: 'Log Entries', }; diff --git a/frontend/app/duck/search.js b/frontend/app/duck/search.js index 10cffed45..bb43947fb 100644 --- a/frontend/app/duck/search.js +++ b/frontend/app/duck/search.js @@ -8,7 +8,12 @@ import { errors as errorsRoute, isRoute } from 'App/routes'; import { fetchList as fetchSessionList, fetchAutoplayList } from './sessions'; import { fetchList as fetchErrorsList } from './errors'; import { FilterCategory, FilterKey } from 'Types/filter/filterType'; -import { filtersMap, liveFiltersMap, conditionalFiltersMap, generateFilterOptions } from 'Types/filter/newFilter'; +import { + filtersMap, + liveFiltersMap, + conditionalFiltersMap, + generateFilterOptions +} from "Types/filter/newFilter"; import { DURATION_FILTER } from 'App/constants/storageKeys'; import Period, { CUSTOM_RANGE } from 'Types/app/period'; diff --git a/frontend/app/hooks/useSessionSearchQueryHandler.ts b/frontend/app/hooks/useSessionSearchQueryHandler.ts index 110b4b72b..08db46b5e 100644 --- a/frontend/app/hooks/useSessionSearchQueryHandler.ts +++ b/frontend/app/hooks/useSessionSearchQueryHandler.ts @@ -1,38 +1,44 @@ -import { useEffect } from 'react'; +import { useEffect, useState } from 'react'; import { useHistory } from 'react-router'; import { createUrlQuery, getFiltersFromQuery } from 'App/utils/search'; interface Props { + onBeforeLoad?: () => Promise; appliedFilter: any; applyFilter: any; loading: boolean; } const useSessionSearchQueryHandler = (props: Props) => { + const [beforeHookLoaded, setBeforeHookLoaded] = useState(!props.onBeforeLoad); const { appliedFilter, applyFilter, loading } = props; const history = useHistory(); useEffect(() => { - const applyFilterFromQuery = () => { + const applyFilterFromQuery = async () => { if (!loading) { + if (props.onBeforeLoad) { + await props.onBeforeLoad(); + setBeforeHookLoaded(true); + } const filter = getFiltersFromQuery(history.location.search, appliedFilter); applyFilter(filter, true, false); } }; - applyFilterFromQuery(); + void applyFilterFromQuery(); }, [loading]); useEffect(() => { const generateUrlQuery = () => { - if (!loading) { + if (!loading && beforeHookLoaded) { const search: any = createUrlQuery(appliedFilter); history.replace({ search }); } }; generateUrlQuery(); - }, [appliedFilter, loading]); + }, [appliedFilter, loading, beforeHookLoaded]); return null; }; diff --git a/frontend/app/mstore/index.tsx b/frontend/app/mstore/index.tsx index f10a4e43f..2266f9b91 100644 --- a/frontend/app/mstore/index.tsx +++ b/frontend/app/mstore/index.tsx @@ -5,22 +5,21 @@ import UserStore from './userStore'; import RoleStore from './roleStore'; import APIClient from 'App/api_client'; import FunnelStore from './funnelStore'; -import { - services -} from 'App/services'; +import { services } from 'App/services'; import SettingsStore from './settingsStore'; import AuditStore from './auditStore'; import NotificationStore from './notificationStore'; import ErrorStore from './errorStore'; import SessionStore from './sessionStore'; import NotesStore from './notesStore'; -import BugReportStore from './bugReportStore' -import RecordingsStore from './recordingsStore' +import BugReportStore from './bugReportStore'; +import RecordingsStore from './recordingsStore'; import AssistMultiviewStore from './assistMultiviewStore'; -import WeeklyReportStore from './weeklyReportConfigStore' -import AlertStore from './alertsStore' -import FeatureFlagsStore from "./featureFlagsStore"; +import WeeklyReportStore from './weeklyReportConfigStore'; +import AlertStore from './alertsStore'; +import FeatureFlagsStore from './featureFlagsStore'; import UxtestingStore from './uxtestingStore'; +import TagWatchStore from './tagWatchStore'; export class RootStore { dashboardStore: DashboardStore; @@ -37,10 +36,11 @@ export class RootStore { bugReportStore: BugReportStore; recordingsStore: RecordingsStore; assistMultiviewStore: AssistMultiviewStore; - weeklyReportStore: WeeklyReportStore - alertsStore: AlertStore - featureFlagsStore: FeatureFlagsStore - uxtestingStore: UxtestingStore + weeklyReportStore: WeeklyReportStore; + alertsStore: AlertStore; + featureFlagsStore: FeatureFlagsStore; + uxtestingStore: UxtestingStore; + tagWatchStore: TagWatchStore; constructor() { this.dashboardStore = new DashboardStore(); @@ -61,13 +61,14 @@ export class RootStore { this.alertsStore = new AlertStore(); this.featureFlagsStore = new FeatureFlagsStore(); this.uxtestingStore = new UxtestingStore(); + this.tagWatchStore = new TagWatchStore(); } initClient() { const client = new APIClient(); - services.forEach(service => { + services.forEach((service) => { service.initClient(client); - }) + }); } } diff --git a/frontend/app/mstore/tagWatchStore.ts b/frontend/app/mstore/tagWatchStore.ts new file mode 100644 index 000000000..433b27451 --- /dev/null +++ b/frontend/app/mstore/tagWatchStore.ts @@ -0,0 +1,66 @@ +import { makeAutoObservable } from 'mobx'; +import { tagWatchService } from 'App/services'; +import { CreateTag, Tag } from 'App/services/TagWatchService'; + +export default class TagWatchStore { + tags: Tag[] = []; + isLoading = false; + + constructor() { + makeAutoObservable(this); + } + + setTags = (tags: Tag[]) => { + this.tags = tags; + }; + + setLoading = (loading: boolean) => { + this.isLoading = loading; + }; + + getTags = async () => { + if (this.isLoading) { + return; + } + this.setLoading(true); + try { + const tags: Tag[] = await tagWatchService.getTags(); + this.setTags(tags); + return tags; + } catch (e) { + console.error(e); + } finally { + this.setLoading(false); + } + }; + + createTag = async (data: CreateTag) => { + try { + const tagId: number = await tagWatchService.createTag(data); + return tagId; + } catch (e) { + console.error(e); + } + }; + + deleteTag = async (id: number) => { + try { + await tagWatchService.deleteTag(id); + this.setTags(this.tags.filter((t) => t.tagId !== id)); + } catch (e) { + console.error(e); + } + }; + + updateTagName = async (id: number, name: string) => { + try { + await tagWatchService.updateTagName(id, name); + const updatedTag = this.tags.find((t) => t.tagId === id) + if (updatedTag) { + this.setTags(this.tags.map((t) => t.tagId === id ? { ...updatedTag, name } : t)); + } + } catch (e) { + console.error(e); + } + }; +} diff --git a/frontend/app/player/web/Screen/Inspector.ts b/frontend/app/player/web/Screen/Inspector.ts index 028f7f919..330567114 100644 --- a/frontend/app/player/web/Screen/Inspector.ts +++ b/frontend/app/player/web/Screen/Inspector.ts @@ -1,19 +1,10 @@ import type Screen from './Screen' import type Marker from './Marker' -//import { select } from 'optimal-select'; - export default class Inspector { - // private captureCallbacks = []; - // private bubblingCallbacks = []; constructor(private screen: Screen, private marker: Marker) {} private onMouseMove = (e: MouseEvent) => { - // const { overlay } = this.screen; - // if (!overlay.contains(e.target)) { - // return; - // } - e.stopPropagation(); const target = this.screen.getElementFromPoint(e); @@ -34,30 +25,15 @@ export default class Inspector { return } this.clickCallback && this.clickCallback({ target }); - // const targets = [ target ]; - // while (target.parentElement !== null) { - // target = target.parentElement; - // targets.push(target); - // } - // for (let i = targets.length - 1; i >= 0; i--) { - // for (let j = 0; j < this.captureCallbacks.length; j++) { - // this.captureCallbacks[j]({ target: targets[i] }); - // } - // } - - // onTargetClick(select(markedTarget, { root: this.screen.document })); } - // addClickListener(callback, useCapture = false) { - // if (useCapture) { - // this.captureCallbacks.push(callback); - // } else { - // //this.bubblingCallbacks.push(callback); - // } - // } + addClickListener(callback: (el: { target: Element }) => void) { + this.clickCallback = callback + } - private clickCallback: (e: { target: Element }) => void | null = null - enable(clickCallback?: Inspector['clickCallback']) { + private clickCallback: (e: { target: Element }) => void = () => {} + + enable() { this.screen.overlay.addEventListener('mousemove', this.onMouseMove) this.screen.overlay.addEventListener('mouseleave', this.onOverlayLeave) this.screen.overlay.addEventListener('click', this.onMarkClick) diff --git a/frontend/app/player/web/Screen/Marker.ts b/frontend/app/player/web/Screen/Marker.ts index d55ed5be3..25d22ac92 100644 --- a/frontend/app/player/web/Screen/Marker.ts +++ b/frontend/app/player/web/Screen/Marker.ts @@ -1,5 +1,6 @@ -import type Screen from './Screen' +import type Screen from './Screen'; import styles from './marker.module.css'; +import { finder } from '@medv/finder'; const metaCharsMap = { '&': '&', @@ -9,7 +10,7 @@ const metaCharsMap = { "'": ''', '/': '/', '`': '`', - '=': '=' + '=': '=', }; function escapeHtml(str: string) { @@ -19,29 +20,30 @@ function escapeHtml(str: string) { }); } - function escapeRegExp(string: string) { - return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } function safeString(string: string) { - return (escapeHtml(escapeRegExp(string))) + return escapeHtml(escapeRegExp(string)); } export default class Marker { private _target: Element | null = null; private selector: string | null = null; - private tooltip: HTMLDivElement - private marker: HTMLDivElement + private readonly tooltip: HTMLDivElement; + private readonly tooltipSelector: HTMLDivElement; + private readonly tooltipHint: HTMLDivElement; + private marker: HTMLDivElement; - constructor(overlay: HTMLElement, private readonly screen: Screen) { + constructor(private readonly overlay: HTMLElement, private readonly screen: Screen) { this.tooltip = document.createElement('div'); this.tooltip.className = styles.tooltip; - this.tooltip.appendChild(document.createElement('div')); - - const htmlStr = document.createElement('div'); - htmlStr.innerHTML = 'Right-click > Inspect for more details.'; - this.tooltip.appendChild(htmlStr); + this.tooltipSelector = document.createElement('div'); + this.tooltipHint = document.createElement('div'); + this.tooltipHint.innerText = '(click to tag element)' + this.tooltipHint.className = styles.tooltipHint; + this.tooltip.append(this.tooltipSelector, this.tooltipHint); const marker = document.createElement('div'); marker.className = styles.marker; @@ -78,19 +80,17 @@ export default class Marker { } unmark() { - this.mark(null) + this.mark(null); } private autodefineTarget() { - // TODO: put to Screen - if (this.selector) { + if (this.selector && this.screen.document) { try { const fitTargets = this.screen.document.querySelectorAll(this.selector); if (fitTargets.length === 0) { this._target = null; } else { - // TODO: fix getCursorTarget()? - // this._target = fitTargets[0]; + this._target = fitTargets[0]; // const cursorTarget = this.screen.getCursorTarget(); // fitTargets.forEach((target) => { // if (target.contains(cursorTarget)) { @@ -108,27 +108,23 @@ export default class Marker { markBySelector(selector: string) { this.selector = selector; + this.lastSelector = selector; this.autodefineTarget(); this.redraw(); } + lastSelector = ''; private getTagString(el: Element) { - const attrs = el.attributes; - let str = `${el.tagName.toLowerCase()}`; - - for (let i = 0; i < attrs.length; i++) { - let k = attrs[i]; - const attribute = k.name; - if (attribute === 'class') { - str += `${'.' + safeString(k.value).split(' ').join('.')}`; - } - - if (attribute === 'id') { - str += `${'#' + safeString(k.value).split(' ').join('#')}`; - } - } - - return str; + if (!this.screen.document) return ''; + const selector = finder(el, { + root: this.screen.document.body, + seedMinLength: 3, + optimizedMinLength: 2, + threshold: 1000, + maxNumberOfTries: 10_000, + }); + this.lastSelector = selector; + return selector } redraw() { @@ -146,6 +142,17 @@ export default class Marker { this.marker.style.width = rect.width + 'px'; this.marker.style.height = rect.height + 'px'; - this.tooltip.firstChild.innerHTML = this.getTagString(this._target); + const replayScale = this.screen.getScale() + if (replayScale < 1) { + const upscale = (1 / replayScale).toFixed(3); + const yShift = ((1 - replayScale)/2) * 100; + this.tooltip.style.transform = `scale(${upscale}) translateY(-${yShift + 0.5}%)` + } + this.tooltipSelector.textContent = this.getTagString(this._target); + } + + + clean() { + this.marker.remove(); } } diff --git a/frontend/app/player/web/Screen/marker.module.css b/frontend/app/player/web/Screen/marker.module.css index fe88efc9e..997c8f0f9 100644 --- a/frontend/app/player/web/Screen/marker.module.css +++ b/frontend/app/player/web/Screen/marker.module.css @@ -39,24 +39,21 @@ position: absolute; left: 0; bottom: 100%; - padding: 15px; + padding: 8px; + max-width: 600px; box-shadow: 2px 2px 1px rgba(40, 40, 100, .3); z-index: 999; border-radius: 3px; background-color: #202124; - min-width: 400px; - font-size: 20px !important; + min-width: 300px; + font-size: 16px !important; + color:#9BBBDC; +} - & div:first-child { - max-width: 600px; - height: 22px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - & div:last-child { - font-size: 18px; - margin-top: 10px; - color: $tealx; - } +.tooltipHint { + margin-top: 4px; + font-size: 14px; + width: 100%; + text-align: center; + color: rgba(138, 170, 201, 0.8); } \ No newline at end of file diff --git a/frontend/app/player/web/WebPlayer.ts b/frontend/app/player/web/WebPlayer.ts index 23b36ed7c..0a23f2c7d 100644 --- a/frontend/app/player/web/WebPlayer.ts +++ b/frontend/app/player/web/WebPlayer.ts @@ -16,6 +16,8 @@ export default class WebPlayer extends Player { ...TargetMarker.INITIAL_STATE, ...MessageManager.INITIAL_STATE, ...MessageLoader.INITIAL_STATE, + ...InspectorController.INITIAL_STATE, + liveTimeTravel: false, inspectorMode: false, } @@ -65,7 +67,7 @@ export default class WebPlayer extends Player { } this.targetMarker = new TargetMarker(this.screen, wpState) - this.inspectorController = new InspectorController(screen) + this.inspectorController = new InspectorController(screen, wpState) const endTime = session.duration?.valueOf() || 0 @@ -126,7 +128,7 @@ export default class WebPlayer extends Player { this.inspectorController.marker?.mark(e) } - toggleInspectorMode = (flag: boolean, clickCallback?: Parameters[0]) => { + toggleInspectorMode = (flag: boolean) => { if (typeof flag !== 'boolean') { const { inspectorMode } = this.wpState.get() flag = !inspectorMode @@ -135,13 +137,17 @@ export default class WebPlayer extends Player { if (flag) { this.pause() this.wpState.update({ inspectorMode: true }) - return this.inspectorController.enableInspector(clickCallback) + return this.inspectorController.enableInspector() } else { this.inspectorController.disableInspector() this.wpState.update({ inspectorMode: false }) } } + markBySelector = (selector: string) => { + this.inspectorController.markBySelector(selector) + } + // Target Marker setActiveTarget = (...args: Parameters) => { this.targetMarker.setActiveTarget(...args) diff --git a/frontend/app/player/web/addons/InspectorController.ts b/frontend/app/player/web/addons/InspectorController.ts index 7c4c48690..31832ad8f 100644 --- a/frontend/app/player/web/addons/InspectorController.ts +++ b/frontend/app/player/web/addons/InspectorController.ts @@ -1,72 +1,58 @@ -import Marker from '../Screen/Marker' -import Inspector from '../Screen/Inspector' -import Screen from '../Screen/Screen' -import type { Dimensions } from '../Screen/types' - +import type { Store } from 'Player'; +import { State } from 'Player/web/addons/TargetMarker'; +import Marker from '../Screen/Marker'; +import Inspector from '../Screen/Inspector'; +import Screen, { ScaleMode } from '../Screen/Screen'; +import type { Dimensions } from '../Screen/types'; export default class InspectorController { - private substitutor: Screen | null = null - private inspector: Inspector | null = null - marker: Marker | null = null - constructor(private screen: Screen) { - screen.overlay.addEventListener('contextmenu', () => { - screen.overlay.style.display = 'none' - const doc = screen.document - if (!doc) { return } - const returnOverlay = () => { - screen.overlay.style.display = 'block' - doc.removeEventListener('mousemove', returnOverlay) - doc.removeEventListener('mouseclick', returnOverlay) // TODO: prevent default in case of input selection - } - doc.addEventListener('mousemove', returnOverlay) - doc.addEventListener('mouseclick', returnOverlay) - }) + static INITIAL_STATE = { + tagSelector: '', + } + private substitutor: Screen | null = null; + private inspector: Inspector | null = null; + marker: Marker | null = null; + + constructor(private screen: Screen, private readonly store: Store<{ tagSelector: string }>) { + screen.overlay.addEventListener('contextmenu', () => { + screen.overlay.style.display = 'none'; + const doc = screen.document; + if (!doc) { + return; + } + const returnOverlay = () => { + screen.overlay.style.display = 'block'; + doc.removeEventListener('mousemove', returnOverlay); + doc.removeEventListener('mouseclick', returnOverlay); // TODO: prevent default in case of input selection + }; + doc.addEventListener('mousemove', returnOverlay); + doc.addEventListener('mouseclick', returnOverlay); + }); } scale(dims: Dimensions) { - if (this.substitutor) { - this.substitutor.scale(dims) - } + this.screen.scale(dims); } - enableInspector(clickCallback?: (e: { target: Element }) => void): Document | null { - const parent = this.screen.getParentElement() - if (!parent) return null; - if (!this.substitutor) { - this.substitutor = new Screen() - this.marker = new Marker(this.substitutor.overlay, this.substitutor) - this.inspector = new Inspector(this.substitutor, this.marker) - //this.inspector.addClickListener(clickCallback, true) - this.substitutor.attach(parent) - } + enableInspector(): Document | null { + this.marker = new Marker(this.screen.overlay, this.screen); + this.inspector = new Inspector(this.screen, this.marker); + this.inspector.addClickListener(() => { + this.store.update({ tagSelector: this.marker?.lastSelector ?? '' }) + }); - this.substitutor.display(false) + this.inspector?.enable(); + return this.screen.document; + } - const docElement = this.screen.document?.documentElement // this.substitutor.document?.importNode( - const doc = this.substitutor.document - if (doc && docElement) { - doc.open() - doc.write(docElement.outerHTML) - doc.close() - - // TODO! : copy stylesheets & cssRules? - } - this.screen.display(false); - this.inspector.enable(clickCallback); - this.substitutor.display(true); - return doc; + markBySelector(selector: string) { + this.marker?.markBySelector(selector); } disableInspector() { - if (this.substitutor) { - const doc = this.substitutor.document; - if (doc) { - doc.documentElement.innerHTML = ""; - } - this.inspector.clean(); - this.substitutor.display(false); - } - this.screen.display(true); + this.inspector?.clean(); + this.inspector = null; + this.marker?.clean(); + this.marker = null; } - } diff --git a/frontend/app/player/web/messages/RawMessageReader.gen.ts b/frontend/app/player/web/messages/RawMessageReader.gen.ts index a392655ba..1ca3c9ce6 100644 --- a/frontend/app/player/web/messages/RawMessageReader.gen.ts +++ b/frontend/app/player/web/messages/RawMessageReader.gen.ts @@ -741,6 +741,14 @@ export default class RawMessageReader extends PrimitiveReader { }; } + case 120: { + const tagId = this.readInt(); if (tagId === null) { return resetPointer() } + return { + tp: MType.TagTrigger, + tagId, + }; + } + case 93: { const timestamp = this.readUint(); if (timestamp === null) { return resetPointer() } const length = this.readUint(); if (length === null) { return resetPointer() } diff --git a/frontend/app/player/web/messages/message.gen.ts b/frontend/app/player/web/messages/message.gen.ts index b121ae69f..c6286dc32 100644 --- a/frontend/app/player/web/messages/message.gen.ts +++ b/frontend/app/player/web/messages/message.gen.ts @@ -63,6 +63,7 @@ import type { RawTabChange, RawTabData, RawCanvasNode, + RawTagTrigger, RawIosEvent, RawIosScreenChanges, RawIosClickEvent, @@ -196,6 +197,8 @@ export type TabData = RawTabData & Timed export type CanvasNode = RawCanvasNode & Timed +export type TagTrigger = RawTagTrigger & Timed + export type IosEvent = RawIosEvent & Timed export type IosScreenChanges = RawIosScreenChanges & Timed diff --git a/frontend/app/player/web/messages/raw.gen.ts b/frontend/app/player/web/messages/raw.gen.ts index ca3af1886..291ead61d 100644 --- a/frontend/app/player/web/messages/raw.gen.ts +++ b/frontend/app/player/web/messages/raw.gen.ts @@ -61,6 +61,7 @@ export const enum MType { TabChange = 117, TabData = 118, CanvasNode = 119, + TagTrigger = 120, IosEvent = 93, IosScreenChanges = 96, IosClickEvent = 100, @@ -494,6 +495,11 @@ export interface RawCanvasNode { timestamp: number, } +export interface RawTagTrigger { + tp: MType.TagTrigger, + tagId: number, +} + export interface RawIosEvent { tp: MType.IosEvent, timestamp: number, @@ -586,4 +592,4 @@ export interface RawIosIssueEvent { } -export type RawMessage = RawTimestamp | RawSetPageLocation | RawSetViewportSize | RawSetViewportScroll | RawCreateDocument | RawCreateElementNode | RawCreateTextNode | RawMoveNode | RawRemoveNode | RawSetNodeAttribute | RawRemoveNodeAttribute | RawSetNodeData | RawSetCssData | RawSetNodeScroll | RawSetInputValue | RawSetInputChecked | RawMouseMove | RawNetworkRequestDeprecated | RawConsoleLog | RawCssInsertRule | RawCssDeleteRule | RawFetch | RawProfiler | RawOTable | RawRedux | RawVuex | RawMobX | RawNgRx | RawGraphQl | RawPerformanceTrack | RawStringDict | RawSetNodeAttributeDict | RawResourceTimingDeprecated | RawConnectionInformation | RawSetPageVisibility | RawLoadFontFace | RawSetNodeFocus | RawLongTask | RawSetNodeAttributeURLBased | RawSetCssDataURLBased | RawCssInsertRuleURLBased | RawMouseClick | RawCreateIFrameDocument | RawAdoptedSsReplaceURLBased | RawAdoptedSsReplace | RawAdoptedSsInsertRuleURLBased | RawAdoptedSsInsertRule | RawAdoptedSsDeleteRule | RawAdoptedSsAddOwner | RawAdoptedSsRemoveOwner | RawZustand | RawNetworkRequest | RawWsChannel | RawSelectionChange | RawMouseThrashing | RawResourceTiming | RawTabChange | RawTabData | RawCanvasNode | RawIosEvent | RawIosScreenChanges | RawIosClickEvent | RawIosInputEvent | RawIosPerformanceEvent | RawIosLog | RawIosInternalError | RawIosNetworkCall | RawIosSwipeEvent | RawIosIssueEvent; +export type RawMessage = RawTimestamp | RawSetPageLocation | RawSetViewportSize | RawSetViewportScroll | RawCreateDocument | RawCreateElementNode | RawCreateTextNode | RawMoveNode | RawRemoveNode | RawSetNodeAttribute | RawRemoveNodeAttribute | RawSetNodeData | RawSetCssData | RawSetNodeScroll | RawSetInputValue | RawSetInputChecked | RawMouseMove | RawNetworkRequestDeprecated | RawConsoleLog | RawCssInsertRule | RawCssDeleteRule | RawFetch | RawProfiler | RawOTable | RawRedux | RawVuex | RawMobX | RawNgRx | RawGraphQl | RawPerformanceTrack | RawStringDict | RawSetNodeAttributeDict | RawResourceTimingDeprecated | RawConnectionInformation | RawSetPageVisibility | RawLoadFontFace | RawSetNodeFocus | RawLongTask | RawSetNodeAttributeURLBased | RawSetCssDataURLBased | RawCssInsertRuleURLBased | RawMouseClick | RawCreateIFrameDocument | RawAdoptedSsReplaceURLBased | RawAdoptedSsReplace | RawAdoptedSsInsertRuleURLBased | RawAdoptedSsInsertRule | RawAdoptedSsDeleteRule | RawAdoptedSsAddOwner | RawAdoptedSsRemoveOwner | RawZustand | RawNetworkRequest | RawWsChannel | RawSelectionChange | RawMouseThrashing | RawResourceTiming | RawTabChange | RawTabData | RawCanvasNode | RawTagTrigger | RawIosEvent | RawIosScreenChanges | RawIosClickEvent | RawIosInputEvent | RawIosPerformanceEvent | RawIosLog | RawIosInternalError | RawIosNetworkCall | RawIosSwipeEvent | RawIosIssueEvent; diff --git a/frontend/app/player/web/messages/tracker-legacy.gen.ts b/frontend/app/player/web/messages/tracker-legacy.gen.ts index 09d7576c6..48b0ed623 100644 --- a/frontend/app/player/web/messages/tracker-legacy.gen.ts +++ b/frontend/app/player/web/messages/tracker-legacy.gen.ts @@ -62,6 +62,7 @@ export const TP_MAP = { 117: MType.TabChange, 118: MType.TabData, 119: MType.CanvasNode, + 120: MType.TagTrigger, 93: MType.IosEvent, 96: MType.IosScreenChanges, 100: MType.IosClickEvent, diff --git a/frontend/app/player/web/messages/tracker.gen.ts b/frontend/app/player/web/messages/tracker.gen.ts index 6ad41e111..47d266617 100644 --- a/frontend/app/player/web/messages/tracker.gen.ts +++ b/frontend/app/player/web/messages/tracker.gen.ts @@ -509,8 +509,13 @@ type TrCanvasNode = [ timestamp: number, ] +type TrTagTrigger = [ + type: 120, + tagId: number, +] -export type TrackerMessage = TrTimestamp | TrSetPageLocation | TrSetViewportSize | TrSetViewportScroll | TrCreateDocument | TrCreateElementNode | TrCreateTextNode | TrMoveNode | TrRemoveNode | TrSetNodeAttribute | TrRemoveNodeAttribute | TrSetNodeData | TrSetNodeScroll | TrSetInputTarget | TrSetInputValue | TrSetInputChecked | TrMouseMove | TrNetworkRequestDeprecated | TrConsoleLog | TrPageLoadTiming | TrPageRenderTiming | TrCustomEvent | TrUserID | TrUserAnonymousID | TrMetadata | TrCSSInsertRule | TrCSSDeleteRule | TrFetch | TrProfiler | TrOTable | TrStateAction | TrRedux | TrVuex | TrMobX | TrNgRx | TrGraphQL | TrPerformanceTrack | TrStringDict | TrSetNodeAttributeDict | TrResourceTimingDeprecated | TrConnectionInformation | TrSetPageVisibility | TrLoadFontFace | TrSetNodeFocus | TrLongTask | TrSetNodeAttributeURLBased | TrSetCSSDataURLBased | TrTechnicalInfo | TrCustomIssue | TrCSSInsertRuleURLBased | TrMouseClick | TrCreateIFrameDocument | TrAdoptedSSReplaceURLBased | TrAdoptedSSInsertRuleURLBased | TrAdoptedSSDeleteRule | TrAdoptedSSAddOwner | TrAdoptedSSRemoveOwner | TrJSException | TrZustand | TrBatchMetadata | TrPartitionedMessage | TrNetworkRequest | TrWSChannel | TrInputChange | TrSelectionChange | TrMouseThrashing | TrUnbindNodes | TrResourceTiming | TrTabChange | TrTabData | TrCanvasNode + +export type TrackerMessage = TrTimestamp | TrSetPageLocation | TrSetViewportSize | TrSetViewportScroll | TrCreateDocument | TrCreateElementNode | TrCreateTextNode | TrMoveNode | TrRemoveNode | TrSetNodeAttribute | TrRemoveNodeAttribute | TrSetNodeData | TrSetNodeScroll | TrSetInputTarget | TrSetInputValue | TrSetInputChecked | TrMouseMove | TrNetworkRequestDeprecated | TrConsoleLog | TrPageLoadTiming | TrPageRenderTiming | TrCustomEvent | TrUserID | TrUserAnonymousID | TrMetadata | TrCSSInsertRule | TrCSSDeleteRule | TrFetch | TrProfiler | TrOTable | TrStateAction | TrRedux | TrVuex | TrMobX | TrNgRx | TrGraphQL | TrPerformanceTrack | TrStringDict | TrSetNodeAttributeDict | TrResourceTimingDeprecated | TrConnectionInformation | TrSetPageVisibility | TrLoadFontFace | TrSetNodeFocus | TrLongTask | TrSetNodeAttributeURLBased | TrSetCSSDataURLBased | TrTechnicalInfo | TrCustomIssue | TrCSSInsertRuleURLBased | TrMouseClick | TrCreateIFrameDocument | TrAdoptedSSReplaceURLBased | TrAdoptedSSInsertRuleURLBased | TrAdoptedSSDeleteRule | TrAdoptedSSAddOwner | TrAdoptedSSRemoveOwner | TrJSException | TrZustand | TrBatchMetadata | TrPartitionedMessage | TrNetworkRequest | TrWSChannel | TrInputChange | TrSelectionChange | TrMouseThrashing | TrUnbindNodes | TrResourceTiming | TrTabChange | TrTabData | TrCanvasNode | TrTagTrigger export default function translate(tMsg: TrackerMessage): RawMessage | null { switch(tMsg[0]) { @@ -1028,6 +1033,13 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { } } + case 120: { + return { + tp: MType.TagTrigger, + tagId: tMsg[1], + } + } + default: return null } diff --git a/frontend/app/services/TagWatchService.ts b/frontend/app/services/TagWatchService.ts new file mode 100644 index 000000000..f8c9ff461 --- /dev/null +++ b/frontend/app/services/TagWatchService.ts @@ -0,0 +1,38 @@ +import BaseService from "App/services/BaseService"; + +export interface CreateTag { + name: string; + selector: string; + ignoreClickRage: boolean; + ignoreDeadClick: boolean; +} + +export interface Tag extends CreateTag { + tagId: number; +} + +export default class TagWatchService extends BaseService { + createTag(data: CreateTag) { + return this.client.post('/tags', data) + .then(r => r.json()) + .then((response: { data: any; }) => response.data || {}) + } + + getTags() { + return this.client.get('/tags') + .then(r => r.json()) + .then((response: { data: any; }) => response.data || {}) + } + + deleteTag(id: number) { + return this.client.delete(`/tags/${id}`) + .then(r => r.json()) + .then((response: { data: any; }) => response.data || {}) + } + + updateTagName(id: number, name: string) { + return this.client.put(`/tags/${id}`, { name }) + .then(r => r.json()) + .then((response: { data: any; }) => response.data || {}) + } +} \ No newline at end of file diff --git a/frontend/app/services/index.ts b/frontend/app/services/index.ts index 14d6af804..100c81b7b 100644 --- a/frontend/app/services/index.ts +++ b/frontend/app/services/index.ts @@ -1,23 +1,24 @@ -import DashboardService from "./DashboardService"; -import MetricService from "./MetricService"; -import FunnelService from "./FunnelService"; -import SessionSerivce from "./SessionService"; -import UserService from "./UserService"; +import DashboardService from './DashboardService'; +import MetricService from './MetricService'; +import FunnelService from './FunnelService'; +import SessionService from './SessionService'; +import UserService from './UserService'; import AuditService from './AuditService'; -import ErrorService from "./ErrorService"; -import NotesService from "./NotesService"; -import RecordingsService from "./RecordingsService"; -import ConfigService from './ConfigService' -import AlertsService from './AlertsService' -import WebhookService from './WebhookService' -import HealthService from "./HealthService"; -import FFlagsService from "App/services/FFlagsService"; -import AssistStatsService from './AssistStatsService' -import UxtestingService from './UxtestingService' +import ErrorService from './ErrorService'; +import NotesService from './NotesService'; +import RecordingsService from './RecordingsService'; +import ConfigService from './ConfigService'; +import AlertsService from './AlertsService'; +import WebhookService from './WebhookService'; +import HealthService from './HealthService'; +import FFlagsService from 'App/services/FFlagsService'; +import AssistStatsService from './AssistStatsService'; +import UxtestingService from './UxtestingService'; +import TagWatchService from 'App/services/TagWatchService'; -export const dashboardService = new DashboardService(); +export const dashboardService = new DashboardService(); export const metricService = new MetricService(); -export const sessionService = new SessionSerivce(); +export const sessionService = new SessionService(); export const userService = new UserService(); export const funnelService = new FunnelService(); export const auditService = new AuditService(); @@ -36,6 +37,8 @@ export const assistStatsService = new AssistStatsService(); export const uxtestingService = new UxtestingService(); +export const tagWatchService = new TagWatchService(); + export const services = [ dashboardService, metricService, @@ -52,5 +55,6 @@ export const services = [ healthService, fflagsService, assistStatsService, - uxtestingService -] \ No newline at end of file + uxtestingService, + tagWatchService, +]; diff --git a/frontend/app/svg/icons/filters/tag-element.svg b/frontend/app/svg/icons/filters/tag-element.svg new file mode 100644 index 000000000..4f60cfef1 --- /dev/null +++ b/frontend/app/svg/icons/filters/tag-element.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/frontend/app/svg/icons/mouse-pointer-click.svg b/frontend/app/svg/icons/mouse-pointer-click.svg new file mode 100644 index 000000000..d3578f05d --- /dev/null +++ b/frontend/app/svg/icons/mouse-pointer-click.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/frontend/app/svg/icons/user-switch.svg b/frontend/app/svg/icons/user-switch.svg new file mode 100644 index 000000000..6ea7b910d --- /dev/null +++ b/frontend/app/svg/icons/user-switch.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/frontend/app/types/filter/filterType.ts b/frontend/app/types/filter/filterType.ts index f7dc00d37..c6855dfd5 100644 --- a/frontend/app/types/filter/filterType.ts +++ b/frontend/app/types/filter/filterType.ts @@ -64,6 +64,8 @@ export const setQueryParamKeyFromFilterkey = (filterKey: string) => { return 'duration'; case FilterKey.FEATURE_FLAG: return 'feature_flag'; + case FilterKey.TAGGED_ELEMENT: + return 'tnw' } }; @@ -149,6 +151,8 @@ export const getFilterKeyTypeByKey = (key: string) => { return FilterKey.DURATION; case FilterKey.FEATURE_FLAG: return 'feature_flag'; + case 'tnw': + return FilterKey.TAGGED_ELEMENT } }; @@ -306,4 +310,5 @@ export enum FilterKey { CLICKMAP_URL = 'clickMapUrl', FEATURE_FLAG = 'featureFlag', + TAGGED_ELEMENT = 'tag', } diff --git a/frontend/app/types/filter/newFilter.js b/frontend/app/types/filter/newFilter.js index 20542c3c8..6867234db 100644 --- a/frontend/app/types/filter/newFilter.js +++ b/frontend/app/types/filter/newFilter.js @@ -262,6 +262,17 @@ export const filters = [ operatorOptions: filterOptions.getOperatorsByKeys(['is']), icon: 'filters/duration' }, + { + key: FilterKey.TAGGED_ELEMENT, + type: FilterType.MULTIPLE_DROPDOWN, + category: FilterCategory.RECORDING_ATTRIBUTES, + label: 'Tagged Element', + operator: 'is', + isEvent: true, + icon: 'filters/tag-element', + operatorOptions: filterOptions.getOperatorsByKeys(['is']), + options: [], + }, { key: FilterKey.USER_COUNTRY, type: FilterType.MULTIPLE_DROPDOWN, @@ -721,6 +732,15 @@ export const addElementToFiltersMap = ( }; }; +export const addOptionsToFilter = ( + key, + options, +) => { + if (filtersMap[key] && filtersMap[key].options) { + filtersMap[key].options = options + } +} + export const addElementToFlagConditionsMap = ( category = FilterCategory.METADATA, key, @@ -830,7 +850,11 @@ export default Record({ if (type === FilterKey.METADATA) { _filter = filtersMap[filter.source]; } else { - _filter = filtersMap[type]; + if (filtersMap[filter.key]) { + _filter = filtersMap[filter.key] + } else { + _filter = filtersMap[type]; + } } } diff --git a/frontend/app/utils/search.ts b/frontend/app/utils/search.ts index 0f1355689..756945d38 100644 --- a/frontend/app/utils/search.ts +++ b/frontend/app/utils/search.ts @@ -48,18 +48,18 @@ export const getFiltersFromQuery = (search: string, filter: any) => { return; } - const entires = getQueryObject(search); - const period: any = getPeriodFromEntries(entires); - const filters = getFiltersFromEntries(entires); + const entries = getQueryObject(search); + const period: any = getPeriodFromEntries(entries); + const filters = getFiltersFromEntries(entries); return Filter({ filters, rangeValue: period.rangeName, startDate: period.start, endDate: period.end }); }; -const getFiltersFromEntries = (entires: any) => { +const getFiltersFromEntries = (entries: any) => { const _filters: any = { ...filtersMap }; const filters: any = []; - if (entires.length > 0) { - entires.forEach((item: any) => { + if (entries.length > 0) { + entries.forEach((item: any) => { if (!item.key || !item.value) { return; } @@ -72,29 +72,33 @@ const getFiltersFromEntries = (entires: any) => { const sourceArr = tmp[1] ? tmp[1].split('|') : []; const sourceOperator = sourceArr.shift(); - if (filterKey) { - filter.type = filterKey; - filter.key = filterKey; + if (filterKey && _filters[filterKey]) { + filter = _filters[filterKey]; + filter.value = valueArr; } else { - filter = _filters[item.key]; - if (!!filter) { - filter.type = filter.key; - filter.key = filter.key; + if (filterKey) { + filter.type = filterKey; + filter.key = filterKey; + } else { + filter = _filters[item.key]; + if (!!filter) { + filter.type = filter.key; + } } - } - if (!filter) { - return; - } + if (!filter) { + return; + } - filter.value = valueArr; - filter.operator = operator; - if (filter.icon === 'filters/metadata') { - filter.source = filter.type; - filter.type = 'MULTIPLE'; - } else { - filter.source = sourceArr && sourceArr.length > 0 ? sourceArr : null; - filter.sourceOperator = !!sourceOperator ? decodeURI(sourceOperator) : null; + filter.value = valueArr; + filter.operator = operator; + if (filter.icon === 'filters/metadata') { + filter.source = filter.type; + filter.type = 'MULTIPLE'; + } else { + filter.source = sourceArr && sourceArr.length > 0 ? sourceArr : null; + filter.sourceOperator = !!sourceOperator ? decodeURI(sourceOperator) : null; + } } if (!filter.filters || filter.filters.size === 0) { @@ -106,15 +110,15 @@ const getFiltersFromEntries = (entires: any) => { return filters; }; -const getPeriodFromEntries = (entires: any) => { - const rangeFilter = entires.find(({ key }: any) => key === 'range'); +const getPeriodFromEntries = (entries: any) => { + const rangeFilter = entries.find(({ key }: any) => key === 'range'); if (!rangeFilter) { return Period(); } if (rangeFilter.value === CUSTOM_RANGE) { - const start = entires.find(({ key }: any) => key === 'rStart').value; - const end = entires.find(({ key }: any) => key === 'rEnd').value; + const start = entries.find(({ key }: any) => key === 'rStart').value; + const end = entries.find(({ key }: any) => key === 'rEnd').value; return Period({ rangeName: rangeFilter.value, start, end }); } diff --git a/frontend/package.json b/frontend/package.json index de466fd57..9ca4e1423 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -27,6 +27,7 @@ "@ant-design/icons": "^5.2.5", "@babel/plugin-transform-private-methods": "^7.23.3", "@floating-ui/react-dom-interactions": "^0.10.3", + "@medv/finder": "^3.1.0", "@sentry/browser": "^5.21.1", "@svg-maps/world": "^1.0.1", "@svgr/webpack": "^6.2.1", diff --git a/frontend/scripts/icons.js b/frontend/scripts/icons.js index 50389745d..29bc01c03 100644 --- a/frontend/scripts/icons.js +++ b/frontend/scripts/icons.js @@ -112,11 +112,11 @@ ${iconPaths.map(icon => ` ${titleCase(icon.fileName)}`).join(',\n')} } from './Icons' -// export type IconNames = ${icons.map((icon) => '\'' + icon.slice(0, -4).replaceAll('-', '_') + '\'').join(' | ')}; -export type OldIconNames = ${icons.map((icon) => '\'' + icon.slice(0, -4) + '\'').join(' | ')}; +// export type NewIconNames = ${icons.map((icon) => '\'' + icon.slice(0, -4).replaceAll('-', '_') + '\'').join(' | ')}; +export type IconNames = ${icons.map((icon) => '\'' + icon.slice(0, -4) + '\'').join(' | ')}; interface Props { - name: OldIconNames; + name: IconNames; size?: number | string; width?: number | string; height?: number | string; diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 156721e02..6923417f2 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -2762,6 +2762,13 @@ __metadata: languageName: node linkType: hard +"@medv/finder@npm:^3.1.0": + version: 3.1.0 + resolution: "@medv/finder@npm:3.1.0" + checksum: 43f8f5af50d51e2609a26b7f1306cf0b170967df9aa0169d70319215957d47066c35d888d8d049d47384639e28c1bba96bc9c09558bb7368ea43c8b0b6e03836 + languageName: node + linkType: hard + "@mrmlnc/readdir-enhanced@npm:^2.2.1": version: 2.2.1 resolution: "@mrmlnc/readdir-enhanced@npm:2.2.1" @@ -18091,6 +18098,7 @@ __metadata: "@babel/runtime": ^7.23.2 "@floating-ui/react-dom-interactions": ^0.10.3 "@jest/globals": ^29.7.0 + "@medv/finder": ^3.1.0 "@openreplay/sourcemap-uploader": ^3.0.8 "@sentry/browser": ^5.21.1 "@storybook/addon-actions": ^6.5.12 diff --git a/mobs/messages.rb b/mobs/messages.rb index 9c6b7e0ba..02954dfad 100644 --- a/mobs/messages.rb +++ b/mobs/messages.rb @@ -518,6 +518,10 @@ message 119, 'CanvasNode' do uint 'Timestamp' end +message 120, 'TagTrigger', :replayer => :devtools do + int 'TagId' +end + ## Backend-only message 125, 'IssueEvent', :replayer => false, :tracker => false do uint 'MessageID' diff --git a/tracker/tracker/package.json b/tracker/tracker/package.json index b99cc9dfb..a996172db 100644 --- a/tracker/tracker/package.json +++ b/tracker/tracker/package.json @@ -1,7 +1,7 @@ { "name": "@openreplay/tracker", "description": "The OpenReplay tracker main package", - "version": "12.0.0", + "version": "12.0.1-2", "keywords": [ "logging", "replay" diff --git a/tracker/tracker/src/common/messages.gen.ts b/tracker/tracker/src/common/messages.gen.ts index 188ba2caa..be0e23b4a 100644 --- a/tracker/tracker/src/common/messages.gen.ts +++ b/tracker/tracker/src/common/messages.gen.ts @@ -73,6 +73,7 @@ export declare const enum Type { TabChange = 117, TabData = 118, CanvasNode = 119, + TagTrigger = 120, } @@ -580,6 +581,11 @@ export type CanvasNode = [ /*timestamp:*/ number, ] +export type TagTrigger = [ + /*type:*/ Type.TagTrigger, + /*tagId:*/ number, +] -type Message = Timestamp | SetPageLocation | SetViewportSize | SetViewportScroll | CreateDocument | CreateElementNode | CreateTextNode | MoveNode | RemoveNode | SetNodeAttribute | RemoveNodeAttribute | SetNodeData | SetNodeScroll | SetInputTarget | SetInputValue | SetInputChecked | MouseMove | NetworkRequestDeprecated | ConsoleLog | PageLoadTiming | PageRenderTiming | CustomEvent | UserID | UserAnonymousID | Metadata | CSSInsertRule | CSSDeleteRule | Fetch | Profiler | OTable | StateAction | Redux | Vuex | MobX | NgRx | GraphQL | PerformanceTrack | StringDict | SetNodeAttributeDict | ResourceTimingDeprecated | ConnectionInformation | SetPageVisibility | LoadFontFace | SetNodeFocus | LongTask | SetNodeAttributeURLBased | SetCSSDataURLBased | TechnicalInfo | CustomIssue | CSSInsertRuleURLBased | MouseClick | CreateIFrameDocument | AdoptedSSReplaceURLBased | AdoptedSSInsertRuleURLBased | AdoptedSSDeleteRule | AdoptedSSAddOwner | AdoptedSSRemoveOwner | JSException | Zustand | BatchMetadata | PartitionedMessage | NetworkRequest | WSChannel | InputChange | SelectionChange | MouseThrashing | UnbindNodes | ResourceTiming | TabChange | TabData | CanvasNode + +type Message = Timestamp | SetPageLocation | SetViewportSize | SetViewportScroll | CreateDocument | CreateElementNode | CreateTextNode | MoveNode | RemoveNode | SetNodeAttribute | RemoveNodeAttribute | SetNodeData | SetNodeScroll | SetInputTarget | SetInputValue | SetInputChecked | MouseMove | NetworkRequestDeprecated | ConsoleLog | PageLoadTiming | PageRenderTiming | CustomEvent | UserID | UserAnonymousID | Metadata | CSSInsertRule | CSSDeleteRule | Fetch | Profiler | OTable | StateAction | Redux | Vuex | MobX | NgRx | GraphQL | PerformanceTrack | StringDict | SetNodeAttributeDict | ResourceTimingDeprecated | ConnectionInformation | SetPageVisibility | LoadFontFace | SetNodeFocus | LongTask | SetNodeAttributeURLBased | SetCSSDataURLBased | TechnicalInfo | CustomIssue | CSSInsertRuleURLBased | MouseClick | CreateIFrameDocument | AdoptedSSReplaceURLBased | AdoptedSSInsertRuleURLBased | AdoptedSSDeleteRule | AdoptedSSAddOwner | AdoptedSSRemoveOwner | JSException | Zustand | BatchMetadata | PartitionedMessage | NetworkRequest | WSChannel | InputChange | SelectionChange | MouseThrashing | UnbindNodes | ResourceTiming | TabChange | TabData | CanvasNode | TagTrigger export default Message diff --git a/tracker/tracker/src/main/app/index.ts b/tracker/tracker/src/main/app/index.ts index 869689959..b140328b9 100644 --- a/tracker/tracker/src/main/app/index.ts +++ b/tracker/tracker/src/main/app/index.ts @@ -1,6 +1,6 @@ import ConditionsManager from '../modules/conditionsManager.js' import FeatureFlags from '../modules/featureFlags.js' -import type Message from './messages.gen.js' +import Message, { TagTrigger } from './messages.gen.js' import { Timestamp, Metadata, @@ -34,6 +34,7 @@ import type { Options as SessOptions } from './session.js' import type { Options as NetworkOptions } from '../modules/network.js' import CanvasRecorder from './canvas.js' import UserTestManager from '../modules/userTesting/index.js' +import TagWatcher from '../modules/tagWatcher.js' import type { Options as WebworkerOptions, @@ -179,6 +180,7 @@ export default class App { private uxtManager: UserTestManager private conditionsManager: ConditionsManager | null = null public featureFlags: FeatureFlags + private tagWatcher: TagWatcher constructor( projectKey: string, @@ -230,6 +232,9 @@ export default class App { this.session = new Session(this, this.options) this.attributeSender = new AttributeSender(this, Boolean(this.options.disableStringDict)) this.featureFlags = new FeatureFlags(this) + this.tagWatcher = new TagWatcher(this.sessionStorage, this.debug.error, (tag) => { + this.send(TagTrigger(tag) as Message) + }) this.session.attachUpdateCallback(({ userID, metadata }) => { if (userID != null) { // TODO: nullable userID @@ -687,6 +692,7 @@ export default class App { this.startCallbacks.forEach((cb) => cb(onStartInfo)) await this.conditionsManager?.fetchConditions(projectID as string, token as string) await this.featureFlags.reloadFlags(token as string) + await this.tagWatcher.fetchTags(this.options.ingestPoint, token as string) this.conditionsManager?.processFlags(this.featureFlags.flags) } const cycle = () => { @@ -896,7 +902,6 @@ export default class App { ) { const reason = 'OpenReplay: trying to call `start()` on the instance that has been started already.' - this.signalError(reason, []) return Promise.resolve(UnsuccessfulStart(reason)) } this.activityState = ActivityState.Starting @@ -1049,8 +1054,10 @@ export default class App { this.compressionThreshold = compressionThreshold const onStartInfo = { sessionToken: token, userUUID, sessionID } // TODO: start as early as possible (before receiving the token) + /** after start */ this.startCallbacks.forEach((cb) => cb(onStartInfo)) // MBTODO: callbacks after DOM "mounted" (observed) void this.featureFlags.reloadFlags() + await this.tagWatcher.fetchTags(this.options.ingestPoint, token) this.activityState = ActivityState.Active if (canvasEnabled) { @@ -1234,6 +1241,7 @@ export default class App { this.ticker.stop() this.stopCallbacks.forEach((cb) => cb()) this.debug.log('OpenReplay tracking stopped.') + this.tagWatcher.clear() if (this.worker && stopWorker) { this.worker.postMessage('stop') } diff --git a/tracker/tracker/src/main/app/messages.gen.ts b/tracker/tracker/src/main/app/messages.gen.ts index 57b4722f9..8f4e332b1 100644 --- a/tracker/tracker/src/main/app/messages.gen.ts +++ b/tracker/tracker/src/main/app/messages.gen.ts @@ -942,3 +942,12 @@ export function CanvasNode( ] } +export function TagTrigger( + tagId: number, +): Messages.TagTrigger { + return [ + Messages.Type.TagTrigger, + tagId, + ] +} + diff --git a/tracker/tracker/src/main/modules/tagWatcher.ts b/tracker/tracker/src/main/modules/tagWatcher.ts new file mode 100644 index 000000000..74ce132ec --- /dev/null +++ b/tracker/tracker/src/main/modules/tagWatcher.ts @@ -0,0 +1,83 @@ +export const WATCHED_TAGS_KEY = '__or__watched_tags__' + +class TagWatcher { + intervals: Record> = {} + tags: { id: number; selector: string }[] = [] + observer: IntersectionObserver + + constructor( + private readonly sessionStorage: Storage, + private readonly errLog: (args: any[]) => void, + private readonly onTag: (tag: number) => void, + ) { + const tags: { id: number; selector: string }[] = JSON.parse( + sessionStorage.getItem(WATCHED_TAGS_KEY) ?? '[]', + ) + this.setTags(tags) + this.observer = new IntersectionObserver((entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + if (entry.target) { + // @ts-ignore + const tag = entry.target.__or_watcher_tagname as number + if (tag) { + this.onTagRendered(tag) + } + this.observer.unobserve(entry.target) + } + } + }) + }) + } + + async fetchTags(ingest: string, token: string) { + return fetch(`${ingest}/v1/web/tags`, { + method: 'GET', + headers: { + Authorization: `Bearer ${token}`, + }, + }) + .then((r) => r.json()) + .then(({ tags }: { tags: { id: number; selector: string }[] }) => { + if (tags && tags.length) { + this.setTags(tags) + this.sessionStorage.setItem(WATCHED_TAGS_KEY, JSON.stringify(tags) || '') + } + }) + .catch((e) => this.errLog(e)) + } + + setTags(tags: { id: number; selector: string }[]) { + this.tags = tags + this.intervals = {} + tags.forEach((tag) => { + this.intervals[tag.id] = setInterval(() => { + const possibleEls = document.querySelectorAll(tag.selector) + if (possibleEls.length > 0) { + const el = possibleEls[0] + // @ts-ignore + el.__or_watcher_tagname = tag.id + this.observer.observe(el) + } + }, 500) + }) + } + + onTagRendered(tagId: number) { + if (this.intervals[tagId]) { + clearInterval(this.intervals[tagId]) + } + this.onTag(tagId) + } + + clear() { + this.tags.forEach((tag) => { + clearInterval(this.intervals[tag.id]) + }) + this.tags = [] + this.intervals = {} + this.observer.disconnect() + } +} + +export default TagWatcher diff --git a/tracker/tracker/src/tests/tagWatcher.test.ts b/tracker/tracker/src/tests/tagWatcher.test.ts new file mode 100644 index 000000000..243730d62 --- /dev/null +++ b/tracker/tracker/src/tests/tagWatcher.test.ts @@ -0,0 +1,103 @@ +import TagWatcher, { WATCHED_TAGS_KEY } from '../main/modules/TagWatcher' +import { describe, expect, jest, afterEach, beforeEach, test } from '@jest/globals' + +describe('TagWatcher', () => { + let sessionStorageMock: Storage + let errLogMock: (args: any[]) => void + const onTag = jest.fn() + let mockObserve: Function + let mockUnobserve: Function + let mockDisconnect: Function + + beforeEach(() => { + sessionStorageMock = { + // @ts-ignore + getItem: jest.fn(), + setItem: jest.fn(), + } + errLogMock = jest.fn() + mockObserve = jest.fn() + mockUnobserve = jest.fn() + mockDisconnect = jest.fn() + + // @ts-ignore + global.IntersectionObserver = jest.fn((callback) => ({ + observe: mockObserve, + unobserve: mockUnobserve, + disconnect: mockDisconnect, + callback, + })) + jest.useFakeTimers() + // @ts-ignore + global.document.querySelectorAll = jest.fn() + }) + + afterEach(() => { + jest.restoreAllMocks() + jest.useRealTimers() + }) + function triggerIntersection(elements: any, isIntersecting: boolean, observer: any) { + const entries = elements.map((el: any) => ({ + isIntersecting, + target: el, + })) + // @ts-ignore + observer.callback(entries) + } + + test('constructor initializes with tags from sessionStorage', () => { + // @ts-ignore + sessionStorageMock.getItem.mockReturnValue('div,span') + const watcher = new TagWatcher(sessionStorageMock, errLogMock, onTag) + expect(watcher.tags).toEqual(['div', 'span']) + expect(watcher.intervals).toHaveProperty('div') + expect(watcher.intervals).toHaveProperty('span') + }) + + test('fetchTags sets tags and updates sessionStorage', async () => { + // @ts-ignore + global.fetch = jest.fn(() => + Promise.resolve({ + json: () => Promise.resolve(['div', 'span', 'p']), + }), + ) + const watcher = new TagWatcher(sessionStorageMock, errLogMock, onTag) + await watcher.fetchTags('https://localhost.com', '123') + expect(watcher.tags).toEqual(['div', 'span', 'p']) + expect(sessionStorageMock.setItem).toHaveBeenCalledWith(WATCHED_TAGS_KEY, 'div,span,p') + }) + + test('setTags sets intervals for each tag', () => { + const watcher = new TagWatcher(sessionStorageMock, errLogMock, onTag) + watcher.setTags([ + { id: 1, selector: 'div' }, + { id: 2, selector: 'p' }, + ]) + expect(watcher.intervals).toHaveProperty('div') + expect(watcher.intervals).toHaveProperty('p') + expect(mockObserve).not.toHaveBeenCalled() // No elements to observe initially + }) + + test('onTagRendered sends messages', () => { + const watcher = new TagWatcher(sessionStorageMock, errLogMock, onTag) + watcher.setTags([{ id: 1, selector: 'div' }]) + // @ts-ignore + document.querySelectorAll.mockReturnValue([{ __or_watcher_tagname: 'div' }]) // Mock a found element + jest.advanceTimersByTime(1000) + triggerIntersection([{ __or_watcher_tagname: 'div' }], true, watcher.observer) + expect(onTag).toHaveBeenCalled() + expect(watcher.observer.unobserve).toHaveBeenCalled() + }) + + test('clear method clears all intervals and resets tags', () => { + const watcher = new TagWatcher(sessionStorageMock, errLogMock, onTag) + watcher.setTags([ + { id: 1, selector: 'div' }, + { id: 2, selector: 'p' }, + ]) + watcher.clear() + expect(watcher.tags).toEqual([]) + expect(watcher.intervals).toEqual({}) + expect(watcher.observer.disconnect).toHaveBeenCalled() + }) +}) diff --git a/tracker/tracker/src/webworker/MessageEncoder.gen.ts b/tracker/tracker/src/webworker/MessageEncoder.gen.ts index cf9f4855e..7f04cf0dc 100644 --- a/tracker/tracker/src/webworker/MessageEncoder.gen.ts +++ b/tracker/tracker/src/webworker/MessageEncoder.gen.ts @@ -294,6 +294,10 @@ export default class MessageEncoder extends PrimitiveEncoder { return this.string(msg[1]) && this.uint(msg[2]) break + case Messages.Type.TagTrigger: + return this.int(msg[1]) + break + } }