@backstage/plugin-notifications-backend-module-slack 0.2.2-next.1 → 0.3.1-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,34 @@
1
1
  # @backstage/plugin-notifications-backend-module-slack
2
2
 
3
+ ## 0.3.1-next.0
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies
8
+ - @backstage/plugin-catalog-node@1.21.0-next.0
9
+ - @backstage/backend-plugin-api@1.7.0-next.0
10
+ - @backstage/plugin-notifications-node@0.2.23-next.0
11
+ - @backstage/catalog-model@1.7.6
12
+ - @backstage/config@1.3.6
13
+ - @backstage/errors@1.2.7
14
+ - @backstage/types@1.2.2
15
+ - @backstage/plugin-notifications-common@0.2.0
16
+
17
+ ## 0.3.0
18
+
19
+ ### Minor Changes
20
+
21
+ - f95a516: Enables optional routes to Slack channels for broadcast notifications based on origin and/or topics.
22
+
23
+ ### Patch Changes
24
+
25
+ - b80857a: Slack notification handler throttling can now be configured with the `concurrencyLimit` and `throttleInterval` options.
26
+ - f8230e4: Updated dependency `@faker-js/faker` to `^10.0.0`.
27
+ - Updated dependencies
28
+ - @backstage/backend-plugin-api@1.6.0
29
+ - @backstage/plugin-catalog-node@1.20.1
30
+ - @backstage/plugin-notifications-node@0.2.22
31
+
3
32
  ## 0.2.2-next.1
4
33
 
5
34
  ### Patch Changes
package/config.d.ts CHANGED
@@ -13,6 +13,8 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
+ import { HumanDuration } from '@backstage/types';
17
+
16
18
  export interface Config {
17
19
  notifications?: {
18
20
  processors?: {
@@ -26,8 +28,40 @@ export interface Config {
26
28
  * Broadcast notification receivers when receiver is set to config
27
29
  * These can be Slack User IDs, Slack User Email addresses, Slack Channel
28
30
  * Names, or Slack Channel IDs. Any valid identifier that chat.postMessage can accept.
31
+ * @deprecated Use broadcastRoutes instead for more granular control
29
32
  */
30
33
  broadcastChannels?: string[];
34
+ /**
35
+ * Optional username to display as the sender of the notification
36
+ */
37
+ username?: string;
38
+ /**
39
+ * Routes for broadcast notifications based on origin and/or topic.
40
+ * Routes are evaluated in order, first match wins.
41
+ * Origin+topic matches take precedence over origin-only matches.
42
+ */
43
+ broadcastRoutes?: Array<{
44
+ /**
45
+ * The origin to match (e.g., 'plugin:catalog', 'external:my-service')
46
+ */
47
+ origin?: string;
48
+ /**
49
+ * The topic to match (e.g., 'entity-updated', 'alerts')
50
+ */
51
+ topic?: string;
52
+ /**
53
+ * The Slack channel(s) to send to. Can be channel IDs, channel names, or user IDs.
54
+ */
55
+ channel: string | string[];
56
+ }>;
57
+ /**
58
+ * Concurrency limit for Slack notifications per backend instance of the notifications plugin, defaults to 10.
59
+ */
60
+ concurrencyLimit?: number;
61
+ /**
62
+ * Throttle duration between Slack notifications per backend instance of the notifications plugin, defaults to 1 minute.
63
+ */
64
+ throttleInterval?: HumanDuration | string;
31
65
  }>;
32
66
  };
33
67
  };
package/dist/index.cjs.js CHANGED
@@ -3,6 +3,7 @@
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  require('@backstage/catalog-model');
6
+ require('@backstage/config');
6
7
  require('@backstage/errors');
7
8
  require('@backstage/types');
8
9
  require('@opentelemetry/api');
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;"}
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var catalogModel = require('@backstage/catalog-model');
4
+ var config = require('@backstage/config');
4
5
  var errors = require('@backstage/errors');
5
6
  var types = require('@backstage/types');
6
7
  var api = require('@opentelemetry/api');
@@ -24,31 +25,58 @@ class SlackNotificationProcessor {
24
25
  messagesSent;
25
26
  messagesFailed;
26
27
  broadcastChannels;
28
+ broadcastRoutes;
27
29
  entityLoader;
28
30
  username;
29
- static fromConfig(config, options) {
30
- const slackConfig = config.getOptionalConfigArray("notifications.processors.slack") ?? [];
31
+ concurrencyLimit;
32
+ throttleInterval;
33
+ static fromConfig(config$1, options) {
34
+ const slackConfig = config$1.getOptionalConfigArray("notifications.processors.slack") ?? [];
31
35
  return slackConfig.map((c) => {
32
36
  const token = c.getString("token");
33
37
  const slack = options.slack ?? new webApi.WebClient(token);
34
38
  const broadcastChannels = c.getOptionalStringArray("broadcastChannels");
35
39
  const username = c.getOptionalString("username");
40
+ const broadcastRoutesConfig = c.getOptionalConfigArray("broadcastRoutes");
41
+ const broadcastRoutes = broadcastRoutesConfig?.map(
42
+ (route) => this.parseBroadcastRoute(route)
43
+ );
44
+ const concurrencyLimit = c.getOptionalNumber("concurrencyLimit") ?? 10;
45
+ const throttleInterval = c.has("throttleInterval") ? types.durationToMilliseconds(
46
+ config.readDurationFromConfig(c, { key: "throttleInterval" })
47
+ ) : types.durationToMilliseconds({ minutes: 1 });
36
48
  return new SlackNotificationProcessor({
37
49
  slack,
38
50
  broadcastChannels,
51
+ broadcastRoutes,
39
52
  username,
53
+ concurrencyLimit,
54
+ throttleInterval,
40
55
  ...options
41
56
  });
42
57
  });
43
58
  }
44
59
  constructor(options) {
45
- const { auth, catalog, logger, slack, broadcastChannels, username } = options;
60
+ const {
61
+ auth,
62
+ catalog,
63
+ logger,
64
+ slack,
65
+ broadcastChannels,
66
+ broadcastRoutes,
67
+ username,
68
+ concurrencyLimit,
69
+ throttleInterval
70
+ } = options;
46
71
  this.logger = logger;
47
72
  this.catalog = catalog;
48
73
  this.auth = auth;
49
74
  this.slack = slack;
50
75
  this.broadcastChannels = broadcastChannels;
76
+ this.broadcastRoutes = broadcastRoutes;
51
77
  this.username = username;
78
+ this.concurrencyLimit = concurrencyLimit ?? 10;
79
+ this.throttleInterval = throttleInterval ?? types.durationToMilliseconds({ minutes: 1 });
52
80
  this.entityLoader = new DataLoader__default.default(
53
81
  async (entityRefs) => {
54
82
  return await this.catalog.getEntitiesByRefs(
@@ -84,8 +112,8 @@ class SlackNotificationProcessor {
84
112
  }
85
113
  );
86
114
  const throttle = pThrottle__default.default({
87
- limit: 10,
88
- interval: types.durationToMilliseconds({ minutes: 1 })
115
+ limit: this.concurrencyLimit,
116
+ interval: this.throttleInterval
89
117
  });
90
118
  const throttled = throttle(
91
119
  (opts) => this.sendNotification(opts)
@@ -160,7 +188,8 @@ class SlackNotificationProcessor {
160
188
  async postProcess(notification, options) {
161
189
  const destinations = [];
162
190
  if (notification.user === null) {
163
- destinations.push(...this.broadcastChannels ?? []);
191
+ const routedChannels = this.getBroadcastDestinations(notification);
192
+ destinations.push(...routedChannels);
164
193
  } else if (options.recipients.type === "entity") {
165
194
  const entityRefs = [options.recipients.entityRef].flat();
166
195
  if (entityRefs.some((e) => catalogModel.parseEntityRef(e).kind === "group")) {
@@ -268,6 +297,62 @@ class SlackNotificationProcessor {
268
297
  throw new Error(`Failed to send notification: ${response.error}`);
269
298
  }
270
299
  }
300
+ static parseBroadcastRoute(route) {
301
+ const channelValue = route.getOptional("channel");
302
+ let channels;
303
+ if (typeof channelValue === "string") {
304
+ channels = [channelValue];
305
+ } else if (Array.isArray(channelValue)) {
306
+ channels = channelValue;
307
+ } else {
308
+ throw new Error(
309
+ "broadcastRoutes entry must have a channel property (string or string[])"
310
+ );
311
+ }
312
+ return {
313
+ origin: route.getOptionalString("origin"),
314
+ topic: route.getOptionalString("topic"),
315
+ channels
316
+ };
317
+ }
318
+ /**
319
+ * Gets the destination channels for a broadcast notification based on
320
+ * configured routes. Routes are matched by origin and/or topic.
321
+ *
322
+ * Matching precedence:
323
+ * 1. Routes with both origin AND topic matching (most specific)
324
+ * 2. Routes with only origin matching
325
+ * 3. Routes with only topic matching
326
+ * 4. Default broadcastChannels (least specific fallback)
327
+ *
328
+ * The first matching route wins within each precedence level.
329
+ */
330
+ getBroadcastDestinations(notification) {
331
+ const { origin } = notification;
332
+ const { topic } = notification.payload;
333
+ if (!this.broadcastRoutes || this.broadcastRoutes.length === 0) {
334
+ return this.broadcastChannels ?? [];
335
+ }
336
+ const originAndTopicMatch = this.broadcastRoutes.find(
337
+ (route) => route.origin !== void 0 && route.topic !== void 0 && route.origin === origin && route.topic === topic
338
+ );
339
+ if (originAndTopicMatch) {
340
+ return originAndTopicMatch.channels;
341
+ }
342
+ const originOnlyMatch = this.broadcastRoutes.find(
343
+ (route) => route.origin !== void 0 && route.topic === void 0 && route.origin === origin
344
+ );
345
+ if (originOnlyMatch) {
346
+ return originOnlyMatch.channels;
347
+ }
348
+ const topicOnlyMatch = this.broadcastRoutes.find(
349
+ (route) => route.topic !== void 0 && route.origin === void 0 && route.topic === topic
350
+ );
351
+ if (topicOnlyMatch) {
352
+ return topicOnlyMatch.channels;
353
+ }
354
+ return this.broadcastChannels ?? [];
355
+ }
271
356
  }
272
357
 
273
358
  exports.SlackNotificationProcessor = SlackNotificationProcessor;
@@ -1 +1 @@
1
- {"version":3,"file":"SlackNotificationProcessor.cjs.js","sources":["../../src/lib/SlackNotificationProcessor.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { AuthService, LoggerService } from '@backstage/backend-plugin-api';\nimport {\n Entity,\n isUserEntity,\n parseEntityRef,\n UserEntity,\n} from '@backstage/catalog-model';\nimport { Config } from '@backstage/config';\nimport { NotFoundError } from '@backstage/errors';\nimport { Notification } from '@backstage/plugin-notifications-common';\nimport {\n NotificationProcessor,\n NotificationSendOptions,\n} from '@backstage/plugin-notifications-node';\nimport { durationToMilliseconds } from '@backstage/types';\nimport { Counter, metrics } from '@opentelemetry/api';\nimport { ChatPostMessageArguments, WebClient } from '@slack/web-api';\nimport DataLoader from 'dataloader';\nimport pThrottle from 'p-throttle';\nimport { ANNOTATION_SLACK_BOT_NOTIFY } from './constants';\nimport { ExpiryMap, toChatPostMessageArgs } from './util';\nimport { CatalogService } from '@backstage/plugin-catalog-node';\n\nexport class SlackNotificationProcessor implements NotificationProcessor {\n private readonly logger: LoggerService;\n private readonly catalog: CatalogService;\n private readonly auth: AuthService;\n private readonly slack: WebClient;\n private readonly sendNotifications: (\n opts: ChatPostMessageArguments[],\n ) => Promise<void>;\n private readonly messagesSent: Counter;\n private readonly messagesFailed: Counter;\n private readonly broadcastChannels?: string[];\n private readonly entityLoader: DataLoader<string, Entity | undefined>;\n private readonly username?: string;\n\n static fromConfig(\n config: Config,\n options: {\n auth: AuthService;\n logger: LoggerService;\n catalog: CatalogService;\n slack?: WebClient;\n broadcastChannels?: string[];\n },\n ): SlackNotificationProcessor[] {\n const slackConfig =\n config.getOptionalConfigArray('notifications.processors.slack') ?? [];\n return slackConfig.map(c => {\n const token = c.getString('token');\n const slack = options.slack ?? new WebClient(token);\n const broadcastChannels = c.getOptionalStringArray('broadcastChannels');\n const username = c.getOptionalString('username');\n return new SlackNotificationProcessor({\n slack,\n broadcastChannels,\n username,\n ...options,\n });\n });\n }\n\n private constructor(options: {\n slack: WebClient;\n auth: AuthService;\n logger: LoggerService;\n catalog: CatalogService;\n broadcastChannels?: string[];\n username?: string;\n }) {\n const { auth, catalog, logger, slack, broadcastChannels, username } =\n options;\n this.logger = logger;\n this.catalog = catalog;\n this.auth = auth;\n this.slack = slack;\n this.broadcastChannels = broadcastChannels;\n this.username = username;\n\n this.entityLoader = new DataLoader<string, Entity | undefined>(\n async entityRefs => {\n return await this.catalog\n .getEntitiesByRefs(\n {\n entityRefs: entityRefs.slice(),\n fields: [\n `kind`,\n `spec.profile.email`,\n `metadata.annotations.${ANNOTATION_SLACK_BOT_NOTIFY}`,\n ],\n },\n { credentials: await this.auth.getOwnServiceCredentials() },\n )\n .then(r => r.items);\n },\n {\n name: 'SlackNotificationProcessor.entityLoader',\n cacheMap: new ExpiryMap(durationToMilliseconds({ minutes: 10 })),\n maxBatchSize: 100,\n batchScheduleFn: cb =>\n setTimeout(cb, durationToMilliseconds({ milliseconds: 10 })),\n },\n );\n\n const meter = metrics.getMeter('default');\n this.messagesSent = meter.createCounter(\n 'notifications.processors.slack.sent.count',\n {\n description: 'Number of messages sent to Slack successfully',\n },\n );\n this.messagesFailed = meter.createCounter(\n 'notifications.processors.slack.error.count',\n {\n description: 'Number of messages that failed to send to Slack',\n },\n );\n\n const throttle = pThrottle({\n limit: 10,\n interval: durationToMilliseconds({ minutes: 1 }),\n });\n const throttled = throttle((opts: ChatPostMessageArguments) =>\n this.sendNotification(opts),\n );\n this.sendNotifications = async (opts: ChatPostMessageArguments[]) => {\n const results = await Promise.allSettled(\n opts.map(message => throttled(message)),\n );\n\n let successCount = 0;\n let failureCount = 0;\n\n results.forEach((result, index) => {\n if (result.status === 'fulfilled') {\n successCount++;\n } else {\n this.logger.error(\n `Failed to send Slack channel notification to ${opts[index].channel}: ${result.reason.message}`,\n );\n failureCount++;\n }\n });\n\n this.messagesSent.add(successCount);\n this.messagesFailed.add(failureCount);\n };\n }\n\n getName(): string {\n return 'SlackNotificationProcessor';\n }\n\n async processOptions(\n options: NotificationSendOptions,\n ): Promise<NotificationSendOptions> {\n if (options.recipients.type !== 'entity') {\n return options;\n }\n\n const entityRefs = [options.recipients.entityRef].flat();\n\n const outbound: ChatPostMessageArguments[] = [];\n await Promise.all(\n entityRefs.map(async entityRef => {\n const compoundEntityRef = parseEntityRef(entityRef);\n // skip users as they are sent direct messages\n if (compoundEntityRef.kind === 'user') {\n return;\n }\n\n let channel;\n try {\n channel = await this.getSlackNotificationTarget(entityRef);\n } catch (error) {\n this.logger.error(\n `Failed to get Slack channel for entity: ${\n (error as Error).message\n }`,\n );\n return;\n }\n\n if (!channel) {\n this.logger.debug(`No Slack channel found for entity: ${entityRef}`);\n return;\n }\n\n this.logger.debug(\n `Sending notification with payload: ${JSON.stringify(\n options.payload,\n )}`,\n );\n\n const payload = toChatPostMessageArgs({\n channel,\n payload: options.payload,\n username: this.username,\n });\n\n this.logger.debug(\n `Sending Slack channel notification: ${JSON.stringify(payload)}`,\n );\n outbound.push(payload);\n }),\n );\n\n await this.sendNotifications(outbound);\n\n return options;\n }\n\n async postProcess(\n notification: Notification,\n options: NotificationSendOptions,\n ): Promise<void> {\n const destinations: string[] = [];\n\n // Handle broadcast case\n if (notification.user === null) {\n destinations.push(...(this.broadcastChannels ?? []));\n } else if (options.recipients.type === 'entity') {\n // Handle user-specific notification\n const entityRefs = [options.recipients.entityRef].flat();\n if (entityRefs.some(e => parseEntityRef(e).kind === 'group')) {\n // We've already dispatched a slack channel message, so let's not send a DM.\n return;\n }\n\n const destination = await this.getSlackNotificationTarget(\n notification.user,\n );\n\n if (!destination) {\n this.logger.error(\n `No slack.com/bot-notify annotation found for user: ${notification.user}`,\n );\n return;\n }\n\n destinations.push(destination);\n }\n\n // If no destinations, nothing to do\n if (destinations.length === 0) {\n return;\n }\n\n // Prepare outbound messages\n const formattedPayload = await this.formatPayloadDescriptionForSlack(\n options.payload,\n );\n const outbound = destinations.map(channel =>\n toChatPostMessageArgs({\n channel,\n payload: formattedPayload,\n username: this.username,\n }),\n );\n\n // Log debug info\n outbound.forEach(payload => {\n this.logger.debug(`Sending notification: ${JSON.stringify(payload)}`);\n });\n\n // Send notifications\n await this.sendNotifications(outbound);\n }\n\n private async formatPayloadDescriptionForSlack(\n payload: Notification['payload'],\n ) {\n return {\n ...payload,\n description: await this.replaceUserRefsWithSlackIds(payload.description),\n };\n }\n\n async replaceUserRefsWithSlackIds(\n text?: string,\n ): Promise<string | undefined> {\n if (!text) return undefined;\n\n // Match user entity refs like \"<@user:default/billy>\"\n const userRefRegex = /<@(user:[^>]+)>/gi;\n const matches = [...text.matchAll(userRefRegex)];\n\n if (matches.length === 0) return text;\n\n const uniqueUserRefs = new Set(\n matches.map(match => match[1].toLowerCase()),\n );\n\n const slackIdMap = new Map<string, string>();\n\n await Promise.all(\n [...uniqueUserRefs].map(async userRef => {\n try {\n const slackId = await this.getSlackNotificationTarget(userRef);\n if (slackId) {\n slackIdMap.set(userRef, `<@${slackId}>`);\n }\n } catch (error) {\n this.logger.warn(\n `Failed to resolve Slack ID for user ref \"${userRef}\": ${error}`,\n );\n }\n }),\n );\n\n return text.replace(userRefRegex, (match, userRef) => {\n const slackId = slackIdMap.get(userRef.toLowerCase());\n return slackId ?? match;\n });\n }\n\n async getSlackNotificationTarget(\n entityRef: string,\n ): Promise<string | undefined> {\n const entity = await this.entityLoader.load(entityRef);\n if (!entity) {\n throw new NotFoundError(`Entity not found: ${entityRef}`);\n }\n\n const slackId = await this.resolveSlackId(entity);\n return slackId;\n }\n\n private async resolveSlackId(entity: Entity): Promise<string | undefined> {\n // First try to get Slack ID from annotations\n const slackId = entity.metadata?.annotations?.[ANNOTATION_SLACK_BOT_NOTIFY];\n if (slackId) {\n return slackId;\n }\n\n // If no Slack ID in annotations and entity is a User, try to find by email\n if (isUserEntity(entity)) {\n return this.findSlackIdByEmail(entity);\n }\n\n return undefined;\n }\n\n private async findSlackIdByEmail(\n entity: UserEntity,\n ): Promise<string | undefined> {\n const email = entity.spec?.profile?.email;\n if (!email) {\n return undefined;\n }\n\n try {\n const user = await this.slack.users.lookupByEmail({ email });\n return user.user?.id;\n } catch (error) {\n this.logger.warn(\n `Failed to lookup Slack user by email ${email}: ${error}`,\n );\n return undefined;\n }\n }\n\n async sendNotification(args: ChatPostMessageArguments): Promise<void> {\n const response = await this.slack.chat.postMessage(args);\n\n if (!response.ok) {\n throw new Error(`Failed to send notification: ${response.error}`);\n }\n }\n}\n"],"names":["WebClient","DataLoader","ANNOTATION_SLACK_BOT_NOTIFY","ExpiryMap","durationToMilliseconds","metrics","pThrottle","parseEntityRef","toChatPostMessageArgs","NotFoundError","isUserEntity"],"mappings":"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"}
1
+ {"version":3,"file":"SlackNotificationProcessor.cjs.js","sources":["../../src/lib/SlackNotificationProcessor.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { AuthService, LoggerService } from '@backstage/backend-plugin-api';\nimport {\n Entity,\n isUserEntity,\n parseEntityRef,\n UserEntity,\n} from '@backstage/catalog-model';\nimport { Config, readDurationFromConfig } from '@backstage/config';\nimport { NotFoundError } from '@backstage/errors';\nimport { Notification } from '@backstage/plugin-notifications-common';\nimport {\n NotificationProcessor,\n NotificationSendOptions,\n} from '@backstage/plugin-notifications-node';\nimport { durationToMilliseconds } from '@backstage/types';\nimport { Counter, metrics } from '@opentelemetry/api';\nimport { ChatPostMessageArguments, WebClient } from '@slack/web-api';\nimport DataLoader from 'dataloader';\nimport pThrottle from 'p-throttle';\nimport { ANNOTATION_SLACK_BOT_NOTIFY } from './constants';\nimport { BroadcastRoute } from './types';\nimport { ExpiryMap, toChatPostMessageArgs } from './util';\nimport { CatalogService } from '@backstage/plugin-catalog-node';\n\nexport class SlackNotificationProcessor implements NotificationProcessor {\n private readonly logger: LoggerService;\n private readonly catalog: CatalogService;\n private readonly auth: AuthService;\n private readonly slack: WebClient;\n private readonly sendNotifications: (\n opts: ChatPostMessageArguments[],\n ) => Promise<void>;\n private readonly messagesSent: Counter;\n private readonly messagesFailed: Counter;\n private readonly broadcastChannels?: string[];\n private readonly broadcastRoutes?: BroadcastRoute[];\n private readonly entityLoader: DataLoader<string, Entity | undefined>;\n private readonly username?: string;\n private readonly concurrencyLimit: number;\n private readonly throttleInterval: number;\n\n static fromConfig(\n config: Config,\n options: {\n auth: AuthService;\n logger: LoggerService;\n catalog: CatalogService;\n slack?: WebClient;\n broadcastChannels?: string[];\n },\n ): SlackNotificationProcessor[] {\n const slackConfig =\n config.getOptionalConfigArray('notifications.processors.slack') ?? [];\n return slackConfig.map(c => {\n const token = c.getString('token');\n const slack = options.slack ?? new WebClient(token);\n const broadcastChannels = c.getOptionalStringArray('broadcastChannels');\n const username = c.getOptionalString('username');\n const broadcastRoutesConfig = c.getOptionalConfigArray('broadcastRoutes');\n const broadcastRoutes = broadcastRoutesConfig?.map(route =>\n this.parseBroadcastRoute(route),\n );\n const concurrencyLimit = c.getOptionalNumber('concurrencyLimit') ?? 10;\n const throttleInterval = c.has('throttleInterval')\n ? durationToMilliseconds(\n readDurationFromConfig(c, { key: 'throttleInterval' }),\n )\n : durationToMilliseconds({ minutes: 1 });\n return new SlackNotificationProcessor({\n slack,\n broadcastChannels,\n broadcastRoutes,\n username,\n concurrencyLimit,\n throttleInterval,\n ...options,\n });\n });\n }\n\n private constructor(options: {\n slack: WebClient;\n auth: AuthService;\n logger: LoggerService;\n catalog: CatalogService;\n broadcastChannels?: string[];\n broadcastRoutes?: BroadcastRoute[];\n username?: string;\n concurrencyLimit?: number;\n throttleInterval?: number;\n }) {\n const {\n auth,\n catalog,\n logger,\n slack,\n broadcastChannels,\n broadcastRoutes,\n username,\n concurrencyLimit,\n throttleInterval,\n } = options;\n this.logger = logger;\n this.catalog = catalog;\n this.auth = auth;\n this.slack = slack;\n this.broadcastChannels = broadcastChannels;\n this.broadcastRoutes = broadcastRoutes;\n this.username = username;\n this.concurrencyLimit = concurrencyLimit ?? 10;\n this.throttleInterval =\n throttleInterval ?? durationToMilliseconds({ minutes: 1 });\n\n this.entityLoader = new DataLoader<string, Entity | undefined>(\n async entityRefs => {\n return await this.catalog\n .getEntitiesByRefs(\n {\n entityRefs: entityRefs.slice(),\n fields: [\n `kind`,\n `spec.profile.email`,\n `metadata.annotations.${ANNOTATION_SLACK_BOT_NOTIFY}`,\n ],\n },\n { credentials: await this.auth.getOwnServiceCredentials() },\n )\n .then(r => r.items);\n },\n {\n name: 'SlackNotificationProcessor.entityLoader',\n cacheMap: new ExpiryMap(durationToMilliseconds({ minutes: 10 })),\n maxBatchSize: 100,\n batchScheduleFn: cb =>\n setTimeout(cb, durationToMilliseconds({ milliseconds: 10 })),\n },\n );\n\n const meter = metrics.getMeter('default');\n this.messagesSent = meter.createCounter(\n 'notifications.processors.slack.sent.count',\n {\n description: 'Number of messages sent to Slack successfully',\n },\n );\n this.messagesFailed = meter.createCounter(\n 'notifications.processors.slack.error.count',\n {\n description: 'Number of messages that failed to send to Slack',\n },\n );\n\n const throttle = pThrottle({\n limit: this.concurrencyLimit,\n interval: this.throttleInterval,\n });\n const throttled = throttle((opts: ChatPostMessageArguments) =>\n this.sendNotification(opts),\n );\n this.sendNotifications = async (opts: ChatPostMessageArguments[]) => {\n const results = await Promise.allSettled(\n opts.map(message => throttled(message)),\n );\n\n let successCount = 0;\n let failureCount = 0;\n\n results.forEach((result, index) => {\n if (result.status === 'fulfilled') {\n successCount++;\n } else {\n this.logger.error(\n `Failed to send Slack channel notification to ${opts[index].channel}: ${result.reason.message}`,\n );\n failureCount++;\n }\n });\n\n this.messagesSent.add(successCount);\n this.messagesFailed.add(failureCount);\n };\n }\n\n getName(): string {\n return 'SlackNotificationProcessor';\n }\n\n async processOptions(\n options: NotificationSendOptions,\n ): Promise<NotificationSendOptions> {\n if (options.recipients.type !== 'entity') {\n return options;\n }\n\n const entityRefs = [options.recipients.entityRef].flat();\n\n const outbound: ChatPostMessageArguments[] = [];\n await Promise.all(\n entityRefs.map(async entityRef => {\n const compoundEntityRef = parseEntityRef(entityRef);\n // skip users as they are sent direct messages\n if (compoundEntityRef.kind === 'user') {\n return;\n }\n\n let channel;\n try {\n channel = await this.getSlackNotificationTarget(entityRef);\n } catch (error) {\n this.logger.error(\n `Failed to get Slack channel for entity: ${\n (error as Error).message\n }`,\n );\n return;\n }\n\n if (!channel) {\n this.logger.debug(`No Slack channel found for entity: ${entityRef}`);\n return;\n }\n\n this.logger.debug(\n `Sending notification with payload: ${JSON.stringify(\n options.payload,\n )}`,\n );\n\n const payload = toChatPostMessageArgs({\n channel,\n payload: options.payload,\n username: this.username,\n });\n\n this.logger.debug(\n `Sending Slack channel notification: ${JSON.stringify(payload)}`,\n );\n outbound.push(payload);\n }),\n );\n\n await this.sendNotifications(outbound);\n\n return options;\n }\n\n async postProcess(\n notification: Notification,\n options: NotificationSendOptions,\n ): Promise<void> {\n const destinations: string[] = [];\n\n // Handle broadcast case\n if (notification.user === null) {\n const routedChannels = this.getBroadcastDestinations(notification);\n destinations.push(...routedChannels);\n } else if (options.recipients.type === 'entity') {\n // Handle user-specific notification\n const entityRefs = [options.recipients.entityRef].flat();\n if (entityRefs.some(e => parseEntityRef(e).kind === 'group')) {\n // We've already dispatched a slack channel message, so let's not send a DM.\n return;\n }\n\n const destination = await this.getSlackNotificationTarget(\n notification.user,\n );\n\n if (!destination) {\n this.logger.error(\n `No slack.com/bot-notify annotation found for user: ${notification.user}`,\n );\n return;\n }\n\n destinations.push(destination);\n }\n\n // If no destinations, nothing to do\n if (destinations.length === 0) {\n return;\n }\n\n // Prepare outbound messages\n const formattedPayload = await this.formatPayloadDescriptionForSlack(\n options.payload,\n );\n const outbound = destinations.map(channel =>\n toChatPostMessageArgs({\n channel,\n payload: formattedPayload,\n username: this.username,\n }),\n );\n\n // Log debug info\n outbound.forEach(payload => {\n this.logger.debug(`Sending notification: ${JSON.stringify(payload)}`);\n });\n\n // Send notifications\n await this.sendNotifications(outbound);\n }\n\n private async formatPayloadDescriptionForSlack(\n payload: Notification['payload'],\n ) {\n return {\n ...payload,\n description: await this.replaceUserRefsWithSlackIds(payload.description),\n };\n }\n\n async replaceUserRefsWithSlackIds(\n text?: string,\n ): Promise<string | undefined> {\n if (!text) return undefined;\n\n // Match user entity refs like \"<@user:default/billy>\"\n const userRefRegex = /<@(user:[^>]+)>/gi;\n const matches = [...text.matchAll(userRefRegex)];\n\n if (matches.length === 0) return text;\n\n const uniqueUserRefs = new Set(\n matches.map(match => match[1].toLowerCase()),\n );\n\n const slackIdMap = new Map<string, string>();\n\n await Promise.all(\n [...uniqueUserRefs].map(async userRef => {\n try {\n const slackId = await this.getSlackNotificationTarget(userRef);\n if (slackId) {\n slackIdMap.set(userRef, `<@${slackId}>`);\n }\n } catch (error) {\n this.logger.warn(\n `Failed to resolve Slack ID for user ref \"${userRef}\": ${error}`,\n );\n }\n }),\n );\n\n return text.replace(userRefRegex, (match, userRef) => {\n const slackId = slackIdMap.get(userRef.toLowerCase());\n return slackId ?? match;\n });\n }\n\n async getSlackNotificationTarget(\n entityRef: string,\n ): Promise<string | undefined> {\n const entity = await this.entityLoader.load(entityRef);\n if (!entity) {\n throw new NotFoundError(`Entity not found: ${entityRef}`);\n }\n\n const slackId = await this.resolveSlackId(entity);\n return slackId;\n }\n\n private async resolveSlackId(entity: Entity): Promise<string | undefined> {\n // First try to get Slack ID from annotations\n const slackId = entity.metadata?.annotations?.[ANNOTATION_SLACK_BOT_NOTIFY];\n if (slackId) {\n return slackId;\n }\n\n // If no Slack ID in annotations and entity is a User, try to find by email\n if (isUserEntity(entity)) {\n return this.findSlackIdByEmail(entity);\n }\n\n return undefined;\n }\n\n private async findSlackIdByEmail(\n entity: UserEntity,\n ): Promise<string | undefined> {\n const email = entity.spec?.profile?.email;\n if (!email) {\n return undefined;\n }\n\n try {\n const user = await this.slack.users.lookupByEmail({ email });\n return user.user?.id;\n } catch (error) {\n this.logger.warn(\n `Failed to lookup Slack user by email ${email}: ${error}`,\n );\n return undefined;\n }\n }\n\n async sendNotification(args: ChatPostMessageArguments): Promise<void> {\n const response = await this.slack.chat.postMessage(args);\n\n if (!response.ok) {\n throw new Error(`Failed to send notification: ${response.error}`);\n }\n }\n\n private static parseBroadcastRoute(route: Config): BroadcastRoute {\n const channelValue = route.getOptional('channel');\n let channels: string[];\n\n if (typeof channelValue === 'string') {\n channels = [channelValue];\n } else if (Array.isArray(channelValue)) {\n channels = channelValue as string[];\n } else {\n throw new Error(\n 'broadcastRoutes entry must have a channel property (string or string[])',\n );\n }\n\n return {\n origin: route.getOptionalString('origin'),\n topic: route.getOptionalString('topic'),\n channels,\n };\n }\n\n /**\n * Gets the destination channels for a broadcast notification based on\n * configured routes. Routes are matched by origin and/or topic.\n *\n * Matching precedence:\n * 1. Routes with both origin AND topic matching (most specific)\n * 2. Routes with only origin matching\n * 3. Routes with only topic matching\n * 4. Default broadcastChannels (least specific fallback)\n *\n * The first matching route wins within each precedence level.\n */\n private getBroadcastDestinations(notification: Notification): string[] {\n const { origin } = notification;\n const { topic } = notification.payload;\n\n if (!this.broadcastRoutes || this.broadcastRoutes.length === 0) {\n // Fall back to legacy broadcastChannels config\n return this.broadcastChannels ?? [];\n }\n\n // Find most specific match\n // Priority 1: origin AND topic match\n const originAndTopicMatch = this.broadcastRoutes.find(\n route =>\n route.origin !== undefined &&\n route.topic !== undefined &&\n route.origin === origin &&\n route.topic === topic,\n );\n\n if (originAndTopicMatch) {\n return originAndTopicMatch.channels;\n }\n\n // Priority 2: origin-only match (no topic specified in route)\n const originOnlyMatch = this.broadcastRoutes.find(\n route =>\n route.origin !== undefined &&\n route.topic === undefined &&\n route.origin === origin,\n );\n\n if (originOnlyMatch) {\n return originOnlyMatch.channels;\n }\n\n // Priority 3: topic-only match (no origin specified in route)\n const topicOnlyMatch = this.broadcastRoutes.find(\n route =>\n route.topic !== undefined &&\n route.origin === undefined &&\n route.topic === topic,\n );\n\n if (topicOnlyMatch) {\n return topicOnlyMatch.channels;\n }\n\n // No match found, fall back to legacy broadcastChannels\n return this.broadcastChannels ?? [];\n }\n}\n"],"names":["config","WebClient","durationToMilliseconds","readDurationFromConfig","DataLoader","ANNOTATION_SLACK_BOT_NOTIFY","ExpiryMap","metrics","pThrottle","parseEntityRef","toChatPostMessageArgs","NotFoundError","isUserEntity"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/plugin-notifications-backend-module-slack",
3
- "version": "0.2.2-next.1",
3
+ "version": "0.3.1-next.0",
4
4
  "description": "The slack backend module for the notifications plugin.",
5
5
  "backstage": {
6
6
  "role": "backend-plugin-module",
@@ -37,13 +37,13 @@
37
37
  "test": "backstage-cli package test"
38
38
  },
39
39
  "dependencies": {
40
- "@backstage/backend-plugin-api": "1.6.0-next.1",
40
+ "@backstage/backend-plugin-api": "1.7.0-next.0",
41
41
  "@backstage/catalog-model": "1.7.6",
42
42
  "@backstage/config": "1.3.6",
43
43
  "@backstage/errors": "1.2.7",
44
- "@backstage/plugin-catalog-node": "1.20.1-next.1",
44
+ "@backstage/plugin-catalog-node": "1.21.0-next.0",
45
45
  "@backstage/plugin-notifications-common": "0.2.0",
46
- "@backstage/plugin-notifications-node": "0.2.22-next.1",
46
+ "@backstage/plugin-notifications-node": "0.2.23-next.0",
47
47
  "@backstage/types": "1.2.2",
48
48
  "@opentelemetry/api": "^1.9.0",
49
49
  "@slack/bolt": "^3.21.4",
@@ -53,11 +53,11 @@
53
53
  "p-throttle": "^4.1.1"
54
54
  },
55
55
  "devDependencies": {
56
- "@backstage/backend-test-utils": "1.10.2-next.1",
57
- "@backstage/cli": "0.35.0-next.2",
58
- "@backstage/plugin-catalog-node": "1.20.1-next.1",
59
- "@backstage/test-utils": "1.7.14-next.0",
60
- "@faker-js/faker": "^8.4.1",
56
+ "@backstage/backend-test-utils": "1.10.4-next.0",
57
+ "@backstage/cli": "0.35.3-next.0",
58
+ "@backstage/plugin-catalog-node": "1.21.0-next.0",
59
+ "@backstage/test-utils": "1.7.15-next.0",
60
+ "@faker-js/faker": "^10.0.0",
61
61
  "msw": "^2.0.0"
62
62
  },
63
63
  "configSchema": "config.d.ts",