Skip to content

Commit 15ad365

Browse files
Merge branch 'master' into junaed/fssdk-11403-readme-update
2 parents 5a532c1 + c477d60 commit 15ad365

File tree

84 files changed

+1994
-1843
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

84 files changed

+1994
-1843
lines changed

.github/workflows/javascript.yml

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -40,30 +40,30 @@ jobs:
4040
CI_USER_TOKEN: ${{ secrets.CI_USER_TOKEN }}
4141
TRAVIS_COM_TOKEN: ${{ secrets.TRAVIS_COM_TOKEN }}
4242

43-
crossbrowser_and_umd_unit_tests:
44-
runs-on: ubuntu-latest
45-
env:
46-
BROWSER_STACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }}
47-
BROWSER_STACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }}
48-
steps:
49-
- uses: actions/checkout@v3
50-
- name: Set up Node
51-
uses: actions/setup-node@v3
52-
with:
53-
node-version: 16
54-
cache: 'npm'
55-
cache-dependency-path: ./package-lock.json
56-
- name: Cross-browser and umd unit tests
57-
working-directory: .
58-
run: |
59-
npm install
60-
npm run test-ci
43+
# crossbrowser_and_umd_unit_tests:
44+
# runs-on: ubuntu-latest
45+
# env:
46+
# BROWSER_STACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }}
47+
# BROWSER_STACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }}
48+
# steps:
49+
# - uses: actions/checkout@v3
50+
# - name: Set up Node
51+
# uses: actions/setup-node@v3
52+
# with:
53+
# node-version: 16
54+
# cache: 'npm'
55+
# cache-dependency-path: ./package-lock.json
56+
# - name: Cross-browser and umd unit tests
57+
# working-directory: .
58+
# run: |
59+
# npm install
60+
# npm run test-ci
6161

6262
unit_tests:
6363
runs-on: ubuntu-latest
6464
strategy:
6565
matrix:
66-
node: ['16', '18', '20', '22']
66+
node: ['18', '20', '22']
6767
steps:
6868
- uses: actions/checkout@v3
6969
- name: Set up Node ${{ matrix.node }}

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
66
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
77

8+
## [5.3.5] - Jan 29, 2025
9+
10+
### Bug Fixes
11+
12+
- Rollout experiment key exclusion from activate method([#949](https://github.com/optimizely/javascript-sdk/pull/949))
13+
- Using optimizely.readyPromise instead of optimizely.onReady to avoid setTimeout call in edge environments. ([#995](https://github.com/optimizely/javascript-sdk/pull/995))
14+
15+
## [4.10.1] - November 18, 2024
16+
17+
### Changed
18+
- update uuid module improt and usage ([#961](https://github.com/optimizely/javascript-sdk/pull/961))
19+
20+
821
## [5.3.4] - Jun 28, 2024
922

1023
### Changed

lib/common_exports.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ export { createStaticProjectConfigManager } from './project_config/config_manage
1919
export { LogLevel } from './logging/logger';
2020

2121
export {
22-
DebugLog,
23-
InfoLog,
24-
WarnLog,
25-
ErrorLog,
22+
DEBUG,
23+
INFO,
24+
WARN,
25+
ERROR,
2626
} from './logging/logger_factory';
2727

2828
export { createLogger } from './logging/logger_factory';

lib/core/decision_service/cmab/cmab_service.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { LoggerFacade } from "../../../logging/logger";
1818
import { IOptimizelyUserContext } from "../../../optimizely_user_context";
1919
import { ProjectConfig } from "../../../project_config/project_config"
2020
import { OptimizelyDecideOption, UserAttributes } from "../../../shared_types"
21-
import { Cache } from "../../../utils/cache/cache";
21+
import { Cache, CacheWithRemove } from "../../../utils/cache/cache";
2222
import { CmabClient } from "./cmab_client";
2323
import { v4 as uuidV4 } from 'uuid';
2424
import murmurhash from "murmurhash";
@@ -53,12 +53,12 @@ export type CmabCacheValue = {
5353

5454
export type CmabServiceOptions = {
5555
logger?: LoggerFacade;
56-
cmabCache: Cache<CmabCacheValue>;
56+
cmabCache: CacheWithRemove<CmabCacheValue>;
5757
cmabClient: CmabClient;
5858
}
5959

6060
export class DefaultCmabService implements CmabService {
61-
private cmabCache: Cache<CmabCacheValue>;
61+
private cmabCache: CacheWithRemove<CmabCacheValue>;
6262
private cmabClient: CmabClient;
6363
private logger?: LoggerFacade;
6464

@@ -81,7 +81,7 @@ export class DefaultCmabService implements CmabService {
8181
}
8282

8383
if (options[OptimizelyDecideOption.RESET_CMAB_CACHE]) {
84-
this.cmabCache.clear();
84+
this.cmabCache.reset();
8585
}
8686

8787
const cacheKey = this.getCacheKey(userContext.getUserId(), ruleId);
@@ -90,7 +90,7 @@ export class DefaultCmabService implements CmabService {
9090
this.cmabCache.remove(cacheKey);
9191
}
9292

93-
const cachedValue = await this.cmabCache.get(cacheKey);
93+
const cachedValue = await this.cmabCache.lookup(cacheKey);
9494

9595
const attributesJson = JSON.stringify(filteredAttributes, Object.keys(filteredAttributes).sort());
9696
const attributesHash = String(murmurhash.v3(attributesJson));
@@ -104,7 +104,7 @@ export class DefaultCmabService implements CmabService {
104104
}
105105

106106
const variation = await this.fetchDecision(ruleId, userContext.getUserId(), filteredAttributes);
107-
this.cmabCache.set(cacheKey, {
107+
this.cmabCache.save(cacheKey, {
108108
attributesHash,
109109
variationId: variation.variationId,
110110
cmabUuid: variation.cmabUuid,

lib/core/decision_service/index.spec.ts

Lines changed: 135 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@
1414
* limitations under the License.
1515
*/
1616
import { describe, it, expect, vi, MockInstance, beforeEach } from 'vitest';
17-
import { CMAB_FETCH_FAILED, DecisionService } from '.';
17+
import { CMAB_DUMMY_ENTITY_ID, CMAB_FETCH_FAILED, DecisionService } from '.';
1818
import { getMockLogger } from '../../tests/mock/mock_logger';
1919
import OptimizelyUserContext from '../../optimizely_user_context';
2020
import { bucket } from '../bucketer';
2121
import { getTestProjectConfig, getTestProjectConfigWithFeatures } from '../../tests/test_data';
2222
import { createProjectConfig, ProjectConfig } from '../../project_config/project_config';
23-
import { BucketerParams, Experiment, OptimizelyDecideOption, UserProfile } from '../../shared_types';
23+
import { BucketerParams, Experiment, OptimizelyDecideOption, UserAttributes, UserProfile } from '../../shared_types';
2424
import { CONTROL_ATTRIBUTES, DECISION_SOURCES } from '../../utils/enums';
2525
import { getDecisionTestDatafile } from '../../tests/decision_test_datafile';
2626
import { Value } from '../../utils/promise/operation_value';
@@ -140,10 +140,18 @@ const verifyBucketCall = (
140140
variationIdMap,
141141
bucketingId,
142142
} = mockBucket.mock.calls[call][0];
143+
let expectedTrafficAllocation = experiment.trafficAllocation;
144+
if (experiment.cmab) {
145+
expectedTrafficAllocation = [{
146+
endOfRange: experiment.cmab.trafficAllocation,
147+
entityId: CMAB_DUMMY_ENTITY_ID,
148+
}];
149+
}
150+
143151
expect(experimentId).toBe(experiment.id);
144152
expect(experimentKey).toBe(experiment.key);
145153
expect(userId).toBe(user.getUserId());
146-
expect(trafficAllocationConfig).toBe(experiment.trafficAllocation);
154+
expect(trafficAllocationConfig).toEqual(expectedTrafficAllocation);
147155
expect(experimentKeyMap).toBe(projectConfig.experimentKeyMap);
148156
expect(experimentIdMap).toBe(projectConfig.experimentIdMap);
149157
expect(groupIdMap).toBe(projectConfig.groupIdMap);
@@ -336,7 +344,7 @@ describe('DecisionService', () => {
336344
const config = createProjectConfig(cloneDeep(testData));
337345
const experiment = config.experimentIdMap['111127'];
338346

339-
const attributes: any = {
347+
const attributes: UserAttributes = {
340348
$opt_experiment_bucket_map: {
341349
'111127': {
342350
variation_id: '111129', // ID of the 'variation' variation
@@ -674,7 +682,7 @@ describe('DecisionService', () => {
674682
const config = createProjectConfig(cloneDeep(testData));
675683
const experiment = config.experimentIdMap['111127'];
676684

677-
const attributes: any = {
685+
const attributes: UserAttributes = {
678686
$opt_experiment_bucket_map: {
679687
'111127': {
680688
variation_id: '111129', // ID of the 'variation' variation
@@ -707,7 +715,7 @@ describe('DecisionService', () => {
707715
const config = createProjectConfig(cloneDeep(testData));
708716
const experiment = config.experimentIdMap['111127'];
709717

710-
const attributes: any = {
718+
const attributes: UserAttributes = {
711719
$opt_experiment_bucket_map: {
712720
'122227': {
713721
variation_id: '111129', // ID of the 'variation' variation
@@ -740,7 +748,7 @@ describe('DecisionService', () => {
740748
const config = createProjectConfig(cloneDeep(testData));
741749
const experiment = config.experimentIdMap['111127'];
742750

743-
const attributes: any = {
751+
const attributes: UserAttributes = {
744752
$opt_experiment_bucket_map: {
745753
'111127': {
746754
variation_id: '111129', // ID of the 'variation' variation
@@ -766,7 +774,7 @@ describe('DecisionService', () => {
766774
const config = createProjectConfig(cloneDeep(testData));
767775
const experiment = config.experimentIdMap['111127'];
768776

769-
const attributes: any = {
777+
const attributes: UserAttributes = {
770778
$opt_experiment_bucket_map: {
771779
'111127': {
772780
variation_id: '111129', // ID of the 'variation' variation
@@ -1327,7 +1335,8 @@ describe('DecisionService', () => {
13271335
});
13281336
});
13291337

1330-
it('should get decision from the cmab service if the experiment is a cmab experiment', async () => {
1338+
it('should not return variation and should not call cmab service \
1339+
for cmab experiment if user is not bucketed into it', async () => {
13311340
const { decisionService, cmabService } = getDecisionService();
13321341
const config = createProjectConfig(getDecisionTestDatafile());
13331342

@@ -1340,6 +1349,57 @@ describe('DecisionService', () => {
13401349
},
13411350
});
13421351

1352+
mockBucket.mockImplementation((param: BucketerParams) => {
1353+
const ruleKey = param.experimentKey;
1354+
if (ruleKey == 'default-rollout-key') {
1355+
return { result: param.trafficAllocationConfig[0].entityId, reasons: [] }
1356+
}
1357+
return {
1358+
result: null,
1359+
reasons: [],
1360+
}
1361+
});
1362+
1363+
const feature = config.featureKeyMap['flag_1'];
1364+
const value = decisionService.resolveVariationsForFeatureList('async', config, [feature], user, {}).get();
1365+
expect(value).toBeInstanceOf(Promise);
1366+
1367+
const variation = (await value)[0];
1368+
expect(variation.result).toEqual({
1369+
experiment: config.experimentKeyMap['default-rollout-key'],
1370+
variation: config.variationIdMap['5007'],
1371+
decisionSource: DECISION_SOURCES.ROLLOUT,
1372+
});
1373+
1374+
verifyBucketCall(0, config, config.experimentKeyMap['exp_3'], user);
1375+
expect(cmabService.getDecision).not.toHaveBeenCalled();
1376+
});
1377+
1378+
it('should get decision from the cmab service if the experiment is a cmab experiment \
1379+
and user is bucketed into it', async () => {
1380+
const { decisionService, cmabService } = getDecisionService();
1381+
const config = createProjectConfig(getDecisionTestDatafile());
1382+
1383+
const user = new OptimizelyUserContext({
1384+
optimizely: {} as any,
1385+
userId: 'tester',
1386+
attributes: {
1387+
country: 'BD',
1388+
age: 80, // should satisfy audience condition for exp_3 which is cmab and not others
1389+
},
1390+
});
1391+
1392+
mockBucket.mockImplementation((param: BucketerParams) => {
1393+
const ruleKey = param.experimentKey;
1394+
if (ruleKey == 'exp_3') {
1395+
return { result: param.trafficAllocationConfig[0].entityId, reasons: [] }
1396+
}
1397+
return {
1398+
result: null,
1399+
reasons: [],
1400+
}
1401+
});
1402+
13431403
cmabService.getDecision.mockResolvedValue({
13441404
variationId: '5003',
13451405
cmabUuid: 'uuid-test',
@@ -1357,6 +1417,8 @@ describe('DecisionService', () => {
13571417
decisionSource: DECISION_SOURCES.FEATURE_TEST,
13581418
});
13591419

1420+
verifyBucketCall(0, config, config.experimentKeyMap['exp_3'], user);
1421+
13601422
expect(cmabService.getDecision).toHaveBeenCalledTimes(1);
13611423
expect(cmabService.getDecision).toHaveBeenCalledWith(
13621424
config,
@@ -1379,6 +1441,17 @@ describe('DecisionService', () => {
13791441
},
13801442
});
13811443

1444+
mockBucket.mockImplementation((param: BucketerParams) => {
1445+
const ruleKey = param.experimentKey;
1446+
if (ruleKey == 'exp_3') {
1447+
return { result: param.trafficAllocationConfig[0].entityId, reasons: [] }
1448+
}
1449+
return {
1450+
result: null,
1451+
reasons: [],
1452+
}
1453+
});
1454+
13821455
cmabService.getDecision.mockResolvedValue({
13831456
variationId: '5003',
13841457
cmabUuid: 'uuid-test',
@@ -1424,6 +1497,17 @@ describe('DecisionService', () => {
14241497
},
14251498
});
14261499

1500+
mockBucket.mockImplementation((param: BucketerParams) => {
1501+
const ruleKey = param.experimentKey;
1502+
if (ruleKey == 'exp_3') {
1503+
return { result: param.trafficAllocationConfig[0].entityId, reasons: [] }
1504+
}
1505+
return {
1506+
result: null,
1507+
reasons: [],
1508+
}
1509+
});
1510+
14271511
cmabService.getDecision.mockRejectedValue(new Error('I am an error'));
14281512

14291513
const feature = config.featureKeyMap['flag_1'];
@@ -1474,6 +1558,17 @@ describe('DecisionService', () => {
14741558

14751559
userProfileServiceAsync?.save.mockImplementation(() => Promise.resolve());
14761560

1561+
mockBucket.mockImplementation((param: BucketerParams) => {
1562+
const ruleKey = param.experimentKey;
1563+
if (ruleKey == 'exp_3') {
1564+
return { result: param.trafficAllocationConfig[0].entityId, reasons: [] }
1565+
}
1566+
return {
1567+
result: null,
1568+
reasons: [],
1569+
}
1570+
});
1571+
14771572
cmabService.getDecision.mockResolvedValue({
14781573
variationId: '5003',
14791574
cmabUuid: 'uuid-test',
@@ -1552,6 +1647,17 @@ describe('DecisionService', () => {
15521647

15531648
userProfileServiceAsync?.save.mockImplementation(() => Promise.resolve());
15541649

1650+
mockBucket.mockImplementation((param: BucketerParams) => {
1651+
const ruleKey = param.experimentKey;
1652+
if (ruleKey == 'exp_3') {
1653+
return { result: param.trafficAllocationConfig[0].entityId, reasons: [] }
1654+
}
1655+
return {
1656+
result: null,
1657+
reasons: [],
1658+
}
1659+
});
1660+
15551661
cmabService.getDecision.mockResolvedValue({
15561662
variationId: '5003',
15571663
cmabUuid: 'uuid-test',
@@ -1605,6 +1711,16 @@ describe('DecisionService', () => {
16051711

16061712
userProfileServiceAsync?.save.mockRejectedValue(new Error('I am an error'));
16071713

1714+
mockBucket.mockImplementation((param: BucketerParams) => {
1715+
const ruleKey = param.experimentKey;
1716+
if (ruleKey == 'exp_3') {
1717+
return { result: param.trafficAllocationConfig[0].entityId, reasons: [] }
1718+
}
1719+
return {
1720+
result: null,
1721+
reasons: [],
1722+
}
1723+
});
16081724

16091725
cmabService.getDecision.mockResolvedValue({
16101726
variationId: '5003',
@@ -1669,6 +1785,16 @@ describe('DecisionService', () => {
16691785
userProfileServiceAsync?.lookup.mockResolvedValue(null);
16701786
userProfileServiceAsync?.save.mockResolvedValue(null);
16711787

1788+
mockBucket.mockImplementation((param: BucketerParams) => {
1789+
const ruleKey = param.experimentKey;
1790+
if (ruleKey == 'exp_3') {
1791+
return { result: param.trafficAllocationConfig[0].entityId, reasons: [] }
1792+
}
1793+
return {
1794+
result: null,
1795+
reasons: [],
1796+
}
1797+
});
16721798

16731799
cmabService.getDecision.mockResolvedValue({
16741800
variationId: '5003',

0 commit comments

Comments
 (0)