mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +01:00
feat: add stale property on toggle (#619)
This commit is contained in:
parent
e4faf3022c
commit
83dda55172
@ -20,6 +20,7 @@ This endpoint is the one all admin ui should use to fetch all available feature
|
|||||||
"description": "lorem ipsum",
|
"description": "lorem ipsum",
|
||||||
"type": "release",
|
"type": "release",
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
|
"stale": false,
|
||||||
"strategies": [
|
"strategies": [
|
||||||
{
|
{
|
||||||
"name": "default",
|
"name": "default",
|
||||||
@ -41,6 +42,7 @@ This endpoint is the one all admin ui should use to fetch all available feature
|
|||||||
"name": "Feature.B",
|
"name": "Feature.B",
|
||||||
"description": "lorem ipsum",
|
"description": "lorem ipsum",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
|
"stale": false,
|
||||||
"strategies": [
|
"strategies": [
|
||||||
{
|
{
|
||||||
"name": "ActiveForUserWithId",
|
"name": "ActiveForUserWithId",
|
||||||
@ -71,6 +73,7 @@ Used to fetch details about a specific featureToggle. This is mostly provded to
|
|||||||
"description": "lorem ipsum..",
|
"description": "lorem ipsum..",
|
||||||
"type": "release",
|
"type": "release",
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
|
"stale": false,
|
||||||
"strategies": [
|
"strategies": [
|
||||||
{
|
{
|
||||||
"name": "default",
|
"name": "default",
|
||||||
@ -93,6 +96,7 @@ Used to fetch details about a specific featureToggle. This is mostly provded to
|
|||||||
"description": "lorem ipsum..",
|
"description": "lorem ipsum..",
|
||||||
"type": "release",
|
"type": "release",
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
|
"stale": false,
|
||||||
"strategies": [
|
"strategies": [
|
||||||
{
|
{
|
||||||
"name": "default",
|
"name": "default",
|
||||||
@ -123,6 +127,7 @@ Returns 200-respose if the feature toggle was created successfully.
|
|||||||
"description": "lorem ipsum..",
|
"description": "lorem ipsum..",
|
||||||
"type": "release",
|
"type": "release",
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
|
"stale": false,
|
||||||
"strategies": [
|
"strategies": [
|
||||||
{
|
{
|
||||||
"name": "default",
|
"name": "default",
|
||||||
@ -189,6 +194,65 @@ None
|
|||||||
"description": "lorem ipsum..",
|
"description": "lorem ipsum..",
|
||||||
"type": "release",
|
"type": "release",
|
||||||
"enabled": false,
|
"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": [
|
"strategies": [
|
||||||
{
|
{
|
||||||
"name": "default",
|
"name": "default",
|
||||||
@ -218,6 +282,7 @@ Used to fetch list of archived feature toggles
|
|||||||
"description": "lorem ipsum",
|
"description": "lorem ipsum",
|
||||||
"type": "release",
|
"type": "release",
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
|
"stale": false,
|
||||||
"strategies": [
|
"strategies": [
|
||||||
{
|
{
|
||||||
"name": "default",
|
"name": "default",
|
||||||
|
@ -29,6 +29,7 @@ This endpoint should never return anything besides a valid _20X or 304-response_
|
|||||||
"description": "lorem ipsum",
|
"description": "lorem ipsum",
|
||||||
"type": "release",
|
"type": "release",
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
|
"stale": false,
|
||||||
"strategies": [
|
"strategies": [
|
||||||
{
|
{
|
||||||
"name": "default",
|
"name": "default",
|
||||||
@ -43,6 +44,7 @@ This endpoint should never return anything besides a valid _20X or 304-response_
|
|||||||
"type": "killswitch",
|
"type": "killswitch",
|
||||||
"description": "lorem ipsum",
|
"description": "lorem ipsum",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
|
"stale": false,
|
||||||
"strategies": [
|
"strategies": [
|
||||||
{
|
{
|
||||||
"name": "ActiveForUserWithId",
|
"name": "ActiveForUserWithId",
|
||||||
@ -80,6 +82,7 @@ Used to fetch details about a specific feature toggle. This is mainly provided t
|
|||||||
"description": "lorem ipsum..",
|
"description": "lorem ipsum..",
|
||||||
"type": "release",
|
"type": "release",
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
|
"stale": false,
|
||||||
"strategies": [
|
"strategies": [
|
||||||
{
|
{
|
||||||
"name": "default",
|
"name": "default",
|
||||||
|
@ -15,6 +15,7 @@ const FEATURE_COLUMNS = [
|
|||||||
'description',
|
'description',
|
||||||
'type',
|
'type',
|
||||||
'enabled',
|
'enabled',
|
||||||
|
'stale',
|
||||||
'strategies',
|
'strategies',
|
||||||
'variants',
|
'variants',
|
||||||
'created_at',
|
'created_at',
|
||||||
@ -100,6 +101,7 @@ class FeatureToggleStore {
|
|||||||
description: row.description,
|
description: row.description,
|
||||||
type: row.type,
|
type: row.type,
|
||||||
enabled: row.enabled > 0,
|
enabled: row.enabled > 0,
|
||||||
|
stale: row.stale,
|
||||||
strategies: row.strategies,
|
strategies: row.strategies,
|
||||||
variants: row.variants,
|
variants: row.variants,
|
||||||
createdAt: row.created_at,
|
createdAt: row.created_at,
|
||||||
@ -112,6 +114,7 @@ class FeatureToggleStore {
|
|||||||
description: data.description,
|
description: data.description,
|
||||||
type: data.type,
|
type: data.type,
|
||||||
enabled: data.enabled ? 1 : 0,
|
enabled: data.enabled ? 1 : 0,
|
||||||
|
stale: data.stale,
|
||||||
archived: data.archived ? 1 : 0,
|
archived: data.archived ? 1 : 0,
|
||||||
strategies: JSON.stringify(data.strategies),
|
strategies: JSON.stringify(data.strategies),
|
||||||
variants: data.variants ? JSON.stringify(data.variants) : null,
|
variants: data.variants ? JSON.stringify(data.variants) : null,
|
||||||
|
@ -54,6 +54,7 @@ const featureShema = joi
|
|||||||
.keys({
|
.keys({
|
||||||
name: nameType,
|
name: nameType,
|
||||||
enabled: joi.boolean().default(false),
|
enabled: joi.boolean().default(false),
|
||||||
|
stale: joi.boolean().default(false),
|
||||||
type: joi.string().default('release'),
|
type: joi.string().default('release'),
|
||||||
description: joi
|
description: joi
|
||||||
.string()
|
.string()
|
||||||
|
@ -19,6 +19,7 @@ test('should be valid toggle name', t => {
|
|||||||
name: 'app.name',
|
name: 'app.name',
|
||||||
type: 'release',
|
type: 'release',
|
||||||
enabled: false,
|
enabled: false,
|
||||||
|
stale: false,
|
||||||
strategies: [{ name: 'default' }],
|
strategies: [{ name: 'default' }],
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -31,6 +32,7 @@ test('should strip extra variant fields', t => {
|
|||||||
name: 'app.name',
|
name: 'app.name',
|
||||||
type: 'release',
|
type: 'release',
|
||||||
enabled: false,
|
enabled: false,
|
||||||
|
stale: false,
|
||||||
strategies: [{ name: 'default' }],
|
strategies: [{ name: 'default' }],
|
||||||
variants: [
|
variants: [
|
||||||
{
|
{
|
||||||
@ -51,6 +53,7 @@ test('should allow weightType=fix', t => {
|
|||||||
name: 'app.name',
|
name: 'app.name',
|
||||||
type: 'release',
|
type: 'release',
|
||||||
enabled: false,
|
enabled: false,
|
||||||
|
stale: false,
|
||||||
strategies: [{ name: 'default' }],
|
strategies: [{ name: 'default' }],
|
||||||
variants: [
|
variants: [
|
||||||
{
|
{
|
||||||
@ -70,6 +73,7 @@ test('should disallow weightType=unknown', t => {
|
|||||||
name: 'app.name',
|
name: 'app.name',
|
||||||
type: 'release',
|
type: 'release',
|
||||||
enabled: false,
|
enabled: false,
|
||||||
|
stale: false,
|
||||||
strategies: [{ name: 'default' }],
|
strategies: [{ name: 'default' }],
|
||||||
variants: [
|
variants: [
|
||||||
{
|
{
|
||||||
@ -92,6 +96,7 @@ test('should be possible to define variant overrides', t => {
|
|||||||
name: 'app.name',
|
name: 'app.name',
|
||||||
type: 'release',
|
type: 'release',
|
||||||
enabled: false,
|
enabled: false,
|
||||||
|
stale: false,
|
||||||
strategies: [{ name: 'default' }],
|
strategies: [{ name: 'default' }],
|
||||||
variants: [
|
variants: [
|
||||||
{
|
{
|
||||||
@ -119,6 +124,7 @@ test('variant overrides must have corect shape', async t => {
|
|||||||
name: 'app.name',
|
name: 'app.name',
|
||||||
type: 'release',
|
type: 'release',
|
||||||
enabled: false,
|
enabled: false,
|
||||||
|
stale: false,
|
||||||
strategies: [{ name: 'default' }],
|
strategies: [{ name: 'default' }],
|
||||||
variants: [
|
variants: [
|
||||||
{
|
{
|
||||||
@ -147,6 +153,7 @@ test('should keep constraints', t => {
|
|||||||
name: 'app.constraints',
|
name: 'app.constraints',
|
||||||
type: 'release',
|
type: 'release',
|
||||||
enabled: false,
|
enabled: false,
|
||||||
|
stale: false,
|
||||||
strategies: [
|
strategies: [
|
||||||
{
|
{
|
||||||
name: 'default',
|
name: 'default',
|
||||||
|
@ -35,6 +35,8 @@ class FeatureController extends Controller {
|
|||||||
this.post('/:featureName/toggle', this.toggle, UPDATE_FEATURE);
|
this.post('/:featureName/toggle', this.toggle, UPDATE_FEATURE);
|
||||||
this.post('/:featureName/toggle/on', this.toggleOn, UPDATE_FEATURE);
|
this.post('/:featureName/toggle/on', this.toggleOn, UPDATE_FEATURE);
|
||||||
this.post('/:featureName/toggle/off', this.toggleOff, 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) {
|
async getAllToggles(req, res) {
|
||||||
@ -126,21 +128,29 @@ class FeatureController extends Controller {
|
|||||||
const name = req.params.featureName;
|
const name = req.params.featureName;
|
||||||
const feature = await this.featureToggleStore.getFeature(name);
|
const feature = await this.featureToggleStore.getFeature(name);
|
||||||
const enabled = !feature.enabled;
|
const enabled = !feature.enabled;
|
||||||
this._toggle(enabled, req, res);
|
this._updateField('enabled', enabled, req, res);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleErrors(res, this.logger, error);
|
handleErrors(res, this.logger, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async toggleOn(req, res) {
|
async toggleOn(req, res) {
|
||||||
this._toggle(true, req, res);
|
this._updateField('enabled', true, req, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
async toggleOff(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 { featureName } = req.params;
|
||||||
const userName = extractUser(req);
|
const userName = extractUser(req);
|
||||||
|
|
||||||
@ -149,7 +159,7 @@ class FeatureController extends Controller {
|
|||||||
featureName,
|
featureName,
|
||||||
);
|
);
|
||||||
|
|
||||||
feature.enabled = enabled;
|
feature[field] = value;
|
||||||
await this.eventStore.store({
|
await this.eventStore.store({
|
||||||
type: FEATURE_UPDATED,
|
type: FEATURE_UPDATED,
|
||||||
createdBy: userName,
|
createdBy: userName,
|
||||||
|
17
migrations/20200806091734-add-stale-flag-to-features.js
Normal file
17
migrations/20200806091734-add-stale-flag-to-features.js
Normal file
@ -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);
|
||||||
|
};
|
@ -87,7 +87,7 @@
|
|||||||
"prom-client": "^12.0.0",
|
"prom-client": "^12.0.0",
|
||||||
"response-time": "^2.3.2",
|
"response-time": "^2.3.2",
|
||||||
"serve-favicon": "^2.5.0",
|
"serve-favicon": "^2.5.0",
|
||||||
"unleash-frontend": "3.4.1-0",
|
"unleash-frontend": "3.5.0-0",
|
||||||
"yargs": "^15.1.0"
|
"yargs": "^15.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -5616,10 +5616,10 @@ universalify@^0.1.0:
|
|||||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
|
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
|
||||||
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
|
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
|
||||||
|
|
||||||
unleash-frontend@3.4.1-0:
|
unleash-frontend@3.5.0-0:
|
||||||
version "3.4.1-0"
|
version "3.5.0-0"
|
||||||
resolved "https://registry.yarnpkg.com/unleash-frontend/-/unleash-frontend-3.4.1-0.tgz#480731a753059ad4b38c98b9a5f014eaa44d912b"
|
resolved "https://registry.yarnpkg.com/unleash-frontend/-/unleash-frontend-3.5.0-0.tgz#e3d3da3e3e8aad03195190cc913f3d2293672305"
|
||||||
integrity sha512-skx+SlOFPHcrrQlY5UhNV6stxblUNaB0mGbZfbh7CdIuWMs1Y+KxVoorTqDaHV0zhyxKQvngzZOd696VnMxA4w==
|
integrity sha512-S+myY4sQZH3Mx3Q/RsKngee1WSX4ndffqup+oQxLsoKQSNiKwtjiA1KnGugeM+SNLIUVgi+b+3JPKWXxkEjw4g==
|
||||||
|
|
||||||
unpipe@1.0.0, unpipe@~1.0.0:
|
unpipe@1.0.0, unpipe@~1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
|
Loading…
Reference in New Issue
Block a user