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:
parent
ea1221c45e
commit
70c7e3f978
@ -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.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
@ -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;
|
||||||
}>;
|
}>;
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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',
|
||||||
},
|
},
|
||||||
|
@ -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(
|
||||||
|
@ -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',
|
||||||
});
|
});
|
||||||
|
@ -255,7 +255,7 @@ export type IFeatureSearchOverview = Exclude<
|
|||||||
createdBy: {
|
createdBy: {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
imageUrl: string | null;
|
imageUrl: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user