From f599423917f2b7ddef46f9cfa0e4c788882c1b12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20L=C3=B3pez=20Dato?= Date: Thu, 10 Oct 2024 09:57:01 -0300 Subject: [PATCH 1/2] Revert "feat: support multiple environment caching" --- flagsmith-core.ts | 56 +++++++++------------ lib/flagsmith-es/package.json | 2 +- lib/flagsmith/package.json | 2 +- lib/react-native-flagsmith/package.json | 2 +- test/cache.test.ts | 67 +++++++++++++------------ test/default-flags.test.ts | 4 +- test/react.test.tsx | 36 ++----------- test/test-constants.ts | 2 +- types.d.ts | 1 - 9 files changed, 68 insertions(+), 104 deletions(-) diff --git a/flagsmith-core.ts b/flagsmith-core.ts index f1381c6..73ddec7 100644 --- a/flagsmith-core.ts +++ b/flagsmith-core.ts @@ -41,9 +41,8 @@ type RequestOptions = { } let AsyncStorage: AsyncStorageType = null; -const DEFAULT_FLAGSMITH_KEY = "FLAGSMITH_DB"; -const DEFAULT_FLAGSMITH_EVENT = "FLAGSMITH_EVENT"; -let FlagsmithEvent = DEFAULT_FLAGSMITH_EVENT; +const FLAGSMITH_KEY = "BULLET_TRAIN_DB"; +const FLAGSMITH_EVENT = "BULLET_TRAIN_EVENT"; const defaultAPI = 'https://edge.api.flagsmith.com/api/v1/'; let eventSource: typeof EventSource; const initError = function(caller: string) { @@ -81,7 +80,7 @@ const Flagsmith = class { } getFlags = () => { - const { api, evaluationContext } = this; + let { api, evaluationContext } = this; this.log("Get Flags") this.isLoading = true; @@ -272,7 +271,7 @@ const Flagsmith = class { timer: number|null= null dtrum= null withTraits?: ITraits|null= null - cacheOptions = {ttl:0, skipAPI: false, loadStale: false, storageKey: undefined as string|undefined} + cacheOptions = {ttl:0, skipAPI: false, loadStale: false} async init(config: IInitConfig) { const evaluationContext = toEvaluationContext(config.evaluationContext || {}); try { @@ -291,7 +290,7 @@ const Flagsmith = class { enableDynatrace, enableAnalytics, realtime, - eventSourceUrl= "https://realtime.flagsmith.com/", + eventSourceUrl= "https://realtime.flagsmith.com/", AsyncStorage: _AsyncStorage, identity, traits, @@ -332,7 +331,7 @@ const Flagsmith = class { onError?.(message); }; this.enableLogs = enableLogs || false; - this.cacheOptions = cacheOptions ? { skipAPI: !!cacheOptions.skipAPI, ttl: cacheOptions.ttl || 0, storageKey:cacheOptions.storageKey, loadStale: !!cacheOptions.loadStale } : this.cacheOptions; + this.cacheOptions = cacheOptions ? { skipAPI: !!cacheOptions.skipAPI, ttl: cacheOptions.ttl || 0, loadStale: !!cacheOptions.loadStale } : this.cacheOptions; if (!this.cacheOptions.ttl && this.cacheOptions.skipAPI) { console.warn("Flagsmith: you have set a cache ttl of 0 and are skipping API calls, this means the API will not be hit unless you clear local storage.") } @@ -346,9 +345,6 @@ const Flagsmith = class { this.ticks = 10000; this.timer = this.enableLogs ? new Date().valueOf() : null; this.cacheFlags = typeof AsyncStorage !== 'undefined' && !!cacheFlags; - - FlagsmithEvent = DEFAULT_FLAGSMITH_EVENT + "_" + evaluationContext.environment.apiKey; - if (_AsyncStorage) { AsyncStorage = _AsyncStorage; } @@ -385,7 +381,7 @@ const Flagsmith = class { } if (AsyncStorage && this.canUseStorage) { - AsyncStorage.getItem(FlagsmithEvent) + AsyncStorage.getItem(FLAGSMITH_EVENT) .then((res)=>{ try { this.evaluationEvent = JSON.parse(res!) || {} @@ -402,12 +398,12 @@ const Flagsmith = class { } if (AsyncStorage && this.canUseStorage) { - AsyncStorage.getItem(FlagsmithEvent, (err, res) => { + AsyncStorage.getItem(FLAGSMITH_EVENT, (err, res) => { if (res && this.evaluationContext.environment) { const json = JSON.parse(res); if (json[this.evaluationContext.environment.apiKey]) { - const state = this.getState(); - this.log("Retrieved events from cache", res); + const state = this.getState(); + this.log("Retrieved events from cache", res); this.setState({ ...state, evaluationEvent: json[this.evaluationContext.environment.apiKey], @@ -457,7 +453,7 @@ const Flagsmith = class { ...json, evaluationContext: toEvaluationContext({ ...json.evaluationContext, - identity: json.evaluationContext?.identity ? { + identity: !!json.evaluationContext?.identity ? { ...json.evaluationContext?.identity, traits: { ...json.evaluationContext?.identity?.traits || {}, @@ -514,7 +510,7 @@ const Flagsmith = class { } }; try { - const res = AsyncStorage.getItemSync? AsyncStorage.getItemSync(this.getStorageKey()) : await AsyncStorage.getItem(this.getStorageKey()); + const res = AsyncStorage.getItemSync? AsyncStorage.getItemSync(FLAGSMITH_KEY) : await AsyncStorage.getItem(FLAGSMITH_KEY); await onRetrievedStorage(null, res) } catch (e) {} } @@ -542,6 +538,15 @@ const Flagsmith = class { } } + private _loadedState(error: any = null, source: FlagSource, isFetching = false) { + return { + error, + isFetching, + isLoading: false, + source + } + } + getAllFlags() { return this.flags; } @@ -655,7 +660,7 @@ const Flagsmith = class { } setContext = (clientEvaluationContext: ClientEvaluationContext) => { - const evaluationContext = toEvaluationContext(clientEvaluationContext); + let evaluationContext = toEvaluationContext(clientEvaluationContext); this.evaluationContext = { ...evaluationContext, environment: evaluationContext.environment || this.evaluationContext.environment, @@ -740,19 +745,6 @@ const Flagsmith = class { return res; }; - private _loadedState(error: any = null, source: FlagSource, isFetching = false) { - return { - error, - isFetching, - isLoading: false, - source - } - } - - private getStorageKey = ()=> { - return this.cacheOptions?.storageKey || DEFAULT_FLAGSMITH_KEY + "_" + this.evaluationContext.environment?.apiKey - } - private log(...args: (unknown)[]) { if (this.enableLogs) { console.log.apply(this, ['FLAGSMITH:', new Date().valueOf() - (this.timer || 0), 'ms', ...args]); @@ -764,7 +756,7 @@ const Flagsmith = class { this.ts = new Date().valueOf(); const state = JSON.stringify(this.getState()); this.log('Setting storage', state); - AsyncStorage!.setItem(this.getStorageKey(), state); + AsyncStorage!.setItem(FLAGSMITH_KEY, state); } } @@ -828,7 +820,7 @@ const Flagsmith = class { private updateEventStorage() { if (this.enableAnalytics) { const events = JSON.stringify(this.getState().evaluationEvent); - AsyncStorage!.setItem(FlagsmithEvent, events); + AsyncStorage!.setItem(FLAGSMITH_EVENT, events); } } diff --git a/lib/flagsmith-es/package.json b/lib/flagsmith-es/package.json index d295b30..fc00c89 100644 --- a/lib/flagsmith-es/package.json +++ b/lib/flagsmith-es/package.json @@ -1,6 +1,6 @@ { "name": "flagsmith-es", - "version": "6.0.0", + "version": "5.0.0", "description": "Feature flagging to support continuous development. This is an esm equivalent of the standard flagsmith npm module.", "main": "./index.js", "type": "module", diff --git a/lib/flagsmith/package.json b/lib/flagsmith/package.json index 972d4f5..68988d6 100644 --- a/lib/flagsmith/package.json +++ b/lib/flagsmith/package.json @@ -1,6 +1,6 @@ { "name": "flagsmith", - "version": "6.0.0", + "version": "5.0.0", "description": "Feature flagging to support continuous development", "main": "./index.js", "repository": { diff --git a/lib/react-native-flagsmith/package.json b/lib/react-native-flagsmith/package.json index 06416c3..bcbde83 100644 --- a/lib/react-native-flagsmith/package.json +++ b/lib/react-native-flagsmith/package.json @@ -1,6 +1,6 @@ { "name": "react-native-flagsmith", - "version": "6.0.0", + "version": "5.0.0", "description": "Feature flagging to support continuous development", "main": "./index.js", "repository": { diff --git a/test/cache.test.ts b/test/cache.test.ts index cb70e15..d88a73b 100644 --- a/test/cache.test.ts +++ b/test/cache.test.ts @@ -1,7 +1,7 @@ +// Sample test import { defaultState, defaultStateAlt, - FLAGSMITH_KEY, getFlagsmith, getStateToCheck, identityState, @@ -33,21 +33,7 @@ describe('Cache', () => { onChange, }); await flagsmith.init(initConfig); - const cache = await AsyncStorage.getItem(FLAGSMITH_KEY); - expect(getStateToCheck(JSON.parse(`${cache}`))).toEqual(defaultState); - }); - test('should set cache after init with custom key', async () => { - const onChange = jest.fn(); - const customKey = 'custom_key'; - const { flagsmith, initConfig, AsyncStorage, mockFetch } = getFlagsmith({ - cacheFlags: true, - cacheOptions: { - storageKey: customKey, - }, - onChange, - }); - await flagsmith.init(initConfig); - const cache = await AsyncStorage.getItem(customKey); + const cache = await AsyncStorage.getItem('BULLET_TRAIN_DB'); expect(getStateToCheck(JSON.parse(`${cache}`))).toEqual(defaultState); }); test('should call onChange with cache then eventually with an API response', async () => { @@ -67,7 +53,7 @@ describe('Cache', () => { cacheFlags: true, onChange, }); - await AsyncStorage.setItem(FLAGSMITH_KEY, JSON.stringify(defaultStateAlt)); + await AsyncStorage.setItem('BULLET_TRAIN_DB', JSON.stringify(defaultStateAlt)); await flagsmith.init(initConfig); // Flags retrieved from cache @@ -100,7 +86,7 @@ describe('Cache', () => { identity: testIdentity, onChange, }); - await AsyncStorage.setItem(FLAGSMITH_KEY, JSON.stringify({ + await AsyncStorage.setItem('BULLET_TRAIN_DB', JSON.stringify({ ...defaultStateAlt, identity: 'bad_identity', })); @@ -116,7 +102,7 @@ describe('Cache', () => { onChange, cacheOptions: { ttl: 1 }, }); - await AsyncStorage.setItem(FLAGSMITH_KEY, JSON.stringify({ + await AsyncStorage.setItem('BULLET_TRAIN_DB', JSON.stringify({ ...defaultStateAlt, ts: new Date().valueOf() - 100, })); @@ -134,7 +120,7 @@ describe('Cache', () => { onChange, cacheOptions: { ttl: 1, loadStale: true }, }); - await AsyncStorage.setItem(FLAGSMITH_KEY, JSON.stringify({ + await AsyncStorage.setItem('BULLET_TRAIN_DB', JSON.stringify({ ...defaultStateAlt, ts: new Date().valueOf() - 100, })); @@ -152,7 +138,7 @@ describe('Cache', () => { onChange, cacheOptions: { ttl: 1000 }, }); - await AsyncStorage.setItem(FLAGSMITH_KEY, JSON.stringify({ + await AsyncStorage.setItem('BULLET_TRAIN_DB', JSON.stringify({ ...defaultStateAlt, ts: new Date().valueOf(), })); @@ -169,7 +155,7 @@ describe('Cache', () => { cacheFlags: false, onChange, }); - await AsyncStorage.setItem(FLAGSMITH_KEY, JSON.stringify({ + await AsyncStorage.setItem('BULLET_TRAIN_DB', JSON.stringify({ ...defaultStateAlt, ts: new Date().valueOf(), })); @@ -187,7 +173,25 @@ describe('Cache', () => { onChange, cacheOptions: { ttl: 1000, skipAPI: true }, }); - await AsyncStorage.setItem(FLAGSMITH_KEY, JSON.stringify({ + await AsyncStorage.setItem('BULLET_TRAIN_DB', JSON.stringify({ + ...defaultStateAlt, + ts: new Date().valueOf(), + })); + await flagsmith.init(initConfig); + expect(onChange).toHaveBeenCalledTimes(1); + expect(mockFetch).toHaveBeenCalledTimes(0); + expect(getStateToCheck(flagsmith.getState())).toEqual({ + ...defaultStateAlt, + }); + }); + test('should not get flags from API when skipAPI is set', async () => { + const onChange = jest.fn(); + const { flagsmith, initConfig, AsyncStorage, mockFetch } = getFlagsmith({ + cacheFlags: true, + onChange, + cacheOptions: { ttl: 1000, skipAPI: true }, + }); + await AsyncStorage.setItem('BULLET_TRAIN_DB', JSON.stringify({ ...defaultStateAlt, ts: new Date().valueOf(), })); @@ -205,7 +209,7 @@ describe('Cache', () => { onChange, cacheOptions: { ttl: 1, skipAPI: true, loadStale: true }, }); - await AsyncStorage.setItem(FLAGSMITH_KEY, JSON.stringify({ + await AsyncStorage.setItem('BULLET_TRAIN_DB', JSON.stringify({ ...defaultStateAlt, ts: new Date().valueOf() - 100, })); @@ -216,7 +220,6 @@ describe('Cache', () => { ...defaultStateAlt, }); }); - test('should validate flags are unchanged when fetched', async () => { const onChange = jest.fn(); const { flagsmith, initConfig, AsyncStorage, mockFetch } = getFlagsmith({ @@ -224,7 +227,7 @@ describe('Cache', () => { cacheFlags: true, preventFetch: true, }); - await AsyncStorage.setItem(FLAGSMITH_KEY, JSON.stringify({ + await AsyncStorage.setItem('BULLET_TRAIN_DB', JSON.stringify({ ...defaultState, })); await flagsmith.init(initConfig); @@ -270,7 +273,7 @@ describe('Cache', () => { preventFetch: true, defaultFlags: defaultState.flags, }); - await AsyncStorage.setItem(FLAGSMITH_KEY, JSON.stringify({ + await AsyncStorage.setItem('BULLET_TRAIN_DB', JSON.stringify({ ...defaultState, })); await flagsmith.init(initConfig); @@ -316,7 +319,7 @@ describe('Cache', () => { preventFetch: true, }); const storage = new SyncStorageMock(); - await storage.setItem(FLAGSMITH_KEY, JSON.stringify({ + await storage.setItem('BULLET_TRAIN_DB', JSON.stringify({ ...defaultState, })); flagsmith.init({ @@ -342,7 +345,7 @@ describe('Cache', () => { preventFetch: true, }); const storage = new SyncStorageMock(); - await storage.setItem(FLAGSMITH_KEY, JSON.stringify({ + await storage.setItem('BULLET_TRAIN_DB', JSON.stringify({ ...identityState, })); const ts = Date.now(); @@ -353,8 +356,8 @@ describe('Cache', () => { }); expect(flagsmith.getAllTraits()).toEqual({ ...identityState.traits, - ts, - }); + ts + }) }); test('should cache transient traits correctly', async () => { const onChange = jest.fn(); @@ -392,4 +395,4 @@ describe('Cache', () => { }, }) }); -}); +}); \ No newline at end of file diff --git a/test/default-flags.test.ts b/test/default-flags.test.ts index a95548e..7c21407 100644 --- a/test/default-flags.test.ts +++ b/test/default-flags.test.ts @@ -1,5 +1,5 @@ // Sample test -import { defaultState, defaultStateAlt, FLAGSMITH_KEY, getFlagsmith, getStateToCheck } from './test-constants'; +import { defaultState, defaultStateAlt, getFlagsmith, getStateToCheck } from './test-constants'; import { IFlags } from '../types'; describe('Default Flags', () => { @@ -51,7 +51,7 @@ describe('Default Flags', () => { cacheFlags: true, defaultFlags: {...defaultFlags, ...itemsToRemove}, }); - await AsyncStorage.setItem(FLAGSMITH_KEY, JSON.stringify({ + await AsyncStorage.setItem('BULLET_TRAIN_DB', JSON.stringify({ ...defaultState, flags: { ...defaultFlags, diff --git a/test/react.test.tsx b/test/react.test.tsx index a91482c..a2feadc 100644 --- a/test/react.test.tsx +++ b/test/react.test.tsx @@ -2,9 +2,8 @@ import React, { FC } from 'react'; import { render, screen, waitFor } from '@testing-library/react'; import { FlagsmithProvider, useFlags, useFlagsmithLoading } from '../lib/flagsmith/react'; import { - defaultState, - delay, - FLAGSMITH_KEY, + defaultState, delay, + environmentID, getFlagsmith, getMockFetchWithValue, identityState, @@ -26,7 +25,6 @@ const FlagsmithPage: FC = () => { ); }; - export default FlagsmithPage; describe('FlagsmithProvider', () => { it('renders without crashing', () => { @@ -93,34 +91,7 @@ describe('FlagsmithProvider', () => { preventFetch: true, defaultFlags: defaultState.flags }); - await AsyncStorage.setItem(FLAGSMITH_KEY, JSON.stringify({ - ...defaultState - }) ) - render( - - - - ); - - await waitFor(() => { - expect(JSON.parse(screen.getByTestId("loading-state").innerHTML)).toEqual({"isLoading":false,"isFetching":false,"error":null,"source":"CACHE"}); - expect(JSON.parse(screen.getByTestId("flags").innerHTML)).toEqual(removeIds(defaultState.flags)); - }); - }); - - it('renders cached flags by custom key', async () => { - const customKey = 'custom_key'; - const onChange = jest.fn(); - const { flagsmith, initConfig, AsyncStorage } = getFlagsmith({ - onChange, - cacheFlags: true, - preventFetch: true, - defaultFlags: defaultState.flags, - cacheOptions: { - storageKey: customKey, - }, - }); - await AsyncStorage.setItem(customKey, JSON.stringify({ + await AsyncStorage.setItem("BULLET_TRAIN_DB", JSON.stringify({ ...defaultState }) ) render( @@ -134,7 +105,6 @@ describe('FlagsmithProvider', () => { expect(JSON.parse(screen.getByTestId("flags").innerHTML)).toEqual(removeIds(defaultState.flags)); }); }); - it('renders default flags', async () => { const onChange = jest.fn(); diff --git a/test/test-constants.ts b/test/test-constants.ts index 0da6b4c..20aebac 100644 --- a/test/test-constants.ts +++ b/test/test-constants.ts @@ -5,7 +5,7 @@ import Mock = jest.Mock; import { promises as fs } from 'fs'; export const environmentID = 'QjgYur4LQTwe5HpvbvhpzK'; // Flagsmith Demo Projects -export const FLAGSMITH_KEY = 'FLAGSMITH_DB' + "_" + environmentID; + export const defaultState = { api: 'https://edge.api.flagsmith.com/api/v1/', evaluationContext: { diff --git a/types.d.ts b/types.d.ts index 1dae996..4ffbb11 100644 --- a/types.d.ts +++ b/types.d.ts @@ -57,7 +57,6 @@ export interface IState { declare type ICacheOptions = { ttl?: number; skipAPI?: boolean; - storageKey?: string; loadStale?: boolean; }; From c772f719fce6e1c09d33ef3f11a7f4cea7925b40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20L=C3=B3pez=20Dato?= Date: Thu, 10 Oct 2024 09:59:01 -0300 Subject: [PATCH 2/2] 7.0.0 --- lib/flagsmith-es/package.json | 2 +- lib/flagsmith/package.json | 2 +- lib/react-native-flagsmith/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/flagsmith-es/package.json b/lib/flagsmith-es/package.json index fc00c89..6187eaf 100644 --- a/lib/flagsmith-es/package.json +++ b/lib/flagsmith-es/package.json @@ -1,6 +1,6 @@ { "name": "flagsmith-es", - "version": "5.0.0", + "version": "7.0.0", "description": "Feature flagging to support continuous development. This is an esm equivalent of the standard flagsmith npm module.", "main": "./index.js", "type": "module", diff --git a/lib/flagsmith/package.json b/lib/flagsmith/package.json index 68988d6..9d16522 100644 --- a/lib/flagsmith/package.json +++ b/lib/flagsmith/package.json @@ -1,6 +1,6 @@ { "name": "flagsmith", - "version": "5.0.0", + "version": "7.0.0", "description": "Feature flagging to support continuous development", "main": "./index.js", "repository": { diff --git a/lib/react-native-flagsmith/package.json b/lib/react-native-flagsmith/package.json index bcbde83..2c7c84a 100644 --- a/lib/react-native-flagsmith/package.json +++ b/lib/react-native-flagsmith/package.json @@ -1,6 +1,6 @@ { "name": "react-native-flagsmith", - "version": "5.0.0", + "version": "7.0.0", "description": "Feature flagging to support continuous development", "main": "./index.js", "repository": {