feat(events)!: migrate EventRouter implementations from EventBroker to EventsService

Signed-off-by: Patrick Jungermann <Patrick.Jungermann@gmail.com>
This commit is contained in:
Patrick Jungermann
2024-01-23 19:20:26 +01:00
parent 56969b6e55
commit eff3ca9ddd
37 changed files with 429 additions and 288 deletions
+76
View File
@@ -0,0 +1,76 @@
---
'@backstage/plugin-events-backend-module-bitbucket-cloud': minor
'@backstage/plugin-events-backend-module-gerrit': minor
'@backstage/plugin-events-backend-module-github': minor
'@backstage/plugin-events-backend-module-gitlab': minor
'@backstage/plugin-events-backend-module-azure': minor
'@backstage/plugin-events-node': minor
---
BREAKING CHANGE: Migrate `EventRouter` implementations from `EventBroker` to `EventsService`.
`EventRouter` uses the new `EventsService` instead of the `EventBroker` now,
causing a breaking change to its signature.
All of its extensions and implementations got adjusted accordingly.
(`SubTopicEventRouter`, `AzureDevOpsEventRouter`, `BitbucketCloudEventRouter`,
`GerritEventRouter`, `GithubEventRouter`, `GitlabEventRouter`)
Required adjustments were made to all backend modules for the new backend system,
now also making use of the `eventsServiceRef` instead of the `eventsExtensionPoint`.
**Migration:**
Example for implementations of `SubTopicEventRouter`:
```diff
import {
EventParams,
+ EventsService,
SubTopicEventRouter,
} from '@backstage/plugin-events-node';
export class GithubEventRouter extends SubTopicEventRouter {
- constructor() {
- super('github');
+ constructor(options: { events: EventsService }) {
+ super({
+ events: options.events,
+ topic: 'github',
+ });
}
+ protected getSubscriberId(): string {
+ return 'GithubEventRouter';
+ }
+
// ...
}
```
Example for a direct extension of `EventRouter`:
```diff
class MyEventRouter extends EventRouter {
- constructor(/* ... */) {
+ constructor(options: {
+ events: EventsService;
+ // ...
+ }) {
- super();
// ...
+ super({
+ events: options.events,
+ topics: topics,
+ });
}
+
+ protected getSubscriberId(): string {
+ return 'MyEventRouter';
+ }
-
- supportsEventTopics(): string[] {
- return this.topics;
- }
}
```
@@ -4,12 +4,15 @@
```ts
import { EventParams } from '@backstage/plugin-events-node';
import { EventsService } from '@backstage/plugin-events-node';
import { SubTopicEventRouter } from '@backstage/plugin-events-node';
// @public
export class AzureDevOpsEventRouter extends SubTopicEventRouter {
constructor();
constructor(options: { events: EventsService });
// (undocumented)
protected determineSubTopic(params: EventParams): string | undefined;
// (undocumented)
protected getSubscriberId(): string;
}
```
@@ -42,8 +42,7 @@
},
"dependencies": {
"@backstage/backend-plugin-api": "workspace:^",
"@backstage/plugin-events-node": "workspace:^",
"winston": "^3.2.1"
"@backstage/plugin-events-node": "workspace:^"
},
"devDependencies": {
"@backstage/backend-test-utils": "workspace:^",
@@ -14,37 +14,44 @@
* limitations under the License.
*/
import { TestEventBroker } from '@backstage/plugin-events-backend-test-utils';
import { TestEventsService } from '@backstage/plugin-events-backend-test-utils';
import { AzureDevOpsEventRouter } from './AzureDevOpsEventRouter';
describe('AzureDevOpsEventRouter', () => {
const eventRouter = new AzureDevOpsEventRouter();
const events = new TestEventsService();
const eventRouter = new AzureDevOpsEventRouter({ events: events });
const topic = 'azureDevOps';
const eventPayload = { eventType: 'test.type', test: 'payload' };
const metadata = {};
it('no $.eventType', () => {
const eventBroker = new TestEventBroker();
eventRouter.setEventBroker(eventBroker);
beforeEach(() => {
events.reset();
});
it('subscribed to topic', () => {
eventRouter.subscribe();
expect(events.subscribed).toHaveLength(1);
expect(events.subscribed[0].id).toEqual('AzureDevOpsEventRouter');
expect(events.subscribed[0].topics).toEqual([topic]);
});
it('no $.eventType', () => {
eventRouter.onEvent({
topic,
eventPayload: { invalid: 'payload' },
metadata,
});
expect(eventBroker.published).toEqual([]);
expect(events.published).toEqual([]);
});
it('with $.eventType', () => {
const eventBroker = new TestEventBroker();
eventRouter.setEventBroker(eventBroker);
eventRouter.onEvent({ topic, eventPayload, metadata });
expect(eventBroker.published.length).toBe(1);
expect(eventBroker.published[0].topic).toEqual('azureDevOps.test.type');
expect(eventBroker.published[0].eventPayload).toEqual(eventPayload);
expect(eventBroker.published[0].metadata).toEqual(metadata);
expect(events.published).toHaveLength(1);
expect(events.published[0].topic).toEqual('azureDevOps.test.type');
expect(events.published[0].eventPayload).toEqual(eventPayload);
expect(events.published[0].metadata).toEqual(metadata);
});
});
@@ -16,6 +16,7 @@
import {
EventParams,
EventsService,
SubTopicEventRouter,
} from '@backstage/plugin-events-node';
@@ -27,8 +28,15 @@ import {
* @public
*/
export class AzureDevOpsEventRouter extends SubTopicEventRouter {
constructor() {
super('azureDevOps');
constructor(options: { events: EventsService }) {
super({
events: options.events,
topic: 'azureDevOps',
});
}
protected getSubscriberId(): string {
return 'AzureDevOpsEventRouter';
}
protected determineSubTopic(params: EventParams): string | undefined {
@@ -14,32 +14,28 @@
* limitations under the License.
*/
import { createServiceFactory } from '@backstage/backend-plugin-api';
import { startTestBackend } from '@backstage/backend-test-utils';
import { eventsExtensionPoint } from '@backstage/plugin-events-node/alpha';
import { TestEventsService } from '@backstage/plugin-events-backend-test-utils';
import { eventsServiceRef } from '@backstage/plugin-events-node';
import { eventsModuleAzureDevOpsEventRouter } from './eventsModuleAzureDevOpsEventRouter';
import { AzureDevOpsEventRouter } from '../router/AzureDevOpsEventRouter';
describe('eventsModuleAzureDevOpsEventRouter', () => {
it('should be correctly wired and set up', async () => {
let addedPublisher: AzureDevOpsEventRouter | undefined;
let addedSubscriber: AzureDevOpsEventRouter | undefined;
const extensionPoint = {
addPublishers: (publisher: any) => {
addedPublisher = publisher;
const events = new TestEventsService();
const eventsServiceFactory = createServiceFactory({
service: eventsServiceRef,
deps: {},
async factory({}) {
return events;
},
addSubscribers: (subscriber: any) => {
addedSubscriber = subscriber;
},
};
await startTestBackend({
extensionPoints: [[eventsExtensionPoint, extensionPoint]],
features: [eventsModuleAzureDevOpsEventRouter()],
});
expect(addedPublisher).not.toBeUndefined();
expect(addedPublisher).toBeInstanceOf(AzureDevOpsEventRouter);
expect(addedSubscriber).not.toBeUndefined();
expect(addedSubscriber).toBeInstanceOf(AzureDevOpsEventRouter);
await startTestBackend({
features: [eventsServiceFactory(), eventsModuleAzureDevOpsEventRouter()],
});
expect(events.subscribed).toHaveLength(1);
expect(events.subscribed[0].id).toEqual('AzureDevOpsEventRouter');
});
});
@@ -15,7 +15,7 @@
*/
import { createBackendModule } from '@backstage/backend-plugin-api';
import { eventsExtensionPoint } from '@backstage/plugin-events-node/alpha';
import { eventsServiceRef } from '@backstage/plugin-events-node';
import { AzureDevOpsEventRouter } from '../router/AzureDevOpsEventRouter';
/**
@@ -31,13 +31,13 @@ export const eventsModuleAzureDevOpsEventRouter = createBackendModule({
register(env) {
env.registerInit({
deps: {
events: eventsExtensionPoint,
events: eventsServiceRef,
},
async init({ events }) {
const eventRouter = new AzureDevOpsEventRouter();
events.addPublishers(eventRouter);
events.addSubscribers(eventRouter);
const eventRouter = new AzureDevOpsEventRouter({
events,
});
await eventRouter.subscribe();
},
});
},
@@ -4,12 +4,15 @@
```ts
import { EventParams } from '@backstage/plugin-events-node';
import { EventsService } from '@backstage/plugin-events-node';
import { SubTopicEventRouter } from '@backstage/plugin-events-node';
// @public
export class BitbucketCloudEventRouter extends SubTopicEventRouter {
constructor();
constructor(options: { events: EventsService });
// (undocumented)
protected determineSubTopic(params: EventParams): string | undefined;
// (undocumented)
protected getSubscriberId(): string;
}
```
@@ -42,8 +42,7 @@
},
"dependencies": {
"@backstage/backend-plugin-api": "workspace:^",
"@backstage/plugin-events-node": "workspace:^",
"winston": "^3.2.1"
"@backstage/plugin-events-node": "workspace:^"
},
"devDependencies": {
"@backstage/backend-test-utils": "workspace:^",
@@ -14,33 +14,40 @@
* limitations under the License.
*/
import { TestEventBroker } from '@backstage/plugin-events-backend-test-utils';
import { TestEventsService } from '@backstage/plugin-events-backend-test-utils';
import { BitbucketCloudEventRouter } from './BitbucketCloudEventRouter';
describe('BitbucketCloudEventRouter', () => {
const eventRouter = new BitbucketCloudEventRouter();
const events = new TestEventsService();
const eventRouter = new BitbucketCloudEventRouter({ events });
const topic = 'bitbucketCloud';
const eventPayload = { test: 'payload' };
const metadata = { 'x-event-key': 'test:type' };
it('no x-event-key', () => {
const eventBroker = new TestEventBroker();
eventRouter.setEventBroker(eventBroker);
beforeEach(() => {
events.reset();
});
it('subscribed to topic', () => {
eventRouter.subscribe();
expect(events.subscribed).toHaveLength(1);
expect(events.subscribed[0].id).toEqual('BitbucketCloudEventRouter');
expect(events.subscribed[0].topics).toEqual([topic]);
});
it('no x-event-key', () => {
eventRouter.onEvent({ topic, eventPayload });
expect(eventBroker.published).toEqual([]);
expect(events.published).toEqual([]);
});
it('with x-event-key', () => {
const eventBroker = new TestEventBroker();
eventRouter.setEventBroker(eventBroker);
eventRouter.onEvent({ topic, eventPayload, metadata });
expect(eventBroker.published.length).toBe(1);
expect(eventBroker.published[0].topic).toEqual('bitbucketCloud.test:type');
expect(eventBroker.published[0].eventPayload).toEqual(eventPayload);
expect(eventBroker.published[0].metadata).toEqual(metadata);
expect(events.published.length).toBe(1);
expect(events.published[0].topic).toEqual('bitbucketCloud.test:type');
expect(events.published[0].eventPayload).toEqual(eventPayload);
expect(events.published[0].metadata).toEqual(metadata);
});
});
@@ -16,6 +16,7 @@
import {
EventParams,
EventsService,
SubTopicEventRouter,
} from '@backstage/plugin-events-node';
@@ -27,8 +28,15 @@ import {
* @public
*/
export class BitbucketCloudEventRouter extends SubTopicEventRouter {
constructor() {
super('bitbucketCloud');
constructor(options: { events: EventsService }) {
super({
events: options.events,
topic: 'bitbucketCloud',
});
}
protected getSubscriberId(): string {
return 'BitbucketCloudEventRouter';
}
protected determineSubTopic(params: EventParams): string | undefined {
@@ -14,32 +14,31 @@
* limitations under the License.
*/
import { createServiceFactory } from '@backstage/backend-plugin-api';
import { startTestBackend } from '@backstage/backend-test-utils';
import { eventsExtensionPoint } from '@backstage/plugin-events-node/alpha';
import { TestEventsService } from '@backstage/plugin-events-backend-test-utils';
import { eventsServiceRef } from '@backstage/plugin-events-node';
import { eventsModuleBitbucketCloudEventRouter } from './eventsModuleBitbucketCloudEventRouter';
import { BitbucketCloudEventRouter } from '../router/BitbucketCloudEventRouter';
describe('eventsModuleBitbucketCloudEventRouter', () => {
it('should be correctly wired and set up', async () => {
let addedPublisher: BitbucketCloudEventRouter | undefined;
let addedSubscriber: BitbucketCloudEventRouter | undefined;
const extensionPoint = {
addPublishers: (publisher: any) => {
addedPublisher = publisher;
const events = new TestEventsService();
const eventsServiceFactory = createServiceFactory({
service: eventsServiceRef,
deps: {},
async factory({}) {
return events;
},
addSubscribers: (subscriber: any) => {
addedSubscriber = subscriber;
},
};
await startTestBackend({
extensionPoints: [[eventsExtensionPoint, extensionPoint]],
features: [eventsModuleBitbucketCloudEventRouter()],
});
expect(addedPublisher).not.toBeUndefined();
expect(addedPublisher).toBeInstanceOf(BitbucketCloudEventRouter);
expect(addedSubscriber).not.toBeUndefined();
expect(addedSubscriber).toBeInstanceOf(BitbucketCloudEventRouter);
await startTestBackend({
features: [
eventsServiceFactory(),
eventsModuleBitbucketCloudEventRouter(),
],
});
expect(events.subscribed).toHaveLength(1);
expect(events.subscribed[0].id).toEqual('BitbucketCloudEventRouter');
});
});
@@ -15,7 +15,7 @@
*/
import { createBackendModule } from '@backstage/backend-plugin-api';
import { eventsExtensionPoint } from '@backstage/plugin-events-node/alpha';
import { eventsServiceRef } from '@backstage/plugin-events-node';
import { BitbucketCloudEventRouter } from '../router/BitbucketCloudEventRouter';
/**
@@ -31,13 +31,13 @@ export const eventsModuleBitbucketCloudEventRouter = createBackendModule({
register(env) {
env.registerInit({
deps: {
events: eventsExtensionPoint,
events: eventsServiceRef,
},
async init({ events }) {
const eventRouter = new BitbucketCloudEventRouter();
events.addPublishers(eventRouter);
events.addSubscribers(eventRouter);
const eventRouter = new BitbucketCloudEventRouter({
events,
});
await eventRouter.subscribe();
},
});
},
@@ -4,12 +4,15 @@
```ts
import { EventParams } from '@backstage/plugin-events-node';
import { EventsService } from '@backstage/plugin-events-node';
import { SubTopicEventRouter } from '@backstage/plugin-events-node';
// @public
export class GerritEventRouter extends SubTopicEventRouter {
constructor();
constructor(options: { events: EventsService });
// (undocumented)
protected determineSubTopic(params: EventParams): string | undefined;
// (undocumented)
protected getSubscriberId(): string;
}
```
@@ -42,8 +42,7 @@
},
"dependencies": {
"@backstage/backend-plugin-api": "workspace:^",
"@backstage/plugin-events-node": "workspace:^",
"winston": "^3.2.1"
"@backstage/plugin-events-node": "workspace:^"
},
"devDependencies": {
"@backstage/backend-test-utils": "workspace:^",
@@ -14,37 +14,44 @@
* limitations under the License.
*/
import { TestEventBroker } from '@backstage/plugin-events-backend-test-utils';
import { TestEventsService } from '@backstage/plugin-events-backend-test-utils';
import { GerritEventRouter } from './GerritEventRouter';
describe('GerritEventRouter', () => {
const eventRouter = new GerritEventRouter();
const events = new TestEventsService();
const eventRouter = new GerritEventRouter({ events: events });
const topic = 'gerrit';
const eventPayload = { type: 'test-type', test: 'payload' };
const metadata = {};
it('no $.type', () => {
const eventBroker = new TestEventBroker();
eventRouter.setEventBroker(eventBroker);
beforeEach(() => {
events.reset();
});
it('subscribed to topic', () => {
eventRouter.subscribe();
expect(events.subscribed).toHaveLength(1);
expect(events.subscribed[0].id).toEqual('GerritEventRouter');
expect(events.subscribed[0].topics).toEqual([topic]);
});
it('no $.type', () => {
eventRouter.onEvent({
topic,
eventPayload: { invalid: 'payload' },
metadata,
});
expect(eventBroker.published).toEqual([]);
expect(events.published).toEqual([]);
});
it('with $.type', () => {
const eventBroker = new TestEventBroker();
eventRouter.setEventBroker(eventBroker);
eventRouter.onEvent({ topic, eventPayload, metadata });
expect(eventBroker.published.length).toBe(1);
expect(eventBroker.published[0].topic).toEqual('gerrit.test-type');
expect(eventBroker.published[0].eventPayload).toEqual(eventPayload);
expect(eventBroker.published[0].metadata).toEqual(metadata);
expect(events.published.length).toBe(1);
expect(events.published[0].topic).toEqual('gerrit.test-type');
expect(events.published[0].eventPayload).toEqual(eventPayload);
expect(events.published[0].metadata).toEqual(metadata);
});
});
@@ -16,6 +16,7 @@
import {
EventParams,
EventsService,
SubTopicEventRouter,
} from '@backstage/plugin-events-node';
@@ -27,8 +28,15 @@ import {
* @public
*/
export class GerritEventRouter extends SubTopicEventRouter {
constructor() {
super('gerrit');
constructor(options: { events: EventsService }) {
super({
events: options.events,
topic: 'gerrit',
});
}
protected getSubscriberId(): string {
return 'GerritEventRouter';
}
protected determineSubTopic(params: EventParams): string | undefined {
@@ -14,32 +14,28 @@
* limitations under the License.
*/
import { createServiceFactory } from '@backstage/backend-plugin-api';
import { startTestBackend } from '@backstage/backend-test-utils';
import { eventsExtensionPoint } from '@backstage/plugin-events-node/alpha';
import { eventsServiceRef } from '@backstage/plugin-events-node';
import { TestEventsService } from '@backstage/plugin-events-backend-test-utils';
import { eventsModuleGerritEventRouter } from './eventsModuleGerritEventRouter';
import { GerritEventRouter } from '../router/GerritEventRouter';
describe('eventsModuleGerritEventRouter', () => {
it('should be correctly wired and set up', async () => {
let addedPublisher: GerritEventRouter | undefined;
let addedSubscriber: GerritEventRouter | undefined;
const extensionPoint = {
addPublishers: (publisher: any) => {
addedPublisher = publisher;
const events = new TestEventsService();
const eventsServiceFactory = createServiceFactory({
service: eventsServiceRef,
deps: {},
async factory({}) {
return events;
},
addSubscribers: (subscriber: any) => {
addedSubscriber = subscriber;
},
};
await startTestBackend({
extensionPoints: [[eventsExtensionPoint, extensionPoint]],
features: [eventsModuleGerritEventRouter()],
});
expect(addedPublisher).not.toBeUndefined();
expect(addedPublisher).toBeInstanceOf(GerritEventRouter);
expect(addedSubscriber).not.toBeUndefined();
expect(addedSubscriber).toBeInstanceOf(GerritEventRouter);
await startTestBackend({
features: [eventsServiceFactory(), eventsModuleGerritEventRouter()],
});
expect(events.subscribed).toHaveLength(1);
expect(events.subscribed[0].id).toEqual('GerritEventRouter');
});
});
@@ -15,7 +15,7 @@
*/
import { createBackendModule } from '@backstage/backend-plugin-api';
import { eventsExtensionPoint } from '@backstage/plugin-events-node/alpha';
import { eventsServiceRef } from '@backstage/plugin-events-node';
import { GerritEventRouter } from '../router/GerritEventRouter';
/**
@@ -31,13 +31,11 @@ export const eventsModuleGerritEventRouter = createBackendModule({
register(env) {
env.registerInit({
deps: {
events: eventsExtensionPoint,
events: eventsServiceRef,
},
async init({ events }) {
const eventRouter = new GerritEventRouter();
events.addPublishers(eventRouter);
events.addSubscribers(eventRouter);
const eventRouter = new GerritEventRouter({ events });
await eventRouter.subscribe();
},
});
},
@@ -5,6 +5,7 @@
```ts
import { Config } from '@backstage/config';
import { EventParams } from '@backstage/plugin-events-node';
import { EventsService } from '@backstage/plugin-events-node';
import { RequestValidator } from '@backstage/plugin-events-node';
import { SubTopicEventRouter } from '@backstage/plugin-events-node';
@@ -15,8 +16,10 @@ export function createGithubSignatureValidator(
// @public
export class GithubEventRouter extends SubTopicEventRouter {
constructor();
constructor(options: { events: EventsService });
// (undocumented)
protected determineSubTopic(params: EventParams): string | undefined;
// (undocumented)
protected getSubscriberId(): string;
}
```
@@ -44,8 +44,7 @@
"@backstage/backend-plugin-api": "workspace:^",
"@backstage/config": "workspace:^",
"@backstage/plugin-events-node": "workspace:^",
"@octokit/webhooks-methods": "^3.0.0",
"winston": "^3.2.1"
"@octokit/webhooks-methods": "^3.0.0"
},
"devDependencies": {
"@backstage/backend-test-utils": "workspace:^",
@@ -14,33 +14,40 @@
* limitations under the License.
*/
import { TestEventBroker } from '@backstage/plugin-events-backend-test-utils';
import { TestEventsService } from '@backstage/plugin-events-backend-test-utils';
import { GithubEventRouter } from './GithubEventRouter';
describe('GithubEventRouter', () => {
const eventRouter = new GithubEventRouter();
const events = new TestEventsService();
const eventRouter = new GithubEventRouter({ events: events });
const topic = 'github';
const eventPayload = { test: 'payload' };
const metadata = { 'x-github-event': 'test_type' };
it('no x-github-event', () => {
const eventBroker = new TestEventBroker();
eventRouter.setEventBroker(eventBroker);
beforeEach(() => {
events.reset();
});
it('subscribed to topic', () => {
eventRouter.subscribe();
expect(events.subscribed).toHaveLength(1);
expect(events.subscribed[0].id).toEqual('GithubEventRouter');
expect(events.subscribed[0].topics).toEqual([topic]);
});
it('no x-github-event', () => {
eventRouter.onEvent({ topic, eventPayload });
expect(eventBroker.published).toEqual([]);
expect(events.published).toEqual([]);
});
it('with x-github-event', () => {
const eventBroker = new TestEventBroker();
eventRouter.setEventBroker(eventBroker);
eventRouter.onEvent({ topic, eventPayload, metadata });
expect(eventBroker.published.length).toBe(1);
expect(eventBroker.published[0].topic).toEqual('github.test_type');
expect(eventBroker.published[0].eventPayload).toEqual(eventPayload);
expect(eventBroker.published[0].metadata).toEqual(metadata);
expect(events.published.length).toBe(1);
expect(events.published[0].topic).toEqual('github.test_type');
expect(events.published[0].eventPayload).toEqual(eventPayload);
expect(events.published[0].metadata).toEqual(metadata);
});
});
@@ -16,6 +16,7 @@
import {
EventParams,
EventsService,
SubTopicEventRouter,
} from '@backstage/plugin-events-node';
@@ -27,8 +28,15 @@ import {
* @public
*/
export class GithubEventRouter extends SubTopicEventRouter {
constructor() {
super('github');
constructor(options: { events: EventsService }) {
super({
events: options.events,
topic: 'github',
});
}
protected getSubscriberId(): string {
return 'GithubEventRouter';
}
protected determineSubTopic(params: EventParams): string | undefined {
@@ -14,32 +14,28 @@
* limitations under the License.
*/
import { createServiceFactory } from '@backstage/backend-plugin-api';
import { startTestBackend } from '@backstage/backend-test-utils';
import { eventsExtensionPoint } from '@backstage/plugin-events-node/alpha';
import { TestEventsService } from '@backstage/plugin-events-backend-test-utils';
import { eventsServiceRef } from '@backstage/plugin-events-node';
import { eventsModuleGithubEventRouter } from './eventsModuleGithubEventRouter';
import { GithubEventRouter } from '../router/GithubEventRouter';
describe('eventsModuleGithubEventRouter', () => {
it('should be correctly wired and set up', async () => {
let addedPublisher: GithubEventRouter | undefined;
let addedSubscriber: GithubEventRouter | undefined;
const extensionPoint = {
addPublishers: (publisher: any) => {
addedPublisher = publisher;
const events = new TestEventsService();
const eventsServiceFactory = createServiceFactory({
service: eventsServiceRef,
deps: {},
async factory({}) {
return events;
},
addSubscribers: (subscriber: any) => {
addedSubscriber = subscriber;
},
};
await startTestBackend({
extensionPoints: [[eventsExtensionPoint, extensionPoint]],
features: [eventsModuleGithubEventRouter()],
});
expect(addedPublisher).not.toBeUndefined();
expect(addedPublisher).toBeInstanceOf(GithubEventRouter);
expect(addedSubscriber).not.toBeUndefined();
expect(addedSubscriber).toBeInstanceOf(GithubEventRouter);
await startTestBackend({
features: [eventsServiceFactory(), eventsModuleGithubEventRouter()],
});
expect(events.subscribed).toHaveLength(1);
expect(events.subscribed[0].id).toEqual('GithubEventRouter');
});
});
@@ -15,7 +15,7 @@
*/
import { createBackendModule } from '@backstage/backend-plugin-api';
import { eventsExtensionPoint } from '@backstage/plugin-events-node/alpha';
import { eventsServiceRef } from '@backstage/plugin-events-node';
import { GithubEventRouter } from '../router/GithubEventRouter';
/**
@@ -31,13 +31,11 @@ export const eventsModuleGithubEventRouter = createBackendModule({
register(env) {
env.registerInit({
deps: {
events: eventsExtensionPoint,
events: eventsServiceRef,
},
async init({ events }) {
const eventRouter = new GithubEventRouter();
events.addPublishers(eventRouter);
events.addSubscribers(eventRouter);
const eventRouter = new GithubEventRouter({ events });
await eventRouter.subscribe();
},
});
},
@@ -5,6 +5,7 @@
```ts
import { Config } from '@backstage/config';
import { EventParams } from '@backstage/plugin-events-node';
import { EventsService } from '@backstage/plugin-events-node';
import { RequestValidator } from '@backstage/plugin-events-node';
import { SubTopicEventRouter } from '@backstage/plugin-events-node';
@@ -13,8 +14,10 @@ export function createGitlabTokenValidator(config: Config): RequestValidator;
// @public
export class GitlabEventRouter extends SubTopicEventRouter {
constructor();
constructor(options: { events: EventsService });
// (undocumented)
protected determineSubTopic(params: EventParams): string | undefined;
// (undocumented)
protected getSubscriberId(): string;
}
```
@@ -43,8 +43,7 @@
"dependencies": {
"@backstage/backend-plugin-api": "workspace:^",
"@backstage/config": "workspace:^",
"@backstage/plugin-events-node": "workspace:^",
"winston": "^3.2.1"
"@backstage/plugin-events-node": "workspace:^"
},
"devDependencies": {
"@backstage/backend-test-utils": "workspace:^",
@@ -14,37 +14,44 @@
* limitations under the License.
*/
import { TestEventBroker } from '@backstage/plugin-events-backend-test-utils';
import { TestEventsService } from '@backstage/plugin-events-backend-test-utils';
import { GitlabEventRouter } from './GitlabEventRouter';
describe('GitlabEventRouter', () => {
const eventRouter = new GitlabEventRouter();
const events = new TestEventsService();
const eventRouter = new GitlabEventRouter({ events: events });
const topic = 'gitlab';
const eventPayload = { event_name: 'test_type', test: 'payload' };
const metadata = {};
it('no $.event_name', () => {
const eventBroker = new TestEventBroker();
eventRouter.setEventBroker(eventBroker);
beforeEach(() => {
events.reset();
});
it('subscribed to topic', () => {
eventRouter.subscribe();
expect(events.subscribed).toHaveLength(1);
expect(events.subscribed[0].id).toEqual('GitlabEventRouter');
expect(events.subscribed[0].topics).toEqual([topic]);
});
it('no $.event_name', () => {
eventRouter.onEvent({
topic,
eventPayload: { invalid: 'payload' },
metadata,
});
expect(eventBroker.published).toEqual([]);
expect(events.published).toEqual([]);
});
it('with $.event_name', () => {
const eventBroker = new TestEventBroker();
eventRouter.setEventBroker(eventBroker);
eventRouter.onEvent({ topic, eventPayload, metadata });
expect(eventBroker.published.length).toBe(1);
expect(eventBroker.published[0].topic).toEqual('gitlab.test_type');
expect(eventBroker.published[0].eventPayload).toEqual(eventPayload);
expect(eventBroker.published[0].metadata).toEqual(metadata);
expect(events.published.length).toBe(1);
expect(events.published[0].topic).toEqual('gitlab.test_type');
expect(events.published[0].eventPayload).toEqual(eventPayload);
expect(events.published[0].metadata).toEqual(metadata);
});
});
@@ -16,6 +16,7 @@
import {
EventParams,
EventsService,
SubTopicEventRouter,
} from '@backstage/plugin-events-node';
@@ -27,8 +28,15 @@ import {
* @public
*/
export class GitlabEventRouter extends SubTopicEventRouter {
constructor() {
super('gitlab');
constructor(options: { events: EventsService }) {
super({
events: options.events,
topic: 'gitlab',
});
}
protected getSubscriberId(): string {
return 'GitlabEventRouter';
}
protected determineSubTopic(params: EventParams): string | undefined {
@@ -14,32 +14,28 @@
* limitations under the License.
*/
import { createServiceFactory } from '@backstage/backend-plugin-api';
import { startTestBackend } from '@backstage/backend-test-utils';
import { eventsExtensionPoint } from '@backstage/plugin-events-node/alpha';
import { TestEventsService } from '@backstage/plugin-events-backend-test-utils';
import { eventsServiceRef } from '@backstage/plugin-events-node';
import { eventsModuleGitlabEventRouter } from './eventsModuleGitlabEventRouter';
import { GitlabEventRouter } from '../router/GitlabEventRouter';
describe('eventsModuleGitlabEventRouter', () => {
it('should be correctly wired and set up', async () => {
let addedPublisher: GitlabEventRouter | undefined;
let addedSubscriber: GitlabEventRouter | undefined;
const extensionPoint = {
addPublishers: (publisher: any) => {
addedPublisher = publisher;
const events = new TestEventsService();
const eventsServiceFactory = createServiceFactory({
service: eventsServiceRef,
deps: {},
async factory({}) {
return events;
},
addSubscribers: (subscriber: any) => {
addedSubscriber = subscriber;
},
};
await startTestBackend({
extensionPoints: [[eventsExtensionPoint, extensionPoint]],
features: [eventsModuleGitlabEventRouter()],
});
expect(addedPublisher).not.toBeUndefined();
expect(addedPublisher).toBeInstanceOf(GitlabEventRouter);
expect(addedSubscriber).not.toBeUndefined();
expect(addedSubscriber).toBeInstanceOf(GitlabEventRouter);
await startTestBackend({
features: [eventsServiceFactory(), eventsModuleGitlabEventRouter()],
});
expect(events.subscribed).toHaveLength(1);
expect(events.subscribed[0].id).toEqual('GitlabEventRouter');
});
});
@@ -15,7 +15,7 @@
*/
import { createBackendModule } from '@backstage/backend-plugin-api';
import { eventsExtensionPoint } from '@backstage/plugin-events-node/alpha';
import { eventsServiceRef } from '@backstage/plugin-events-node';
import { GitlabEventRouter } from '../router/GitlabEventRouter';
/**
@@ -31,13 +31,11 @@ export const eventsModuleGitlabEventRouter = createBackendModule({
register(env) {
env.registerInit({
deps: {
events: eventsExtensionPoint,
events: eventsServiceRef,
},
async init({ events }) {
const eventRouter = new GitlabEventRouter();
events.addPublishers(eventRouter);
events.addSubscribers(eventRouter);
const eventRouter = new GitlabEventRouter({ events: events });
await eventRouter.subscribe();
},
});
},
+6 -8
View File
@@ -39,17 +39,17 @@ export interface EventPublisher {
}
// @public
export abstract class EventRouter implements EventPublisher, EventSubscriber {
export abstract class EventRouter {
protected constructor(options: { events: EventsService; topics: string[] });
// (undocumented)
protected abstract determineDestinationTopic(
params: EventParams,
): string | undefined;
// (undocumented)
protected abstract getSubscriberId(): string;
// (undocumented)
onEvent(params: EventParams): Promise<void>;
// (undocumented)
setEventBroker(eventBroker: EventBroker): Promise<void>;
// (undocumented)
abstract supportsEventTopics(): string[];
subscribe(): Promise<void>;
}
// @public
@@ -114,12 +114,10 @@ export type RequestValidator = (
// @public
export abstract class SubTopicEventRouter extends EventRouter {
protected constructor(topic: string);
protected constructor(options: { events: EventsService; topic: string });
// (undocumented)
protected determineDestinationTopic(params: EventParams): string | undefined;
// (undocumented)
protected abstract determineSubTopic(params: EventParams): string | undefined;
// (undocumented)
supportsEventTopics(): string[];
}
```
+17 -22
View File
@@ -14,11 +14,19 @@
* limitations under the License.
*/
import { EventBroker } from './EventBroker';
import { EventParams } from './EventParams';
import { EventRouter } from './EventRouter';
import { EventsService } from './EventsService';
class TestEventRouter extends EventRouter {
constructor(events: EventsService) {
super({ events, topics: ['my-topic'] });
}
protected getSubscriberId(): string {
return 'TestEventRouter';
}
protected determineDestinationTopic(params: EventParams): string | undefined {
const payload = params.eventPayload as { value?: number };
if (payload.value === undefined) {
@@ -27,26 +35,21 @@ class TestEventRouter extends EventRouter {
return payload.value % 2 === 0 ? 'even' : 'odd';
}
supportsEventTopics(): string[] {
return ['my-topic'];
}
}
describe('EventRouter', () => {
const eventRouter = new TestEventRouter();
const published: EventParams[] = [];
const events: EventsService = {
publish: async event => {
published.push(event);
},
subscribe: async _subscription => {},
};
const eventRouter = new TestEventRouter(events);
const topic = 'my-topic';
const metadata = { random: 'metadata' };
it('no destination topic', async () => {
const published: EventParams[] = [];
const eventBroker = {
publish: (params: EventParams) => {
published.push(params);
},
} as EventBroker;
await eventRouter.setEventBroker(eventBroker);
await eventRouter.onEvent({
topic,
eventPayload: { discarded: 'event' },
@@ -57,14 +60,6 @@ describe('EventRouter', () => {
});
it('with destination topic', async () => {
const published: EventParams[] = [];
const eventBroker = {
publish: (params: EventParams) => {
published.push(params);
},
} as EventBroker;
await eventRouter.setEventBroker(eventBroker);
const payloadEven = { value: 2 };
const payloadOdd = { value: 3 };
await eventRouter.onEvent({ topic, eventPayload: payloadEven, metadata });
+32 -12
View File
@@ -14,10 +14,8 @@
* limitations under the License.
*/
import { EventBroker } from './EventBroker';
import { EventParams } from './EventParams';
import { EventPublisher } from './EventPublisher';
import { EventSubscriber } from './EventSubscriber';
import { EventsService } from './EventsService';
/**
* Subscribes to a topic and - depending on a set of conditions -
@@ -26,13 +24,41 @@ import { EventSubscriber } from './EventSubscriber';
* @see {@link https://www.enterpriseintegrationpatterns.com/MessageRouter.html | Message Router pattern}.
* @public
*/
export abstract class EventRouter implements EventPublisher, EventSubscriber {
private eventBroker?: EventBroker;
export abstract class EventRouter {
private readonly events: EventsService;
private readonly topics: string[];
private subscribed: boolean = false;
protected constructor(options: { events: EventsService; topics: string[] }) {
this.events = options.events;
this.topics = options.topics;
}
protected abstract getSubscriberId(): string;
protected abstract determineDestinationTopic(
params: EventParams,
): string | undefined;
/**
* Subscribes itself to the topic(s),
* after which events potentially can be received
* and processed by {@link EventRouter.onEvent}.
*/
async subscribe(): Promise<void> {
if (this.subscribed) {
return;
}
this.subscribed = true;
await this.events.subscribe({
id: this.getSubscriberId(),
topics: this.topics,
onEvent: this.onEvent.bind(this),
});
}
async onEvent(params: EventParams): Promise<void> {
const topic = this.determineDestinationTopic(params);
@@ -41,15 +67,9 @@ export abstract class EventRouter implements EventPublisher, EventSubscriber {
}
// republish to different topic
this.eventBroker?.publish({
await this.events.publish({
...params,
topic,
});
}
async setEventBroker(eventBroker: EventBroker): Promise<void> {
this.eventBroker = eventBroker;
}
abstract supportsEventTopics(): string[];
}
@@ -14,13 +14,17 @@
* limitations under the License.
*/
import { EventBroker } from './EventBroker';
import { EventParams } from './EventParams';
import { EventsService } from './EventsService';
import { SubTopicEventRouter } from './SubTopicEventRouter';
class TestSubTopicEventRouter extends SubTopicEventRouter {
constructor() {
super('my-topic');
constructor(events: EventsService) {
super({ events, topic: 'my-topic' });
}
protected getSubscriberId(): string {
return 'TestSubTopicEventRouter';
}
protected determineSubTopic(params: EventParams): string | undefined {
@@ -29,34 +33,25 @@ class TestSubTopicEventRouter extends SubTopicEventRouter {
}
describe('SubTopicEventRouter', () => {
const eventRouter = new TestSubTopicEventRouter();
const published: EventParams[] = [];
const events: EventsService = {
publish: async event => {
published.push(event);
},
subscribe: async _subscription => {},
};
const eventRouter = new TestSubTopicEventRouter(events);
const topic = 'my-topic';
const eventPayload = { test: 'payload' };
const metadata = { 'x-my-event': 'test.type' };
it('no x-my-event', async () => {
const published: EventParams[] = [];
const eventBroker = {
publish: (params: EventParams) => {
published.push(params);
},
} as EventBroker;
await eventRouter.setEventBroker(eventBroker);
await eventRouter.onEvent({ topic, eventPayload });
expect(published).toEqual([]);
});
it('with x-my-event', async () => {
const published: EventParams[] = [];
const eventBroker = {
publish: (params: EventParams) => {
published.push(params);
},
} as EventBroker;
await eventRouter.setEventBroker(eventBroker);
await eventRouter.onEvent({ topic, eventPayload, metadata });
expect(published.length).toBe(1);
@@ -16,6 +16,7 @@
import { EventParams } from './EventParams';
import { EventRouter } from './EventRouter';
import { EventsService } from './EventsService';
/**
* Subscribes to the provided (generic) topic
@@ -27,8 +28,11 @@ import { EventRouter } from './EventRouter';
* @public
*/
export abstract class SubTopicEventRouter extends EventRouter {
protected constructor(private readonly topic: string) {
super();
protected constructor(options: { events: EventsService; topic: string }) {
super({
events: options.events,
topics: [options.topic],
});
}
protected abstract determineSubTopic(params: EventParams): string | undefined;
@@ -37,8 +41,4 @@ export abstract class SubTopicEventRouter extends EventRouter {
const subTopic = this.determineSubTopic(params);
return subTopic ? `${params.topic}.${subTopic}` : undefined;
}
supportsEventTopics(): string[] {
return [this.topic];
}
}
-5
View File
@@ -6404,7 +6404,6 @@ __metadata:
"@backstage/cli": "workspace:^"
"@backstage/plugin-events-backend-test-utils": "workspace:^"
"@backstage/plugin-events-node": "workspace:^"
winston: ^3.2.1
languageName: unknown
linkType: soft
@@ -6417,7 +6416,6 @@ __metadata:
"@backstage/cli": "workspace:^"
"@backstage/plugin-events-backend-test-utils": "workspace:^"
"@backstage/plugin-events-node": "workspace:^"
winston: ^3.2.1
languageName: unknown
linkType: soft
@@ -6430,7 +6428,6 @@ __metadata:
"@backstage/cli": "workspace:^"
"@backstage/plugin-events-backend-test-utils": "workspace:^"
"@backstage/plugin-events-node": "workspace:^"
winston: ^3.2.1
languageName: unknown
linkType: soft
@@ -6445,7 +6442,6 @@ __metadata:
"@backstage/plugin-events-backend-test-utils": "workspace:^"
"@backstage/plugin-events-node": "workspace:^"
"@octokit/webhooks-methods": ^3.0.0
winston: ^3.2.1
languageName: unknown
linkType: soft
@@ -6459,7 +6455,6 @@ __metadata:
"@backstage/config": "workspace:^"
"@backstage/plugin-events-backend-test-utils": "workspace:^"
"@backstage/plugin-events-node": "workspace:^"
winston: ^3.2.1
languageName: unknown
linkType: soft