1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-11 00:08:30 +01:00

feat: slack app addon default channels (#4308)

https://linear.app/unleash/issue/2-1249/add-support-for-default-slack-channels

Adds support for default Slack channels (multiple, comma-separated). 

Some of the events we are handling do not have associated tags, or maybe
the tags are empty. This adds a "default Slack channels" parameter to
the addon configuration in order to post messages to those channels in
those cases.

<img width="643" alt="image"
src="https://github.com/Unleash/unleash/assets/14320932/ee23d6c7-33b7-4968-a0b1-13b546b5b2a2">

---------

Co-authored-by: Gastón Fournier <gaston@getunleash.io>
This commit is contained in:
Nuno Góis 2023-07-21 14:15:43 +01:00 committed by GitHub
parent 34c4dd573a
commit d6c8493156
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 99 additions and 5 deletions

View File

@ -83,3 +83,45 @@ exports[`SlackAppAddon should post to all channels in tags 2`] = `
"text": "some@user.com *enabled* <http://some-url.com/projects/default/features/some-toggle|some-toggle> 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* <http://some-url.com/projects/default/features/some-toggle|some-toggle> 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* <http://some-url.com/projects/default/features/some-toggle|some-toggle> in *development* environment in project *default*",
}
`;

View File

@ -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,

View File

@ -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);

View File

@ -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<void> {
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;