1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-07-26 13:48:33 +02:00

refactor: playground uses segment read model (#6424)

This commit is contained in:
Mateusz Kwasniewski 2024-03-05 08:34:26 +01:00 committed by GitHub
parent 62dc77db16
commit 095b6eca84
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 85 additions and 41 deletions

View File

@ -4,22 +4,28 @@ import { IUnleashServices } from '../../types/services';
import { ALL } from '../../types/models/api-token'; import { ALL } from '../../types/models/api-token';
import { PlaygroundFeatureSchema } from '../../openapi/spec/playground-feature-schema'; import { PlaygroundFeatureSchema } from '../../openapi/spec/playground-feature-schema';
import { Logger } from '../../logger'; import { Logger } from '../../logger';
import { IFlagResolver, ISegment, IUnleashConfig } from '../../types'; import {
IFlagResolver,
ISegment,
ISegmentReadModel,
IUnleashConfig,
} from '../../types';
import { offlineUnleashClient } from './offline-unleash-client'; import { offlineUnleashClient } from './offline-unleash-client';
import { FeatureInterface } from '../../features/playground/feature-evaluator/feature'; import { FeatureInterface } from '../../features/playground/feature-evaluator/feature';
import { import {
EvaluatedPlaygroundStrategy, EvaluatedPlaygroundStrategy,
FeatureStrategiesEvaluationResult, FeatureStrategiesEvaluationResult,
} from '../../features/playground/feature-evaluator/client'; } from '../../features/playground/feature-evaluator/client';
import { ISegmentService } from '../../segments/segment-service-interface';
import { FeatureConfigurationClient } from '../feature-toggle/types/feature-toggle-strategies-store-type'; import { FeatureConfigurationClient } from '../feature-toggle/types/feature-toggle-strategies-store-type';
import { generateObjectCombinations } from './generateObjectCombinations'; import { generateObjectCombinations } from './generateObjectCombinations';
import groupBy from 'lodash.groupby'; import groupBy from 'lodash.groupby';
import { omitKeys } from '../../util'; import { omitKeys } from '../../util';
import { AdvancedPlaygroundFeatureSchema } from '../../openapi'; import {
AdvancedPlaygroundFeatureSchema,
playgroundStrategyEvaluation,
} from '../../openapi';
import { AdvancedPlaygroundEnvironmentFeatureSchema } from '../../openapi/spec/advanced-playground-environment-feature-schema'; import { AdvancedPlaygroundEnvironmentFeatureSchema } from '../../openapi/spec/advanced-playground-environment-feature-schema';
import { validateQueryComplexity } from './validateQueryComplexity'; import { validateQueryComplexity } from './validateQueryComplexity';
import { playgroundStrategyEvaluation } from '../../openapi';
import { IPrivateProjectChecker } from '../private-project/privateProjectCheckerType'; import { IPrivateProjectChecker } from '../private-project/privateProjectCheckerType';
import { getDefaultVariant } from './feature-evaluator/variant'; import { getDefaultVariant } from './feature-evaluator/variant';
@ -66,30 +72,28 @@ export class PlaygroundService {
private readonly featureToggleService: FeatureToggleService; private readonly featureToggleService: FeatureToggleService;
private readonly segmentService: ISegmentService; private readonly flagResolver: IFlagResolver;
private flagResolver: IFlagResolver; private readonly privateProjectChecker: IPrivateProjectChecker;
private privateProjectChecker: IPrivateProjectChecker; private readonly segmentReadModel: ISegmentReadModel;
constructor( constructor(
config: IUnleashConfig, config: IUnleashConfig,
{ {
featureToggleServiceV2, featureToggleServiceV2,
segmentService,
privateProjectChecker, privateProjectChecker,
}: Pick< }: Pick<
IUnleashServices, IUnleashServices,
| 'featureToggleServiceV2' 'featureToggleServiceV2' | 'privateProjectChecker'
| 'segmentService'
| 'privateProjectChecker'
>, >,
segmentReadModel: ISegmentReadModel,
) { ) {
this.logger = config.getLogger('services/playground-service.ts'); this.logger = config.getLogger('services/playground-service.ts');
this.flagResolver = config.flagResolver; this.flagResolver = config.flagResolver;
this.featureToggleService = featureToggleServiceV2; this.featureToggleService = featureToggleServiceV2;
this.segmentService = segmentService;
this.privateProjectChecker = privateProjectChecker; this.privateProjectChecker = privateProjectChecker;
this.segmentReadModel = segmentReadModel;
} }
async evaluateAdvancedQuery( async evaluateAdvancedQuery(
@ -99,7 +103,7 @@ export class PlaygroundService {
limit: number, limit: number,
userId: number, userId: number,
): Promise<AdvancedPlaygroundFeatureEvaluationResult[]> { ): Promise<AdvancedPlaygroundFeatureEvaluationResult[]> {
const segments = await this.segmentService.getActive(); const segments = await this.segmentReadModel.getActive();
let filteredProjects: typeof projects = projects; let filteredProjects: typeof projects = projects;
@ -266,7 +270,7 @@ export class PlaygroundService {
): Promise<PlaygroundFeatureEvaluationResult[]> { ): Promise<PlaygroundFeatureEvaluationResult[]> {
const [{ features, featureProject }, segments] = await Promise.all([ const [{ features, featureProject }, segments] = await Promise.all([
this.resolveFeatures(projects, environment), this.resolveFeatures(projects, environment),
this.segmentService.getActive(), this.segmentReadModel.getActive(),
]); ]);
const result = await this.evaluate({ const result = await this.evaluate({

View File

@ -1,4 +1,4 @@
import { IFeatureStrategySegment, ISegment } from '../../types'; import { IClientSegment, IFeatureStrategySegment, ISegment } from '../../types';
import { ISegmentReadModel } from './segment-read-model-type'; import { ISegmentReadModel } from './segment-read-model-type';
export class FakeSegmentReadModel implements ISegmentReadModel { export class FakeSegmentReadModel implements ISegmentReadModel {
@ -9,4 +9,12 @@ export class FakeSegmentReadModel implements ISegmentReadModel {
async getAllFeatureStrategySegments(): Promise<IFeatureStrategySegment[]> { async getAllFeatureStrategySegments(): Promise<IFeatureStrategySegment[]> {
return []; return [];
} }
async getActive(): Promise<ISegment[]> {
return [];
}
async getActiveForClient(): Promise<IClientSegment[]> {
return [];
}
} }

View File

@ -1,6 +1,8 @@
import { IFeatureStrategySegment, ISegment } from '../../types'; import { IClientSegment, IFeatureStrategySegment, ISegment } from '../../types';
export interface ISegmentReadModel { export interface ISegmentReadModel {
getAll(): Promise<ISegment[]>; getAll(): Promise<ISegment[]>;
getAllFeatureStrategySegments(): Promise<IFeatureStrategySegment[]>; getAllFeatureStrategySegments(): Promise<IFeatureStrategySegment[]>;
getActive(): Promise<ISegment[]>;
getActiveForClient(): Promise<IClientSegment[]>;
} }

View File

@ -1,4 +1,9 @@
import { IConstraint, IFeatureStrategySegment, ISegment } from '../../types'; import {
IClientSegment,
IConstraint,
IFeatureStrategySegment,
ISegment,
} from '../../types';
import { ISegmentReadModel } from './segment-read-model-type'; import { ISegmentReadModel } from './segment-read-model-type';
import NotFoundError from '../../error/notfound-error'; import NotFoundError from '../../error/notfound-error';
import { Db } from '../../db/db'; import { Db } from '../../db/db';
@ -75,4 +80,28 @@ export class SegmentReadModel implements ISegmentReadModel {
segmentId: row.segment_id, segmentId: row.segment_id,
})); }));
} }
async getActive(): Promise<ISegment[]> {
const rows: ISegmentRow[] = await this.db
.distinct(this.prefixColumns())
.from('segments')
.orderBy('name', 'asc')
.join(
'feature_strategy_segment',
'feature_strategy_segment.segment_id',
'segments.id',
);
return rows.map(this.mapRow);
}
async getActiveForClient(): Promise<IClientSegment[]> {
const fullSegments = await this.getActive();
return fullSegments.map((segments) => ({
id: segments.id,
name: segments.name,
constraints: segments.constraints,
}));
}
} }

View File

@ -33,8 +33,6 @@ export interface ISegmentService {
validateName(name: string): Promise<void>; validateName(name: string): Promise<void>;
getActive(): Promise<ISegment[]>;
getActiveForClient(): Promise<IClientSegment[]>; getActiveForClient(): Promise<IClientSegment[]>;
getAll(): Promise<ISegment[]>; getAll(): Promise<ISegment[]>;

View File

@ -109,6 +109,8 @@ import {
createInstanceStatsService, createInstanceStatsService,
} from '../features/instance-stats/createInstanceStatsService'; } from '../features/instance-stats/createInstanceStatsService';
import { InactiveUsersService } from '../users/inactive/inactive-users-service'; import { InactiveUsersService } from '../users/inactive/inactive-users-service';
import { SegmentReadModel } from '../features/segment/segment-read-model';
import { FakeSegmentReadModel } from '../features/segment/fake-segment-read-model';
export const createServices = ( export const createServices = (
stores: IUnleashStores, stores: IUnleashStores,
@ -138,6 +140,9 @@ export const createServices = (
const dependentFeaturesReadModel = db const dependentFeaturesReadModel = db
? new DependentFeaturesReadModel(db) ? new DependentFeaturesReadModel(db)
: new FakeDependentFeaturesReadModel(); : new FakeDependentFeaturesReadModel();
const segmentReadModel = db
? new SegmentReadModel(db)
: new FakeSegmentReadModel();
const contextService = new ContextService( const contextService = new ContextService(
stores, stores,
@ -270,11 +275,14 @@ export const createServices = (
const userSplashService = new UserSplashService(stores, config); const userSplashService = new UserSplashService(stores, config);
const openApiService = new OpenApiService(config); const openApiService = new OpenApiService(config);
const clientSpecService = new ClientSpecService(config); const clientSpecService = new ClientSpecService(config);
const playgroundService = new PlaygroundService(config, { const playgroundService = new PlaygroundService(
config,
{
featureToggleServiceV2, featureToggleServiceV2,
segmentService,
privateProjectChecker, privateProjectChecker,
}); },
segmentReadModel,
);
const configurationRevisionService = new ConfigurationRevisionService( const configurationRevisionService = new ConfigurationRevisionService(
stores, stores,

View File

@ -79,10 +79,6 @@ export class SegmentService implements ISegmentService {
return this.segmentStore.getAll(this.config.isEnterprise); return this.segmentStore.getAll(this.config.isEnterprise);
} }
async getActive(): Promise<ISegment[]> {
return this.segmentStore.getActive();
}
async getActiveForClient(): Promise<IClientSegment[]> { async getActiveForClient(): Promise<IClientSegment[]> {
return this.segmentStore.getActiveForClient(); return this.segmentStore.getActiveForClient();
} }

View File

@ -377,7 +377,7 @@ test(`should import segments and connect them to feature strategies`, async () =
.expect(202); .expect(202);
const allSegments = await app.services.segmentService.getAll(); const allSegments = await app.services.segmentService.getAll();
const activeSegments = await app.services.segmentService.getActive(); const activeSegments = await db.stores.segmentReadModel.getActive();
expect(allSegments.length).toEqual(2); expect(allSegments.length).toEqual(2);
expect(collectIds(allSegments)).toEqual([1, 2]); expect(collectIds(allSegments)).toEqual([1, 2]);

View File

@ -20,18 +20,14 @@ import { SdkContextSchema } from '../../../lib/openapi/spec/sdk-context-schema';
import { SegmentSchema } from '../../../lib/openapi/spec/segment-schema'; import { SegmentSchema } from '../../../lib/openapi/spec/segment-schema';
import { playgroundStrategyEvaluation } from '../../../lib/openapi/spec/playground-strategy-schema'; import { playgroundStrategyEvaluation } from '../../../lib/openapi/spec/playground-strategy-schema';
import { PlaygroundSegmentSchema } from '../../../lib/openapi/spec/playground-segment-schema'; import { PlaygroundSegmentSchema } from '../../../lib/openapi/spec/playground-segment-schema';
import { ISegmentService } from '../../../lib/segments/segment-service-interface';
import { createPrivateProjectChecker } from '../../../lib/features/private-project/createPrivateProjectChecker'; import { createPrivateProjectChecker } from '../../../lib/features/private-project/createPrivateProjectChecker';
import { import { createFeatureToggleService } from '../../../lib/features';
createFeatureToggleService, import { SegmentReadModel } from '../../../lib/features/segment/segment-read-model';
createSegmentService,
} from '../../../lib/features';
let stores: IUnleashStores; let stores: IUnleashStores;
let db: ITestDb; let db: ITestDb;
let service: PlaygroundService; let service: PlaygroundService;
let featureToggleService: FeatureToggleService; let featureToggleService: FeatureToggleService;
let segmentService: ISegmentService;
beforeAll(async () => { beforeAll(async () => {
const config = createTestConfig(); const config = createTestConfig();
@ -41,14 +37,17 @@ beforeAll(async () => {
db.rawDatabase, db.rawDatabase,
config, config,
); );
segmentService = createSegmentService(db.rawDatabase, config); const segmentReadModel = new SegmentReadModel(db.rawDatabase);
featureToggleService = createFeatureToggleService(db.rawDatabase, config); featureToggleService = createFeatureToggleService(db.rawDatabase, config);
service = new PlaygroundService(config, { service = new PlaygroundService(
config,
{
featureToggleServiceV2: featureToggleService, featureToggleServiceV2: featureToggleService,
segmentService,
privateProjectChecker, privateProjectChecker,
}); },
segmentReadModel,
);
}); });
afterAll(async () => { afterAll(async () => {