Skip to content
Draft
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
10 changes: 7 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ jobs:

- name: Test
run: yarn test
env:
MANAGEMENT_API_KEY: ${{ secrets.MANAGEMENT_API_KEY }}

- name: Configure Git User
run: |
Expand All @@ -58,7 +60,8 @@ jobs:
if: ${{ github.event.inputs.dryRun == 'true'}}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: yarn lerna version --no-push --no-git-tag-version --loglevel silly --yes
run: |
yarn lerna version --no-push --no-git-tag-version --loglevel silly --yes

- name: Setup NPM Token
if: ${{ github.event.inputs.dryRun == 'false'}}
Expand All @@ -71,6 +74,7 @@ jobs:
if: ${{ github.event.inputs.dryRun == 'false'}}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Adds all params to both version and publish just to be safe.
run: |
yarn lerna version --yes
yarn lerna publish from-git --yes --loglevel silly
yarn lerna version --conventionalPrerelease --preid=beta --no-private
yarn lerna publish from-git --loglevel silly --dist-tag=beta --pre-dist-tag=beta --conventionalPrerelease --preid=beta --no-private
2 changes: 2 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,5 @@ jobs:

- name: Test
run: yarn test
env:
MANAGEMENT_API_KEY: ${{ secrets.MANAGEMENT_API_KEY }}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,6 @@ dist/

# Example Experiment tag script
packages/experiment-tag/example/

# Environment variables
.env*
5 changes: 3 additions & 2 deletions lerna.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@
"useWorkspaces": true,
"command": {
"version": {
"allowBranch": "main",
"allowBranch": "stream-vardata",
"conventionalCommits": true,
"createRelease": "github",
"message": "chore(release): publish",
"preid": "beta"
"preid": "beta",
"conventionalPrerelease": true
}
}
}
93 changes: 53 additions & 40 deletions packages/analytics-connector/src/util/equals.ts
Original file line number Diff line number Diff line change
@@ -1,54 +1,67 @@
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const isEqual = (obj1: any, obj2: any): boolean => {
const primitive = ['string', 'number', 'boolean', 'undefined'];
const typeA = typeof obj1;
const typeB = typeof obj2;
if (typeA !== typeB) {
return false;
}
for (const p of primitive) {
if (p === typeA) {
return obj1 === obj2;
}
}
// check null
if (obj1 == null && obj2 == null) {
export const isEqual = (value1: any, value2: any, seen = new WeakSet()) => {
// 1. Strict equality check for primitives and same object references
if (value1 === value2) {
return true;
} else if (obj1 == null || obj2 == null) {
return false;
}
// if got here - objects
if (obj1.length !== obj2.length) {

// 2. Handle null and undefined (already covered by === if both are null/undefined)
// If one is null/undefined and the other is not, they are not equal.
if (value1 == null || value2 == null) {
return false;
}
//check if arrays
const isArrayA = Array.isArray(obj1);
const isArrayB = Array.isArray(obj2);
if (isArrayA !== isArrayB) {

// 3. Handle different types
if (typeof value1 !== typeof value2) {
return false;
}
if (isArrayA && isArrayB) {
//arrays
for (let i = 0; i < obj1.length; i++) {
if (!isEqual(obj1[i], obj2[i])) {

// 4. Handle specific object types (Date, RegExp)
if (value1 instanceof Date && value2 instanceof Date) {
return value1.getTime() === value2.getTime();
}
if (value1 instanceof RegExp && value2 instanceof RegExp) {
return value1.source === value2.source && value1.flags === value2.flags;
}

// 5. Handle objects and arrays (deep comparison)
if (typeof value1 === 'object' && typeof value2 === 'object') {
// Prevent infinite recursion with circular references
if (seen.has(value1) && seen.has(value2)) {
return true; // Already processed and found to be equal
}
seen.add(value1);
seen.add(value2);

// Compare arrays
if (Array.isArray(value1) && Array.isArray(value2)) {
if (value1.length !== value2.length) {
return false;
}
for (let i = 0; i < value1.length; i++) {
if (!isEqual(value1[i], value2[i], seen)) {
return false;
}
}
return true;
}
} else {
//objects
const sorted1 = Object.keys(obj1).sort();
const sorted2 = Object.keys(obj2).sort();
if (!isEqual(sorted1, sorted2)) {

// Compare plain objects
const keys1 = Object.keys(value1);
const keys2 = Object.keys(value2);

if (keys1.length !== keys2.length) {
return false;
}
//compare object values
let result = true;
Object.keys(obj1).forEach((key) => {
if (!isEqual(obj1[key], obj2[key])) {
result = false;

for (const key of keys1) {
if (!keys2.includes(key) || !isEqual(value1[key], value2[key], seen)) {
return false;
}
});
return result;
}
return true;
}
return true;
};

// 6. Default case: values are not equal
return false;
}
8 changes: 8 additions & 0 deletions packages/experiment-browser/jest.config.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const { pathsToModuleNameMapper } = require('ts-jest');
const dotenv = require('dotenv');

const package = require('./package');
const { compilerOptions } = require('./tsconfig.test.json');

dotenv.config({path: process.env['ENVIRONMENT'] ? '.env.' + process.env['ENVIRONMENT'] : '.env'});
const MANAGEMENT_API_SERVER_URL = process.env['MANAGEMENT_API_SERVER_URL'] || 'https://experiment.amplitude.com';

module.exports = {
preset: 'ts-jest',
testEnvironment: 'jsdom',
Expand All @@ -16,4 +20,8 @@ module.exports = {
'^.+\\.tsx?$': ['ts-jest', { tsconfig: './tsconfig.test.json' }],
},
testTimeout: 10 * 1000,
// Remove this once management-api has CORS properly configured.
testEnvironmentOptions: {
url: MANAGEMENT_API_SERVER_URL,
},
};
1 change: 1 addition & 0 deletions packages/experiment-browser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"@amplitude/experiment-core": "^0.11.0",
"@amplitude/ua-parser-js": "^0.7.31",
"base64-js": "1.5.1",
"eventsource": "^2",
"unfetch": "4.1.0"
},
"devDependencies": {
Expand Down
14 changes: 14 additions & 0 deletions packages/experiment-browser/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,18 @@ export interface ExperimentConfig {
*/
flagConfigPollingIntervalMillis?: number;

/**
* If true, the client will stream updates for remote evaluation from the server.
* fetch() will update variants and initiate a connection to the server.
*/
streamVariants?: boolean;

/**
* The URL to stream remote evaluation updates from. This is only used if
* `streamVariants` is `true`.
*/
streamVariantsServerUrl?: string;

/**
* Explicitly enable or disable calling {@link fetch()} on {@link start()}:
*
Expand Down Expand Up @@ -190,6 +202,8 @@ export const Defaults: ExperimentConfig = {
automaticExposureTracking: true,
pollOnStart: true,
flagConfigPollingIntervalMillis: 300000,
streamVariants: false,
streamVariantsServerUrl: 'https://stream.lab.amplitude.com',
fetchOnStart: true,
automaticFetchOnAmplitudeIdentityChange: false,
userProvider: null,
Expand Down
Loading