diff --git a/src/lib/addons/addon.test.ts b/src/lib/addons/addon.test.ts index d7703dbb00..46799c618a 100644 --- a/src/lib/addons/addon.test.ts +++ b/src/lib/addons/addon.test.ts @@ -2,28 +2,32 @@ import nock from 'nock'; import noLogger from '../../test/fixtures/no-logger'; import SlackAddon from './slack'; +import type { IAddonConfig, IFlagResolver } from '../types'; +import type { IntegrationEventsService } from '../services'; beforeEach(() => { nock.disableNetConnect(); }); +const url = 'https://test.some.com'; + +const ARGS: IAddonConfig = { + getLogger: noLogger, + unleashUrl: url, + integrationEventsService: {} as IntegrationEventsService, + flagResolver: {} as IFlagResolver, +}; + test('Does not retry if request succeeds', async () => { const url = 'https://test.some.com'; - const addon = new SlackAddon({ - getLogger: noLogger, - unleashUrl: url, - }); + const addon = new SlackAddon(ARGS); nock(url).get('/').reply(201); const res = await addon.fetchRetry(url); expect(res.ok).toBe(true); }); test('Retries once, and succeeds', async () => { - const url = 'https://test.some.com'; - const addon = new SlackAddon({ - getLogger: noLogger, - unleashUrl: url, - }); + const addon = new SlackAddon(ARGS); nock(url).get('/').replyWithError('testing retry'); nock(url).get('/').reply(200); const res = await addon.fetchRetry(url); @@ -32,22 +36,14 @@ test('Retries once, and succeeds', async () => { }); test('Does not throw if response is error', async () => { - const url = 'https://test.some.com'; - const addon = new SlackAddon({ - getLogger: noLogger, - unleashUrl: url, - }); + const addon = new SlackAddon(ARGS); nock(url).get('/').twice().replyWithError('testing retry'); const res = await addon.fetchRetry(url); expect(res.ok).toBe(false); }); test('Supports custom number of retries', async () => { - const url = 'https://test.some.com'; - const addon = new SlackAddon({ - getLogger: noLogger, - unleashUrl: url, - }); + const addon = new SlackAddon(ARGS); let retries = 0; nock(url).get('/').twice().replyWithError('testing retry'); nock(url).get('/').reply(201); diff --git a/src/lib/addons/addon.ts b/src/lib/addons/addon.ts index b1aabcca49..08c42b6ff3 100644 --- a/src/lib/addons/addon.ts +++ b/src/lib/addons/addon.ts @@ -1,9 +1,11 @@ import fetch from 'make-fetch-happen'; import { addonDefinitionSchema } from './addon-schema'; -import type { IUnleashConfig } from '../types/option'; import type { Logger } from '../logger'; -import type { IAddonDefinition } from '../types/model'; +import type { IAddonConfig, IAddonDefinition } from '../types/model'; import type { IEvent } from '../types/events'; +import type { IntegrationEventsService } from '../features/integration-events/integration-events-service'; +import type { IntegrationEventWriteModel } from '../features/integration-events/integration-events-store'; +import type { IFlagResolver } from '../types'; export default abstract class Addon { logger: Logger; @@ -12,9 +14,13 @@ export default abstract class Addon { _definition: IAddonDefinition; + integrationEventsService: IntegrationEventsService; + + flagResolver: IFlagResolver; + constructor( definition: IAddonDefinition, - { getLogger }: Pick, + { getLogger, integrationEventsService, flagResolver }: IAddonConfig, ) { this.logger = getLogger(`addon/${definition.name}`); const { error } = addonDefinitionSchema.validate(definition); @@ -27,6 +33,8 @@ export default abstract class Addon { } this._name = definition.name; this._definition = definition; + this.integrationEventsService = integrationEventsService; + this.flagResolver = flagResolver; } get name(): string { @@ -60,13 +68,25 @@ export default abstract class Addon { } status code ${e.code}`, e, ); - res = { statusCode: e.code, ok: false }; + res = { status: e.code, ok: false }; } return res; } // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - abstract handleEvent(event: IEvent, parameters: any): Promise; + abstract handleEvent( + event: IEvent, + parameters: any, + integrationId: number, + ): Promise; + + async registerEvent( + integrationEvent: IntegrationEventWriteModel, + ): Promise { + if (this.flagResolver.isEnabled('integrationEvents')) { + await this.integrationEventsService.registerEvent(integrationEvent); + } + } destroy?(): void; } diff --git a/src/lib/addons/datadog.test.ts b/src/lib/addons/datadog.test.ts index 1b25e99987..2a8c9045b8 100644 --- a/src/lib/addons/datadog.test.ts +++ b/src/lib/addons/datadog.test.ts @@ -9,9 +9,19 @@ import type { Logger } from '../logger'; import DatadogAddon from './datadog'; import noLogger from '../../test/fixtures/no-logger'; +import type { IAddonConfig, IFlagResolver } from '../types'; +import type { IntegrationEventsService } from '../services'; let fetchRetryCalls: any[] = []; +const INTEGRATION_ID = 1337; +const ARGS: IAddonConfig = { + getLogger: noLogger, + unleashUrl: 'http://some-url.com', + integrationEventsService: {} as IntegrationEventsService, + flagResolver: {} as IFlagResolver, +}; + jest.mock( './addon', () => @@ -32,14 +42,15 @@ jest.mock( }); return Promise.resolve({ status: 200 }); } + + async registerEvent(_) { + return Promise.resolve(); + } }, ); test('Should call datadog webhook', async () => { - const addon = new DatadogAddon({ - getLogger: noLogger, - unleashUrl: 'http://some-url.com', - }); + const addon = new DatadogAddon(ARGS); const event: IEvent = { id: 1, createdAt: new Date(), @@ -59,17 +70,14 @@ test('Should call datadog webhook', async () => { apiKey: 'fakeKey', }; - await addon.handleEvent(event, parameters); + await addon.handleEvent(event, parameters, INTEGRATION_ID); expect(fetchRetryCalls.length).toBe(1); expect(fetchRetryCalls[0].url).toBe(parameters.url); expect(fetchRetryCalls[0].options.body).toMatchSnapshot(); }); test('Should call datadog webhook for archived toggle', async () => { - const addon = new DatadogAddon({ - getLogger: noLogger, - unleashUrl: 'http://some-url.com', - }); + const addon = new DatadogAddon(ARGS); const event: IEvent = { id: 2, createdAt: new Date(), @@ -87,17 +95,14 @@ test('Should call datadog webhook for archived toggle', async () => { apiKey: 'fakeKey', }; - await addon.handleEvent(event, parameters); + await addon.handleEvent(event, parameters, INTEGRATION_ID); expect(fetchRetryCalls.length).toBe(1); expect(fetchRetryCalls[0].url).toBe(parameters.url); expect(fetchRetryCalls[0].options.body).toMatchSnapshot(); }); test('Should call datadog webhook for archived toggle with project info', async () => { - const addon = new DatadogAddon({ - getLogger: noLogger, - unleashUrl: 'http://some-url.com', - }); + const addon = new DatadogAddon(ARGS); const event: IEvent = { id: 2, createdAt: new Date(), @@ -116,17 +121,14 @@ test('Should call datadog webhook for archived toggle with project info', async apiKey: 'fakeKey', }; - await addon.handleEvent(event, parameters); + await addon.handleEvent(event, parameters, INTEGRATION_ID); expect(fetchRetryCalls.length).toBe(1); expect(fetchRetryCalls[0].url).toBe(parameters.url); expect(fetchRetryCalls[0].options.body).toMatchSnapshot(); }); test('Should call datadog webhook for toggled environment', async () => { - const addon = new DatadogAddon({ - getLogger: noLogger, - unleashUrl: 'http://some-url.com', - }); + const addon = new DatadogAddon(ARGS); const event: IEvent = { id: 2, createdAt: new Date(), @@ -146,7 +148,7 @@ test('Should call datadog webhook for toggled environment', async () => { apiKey: 'fakeKey', }; - await addon.handleEvent(event, parameters); + await addon.handleEvent(event, parameters, INTEGRATION_ID); expect(fetchRetryCalls).toHaveLength(1); expect(fetchRetryCalls[0].url).toBe(parameters.url); expect(fetchRetryCalls[0].options.body).toMatch(/disabled/); @@ -154,10 +156,7 @@ test('Should call datadog webhook for toggled environment', async () => { }); test('Should include customHeaders in headers when calling service', async () => { - const addon = new DatadogAddon({ - getLogger: noLogger, - unleashUrl: 'http://some-url.com', - }); + const addon = new DatadogAddon(ARGS); const event: IEvent = { id: 2, createdAt: new Date(), @@ -177,7 +176,7 @@ test('Should include customHeaders in headers when calling service', async () => apiKey: 'fakeKey', customHeaders: `{ "MY_CUSTOM_HEADER": "MY_CUSTOM_VALUE" }`, }; - await addon.handleEvent(event, parameters); + await addon.handleEvent(event, parameters, INTEGRATION_ID); expect(fetchRetryCalls).toHaveLength(1); expect(fetchRetryCalls[0].url).toBe(parameters.url); expect(fetchRetryCalls[0].options.body).toMatch(/disabled/); @@ -186,10 +185,7 @@ test('Should include customHeaders in headers when calling service', async () => }); test('Should not include source_type_name when included in the config', async () => { - const addon = new DatadogAddon({ - getLogger: noLogger, - unleashUrl: 'http://some-url.com', - }); + const addon = new DatadogAddon(ARGS); const event: IEvent = { id: 2, createdAt: new Date(), @@ -210,7 +206,7 @@ test('Should not include source_type_name when included in the config', async () sourceTypeName: 'my-custom-source-type', }; - await addon.handleEvent(event, parameters); + await addon.handleEvent(event, parameters, INTEGRATION_ID); expect(fetchRetryCalls).toHaveLength(1); expect(fetchRetryCalls[0].url).toBe(parameters.url); expect(fetchRetryCalls[0].options.body).toMatch( @@ -221,10 +217,7 @@ test('Should not include source_type_name when included in the config', async () }); test('Should call datadog webhook with JSON when template set', async () => { - const addon = new DatadogAddon({ - getLogger: noLogger, - unleashUrl: 'http://some-url.com', - }); + const addon = new DatadogAddon(ARGS); const event: IEvent = { id: 1, createdAt: new Date(), @@ -246,7 +239,7 @@ test('Should call datadog webhook with JSON when template set', async () => { '{\n "event": "{{event.type}}",\n "createdBy": "{{event.createdBy}}"\n}', }; - await addon.handleEvent(event, parameters); + await addon.handleEvent(event, parameters, INTEGRATION_ID); expect(fetchRetryCalls.length).toBe(1); expect(fetchRetryCalls[0].url).toBe(parameters.url); expect(fetchRetryCalls[0].options.body).toMatchSnapshot(); diff --git a/src/lib/addons/datadog.ts b/src/lib/addons/datadog.ts index 3b611a111e..2d39614266 100644 --- a/src/lib/addons/datadog.ts +++ b/src/lib/addons/datadog.ts @@ -39,6 +39,7 @@ export default class DatadogAddon extends Addon { async handleEvent( event: IEvent, parameters: IDatadogParameters, + integrationId: number, ): Promise { const { url = 'https://api.datadoghq.com/api/v1/events', diff --git a/src/lib/addons/index.ts b/src/lib/addons/index.ts index 61e8725c9f..368081a0f8 100644 --- a/src/lib/addons/index.ts +++ b/src/lib/addons/index.ts @@ -4,26 +4,21 @@ import TeamsAddon from './teams'; import DatadogAddon from './datadog'; import NewRelicAddon from './new-relic'; import type Addon from './addon'; -import type { LogProvider } from '../logger'; import SlackAppAddon from './slack-app'; -import type { IFlagResolver } from '../types'; +import type { IAddonConfig } from '../types'; export interface IAddonProviders { [key: string]: Addon; } -export const getAddons: (args: { - getLogger: LogProvider; - unleashUrl: string; - flagResolver: IFlagResolver; -}) => IAddonProviders = ({ getLogger, unleashUrl, flagResolver }) => { +export const getAddons: (args: IAddonConfig) => IAddonProviders = (args) => { const addons: Addon[] = [ - new Webhook({ getLogger }), - new SlackAddon({ getLogger, unleashUrl }), - new SlackAppAddon({ getLogger, unleashUrl }), - new TeamsAddon({ getLogger, unleashUrl }), - new DatadogAddon({ getLogger, unleashUrl }), - new NewRelicAddon({ getLogger, unleashUrl }), + new Webhook(args), + new SlackAddon(args), + new SlackAppAddon(args), + new TeamsAddon(args), + new DatadogAddon(args), + new NewRelicAddon(args), ]; return addons.reduce((map, addon) => { diff --git a/src/lib/addons/new-relic.test.ts b/src/lib/addons/new-relic.test.ts index e3870211c9..99e696c474 100644 --- a/src/lib/addons/new-relic.test.ts +++ b/src/lib/addons/new-relic.test.ts @@ -2,6 +2,8 @@ import { FEATURE_ARCHIVED, FEATURE_CREATED, FEATURE_ENVIRONMENT_DISABLED, + type IFlagResolver, + type IAddonConfig, type IEvent, } from '../types'; import type { Logger } from '../logger'; @@ -11,11 +13,20 @@ import NewRelicAddon, { type INewRelicParameters } from './new-relic'; import noLogger from '../../test/fixtures/no-logger'; import { gunzip } from 'node:zlib'; import { promisify } from 'util'; +import type { IntegrationEventsService } from '../services'; const asyncGunzip = promisify(gunzip); let fetchRetryCalls: any[] = []; +const INTEGRATION_ID = 1337; +const ARGS: IAddonConfig = { + getLogger: noLogger, + unleashUrl: 'http://some-url.com', + integrationEventsService: {} as IntegrationEventsService, + flagResolver: {} as IFlagResolver, +}; + jest.mock( './addon', () => @@ -36,6 +47,10 @@ jest.mock( }); return Promise.resolve({ status: 200 }); } + + async registerEvent(_) { + return Promise.resolve(); + } }, ); @@ -59,12 +74,9 @@ const defaultEvent = { } as IEvent; const makeAddHandleEvent = (event: IEvent, parameters: INewRelicParameters) => { - const addon = new NewRelicAddon({ - getLogger: noLogger, - unleashUrl: 'http://some-url.com', - }); + const addon = new NewRelicAddon(ARGS); - return () => addon.handleEvent(event, parameters); + return () => addon.handleEvent(event, parameters, INTEGRATION_ID); }; test.each([ diff --git a/src/lib/addons/new-relic.ts b/src/lib/addons/new-relic.ts index 028a516a95..40357f5924 100644 --- a/src/lib/addons/new-relic.ts +++ b/src/lib/addons/new-relic.ts @@ -44,6 +44,7 @@ export default class NewRelicAddon extends Addon { async handleEvent( event: IEvent, parameters: INewRelicParameters, + integrationId: number, ): Promise { const { url, licenseKey, customHeaders, bodyTemplate } = parameters; const context = { diff --git a/src/lib/addons/slack-app.test.ts b/src/lib/addons/slack-app.test.ts index 32110a3b7d..c693ed811d 100644 --- a/src/lib/addons/slack-app.test.ts +++ b/src/lib/addons/slack-app.test.ts @@ -1,10 +1,32 @@ import { type IEvent, FEATURE_ENVIRONMENT_ENABLED } from '../types/events'; import SlackAppAddon from './slack-app'; import { type ChatPostMessageArguments, ErrorCode } from '@slack/web-api'; -import { SYSTEM_USER_ID } from '../types'; +import { + type IAddonConfig, + type IFlagResolver, + SYSTEM_USER_ID, +} from '../types'; +import type { IntegrationEventsService } from '../services'; const slackApiCalls: ChatPostMessageArguments[] = []; +const loggerMock = { + debug: jest.fn(), + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + fatal: jest.fn(), +}; +const getLogger = jest.fn(() => loggerMock); + +const INTEGRATION_ID = 1337; +const ARGS: IAddonConfig = { + getLogger, + unleashUrl: 'http://some-url.com', + integrationEventsService: {} as IntegrationEventsService, + flagResolver: {} as IFlagResolver, +}; + let postMessage = jest.fn().mockImplementation((options) => { slackApiCalls.push(options); return Promise.resolve(); @@ -28,14 +50,6 @@ jest.mock('@slack/web-api', () => ({ describe('SlackAppAddon', () => { let addon: SlackAppAddon; const accessToken = 'test-access-token'; - const loggerMock = { - debug: jest.fn(), - info: jest.fn(), - warn: jest.fn(), - error: jest.fn(), - fatal: jest.fn(), - }; - const getLogger = jest.fn(() => loggerMock); const mockError = { code: ErrorCode.PlatformError, data: { @@ -64,10 +78,7 @@ describe('SlackAppAddon', () => { jest.useFakeTimers(); slackApiCalls.length = 0; postMessage.mockClear(); - addon = new SlackAppAddon({ - getLogger, - unleashUrl: 'http://some-url.com', - }); + addon = new SlackAppAddon(ARGS); }); afterEach(() => { @@ -75,20 +86,28 @@ describe('SlackAppAddon', () => { }); it('should post message when feature is toggled', async () => { - await addon.handleEvent(event, { - accessToken, - defaultChannels: 'general', - }); + await addon.handleEvent( + event, + { + accessToken, + defaultChannels: 'general', + }, + INTEGRATION_ID, + ); expect(slackApiCalls.length).toBe(1); expect(slackApiCalls[0].channel).toBe('general'); }); it('should post to all channels in defaultChannels', async () => { - await addon.handleEvent(event, { - accessToken, - defaultChannels: 'general, another-channel-1', - }); + await addon.handleEvent( + event, + { + accessToken, + defaultChannels: 'general, another-channel-1', + }, + INTEGRATION_ID, + ); expect(slackApiCalls.length).toBe(2); expect(slackApiCalls[0].channel).toBe('general'); @@ -104,10 +123,14 @@ describe('SlackAppAddon', () => { ], }; - await addon.handleEvent(eventWith2Tags, { - accessToken, - defaultChannels: '', - }); + await addon.handleEvent( + eventWith2Tags, + { + accessToken, + defaultChannels: '', + }, + INTEGRATION_ID, + ); expect(slackApiCalls.length).toBe(2); expect(slackApiCalls[0].channel).toBe('general'); @@ -123,10 +146,14 @@ describe('SlackAppAddon', () => { ], }; - await addon.handleEvent(eventWith2Tags, { - accessToken, - defaultChannels: 'another-channel-1, another-channel-2', - }); + await addon.handleEvent( + eventWith2Tags, + { + accessToken, + defaultChannels: 'another-channel-1, another-channel-2', + }, + INTEGRATION_ID, + ); expect(slackApiCalls.length).toBe(3); expect(slackApiCalls[0].channel).toBe('general'); @@ -135,10 +162,14 @@ describe('SlackAppAddon', () => { }); it('should not post a message if there are no tagged channels and no defaultChannels', async () => { - await addon.handleEvent(event, { - accessToken, - defaultChannels: '', - }); + await addon.handleEvent( + event, + { + accessToken, + defaultChannels: '', + }, + INTEGRATION_ID, + ); expect(slackApiCalls.length).toBe(0); }); @@ -146,10 +177,14 @@ describe('SlackAppAddon', () => { it('should log error when an API call fails', async () => { postMessage = jest.fn().mockRejectedValue(mockError); - await addon.handleEvent(event, { - accessToken, - defaultChannels: 'general', - }); + await addon.handleEvent( + event, + { + accessToken, + defaultChannels: 'general', + }, + INTEGRATION_ID, + ); expect(loggerMock.warn).toHaveBeenCalledWith( `Error handling event ${event.type}. A platform error occurred: ${JSON.stringify(mockError.data)}`, @@ -173,10 +208,14 @@ describe('SlackAppAddon', () => { .mockResolvedValueOnce({ ok: true }) .mockRejectedValueOnce(mockError); - await addon.handleEvent(eventWith3Tags, { - accessToken, - defaultChannels: '', - }); + await addon.handleEvent( + eventWith3Tags, + { + accessToken, + defaultChannels: '', + }, + INTEGRATION_ID, + ); expect(postMessage).toHaveBeenCalledTimes(3); expect(loggerMock.warn).toHaveBeenCalledWith( diff --git a/src/lib/addons/slack-app.ts b/src/lib/addons/slack-app.ts index ce95e4bf11..6ea54f5329 100644 --- a/src/lib/addons/slack-app.ts +++ b/src/lib/addons/slack-app.ts @@ -44,6 +44,7 @@ export default class SlackAppAddon extends Addon { async handleEvent( event: IEvent, parameters: ISlackAppAddonParameters, + integrationId: number, ): Promise { try { const { accessToken, defaultChannels } = parameters; diff --git a/src/lib/addons/slack.test.ts b/src/lib/addons/slack.test.ts index ce5e134423..abe9d1ea45 100644 --- a/src/lib/addons/slack.test.ts +++ b/src/lib/addons/slack.test.ts @@ -9,10 +9,23 @@ import type { Logger } from '../logger'; import SlackAddon from './slack'; import noLogger from '../../test/fixtures/no-logger'; -import { SYSTEM_USER_ID } from '../types'; +import { + type IAddonConfig, + type IFlagResolver, + SYSTEM_USER_ID, +} from '../types'; +import type { IntegrationEventsService } from '../services'; let fetchRetryCalls: any[] = []; +const INTEGRATION_ID = 1337; +const ARGS: IAddonConfig = { + getLogger: noLogger, + unleashUrl: 'http://some-url.com', + integrationEventsService: {} as IntegrationEventsService, + flagResolver: {} as IFlagResolver, +}; + jest.mock( './addon', () => @@ -33,14 +46,15 @@ jest.mock( }); return Promise.resolve({ status: 200 }); } + + async registerEvent(_) { + return Promise.resolve(); + } }, ); test('Should call slack webhook', async () => { - const addon = new SlackAddon({ - getLogger: noLogger, - unleashUrl: 'http://some-url.com', - }); + const addon = new SlackAddon(ARGS); const event: IEvent = { id: 1, createdAt: new Date(), @@ -62,17 +76,14 @@ test('Should call slack webhook', async () => { defaultChannel: 'general', }; - await addon.handleEvent(event, parameters); + await addon.handleEvent(event, parameters, INTEGRATION_ID); expect(fetchRetryCalls.length).toBe(1); expect(fetchRetryCalls[0].url).toBe(parameters.url); expect(fetchRetryCalls[0].options.body).toMatchSnapshot(); }); test('Should call slack webhook for archived toggle', async () => { - const addon = new SlackAddon({ - getLogger: noLogger, - unleashUrl: 'http://some-url.com', - }); + const addon = new SlackAddon(ARGS); const event: IEvent = { id: 2, createdAt: new Date(), @@ -90,17 +101,14 @@ test('Should call slack webhook for archived toggle', async () => { defaultChannel: 'general', }; - await addon.handleEvent(event, parameters); + await addon.handleEvent(event, parameters, INTEGRATION_ID); expect(fetchRetryCalls.length).toBe(1); expect(fetchRetryCalls[0].url).toBe(parameters.url); expect(fetchRetryCalls[0].options.body).toMatchSnapshot(); }); test('Should call slack webhook for archived toggle with project info', async () => { - const addon = new SlackAddon({ - getLogger: noLogger, - unleashUrl: 'http://some-url.com', - }); + const addon = new SlackAddon(ARGS); const event: IEvent = { id: 2, createdAt: new Date(), @@ -119,17 +127,14 @@ test('Should call slack webhook for archived toggle with project info', async () defaultChannel: 'general', }; - await addon.handleEvent(event, parameters); + await addon.handleEvent(event, parameters, INTEGRATION_ID); expect(fetchRetryCalls.length).toBe(1); expect(fetchRetryCalls[0].url).toBe(parameters.url); expect(fetchRetryCalls[0].options.body).toMatchSnapshot(); }); test(`Should call webhook for toggled environment`, async () => { - const addon = new SlackAddon({ - getLogger: noLogger, - unleashUrl: 'http://some-url.com', - }); + const addon = new SlackAddon(ARGS); const event: IEvent = { id: 2, createdAt: new Date(), @@ -149,7 +154,7 @@ test(`Should call webhook for toggled environment`, async () => { defaultChannel: 'general', }; - await addon.handleEvent(event, parameters); + await addon.handleEvent(event, parameters, INTEGRATION_ID); expect(fetchRetryCalls).toHaveLength(1); expect(fetchRetryCalls[0].url).toBe(parameters.url); expect(fetchRetryCalls[0].options.body).toMatch(/disabled/); @@ -157,10 +162,7 @@ test(`Should call webhook for toggled environment`, async () => { }); test('Should use default channel', async () => { - const addon = new SlackAddon({ - getLogger: noLogger, - unleashUrl: 'http://some-url.com', - }); + const addon = new SlackAddon(ARGS); const event: IEvent = { id: 3, createdAt: new Date(), @@ -180,7 +182,7 @@ test('Should use default channel', async () => { defaultChannel: 'some-channel', }; - await addon.handleEvent(event, parameters); + await addon.handleEvent(event, parameters, INTEGRATION_ID); const req = JSON.parse(fetchRetryCalls[0].options.body); @@ -188,10 +190,7 @@ test('Should use default channel', async () => { }); test('Should override default channel with data from tag', async () => { - const addon = new SlackAddon({ - getLogger: noLogger, - unleashUrl: 'http://some-url.com', - }); + const addon = new SlackAddon(ARGS); const event: IEvent = { id: 4, createdAt: new Date(), @@ -217,7 +216,7 @@ test('Should override default channel with data from tag', async () => { defaultChannel: 'some-channel', }; - await addon.handleEvent(event, parameters); + await addon.handleEvent(event, parameters, INTEGRATION_ID); const req = JSON.parse(fetchRetryCalls[0].options.body); @@ -225,10 +224,7 @@ test('Should override default channel with data from tag', async () => { }); test('Should post to all channels in tags', async () => { - const addon = new SlackAddon({ - getLogger: noLogger, - unleashUrl: 'http://some-url.com', - }); + const addon = new SlackAddon(ARGS); const event: IEvent = { id: 5, createdAt: new Date(), @@ -258,7 +254,7 @@ test('Should post to all channels in tags', async () => { defaultChannel: 'some-channel', }; - await addon.handleEvent(event, parameters); + await addon.handleEvent(event, parameters, INTEGRATION_ID); const req1 = JSON.parse(fetchRetryCalls[0].options.body); const req2 = JSON.parse(fetchRetryCalls[1].options.body); @@ -269,10 +265,7 @@ test('Should post to all channels in tags', async () => { }); test('Should include custom headers from parameters in call to service', async () => { - const addon = new SlackAddon({ - getLogger: noLogger, - unleashUrl: 'http://some-url.com', - }); + const addon = new SlackAddon(ARGS); const event: IEvent = { id: 2, createdAt: new Date(), @@ -293,7 +286,7 @@ test('Should include custom headers from parameters in call to service', async ( customHeaders: `{ "MY_CUSTOM_HEADER": "MY_CUSTOM_VALUE" }`, }; - await addon.handleEvent(event, parameters); + await addon.handleEvent(event, parameters, INTEGRATION_ID); expect(fetchRetryCalls).toHaveLength(1); expect(fetchRetryCalls[0].url).toBe(parameters.url); expect(fetchRetryCalls[0].options.body).toMatch(/disabled/); diff --git a/src/lib/addons/slack.ts b/src/lib/addons/slack.ts index e496206eb0..6804b4e039 100644 --- a/src/lib/addons/slack.ts +++ b/src/lib/addons/slack.ts @@ -32,6 +32,7 @@ export default class SlackAddon extends Addon { async handleEvent( event: IEvent, parameters: ISlackAddonParameters, + integrationId: number, ): Promise { const { url, diff --git a/src/lib/addons/teams.test.ts b/src/lib/addons/teams.test.ts index 6352a8b218..e36bd5881c 100644 --- a/src/lib/addons/teams.test.ts +++ b/src/lib/addons/teams.test.ts @@ -10,10 +10,23 @@ import { import TeamsAddon from './teams'; import noLogger from '../../test/fixtures/no-logger'; -import { SYSTEM_USER_ID } from '../types'; +import { + type IAddonConfig, + type IFlagResolver, + SYSTEM_USER_ID, +} from '../types'; +import type { IntegrationEventsService } from '../services'; let fetchRetryCalls: any[]; +const INTEGRATION_ID = 1337; +const ARGS: IAddonConfig = { + getLogger: noLogger, + unleashUrl: 'http://some-url.com', + integrationEventsService: {} as IntegrationEventsService, + flagResolver: {} as IFlagResolver, +}; + jest.mock( './addon', () => @@ -34,14 +47,15 @@ jest.mock( }); return Promise.resolve({ status: 200 }); } + + async registerEvent(_) { + return Promise.resolve(); + } }, ); test('Should call teams webhook', async () => { - const addon = new TeamsAddon({ - getLogger: noLogger, - unleashUrl: 'http://some-url.com', - }); + const addon = new TeamsAddon(ARGS); const event: IEvent = { id: 1, createdAt: new Date(), @@ -60,17 +74,14 @@ test('Should call teams webhook', async () => { url: 'http://hooks.office.com', }; - await addon.handleEvent(event, parameters); + await addon.handleEvent(event, parameters, INTEGRATION_ID); expect(fetchRetryCalls.length).toBe(1); expect(fetchRetryCalls[0].url).toBe(parameters.url); expect(fetchRetryCalls[0].options.body).toMatchSnapshot(); }); test('Should call teams webhook for archived toggle', async () => { - const addon = new TeamsAddon({ - getLogger: noLogger, - unleashUrl: 'http://some-url.com', - }); + const addon = new TeamsAddon(ARGS); const event: IEvent = { id: 1, createdAt: new Date(), @@ -87,17 +98,14 @@ test('Should call teams webhook for archived toggle', async () => { url: 'http://hooks.office.com', }; - await addon.handleEvent(event, parameters); + await addon.handleEvent(event, parameters, INTEGRATION_ID); expect(fetchRetryCalls.length).toBe(1); expect(fetchRetryCalls[0].url).toBe(parameters.url); expect(fetchRetryCalls[0].options.body).toMatchSnapshot(); }); test('Should call teams webhook for archived toggle with project info', async () => { - const addon = new TeamsAddon({ - getLogger: noLogger, - unleashUrl: 'http://some-url.com', - }); + const addon = new TeamsAddon(ARGS); const event: IEvent = { id: 1, createdAt: new Date(), @@ -115,17 +123,14 @@ test('Should call teams webhook for archived toggle with project info', async () url: 'http://hooks.office.com', }; - await addon.handleEvent(event, parameters); + await addon.handleEvent(event, parameters, INTEGRATION_ID); expect(fetchRetryCalls.length).toBe(1); expect(fetchRetryCalls[0].url).toBe(parameters.url); expect(fetchRetryCalls[0].options.body).toMatchSnapshot(); }); test(`Should call teams webhook for toggled environment`, async () => { - const addon = new TeamsAddon({ - getLogger: noLogger, - unleashUrl: 'http://some-url.com', - }); + const addon = new TeamsAddon(ARGS); const event: IEvent = { id: 2, createdAt: new Date(), @@ -144,7 +149,7 @@ test(`Should call teams webhook for toggled environment`, async () => { url: 'http://hooks.slack.com', }; - await addon.handleEvent(event, parameters); + await addon.handleEvent(event, parameters, INTEGRATION_ID); expect(fetchRetryCalls).toHaveLength(1); expect(fetchRetryCalls[0].url).toBe(parameters.url); expect(fetchRetryCalls[0].options.body).toMatch(/disabled/); @@ -152,10 +157,7 @@ test(`Should call teams webhook for toggled environment`, async () => { }); test('Should include custom headers in call to teams', async () => { - const addon = new TeamsAddon({ - getLogger: noLogger, - unleashUrl: 'http://some-url.com', - }); + const addon = new TeamsAddon(ARGS); const event: IEvent = { id: 2, createdAt: new Date(), @@ -175,7 +177,7 @@ test('Should include custom headers in call to teams', async () => { customHeaders: `{ "MY_CUSTOM_HEADER": "MY_CUSTOM_VALUE" }`, }; - await addon.handleEvent(event, parameters); + await addon.handleEvent(event, parameters, INTEGRATION_ID); expect(fetchRetryCalls).toHaveLength(1); expect(fetchRetryCalls[0].url).toBe(parameters.url); expect(fetchRetryCalls[0].options.body).toMatch(/disabled/); diff --git a/src/lib/addons/teams.ts b/src/lib/addons/teams.ts index 382e818103..2957186fa0 100644 --- a/src/lib/addons/teams.ts +++ b/src/lib/addons/teams.ts @@ -24,6 +24,7 @@ export default class TeamsAddon extends Addon { async handleEvent( event: IEvent, parameters: ITeamsParameters, + integrationId: number, ): Promise { const { url, customHeaders } = parameters; const { createdBy } = event; diff --git a/src/lib/addons/webhook.test.ts b/src/lib/addons/webhook.test.ts index 03729ba015..6d76c63522 100644 --- a/src/lib/addons/webhook.test.ts +++ b/src/lib/addons/webhook.test.ts @@ -5,9 +5,24 @@ import { FEATURE_CREATED, type IEvent } from '../types/events'; import WebhookAddon from './webhook'; import noLogger from '../../test/fixtures/no-logger'; -import { SYSTEM_USER_ID } from '../types'; +import { + type IAddonConfig, + type IFlagResolver, + serializeDates, + SYSTEM_USER_ID, +} from '../types'; +import type { IntegrationEventsService } from '../services'; let fetchRetryCalls: any[] = []; +const registerEventMock = jest.fn(); + +const INTEGRATION_ID = 1337; +const ARGS: IAddonConfig = { + getLogger: noLogger, + unleashUrl: 'http://some-url.com', + integrationEventsService: {} as IntegrationEventsService, + flagResolver: {} as IFlagResolver, +}; jest.mock( './addon', @@ -27,127 +42,182 @@ jest.mock( retries, backoff, }); - return Promise.resolve({ status: 200 }); + return Promise.resolve({ ok: true, status: 200 }); + } + + async registerEvent(event) { + return registerEventMock(event); } }, ); -test('Should handle event without "bodyTemplate"', () => { - const addon = new WebhookAddon({ getLogger: noLogger }); - const event: IEvent = { - id: 1, - createdAt: new Date(), - createdByUserId: SYSTEM_USER_ID, - type: FEATURE_CREATED, - createdBy: 'some@user.com', - featureName: 'some-toggle', - data: { - name: 'some-toggle', - enabled: false, - strategies: [{ name: 'default' }], - }, - }; +describe('Webhook integration', () => { + beforeEach(() => { + registerEventMock.mockClear(); + }); - const parameters = { - url: 'http://test.webhook.com', - }; + test('Should handle event without "bodyTemplate"', () => { + const addon = new WebhookAddon(ARGS); + const event: IEvent = { + id: 1, + createdAt: new Date(), + createdByUserId: SYSTEM_USER_ID, + type: FEATURE_CREATED, + createdBy: 'some@user.com', + featureName: 'some-toggle', + data: { + name: 'some-toggle', + enabled: false, + strategies: [{ name: 'default' }], + }, + }; - addon.handleEvent(event, parameters); - expect(fetchRetryCalls.length).toBe(1); - expect(fetchRetryCalls[0].url).toBe(parameters.url); - expect(fetchRetryCalls[0].options.body).toBe(JSON.stringify(event)); -}); - -test('Should format event with "bodyTemplate"', () => { - const addon = new WebhookAddon({ getLogger: noLogger }); - const event: IEvent = { - id: 1, - createdAt: new Date(), - createdByUserId: SYSTEM_USER_ID, - type: FEATURE_CREATED, - createdBy: 'some@user.com', - featureName: 'some-toggle', - data: { - name: 'some-toggle', - enabled: false, - strategies: [{ name: 'default' }], - }, - }; - - const parameters = { - url: 'http://test.webhook.com/plain', - bodyTemplate: '{{event.type}} on toggle {{event.data.name}}', - contentType: 'text/plain', - }; - - addon.handleEvent(event, parameters); - const call = fetchRetryCalls[0]; - expect(fetchRetryCalls.length).toBe(1); - expect(call.url).toBe(parameters.url); - expect(call.options.headers['Content-Type']).toBe('text/plain'); - expect(call.options.body).toBe('feature-created on toggle some-toggle'); -}); - -test('Should format event with "authorization"', () => { - const addon = new WebhookAddon({ getLogger: noLogger }); - const event: IEvent = { - id: 1, - createdAt: new Date(), - createdByUserId: SYSTEM_USER_ID, - type: FEATURE_CREATED, - createdBy: 'some@user.com', - featureName: 'some-toggle', - data: { - name: 'some-toggle', - enabled: false, - strategies: [{ name: 'default' }], - }, - }; - - const parameters = { - url: 'http://test.webhook.com/plain', - bodyTemplate: '{{event.type}} on toggle {{event.data.name}}', - contentType: 'text/plain', - authorization: 'API KEY 123abc', - }; - - addon.handleEvent(event, parameters); - const call = fetchRetryCalls[0]; - expect(fetchRetryCalls.length).toBe(1); - expect(call.url).toBe(parameters.url); - expect(call.options.headers.Authorization).toBe(parameters.authorization); - expect(call.options.body).toBe('feature-created on toggle some-toggle'); -}); - -test('Should handle custom headers', async () => { - const addon = new WebhookAddon({ getLogger: noLogger }); - const event: IEvent = { - id: 1, - createdAt: new Date(), - createdByUserId: SYSTEM_USER_ID, - type: FEATURE_CREATED, - createdBy: 'some@user.com', - featureName: 'some-toggle', - data: { - name: 'some-toggle', - enabled: false, - strategies: [{ name: 'default' }], - }, - }; - - const parameters = { - url: 'http://test.webhook.com/plain', - bodyTemplate: '{{event.type}} on toggle {{event.data.name}}', - contentType: 'text/plain', - authorization: 'API KEY 123abc', - customHeaders: `{ "MY_CUSTOM_HEADER": "MY_CUSTOM_VALUE" }`, - }; - - addon.handleEvent(event, parameters); - const call = fetchRetryCalls[0]; - expect(fetchRetryCalls.length).toBe(1); - expect(call.url).toBe(parameters.url); - expect(call.options.headers.Authorization).toBe(parameters.authorization); - expect(call.options.headers.MY_CUSTOM_HEADER).toBe('MY_CUSTOM_VALUE'); - expect(call.options.body).toBe('feature-created on toggle some-toggle'); + const parameters = { + url: 'http://test.webhook.com', + }; + + addon.handleEvent(event, parameters, INTEGRATION_ID); + expect(fetchRetryCalls.length).toBe(1); + expect(fetchRetryCalls[0].url).toBe(parameters.url); + expect(fetchRetryCalls[0].options.body).toBe(JSON.stringify(event)); + }); + + test('Should format event with "bodyTemplate"', () => { + const addon = new WebhookAddon(ARGS); + const event: IEvent = { + id: 1, + createdAt: new Date(), + createdByUserId: SYSTEM_USER_ID, + type: FEATURE_CREATED, + createdBy: 'some@user.com', + featureName: 'some-toggle', + data: { + name: 'some-toggle', + enabled: false, + strategies: [{ name: 'default' }], + }, + }; + + const parameters = { + url: 'http://test.webhook.com/plain', + bodyTemplate: '{{event.type}} on toggle {{event.data.name}}', + contentType: 'text/plain', + }; + + addon.handleEvent(event, parameters, INTEGRATION_ID); + const call = fetchRetryCalls[0]; + expect(fetchRetryCalls.length).toBe(1); + expect(call.url).toBe(parameters.url); + expect(call.options.headers['Content-Type']).toBe('text/plain'); + expect(call.options.body).toBe('feature-created on toggle some-toggle'); + }); + + test('Should format event with "authorization"', () => { + const addon = new WebhookAddon(ARGS); + const event: IEvent = { + id: 1, + createdAt: new Date(), + createdByUserId: SYSTEM_USER_ID, + type: FEATURE_CREATED, + createdBy: 'some@user.com', + featureName: 'some-toggle', + data: { + name: 'some-toggle', + enabled: false, + strategies: [{ name: 'default' }], + }, + }; + + const parameters = { + url: 'http://test.webhook.com/plain', + bodyTemplate: '{{event.type}} on toggle {{event.data.name}}', + contentType: 'text/plain', + authorization: 'API KEY 123abc', + }; + + addon.handleEvent(event, parameters, INTEGRATION_ID); + const call = fetchRetryCalls[0]; + expect(fetchRetryCalls.length).toBe(1); + expect(call.url).toBe(parameters.url); + expect(call.options.headers.Authorization).toBe( + parameters.authorization, + ); + expect(call.options.body).toBe('feature-created on toggle some-toggle'); + }); + + test('Should handle custom headers', async () => { + const addon = new WebhookAddon(ARGS); + const event: IEvent = { + id: 1, + createdAt: new Date(), + createdByUserId: SYSTEM_USER_ID, + type: FEATURE_CREATED, + createdBy: 'some@user.com', + featureName: 'some-toggle', + data: { + name: 'some-toggle', + enabled: false, + strategies: [{ name: 'default' }], + }, + }; + + const parameters = { + url: 'http://test.webhook.com/plain', + bodyTemplate: '{{event.type}} on toggle {{event.data.name}}', + contentType: 'text/plain', + authorization: 'API KEY 123abc', + customHeaders: `{ "MY_CUSTOM_HEADER": "MY_CUSTOM_VALUE" }`, + }; + + addon.handleEvent(event, parameters, INTEGRATION_ID); + const call = fetchRetryCalls[0]; + expect(fetchRetryCalls.length).toBe(1); + expect(call.url).toBe(parameters.url); + expect(call.options.headers.Authorization).toBe( + parameters.authorization, + ); + expect(call.options.headers.MY_CUSTOM_HEADER).toBe('MY_CUSTOM_VALUE'); + expect(call.options.body).toBe('feature-created on toggle some-toggle'); + }); + + test('Should call registerEvent', async () => { + const addon = new WebhookAddon(ARGS); + const event: IEvent = { + id: 1, + createdAt: new Date(), + createdByUserId: SYSTEM_USER_ID, + type: FEATURE_CREATED, + createdBy: 'some@user.com', + featureName: 'some-toggle', + data: { + name: 'some-toggle', + enabled: false, + strategies: [{ name: 'default' }], + }, + }; + + const parameters = { + url: 'http://test.webhook.com/plain', + bodyTemplate: '{{event.type}} on toggle {{event.data.name}}', + contentType: 'text/plain', + authorization: 'API KEY 123abc', + customHeaders: `{ "MY_CUSTOM_HEADER": "MY_CUSTOM_VALUE" }`, + }; + + await addon.handleEvent(event, parameters, INTEGRATION_ID); + + expect(registerEventMock).toHaveBeenCalledTimes(1); + expect(registerEventMock).toHaveBeenCalledWith({ + integrationId: INTEGRATION_ID, + state: 'success', + stateDetails: + 'Webhook request was successful with status code: 200', + event: serializeDates(event), + details: { + url: parameters.url, + contentType: parameters.contentType, + body: 'feature-created on toggle some-toggle', + }, + }); + }); }); diff --git a/src/lib/addons/webhook.ts b/src/lib/addons/webhook.ts index 1d0e7eec89..8b7c67d686 100644 --- a/src/lib/addons/webhook.ts +++ b/src/lib/addons/webhook.ts @@ -1,8 +1,9 @@ import Mustache from 'mustache'; import Addon from './addon'; import definition from './webhook-definition'; -import type { LogProvider } from '../logger'; import type { IEvent } from '../types/events'; +import { type IAddonConfig, serializeDates } from '../types'; +import type { IntegrationEventState } from '../features/integration-events/integration-events-store'; interface IParameters { url: string; @@ -13,11 +14,18 @@ interface IParameters { } export default class Webhook extends Addon { - constructor(args: { getLogger: LogProvider }) { + constructor(args: IAddonConfig) { super(definition, args); } - async handleEvent(event: IEvent, parameters: IParameters): Promise { + async handleEvent( + event: IEvent, + parameters: IParameters, + integrationId: number, + ): Promise { + let state: IntegrationEventState = 'success'; + let stateDetails = ''; + const { url, bodyTemplate, contentType, authorization, customHeaders } = parameters; const context = { @@ -39,9 +47,10 @@ export default class Webhook extends Addon { try { extraHeaders = JSON.parse(customHeaders); } catch (e) { - this.logger.warn( - `Could not parse the json in the customHeaders parameter. [${customHeaders}]`, - ); + state = 'successWithErrors'; + stateDetails = + 'Could not parse the JSON in the customHeaders parameter.'; + this.logger.warn(stateDetails); } } const requestOpts = { @@ -58,5 +67,24 @@ export default class Webhook extends Addon { this.logger.info( `Handled event "${event.type}". Status code: ${res.status}`, ); + + if (res.ok) { + stateDetails = `Webhook request was successful with status code: ${res.status}`; + } else { + state = 'failed'; + stateDetails = `Webhook request failed with status code: ${res.status}`; + } + + this.registerEvent({ + integrationId, + state, + stateDetails, + event: serializeDates(event), + details: { + url, + contentType, + body, + }, + }); } } diff --git a/src/lib/features/integration-events/integration-events-store.ts b/src/lib/features/integration-events/integration-events-store.ts index 1bb7824122..31bf87d4ad 100644 --- a/src/lib/features/integration-events/integration-events-store.ts +++ b/src/lib/features/integration-events/integration-events-store.ts @@ -7,6 +7,8 @@ export type IntegrationEventWriteModel = Omit< 'id' | 'createdAt' >; +export type IntegrationEventState = IntegrationEventWriteModel['state']; + export class IntegrationEventsStore extends CRUDStore< IntegrationEventSchema, IntegrationEventWriteModel diff --git a/src/lib/services/addon-service-test-simple-addon.ts b/src/lib/services/addon-service-test-simple-addon.ts index ea80a7febd..d159db68a1 100644 --- a/src/lib/services/addon-service-test-simple-addon.ts +++ b/src/lib/services/addon-service-test-simple-addon.ts @@ -1,6 +1,6 @@ import Addon from '../addons/addon'; import getLogger from '../../test/fixtures/no-logger'; -import type { IAddonDefinition } from '../types/model'; +import type { IAddonConfig, IAddonDefinition } from '../types/model'; import { FEATURE_ARCHIVED, FEATURE_CREATED, @@ -8,6 +8,14 @@ import { FEATURE_UPDATED, type IEvent, } from '../types/events'; +import type { IFlagResolver, IntegrationEventsService } from '../internals'; + +const ARGS: IAddonConfig = { + getLogger, + unleashUrl: 'http://some-url.com', + integrationEventsService: {} as IntegrationEventsService, + flagResolver: {} as IFlagResolver, +}; const definition: IAddonDefinition = { name: 'simple', @@ -57,7 +65,7 @@ export default class SimpleAddon extends Addon { events: any[]; constructor() { - super(definition, { getLogger }); + super(definition, ARGS); this.events = []; } diff --git a/src/lib/services/addon-service.test.ts b/src/lib/services/addon-service.test.ts index 50d8aba708..b19224c6f0 100644 --- a/src/lib/services/addon-service.test.ts +++ b/src/lib/services/addon-service.test.ts @@ -15,8 +15,9 @@ import type { IAddonDto } from '../types/stores/addon-store'; import SimpleAddon from './addon-service-test-simple-addon'; import type { IAddonProviders } from '../addons'; import EventService from '../features/events/event-service'; -import { SYSTEM_USER, TEST_AUDIT_USER } from '../types'; +import { type IFlagResolver, SYSTEM_USER, TEST_AUDIT_USER } from '../types'; import EventEmitter from 'node:events'; +import { IntegrationEventsService } from '../internals'; const MASKED_VALUE = '*****'; @@ -35,6 +36,10 @@ function getSetup() { { getLogger }, eventService, ); + const integrationEventsService = new IntegrationEventsService(stores, { + getLogger, + flagResolver: {} as IFlagResolver, + }); addonProvider = { simple: new SimpleAddon() }; return { @@ -47,6 +52,7 @@ function getSetup() { }, tagTypeService, eventService, + integrationEventsService, addonProvider, ), eventService, diff --git a/src/lib/services/addon-service.ts b/src/lib/services/addon-service.ts index b680c2a666..76c1f1b37c 100644 --- a/src/lib/services/addon-service.ts +++ b/src/lib/services/addon-service.ts @@ -67,6 +67,7 @@ export default class AddonService { }: Pick, tagTypeService: TagTypeService, eventService: EventService, + integrationEventsService, addons?: IAddonProviders, ) { this.addonStore = addonStore; @@ -80,6 +81,7 @@ export default class AddonService { getAddons({ getLogger, unleashUrl: server.unleashUrl, + integrationEventsService, flagResolver, }); this.sensitiveParams = this.loadSensitiveParams(this.addonProviders); @@ -145,6 +147,7 @@ export default class AddonService { addonProviders[addon.provider].handleEvent( event, addon.parameters, + addon.id, ), ); }); diff --git a/src/lib/services/index.ts b/src/lib/services/index.ts index 8a4f04ef60..918891b94b 100644 --- a/src/lib/services/index.ts +++ b/src/lib/services/index.ts @@ -201,6 +201,7 @@ export const createServices = ( config, tagTypeService, eventService, + integrationEventsService, ); const sessionService = new SessionService(stores, config); const settingService = new SettingService(stores, config, eventService); diff --git a/src/lib/types/model.ts b/src/lib/types/model.ts index d66fc87863..d2d9015597 100644 --- a/src/lib/types/model.ts +++ b/src/lib/types/model.ts @@ -7,6 +7,8 @@ import type { IProjectStats } from '../features/project/project-service'; import type { CreateFeatureStrategySchema } from '../openapi'; import type { ProjectEnvironment } from '../features/project/project-store-type'; import type { FeatureSearchEnvironmentSchema } from '../openapi/spec/feature-search-environment-schema'; +import type { IntegrationEventsService } from '../features/integration-events/integration-events-service'; +import type { IFlagResolver } from './experimental'; export type Operator = (typeof ALL_OPERATORS)[number]; @@ -376,6 +378,8 @@ export interface IAddonAlert { export interface IAddonConfig { getLogger: LogProvider; unleashUrl: string; + integrationEventsService: IntegrationEventsService; + flagResolver: IFlagResolver; } export interface IUserWithRole { diff --git a/src/test/e2e/services/addon-service.e2e.test.ts b/src/test/e2e/services/addon-service.e2e.test.ts index ddcfb5d1f3..32043d0030 100644 --- a/src/test/e2e/services/addon-service.e2e.test.ts +++ b/src/test/e2e/services/addon-service.e2e.test.ts @@ -7,7 +7,7 @@ import { type IUnleashStores, TEST_AUDIT_USER } from '../../../lib/types'; import SimpleAddon from '../../../lib/services/addon-service-test-simple-addon'; import TagTypeService from '../../../lib/features/tag-type/tag-type-service'; import { FEATURE_CREATED } from '../../../lib/types/events'; -import { EventService } from '../../../lib/services'; +import { EventService, IntegrationEventsService } from '../../../lib/services'; const addonProvider = { simple: new SimpleAddon() }; @@ -24,11 +24,16 @@ beforeAll(async () => { stores = db.stores; const eventService = new EventService(stores, config); const tagTypeService = new TagTypeService(stores, config, eventService); + const integrationEventsService = new IntegrationEventsService( + stores, + config, + ); addonService = new AddonService( stores, config, tagTypeService, eventService, + integrationEventsService, addonProvider, ); });