Skip to content

Commit

Permalink
Merge pull request #525 from DataDog/louiszawadzki/graphql-integration
Browse files Browse the repository at this point in the history
Graphql integration
  • Loading branch information
louiszawadzki authored Oct 5, 2023
2 parents d7bd922 + 10af167 commit 487bab0
Show file tree
Hide file tree
Showing 21 changed files with 1,181 additions and 24 deletions.
4 changes: 3 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ yarn install

This repository contains 2 main projects:

* SDK project (in the `packages` folder), which consists of 4 workspaces:
* SDK project (in the `packages` folder), which consists of the following workspaces:
* `codepush`: an integration for the [react-native-code-push](https://github.com/microsoft/react-native-code-push) library.
* `core`: the core React Native SDK allowing tracking of logs, spans and RUM events.
* `react-native-apollo-client`: an integration for the [Apollo Client](https://www.apollographql.com/docs/react/integrations/react-native/) library.
* `react-native-navigation`: an integration for the [react-native-navigation](https://github.com/wix/react-native-navigation) library.
* `react-native-webview`: an integration for the [`react-native-webview`](https://github.com/react-native-webview/react-native-webview) library.
* `react-navigation`: an integration for the [react-navigation](https://github.com/react-navigation/react-navigation) library.
* Sample app project (in the `example` folder)

Expand Down
10 changes: 9 additions & 1 deletion packages/core/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ import { SdkVerbosity } from './SdkVerbosity';
import { TrackingConsent } from './TrackingConsent';
import { DdLogs } from './logs/DdLogs';
import { DdRum } from './rum/DdRum';
import {
DATADOG_GRAPH_QL_OPERATION_TYPE_HEADER,
DATADOG_GRAPH_QL_OPERATION_NAME_HEADER,
DATADOG_GRAPH_QL_VARIABLES_HEADER
} from './rum/instrumentation/resourceTracking/graphql/graphqlHeaders';
import { RumActionType, ErrorSource, PropagatorType } from './rum/types';
import { DatadogProvider } from './sdk/DatadogProvider/DatadogProvider';
import { DdTrace } from './trace/DdTrace';
Expand All @@ -42,5 +47,8 @@ export {
VitalsUpdateFrequency,
PropagatorType,
UploadFrequency,
BatchSize
BatchSize,
DATADOG_GRAPH_QL_OPERATION_TYPE_HEADER,
DATADOG_GRAPH_QL_OPERATION_NAME_HEADER,
DATADOG_GRAPH_QL_VARIABLES_HEADER
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2016-Present Datadog, Inc.
*/

import {
DATADOG_GRAPH_QL_OPERATION_NAME_HEADER,
DATADOG_GRAPH_QL_OPERATION_TYPE_HEADER,
DATADOG_GRAPH_QL_VARIABLES_HEADER,
isDatadogCustomHeader
} from '../graphqlHeaders';

describe('GraphQL custom headers', () => {
it.each([
DATADOG_GRAPH_QL_OPERATION_NAME_HEADER,
DATADOG_GRAPH_QL_OPERATION_TYPE_HEADER,
DATADOG_GRAPH_QL_VARIABLES_HEADER
])('%s matches the custom header pattern', header => {
expect(isDatadogCustomHeader(header)).toBeTruthy();
});

describe('isDatadogCustomHeader', () => {
it('returns false for non-custom headers', () => {
expect(isDatadogCustomHeader('non-custom-header')).toBeFalsy();
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2016-Present Datadog, Inc.
*/

const DATADOG_CUSTOM_HEADER_PREFIX = '_dd-custom-header';

export const DATADOG_GRAPH_QL_OPERATION_NAME_HEADER = `${DATADOG_CUSTOM_HEADER_PREFIX}-graph-ql-operation-name`;
export const DATADOG_GRAPH_QL_VARIABLES_HEADER = `${DATADOG_CUSTOM_HEADER_PREFIX}-graph-ql-variables`;
export const DATADOG_GRAPH_QL_OPERATION_TYPE_HEADER = `${DATADOG_CUSTOM_HEADER_PREFIX}-graph-ql-operation-type`;

export const isDatadogCustomHeader = (header: string) => {
return header.match(new RegExp(`^${DATADOG_CUSTOM_HEADER_PREFIX}`));
};
Original file line number Diff line number Diff line change
Expand Up @@ -34,29 +34,44 @@ export class ResourceReporter {

const formatResourceStartContext = (
tracingAttributes: RUMResource['tracingAttributes']
): Record<string, string | number> | undefined => {
return tracingAttributes.samplingPriorityHeader === '0'
? undefined
: {
'_dd.span_id': tracingAttributes.spanId.toString(10),
'_dd.trace_id': tracingAttributes.traceId.toString(10),
'_dd.rule_psr': tracingAttributes.rulePsr
};
): Record<string, string | number> => {
const attributes: Record<string, string | number> = {};
if (tracingAttributes.samplingPriorityHeader !== '0') {
attributes['_dd.span_id'] = tracingAttributes.spanId.toString(10);
attributes['_dd.trace_id'] = tracingAttributes.traceId.toString(10);
attributes['_dd.rule_psr'] = tracingAttributes.rulePsr;
}

return attributes;
};

const formatResourceStopContext = (
timings: RUMResource['timings']
timings: RUMResource['timings'],
graphqlAttributes: RUMResource['graphqlAttributes']
): Record<string, unknown> => {
return {
'_dd.resource_timings':
timings.responseStartTime !== undefined
? createTimings(
timings.startTime,
timings.responseStartTime,
timings.stopTime
)
: null
};
const attributes: Record<string, unknown> = {};

if (timings.responseStartTime !== undefined) {
attributes['_dd.resource_timings'] = createTimings(
timings.startTime,
timings.responseStartTime,
timings.stopTime
);
}

if (graphqlAttributes?.operationType) {
attributes['_dd.graphql.operation_type'] =
graphqlAttributes.operationType;
if (graphqlAttributes.operationName) {
attributes['_dd.graphql.operation_name'] =
graphqlAttributes.operationName;
}
if (graphqlAttributes.variables) {
attributes['_dd.graphql.variables'] = graphqlAttributes.variables;
}
}

return attributes;
};

const reportResource = async (resource: RUMResource) => {
Expand All @@ -73,7 +88,7 @@ const reportResource = async (resource: RUMResource) => {
resource.response.statusCode,
resource.request.kind,
resource.response.size,
formatResourceStopContext(resource.timings),
formatResourceStopContext(resource.timings, resource.graphqlAttributes),
resource.timings.stopTime,
resource.resourceContext
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ import Timer from '../../../../../Timer';
import { getTracingHeaders } from '../../distributedTracing/distributedTracingHeaders';
import type { DdRumResourceTracingAttributes } from '../../distributedTracing/distributedTracing';
import { getTracingAttributes } from '../../distributedTracing/distributedTracing';
import {
DATADOG_GRAPH_QL_OPERATION_NAME_HEADER,
DATADOG_GRAPH_QL_OPERATION_TYPE_HEADER,
DATADOG_GRAPH_QL_VARIABLES_HEADER,
isDatadogCustomHeader
} from '../../graphql/graphqlHeaders';
import type { RequestProxyOptions } from '../interfaces/RequestProxy';
import { RequestProxy } from '../interfaces/RequestProxy';

Expand All @@ -22,6 +28,11 @@ interface DdRumXhr extends XMLHttpRequest {
}

interface DdRumXhrContext {
graphql: {
operationType?: string;
operationName?: string;
variables?: string;
};
method: string;
url: string;
reported: boolean;
Expand All @@ -41,6 +52,7 @@ export class XHRProxy extends RequestProxy {
private providers: XHRProxyProviders;
private static originalXhrOpen: typeof XMLHttpRequest.prototype.open;
private static originalXhrSend: typeof XMLHttpRequest.prototype.send;
private static originalXhrSetRequestHeader: typeof XMLHttpRequest.prototype.setRequestHeader;

constructor(providers: XHRProxyProviders) {
super();
Expand All @@ -50,12 +62,15 @@ export class XHRProxy extends RequestProxy {
onTrackingStart = (context: RequestProxyOptions) => {
XHRProxy.originalXhrOpen = this.providers.xhrType.prototype.open;
XHRProxy.originalXhrSend = this.providers.xhrType.prototype.send;
XHRProxy.originalXhrSetRequestHeader = this.providers.xhrType.prototype.setRequestHeader;
proxyRequests(this.providers, context);
};

onTrackingStop = () => {
this.providers.xhrType.prototype.open = XHRProxy.originalXhrOpen;
this.providers.xhrType.prototype.send = XHRProxy.originalXhrSend;
this.providers.xhrType.prototype.setRequestHeader =
XHRProxy.originalXhrSetRequestHeader;
};
}

Expand All @@ -65,6 +80,7 @@ const proxyRequests = (
): void => {
proxyOpen(providers, context);
proxySend(providers);
proxySetRequestHeader(providers);
};

const proxyOpen = (
Expand All @@ -88,6 +104,7 @@ const proxyOpen = (
url,
reported: false,
timer: new Timer(),
graphql: {},
tracingAttributes: getTracingAttributes({
hostname,
firstPartyHostsRegexMap,
Expand Down Expand Up @@ -166,6 +183,7 @@ const reportXhr = async (
url: context.url,
kind: 'xhr'
},
graphqlAttributes: context.graphql,
tracingAttributes: context.tracingAttributes,
response: {
statusCode: xhrProxy.status,
Expand All @@ -181,3 +199,32 @@ const reportXhr = async (
resourceContext: xhrProxy
});
};

const proxySetRequestHeader = (providers: XHRProxyProviders): void => {
const xhrType = providers.xhrType;
const originalXhrSetRequestHeader = xhrType.prototype.setRequestHeader;

xhrType.prototype.setRequestHeader = function (
this: DdRumXhr,
header: string,
value: string
) {
if (isDatadogCustomHeader(header)) {
if (header === DATADOG_GRAPH_QL_OPERATION_NAME_HEADER) {
this._datadog_xhr.graphql.operationName = value;
return;
}
if (header === DATADOG_GRAPH_QL_OPERATION_TYPE_HEADER) {
this._datadog_xhr.graphql.operationType = value;
return;
}
if (header === DATADOG_GRAPH_QL_VARIABLES_HEADER) {
this._datadog_xhr.graphql.variables = value;
return;
}
}

// eslint-disable-next-line prefer-rest-params
return originalXhrSetRequestHeader.apply(this, arguments as any);
};
};
Loading

0 comments on commit 487bab0

Please sign in to comment.