1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-20 00:08:02 +01:00

feat: make client features endpoint memoizable (#734)

This commit is contained in:
Christopher Kolstad 2021-02-23 13:50:22 +01:00 committed by Ivar Conradi Østhus
parent 5f83fbc43d
commit 44af41547e
No known key found for this signature in database
GPG Key ID: 31AC596886B0BD09
7 changed files with 157 additions and 15 deletions

View File

@ -126,6 +126,7 @@
"prettier": "^1.19.1",
"proxyquire": "^2.1.3",
"source-map-support": "^0.5.19",
"sinon": "^9.2.4",
"superagent": "^6.1.0",
"supertest": "^5.0.0",
"ts-node": "^9.1.1",

View File

@ -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,
},
},
};
}

View File

@ -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);

View File

@ -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();

View File

@ -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',

View File

@ -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);
}
}

View File

@ -461,6 +461,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"
@ -468,6 +475,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"
@ -1914,7 +1942,7 @@ dicer@0.2.5:
readable-stream "1.1.x"
streamsearch "0.1.2"
diff@^4.0.1:
diff@^4.0.1, 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==
@ -3840,6 +3868,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"
@ -4059,6 +4092,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"
@ -4441,6 +4479,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"
@ -4925,6 +4974,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"
@ -5714,6 +5770,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"
@ -6345,7 +6413,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==