Tracker GrahpQL: update doc and tracker initialization + add option to pass sanitizer function (#2402)
* fix(graphQL): update doc and tracker initialization + add option to pass sanitizer function * improvement(graphQL): improve sanitizer type & apollo operation name
This commit is contained in:
parent
fed6d3e120
commit
a47ff4fcf0
7 changed files with 171 additions and 100 deletions
|
|
@ -18,13 +18,13 @@ returns `result` without changes.
|
|||
|
||||
```js
|
||||
import Tracker from '@openreplay/tracker';
|
||||
import trackerGraphQL from '@openreplay/tracker-graphql';
|
||||
import { createGraphqlMiddleware } from '@openreplay/tracker-graphql';
|
||||
|
||||
const tracker = new Tracker({
|
||||
projectKey: YOUR_PROJECT_KEY,
|
||||
});
|
||||
|
||||
export const recordGraphQL = tracker.plugin(trackerGraphQL());
|
||||
export const recordGraphQL = tracker.use(createGraphqlMiddleware());
|
||||
```
|
||||
|
||||
### Relay
|
||||
|
|
@ -33,15 +33,28 @@ If you're using [Relay network tools](https://github.com/relay-tools/react-relay
|
|||
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'
|
||||
import { createRelayMiddleware } from '@openreplay/tracker-graphql';
|
||||
|
||||
const trackerMiddleware = createRelayMiddleware(tracker)
|
||||
const trackerMiddleware = tracker.use(createRelayMiddleware());
|
||||
|
||||
const network = new RelayNetworkLayer([
|
||||
// your middleware
|
||||
// ,
|
||||
trackerMiddleware
|
||||
])
|
||||
trackerMiddleware,
|
||||
]);
|
||||
```
|
||||
|
||||
You can pass a Sanitizer function to `createRelayMiddleware` to sanitize the variables and data before sending them to OpenReplay.
|
||||
|
||||
```js
|
||||
const trackerLink = tracker.use(
|
||||
createRelayMiddleware((variables) => {
|
||||
return {
|
||||
...variables,
|
||||
password: '***',
|
||||
};
|
||||
}),
|
||||
);
|
||||
```
|
||||
|
||||
Or you can manually put `recordGraphQL` call
|
||||
|
|
@ -52,22 +65,22 @@ then you should do something like below
|
|||
import { createGraphqlMiddleware } from '@openreplay/tracker-graphql'; // see above for recordGraphQL definition
|
||||
import { Environment } from 'relay-runtime';
|
||||
|
||||
const handler = createGraphqlMiddleware(tracker)
|
||||
const handler = tracker.use(createGraphqlMiddleware());
|
||||
|
||||
function fetchQuery(operation, variables, cacheConfig, uploadables) {
|
||||
return fetch('www.myapi.com/resource', {
|
||||
// ...
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(result =>
|
||||
handler(
|
||||
// op kind, name, variables, response, duration (default 0)
|
||||
operation.operationKind,
|
||||
operation.name,
|
||||
variables,
|
||||
result,
|
||||
duration,
|
||||
),
|
||||
.then((response) => response.json())
|
||||
.then((result) =>
|
||||
handler(
|
||||
// op kind, name, variables, response, duration (default 0)
|
||||
operation.operationKind,
|
||||
operation.name,
|
||||
variables,
|
||||
result,
|
||||
duration,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -81,10 +94,23 @@ See [Relay Network Layer](https://relay.dev/docs/en/network-layer) for details.
|
|||
For [Apollo](https://www.apollographql.com/) you should create a new `ApolloLink`
|
||||
|
||||
```js
|
||||
import { createTrackerLink } from '@openreplay/tracker-graphql'
|
||||
import { createTrackerLink } from '@openreplay/tracker-graphql';
|
||||
|
||||
const trackerLink = createTrackerLink(tracker);
|
||||
const yourLink = new ApolloLink(trackerLink)
|
||||
const trackerLink = tracker.use(createTrackerLink());
|
||||
const yourLink = new ApolloLink(trackerLink);
|
||||
```
|
||||
|
||||
You can pass a Sanitizer function to `createRelayMiddleware` to sanitize the variables and data before sending them to OpenReplay.
|
||||
|
||||
```js
|
||||
const trackerLink = tracker.use(
|
||||
createTrackerLink((variables) => {
|
||||
return {
|
||||
...variables,
|
||||
password: '***',
|
||||
};
|
||||
}),
|
||||
);
|
||||
```
|
||||
|
||||
Alternatively you can use generic graphql handler:
|
||||
|
|
@ -93,18 +119,21 @@ Alternatively you can use generic graphql handler:
|
|||
import { createGraphqlMiddleware } from '@openreplay/tracker-graphql'; // see above for recordGraphQL definition
|
||||
import { ApolloLink } from 'apollo-link';
|
||||
|
||||
const handler = createGraphqlMiddleware(tracker)
|
||||
const handler = tracker.use(createGraphqlMiddleware());
|
||||
|
||||
const trackerApolloLink = new ApolloLink((operation, forward) => {
|
||||
return forward(operation).map(result =>
|
||||
handler(
|
||||
operation.setContext({ start: performance.now() });
|
||||
return forward(operation).map((result) => {
|
||||
const time = performance.now() - operation.getContext().start;
|
||||
return handler(
|
||||
// op kind, name, variables, response, duration (default 0)
|
||||
operation.query.definitions[0].operation,
|
||||
operation.operationName,
|
||||
operation.variables,
|
||||
result,
|
||||
),
|
||||
);
|
||||
time,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
const link = ApolloLink.from([
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { App, Messages } from '@openreplay/tracker';
|
||||
import Observable from 'zen-observable';
|
||||
import { Sanitizer } from './types';
|
||||
|
||||
type Operation = {
|
||||
query: Record<string, any>;
|
||||
|
|
@ -9,48 +10,63 @@ type Operation = {
|
|||
};
|
||||
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();
|
||||
},
|
||||
});
|
||||
export const createTrackerLink = (
|
||||
sanitizer?: Sanitizer<Record<string, any> | undefined | null>,
|
||||
) => {
|
||||
return (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();
|
||||
const operationDefinition = operation.query.definitions[0];
|
||||
app.send(
|
||||
Messages.GraphQL(
|
||||
operationDefinition.kind === 'OperationDefinition'
|
||||
? operationDefinition.operation
|
||||
: 'unknown?',
|
||||
operation.operationName,
|
||||
JSON.stringify(
|
||||
sanitizer
|
||||
? sanitizer(operation.variables)
|
||||
: operation.variables,
|
||||
),
|
||||
JSON.stringify(sanitizer ? sanitizer(value.data) : 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(
|
||||
sanitizer
|
||||
? sanitizer(operation.variables)
|
||||
: operation.variables,
|
||||
),
|
||||
JSON.stringify(error),
|
||||
end - start,
|
||||
),
|
||||
);
|
||||
observer.error(error);
|
||||
},
|
||||
complete() {
|
||||
observer.complete();
|
||||
},
|
||||
});
|
||||
|
||||
return () => subscription.unsubscribe();
|
||||
});
|
||||
return () => subscription.unsubscribe();
|
||||
});
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { App, Messages } from "@openreplay/tracker";
|
||||
import { App, Messages } from '@openreplay/tracker';
|
||||
|
||||
function createGraphqlMiddleware() {
|
||||
return (app: App | null) => {
|
||||
|
|
@ -10,7 +10,7 @@ function createGraphqlMiddleware() {
|
|||
operationName: string,
|
||||
variables: any,
|
||||
result: any,
|
||||
duration = 0
|
||||
duration = 0,
|
||||
) => {
|
||||
try {
|
||||
app.send(
|
||||
|
|
@ -30,4 +30,4 @@ function createGraphqlMiddleware() {
|
|||
};
|
||||
}
|
||||
|
||||
export default createGraphqlMiddleware
|
||||
export default createGraphqlMiddleware;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
import createTrackerLink from './apolloMiddleware.js';
|
||||
import createRelayMiddleware from './relayMiddleware.js';
|
||||
import createGraphqlMiddleware from './graphqlMiddleware.js';
|
||||
import { Sanitizer } from './types.js';
|
||||
|
||||
export {
|
||||
createTrackerLink,
|
||||
createRelayMiddleware,
|
||||
createGraphqlMiddleware,
|
||||
}
|
||||
Sanitizer,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,37 +1,55 @@
|
|||
import { App, Messages } from '@openreplay/tracker';
|
||||
import type { Middleware, RelayRequest } from './relaytypes';
|
||||
import { Sanitizer } from './types';
|
||||
|
||||
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))
|
||||
const createRelayMiddleware = (sanitizer?: Sanitizer<Record<string, any>>) => {
|
||||
return (app: App | null): Middleware => {
|
||||
if (!app) {
|
||||
return (next) => async (req) => await next(req);
|
||||
}
|
||||
return resp;
|
||||
}
|
||||
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,
|
||||
sanitizer,
|
||||
),
|
||||
);
|
||||
});
|
||||
} else {
|
||||
app.send(
|
||||
getMessage(
|
||||
req,
|
||||
resp.json as Record<string, any>,
|
||||
end - start,
|
||||
sanitizer,
|
||||
),
|
||||
);
|
||||
}
|
||||
return resp;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
function getMessage(request: RelayRequest, json: Record<string, any>, duration: number) {
|
||||
function getMessage(
|
||||
request: RelayRequest,
|
||||
json: Record<string, any>,
|
||||
duration: number,
|
||||
sanitizer?: Sanitizer<Record<string, any>>,
|
||||
) {
|
||||
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
|
||||
)
|
||||
const vars = JSON.stringify(
|
||||
sanitizer ? sanitizer(request.variables) : request.variables,
|
||||
);
|
||||
const opResp = JSON.stringify(sanitizer ? sanitizer(json) : json);
|
||||
return Messages.GraphQL(opKind, opName, vars, opResp, duration);
|
||||
}
|
||||
|
||||
export default createRelayMiddleware
|
||||
export default createRelayMiddleware;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
type ConcreteBatch = {
|
||||
kind: 'Batch';
|
||||
fragment: any;
|
||||
|
|
@ -9,7 +8,7 @@ type ConcreteBatch = {
|
|||
text: string | null;
|
||||
operationKind: string;
|
||||
};
|
||||
type Variables = { [name: string]: any };
|
||||
export type Variables = { [name: string]: any };
|
||||
interface FetchOpts {
|
||||
url?: string;
|
||||
method: 'POST' | 'GET';
|
||||
|
|
@ -17,7 +16,13 @@ interface FetchOpts {
|
|||
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';
|
||||
cache?:
|
||||
| 'default'
|
||||
| 'no-store'
|
||||
| 'reload'
|
||||
| 'no-cache'
|
||||
| 'force-cache'
|
||||
| 'only-if-cached';
|
||||
redirect?: 'follow' | 'error' | 'manual';
|
||||
signal?: AbortSignal;
|
||||
[name: string]: any;
|
||||
|
|
|
|||
1
tracker/tracker-graphql/src/types.ts
Normal file
1
tracker/tracker-graphql/src/types.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export type Sanitizer<T> = (values: T) => Partial<T>;
|
||||
Loading…
Add table
Reference in a new issue