Graphql plugin update (#1835) -- v1.19.0

* feat(tracker): relay + apollo plugins

* fix(tracker): type fixes

* fix(tracker): update mobs messages

* fix msg conflict
This commit is contained in:
Delirium 2024-06-25 10:13:13 +02:00 committed by GitHub
parent 3654dccec1
commit 6a42d96e21
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 525 additions and 78 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

BIN
tracker/tracker-graphql/bun.lockb Executable file

Binary file not shown.

View file

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

View file

@ -0,0 +1,57 @@
import { App, Messages } from '@openreplay/tracker';
import Observable from 'zen-observable';
type Operation = {
query: Record<string, any>;
variables: Record<string, any>;
operationName: string;
extensions: Record<string, any>;
};
type NextLink = (operation: Operation) => Observable<Record<string, any>>;
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;

View file

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

View file

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

View file

@ -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<string, any>, end - start))
})
} else {
app.send(getMessage(req, resp.json as Record<string, any>, end - start))
}
return resp;
}
};
function getMessage(request: RelayRequest, json: Record<string, any>, 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

View file

@ -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<string, any>;
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<RelayResponse>;
export type Middleware = (next: MiddlewareNextFn) => MiddlewareNextFn;

View file

@ -7,6 +7,7 @@
"module": "es6",
"moduleResolution": "node",
"declaration": true,
"outDir": "./lib"
"outDir": "./lib",
"allowSyntheticDefaultImports": true
}
}

View file

@ -1,3 +1,7 @@
# 15.0.0
- updated graphql plugin and messages
# 14.0.0
- titles for tabs

View file

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

View file

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

View file

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