feat: add notifications filtering by processors

Signed-off-by: Yaron Dayagi <ydayagi@redhat.com>
This commit is contained in:
Yaron Dayagi
2024-05-12 19:14:29 +03:00
parent 2c3f493ee3
commit 07a789b8c5
8 changed files with 137 additions and 6 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-notifications-node': major
---
add notifications filtering by processors
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-notifications-backend-module-email': minor
---
add notification filters
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-notifications-backend': major
---
adding filtering of notifications by processors
+15
View File
@@ -15,6 +15,7 @@
*/
import { HumanDuration } from '@backstage/types';
import { NotificationSeverity } from '@backstage/plugin-notifications-common';
export interface Config {
/**
@@ -117,6 +118,20 @@ export interface Config {
*/
ttl?: HumanDuration;
};
filter?: {
/**
* Minimum severity. A notification with lower severity will not be emailed
*/
minSeverity?: NotificationSeverity;
/**
* Maximum severity. A notification with higher severity will not be emailed
*/
maxSeverity?: NotificationSeverity;
/**
* A notification who's topic is in this array will not be emailed
*/
excludedTopics?: string[];
};
};
};
};
@@ -15,6 +15,7 @@
*/
import {
NotificationProcessor,
NotificationProcessorFilters,
NotificationSendOptions,
} from '@backstage/plugin-notifications-node';
import {
@@ -28,7 +29,11 @@ import {
CATALOG_FILTER_EXISTS,
CatalogClient,
} from '@backstage/catalog-client';
import { Notification } from '@backstage/plugin-notifications-common';
import {
Notification,
notificationSeverities,
NotificationSeverity,
} from '@backstage/plugin-notifications-common';
import {
createSendmailTransport,
createSesTransport,
@@ -51,6 +56,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor {
private readonly concurrencyLimit: number;
private readonly throttleInterval: number;
private readonly frontendBaseUrl: string;
private readonly filter: NotificationProcessorFilters;
constructor(
private readonly logger: LoggerService,
@@ -80,6 +86,30 @@ export class NotificationsEmailProcessor implements NotificationProcessor {
? durationToMilliseconds(readDurationFromConfig(cacheConfig))
: 3_600_000;
this.frontendBaseUrl = config.getString('app.baseUrl');
this.filter = {};
const minSeverity = emailProcessorConfig.getOptionalString(
'filter.minSeverity',
) as NotificationSeverity;
if (minSeverity) {
if (notificationSeverities.includes(minSeverity)) {
this.filter.minSeverity = minSeverity;
} else {
throw new Error(`Invalid minSeverity: ${minSeverity}`);
}
}
const maxSeverity = emailProcessorConfig.getOptionalString(
'filter.maxSeverity',
) as NotificationSeverity;
if (maxSeverity) {
if (notificationSeverities.includes(maxSeverity)) {
this.filter.maxSeverity = maxSeverity;
} else {
throw new Error(`Invalid maxSeverity: ${maxSeverity}`);
}
}
this.filter.excludedTopics = emailProcessorConfig.getOptionalStringArray(
'filter.excludedTopics',
);
}
private async getTransporter() {
@@ -312,4 +342,8 @@ export class NotificationsEmailProcessor implements NotificationProcessor {
await this.sendTemplateEmail(notification, emails);
}
getNotificationFilters(): NotificationProcessorFilters {
return this.filter;
}
}
@@ -48,7 +48,9 @@ import { SignalsService } from '@backstage/plugin-signals-node';
import {
NewNotificationSignal,
Notification,
NotificationPayload,
NotificationReadSignal,
notificationSeverities,
NotificationStatus,
} from '@backstage/plugin-notifications-common';
import { parseEntityOrderFieldParams } from './parseEntityOrderFieldParams';
@@ -177,9 +179,46 @@ export async function createRouter(
return users;
};
const processOptions = async (opts: NotificationSendOptions) => {
let ret = opts;
const filterProcessors = (payload: NotificationPayload) => {
const result: NotificationProcessor[] = [];
for (const processor of processors) {
if (processor.getNotificationFilters) {
const filters = processor.getNotificationFilters();
if (filters.minSeverity) {
if (
notificationSeverities.indexOf(payload.severity ?? 'normal') >
notificationSeverities.indexOf(filters.minSeverity)
) {
continue;
}
}
if (filters.maxSeverity) {
if (
notificationSeverities.indexOf(payload.severity ?? 'normal') <
notificationSeverities.indexOf(filters.maxSeverity)
) {
continue;
}
}
if (filters.excludedTopics && payload.topic) {
if (filters.excludedTopics.includes(payload.topic)) {
continue;
}
}
}
result.push(processor);
}
return result;
};
const processOptions = async (opts: NotificationSendOptions) => {
const filtered = filterProcessors(opts.payload);
let ret = opts;
for (const processor of filtered) {
try {
ret = processor.processOptions
? await processor.processOptions(ret)
@@ -197,8 +236,9 @@ export async function createRouter(
notification: Notification,
opts: NotificationSendOptions,
) => {
const filtered = filterProcessors(notification.payload);
let ret = notification;
for (const processor of processors) {
for (const processor of filtered) {
try {
ret = processor.preProcess
? await processor.preProcess(ret, opts)
@@ -216,7 +256,8 @@ export async function createRouter(
notification: Notification,
opts: NotificationSendOptions,
) => {
for (const processor of processors) {
const filtered = filterProcessors(notification.payload);
for (const processor of filtered) {
if (processor.postProcess) {
try {
await processor.postProcess(notification, opts);
+9
View File
@@ -8,6 +8,7 @@ import { DiscoveryService } from '@backstage/backend-plugin-api';
import { ExtensionPoint } from '@backstage/backend-plugin-api';
import { Notification as Notification_2 } from '@backstage/plugin-notifications-common';
import { NotificationPayload } from '@backstage/plugin-notifications-common';
import { NotificationSeverity } from '@backstage/plugin-notifications-common';
import { ServiceRef } from '@backstage/backend-plugin-api';
// @public (undocumented)
@@ -23,6 +24,7 @@ export class DefaultNotificationService implements NotificationService {
// @public
export interface NotificationProcessor {
getName(): string;
getNotificationFilters?(): NotificationProcessorFilters;
postProcess?(
notification: Notification_2,
options: NotificationSendOptions,
@@ -36,6 +38,13 @@ export interface NotificationProcessor {
): Promise<NotificationSendOptions>;
}
// @public (undocumented)
export type NotificationProcessorFilters = {
minSeverity?: NotificationSeverity;
maxSeverity?: NotificationSeverity;
excludedTopics?: string[];
};
// @public (undocumented)
export type NotificationRecipients =
| {
+18 -1
View File
@@ -14,7 +14,10 @@
* limitations under the License.
*/
import { createExtensionPoint } from '@backstage/backend-plugin-api';
import { Notification } from '@backstage/plugin-notifications-common';
import {
Notification,
NotificationSeverity,
} from '@backstage/plugin-notifications-common';
import { NotificationSendOptions } from './service';
/**
@@ -90,6 +93,11 @@ export interface NotificationProcessor {
notification: Notification,
options: NotificationSendOptions,
): Promise<void>;
/**
* notification filters are used to call the processor only in certain conditions
*/
getNotificationFilters?(): NotificationProcessorFilters;
}
/**
@@ -108,3 +116,12 @@ export const notificationsProcessingExtensionPoint =
createExtensionPoint<NotificationsProcessingExtensionPoint>({
id: 'notifications.processing',
});
/**
* @public
*/
export type NotificationProcessorFilters = {
minSeverity?: NotificationSeverity;
maxSeverity?: NotificationSeverity;
excludedTopics?: string[];
};