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:
parent
34c4dd573a
commit
d6c8493156
@ -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*",
|
"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*",
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
@ -48,6 +48,15 @@ const slackAppDefinition: IAddonDefinition = {
|
|||||||
required: true,
|
required: true,
|
||||||
sensitive: 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: [
|
events: [
|
||||||
FEATURE_CREATED,
|
FEATURE_CREATED,
|
||||||
|
@ -79,6 +79,7 @@ describe('SlackAppAddon', () => {
|
|||||||
jest.useFakeTimers();
|
jest.useFakeTimers();
|
||||||
slackApiCalls.length = 0;
|
slackApiCalls.length = 0;
|
||||||
conversationsList.mockClear();
|
conversationsList.mockClear();
|
||||||
|
postMessage.mockClear();
|
||||||
addon = new SlackAppAddon({
|
addon = new SlackAppAddon({
|
||||||
getLogger,
|
getLogger,
|
||||||
unleashUrl: 'http://some-url.com',
|
unleashUrl: 'http://some-url.com',
|
||||||
@ -153,6 +154,37 @@ describe('SlackAppAddon', () => {
|
|||||||
expect(conversationsList).toHaveBeenCalledTimes(2);
|
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 () => {
|
it('should log error when an API call fails', async () => {
|
||||||
postMessage = jest.fn().mockRejectedValue(mockError);
|
postMessage = jest.fn().mockRejectedValue(mockError);
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@ const CACHE_SECONDS = 30;
|
|||||||
|
|
||||||
interface ISlackAppAddonParameters {
|
interface ISlackAppAddonParameters {
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
|
defaultChannels: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class SlackAppAddon extends Addon {
|
export default class SlackAppAddon extends Addon {
|
||||||
@ -52,17 +53,20 @@ export default class SlackAppAddon extends Addon {
|
|||||||
parameters: ISlackAppAddonParameters,
|
parameters: ISlackAppAddonParameters,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const { accessToken } = parameters;
|
const { accessToken, defaultChannels } = parameters;
|
||||||
if (!accessToken) {
|
if (!accessToken) {
|
||||||
this.logger.warn('No access token provided.');
|
this.logger.warn('No access token provided.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const taggedChannels = this.findTaggedChannels(event);
|
const taggedChannels = this.findTaggedChannels(event);
|
||||||
if (!taggedChannels.length) {
|
const eventChannels = taggedChannels.length
|
||||||
|
? taggedChannels
|
||||||
|
: this.getDefaultChannels(defaultChannels);
|
||||||
|
|
||||||
|
if (!eventChannels.length) {
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
`No Slack channels tagged for event ${event.type}`,
|
`No Slack channels found for event ${event.type}.`,
|
||||||
event,
|
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -99,7 +103,7 @@ export default class SlackAppAddon extends Addon {
|
|||||||
const url = this.msgFormatter.featureLink(event);
|
const url = this.msgFormatter.featureLink(event);
|
||||||
|
|
||||||
const slackChannelsToPostTo = currentSlackChannels.filter(
|
const slackChannelsToPostTo = currentSlackChannels.filter(
|
||||||
({ id, name }) => id && name && taggedChannels.includes(name),
|
({ id, name }) => id && name && eventChannels.includes(name),
|
||||||
);
|
);
|
||||||
|
|
||||||
const requests = slackChannelsToPostTo.map(({ id }) =>
|
const requests = slackChannelsToPostTo.map(({ id }) =>
|
||||||
@ -151,6 +155,13 @@ export default class SlackAppAddon extends Addon {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getDefaultChannels(defaultChannels?: string): string[] {
|
||||||
|
if (defaultChannels) {
|
||||||
|
return defaultChannels.split(',').map((c) => c.trim());
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
startCacheInvalidation(): void {
|
startCacheInvalidation(): void {
|
||||||
this.slackChannelsCacheTimeout = setInterval(() => {
|
this.slackChannelsCacheTimeout = setInterval(() => {
|
||||||
this.slackChannels = undefined;
|
this.slackChannels = undefined;
|
||||||
|
Loading…
Reference in New Issue
Block a user