Skip to content

Commit

Permalink
[Main][Task]27080650: Initialization Should Handle Offline Support De…
Browse files Browse the repository at this point in the history
…pendency (#2358)

* expose send next batch

* update

* channel init

* update

* update

* update

* update

* update

* update

* update

* udpate

* update

* update

* update
  • Loading branch information
Karlie-777 committed Jun 8, 2024
1 parent 04e33d6 commit 46ce907
Show file tree
Hide file tree
Showing 13 changed files with 536 additions and 116 deletions.
164 changes: 164 additions & 0 deletions AISKU/Tests/Unit/src/applicationinsights.e2e.tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { IDependencyTelemetry, ContextTagKeys, Event, Trace, Exception, Metric,
import { ITelemetryItem, getGlobal, newId, dumpObj, BaseTelemetryPlugin, IProcessTelemetryContext, __getRegisteredEvents, arrForEach, IConfiguration, FeatureOptInMode } from "@microsoft/applicationinsights-core-js";
import { TelemetryContext } from '@microsoft/applicationinsights-properties-js';
import { CONFIG_ENDPOINT_URL } from '../../../src/InternalConstants';
import { OfflineChannel } from '@microsoft/applicationinsights-offlinechannel-js';


export class ApplicationInsightsTests extends AITestClass {
Expand Down Expand Up @@ -44,6 +45,7 @@ export class ApplicationInsightsTests extends AITestClass {
private tagKeys = new ContextTagKeys();
private _config;
private _appId: string;
private _ctx: any;

constructor(testName?: string) {
super(testName || "ApplicationInsightsTests");
Expand Down Expand Up @@ -79,6 +81,7 @@ export class ApplicationInsightsTests extends AITestClass {
this.isFetchPolyfill = fetch["polyfill"];
this.useFakeServer = false;
this._config = this._getTestConfig(this._sessionPrefix);
this._ctx = {};

const init = new ApplicationInsights({
config: this._config
Expand Down Expand Up @@ -285,8 +288,123 @@ export class ApplicationInsightsTests extends AITestClass {
extConfig = ai.config.extensionConfig["AppInsightsCfgSyncPlugin"];
Assert.equal(extConfig.scheduleFetchTimeout, expectedTimeout, "timeout should be changes dynamically");
ai.unload(false);
if (ai && ai["dependencies"]) {
ai["dependencies"].teardown();
}
}
});

this.testCase({
name: "CfgSync DynamicConfigTests: Offline Support can be added and initialized with endpoint url",
useFakeTimers: true,
test: () => {
this.clock.tick(1);
// if fake timer is turned on, session data will return 0 and will throw sesson not renew error
let offlineChannel = new OfflineChannel();
let config = {
instrumentationKey: "testIKey",
endpointUrl: "testUrl",
extensionConfig:{
["AppInsightsCfgSyncPlugin"]: {
cfgUrl: ""
}

},
extensions:[offlineChannel]
} as IConfiguration & IConfig;
let ai = new ApplicationInsights({config: config});
ai.loadAppInsights();
this.clock.tick(1);

let sendChannel = ai.getPlugin(BreezeChannelIdentifier);
let offlineChannelPlugin = ai.getPlugin("OfflineChannel").plugin;
Assert.equal(sendChannel.plugin.isInitialized(), true, "sender is initialized");
Assert.equal(offlineChannelPlugin.isInitialized(), true, "offline channel is initialized");
let urlConfig = offlineChannelPlugin["_getDbgPlgTargets"]()[0];
Assert.ok(urlConfig, "offline url config is initialized");

ai.unload(false);
if (ai && ai["dependencies"]) {
ai["dependencies"].teardown();
}
offlineChannel.teardown();

}
});

this.testCase({
name: "CfgSync DynamicConfigTests: Offline Support can be added and initialized with channels",
useFakeTimers: true,
test: () => {
this.clock.tick(1);
let offlineChannel = new OfflineChannel();
let config = {
instrumentationKey: "testIKey",
endpointUrl: "testUrl",
extensionConfig:{
["AppInsightsCfgSyncPlugin"]: {
cfgUrl: ""
}

},
channels:[[offlineChannel]]
} as IConfiguration & IConfig;
let ai = new ApplicationInsights({config: config});
ai.loadAppInsights();
this.clock.tick(1);

let sendChannel = ai.getPlugin(BreezeChannelIdentifier);
let offlineChannelPlugin = ai.getPlugin("OfflineChannel").plugin;
Assert.equal(sendChannel.plugin.isInitialized(), true, "sender is initialized");
Assert.equal(offlineChannelPlugin.isInitialized(), true, "offline channel is initialized");
let urlConfig = offlineChannelPlugin["_getDbgPlgTargets"]()[0];
Assert.ok(urlConfig, "offline url config is initialized");


ai.unload(false);
if (ai && ai["dependencies"]) {
ai["dependencies"].teardown();
}
offlineChannel.teardown();

}
});

this.testCase({
name: "CfgSync DynamicConfigTests: Offline Support can be added and initialized without endpoint url",
useFakeTimers: true,
test: () => {
this.clock.tick(1);
let offlineChannel = new OfflineChannel();
let config = {
instrumentationKey: "testIKey",
extensionConfig:{
["AppInsightsCfgSyncPlugin"]: {
cfgUrl: ""
}

},
channels:[[offlineChannel]]
} as IConfiguration & IConfig;
let ai = new ApplicationInsights({config: config});
ai.loadAppInsights();
this.clock.tick(1);

let sendChannel = ai.getPlugin(BreezeChannelIdentifier);
let offlineChannelPlugin = ai.getPlugin("OfflineChannel").plugin;
Assert.equal(sendChannel.plugin.isInitialized(), true, "sender is initialized");
Assert.equal(offlineChannelPlugin.isInitialized(), true, "offline channel is initialized");
let urlConfig = offlineChannelPlugin["_getDbgPlgTargets"]()[0];
Assert.ok(urlConfig, "offline url config is initialized");

ai.unload(false);
if (ai && ai["dependencies"]) {
ai["dependencies"].teardown();
}
offlineChannel.teardown();
}
});

}

public addCDNOverrideTests(): void {
Expand Down Expand Up @@ -318,6 +436,52 @@ export class ApplicationInsightsTests extends AITestClass {
}

public addAsyncTests(): void {
this.testCaseAsync({
name: "E2E.GenericTests: Send events with offline support",
stepDelay: 1,
steps: [() => {
let offlineChannel = new OfflineChannel();
this._ai.addPlugin(offlineChannel);
this._ctx.offlineChannel = offlineChannel;

}].concat(PollingAssert.createPollingAssert(() => {
let offlineChannel = this._ctx.offlineChannel;
if (offlineChannel && offlineChannel.isInitialized()) {
let urlConfig = offlineChannel["_getDbgPlgTargets"]()[0];
Assert.ok(urlConfig, "offline url config is initialized");

let offlineListener = offlineChannel.getOfflineListener() as any;
Assert.ok(offlineListener, "offlineListener should be initialized");

// online
offlineListener.setOnlineState(1);
let inMemoTimer = offlineChannel["_getDbgPlgTargets"]()[3];
Assert.ok(!inMemoTimer, "offline in memo timer should be null");
this._ai.trackEvent({ name: "online event", properties: { "prop1": "value1" }, measurements: { "measurement1": 200 } });

// set to offline status right way
offlineListener.setOnlineState(2);
this._ai.trackEvent({ name: "offline event", properties: { "prop2": "value2" }, measurements: { "measurement2": 200 } });
inMemoTimer = offlineChannel["_getDbgPlgTargets"]()[3];
Assert.ok(inMemoTimer, "in memo timer should not be null");
let inMemoBatch = offlineChannel["_getDbgPlgTargets"]()[1];
Assert.equal(inMemoBatch && inMemoBatch.count(), 1, "should have one event");

return true
}
return false
}, "Wait for init" + new Date().toISOString(), 60, 1000) as any).concat(this.asserts(1)).concat(() => {
const payloadStr: string[] = this.getPayloadMessages(this.successSpy);
if (payloadStr.length > 0) {
const payload = JSON.parse(payloadStr[0]);
const data = payload.data;
Assert.ok( payload && payload.iKey);
Assert.equal( ApplicationInsightsTests._instrumentationKey,payload.iKey,"payload ikey is not set correctly" );
Assert.ok(data && data.baseData && data.baseData.properties["prop1"]);
Assert.ok(data && data.baseData && data.baseData.measurements["measurement1"]);
}
})
});
this.testCaseAsync({
name: 'E2E.GenericTests: trackEvent sends to backend',
stepDelay: 1,
Expand Down
1 change: 1 addition & 0 deletions AISKU/Tests/UnitTests.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
modules.add("qunit");
modules.add("sinon");
modules.add("pako","./node_modules/pako/dist/pako");
modules.add("@microsoft/applicationinsights-offlinechannel-js"); // Load offline plugin

loadFetchModule(modules, "whatwg-fetch");
loadCommonModules(modules, function () {
Expand Down
1 change: 1 addition & 0 deletions AISKU/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
},
"devDependencies": {
"@microsoft/ai-test-framework": "0.0.1",
"@microsoft/applicationinsights-offlinechannel-js": "0.2.1",
"@microsoft/applicationinsights-rollup-plugin-uglify3-js": "1.0.0",
"@microsoft/applicationinsights-rollup-es5": "1.0.2",
"sinon": "^7.3.1",
Expand Down
3 changes: 3 additions & 0 deletions channels/offline-channel-js/Tests/Unit/src/TestHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ export class TestChannel extends BaseTelemetryPlugin implements IChannelControls
public priority: number = 1001;
public endpoint: string = DEFAULT_BREEZE_ENDPOINT + DEFAULT_BREEZE_PATH;
public isIdle: boolean = true;
public isInitialized = () => {
return true;
}

lastEventAdded: ITelemetryItem;
eventsAdded: ITelemetryItem[] = [];
Expand Down
23 changes: 20 additions & 3 deletions channels/offline-channel-js/Tests/Unit/src/channel.tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,11 @@ export class ChannelTests extends AITestClass {

this.testCase({
name: "Channel: Init from core",
useFakeTimers: true,
test: () => {
let channel = new OfflineChannel();
this.core.initialize(this.coreConfig,[channel]);
let onlineChannel = new TestChannel();
this.core.initialize(this.coreConfig,[channel, onlineChannel]);
this.core.addNotificationListener({
eventsDiscarded: (evts, reason) => {
this.evtDiscard += 1;
Expand All @@ -79,6 +81,8 @@ export class ChannelTests extends AITestClass {
}
});

this.clock.tick(1);

let offlineListener = channel.getOfflineListener() as any;
offlineListener.setOnlineState(1);
let evt = mockTelemetryItem();
Expand All @@ -99,10 +103,12 @@ export class ChannelTests extends AITestClass {

this.testCase({
name: "Channel: Init from core indexed db",
useFakeTimers: true,
test: () => {
let channel = new OfflineChannel();
let onlineChannel = new TestChannel();
this.coreConfig.extensionConfig = {["OfflineChannel"]: {providers:[eStorageProviders.IndexedDb], inMemoMaxTime: 2000} as IOfflineChannelConfiguration};
this.core.initialize(this.coreConfig,[channel]);
this.core.initialize(this.coreConfig,[channel, onlineChannel]);
this.core.addNotificationListener({
eventsDiscarded: (evts, reason) => {
this.evtDiscard += 1;
Expand All @@ -118,7 +124,7 @@ export class ChannelTests extends AITestClass {
this.batchDrop += 1;
}
});

this.clock.tick(1);
let offlineListener = channel.getOfflineListener() as any;
offlineListener.setOnlineState(1);
let evt = mockTelemetryItem();
Expand Down Expand Up @@ -171,6 +177,8 @@ export class ChannelTests extends AITestClass {
this.onDone(() => {
channel.teardown();
});

this.clock.tick(1);
let offlineListener = channel.getOfflineListener() as any;
offlineListener.setOnlineState(1);
let evt = mockTelemetryItem();
Expand Down Expand Up @@ -295,6 +303,8 @@ export class ChannelTests extends AITestClass {
let channel = new OfflineChannel();
this.coreConfig.extensionConfig = {["OfflineChannel"]: {providers:[eStorageProviders.IndexedDb], inMemoMaxTime: 2000} as IOfflineChannelConfiguration};
channel.initialize(this.coreConfig, this.core,[]);

this.clock.tick(1);
let senderInst = channel["_getDbgPlgTargets"]()[2];
let sender1 = (payload: any, oncomplete: any, sync?: boolean) => {
oncomplete(200, {});
Expand Down Expand Up @@ -392,6 +402,8 @@ export class ChannelTests extends AITestClass {
}
});

this.clock.tick(1);

Assert.equal(this.evtDiscard, 0, "discard listener notification should not be called");
Assert.equal(this.evtStore, 0, "store listener notification should not be called");
Assert.equal(this.evtSent, 0, "sent listener notification should not be called");
Expand Down Expand Up @@ -460,6 +472,8 @@ export class ChannelTests extends AITestClass {
this.batchDrop += 1;
}
});

this.clock.tick(1);
let inMemoBatch = channel["_getDbgPlgTargets"]()[1];
this.sandbox.stub((inMemoBatch) as any, "addEvent").callsFake((evt) => {
return false;
Expand Down Expand Up @@ -517,6 +531,7 @@ export class ChannelTests extends AITestClass {
this.batchDrop += 1;
}
});
this.clock.tick(1);

let senderInst = channel["_getDbgPlgTargets"]()[2];
let sender1 = (payload: any, oncomplete: any, sync?: boolean) => {
Expand Down Expand Up @@ -581,6 +596,8 @@ export class ChannelTests extends AITestClass {
// make sure in memo time is long enough
this.coreConfig.extensionConfig = {["OfflineChannel"]: {providers:[eStorageProviders.LocalStorage], inMemoMaxTime: 200000} as IOfflineChannelConfiguration};
this.core.initialize(this.coreConfig,[channel, sendChannel]);

this.clock.tick(1);

let offlineListener = channel.getOfflineListener() as any;
offlineListener.setOnlineState(2);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export function runTests() {
new OfflineWebProviderTests().registerTests();
new OfflineDbProviderTests().registerTests();
new OfflineInMemoryBatchTests().registerTests();
new OfflineBatchHandlerTests().registerTests();
new ChannelTests().registerTests();
new OfflineBatchHandlerTests().registerTests();
new Offlinetimer().registerTests();
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ export class Offlinetimer extends AITestClass {
let channel = new OfflineChannel();
let onlineChannel = new TestChannel();
this.core.initialize(this.coreConfig,[channel, onlineChannel]);

this.clock.tick(1);
let offlineListener = channel.getOfflineListener() as any;

// online, processTelemetry is not called
Expand Down Expand Up @@ -153,6 +155,7 @@ export class Offlinetimer extends AITestClass {
let channel = new OfflineChannel();
let onlineChannel = new TestChannel();
this.core.initialize(this.coreConfig,[channel, onlineChannel]);
this.clock.tick(1);
let offlineListener = channel.getOfflineListener() as any;

// online, processTelemetry is not called
Expand Down
3 changes: 2 additions & 1 deletion channels/offline-channel-js/src/Helpers/Utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { EventPersistence } from "@microsoft/applicationinsights-common";
import { INotificationManager, ITelemetryItem, NotificationManager, generateW3CId } from "@microsoft/applicationinsights-core-js";
import { isString, objKeys, strSubstr } from "@nevware21/ts-utils";
import { isValidPersistenceLevel } from "../Providers/IndexDbProvider";
import { IPostTransmissionTelemetryItem } from "../applicationinsights-offlinechannel-js";
import { IPostTransmissionTelemetryItem } from "../Interfaces/IInMemoryBatch";


// Endpoint schema
// <prefix>.<suffix>
Expand Down
Loading

0 comments on commit 46ce907

Please sign in to comment.