diff --git a/src/lib/services/state-schema.ts b/src/lib/services/state-schema.ts index 53ace4f461..2345d7aa93 100644 --- a/src/lib/services/state-schema.ts +++ b/src/lib/services/state-schema.ts @@ -11,7 +11,7 @@ export const featureStrategySchema = joi .keys({ id: joi.string().optional(), featureName: joi.string(), - projectName: joi.string(), + projectId: joi.string(), environment: joi.string(), parameters: joi.object().optional(), constraints: joi.array().optional(), @@ -29,6 +29,9 @@ export const environmentSchema = joi.object().keys({ name: nameType.allow(':global:'), displayName: joi.string().optional().allow(''), type: joi.string().required(), + sortOrder: joi.number().optional(), + enabled: joi.boolean().optional(), + protected: joi.boolean().optional(), }); export const updateEnvironmentSchema = joi.object().keys({ diff --git a/src/test/e2e/api/admin/state.e2e.test.ts b/src/test/e2e/api/admin/state.e2e.test.ts index a05be8046d..e396d81153 100644 --- a/src/test/e2e/api/admin/state.e2e.test.ts +++ b/src/test/e2e/api/admin/state.e2e.test.ts @@ -1,11 +1,12 @@ -import dbInit from '../../helpers/database-init'; -import { setupApp } from '../../helpers/test-helper'; +import dbInit, { ITestDb } from '../../helpers/database-init'; +import { IUnleashTest, setupApp } from '../../helpers/test-helper'; import getLogger from '../../../fixtures/no-logger'; +import { GLOBAL_ENV } from '../../../../lib/types/environment'; const importData = require('../../../examples/import.json'); -let app; -let db; +let app: IUnleashTest; +let db: ITestDb; beforeAll(async () => { db = await dbInit('state_api_serial', getLogger); @@ -93,3 +94,220 @@ test('imports strategies and features from yaml file', async () => { .attach('file', 'src/test/examples/import.yml') .expect(202); }); + +test('import works for 3.17 json format', async () => { + await app.request + .post('/api/admin/state/import') + .attach('file', 'src/test/examples/exported3176.json') + .expect(202); +}); + +test('import works for 3.17 enterprise json format', async () => { + await app.request + .post('/api/admin/state/import') + .attach('file', 'src/test/examples/exported-3175-enterprise.json') + .expect(202); +}); +test('import works for 4.0 enterprise format', async () => { + await app.request + .post('/api/admin/state/import') + .attach('file', 'src/test/examples/exported405-enterprise.json') + .expect(202); +}); + +test('import for 4.1.2 enterprise format fails', async () => { + await expect(async () => + app.request + .post('/api/admin/state/import') + .attach('file', 'src/test/examples/exported412-enterprise.json') + .expect(202), + ).rejects; +}); + +test('import for 4.1.2 enterprise format fixed works', async () => { + await app.request + .post('/api/admin/state/import') + .attach( + 'file', + 'src/test/examples/exported412-enterprise-necessary-fixes.json', + ) + .expect(202); +}); + +test('Can roundtrip. I.e. export and then import', async () => { + const projectId = 'export-project'; + const environmentId = 'export-environment'; + const userName = 'export-user'; + const featureName = 'export.feature'; + await db.stores.environmentStore.create({ + name: environmentId, + type: 'test', + displayName: 'Environment for export', + }); + await db.stores.projectStore.create({ + name: projectId, + id: projectId, + description: 'Project for export', + }); + await app.services.environmentService.addEnvironmentToProject( + environmentId, + projectId, + ); + await app.services.featureToggleServiceV2.createFeatureToggle( + projectId, + { + type: 'Release', + name: featureName, + description: 'Feature for export', + }, + userName, + ); + await app.services.featureToggleServiceV2.createStrategy( + { + name: 'default', + constraints: [ + { contextName: 'userId', operator: 'IN', values: ['123'] }, + ], + parameters: {}, + }, + projectId, + featureName, + environmentId, + ); + const data = await app.services.stateService.export({}); + await app.services.stateService.import({ + data, + dropBeforeImport: true, + keepExisting: false, + userName: 'export-tester', + }); +}); + +test('Roundtrip with tags works', async () => { + const projectId = 'tags-project'; + const environmentId = 'tags-environment'; + const userName = 'tags-user'; + const featureName = 'tags.feature'; + await db.stores.environmentStore.create({ + name: environmentId, + type: 'test', + displayName: 'Environment for export', + }); + await db.stores.projectStore.create({ + name: projectId, + id: projectId, + description: 'Project for export', + }); + await app.services.environmentService.addEnvironmentToProject( + environmentId, + projectId, + ); + await app.services.featureToggleServiceV2.createFeatureToggle( + projectId, + { + type: 'Release', + name: featureName, + description: 'Feature for export', + }, + userName, + ); + await app.services.featureToggleServiceV2.createStrategy( + { + name: 'default', + constraints: [ + { contextName: 'userId', operator: 'IN', values: ['123'] }, + ], + parameters: {}, + }, + projectId, + featureName, + environmentId, + ); + await app.services.featureTagService.addTag( + featureName, + { type: 'simple', value: 'export-test' }, + userName, + ); + await app.services.featureTagService.addTag( + featureName, + { type: 'simple', value: 'export-test-2' }, + userName, + ); + const data = await app.services.stateService.export({}); + await app.services.stateService.import({ + data, + dropBeforeImport: true, + keepExisting: false, + userName: 'export-tester', + }); + + const f = await app.services.featureTagService.listTags(featureName); + expect(f).toHaveLength(2); +}); + +test('Roundtrip with strategies in multiple environments works', async () => { + const projectId = 'multiple-environment-project'; + const environmentId = 'multiple-environment-environment'; + const userName = 'multiple-environment-user'; + const featureName = 'multiple-environment.feature'; + await db.stores.environmentStore.create({ + name: environmentId, + type: 'test', + displayName: 'Environment for export', + }); + await db.stores.projectStore.create({ + name: projectId, + id: projectId, + description: 'Project for export', + }); + await app.services.environmentService.addEnvironmentToProject( + environmentId, + projectId, + ); + await app.services.environmentService.addEnvironmentToProject( + GLOBAL_ENV, + projectId, + ); + await app.services.featureToggleServiceV2.createFeatureToggle( + projectId, + { + type: 'Release', + name: featureName, + description: 'Feature for export', + }, + userName, + ); + await app.services.featureToggleServiceV2.createStrategy( + { + name: 'default', + constraints: [ + { contextName: 'userId', operator: 'IN', values: ['123'] }, + ], + parameters: {}, + }, + projectId, + featureName, + environmentId, + ); + await app.services.featureToggleServiceV2.createStrategy( + { + name: 'default', + constraints: [ + { contextName: 'userId', operator: 'IN', values: ['123'] }, + ], + parameters: {}, + }, + projectId, + featureName, + GLOBAL_ENV, + ); + const data = await app.services.stateService.export({}); + await app.services.stateService.import({ + data, + dropBeforeImport: true, + keepExisting: false, + userName: 'export-tester', + }); + const f = await app.services.featureToggleServiceV2.getFeature(featureName); + expect(f.environments).toHaveLength(2); +}); diff --git a/src/test/examples/exported-3175-enterprise.json b/src/test/examples/exported-3175-enterprise.json new file mode 100644 index 0000000000..b447711506 --- /dev/null +++ b/src/test/examples/exported-3175-enterprise.json @@ -0,0 +1 @@ +{"version":1,"features":[{"name":"in-another-project","description":"","type":"release","project":"someother","enabled":true,"stale":false,"strategies":[{"name":"gradualRolloutRandom","parameters":{"percentage":"29"},"constraints":[{"contextName":"environment","operator":"IN","values":["dev"]},{"contextName":"environment","operator":"IN","values":["prod"]}]}],"variants":[],"createdAt":"2021-09-17T07:14:03.718Z","lastSeenAt":null},{"name":"this-is-fun","description":"","type":"release","project":"default","enabled":true,"stale":false,"strategies":[{"name":"gradualRolloutRandom","parameters":{"percentage":"100"}}],"variants":[],"createdAt":"2021-09-17T07:06:40.925Z","lastSeenAt":null},{"name":"version.three.seventeen","description":"","type":"operational","project":"default","enabled":true,"stale":false,"strategies":[{"name":"default","parameters":{}}],"variants":[],"createdAt":"2021-09-17T07:06:56.421Z","lastSeenAt":null},{"name":"with-constraints","description":"","type":"release","project":"default","enabled":true,"stale":false,"strategies":[{"name":"default","parameters":{},"constraints":[{"contextName":"userId","operator":"IN","values":["123456"]}]}],"variants":[],"createdAt":"2021-09-17T07:14:39.509Z","lastSeenAt":null}],"strategies":[],"projects":[{"id":"default","name":"Default","description":"Default project","createdAt":"2021-09-17T05:06:16.299Z"},{"id":"someother","name":"Some other project","description":"","createdAt":"2021-09-17T05:13:45.011Z"}],"tagTypes":[{"name":"simple","description":"Used to simplify filtering of features","icon":"#"}],"tags":[],"featureTags":[]} \ No newline at end of file diff --git a/src/test/examples/exported3176.json b/src/test/examples/exported3176.json new file mode 100644 index 0000000000..edfd091446 --- /dev/null +++ b/src/test/examples/exported3176.json @@ -0,0 +1 @@ +{"version":1,"features":[{"name":"this-is-fun","description":"","type":"release","project":"default","enabled":true,"stale":false,"strategies":[{"name":"gradualRolloutRandom","parameters":{"percentage":"100"}}],"variants":[],"createdAt":"2021-09-17T07:06:40.925Z","lastSeenAt":null},{"name":"version.three.seventeen","description":"","type":"operational","project":"default","enabled":true,"stale":false,"strategies":[{"name":"default","parameters":{}}],"variants":[],"createdAt":"2021-09-17T07:06:56.421Z","lastSeenAt":null}],"strategies":[],"projects":[{"id":"default","name":"Default","description":"Default project","createdAt":"2021-09-17T05:06:16.299Z"}],"tagTypes":[{"name":"simple","description":"Used to simplify filtering of features","icon":"#"}],"tags":[],"featureTags":[]} \ No newline at end of file diff --git a/src/test/examples/exported405-enterprise.json b/src/test/examples/exported405-enterprise.json new file mode 100644 index 0000000000..d2c0b5e30d --- /dev/null +++ b/src/test/examples/exported405-enterprise.json @@ -0,0 +1 @@ +{"version":1,"features":[{"name":"another-toggle","description":"","type":"release","project":"someother","enabled":true,"stale":false,"strategies":[{"name":"userWithId","parameters":{"userIds":"12541,123"},"constraints":[]}],"variants":[],"createdAt":"2021-09-17T07:22:16.404Z","lastSeenAt":null},{"name":"in-another-project","description":"","type":"release","project":"someother","enabled":true,"stale":false,"strategies":[{"name":"gradualRolloutRandom","parameters":{"percentage":"29"},"constraints":[{"contextName":"environment","operator":"IN","values":["dev"]},{"contextName":"environment","operator":"IN","values":["prod"]}]}],"variants":[],"createdAt":"2021-09-17T07:14:03.718Z","lastSeenAt":null},{"name":"this-is-fun","description":"","type":"release","project":"default","enabled":true,"stale":false,"strategies":[{"name":"gradualRolloutRandom","parameters":{"percentage":"100"}}],"variants":[],"createdAt":"2021-09-17T07:06:40.925Z","lastSeenAt":null},{"name":"version.three.seventeen","description":"","type":"operational","project":"default","enabled":true,"stale":false,"strategies":[{"name":"default","parameters":{}}],"variants":[],"createdAt":"2021-09-17T07:06:56.421Z","lastSeenAt":null},{"name":"with-constraints","description":"","type":"release","project":"default","enabled":true,"stale":false,"strategies":[{"name":"default","parameters":{},"constraints":[{"contextName":"userId","operator":"IN","values":["123456"]}]}],"variants":[],"createdAt":"2021-09-17T07:14:39.509Z","lastSeenAt":null}],"strategies":[{"name":"gradualRolloutRandom","description":"Randomly activate the feature toggle. No stickiness.","parameters":[{"name":"percentage","type":"percentage","description":"","required":false}],"deprecated":true},{"name":"gradualRolloutSessionId","description":"Gradually activate feature toggle. Stickiness based on session id.","parameters":[{"name":"percentage","type":"percentage","description":"","required":false},{"name":"groupId","type":"string","description":"Used to define a activation groups, which allows you to correlate across feature toggles.","required":true}],"deprecated":true},{"name":"gradualRolloutUserId","description":"Gradually activate feature toggle for logged in users. Stickiness based on user id.","parameters":[{"name":"percentage","type":"percentage","description":"","required":false},{"name":"groupId","type":"string","description":"Used to define a activation groups, which allows you to correlate across feature toggles.","required":true}],"deprecated":true}],"projects":[{"id":"default","name":"Default","description":"Default project","createdAt":"2021-09-17T05:06:16.299Z"},{"id":"someother","name":"Some other project","description":"","createdAt":"2021-09-17T05:13:45.011Z"}],"tagTypes":[{"name":"simple","description":"Used to simplify filtering of features","icon":"#"}],"tags":[],"featureTags":[]} \ No newline at end of file diff --git a/src/test/examples/exported412-enterprise-necessary-fixes.json b/src/test/examples/exported412-enterprise-necessary-fixes.json new file mode 100644 index 0000000000..d1b1093d1c --- /dev/null +++ b/src/test/examples/exported412-enterprise-necessary-fixes.json @@ -0,0 +1,275 @@ +{ + "version": 2, + "features": [ + { + "name": "this-is-fun", + "description": "", + "type": "release", + "project": "default", + "stale": false, + "variants": [], + "createdAt": "2021-09-17T07:06:40.925Z", + "lastSeenAt": null + }, + { + "name": "version.three.seventeen", + "description": "", + "type": "operational", + "project": "default", + "stale": false, + "variants": [], + "createdAt": "2021-09-17T07:06:56.421Z", + "lastSeenAt": null + }, + { + "name": "in-another-project", + "description": "", + "type": "release", + "project": "someother", + "stale": false, + "variants": [], + "createdAt": "2021-09-17T07:14:03.718Z", + "lastSeenAt": null + }, + { + "name": "with-constraints", + "description": "", + "type": "release", + "project": "default", + "stale": false, + "variants": [], + "createdAt": "2021-09-17T07:14:39.509Z", + "lastSeenAt": null + }, + { + "name": "another-toggle", + "description": "", + "type": "release", + "project": "someother", + "stale": false, + "variants": [], + "createdAt": "2021-09-17T07:22:16.404Z", + "lastSeenAt": null + }, + { + "name": "toggle-created-in-4-1", + "description": "", + "type": "operational", + "project": "default", + "stale": false, + "variants": [], + "createdAt": "2021-09-17T07:24:13.897Z", + "lastSeenAt": null + } + ], + "strategies": [ + { + "name": "gradualRolloutRandom", + "description": "Randomly activate the feature toggle. No stickiness.", + "parameters": [ + { + "name": "percentage", + "type": "percentage", + "description": "", + "required": false + } + ], + "deprecated": true + }, + { + "name": "gradualRolloutSessionId", + "description": "Gradually activate feature toggle. Stickiness based on session id.", + "parameters": [ + { + "name": "percentage", + "type": "percentage", + "description": "", + "required": false + }, + { + "name": "groupId", + "type": "string", + "description": "Used to define a activation groups, which allows you to correlate across feature toggles.", + "required": true + } + ], + "deprecated": true + }, + { + "name": "gradualRolloutUserId", + "description": "Gradually activate feature toggle for logged in users. Stickiness based on user id.", + "parameters": [ + { + "name": "percentage", + "type": "percentage", + "description": "", + "required": false + }, + { + "name": "groupId", + "type": "string", + "description": "Used to define a activation groups, which allows you to correlate across feature toggles.", + "required": true + } + ], + "deprecated": true + } + ], + "projects": [ + { + "id": "default", + "name": "Default", + "description": "Default project", + "createdAt": "2021-09-17T05:06:16.299Z", + "health": 100 + }, + { + "id": "someother", + "name": "Some other project", + "description": "", + "createdAt": "2021-09-17T05:13:45.011Z", + "health": 100 + } + ], + "tagTypes": [ + { + "name": "simple", + "description": "Used to simplify filtering of features", + "icon": "#" + } + ], + "tags": [], + "featureTags": [], + "featureStrategies": [ + { + "id": "2ea91298-4565-4db2-8a23-50757001a076", + "featureName": "this-is-fun", + "projectId": "default", + "environment": ":global:", + "strategyName": "gradualRolloutRandom", + "parameters": { + "percentage": "100" + }, + "constraints": [], + "createdAt": "2021-09-17T07:23:39.374Z" + }, + { + "id": "edaffaee-cf6e-473f-b137-ae15fb88ff53", + "featureName": "version.three.seventeen", + "projectId": "default", + "environment": ":global:", + "strategyName": "default", + "parameters": {}, + "constraints": [], + "createdAt": "2021-09-17T07:23:39.374Z" + }, + { + "id": "e6eaede4-027a-41a9-8e80-0e0fc0a5d7af", + "featureName": "in-another-project", + "projectId": "someother", + "environment": ":global:", + "strategyName": "gradualRolloutRandom", + "parameters": { + "percentage": "29" + }, + "constraints": [ + { + "values": [ + "dev" + ], + "operator": "IN", + "contextName": "environment" + }, + { + "values": [ + "prod" + ], + "operator": "IN", + "contextName": "environment" + } + ], + "createdAt": "2021-09-17T07:23:39.374Z" + }, + { + "id": "da60e934-246c-4b3e-b314-f2fd1828dd51", + "featureName": "with-constraints", + "projectId": "default", + "environment": ":global:", + "strategyName": "default", + "parameters": {}, + "constraints": [ + { + "values": [ + "123456" + ], + "operator": "IN", + "contextName": "userId" + } + ], + "createdAt": "2021-09-17T07:23:39.374Z" + }, + { + "id": "162058f5-3600-4299-97df-d543a0301bdd", + "featureName": "another-toggle", + "projectId": "someother", + "environment": ":global:", + "strategyName": "userWithId", + "parameters": { + "userIds": "12541,123" + }, + "constraints": [], + "createdAt": "2021-09-17T07:23:39.374Z" + }, + { + "id": "5630e0fb-ebc1-4313-b6df-06b0a563c7b4", + "featureName": "toggle-created-in-4-1", + "projectId": "default", + "environment": ":global:", + "strategyName": "applicationHostname", + "parameters": { + "hostNames": "vg.no" + }, + "constraints": [], + "createdAt": "2021-09-17T07:24:13.904Z" + } + ], + "environments": [ + { + "name": ":global:", + "displayName": "Across all environments", + "type": "production" + } + ], + "featureEnvironments": [ + { + "enabled": true, + "featureName": "this-is-fun", + "environment": ":global:" + }, + { + "enabled": true, + "featureName": "version.three.seventeen", + "environment": ":global:" + }, + { + "enabled": true, + "featureName": "in-another-project", + "environment": ":global:" + }, + { + "enabled": true, + "featureName": "with-constraints", + "environment": ":global:" + }, + { + "enabled": true, + "featureName": "another-toggle", + "environment": ":global:" + }, + { + "enabled": true, + "featureName": "toggle-created-in-4-1", + "environment": ":global:" + } + ] +} diff --git a/src/test/examples/exported412-enterprise.json b/src/test/examples/exported412-enterprise.json new file mode 100644 index 0000000000..f14f23835d --- /dev/null +++ b/src/test/examples/exported412-enterprise.json @@ -0,0 +1,274 @@ +exported412-enterprise.json{ + "version": 2, + "features": [ + { + "name": "this-is-fun", + "description": "", + "type": "release", + "project": "default", + "stale": false, + "variants": [], + "createdAt": "2021-09-17T07:06:40.925Z", + "lastSeenAt": null + }, + { + "name": "version.three.seventeen", + "description": "", + "type": "operational", + "project": "default", + "stale": false, + "variants": [], + "createdAt": "2021-09-17T07:06:56.421Z", + "lastSeenAt": null + }, + { + "name": "in-another-project", + "description": "", + "type": "release", + "project": "someother", + "stale": false, + "variants": [], + "createdAt": "2021-09-17T07:14:03.718Z", + "lastSeenAt": null + }, + { + "name": "with-constraints", + "description": "", + "type": "release", + "project": "default", + "stale": false, + "variants": [], + "createdAt": "2021-09-17T07:14:39.509Z", + "lastSeenAt": null + }, + { + "name": "another-toggle", + "description": "", + "type": "release", + "project": "someother", + "stale": false, + "variants": [], + "createdAt": "2021-09-17T07:22:16.404Z", + "lastSeenAt": null + }, + { + "name": "toggle-created-in-4-1", + "description": "", + "type": "operational", + "project": "default", + "stale": false, + "variants": [], + "createdAt": "2021-09-17T07:24:13.897Z", + "lastSeenAt": null + } + ], + "strategies": [ + { + "name": "gradualRolloutRandom", + "description": "Randomly activate the feature toggle. No stickiness.", + "parameters": [ + { + "name": "percentage", + "type": "percentage", + "description": "", + "required": false + } + ], + "deprecated": true + }, + { + "name": "gradualRolloutSessionId", + "description": "Gradually activate feature toggle. Stickiness based on session id.", + "parameters": [ + { + "name": "percentage", + "type": "percentage", + "description": "", + "required": false + }, + { + "name": "groupId", + "type": "string", + "description": "Used to define a activation groups, which allows you to correlate across feature toggles.", + "required": true + } + ], + "deprecated": true + }, + { + "name": "gradualRolloutUserId", + "description": "Gradually activate feature toggle for logged in users. Stickiness based on user id.", + "parameters": [ + { + "name": "percentage", + "type": "percentage", + "description": "", + "required": false + }, + { + "name": "groupId", + "type": "string", + "description": "Used to define a activation groups, which allows you to correlate across feature toggles.", + "required": true + } + ], + "deprecated": true + } + ], + "projects": [ + { + "id": "default", + "name": "Default", + "description": "Default project", + "createdAt": "2021-09-17T05:06:16.299Z", + "health": 100 + }, + { + "id": "someother", + "name": "Some other project", + "description": "", + "createdAt": "2021-09-17T05:13:45.011Z", + "health": 100 + } + ], + "tagTypes": [ + { + "name": "simple", + "description": "Used to simplify filtering of features", + "icon": "#" + } + ], + "tags": [], + "featureTags": [], + "featureStrategies": [ + { + "id": "2ea91298-4565-4db2-8a23-50757001a076", + "featureName": "this-is-fun", + "projectName": "default", + "environment": ":global:", + "strategyName": "gradualRolloutRandom", + "parameters": { + "percentage": "100" + }, + "constraints": [], + "createdAt": "2021-09-17T07:23:39.374Z" + }, + { + "id": "edaffaee-cf6e-473f-b137-ae15fb88ff53", + "featureName": "version.three.seventeen", + "projectName": "default", + "environment": ":global:", + "strategyName": "default", + "parameters": {}, + "constraints": [], + "createdAt": "2021-09-17T07:23:39.374Z" + }, + { + "id": "e6eaede4-027a-41a9-8e80-0e0fc0a5d7af", + "featureName": "in-another-project", + "projectName": "someother", + "environment": ":global:", + "strategyName": "gradualRolloutRandom", + "parameters": { + "percentage": "29" + }, + "constraints": [ + { + "values": [ + "dev" + ], + "operator": "IN", + "contextName": "environment" + }, + { + "values": [ + "prod" + ], + "operator": "IN", + "contextName": "environment" + } + ], + "createdAt": "2021-09-17T07:23:39.374Z" + }, + { + "id": "da60e934-246c-4b3e-b314-f2fd1828dd51", + "featureName": "with-constraints", + "projectName": "default", + "environment": ":global:", + "strategyName": "default", + "parameters": {}, + "constraints": [ + { + "values": [ + "123456" + ], + "operator": "IN", + "contextName": "userId" + } + ], + "createdAt": "2021-09-17T07:23:39.374Z" + }, + { + "id": "162058f5-3600-4299-97df-d543a0301bdd", + "featureName": "another-toggle", + "projectName": "someother", + "environment": ":global:", + "strategyName": "userWithId", + "parameters": { + "userIds": "12541,123" + }, + "constraints": [], + "createdAt": "2021-09-17T07:23:39.374Z" + }, + { + "id": "5630e0fb-ebc1-4313-b6df-06b0a563c7b4", + "featureName": "toggle-created-in-4-1", + "projectName": "default", + "environment": ":global:", + "strategyName": "applicationHostname", + "parameters": { + "hostNames": "vg.no" + }, + "constraints": [], + "createdAt": "2021-09-17T07:24:13.904Z" + } + ], + "environments": [ + { + "name": ":global:", + "displayName": "Across all environments" + } + ], + "featureEnvironments": [ + { + "enabled": true, + "featureName": "this-is-fun", + "environment": ":global:" + }, + { + "enabled": true, + "featureName": "version.three.seventeen", + "environment": ":global:" + }, + { + "enabled": true, + "featureName": "in-another-project", + "environment": ":global:" + }, + { + "enabled": true, + "featureName": "with-constraints", + "environment": ":global:" + }, + { + "enabled": true, + "featureName": "another-toggle", + "environment": ":global:" + }, + { + "enabled": true, + "featureName": "toggle-created-in-4-1", + "environment": ":global:" + } + ] +}