mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-31 13:47:02 +02:00
feat: Add support for toggle types (#618)
This commit is contained in:
parent
045a2dc621
commit
6568457ed8
@ -18,6 +18,7 @@ This endpoint is the one all admin ui should use to fetch all available feature
|
|||||||
{
|
{
|
||||||
"name": "Feature.A",
|
"name": "Feature.A",
|
||||||
"description": "lorem ipsum",
|
"description": "lorem ipsum",
|
||||||
|
"type": "release",
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
"strategies": [
|
"strategies": [
|
||||||
{
|
{
|
||||||
@ -68,6 +69,7 @@ Used to fetch details about a specific featureToggle. This is mostly provded to
|
|||||||
{
|
{
|
||||||
"name": "Feature.A",
|
"name": "Feature.A",
|
||||||
"description": "lorem ipsum..",
|
"description": "lorem ipsum..",
|
||||||
|
"type": "release",
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
"strategies": [
|
"strategies": [
|
||||||
{
|
{
|
||||||
@ -89,6 +91,7 @@ Used to fetch details about a specific featureToggle. This is mostly provded to
|
|||||||
{
|
{
|
||||||
"name": "Feature.A",
|
"name": "Feature.A",
|
||||||
"description": "lorem ipsum..",
|
"description": "lorem ipsum..",
|
||||||
|
"type": "release",
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
"strategies": [
|
"strategies": [
|
||||||
{
|
{
|
||||||
@ -99,7 +102,12 @@ Used to fetch details about a specific featureToggle. This is mostly provded to
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Used by the admin-dashboard to create a new feature toggles. The name **must be unique**, otherwise you will get a _403-response_.
|
Used by the admin-dashboard to create a new feature toggles.
|
||||||
|
|
||||||
|
**Notes:**
|
||||||
|
|
||||||
|
- _name_ **must be globally unique**, otherwise you will get a _403-response_.
|
||||||
|
- _type_ is optional. If not defined it defaults to `release`
|
||||||
|
|
||||||
Returns 200-respose if the feature toggle was created successfully.
|
Returns 200-respose if the feature toggle was created successfully.
|
||||||
|
|
||||||
@ -113,6 +121,7 @@ Returns 200-respose if the feature toggle was created successfully.
|
|||||||
{
|
{
|
||||||
"name": "Feature.A",
|
"name": "Feature.A",
|
||||||
"description": "lorem ipsum..",
|
"description": "lorem ipsum..",
|
||||||
|
"type": "release",
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
"strategies": [
|
"strategies": [
|
||||||
{
|
{
|
||||||
@ -150,6 +159,7 @@ None
|
|||||||
{
|
{
|
||||||
"name": "Feature.A",
|
"name": "Feature.A",
|
||||||
"description": "lorem ipsum..",
|
"description": "lorem ipsum..",
|
||||||
|
"type": "release",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"strategies": [
|
"strategies": [
|
||||||
{
|
{
|
||||||
@ -177,6 +187,7 @@ None
|
|||||||
{
|
{
|
||||||
"name": "Feature.A",
|
"name": "Feature.A",
|
||||||
"description": "lorem ipsum..",
|
"description": "lorem ipsum..",
|
||||||
|
"type": "release",
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
"strategies": [
|
"strategies": [
|
||||||
{
|
{
|
||||||
@ -205,6 +216,7 @@ Used to fetch list of archived feature toggles
|
|||||||
{
|
{
|
||||||
"name": "Feature.A",
|
"name": "Feature.A",
|
||||||
"description": "lorem ipsum",
|
"description": "lorem ipsum",
|
||||||
|
"type": "release",
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
"strategies": [
|
"strategies": [
|
||||||
{
|
{
|
||||||
|
50
docs/api/admin/feature-types-api.md
Normal file
50
docs/api/admin/feature-types-api.md
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
---
|
||||||
|
id: events
|
||||||
|
title: /api/admin/feature-types
|
||||||
|
---
|
||||||
|
|
||||||
|
# Feature Types API
|
||||||
|
|
||||||
|
`GET: http://unleash.host.com/api/admin/feature-types`
|
||||||
|
|
||||||
|
Used to fetch all feature types defined in the unleash system.
|
||||||
|
|
||||||
|
**Response**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"types": [
|
||||||
|
{
|
||||||
|
"id": "release",
|
||||||
|
"name": "Release",
|
||||||
|
"description": "Used to enable trunk-based development for teams practicing Continuous Delivery.",
|
||||||
|
"lifetimeDays": 40
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "experiment",
|
||||||
|
"name": "Experiment",
|
||||||
|
"description": "Used to perform multivariate or A/B testing.",
|
||||||
|
"lifetimeDays": 40
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ops",
|
||||||
|
"name": "Operational",
|
||||||
|
"description": "Used to control operational aspects of the system behavior.",
|
||||||
|
"lifetimeDays": 7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "killswitch",
|
||||||
|
"name": "Kill switch",
|
||||||
|
"description": "Used to to gracefully degrade system functionality.",
|
||||||
|
"lifetimeDays": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "permission",
|
||||||
|
"name": "Permission",
|
||||||
|
"description": "Used to change the features or product experience that certain users receive.",
|
||||||
|
"lifetimeDays": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
@ -27,6 +27,7 @@ This endpoint should never return anything besides a valid _20X or 304-response_
|
|||||||
{
|
{
|
||||||
"name": "Feature.A",
|
"name": "Feature.A",
|
||||||
"description": "lorem ipsum",
|
"description": "lorem ipsum",
|
||||||
|
"type": "release",
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
"strategies": [
|
"strategies": [
|
||||||
{
|
{
|
||||||
@ -39,6 +40,7 @@ This endpoint should never return anything besides a valid _20X or 304-response_
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Feature.B",
|
"name": "Feature.B",
|
||||||
|
"type": "killswitch",
|
||||||
"description": "lorem ipsum",
|
"description": "lorem ipsum",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"strategies": [
|
"strategies": [
|
||||||
@ -76,6 +78,7 @@ Used to fetch details about a specific feature toggle. This is mainly provided t
|
|||||||
{
|
{
|
||||||
"name": "Feature.A",
|
"name": "Feature.A",
|
||||||
"description": "lorem ipsum..",
|
"description": "lorem ipsum..",
|
||||||
|
"type": "release",
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
"strategies": [
|
"strategies": [
|
||||||
{
|
{
|
||||||
|
@ -10,7 +10,7 @@ This document describes our current database schema used in PostgreSQL. We use d
|
|||||||
Used by db-migrate module to keep track of migrations.
|
Used by db-migrate module to keep track of migrations.
|
||||||
|
|
||||||
| NAME | TYPE | SIZE | NULLABLE | COLUMN_DEF |
|
| NAME | TYPE | SIZE | NULLABLE | COLUMN_DEF |
|
||||||
| ------ | --------- | ---- | -------- | -------------------------------------- |
|
| --- | --- | --- | --- | --- |
|
||||||
| id | serial | 10 | 0 | nextval('migrations_id_seq'::regclass) |
|
| id | serial | 10 | 0 | nextval('migrations_id_seq'::regclass) |
|
||||||
| name | varchar | 255 | 0 | (null) |
|
| name | varchar | 255 | 0 | (null) |
|
||||||
| run_on | timestamp | 29 | 0 | (null) |
|
| run_on | timestamp | 29 | 0 | (null) |
|
||||||
@ -18,7 +18,7 @@ Used by db-migrate module to keep track of migrations.
|
|||||||
## Table: _events_
|
## Table: _events_
|
||||||
|
|
||||||
| NAME | TYPE | SIZE | NULLABLE | COLUMN_DEF |
|
| NAME | TYPE | SIZE | NULLABLE | COLUMN_DEF |
|
||||||
| ---------- | --------- | ---------- | -------- | ---------------------------------- |
|
| --- | --- | --- | --- | --- |
|
||||||
| id | serial | 10 | 0 | nextval('events_id_seq'::regclass) |
|
| id | serial | 10 | 0 | nextval('events_id_seq'::regclass) |
|
||||||
| created_at | timestamp | 29 | 1 | now() |
|
| created_at | timestamp | 29 | 1 | now() |
|
||||||
| type | varchar | 255 | 0 | (null) |
|
| type | varchar | 255 | 0 | (null) |
|
||||||
@ -37,13 +37,14 @@ Used by db-migrate module to keep track of migrations.
|
|||||||
## Table: _features_
|
## Table: _features_
|
||||||
|
|
||||||
| **NAME** | **TYPE** | **SIZE** | **NULLABLE** | **COLUMN_DEF** | **COMMENT** |
|
| **NAME** | **TYPE** | **SIZE** | **NULLABLE** | **COLUMN_DEF** | **COMMENT** |
|
||||||
| ----------- | --------- | ---------- | ------------ | -------------- | ----------- |
|
| --- | --- | --- | --- | --- | --- |
|
||||||
| created_at | timestamp | 29 | 1 | now() | |
|
| created_at | timestamp | 29 | 1 | now() | |
|
||||||
| name | varchar | 255 | 0 | (null) | |
|
| name | varchar | 255 | 0 | (null) | |
|
||||||
| enabled | int4 | 10 | 1 | 0 | |
|
| enabled | int4 | 10 | 1 | 0 | |
|
||||||
| description | text | 2147483647 | 1 | (null) | |
|
| description | text | 2147483647 | 1 | (null) | |
|
||||||
| archived | int4 | 10 | 1 | 0 | |
|
| archived | int4 | 10 | 1 | 0 | |
|
||||||
| strategies | json | 2147483647 | 1 | (null) | |
|
| strategies | json | 2147483647 | 1 | (null) | |
|
||||||
|
| type | varchar | 2147483647 | 1 | release | |
|
||||||
|
|
||||||
## Table: _client_strategies_
|
## Table: _client_strategies_
|
||||||
|
|
||||||
@ -66,7 +67,16 @@ Used by db-migrate module to keep track of migrations.
|
|||||||
## Table: _client_metrics_
|
## Table: _client_metrics_
|
||||||
|
|
||||||
| COLUMN_NAME | TYPE_NAME | COLUMN_SIZE | NULLABLE | COLUMN_DEF |
|
| COLUMN_NAME | TYPE_NAME | COLUMN_SIZE | NULLABLE | COLUMN_DEF |
|
||||||
| ----------- | --------- | ----------- | -------- | ------------------------------------------ |
|
| --- | --- | --- | --- | --- |
|
||||||
| id | serial | 10 | 0 | nextval('client_metrics_id_seq'::regclass) |
|
| id | serial | 10 | 0 | nextval('client_metrics_id_seq'::regclass) |
|
||||||
| created_at | timestamp | 29 | 1 | now() |
|
| created_at | timestamp | 29 | 1 | now() |
|
||||||
| metrics | json | 2147483647 | 1 | (null) |
|
| metrics | json | 2147483647 | 1 | (null) |
|
||||||
|
|
||||||
|
## Table: _feature_types_
|
||||||
|
|
||||||
|
| COLUMN_NAME | TYPE_NAME | COLUMN_SIZE | NULLABLE | COLUMN_DEF |
|
||||||
|
| ------------- | --------- | ----------- | -------- | ---------- |
|
||||||
|
| id | varchar | 255 | 0 | (null) |
|
||||||
|
| name | varchar | | 0 | (null) |
|
||||||
|
| description | varchar | | 1 | (null) |
|
||||||
|
| lifetime_days | integer | | 1 | (null) |
|
||||||
|
@ -13,6 +13,7 @@ const NotFoundError = require('../error/notfound-error');
|
|||||||
const FEATURE_COLUMNS = [
|
const FEATURE_COLUMNS = [
|
||||||
'name',
|
'name',
|
||||||
'description',
|
'description',
|
||||||
|
'type',
|
||||||
'enabled',
|
'enabled',
|
||||||
'strategies',
|
'strategies',
|
||||||
'variants',
|
'variants',
|
||||||
@ -97,6 +98,7 @@ class FeatureToggleStore {
|
|||||||
return {
|
return {
|
||||||
name: row.name,
|
name: row.name,
|
||||||
description: row.description,
|
description: row.description,
|
||||||
|
type: row.type,
|
||||||
enabled: row.enabled > 0,
|
enabled: row.enabled > 0,
|
||||||
strategies: row.strategies,
|
strategies: row.strategies,
|
||||||
variants: row.variants,
|
variants: row.variants,
|
||||||
@ -108,6 +110,7 @@ class FeatureToggleStore {
|
|||||||
return {
|
return {
|
||||||
name: data.name,
|
name: data.name,
|
||||||
description: data.description,
|
description: data.description,
|
||||||
|
type: data.type,
|
||||||
enabled: data.enabled ? 1 : 0,
|
enabled: data.enabled ? 1 : 0,
|
||||||
archived: data.archived ? 1 : 0,
|
archived: data.archived ? 1 : 0,
|
||||||
strategies: JSON.stringify(data.strategies),
|
strategies: JSON.stringify(data.strategies),
|
||||||
|
29
lib/db/feature-type-store.js
Normal file
29
lib/db/feature-type-store.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const COLUMNS = ['id', 'name', 'description', 'lifetime_days'];
|
||||||
|
const TABLE = 'feature_types';
|
||||||
|
|
||||||
|
class FeatureToggleStore {
|
||||||
|
constructor(db, getLogger) {
|
||||||
|
this.db = db;
|
||||||
|
this.getLogger = getLogger('feature-type-store.js');
|
||||||
|
}
|
||||||
|
|
||||||
|
getAll() {
|
||||||
|
return this.db
|
||||||
|
.select(COLUMNS)
|
||||||
|
.from(TABLE)
|
||||||
|
.map(this.rowToFeatureType);
|
||||||
|
}
|
||||||
|
|
||||||
|
rowToFeatureType(row) {
|
||||||
|
return {
|
||||||
|
id: row.id,
|
||||||
|
name: row.name,
|
||||||
|
description: row.description,
|
||||||
|
lifetimeDays: row.lifetime_days,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = FeatureToggleStore;
|
@ -3,6 +3,7 @@
|
|||||||
const { createDb } = require('./db-pool');
|
const { createDb } = require('./db-pool');
|
||||||
const EventStore = require('./event-store');
|
const EventStore = require('./event-store');
|
||||||
const FeatureToggleStore = require('./feature-toggle-store');
|
const FeatureToggleStore = require('./feature-toggle-store');
|
||||||
|
const FeatureTypeStore = require('./feature-type-store');
|
||||||
const StrategyStore = require('./strategy-store');
|
const StrategyStore = require('./strategy-store');
|
||||||
const ClientInstanceStore = require('./client-instance-store');
|
const ClientInstanceStore = require('./client-instance-store');
|
||||||
const ClientMetricsDb = require('./client-metrics-db');
|
const ClientMetricsDb = require('./client-metrics-db');
|
||||||
@ -22,6 +23,7 @@ module.exports.createStores = (config, eventBus) => {
|
|||||||
db,
|
db,
|
||||||
eventStore,
|
eventStore,
|
||||||
featureToggleStore: new FeatureToggleStore(db, eventStore, getLogger),
|
featureToggleStore: new FeatureToggleStore(db, eventStore, getLogger),
|
||||||
|
featureTypeStore: new FeatureTypeStore(db, getLogger),
|
||||||
strategyStore: new StrategyStore(db, eventStore, getLogger),
|
strategyStore: new StrategyStore(db, eventStore, getLogger),
|
||||||
clientApplicationsStore: new ClientApplicationsStore(
|
clientApplicationsStore: new ClientApplicationsStore(
|
||||||
db,
|
db,
|
||||||
|
@ -54,6 +54,7 @@ const featureShema = joi
|
|||||||
.keys({
|
.keys({
|
||||||
name: nameType,
|
name: nameType,
|
||||||
enabled: joi.boolean().default(false),
|
enabled: joi.boolean().default(false),
|
||||||
|
type: joi.string().default('release'),
|
||||||
description: joi
|
description: joi
|
||||||
.string()
|
.string()
|
||||||
.allow('')
|
.allow('')
|
||||||
|
@ -17,6 +17,7 @@ test('should require URL firendly name', t => {
|
|||||||
test('should be valid toggle name', t => {
|
test('should be valid toggle name', t => {
|
||||||
const toggle = {
|
const toggle = {
|
||||||
name: 'app.name',
|
name: 'app.name',
|
||||||
|
type: 'release',
|
||||||
enabled: false,
|
enabled: false,
|
||||||
strategies: [{ name: 'default' }],
|
strategies: [{ name: 'default' }],
|
||||||
};
|
};
|
||||||
@ -28,6 +29,7 @@ test('should be valid toggle name', t => {
|
|||||||
test('should strip extra variant fields', t => {
|
test('should strip extra variant fields', t => {
|
||||||
const toggle = {
|
const toggle = {
|
||||||
name: 'app.name',
|
name: 'app.name',
|
||||||
|
type: 'release',
|
||||||
enabled: false,
|
enabled: false,
|
||||||
strategies: [{ name: 'default' }],
|
strategies: [{ name: 'default' }],
|
||||||
variants: [
|
variants: [
|
||||||
@ -47,6 +49,7 @@ test('should strip extra variant fields', t => {
|
|||||||
test('should allow weightType=fix', t => {
|
test('should allow weightType=fix', t => {
|
||||||
const toggle = {
|
const toggle = {
|
||||||
name: 'app.name',
|
name: 'app.name',
|
||||||
|
type: 'release',
|
||||||
enabled: false,
|
enabled: false,
|
||||||
strategies: [{ name: 'default' }],
|
strategies: [{ name: 'default' }],
|
||||||
variants: [
|
variants: [
|
||||||
@ -65,6 +68,7 @@ test('should allow weightType=fix', t => {
|
|||||||
test('should disallow weightType=unknown', t => {
|
test('should disallow weightType=unknown', t => {
|
||||||
const toggle = {
|
const toggle = {
|
||||||
name: 'app.name',
|
name: 'app.name',
|
||||||
|
type: 'release',
|
||||||
enabled: false,
|
enabled: false,
|
||||||
strategies: [{ name: 'default' }],
|
strategies: [{ name: 'default' }],
|
||||||
variants: [
|
variants: [
|
||||||
@ -86,6 +90,7 @@ test('should disallow weightType=unknown', t => {
|
|||||||
test('should be possible to define variant overrides', t => {
|
test('should be possible to define variant overrides', t => {
|
||||||
const toggle = {
|
const toggle = {
|
||||||
name: 'app.name',
|
name: 'app.name',
|
||||||
|
type: 'release',
|
||||||
enabled: false,
|
enabled: false,
|
||||||
strategies: [{ name: 'default' }],
|
strategies: [{ name: 'default' }],
|
||||||
variants: [
|
variants: [
|
||||||
@ -112,6 +117,7 @@ test('variant overrides must have corect shape', async t => {
|
|||||||
t.plan(1);
|
t.plan(1);
|
||||||
const toggle = {
|
const toggle = {
|
||||||
name: 'app.name',
|
name: 'app.name',
|
||||||
|
type: 'release',
|
||||||
enabled: false,
|
enabled: false,
|
||||||
strategies: [{ name: 'default' }],
|
strategies: [{ name: 'default' }],
|
||||||
variants: [
|
variants: [
|
||||||
@ -139,6 +145,7 @@ test('variant overrides must have corect shape', async t => {
|
|||||||
test('should keep constraints', t => {
|
test('should keep constraints', t => {
|
||||||
const toggle = {
|
const toggle = {
|
||||||
name: 'app.constraints',
|
name: 'app.constraints',
|
||||||
|
type: 'release',
|
||||||
enabled: false,
|
enabled: false,
|
||||||
strategies: [
|
strategies: [
|
||||||
{
|
{
|
||||||
|
22
lib/routes/admin-api/feature-type.js
Normal file
22
lib/routes/admin-api/feature-type.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const Controller = require('../controller');
|
||||||
|
|
||||||
|
const version = 1;
|
||||||
|
|
||||||
|
class FeatureTypeController extends Controller {
|
||||||
|
constructor(config) {
|
||||||
|
super(config);
|
||||||
|
this.featureTypeStore = config.stores.featureTypeStore;
|
||||||
|
this.logger = config.getLogger('/admin-api/feature-type.js');
|
||||||
|
|
||||||
|
this.get('/', this.getAllFeatureTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAllFeatureTypes(req, res) {
|
||||||
|
const types = await this.featureTypeStore.getAll();
|
||||||
|
res.json({ version, types });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = FeatureTypeController;
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
const Controller = require('../controller');
|
const Controller = require('../controller');
|
||||||
const FeatureController = require('./feature.js');
|
const FeatureController = require('./feature.js');
|
||||||
|
const FeatureTypeController = require('./feature-type.js');
|
||||||
const ArchiveController = require('./archive.js');
|
const ArchiveController = require('./archive.js');
|
||||||
const EventController = require('./event.js');
|
const EventController = require('./event.js');
|
||||||
const StrategyController = require('./strategy');
|
const StrategyController = require('./strategy');
|
||||||
@ -18,6 +19,10 @@ class AdminApi extends Controller {
|
|||||||
|
|
||||||
this.app.get('/', this.index);
|
this.app.get('/', this.index);
|
||||||
this.app.use('/features', new FeatureController(config).router);
|
this.app.use('/features', new FeatureController(config).router);
|
||||||
|
this.app.use(
|
||||||
|
'/feature-types',
|
||||||
|
new FeatureTypeController(config).router,
|
||||||
|
);
|
||||||
this.app.use('/archive', new ArchiveController(config).router);
|
this.app.use('/archive', new ArchiveController(config).router);
|
||||||
this.app.use('/strategies', new StrategyController(config).router);
|
this.app.use('/strategies', new StrategyController(config).router);
|
||||||
this.app.use('/events', new EventController(config).router);
|
this.app.use('/events', new EventController(config).router);
|
||||||
|
38
migrations/20200805091409-add-feature-toggle-type.js
Normal file
38
migrations/20200805091409-add-feature-toggle-type.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
/* eslint camelcase: "off" */
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const async = require('async');
|
||||||
|
|
||||||
|
exports.up = function(db, cb) {
|
||||||
|
async.series(
|
||||||
|
[
|
||||||
|
db.createTable.bind(db, 'feature_types', {
|
||||||
|
id: {
|
||||||
|
type: 'string',
|
||||||
|
length: 255,
|
||||||
|
primaryKey: true,
|
||||||
|
notNull: true,
|
||||||
|
},
|
||||||
|
name: { type: 'string', notNull: true },
|
||||||
|
description: { type: 'string' },
|
||||||
|
lifetime_days: { type: 'int' },
|
||||||
|
}),
|
||||||
|
db.runSql.bind(
|
||||||
|
db,
|
||||||
|
`
|
||||||
|
INSERT INTO feature_types(id, name, description, lifetime_days) VALUES('release', 'Release', 'Used to enable trunk-based development for teams practicing Continuous Delivery.', 40);
|
||||||
|
INSERT INTO feature_types(id, name, description, lifetime_days) VALUES('experiment', 'Experiment', 'Used to perform multivariate or A/B testing.', 40);
|
||||||
|
INSERT INTO feature_types(id, name, description, lifetime_days) VALUES('operational', 'Operational', 'Used to control operational aspects of the system behavior.', 7);
|
||||||
|
INSERT INTO feature_types(id, name, description, lifetime_days) VALUES('kill-switch', 'Kill switch', 'Used to to gracefully degrade system functionality.', null);
|
||||||
|
INSERT INTO feature_types(id, name, description, lifetime_days) VALUES('permission', 'Permission', 'Used to change the features or product experience that certain users receive.', null);
|
||||||
|
`,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
cb,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.down = function(db, cb) {
|
||||||
|
return db.dropTable('feature_types', cb);
|
||||||
|
};
|
17
migrations/20200805094311-add-feature-type-to-features.js
Normal file
17
migrations/20200805094311-add-feature-type-to-features.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
exports.up = function(db, cb) {
|
||||||
|
return db.addColumn(
|
||||||
|
'features',
|
||||||
|
'type',
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
defaultValue: 'release',
|
||||||
|
},
|
||||||
|
cb,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.down = function(db, cb) {
|
||||||
|
return db.removeColumn('features', 'type', 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.0",
|
"unleash-frontend": "3.4.1-0",
|
||||||
"yargs": "^15.1.0"
|
"yargs": "^15.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -250,3 +250,30 @@ test.serial('creates new feature toggle with variant overrides', async t => {
|
|||||||
.set('Content-Type', 'application/json')
|
.set('Content-Type', 'application/json')
|
||||||
.expect(201);
|
.expect(201);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test.serial('creates new feature toggle without type', async t => {
|
||||||
|
t.plan(1);
|
||||||
|
const request = await setupApp(stores);
|
||||||
|
await request.post('/api/admin/features').send({
|
||||||
|
name: 'com.test.noType',
|
||||||
|
enabled: false,
|
||||||
|
strategies: [{ name: 'default' }],
|
||||||
|
});
|
||||||
|
await request.get('/api/admin/features/com.test.noType').expect(res => {
|
||||||
|
t.is(res.body.type, 'release');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.serial('creates new feature toggle with type', async t => {
|
||||||
|
t.plan(1);
|
||||||
|
const request = await setupApp(stores);
|
||||||
|
await request.post('/api/admin/features').send({
|
||||||
|
name: 'com.test.withType',
|
||||||
|
type: 'killswitch',
|
||||||
|
enabled: false,
|
||||||
|
strategies: [{ name: 'default' }],
|
||||||
|
});
|
||||||
|
await request.get('/api/admin/features/com.test.withType').expect(res => {
|
||||||
|
t.is(res.body.type, 'killswitch');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
2
test/fixtures/fake-feature-toggle-store.js
vendored
2
test/fixtures/fake-feature-toggle-store.js
vendored
@ -9,7 +9,7 @@ module.exports = () => {
|
|||||||
if (toggle) {
|
if (toggle) {
|
||||||
return Promise.resolve(toggle);
|
return Promise.resolve(toggle);
|
||||||
}
|
}
|
||||||
return Promise.reject();
|
return Promise.reject(new Error('could not find toggle'));
|
||||||
},
|
},
|
||||||
hasFeature: name => {
|
hasFeature: name => {
|
||||||
const toggle = _features.find(f => f.name === name);
|
const toggle = _features.find(f => f.name === name);
|
||||||
|
@ -5554,10 +5554,10 @@ universalify@^0.1.0:
|
|||||||
version "0.1.2"
|
version "0.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
|
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
|
||||||
|
|
||||||
unleash-frontend@3.4.0:
|
unleash-frontend@3.4.1-0:
|
||||||
version "3.4.0"
|
version "3.4.1-0"
|
||||||
resolved "https://registry.yarnpkg.com/unleash-frontend/-/unleash-frontend-3.4.0.tgz#6df17f56904dab59e7c99765d5443e7716aa0734"
|
resolved "https://registry.yarnpkg.com/unleash-frontend/-/unleash-frontend-3.4.1-0.tgz#480731a753059ad4b38c98b9a5f014eaa44d912b"
|
||||||
integrity sha512-ZHzaPSoBKZGyp+Bneo2vBhTUbc34aOBh6hxyjZm3v/ol7PpTt6D3opoIVe18UElLOAZft2Kjq2Gtv/1gvJx6pg==
|
integrity sha512-skx+SlOFPHcrrQlY5UhNV6stxblUNaB0mGbZfbh7CdIuWMs1Y+KxVoorTqDaHV0zhyxKQvngzZOd696VnMxA4w==
|
||||||
|
|
||||||
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