Skip to content

Commit 84aeade

Browse files
author
Ali Abdelfattah
authored
Merge pull request #641 from Instabug/update/support-graphQL
[MOB-6124] GraphQL Support
2 parents aa26286 + 90c3933 commit 84aeade

File tree

11 files changed

+305
-44
lines changed

11 files changed

+305
-44
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
## master
22

3+
* Adds GraphQL support for APM network traces with proper grouping
34
* Adds APM.endAppLaunch API
45
* Fixes an issue with iOS sourcemap upload that causes the build to fail
56

__tests__/apm.spec.js

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*/
55

66
import "react-native";
7-
import { NativeModules } from "react-native";
7+
import { NativeModules, Platform } from "react-native";
88
import "../jest/mockAPM";
99
import APM from "../modules/APM";
1010
import sinon from "sinon";
@@ -22,6 +22,8 @@ describe("APM Module", () => {
2222
const startUITrace = sinon.spy(NativeModules.IBGAPM, "startUITrace");
2323
const endUITrace = sinon.spy(NativeModules.IBGAPM, "endUITrace");
2424
const endAppLaunch = sinon.spy(NativeModules.IBGAPM, "endAppLaunch");
25+
const setNetworkLoggingEnabled = sinon.spy(NativeModules.Instabug, "setNetworkLoggingEnabled");
26+
const _ibgSleep = sinon.spy(NativeModules.IBGAPM, "ibgSleep");
2527

2628
beforeEach(() => {
2729
IBGEventEmitter.removeAllListeners();
@@ -39,6 +41,13 @@ describe("APM Module", () => {
3941
expect(setAppLaunchEnabled.calledOnceWithExactly(true)).toBe(true);
4042
});
4143

44+
it("should call the native method setNetworkEnabledIOS", () => {
45+
Platform.OS = 'ios';
46+
APM.setNetworkEnabledIOS(true);
47+
48+
expect(setNetworkLoggingEnabled.calledOnceWithExactly(true)).toBe(true);
49+
});
50+
4251
it("should call the native method endAppLaunch", () => {
4352
APM.endAppLaunch();
4453

@@ -56,27 +65,27 @@ describe("APM Module", () => {
5665

5766
expect(setLogLevel.calledOnceWithExactly(APM.logLevel.verbose)).toBe(true);
5867
});
59-
68+
6069
it("should call the native method startExecutionTrace", () => {
6170
APM.startExecutionTrace("trace");
62-
71+
6372
expect(startExecutionTrace.calledOnceWith("trace")).toBe(true);
6473
});
65-
74+
6675
it("should call the native method setExecutionTraceAttribute", () => {
6776
const trace = APM.startExecutionTrace("trace").then(() => {
6877
trace.setAttribute("key", "value");
6978
expect(setExecutionTraceAttribute.calledOnceWithExactly(expect.any(String), "key", "value")).toBe(true);
7079
});
7180
});
72-
81+
7382
it("should call the native method endExecutionTrace", () => {
7483
const trace = APM.startExecutionTrace("trace").then(() => {
7584
trace.end();
7685
expect(endExecutionTrace.calledOnceWithExactly(expect.any(String))).toBe(true);
7786
});
7887
});
79-
88+
8089
it("should call the native method startUITrace", () => {
8190
APM.startUITrace("uiTrace");
8291

@@ -88,4 +97,10 @@ describe("APM Module", () => {
8897

8998
expect(endUITrace.calledOnceWithExactly()).toBe(true);
9099
});
100+
101+
it("should call the native method _ibgSleep", () => {
102+
APM._ibgSleep();
103+
104+
expect(_ibgSleep.calledOnceWithExactly()).toBe(true);
105+
});
91106
});

__tests__/networkLogger.spec.js

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ jest.mock('NativeModules', () => {
1919
networkLog: jest.fn(),
2020
addListener: jest.fn()
2121
},
22+
IBGAPM: {
23+
networkLog: jest.fn(),
24+
},
2225
};
2326
});
2427

@@ -33,6 +36,8 @@ describe('NetworkLogger Module', () => {
3336
const enableInterception = sinon.spy(Interceptor, 'enableInterception');
3437
const disableInterception = sinon.spy(Interceptor, 'disableInterception');
3538
const networkLog = sinon.spy(NativeModules.Instabug, 'networkLog');
39+
const apmNetworkLog = sinon.spy(NativeModules.IBGAPM, 'networkLog');
40+
const apolloLinkRequestHandler = sinon.spy(NetworkLogger, 'apolloLinkRequestHandler')
3641

3742
const network = {
3843
url: 'https://api.instabug.com',
@@ -53,6 +58,8 @@ describe('NetworkLogger Module', () => {
5358
networkLog.resetHistory();
5459
IBGEventEmitter.removeAllListeners();
5560
NetworkLogger.setNetworkDataObfuscationHandler(null);
61+
apmNetworkLog.resetHistory();
62+
apolloLinkRequestHandler.resetHistory();
5663
});
5764

5865
it('should set onProgressCallback with callback', () => {
@@ -98,7 +105,7 @@ describe('NetworkLogger Module', () => {
98105
NetworkLogger.setEnabled(true);
99106

100107
expect(networkLog.calledOnceWithExactly(JSON.stringify(network))).toBe(true);
101-
108+
expect(apmNetworkLog.calledOnceWithExactly(JSON.stringify(network))).toBe(true);
102109
});
103110

104111
it('should send log network when setNetworkDataObfuscationHandler is set and Platform is ios', async () => {
@@ -137,6 +144,7 @@ describe('NetworkLogger Module', () => {
137144
const newData = clone(network);
138145
newData.requestHeaders['token'] = randomString;
139146
expect(networkLog.calledOnceWithExactly(JSON.stringify(newData))).toBe(true);
147+
expect(apmNetworkLog.calledOnceWithExactly(JSON.stringify(newData))).toBe(true);
140148
});
141149

142150
});
@@ -148,8 +156,27 @@ describe('NetworkLogger Module', () => {
148156
NetworkLogger.setEnabled(true);
149157

150158
expect(networkLog.notCalled).toBe(true);
151-
159+
expect(apmNetworkLog.notCalled).toBe(true);
152160
});
153161

162+
it('should test that operationSetContext at apollo handler called', async () => {
163+
const operation = {
164+
setContext : (callback) => callback({ headers : {} }),
165+
operationName : "operationName"
166+
};
167+
const forward = jest.fn();
168+
const operationSetContextMock = sinon.spy(operation, 'setContext')
169+
170+
NetworkLogger.apolloLinkRequestHandler(operation, forward);
171+
expect(operationSetContextMock.calledOnce).toBe(true);
172+
});
173+
174+
it('should test that apollo handler called with catch error', async () => {
175+
const operation = {};
176+
const forward = jest.fn();
177+
178+
NetworkLogger.apolloLinkRequestHandler(operation, forward);
179+
expect(apolloLinkRequestHandler.calledOnce).toBe(true);
180+
});
154181

155182
});

__tests__/xhrNetworkInterceptor.spec.js

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'react-native';
22
import sinon from 'sinon';
33
import FakeRequest from '../jest/fakeNetworkRequest';
4+
import InstabugConstants from '../utils/InstabugConstants';
45

56
global.XMLHttpRequest = sinon.useFakeXMLHttpRequest();
67

@@ -44,7 +45,7 @@ describe('Network Interceptor', () => {
4445

4546
it('should set network object on calling setRequestHeader', (done) => {
4647

47-
let requestHeaders = { 'Content-type': 'application/json', 'token': '9u4hiudhi3bf' };
48+
let requestHeaders = { 'content-type': 'application/json', 'token': '9u4hiudhi3bf' };
4849

4950
Interceptor.enableInterception();
5051
Interceptor.setOnDoneCallback((network) => {
@@ -87,7 +88,7 @@ describe('Network Interceptor', () => {
8788

8889
it('should set responseHeaders in network object on receiving response', (done) => {
8990

90-
const headers = { 'Content-type': 'application/json', 'Accept': 'text/html' }
91+
const headers = { 'Content-type': 'application/json', 'Accept': 'text/html','Content-Length':144 }
9192
Interceptor.enableInterception();
9293
Interceptor.setOnDoneCallback((network) => {
9394
expect(network.responseHeaders['Content-type'].trim()).toEqual(headers['Content-type']);
@@ -168,6 +169,54 @@ describe('Network Interceptor', () => {
168169
FakeRequest.mockResponse(requests[0]);
169170
expect(callback).not.toHaveBeenCalled();
170171
});
172+
it('should set gqlQueryName in network object on receiving response', (done) => {
173+
const headers = {};
174+
headers[InstabugConstants.GRAPHQL_HEADER] =
175+
InstabugConstants.GRAPHQL_HEADER;
176+
const responseBody = { data: [{ item: 'first' }, { item: 'second' }] };
177+
Interceptor.enableInterception();
178+
Interceptor.setOnDoneCallback((network) => {
179+
expect(network.gqlQueryName).toEqual(
180+
headers[InstabugConstants.GRAPHQL_HEADER]
181+
);
182+
done();
183+
});
184+
FakeRequest.open(method, url);
185+
FakeRequest.setRequestHeaders(headers);
186+
FakeRequest.send();
187+
FakeRequest.mockResponse(requests[0], null, JSON.stringify(responseBody));
188+
});
189+
190+
it('should set gqlQueryName in network object on receiving response with empty string', (done) => {
191+
const headers = {};
192+
headers[InstabugConstants.GRAPHQL_HEADER] = 'null';
193+
Interceptor.enableInterception();
194+
Interceptor.setOnDoneCallback((network) => {
195+
expect(network.gqlQueryName).toEqual('');
196+
done();
197+
});
198+
FakeRequest.open(method, url);
199+
FakeRequest.setRequestHeaders(headers);
200+
FakeRequest.send();
201+
FakeRequest.mockResponse(requests[0]);
202+
});
203+
204+
it('should set serverErrorMessage in network object on receiving response', (done) => {
205+
const headers = {};
206+
headers[InstabugConstants.GRAPHQL_HEADER] =
207+
InstabugConstants.GRAPHQL_HEADER;
208+
const responseBody = { errors: [{ item: 'first' }, { item: 'second' }] };
209+
Interceptor.enableInterception();
210+
Interceptor.setOnDoneCallback((network) => {
211+
expect(network.serverErrorMessage).toEqual('GraphQLError');
212+
done();
213+
});
214+
FakeRequest.open(method, url);
215+
FakeRequest.setRequestHeaders(headers);
216+
FakeRequest.setResponseType('text');
217+
FakeRequest.send();
218+
FakeRequest.mockResponse(requests[0], null, JSON.stringify(responseBody));
219+
});
171220

172221

173-
});
222+
});

android/src/main/java/com/instabug/reactlibrary/RNInstabugAPMModule.java

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,44 @@
44
import android.os.Handler;
55
import android.os.Looper;
66
import android.os.SystemClock;
7+
import android.util.Log;
78

89
import com.facebook.react.bridge.Callback;
910
import com.facebook.react.bridge.ReactApplicationContext;
1011
import com.facebook.react.bridge.ReactContextBaseJavaModule;
1112
import com.facebook.react.bridge.ReactMethod;
1213
import com.instabug.apm.APM;
1314
import com.instabug.apm.model.ExecutionTrace;
15+
import com.instabug.apm.networking.APMNetworkLogger;
1416
import com.instabug.bug.BugReporting;
1517
import com.instabug.chat.Chats;
1618
import com.instabug.library.Feature;
1719
import com.instabug.reactlibrary.utils.InstabugUtil;
1820
import com.instabug.reactlibrary.utils.MainThreadHandler;
1921

22+
import org.json.JSONException;
23+
import org.json.JSONObject;
24+
25+
import java.io.File;
26+
import java.lang.reflect.InvocationTargetException;
27+
import java.lang.reflect.Method;
28+
29+
import java.io.File;
30+
import java.lang.reflect.InvocationTargetException;
31+
import java.lang.reflect.Method;
32+
import java.util.ArrayList;
2033
import java.util.HashMap;
34+
import java.util.Iterator;
35+
import java.util.LinkedHashMap;
36+
import java.util.List;
37+
import java.util.Locale;
38+
import java.util.Map;
39+
import com.instabug.library.Platform;
2140

2241
import javax.annotation.Nonnull;
2342

43+
import static com.instabug.reactlibrary.utils.InstabugUtil.getMethod;
44+
2445
public class RNInstabugAPMModule extends ReactContextBaseJavaModule {
2546

2647
public RNInstabugAPMModule(ReactApplicationContext reactApplicationContext) {
@@ -238,4 +259,62 @@ public void run() {
238259
}
239260
});
240261
}
262+
263+
/**
264+
* Send Apm network log by Reflection
265+
*/
266+
@ReactMethod
267+
public void networkLog(String networkData) throws JSONException {
268+
try{
269+
APMNetworkLogger apmNetworkLogger = new APMNetworkLogger();
270+
JSONObject jsonObject = new JSONObject(networkData);
271+
final String requestUrl = (String) jsonObject.get("url");
272+
final String requestBody = (String) jsonObject.get("requestBody");
273+
final String responseBody = (String) jsonObject.get("responseBody");
274+
final String requestMethod = (String) jsonObject.get("method");
275+
//--------------------------------------------
276+
final String requestContentType = (String) jsonObject.get("requestContentType");
277+
final String responseContentType = (String) jsonObject.get("contentType");
278+
//--------------------------------------------
279+
final long requestBodySize = ((Number) jsonObject.get("requestBodySize")).longValue();
280+
final long responseBodySize = ((Number) jsonObject.get("responseBodySize")).longValue();
281+
//--------------------------------------------
282+
final String errorDomain = (String) jsonObject.get("errorDomain");
283+
final Integer statusCode = (Integer) jsonObject.get("responseCode");
284+
final long requestDuration = ((Number) jsonObject.get("duration")).longValue();
285+
final long requestStartTime = ((Number) jsonObject.get("startTime")).longValue() * 1000;
286+
final String requestHeaders = (String) jsonObject.get("requestHeaders").toString();
287+
final String responseHeaders = (String) jsonObject.get("responseHeaders").toString();
288+
final String errorMessage;
289+
if(errorDomain.equals("")) {
290+
errorMessage = null;
291+
} else {
292+
errorMessage = errorDomain;
293+
}
294+
//--------------------------------------------
295+
String gqlQueryName = null;
296+
if(jsonObject.has("gqlQueryName")){
297+
gqlQueryName = (String) jsonObject.get("gqlQueryName");
298+
}
299+
final String serverErrorMessage = (String) jsonObject.get("serverErrorMessage");
300+
301+
try {
302+
Method method = getMethod(Class.forName("com.instabug.apm.networking.APMNetworkLogger"), "log", long.class, long.class, String.class, String.class, long.class, String.class, String.class, String.class, String.class, String.class, long.class, int.class, String.class, String.class, String.class, String.class);
303+
if (method != null) {
304+
method.invoke(apmNetworkLogger, requestStartTime, requestDuration, requestHeaders, requestBody, requestBodySize, requestMethod, requestUrl, requestContentType, responseHeaders, responseBody, responseBodySize, statusCode, responseContentType, errorMessage, gqlQueryName, serverErrorMessage);
305+
} else {
306+
Log.e("IB-CP-Bridge", "apmNetworkLogByReflection was not found by reflection");
307+
}
308+
} catch (ClassNotFoundException e) {
309+
e.printStackTrace();
310+
} catch (IllegalAccessException e) {
311+
e.printStackTrace();
312+
} catch (InvocationTargetException e) {
313+
e.printStackTrace();
314+
}
315+
}
316+
catch(Exception e) {
317+
e.printStackTrace();
318+
}
319+
}
241320
}

index.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,12 @@ export namespace NetworkLogger {
532532
* @param {function} handler
533533
*/
534534
function setProgressHandlerForRequest(handler: () => void): void;
535+
/**
536+
* Apollo Link Request Handler to track network log for graphQL using apollo
537+
* @param {any} operation
538+
* @param {any} forward
539+
*/
540+
function apolloLinkRequestHandler(operation: any, forward: any):any;
535541
}
536542
export class Trace {
537543
constructor(id: string, name?: string, attributes?: object);

ios/RNInstabug/InstabugReactBridge.m

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,16 @@ - (dispatch_queue_t)methodQueue {
376376
int64_t startTime = [networkData[@"startTime"] integerValue] * 1000;
377377
int64_t duration = [networkData[@"duration"] doubleValue] * 1000;
378378

379-
SEL networkLogSEL = NSSelectorFromString(@"addNetworkLogWithUrl:method:requestBody:requestBodySize:responseBody:responseBodySize:responseCode:requestHeaders:responseHeaders:contentType:errorDomain:errorCode:startTime:duration:");
379+
NSString* gqlQueryName = nil;
380+
NSString* serverErrorMessage = nil;
381+
if (networkData[@"gqlQueryName"] != [NSNull null]) {
382+
gqlQueryName = networkData[@"gqlQueryName"];
383+
}
384+
if (networkData[@"serverErrorMessage"] != [NSNull null]) {
385+
serverErrorMessage = networkData[@"serverErrorMessage"];
386+
}
387+
388+
SEL networkLogSEL = NSSelectorFromString(@"addNetworkLogWithUrl:method:requestBody:requestBodySize:responseBody:responseBodySize:responseCode:requestHeaders:responseHeaders:contentType:errorDomain:errorCode:startTime:duration:gqlQueryName:serverErrorMessage:");
380389

381390
if([[IBGNetworkLogger class] respondsToSelector:networkLogSEL]) {
382391
NSInvocation *inv = [NSInvocation invocationWithMethodSignature:[[IBGNetworkLogger class] methodSignatureForSelector:networkLogSEL]];
@@ -397,7 +406,9 @@ - (dispatch_queue_t)methodQueue {
397406
[inv setArgument:&(errorCode) atIndex:13];
398407
[inv setArgument:&(startTime) atIndex:14];
399408
[inv setArgument:&(duration) atIndex:15];
400-
409+
[inv setArgument:&(gqlQueryName) atIndex:16];
410+
[inv setArgument:&(serverErrorMessage) atIndex:17];
411+
401412
[inv invoke];
402413
}
403414
}

0 commit comments

Comments
 (0)