diff --git a/src/lib/addons/__snapshots__/slack-app.test.ts.snap b/src/lib/addons/__snapshots__/slack-app.test.ts.snap index 339251ad53..a19a23e0b1 100644 --- a/src/lib/addons/__snapshots__/slack-app.test.ts.snap +++ b/src/lib/addons/__snapshots__/slack-app.test.ts.snap @@ -83,3 +83,45 @@ exports[`SlackAppAddon should post to all channels in tags 2`] = ` "text": "some@user.com *enabled* in *development* environment in project *default*", } `; + +exports[`SlackAppAddon should use defaultChannels if no tagged channels are found 1`] = ` +{ + "attachments": [ + { + "actions": [ + { + "name": "featureToggle", + "style": "primary", + "text": "Open in Unleash", + "type": "button", + "url": "http://some-url.com/projects/default/features/some-toggle", + "value": "featureToggle", + }, + ], + }, + ], + "channel": 1, + "text": "some@user.com *enabled* in *development* environment in project *default*", +} +`; + +exports[`SlackAppAddon should use defaultChannels if no tagged channels are found 2`] = ` +{ + "attachments": [ + { + "actions": [ + { + "name": "featureToggle", + "style": "primary", + "text": "Open in Unleash", + "type": "button", + "url": "http://some-url.com/projects/default/features/some-toggle", + "value": "featureToggle", + }, + ], + }, + ], + "channel": 2, + "text": "some@user.com *enabled* in *development* environment in project *default*", +} +`; diff --git a/src/lib/addons/slack-app-definition.ts b/src/lib/addons/slack-app-definition.ts index d25b8cd6d6..5df01d5c7b 100644 --- a/src/lib/addons/slack-app-definition.ts +++ b/src/lib/addons/slack-app-definition.ts @@ -48,6 +48,15 @@ const slackAppDefinition: IAddonDefinition = { required: true, sensitive: true, }, + { + name: 'defaultChannels', + displayName: 'Default channels', + description: + 'A comma-separated list of channels to post to if no tagged channels are found (e.g. a toggle without tags, or an event with no tags associated).', + type: 'text', + required: false, + sensitive: false, + }, ], events: [ FEATURE_CREATED, diff --git a/src/lib/addons/slack-app.test.ts b/src/lib/addons/slack-app.test.ts index 7bd25c35f5..02325bbf41 100644 --- a/src/lib/addons/slack-app.test.ts +++ b/src/lib/addons/slack-app.test.ts @@ -79,6 +79,7 @@ describe('SlackAppAddon', () => { jest.useFakeTimers(); slackApiCalls.length = 0; conversationsList.mockClear(); + postMessage.mockClear(); addon = new SlackAppAddon({ getLogger, unleashUrl: 'http://some-url.com', @@ -153,6 +154,37 @@ describe('SlackAppAddon', () => { expect(conversationsList).toHaveBeenCalledTimes(2); }); + it('should not post a message if there are no tagged channels and no defaultChannels', async () => { + const eventWithoutTags: IEvent = { + ...event, + tags: [], + }; + + await addon.handleEvent(eventWithoutTags, { + accessToken, + }); + + expect(slackApiCalls.length).toBe(0); + }); + + it('should use defaultChannels if no tagged channels are found', async () => { + const eventWithoutTags: IEvent = { + ...event, + tags: [], + }; + + await addon.handleEvent(eventWithoutTags, { + accessToken, + defaultChannels: 'general, another-channel-1', + }); + + expect(slackApiCalls.length).toBe(2); + expect(slackApiCalls[0].channel).toBe(1); + expect(slackApiCalls[0]).toMatchSnapshot(); + expect(slackApiCalls[1].channel).toBe(2); + expect(slackApiCalls[1]).toMatchSnapshot(); + }); + it('should log error when an API call fails', async () => { postMessage = jest.fn().mockRejectedValue(mockError); diff --git a/src/lib/addons/slack-app.ts b/src/lib/addons/slack-app.ts index a478167815..9c38ec2294 100644 --- a/src/lib/addons/slack-app.ts +++ b/src/lib/addons/slack-app.ts @@ -25,6 +25,7 @@ const CACHE_SECONDS = 30; interface ISlackAppAddonParameters { accessToken: string; + defaultChannels: string; } export default class SlackAppAddon extends Addon { @@ -52,17 +53,20 @@ export default class SlackAppAddon extends Addon { parameters: ISlackAppAddonParameters, ): Promise { try { - const { accessToken } = parameters; + const { accessToken, defaultChannels } = parameters; if (!accessToken) { this.logger.warn('No access token provided.'); return; } const taggedChannels = this.findTaggedChannels(event); - if (!taggedChannels.length) { + const eventChannels = taggedChannels.length + ? taggedChannels + : this.getDefaultChannels(defaultChannels); + + if (!eventChannels.length) { this.logger.debug( - `No Slack channels tagged for event ${event.type}`, - event, + `No Slack channels found for event ${event.type}.`, ); return; } @@ -99,7 +103,7 @@ export default class SlackAppAddon extends Addon { const url = this.msgFormatter.featureLink(event); const slackChannelsToPostTo = currentSlackChannels.filter( - ({ id, name }) => id && name && taggedChannels.includes(name), + ({ id, name }) => id && name && eventChannels.includes(name), ); const requests = slackChannelsToPostTo.map(({ id }) => @@ -151,6 +155,13 @@ export default class SlackAppAddon extends Addon { return []; } + getDefaultChannels(defaultChannels?: string): string[] { + if (defaultChannels) { + return defaultChannels.split(',').map((c) => c.trim()); + } + return []; + } + startCacheInvalidation(): void { this.slackChannelsCacheTimeout = setInterval(() => { this.slackChannels = undefined;