Repository: microsoft/ApplicationInsights-node.js
Pull request #558 (2019-10-18 20:08:26)

adding trackAvailability api support
This PR addresses this issue - #429 

I have validated the changes by calling the api & saw the events under availability events in app insights.

I havn't added this support for QuickPulseEnvelopFactory, since I don't see why we will be using this for live metrics.





devaradhanm: @markwolff , can you merge this PR? also, when can we expect this change to be available?
markwolff: > @markwolff , can you merge this PR? also, when can we expect this change to be available?

I'm finishing up on some new patching [here](https://github.com/microsoft/node-diagnostic-channel/pull/64), and it would require a minor version bump on this package, so I'd like to tag it along with your PR. Rough ETA is next week at the latest, but probably this week.

Patch:
diff --git a/Declarations/Constants.ts b/Declarations/Constants.ts
index d0151a2..a89ef81 100644
--- a/Declarations/Constants.ts
+++ b/Declarations/Constants.ts
@@ -58,8 +58,15 @@ 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";
-export type QuickPulseType = "EventTelemetryDocument" | "ExceptionTelemetryDocument" | "TraceTelemetryDocument" | "MetricTelemetryDocument" | "RequestTelemetryDocument" | "DependencyTelemetryDocument";
+export type QuickPulseDocumentType = "Event" | "Exception" | "Trace" | "Metric" | "Request" | "RemoteDependency" | "Availability";
+export type QuickPulseType =
+    | "EventTelemetryDocument"
+    | "ExceptionTelemetryDocument"
+    | "TraceTelemetryDocument"
+    | "MetricTelemetryDocument"
+    | "RequestTelemetryDocument"
+    | "DependencyTelemetryDocument"
+    | "AvailabilityTelemetryDocument";
 
 export const QuickPulseDocumentType: {[key in Contracts.TelemetryTypeKeys]: QuickPulseDocumentType} = {
     Event: "Event",
@@ -67,7 +74,8 @@ export const QuickPulseDocumentType: {[key in Contracts.TelemetryTypeKeys]: Quic
     Trace: "Trace",
     Metric: "Metric",
     Request: "Request",
-    Dependency: "RemoteDependency"
+    Dependency: "RemoteDependency",
+    Availability: "Availability"
 };
 
 export const QuickPulseType: {[key in Contracts.TelemetryTypeKeys]: QuickPulseType} = {
@@ -76,7 +84,8 @@ export const QuickPulseType: {[key in Contracts.TelemetryTypeKeys]: QuickPulseTy
     Trace: "TraceTelemetryDocument",
     Metric: "MetricTelemetryDocument",
     Request: "RequestTelemetryDocument",
-    Dependency: "DependencyTelemetryDocument"
+    Dependency: "DependencyTelemetryDocument",
+    Availability: "AvailabilityTelemetryDocument"
 };
 
 export const TelemetryTypeStringToQuickPulseType: {[key in Contracts.TelemetryTypeValues]: QuickPulseType} = {
@@ -85,7 +94,8 @@ export const TelemetryTypeStringToQuickPulseType: {[key in Contracts.TelemetryTy
     MessageData: QuickPulseType.Trace,
     MetricData: QuickPulseType.Metric,
     RequestData: QuickPulseType.Request,
-    RemoteDependencyData: QuickPulseType.Dependency
+    RemoteDependencyData: QuickPulseType.Dependency,
+    AvailabilityData: QuickPulseType.Availability
 };
 
 export const TelemetryTypeStringToQuickPulseDocumentType: {[key in Contracts.TelemetryTypeValues]: QuickPulseDocumentType} = {
@@ -94,5 +104,6 @@ export const TelemetryTypeStringToQuickPulseDocumentType: {[key in Contracts.Tel
     MessageData: QuickPulseDocumentType.Trace,
     MetricData: QuickPulseDocumentType.Metric,
     RequestData: QuickPulseDocumentType.Request,
-    RemoteDependencyData: QuickPulseDocumentType.Dependency
+    RemoteDependencyData: QuickPulseDocumentType.Dependency,
+    AvailabilityData: QuickPulseDocumentType.Availability
 };
diff --git a/Declarations/Contracts/TelemetryTypes/AvailabilityTelemetry.ts b/Declarations/Contracts/TelemetryTypes/AvailabilityTelemetry.ts
new file mode 100644
index 0000000..187b453
--- /dev/null
+++ b/Declarations/Contracts/TelemetryTypes/AvailabilityTelemetry.ts
@@ -0,0 +1,42 @@
+import { Telemetry }  from "./Telemetry";
+
+/**
+ * Telemetry type used for availability web test results.
+ */
+export interface AvailabilityTelemetry extends Telemetry {
+
+        /**
+         * Identifier of a test run. Use it to correlate steps of test run and telemetry generated by the service.
+         */
+        id: string;
+
+        /**
+         * Name of the test that these availability results represent.
+         */
+        name: string;
+
+        /**
+         * Request duration in ms
+         */
+        duration: number;
+
+        /**
+         * Success flag.
+         */
+        success: boolean;
+
+        /**
+         * Name of the location where the test was run from.
+         */
+        runLocation: string;
+
+        /**
+         * Diagnostic message for the result.
+         */
+        message: string;
+
+        /**
+         * 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 d80918c..24c4ffd 100644
--- a/Declarations/Contracts/TelemetryTypes/TelemetryType.ts
+++ b/Declarations/Contracts/TelemetryTypes/TelemetryType.ts
@@ -1,5 +1,12 @@
-export type TelemetryTypeKeys = "Event" | "Exception" | "Trace" | "Metric" | "Request" | "Dependency";
-export type TelemetryTypeValues = "EventData" | "ExceptionData" | "MessageData" | "MetricData" | "RequestData" | "RemoteDependencyData";
+export type TelemetryTypeKeys = "Event" | "Exception" | "Trace" | "Metric" | "Request" | "Dependency" | "Availability";
+export type TelemetryTypeValues =
+    | "EventData"
+    | "ExceptionData"
+    | "MessageData"
+    | "MetricData"
+    | "RequestData"
+    | "RemoteDependencyData"
+    | "AvailabilityData";
 
 /**
  * Converts the user-friendly enumeration TelemetryType to the underlying schema baseType value
@@ -19,6 +26,8 @@ export function telemetryTypeToBaseType(type: TelemetryType): TelemetryTypeValue
             return "RequestData";
         case TelemetryType.Dependency:
             return "RemoteDependencyData";
+        case TelemetryType.Availability:
+            return "AvailabilityData";
     }
     return undefined;
 }
@@ -41,6 +50,8 @@ export function baseTypeToTelemetryType(baseType: TelemetryTypeValues): Telemetr
             return TelemetryType.Request;
         case "RemoteDependencyData":
             return TelemetryType.Dependency;
+        case "AvailabilityData":
+            return TelemetryType.Availability;
     }
     return undefined;
 }
@@ -51,7 +62,8 @@ export const TelemetryTypeString: {[key: string]: TelemetryTypeValues} = {
     Trace: "MessageData",
     Metric: "MetricData",
     Request: "RequestData",
-    Dependency: "RemoteDependencyData"
+    Dependency: "RemoteDependencyData",
+    Availability: "AvailabilityData"
 }
 
 /**
@@ -63,7 +75,8 @@ export enum TelemetryType {
     Trace,
     Metric,
     Request,
-    Dependency
+    Dependency,
+    Availability
 }
 
 export interface Identified {
diff --git a/Declarations/Contracts/TelemetryTypes/index.ts b/Declarations/Contracts/TelemetryTypes/index.ts
index b0fe24d..a2493e8 100644
--- a/Declarations/Contracts/TelemetryTypes/index.ts
+++ b/Declarations/Contracts/TelemetryTypes/index.ts
@@ -8,5 +8,6 @@ export * from "./Telemetry";
 
 export * from "./NodeHttpDependencyTelemetry";
 export * from "./NodeHttpRequestTelemetry";
+export * from "./AvailabilityTelemetry";
 
 export * from "./TelemetryType";
\ No newline at end of file
diff --git a/Library/EnvelopeFactory.ts b/Library/EnvelopeFactory.ts
index 04f8358..a38fa79 100644
--- a/Library/EnvelopeFactory.ts
+++ b/Library/EnvelopeFactory.ts
@@ -48,6 +48,9 @@ class EnvelopeFactory {
             case Contracts.TelemetryType.Metric:
                 data = EnvelopeFactory.createMetricData(<Contracts.MetricTelemetry>telemetry);
                 break;
+            case Contracts.TelemetryType.Availability:
+                data = EnvelopeFactory.createAvailabilityData(<Contracts.AvailabilityTelemetry>telemetry);
+                break;
         }
 
         if (commonProperties && Contracts.domainSupportsProperties(data.baseData)) { // Do instanceof check. TS will automatically cast and allow the properties property
@@ -216,6 +219,31 @@ class EnvelopeFactory {
         return data;
     }
 
+    private static createAvailabilityData(
+        telemetry: Contracts.AvailabilityTelemetry & Contracts.Identified,
+    ): Contracts.Data<Contracts.AvailabilityData> {
+        let availabilityData = new Contracts.AvailabilityData();
+        
+        if (telemetry.id) {
+            availabilityData.id = telemetry.id;
+        } else {
+            availabilityData.id = Util.w3cTraceId();
+        }
+        availabilityData.name = telemetry.name;
+        availabilityData.duration = Util.msToTimeSpan(telemetry.duration);
+        availabilityData.success = telemetry.success;
+        availabilityData.runLocation = telemetry.runLocation;
+        availabilityData.message = telemetry.message;
+        availabilityData.measurements = telemetry.measurements;
+        availabilityData.properties = telemetry.properties;
+
+        let data = new Contracts.Data<Contracts.AvailabilityData>();
+        data.baseType = Contracts.telemetryTypeToBaseType(Contracts.TelemetryType.Availability);
+        data.baseData = availabilityData;
+
+        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 68faa63..054880e 100644
--- a/Library/TelemetryClient.ts
+++ b/Library/TelemetryClient.ts
@@ -41,6 +41,14 @@ class TelemetryClient {
         this.channel = new Channel(() => config.disableAppInsights, () => config.maxBatchSize, () => config.maxBatchIntervalMs, sender);
     }
 
+    /**
+     * Log information about availability of an application
+     * @param telemetry      Object encapsulating tracking options
+     */
+    public trackAvailability(telemetry: Contracts.AvailabilityTelemetry): void {
+        this.track(telemetry, Contracts.TelemetryType.Availability);
+    }
+
     /**
      * Log a trace message
      * @param telemetry      Object encapsulating tracking options


Test patch:
diff --git a/Tests/EndToEnd.tests.ts b/Tests/EndToEnd.tests.ts
index d5e106b..ba8c672 100644
--- a/Tests/EndToEnd.tests.ts
+++ b/Tests/EndToEnd.tests.ts
@@ -166,11 +166,18 @@ describe("EndToEnd", () => {
         });
 
         it("should send telemetry", (done) => {
+            const expectedTelemetryData: AppInsights.Contracts.AvailabilityTelemetry =  { 
+                duration: 100, id: "id1", message: "message1",success : true, name: "name1", runLocation: "east us" 
+            };
+
             var client = new AppInsights.TelemetryClient("iKey");
+            
             client.trackEvent({ name: "test event" });
             client.trackException({ exception: new Error("test error") });
             client.trackMetric({ name: "test metric", value: 3 });
             client.trackTrace({ message: "test trace" });
+            client.trackAvailability(expectedTelemetryData);
+            
             client.flush({
                 callback: (response) => {
                     assert.ok(response, "response should not be empty");
diff --git a/Tests/Library/Client.tests.ts b/Tests/Library/Client.tests.ts
index d5a9c51..e86f6f1 100644
--- a/Tests/Library/Client.tests.ts
+++ b/Tests/Library/Client.tests.ts
@@ -135,6 +135,30 @@ describe("Library/TelemetryClient", () => {
         });
     });
 
+    
+    describe("#trackAvailability()", () => {
+        it("should track availability with correct data", () => {
+            trackStub.reset();
+            const expectedTelemetryData: Contracts.AvailabilityTelemetry =  { 
+                duration: 100, id: "id1", message: "message1",success : true, name: "name1", runLocation: "east us" 
+            };
+
+            client.trackAvailability(expectedTelemetryData);
+
+            assert.ok(trackStub.calledOnce);
+
+            const availabilityTelemetry = <Contracts.AvailabilityTelemetry>trackStub.firstCall.args[0];
+
+            assert.equal(availabilityTelemetry.message, expectedTelemetryData.message);
+            assert.equal(availabilityTelemetry.name, expectedTelemetryData.name);
+            assert.equal(availabilityTelemetry.runLocation, expectedTelemetryData.runLocation);
+        });
+
+        it("should not crash with invalid input", () => {
+            invalidInputHelper("trackAvailability");
+        });
+    });
+
     describe("#trackException()", () => {
         it("should track Exception with correct data - Error only", () => {
             trackStub.reset();
diff --git a/Tests/Library/EnvelopeFactoryTests.ts b/Tests/Library/EnvelopeFactoryTests.ts
index b0c9932..865323c 100644
--- a/Tests/Library/EnvelopeFactoryTests.ts
+++ b/Tests/Library/EnvelopeFactoryTests.ts
@@ -5,6 +5,7 @@ import http = require("http");
 import EnvelopeFactory = require("../../Library/EnvelopeFactory");
 import Contracts = require("../../Declarations/Contracts");
 import Client = require("../../Library/TelemetryClient");
+import Util = require("../../Library/Util");
 
 describe("Library/EnvelopeFactory", () => {
         
@@ -111,4 +112,46 @@ describe("Library/EnvelopeFactory", () => {
             assert.deepEqual(actual, expected);
         });
     });
+
+    describe("AvailabilityData", () => {
+        let availabilityTelemetry: Contracts.AvailabilityTelemetry;
+        beforeEach(() => {
+            availabilityTelemetry  = {
+                success : true,
+                duration: 100,
+                measurements: { "m1" : 1},
+                runLocation: "west us",
+                properties: {
+                    "prop1" : "prop1 value"
+                },
+                message: "availability test message",
+                name: "availability test name",
+                id: "availability test id",
+            };
+        });
+
+        it("creates when id not set", () => {
+            availabilityTelemetry.id = undefined;
+
+            var envelope = EnvelopeFactory.createEnvelope(availabilityTelemetry, Contracts.TelemetryType.Availability);
+            var data =  <Contracts.Data<Contracts.AvailabilityData>>envelope.data;
+            assert.ok(data.baseData.id != null);
+        });
+
+        it("creates data with given content", () => {
+            var envelope = EnvelopeFactory.createEnvelope(availabilityTelemetry, Contracts.TelemetryType.Availability);
+            var data =  <Contracts.Data<Contracts.AvailabilityData>>envelope.data;
+            
+            assert.deepEqual(data.baseType, "AvailabilityData");
+            
+            assert.deepEqual(data.baseData.id, availabilityTelemetry.id);
+            assert.deepEqual(data.baseData.measurements, availabilityTelemetry.measurements);
+            assert.deepEqual(data.baseData.success, availabilityTelemetry.success);
+            assert.deepEqual(data.baseData.runLocation, availabilityTelemetry.runLocation);
+            assert.deepEqual(data.baseData.name, availabilityTelemetry.name);
+            assert.deepEqual(data.baseData.properties, availabilityTelemetry.properties);
+            assert.deepEqual(data.baseData.duration, Util.msToTimeSpan(availabilityTelemetry.duration));
+
+        });
+    });
 });