mirror of
https://github.com/Unleash/unleash.git
synced 2024-12-22 19:07:54 +01:00
feat: add title to strategy (#3510)
<!-- Thanks for creating a PR! To make it easier for reviewers and everyone else to understand what your changes relate to, please add some relevant content to the headings below. Feel free to ignore or delete sections that you don't think are relevant. Thank you! ❤️ --> Adds title column to strategies, feature_strategies and features_view in the db Updates model/schemas ## About the changes <!-- Describe the changes introduced. What are they and why are they being introduced? Feel free to also add screenshots or steps to view the changes if they're visual. --> <!-- Does it close an issue? Multiple? --> Closes # [1-855](https://linear.app/unleash/issue/1-855/allow-for-title-on-strategy-backend) <!-- (For internal contributors): Does it relate to an issue on public roadmap? --> <!-- Relates to [roadmap](https://github.com/orgs/Unleash/projects/10) item: # --> ### Important files <!-- PRs can contain a lot of changes, but not all changes are equally important. Where should a reviewer start looking to get an overview of the changes? Are any files particularly important? --> ## Discussion points <!-- Anything about the PR you'd like to discuss before it gets merged? Got any questions or doubts? --> --------- Signed-off-by: andreas-unleash <andreas@getunleash.ai>
This commit is contained in:
parent
5940a81158
commit
2da279b7fb
@ -10,20 +10,20 @@ import {
|
||||
IConstraint,
|
||||
IEnvironmentOverview,
|
||||
IFeatureOverview,
|
||||
IFeatureStrategiesStore,
|
||||
IFeatureStrategy,
|
||||
IFeatureToggleClient,
|
||||
IFlagResolver,
|
||||
IStrategyConfig,
|
||||
ITag,
|
||||
} from '../types/model';
|
||||
import { IFeatureStrategiesStore } from '../types/stores/feature-strategies-store';
|
||||
import { PartialDeep, PartialSome } from '../types/partial';
|
||||
PartialDeep,
|
||||
PartialSome,
|
||||
} from '../types';
|
||||
import FeatureToggleStore from './feature-toggle-store';
|
||||
import { ensureStringValue } from '../util/ensureStringValue';
|
||||
import { mapValues } from '../util/map-values';
|
||||
import { IFlagResolver } from '../types/experimental';
|
||||
import { ensureStringValue, mapValues } from '../util';
|
||||
import { IFeatureProjectUserParams } from '../routes/admin-api/project/project-features';
|
||||
import Raw = Knex.Raw;
|
||||
import { Db } from './db';
|
||||
import Raw = Knex.Raw;
|
||||
|
||||
const COLUMNS = [
|
||||
'id',
|
||||
@ -31,6 +31,7 @@ const COLUMNS = [
|
||||
'project_name',
|
||||
'environment',
|
||||
'strategy_name',
|
||||
'title',
|
||||
'parameters',
|
||||
'constraints',
|
||||
'created_at',
|
||||
@ -55,6 +56,7 @@ interface IFeatureStrategiesTable {
|
||||
feature_name: string;
|
||||
project_name: string;
|
||||
environment: string;
|
||||
title?: string | null;
|
||||
strategy_name: string;
|
||||
parameters: object;
|
||||
constraints: string;
|
||||
@ -76,6 +78,7 @@ function mapRow(row: IFeatureStrategiesTable): IFeatureStrategy {
|
||||
projectId: row.project_name,
|
||||
environment: row.environment,
|
||||
strategyName: row.strategy_name,
|
||||
title: row.title,
|
||||
parameters: mapValues(row.parameters || {}, ensureStringValue),
|
||||
constraints: (row.constraints as unknown as IConstraint[]) || [],
|
||||
createdAt: row.created_at,
|
||||
@ -90,6 +93,7 @@ function mapInput(input: IFeatureStrategy): IFeatureStrategiesTable {
|
||||
project_name: input.projectId,
|
||||
environment: input.environment,
|
||||
strategy_name: input.strategyName,
|
||||
title: input.title,
|
||||
parameters: input.parameters,
|
||||
constraints: JSON.stringify(input.constraints || []),
|
||||
created_at: input.createdAt,
|
||||
@ -101,6 +105,7 @@ interface StrategyUpdate {
|
||||
strategy_name: string;
|
||||
parameters: object;
|
||||
constraints: string;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
function mapStrategyUpdate(
|
||||
@ -113,6 +118,9 @@ function mapStrategyUpdate(
|
||||
if (input.parameters !== null) {
|
||||
update.parameters = input.parameters;
|
||||
}
|
||||
if (input.title !== null) {
|
||||
update.title = input.title;
|
||||
}
|
||||
update.constraints = JSON.stringify(input.constraints || []);
|
||||
return update;
|
||||
}
|
||||
@ -376,8 +384,8 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
|
||||
feature: PartialDeep<IFeatureToggleClient>,
|
||||
row: Record<string, any>,
|
||||
) {
|
||||
const strategy = feature.strategies.find(
|
||||
(s) => s.id === row.strategy_id,
|
||||
const strategy = feature.strategies?.find(
|
||||
(s) => s?.id === row.strategy_id,
|
||||
);
|
||||
if (!strategy) {
|
||||
return;
|
||||
@ -581,6 +589,7 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
|
||||
parameters: r.parameters,
|
||||
sortOrder: r.sort_order,
|
||||
id: r.strategy_id,
|
||||
title: r.strategy_title || '',
|
||||
};
|
||||
if (!includeId) {
|
||||
delete strategy.id;
|
||||
|
@ -11,6 +11,7 @@ import {
|
||||
import { Db } from './db';
|
||||
|
||||
const STRATEGY_COLUMNS = [
|
||||
'title',
|
||||
'name',
|
||||
'description',
|
||||
'parameters',
|
||||
@ -21,6 +22,7 @@ const STRATEGY_COLUMNS = [
|
||||
const TABLE = 'strategies';
|
||||
|
||||
interface IStrategyRow {
|
||||
title: string;
|
||||
name: string;
|
||||
built_in: number;
|
||||
description: string;
|
||||
@ -109,6 +111,7 @@ export default class StrategyStore implements IStrategyStore {
|
||||
description: row.description,
|
||||
parameters: row.parameters,
|
||||
deprecated: row.deprecated,
|
||||
title: row.title,
|
||||
};
|
||||
}
|
||||
|
||||
@ -121,6 +124,7 @@ export default class StrategyStore implements IStrategyStore {
|
||||
description: row.description,
|
||||
parameters: row.parameters,
|
||||
deprecated: row.deprecated,
|
||||
title: row.title,
|
||||
};
|
||||
}
|
||||
|
||||
@ -130,6 +134,7 @@ export default class StrategyStore implements IStrategyStore {
|
||||
name: data.name,
|
||||
description: data.description,
|
||||
parameters: JSON.stringify(data.parameters),
|
||||
title: data.title,
|
||||
};
|
||||
}
|
||||
|
||||
@ -166,6 +171,7 @@ export default class StrategyStore implements IStrategyStore {
|
||||
built_in: data.builtIn ? 1 : 0,
|
||||
sort_order: data.sortOrder || 9999,
|
||||
display_name: data.displayName,
|
||||
title: data.title,
|
||||
};
|
||||
await this.db(TABLE).insert(rowData).onConflict(['name']).merge();
|
||||
}
|
||||
|
@ -9,22 +9,49 @@ export const createFeatureStrategySchema = {
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
description: 'The name or type of strategy',
|
||||
example: 'flexibleRollout',
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
description: 'A descriptive title for the strategy',
|
||||
example: 'Gradual Rollout 25-Prod',
|
||||
},
|
||||
sortOrder: {
|
||||
type: 'number',
|
||||
description: 'The order of the strategy in the list',
|
||||
example: 9999,
|
||||
},
|
||||
constraints: {
|
||||
type: 'array',
|
||||
description: 'A list of the constraints attached to the strategy',
|
||||
example: [
|
||||
{
|
||||
values: ['1', '2'],
|
||||
inverted: false,
|
||||
operator: 'IN',
|
||||
contextName: 'appName',
|
||||
caseInsensitive: false,
|
||||
},
|
||||
],
|
||||
items: {
|
||||
$ref: '#/components/schemas/constraintSchema',
|
||||
},
|
||||
},
|
||||
parameters: {
|
||||
description: 'An object containing the parameters for the strategy',
|
||||
example: {
|
||||
groupId: 'some_new',
|
||||
rollout: '25',
|
||||
stickiness: 'sessionId',
|
||||
},
|
||||
$ref: '#/components/schemas/parametersSchema',
|
||||
},
|
||||
segments: {
|
||||
type: 'array',
|
||||
description: 'Ids of segments to use for this strategy',
|
||||
example: [1, 2],
|
||||
items: {
|
||||
type: 'number',
|
||||
},
|
||||
|
@ -4,30 +4,49 @@ import { parametersSchema } from './parameters-schema';
|
||||
|
||||
export const featureStrategySchema = {
|
||||
$id: '#/components/schemas/featureStrategySchema',
|
||||
description:
|
||||
'A singles activation strategy configuration schema for a feature',
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
required: ['name'],
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
description: 'A uuid for the feature strategy',
|
||||
example: '6b5157cb-343a-41e7-bfa3-7b4ec3044840',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
description: 'The name or type of strategy',
|
||||
example: 'flexibleRollout',
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
description: 'A descriptive title for the strategy',
|
||||
example: 'Gradual Rollout 25-Prod',
|
||||
nullable: true,
|
||||
},
|
||||
featureName: {
|
||||
type: 'string',
|
||||
description: 'The name or feature the strategy is attached to',
|
||||
example: 'myAwesomeFeature',
|
||||
},
|
||||
sortOrder: {
|
||||
type: 'number',
|
||||
description: 'The order of the strategy in the list',
|
||||
example: 9999,
|
||||
},
|
||||
segments: {
|
||||
type: 'array',
|
||||
description: 'A list of segment ids attached to the strategy',
|
||||
example: [1, 2],
|
||||
items: {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
constraints: {
|
||||
type: 'array',
|
||||
description: 'A list of the constraints attached to the strategy',
|
||||
items: {
|
||||
$ref: '#/components/schemas/constraintSchema',
|
||||
},
|
||||
|
@ -4,6 +4,7 @@ import { StrategySchema } from './strategy-schema';
|
||||
test('strategySchema', () => {
|
||||
const data: StrategySchema = {
|
||||
description: '',
|
||||
title: '',
|
||||
name: '',
|
||||
displayName: '',
|
||||
editable: false,
|
||||
@ -25,4 +26,9 @@ test('strategySchema', () => {
|
||||
expect(
|
||||
validateSchema('#/components/schemas/strategySchema', {}),
|
||||
).toMatchSnapshot();
|
||||
|
||||
const { title, ...noTitle } = { ...data };
|
||||
expect(
|
||||
validateSchema('#/components/schemas/strategySchema', noTitle),
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
@ -2,6 +2,8 @@ import { FromSchema } from 'json-schema-to-ts';
|
||||
|
||||
export const strategySchema = {
|
||||
$id: '#/components/schemas/strategySchema',
|
||||
description:
|
||||
'The [activation strategy](https://docs.getunleash.io/reference/activation-strategies) schema',
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
required: [
|
||||
@ -13,39 +15,60 @@ export const strategySchema = {
|
||||
'parameters',
|
||||
],
|
||||
properties: {
|
||||
title: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
description: 'An optional title for the strategy',
|
||||
example: 'GradualRollout - Prod25',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
description: 'The name or type of the strategy',
|
||||
example: 'flexibleRollout',
|
||||
},
|
||||
displayName: {
|
||||
type: 'string',
|
||||
description: 'A human friendly name for the strategy',
|
||||
example: 'Gradual Rollout',
|
||||
nullable: true,
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
description: 'A short description for the strategy',
|
||||
example: 'Gradual rollout to logged in users',
|
||||
},
|
||||
editable: {
|
||||
type: 'boolean',
|
||||
description: 'Determines whether the strategy allows for editing',
|
||||
example: true,
|
||||
},
|
||||
deprecated: {
|
||||
type: 'boolean',
|
||||
description: '',
|
||||
example: true,
|
||||
},
|
||||
parameters: {
|
||||
type: 'array',
|
||||
description: 'A list of relevant parameters for each strategy',
|
||||
items: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
example: 'percentage',
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
example: 'percentage',
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
example: 'Gradual rollout to logged in users',
|
||||
},
|
||||
required: {
|
||||
type: 'boolean',
|
||||
example: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1,17 +1,5 @@
|
||||
import { IUnleashConfig } from '../types/option';
|
||||
import { IFlagResolver, IUnleashStores } from '../types';
|
||||
import { Logger } from '../logger';
|
||||
import BadDataError from '../error/bad-data-error';
|
||||
import NameExistsError from '../error/name-exists-error';
|
||||
import InvalidOperationError from '../error/invalid-operation-error';
|
||||
import { FOREIGN_KEY_VIOLATION } from '../error/db-error';
|
||||
import {
|
||||
constraintSchema,
|
||||
featureMetadataSchema,
|
||||
nameSchema,
|
||||
variantsArraySchema,
|
||||
} from '../schema/feature-schema';
|
||||
import {
|
||||
EnvironmentVariantEvent,
|
||||
FEATURE_UPDATED,
|
||||
FeatureArchivedEvent,
|
||||
FeatureChangeProjectEvent,
|
||||
@ -25,17 +13,30 @@ import {
|
||||
FeatureStrategyRemoveEvent,
|
||||
FeatureStrategyUpdateEvent,
|
||||
FeatureVariantEvent,
|
||||
EnvironmentVariantEvent,
|
||||
} from '../types/events';
|
||||
IEventStore,
|
||||
IFeatureTagStore,
|
||||
IFeatureToggleStore,
|
||||
IFlagResolver,
|
||||
IProjectStore,
|
||||
IUnleashConfig,
|
||||
IUnleashStores,
|
||||
} from '../types';
|
||||
import { Logger } from '../logger';
|
||||
import BadDataError from '../error/bad-data-error';
|
||||
import NameExistsError from '../error/name-exists-error';
|
||||
import InvalidOperationError from '../error/invalid-operation-error';
|
||||
import { FOREIGN_KEY_VIOLATION } from '../error';
|
||||
import {
|
||||
constraintSchema,
|
||||
featureMetadataSchema,
|
||||
nameSchema,
|
||||
variantsArraySchema,
|
||||
} from '../schema/feature-schema';
|
||||
import NotFoundError from '../error/notfound-error';
|
||||
import {
|
||||
FeatureConfigurationClient,
|
||||
IFeatureStrategiesStore,
|
||||
} from '../types/stores/feature-strategies-store';
|
||||
import { IEventStore } from '../types/stores/event-store';
|
||||
import { IProjectStore } from '../types/stores/project-store';
|
||||
import { IFeatureTagStore } from '../types/stores/feature-tag-store';
|
||||
import { IFeatureToggleStore } from '../types/stores/feature-toggle-store';
|
||||
import {
|
||||
FeatureToggle,
|
||||
FeatureToggleDTO,
|
||||
@ -354,6 +355,7 @@ class FeatureToggleService {
|
||||
return {
|
||||
id: featureStrategy.id,
|
||||
name: featureStrategy.strategyName,
|
||||
title: featureStrategy.title,
|
||||
constraints: featureStrategy.constraints || [],
|
||||
parameters: featureStrategy.parameters,
|
||||
segments: segments.map((segment) => segment.id) ?? [],
|
||||
@ -415,6 +417,7 @@ class FeatureToggleService {
|
||||
const newFeatureStrategy =
|
||||
await this.featureStrategiesStore.createStrategyFeatureEnv({
|
||||
strategyName: strategyConfig.name,
|
||||
title: strategyConfig.title,
|
||||
constraints: strategyConfig.constraints || [],
|
||||
parameters: strategyConfig.parameters || {},
|
||||
sortOrder: strategyConfig.sortOrder,
|
||||
@ -598,6 +601,7 @@ class FeatureToggleService {
|
||||
* @param id - strategy id
|
||||
* @param context - Which context does this strategy live in (projectId, featureName, environment)
|
||||
* @param createdBy - Which user does this strategy belong to
|
||||
* @param user
|
||||
*/
|
||||
async deleteStrategy(
|
||||
id: string,
|
||||
@ -689,6 +693,7 @@ class FeatureToggleService {
|
||||
* @param featureName
|
||||
* @param archived - return archived or non archived toggles
|
||||
* @param projectId - provide if you're requesting the feature in the context of a specific project.
|
||||
* @param userId
|
||||
*/
|
||||
async getFeature({
|
||||
featureName,
|
||||
@ -1007,6 +1012,7 @@ class FeatureToggleService {
|
||||
constraints: strategy.constraints || [],
|
||||
parameters: strategy.parameters,
|
||||
segments: [],
|
||||
title: strategy.title,
|
||||
};
|
||||
|
||||
if (segments && segments.length > 0) {
|
||||
|
@ -5,6 +5,7 @@ const strategySchema = joi
|
||||
.object()
|
||||
.keys({
|
||||
name: nameType,
|
||||
title: joi.string().allow(null).allow('').optional(),
|
||||
editable: joi.boolean().default(true),
|
||||
deprecated: joi.boolean().default(false),
|
||||
description: joi.string().allow(null).allow('').optional(),
|
||||
|
@ -27,6 +27,7 @@ export interface IStrategyConfig {
|
||||
segments?: number[];
|
||||
parameters?: { [key: string]: string };
|
||||
sortOrder?: number;
|
||||
title?: string | null;
|
||||
}
|
||||
export interface IFeatureStrategy {
|
||||
id: string;
|
||||
@ -39,6 +40,7 @@ export interface IFeatureStrategy {
|
||||
constraints: IConstraint[];
|
||||
createdAt?: Date;
|
||||
segments?: number[];
|
||||
title?: string | null;
|
||||
}
|
||||
|
||||
export interface FeatureToggleDTO {
|
||||
|
@ -7,6 +7,7 @@ export interface IStrategy {
|
||||
parameters: object[];
|
||||
deprecated: boolean;
|
||||
displayName: string;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export interface IEditableStrategy {
|
||||
@ -14,6 +15,7 @@ export interface IEditableStrategy {
|
||||
description?: string;
|
||||
parameters: object;
|
||||
deprecated: boolean;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export interface IMinimalStrategy {
|
||||
@ -21,6 +23,7 @@ export interface IMinimalStrategy {
|
||||
description?: string;
|
||||
editable?: boolean;
|
||||
parameters?: any[];
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export interface IStrategyImport {
|
||||
@ -31,6 +34,7 @@ export interface IStrategyImport {
|
||||
builtIn?: boolean;
|
||||
sortOrder?: number;
|
||||
displayName?: string;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export interface IMinimalStrategyRow {
|
||||
@ -38,6 +42,7 @@ export interface IMinimalStrategyRow {
|
||||
description?: string;
|
||||
editable?: boolean;
|
||||
parameters?: string;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export interface IStrategyStore extends Store<IStrategy, string> {
|
||||
|
@ -12,6 +12,7 @@ export type SegmentForEvaluation = {
|
||||
|
||||
export interface StrategyTransportInterface {
|
||||
name: string;
|
||||
title?: string;
|
||||
parameters: any;
|
||||
constraints: Constraint[];
|
||||
segments?: number[];
|
||||
@ -62,7 +63,7 @@ export class Strategy {
|
||||
};
|
||||
}
|
||||
|
||||
const mappedConstraints = [];
|
||||
const mappedConstraints: PlaygroundConstraintSchema[] = [];
|
||||
for (const constraint of constraints) {
|
||||
if (constraint) {
|
||||
mappedConstraints.push({
|
||||
|
84
src/migrations/20230412125618-add-title-to-strategy.js
Normal file
84
src/migrations/20230412125618-add-title-to-strategy.js
Normal file
@ -0,0 +1,84 @@
|
||||
'use strict';
|
||||
|
||||
exports.up = function (db, callback) {
|
||||
db.runSql(
|
||||
`
|
||||
ALTER TABLE strategies ADD COLUMN IF NOT EXISTS title TEXT;
|
||||
ALTER TABLE feature_strategies ADD COLUMN IF NOT EXISTS title TEXT;
|
||||
|
||||
CREATE OR REPLACE VIEW features_view AS
|
||||
SELECT
|
||||
features.name as name,
|
||||
features.description as description,
|
||||
features.type as type,
|
||||
features.project as project,
|
||||
features.stale as stale,
|
||||
feature_environments.variants as variants,
|
||||
features.impression_data as impression_data,
|
||||
features.created_at as created_at,
|
||||
features.last_seen_at as last_seen_at,
|
||||
features.archived_at as archived_at,
|
||||
feature_environments.enabled as enabled,
|
||||
feature_environments.environment as environment,
|
||||
environments.name as environment_name,
|
||||
environments.type as environment_type,
|
||||
environments.sort_order as environment_sort_order,
|
||||
feature_strategies.id as strategy_id,
|
||||
feature_strategies.strategy_name as strategy_name,
|
||||
feature_strategies.parameters as parameters,
|
||||
feature_strategies.constraints as constraints,
|
||||
feature_strategies.sort_order as sort_order,
|
||||
fss.segment_id as segments,
|
||||
feature_strategies.title as strategy_title
|
||||
FROM
|
||||
features
|
||||
LEFT JOIN feature_environments ON feature_environments.feature_name = features.name
|
||||
LEFT JOIN feature_strategies ON feature_strategies.feature_name = feature_environments.feature_name
|
||||
and feature_strategies.environment = feature_environments.environment
|
||||
LEFT JOIN environments ON feature_environments.environment = environments.name
|
||||
LEFT JOIN feature_strategy_segment as fss ON fss.feature_strategy_id = feature_strategies.id;
|
||||
`,
|
||||
callback,
|
||||
);
|
||||
};
|
||||
|
||||
exports.down = function (db, callback) {
|
||||
db.runSql(
|
||||
`
|
||||
ALTER TABLE strategies DROP COLUMN IF EXISTS title;
|
||||
ALTER TABLE feature_strategies DROP COLUMN IF EXISTS title;
|
||||
DROP VIEW features_view;
|
||||
CREATE VIEW features_view AS
|
||||
SELECT
|
||||
features.name as name,
|
||||
features.description as description,
|
||||
features.type as type,
|
||||
features.project as project,
|
||||
features.stale as stale,
|
||||
feature_environments.variants as variants,
|
||||
features.impression_data as impression_data,
|
||||
features.created_at as created_at,
|
||||
features.last_seen_at as last_seen_at,
|
||||
features.archived_at as archived_at,
|
||||
feature_environments.enabled as enabled,
|
||||
feature_environments.environment as environment,
|
||||
environments.name as environment_name,
|
||||
environments.type as environment_type,
|
||||
environments.sort_order as environment_sort_order,
|
||||
feature_strategies.id as strategy_id,
|
||||
feature_strategies.strategy_name as strategy_name,
|
||||
feature_strategies.parameters as parameters,
|
||||
feature_strategies.constraints as constraints,
|
||||
feature_strategies.sort_order as sort_order,
|
||||
fss.segment_id as segments
|
||||
FROM
|
||||
features
|
||||
LEFT JOIN feature_environments ON feature_environments.feature_name = features.name
|
||||
LEFT JOIN feature_strategies ON feature_strategies.feature_name = feature_environments.feature_name
|
||||
and feature_strategies.environment = feature_environments.environment
|
||||
LEFT JOIN environments ON feature_environments.environment = environments.name
|
||||
LEFT JOIN feature_strategy_segment as fss ON fss.feature_strategy_id = feature_strategies.id;
|
||||
`,
|
||||
callback,
|
||||
);
|
||||
};
|
@ -204,3 +204,54 @@ test('can update a exiting strategy with deprecated', async () => {
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
});
|
||||
|
||||
test('can create a strategy with a title', async () => {
|
||||
await app.request
|
||||
.post('/api/admin/strategies')
|
||||
.send({
|
||||
name: 'myCustomStrategyWithTitle',
|
||||
description: 'Best strategy ever.',
|
||||
parameters: [],
|
||||
title: 'This is the best strategy ever',
|
||||
})
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(201);
|
||||
|
||||
const { body: strategy } = await app.request.get(
|
||||
'/api/admin/strategies/myCustomStrategyWithTitle',
|
||||
);
|
||||
|
||||
expect(strategy.title).toBe('This is the best strategy ever');
|
||||
|
||||
strategy.description = 'A new desc';
|
||||
|
||||
return app.request
|
||||
.put('/api/admin/strategies/myCustomStrategyWithTitle')
|
||||
.send(strategy)
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
});
|
||||
|
||||
test('can update a strategy with a title', async () => {
|
||||
await app.request
|
||||
.post('/api/admin/strategies')
|
||||
.send({
|
||||
name: 'myCustomStrategy2',
|
||||
description: 'Best strategy ever.',
|
||||
parameters: [],
|
||||
})
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(201);
|
||||
|
||||
const { body: strategy } = await app.request.get(
|
||||
'/api/admin/strategies/myCustomStrategy2',
|
||||
);
|
||||
|
||||
strategy.title = 'This is the best strategy ever';
|
||||
|
||||
return app.request
|
||||
.put('/api/admin/strategies/myCustomStrategy2')
|
||||
.send(strategy)
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
});
|
||||
|
@ -936,27 +936,60 @@ exports[`should serve the OpenAPI spec 1`] = `
|
||||
"createFeatureStrategySchema": {
|
||||
"properties": {
|
||||
"constraints": {
|
||||
"description": "A list of the constraints attached to the strategy",
|
||||
"example": [
|
||||
{
|
||||
"caseInsensitive": false,
|
||||
"contextName": "appName",
|
||||
"inverted": false,
|
||||
"operator": "IN",
|
||||
"values": [
|
||||
"1",
|
||||
"2",
|
||||
],
|
||||
},
|
||||
],
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/constraintSchema",
|
||||
},
|
||||
"type": "array",
|
||||
},
|
||||
"name": {
|
||||
"description": "The name or type of strategy",
|
||||
"example": "flexibleRollout",
|
||||
"type": "string",
|
||||
},
|
||||
"parameters": {
|
||||
"$ref": "#/components/schemas/parametersSchema",
|
||||
"description": "An object containing the parameters for the strategy",
|
||||
"example": {
|
||||
"groupId": "some_new",
|
||||
"rollout": "25",
|
||||
"stickiness": "sessionId",
|
||||
},
|
||||
},
|
||||
"segments": {
|
||||
"description": "Ids of segments to use for this strategy",
|
||||
"example": [
|
||||
1,
|
||||
2,
|
||||
],
|
||||
"items": {
|
||||
"type": "number",
|
||||
},
|
||||
"type": "array",
|
||||
},
|
||||
"sortOrder": {
|
||||
"description": "The order of the strategy in the list",
|
||||
"example": 9999,
|
||||
"type": "number",
|
||||
},
|
||||
"title": {
|
||||
"description": "A descriptive title for the strategy",
|
||||
"example": "Gradual Rollout 25-Prod",
|
||||
"nullable": true,
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
@ -1628,34 +1661,55 @@ exports[`should serve the OpenAPI spec 1`] = `
|
||||
},
|
||||
"featureStrategySchema": {
|
||||
"additionalProperties": false,
|
||||
"description": "A singles activation strategy configuration schema for a feature",
|
||||
"properties": {
|
||||
"constraints": {
|
||||
"description": "A list of the constraints attached to the strategy",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/constraintSchema",
|
||||
},
|
||||
"type": "array",
|
||||
},
|
||||
"featureName": {
|
||||
"description": "The name or feature the strategy is attached to",
|
||||
"example": "myAwesomeFeature",
|
||||
"type": "string",
|
||||
},
|
||||
"id": {
|
||||
"description": "A uuid for the feature strategy",
|
||||
"example": "6b5157cb-343a-41e7-bfa3-7b4ec3044840",
|
||||
"type": "string",
|
||||
},
|
||||
"name": {
|
||||
"description": "The name or type of strategy",
|
||||
"example": "flexibleRollout",
|
||||
"type": "string",
|
||||
},
|
||||
"parameters": {
|
||||
"$ref": "#/components/schemas/parametersSchema",
|
||||
},
|
||||
"segments": {
|
||||
"description": "A list of segment ids attached to the strategy",
|
||||
"example": [
|
||||
1,
|
||||
2,
|
||||
],
|
||||
"items": {
|
||||
"type": "number",
|
||||
},
|
||||
"type": "array",
|
||||
},
|
||||
"sortOrder": {
|
||||
"description": "The order of the strategy in the list",
|
||||
"example": 9999,
|
||||
"type": "number",
|
||||
},
|
||||
"title": {
|
||||
"description": "A descriptive title for the strategy",
|
||||
"example": "Gradual Rollout 25-Prod",
|
||||
"nullable": true,
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
@ -3734,37 +3788,53 @@ Stats are divided into current and previous **windows**.
|
||||
},
|
||||
"strategySchema": {
|
||||
"additionalProperties": false,
|
||||
"description": "The [activation strategy](https://docs.getunleash.io/reference/activation-strategies) schema",
|
||||
"properties": {
|
||||
"deprecated": {
|
||||
"description": "",
|
||||
"example": true,
|
||||
"type": "boolean",
|
||||
},
|
||||
"description": {
|
||||
"description": "A short description for the strategy",
|
||||
"example": "Gradual rollout to logged in users",
|
||||
"type": "string",
|
||||
},
|
||||
"displayName": {
|
||||
"description": "A human friendly name for the strategy",
|
||||
"example": "Gradual Rollout",
|
||||
"nullable": true,
|
||||
"type": "string",
|
||||
},
|
||||
"editable": {
|
||||
"description": "Determines whether the strategy allows for editing",
|
||||
"example": true,
|
||||
"type": "boolean",
|
||||
},
|
||||
"name": {
|
||||
"description": "The name or type of the strategy",
|
||||
"example": "flexibleRollout",
|
||||
"type": "string",
|
||||
},
|
||||
"parameters": {
|
||||
"description": "A list of relevant parameters for each strategy",
|
||||
"items": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"description": {
|
||||
"example": "Gradual rollout to logged in users",
|
||||
"type": "string",
|
||||
},
|
||||
"name": {
|
||||
"example": "percentage",
|
||||
"type": "string",
|
||||
},
|
||||
"required": {
|
||||
"example": true,
|
||||
"type": "boolean",
|
||||
},
|
||||
"type": {
|
||||
"example": "percentage",
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
@ -3772,6 +3842,12 @@ Stats are divided into current and previous **windows**.
|
||||
},
|
||||
"type": "array",
|
||||
},
|
||||
"title": {
|
||||
"description": "An optional title for the strategy",
|
||||
"example": "GradualRollout - Prod25",
|
||||
"nullable": true,
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
|
@ -1,16 +1,17 @@
|
||||
import FeatureToggleService from '../../../lib/services/feature-toggle-service';
|
||||
import { createTestConfig } from '../../config/test-config';
|
||||
import dbInit from '../helpers/database-init';
|
||||
import { DEFAULT_ENV } from '../../../lib/util/constants';
|
||||
import { SegmentService } from '../../../lib/services/segment-service';
|
||||
import { FeatureStrategySchema } from '../../../lib/openapi/spec/feature-strategy-schema';
|
||||
import { DEFAULT_ENV } from '../../../lib/util';
|
||||
import {
|
||||
AccessService,
|
||||
GroupService,
|
||||
SegmentService,
|
||||
} from '../../../lib/services';
|
||||
import { FeatureStrategySchema } from '../../../lib/openapi';
|
||||
import User from '../../../lib/types/user';
|
||||
import { IConstraint, IVariant } from '../../../lib/types/model';
|
||||
import { AccessService } from '../../../lib/services/access-service';
|
||||
import { GroupService } from '../../../lib/services/group-service';
|
||||
import { IConstraint, IVariant, SKIP_CHANGE_REQUEST } from '../../../lib/types';
|
||||
import EnvironmentService from '../../../lib/services/environment-service';
|
||||
import { NoAccessError } from '../../../lib/error';
|
||||
import { SKIP_CHANGE_REQUEST } from '../../../lib/types';
|
||||
import { ISegmentService } from '../../../lib/segments/segment-service-interface';
|
||||
import { ChangeRequestAccessReadModel } from '../../../lib/features/change-request-access-service/sql-change-request-access-read-model';
|
||||
|
||||
@ -134,6 +135,7 @@ test('Should be able to get strategy by id', async () => {
|
||||
name: 'default',
|
||||
constraints: [],
|
||||
parameters: {},
|
||||
title: 'some-title',
|
||||
};
|
||||
await service.createFeatureToggle(
|
||||
projectId,
|
||||
|
Loading…
Reference in New Issue
Block a user