Skip to content

Commit

Permalink
feat(feedback): add support to integrate to backstage notifications (#…
Browse files Browse the repository at this point in the history
…1463)

This adds support to send notification to the entity (as in, its owner)
when a new feedback is given. Requires that notifications service is
installed in the application and enabled in the feedback configuration.

Closes #1460

Signed-off-by: Heikki Hellgren <[email protected]>
  • Loading branch information
drodil authored Oct 3, 2024
1 parent 3306564 commit 639cb05
Show file tree
Hide file tree
Showing 9 changed files with 110 additions and 4 deletions.
5 changes: 5 additions & 0 deletions workspaces/feedback/.changeset/pink-laws-destroy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@backstage-community/plugin-feedback-backend': patch
---

Feedback backend now supports integration to the Backstage notifications system
1 change: 1 addition & 0 deletions workspaces/feedback/plugins/feedback-backend/api-report.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { BackendFeature } from '@backstage/backend-plugin-api';
import { Config } from '@backstage/config';
import express from 'express';
import { LoggerService } from '@backstage/backend-plugin-api';
import { NotificationService } from '@backstage/plugin-notifications-node';
import { PluginEndpointDiscovery } from '@backstage/backend-common';

// Warning: (ae-forgotten-export) The symbol "RouterOptions" needs to be exported by the entry point index.d.ts
Expand Down
4 changes: 4 additions & 0 deletions workspaces/feedback/plugins/feedback-backend/config.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ export interface Config {
*/
caCert?: string;
};
/**
* Integrate to the Backstage notifications service
*/
notifications?: boolean;
};
};
}
1 change: 1 addition & 0 deletions workspaces/feedback/plugins/feedback-backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"@backstage/catalog-client": "^1.7.0",
"@backstage/catalog-model": "^1.7.0",
"@backstage/config": "^1.2.0",
"@backstage/plugin-notifications-node": "^0.2.6",
"@types/express": "*",
"axios": "^1.6.4",
"express": "^4.17.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export const mockConfig = {
token: '###',
},
],
notifications: true,
},
},
};
13 changes: 11 additions & 2 deletions workspaces/feedback/plugins/feedback-backend/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
coreServices,
createBackendPlugin,
} from '@backstage/backend-plugin-api';

import { notificationService } from '@backstage/plugin-notifications-node';
import { createRouter } from './service/router';

export const feedbackPlugin = createBackendPlugin({
Expand All @@ -30,14 +30,23 @@ export const feedbackPlugin = createBackendPlugin({
config: coreServices.rootConfig,
discovery: coreServices.discovery,
auth: coreServices.auth,
notifications: notificationService,
},
async init({ logger, httpRouter, config, discovery, auth }) {
async init({
logger,
httpRouter,
config,
discovery,
auth,
notifications,
}) {
httpRouter.use(
await createRouter({
logger: logger,
config: config,
discovery: discovery,
auth: auth,
notifications,
}),
);
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
mockUser,
} from '../mocks';
import { createRouter } from './router';
import { NotificationService } from '@backstage/plugin-notifications-node';

const handlers = [
rest.get(
Expand Down Expand Up @@ -86,6 +87,9 @@ describe('Router', () => {
mswMockServer.listen({ onUnhandledRequest: 'bypass' });
const config: Config = new ConfigReader(mockConfig);
const discovery: DiscoveryService = HostDiscovery.fromConfig(config);
const notificationsMock: jest.Mocked<NotificationService> = {
send: jest.fn(),
};
const logger: LoggerService = getRootLogger().child({
service: 'feedback-backend',
});
Expand All @@ -98,6 +102,7 @@ describe('Router', () => {
config: config,
discovery: discovery,
auth: auth,
notifications: notificationsMock,
});
app = express().use(router);
});
Expand Down Expand Up @@ -168,6 +173,19 @@ describe('Router', () => {
expect(response.body.message).toEqual('Issue created successfully');
expect(response.body.data.feedbackId).toEqual(mockFeedback.feedbackId);
expect(response.statusCode).toEqual(201);
expect(notificationsMock.send).toHaveBeenCalledWith({
payload: {
description: 'Unit Test Issue',
link: 'http://localhost:3000/catalog/default/Component/example-website/feedback',
severity: 'normal',
title: 'New issue for Example App',
topic: 'feedback-component:default/example-website',
},
recipients: {
entityRef: 'component:default/example-website',
type: 'entity',
},
});
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,20 @@ import { DatabaseFeedbackStore } from '../database/feedbackStore';
import { FeedbackCategory, FeedbackModel } from '../model/feedback.model';
import { NodeMailer } from './emails';

import { NotificationService } from '@backstage/plugin-notifications-node';

export interface RouterOptions {
logger: LoggerService;
config: Config;
discovery: PluginEndpointDiscovery;
auth: AuthService;
notifications?: NotificationService;
}

export async function createRouter(
options: RouterOptions,
): Promise<express.Router> {
const { logger, config, discovery, auth } = options;
const { logger, config, discovery, auth, notifications } = options;
const router = Router();
const feedbackDB = await DatabaseFeedbackStore.create({
database: DatabaseManager.fromConfig(config).forPlugin('feedback'),
Expand All @@ -51,6 +54,10 @@ export async function createRouter(

const mailer = new NodeMailer(config, logger);
const catalogClient = new CatalogClient({ discoveryApi: discovery });
const notificationsEnabled =
(config.getOptionalBoolean('feedback.integrations.notifications') ??
false) &&
notifications !== undefined;

router.use(express.json());
logger.info('Feedback backend plugin is running');
Expand Down Expand Up @@ -119,6 +126,21 @@ export async function createRouter(
data: respObj,
});

if (notificationsEnabled) {
notifications.send({
recipients: { type: 'entity', entityRef: reqData.projectId! },
payload: {
title: `New ${feedbackType.toLocaleLowerCase('en-US')} for ${
entityRef.metadata.title ?? entityRef.metadata.name
}`,
description: reqData.summary,
link: `${entityRoute}`,
severity: 'normal',
topic: `feedback-${reqData.projectId}`,
},
});
}

if (entityRef.metadata.annotations) {
const annotations = entityRef.metadata.annotations;
const type = annotations['feedback/type'];
Expand Down
47 changes: 46 additions & 1 deletion workspaces/feedback/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2533,6 +2533,7 @@ __metadata:
"@backstage/config": ^1.2.0
"@backstage/plugin-auth-backend": ^0.23.0
"@backstage/plugin-auth-backend-module-guest-provider": ^0.2.0
"@backstage/plugin-notifications-node": ^0.2.6
"@janus-idp/cli": 1.13.1
"@types/express": "*"
"@types/nodemailer": 6.4.15
Expand Down Expand Up @@ -3853,6 +3854,33 @@ __metadata:
languageName: node
linkType: hard

"@backstage/plugin-notifications-common@npm:^0.0.5":
version: 0.0.5
resolution: "@backstage/plugin-notifications-common@npm:0.0.5"
dependencies:
"@backstage/config": ^1.2.0
"@material-ui/icons": ^4.9.1
checksum: 3cdfd9565d9bfcb2d7187381e697978833d54ce6633baa4d18494a55761006f7893b73ddfdfc6e0d068fe470559aa2febbd7dac401fbcad2bff9a959f61a71d1
languageName: node
linkType: hard

"@backstage/plugin-notifications-node@npm:^0.2.6":
version: 0.2.6
resolution: "@backstage/plugin-notifications-node@npm:0.2.6"
dependencies:
"@backstage/backend-common": ^0.25.0
"@backstage/backend-plugin-api": ^1.0.0
"@backstage/catalog-client": ^1.7.0
"@backstage/catalog-model": ^1.7.0
"@backstage/plugin-notifications-common": ^0.0.5
"@backstage/plugin-signals-node": ^0.1.11
knex: ^3.0.0
node-fetch: ^2.7.0
uuid: ^9.0.0
checksum: 8dabc31a84bb6aea2e52eea9103e79f545794d01d75bda273117b362a3548cb6813eaca5883389d7943c8f6d01bca0d646af079a3b2903ec89adc631ad51a21b
languageName: node
linkType: hard

"@backstage/plugin-permission-common@npm:^0.8.1":
version: 0.8.1
resolution: "@backstage/plugin-permission-common@npm:0.8.1"
Expand Down Expand Up @@ -3988,6 +4016,23 @@ __metadata:
languageName: node
linkType: hard

"@backstage/plugin-signals-node@npm:^0.1.11":
version: 0.1.11
resolution: "@backstage/plugin-signals-node@npm:0.1.11"
dependencies:
"@backstage/backend-common": ^0.25.0
"@backstage/backend-plugin-api": ^1.0.0
"@backstage/config": ^1.2.0
"@backstage/plugin-auth-node": ^0.5.2
"@backstage/plugin-events-node": ^0.4.0
"@backstage/types": ^1.1.1
express: ^4.17.1
uuid: ^9.0.0
ws: ^8.18.0
checksum: 1c4e012666d40a5fddcc7a88d7b5d776e6aea958b5adafc2ae9619354aa693c63f30e6b52d02d6b418de6bac8462422dff0d8c98ace4996ff84c51093065409c
languageName: node
linkType: hard

"@backstage/release-manifests@npm:^0.0.11":
version: 0.0.11
resolution: "@backstage/release-manifests@npm:0.0.11"
Expand Down Expand Up @@ -28808,7 +28853,7 @@ __metadata:
languageName: node
linkType: hard

"ws@npm:^8.11.0, ws@npm:^8.13.0, ws@npm:^8.16.0":
"ws@npm:^8.11.0, ws@npm:^8.13.0, ws@npm:^8.16.0, ws@npm:^8.18.0":
version: 8.18.0
resolution: "ws@npm:8.18.0"
peerDependencies:
Expand Down

0 comments on commit 639cb05

Please sign in to comment.