Address dynamic corner-cases - Task 15982357: [Beta] [AI] Dynamic config for Properties extension (#1977)
- when the referenced property is not an object/array (ie. undefined or null) - we are assigning a referenced object/array to null / undefined
This commit is contained in:
Родитель
af918812b6
Коммит
bc620b524c
|
@ -700,7 +700,6 @@ export class DynamicConfigTests extends AITestClass {
|
|||
}
|
||||
});
|
||||
|
||||
|
||||
this.testCase({
|
||||
name: "Validate read-only",
|
||||
useFakeTimers: true,
|
||||
|
@ -751,6 +750,203 @@ export class DynamicConfigTests extends AITestClass {
|
|||
}
|
||||
});
|
||||
|
||||
this.testCase({
|
||||
name: "Validate updating referenced objects / arrays with non object or null / undefined",
|
||||
useFakeTimers: true,
|
||||
test: () => {
|
||||
let expectedUserCfg = {
|
||||
userTest: {} as any
|
||||
};
|
||||
|
||||
let theConfig: any = {
|
||||
instrumentationKey: "testiKey",
|
||||
endpointUrl: "https://localhost:9001",
|
||||
enableDebugExceptions: false,
|
||||
loggingLevelConsole: 1,
|
||||
extensionConfig: {
|
||||
"test": {} as any
|
||||
},
|
||||
userCfg: {
|
||||
userTest: {} as any
|
||||
}
|
||||
};
|
||||
|
||||
let handler = createDynamicConfig(theConfig, {});
|
||||
let config = handler.cfg;
|
||||
let userCfg = handler.ref(theConfig, "userCfg");
|
||||
|
||||
Assert.deepEqual(expectedUserCfg, userCfg, "Validate that the expected user Cfg")
|
||||
Assert.ok(userCfg === config.userCfg, "Validate userCfg reference is as expected")
|
||||
|
||||
// Assign the referenced object with null / undefined
|
||||
config.userCfg = null;
|
||||
Assert.deepEqual({ userTest: undefined }, config.userCfg);
|
||||
Assert.ok(userCfg === config.userCfg, "The previous referenced value should still match");
|
||||
|
||||
config.userCfg = undefined;
|
||||
Assert.deepEqual({ userTest: undefined }, config.userCfg);
|
||||
Assert.ok(userCfg === config.userCfg, "The previous referenced value should still match");
|
||||
|
||||
// Assign back to an object (should become automatically referenced)
|
||||
config.userCfg = {};
|
||||
Assert.deepEqual({ userTest: undefined }, config.userCfg);
|
||||
Assert.ok(userCfg === config.userCfg, "The previous referenced value should still match");
|
||||
|
||||
// Grab a reference and update
|
||||
userCfg = config.userCfg;
|
||||
Assert.ok(userCfg === config.userCfg, "References should now match");
|
||||
|
||||
config.userCfg = { hello: "World" };
|
||||
Assert.ok(userCfg === config.userCfg, "References should still match");
|
||||
Assert.ok(objHasOwn(config.userCfg, "hello"), "Direct config reference should have the new property");
|
||||
Assert.equal("World", config.userCfg.hello);
|
||||
Assert.ok(objHasOwn(userCfg, "hello"), "previous reference should have the new property");
|
||||
Assert.equal("World", userCfg.hello);
|
||||
|
||||
config.userCfg = expectedUserCfg;
|
||||
Assert.ok(userCfg === config.userCfg, "References should still match");
|
||||
Assert.ok(objHasOwn(config.userCfg, "userTest"), "Direct config reference should have the new property");
|
||||
Assert.ok(objHasOwn(config.userCfg, "hello"), "Direct config reference should have the new property");
|
||||
Assert.equal(undefined, config.userCfg.hello);
|
||||
Assert.deepEqual({}, config.userCfg.userTest);
|
||||
Assert.ok(objHasOwn(userCfg, "userTest"), "Direct config reference should have the new property");
|
||||
Assert.ok(objHasOwn(userCfg, "hello"), "previous reference should have the new property");
|
||||
Assert.equal(undefined, userCfg.hello);
|
||||
Assert.deepEqual({}, userCfg.userTest);
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Now try deleting the key, which will also drop the previous reference
|
||||
// ---------------------------------------------------------------------
|
||||
delete config.userCfg;
|
||||
Assert.ok(!objHasOwn(config, "userCfg"), "userCfg is no longer present");
|
||||
Assert.equal(undefined, config.userCfg, "Validate that the config.userCfg was removed");
|
||||
Assert.ok(userCfg !== config.userCfg, "Validate userCfg reference should no longer match");
|
||||
|
||||
// Assign the referenced object with null / undefined
|
||||
config.userCfg = null;
|
||||
Assert.ok(objHasOwn(config, "userCfg"), "userCfg is present");
|
||||
Assert.equal(null, config.userCfg, "Validate that the config.userCfg is null");
|
||||
Assert.ok(userCfg !== config.userCfg, "Validate userCfg reference should no longer match");
|
||||
|
||||
config.userCfg = undefined;
|
||||
Assert.ok(objHasOwn(config, "userCfg"), "userCfg is present");
|
||||
Assert.equal(undefined, config.userCfg, "Validate that the config.userCfg is undefined");
|
||||
Assert.ok(userCfg !== config.userCfg, "The previous referenced value should still match");
|
||||
|
||||
// Assign back to an object (should become automatically referenced)
|
||||
config.userCfg = {};
|
||||
Assert.deepEqual({}, config.userCfg);
|
||||
|
||||
// Grab a reference and update
|
||||
userCfg = config.userCfg;
|
||||
Assert.ok(userCfg === config.userCfg, "References should now match");
|
||||
|
||||
config.userCfg = { hello: "World" };
|
||||
Assert.ok(userCfg !== config.userCfg, "References should not match");
|
||||
Assert.ok(objHasOwn(config.userCfg, "hello"), "Direct config reference should have the new property");
|
||||
Assert.equal("World", config.userCfg.hello);
|
||||
Assert.ok(!objHasOwn(userCfg, "hello"), "previous reference should have the new property");
|
||||
Assert.equal(undefined, userCfg.hello);
|
||||
}
|
||||
});
|
||||
|
||||
this.testCase({
|
||||
name: "Validate updating initial referenced undefined value with objects / arrays with non object or null / undefined",
|
||||
useFakeTimers: true,
|
||||
test: () => {
|
||||
let expectedUserCfg = {
|
||||
userTest: {} as any
|
||||
};
|
||||
|
||||
let theConfig: any = {
|
||||
instrumentationKey: "testiKey",
|
||||
endpointUrl: "https://localhost:9001",
|
||||
enableDebugExceptions: false,
|
||||
loggingLevelConsole: 1,
|
||||
userCfg: undefined
|
||||
};
|
||||
|
||||
let handler = createDynamicConfig(theConfig, {});
|
||||
let config = handler.cfg;
|
||||
|
||||
let userCfg = handler.ref(theConfig, "userCfg");
|
||||
|
||||
Assert.equal(undefined, config.userCfg, "Check that the userCfg value is as expected");
|
||||
Assert.deepEqual(undefined, userCfg, "Validate that the expected user Cfg")
|
||||
Assert.ok(userCfg === config.userCfg, "Validate userCfg reference is as expected")
|
||||
|
||||
// Assign the referenced object with null / undefined
|
||||
config.userCfg = null;
|
||||
Assert.equal(null, config.userCfg);
|
||||
Assert.ok(userCfg !== config.userCfg, "The previous referenced value should no longer match");
|
||||
|
||||
config.userCfg = undefined;
|
||||
Assert.equal(undefined, config.userCfg);
|
||||
Assert.ok(userCfg === config.userCfg, "The previous referenced value should no longer match");
|
||||
|
||||
// Assign back to an object (should become automatically referenced)
|
||||
config.userCfg = {};
|
||||
Assert.equal(0, objKeys(config.userCfg), "Check the number of keys");
|
||||
Assert.ok(userCfg !== config.userCfg, "The previous referenced value should no longer match");
|
||||
|
||||
// Grab a reference and update
|
||||
userCfg = config.userCfg;
|
||||
Assert.ok(userCfg === config.userCfg, "References should now match");
|
||||
|
||||
config.userCfg = { hello: "World" };
|
||||
Assert.ok(userCfg === config.userCfg, "References should still match");
|
||||
Assert.ok(objHasOwn(config.userCfg, "hello"), "Direct config reference should have the new property")
|
||||
Assert.equal("World", config.userCfg.hello);
|
||||
Assert.ok(objHasOwn(userCfg, "hello"), "previous reference should have the new property")
|
||||
Assert.equal("World", userCfg.hello);
|
||||
|
||||
config.userCfg = expectedUserCfg;
|
||||
Assert.ok(userCfg === config.userCfg, "References should still match");
|
||||
Assert.ok(objHasOwn(config.userCfg, "userTest"), "Direct config reference should have the new property")
|
||||
Assert.ok(objHasOwn(config.userCfg, "hello"), "Direct config reference should have the new property")
|
||||
Assert.equal(undefined, config.userCfg.hello);
|
||||
Assert.deepEqual({}, config.userCfg.userTest);
|
||||
Assert.ok(objHasOwn(userCfg, "userTest"), "Direct config reference should have the new property")
|
||||
Assert.ok(objHasOwn(userCfg, "hello"), "previous reference should have the new property")
|
||||
Assert.equal(undefined, userCfg.hello);
|
||||
Assert.deepEqual({}, userCfg.userTest);
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Now try deleting the key, which will also drop the previous reference
|
||||
// ---------------------------------------------------------------------
|
||||
delete config.userCfg;
|
||||
Assert.ok(!objHasOwn(config, "userCfg"), "userCfg is no longer present");
|
||||
Assert.equal(undefined, config.userCfg, "Validate that the config.userCfg was removed");
|
||||
Assert.ok(userCfg !== config.userCfg, "Validate userCfg reference should no longer match");
|
||||
|
||||
// Assign the referenced object with null / undefined
|
||||
config.userCfg = null;
|
||||
Assert.ok(objHasOwn(config, "userCfg"), "userCfg is present");
|
||||
Assert.equal(null, config.userCfg, "Validate that the config.userCfg is null");
|
||||
Assert.ok(userCfg !== config.userCfg, "Validate userCfg reference should no longer match");
|
||||
|
||||
config.userCfg = undefined;
|
||||
Assert.ok(objHasOwn(config, "userCfg"), "userCfg is present");
|
||||
Assert.equal(undefined, config.userCfg, "Validate that the config.userCfg is undefined");
|
||||
Assert.ok(userCfg !== config.userCfg, "The previous referenced value should still match");
|
||||
|
||||
// Assign back to an object (should become automatically referenced)
|
||||
config.userCfg = {};
|
||||
Assert.deepEqual({}, config.userCfg);
|
||||
|
||||
// Grab a reference and update
|
||||
userCfg = config.userCfg;
|
||||
Assert.ok(userCfg === config.userCfg, "References should now match");
|
||||
|
||||
config.userCfg = { hello: "World" };
|
||||
Assert.ok(userCfg !== config.userCfg, "References should not match");
|
||||
Assert.ok(objHasOwn(config.userCfg, "hello"), "Direct config reference should have the new property");
|
||||
Assert.equal("World", config.userCfg.hello);
|
||||
Assert.ok(!objHasOwn(userCfg, "hello"), "previous reference should have the new property");
|
||||
Assert.equal(undefined, userCfg.hello);
|
||||
}
|
||||
});
|
||||
|
||||
function createPerfMgr(core: IAppInsightsCore, manager: INotificationManager): IPerfManager {
|
||||
return createPerfMgr(core, manager);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
import {
|
||||
arrForEach, arrIndexOf, dumpObj, isArray, isPlainObject, objDefineAccessors, objDefineProp, objForEachKey, objGetOwnPropertyDescriptor
|
||||
} from "@nevware21/ts-utils";
|
||||
import { UNDEFINED_VALUE } from "../JavaScriptSDK/InternalConstants";
|
||||
import { CFG_HANDLER_LINK, throwInvalidAccess } from "./DynamicSupport";
|
||||
import { IWatcherHandler, _IDynamicDetail } from "./IDynamicWatcher";
|
||||
import { _IDynamicConfigHandlerState, _IDynamicGetter } from "./_IDynamicConfigHandlerState";
|
||||
|
@ -63,10 +64,15 @@ function _makeDynamicProperty<T, C, V = any>(state: _IDynamicConfigHandlerState<
|
|||
|
||||
// Flag to optimize lookup response time by avoiding additional function calls
|
||||
let checkDynamic = true;
|
||||
let isObjectOrArray = false;
|
||||
|
||||
function _getProperty() {
|
||||
|
||||
if (checkDynamic) {
|
||||
if (value && !value[CFG_HANDLER_LINK] && (isPlainObject(value) || isArray(value))) {
|
||||
isObjectOrArray = isObjectOrArray || (value && (isPlainObject(value) || isArray(value)));
|
||||
|
||||
// Make sure that if it's an object that we make it dynamic
|
||||
if (value && !value[CFG_HANDLER_LINK] && isObjectOrArray) {
|
||||
// It doesn't look like it's already dynamic so lets make sure it's converted the object into a dynamic Config as well
|
||||
value = _makeDynamicObject(state, value);
|
||||
}
|
||||
|
@ -98,13 +104,21 @@ function _makeDynamicProperty<T, C, V = any>(state: _IDynamicConfigHandlerState<
|
|||
throwInvalidAccess("[" + name + "] is read-only:" + dumpObj(theConfig));
|
||||
}
|
||||
|
||||
let isReferenced = _getProperty[state.rf];
|
||||
if(isPlainObject(value) || isArray(value)) {
|
||||
if (checkDynamic) {
|
||||
isObjectOrArray = isObjectOrArray || (value && (isPlainObject(value) || isArray(value)));
|
||||
checkDynamic = false;
|
||||
}
|
||||
|
||||
// The value must be a plain object or an array to enforce the reference (in-place updates)
|
||||
let isReferenced = isObjectOrArray && _getProperty[state.rf];
|
||||
|
||||
if(isObjectOrArray) {
|
||||
// We are about to replace a plain object or an array
|
||||
if (isReferenced) {
|
||||
// Reassign the properties from the current value to the same properties from the newValue
|
||||
// This will set properties not in the newValue to undefined
|
||||
objForEachKey(value, (key) => {
|
||||
value[key] = newValue[key];
|
||||
value[key] = newValue ? newValue[key] : UNDEFINED_VALUE;
|
||||
});
|
||||
|
||||
// Now assign / re-assign value with all of the keys from newValue
|
||||
|
@ -129,14 +143,17 @@ function _makeDynamicProperty<T, C, V = any>(state: _IDynamicConfigHandlerState<
|
|||
}
|
||||
}
|
||||
|
||||
checkDynamic = false;
|
||||
if (!isReferenced && (isPlainObject(newValue) || isArray(newValue))) {
|
||||
// As the newValue is an object/array lets preemptively make it dynamic
|
||||
_makeDynamicObject(state, newValue);
|
||||
}
|
||||
if (newValue !== value) {
|
||||
let newIsObjectOrArray = newValue && (isPlainObject(newValue) || isArray(newValue));
|
||||
if (!isReferenced && newIsObjectOrArray) {
|
||||
// As the newValue is an object/array lets preemptively make it dynamic
|
||||
_makeDynamicObject(state, newValue);
|
||||
}
|
||||
|
||||
// Now assign the internal "value" to the newValue
|
||||
value = newValue;
|
||||
// Now assign the internal "value" to the newValue
|
||||
value = newValue;
|
||||
isObjectOrArray = newIsObjectOrArray;
|
||||
}
|
||||
|
||||
// Cause any listeners to be scheduled for notification
|
||||
state.add(detail);
|
||||
|
@ -178,9 +195,9 @@ export function _setDynamicProperty<T, C, V = any>(state: _IDynamicConfigHandler
|
|||
return value;
|
||||
}
|
||||
|
||||
export function _makeDynamicObject<T>(state: _IDynamicConfigHandlerState<T>, target: any/*, newValues?: any*/) {
|
||||
export function _makeDynamicObject<T>(state: _IDynamicConfigHandlerState<T>, target: any) {
|
||||
// Assign target with new value properties (converting into dynamic properties in the process)
|
||||
objForEachKey(/*newValues || */target, (key, value) => {
|
||||
objForEachKey(target, (key, value) => {
|
||||
// Assign and/or make the property dynamic
|
||||
_setDynamicProperty(state, target, key, value);
|
||||
});
|
||||
|
|
Загрузка…
Ссылка в новой задаче