mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-09 00:18:00 +01:00
task: Disables feature_environments without strategies (#1003)
This commit is contained in:
parent
ac049d2e00
commit
a401580228
@ -10,7 +10,10 @@ import { DB_TIME } from '../metric-events';
|
|||||||
import { IFeatureEnvironment } from '../types/model';
|
import { IFeatureEnvironment } from '../types/model';
|
||||||
import NotFoundError from '../error/notfound-error';
|
import NotFoundError from '../error/notfound-error';
|
||||||
|
|
||||||
const T = { featureEnvs: 'feature_environments' };
|
const T = {
|
||||||
|
featureEnvs: 'feature_environments',
|
||||||
|
featureStrategies: 'feature_strategies',
|
||||||
|
};
|
||||||
|
|
||||||
interface IFeatureEnvironmentRow {
|
interface IFeatureEnvironmentRow {
|
||||||
environment: string;
|
environment: string;
|
||||||
@ -92,6 +95,22 @@ export class FeatureEnvironmentStore implements IFeatureEnvironmentStore {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async disableEnvironmentIfNoStrategies(
|
||||||
|
featureName: string,
|
||||||
|
environment: string,
|
||||||
|
): Promise<void> {
|
||||||
|
const result = await this.db.raw(
|
||||||
|
`SELECT EXISTS (SELECT 1 FROM ${T.featureStrategies} WHERE feature_name = ? AND environment = ?) AS enabled`,
|
||||||
|
[featureName, environment],
|
||||||
|
);
|
||||||
|
const { enabled } = result.rows[0];
|
||||||
|
if (!enabled) {
|
||||||
|
await this.db(T.featureEnvs)
|
||||||
|
.update({ enabled: false })
|
||||||
|
.where({ feature_name: featureName, environment });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async addEnvironmentToFeature(
|
async addEnvironmentToFeature(
|
||||||
featureName: string,
|
featureName: string,
|
||||||
environment: string,
|
environment: string,
|
||||||
|
@ -300,12 +300,13 @@ export default class ProjectFeaturesController extends Controller {
|
|||||||
res: Response,
|
res: Response,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
this.logger.info('Deleting strategy');
|
this.logger.info('Deleting strategy');
|
||||||
const { environment, projectId } = req.params;
|
const { environment, projectId, featureName } = req.params;
|
||||||
const userName = extractUsername(req);
|
const userName = extractUsername(req);
|
||||||
const { strategyId } = req.params;
|
const { strategyId } = req.params;
|
||||||
this.logger.info(strategyId);
|
this.logger.info(strategyId);
|
||||||
const strategy = await this.featureService.deleteStrategy(
|
const strategy = await this.featureService.deleteStrategy(
|
||||||
strategyId,
|
strategyId,
|
||||||
|
featureName,
|
||||||
userName,
|
userName,
|
||||||
projectId,
|
projectId,
|
||||||
environment,
|
environment,
|
||||||
|
@ -226,6 +226,7 @@ class FeatureToggleServiceV2 {
|
|||||||
*/
|
*/
|
||||||
async deleteStrategy(
|
async deleteStrategy(
|
||||||
id: string,
|
id: string,
|
||||||
|
featureName: string,
|
||||||
userName: string,
|
userName: string,
|
||||||
project: string = 'default',
|
project: string = 'default',
|
||||||
environment: string = DEFAULT_ENV,
|
environment: string = DEFAULT_ENV,
|
||||||
@ -240,6 +241,11 @@ class FeatureToggleServiceV2 {
|
|||||||
id,
|
id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
// If there are no strategies left for environment disable it
|
||||||
|
await this.featureEnvironmentStore.disableEnvironmentIfNoStrategies(
|
||||||
|
featureName,
|
||||||
|
environment,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getStrategiesForEnvironment(
|
async getStrategiesForEnvironment(
|
||||||
|
@ -34,7 +34,10 @@ export interface IFeatureEnvironmentStore
|
|||||||
environment: string,
|
environment: string,
|
||||||
enabled: boolean,
|
enabled: boolean,
|
||||||
): Promise<void>;
|
): Promise<void>;
|
||||||
|
disableEnvironmentIfNoStrategies(
|
||||||
|
featureName: string,
|
||||||
|
environment: string,
|
||||||
|
): Promise<void>;
|
||||||
disconnectFeatures(environment: string, project: string): Promise<void>;
|
disconnectFeatures(environment: string, project: string): Promise<void>;
|
||||||
connectFeatures(environment: string, projectId: string): Promise<void>;
|
connectFeatures(environment: string, projectId: string): Promise<void>;
|
||||||
|
|
||||||
|
@ -1108,3 +1108,140 @@ test('Feature strategies list should respect strategy sortorders for each enviro
|
|||||||
expect(strategies[1].sortOrder).toBe(sortOrderSecond);
|
expect(strategies[1].sortOrder).toBe(sortOrderSecond);
|
||||||
expect(strategies[2].sortOrder).toBe(sortOrderDefault);
|
expect(strategies[2].sortOrder).toBe(sortOrderDefault);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Deleting last strategy for feature environment should disable that environment', async () => {
|
||||||
|
const envName = 'last_strategy_delete_env';
|
||||||
|
const featureName = 'last_strategy_delete_feature';
|
||||||
|
// Create environment
|
||||||
|
await db.stores.environmentStore.create({
|
||||||
|
name: envName,
|
||||||
|
type: 'test',
|
||||||
|
});
|
||||||
|
// Connect environment to project
|
||||||
|
await app.request
|
||||||
|
.post('/api/admin/projects/default/environments')
|
||||||
|
.send({
|
||||||
|
environment: envName,
|
||||||
|
})
|
||||||
|
.expect(200);
|
||||||
|
await app.request
|
||||||
|
.post('/api/admin/projects/default/features')
|
||||||
|
.send({ name: featureName })
|
||||||
|
.expect(201);
|
||||||
|
let strategyId;
|
||||||
|
await app.request
|
||||||
|
.post(
|
||||||
|
`/api/admin/projects/default/features/${featureName}/environments/${envName}/strategies`,
|
||||||
|
)
|
||||||
|
.send({
|
||||||
|
name: 'default',
|
||||||
|
parameters: {
|
||||||
|
userId: 'string',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.expect(200)
|
||||||
|
.expect((res) => {
|
||||||
|
strategyId = res.body.id;
|
||||||
|
});
|
||||||
|
// Enable feature_environment
|
||||||
|
await app.request
|
||||||
|
.post(
|
||||||
|
`/api/admin/projects/default/features/${featureName}/environments/${envName}/on`,
|
||||||
|
)
|
||||||
|
.send({})
|
||||||
|
.expect(200);
|
||||||
|
await app.request
|
||||||
|
.get(
|
||||||
|
`/api/admin/projects/default/features/${featureName}/environments/${envName}`,
|
||||||
|
)
|
||||||
|
.expect(200)
|
||||||
|
.expect((res) => {
|
||||||
|
expect(res.body.enabled).toBeTruthy();
|
||||||
|
});
|
||||||
|
// Delete last strategy, this should also disable the environment
|
||||||
|
await app.request.delete(
|
||||||
|
`/api/admin/projects/default/features/${featureName}/environments/${envName}/strategies/${strategyId}`,
|
||||||
|
);
|
||||||
|
await app.request
|
||||||
|
.get(
|
||||||
|
`/api/admin/projects/default/features/${featureName}/environments/${envName}`,
|
||||||
|
)
|
||||||
|
.expect(200)
|
||||||
|
.expect((res) => {
|
||||||
|
expect(res.body.enabled).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Deleting strategy for feature environment should not disable that environment as long as there are other strategies', async () => {
|
||||||
|
const envName = 'any_strategy_delete_env';
|
||||||
|
const featureName = 'any_strategy_delete_feature';
|
||||||
|
// Create environment
|
||||||
|
await db.stores.environmentStore.create({
|
||||||
|
name: envName,
|
||||||
|
type: 'test',
|
||||||
|
});
|
||||||
|
// Connect environment to project
|
||||||
|
await app.request
|
||||||
|
.post('/api/admin/projects/default/environments')
|
||||||
|
.send({
|
||||||
|
environment: envName,
|
||||||
|
})
|
||||||
|
.expect(200);
|
||||||
|
await app.request
|
||||||
|
.post('/api/admin/projects/default/features')
|
||||||
|
.send({ name: featureName })
|
||||||
|
.expect(201);
|
||||||
|
let strategyId;
|
||||||
|
await app.request
|
||||||
|
.post(
|
||||||
|
`/api/admin/projects/default/features/${featureName}/environments/${envName}/strategies`,
|
||||||
|
)
|
||||||
|
.send({
|
||||||
|
name: 'default',
|
||||||
|
parameters: {
|
||||||
|
userId: 'string',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.expect(200)
|
||||||
|
.expect((res) => {
|
||||||
|
strategyId = res.body.id;
|
||||||
|
});
|
||||||
|
await app.request
|
||||||
|
.post(
|
||||||
|
`/api/admin/projects/default/features/${featureName}/environments/${envName}/strategies`,
|
||||||
|
)
|
||||||
|
.send({
|
||||||
|
name: 'default',
|
||||||
|
parameters: {
|
||||||
|
customerId: 'string',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.expect(200);
|
||||||
|
// Enable feature_environment
|
||||||
|
await app.request
|
||||||
|
.post(
|
||||||
|
`/api/admin/projects/default/features/${featureName}/environments/${envName}/on`,
|
||||||
|
)
|
||||||
|
.send({})
|
||||||
|
.expect(200);
|
||||||
|
await app.request
|
||||||
|
.get(
|
||||||
|
`/api/admin/projects/default/features/${featureName}/environments/${envName}`,
|
||||||
|
)
|
||||||
|
.expect(200)
|
||||||
|
.expect((res) => {
|
||||||
|
expect(res.body.enabled).toBeTruthy();
|
||||||
|
});
|
||||||
|
// Delete a strategy, this should also disable the environment
|
||||||
|
await app.request.delete(
|
||||||
|
`/api/admin/projects/default/features/${featureName}/environments/${envName}/strategies/${strategyId}`,
|
||||||
|
);
|
||||||
|
await app.request
|
||||||
|
.get(
|
||||||
|
`/api/admin/projects/default/features/${featureName}/environments/${envName}`,
|
||||||
|
)
|
||||||
|
.expect(200)
|
||||||
|
.expect((res) => {
|
||||||
|
expect(res.body.enabled).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -148,4 +148,13 @@ export default class FakeFeatureEnvironmentStore
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
return Promise.reject(new Error('Not implemented'));
|
return Promise.reject(new Error('Not implemented'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
disableEnvironmentIfNoStrategies(
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
featureName: string,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
environment: string,
|
||||||
|
): Promise<void> {
|
||||||
|
return Promise.reject(new Error('Not implemented'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user