diff --git a/docs/api/admin/feature-toggles-api.md b/docs/api/admin/feature-toggles-api.md index 395ea5c024..1127bb1a1c 100644 --- a/docs/api/admin/feature-toggles-api.md +++ b/docs/api/admin/feature-toggles-api.md @@ -20,6 +20,7 @@ This endpoint is the one all admin ui should use to fetch all available feature "description": "lorem ipsum", "type": "release", "enabled": false, + "stale": false, "strategies": [ { "name": "default", @@ -41,6 +42,7 @@ This endpoint is the one all admin ui should use to fetch all available feature "name": "Feature.B", "description": "lorem ipsum", "enabled": true, + "stale": false, "strategies": [ { "name": "ActiveForUserWithId", @@ -71,6 +73,7 @@ Used to fetch details about a specific featureToggle. This is mostly provded to "description": "lorem ipsum..", "type": "release", "enabled": false, + "stale": false, "strategies": [ { "name": "default", @@ -93,6 +96,7 @@ Used to fetch details about a specific featureToggle. This is mostly provded to "description": "lorem ipsum..", "type": "release", "enabled": false, + "stale": false, "strategies": [ { "name": "default", @@ -123,6 +127,7 @@ Returns 200-respose if the feature toggle was created successfully. "description": "lorem ipsum..", "type": "release", "enabled": false, + "stale": false, "strategies": [ { "name": "default", @@ -189,6 +194,65 @@ None "description": "lorem ipsum..", "type": "release", "enabled": false, + "stale": false, + "strategies": [ + { + "name": "default", + "parameters": {} + } + ], + "variants": [] +} +``` + +### Mark a Feature Toggle as "stale" + +`POST: http://unleash.host.com/api/admin/features/:featureName/stale/on` + +Used to mark a feature toggle as stale (deprecated). The :featureName must match an existing Feature Toggle. Returns 200-response if the feature toggle was enabled successfully. + +**Body** + +None + +**Example response:** + +```json +{ + "name": "Feature.A", + "description": "lorem ipsum..", + "type": "release", + "enabled": true, + "stale": true, + "strategies": [ + { + "name": "default", + "parameters": {} + } + ], + "variants": [] +} +``` + +### Mark a Feature Toggle as "active" + +`POST: http://unleash.host.com/api/admin/features/:featureName/stale/off` + +Used to mark a feature toggle active (remove stale marking). The :featureName must match an existing Feature Toggle. Returns 200-response if the feature toggle was disabled successfully. + +**Body** + +None + +**Example response:** + +```json +{ + "name": "Feature.A", + "description": "lorem ipsum..", + "type": "release", + "enabled": false, + "stale": false, "strategies": [ { "name": "default", @@ -218,6 +282,7 @@ Used to fetch list of archived feature toggles "description": "lorem ipsum", "type": "release", "enabled": false, + "stale": false, "strategies": [ { "name": "default", diff --git a/docs/api/client/feature-toggles-api.md b/docs/api/client/feature-toggles-api.md index 286ac188ef..376778f8b3 100644 --- a/docs/api/client/feature-toggles-api.md +++ b/docs/api/client/feature-toggles-api.md @@ -29,6 +29,7 @@ This endpoint should never return anything besides a valid _20X or 304-response_ "description": "lorem ipsum", "type": "release", "enabled": false, + "stale": false, "strategies": [ { "name": "default", @@ -43,6 +44,7 @@ This endpoint should never return anything besides a valid _20X or 304-response_ "type": "killswitch", "description": "lorem ipsum", "enabled": true, + "stale": false, "strategies": [ { "name": "ActiveForUserWithId", @@ -80,6 +82,7 @@ Used to fetch details about a specific feature toggle. This is mainly provided t "description": "lorem ipsum..", "type": "release", "enabled": false, + "stale": false, "strategies": [ { "name": "default", diff --git a/lib/db/feature-toggle-store.js b/lib/db/feature-toggle-store.js index 65229eda4a..a0c3b1b204 100644 --- a/lib/db/feature-toggle-store.js +++ b/lib/db/feature-toggle-store.js @@ -15,6 +15,7 @@ const FEATURE_COLUMNS = [ 'description', 'type', 'enabled', + 'stale', 'strategies', 'variants', 'created_at', @@ -100,6 +101,7 @@ class FeatureToggleStore { description: row.description, type: row.type, enabled: row.enabled > 0, + stale: row.stale, strategies: row.strategies, variants: row.variants, createdAt: row.created_at, @@ -112,6 +114,7 @@ class FeatureToggleStore { description: data.description, type: data.type, enabled: data.enabled ? 1 : 0, + stale: data.stale, archived: data.archived ? 1 : 0, strategies: JSON.stringify(data.strategies), variants: data.variants ? JSON.stringify(data.variants) : null, diff --git a/lib/routes/admin-api/feature-schema.js b/lib/routes/admin-api/feature-schema.js index f965f8feab..9287139aae 100644 --- a/lib/routes/admin-api/feature-schema.js +++ b/lib/routes/admin-api/feature-schema.js @@ -54,6 +54,7 @@ const featureShema = joi .keys({ name: nameType, enabled: joi.boolean().default(false), + stale: joi.boolean().default(false), type: joi.string().default('release'), description: joi .string() diff --git a/lib/routes/admin-api/feature-schema.test.js b/lib/routes/admin-api/feature-schema.test.js index 751ced0214..e736537276 100644 --- a/lib/routes/admin-api/feature-schema.test.js +++ b/lib/routes/admin-api/feature-schema.test.js @@ -19,6 +19,7 @@ test('should be valid toggle name', t => { name: 'app.name', type: 'release', enabled: false, + stale: false, strategies: [{ name: 'default' }], }; @@ -31,6 +32,7 @@ test('should strip extra variant fields', t => { name: 'app.name', type: 'release', enabled: false, + stale: false, strategies: [{ name: 'default' }], variants: [ { @@ -51,6 +53,7 @@ test('should allow weightType=fix', t => { name: 'app.name', type: 'release', enabled: false, + stale: false, strategies: [{ name: 'default' }], variants: [ { @@ -70,6 +73,7 @@ test('should disallow weightType=unknown', t => { name: 'app.name', type: 'release', enabled: false, + stale: false, strategies: [{ name: 'default' }], variants: [ { @@ -92,6 +96,7 @@ test('should be possible to define variant overrides', t => { name: 'app.name', type: 'release', enabled: false, + stale: false, strategies: [{ name: 'default' }], variants: [ { @@ -119,6 +124,7 @@ test('variant overrides must have corect shape', async t => { name: 'app.name', type: 'release', enabled: false, + stale: false, strategies: [{ name: 'default' }], variants: [ { @@ -147,6 +153,7 @@ test('should keep constraints', t => { name: 'app.constraints', type: 'release', enabled: false, + stale: false, strategies: [ { name: 'default', diff --git a/lib/routes/admin-api/feature.js b/lib/routes/admin-api/feature.js index e52fab4c4b..d07ef7fc79 100644 --- a/lib/routes/admin-api/feature.js +++ b/lib/routes/admin-api/feature.js @@ -35,6 +35,8 @@ class FeatureController extends Controller { this.post('/:featureName/toggle', this.toggle, UPDATE_FEATURE); this.post('/:featureName/toggle/on', this.toggleOn, UPDATE_FEATURE); this.post('/:featureName/toggle/off', this.toggleOff, UPDATE_FEATURE); + this.post('/:featureName/stale/on', this.staleOn, UPDATE_FEATURE); + this.post('/:featureName/stale/off', this.staleOff, UPDATE_FEATURE); } async getAllToggles(req, res) { @@ -126,21 +128,29 @@ class FeatureController extends Controller { const name = req.params.featureName; const feature = await this.featureToggleStore.getFeature(name); const enabled = !feature.enabled; - this._toggle(enabled, req, res); + this._updateField('enabled', enabled, req, res); } catch (error) { handleErrors(res, this.logger, error); } } async toggleOn(req, res) { - this._toggle(true, req, res); + this._updateField('enabled', true, req, res); } async toggleOff(req, res) { - this._toggle(false, req, res); + this._updateField('enabled', false, req, res); } - async _toggle(enabled, req, res) { + async staleOn(req, res) { + this._updateField('stale', true, req, res); + } + + async staleOff(req, res) { + this._updateField('stale', false, req, res); + } + + async _updateField(field, value, req, res) { const { featureName } = req.params; const userName = extractUser(req); @@ -149,7 +159,7 @@ class FeatureController extends Controller { featureName, ); - feature.enabled = enabled; + feature[field] = value; await this.eventStore.store({ type: FEATURE_UPDATED, createdBy: userName, diff --git a/migrations/20200806091734-add-stale-flag-to-features.js b/migrations/20200806091734-add-stale-flag-to-features.js new file mode 100644 index 0000000000..944245f8c8 --- /dev/null +++ b/migrations/20200806091734-add-stale-flag-to-features.js @@ -0,0 +1,17 @@ +'use strict'; + +exports.up = function(db, cb) { + return db.addColumn( + 'features', + 'stale', + { + type: 'boolean', + defaultValue: false, + }, + cb, + ); +}; + +exports.down = function(db, cb) { + return db.removeColumn('features', 'stale', cb); +}; diff --git a/package.json b/package.json index c4708a1b96..d6cfeebbdf 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,7 @@ "prom-client": "^12.0.0", "response-time": "^2.3.2", "serve-favicon": "^2.5.0", - "unleash-frontend": "3.4.1-0", + "unleash-frontend": "3.5.0-0", "yargs": "^15.1.0" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index 506dee1d6c..7c9c37697f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5616,10 +5616,10 @@ universalify@^0.1.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== -unleash-frontend@3.4.1-0: - version "3.4.1-0" - resolved "https://registry.yarnpkg.com/unleash-frontend/-/unleash-frontend-3.4.1-0.tgz#480731a753059ad4b38c98b9a5f014eaa44d912b" - integrity sha512-skx+SlOFPHcrrQlY5UhNV6stxblUNaB0mGbZfbh7CdIuWMs1Y+KxVoorTqDaHV0zhyxKQvngzZOd696VnMxA4w== +unleash-frontend@3.5.0-0: + version "3.5.0-0" + resolved "https://registry.yarnpkg.com/unleash-frontend/-/unleash-frontend-3.5.0-0.tgz#e3d3da3e3e8aad03195190cc913f3d2293672305" + integrity sha512-S+myY4sQZH3Mx3Q/RsKngee1WSX4ndffqup+oQxLsoKQSNiKwtjiA1KnGugeM+SNLIUVgi+b+3JPKWXxkEjw4g== unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0"