mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: backend for retrieving tag colors (#9610)
Add backend for retrieving tag colors
This commit is contained in:
		
							parent
							
								
									9106fbf721
								
							
						
					
					
						commit
						7d7a949093
					
				| @ -102,11 +102,18 @@ class FeatureTagStore implements IFeatureTagStore { | ||||
|         const stopTimer = this.timer('getAllForFeature'); | ||||
|         if (await this.featureExists(featureName)) { | ||||
|             const rows = await this.db | ||||
|                 .select(COLUMNS) | ||||
|                 .select([...COLUMNS, 'tag_types.color as color']) | ||||
|                 .from<FeatureTagTable>(TABLE) | ||||
|                 .leftJoin('tag_types', 'tag_types.name', 'feature_tag.tag_type') | ||||
|                 .where({ feature_name: featureName }); | ||||
| 
 | ||||
|             stopTimer(); | ||||
|             return rows.map(this.featureTagRowToTag); | ||||
| 
 | ||||
|             return rows.map((row) => ({ | ||||
|                 type: row.tag_type, | ||||
|                 value: row.tag_value, | ||||
|                 color: row.color, | ||||
|             })); | ||||
|         } else { | ||||
|             throw new NotFoundError( | ||||
|                 `Could not find feature with name ${featureName}`, | ||||
|  | ||||
| @ -134,6 +134,7 @@ class FeatureSearchStore implements IFeatureSearchStore { | ||||
|                     'environments.sort_order as environment_sort_order', | ||||
|                     'ft.tag_value as tag_value', | ||||
|                     'ft.tag_type as tag_type', | ||||
|                     'tag_types.color as tag_type_color', | ||||
|                     'segments.name as segment_name', | ||||
|                     'users.id as user_id', | ||||
|                     'users.name as user_name', | ||||
| @ -207,6 +208,7 @@ class FeatureSearchStore implements IFeatureSearchStore { | ||||
|                         'ft.feature_name', | ||||
|                         'features.name', | ||||
|                     ) | ||||
|                     .leftJoin('tag_types', 'tag_types.name', 'ft.tag_type') | ||||
|                     .leftJoin( | ||||
|                         'feature_strategies', | ||||
|                         'feature_strategies.feature_name', | ||||
| @ -548,6 +550,7 @@ class FeatureSearchStore implements IFeatureSearchStore { | ||||
|         return { | ||||
|             value: r.tag_value, | ||||
|             type: r.tag_type, | ||||
|             color: r.tag_type_color, | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -1252,3 +1252,37 @@ test('should return archived when query param set', async () => { | ||||
|         ], | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
| test('should return tags with color information from tag type', async () => { | ||||
|     await app.createFeature('my_feature_a'); | ||||
| 
 | ||||
|     await app.request | ||||
|         .put('/api/admin/tag-types/simple') | ||||
|         .send({ | ||||
|             name: 'simple', | ||||
|             color: '#FF0000', | ||||
|         }) | ||||
|         .expect(200); | ||||
| 
 | ||||
|     await app.addTag('my_feature_a', { | ||||
|         type: 'simple', | ||||
|         value: 'my_tag', | ||||
|     }); | ||||
| 
 | ||||
|     const { body } = await searchFeatures({}); | ||||
| 
 | ||||
|     expect(body).toMatchObject({ | ||||
|         features: [ | ||||
|             { | ||||
|                 name: 'my_feature_a', | ||||
|                 tags: [ | ||||
|                     { | ||||
|                         type: 'simple', | ||||
|                         value: 'my_tag', | ||||
|                         color: '#FF0000', | ||||
|                     }, | ||||
|                 ], | ||||
|             }, | ||||
|         ], | ||||
|     }); | ||||
| }); | ||||
|  | ||||
| @ -13,6 +13,7 @@ import { projectEnvironmentSchema } from './project-environment-schema'; | ||||
| import { createStrategyVariantSchema } from './create-strategy-variant-schema'; | ||||
| import { strategyVariantSchema } from './strategy-variant-schema'; | ||||
| import { createFeatureNamingPatternSchema } from './create-feature-naming-pattern-schema'; | ||||
| import { tagSchema } from './tag-schema'; | ||||
| 
 | ||||
| export const deprecatedProjectOverviewSchema = { | ||||
|     $id: '#/components/schemas/deprecatedProjectOverviewSchema', | ||||
| @ -144,6 +145,7 @@ export const deprecatedProjectOverviewSchema = { | ||||
|             variantSchema, | ||||
|             projectStatsSchema, | ||||
|             createFeatureNamingPatternSchema, | ||||
|             tagSchema, | ||||
|         }, | ||||
|     }, | ||||
| } as const; | ||||
|  | ||||
| @ -13,6 +13,7 @@ import { projectEnvironmentSchema } from './project-environment-schema'; | ||||
| import { createStrategyVariantSchema } from './create-strategy-variant-schema'; | ||||
| import { strategyVariantSchema } from './strategy-variant-schema'; | ||||
| import { createFeatureNamingPatternSchema } from './create-feature-naming-pattern-schema'; | ||||
| import { tagSchema } from './tag-schema'; | ||||
| 
 | ||||
| export const healthOverviewSchema = { | ||||
|     $id: '#/components/schemas/healthOverviewSchema', | ||||
| @ -138,6 +139,7 @@ export const healthOverviewSchema = { | ||||
|             variantSchema, | ||||
|             projectStatsSchema, | ||||
|             createFeatureNamingPatternSchema, | ||||
|             tagSchema, | ||||
|         }, | ||||
|     }, | ||||
| } as const; | ||||
|  | ||||
| @ -24,6 +24,13 @@ export const tagSchema = { | ||||
|                 'The [type](https://docs.getunleash.io/reference/feature-toggles#tags) of the tag', | ||||
|             example: 'simple', | ||||
|         }, | ||||
|         color: { | ||||
|             type: 'string', | ||||
|             description: 'The hexadecimal color code for the tag type.', | ||||
|             example: '#FFFFFF', | ||||
|             pattern: '^#[0-9A-Fa-f]{6}$', | ||||
|             nullable: true, | ||||
|         }, | ||||
|     }, | ||||
|     components: {}, | ||||
| } as const; | ||||
|  | ||||
| @ -355,6 +355,7 @@ export interface IFeatureToggleDeltaQuery extends IFeatureToggleQuery { | ||||
| export interface ITag { | ||||
|     value: string; | ||||
|     type: string; | ||||
|     color?: string | null; | ||||
| } | ||||
| 
 | ||||
| export interface IAddonParameterDefinition { | ||||
|  | ||||
| @ -10,13 +10,17 @@ let db: ITestDb; | ||||
| 
 | ||||
| beforeAll(async () => { | ||||
|     db = await dbInit('tag_api_serial', getLogger); | ||||
|     app = await setupAppWithCustomConfig(db.stores, { | ||||
|         experimental: { | ||||
|             flags: { | ||||
|                 strictSchemaValidation: true, | ||||
|     app = await setupAppWithCustomConfig( | ||||
|         db.stores, | ||||
|         { | ||||
|             experimental: { | ||||
|                 flags: { | ||||
|                     strictSchemaValidation: true, | ||||
|                 }, | ||||
|             }, | ||||
|         }, | ||||
|     }); | ||||
|         db.rawDatabase, | ||||
|     ); | ||||
| }); | ||||
| 
 | ||||
| afterAll(async () => { | ||||
| @ -219,3 +223,47 @@ test('backward compatibility: the API should return invalid tag names if they ex | ||||
|     const { body } = await app.request.get('/api/admin/tags').expect(200); | ||||
|     expect(body.tags).toContainEqual(tag); | ||||
| }); | ||||
| 
 | ||||
| test('should include tag color information when getting feature tags', async () => { | ||||
|     const featureName = 'test.feature.with.color'; | ||||
|     const tagType = 'simple'; | ||||
|     const tag = { | ||||
|         value: 'TeamRed', | ||||
|         type: tagType, | ||||
|     }; | ||||
| 
 | ||||
|     await app.request.post('/api/admin/projects/default/features').send({ | ||||
|         name: featureName, | ||||
|         type: 'kill-switch', | ||||
|         enabled: true, | ||||
|         strategies: [{ name: 'default' }], | ||||
|     }); | ||||
| 
 | ||||
|     await app.request | ||||
|         .put(`/api/admin/tag-types/${tagType}`) | ||||
|         .send({ | ||||
|             name: tagType, | ||||
|             color: '#FF0000', | ||||
|         }) | ||||
|         .expect(200); | ||||
| 
 | ||||
|     await app.request | ||||
|         .put(`/api/admin/features/${featureName}/tags`) | ||||
|         .send({ addedTags: [tag], removedTags: [] }) | ||||
|         .expect(200); | ||||
| 
 | ||||
|     const { body } = await app.request | ||||
|         .get(`/api/admin/features/${featureName}/tags`) | ||||
|         .expect('Content-Type', /json/) | ||||
|         .expect(200); | ||||
| 
 | ||||
|     expect(body).toMatchObject({ | ||||
|         tags: [ | ||||
|             { | ||||
|                 value: 'TeamRed', | ||||
|                 type: 'simple', | ||||
|                 color: '#FF0000', | ||||
|             }, | ||||
|         ], | ||||
|     }); | ||||
| }); | ||||
|  | ||||
| @ -13,6 +13,7 @@ let featureToggleStore: IFeatureToggleStore; | ||||
| const featureName = 'test-tag'; | ||||
| const tag = { type: 'simple', value: 'test' }; | ||||
| const TESTUSERID = 3333; | ||||
| const DEFAULT_TAG_COLOR = '#FFFFFF'; | ||||
| 
 | ||||
| beforeAll(async () => { | ||||
|     db = await dbInit('feature_tag_store_serial', getLogger); | ||||
| @ -45,7 +46,7 @@ test('should tag feature', async () => { | ||||
|         createdByUserId: TESTUSERID, | ||||
|     }); | ||||
|     expect(featureTags).toHaveLength(1); | ||||
|     expect(featureTags[0]).toStrictEqual(tag); | ||||
|     expect(featureTags[0]).toStrictEqual({ ...tag, color: DEFAULT_TAG_COLOR }); | ||||
|     expect(featureTag!.featureName).toBe(featureName); | ||||
|     expect(featureTag!.tagValue).toBe(tag.value); | ||||
| }); | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user