diff --git a/src/lib/features/feature-toggle/feature-toggle-strategies-store.ts b/src/lib/features/feature-toggle/feature-toggle-strategies-store.ts index b8d1ef3ccc..c21afcb667 100644 --- a/src/lib/features/feature-toggle/feature-toggle-strategies-store.ts +++ b/src/lib/features/feature-toggle/feature-toggle-strategies-store.ts @@ -21,7 +21,7 @@ import type { PartialSome, } from '../../types'; import FeatureToggleStore from './feature-toggle-store'; -import { ensureStringValue, mapValues } from '../../util'; +import { ensureStringValue, generateImageUrl, mapValues } from '../../util'; import type { IFeatureProjectUserParams } from './feature-toggle-controller'; import type { Db } from '../../db/db'; import { isAfter } from 'date-fns'; @@ -453,6 +453,22 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore { acc.stale = r.stale; acc.createdAt = r.created_at; + if (r.user_id) { + const name = + r.user_name || + r.user_username || + r.user_email || + 'unknown'; + acc.createdBy = { + id: r.user_id, + name, + imageUrl: generateImageUrl({ + id: r.user_id, + email: r.user_email, + username: name, + }), + }; + } acc.type = r.type; if (!acc.environments[r.environment]) { acc.environments[r.environment] = { diff --git a/src/lib/features/feature-toggle/tests/feature-toggles.auth.e2e.test.ts b/src/lib/features/feature-toggle/tests/feature-toggles.auth.e2e.test.ts index 6d28ec9894..85710d4e0e 100644 --- a/src/lib/features/feature-toggle/tests/feature-toggles.auth.e2e.test.ts +++ b/src/lib/features/feature-toggle/tests/feature-toggles.auth.e2e.test.ts @@ -132,3 +132,37 @@ test('Should not be possible auto-enable feature flag without CREATE_FEATURE_STR .post(`${url}/${name}/environments/default/on`) .expect(403); }); + +test('Should read flag creator', async () => { + const email = 'user@getunleash.io'; + const url = '/api/admin/projects/default/features/'; + const name = 'creator.flag'; + + const user = await app.services.userService.createUser( + { + email, + rootRole: RoleName.EDITOR, + }, + TEST_AUDIT_USER, + ); + + await db.stores.featureToggleStore.create('default', { + name, + createdByUserId: user.id, + }); + + await app.request.post('/auth/demo/login').send({ + email, + }); + + const { body: feature } = await app.request + .get(`${url}/${name}`) + .expect(200); + + expect(feature.createdBy).toEqual({ + id: user.id, + name: 'user@getunleash.io', + imageUrl: + 'https://gravatar.com/avatar/3957b71c0a6d2528f03b423f432ed2efe855d263400f960248a1080493d9d68a?s=42&d=retro&r=g', + }); +}); diff --git a/src/lib/openapi/spec/feature-schema.ts b/src/lib/openapi/spec/feature-schema.ts index 70c7e9d67f..ec1c70a959 100644 --- a/src/lib/openapi/spec/feature-schema.ts +++ b/src/lib/openapi/spec/feature-schema.ts @@ -73,6 +73,29 @@ export const featureSchema = { example: '2023-01-28T15:21:39.975Z', description: 'The date the feature was created', }, + createdBy: { + type: 'object', + description: 'User who created the feature flag', + additionalProperties: false, + required: ['id', 'name', 'imageUrl'], + properties: { + id: { + description: 'The user id', + type: 'integer', + example: 123, + }, + name: { + description: 'Name of the user', + type: 'string', + example: 'User', + }, + imageUrl: { + description: `URL used for the user profile image`, + type: 'string', + example: 'https://example.com/242x200.png', + }, + }, + }, archivedAt: { type: 'string', format: 'date-time', diff --git a/src/lib/types/model.ts b/src/lib/types/model.ts index ba98de256c..fd6c1dba87 100644 --- a/src/lib/types/model.ts +++ b/src/lib/types/model.ts @@ -61,6 +61,11 @@ export interface FeatureToggleDTO { impressionData?: boolean; variants?: IVariant[]; createdByUserId?: number; + createdBy?: { + id: number; + name: string; + imageUrl: string; + }; } export interface FeatureToggle extends FeatureToggleDTO {