1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-05-31 01:16:01 +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 {
IFeatureSearchParams,
IQueryParam,
@ -9,7 +9,7 @@ export default class FakeFeatureSearchStore implements IFeatureSearchStore {
searchFeatures(
params: IFeatureSearchParams,
queryParams: IQueryParam[],
): Promise<{ features: IFeatureOverview[]; total: number }> {
): Promise<{ features: IFeatureSearchOverview[]; total: number }> {
throw new Error('Method not implemented.');
}
}

View File

@ -2,6 +2,7 @@ import type { Response } from 'express';
import Controller from '../../routes/controller';
import type { FeatureSearchService, OpenApiService } from '../../services';
import {
type IFeatureSearchOverview,
type IFlagResolver,
type IUnleashConfig,
type IUnleashServices,
@ -12,6 +13,7 @@ import type { Logger } from '../../logger';
import {
createResponseSchema,
getStandardResponses,
type SearchFeaturesSchema,
searchFeaturesSchema,
} from '../../openapi';
import type { IAuthRequest } from '../../routes/unleash-types';
@ -20,6 +22,7 @@ import {
featureSearchQueryParameters,
} from '../../openapi/spec/feature-search-query-parameters';
import { normalizeQueryParams } from './search-utils';
import { anonymise } from '../../util';
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(
req: IAuthRequest<any, any, any, FeatureSearchQueryParameters>,
res: Response,
res: Response<SearchFeaturesSchema>,
): Promise<void> {
const {
query,
@ -131,7 +149,7 @@ export default class FeatureSearchController extends Controller {
res,
searchFeaturesSchema.$id,
serializeDates({
features,
features: this.maybeAnonymise(features),
total,
}),
);

View File

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

View File

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

View File

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

View File

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

View File

@ -18,7 +18,14 @@ let db: ITestDb;
beforeAll(async () => {
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 () => {
@ -161,7 +168,7 @@ test('Should read flag creator', async () => {
expect(feature.createdBy).toEqual({
id: user.id,
name: 'user@getunleash.io',
name: '3957b71c0@unleash.run',
imageUrl:
'https://gravatar.com/avatar/3957b71c0a6d2528f03b423f432ed2efe855d263400f960248a1080493d9d68a?s=42&d=retro&r=g',
});

View File

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