mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +01:00
feat: feature collaborators added to API behind a flag (#7627)
This commit is contained in:
parent
203b700e27
commit
71b3a2ae0a
@ -55,6 +55,8 @@ import { createEventsService } from '../events/createEventsService';
|
||||
import { EventEmitter } from 'stream';
|
||||
import { FeatureLifecycleReadModel } from '../feature-lifecycle/feature-lifecycle-read-model';
|
||||
import { FakeFeatureLifecycleReadModel } from '../feature-lifecycle/fake-feature-lifecycle-read-model';
|
||||
import { FakeFeatureCollaboratorsReadModel } from './fake-feature-collaborators-read-model';
|
||||
import { FeatureCollaboratorsReadModel } from './feature-collaborators-read-model';
|
||||
|
||||
export const createFeatureToggleService = (
|
||||
db: Db,
|
||||
@ -131,6 +133,8 @@ export const createFeatureToggleService = (
|
||||
|
||||
const dependentFeaturesService = createDependentFeaturesService(config)(db);
|
||||
|
||||
const featureCollaboratorsReadModel = new FeatureCollaboratorsReadModel(db);
|
||||
|
||||
const featureToggleService = new FeatureToggleService(
|
||||
{
|
||||
featureStrategiesStore,
|
||||
@ -151,6 +155,7 @@ export const createFeatureToggleService = (
|
||||
dependentFeaturesReadModel,
|
||||
dependentFeaturesService,
|
||||
featureLifecycleReadModel,
|
||||
featureCollaboratorsReadModel,
|
||||
);
|
||||
return featureToggleService;
|
||||
};
|
||||
@ -192,6 +197,8 @@ export const createFakeFeatureToggleService = (config: IUnleashConfig) => {
|
||||
const dependentFeaturesReadModel = new FakeDependentFeaturesReadModel();
|
||||
const dependentFeaturesService = createFakeDependentFeaturesService(config);
|
||||
const featureLifecycleReadModel = new FakeFeatureLifecycleReadModel();
|
||||
const featureCollaboratorsReadModel =
|
||||
new FakeFeatureCollaboratorsReadModel();
|
||||
|
||||
const featureToggleService = new FeatureToggleService(
|
||||
{
|
||||
@ -218,6 +225,7 @@ export const createFakeFeatureToggleService = (config: IUnleashConfig) => {
|
||||
dependentFeaturesReadModel,
|
||||
dependentFeaturesService,
|
||||
featureLifecycleReadModel,
|
||||
featureCollaboratorsReadModel,
|
||||
);
|
||||
return {
|
||||
featureToggleService,
|
||||
|
@ -705,6 +705,19 @@ export default class ProjectFeaturesController extends Controller {
|
||||
) {
|
||||
return {
|
||||
...feature,
|
||||
...(feature.collaborators
|
||||
? {
|
||||
collaborators: {
|
||||
...feature.collaborators,
|
||||
users: feature.collaborators.users.map(
|
||||
(user) => ({
|
||||
...user,
|
||||
name: anonymise(user.name),
|
||||
}),
|
||||
),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
createdBy: {
|
||||
...feature.createdBy,
|
||||
name: anonymise(feature.createdBy?.name),
|
||||
|
@ -20,6 +20,7 @@ import {
|
||||
type IAuditUser,
|
||||
type IConstraint,
|
||||
type IDependency,
|
||||
type IFeatureCollaboratorsReadModel,
|
||||
type IFeatureEnvironmentInfo,
|
||||
type IFeatureEnvironmentStore,
|
||||
type IFeatureLifecycleStage,
|
||||
@ -110,6 +111,7 @@ import type EventEmitter from 'node:events';
|
||||
import type { IFeatureLifecycleReadModel } from '../feature-lifecycle/feature-lifecycle-read-model-type';
|
||||
import type { ResourceLimitsSchema } from '../../openapi';
|
||||
import { throwExceedsLimitError } from '../../error/exceeds-limit-error';
|
||||
import type { Collaborator } from './types/feature-collaborators-read-model-type';
|
||||
|
||||
interface IFeatureContext {
|
||||
featureName: string;
|
||||
@ -177,6 +179,8 @@ class FeatureToggleService {
|
||||
|
||||
private featureLifecycleReadModel: IFeatureLifecycleReadModel;
|
||||
|
||||
private featureCollaboratorsReadModel: IFeatureCollaboratorsReadModel;
|
||||
|
||||
private dependentFeaturesService: DependentFeaturesService;
|
||||
|
||||
private eventBus: EventEmitter;
|
||||
@ -221,6 +225,7 @@ class FeatureToggleService {
|
||||
dependentFeaturesReadModel: IDependentFeaturesReadModel,
|
||||
dependentFeaturesService: DependentFeaturesService,
|
||||
featureLifecycleReadModel: IFeatureLifecycleReadModel,
|
||||
featureCollaboratorsReadModel: IFeatureCollaboratorsReadModel,
|
||||
) {
|
||||
this.logger = getLogger('services/feature-toggle-service.ts');
|
||||
this.featureStrategiesStore = featureStrategiesStore;
|
||||
@ -240,6 +245,7 @@ class FeatureToggleService {
|
||||
this.dependentFeaturesReadModel = dependentFeaturesReadModel;
|
||||
this.dependentFeaturesService = dependentFeaturesService;
|
||||
this.featureLifecycleReadModel = featureLifecycleReadModel;
|
||||
this.featureCollaboratorsReadModel = featureCollaboratorsReadModel;
|
||||
this.eventBus = eventBus;
|
||||
this.resourceLimits = resourceLimits;
|
||||
}
|
||||
@ -1088,10 +1094,19 @@ class FeatureToggleService {
|
||||
let dependencies: IDependency[] = [];
|
||||
let children: string[] = [];
|
||||
let lifecycle: IFeatureLifecycleStage | undefined = undefined;
|
||||
[dependencies, children, lifecycle] = await Promise.all([
|
||||
let collaborators: Collaborator[] = [];
|
||||
const featureCollaboratorsEnabled = this.flagResolver.isEnabled(
|
||||
'featureCollaborators',
|
||||
);
|
||||
[dependencies, children, lifecycle, collaborators] = await Promise.all([
|
||||
this.dependentFeaturesReadModel.getParents(featureName),
|
||||
this.dependentFeaturesReadModel.getChildren([featureName]),
|
||||
this.featureLifecycleReadModel.findCurrentStage(featureName),
|
||||
featureCollaboratorsEnabled
|
||||
? this.featureCollaboratorsReadModel.getFeatureCollaborators(
|
||||
featureName,
|
||||
)
|
||||
: Promise.resolve([]),
|
||||
]);
|
||||
|
||||
if (environmentVariants) {
|
||||
@ -1106,6 +1121,9 @@ class FeatureToggleService {
|
||||
dependencies,
|
||||
children,
|
||||
lifecycle,
|
||||
...(featureCollaboratorsEnabled
|
||||
? { collaborators: { users: collaborators } }
|
||||
: {}),
|
||||
};
|
||||
} else {
|
||||
const result =
|
||||
@ -1119,6 +1137,9 @@ class FeatureToggleService {
|
||||
dependencies,
|
||||
children,
|
||||
lifecycle,
|
||||
...(featureCollaboratorsEnabled
|
||||
? { collaborators: { users: collaborators } }
|
||||
: {}),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -18,14 +18,19 @@ let db: ITestDb;
|
||||
|
||||
beforeAll(async () => {
|
||||
db = await dbInit('feature_strategy_auth_api_serial', getLogger);
|
||||
app = await setupAppWithAuth(db.stores, {
|
||||
experimental: {
|
||||
flags: {
|
||||
strictSchemaValidation: true,
|
||||
anonymiseEventLog: true,
|
||||
app = await setupAppWithAuth(
|
||||
db.stores,
|
||||
{
|
||||
experimental: {
|
||||
flags: {
|
||||
strictSchemaValidation: true,
|
||||
anonymiseEventLog: true,
|
||||
featureCollaborators: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
db.rawDatabase,
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
@ -140,7 +145,7 @@ test('Should not be possible auto-enable feature flag without CREATE_FEATURE_STR
|
||||
.expect(403);
|
||||
});
|
||||
|
||||
test('Should read flag creator', async () => {
|
||||
test('Should read flag creator and collaborators', async () => {
|
||||
const email = 'user@getunleash.io';
|
||||
const url = '/api/admin/projects/default/features/';
|
||||
const name = 'creator.flag';
|
||||
@ -153,10 +158,14 @@ test('Should read flag creator', async () => {
|
||||
TEST_AUDIT_USER,
|
||||
);
|
||||
|
||||
await db.stores.featureToggleStore.create('default', {
|
||||
name,
|
||||
createdByUserId: user.id,
|
||||
});
|
||||
await app.services.featureToggleService.createFeatureToggle(
|
||||
'default',
|
||||
{
|
||||
name,
|
||||
createdByUserId: user.id,
|
||||
},
|
||||
{ id: user.id, username: 'irrelevant', ip: '::1' },
|
||||
);
|
||||
|
||||
await app.request.post('/auth/demo/login').send({
|
||||
email,
|
||||
@ -166,10 +175,16 @@ test('Should read flag creator', async () => {
|
||||
.get(`${url}/${name}`)
|
||||
.expect(200);
|
||||
|
||||
expect(feature.createdBy).toEqual({
|
||||
const expectedUser = {
|
||||
id: user.id,
|
||||
name: '3957b71c0@unleash.run',
|
||||
imageUrl:
|
||||
'https://gravatar.com/avatar/3957b71c0a6d2528f03b423f432ed2efe855d263400f960248a1080493d9d68a?s=42&d=retro&r=g',
|
||||
};
|
||||
|
||||
expect(feature.createdBy).toEqual(expectedUser);
|
||||
|
||||
expect(feature.collaborators).toStrictEqual({
|
||||
users: [expectedUser],
|
||||
});
|
||||
});
|
||||
|
@ -99,6 +99,7 @@ beforeAll(async () => {
|
||||
experimental: {
|
||||
flags: {
|
||||
strictSchemaValidation: true,
|
||||
featureCollaborators: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1,6 +1,7 @@
|
||||
import {
|
||||
FEATURE_POTENTIALLY_STALE_ON,
|
||||
type IBaseEvent,
|
||||
type IFeatureCollaboratorsReadModel,
|
||||
type IUnleashConfig,
|
||||
type IUnleashStores,
|
||||
} from '../types';
|
||||
@ -68,6 +69,7 @@ test('Should only store events for potentially stale on', async () => {
|
||||
{} as IDependentFeaturesReadModel,
|
||||
{} as DependentFeaturesService,
|
||||
{} as IFeatureLifecycleReadModel,
|
||||
{} as IFeatureCollaboratorsReadModel,
|
||||
);
|
||||
|
||||
await featureToggleService.updatePotentiallyStaleFeatures();
|
||||
|
@ -135,6 +135,8 @@ import {
|
||||
createFakeApiTokenService,
|
||||
} from '../features/api-tokens/createApiTokenService';
|
||||
import { IntegrationEventsService } from '../features/integration-events/integration-events-service';
|
||||
import { FeatureCollaboratorsReadModel } from '../features/feature-toggle/feature-collaborators-read-model';
|
||||
import { FakeFeatureCollaboratorsReadModel } from '../features/feature-toggle/fake-feature-collaborators-read-model';
|
||||
|
||||
export const createServices = (
|
||||
stores: IUnleashStores,
|
||||
@ -263,6 +265,10 @@ export const createServices = (
|
||||
? createFeatureSearchService(config)(db)
|
||||
: createFakeFeatureSearchService(config);
|
||||
|
||||
const featureCollaboratorsReadModel = db
|
||||
? new FeatureCollaboratorsReadModel(db)
|
||||
: new FakeFeatureCollaboratorsReadModel();
|
||||
|
||||
const featureToggleServiceV2 = new FeatureToggleService(
|
||||
stores,
|
||||
config,
|
||||
@ -274,6 +280,7 @@ export const createServices = (
|
||||
dependentFeaturesReadModel,
|
||||
dependentFeaturesService,
|
||||
featureLifecycleReadModel,
|
||||
featureCollaboratorsReadModel,
|
||||
);
|
||||
const transactionalEnvironmentService = db
|
||||
? withTransactional(createEnvironmentService(config), db)
|
||||
|
@ -9,6 +9,7 @@ import type { ProjectEnvironment } from '../features/project/project-store-type'
|
||||
import type { FeatureSearchEnvironmentSchema } from '../openapi/spec/feature-search-environment-schema';
|
||||
import type { IntegrationEventsService } from '../features/integration-events/integration-events-service';
|
||||
import type { IFlagResolver } from './experimental';
|
||||
import type { Collaborator } from '../features/feature-toggle/types/feature-collaborators-read-model-type';
|
||||
|
||||
export type Operator = (typeof ALL_OPERATORS)[number];
|
||||
|
||||
@ -114,6 +115,7 @@ export interface FeatureToggleView extends FeatureToggleWithEnvironment {
|
||||
dependencies: IDependency[];
|
||||
children: string[];
|
||||
lifecycle: IFeatureLifecycleStage | undefined;
|
||||
collaborators?: { users: Collaborator[] };
|
||||
}
|
||||
|
||||
// @deprecated
|
||||
|
Loading…
Reference in New Issue
Block a user