1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-06-09 01:17:06 +02:00

feat: Anonimize demo users list flag view (#7432)

This commit is contained in:
Mateusz Kwasniewski 2024-06-24 13:48:08 +02:00 committed by GitHub
parent ea1221c45e
commit 70c7e3f978
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 64 additions and 18 deletions

View File

@ -1,4 +1,4 @@
import type { IFeatureOverview } from '../../types'; import type { IFeatureSearchOverview } from '../../types';
import type { import type {
IFeatureSearchParams, IFeatureSearchParams,
IQueryParam, IQueryParam,
@ -9,7 +9,7 @@ export default class FakeFeatureSearchStore implements IFeatureSearchStore {
searchFeatures( searchFeatures(
params: IFeatureSearchParams, params: IFeatureSearchParams,
queryParams: IQueryParam[], queryParams: IQueryParam[],
): Promise<{ features: IFeatureOverview[]; total: number }> { ): Promise<{ features: IFeatureSearchOverview[]; total: number }> {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }
} }

View File

@ -2,6 +2,7 @@ import type { Response } from 'express';
import Controller from '../../routes/controller'; import Controller from '../../routes/controller';
import type { FeatureSearchService, OpenApiService } from '../../services'; import type { FeatureSearchService, OpenApiService } from '../../services';
import { import {
type IFeatureSearchOverview,
type IFlagResolver, type IFlagResolver,
type IUnleashConfig, type IUnleashConfig,
type IUnleashServices, type IUnleashServices,
@ -12,6 +13,7 @@ import type { Logger } from '../../logger';
import { import {
createResponseSchema, createResponseSchema,
getStandardResponses, getStandardResponses,
type SearchFeaturesSchema,
searchFeaturesSchema, searchFeaturesSchema,
} from '../../openapi'; } from '../../openapi';
import type { IAuthRequest } from '../../routes/unleash-types'; import type { IAuthRequest } from '../../routes/unleash-types';
@ -20,6 +22,7 @@ import {
featureSearchQueryParameters, featureSearchQueryParameters,
} from '../../openapi/spec/feature-search-query-parameters'; } from '../../openapi/spec/feature-search-query-parameters';
import { normalizeQueryParams } from './search-utils'; import { normalizeQueryParams } from './search-utils';
import { anonymise } from '../../util';
const PATH = '/features'; const PATH = '/features';
@ -71,9 +74,24 @@ export default class FeatureSearchController extends Controller {
}); });
} }
maybeAnonymise(
features: IFeatureSearchOverview[],
): IFeatureSearchOverview[] {
if (this.flagResolver.isEnabled('anonymiseEventLog')) {
return features.map((feature) => ({
...feature,
createdBy: {
...feature.createdBy,
name: anonymise(feature.createdBy.name),
},
}));
}
return features;
}
async searchFeatures( async searchFeatures(
req: IAuthRequest<any, any, any, FeatureSearchQueryParameters>, req: IAuthRequest<any, any, any, FeatureSearchQueryParameters>,
res: Response, res: Response<SearchFeaturesSchema>,
): Promise<void> { ): Promise<void> {
const { const {
query, query,
@ -131,7 +149,7 @@ export default class FeatureSearchController extends Controller {
res, res,
searchFeaturesSchema.$id, searchFeaturesSchema.$id,
serializeDates({ serializeDates({
features, features: this.maybeAnonymise(features),
total, total,
}), }),
); );

View File

@ -2,14 +2,14 @@ import type {
IFeatureSearchParams, IFeatureSearchParams,
IQueryParam, IQueryParam,
} from '../feature-toggle/types/feature-toggle-strategies-store-type'; } from '../feature-toggle/types/feature-toggle-strategies-store-type';
import type { IFeatureOverview } from '../../types'; import type { IFeatureSearchOverview } from '../../types';
export interface IFeatureSearchStore { export interface IFeatureSearchStore {
searchFeatures( searchFeatures(
params: IFeatureSearchParams, params: IFeatureSearchParams,
queryParams: IQueryParam[], queryParams: IQueryParam[],
): Promise<{ ): Promise<{
features: IFeatureOverview[]; features: IFeatureSearchOverview[];
total: number; total: number;
}>; }>;
} }

View File

@ -4,7 +4,6 @@ import metricsHelper from '../../util/metrics-helper';
import { DB_TIME } from '../../metric-events'; import { DB_TIME } from '../../metric-events';
import type { Logger, LogProvider } from '../../logger'; import type { Logger, LogProvider } from '../../logger';
import type { import type {
IFeatureOverview,
IFeatureSearchOverview, IFeatureSearchOverview,
IFeatureSearchStore, IFeatureSearchStore,
IFlagResolver, IFlagResolver,
@ -21,8 +20,8 @@ import { applyGenericQueryParams, applySearchFilters } from './search-utils';
import type { FeatureSearchEnvironmentSchema } from '../../openapi/spec/feature-search-environment-schema'; import type { FeatureSearchEnvironmentSchema } from '../../openapi/spec/feature-search-environment-schema';
import { generateImageUrl } from '../../util'; import { generateImageUrl } from '../../util';
const sortEnvironments = (overview: IFeatureOverview[]) => { const sortEnvironments = (overview: IFeatureSearchOverview[]) => {
return overview.map((data: IFeatureOverview) => ({ return overview.map((data: IFeatureSearchOverview) => ({
...data, ...data,
environments: data.environments environments: data.environments
.filter((f) => f.name) .filter((f) => f.name)
@ -106,7 +105,7 @@ class FeatureSearchStore implements IFeatureSearchStore {
}: IFeatureSearchParams, }: IFeatureSearchParams,
queryParams: IQueryParam[], queryParams: IQueryParam[],
): Promise<{ ): Promise<{
features: IFeatureOverview[]; features: IFeatureSearchOverview[];
total: number; total: number;
}> { }> {
const stopTimer = this.timer('searchFeatures'); const stopTimer = this.timer('searchFeatures');
@ -325,7 +324,9 @@ class FeatureSearchStore implements IFeatureSearchStore {
rows, rows,
featureLifecycleEnabled, featureLifecycleEnabled,
); );
const features = sortEnvironments(overview); const features = sortEnvironments(
overview,
) as IFeatureSearchOverview[];
return { return {
features, features,
total: Number(rows[0].total) || 0, total: Number(rows[0].total) || 0,

View File

@ -22,6 +22,7 @@ beforeAll(async () => {
flags: { flags: {
strictSchemaValidation: true, strictSchemaValidation: true,
featureLifecycle: true, featureLifecycle: true,
anonymiseEventLog: true,
}, },
}, },
}, },
@ -186,7 +187,7 @@ test('should search matching features by name', async () => {
name: 'my_feature_a', name: 'my_feature_a',
createdBy: { createdBy: {
id: 1, id: 1,
name: 'user@getunleash.io', name: '3957b71c0@unleash.run',
imageUrl: imageUrl:
'https://gravatar.com/avatar/3957b71c0a6d2528f03b423f432ed2efe855d263400f960248a1080493d9d68a?s=42&d=retro&r=g', 'https://gravatar.com/avatar/3957b71c0a6d2528f03b423f432ed2efe855d263400f960248a1080493d9d68a?s=42&d=retro&r=g',
}, },
@ -195,7 +196,7 @@ test('should search matching features by name', async () => {
name: 'my_feature_b', name: 'my_feature_b',
createdBy: { createdBy: {
id: 1, id: 1,
name: 'user@getunleash.io', name: '3957b71c0@unleash.run',
imageUrl: imageUrl:
'https://gravatar.com/avatar/3957b71c0a6d2528f03b423f432ed2efe855d263400f960248a1080493d9d68a?s=42&d=retro&r=g', 'https://gravatar.com/avatar/3957b71c0a6d2528f03b423f432ed2efe855d263400f960248a1080493d9d68a?s=42&d=retro&r=g',
}, },

View File

@ -6,6 +6,7 @@ import {
CREATE_FEATURE_STRATEGY, CREATE_FEATURE_STRATEGY,
DELETE_FEATURE, DELETE_FEATURE,
DELETE_FEATURE_STRATEGY, DELETE_FEATURE_STRATEGY,
type FeatureToggleView,
type IFlagResolver, type IFlagResolver,
type IUnleashConfig, type IUnleashConfig,
type IUnleashServices, type IUnleashServices,
@ -52,6 +53,7 @@ import type {
UnleashTransaction, UnleashTransaction,
} from '../../db/transaction'; } from '../../db/transaction';
import { BadDataError } from '../../error'; import { BadDataError } from '../../error';
import { anonymise } from '../../util';
interface FeatureStrategyParams { interface FeatureStrategyParams {
projectId: string; projectId: string;
@ -694,9 +696,25 @@ export default class ProjectFeaturesController extends Controller {
); );
} }
maybeAnonymise(feature: FeatureToggleView): FeatureToggleView {
if (
this.flagResolver.isEnabled('anonymiseEventLog') &&
feature.createdBy
) {
return {
...feature,
createdBy: {
...feature.createdBy,
name: anonymise(feature.createdBy?.name),
},
};
}
return feature;
}
async getFeature( async getFeature(
req: IAuthRequest<FeatureParams, any, any, any>, req: IAuthRequest<FeatureParams, any, any, any>,
res: Response, res: Response<FeatureSchema>,
): Promise<void> { ): Promise<void> {
const { featureName, projectId } = req.params; const { featureName, projectId } = req.params;
const { variantEnvironments } = req.query; const { variantEnvironments } = req.query;
@ -708,7 +726,8 @@ export default class ProjectFeaturesController extends Controller {
environmentVariants: variantEnvironments === 'true', environmentVariants: variantEnvironments === 'true',
userId: user.id, userId: user.id,
}); });
res.status(200).json(feature);
res.status(200).json(serializeDates(this.maybeAnonymise(feature)));
} }
async updateFeature( async updateFeature(

View File

@ -18,7 +18,14 @@ let db: ITestDb;
beforeAll(async () => { beforeAll(async () => {
db = await dbInit('feature_strategy_auth_api_serial', getLogger); db = await dbInit('feature_strategy_auth_api_serial', getLogger);
app = await setupAppWithAuth(db.stores); app = await setupAppWithAuth(db.stores, {
experimental: {
flags: {
strictSchemaValidation: true,
anonymiseEventLog: true,
},
},
});
}); });
afterEach(async () => { afterEach(async () => {
@ -161,7 +168,7 @@ test('Should read flag creator', async () => {
expect(feature.createdBy).toEqual({ expect(feature.createdBy).toEqual({
id: user.id, id: user.id,
name: 'user@getunleash.io', name: '3957b71c0@unleash.run',
imageUrl: imageUrl:
'https://gravatar.com/avatar/3957b71c0a6d2528f03b423f432ed2efe855d263400f960248a1080493d9d68a?s=42&d=retro&r=g', 'https://gravatar.com/avatar/3957b71c0a6d2528f03b423f432ed2efe855d263400f960248a1080493d9d68a?s=42&d=retro&r=g',
}); });

View File

@ -255,7 +255,7 @@ export type IFeatureSearchOverview = Exclude<
createdBy: { createdBy: {
id: number; id: number;
name: string; name: string;
imageUrl: string | null; imageUrl: string;
}; };
}; };