From b80857ab0bee5a00dc7d538843b1ce9692f86a41 Mon Sep 17 00:00:00 2001 From: Kai Dubauskas Date: Mon, 17 Nov 2025 16:36:58 -0500 Subject: [PATCH] feat: add rate limit configuration Signed-off-by: Kai Dubauskas --- .changeset/pretty-breads-speak.md | 5 ++ .../lib/SlackNotificationProcessor.test.ts | 61 +++++++++++++++++++ .../src/lib/SlackNotificationProcessor.ts | 16 ++++- 3 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 .changeset/pretty-breads-speak.md diff --git a/.changeset/pretty-breads-speak.md b/.changeset/pretty-breads-speak.md new file mode 100644 index 0000000000..f3c77557e4 --- /dev/null +++ b/.changeset/pretty-breads-speak.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications-backend-module-slack': patch +--- + +The rate limit is now a config variable diff --git a/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.test.ts b/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.test.ts index ad4bf01e9f..02034b6470 100644 --- a/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.test.ts +++ b/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.test.ts @@ -953,4 +953,65 @@ describe('SlackNotificationProcessor', () => { ); }); }); + + describe('when rate limit is not configured', () => { + it('should use default rate limit of 10 messages per minute', async () => { + const slack = new WebClient(); + + const processor = SlackNotificationProcessor.fromConfig(config, { + auth, + logger, + catalog: catalogServiceMock({ + entities: DEFAULT_ENTITIES_RESPONSE.items, + }), + slack, + })[0]; + + await processor.processOptions({ + recipients: { type: 'entity', entityRef: 'group:default/mock' }, + payload: { title: 'notification' }, + }); + + expect(slack.chat.postMessage).toHaveBeenCalled(); + }); + }); + + describe('when rate limit is configured', () => { + it('should use custom rate limit value', async () => { + const slack = new WebClient(); + const rateLimitConfig = mockServices.rootConfig({ + data: { + app: { + baseUrl: 'https://example.org', + }, + notifications: { + processors: { + slack: [ + { + token: 'mock-token', + rateLimit: 5, + }, + ], + }, + }, + }, + }); + + const processor = SlackNotificationProcessor.fromConfig(rateLimitConfig, { + auth, + logger, + catalog: catalogServiceMock({ + entities: DEFAULT_ENTITIES_RESPONSE.items, + }), + slack, + })[0]; + + await processor.processOptions({ + recipients: { type: 'entity', entityRef: 'group:default/mock' }, + payload: { title: 'notification' }, + }); + + expect(slack.chat.postMessage).toHaveBeenCalled(); + }); + }); }); diff --git a/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.ts b/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.ts index f9b00c9a58..739ab25d57 100644 --- a/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.ts +++ b/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.ts @@ -68,10 +68,12 @@ export class SlackNotificationProcessor implements NotificationProcessor { const slack = options.slack ?? new WebClient(token); const broadcastChannels = c.getOptionalStringArray('broadcastChannels'); const username = c.getOptionalString('username'); + const rateLimit = c.getOptionalNumber('rateLimit'); return new SlackNotificationProcessor({ slack, broadcastChannels, username, + rateLimit, ...options, }); }); @@ -84,9 +86,17 @@ export class SlackNotificationProcessor implements NotificationProcessor { catalog: CatalogService; broadcastChannels?: string[]; username?: string; + rateLimit?: number; }) { - const { auth, catalog, logger, slack, broadcastChannels, username } = - options; + const { + auth, + catalog, + logger, + slack, + broadcastChannels, + username, + rateLimit, + } = options; this.logger = logger; this.catalog = catalog; this.auth = auth; @@ -134,7 +144,7 @@ export class SlackNotificationProcessor implements NotificationProcessor { ); const throttle = pThrottle({ - limit: 10, + limit: rateLimit ?? 10, interval: durationToMilliseconds({ minutes: 1 }), }); const throttled = throttle((opts: ChatPostMessageArguments) =>