From cdd483ffeca2afcc8d627d272efcc61eb52a7d3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivar=20Conradi=20=C3=98sthus?= Date: Thu, 24 Oct 2019 16:58:09 +0200 Subject: [PATCH] feat: Add new Flexible Rollout Strategy (#517) * feat: Add new Flexible Rollout Strategy fixes #516 * feat: update unleash-frontend to version 3.2.8 * chore: update flexible rollout documentation --- docs/activation-strategies.md | 14 ++++ ...0191023184858-flexible-rollout-strategy.js | 64 +++++++++++++++++++ migrations/flexible-rollout-strategy.json | 24 +++++++ package.json | 2 +- yarn.lock | 8 +-- 5 files changed, 107 insertions(+), 5 deletions(-) create mode 100644 migrations/20191023184858-flexible-rollout-strategy.js create mode 100644 migrations/flexible-rollout-strategy.json diff --git a/docs/activation-strategies.md b/docs/activation-strategies.md index 6caa53790d..25a88d6512 100644 --- a/docs/activation-strategies.md +++ b/docs/activation-strategies.md @@ -21,6 +21,20 @@ Active for users with a `userId` defined in the `userIds` list. Typically I want - userIds - _List of user IDs you want the feature toggle to be enabled for_ +## flexibleRollout + +A flexible rollout strategy which combines all gradual rollout strategies in to a single strategy (and will in time replace them). This strategy have different options for how you want to handle the stickiness, and have sane default mode. + +**Parameters** + +- **stickiness** is used to define how we guarantee consistency for gradual rollout. The same userId and the same rollout percentage should give predictable results. Configuration that should be supported: + - **DEFAULT** - Unleash chooses the first value present on the context in defined order userId, sessionId, random. + - **USERID** - guaranteed to be sticky on userId. If userId not present the behaviour would be false + - **SESSIONID - **guaranteed to be sticky on sessionId. If sessionId not present the behaviour would be false. + - **RANDOM** - no stickiness guaranteed. For every isEnabled call it will yield a random true/false based on the selected rollout percentage. +- **groupId** is used to ensure that different toggles will **hash differently** for the same user. The groupId defaults to _feature toggle name_, but is overridable by the user to _correlate rollout_ of multiple feature toggles. +- **rollout** The percentage (0-100) you want to enable the feature toggle for. + ## gradualRolloutUserId The `gradualRolloutUserId` strategy gradually activates a feature toggle for logged in users. Stickiness is based on the user ID. The strategy guarantees that the same user gets the same experience every time across devices. It also assures that a user which is among the first 10% will also be among the first 20% of the users. That way, we ensure the users get the same experience, even if we gradually increase the number of users exposed to a particular feature. To achieve this, we hash the user ID and normalise the hash value to a number between 1 and 100 with a simple modulo operator. diff --git a/migrations/20191023184858-flexible-rollout-strategy.js b/migrations/20191023184858-flexible-rollout-strategy.js new file mode 100644 index 0000000000..7615944763 --- /dev/null +++ b/migrations/20191023184858-flexible-rollout-strategy.js @@ -0,0 +1,64 @@ +'use strict'; + +const flexibleRollout = require('./flexible-rollout-strategy.json'); +const async = require('async'); + +function insertStrategySQL(strategy) { + return ` + INSERT INTO strategies (name, description, parameters, built_in) + SELECT '${strategy.name}', '${strategy.description}', '${JSON.stringify( + strategy.parameters + )}', 1 + WHERE + NOT EXISTS ( + SELECT name FROM strategies WHERE name = '${strategy.name}' + );`; +} + +function insertEventsSQL(strategy) { + return ` + INSERT INTO events (type, created_by, data) + SELECT 'strategy-created', 'migration', '${JSON.stringify(strategy)}' + WHERE + NOT EXISTS ( + SELECT name FROM strategies WHERE name = '${strategy.name}' + );`; +} + +function removeEventsSQL(strategy) { + return ` + INSERT INTO events (type, created_by, data) + SELECT 'strategy-deleted', 'migration', '${JSON.stringify(strategy)}' + WHERE + EXISTS ( + SELECT name FROM strategies WHERE name = '${ + strategy.name + }' AND built_in = 1 + );`; +} + +function removeStrategySQL(strategy) { + return ` + DELETE FROM strategies + WHERE name = '${strategy.name}' AND built_in = 1`; +} + +exports.up = function(db, callback) { + async.series( + [ + db.runSql.bind(db, insertEventsSQL(flexibleRollout)), + db.runSql.bind(db, insertStrategySQL(flexibleRollout)), + ], + callback + ); +}; + +exports.down = function(db, callback) { + async.series( + [ + db.runSql.bind(db, removeEventsSQL(flexibleRollout)), + db.runSql.bind(db, removeStrategySQL(flexibleRollout)), + ], + callback + ); +}; diff --git a/migrations/flexible-rollout-strategy.json b/migrations/flexible-rollout-strategy.json new file mode 100644 index 0000000000..d4b43b29c0 --- /dev/null +++ b/migrations/flexible-rollout-strategy.json @@ -0,0 +1,24 @@ +{ + "name": "flexibleRollout", + "description": "Gradually activate feature toggle based on sane stickiness", + "parameters": [ + { + "name": "rollout", + "type": "percentage", + "description": "", + "required": false + }, + { + "name": "stickiness", + "type": "string", + "description": "Used define stickiness. Possible values: default, userId, sessionId, random", + "required": true + }, + { + "name": "groupId", + "type": "string", + "description": "Used to define a activation groups, which allows you to correlate across feature toggles.", + "required": true + } + ] +} diff --git a/package.json b/package.json index c857e73164..158f01c2c1 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,7 @@ "prometheus-gc-stats": "^0.6.1", "response-time": "^2.3.2", "serve-favicon": "^2.5.0", - "unleash-frontend": "3.2.7", + "unleash-frontend": "3.2.8", "yargs": "^14.0.0" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index 35d034a252..d3ee1327a4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5906,10 +5906,10 @@ universalify@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" -unleash-frontend@3.2.7: - version "3.2.7" - resolved "https://registry.yarnpkg.com/unleash-frontend/-/unleash-frontend-3.2.7.tgz#ec2af024229589d2f070e6961de65bb063375b40" - integrity sha512-30fZKazTiPbTL2335T6B0EBjQbqezlZ/wb9OFGSIjIA6S14/9+5hL9qx6E6V2dCtX0FX32pGGXnSrjuzq2on7Q== +unleash-frontend@3.2.8: + version "3.2.8" + resolved "https://registry.yarnpkg.com/unleash-frontend/-/unleash-frontend-3.2.8.tgz#b8dedc23d3c44cac313c6da617940b5ea5162b83" + integrity sha512-QeXWOE+nxtumZYOyt9KZsIVpnaQPgEibAsvGhsS0t8ZidSnUybPbY/NE82IDOVneKO34kwfRCTnIkiQQLc4EEw== unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0"