mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-20 00:08:02 +01:00
feat: context/segment usage private (#4826)
This commit is contained in:
parent
fd9aeec0fb
commit
2bf995e731
@ -77,7 +77,6 @@ export const SegmentFormStepOne: React.FC<ISegmentFormPartOneProps> = ({
|
||||
const projectsUsed = new Set<string>(
|
||||
strategies.map(({ projectId }) => projectId!).filter(Boolean)
|
||||
);
|
||||
|
||||
const availableProjects = projects.filter(
|
||||
({ id }) =>
|
||||
!projectsUsed.size ||
|
||||
|
@ -5,6 +5,7 @@ import { IFeatureStrategy } from 'interfaces/strategy';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { formatStrategyName } from 'utils/strategyNames';
|
||||
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
|
||||
const StyledUl = styled('ul')(({ theme }) => ({
|
||||
listStyle: 'none',
|
||||
@ -37,7 +38,6 @@ export const SegmentProjectAlert = ({
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const projectList = (
|
||||
<StyledUl>
|
||||
{Array.from(projectsUsed).map(projectId => (
|
||||
@ -78,11 +78,19 @@ export const SegmentProjectAlert = ({
|
||||
</StyledUl>
|
||||
);
|
||||
|
||||
if (projectsUsed.length > 1) {
|
||||
if (projectsUsed.length > 0) {
|
||||
return (
|
||||
<StyledAlert severity="info">
|
||||
You can't specify a project for this segment because it is used
|
||||
in multiple projects:
|
||||
<ConditionallyRender
|
||||
condition={projectsUsed.length > 1}
|
||||
show={
|
||||
<span>
|
||||
You can't specify a project for this segment because
|
||||
it is used in multiple projects:
|
||||
</span>
|
||||
}
|
||||
elseShow={<span>Usage of this segment:</span>}
|
||||
/>
|
||||
{projectList}
|
||||
</StyledAlert>
|
||||
);
|
||||
|
@ -38,11 +38,15 @@ import FakeFeatureStrategiesStore from '../../../test/fixtures/fake-feature-stra
|
||||
import FakeFeatureEnvironmentStore from '../../../test/fixtures/fake-feature-environment-store';
|
||||
import FakeStrategiesStore from '../../../test/fixtures/fake-strategies-store';
|
||||
import EventStore from '../../db/event-store';
|
||||
import {
|
||||
createFakePrivateProjectChecker,
|
||||
createPrivateProjectChecker,
|
||||
} from '../private-project/createPrivateProjectChecker';
|
||||
|
||||
export const createFakeExportImportTogglesService = (
|
||||
config: IUnleashConfig,
|
||||
): ExportImportService => {
|
||||
const { getLogger } = config;
|
||||
const { getLogger, flagResolver } = config;
|
||||
const importTogglesStore = {} as ImportTogglesStore;
|
||||
const featureToggleStore = new FakeFeatureToggleStore();
|
||||
const tagStore = new FakeTagStore();
|
||||
@ -57,6 +61,7 @@ export const createFakeExportImportTogglesService = (
|
||||
const featureEnvironmentStore = new FakeFeatureEnvironmentStore();
|
||||
const accessService = createFakeAccessService(config);
|
||||
const featureToggleService = createFakeFeatureToggleService(config);
|
||||
const privateProjectChecker = createFakePrivateProjectChecker();
|
||||
|
||||
const featureTagService = new FeatureTagService(
|
||||
{
|
||||
@ -74,7 +79,8 @@ export const createFakeExportImportTogglesService = (
|
||||
contextFieldStore,
|
||||
featureStrategiesStore,
|
||||
},
|
||||
{ getLogger },
|
||||
{ getLogger, flagResolver },
|
||||
privateProjectChecker,
|
||||
);
|
||||
const strategyService = new StrategyService(
|
||||
{ strategyStore, eventStore },
|
||||
@ -152,6 +158,7 @@ export const createExportImportTogglesService = (
|
||||
const eventStore = new EventStore(db, getLogger);
|
||||
const accessService = createAccessService(db, config);
|
||||
const featureToggleService = createFeatureToggleService(db, config);
|
||||
const privateProjectChecker = createPrivateProjectChecker(db, config);
|
||||
|
||||
const featureTagService = new FeatureTagService(
|
||||
{
|
||||
@ -169,7 +176,8 @@ export const createExportImportTogglesService = (
|
||||
contextFieldStore,
|
||||
featureStrategiesStore,
|
||||
},
|
||||
{ getLogger },
|
||||
{ getLogger, flagResolver },
|
||||
privateProjectChecker,
|
||||
);
|
||||
const strategyService = new StrategyService(
|
||||
{ strategyStore, eventStore },
|
||||
|
@ -11,6 +11,10 @@ import {
|
||||
createChangeRequestAccessReadModel,
|
||||
createFakeChangeRequestAccessService,
|
||||
} from '../change-request-access-service/createChangeRequestAccessReadModel';
|
||||
import {
|
||||
createFakePrivateProjectChecker,
|
||||
createPrivateProjectChecker,
|
||||
} from '../private-project/createPrivateProjectChecker';
|
||||
|
||||
export const createSegmentService = (
|
||||
db: Db,
|
||||
@ -34,11 +38,13 @@ export const createSegmentService = (
|
||||
db,
|
||||
config,
|
||||
);
|
||||
const privateProjectChecker = createPrivateProjectChecker(db, config);
|
||||
|
||||
return new SegmentService(
|
||||
{ segmentStore, featureStrategiesStore, eventStore },
|
||||
changeRequestAccessReadModel,
|
||||
config,
|
||||
privateProjectChecker,
|
||||
);
|
||||
};
|
||||
|
||||
@ -50,9 +56,12 @@ export const createFakeSegmentService = (
|
||||
const featureStrategiesStore = new FakeFeatureStrategiesStore();
|
||||
const changeRequestAccessReadModel = createFakeChangeRequestAccessService();
|
||||
|
||||
const privateProjectChecker = createFakePrivateProjectChecker();
|
||||
|
||||
return new SegmentService(
|
||||
{ segmentStore, featureStrategiesStore, eventStore },
|
||||
changeRequestAccessReadModel,
|
||||
config,
|
||||
privateProjectChecker,
|
||||
);
|
||||
};
|
||||
|
@ -347,7 +347,8 @@ export class SegmentsController extends Controller {
|
||||
res: Response<SegmentStrategiesSchema>,
|
||||
): Promise<void> {
|
||||
const { id } = req.params;
|
||||
const strategies = await this.segmentService.getStrategies(id);
|
||||
const { user } = req;
|
||||
const strategies = await this.segmentService.getStrategies(id, user.id);
|
||||
|
||||
// Remove unnecessary IFeatureStrategy fields from the response.
|
||||
const segmentStrategies = strategies.map((strategy) => ({
|
||||
@ -368,7 +369,8 @@ export class SegmentsController extends Controller {
|
||||
res: Response,
|
||||
): Promise<void> {
|
||||
const id = Number(req.params.id);
|
||||
const strategies = await this.segmentService.getStrategies(id);
|
||||
const { user } = req;
|
||||
const strategies = await this.segmentService.getStrategies(id, user.id);
|
||||
|
||||
if (strategies.length > 0) {
|
||||
res.status(409).send();
|
||||
|
@ -301,8 +301,12 @@ export class ContextController extends Controller {
|
||||
res: Response<ContextFieldStrategiesSchema>,
|
||||
): Promise<void> {
|
||||
const { contextField } = req.params;
|
||||
const { user } = req;
|
||||
const contextFields =
|
||||
await this.contextService.getStrategiesByContextField(contextField);
|
||||
await this.contextService.getStrategiesByContextField(
|
||||
contextField,
|
||||
user.id,
|
||||
);
|
||||
|
||||
this.openApiService.respondWithValidation(
|
||||
200,
|
||||
|
@ -13,7 +13,7 @@ export interface ISegmentService {
|
||||
|
||||
get(id: number): Promise<ISegment>;
|
||||
|
||||
getStrategies(id: number): Promise<IFeatureStrategy[]>;
|
||||
getStrategies(id: number, userId: number): Promise<IFeatureStrategy[]>;
|
||||
|
||||
validateName(name: string): Promise<void>;
|
||||
|
||||
|
@ -9,6 +9,8 @@ import { IProjectStore } from '../types/stores/project-store';
|
||||
import { IFeatureStrategiesStore, IUnleashStores } from '../types/stores';
|
||||
import { IUnleashConfig } from '../types/option';
|
||||
import { ContextFieldStrategiesSchema } from '../openapi/spec/context-field-strategies-schema';
|
||||
import { IFeatureStrategy, IFlagResolver } from '../types';
|
||||
import { IPrivateProjectChecker } from '../features/private-project/privateProjectCheckerType';
|
||||
|
||||
const { contextSchema, nameSchema } = require('./context-schema');
|
||||
const NameExistsError = require('../error/name-exists-error');
|
||||
@ -30,6 +32,10 @@ class ContextService {
|
||||
|
||||
private logger: Logger;
|
||||
|
||||
private flagResolver: IFlagResolver;
|
||||
|
||||
private privateProjectChecker: IPrivateProjectChecker;
|
||||
|
||||
constructor(
|
||||
{
|
||||
projectStore,
|
||||
@ -43,10 +49,16 @@ class ContextService {
|
||||
| 'contextFieldStore'
|
||||
| 'featureStrategiesStore'
|
||||
>,
|
||||
{ getLogger }: Pick<IUnleashConfig, 'getLogger'>,
|
||||
{
|
||||
getLogger,
|
||||
flagResolver,
|
||||
}: Pick<IUnleashConfig, 'getLogger' | 'flagResolver'>,
|
||||
privateProjectChecker: IPrivateProjectChecker,
|
||||
) {
|
||||
this.privateProjectChecker = privateProjectChecker;
|
||||
this.projectStore = projectStore;
|
||||
this.eventStore = eventStore;
|
||||
this.flagResolver = flagResolver;
|
||||
this.contextFieldStore = contextFieldStore;
|
||||
this.featureStrategiesStore = featureStrategiesStore;
|
||||
this.logger = getLogger('services/context-service.js');
|
||||
@ -62,9 +74,31 @@ class ContextService {
|
||||
|
||||
async getStrategiesByContextField(
|
||||
name: string,
|
||||
userId: number,
|
||||
): Promise<ContextFieldStrategiesSchema> {
|
||||
const strategies =
|
||||
await this.featureStrategiesStore.getStrategiesByContextField(name);
|
||||
if (this.flagResolver.isEnabled('privateProjects')) {
|
||||
const accessibleProjects =
|
||||
await this.privateProjectChecker.getUserAccessibleProjects(
|
||||
userId,
|
||||
);
|
||||
if (accessibleProjects.mode === 'all') {
|
||||
return this.mapStrategies(strategies);
|
||||
} else {
|
||||
return this.mapStrategies(
|
||||
strategies.filter((strategy) =>
|
||||
accessibleProjects.projects.includes(
|
||||
strategy.projectId,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
return this.mapStrategies(strategies);
|
||||
}
|
||||
|
||||
private mapStrategies(strategies: IFeatureStrategy[]) {
|
||||
return {
|
||||
strategies: strategies.map((strategy) => ({
|
||||
id: strategy.id,
|
||||
|
@ -169,7 +169,15 @@ export const createServices = (
|
||||
config,
|
||||
lastSeenService,
|
||||
);
|
||||
const contextService = new ContextService(stores, config);
|
||||
const privateProjectChecker = db
|
||||
? createPrivateProjectChecker(db, config)
|
||||
: createFakePrivateProjectChecker();
|
||||
|
||||
const contextService = new ContextService(
|
||||
stores,
|
||||
config,
|
||||
privateProjectChecker,
|
||||
);
|
||||
const emailService = new EmailService(config.email, config.getLogger);
|
||||
const eventService = new EventService(stores, config);
|
||||
const featureTypeService = new FeatureTypeService(stores, config);
|
||||
@ -201,11 +209,9 @@ export const createServices = (
|
||||
stores,
|
||||
changeRequestAccessReadModel,
|
||||
config,
|
||||
privateProjectChecker,
|
||||
);
|
||||
|
||||
const privateProjectChecker = db
|
||||
? createPrivateProjectChecker(db, config)
|
||||
: createFakePrivateProjectChecker();
|
||||
const clientInstanceService = new ClientInstanceService(
|
||||
stores,
|
||||
config,
|
||||
|
@ -22,6 +22,7 @@ import BadDataError from '../error/bad-data-error';
|
||||
import { ISegmentService } from '../segments/segment-service-interface';
|
||||
import { PermissionError } from '../error';
|
||||
import { IChangeRequestAccessReadModel } from '../features/change-request-access-service/change-request-access-read-model';
|
||||
import { IPrivateProjectChecker } from '../features/private-project/privateProjectCheckerType';
|
||||
|
||||
export class SegmentService implements ISegmentService {
|
||||
private logger: Logger;
|
||||
@ -38,6 +39,8 @@ export class SegmentService implements ISegmentService {
|
||||
|
||||
private flagResolver: IFlagResolver;
|
||||
|
||||
private privateProjectChecker: IPrivateProjectChecker;
|
||||
|
||||
constructor(
|
||||
{
|
||||
segmentStore,
|
||||
@ -49,11 +52,13 @@ export class SegmentService implements ISegmentService {
|
||||
>,
|
||||
changeRequestAccessReadModel: IChangeRequestAccessReadModel,
|
||||
config: IUnleashConfig,
|
||||
privateProjectChecker: IPrivateProjectChecker,
|
||||
) {
|
||||
this.segmentStore = segmentStore;
|
||||
this.featureStrategiesStore = featureStrategiesStore;
|
||||
this.eventStore = eventStore;
|
||||
this.changeRequestAccessReadModel = changeRequestAccessReadModel;
|
||||
this.privateProjectChecker = privateProjectChecker;
|
||||
this.logger = config.getLogger('services/segment-service.ts');
|
||||
this.flagResolver = config.flagResolver;
|
||||
this.config = config;
|
||||
@ -81,8 +86,26 @@ export class SegmentService implements ISegmentService {
|
||||
}
|
||||
|
||||
// Used by unleash-enterprise.
|
||||
async getStrategies(id: number): Promise<IFeatureStrategy[]> {
|
||||
return this.featureStrategiesStore.getStrategiesBySegment(id);
|
||||
async getStrategies(
|
||||
id: number,
|
||||
userId: number,
|
||||
): Promise<IFeatureStrategy[]> {
|
||||
const strategies =
|
||||
await this.featureStrategiesStore.getStrategiesBySegment(id);
|
||||
if (this.flagResolver.isEnabled('privateProjects')) {
|
||||
const accessibleProjects =
|
||||
await this.privateProjectChecker.getUserAccessibleProjects(
|
||||
userId,
|
||||
);
|
||||
if (accessibleProjects.mode === 'all') {
|
||||
return strategies;
|
||||
} else {
|
||||
return strategies.filter((strategy) =>
|
||||
accessibleProjects.projects.includes(strategy.projectId),
|
||||
);
|
||||
}
|
||||
}
|
||||
return strategies;
|
||||
}
|
||||
|
||||
async create(
|
||||
|
@ -253,7 +253,12 @@ beforeAll(async () => {
|
||||
featureToggleService = new FeatureToggleService(
|
||||
stores,
|
||||
config,
|
||||
new SegmentService(stores, changeRequestAccessReadModel, config),
|
||||
new SegmentService(
|
||||
stores,
|
||||
changeRequestAccessReadModel,
|
||||
config,
|
||||
privateProjectChecker,
|
||||
),
|
||||
accessService,
|
||||
changeRequestAccessReadModel,
|
||||
privateProjectChecker,
|
||||
|
@ -39,7 +39,12 @@ beforeAll(async () => {
|
||||
const featureToggleService = new FeatureToggleService(
|
||||
stores,
|
||||
config,
|
||||
new SegmentService(stores, changeRequestAccessReadModel, config),
|
||||
new SegmentService(
|
||||
stores,
|
||||
changeRequestAccessReadModel,
|
||||
config,
|
||||
privateProjectChecker,
|
||||
),
|
||||
accessService,
|
||||
changeRequestAccessReadModel,
|
||||
privateProjectChecker,
|
||||
|
@ -54,15 +54,17 @@ beforeAll(async () => {
|
||||
db.rawDatabase,
|
||||
accessService,
|
||||
);
|
||||
segmentService = new SegmentService(
|
||||
stores,
|
||||
changeRequestAccessReadModel,
|
||||
config,
|
||||
);
|
||||
const privateProjectChecker = createPrivateProjectChecker(
|
||||
db.rawDatabase,
|
||||
config,
|
||||
);
|
||||
segmentService = new SegmentService(
|
||||
stores,
|
||||
changeRequestAccessReadModel,
|
||||
config,
|
||||
privateProjectChecker,
|
||||
);
|
||||
|
||||
service = new FeatureToggleService(
|
||||
stores,
|
||||
config,
|
||||
|
@ -43,15 +43,17 @@ beforeAll(async () => {
|
||||
db.rawDatabase,
|
||||
accessService,
|
||||
);
|
||||
segmentService = new SegmentService(
|
||||
stores,
|
||||
changeRequestAccessReadModel,
|
||||
config,
|
||||
);
|
||||
const privateProjectChecker = createPrivateProjectChecker(
|
||||
db.rawDatabase,
|
||||
config,
|
||||
);
|
||||
segmentService = new SegmentService(
|
||||
stores,
|
||||
changeRequestAccessReadModel,
|
||||
config,
|
||||
privateProjectChecker,
|
||||
);
|
||||
|
||||
featureToggleService = new FeatureToggleService(
|
||||
stores,
|
||||
config,
|
||||
|
@ -44,7 +44,12 @@ beforeAll(async () => {
|
||||
featureToggleService = new FeatureToggleService(
|
||||
stores,
|
||||
config,
|
||||
new SegmentService(stores, changeRequestAccessReadModel, config),
|
||||
new SegmentService(
|
||||
stores,
|
||||
changeRequestAccessReadModel,
|
||||
config,
|
||||
privateProjectChecker,
|
||||
),
|
||||
accessService,
|
||||
changeRequestAccessReadModel,
|
||||
privateProjectChecker,
|
||||
|
@ -65,7 +65,12 @@ beforeAll(async () => {
|
||||
featureToggleService = new FeatureToggleService(
|
||||
stores,
|
||||
config,
|
||||
new SegmentService(stores, changeRequestAccessReadModel, config),
|
||||
new SegmentService(
|
||||
stores,
|
||||
changeRequestAccessReadModel,
|
||||
config,
|
||||
privateProjectChecker,
|
||||
),
|
||||
accessService,
|
||||
changeRequestAccessReadModel,
|
||||
privateProjectChecker,
|
||||
|
Loading…
Reference in New Issue
Block a user