diff --git a/package.json b/package.json index 93fe7bfa10..4d7cd1f40c 100644 --- a/package.json +++ b/package.json @@ -132,7 +132,6 @@ "pkginfo": "^0.4.1", "prom-client": "^14.0.0", "response-time": "^2.3.2", - "sanitize-filename": "^1.6.3", "semver": "^7.3.5", "serve-favicon": "^2.5.0", "stoppable": "^1.1.0", diff --git a/src/lib/services/feature-toggle-service.ts b/src/lib/services/feature-toggle-service.ts index 7255666ce0..578810369d 100644 --- a/src/lib/services/feature-toggle-service.ts +++ b/src/lib/services/feature-toggle-service.ts @@ -228,6 +228,16 @@ class FeatureToggleService { 'You can not change the featureName for an activation strategy.', ); } + + if ( + strategy.parameters && + 'stickiness' in strategy.parameters && + strategy.parameters.stickiness === '' + ) { + throw new InvalidOperationError( + 'You can not have an empty string for stickiness.', + ); + } } async validateProjectCanAccessSegments( @@ -413,6 +423,14 @@ class FeatureToggleService { ); } + if ( + strategyConfig.parameters && + 'stickiness' in strategyConfig.parameters && + strategyConfig.parameters.stickiness === '' + ) { + strategyConfig.parameters.stickiness = 'default'; + } + try { const newFeatureStrategy = await this.featureStrategiesStore.createStrategyFeatureEnv({ diff --git a/src/migrations/20230420211308-update-context-fields-add-sessionId.js b/src/migrations/20230420211308-update-context-fields-add-sessionId.js new file mode 100644 index 0000000000..4a2dc54d63 --- /dev/null +++ b/src/migrations/20230420211308-update-context-fields-add-sessionId.js @@ -0,0 +1,22 @@ +'use strict'; + +exports.up = function (db, callback) { + db.runSql( + ` + INSERT INTO context_fields(name, description, sort_order, stickiness) VALUES('sessionId', 'Allows you to constrain on sessionId', 4, true) ON CONFLICT DO NOTHING; + + UPDATE context_fields + SET stickiness = true + WHERE name LIKE 'userId' AND stickiness is null; + `, + callback, + ); +}; + +exports.down = function (db, callback) { + db.runSql( + ` + `, + callback, + ); +}; diff --git a/src/test/e2e/api/admin/feature.e2e.test.ts b/src/test/e2e/api/admin/feature.e2e.test.ts index 4583c21c91..430c526ed3 100644 --- a/src/test/e2e/api/admin/feature.e2e.test.ts +++ b/src/test/e2e/api/admin/feature.e2e.test.ts @@ -17,8 +17,11 @@ let app: IUnleashTest; let db: ITestDb; const defaultStrategy = { - name: 'default', - parameters: {}, + name: 'flexibleRollout', + parameters: { + rollout: '100', + stickiness: '', + }, constraints: [], }; @@ -844,3 +847,77 @@ test('Can add and remove tags at the same time', async () => { expect(res.body.tags).toHaveLength(1); }); }); + +test('Should return "default" for stickiness when creating a flexibleRollout strategy with "" for stickiness', async () => { + const username = 'toggle-feature'; + const feature = { + name: 'test-featureA', + description: 'the #1 feature', + }; + const projectId = 'default'; + + await app.services.featureToggleServiceV2.createFeatureToggle( + projectId, + feature, + username, + ); + await app.services.featureToggleServiceV2.createStrategy( + defaultStrategy, + { projectId, featureName: feature.name, environment: DEFAULT_ENV }, + username, + ); + + await app.request + .get( + `/api/admin/projects/${projectId}/features/${feature.name}/environments/${DEFAULT_ENV}`, + ) + .expect((res) => { + const toggle = res.body; + expect(toggle.strategies).toHaveLength(1); + expect(toggle.strategies[0].parameters.stickiness).toBe('default'); + }); + + await app.request + .get(`/api/admin/features/${feature.name}`) + .expect((res) => { + const toggle = res.body; + expect(toggle.strategies).toHaveLength(1); + expect(toggle.strategies[0].parameters.stickiness).toBe('default'); + }); +}); + +test('Should throw error when updating a flexibleRollout strategy with "" for stickiness', async () => { + const username = 'toggle-feature'; + const feature = { + name: 'test-featureB', + description: 'the #1 feature', + }; + const projectId = 'default'; + + await app.services.featureToggleServiceV2.createFeatureToggle( + projectId, + feature, + username, + ); + await app.services.featureToggleServiceV2.createStrategy( + defaultStrategy, + { projectId, featureName: feature.name, environment: DEFAULT_ENV }, + username, + ); + + const featureToggle = + await app.services.featureToggleServiceV2.getFeatureToggle( + feature.name, + ); + + await app.request + .patch( + `/api/admin/projects/${projectId}/features/${feature.name}/environments/${DEFAULT_ENV}/strategies/${featureToggle.environments[0].strategies[0].id}`, + ) + .send(defaultStrategy) + .expect((res) => { + const result = res.body; + expect(res.status).toBe(400); + expect(result.error).toBe('Request validation failed'); + }); +}); diff --git a/website/docs/reference/projects.md b/website/docs/reference/projects.md index 645d65db3c..f439bdbf61 100644 --- a/website/docs/reference/projects.md +++ b/website/docs/reference/projects.md @@ -43,13 +43,15 @@ The UI shows the currently available projects. To create a new project, use the The configuration of a new Project is now available. the following input is available to create the new Project. -![A project creation form. The form fields are labeled "project ID", "name", and "description". The "Create" button is highlighted.](/img/projects_save_new_project.png) +![A project creation form. The "Create" button is highlighted.](/img/projects_save_new_project_v2.png) -| Item | Description | -| ------------ | ---------------------------------- | -| Project Id | Id for this Project | -| Project name | The name of the Project. | -| Description | A short description of the project | +| Item | Description | +|--------------------|---------------------------------------------------------------------------------------------| +| Project Id | Id for this Project | +| Project name | The name of the Project. | +| Description | A short description of the project | +| Mode | The project [collaboration mode](/reference/project-collaboration-mode.md) | +| Default Stickiness | The default stickiness for the project. This setting controls the default stickiness value for variants and for the gradual rollout strategy. | ## Deleting an existing project {#deleting-an-existing-project} diff --git a/website/static/img/projects_save_new_project.png b/website/static/img/projects_save_new_project.png deleted file mode 100644 index dda3786d26..0000000000 Binary files a/website/static/img/projects_save_new_project.png and /dev/null differ diff --git a/website/static/img/projects_save_new_project_v2.png b/website/static/img/projects_save_new_project_v2.png new file mode 100644 index 0000000000..e0f099c575 Binary files /dev/null and b/website/static/img/projects_save_new_project_v2.png differ