mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: make client features endpoint memoizable (#734)
This commit is contained in:
		
							parent
							
								
									35486efe77
								
							
						
					
					
						commit
						dddfbbf645
					
				| @ -121,6 +121,7 @@ | ||||
|     "passport-google-auth": "^1.0.2", | ||||
|     "prettier": "^1.19.1", | ||||
|     "proxyquire": "^2.1.3", | ||||
|     "sinon": "^9.2.4", | ||||
|     "superagent": "^6.1.0", | ||||
|     "supertest": "^5.0.0", | ||||
|     "tsc-watch": "^4.2.9", | ||||
|  | ||||
| @ -90,6 +90,12 @@ function defaultOptions() { | ||||
|         version, | ||||
|         secureHeaders: process.env.SECURE_HEADERS || false, | ||||
|         enableOAS: process.env.ENABLE_OAS || false, | ||||
|         experimental: { | ||||
|             clientFeatureMemoize: { | ||||
|                 enabled: process.env.CLIENT_FEATURE_MEMOIZE || false, | ||||
|                 maxAge: process.env.CLIENT_FEATURE_MAXAGE || 1000, | ||||
|             }, | ||||
|         }, | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -2,6 +2,8 @@ | ||||
| 
 | ||||
| import { handleErrors } from '../admin-api/util'; | ||||
| 
 | ||||
| const memoizee = require('memoizee'); | ||||
| 
 | ||||
| const Controller = require('../controller'); | ||||
| 
 | ||||
| const version = 1; | ||||
| @ -16,20 +18,43 @@ const FEATURE_COLUMNS_CLIENT = [ | ||||
| ]; | ||||
| 
 | ||||
| class FeatureController extends Controller { | ||||
|     constructor({ featureToggleService }, getLogger) { | ||||
|     constructor({ featureToggleService }, { getLogger, experimental }) { | ||||
|         super(); | ||||
|         this.toggleService = featureToggleService; | ||||
|         this.logger = getLogger('client-api/feature.js'); | ||||
|         this.get('/', this.getAll); | ||||
|         this.get('/:featureName', this.getFeatureToggle); | ||||
|         if (experimental && experimental.clientFeatureMemoize) { | ||||
|             this.cache = experimental.clientFeatureMemoize.enabled; | ||||
|             this.cachedFeatures = memoizee( | ||||
|                 (query, fields) => | ||||
|                     this.toggleService.getFeatures(query, fields), | ||||
|                 { | ||||
|                     promise: true, | ||||
|                     maxAge: experimental.clientFeatureMemoize.maxAge, | ||||
|                     normalizer(args) { | ||||
|                         // args is arguments object as accessible in memoized function
 | ||||
|                         return JSON.stringify(args[0]); | ||||
|                     }, | ||||
|                 }, | ||||
|             ); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     async getAll(req, res) { | ||||
|         try { | ||||
|             const features = await this.toggleService.getFeatures( | ||||
|                 req.query, | ||||
|                 FEATURE_COLUMNS_CLIENT, | ||||
|             ); | ||||
|             let features; | ||||
|             if (this.cache) { | ||||
|                 features = await this.cachedFeatures( | ||||
|                     req.query, | ||||
|                     FEATURE_COLUMNS_CLIENT, | ||||
|                 ); | ||||
|             } else { | ||||
|                 features = await this.toggleService.getFeatures( | ||||
|                     req.query, | ||||
|                     FEATURE_COLUMNS_CLIENT, | ||||
|                 ); | ||||
|             } | ||||
|             res.json({ version, features }); | ||||
|         } catch (e) { | ||||
|             handleErrors(res, this.logger, e); | ||||
|  | ||||
| @ -3,10 +3,12 @@ | ||||
| const test = require('ava'); | ||||
| const supertest = require('supertest'); | ||||
| const { EventEmitter } = require('events'); | ||||
| const sinon = require('sinon'); | ||||
| const store = require('../../../test/fixtures/store'); | ||||
| const getLogger = require('../../../test/fixtures/no-logger'); | ||||
| const getApp = require('../../app'); | ||||
| const { createServices } = require('../../services'); | ||||
| const FeatureController = require('./feature'); | ||||
| 
 | ||||
| const eventBus = new EventEmitter(); | ||||
| 
 | ||||
| @ -40,6 +42,52 @@ test('should get empty getFeatures via client', t => { | ||||
|         }); | ||||
| }); | ||||
| 
 | ||||
| test('if caching is enabled should memoize', t => { | ||||
|     const getFeatures = sinon.fake.returns([]); | ||||
| 
 | ||||
|     const featureToggleService = { | ||||
|         getFeatures, | ||||
|     }; | ||||
|     const controller = new FeatureController( | ||||
|         { featureToggleService }, | ||||
|         { | ||||
|             getLogger, | ||||
|             experimental: { | ||||
|                 clientFeatureMemoize: { | ||||
|                     enabled: true, | ||||
|                     maxAge: 10000, | ||||
|                 }, | ||||
|             }, | ||||
|         }, | ||||
|     ); | ||||
|     controller.getAll({ query: {} }, { json: () => {} }); | ||||
|     controller.getAll({ query: {} }, { json: () => {} }); | ||||
|     t.is(getFeatures.callCount, 1); | ||||
| }); | ||||
| 
 | ||||
| test('if caching is not enabled all calls goes to service', t => { | ||||
|     const getFeatures = sinon.fake.returns([]); | ||||
| 
 | ||||
|     const featureToggleService = { | ||||
|         getFeatures, | ||||
|     }; | ||||
|     const controller = new FeatureController( | ||||
|         { featureToggleService }, | ||||
|         { | ||||
|             getLogger, | ||||
|             experimental: { | ||||
|                 clientFeatureMemoize: { | ||||
|                     enabled: false, | ||||
|                     maxAge: 10000, | ||||
|                 }, | ||||
|             }, | ||||
|         }, | ||||
|     ); | ||||
|     controller.getAll({ query: {} }, { json: () => {} }); | ||||
|     controller.getAll({ query: {} }, { json: () => {} }); | ||||
|     t.is(getFeatures.callCount, 2); | ||||
| }); | ||||
| 
 | ||||
| test('fetch single feature', t => { | ||||
|     t.plan(1); | ||||
|     const { request, featureToggleStore, base } = getSetup(); | ||||
|  | ||||
| @ -13,10 +13,7 @@ class ClientApi extends Controller { | ||||
|         const { getLogger } = config; | ||||
| 
 | ||||
|         this.get('/', this.index); | ||||
|         this.use( | ||||
|             '/features', | ||||
|             new FeatureController(services, getLogger).router, | ||||
|         ); | ||||
|         this.use('/features', new FeatureController(services, config).router); | ||||
|         this.use('/metrics', new MetricsController(services, getLogger).router); | ||||
|         this.use( | ||||
|             '/register', | ||||
|  | ||||
| @ -22,10 +22,7 @@ class IndexRouter extends Controller { | ||||
| 
 | ||||
|         // legacy support (remove in 4.x)
 | ||||
|         if (config.enableLegacyRoutes) { | ||||
|             const featureController = new FeatureController( | ||||
|                 services, | ||||
|                 config.getLogger, | ||||
|             ); | ||||
|             const featureController = new FeatureController(services, config); | ||||
|             this.use('/api/features', featureController.router); | ||||
|         } | ||||
|     } | ||||
|  | ||||
							
								
								
									
										75
									
								
								yarn.lock
									
									
									
									
									
								
							
							
						
						
									
										75
									
								
								yarn.lock
									
									
									
									
									
								
							| @ -454,6 +454,13 @@ | ||||
|   resolved "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz" | ||||
|   integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== | ||||
| 
 | ||||
| "@sinonjs/commons@^1.6.0", "@sinonjs/commons@^1.8.1": | ||||
|   version "1.8.2" | ||||
|   resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.2.tgz#858f5c4b48d80778fde4b9d541f27edc0d56488b" | ||||
|   integrity sha512-sruwd86RJHdsVf/AtBoijDmUqJp3B6hF/DGC23C+JaegnDHaZyewCjoVGTdg3J0uz3Zs7NnIT05OBOmML72lQw== | ||||
|   dependencies: | ||||
|     type-detect "4.0.8" | ||||
| 
 | ||||
| "@sinonjs/commons@^1.7.0": | ||||
|   version "1.8.1" | ||||
|   resolved "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.1.tgz" | ||||
| @ -461,6 +468,27 @@ | ||||
|   dependencies: | ||||
|     type-detect "4.0.8" | ||||
| 
 | ||||
| "@sinonjs/fake-timers@^6.0.0", "@sinonjs/fake-timers@^6.0.1": | ||||
|   version "6.0.1" | ||||
|   resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz#293674fccb3262ac782c7aadfdeca86b10c75c40" | ||||
|   integrity sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA== | ||||
|   dependencies: | ||||
|     "@sinonjs/commons" "^1.7.0" | ||||
| 
 | ||||
| "@sinonjs/samsam@^5.3.1": | ||||
|   version "5.3.1" | ||||
|   resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-5.3.1.tgz#375a45fe6ed4e92fca2fb920e007c48232a6507f" | ||||
|   integrity sha512-1Hc0b1TtyfBu8ixF/tpfSHTVWKwCBLY4QJbkgnE7HcwyvT2xArDxb4K7dMgqRm3szI+LJbzmW/s4xxEhv6hwDg== | ||||
|   dependencies: | ||||
|     "@sinonjs/commons" "^1.6.0" | ||||
|     lodash.get "^4.4.2" | ||||
|     type-detect "^4.0.8" | ||||
| 
 | ||||
| "@sinonjs/text-encoding@^0.7.1": | ||||
|   version "0.7.1" | ||||
|   resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5" | ||||
|   integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ== | ||||
| 
 | ||||
| "@szmarczak/http-timer@^1.1.2": | ||||
|   version "1.1.2" | ||||
|   resolved "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz" | ||||
| @ -1809,6 +1837,11 @@ dicer@0.2.5: | ||||
|     readable-stream "1.1.x" | ||||
|     streamsearch "0.1.2" | ||||
| 
 | ||||
| diff@^4.0.2: | ||||
|   version "4.0.2" | ||||
|   resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" | ||||
|   integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== | ||||
| 
 | ||||
| dir-glob@^3.0.1: | ||||
|   version "3.0.1" | ||||
|   resolved "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz" | ||||
| @ -3682,6 +3715,11 @@ jsprim@^1.2.2: | ||||
|     json-schema "0.2.3" | ||||
|     verror "1.10.0" | ||||
| 
 | ||||
| just-extend@^4.0.2: | ||||
|   version "4.1.1" | ||||
|   resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-4.1.1.tgz#158f1fdb01f128c411dc8b286a7b4837b3545282" | ||||
|   integrity sha512-aWgeGFW67BP3e5181Ep1Fv2v8z//iBJfrvyTnq8wG86vEESwmonn1zPBJ0VfmT9CJq2FIT0VsETtrNFm2a+SHA== | ||||
| 
 | ||||
| jwa@^1.4.1: | ||||
|   version "1.4.1" | ||||
|   resolved "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz" | ||||
| @ -3901,6 +3939,11 @@ lodash.flattendeep@^4.4.0: | ||||
|   resolved "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz" | ||||
|   integrity sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI= | ||||
| 
 | ||||
| lodash.get@^4.4.2: | ||||
|   version "4.4.2" | ||||
|   resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" | ||||
|   integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= | ||||
| 
 | ||||
| lodash.isequal@^4.5.0: | ||||
|   version "4.5.0" | ||||
|   resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" | ||||
| @ -4278,6 +4321,17 @@ nice-try@^1.0.4: | ||||
|   resolved "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz" | ||||
|   integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== | ||||
| 
 | ||||
| nise@^4.0.4: | ||||
|   version "4.1.0" | ||||
|   resolved "https://registry.yarnpkg.com/nise/-/nise-4.1.0.tgz#8fb75a26e90b99202fa1e63f448f58efbcdedaf6" | ||||
|   integrity sha512-eQMEmGN/8arp0xsvGoQ+B1qvSkR73B1nWSCh7nOt5neMCtwcQVYQGdzQMhcNscktTsWB54xnlSQFzOAPJD8nXA== | ||||
|   dependencies: | ||||
|     "@sinonjs/commons" "^1.7.0" | ||||
|     "@sinonjs/fake-timers" "^6.0.0" | ||||
|     "@sinonjs/text-encoding" "^0.7.1" | ||||
|     just-extend "^4.0.2" | ||||
|     path-to-regexp "^1.7.0" | ||||
| 
 | ||||
| node-cleanup@^2.1.2: | ||||
|   version "2.1.2" | ||||
|   resolved "https://registry.yarnpkg.com/node-cleanup/-/node-cleanup-2.1.2.tgz#7ac19abd297e09a7f72a71545d951b517e4dde2c" | ||||
| @ -4752,6 +4806,13 @@ path-to-regexp@0.1.7: | ||||
|   resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz" | ||||
|   integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= | ||||
| 
 | ||||
| path-to-regexp@^1.7.0: | ||||
|   version "1.8.0" | ||||
|   resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" | ||||
|   integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== | ||||
|   dependencies: | ||||
|     isarray "0.0.1" | ||||
| 
 | ||||
| path-to-regexp@^2.2.1: | ||||
|   version "2.4.0" | ||||
|   resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-2.4.0.tgz#35ce7f333d5616f1c1e1bfe266c3aba2e5b2e704" | ||||
| @ -5536,6 +5597,18 @@ signal-exit@^3.0.2: | ||||
|   resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz" | ||||
|   integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== | ||||
| 
 | ||||
| sinon@^9.2.4: | ||||
|   version "9.2.4" | ||||
|   resolved "https://registry.yarnpkg.com/sinon/-/sinon-9.2.4.tgz#e55af4d3b174a4443a8762fa8421c2976683752b" | ||||
|   integrity sha512-zljcULZQsJxVra28qIAL6ow1Z9tpattkCTEJR4RBP3TGc00FcttsP5pK284Nas5WjMZU5Yzy3kAIp3B3KRf5Yg== | ||||
|   dependencies: | ||||
|     "@sinonjs/commons" "^1.8.1" | ||||
|     "@sinonjs/fake-timers" "^6.0.1" | ||||
|     "@sinonjs/samsam" "^5.3.1" | ||||
|     diff "^4.0.2" | ||||
|     nise "^4.0.4" | ||||
|     supports-color "^7.1.0" | ||||
| 
 | ||||
| slash@^3.0.0: | ||||
|   version "3.0.0" | ||||
|   resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz" | ||||
| @ -6143,7 +6216,7 @@ type-check@~0.3.2: | ||||
|   dependencies: | ||||
|     prelude-ls "~1.1.2" | ||||
| 
 | ||||
| type-detect@4.0.8: | ||||
| type-detect@4.0.8, type-detect@^4.0.8: | ||||
|   version "4.0.8" | ||||
|   resolved "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz" | ||||
|   integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user