Adds username as optional config in order to send with a different username than the Slack App have.
Signed-off-by: Henrik Edegård <henrik.edegard@fortnox.se>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-notifications-backend-module-slack': minor
|
||||
---
|
||||
|
||||
Adds username as optional config in order to send Slack notifications with a specific username in the case when using one Slack App for more than just Backstage.
|
||||
@@ -148,6 +148,7 @@ notifications:
|
||||
- token: xoxb-XXXXXXXXX
|
||||
broadcastChannels: # Optional, if you wish to support broadcast notifications.
|
||||
- C12345678
|
||||
username: 'Backstage Bot' # Optional, defaults to the name of the Slack App.
|
||||
```
|
||||
|
||||
Multiple instances can be added in the `slack` array, allowing you to have multiple configurations if you need to send
|
||||
|
||||
+291
@@ -542,6 +542,297 @@ describe('SlackNotificationProcessor', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('when username is configured', () => {
|
||||
it('should include username in group messages', async () => {
|
||||
const slack = new WebClient();
|
||||
const usernameConfig = mockServices.rootConfig({
|
||||
data: {
|
||||
app: {
|
||||
baseUrl: 'https://example.org',
|
||||
},
|
||||
notifications: {
|
||||
processors: {
|
||||
slack: [
|
||||
{
|
||||
token: 'mock-token',
|
||||
username: 'BackstageBot',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const processor = SlackNotificationProcessor.fromConfig(usernameConfig, {
|
||||
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).toHaveBeenCalledWith({
|
||||
channel: 'C12345678',
|
||||
text: 'notification',
|
||||
username: 'BackstageBot',
|
||||
attachments: [
|
||||
{
|
||||
color: '#00A699',
|
||||
blocks: [
|
||||
{
|
||||
type: 'section',
|
||||
text: {
|
||||
text: 'No description provided',
|
||||
type: 'mrkdwn',
|
||||
},
|
||||
accessory: {
|
||||
type: 'button',
|
||||
text: {
|
||||
type: 'plain_text',
|
||||
text: 'View More',
|
||||
},
|
||||
action_id: 'button-action',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'context',
|
||||
elements: [
|
||||
{
|
||||
type: 'plain_text',
|
||||
text: 'Severity: normal',
|
||||
emoji: true,
|
||||
},
|
||||
{
|
||||
type: 'plain_text',
|
||||
text: 'Topic: N/A',
|
||||
emoji: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
fallback: 'notification',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should include username in direct user messages', async () => {
|
||||
const slack = new WebClient();
|
||||
const usernameConfig = mockServices.rootConfig({
|
||||
data: {
|
||||
app: {
|
||||
baseUrl: 'https://example.org',
|
||||
},
|
||||
notifications: {
|
||||
processors: {
|
||||
slack: [
|
||||
{
|
||||
token: 'mock-token',
|
||||
username: 'BackstageBot',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const processor = SlackNotificationProcessor.fromConfig(usernameConfig, {
|
||||
auth,
|
||||
logger,
|
||||
catalog: catalogServiceMock({
|
||||
entities: DEFAULT_ENTITIES_RESPONSE.items,
|
||||
}),
|
||||
slack,
|
||||
})[0];
|
||||
|
||||
await processor.postProcess(
|
||||
{
|
||||
origin: 'plugin',
|
||||
id: '1234',
|
||||
user: 'user:default/mock',
|
||||
created: new Date(),
|
||||
payload: {
|
||||
title: 'notification',
|
||||
link: '/catalog/user/default/jane.doe',
|
||||
},
|
||||
},
|
||||
{
|
||||
recipients: { type: 'entity', entityRef: 'user:default/mock' },
|
||||
payload: { title: 'notification' },
|
||||
},
|
||||
);
|
||||
|
||||
expect(slack.chat.postMessage).toHaveBeenCalledWith({
|
||||
channel: 'U12345678',
|
||||
text: 'notification',
|
||||
username: 'BackstageBot',
|
||||
attachments: [
|
||||
{
|
||||
color: '#00A699',
|
||||
blocks: [
|
||||
{
|
||||
type: 'section',
|
||||
text: {
|
||||
text: 'No description provided',
|
||||
type: 'mrkdwn',
|
||||
},
|
||||
accessory: {
|
||||
type: 'button',
|
||||
text: {
|
||||
type: 'plain_text',
|
||||
text: 'View More',
|
||||
},
|
||||
action_id: 'button-action',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'context',
|
||||
elements: [
|
||||
{
|
||||
type: 'plain_text',
|
||||
text: 'Severity: normal',
|
||||
emoji: true,
|
||||
},
|
||||
{
|
||||
type: 'plain_text',
|
||||
text: 'Topic: N/A',
|
||||
emoji: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
fallback: 'notification',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should include username in broadcast messages', async () => {
|
||||
const slack = new WebClient();
|
||||
const usernameAndBroadcastConfig = mockServices.rootConfig({
|
||||
data: {
|
||||
app: {
|
||||
baseUrl: 'https://example.org',
|
||||
},
|
||||
notifications: {
|
||||
processors: {
|
||||
slack: [
|
||||
{
|
||||
token: 'mock-token',
|
||||
username: 'BackstageBot',
|
||||
broadcastChannels: ['C12345678'],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const processor = SlackNotificationProcessor.fromConfig(
|
||||
usernameAndBroadcastConfig,
|
||||
{
|
||||
auth,
|
||||
logger,
|
||||
catalog: catalogServiceMock({
|
||||
entities: DEFAULT_ENTITIES_RESPONSE.items,
|
||||
}),
|
||||
slack,
|
||||
},
|
||||
)[0];
|
||||
|
||||
await processor.postProcess(
|
||||
{
|
||||
origin: 'plugin',
|
||||
id: '1234',
|
||||
user: null,
|
||||
created: new Date(),
|
||||
payload: {
|
||||
title: 'notification',
|
||||
link: '/catalog/user/default/jane.doe',
|
||||
},
|
||||
},
|
||||
{
|
||||
recipients: { type: 'broadcast' },
|
||||
payload: { title: 'notification' },
|
||||
},
|
||||
);
|
||||
|
||||
expect(slack.chat.postMessage).toHaveBeenCalledWith({
|
||||
channel: 'C12345678',
|
||||
text: 'notification',
|
||||
username: 'BackstageBot',
|
||||
attachments: [
|
||||
{
|
||||
color: '#00A699',
|
||||
blocks: [
|
||||
{
|
||||
type: 'section',
|
||||
text: {
|
||||
text: 'No description provided',
|
||||
type: 'mrkdwn',
|
||||
},
|
||||
accessory: {
|
||||
type: 'button',
|
||||
text: {
|
||||
type: 'plain_text',
|
||||
text: 'View More',
|
||||
},
|
||||
action_id: 'button-action',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'context',
|
||||
elements: [
|
||||
{
|
||||
type: 'plain_text',
|
||||
text: 'Severity: normal',
|
||||
emoji: true,
|
||||
},
|
||||
{
|
||||
type: 'plain_text',
|
||||
text: 'Topic: N/A',
|
||||
emoji: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
fallback: 'notification',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when username is not configured', () => {
|
||||
it('should not include username in messages', 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' },
|
||||
});
|
||||
|
||||
const calls = (slack.chat.postMessage as jest.Mock).mock.calls;
|
||||
expect(calls).toHaveLength(1);
|
||||
expect(calls[0][0]).not.toHaveProperty('username');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when replacing user entity refs with Slack IDs', () => {
|
||||
const createBaseMessage = (text: string) => ({
|
||||
channel: 'U12345678',
|
||||
|
||||
@@ -49,6 +49,7 @@ export class SlackNotificationProcessor implements NotificationProcessor {
|
||||
private readonly messagesFailed: Counter;
|
||||
private readonly broadcastChannels?: string[];
|
||||
private readonly entityLoader: DataLoader<string, Entity | undefined>;
|
||||
private readonly username?: string;
|
||||
|
||||
static fromConfig(
|
||||
config: Config,
|
||||
@@ -66,9 +67,11 @@ export class SlackNotificationProcessor implements NotificationProcessor {
|
||||
const token = c.getString('token');
|
||||
const slack = options.slack ?? new WebClient(token);
|
||||
const broadcastChannels = c.getOptionalStringArray('broadcastChannels');
|
||||
const username = c.getOptionalString('username');
|
||||
return new SlackNotificationProcessor({
|
||||
slack,
|
||||
broadcastChannels,
|
||||
username,
|
||||
...options,
|
||||
});
|
||||
});
|
||||
@@ -80,13 +83,16 @@ export class SlackNotificationProcessor implements NotificationProcessor {
|
||||
logger: LoggerService;
|
||||
catalog: CatalogService;
|
||||
broadcastChannels?: string[];
|
||||
username?: string;
|
||||
}) {
|
||||
const { auth, catalog, logger, slack, broadcastChannels } = options;
|
||||
const { auth, catalog, logger, slack, broadcastChannels, username } =
|
||||
options;
|
||||
this.logger = logger;
|
||||
this.catalog = catalog;
|
||||
this.auth = auth;
|
||||
this.slack = slack;
|
||||
this.broadcastChannels = broadcastChannels;
|
||||
this.username = username;
|
||||
|
||||
this.entityLoader = new DataLoader<string, Entity | undefined>(
|
||||
async entityRefs => {
|
||||
@@ -206,6 +212,7 @@ export class SlackNotificationProcessor implements NotificationProcessor {
|
||||
const payload = toChatPostMessageArgs({
|
||||
channel,
|
||||
payload: options.payload,
|
||||
...(this.username && { username: this.username }),
|
||||
});
|
||||
|
||||
this.logger.debug(
|
||||
@@ -261,7 +268,11 @@ export class SlackNotificationProcessor implements NotificationProcessor {
|
||||
options.payload,
|
||||
);
|
||||
const outbound = destinations.map(channel =>
|
||||
toChatPostMessageArgs({ channel, payload: formattedPayload }),
|
||||
toChatPostMessageArgs({
|
||||
channel,
|
||||
payload: formattedPayload,
|
||||
...(this.username && { username: this.username }),
|
||||
}),
|
||||
);
|
||||
|
||||
// Log debug info
|
||||
|
||||
@@ -23,12 +23,14 @@ import { ChatPostMessageArguments, KnownBlock } from '@slack/web-api';
|
||||
export function toChatPostMessageArgs(options: {
|
||||
channel: string;
|
||||
payload: NotificationPayload;
|
||||
username?: string;
|
||||
}): ChatPostMessageArguments {
|
||||
const { channel, payload } = options;
|
||||
const { channel, payload, username } = options;
|
||||
|
||||
const args: ChatPostMessageArguments = {
|
||||
channel,
|
||||
text: payload.title,
|
||||
...(username && { username }),
|
||||
attachments: [
|
||||
{
|
||||
color: getColor(payload.severity),
|
||||
|
||||
Reference in New Issue
Block a user