1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-07-21 13:47:39 +02:00

feat: backend for retrieving tag colors (#9610)

Add backend for retrieving tag colors
This commit is contained in:
Fredrik Strand Oseberg 2025-03-25 14:45:44 +01:00 committed by GitHub
parent 9106fbf721
commit 7d7a949093
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 113 additions and 8 deletions

View File

@ -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}`,

View File

@ -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,
};
}

View File

@ -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',
},
],
},
],
});
});

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -355,6 +355,7 @@ export interface IFeatureToggleDeltaQuery extends IFeatureToggleQuery {
export interface ITag {
value: string;
type: string;
color?: string | null;
}
export interface IAddonParameterDefinition {

View File

@ -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',
},
],
});
});

View File

@ -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);
});