feat(notifications): add support for included topics
this can be used to configure processors to only process notifications that contain specific topics. if notification does not have a topic and included topics are configured, the notification will not get processed by the specific processor. this is mainly for the email processor but can be used by other processors as well. closes #32497 Signed-off-by: Hellgren Heikki <heikki.hellgren@op.fi>
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/plugin-notifications-backend-module-email': patch
|
||||
'@backstage/plugin-notifications-backend': patch
|
||||
'@backstage/plugin-notifications-common': patch
|
||||
---
|
||||
|
||||
Allow configuring included topics for email notifications.
|
||||
+6
-1
@@ -161,9 +161,14 @@ export interface Config {
|
||||
*/
|
||||
maxSeverity?: NotificationSeverity;
|
||||
/**
|
||||
* A notification who's topic is in this array will not be emailed
|
||||
* A notification with topic is in this array will not be emailed
|
||||
*/
|
||||
excludedTopics?: string[];
|
||||
/**
|
||||
* A notification with topic in this array will be emailed. If not defined, only
|
||||
* excludedTopics takes effect.
|
||||
*/
|
||||
includedTopics?: string[];
|
||||
};
|
||||
/**
|
||||
* White list of addresses to send email to
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
TestDatabases,
|
||||
} from '@backstage/backend-test-utils';
|
||||
import {
|
||||
NotificationProcessor,
|
||||
NotificationRecipientResolver,
|
||||
NotificationSendOptions,
|
||||
} from '@backstage/plugin-notifications-node';
|
||||
@@ -827,6 +828,118 @@ describe.each(databases.eachSupportedId())('createRouter (%s)', databaseId => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /notifications with custom processor', () => {
|
||||
const httpAuth = mockServices.httpAuth({
|
||||
defaultCredentials: mockCredentials.service(),
|
||||
});
|
||||
|
||||
const customProcessor: NotificationProcessor = {
|
||||
getName: () => 'customProcessor',
|
||||
processOptions: jest.fn(),
|
||||
preProcess: jest.fn(),
|
||||
postProcess: jest.fn(),
|
||||
getNotificationFilters: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.resetAllMocks();
|
||||
const client = await database.getClient();
|
||||
await client('notification').del();
|
||||
await client('broadcast').del();
|
||||
await client('user_settings').del();
|
||||
|
||||
(customProcessor.processOptions as jest.Mock).mockImplementation(
|
||||
opts => opts,
|
||||
);
|
||||
(customProcessor.preProcess as jest.Mock).mockImplementation(
|
||||
(notification, _options) => notification,
|
||||
);
|
||||
|
||||
const router = await createRouter({
|
||||
logger: mockServices.logger.mock(),
|
||||
store,
|
||||
signals: signalService,
|
||||
userInfo,
|
||||
config,
|
||||
httpAuth,
|
||||
auth,
|
||||
catalog,
|
||||
processors: [customProcessor],
|
||||
});
|
||||
app = express().use(router).use(mockErrorHandler());
|
||||
});
|
||||
|
||||
const sendNotification = async (data: NotificationSendOptions) =>
|
||||
request(app)
|
||||
.post('/notifications')
|
||||
.send(data)
|
||||
.set('Content-Type', 'application/json')
|
||||
.set('Accept', 'application/json');
|
||||
|
||||
it('should not call processor preProcess if topic is excluded', async () => {
|
||||
(customProcessor.getNotificationFilters as jest.Mock).mockReturnValue({
|
||||
excludedTopics: ['topic1'],
|
||||
});
|
||||
// Should be processed
|
||||
await sendNotification({
|
||||
recipients: {
|
||||
type: 'broadcast',
|
||||
},
|
||||
payload: {
|
||||
title: 'test notification',
|
||||
topic: 'topic2',
|
||||
},
|
||||
});
|
||||
// Excluded, should not be processed
|
||||
await sendNotification({
|
||||
recipients: {
|
||||
type: 'broadcast',
|
||||
},
|
||||
payload: {
|
||||
title: 'test notification',
|
||||
topic: 'topic1',
|
||||
},
|
||||
});
|
||||
expect(customProcessor.preProcess).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should not call processor preProcess if topic is not included', async () => {
|
||||
(customProcessor.getNotificationFilters as jest.Mock).mockReturnValue({
|
||||
includedTopics: ['topic1'],
|
||||
});
|
||||
// Should not be processed, not included topic
|
||||
await sendNotification({
|
||||
recipients: {
|
||||
type: 'broadcast',
|
||||
},
|
||||
payload: {
|
||||
title: 'test notification',
|
||||
topic: 'topic2',
|
||||
},
|
||||
});
|
||||
// Should not be processed, no topic
|
||||
await sendNotification({
|
||||
recipients: {
|
||||
type: 'broadcast',
|
||||
},
|
||||
payload: {
|
||||
title: 'test notification',
|
||||
},
|
||||
});
|
||||
// Included, should be processed
|
||||
await sendNotification({
|
||||
recipients: {
|
||||
type: 'broadcast',
|
||||
},
|
||||
payload: {
|
||||
title: 'test notification',
|
||||
topic: 'topic1',
|
||||
},
|
||||
});
|
||||
expect(customProcessor.preProcess).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /', () => {
|
||||
const httpAuth = mockServices.httpAuth({
|
||||
defaultCredentials: mockCredentials.user(),
|
||||
|
||||
@@ -365,6 +365,15 @@ export async function createRouter(
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (filters.includedTopics) {
|
||||
if (
|
||||
!payload.topic ||
|
||||
!filters.includedTopics.includes(payload.topic)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
result.push(processor);
|
||||
}
|
||||
@@ -418,14 +427,16 @@ export async function createRouter(
|
||||
) => {
|
||||
const filtered = await filterProcessors(notification);
|
||||
for (const processor of filtered) {
|
||||
if (processor.postProcess) {
|
||||
try {
|
||||
await processor.postProcess(notification, opts);
|
||||
} catch (e) {
|
||||
logger.error(
|
||||
`Error while post processing notification with ${processor.getName()}: ${e}`,
|
||||
);
|
||||
}
|
||||
if (!processor.postProcess) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
await processor.postProcess(notification, opts);
|
||||
} catch (e) {
|
||||
logger.error(
|
||||
`Error while post processing notification with ${processor.getName()}: ${e}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -64,6 +64,7 @@ export type NotificationProcessorFilters = {
|
||||
minSeverity?: NotificationSeverity;
|
||||
maxSeverity?: NotificationSeverity;
|
||||
excludedTopics?: string[];
|
||||
includedTopics?: string[];
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
|
||||
@@ -43,5 +43,8 @@ export const getProcessorFiltersFromConfig = (config: Config) => {
|
||||
filter.excludedTopics = config.getOptionalStringArray(
|
||||
'filter.excludedTopics',
|
||||
);
|
||||
filter.includedTopics = config.getOptionalStringArray(
|
||||
'filter.includedTopics',
|
||||
);
|
||||
return filter;
|
||||
};
|
||||
|
||||
@@ -130,6 +130,7 @@ export type NotificationProcessorFilters = {
|
||||
minSeverity?: NotificationSeverity;
|
||||
maxSeverity?: NotificationSeverity;
|
||||
excludedTopics?: string[];
|
||||
includedTopics?: string[];
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user