mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-31 00:16:47 +01:00
feat: update strategy segments with edit / create strategy (#2420)
* Refactors how we add / edit segments to make it more ergonomic to work with in regards to change requests
This commit is contained in:
parent
51ad239553
commit
978674e33a
@ -10,9 +10,10 @@ import {
|
|||||||
import StringTruncator from 'component/common/StringTruncator/StringTruncator';
|
import StringTruncator from 'component/common/StringTruncator/StringTruncator';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { useStyles } from './StrategyItemContainer.styles';
|
import { useStyles } from './StrategyItemContainer.styles';
|
||||||
|
import { PlaygroundStrategySchema } from 'component/playground/Playground/interfaces/playground.model';
|
||||||
|
|
||||||
interface IStrategyItemContainerProps {
|
interface IStrategyItemContainerProps {
|
||||||
strategy: IFeatureStrategy;
|
strategy: IFeatureStrategy | PlaygroundStrategySchema;
|
||||||
onDragStart?: DragEventHandler<HTMLButtonElement>;
|
onDragStart?: DragEventHandler<HTMLButtonElement>;
|
||||||
onDragEnd?: DragEventHandler<HTMLButtonElement>;
|
onDragEnd?: DragEventHandler<HTMLButtonElement>;
|
||||||
actions?: ReactNode;
|
actions?: ReactNode;
|
||||||
|
@ -90,14 +90,7 @@ export const FeatureStrategyCreate = () => {
|
|||||||
environmentId,
|
environmentId,
|
||||||
payload
|
payload
|
||||||
);
|
);
|
||||||
if (uiConfig.flags.SE) {
|
|
||||||
await setStrategySegments({
|
|
||||||
environmentId,
|
|
||||||
projectId,
|
|
||||||
strategyId: created.id,
|
|
||||||
segmentIds: segments.map(s => s.id),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
setToastData({
|
setToastData({
|
||||||
title: 'Strategy created',
|
title: 'Strategy created',
|
||||||
type: 'success',
|
type: 'success',
|
||||||
@ -121,7 +114,7 @@ export const FeatureStrategyCreate = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onSubmit = async () => {
|
const onSubmit = async () => {
|
||||||
const payload = createStrategyPayload(strategy);
|
const payload = createStrategyPayload(strategy, segments);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (isChangeRequestConfigured(environmentId)) {
|
if (isChangeRequestConfigured(environmentId)) {
|
||||||
|
@ -101,15 +101,8 @@ export const FeatureStrategyEdit = () => {
|
|||||||
strategyId,
|
strategyId,
|
||||||
payload
|
payload
|
||||||
);
|
);
|
||||||
if (uiConfig.flags.SE) {
|
|
||||||
await setStrategySegments({
|
|
||||||
environmentId,
|
|
||||||
projectId,
|
|
||||||
strategyId,
|
|
||||||
segmentIds: segments.map(s => s.id),
|
|
||||||
});
|
|
||||||
await refetchSavedStrategySegments();
|
await refetchSavedStrategySegments();
|
||||||
}
|
|
||||||
setToastData({
|
setToastData({
|
||||||
title: 'Strategy updated',
|
title: 'Strategy updated',
|
||||||
type: 'success',
|
type: 'success',
|
||||||
@ -133,7 +126,8 @@ export const FeatureStrategyEdit = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onSubmit = async () => {
|
const onSubmit = async () => {
|
||||||
const payload = createStrategyPayload(strategy);
|
const segmentsToSubmit = uiConfig?.flags.SE ? segments : [];
|
||||||
|
const payload = createStrategyPayload(strategy, segmentsToSubmit);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (isChangeRequestConfigured(environmentId)) {
|
if (isChangeRequestConfigured(environmentId)) {
|
||||||
@ -191,11 +185,13 @@ export const FeatureStrategyEdit = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const createStrategyPayload = (
|
export const createStrategyPayload = (
|
||||||
strategy: Partial<IFeatureStrategy>
|
strategy: Partial<IFeatureStrategy>,
|
||||||
|
segments: ISegment[]
|
||||||
): IFeatureStrategyPayload => ({
|
): IFeatureStrategyPayload => ({
|
||||||
name: strategy.name,
|
name: strategy.name,
|
||||||
constraints: strategy.constraints ?? [],
|
constraints: strategy.constraints ?? [],
|
||||||
parameters: strategy.parameters ?? {},
|
parameters: strategy.parameters ?? {},
|
||||||
|
segments: segments.map(segment => segment.id),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const formatFeaturePath = (
|
export const formatFeaturePath = (
|
||||||
|
@ -86,7 +86,6 @@ export const FeatureStrategyEmpty = ({
|
|||||||
const { id, ...strategyCopy } = {
|
const { id, ...strategyCopy } = {
|
||||||
...strategy,
|
...strategy,
|
||||||
environment: environmentId,
|
environment: environmentId,
|
||||||
copyOf: strategy.id,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return addStrategyToFeature(
|
return addStrategyToFeature(
|
||||||
|
@ -8,7 +8,7 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { AddToPhotos as CopyIcon, Lock } from '@mui/icons-material';
|
import { AddToPhotos as CopyIcon, Lock } from '@mui/icons-material';
|
||||||
import { IFeatureStrategy } from 'interfaces/strategy';
|
import { IFeatureStrategy, IFeatureStrategyPayload } from 'interfaces/strategy';
|
||||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||||
import { IFeatureEnvironment } from 'interfaces/featureToggle';
|
import { IFeatureEnvironment } from 'interfaces/featureToggle';
|
||||||
import AccessContext from 'contexts/AccessContext';
|
import AccessContext from 'contexts/AccessContext';
|
||||||
@ -27,7 +27,7 @@ import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
|
|||||||
interface ICopyStrategyIconMenuProps {
|
interface ICopyStrategyIconMenuProps {
|
||||||
environmentId: string;
|
environmentId: string;
|
||||||
environments: IFeatureEnvironment['name'][];
|
environments: IFeatureEnvironment['name'][];
|
||||||
strategy: IFeatureStrategy;
|
strategy: IFeatureStrategyPayload;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CopyStrategyIconMenu: VFC<ICopyStrategyIconMenuProps> = ({
|
export const CopyStrategyIconMenu: VFC<ICopyStrategyIconMenuProps> = ({
|
||||||
@ -60,15 +60,14 @@ export const CopyStrategyIconMenu: VFC<ICopyStrategyIconMenuProps> = ({
|
|||||||
onChangeRequestAddStrategyConfirm,
|
onChangeRequestAddStrategyConfirm,
|
||||||
} = useChangeRequestAddStrategy(projectId, featureId, 'addStrategy');
|
} = useChangeRequestAddStrategy(projectId, featureId, 'addStrategy');
|
||||||
|
|
||||||
const onCopyStrategy = async (environment: string) => {
|
const onCopyStrategy = async (targetEnvironment: string) => {
|
||||||
const { id, ...strategyCopy } = {
|
const { id, ...strategyCopy } = {
|
||||||
...strategy,
|
...strategy,
|
||||||
environment,
|
targetEnvironment,
|
||||||
copyOf: strategy.id,
|
|
||||||
};
|
};
|
||||||
if (isChangeRequestConfigured(environmentId)) {
|
if (isChangeRequestConfigured(targetEnvironment)) {
|
||||||
await onChangeRequestAddStrategy(
|
await onChangeRequestAddStrategy(
|
||||||
environment,
|
targetEnvironment,
|
||||||
{
|
{
|
||||||
id,
|
id,
|
||||||
...strategyCopy,
|
...strategyCopy,
|
||||||
@ -82,14 +81,14 @@ export const CopyStrategyIconMenu: VFC<ICopyStrategyIconMenuProps> = ({
|
|||||||
await addStrategyToFeature(
|
await addStrategyToFeature(
|
||||||
projectId,
|
projectId,
|
||||||
featureId,
|
featureId,
|
||||||
environment,
|
targetEnvironment,
|
||||||
strategy
|
strategy
|
||||||
);
|
);
|
||||||
refetchFeature();
|
refetchFeature();
|
||||||
refetchFeatureImmutable();
|
refetchFeatureImmutable();
|
||||||
setToastData({
|
setToastData({
|
||||||
title: `Strategy created`,
|
title: `Strategy created`,
|
||||||
text: `Successfully copied a strategy to ${environment}`,
|
text: `Successfully copied a strategy to ${targetEnvironment}`,
|
||||||
type: 'success',
|
type: 'success',
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -8,6 +8,7 @@ import { StrategyExecution } from './StrategyExecution/StrategyExecution';
|
|||||||
import { useStyles } from './FeatureStrategyItem.styles';
|
import { useStyles } from './FeatureStrategyItem.styles';
|
||||||
import { StrategyItemContainer } from 'component/common/StrategyItemContainer/StrategyItemContainer';
|
import { StrategyItemContainer } from 'component/common/StrategyItemContainer/StrategyItemContainer';
|
||||||
import { objectId } from 'utils/objectId';
|
import { objectId } from 'utils/objectId';
|
||||||
|
import { IFeatureStrategy } from 'interfaces/strategy';
|
||||||
|
|
||||||
interface IFeatureStrategyItemProps {
|
interface IFeatureStrategyItemProps {
|
||||||
strategy: PlaygroundStrategySchema;
|
strategy: PlaygroundStrategySchema;
|
||||||
|
@ -9,6 +9,7 @@ export interface IFeatureStrategy {
|
|||||||
featureName?: string;
|
featureName?: string;
|
||||||
projectId?: string;
|
projectId?: string;
|
||||||
environment?: string;
|
environment?: string;
|
||||||
|
segments?: number[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IFeatureStrategyParameters {
|
export interface IFeatureStrategyParameters {
|
||||||
@ -20,7 +21,7 @@ export interface IFeatureStrategyPayload {
|
|||||||
name?: string;
|
name?: string;
|
||||||
constraints: IConstraint[];
|
constraints: IConstraint[];
|
||||||
parameters: IFeatureStrategyParameters;
|
parameters: IFeatureStrategyParameters;
|
||||||
copyOf?: string;
|
segments?: number[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IStrategy {
|
export interface IStrategy {
|
||||||
|
@ -11,11 +11,12 @@ import {
|
|||||||
IEnvironmentOverview,
|
IEnvironmentOverview,
|
||||||
IFeatureOverview,
|
IFeatureOverview,
|
||||||
IFeatureStrategy,
|
IFeatureStrategy,
|
||||||
|
IFeatureToggleClient,
|
||||||
IStrategyConfig,
|
IStrategyConfig,
|
||||||
ITag,
|
ITag,
|
||||||
} from '../types/model';
|
} from '../types/model';
|
||||||
import { IFeatureStrategiesStore } from '../types/stores/feature-strategies-store';
|
import { IFeatureStrategiesStore } from '../types/stores/feature-strategies-store';
|
||||||
import { PartialSome } from '../types/partial';
|
import { PartialDeep, PartialSome } from '../types/partial';
|
||||||
import FeatureToggleStore from './feature-toggle-store';
|
import FeatureToggleStore from './feature-toggle-store';
|
||||||
import { ensureStringValue } from '../util/ensureStringValue';
|
import { ensureStringValue } from '../util/ensureStringValue';
|
||||||
import { mapValues } from '../util/map-values';
|
import { mapValues } from '../util/map-values';
|
||||||
@ -237,6 +238,7 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
|
|||||||
'feature_strategies.parameters as parameters',
|
'feature_strategies.parameters as parameters',
|
||||||
'feature_strategies.constraints as constraints',
|
'feature_strategies.constraints as constraints',
|
||||||
'feature_strategies.sort_order as sort_order',
|
'feature_strategies.sort_order as sort_order',
|
||||||
|
'fss.segment_id as segments',
|
||||||
)
|
)
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
'feature_environments',
|
'feature_environments',
|
||||||
@ -259,6 +261,11 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
|
|||||||
'feature_environments.environment',
|
'feature_environments.environment',
|
||||||
'environments.name',
|
'environments.name',
|
||||||
)
|
)
|
||||||
|
.leftJoin(
|
||||||
|
'feature_strategy_segment as fss',
|
||||||
|
`fss.feature_strategy_id`,
|
||||||
|
`feature_strategies.id`,
|
||||||
|
)
|
||||||
.where('features.name', featureName)
|
.where('features.name', featureName)
|
||||||
.modify(FeatureToggleStore.filterByArchived, archived);
|
.modify(FeatureToggleStore.filterByArchived, archived);
|
||||||
stopTimer();
|
stopTimer();
|
||||||
@ -267,6 +274,7 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
|
|||||||
if (acc.environments === undefined) {
|
if (acc.environments === undefined) {
|
||||||
acc.environments = {};
|
acc.environments = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
acc.name = r.name;
|
acc.name = r.name;
|
||||||
acc.impressionData = r.impression_data;
|
acc.impressionData = r.impression_data;
|
||||||
acc.description = r.description;
|
acc.description = r.description;
|
||||||
@ -281,6 +289,7 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
|
|||||||
name: r.environment,
|
name: r.environment,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const env = acc.environments[r.environment];
|
const env = acc.environments[r.environment];
|
||||||
env.enabled = r.enabled;
|
env.enabled = r.enabled;
|
||||||
env.type = r.environment_type;
|
env.type = r.environment_type;
|
||||||
@ -289,10 +298,18 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
|
|||||||
env.strategies = [];
|
env.strategies = [];
|
||||||
}
|
}
|
||||||
if (r.strategy_id) {
|
if (r.strategy_id) {
|
||||||
|
const found = env.strategies.find(
|
||||||
|
(strategy) => strategy.id === r.strategy_id,
|
||||||
|
);
|
||||||
|
if (!found) {
|
||||||
env.strategies.push(
|
env.strategies.push(
|
||||||
FeatureStrategiesStore.getAdminStrategy(r),
|
FeatureStrategiesStore.getAdminStrategy(r),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (r.segments) {
|
||||||
|
this.addSegmentIdsToStrategy(env, r);
|
||||||
|
}
|
||||||
acc.environments[r.environment] = env;
|
acc.environments[r.environment] = env;
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
@ -318,6 +335,22 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private addSegmentIdsToStrategy(
|
||||||
|
feature: PartialDeep<IFeatureToggleClient>,
|
||||||
|
row: Record<string, any>,
|
||||||
|
) {
|
||||||
|
const strategy = feature.strategies.find(
|
||||||
|
(s) => s.id === row.strategy_id,
|
||||||
|
);
|
||||||
|
if (!strategy) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!strategy.segments) {
|
||||||
|
strategy.segments = [];
|
||||||
|
}
|
||||||
|
strategy.segments.push(row.segments);
|
||||||
|
}
|
||||||
|
|
||||||
private static getEnvironment(r: any): IEnvironmentOverview {
|
private static getEnvironment(r: any): IEnvironmentOverview {
|
||||||
return {
|
return {
|
||||||
name: r.environment,
|
name: r.environment,
|
||||||
|
@ -19,9 +19,6 @@ export const createFeatureStrategySchema = {
|
|||||||
$ref: '#/components/schemas/constraintSchema',
|
$ref: '#/components/schemas/constraintSchema',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
copyOf: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
parameters: {
|
parameters: {
|
||||||
$ref: '#/components/schemas/parametersSchema',
|
$ref: '#/components/schemas/parametersSchema',
|
||||||
},
|
},
|
||||||
|
@ -598,7 +598,11 @@ export default class ProjectFeaturesController extends Controller {
|
|||||||
res: Response<FeatureStrategySchema>,
|
res: Response<FeatureStrategySchema>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { projectId, featureName, environment } = req.params;
|
const { projectId, featureName, environment } = req.params;
|
||||||
const { copyOf, ...strategyConfig } = req.body;
|
const { ...strategyConfig } = req.body;
|
||||||
|
|
||||||
|
if (!strategyConfig.segmentIds) {
|
||||||
|
strategyConfig.segmentIds = [];
|
||||||
|
}
|
||||||
|
|
||||||
const userName = extractUsername(req);
|
const userName = extractUsername(req);
|
||||||
const strategy = await this.featureService.createStrategy(
|
const strategy = await this.featureService.createStrategy(
|
||||||
@ -607,16 +611,6 @@ export default class ProjectFeaturesController extends Controller {
|
|||||||
userName,
|
userName,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (copyOf) {
|
|
||||||
this.logger.info(
|
|
||||||
`Cloning segments from: strategyId=${copyOf} to: strategyId=${strategy.id} `,
|
|
||||||
);
|
|
||||||
await this.segmentService.cloneStrategySegments(
|
|
||||||
copyOf,
|
|
||||||
strategy.id,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const updatedStrategy = await this.featureService.getStrategy(
|
const updatedStrategy = await this.featureService.getStrategy(
|
||||||
strategy.id,
|
strategy.id,
|
||||||
);
|
);
|
||||||
@ -661,6 +655,11 @@ export default class ProjectFeaturesController extends Controller {
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { strategyId, environment, projectId, featureName } = req.params;
|
const { strategyId, environment, projectId, featureName } = req.params;
|
||||||
const userName = extractUsername(req);
|
const userName = extractUsername(req);
|
||||||
|
|
||||||
|
if (!req.body.segmentIds) {
|
||||||
|
req.body.segmentIds = [];
|
||||||
|
}
|
||||||
|
|
||||||
const updatedStrategy = await this.featureService.updateStrategy(
|
const updatedStrategy = await this.featureService.updateStrategy(
|
||||||
strategyId,
|
strategyId,
|
||||||
req.body,
|
req.body,
|
||||||
|
@ -332,6 +332,16 @@ class FeatureToggleService {
|
|||||||
environment,
|
environment,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (
|
||||||
|
strategyConfig.segments &&
|
||||||
|
Array.isArray(strategyConfig.segments)
|
||||||
|
) {
|
||||||
|
await this.segmentService.updateStrategySegments(
|
||||||
|
newFeatureStrategy.id,
|
||||||
|
strategyConfig.segments,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const tags = await this.tagStore.getAllTagsForFeature(featureName);
|
const tags = await this.tagStore.getAllTagsForFeature(featureName);
|
||||||
const segments = await this.segmentService.getByStrategy(
|
const segments = await this.segmentService.getByStrategy(
|
||||||
newFeatureStrategy.id,
|
newFeatureStrategy.id,
|
||||||
@ -394,6 +404,13 @@ class FeatureToggleService {
|
|||||||
updates,
|
updates,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (updates.segments && Array.isArray(updates.segments)) {
|
||||||
|
await this.segmentService.updateStrategySegments(
|
||||||
|
strategy.id,
|
||||||
|
updates.segments,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const segments = await this.segmentService.getByStrategy(
|
const segments = await this.segmentService.getByStrategy(
|
||||||
strategy.id,
|
strategy.id,
|
||||||
);
|
);
|
||||||
|
@ -140,6 +140,40 @@ export class SegmentService {
|
|||||||
await this.segmentStore.addToStrategy(id, strategyId);
|
await this.segmentStore.addToStrategy(id, strategyId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updateStrategySegments(
|
||||||
|
strategyId: string,
|
||||||
|
segmentIds: number[],
|
||||||
|
): Promise<void> {
|
||||||
|
if (segmentIds.length > this.config.strategySegmentsLimit) {
|
||||||
|
throw new BadDataError(
|
||||||
|
`Strategies may not have more than ${this.config.strategySegmentsLimit} segments`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const segments = await this.getByStrategy(strategyId);
|
||||||
|
const currentSegmentIds = segments.map((segment) => segment.id);
|
||||||
|
|
||||||
|
const segmentIdsToRemove = currentSegmentIds.filter(
|
||||||
|
(id) => !segmentIds.includes(id),
|
||||||
|
);
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
segmentIdsToRemove.map((segmentId) =>
|
||||||
|
this.removeFromStrategy(segmentId, strategyId),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const segmentIdsToAdd = segmentIds.filter(
|
||||||
|
(id) => !currentSegmentIds.includes(id),
|
||||||
|
);
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
segmentIdsToAdd.map((segmentId) =>
|
||||||
|
this.addToStrategy(segmentId, strategyId),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Used by unleash-enterprise.
|
// Used by unleash-enterprise.
|
||||||
async removeFromStrategy(id: number, strategyId: string): Promise<void> {
|
async removeFromStrategy(id: number, strategyId: string): Promise<void> {
|
||||||
await this.segmentStore.removeFromStrategy(id, strategyId);
|
await this.segmentStore.removeFromStrategy(id, strategyId);
|
||||||
|
@ -36,6 +36,7 @@ export interface IFeatureStrategy {
|
|||||||
sortOrder?: number;
|
sortOrder?: number;
|
||||||
constraints: IConstraint[];
|
constraints: IConstraint[];
|
||||||
createdAt?: Date;
|
createdAt?: Date;
|
||||||
|
segments?: number[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FeatureToggleDTO {
|
export interface FeatureToggleDTO {
|
||||||
|
@ -13,7 +13,12 @@ import {
|
|||||||
import ApiUser from '../../../../../lib/types/api-user';
|
import ApiUser from '../../../../../lib/types/api-user';
|
||||||
import { ApiTokenType } from '../../../../../lib/types/models/api-token';
|
import { ApiTokenType } from '../../../../../lib/types/models/api-token';
|
||||||
import IncompatibleProjectError from '../../../../../lib/error/incompatible-project-error';
|
import IncompatibleProjectError from '../../../../../lib/error/incompatible-project-error';
|
||||||
import { IVariant, RoleName, WeightType } from '../../../../../lib/types/model';
|
import {
|
||||||
|
IStrategyConfig,
|
||||||
|
IVariant,
|
||||||
|
RoleName,
|
||||||
|
WeightType,
|
||||||
|
} from '../../../../../lib/types/model';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import supertest from 'supertest';
|
import supertest from 'supertest';
|
||||||
import { randomId } from '../../../../../lib/util/random-id';
|
import { randomId } from '../../../../../lib/util/random-id';
|
||||||
@ -24,6 +29,60 @@ const sortOrderFirst = 0;
|
|||||||
const sortOrderSecond = 10;
|
const sortOrderSecond = 10;
|
||||||
const sortOrderDefault = 9999;
|
const sortOrderDefault = 9999;
|
||||||
|
|
||||||
|
const createFeatureToggle = (featureName: string, project = 'default') => {
|
||||||
|
return app.request.post(`/api/admin/projects/${project}/features`).send({
|
||||||
|
name: featureName,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const createSegment = async (segmentName: string) => {
|
||||||
|
const segment = await app.services.segmentService.create(
|
||||||
|
{
|
||||||
|
name: segmentName,
|
||||||
|
description: '',
|
||||||
|
constraints: [
|
||||||
|
{
|
||||||
|
contextName: 'appName',
|
||||||
|
operator: 'IN',
|
||||||
|
values: ['test'],
|
||||||
|
caseInsensitive: false,
|
||||||
|
inverted: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ username: 'testuser', email: 'test@test.com' },
|
||||||
|
);
|
||||||
|
|
||||||
|
return segment;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createStrategy = async (
|
||||||
|
featureName: string,
|
||||||
|
payload: IStrategyConfig,
|
||||||
|
) => {
|
||||||
|
return app.request
|
||||||
|
.post(
|
||||||
|
`/api/admin/projects/default/features/${featureName}/environments/default/strategies`,
|
||||||
|
)
|
||||||
|
.send(payload)
|
||||||
|
.expect(200);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateStrategy = async (
|
||||||
|
featureName: string,
|
||||||
|
strategyId: string,
|
||||||
|
payload: IStrategyConfig,
|
||||||
|
) => {
|
||||||
|
const { body } = await app.request
|
||||||
|
.put(
|
||||||
|
`/api/admin/projects/default/features/${featureName}/environments/default/strategies/${strategyId}`,
|
||||||
|
)
|
||||||
|
.send(payload)
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
return body;
|
||||||
|
};
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
db = await dbInit('feature_strategy_api_serial', getLogger);
|
db = await dbInit('feature_strategy_api_serial', getLogger);
|
||||||
app = await setupApp(db.stores);
|
app = await setupApp(db.stores);
|
||||||
@ -2570,3 +2629,84 @@ test('should return strategies in correct order when new strategies are added',
|
|||||||
expect(strategiesReOrdered[3].sortOrder).toBe(9999);
|
expect(strategiesReOrdered[3].sortOrder).toBe(9999);
|
||||||
expect(strategiesReOrdered[3].id).toBe(strategyThree.id);
|
expect(strategiesReOrdered[3].id).toBe(strategyThree.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test.only('should create a strategy with segments', async () => {
|
||||||
|
const feature = { name: uuidv4(), impressionData: false };
|
||||||
|
await createFeatureToggle(feature.name);
|
||||||
|
const segment = await createSegment('segmentOne');
|
||||||
|
const { body: strategyOne } = await createStrategy(feature.name, {
|
||||||
|
name: 'default',
|
||||||
|
parameters: {
|
||||||
|
userId: 'string',
|
||||||
|
},
|
||||||
|
segments: [segment.id],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Can get the strategy with segment ids
|
||||||
|
await app.request
|
||||||
|
.get(`/api/admin/projects/default/features/${feature.name}`)
|
||||||
|
.expect((res) => {
|
||||||
|
const defaultEnv = res.body.environments.find(
|
||||||
|
(env) => env.name === 'default',
|
||||||
|
);
|
||||||
|
const strategy = defaultEnv.strategies.find(
|
||||||
|
(strat) => strat.id === strategyOne.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(strategy.segments).toEqual([segment.id]);
|
||||||
|
});
|
||||||
|
|
||||||
|
await updateStrategy(feature.name, strategyOne.id, {
|
||||||
|
name: 'default',
|
||||||
|
parameters: {
|
||||||
|
userId: 'string',
|
||||||
|
},
|
||||||
|
segments: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
await app.request
|
||||||
|
.get(`/api/admin/projects/default/features/${feature.name}`)
|
||||||
|
.expect((res) => {
|
||||||
|
const defaultEnv = res.body.environments.find(
|
||||||
|
(env) => env.name === 'default',
|
||||||
|
);
|
||||||
|
const strategy = defaultEnv.strategies.find(
|
||||||
|
(strat) => strat.id === strategyOne.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(strategy.segments).toBe(undefined);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.only('should add multiple segments to a strategy', async () => {
|
||||||
|
const feature = { name: uuidv4(), impressionData: false };
|
||||||
|
await createFeatureToggle(feature.name);
|
||||||
|
const segment = await createSegment('seg1');
|
||||||
|
const segmentTwo = await createSegment('seg2');
|
||||||
|
const segmentThree = await createSegment('seg3');
|
||||||
|
const { body: strategyOne } = await createStrategy(feature.name, {
|
||||||
|
name: 'default',
|
||||||
|
parameters: {
|
||||||
|
userId: 'string',
|
||||||
|
},
|
||||||
|
segments: [segment.id, segmentTwo.id, segmentThree.id],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Can get the strategy with segment ids
|
||||||
|
await app.request
|
||||||
|
.get(`/api/admin/projects/default/features/${feature.name}`)
|
||||||
|
.expect((res) => {
|
||||||
|
const defaultEnv = res.body.environments.find(
|
||||||
|
(env) => env.name === 'default',
|
||||||
|
);
|
||||||
|
const strategy = defaultEnv.strategies.find(
|
||||||
|
(strat) => strat.id === strategyOne.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(strategy.segments).toEqual([
|
||||||
|
segment.id,
|
||||||
|
segmentTwo.id,
|
||||||
|
segmentThree.id,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -709,9 +709,6 @@ exports[`should serve the OpenAPI spec 1`] = `
|
|||||||
},
|
},
|
||||||
"type": "array",
|
"type": "array",
|
||||||
},
|
},
|
||||||
"copyOf": {
|
|
||||||
"type": "string",
|
|
||||||
},
|
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user