mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	chore: register integration events in New Relic integration (#7636)
https://linear.app/unleash/issue/2-2462/register-integration-events-new-relic Registers integration events in the **New Relic** integration. Similar to: - #7635 - #7634 - #7631 - #7626 - #7621
This commit is contained in:
		
							parent
							
								
									fe6a758f80
								
							
						
					
					
						commit
						9ff393b3d7
					
				| @ -1,6 +1,6 @@ | ||||
| // Jest Snapshot v1, https://goo.gl/fbAQLP | ||||
| 
 | ||||
| exports[`Should call New Relic Event API for $type toggle 1`] = ` | ||||
| exports[`New Relic integration Should call New Relic Event API for $type toggle 1`] = ` | ||||
| { | ||||
|   "Api-Key": "fakeLicenseKey", | ||||
|   "Content-Encoding": "gzip", | ||||
| @ -8,7 +8,7 @@ exports[`Should call New Relic Event API for $type toggle 1`] = ` | ||||
| } | ||||
| `; | ||||
| 
 | ||||
| exports[`Should call New Relic Event API for FEATURE_ARCHIVED toggle with project info 1`] = ` | ||||
| exports[`New Relic integration Should call New Relic Event API for FEATURE_ARCHIVED toggle with project info 1`] = ` | ||||
| { | ||||
|   "Api-Key": "fakeLicenseKey", | ||||
|   "Content-Encoding": "gzip", | ||||
| @ -16,7 +16,7 @@ exports[`Should call New Relic Event API for FEATURE_ARCHIVED toggle with projec | ||||
| } | ||||
| `; | ||||
| 
 | ||||
| exports[`Should call New Relic Event API for FEATURE_ARCHIVED with project info 1`] = ` | ||||
| exports[`New Relic integration Should call New Relic Event API for FEATURE_ARCHIVED with project info 1`] = ` | ||||
| { | ||||
|   "Api-Key": "fakeLicenseKey", | ||||
|   "Content-Encoding": "gzip", | ||||
| @ -24,7 +24,7 @@ exports[`Should call New Relic Event API for FEATURE_ARCHIVED with project info | ||||
| } | ||||
| `; | ||||
| 
 | ||||
| exports[`Should call New Relic Event API for custom body template 1`] = ` | ||||
| exports[`New Relic integration Should call New Relic Event API for custom body template 1`] = ` | ||||
| { | ||||
|   "Api-Key": "fakeLicenseKey", | ||||
|   "Content-Encoding": "gzip", | ||||
| @ -32,7 +32,7 @@ exports[`Should call New Relic Event API for custom body template 1`] = ` | ||||
| } | ||||
| `; | ||||
| 
 | ||||
| exports[`Should call New Relic Event API for customHeaders in headers when calling service 1`] = ` | ||||
| exports[`New Relic integration Should call New Relic Event API for customHeaders in headers when calling service 1`] = ` | ||||
| { | ||||
|   "Api-Key": "fakeLicenseKey", | ||||
|   "Content-Encoding": "gzip", | ||||
| @ -41,7 +41,7 @@ exports[`Should call New Relic Event API for customHeaders in headers when calli | ||||
| } | ||||
| `; | ||||
| 
 | ||||
| exports[`Should call New Relic Event API for toggled environment 1`] = ` | ||||
| exports[`New Relic integration Should call New Relic Event API for toggled environment 1`] = ` | ||||
| { | ||||
|   "Api-Key": "fakeLicenseKey", | ||||
|   "Content-Encoding": "gzip", | ||||
|  | ||||
| @ -5,6 +5,7 @@ import { | ||||
|     type IFlagResolver, | ||||
|     type IAddonConfig, | ||||
|     type IEvent, | ||||
|     serializeDates, | ||||
| } from '../types'; | ||||
| import type { Logger } from '../logger'; | ||||
| 
 | ||||
| @ -18,6 +19,7 @@ import type { IntegrationEventsService } from '../services'; | ||||
| const asyncGunzip = promisify(gunzip); | ||||
| 
 | ||||
| let fetchRetryCalls: any[] = []; | ||||
| const registerEventMock = jest.fn(); | ||||
| 
 | ||||
| const INTEGRATION_ID = 1337; | ||||
| const ARGS: IAddonConfig = { | ||||
| @ -45,11 +47,11 @@ jest.mock( | ||||
|                     retries, | ||||
|                     backoff, | ||||
|                 }); | ||||
|                 return Promise.resolve({ status: 200 }); | ||||
|                 return Promise.resolve({ ok: true, status: 200 }); | ||||
|             } | ||||
| 
 | ||||
|             async registerEvent(_) { | ||||
|                 return Promise.resolve(); | ||||
|             async registerEvent(event) { | ||||
|                 return registerEventMock(event); | ||||
|             } | ||||
|         }, | ||||
| ); | ||||
| @ -79,98 +81,132 @@ const makeAddHandleEvent = (event: IEvent, parameters: INewRelicParameters) => { | ||||
|     return () => addon.handleEvent(event, parameters, INTEGRATION_ID); | ||||
| }; | ||||
| 
 | ||||
| test.each([ | ||||
|     { | ||||
|         partialEvent: { type: FEATURE_CREATED }, | ||||
|         test: '$type toggle', | ||||
|     }, | ||||
|     { | ||||
|         partialEvent: { | ||||
|             type: FEATURE_ARCHIVED, | ||||
|             data: { | ||||
|                 name: 'some-toggle', | ||||
|             }, | ||||
|         }, | ||||
|         test: 'FEATURE_ARCHIVED toggle with project info', | ||||
|     }, | ||||
|     { | ||||
|         partialEvent: { | ||||
|             type: FEATURE_ARCHIVED, | ||||
|             project: 'some-project', | ||||
|             data: { | ||||
|                 name: 'some-toggle', | ||||
|             }, | ||||
|         }, | ||||
|         test: 'FEATURE_ARCHIVED with project info', | ||||
|     }, | ||||
|     { | ||||
|         partialEvent: { | ||||
|             type: FEATURE_ENVIRONMENT_DISABLED, | ||||
|             environment: 'development', | ||||
|         }, | ||||
|         test: 'toggled environment', | ||||
|     }, | ||||
|     { | ||||
|         partialEvent: { | ||||
|             type: FEATURE_ENVIRONMENT_DISABLED, | ||||
|             environment: 'development', | ||||
|         }, | ||||
|         partialParameters: { | ||||
|             customHeaders: `{ "MY_CUSTOM_HEADER": "MY_CUSTOM_VALUE" }`, | ||||
|         }, | ||||
|         test: 'customHeaders in headers when calling service', | ||||
|     }, | ||||
|     { | ||||
|         partialEvent: { | ||||
|             type: FEATURE_ENVIRONMENT_DISABLED, | ||||
|             environment: 'development', | ||||
|         }, | ||||
|         partialParameters: { | ||||
|             bodyTemplate: | ||||
|                 '{\n  "eventType": "{{event.type}}",\n  "createdBy": "{{event.createdBy}}"\n}', | ||||
|         }, | ||||
|         test: 'custom body template', | ||||
|     }, | ||||
| ] as Array<{ | ||||
|     partialEvent: Partial<IEvent>; | ||||
|     partialParameters?: Partial<INewRelicParameters>; | ||||
|     test: String; | ||||
| }>)( | ||||
|     'Should call New Relic Event API for $test', | ||||
|     async ({ partialEvent, partialParameters }) => { | ||||
|         const event = { | ||||
|             ...defaultEvent, | ||||
|             ...partialEvent, | ||||
|         }; | ||||
| describe('New Relic integration', () => { | ||||
|     beforeEach(() => { | ||||
|         registerEventMock.mockClear(); | ||||
|     }); | ||||
| 
 | ||||
|         const parameters = { | ||||
|             ...defaultParameters, | ||||
|             ...partialParameters, | ||||
|         }; | ||||
|     test.each([ | ||||
|         { | ||||
|             partialEvent: { type: FEATURE_CREATED }, | ||||
|             test: '$type toggle', | ||||
|         }, | ||||
|         { | ||||
|             partialEvent: { | ||||
|                 type: FEATURE_ARCHIVED, | ||||
|                 data: { | ||||
|                     name: 'some-toggle', | ||||
|                 }, | ||||
|             }, | ||||
|             test: 'FEATURE_ARCHIVED toggle with project info', | ||||
|         }, | ||||
|         { | ||||
|             partialEvent: { | ||||
|                 type: FEATURE_ARCHIVED, | ||||
|                 project: 'some-project', | ||||
|                 data: { | ||||
|                     name: 'some-toggle', | ||||
|                 }, | ||||
|             }, | ||||
|             test: 'FEATURE_ARCHIVED with project info', | ||||
|         }, | ||||
|         { | ||||
|             partialEvent: { | ||||
|                 type: FEATURE_ENVIRONMENT_DISABLED, | ||||
|                 environment: 'development', | ||||
|             }, | ||||
|             test: 'toggled environment', | ||||
|         }, | ||||
|         { | ||||
|             partialEvent: { | ||||
|                 type: FEATURE_ENVIRONMENT_DISABLED, | ||||
|                 environment: 'development', | ||||
|             }, | ||||
|             partialParameters: { | ||||
|                 customHeaders: `{ "MY_CUSTOM_HEADER": "MY_CUSTOM_VALUE" }`, | ||||
|             }, | ||||
|             test: 'customHeaders in headers when calling service', | ||||
|         }, | ||||
|         { | ||||
|             partialEvent: { | ||||
|                 type: FEATURE_ENVIRONMENT_DISABLED, | ||||
|                 environment: 'development', | ||||
|             }, | ||||
|             partialParameters: { | ||||
|                 bodyTemplate: | ||||
|                     '{\n  "eventType": "{{event.type}}",\n  "createdBy": "{{event.createdBy}}"\n}', | ||||
|             }, | ||||
|             test: 'custom body template', | ||||
|         }, | ||||
|     ] as Array<{ | ||||
|         partialEvent: Partial<IEvent>; | ||||
|         partialParameters?: Partial<INewRelicParameters>; | ||||
|         test: String; | ||||
|     }>)( | ||||
|         'Should call New Relic Event API for $test', | ||||
|         async ({ partialEvent, partialParameters }) => { | ||||
|             const event = { | ||||
|                 ...defaultEvent, | ||||
|                 ...partialEvent, | ||||
|             }; | ||||
| 
 | ||||
|         const handleEvent = makeAddHandleEvent(event, parameters); | ||||
|             const parameters = { | ||||
|                 ...defaultParameters, | ||||
|                 ...partialParameters, | ||||
|             }; | ||||
| 
 | ||||
|             const handleEvent = makeAddHandleEvent(event, parameters); | ||||
| 
 | ||||
|             await handleEvent(); | ||||
|             expect(fetchRetryCalls.length).toBe(1); | ||||
| 
 | ||||
|             const { url, options } = fetchRetryCalls[0]; | ||||
|             const jsonBody = JSON.parse( | ||||
|                 (await asyncGunzip(options.body)).toString(), | ||||
|             ); | ||||
| 
 | ||||
|             expect(url).toBe(parameters.url); | ||||
|             expect(options.method).toBe('POST'); | ||||
|             expect(options.headers['Api-Key']).toBe(parameters.licenseKey); | ||||
|             expect(options.headers['Content-Type']).toBe('application/json'); | ||||
|             expect(options.headers['Content-Encoding']).toBe('gzip'); | ||||
|             expect(options.headers).toMatchSnapshot(); | ||||
| 
 | ||||
|             expect(jsonBody.eventType).toBe('UnleashServiceEvent'); | ||||
|             expect(jsonBody.unleashEventType).toBe(event.type); | ||||
|             expect(jsonBody.featureName).toBe(event.data.name); | ||||
|             expect(jsonBody.environment).toBe(event.environment); | ||||
|             expect(jsonBody.createdBy).toBe(event.createdBy); | ||||
|             expect(jsonBody.createdByUserId).toBe(event.createdByUserId); | ||||
|             expect(jsonBody.createdAt).toBe(event.createdAt.getTime()); | ||||
|         }, | ||||
|     ); | ||||
| 
 | ||||
|     test('Should call registerEvent', async () => { | ||||
|         const handleEvent = makeAddHandleEvent(defaultEvent, defaultParameters); | ||||
| 
 | ||||
|         await handleEvent(); | ||||
|         expect(fetchRetryCalls.length).toBe(1); | ||||
| 
 | ||||
|         const { url, options } = fetchRetryCalls[0]; | ||||
|         const jsonBody = JSON.parse( | ||||
|             (await asyncGunzip(options.body)).toString(), | ||||
|         ); | ||||
| 
 | ||||
|         expect(url).toBe(parameters.url); | ||||
|         expect(options.method).toBe('POST'); | ||||
|         expect(options.headers['Api-Key']).toBe(parameters.licenseKey); | ||||
|         expect(options.headers['Content-Type']).toBe('application/json'); | ||||
|         expect(options.headers['Content-Encoding']).toBe('gzip'); | ||||
|         expect(options.headers).toMatchSnapshot(); | ||||
| 
 | ||||
|         expect(jsonBody.eventType).toBe('UnleashServiceEvent'); | ||||
|         expect(jsonBody.unleashEventType).toBe(event.type); | ||||
|         expect(jsonBody.featureName).toBe(event.data.name); | ||||
|         expect(jsonBody.environment).toBe(event.environment); | ||||
|         expect(jsonBody.createdBy).toBe(event.createdBy); | ||||
|         expect(jsonBody.createdByUserId).toBe(event.createdByUserId); | ||||
|         expect(jsonBody.createdAt).toBe(event.createdAt.getTime()); | ||||
|     }, | ||||
| ); | ||||
|         expect(registerEventMock).toHaveBeenCalledTimes(1); | ||||
|         expect(registerEventMock).toHaveBeenCalledWith({ | ||||
|             integrationId: INTEGRATION_ID, | ||||
|             state: 'success', | ||||
|             stateDetails: | ||||
|                 'New Relic Events API request was successful with status code: 200.', | ||||
|             event: serializeDates(defaultEvent), | ||||
|             details: { | ||||
|                 url: defaultParameters.url, | ||||
|                 body: { | ||||
|                     eventType: 'UnleashServiceEvent', | ||||
|                     unleashEventType: defaultEvent.type, | ||||
|                     featureName: defaultEvent.featureName, | ||||
|                     environment: defaultEvent.environment, | ||||
|                     createdBy: defaultEvent.createdBy, | ||||
|                     createdByUserId: defaultEvent.createdByUserId, | ||||
|                     createdAt: defaultEvent.createdAt.getTime(), | ||||
|                     ...defaultEvent.data, | ||||
|                 }, | ||||
|             }, | ||||
|         }); | ||||
|     }); | ||||
| }); | ||||
|  | ||||
| @ -2,7 +2,12 @@ import Addon from './addon'; | ||||
| 
 | ||||
| import definition from './new-relic-definition'; | ||||
| import Mustache from 'mustache'; | ||||
| import type { IAddonConfig, IEvent, IEventType } from '../types'; | ||||
| import { | ||||
|     type IAddonConfig, | ||||
|     type IEvent, | ||||
|     type IEventType, | ||||
|     serializeDates, | ||||
| } from '../types'; | ||||
| import { | ||||
|     type FeatureEventFormatter, | ||||
|     FeatureEventFormatterMd, | ||||
| @ -10,6 +15,7 @@ import { | ||||
| } from './feature-event-formatter-md'; | ||||
| import { gzip } from 'node:zlib'; | ||||
| import { promisify } from 'util'; | ||||
| import type { IntegrationEventState } from '../features/integration-events/integration-events-store'; | ||||
| 
 | ||||
| const asyncGzip = promisify(gzip); | ||||
| 
 | ||||
| @ -46,6 +52,9 @@ export default class NewRelicAddon extends Addon { | ||||
|         parameters: INewRelicParameters, | ||||
|         integrationId: number, | ||||
|     ): Promise<void> { | ||||
|         let state: IntegrationEventState = 'success'; | ||||
|         const stateDetails: string[] = []; | ||||
| 
 | ||||
|         const { url, licenseKey, customHeaders, bodyTemplate } = parameters; | ||||
|         const context = { | ||||
|             event, | ||||
| @ -74,9 +83,11 @@ export default class NewRelicAddon extends Addon { | ||||
|             try { | ||||
|                 extraHeaders = JSON.parse(customHeaders); | ||||
|             } catch (e) { | ||||
|                 this.logger.warn( | ||||
|                     `Could not parse the json in the customHeaders parameter. [${customHeaders}]`, | ||||
|                 ); | ||||
|                 state = 'successWithErrors'; | ||||
|                 const badHeadersMessage = | ||||
|                     'Could not parse the JSON in the customHeaders parameter.'; | ||||
|                 stateDetails.push(badHeadersMessage); | ||||
|                 this.logger.warn(badHeadersMessage); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| @ -92,8 +103,29 @@ export default class NewRelicAddon extends Addon { | ||||
|         }; | ||||
| 
 | ||||
|         const res = await this.fetchRetry(url, requestOpts); | ||||
|         this.logger.info( | ||||
|             `Handled event ${event.type}. Status codes=${res.status}`, | ||||
|         ); | ||||
| 
 | ||||
|         this.logger.info(`Handled event "${event.type}".`); | ||||
| 
 | ||||
|         if (res.ok) { | ||||
|             const successMessage = `New Relic Events API request was successful with status code: ${res.status}.`; | ||||
|             stateDetails.push(successMessage); | ||||
|             this.logger.info(successMessage); | ||||
|         } else { | ||||
|             state = 'failed'; | ||||
|             const failedMessage = `New Relic Events API request failed with status code: ${res.status}.`; | ||||
|             stateDetails.push(failedMessage); | ||||
|             this.logger.warn(failedMessage); | ||||
|         } | ||||
| 
 | ||||
|         this.registerEvent({ | ||||
|             integrationId, | ||||
|             state, | ||||
|             stateDetails: stateDetails.join('\n'), | ||||
|             event: serializeDates(event), | ||||
|             details: { | ||||
|                 url, | ||||
|                 body, | ||||
|             }, | ||||
|         }); | ||||
|     } | ||||
| } | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user