feat: allow defining default notification settings via config
Signed-off-by: Hellgren Heikki <heikki.hellgren@op.fi>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-notifications-backend': patch
|
||||
---
|
||||
|
||||
Allow defining default notification settings via configuration
|
||||
+16
@@ -28,5 +28,21 @@ export interface Config {
|
||||
* Throttle duration between notification sending, defaults to 50ms
|
||||
*/
|
||||
throttleInterval?: HumanDuration | string;
|
||||
/**
|
||||
* Default settings for user specific notification settings
|
||||
*/
|
||||
defaultSettings?: {
|
||||
channels?: {
|
||||
id: string;
|
||||
origins?: {
|
||||
id: string;
|
||||
enabled: boolean;
|
||||
topics?: {
|
||||
id: string;
|
||||
enabled: boolean;
|
||||
}[];
|
||||
}[];
|
||||
}[];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -51,7 +51,24 @@ describe.each(databases.eachSupportedId())('createRouter (%s)', databaseId => {
|
||||
|
||||
const auth = mockServices.auth();
|
||||
const config = mockServices.rootConfig({
|
||||
data: { app: { baseUrl: 'http://localhost' } },
|
||||
data: {
|
||||
app: { baseUrl: 'http://localhost' },
|
||||
notifications: {
|
||||
defaultSettings: {
|
||||
channels: [
|
||||
{
|
||||
id: 'Web',
|
||||
origins: [
|
||||
{
|
||||
id: 'external:test-service2',
|
||||
enabled: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const catalog = catalogServiceMock({
|
||||
@@ -309,6 +326,15 @@ describe.each(databases.eachSupportedId())('createRouter (%s)', databaseId => {
|
||||
|
||||
it('should not send to user entity if origin is disabled in settings', async () => {
|
||||
const client = await database.getClient();
|
||||
// Insert a notification with a origin
|
||||
await client('notification').insert({
|
||||
id: uuid(),
|
||||
user: 'user:default/mock',
|
||||
origin: 'external:test-service',
|
||||
title: 'Test notification',
|
||||
created: new Date(),
|
||||
severity: 'normal',
|
||||
});
|
||||
await client('user_settings').insert({
|
||||
settings_key_hash: 'hash',
|
||||
user: 'user:default/mock',
|
||||
@@ -333,11 +359,22 @@ describe.each(databases.eachSupportedId())('createRouter (%s)', databaseId => {
|
||||
const notifications = await client('notification')
|
||||
.where('user', 'user:default/mock')
|
||||
.select();
|
||||
expect(notifications).toHaveLength(0);
|
||||
// This should not create a new notification since the origin is disabled
|
||||
expect(notifications).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should not send to user entity if topic is disabled in settings', async () => {
|
||||
const client = await database.getClient();
|
||||
// Insert a notification with a topic
|
||||
await client('notification').insert({
|
||||
id: uuid(),
|
||||
user: 'user:default/mock',
|
||||
origin: 'external:test-service',
|
||||
topic: 'test-topic',
|
||||
title: 'Test notification',
|
||||
created: new Date(),
|
||||
severity: 'normal',
|
||||
});
|
||||
await client('user_settings').insert({
|
||||
settings_key_hash: 'hash',
|
||||
user: 'user:default/mock',
|
||||
@@ -364,7 +401,8 @@ describe.each(databases.eachSupportedId())('createRouter (%s)', databaseId => {
|
||||
const notifications = await client('notification')
|
||||
.where('user', 'user:default/mock')
|
||||
.select();
|
||||
expect(notifications).toHaveLength(0);
|
||||
// This should not create a new notification since the topic is disabled
|
||||
expect(notifications).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should send to user entity if origin is enabled, but topic is disabled in settings', async () => {
|
||||
@@ -565,9 +603,30 @@ describe.each(databases.eachSupportedId())('createRouter (%s)', databaseId => {
|
||||
jest.resetAllMocks();
|
||||
const client = await database.getClient();
|
||||
await client('user_settings').del();
|
||||
await client('notification').del();
|
||||
|
||||
await client('notification').insert({
|
||||
id: uuid(),
|
||||
user: 'user:default/mock',
|
||||
origin: 'external:test-service',
|
||||
topic: 'test-topic',
|
||||
title: 'Test notification',
|
||||
created: new Date(),
|
||||
severity: 'normal',
|
||||
});
|
||||
|
||||
await client('notification').insert({
|
||||
id: uuid(),
|
||||
user: 'user:default/mock',
|
||||
origin: 'external:test-service2',
|
||||
title: 'Test notification',
|
||||
topic: 'test-topic2',
|
||||
created: new Date(),
|
||||
severity: 'normal',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return user settings', async () => {
|
||||
it('should return origin settings correctly', async () => {
|
||||
const client = await database.getClient();
|
||||
await client('user_settings').insert({
|
||||
settings_key_hash: 'hash',
|
||||
@@ -583,9 +642,76 @@ describe.each(databases.eachSupportedId())('createRouter (%s)', databaseId => {
|
||||
channels: [
|
||||
{
|
||||
id: 'Web',
|
||||
origins: [
|
||||
{ enabled: false, id: 'external:test-service', topics: [] },
|
||||
],
|
||||
origins: expect.arrayContaining([
|
||||
{
|
||||
enabled: false,
|
||||
id: 'external:test-service',
|
||||
topics: [{ enabled: false, id: 'test-topic' }],
|
||||
},
|
||||
{
|
||||
enabled: false,
|
||||
id: 'external:test-service2',
|
||||
topics: [{ enabled: false, id: 'test-topic2' }],
|
||||
},
|
||||
]),
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should return topic settings correctly', async () => {
|
||||
const client = await database.getClient();
|
||||
await client('user_settings').insert({
|
||||
settings_key_hash: 'hash',
|
||||
user: 'user:default/mock',
|
||||
channel: 'Web',
|
||||
origin: 'external:test-service',
|
||||
topic: 'test-topic',
|
||||
enabled: false,
|
||||
});
|
||||
|
||||
const response = await request(app).get('/settings');
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toEqual({
|
||||
channels: [
|
||||
{
|
||||
id: 'Web',
|
||||
origins: expect.arrayContaining([
|
||||
{
|
||||
enabled: true,
|
||||
id: 'external:test-service',
|
||||
topics: [{ enabled: false, id: 'test-topic' }],
|
||||
},
|
||||
{
|
||||
enabled: false,
|
||||
id: 'external:test-service2',
|
||||
topics: [{ enabled: false, id: 'test-topic2' }],
|
||||
},
|
||||
]),
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should return default user settings from config', async () => {
|
||||
const response = await request(app).get('/settings');
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toEqual({
|
||||
channels: [
|
||||
{
|
||||
id: 'Web',
|
||||
origins: expect.arrayContaining([
|
||||
{
|
||||
enabled: true,
|
||||
id: 'external:test-service',
|
||||
topics: [{ enabled: true, id: 'test-topic' }],
|
||||
},
|
||||
{
|
||||
enabled: false,
|
||||
id: 'external:test-service2',
|
||||
topics: [{ enabled: false, id: 'test-topic2' }],
|
||||
},
|
||||
]),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
@@ -99,6 +99,8 @@ export async function createRouter(
|
||||
limit: concurrencyLimit,
|
||||
interval: throttleInterval,
|
||||
});
|
||||
const defaultNotificationSettings: NotificationSettings | undefined =
|
||||
config.getOptional<NotificationSettings>('notifications.defaultSettings');
|
||||
|
||||
const getUser = async (req: Request<unknown>) => {
|
||||
const credentials = await httpAuth.credentials(req, { allow: ['user'] });
|
||||
@@ -106,61 +108,94 @@ export async function createRouter(
|
||||
return info.userEntityRef;
|
||||
};
|
||||
|
||||
const getNotificationChannels = () => {
|
||||
return [WEB_NOTIFICATION_CHANNEL, ...processors.map(p => p.getName())];
|
||||
};
|
||||
|
||||
const getTopicSettings = (
|
||||
topic: any,
|
||||
existingOrigin: OriginSetting | undefined,
|
||||
defaultOriginSettings: OriginSetting | undefined,
|
||||
defaultEnabled: boolean,
|
||||
) => {
|
||||
const existingTopic = existingOrigin?.topics?.find(
|
||||
t => t.id === topic.topic,
|
||||
t => t.id.toLowerCase() === topic.topic.toLowerCase(),
|
||||
);
|
||||
const defaultTopicSettings = defaultOriginSettings?.topics?.find(
|
||||
t => t.id.toLowerCase() === topic.topic.toLowerCase(),
|
||||
);
|
||||
|
||||
return {
|
||||
id: topic.topic,
|
||||
enabled: existingTopic ? existingTopic.enabled : defaultEnabled,
|
||||
enabled: existingTopic
|
||||
? existingTopic.enabled
|
||||
: defaultTopicSettings?.enabled ?? defaultEnabled,
|
||||
};
|
||||
};
|
||||
|
||||
const getOriginSettings = (
|
||||
originId: string,
|
||||
existingChannel: ChannelSetting | undefined,
|
||||
defaultChannelSettings: ChannelSetting | undefined,
|
||||
topics: { origin: string; topic: string }[],
|
||||
) => {
|
||||
const existingOrigin = existingChannel?.origins.find(
|
||||
o => o.id === originId,
|
||||
const existingOrigin = existingChannel?.origins?.find(
|
||||
o => o.id.toLowerCase() === originId.toLowerCase(),
|
||||
);
|
||||
const defaultEnabled = existingOrigin ? existingOrigin.enabled : true;
|
||||
|
||||
const defaultOriginSettings = defaultChannelSettings?.origins?.find(
|
||||
c => c.id.toLowerCase() === originId.toLowerCase(),
|
||||
);
|
||||
|
||||
const defaultEnabled = existingOrigin
|
||||
? existingOrigin.enabled
|
||||
: defaultOriginSettings?.enabled ?? true;
|
||||
|
||||
return {
|
||||
id: originId,
|
||||
enabled: defaultEnabled,
|
||||
topics: topics
|
||||
.filter(t => t.origin === originId)
|
||||
.map(t => getTopicSettings(t, existingOrigin, defaultEnabled)),
|
||||
.map(t =>
|
||||
getTopicSettings(
|
||||
t,
|
||||
existingOrigin,
|
||||
defaultOriginSettings,
|
||||
defaultEnabled,
|
||||
),
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
const getNotificationChannels = () => {
|
||||
return [WEB_NOTIFICATION_CHANNEL, ...processors.map(p => p.getName())];
|
||||
};
|
||||
|
||||
const getChannelSettings = (
|
||||
channelId: string,
|
||||
settings: NotificationSettings,
|
||||
origins: string[],
|
||||
topics: { origin: string; topic: string }[],
|
||||
) => {
|
||||
const existingChannel = settings.channels.find(c => c.id === channelId);
|
||||
if (existingChannel) {
|
||||
return existingChannel;
|
||||
}
|
||||
const existingChannel = settings.channels.find(
|
||||
c => c.id.toLowerCase() === channelId.toLowerCase(),
|
||||
);
|
||||
const defaultChannelSettings = defaultNotificationSettings?.channels?.find(
|
||||
c => c.id.toLowerCase() === channelId.toLowerCase(),
|
||||
);
|
||||
|
||||
return {
|
||||
id: channelId,
|
||||
origins: origins.map(originId =>
|
||||
getOriginSettings(originId, existingChannel, topics),
|
||||
getOriginSettings(
|
||||
originId,
|
||||
existingChannel,
|
||||
defaultChannelSettings,
|
||||
topics,
|
||||
),
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
const getNotificationSettings = async (user: string) => {
|
||||
const getNotificationSettings = async (
|
||||
user: string,
|
||||
): Promise<NotificationSettings> => {
|
||||
const { origins } = await store.getUserNotificationOrigins({ user });
|
||||
const { topics } = await store.getUserNotificationTopics({ user });
|
||||
const settings = await store.getNotificationSettings({ user });
|
||||
|
||||
Reference in New Issue
Block a user