Skip to content

Commit ba718d4

Browse files
Merge pull request #164 from microsoftgraph/TelemetryHandler
Telemetry Handler implementation.
2 parents 430d92d + a4d6aa2 commit ba718d4

16 files changed

+431
-29
lines changed

.vscode/tasks.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
"tasks": [
66
{
77
"label": "Run Compile",
8-
"type": "npm",
9-
"script": "compile",
8+
"type": "typescript",
9+
"tsconfig": "tsconfig-cjs.json",
1010
"group": "build"
1111
}
1212
]

spec/middleware/MiddlewareControl.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ describe("MiddlewareControl.ts", () => {
2525
assert.equal(middlewareControl["middlewareOptions"].size, 1);
2626
});
2727

28-
it("Should create empty middleware options map for empty middleware options array", () => {
29-
const middlewareControl = new MiddlewareControl([]);
28+
it("Should create empty middleware options map by default", () => {
29+
const middlewareControl = new MiddlewareControl();
3030
assert.isDefined(middlewareControl["middlewareOptions"]);
3131
assert.equal(middlewareControl["middlewareOptions"].size, 0);
3232
});

spec/middleware/MiddlewareUtil.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import { assert } from "chai";
99

1010
import { FetchOptions } from "../../src/IFetchOptions";
11-
import { cloneRequestWithNewUrl, getRequestHeader, setRequestHeader } from "../../src/middleware/MiddlewareUtil";
11+
import { generateUUID, getRequestHeader, setRequestHeader } from "../../src/middleware/MiddlewareUtil";
1212

1313
describe("MiddlewareUtil.ts", async () => {
1414
describe("setRequestHeader", () => {
@@ -117,4 +117,11 @@ describe("MiddlewareUtil.ts", async () => {
117117
assert.equal(headerValue, value);
118118
});
119119
});
120+
121+
describe("generateUUID", () => {
122+
it("Should return the unique uuid", () => {
123+
const uuid = generateUUID();
124+
assert.equal(uuid.length, 36);
125+
});
126+
});
120127
});

spec/middleware/TelemetryHandler.ts

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/**
2+
* -------------------------------------------------------------------------------------------
3+
* Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License.
4+
* See License in the project root for license information.
5+
* -------------------------------------------------------------------------------------------
6+
*/
7+
8+
import { assert } from "chai";
9+
10+
import { PACKAGE_VERSION } from "../../src/Constants";
11+
import { Context } from "../../src/IContext";
12+
import { MiddlewareControl } from "../../src/middleware/MiddlewareControl";
13+
import { FeatureUsageFlag, TelemetryHandlerOptions } from "../../src/middleware/options/TelemetryHandlerOptions";
14+
import { TelemetryHandler } from "../../src/middleware/TelemetryHandler";
15+
import { DummyHTTPMessageHandler } from "../DummyHTTPMessageHandler";
16+
17+
describe("TelemetryHandler.ts", () => {
18+
/* tslint:disable: no-string-literal */
19+
describe("execute", function() {
20+
this.timeout(20 * 1000);
21+
const telemetryHandler = new TelemetryHandler();
22+
const dummyHTTPHandler = new DummyHTTPMessageHandler();
23+
telemetryHandler.setNext(dummyHTTPHandler);
24+
const okayResponse = new Response("", {
25+
status: 200,
26+
statusText: "OK",
27+
});
28+
it("Should not disturb client-request-id in the header", async () => {
29+
try {
30+
const uuid = "dummy_uuid";
31+
const context: Context = {
32+
request: "url",
33+
options: {
34+
headers: {
35+
"client-request-id": uuid,
36+
},
37+
},
38+
};
39+
dummyHTTPHandler.setResponses([okayResponse]);
40+
await telemetryHandler.execute(context);
41+
assert.equal(context.options.headers["client-request-id"], uuid);
42+
} catch (error) {
43+
throw error;
44+
}
45+
});
46+
47+
it("Should create client-request-id if one is not present in the request header", async () => {
48+
try {
49+
const context: Context = {
50+
request: "url",
51+
options: {
52+
headers: {
53+
method: "GET",
54+
},
55+
},
56+
};
57+
dummyHTTPHandler.setResponses([okayResponse]);
58+
await telemetryHandler.execute(context);
59+
assert.isDefined(context.options.headers["client-request-id"]);
60+
} catch (error) {
61+
throw error;
62+
}
63+
});
64+
65+
it("Should set sdk version header without feature flag usage if telemetry options is not present", async () => {
66+
try {
67+
const context: Context = {
68+
request: "url",
69+
options: {
70+
headers: {
71+
method: "GET",
72+
},
73+
},
74+
};
75+
dummyHTTPHandler.setResponses([okayResponse]);
76+
await telemetryHandler.execute(context);
77+
assert.equal(context.options.headers["SdkVersion"], `graph-js/${PACKAGE_VERSION}`);
78+
} catch (error) {
79+
throw error;
80+
}
81+
});
82+
83+
it("Should set sdk version header with feature flag", async () => {
84+
try {
85+
const telemetryOptions = new TelemetryHandlerOptions();
86+
telemetryOptions["setFeatureUsage"](FeatureUsageFlag.AUTHENTICATION_HANDLER_ENABLED);
87+
const context: Context = {
88+
request: "url",
89+
options: {
90+
headers: {
91+
method: "GET",
92+
},
93+
},
94+
middlewareControl: new MiddlewareControl([telemetryOptions]),
95+
};
96+
dummyHTTPHandler.setResponses([okayResponse]);
97+
await telemetryHandler.execute(context);
98+
assert.equal(context.options.headers["SdkVersion"], `graph-js/${PACKAGE_VERSION} (featureUsage=${FeatureUsageFlag.AUTHENTICATION_HANDLER_ENABLED.toString(16)})`);
99+
} catch (error) {
100+
throw error;
101+
}
102+
});
103+
});
104+
/* tslint:enable: no-string-literal */
105+
});
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/**
2+
* -------------------------------------------------------------------------------------------
3+
* Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License.
4+
* See License in the project root for license information.
5+
* -------------------------------------------------------------------------------------------
6+
*/
7+
8+
import { assert } from "chai";
9+
10+
import { Context } from "../../src/IContext";
11+
import { MiddlewareControl } from "../../src/middleware/MiddlewareControl";
12+
import { FeatureUsageFlag, TelemetryHandlerOptions } from "../../src/middleware/options/TelemetryHandlerOptions";
13+
14+
describe("TelemetryHandlerOptions.ts", () => {
15+
/* tslint:disable: no-string-literal no-bitwise */
16+
describe("constructor", () => {
17+
it("Should initialize the feature usage flag with NONE flag", () => {
18+
const telemetryOptions = new TelemetryHandlerOptions();
19+
assert.equal(telemetryOptions["featureUsage"], FeatureUsageFlag.NONE);
20+
});
21+
});
22+
23+
describe("updateFeatureUsageFlag", () => {
24+
it("Should update feature usage flag for middleware control without telemetry handler option", () => {
25+
const context: Context = {
26+
request: "url",
27+
middlewareControl: new MiddlewareControl(),
28+
};
29+
TelemetryHandlerOptions.updateFeatureUsageFlag(context, FeatureUsageFlag.AUTHENTICATION_HANDLER_ENABLED);
30+
const options: TelemetryHandlerOptions = context.middlewareControl.getMiddlewareOptions(TelemetryHandlerOptions.name) as TelemetryHandlerOptions;
31+
assert.equal(options["featureUsage"], FeatureUsageFlag.AUTHENTICATION_HANDLER_ENABLED);
32+
});
33+
34+
it("Should update feature usage flag for middleware control with telemetry handler option", () => {
35+
const context: Context = {
36+
request: "url",
37+
middlewareControl: new MiddlewareControl([new TelemetryHandlerOptions()]),
38+
};
39+
TelemetryHandlerOptions.updateFeatureUsageFlag(context, FeatureUsageFlag.AUTHENTICATION_HANDLER_ENABLED);
40+
const options: TelemetryHandlerOptions = context.middlewareControl.getMiddlewareOptions(TelemetryHandlerOptions.name) as TelemetryHandlerOptions;
41+
assert.equal(options["featureUsage"], FeatureUsageFlag.AUTHENTICATION_HANDLER_ENABLED);
42+
});
43+
44+
it("Should update feature usage flag for context object without middleware control object", () => {
45+
const context: Context = {
46+
request: "url",
47+
};
48+
TelemetryHandlerOptions.updateFeatureUsageFlag(context, FeatureUsageFlag.AUTHENTICATION_HANDLER_ENABLED);
49+
const options: TelemetryHandlerOptions = context.middlewareControl.getMiddlewareOptions(TelemetryHandlerOptions.name) as TelemetryHandlerOptions;
50+
assert.equal(options["featureUsage"], FeatureUsageFlag.AUTHENTICATION_HANDLER_ENABLED);
51+
});
52+
});
53+
54+
describe("setFeatureUsage", () => {
55+
it("Should set a given flags", () => {
56+
const telemetryOptions = new TelemetryHandlerOptions();
57+
telemetryOptions["setFeatureUsage"](FeatureUsageFlag.AUTHENTICATION_HANDLER_ENABLED);
58+
telemetryOptions["setFeatureUsage"](FeatureUsageFlag.RETRY_HANDLER_ENABLED);
59+
assert.equal(telemetryOptions["featureUsage"] & FeatureUsageFlag.AUTHENTICATION_HANDLER_ENABLED, FeatureUsageFlag.AUTHENTICATION_HANDLER_ENABLED);
60+
assert.equal(telemetryOptions["featureUsage"] & FeatureUsageFlag.RETRY_HANDLER_ENABLED, FeatureUsageFlag.RETRY_HANDLER_ENABLED);
61+
assert.equal(telemetryOptions["featureUsage"] & FeatureUsageFlag.REDIRECT_HANDLER_ENABLED, FeatureUsageFlag.NONE);
62+
});
63+
});
64+
65+
describe("getFeatureUsage", () => {
66+
it("Should return the feature usage in hexadecimal string", () => {
67+
const telemetryOptions = new TelemetryHandlerOptions();
68+
telemetryOptions["setFeatureUsage"](FeatureUsageFlag.AUTHENTICATION_HANDLER_ENABLED);
69+
telemetryOptions["setFeatureUsage"](FeatureUsageFlag.RETRY_HANDLER_ENABLED);
70+
const usageFlag = telemetryOptions.getFeatureUsage();
71+
assert.equal(usageFlag, "6");
72+
});
73+
});
74+
/* tslint:enable: no-string-literal no-bitwise*/
75+
});

src/GraphRequest.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
* @module GraphRequest
1010
*/
1111

12-
import { PACKAGE_VERSION } from "./Constants";
1312
import { GraphError } from "./GraphError";
1413
import { GraphErrorHandler } from "./GraphErrorHandler";
1514
import { oDataQueryNames, serializeContent, urlJoin } from "./GraphRequestUtil";
@@ -255,9 +254,6 @@ export class GraphRequest {
255254
* @returns Nothing
256255
*/
257256
private updateRequestOptions(options: FetchOptions): void {
258-
const defaultHeaders = {
259-
SdkVersion: `graph-js-${PACKAGE_VERSION}`,
260-
};
261257
const optionsHeaders: HeadersInit = { ...options.headers };
262258
if (this.config.fetchOptions !== undefined) {
263259
const fetchOptions: FetchOptions = { ...this.config.fetchOptions };
@@ -267,7 +263,6 @@ export class GraphRequest {
267263
}
268264
}
269265
Object.assign(options, this._options);
270-
Object.assign(optionsHeaders, defaultHeaders);
271266
if (options.headers !== undefined) {
272267
Object.assign(optionsHeaders, options.headers);
273268
}

src/HTTPClientFactory.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { RedirectHandlerOptions } from "./middleware/options/RedirectHandlerOpti
1818
import { RetryHandlerOptions } from "./middleware/options/RetryHandlerOptions";
1919
import { RedirectHandler } from "./middleware/RedirectHandler";
2020
import { RetryHandler } from "./middleware/RetryHandler";
21+
import { TelemetryHandler } from "./middleware/TelemetryHandler";
2122

2223
/**
2324
* @private
@@ -39,19 +40,28 @@ export class HTTPClientFactory {
3940
* Creates HTTPClient with default middleware chain
4041
* @param {AuthenticationProvider} authProvider - The authentication provider instance
4142
* @returns A HTTPClient instance
43+
*
44+
* NOTE: These are the things that we need to remember while doing modifications in the below default pipeline.
45+
* * HTTPMessageHander should be the last one in the middleware pipeline, because this makes the actual network call of the request
46+
* * TelemetryHandler should be the one prior to the last middleware in the chain, because this is the one which actually collects and appends the usage flag and placing this handler * before making the actual network call ensures that the usage of all features are recorded in the flag.
47+
* * The best place for AuthenticationHandler is in the starting of the pipeline, because every other handler might have to work for multiple times for a request but the auth token for
48+
* them will remain same. For example, Retry and Redirect handlers might be working multiple times for a request based on the response but their auth token would remain same.
4249
*/
4350
public static createWithAuthenticationProvider(authProvider: AuthenticationProvider): HTTPClient {
4451
const authenticationHandler = new AuthenticationHandler(authProvider);
4552
const retryHandler = new RetryHandler(new RetryHandlerOptions());
53+
const telemetryHandler = new TelemetryHandler();
4654
const httpMessageHandler = new HTTPMessageHandler();
55+
4756
authenticationHandler.setNext(retryHandler);
4857
if (isNodeEnvironment()) {
4958
const redirectHandler = new RedirectHandler(new RedirectHandlerOptions());
5059
retryHandler.setNext(redirectHandler);
51-
redirectHandler.setNext(httpMessageHandler);
60+
redirectHandler.setNext(telemetryHandler);
5261
} else {
53-
retryHandler.setNext(httpMessageHandler);
62+
retryHandler.setNext(telemetryHandler);
5463
}
64+
telemetryHandler.setNext(httpMessageHandler);
5565
return HTTPClientFactory.createWithMiddleware(authenticationHandler);
5666
}
5767

src/browser/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@ export * from "../middleware/AuthenticationHandler";
1212
export * from "../middleware/HTTPMessageHandler";
1313
export * from "../middleware/IMiddleware";
1414
export * from "../middleware/RetryHandler";
15+
export * from "../middleware/TelemetryHandler";
1516

1617
export * from "../middleware/options/AuthenticationHandlerOptions";
1718
export * from "../middleware/options/IMiddlewareOptions";
1819
export * from "../middleware/options/RetryHandlerOptions";
20+
export * from "../middleware/options/TelemetryHandlerOptions";
1921

2022
export * from "../tasks/OneDriveLargeFileUploadTask";
2123
export * from "../tasks/PageIterator";

src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,14 @@ export * from "./middleware/AuthenticationHandler";
1414
export * from "./middleware/HTTPMessageHandler";
1515
export * from "./middleware/IMiddleware";
1616
export * from "./middleware/RetryHandler";
17+
export * from "./middleware/RedirectHandler";
18+
export * from "./middleware/TelemetryHandler";
1719

1820
export * from "./middleware/options/AuthenticationHandlerOptions";
1921
export * from "./middleware/options/IMiddlewareOptions";
2022
export * from "./middleware/options/RetryHandlerOptions";
23+
export * from "./middleware/options/RedirectHandlerOptions";
24+
export * from "./middleware/options/TelemetryHandlerOptions";
2125

2226
export * from "./tasks/OneDriveLargeFileUploadTask";
2327
export * from "./tasks/PageIterator";

src/middleware/AuthenticationHandler.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { Middleware } from "./IMiddleware";
1717
import { MiddlewareControl } from "./MiddlewareControl";
1818
import { setRequestHeader } from "./MiddlewareUtil";
1919
import { AuthenticationHandlerOptions } from "./options/AuthenticationHandlerOptions";
20+
import { FeatureUsageFlag, TelemetryHandlerOptions } from "./options/TelemetryHandlerOptions";
2021

2122
/**
2223
* @class
@@ -77,6 +78,7 @@ export class AuthenticationHandler implements Middleware {
7778
const token: string = await authenticationProvider.getAccessToken(authenticationProviderOptions);
7879
const bearerKey: string = `Bearer ${token}`;
7980
setRequestHeader(context.request, context.options, AuthenticationHandler.AUTHORIZATION_HEADER, bearerKey);
81+
TelemetryHandlerOptions.updateFeatureUsageFlag(context, FeatureUsageFlag.AUTHENTICATION_HANDLER_ENABLED);
8082
return await this.nextMiddleware.execute(context);
8183
} catch (error) {
8284
throw error;

0 commit comments

Comments
 (0)