diff --git a/AISKU/Tests/Unit/src/IAnalyticsConfig.Tests.ts b/AISKU/Tests/Unit/src/IAnalyticsConfig.Tests.ts new file mode 100644 index 000000000..d9bd8c6f5 --- /dev/null +++ b/AISKU/Tests/Unit/src/IAnalyticsConfig.Tests.ts @@ -0,0 +1,105 @@ +import { ApplicationInsights, IAnalyticsConfig, IAppInsights, IConfig, ApplicationAnalytics } from "../../../src/applicationinsights-web"; +import { AITestClass, Assert } from "@microsoft/ai-test-framework"; +import { AnalyticsPluginIdentifier, utlRemoveSessionStorage } from "@microsoft/applicationinsights-common"; +import { AppInsightsCore, IConfiguration, isFunction, onConfigChange } from "@microsoft/applicationinsights-core-js"; +import { Sender } from "@microsoft/applicationinsights-channel-js"; + +const TestInstrumentationKey = 'b7170927-2d1c-44f1-acec-59f4e1751c11'; + +export class IAnalyticsConfigTests extends AITestClass { + + public testInitialize() { + this._disableDynProtoBaseFuncs(); + } + + public testCleanup() { + // Clean up session storage + utlRemoveSessionStorage(null as any, "AI_sentBuffer"); + utlRemoveSessionStorage(null as any, "AI_buffer"); + } + + public registerTests() { + + this.testCase({ + name: "IAnalyticsConfig: Interface compatibility with existing functionality", + test: () => { + // Test that the interface doesn't break existing functionality + // Use root configuration (IConfiguration) for ApplicationInsights initialization + const init = new ApplicationInsights({ + config: { + instrumentationKey: TestInstrumentationKey + } + }); + this.onDone(() => { + if (init && init.unload) { + init.unload(false); + } + }); + init.loadAppInsights(); + + // These should work as before + Assert.ok(isFunction(init.trackEvent), "trackEvent should be available"); + Assert.ok(isFunction(init.trackPageView), "trackPageView should be available"); + Assert.ok(isFunction(init.trackException), "trackException should be available"); + Assert.ok(isFunction(init.trackTrace), "trackTrace should be available"); + Assert.ok(isFunction(init.trackMetric), "trackMetric should be available"); + Assert.ok(isFunction(init.trackDependencyData), "trackDependencyData should be available"); + } + }); + + this.testCase({ + name: "IAnalyticsConfig: onConfigChange integration test", + useFakeTimers: true, + test: () => { + let theConfig: IConfiguration & IConfig = { + instrumentationKey: TestInstrumentationKey, + samplingPercentage: 50 + }; + + const core = new AppInsightsCore(); + const init = new ApplicationInsights({ + config: theConfig + }); + this.onDone(() => { + if (init && init.unload) { + init.unload(false); + } + }); + + init.loadAppInsights(); + let onChangeCalled = 0; + let expectedSamplingPercentage = 50; + + let handler = onConfigChange(theConfig, (details) => { + onChangeCalled++; + Assert.equal(TestInstrumentationKey, details.cfg.instrumentationKey, "Expect the iKey to be set"); + if (details.cfg.samplingPercentage !== undefined) { + Assert.equal(expectedSamplingPercentage, details.cfg.samplingPercentage, "Expect the sampling percentage to be set"); + } + }); + + // Initial call should happen + Assert.equal(1, onChangeCalled, "OnCfgChange was called exactly once initially"); + let initialCallCount = onChangeCalled; + + // Change a config value + expectedSamplingPercentage = 75; + (theConfig as any).samplingPercentage = expectedSamplingPercentage; + + // Wait for the change to propagate + this.clock.tick(1); + Assert.ok(onChangeCalled > initialCallCount, "Expected the onChanged was called when config changed"); + + // Remove the handler + handler.rm(); + let callCountBeforeRemoval = onChangeCalled; + + expectedSamplingPercentage = 25; + (theConfig as any).samplingPercentage = expectedSamplingPercentage; + + this.clock.tick(1); + Assert.equal(callCountBeforeRemoval, onChangeCalled, "Expected the onChanged was not called after handler removal"); + } + }); + } +} \ No newline at end of file diff --git a/AISKU/Tests/Unit/src/aiskuunittests.ts b/AISKU/Tests/Unit/src/aiskuunittests.ts index 39a168f4e..8a53729b0 100644 --- a/AISKU/Tests/Unit/src/aiskuunittests.ts +++ b/AISKU/Tests/Unit/src/aiskuunittests.ts @@ -9,6 +9,7 @@ import { SenderE2ETests } from './sender.e2e.tests'; import { SnippetInitializationTests } from './SnippetInitialization.Tests'; import { CdnThrottle} from "./CdnThrottle.tests"; import { ThrottleSentMessage } from "./ThrottleSentMessage.tests"; +import { IAnalyticsConfigTests } from './IAnalyticsConfig.Tests'; export function runTests() { new GlobalTestHooks().registerTests(); @@ -23,4 +24,5 @@ export function runTests() { new SnippetInitializationTests(true).registerTests(); new ThrottleSentMessage().registerTests(); new CdnThrottle().registerTests(); + new IAnalyticsConfigTests().registerTests(); } \ No newline at end of file diff --git a/AISKU/src/applicationinsights-web.ts b/AISKU/src/applicationinsights-web.ts index b26f55015..0ad8ca2b3 100644 --- a/AISKU/src/applicationinsights-web.ts +++ b/AISKU/src/applicationinsights-web.ts @@ -57,7 +57,7 @@ export { EventPersistence } from "@microsoft/applicationinsights-common"; export { Sender, ISenderConfig } from "@microsoft/applicationinsights-channel-js"; -export { ApplicationInsights as ApplicationAnalytics, IAppInsightsInternal } from "@microsoft/applicationinsights-analytics-js"; +export { ApplicationInsights as ApplicationAnalytics, IAppInsightsInternal, IAnalyticsConfig } from "@microsoft/applicationinsights-analytics-js"; export { PropertiesPlugin } from "@microsoft/applicationinsights-properties-js"; export { AjaxPlugin as DependenciesPlugin, IDependenciesPlugin, diff --git a/extensions/applicationinsights-analytics-js/src/JavaScriptSDK/AnalyticsPlugin.ts b/extensions/applicationinsights-analytics-js/src/JavaScriptSDK/AnalyticsPlugin.ts index ba1685fbe..7d03423d7 100644 --- a/extensions/applicationinsights-analytics-js/src/JavaScriptSDK/AnalyticsPlugin.ts +++ b/extensions/applicationinsights-analytics-js/src/JavaScriptSDK/AnalyticsPlugin.ts @@ -23,6 +23,7 @@ import { } from "@microsoft/applicationinsights-core-js"; import { PropertiesPlugin } from "@microsoft/applicationinsights-properties-js"; import { isArray, isError, objDeepFreeze, objDefine, scheduleTimeout, strIndexOf } from "@nevware21/ts-utils"; +import { IAnalyticsConfig } from "./Interfaces/IAnalyticsConfig"; import { IAppInsightsInternal, PageViewManager } from "./Telemetry/PageViewManager"; import { PageViewPerformanceManager } from "./Telemetry/PageViewPerformanceManager"; import { PageVisitTimeManager } from "./Telemetry/PageVisitTimeManager"; @@ -52,7 +53,7 @@ function _getReason(error: any) { const MinMilliSeconds = 60000; -const defaultValues: IConfigDefaults = objDeepFreeze({ +const defaultValues: IConfigDefaults = objDeepFreeze({ sessionRenewalMs: cfgDfSet(_chkConfigMilliseconds, 30 * 60 * 1000), sessionExpirationMs: cfgDfSet(_chkConfigMilliseconds, 24 * 60 * 60 * 1000), disableExceptionTracking: cfgDfBoolean(), @@ -84,7 +85,7 @@ function _chkSampling(value: number) { return !isNaN(value) && value > 0 && value <= 100; } -function _updateStorageUsage(extConfig: IConfig) { +function _updateStorageUsage(extConfig: IAnalyticsConfig) { // Not resetting the storage usage as someone may have manually called utlDisableStorage, so this will only // reset based if the configuration option is provided if (!isUndefined(extConfig.isStorageUseDisabled)) { @@ -121,7 +122,7 @@ export class AnalyticsPlugin extends BaseTelemetryPlugin implements IAppInsights let _autoExceptionInstrumented: boolean; let _enableUnhandledPromiseRejectionTracking: boolean; let _autoUnhandledPromiseInstrumented: boolean; - let _extConfig: IConfig & IConfiguration; + let _extConfig: IAnalyticsConfig; let _autoTrackPageVisitTime: boolean; let _expCfg: IExceptionConfig; diff --git a/extensions/applicationinsights-analytics-js/src/JavaScriptSDK/Interfaces/IAnalyticsConfig.ts b/extensions/applicationinsights-analytics-js/src/JavaScriptSDK/Interfaces/IAnalyticsConfig.ts new file mode 100644 index 000000000..e0c00ef29 --- /dev/null +++ b/extensions/applicationinsights-analytics-js/src/JavaScriptSDK/Interfaces/IAnalyticsConfig.ts @@ -0,0 +1,119 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { IExceptionConfig } from "@microsoft/applicationinsights-core-js"; + +/** + * Configuration interface specifically for AnalyticsPlugin + * This interface defines only the configuration properties that the Analytics plugin uses. + */ +export interface IAnalyticsConfig { + /** + * A session is logged if the user is inactive for this amount of time in milliseconds. + * @default 1800000 (30 minutes) + */ + sessionRenewalMs?: number; + + /** + * A session is logged if it has continued for this amount of time in milliseconds. + * @default 86400000 (24 hours) + */ + sessionExpirationMs?: number; + + /** + * If true, exceptions are not autocollected. + * @default false + */ + disableExceptionTracking?: boolean; + + /** + * If true, on a pageview, the previous instrumented page's view time is tracked and sent as telemetry and a new timer is started for the current pageview. + * @default false + */ + autoTrackPageVisitTime?: boolean; + + /** + * If true, default behavior of trackPageView is changed to record end of page view duration interval when trackPageView is called. + * @default false + */ + overridePageViewDuration?: boolean; + + /** + * Define whether to track unhandled promise rejections and report as JS errors. + * @default false + */ + enableUnhandledPromiseRejectionTracking?: boolean; + + /** + * Internal flag to track if unhandled promise instrumentation is already set up. + * @default false + * @internal Internal use only + * @ignore INTERNAL ONLY + */ + autoUnhandledPromiseInstrumented?: boolean; + + /** + * Percentage of events that will be sent. Value must be between 0 and 100. + * @default 100 + * @example 50 // Only send 50% of events + */ + samplingPercentage?: number; + + /** + * If true, the SDK will not store or read any data from local and session storage. + * @default false + */ + isStorageUseDisabled?: boolean; + + /** + * If true, the SDK will track all Browser Link requests. + * @default false + */ + isBrowserLinkTrackingEnabled?: boolean; + + /** + * Automatically track route changes in Single Page Applications (SPA). If true, each route change will send a new Pageview to Application Insights. + * @default false + */ + enableAutoRouteTracking?: boolean; + + /** + * An optional value that will be used as name postfix for localStorage and session cookie name. + * @default "" + * @example "MyApp" // Results in localStorage keys like "ai_session_MyApp" + */ + namePrefix?: string; + + /** + * If true, debugging data is thrown as an exception by the logger. + * @default false + */ + enableDebug?: boolean; + + /** + * If true, flush method will not be called when onBeforeUnload event triggers. + * @default false + */ + disableFlushOnBeforeUnload?: boolean; + + /** + * If true, flush method will not be called when onPageHide or onVisibilityChange (hidden state) event(s) trigger. + * @default false + */ + disableFlushOnUnload?: boolean; + + /** + * Internal flag to track if exception instrumentation is already set up. + * @default false + * @internal Internal use only + * @ignore INTERNAL ONLY + */ + autoExceptionInstrumented?: boolean; + + /** + * Exception configuration for additional exception handling options. + * @default { inclScripts: false, expLog: undefined, maxLogs: 50 } + */ + expCfg?: IExceptionConfig; +} + diff --git a/extensions/applicationinsights-analytics-js/src/applicationinsights-analytics-js.ts b/extensions/applicationinsights-analytics-js/src/applicationinsights-analytics-js.ts index 772402c5c..1092f64b7 100644 --- a/extensions/applicationinsights-analytics-js/src/applicationinsights-analytics-js.ts +++ b/extensions/applicationinsights-analytics-js/src/applicationinsights-analytics-js.ts @@ -3,3 +3,4 @@ export { AnalyticsPlugin, AnalyticsPlugin as ApplicationInsights } from "./JavaScriptSDK/AnalyticsPlugin"; export { IAppInsightsInternal } from "./JavaScriptSDK/Telemetry/PageViewManager"; +export { IAnalyticsConfig } from "./JavaScriptSDK/Interfaces/IAnalyticsConfig";