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:
parent
9106fbf721
commit
7d7a949093
@ -102,11 +102,18 @@ class FeatureTagStore implements IFeatureTagStore {
|
|||||||
const stopTimer = this.timer('getAllForFeature');
|
const stopTimer = this.timer('getAllForFeature');
|
||||||
if (await this.featureExists(featureName)) {
|
if (await this.featureExists(featureName)) {
|
||||||
const rows = await this.db
|
const rows = await this.db
|
||||||
.select(COLUMNS)
|
.select([...COLUMNS, 'tag_types.color as color'])
|
||||||
.from<FeatureTagTable>(TABLE)
|
.from<FeatureTagTable>(TABLE)
|
||||||
|
.leftJoin('tag_types', 'tag_types.name', 'feature_tag.tag_type')
|
||||||
.where({ feature_name: featureName });
|
.where({ feature_name: featureName });
|
||||||
|
|
||||||
stopTimer();
|
stopTimer();
|
||||||
return rows.map(this.featureTagRowToTag);
|
|
||||||
|
return rows.map((row) => ({
|
||||||
|
type: row.tag_type,
|
||||||
|
value: row.tag_value,
|
||||||
|
color: row.color,
|
||||||
|
}));
|
||||||
} else {
|
} else {
|
||||||
throw new NotFoundError(
|
throw new NotFoundError(
|
||||||
`Could not find feature with name ${featureName}`,
|
`Could not find feature with name ${featureName}`,
|
||||||
|
@ -134,6 +134,7 @@ class FeatureSearchStore implements IFeatureSearchStore {
|
|||||||
'environments.sort_order as environment_sort_order',
|
'environments.sort_order as environment_sort_order',
|
||||||
'ft.tag_value as tag_value',
|
'ft.tag_value as tag_value',
|
||||||
'ft.tag_type as tag_type',
|
'ft.tag_type as tag_type',
|
||||||
|
'tag_types.color as tag_type_color',
|
||||||
'segments.name as segment_name',
|
'segments.name as segment_name',
|
||||||
'users.id as user_id',
|
'users.id as user_id',
|
||||||
'users.name as user_name',
|
'users.name as user_name',
|
||||||
@ -207,6 +208,7 @@ class FeatureSearchStore implements IFeatureSearchStore {
|
|||||||
'ft.feature_name',
|
'ft.feature_name',
|
||||||
'features.name',
|
'features.name',
|
||||||
)
|
)
|
||||||
|
.leftJoin('tag_types', 'tag_types.name', 'ft.tag_type')
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
'feature_strategies',
|
'feature_strategies',
|
||||||
'feature_strategies.feature_name',
|
'feature_strategies.feature_name',
|
||||||
@ -548,6 +550,7 @@ class FeatureSearchStore implements IFeatureSearchStore {
|
|||||||
return {
|
return {
|
||||||
value: r.tag_value,
|
value: r.tag_value,
|
||||||
type: r.tag_type,
|
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 { createStrategyVariantSchema } from './create-strategy-variant-schema';
|
||||||
import { strategyVariantSchema } from './strategy-variant-schema';
|
import { strategyVariantSchema } from './strategy-variant-schema';
|
||||||
import { createFeatureNamingPatternSchema } from './create-feature-naming-pattern-schema';
|
import { createFeatureNamingPatternSchema } from './create-feature-naming-pattern-schema';
|
||||||
|
import { tagSchema } from './tag-schema';
|
||||||
|
|
||||||
export const deprecatedProjectOverviewSchema = {
|
export const deprecatedProjectOverviewSchema = {
|
||||||
$id: '#/components/schemas/deprecatedProjectOverviewSchema',
|
$id: '#/components/schemas/deprecatedProjectOverviewSchema',
|
||||||
@ -144,6 +145,7 @@ export const deprecatedProjectOverviewSchema = {
|
|||||||
variantSchema,
|
variantSchema,
|
||||||
projectStatsSchema,
|
projectStatsSchema,
|
||||||
createFeatureNamingPatternSchema,
|
createFeatureNamingPatternSchema,
|
||||||
|
tagSchema,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
@ -13,6 +13,7 @@ import { projectEnvironmentSchema } from './project-environment-schema';
|
|||||||
import { createStrategyVariantSchema } from './create-strategy-variant-schema';
|
import { createStrategyVariantSchema } from './create-strategy-variant-schema';
|
||||||
import { strategyVariantSchema } from './strategy-variant-schema';
|
import { strategyVariantSchema } from './strategy-variant-schema';
|
||||||
import { createFeatureNamingPatternSchema } from './create-feature-naming-pattern-schema';
|
import { createFeatureNamingPatternSchema } from './create-feature-naming-pattern-schema';
|
||||||
|
import { tagSchema } from './tag-schema';
|
||||||
|
|
||||||
export const healthOverviewSchema = {
|
export const healthOverviewSchema = {
|
||||||
$id: '#/components/schemas/healthOverviewSchema',
|
$id: '#/components/schemas/healthOverviewSchema',
|
||||||
@ -138,6 +139,7 @@ export const healthOverviewSchema = {
|
|||||||
variantSchema,
|
variantSchema,
|
||||||
projectStatsSchema,
|
projectStatsSchema,
|
||||||
createFeatureNamingPatternSchema,
|
createFeatureNamingPatternSchema,
|
||||||
|
tagSchema,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
@ -24,6 +24,13 @@ export const tagSchema = {
|
|||||||
'The [type](https://docs.getunleash.io/reference/feature-toggles#tags) of the tag',
|
'The [type](https://docs.getunleash.io/reference/feature-toggles#tags) of the tag',
|
||||||
example: 'simple',
|
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: {},
|
components: {},
|
||||||
} as const;
|
} as const;
|
||||||
|
@ -355,6 +355,7 @@ export interface IFeatureToggleDeltaQuery extends IFeatureToggleQuery {
|
|||||||
export interface ITag {
|
export interface ITag {
|
||||||
value: string;
|
value: string;
|
||||||
type: string;
|
type: string;
|
||||||
|
color?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IAddonParameterDefinition {
|
export interface IAddonParameterDefinition {
|
||||||
|
@ -10,13 +10,17 @@ let db: ITestDb;
|
|||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
db = await dbInit('tag_api_serial', getLogger);
|
db = await dbInit('tag_api_serial', getLogger);
|
||||||
app = await setupAppWithCustomConfig(db.stores, {
|
app = await setupAppWithCustomConfig(
|
||||||
experimental: {
|
db.stores,
|
||||||
flags: {
|
{
|
||||||
strictSchemaValidation: true,
|
experimental: {
|
||||||
|
flags: {
|
||||||
|
strictSchemaValidation: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
db.rawDatabase,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
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);
|
const { body } = await app.request.get('/api/admin/tags').expect(200);
|
||||||
expect(body.tags).toContainEqual(tag);
|
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 featureName = 'test-tag';
|
||||||
const tag = { type: 'simple', value: 'test' };
|
const tag = { type: 'simple', value: 'test' };
|
||||||
const TESTUSERID = 3333;
|
const TESTUSERID = 3333;
|
||||||
|
const DEFAULT_TAG_COLOR = '#FFFFFF';
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
db = await dbInit('feature_tag_store_serial', getLogger);
|
db = await dbInit('feature_tag_store_serial', getLogger);
|
||||||
@ -45,7 +46,7 @@ test('should tag feature', async () => {
|
|||||||
createdByUserId: TESTUSERID,
|
createdByUserId: TESTUSERID,
|
||||||
});
|
});
|
||||||
expect(featureTags).toHaveLength(1);
|
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!.featureName).toBe(featureName);
|
||||||
expect(featureTag!.tagValue).toBe(tag.value);
|
expect(featureTag!.tagValue).toBe(tag.value);
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user