From 6a42d96e21e522e48bc4f2efd67f2c46b1633270 Mon Sep 17 00:00:00 2001 From: Delirium Date: Tue, 25 Jun 2024 10:13:13 +0200 Subject: [PATCH] Graphql plugin update (#1835) -- v1.19.0 * feat(tracker): relay + apollo plugins * fix(tracker): type fixes * fix(tracker): update mobs messages * fix msg conflict --- backend/pkg/messages/messages.go | 44 +++++++-- backend/pkg/messages/read-message.go | 32 ++++++- ee/connectors/msgcodec/messages.py | 16 +++- ee/connectors/msgcodec/messages.pyx | 23 ++++- ee/connectors/msgcodec/msgcodec.py | 14 ++- ee/connectors/msgcodec/msgcodec.pyx | 14 ++- frontend/app/player/web/TabManager.ts | 1 + .../web/messages/RawMessageReader.gen.ts | 20 ++++- .../app/player/web/messages/message.gen.ts | 7 +- frontend/app/player/web/messages/raw.gen.ts | 19 +++- .../player/web/messages/tracker-legacy.gen.ts | 3 +- .../app/player/web/messages/tracker.gen.ts | 28 +++++- mobs/messages.rb | 11 ++- tracker/tracker-graphql/README.md | 54 +++++++++-- tracker/tracker-graphql/bun.lockb | Bin 0 -> 241648 bytes tracker/tracker-graphql/package.json | 11 ++- .../tracker-graphql/src/apolloMiddleware.ts | 57 ++++++++++++ .../tracker-graphql/src/graphqlMiddleware.ts | 33 +++++++ tracker/tracker-graphql/src/index.ts | 35 ++------ .../tracker-graphql/src/relayMiddleware.ts | 37 ++++++++ tracker/tracker-graphql/src/relaytypes.ts | 85 ++++++++++++++++++ tracker/tracker-graphql/tsconfig.json | 3 +- tracker/tracker/CHANGELOG.md | 4 + tracker/tracker/src/common/messages.gen.ts | 19 +++- tracker/tracker/src/main/app/messages.gen.ts | 25 +++++- .../src/webworker/MessageEncoder.gen.ts | 8 +- 26 files changed, 525 insertions(+), 78 deletions(-) create mode 100755 tracker/tracker-graphql/bun.lockb create mode 100644 tracker/tracker-graphql/src/apolloMiddleware.ts create mode 100644 tracker/tracker-graphql/src/graphqlMiddleware.ts create mode 100644 tracker/tracker-graphql/src/relayMiddleware.ts create mode 100644 tracker/tracker-graphql/src/relaytypes.ts diff --git a/backend/pkg/messages/messages.go b/backend/pkg/messages/messages.go index 9f82d47cb..5b0c4acee 100644 --- a/backend/pkg/messages/messages.go +++ b/backend/pkg/messages/messages.go @@ -44,7 +44,7 @@ const ( MsgVuex = 45 MsgMobX = 46 MsgNgRx = 47 - MsgGraphQL = 48 + MsgGraphQLDeprecated = 48 MsgPerformanceTrack = 49 MsgStringDict = 50 MsgSetNodeAttributeDict = 51 @@ -90,6 +90,7 @@ const ( MsgTagTrigger = 120 MsgRedux = 121 MsgSetPageLocation = 122 + MsgGraphQL = 123 MsgIssueEvent = 125 MsgSessionEnd = 126 MsgSessionSearch = 127 @@ -1203,30 +1204,32 @@ func (msg *NgRx) TypeID() int { return 47 } -type GraphQL struct { +type GraphQLDeprecated struct { message OperationKind string OperationName string Variables string Response string + Duration int64 } -func (msg *GraphQL) Encode() []byte { - buf := make([]byte, 41+len(msg.OperationKind)+len(msg.OperationName)+len(msg.Variables)+len(msg.Response)) +func (msg *GraphQLDeprecated) Encode() []byte { + buf := make([]byte, 51+len(msg.OperationKind)+len(msg.OperationName)+len(msg.Variables)+len(msg.Response)) buf[0] = 48 p := 1 p = WriteString(msg.OperationKind, buf, p) p = WriteString(msg.OperationName, buf, p) p = WriteString(msg.Variables, buf, p) p = WriteString(msg.Response, buf, p) + p = WriteInt(msg.Duration, buf, p) return buf[:p] } -func (msg *GraphQL) Decode() Message { +func (msg *GraphQLDeprecated) Decode() Message { return msg } -func (msg *GraphQL) TypeID() int { +func (msg *GraphQLDeprecated) TypeID() int { return 48 } @@ -2411,6 +2414,35 @@ func (msg *SetPageLocation) TypeID() int { return 122 } +type GraphQL struct { + message + OperationKind string + OperationName string + Variables string + Response string + Duration uint64 +} + +func (msg *GraphQL) Encode() []byte { + buf := make([]byte, 51+len(msg.OperationKind)+len(msg.OperationName)+len(msg.Variables)+len(msg.Response)) + buf[0] = 123 + p := 1 + p = WriteString(msg.OperationKind, buf, p) + p = WriteString(msg.OperationName, buf, p) + p = WriteString(msg.Variables, buf, p) + p = WriteString(msg.Response, buf, p) + p = WriteUint(msg.Duration, buf, p) + return buf[:p] +} + +func (msg *GraphQL) Decode() Message { + return msg +} + +func (msg *GraphQL) TypeID() int { + return 123 +} + type IssueEvent struct { message MessageID uint64 diff --git a/backend/pkg/messages/read-message.go b/backend/pkg/messages/read-message.go index 4ef9ba53f..4ddde9c3b 100644 --- a/backend/pkg/messages/read-message.go +++ b/backend/pkg/messages/read-message.go @@ -693,9 +693,9 @@ func DecodeNgRx(reader BytesReader) (Message, error) { return msg, err } -func DecodeGraphQL(reader BytesReader) (Message, error) { +func DecodeGraphQLDeprecated(reader BytesReader) (Message, error) { var err error = nil - msg := &GraphQL{} + msg := &GraphQLDeprecated{} if msg.OperationKind, err = reader.ReadString(); err != nil { return nil, err } @@ -708,6 +708,9 @@ func DecodeGraphQL(reader BytesReader) (Message, error) { if msg.Response, err = reader.ReadString(); err != nil { return nil, err } + if msg.Duration, err = reader.ReadInt(); err != nil { + return nil, err + } return msg, err } @@ -1470,6 +1473,27 @@ func DecodeSetPageLocation(reader BytesReader) (Message, error) { return msg, err } +func DecodeGraphQL(reader BytesReader) (Message, error) { + var err error = nil + msg := &GraphQL{} + if msg.OperationKind, err = reader.ReadString(); err != nil { + return nil, err + } + if msg.OperationName, err = reader.ReadString(); err != nil { + return nil, err + } + if msg.Variables, err = reader.ReadString(); err != nil { + return nil, err + } + if msg.Response, err = reader.ReadString(); err != nil { + return nil, err + } + if msg.Duration, err = reader.ReadUint(); err != nil { + return nil, err + } + return msg, err +} + func DecodeIssueEvent(reader BytesReader) (Message, error) { var err error = nil msg := &IssueEvent{} @@ -2019,7 +2043,7 @@ func ReadMessage(t uint64, reader BytesReader) (Message, error) { case 47: return DecodeNgRx(reader) case 48: - return DecodeGraphQL(reader) + return DecodeGraphQLDeprecated(reader) case 49: return DecodePerformanceTrack(reader) case 50: @@ -2110,6 +2134,8 @@ func ReadMessage(t uint64, reader BytesReader) (Message, error) { return DecodeRedux(reader) case 122: return DecodeSetPageLocation(reader) + case 123: + return DecodeGraphQL(reader) case 125: return DecodeIssueEvent(reader) case 126: diff --git a/ee/connectors/msgcodec/messages.py b/ee/connectors/msgcodec/messages.py index 5839934b0..f14b05f92 100644 --- a/ee/connectors/msgcodec/messages.py +++ b/ee/connectors/msgcodec/messages.py @@ -404,14 +404,15 @@ class NgRx(Message): self.duration = duration -class GraphQL(Message): +class GraphQLDeprecated(Message): __id__ = 48 - def __init__(self, operation_kind, operation_name, variables, response): + def __init__(self, operation_kind, operation_name, variables, response, duration): self.operation_kind = operation_kind self.operation_name = operation_name self.variables = variables self.response = response + self.duration = duration class PerformanceTrack(Message): @@ -847,6 +848,17 @@ class SetPageLocation(Message): self.document_title = document_title +class GraphQL(Message): + __id__ = 123 + + def __init__(self, operation_kind, operation_name, variables, response, duration): + self.operation_kind = operation_kind + self.operation_name = operation_name + self.variables = variables + self.response = response + self.duration = duration + + class IssueEvent(Message): __id__ = 125 diff --git a/ee/connectors/msgcodec/messages.pyx b/ee/connectors/msgcodec/messages.pyx index bbbb4257f..d495bfaf4 100644 --- a/ee/connectors/msgcodec/messages.pyx +++ b/ee/connectors/msgcodec/messages.pyx @@ -596,19 +596,21 @@ cdef class NgRx(PyMessage): self.duration = duration -cdef class GraphQL(PyMessage): +cdef class GraphQLDeprecated(PyMessage): cdef public int __id__ cdef public str operation_kind cdef public str operation_name cdef public str variables cdef public str response + cdef public long duration - def __init__(self, str operation_kind, str operation_name, str variables, str response): + def __init__(self, str operation_kind, str operation_name, str variables, str response, long duration): self.__id__ = 48 self.operation_kind = operation_kind self.operation_name = operation_name self.variables = variables self.response = response + self.duration = duration cdef class PerformanceTrack(PyMessage): @@ -1252,6 +1254,23 @@ cdef class SetPageLocation(PyMessage): self.document_title = document_title +cdef class GraphQL(PyMessage): + cdef public int __id__ + cdef public str operation_kind + cdef public str operation_name + cdef public str variables + cdef public str response + cdef public unsigned long duration + + def __init__(self, str operation_kind, str operation_name, str variables, str response, unsigned long duration): + self.__id__ = 123 + self.operation_kind = operation_kind + self.operation_name = operation_name + self.variables = variables + self.response = response + self.duration = duration + + 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 c4b88569b..6a4a73bdc 100644 --- a/ee/connectors/msgcodec/msgcodec.py +++ b/ee/connectors/msgcodec/msgcodec.py @@ -408,11 +408,12 @@ class MessageCodec(Codec): ) if message_id == 48: - return GraphQL( + return GraphQLDeprecated( operation_kind=self.read_string(reader), operation_name=self.read_string(reader), variables=self.read_string(reader), - response=self.read_string(reader) + response=self.read_string(reader), + duration=self.read_int(reader) ) if message_id == 49: @@ -758,6 +759,15 @@ class MessageCodec(Codec): document_title=self.read_string(reader) ) + if message_id == 123: + return GraphQL( + operation_kind=self.read_string(reader), + operation_name=self.read_string(reader), + variables=self.read_string(reader), + response=self.read_string(reader), + duration=self.read_uint(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 791f7fae9..b7f9e105e 100644 --- a/ee/connectors/msgcodec/msgcodec.pyx +++ b/ee/connectors/msgcodec/msgcodec.pyx @@ -506,11 +506,12 @@ cdef class MessageCodec: ) if message_id == 48: - return GraphQL( + return GraphQLDeprecated( operation_kind=self.read_string(reader), operation_name=self.read_string(reader), variables=self.read_string(reader), - response=self.read_string(reader) + response=self.read_string(reader), + duration=self.read_int(reader) ) if message_id == 49: @@ -856,6 +857,15 @@ cdef class MessageCodec: document_title=self.read_string(reader) ) + if message_id == 123: + return GraphQL( + operation_kind=self.read_string(reader), + operation_name=self.read_string(reader), + variables=self.read_string(reader), + response=self.read_string(reader), + duration=self.read_uint(reader) + ) + if message_id == 125: return IssueEvent( message_id=self.read_uint(reader), diff --git a/frontend/app/player/web/TabManager.ts b/frontend/app/player/web/TabManager.ts index 7c0b5c4d1..a61a1c656 100644 --- a/frontend/app/player/web/TabManager.ts +++ b/frontend/app/player/web/TabManager.ts @@ -263,6 +263,7 @@ export default class TabSessionManager { case MType.MobX: this.lists.lists.mobx.append(msg); break; + case MType.GraphQlDeprecated: case MType.GraphQl: this.lists.lists.graphql.append(msg); break; diff --git a/frontend/app/player/web/messages/RawMessageReader.gen.ts b/frontend/app/player/web/messages/RawMessageReader.gen.ts index 451859a7e..10b178914 100644 --- a/frontend/app/player/web/messages/RawMessageReader.gen.ts +++ b/frontend/app/player/web/messages/RawMessageReader.gen.ts @@ -348,12 +348,14 @@ export default class RawMessageReader extends PrimitiveReader { const operationName = this.readString(); if (operationName === null) { return resetPointer() } const variables = this.readString(); if (variables === null) { return resetPointer() } const response = this.readString(); if (response === null) { return resetPointer() } + const duration = this.readInt(); if (duration === null) { return resetPointer() } return { - tp: MType.GraphQl, + tp: MType.GraphQlDeprecated, operationKind, operationName, variables, response, + duration, }; } @@ -795,6 +797,22 @@ export default class RawMessageReader extends PrimitiveReader { }; } + case 123: { + const operationKind = this.readString(); if (operationKind === null) { return resetPointer() } + const operationName = this.readString(); if (operationName === null) { return resetPointer() } + const variables = this.readString(); if (variables === null) { return resetPointer() } + const response = this.readString(); if (response === null) { return resetPointer() } + const duration = this.readUint(); if (duration === null) { return resetPointer() } + return { + tp: MType.GraphQl, + operationKind, + operationName, + variables, + response, + duration, + }; + } + 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 3f6dc2ea1..d570101a0 100644 --- a/frontend/app/player/web/messages/message.gen.ts +++ b/frontend/app/player/web/messages/message.gen.ts @@ -32,7 +32,7 @@ import type { RawVuex, RawMobX, RawNgRx, - RawGraphQl, + RawGraphQlDeprecated, RawPerformanceTrack, RawStringDict, RawSetNodeAttributeDict, @@ -67,6 +67,7 @@ import type { RawTagTrigger, RawRedux, RawSetPageLocation, + RawGraphQl, RawMobileEvent, RawMobileScreenChanges, RawMobileClickEvent, @@ -138,7 +139,7 @@ export type MobX = RawMobX & Timed export type NgRx = RawNgRx & Timed -export type GraphQl = RawGraphQl & Timed +export type GraphQlDeprecated = RawGraphQlDeprecated & Timed export type PerformanceTrack = RawPerformanceTrack & Timed @@ -208,6 +209,8 @@ export type Redux = RawRedux & Timed export type SetPageLocation = RawSetPageLocation & Timed +export type GraphQl = RawGraphQl & Timed + export type MobileEvent = RawMobileEvent & Timed export type MobileScreenChanges = RawMobileScreenChanges & Timed diff --git a/frontend/app/player/web/messages/raw.gen.ts b/frontend/app/player/web/messages/raw.gen.ts index ddf578aca..a62a8d3d7 100644 --- a/frontend/app/player/web/messages/raw.gen.ts +++ b/frontend/app/player/web/messages/raw.gen.ts @@ -30,7 +30,7 @@ export const enum MType { Vuex = 45, MobX = 46, NgRx = 47, - GraphQl = 48, + GraphQlDeprecated = 48, PerformanceTrack = 49, StringDict = 50, SetNodeAttributeDict = 51, @@ -65,6 +65,7 @@ export const enum MType { TagTrigger = 120, Redux = 121, SetPageLocation = 122, + GraphQl = 123, MobileEvent = 93, MobileScreenChanges = 96, MobileClickEvent = 100, @@ -268,12 +269,13 @@ export interface RawNgRx { duration: number, } -export interface RawGraphQl { - tp: MType.GraphQl, +export interface RawGraphQlDeprecated { + tp: MType.GraphQlDeprecated, operationKind: string, operationName: string, variables: string, response: string, + duration: number, } export interface RawPerformanceTrack { @@ -529,6 +531,15 @@ export interface RawSetPageLocation { documentTitle: string, } +export interface RawGraphQl { + tp: MType.GraphQl, + operationKind: string, + operationName: string, + variables: string, + response: string, + duration: number, +} + export interface RawMobileEvent { tp: MType.MobileEvent, timestamp: number, @@ -621,4 +632,4 @@ export interface RawMobileIssueEvent { } -export type RawMessage = RawTimestamp | RawSetPageLocationDeprecated | RawSetViewportSize | RawSetViewportScroll | RawCreateDocument | RawCreateElementNode | RawCreateTextNode | RawMoveNode | RawRemoveNode | RawSetNodeAttribute | RawRemoveNodeAttribute | RawSetNodeData | RawSetCssData | RawSetNodeScroll | RawSetInputValue | RawSetInputChecked | RawMouseMove | RawNetworkRequestDeprecated | RawConsoleLog | RawCssInsertRule | RawCssDeleteRule | RawFetch | RawProfiler | RawOTable | RawReduxDeprecated | RawVuex | RawMobX | RawNgRx | RawGraphQl | RawPerformanceTrack | RawStringDict | RawSetNodeAttributeDict | RawResourceTimingDeprecated | RawConnectionInformation | RawSetPageVisibility | RawLoadFontFace | RawSetNodeFocus | RawLongTask | RawSetNodeAttributeURLBased | RawSetCssDataURLBased | RawCssInsertRuleURLBased | RawMouseClick | RawMouseClickDeprecated | RawCreateIFrameDocument | RawAdoptedSsReplaceURLBased | RawAdoptedSsReplace | RawAdoptedSsInsertRuleURLBased | RawAdoptedSsInsertRule | RawAdoptedSsDeleteRule | RawAdoptedSsAddOwner | RawAdoptedSsRemoveOwner | RawZustand | RawNetworkRequest | RawWsChannel | RawSelectionChange | RawMouseThrashing | RawResourceTiming | RawTabChange | RawTabData | RawCanvasNode | RawTagTrigger | RawRedux | RawSetPageLocation | RawMobileEvent | RawMobileScreenChanges | RawMobileClickEvent | RawMobileInputEvent | RawMobilePerformanceEvent | RawMobileLog | RawMobileInternalError | RawMobileNetworkCall | RawMobileSwipeEvent | RawMobileIssueEvent; +export type RawMessage = RawTimestamp | RawSetPageLocationDeprecated | RawSetViewportSize | RawSetViewportScroll | RawCreateDocument | RawCreateElementNode | RawCreateTextNode | RawMoveNode | RawRemoveNode | RawSetNodeAttribute | RawRemoveNodeAttribute | RawSetNodeData | RawSetCssData | RawSetNodeScroll | RawSetInputValue | RawSetInputChecked | RawMouseMove | RawNetworkRequestDeprecated | RawConsoleLog | RawCssInsertRule | RawCssDeleteRule | RawFetch | RawProfiler | RawOTable | RawReduxDeprecated | RawVuex | RawMobX | RawNgRx | RawGraphQlDeprecated | RawPerformanceTrack | RawStringDict | RawSetNodeAttributeDict | RawResourceTimingDeprecated | RawConnectionInformation | RawSetPageVisibility | RawLoadFontFace | RawSetNodeFocus | RawLongTask | RawSetNodeAttributeURLBased | RawSetCssDataURLBased | RawCssInsertRuleURLBased | RawMouseClick | RawMouseClickDeprecated | RawCreateIFrameDocument | RawAdoptedSsReplaceURLBased | RawAdoptedSsReplace | RawAdoptedSsInsertRuleURLBased | RawAdoptedSsInsertRule | RawAdoptedSsDeleteRule | RawAdoptedSsAddOwner | RawAdoptedSsRemoveOwner | RawZustand | RawNetworkRequest | RawWsChannel | RawSelectionChange | RawMouseThrashing | RawResourceTiming | RawTabChange | RawTabData | RawCanvasNode | RawTagTrigger | RawRedux | RawSetPageLocation | RawGraphQl | RawMobileEvent | RawMobileScreenChanges | RawMobileClickEvent | RawMobileInputEvent | RawMobilePerformanceEvent | RawMobileLog | RawMobileInternalError | RawMobileNetworkCall | RawMobileSwipeEvent | RawMobileIssueEvent; diff --git a/frontend/app/player/web/messages/tracker-legacy.gen.ts b/frontend/app/player/web/messages/tracker-legacy.gen.ts index e942a32e2..3404bad15 100644 --- a/frontend/app/player/web/messages/tracker-legacy.gen.ts +++ b/frontend/app/player/web/messages/tracker-legacy.gen.ts @@ -31,7 +31,7 @@ export const TP_MAP = { 45: MType.Vuex, 46: MType.MobX, 47: MType.NgRx, - 48: MType.GraphQl, + 48: MType.GraphQlDeprecated, 49: MType.PerformanceTrack, 50: MType.StringDict, 51: MType.SetNodeAttributeDict, @@ -66,6 +66,7 @@ export const TP_MAP = { 120: MType.TagTrigger, 121: MType.Redux, 122: MType.SetPageLocation, + 123: MType.GraphQl, 93: MType.MobileEvent, 96: MType.MobileScreenChanges, 100: MType.MobileClickEvent, diff --git a/frontend/app/player/web/messages/tracker.gen.ts b/frontend/app/player/web/messages/tracker.gen.ts index 18c9a9e84..cdf0be6c9 100644 --- a/frontend/app/player/web/messages/tracker.gen.ts +++ b/frontend/app/player/web/messages/tracker.gen.ts @@ -242,12 +242,13 @@ type TrNgRx = [ duration: number, ] -type TrGraphQL = [ +type TrGraphQLDeprecated = [ type: 48, operationKind: string, operationName: string, variables: string, response: string, + duration: number, ] type TrPerformanceTrack = [ @@ -540,8 +541,17 @@ type TrSetPageLocation = [ documentTitle: string, ] +type TrGraphQL = [ + type: 123, + operationKind: string, + operationName: string, + variables: string, + response: string, + duration: number, +] -export type TrackerMessage = TrTimestamp | TrSetPageLocationDeprecated | 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 | TrReduxDeprecated | TrVuex | TrMobX | TrNgRx | TrGraphQL | TrPerformanceTrack | TrStringDict | TrSetNodeAttributeDict | TrResourceTimingDeprecated | TrConnectionInformation | TrSetPageVisibility | TrLoadFontFace | TrSetNodeFocus | TrLongTask | TrSetNodeAttributeURLBased | TrSetCSSDataURLBased | TrTechnicalInfo | TrCustomIssue | TrCSSInsertRuleURLBased | TrMouseClick | TrMouseClickDeprecated | TrCreateIFrameDocument | TrAdoptedSSReplaceURLBased | TrAdoptedSSInsertRuleURLBased | TrAdoptedSSDeleteRule | TrAdoptedSSAddOwner | TrAdoptedSSRemoveOwner | TrJSException | TrZustand | TrBatchMetadata | TrPartitionedMessage | TrNetworkRequest | TrWSChannel | TrInputChange | TrSelectionChange | TrMouseThrashing | TrUnbindNodes | TrResourceTiming | TrTabChange | TrTabData | TrCanvasNode | TrTagTrigger | TrRedux | TrSetPageLocation + +export type TrackerMessage = TrTimestamp | TrSetPageLocationDeprecated | 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 | TrReduxDeprecated | TrVuex | TrMobX | TrNgRx | TrGraphQLDeprecated | TrPerformanceTrack | TrStringDict | TrSetNodeAttributeDict | TrResourceTimingDeprecated | TrConnectionInformation | TrSetPageVisibility | TrLoadFontFace | TrSetNodeFocus | TrLongTask | TrSetNodeAttributeURLBased | TrSetCSSDataURLBased | TrTechnicalInfo | TrCustomIssue | TrCSSInsertRuleURLBased | TrMouseClick | TrMouseClickDeprecated | TrCreateIFrameDocument | TrAdoptedSSReplaceURLBased | TrAdoptedSSInsertRuleURLBased | TrAdoptedSSDeleteRule | TrAdoptedSSAddOwner | TrAdoptedSSRemoveOwner | TrJSException | TrZustand | TrBatchMetadata | TrPartitionedMessage | TrNetworkRequest | TrWSChannel | TrInputChange | TrSelectionChange | TrMouseThrashing | TrUnbindNodes | TrResourceTiming | TrTabChange | TrTabData | TrCanvasNode | TrTagTrigger | TrRedux | TrSetPageLocation | TrGraphQL export default function translate(tMsg: TrackerMessage): RawMessage | null { switch(tMsg[0]) { @@ -786,11 +796,12 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { case 48: { return { - tp: MType.GraphQl, + tp: MType.GraphQlDeprecated, operationKind: tMsg[1], operationName: tMsg[2], variables: tMsg[3], response: tMsg[4], + duration: tMsg[5], } } @@ -1098,6 +1109,17 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { } } + case 123: { + return { + tp: MType.GraphQl, + operationKind: tMsg[1], + operationName: tMsg[2], + variables: tMsg[3], + response: tMsg[4], + duration: tMsg[5], + } + } + default: return null } diff --git a/mobs/messages.rb b/mobs/messages.rb index 7c768e61a..f5dfaed52 100644 --- a/mobs/messages.rb +++ b/mobs/messages.rb @@ -244,11 +244,12 @@ message 47, 'NgRx', :replayer => :devtools do string 'State' uint 'Duration' end -message 48, 'GraphQL', :replayer => :devtools do +message 48, 'GraphQLDeprecated', :replayer => :devtools do string 'OperationKind' string 'OperationName' string 'Variables' string 'Response' + int 'Duration' end message 49, 'PerformanceTrack' do #, :replayer => :devtools --> requires player performance refactoring (now is tied with nodes counter) int 'Frames' @@ -547,6 +548,14 @@ message 122, 'SetPageLocation' do string 'DocumentTitle' end +message 123, 'GraphQL', :replayer => :devtools do + string 'OperationKind' + string 'OperationName' + string 'Variables' + string 'Response' + uint 'Duration' +end + ## Backend-only message 125, 'IssueEvent', :replayer => false, :tracker => false do uint 'MessageID' diff --git a/tracker/tracker-graphql/README.md b/tracker/tracker-graphql/README.md index 4fe38734f..4cc3a4931 100644 --- a/tracker/tracker-graphql/README.md +++ b/tracker/tracker-graphql/README.md @@ -12,8 +12,9 @@ npm i @openreplay/tracker-graphql Initialize the `@openreplay/tracker` package as usual and load the plugin into it. The `plugin` call will return the function, which receives four variables -`operationKind`, `operationName`, `variables`, `result` -and returns `result` without changes. +`operationKind`, `operationName`, `variables`, `result` and `duration` (default 0) + +returns `result` without changes. ```js import Tracker from '@openreplay/tracker'; @@ -28,21 +29,45 @@ export const recordGraphQL = tracker.plugin(trackerGraphQL()); ### Relay -For [Relay](https://relay.dev/) you should manually put `recordGraphQL` call +If you're using [Relay network tools](https://github.com/relay-tools/react-relay-network-modern), +you can simply [create a middleware](https://github.com/relay-tools/react-relay-network-modern/tree/master?tab=readme-ov-file#example-of-injecting-networklayer-with-middlewares-on-the-client-side) + +```js +import { createRelayMiddleware } from '@openreplay/tracker-graphql' + +const trackerMiddleware = createRelayMiddleware(tracker) + +const network = new RelayNetworkLayer([ + // your middleware + // , + trackerMiddleware +]) +``` + +Or you can manually put `recordGraphQL` call to the `NetworkLayer` implementation. If you are standard `Network.create` way to implement it, then you should do something like below ```js -import { recordGraphQL } from 'tracker'; // see above for recordGraphQL definition +import { createGraphqlMiddleware } from '@openreplay/tracker-graphql'; // see above for recordGraphQL definition import { Environment } from 'relay-runtime'; +const handler = createGraphqlMiddleware(tracker) + function fetchQuery(operation, variables, cacheConfig, uploadables) { return fetch('www.myapi.com/resource', { // ... }) .then(response => response.json()) .then(result => - recordGraphQL(operation.operationKind, operation.name, variables, result), + handler( + // op kind, name, variables, response, duration (default 0) + operation.operationKind, + operation.name, + variables, + result, + duration, + ), ); } @@ -53,16 +78,27 @@ See [Relay Network Layer](https://relay.dev/docs/en/network-layer) for details. ### Apollo -For [Apollo](https://www.apollographql.com/) you should create a new `ApolloLink` with -`recordGraphQL` call and put it to your chain like below +For [Apollo](https://www.apollographql.com/) you should create a new `ApolloLink` ```js -import { recordGraphQL } from 'tracker'; // see above for recordGraphQL definition +import { createTrackerLink } from '@openreplay/tracker-graphql' + +const trackerLink = createTrackerLink(tracker); +const yourLink = new ApolloLink(trackerLink) +``` + +Alternatively you can use generic graphql handler: + +```js +import { createGraphqlMiddleware } from '@openreplay/tracker-graphql'; // see above for recordGraphQL definition import { ApolloLink } from 'apollo-link'; +const handler = createGraphqlMiddleware(tracker) + const trackerApolloLink = new ApolloLink((operation, forward) => { return forward(operation).map(result => - recordGraphQL( + handler( + // op kind, name, variables, response, duration (default 0) operation.query.definitions[0].operation, operation.operationName, operation.variables, diff --git a/tracker/tracker-graphql/bun.lockb b/tracker/tracker-graphql/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..9ba1a40dbf5a27bb94992899e446eadc97aa9a7d GIT binary patch literal 241648 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 M>a_p)fBNtL02gr+>Hq)$ literal 0 HcmV?d00001 diff --git a/tracker/tracker-graphql/package.json b/tracker/tracker-graphql/package.json index 415982dc2..a74fe4134 100644 --- a/tracker/tracker-graphql/package.json +++ b/tracker/tracker-graphql/package.json @@ -1,7 +1,7 @@ { "name": "@openreplay/tracker-graphql", "description": "Tracker plugin for GraphQL requests recording", - "version": "3.0.1", + "version": "4.0.1-2", "keywords": [ "graphql", "logging", @@ -22,12 +22,17 @@ "prepublishOnly": "npm run build" }, "peerDependencies": { - "@openreplay/tracker": ">=3.0.0" + "@openreplay/tracker": ">=13.0.0" }, "devDependencies": { "@openreplay/tracker": "file:../tracker", "prettier": "^1.18.2", "replace-in-files-cli": "^1.0.0", - "typescript": "^3.6.4" + "typescript": "^5.3.3" + }, + "dependencies": { + "@apollo/client": "^3.9.5", + "@types/zen-observable": "^0.8.7", + "zen-observable": "^0.10.0" } } diff --git a/tracker/tracker-graphql/src/apolloMiddleware.ts b/tracker/tracker-graphql/src/apolloMiddleware.ts new file mode 100644 index 000000000..948186b77 --- /dev/null +++ b/tracker/tracker-graphql/src/apolloMiddleware.ts @@ -0,0 +1,57 @@ +import { App, Messages } from '@openreplay/tracker'; +import Observable from 'zen-observable'; + +type Operation = { + query: Record; + variables: Record; + operationName: string; + extensions: Record; +}; +type NextLink = (operation: Operation) => Observable>; + +export const createTrackerLink = (app: App | null) => { + if (!app) { + return (operation: Operation, forward: NextLink) => forward(operation); + } + return (operation: Operation, forward: NextLink) => { + return new Observable((observer) => { + const start = app.timestamp(); + const observable = forward(operation); + const subscription = observable.subscribe({ + next(value) { + const end = app.timestamp(); + app.send( + Messages.GraphQL( + operation.query.definitions[0].kind, + operation.operationName, + JSON.stringify(operation.variables), + JSON.stringify(value.data), + end - start, + ), + ); + observer.next(value); + }, + error(error) { + const end = app.timestamp(); + app.send( + Messages.GraphQL( + operation.query.definitions[0].kind, + operation.operationName, + JSON.stringify(operation.variables), + JSON.stringify(error), + end - start, + ), + ); + observer.error(error); + }, + complete() { + observer.complete(); + }, + }); + + return () => subscription.unsubscribe(); + }); + }; +}; + +export default createTrackerLink; diff --git a/tracker/tracker-graphql/src/graphqlMiddleware.ts b/tracker/tracker-graphql/src/graphqlMiddleware.ts new file mode 100644 index 000000000..e5302d232 --- /dev/null +++ b/tracker/tracker-graphql/src/graphqlMiddleware.ts @@ -0,0 +1,33 @@ +import { App, Messages } from "@openreplay/tracker"; + +function createGraphqlMiddleware() { + return (app: App | null) => { + if (app === null) { + return (_1: string, _2: string, _3: any, result: any) => result; + } + return ( + operationKind: string, + operationName: string, + variables: any, + result: any, + duration = 0 + ) => { + try { + app.send( + Messages.GraphQL( + operationKind, + operationName, + JSON.stringify(variables), + JSON.stringify(result), + duration, + ), + ); + } catch (e) { + console.error(e); + } + return result; + }; + }; +} + +export default createGraphqlMiddleware \ No newline at end of file diff --git a/tracker/tracker-graphql/src/index.ts b/tracker/tracker-graphql/src/index.ts index afb878cfe..339836352 100644 --- a/tracker/tracker-graphql/src/index.ts +++ b/tracker/tracker-graphql/src/index.ts @@ -1,28 +1,9 @@ -import { App, Messages } from '@openreplay/tracker'; +import createTrackerLink from './apolloMiddleware.js'; +import createRelayMiddleware from './relayMiddleware.js'; +import createGraphqlMiddleware from './graphqlMiddleware.js'; -export default function() { - return (app: App | null) => { - if (app === null) { - return (_1: string, _2: string, _3: any, result: any) => result; - } - return ( - operationKind: string, - operationName: string, - variables: any, - result: any, - ) => { - try { - app.send( - Messages.GraphQL( - operationKind, - operationName, - JSON.stringify(variables), - JSON.stringify(result), - ), - ); - } finally { - return result; - } - }; - }; -} +export { + createTrackerLink, + createRelayMiddleware, + createGraphqlMiddleware, +} \ No newline at end of file diff --git a/tracker/tracker-graphql/src/relayMiddleware.ts b/tracker/tracker-graphql/src/relayMiddleware.ts new file mode 100644 index 000000000..f1e9cc721 --- /dev/null +++ b/tracker/tracker-graphql/src/relayMiddleware.ts @@ -0,0 +1,37 @@ +import { App, Messages } from '@openreplay/tracker'; +import type { Middleware, RelayRequest } from './relaytypes'; + +const createRelayMiddleware = (app: App | null): Middleware => { + if (!app) { + return (next) => async (req) => await next(req); + } + return (next) => async (req) => { + const start = app.timestamp(); + const resp = await next(req) + const end = app.timestamp(); + if ('requests' in req) { + req.requests.forEach((request) => { + app.send(getMessage(request, resp.json as Record, end - start)) + }) + } else { + app.send(getMessage(req, resp.json as Record, end - start)) + } + return resp; + } +}; + +function getMessage(request: RelayRequest, json: Record, duration: number) { + const opKind = request.operation.kind; + const opName = request.operation.name; + const vars = JSON.stringify(request.variables) + const opResp = JSON.stringify(json) + return Messages.GraphQL( + opKind, + opName, + vars, + opResp, + duration + ) +} + +export default createRelayMiddleware diff --git a/tracker/tracker-graphql/src/relaytypes.ts b/tracker/tracker-graphql/src/relaytypes.ts new file mode 100644 index 000000000..4330918d5 --- /dev/null +++ b/tracker/tracker-graphql/src/relaytypes.ts @@ -0,0 +1,85 @@ + +type ConcreteBatch = { + kind: 'Batch'; + fragment: any; + id: string | null; + metadata: { [key: string]: any }; + name: string; + query: any; + text: string | null; + operationKind: string; +}; +type Variables = { [name: string]: any }; +interface FetchOpts { + url?: string; + method: 'POST' | 'GET'; + headers: Headers; + body: string | FormData; + credentials?: 'same-origin' | 'include' | 'omit'; + mode?: 'cors' | 'websocket' | 'navigate' | 'no-cors' | 'same-origin'; + cache?: 'default' | 'no-store' | 'reload' | 'no-cache' | 'force-cache' | 'only-if-cached'; + redirect?: 'follow' | 'error' | 'manual'; + signal?: AbortSignal; + [name: string]: any; +} +export interface RelayRequest { + lastGenId: number; + id: string; + fetchOpts: FetchOpts; + + operation: ConcreteBatch; + variables: Variables; + controller: AbortController | null; + + getBody(): string | FormData; + prepareBody(): string | FormData; + getID(): string; + getQueryString(): string; + getVariables(): Variables; + isMutation(): boolean; + isFormData(): boolean; + cancel(): boolean; + clone(): RelayRequest; +} + +declare class RelayRequestBatch { + requests: RelayRequest[]; + + setFetchOption(name: string, value: any): void; + setFetchOptions(opts: {}): void; + getBody(): string; + prepareBody(): string; + getIds(): string[]; + getID(): string; + isMutation(): boolean; + isFormData(): boolean; + clone(): RelayRequestBatch; + getQueryString(): string; +} +type GraphQLResponseErrors = Array<{ + message: string; + locations?: [{ column: number; line: number }]; + stack?: string[]; +}>; +export interface RelayResponse { + _res: any; + + data?: Record; + errors?: GraphQLResponseErrors; + + ok: any; + status: number; + statusText?: string; + headers?: Headers; + url?: string; + text?: string; + json: unknown; + + processJsonData(json: unknown): void; + clone(): RelayResponse; + toString(): string; +} + +export type RelayRequestAny = RelayRequest | RelayRequestBatch; +export type MiddlewareNextFn = (req: RelayRequestAny) => Promise; +export type Middleware = (next: MiddlewareNextFn) => MiddlewareNextFn; diff --git a/tracker/tracker-graphql/tsconfig.json b/tracker/tracker-graphql/tsconfig.json index ce07a685b..31699449b 100644 --- a/tracker/tracker-graphql/tsconfig.json +++ b/tracker/tracker-graphql/tsconfig.json @@ -7,6 +7,7 @@ "module": "es6", "moduleResolution": "node", "declaration": true, - "outDir": "./lib" + "outDir": "./lib", + "allowSyntheticDefaultImports": true } } diff --git a/tracker/tracker/CHANGELOG.md b/tracker/tracker/CHANGELOG.md index 15298ce84..8bc8481fa 100644 --- a/tracker/tracker/CHANGELOG.md +++ b/tracker/tracker/CHANGELOG.md @@ -1,3 +1,7 @@ +# 15.0.0 + +- updated graphql plugin and messages + # 14.0.0 - titles for tabs diff --git a/tracker/tracker/src/common/messages.gen.ts b/tracker/tracker/src/common/messages.gen.ts index 73c66f16e..7ec02abe7 100644 --- a/tracker/tracker/src/common/messages.gen.ts +++ b/tracker/tracker/src/common/messages.gen.ts @@ -37,7 +37,7 @@ export declare const enum Type { Vuex = 45, MobX = 46, NgRx = 47, - GraphQL = 48, + GraphQLDeprecated = 48, PerformanceTrack = 49, StringDict = 50, SetNodeAttributeDict = 51, @@ -77,6 +77,7 @@ export declare const enum Type { TagTrigger = 120, Redux = 121, SetPageLocation = 122, + GraphQL = 123, } @@ -317,12 +318,13 @@ export type NgRx = [ /*duration:*/ number, ] -export type GraphQL = [ - /*type:*/ Type.GraphQL, +export type GraphQLDeprecated = [ + /*type:*/ Type.GraphQLDeprecated, /*operationKind:*/ string, /*operationName:*/ string, /*variables:*/ string, /*response:*/ string, + /*duration:*/ number, ] export type PerformanceTrack = [ @@ -615,6 +617,15 @@ export type SetPageLocation = [ /*documentTitle:*/ string, ] +export type GraphQL = [ + /*type:*/ Type.GraphQL, + /*operationKind:*/ string, + /*operationName:*/ string, + /*variables:*/ string, + /*response:*/ string, + /*duration:*/ number, +] -type Message = Timestamp | SetPageLocationDeprecated | 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 | ReduxDeprecated | Vuex | MobX | NgRx | GraphQL | PerformanceTrack | StringDict | SetNodeAttributeDict | ResourceTimingDeprecated | ConnectionInformation | SetPageVisibility | LoadFontFace | SetNodeFocus | LongTask | SetNodeAttributeURLBased | SetCSSDataURLBased | TechnicalInfo | CustomIssue | CSSInsertRuleURLBased | MouseClick | MouseClickDeprecated | CreateIFrameDocument | AdoptedSSReplaceURLBased | AdoptedSSInsertRuleURLBased | AdoptedSSDeleteRule | AdoptedSSAddOwner | AdoptedSSRemoveOwner | JSException | Zustand | BatchMetadata | PartitionedMessage | NetworkRequest | WSChannel | InputChange | SelectionChange | MouseThrashing | UnbindNodes | ResourceTiming | TabChange | TabData | CanvasNode | TagTrigger | Redux | SetPageLocation + +type Message = Timestamp | SetPageLocationDeprecated | 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 | ReduxDeprecated | Vuex | MobX | NgRx | GraphQLDeprecated | PerformanceTrack | StringDict | SetNodeAttributeDict | ResourceTimingDeprecated | ConnectionInformation | SetPageVisibility | LoadFontFace | SetNodeFocus | LongTask | SetNodeAttributeURLBased | SetCSSDataURLBased | TechnicalInfo | CustomIssue | CSSInsertRuleURLBased | MouseClick | MouseClickDeprecated | CreateIFrameDocument | AdoptedSSReplaceURLBased | AdoptedSSInsertRuleURLBased | AdoptedSSDeleteRule | AdoptedSSAddOwner | AdoptedSSRemoveOwner | JSException | Zustand | BatchMetadata | PartitionedMessage | NetworkRequest | WSChannel | InputChange | SelectionChange | MouseThrashing | UnbindNodes | ResourceTiming | TabChange | TabData | CanvasNode | TagTrigger | Redux | SetPageLocation | GraphQL export default Message diff --git a/tracker/tracker/src/main/app/messages.gen.ts b/tracker/tracker/src/main/app/messages.gen.ts index 60e36ca38..a951a6148 100644 --- a/tracker/tracker/src/main/app/messages.gen.ts +++ b/tracker/tracker/src/main/app/messages.gen.ts @@ -444,18 +444,20 @@ export function NgRx( ] } -export function GraphQL( +export function GraphQLDeprecated( operationKind: string, operationName: string, variables: string, response: string, -): Messages.GraphQL { + duration: number, +): Messages.GraphQLDeprecated { return [ - Messages.Type.GraphQL, + Messages.Type.GraphQLDeprecated, operationKind, operationName, variables, response, + duration, ] } @@ -1000,3 +1002,20 @@ export function SetPageLocation( ] } +export function GraphQL( + operationKind: string, + operationName: string, + variables: string, + response: string, + duration: number, +): Messages.GraphQL { + return [ + Messages.Type.GraphQL, + operationKind, + operationName, + variables, + response, + duration, + ] +} + diff --git a/tracker/tracker/src/webworker/MessageEncoder.gen.ts b/tracker/tracker/src/webworker/MessageEncoder.gen.ts index e1d294fac..bec1496be 100644 --- a/tracker/tracker/src/webworker/MessageEncoder.gen.ts +++ b/tracker/tracker/src/webworker/MessageEncoder.gen.ts @@ -150,8 +150,8 @@ export default class MessageEncoder extends PrimitiveEncoder { return this.string(msg[1]) && this.string(msg[2]) && this.uint(msg[3]) break - case Messages.Type.GraphQL: - return this.string(msg[1]) && this.string(msg[2]) && this.string(msg[3]) && this.string(msg[4]) + case Messages.Type.GraphQLDeprecated: + return this.string(msg[1]) && this.string(msg[2]) && this.string(msg[3]) && this.string(msg[4]) && this.int(msg[5]) break case Messages.Type.PerformanceTrack: @@ -310,6 +310,10 @@ export default class MessageEncoder extends PrimitiveEncoder { return this.string(msg[1]) && this.string(msg[2]) && this.uint(msg[3]) && this.string(msg[4]) break + case Messages.Type.GraphQL: + return this.string(msg[1]) && this.string(msg[2]) && this.string(msg[3]) && this.string(msg[4]) && this.uint(msg[5]) + break + } }