1
0
mirror of https://github.com/Unleash/unleash.git synced 2024-12-22 19:07:54 +01:00

feat: add new more specific feature/environment events to addons (#994)

* feat: add new more specific feature/environment events to addons

* Updated strategy change text

* Update all three addon messages for strategy

* Link to new features view for strategy change text

Co-authored-by: Ivar Conradi Østhus <ivarconr@gmail.com>
This commit is contained in:
Christopher Kolstad 2021-10-07 10:22:20 +02:00 committed by GitHub
parent 14e09fe6aa
commit b4b222f4c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 439 additions and 26 deletions

View File

@ -1,5 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Should call datadog webhook for archived toggle 1`] = `"{\\"text\\":\\"%%% \\\\n The feature toggle *[some-toggle](http://some-url.com/archive/strategies/some-toggle)* was *archived* by some@user.com. \\\\n %%% \\",\\"title\\":\\"Unleash notification update\\"}"`;
exports[`Should call datadog webhook 1`] = `"{\\"text\\":\\"%%% \\\\n some@user.com created feature toggle [some-toggle](http://some-url.com/features/strategies/some-toggle)\\\\n**Enabled**: no | **Type**: undefined | **Project**: undefined\\\\n**Activation strategies**: \`\`\`- name: default\\\\n\`\`\` \\\\n %%% \\",\\"title\\":\\"Unleash notification update\\"}"`;
exports[`Should call datadog webhook for archived toggle 1`] = `"{\\"text\\":\\"%%% \\\\n The feature toggle *[some-toggle](http://some-url.com/archive/strategies/some-toggle)* was *archived* by some@user.com. \\\\n %%% \\",\\"title\\":\\"Unleash notification update\\"}"`;
exports[`Should call datadog webhook for toggled environment 1`] = `"{\\"text\\":\\"%%% \\\\n The feature toggle *<http://some-url.com/features/strategies/some-toggle|some-toggle>* in the default project was disabled in environment *development* \\\\n %%% \\",\\"title\\":\\"Unleash notification update\\"}"`;

View File

@ -3,3 +3,5 @@
exports[`Should call slack webhook 1`] = `"{\\"username\\":\\"Unleash\\",\\"icon_emoji\\":\\":unleash:\\",\\"text\\":\\"some@user.com created feature toggle <http://some-url.com/features/strategies/some-toggle|some-toggle>\\\\n*Enabled*: no | *Type*: undefined | *Project*: undefined\\\\n*Activation strategies*: \`\`\`- name: default\\\\n\`\`\`\\",\\"channel\\":\\"#undefined\\",\\"attachments\\":[{\\"actions\\":[{\\"name\\":\\"featureToggle\\",\\"text\\":\\"Open in Unleash\\",\\"type\\":\\"button\\",\\"value\\":\\"featureToggle\\",\\"style\\":\\"primary\\",\\"url\\":\\"http://some-url.com/features/strategies/some-toggle\\"}]}]}"`;
exports[`Should call slack webhook for archived toggle 1`] = `"{\\"username\\":\\"Unleash\\",\\"icon_emoji\\":\\":unleash:\\",\\"text\\":\\"The feature toggle *<http://some-url.com/archive/strategies/some-toggle|some-toggle>* was *archived* by some@user.com.\\",\\"channel\\":\\"#undefined\\",\\"attachments\\":[{\\"actions\\":[{\\"name\\":\\"featureToggle\\",\\"text\\":\\"Open in Unleash\\",\\"type\\":\\"button\\",\\"value\\":\\"featureToggle\\",\\"style\\":\\"primary\\",\\"url\\":\\"http://some-url.com/archive/strategies/some-toggle\\"}]}]}"`;
exports[`Should call webhook for toggled environment 1`] = `"{\\"username\\":\\"Unleash\\",\\"icon_emoji\\":\\":unleash:\\",\\"text\\":\\"The feature toggle *<http://some-url.com/features/strategies/some-toggle|some-toggle>* in the default project was disabled in environment *development*\\",\\"channel\\":\\"#undefined\\",\\"attachments\\":[{\\"actions\\":[{\\"name\\":\\"featureToggle\\",\\"text\\":\\"Open in Unleash\\",\\"type\\":\\"button\\",\\"value\\":\\"featureToggle\\",\\"style\\":\\"primary\\",\\"url\\":\\"http://some-url.com/features/strategies/some-toggle\\"}]}]}"`;

View File

@ -3,3 +3,5 @@
exports[`Should call teams webhook 1`] = `"{\\"themeColor\\":\\"0076D7\\",\\"summary\\":\\"Message\\",\\"sections\\":[{\\"activityTitle\\":\\"Feature toggle some-toggle | *Type*: undefined | *Project*: undefined <br /> *Activation strategies*: \\\\n- name: default\\\\n\\",\\"activitySubtitle\\":\\"Unleash notification update\\",\\"facts\\":[{\\"name\\":\\"User\\",\\"value\\":\\"some@user.com\\"},{\\"name\\":\\"Action\\",\\"value\\":\\"Create\\"},{\\"name\\":\\"Enabled\\",\\"value\\":\\"*no*\\"}]}],\\"potentialAction\\":[{\\"@type\\":\\"OpenUri\\",\\"name\\":\\"Go to feature\\",\\"targets\\":[{\\"os\\":\\"default\\",\\"uri\\":\\"http://some-url.com/features/strategies/some-toggle\\"}]}]}"`;
exports[`Should call teams webhook for archived toggle 1`] = `"{\\"themeColor\\":\\"0076D7\\",\\"summary\\":\\"Message\\",\\"sections\\":[{\\"activityTitle\\":\\"The feature toggle *some-toggle* was *archived*\\",\\"activitySubtitle\\":\\"Unleash notification update\\",\\"facts\\":[{\\"name\\":\\"User\\",\\"value\\":\\"some@user.com\\"},{\\"name\\":\\"Action\\",\\"value\\":\\"feature-archived\\"},{\\"name\\":\\"Enabled\\",\\"value\\":\\"*no*\\"}]}],\\"potentialAction\\":[{\\"@type\\":\\"OpenUri\\",\\"name\\":\\"Go to feature\\",\\"targets\\":[{\\"os\\":\\"default\\",\\"uri\\":\\"http://some-url.com/archive/strategies/some-toggle\\"}]}]}"`;
exports[`Should call teams webhook for toggled environment 1`] = `"{\\"themeColor\\":\\"0076D7\\",\\"summary\\":\\"Message\\",\\"sections\\":[{\\"activityTitle\\":\\"The feature toggle *<http://some-url.com/features/strategies/some-toggle|some-toggle>* in the default project was disabled in environment *development*\\",\\"activitySubtitle\\":\\"Unleash notification update\\",\\"facts\\":[{\\"name\\":\\"User\\",\\"value\\":\\"some@user.com\\"},{\\"name\\":\\"Action\\",\\"value\\":\\"feature-environment-disabled\\"},{\\"name\\":\\"Enabled\\",\\"value\\":\\"*no*\\"}]}],\\"potentialAction\\":[{\\"@type\\":\\"OpenUri\\",\\"name\\":\\"Go to feature\\",\\"targets\\":[{\\"os\\":\\"default\\",\\"uri\\":\\"http://some-url.com/features/strategies/some-toggle\\"}]}]}"`;

View File

@ -5,6 +5,13 @@ import {
FEATURE_REVIVED,
FEATURE_STALE_ON,
FEATURE_STALE_OFF,
FEATURE_ENVIRONMENT_ENABLED,
FEATURE_ENVIRONMENT_DISABLED,
FEATURE_STRATEGY_REMOVE,
FEATURE_STRATEGY_UPDATE,
FEATURE_STRATEGY_ADD,
FEATURE_METADATA_UPDATED,
FEATURE_PROJECT_CHANGE,
} from '../types/events';
import { IAddonDefinition } from '../types/model';
@ -40,6 +47,13 @@ const dataDogDefinition: IAddonDefinition = {
FEATURE_REVIVED,
FEATURE_STALE_ON,
FEATURE_STALE_OFF,
FEATURE_ENVIRONMENT_ENABLED,
FEATURE_ENVIRONMENT_DISABLED,
FEATURE_STRATEGY_REMOVE,
FEATURE_STRATEGY_UPDATE,
FEATURE_STRATEGY_ADD,
FEATURE_METADATA_UPDATED,
FEATURE_PROJECT_CHANGE,
],
tagTypes: [
{

View File

@ -1,4 +1,8 @@
import { FEATURE_CREATED, FEATURE_ARCHIVED } from '../types/events';
import {
FEATURE_ARCHIVED,
FEATURE_CREATED,
FEATURE_ENVIRONMENT_DISABLED,
} from '../types/events';
import { Logger } from '../logger';
import DatadogAddon from './datadog';
@ -58,7 +62,7 @@ test('Should call datadog webhook', async () => {
expect(fetchRetryCalls[0].options.body).toMatchSnapshot();
});
test('Should call datadog webhook for archived toggle', async () => {
test('Should call datadog webhook for archived toggle', async () => {
const addon = new DatadogAddon({
getLogger: noLogger,
unleashUrl: 'http://some-url.com',
@ -82,3 +86,31 @@ test('Should call datadog webhook for archived toggle', async () => {
expect(fetchRetryCalls[0].url).toBe(parameters.url);
expect(fetchRetryCalls[0].options.body).toMatchSnapshot();
});
test(`Should call datadog webhook for toggled environment`, async () => {
const addon = new DatadogAddon({
getLogger: noLogger,
unleashUrl: 'http://some-url.com',
});
const event: IEvent = {
id: 2,
createdAt: new Date(),
type: FEATURE_ENVIRONMENT_DISABLED,
createdBy: 'some@user.com',
environment: 'development',
project: 'default',
data: {
name: 'some-toggle',
},
};
const parameters = {
url: 'http://hooks.slack.com',
};
await addon.handleEvent(event, parameters);
expect(fetchRetryCalls).toHaveLength(1);
expect(fetchRetryCalls[0].url).toBe(parameters.url);
expect(fetchRetryCalls[0].options.body).toMatch(/disabled/);
expect(fetchRetryCalls[0].options.body).toMatchSnapshot();
});

View File

@ -7,6 +7,13 @@ import {
FEATURE_REVIVED,
FEATURE_STALE_ON,
FEATURE_STALE_OFF,
FEATURE_ENVIRONMENT_ENABLED,
FEATURE_STRATEGY_UPDATE,
FEATURE_STRATEGY_ADD,
FEATURE_ENVIRONMENT_DISABLED,
FEATURE_STRATEGY_REMOVE,
FEATURE_METADATA_UPDATED,
FEATURE_PROJECT_CHANGE,
} from '../types/events';
import definition from './datadog-definition';
@ -31,6 +38,25 @@ export default class DatadogAddon extends Addon {
text = this.generateArchivedText(event);
} else if ([FEATURE_STALE_ON, FEATURE_STALE_OFF].includes(event.type)) {
text = this.generateStaleText(event);
} else if (
[
FEATURE_ENVIRONMENT_DISABLED,
FEATURE_ENVIRONMENT_ENABLED,
].includes(event.type)
) {
text = this.generateEnvironmentToggleText(event);
} else if (
[
FEATURE_STRATEGY_ADD,
FEATURE_STRATEGY_REMOVE,
FEATURE_STRATEGY_UPDATE,
].includes(event.type)
) {
text = this.generateStrategyChangeText(event);
} else if (FEATURE_METADATA_UPDATED === event.type) {
text = this.generateMetadataText(event);
} else if (FEATURE_PROJECT_CHANGE === event.type) {
text = this.generateProjectChangeText(event);
} else {
text = this.generateText(event);
}
@ -58,6 +84,44 @@ export default class DatadogAddon extends Addon {
);
}
generateEnvironmentToggleText(event: IEvent): string {
const { environment, project, data, type } = event;
const toggleStatus =
type === FEATURE_ENVIRONMENT_ENABLED ? 'enabled' : 'disabled';
const feature = `<${this.featureLink(event)}|${data.name}>`;
return `The feature toggle *${feature}* in the ${project} project was ${toggleStatus} in environment *${environment}*`;
}
generateStrategyChangeText(event: IEvent): string {
const { environment, project, data, type } = event;
const feature = `<${this.strategiesLink(event)}|${data.featureName}>`;
let action;
if (FEATURE_STRATEGY_UPDATE === type) {
action = 'updated in';
} else if (FEATURE_STRATEGY_ADD) {
action = 'added to';
} else {
action = 'removed from';
}
const strategyText = `a ${data.name} strategy ${action} the *${environment}* environment`;
return `The feature toggle *${feature}* in project: ${project} had ${strategyText}`;
}
generateMetadataText(event: IEvent): string {
const { createdBy, project, data } = event;
const feature = `<${this.featureLink(event)}|${data.name}>`;
return `${createdBy} updated the metadata for ${feature} in project ${project}`;
}
generateProjectChangeText(event: IEvent): string {
const { createdBy, project, data } = event;
return `${createdBy} moved ${data.name} to ${project}`;
}
strategiesLink(event: IEvent): string {
return `${this.unleashUrl}/projects/${event.project}/features2/${event.data.featureName}/strategies?environment=${event.environment}`;
}
featureLink(event: IEvent): string {
const path = event.type === FEATURE_ARCHIVED ? 'archive' : 'features';
return `${this.unleashUrl}/${path}/strategies/${event.data.name}`;

View File

@ -5,6 +5,13 @@ import {
FEATURE_REVIVED,
FEATURE_STALE_ON,
FEATURE_STALE_OFF,
FEATURE_ENVIRONMENT_ENABLED,
FEATURE_ENVIRONMENT_DISABLED,
FEATURE_STRATEGY_REMOVE,
FEATURE_STRATEGY_UPDATE,
FEATURE_STRATEGY_ADD,
FEATURE_METADATA_UPDATED,
FEATURE_PROJECT_CHANGE,
} from '../types/events';
import { IAddonDefinition } from '../types/model';
@ -58,6 +65,13 @@ const slackDefinition: IAddonDefinition = {
FEATURE_REVIVED,
FEATURE_STALE_ON,
FEATURE_STALE_OFF,
FEATURE_ENVIRONMENT_ENABLED,
FEATURE_ENVIRONMENT_DISABLED,
FEATURE_STRATEGY_REMOVE,
FEATURE_STRATEGY_UPDATE,
FEATURE_STRATEGY_ADD,
FEATURE_METADATA_UPDATED,
FEATURE_PROJECT_CHANGE,
],
tagTypes: [
{

View File

@ -1,4 +1,8 @@
import { FEATURE_CREATED, FEATURE_ARCHIVED } from '../types/events';
import {
FEATURE_CREATED,
FEATURE_ARCHIVED,
FEATURE_ENVIRONMENT_DISABLED,
} from '../types/events';
import { Logger } from '../logger';
import SlackAddon from './slack';
@ -83,6 +87,34 @@ test('Should call slack webhook for archived toggle', async () => {
expect(fetchRetryCalls[0].options.body).toMatchSnapshot();
});
test(`Should call webhook for toggled environment`, async () => {
const addon = new SlackAddon({
getLogger: noLogger,
unleashUrl: 'http://some-url.com',
});
const event: IEvent = {
id: 2,
createdAt: new Date(),
type: FEATURE_ENVIRONMENT_DISABLED,
createdBy: 'some@user.com',
environment: 'development',
project: 'default',
data: {
name: 'some-toggle',
},
};
const parameters = {
url: 'http://hooks.slack.com',
};
await addon.handleEvent(event, parameters);
expect(fetchRetryCalls).toHaveLength(1);
expect(fetchRetryCalls[0].url).toBe(parameters.url);
expect(fetchRetryCalls[0].options.body).toMatch(/disabled/);
expect(fetchRetryCalls[0].options.body).toMatchSnapshot();
});
test('Should use default channel', async () => {
const addon = new SlackAddon({
getLogger: noLogger,

View File

@ -11,6 +11,13 @@ import {
FEATURE_REVIVED,
FEATURE_STALE_ON,
FEATURE_STALE_OFF,
FEATURE_STRATEGY_UPDATE,
FEATURE_STRATEGY_REMOVE,
FEATURE_STRATEGY_ADD,
FEATURE_ENVIRONMENT_DISABLED,
FEATURE_ENVIRONMENT_ENABLED,
FEATURE_METADATA_UPDATED,
FEATURE_PROJECT_CHANGE,
} from '../types/events';
export default class SlackAddon extends Addon {
@ -42,6 +49,25 @@ export default class SlackAddon extends Addon {
text = this.generateArchivedText(event);
} else if ([FEATURE_STALE_ON, FEATURE_STALE_OFF].includes(event.type)) {
text = this.generateStaleText(event);
} else if (
[
FEATURE_ENVIRONMENT_DISABLED,
FEATURE_ENVIRONMENT_ENABLED,
].includes(event.type)
) {
text = this.generateEnvironmentToggleText(event);
} else if (
[
FEATURE_STRATEGY_ADD,
FEATURE_STRATEGY_REMOVE,
FEATURE_STRATEGY_UPDATE,
].includes(event.type)
) {
text = this.generateStrategyChangeText(event);
} else if (FEATURE_METADATA_UPDATED === event.type) {
text = this.generateMetadataText(event);
} else if (FEATURE_PROJECT_CHANGE === event.type) {
text = this.generateProjectChangeText(event);
} else {
text = this.generateText(event);
}
@ -82,6 +108,44 @@ export default class SlackAddon extends Addon {
this.logger.info(`Handled event ${event.type}. Status codes=${codes}`);
}
generateEnvironmentToggleText(event: IEvent): string {
const { environment, project, data, type } = event;
const toggleStatus =
type === FEATURE_ENVIRONMENT_ENABLED ? 'enabled' : 'disabled';
const feature = `<${this.featureLink(event)}|${data.name}>`;
return `The feature toggle *${feature}* in the ${project} project was ${toggleStatus} in environment *${environment}*`;
}
generateStrategyChangeText(event: IEvent): string {
const { environment, project, data, type } = event;
const feature = `<${this.strategiesLink(event)}|${data.featureName}>`;
let action;
if (FEATURE_STRATEGY_UPDATE === type) {
action = 'updated in';
} else if (FEATURE_STRATEGY_ADD) {
action = 'added to';
} else {
action = 'removed from';
}
const strategyText = `a ${data.name} strategy ${action} the *${environment}* environment`;
return `The feature toggle *${feature}* in project: ${project} had ${strategyText}`;
}
generateMetadataText(event: IEvent): string {
const { createdBy, project, data } = event;
const feature = `<${this.featureLink(event)}|${data.name}>`;
return `${createdBy} updated the metadata for ${feature} in project ${project}`;
}
generateProjectChangeText(event: IEvent): string {
const { createdBy, project, data } = event;
return `${createdBy} moved ${data.name} to ${project}`;
}
strategiesLink(event: IEvent): string {
return `${this.unleashUrl}/projects/${event.project}/features2/${event.data.featureName}/strategies?environment=${event.environment}`;
}
featureLink(event: IEvent): string {
const path = event.type === FEATURE_ARCHIVED ? 'archive' : 'features';
return `${this.unleashUrl}/${path}/strategies/${event.data.name}`;

View File

@ -5,6 +5,13 @@ import {
FEATURE_REVIVED,
FEATURE_STALE_ON,
FEATURE_STALE_OFF,
FEATURE_ENVIRONMENT_ENABLED,
FEATURE_ENVIRONMENT_DISABLED,
FEATURE_STRATEGY_REMOVE,
FEATURE_STRATEGY_UPDATE,
FEATURE_STRATEGY_ADD,
FEATURE_METADATA_UPDATED,
FEATURE_PROJECT_CHANGE,
} from '../types/events';
import { IAddonDefinition } from '../types/model';
@ -29,6 +36,13 @@ const teamsDefinition: IAddonDefinition = {
FEATURE_REVIVED,
FEATURE_STALE_ON,
FEATURE_STALE_OFF,
FEATURE_ENVIRONMENT_ENABLED,
FEATURE_ENVIRONMENT_DISABLED,
FEATURE_STRATEGY_REMOVE,
FEATURE_STRATEGY_UPDATE,
FEATURE_STRATEGY_ADD,
FEATURE_METADATA_UPDATED,
FEATURE_PROJECT_CHANGE,
],
};

View File

@ -1,6 +1,10 @@
import { Logger } from '../logger';
import { FEATURE_CREATED, FEATURE_ARCHIVED } from '../types/events';
import {
FEATURE_ARCHIVED,
FEATURE_CREATED,
FEATURE_ENVIRONMENT_DISABLED,
} from '../types/events';
import TeamsAddon from './teams';
@ -83,3 +87,31 @@ test('Should call teams webhook for archived toggle', async () => {
expect(fetchRetryCalls[0].url).toBe(parameters.url);
expect(fetchRetryCalls[0].options.body).toMatchSnapshot();
});
test(`Should call teams webhook for toggled environment`, async () => {
const addon = new TeamsAddon({
getLogger: noLogger,
unleashUrl: 'http://some-url.com',
});
const event: IEvent = {
id: 2,
createdAt: new Date(),
type: FEATURE_ENVIRONMENT_DISABLED,
createdBy: 'some@user.com',
environment: 'development',
project: 'default',
data: {
name: 'some-toggle',
},
};
const parameters = {
url: 'http://hooks.slack.com',
};
await addon.handleEvent(event, parameters);
expect(fetchRetryCalls).toHaveLength(1);
expect(fetchRetryCalls[0].url).toBe(parameters.url);
expect(fetchRetryCalls[0].options.body).toMatch(/disabled/);
expect(fetchRetryCalls[0].options.body).toMatchSnapshot();
});

View File

@ -2,12 +2,19 @@ import YAML from 'js-yaml';
import Addon from './addon';
import {
FEATURE_CREATED,
FEATURE_UPDATED,
FEATURE_ARCHIVED,
FEATURE_CREATED,
FEATURE_ENVIRONMENT_DISABLED,
FEATURE_ENVIRONMENT_ENABLED,
FEATURE_METADATA_UPDATED,
FEATURE_PROJECT_CHANGE,
FEATURE_REVIVED,
FEATURE_STALE_ON,
FEATURE_STALE_OFF,
FEATURE_STALE_ON,
FEATURE_STRATEGY_ADD,
FEATURE_STRATEGY_REMOVE,
FEATURE_STRATEGY_UPDATE,
FEATURE_UPDATED,
} from '../types/events';
import { LogProvider } from '../logger';
@ -31,6 +38,25 @@ export default class TeamsAddon extends Addon {
text = this.generateArchivedText(event);
} else if ([FEATURE_STALE_ON, FEATURE_STALE_OFF].includes(event.type)) {
text = this.generateStaleText(event);
} else if (
[
FEATURE_ENVIRONMENT_DISABLED,
FEATURE_ENVIRONMENT_ENABLED,
].includes(event.type)
) {
text = this.generateEnvironmentToggleText(event);
} else if (
[
FEATURE_STRATEGY_ADD,
FEATURE_STRATEGY_REMOVE,
FEATURE_STRATEGY_UPDATE,
].includes(event.type)
) {
text = this.generateStrategyChangeText(event);
} else if (FEATURE_METADATA_UPDATED === event.type) {
text = this.generateMetadataText(event);
} else if (FEATURE_PROJECT_CHANGE === event.type) {
text = this.generateProjectChangeText(event);
} else {
text = this.generateText(event);
}
@ -85,6 +111,44 @@ export default class TeamsAddon extends Addon {
);
}
generateEnvironmentToggleText(event: IEvent): string {
const { environment, project, data, type } = event;
const toggleStatus =
type === FEATURE_ENVIRONMENT_ENABLED ? 'enabled' : 'disabled';
const feature = `<${this.featureLink(event)}|${data.name}>`;
return `The feature toggle *${feature}* in the ${project} project was ${toggleStatus} in environment *${environment}*`;
}
generateStrategyChangeText(event: IEvent): string {
const { environment, project, data, type } = event;
const feature = `<${this.strategiesLink(event)}|${data.featureName}>`;
let action;
if (FEATURE_STRATEGY_UPDATE === type) {
action = 'updated in';
} else if (FEATURE_STRATEGY_ADD) {
action = 'added to';
} else {
action = 'removed from';
}
const strategyText = `a ${data.name} strategy ${action} the *${environment}* environment`;
return `The feature toggle *${feature}* in project: ${project} had ${strategyText}`;
}
generateMetadataText(event: IEvent): string {
const { createdBy, project, data } = event;
const feature = `<${this.featureLink(event)}|${data.name}>`;
return `${createdBy} updated the metadata for ${feature} in project ${project}`;
}
generateProjectChangeText(event: IEvent): string {
const { createdBy, project, data } = event;
return `${createdBy} moved ${data.name} to ${project}`;
}
strategiesLink(event: IEvent): string {
return `${this.unleashUrl}/projects/${event.project}/features2/${event.data.featureName}/strategies?environment=${event.environment}`;
}
featureLink(event: IEvent): string {
const path = event.type === FEATURE_ARCHIVED ? 'archive' : 'features';
return `${this.unleashUrl}/${path}/strategies/${event.data.name}`;

View File

@ -1,9 +1,16 @@
import {
FEATURE_ARCHIVED,
FEATURE_CREATED,
FEATURE_ENVIRONMENT_DISABLED,
FEATURE_ENVIRONMENT_ENABLED,
FEATURE_METADATA_UPDATED,
FEATURE_PROJECT_CHANGE,
FEATURE_REVIVED,
FEATURE_STALE_OFF,
FEATURE_STALE_ON,
FEATURE_STRATEGY_ADD,
FEATURE_STRATEGY_REMOVE,
FEATURE_STRATEGY_UPDATE,
FEATURE_UPDATED,
} from '../types/events';
import { IAddonDefinition } from '../types/model';
@ -54,9 +61,17 @@ const webhookDefinition: IAddonDefinition = {
FEATURE_CREATED,
FEATURE_UPDATED,
FEATURE_ARCHIVED,
FEATURE_METADATA_UPDATED,
FEATURE_REVIVED,
FEATURE_STALE_ON,
FEATURE_STALE_OFF,
FEATURE_ENVIRONMENT_ENABLED,
FEATURE_ENVIRONMENT_DISABLED,
FEATURE_STRATEGY_REMOVE,
FEATURE_STRATEGY_UPDATE,
FEATURE_STRATEGY_ADD,
FEATURE_METADATA_UPDATED,
FEATURE_PROJECT_CHANGE,
],
};

View File

@ -52,13 +52,12 @@ export default class FeatureToggleClientStore
parameters: r.parameters,
id: r.strategy_id,
};
} else {
return {
name: r.strategy_name,
constraints: r.constraints || [],
parameters: r.parameters,
};
}
return {
name: r.strategy_name,
constraints: r.constraints || [],
parameters: r.parameters,
};
}
private async getAll(

View File

@ -122,7 +122,7 @@ class FeatureToggleServiceV2 {
project: projectId,
createdBy: userName,
environment,
data,
data: { ...data, featureName: newFeatureStrategy.featureName },
});
return data;
} catch (e) {
@ -134,13 +134,6 @@ class FeatureToggleServiceV2 {
throw e;
}
}
/*
TODO after 4.1.0 release:
- add FEATURE_STRATEGY_ADD event
- add FEATURE_STRATEGY_REMOVE event
- add FEATURE_STRATEGY_UPDATE event
*/
/**
* PUT /api/admin/projects/:projectId/features/:featureName/strategies/:strategyId ?
* {
@ -167,6 +160,7 @@ class FeatureToggleServiceV2 {
const data = {
id: strategy.id,
name: strategy.strategyName,
featureName: strategy.featureName,
constraints: strategy.constraints || [],
parameters: strategy.parameters,
};
@ -217,12 +211,15 @@ class FeatureToggleServiceV2 {
}
/**
* DELETE /api/admin/projects/:projectId/features/:featureName/strategies/:strategyId ?
* DELETE /api/admin/projects/:projectId/features/:featureName/environments/:environmentName/strategies/:strategyId
* {
*
* }
* @param id
* @param updates
* @param id - strategy id
* @param featureName - Name of the feature the strategy belongs to
* @param userName - Who's doing the change
* @param project - Which project does this feature toggle belong to
* @param environment - Which environment does this strategy belong to
*/
async deleteStrategy(
id: string,
@ -239,6 +236,7 @@ class FeatureToggleServiceV2 {
createdBy: userName,
data: {
id,
featureName,
},
});
// If there are no strategies left for environment disable it
@ -401,6 +399,13 @@ class FeatureToggleServiceV2 {
project: projectId,
tags,
});
await this.eventStore.store({
type: FEATURE_UPDATED,
createdBy: userName,
data: featureToggle,
project: projectId,
tags,
});
return featureToggle;
}

View File

@ -5,6 +5,8 @@ import { DEFAULT_ENV } from '../../../../../lib/util/constants';
import {
FEATURE_ENVIRONMENT_DISABLED,
FEATURE_ENVIRONMENT_ENABLED,
FEATURE_METADATA_UPDATED,
FEATURE_STRATEGY_REMOVE,
} from '../../../../../lib/types/events';
let app: IUnleashTest;
@ -520,6 +522,13 @@ test('Should patch feature toggle', async () => {
expect(toggle.description).toBe('New desc');
expect(toggle.type).toBe('kill-switch');
expect(toggle.archived).toBeFalsy();
const events = await db.stores.eventStore.getAll({
type: FEATURE_METADATA_UPDATED,
});
const updateForOurToggle = events.find((e) => e.data.name === name);
expect(updateForOurToggle).toBeTruthy();
expect(updateForOurToggle.data.description).toBe('New desc');
expect(updateForOurToggle.data.type).toBe('kill-switch');
});
test('Should archive feature toggle', async () => {
@ -902,6 +911,55 @@ test('Can not enable environment for feature without strategies', async () => {
expect(enabledFeatureEnv.type).toBe('test');
});
});
test('Deleting a strategy should include name of feature strategy was deleted from', async () => {
const environment = 'delete_strategy_env';
const featureName = 'delete_strategy_feature';
// Create environment
await db.stores.environmentStore.create({
name: environment,
type: 'test',
});
// Connect environment to project
await app.request
.post('/api/admin/projects/default/environments')
.send({ environment })
.expect(200);
// Create feature
await app.request
.post('/api/admin/projects/default/features')
.send({
name: featureName,
})
.set('Content-Type', 'application/json')
.expect(201);
let strategyId;
await app.request
.post(
`/api/admin/projects/default/features/${featureName}/environments/${environment}/strategies`,
)
.send({ name: 'default', constraints: [], properties: {} })
.expect(200)
.expect((res) => {
strategyId = res.body.id;
});
expect(strategyId).toBeTruthy();
// Delete strategy
await app.request
.delete(
`/api/admin/projects/default/features/${featureName}/environments/${environment}/strategies/${strategyId}`,
)
.expect(200);
const events = await db.stores.eventStore.getAll({
type: FEATURE_STRATEGY_REMOVE,
});
expect(events).toHaveLength(1);
expect(events[0].data.featureName).toBe(featureName);
expect(events[0].environment).toBe(environment);
expect(events[0].data.id).toBe(strategyId);
});
test('Enabling environment creates a FEATURE_ENVIRONMENT_ENABLED event', async () => {
const environment = 'environment_enabled_env';
const featureName = 'com.test.enable.environment.event.sent';

View File

@ -131,7 +131,6 @@ test('Should be able to get strategy by id', async () => {
constraints: [],
parameters: {},
};
await service.createFeatureToggle(
'default',
{
@ -145,6 +144,7 @@ test('Should be able to get strategy by id', async () => {
'default',
'Demo',
userName,
DEFAULT_ENV,
);
const fetchedConfig = await service.getStrategy(createdConfig.id);
expect(fetchedConfig).toEqual(createdConfig);