Skip to content

Commit

Permalink
[Main][Task] 29445638: Fix Promise Initialization Sender Config Issue (
Browse files Browse the repository at this point in the history
…#2413)

* handle init promise better

* update
  • Loading branch information
Karlie-777 authored Sep 16, 2024
1 parent 3a1fdb1 commit 8628bba
Show file tree
Hide file tree
Showing 8 changed files with 1,130 additions and 286 deletions.
81 changes: 75 additions & 6 deletions AISKU/Tests/Unit/src/applicationinsights.e2e.tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ export class ApplicationInsightsTests extends AITestClass {
});

this.testCaseAsync({
name: "Init: init with cs promise, change with cs string",
name: "Init: init with cs promise, when it is resolved and then change with cs string",
stepDelay: 100,
useFakeTimers: true,
steps: [() => {
Expand All @@ -273,6 +273,7 @@ export class ApplicationInsightsTests extends AITestClass {
let csPromise = createAsyncResolvedPromise("InstrumentationKey=testIkey;ingestionendpoint=testUrl");
this._config.connectionString = csPromise;
this._config.initTimeOut= 80000;
this._ctx.csPromise = csPromise;


let init = new ApplicationInsights({
Expand All @@ -284,13 +285,79 @@ export class ApplicationInsightsTests extends AITestClass {
let core = this._ai.core;
let status = core.activeStatus && core.activeStatus();
Assert.equal(status, ActiveStatus.PENDING, "status should be set to pending");


}].concat(PollingAssert.createPollingAssert(() => {
let core = this._ai.core
let activeStatus = core.activeStatus && core.activeStatus();
let csPromise = this._ctx.csPromise;
let config = this._ai.config;

if (csPromise.state === "resolved" && activeStatus === ActiveStatus.ACTIVE) {
Assert.equal("testIkey", core.config.instrumentationKey, "ikey should be set");
Assert.equal("testUrl/v2/track", core.config.endpointUrl ,"endpoint shoule be set");

config.connectionString = "InstrumentationKey=testIkey1;ingestionendpoint=testUrl1";
this.clock.tick(1);
let status = core.activeStatus && core.activeStatus();
// promise is not resolved, no new changes applied
Assert.equal(status, ActiveStatus.ACTIVE, "status should be set to active test1");
return true;
}
return false;
}, "Wait for promise response" + new Date().toISOString(), 60, 1000) as any).concat(PollingAssert.createPollingAssert(() => {
let core = this._ai.core
let activeStatus = core.activeStatus && core.activeStatus();

if (activeStatus === ActiveStatus.ACTIVE) {
Assert.equal("testIkey1", core.config.instrumentationKey, "ikey should be set test1");
Assert.equal("testUrl1/v2/track", core.config.endpointUrl ,"endpoint shoule be set test1");
return true;
}
return false;
}, "Wait for new string response" + new Date().toISOString(), 60, 1000) as any)
});

this.testCaseAsync({
name: "Init: init with cs promise and change with cs string at the same time",
stepDelay: 100,
useFakeTimers: true,
steps: [() => {

// unload previous one first
let oriInst = this._ai;
if (oriInst && oriInst.unload) {
// force unload
oriInst.unload(false);
}

if (oriInst && oriInst["dependencies"]) {
oriInst["dependencies"].teardown();
}

this._config = this._getTestConfig(this._sessionPrefix);
let csPromise = createAsyncResolvedPromise("InstrumentationKey=testIkey;ingestionendpoint=testUrl");
this._config.connectionString = csPromise;
this._config.initTimeOut= 80000;
this._ctx.csPromise = csPromise;


let init = new ApplicationInsights({
config: this._config
});
init.loadAppInsights();
this._ai = init;
let config = this._ai.config;
let core = this._ai.core;
let status = core.activeStatus && core.activeStatus();
Assert.equal(status, ActiveStatus.PENDING, "status should be set to pending");

config.connectionString = "InstrumentationKey=testIkey1;ingestionendpoint=testUrl1";
this.clock.tick(1);
status = core.activeStatus && core.activeStatus();
// promise is not resolved, no new changes applied
Assert.equal(status, ActiveStatus.PENDING, "status should be set to pending test1");
Assert.equal(status, ActiveStatus.ACTIVE, "active status should be set to active in next executing cycle");
// Assert.equal(status, ActiveStatus.PENDING, "status should be set to pending test1");



}].concat(PollingAssert.createPollingAssert(() => {
Expand All @@ -306,6 +373,7 @@ export class ApplicationInsightsTests extends AITestClass {
}, "Wait for promise response" + new Date().toISOString(), 60, 1000) as any)
});


this.testCaseAsync({
name: "Init: init with cs promise and offline channel",
stepDelay: 100,
Expand Down Expand Up @@ -345,7 +413,8 @@ export class ApplicationInsightsTests extends AITestClass {
config.connectionString = "InstrumentationKey=testIkey1;ingestionendpoint=testUrl1"
this.clock.tick(1);
status = core.activeStatus && core.activeStatus();
Assert.equal(status, ActiveStatus.PENDING, "status should be set to pending test1");
Assert.equal(status, ActiveStatus.ACTIVE, "active status should be set to active in next executing cycle");
// Assert.equal(status, ActiveStatus.PENDING, "status should be set to pending test1");


}].concat(PollingAssert.createPollingAssert(() => {
Expand Down Expand Up @@ -387,12 +456,12 @@ export class ApplicationInsightsTests extends AITestClass {
Assert.equal(status, ActiveStatus.ACTIVE, "status should be set to active");

let csPromise = createAsyncResolvedPromise("InstrumentationKey=testIkey;ingestionendpoint=testUrl");

config.connectionString = csPromise;
config.initTimeOut = 80000;
this.clock.tick(1);
status = core.activeStatus && core.activeStatus();
Assert.equal(status, ActiveStatus.PENDING, "status should be set to pending");
Assert.equal(status, ActiveStatus.ACTIVE, "active status should be set to active in next executing cycle");
//Assert.equal(status, ActiveStatus.PENDING, "status should be set to pending");


}].concat(PollingAssert.createPollingAssert(() => {
Expand Down
58 changes: 57 additions & 1 deletion AISKU/Tests/Unit/src/sender.e2e.tests.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { ApplicationInsights, IApplicationInsights } from '../../../src/applicationinsights-web'
import { Sender } from '@microsoft/applicationinsights-channel-js';
import { BreezeChannelIdentifier, utlGetSessionStorage, utlRemoveSessionStorage } from '@microsoft/applicationinsights-common';
import { dumpObj, getJSON, isArray } from '@microsoft/applicationinsights-core-js';
import { ActiveStatus, dumpObj, getJSON, isArray } from '@microsoft/applicationinsights-core-js';
import { SinonSpy } from 'sinon';
import { Assert, AITestClass, PollingAssert} from "@microsoft/ai-test-framework"
import { createAsyncResolvedPromise } from '@nevware21/ts-async';

export class SenderE2ETests extends AITestClass {
private readonly _instrumentationKey = 'b7170927-2d1c-44f1-acec-59f4e1751c11';
Expand Down Expand Up @@ -127,6 +128,61 @@ export class SenderE2ETests extends AITestClass {
.concat(PollingAssert.createPollingAssert(() => this.successSpy.called && this.isSessionSentEmpty(), "SentBuffer Session storage is empty", 15, 1000) as any)
.concat(PollingAssert.createPollingAssert(() => this.successSpy.called && this.isSessionEmpty(), "Buffer Session storage is empty", 15, 1000) as any)
});

this.testCaseAsync({
name: 'SendBuffer: Session storage is cleared after a send with cs promise',
stepDelay: this.delay,
steps: [
() => {
if (this._ai && this._ai.unload) {
this._ai.unload(false);
}

let csPromise = createAsyncResolvedPromise(`InstrumentationKey=${this._instrumentationKey}`);
let init = new ApplicationInsights({
config: {
connectionString: csPromise,
loggingLevelConsole: 999,
extensionConfig: {
'AppInsightsChannelPlugin': {
maxBatchInterval: 2000,
maxBatchSizeInBytes: 10*1024*1024 // 10 MB
},
["AppInsightsCfgSyncPlugin"]: {
cfgUrl: ""
}

}
},
queue: [],
version: 2.0
});
this._ai = init.loadAppInsights();

// Setup Sinon stuff
this._sender = this._ai.getPlugin<Sender>(BreezeChannelIdentifier).plugin;
this._sender._buffer.clear();
this.errorSpy = this.sandbox.spy(this._sender, '_onError');
this.successSpy = this.sandbox.spy(this._sender, '_onSuccess');
this.loggingSpy = this.sandbox.stub(this._ai.appInsights.core.logger, 'throwInternal');
this.clearSpy = this.sandbox.spy(this._sender._buffer, 'clearSent');
this._ai.trackTrace({message: 'test trace'});
}
].concat(PollingAssert.createPollingAssert(() => {
let core = this._ai.appInsights.core
let activeStatus = core.activeStatus && core.activeStatus();

if (activeStatus === ActiveStatus.ACTIVE ) {
Assert.equal(this._instrumentationKey, core.config.instrumentationKey, "ikey should be set");
return true;
}
return false;
}, "Wait for promise response" + new Date().toISOString(), 60, 1000) as any)
.concat(this.waitForResponse())
.concat(this.boilerPlateAsserts)
.concat(PollingAssert.createPollingAssert(() => this.successSpy.called && this.isSessionSentEmpty(), "SentBuffer Session storage is empty", 15, 1000) as any)
.concat(PollingAssert.createPollingAssert(() => this.successSpy.called && this.isSessionEmpty(), "Buffer Session storage is empty", 15, 1000) as any)
});
}

private addTrackEndpointTests(): void {
Expand Down
10 changes: 5 additions & 5 deletions AISKU/src/AISku.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import {
IDependencyListenerHandler
} from "@microsoft/applicationinsights-dependencies-js";
import { PropertiesPlugin } from "@microsoft/applicationinsights-properties-js";
import { IPromise, createAsyncPromise, createPromise, doAwaitResponse } from "@nevware21/ts-async";
import { IPromise, createPromise, createSyncPromise, doAwaitResponse } from "@nevware21/ts-async";
import { arrForEach, arrIndexOf, isPromiseLike, objDefine, objForEachKey, strIndexOf, throwUnsupported } from "@nevware21/ts-utils";
import { IApplicationInsights } from "./IApplicationInsights";
import {
Expand Down Expand Up @@ -203,7 +203,7 @@ export class AppInsightsSku implements IApplicationInsights {
let configCs = _config.connectionString;

function _parseCs() {
return createAsyncPromise<ConnectionString>((resolve, reject) => {
return createSyncPromise<ConnectionString>((resolve, reject) => {
doAwaitResponse(configCs, (res) => {
let curCs = res && res.value;
let parsedCs = null;
Expand All @@ -220,7 +220,7 @@ export class AppInsightsSku implements IApplicationInsights {
}

if (isPromiseLike(configCs)) {
let ikeyPromise = createAsyncPromise<string>((resolve, reject) => {
let ikeyPromise = createSyncPromise<string>((resolve, reject) => {
_parseCs().then((cs) => {
let ikey = _config.instrumentationKey;
ikey = cs && cs.instrumentationkey || ikey;
Expand All @@ -235,7 +235,7 @@ export class AppInsightsSku implements IApplicationInsights {

let url: IPromise<string> | string = _config.userOverrideEndpointUrl;
if (isNullOrUndefined(url)) {
url = createAsyncPromise<string>((resolve, reject) => {
url = createSyncPromise<string>((resolve, reject) => {
_parseCs().then((cs) => {
let url = _config.endpointUrl;
let ingest = cs && cs.ingestionendpoint;
Expand All @@ -254,7 +254,7 @@ export class AppInsightsSku implements IApplicationInsights {
_config.endpointUrl = url;

}
if (isString(configCs)) {
if (isString(configCs) && configCs) {
// confirm if promiselike function present
// handle cs promise here
// add cases to oneNote
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,11 @@ export class SenderTests extends AITestClass {
if (activeStatus === ActiveStatus.ACTIVE) {
QUnit.assert.equal("testIkey", core.config.instrumentationKey, "ikey should be set");
QUnit.assert.equal("testUrl", core.config.endpointUrl ,"endpoint shoule be set");
// getExtCfg only finds undefined values from core
let senderConfig = this._sender._senderConfig;
QUnit.assert.equal("testIkey", senderConfig.instrumentationKey, "sender ikey should be set");
QUnit.assert.equal("testUrl", senderConfig.endpointUrl ,"sender endpoint shoule be set");

return true;
}
return false;
Expand Down
15 changes: 13 additions & 2 deletions channels/applicationinsights-channel-js/src/Sender.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ import {
prependTransports, runTargetUnload
} from "@microsoft/applicationinsights-core-js";
import { IPromise } from "@nevware21/ts-async";
import { ITimerHandler, isNumber, isString, isTruthy, objDeepFreeze, objDefine, scheduleTimeout } from "@nevware21/ts-utils";
import {
ITimerHandler, isNumber, isPromiseLike, isString, isTruthy, objDeepFreeze, objDefine, scheduleTimeout
} from "@nevware21/ts-utils";
import {
DependencyEnvelopeCreator, EventEnvelopeCreator, ExceptionEnvelopeCreator, MetricEnvelopeCreator, PageViewEnvelopeCreator,
PageViewPerformanceEnvelopeCreator, TraceEnvelopeCreator
Expand Down Expand Up @@ -264,8 +266,17 @@ export class Sender extends BaseTelemetryPlugin implements IChannelControls {
utlSetStoragePrefix(config.storagePrefix);
}
let ctx = createProcessTelemetryContext(null, config, core);
// getExtCfg only finds undefined values from core
let senderConfig = ctx.getExtCfg(identifier, defaultAppInsightsChannelConfig);
if(isPromiseLike(senderConfig.endpointUrl)) {
// if it is promise, means the endpoint url is from core.endpointurl
senderConfig.endpointUrl = config.endpointUrl as any;
}

if(isPromiseLike(senderConfig.instrumentationKey)) {
// if it is promise, means the endpoint url is from core.endpointurl
senderConfig.instrumentationKey = config.instrumentationKey as any;
}
objDefine(_self, "_senderConfig", {
g: function() {
return senderConfig;
Expand Down Expand Up @@ -356,7 +367,7 @@ export class Sender extends BaseTelemetryPlugin implements IChannelControls {
_self._sample = new Sample(senderConfig.samplingPercentage, diagLog);

_instrumentationKey = senderConfig.instrumentationKey;
if(!_validateInstrumentationKey(_instrumentationKey, config)) {
if(!isPromiseLike(_instrumentationKey) && !_validateInstrumentationKey(_instrumentationKey, config)) {
_throwInternal(diagLog,
eLoggingSeverity.CRITICAL,
_eInternalMessageId.InvalidInstrumentationKey, "Invalid Instrumentation key " + _instrumentationKey);
Expand Down
Loading

0 comments on commit 8628bba

Please sign in to comment.