From d9f40f0182385221304a07c33f48edd94928acd5 Mon Sep 17 00:00:00 2001 From: Nev Wylie <54870357+MSNev@users.noreply.github.com> Date: Thu, 29 May 2025 10:10:23 -0700 Subject: [PATCH] [WIP] Re-Enable SdkStats - (Partial) Rename from StatsBeat to Sdk Stats --- .../Tests/Unit/src/SdkStats.tests.ts | 322 +++++++++++++++ .../Tests/Unit/src/StatsBeat.tests.ts | 322 --------------- .../Tests/Unit/src/aichannel.tests.ts | 4 +- .../src/SendBuffer.ts | 2 +- .../src/Sender.ts | 110 +++--- common/config/rush/npm-shrinkwrap.json | 287 +++++++------- shared/1ds-core-js/src/Utils.ts | 2 +- .../Unit/src/AppInsightsCoreSize.Tests.ts | 8 +- .../Tests/Unit/src/SdkStats.Tests.ts | 366 ++++++++++++++++++ .../Tests/Unit/src/StatsBeat.Tests.ts | 366 ------------------ .../IAppInsightsCore.ts | 34 +- .../IConfiguration.ts | 36 +- .../{IStatsBeat.ts => ISdkStats.ts} | 43 +- .../src/JavaScriptSDK.Interfaces/IStatsMgr.ts | 10 +- .../src/JavaScriptSDK/AppInsightsCore.ts | 165 ++++---- .../{StatsBeat.ts => SdkStats.ts} | 18 +- .../src/applicationinsights-core-js.ts | 10 +- 17 files changed, 1081 insertions(+), 1024 deletions(-) create mode 100644 channels/applicationinsights-channel-js/Tests/Unit/src/SdkStats.tests.ts delete mode 100644 channels/applicationinsights-channel-js/Tests/Unit/src/StatsBeat.tests.ts create mode 100644 shared/AppInsightsCore/Tests/Unit/src/SdkStats.Tests.ts delete mode 100644 shared/AppInsightsCore/Tests/Unit/src/StatsBeat.Tests.ts rename shared/AppInsightsCore/src/JavaScriptSDK.Interfaces/{IStatsBeat.ts => ISdkStats.ts} (80%) rename shared/AppInsightsCore/src/JavaScriptSDK/{StatsBeat.ts => SdkStats.ts} (96%) diff --git a/channels/applicationinsights-channel-js/Tests/Unit/src/SdkStats.tests.ts b/channels/applicationinsights-channel-js/Tests/Unit/src/SdkStats.tests.ts new file mode 100644 index 000000000..5d6fc5412 --- /dev/null +++ b/channels/applicationinsights-channel-js/Tests/Unit/src/SdkStats.tests.ts @@ -0,0 +1,322 @@ +import { AITestClass, Assert, PollingAssert } from "@microsoft/ai-test-framework"; +import { AppInsightsCore, createStatsMgr, eStatsType, FeatureOptInMode, getWindow, IPayloadData, ISdkStatsState, IStatsMgr, ITelemetryItem, IUnloadHook, TransportType } from "@microsoft/applicationinsights-core-js"; +import { Sender } from "../../../src/Sender"; +import { SinonSpy, SinonStub } from "sinon"; +import { ISenderConfig } from "../../../types/applicationinsights-channel-js"; +import { isBeaconApiSupported } from "@microsoft/applicationinsights-common"; + +export class SdkStatsTests extends AITestClass { + private _core: AppInsightsCore; + private _sender: Sender; + private _statsMgr: IStatsMgr; + private _statsMgrUnloadHook: IUnloadHook | null; + private sdkStatsCountSpy: SinonSpy; + private fetchStub: sinon.SinonStub; + private beaconStub: sinon.SinonStub; + private trackSpy: SinonSpy; + + public testInitialize() { + this._core = new AppInsightsCore(); + this._sender = new Sender(); + this._statsMgr = createStatsMgr(); + } + + public testFinishedCleanup() { + if (this._sender && this._sender.isInitialized()) { + this._sender.pause(); + this._sender._buffer.clear(); + this._sender.teardown(); + } + this._sender = null; + this._core = null; + this._statsMgr = null; + if (this._statsMgrUnloadHook) { + this._statsMgrUnloadHook.rm(); + this._statsMgrUnloadHook = null; + } + if (this.sdkStatsCountSpy) { + this.sdkStatsCountSpy.restore(); + } + if (this.fetchStub) { + this.fetchStub.restore(); + } + if (this.beaconStub) { + this.beaconStub.restore(); + } + if (this.trackSpy) { + this.trackSpy.restore(); + } + } + + private initializeCoreAndSender(config: any, instrumentationKey: string) { + const sender = new Sender(); + const core = new AppInsightsCore(); + const coreConfig = { + instrumentationKey, + _sdk: { + stats: { + shrtInt: 900, + endCfg: [ + { + type: 0, + keyMap: [ + { + key: "stats-key1", + match: [ "https://example.endpoint.com" ] + } + ] + } + ] + } + }, + extensionConfig: { [sender.identifier]: config } + }; + + let statsMgr = createStatsMgr(); + // Initialize + let unloadHook = statsMgr.init(this._core, { + feature: "SdkStats", + getCfg: (core, cfg) => { + return cfg?._sdk?.stats; + } + }); + + core.initialize(coreConfig, [sender]); + core.setStatsMgr(statsMgr); + + this.sdkStatsCountSpy = this.sandbox.spy(core.getSdkStats(), "count"); + this.trackSpy = this.sandbox.spy(core, "track"); + + this.onDone(() => { + sender.teardown(); + }); + + return { core, sender, statsMgr, unloadHook }; + } + + private createSenderConfig(transportType: TransportType) { + return { + endpointUrl: "https://test", + emitLineDelimitedJson: false, + maxBatchInterval: 15000, + maxBatchSizeInBytes: 102400, + disableTelemetry: false, + enableSessionStorageBuffer: true, + isRetryDisabled: false, + isBeaconApiDisabled: false, + disableXhr: false, + onunloadDisableFetch: false, + onunloadDisableBeacon: false, + namePrefix: "", + samplingPercentage: 100, + customHeaders: [{ header: "header", value: "val" }], + convertUndefined: "", + eventsLimitInMem: 10000, + transports: [transportType] + }; + } + + private processTelemetryAndFlush(sender: Sender, telemetryItem: ITelemetryItem) { + try { + sender.processTelemetry(telemetryItem, null); + sender.flush(); + } catch (e) { + QUnit.assert.ok(false, "Unexpected error during telemetry processing"); + } + this.clock.tick(900000); // Simulate time passing for sdk stats to be sent + } + + private assertSdkStatsCall(statusCode: number, eventName: string) { + Assert.equal(this.sdkStatsCountSpy.callCount, 1, "SdkStats count should be called once"); + Assert.equal(this.sdkStatsCountSpy.firstCall.args[0], statusCode, `SdkStats count should be called with status ${statusCode}`); + const data = JSON.stringify(this.sdkStatsCountSpy.firstCall.args[1]); + Assert.ok(data.includes("startTime"), "SdkStats count should be called with startTime set"); + const sdkStatsEvent = this.trackSpy.firstCall.args[0]; + Assert.equal(sdkStatsEvent.baseType, "MetricData", "SdkStats event should be of type MetricData"); + Assert.equal(sdkStatsEvent.baseData.name, eventName, `SdkStats event should be of type ${eventName}`); + } + + public registerTests() { + this.testCase({ + name: "SdkStats initializes when stats is true", + test: () => { + const config = { + instrumentationKey: "Test-iKey", + featureOptIn: { + "SdkStats": { + mode: FeatureOptInMode.enable + } + }, + _sdk: { + stats: { + shrtInt: 900, + endCfg: [ + { + type: 0, + keyMap: [ + { + key: "stats-key1", + match: [ "https://example.endpoint.com" ] + } + ] + } + ] + } + }, + }; + + this._core.initialize(config, [this._sender]); + this._statsMgrUnloadHook = this._statsMgr.init(this._core, { + feature: "SdkStats", + getCfg: (core, cfg) => { + return cfg?._sdk?.stats; + } + }); + let sdkStatsState: ISdkStatsState = { + cKey: "Test-iKey", + endpoint: "https://example.endpoint.com", + sdkVer: "1.0.0", + type: eStatsType.SDK + }; + + const sdkStats = this._core.getSdkStats(sdkStatsState); + + QUnit.assert.ok(sdkStats, "SdkStats is initialized"); + QUnit.assert.ok(sdkStats.enabled, "SdkStats is marked as initialized"); + } + }); + + this.testCaseAsync({ + name: "SdkStats increments success count when fetch sender is called once", + useFakeTimers: true, + useFakeServer: true, + stepDelay: 100, + steps: [ + () => { + this.fetchStub = this.sandbox.stub(window, "fetch").callsFake(() => { // only fetch is supported to stub, why? + return Promise.resolve(new Response("{}", { status: 200, statusText: "OK" })); + }); + + const config = this.createSenderConfig(TransportType.Fetch); + const { sender } = this.initializeCoreAndSender(config, "000e0000-e000-0000-a000-000000000000"); + + const telemetryItem: ITelemetryItem = { + name: "fake item", + iKey: "testIkey2;ingestionendpoint=testUrl1", + baseType: "some type", + baseData: {} + }; + + this.processTelemetryAndFlush(sender, telemetryItem); + + } + ].concat(PollingAssert.createPollingAssert(() => { + if (this.sdkStatsCountSpy.called && this.fetchStub.called) { + this.assertSdkStatsCall(200, "Request_Success_Count"); + return true; + } + return false; + }, "Waiting for fetch sender and SdkStats count to be called") as any) + }); + + this.testCaseAsync({ + name: "SdkStats increments throttle count when fetch sender is called with status 439", + useFakeTimers: true, + stepDelay: 100, + steps: [ + () => { + this.fetchStub = this.sandbox.stub(window, "fetch").callsFake(() => { + return Promise.resolve(new Response("{}", { status: 439, statusText: "Too Many Requests" })); + }); + + const config = this.createSenderConfig(TransportType.Fetch); + const { sender } = this.initializeCoreAndSender(config, "000e0000-e000-0000-a000-000000000000"); + + const telemetryItem: ITelemetryItem = { + name: "fake item", + iKey: "testIkey2;ingestionendpoint=testUrl1", + baseType: "some type", + baseData: {} + }; + + this.processTelemetryAndFlush(sender, telemetryItem); + } + ].concat(PollingAssert.createPollingAssert(() => { + if (this.sdkStatsCountSpy.called && this.fetchStub.called) { + this.assertSdkStatsCall(439, "Throttle_Count"); + return true; + } + return false; + }, "Waiting for fetch sender and SdkStats count to be called") as any) + }); + + this.testCaseAsync({ + name: "SdkStats increments success count for beacon sender", + useFakeTimers: true, + stepDelay: 100, + steps: [ + () => { + const config = this.createSenderConfig(TransportType.Beacon); + const { sender } = this.initializeCoreAndSender(config, "000e0000-e000-0000-a000-000000000000"); + + const telemetryItem: ITelemetryItem = { + name: "fake item", + iKey: "testIkey2;ingestionendpoint=testUrl1", + baseType: "some type", + baseData: {} + }; + let sendBeaconCalled = false; + this.hookSendBeacon((url: string) => { + sendBeaconCalled = true; + return true; + }); + QUnit.assert.ok(isBeaconApiSupported(), "Beacon API is supported"); + this.processTelemetryAndFlush(sender, telemetryItem); + } + ].concat(PollingAssert.createPollingAssert(() => { + if (this.sdkStatsCountSpy.called) { + this.assertSdkStatsCall(200, "Request_Success_Count"); + return true; + } + return false; + }, "Waiting for beacon sender and SdkStats count to be called") as any) + }); + + + this.testCaseAsync({ + name: "SdkStats increments success count for xhr sender", + useFakeTimers: true, + useFakeServer: true, + stepDelay: 100, + fakeServerAutoRespond: true, + steps: [ + () => { + let window = getWindow(); + let fakeXMLHttpRequest = (window as any).XMLHttpRequest; // why we do this? + let config = this.createSenderConfig(TransportType.Xhr) && {disableSendBeaconSplit: true}; + const { sender } = this.initializeCoreAndSender(config, "000e0000-e000-0000-a000-000000000000"); + console.log("xhr sender called", this._getXhrRequests().length); + + const telemetryItem: ITelemetryItem = { + name: "fake item", + iKey: "testIkey2;ingestionendpoint=testUrl1", + baseType: "some type", + baseData: {} + }; + this.processTelemetryAndFlush(sender, telemetryItem); + QUnit.assert.equal(1, this._getXhrRequests().length, "xhr sender is called"); + console.log("xhr sender is called", this._getXhrRequests().length); + (window as any).XMLHttpRequest = fakeXMLHttpRequest; + + } + ].concat(PollingAssert.createPollingAssert(() => { + if (this.sdkStatsCountSpy.called) { + this.assertSdkStatsCall(200, "Request_Success_Count"); + console.log("SdkStats count called with success count for xhr sender"); + return true; + } + return false; + }, "Waiting for xhr sender and SdkStats count to be called", 60, 1000) as any) + }); +} +} \ No newline at end of file diff --git a/channels/applicationinsights-channel-js/Tests/Unit/src/StatsBeat.tests.ts b/channels/applicationinsights-channel-js/Tests/Unit/src/StatsBeat.tests.ts deleted file mode 100644 index 71ccf4478..000000000 --- a/channels/applicationinsights-channel-js/Tests/Unit/src/StatsBeat.tests.ts +++ /dev/null @@ -1,322 +0,0 @@ -// import { AITestClass, Assert, PollingAssert } from "@microsoft/ai-test-framework"; -// import { AppInsightsCore, createStatsMgr, eStatsType, FeatureOptInMode, getWindow, IPayloadData, IStatsBeatState, IStatsMgr, ITelemetryItem, IUnloadHook, TransportType } from "@microsoft/applicationinsights-core-js"; -// import { Sender } from "../../../src/Sender"; -// import { SinonSpy, SinonStub } from "sinon"; -// import { ISenderConfig } from "../../../types/applicationinsights-channel-js"; -// import { isBeaconApiSupported } from "@microsoft/applicationinsights-common"; - -// export class StatsbeatTests extends AITestClass { -// private _core: AppInsightsCore; -// private _sender: Sender; -// private _statsMgr: IStatsMgr; -// private _statsMgrUnloadHook: IUnloadHook | null; -// private statsbeatCountSpy: SinonSpy; -// private fetchStub: sinon.SinonStub; -// private beaconStub: sinon.SinonStub; -// private trackSpy: SinonSpy; - -// public testInitialize() { -// this._core = new AppInsightsCore(); -// this._sender = new Sender(); -// this._statsMgr = createStatsMgr(); -// } - -// public testFinishedCleanup() { -// if (this._sender && this._sender.isInitialized()) { -// this._sender.pause(); -// this._sender._buffer.clear(); -// this._sender.teardown(); -// } -// this._sender = null; -// this._core = null; -// this._statsMgr = null; -// if (this._statsMgrUnloadHook) { -// this._statsMgrUnloadHook.rm(); -// this._statsMgrUnloadHook = null; -// } -// if (this.statsbeatCountSpy) { -// this.statsbeatCountSpy.restore(); -// } -// if (this.fetchStub) { -// this.fetchStub.restore(); -// } -// if (this.beaconStub) { -// this.beaconStub.restore(); -// } -// if (this.trackSpy) { -// this.trackSpy.restore(); -// } -// } - -// private initializeCoreAndSender(config: any, instrumentationKey: string) { -// const sender = new Sender(); -// const core = new AppInsightsCore(); -// const coreConfig = { -// instrumentationKey, -// _sdk: { -// stats: { -// shrtInt: 900, -// endCfg: [ -// { -// type: 0, -// keyMap: [ -// { -// key: "stats-key1", -// match: [ "https://example.endpoint.com" ] -// } -// ] -// } -// ] -// } -// }, -// extensionConfig: { [sender.identifier]: config } -// }; - -// let statsMgr = createStatsMgr(); -// // Initialize -// let unloadHook = statsMgr.init(this._core, { -// feature: "StatsBeat", -// getCfg: (core, cfg) => { -// return cfg?._sdk?.stats; -// } -// }); - -// core.initialize(coreConfig, [sender]); -// core.setStatsMgr(statsMgr); - -// this.statsbeatCountSpy = this.sandbox.spy(core.getStatsBeat(), "count"); -// this.trackSpy = this.sandbox.spy(core, "track"); - -// this.onDone(() => { -// sender.teardown(); -// }); - -// return { core, sender, statsMgr, unloadHook }; -// } - -// private createSenderConfig(transportType: TransportType) { -// return { -// endpointUrl: "https://test", -// emitLineDelimitedJson: false, -// maxBatchInterval: 15000, -// maxBatchSizeInBytes: 102400, -// disableTelemetry: false, -// enableSessionStorageBuffer: true, -// isRetryDisabled: false, -// isBeaconApiDisabled: false, -// disableXhr: false, -// onunloadDisableFetch: false, -// onunloadDisableBeacon: false, -// namePrefix: "", -// samplingPercentage: 100, -// customHeaders: [{ header: "header", value: "val" }], -// convertUndefined: "", -// eventsLimitInMem: 10000, -// transports: [transportType] -// }; -// } - -// private processTelemetryAndFlush(sender: Sender, telemetryItem: ITelemetryItem) { -// try { -// sender.processTelemetry(telemetryItem, null); -// sender.flush(); -// } catch (e) { -// QUnit.assert.ok(false, "Unexpected error during telemetry processing"); -// } -// this.clock.tick(900000); // Simulate time passing for statsbeat to be sent -// } - -// private assertStatsbeatCall(statusCode: number, eventName: string) { -// Assert.equal(this.statsbeatCountSpy.callCount, 1, "Statsbeat count should be called once"); -// Assert.equal(this.statsbeatCountSpy.firstCall.args[0], statusCode, `Statsbeat count should be called with status ${statusCode}`); -// const data = JSON.stringify(this.statsbeatCountSpy.firstCall.args[1]); -// Assert.ok(data.includes("startTime"), "Statsbeat count should be called with startTime set"); -// const statsbeatEvent = this.trackSpy.firstCall.args[0]; -// Assert.equal(statsbeatEvent.baseType, "MetricData", "Statsbeat event should be of type MetricData"); -// Assert.equal(statsbeatEvent.baseData.name, eventName, `Statsbeat event should be of type ${eventName}`); -// } - -// public registerTests() { -// this.testCase({ -// name: "Statsbeat initializes when stats is true", -// test: () => { -// const config = { -// instrumentationKey: "Test-iKey", -// featureOptIn: { -// "StatsBeat": { -// mode: FeatureOptInMode.enable -// } -// }, -// _sdk: { -// stats: { -// shrtInt: 900, -// endCfg: [ -// { -// type: 0, -// keyMap: [ -// { -// key: "stats-key1", -// match: [ "https://example.endpoint.com" ] -// } -// ] -// } -// ] -// } -// }, -// }; - -// this._core.initialize(config, [this._sender]); -// this._statsMgrUnloadHook = this._statsMgr.init(this._core, { -// feature: "StatsBeat", -// getCfg: (core, cfg) => { -// return cfg?._sdk?.stats; -// } -// }); -// let statsBeatState: IStatsBeatState = { -// cKey: "Test-iKey", -// endpoint: "https://example.endpoint.com", -// sdkVer: "1.0.0", -// type: eStatsType.SDK -// }; - -// const statsbeat = this._core.getStatsBeat(statsBeatState); - -// QUnit.assert.ok(statsbeat, "Statsbeat is initialized"); -// QUnit.assert.ok(statsbeat.enabled, "Statsbeat is marked as initialized"); -// } -// }); - -// this.testCaseAsync({ -// name: "Statsbeat increments success count when fetch sender is called once", -// useFakeTimers: true, -// useFakeServer: true, -// stepDelay: 100, -// steps: [ -// () => { -// this.fetchStub = this.sandbox.stub(window, "fetch").callsFake(() => { // only fetch is supported to stub, why? -// return Promise.resolve(new Response("{}", { status: 200, statusText: "OK" })); -// }); - -// const config = this.createSenderConfig(TransportType.Fetch); -// const { sender } = this.initializeCoreAndSender(config, "000e0000-e000-0000-a000-000000000000"); - -// const telemetryItem: ITelemetryItem = { -// name: "fake item", -// iKey: "testIkey2;ingestionendpoint=testUrl1", -// baseType: "some type", -// baseData: {} -// }; - -// this.processTelemetryAndFlush(sender, telemetryItem); - -// } -// ].concat(PollingAssert.createPollingAssert(() => { -// if (this.statsbeatCountSpy.called && this.fetchStub.called) { -// this.assertStatsbeatCall(200, "Request_Success_Count"); -// return true; -// } -// return false; -// }, "Waiting for fetch sender and Statsbeat count to be called") as any) -// }); - -// this.testCaseAsync({ -// name: "Statsbeat increments throttle count when fetch sender is called with status 439", -// useFakeTimers: true, -// stepDelay: 100, -// steps: [ -// () => { -// this.fetchStub = this.sandbox.stub(window, "fetch").callsFake(() => { -// return Promise.resolve(new Response("{}", { status: 439, statusText: "Too Many Requests" })); -// }); - -// const config = this.createSenderConfig(TransportType.Fetch); -// const { sender } = this.initializeCoreAndSender(config, "000e0000-e000-0000-a000-000000000000"); - -// const telemetryItem: ITelemetryItem = { -// name: "fake item", -// iKey: "testIkey2;ingestionendpoint=testUrl1", -// baseType: "some type", -// baseData: {} -// }; - -// this.processTelemetryAndFlush(sender, telemetryItem); -// } -// ].concat(PollingAssert.createPollingAssert(() => { -// if (this.statsbeatCountSpy.called && this.fetchStub.called) { -// this.assertStatsbeatCall(439, "Throttle_Count"); -// return true; -// } -// return false; -// }, "Waiting for fetch sender and Statsbeat count to be called") as any) -// }); - -// this.testCaseAsync({ -// name: "Statsbeat increments success count for beacon sender", -// useFakeTimers: true, -// stepDelay: 100, -// steps: [ -// () => { -// const config = this.createSenderConfig(TransportType.Beacon); -// const { sender } = this.initializeCoreAndSender(config, "000e0000-e000-0000-a000-000000000000"); - -// const telemetryItem: ITelemetryItem = { -// name: "fake item", -// iKey: "testIkey2;ingestionendpoint=testUrl1", -// baseType: "some type", -// baseData: {} -// }; -// let sendBeaconCalled = false; -// this.hookSendBeacon((url: string) => { -// sendBeaconCalled = true; -// return true; -// }); -// QUnit.assert.ok(isBeaconApiSupported(), "Beacon API is supported"); -// this.processTelemetryAndFlush(sender, telemetryItem); -// } -// ].concat(PollingAssert.createPollingAssert(() => { -// if (this.statsbeatCountSpy.called) { -// this.assertStatsbeatCall(200, "Request_Success_Count"); -// return true; -// } -// return false; -// }, "Waiting for beacon sender and Statsbeat count to be called") as any) -// }); - - -// this.testCaseAsync({ -// name: "Statsbeat increments success count for xhr sender", -// useFakeTimers: true, -// useFakeServer: true, -// stepDelay: 100, -// fakeServerAutoRespond: true, -// steps: [ -// () => { -// let window = getWindow(); -// let fakeXMLHttpRequest = (window as any).XMLHttpRequest; // why we do this? -// let config = this.createSenderConfig(TransportType.Xhr) && {disableSendBeaconSplit: true}; -// const { sender } = this.initializeCoreAndSender(config, "000e0000-e000-0000-a000-000000000000"); -// console.log("xhr sender called", this._getXhrRequests().length); - -// const telemetryItem: ITelemetryItem = { -// name: "fake item", -// iKey: "testIkey2;ingestionendpoint=testUrl1", -// baseType: "some type", -// baseData: {} -// }; -// this.processTelemetryAndFlush(sender, telemetryItem); -// QUnit.assert.equal(1, this._getXhrRequests().length, "xhr sender is called"); -// console.log("xhr sender is called", this._getXhrRequests().length); -// (window as any).XMLHttpRequest = fakeXMLHttpRequest; - -// } -// ].concat(PollingAssert.createPollingAssert(() => { -// if (this.statsbeatCountSpy.called) { -// this.assertStatsbeatCall(200, "Request_Success_Count"); -// console.log("Statsbeat count called with success count for xhr sender"); -// return true; -// } -// return false; -// }, "Waiting for xhr sender and Statsbeat count to be called", 60, 1000) as any) -// }); -// } -// } \ No newline at end of file diff --git a/channels/applicationinsights-channel-js/Tests/Unit/src/aichannel.tests.ts b/channels/applicationinsights-channel-js/Tests/Unit/src/aichannel.tests.ts index 477265d46..4849aec02 100644 --- a/channels/applicationinsights-channel-js/Tests/Unit/src/aichannel.tests.ts +++ b/channels/applicationinsights-channel-js/Tests/Unit/src/aichannel.tests.ts @@ -1,11 +1,11 @@ import { SenderTests } from "./Sender.tests"; import { SampleTests } from "./Sample.tests"; import { GlobalTestHooks } from "./GlobalTestHooks.Test"; -// import { StatsbeatTests } from "./StatsBeat.tests"; +import { SdkStatsTests } from "./SdkStats.tests"; export function runTests() { new GlobalTestHooks().registerTests(); new SenderTests().registerTests(); new SampleTests().registerTests(); - // new StatsbeatTests().registerTests(); + new SdkStatsTests().registerTests(); } \ No newline at end of file diff --git a/channels/applicationinsights-channel-js/src/SendBuffer.ts b/channels/applicationinsights-channel-js/src/SendBuffer.ts index 239425269..bcbbf0f81 100644 --- a/channels/applicationinsights-channel-js/src/SendBuffer.ts +++ b/channels/applicationinsights-channel-js/src/SendBuffer.ts @@ -103,7 +103,7 @@ abstract class BaseSendBuffer { if (!isNullOrUndefined(_maxRetryCnt)) { if (payload.cnt > _maxRetryCnt) { // TODO: add log here on dropping payloads - // will log statsbeat exception later here + // will log sdk stats exception later here return; } diff --git a/channels/applicationinsights-channel-js/src/Sender.ts b/channels/applicationinsights-channel-js/src/Sender.ts index 5722b8905..c88d0679b 100644 --- a/channels/applicationinsights-channel-js/src/Sender.ts +++ b/channels/applicationinsights-channel-js/src/Sender.ts @@ -7,12 +7,12 @@ import { import { ActiveStatus, BaseTelemetryPlugin, IAppInsightsCore, IBackendResponse, IChannelControls, IConfigDefaults, IConfiguration, IDiagnosticLogger, IInternalOfflineSupport, INotificationManager, IPayloadData, IPlugin, IProcessTelemetryContext, - IProcessTelemetryUnloadContext, ITelemetryItem, ITelemetryPluginChain, ITelemetryUnloadState, IXDomainRequest, IXHROverride, - OnCompleteCallback, SendPOSTFunction, SendRequestReason, SenderPostManager, TransportType, _ISendPostMgrConfig, _ISenderOnComplete, - _eInternalMessageId, _throwInternal, _warnToConsole, arrForEach, cfgDfBoolean, cfgDfValidate, createProcessTelemetryContext, - createUniqueNamespace, dateNow, dumpObj, eLoggingSeverity, formatErrorMessageXdr, formatErrorMessageXhr, getExceptionName, getIEVersion, - isArray, isBeaconsSupported, isFeatureEnabled, isFetchSupported, isNullOrUndefined, mergeEvtNamespace, objExtend, onConfigChange, - parseResponse, prependTransports, runTargetUnload + IProcessTelemetryUnloadContext, ISdkStatsState as ISdkStatsState, IStatsEventData, ITelemetryItem, ITelemetryPluginChain, + ITelemetryUnloadState, IXDomainRequest, IXHROverride, OnCompleteCallback, SendPOSTFunction, SendRequestReason, SenderPostManager, + TransportType, _ISendPostMgrConfig, _ISenderOnComplete, _eInternalMessageId, _throwInternal, _warnToConsole, arrForEach, cfgDfBoolean, + cfgDfValidate, createProcessTelemetryContext, createUniqueNamespace, dateNow, dumpObj, eLoggingSeverity, eStatsType, + formatErrorMessageXdr, formatErrorMessageXhr, getExceptionName, getIEVersion, isArray, isBeaconsSupported, isFeatureEnabled, + isFetchSupported, isNullOrUndefined, mergeEvtNamespace, objExtend, onConfigChange, parseResponse, prependTransports, runTargetUnload } from "@microsoft/applicationinsights-core-js"; import { IPromise, createPromise, doAwait, doAwaitResponse } from "@nevware21/ts-async"; import { @@ -36,7 +36,7 @@ const FetchSyncRequestSizeLimitBytes = 65000; // approx 64kb (the current Edge, interface IInternalPayloadData extends IPayloadData { oriPayload: IInternalStorageItem[]; retryCnt?: number; - // statsBeatData?: IStatsEventData; + sdkStatsData?: IStatsEventData; } @@ -673,20 +673,20 @@ export class Sender extends BaseTelemetryPlugin implements IChannelControls { } - // function _getStatsBeat() { - // let statsBeatConfig: IStatsBeatState = { - // cKey: _self._senderConfig.instrumentationKey, - // endpoint: _endpointUrl, - // sdkVer: EnvelopeCreator.Version, - // type: eStatsType.SDK - // }; + function _getSdkStats() { + let sdkStatsConfig: ISdkStatsState = { + cKey: _self._senderConfig.instrumentationKey, + endpoint: _endpointUrl, + sdkVer: EnvelopeCreator.Version, + type: eStatsType.SDK + }; - // let core = _self.core; + let core = _self.core; - // // During page unload the core may have been cleared and some async events may not have been sent yet - // // resulting in the core being null. In this case we don't want to create a statsbeat instance - // return core ? core.getStatsBeat(statsBeatConfig) : null; - // } + // During page unload the core may have been cleared and some async events may not have been sent yet + // resulting in the core being null. In this case we don't want to create a sdkStats instance + return core ? core.getSdkStats(sdkStatsConfig) : null; + } function _xdrOnLoad (xdr: IXDomainRequest, payload: IInternalStorageItem[]) { const responseText = _getResponseText(xdr); @@ -714,23 +714,23 @@ export class Sender extends BaseTelemetryPlugin implements IChannelControls { if (!payloadArr) { return; } - //const responseText = _getResponseText(xdr); - // let statsbeat = _getStatsBeat(); - // if (statsbeat) { - // if (xdr && (responseText + "" === "200" || responseText === "")) { - // _consecutiveErrors = 0; - // statsbeat.count(200, payload, _endpointUrl); - // } else { - // const results = parseResponse(responseText); + const responseText = _getResponseText(xdr); + let sdkStats = _getSdkStats(); + if (sdkStats) { + if (xdr && (responseText + "" === "200" || responseText === "")) { + _consecutiveErrors = 0; + sdkStats.count(200, payload, _endpointUrl); + } else { + const results = parseResponse(responseText); - // if (results && results.itemsReceived && results.itemsReceived > results.itemsAccepted - // && !_isRetryDisabled) { - // statsbeat.count(206, payload, _endpointUrl); - // } else { - // statsbeat.count(499, payload, _endpointUrl); - // } - // } - // } + if (results && results.itemsReceived && results.itemsReceived > results.itemsAccepted + && !_isRetryDisabled) { + sdkStats.count(206, payload, _endpointUrl); + } else { + sdkStats.count(499, payload, _endpointUrl); + } + } + } return _xdrOnLoad(xdr, payloadArr); @@ -740,10 +740,10 @@ export class Sender extends BaseTelemetryPlugin implements IChannelControls { if (!payloadArr) { return; } - // let statsbeat = _getStatsBeat(); - // if (statsbeat) { - // statsbeat.count(response.status, payload, _endpointUrl); - // } + let sdkStats = _getSdkStats(); + if (sdkStats) { + sdkStats.count(response.status, payload, _endpointUrl); + } return _checkResponsStatus(response.status, payloadArr, response.url, payloadArr.length, response.statusText, resValue || ""); }, xhrOnComplete: (request: XMLHttpRequest, oncomplete: OnCompleteCallback, payload?: IPayloadData) => { @@ -751,18 +751,18 @@ export class Sender extends BaseTelemetryPlugin implements IChannelControls { if (!payloadArr) { return; } - // let statsbeat = _getStatsBeat(); - // if (statsbeat && request.readyState === 4) { - // statsbeat.count(request.status, payload, _endpointUrl); - // } + let sdkStats = _getSdkStats(); + if (sdkStats && request.readyState === 4) { + sdkStats.count(request.status, payload, _endpointUrl); + } return _xhrReadyStateChange(request, payloadArr, payloadArr.length); }, beaconOnRetry: (data: IPayloadData, onComplete: OnCompleteCallback, canSend: (payload: IPayloadData, oncomplete: OnCompleteCallback, sync?: boolean) => boolean) => { - // let statsbeat = _getStatsBeat(); - // if (statsbeat) { - // statsbeat.count(499, data, _endpointUrl); - // } + let sdkStats = _getSdkStats(); + if (sdkStats) { + sdkStats.count(499, data, _endpointUrl); + } return _onBeaconRetry(data, onComplete, canSend); } @@ -1007,17 +1007,17 @@ export class Sender extends BaseTelemetryPlugin implements IChannelControls { function _doSend(sendInterface: IXHROverride, payload: IInternalStorageItem[], isAsync: boolean, markAsSent: boolean = true): void | IPromise { let onComplete = (status: number, headers: {[headerName: string]: string;}, response?: string) => { - // let statsbeat = _getStatsBeat(); - // if (statsbeat) { - // statsbeat.count(status, payloadData, _endpointUrl); - // } + let sdkStats = _getSdkStats(); + if (sdkStats) { + sdkStats.count(status, payloadData, _endpointUrl); + } return _getOnComplete(payload, status, headers, response); } let payloadData = _getPayload(payload); - // if (payloadData) { - // payloadData.statsBeatData = {startTime: dateNow()}; - // } + if (payloadData) { + payloadData.sdkStatsData = {startTime: dateNow()}; + } let sendPostFunc: SendPOSTFunction = sendInterface && sendInterface.sendPOST; if (sendPostFunc && payloadData) { @@ -1360,7 +1360,7 @@ export class Sender extends BaseTelemetryPlugin implements IChannelControls { let core = _self.core; if (core) { // During page unload the core may have been cleared and some async events may not have been sent yet - // resulting in the core being null. In this case we don't want to create a statsbeat instance + // resulting in the core being null. In this case we don't want to create a sdk stats instance if (core[func]) { result = core[func](); diff --git a/common/config/rush/npm-shrinkwrap.json b/common/config/rush/npm-shrinkwrap.json index e6a155fa9..85939e130 100644 --- a/common/config/rush/npm-shrinkwrap.json +++ b/common/config/rush/npm-shrinkwrap.json @@ -181,9 +181,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", - "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", "peer": true, "dependencies": { "eslint-visitor-keys": "^3.4.3" @@ -321,9 +321,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.33.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.33.0.tgz", - "integrity": "sha512-5K1/mKhWaMfreBGJTwval43JJmkip0RmM+3+IuqupeSKNC/Th2Kc7ucaq5ovTSra/OOKB9c58CGSz3QMVbWt0A==", + "version": "9.35.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.35.0.tgz", + "integrity": "sha512-30iXE9whjlILfWobBkNerJo+TXYsgVM5ERQwMcMKCHckHflCmf7wXDAHlARoWnh0s1U72WqlbeyE7iAcCzuCPw==", "peer": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -364,31 +364,18 @@ } }, "node_modules/@humanfs/node": { - "version": "0.16.6", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", - "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", "peer": true, "dependencies": { "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.3.0" + "@humanwhocodes/retry": "^0.4.0" }, "engines": { "node": ">=18.18.0" } }, - "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", - "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", - "peer": true, - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -440,17 +427,17 @@ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==" }, "node_modules/@microsoft/api-extractor": { - "version": "7.52.10", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.52.10.tgz", - "integrity": "sha512-LhKytJM5ZJkbHQVfW/3o747rZUNs/MGg6j/wt/9qwwqEOfvUDTYXXxIBuMgrRXhJ528p41iyz4zjBVHZU74Odg==", + "version": "7.52.12", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.52.12.tgz", + "integrity": "sha512-f1UNgOLCMydwCJ+eZvH0dMxMq3lEEvXsLqlvDOdx136cRITK6xPES2xxgN/0NPCFpQad2HtMHxtPM9oGuqQx6g==", "dependencies": { "@microsoft/api-extractor-model": "7.30.7", "@microsoft/tsdoc": "~0.15.1", "@microsoft/tsdoc-config": "~0.17.1", "@rushstack/node-core-library": "5.14.0", "@rushstack/rig-package": "0.5.3", - "@rushstack/terminal": "0.15.4", - "@rushstack/ts-command-line": "5.0.2", + "@rushstack/terminal": "0.16.0", + "@rushstack/ts-command-line": "5.0.3", "lodash": "~4.17.15", "minimatch": "10.0.3", "resolve": "~1.22.1", @@ -700,17 +687,17 @@ } }, "node_modules/@rollup/plugin-replace/node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "version": "0.30.19", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz", + "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" + "@jridgewell/sourcemap-codec": "^1.5.5" } }, "node_modules/@rollup/pluginutils": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.2.0.tgz", - "integrity": "sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", @@ -1502,9 +1489,9 @@ } }, "node_modules/@rushstack/terminal": { - "version": "0.15.4", - "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.15.4.tgz", - "integrity": "sha512-OQSThV0itlwVNHV6thoXiAYZlQh4Fgvie2CzxFABsbO2MWQsI4zOh3LRNigYSTrmS+ba2j0B3EObakPzf/x6Zg==", + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.16.0.tgz", + "integrity": "sha512-WEvNuKkoR1PXorr9SxO0dqFdSp1BA+xzDrIm/Bwlc5YHg2FFg6oS+uCTYjerOhFuqCW+A3vKBm6EmKWSHfgx/A==", "dependencies": { "@rushstack/node-core-library": "5.14.0", "supports-color": "~8.1.1" @@ -1519,11 +1506,11 @@ } }, "node_modules/@rushstack/ts-command-line": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-5.0.2.tgz", - "integrity": "sha512-+AkJDbu1GFMPIU8Sb7TLVXDv/Q7Mkvx+wAjEl8XiXVVq+p1FmWW6M3LYpJMmoHNckSofeMecgWg5lfMwNAAsEQ==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-5.0.3.tgz", + "integrity": "sha512-bgPhQEqLVv/2hwKLYv/XvsTWNZ9B/+X1zJ7WgQE9rO5oiLzrOZvkIW4pk13yOQBhHyjcND5qMOa6p83t+Z66iQ==", "dependencies": { - "@rushstack/terminal": "0.15.4", + "@rushstack/terminal": "0.16.0", "@types/argparse": "1.0.38", "argparse": "~1.0.9", "string-argv": "~0.3.1" @@ -1739,9 +1726,9 @@ "integrity": "sha512-N4xp3v4s7f0jb2Oij6+6xw5QhH7/IgHCoGIFLCWtbEWoPkGYp8Te4mIwIP21qaurr6ed5JiPMiy2/ZoiGPkLIw==" }, "node_modules/@types/react": { - "version": "16.14.65", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.14.65.tgz", - "integrity": "sha512-Guc3kE+W8LrQB9I3bF3blvNH15dXFIVIHIJTqrF8cp5XI/3IJcHGo4C3sJNPb8Zx49aofXKnAGIKyonE4f7XWg==", + "version": "16.14.66", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.14.66.tgz", + "integrity": "sha512-KPilYP4+25N2ki7vrB4adSR2ucAj95xJcGfKC09bsxcHT+QtB//K7i1FenPnbkLA0Xt9pRi1/RXC1wxFvL9Wtw==", "dependencies": { "@types/prop-types": "*", "@types/scheduler": "^0.16", @@ -1786,16 +1773,16 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.39.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.39.1.tgz", - "integrity": "sha512-yYegZ5n3Yr6eOcqgj2nJH8cH/ZZgF+l0YIdKILSDjYFRjgYQMgv/lRjV5Z7Up04b9VYUondt8EPMqg7kTWgJ2g==", + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.43.0.tgz", + "integrity": "sha512-8tg+gt7ENL7KewsKMKDHXR1vm8tt9eMxjJBYINf6swonlWgkYn5NwyIgXpbbDxTNU5DgpDFfj95prcTq2clIQQ==", "peer": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.39.1", - "@typescript-eslint/type-utils": "8.39.1", - "@typescript-eslint/utils": "8.39.1", - "@typescript-eslint/visitor-keys": "8.39.1", + "@typescript-eslint/scope-manager": "8.43.0", + "@typescript-eslint/type-utils": "8.43.0", + "@typescript-eslint/utils": "8.43.0", + "@typescript-eslint/visitor-keys": "8.43.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", @@ -1809,21 +1796,21 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.39.1", + "@typescript-eslint/parser": "^8.43.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.39.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.39.1.tgz", - "integrity": "sha512-pUXGCuHnnKw6PyYq93lLRiZm3vjuslIy7tus1lIQTYVK9bL8XBgJnCWm8a0KcTtHC84Yya1Q6rtll+duSMj0dg==", + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.43.0.tgz", + "integrity": "sha512-B7RIQiTsCBBmY+yW4+ILd6mF5h1FUwJsVvpqkrgpszYifetQ2Ke+Z4u6aZh0CblkUGIdR59iYVyXqqZGkZ3aBw==", "peer": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.39.1", - "@typescript-eslint/types": "8.39.1", - "@typescript-eslint/typescript-estree": "8.39.1", - "@typescript-eslint/visitor-keys": "8.39.1", + "@typescript-eslint/scope-manager": "8.43.0", + "@typescript-eslint/types": "8.43.0", + "@typescript-eslint/typescript-estree": "8.43.0", + "@typescript-eslint/visitor-keys": "8.43.0", "debug": "^4.3.4" }, "engines": { @@ -1839,13 +1826,13 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.39.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.39.1.tgz", - "integrity": "sha512-8fZxek3ONTwBu9ptw5nCKqZOSkXshZB7uAxuFF0J/wTMkKydjXCzqqga7MlFMpHi9DoG4BadhmTkITBcg8Aybw==", + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.43.0.tgz", + "integrity": "sha512-htB/+D/BIGoNTQYffZw4uM4NzzuolCoaA/BusuSIcC8YjmBYQioew5VUZAYdAETPjeed0hqCaW7EHg+Robq8uw==", "peer": true, "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.39.1", - "@typescript-eslint/types": "^8.39.1", + "@typescript-eslint/tsconfig-utils": "^8.43.0", + "@typescript-eslint/types": "^8.43.0", "debug": "^4.3.4" }, "engines": { @@ -1860,13 +1847,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.39.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.39.1.tgz", - "integrity": "sha512-RkBKGBrjgskFGWuyUGz/EtD8AF/GW49S21J8dvMzpJitOF1slLEbbHnNEtAHtnDAnx8qDEdRrULRnWVx27wGBw==", + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.43.0.tgz", + "integrity": "sha512-daSWlQ87ZhsjrbMLvpuuMAt3y4ba57AuvadcR7f3nl8eS3BjRc8L9VLxFLk92RL5xdXOg6IQ+qKjjqNEimGuAg==", "peer": true, "dependencies": { - "@typescript-eslint/types": "8.39.1", - "@typescript-eslint/visitor-keys": "8.39.1" + "@typescript-eslint/types": "8.43.0", + "@typescript-eslint/visitor-keys": "8.43.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1877,9 +1864,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.39.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.39.1.tgz", - "integrity": "sha512-ePUPGVtTMR8XMU2Hee8kD0Pu4NDE1CN9Q1sxGSGd/mbOtGZDM7pnhXNJnzW63zk/q+Z54zVzj44HtwXln5CvHA==", + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.43.0.tgz", + "integrity": "sha512-ALC2prjZcj2YqqL5X/bwWQmHA2em6/94GcbB/KKu5SX3EBDOsqztmmX1kMkvAJHzxk7TazKzJfFiEIagNV3qEA==", "peer": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1893,14 +1880,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.39.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.39.1.tgz", - "integrity": "sha512-gu9/ahyatyAdQbKeHnhT4R+y3YLtqqHyvkfDxaBYk97EcbfChSJXyaJnIL3ygUv7OuZatePHmQvuH5ru0lnVeA==", + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.43.0.tgz", + "integrity": "sha512-qaH1uLBpBuBBuRf8c1mLJ6swOfzCXryhKND04Igr4pckzSEW9JX5Aw9AgW00kwfjWJF0kk0ps9ExKTfvXfw4Qg==", "peer": true, "dependencies": { - "@typescript-eslint/types": "8.39.1", - "@typescript-eslint/typescript-estree": "8.39.1", - "@typescript-eslint/utils": "8.39.1", + "@typescript-eslint/types": "8.43.0", + "@typescript-eslint/typescript-estree": "8.43.0", + "@typescript-eslint/utils": "8.43.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -1917,9 +1904,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.39.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.39.1.tgz", - "integrity": "sha512-7sPDKQQp+S11laqTrhHqeAbsCfMkwJMrV7oTDvtDds4mEofJYir414bYKUEb8YPUm9QL3U+8f6L6YExSoAGdQw==", + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.43.0.tgz", + "integrity": "sha512-vQ2FZaxJpydjSZJKiSW/LJsabFFvV7KgLC5DiLhkBcykhQj8iK9BOaDmQt74nnKdLvceM5xmhaTF+pLekrxEkw==", "peer": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1930,15 +1917,15 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.39.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.39.1.tgz", - "integrity": "sha512-EKkpcPuIux48dddVDXyQBlKdeTPMmALqBUbEk38McWv0qVEZwOpVJBi7ugK5qVNgeuYjGNQxrrnoM/5+TI/BPw==", + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.43.0.tgz", + "integrity": "sha512-7Vv6zlAhPb+cvEpP06WXXy/ZByph9iL6BQRBDj4kmBsW98AqEeQHlj/13X+sZOrKSo9/rNKH4Ul4f6EICREFdw==", "peer": true, "dependencies": { - "@typescript-eslint/project-service": "8.39.1", - "@typescript-eslint/tsconfig-utils": "8.39.1", - "@typescript-eslint/types": "8.39.1", - "@typescript-eslint/visitor-keys": "8.39.1", + "@typescript-eslint/project-service": "8.43.0", + "@typescript-eslint/tsconfig-utils": "8.43.0", + "@typescript-eslint/types": "8.43.0", + "@typescript-eslint/visitor-keys": "8.43.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -1994,15 +1981,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.39.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.39.1.tgz", - "integrity": "sha512-VF5tZ2XnUSTuiqZFXCZfZs1cgkdd3O/sSYmdo2EpSyDlC86UM/8YytTmKnehOW3TGAlivqTDT6bS87B/GQ/jyg==", + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.43.0.tgz", + "integrity": "sha512-S1/tEmkUeeswxd0GGcnwuVQPFWo8NzZTOMxCvw8BX7OMxnNae+i8Tm7REQen/SwUIPoPqfKn7EaZ+YLpiB3k9g==", "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.39.1", - "@typescript-eslint/types": "8.39.1", - "@typescript-eslint/typescript-estree": "8.39.1" + "@typescript-eslint/scope-manager": "8.43.0", + "@typescript-eslint/types": "8.43.0", + "@typescript-eslint/typescript-estree": "8.43.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2017,12 +2004,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.39.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.39.1.tgz", - "integrity": "sha512-W8FQi6kEh2e8zVhQ0eeRnxdvIoOkAp/CPAahcNio6nO9dsIwb9b34z90KOlheoyuVf6LSOEdjlkxSkapNEc+4A==", + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.43.0.tgz", + "integrity": "sha512-T+S1KqRD4sg/bHfLwrpF/K3gQLBM1n7Rp7OjjikjTEssI2YJzQpi5WXoynOaQ93ERIuq3O8RBTOUYDKszUCEHw==", "peer": true, "dependencies": { - "@typescript-eslint/types": "8.39.1", + "@typescript-eslint/types": "8.43.0", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -2129,9 +2116,9 @@ } }, "node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "engines": { "node": ">=12" }, @@ -2336,11 +2323,6 @@ "node": ">=6.0.0" } }, - "node_modules/b4a": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", - "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==" - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -2353,14 +2335,16 @@ "optional": true }, "node_modules/bare-fs": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.2.0.tgz", - "integrity": "sha512-oRfrw7gwwBVAWx9S5zPMo2iiOjxyiZE12DmblmMQREgcogbNO0AFaZ+QBxxkEXiPspcpvO/Qtqn8LabUx4uYXg==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.4.1.tgz", + "integrity": "sha512-B7VdbD19fypJh6jCXgYge06pxj3UvMv+x8IOZjTSqpiy/ib3UHhwe8Zm7wqxHe7QI3CrySjui4YIRBvocuBthg==", "optional": true, "dependencies": { "bare-events": "^2.5.4", "bare-path": "^3.0.0", - "bare-stream": "^2.6.4" + "bare-stream": "^2.6.4", + "bare-url": "^2.2.2", + "fast-fifo": "^1.3.2" }, "engines": { "bare": ">=1.16.0" @@ -2375,9 +2359,9 @@ } }, "node_modules/bare-os": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.1.tgz", - "integrity": "sha512-uaIjxokhFidJP+bmmvKSgiMzj2sV5GPHaZVAIktcxcpCyBFFWO+YlikVAdhmUo2vYFvFhOXIAlldqV29L8126g==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.2.tgz", + "integrity": "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==", "optional": true, "engines": { "bare": ">=1.14.0" @@ -2413,6 +2397,15 @@ } } }, + "node_modules/bare-url": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.2.2.tgz", + "integrity": "sha512-g+ueNGKkrjMazDG3elZO1pNs3HY5+mMmOet1jtKyhOaCnkLzitxf26z7hoAEkDNgdNmnc1KIlt/dw6Po6xZMpA==", + "optional": true, + "dependencies": { + "bare-path": "^3.0.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -2471,9 +2464,9 @@ } }, "node_modules/browserslist": { - "version": "4.25.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.2.tgz", - "integrity": "sha512-0si2SJK3ooGzIawRu61ZdPCO1IncZwS8IzuX73sPZsXW6EQ/w/DAfPyKI8l1ETTCr2MnvqWitmlCUxgdul45jA==", + "version": "4.25.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.4.tgz", + "integrity": "sha512-4jYpcjabC606xJ3kw2QwGEZKX0Aw7sgQdZCvIK9dhVSPh76BKo+C+btT1RRofH7B+8iNpEbgGNVWiLki5q93yg==", "funding": [ { "type": "opencollective", @@ -2489,8 +2482,8 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001733", - "electron-to-chromium": "^1.5.199", + "caniuse-lite": "^1.0.30001737", + "electron-to-chromium": "^1.5.211", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" }, @@ -2541,9 +2534,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001735", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001735.tgz", - "integrity": "sha512-EV/laoX7Wq2J9TQlyIXRxTJqIw4sxfXS4OYgudGxBYRuTv0q7AM6yMEpU/Vo1I94thg9U6EZ2NfZx9GJq83u7w==", + "version": "1.0.30001741", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001741.tgz", + "integrity": "sha512-QGUGitqsc8ARjLdgAfxETDhRbJ0REsP6O3I96TAth/mVjh2cYzN2u+3AzPP3aVSm2FehEItaJw1xd+IGBXWeSw==", "funding": [ { "type": "opencollective", @@ -2921,9 +2914,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/electron-to-chromium": { - "version": "1.5.203", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.203.tgz", - "integrity": "sha512-uz4i0vLhfm6dLZWbz/iH88KNDV+ivj5+2SA+utpgjKaj9Q0iDLuwk6Idhe9BTxciHudyx6IvTvijhkPvFGUQ0g==" + "version": "1.5.217", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.217.tgz", + "integrity": "sha512-Pludfu5iBxp9XzNl0qq2G87hdD17ZV7h5T4n6rQXDi3nCyloBV3jreE9+8GC6g4X/5yxqVgXEURpcLtM0WS4jA==" }, "node_modules/emoji-regex": { "version": "8.0.0", @@ -3024,18 +3017,18 @@ } }, "node_modules/eslint": { - "version": "9.33.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.33.0.tgz", - "integrity": "sha512-TS9bTNIryDzStCpJN93aC5VRSW3uTx9sClUn4B87pwiCaJh220otoI0X8mJKr+VcPtniMdN8GKjlwgWGUv5ZKA==", + "version": "9.35.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.35.0.tgz", + "integrity": "sha512-QePbBFMJFjgmlE+cXAlbHZbHpdFVS2E/6vzCy7aKlebddvl1vadiC4JFV5u/wqTkNUwEV8WrQi257jf5f06hrg==", "peer": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.0", "@eslint/config-helpers": "^0.3.1", "@eslint/core": "^0.15.2", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.33.0", + "@eslint/js": "9.35.0", "@eslint/plugin-kit": "^0.3.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -4880,9 +4873,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==" + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.20.tgz", + "integrity": "sha512-7gK6zSXEH6neM212JgfYFXe+GmZQM+fia5SsusuBIUgnPheLFBmIPhtFoAQRj8/7wASYQnbDlHPVwY0BefoFgA==" }, "node_modules/node-watch": { "version": "0.7.3", @@ -5367,7 +5360,7 @@ "version": "24.8.2", "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.8.2.tgz", "integrity": "sha512-Sn6SBPwJ6ASFvQ7knQkR+yG7pcmr4LfXzmoVp3NR0xXyBbPhJa8a8ybtb6fnw1g/DD/2t34//yirubVczko37w==", - "deprecated": "< 24.9.0 is no longer supported", + "deprecated": "< 24.10.2 is no longer supported", "hasInstallScript": true, "dependencies": { "@puppeteer/browsers": "2.10.4", @@ -6300,6 +6293,19 @@ "bare-path": "^3.0.0" } }, + "node_modules/tar-fs/node_modules/b4a": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.1.tgz", + "integrity": "sha512-ZovbrBV0g6JxK5cGUF1Suby1vLfKjv4RWi8IxoaO/Mon8BDD9I21RxjHFtgQ+kskJqLAVyQZly3uMBui+vhc8Q==", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, "node_modules/tar-fs/node_modules/tar-stream": { "version": "3.1.7", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", @@ -6333,6 +6339,19 @@ "b4a": "^1.6.4" } }, + "node_modules/text-decoder/node_modules/b4a": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.1.tgz", + "integrity": "sha512-ZovbrBV0g6JxK5cGUF1Suby1vLfKjv4RWi8IxoaO/Mon8BDD9I21RxjHFtgQ+kskJqLAVyQZly3uMBui+vhc8Q==", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, "node_modules/tiny-glob": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz", diff --git a/shared/1ds-core-js/src/Utils.ts b/shared/1ds-core-js/src/Utils.ts index 4ce5bb42f..e2b6823b5 100644 --- a/shared/1ds-core-js/src/Utils.ts +++ b/shared/1ds-core-js/src/Utils.ts @@ -14,7 +14,7 @@ import { IEventProperty, IExtendedTelemetryItem } from "./DataModels"; import { EventLatency, EventLatencyValue, FieldValueSanitizerType, GuidStyle, eEventPropertyType, eValueKind } from "./Enums"; import { STR_EMPTY } from "./InternalConstants"; -export const Version = "#version#"; +export const Version = "4.3.7"; export const FullVersionString = "1DS-Web-JS-" + Version; const ObjHasOwnProperty = ObjProto.hasOwnProperty; diff --git a/shared/AppInsightsCore/Tests/Unit/src/AppInsightsCoreSize.Tests.ts b/shared/AppInsightsCore/Tests/Unit/src/AppInsightsCoreSize.Tests.ts index 87bcfa7f6..4cf380066 100644 --- a/shared/AppInsightsCore/Tests/Unit/src/AppInsightsCoreSize.Tests.ts +++ b/shared/AppInsightsCore/Tests/Unit/src/AppInsightsCoreSize.Tests.ts @@ -51,10 +51,10 @@ function _checkSize(checkType: string, maxSize: number, size: number, isNightly: } export class AppInsightsCoreSizeCheck extends AITestClass { - private readonly MAX_RAW_SIZE = 68; - private readonly MAX_BUNDLE_SIZE = 68; - private readonly MAX_RAW_DEFLATE_SIZE = 29; - private readonly MAX_BUNDLE_DEFLATE_SIZE = 29; + private readonly MAX_RAW_SIZE = 72; + private readonly MAX_BUNDLE_SIZE = 72; + private readonly MAX_RAW_DEFLATE_SIZE = 30; + private readonly MAX_BUNDLE_DEFLATE_SIZE = 30; private readonly rawFilePath = "../dist/es5/applicationinsights-core-js.min.js"; private readonly prodFilePath = "../browser/es5/applicationinsights-core-js.min.js"; diff --git a/shared/AppInsightsCore/Tests/Unit/src/SdkStats.Tests.ts b/shared/AppInsightsCore/Tests/Unit/src/SdkStats.Tests.ts new file mode 100644 index 000000000..49e2925e4 --- /dev/null +++ b/shared/AppInsightsCore/Tests/Unit/src/SdkStats.Tests.ts @@ -0,0 +1,366 @@ +import * as sinon from "sinon"; +import { Assert, AITestClass } from "@microsoft/ai-test-framework"; +import { IPayloadData } from "../../../src/JavaScriptSDK.Interfaces/IXHROverride"; +import { IStatsMgr } from "../../../src/JavaScriptSDK.Interfaces/IStatsMgr"; +import { AppInsightsCore } from "../../../src/JavaScriptSDK/AppInsightsCore"; +import { IConfiguration } from "../../../src/JavaScriptSDK.Interfaces/IConfiguration"; +import { createStatsMgr } from "../../../src/JavaScriptSDK/SdkStats"; +import { ISdkStatsState } from "../../../src/JavaScriptSDK.Interfaces/ISdkStats"; +import { eStatsType } from "../../../src/JavaScriptSDK.Enums/StatsType"; +import { ITelemetryItem } from "../../../src/JavaScriptSDK.Interfaces/ITelemetryItem"; +import { IPlugin } from "../../../src/JavaScriptSDK.Interfaces/ITelemetryPlugin"; +import { IAppInsightsCore } from "../../../src/JavaScriptSDK.Interfaces/IAppInsightsCore"; +import { FeatureOptInMode } from "../../../src/JavaScriptSDK.Enums/FeatureOptInEnums"; + +const STATS_COLLECTION_SHORT_INTERVAL: number = 900; // 15 minutes + +export class StatsBeatTests extends AITestClass { + private _core: AppInsightsCore; + private _config: IConfiguration; + private _statsMgr: IStatsMgr; + private _trackSpy: sinon.SinonSpy; + + constructor(emulateIe: boolean) { + super("StatsBeatTests", emulateIe); + } + + public testInitialize() { + let _self = this; + super.testInitialize(); + + _self._config = { + instrumentationKey: "Test-iKey", + disableInstrumentationKeyValidation: true, + _sdk: { + stats: { + shrtInt: STATS_COLLECTION_SHORT_INTERVAL, + endCfg: [ + { + type: 0, + keyMap: [ + { + key: "stats-key1", + match: [ "https://example.endpoint.com" ] + } + ] + } + ] + } + } + }; + + _self._statsMgr = createStatsMgr(); + _self._core = new AppInsightsCore(); + // _self._statsMgr.init(_self._core, { + // feature: "StatsBeat", + // getCfg: (core, cfg) => { + // return cfg?._sdk?.stats; + // } + // }); + + // Create spy for tracking telemetry + _self._trackSpy = this.sandbox.spy(_self._core, "track"); + } + + public testCleanup() { + super.testCleanup(); + this._core = null as any; + this._statsMgr = null as any; + } + + public registerTests() { + + this.testCase({ + name: "StatsBeat: Initialization", + test: () => { + // Test with no initialization + Assert.equal(false, this._statsMgr.enabled, "StatsBeat should not be initialized by default"); + + let statsBeatState: ISdkStatsState = { + cKey: "Test-iKey", + endpoint: "https://example.endpoint.com", + sdkVer: "1.0.0", + type: eStatsType.SDK + }; + Assert.equal(null, this._statsMgr.newInst(statsBeatState), "StatsBeat should not be created before initialization"); + + // Initialize + this._statsMgr.init(this._core, { + feature: "StatsBeat", + getCfg: (core, cfg) => { + return cfg?._sdk?.stats; + } + }); + Assert.equal(true, this._statsMgr.enabled, "StatsBeat should be initialized after initialization"); + + let newInst = this._statsMgr.newInst(statsBeatState); + Assert.ok(!!newInst, "StatsBeat should be created after initialization"); + Assert.equal(true, newInst.enabled, "StatsBeat should be enabled after initialization"); + Assert.equal("https://example.endpoint.com", newInst.endpoint); + Assert.equal(0, newInst.type); + } + }); + + this.testCase({ + name: "StatsBeat: count method tracks request metrics", + useFakeTimers: true, + test: () => { + // Initialize StatsBeat + this._statsMgr.init(this._core, { + feature: "StatsBeat", + getCfg: (core, cfg) => { + return cfg?._sdk?.stats; + } + }); + + // Create mock payload data with timing information + const payloadData = { + urlString: "https://example.endpoint.com", + data: "testData", + headers: {}, + timeout: 0, + disableXhrSync: false, + sdkStatsData: { + startTime: "2023-10-01T00:00:00Z" // Simulated start time + } + } as IPayloadData; + + let statsBeatState: ISdkStatsState = { + cKey: "Test-iKey", + endpoint: "https://example.endpoint.com", + sdkVer: "1.0.0", + type: eStatsType.SDK + }; + let statsBeat = this._statsMgr.newInst(statsBeatState); + + // Test successful request + statsBeat.count(200, payloadData, "https://example.endpoint.com"); + + // Test failed request + statsBeat.count(500, payloadData, "https://example.endpoint.com"); + + // Test throttled request + statsBeat.count(429, payloadData, "https://example.endpoint.com"); + + // Verify that trackStatsbeats is called when the timer fires + this.clock.tick(STATS_COLLECTION_SHORT_INTERVAL + 1); + + // Verify that track was called + Assert.ok(this._trackSpy.called, "track should be called when statsbeat timer fires"); + + // When the timer fires, multiple metrics should be sent + Assert.ok(this._trackSpy.callCount >= 3, "Multiple metrics should be tracked"); + } + }); + + this.testCase({ + name: "StatsBeat: countException method tracks exceptions", + useFakeTimers: true, + test: () => { + // Initialize StatsBeat + this._statsMgr.init(this._core, { + feature: "StatsBeat", + getCfg: (core, cfg) => { + return cfg?._sdk?.stats; + } + }); + + let statsBeatState: ISdkStatsState = { + cKey: "Test-iKey", + endpoint: "https://example.endpoint.com", + sdkVer: "1.0.0", + type: eStatsType.SDK + }; + let statsBeat = this._statsMgr.newInst(statsBeatState); + + // Count an exception + statsBeat.countException("https://example.endpoint.com", "NetworkError"); + + // Verify that trackStatsbeats is called when the timer fires + this.clock.tick(STATS_COLLECTION_SHORT_INTERVAL + 1); + + // Verify that track was called + Assert.ok(this._trackSpy.called, "track should be called when statsbeat timer fires"); + + // Check that exception metrics are tracked + let foundExceptionMetric = false; + for (let i = 0; i < this._trackSpy.callCount; i++) { + const call = this._trackSpy.getCall(i); + const item: ITelemetryItem = call.args[0]; + if (item.baseData && + item.baseData.properties && + item.baseData.properties.exceptionType === "NetworkError") { + foundExceptionMetric = true; + break; + } + } + + Assert.ok(foundExceptionMetric, "Exception metrics should be tracked"); + } + }); + + this.testCase({ + name: "StatsBeat: does not send metrics for different endpoints", + useFakeTimers: true, + test: () => { + // Initialize StatsBeat for a specific endpoint + this._statsMgr.init(this._core, { + feature: "StatsBeat", + getCfg: (core, cfg) => { + return cfg?._sdk?.stats; + } + }); + + // Create mock payload data + const payloadData = { + urlString: "https://example.endpoint.com", + data: "testData", + headers: {}, + timeout: 0, + disableXhrSync: false, + sdkStatsData: { + startTime: Date.now() + } + } as IPayloadData; + + let statsBeatState: ISdkStatsState = { + cKey: "Test-iKey", + endpoint: "https://example.endpoint.com", + sdkVer: "1.0.0", + type: eStatsType.SDK + }; + let statsBeat = this._statsMgr.newInst(statsBeatState); + + // Set up spies to check internal calls + const countSpy = this.sandbox.spy(statsBeat, "count"); + + // Count metrics for a different endpoint + statsBeat.count(200, payloadData, "https://different.endpoint.com"); + + // Verify that trackStatsbeats is called when the timer fires + this.clock.tick(STATS_COLLECTION_SHORT_INTERVAL + 1); + // The count method was called, but it should return early + Assert.equal(1, countSpy.callCount, "count method should be called"); + Assert.equal(0, this._trackSpy.callCount, "track should not be called for different endpoint"); + } + }); + + this.testCase({ + name: "StatsBeat: test dynamic configuration changes", + useFakeTimers: true, + test: () => { + // Setup core with statsbeat enabled + this._core.initialize(this._config, [new ChannelPlugin()]); + // Initialize StatsBeat for a specific endpoint + this._statsMgr.init(this._core, { + feature: "StatsBeat", + getCfg: (core, cfg) => { + return cfg?._sdk?.stats; + } + }); + this._core.setStatsMgr(this._statsMgr); + + let statsBeatState: ISdkStatsState = { + cKey: "Test-iKey", + endpoint: "https://example.endpoint.com", + sdkVer: "1.0.0", + type: eStatsType.SDK + }; + + // Verify that statsbeat is created + const statsbeat = this._core.getSdkStats(statsBeatState); + Assert.ok(!!statsbeat, "Statsbeat should be created"); + + // Explicitly disable statsbeat + this._core.config.featureOptIn["StatsBeat"].mode = FeatureOptInMode.disable; + this.clock.tick(1); // Allow time for config changes to propagate + + // Verify that statsbeat is removed + const updatedStatsbeat = this._core.getSdkStats(statsBeatState); + Assert.ok(!updatedStatsbeat, "Statsbeat should be removed when disabled"); + + // Re-enable statsbeat + this._core.config.featureOptIn["StatsBeat"].mode = FeatureOptInMode.enable; + this.clock.tick(1); // Allow time for config changes to propagate + + // Verify that statsbeat is created again + const reenabledStatsbeat = this._core.getSdkStats(statsBeatState); + Assert.ok(reenabledStatsbeat, "Statsbeat should be recreated when re-enabled"); + + // Test that statsbeat is not created when disabled with undefined + this._core.config.featureOptIn["StatsBeat"].mode = FeatureOptInMode.none; + this.clock.tick(1); // Allow time for config changes to propagate + + // Verify that statsbeat is removed + Assert.ok(!this._core.getSdkStats(statsBeatState), "Statsbeat should be removed when disabled"); + + // Re-enable statsbeat + this._core.config.featureOptIn["StatsBeat"].mode = FeatureOptInMode.enable; + this.clock.tick(1); // Allow time for config changes to propagate + + // Verify that statsbeat is created again + Assert.ok(!!this._core.getSdkStats(statsBeatState), "Statsbeat should be recreated when re-enabled"); + + // Test that statsbeat is not created when disabled with null value + this._core.config.featureOptIn["StatsBeat"].mode = null; + this.clock.tick(1); // Allow time for config changes to propagate + + // Verify that statsbeat is removed + Assert.ok(!this._core.getSdkStats(statsBeatState), "Statsbeat should be removed when disabled"); + } + }); + } +} + +class ChannelPlugin implements IPlugin { + public isFlushInvoked = false; + public isTearDownInvoked = false; + public isResumeInvoked = false; + public isPauseInvoked = false; + + public identifier = "Sender"; + public priority: number = 1001; + + constructor() { + this.processTelemetry = this._processTelemetry.bind(this); + } + + public pause(): void { + this.isPauseInvoked = true; + } + + public resume(): void { + this.isResumeInvoked = true; + } + + public teardown(): void { + this.isTearDownInvoked = true; + } + + flush(async?: boolean, callBack?: () => void): void { + this.isFlushInvoked = true; + if (callBack) { + callBack(); + } + } + + public processTelemetry(env: ITelemetryItem) {} + + setNextPlugin(next: any) { + // no next setup + } + + public initialize = (config: IConfiguration, core: IAppInsightsCore, plugin: IPlugin[]) => { + } + + private _processTelemetry(env: ITelemetryItem) { + } +} + +class CustomTestError extends Error { + constructor(message = "") { + super(message); + this.name = "CustomTestError"; + this.message = message + " -- test error."; + } +} \ No newline at end of file diff --git a/shared/AppInsightsCore/Tests/Unit/src/StatsBeat.Tests.ts b/shared/AppInsightsCore/Tests/Unit/src/StatsBeat.Tests.ts deleted file mode 100644 index 3ba1eb90c..000000000 --- a/shared/AppInsightsCore/Tests/Unit/src/StatsBeat.Tests.ts +++ /dev/null @@ -1,366 +0,0 @@ -// import * as sinon from "sinon"; -// import { Assert, AITestClass } from "@microsoft/ai-test-framework"; -// import { IPayloadData } from "../../../src/JavaScriptSDK.Interfaces/IXHROverride"; -// import { IStatsMgr } from "../../../src/JavaScriptSDK.Interfaces/IStatsMgr"; -// import { AppInsightsCore } from "../../../src/JavaScriptSDK/AppInsightsCore"; -// import { IConfiguration } from "../../../src/JavaScriptSDK.Interfaces/IConfiguration"; -// import { createStatsMgr } from "../../../src/JavaScriptSDK/StatsBeat"; -// import { IStatsBeatState } from "../../../src/JavaScriptSDK.Interfaces/IStatsBeat"; -// import { eStatsType } from "../../../src/JavaScriptSDK.Enums/StatsType"; -// import { ITelemetryItem } from "../../../src/JavaScriptSDK.Interfaces/ITelemetryItem"; -// import { IPlugin } from "../../../src/JavaScriptSDK.Interfaces/ITelemetryPlugin"; -// import { IAppInsightsCore } from "../../../src/JavaScriptSDK.Interfaces/IAppInsightsCore"; -// import { FeatureOptInMode } from "../../../src/JavaScriptSDK.Enums/FeatureOptInEnums"; - -// const STATS_COLLECTION_SHORT_INTERVAL: number = 900; // 15 minutes - -// export class StatsBeatTests extends AITestClass { -// private _core: AppInsightsCore; -// private _config: IConfiguration; -// private _statsMgr: IStatsMgr; -// private _trackSpy: sinon.SinonSpy; - -// constructor(emulateIe: boolean) { -// super("StatsBeatTests", emulateIe); -// } - -// public testInitialize() { -// let _self = this; -// super.testInitialize(); - -// _self._config = { -// instrumentationKey: "Test-iKey", -// disableInstrumentationKeyValidation: true, -// _sdk: { -// stats: { -// shrtInt: STATS_COLLECTION_SHORT_INTERVAL, -// endCfg: [ -// { -// type: 0, -// keyMap: [ -// { -// key: "stats-key1", -// match: [ "https://example.endpoint.com" ] -// } -// ] -// } -// ] -// } -// } -// }; - -// _self._statsMgr = createStatsMgr(); -// _self._core = new AppInsightsCore(); -// // _self._statsMgr.init(_self._core, { -// // feature: "StatsBeat", -// // getCfg: (core, cfg) => { -// // return cfg?._sdk?.stats; -// // } -// // }); - -// // Create spy for tracking telemetry -// _self._trackSpy = this.sandbox.spy(_self._core, "track"); -// } - -// public testCleanup() { -// super.testCleanup(); -// this._core = null as any; -// this._statsMgr = null as any; -// } - -// public registerTests() { - -// this.testCase({ -// name: "StatsBeat: Initialization", -// test: () => { -// // Test with no initialization -// Assert.equal(false, this._statsMgr.enabled, "StatsBeat should not be initialized by default"); - -// let statsBeatState: IStatsBeatState = { -// cKey: "Test-iKey", -// endpoint: "https://example.endpoint.com", -// sdkVer: "1.0.0", -// type: eStatsType.SDK -// }; -// Assert.equal(null, this._statsMgr.newInst(statsBeatState), "StatsBeat should not be created before initialization"); - -// // Initialize -// this._statsMgr.init(this._core, { -// feature: "StatsBeat", -// getCfg: (core, cfg) => { -// return cfg?._sdk?.stats; -// } -// }); -// Assert.equal(true, this._statsMgr.enabled, "StatsBeat should be initialized after initialization"); - -// let newInst = this._statsMgr.newInst(statsBeatState); -// Assert.ok(!!newInst, "StatsBeat should be created after initialization"); -// Assert.equal(true, newInst.enabled, "StatsBeat should be enabled after initialization"); -// Assert.equal("https://example.endpoint.com", newInst.endpoint); -// Assert.equal(0, newInst.type); -// } -// }); - -// this.testCase({ -// name: "StatsBeat: count method tracks request metrics", -// useFakeTimers: true, -// test: () => { -// // Initialize StatsBeat -// this._statsMgr.init(this._core, { -// feature: "StatsBeat", -// getCfg: (core, cfg) => { -// return cfg?._sdk?.stats; -// } -// }); - -// // Create mock payload data with timing information -// const payloadData = { -// urlString: "https://example.endpoint.com", -// data: "testData", -// headers: {}, -// timeout: 0, -// disableXhrSync: false, -// statsBeatData: { -// startTime: "2023-10-01T00:00:00Z" // Simulated start time -// } -// } as IPayloadData; - -// let statsBeatState: IStatsBeatState = { -// cKey: "Test-iKey", -// endpoint: "https://example.endpoint.com", -// sdkVer: "1.0.0", -// type: eStatsType.SDK -// }; -// let statsBeat = this._statsMgr.newInst(statsBeatState); - -// // Test successful request -// statsBeat.count(200, payloadData, "https://example.endpoint.com"); - -// // Test failed request -// statsBeat.count(500, payloadData, "https://example.endpoint.com"); - -// // Test throttled request -// statsBeat.count(429, payloadData, "https://example.endpoint.com"); - -// // Verify that trackStatsbeats is called when the timer fires -// this.clock.tick(STATS_COLLECTION_SHORT_INTERVAL + 1); - -// // Verify that track was called -// Assert.ok(this._trackSpy.called, "track should be called when statsbeat timer fires"); - -// // When the timer fires, multiple metrics should be sent -// Assert.ok(this._trackSpy.callCount >= 3, "Multiple metrics should be tracked"); -// } -// }); - -// this.testCase({ -// name: "StatsBeat: countException method tracks exceptions", -// useFakeTimers: true, -// test: () => { -// // Initialize StatsBeat -// this._statsMgr.init(this._core, { -// feature: "StatsBeat", -// getCfg: (core, cfg) => { -// return cfg?._sdk?.stats; -// } -// }); - -// let statsBeatState: IStatsBeatState = { -// cKey: "Test-iKey", -// endpoint: "https://example.endpoint.com", -// sdkVer: "1.0.0", -// type: eStatsType.SDK -// }; -// let statsBeat = this._statsMgr.newInst(statsBeatState); - -// // Count an exception -// statsBeat.countException("https://example.endpoint.com", "NetworkError"); - -// // Verify that trackStatsbeats is called when the timer fires -// this.clock.tick(STATS_COLLECTION_SHORT_INTERVAL + 1); - -// // Verify that track was called -// Assert.ok(this._trackSpy.called, "track should be called when statsbeat timer fires"); - -// // Check that exception metrics are tracked -// let foundExceptionMetric = false; -// for (let i = 0; i < this._trackSpy.callCount; i++) { -// const call = this._trackSpy.getCall(i); -// const item: ITelemetryItem = call.args[0]; -// if (item.baseData && -// item.baseData.properties && -// item.baseData.properties.exceptionType === "NetworkError") { -// foundExceptionMetric = true; -// break; -// } -// } - -// Assert.ok(foundExceptionMetric, "Exception metrics should be tracked"); -// } -// }); - -// this.testCase({ -// name: "StatsBeat: does not send metrics for different endpoints", -// useFakeTimers: true, -// test: () => { -// // Initialize StatsBeat for a specific endpoint -// this._statsMgr.init(this._core, { -// feature: "StatsBeat", -// getCfg: (core, cfg) => { -// return cfg?._sdk?.stats; -// } -// }); - -// // Create mock payload data -// const payloadData = { -// urlString: "https://example.endpoint.com", -// data: "testData", -// headers: {}, -// timeout: 0, -// disableXhrSync: false, -// statsBeatData: { -// startTime: Date.now() -// } -// } as IPayloadData; - -// let statsBeatState: IStatsBeatState = { -// cKey: "Test-iKey", -// endpoint: "https://example.endpoint.com", -// sdkVer: "1.0.0", -// type: eStatsType.SDK -// }; -// let statsBeat = this._statsMgr.newInst(statsBeatState); - -// // Set up spies to check internal calls -// const countSpy = this.sandbox.spy(statsBeat, "count"); - -// // Count metrics for a different endpoint -// statsBeat.count(200, payloadData, "https://different.endpoint.com"); - -// // Verify that trackStatsbeats is called when the timer fires -// this.clock.tick(STATS_COLLECTION_SHORT_INTERVAL + 1); -// // The count method was called, but it should return early -// Assert.equal(1, countSpy.callCount, "count method should be called"); -// Assert.equal(0, this._trackSpy.callCount, "track should not be called for different endpoint"); -// } -// }); - -// this.testCase({ -// name: "StatsBeat: test dynamic configuration changes", -// useFakeTimers: true, -// test: () => { -// // Setup core with statsbeat enabled -// this._core.initialize(this._config, [new ChannelPlugin()]); -// // Initialize StatsBeat for a specific endpoint -// this._statsMgr.init(this._core, { -// feature: "StatsBeat", -// getCfg: (core, cfg) => { -// return cfg?._sdk?.stats; -// } -// }); -// this._core.setStatsMgr(this._statsMgr); - -// let statsBeatState: IStatsBeatState = { -// cKey: "Test-iKey", -// endpoint: "https://example.endpoint.com", -// sdkVer: "1.0.0", -// type: eStatsType.SDK -// }; - -// // Verify that statsbeat is created -// const statsbeat = this._core.getStatsBeat(statsBeatState); -// Assert.ok(!!statsbeat, "Statsbeat should be created"); - -// // Explicitly disable statsbeat -// this._core.config.featureOptIn["StatsBeat"].mode = FeatureOptInMode.disable; -// this.clock.tick(1); // Allow time for config changes to propagate - -// // Verify that statsbeat is removed -// const updatedStatsbeat = this._core.getStatsBeat(statsBeatState); -// Assert.ok(!updatedStatsbeat, "Statsbeat should be removed when disabled"); - -// // Re-enable statsbeat -// this._core.config.featureOptIn["StatsBeat"].mode = FeatureOptInMode.enable; -// this.clock.tick(1); // Allow time for config changes to propagate - -// // Verify that statsbeat is created again -// const reenabledStatsbeat = this._core.getStatsBeat(statsBeatState); -// Assert.ok(reenabledStatsbeat, "Statsbeat should be recreated when re-enabled"); - -// // Test that statsbeat is not created when disabled with undefined -// this._core.config.featureOptIn["StatsBeat"].mode = FeatureOptInMode.none; -// this.clock.tick(1); // Allow time for config changes to propagate - -// // Verify that statsbeat is removed -// Assert.ok(!this._core.getStatsBeat(statsBeatState), "Statsbeat should be removed when disabled"); - -// // Re-enable statsbeat -// this._core.config.featureOptIn["StatsBeat"].mode = FeatureOptInMode.enable; -// this.clock.tick(1); // Allow time for config changes to propagate - -// // Verify that statsbeat is created again -// Assert.ok(!!this._core.getStatsBeat(statsBeatState), "Statsbeat should be recreated when re-enabled"); - -// // Test that statsbeat is not created when disabled with null value -// this._core.config.featureOptIn["StatsBeat"].mode = null; -// this.clock.tick(1); // Allow time for config changes to propagate - -// // Verify that statsbeat is removed -// Assert.ok(!this._core.getStatsBeat(statsBeatState), "Statsbeat should be removed when disabled"); -// } -// }); -// } -// } - -// class ChannelPlugin implements IPlugin { -// public isFlushInvoked = false; -// public isTearDownInvoked = false; -// public isResumeInvoked = false; -// public isPauseInvoked = false; - -// public identifier = "Sender"; -// public priority: number = 1001; - -// constructor() { -// this.processTelemetry = this._processTelemetry.bind(this); -// } - -// public pause(): void { -// this.isPauseInvoked = true; -// } - -// public resume(): void { -// this.isResumeInvoked = true; -// } - -// public teardown(): void { -// this.isTearDownInvoked = true; -// } - -// flush(async?: boolean, callBack?: () => void): void { -// this.isFlushInvoked = true; -// if (callBack) { -// callBack(); -// } -// } - -// public processTelemetry(env: ITelemetryItem) {} - -// setNextPlugin(next: any) { -// // no next setup -// } - -// public initialize = (config: IConfiguration, core: IAppInsightsCore, plugin: IPlugin[]) => { -// } - -// private _processTelemetry(env: ITelemetryItem) { -// } -// } - -// class CustomTestError extends Error { -// constructor(message = "") { -// super(message); -// this.name = "CustomTestError"; -// this.message = message + " -- test error."; -// } -// } \ No newline at end of file diff --git a/shared/AppInsightsCore/src/JavaScriptSDK.Interfaces/IAppInsightsCore.ts b/shared/AppInsightsCore/src/JavaScriptSDK.Interfaces/IAppInsightsCore.ts index 39859e849..ddddb4b96 100644 --- a/shared/AppInsightsCore/src/JavaScriptSDK.Interfaces/IAppInsightsCore.ts +++ b/shared/AppInsightsCore/src/JavaScriptSDK.Interfaces/IAppInsightsCore.ts @@ -16,14 +16,14 @@ import { INotificationListener } from "./INotificationListener"; import { INotificationManager } from "./INotificationManager"; import { IPerfManagerProvider } from "./IPerfManager"; import { IProcessTelemetryContext } from "./IProcessTelemetryContext"; +import { ISdkStats, ISdkStatsState } from "./ISdkStats"; +import { IStatsMgr } from "./IStatsMgr"; import { ITelemetryInitializerHandler, TelemetryInitializerFunction } from "./ITelemetryInitializers"; import { ITelemetryItem } from "./ITelemetryItem"; import { IPlugin, ITelemetryPlugin } from "./ITelemetryPlugin"; import { ITelemetryUnloadState } from "./ITelemetryUnloadState"; import { ILegacyUnloadHook, IUnloadHook } from "./IUnloadHook"; -// import { IStatsBeat, IStatsBeatState } from "./IStatsBeat"; -// import { IStatsMgr } from "./IStatsMgr"; export interface ILoadedPlugin { plugin: T; @@ -126,21 +126,21 @@ export interface IAppInsightsCore(core: IAppInsightsCore, cfg: IStatsMgrConfig) => IUnloadHook | null; /** - * Returns a new {@link IStatsBeat} instance for the current state which includes the endpoint. + * Returns a new {@link ISdkStats} instance for the current state which includes the endpoint. * This method should be called only after the manager has been initialized and the * {@link IStatsBeatConfig} has been set, otherwise it will return null. * @param state - The current state of the stats beat manager. * @returns A new instance of the stats beat or null if the manager or the configuration does not support - * the {@link IStatsBeatState}. + * the {@link ISdkStatsState}. */ - newInst: (state: IStatsBeatState) => IStatsBeat; -} \ No newline at end of file + newInst: (state: ISdkStatsState) => ISdkStats; +} diff --git a/shared/AppInsightsCore/src/JavaScriptSDK/AppInsightsCore.ts b/shared/AppInsightsCore/src/JavaScriptSDK/AppInsightsCore.ts index 1650c97e2..c151a3435 100644 --- a/shared/AppInsightsCore/src/JavaScriptSDK/AppInsightsCore.ts +++ b/shared/AppInsightsCore/src/JavaScriptSDK/AppInsightsCore.ts @@ -7,6 +7,7 @@ import { ITimerHandler, arrAppend, arrForEach, arrIndexOf, createTimeout, deepExtend, hasDocument, isFunction, isNullOrUndefined, isPlainObject, isPromiseLike, objDeepFreeze, objDefine, objForEachKey, objFreeze, objHasOwn, scheduleTimeout, throwError } from "@nevware21/ts-utils"; +import { cfgDfMerge } from "../Config/ConfigDefaultHelpers"; import { createDynamicConfig, onConfigChange } from "../Config/DynamicConfig"; import { IConfigDefaults } from "../Config/IConfigDefaults"; import { IDynamicConfigHandler, _IInternalDynamicConfigHandler } from "../Config/IDynamicConfigHandler"; @@ -20,7 +21,7 @@ import { TelemetryUpdateReason } from "../JavaScriptSDK.Enums/TelemetryUpdateRea import { IAppInsightsCore, ILoadedPlugin } from "../JavaScriptSDK.Interfaces/IAppInsightsCore"; import { IChannelControls } from "../JavaScriptSDK.Interfaces/IChannelControls"; import { IChannelControlsHost } from "../JavaScriptSDK.Interfaces/IChannelControlsHost"; -import { IConfiguration } from "../JavaScriptSDK.Interfaces/IConfiguration"; +import { IConfiguration, IInternalSdkConfiguration } from "../JavaScriptSDK.Interfaces/IConfiguration"; import { ICookieMgr } from "../JavaScriptSDK.Interfaces/ICookieMgr"; import { IDiagnosticLogger } from "../JavaScriptSDK.Interfaces/IDiagnosticLogger"; import { IDistributedTraceContext } from "../JavaScriptSDK.Interfaces/IDistributedTraceContext"; @@ -28,6 +29,8 @@ import { INotificationListener } from "../JavaScriptSDK.Interfaces/INotification import { INotificationManager } from "../JavaScriptSDK.Interfaces/INotificationManager"; import { IPerfManager } from "../JavaScriptSDK.Interfaces/IPerfManager"; import { IProcessTelemetryContext, IProcessTelemetryUpdateContext } from "../JavaScriptSDK.Interfaces/IProcessTelemetryContext"; +import { ISdkStats, ISdkStatsState, IStatsBeatConfig } from "../JavaScriptSDK.Interfaces/ISdkStats"; +import { IStatsMgr } from "../JavaScriptSDK.Interfaces/IStatsMgr"; import { ITelemetryInitializerHandler, TelemetryInitializerFunction } from "../JavaScriptSDK.Interfaces/ITelemetryInitializers"; import { ITelemetryItem } from "../JavaScriptSDK.Interfaces/ITelemetryItem"; import { IPlugin, ITelemetryPlugin } from "../JavaScriptSDK.Interfaces/ITelemetryPlugin"; @@ -55,8 +58,6 @@ import { TelemetryInitializerPlugin } from "./TelemetryInitializerPlugin"; import { IUnloadHandlerContainer, UnloadHandler, createUnloadHandlerContainer } from "./UnloadHandlerContainer"; import { IUnloadHookContainer, createUnloadHookContainer } from "./UnloadHookContainer"; -// import { IStatsBeat, IStatsBeatConfig, IStatsBeatState } from "../JavaScriptSDK.Interfaces/IStatsBeat"; -// import { IStatsMgr } from "../JavaScriptSDK.Interfaces/IStatsMgr"; const strValidationError = "Plugins must provide initialize method"; const strNotificationManager = "_notificationManager"; const strSdkUnloadingError = "SDK is still unloading..."; @@ -65,22 +66,22 @@ const maxInitQueueSize = 100; const maxInitTimeout = 50000; // const strPluginUnloadFailed = "Failed to unload plugin"; -// /** -// * Default StatsBeatMgr configuration -// * @internal -// */ -// const defaultStatsCfg: IConfigDefaults = objDeepFreeze({ -// shrtInt: UNDEFINED_VALUE, -// endCfg: cfgDfMerge([]) -// }); - -// /** -// * Default SDK initialization configuration -// * @internal -// */ -// const defaultSdkConfig: IConfigDefaults = objDeepFreeze({ -// stats: { rdOnly: true, mrg: true, v: defaultStatsCfg } -// }); +/** + * Default StatsBeatMgr configuration + * @internal + */ +const defaultStatsCfg: IConfigDefaults = objDeepFreeze({ + shrtInt: UNDEFINED_VALUE, + endCfg: cfgDfMerge([]) +}); + +/** + * Default SDK initialization configuration + * @internal + */ +const defaultSdkConfig: IConfigDefaults = objDeepFreeze({ + stats: { rdOnly: true, mrg: true, v: defaultStatsCfg } +}); /** * The default settings for the config. @@ -94,8 +95,8 @@ const defaultConfig: IConfigDefaults = objDeepFreeze({ [STR_EXTENSION_CONFIG]: { ref: true, v: {} }, [STR_CREATE_PERF_MGR]: UNDEFINED_VALUE, loggingLevelConsole: eLoggingSeverity.DISABLED, - diagnosticLogInterval: UNDEFINED_VALUE - // _sdk: { rdOnly: true, ref: true, v: defaultSdkConfig } + diagnosticLogInterval: UNDEFINED_VALUE, + _sdk: { rdOnly: true, ref: true, v: defaultSdkConfig } }); /** @@ -294,8 +295,8 @@ export class AppInsightsCore im let _logger: IDiagnosticLogger; let _eventQueue: ITelemetryItem[]; let _notificationManager: INotificationManager | null | undefined; - // let _statsBeat: IStatsBeat | null; - // let _statsMgr: IStatsMgr | null; + let _statsBeat: ISdkStats | null; + let _statsMgr: IStatsMgr | null; let _perfManager: IPerfManager | null; let _cfgPerfManager: IPerfManager | null; let _cookieManager: ICookieMgr | null; @@ -525,47 +526,47 @@ export class AppInsightsCore im _perfManager = perfMgr; }; - // _self.getStatsBeat = (statsBeatState: IStatsBeatState) => { - // // create a new statsbeat if not initialize yet or the endpoint is different - // // otherwise, return the existing one, or null - - // if (statsBeatState) { - // if (_statsMgr && _statsMgr.enabled) { - // if (_statsBeat && _statsBeat.endpoint !== statsBeatState.endpoint) { - // // Different endpoint, so unload the existing and create a new one - // _statsBeat.enabled = false; - // _statsBeat = null; - // } - - // if (!_statsBeat) { - // // Create a new statsbeat instance - // _statsBeat = _statsMgr.newInst(statsBeatState); - // } - // } else if (_statsBeat) { - // // Disable and remove any previously created statsbeat instance - // _statsBeat.enabled = false; - // _statsBeat = null; - // } - - // // Return the current statsbeat instance or null if not created - // return _statsBeat; - // } - - // // Return null as no statsbeat state was provided - // return null; - // }; - - // _self.setStatsMgr = (statsMgr: IStatsMgr) => { - // if (_statsMgr && _statsMgr !== statsMgr) { - // // Disable any previously created statsbeat instance - // if (_statsBeat) { - // _statsBeat.enabled = false; - // _statsBeat = null; - // } - // } - - // _statsMgr = statsMgr; - // }; + _self.getSdkStats = (statsBeatState: ISdkStatsState) => { + // create a new statsbeat if not initialize yet or the endpoint is different + // otherwise, return the existing one, or null + + if (statsBeatState) { + if (_statsMgr && _statsMgr.enabled) { + if (_statsBeat && _statsBeat.endpoint !== statsBeatState.endpoint) { + // Different endpoint, so unload the existing and create a new one + _statsBeat.enabled = false; + _statsBeat = null; + } + + if (!_statsBeat) { + // Create a new statsbeat instance + _statsBeat = _statsMgr.newInst(statsBeatState); + } + } else if (_statsBeat) { + // Disable and remove any previously created statsbeat instance + _statsBeat.enabled = false; + _statsBeat = null; + } + + // Return the current statsbeat instance or null if not created + return _statsBeat; + } + + // Return null as no statsbeat state was provided + return null; + }; + + _self.setStatsMgr = (statsMgr: IStatsMgr) => { + if (_statsMgr && _statsMgr !== statsMgr) { + // Disable any previously created statsbeat instance + if (_statsBeat) { + _statsBeat.enabled = false; + _statsBeat = null; + } + } + + _statsMgr = statsMgr; + }; _self.eventCnt = (): number => { return _eventQueue.length; @@ -810,11 +811,11 @@ export class AppInsightsCore im let processUnloadCtx = createProcessTelemetryUnloadContext(_getPluginChain(), _self); processUnloadCtx.onComplete(() => { - // if (_statsBeat) { - // // Disable any statsbeat instance - // _statsBeat.enabled = false; - // _statsBeat = null; - // } + if (_statsBeat) { + // Disable any statsbeat instance + _statsBeat.enabled = false; + _statsBeat = null; + } _hookContainer.run(_self.logger); @@ -1087,7 +1088,7 @@ export class AppInsightsCore im runTargetUnload(_notificationManager, false); _notificationManager = null; _perfManager = null; - // _statsBeat = null; + _statsBeat = null; _cfgPerfManager = null; runTargetUnload(_cookieManager, false); _cookieManager = null; @@ -1114,11 +1115,11 @@ export class AppInsightsCore im _initInMemoMaxSize = null; _isStatusSet = false; _initTimer = null; - // if (_statsBeat) { - // // Unload and disable any statsbeat instance - // _statsBeat.enabled = false; - // } - // _statsBeat = null; + if (_statsBeat) { + // Unload and disable any statsbeat instance + _statsBeat.enabled = false; + } + _statsBeat = null; } function _createTelCtx(): IProcessTelemetryContext { @@ -1505,14 +1506,14 @@ export class AppInsightsCore im return null; } - // public getStatsBeat(statsBeatState: IStatsBeatState): IStatsBeat { - // // @ DynamicProtoStub -- DO NOT add any code as this will be removed during packaging - // return null; - // } + public getSdkStats(statsBeatState: ISdkStatsState): ISdkStats { + // @DynamicProtoStub -- DO NOT add any code as this will be removed during packaging + return null; + } - // public setStatsMgr(statsMgr?: IStatsMgr): void { - // // @ DynamicProtoStub -- DO NOT add any code as this will be removed during packaging - // } + public setStatsMgr(statsMgr?: IStatsMgr): void { + // @DynamicProtoStub -- DO NOT add any code as this will be removed during packaging + } public setPerfMgr(perfMgr: IPerfManager) { // @DynamicProtoStub -- DO NOT add any code as this will be removed during packaging diff --git a/shared/AppInsightsCore/src/JavaScriptSDK/StatsBeat.ts b/shared/AppInsightsCore/src/JavaScriptSDK/SdkStats.ts similarity index 96% rename from shared/AppInsightsCore/src/JavaScriptSDK/StatsBeat.ts rename to shared/AppInsightsCore/src/JavaScriptSDK/SdkStats.ts index 1834903dc..6d8690881 100644 --- a/shared/AppInsightsCore/src/JavaScriptSDK/StatsBeat.ts +++ b/shared/AppInsightsCore/src/JavaScriptSDK/SdkStats.ts @@ -10,7 +10,7 @@ import { eStatsType } from "../JavaScriptSDK.Enums/StatsType"; import { IAppInsightsCore } from "../JavaScriptSDK.Interfaces/IAppInsightsCore"; import { IConfiguration } from "../JavaScriptSDK.Interfaces/IConfiguration"; import { INetworkStatsbeat } from "../JavaScriptSDK.Interfaces/INetworkStatsbeat"; -import { IStatsBeat, IStatsBeatConfig, IStatsBeatState, IStatsEndpointConfig } from "../JavaScriptSDK.Interfaces/IStatsBeat"; +import { ISdkStats, ISdkStatsState, IStatsBeatConfig, IStatsEndpointConfig } from "../JavaScriptSDK.Interfaces/ISdkStats"; import { IStatsMgr, IStatsMgrConfig } from "../JavaScriptSDK.Interfaces/IStatsMgr"; import { ITelemetryItem } from "../JavaScriptSDK.Interfaces/ITelemetryItem"; import { IPayloadData } from "../JavaScriptSDK.Interfaces/IXHROverride"; @@ -46,7 +46,7 @@ interface _IMgrCallbacks { * @param statsbeatEvent - The statsbeat event to send to the core * @param endpoint - The endpoint to send the event to */ - track: (statsBeat: IStatsBeat, statsbeatEvent: ITelemetryItem) => void; + track: (statsBeat: ISdkStats, statsbeatEvent: ITelemetryItem) => void; } /** @@ -102,7 +102,7 @@ function _createNetworkStatsbeat(host: string): INetworkStatsbeat { * @param statsBeatStats - The statsbeat state to use for the IStatsBeat instance. * @returns A new IStatsBeat instance. */ -function _createStatsBeat(mgr: _IMgrCallbacks, statsBeatStats: IStatsBeatState): IStatsBeat { +function _createStatsBeat(mgr: _IMgrCallbacks, statsBeatStats: ISdkStatsState): ISdkStats { let _networkCounter: INetworkStatsbeat = _createNetworkStatsbeat(statsBeatStats.endpoint); let _timeoutHandle: ITimerHandler; // Handle to the timer for sending telemetry. This way, we would not send telemetry when system sleep. let _isEnabled: boolean = true; // Flag to check if statsbeat is enabled or not @@ -241,15 +241,15 @@ function _createStatsBeat(mgr: _IMgrCallbacks, statsBeatStats: IStatsBeatState): } // THE statsbeat instance being created and returned - let statsBeat: IStatsBeat = { + let statsBeat: ISdkStats = { enabled: !!_isEnabled, endpoint: STR_EMPTY, type: eStatsType.SDK, count: (status: number, payloadData: IPayloadData, endpoint: string) => { if (_isEnabled && _checkEndpoint(endpoint)) { - if (payloadData && (payloadData as any)["statsBeatData"] && (payloadData as any)["statsBeatData"]["startTime"]) { + if (payloadData && (payloadData as any)["sdkStatsData"] && (payloadData as any)["sdkStatsData"]["startTime"]) { _networkCounter.totalRequest = (_networkCounter.totalRequest || 0) + 1; - _networkCounter.requestDuration += utcNow() - (payloadData as any)["statsBeatData"]["startTime"]; + _networkCounter.requestDuration += utcNow() - (payloadData as any)["sdkStatsData"]["startTime"]; } let retryArray = [401, 403, 408, 429, 500, 502, 503, 504]; @@ -371,7 +371,7 @@ export function createStatsMgr(): IStatsMgr { } } - function _track(statsBeat: IStatsBeat, statsBeatEvent: ITelemetryItem) { + function _track(statsBeat: ISdkStats, statsBeatEvent: ITelemetryItem) { if (_isMgrEnabled && _statsBeatConfig) { let endpoint = statsBeat.endpoint; let sendEvt = !!statsBeat.type; @@ -397,8 +397,8 @@ export function createStatsMgr(): IStatsMgr { } } - function _createInstance(state: IStatsBeatState): IStatsBeat { - let instance: IStatsBeat = null; + function _createInstance(state: ISdkStatsState): ISdkStats { + let instance: ISdkStats = null; if (_isMgrEnabled) { let callbacks: _IMgrCallbacks = { diff --git a/shared/AppInsightsCore/src/applicationinsights-core-js.ts b/shared/AppInsightsCore/src/applicationinsights-core-js.ts index 12a4be475..df4903489 100644 --- a/shared/AppInsightsCore/src/applicationinsights-core-js.ts +++ b/shared/AppInsightsCore/src/applicationinsights-core-js.ts @@ -17,7 +17,7 @@ export { IPayloadData, SendPOSTFunction, IXHROverride, OnCompleteCallback } from export { IUnloadHook, ILegacyUnloadHook } from "./JavaScriptSDK.Interfaces/IUnloadHook"; export { eEventsDiscardedReason, EventsDiscardedReason, eBatchDiscardedReason, BatchDiscardedReason } from "./JavaScriptSDK.Enums/EventsDiscardedReason"; export { SendRequestReason, TransportType } from "./JavaScriptSDK.Enums/SendRequestReason"; -//export { StatsType, eStatsType } from "./JavaScriptSDK.Enums/StatsType"; +export { StatsType, eStatsType } from "./JavaScriptSDK.Enums/StatsType"; export { TelemetryUpdateReason } from "./JavaScriptSDK.Enums/TelemetryUpdateReason"; export { TelemetryUnloadReason } from "./JavaScriptSDK.Enums/TelemetryUnloadReason"; export { eActiveStatus, ActiveStatus } from "./JavaScriptSDK.Enums/InitActiveStatusEnum" @@ -37,10 +37,10 @@ export { parseResponse } from "./JavaScriptSDK/ResponseHelpers"; export { IXDomainRequest, IBackendResponse } from "./JavaScriptSDK.Interfaces/IXDomainRequest"; export { _ISenderOnComplete, _ISendPostMgrConfig, _ITimeoutOverrideWrapper, _IInternalXhrOverride } from "./JavaScriptSDK.Interfaces/ISenderPostManager"; export { SenderPostManager } from "./JavaScriptSDK/SenderPostManager"; -//export { IStatsBeat, IStatsBeatConfig, IStatsBeatKeyMap as IStatsBeatEndpoints, IStatsBeatState} from "./JavaScriptSDK.Interfaces/IStatsBeat"; -//export { IStatsEventData } from "./JavaScriptSDK.Interfaces/IStatsEventData"; -//export { IStatsMgr, IStatsMgrConfig } from "./JavaScriptSDK.Interfaces/IStatsMgr"; -//export { createStatsMgr } from "./JavaScriptSDK/StatsBeat"; +export { ISdkStats, IStatsBeatConfig, IStatsBeatKeyMap, ISdkStatsState } from "./JavaScriptSDK.Interfaces/ISdkStats"; +export { IStatsEventData } from "./JavaScriptSDK.Interfaces/IStatsEventData"; +export { IStatsMgr, IStatsMgrConfig } from "./JavaScriptSDK.Interfaces/IStatsMgr"; +export { createStatsMgr } from "./JavaScriptSDK/SdkStats"; export { isArray, isTypeof, isUndefined, isNullOrUndefined, objHasOwnProperty as hasOwnProperty, isObject, isFunction, strEndsWith, strStartsWith, isDate, isError, isString, isNumber, isBoolean, arrForEach, arrIndexOf,