mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +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:
		
							parent
							
								
									14e09fe6aa
								
							
						
					
					
						commit
						b4b222f4c9
					
				@ -1,5 +1,7 @@
 | 
				
			|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
 | 
					// 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 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\\"}"`;
 | 
				
			||||||
 | 
				
			|||||||
@ -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 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 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\\"}]}]}"`;
 | 
				
			||||||
 | 
				
			|||||||
@ -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 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 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\\"}]}]}"`;
 | 
				
			||||||
 | 
				
			|||||||
@ -5,6 +5,13 @@ import {
 | 
				
			|||||||
    FEATURE_REVIVED,
 | 
					    FEATURE_REVIVED,
 | 
				
			||||||
    FEATURE_STALE_ON,
 | 
					    FEATURE_STALE_ON,
 | 
				
			||||||
    FEATURE_STALE_OFF,
 | 
					    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';
 | 
					} from '../types/events';
 | 
				
			||||||
import { IAddonDefinition } from '../types/model';
 | 
					import { IAddonDefinition } from '../types/model';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -40,6 +47,13 @@ const dataDogDefinition: IAddonDefinition = {
 | 
				
			|||||||
        FEATURE_REVIVED,
 | 
					        FEATURE_REVIVED,
 | 
				
			||||||
        FEATURE_STALE_ON,
 | 
					        FEATURE_STALE_ON,
 | 
				
			||||||
        FEATURE_STALE_OFF,
 | 
					        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: [
 | 
					    tagTypes: [
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
 | 
				
			|||||||
@ -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 { Logger } from '../logger';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import DatadogAddon from './datadog';
 | 
					import DatadogAddon from './datadog';
 | 
				
			||||||
@ -82,3 +86,31 @@ test('Should call datadog webhook for archived toggle', async () => {
 | 
				
			|||||||
    expect(fetchRetryCalls[0].url).toBe(parameters.url);
 | 
					    expect(fetchRetryCalls[0].url).toBe(parameters.url);
 | 
				
			||||||
    expect(fetchRetryCalls[0].options.body).toMatchSnapshot();
 | 
					    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();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
				
			|||||||
@ -7,6 +7,13 @@ import {
 | 
				
			|||||||
    FEATURE_REVIVED,
 | 
					    FEATURE_REVIVED,
 | 
				
			||||||
    FEATURE_STALE_ON,
 | 
					    FEATURE_STALE_ON,
 | 
				
			||||||
    FEATURE_STALE_OFF,
 | 
					    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';
 | 
					} from '../types/events';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import definition from './datadog-definition';
 | 
					import definition from './datadog-definition';
 | 
				
			||||||
@ -31,6 +38,25 @@ export default class DatadogAddon extends Addon {
 | 
				
			|||||||
            text = this.generateArchivedText(event);
 | 
					            text = this.generateArchivedText(event);
 | 
				
			||||||
        } else if ([FEATURE_STALE_ON, FEATURE_STALE_OFF].includes(event.type)) {
 | 
					        } else if ([FEATURE_STALE_ON, FEATURE_STALE_OFF].includes(event.type)) {
 | 
				
			||||||
            text = this.generateStaleText(event);
 | 
					            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 {
 | 
					        } else {
 | 
				
			||||||
            text = this.generateText(event);
 | 
					            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 {
 | 
					    featureLink(event: IEvent): string {
 | 
				
			||||||
        const path = event.type === FEATURE_ARCHIVED ? 'archive' : 'features';
 | 
					        const path = event.type === FEATURE_ARCHIVED ? 'archive' : 'features';
 | 
				
			||||||
        return `${this.unleashUrl}/${path}/strategies/${event.data.name}`;
 | 
					        return `${this.unleashUrl}/${path}/strategies/${event.data.name}`;
 | 
				
			||||||
 | 
				
			|||||||
@ -5,6 +5,13 @@ import {
 | 
				
			|||||||
    FEATURE_REVIVED,
 | 
					    FEATURE_REVIVED,
 | 
				
			||||||
    FEATURE_STALE_ON,
 | 
					    FEATURE_STALE_ON,
 | 
				
			||||||
    FEATURE_STALE_OFF,
 | 
					    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';
 | 
					} from '../types/events';
 | 
				
			||||||
import { IAddonDefinition } from '../types/model';
 | 
					import { IAddonDefinition } from '../types/model';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -58,6 +65,13 @@ const slackDefinition: IAddonDefinition = {
 | 
				
			|||||||
        FEATURE_REVIVED,
 | 
					        FEATURE_REVIVED,
 | 
				
			||||||
        FEATURE_STALE_ON,
 | 
					        FEATURE_STALE_ON,
 | 
				
			||||||
        FEATURE_STALE_OFF,
 | 
					        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: [
 | 
					    tagTypes: [
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
 | 
				
			|||||||
@ -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 { Logger } from '../logger';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import SlackAddon from './slack';
 | 
					import SlackAddon from './slack';
 | 
				
			||||||
@ -83,6 +87,34 @@ test('Should call slack webhook for archived toggle', async () => {
 | 
				
			|||||||
    expect(fetchRetryCalls[0].options.body).toMatchSnapshot();
 | 
					    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 () => {
 | 
					test('Should use default channel', async () => {
 | 
				
			||||||
    const addon = new SlackAddon({
 | 
					    const addon = new SlackAddon({
 | 
				
			||||||
        getLogger: noLogger,
 | 
					        getLogger: noLogger,
 | 
				
			||||||
 | 
				
			|||||||
@ -11,6 +11,13 @@ import {
 | 
				
			|||||||
    FEATURE_REVIVED,
 | 
					    FEATURE_REVIVED,
 | 
				
			||||||
    FEATURE_STALE_ON,
 | 
					    FEATURE_STALE_ON,
 | 
				
			||||||
    FEATURE_STALE_OFF,
 | 
					    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';
 | 
					} from '../types/events';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default class SlackAddon extends Addon {
 | 
					export default class SlackAddon extends Addon {
 | 
				
			||||||
@ -42,6 +49,25 @@ export default class SlackAddon extends Addon {
 | 
				
			|||||||
            text = this.generateArchivedText(event);
 | 
					            text = this.generateArchivedText(event);
 | 
				
			||||||
        } else if ([FEATURE_STALE_ON, FEATURE_STALE_OFF].includes(event.type)) {
 | 
					        } else if ([FEATURE_STALE_ON, FEATURE_STALE_OFF].includes(event.type)) {
 | 
				
			||||||
            text = this.generateStaleText(event);
 | 
					            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 {
 | 
					        } else {
 | 
				
			||||||
            text = this.generateText(event);
 | 
					            text = this.generateText(event);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -82,6 +108,44 @@ export default class SlackAddon extends Addon {
 | 
				
			|||||||
        this.logger.info(`Handled event ${event.type}. Status codes=${codes}`);
 | 
					        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 {
 | 
					    featureLink(event: IEvent): string {
 | 
				
			||||||
        const path = event.type === FEATURE_ARCHIVED ? 'archive' : 'features';
 | 
					        const path = event.type === FEATURE_ARCHIVED ? 'archive' : 'features';
 | 
				
			||||||
        return `${this.unleashUrl}/${path}/strategies/${event.data.name}`;
 | 
					        return `${this.unleashUrl}/${path}/strategies/${event.data.name}`;
 | 
				
			||||||
 | 
				
			|||||||
@ -5,6 +5,13 @@ import {
 | 
				
			|||||||
    FEATURE_REVIVED,
 | 
					    FEATURE_REVIVED,
 | 
				
			||||||
    FEATURE_STALE_ON,
 | 
					    FEATURE_STALE_ON,
 | 
				
			||||||
    FEATURE_STALE_OFF,
 | 
					    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';
 | 
					} from '../types/events';
 | 
				
			||||||
import { IAddonDefinition } from '../types/model';
 | 
					import { IAddonDefinition } from '../types/model';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -29,6 +36,13 @@ const teamsDefinition: IAddonDefinition = {
 | 
				
			|||||||
        FEATURE_REVIVED,
 | 
					        FEATURE_REVIVED,
 | 
				
			||||||
        FEATURE_STALE_ON,
 | 
					        FEATURE_STALE_ON,
 | 
				
			||||||
        FEATURE_STALE_OFF,
 | 
					        FEATURE_STALE_OFF,
 | 
				
			||||||
 | 
					        FEATURE_ENVIRONMENT_ENABLED,
 | 
				
			||||||
 | 
					        FEATURE_ENVIRONMENT_DISABLED,
 | 
				
			||||||
 | 
					        FEATURE_STRATEGY_REMOVE,
 | 
				
			||||||
 | 
					        FEATURE_STRATEGY_UPDATE,
 | 
				
			||||||
 | 
					        FEATURE_STRATEGY_ADD,
 | 
				
			||||||
 | 
					        FEATURE_METADATA_UPDATED,
 | 
				
			||||||
 | 
					        FEATURE_PROJECT_CHANGE,
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,10 @@
 | 
				
			|||||||
import { Logger } from '../logger';
 | 
					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';
 | 
					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].url).toBe(parameters.url);
 | 
				
			||||||
    expect(fetchRetryCalls[0].options.body).toMatchSnapshot();
 | 
					    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();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
				
			|||||||
@ -2,12 +2,19 @@ import YAML from 'js-yaml';
 | 
				
			|||||||
import Addon from './addon';
 | 
					import Addon from './addon';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
    FEATURE_CREATED,
 | 
					 | 
				
			||||||
    FEATURE_UPDATED,
 | 
					 | 
				
			||||||
    FEATURE_ARCHIVED,
 | 
					    FEATURE_ARCHIVED,
 | 
				
			||||||
 | 
					    FEATURE_CREATED,
 | 
				
			||||||
 | 
					    FEATURE_ENVIRONMENT_DISABLED,
 | 
				
			||||||
 | 
					    FEATURE_ENVIRONMENT_ENABLED,
 | 
				
			||||||
 | 
					    FEATURE_METADATA_UPDATED,
 | 
				
			||||||
 | 
					    FEATURE_PROJECT_CHANGE,
 | 
				
			||||||
    FEATURE_REVIVED,
 | 
					    FEATURE_REVIVED,
 | 
				
			||||||
    FEATURE_STALE_ON,
 | 
					 | 
				
			||||||
    FEATURE_STALE_OFF,
 | 
					    FEATURE_STALE_OFF,
 | 
				
			||||||
 | 
					    FEATURE_STALE_ON,
 | 
				
			||||||
 | 
					    FEATURE_STRATEGY_ADD,
 | 
				
			||||||
 | 
					    FEATURE_STRATEGY_REMOVE,
 | 
				
			||||||
 | 
					    FEATURE_STRATEGY_UPDATE,
 | 
				
			||||||
 | 
					    FEATURE_UPDATED,
 | 
				
			||||||
} from '../types/events';
 | 
					} from '../types/events';
 | 
				
			||||||
import { LogProvider } from '../logger';
 | 
					import { LogProvider } from '../logger';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -31,6 +38,25 @@ export default class TeamsAddon extends Addon {
 | 
				
			|||||||
            text = this.generateArchivedText(event);
 | 
					            text = this.generateArchivedText(event);
 | 
				
			||||||
        } else if ([FEATURE_STALE_ON, FEATURE_STALE_OFF].includes(event.type)) {
 | 
					        } else if ([FEATURE_STALE_ON, FEATURE_STALE_OFF].includes(event.type)) {
 | 
				
			||||||
            text = this.generateStaleText(event);
 | 
					            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 {
 | 
					        } else {
 | 
				
			||||||
            text = this.generateText(event);
 | 
					            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 {
 | 
					    featureLink(event: IEvent): string {
 | 
				
			||||||
        const path = event.type === FEATURE_ARCHIVED ? 'archive' : 'features';
 | 
					        const path = event.type === FEATURE_ARCHIVED ? 'archive' : 'features';
 | 
				
			||||||
        return `${this.unleashUrl}/${path}/strategies/${event.data.name}`;
 | 
					        return `${this.unleashUrl}/${path}/strategies/${event.data.name}`;
 | 
				
			||||||
 | 
				
			|||||||
@ -1,9 +1,16 @@
 | 
				
			|||||||
import {
 | 
					import {
 | 
				
			||||||
    FEATURE_ARCHIVED,
 | 
					    FEATURE_ARCHIVED,
 | 
				
			||||||
    FEATURE_CREATED,
 | 
					    FEATURE_CREATED,
 | 
				
			||||||
 | 
					    FEATURE_ENVIRONMENT_DISABLED,
 | 
				
			||||||
 | 
					    FEATURE_ENVIRONMENT_ENABLED,
 | 
				
			||||||
 | 
					    FEATURE_METADATA_UPDATED,
 | 
				
			||||||
 | 
					    FEATURE_PROJECT_CHANGE,
 | 
				
			||||||
    FEATURE_REVIVED,
 | 
					    FEATURE_REVIVED,
 | 
				
			||||||
    FEATURE_STALE_OFF,
 | 
					    FEATURE_STALE_OFF,
 | 
				
			||||||
    FEATURE_STALE_ON,
 | 
					    FEATURE_STALE_ON,
 | 
				
			||||||
 | 
					    FEATURE_STRATEGY_ADD,
 | 
				
			||||||
 | 
					    FEATURE_STRATEGY_REMOVE,
 | 
				
			||||||
 | 
					    FEATURE_STRATEGY_UPDATE,
 | 
				
			||||||
    FEATURE_UPDATED,
 | 
					    FEATURE_UPDATED,
 | 
				
			||||||
} from '../types/events';
 | 
					} from '../types/events';
 | 
				
			||||||
import { IAddonDefinition } from '../types/model';
 | 
					import { IAddonDefinition } from '../types/model';
 | 
				
			||||||
@ -54,9 +61,17 @@ const webhookDefinition: IAddonDefinition = {
 | 
				
			|||||||
        FEATURE_CREATED,
 | 
					        FEATURE_CREATED,
 | 
				
			||||||
        FEATURE_UPDATED,
 | 
					        FEATURE_UPDATED,
 | 
				
			||||||
        FEATURE_ARCHIVED,
 | 
					        FEATURE_ARCHIVED,
 | 
				
			||||||
 | 
					        FEATURE_METADATA_UPDATED,
 | 
				
			||||||
        FEATURE_REVIVED,
 | 
					        FEATURE_REVIVED,
 | 
				
			||||||
        FEATURE_STALE_ON,
 | 
					        FEATURE_STALE_ON,
 | 
				
			||||||
        FEATURE_STALE_OFF,
 | 
					        FEATURE_STALE_OFF,
 | 
				
			||||||
 | 
					        FEATURE_ENVIRONMENT_ENABLED,
 | 
				
			||||||
 | 
					        FEATURE_ENVIRONMENT_DISABLED,
 | 
				
			||||||
 | 
					        FEATURE_STRATEGY_REMOVE,
 | 
				
			||||||
 | 
					        FEATURE_STRATEGY_UPDATE,
 | 
				
			||||||
 | 
					        FEATURE_STRATEGY_ADD,
 | 
				
			||||||
 | 
					        FEATURE_METADATA_UPDATED,
 | 
				
			||||||
 | 
					        FEATURE_PROJECT_CHANGE,
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -52,14 +52,13 @@ export default class FeatureToggleClientStore
 | 
				
			|||||||
                parameters: r.parameters,
 | 
					                parameters: r.parameters,
 | 
				
			||||||
                id: r.strategy_id,
 | 
					                id: r.strategy_id,
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
        } else {
 | 
					        }
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            name: r.strategy_name,
 | 
					            name: r.strategy_name,
 | 
				
			||||||
            constraints: r.constraints || [],
 | 
					            constraints: r.constraints || [],
 | 
				
			||||||
            parameters: r.parameters,
 | 
					            parameters: r.parameters,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private async getAll(
 | 
					    private async getAll(
 | 
				
			||||||
        featureQuery?: IFeatureToggleQuery,
 | 
					        featureQuery?: IFeatureToggleQuery,
 | 
				
			||||||
 | 
				
			|||||||
@ -122,7 +122,7 @@ class FeatureToggleServiceV2 {
 | 
				
			|||||||
                project: projectId,
 | 
					                project: projectId,
 | 
				
			||||||
                createdBy: userName,
 | 
					                createdBy: userName,
 | 
				
			||||||
                environment,
 | 
					                environment,
 | 
				
			||||||
                data,
 | 
					                data: { ...data, featureName: newFeatureStrategy.featureName },
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
            return data;
 | 
					            return data;
 | 
				
			||||||
        } catch (e) {
 | 
					        } catch (e) {
 | 
				
			||||||
@ -134,13 +134,6 @@ class FeatureToggleServiceV2 {
 | 
				
			|||||||
            throw e;
 | 
					            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 ?
 | 
					     * PUT /api/admin/projects/:projectId/features/:featureName/strategies/:strategyId ?
 | 
				
			||||||
     * {
 | 
					     * {
 | 
				
			||||||
@ -167,6 +160,7 @@ class FeatureToggleServiceV2 {
 | 
				
			|||||||
            const data = {
 | 
					            const data = {
 | 
				
			||||||
                id: strategy.id,
 | 
					                id: strategy.id,
 | 
				
			||||||
                name: strategy.strategyName,
 | 
					                name: strategy.strategyName,
 | 
				
			||||||
 | 
					                featureName: strategy.featureName,
 | 
				
			||||||
                constraints: strategy.constraints || [],
 | 
					                constraints: strategy.constraints || [],
 | 
				
			||||||
                parameters: strategy.parameters,
 | 
					                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 id - strategy id
 | 
				
			||||||
     * @param updates
 | 
					     * @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(
 | 
					    async deleteStrategy(
 | 
				
			||||||
        id: string,
 | 
					        id: string,
 | 
				
			||||||
@ -239,6 +236,7 @@ class FeatureToggleServiceV2 {
 | 
				
			|||||||
            createdBy: userName,
 | 
					            createdBy: userName,
 | 
				
			||||||
            data: {
 | 
					            data: {
 | 
				
			||||||
                id,
 | 
					                id,
 | 
				
			||||||
 | 
					                featureName,
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        // If there are no strategies left for environment disable it
 | 
					        // If there are no strategies left for environment disable it
 | 
				
			||||||
@ -401,6 +399,13 @@ class FeatureToggleServiceV2 {
 | 
				
			|||||||
            project: projectId,
 | 
					            project: projectId,
 | 
				
			||||||
            tags,
 | 
					            tags,
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					        await this.eventStore.store({
 | 
				
			||||||
 | 
					            type: FEATURE_UPDATED,
 | 
				
			||||||
 | 
					            createdBy: userName,
 | 
				
			||||||
 | 
					            data: featureToggle,
 | 
				
			||||||
 | 
					            project: projectId,
 | 
				
			||||||
 | 
					            tags,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
        return featureToggle;
 | 
					        return featureToggle;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -5,6 +5,8 @@ import { DEFAULT_ENV } from '../../../../../lib/util/constants';
 | 
				
			|||||||
import {
 | 
					import {
 | 
				
			||||||
    FEATURE_ENVIRONMENT_DISABLED,
 | 
					    FEATURE_ENVIRONMENT_DISABLED,
 | 
				
			||||||
    FEATURE_ENVIRONMENT_ENABLED,
 | 
					    FEATURE_ENVIRONMENT_ENABLED,
 | 
				
			||||||
 | 
					    FEATURE_METADATA_UPDATED,
 | 
				
			||||||
 | 
					    FEATURE_STRATEGY_REMOVE,
 | 
				
			||||||
} from '../../../../../lib/types/events';
 | 
					} from '../../../../../lib/types/events';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
let app: IUnleashTest;
 | 
					let app: IUnleashTest;
 | 
				
			||||||
@ -520,6 +522,13 @@ test('Should patch feature toggle', async () => {
 | 
				
			|||||||
    expect(toggle.description).toBe('New desc');
 | 
					    expect(toggle.description).toBe('New desc');
 | 
				
			||||||
    expect(toggle.type).toBe('kill-switch');
 | 
					    expect(toggle.type).toBe('kill-switch');
 | 
				
			||||||
    expect(toggle.archived).toBeFalsy();
 | 
					    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 () => {
 | 
					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');
 | 
					            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 () => {
 | 
					test('Enabling environment creates a FEATURE_ENVIRONMENT_ENABLED event', async () => {
 | 
				
			||||||
    const environment = 'environment_enabled_env';
 | 
					    const environment = 'environment_enabled_env';
 | 
				
			||||||
    const featureName = 'com.test.enable.environment.event.sent';
 | 
					    const featureName = 'com.test.enable.environment.event.sent';
 | 
				
			||||||
 | 
				
			|||||||
@ -131,7 +131,6 @@ test('Should be able to get strategy by id', async () => {
 | 
				
			|||||||
        constraints: [],
 | 
					        constraints: [],
 | 
				
			||||||
        parameters: {},
 | 
					        parameters: {},
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					 | 
				
			||||||
    await service.createFeatureToggle(
 | 
					    await service.createFeatureToggle(
 | 
				
			||||||
        'default',
 | 
					        'default',
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
@ -145,6 +144,7 @@ test('Should be able to get strategy by id', async () => {
 | 
				
			|||||||
        'default',
 | 
					        'default',
 | 
				
			||||||
        'Demo',
 | 
					        'Demo',
 | 
				
			||||||
        userName,
 | 
					        userName,
 | 
				
			||||||
 | 
					        DEFAULT_ENV,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    const fetchedConfig = await service.getStrategy(createdConfig.id);
 | 
					    const fetchedConfig = await service.getStrategy(createdConfig.id);
 | 
				
			||||||
    expect(fetchedConfig).toEqual(createdConfig);
 | 
					    expect(fetchedConfig).toEqual(createdConfig);
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user