mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	Feat: remove last seen refactor flag (#5423)
What it says on the box --------- Signed-off-by: andreas-unleash <andreas@getunleash.ai> Co-authored-by: andreas-unleash <andreas@getunleash.ai>
This commit is contained in:
		
							parent
							
								
									ef8edf9c44
								
							
						
					
					
						commit
						e5760b5690
					
				| @ -102,7 +102,6 @@ exports[`should create default config 1`] = ` | ||||
|       "responseTimeWithAppNameKillSwitch": false, | ||||
|       "scheduledConfigurationChanges": false, | ||||
|       "strictSchemaValidation": false, | ||||
|       "useLastSeenRefactor": false, | ||||
|     }, | ||||
|     "externalResolver": { | ||||
|       "getVariant": [Function], | ||||
|  | ||||
| @ -189,9 +189,7 @@ export class FeatureToggleRowConverter { | ||||
|             feature.createdAt = r.created_at; | ||||
|             feature.favorite = r.favorite; | ||||
| 
 | ||||
|             if (this.flagResolver.isEnabled('useLastSeenRefactor')) { | ||||
|                 this.addLastSeenByEnvironment(feature, r); | ||||
|             } | ||||
|             this.addLastSeenByEnvironment(feature, r); | ||||
| 
 | ||||
|             acc[r.name] = feature; | ||||
|             return acc; | ||||
| @ -246,9 +244,7 @@ export class FeatureToggleRowConverter { | ||||
|             feature.lastSeenAt = row.last_seen_at; | ||||
|             feature.archivedAt = row.archived_at; | ||||
| 
 | ||||
|             if (this.flagResolver.isEnabled('useLastSeenRefactor')) { | ||||
|                 this.addLastSeenByEnvironment(feature, row); | ||||
|             } | ||||
|             this.addLastSeenByEnvironment(feature, row); | ||||
| 
 | ||||
|             acc[row.name] = feature; | ||||
|             return acc; | ||||
|  | ||||
| @ -1970,13 +1970,7 @@ class FeatureToggleService { | ||||
|         archived: boolean, | ||||
|         userId: number, | ||||
|     ): Promise<FeatureToggle[]> { | ||||
|         let features; | ||||
| 
 | ||||
|         if (this.flagResolver.isEnabled('useLastSeenRefactor')) { | ||||
|             features = await this.featureToggleStore.getArchivedFeatures(); | ||||
|         } else { | ||||
|             features = await this.featureToggleStore.getAll({ archived }); | ||||
|         } | ||||
|         const features = await this.featureToggleStore.getArchivedFeatures(); | ||||
| 
 | ||||
|         if (this.flagResolver.isEnabled('privateProjects')) { | ||||
|             const projectAccess = | ||||
| @ -1998,11 +1992,7 @@ class FeatureToggleService { | ||||
|         archived: boolean, | ||||
|         project: string, | ||||
|     ): Promise<FeatureToggle[]> { | ||||
|         if (this.flagResolver.isEnabled('useLastSeenRefactor')) { | ||||
|             return this.featureToggleStore.getArchivedFeatures(project); | ||||
|         } else { | ||||
|             return this.featureToggleStore.getAll({ archived, project }); | ||||
|         } | ||||
|         return this.featureToggleStore.getArchivedFeatures(project); | ||||
|     } | ||||
| 
 | ||||
|     async getProjectId(name: string): Promise<string | undefined> { | ||||
|  | ||||
| @ -171,15 +171,13 @@ export default class FeatureToggleStore implements IFeatureToggleStore { | ||||
|         builder.addSelectColumn('ft.tag_value as tag_value'); | ||||
|         builder.addSelectColumn('ft.tag_type as tag_type'); | ||||
| 
 | ||||
|         if (this.flagResolver.isEnabled('useLastSeenRefactor')) { | ||||
|             builder.withLastSeenByEnvironment(archived); | ||||
|             builder.addSelectColumn( | ||||
|                 'last_seen_at_metrics.last_seen_at as env_last_seen_at', | ||||
|             ); | ||||
|             builder.addSelectColumn( | ||||
|                 'last_seen_at_metrics.environment as last_seen_at_env', | ||||
|             ); | ||||
|         } | ||||
|         builder.withLastSeenByEnvironment(archived); | ||||
|         builder.addSelectColumn( | ||||
|             'last_seen_at_metrics.last_seen_at as env_last_seen_at', | ||||
|         ); | ||||
|         builder.addSelectColumn( | ||||
|             'last_seen_at_metrics.environment as last_seen_at_env', | ||||
|         ); | ||||
| 
 | ||||
|         if (userId) { | ||||
|             builder.withFavorites(userId); | ||||
|  | ||||
| @ -341,23 +341,21 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore { | ||||
| 
 | ||||
|         let selectColumns = ['features_view.*'] as (string | Raw<any>)[]; | ||||
| 
 | ||||
|         if (this.flagResolver.isEnabled('useLastSeenRefactor')) { | ||||
|             query.leftJoin('last_seen_at_metrics', function () { | ||||
|                 this.on( | ||||
|                     'last_seen_at_metrics.environment', | ||||
|                     '=', | ||||
|                     'features_view.environment_name', | ||||
|                 ).andOn( | ||||
|                     'last_seen_at_metrics.feature_name', | ||||
|                     '=', | ||||
|                     'features_view.name', | ||||
|                 ); | ||||
|             }); | ||||
|             // Override feature view for now
 | ||||
|             selectColumns.push( | ||||
|                 'last_seen_at_metrics.last_seen_at as env_last_seen_at', | ||||
|         query.leftJoin('last_seen_at_metrics', function () { | ||||
|             this.on( | ||||
|                 'last_seen_at_metrics.environment', | ||||
|                 '=', | ||||
|                 'features_view.environment_name', | ||||
|             ).andOn( | ||||
|                 'last_seen_at_metrics.feature_name', | ||||
|                 '=', | ||||
|                 'features_view.name', | ||||
|             ); | ||||
|         } | ||||
|         }); | ||||
|         // Override feature view for now
 | ||||
|         selectColumns.push( | ||||
|             'last_seen_at_metrics.last_seen_at as env_last_seen_at', | ||||
|         ); | ||||
| 
 | ||||
|         if (userId) { | ||||
|             query = query.leftJoin(`favorite_features`, function () { | ||||
| @ -631,19 +629,17 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore { | ||||
|                         'segments.id', | ||||
|                     ); | ||||
| 
 | ||||
|                 if (this.flagResolver.isEnabled('useLastSeenRefactor')) { | ||||
|                     query.leftJoin('last_seen_at_metrics', function () { | ||||
|                         this.on( | ||||
|                             'last_seen_at_metrics.environment', | ||||
|                             '=', | ||||
|                             'environments.name', | ||||
|                         ).andOn( | ||||
|                             'last_seen_at_metrics.feature_name', | ||||
|                             '=', | ||||
|                             'features.name', | ||||
|                         ); | ||||
|                     }); | ||||
|                 } | ||||
|                 query.leftJoin('last_seen_at_metrics', function () { | ||||
|                     this.on( | ||||
|                         'last_seen_at_metrics.environment', | ||||
|                         '=', | ||||
|                         'environments.name', | ||||
|                     ).andOn( | ||||
|                         'last_seen_at_metrics.feature_name', | ||||
|                         '=', | ||||
|                         'features.name', | ||||
|                     ); | ||||
|                 }); | ||||
| 
 | ||||
|                 let selectColumns = [ | ||||
|                     'features.name as feature_name', | ||||
| @ -664,11 +660,8 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore { | ||||
|                     'segments.name as segment_name', | ||||
|                 ] as (string | Raw<any> | Knex.QueryBuilder)[]; | ||||
| 
 | ||||
|                 let lastSeenQuery = 'feature_environments.last_seen_at'; | ||||
|                 if (this.flagResolver.isEnabled('useLastSeenRefactor')) { | ||||
|                     lastSeenQuery = 'last_seen_at_metrics.last_seen_at'; | ||||
|                     selectColumns.push(`${lastSeenQuery} as env_last_seen_at`); | ||||
|                 } | ||||
|                 const lastSeenQuery = 'last_seen_at_metrics.last_seen_at'; | ||||
|                 selectColumns.push(`${lastSeenQuery} as env_last_seen_at`); | ||||
| 
 | ||||
|                 if (userId) { | ||||
|                     query.leftJoin(`favorite_features`, function () { | ||||
| @ -802,19 +795,13 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore { | ||||
|             ) | ||||
|             .leftJoin('feature_tag as ft', 'ft.feature_name', 'features.name'); | ||||
| 
 | ||||
|         if (this.flagResolver.isEnabled('useLastSeenRefactor')) { | ||||
|             query.leftJoin('last_seen_at_metrics', function () { | ||||
|                 this.on( | ||||
|                     'last_seen_at_metrics.environment', | ||||
|                     '=', | ||||
|                     'environments.name', | ||||
|                 ).andOn( | ||||
|                     'last_seen_at_metrics.feature_name', | ||||
|                     '=', | ||||
|                     'features.name', | ||||
|                 ); | ||||
|             }); | ||||
|         } | ||||
|         query.leftJoin('last_seen_at_metrics', function () { | ||||
|             this.on( | ||||
|                 'last_seen_at_metrics.environment', | ||||
|                 '=', | ||||
|                 'environments.name', | ||||
|             ).andOn('last_seen_at_metrics.feature_name', '=', 'features.name'); | ||||
|         }); | ||||
| 
 | ||||
|         let selectColumns = [ | ||||
|             'features.name as feature_name', | ||||
| @ -833,15 +820,9 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore { | ||||
|             'ft.tag_type as tag_type', | ||||
|         ] as (string | Raw<any> | Knex.QueryBuilder)[]; | ||||
| 
 | ||||
|         if (this.flagResolver.isEnabled('useLastSeenRefactor')) { | ||||
|             selectColumns.push( | ||||
|                 'last_seen_at_metrics.last_seen_at as env_last_seen_at', | ||||
|             ); | ||||
|         } else { | ||||
|             selectColumns.push( | ||||
|                 'feature_environments.last_seen_at as env_last_seen_at', | ||||
|             ); | ||||
|         } | ||||
|         selectColumns.push( | ||||
|             'last_seen_at_metrics.last_seen_at as env_last_seen_at', | ||||
|         ); | ||||
| 
 | ||||
|         if (userId) { | ||||
|             query = query.leftJoin(`favorite_features`, function () { | ||||
|  | ||||
| @ -22,7 +22,6 @@ beforeAll(async () => { | ||||
|         experimental: { | ||||
|             flags: { | ||||
|                 strictSchemaValidation: true, | ||||
|                 useLastSeenRefactor: true, | ||||
|             }, | ||||
|         }, | ||||
|     }; | ||||
|  | ||||
| @ -689,9 +689,7 @@ test('Should return last seen at per environment', async () => { | ||||
|     expect(environments[0].lastSeenAt).toEqual(new Date(date)); | ||||
| 
 | ||||
|     // Test with feature flag on
 | ||||
|     const config = createTestConfig({ | ||||
|         experimental: { flags: { useLastSeenRefactor: true } }, | ||||
|     }); | ||||
|     const config = createTestConfig(); | ||||
| 
 | ||||
|     const featureService = createFeatureToggleService(db.rawDatabase, config); | ||||
| 
 | ||||
|  | ||||
| @ -20,7 +20,6 @@ beforeAll(async () => { | ||||
|                     strictSchemaValidation: true, | ||||
|                     strategyVariant: true, | ||||
|                     privateProjects: true, | ||||
|                     useLastSeenRefactor: true, | ||||
|                 }, | ||||
|             }, | ||||
|         }, | ||||
|  | ||||
| @ -185,34 +185,6 @@ test('schema allow yes=<string nbr>', () => { | ||||
|     expect(value.bucket.toggles.Demo2.no).toBe(256); | ||||
| }); | ||||
| 
 | ||||
| test('should set lastSeen on toggle', async () => { | ||||
|     expect.assertions(1); | ||||
|     stores.featureToggleStore.create('default', { | ||||
|         name: 'toggleLastSeen', | ||||
|     }); | ||||
|     await request | ||||
|         .post('/api/client/metrics') | ||||
|         .send({ | ||||
|             appName: 'demo', | ||||
|             bucket: { | ||||
|                 start: Date.now(), | ||||
|                 stop: Date.now(), | ||||
|                 toggles: { | ||||
|                     toggleLastSeen: { | ||||
|                         yes: 200, | ||||
|                         no: 0, | ||||
|                     }, | ||||
|                 }, | ||||
|             }, | ||||
|         }) | ||||
|         .expect(202); | ||||
| 
 | ||||
|     await services.lastSeenService.store(); | ||||
|     const toggle = await stores.featureToggleStore.get('toggleLastSeen'); | ||||
| 
 | ||||
|     expect(toggle.lastSeenAt).toBeTruthy(); | ||||
| }); | ||||
| 
 | ||||
| test('should return a 400 when required fields are missing', async () => { | ||||
|     stores.featureToggleStore.create('default', { | ||||
|         name: 'toggleLastSeen', | ||||
|  | ||||
| @ -1,39 +0,0 @@ | ||||
| import { Logger } from '../../../logger'; | ||||
| import { IFeatureOverview } from '../../../types'; | ||||
| import { IFeatureLastSeenResults } from './last-seen-read-model'; | ||||
| 
 | ||||
| export class LastSeenMapper { | ||||
|     mapToFeatures( | ||||
|         features: IFeatureOverview[], | ||||
|         lastSeenAtPerEnvironment: IFeatureLastSeenResults, | ||||
|         logger: Logger, | ||||
|     ): IFeatureOverview[] { | ||||
|         return features.map((feature) => { | ||||
|             if (!feature.environments) { | ||||
|                 logger.warn('Feature without environments:', feature); | ||||
|                 return feature; | ||||
|             } | ||||
| 
 | ||||
|             feature.environments = feature.environments.map((environment) => { | ||||
|                 const noData = | ||||
|                     !lastSeenAtPerEnvironment[feature.name] || | ||||
|                     !lastSeenAtPerEnvironment[feature.name][environment.name]; | ||||
| 
 | ||||
|                 if (noData) { | ||||
|                     logger.warn( | ||||
|                         'No last seen data for environment:', | ||||
|                         environment, | ||||
|                     ); | ||||
|                     return environment; | ||||
|                 } | ||||
| 
 | ||||
|                 environment.lastSeenAt = new Date( | ||||
|                     lastSeenAtPerEnvironment[feature.name][environment.name] | ||||
|                         .lastSeen, | ||||
|                 ); | ||||
|                 return environment; | ||||
|             }); | ||||
|             return feature; | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| @ -51,11 +51,7 @@ export class LastSeenService { | ||||
|                 `Updating last seen for ${lastSeenToggles.length} toggles`, | ||||
|             ); | ||||
| 
 | ||||
|             if (this.config.flagResolver.isEnabled('useLastSeenRefactor')) { | ||||
|                 await this.lastSeenStore.setLastSeen(lastSeenToggles); | ||||
|             } else { | ||||
|                 await this.featureToggleStore.setLastSeen(lastSeenToggles); | ||||
|             } | ||||
|             await this.lastSeenStore.setLastSeen(lastSeenToggles); | ||||
|         } | ||||
|         return count; | ||||
|     } | ||||
| @ -81,8 +77,6 @@ export class LastSeenService { | ||||
|     } | ||||
| 
 | ||||
|     async cleanLastSeen() { | ||||
|         if (this.flagResolver.isEnabled('useLastSeenRefactor')) { | ||||
|             await this.lastSeenStore.cleanLastSeen(); | ||||
|         } | ||||
|         await this.lastSeenStore.cleanLastSeen(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -1,95 +0,0 @@ | ||||
| import { IFeatureOverview } from '../../../../types'; | ||||
| import { LastSeenMapper } from '../last-seen-mapper'; | ||||
| import getLogger from '../../../../../test/fixtures/no-logger'; | ||||
| 
 | ||||
| test('should produce correct output when mapped', () => { | ||||
|     const mapper = new LastSeenMapper(); | ||||
| 
 | ||||
|     const inputLastSeen = { | ||||
|         exp: { | ||||
|             production: { lastSeen: '2023-10-05T07:27:04.286Z' }, | ||||
|             development: { lastSeen: '2023-10-04T19:03:29.682Z' }, | ||||
|         }, | ||||
|         'payment-system': { | ||||
|             production: { lastSeen: '2023-10-05T07:27:04.286Z' }, | ||||
|             development: { lastSeen: '2023-10-04T19:03:29.682Z' }, | ||||
|         }, | ||||
|     }; | ||||
| 
 | ||||
|     const inputFeatures: IFeatureOverview[] = [ | ||||
|         { | ||||
|             type: 'release', | ||||
|             description: null, | ||||
|             favorite: false, | ||||
|             name: 'payment-system', | ||||
|             // @ts-ignore
 | ||||
|             createdAt: '2023-06-30T12:57:20.476Z', | ||||
|             // @ts-ignore
 | ||||
|             lastSeenAt: '2023-10-03T13:08:16.263Z', | ||||
|             stale: false, | ||||
|             impressionData: false, | ||||
|             environments: [ | ||||
|                 { | ||||
|                     name: 'development', | ||||
|                     enabled: false, | ||||
|                     type: 'development', | ||||
|                     sortOrder: 2, | ||||
|                     variantCount: 0, | ||||
|                     // @ts-ignore
 | ||||
|                     lastSeenAt: '2023-10-04T19:03:29.682Z', | ||||
|                 }, | ||||
|                 { | ||||
|                     name: 'production', | ||||
|                     enabled: true, | ||||
|                     type: 'production', | ||||
|                     sortOrder: 3, | ||||
|                     variantCount: 0, | ||||
|                     // @ts-ignore
 | ||||
|                     lastSeenAt: '2023-10-05T07:27:04.286Z', | ||||
|                 }, | ||||
|             ], | ||||
|         }, | ||||
|         { | ||||
|             type: 'experiment', | ||||
|             description: null, | ||||
|             favorite: false, | ||||
|             name: 'exp', | ||||
|             // @ts-ignore
 | ||||
|             createdAt: '2023-09-13T08:08:28.211Z', | ||||
|             // @ts-ignore
 | ||||
|             lastSeenAt: '2023-10-03T13:08:16.263Z', | ||||
|             stale: false, | ||||
|             impressionData: false, | ||||
|             environments: [ | ||||
|                 { | ||||
|                     name: 'development', | ||||
|                     enabled: false, | ||||
|                     type: 'development', | ||||
|                     sortOrder: 2, | ||||
|                     variantCount: 0, | ||||
|                     // @ts-ignore
 | ||||
|                     lastSeenAt: '2023-10-04T19:03:29.682Z', | ||||
|                 }, | ||||
|                 { | ||||
|                     name: 'production', | ||||
|                     enabled: true, | ||||
|                     type: 'production', | ||||
|                     sortOrder: 3, | ||||
|                     variantCount: 0, | ||||
|                     // @ts-ignore
 | ||||
|                     lastSeenAt: '2023-10-05T07:27:04.286Z', | ||||
|                 }, | ||||
|             ], | ||||
|         }, | ||||
|     ]; | ||||
| 
 | ||||
|     const logger = getLogger(); | ||||
| 
 | ||||
|     const result = mapper.mapToFeatures(inputFeatures, inputLastSeen, logger); | ||||
| 
 | ||||
|     expect(result[0].environments[0].name).toBe('development'); | ||||
|     expect(result[0].name).toBe('payment-system'); | ||||
|     expect(result[0].environments[0].lastSeenAt).toEqual( | ||||
|         new Date(inputLastSeen['payment-system'].development.lastSeen), | ||||
|     ); | ||||
| }); | ||||
| @ -16,7 +16,6 @@ beforeAll(async () => { | ||||
|             experimental: { | ||||
|                 flags: { | ||||
|                     strictSchemaValidation: true, | ||||
|                     useLastSeenRefactor: true, | ||||
|                 }, | ||||
|             }, | ||||
|         }, | ||||
|  | ||||
| @ -36,7 +36,9 @@ function initLastSeenService(flagEnabled = true) { | ||||
| } | ||||
| 
 | ||||
| test('should not add duplicates per feature/environment', async () => { | ||||
|     const { lastSeenService, featureToggleStore } = initLastSeenService(false); | ||||
|     const { lastSeenService, featureToggleStore, lastSeenStore } = | ||||
|         initLastSeenService(false); | ||||
|     const lastSeenSpy = jest.spyOn(lastSeenStore, 'setLastSeen'); | ||||
| 
 | ||||
|     lastSeenService.updateLastSeen([ | ||||
|         { | ||||
| @ -59,10 +61,8 @@ test('should not add duplicates per feature/environment', async () => { | ||||
|             timestamp: new Date(), | ||||
|         }, | ||||
|     ]); | ||||
|     featureToggleStore.setLastSeen = jest.fn(); | ||||
|     await lastSeenService.store(); | ||||
| 
 | ||||
|     expect(featureToggleStore.setLastSeen).toHaveBeenCalledWith([ | ||||
|     expect(lastSeenSpy).toHaveBeenCalledWith([ | ||||
|         { | ||||
|             environment: 'development', | ||||
|             featureName: 'myFeature', | ||||
| @ -96,7 +96,6 @@ test('should call last seen at store with correct data', async () => { | ||||
|         }, | ||||
|     ]); | ||||
|     lastSeenStore.setLastSeen = jest.fn(); | ||||
|     featureToggleStore.setLastSeen = jest.fn(); | ||||
|     await lastSeenService.store(); | ||||
| 
 | ||||
|     expect(lastSeenStore.setLastSeen).toHaveBeenCalledWith([ | ||||
| @ -105,5 +104,4 @@ test('should call last seen at store with correct data', async () => { | ||||
|             featureName: 'myFeature', | ||||
|         }, | ||||
|     ]); | ||||
|     expect(featureToggleStore.setLastSeen).toHaveBeenCalledTimes(0); | ||||
| }); | ||||
|  | ||||
							
								
								
									
										122
									
								
								src/lib/services/scheduler-service.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								src/lib/services/scheduler-service.test.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,122 @@ | ||||
| import { LogProvider } from '../logger'; | ||||
| import { SchedulerService } from '../features/scheduler/scheduler-service'; | ||||
| import { createTestConfig } from '../../test/config/test-config'; | ||||
| import FakeSettingStore from '../../test/fixtures/fake-setting-store'; | ||||
| import SettingService from './setting-service'; | ||||
| import EventService from './event-service'; | ||||
| import MaintenanceService from '../features/maintenance/maintenance-service'; | ||||
| 
 | ||||
| function ms(timeMs) { | ||||
|     return new Promise((resolve) => setTimeout(resolve, timeMs)); | ||||
| } | ||||
| 
 | ||||
| const getLogger = () => { | ||||
|     const records: any[] = []; | ||||
|     const logger: LogProvider = () => ({ | ||||
|         error(...args: any[]) { | ||||
|             records.push(args); | ||||
|         }, | ||||
|         debug() {}, | ||||
|         info() {}, | ||||
|         warn() {}, | ||||
|         fatal() {}, | ||||
|     }); | ||||
|     const getRecords = () => { | ||||
|         return records; | ||||
|     }; | ||||
| 
 | ||||
|     return { logger, getRecords }; | ||||
| }; | ||||
| 
 | ||||
| let schedulerService: SchedulerService; | ||||
| let getRecords; | ||||
| 
 | ||||
| beforeEach(() => { | ||||
|     const config = createTestConfig(); | ||||
|     const settingStore = new FakeSettingStore(); | ||||
|     const settingService = new SettingService({ settingStore }, config, { | ||||
|         storeEvent() {}, | ||||
|     } as unknown as EventService); | ||||
|     const maintenanceService = new MaintenanceService(config, settingService); | ||||
|     const { logger, getRecords: getRecordsFn } = getLogger(); | ||||
|     getRecords = getRecordsFn; | ||||
| 
 | ||||
|     schedulerService = new SchedulerService( | ||||
|         logger, | ||||
|         maintenanceService, | ||||
|         config.eventBus, | ||||
|     ); | ||||
| }); | ||||
| 
 | ||||
| test('Schedules job immediately', async () => { | ||||
|     const job = jest.fn(); | ||||
|     await schedulerService.schedule(job, 10, 'test-id'); | ||||
| 
 | ||||
|     expect(job).toBeCalledTimes(1); | ||||
|     schedulerService.stop(); | ||||
| }); | ||||
| 
 | ||||
| test('Can schedule a single regular job', async () => { | ||||
|     const job = jest.fn(); | ||||
|     await schedulerService.schedule(job, 50, 'test-id-3'); | ||||
|     await ms(75); | ||||
| 
 | ||||
|     expect(job).toBeCalledTimes(2); | ||||
|     schedulerService.stop(); | ||||
| }); | ||||
| 
 | ||||
| test('Can schedule multiple jobs at the same interval', async () => { | ||||
|     const job = jest.fn(); | ||||
|     const anotherJob = jest.fn(); | ||||
| 
 | ||||
|     await schedulerService.schedule(job, 50, 'test-id-6'); | ||||
|     await schedulerService.schedule(anotherJob, 50, 'test-id-7'); | ||||
|     await ms(75); | ||||
| 
 | ||||
|     expect(job).toBeCalledTimes(2); | ||||
|     expect(anotherJob).toBeCalledTimes(2); | ||||
|     schedulerService.stop(); | ||||
| }); | ||||
| 
 | ||||
| test('Can schedule multiple jobs at the different intervals', async () => { | ||||
|     const job = jest.fn(); | ||||
|     const anotherJob = jest.fn(); | ||||
| 
 | ||||
|     await schedulerService.schedule(job, 100, 'test-id-8'); | ||||
|     await schedulerService.schedule(anotherJob, 200, 'test-id-9'); | ||||
|     await ms(250); | ||||
| 
 | ||||
|     expect(job).toBeCalledTimes(3); | ||||
|     expect(anotherJob).toBeCalledTimes(2); | ||||
|     schedulerService.stop(); | ||||
| }); | ||||
| 
 | ||||
| test('Can handle crash of a async job', async () => { | ||||
|     const job = async () => { | ||||
|         await Promise.reject('async reason'); | ||||
|     }; | ||||
| 
 | ||||
|     await schedulerService.schedule(job, 50, 'test-id-10'); | ||||
|     await ms(75); | ||||
| 
 | ||||
|     schedulerService.stop(); | ||||
|     expect(getRecords()).toEqual([ | ||||
|         ['scheduled job failed | id: test-id-10 | async reason'], | ||||
|         ['scheduled job failed | id: test-id-10 | async reason'], | ||||
|     ]); | ||||
| }); | ||||
| 
 | ||||
| test('Can handle crash of a sync job', async () => { | ||||
|     const job = () => { | ||||
|         throw new Error('sync reason'); | ||||
|     }; | ||||
| 
 | ||||
|     await schedulerService.schedule(job, 50, 'test-id-11'); | ||||
|     await ms(75); | ||||
| 
 | ||||
|     schedulerService.stop(); | ||||
|     expect(getRecords()).toEqual([ | ||||
|         ['scheduled job failed | id: test-id-11 | Error: sync reason'], | ||||
|         ['scheduled job failed | id: test-id-11 | Error: sync reason'], | ||||
|     ]); | ||||
| }); | ||||
| @ -25,7 +25,6 @@ export type IFlagKey = | ||||
|     | 'customRootRolesKillSwitch' | ||||
|     | 'privateProjects' | ||||
|     | 'disableMetrics' | ||||
|     | 'useLastSeenRefactor' | ||||
|     | 'banners' | ||||
|     | 'featureSearchAPI' | ||||
|     | 'featureSearchFrontend' | ||||
| @ -114,10 +113,6 @@ const flags: IFlags = { | ||||
|         process.env.UNLEASH_EXPERIMENTAL_DISABLE_METRICS, | ||||
|         false, | ||||
|     ), | ||||
|     useLastSeenRefactor: parseEnvVarBoolean( | ||||
|         process.env.UNLEASH_EXPERIMENTAL_USE_LAST_SEEN_REFACTOR, | ||||
|         false, | ||||
|     ), | ||||
|     featureSearchAPI: parseEnvVarBoolean( | ||||
|         process.env.UNLEASH_EXPERIMENTAL_FEATURE_SEARCH_API, | ||||
|         false, | ||||
|  | ||||
| @ -38,7 +38,6 @@ process.nextTick(async () => { | ||||
|                         anonymiseEventLog: false, | ||||
|                         responseTimeWithAppNameKillSwitch: false, | ||||
|                         privateProjects: true, | ||||
|                         useLastSeenRefactor: true, | ||||
|                         featureSearchAPI: true, | ||||
|                         featureSearchFrontend: false, | ||||
|                     }, | ||||
|  | ||||
| @ -77,14 +77,13 @@ test('returns three archived toggles', async () => { | ||||
| }); | ||||
| 
 | ||||
| test('returns three archived toggles with archivedAt', async () => { | ||||
|     expect.assertions(3); | ||||
|     expect.assertions(2); | ||||
|     return app.request | ||||
|         .get('/api/admin/archive/features') | ||||
|         .expect('Content-Type', /json/) | ||||
|         .expect(200) | ||||
|         .expect((res) => { | ||||
|             expect(res.body.features.length).toEqual(3); | ||||
|             expect(res.body.features.every((f) => f.archived)).toEqual(true); | ||||
|             expect(res.body.features.every((f) => f.archivedAt)).toEqual(true); | ||||
|         }); | ||||
| }); | ||||
|  | ||||
| @ -14,6 +14,7 @@ let app: IUnleashTest; | ||||
| let db: ITestDb; | ||||
| 
 | ||||
| let projectStore: IProjectStore; | ||||
| const testDate = '2023-10-01T12:34:56.000Z'; | ||||
| 
 | ||||
| beforeAll(async () => { | ||||
|     db = await dbInit('projects_api_serial', getLogger); | ||||
| @ -146,7 +147,12 @@ test('response for default project should include created_at', async () => { | ||||
| test('response should include last seen at per environment', async () => { | ||||
|     await app.createFeature('my-new-feature-toggle'); | ||||
| 
 | ||||
|     await insertLastSeenAt('my-new-feature-toggle', db.rawDatabase, 'default'); | ||||
|     await insertLastSeenAt( | ||||
|         'my-new-feature-toggle', | ||||
|         db.rawDatabase, | ||||
|         'default', | ||||
|         testDate, | ||||
|     ); | ||||
|     await insertFeatureEnvironmentsLastSeen( | ||||
|         'my-new-feature-toggle', | ||||
|         db.rawDatabase, | ||||
| @ -158,19 +164,11 @@ test('response should include last seen at per environment', async () => { | ||||
|         .expect('Content-Type', /json/) | ||||
|         .expect(200); | ||||
| 
 | ||||
|     expect(body.features[0].environments[0].lastSeenAt).toEqual( | ||||
|         '2022-05-01T12:34:56.000Z', | ||||
|     ); | ||||
|     expect(body.features[0].environments[0].lastSeenAt).toEqual(testDate); | ||||
| 
 | ||||
|     const appWithLastSeenRefactor = await setupAppWithCustomConfig( | ||||
|         db.stores, | ||||
|         { | ||||
|             experimental: { | ||||
|                 flags: { | ||||
|                     useLastSeenRefactor: true, | ||||
|                 }, | ||||
|             }, | ||||
|         }, | ||||
|         {}, | ||||
|         db.rawDatabase, | ||||
|     ); | ||||
| 
 | ||||
| @ -187,13 +185,7 @@ test('response should include last seen at per environment', async () => { | ||||
| test('response should include last seen at per environment for multiple environments', async () => { | ||||
|     const appWithLastSeenRefactor = await setupAppWithCustomConfig( | ||||
|         db.stores, | ||||
|         { | ||||
|             experimental: { | ||||
|                 flags: { | ||||
|                     useLastSeenRefactor: true, | ||||
|                 }, | ||||
|             }, | ||||
|         }, | ||||
|         {}, | ||||
|         db.rawDatabase, | ||||
|     ); | ||||
|     await app.createFeature('my-new-feature-toggle'); | ||||
|  | ||||
| @ -1,143 +0,0 @@ | ||||
| import { createTestConfig } from '../../config/test-config'; | ||||
| import dbInit from '../helpers/database-init'; | ||||
| import { IUnleashStores } from '../../../lib/types/stores'; | ||||
| import { LastSeenService } from '../../../lib/services/client-metrics/last-seen/last-seen-service'; | ||||
| import { IClientMetricsEnv } from '../../../lib/types/stores/client-metrics-store-v2'; | ||||
| 
 | ||||
| let stores: IUnleashStores; | ||||
| let db; | ||||
| let config; | ||||
| 
 | ||||
| beforeAll(async () => { | ||||
|     config = createTestConfig(); | ||||
|     db = await dbInit('last_seen_service_serial', config.getLogger); | ||||
|     stores = db.stores; | ||||
| }); | ||||
| beforeEach(async () => { | ||||
|     await stores.featureToggleStore.deleteAll(); | ||||
| }); | ||||
| afterAll(async () => { | ||||
|     await db.destroy(); | ||||
| }); | ||||
| 
 | ||||
| test('Should update last seen for known toggles', async () => { | ||||
|     const service = new LastSeenService( | ||||
|         { | ||||
|             lastSeenStore: stores.lastSeenStore, | ||||
|             featureToggleStore: stores.featureToggleStore, | ||||
|         }, | ||||
|         config, | ||||
|     ); | ||||
|     const time = Date.now() - 100; | ||||
|     await stores.featureToggleStore.create('default', { name: 'ta1' }); | ||||
| 
 | ||||
|     const metrics: IClientMetricsEnv[] = [ | ||||
|         { | ||||
|             featureName: 'ta1', | ||||
|             appName: 'some-App', | ||||
|             environment: 'default', | ||||
|             timestamp: new Date(time), | ||||
|             yes: 1, | ||||
|             no: 0, | ||||
|         }, | ||||
|         { | ||||
|             featureName: 'ta2', | ||||
|             appName: 'some-App', | ||||
|             environment: 'default', | ||||
|             timestamp: new Date(time), | ||||
|             yes: 1, | ||||
|             no: 0, | ||||
|         }, | ||||
|     ]; | ||||
| 
 | ||||
|     service.updateLastSeen(metrics); | ||||
|     await service.store(); | ||||
| 
 | ||||
|     const t1 = await stores.featureToggleStore.get('ta1'); | ||||
| 
 | ||||
|     expect(t1.lastSeenAt.getTime()).toBeGreaterThan(time); | ||||
| }); | ||||
| 
 | ||||
| test('Should not update last seen toggles with 0 metrics', async () => { | ||||
|     // jest.useFakeTimers();
 | ||||
|     const service = new LastSeenService( | ||||
|         { | ||||
|             lastSeenStore: stores.lastSeenStore, | ||||
|             featureToggleStore: stores.featureToggleStore, | ||||
|         }, | ||||
|         config, | ||||
|     ); | ||||
|     const time = Date.now(); | ||||
|     await stores.featureToggleStore.create('default', { name: 'tb1' }); | ||||
|     await stores.featureToggleStore.create('default', { name: 'tb2' }); | ||||
| 
 | ||||
|     const metrics: IClientMetricsEnv[] = [ | ||||
|         { | ||||
|             featureName: 'tb1', | ||||
|             appName: 'some-App', | ||||
|             environment: 'default', | ||||
|             timestamp: new Date(time), | ||||
|             yes: 1, | ||||
|             no: 0, | ||||
|         }, | ||||
|         { | ||||
|             featureName: 'tb2', | ||||
|             appName: 'some-App', | ||||
|             environment: 'default', | ||||
|             timestamp: new Date(time), | ||||
|             yes: 0, | ||||
|             no: 0, | ||||
|         }, | ||||
|     ]; | ||||
| 
 | ||||
|     service.updateLastSeen(metrics); | ||||
| 
 | ||||
|     // bypass interval waiting
 | ||||
|     await service.store(); | ||||
| 
 | ||||
|     const t1 = await stores.featureToggleStore.get('tb1'); | ||||
|     const t2 = await stores.featureToggleStore.get('tb2'); | ||||
| 
 | ||||
|     expect(t2.lastSeenAt).toBeNull(); | ||||
|     expect(t1.lastSeenAt.getTime()).toBeGreaterThanOrEqual(time); | ||||
| }); | ||||
| 
 | ||||
| test('Should not update anything for 0 toggles', async () => { | ||||
|     // jest.useFakeTimers();
 | ||||
|     const service = new LastSeenService( | ||||
|         { | ||||
|             lastSeenStore: stores.lastSeenStore, | ||||
|             featureToggleStore: stores.featureToggleStore, | ||||
|         }, | ||||
|         config, | ||||
|     ); | ||||
|     const time = Date.now(); | ||||
|     await stores.featureToggleStore.create('default', { name: 'tb1' }); | ||||
|     await stores.featureToggleStore.create('default', { name: 'tb2' }); | ||||
| 
 | ||||
|     const metrics: IClientMetricsEnv[] = [ | ||||
|         { | ||||
|             featureName: 'tb1', | ||||
|             appName: 'some-App', | ||||
|             environment: 'default', | ||||
|             timestamp: new Date(time), | ||||
|             yes: 0, | ||||
|             no: 0, | ||||
|         }, | ||||
|         { | ||||
|             featureName: 'tb2', | ||||
|             appName: 'some-App', | ||||
|             environment: 'default', | ||||
|             timestamp: new Date(time), | ||||
|             yes: 0, | ||||
|             no: 0, | ||||
|         }, | ||||
|     ]; | ||||
| 
 | ||||
|     service.updateLastSeen(metrics); | ||||
| 
 | ||||
|     // bypass interval waiting
 | ||||
|     const count = await service.store(); | ||||
| 
 | ||||
|     expect(count).toBe(0); | ||||
| }); | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user