Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/evaluator/__tests__/evaluate-feature.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { evaluateFeature } from '../index';
import { EXCEPTION, NOT_IN_SPLIT, SPLIT_ARCHIVED, SPLIT_KILLED, SPLIT_NOT_FOUND } from '../../utils/labels';
import { EXCEPTION, NOT_IN_SPLIT, SPLIT_ARCHIVED, SPLIT_KILLED, DEFINITION_NOT_FOUND } from '../../utils/labels';
import { loggerMock } from '../../logger/__tests__/sdkLogger.mock';
import { ISplit } from '../../dtos/types';
import { IStorageSync } from '../../storages/types';
Expand Down Expand Up @@ -53,7 +53,7 @@ test('EVALUATOR / should return right label, treatment and config if storage ret
config: '{color:\'black\'}', changeNumber: 1487277320548
};
const expectedOutputControl = {
treatment: 'control', label: SPLIT_NOT_FOUND, config: null
treatment: 'control', label: DEFINITION_NOT_FOUND, config: null
};

const evaluationWithConfig = evaluateFeature(
Expand Down
6 changes: 3 additions & 3 deletions src/evaluator/__tests__/evaluate-features.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { evaluateFeatures, evaluateFeaturesByFlagSets } from '../index';
import { EXCEPTION, NOT_IN_SPLIT, SPLIT_ARCHIVED, SPLIT_KILLED, SPLIT_NOT_FOUND } from '../../utils/labels';
import { EXCEPTION, NOT_IN_SPLIT, SPLIT_ARCHIVED, SPLIT_KILLED, DEFINITION_NOT_FOUND } from '../../utils/labels';
import { loggerMock } from '../../logger/__tests__/sdkLogger.mock';
import { WARN_FLAGSET_WITHOUT_FLAGS } from '../../logger/constants';
import { ISplit } from '../../dtos/types';
Expand Down Expand Up @@ -71,7 +71,7 @@ test('EVALUATOR - Multiple evaluations at once / should return right labels, tre
config: '{color:\'black\'}', changeNumber: 1487277320548
},
not_existent_split: {
treatment: 'control', label: SPLIT_NOT_FOUND, config: null
treatment: 'control', label: DEFINITION_NOT_FOUND, config: null
},
};

Expand Down Expand Up @@ -122,7 +122,7 @@ describe('EVALUATOR - Multiple evaluations at once by flag sets', () => {
config: '{color:\'black\'}', changeNumber: 1487277320548
},
not_existent_split: {
treatment: 'control', label: SPLIT_NOT_FOUND, config: null
treatment: 'control', label: DEFINITION_NOT_FOUND, config: null
},
};

Expand Down
4 changes: 2 additions & 2 deletions src/evaluator/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { engineParser } from './Engine';
import { thenable } from '../utils/promise/thenable';
import { EXCEPTION, SPLIT_NOT_FOUND } from '../utils/labels';
import { EXCEPTION, DEFINITION_NOT_FOUND } from '../utils/labels';
import { CONTROL } from '../utils/constants';
import { ISplit, MaybeThenable } from '../dtos/types';
import { IStorageAsync, IStorageSync } from '../storages/types';
Expand Down Expand Up @@ -148,7 +148,7 @@ function getEvaluation(
): MaybeThenable<IEvaluationResult> {
let evaluation: MaybeThenable<IEvaluationResult> = {
treatment: CONTROL,
label: SPLIT_NOT_FOUND,
label: DEFINITION_NOT_FOUND,
config: null
};

Expand Down
2 changes: 1 addition & 1 deletion src/logger/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export const WARN_SETTING_NULL = 211;
export const WARN_TRIMMING_PROPERTIES = 212;
export const WARN_CONVERTING = 213;
export const WARN_TRIMMING = 214;
export const WARN_NOT_EXISTENT_SPLIT = 215;
export const WARN_NOT_EXISTENT_DEFINITION = 215;
export const WARN_LOWERCASE_TRAFFIC_TYPE = 216;
export const WARN_NOT_EXISTENT_TT = 217;
export const WARN_INTEGRATION_INVALID = 218;
Expand Down
4 changes: 2 additions & 2 deletions src/logger/messages/warn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ export const codesWarn: [number, string][] = codesError.concat([
[c.SUBMITTERS_PUSH_FAILS, c.LOG_PREFIX_SYNC_SUBMITTERS + 'Dropping %s after retry. Reason: %s.'],
[c.SUBMITTERS_PUSH_RETRY, c.LOG_PREFIX_SYNC_SUBMITTERS + 'Failed to push %s, keeping data to retry on next iteration. Reason: %s.'],
// client status
[c.CLIENT_NOT_READY_FROM_CACHE, '%s: the SDK is not ready to evaluate. Results may be incorrect%s. Make sure to wait for SDK readiness before using this method.'],
[c.CLIENT_NOT_READY_FROM_CACHE, '%s: the SDK is not ready to evaluate. Results may be incorrect. Make sure to wait for SDK readiness before using this method.'],
[c.CLIENT_NO_LISTENER, 'No listeners for SDK_READY event detected. Incorrect control treatments could have been logged if you called getTreatment/s while the SDK was not yet synchronized with the backend.'],
// input validation
[c.WARN_SETTING_NULL, '%s: Property "%s" is of invalid type. Setting value to null.'],
[c.WARN_TRIMMING_PROPERTIES, '%s: more than 300 properties were provided. Some of them will be trimmed when processed.'],
[c.WARN_CONVERTING, '%s: %s "%s" is not of type string, converting.'],
[c.WARN_TRIMMING, '%s: %s "%s" has extra whitespace, trimming.'],
[c.WARN_NOT_EXISTENT_SPLIT, '%s: feature flag "%s" does not exist in this environment. Please double check what feature flags exist in the Split user interface.'],
[c.WARN_NOT_EXISTENT_DEFINITION, '%s: definition "%s" does not exist in this environment. Please double check what definitions exist in the Split user interface.'],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How are we going to identify which definition we're talking about? Whether it's a segment, flag, or config definition?

[c.WARN_LOWERCASE_TRAFFIC_TYPE, '%s: traffic_type_name should be all lowercase - converting string to lowercase.'],
[c.WARN_NOT_EXISTENT_TT, '%s: traffic type "%s" does not have any corresponding feature flag in this environment, make sure you\'re tracking your events to a valid traffic type defined in the Split user interface.'],
[c.WARN_FLAGSET_NOT_CONFIGURED, '%s: you passed %s which is not part of the configured FlagSetsFilter, ignoring Flag Set.'],
Expand Down
4 changes: 2 additions & 2 deletions src/sdkClient/client.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { evaluateFeature, evaluateFeatures, evaluateFeaturesByFlagSets } from '../evaluator';
import { thenable } from '../utils/promise/thenable';
import { getMatching, getBucketing } from '../utils/key';
import { validateSplitExistence } from '../utils/inputValidation/splitExistence';
import { validateDefinitionExistence } from '../utils/inputValidation/definitionExistence';
import { validateTrafficTypeExistence } from '../utils/inputValidation/trafficTypeExistence';
import { SDK_NOT_READY } from '../utils/labels';
import { CONTROL, TREATMENT, TREATMENTS, TREATMENT_WITH_CONFIG, TREATMENTS_WITH_CONFIG, TRACK, TREATMENTS_WITH_CONFIG_BY_FLAGSETS, TREATMENTS_BY_FLAGSETS, TREATMENTS_BY_FLAGSET, TREATMENTS_WITH_CONFIG_BY_FLAGSET, GET_TREATMENTS_WITH_CONFIG, GET_TREATMENTS_BY_FLAG_SETS, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, GET_TREATMENTS_BY_FLAG_SET, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET, GET_TREATMENT_WITH_CONFIG, GET_TREATMENT, GET_TREATMENTS, TRACK_FN_LABEL } from '../utils/constants';
Expand Down Expand Up @@ -151,7 +151,7 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
}

// If no target/key, no impression is tracked
if (key && validateSplitExistence(log, readinessManager, featureFlagName, label, invokingMethodName)) {
if (key && validateDefinitionExistence(log, readinessManager, featureFlagName, label, invokingMethodName)) {
const matchingKey = getMatching(key);
const bucketingKey = getBucketing(key);

Expand Down
2 changes: 1 addition & 1 deletion src/sdkClient/clientInputValidation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export function clientInputValidationDecorator<TClient extends SplitIO.IClient |
const isNotDestroyed = validateIfNotDestroyed(log, readinessManager, methodName);
const options = validateEvaluationOptions(log, maybeOptions, methodName);

validateIfReadyFromCache(log, readinessManager, methodName, nameOrNames);
validateIfReadyFromCache(log, readinessManager, methodName);

const valid = isNotDestroyed && key && nameOrNames && attributes !== false;

Expand Down
6 changes: 3 additions & 3 deletions src/sdkManager/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { objectAssign } from '../utils/lang/objectAssign';
import { thenable } from '../utils/promise/thenable';
import { find } from '../utils/lang';
import { validateSplit, validateSplitExistence, validateIfOperational } from '../utils/inputValidation';
import { validateSplit, validateDefinitionExistence, validateIfOperational } from '../utils/inputValidation';
import { ISplitsCacheAsync, ISplitsCacheSync } from '../storages/types';
import { ISdkReadinessManager } from '../readiness/types';
import { ISplit } from '../dtos/types';
Expand Down Expand Up @@ -74,12 +74,12 @@ export function sdkManagerFactory<TSplitCache extends ISplitsCacheSync | ISplits

if (thenable(split)) {
return split.catch(() => null).then(result => { // handle possible rejections when using pluggable storage
validateSplitExistence(log, readinessManager, splitName, result, SPLIT_FN_LABEL);
validateDefinitionExistence(log, readinessManager, splitName, result, SPLIT_FN_LABEL);
return objectToView(result);
});
}

validateSplitExistence(log, readinessManager, splitName, split, SPLIT_FN_LABEL);
validateDefinitionExistence(log, readinessManager, splitName, split, SPLIT_FN_LABEL);

return objectToView(split);
},
Expand Down
2 changes: 1 addition & 1 deletion src/sync/polling/pollingManagerSS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export function pollingManagerSSFactory(
// Start periodic fetching (polling)
start() {
log.info(POLLING_START);
log.debug(LOG_PREFIX_SYNC_POLLING + `Splits will be refreshed each ${settings.scheduler.featuresRefreshRate} millis`);
log.debug(LOG_PREFIX_SYNC_POLLING + `Definitions will be refreshed each ${settings.scheduler.featuresRefreshRate} millis`);
log.debug(LOG_PREFIX_SYNC_POLLING + `Segments will be refreshed each ${settings.scheduler.segmentsRefreshRate} millis`);

const startingUp = splitsSyncTask.start();
Expand Down
47 changes: 47 additions & 0 deletions src/utils/inputValidation/__tests__/definitionExistence.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@

import * as LabelConstants from '../../labels';

import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock';

import { validateDefinitionExistence } from '../definitionExistence';
import { IReadinessManager } from '../../../readiness/types';
import { WARN_NOT_EXISTENT_DEFINITION } from '../../../logger/constants';

describe('Split existence (special case)', () => {

afterEach(() => { loggerMock.mockClear(); });

test('Should return a boolean indicating if the SDK was ready and there was no Split object or "definition not found" label', () => {
// @ts-expect-error
let readinessManagerMock = {
isReady: jest.fn(() => false) // Fake the signal for the non ready SDK
} as IReadinessManager;

expect(validateDefinitionExistence(loggerMock, readinessManagerMock, 'some_split', {}, 'test_method')).toBe(true); // Should always return true when the SDK is not ready.
expect(validateDefinitionExistence(loggerMock, readinessManagerMock, 'some_split', null, 'test_method')).toBe(true); // Should always return true when the SDK is not ready.
expect(validateDefinitionExistence(loggerMock, readinessManagerMock, 'some_split', undefined, 'test_method')).toBe(true); // Should always return true when the SDK is not ready.
expect(validateDefinitionExistence(loggerMock, readinessManagerMock, 'some_split', 'a label', 'test_method')).toBe(true); // Should always return true when the SDK is not ready.
expect(validateDefinitionExistence(loggerMock, readinessManagerMock, 'some_split', LabelConstants.DEFINITION_NOT_FOUND, 'test_method')).toBe(true); // Should always return true when the SDK is not ready.

expect(loggerMock.warn).not.toBeCalled(); // There should have been no warning logs since the SDK was not ready yet.
expect(loggerMock.error).not.toBeCalled(); // There should have been no error logs since the SDK was not ready yet.

// Prepare the mock to fake that the SDK is ready now.
(readinessManagerMock.isReady as jest.Mock).mockImplementation(() => true);

expect(validateDefinitionExistence(loggerMock, readinessManagerMock, 'other_split', {}, 'other_method')).toBe(true); // Should return true if it receives a Split Object instead of null (when the object is not found, for manager).
expect(validateDefinitionExistence(loggerMock, readinessManagerMock, 'other_split', 'a label', 'other_method')).toBe(true); // Should return true if it receives a Label and it is not split not found (when the Split was not found on the storage, for client).

expect(loggerMock.warn).not.toBeCalled(); // There should have been no warning logs since the values we used so far were considered valid.
expect(loggerMock.error).not.toBeCalled(); // There should have been no error logs since the values we used so far were considered valid.

expect(validateDefinitionExistence(loggerMock, readinessManagerMock, 'other_split', null, 'other_method')).toBe(false); // Should return false if it receives a non-truthy value as a split object or label
expect(validateDefinitionExistence(loggerMock, readinessManagerMock, 'other_split', undefined, 'other_method')).toBe(false); // Should return false if it receives a non-truthy value as a split object or label
expect(validateDefinitionExistence(loggerMock, readinessManagerMock, 'other_split', LabelConstants.DEFINITION_NOT_FOUND, 'other_method')).toBe(false); // Should return false if it receives a label but it is the split not found one.

expect(loggerMock.warn).toBeCalledTimes(3); // It should have logged 3 warnings, one per each time we called it
loggerMock.warn.mock.calls.forEach(call => expect(call).toEqual([WARN_NOT_EXISTENT_DEFINITION, ['other_method', 'other_split']])); // Warning logs should have the correct message.

expect(loggerMock.error).not.toBeCalled(); // We log warnings, not errors.
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ describe('validateIfReadyFromCache', () => {
// @ts-ignore
expect(validateIfReadyFromCache(loggerMock, readinessManagerMock, 'test_method')).toBe(false); // It should return true if SDK was ready.
expect(readinessManagerMock.isReadyFromCache).toBeCalledTimes(1); // It checks for SDK_READY_FROM_CACHE status.
expect(loggerMock.warn).toBeCalledWith(CLIENT_NOT_READY_FROM_CACHE, ['test_method', '']); // It should log the expected warning.
expect(loggerMock.warn).toBeCalledWith(CLIENT_NOT_READY_FROM_CACHE, ['test_method']); // It should log the expected warning.
expect(loggerMock.error).not.toBeCalled(); // But it should not log any errors.
});
});
47 changes: 0 additions & 47 deletions src/utils/inputValidation/__tests__/splitExistence.spec.ts

This file was deleted.

19 changes: 19 additions & 0 deletions src/utils/inputValidation/definitionExistence.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { FALLBACK_DEFINITION_NOT_FOUND, DEFINITION_NOT_FOUND } from '../labels';
import { IReadinessManager } from '../../readiness/types';
import { ILogger } from '../../logger/types';
import { WARN_NOT_EXISTENT_DEFINITION } from '../../logger/constants';

/**
* This is defined here and in this format mostly because of the logger and the fact that it's considered a validation at product level.
* But it's not going to run on the input validation layer. In any case, the most compelling reason to use it as we do is to avoid going to Redis and get a definition twice.
*/
export function validateDefinitionExistence(log: ILogger, readinessManager: IReadinessManager, definitionName: string, labelOrDefinitionObj: any, method: string): boolean {
if (readinessManager.isReady()) { // Only if it's ready (synced with BE) we validate this, otherwise it may just be that the SDK is still syncing
if (labelOrDefinitionObj === DEFINITION_NOT_FOUND || labelOrDefinitionObj === FALLBACK_DEFINITION_NOT_FOUND || labelOrDefinitionObj == null) {
log.warn(WARN_NOT_EXISTENT_DEFINITION, [method, definitionName]);
return false;
}
}

return true;
}
2 changes: 1 addition & 1 deletion src/utils/inputValidation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ export { validateSplit } from './split';
export { validateSplits } from './splits';
export { validateTrafficType } from './trafficType';
export { validateIfNotDestroyed, validateIfReadyFromCache, validateIfOperational } from './isOperational';
export { validateSplitExistence } from './splitExistence';
export { validateDefinitionExistence } from './definitionExistence';
export { validateTrafficTypeExistence } from './trafficTypeExistence';
export { validateEvaluationOptions } from './eventProperties';
Loading