diff --git a/src/lib/db/feature-strategy-store.ts b/src/lib/db/feature-strategy-store.ts index df04e4bc9b..00b36bd7c0 100644 --- a/src/lib/db/feature-strategy-store.ts +++ b/src/lib/db/feature-strategy-store.ts @@ -210,6 +210,10 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore { 'features.last_seen_at as last_seen_at', 'feature_environments.enabled as enabled', 'feature_environments.environment as environment', + 'environments.name as environment_name', + 'environments.type as environment_type', + 'environments.sort_order as environment_sort_order', + 'environments.display_name as environment_display_name', 'feature_strategies.id as strategy_id', 'feature_strategies.strategy_name as strategy_name', 'feature_strategies.parameters as parameters', @@ -232,7 +236,13 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore { 'feature_environments.environment', ); }) - .where({ name: featureName, archived: archived ? 1 : 0 }); + .fullOuterJoin( + 'environments', + 'feature_environments.environment', + 'environments.name', + ) + .where('features.name', featureName) + .andWhere('features.archived', archived ? 1 : 0); stopTimer(); if (rows.length > 0) { const featureToggle = rows.reduce((acc, r) => { @@ -254,6 +264,8 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore { } const env = acc.environments[r.environment]; env.enabled = r.enabled; + env.type = r.environment_type; + env.sortOrder = r.environment_sort_order; if (!env.strategies) { env.strategies = []; } @@ -265,7 +277,10 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore { }, {}); featureToggle.environments = Object.values( featureToggle.environments, - ); + ).sort((a, b) => { + // @ts-ignore + return a.sortOrder - b.sortOrder; + }); featureToggle.environments = featureToggle.environments.map((e) => { e.strategies = e.strategies.sort( (a, b) => a.sortOrder - b.sortOrder, @@ -286,6 +301,8 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore { name: r.environment, displayName: r.display_name, enabled: r.enabled, + type: r.environment_type, + sortOrder: r.environment_sort_order, }; } @@ -304,6 +321,8 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore { 'feature_environments.enabled as enabled', 'feature_environments.environment as environment', 'environments.display_name as display_name', + 'environments.type as environment_type', + 'environments.sort_order as environment_sort_order', ) .fullOuterJoin( 'feature_environments', diff --git a/src/lib/types/model.ts b/src/lib/types/model.ts index 25a218509e..34c1b4c661 100644 --- a/src/lib/types/model.ts +++ b/src/lib/types/model.ts @@ -114,12 +114,15 @@ export interface IEnvironmentCreate { name: string; displayName: string; type: string; + sortOrder?: number; } export interface IEnvironmentOverview { name: string; displayName: string; enabled: boolean; + type: string; + sortOrder: number; } export interface IFeatureOverview { diff --git a/src/test/e2e/api/admin/project/feature.strategy.e2e.test.ts b/src/test/e2e/api/admin/project/feature.strategy.e2e.test.ts index 3815950798..f8234a6405 100644 --- a/src/test/e2e/api/admin/project/feature.strategy.e2e.test.ts +++ b/src/test/e2e/api/admin/project/feature.strategy.e2e.test.ts @@ -1,6 +1,7 @@ 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'; let app: IUnleashTest; let db: ITestDb; @@ -579,6 +580,76 @@ test('Can add strategy to feature toggle to default env', async () => { }); }); +test('Environments are returned in sortOrder', async () => { + const sortedSecond = 'sortedSecond'; + const sortedLast = 'sortedLast'; + const featureName = 'feature.strategy.toggle.sortOrder'; + // Create environments + await db.stores.environmentStore.create({ + name: sortedLast, + displayName: 'Enable feature for environment', + type: 'production', + sortOrder: 8000, + }); + await db.stores.environmentStore.create({ + name: sortedSecond, + displayName: 'Enable feature for environment', + type: 'production', + sortOrder: 8, + }); + + // Connect environments to project + await app.request + .post('/api/admin/projects/default/environments') + .send({ + environment: sortedSecond, + }) + .expect(200); + await app.request + .post('/api/admin/projects/default/environments') + .send({ + environment: sortedLast, + }) + .expect(200); + /* Create feature toggle */ + await app.request + .post('/api/admin/projects/default/features') + .send({ name: featureName }) + .expect(201); + /* create strategies connected to feature toggle */ + await app.request + .post( + `/api/admin/projects/default/features/${featureName}/environments/${sortedSecond}/strategies`, + ) + .send({ + name: 'default', + parameters: { + userId: 'string', + }, + }) + .expect(200); + await app.request + .post( + `/api/admin/projects/default/features/${featureName}/environments/${sortedLast}/strategies`, + ) + .send({ + name: 'default', + parameters: { + userId: 'string', + }, + }) + .expect(200); + await app.request + .get(`/api/admin/projects/default/features/${featureName}`) + .expect(200) + .expect((res) => { + expect(res.body.environments).toHaveLength(3); + expect(res.body.environments[0].name).toBe(GLOBAL_ENV); + expect(res.body.environments[1].name).toBe(sortedSecond); + expect(res.body.environments[2].name).toBe(sortedLast); + }); +}); + test('Can get strategies for feature and environment', async () => { const envName = 'get-strategy'; // Create environment @@ -829,7 +900,7 @@ test('Can not enable environment for feature without strategies', async () => { .set('Content-Type', 'application/json') .expect(403); await app.request - .get('/api/admin/projects/default/features/com.test.enable.environment') + .get(`/api/admin/projects/default/features/${featureName}`) .expect(200) .expect('Content-Type', /json/) .expect((res) => { @@ -837,6 +908,7 @@ test('Can not enable environment for feature without strategies', async () => { (e) => e.name === environment, ); expect(enabledFeatureEnv.enabled).toBe(false); + expect(enabledFeatureEnv.type).toBe('test'); }); });