@backstage/plugin-notifications-backend-module-slack 0.1.1-next.2 → 0.1.1

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,45 @@
1
1
  # @backstage/plugin-notifications-backend-module-slack
2
2
 
3
+ ## 0.1.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 4f10768: Fix slack notification processor to handle a notification with an empty description
8
+ - f6480c7: Fix dataloader caching, and use the proper catalog service ref
9
+ - a1c5bbb: Added email-based Slack User ID lookup if `metadata.annotations.slack.com/bot-notify` is missing from user entity
10
+ - e099d0a: Notifications which mention user entity refs are now replaced with Slack compatible mentions.
11
+
12
+ Example: `Welcome <@user:default/billy>!` -> `Welcome <@U123456890>!`
13
+
14
+ - Updated dependencies
15
+ - @backstage/catalog-model@1.7.4
16
+ - @backstage/plugin-catalog-node@1.17.0
17
+ - @backstage/backend-plugin-api@1.3.1
18
+ - @backstage/config@1.3.2
19
+ - @backstage/plugin-notifications-node@0.2.15
20
+ - @backstage/errors@1.2.7
21
+ - @backstage/types@1.2.1
22
+ - @backstage/plugin-notifications-common@0.0.8
23
+
24
+ ## 0.1.1-next.3
25
+
26
+ ### Patch Changes
27
+
28
+ - f6480c7: Fix dataloader caching, and use the proper catalog service ref
29
+ - e099d0a: Notifications which mention user entity refs are now replaced with Slack compatible mentions.
30
+
31
+ Example: `Welcome <@user:default/billy>!` -> `Welcome <@U123456890>!`
32
+
33
+ - Updated dependencies
34
+ - @backstage/backend-plugin-api@1.3.1-next.2
35
+ - @backstage/catalog-model@1.7.3
36
+ - @backstage/config@1.3.2
37
+ - @backstage/errors@1.2.7
38
+ - @backstage/types@1.2.1
39
+ - @backstage/plugin-catalog-node@1.17.0-next.2
40
+ - @backstage/plugin-notifications-common@0.0.8
41
+ - @backstage/plugin-notifications-node@0.2.15-next.2
42
+
3
43
  ## 0.1.1-next.2
4
44
 
5
45
  ### Patch Changes
@@ -24,6 +24,7 @@ class SlackNotificationProcessor {
24
24
  messagesSent;
25
25
  messagesFailed;
26
26
  broadcastChannels;
27
+ entityLoader;
27
28
  static fromConfig(config, options) {
28
29
  const slackConfig = config.getOptionalConfigArray("notifications.processors.slack") ?? [];
29
30
  return slackConfig.map((c) => {
@@ -44,6 +45,27 @@ class SlackNotificationProcessor {
44
45
  this.auth = auth;
45
46
  this.slack = slack;
46
47
  this.broadcastChannels = broadcastChannels;
48
+ this.entityLoader = new DataLoader__default.default(
49
+ async (entityRefs) => {
50
+ return await this.catalog.getEntitiesByRefs(
51
+ {
52
+ entityRefs: entityRefs.slice(),
53
+ fields: [
54
+ `kind`,
55
+ `spec.profile.email`,
56
+ `metadata.annotations.${constants.ANNOTATION_SLACK_BOT_NOTIFY}`
57
+ ]
58
+ },
59
+ { credentials: await this.auth.getOwnServiceCredentials() }
60
+ ).then((r) => r.items);
61
+ },
62
+ {
63
+ name: "SlackNotificationProcessor.entityLoader",
64
+ cacheMap: new util.ExpiryMap(types.durationToMilliseconds({ minutes: 10 })),
65
+ maxBatchSize: 100,
66
+ batchScheduleFn: (cb) => setTimeout(cb, types.durationToMilliseconds({ milliseconds: 10 }))
67
+ }
68
+ );
47
69
  const meter = api.metrics.getMeter("default");
48
70
  this.messagesSent = meter.createCounter(
49
71
  "notifications.processors.slack.sent.count",
@@ -127,7 +149,6 @@ class SlackNotificationProcessor {
127
149
  outbound.push(payload);
128
150
  })
129
151
  );
130
- console.log("dispatching message");
131
152
  await this.sendNotifications(outbound);
132
153
  return options;
133
154
  }
@@ -154,41 +175,54 @@ class SlackNotificationProcessor {
154
175
  if (destinations.length === 0) {
155
176
  return;
156
177
  }
178
+ const formattedPayload = await this.formatPayloadDescriptionForSlack(
179
+ options.payload
180
+ );
157
181
  const outbound = destinations.map(
158
- (channel) => util.toChatPostMessageArgs({ channel, payload: options.payload })
182
+ (channel) => util.toChatPostMessageArgs({ channel, payload: formattedPayload })
159
183
  );
160
184
  outbound.forEach((payload) => {
161
185
  this.logger.debug(`Sending notification: ${JSON.stringify(payload)}`);
162
186
  });
163
187
  await this.sendNotifications(outbound);
164
188
  }
165
- async getEntities(entityRefs) {
166
- const { token } = await this.auth.getPluginRequestToken({
167
- onBehalfOf: await this.auth.getOwnServiceCredentials(),
168
- targetPluginId: "catalog"
169
- });
170
- const response = await this.catalog.getEntitiesByRefs(
171
- {
172
- entityRefs: entityRefs.slice(),
173
- fields: [
174
- `kind`,
175
- `spec.profile.email`,
176
- `metadata.annotations.${constants.ANNOTATION_SLACK_BOT_NOTIFY}`
177
- ]
178
- },
179
- {
180
- token
181
- }
189
+ async formatPayloadDescriptionForSlack(payload) {
190
+ return {
191
+ ...payload,
192
+ description: await this.replaceUserRefsWithSlackIds(payload.description)
193
+ };
194
+ }
195
+ async replaceUserRefsWithSlackIds(text) {
196
+ if (!text) return void 0;
197
+ const userRefRegex = /<@(user:[^>]+)>/gi;
198
+ const matches = [...text.matchAll(userRefRegex)];
199
+ if (matches.length === 0) return text;
200
+ const uniqueUserRefs = new Set(
201
+ matches.map((match) => match[1].toLowerCase())
202
+ );
203
+ const slackIdMap = /* @__PURE__ */ new Map();
204
+ await Promise.all(
205
+ [...uniqueUserRefs].map(async (userRef) => {
206
+ try {
207
+ const slackId = await this.getSlackNotificationTarget(userRef);
208
+ if (slackId) {
209
+ slackIdMap.set(userRef, `<@${slackId}>`);
210
+ }
211
+ } catch (error) {
212
+ this.logger.warn(
213
+ `Failed to resolve Slack ID for user ref "${userRef}": ${error}`
214
+ );
215
+ }
216
+ })
182
217
  );
183
- return response.items;
218
+ return text.replace(userRefRegex, (match, userRef) => {
219
+ const slackId = slackIdMap.get(userRef.toLowerCase());
220
+ return slackId ?? match;
221
+ });
184
222
  }
185
223
  async getSlackNotificationTarget(entityRef) {
186
- const entityLoader = new DataLoader__default.default(
187
- (entityRefs) => this.getEntities(entityRefs)
188
- );
189
- const entity = await entityLoader.load(entityRef);
224
+ const entity = await this.entityLoader.load(entityRef);
190
225
  if (!entity) {
191
- console.log(`Entity not found: ${entityRef}`);
192
226
  throw new errors.NotFoundError(`Entity not found: ${entityRef}`);
193
227
  }
194
228
  const slackId = await this.resolveSlackId(entity);
@@ -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 {\n AuthService,\n DiscoveryService,\n LoggerService,\n} from '@backstage/backend-plugin-api';\nimport { CatalogApi } from '@backstage/catalog-client';\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 { toChatPostMessageArgs } from './util';\n\nexport class SlackNotificationProcessor implements NotificationProcessor {\n private readonly logger: LoggerService;\n private readonly catalog: CatalogApi;\n private readonly auth: AuthService;\n private readonly slack: WebClient;\n private readonly sendNotifications;\n private readonly messagesSent: Counter;\n private readonly messagesFailed: Counter;\n private readonly broadcastChannels?: string[];\n\n static fromConfig(\n config: Config,\n options: {\n auth: AuthService;\n discovery: DiscoveryService;\n logger: LoggerService;\n catalog: CatalogApi;\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 return new SlackNotificationProcessor({\n slack,\n broadcastChannels,\n ...options,\n });\n });\n }\n\n private constructor(options: {\n slack: WebClient;\n auth: AuthService;\n discovery: DiscoveryService;\n logger: LoggerService;\n catalog: CatalogApi;\n broadcastChannels?: string[];\n }) {\n const { auth, catalog, logger, slack, broadcastChannels } = options;\n this.logger = logger;\n this.catalog = catalog;\n this.auth = auth;\n this.slack = slack;\n this.broadcastChannels = broadcastChannels;\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 => {\n if (result.status === 'fulfilled') {\n successCount++;\n } else {\n this.logger.error(\n `Failed to send Slack channel notification: ${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 });\n\n this.logger.debug(\n `Sending Slack channel notification: ${JSON.stringify(payload)}`,\n );\n outbound.push(payload);\n }),\n );\n\n console.log('dispatching message');\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 outbound = destinations.map(channel =>\n toChatPostMessageArgs({ channel, payload: options.payload }),\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 async getEntities(\n entityRefs: readonly string[],\n ): Promise<(Entity | undefined)[]> {\n const { token } = await this.auth.getPluginRequestToken({\n onBehalfOf: await this.auth.getOwnServiceCredentials(),\n targetPluginId: 'catalog',\n });\n\n const response = await this.catalog.getEntitiesByRefs(\n {\n entityRefs: entityRefs.slice(),\n fields: [\n `kind`,\n `spec.profile.email`,\n `metadata.annotations.${ANNOTATION_SLACK_BOT_NOTIFY}`,\n ],\n },\n {\n token,\n },\n );\n\n return response.items;\n }\n\n async getSlackNotificationTarget(\n entityRef: string,\n ): Promise<string | undefined> {\n const entityLoader = new DataLoader<string, Entity | undefined>(\n entityRefs => this.getEntities(entityRefs),\n );\n const entity = await entityLoader.load(entityRef);\n\n if (!entity) {\n console.log(`Entity not found: ${entityRef}`);\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","metrics","pThrottle","durationToMilliseconds","parseEntityRef","toChatPostMessageArgs","ANNOTATION_SLACK_BOT_NOTIFY","DataLoader","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 } 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 private readonly messagesSent: Counter;\n private readonly messagesFailed: Counter;\n private readonly broadcastChannels?: string[];\n private readonly entityLoader: DataLoader<string, Entity | undefined>;\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 return new SlackNotificationProcessor({\n slack,\n broadcastChannels,\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 }) {\n const { auth, catalog, logger, slack, broadcastChannels } = options;\n this.logger = logger;\n this.catalog = catalog;\n this.auth = auth;\n this.slack = slack;\n this.broadcastChannels = broadcastChannels;\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 => {\n if (result.status === 'fulfilled') {\n successCount++;\n } else {\n this.logger.error(\n `Failed to send Slack channel notification: ${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 });\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({ channel, payload: formattedPayload }),\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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"}
@@ -65,7 +65,40 @@ function getColor(severity) {
65
65
  return "#00A699";
66
66
  }
67
67
  }
68
+ class ExpiryMap extends Map {
69
+ #ttlMs;
70
+ #timestamps = /* @__PURE__ */ new Map();
71
+ constructor(ttlMs) {
72
+ super();
73
+ this.#ttlMs = ttlMs;
74
+ }
75
+ set(key, value) {
76
+ const result = super.set(key, value);
77
+ this.#timestamps.set(key, Date.now());
78
+ return result;
79
+ }
80
+ get(key) {
81
+ if (!this.has(key)) {
82
+ return void 0;
83
+ }
84
+ const timestamp = this.#timestamps.get(key);
85
+ if (Date.now() - timestamp > this.#ttlMs) {
86
+ this.delete(key);
87
+ return void 0;
88
+ }
89
+ return super.get(key);
90
+ }
91
+ delete(key) {
92
+ this.#timestamps.delete(key);
93
+ return super.delete(key);
94
+ }
95
+ clear() {
96
+ this.#timestamps.clear();
97
+ return super.clear();
98
+ }
99
+ }
68
100
 
101
+ exports.ExpiryMap = ExpiryMap;
69
102
  exports.toChatPostMessageArgs = toChatPostMessageArgs;
70
103
  exports.toSlackBlockKit = toSlackBlockKit;
71
104
  //# sourceMappingURL=util.cjs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"util.cjs.js","sources":["../../src/lib/util.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 {\n NotificationPayload,\n NotificationSeverity,\n} from '@backstage/plugin-notifications-common';\nimport { ChatPostMessageArguments, KnownBlock } from '@slack/web-api';\n\nexport function toChatPostMessageArgs(options: {\n channel: string;\n payload: NotificationPayload;\n}): ChatPostMessageArguments {\n const { channel, payload } = options;\n\n const args: ChatPostMessageArguments = {\n channel,\n text: payload.title,\n attachments: [\n {\n color: getColor(payload.severity),\n blocks: toSlackBlockKit(payload),\n fallback: payload.title,\n },\n ],\n };\n\n return args;\n}\n\nexport function toSlackBlockKit(payload: NotificationPayload): KnownBlock[] {\n const { description, link, severity, topic } = payload;\n return [\n {\n type: 'section',\n text: {\n type: 'mrkdwn',\n text: description ?? 'No description provided',\n },\n accessory: {\n type: 'button',\n text: {\n type: 'plain_text',\n text: 'View More',\n },\n ...(link && { url: link }),\n action_id: 'button-action',\n },\n },\n {\n type: 'context',\n elements: [\n {\n type: 'plain_text',\n text: `Severity: ${severity ?? 'normal'}`,\n emoji: true,\n },\n {\n type: 'plain_text',\n text: `Topic: ${topic ?? 'N/A'}`,\n emoji: true,\n },\n ],\n },\n ];\n}\n\nfunction getColor(severity: NotificationSeverity | undefined) {\n switch (severity) {\n case 'critical':\n return '#FF0000'; // Red\n case 'high':\n return '#FFA500'; // Orange\n case 'low':\n case 'normal':\n default:\n return '#00A699'; // Neutral color\n }\n}\n"],"names":[],"mappings":";;AAsBO,SAAS,sBAAsB,OAGT,EAAA;AAC3B,EAAM,MAAA,EAAE,OAAS,EAAA,OAAA,EAAY,GAAA,OAAA;AAE7B,EAAA,MAAM,IAAiC,GAAA;AAAA,IACrC,OAAA;AAAA,IACA,MAAM,OAAQ,CAAA,KAAA;AAAA,IACd,WAAa,EAAA;AAAA,MACX;AAAA,QACE,KAAA,EAAO,QAAS,CAAA,OAAA,CAAQ,QAAQ,CAAA;AAAA,QAChC,MAAA,EAAQ,gBAAgB,OAAO,CAAA;AAAA,QAC/B,UAAU,OAAQ,CAAA;AAAA;AACpB;AACF,GACF;AAEA,EAAO,OAAA,IAAA;AACT;AAEO,SAAS,gBAAgB,OAA4C,EAAA;AAC1E,EAAA,MAAM,EAAE,WAAA,EAAa,IAAM,EAAA,QAAA,EAAU,OAAU,GAAA,OAAA;AAC/C,EAAO,OAAA;AAAA,IACL;AAAA,MACE,IAAM,EAAA,SAAA;AAAA,MACN,IAAM,EAAA;AAAA,QACJ,IAAM,EAAA,QAAA;AAAA,QACN,MAAM,WAAe,IAAA;AAAA,OACvB;AAAA,MACA,SAAW,EAAA;AAAA,QACT,IAAM,EAAA,QAAA;AAAA,QACN,IAAM,EAAA;AAAA,UACJ,IAAM,EAAA,YAAA;AAAA,UACN,IAAM,EAAA;AAAA,SACR;AAAA,QACA,GAAI,IAAA,IAAQ,EAAE,GAAA,EAAK,IAAK,EAAA;AAAA,QACxB,SAAW,EAAA;AAAA;AACb,KACF;AAAA,IACA;AAAA,MACE,IAAM,EAAA,SAAA;AAAA,MACN,QAAU,EAAA;AAAA,QACR;AAAA,UACE,IAAM,EAAA,YAAA;AAAA,UACN,IAAA,EAAM,CAAa,UAAA,EAAA,QAAA,IAAY,QAAQ,CAAA,CAAA;AAAA,UACvC,KAAO,EAAA;AAAA,SACT;AAAA,QACA;AAAA,UACE,IAAM,EAAA,YAAA;AAAA,UACN,IAAA,EAAM,CAAU,OAAA,EAAA,KAAA,IAAS,KAAK,CAAA,CAAA;AAAA,UAC9B,KAAO,EAAA;AAAA;AACT;AACF;AACF,GACF;AACF;AAEA,SAAS,SAAS,QAA4C,EAAA;AAC5D,EAAA,QAAQ,QAAU;AAAA,IAChB,KAAK,UAAA;AACH,MAAO,OAAA,SAAA;AAAA;AAAA,IACT,KAAK,MAAA;AACH,MAAO,OAAA,SAAA;AAAA;AAAA,IACT,KAAK,KAAA;AAAA,IACL,KAAK,QAAA;AAAA,IACL;AACE,MAAO,OAAA,SAAA;AAAA;AAEb;;;;;"}
1
+ {"version":3,"file":"util.cjs.js","sources":["../../src/lib/util.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 {\n NotificationPayload,\n NotificationSeverity,\n} from '@backstage/plugin-notifications-common';\nimport { ChatPostMessageArguments, KnownBlock } from '@slack/web-api';\n\nexport function toChatPostMessageArgs(options: {\n channel: string;\n payload: NotificationPayload;\n}): ChatPostMessageArguments {\n const { channel, payload } = options;\n\n const args: ChatPostMessageArguments = {\n channel,\n text: payload.title,\n attachments: [\n {\n color: getColor(payload.severity),\n blocks: toSlackBlockKit(payload),\n fallback: payload.title,\n },\n ],\n };\n\n return args;\n}\n\nexport function toSlackBlockKit(payload: NotificationPayload): KnownBlock[] {\n const { description, link, severity, topic } = payload;\n return [\n {\n type: 'section',\n text: {\n type: 'mrkdwn',\n text: description ?? 'No description provided',\n },\n accessory: {\n type: 'button',\n text: {\n type: 'plain_text',\n text: 'View More',\n },\n ...(link && { url: link }),\n action_id: 'button-action',\n },\n },\n {\n type: 'context',\n elements: [\n {\n type: 'plain_text',\n text: `Severity: ${severity ?? 'normal'}`,\n emoji: true,\n },\n {\n type: 'plain_text',\n text: `Topic: ${topic ?? 'N/A'}`,\n emoji: true,\n },\n ],\n },\n ];\n}\n\nfunction getColor(severity: NotificationSeverity | undefined) {\n switch (severity) {\n case 'critical':\n return '#FF0000'; // Red\n case 'high':\n return '#FFA500'; // Orange\n case 'low':\n case 'normal':\n default:\n return '#00A699'; // Neutral color\n }\n}\n\n// Simple expiry map for the data loader, which only expects a map that implements set, get, and delete and clear\nexport class ExpiryMap<K, V> extends Map<K, V> {\n #ttlMs: number;\n #timestamps: Map<K, number> = new Map();\n\n constructor(ttlMs: number) {\n super();\n this.#ttlMs = ttlMs;\n }\n\n set(key: K, value: V) {\n const result = super.set(key, value);\n this.#timestamps.set(key, Date.now());\n return result;\n }\n\n get(key: K) {\n if (!this.has(key)) {\n return undefined;\n }\n const timestamp = this.#timestamps.get(key)!;\n if (Date.now() - timestamp > this.#ttlMs) {\n this.delete(key);\n return undefined;\n }\n return super.get(key);\n }\n\n delete(key: K) {\n this.#timestamps.delete(key);\n return super.delete(key);\n }\n\n clear() {\n this.#timestamps.clear();\n return super.clear();\n }\n}\n"],"names":[],"mappings":"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"}
@@ -1,9 +1,9 @@
1
1
  'use strict';
2
2
 
3
3
  var backendPluginApi = require('@backstage/backend-plugin-api');
4
- var catalogClient = require('@backstage/catalog-client');
5
4
  var pluginNotificationsNode = require('@backstage/plugin-notifications-node');
6
5
  var SlackNotificationProcessor = require('./lib/SlackNotificationProcessor.cjs.js');
6
+ var pluginCatalogNode = require('@backstage/plugin-catalog-node');
7
7
 
8
8
  const notificationsModuleSlack = backendPluginApi.createBackendModule({
9
9
  pluginId: "notifications",
@@ -13,20 +13,16 @@ const notificationsModuleSlack = backendPluginApi.createBackendModule({
13
13
  deps: {
14
14
  auth: backendPluginApi.coreServices.auth,
15
15
  config: backendPluginApi.coreServices.rootConfig,
16
- discovery: backendPluginApi.coreServices.discovery,
17
16
  logger: backendPluginApi.coreServices.logger,
17
+ catalog: pluginCatalogNode.catalogServiceRef,
18
18
  notifications: pluginNotificationsNode.notificationsProcessingExtensionPoint
19
19
  },
20
- async init({ auth, config, discovery, logger, notifications }) {
21
- const catalogClient$1 = new catalogClient.CatalogClient({
22
- discoveryApi: discovery
23
- });
20
+ async init({ auth, config, logger, catalog, notifications }) {
24
21
  notifications.addProcessor(
25
22
  SlackNotificationProcessor.SlackNotificationProcessor.fromConfig(config, {
26
23
  auth,
27
- discovery,
28
24
  logger,
29
- catalog: catalogClient$1
25
+ catalog
30
26
  })
31
27
  );
32
28
  }
@@ -1 +1 @@
1
- {"version":3,"file":"module.cjs.js","sources":["../src/module.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 */\nimport {\n coreServices,\n createBackendModule,\n} from '@backstage/backend-plugin-api';\nimport { CatalogClient } from '@backstage/catalog-client';\nimport { notificationsProcessingExtensionPoint } from '@backstage/plugin-notifications-node';\nimport { SlackNotificationProcessor } from './lib/SlackNotificationProcessor';\n\n/**\n * The Slack notification processor for use with the notifications plugin.\n * This allows sending of notifications via Slack DMs or to channels.\n *\n * @public\n */\nexport const notificationsModuleSlack = createBackendModule({\n pluginId: 'notifications',\n moduleId: 'slack',\n register(reg) {\n reg.registerInit({\n deps: {\n auth: coreServices.auth,\n config: coreServices.rootConfig,\n discovery: coreServices.discovery,\n logger: coreServices.logger,\n notifications: notificationsProcessingExtensionPoint,\n },\n async init({ auth, config, discovery, logger, notifications }) {\n const catalogClient = new CatalogClient({\n discoveryApi: discovery,\n });\n\n notifications.addProcessor(\n SlackNotificationProcessor.fromConfig(config, {\n auth,\n discovery,\n logger,\n catalog: catalogClient,\n }),\n );\n },\n });\n },\n});\n"],"names":["createBackendModule","coreServices","notificationsProcessingExtensionPoint","catalogClient","CatalogClient","SlackNotificationProcessor"],"mappings":";;;;;;;AA6BO,MAAM,2BAA2BA,oCAAoB,CAAA;AAAA,EAC1D,QAAU,EAAA,eAAA;AAAA,EACV,QAAU,EAAA,OAAA;AAAA,EACV,SAAS,GAAK,EAAA;AACZ,IAAA,GAAA,CAAI,YAAa,CAAA;AAAA,MACf,IAAM,EAAA;AAAA,QACJ,MAAMC,6BAAa,CAAA,IAAA;AAAA,QACnB,QAAQA,6BAAa,CAAA,UAAA;AAAA,QACrB,WAAWA,6BAAa,CAAA,SAAA;AAAA,QACxB,QAAQA,6BAAa,CAAA,MAAA;AAAA,QACrB,aAAe,EAAAC;AAAA,OACjB;AAAA,MACA,MAAM,KAAK,EAAE,IAAA,EAAM,QAAQ,SAAW,EAAA,MAAA,EAAQ,eAAiB,EAAA;AAC7D,QAAM,MAAAC,eAAA,GAAgB,IAAIC,2BAAc,CAAA;AAAA,UACtC,YAAc,EAAA;AAAA,SACf,CAAA;AAED,QAAc,aAAA,CAAA,YAAA;AAAA,UACZC,qDAAA,CAA2B,WAAW,MAAQ,EAAA;AAAA,YAC5C,IAAA;AAAA,YACA,SAAA;AAAA,YACA,MAAA;AAAA,YACA,OAAS,EAAAF;AAAA,WACV;AAAA,SACH;AAAA;AACF,KACD,CAAA;AAAA;AAEL,CAAC;;;;"}
1
+ {"version":3,"file":"module.cjs.js","sources":["../src/module.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 */\nimport {\n coreServices,\n createBackendModule,\n} from '@backstage/backend-plugin-api';\nimport { notificationsProcessingExtensionPoint } from '@backstage/plugin-notifications-node';\nimport { SlackNotificationProcessor } from './lib/SlackNotificationProcessor';\nimport { catalogServiceRef } from '@backstage/plugin-catalog-node';\n\n/**\n * The Slack notification processor for use with the notifications plugin.\n * This allows sending of notifications via Slack DMs or to channels.\n *\n * @public\n */\nexport const notificationsModuleSlack = createBackendModule({\n pluginId: 'notifications',\n moduleId: 'slack',\n register(reg) {\n reg.registerInit({\n deps: {\n auth: coreServices.auth,\n config: coreServices.rootConfig,\n logger: coreServices.logger,\n catalog: catalogServiceRef,\n notifications: notificationsProcessingExtensionPoint,\n },\n async init({ auth, config, logger, catalog, notifications }) {\n notifications.addProcessor(\n SlackNotificationProcessor.fromConfig(config, {\n auth,\n logger,\n catalog,\n }),\n );\n },\n });\n },\n});\n"],"names":["createBackendModule","coreServices","catalogServiceRef","notificationsProcessingExtensionPoint","SlackNotificationProcessor"],"mappings":";;;;;;;AA6BO,MAAM,2BAA2BA,oCAAoB,CAAA;AAAA,EAC1D,QAAU,EAAA,eAAA;AAAA,EACV,QAAU,EAAA,OAAA;AAAA,EACV,SAAS,GAAK,EAAA;AACZ,IAAA,GAAA,CAAI,YAAa,CAAA;AAAA,MACf,IAAM,EAAA;AAAA,QACJ,MAAMC,6BAAa,CAAA,IAAA;AAAA,QACnB,QAAQA,6BAAa,CAAA,UAAA;AAAA,QACrB,QAAQA,6BAAa,CAAA,MAAA;AAAA,QACrB,OAAS,EAAAC,mCAAA;AAAA,QACT,aAAe,EAAAC;AAAA,OACjB;AAAA,MACA,MAAM,KAAK,EAAE,IAAA,EAAM,QAAQ,MAAQ,EAAA,OAAA,EAAS,eAAiB,EAAA;AAC3D,QAAc,aAAA,CAAA,YAAA;AAAA,UACZC,qDAAA,CAA2B,WAAW,MAAQ,EAAA;AAAA,YAC5C,IAAA;AAAA,YACA,MAAA;AAAA,YACA;AAAA,WACD;AAAA,SACH;AAAA;AACF,KACD,CAAA;AAAA;AAEL,CAAC;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/plugin-notifications-backend-module-slack",
3
- "version": "0.1.1-next.2",
3
+ "version": "0.1.1",
4
4
  "description": "The slack backend module for the notifications plugin.",
5
5
  "backstage": {
6
6
  "role": "backend-plugin-module",
@@ -37,14 +37,14 @@
37
37
  "test": "backstage-cli package test"
38
38
  },
39
39
  "dependencies": {
40
- "@backstage/backend-plugin-api": "1.3.1-next.1",
41
- "@backstage/catalog-client": "1.10.0-next.0",
42
- "@backstage/catalog-model": "1.7.3",
43
- "@backstage/config": "1.3.2",
44
- "@backstage/errors": "1.2.7",
45
- "@backstage/plugin-notifications-common": "0.0.8",
46
- "@backstage/plugin-notifications-node": "0.2.15-next.1",
47
- "@backstage/types": "1.2.1",
40
+ "@backstage/backend-plugin-api": "^1.3.1",
41
+ "@backstage/catalog-model": "^1.7.4",
42
+ "@backstage/config": "^1.3.2",
43
+ "@backstage/errors": "^1.2.7",
44
+ "@backstage/plugin-catalog-node": "^1.17.0",
45
+ "@backstage/plugin-notifications-common": "^0.0.8",
46
+ "@backstage/plugin-notifications-node": "^0.2.15",
47
+ "@backstage/types": "^1.2.1",
48
48
  "@opentelemetry/api": "^1.9.0",
49
49
  "@slack/bolt": "^3.21.4",
50
50
  "@slack/types": "^2.14.0",
@@ -53,10 +53,10 @@
53
53
  "p-throttle": "^4.1.1"
54
54
  },
55
55
  "devDependencies": {
56
- "@backstage/backend-test-utils": "1.5.0-next.2",
57
- "@backstage/cli": "0.32.1-next.2",
58
- "@backstage/plugin-catalog-node": "1.17.0-next.1",
59
- "@backstage/test-utils": "1.7.8-next.1",
56
+ "@backstage/backend-test-utils": "^1.5.0",
57
+ "@backstage/cli": "^0.32.1",
58
+ "@backstage/plugin-catalog-node": "^1.17.0",
59
+ "@backstage/test-utils": "^1.7.8",
60
60
  "@faker-js/faker": "^8.4.1",
61
61
  "msw": "^2.0.0"
62
62
  },