Repository: microsoft/ApplicationInsights-node.js
Pull request #616 (2020-04-20 09:49:28)

Added PageView telemetry type and tests
Hi @markwolff - raising a PR to add PageView telemetry as recommended.

As discussed via email, we use this library within Bot Framework and are using PageViews as part of our telemetry capture. Please let me know if anything needs updating.

Once this is accepted, would you be able to share when the next release will be?

Thanks


Patch:
diff --git a/Declarations/Constants.ts b/Declarations/Constants.ts
index a89ef81..d406246 100644
--- a/Declarations/Constants.ts
+++ b/Declarations/Constants.ts
@@ -58,7 +58,7 @@ export const PerformanceToQuickPulseCounter: {[key: string]: QuickPulseCounter}
 
 // Note: Explicitly define these types instead of using enum due to
 // potential 'export enum' issues with typescript < 2.0.
-export type QuickPulseDocumentType = "Event" | "Exception" | "Trace" | "Metric" | "Request" | "RemoteDependency" | "Availability";
+export type QuickPulseDocumentType = "Event" | "Exception" | "Trace" | "Metric" | "Request" | "RemoteDependency" | "Availability" | "PageView";
 export type QuickPulseType =
     | "EventTelemetryDocument"
     | "ExceptionTelemetryDocument"
@@ -66,7 +66,8 @@ export type QuickPulseType =
     | "MetricTelemetryDocument"
     | "RequestTelemetryDocument"
     | "DependencyTelemetryDocument"
-    | "AvailabilityTelemetryDocument";
+    | "AvailabilityTelemetryDocument"
+    | "PageViewTelemetryDocument";
 
 export const QuickPulseDocumentType: {[key in Contracts.TelemetryTypeKeys]: QuickPulseDocumentType} = {
     Event: "Event",
@@ -75,7 +76,8 @@ export const QuickPulseDocumentType: {[key in Contracts.TelemetryTypeKeys]: Quic
     Metric: "Metric",
     Request: "Request",
     Dependency: "RemoteDependency",
-    Availability: "Availability"
+    Availability: "Availability",
+    PageView: "PageView",
 };
 
 export const QuickPulseType: {[key in Contracts.TelemetryTypeKeys]: QuickPulseType} = {
@@ -85,7 +87,8 @@ export const QuickPulseType: {[key in Contracts.TelemetryTypeKeys]: QuickPulseTy
     Metric: "MetricTelemetryDocument",
     Request: "RequestTelemetryDocument",
     Dependency: "DependencyTelemetryDocument",
-    Availability: "AvailabilityTelemetryDocument"
+    Availability: "AvailabilityTelemetryDocument",
+    PageView: "PageViewTelemetryDocument",
 };
 
 export const TelemetryTypeStringToQuickPulseType: {[key in Contracts.TelemetryTypeValues]: QuickPulseType} = {
@@ -95,7 +98,8 @@ export const TelemetryTypeStringToQuickPulseType: {[key in Contracts.TelemetryTy
     MetricData: QuickPulseType.Metric,
     RequestData: QuickPulseType.Request,
     RemoteDependencyData: QuickPulseType.Dependency,
-    AvailabilityData: QuickPulseType.Availability
+    AvailabilityData: QuickPulseType.Availability,
+    PageViewData: QuickPulseType.PageView
 };
 
 export const TelemetryTypeStringToQuickPulseDocumentType: {[key in Contracts.TelemetryTypeValues]: QuickPulseDocumentType} = {
@@ -105,5 +109,6 @@ export const TelemetryTypeStringToQuickPulseDocumentType: {[key in Contracts.Tel
     MetricData: QuickPulseDocumentType.Metric,
     RequestData: QuickPulseDocumentType.Request,
     RemoteDependencyData: QuickPulseDocumentType.Dependency,
-    AvailabilityData: QuickPulseDocumentType.Availability
+    AvailabilityData: QuickPulseDocumentType.Availability,
+    PageViewData: QuickPulseDocumentType.PageView
 };
diff --git a/Declarations/Contracts/TelemetryTypes/PageViewTelemetry.ts b/Declarations/Contracts/TelemetryTypes/PageViewTelemetry.ts
new file mode 100644
index 0000000..af2126e
--- /dev/null
+++ b/Declarations/Contracts/TelemetryTypes/PageViewTelemetry.ts
@@ -0,0 +1,27 @@
+import { Telemetry }  from "./Telemetry";
+
+/**
+ * Telemetry type used for availability web test results.
+ */
+export interface PageViewTelemetry extends Telemetry {
+
+        /**
+         * Name of the test that these availability results represent.
+         */
+        name?: string;
+
+        /**
+         * URL of the page to track.
+         */
+        url?: string;
+
+        /**
+         * Request duration in ms
+         */
+        duration?: number;
+
+        /**
+         * Metrics associated with this event, displayed in Metrics Explorer on the portal.
+         */
+        measurements?: { [key: string]: number; };
+}
\ No newline at end of file
diff --git a/Declarations/Contracts/TelemetryTypes/TelemetryType.ts b/Declarations/Contracts/TelemetryTypes/TelemetryType.ts
index 24c4ffd..a473c90 100644
--- a/Declarations/Contracts/TelemetryTypes/TelemetryType.ts
+++ b/Declarations/Contracts/TelemetryTypes/TelemetryType.ts
@@ -1,4 +1,4 @@
-export type TelemetryTypeKeys = "Event" | "Exception" | "Trace" | "Metric" | "Request" | "Dependency" | "Availability";
+export type TelemetryTypeKeys = "Event" | "Exception" | "Trace" | "Metric" | "Request" | "Dependency" | "Availability" | "PageView";
 export type TelemetryTypeValues =
     | "EventData"
     | "ExceptionData"
@@ -6,7 +6,8 @@ export type TelemetryTypeValues =
     | "MetricData"
     | "RequestData"
     | "RemoteDependencyData"
-    | "AvailabilityData";
+    | "AvailabilityData"
+    | "PageViewData";
 
 /**
  * Converts the user-friendly enumeration TelemetryType to the underlying schema baseType value
@@ -28,6 +29,8 @@ export function telemetryTypeToBaseType(type: TelemetryType): TelemetryTypeValue
             return "RemoteDependencyData";
         case TelemetryType.Availability:
             return "AvailabilityData";
+        case TelemetryType.PageView:
+            return "PageViewData";
     }
     return undefined;
 }
@@ -52,6 +55,8 @@ export function baseTypeToTelemetryType(baseType: TelemetryTypeValues): Telemetr
             return TelemetryType.Dependency;
         case "AvailabilityData":
             return TelemetryType.Availability;
+        case "PageViewData":
+            return TelemetryType.PageView;
     }
     return undefined;
 }
@@ -63,7 +68,8 @@ export const TelemetryTypeString: {[key: string]: TelemetryTypeValues} = {
     Metric: "MetricData",
     Request: "RequestData",
     Dependency: "RemoteDependencyData",
-    Availability: "AvailabilityData"
+    Availability: "AvailabilityData",
+    PageView: "PageViewData",
 }
 
 /**
@@ -76,7 +82,8 @@ export enum TelemetryType {
     Metric,
     Request,
     Dependency,
-    Availability
+    Availability,
+    PageView
 }
 
 export interface Identified {
diff --git a/Declarations/Contracts/TelemetryTypes/index.ts b/Declarations/Contracts/TelemetryTypes/index.ts
index a2493e8..c7dd2ed 100644
--- a/Declarations/Contracts/TelemetryTypes/index.ts
+++ b/Declarations/Contracts/TelemetryTypes/index.ts
@@ -9,5 +9,6 @@ export * from "./Telemetry";
 export * from "./NodeHttpDependencyTelemetry";
 export * from "./NodeHttpRequestTelemetry";
 export * from "./AvailabilityTelemetry";
+export * from "./PageViewTelemetry";
 
 export * from "./TelemetryType";
\ No newline at end of file
diff --git a/Library/EnvelopeFactory.ts b/Library/EnvelopeFactory.ts
index 3ee5924..7cd499f 100644
--- a/Library/EnvelopeFactory.ts
+++ b/Library/EnvelopeFactory.ts
@@ -51,6 +51,9 @@ class EnvelopeFactory {
             case Contracts.TelemetryType.Availability:
                 data = EnvelopeFactory.createAvailabilityData(<Contracts.AvailabilityTelemetry>telemetry);
                 break;
+            case Contracts.TelemetryType.PageView:
+                data = EnvelopeFactory.createPageViewData(<Contracts.PageViewTelemetry>telemetry);
+                break;
         }
 
         if (commonProperties && Contracts.domainSupportsProperties(data.baseData)) { // Do instanceof check. TS will automatically cast and allow the properties property
@@ -246,6 +249,24 @@ class EnvelopeFactory {
         return data;
     }
 
+    private static createPageViewData(
+        telemetry: Contracts.PageViewTelemetry & Contracts.Identified,
+    ): Contracts.Data<Contracts.PageViewData> {
+        let pageViewData = new Contracts.PageViewData();
+
+        pageViewData.name = telemetry.name;
+        pageViewData.duration = Util.msToTimeSpan(telemetry.duration);
+        pageViewData.url = telemetry.url;
+        pageViewData.measurements = telemetry.measurements;
+        pageViewData.properties = telemetry.properties;
+
+        let data = new Contracts.Data<Contracts.PageViewData>();
+        data.baseType = Contracts.telemetryTypeToBaseType(Contracts.TelemetryType.PageView);
+        data.baseData = pageViewData;
+
+        return data;
+    }
+
     private static getTags(context: Context, tagOverrides?: { [key: string]: string; }) {
         var correlationContext = CorrelationContextManager.getCurrentContext();
 
diff --git a/Library/TelemetryClient.ts b/Library/TelemetryClient.ts
index 054880e..214297f 100644
--- a/Library/TelemetryClient.ts
+++ b/Library/TelemetryClient.ts
@@ -49,6 +49,14 @@ class TelemetryClient {
         this.track(telemetry, Contracts.TelemetryType.Availability);
     }
 
+    /**
+     * Log a page view
+     * @param telemetry      Object encapsulating tracking options
+     */
+    public trackPageView(telemetry: Contracts.PageViewTelemetry): void {
+        this.track(telemetry, Contracts.TelemetryType.PageView);
+    }
+
     /**
      * Log a trace message
      * @param telemetry      Object encapsulating tracking options


Test patch:
diff --git a/Tests/Library/Client.tests.ts b/Tests/Library/Client.tests.ts
index e86f6f1..540c4fa 100644
--- a/Tests/Library/Client.tests.ts
+++ b/Tests/Library/Client.tests.ts
@@ -109,6 +109,33 @@ describe("Library/TelemetryClient", () => {
         });
     });
 
+    describe("#trackPageView()", () => {
+        it("should track Page View with correct data", () => {
+            trackStub.reset();
+            client.trackPageView({ name: name });
+            client.trackPageView({ name: name, properties, measurements });
+            client.trackPageView({ name: name, url: "https://www.test.com", duration: 100 });
+
+            assert.ok(trackStub.calledThrice);
+
+            var eventTelemetry1 = <Contracts.PageViewTelemetry>trackStub.firstCall.args[0];
+            var eventTelemetry2 = <Contracts.PageViewTelemetry>trackStub.secondCall.args[0];
+            var eventTelemetry3 = <Contracts.PageViewTelemetry>trackStub.thirdCall.args[0];
+
+            assert.equal(eventTelemetry1.name, name);
+            assert.equal(eventTelemetry2.name, name);
+            assert.deepEqual(eventTelemetry2.properties, properties);
+            assert.deepEqual(eventTelemetry2.measurements, measurements);
+            assert.equal(eventTelemetry3.name, name);
+            assert.equal(eventTelemetry3.url, "https://www.test.com");
+            assert.equal(eventTelemetry3.duration, 100);
+        });
+
+        it("should not crash with invalid input", () => {
+            invalidInputHelper("trackPageView");
+        });
+    });
+
     describe("#trackTrace()", () => {
         it("should track Trace with correct data", () => {
             trackStub.reset();
diff --git a/Tests/Library/EnvelopeFactoryTests.ts b/Tests/Library/EnvelopeFactoryTests.ts
index b31ac5a..8d30a98 100644
--- a/Tests/Library/EnvelopeFactoryTests.ts
+++ b/Tests/Library/EnvelopeFactoryTests.ts
@@ -170,4 +170,33 @@ describe("Library/EnvelopeFactory", () => {
 
         });
     });
+
+    describe("PageViewData", () => {
+        let pageViewTelemetry: Contracts.PageViewTelemetry;
+        beforeEach(() => {
+            pageViewTelemetry  = {
+                duration: 100,
+                measurements: { "m1" : 1},
+                properties: {
+                    "prop1" : "prop1 value"
+                },
+                url: "https://www.test.com",
+                name: "availability test name",
+            };
+        });
+
+        it("creates data with given content", () => {
+            var envelope = EnvelopeFactory.createEnvelope(pageViewTelemetry, Contracts.TelemetryType.PageView);
+            var data =  <Contracts.Data<Contracts.PageViewData>>envelope.data;
+
+            assert.deepEqual(data.baseType, "PageViewData");
+
+            assert.deepEqual(data.baseData.url, pageViewTelemetry.url);
+            assert.deepEqual(data.baseData.measurements, pageViewTelemetry.measurements);
+            assert.deepEqual(data.baseData.name, pageViewTelemetry.name);
+            assert.deepEqual(data.baseData.properties, pageViewTelemetry.properties);
+            assert.deepEqual(data.baseData.duration, Util.msToTimeSpan(pageViewTelemetry.duration));
+
+        });
+    });
 });