mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: application missing features backend (#6330)
This PR adds a property issues to application schema, and also adds all the missing features that have been reported by SDK, but do not exist in Unleash.
This commit is contained in:
		
							parent
							
								
									d1e93228a3
								
							
						
					
					
						commit
						89d113f1ff
					
				| @ -10,6 +10,7 @@ import { Logger, LogProvider } from '../logger'; | |||||||
| import { Db } from './db'; | import { Db } from './db'; | ||||||
| import { IApplicationOverview } from '../features/metrics/instance/models'; | import { IApplicationOverview } from '../features/metrics/instance/models'; | ||||||
| import { applySearchFilters } from '../features/feature-search/search-utils'; | import { applySearchFilters } from '../features/feature-search/search-utils'; | ||||||
|  | import { ApplicationOverviewIssuesSchema } from '../openapi/spec/application-overview-issues-schema'; | ||||||
| 
 | 
 | ||||||
| const COLUMNS = [ | const COLUMNS = [ | ||||||
|     'app_name', |     'app_name', | ||||||
| @ -305,7 +306,6 @@ export default class ClientApplicationsStore | |||||||
|                 ); |                 ); | ||||||
|             }) |             }) | ||||||
|             .where('a.app_name', appName); |             .where('a.app_name', appName); | ||||||
| 
 |  | ||||||
|         const rows = await query; |         const rows = await query; | ||||||
|         if (!rows.length) { |         if (!rows.length) { | ||||||
|             throw new NotFoundError(`Could not find appName=${appName}`); |             throw new NotFoundError(`Could not find appName=${appName}`); | ||||||
| @ -316,22 +316,41 @@ export default class ClientApplicationsStore | |||||||
| 
 | 
 | ||||||
|     mapApplicationOverviewData(rows: any[]): IApplicationOverview { |     mapApplicationOverviewData(rows: any[]): IApplicationOverview { | ||||||
|         const featureCount = new Set(rows.map((row) => row.feature_name)).size; |         const featureCount = new Set(rows.map((row) => row.feature_name)).size; | ||||||
|  |         const missingFeatures: Set<string> = new Set(); | ||||||
| 
 | 
 | ||||||
|         const environments = rows.reduce((acc, row) => { |         const environments = rows.reduce((acc, row) => { | ||||||
|             const { environment, instance_id, sdk_version, last_seen } = row; |             const { | ||||||
|  |                 environment, | ||||||
|  |                 instance_id, | ||||||
|  |                 sdk_version, | ||||||
|  |                 last_seen, | ||||||
|  |                 project, | ||||||
|  |                 feature_name, | ||||||
|  |             } = row; | ||||||
|  | 
 | ||||||
|  |             if (!environment) return acc; | ||||||
|  | 
 | ||||||
|  |             if (!project && feature_name) { | ||||||
|  |                 missingFeatures.add(feature_name); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|             let env = acc.find((e) => e.name === environment); |             let env = acc.find((e) => e.name === environment); | ||||||
|             if (!env) { |             if (!env) { | ||||||
|                 env = { |                 env = { | ||||||
|                     name: environment, |                     name: environment, | ||||||
|                     instanceCount: 1, |                     instanceCount: instance_id ? 1 : 0, | ||||||
|                     sdks: sdk_version ? [sdk_version] : [], |                     sdks: sdk_version ? [sdk_version] : [], | ||||||
|                     lastSeen: last_seen, |                     lastSeen: last_seen, | ||||||
|                     uniqueInstanceIds: new Set([instance_id]), |                     uniqueInstanceIds: new Set( | ||||||
|  |                         instance_id ? [instance_id] : [], | ||||||
|  |                     ), | ||||||
|                 }; |                 }; | ||||||
|                 acc.push(env); |                 acc.push(env); | ||||||
|             } else { |             } else { | ||||||
|  |                 if (instance_id && !env.uniqueInstanceIds.has(instance_id)) { | ||||||
|                     env.uniqueInstanceIds.add(instance_id); |                     env.uniqueInstanceIds.add(instance_id); | ||||||
|                     env.instanceCount = env.uniqueInstanceIds.size; |                     env.instanceCount = env.uniqueInstanceIds.size; | ||||||
|  |                 } | ||||||
|                 if (sdk_version && !env.sdks.includes(sdk_version)) { |                 if (sdk_version && !env.sdks.includes(sdk_version)) { | ||||||
|                     env.sdks.push(sdk_version); |                     env.sdks.push(sdk_version); | ||||||
|                 } |                 } | ||||||
| @ -342,12 +361,21 @@ export default class ClientApplicationsStore | |||||||
| 
 | 
 | ||||||
|             return acc; |             return acc; | ||||||
|         }, []); |         }, []); | ||||||
| 
 |  | ||||||
|         environments.forEach((env) => { |         environments.forEach((env) => { | ||||||
|             delete env.uniqueInstanceIds; |             delete env.uniqueInstanceIds; | ||||||
|             env.sdks.sort(); |             env.sdks.sort(); | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|  |         const issues: ApplicationOverviewIssuesSchema[] = | ||||||
|  |             missingFeatures.size > 0 | ||||||
|  |                 ? [ | ||||||
|  |                       { | ||||||
|  |                           type: 'missingFeatures', | ||||||
|  |                           items: [...missingFeatures], | ||||||
|  |                       }, | ||||||
|  |                   ] | ||||||
|  |                 : []; | ||||||
|  | 
 | ||||||
|         return { |         return { | ||||||
|             projects: [ |             projects: [ | ||||||
|                 ...new Set( |                 ...new Set( | ||||||
| @ -358,6 +386,7 @@ export default class ClientApplicationsStore | |||||||
|             ], |             ], | ||||||
|             featureCount, |             featureCount, | ||||||
|             environments, |             environments, | ||||||
|  |             issues, | ||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -9,7 +9,7 @@ export const applicationOverviewIssuesSchema = { | |||||||
|     properties: { |     properties: { | ||||||
|         type: { |         type: { | ||||||
|             type: 'string', |             type: 'string', | ||||||
|             enum: ['missingFeature', 'missingStrategy'], |             enum: ['missingFeatures', 'missingStrategies'], | ||||||
|             description: 'The name of this action.', |             description: 'The name of this action.', | ||||||
|         }, |         }, | ||||||
|         items: { |         items: { | ||||||
|  | |||||||
| @ -5,8 +5,8 @@ test('applicationOverviewSchema', () => { | |||||||
|         projects: ['default', 'dx'], |         projects: ['default', 'dx'], | ||||||
|         featureCount: 12, |         featureCount: 12, | ||||||
|         issues: [ |         issues: [ | ||||||
|             { type: 'missingFeature', items: ['feature1'] }, |             { type: 'missingFeatures', items: ['feature1'] }, | ||||||
|             { type: 'missingStrategy', items: ['strategy1'] }, |             { type: 'missingStrategies', items: ['strategy1'] }, | ||||||
|         ], |         ], | ||||||
|         environments: [ |         environments: [ | ||||||
|             { |             { | ||||||
|  | |||||||
| @ -8,7 +8,7 @@ export const applicationOverviewSchema = { | |||||||
|     description: |     description: | ||||||
|         "Data about an application that's connected to Unleash via an SDK.", |         "Data about an application that's connected to Unleash via an SDK.", | ||||||
|     additionalProperties: false, |     additionalProperties: false, | ||||||
|     required: ['projects', 'featureCount', 'environments'], |     required: ['projects', 'featureCount', 'environments', 'issues'], | ||||||
|     properties: { |     properties: { | ||||||
|         projects: { |         projects: { | ||||||
|             description: 'The list of projects the application has been using.', |             description: 'The list of projects the application has been using.', | ||||||
|  | |||||||
| @ -122,6 +122,7 @@ test('should show correct number of total', async () => { | |||||||
| 
 | 
 | ||||||
|     const expected = { |     const expected = { | ||||||
|         projects: ['default'], |         projects: ['default'], | ||||||
|  |         issues: [], | ||||||
|         environments: [ |         environments: [ | ||||||
|             { |             { | ||||||
|                 instanceCount: 2, |                 instanceCount: 2, | ||||||
| @ -134,3 +135,49 @@ test('should show correct number of total', async () => { | |||||||
| 
 | 
 | ||||||
|     expect(body).toMatchObject(expected); |     expect(body).toMatchObject(expected); | ||||||
| }); | }); | ||||||
|  | 
 | ||||||
|  | test('should show missing features', async () => { | ||||||
|  |     await Promise.all([ | ||||||
|  |         app.createFeature('toggle-name-1'), | ||||||
|  |         app.request.post('/api/client/register').send({ | ||||||
|  |             appName: metrics.appName, | ||||||
|  |             instanceId: metrics.instanceId, | ||||||
|  |             strategies: ['default'], | ||||||
|  |             sdkVersion: 'unleash-client-test', | ||||||
|  |             started: Date.now(), | ||||||
|  |             interval: 10, | ||||||
|  |         }), | ||||||
|  |     ]); | ||||||
|  |     await app.services.clientInstanceService.bulkAdd(); | ||||||
|  |     await app.request | ||||||
|  |         .post('/api/client/metrics') | ||||||
|  |         .set('Authorization', defaultToken.secret) | ||||||
|  |         .send(metrics) | ||||||
|  |         .expect(202); | ||||||
|  | 
 | ||||||
|  |     await app.services.clientMetricsServiceV2.bulkAdd(); | ||||||
|  | 
 | ||||||
|  |     const { body } = await app.request | ||||||
|  |         .get(`/api/admin/metrics/applications/${metrics.appName}/overview`) | ||||||
|  |         .expect(200); | ||||||
|  | 
 | ||||||
|  |     const expected = { | ||||||
|  |         projects: ['default'], | ||||||
|  |         issues: [ | ||||||
|  |             { | ||||||
|  |                 type: 'missingFeatures', | ||||||
|  |                 items: ['toggle-name-2', 'toggle-name-3'], | ||||||
|  |             }, | ||||||
|  |         ], | ||||||
|  |         environments: [ | ||||||
|  |             { | ||||||
|  |                 instanceCount: 1, | ||||||
|  |                 name: 'default', | ||||||
|  |                 sdks: ['unleash-client-test'], | ||||||
|  |             }, | ||||||
|  |         ], | ||||||
|  |         featureCount: 3, | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     expect(body).toMatchObject(expected); | ||||||
|  | }); | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user